Routage IP avec Linux et iproute2

Présentation

Je vous propose d’expérimenter le routage IP sous Linux (Debian), grâce aux outils de la suite iproute2, afin de mieux comprendre ce mécanisme essentiel du niveau 3 de l’OSI.

  • Nous allons dans un premier temps installer un environnement de travail virtuel avec deux réseaux locaux « fermés » (LAN1 et LAN2) et trois machines légères : une dans chaque LAN et une avec « une patte » dans chaque réseau local.
  • Puis nous mettrons en œuvre un routage statique entre ces deux réseaux.

Il est recommandé de connaître le Protocole Internet (IP) et le principe du routage associé. Pour plus de clarté, nous n’utiliserons dans ces travaux que le protocole IPv4 ; le principe reste le même pour IPv6.

A propos d’iproute2

On désigne par iproute2 est une collection d’utilitaires pour la gestion des protocoles TCP, UDP, IP et la gestion du réseau sous Linux, supportant l’IPv4 et l’IPv6. La collection iproute2 est destinée à remplacer toute une suite d’outils réseau standard Unix (appelée net-tools) qui étaient anciennement utilisés pour les tâches de configuration d’interfaces réseau, tables de routage, et gestion de table ARP. La suite net-tools est dépréciée, il est recommandé de ne plus l’utiliser.

Voici quelques exemples d’outils net-tools remplacés par iproute2 :

UsageAncien outil net-toolsCommande iproute2
Adressage (niv. 2)ifconfigip link
Adressage (niv. 3)ifconfigip addr
Routagerouteip route
Résolution d’adressesarpip neigh
VLANvconfigip link
Tunnelsiptunnelip tunnel
Multicastipmaddrip maddr
Statistiquesnetstatss
Source : Wikipedia

Préparation

Nous allons utiliser notre logiciel hyperviseur préféré pour créer trois machines GNU/Linux minimales  :

Nom de la VMhostname Unix
Debian AdebianA
Debian BdebianB
Debian XdebianX
Nous aurons besoin de trois machines virtuelles Debian minimales.

Pour créer ces machines, voir le tutoriel de référence dédié à cela. Les caractéristiques communes initiales de nos trois machines sont :

  • 1 CPU
  • 1 GB RAM
  • 1 HDD 10 GB
  • 1 interface réseau en mode NAT

Préparation des VM

Le PC hôte de nos VM doit être connecté à Internet afin que chaque VM soit elle-même connectée à Internet via la connexion NAT (recommandé) ; il est possible aussi d’utiliser le mode bridge (pont) sur l’interface physique connectée à Internet.

Toutes les commandes à suivre sont réalisées en mode console, connecté avec l’utilisateur root.

On vérifie la connexion à Internet, en testant la connexion à une adresse IP externe connue (ici le DNS Cloudflare) :

# ping 1.1.1.1
Un ping 1.1.1.1 fonctionnel ; on le stoppe avec [CTRL]-[C]

et à un DNS fonctionnel :

# ping debian.org
ping avec résolution de nom (debian.org renvoie ici à l’IP publique 130.89.148.77)

On commence par les opérations classiques de mise à jour. On met à jour la base de données du gestionnaire de paquets Debian :

# apt update
# apt update ; le résultat indique ici qu’il y a des paquets qui peuvent être mis à jour.

Puis on procède aux mises à jour s’il y en a :

# apt upgrade
# apt upgrade met à jour ici deux paquets.

Éventuellement un peu de ménage (dans notre cas c’est juste pour rappeler les commandes ; et encore, on pourrait aller plus loin à ce sujet) :

# apt clean
# apt autoremove

Enfin, on installe sur chaque machine les utilitaires tcpdump et traceroute :

# apt install -y tcpdump traceroute

Ces opérations sont à faire pour chaque VM ; on peut aussi le faire pour une, puis la cloner deux fois (attention à bien générer de nouvelles adresses MAC pour les interfaces réseau).

Connexion des cartes réseau aux segments LAN

Une fois les trois machines préparées, il faut les éteindre :

# poweroff

Il faut maintenant modifier le « hardware virtuel » des VM afin de créer l’infrastructure suivante (on utilise les réseaux internes de l’hyperviseur) :

Attention, dans cette configuration, les machines ne seront plus connectées à Internet.

Voici la configuration sous VirtualBOX de l’interface réseau de debianA connectée au réseau interne LAN1 :

Puis la configuration pour debianB qui est sur LAN2 :

La machine debianX quant à elle dispose de deux cartes réseau, une sur chaque LAN :

La carte 1 sur le LAN1, on repère l’adresse MAC.
La carte 2 sur le LAN2.

Enfin, nous modifions le nom de chaque machine (hostname), si besoin (notamment si vous avez procédé par clonage ; car lors de l’installation Debian, le nom de machine est demandé)  :

A# echo "debianA" > /etc/hostname

NB : par convention, quand c’est utile, je fais précéder le prompt (# ou $) du nom de la machine concernée (A, B, ou X)

B# echo "debianB" > /etc/hostname
X# echo "debianX" > /etc/hostname

Est-ce suffisant pour modifier correctement le nom des machines ? Nope. Il y a un autre fichier qui contient le hostname, afin de le résoudre avec l’ip 127.0.1.1. C’est le fichier primaire de résolution DNS : /etc/hosts. Si on ne fait pas cette modification, on aura par exemple des erreurs avec la commande sudo qui affichera systématiquement :

sudo: impossible de résoudre l'hôte debianX: Nom ou service inconnu

Utilisons l’outil sed, en présumant que nous avons nommé la machine debian lors de l’installation. Pour chaque machine A, B et X :

A# sed -i 's/debian/debianA/g' /etc/hosts
B# sed -i 's/debian/debianB/g' /etc/hosts
X# sed -i 's/debian/debianX/g' /etc/hosts

Les machines doivent être redémarrées pour que le hostname soit pris en compte :

# reboot

Configuration des adresses IP

Nous utiliserons donc la suite de commandes iproute2, devenue le standard sur les systèmes GNU/Linux.

La commande ip link show (que l’on peut abréger avec ip link et même ip l), permet d’afficher les interfaces disponibles. Ajoutons l’option -c pour avoir un peu de couleur :

X# ip -c l
Nous voyons les deux interfaces Ethernet de debianX. Bien repérer leurs noms ; ici : eth0 et eth1

Grâce aux adresses MAC, on peut valider quelle interface est en lien avec quel réseau. Ici, eth0 (adresse MAC 08:00:27:83:7C:BB) est clairement la carte qui est connectée au LAN1, selon le repérage effectué juste avant.

La carte eth1 est DOWN, car elle a été ajoutée après l’installation, donc non préconfigurée, et elle n’est pas encore configurée dans le système.

Nous pouvons attribuer l’adresse IPv4 192.168.10.1/24 à la machine A sur son interface eth0 (si c’est son nom ; avec vmware workstation par exemple, elle aurait pu s’appeler ens32) :

A# ip addr add 192.168.10.1/24 dev eth0
A# ip -c a

Cependant, cette configuration ne sera pas persistante au prochain redémarrage du système.

Pour ce faire, il faut modifier le fichier de configuration des interfaces réseau :

A# nano /etc/network/interfaces 

L’écran d’édition doit proposer quelque chose de ce genre ; il faut modifier les paramètres (en gras) de l’interface réseau primaire (en conservant le nom de l’interface sur votre système, il est possible que ce soit plutôt enp0s3 ou ens32 que eth0) :

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug eth0
iface eth0 inet static
address 192.168.10.1/24

NB : nano est dans ce cas l’éditeur de texte, mais ça peut être un autre, comme vi ou vim.

NB : la configuration de l’interface primaire est précédée de celle de la boucle « loopback » (lo) et de quelques commentaires . Attention aux erreurs de frappe qui « décommentent » par mégarde certaines lignes dans les fichiers de configuration… et plantent l’initialisation correcte du système (une cause fréquente est l’état de cette satanée touche VERR.NUM…)

Après un reboot de la machine, la configuration IPv4 est validée :

Il nous faut paramétrer maintenant les autres machines. D’abord la debianB :

B# nano /etc/network/interfaces 
...
allow-hotplug eth0
iface eth0 inet static
address 192.168.11.1/24

Puis la debianX, avec ses deux cartes eth0 et eth1 :

X# nano /etc/network/interfaces 
...
allow-hotplug eth0
iface eth0 inet static
address 192.168.10.254/24

allow-hotplug eth1
iface eth1 inet static
address 192.168.11.254/24

et voilà pour la debianX (après un reboot bien sûr) :

Test de fonctionnement

Une fois tout cela en place, il doit être possible de « pinger » la machine X depuis la machine A :

A# ping 192.168.10.254

Et de « pinger » la machine X depuis la machine B :

B# ping 192.168.11.254

Et enfin de « pinger » les machines A et B depuis la machine X :

X# ping 192.168.10.1
X# ping 192.168.11.1

Cependant, il n’est (normalement) pas possible de pinger la machine B depuis la machine A :

A# ping 192.168.11.1

En effet, la machine debianA ne « connaît » pas ce réseau 192.168.11.0/24, et encore moins le moyen de s’y rendre.

Petit focus sur le voisinage réseau (ARP)

Regardons quelques commandes iproute2 qui permettent de gérer le cache ARP de la machine. En effet, chaque machine disposant d’une pile (stack) IP gère une table d’association [adresse MAC] <> [adresse IP]. Chaque entrée de cette table a par ailleurs une durée de vie limitée. Essayez ces quelques commandes :

A# ping 192.168.10.254
A# ip neigh
A# ip n
A# ip n flush all
A# ip n
A# ping -c 1 192.168.10.254 && watch ip n

Avec la dernière commande, vous pourrez normalement voir l’évolution d’une entrée ARP entre les états DELAY, REACHABLE (atteignable) et STALE (périmé).

On peut aussi créer une association permanente (attention c’est touchy) :

A# ip n add 192.168.10.254 lladdr 08:00:27:83:7C:BB dev eth0
A# ip n flush all
A# ip n
A# ip n del 192.168.10.254 lladdr 08:00:27:83:7C:BB dev eth0
A# ip n

L’usage d’entrées ARP permanentes peut avoir deux raisons :

  • éviter dans un réseau très dense des requêtes ARP,
  • empêcher l’ARP « spoofing », où des machines se font passer pour d’autres lors des requêtes ARP.

Mise en place du routage sur la machine debianX

Par défaut, le routage n’est pas activé sur une machine Linux standard, et notamment ici sur la machine debianX. Il faut donc activer le routage IPv4 sur notre machine X :

X# sysctl -w net.ipv4.ip_forward=1

Profitons-en au passage pour désactiver intégralement la gestion de l’IPv6 sur notre machine (quand on n’a pas envie de gérer quelque chose, il vaut mieux le désactiver que de laisser les réglages par défaut) :

X# sysctl -w net.ipv6.conf.all.disable_ipv6=1

Les commandes sysctl permettent de configurer certaines fonctions du noyau Linux. Les deux commandes que nous venons de lancer fonctionnent, mais ne sont pas persistantes. Pour ce faire, il faut modifier le fichier adéquat :

X# nano /etc/sysctl.conf

et ajouter les deux lignes suivantes :

net.ipv4.ip_forward=1
net.ipv6.conf.all.disable_ipv6=1

Il est probable que la première existe déjà dans le fichier, mais qu’elle soit commentée (et donc désactivée). Puis on relance la machine X, et on vérifie que le routage IPv4 est activé, et qu’il n’a plus d’adresse IPv6 :

X# reboot
...
X# sysctl net.ipv4.ip_forward
X# ip -c a

Tables de routage

Quelle commande permet d’afficher la table de routage actuelle de chaque machine ?

# ip route

Ce qui doit nous donner, pour chacune des machines A, B, X :

192.168.10.0/24 dev eth0 proto kernel scope link src 192.168.10.1
192.168.11.0/24 dev eth0 proto kernel scope link src 192.168.11.1
192.168.10.0/24 dev eth0 proto kernel scope link src 192.168.10.254
192.168.11.0/24 dev eth1 proto kernel scope link src 192.168.11.254

Lancer l’utilitaire tcpdump sur la machine X afin de visualiser les paquets ICMP qui transitent sur son interface reliée à LAN1 (donc sur l’interface eth0) :

X# tcpdump -i eth0 icmp

Quelle est la commande pour activer un routage IP de la machine A vers la machine B ?

A# ip route add 192.168.11.0/24 via 192.168.10.254 dev eth0

Traduction : pour joindre le réseau 192.168.11.0/24, il faut passer par la passerelle (gateway) 192.168.10.254, via l’interface eth0 (rappel évident : une passerelle doit toujours faire partie d’un réseau auquel je suis directement connecté).

Que donne un ping de A vers B désormais ?

A# ping 192.168.11.1

ça ne fonctionne pas. Si on analyse les résultats affichés sur le tcpdump qui tourne sur X, on devine la cause du problème. Il ne faut pas oublier qu’un ping c’est une demande (request) de A vers B, suivi d’une réponse (reply) de B vers A, pour valider la bonne connexion de niveau 3 (IP). Il manque donc ici le message retour de B vers A, donc la route de B vers A.

Quelles est la commande pour activer un routage IP de la machine B vers la machine A ?

B# ip route add 192.168.10.0/24 via 192.168.11.254 dev eth0

Et c’est la joie dans la place ! la route est en place de bout en bout :

Capture d’écran de la machine routeur debianX, avec tcpdump

La commande traceroute permet de savoir le chemin emprunté pour joindre la machine distante :

A# traceroute 192.168.11.1

On voit clairement que le paquet est d’abord passé par la machine X, avant de joindre la machine B :

Persistance des routes statiques

Après avoir contemplé la magie du routage IP (ce mécanisme est le fondement de la majorité des échanges sur Internet), nous ne saurions être pleinement satisfait si les routes statiques que nous avons réalisées n’étaient pas persistantes.

Qu’à cela ne tienne.

Nous allons nous rendre dans le répertoire du système prévu justement pour des scripts qui sont exécutés à chaque fois qu’une interface du réseau est « up » :

A# cd /etc/network/if-up.d

Et ajoutons un script pour chaque machine (ici la debianA) :

A# nano route_x

A priori, on l’imagine simplement contenant deux lignes :

  • Le « shebang » qui indique que c’est un script texte et le shell à utiliser
  • La commande d’ajout de la route
#!/usr/bin/env bash
ip route add 192.168.11.0/24 via 192.168.10.254 dev eth0

Il ne faut pas oublier de le rendre exécutable :

A# chmod +x route_x

Après redémarrage de la machine, vous pourrez vous rendre compte que ça ne fonctionne pas très bien… Si, si. La route statique existe, sans doute, mais vous aurez aperçu un petit [FAILED] dans la séquence de démarrage, et le service networking est en réalité HS, voyez par vous-même :

# systemctl status networking

En fait, ce script, que l’on voit de-ci de-là comme solution pour une route statique, n’est pas satisfaisant.

D’abord, dans un script, il faut prendre la bonne habitude d’écrire les commandes avec un chemin absolu, et non relatif, comme ici (« ip … »), donc déjà :

#!/usr/bin/env bash
/sbin/ip route add 192.168.11.0/24 via 192.168.10.254 dev eth0

D’autre part, les scripts présents dans if-up.d sont lancés pour chaque interface qui passe UP, ainsi qu’une fois supplémentaire quand toutes sont UP. A chaque fois, la variable $IFACE contiendra le nom de l’interface en question (et aura la valeur « –all » pour le dernier cas).

Notre script sera donc plus correct ainsi :

#!/usr/bin/env bash
if [ "$IFACE" = "eth0" ] ; then
  /sbin/ip route add 192.168.11.0/24 via 192.168.10.254 dev eth0
fi

Relancer les machines, et vérifier que cette fois, tout est fonctionnel, sans erreur, paisible et serein.

Est-ce la seule méthode ?

Il existe (au moins) une autre méthode pour créer cette route statique persistante : au lieu de créer un script dans le répertoire if-up.d, on peut insérer la commande de création de la route directement dans le fichier de configuration des interfaces, grâce à la directive post-up :

Par exemple ici pour la machine A (fichier /etc/network/interfaces):

...
allow-hotplug eth0
iface eth0 inet static
address 192.168.10.1/24
post-up /sbin/ip route add 192.168.11.0/24 via 192.168.10.254 dev eth0

Conclusion

Nous avons donc pu au travers d’une maquette simple expérimenter les fondamentaux du routage avec Linux, utiliser quelques outils iproute2 et aborder quelques point importants. J’espère que ce tutoriel vous aura plu.

Pour en savoir plus sur iproute2 : https://inetdoc.net/guides/lartc/lartc.iproute2.html ainsi que https://manpages.debian.org/bullseye/iproute2/index.html

Pour en savoir plus sur la gestion des interfaces réseau : https://manpages.debian.org/bullseye/ifupdown/interfaces.5.en.html

Dernière mise à jour : 29/01/2023