+ What's ARP Got to Do with It?
+ IPv4 routes with an IPv6 next hop
Je découvre que des systèmes (Linux, Mikrotik, Arista, Cisco), et même le protocole de routage Babel, prennent en charge les « v4-via-v6 routes », c'est-à-dire des routes IPv4 dont le prochain saut est une IPv6.
L'adresse MAC du prochain saut est donc résolue avec ICMPv6 NDP au lieu d'ARP.
Sous Linux, ces routes s'insèrent dans la table de routage de cette manière : ip -4 r a default via inet6 fe80::1 dev enp1s0. (« inet6 » après « via ».) Elles ont cette tête dans la table de routage : default via inet6 fe80::1 dev enp1s0.
C'est absolument génial puisque cela permet un réseau informatique intégralement en IPv6, contenant des machines qui peuvent joindre les réseaux IPv4 sans mécanisme de transition IPv6<>IPv4. Évidemment, un tel réseau doit quand même être en capacité d'attribuer des adresses IPv4, et les routeurs et les terminaux doivent prendre en charge IPv6 et IPv4, donc tous les scénarios de transition d'IPv4 vers IPv6 ne sont pas couverts. (Évidemment, des réseaux uniquement IPv4 ne pourront pas joindre des destinations IPv6.)
Linux prend en charge cela depuis sa version 5.2 de mi-2019. :O
Comme l'expliquent les articles sus-pointés, c'est la suite logique d'une configuration fortement répandue, notamment chez les hébergeurs, qui consiste à configurer une machine virtuelle dans un réseau contenant une seule IPv4 (/32), voire une seule adresse IPv6 (/128), ce qui fait disparaître la notion de réseau niveau 2 et certaines des attaques qui vont avec (possibilité, pour l'hébergeur, de nettoyer l'intégralité du trafic ‒ ex. : anti-DDoS entre machines d'un même réseau ‒, et d'isoler ses clients) : ARP ne sert plus qu'à résoudre l'adresse MAC du routeur. Autant aller jusqu'au bout et résoudre cette adresse MAC via ICMPv6. :D
(D'autres attaques, comme un serveur DHCP malveillant, un prétendu routeur IPv6 malveillant ‒ Router Advertisement ‒, etc. sont également résolues, mais une configuration statique produirait le même résultat, donc ça ne compte pas vraiment. Si la possibilité d'une usurpation du routeur via ARP disparaît, une usurpation NDP demeure.)
Forcément, j'ai eu envie de monter une maquette.
Trois machines virtuelles KVM faisant tourner Debian 13 (Trixie).
Terminal (198.18.0.2/32 / 2001:db8::2/64) <--> Routeur (198.19.0.1/24 / 2001:2::1/64) <--> Ternet (198.19.0.2/24 / 2001:2::2/64).
Oui, Routeur n'a pas d'adresse de la forme 198.18.X.X, et n'est pas plus présente dans le réseau 2001:db8::/64. Il a des routes vers ces réseaux dont le prochain saut est une IPv6 locale au lien (rappel : en IPv6, le routage devrait toujours se faire ainsi).
Oui, Ternet est une machine configurée de manière usuelle.
Pour rappel, 198.18.0.0/15, 2001:db8::/32, et 2001:2::/48 sont des réseaux réservés pour des tests ou de la documentation, cf. IPv4 Special-Purpose Address Space et IPv6 Special-Purpose Address Space.
# # Éviter des requêtes DNS inutiles dans mes traces réseau
# systemctl stop systemd-timesyncd
# systemctl mask systemd-timesyncd
# cat > /etc/network/interfaces << EOF
auto lo enp1s0
iface lo inet loopback
iface enp1s0 inet6 static
address 2001:db8::2/64
gateway fe80::1
accept_ra 0
autoconf 0
dad-attempts 0
iface enp1s0 inet static
address 198.18.0.2/32
post-up /usr/sbin/ip -4 route add default via inet6 fe80::1 dev enp1s0
post-up /usr/sbin/ip -6 a a fe80::2/64 dev enp1s0
EOF
# systemctl stop systemd-timesyncd
# systemctl mask systemd-timesyncd
# cat > /etc/network/interfaces << EOF
auto lo enp1s0
iface lo inet loopback
iface enp1s0 inet6 static
address 2001:2::2/64
gateway fe80::1
accept_ra 0
autoconf 0
dad-attempts 0
iface enp1s0 inet static
address 198.19.0.2/24
gateway 198.19.0.1
EOF
# systemctl stop systemd-timesyncd
# systemctl mask systemd-timesyncd
# cat > /etc/network/interfaces << EOF
auto lo enp1s0 enp7s0
iface lo inet loopback
iface enp1s0 inet6 static
address fe80::1/64
post-up /usr/sbin/ip -4 route add 198.18.0.2/32 via inet6 fe80::2 dev enp1s0
post-up /usr/sbin/ip -6 route add 2001:db8::/64 via fe80::2 dev enp1s0
post-up echo 1 > /proc/sys/net/ipv4/ip_forward
post-up echo 1 > /proc/sys/net/ipv6/conf/all/forwarding
post-up echo 1 > /proc/sys/net/ipv6/conf/enp1s0/forwarding
iface enp7s0 inet6 static
address 2001:2::1/64
post-up /usr/sbin/ip a a fe80::1/64 dev enp7s0
post-up echo 1 > /proc/sys/net/ipv6/conf/enp7s0/forwarding
iface enp7s0 inet static
address 198.19.0.1/24
EOF
# ip a s dev enp1s0
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:70:9f:c8 brd ff:ff:ff:ff:ff:ff
altname enx525400709fc8
inet 198.18.0.2/32 brd 198.18.0.2 scope global enp1s0
valid_lft forever preferred_lft forever
inet6 2001:db8::2/64 scope global nodad
valid_lft forever preferred_lft forever
inet6 fe80::2/64 scope link
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe70:9fc8/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
# ip -4 r s
default via inet6 fe80::1 dev enp1s0
# ip -6 r s
2001:db8::/64 dev enp1s0 proto kernel metric 256 pref medium
fe80::/64 dev enp1s0 proto kernel metric 256 pref medium
default via fe80::1 dev enp1s0 metric 1024 onlink pref medium
# ip -4 n
#
# ip -6 n
#
# ip a s dev enp1s0
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:16:68:77 brd ff:ff:ff:ff:ff:ff
altname enx525400166877
inet 198.19.0.2/24 brd 198.19.0.255 scope global enp1s0
valid_lft forever preferred_lft forever
inet6 2001:2::2/64 scope global nodad
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe16:6877/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
# ip -4 r s
default via 198.19.0.1 dev enp1s0 onlink
198.19.0.0/24 dev enp1s0 proto kernel scope link src 198.19.0.2
# ip -6 r s
2001:2::/64 dev enp1s0 proto kernel metric 256 pref medium
fe80::/64 dev enp1s0 proto kernel metric 256 pref medium
default via fe80::1 dev enp1s0 metric 1024 onlink pref medium
# ip -4 n
#
# ip -6 n
#
# ip a s dev enp1s0
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:69:0f:ed brd ff:ff:ff:ff:ff:ff
altname enx525400690fed
inet6 fe80::1/64 scope link
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe69:fed/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
# ip a s dev enp7s0
3: enp7s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:2a:ea:b5 brd ff:ff:ff:ff:ff:ff
altname enx5254002aeab5
inet 198.19.0.1/24 brd 198.19.0.255 scope global enp7s0
valid_lft forever preferred_lft forever
inet6 2001:2::1/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::1/64 scope link
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe2a:eab5/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
# ip -4 r s
198.18.0.2 via inet6 fe80::2 dev enp1s0
198.19.0.0/24 dev enp7s0 proto kernel scope link src 198.19.0.1
# ip -6 r s
2001:2::/64 dev enp7s0 proto kernel metric 256 pref medium
2001:db8::/64 via fe80::2 dev enp1s0 metric 1024 pref medium
fe80::/64 dev enp7s0 proto kernel metric 256 pref medium
fe80::/64 dev enp1s0 proto kernel metric 256 pref medium
# ip -4 n
#
# ip -6 n
#
Le test : ping -c 2 198.19.0.2 depuis Terminal.
# ip -4 n
#
# ip -6 n
fe80::1 dev enp1s0 lladdr 52:54:00:69:0f:ed router REACHABLE
# ip -4 n
198.19.0.1 dev enp1s0 lladdr 52:54:00:2a:ea:b5 DELAY
# ip -6 n
#
# ip -4 n
198.19.0.2 dev enp7s0 lladdr 52:54:00:16:68:77 REACHABLE
# ip -6 n
fe80::2 dev enp1s0 lladdr 52:54:00:70:9f:c8 REACHABLE
1 0.000000 fe80::5054:ff:fe70:9fc8 → ff02::1:ff00:1 ICMPv6 86 Neighbor Solicitation for fe80::1 from 52:54:00:70:9f:c8
2 0.000473 fe80::1 → fe80::5054:ff:fe70:9fc8 ICMPv6 86 Neighbor Advertisement fe80::1 (rtr, sol, ovr) is at 52:54:00:69:0f:ed
3 0.000509 198.18.0.2 → 198.19.0.2 ICMP 98 Echo (ping) request id=0x0001, seq=1/256, ttl=64
4 0.001634 fe80::5054:ff:fe69:fed → ff02::1:ff00:2 ICMPv6 86 Neighbor Solicitation for fe80::2 from 52:54:00:69:0f:ed
5 0.001668 fe80::2 → fe80::5054:ff:fe69:fed ICMPv6 86 Neighbor Advertisement fe80::2 (sol, ovr) is at 52:54:00:70:9f:c8
6 0.001919 198.19.0.2 → 198.18.0.2 ICMP 98 Echo (ping) reply id=0x0001, seq=1/256, ttl=63 (request in 3)
7 1.001385 198.18.0.2 → 198.19.0.2 ICMP 98 Echo (ping) request id=0x0001, seq=2/512, ttl=64
8 1.002604 198.19.0.2 → 198.18.0.2 ICMP 98 Echo (ping) reply id=0x0001, seq=2/512, ttl=63 (request in 7)
1 0.000000 52:54:00:2a:ea:b5 → Broadcast ARP 42 Who has 198.19.0.2? Tell 198.19.0.1
2 0.000057 52:54:00:16:68:77 → 52:54:00:2a:ea:b5 ARP 42 198.19.0.2 is at 52:54:00:16:68:77
3 0.000335 198.18.0.2 → 198.19.0.2 ICMP 98 Echo (ping) request id=0x0001, seq=1/256, ttl=63
4 0.000397 198.19.0.2 → 198.18.0.2 ICMP 98 Echo (ping) reply id=0x0001, seq=1/256, ttl=64 (request in 3)
5 1.001143 198.18.0.2 → 198.19.0.2 ICMP 98 Echo (ping) request id=0x0001, seq=2/512, ttl=63
6 1.001190 198.19.0.2 → 198.18.0.2 ICMP 98 Echo (ping) reply id=0x0001, seq=2/512, ttl=64 (request in 5)
Certains paquets sont en double car je capture sur les deux interfaces réseau.
1 0.000000 fe80::5054:ff:fe70:9fc8 → ff02::1:ff00:1 ICMPv6 92 Neighbor Solicitation for fe80::1 from 52:54:00:70:9f:c8
2 0.000076 fe80::1 → fe80::5054:ff:fe70:9fc8 ICMPv6 92 Neighbor Advertisement fe80::1 (rtr, sol, ovr) is at 52:54:00:69:0f:ed
3 0.000412 198.18.0.2 → 198.19.0.2 ICMP 104 Echo (ping) request id=0x0001, seq=1/256, ttl=64
4 0.000454 52:54:00:2a:ea:b5 → ARP 48 Who has 198.19.0.2? Tell 198.19.0.1
5 0.000824 52:54:00:16:68:77 → ARP 48 198.19.0.2 is at 52:54:00:16:68:77
6 0.000833 198.18.0.2 → 198.19.0.2 ICMP 104 Echo (ping) request id=0x0001, seq=1/256, ttl=63
7 0.001203 198.19.0.2 → 198.18.0.2 ICMP 104 Echo (ping) reply id=0x0001, seq=1/256, ttl=64 (request in 6)
8 0.001237 fe80::5054:ff:fe69:fed → ff02::1:ff00:2 ICMPv6 92 Neighbor Solicitation for fe80::2 from 52:54:00:69:0f:ed
9 0.001566 fe80::2 → fe80::5054:ff:fe69:fed ICMPv6 92 Neighbor Advertisement fe80::2 (sol, ovr) is at 52:54:00:70:9f:c8
10 0.001595 198.19.0.2 → 198.18.0.2 ICMP 104 Echo (ping) reply id=0x0001, seq=1/256, ttl=63
11 1.001480 198.18.0.2 → 198.19.0.2 ICMP 104 Echo (ping) request id=0x0001, seq=2/512, ttl=64
12 1.001504 198.18.0.2 → 198.19.0.2 ICMP 104 Echo (ping) request id=0x0001, seq=2/512, ttl=63
13 1.002104 198.19.0.2 → 198.18.0.2 ICMP 104 Echo (ping) reply id=0x0001, seq=2/512, ttl=64 (request in 12)
14 1.002121 198.19.0.2 → 198.18.0.2 ICMP 104 Echo (ping) reply id=0x0001, seq=2/512, ttl=63
Pour ceux qui se demandent si Linux permet des routes IPv6 dont le prochain saut est une IPv4 : nope.
Sur Routeur :
# ip -6 r a 3fff:42::/64 via inet 198.19.0.2 dev enp7s0
Error: IPv6 does not support RTA_VIA attribute.
# ip -6 r a 3fff:42::/64 via 198.19.0.2 dev enp7s0
Error: inet6 address is expected rather than "198.19.0.2".
Via https://mastodon.gougere.fr/@bortzmeyer/116985715930270669 et https://bsky.app/profile/aeris.eu.org.