Florent Peterschmitt

Linux Kernel Network Namespaces et dnsmasq

Modification du 28/06/2017 : utilisation de la fonctionnalité intégrée pour la gestion du resolv.conf

Bien souvent, nos clients nous mettent à disposition des VPN pour pouvoir les aider à distance pour les cas de support.

Parmis ces VPN, on a Juniper Network Connect, qui touche aux routes et ne permet pas de les modifier (il les remet comme il le souhaite).

Du coup aujourd’hui je suis obligé de passer par une VM/conteneur. C’est peu pratique, mais on va voir comment s’en sortir avec les namespaces réseau et dnsmasq.

Network Namespaces

Un article très intéressant détail ce que sont les namespaces, et je vous en recommande la lecture avant de lire la suite ici. Car en bonne feignasse... voilà.

En gros :

With network namespaces, you can have different
and separate instances of network interfaces and
routing tables that operate independent of each other.

Un peu de théorie

L’objectif est d’avoir un environnement réseau indépendant du reste du système, pour que la connexion VPN puisse y vivre sa vie, sans venir m’enquiquiner sur le reste de mes connexions.

Pour y arriver, j’ai choisi d’utiliser une configuration natée (le VPN est ipv4 uniquement) avec une interface virtuelle qui servira de pont entre mon interface physique et l’interface du namespace.

Mise en place du namespace

Création du namespace :

ip netns add juniper

Mais où sont passées nos interfaces ! ;-)

ip netns exec juniper ip l
ip netns exec juniper iptables -nL

On créer une paire d’interfaces :

  • jun1 : interface dédiée au namespace
  • jun0 : pont entre le lien physique (eth0) et l’interface du namespace (jun1)
ip link add jun0 type veth peer name jun1

On migre jun1 dans le namespace :

ip link set jun1 netns juniper
ip netns exec juniper ip l # Oooooh !

On configure le réseau comme on le ferait avec n’importe quel système. Note : j’utilise ip mais on pourrait utiliser ifconfig...

ip netns exec juniper ip a add 10.252.0.1/24 dev jun1
ip netns exec juniper ip l set jun1 up
ip netns exec juniper ip r add default via 10.252.0.254 dev jun1

On positionne jun0 comme passerelle :

ip a add 10.252.0.254/24 dev jun0
ip l set jun0 up
iptables -t nat -A POSTROUTING -s 10.252.0.0/24 ! -d 10.253.0.0/24 -j MASQUERADE

Voilà, notre namespace réseau juniper est configuré et actif. Maintenant on va pouvoir lancer notre VPN dans ce namespace :

ip netns exec juniper openconnect --juniper <endpoint>

Une histoire de résolution

Nouvelle version

Merci à Schnouki de m’avoir notifié de cette fonctionnalité, bien, bien plus simple :

mkdir -p /etc/netns/juniper
touch /etc/netns/juniper/resolv.conf

juniper est à remplacer par le nom de votre namespace évidemment :)

Et voilà c’est fini !

Vieille version

Le problème avec ce VPN, c’est le fichier /etc/resolv.conf qui sera écrasé par openconnect. Hé oui, on a isolé le réseau, mais rien de plus. Donc on va toujours pouvoir faire ping 8.8.8.8, mais par contre pour la résolution... ça va être compliqué.

Ou pas : avec dnsmasq, on peut faire ce qu’on veut !

Du coup, comme /etc/resolv.conf est partagé pour tous les namespaces… mais que chaque namespace dispose de sa propre pile réseau… on va pouvoir lancer un dnsmasq sur le système hôte, et un autre dans le namespace : chacun écoutera sur le port 53 de son namespace, et n’entrera pas en conflict avec son voisin !

Le but du jeu, c’est que le dnsmasq du namespace fasse les résolutions DNS pour ce qui concerne notre VPN, et l’autre fasse les résolutions habituelles.

En imaginant que mon VPN me donne accès à un réseau 10.0.0.0/8, que le serveur DNS "du VPN" soit 10.9.8.253 et que le domaine soit domaine.prive :

/etc/dnsmasq.conf

server=/domaine.prive/10.9.8.253
/etc/resolv.conf :
nameserver 127.0.0.1
nameserver <votre DNS public favoris>

Avec cette conf, dnsmasq va tout seul comprendre qu’il faudra forwarder les requêtes auxquels il ne peut répondre aux autres serveurs DNS configurés (resolv.conf), mais va en revanche contacter les serveurs DNS pour les domaines précisés, ici domaine.prive.

Une fois qu’on a fait ça, on démarre dnsmasq sur notre "hôte", et dans le namespace :

# on se met bien dans notre namespace
ip netns exec juniper /bin/bash
ip a add 127.0.0.1/8 dev lo
ip l set lo up
dnsmasq -d

On pourra même se targuer d’un chattr +i /etc/resolv.conf pour que personne ne vienne casser la conf. Et là, on est royal :

ip netns exec juniper su - wrk -c "export DISPLAY=:0; firefox"

Notre firefox est lancé dans un namespace réseau, donc il est dans le VPN juniper, pendant que le reste de notre système vit sa vie.

Un petit schéma peut-être ?

schema