Sfondo
A partire dal kernel 2.6.24, Linux supporta 6 diversi tipi di namespace. Gli spazi dei nomi sono utili per creare processi più isolati dal resto del sistema, senza la necessità di utilizzare una tecnologia di virtualizzazione di basso livello completa.
- CLONE_NEWIPC:Spazi dei nomi IPC:le code di messaggi SystemV IPC e POSIX possono essere isolate.
- CLONE_NEWPID:Spazi dei nomi PID:i PID sono isolati, il che significa che un PID virtuale all'interno dello spazio dei nomi può entrare in conflitto con un PID al di fuori dello spazio dei nomi. I PID all'interno dello spazio dei nomi verranno mappati ad altri PID al di fuori dello spazio dei nomi. Il primo PID all'interno dello spazio dei nomi sarà '1' che al di fuori dello spazio dei nomi è assegnato a init
- CLONE_NEWNET:Spazi dei nomi di rete:le reti (/proc/net, IP, interfacce e percorsi) sono isolate. I servizi possono essere eseguiti sulle stesse porte all'interno degli spazi dei nomi e possono essere create interfacce virtuali "duplicate".
- CLONE_NEWNS:Monta spazi dei nomi. Abbiamo la possibilità di isolare i punti di montaggio così come appaiono ai processi. Usando gli spazi dei nomi di montaggio, possiamo ottenere funzionalità simili a chroot() ma con una maggiore sicurezza.
- CLONE_NEWUTS:spazi dei nomi UTS. Lo scopo principale di questo spazio dei nomi è isolare il nome host e il nome NIS.
- CLONE_NEWUSER:spazi dei nomi utente. Qui, gli ID utente e gruppo sono diversi all'interno e all'esterno degli spazi dei nomi e possono essere duplicati.
Diamo un'occhiata prima alla struttura di un programma C, necessario per dimostrare gli spazi dei nomi dei processi. Quanto segue è stato testato su Debian 6 e 7. Per prima cosa, dobbiamo allocare una pagina di memoria nello stack e impostare un puntatore alla fine di quella pagina di memoria. Usiamo alloca per allocare la memoria dello stack piuttosto che malloc che allocherebbe la memoria nell'heap.
void *mem = alloca(sysconf(_SC_PAGESIZE)) + sysconf(_SC_PAGESIZE);
Successivamente, utilizziamo clone per creare un processo figlio, passando la posizione del nostro stack figlio 'mem', nonché i flag richiesti per specificare un nuovo spazio dei nomi. Specifichiamo 'chiamato' come funzione da eseguire all'interno dello spazio figlio:
mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL);
Dopo aver chiamato clone, aspettiamo che il processo figlio finisca, prima di terminare il genitore. In caso contrario, il flusso di esecuzione padre continuerà e terminerà immediatamente dopo, ripulendo il figlio con esso:
while (waitpid(mypid, &r, 0) < 0 && errno == EINTR) { continue; }
Infine, torniamo alla shell con il codice di uscita del figlio:
if (WIFEXITED(r)) { return WEXITSTATUS(r); } return EXIT_FAILURE;
Ora, diamo un'occhiata alla funzione chiamata:
static int callee() { int ret; mount("proc", "/proc", "proc", 0, ""); setgid(u); setgroups(0, NULL); setuid(u); ret = execl("/bin/bash", "/bin/bash", NULL); return ret; }
Qui, montiamo un filesystem /proc, quindi impostiamo uid (ID utente) e gid (ID gruppo) sul valore di 'u' prima di generare la shell /bin/bash. LXC è uno strumento di virtualizzazione a livello di sistema operativo che utilizza cgroup e namespace per l'isolamento delle risorse. Mettiamo tutto insieme, impostando 'u' su 65534 che è l'utente "nobody" e il gruppo "nogroup" su Debian:
#define _GNU_SOURCE #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/mount.h> #include <grp.h> #include <alloca.h> #include <errno.h> #include <sched.h> static int callee(); const int u = 65534; int main(int argc, char *argv[]) { int r; pid_t mypid; void *mem = alloca(sysconf(_SC_PAGESIZE)) + sysconf(_SC_PAGESIZE); mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL); while (waitpid(mypid, &r, 0) < 0 && errno == EINTR) { continue; } if (WIFEXITED(r)) { return WEXITSTATUS(r); } return EXIT_FAILURE; } static int callee() { int ret; mount("proc", "/proc", "proc", 0, ""); setgid(u); setgroups(0, NULL); setuid(u); ret = execl("/bin/bash", "/bin/bash", NULL); return ret; }
Per eseguire il codice si ottiene quanto segue:
[email protected]:~/pen/tmp# gcc -O -o ns.c -Wall -Werror -ansi -c89 ns.c [email protected]:~/pen/tmp# ./ns [email protected]:~/pen/tmp$ id uid=65534(nobody) gid=65534(nogroup) [email protected]:~/pen/tmp$ ps auxw USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND nobody 1 0.0 0.0 4620 1816 pts/1 S 21:21 0:00 /bin/bash nobody 5 0.0 0.0 2784 1064 pts/1 R+ 21:21 0:00 ps auxw [email protected]:~/pen/tmp$
Si noti che l'UID e il GID sono impostati su nessuno e nogroup. Si noti in particolare che l'output completo di ps mostra solo due processi in esecuzione e che i loro PID sono rispettivamente 1 e 5. Ora, passiamo all'utilizzo di ip netns per lavorare con gli spazi dei nomi di rete. Innanzitutto, confermiamo che attualmente non esistono spazi dei nomi:
[email protected]:~# ip netns list Object "netns" is unknown, try "ip help".
In questo caso, o IP ha bisogno di un aggiornamento o il kernel lo fa. Supponendo che tu abbia un kernel più recente di 2.6.24, molto probabilmente è ip. Dopo l'aggiornamento, l'elenco ip netns non dovrebbe restituire nulla per impostazione predefinita. Aggiungiamo un nuovo spazio dei nomi chiamato 'ns1':
[email protected]:~# ip netns add ns1 [email protected]:~# ip netns list ns1
Per prima cosa, elenchiamo le interfacce correnti:
[email protected]:~# ip link list 1: lo:mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 1000 link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff
Ora per creare una nuova interfaccia virtuale e aggiungerla al nostro nuovo spazio dei nomi. Le interfacce virtuali sono create in coppia e sono collegate tra loro:immagina un cavo crossover virtuale:
[email protected]:~# ip link add veth0 type veth peer name veth1 [email protected]:~# ip link list 1: lo:ifconfig -a ora mostrerà anche l'aggiunta di veth0 e veth1.mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 1000 link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff 3: veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000 link/ether d2:e9:52:18:19:ab brd ff:ff:ff:ff:ff:ff 4: veth0: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000 link/ether f2:f7:5e:e2:22:ac brd ff:ff:ff:ff:ff:ff
Ottimo, ora per assegnare le nostre nuove interfacce allo spazio dei nomi. Nota che ip netns exec viene utilizzato per eseguire comandi all'interno dello spazio dei nomi:
[email protected]:~# ip link set veth1 netns ns1 [email protected]:~# ip netns exec ns1 ip link list 1: lo:ifconfig -a ora mostrerà solo veth0, poiché veth1 è nello spazio dei nomi ns1.mtu 65536 qdisc noop state DOWN mode DEFAULT link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 3: veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000 link/ether d2:e9:52:18:19:ab brd ff:ff:ff:ff:ff:ff
Se vogliamo eliminare veth0/veth1:
ip netns exec ns1 ip link del veth1
Ora possiamo assegnare l'indirizzo IP 192.168.5.5/24 a veth0 sul nostro host:
ifconfig veth0 192.168.5.5/24
E assegna veth1 192.168.5.10/24 entro ns1:
ip netns exec ns1 ifconfig veth1 192.168.5.10/24 up
Per eseguire l'elenco di indirizzi IP sia sul nostro host che all'interno del nostro spazio dei nomi:
[email protected]:~# ip addr list 1: lo:mtu 65536 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000 link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff inet 192.168.3.122/24 brd 192.168.3.255 scope global eth0 inet6 fe80::20c:29ff:fe65:259e/64 scope link valid_lft forever preferred_lft forever 6: veth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 86:b2:c7:bd:c9:11 brd ff:ff:ff:ff:ff:ff inet 192.168.5.5/24 brd 192.168.5.255 scope global veth0 inet6 fe80::84b2:c7ff:febd:c911/64 scope link valid_lft forever preferred_lft forever [email protected]:~# ip netns exec ns1 ip addr list 1: lo: mtu 65536 qdisc noop state DOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 5: veth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 12:bd:b6:76:a6:eb brd ff:ff:ff:ff:ff:ff inet 192.168.5.10/24 brd 192.168.5.255 scope global veth1 inet6 fe80::10bd:b6ff:fe76:a6eb/64 scope link valid_lft forever preferred_lft forever
Per visualizzare le tabelle di routing all'interno e all'esterno dello spazio dei nomi:
[email protected]:~# ip route list default via 192.168.3.1 dev eth0 proto static 192.168.3.0/24 dev eth0 proto kernel scope link src 192.168.3.122 192.168.5.0/24 dev veth0 proto kernel scope link src 192.168.5.5 [email protected]:~# ip netns exec ns1 ip route list 192.168.5.0/24 dev veth1 proto kernel scope link src 192.168.5.10
Infine, per connettere le nostre interfacce fisiche e virtuali, avremo bisogno di un bridge. Colleghiamo eth0 e veth0 sull'host, quindi utilizziamo DHCP per ottenere un IP all'interno dello spazio dei nomi ns1:
[email protected]:~# brctl addbr br0 [email protected]:~# brctl addif br0 eth0 [email protected]:~# brctl addif br0 veth0 [email protected]:~# ifconfig eth0 0.0.0.0 [email protected]:~# ifconfig veth0 0.0.0.0 [email protected]:~# dhclient br0 [email protected]:~# ip addr list br0 7: br0:mtu 1500 qdisc noqueue state UP link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff inet 192.168.3.122/24 brd 192.168.3.255 scope global br0 inet6 fe80::20c:29ff:fe65:259e/64 scope link valid_lft forever preferred_lft forever
A br0 è stato assegnato un IP di 192.168.3.122/24. Ora per lo spazio dei nomi:
[email protected]:~# ip netns exec ns1 dhclient veth1 [email protected]:~# ip netns exec ns1 ip addr list 1: lo:mtu 65536 qdisc noop state DOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 5: veth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 12:bd:b6:76:a6:eb brd ff:ff:ff:ff:ff:ff inet 192.168.3.248/24 brd 192.168.3.255 scope global veth1 inet6 fe80::10bd:b6ff:fe76:a6eb/64 scope link valid_lft forever preferred_lft forever
Eccellente! veth1 è stato assegnato 192.168.3.248/24
Link
IO Digital Sec
Consulente Linux