J’exploite des serveurs sur lesquels nous faisons du BGP (EVPN - VXLAN). L’architecture est similaire à celle décrite par Vincent Bernat. Les hyperviseurs jouent le rôle de routeur pour les VM.

Quand j’ai fait le plan d’addressage, j’ai pris des réseaux IPv4/IPv6 respectivement en /24 et /64 avec les VIP gateway positionnées sur la première IP disponible : 10.0.0.1/24 et fdc0:3489:e0fd::/64.

Cette topologie fonctionne parfaitement depuis 2 ans. Récement, nous avons dû déployer une VM dédiée à l’interconnexion de deux VRF. Nous utilisons des sessions iBGP pour l’échange de routes entre une VM et un hyperviseur.

hv-vm-bgp-sessions

Dans notre cas la session est bien montée et les routes échangées sont correctes. J’active le forwarding IPv6 sur la VM (net.ipv6.conf.all.forwarding=1). À ce moment précis la session BGP tombe. Que s’est-il passé ?

Première étape de debug, l’analyse des logs du démon sur la VM :

1
2
3
4
Dec 25 09:42:23 firewall bgpd[10797]: [PXVXG-TFNNT] %ADJCHANGE: neighbor fdc0:3489:e0fd:: in vrf vrf1 Down Peer closed the session
Dec 25 09:42:23 firewall bgpd[10797]: [PXVXG-TFNNT] %ADJCHANGE: neighbor fdc0:3489:e0fe:: in vrf vrf2 Down Peer closed the session
Dec 25 09:42:24 firewall bgpd[10797]: [TXY0T-CYY6F][EC 100663299] Can't get remote address and port: Transport endpoint is not connected
Dec 25 09:42:24 firewall bgpd[10797]: [TXY0T-CYY6F][EC 100663299] Can't get remote address and port: Transport endpoint is not connected

L’erreur semble assez claire, notre neighbor BGP ne semble plus joignable.

1
2
3
root@firewall:~# ip vrf exec vrf1 ping fdc0:3489:e0fd::
PING fdc0:3489:e0fd:: (fdc0:3489:e0fd::) 56 data bytes
64 bytes from fdc0:3489:e0fd::1: icmp_seq=1 ttl=64 time=0.025 ms

Premier indice, la réponse est trés rapide et le from du reply vient de la VM.

Je décide de regarder la route utilisée par mon kernel pour joindre cette IP :

1
2
root@firewall:~# ip route get fdc0:3489:e0fd:: vrf vrf1
anycast fdc0:3489:e0fd:: from :: dev vrf1 table vrf1 proto kernel src fdc0:3489:e0fd::1 metric 0 pref medium

Nous constatons une route anycast. Je n’ai jamais vu le type de route anycast mais le fonctionnement semble être identique aux routes de type local.

En résumé, le kernel ajoute une route anycast sur la premiére IPv6 de notre réseau dès que le forwarding est activé.

En fouillant dans le code du kernel je tombe sur dev_forward_change. Nous allons utiliser les function tracer afin de comprendre l’implémentation. J’ai prévu de faire plus tard un article sur ce sujet.

Vérifions si une fonction tracer est disponible.

1
2
root@firewall:/sys/kernel/debug/tracing# grep -i dev_forward_change available_filter_functions
dev_forward_change

C’est disponible, nous pouvons activer le tracing et activer le forwarding.

1
2
3
4
root@firewall:/sys/kernel/debug/tracing# echo dev_forward_change > set_ftrace_filter
root@firewall:/sys/kernel/debug/tracing# echo function > current_tracer
root@firewall:/sys/kernel/debug/tracing# echo 1 > tracing_on
root@firewall:/sys/kernel/debug/tracing# sysctl -w net.ipv6.conf.all.forwarding=1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
root@firewall:/sys/kernel/debug/tracing# cat trace
# tracer: function
#
# entries-in-buffer/entries-written: 6/6   #P:2
#
#                                _-----=> irqs-off/BH-disabled
#                               / _----=> need-resched
#                              | / _---=> hardirq/softirq
#                              || / _--=> preempt-depth
#                              ||| / _-=> migrate-disable
#                              |||| /     delay
#           TASK-PID     CPU#  |||||  TIMESTAMP  FUNCTION
#              | |         |   |||||     |         |
          sysctl-2147    [001] ...1.  7568.153838: __ipv6_dev_ac_inc <-dev_forward_change
          sysctl-2147    [001] ...1.  7568.153875: __ipv6_dev_ac_inc <-dev_forward_change
          sysctl-2147    [001] ...1.  7568.153957: __ipv6_dev_ac_inc <-dev_forward_change
          sysctl-2147    [001] ...1.  7568.153981: __ipv6_dev_ac_inc <-dev_forward_change
          sysctl-2147    [001] ...1.  7568.154053: __ipv6_dev_ac_inc <-dev_forward_change

Ça nous confirme bien que la fonction dev_forward_change est bien utilisée, nous pouvons continuer la lecture. Quelques lignes plus loin nous constatons l’appel à addrconf_join_anycast

Voici la fonction qui decide quelle IP voler à notre hyperviseur :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static void addrconf_join_anycast(struct inet6_ifaddr *ifp)
{
        struct in6_addr addr;

        if (ifp->prefix_len >= 127) /* RFC 6164 */
                return;
        ipv6_addr_prefix(&addr, &ifp->addr, ifp->prefix_len);
        if (ipv6_addr_any(&addr))
                return;
        __ipv6_dev_ac_inc(ifp->idev, &addr);
}

Dans cette fonction, on constate que si l’IP portée sur l’interface est dans un préfixe plus grand qu’un /127, alors nous appelons la fonction __ipv6_dev_ac_inc avec le device et le préfixe.

La fonction __ipv6_dev_ac_inc, ajoute cette route anycast grâce à la fonction ip6_route_info_create.

Aprés avoir compris ce cheminement et avoir trouvé les bon mots-clés, je suis tombé sur une série d’articles comme celui de Daryll Swer et celui de Karl Auer. Enfin, si vous souhaitez approfondir, je vous conseille la RFC 4291.

En résumé, activer le forwarding IPv6 sur notre VM a eu un effet inattendu : le kernel a automatiquement ajouté une adresse anycast “Subnet Router” (merci la RFC 4291), ce qui a gentiment court-circuité notre session BGP en s’appropriant l’adresse qu’on utilisait.

Aujourd’hui je n’ai jamais vu de gens utiliser volontairement les propriétées de cette addresse, de plus le comportement de linux peu visible peut être source d’erreur.

Cepandant la solution est plutôt simple : éviter la première adresse du réseau (::). Une adresse spécifique comme ::1 fera parfaitement l’affaire.