En historie om manglende DNS-pakker fra teknisk støtte for Google Cloud

Fra Google Blog Editor: Har du noen gang lurt på hvordan Google Cloud Technical Solutions (TSE)-ingeniører håndterer støtteforespørslene dine? TSE tekniske støtteingeniører er ansvarlige for å identifisere og korrigere brukerrapporterte kilder til problemer. Noen av disse problemene er ganske enkle, men noen ganger kommer du over en billett som krever oppmerksomhet fra flere ingeniører samtidig. I denne artikkelen vil en av TSE-ansatte fortelle oss om et veldig vanskelig problem fra hans nylige praksis - tilfelle av manglende DNS-pakker. I denne historien skal vi se hvordan ingeniørene klarte å løse situasjonen, og hvilke nye ting de lærte mens de fikset feilen. Vi håper denne historien ikke bare lærer deg om en dyptliggende feil, men at den også gir deg innsikt i prosessene som går med til å sende inn en støttebillett til Google Cloud.

En historie om manglende DNS-pakker fra teknisk støtte for Google Cloud

Feilsøking er både en vitenskap og en kunst. Det hele starter med å bygge en hypotese om årsaken til den ikke-standardiserte oppførselen til systemet, hvoretter det testes for styrke. Men før vi formulerer en hypotese, må vi klart definere og presist formulere problemet. Hvis spørsmålet høres for vagt ut, må du analysere alt nøye; Dette er "kunsten" med feilsøking.

Under Google Cloud blir slike prosesser eksponentielt mer komplekse, ettersom Google Cloud prøver sitt beste for å garantere personvernet til brukerne. På grunn av dette har ikke TSE-ingeniører tilgang til å redigere systemene dine, og heller ikke muligheten til å se konfigurasjoner så bredt som brukere gjør. Derfor, for å teste noen av hypotesene våre, kan vi (ingeniører) ikke raskt endre systemet.

Noen brukere tror at vi vil fikse alt som mekanikk i en biltjeneste, og ganske enkelt sende oss ID-en til en virtuell maskin, mens prosessen i virkeligheten foregår i et samtaleformat: samle inn informasjon, danne og bekrefte (eller tilbakevise) hypoteser, og til syvende og sist er beslutningsproblemer basert på kommunikasjon med klienten.

Problemet det er snakk om

I dag har vi en historie med en god slutt. En av grunnene til den vellykkede løsningen av den foreslåtte saken er en svært detaljert og nøyaktig beskrivelse av problemet. Nedenfor kan du se en kopi av den første billetten (redigert for å skjule konfidensiell informasjon):
En historie om manglende DNS-pakker fra teknisk støtte for Google Cloud
Denne meldingen inneholder mye nyttig informasjon for oss:

  • Spesifikk VM spesifisert
  • Selve problemet er indikert - DNS fungerer ikke
  • Det er angitt hvor problemet manifesterer seg - VM og container
  • Trinnene brukeren tok for å identifisere problemet er angitt.

Forespørselen ble registrert som “P1: Critical Impact - Service Unusable in production”, som betyr konstant overvåking av situasjonen 24/7 i henhold til “Follow the Sun”-ordningen (du kan lese mer om prioriteringer av brukerforespørsler), med overføring fra ett teknisk støtteteam til et annet med hver tidssoneskifte. Faktisk, da problemet nådde teamet vårt i Zürich, hadde det allerede sirklet kloden. På dette tidspunktet hadde brukeren tatt avbøtende tiltak, men var redd for en gjentakelse av situasjonen i produksjonen, siden grunnårsaken ennå ikke var oppdaget.

Da billetten nådde Zürich, hadde vi allerede følgende informasjon tilgjengelig:

  • Innhold /etc/hosts
  • Innhold /etc/resolv.conf
  • Utgang iptables-save
  • Settes sammen av teamet ngrep pcap filen

Med disse dataene var vi klare til å starte "undersøkelse" og feilsøkingsfasen.

Våre første skritt

Først og fremst sjekket vi loggene og statusen til metadataserveren og forsikret oss om at den fungerte riktig. Metadataserveren svarer på IP-adressen 169.254.169.254 og er blant annet ansvarlig for å kontrollere domenenavn. Vi har også dobbeltsjekket at brannmuren fungerer korrekt med VM og ikke blokkerer pakker.

Det var et slags merkelig problem: nmap-sjekken tilbakeviste hovedhypotesen vår om tap av UDP-pakker, så vi mentalt kom opp med flere alternativer og måter å sjekke dem på:

  • Blir pakker droppet selektivt? => Sjekk iptables regler
  • Er den ikke for liten? MTU? => Sjekk utgang ip a show
  • Påvirker problemet bare UDP-pakker eller TCP også? => Kjør bort dig +tcp
  • Returneres gravegenererte pakker? => Kjør bort tcpdump
  • Fungerer libdns riktig? => Kjør bort strace for å kontrollere overføringen av pakker i begge retninger

Her bestemmer vi oss for å ringe brukeren for å feilsøke problemer live.

Under samtalen kan vi sjekke flere ting:

  • Etter flere kontroller ekskluderer vi iptables-regler fra listen over årsaker
  • Vi sjekker nettverksgrensesnitt og rutingtabeller, og dobbeltsjekker at MTU er riktig
  • Det oppdager vi dig +tcp google.com (TCP) fungerer som det skal, men dig google.com (UDP) fungerer ikke
  • Etter å ha kjørt bort tcpdump det fungerer fortsatt dig, finner vi at UDP-pakker blir returnert
  • Vi kjører bort strace dig google.com og vi ser hvordan grave kaller riktig sendmsg() и recvms(), men den andre blir avbrutt av et tidsavbrudd

Dessverre kommer slutten av skiftet og vi er tvunget til å eskalere problemet til neste tidssone. Forespørselen vekket imidlertid interesse i teamet vårt, og en kollega foreslår å lage den første DNS-pakken ved å bruke den skrapete Python-modulen.

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

Dette fragmentet oppretter en DNS-pakke og sender forespørselen til metadataserveren.

Brukeren kjører koden, DNS-svaret returneres, og applikasjonen mottar den, og bekrefter at det ikke er noe problem på nettverksnivå.

Etter nok en "jorden rundt" kommer forespørselen tilbake til teamet vårt, og jeg overfører den fullstendig til meg selv, og tenker at det vil være mer praktisk for brukeren hvis forespørselen slutter å sirkle fra sted til sted.

I mellomtiden godtar brukeren å gi et øyeblikksbilde av systembildet. Dette er veldig gode nyheter: muligheten til å teste systemet selv gjør feilsøkingen mye raskere, fordi jeg ikke lenger trenger å be brukeren om å kjøre kommandoer, sende meg resultatene og analysere dem, jeg kan gjøre alt selv!

Kollegene mine begynner å misunne meg litt. Over lunsj diskuterer vi konverteringen, men ingen aner hva som skjer. Heldigvis har brukeren selv allerede iverksatt tiltak for å dempe konsekvensene og har ikke hastverk, så vi har tid til å dissekere problemet. Og siden vi har et bilde, kan vi kjøre alle tester som interesserer oss. Flott!

Tar et skritt tilbake

Et av de mest populære intervjuspørsmålene for systemingeniørstillinger er: «Hva skjer når du pinger www.google.com? Spørsmålet er stort, siden kandidaten trenger å beskrive alt fra skallet til brukerplass, til systemkjernen og deretter til nettverket. Jeg smiler: noen ganger viser intervjuspørsmål seg å være nyttige i det virkelige liv...

Jeg bestemmer meg for å bruke dette HR-spørsmålet på et nåværende problem. Grovt sett, når du prøver å bestemme et DNS-navn, skjer følgende:

  1. Applikasjonen kaller opp et systembibliotek som libdns
  2. libdns sjekker systemkonfigurasjonen til hvilken DNS-server den skal kontakte (i diagrammet er dette 169.254.169.254, metadataserver)
  3. libdns bruker systemanrop til å lage en UDP-socket (SOKET_DGRAM) og sende UDP-pakker med en DNS-spørring i begge retninger
  4. Gjennom sysctl-grensesnittet kan du konfigurere UDP-stakken på kjernenivå
  5. Kjernen samhandler med maskinvaren for å overføre pakker over nettverket via nettverksgrensesnittet
  6. Hypervisoren fanger opp og overfører pakken til metadataserveren ved kontakt med den
  7. Metadataserveren bestemmer ved sin magi DNS-navnet og returnerer et svar med samme metode

En historie om manglende DNS-pakker fra teknisk støtte for Google Cloud
La meg minne deg på hvilke hypoteser vi allerede har vurdert:

Hypotese: Ødelagte biblioteker

  • Test 1: kjør strace i systemet, sjekk at dig kaller de riktige systemkallene
  • Resultat: Riktige systemanrop blir kalt
  • Test 2: bruke srapy for å sjekke om vi kan bestemme navn som går utenom systembiblioteker
  • Resultat: vi kan
  • Test 3: kjør rpm –V på libdns-pakken og md5sum-bibliotekfilene
  • Resultat: bibliotekkoden er helt identisk med koden i det fungerende operativsystemet
  • Test 4: monter brukerens rotsystembilde på en VM uten denne oppførselen, kjør chroot, se om DNS fungerer
  • Resultat: DNS fungerer som det skal

Konklusjon basert på tester: problemet er ikke i bibliotekene

Hypotese: Det er en feil i DNS-innstillingene

  • Test 1: sjekk tcpdump og se om DNS-pakker sendes og returneres riktig etter å ha kjørt dig
  • Resultat: pakker sendes riktig
  • Test 2: dobbeltsjekk på serveren /etc/nsswitch.conf и /etc/resolv.conf
  • Resultat: alt er riktig

Konklusjon basert på tester: problemet er ikke med DNS-konfigurasjonen

Hypotese: kjerne skadet

  • Test: installer ny kjerne, sjekk signatur, start på nytt
  • Resultat: lignende oppførsel

Konklusjon basert på tester: kjernen er ikke skadet

Hypotese: feil oppførsel av brukernettverket (eller hypervisornettverksgrensesnittet)

  • Test 1: Sjekk brannmurinnstillingene dine
  • Resultat: brannmuren sender DNS-pakker på både verten og GCP
  • Test 2: fange opp trafikk og overvåke riktigheten av overføring og retur av DNS-forespørsler
  • Resultat: tcpdump bekrefter at verten har mottatt returpakker

Konklusjon basert på tester: problemet er ikke i nettverket

Hypotese: metadataserveren fungerer ikke

  • Test 1: sjekk metadataserverloggene for uregelmessigheter
  • Resultat: det er ingen uregelmessigheter i loggene
  • Test 2: Omgå metadataserveren via dig @8.8.8.8
  • Resultat: Oppløsningen er ødelagt selv uten bruk av en metadataserver

Konklusjon basert på tester: problemet er ikke med metadataserveren

Den nederste linjen: vi testet alle delsystemer unntatt kjøretidsinnstillinger!

Dykk inn i kjernens kjøretidsinnstillinger

For å konfigurere kjernekjøringsmiljøet kan du bruke kommandolinjealternativer (grub) eller sysctl-grensesnittet. jeg så inn /etc/sysctl.conf og tenk, jeg oppdaget flere egendefinerte innstillinger. Jeg følte det som om jeg hadde tatt tak i noe, og jeg forkastet alle innstillinger som ikke er nettverk eller ikke-tcp, forblir med fjellinnstillingene net.core. Så gikk jeg til der vertstillatelsene var i VM-en og begynte å bruke innstillingene en etter en, en etter en, med den ødelagte VM-en, til jeg fant den skyldige:

net.core.rmem_default = 2147483647

Her er den, en DNS-brytende konfigurasjon! Jeg fant drapsvåpenet. Men hvorfor skjer dette? Jeg trengte fortsatt et motiv.

Den grunnleggende DNS-pakkebufferstørrelsen konfigureres via net.core.rmem_default. En typisk verdi er et sted rundt 200KiB, men hvis serveren din mottar mange DNS-pakker, kan det være lurt å øke bufferstørrelsen. Hvis bufferen er full når en ny pakke kommer, for eksempel fordi applikasjonen ikke behandler den raskt nok, vil du begynne å miste pakker. Vår klient økte bufferstørrelsen riktig fordi han var redd for tap av data, siden han brukte et program for å samle inn beregninger gjennom DNS-pakker. Verdien han satte var den maksimale mulige: 231-1 (hvis satt til 231, vil kjernen returnere "UGYLDIG ARGUMENT").

Plutselig skjønte jeg hvorfor nmap og scapy fungerte riktig: de brukte rå sockets! Raw sockets er forskjellige fra vanlige sockets: de omgår iptables, og de er ikke bufret!

Men hvorfor skaper "buffer for stor" problemer? Det fungerer tydeligvis ikke etter hensikten.

På dette tidspunktet kunne jeg reprodusere problemet på flere kjerner og flere distribusjoner. Problemet dukket allerede opp på 3.x-kjernen og nå dukket det også opp på 5.x-kjernen.

Faktisk ved oppstart

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

DNS sluttet å virke.

Jeg begynte å lete etter arbeidsverdier gjennom en enkel binær søkealgoritme og fant ut at systemet fungerte med 2147481343, men dette tallet var et meningsløst sett med tall for meg. Jeg foreslo at klienten skulle prøve dette nummeret, og han svarte at systemet fungerte med google.com, men fortsatt ga en feilmelding med andre domener, så jeg fortsatte etterforskningen.

jeg har installert dropwatch, et verktøy som burde vært brukt tidligere: det viser nøyaktig hvor i kjernen en pakke havner. Den skyldige var funksjonen udp_queue_rcv_skb. Jeg lastet ned kjernekildene og la til noen funksjon printk for å spore nøyaktig hvor pakken havner. Jeg fant raskt den rette tilstanden if, og rett og slett stirret på det en stund, for det var da alt endelig kom sammen til et helt bilde: 231-1, et meningsløst tall, et ikke-fungerende domene... Det var et stykke kode i __udp_enqueue_schedule_skb:

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

Vennligst merk:

  • rmem er av typen int
  • size er av typen u16 (unsigned sixteen-bit int) og lagrer pakkestørrelsen
  • sk->sk_rcybuf er av typen int og lagrer bufferstørrelsen som per definisjon er lik verdien i net.core.rmem_default

Når sk_rcvbuf nærmer seg 231, kan summering av pakkestørrelsen resultere i heltallsoverløp. Og siden det er en int, blir verdien negativ, så betingelsen blir sann når den skal være usann (du kan lese mer om dette på link).

Feilen kan rettes på en triviell måte: ved støping unsigned int. Jeg brukte rettelsen og startet systemet på nytt og DNS fungerte igjen.

Smak av seier

Jeg videresendte mine funn til klienten og sendte LKML kjerneoppdatering. Jeg er fornøyd: hver brikke i puslespillet passer sammen, jeg kan forklare nøyaktig hvorfor vi observerte det vi observerte, og viktigst av alt, vi var i stand til å finne en løsning på problemet takket være teamarbeidet vårt!

Det er verdt å erkjenne at saken viste seg å være sjelden, og heldigvis får vi sjelden så komplekse forespørsler fra brukere.

En historie om manglende DNS-pakker fra teknisk støtte for Google Cloud


Kilde: www.habr.com

Legg til en kommentar