Zum Inhalt

Bind als Caching-Only-DNS-Resolver

Stand: 03/2025
Bind9-Logo
Verwendete Software
- Ubuntu 24.04 LTS
- Bind9 9.18.30
Weitere Dokumentationen:

Vorwort

Diese Dokumentation beschreibt die Installation eines Caching-Only-DNS-Resolver mit Bind.
Ziel der Dokumentation ist es, die Einrichtung und Konfiguration eines Caching-Only-DNS-Resolvers zu beschreiben, der DNS-Anfragen effizient lokal zwischenspeichert und so die Namensauflösung beschleunigt, ohne selbst autoritative Zonen bereitzustellen.

Es sind Grundkenntnisse im Umgang mit Linux erforderlich, da die Vorgehensweise, wie sie hier beschrieben ist, in manchen Teilen abweichen kann. Kein System ist wie das Andere. Die Dokumentation wurde sorgfältig von mir geprüft. Dennoch kann ich keinerlei Haftung für Schäden an Ihrem System oder eine Gewährleistung übernehmen. Bitte verwenden Sie diese Dokumentation auf eigene Gefahr.


Server vorbereiten

Root-Rechte einräumen

Während der gesamten Dokumentation werden Root-Rechte benötigt. Um auf das Voranstellen von sudo zu verzichten, verschaffen Sie sich für die gesamte Sitzung erhöhte Rechte mit:

sudo -i

Update durchführen

Bringen Sie den Server auf den aktuellen Patchstand:

apt update && apt upgrade

Software installieren

Laden Sie Bind aus dem offiziellen Ubuntu-Repository herunter:

apt install bind9


Bind einrichten

Bind9 konfigurieren

Hinweis!

Wenn Sie den DNS-Resolver an einem Internet-Anschluss ohne Dual-Stack (ohne IPv6) betreiben, muss IPv6 in Bind deaktiviert werden, da Bind ansonsten versucht, die Namensauflösung über die IPv6-Adressen der Root-Server durchzuführen.

Editieren Sie die dazu die Datei /etc/default/named und ändern Sie folgenden Parameter:

nano /etc/default/named
named
OPTIONS="-u bind -4"

Fügen Sie der Datei /etc/bind/named.conf.options folgendes hinzu, damit IPv6-Adressen bei der DNS-Abfrage ausgeblendet werden:
nano /etc/bind/named.conf.options
named.conf.options
plugin query "filter-aaaa.so" {
  filter-aaaa-on-v4 yes;
};



Benennen Sie zunächst die /etc/bind/named.conf.options um, damit Sie eine Sicherheitskopie der Original-Datei haben:

mv /etc/bind/named.conf.options /etc/bind/named.conf.options.bak

Erstellen Sie eine neue Datei /etc/bind/named.conf.options:
nano /etc/bind/named.conf.options
named.conf.options
options {
    directory "/var/cache/bind";

        dnssec-validation auto;
        auth-nxdomain no;
        recursion yes;

        listen-on {
              127.0.0.1;
        };

      allow-query {
              127.0.0.1;
      };

    allow-transfer { none; };
    allow-update { none; };

    version none;
    hostname none;
    server-id none;
};

controls {
      inet 127.0.0.1 port 953 allow { 127.0.0.1; };
};

Editieren Sie die Date /etc/bind/named.conf.default-zones und ändern Sie folgenden Parameter:
nano /etc/bind/named.conf.default-zones
named.conf.default-zones
zone "." {
    type hint;
    file "/etc/bind/db.root";
};

Root-Hints und DNSSEC-Keys aktualisieren

Laden Sie die Liste der Root-DNS-Server /etc/bind/db.root herunter:

dig +bufsize=1200 +norec NS . @a.root-servers.net > /etc/bind/db.root

Laden Sie die Liste der dazugehörigen DNSSEC-Keys /etc/bind/bind.keys herunter:
wget "https://ftp.isc.org/isc/bind9/keys/9.11/bind.keys.v9_11" -q -O /etc/bind/bind.keys

Löschen Sie eventuell vorhandene Cache-Dateien:
rm /var/cache/bind/managed-keys.bind*

Starten Sie Bind neu, damit die Konfiguration angewendet wird:
service bind9 restart

Cron-Job einrichten

Info

Die Liste der Root-Nameserver und die dazugehörigen DNSSEC-Keys können sich in unregelmäßigen Abständen ändern. Damit unser DNS-Resolver reibungslos DNS-Abfragen durchführen kann, müssen die Listen immer auf dem neuesten Stand gehalten werden.

Dieser Cron-Job aktualisiert wöchentlich beide Listen und versendet einen Status-Report per E-Mail.


Erstellen Sie die Datei/etc/cron.weekly/twilight-bind-roothints mit folgendem Inhalt:

nano /etc/cron.weekly/twilight-bind-roothints

twilight-bind-roothints
#!/bin/sh
#
#            ___________       .__.__  .__       .__     __
#            \__    ___/_  _  _|__|  | |__| ____ |  |___/  |_
#              |    |  \ \/ \/ /  |  | |  |/ ___\|  |  \   __\
#              |    |   \     /|  |  |_|  / /_/  >   Y  \  |
#              |____|    \/\_/ |__|____/__\___  /|___|  /__|
#                                        /_____/      \/
#
#  Twilight-Networks ∙ est. 2k1 ∙ fxp ∙ h4x ∙ nfo ∙ scene ∙ legacy lives
#  Keep it lean, keep it silent. Code like it's 2002.
#
##########################################################################
# Script Name   : twilight-bind-roothints
# Description   : Updates BIND9 root hints (db.root) and DNSSEC keys
#                 (bind.keys) from official sources. Automatically
#                 creates backups and sends status notifications
#                 via email. Designed for use in cron environments.
#
# Version       : 1.01
# Last Update   : 2025-03-28
# Author        : Twilight
# Contact       : [email protected]
# Web           : https://www.twilight-networks.com
#
# Getestet mit  : Ubuntu 24.04 LTS
#                 Bind 9.18.30
##########################################################################


# Config
BINDDIR='/etc/bind'
ROOTHINTS='db.root'
ROOTHINTSKEYS='bind.keys'
CHECKONLINE='1.1.1.1'
MAILTO='[email protected]'
MAILFROM='[email protected]'
DNSSEC_URL="https://ftp.isc.org/isc/bind9/keys/9.11/bind.keys.v9_11"
EMAILREPORT=true


# Below this line: scene code. You break it, you bought it.
#########################################################################

# Internal
TMPDIR=$(mktemp -d /tmp/bindupdate.XXXXXX)
ROOTHINTS_NEW="${TMPDIR}/${ROOTHINTS}.new"
ROOTHINTSKEYS_NEW="${TMPDIR}/${ROOTHINTSKEYS}.new"
ERRORLOG_ROOT="${TMPDIR}/errors_roothints"
ERRORLOG_KEYS="${TMPDIR}/errors_keys"
HOSTNAME_SHORT=$(hostname -s)

cleanup() {
    rm -rf "$TMPDIR"
}
trap cleanup EXIT

# Check BIND
check_bind_status() {
    if ! rndc status 2>/dev/null | grep -q "server is"; then
        log_error "[ERROR] Bind ist nicht aktiv oder rndc verweigert Zugriff."
        exit 1
    fi
}

# Check Internet
check_network() {
    if ! ping -qnc 1 "$CHECKONLINE" >/dev/null 2>&1; then
        log_error "[ERROR] Keine Internetverbindung zu $CHECKONLINE."
        exit 1
    fi
}

# Download root hints
download_roothints() {
    dig +bufsize=1200 +norec NS . @a.root-servers.net >"$ROOTHINTS_NEW" 2>"$ERRORLOG_ROOT"
    grep -q 'NOERROR' "$ROOTHINTS_NEW" || {
        log_error "[ERROR] Fehler beim Download der Root-Hints."
        cat "$ERRORLOG_ROOT"
        exit 1
    }
}

# Download DNSSEC keys
download_dnssec_keys() {
    wget "$DNSSEC_URL" -q -O "$ROOTHINTSKEYS_NEW" 2>"$ERRORLOG_KEYS"
    grep -q 'managed-keys' "$ROOTHINTSKEYS_NEW" || {
        log_error "[ERROR] Fehler beim Download der DNSSEC-Keys."
        cat "$ERRORLOG_KEYS"
        exit 1
    }
}

# Vergleichsfunktion, die auch bei fehlenden Altdateien sauber arbeitet
filtered_sha256() {
    grep -vE '^\s*(;|$)' "$1" | sha256sum | awk '{print $1}'
}

files_differ() {
    [ ! -f "$2" ] && return 0

    old_hash=$(filtered_sha256 "$2")
    new_hash=$(filtered_sha256 "$1")

    [ "$old_hash" != "$new_hash" ]
}

# Logging
log_info() {
    logger -t bind-update "$1"
    echo "$1"
}

log_error() {
    logger -t bind-update "$1"
    echo "$1" >&2
    $EMAILREPORT && report_error "$1" "$(tail -n 50 "$ERRORLOG_ROOT" "$ERRORLOG_KEYS" 2>/dev/null | grep -v '^$' || echo "Keine weiteren Details verfügbar.")"
}

# Report-E-Mail
report_success() {
    {
        echo "From: $MAILFROM"
        echo "To: $MAILTO"
        echo "Subject: [bind] [OK] ${HOSTNAME_SHORT} Root-Hints und DNSSEC-Keys aktualisiert"
        echo "MIME-Version: 1.0"
        echo "Content-Type: text/html; charset=UTF-8"
        echo
        echo "<html><body><pre style=\"font-family: monospace; font-size: 13px; color: #333; background: #f9f9f9; padding: 1em; border: 1px solid #ccc;\">"
        echo "Aktualisierung erfolgreich:"
        echo "- $BINDDIR/$ROOTHINTS"
        echo "- $BINDDIR/$ROOTHINTSKEYS"
        echo
        echo "Backups unter:"
        echo "- $BINDDIR/${ROOTHINTS}.bak"
        echo "- $BINDDIR/${ROOTHINTSKEYS}.bak"
        echo "</pre></body></html>"
    } | /usr/sbin/sendmail -t
}

report_nochange() {
    {
        echo "From: $MAILFROM"
        echo "To: $MAILTO"
        echo "Subject: [bind] [OK] ${HOSTNAME_SHORT} Root-Hints und DNSSEC-Keys unverändert"
        echo "MIME-Version: 1.0"
        echo "Content-Type: text/html; charset=UTF-8"
        echo
        echo "<html><body><pre style=\"font-family: monospace; font-size: 13px; color: #333; background: #f9f9f9; padding: 1em; border: 1px solid #ccc;\">"
        echo "Es waren keine Änderungen erforderlich. Bind wurde nicht neugestartet."
        echo "</pre></body></html>"
    } | /usr/sbin/sendmail -t
}

report_error() {
    local subject="$1"
    local message="$2"

    {
        echo "From: $MAILFROM"
        echo "To: $MAILTO"
        echo "Subject: [bind] [ERROR] ${HOSTNAME_SHORT} $subject"
        echo "MIME-Version: 1.0"
        echo "Content-Type: text/html; charset=UTF-8"
        echo
        echo "<html><body><pre style=\"font-family: monospace; font-size: 13px; color: #333; background: #f9f9f9; padding: 1em; border: 1px solid #ccc;\">"
        echo "$message"
        echo "</pre></body></html>"
    } | /usr/sbin/sendmail -t
}

perform_update() {
    log_info "[INFO] Änderungen erkannt – Update wird durchgeführt."

    cp "$BINDDIR/$ROOTHINTS" "$BINDDIR/${ROOTHINTS}.bak" 2>/dev/null || true
    cp "$BINDDIR/$ROOTHINTSKEYS" "$BINDDIR/${ROOTHINTSKEYS}.bak" 2>/dev/null || true

    install -o root -g root -m 444 "$ROOTHINTS_NEW" "$BINDDIR/$ROOTHINTS"
    install -o root -g root -m 444 "$ROOTHINTSKEYS_NEW" "$BINDDIR/$ROOTHINTSKEYS"

    rndc reload
    log_info "[INFO] Bind wurde neu geladen."

    $EMAILREPORT && report_success
}

main() {
    check_bind_status
    check_network
    cd "$BINDDIR" || {
        log_error "[ERROR] Zugriff auf $BINDDIR nicht möglich."
        exit 1
    }

    download_roothints
    download_dnssec_keys

    if files_differ "$ROOTHINTS_NEW" "$ROOTHINTS" || files_differ "$ROOTHINTSKEYS_NEW" "$ROOTHINTSKEYS"; then
        perform_update
    else
        log_info "[INFO] Keine Änderungen erkannt – kein Update notwendig."
        $EMAILREPORT && report_nochange
    fi
}

main

#end
Anzupassende Parameter:
[...]
# Reports are sent to
MAILTO='[email protected]'

# Reports are sent by
MAILFROM='[email protected]'
[...]

Vergeben Sie die benötigte Berechtigung, damit die Datei ausgeführt werden kann:
chmod +x /etc/cron.weekly/twilight-bind-roothints

DNS-Resolver via Netplan bekanntgeben

Editieren Sie die Datei /etc/netplan/50-cloud-init.yaml und tragen Sie 127.0.0.1 als Nameserver ein:
Ersetzen Sie 50-cloud-init.yaml durch die Datei, die auf Ihrem System verwendet wird.

nano /etc/netplan/50-cloud-init.yaml
50-cloud-init.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      nameservers:
        search: [example.com]
        addresses:
          - "127.0.0.1"

Wenden Sie die neue Konfiguration an:
netplan apply

Kontrollieren Sie, ob der lokale DNS-Resolver (127.0.0.1) als Nameserver verwendet wird:
resolvectl status
  [...]
  Link 2 (eth0)
        Current Scopes: DNS
  DefaultRoute setting: yes
         LLMNR setting: yes
  MulticastDNS setting: no
    DNSOverTLS setting: no
        DNSSEC setting: no
      DNSSEC supported: no
    Current DNS Server: 127.0.0.1
           DNS Servers: 127.0.0.1
            DNS Domain: example.com


Inbetriebnahme und Test

DNS-Resolver testen

Fragen Sie einen Hostnamen (z. B. heise.de) ab und kontrollieren Sie die Ausgabe:

nslookup heise.de
Server:         127.0.0.53
Address:        127.0.0.53#53

Non-authoritative answer:
Name:   heise.de
Address: 193.99.144.80


Abschluss

Wenn die DNS-Abfrage erfolgreich verlief und Sie keine Fehlermeldungen erhalten haben, ist Ihr DNS-Resolver einsatzbereit.