[Linux] Catturare il traffico di rete di un singolo processo usando i namespace di rete
L’altro giorno dovevo controllare il traffico generato da un programma (Skype). La mia macchina ovviamente sputa fuori un sacco di dati in rete (firefox aperto con diverse tab con connessioni http, client di chat, lettori di feed).
L’approccio classico per questo tipo di cose è: apro tcpdump/wireshark, penso un po’ alle porte tcp/udp e indirizzi che mi aspetto grossomodo di vedere uscire dal programma e filtro su quello. Oppure avvio tutto senza filtri e spero nella valanga di roba che esce di beccare quello che mi interessa (tanti auguri quando avete molto traffico…). Oppure se proprio voglio sprecare risorse avvio una macchina virtuale (solo per questo? really?).
Un altro approccio è lanciare il programma interessato con un GID (creato appositamente per l’applicazione) diverso da quello normale e poi, utilizzando il modulo owner (-m owner --gid
) di iptables, identificare tutti i pacchetti di quel gruppo e con il target ULOG tirarci fuori un file pcap.
Sinceramente però avevo poca voglia di fare così e ho appena imparato ad usare i namespace di rete su Linux (thanks Max!), quindi ho provato un altro modo un po’ come “esercizio di stile” (cit.). Non prendetela troppo come una roba per final users, ma semmai per p0w3r u5erZ.
Su Linux sono stati introdotti, ormai da un po’, diversi namespace. In particolare per quanto riguarda la rete, abbiamo la possibilità di avviare un processo in un namespace di rete diverso da quello di default. In questo modo il processo non vedrà nessuna interfaccia, nessuna regola di routing, nessun indirizzo ip, nessuna regola di iptables. In pratica è come se, esclusivamente dal punto di vista della rete, il processo si trova in un sistema “nuovo”. Tra l’altro questo fatto coinvolge anche tutti i processi figli. Poi volendo ci sono anche i namespace PID, UID, mount, …, insomma usandoli tutti possiamo fare più o meno una macchina virtuale “leggera”, che gira sempre sopra lo stesso kernel.
Detto questo bisogna sapere che è anche possibile creare delle interfacce di rete “tunnel” tra un namespace di rete e l’altro.
Utilizzeremo un tool per “maneggiare” i namespace nel kernel Linux (basta avere una versione del kernel >=2.6.26). Il tool è “unshare” e dovreste trovarlo già installato all’interno della vostra distribuzione. Fa solitamente parte del pacchetto util-linux. Un tool molto simile (alternativo) che ha qualche funzionalità in più è “vspace” del pacchetto “util-vserver” (che però dovete installare quasi sicuramente a parte).
A questo punto possiamo aprire un processo bash all’interno di un nuovo namespace:
$ sudo unshare --net /bin/bash
sudo è necessario perché viene fatta una syscall (per la precisione la clone con flags=CLONE_VFORK|CLONE_NEWNET|SIGCHLD ) che richiede privilegi di superutente.
Ok, ora nella bash che abbiamo lanciato noteremo l’ assenza di interfaccie di rete (eccetto il loopback, che comunque mancherà di indirizzo ip):
$ ip link show
5: lo: mtu 16436 qdisc noop state DOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
Di regole di routing:
$ ip route show
$
E di regole di iptables
$ iptables -nvL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
$ ip6tables -nvL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Possiamo per prima cosa assegnare un indirizzo (IPv4 e IPv6) all’interfaccia di loopback, che potrebbe servire per IPC tra processi:
$ ip addr add 127.0.0.1/8 dev lo
$ ip addr add ::1/128 dev lo
$ ip link set dev lo up
Adesso ci serve tirare su una comunicazione verso l’esterno, quindi utilizzeremo due interfacce di rete virtuali, che faranno da “ponte” tra il namespace che abbiamo creato e quello “globale” (quello “solito” insomma).
Quindi da un altro terminale fuori dal namespace che abbiamo creato, facciamo un:
$ sudo ip link add name antani0 type veth peer name antani1
Questo ci creerà nel namespace “globale” due interfacce: antani0 e antani1, “finte” ethernet, collegate tra di loro in un unico dominio di collisione. Ora abbiamo la possibilità di spostare una delle interfacce che abbiamo creato, all’interno del nuovo namespace.
Per identificare il namespace di rete che abbiamo creato, possiamo cercare nella lista dei processi il PID del nostro bash “speciale” e segnarcelo. A questo punto facendo:
$ sudo ip link set dev antani1 netns /proc/$PID_BASH/ns/net
Diremo di spostare l’interfaccia antani1 all’interno del namespace di rete utilizzato dal nostro processo bash “speciale”.
E ora è tutto semplice dato che abbiamo un collegamento con il mondo “esterno” al namespace. Possiamo mettere in bridge l’interfaccia con una fisica o fare un NAT. Mostrerò questa seconda soluzione (che è quella che ho provato io per poter fare degli esperimenti appunto con NAT e skype). Questo non significa che sia una buona idea fare un NAT.
Diamo un indirizzo all’interfaccia fuori dal namespace e diamo qualche comando per abilitare il NAT:
$ sudo ip addr add 172.16.0.1/24 dev antani0
$ sudo ip link set dev antani0 up
$ sudo su -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
$ sudo iptables -P FORWARD ACCEPT # Per semplicità, se avete la policy drop aggiungete quelle due regole in più..
$ sudo iptables -t nat -A POSTROUTING -s 172.16.0.0/24 -j MASQUERADE
E dentro la nostra bash speciale, dove vedremo comparsa la nuova interfaccia, assegnamo un altro ip e impostiamo il gateway di default:
$ sudo ip addr add 172.16.0.2/24 dev antani1
$ sudo ip link set dev antani1 up
$ sudo ip route add default via 172.16.0.1 dev wlan0
A questo punto ci basta avviare fuori dal namespace tcpdump o wireshark in ascolto sull’interfaccia antani0 e lanciare dalla nostra bash “speciale” skype (o qualunque altro programma di cui vogliamo catturare il traffico).
In realtà se notate ho lasciato sottointeso un passaggio: skype e qualunque altro programma lancerete nel nuovo namespace utilizzerà sempre le solite informazioni per il DNS, quelle salvate in /etc/resolv.conf e /etc/hosts. Queste informazioni risiedono sul filesystem e quindi non sono affette dal namespace di rete, saranno dunque uguali al namespace “globale”.
A me serviva però avere dei DNS diversi per skype, rispetto a quelli globali di sistema. D’altro canto non volevo cambiare quelli di sistema perché poi mi avrebbero rallentato la risoluzione nomi di tutti i programmi.
Fortunatamente c’è anche un modo per risolvere questo problema! Ed è grazie al namespace di mount!
Infatti oltre al namespace di rete potete abilitare anche il namespace di mount. Questo significa che quando lanciate un programma in un nuovo namespace di mount, vedrete tutti i mount “soliti”, però quelli nuovi che farete dall’interno del namespace saranno visibili solo a voi e non all’esterno.
Quindi rifacciamo la procedura di prima cambiando il primo comando in:
sudo unshare --net --mount /bin/bash
E avremo sia un nuovo namespace di rete che un nuovo namespace di mount.
Ma perché voglio usare il namespace di mount? Pochi sanno (e non lo sapevo neanche io) che è possibile montare un singolo file sopra un’altro già esistente, usando mount –bind. Montando un file sopra uno esistente, il risultato sarà che accedendo a quel file vedremo solo l’ultima “versione” montata.
Possiamo allora creare da qualche parte una versione sostituiva di /etc/resolv.conf, ad esempio in /tmp/, e scriverci dentro quel cavolo che ci pare, poi, dall’interno del nostro namespace di mount (e rete) faremo tipo:
mount --bind /tmp/resolv.conf /etc/resolv.conf
Ed è fatta! In questo modo abbiamo messo una versione “nuova” di /etc/resolv.conf sopra a quella vecchia, nascondendola, e questa modifica sarà visibile solo dall’interno del nostro bash speciale, dato che stiamo operando, come già detto, in un namespace di mount diverso. Fuori dal namespace di mount nuovo continueremo a vedere la vecchia versione del file. Ci serve farlo anche per /etc/host o /etc/nsswitch.conf (altro file interrogato per risolvere i DNS)? Basta ripetere il mount anche per quegli altri files!
Tra l’altro, se siete interessati, è possibile anche spostare una interfaccia di rete esistente, tipo eth0, sfruttando lo stesso comando visto prima per “spostare” le interfacce! Anche questo può tornare molto utile
Tutto questo non è fantastico?
Tra l’altro unendo queste cose con anche il namespace PID e user penso sia possibile creare una sandbox seria per skype. Se qualcuno si sta chiedendo perché sandboxare skype forse è meglio che vada a informarsi su cosa è successo un po’ di mesi fa quando una versione deoffuscata del programma è girata in rete… Oltre a questo resta il fatto che è un software proprietario con codice offuscato e che funziona in modo abbastanza strano. Farlo girare in una VM forse è esagerato (e anche scomodo), però con questi trucchetti si possono fare (a mio parere) cose molto interessanti, sia per sandboxare programmi che per fare test di networking vari.
Filed under: diario, hgcomo, informatica | 1 Comment
Tag:catturare, iptables, linux, lxc, mount, namespace, pcap, processo, traffico, util-vserver, vspace
Ho cambiato utilizzando il comando unshare invece di vspace. Unshare è già integrato all’interno di molte distro