DNS Anycast con BIRD2 e BGP: configurazione multi-sede

13 min di lettura·Matthieu·high-availabilitynftablesdnsanycastbind9bird2bgp|

Implementa DNS anycast su più sedi VPS con BIRD2 e BIND9. Annuncio di rotte BGP, sincronizzazione zone con TSIG, failover tramite health-check e hardening nftables.

Il DNS anycast assegna lo stesso indirizzo IP a server DNS in più sedi. Ogni server annuncia l'IP tramite BGP. I router indirizzano le query al server più vicino in base alla topologia di rete. Se un server va in errore e ritira la sua rotta BGP, le query raggiungono automaticamente il server successivo più vicino. Il risultato: risoluzione DNS a bassa latenza e failover automatico senza modifiche lato client.

Questa guida implementa un DNS anycast in produzione su due nodi VPS Virtua usando BIRD2 come demone di routing e BIND9 come server DNS autoritativo. La sicurezza è integrata in ogni passaggio, non aggiunta alla fine.

Cosa costruirai:

  • Due nodi VPS in sedi diverse, ciascuno che annuncia lo stesso prefisso /24 via BGP
  • BIND9 come server DNS autoritativo in ascolto sull'IP anycast
  • Sincronizzazione delle zone tra nodi tramite trasferimenti autenticati TSIG
  • Uno script di health-check che ritira le rotte BGP quando il DNS fallisce
  • Regole firewall nftables per blindare ogni nodo

Quali sono i prerequisiti per il DNS anycast?

Prima di iniziare, queste risorse devono essere già provisionate. Se non hai mai configurato BGP su un VPS, leggi prima Configurazione BGP con BIRD2 su VPS Linux. Per creare record ROA, consulta RPKI ROA per BGP: creare ROA, validare le rotte in BIRD2 e FRR.

Requisito Dettagli
ASN Il tuo ASN (es. AS212345), registrato presso un RIR
Prefisso IPv4 Almeno un /24 (es. 198.51.100.0/24). I prefissi più lunghi di /24 vengono filtrati dalla maggior parte dei provider di transito.
Prefisso IPv6 Almeno un /48 (es. 2001:db8:abcd::/48)
Record ROA Creati nel portale del tuo RIR per entrambi i prefissi, che autorizzano il tuo ASN
Nodi VPS 2+ VPS Virtua in sedi diverse, ciascuno con sessione BGP verso il router upstream
Info BGP upstream IP e ASN del neighbor per ogni nodo, forniti da Virtua
Debian 12 Questa guida usa Debian Bookworm. Adatta i nomi dei pacchetti per altre distribuzioni.

In questa guida usiamo i seguenti valori di esempio. Sostituiscili con i tuoi:

Variabile Valore
Il tuo ASN 212345
IPv4 anycast 198.51.100.1
Prefisso anycast 198.51.100.0/24
IPv6 anycast 2001:db8:abcd::1
Prefisso IPv6 anycast 2001:db8:abcd::/48
Nodo A (Francoforte) IP principale 203.0.113.10
Nodo A neighbor upstream 169.254.169.1 AS 64496
Nodo B (Amsterdam) IP principale 203.0.113.20
Nodo B neighbor upstream 169.254.169.1 AS 64496
Zona DNS example.com

Com'è la topologia di rete?

                    ┌─────────────┐
                    │   Internet   │
                    └──────┬───────┘
                           │
              ┌────────────┴────────────┐
              │                         │
     ┌────────┴────────┐      ┌────────┴────────┐
     │  Router Virtua   │      │  Router Virtua   │
     │  Francoforte      │      │  Amsterdam        │
     │  AS 64496         │      │  AS 64496         │
     └────────┬────────┘      └────────┬────────┘
              │ eBGP                    │ eBGP
     ┌────────┴────────┐      ┌────────┴────────┐
     │  Nodo A (VPS)    │      │  Nodo 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 (tramite   │
              │  IP principali)         │
              └─────────────────────────┘

Entrambi i nodi annunciano 198.51.100.0/24 e 2001:db8:abcd::/48 via BGP. I client raggiungono il nodo topologicamente più vicino. I trasferimenti di zona avvengono sugli IP principali (unicast), non sull'indirizzo anycast.

Come creo l'interfaccia loopback anycast?

Ogni nodo ha bisogno dell'IP anycast assegnato a un'interfaccia locale. Un'interfaccia dummy è la scelta migliore perché è sempre attiva e non ha dipendenze fisiche. Configurala con systemd-networkd per renderla persistente ai riavvii.

Crea il file netdev:

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

Crea il file network per assegnare gli IP anycast. Usa la lunghezza completa del prefisso (/24 per IPv4, /48 per IPv6) affinché il protocollo direct in BIRD2 veda rotte connesse che corrispondono ai prefissi da annunciare:

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

Abilita e avvia systemd-networkd. Se il tuo VPS usa ifupdown per l'interfaccia principale (controlla /etc/network/interfaces), non usare systemctl restart systemd-networkd perché potrebbe interrompere la rete principale. L'avvio iniziale è sicuro perché systemd-networkd gestisce solo le interfacce che hanno file di configurazione in /etc/systemd/network/:

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

Output atteso:

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

Lo stato dell'interfaccia mostra UNKNOWN, che è normale per le interfacce dummy. Significa che il livello di collegamento è sempre attivo. Entrambi gli indirizzi IPv4 e IPv6 devono apparire.

Ripeti su ogni nodo anycast. La configurazione è identica.

Quale configurazione BIRD2 serve per l'annuncio di rotte anycast?

BIRD2 annuncia il prefisso anycast al router upstream via eBGP. Il design chiave: BIRD2 esporta solo le rotte apprese dall'interfaccia anycast0 tramite il protocollo direct. Quando l'interfaccia va giù (o lo script di health-check la disattiva), BIRD2 ritira automaticamente la rotta.

Installa BIRD2:

apt update && apt install -y bird2

Debian 12 include BIRD 2.0.12. Per funzionalità più recenti (miglioramenti BFD, filtri avanzati), aggiungi il repository ufficiale BIRD.

Configurazione del nodo 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;
    };
}

Punti chiave:

  • protocol direct anycast apprende rotte solo dall'interfaccia anycast0. Nessun'altra interfaccia finisce nella tabella di routing.
  • I filtri di esportazione limitano gli annunci esattamente al tuo /24 e /48. Questo previene leak di rotte accidentali.
  • password abilita l'autenticazione TCP MD5 (RFC 2385) sulla sessione BGP. Ottieni il segreto condiviso dal tuo provider upstream.
  • import none significa che questo nodo non accetta rotte dall'upstream. I nodi anycast annunciano soltanto; usano la rotta predefinita del VPS per il traffico in uscita.
  • graceful restart on riduce il route flap durante i riavvii di BIRD2.

Configurazione del nodo B

Copia lo stesso file sul nodo B. Modifica solo:

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

Avvio di BIRD2

Abilita e avvia BIRD2:

systemctl enable --now bird

enable lo rende persistente ai riavvii. --now lo avvia immediatamente.

Controlla lo stato:

systemctl status bird

Controlla lo stato delle sessioni BGP:

birdc show protocols

Output atteso:

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

Entrambe le sessioni BGP devono mostrare Established. Se vedi Active o Connect, controlla l'IP del neighbor, l'ASN e la password. Consulta i log con journalctl -u bird -f.

Controlla le rotte esportate:

birdc show route export upstream4

Atteso:

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

La rotta proviene dal protocollo direct anycast ed è esportata verso upstream4. Ripeti sul nodo B.

Come configuro BIND9 per ascoltare sull'IP anycast?

BIND9 funziona come server DNS esclusivamente autoritativo su ogni nodo, in ascolto sull'IP anycast. I client interrogano l'IP anycast; BGP assicura che raggiungano il nodo più vicino.

Installa BIND9:

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

Generare una chiave TSIG per i trasferimenti di zona

I trasferimenti di zona tra nodi devono essere autenticati. Genera una chiave TSIG con tsig-keygen:

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

L'output è così:

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

Copia questo file identico su tutti i nodi. Il nome della chiave e il segreto devono corrispondere ovunque.

Imposta permessi restrittivi:

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

Il file deve mostrare proprietà root:bind e permessi 640:

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

Configurazione del nodo A (primario)

Modifica /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;
};

Includi la chiave TSIG. Modifica /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;
};

Crea il file di zona /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.

Imposta la proprietà:

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

Configurazione del nodo B (secondario)

Sul nodo B, /etc/bind/named.conf.options è identico al nodo A.

/etc/bind/named.conf.local è diverso:

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 direttiva server indica a BIND9 di autenticare ogni comunicazione con il nodo A usando la chiave TSIG. I trasferimenti di zona sono automatici una volta configurato.

Avvio di BIND9

Abilita e avvia su entrambi i nodi:

systemctl enable --now named

Controlla lo stato:

systemctl status named

Testa la risoluzione DNS sull'IP anycast:

dig @198.51.100.1 example.com A +short

Atteso:

198.51.100.10

Sul nodo B, controlla che la zona sia stata trasferita:

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

Controlla i log BIND9 per il trasferimento:

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

Atteso:

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

Il numero di serie deve corrispondere su entrambi i nodi. Se il secondario mostra un numero diverso, il trasferimento è fallito. Controlla la coerenza della chiave TSIG e le regole del firewall.

Quali regole firewall servono per un nodo DNS anycast BGP?

Blinda ogni nodo con nftables. Permetti solo il necessario: SSH per la gestione, BGP dal router upstream e DNS da ovunque.

Crea /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;
    }
}

Adatta le righe ip saddr e ip6 saddr sul nodo B (scambia gli IP dei peer per il trasferimento di zona e usa il neighbor IPv6 upstream del nodo B). In produzione, limita SSH al tuo CIDR di gestione.

Applica e abilita:

systemctl enable --now nftables

Elenca le regole attive per confermare il caricamento:

nft list ruleset

Poi testa che il DNS funzioni ancora dall'esterno:

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

Se il DNS smette di funzionare dopo l'applicazione delle regole firewall, controlla che daddr corrisponda esattamente al tuo IP anycast e che l'interfaccia anycast0 sia attiva.

Come faccio a monitorare il DNS con health-check e ritirare le rotte BGP in caso di guasto?

Uno script di health-check monitora BIND9 e segnala a BIRD2 di ritirare la rotta anycast quando il DNS è giù. Il metodo: disattivare l'interfaccia anycast0, che fa sì che BIRD2 ritiri automaticamente il prefisso poiché il protocollo direct non vede più la rotta.

Perché non affidarsi solo ai timer BGP?

Con i timer BGP predefiniti (90 s hold, 30 s keepalive), il failover richiede fino a 90 secondi. Un health-check rileva il guasto DNS in pochi secondi e attiva il ritiro immediatamente. La tabella seguente mostra l'impatto:

Metodo Tempo di rilevamento Convergenza
Scadenza hold timer BGP 90 secondi 90-180 secondi
BFD (se disponibile) <1 secondo 1-3 secondi
Script di health-check 5-15 secondi 35-45 secondi (+ propagazione BGP)

BFD rileva solo guasti di collegamento/sessione, non guasti applicativi. Lo script di health-check cattura crash di BIND9, errori di configurazione e disco pieno che BFD non può vedere.

Creare un utente dedicato

L'health-check viene eseguito come utente dedicato con privilegi minimi:

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

Concedigli il permesso di controllare l'interfaccia anycast. Crea una regola 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

Scrivere lo script di health-check

Crea /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

Imposta i permessi:

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

Creare un servizio systemd

Crea /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 è richiesto qui perché lo script usa sudo per commutare l'interfaccia. La regola sudoers limita quali comandi l'utente può eseguire.

La sezione [Unit] usa Wants= invece di Requires=. Con Requires=, systemd fermerebbe l'health-check quando named si ferma. È esattamente il contrario di ciò che serve: l'health-check deve continuare a funzionare per rilevare il guasto DNS e ritirare la rotta.

Abilita e avvia:

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

Controlla che sia in esecuzione:

systemctl status anycast-healthcheck

I log devono mostrare il messaggio di avvio:

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

Come testo il failover del DNS anycast?

Esegui questi test da una macchina esterna, non da nessuno dei nodi.

Passo 1: confermare che entrambi i nodi annunciano

Dalla tua macchina locale:

dig @198.51.100.1 example.com A +short

Conferma di ricevere una risposta. Esegui un traceroute per vedere quale nodo raggiungi:

traceroute -n 198.51.100.1

Il percorso mostra quale sede è più vicina a te.

Passo 2: simulare un guasto DNS sul nodo A

Sul nodo A, ferma BIND9:

systemctl stop named

Osserva i log dell'health-check:

journalctl -u anycast-healthcheck -f

Sequenza attesa:

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

Dopo 15 secondi (3 controlli a intervalli di 5 secondi), l'health-check disattiva anycast0. BIRD2 rileva il cambio dell'interfaccia e ritira la rotta.

Controlla che la rotta sia sparita:

birdc show route export upstream4

La tabella deve essere vuota. Il router upstream rimuove 198.51.100.0/24 dal nodo A e inoltra tutto il traffico al nodo B.

Passo 3: verificare il failover del client

Dalla tua macchina esterna, interroga di nuovo:

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

La prima query dopo il ritiro potrebbe andare in timeout (la vecchia rotta è ancora in cache in alcuni router). Le query successive raggiungono il nodo B. La convergenza BGP richiede tipicamente 30-90 secondi a seconda della configurazione del tuo upstream.

Passo 4: ripristinare il nodo A

systemctl start named

L'health-check rileva il ritorno del DNS dopo 2 successi consecutivi (10 secondi) e riattiva anycast0. BIRD2 riannuncia la rotta.

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

Atteso:

ANNOUNCE: anycast0 up after 2 consecutive successes

Controlla le sessioni BGP:

birdc show protocols

Entrambe le sessioni BGP devono mostrare di nuovo Established.

Passo 5: misurare il tempo di convergenza

Per una misura precisa, avvia un ciclo dig continuo da una macchina esterna:

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

Ferma BIND9 sul nodo che stai attualmente raggiungendo. Conta i secondi tra l'ultima risposta riuscita e la prima risposta riuscita dall'altro nodo. Sull'infrastruttura Virtua con timer BGP predefiniti, aspettati 30-60 secondi di indisponibilità parziale.

Come si confronta BIRD2 con altri demoni di routing per l'anycast?

Caratteristica BIRD2 FRRouting ExaBGP
Linguaggio di configurazione Filtri dichiarativi CLI in stile Cisco JSON/API
Integrazione anycast Protocollo direct su interfaccia dummy Rotta statica + redistribuzione Script esterno annuncia/ritira
Metodo health-check Up/down dell'interfaccia attiva il cambio di rotta Comandi vtysh da script Gestore di processi integrato
Supporto IPv6 Configurazione unificata (canali ipv4/ipv6) Famiglie di indirizzi separate Manuale per famiglia
Complessità filtri Linguaggio di filtri completo con funzioni Route map, prefix list Solo logica esterna
Consumo memoria Basso (~10 MB per tabella piccola) Medio (~30 MB) Molto basso (~5 MB)
Maturità in produzione IXP, DNS su larga scala (Cloudflare, RIPE) ISP, data center Piccoli deployment, monitoraggio

Il vantaggio di BIRD2 per l'anycast: il protocol direct su un'interfaccia dummy offre il ritiro automatico della rotta quando l'interfaccia va giù. Nessun scripting esterno necessario per la parte BGP. L'health-check deve solo commutare l'interfaccia.

Come sincronizzo le zone DNS tra nodi anycast?

La sincronizzazione delle zone usa la replica primario/secondario integrata in BIND9 via AXFR, autenticata con TSIG. La configurazione è stata fatta nella sezione BIND9 sopra. Ecco i dettagli operativi.

Gli aggiornamenti di zona fluiscono in una direzione: modifica il file di zona sul nodo A (primario), incrementa il numero di serie e ricarica:

# 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

Esegui sempre named-checkzone prima di ricaricare. Cattura gli errori di sintassi.

rndc reload example.com

Il nodo B riceve un NOTIFY dal nodo A e scarica la zona aggiornata via AXFR. Sul nodo B, controlla il numero di serie:

dig @127.0.0.1 example.com SOA +short

Il numero di serie deve corrispondere. In caso contrario, controlla:

  1. Il firewall permette TCP 53 dal nodo A al nodo B
  2. La chiave TSIG è identica su entrambi i nodi
  3. journalctl -u named sul nodo B per errori di trasferimento

Aggiungere un terzo nodo: configuralo come un altro secondario. Aggiungi il suo IP alla lista also-notify e alle regole nftables del nodo A. Nessuna modifica necessaria sui secondari esistenti.

E per quanto riguarda il BGP anycast IPv6?

La configurazione BIRD2 sopra include già IPv6. Il blocco protocol direct anycast ha entrambi i canali ipv4 e ipv6. La sessione BGP upstream6 esporta il prefisso /48.

Controlla l'esportazione delle rotte 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

Testa il DNS su IPv6:

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

Risoluzione dei problemi

Sessione BGP bloccata in Active/Connect:

journalctl -u bird -f

Cause comuni: IP del neighbor sbagliato, ASN sbagliato, password MD5 non corrispondente, firewall che blocca TCP 179. Esegui birdc show protocols all upstream4 per messaggi di errore dettagliati.

BIND9 non ascolta sull'IP anycast:

ss -tlnp | grep ':53'

Se vedi solo 127.0.0.1:53, controlla che listen-on includa l'IP anycast e che l'interfaccia anycast0 fosse attiva prima dell'avvio di BIND9. Riavvia BIND9 dopo l'attivazione dell'interfaccia: systemctl restart named.

Trasferimento di zona fallito:

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

Controlla che il file della chiave sia leggibile dall'utente bind: ls -la /etc/bind/anycast-transfer.key. Esegui un test di trasferimento manuale: dig @203.0.113.10 example.com AXFR -k /etc/bind/anycast-transfer.key.

L'health-check non ritira le rotte:

journalctl -u anycast-healthcheck -f

Controlla i permessi sudo: sudo -u anycast-healthcheck sudo -l. L'utente deve poter eseguire /usr/sbin/ip link set anycast0 up e down senza password.

Query in timeout dopo il failover:

I client DNS mettono in cache le risposte. Il TTL nel file di zona (300 secondi / 5 minuti) determina quanto tempo i client usano dati obsoleti. La convergenza BGP aggiunge 30-90 secondi. Tempo totale di failover dal punto di vista del client: fino al TTL più il tempo di convergenza. Riduci il TTL minimo del SOA se serve un failover più rapido, ma non scendere sotto i 60 secondi per le zone autoritative.

Checklist di produzione

Prima di andare in produzione:

  • Record ROA creati e validati (rpki-client o portale del tuo RIR)
  • Sessioni BGP stabilite su tutti i nodi (birdc show protocols)
  • Prefisso anycast visibile nelle tabelle di routing globali (usa un looking glass)
  • BIND9 risponde sull'IP anycast da reti esterne
  • Numero di serie della zona corrisponde su tutti i nodi
  • Permessi della chiave TSIG impostati a 640, proprietario root:bind
  • Regole nftables attive su tutti i nodi
  • Servizio health-check abilitato e in esecuzione
  • Failover testato: BIND9 fermato, ritiro rotta confermato, ripristino confermato
  • Monitoraggio configurato: allarmi su sessione BGP down, processo BIND9, guasti health-check
  • journalctl -u bird e journalctl -u named non mostrano errori

Per la guida principale sui fondamenti BGP e sull'uso del proprio spazio IP, consulta BGP e Bring Your Own IP su un VPS: la guida completa. Per il filtraggio delle rotte BGP e l'hardening di sicurezza, consulta BGP Route Filtering: Prefix List, Filtri AS-Path, Bogon Rejection e GTSM.


Copyright 2026 Virtua.Cloud. Tutti i diritti riservati. Questo contenuto è un'opera originale del team Virtua.Cloud. La riproduzione, ripubblicazione o redistribuzione senza autorizzazione scritta è vietata.

Pronto a provare?

Esegui sessioni BGP sul tuo spazio IP con VPS Virtua.Cloud.

Vedi piani VPS