Eine Geschichte über fehlende DNS-Pakete vom technischen Support von Google Cloud

Vom Google Blog-Editor: Haben Sie sich jemals gefragt, wie die Ingenieure von Google Cloud Technical Solutions (TSE) Ihre Supportanfragen bearbeiten? Die Techniker des technischen Supports von TSE sind dafür verantwortlich, vom Benutzer gemeldete Problemquellen zu identifizieren und zu beheben. Einige dieser Probleme sind recht einfach, aber manchmal stößt man auf ein Ticket, das die Aufmerksamkeit mehrerer Ingenieure gleichzeitig erfordert. In diesem Artikel erzählt uns einer der TSE-Mitarbeiter von einem sehr kniffligen Problem aus seiner jüngsten Praxis – Fall fehlender DNS-Pakete. In dieser Geschichte werden wir sehen, wie es den Ingenieuren gelungen ist, die Situation zu lösen, und welche neuen Dinge sie bei der Behebung des Fehlers gelernt haben. Wir hoffen, dass diese Geschichte Sie nicht nur über einen tiefgreifenden Fehler aufklärt, sondern Ihnen auch Einblick in die Prozesse gibt, die bei der Einreichung eines Support-Tickets bei Google Cloud ablaufen.

Eine Geschichte über fehlende DNS-Pakete vom technischen Support von Google Cloud

Fehlerbehebung ist sowohl eine Wissenschaft als auch eine Kunst. Alles beginnt damit, eine Hypothese über den Grund für das nicht standardmäßige Verhalten des Systems aufzustellen und es anschließend auf Stärke zu testen. Bevor wir jedoch eine Hypothese formulieren, müssen wir das Problem klar definieren und präzise formulieren. Wenn die Frage zu vage klingt, müssen Sie alles sorgfältig analysieren. Das ist die „Kunst“ der Fehlerbehebung.

Unter Google Cloud werden solche Prozesse exponentiell komplexer, da Google Cloud sein Bestes gibt, um die Privatsphäre seiner Nutzer zu gewährleisten. Aus diesem Grund haben TSE-Ingenieure keinen Zugriff auf die Bearbeitung Ihrer Systeme und auch nicht die Möglichkeit, Konfigurationen so umfassend anzuzeigen wie Benutzer. Daher können wir (Ingenieure) das System nicht schnell ändern, um eine unserer Hypothesen zu testen.

Einige Benutzer glauben, dass wir wie Mechaniker bei einem Autoservice alles reparieren und uns einfach die ID einer virtuellen Maschine senden, während der Prozess in Wirklichkeit in einem Konversationsformat stattfindet: Informationen sammeln, Hypothesen aufstellen und bestätigen (oder widerlegen), und letztendlich basieren Entscheidungsprobleme auf der Kommunikation mit dem Kunden.

Problem in Frage

Heute haben wir eine Geschichte mit einem guten Ende. Einer der Gründe für die erfolgreiche Lösung des vorgeschlagenen Falles ist eine sehr detaillierte und genaue Beschreibung des Problems. Unten sehen Sie eine Kopie des ersten Tickets (bearbeitet, um vertrauliche Informationen auszublenden):
Eine Geschichte über fehlende DNS-Pakete vom technischen Support von Google Cloud
Diese Nachricht enthält viele nützliche Informationen für uns:

  • Spezifische VM angegeben
  • Das Problem selbst wird angezeigt - DNS funktioniert nicht
  • Es wird angezeigt, wo sich das Problem manifestiert – VM und Container
  • Die Schritte, die der Benutzer unternommen hat, um das Problem zu identifizieren, werden angezeigt.

Die Anfrage wurde als „P1: Critical Impact – Service Unusable in Production“ registriert, was eine ständige Überwachung der Situation rund um die Uhr gemäß dem „Follow the Sun“-Schema bedeutet (mehr dazu erfahren Sie hier). Prioritäten der Benutzeranfragen), wobei es bei jedem Zeitzonenwechsel von einem technischen Supportteam zum anderen wechselt. Als das Problem unser Team in Zürich erreichte, war es tatsächlich bereits um die Welt gegangen. Zu diesem Zeitpunkt hatte der Anwender zwar Abhilfemaßnahmen ergriffen, befürchtete jedoch eine Wiederholung der Situation in der Produktion, da die Grundursache noch nicht entdeckt war.

Als das Ticket in Zürich eintraf, lagen uns bereits folgende Informationen vor:

  • Inhalt /etc/hosts
  • Inhalt /etc/resolv.conf
  • Abschluss iptables-save
  • Vom Team zusammengestellt ngrep pcap-Datei

Mit diesen Daten waren wir bereit, mit der „Untersuchungs-“ und Fehlerbehebungsphase zu beginnen.

Unsere ersten Schritte

Zunächst haben wir die Protokolle und den Status des Metadatenservers überprüft und sichergestellt, dass er ordnungsgemäß funktioniert. Der Metadatenserver antwortet auf die IP-Adresse 169.254.169.254 und ist unter anderem für die Steuerung von Domainnamen zuständig. Wir haben außerdem noch einmal überprüft, dass die Firewall korrekt mit der VM funktioniert und keine Pakete blockiert.

Es war ein seltsames Problem: Die nmap-Prüfung widerlegte unsere Haupthypothese über den Verlust von UDP-Paketen, also haben wir uns im Geiste mehrere weitere Optionen und Möglichkeiten ausgedacht, sie zu überprüfen:

  • Werden Pakete selektiv verworfen? => Überprüfen Sie die Iptables-Regeln
  • Ist es nicht zu klein? MTU? => Ausgabe prüfen ip a show
  • Betrifft das Problem nur UDP-Pakete oder auch TCP? => Wegfahren dig +tcp
  • Werden von dig generierte Pakete zurückgegeben? => Wegfahren tcpdump
  • Funktioniert libdns ordnungsgemäß? => Wegfahren strace um die Übertragung von Paketen in beide Richtungen zu überprüfen

Hier entscheiden wir uns, den Benutzer anzurufen, um Probleme live zu beheben.

Während des Anrufs können wir mehrere Dinge überprüfen:

  • Nach mehreren Prüfungen schließen wir iptables-Regeln aus der Liste der Gründe aus
  • Wir überprüfen Netzwerkschnittstellen und Routing-Tabellen und überprüfen noch einmal, ob die MTU korrekt ist
  • Das entdecken wir dig +tcp google.com (TCP) funktioniert wie es sollte, aber dig google.com (UDP) funktioniert nicht
  • Weggefahren tcpdump Während der Arbeit dig, stellen wir fest, dass UDP-Pakete zurückgegeben werden
  • Wir fahren weg strace dig google.com und wir sehen, wie dig richtig ruft sendmsg() и recvms(), der zweite wird jedoch durch eine Zeitüberschreitung unterbrochen

Leider ist die Schicht zu Ende und wir sind gezwungen, das Problem auf die nächste Zeitzone auszuweiten. Die Anfrage weckte jedoch Interesse in unserem Team und ein Kollege schlägt vor, das erste DNS-Paket mithilfe des Scrapy-Python-Moduls zu erstellen.

from scapy.all import *

answer = sr1(IP(dst="169.254.169.254")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname="google.com")),verbose=0)
print ("169.254.169.254", answer[DNS].summary())

Dieses Fragment erstellt ein DNS-Paket und sendet die Anfrage an den Metadatenserver.

Der Benutzer führt den Code aus, die DNS-Antwort wird zurückgegeben und die Anwendung empfängt sie, wodurch bestätigt wird, dass auf Netzwerkebene kein Problem vorliegt.

Nach einer weiteren „Weltreise“ kehrt die Anfrage zu unserem Team zurück, und ich übertrage sie vollständig auf mich selbst, da ich denke, dass es für den Benutzer bequemer ist, wenn die Anfrage nicht mehr von Ort zu Ort kreist.

In der Zwischenzeit erklärt sich der Benutzer freundlicherweise damit einverstanden, einen Schnappschuss des Systemabbilds bereitzustellen. Das sind sehr gute Neuigkeiten: Die Möglichkeit, das System selbst zu testen, beschleunigt die Fehlerbehebung erheblich, da ich den Benutzer nicht mehr auffordern muss, Befehle auszuführen, mir die Ergebnisse zu senden und sie zu analysieren, sondern ich kann alles selbst erledigen!

Meine Kollegen fangen an, mich ein wenig zu beneiden. Während des Mittagessens besprechen wir den Umbau, aber niemand hat eine Ahnung, was los ist. Glücklicherweise hat der Benutzer selbst bereits Maßnahmen zur Abmilderung der Folgen ergriffen und hat es nicht eilig, sodass wir Zeit haben, das Problem zu analysieren. Und da wir ein Image haben, können wir alle Tests durchführen, die uns interessieren. Großartig!

Einen Schritt zurück machen

Eine der beliebtesten Interviewfragen für Positionen als Systemingenieur lautet: „Was passiert, wenn Sie pingen?“ www.google.com? Die Frage ist großartig, da der Kandidat alles beschreiben muss, von der Shell über den Benutzerbereich, den Systemkernel und dann bis zum Netzwerk. Ich lächle: Manchmal erweisen sich Interviewfragen im wirklichen Leben als nützlich ...

Ich beschließe, diese HR-Frage auf ein aktuelles Problem anzuwenden. Wenn Sie versuchen, einen DNS-Namen zu ermitteln, passiert grob gesagt Folgendes:

  1. Die Anwendung ruft eine Systembibliothek wie libdns auf
  2. libdns prüft in der Systemkonfiguration, welchen DNS-Server es kontaktieren soll (im Diagramm ist dies 169.254.169.254, Metadatenserver)
  3. libdns verwendet Systemaufrufe, um einen UDP-Socket (SOKET_DGRAM) zu erstellen und UDP-Pakete mit einer DNS-Abfrage in beide Richtungen zu senden
  4. Über die sysctl-Schnittstelle können Sie den UDP-Stack auf Kernel-Ebene konfigurieren
  5. Der Kernel interagiert mit der Hardware, um Pakete über die Netzwerkschnittstelle über das Netzwerk zu übertragen
  6. Der Hypervisor fängt das Paket ab und übermittelt es bei Kontakt an den Metadatenserver
  7. Der Metadatenserver ermittelt durch seine Magie den DNS-Namen und gibt mit derselben Methode eine Antwort zurück

Eine Geschichte über fehlende DNS-Pakete vom technischen Support von Google Cloud
Ich möchte Sie daran erinnern, welche Hypothesen wir bereits berücksichtigt haben:

Hypothese: Kaputte Bibliotheken

  • Test 1: Führen Sie „strace“ im System aus und prüfen Sie, ob „dig“ die richtigen Systemaufrufe aufruft
  • Ergebnis: Es werden die richtigen Systemaufrufe aufgerufen
  • Test 2: Mit srapy prüfen, ob wir Namen unter Umgehung von Systembibliotheken ermitteln können
  • Ergebnis: Wir können
  • Test 3: Führen Sie rpm –V für das libdns-Paket und die md5sum-Bibliotheksdateien aus
  • Ergebnis: Der Bibliothekscode ist vollständig identisch mit dem Code im funktionierenden Betriebssystem
  • Test 4: Mounten Sie das Root-System-Image des Benutzers auf einer VM ohne dieses Verhalten, führen Sie chroot aus und prüfen Sie, ob DNS funktioniert
  • Ergebnis: DNS funktioniert ordnungsgemäß

Fazit basierend auf Tests: Das Problem liegt nicht in den Bibliotheken

Hypothese: Es liegt ein Fehler in den DNS-Einstellungen vor

  • Test 1: Überprüfen Sie tcpdump und prüfen Sie, ob DNS-Pakete nach dem Ausführen von dig korrekt gesendet und zurückgegeben werden
  • Ergebnis: Pakete werden korrekt übertragen
  • Test 2: Überprüfen Sie den Server noch einmal /etc/nsswitch.conf и /etc/resolv.conf
  • Ergebnis: Alles stimmt

Fazit basierend auf Tests: Das Problem liegt nicht an der DNS-Konfiguration

Hypothese: Kern beschädigt

  • Test: Neuen Kernel installieren, Signatur prüfen, neu starten
  • Ergebnis: ähnliches Verhalten

Fazit basierend auf Tests: Der Kernel ist nicht beschädigt

Hypothese: falsches Verhalten des Benutzernetzwerks (oder der Hypervisor-Netzwerkschnittstelle)

  • Test 1: Überprüfen Sie Ihre Firewall-Einstellungen
  • Ergebnis: Die Firewall leitet DNS-Pakete sowohl auf dem Host als auch auf der GCP weiter
  • Test 2: Datenverkehr abfangen und die Richtigkeit der Übertragung und Rückgabe von DNS-Anfragen überwachen
  • Ergebnis: tcpdump bestätigt, dass der Host Rückgabepakete empfangen hat

Fazit basierend auf Tests: Das Problem liegt nicht im Netzwerk

Hypothese: Der Metadatenserver funktioniert nicht

  • Test 1: Überprüfen Sie die Protokolle des Metadatenservers auf Anomalien
  • Ergebnis: Es gibt keine Anomalien in den Protokollen
  • Test 2: Umgehen Sie den Metadatenserver über dig @8.8.8.8
  • Ergebnis: Die Auflösung ist auch ohne Verwendung eines Metadatenservers fehlerhaft

Fazit basierend auf Tests: Das Problem liegt nicht beim Metadatenserver

Unterm Strich: Wir haben alle Subsysteme außer getestet Laufzeiteinstellungen!

Eintauchen in die Kernel-Laufzeiteinstellungen

Um die Kernel-Ausführungsumgebung zu konfigurieren, können Sie Befehlszeilenoptionen (grub) oder die sysctl-Schnittstelle verwenden. Ich habe nachgeschaut /etc/sysctl.conf Und denken Sie nur, ich habe mehrere benutzerdefinierte Einstellungen entdeckt. Mit dem Gefühl, als hätte ich etwas festgehalten, verwarf ich alle Nicht-Netzwerk- oder Nicht-TCP-Einstellungen und blieb bei den Bergeinstellungen net.core. Dann ging ich zu der Stelle, an der sich die Hostberechtigungen in der VM befanden, und begann, die Einstellungen eine nach der anderen, eine nach der anderen, auf die kaputte VM anzuwenden, bis ich den Übeltäter fand:

net.core.rmem_default = 2147483647

Hier ist sie, eine DNS-zerstörende Konfiguration! Ich habe die Mordwaffe gefunden. Aber warum passiert das? Ich brauchte noch ein Motiv.

Die grundlegende Größe des DNS-Paketpuffers wird über konfiguriert net.core.rmem_default. Ein typischer Wert liegt bei etwa 200 KiB. Wenn Ihr Server jedoch viele DNS-Pakete empfängt, möchten Sie möglicherweise die Puffergröße erhöhen. Wenn der Puffer beim Eintreffen eines neuen Pakets voll ist, beispielsweise weil die Anwendung es nicht schnell genug verarbeitet, kommt es zu Paketverlusten. Unser Kunde hat die Puffergröße richtigerweise erhöht, weil er Angst vor Datenverlust hatte, da er eine Anwendung zum Sammeln von Metriken über DNS-Pakete verwendete. Der von ihm festgelegte Wert war der maximal mögliche Wert: 231-1 (bei Einstellung auf 231 gibt der Kernel „INVALID ARGUMENT“ zurück).

Plötzlich wurde mir klar, warum nmap und scapy richtig funktionierten: Sie verwendeten Raw-Sockets! Raw-Sockets unterscheiden sich von normalen Sockets: Sie umgehen iptables und sind nicht gepuffert!

Aber warum verursacht „Puffer zu groß“ Probleme? Es funktioniert eindeutig nicht wie beabsichtigt.

Zu diesem Zeitpunkt konnte ich das Problem auf mehreren Kerneln und mehreren Distributionen reproduzieren. Das Problem trat bereits im 3.x-Kernel auf und nun auch im 5.x-Kernel.

Tatsächlich beim Start

sysctl -w net.core.rmem_default=$((2**31-1))

DNS funktioniert nicht mehr.

Ich begann mit der Suche nach Arbeitswerten über einen einfachen binären Suchalgorithmus und stellte fest, dass das System mit 2147481343 funktionierte, aber diese Zahl war für mich eine bedeutungslose Zahlenfolge. Ich schlug dem Kunden vor, es mit dieser Nummer zu versuchen, und er antwortete, dass das System mit google.com funktioniere, aber bei anderen Domains immer noch einen Fehler auslöste, also setzte ich meine Untersuchung fort.

ich habe installiert Dropwatch, ein Tool, das schon früher hätte verwendet werden sollen: Es zeigt genau an, wo im Kernel ein Paket landet. Der Schuldige war die Funktion udp_queue_rcv_skb. Ich habe die Kernel-Quellen heruntergeladen und einige hinzugefügt Funktionen printk um zu verfolgen, wo genau das Paket landet. Ich habe schnell den richtigen Zustand gefunden if, und starrte es eine Zeit lang einfach an, denn dann fügte sich endlich alles zu einem Gesamtbild zusammen: 231-1, eine bedeutungslose Zahl, eine nicht funktionierende Domäne ... Es war ein Stück Code in __udp_enqueue_schedule_skb:

if (rmem > (size + sk->sk_rcvbuf))
		goto uncharge_drop;

Beachten Sie:

  • rmem ist vom Typ int
  • size ist vom Typ u16 (unsigned sixteen-bit int) und speichert die Paketgröße
  • sk->sk_rcybuf ist vom Typ int und speichert die Puffergröße, die per Definition gleich dem Wert in ist net.core.rmem_default

Wenn sk_rcvbuf Nähert sich 231, kann die Summierung der Paketgröße dazu führen Ganzzahlüberlauf. Und da es ein int ist, wird sein Wert negativ, sodass die Bedingung wahr wird, obwohl sie falsch sein sollte (mehr darüber können Sie unter lesen). Link).

Der Fehler lässt sich auf triviale Weise beheben: durch Casting unsigned int. Ich habe den Fix angewendet und das System neu gestartet und DNS hat wieder funktioniert.

Geschmack des Sieges

Ich habe meine Erkenntnisse an den Kunden weitergeleitet und verschickt LKML Kernel-Patch. Ich freue mich: Jedes Puzzleteil passt zusammen, ich kann genau erklären, warum wir beobachtet haben, was wir beobachtet haben, und vor allem konnten wir dank unserer Teamarbeit eine Lösung für das Problem finden!

Es ist erwähnenswert, dass sich dieser Fall als selten herausstellte und wir glücklicherweise selten so komplexe Anfragen von Benutzern erhalten.

Eine Geschichte über fehlende DNS-Pakete vom technischen Support von Google Cloud


Source: habr.com

Kommentar hinzufügen