tuto4 ready
This commit is contained in:
parent
b5de41662b
commit
e928733d61
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
include ../pandoc-opts.mk
|
include ../pandoc-opts.mk
|
||||||
|
|
||||||
SOURCES_TUTO = tutorial.md mount.md intro.md setup.md howto.md setns.md setns-samples.md cmpns.md lifetime.md namespaces_more.md networkns.md pidns.md mountns.md userns.md docker-exec.md rendu.md
|
SOURCES_TUTO = tutorial.md mount.md intro.md setup.md lsns.md howto.md setns.md setns-samples.md setns-pid.md cmpns.md lifetime.md namespaces_more.md ../new-page.md networkns.md ../new-page.md pidns.md ../new-page.md mountns.md ../new-page.md userns.md docker-exec.md rendu.md
|
||||||
|
|
||||||
|
|
||||||
all: tutorial.pdf
|
all: tutorial.pdf
|
||||||
|
@ -5,20 +5,23 @@
|
|||||||
Les *namespaces* d'un programme sont exposés sous forme de liens symboliques
|
Les *namespaces* d'un programme sont exposés sous forme de liens symboliques
|
||||||
dans le répertoire `/proc/<PID>/ns/`.
|
dans le répertoire `/proc/<PID>/ns/`.
|
||||||
|
|
||||||
Deux programmes qui partagent un même *namespace* auront un lien vers la même
|
Deux programmes qui partagent un même *namespace* auront un lien vers le même
|
||||||
structure de données.
|
*inode*.
|
||||||
|
|
||||||
Écrivons un script ou un programme, `cmpns`, permettant de déterminer si deux
|
Écrivons un script, `cmpns`, permettant de déterminer si deux programmes
|
||||||
programmes s'exécutent dans les mêmes *namespaces*. On ignorera les
|
s'exécutent dans les mêmes *namespaces*. On ignorera les *namespace*s
|
||||||
*namespace*s `*_for_children`, car ils ne font pas partie du cycle d'exécution
|
`*_for_children`, car ils ne font pas partie du cycle d'exécution que l'on
|
||||||
que l'on cherche à comparer.
|
cherche à comparer.
|
||||||
|
|
||||||
|
En shell, vous aurez besoin de `grep(1)` et de `readlink(1)`.
|
||||||
|
|
||||||
|
|
||||||
#### Exemples {.unnumbered}
|
#### Exemples {.unnumbered}
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```
|
```
|
||||||
42sh$ ./cmpns $(pgrep influxdb) $(pgrep init)
|
42sh$ docker run -d influxdb
|
||||||
|
42sh$ ./cmpns $(pgrep influxd) $(pgrep init)
|
||||||
- cgroup: differ
|
- cgroup: differ
|
||||||
- ipc: differ
|
- ipc: differ
|
||||||
- mnt: differ
|
- mnt: differ
|
||||||
|
@ -1,28 +1,27 @@
|
|||||||
\newpage
|
|
||||||
|
|
||||||
Utiliser les *namespace*s
|
Utiliser les *namespace*s
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
### S'isoler dans un nouveau *namespace*
|
### S'isoler dans un nouveau *namespace*
|
||||||
|
|
||||||
Si l'on voit l'isolation procurée par les *namespace*s en parallèle avec les
|
Si l'on voit l'isolation procurée par les *namespace*s comme des machines
|
||||||
machines virtuelles, on peut se dire qu'il suffit d'exécuter un appel système
|
virtuelles, on peut se dire qu'il suffit d'exécuter un appel système pour
|
||||||
pour arriver dans un conteneur bien isolé. Cependant, le choix fait par les
|
arriver dans un conteneur bien isolé. Cependant, le choix fait par les
|
||||||
développeurs de Linux a été de laisser le choix des espaces de noms dont on veut
|
développeurs de Linux a été de laisser le choix des espaces de noms dont on
|
||||||
se dissocier.
|
veut se dissocier.
|
||||||
|
|
||||||
L'intérêt principal de cette approche, exploitée bien après la mise en avant du
|
L'intérêt principal de cette approche, exploitée bien après la mise en avant du
|
||||||
concept, est que l'utilisation des *namespace*s ne se limite pas seulement à
|
concept, est que l'utilisation des *namespace*s ne se limite pas seulement à
|
||||||
des machines virtuelles légères. On retrouve ainsi dans Google Chrome de
|
des machines virtuelles légères. On retrouve ainsi dans Google Chrome et
|
||||||
nombreuses utilisations des *namespace*s dans le simple but d'accroître la
|
Firefox de nombreuses utilisations des *namespace*s dans le simple but
|
||||||
sécurité de leur navigateur. Ainsi, les threads de rendu n'ont pas accès au
|
d'accroître la sécurité de leur navigateur. Ainsi, les *threads* de rendu n'ont
|
||||||
réseau et sont cloisonnés de manière transparente pour l'utilisateur.
|
pas accès au réseau et sont cloisonnés de manière transparente pour
|
||||||
|
l'utilisateur.
|
||||||
|
|
||||||
Nous allons voir dans cette partie plusieurs méthodes pour utiliser ces espaces
|
Nous allons voir dans cette partie plusieurs méthodes pour utiliser ces espaces
|
||||||
de noms.
|
de noms.
|
||||||
|
|
||||||
|
|
||||||
#### Dans son shell
|
#### Dans son shell\
|
||||||
|
|
||||||
De la même manière que l'on peut utiliser l'appel système `chroot(2)` depuis un
|
De la même manière que l'on peut utiliser l'appel système `chroot(2)` depuis un
|
||||||
shell via la commande `chroot(1)`, la commande `unshare(1)` permet de faire le
|
shell via la commande `chroot(1)`, la commande `unshare(1)` permet de faire le
|
||||||
@ -55,16 +54,16 @@ Nous avons pu ici modifier le nom de la machine, sans que cela n'affecte notre
|
|||||||
machine hôte.
|
machine hôte.
|
||||||
|
|
||||||
|
|
||||||
#### Les appels système
|
#### Les appels système\
|
||||||
|
|
||||||
L'appel système par excellence pour contrôler l'isolation d'un nouveau
|
L'appel système par excellence pour contrôler l'isolation d'un nouveau
|
||||||
processus est `clone(2)`.
|
processus est `clone(2)`.
|
||||||
|
|
||||||
Ce *syscall*, propre à Linux, crée habituellement un nouveau processus (mais
|
Ce *syscall*, propre à Linux, crée habituellement un nouveau processus enfant
|
||||||
aussi des threads) enfant de notre processus courant, comme `fork(2)` (qui lui
|
de notre processus courant (mais il peut aussi créer des *threads*, on va voir
|
||||||
est un appel système POSIX) mais prend en plus de nombreux
|
qu'il fait beaucoup de choses), comme `fork(2)` (qui lui est un appel système
|
||||||
paramètres. L'isolement ou non du processus se fait en fonction des *flags* qui
|
POSIX). Mais il prend en plus de nombreux paramètres. L'isolement ou non du
|
||||||
sont passés :
|
processus se fait en fonction des *flags* qui sont passés. On retrouve donc :
|
||||||
|
|
||||||
* `CLONE_NEWNS`,
|
* `CLONE_NEWNS`,
|
||||||
* `CLONE_NEWUTS`,
|
* `CLONE_NEWUTS`,
|
||||||
@ -75,12 +74,29 @@ sont passés :
|
|||||||
* `CLONE_NEWCGROUP`,
|
* `CLONE_NEWCGROUP`,
|
||||||
* `CLONE_NEWTIME`.
|
* `CLONE_NEWTIME`.
|
||||||
|
|
||||||
|
::::: {.more}
|
||||||
|
|
||||||
|
Le nom du *flag* `CLONE_NEWNS` est historique et assez peu explicite
|
||||||
|
contrairement aux autres : il désigne en fait l'espace de nom `mount`.
|
||||||
|
|
||||||
|
Au départ, les *namespace*s ont étés pensés pour former un tout : une couche
|
||||||
|
d'isolation complète pour les processus. Mais lors des développements suivants,
|
||||||
|
il s'est avéré pratique de pouvoir choisir finement de quels aspects on
|
||||||
|
souhaitait se dissocier.
|
||||||
|
|
||||||
|
C'est ainsi que pour chaque nouveau *namespace*, un nouveau *flag* est
|
||||||
|
introduit.
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
On peut bien entendu cumuler un ou plusieurs de ces *flags*, et les combiner
|
On peut bien entendu cumuler un ou plusieurs de ces *flags*, et les combiner
|
||||||
avec d'autres attendus par `clone(2)`.
|
avec d'autres attendus par `clone(2)`.
|
||||||
|
|
||||||
Ces mêmes *flags* sont utilisés lors des appels à `unshare(2)` ou `setns(2)`,
|
Ces mêmes *flags* sont utilisés lors des appels à `unshare(2)` ou `setns(2)`,
|
||||||
que nous verrons plus tard.
|
que nous verrons plus tard.
|
||||||
|
|
||||||
|
::::: {.code}
|
||||||
|
|
||||||
Pour créer un nouveau processus qui sera à la fois dans un nouvel espace de
|
Pour créer un nouveau processus qui sera à la fois dans un nouvel espace de
|
||||||
noms réseau et dans un nouveau *namespace* `cgroup`, on écrirait un code C
|
noms réseau et dans un nouveau *namespace* `cgroup`, on écrirait un code C
|
||||||
semblable à :
|
semblable à :
|
||||||
@ -105,21 +121,34 @@ pid_t pid = clone(do_execvp, // First function executed by child
|
|||||||
Dans cet exemple, le processus fils créé disposera d'un nouvel espace de noms
|
Dans cet exemple, le processus fils créé disposera d'un nouvel espace de noms
|
||||||
pour les *CGroups* et disposera d'une nouvelle pile réseau.
|
pour les *CGroups* et disposera d'une nouvelle pile réseau.
|
||||||
|
|
||||||
Un exemple complet d'utilisation de `clone(2)` et du *namespace* `UTS` est
|
::::: {.question}
|
||||||
donné dans le `man` de l'appel système.
|
|
||||||
|
#### Quel est le rôle du *flag* `SIGCHLD` ? {-}
|
||||||
|
\
|
||||||
|
|
||||||
|
Lorsque l'on crée un nouveau processus, on ajoute l'option `SIGCHLD` afin
|
||||||
|
d'être notifié par signal lorsque notre processus fil a terminé son
|
||||||
|
exécution. Cela permet d'être réveillé de notre `wait(2)`.
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
|
L'appel système `clone(2)` va donc créer un nouveau processus, ou un nouveau
|
||||||
|
*thread*, mais parfois on souhaite juste isoler notre processus actuel.
|
||||||
|
|
||||||
|
|
||||||
##### `unshare`\
|
##### `unshare`\
|
||||||
|
|
||||||
L'appel système `clone(2)` va créer un nouveau processus, ou un nouveau
|
Lorsque l'on souhaite faire entrer notre processus courant ou notre *thread*
|
||||||
thread. Parfois, on souhaite faire entrer notre processus ou thread en cours
|
dans un nouvel espace de noms, on peut utiliser l'appel système
|
||||||
dans un nouvel espace de noms. Dans ce cas, on utilisera l'appel système
|
|
||||||
`unshare(2)`. Celui-ci prend uniquement en argument une liste de *flags* des
|
`unshare(2)`. Celui-ci prend uniquement en argument une liste de *flags* des
|
||||||
*namespace*s dont on souhaite se dissocier.
|
*namespace*s dont on souhaite se dissocier.
|
||||||
|
|
||||||
::::: {.warning}
|
::::: {.warning}
|
||||||
Le comportement de `unshare(2)` (ou `unshare(3)`) avec les *namespace*s *PID*
|
|
||||||
et *Time* n'est pas celui que l'on peut attendre !
|
**Le comportement de `unshare(2)` (ou `unshare(3)`) avec les *namespace*s *PID*
|
||||||
|
et *Time* n'est pas celui que l'on peut attendre !**\
|
||||||
|
|
||||||
En effet, après avoir appelé `unshare`, le processus reste tout de même dans
|
En effet, après avoir appelé `unshare`, le processus reste tout de même dans
|
||||||
son *namespace* *Time* ou *PID* d'origine, seuls ses enfants (après un appel à
|
son *namespace* *Time* ou *PID* d'origine, seuls ses enfants (après un appel à
|
||||||
@ -135,4 +164,8 @@ peuvent être différents dans le dossier `/proc/<PID>/ns`.
|
|||||||
On ne remarque pas cette bizarrerie avec `clone(2)`, car il crée déjà un
|
On ne remarque pas cette bizarrerie avec `clone(2)`, car il crée déjà un
|
||||||
nouveau processus, son PID et sa `CLOCK_MONOTONIC` sont directement à la bonne
|
nouveau processus, son PID et sa `CLOCK_MONOTONIC` sont directement à la bonne
|
||||||
valeur dès l'exécution de la fonction `fn`.
|
valeur dès l'exécution de la fonction `fn`.
|
||||||
|
|
||||||
:::::
|
:::::
|
||||||
|
|
||||||
|
Nous avons vu comment créer et nous dissocier d'un espace de nom. Maintenant
|
||||||
|
voyons comment en rejoindre un déjà existant.
|
||||||
|
@ -10,17 +10,18 @@ notre machine.
|
|||||||
Ces fonctionnalités sont très utiles pour éviter les dénis de service, mais nos
|
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
|
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
|
maintenant que nos processus n'aient pas accès à l'ensemble des fichiers, ne
|
||||||
puissent pas interagir avec les autres processus, avoir sa propre pile
|
puissent pas interagir avec les autres processus, avoir leur propre pile
|
||||||
réseau, ... Voyons maintenant les *namespaces* qui vont nous permettre de faire
|
réseau, ... Voyons maintenant les *namespaces* qui vont nous permettre de faire
|
||||||
cela.
|
cela.
|
||||||
|
|
||||||
|
|
||||||
Initiation rapide
|
Présentation des *namespaces*
|
||||||
-----------------
|
-----------------------------
|
||||||
|
|
||||||
Les espaces de noms du noyau, que l'on appelle *namespaces*, permettent de
|
Les espaces de noms du noyau, que l'on appelle *namespaces*, permettent de
|
||||||
dupliquer certaines structures, habituellement considérées uniques pour le
|
dupliquer certaines structures, habituellement considérées uniques pour le
|
||||||
noyau, dans le but de les isoler d'un groupe de processus à un autre.
|
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`,
|
On en dénombre huit (le dernier ayant été ajouté dans Linux 5.6) : `cgroup`,
|
||||||
`IPC`, `network`, `mount`, `PID`, `time`, `user` et `UTS`.
|
`IPC`, `network`, `mount`, `PID`, `time`, `user` et `UTS`.
|
||||||
@ -33,12 +34,13 @@ que AppArmor, SELinux, Yama, ...).
|
|||||||
|
|
||||||
[^NSDOC]: <https://www.kernel.org/doc/ols/2006/ols2006v1-pages-101-112.pdf>
|
[^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}
|
#### L'espace de noms `mount` {.unnumbered #mount-ns}
|
||||||
|
|
||||||
Depuis Linux 2.4.19.
|
Depuis Linux 2.4.19.
|
||||||
|
|
||||||
Cet espace de noms isole la liste des points de montage.
|
Cet espace de noms dissocie la liste des points de montage.
|
||||||
|
|
||||||
Chaque processus appartenant à un *namespace mount* différent peut monter,
|
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émonter et réorganiser à sa guise les points de montage, sans que cela n'ait
|
||||||
@ -47,6 +49,9 @@ 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
|
lorsqu'elle aura effectivement été démontée de chaque *namespace mount* dans
|
||||||
lequel elle était montée.
|
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}
|
#### L'espace de noms `UTS` {.unnumbered #uts-ns}
|
||||||
|
|
||||||
@ -136,7 +141,7 @@ Depuis Linux 5.6.
|
|||||||
|
|
||||||
Avec cet espace de noms, il n'est pas possible de virtualiser l'heure d'un de
|
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
|
nos conteneurs (on peut seulement changer le fuseau horaire, puisqu'ils sont
|
||||||
gérés par la `libc`). Les horloges virtualisations avec ce *namespace* sont les
|
gérés par la `libc`). Les horloges virtualisées avec ce *namespace* sont les
|
||||||
compteurs `CLOCK_MONOTONIC` et `CLOCK_BOOTTIME`.
|
compteurs `CLOCK_MONOTONIC` et `CLOCK_BOOTTIME`.
|
||||||
|
|
||||||
Lorsque l'on souhaite mesurer un écoulement de temps, la méthode naïve consiste
|
Lorsque l'on souhaite mesurer un écoulement de temps, la méthode naïve consiste
|
||||||
|
@ -30,6 +30,7 @@ obtenir un descripteur de fichier valide vers le *namespace* (pour passer à
|
|||||||
|
|
||||||
::::: {.question}
|
::::: {.question}
|
||||||
#### Faire persister un *namespace* ? {-}
|
#### Faire persister un *namespace* ? {-}
|
||||||
|
\
|
||||||
|
|
||||||
Il n'est pas possible de faire persister un espace de noms d'un reboot à
|
Il n'est pas possible de faire persister un espace de noms d'un reboot à
|
||||||
l'autre.\
|
l'autre.\
|
||||||
|
119
tutorial/4/lsns.md
Normal file
119
tutorial/4/lsns.md
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
Explorons les *namespaces*
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Maintenant que nous avons quelques notions de base sur les espaces de nom,
|
||||||
|
voyons quelles surprises nous réserve notre système...
|
||||||
|
|
||||||
|
|
||||||
|
### Voir les *namespace*s de notre système
|
||||||
|
|
||||||
|
La première commande que l'on va utiliser est `lsns(8)`, afin d'afficher tous
|
||||||
|
les *namespace*s actuels de notre machine.
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```bash
|
||||||
|
42sh# lsns
|
||||||
|
NS TYPE NPROCS PID USER COMMAND
|
||||||
|
4026531834 time 238 1 root /sbin/init
|
||||||
|
4026531835 cgroup 238 1 root /sbin/init
|
||||||
|
4026531836 pid 239 1 root /sbin/init
|
||||||
|
4026531837 user 227 1 root /sbin/init
|
||||||
|
4026531838 uts 231 1 root /sbin/init
|
||||||
|
4026531839 ipc 228 1 root /sbin/init
|
||||||
|
4026531840 net 228 1 root /sbin/init
|
||||||
|
4026531841 mnt 223 1 root /sbin/init
|
||||||
|
4026532230 uts 1 227 root ├─/usr/lib/systemd/systemd-udevd
|
||||||
|
4026532483 mnt 4 366 root ├─/usr/lib/systemd/systemd-userdbd
|
||||||
|
4026532485 mnt 1 363 systemd-resolve ├─/usr/lib/systemd/systemd-resolved
|
||||||
|
4026532486 mnt 1 364 systemd-timesync ├─/usr/lib/systemd/systemd-timesyncd
|
||||||
|
4026532491 mnt 1 381 root ├─/usr/lib/systemd/systemd-logind
|
||||||
|
4026532569 mnt 1 428 systemd-network └─/usr/lib/systemd/systemd-networkd
|
||||||
|
4026531862 mnt 1 33 root kdevtmpfs
|
||||||
|
[...]
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Cette commande nous dévoile déjà de nombreuses choses :
|
||||||
|
|
||||||
|
- Chaque processus se trouve dans un *namespace* de chaque type : le noyau n'a
|
||||||
|
pas de notion de « processus hôte » (sans *namespace*) et « processus
|
||||||
|
contenerisé » (dans un *namespace*). Le processus initial de la machine se
|
||||||
|
retrouve donc dans des espaces de nom, tout comme les processus d'un conteneur.
|
||||||
|
|
||||||
|
- On aperçoit un genre de hiérarchie dans certain cas.
|
||||||
|
|
||||||
|
- La première colonne nous renseigne sur l'identifiant du *namespace*.
|
||||||
|
|
||||||
|
- `kdevtmpfs` : un *thread* du noyau, s'exécute dans un espace de nom `mnt`
|
||||||
|
dédié.
|
||||||
|
|
||||||
|
|
||||||
|
Vous verrez surement davantage de processus si vous exécutez cette commande sur
|
||||||
|
une machine que vous utilisez : chaque conteneur y sera bien entendu listé,
|
||||||
|
quelque soit la technologie sous-jacente (Docker, podman, CRI-O, snapd, ...),
|
||||||
|
mais chose plus étonnante, Chrome et Firefox tirent également parti des espaces
|
||||||
|
de nom pour de la défense en profondeur.
|
||||||
|
|
||||||
|
|
||||||
|
### Voir les *namespace*s d'un processus
|
||||||
|
|
||||||
|
Chaque processus lancé est donc rattaché à une liste d'espaces de nom, y
|
||||||
|
compris s'il est issu du système de base (« l'hôte »).
|
||||||
|
|
||||||
|
Nous pouvons dès lors consulter le dossier `/proc/<PID>/ns/` de chaque
|
||||||
|
processus, pour consulter les différents espaces de nom de nos processus.
|
||||||
|
|
||||||
|
Tous les processus ont la même liste de fichiers. Ils sont tous liés à un
|
||||||
|
espace de noms par *namespace* utilisable avec la version noyau dont on
|
||||||
|
dispose. D'une machine à l'autre, d'une version du noyau à l'autre, il est
|
||||||
|
normal d'avoir une liste de *namespace*s différente, mais d'un processus à
|
||||||
|
l'autre sur un même noyau, nous aurons les mêmes espaces de nom disponibles,
|
||||||
|
donc les mêmes fichiers.
|
||||||
|
|
||||||
|
Ces fichiers sont en fait des liens symboliques un peu particuliers, car ils ne
|
||||||
|
pointent pas vers une destination "valide" :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```bash
|
||||||
|
42sh$ ls -l /proc/self/ns
|
||||||
|
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 cgroup -> 'cgroup:[4026531835]'
|
||||||
|
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 ipc -> 'ipc:[4026531839]'
|
||||||
|
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 mnt -> 'mnt:[4026531840]'
|
||||||
|
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 net -> 'net:[4026532008]'
|
||||||
|
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid -> 'pid:[4026531836]'
|
||||||
|
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid_for_children -> 'pid:[4026531836]'
|
||||||
|
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 time -> 'time:[4026531834]'
|
||||||
|
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 time_for_children -> 'time:[4026531834]'
|
||||||
|
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 user -> 'user:[4026531837]'
|
||||||
|
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 uts -> 'uts:[4026531838]'
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Les liens référencent une structure du noyau résidant en mémoire. Les numéros
|
||||||
|
entre crochets sont les *inodes* du système de fichiers `nsfs`, que l'on a pu
|
||||||
|
voir dans la première colonne de `lsns(8)`. Ce sont les identifiants des
|
||||||
|
espaces de nom.
|
||||||
|
|
||||||
|
Ces *inodes* seront les mêmes pour deux processus qui partagent le même espace
|
||||||
|
de noms : la structure pointée sera identique. Elle sera par contre différente
|
||||||
|
si l'espace de nom est différent, l'*inode* sera donc différent.
|
||||||
|
|
||||||
|
|
||||||
|
::::: {.question}
|
||||||
|
|
||||||
|
##### `*_for_children` {-}
|
||||||
|
|
||||||
|
Vous avez peut-être remarqué des fichiers `*_for_children` dans le dossier `ns`
|
||||||
|
de vos processus. Les espaces de noms *PID* et *Time*, lorsqu'on les change
|
||||||
|
pour un processus, ne s'appliquent pas directement au processus en cours
|
||||||
|
d'exécution, la dissociation de *namespace* ne pourra se faire que pour les
|
||||||
|
processus/threads fils.
|
||||||
|
|
||||||
|
`pid_for_children` et `time_for_children` représentent donc les *namespace*s
|
||||||
|
qui seront attribués aux processus fils lancés par un `clone(2)` ou un
|
||||||
|
`fork(2)`.
|
||||||
|
|
||||||
|
En attendant notre processus courant conserve ses espaces de nom `pid` et
|
||||||
|
`time`.
|
||||||
|
|
||||||
|
:::::
|
@ -187,7 +187,9 @@ mount -t tmpfs none /mnt/test-shared/foo
|
|||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Le point de montage apparaît bien sous `/mnt/test-slave/foo`. Par contre :
|
Le point de montage apparaît bien sous `/mnt/test-slave/foo`.
|
||||||
|
|
||||||
|
Par contre :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```bash
|
||||||
@ -288,10 +290,15 @@ mount --move /dev /newroot/dev
|
|||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
::::: {.question}
|
||||||
|
|
||||||
|
#### Quand a-t-on besoin de déplacer un point de montage ? {-}
|
||||||
|
\
|
||||||
Cette possibilité s'emploie notamment lorsque l'on souhaite changer la racine
|
Cette possibilité s'emploie notamment lorsque l'on souhaite changer la racine
|
||||||
de notre système de fichiers : par exemple pour passer de l'*initramfs* au
|
de notre système de fichiers : par exemple pour passer de l'*initramfs* au
|
||||||
système démarré, de notre système hôte au système d'un conteneur, ...
|
système démarré, ou encore de notre système hôte au système d'un conteneur, ...
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
## Aller plus loin {-}
|
## Aller plus loin {-}
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
\newpage
|
|
||||||
|
|
||||||
Le *namespace* `mount`
|
Le *namespace* `mount`
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
@ -20,7 +18,7 @@ montage, récursivement, dès que l'on est entré dans notre nouvel espace de
|
|||||||
noms.
|
noms.
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```
|
||||||
mount --make-rslave /
|
mount --make-rslave /
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
@ -45,7 +43,7 @@ montage virtuels. Le changement de racine sera donc effectif uniquement dans
|
|||||||
cet espace de noms.
|
cet espace de noms.
|
||||||
|
|
||||||
|
|
||||||
#### L'environnement
|
#### L'environnement\
|
||||||
|
|
||||||
Pour pouvoir changer de racine, il est nécessaire que la nouvelle racine soit
|
Pour pouvoir changer de racine, il est nécessaire que la nouvelle racine soit
|
||||||
la racine d'un point de montage, comme l'explique `pivot_root(2)`. En effet, il
|
la racine d'un point de montage, comme l'explique `pivot_root(2)`. En effet, il
|
||||||
@ -55,7 +53,7 @@ ne se trouvait pas à la racine d'une partition au moment du basculement.
|
|||||||
Si vous n'avez pas de partition à disposition, vous pouvez utiliser un `tmpfs` :
|
Si vous n'avez pas de partition à disposition, vous pouvez utiliser un `tmpfs` :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```
|
||||||
42sh# mkdir /mnt/newroot
|
42sh# mkdir /mnt/newroot
|
||||||
42sh# mount -t tmpfs none /mnt/newroot
|
42sh# mount -t tmpfs none /mnt/newroot
|
||||||
```
|
```
|
||||||
@ -75,7 +73,7 @@ Voici les grandes étapes du changement de racine :
|
|||||||
3. `pivot_root` !
|
3. `pivot_root` !
|
||||||
|
|
||||||
|
|
||||||
#### S'isoler
|
#### S'isoler\
|
||||||
|
|
||||||
Notre but étant de démonter toutes les partitions superflues, nous allons
|
Notre but étant de démonter toutes les partitions superflues, nous allons
|
||||||
devoir nous isoler sur :
|
devoir nous isoler sur :
|
||||||
@ -89,48 +87,54 @@ devoir nous isoler sur :
|
|||||||
Isolons-nous :
|
Isolons-nous :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```
|
||||||
42sh# unshare -p -m -f --mount-proc
|
42sh# unshare -p -m -f --mount-proc
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
::::: {.warning}
|
||||||
|
|
||||||
#### Dissocier la propagation des démontages
|
Avant de pouvoir commencer à démonter les partitions, il faut
|
||||||
|
|
||||||
Attention ! avant de pouvoir commencer à démonter les partitions, il faut
|
|
||||||
s'assurer que les démontages ne se propagent pas via une politique de *shared
|
s'assurer que les démontages ne se propagent pas via une politique de *shared
|
||||||
mount*.
|
mount*.
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
|
|
||||||
|
#### Dissocier la propagation des démontages\
|
||||||
|
|
||||||
Commençons donc par étiqueter tous nos points de montage (de ce *namespace*),
|
Commençons donc par étiqueter tous nos points de montage (de ce *namespace*),
|
||||||
comme esclaves :
|
comme esclaves :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```
|
||||||
42sh# mount --make-rslave /
|
42sh# mount --make-rslave /
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
#### Démonter tout !
|
#### Démonter tout !\
|
||||||
|
|
||||||
À vous maintenant de démonter vos points d'attache. Il ne devrait vous rester
|
À vous maintenant de démonter vos points d'attache. Il ne devrait vous rester
|
||||||
après cette étape que : `/`, `/dev`, `/sys`, `/proc`, `/run` et leurs fils.
|
après cette étape que : `/`, `/dev`, `/sys`, `/proc`, `/run` et leurs fils.
|
||||||
|
|
||||||
|
|
||||||
#### Switch !
|
#### Switch !\
|
||||||
|
|
||||||
À ce stade, dans votre console, vous avez plusieurs solutions : utiliser
|
À ce stade, dans votre console, vous avez plusieurs solutions : utiliser
|
||||||
`switch_root(8)` ou `pivot_root(8)`. La première abstrait plus de choses que la
|
`switch_root(8)` ou `pivot_root(8)`. La première abstrait plus de choses que la
|
||||||
seconde.
|
seconde.
|
||||||
|
|
||||||
|
|
||||||
##### `switch_root`\
|
##### `switch_root` {-}
|
||||||
|
\
|
||||||
|
|
||||||
Cette commande s'occupe de déplacer les partitions restantes pour vous, et lance
|
Cette commande s'occupe de déplacer les partitions restantes pour vous, et lance
|
||||||
la première commande (*init*) de votre choix.
|
la première commande (*init*) de votre choix.
|
||||||
|
|
||||||
|
|
||||||
##### `pivot_root`\
|
##### `pivot_root` {-}
|
||||||
|
\
|
||||||
|
|
||||||
Cette commande, plus proche du fonctionnement de l'appel système
|
Cette commande, plus proche du fonctionnement de l'appel système
|
||||||
`pivot_root(2)`, requiert de notre part que nous ayons préalablement déplacé
|
`pivot_root(2)`, requiert de notre part que nous ayons préalablement déplacé
|
||||||
@ -147,9 +151,58 @@ Pour lancer la première commande dans la nouvelle racine, on passe généraleme
|
|||||||
par :
|
par :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```
|
||||||
42sh# exec chroot / command
|
42sh# exec chroot / command
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
#### Erreurs courantes {-}
|
||||||
|
\
|
||||||
|
|
||||||
|
Voici une liste des erreurs les plus courantes que vous allez sans doute rencontrer :
|
||||||
|
|
||||||
|
`EINVAL`
|
||||||
|
: - La nouvelle racine n'est pas un point de montage.
|
||||||
|
: - Le chemin où placer l'ancienne racine n'est pas sur la nouvelle racine.
|
||||||
|
: - Le point de montage de la nouvelle ou l'ancienne racine utilise le type de propagation *shared*.
|
||||||
|
|
||||||
|
`EBUSY`
|
||||||
|
: La nouvelle racine ou le chemin où mettre l'ancienne racine se trouve sur la racine actuelle.
|
||||||
|
|
||||||
|
`ENOTDIR`
|
||||||
|
: La nouvelle racine ou le chemin où mettre l'ancienne racine ne sont pas des dossiers.
|
||||||
|
|
||||||
|
`EPERM`
|
||||||
|
: Le processus appelant ne dispose pas de la capability `CAP_SYS_ADMIN`.
|
||||||
|
|
||||||
|
Vous pouvez retrouver toutes les erreurs dans le manuel de `pivot_root(2)`.
|
||||||
|
|
||||||
|
#### Assemblage {-}
|
||||||
|
\
|
||||||
|
|
||||||
|
Vous devriez maintenant pouvoir réaliser un script `myswitch_root.sh` qui
|
||||||
|
enchaîne toutes les étapes précédentes.
|
||||||
|
|
||||||
|
On considère préalablement que l'environnement est propice à la réalisation de ce script :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```
|
||||||
|
42sh# mkdir -p /mnt/newroot
|
||||||
|
42sh# mount -t tmpfs none /mnt/newroot
|
||||||
|
42sh# wget https://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86_64/alpine-minirootfs-3.14.8-x86_64.tar.gz
|
||||||
|
42sh# tar xpf alpine-minirootfs-*.tar.gz -C /mnt/newroot
|
||||||
|
42sh# cd /
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Puis on s'attend à le lancer ainsi :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```
|
||||||
|
42sh# ~/myswitch_root.sh /mnt/newroot /bin/bash
|
||||||
|
innewns# _
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
:::::
|
:::::
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
\newpage
|
|
||||||
|
|
||||||
Le *namespace* `network` {#net-ns}
|
Le *namespace* `network` {#net-ns}
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
Voyons maintenant plus en détail les différents espaces de nom, leurs
|
||||||
|
caractéristiques et leurs usages ; en commençant par le *namespace* `network`.
|
||||||
|
|
||||||
### Introduction
|
### Introduction
|
||||||
|
|
||||||
L'espace de noms `network`, comme son nom l'indique permet de virtualiser tout
|
L'espace de noms `network`, comme son nom l'indique permet de virtualiser tout
|
||||||
@ -14,16 +15,25 @@ environnement qui n'a plus qu'une interface de *loopback* :
|
|||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```
|
```
|
||||||
42sh# unshare -n ip a
|
42sh# unshare --net ip a
|
||||||
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
|
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
|
||||||
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
::::: {.warning}
|
||||||
|
|
||||||
Bien que portant le même nom que l'interface de *loopback* de notre
|
Bien que portant le même nom que l'interface de *loopback* de notre
|
||||||
environnement principal, il s'agit bien de deux interfaces isolées l'une de
|
environnement principal, il s'agit bien de deux interfaces isolées l'une de
|
||||||
l'autre.
|
l'autre.
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
|
Afin d'amener du réseau à notre nouvel espace de nom, il va falloir lui
|
||||||
|
attribuer des interface. En fait, nous allons pouvoir déplacer nos interfaces
|
||||||
|
réseaux, dans le *namespace* vers lequel elle doit être accessible. Une
|
||||||
|
interface donnée ne peut se trouver que dans un seul *namespace* à la fois.
|
||||||
|
|
||||||
Qui dit nouvelle pile réseau, dit également que les ports qui sont assignés
|
Qui dit nouvelle pile réseau, dit également que les ports qui sont assignés
|
||||||
dans l'espace principal, ne le sont plus dans le conteneur : il est donc
|
dans l'espace principal, ne le sont plus dans le conteneur : il est donc
|
||||||
possible de lancer un serveur web sans qu'il n'entre en conflit avec celui d'un
|
possible de lancer un serveur web sans qu'il n'entre en conflit avec celui d'un
|
||||||
@ -38,17 +48,51 @@ La suite d'outils `iproute2` propose une interface simplifiée pour utiliser le
|
|||||||
Nous pouvons tout d'abord créer un nouvel espace de noms :
|
Nous pouvons tout d'abord créer un nouvel espace de noms :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```
|
||||||
42sh# ip netns add virli
|
42sh# ip netns add virli
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
::::: {.code}
|
||||||
|
|
||||||
La technique utilisée ici pour avoir des *namespaces* nommés est la même que
|
La technique utilisée ici pour avoir des *namespaces* nommés est la même que
|
||||||
celle que nous avons vue dans
|
celle que nous avons vue dans
|
||||||
[la première partie sur les *namespaces*](#ns-lifetime) : via un `mount --bind`
|
[la première partie sur les *namespaces*](#ns-lifetime) : via un `mount --bind`
|
||||||
dans le dossier `/var/run/netns/`. Cela permet de faire persister le namespace
|
dans le dossier `/var/run/netns/`. Cela permet de faire persister le namespace
|
||||||
malgré le fait que plus aucun processus ne s'y exécute.
|
malgré le fait que plus aucun processus ne s'y exécute.
|
||||||
|
|
||||||
|
Nous pouvons créer artificiellement des entrées pour `ip netns` avec les
|
||||||
|
quelques commandes suivantes :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```
|
||||||
|
# On affiche la liste des netns déjà créés
|
||||||
|
42sh# ip netns
|
||||||
|
virli
|
||||||
|
|
||||||
|
# On crée un fichier pour servir de réceptacle au bind mount
|
||||||
|
42sh# touch /var/run/netns/foo
|
||||||
|
|
||||||
|
# On crée un nouveau namespace net, puis on bind tout de suite le
|
||||||
|
# fichier de namespace vers le fichier que l'on vient de créer
|
||||||
|
42sh# unshare --net \
|
||||||
|
mount --bind /proc/self/ns/net /var/run/netns/foo
|
||||||
|
|
||||||
|
# Testons si cela a bien marché
|
||||||
|
42sh# ip netns
|
||||||
|
foo virli
|
||||||
|
42sh# ip netns exec foo ip link
|
||||||
|
1: lo: <LOOPBACK> mut 65536 qdisc noop state DOWN mode DEFAULT group default
|
||||||
|
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Les fichiers utilisés par `ip netns` ne sont donc rien de plus que des
|
||||||
|
*bind-mount*. Ce qui explique qu'ils soient persistant même sans processus
|
||||||
|
s'exécutant à l'intérieur.
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
Maintenant que notre *namespace* est créé, nous pouvons regarder s'il contient
|
Maintenant que notre *namespace* est créé, nous pouvons regarder s'il contient
|
||||||
des interfaces :
|
des interfaces :
|
||||||
|
|
||||||
@ -67,7 +111,7 @@ D'ailleurs, cette interface est rapportée comme étant désactivée, activons-l
|
|||||||
via la commande :
|
via la commande :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```
|
||||||
42sh# ip netns exec virli ip link set dev lo up
|
42sh# ip netns exec virli ip link set dev lo up
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
@ -105,6 +149,171 @@ entre d'un côté sort de l'autre et inversement. La commande précédente a don
|
|||||||
créé deux interfaces `veth0` et `veth1` : les paquets envoyés sur `veth0` sont
|
créé deux interfaces `veth0` et `veth1` : les paquets envoyés sur `veth0` sont
|
||||||
donc reçus par `veth1` et les paquets envoyés à `veth1` sont reçus par `veth0`.
|
donc reçus par `veth1` et les paquets envoyés à `veth1` sont reçus par `veth0`.
|
||||||
|
|
||||||
|
::::: {.code}
|
||||||
|
|
||||||
|
Pour réaliser ces étapes dans un langage de programmation, nous allons passer
|
||||||
|
par Netlink. Il s'agit d'une interface de communication entre le noyau et
|
||||||
|
l'espace utilisateur. Il faut commencer par créer une `socket` pour avoir accès
|
||||||
|
à l'API Netlink, nous pourrons ensuite envoyer des requêtes, comme celle nous
|
||||||
|
permettant de créer notre interface `veth`.
|
||||||
|
|
||||||
|
Un message Netlink a une structure, alignée sur 4 octets, contenant un en-tête
|
||||||
|
et des données. Le format de l'en-tête est décrit dans le RFC
|
||||||
|
3549[^RFC3549] :
|
||||||
|
|
||||||
|
[^RFC3549]: <https://tools.ietf.org/html/rfc3549>
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```c
|
||||||
|
struct nlmsghdr {
|
||||||
|
uint32_t nlmsg_len; // Length of message including header
|
||||||
|
uint16_t nlmsg_type; // Type of message content
|
||||||
|
uint16_t nlmsg_flags; // Additional flags
|
||||||
|
uint32_t nlmsg_seq; // Sequence number
|
||||||
|
uint32_t nlmsg_pid; // Sender port ID
|
||||||
|
};
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Parmi les fonctionnalités de Netlink, nous allons utiliser le module NIS
|
||||||
|
(Network Interface Service)[^REF3549NIS]. Il spécifie le format par lequel
|
||||||
|
doivent commencer les données liées à l'administration d'interfaces réseau.
|
||||||
|
|
||||||
|
[^RFC3549NIS]: Network Interface Service Module <https://tools.ietf.org/html/rfc3549#section-2.3.3.1>
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```c
|
||||||
|
struct ifinfomsg {
|
||||||
|
uint8_t ifi_family; // AF_UNSPEC
|
||||||
|
// uint8_t Reserved
|
||||||
|
uint16_t ifi_type; // Device type
|
||||||
|
int32_t ifi_index; // Interface index
|
||||||
|
uint32_t ifi_flags; // Device flags
|
||||||
|
uint32_t ifi_change; // change mask
|
||||||
|
};
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Le module NIS a besoin que les données soient transmises sous forme d'*attributs
|
||||||
|
Netlink*. Ces attributs fournissent un moyen de segmenter la charge utile en
|
||||||
|
sous-sections. Un attribut a une taille et un type, en plus de sa charge utile.
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```c
|
||||||
|
struct rtattr {
|
||||||
|
uint16_t rta_len; // Length of option
|
||||||
|
uint16_t rta_type; // Type of option
|
||||||
|
// Data follows
|
||||||
|
};
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Le *payload* du message Netlink sera donc transmis comme une liste d'attributs (où
|
||||||
|
chaque attribut peut à son tour avoir des attributs imbriqués).
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```c
|
||||||
|
struct rtattr {
|
||||||
|
unsigned short rta_len;
|
||||||
|
unsigned short rta_type;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
En se basant sur du code d'`ip link`[^IPLINK], on peut reconstituer la
|
||||||
|
communication suivante :
|
||||||
|
|
||||||
|
[^IPLINK]: <https://github.com/shemminger/iproute2/blob/main/ip/link_veth.c>
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```c
|
||||||
|
#define MAX_PAYLOAD 1024
|
||||||
|
|
||||||
|
struct nlreq {
|
||||||
|
struct nlmsghdr hdr; // Netlink message header
|
||||||
|
struct ifinfomsg msg; // First data filled with NIS module info
|
||||||
|
char buf[MAX_PAYLOAD]; // Remaining payload
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
create_veth(char *ifname, char *peername)
|
||||||
|
{
|
||||||
|
// Create the netlink socket
|
||||||
|
int sock_fd = socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
|
||||||
|
if (sock_fd < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
uint16_t flags =
|
||||||
|
NLM_F_REQUEST // We build a request message
|
||||||
|
| NLM_F_CREATE // Create a device if it doesn't exist
|
||||||
|
| NLM_F_EXCL // Do nothing if it already exists
|
||||||
|
| NLM_F_ACK; // Except an ack as reply or an error
|
||||||
|
|
||||||
|
// Initialise request message
|
||||||
|
struct nl_req req = {
|
||||||
|
.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
|
||||||
|
.hdr.nlmsg_flags = flags,
|
||||||
|
.hdr.nlmsg_type = RTM_NEWLINK,
|
||||||
|
.msg.ifi_family = PF_NETLINK,
|
||||||
|
};
|
||||||
|
struct nlmsghdr *hdr = &req.hdr;
|
||||||
|
int maxlen = sizeof(req);
|
||||||
|
|
||||||
|
// Attribute r0 with veth info
|
||||||
|
addattr_l(hdr, maxlen, IFLA_IFNAME, ifname, strlen(ifname) + 1);
|
||||||
|
|
||||||
|
// Attribute r1 nested within r1, containing iface info
|
||||||
|
struct rtattr *linfo =
|
||||||
|
addattr_nest(n, maxlen, IFLA_LINKINFO);
|
||||||
|
// Specify the device type is veth
|
||||||
|
addattr_l(hdr, maxlen, IFLA_INFO_KIND, "veth", 5);
|
||||||
|
|
||||||
|
// r2: another nested attribute
|
||||||
|
struct rtattr *linfodata =
|
||||||
|
addattr_nest(hdr, maxlen, IFLA_INFO_DATA);
|
||||||
|
|
||||||
|
// r3: nested attribute, contains the peer name
|
||||||
|
struct rtattr *peerinfo =
|
||||||
|
addattr_nest(n, maxlen, VETH_INFO_PEER);
|
||||||
|
n->nlmsg_len += sizeof(struct ifinfomsg);
|
||||||
|
addattr_l(n, maxlen, IFLA_IFNAME, peername, strlen(peername) + 1);
|
||||||
|
addattr_nest_end(hdr, peerinfo);
|
||||||
|
|
||||||
|
addattr_nest_end(hdr, linfodata);
|
||||||
|
addattr_nest_end(hdr, linfo);
|
||||||
|
|
||||||
|
// Send the message
|
||||||
|
sendmsg(sock_fd, &req, 0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Maintenant que l'on a notre interface virtuelle, nous pouvons envoyer un second
|
||||||
|
message pour la déplacer dans un nouveau *namespace*[^IPSETNS] :
|
||||||
|
|
||||||
|
[^IPSETNS]: <https://github.com/shemminger/iproute2/blob/main/ip/iplink.c#L677>
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```c
|
||||||
|
// Initialise request message
|
||||||
|
struct nl_req req = {
|
||||||
|
.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
|
||||||
|
.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
|
||||||
|
.hdr.nlmsg_type = RTM_NEWLINK,
|
||||||
|
.msg.ifi_family = PF_NETLINK,
|
||||||
|
};
|
||||||
|
|
||||||
|
addattr_l(&req.hdr, sizeof(req), IFLA_NET_NS_FD, &netns, 4);
|
||||||
|
addattr_l(&req.hdr, sizeof(req), IFLA_IFNAME,
|
||||||
|
ifname, strlen(ifname) + 1);
|
||||||
|
|
||||||
|
sendmsg(sock_fd, &req, 0);
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
Dans cette configuration, ces deux interfaces ne sont pas très utiles, mais si
|
Dans cette configuration, ces deux interfaces ne sont pas très utiles, mais si
|
||||||
l'on place l'une des deux extrémités dans un autre *namespace* `network`, il
|
l'on place l'une des deux extrémités dans un autre *namespace* `network`, il
|
||||||
devient alors possible de réaliser un échange de paquets entre les deux.
|
devient alors possible de réaliser un échange de paquets entre les deux.
|
||||||
@ -112,7 +321,7 @@ devient alors possible de réaliser un échange de paquets entre les deux.
|
|||||||
Pour déplacer `veth1` dans notre *namespace* `virli` :
|
Pour déplacer `veth1` dans notre *namespace* `virli` :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```
|
||||||
42sh# ip link set veth1 netns virli
|
42sh# ip link set veth1 netns virli
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
@ -120,7 +329,7 @@ Pour déplacer `veth1` dans notre *namespace* `virli` :
|
|||||||
Il ne reste maintenant plus qu'à assigner une IP à chacune des interfaces :
|
Il ne reste maintenant plus qu'à assigner une IP à chacune des interfaces :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```
|
||||||
42sh# ip netns exec virli ip a add 10.10.10.42/24 dev veth1
|
42sh# ip netns exec virli ip a add 10.10.10.42/24 dev veth1
|
||||||
42sh# ip a add 10.10.10.41/24 dev veth0
|
42sh# ip a add 10.10.10.41/24 dev veth0
|
||||||
```
|
```
|
||||||
@ -150,8 +359,9 @@ couches du noyau. Utiliser les interfaces *veth* est plutôt simple et disponibl
|
|||||||
partout, mais c'est loin d'être la technique la plus rapide ou la moins
|
partout, mais c'est loin d'être la technique la plus rapide ou la moins
|
||||||
gourmande.
|
gourmande.
|
||||||
|
|
||||||
|
Voyons ensemble les autres possibilités à notre disposition.
|
||||||
|
|
||||||
#### VLAN
|
#### VLAN \
|
||||||
|
|
||||||
Il est possible d'attribuer juste une interface de VLAN, si l'on a un switch
|
Il est possible d'attribuer juste une interface de VLAN, si l'on a un switch
|
||||||
supportant la technologie [802.1q](https://fr.wikipedia.org/wiki/IEEE_802.1Q)
|
supportant la technologie [802.1q](https://fr.wikipedia.org/wiki/IEEE_802.1Q)
|
||||||
@ -165,8 +375,11 @@ derrière notre machine.
|
|||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
On attribuera alors à chaque conteneur une interface de VLAN différente. Cela
|
||||||
|
peut donner lieu à une configuration de switch(s) assez complexe.
|
||||||
|
|
||||||
#### MACVLAN
|
|
||||||
|
#### MACVLAN \
|
||||||
|
|
||||||
<!-- https://hicu.be/bridge-vs-macvlan -->
|
<!-- https://hicu.be/bridge-vs-macvlan -->
|
||||||
|
|
||||||
@ -176,11 +389,11 @@ MACVLAN. S'il est activé dans votre noyau, vous allez avoir le choix entre l'un
|
|||||||
des quatre modes : *private*, VEPA, *bridge* ou *passthru*.
|
des quatre modes : *private*, VEPA, *bridge* ou *passthru*.
|
||||||
|
|
||||||
Quel que soit le mode choisi, les paquets en provenance d'autres machines et à
|
Quel que soit le mode choisi, les paquets en provenance d'autres machines et à
|
||||||
destination d'un MAC seront délivrés à l'interface possédant la MAC. Les
|
destination d'une MAC seront délivrés à l'interface possédant ladite MAC. Les
|
||||||
différences entre les modes se trouvent au niveau de la communication entre les
|
différences entre les modes se trouvent au niveau de la communication entre les
|
||||||
interfaces.
|
interfaces.
|
||||||
|
|
||||||
##### VEPA
|
##### VEPA \
|
||||||
|
|
||||||
Dans ce mode, tous les paquets sortants sont directement envoyés sur
|
Dans ce mode, tous les paquets sortants sont directement envoyés sur
|
||||||
l'interface Ethernet de sortie, sans qu'aucun routage préalable n'ait été
|
l'interface Ethernet de sortie, sans qu'aucun routage préalable n'ait été
|
||||||
@ -198,7 +411,7 @@ Pour construire une nouvelle interface de ce type :
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
##### *Private*
|
##### *Private* \
|
||||||
|
|
||||||
À la différence du mode *VEPA*, si un paquet émis par un conteneur à
|
À la différence du mode *VEPA*, si un paquet émis par un conteneur à
|
||||||
destination d'un autre conteneur est réfléchi par un switch, le paquet ne sera
|
destination d'un autre conteneur est réfléchi par un switch, le paquet ne sera
|
||||||
@ -214,12 +427,12 @@ conteneur de la même machine.
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
##### *Bridge*
|
##### *Bridge* \
|
||||||
|
|
||||||
À l'inverse des modes *VEPA* et *private*, les paquets sont routés selon leur
|
En mode *Bridge*, les paquets sont routés selon leur adresse MAC : si jamais
|
||||||
adresse MAC : si jamais une adresse MAC est connue, le paquet est délivré à
|
une adresse MAC est connue, le paquet est délivré à l'interface MACVLAN
|
||||||
l'interface MACVLAN correspondante ; dans le cas contraire, le paquet est
|
correspondante ; dans le cas contraire, le paquet est envoyé sur l'interface de
|
||||||
envoyé sur l'interface de sortie.
|
sortie.
|
||||||
|
|
||||||
Pour construire une nouvelle interface de ce type :
|
Pour construire une nouvelle interface de ce type :
|
||||||
|
|
||||||
@ -230,6 +443,28 @@ Pour construire une nouvelle interface de ce type :
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
##### *passthru* \
|
||||||
|
|
||||||
|
Enfin, le mode *passthru* permet de récupérer le contrôle sur tout ce qu'il
|
||||||
|
reste du périphérique initial (notamment pour lui changer sa MAC propre, ou
|
||||||
|
pour activer le mode de promiscuité).
|
||||||
|
|
||||||
|
L'intérêt est surtout de pouvoir donner cette interface à un conteneur ou une
|
||||||
|
machine virtuelle, sans lui donner un accès complet à l'interface physique (et
|
||||||
|
notamment aux autres MACVLAN).
|
||||||
|
|
||||||
|
On construit l'interface en mode *passthru* de cette façon :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```
|
||||||
|
42sh# ip link add link eth0 mac3 type macvlan mode passthru
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Une seule interface MACVLAN peut être en mode *passthru* par interface
|
||||||
|
physique.
|
||||||
|
|
||||||
|
|
||||||
### Aller plus loin {-}
|
### Aller plus loin {-}
|
||||||
|
|
||||||
Pour approfondir les différentes techniques de routage, je vous
|
Pour approfondir les différentes techniques de routage, je vous
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
\newpage
|
|
||||||
|
|
||||||
Le *namespace* `PID` {#pid-ns}
|
Le *namespace* `PID` {#pid-ns}
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
@ -23,13 +21,92 @@ trouve.
|
|||||||
Première étape s'isoler :
|
Première étape s'isoler :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```
|
||||||
42sh# unshare --pid --fork /bin/bash
|
42sh# unshare --pid --fork /bin/bash
|
||||||
|
inpidns# echo $$
|
||||||
|
1
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Nous utilisons ici l'option `-f`, pour que le passage dans le nouvel espace de
|
::::: {.question}
|
||||||
noms des PID soit effectif (cf. [Introduction](#pid-ns-intro)).
|
|
||||||
|
#### Qu'est-ce qu'il se passe sans l'option `--fork` ? {-}
|
||||||
|
\
|
||||||
|
|
||||||
|
Nous utilisons ici l'option `--fork`, pour que le passage dans le nouvel espace
|
||||||
|
de noms des PID soit effectif (cf. [Introduction](#pid-ns-intro)). Si on
|
||||||
|
l'omet, voici ce qu'il se passe :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```
|
||||||
|
42sh# unshare --pid /bin/sh
|
||||||
|
inpidns# echo $$
|
||||||
|
23456
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Ce n'est apparemment pas ce que l'on souhaitait : on est toujours dans l'ancien
|
||||||
|
*namespace*, puisqu'on a juste `unshare(2)`, sans `fork(2)`. Si l'on va plus
|
||||||
|
loin et que l'on `fork` en demandant à `sh` d'exécuter un nouveau processus :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```
|
||||||
|
42sh# unshare --pid /bin/sh
|
||||||
|
inpidns# echo $$
|
||||||
|
34567
|
||||||
|
inpidns# sh
|
||||||
|
inpidns# echo $$
|
||||||
|
1
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
C'est d'ailleurs le bon moment pour regarder le contenu de notre fichier
|
||||||
|
`pid_for_children` pour le processus `34567` :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```
|
||||||
|
42sh# ls -l /proc/34567/ns/pid*
|
||||||
|
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid -> 'pid:[4026531836]'
|
||||||
|
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid_for_children -> 'pid:[4026532993]'
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
On remarque bien que l'on se trouve dans un entre-deux : `pid` pointe toujours
|
||||||
|
sur l'*inode* de l'arbre de PID initial, tandis que `pid_for_children` est prêt
|
||||||
|
à transmettre le nouvel *inode* aux nouveaux processus.
|
||||||
|
\
|
||||||
|
|
||||||
|
#### Pourquoi ça ne fonctionne pas du tout avec `bash` ? {-}
|
||||||
|
\
|
||||||
|
|
||||||
|
Si on utilise `bash` à la place de `sh` dans les exemples précédents, toujours
|
||||||
|
sans l'option `--fork`, on obtient une erreur plutôt étrange :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```
|
||||||
|
42sh# unshare --pid /bin/bash
|
||||||
|
inpidns# echo $$
|
||||||
|
65432
|
||||||
|
inpidns# sh
|
||||||
|
-bash: fork: Cannot allocate memory
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Il se trouve que `bash` commence par `fork(2)` lui-même afin de réaliser un
|
||||||
|
certain nombre d'opérations. Notre PID 1, le premier PID de notre conteneur, a
|
||||||
|
donc été alloué à un processus d'initialisation de `bash`, qui s'est terminé
|
||||||
|
depuis.\
|
||||||
|
|
||||||
|
Le comportement du noyau, lorsque le PID 1 se termine, est de lancer un *kernel
|
||||||
|
panic* (car c'est un processus indispensable, notamment de part son rôle de
|
||||||
|
parent pour tous les processus orphelin). Au sein d'un *namespace* `PID` qui
|
||||||
|
n'est pas le *namespace* racine, le noyau appelle la fonction
|
||||||
|
`disable_pid_allocation` qui retire le *flag* `PIDNS_HASH_ADDING` de l'espace
|
||||||
|
de nom. Le fait de ne pas avoir `PIDNS_HASH_ADDING` fait retourner `ENOMEM` à
|
||||||
|
la fonction `alloc_pid` appelée par `fork(2)` et `clone(2)`. On n'a donc pas
|
||||||
|
d'autre choix que de quitter ce *namespace* pour en recréer un nouveau.
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
Un coup d'œil à `top` ou `ps aux` devrait nous montrer que l'on est maintenant
|
Un coup d'œil à `top` ou `ps aux` devrait nous montrer que l'on est maintenant
|
||||||
le seul processus ... pourtant, il n'en est rien, ces deux commandes continuent
|
le seul processus ... pourtant, il n'en est rien, ces deux commandes continuent
|
||||||
@ -45,12 +122,12 @@ notre système initial. Pour s'en sortir, il est nécessaire de s'isoler dans un
|
|||||||
*namespace* `mount` séparé.
|
*namespace* `mount` séparé.
|
||||||
|
|
||||||
|
|
||||||
#### Double isolation : ajout du *namespace* `mount`
|
### Double isolation : ajout du *namespace* `mount`
|
||||||
|
|
||||||
Voici la nouvelle ligne de commande que l'on va utiliser :
|
Voici la nouvelle ligne de commande que l'on va utiliser :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```
|
||||||
42sh# unshare --pid --mount --fork --mount-proc /bin/bash
|
42sh# unshare --pid --mount --fork --mount-proc /bin/bash
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
@ -90,11 +167,11 @@ de noms.
|
|||||||
|
|
||||||
[^PR_SET_CHILD_SUBREAPER]: en réalité, ce comportement est lié à la propriété
|
[^PR_SET_CHILD_SUBREAPER]: en réalité, ce comportement est lié à la propriété
|
||||||
`PR_SET_CHILD_SUBREAPER`, qui peut être définie pour n'importe quel processus
|
`PR_SET_CHILD_SUBREAPER`, qui peut être définie pour n'importe quel processus
|
||||||
de l'arborescence. Le processus au PID 1 hérite forcément de cette propriété\ ;
|
de l'arborescence. Le processus au PID 1 hérite forcément de cette propriété ;
|
||||||
il va donc récupérer tous les orphelins, si aucun de leurs parents n'a la
|
il va donc récupérer tous les orphelins, si aucun de leurs parents n'a la
|
||||||
propriété définie.
|
propriété définie.
|
||||||
|
|
||||||
Lorsque l'on lance un processus via `nsenter(1)` ou `setns(2)`, cela crée un
|
Lorsqu'on lance un processus via `nsenter(1)` ou `setns(2)`, cela crée un
|
||||||
processus qui n'est sans doute pas un fils direct du processus d'`init` de
|
processus qui n'est sans doute pas un fils direct du processus d'`init` de
|
||||||
notre conteneur. Malgré tout, même s'il est affiché comme n'étant pas un fils à
|
notre conteneur. Malgré tout, même s'il est affiché comme n'étant pas un fils à
|
||||||
l'extérieur du conteneur, les propriétés d'`init` sont biens appliquées à
|
l'extérieur du conteneur, les propriétés d'`init` sont biens appliquées à
|
||||||
|
@ -1,51 +1,65 @@
|
|||||||
\newpage
|
\newpage
|
||||||
|
|
||||||
Projet et rendu
|
Rendu
|
||||||
===============
|
=====
|
||||||
|
|
||||||
Est attendu d'ici le TP suivant :
|
Est attendu d'ici le cours suivant :
|
||||||
|
|
||||||
- le rendu des exercice de ce TP ;
|
- vos réponses à l'évaluation du cours,
|
||||||
- vos réponses à [l'évaluation du cours](https://virli.nemunai.re/quiz/15).
|
- [SRS] tous les exercices de ce TP,
|
||||||
|
- [GISTRE] l'avancement des paliers du [projet final](https://virli.nemunai.re/project-gistre.pdf).
|
||||||
Pour les GISTRE (et en bonus pour les SRS), [un
|
|
||||||
projet](https://virli.nemunai.re/project-2.pdf) est à rendre pour le 13
|
|
||||||
novembre. Consultez les modalités de rendu sur le sujet directement.
|
|
||||||
|
|
||||||
## Modalités de rendu
|
|
||||||
|
|
||||||
En tant que personnes sensibilisées à la sécurité des échanges électroniques,
|
|
||||||
vous devrez m'envoyer vos rendus signés avec votre clef PGP.
|
|
||||||
|
|
||||||
Un service automatique s'occupe de réceptionner vos rendus, de faire des
|
|
||||||
vérifications élémentaires et de vous envoyer un accusé de réception (ou de
|
|
||||||
rejet).
|
|
||||||
|
|
||||||
Ce service écoute sur l'adresse <virli@nemunai.re>. C'est donc à cette adresse
|
|
||||||
et exclusivement à celle-ci que vous devez envoyer vos rendus. Tout rendu
|
|
||||||
envoyé à une autre adresse et/ou non signé et/ou reçu après la correction ne
|
|
||||||
sera pas pris en compte.
|
|
||||||
|
|
||||||
Afin d'orienter correctement votre rendu, ajoutez une balise `[TP4]` au sujet
|
|
||||||
de votre courriel. N'hésitez pas à indiquer dans le corps du message votre
|
|
||||||
ressenti et vos difficultés ou bien alors écrivez votre meilleure histoire
|
|
||||||
drôle si vous n'avez rien à dire.
|
|
||||||
|
|
||||||
|
|
||||||
Tarball
|
Arborescence attendue (SRS)
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Tous les exercices de ce TP sont à placer dans une tarball (pas d'archive ZIP,
|
Tous les fichiers identifiés comme étant à rendre sont à placer dans un dépôt
|
||||||
RAR, ...).
|
Git privé, que vous partagerez avec [votre
|
||||||
|
professeur](https://gitlab.cri.epita.fr/nemunaire/).
|
||||||
|
|
||||||
Voici une arborescence type (adaptez les extensions et les éventuels
|
Voici une arborescence type (vous pourriez avoir des fichiers supplémentaires) :
|
||||||
fichiers supplémentaires associés au langage que vous aurez choisi
|
|
||||||
pour chaque exercice) :
|
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```
|
```
|
||||||
login_x-TP4/cmpns.sh
|
./cmpns.sh
|
||||||
login_x-TP4/mydocker_exec.sh
|
./mydocker_exec.sh
|
||||||
login_x-TP4/myswitch_root.sh
|
./myswitch_root.sh
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Votre rendu sera pris en compte en faisant un [tag **signé par votre clef
|
||||||
|
PGP**](https://lessons.nemunai.re/keys). Consultez les détails du rendu (nom du
|
||||||
|
tag, ...) sur la page dédiée au projet sur la plateforme de rendu.
|
||||||
|
|
||||||
|
::::: {.question}
|
||||||
|
|
||||||
|
Si vous utilisez un seul dépôt pour tous vos rendus, vous **DEVRIEZ**
|
||||||
|
créer une branche distincte pour chaque rendu :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```
|
||||||
|
42sh$ git checkout --orphan renduX
|
||||||
|
42sh$ git reset
|
||||||
|
42sh$ rm -r *
|
||||||
|
42sh$ # Créer l'arborescence de rendu ici
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Pour retrouver ensuite vos rendus des travaux précédents :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```
|
||||||
|
42sh$ git checkout renduY
|
||||||
|
-- ou --
|
||||||
|
42sh$ git checkout master
|
||||||
|
...
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Chaque branche est complètement indépendante l'une de l'autre. Vous
|
||||||
|
pouvez avoir les exercices du TP1 sur `master`, les exercices du TP4
|
||||||
|
sur `rendu4`, ... ce qui vous permet d'avoir une arborescence
|
||||||
|
correspondant à ce qui est demandé, sans pour autant perdre votre
|
||||||
|
travail (ou le rendre plus difficile d'accès).
|
||||||
|
|
||||||
|
::::
|
||||||
|
25
tutorial/4/setns-pid.md
Normal file
25
tutorial/4/setns-pid.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
\
|
||||||
|
|
||||||
|
D'une manière similaire à notre dernier exemple, depuis Linux 5.8, `setns(2)`
|
||||||
|
peut utiliser comme premier argument, un *file descriptor* pointant un
|
||||||
|
processus (`pidfd`).
|
||||||
|
|
||||||
|
Eh oui, outre l'obtention d'un *file descriptor* sur un lien symbolique étrange
|
||||||
|
d'une structure du noyau, il est possible d'en obtenir un sur un processus :
|
||||||
|
|
||||||
|
- soit en réalisant un `open(2)` d'un dossier `/proc/<PID>` ;
|
||||||
|
- soit en utilisant l'appel système `pidfd_open(2)`, en précisant l'identifiant
|
||||||
|
du processus dont on souhaite obtenir le *file descriptor* ;
|
||||||
|
- soit en retour d'un `clone(2)` avec l'option `CLONE_PIDFD`.
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```bash
|
||||||
|
int fd = pidfd_open(42, 0);
|
||||||
|
setns(fd, CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWUTS);
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
À travers cet exemple, on cherche à récupérer un *file descriptor* pour le
|
||||||
|
processus 42. On le passe ensuite à `setns(2)` en précisant que l'on ne
|
||||||
|
souhaite rejoindre que les espaces de noms Utilisateurs, Réseau et UTS (nom de
|
||||||
|
la machine).
|
@ -1,5 +1,5 @@
|
|||||||
#### Exemple C
|
#### Exemple C {-}
|
||||||
|
\
|
||||||
Voici un exemple de code C utilisant `setns(2)` :
|
Voici un exemple de code C utilisant `setns(2)` :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
@ -35,10 +35,58 @@ main(int argc, char *argv[])
|
|||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Ce programme prend au minimum deux arguments :
|
||||||
|
- le chemin d'un fichier d'espace de nom que l'on souhaite rejoindre (le chemin
|
||||||
|
vers le lien symbolique donc) ;
|
||||||
|
- le programme (et ses arguments) que l'on souhaite souhaite exécuter une fois
|
||||||
|
que l'on a rejoint l'espace de noms ciblé.
|
||||||
|
|
||||||
#### Exemple shell
|
Dans un premier temps, on ouvre le fichier passé en paramètre afin d'obtenir un
|
||||||
|
*file descriptor* de la structure du noyau.
|
||||||
|
|
||||||
Dans un shell, on utilisera la commande `nsenter(1)` :
|
On passe ensuite ce *file descriptor* en argument de l'appel système
|
||||||
|
`setns(2)`. Et enfin on exécute la commande données dans les derniers
|
||||||
|
paramètres.
|
||||||
|
|
||||||
|
::::: {.question}
|
||||||
|
|
||||||
|
#### Qu'attend le deuxième argument de `setns(2)` ? {-}
|
||||||
|
\
|
||||||
|
|
||||||
|
Il s'agit d'une contrainte sur le type d'espace de nom que l'on souhaite
|
||||||
|
rejoindre, dans le cas où on ne souhaite pas rejoindre n'importe quel espace de
|
||||||
|
nom.
|
||||||
|
|
||||||
|
Une fois encore, on utilisera les mêmes options `CLONE_NEW*` lorsque l'on
|
||||||
|
attendra un type particulier d'espace de nom, ou 0 pour autoriser tous les
|
||||||
|
types.
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
|
|
||||||
|
::::: {.question}
|
||||||
|
|
||||||
|
#### Peut-on connaître le type de *namespace* à partir du *file descriptor* ? {-}
|
||||||
|
\
|
||||||
|
|
||||||
|
Il est possible de récupérer le type d'espace de nom en passant notre *file
|
||||||
|
descriptor* à `ioctl(2)`, avec le *flag* `NS_GET_NSTYPE` :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```c
|
||||||
|
nstype = ioctl(fd, NS_GET_NSTYPE);
|
||||||
|
if (nstype & CLONE_NEWUTS != 0) {
|
||||||
|
... // This is a file descriptor to an UTS namespace
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
|
|
||||||
|
#### Exemple shell {-}
|
||||||
|
\
|
||||||
|
Dans un shell, nous utiliserons la commande `nsenter(1)` :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```bash
|
||||||
@ -55,3 +103,25 @@ chaton
|
|||||||
jument
|
jument
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Avec `nsenter(1)`, il est possible de cibler un processus particulier, sans
|
||||||
|
aller nécessairement faire référence aux fichiers de `/proc`, en utilisant
|
||||||
|
l'option `--target` :
|
||||||
|
|
||||||
|
<div lang="en-US">
|
||||||
|
```bash
|
||||||
|
42sh# unshare --uts /bin/bash
|
||||||
|
inutsns# echo $$
|
||||||
|
42
|
||||||
|
inutsns# hostname jument
|
||||||
|
# Keep this shell active to perform nexts steps, in another shell.
|
||||||
|
|
||||||
|
42sh# hostname
|
||||||
|
chaton
|
||||||
|
42sh# nsenter --target 42 --uts /bin/bash
|
||||||
|
inutsns# hostname
|
||||||
|
jument
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Les options supplémentaires `--uts`, `--mount`, `--ipc`, `--pid`, ... ne prennent alors pas d'argument, mais désignent les espaces de noms du processus ciblé que l'on souhaite rejoindre.
|
||||||
|
@ -1,71 +1,15 @@
|
|||||||
\newpage
|
|
||||||
|
|
||||||
### Rejoindre un *namespace*
|
### Rejoindre un *namespace*
|
||||||
|
|
||||||
Rejoindre un espace de noms se fait en utilisant l'appel système `setns(2)`, ou
|
Rejoindre un espace de noms se fait en utilisant l'appel système `setns(2)`, ou
|
||||||
la commande `nsenter(1)`. Il est nécessaire de donner en argument
|
la commande `nsenter(1)`. Il est nécessaire de donner en argument
|
||||||
respectivement un *file descriptor* ou le chemin vers le fichier, lien
|
respectivement un *file descriptor* ou le chemin vers le fichier, lien
|
||||||
symbolique, représentant l'espace de nom.
|
symbolique, représentant l'espace de nom (dans `/proc/<PID>/ns/...`).
|
||||||
|
|
||||||
|
Une particularité de ces fichiers, que l'on ne peut pas afficher (leurs liens
|
||||||
#### Voir les *namespace*s d'un processus
|
ne pointent pas sur des fichiers que l'on peut atteindre), c'est que l'on peut
|
||||||
|
les ouvrir avec `open(2)` pour obtenir un *file descriptor* que l'on pourra
|
||||||
Chaque processus lancé est rattaché à une liste d'espaces de nom, y compris
|
passer à `setns(2)`.
|
||||||
s'il est issu du système de base (« l'hôte »).
|
|
||||||
|
|
||||||
Nous pouvons dès lors consulter le dossier `/proc/<PID>/ns/` de chaque
|
|
||||||
processus, pour consulter les différents espaces de nom de nos processus.
|
|
||||||
|
|
||||||
Tous les processus auront la même liste de fichiers, car ils sont liés à un
|
|
||||||
espace de noms par *namespace* utilisable avec le noyau. D'une machine à
|
|
||||||
l'autre, d'une version du noyau à l'autre, il est normal d'avoir une liste de
|
|
||||||
*namespace*s différents, mais d'un processus à l'autre sur le même noyau, nous
|
|
||||||
aurons les mêmes.
|
|
||||||
|
|
||||||
Ces fichiers sont en fait des liens symboliques un peu particuliers, car ils ne
|
|
||||||
pointent pas vers une destination valide :
|
|
||||||
|
|
||||||
<div lang="en-US">
|
|
||||||
```bash
|
|
||||||
42sh$ ls -l /proc/self/ns
|
|
||||||
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 cgroup -> 'cgroup:[4026531835]'
|
|
||||||
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 ipc -> 'ipc:[4026531839]'
|
|
||||||
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 mnt -> 'mnt:[4026531840]'
|
|
||||||
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 net -> 'net:[4026532008]'
|
|
||||||
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid -> 'pid:[4026531836]'
|
|
||||||
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid_for_children -> 'pid:[4026531836]'
|
|
||||||
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 time -> 'time:[4026531834]'
|
|
||||||
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 time_for_children -> 'time:[4026531834]'
|
|
||||||
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 user -> 'user:[4026531837]'
|
|
||||||
lrwxrwxrwx 1 nemunaire 1 oct. 23:42 uts -> 'uts:[4026531838]'
|
|
||||||
|
|
||||||
```
|
|
||||||
</div>
|
|
||||||
|
|
||||||
Les liens référencent une structure du noyau résidant en mémoire. Les numéros
|
|
||||||
entre crochets seront les mêmes pour deux processus qui partagent le même
|
|
||||||
espace de noms. La structure pointée sera différente si l'espace de nom est
|
|
||||||
différent, donc le numéro sera différent.
|
|
||||||
|
|
||||||
On ne peut pas afficher tel quel les structures, mais on peut l'ouvrir avec
|
|
||||||
`open(2)` pour obtenir un *file descriptor* que l'on pourra passer à
|
|
||||||
`setns(2)`.
|
|
||||||
|
|
||||||
Pour les commandes *shell*, il convient de donner en argument le chemin vers le
|
Pour les commandes *shell*, il convient de donner en argument le chemin vers le
|
||||||
lien symbolique : la commande se chargera d'`open(2)` le fichier.
|
lien symbolique : la commande se chargera d'`open(2)` le fichier pour obtenir
|
||||||
|
le *file descriptor* nécessaire.
|
||||||
|
|
||||||
::::: {.question}
|
|
||||||
|
|
||||||
##### `*_for_children` {-}
|
|
||||||
|
|
||||||
Vous avez peut-être remarqué des fichiers `*_for_children` dans le dossier `ns`
|
|
||||||
de vos processus. Nous verrons par la suite que les espaces de noms *PID* et
|
|
||||||
*Time*, une fois créés, ne s'appliquent pas directement au processus en cours
|
|
||||||
d'exécution, la dissociation de *namespace* ne peut se faire que pour les
|
|
||||||
processus/threads fils.
|
|
||||||
|
|
||||||
`pid_for_children` et `time_for_children` représentent donc les *namespace*s
|
|
||||||
qui seront attribués aux fils lancés après un `unshare(2)` réussi.
|
|
||||||
|
|
||||||
:::::
|
|
||||||
|
@ -3,7 +3,7 @@ title: Virtualisation légère -- TP n^o^ 4
|
|||||||
subtitle: Linux Internals partie 2
|
subtitle: Linux Internals partie 2
|
||||||
author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps}
|
author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps}
|
||||||
institute: EPITA
|
institute: EPITA
|
||||||
date: Jeudi 7 octobre 2021
|
date: Mercredi 9 novembre 2022
|
||||||
abstract: |
|
abstract: |
|
||||||
Le but de ce second TP sur les mécanismes internes du noyau va nous
|
Le but de ce second TP sur les mécanismes internes du noyau va nous
|
||||||
permettre d'utiliser les commandes et les appels système relatifs
|
permettre d'utiliser les commandes et les appels système relatifs
|
||||||
@ -12,15 +12,8 @@ abstract: |
|
|||||||
|
|
||||||
\vspace{1em}
|
\vspace{1em}
|
||||||
|
|
||||||
Tous les exercices de ce TP sont à rendre à <virli@nemunai.re> au
|
Les exercices de ce cours sont à rendre au plus tard le mardi 15
|
||||||
plus tard le mercredi 4 novembre 2021 à 23 h 42. Consultez la dernière
|
novembre 2022 à 23 h 42. Consultez les sections matérialisées par un
|
||||||
section de chaque partie pour plus d'informations sur les éléments à
|
bandeau jaunes et un engrenage pour plus d'informations sur les
|
||||||
rendre. Et n'oubliez pas de répondre aux [questions de
|
éléments à rendre.
|
||||||
cours](https://virli.nemunai.re/quiz/13).
|
|
||||||
|
|
||||||
En tant que personnes sensibilisées à la sécurité des échanges électroniques,
|
|
||||||
vous devrez m'envoyer vos rendus signés avec votre clef PGP. Pensez à
|
|
||||||
[me](https://keys.openpgp.org/search?q=nemunaire%40nemunai.re) faire signer
|
|
||||||
votre clef et n'hésitez pas à [faire signer la
|
|
||||||
vôtre](https://www.meetup.com/fr/Paris-certification-de-cles-PGP-et-CAcert/).
|
|
||||||
...
|
...
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
\newpage
|
|
||||||
|
|
||||||
Le *namespace* `user` {#user-ns}
|
Le *namespace* `user` {#user-ns}
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
@ -23,8 +21,8 @@ utilisateur, on obtient les privilèges requis pour créer tous les autres types
|
|||||||
de *namespaces*.
|
de *namespaces*.
|
||||||
|
|
||||||
Grâce à cette technique, il est possible de lancer des conteneurs en tant que
|
Grâce à cette technique, il est possible de lancer des conteneurs en tant que
|
||||||
simple utilisateur ; le projet [Singularity](https://sylabs.io/) repose
|
simple utilisateur ; les projets [`podman`](https://podman.io/) et
|
||||||
en grande partie sur cela.
|
[Singularity](https://sylabs.io/), ... reposent en grande partie sur cela.
|
||||||
|
|
||||||
|
|
||||||
### Correspondance des utilisateurs et des groupes
|
### Correspondance des utilisateurs et des groupes
|
||||||
@ -34,7 +32,7 @@ garder dans le nouvel espace, que les utilisateurs et les groupes utiles au
|
|||||||
processus, en les renumérotant au passage si besoin.
|
processus, en les renumérotant au passage si besoin.
|
||||||
|
|
||||||
|
|
||||||
#### L'utilisateur -2 : *nobody*
|
#### L'utilisateur -2 : *nobody*\
|
||||||
|
|
||||||
Lorsque l'on arrive dans un nouvel espace, aucun utilisateur ni groupe n'est
|
Lorsque l'on arrive dans un nouvel espace, aucun utilisateur ni groupe n'est
|
||||||
défini. Dans cette situation, tous les identifiants d'utilisateur et de groupe,
|
défini. Dans cette situation, tous les identifiants d'utilisateur et de groupe,
|
||||||
@ -45,13 +43,14 @@ l'utilisateur *nobody* et au groupe *nogroup*.
|
|||||||
non-modification d'un paramètre passé en argument d'une fonction.
|
non-modification d'un paramètre passé en argument d'une fonction.
|
||||||
|
|
||||||
|
|
||||||
#### `uid_map` et `gid_map`
|
#### `uid_map` et `gid_map`\
|
||||||
|
|
||||||
Pour établir la correspondance, une fois que l'on a créé le nouveau
|
Pour établir la correspondance, une fois que l'on a créé le nouveau
|
||||||
*namespace*, ces deux fichiers, accessibles dans `/proc/self/`, peuvent être
|
*namespace*, ces deux fichiers, accessibles dans `/proc/self/`, peuvent être
|
||||||
écrits une fois.
|
écrits une fois.
|
||||||
|
|
||||||
##### `uid_map`\
|
##### `uid_map` {-}
|
||||||
|
\
|
||||||
|
|
||||||
Sur chaque ligne, on doit indiquer :
|
Sur chaque ligne, on doit indiquer :
|
||||||
|
|
||||||
@ -89,13 +88,21 @@ l'utilisateur root, dans le conteneur équivaut à l'utilisateur 1000 hors de
|
|||||||
l'espace de noms.
|
l'espace de noms.
|
||||||
|
|
||||||
|
|
||||||
##### `gid_map`\
|
##### `gid_map` {-}
|
||||||
|
\
|
||||||
|
|
||||||
Le principe est identique pour ce fichier, mais agit sur les correspondances
|
Le principe est identique pour ce fichier, mais agit sur les correspondances
|
||||||
des groupes au lieu des utilisateurs.
|
des groupes au lieu des utilisateurs.
|
||||||
|
|
||||||
|
Il y a cependant un subtilité car il faut écrire la chaîne `deny` dans le
|
||||||
|
fichier `setgroups` avant de modifier `gid_map`.
|
||||||
|
|
||||||
### Utilisation de l'espace de noms
|
|
||||||
|
### Utilisation basique de l'espace de noms
|
||||||
|
|
||||||
|
Avec `unshare(1)`, voici comment, en tant que simple utilisateur, se dissocier
|
||||||
|
de plusieurs *namespace*s en gardant un environnement cohérent, dans lequel on
|
||||||
|
devient le super-utilisateur :
|
||||||
|
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```bash
|
```bash
|
||||||
|
Loading…
Reference in New Issue
Block a user