Artikel mit ‘djbdns’ getagged

DNS-Resolving mit persistentem Cache

Samstag, 03. September 2011

Wir fahren in Bezug auf DNS-Caching eine einfache, aber sehr effektive Strategie: Alle von uns verwalteten Hosts bekommen einen lokalen DNS-Resolver. Zwar haben wir in unseren Netzen jeweils ebenfalls DNS-Resolver, die direkt mit einem Eintrag in der /etc/resolv.conf netzintern genutzt werden können, aber das brauchen wir eigentlich nur während der Installation von Systemen, die wir typischerweise via PXE-Boot und dann über das Netz machen, wofür ein fertig bereitstehender Resolver vonnöten ist. Sobald eine Maschine läuft, bekommt sie einen eigenen Resolver.

Dieser Ansatz mag erstmal ungewöhnlich wirken, gehört doch das Eintragen von klassischerweise zwei DNS-Resolvern in die eigene Netzwerkkonfiguration zum üblichen Standard. Ein Aspekt ist sicherlich eine gewisse Performancesteigerung – was der gängige Grund sein dürfte, warum oft empfohlen wird, einen lokalen DNS-Cache auf dem heimischen Rechner zu installieren, denn bei gar nicht mal so wenigen Zugangsprovidern sind die von jenen bereitgestellten Resolver oftmals in bedauernswertem Zustand, was die Performance angeht, und gerade die günstigeren Plaste-DSL-Router für Daheim bekleckern sich hier auch oft nicht mit Ruhm – gelegentlich mag auch mein Linksys-Router einfach keine DNS-Anfragen mehr auflösen oder an den Upstream-Resolver durchreichen, obwohl er den Internetzugang an sich noch bereitstellt – nur ein Router-Reboot hilft dann noch. „Stabil“ ist anders.

Im Rechenzentrum zählen diese Argumente weniger. Die dort bereitgestellten DNS-Resolver liegen ja im gleichen Netz wie die anfragenden Hosts, und sie sind zudem mit üppiger Hardware ausgestattet. Was die Performance angeht, liegen wir hier also eher von Optimierungen im Millisekundenbereich, die es wirklich nicht wert wären.

Worum’s uns viel mehr geht, ist, mögliche Störungsquellen zu minimieren. Das DNS an sich ist voller Redundanz und Lastverteilung: Müssen DNS-Anfragen aufgelöst werden, stehen satte 13 IPv4-Adressen von Root-Nameservern bereit; einige sind auch via IPv6 erreichbar. Hinter den meisten stehen nicht etwa einzelne Hosts, sondern gleich etliche, die via Anycast geografisch verteilt sind. Fragt man jene beispielsweise nach der .de-Delegation, erhält man derzeit immerhin gleich fünf mögliche Nameserver, auch wiederum geografisch verteilt und zum Teil über IPv6 erreichbar. Für jede einzelne .de-Domain sind dann auch wieder mindestens zwei Nameserver zuständig, die gemäß der Vorgaben der DENIC in unterschiedlichen Class-C-Netzen stehen müssen (auch wenn das nicht zwingend eine geografische Trennung bedeutet). Es gibt also Redundanzen, wohin das Auge sieht. In den seltenen Fällen, wo tatsächlich einer dieser Hosts gestört ist, hat ein DNS-Resolver also praktisch immer genug Alternativen, um ans Ziel zu kommen – und das auch performant, weil er natürlich auch die Information, dass ein bestimmter Host aktuell nicht ansprechbar ist, eine gewisse Zeit lang cachen kann.

Und diese ganze Redundanz soll man nun aufgeben, in dem man alle Hosts in seinem Netzwerk über einen einzigen DNS-Resolver führt? Natürlich, natürlich: Wenigstens zwei sollten es schon sein; mehr als drei sind aber normalerweise schon gar nicht möglich. Wenn nun einer ausfällt, gönnt sich die C-Library aber defaultmäßig stolze fünf Sekunden, bis sie den nächsten Nameserver versucht – und versucht es ebenso defaultmäßig immer wieder beim ersten. Zwar lässt sich das in gewissen Grenzen anpassen (hint, hint: options timeout:1 rotate), aber so weit, dass dann z.B. ein Resolver, der ein paar Mal nicht antwortet, für eine gewisse Zeit lang gar nicht mehr angesprochen wird, geht die Logik dann eben doch nicht. Es gibt also zwar auch ein bisschen Redundanz, aber wirklich elegant ist sie nicht. Man braucht also wenn dann schon wirklich hoch verfügbare DNS-Resolver im Netz, denn jeder Ausfall zieht empfindliche Probleme nach sich: Logins via SSH brauchen sekundenlang, weil das System versucht, ein Reverse Lookup für die IP durchzuführen und danach auch noch ein Forward Lookup, um zu schauen, ob’s passt, und wehe dem, der kein HostnameLookups Off in seiner Apache-Konfiguration hat.

Ein lokaler DNS-Resolver hingegen fällt eigentlich nur in einer Situation aus: Nämlich dann, wenn die komplette Maschine ausfällt – und dann ist’s ohnehin egal, bzw. dann hat man ohnehin dringendere Probleme. Vor allem aber beträfe ein reiner Resolver-Ausfall dann nur diesen einen Host – und nicht gleich sämtliche Hosts im Netz, die jenen Resolver benutzen. Insofern fahren wir hier gewissermaßen eine Insel-Strategie: Wenn was kaputtgeht, dann jedenfalls nur sehr begrenzt. Und die Realität zeigt nun im Lauf von Jahren auf Dutzenden von Hosts: Es geht nichts kaputt. Das Setup ist rock-solid.

Als lokalen DNS-Cache benutzen wir hierbei dnscache aus dem djbdns-Paket. Das ist, ohne Frage, für jemanden, der sonst nicht mit Software aus dem DJB-Universum, mit einer gewöhnungsbedürften Installation verbunden, die aber schnell ihre Vorzüge ausspielt – und letztlich punktet dnscache genau wie tinydns eben besonders auch mit hoher Stabilität und Sicherheit. Einige Dinge sind aber zu beachten:

Erstens, errno.

Damit djbdns mit einer aktuellen glibc kompiliert werden kann, muss in conf-cc am Ende des Compileraufrufs -include /usr/include/errno.h angehängt werden; ein Umstand, den Dan Bernstein als Bug der glibc ansieht, auch wenn das durchaus diskutiert werden kann (runterscrollen zu „references“).

Zweitens, Root-Nameserver.

Die mit djbdns mitgelieferte Liste von Root-Nameservern ist nicht mehr ganz aktuell. Das lässt sich aber schnell fixen, weil natürlich jeder der (noch verfügbaren) Root-Nameserver eine aktuelle Liste liefern kann. Mit

dnsip `dnsqr ns . | awk '/^answer: \./ { print $5 }'` >
/service/dnscache/root/servers/@

lässt sie sich fix aktualisieren; ein

svc -h /service/dnscache

informiert dnscache zur Laufzeit über die neue Serverliste.

Drittens, Cache-Größe.

Die Standardkonfiguration gibt dnscache eine Cache-Größe von gerade mal 1 MB. Das hat zur Folge, dass insbesondere auf Systemen, die viele DNS-Anfragen machen, der Cache möglicherweise eine stärkere Fluktuation aufweist als nötig wäre, mit der Folge, dass DNS-Anfragen, die er eigentlich aus dem Cache hätte beantworten können, wenn jener nicht zu früh wieder hätte aufgeräumt werden müssen, erneut stellen muss. Angesichts heutiger RAM-Dimensionen stellt es mit Sicherheit kein Problem dar, dem Cache 10 MB zu verpassen:

echo 10000000 > /service/dnscache/env/CACHESIZE

vergrößert den Cache, und

echo 12000000 > /service/dnscache/env/DATALIMIT

teilt dem dnscache begrenzenden softlimit mit, dass dnscache nun mehr RAM belegen darf. Mit

svc -t /service/dnscache

wird die laufende dnscache-Instanz beendet, woraufhin svscan den Dienst automatisch neu startet. Welche Cache-Größe wirklich angemessen ist, führt hier zu weit; entsprechende Strategien sind aber bereits an verschiedenen Stellen dokumentiert.

Viertens sind wir kürzlich auf einen Patch gestoßen, der dnscache etwas äußerst Nützliches beibringen, nämlich die Möglichkeit, seinen aktuellen Cacheinhalt in eine Datei zu dumpen – und bei einem Start seinen Cache auch erstmal direkt aus jener Datei wieder zu befüllen. Auf diese Weise ist der Cache von dnscache auch nach einem Neustart sofort „warm“, ohne dass es erstmal wieder damit beginnen muss, von den Root-Nameservern her die TLD-spezifischen-Nameserver aufzulösen, und so weiter.

Der ursprüngliche Patch stammt von Efgé und bringt die Funktionalität ein, durch Senden eines SIGALRM dnscache dazu zu veranlassen, seinen Cacheinhalt in eine Datei zu dumpen, deren Name durch eine zuvor gesetzte Umgebungsvariable definiert wird, sowie die Funktionalität, jene Datei beim Starten wieder einzulesen. Nikola Vladov hat auf dieser Basis noch einen erweiterten Patch entwickelt, der zum einen dafür sorgt, dass dnscache beim Beenden automatisch einmal seinen Cache speichert, und der selbiges zudem in einem festlegbaren Rhythmus auch von sich aus tut, was insbesondere für Fälle praktisch ist, in denen ein Host crasht und von daher keine Gelegenheit mehr hat, den Cache zu schreiben wie bei einem sauberen Herunterfahren (nicht, dass sowas ständig vorkäme, aber der kluge Mann baut vor).

Lokal ordentlich zu cachen ist in jedem Fall eine gute Praxis, nicht nur der eigenen Performance wegen, sondern auch, um die Last, die man durch DNS-Anfragen auf anderen Hosts erzeugt, gering zu halten – es gewinnen also beide Seiten. Um so wichtiger ist das, wenn man nicht nur „gewöhnliche“ DNS-Daten abfragt, sondern auch Datenbanken, die via DNS publiziert werden, wie beispielsweise das IP to ASN Mapping der Team Cymru Community Services (mit herzlichem Dank an Daniel für den Tipp) – was für uns der Hauptgrund war, uns eingehender mit der Cache-Dumping-Thematik zu befassen. Der konkrete Anlass dafür reicht aber noch problemlos für einen weiteren Blogpost – stay tuned.

Wildcard-Records mit tinydns

Donnerstag, 11. März 2010

Direkt vorab: Unsere Bitte um Entschuldigung gibt’s weiter unten. Für die technisch Interessierten aber erstmal die Hintergrundgeschichte.

Ein durchaus angenehmes Feature von tinydns, den von uns eingesetzten DNS-Server, ist die Möglichkeit, Wildcard-Records im DNS zu definieren. So legen wir für die meisten Hosts standardmäßig an:

+domain.tld:1.2.3.4
+*.domain.tld:1.2.3.4

Mit letzteren sind dann alle Varianten von www.domain.tld über ftp.domain.tld bis zu admin.domain.tld automatisch abgedeckt. Sollen einzelne Subdomains davon abweichen, definitiert man sie einfach separat:

+admin.domain.tld:1.2.3.5

So löst admin.domain.tld korrekt auf 1.2.3.5 auf; irgendwas.domain.tld aber weiterhin per Wildcard auf 1.2.3.4. So weit, so gut. Die Dokumentation von tinydns-data formuliert dies so:

Information for *.fqdn is provided for every name ending with .fqdn, except names that have their own records

Nun, ich hätte wohl auch das zweite darunterliegende Beispiel lesen sollen, und zwar sehr genau. Es verrät nämlich im Vorbeigehen eine wichtige Falle, über die wir heute gestolpert sind. Unser bisheriges Verständnis ließe sich etwa so formulieren:

Gibt es für den Hostnamen H einen Record vom Typ T, so übergeht dieser alle Wildcard-Records, die ansonsten auf den Hostnamen H und den Typ T gepasst hätten.

Leider ist das ein Irrtum. Die korrekte Formulierung muss nämlich heißen:

Gibt es für den Hostnamen H einen Record irgendeines Typs, so übergeht dieser alle Wildcard-Records, die ansonsten auf den Hostnamen H und irgendeinen Typ gepasst hätten.

Nicht sofort verstanden? Kein Problem. Konkretes Beispiel. Wir hatten in unserer data-Datei Folgendes stehen:

+*.jonaspasche.com:82.98.82.13

Hinter der IP 82.98.82.13 steht einer unserer Server namens wally, der mit vollständigem Namen also wally.jonaspasche.com heißt. Über obigen Wildcard-Record wurde er immer schon korrekt aufgelöst.

Nun hat mein Kollege Christopher heute SSHFP-Records angelegt, so dass nun folgendes in der data-Datei stand:

+*.jonaspasche.com:82.98.82.13
:wally.jonaspasche.com:44:[...]

Und nun die Falle: Fragt man den DNS-Server nun nach dem A-Record von wally.jonaspasche.com, liefert er nichts mehr zurück. Die dahinterstehende Logik ist laut (nun endlich richtig verstandener) Dokumentation: Da es irgendeinen Record gibt, der spezifisch für den Hostnamen wally.jonaspasche.com gilt (nämlich den SSHFP-Record), werden alle ansonsten auf eine Anfrage im Prinzip passenden Wildcard-Records ignoriert, und zwar unabhängig davon, ob sie auch zum gewünschten Typ des angefragten DNS-Records passen oder nicht.

Das finde ich persönlich absolut nicht intuitiv, und ich bin noch nicht ganz mit mir übereingekommen, ob ich es zumindest jetzt, wo ich es verstanden habe, zumindest sinnvoll finde. Ich tendiere zu nein. Aber ich muss zähneknirschend zugeben, dass es eben kein Bug ist, sondern dokumentiert ist – nur wenngleich eben so, dass die entsprechende Stelle missverständlich ist und man schon das Beispiel mit dem Nachsatz sehr genau interpretieren muss.

Da war ich doch fast schon der Meinung, in Sachen tinydns nun wirklich schon seit Jahren ein alter Hase zu sein, dem man so schnell nix mehr vormacht. Denkste. Aber es ist sicherlich nicht das Schlechteste, das eigene Wissen durchaus auch nach Jahren ruhig nochmal hinterfragen zu lassen.

Es tut uns leid.

Wir bitten alle Kunden um Entschuldigung, die aufgrund dieses Vorfalls vorübergehend Probleme mit dem Abruf und Versand von E-Mails hatten. Ärgerlicherweise waren hierbei primär jene Kunden betroffen, die für ihre Mail-Verbindungen SSL-Verschlüsselung benutzen und daher dann auch wally.jonaspasche.com als Hostname – weil das SSL-Zertifikat auf diesen Namen ausgestellt ist. Wer als Mailserver seine eigene Domain angegeben hat, was (an sich „leider“, im konkreten Fall aber „zum Glück“) die überwiegende Mehrheit ist, dürfte keine Probleme festgestellt haben.

Nachdem wir nun den entsprechenden Erkenntnisgewinn hatten, können wir beschämt sagen: Kommt nicht wieder vor. Das nicht. Wir bitten darum, den Vorfall als positives Zeichen dafür anzusehen, dass auch wir nur Menschen und keine Götter sind. Herzlichen Dank.


Impressum