DNS Anycast avec BIRD2 et BGP : déploiement multi-sites

15 min de lecture·Matthieu·high-availabilitynftablesdnsanycastbind9bird2bgp|

Déployez un DNS anycast sur plusieurs VPS avec BIRD2 et BIND9. Configuration BGP, synchronisation de zones avec TSIG, basculement par health-check et durcissement nftables.

Le DNS anycast attribue la même adresse IP à des serveurs DNS situés dans plusieurs emplacements. Chaque serveur annonce cette IP via BGP. Les routeurs dirigent les requêtes vers le serveur le plus proche en fonction de la topologie réseau. Si un serveur tombe et retire sa route BGP, les requêtes atteignent automatiquement le serveur suivant le plus proche. Résultat : une résolution DNS à faible latence et un basculement automatique, sans modification côté client.

Ce guide déploie un DNS anycast en production sur deux nœuds VPS Virtua avec BIRD2 comme démon de routage et BIND9 comme serveur DNS autoritaire. La sécurité est intégrée à chaque étape, pas ajoutée à la fin.

Ce que vous allez construire :

  • Deux nœuds VPS dans des emplacements différents, chacun annonçant le même préfixe /24 via BGP
  • BIND9 en serveur DNS autoritaire, à l'écoute sur l'IP anycast
  • Synchronisation de zones entre nœuds par transferts authentifiés TSIG
  • Un script de health-check qui retire les routes BGP en cas de panne DNS
  • Des règles de pare-feu nftables verrouillant chaque nœud

Quels sont les prérequis pour le DNS anycast ?

Avant de commencer, les ressources suivantes doivent être provisionnées. Si vous n'avez jamais configuré BGP sur un VPS, commencez par Configuration BGP avec BIRD2 sur un VPS Linux. Pour créer des enregistrements ROA, consultez RPKI ROA pour BGP : créer des ROAs, valider les routes dans BIRD2 et FRR.

Prérequis Détails
ASN Votre propre ASN (ex. AS212345), enregistré auprès d'un RIR
Préfixe IPv4 Au minimum un /24 (ex. 198.51.100.0/24). Les préfixes plus longs que /24 sont filtrés par la plupart des fournisseurs de transit.
Préfixe IPv6 Au minimum un /48 (ex. 2001:db8:abcd::/48)
Enregistrements ROA Créés dans le portail de votre RIR pour les deux préfixes, autorisant votre ASN
Nœuds VPS 2+ VPS Virtua dans des emplacements différents, chacun avec une session BGP vers le routeur amont
Infos BGP amont IP et ASN du voisin pour chaque nœud, fournis par Virtua
Debian 12 Ce guide utilise Debian Bookworm. Adaptez les noms de paquets pour d'autres distributions.

Tout au long de ce guide, nous utilisons ces valeurs d'exemple. Remplacez-les par les vôtres :

Variable Valeur
Votre ASN 212345
IPv4 anycast 198.51.100.1
Préfixe anycast 198.51.100.0/24
IPv6 anycast 2001:db8:abcd::1
Préfixe IPv6 anycast 2001:db8:abcd::/48
Nœud A (Francfort) IP principale 203.0.113.10
Nœud A voisin amont 169.254.169.1 AS 64496
Nœud B (Amsterdam) IP principale 203.0.113.20
Nœud B voisin amont 169.254.169.1 AS 64496
Zone DNS example.com

À quoi ressemble la topologie réseau ?

                    ┌─────────────┐
                    │   Internet   │
                    └──────┬───────┘
                           │
              ┌────────────┴────────────┐
              │                         │
     ┌────────┴────────┐      ┌────────┴────────┐
     │  Routeur Virtua  │      │  Routeur Virtua  │
     │  Francfort        │      │  Amsterdam        │
     │  AS 64496         │      │  AS 64496         │
     └────────┬────────┘      └────────┬────────┘
              │ eBGP                    │ eBGP
     ┌────────┴────────┐      ┌────────┴────────┐
     │  Nœud A (VPS)    │      │  Nœud B (VPS)    │
     │  AS 212345        │      │  AS 212345        │
     │  BIRD2 + BIND9    │      │  BIRD2 + BIND9    │
     │  anycast0:         │      │  anycast0:         │
     │  198.51.100.1/24  │      │  198.51.100.1/24  │
     │  2001:db8:abcd::1 │      │  2001:db8:abcd::1 │
     └──────────────────┘      └──────────────────┘
              │                         │
              │  AXFR + TSIG (via       │
              │  IPs principales)       │
              └─────────────────────────┘

Les deux nœuds annoncent 198.51.100.0/24 et 2001:db8:abcd::/48 via BGP. Les clients atteignent le nœud le plus proche topologiquement. Les transferts de zones passent par les IP principales (unicast), pas par l'adresse anycast.

Comment créer l'interface loopback anycast ?

Chaque nœud a besoin de l'IP anycast assignée à une interface locale. Une interface dummy convient parfaitement : elle est toujours active et n'a aucune dépendance physique. Configurez-la avec systemd-networkd pour qu'elle persiste après les redémarrages.

Créez le fichier netdev :

cat > /etc/systemd/network/10-anycast.netdev << 'EOF'
[NetDev]
Name=anycast0
Kind=dummy
EOF

Créez le fichier network pour assigner les IP anycast. Utilisez la longueur de préfixe complète (/24 pour IPv4, /48 pour IPv6) afin que le protocole direct dans BIRD2 voie des routes connectées correspondant aux préfixes que vous souhaitez annoncer :

cat > /etc/systemd/network/10-anycast.network << 'EOF'
[Match]
Name=anycast0

[Network]
Address=198.51.100.1/24
Address=2001:db8:abcd::1/48
EOF

Activez et démarrez systemd-networkd. Si votre VPS utilise ifupdown pour l'interface principale (vérifiez /etc/network/interfaces), n'utilisez pas systemctl restart systemd-networkd car cela peut perturber le réseau principal. Le démarrage initial est sûr, car systemd-networkd ne gère que les interfaces ayant un fichier de configuration dans /etc/systemd/network/ :

systemctl enable --now systemd-networkd
ip addr show anycast0

Sortie attendue :

3: anycast0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether a2:b4:c6:d8:e0:f2 brd ff:ff:ff:ff:ff:ff
    inet 198.51.100.1/24 brd 198.51.100.255 scope global anycast0
       valid_lft forever preferred_lft forever
    inet6 2001:db8:abcd::1/48 scope global
       valid_lft forever preferred_lft forever

L'état de l'interface affiche UNKNOWN, ce qui est normal pour les interfaces dummy. Cela signifie que la couche liaison est toujours active. Les deux adresses IPv4 et IPv6 doivent apparaître.

Répétez cette opération sur chaque nœud anycast. La configuration est identique.

Quelle configuration BIRD2 est nécessaire pour l'annonce de routes anycast ?

BIRD2 annonce le préfixe anycast au routeur amont via eBGP. Le principe clé : BIRD2 n'exporte que les routes apprises depuis l'interface anycast0 via le protocole direct. Quand l'interface tombe (ou que le script de health-check la désactive), BIRD2 retire automatiquement la route.

Installez BIRD2 :

apt update && apt install -y bird2

Debian 12 fournit BIRD 2.0.12. Pour les fonctionnalités plus récentes (améliorations BFD, filtres avancés), ajoutez le dépôt BIRD officiel.

Configuration du nœud A (/etc/bird/bird.conf)

# /etc/bird/bird.conf -- Node A (Frankfurt)

log syslog all;
router id 203.0.113.10;

# Scan interfaces every 10 seconds
protocol device {
    scan time 10;
}

# Learn routes from the anycast dummy interface
protocol direct anycast {
    ipv4;
    ipv6;
    interface "anycast0";
}

# Define the prefixes we are authorized to announce
define ANYCAST_V4 = [ 198.51.100.0/24 ];
define ANYCAST_V6 = [ 2001:db8:abcd::/48 ];

# Export filter: only announce our anycast prefixes
filter export_anycast_v4 {
    if net ~ ANYCAST_V4 then accept;
    reject;
}

filter export_anycast_v6 {
    if net ~ ANYCAST_V6 then accept;
    reject;
}

# IPv4 BGP session to upstream
protocol bgp upstream4 {
    description "Virtua Frankfurt upstream IPv4";
    local 203.0.113.10 as 212345;
    neighbor 169.254.169.1 as 64496;
    password "your-bgp-md5-secret";
    hold time 90;
    keepalive time 30;
    graceful restart on;

    ipv4 {
        import none;
        export filter export_anycast_v4;
    };
}

# IPv6 BGP session to upstream
protocol bgp upstream6 {
    description "Virtua Frankfurt upstream IPv6";
    local 2001:db8:1::10 as 212345;
    neighbor 2001:db8:1::1 as 64496;
    password "your-bgp-md5-secret";
    hold time 90;
    keepalive time 30;
    graceful restart on;

    ipv6 {
        import none;
        export filter export_anycast_v6;
    };
}

Points clés :

  • protocol direct anycast apprend les routes uniquement depuis l'interface anycast0. Aucune autre interface ne fuit dans la table de routage.
  • Les filtres d'export restreignent les annonces à votre /24 et /48 exactement. Cela empêche les fuites de routes accidentelles.
  • password active l'authentification TCP MD5 (RFC 2385) sur la session BGP. Obtenez le secret partagé auprès de votre fournisseur amont.
  • import none signifie que ce nœud n'accepte aucune route de l'amont. Les nœuds anycast ne font qu'annoncer ; ils utilisent la route par défaut du VPS pour le trafic sortant.
  • graceful restart on réduit l'instabilité de route (route flap) lors des redémarrages de BIRD2.

Configuration du nœud B

Copiez le même fichier sur le nœud B. Modifiez uniquement :

router id 203.0.113.20;

# In protocol bgp upstream4:
    local 203.0.113.20 as 212345;
    # neighbor IP and AS may differ per location, use the values Virtua provides

Démarrage de BIRD2

Activez et démarrez BIRD2 :

systemctl enable --now bird

enable assure la persistance après redémarrage. --now lance le service immédiatement.

Vérifiez l'état :

systemctl status bird

Vérifiez l'état des sessions BGP :

birdc show protocols

Sortie attendue :

BIRD 2.0.12 ready.
Name       Proto      Table      State  Since         Info
device1    Device     ---        up     2026-03-19
anycast    Direct     ---        up     2026-03-19
upstream4  BGP        ---        up     2026-03-19    Established
upstream6  BGP        ---        up     2026-03-19    Established

Les deux sessions BGP doivent afficher Established. Si vous voyez Active ou Connect à la place, vérifiez l'IP du voisin, l'ASN et le mot de passe. Consultez les logs avec journalctl -u bird -f.

Vérifiez les routes exportées :

birdc show route export upstream4

Résultat attendu :

BIRD 2.0.12 ready.
Table master4:
198.51.100.0/24      unicast [anycast 2026-03-19] * (240)
	dev anycast0

La route provient du protocole direct anycast et est exportée vers upstream4. Répétez sur le nœud B.

Comment configurer BIND9 pour écouter sur l'IP anycast ?

BIND9 fonctionne comme serveur DNS autoritaire uniquement sur chaque nœud, à l'écoute sur l'IP anycast. Les clients interrogent l'IP anycast ; BGP s'assure qu'ils atteignent le nœud le plus proche.

Installez BIND9 :

apt update && apt install -y bind9 bind9-utils

Générer une clé TSIG pour les transferts de zones

Les transferts de zones entre nœuds doivent être authentifiés. Générez une clé TSIG avec tsig-keygen :

tsig-keygen -a hmac-sha256 anycast-transfer > /etc/bind/anycast-transfer.key

Le résultat ressemble à ceci :

cat /etc/bind/anycast-transfer.key
key "anycast-transfer" {
    algorithm hmac-sha256;
    secret "base64-encoded-secret-here";
};

Copiez ce fichier à l'identique sur tous les nœuds. Le nom de la clé et le secret doivent correspondre partout.

Définissez des permissions restrictives :

chown root:bind /etc/bind/anycast-transfer.key
chmod 640 /etc/bind/anycast-transfer.key

Le fichier doit appartenir à root:bind avec les permissions 640 :

ls -la /etc/bind/anycast-transfer.key
-rw-r----- 1 root bind 113 Mar 19 12:00 /etc/bind/anycast-transfer.key

Configuration du nœud A (primaire)

Éditez /etc/bind/named.conf.options :

options {
    directory "/var/cache/bind";

    // Listen only on the anycast IP and localhost
    listen-on { 198.51.100.1; 127.0.0.1; };
    listen-on-v6 { 2001:db8:abcd::1; ::1; };

    // Authoritative only, no recursion
    recursion no;
    allow-recursion { none; };

    // Hide version to avoid targeted exploits
    version "not disclosed";

    // Disable zone transfers by default
    allow-transfer { none; };

    // Rate limiting to mitigate DNS amplification
    rate-limit {
        responses-per-second 10;
        window 5;
    };

    dnssec-validation auto;
};

Incluez la clé TSIG. Éditez /etc/bind/named.conf.local :

include "/etc/bind/anycast-transfer.key";

// Allow transfers only to Node B using TSIG
acl "secondaries" {
    key "anycast-transfer";
};

zone "example.com" {
    type primary;
    file "/var/lib/bind/db.example.com";
    allow-transfer { secondaries; };
    also-notify { 203.0.113.20; };
    notify yes;
};

Créez le fichier de zone /var/lib/bind/db.example.com :

$TTL 300
@   IN  SOA ns1.example.com. admin.example.com. (
        2026031901  ; Serial (YYYYMMDDNN)
        3600        ; Refresh (1 hour)
        900         ; Retry (15 minutes)
        604800      ; Expire (1 week)
        300         ; Negative cache TTL (5 minutes)
)

@       IN  NS      ns1.example.com.
@       IN  NS      ns2.example.com.

; NS records point to the anycast IP, same IP, different names
ns1     IN  A       198.51.100.1
ns1     IN  AAAA    2001:db8:abcd::1
ns2     IN  A       198.51.100.1
ns2     IN  AAAA    2001:db8:abcd::1

; Your records
@       IN  A       198.51.100.10
@       IN  AAAA    2001:db8:abcd::10
www     IN  CNAME   example.com.
mail    IN  A       198.51.100.25
@       IN  MX  10  mail.example.com.

Définissez les propriétaires :

chown bind:bind /var/lib/bind/db.example.com
chmod 640 /var/lib/bind/db.example.com

Configuration du nœud B (secondaire)

Sur le nœud B, /etc/bind/named.conf.options est identique au nœud A.

/etc/bind/named.conf.local diffère :

include "/etc/bind/anycast-transfer.key";

server 203.0.113.10 {
    keys { "anycast-transfer"; };
};

zone "example.com" {
    type secondary;
    file "/var/lib/bind/db.example.com";
    primaries { 203.0.113.10 key "anycast-transfer"; };
};

La directive server indique à BIND9 d'authentifier toute communication avec le nœud A à l'aide de la clé TSIG. Les transferts de zones sont automatiques une fois cette configuration en place.

Démarrage de BIND9

Activez et démarrez sur les deux nœuds :

systemctl enable --now named

Vérifiez l'état :

systemctl status named

Testez la résolution DNS sur l'IP anycast :

dig @198.51.100.1 example.com A +short

Résultat attendu :

198.51.100.10

Sur le nœud B, vérifiez que la zone a été transférée :

dig @198.51.100.1 example.com SOA +short
ns1.example.com. admin.example.com. 2026031901 3600 900 604800 300

Consultez les logs BIND9 pour le transfert :

journalctl -u named --no-pager | grep "transfer of"

Résultat attendu :

transfer of 'example.com/IN' from 203.0.113.10#53: Transfer status: success

Le numéro de série doit correspondre sur les deux nœuds. S'il diffère sur le secondaire, le transfert a échoué. Vérifiez la cohérence de la clé TSIG et les règles de pare-feu.

Quelles règles de pare-feu sont nécessaires pour un nœud DNS anycast BGP ?

Verrouillez chaque nœud avec nftables. N'autorisez que le nécessaire : SSH pour l'administration, BGP depuis le routeur amont, et DNS depuis partout.

Créez /etc/nftables.conf :

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        # Connection tracking
        ct state established,related accept
        ct state invalid drop

        # Loopback
        iif lo accept

        # ICMP and ICMPv6 (needed for path MTU discovery and diagnostics)
        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept

        # SSH (restrict to your management IPs in production)
        tcp dport 22 accept

        # BGP from upstream router only (IPv4)
        ip saddr 169.254.169.1 tcp dport 179 accept
        ip saddr 169.254.169.1 tcp sport 179 accept

        # BGP from upstream router only (IPv6)
        ip6 saddr 2001:db8:1::1 tcp dport 179 accept
        ip6 saddr 2001:db8:1::1 tcp sport 179 accept

        # DNS on anycast IP
        ip daddr 198.51.100.1 udp dport 53 accept
        ip daddr 198.51.100.1 tcp dport 53 accept
        ip6 daddr 2001:db8:abcd::1 udp dport 53 accept
        ip6 daddr 2001:db8:abcd::1 tcp dport 53 accept

        # Zone transfers from the other node (TSIG-authenticated at app layer,
        # but we also restrict at network layer)
        ip saddr 203.0.113.20 tcp dport 53 accept
        ip saddr 203.0.113.10 tcp dport 53 accept

        # Log and drop everything else
        log prefix "nftables-drop: " limit rate 5/minute
        drop
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}

Adaptez les lignes ip saddr et ip6 saddr sur le nœud B (inversez les IP de transfert de zone, et utilisez le voisin IPv6 amont du nœud B). En production, restreignez SSH à votre CIDR d'administration.

Appliquez et activez :

systemctl enable --now nftables

Listez les règles actives pour confirmer leur chargement :

nft list ruleset

Puis testez que le DNS fonctionne toujours depuis l'extérieur :

# Run this from your local machine, NOT the server
dig @198.51.100.1 example.com A +short

Si le DNS cesse de fonctionner après l'application des règles de pare-feu, vérifiez que le daddr correspond exactement à votre IP anycast et que l'interface anycast0 est active.

Comment vérifier le DNS par health-check et retirer les routes BGP en cas de panne ?

Un script de health-check surveille BIND9 et signale à BIRD2 de retirer la route anycast quand le DNS est en panne. La méthode : désactiver l'interface anycast0, ce qui amène BIRD2 à retirer automatiquement le préfixe puisque le protocole direct ne voit plus la route.

Pourquoi ne pas se fier uniquement aux timers BGP ?

Avec les timers BGP par défaut (90 s hold, 30 s keepalive), le basculement prend jusqu'à 90 secondes. Un health-check détecte la panne DNS en quelques secondes et déclenche le retrait immédiatement. Le tableau ci-dessous montre l'impact :

Méthode Temps de détection Convergence
Expiration du hold timer BGP 90 secondes 90-180 secondes
BFD (si disponible) <1 seconde 1-3 secondes
Script de health-check 5-15 secondes 35-45 secondes (+ propagation BGP)

BFD ne détecte que les pannes de liaison/session, pas les pannes applicatives. Le script de health-check attrape les crashs de BIND9, les erreurs de configuration et les disques pleins que BFD ne peut pas voir.

Créer un utilisateur dédié

Le health-check s'exécute sous un utilisateur dédié avec des privilèges minimaux :

useradd --system --no-create-home --shell /usr/sbin/nologin anycast-healthcheck

Accordez-lui la permission de contrôler l'interface anycast. Créez une règle sudoers :

cat > /etc/sudoers.d/anycast-healthcheck << 'EOF'
anycast-healthcheck ALL=(root) NOPASSWD: /usr/sbin/ip link set anycast0 up, /usr/sbin/ip link set anycast0 down
EOF
chmod 440 /etc/sudoers.d/anycast-healthcheck

Écrire le script de health-check

Créez /usr/local/bin/anycast-healthcheck.sh :

#!/bin/bash
# Anycast DNS health check with hysteresis
# Withdraws BGP route by downing anycast0 when BIND9 is unhealthy

set -euo pipefail

ANYCAST_IF="anycast0"
CHECK_IP="127.0.0.1"
CHECK_DOMAIN="example.com"
CHECK_TYPE="SOA"

# Hysteresis: 3 failures to withdraw, 2 successes to re-announce
FAIL_THRESHOLD=3
RECOVER_THRESHOLD=2
CHECK_INTERVAL=5

fail_count=0
recover_count=0
is_withdrawn=false

log() {
    logger -t anycast-healthcheck "$1"
}

check_dns() {
    dig +time=2 +tries=1 @"${CHECK_IP}" "${CHECK_DOMAIN}" "${CHECK_TYPE}" > /dev/null 2>&1
}

withdraw_route() {
    if [ "$is_withdrawn" = false ]; then
        sudo /usr/sbin/ip link set "${ANYCAST_IF}" down
        is_withdrawn=true
        log "WITHDRAW: ${ANYCAST_IF} down after ${FAIL_THRESHOLD} consecutive failures"
    fi
}

announce_route() {
    if [ "$is_withdrawn" = true ]; then
        sudo /usr/sbin/ip link set "${ANYCAST_IF}" up
        is_withdrawn=true  # will be set to false after verification
        # Verify the interface came back
        sleep 1
        if ip link show "${ANYCAST_IF}" | grep -q "UP"; then
            is_withdrawn=false
            recover_count=0
            log "ANNOUNCE: ${ANYCAST_IF} up after ${RECOVER_THRESHOLD} consecutive successes"
        else
            log "ERROR: failed to bring ${ANYCAST_IF} up"
        fi
    fi
}

log "Starting anycast health check for ${CHECK_DOMAIN} on ${CHECK_IP}"

while true; do
    if check_dns; then
        fail_count=0
        recover_count=$((recover_count + 1))
        if [ "$recover_count" -ge "$RECOVER_THRESHOLD" ]; then
            announce_route
        fi
    else
        recover_count=0
        fail_count=$((fail_count + 1))
        log "DNS check failed (${fail_count}/${FAIL_THRESHOLD})"
        if [ "$fail_count" -ge "$FAIL_THRESHOLD" ]; then
            withdraw_route
        fi
    fi
    sleep "${CHECK_INTERVAL}"
done

Définissez les permissions :

chmod 755 /usr/local/bin/anycast-healthcheck.sh

Créer un service systemd

Créez /etc/systemd/system/anycast-healthcheck.service :

[Unit]
Description=Anycast DNS health check
After=bird.service named.service
Wants=bird.service named.service

[Service]
Type=simple
User=anycast-healthcheck
ExecStart=/usr/local/bin/anycast-healthcheck.sh
Restart=always
RestartSec=5

# Hardening
NoNewPrivileges=no
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadOnlyPaths=/
ReadWritePaths=/run

[Install]
WantedBy=multi-user.target

NoNewPrivileges=no est requis ici car le script utilise sudo pour basculer l'interface. La règle sudoers limite les commandes que l'utilisateur peut exécuter.

La section [Unit] utilise Wants= au lieu de Requires=. Avec Requires=, systemd arrêterait le health-check quand named s'arrête. C'est exactement l'inverse du comportement souhaité : le health-check doit continuer à tourner pour détecter la panne DNS et retirer la route.

Activez et démarrez :

systemctl daemon-reload
systemctl enable --now anycast-healthcheck

Vérifiez qu'il fonctionne :

systemctl status anycast-healthcheck

Les logs doivent afficher le message de démarrage :

journalctl -u anycast-healthcheck -f
Starting anycast health check for example.com on 127.0.0.1

Comment tester le basculement DNS anycast ?

Exécutez ces tests depuis une machine externe, pas depuis l'un des nœuds.

Étape 1 : confirmer que les deux nœuds annoncent

Depuis votre machine locale :

dig @198.51.100.1 example.com A +short

Confirmez que vous obtenez une réponse. Lancez un traceroute pour voir quel nœud vous atteignez :

traceroute -n 198.51.100.1

Le chemin indique quel emplacement est le plus proche de vous.

Étape 2 : simuler une panne DNS sur le nœud A

Sur le nœud A, arrêtez BIND9 :

systemctl stop named

Surveillez les logs du health-check :

journalctl -u anycast-healthcheck -f

Séquence attendue :

DNS check failed (1/3)
DNS check failed (2/3)
DNS check failed (3/3)
WITHDRAW: anycast0 down after 3 consecutive failures

Après 15 secondes (3 vérifications à 5 secondes d'intervalle), le health-check désactive anycast0. BIRD2 détecte le changement d'interface et retire la route.

Vérifiez que la route a disparu :

birdc show route export upstream4

La table doit être vide. Le routeur amont retire 198.51.100.0/24 du nœud A et redirige tout le trafic vers le nœud B.

Étape 3 : vérifier le basculement côté client

Depuis votre machine externe, interrogez à nouveau :

dig @198.51.100.1 example.com A +short +time=5

La première requête après le retrait peut expirer (l'ancienne route est encore en cache sur certains routeurs). Les requêtes suivantes atteignent le nœud B. La convergence BGP prend généralement 30 à 90 secondes selon la configuration de votre amont.

Étape 4 : restaurer le nœud A

systemctl start named

Le health-check détecte le retour du DNS après 2 succès consécutifs (10 secondes) et réactive anycast0. BIRD2 ré-annonce la route.

journalctl -u anycast-healthcheck --no-pager | tail -5

Résultat attendu :

ANNOUNCE: anycast0 up after 2 consecutive successes

Vérifiez les sessions BGP :

birdc show protocols

Les deux sessions BGP doivent à nouveau afficher Established.

Étape 5 : mesurer le temps de convergence

Pour une mesure précise, lancez une boucle dig continue depuis une machine externe :

while true; do
    echo -n "$(date +%H:%M:%S) "
    dig @198.51.100.1 example.com A +short +time=1 +tries=1 || echo "TIMEOUT"
    sleep 1
done

Arrêtez BIND9 sur le nœud que vous atteignez actuellement. Comptez les secondes entre la dernière réponse réussie et la première réponse réussie provenant de l'autre nœud. Sur l'infrastructure Virtua avec les timers BGP par défaut, attendez-vous à 30-60 secondes d'indisponibilité partielle.

Comment BIRD2 se compare-t-il aux autres démons de routage pour l'anycast ?

Fonctionnalité BIRD2 FRRouting ExaBGP
Langage de configuration Filtres déclaratifs CLI type Cisco JSON/API
Intégration anycast Protocole direct sur interface dummy Route statique + redistribution Script externe annonce/retire
Méthode de health-check Up/down de l'interface déclenche le changement de route Commandes vtysh depuis un script Gestionnaire de processus intégré
Support IPv6 Config unifiée (canaux ipv4/ipv6) Familles d'adresses séparées Manuel par famille
Complexité des filtres Langage de filtres complet avec fonctions Route maps, prefix lists Logique externe uniquement
Empreinte mémoire Faible (~10 Mo pour une petite table) Moyenne (~30 Mo) Très faible (~5 Mo)
Maturité en production IXP, DNS large échelle (utilisé par Cloudflare, RIPE) FAI, centres de données Petits déploiements, monitoring

L'avantage de BIRD2 pour l'anycast : le protocol direct sur une interface dummy offre un retrait automatique de route quand l'interface tombe. Pas besoin de script externe pour la partie BGP. Le health-check n'a qu'à basculer l'interface.

Comment synchroniser les zones DNS entre nœuds anycast ?

La synchronisation de zones utilise la réplication primaire/secondaire intégrée à BIND9 via AXFR, authentifiée par TSIG. La configuration a été faite dans la section BIND9 ci-dessus. Voici les détails opérationnels.

Les mises à jour de zone sont unidirectionnelles : éditez le fichier de zone sur le nœud A (primaire), incrémentez le numéro de série, et rechargez :

# On Node A after editing the zone file
named-checkzone example.com /var/lib/bind/db.example.com
zone example.com/IN: loaded serial 2026031902
OK

Lancez toujours named-checkzone avant de recharger. Il détecte les erreurs de syntaxe.

rndc reload example.com

Le nœud B reçoit un NOTIFY du nœud A et récupère la zone mise à jour via AXFR. Sur le nœud B, vérifiez le numéro de série :

dig @127.0.0.1 example.com SOA +short

Le numéro de série doit correspondre. Si ce n'est pas le cas, vérifiez :

  1. Le pare-feu autorise TCP 53 du nœud A vers le nœud B
  2. La clé TSIG est identique sur les deux nœuds
  3. journalctl -u named sur le nœud B pour les erreurs de transfert

Ajouter un troisième nœud : configurez-le comme un autre secondaire. Ajoutez son IP à la liste also-notify et aux règles nftables du nœud A. Aucune modification nécessaire sur les secondaires existants.

Qu'en est-il du BGP anycast en IPv6 ?

La configuration BIRD2 ci-dessus inclut déjà IPv6. Le bloc protocol direct anycast a les deux canaux ipv4 et ipv6. La session BGP upstream6 exporte le préfixe /48.

Vérifiez l'export de route IPv6 :

birdc show route export upstream6
BIRD 2.0.12 ready.
Table master6:
2001:db8:abcd::/48   unicast [anycast 2026-03-19] * (240)
	dev anycast0

Testez le DNS en IPv6 :

dig @2001:db8:abcd::1 example.com AAAA +short

Dépannage

Session BGP bloquée en Active/Connect :

journalctl -u bird -f

Causes courantes : mauvaise IP de voisin, mauvais ASN, mot de passe MD5 incorrect, pare-feu bloquant TCP 179. Exécutez birdc show protocols all upstream4 pour des messages d'erreur détaillés.

BIND9 n'écoute pas sur l'IP anycast :

ss -tlnp | grep ':53'

Si vous ne voyez que 127.0.0.1:53, vérifiez que listen-on inclut l'IP anycast et que l'interface anycast0 était active avant le démarrage de BIND9. Redémarrez BIND9 après l'activation de l'interface : systemctl restart named.

Échec du transfert de zone :

journalctl -u named | grep -i "tsig\|transfer\|refused"

Vérifiez que le fichier de clé est lisible par l'utilisateur bind : ls -la /etc/bind/anycast-transfer.key. Testez un transfert manuel : dig @203.0.113.10 example.com AXFR -k /etc/bind/anycast-transfer.key.

Le health-check ne retire pas les routes :

journalctl -u anycast-healthcheck -f

Vérifiez les permissions sudo : sudo -u anycast-healthcheck sudo -l. L'utilisateur doit pouvoir exécuter /usr/sbin/ip link set anycast0 up et down sans mot de passe.

Requêtes en timeout après le basculement :

Les clients DNS mettent les réponses en cache. Le TTL dans le fichier de zone (300 secondes / 5 minutes) détermine combien de temps les clients utilisent des données périmées. La convergence BGP ajoute 30 à 90 secondes. Temps total de basculement du point de vue du client : jusqu'au TTL plus le temps de convergence. Réduisez le TTL minimum du SOA si un basculement plus rapide est nécessaire, mais ne descendez pas en dessous de 60 secondes pour les zones autoritaires.

Checklist de mise en production

Avant de passer en production :

  • Enregistrements ROA créés et validés (rpki-client ou portail de votre RIR)
  • Sessions BGP établies sur tous les nœuds (birdc show protocols)
  • Préfixe anycast visible dans les tables de routage mondiales (utilisez un looking glass)
  • BIND9 répond sur l'IP anycast depuis des réseaux externes
  • Le numéro de série de zone correspond sur tous les nœuds
  • Permissions de la clé TSIG à 640, propriétaire root:bind
  • Règles nftables actives sur tous les nœuds
  • Service de health-check activé et en cours d'exécution
  • Basculement testé : arrêt de BIND9, retrait de route confirmé, récupération confirmée
  • Monitoring configuré : alertes sur session BGP down, processus BIND9, échecs du health-check
  • journalctl -u bird et journalctl -u named ne montrent aucune erreur

Pour le guide parent couvrant les fondamentaux BGP et l'apport de votre propre espace IP, consultez BGP et Bring Your Own IP sur un VPS : le guide complet. Pour le filtrage de routes BGP et le durcissement sécurité, consultez BGP Route Filtering : Prefix Lists, Filtres AS-Path, Rejet des Bogons et GTSM.


Copyright 2026 Virtua.Cloud. Tous droits réservés. Ce contenu est une création originale de l'équipe Virtua.Cloud. Toute reproduction, republication ou redistribution sans autorisation écrite est interdite.