Looking for Computer Science & Information Technology online
courses ?
Check my new web site: https://www.yesik.it !
Un des avantages que je trouve à User Mode Linux est de pouvoir emmener sur mon portable différents serveurs sous la forme de machines virtuelles que je peux démarrer au gré de mes besoins. Par ailleurs, la totale isolation entre ces machines me permet d'éviter toute interférence ou conflit de configuration. Enfin, j'y vois aussi la possibilité de fournir des serveurs clé en main sous la forme d'images disques pour permettre à mes étudiants d'effectuer des manipulations sur un système, sans devoir nécessairement l'installer eux-même, ni même être root sur leur machine.
D'un point de vue opérationnel, je suis amené à connecter mon portable sur différents réseaux. Donc avec une adresse IP susceptible de changer. Et pourtant, je souhaiterais conserver des adresses IP fixes pour mes serveurs virtuels. Le tout en leur permettant d'accéder tout de même au réseau physique.
Une véritable quadrature du cercle! Heureusement, User Mode Linux – et Linux en général – offrent des possibilités de configuration qui vont me permettre de mettre en place ce réseau User Mode Linux sur machine nomade
Sommaire
Le problème
Donc, je souhaite pouvoir connecter mon portable sur différents réseaux. Qu'elle me soit fournie par DHCP ou configurée manuellement par mes soins, mon portable possédera une adresse qui variera de réseau en réseau. Par ailleurs, je veux pouvoir faire tourner un nombre arbitraire de machines virtuelles sur mon portable, et leur permettre d'accéder au réseau. Tout en conservant leurs adresses IP statiques.
Comme on le constate dans l'illustration ci-contre, avec cette configuration, mon portable va servir d'interface entre les sous-réseaux physique et virtuel. Ici, pour mettre en place cette architecture, je vais opter pour une solution basée sur le routage Linux (forwarding et masquerade) et un switch UML virtuel (uml_switch).
Mise en oeuvre
Réseau physique
Je suppose ici le réseau physique opérationnel. Sur mon portable, l'interface physique est eth1. Dans mon utilisation quotidienne, celle-ci peut avoir une adresse IP statique ou fournie par un serveur DHCP – selon les réseaux auxquels je me connecte. Pour l'instant, je ne vais pas avoir à y toucher. D'ailleurs, même plus tard...
Pour référence, voici la configuration de mon interface physique telle que définie dans /etc/network/interfaces:
allow-hotplug eth1 iface eth1 inet static address 10.129.36.99 netmask 255.255.255.0 network 10.129.36.0 broadcast 10.129.36.255 gateway 10.129.36.245
Réseau virtuel

Installation
Sous Debian vous aurez besoin du paquet uml-utilities pour ce qui suit. Si ce logiciel n'est pas déjà sur votre machine utilisez le gestionnaire de paquets de votre distribution pour procéder à l'installation.
sh# apt-get install uml-utilities
Côté réseau virtuel, je vais devoir faire deux choses:
- Ajouter une interface virtuelle sur l'hôte;
- Ajouter un switch virtuel pour interconnecter les machines virtuelles – et l'interface virtuelle de l'hôte.
L'interface virtuelle va être un tunnel TUN/TAP. Pour plus de renseignement, je vous conseille l'article "Connecter une machine virtuelle UML au réseau physique". En deux mots, ce tunnel est juste un moyen de communication qui ressemble à une interface réseau ordinaire, à ceci près que cette interface est associée à un processus plutôt qu'à une carte réseau.
On peut configurer ce tunnel dans /etc/network/interfaces:
# UML virtual network interface
auto uml-tap
iface uml-tap inet static
address 10.129.37.99
netmask 255.255.255.0
broadcast 10.129.37.255
pre-up tunctl -g uml-net -t uml-tap
pre-up ifconfig uml-tap up
post-down tunctl -d uml-tap
Remarquez que cette interface est définie avec une adresse statique sur le sous-réseau 10.129.37.0/24. J'ai choisi ce sous-réseau, car il n'appartient à aucun des réseaux physiques auxquels je me connecte régulièrement. En particulier, remarquez que l'adresse de cette interface n'est pas sur le même sous-réseau que celle de mon interface physique eth1.
Désormais, au redémarrage de la machine, l'interface virtuelle sera démarrée (grâce à la clause auto uml-tap). Ici, pour la démarrer manuellement, nous pouvons utiliser ifup:
sh# ifup uml-tap Set 'uml-tap' persistent and owned by gid 117
Vous pouvez vous assurer du démarrage de l'interface avec ifconfig:
sh# ifconfig uml-tap uml-tap Link encap:Ethernet HWaddr 00:ff:c1:d9:15:c9 inet addr:10.129.37.99 Bcast:10.129.37.255 Mask:255.255.255.0 inet6 addr: fe80::2ff:c1ff:fed9:15c9/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:26 overruns:0 carrier:0 collisions:0 txqueuelen:500 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
Le switch
Sous Debian, si vous avez installé le paquet uml-utilities, un switch virtuel doit déjà se lancer automatiquement au démarrage de la machine.
La seule modification à faire sera dans le fichier /etc/default/uml-utilities pour préciser qu'un port de notre switch est connecté à l'interface uml-tap. Voici ma configuration:
sh# cat /etc/default/uml-utilities
# Options to pass to uml_switch.
# set to "false" if you want to prevent uml_switch from
# starting with SysV scripts in /etc/init.d
# UML_SWITCH_START="false"
# For preconfigured tap setup, see
# /usr/share/doc/uml-utilities/examples/interfaces.example
UML_SWITCH_OPTIONS="-tap uml-tap"
# User as which to run uml_switch
#UML_SWITCH_USER="uml-net"
# Socket file to use
# Debian's default is:
#UML_SWITCH_CTL="/var/run/uml-utilities/uml_switch.ctl"
#
# if you instead use your rolled up kernel from upstream
# sources you may want to uncomment the following:
#UML_SWITCH_CTL="/tmp/uml.ctl"
Reste à redémarrer le switch:
sh# /etc/init.d/uml-utilities restart Stopping User-mode networking switch: uml_switch. Starting User-mode networking switch: uml_switch.
Connecter une machine
Passons maintenant aux machines virtuelles. Avec UML, la connexion d'une machine virtuelle à un switch tout aussi virtuel peut se faire en ajoutant le paramètre de démarrage eth0=daemon,,unix:
sh$ linux ubda=machine1.cow,lenny-image \
con=pts con0=fd:0,fd:1 \
eth0=daemon,,unix
A ce stade, nous avons doté la machine virtuelle d'une interface réseau (eth0). Et cette interface est reliée au switch (daemon,,unix). Mais, tout comme avec une machine physique, brancher les câbles ne suffit pas. Il faut aussi faire la configuration à proprement parler. Cela peut se faire le plus normalement du monde dans le fichier /etc/network/interfaces de la machine UML:
uml:~# cat > /etc/network/interfaces << EOF auto eth0 iface eth0 inet static address 10.129.37.201 netmask 255.255.255.0 broadcast 10.129.37.255 gateway 10.129.37.99 EOF
Deux points à noter:
- Tout d'abord, ma machine virtuelle est sur le même sous-réseau que l'interface uml-tap de l'interface virtuelle. C'est logique, puisque l'hôte et ses machines virtuelles forment leur propre réseau.
- Ensuite, je définis l'hôte physique comme passerelle. Ici encore, cela se comprend, puisque le seul moyen de sortir pour mes machines virtuelles sera en passant par l'hôte physique.
A nouveau, le réseau sera configuré automatiquement au démarrage (auto eth0) – ou peut être démarré explicitement avec ifup:
uml:~# ifup eth0 uml:~# ifconfig eth0 eth0 Link encap:Ethernet HWaddr 16:52:2d:40:7a:01 inet addr:10.129.37.201 Bcast:10.129.37.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:1 errors:0 dropped:0 overruns:0 frame:0 TX packets:3 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:28 (28.0 B) TX bytes:202 (202.0 B) Interrupt:5
Bien sûr, la configuration d'une (ou plusieurs) autre(s) machine(s) virtuelle(s) UML se fera à l'identique:
sh$ linux ubda=machine2.cow,lenny-image \ con=pts con0=fd:0,fd:1 \ eth0=daemon,,unix [...] uml:~# cat > /etc/network/interfaces << EOF auto eth0 iface eth0 inet static address 10.129.37.202 netmask 255.255.255.0 broadcast 10.129.37.255 gateway 10.129.37.245 EOF
Pour vous assurer du fonctionnement de ce réseau virtuel, vous pouvez pinguer de l'hôte vers une machine virtuelle:
sh$ ping -c 1 10.129.37.201 PING 10.129.37.201 (10.129.37.201) 56(84) bytes of data. 64 bytes from 10.129.37.201: icmp_seq=1 ttl=64 time=0.342 ms --- 10.129.37.201 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.342/0.342/0.342/0.000 ms
Ou d'une machine virtuelle à l'autre:
uml:~# ping -c 1 10.129.37.202 PING 10.129.37.202 (10.129.37.202) 56(84) bytes of data. 64 bytes from 10.129.37.202: icmp_seq=1 ttl=64 time=12.0 ms --- 10.129.37.202 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 12.058/12.058/12.058/0.000 ms
Ou encore d'une machine virtuelle vers l'hôte:
uml:~# ping -c 1 10.129.37.99 PING 10.129.37.99 (10.129.37.99) 56(84) bytes of data. 64 bytes from 10.129.37.99: icmp_seq=1 ttl=64 time=0.397 ms --- 10.129.37.99 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.397/0.397/0.397/0.000 ms
Et l'accès à l'extérieur?
Quand à l'accès au réseau physique externe? Essayons de pinguer une autre machine sur le réseau physique:
uml:~# ping -c 10 10.129.36.243
PING 10.129.36.243 (10.129.36.243) 56(84) bytes of data.
--- 10.129.36.243 ping statistics ---
10 packets transmitted, 0 received, 100% packet loss, time 9112ms
Visiblement, nos pings se sont perdus. C'est compréhensible, car nous avons constitué un réseau en connectant l'hôte (via l'interface uml-tap) et ses machines virtuelles (via leur interface eth0). Mais le réseau virtuel ainsi créé n'est pas relié au réseau physique. Autrement dit, pour permettre aux machines virtuelles de communiquer avec le monde extérieur, il faut encore interconnecter les interfaces uml-tap et eth1 de l'hôte.
Forwarding IP
La solution ici va être d'utiliser la fonctionnalité de forwarding IP du noyau Linux:
sh# echo 1 > /proc/sys/net/ipv4/ip_forward
Avec cette option activée, un paquet arrivant sur une interface réseau, mais qui n'est pas destiné à la machine, sera acheminé automatiquement sur une autre interface si le noyau détermine que ce paquet peut arriver à sa destination par cette route.

Piège:
Je suppose ici que votre noyau a été compilé avec le support pour le forwarding. C'est le cas pour les noyaux standards des distributions les plus courantes. Par contre, à éventuellement vérifier si vous avez compilé vous-même votre noyau.
Facile, rapide ... mais malheureusement insuffisant. En effet:
uml:~# ping -c 4 10.129.36.243
PING 10.129.36.243 (10.129.36.243) 56(84) bytes of data.
--- 10.129.36.243 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3043ms
Histoire de comprendre ce qui se passe, on peut relancer ces pings tout en faisant une capture de trame avec tcpdump sur l'hôte:
sh# tcpdump -c 4 -n -i eth1 icmp listening on eth1, link-type EN10MB (Ethernet), capture size 96 bytes 18:39:46.676641 IP 10.129.37.202 > 10.129.36.243: ICMP echo request, id 3332, seq 1, length 64 18:39:47.696941 IP 10.129.37.202 > 10.129.36.243: ICMP echo request, id 3332, seq 2, length 64 18:39:48.716920 IP 10.129.37.202 > 10.129.36.243: ICMP echo request, id 3332, seq 3, length 64 18:39:49.717056 IP 10.129.37.202 > 10.129.36.243: ICMP echo request, id 3332, seq 4, length 64 4 packets captured 4 packets received by filter 0 packets dropped by kernel

Voilà qui est très instructif: en effet, il apparaît que les requêtes ICMP echo (les pings) sortent. Mais qu'aucune réponse ne revient. Et pourtant, la machine cible est bien active – et je peux même vous dire qu'elle répond! Alors comment se fait-il que cette réponse se perde? L'explication est simple: les machines du réseau physique n'ont aucune connaissance de l'existance de mon réseau virtuel. Et comme il est sur un autre sous-réseau, tout paquet à destination d'une de mes machines virtuelles est transmis ... à la passerelle connue par les machines du réseau physique. Passerelle qui ne sait pas acheminer ces paquets vers mes machines virtuelles. Autrement dit, les paquets repartent par une mauvaise route, et finissent par se perdre.
Normalement, la solution serait de configurer convenablement les tables de routages statiques ou d'utiliser un système de routage dynamique. Mais ici, je suis dans un cas particulier: rappelez-vous que mon réseau virtuel est nomade. Je suis susceptible de l'intégrer à divers réseau. Par ailleurs, sur un certain nombre de ces réseaux, je ne suis pas administrateur. Et même si c'était le cas, l'idée d'aller tripatouiller les tables de routage juste pour me permettre à l'occasion de connecter quelques machines virtuelles ne me semblerait pas des plus souhaitables. Donc il me faut suivre une autre piste...
Masquerading
Une solution est d'utiliser une technique de mascarade (masquerading), pour cacher les adresses IP fixes de mes machines virtuelles derrière l'adresse IP de l'hôte sur le réseau physique. De cette manière, vu du réseau physique, tous les paquets qui sortiront de mon portable porteront son adresse IP. En supposant que le réseau physique qui m'accueille soit bien configuré, je pourrai donc recevoir les réponses. Et quand mon portable recevra ces réponses, l'opération de mascarade sera inversée, et l'hôte fera suivre (on retrouve ici le forwarding) les paquets à leur destinataire virtuel.

Sous Linux – et cette fois encore sous réserve que votre noyau ait été compilé avec les options adéquates – l'activation de ce système nécessite juste le chargement du module netfilter ipt_MASQUERADE, et l'activation de cette fonctionnalité sur l'interface sortante:
sh# modprobe ipt_MASQUERADE sh# iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE # Si ce n'est pas déjà fait, activer le "forwarding" sh# echo 1 > /proc/sys/net/ipv4/ip_forward

netfilter et iptables
Vous êtes peut-être plus familier de l'utilisation de netfilter et iptables pour mettre en place un firewall. Mais de par leur aspect modulaire, ces logiciels peuvent faire bien plus. Ici, nous utilisons les capacités de translation d'adresse sortante (SNAT) de netfilter pour masquer l'adresse des machines virtuelles derrière celle de leur hôte.
Après toutes ces manipulations, un petit test va nous permettre de vérifier que notre configuration fonctionne convenablement:
uml:~# ping -c 4 10.129.36.243
PING 10.129.36.243 (10.129.36.243) 56(84) bytes of data.
64 bytes from 10.129.36.243: icmp_seq=1 ttl=254 time=50.4 ms
64 bytes from 10.129.36.243: icmp_seq=2 ttl=254 time=1.83 ms
64 bytes from 10.129.36.243: icmp_seq=3 ttl=254 time=2.01 ms
64 bytes from 10.129.36.243: icmp_seq=4 ttl=254 time=1.90 ms
--- 10.129.36.243 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3044ms
rtt min/avg/max/mdev = 1.831/14.043/50.424/21.004 ms
Configuration permanente
Pour terminer nous allons faire en sorte que la configuration forwarding et masquerade soit permanente. Sous Debian, il est possible d'utiliser /etc/network/interfaces. Voici les modification que j'ai faites dans la configuration de l'hôte:
iface uml-tap inet static address 10.129.37.99 netmask 255.255.255.0 broadcast 10.129.37.255 pre-up tunctl -g uml-net -t uml-tap pre-up ifconfig uml-tap up post-down tunctl -d uml-tap post-up modprobe ipt_MASQUERADE post-up iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE post-up echo 1 > /proc/sys/net/ipv4/ip_forward
Comme vous le voyez, cela se limite essentiellement à recopier les commandes que nous avons entrées manuellement dans la section post-up de l'interface uml-tap. De cette manière, elles seront automatiquement exécutées après le démarrage (post-up) de l'interface correspondante. A titre d'exercice, je vous laisse ajouter les sections réciproques pre-down pour désactiver ces fonctionnalités en même temps que l'interface.
DNS
A strictement parler, ça y est: les machines de mon réseau virtuel peuvent accéder en toute transparence au réseau physique sur lequel je connecte mon portable.
Néanmoins, il y a au moins un service d'utilisation courante qui mériterait d'être un peu mieux intégré dans cette architecture: le DNS.
Ici, mon idée va être de faire en sorte que toutes mes machines virtuelles utilisent leur hôte comme serveur DNS, et que celui-ci fasse suivre les requêtes au vrai serveur DNS du réseau qui l'accueille. C'est le principe d'un proxy – bien que dans ce contexte, on parle plus volontiers de serveur de cache DNS (DNS forwarder).
Dnsmasq is a lightweight, easy to configure DNS forwarder and DHCP server. It is designed to provide DNS and, optionally, DHCP, to a small network.
Pour atteindre ce but, on a le choix entre plusieurs logiciels. Ici, je vais laisser de côté l'omniprésent bind pour m'intéresser à une solution plus légère: Dnsmasq.
Sous Debian, le paquet dnsmasq est disponible dans les dépôts standard. L'installation se résume donc à sa plus simple expression:
sh# apt-get install dnsmasq
Un des avantages que je vois à dnsmasq ici, est qu'il honore le contenu de /etc/resolv.conf. Autrement dit, il fera suivre les requêtes DNS entrantes sur un des DNS configurés sur l'hôte. Non seulement cela limite la configuration de dnsmasq à proprement parler, mais surtout, cela me garantit la synchronisation avec les informations obtenues automatiquement par DHCP.
Deux changements de configuration à effectuer. Le premier, sur l'hôte, pour n'accepter les requêtes DNS entrantes que sur l'interface du réseau virtuel, à savoir uml-tap:
sh# cat /etc/dnsmasq.conf
[...]
# If you want dnsmasq to listen for DHCP and DNS requests only on
# specified interfaces (and the loopback) give the name of the
# interface (eg eth0) here.
# Repeat the line for more than one interface.
interface=uml-tap
[...]
sh# /etc/init.d/dnsmasq restart
Restarting DNS forwarder and DHCP server: dnsmasq.
Seconde modification, cette fois sur la machine virtuelle, pour lui indiquer d'utiliser son hôte comme serveur DNS:
uml:~# echo 'nameserver 10.129.37.99' > /etc/resolv.conf

Piège:
Bien que l'utilité en soit limitée sur une machine virtuelle UML avec l'architecture proposée ici, si vous avez installé resolvconf sur votre machine virtuelle, la modification est plutôt à faire dans /etc/network/interfaces:
iface eth0 inet static
address 10.129.37.201
netmask 255.255.255.0
broadcast 10.129.37.255
gateway 10.129.37.99
dns-nameservers 10.129.37.99
Ce sera alors resolvconf qui mettra à jour /etc/resolv.conf quand l'interface réseau sera activée.
Tout est en ordre? Procédons à un test pour vérifier:
uml:~# ping -c 4 www.google.com PING www.l.google.com (209.85.227.103) 56(84) bytes of data. 64 bytes from wy-in-f103.1e100.net (209.85.227.103): icmp_seq=1 ttl=52 time=87.0 ms 64 bytes from wy-in-f103.1e100.net (209.85.227.103): icmp_seq=2 ttl=52 time=101 ms 64 bytes from wy-in-f103.1e100.net (209.85.227.103): icmp_seq=3 ttl=52 time=115 ms 64 bytes from wy-in-f103.1e100.net (209.85.227.103): icmp_seq=4 ttl=52 time=81.4 ms --- www.l.google.com ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3042ms rtt min/avg/max/mdev = 81.481/96.574/115.825/13.410 ms
Visiblement, la résolution du nom www.google.com bien eu lieu – et les pings passent.
Conclusion
Au chapitre des améliorations, on pourrait envisager d'ajouter quelques règles iptables pour sécuriser l'accès aux machines virtuelles. Et, dans l'esprit de ce qui a été présenté pour le DNS, on pourrait aussi envisager de mettre un proxy http – histoire d'abstraire totalement les machines virtuelles du réseau physique sur lequel l'hôte est connecté pour l'accès au web. Ou agir de même pour n'importe quel autre service que vous pourriez être tenté d'utiliser.
En tous cas, à l'issue de cet article, mon réseau virtuel nomade est opérationnel. Certes UML n'est pas nécessairement aussi adapté au déploiement en production que des solutions comme Xen. Mais au moins cela me permet de disposer à la fois de serveurs plugables et d'une plate-forme permettant de mettre en pratique les fonctionnalités réseau d'un système Linux. Bref, une solution légère et facile à mettre en oeuvre – idéale pour faire des démonstrations ou du développement!
Ressources
- (en) Carla Schroder. Linux Networking Cookbook. O'Reilly, 2007. ISBN 978-0-596-10248-7.