Anycast-DNS met BIRD2 en BGP: multi-locatie setup

12 min leestijd·Matthieu·high-availabilitynftablesdnsanycastbind9bird2bgp|

Implementeer anycast-DNS op meerdere VPS-locaties met BIRD2 en BIND9. BGP-routeaankondiging, zonesynchronisatie met TSIG, health-check failover en nftables-beveiliging.

Anycast-DNS wijst hetzelfde IP-adres toe aan DNS-servers op meerdere locaties. Elke server kondigt het IP aan via BGP. Routers sturen queries naar de dichtstbijzijnde server op basis van netwerktopologie. Als een server uitvalt en zijn BGP-route intrekt, bereiken queries automatisch de volgende dichtstbijzijnde server. Dit geeft je DNS-resolutie met lage latentie en automatische failover zonder wijzigingen aan de clientzijde.

Deze handleiding implementeert een productie anycast-DNS setup op twee Virtua VPS-nodes met BIRD2 als routing-daemon en BIND9 als autoritatieve DNS-server. Beveiliging is in elke stap ingebouwd, niet achteraf toegevoegd.

Wat je gaat bouwen:

  • Twee VPS-nodes op verschillende locaties, die elk hetzelfde /24-prefix via BGP aankondigen
  • BIND9 als autoritatieve DNS-server die luistert op het anycast-IP
  • Zonesynchronisatie tussen nodes via TSIG-geauthenticeerde transfers
  • Een health-check script dat BGP-routes intrekt wanneer DNS faalt
  • nftables-firewallregels die elke node beveiligen

Wat zijn de vereisten voor anycast-DNS?

Voordat je begint, moeten de volgende resources al ingericht zijn. Als je nog nooit BGP op een VPS hebt geconfigureerd, lees dan eerst BIRD2 BGP-configuratie op een Linux VPS. Voor het aanmaken van ROA-records, zie RPKI ROA voor BGP: ROA's aanmaken, routes valideren in BIRD2 en FRR.

Vereiste Details
ASN Je eigen ASN (bijv. AS212345), geregistreerd bij een RIR
IPv4-prefix Minimaal een /24 (bijv. 198.51.100.0/24). Prefixen langer dan /24 worden door de meeste transit-providers gefilterd.
IPv6-prefix Minimaal een /48 (bijv. 2001:db8:abcd::/48)
ROA-records Aangemaakt in je RIR-portal voor beide prefixen, die je ASN autoriseren
VPS-nodes 2+ Virtua VPS op verschillende locaties, elk met een BGP-sessie naar de upstream-router
Upstream BGP-info Neighbor-IP en ASN per node, door Virtua verstrekt
Debian 12 Deze handleiding gebruikt Debian Bookworm. Pas pakketnamen aan voor andere distributies.

In deze handleiding gebruiken we de volgende voorbeeldwaarden. Vervang ze door je eigen waarden:

Variabele Waarde
Je ASN 212345
Anycast IPv4 198.51.100.1
Anycast-prefix 198.51.100.0/24
Anycast IPv6 2001:db8:abcd::1
Anycast IPv6-prefix 2001:db8:abcd::/48
Node A (Frankfurt) primair IP 203.0.113.10
Node A upstream neighbor 169.254.169.1 AS 64496
Node B (Amsterdam) primair IP 203.0.113.20
Node B upstream neighbor 169.254.169.1 AS 64496
DNS-zone example.com

Hoe ziet de netwerktopologie eruit?

                    ┌─────────────┐
                    │   Internet   │
                    └──────┬───────┘
                           │
              ┌────────────┴────────────┐
              │                         │
     ┌────────┴────────┐      ┌────────┴────────┐
     │  Virtua Router   │      │  Virtua Router   │
     │  Frankfurt        │      │  Amsterdam        │
     │  AS 64496         │      │  AS 64496         │
     └────────┬────────┘      └────────┬────────┘
              │ eBGP                    │ eBGP
     ┌────────┴────────┐      ┌────────┴────────┐
     │  Node A (VPS)    │      │  Node 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       │
              │  primaire IPs)          │
              └─────────────────────────┘

Beide nodes kondigen 198.51.100.0/24 en 2001:db8:abcd::/48 aan via BGP. Clients bereiken de topologisch dichtstbijzijnde node. Zonetransfers verlopen via de primaire (unicast) IPs, niet via het anycast-adres.

Hoe maak ik de anycast loopback-interface aan?

Elke node heeft het anycast-IP nodig op een lokale interface. Een dummy-interface is hiervoor het meest geschikt omdat deze altijd actief is en geen fysieke afhankelijkheid heeft. Configureer deze met systemd-networkd zodat het herstart overleeft.

Maak het netdev-bestand aan:

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

Maak het network-bestand aan om de anycast-IPs toe te wijzen. Gebruik de volledige prefixlengte (/24 voor IPv4, /48 voor IPv6) zodat het direct-protocol in BIRD2 verbonden routes ziet die overeenkomen met de prefixen die je wilt aankondigen:

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

Activeer en start systemd-networkd. Als je VPS ifupdown gebruikt voor de primaire interface (controleer /etc/network/interfaces), gebruik dan niet systemctl restart systemd-networkd omdat dit het primaire netwerk kan verstoren. De eerste start is veilig omdat systemd-networkd alleen interfaces beheert die configuratiebestanden hebben in /etc/systemd/network/:

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

Verwachte output:

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

De interfacestatus toont UNKNOWN, wat normaal is voor dummy-interfaces. Het betekent dat de linklaag altijd actief is. Zowel het IPv4- als IPv6-adres moeten zichtbaar zijn.

Herhaal dit op elke anycast-node. De configuratie is identiek.

Welke BIRD2-configuratie is nodig voor anycast-routeaankondiging?

BIRD2 kondigt het anycast-prefix aan bij de upstream-router via eBGP. Het kernontwerp: BIRD2 exporteert alleen routes die via het direct-protocol van de anycast0-interface zijn geleerd. Wanneer de interface uitvalt (of het health-check script deze uitschakelt), trekt BIRD2 de route automatisch in.

Installeer BIRD2:

apt update && apt install -y bird2

Debian 12 levert BIRD 2.0.12 mee. Voor nieuwere functies (BFD-verbeteringen, filterverbeteringen) voeg je de officiële BIRD-repository toe.

Configuratie van Node 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;
    };
}

Belangrijke punten:

  • protocol direct anycast leert routes alleen van de anycast0-interface. Geen andere interfaces lekken naar de routeringstabel.
  • Exportfilters beperken aankondigingen tot precies je /24 en /48. Dit voorkomt onbedoelde route-leks.
  • password schakelt TCP MD5-authenticatie (RFC 2385) in op de BGP-sessie. Vraag het gedeelde geheim op bij je upstream-provider.
  • import none betekent dat deze node geen routes van de upstream accepteert. Anycast-nodes kondigen alleen aan; ze gebruiken de standaardroute van de VPS voor uitgaand verkeer.
  • graceful restart on vermindert route-flapping bij BIRD2-herstarts.

Configuratie van Node B

Kopieer hetzelfde bestand naar Node B. Wijzig alleen:

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

BIRD2 starten

Activeer en start BIRD2:

systemctl enable --now bird

enable zorgt voor persistentie na herstarts. --now start het direct.

Status controleren:

systemctl status bird

BGP-sessiestatus controleren:

birdc show protocols

Verwachte output:

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

Beide BGP-sessies moeten Established tonen. Als je in plaats daarvan Active of Connect ziet, controleer dan het neighbor-IP, ASN en wachtwoord. Bekijk logs met journalctl -u bird -f.

Geëxporteerde routes controleren:

birdc show route export upstream4

Verwacht:

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

De route komt van het anycast direct-protocol en wordt geëxporteerd naar upstream4. Herhaal op Node B.

Hoe configureer ik BIND9 om te luisteren op het anycast-IP?

BIND9 draait als puur autoritatieve DNS-server op elke node en luistert op het anycast-IP. Clients bevragen het anycast-IP; BGP zorgt ervoor dat ze de dichtstbijzijnde node bereiken.

BIND9 installeren:

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

TSIG-sleutel genereren voor zonetransfers

Zonetransfers tussen nodes moeten geauthenticeerd zijn. Genereer een TSIG-sleutel met tsig-keygen:

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

De output ziet er zo uit:

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

Kopieer dit exacte bestand naar alle nodes. De sleutelnaam en het geheim moeten overal overeenkomen.

Stel restrictieve rechten in:

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

Het bestand moet root:bind eigenaarschap en 640 rechten tonen:

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

Configuratie van Node A (primair)

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

Voeg de TSIG-sleutel toe. Bewerk /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;
};

Maak het zonebestand /var/lib/bind/db.example.com aan:

$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.

Eigenaarschap instellen:

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

Configuratie van Node B (secundair)

Op Node B is /etc/bind/named.conf.options identiek aan Node A.

/etc/bind/named.conf.local verschilt:

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"; };
};

De server-directive vertelt BIND9 om alle communicatie met Node A te authenticeren met de TSIG-sleutel. Zonetransfers zijn automatisch zodra dit is geconfigureerd.

BIND9 starten

Op beide nodes activeren en starten:

systemctl enable --now named

Status controleren:

systemctl status named

DNS-resolutie testen tegen het anycast-IP:

dig @198.51.100.1 example.com A +short

Verwacht:

198.51.100.10

Op Node B controleren of de zone is overgedragen:

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

BIND9-logs controleren op de transfer:

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

Verwacht:

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

Het serienummer moet op beide nodes overeenkomen. Als de secundaire een ander serienummer toont, is de transfer mislukt. Controleer de consistentie van de TSIG-sleutel en firewallregels.

Welke firewallregels heeft een BGP anycast-DNS-node nodig?

Beveilig elke node met nftables. Sta alleen het noodzakelijke toe: SSH voor beheer, BGP van de upstream-router en DNS van overal.

Maak /etc/nftables.conf aan:

#!/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;
    }
}

Pas de ip saddr- en ip6 saddr-regels aan op Node B (wissel de zonetransfer-peer-IPs en gebruik de IPv6 upstream-neighbor van Node B). In productie beperk je SSH tot je beheer-CIDR.

Toepassen en activeren:

systemctl enable --now nftables

Actieve regels weergeven om te bevestigen dat ze geladen zijn:

nft list ruleset

Test vervolgens of DNS nog werkt van buitenaf:

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

Als DNS stopt met werken na het toepassen van firewallregels, controleer dan of daddr exact overeenkomt met je anycast-IP en of de anycast0-interface actief is.

Hoe controleer ik DNS via health-check en trek ik BGP-routes in bij een storing?

Een health-check script bewaakt BIND9 en signaleert BIRD2 om de anycast-route in te trekken wanneer DNS uitvalt. De methode: de anycast0-interface uitschakelen, waardoor BIRD2 het prefix automatisch intrekt omdat het direct-protocol de route niet meer ziet.

Waarom niet gewoon op BGP-timers vertrouwen?

Met standaard BGP-timers (90 s hold, 30 s keepalive) duurt failover tot 90 seconden. Een health-check detecteert DNS-uitval binnen seconden en activeert het intrekken onmiddellijk. De onderstaande tabel toont het verschil:

Methode Detectietijd Convergentie
BGP hold timer verlopen 90 seconden 90-180 seconden
BFD (indien beschikbaar) <1 seconde 1-3 seconden
Health-check script 5-15 seconden 35-45 seconden (+ BGP-propagatie)

BFD detecteert alleen link-/sessiestoringen, geen applicatiestoringen. Het health-check script vangt BIND9-crashes, misconfiguraties en volle schijven op die BFD niet kan zien.

Dedicated gebruiker aanmaken

De health-check draait als dedicated gebruiker met minimale rechten:

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

Geef deze gebruiker toestemming om de anycast-interface te bedienen. Maak een sudoers-regel aan:

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

Health-check script schrijven

Maak /usr/local/bin/anycast-healthcheck.sh aan:

#!/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

Rechten instellen:

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

Systemd-service aanmaken

Maak /etc/systemd/system/anycast-healthcheck.service aan:

[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 is hier vereist omdat het script sudo gebruikt om de interface te schakelen. De sudoers-regel beperkt welke commando's de gebruiker mag uitvoeren.

De [Unit]-sectie gebruikt Wants= in plaats van Requires=. Met Requires= zou systemd de health-check stoppen wanneer named stopt. Dat is precies het tegenovergestelde van wat je wilt: de health-check moet blijven draaien om de DNS-storing te detecteren en de route in te trekken.

Activeren en starten:

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

Controleren of het draait:

systemctl status anycast-healthcheck

De logs moeten het opstartbericht tonen:

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

Hoe test ik anycast-DNS failover?

Voer deze tests uit vanaf een externe machine, niet vanaf een van de nodes.

Stap 1: bevestig dat beide nodes aankondigen

Vanaf je lokale machine:

dig @198.51.100.1 example.com A +short

Bevestig dat je een antwoord krijgt. Voer een traceroute uit om te zien welke node je bereikt:

traceroute -n 198.51.100.1

Het pad toont welke locatie het dichtst bij je is.

Stap 2: simuleer een DNS-storing op Node A

Op Node A, stop BIND9:

systemctl stop named

Bekijk de health-check logs:

journalctl -u anycast-healthcheck -f

Verwachte reeks:

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

Na 15 seconden (3 controles met 5-seconden intervallen) schakelt de health-check anycast0 uit. BIRD2 detecteert de interfacewijziging en trekt de route in.

Controleer of de route verdwenen is:

birdc show route export upstream4

De tabel moet leeg zijn. De upstream-router verwijdert 198.51.100.0/24 van Node A en stuurt al het verkeer naar Node B.

Stap 3: client-failover controleren

Vanaf je externe machine, bevraag opnieuw:

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

De eerste query na het intrekken kan een timeout geven (de oude route is nog gecacht in sommige routers). Vervolgqueries bereiken Node B. BGP-convergentie duurt doorgaans 30-90 seconden afhankelijk van de upstream-configuratie.

Stap 4: Node A herstellen

systemctl start named

De health-check detecteert dat DNS terug is na 2 opeenvolgende successen (10 seconden) en activeert anycast0. BIRD2 kondigt de route opnieuw aan.

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

Verwacht:

ANNOUNCE: anycast0 up after 2 consecutive successes

BGP-sessies controleren:

birdc show protocols

Beide BGP-sessies moeten weer Established tonen.

Stap 5: convergentietijd meten

Voor een nauwkeurige meting, voer een continue dig-loop uit vanaf een externe machine:

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

Stop BIND9 op de node die je momenteel bereikt. Tel de seconden tussen het laatste succesvolle antwoord en het eerste succesvolle antwoord van de andere node. Op Virtua-infrastructuur met standaard BGP-timers kun je 30-60 seconden gedeeltelijke onbereikbaarheid verwachten.

Hoe verhoudt BIRD2 zich tot andere routing-daemons voor anycast?

Eigenschap BIRD2 FRRouting ExaBGP
Configuratietaal Declaratieve filters Cisco-achtige CLI JSON/API
Anycast-integratie Direct-protocol op dummy-interface Statische route + redistributie Extern script kondigt aan/trekt in
Health-check methode Interface up/down triggert routewijziging vtysh-commando's vanuit script Ingebouwde procesmanager
IPv6-ondersteuning Uniforme configuratie (ipv4/ipv6-kanalen) Aparte adresfamilies Handmatig per familie
Filtercomplexiteit Volledige filtertaal met functies Route maps, prefix lists Alleen externe logica
Geheugengebruik Laag (~10 MB voor kleine tabel) Gemiddeld (~30 MB) Zeer laag (~5 MB)
Productievolwassenheid IXPs, grootschalige DNS (Cloudflare, RIPE) ISPs, datacenters Kleine deployments, monitoring

Het voordeel van BIRD2 voor anycast: het protocol direct op een dummy-interface biedt automatisch intrekken van routes wanneer de interface uitvalt. Geen extern scripting nodig voor het BGP-deel. De health-check hoeft alleen de interface te schakelen.

Hoe synchroniseer ik DNS-zones tussen anycast-nodes?

Zonesynchronisatie gebruikt de ingebouwde primair/secundair replicatie van BIND9 via AXFR, geauthenticeerd met TSIG. De configuratie is hierboven in de BIND9-sectie gedaan. Hier de operationele details.

Zone-updates stromen in één richting: bewerk het zonebestand op Node A (primair), verhoog het serienummer en herlaad:

# 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

Voer altijd named-checkzone uit voor het herladen. Het vangt syntaxfouten op.

rndc reload example.com

Node B ontvangt een NOTIFY van Node A en haalt de bijgewerkte zone op via AXFR. Op Node B het serienummer controleren:

dig @127.0.0.1 example.com SOA +short

Het serienummer moet overeenkomen. Als dat niet het geval is, controleer dan:

  1. De firewall staat TCP 53 toe van Node A naar Node B
  2. De TSIG-sleutel is identiek op beide nodes
  3. journalctl -u named op Node B voor transferfouten

Een derde node toevoegen: configureer deze als extra secundaire node. Voeg het IP toe aan de also-notify-lijst en de nftables-regels op Node A. Geen wijzigingen nodig op bestaande secundaire nodes.

Hoe zit het met IPv6 BGP anycast?

De BIRD2-configuratie hierboven bevat al IPv6. Het protocol direct anycast-blok heeft zowel ipv4- als ipv6-kanalen. De upstream6 BGP-sessie exporteert het /48-prefix.

IPv6-route-export controleren:

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

DNS via IPv6 testen:

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

Probleemoplossing

BGP-sessie vastgelopen in Active/Connect:

journalctl -u bird -f

Veelvoorkomende oorzaken: verkeerd neighbor-IP, verkeerd ASN, MD5-wachtwoord komt niet overeen, firewall blokkeert TCP 179. Voer birdc show protocols all upstream4 uit voor gedetailleerde foutmeldingen.

BIND9 luistert niet op het anycast-IP:

ss -tlnp | grep ':53'

Als je alleen 127.0.0.1:53 ziet, controleer dan of listen-on het anycast-IP bevat en of de anycast0-interface actief was voordat BIND9 startte. Herstart BIND9 nadat de interface actief is: systemctl restart named.

Zonetransfer mislukt:

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

Controleer of het sleutelbestand leesbaar is door de bind-gebruiker: ls -la /etc/bind/anycast-transfer.key. Voer een handmatige transfertest uit: dig @203.0.113.10 example.com AXFR -k /etc/bind/anycast-transfer.key.

Health-check trekt routes niet in:

journalctl -u anycast-healthcheck -f

Controleer sudo-rechten: sudo -u anycast-healthcheck sudo -l. De gebruiker moet /usr/sbin/ip link set anycast0 up en down zonder wachtwoord kunnen uitvoeren.

Queries geven timeout na failover:

DNS-clients cachen antwoorden. De TTL in het zonebestand (300 seconden / 5 minuten) bepaalt hoe lang clients verouderde data gebruiken. BGP-convergentie voegt 30-90 seconden toe. Totale failovertijd vanuit clientperspectief: tot de TTL plus convergentietijd. Verlaag de SOA minimum TTL als snellere failover belangrijk is, maar ga niet onder de 60 seconden voor autoritatieve zones.

Productiechecklist

Voor je live gaat:

  • ROA-records aangemaakt en gevalideerd (rpki-client of je RIR-portal)
  • BGP-sessies op alle nodes tot stand gebracht (birdc show protocols)
  • Anycast-prefix zichtbaar in globale routeringstabellen (gebruik een looking glass)
  • BIND9 reageert op het anycast-IP vanaf externe netwerken
  • Zoneserienummer komt overeen op alle nodes
  • TSIG-sleutelrechten ingesteld op 640, eigenaar root:bind
  • nftables-regels actief op alle nodes
  • Health-check service geactiveerd en draaiend
  • Failover getest: BIND9 gestopt, route-intrekking bevestigd, herstel bevestigd
  • Monitoring geconfigureerd: alerts bij BGP-sessie down, BIND9-proces, health-check fouten
  • journalctl -u bird en journalctl -u named tonen geen fouten

Voor de bovenliggende handleiding over BGP-basisprincipes en het meenemen van je eigen IP-ruimte, zie BGP en Bring Your Own IP op een VPS: de complete gids. Voor BGP-routefiltering en beveiligingshardening, zie BGP Route Filtering: Prefix Lists, AS-Path Filters, Bogon Rejection en GTSM.


Copyright 2026 Virtua.Cloud. Alle rechten voorbehouden. Deze inhoud is een origineel werk van het Virtua.Cloud-team. Reproductie, herpublicatie of herdistributie zonder schriftelijke toestemming is verboden.

Klaar om het zelf te proberen?

Draai BGP-sessies op uw eigen IP-ruimte met Virtua.Cloud VPS.

Bekijk VPS-aanbod