Émulation d'architectures réseaux avec netkit
Cet article est issu de l'article publié dans le numéro de MISC 40 dont je suis l'auteur.
Tous droits réservés à MISC.
La virtualisation de serveurs et de postes de travail est aujourd'hui une pratique extrêmement répandue. Elle est devenue indispensable à tous les administrateurs systèmes pour effectuer des tests de pré-production ou optimiser les ressources matérielles des data centers. Nous exposons dans cet article ce que les mécanismes de virtualisation peuvent apporter aux administrateurs et architectes réseaux dans une optique de prototypage. Ces outils servent également dans le cadre d'enseignement ou comme outil d’étude pour le pen-tester souhaitant répliquer l'architecture d'une partie du réseau audité pour effectuer des tests avant de lancer l'attaque en réel.
La reproduction en laboratoire de réseaux réalistes avec du matériel dédié est souvent laborieuse Le matériel est volumineux, cher et surtout extrêmement bruyant. Virtualiser des réseaux complexes à partir d'un simple poste de travail présente de nombreux avantages.
Les briques présentées permettent par exemple de tester l'attaque contre DNS décrite par Dan Kaminsky dans différentes configurations et avec une architecture DNS complète (root(s) serveur(s), master(s) de zone, cache(s) et forwarder(s)) sur un simple PC sous Linux.
Elles peuvent également être mises à contribution pour reproduire l'attaque contre youtube par Pakistan Telecom (quelle bande de farceurs !) [ http://www.ripe.net/news/study-youtube-hijacking.html ] ou encore tester l'attaque décrite Anton Kapela et Alex Pilosov sur le détournement de trafic réseau exploitant les faiblesses de BGP et présenté à la DefCon 2008.
Présentation de netkit
Netkit [http://www.netkit.org] est un logiciel libre sous licence GPL développé par le groupe "computer network research" de l'université italienne Rome 3 et par le LUG (Linux User Group) "Roma 3".
Il est composé de différents scripts permettant le lancement et l'arrêt des machines virtuelles, d'un noyau Linux UML et de l'image d'une distribution issue de Debian disposant d'outils liés au réseau dont voici une liste non exhaustive :
Il est composé de différents scripts permettant le lancement et l'arrêt des machines virtuelles, d'un noyau Linux UML et de l'image d'une distribution issue de Debian disposant d'outils liés au réseau dont voici une liste non exhaustive :
- Support de 802.1q (VLAN), 802.1d (Spanning Tree).
- Démons de routage dynamiques (quagga) avec support de MPLS et distribution de labels par LDP.
- Support des tunnels IPsec, GRE et MPLS (IP over MPLS et Ethernet over MPLS).
- Support d'IPv6.
- Outils de filtrage (iptables, ebtables).
- DNS coté serveur (Bind) et client (host et dig).
- SMTP avec Exim.
- FTP avec Proftpd et TFTP avec atftpd ainsi que des clients respectifs.
- Apache, Squid et différents clients web en mode texte.
- Telnet et ssh cotés clients et serveurs.
- Samba.
- Outils de sniffing (tcpdump, ettercap, dnsiff, ...)
- Outils de détection d'intrusion (snort, arpwatch, ...)
- Outils offensifs (nmap, hping, arpspoof, ...)
- ...
Le site diffusant netkit met également à disposition un très grand nombre de laboratoires prêts à fonctionner (architecture DNS complète, routage dynamiques RIP, BGP, …) avec une documentation associée décrivant de manière détaillée le fonctionnement.
Les machines virtuelles peuvent accéder à l’extérieur via une interface TAP (ce sont des interfaces virtuelles sous Linux liée à un ou des processus). Dès lors. les machines netkit étant dérivées de DEBIAN, un simple "apt-get install" permettra d'installer n'importe quel paquet de la branche "unstable", le fichier sources.list n'a même pas besoin d'être édité pour ceci.
Netkit fonctionne sur Linux et son installation extrêmement simple ne sera pas détaillée. Il suffit de télécharger les trois archives correspondantes respectivement aux scripts, au noyau Linux UML et au système de fichier, de les décompresser et d'initialiser quelques variables d'environnement. L'installation ne nécessite pas les droits root et il en est de même pour l'utilisation sauf pour la création éventuelle d’une interface TAP sur le système hôte.
Découverte au travers d'un premier lab
Lancement de deux PC
Nous commençons par lancer deux machines virtuelles dans un même LAN que nous appellons "A".
$ vstart client --eth0=A
$ vstart pirate --eth0=A
$ vstart pirate --eth0=A
Un terminal X est associé à chacune des machines virtuelles (« client » et « pirate ») lors de leur création. Chacune dispose d'une interface réseau (eth0) relié au même hub virtuel du domaine de broadcast « A » (c'est-à-dire sur le même domaine de collision).Tous les paquets émis sur celui-ci seront transmis à toutes les interfaces des machines netkit connectées à ce hub.
Attention : remplacer le hub virtuel par un switch Il est également possible de remplacer les hubs virtuels par des switchs virtuels en modifiant les scripts de lancement de netkit. Pour ceci il conviendra d'éditer le fichier bin/script_utils et de remplacer la ligne suivante: HUB_COMMAND="$NETKIT_HOME/bin/uml_switch -tap $TAP_DEVICE -hub -unix $1 </dev/null 2>&1" par HUB_COMMAND="$NETKIT_HOME/bin/uml_switch -tap $TAP_DEVICE -unix $1 </dev/null 2>&1" et la ligne suivante: HUB_COMMAND="$NETKIT_HOME/bin/uml_switch -hub -unix $1 </dev/null 2>&1" par HUB_COMMAND="$NETKIT_HOME/bin/uml_switch -unix $1 </dev/null 2>&1" Il est également possible de créer une machine netkit faisant office de bridge comme nous le verrons par la suite. |
Nous disposons maintenant de deux machines virtuelles raccordées toutes les deux au hub virtuel "A", il ne reste plus qu’à configurer nos deux cartes réseaux :
Sur "client":
client:~# ifconfig eth0 172.30.0.1
Puis sur “pirate”:
pirate:~# ifconfig eth0 172.30.0.66
pirate:~# arp -an
pirate:~# ping -c 1 172.30.0.1
PING 172.30.0.1 (172.30.0.1) 56(84) bytes of data.
64 bytes from 172.30.0.1: icmp_seq=1 ttl=64 time=10.7 ms
--- 172.30.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 10.789/10.789/10.789/0.000 ms
routeur:~# arp -an
? (172.30.0.1) at CE:38:C3:A7:D6:72 [ether] on eth0
pirate:~# arp -an
pirate:~# ping -c 1 172.30.0.1
PING 172.30.0.1 (172.30.0.1) 56(84) bytes of data.
64 bytes from 172.30.0.1: icmp_seq=1 ttl=64 time=10.7 ms
--- 172.30.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 10.789/10.789/10.789/0.000 ms
routeur:~# arp -an
? (172.30.0.1) at CE:38:C3:A7:D6:72 [ether] on eth0
Routeur avec accès à l'extérieur
Pour accéder à l’extérieur, nous créons un routeur disposant d’une interface eth0 sur le hub virtuel « A » et d’une interface eth1 (interface TAP du système hôte) afin de faire communiquer l’ensemble des machines virtuelles vers l’extérieur et donc de pouvoir installer de nouveaux paquets.
Le réseau ressemble alors à ceci :
Le réseau ressemble alors à ceci :
Attention : interfaces TAPLa création de machine virtuelle disposant d'interface TAP nécessite que l'utilisateur dispose des droits root sur le système hôte. La commande peut être lancée avec les droits sans privilège, l'utilisateur sera invité, au moment de la création de l'interface TAP, à fournir le mot de passe root. |
Nous pouvons noter l'apparition de l'interface nk_tap_cedric avec l'adresse IP 10.0.0.1 sur le système hôte :
$ vstart routeur --eth1=tap,10.0.0.1,10.0.0.2 --eth0=A
$ ifconfig
(...)
nk_tap_cedric Link encap:Ethernet HWaddr 00:ff:c4:e2:54:9a
inet adr:10.0.0.1 Bcast:10.255.255.255 Masque:255.0.0.0
adr inet6: fe80::2ff:c4ff:fee2:549a/64 Scope:Lien
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
Packets reçus:0 erreurs:0 :0 overruns:0 frame:0
TX packets:26 errors:0 dropped:4 overruns:0 carrier:0
collisions:0 lg file transmission:500
Octets reçus:0 (0.0 B) Octets transmis:3817 (3.7 KB)
De même une interface eth1 avec l'IP 10.0.0.2 a été créée sur la machine virtuelle "routeur" qui peut maintenant joindre l'extérieur.
En outre, dans sa grandeur et afin de nous faciliter la vie, netkit a effectué les opérations suivantes sur le système hôte :
$ ifconfig
(...)
nk_tap_cedric Link encap:Ethernet HWaddr 00:ff:c4:e2:54:9a
inet adr:10.0.0.1 Bcast:10.255.255.255 Masque:255.0.0.0
adr inet6: fe80::2ff:c4ff:fee2:549a/64 Scope:Lien
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
Packets reçus:0 erreurs:0 :0 overruns:0 frame:0
TX packets:26 errors:0 dropped:4 overruns:0 carrier:0
collisions:0 lg file transmission:500
Octets reçus:0 (0.0 B) Octets transmis:3817 (3.7 KB)
De même une interface eth1 avec l'IP 10.0.0.2 a été créée sur la machine virtuelle "routeur" qui peut maintenant joindre l'extérieur.
En outre, dans sa grandeur et afin de nous faciliter la vie, netkit a effectué les opérations suivantes sur le système hôte :
- ajout d’une règle de translation d'adresse ;
- ajout d’une règle autorisant les flux émis par notre interface TAP ;
- activation du routage :
$ sudo iptables-save
# Generated by iptables-save v1.3.8 on Mon Aug 11 22:30:56 2008
*nat
:PREROUTING ACCEPT [1:105]
:POSTROUTING ACCEPT [1:149]
:OUTPUT ACCEPT [13:2343]
-A POSTROUTING -j MASQUERADE
COMMIT
# Completed on Mon Aug 11 22:30:56 2008
# Generated by iptables-save v1.3.8 on Mon Aug 11 22:30:56 2008
*filter
:INPUT ACCEPT [41715:40819632]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [35749:4377205]
-A FORWARD -i nk_tap_+ -j ACCEPT
COMMIT
# Completed on Mon Aug 11 22:30:56 2008
$ cat /proc/sys/net/ipv4/ip_forward
1
Netkit a également ajouté une règle de routage sur le routeur précisant que la passerelle par défaut est l’interface TAP du système hôte.
Il ne reste plus qu'à finir de configurer le DNS sur notre routeur (fichier /etc/resolv.conf), son adresse interne (disons 172.30.0.250) et de s'assurer que le routage est activé (il l'est par défaut sur toutes les machines netkit).
Il ne reste plus qu'à finir de configurer le DNS sur notre routeur (fichier /etc/resolv.conf), son adresse interne (disons 172.30.0.250) et de s'assurer que le routage est activé (il l'est par défaut sur toutes les machines netkit).
routeur:~# cat > /etc/resolv.conf
nameserver 192.168.2.1
routeur:~# ifconfig eth0 172.30.0.250
routeur:~# ping -c 1 www.google.com
PING www.l.google.com (209.85.129.99) 56(84) bytes of data.
64 bytes from fk-in-f99.google.com (209.85.129.99): icmp_seq=1 ttl=241 time=64.1 ms
--- www.l.google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 64.163/64.163/64.163/0.000 ms
nameserver 192.168.2.1
routeur:~# ifconfig eth0 172.30.0.250
routeur:~# ping -c 1 www.google.com
PING www.l.google.com (209.85.129.99) 56(84) bytes of data.
64 bytes from fk-in-f99.google.com (209.85.129.99): icmp_seq=1 ttl=241 time=64.1 ms
--- www.l.google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 64.163/64.163/64.163/0.000 ms
Puis sur « client » et « pirate » :
client:~# cat > /etc/resolv.conf
nameserver 192.168.2.1
client:~# route add default gw 172.30.0.250
nameserver 192.168.2.1
client:~# route add default gw 172.30.0.250
Cela suffit-il pour que les machines autres que le routeur puissent joindre l'extérieur?
Non! Le système hôte sera incapable de router les paquets retour. Il n'a en effet aucune connaissance du plan d'adressage internet des machines netkit (dans notre cas 172.30.0.0/16). Nous avons deux solutions :
- Réaliser une seconde translation d'adresse au niveau du routeur pour que les paquets qu'il émet vers l'interface TAP le soient avec l'adresse IP 10.0.0.2.
- Ajouter une route sur le système hôte pour router les paquets 172.30.0.0/16 vers l'interface TAP.
C'est la première solution que nous choisirons.
Sur le routeur :
routeur:~# iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
Vérifions que tout fonctionne comme escompté sur le client :
client:~# ping -c 1 www.google.com
PING www.l.google.com (209.85.129.104) 56(84) bytes of data.
64 bytes from fk-in-f104.google.com (209.85.129.104): icmp_seq=1 ttl=240 time=64.0 ms
--- www.l.google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 64.071/64.071/64.071/0.000 ms
PING www.l.google.com (209.85.129.104) 56(84) bytes of data.
64 bytes from fk-in-f104.google.com (209.85.129.104): icmp_seq=1 ttl=240 time=64.0 ms
--- www.l.google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 64.071/64.071/64.071/0.000 ms
Il est maintenant possible d'installer des paquets supplémentaires sur les machines virtuelles ou de mettre à jour les paquets présents. Pour ceci il est toutefois nécessaire d'augmenter la mémoire de la machine concernée grâce à l'option -M afin d'utiliser apt-get confortablement.
Attaque de type Man in The Middle
Nous allons maintenant nous essayer à une attaque de type Man In The Middle par arp cache poisoning. Ce type d'attaque déjà été évoqué dans MISC et la littérature sur le sujet est abondante.
De manière synthétique, le principe de l'attaque est de corrompre les tables de correspondance ARP des machines dont nous voulons rediriger les flux pour leur faire croire que la nouvelle adresse MAC de leur correspondant est la notre.
Par ailleurs, afin de faciliter la compréhension, nous avons modifié le script bin/script_utils afin que les machines soient connectées sur des switchs virtuels plutôt que des hubs comme expliqué précédemment.
Pour lancer l'attaque, nous utilisons l'outil arpspoof de la suite dsniff comme suit sur la machine "pirate" alors que la machine "client" ping www.google.com :
Par ailleurs, afin de faciliter la compréhension, nous avons modifié le script bin/script_utils afin que les machines soient connectées sur des switchs virtuels plutôt que des hubs comme expliqué précédemment.
Pour lancer l'attaque, nous utilisons l'outil arpspoof de la suite dsniff comme suit sur la machine "pirate" alors que la machine "client" ping www.google.com :
pirate:~# route add default gw 172.30.0.250
pirate:~# arpspoof -t 172.30.0.1 172.30.0.250 2> /dev/null &
[1] 438
pirate:~# arpspoof -t 172.30.0.250 172.30.0.1 2> /dev/null &
[2] 439
pirate:~# tcpdump -n ip
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
20:50:28.207517 IP 172.30.0.1.32768 > 192.168.2.1.53: 37172+ A? www.google.com. (32)
20:50:28.210729 IP 172.30.0.66 > 172.30.0.1: ICMP redirect 192.168.2.1 to host 172.30.0.250, length 68
20:50:28.211176 IP 172.30.0.1.32768 > 192.168.2.1.53: 37172+ A? www.google.com. (32)
20:50:28.208438 IP 192.168.2.1.53 > 172.30.0.1.32768: 37172 4/0/0 CNAME www.l.google.com.,[|domain]
20:50:28.208461 IP 192.168.2.1.53 > 172.30.0.1.32768: 37172 4/0/0 CNAME www.l.google.com.,[|domain]
20:50:28.215245 IP 172.30.0.1 > 209.85.129.99: ICMP echo request, id 46849, seq 1, length 64
20:50:28.215353 IP 172.30.0.1 > 209.85.129.99: ICMP echo request, id 46849, seq 1, length 64
20:50:28.279674 IP 209.85.129.99 > 172.30.0.1: ICMP echo reply, id 46849, seq 1, length 64
20:50:28.279717 IP 209.85.129.99 > 172.30.0.1: ICMP echo reply, id 46849, seq 1, length 64
20:50:28.281864 IP 172.30.0.1.32768 > 192.168.2.1.53: 21907+ PTR? 99.129.85.209.in-addr.arpa. (44)
20:50:28.281929 IP 172.30.0.66 > 172.30.0.1: ICMP redirect 192.168.2.1 to host 172.30.0.250, length 80
20:50:28.281953 IP 172.30.0.1.32768 > 192.168.2.1.53: 21907+ PTR? 99.129.85.209.in-addr.arpa. (44)
20:50:28.283556 IP 192.168.2.1.53 > 172.30.0.1.32768: 21907 1/0/0 (78)
20:50:28.283596 IP 192.168.2.1.53 > 172.30.0.1.32768: 21907 1/0/0 (78)
pirate:~# arpspoof -t 172.30.0.1 172.30.0.250 2> /dev/null &
[1] 438
pirate:~# arpspoof -t 172.30.0.250 172.30.0.1 2> /dev/null &
[2] 439
pirate:~# tcpdump -n ip
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
20:50:28.207517 IP 172.30.0.1.32768 > 192.168.2.1.53: 37172+ A? www.google.com. (32)
20:50:28.210729 IP 172.30.0.66 > 172.30.0.1: ICMP redirect 192.168.2.1 to host 172.30.0.250, length 68
20:50:28.211176 IP 172.30.0.1.32768 > 192.168.2.1.53: 37172+ A? www.google.com. (32)
20:50:28.208438 IP 192.168.2.1.53 > 172.30.0.1.32768: 37172 4/0/0 CNAME www.l.google.com.,[|domain]
20:50:28.208461 IP 192.168.2.1.53 > 172.30.0.1.32768: 37172 4/0/0 CNAME www.l.google.com.,[|domain]
20:50:28.215245 IP 172.30.0.1 > 209.85.129.99: ICMP echo request, id 46849, seq 1, length 64
20:50:28.215353 IP 172.30.0.1 > 209.85.129.99: ICMP echo request, id 46849, seq 1, length 64
20:50:28.279674 IP 209.85.129.99 > 172.30.0.1: ICMP echo reply, id 46849, seq 1, length 64
20:50:28.279717 IP 209.85.129.99 > 172.30.0.1: ICMP echo reply, id 46849, seq 1, length 64
20:50:28.281864 IP 172.30.0.1.32768 > 192.168.2.1.53: 21907+ PTR? 99.129.85.209.in-addr.arpa. (44)
20:50:28.281929 IP 172.30.0.66 > 172.30.0.1: ICMP redirect 192.168.2.1 to host 172.30.0.250, length 80
20:50:28.281953 IP 172.30.0.1.32768 > 192.168.2.1.53: 21907+ PTR? 99.129.85.209.in-addr.arpa. (44)
20:50:28.283556 IP 192.168.2.1.53 > 172.30.0.1.32768: 21907 1/0/0 (78)
20:50:28.283596 IP 192.168.2.1.53 > 172.30.0.1.32768: 21907 1/0/0 (78)
Il est intéressant de noter que les paquets apparaissent en double , ils arrivent sur l’interface, puis sont re-routés sur cette même interface vers le destinataire légitime. Une telle décision de routage n’est d’ailleurs pas particulièrement propre et le moteur de routage de linux en avertit l’émetteur par un message « ICMP redirect » l’informant que le paquet devrait mieux être directement envoyé vers 172.30.0.250 (ce que croit faire le client).
L’émission de ces paquets peut être désactivée sous linux de la manière suivante :
# echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects
Un routeur au bout d'un bâton
L'objet de ce lab est d'introduire le concept de VLAN ainsi que cette merveille d'architecture réseau nommé "router on a stick":

Ce type d'architecture permet de comprendre les mécanismes de routage inter-VLAN. Nous avons un routeur disposant d'une seule interface physique et qui réalise du routage entre les zones 10.0.0.0/24 et 192.168.0.0/24. Dans cet exemple, il est connecté à deux zones, mais il pourrait très bien par la même interface router des paquets vers beaucoup plus de VLAN. Ceci permet d'économiser des ports, souvent couteux, sur les équipements de type routeurs et firewalls.
Ce type d'architecture permet de comprendre les mécanismes de routage inter-VLAN. Nous avons un routeur disposant d'une seule interface physique et qui réalise du routage entre les zones 10.0.0.0/24 et 192.168.0.0/24. Dans cet exemple, il est connecté à deux zones, mais il pourrait très bien par la même interface router des paquets vers beaucoup plus de VLAN. Ceci permet d'économiser des ports, souvent couteux, sur les équipements de type routeurs et firewalls.
La représentation logique en couche 3 de cette architecture est celle-ci :
Rappel sur les VLAN
Les VLAN sont une technologie permettant de faire coexister, de manière étanche, différents LAN (c'est-à-dire domaines de broadcast).
Sur chacun des ports d'un switch supportant les VLAN, l'administrateur spécifie à quel VLAN appartient le port. Deux ports appartenant au même VLAN peuvent communiquer tandis que les échanges entre deux ports appartenant à des VLAN différents sont impossibles (comme s'ils étaient sur deux switchs différents).
Le second concept à la base des VLAN est l'encapsulation 802.1Q. Ce mécanisme permet le multiplexage de VLAN sur un lien. Il est en effet possible de spécifier qu'un port appartient à plusieurs VLAN. Dans ce cas, le switch ajoutera un tag 802.1Q de 4 octets sur la couche Ethernet des paquets émis par cette interface. Les deux premiers octets déterminent le numéro de VLAN auquel appartient le paquet. Dans la terminologie Cisco ces ports sont qualifiés de "trunk".
Par ce moyen il est possible :
Sur chacun des ports d'un switch supportant les VLAN, l'administrateur spécifie à quel VLAN appartient le port. Deux ports appartenant au même VLAN peuvent communiquer tandis que les échanges entre deux ports appartenant à des VLAN différents sont impossibles (comme s'ils étaient sur deux switchs différents).
Le second concept à la base des VLAN est l'encapsulation 802.1Q. Ce mécanisme permet le multiplexage de VLAN sur un lien. Il est en effet possible de spécifier qu'un port appartient à plusieurs VLAN. Dans ce cas, le switch ajoutera un tag 802.1Q de 4 octets sur la couche Ethernet des paquets émis par cette interface. Les deux premiers octets déterminent le numéro de VLAN auquel appartient le paquet. Dans la terminologie Cisco ces ports sont qualifiés de "trunk".
Par ce moyen il est possible :
- de propager nos VLAN en chaînant des switchs via des liens 802.1Q.
- de limiter le nombre d'interfaces des équipements réalisant du routage (routeur et firewalls). Il est en effet possible de router un paquet d'un VLAN vers un autre en ne disposant que d'une seule interface physique, on parle alors de routage inter-VLAN. Par exemple, le paquet entre avec un tag 802.1q spécifiant qu'il appartient au VLAN 100, l'équipement prend une décision de routage et réémet le paquet sur la même interface avec un tag 802.1q spécifiant qu'il appartient maintenant au VLAN 200.
Les VLAN sont aujourd'hui supportés par la quasi totalité des switchs, routeurs et firewalls utilisés dans un contexte professionnel ainsi que sur les principaux OS (en particulier Windows ou Linux). Cette technologie est utilisée dans la pluparts des réseaux d’envergure (grandes entreprises, administrations, fournisseurs d’accès, …).
Dans notre exemple, nous allons "émuler" un switch supportant les VLAN par une machine sous Linux utilisant des fonctionnalités de type bridge.
Création de l'architecture
Nous avons dans ce laboratoire un routeur virtuel (R) disposant d'une seule interface, d'un switch (S) disposant de 3 interfaces et de deux PC. Lancer tout ceci en ligne de commande sans se tromper peut paraître un peu complexe mais netkit offre un système de fichiers de configuration permettant d'automatiser le lancement de laboratoires préconfigurés.
Afin de construire cette architecture, nous nous appuyons sur une interface graphique de netkit, VisualNetkit [http://code.google.com/p/visual-netkit/], aidant à la génération de ces fichiers de configuration.
Le paquet se compile sans difficulté sur une Ubuntu Hardy et le réseau ci-dessus se configure en quelques minutes à la souris :
Afin de construire cette architecture, nous nous appuyons sur une interface graphique de netkit, VisualNetkit [http://code.google.com/p/visual-netkit/], aidant à la génération de ces fichiers de configuration.
Le paquet se compile sans difficulté sur une Ubuntu Hardy et le réseau ci-dessus se configure en quelques minutes à la souris :
Voici ce qui apparaît dans le répertoire où nous avons sauvegardé notre laboratoire :
$ ls
lab.conf PC1 PC2 R S
lab.xml PC1.startup PC2.startup R.startup S.startup
lab.conf PC1 PC2 R S
lab.xml PC1.startup PC2.startup R.startup S.startup
Le fichier lab.conf contient la liste des machines virtuelles avec les interfaces associées :
###
# This file is createb by Visual Netkit 1.0b version
# http://www.netkit.org ~ http://code.google.com/p/visual-netkit/
#
LAB_DESCRIPTION="Routage inter-vlan par Cedric Foll"
LAB_VERSION="1.0"
LAB_AUTHOR="Cedric Foll"
LAB_EMAIL="cedric.foll@laposte.net"
LAB_WEB="http://cedric.foll.name/"
PC1[0]="A"
PC2[0]="B"
R[0]="Interco"
S[0]="Interco"
S[1]="A"
S[2]="B"
# This file is createb by Visual Netkit 1.0b version
# http://www.netkit.org ~ http://code.google.com/p/visual-netkit/
#
LAB_DESCRIPTION="Routage inter-vlan par Cedric Foll"
LAB_VERSION="1.0"
LAB_AUTHOR="Cedric Foll"
LAB_EMAIL="cedric.foll@laposte.net"
LAB_WEB="http://cedric.foll.name/"
PC1[0]="A"
PC2[0]="B"
R[0]="Interco"
S[0]="Interco"
S[1]="A"
S[2]="B"
Les fichiers *.startup contiennent la liste des commandes exécutées après lancement de la machine virtuelle. VisualNetkit se contentera de les créer en préconfigurant éventuellement les adresses IP. Il conviendra ensuite d'éditer ces fichiers afin de d'automatiser l'exécution des commandes nécessaires à notre architecture.
Par exemple R.startup contient les lignes suivantes :
1: ###
2: # This file is createb by Visual Netkit 1.0b version
3: # http://www.netkit.org ~ http://code.google.com/p/visual-netkit/
4: #
5: /sbin/ifconfig eth0 up ## 'Interco' collision domain ##
6: /sbin/vconfig add eth0 100
7: /sbin/ifconfig eth0.100 10.0.0.250
8: /sbin/vconfig add eth0 200
9: /sbin/ifconfig eth0.200 192.168.0.250
2: # This file is createb by Visual Netkit 1.0b version
3: # http://www.netkit.org ~ http://code.google.com/p/visual-netkit/
4: #
5: /sbin/ifconfig eth0 up ## 'Interco' collision domain ##
6: /sbin/vconfig add eth0 100
7: /sbin/ifconfig eth0.100 10.0.0.250
8: /sbin/vconfig add eth0 200
9: /sbin/ifconfig eth0.200 192.168.0.250
Quelques explications :
Sous Linux, la création de sous-interfaces liées à des VLAN se fait via la commande vconfig. Le premier appel à vconfig ci-dessus a la signification suivante :
- Ligne 5 : création de l’interface eth0, qui n’a pas besoin d’adresse
- Ligne 6 : création d'une sous-interface à eth0. Cette sous-interface sera nommée eth0.100.
- Les paquets Ethernet avec un tag 802.1q arrivant sur l'interface eth0 spécifiant qu'ils appartiennent au vlan 100 sont transmis à la sous-interface eth0.100.
- Les paquets émis via la sous-interface eth0.100 sont envoyés par l'interface eth0 avec un tag 802.1q spécifiant qu'ils appartiennent au VLAN 100.
- Ligne 7 : attribution d’une adresse à cette sous interface.
- Ligne 8 et 9 : même opération pour la seconde sous interface qui se trouve dans le vlan 200.
Ensuite, le fichier S.startup contient ceci:
###
# This file is createb by Visual Netkit 1.0b version
# http://www.netkit.org ~ http://code.google.com/p/visual-netkit/
#
/sbin/ifconfig eth0 up ## 'Interco' collision domain ##
/sbin/ifconfig eth1 up ## 'A' collision domain ##
/sbin/ifconfig eth2 up ## 'B' collision domain ##
/sbin/vconfig add eth0 100
/sbin/ifconfig eth0.100 up
/sbin/vconfig add eth0 200
/sbin/ifconfig eth0.200 up
/usr/sbin/brctl addbr vlan100
/usr/sbin/brctl addif vlan100 eth1
/usr/sbin/brctl addif vlan100 eth0.100
/sbin/ifconfig vlan100 up
/usr/sbin/brctl addbr vlan200
/usr/sbin/brctl addif vlan200 eth2
/usr/sbin/brctl addif vlan200 eth0.200
/sbin/ifconfig eth0.200 up
/usr/sbin/brctl addbr vlan100
/usr/sbin/brctl addif vlan100 eth1
/usr/sbin/brctl addif vlan100 eth0.100
/sbin/ifconfig vlan100 up
/usr/sbin/brctl addbr vlan200
/usr/sbin/brctl addif vlan200 eth2
/usr/sbin/brctl addif vlan200 eth0.200
/sbin/ifconfig vlan200 up
Sur cette machine nous créons deux sous-interfaces à l'interface eth0 (eth0.100 et eth0.200). Ensuite nous "bridgeons" (commande brctl) les interfaces eth0.100 avec eth1 et eth0.200 avec eth2.
Aussi, nous nous retrouvons avec un "switch" dont l'interface eth1 se trouve dans le VLAN 100, l'interface eth2 dans le VLAN 200 et l'interface eth0 est en mode 802.1q propageant les VLAN 100 et 200.
En bridgeant nos interfaces et sous-interfaces nous avons décrit la définition d'un switch configuré avec des VLAN (i.e. seules les interfaces dans les mêmes VLAN peuvent communiquer, c'est-à-dire que les interfaces dans les mêmes VLAN sont "bridgées").
Sur cette machine nous créons deux sous-interfaces à l'interface eth0 (eth0.100 et eth0.200). Ensuite nous "bridgeons" (commande brctl) les interfaces eth0.100 avec eth1 et eth0.200 avec eth2.
Aussi, nous nous retrouvons avec un "switch" dont l'interface eth1 se trouve dans le VLAN 100, l'interface eth2 dans le VLAN 200 et l'interface eth0 est en mode 802.1q propageant les VLAN 100 et 200.
En bridgeant nos interfaces et sous-interfaces nous avons décrit la définition d'un switch configuré avec des VLAN (i.e. seules les interfaces dans les mêmes VLAN peuvent communiquer, c'est-à-dire que les interfaces dans les mêmes VLAN sont "bridgées").
Enfin, les répertoires ayant pour nom ceux des machines virtuelles (PC1, PC2, R et S dans notre exemple) peuvent contenir des fichiers qui seront copiés dans l’arborescence des machines virtuelles correspondantes afin, par exemple, d’automatiser la configuration des démons.
Il ne reste plus qu'à tester notre laboratoire et vérifier que tout fonctionne comme escompté :
Sur PC1:
PC1:~# ping -c 1 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=63 time=1.04 ms
--- 192.168.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.049/1.049/1.049/0.000 ms
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=63 time=1.04 ms
--- 192.168.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.049/1.049/1.049/0.000 ms
Sur R:
R:~# tcpdump -n -i eth0
tcpdump: WARNING: eth0: no IPv4 address assigned
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
23:09:30.328238 vlan 100, p 0, IP 10.0.0.1 > 192.168.0.1: ICMP echo request, id 42497, seq 1, length 64
23:09:30.340558 vlan 200, p 0, IP 10.0.0.1 > 192.168.0.1: ICMP echo request, id 42497, seq 1, length 64
23:09:30.328674 vlan 200, p 0, IP 192.168.0.1 > 10.0.0.1: ICMP echo reply, id 42497, seq 1, length 64
23:09:30.328707 vlan 100, p 0, IP 192.168.0.1 > 10.0.0.1: ICMP echo reply, id 42497, seq 1, length 64
tcpdump: WARNING: eth0: no IPv4 address assigned
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
23:09:30.328238 vlan 100, p 0, IP 10.0.0.1 > 192.168.0.1: ICMP echo request, id 42497, seq 1, length 64
23:09:30.340558 vlan 200, p 0, IP 10.0.0.1 > 192.168.0.1: ICMP echo request, id 42497, seq 1, length 64
23:09:30.328674 vlan 200, p 0, IP 192.168.0.1 > 10.0.0.1: ICMP echo reply, id 42497, seq 1, length 64
23:09:30.328707 vlan 100, p 0, IP 192.168.0.1 > 10.0.0.1: ICMP echo reply, id 42497, seq 1, length 64
Nous notons, avec émerveillement, le paquet "icmp request" entré avec un tag spécifiant qu'il appartient au VLAN 100 puis ressortir avec un tag spécifiant qu'il appartient au VLAN 200. Notre routeur a fait son travail.
Il est également possible de réaliser le tcpdump à partir d'une sous-interface et dans ce cas il n'y a plus de tag 802.1q:
R:~# tcpdump -n -i eth0.100
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0.100, link-type EN10MB (Ethernet), capture size 96 bytes
23:09:46.647203 IP 10.0.0.1 > 192.168.0.1: ICMP echo request, id 42753, seq 1, length 64
23:09:46.647580 IP 192.168.0.1 > 10.0.0.1: ICMP echo reply, id 42753, seq 1, length 64
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0.100, link-type EN10MB (Ethernet), capture size 96 bytes
23:09:46.647203 IP 10.0.0.1 > 192.168.0.1: ICMP echo request, id 42753, seq 1, length 64
23:09:46.647580 IP 192.168.0.1 > 10.0.0.1: ICMP echo reply, id 42753, seq 1, length 64
Petite digression sur l’incommunicabilité dans les réseaux IP.
Que se passe-t-il si un paquet de 1500 octets arrive sur une interface non taggée (par exemple eth2) avant d'être transmis vers une sous-interface (par exemple eth0.200) et se voir ajouter ainsi 4 octets?
Nous nous retrouvons alors devant un problème aussi classique que souvant complexe des mécanismes liés à l'encapsulation et autre tunneling. En effet, comme le 802.1q ajoute 4 octets sur la couche Ethernet, les paquets déjà au maximum du MTU avant l’ajout de ce tag vont poser problème et être détruits.
Dans des cas aussi simples que celui-ci les équipements récents, à la différence d’un linux transformé en switch, savent s'en sortir mais dans des cas plus compliqués (lorsque l'overhead est plus important) comme avec du tunneling (IPSec, GRE, PPPoE, ...) il est nécessaire de trouver une solution satisfaisante.
Comment ce type de problème peut-il être résolu de manière transparente pour les administrateurs systèmes (c'est-à-dire sans repasser sur tous les serveurs pour baisser de quatre octets leur MTU) ?
Comment ce type de problème peut-il être résolu de manière transparente pour les administrateurs systèmes (c'est-à-dire sans repasser sur tous les serveurs pour baisser de quatre octets leur MTU) ?
- La fragmentation. Pour ceci un équipement de couche 3 (un routeur) doit détecter que le paquet est trop gros et envoyer un paquet icmp "need to fragment" pour provoquer une fragmentation du paquet incriminé. Dans notre cas, le problème se produit avant qu'un équipement de couche 3 soit atteint. De plus, ce mécanisme est jugé obsolète et beaucoup d'OS ajoutent le tag DF (Don't Fragment) sur tous leurs paquets pour interdire la fragmentation.
- Path MTU discovery (RFC1191). Le mécanisme est similaire au précédent sauf qu'au lieu de fragmenter les paquets à la réception du message d'erreur ICMP, le client va envoyer des paquets avec un MTU plus faible. Cependant, le problème est le même que la fragmentation, la difficulté provient d'un équipement de couche 2 en amont de tout équipement de couche 3, il n'y a donc pas d'ICMP possible.
- La RFC 4821. Celle ci prévoit de diminuer le MTU lorsque des paquets sont perdus. Malheureusement elle n’est pas encore largement implémentée (la RFC date de mars 2007).
- Le champs MSS (Maximum Segment Size) de la couche TCP. Il informe le destinataire des paquets de la taille maximum des données TCP que nous sommes à même d'accepter (ce qui a une incidence directe sur la taille des paquets IP). Pour notre plus grand bonheur, iptables est capable d'altérer dynamiquement les paquets qu'il transmet pour adapter le MSS à son MTU. Ceci se fait par la commande suivante :
# iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
Cette manipulation ne fonctionnant que pour les flux TCP l'administrateur réseau superstitieux brûlera un cierge afin qu'il n'y ai pas trop de paquets UDP de plus de 1496 octets qui transitent...
