Anycast-DNS mit BIRD2 und BGP: Multi-Standort-Setup

12 Min. Lesezeit·Matthieu·high-availabilitynftablesdnsanycastbind9bird2bgp|

Anycast-DNS über mehrere VPS-Standorte mit BIRD2 und BIND9 bereitstellen. BGP-Routenankündigung, Zonensynchronisation mit TSIG, Health-Check-Failover und nftables-Absicherung.

Anycast-DNS weist mehreren DNS-Servern an verschiedenen Standorten dieselbe IP-Adresse zu. Jeder Server kündigt die IP per BGP an. Router leiten Anfragen basierend auf der Netzwerktopologie zum nächstgelegenen Server. Fällt ein Server aus und zieht seine BGP-Route zurück, erreichen Anfragen automatisch den nächsten erreichbaren Server. Das ergibt DNS-Auflösung mit niedriger Latenz und automatisches Failover ohne clientseitige Änderungen.

Diese Anleitung richtet ein produktionsreifes Anycast-DNS auf zwei Virtua-VPS-Knoten ein, mit BIRD2 als Routing-Daemon und BIND9 als autoritativem DNS-Server. Sicherheitsmaßnahmen sind in jeden Schritt eingebaut, nicht nachträglich hinzugefügt.

Was Sie aufbauen werden:

  • Zwei VPS-Knoten an verschiedenen Standorten, die jeweils dasselbe /24-Präfix per BGP ankündigen
  • BIND9 als autoritativer DNS-Server, der auf der Anycast-IP lauscht
  • Zonensynchronisation zwischen Knoten über TSIG-authentifizierte Transfers
  • Ein Health-Check-Skript, das BGP-Routen zurückzieht, wenn DNS ausfällt
  • nftables-Firewallregeln zur Absicherung jedes Knotens

Was sind die Voraussetzungen für Anycast-DNS?

Bevor Sie beginnen, müssen folgende Ressourcen bereitgestellt sein. Falls Sie noch nie BGP auf einem VPS konfiguriert haben, lesen Sie zuerst BIRD2 BGP-Konfiguration auf einem Linux VPS. Für die Erstellung von ROA-Einträgen siehe RPKI ROA für BGP: ROAs erstellen, Routen in BIRD2 und FRR validieren.

Voraussetzung Details
ASN Ihre eigene ASN (z.B. AS212345), bei einem RIR registriert
IPv4-Präfix Mindestens ein /24 (z.B. 198.51.100.0/24). Längere Präfixe als /24 werden von den meisten Transit-Anbietern gefiltert.
IPv6-Präfix Mindestens ein /48 (z.B. 2001:db8:abcd::/48)
ROA-Einträge Im RIR-Portal für beide Präfixe erstellt, Ihre ASN autorisierend
VPS-Knoten 2+ Virtua-VPS an verschiedenen Standorten, jeweils mit BGP-Session zum Upstream-Router
Upstream-BGP-Infos Neighbor-IP und ASN pro Knoten, von Virtua bereitgestellt
Debian 12 Diese Anleitung verwendet Debian Bookworm. Passen Sie Paketnamen für andere Distributionen an.

In dieser Anleitung verwenden wir folgende Beispielwerte. Ersetzen Sie diese durch Ihre eigenen:

Variable Wert
Ihre ASN 212345
Anycast-IPv4 198.51.100.1
Anycast-Präfix 198.51.100.0/24
Anycast-IPv6 2001:db8:abcd::1
Anycast-IPv6-Präfix 2001:db8:abcd::/48
Knoten A (Frankfurt) primäre IP 203.0.113.10
Knoten A Upstream-Neighbor 169.254.169.1 AS 64496
Knoten B (Amsterdam) primäre IP 203.0.113.20
Knoten B Upstream-Neighbor 169.254.169.1 AS 64496
DNS-Zone example.com

Wie sieht die Netzwerktopologie aus?

                    ┌─────────────┐
                    │   Internet   │
                    └──────┬───────┘
                           │
              ┌────────────┴────────────┐
              │                         │
     ┌────────┴────────┐      ┌────────┴────────┐
     │  Virtua-Router   │      │  Virtua-Router   │
     │  Frankfurt        │      │  Amsterdam        │
     │  AS 64496         │      │  AS 64496         │
     └────────┬────────┘      └────────┬────────┘
              │ eBGP                    │ eBGP
     ┌────────┴────────┐      ┌────────┴────────┐
     │  Knoten A (VPS)  │      │  Knoten 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 (über      │
              │  primäre IPs)           │
              └─────────────────────────┘

Beide Knoten kündigen 198.51.100.0/24 und 2001:db8:abcd::/48 per BGP an. Clients erreichen den topologisch nächsten Knoten. Zonentransfers laufen über die primären (Unicast-)IPs, nicht über die Anycast-Adresse.

Wie erstelle ich das Anycast-Loopback-Interface?

Jeder Knoten benötigt die Anycast-IP auf einem lokalen Interface. Ein Dummy-Interface eignet sich am besten, da es immer aktiv ist und keine physische Abhängigkeit hat. Konfigurieren Sie es mit systemd-networkd, damit es Neustarts übersteht.

Erstellen Sie die Netdev-Datei:

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

Erstellen Sie die Network-Datei, um die Anycast-IPs zuzuweisen. Verwenden Sie die vollständige Präfixlänge (/24 für IPv4, /48 für IPv6), damit das direct-Protokoll in BIRD2 verbundene Routen sieht, die zu den anzukündigenden Präfixen passen:

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

Aktivieren und starten Sie systemd-networkd. Falls Ihr VPS ifupdown für das primäre Interface verwendet (prüfen Sie /etc/network/interfaces), verwenden Sie nicht systemctl restart systemd-networkd, da dies das primäre Netzwerk stören kann. Der Erststart ist sicher, da systemd-networkd nur Interfaces verwaltet, die Konfigurationsdateien in /etc/systemd/network/ haben:

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

Erwartete Ausgabe:

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

Der Interface-Status zeigt UNKNOWN, was für Dummy-Interfaces normal ist. Es bedeutet, dass die Verbindungsschicht immer aktiv ist. Beide IPv4- und IPv6-Adressen sollten erscheinen.

Wiederholen Sie dies auf jedem Anycast-Knoten. Die Konfiguration ist identisch.

Welche BIRD2-Konfiguration wird für die Anycast-Routenankündigung benötigt?

BIRD2 kündigt das Anycast-Präfix beim Upstream-Router per eBGP an. Das Kernprinzip: BIRD2 exportiert nur Routen, die über das direct-Protokoll vom anycast0-Interface gelernt wurden. Wenn das Interface ausfällt (oder das Health-Check-Skript es deaktiviert), zieht BIRD2 die Route automatisch zurück.

Installieren Sie BIRD2:

apt update && apt install -y bird2

Debian 12 liefert BIRD 2.0.12 aus. Für neuere Funktionen (BFD-Verbesserungen, erweiterte Filter) fügen Sie das offizielle BIRD-Repository hinzu.

Konfiguration von Knoten 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;
    };
}

Wichtige Punkte:

  • protocol direct anycast lernt Routen nur vom anycast0-Interface. Keine anderen Interfaces gelangen in die Routing-Tabelle.
  • Export-Filter beschränken Ankündigungen auf genau Ihr /24 und /48. Das verhindert versehentliche Route-Leaks.
  • password aktiviert TCP-MD5-Authentifizierung (RFC 2385) auf der BGP-Session. Das Shared Secret erhalten Sie von Ihrem Upstream-Anbieter.
  • import none bedeutet, dass dieser Knoten keine Routen vom Upstream akzeptiert. Anycast-Knoten kündigen nur an; sie verwenden die Standard-Route des VPS für ausgehenden Verkehr.
  • graceful restart on reduziert Route-Flapping bei BIRD2-Neustarts.

Konfiguration von Knoten B

Kopieren Sie dieselbe Datei auf Knoten B. Ändern Sie nur:

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

Aktivieren und starten Sie BIRD2:

systemctl enable --now bird

enable sorgt für Persistenz nach Neustarts. --now startet den Dienst sofort.

Status prüfen:

systemctl status bird

BGP-Session-Status prüfen:

birdc show protocols

Erwartete Ausgabe:

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-Sessions sollten Established anzeigen. Sehen Sie stattdessen Active oder Connect, prüfen Sie Neighbor-IP, ASN und Passwort. Logs einsehen mit journalctl -u bird -f.

Exportierte Routen prüfen:

birdc show route export upstream4

Erwartet:

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

Die Route stammt vom anycast-Direct-Protokoll und wird an upstream4 exportiert. Auf Knoten B wiederholen.

Wie konfiguriere ich BIND9 zum Lauschen auf der Anycast-IP?

BIND9 läuft als rein autoritativer DNS-Server auf jedem Knoten und lauscht auf der Anycast-IP. Clients befragen die Anycast-IP; BGP stellt sicher, dass sie den nächsten Knoten erreichen.

BIND9 installieren:

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

TSIG-Schlüssel für Zonentransfers generieren

Zonentransfers zwischen Knoten müssen authentifiziert sein. Generieren Sie einen TSIG-Schlüssel mit tsig-keygen:

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

Die Ausgabe sieht so aus:

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

Kopieren Sie diese Datei exakt auf alle Knoten. Schlüsselname und Secret müssen überall übereinstimmen.

Restriktive Berechtigungen setzen:

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

Die Datei sollte root:bind als Eigentümer und 640 als Berechtigungen zeigen:

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

Konfiguration von Knoten A (primär)

Bearbeiten Sie /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;
};

TSIG-Schlüssel einbinden. Bearbeiten Sie /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;
};

Erstellen Sie die Zonendatei /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.

Eigentümer setzen:

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

Konfiguration von Knoten B (sekundär)

Auf Knoten B ist /etc/bind/named.conf.options identisch mit Knoten A.

/etc/bind/named.conf.local unterscheidet sich:

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

Die server-Anweisung weist BIND9 an, jede Kommunikation mit Knoten A per TSIG-Schlüssel zu authentifizieren. Zonentransfers sind automatisch, sobald dies konfiguriert ist.

BIND9 starten

Auf beiden Knoten aktivieren und starten:

systemctl enable --now named

Status prüfen:

systemctl status named

DNS-Auflösung gegen die Anycast-IP testen:

dig @198.51.100.1 example.com A +short

Erwartet:

198.51.100.10

Auf Knoten B prüfen, ob die Zone übertragen wurde:

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

BIND9-Logs auf den Transfer prüfen:

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

Erwartet:

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

Die Seriennummer muss auf beiden Knoten übereinstimmen. Zeigt der sekundäre Knoten eine andere Seriennummer, ist der Transfer fehlgeschlagen. Prüfen Sie TSIG-Schlüssel-Konsistenz und Firewallregeln.

Welche Firewallregeln braucht ein BGP-Anycast-DNS-Knoten?

Sichern Sie jeden Knoten mit nftables ab. Erlauben Sie nur das Nötige: SSH zur Verwaltung, BGP vom Upstream-Router und DNS von überall.

Erstellen Sie /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;
    }
}

Passen Sie die ip saddr- und ip6 saddr-Zeilen auf Knoten B an (tauschen Sie die Zonentransfer-Peer-IPs, und verwenden Sie den IPv6-Upstream-Neighbor von Knoten B). In Produktion beschränken Sie SSH auf Ihr Verwaltungs-CIDR.

Anwenden und aktivieren:

systemctl enable --now nftables

Aktive Regeln auflisten, um das Laden zu bestätigen:

nft list ruleset

Dann testen, ob DNS von außen noch funktioniert:

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

Falls DNS nach dem Anwenden der Firewallregeln nicht mehr funktioniert, prüfen Sie, ob daddr genau Ihrer Anycast-IP entspricht und ob das anycast0-Interface aktiv ist.

Wie prüfe ich DNS per Health-Check und ziehe BGP-Routen bei Ausfall zurück?

Ein Health-Check-Skript überwacht BIND9 und signalisiert BIRD2, die Anycast-Route zurückzuziehen, wenn DNS ausfällt. Die Methode: Das anycast0-Interface herunterfahren, woraufhin BIRD2 das Präfix automatisch zurückzieht, da das direct-Protokoll die Route nicht mehr sieht.

Warum nicht einfach auf BGP-Timer vertrauen?

Mit Standard-BGP-Timern (90 s Hold, 30 s Keepalive) dauert ein Failover bis zu 90 Sekunden. Ein Health-Check erkennt DNS-Ausfälle in Sekunden und löst den Rückzug sofort aus. Die folgende Tabelle zeigt die Auswirkungen:

Methode Erkennungszeit Konvergenz
BGP-Hold-Timer-Ablauf 90 Sekunden 90-180 Sekunden
BFD (falls verfügbar) <1 Sekunde 1-3 Sekunden
Health-Check-Skript 5-15 Sekunden 35-45 Sekunden (+ BGP-Propagation)

BFD erkennt nur Link-/Session-Ausfälle, keine Anwendungsausfälle. Das Health-Check-Skript fängt BIND9-Abstürze, Fehlkonfigurationen und volle Festplatten ab, die BFD nicht sehen kann.

Dedizierten Benutzer anlegen

Der Health-Check läuft unter einem dedizierten Benutzer mit minimalen Rechten:

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

Erteilen Sie ihm die Berechtigung, das Anycast-Interface zu steuern. Erstellen Sie eine Sudoers-Regel:

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-Skript schreiben

Erstellen Sie /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

Berechtigungen setzen:

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

Systemd-Service erstellen

Erstellen Sie /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 ist hier erforderlich, weil das Skript sudo zum Umschalten des Interfaces nutzt. Die Sudoers-Regel beschränkt, welche Befehle der Benutzer ausführen darf.

Der [Unit]-Abschnitt verwendet Wants= statt Requires=. Mit Requires= würde systemd den Health-Check stoppen, wenn named stoppt. Das ist genau das Gegenteil des gewünschten Verhaltens: Der Health-Check muss weiterlaufen, um den DNS-Ausfall zu erkennen und die Route zurückzuziehen.

Aktivieren und starten:

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

Prüfen, ob es läuft:

systemctl status anycast-healthcheck

Die Logs sollten die Startmeldung zeigen:

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

Wie teste ich das Anycast-DNS-Failover?

Führen Sie diese Tests von einer externen Maschine aus, nicht von einem der Knoten.

Schritt 1: Bestätigen, dass beide Knoten ankündigen

Von Ihrer lokalen Maschine:

dig @198.51.100.1 example.com A +short

Bestätigen Sie eine Antwort. Führen Sie einen Traceroute aus, um zu sehen, welchen Knoten Sie erreichen:

traceroute -n 198.51.100.1

Der Pfad zeigt, welcher Standort Ihnen am nächsten ist.

Schritt 2: DNS-Ausfall auf Knoten A simulieren

Auf Knoten A BIND9 stoppen:

systemctl stop named

Health-Check-Logs beobachten:

journalctl -u anycast-healthcheck -f

Erwartete Sequenz:

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

Nach 15 Sekunden (3 Prüfungen im 5-Sekunden-Intervall) deaktiviert der Health-Check anycast0. BIRD2 erkennt die Interface-Änderung und zieht die Route zurück.

Prüfen, ob die Route verschwunden ist:

birdc show route export upstream4

Die Tabelle sollte leer sein. Der Upstream-Router entfernt 198.51.100.0/24 von Knoten A und leitet den gesamten Verkehr an Knoten B weiter.

Schritt 3: Client-Failover prüfen

Von Ihrer externen Maschine erneut abfragen:

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

Die erste Abfrage nach dem Rückzug kann ein Timeout ergeben (die alte Route ist in manchen Routern noch gecacht). Folgeabfragen erreichen Knoten B. Die BGP-Konvergenz dauert je nach Upstream-Konfiguration typischerweise 30-90 Sekunden.

Schritt 4: Knoten A wiederherstellen

systemctl start named

Der Health-Check erkennt, dass DNS nach 2 aufeinanderfolgenden Erfolgen (10 Sekunden) wieder funktioniert, und aktiviert anycast0. BIRD2 kündigt die Route erneut an.

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

Erwartet:

ANNOUNCE: anycast0 up after 2 consecutive successes

BGP-Sessions prüfen:

birdc show protocols

Beide BGP-Sessions sollten wieder Established anzeigen.

Schritt 5: Konvergenzzeit messen

Für eine genaue Messung starten Sie eine kontinuierliche dig-Schleife von einer externen Maschine:

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

Stoppen Sie BIND9 auf dem Knoten, den Sie gerade erreichen. Zählen Sie die Sekunden zwischen der letzten erfolgreichen Antwort und der ersten erfolgreichen Antwort vom anderen Knoten. Auf Virtua-Infrastruktur mit Standard-BGP-Timern sind 30-60 Sekunden partielle Nichterreichbarkeit zu erwarten.

Wie schneidet BIRD2 im Vergleich zu anderen Routing-Daemons für Anycast ab?

Eigenschaft BIRD2 FRRouting ExaBGP
Konfigurationssprache Deklarative Filter Cisco-ähnliche CLI JSON/API
Anycast-Integration Direct-Protokoll auf Dummy-Interface Statische Route + Redistribution Externes Skript kündigt an/zieht zurück
Health-Check-Methode Interface-Up/Down löst Routenänderung aus vtysh-Befehle aus Skript Eingebauter Prozessmanager
IPv6-Unterstützung Einheitliche Konfiguration (IPv4/IPv6-Kanäle) Separate Adressfamilien Manuell pro Familie
Filterkomplexität Vollständige Filtersprache mit Funktionen Route Maps, Prefix Lists Nur externe Logik
Speicherverbrauch Niedrig (~10 MB bei kleiner Tabelle) Mittel (~30 MB) Sehr niedrig (~5 MB)
Produktionsreife IXPs, großangelegtes DNS (Cloudflare, RIPE) ISPs, Rechenzentren Kleine Deployments, Monitoring

Der Vorteil von BIRD2 für Anycast: Das protocol direct auf einem Dummy-Interface bietet automatischen Routenrückzug, wenn das Interface ausfällt. Kein externes Scripting für den BGP-Teil nötig. Der Health-Check muss nur das Interface umschalten.

Wie synchronisiere ich DNS-Zonen über Anycast-Knoten hinweg?

Die Zonensynchronisation nutzt die in BIND9 eingebaute Primär/Sekundär-Replikation über AXFR, authentifiziert mit TSIG. Die Konfiguration wurde im BIND9-Abschnitt oben vorgenommen. Hier die operativen Details.

Zonenaktualisierungen fließen in eine Richtung: Bearbeiten Sie die Zonendatei auf Knoten A (primär), erhöhen Sie die Seriennummer und laden Sie neu:

# 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

Führen Sie immer named-checkzone vor dem Neuladen aus. Es findet Syntaxfehler.

rndc reload example.com

Knoten B erhält ein NOTIFY von Knoten A und holt die aktualisierte Zone per AXFR. Auf Knoten B die Seriennummer prüfen:

dig @127.0.0.1 example.com SOA +short

Die Seriennummer muss übereinstimmen. Falls nicht, prüfen Sie:

  1. Die Firewall erlaubt TCP 53 von Knoten A zu Knoten B
  2. Der TSIG-Schlüssel ist auf beiden Knoten identisch
  3. journalctl -u named auf Knoten B auf Transferfehler

Dritten Knoten hinzufügen: Konfigurieren Sie ihn als weiteren sekundären Knoten. Fügen Sie seine IP zur also-notify-Liste und den nftables-Regeln auf Knoten A hinzu. Keine Änderungen an bestehenden sekundären Knoten nötig.

Was ist mit IPv6-BGP-Anycast?

Die obige BIRD2-Konfiguration enthält bereits IPv6. Der protocol direct anycast-Block hat beide Kanäle ipv4 und ipv6. Die upstream6-BGP-Session exportiert das /48-Präfix.

IPv6-Routenexport prüfen:

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 über IPv6 testen:

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

Fehlerbehebung

BGP-Session hängt in Active/Connect:

journalctl -u bird -f

Häufige Ursachen: falsche Neighbor-IP, falsche ASN, MD5-Passwort-Mismatch, Firewall blockiert TCP 179. Führen Sie birdc show protocols all upstream4 für detaillierte Fehlermeldungen aus.

BIND9 lauscht nicht auf der Anycast-IP:

ss -tlnp | grep ':53'

Sehen Sie nur 127.0.0.1:53, prüfen Sie, ob listen-on die Anycast-IP enthält und ob das anycast0-Interface vor dem Start von BIND9 aktiv war. Starten Sie BIND9 nach dem Hochfahren des Interfaces neu: systemctl restart named.

Zonentransfer schlägt fehl:

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

Prüfen Sie, ob die Schlüsseldatei vom bind-Benutzer gelesen werden kann: ls -la /etc/bind/anycast-transfer.key. Manuellen Transfertest ausführen: dig @203.0.113.10 example.com AXFR -k /etc/bind/anycast-transfer.key.

Health-Check zieht Routen nicht zurück:

journalctl -u anycast-healthcheck -f

Sudo-Berechtigungen prüfen: sudo -u anycast-healthcheck sudo -l. Der Benutzer sollte /usr/sbin/ip link set anycast0 up und down ohne Passwort ausführen können.

Abfragen laufen nach Failover in ein Timeout:

DNS-Clients cachen Antworten. Der TTL in der Zonendatei (300 Sekunden / 5 Minuten) bestimmt, wie lange Clients veraltete Daten verwenden. Die BGP-Konvergenz fügt 30-90 Sekunden hinzu. Gesamte Failover-Zeit aus Client-Sicht: bis zum TTL plus Konvergenzzeit. Senken Sie den SOA-Minimum-TTL, wenn schnelleres Failover wichtig ist, aber gehen Sie für autoritative Zonen nicht unter 60 Sekunden.

Produktions-Checkliste

Vor dem Go-Live:

  • ROA-Einträge erstellt und validiert (rpki-client oder Ihr RIR-Portal)
  • BGP-Sessions auf allen Knoten etabliert (birdc show protocols)
  • Anycast-Präfix in globalen Routing-Tabellen sichtbar (nutzen Sie ein Looking Glass)
  • BIND9 antwortet auf der Anycast-IP aus externen Netzen
  • Zonenseriennummer stimmt auf allen Knoten überein
  • TSIG-Schlüssel-Berechtigungen auf 640 gesetzt, Eigentümer root:bind
  • nftables-Regeln auf allen Knoten aktiv
  • Health-Check-Service aktiviert und läuft
  • Failover getestet: BIND9 gestoppt, Routenrückzug bestätigt, Wiederherstellung bestätigt
  • Monitoring konfiguriert: Alarme bei BGP-Session-Down, BIND9-Prozess, Health-Check-Fehlern
  • journalctl -u bird und journalctl -u named zeigen keine Fehler

Für die übergeordnete Anleitung zu BGP-Grundlagen und eigenem IP-Adressraum siehe BGP und Bring Your Own IP auf einem VPS: Der vollständige Leitfaden. Für BGP-Routenfilterung und Sicherheitshärtung siehe BGP Route Filtering: Prefix-Listen, AS-Path-Filter, Bogon-Ablehnung und GTSM.

Anycast-DNS mit BIRD2 und BGP: Multi-Standort-Setup