Een verhaal over ontbrekende DNS-pakketten van de technische ondersteuning van Google Cloud

Van de Google Blog-editor: Heeft u zich ooit afgevraagd hoe de technici van Google Cloud Technical Solutions (TSE) omgaan met uw ondersteuningsverzoeken? TSE Technical Support Engineers zijn verantwoordelijk voor het identificeren en corrigeren van door de gebruiker gerapporteerde bronnen van problemen. Sommige van deze problemen zijn vrij eenvoudig, maar soms kom je een ticket tegen dat de aandacht van meerdere technici tegelijk vereist. In dit artikel zal een van de TSE-medewerkers ons vertellen over een heel lastig probleem uit zijn recente praktijk: geval van ontbrekende DNS-pakketten. In dit verhaal zullen we zien hoe de ingenieurs erin slaagden de situatie op te lossen en welke nieuwe dingen ze leerden tijdens het oplossen van de fout. We hopen dat dit verhaal u niet alleen informeert over een diepgewortelde bug, maar u ook inzicht geeft in de processen die gepaard gaan met het indienen van een supportticket bij Google Cloud.

Een verhaal over ontbrekende DNS-pakketten van de technische ondersteuning van Google Cloud

Het oplossen van problemen is zowel een wetenschap als een kunst. Het begint allemaal met het bouwen van een hypothese over de reden voor het afwijkende gedrag van het systeem, waarna het op sterkte wordt getest. Voordat we echter een hypothese formuleren, moeten we het probleem duidelijk definiëren en nauwkeurig formuleren. Als de vraag te vaag klinkt, zul je alles zorgvuldig moeten analyseren; Dit is de ‘kunst’ van het oplossen van problemen.

Onder Google Cloud worden dergelijke processen exponentieel complexer, omdat Google Cloud zijn best doet om de privacy van zijn gebruikers te garanderen. Hierdoor hebben TSE-ingenieurs geen toegang om uw systemen te bewerken, noch de mogelijkheid om configuraties zo breed te bekijken als gebruikers. Daarom kunnen wij (ingenieurs) het systeem niet snel aanpassen om onze hypothesen te testen.

Sommige gebruikers geloven dat we alles zullen repareren zoals de mechanica van een autoservice, en ons eenvoudigweg de ID van een virtuele machine zullen sturen, terwijl het proces in werkelijkheid plaatsvindt in een conversatieformaat: het verzamelen van informatie, het vormen en bevestigen (of weerleggen) van hypothesen, en uiteindelijk zijn beslissingsproblemen gebaseerd op communicatie met de klant.

Probleem in kwestie

Vandaag hebben we een verhaal met een goed einde. Een van de redenen voor de succesvolle oplossing van de voorgestelde zaak is een zeer gedetailleerde en nauwkeurige beschrijving van het probleem. Hieronder ziet u een kopie van het eerste ticket (bewerkt om vertrouwelijke informatie te verbergen):
Een verhaal over ontbrekende DNS-pakketten van de technische ondersteuning van Google Cloud
Dit bericht bevat veel nuttige informatie voor ons:

  • Specifieke VM gespecificeerd
  • Het probleem zelf wordt aangegeven: DNS werkt niet
  • Er wordt aangegeven waar het probleem zich manifesteert: VM en container
  • De stappen die de gebruiker heeft ondernomen om het probleem te identificeren, worden aangegeven.

Het verzoek werd geregistreerd als “P1: Critical Impact - Service Unusable in production”, wat betekent dat de situatie 24/7 constant wordt gemonitord volgens het “Follow the Sun”-schema (lees meer over prioriteiten van gebruikersverzoeken), met de overdracht van het ene technische ondersteuningsteam naar het andere bij elke tijdzoneverschuiving. Tegen de tijd dat het probleem ons team in Zürich bereikte, had het de wereld al rondgereisd. Tegen die tijd had de gebruiker beperkende maatregelen genomen, maar was hij bang voor een herhaling van de situatie in de productie, omdat de oorzaak nog niet was ontdekt.

Tegen de tijd dat het ticket Zürich bereikte, hadden we de volgende informatie al bij de hand:

  • Inhoud /etc/hosts
  • Inhoud /etc/resolv.conf
  • Uitgang iptables-save
  • Samengesteld door het team ngrep pcap-bestand

Met deze gegevens waren we klaar om aan de fase van ‘onderzoek’ en probleemoplossing te beginnen.

Onze eerste stappen

Allereerst hebben we de logs en status van de metadataserver gecontroleerd en ervoor gezorgd dat deze correct werkte. De metadataserver reageert op het IP-adres 169.254.169.254 en is onder meer verantwoordelijk voor het beheer van domeinnamen. We hebben ook dubbel gecontroleerd of de firewall correct werkt met de VM en geen pakketten blokkeert.

Het was een vreemd probleem: de nmap-controle weerlegde onze hoofdhypothese over het verlies van UDP-pakketten, dus bedachten we mentaal nog een aantal opties en manieren om ze te controleren:

  • Worden pakketten selectief verwijderd? => Controleer iptables-regels
  • Is het niet te klein? MTU? => Controleer uitvoer ip a show
  • Heeft het probleem alleen betrekking op UDP-pakketten of ook op TCP? => Rijd weg dig +tcp
  • Worden door dig gegenereerde pakketten geretourneerd? => Rijd weg tcpdump
  • Werkt libdns correct? => Rijd weg strace om de verzending van pakketten in beide richtingen te controleren

Hier besluiten we de gebruiker te bellen om problemen live op te lossen.

Tijdens het gesprek kunnen wij een aantal zaken controleren:

  • Na verschillende controles sluiten we iptables-regels uit van de lijst met redenen
  • We controleren netwerkinterfaces en routeringstabellen en controleren nogmaals of de MTU correct is
  • Wij ontdekken dat dig +tcp google.com (TCP) werkt zoals het hoort, maar dig google.com (UDP) werkt niet
  • Weggereden zijn tcpdump tijdens het werk dig, zien we dat UDP-pakketten worden geretourneerd
  • Wij rijden weg strace dig google.com en we zien hoe dig correct roept sendmsg() и recvms(), maar de tweede wordt onderbroken door een time-out

Helaas komt het einde van de dienst en zijn we genoodzaakt het probleem te escaleren naar de volgende tijdzone. Het verzoek wekte echter interesse bij ons team, en een collega stelt voor om het eerste DNS-pakket te maken met behulp van de scrapy Python-module.

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())

Dit fragment creëert een DNS-pakket en stuurt het verzoek naar de metadataserver.

De gebruiker voert de code uit, het DNS-antwoord wordt geretourneerd en de applicatie ontvangt deze, waarmee wordt bevestigd dat er geen probleem is op netwerkniveau.

Na nog een 'reis rond de wereld' keert het verzoek terug naar ons team, en ik draag het volledig over aan mezelf, in de veronderstelling dat het voor de gebruiker handiger zal zijn als het verzoek niet meer van plaats naar plaats circuleert.

In de tussentijd gaat de gebruiker ermee akkoord een momentopname van de systeemimage te verstrekken. Dit is heel goed nieuws: de mogelijkheid om het systeem zelf te testen maakt het oplossen van problemen veel sneller, omdat ik de gebruiker niet langer hoef te vragen opdrachten uit te voeren, mij de resultaten te sturen en te analyseren, ik kan alles zelf doen!

Mijn collega’s beginnen mij een beetje te benijden. Tijdens de lunch bespreken we de ombouw, maar niemand heeft enig idee wat er aan de hand is. Gelukkig heeft de gebruiker zelf al maatregelen genomen om de gevolgen te verzachten en heeft hij geen haast, dus we hebben tijd om het probleem te ontleden. En omdat we een beeld hebben, kunnen we alle tests uitvoeren die ons interesseren. Geweldig!

Een stapje terug doen

Een van de meest populaire sollicitatievragen voor functies als systeemingenieur is: “Wat gebeurt er als je pingt www.google.com? De vraag is geweldig, omdat de kandidaat alles moet beschrijven, van de shell tot de gebruikersruimte, tot de systeemkernel en vervolgens tot het netwerk. Ik glimlach: soms blijken interviewvragen in het echte leven nuttig te zijn...

Ik besluit deze HR-vraag toe te passen op een actueel probleem. Grofweg gebeurt het volgende wanneer u een DNS-naam probeert te bepalen:

  1. De applicatie roept een systeembibliotheek aan, zoals libdns
  2. libdns controleert de systeemconfiguratie waarmee de DNS-server contact moet maken (in het diagram is dit 169.254.169.254, metadataserver)
  3. libdns gebruikt systeemaanroepen om een ​​UDP-socket (SOKET_DGRAM) te maken en UDP-pakketten met een DNS-query in beide richtingen te verzenden
  4. Via de sysctl-interface kunt u de UDP-stack op kernelniveau configureren
  5. De kernel werkt samen met de hardware om pakketten via de netwerkinterface over het netwerk te verzenden
  6. De hypervisor vangt het pakket op en verzendt het naar de metadataserver wanneer er contact mee wordt gemaakt
  7. De metadataserver bepaalt op magische wijze de DNS-naam en retourneert een antwoord met dezelfde methode

Een verhaal over ontbrekende DNS-pakketten van de technische ondersteuning van Google Cloud
Laat me u eraan herinneren welke hypothesen we al hebben overwogen:

Hypothese: kapotte bibliotheken

  • Test 1: voer strace uit in het systeem, controleer of dig de juiste systeemaanroepen aanroept
  • Resultaat: Er worden correcte systeemoproepen gebeld
  • Test 2: met behulp van sapy controleren of we namen kunnen bepalen die de systeembibliotheken omzeilen
  • Resultaat: we kunnen het
  • Test 3: voer rpm –V uit op het libdns-pakket en de md5sum-bibliotheekbestanden
  • Resultaat: de bibliotheekcode is volledig identiek aan de code in het werkende besturingssysteem
  • Test 4: mount de rootsysteemimage van de gebruiker op een VM zonder dit gedrag, voer chroot uit, kijk of DNS werkt
  • Resultaat: DNS werkt correct

Conclusie op basis van testen: het probleem ligt niet in de bibliotheken

Hypothese: Er is een fout in de DNS-instellingen

  • Test 1: controleer tcpdump en kijk of DNS-pakketten correct worden verzonden en geretourneerd na het uitvoeren van dig
  • Resultaat: pakketten worden correct verzonden
  • Test 2: dubbele controle op de server /etc/nsswitch.conf и /etc/resolv.conf
  • Resultaat: alles klopt

Conclusie op basis van testen: het probleem ligt niet bij de DNS-configuratie

Hypothese: kern beschadigd

  • Test: nieuwe kernel installeren, handtekening controleren, opnieuw opstarten
  • Resultaat: soortgelijk gedrag

Conclusie op basis van testen: de kernel is niet beschadigd

Hypothese: onjuist gedrag van het gebruikersnetwerk (of hypervisor-netwerkinterface)

  • Test 1: Controleer uw firewallinstellingen
  • Resultaat: de firewall geeft DNS-pakketten door op zowel de host als GCP
  • Test 2: verkeer onderscheppen en de juistheid van de verzending en retourzending van DNS-verzoeken bewaken
  • Resultaat: tcpdump bevestigt dat de host retourpakketten heeft ontvangen

Conclusie op basis van testen: het probleem ligt niet in het netwerk

Hypothese: de metadataserver werkt niet

  • Test 1: controleer de logbestanden van de metadataserver op afwijkingen
  • Resultaat: er zijn geen afwijkingen in de logboeken
  • Test 2: Omzeil de metadataserver via dig @8.8.8.8
  • Resultaat: De resolutie is zelfs zonder gebruik van een metadataserver verbroken

Conclusie op basis van testen: het probleem ligt niet bij de metadataserver

De bottom line: we hebben alle subsystemen getest behalve runtime-instellingen!

Duiken in de Kernel Runtime-instellingen

Om de kerneluitvoeringsomgeving te configureren, kunt u opdrachtregelopties (grub) of de sysctl-interface gebruiken. Ik keek naar binnen /etc/sysctl.conf en bedenk eens: ik heb verschillende aangepaste instellingen ontdekt. Omdat ik het gevoel had dat ik me ergens aan had vastgegrepen, gooide ik alle niet-netwerk- of niet-tcp-instellingen weg en bleef bij de berginstellingen net.core. Vervolgens ging ik naar de plek waar de hostrechten zich in de VM bevonden en begon de instellingen één voor één, de een na de ander, toe te passen op de kapotte VM, totdat ik de boosdoener vond:

net.core.rmem_default = 2147483647

Hier is het, een DNS-brekende configuratie! Ik heb het moordwapen gevonden. Maar waarom gebeurt dit? Ik had nog steeds een motief nodig.

De basisgrootte van de DNS-pakketbuffer wordt geconfigureerd via net.core.rmem_default. Een typische waarde ligt ergens rond de 200KiB, maar als uw server veel DNS-pakketten ontvangt, wilt u wellicht de buffergrootte vergroten. Als de buffer vol is als er een nieuw pakket binnenkomt, bijvoorbeeld omdat de applicatie het niet snel genoeg verwerkt, dan zul je pakketten gaan verliezen. Onze klant heeft de buffergrootte correct vergroot omdat hij bang was voor gegevensverlies, omdat hij een applicatie gebruikte voor het verzamelen van statistieken via DNS-pakketten. De waarde die hij instelde was de maximaal mogelijke: 231-1 (indien ingesteld op 231, retourneert de kernel “INVALID ARGUMENT”).

Plotseling besefte ik waarom nmap en scapy correct werkten: ze gebruikten raw sockets! Raw sockets zijn anders dan gewone sockets: ze omzeilen iptables en zijn niet gebufferd!

Maar waarom veroorzaakt "buffer te groot" problemen? Het werkt duidelijk niet zoals bedoeld.

Op dit punt kon ik het probleem reproduceren op meerdere kernels en meerdere distributies. Het probleem verscheen al op de 3.x-kernel en nu ook op de 5.x-kernel.

Bij het opstarten inderdaad

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

DNS werkt niet meer.

Ik ging op zoek naar werkwaarden via een eenvoudig binair zoekalgoritme en ontdekte dat het systeem werkte met 2147481343, maar dit getal was voor mij een betekenisloze reeks getallen. Ik stelde de klant voor om dit nummer te proberen, en hij antwoordde dat het systeem werkte met google.com, maar nog steeds een foutmelding gaf bij andere domeinen, dus vervolgde ik mijn onderzoek.

Ik heb geïnstalleerd dropwatch, een tool die eerder gebruikt had moeten worden: het laat precies zien waar in de kernel een pakketje terechtkomt. De boosdoener was de functie udp_queue_rcv_skb. Ik heb de kernelbronnen gedownload en er een paar toegevoegd functie printk om bij te houden waar het pakketje precies terechtkomt. Ik vond snel de juiste voorwaarde if, en staarde er gewoon een tijdje naar, want op dat moment kwam alles eindelijk samen in een heel plaatje: 231-1, een betekenisloos getal, een niet-werkend domein... Het was een stukje code in __udp_enqueue_schedule_skb:

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

Let op:

  • rmem is van het type int
  • size is van het type u16 (unsigned zestien-bit int) en slaat de pakketgrootte op
  • sk->sk_rcybuf is van het type int en slaat de buffergrootte op die per definitie gelijk is aan de waarde in net.core.rmem_default

Wanneer sk_rcvbuf benaderingen 231, kan het optellen van de pakketgrootte resulteren in gehele overloop. En omdat het een int is, wordt de waarde ervan negatief, dus de voorwaarde wordt waar terwijl deze onwaar zou moeten zijn (je kunt hier meer over lezen op link).

De fout kan op een triviale manier worden gecorrigeerd: door te gieten unsigned int. Ik heb de oplossing toegepast en het systeem opnieuw opgestart en DNS werkte weer.

Smaak van de overwinning

Ik heb mijn bevindingen doorgestuurd naar de opdrachtgever en opgestuurd lkml kernel-patch. Ik ben blij: alle stukjes van de puzzel passen in elkaar, ik kan precies uitleggen waarom we hebben waargenomen wat we hebben waargenomen en het allerbelangrijkste: dankzij ons teamwerk hebben we een oplossing voor het probleem kunnen vinden!

Het is de moeite waard om te erkennen dat dit geval zeldzaam bleek te zijn, en gelukkig ontvangen we zelden zulke complexe verzoeken van gebruikers.

Een verhaal over ontbrekende DNS-pakketten van de technische ondersteuning van Google Cloud


Bron: www.habr.com

Voeg een reactie