\newpage

Les espaces de noms -- *namespaces*
===================================

Nous avons vu un certain nombre de fonctionnalités offertes par le noyau Linux
pour limiter, autoriser ou contraindre différents usages des ressources de
notre machine.

Ces fonctionnalités sont très utiles pour éviter les dénis de service, mais nos
processus ne sont pas particulièrement isolés du reste du système. On aimerait
maintenant que nos processus n'aient pas accès à l'ensemble des fichiers, ne
puissent pas interagir avec les autres processus, avoir leur propre pile
réseau, ... Voyons maintenant les *namespaces* qui vont nous permettre de faire
cela.


Présentation des *namespaces*
-----------------------------

Les espaces de noms du noyau, que l'on appelle *namespaces*, permettent de
dupliquer certaines structures, habituellement considérées uniques pour le
noyau, dans le but qu'un groupe de processus soit isolé d'autres processus, sur
certains aspects de l'environnement dans lequel il s'exécute.

On en dénombre huit (le dernier ayant été ajouté dans Linux 5.6) : `cgroup`,
`IPC`, `network`, `mount`, `PID`, `time`, `user` et `UTS`.

La notion d'espace de noms est relativement nouvelle et a été intégrée
progressivement au sein du noyau Linux. Aussi, toutes les structures ne sont
pas encore *containerisables* : le document fondateur[^NSDOC] parle ainsi
d'isoler les journaux d'événements ou encore les modules de sécurité (LSM, tels
que AppArmor, SELinux, Yama, ...).

[^NSDOC]: <https://www.kernel.org/doc/ols/2006/ols2006v1-pages-101-112.pdf>

Commençons par passer en revue rapidement les différents *namespaces*.

#### L'espace de noms `mount`  {.unnumbered #mount-ns}

Depuis Linux 2.4.19.

Cet espace de noms dissocie la liste des points de montage.

Chaque processus appartenant à un *namespace mount* différent peut monter,
démonter et réorganiser à sa guise les points de montage, sans que cela n'ait
d'impact sur les processus hors de cet espace de noms. Une partition ne sera
donc pas nécessairement démontée après un appel à `umount(2)`, elle le sera
lorsqu'elle aura effectivement été démontée de chaque *namespace mount* dans
lequel elle était montée.

Il s'agit d'une version améliorée de nos bons vieux `chroot`, puisqu'il n'est
plus possible de s'en échapper en remontant l'arborescence.


#### L'espace de noms `UTS`  {.unnumbered #uts-ns}

Depuis Linux 2.6.19.

Cet espace de noms isole le nom de machine et son domaine NIS.


#### L'espace de noms `IPC`  {.unnumbered #ipc-ns}

Depuis Linux 2.6.19.

Cet espace de noms isole les objets IPC et les files de messages POSIX.

Une fois le *namespace* attaché à un processus, il ne peut alors plus parler
qu'avec les autres processus de son espace de noms (lorsque ceux-ci passent par
l'API IPC du noyau).


#### L'espace de noms `PID` {.unnumbered}

Depuis Linux 2.6.24.

Cet espace de noms isole la liste des processus et virtualise leurs numéros.

Une fois dans un espace, le processus ne voit que le sous-arbre de processus
également attachés à son espace. Il s'agit d'un sous-ensemble de l'arbre global
de PID : les processus de tous les PID *namespaces* apparaissent donc dans
l'arbre initial.

Pour chaque nouvel espace de noms de processus, une nouvelle numérotation est
initiée. Ainsi, le premier processus de cet espace porte le numéro 1 et aura
les mêmes propriétés que le processus `init` usuel\ ; entre autres, si un
processus est rendu orphelin dans ce *namespace*, il devient un fils de ce
processus, et non un fils de l'`init` de l'arbre global.


#### L'espace de nom `network` {.unnumbered}

Depuis Linux 2.6.29.

Cet espace de noms fournit une isolation pour toutes les ressources associées
aux réseaux : les interfaces, les piles protocolaires IPv4 et IPv6, les tables
de routage, règles pare-feu, ports numérotés, etc.

Une interface réseau (`eth0`, `wlan0`, ...) ne peut se trouver que dans un seul
espace de noms à la fois. Il est par contre possible de les déplacer.

Lorsque le *namespace* est libéré (généralement lorsque le dernier processus
attaché à cet espace de noms se termine), les interfaces qui le composent sont
ramenées dans l'espace initial/racine (et non pas dans l'espace parent, en cas
d'imbrication).


#### L'espace de noms `user` {.unnumbered}

Depuis Linux 3.8.

Cet espace de noms isole la liste des utilisateurs, des groupes, leurs
identifiants, les *capabilities*, la racine et le trousseau de clefs du noyau.

La principale caractéristique est que les identifiants d'utilisateur et de
groupe pour un processus peuvent être différents entre l'intérieur et
l'extérieur de l'espace de noms. Il est donc possible, alors que l'on est un
simple utilisateur à l'extérieur du *namespace*, d'avoir l'UID 0 dans le
conteneur.


#### L'espace de noms `cgroup`  {.unnumbered #cgroup-ns}

Depuis Linux 4.6.

Cet espace de noms filtre l'arborescence des *Control Group* en changeant la
racine vue par le processus. Au sein d'un *namespace* `cgroup`, la racine vue
correspond en fait à un sous-groupe de l'arborescence globale.

Ainsi, un processus dans un *CGroup* *namespace* ne peut pas voir le contenu
des sous-groupes parents (pouvant laisser fuiter des informations sur le reste
du système). Cela peut également permettre de faciliter la migration de
processus (d'un système à un autre) : l'arborescence des *cgroups* n'a alors
plus d'importance car le processus ne voit que son groupe.


#### L'espace de noms `time`  {.unnumbered #time-ns}

Depuis Linux 5.6.

Avec cet espace de noms, il n'est pas possible de virtualiser l'heure d'un de
nos conteneurs (on peut seulement changer le fuseau horaire, puisqu'ils sont
gérés par la `libc`). Les horloges virtualisées avec ce *namespace* sont les
compteurs `CLOCK_MONOTONIC` et `CLOCK_BOOTTIME`.

Lorsque l'on souhaite mesurer un écoulement de temps, la méthode naïve consiste
à enregistrer l'heure au départ de notre opération, puis à faire une
soustraction avec l'heure de fin. Cette technique fonctionne bien, à partir du
moment où l'on est sûr que l'horloge ne remontera pas dans le temps, parce
qu'elle se synchronise ou que le changement d'heure été/hiver
intervient, ... Pour palier ces situations imprévisibles, le noyau expose une
horloge dite monotone (`CLOCK_MONOTONIC`) : cette horloge démarre à un entier
abstrait et s'incrèmente chaque seconde qui passe, sans jamais sauter de
secondes, ni revenir en arrière. C'est une horloge fiable pour calculer des
intervalles de temps.

De la même manière `CLOCK_BOOTTIME` mesure le temps qui s'écoule, mais prend en
compte les moments où la machine est en veille (alors que `CLOCK_MONOTONIC` ne
compte que les moments où la machine est en éveil).

Étant donné l'usage de ces deux horloges, en cas de migration d'un processus
d'une machine à une autre, il convient de recopier l'état des horloges
monotones qu'il utilise, afin que ses calculs ne soient pas chamboulés.
