bk99.de entertain the web since 1997

DNS umleiten: Jede Anfrage auf Port 53 landet beim eigenen Resolver

Zusammenfassung

Im Juli 2009 habe ich mir notiert, wie mein Linux-Router jede DNS-Anfrage aus den internen Netzen auf den eigenen Bind9 umleitet, egal welchen Server ein Gerät eingetragen hat. Drei Bausteine reichten: DNAT auf Port 53, eine Ausgangsregel, die nur dem Bind-Benutzer den Weg nach draußen erlaubt, und als Alternative Regeln für die Server aus der resolv.conf. Dieser Beitrag aus meinem alten Blog erklärt die Regeln, zeigt sie in nftables und sagt, wo die Methode heute an DNS über TLS und HTTPS endet.

Die Notiz von 2009

Die Seite in meinem alten Plone-Blog war eine Gedächtnisstütze, kein Artikel. Sie begann mit dem Satz „Notiz: Falls ich wieder einmal jemanden einen DNS Server aufzwingen möchte.“ Dahinter standen Auszüge aus dem Firewall-Skript meines Routers, der damals mehrere interne Netze an eth1, eth2 und eth4 hatte und über eth0 ins Internet ging. Auf dem Router lief Bind9 als Resolver für alle Netze.

Das Ziel: Ein Gerät im Netz soll seine Namen beim Router auflösen, auch wenn darin fest ein anderer DNS-Server eingetragen ist. Nur dann wirken eigene Zonen für interne Namen, ein gemeinsamer Cache und später auch Sperrlisten für Werbung.

Baustein 1: Alles auf Port 53 umbiegen

Die erste Regelgruppe, hier der Auszug für eth1, fängt im PREROUTING jede Anfrage an Port 53 ab, die aus einem internen Netz kommt und nicht ohnehin an den Router gerichtet ist, und schreibt das Ziel per DNAT auf die Adresse des Routers um. Das passiert für UDP und für TCP, weil große Antworten und Zonentransfers über TCP laufen. Die Variablen stammen aus dem Rest des Skripts; $ifc_eth1 ist 1, wenn die Schnittstelle existiert.

# alle DNS Anfragen auf lokalen Bind9 umleiten
[ $ifc_eth1 -eq 1 ] && $iptables -t nat -A PREROUTING -i eth1 -p udp -s $net_eth1 -d ! $ip_eth1 --dport 53 -j DNAT --to $ip_eth1
[ $ifc_eth1 -eq 1 ] && $iptables -t nat -A PREROUTING -i eth1 -p tcp -s $net_eth1 -d ! $ip_eth1 --dport 53 -j DNAT --to $ip_eth1

Für das Gerät sieht alles normal aus. Es fragt zum Beispiel 8.8.8.8 und bekommt eine Antwort mit genau dieser Absenderadresse, denn conntrack schreibt die Antwort des Routers auf dem Rückweg wieder um. Dass in Wahrheit der Router geantwortet hat, merkt es nicht.

Baustein 2: Nur Bind darf nach draußen

Umleiten allein reicht nicht, solange der Router selbst beliebige DNS-Anfragen ins Internet lässt. Die zweite Regelgruppe erlaubt neue Verbindungen auf Port 53 über eth0 nur Prozessen mit der Benutzerkennung 103, unter der auf meinem System Bind lief. Das Modul owner funktioniert nur in den Ketten OUTPUT und POSTROUTING, denn nur bei Paketen, die auf dem Rechner selbst entstehen, weiß der Kernel, welcher Prozess sie erzeugt hat.

# Bind9 darf DNS Anfragen stellen
[ $ifc_eth0 -eq 1 ] && $iptables -A OUTPUT -o eth0 -p udp -s $ip_eth0 --dport 53 -m owner --uid-owner 103 -m state --state NEW -j ACCEPT
[ $ifc_eth0 -eq 1 ] && $iptables -A OUTPUT -o eth0 -p tcp -s $ip_eth0 --dport 53 -m owner --uid-owner 103 -m state --state NEW -j ACCEPT

Fest eingetragene Nummern sind dabei eine Falle: Auf einem anderen System gehört die 103 einem ganz anderen Dienst. Besser ist der Name, also --uid-owner bind, den iptables beim Laden selbst auflöst.

Variante: nur Forwarder

Als Alternative stand darunter eine Schleife für den Fall, dass der Router nicht selbst rekursiv auflöst, sondern nur an die DNS-Server des Providers weiterreicht. Sie zählt die nameserver-Zeilen in /etc/resolv.conf, holt jede Adresse einzeln heraus und erlaubt nur zu diesen Servern ausgehende Verbindungen auf Port 53.

# DNS Server aus der /etc/resolv.conf zaehlen
int_dnsserver=$($grep ^nameserver /etc/resolv.conf | $wc -l | $tr -d " ")

# DNS Anfragen auf Server in /etc/resolv.conf
for ((I=1; $I <= $int_dnsserver; I++)) ; do
  # DNS-IP's ermitteln
  dnsip=$($grep ^nameserver /etc/resolv.conf | $head -n $I | $tail -n 1 | $cut -d " " -f 2)
  # $DNSIP in die chains schreiben
  [ $ifc_eth0 -eq 1 ] && $iptables -A OUTPUT -o eth0 -p tcp -s $ip_eth0 -d $dnsip --dport 53 -m state --state NEW -j ACCEPT
  [ $ifc_eth0 -eq 1 ] && $iptables -A OUTPUT -o eth0 -p udp -s $ip_eth0 -d $dnsip --dport 53 -m state --state NEW -j ACCEPT
  # Schleifenabbruch
  if [ $I -eq $int_dnsserver ] ; then
    break
  fi
done

Mit dem Blick von heute: Das break am Ende ist überflüssig, die Schleife endet ohnehin nach dem letzten Server. Kürzer wäre eine Zeile wie awk '/^nameserver/ {print $2}' /etc/resolv.conf als Liste für for. Und die Regeln entstehen nur einmal beim Start der Firewall; ändert DHCP die resolv.conf später, passen sie nicht mehr.

Warum das wenig später wichtiger wurde

Am 3. Dezember 2009, ein knappes halbes Jahr nach meiner Notiz, startete Google seinen öffentlichen Resolver unter 8.8.8.8 und 8.8.4.4. Ab da trugen immer mehr Geräte und Anleitungen diese Adressen fest ein, an jedem eigenen Resolver vorbei. Genau gegen solche fest eingestellten Server hilft die Umleitung auf Port 53.

Dieselbe Idee heute

In iptables gilt die Schreibweise -d ! als veraltet, heute steht das Ausrufezeichen vor der Option: ! -d $ip_eth1. Die meisten Distributionen nutzen inzwischen nftables, dort fasst eine einzige Regel UDP und TCP zusammen:

table inet dnsumleitung {
  chain prerouting {
    type nat hook prerouting priority dstnat; policy accept;
    iifname "lan0" ip daddr != 192.168.1.1 meta l4proto { tcp, udp } th dport 53 dnat ip to 192.168.1.1
  }
}

Für IPv6 gab es 2009 keine Lösung: NAT für IPv6 kam erst mit Linux 3.7 Ende 2012 in den Kernel. Wer heute IPv6 im Netz hat, braucht eine zweite Regel mit ip6, sonst fragen Geräte über IPv6 weiter, an wen sie wollen.

Die größere Grenze ist Verschlüsselung. DNS über TLS (RFC 7858) läuft auf Port 853 und lässt sich sperren; Geräte, die es nur bei Verfügbarkeit nutzen, fallen dann auf normales DNS zurück, fest eingestellte Geräte lösen dagegen gar nicht mehr auf. DNS über HTTPS (RFC 8484) dagegen ist von gewöhnlichem HTTPS nicht zu unterscheiden. Umleiten lässt es sich nicht, nur über Sperrlisten bekannter Anbieter erschweren. Firefox prüft zusätzlich die sogenannte Kanarienvogel-Domain use-application-dns.net: Antwortet der Resolver im Netz darauf mit NXDOMAIN, schaltet Firefox seine automatische DoH-Nutzung ab, sofern der Nutzer sie nicht selbst aktiviert hat.

Bei mir zu Hause macht das heute die Firewall, eine OPNsense: Unbound löst auf, prüft DNSSEC und wendet Sperrlisten gegen Werbung an, und DNS aus den internen Netzen wird per NAT auf die Firewall umgeleitet. Das Prinzip ist dasselbe wie 2009, nur in der Oberfläche statt im Shell-Skript.

Eigenes Netz ist nicht Internet

Im eigenen Netz und für die eigenen Geräte ist diese Umleitung Selbstverwaltung: Ich entscheide, wer meine Namen auflöst, welche Werbeserver nicht erreichbar sind und welche Geräte im Gästenetz was sehen. Wer anderen aber über den Netzbetreiber oder per Gesetz einen Resolver aufzwingt, baut mit derselben Technik eine Zensurinfrastruktur. Genau so funktionieren staatliche DNS-Sperren, wie Frankreichs Sperre für TikTok, Instagram, Snapchat und X. Das lehne ich ab, und dass verschlüsseltes DNS solche Sperren umgehen kann, ändert nichts daran, dass sie falsch sind.

Zahlen im Überblick

  • Die Notiz stand in meinem alten Plone-Blog unter /dokumente/dns-umleiten, zuletzt geändert am 17. Juli 2009.
  • Google Public DNS startete am 3. Dezember 2009 unter 8.8.8.8 und 8.8.4.4.
  • Das iptables-Modul owner wirkt nur in den Ketten OUTPUT und POSTROUTING, also für Pakete des Rechners selbst.
  • NAT für IPv6 kam mit Linux 3.7 Ende 2012 in den Kernel.
  • DNS über TLS nutzt Port 853, DNS über HTTPS den gewöhnlichen HTTPS-Port 443.

Referenzen

Bemerkungen

  • Dieser Beitrag geht auf eine Notiz aus meinem alten Plone-Blog zurück, die im Webarchiv nur in einer einzigen Aufnahme vom Mai 2010 erhalten ist. 2026 habe ich die Regeln wörtlich übernommen, bei der Umleitung nur die Zeilen für eth1, und um Erklärungen und den heutigen Stand ergänzt.
  • Die Bezeichnungen der Schnittstellen und die Benutzerkennung 103 stammen aus meinem damaligen Router und lassen sich nicht auf andere Systeme übertragen.

Originalnotiz im Webarchiv