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

Le problème

Réseau UML.png

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:

  1. Ajouter une interface virtuelle sur l'hôte;
  2. 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:

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
L'hôte a accès au réseau virtuel et au réseau physique. Mais il manque quelque-chose entre les interfaces eth1 et uml-tap pour interconnecter ces deux réseaux.

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
Dans la configuration avec le forwarding seul, les requêtes trouvent bien leur destination car la machine virtuelle source est configurée pour les faire transiter par son hôte – qui sert pour l'occasion de passerelle. Par contre, la machine destination est incapable de retrouver le chemin de la machine virtuelle pour lui répondre. Elle transmet donc la réponse à la passerelle du réseau physique ... qui elle non plus ne connait pas la route vers la machine virtuelle. Les réponses sont donc perdues.

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.

Grâce au système de mascarade (maquerading), quand une machine virtuelle fera une requête, l'hôte substituera sa propre adresse IP dans le paquet à la place de l'adresse de la machine virtuelle. Les paquets pourront donc être acheminés et les réponses reviendront exactement comme si l'hôte en était à l'origine. Mais au retour, ce dernier se souviendra que le véritable destinataire est une des machines virtuelles, et il lui fera suivre le paquet.

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.

http://thekelleys.org.uk/dnsmasq/doc.html

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