En berättelse om saknade DNS-paket från teknisk support för Google Cloud

Från Google Blog Editor: Har du någonsin undrat hur Google Cloud Technical Solutions (TSE) ingenjörer hanterar dina supportförfrågningar? TSE:s tekniska supportingenjörer är ansvariga för att identifiera och korrigera användarrapporterade problemkällor. Vissa av dessa problem är ganska enkla, men ibland stöter du på en biljett som kräver uppmärksamhet från flera ingenjörer samtidigt. I den här artikeln kommer en av TSE-anställda att berätta om ett mycket knepigt problem från hans senaste praktik - fall av saknade DNS-paket. I den här historien kommer vi att se hur ingenjörerna lyckades lösa situationen och vilka nya saker de lärde sig när de åtgärdade felet. Vi hoppas att den här berättelsen inte bara utbildar dig om en djupt rotad bugg, utan också ger dig insikt i de processer som krävs för att lämna in ett supportärende hos Google Cloud.

En berättelse om saknade DNS-paket från teknisk support för Google Cloud

Felsökning är både en vetenskap och en konst. Det hela börjar med att bygga en hypotes om orsaken till systemets icke-standardiserade beteende, varefter det testas för styrka. Men innan vi formulerar en hypotes måste vi tydligt definiera och exakt formulera problemet. Om frågan låter för vag, måste du analysera allt noggrant; Detta är "konsten" att felsöka.

Under Google Cloud blir sådana processer exponentiellt mer komplexa, eftersom Google Cloud gör sitt bästa för att garantera användarnas integritet. På grund av detta har TSE-ingenjörer inte tillgång till att redigera dina system, och inte heller möjligheten att se konfigurationer så brett som användare gör. Därför, för att testa någon av våra hypoteser, kan vi (ingenjörer) inte snabbt modifiera systemet.

Vissa användare tror att vi kommer att fixa allt som mekanik i en biltjänst och helt enkelt skicka oss ID:t för en virtuell maskin, medan processen i själva verket sker i ett konversationsformat: samla in information, bilda och bekräfta (eller vederlägga) hypoteser, och i slutändan baseras ett beslutsproblem på kommunikation med klienten.

Problemet i fråga

Idag har vi en historia med ett bra slut. En av anledningarna till den framgångsrika lösningen av det föreslagna fallet är en mycket detaljerad och korrekt beskrivning av problemet. Nedan kan du se en kopia av den första biljetten (redigerad för att dölja konfidentiell information):
En berättelse om saknade DNS-paket från teknisk support för Google Cloud
Detta meddelande innehåller mycket användbar information för oss:

  • Specifik virtuell dator specificerad
  • Själva problemet indikeras - DNS fungerar inte
  • Det anges var problemet visar sig - VM och container
  • De steg som användaren tog för att identifiera problemet anges.

Förfrågan registrerades som "P1: Critical Impact - Service Unusable in production", vilket innebär konstant övervakning av situationen 24/7 enligt schemat "Follow the Sun" (du kan läsa mer om prioriteringar av användarförfrågningar), med dess överföring från ett tekniskt supportteam till ett annat med varje tidszonförskjutning. Faktum är att när problemet nådde vårt team i Zürich hade det redan cirklat runt jorden. Vid det här laget hade användaren vidtagit lindrande åtgärder, men var rädd för en upprepning av situationen i produktionen, eftersom grundorsaken ännu inte hade upptäckts.

När biljetten nådde Zürich hade vi redan följande information till hands:

  • Innehåll /etc/hosts
  • Innehåll /etc/resolv.conf
  • Utgång iptables-save
  • Sammansatt av teamet ngrep pcap-fil

Med dessa data var vi redo att påbörja "utredningen" och felsökningsfasen.

Våra första steg

Först och främst kontrollerade vi loggar och status för metadataservern och såg till att den fungerade korrekt. Metadataservern svarar på IP-adressen 169.254.169.254 och ansvarar bland annat för att kontrollera domännamn. Vi dubbelkollade också att brandväggen fungerar korrekt med den virtuella datorn och inte blockerar paket.

Det var något slags konstigt problem: nmap-kontrollen tillbakavisade vår huvudhypotes om förlusten av UDP-paket, så vi kom mentalt på flera alternativ och sätt att kontrollera dem:

  • Släpps paket selektivt? => Kontrollera iptables regler
  • Är den inte för liten? MTU? => Kontrollera utgången ip a show
  • Påverkar problemet bara UDP-paket eller TCP också? => Kör iväg dig +tcp
  • Returneras grävgenererade paket? => Kör iväg tcpdump
  • Fungerar libdns korrekt? => Kör iväg strace för att kontrollera överföringen av paket i båda riktningarna

Här bestämmer vi oss för att ringa användaren för att felsöka problem live.

Under samtalet kan vi kontrollera flera saker:

  • Efter flera kontroller utesluter vi iptables-regler från listan över skäl
  • Vi kontrollerar nätverksgränssnitt och routingtabeller och dubbelkollar att MTU:n är korrekt
  • Det upptäcker vi dig +tcp google.com (TCP) fungerar som det ska, men dig google.com (UDP) fungerar inte
  • Har kört iväg tcpdump medan du arbetar dig, finner vi att UDP-paket returneras
  • Vi kör iväg strace dig google.com och vi ser hur gräva korrekt ringer sendmsg() и recvms(), men den andra avbryts av en timeout

Tyvärr kommer slutet av skiftet och vi tvingas eskalera problemet till nästa tidszon. Förfrågan väckte dock intresse hos vårt team, och en kollega föreslår att man skapar det första DNS-paketet med hjälp av den skrapiga 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())

Detta fragment skapar ett DNS-paket och skickar begäran till metadataservern.

Användaren kör koden, DNS-svaret returneras och applikationen tar emot det, vilket bekräftar att det inte finns några problem på nätverksnivå.

Efter ytterligare en "jorden runt-resa" återvänder förfrågan till vårt team, och jag överför den helt och hållet till mig själv, och tänker att det kommer att vara bekvämare för användaren om förfrågan slutar cirkulera från plats till plats.

Under tiden accepterar användaren att tillhandahålla en ögonblicksbild av systembilden. Detta är mycket goda nyheter: möjligheten att testa systemet själv gör felsökningen mycket snabbare, eftersom jag inte längre behöver be användaren att köra kommandon, skicka mig resultaten och analysera dem, jag kan göra allt själv!

Mina kollegor börjar avundas mig lite. Under lunchen diskuterar vi omställningen, men ingen har någon aning om vad som händer. Lyckligtvis har användaren själv redan vidtagit åtgärder för att mildra konsekvenserna och har ingen brådska, så vi har tid att dissekera problemet. Och eftersom vi har en bild kan vi köra alla tester som intresserar oss. Bra!

Att ta ett steg tillbaka

En av de mest populära intervjufrågorna för positioner som systemingenjör är: "Vad händer när du plingar www.google.com? Frågan är stor, eftersom kandidaten behöver beskriva allt från skalet till användarutrymmet, till systemkärnan och sedan till nätverket. Jag ler: ibland visar sig intervjufrågor vara användbara i verkligheten...

Jag bestämmer mig för att tillämpa denna HR-fråga på ett aktuellt problem. Grovt sett händer följande när du försöker fastställa ett DNS-namn:

  1. Applikationen anropar ett systembibliotek såsom libdns
  2. libdns kontrollerar systemkonfigurationen till vilken DNS-server den ska kontakta (i diagrammet är detta 169.254.169.254, metadataserver)
  3. libdns använder systemanrop för att skapa en UDP-socket (SOKET_DGRAM) och skicka UDP-paket med en DNS-fråga i båda riktningarna
  4. Genom sysctl-gränssnittet kan du konfigurera UDP-stacken på kärnnivå
  5. Kärnan interagerar med hårdvaran för att överföra paket över nätverket via nätverksgränssnittet
  6. Hypervisorn fångar och överför paketet till metadataservern vid kontakt med den
  7. Metadataservern bestämmer genom sin magi DNS-namnet och returnerar ett svar med samma metod

En berättelse om saknade DNS-paket från teknisk support för Google Cloud
Låt mig påminna dig om vilka hypoteser vi redan har övervägt:

Hypotes: Trasiga bibliotek

  • Test 1: kör strace i systemet, kontrollera att dig anropar rätt systemanrop
  • Resultat: Korrekta systemanrop anropas
  • Test 2: använda srapy för att kontrollera om vi kan bestämma namn som går förbi systembibliotek
  • Resultat: vi kan
  • Test 3: kör rpm –V på libdns-paketet och md5sum-biblioteksfiler
  • Resultat: bibliotekskoden är helt identisk med koden i det fungerande operativsystemet
  • Test 4: montera användarens rotsystembild på en virtuell dator utan detta beteende, kör chroot, se om DNS fungerar
  • Resultat: DNS fungerar korrekt

Slutsats baserad på tester: problemet finns inte i biblioteken

Hypotes: Det finns ett fel i DNS-inställningarna

  • Test 1: kontrollera tcpdump och se om DNS-paket skickas och returneras korrekt efter att du kört dig
  • Resultat: paket överförs korrekt
  • Test 2: dubbelkolla servern /etc/nsswitch.conf и /etc/resolv.conf
  • Resultat: allt stämmer

Slutsats baserad på tester: problemet ligger inte i DNS-konfigurationen

Hypotes: kärna skadad

  • Testa: installera ny kärna, kontrollera signatur, starta om
  • Resultat: liknande beteende

Slutsats baserad på tester: kärnan är inte skadad

Hypotes: felaktigt beteende hos användarnätverket (eller hypervisornätverkets gränssnitt)

  • Test 1: Kontrollera dina brandväggsinställningar
  • Resultat: brandväggen skickar DNS-paket på både värden och GCP
  • Test 2: fånga upp trafik och övervaka korrektheten av överföringen och returen av DNS-förfrågningar
  • Resultat: tcpdump bekräftar att värden har tagit emot returpaket

Slutsats baserad på tester: problemet finns inte i nätverket

Hypotes: metadataservern fungerar inte

  • Test 1: kontrollera metadataserverloggarna för avvikelser
  • Resultat: det finns inga anomalier i loggarna
  • Test 2: Förbigå metadataservern via dig @8.8.8.8
  • Resultat: Upplösningen bryts även utan att använda en metadataserver

Slutsats baserad på tester: problemet ligger inte i metadataservern

Summan av kardemumman: vi testade alla delsystem utom körtidsinställningar!

Dyk in i Kernel Runtime Settings

För att konfigurera kärnexekveringsmiljön kan du använda kommandoradsalternativ (grub) eller sysctl-gränssnittet. Jag tittade in /etc/sysctl.conf och tänk bara, jag upptäckte flera anpassade inställningar. Kändes som om jag hade tagit tag i något, jag kasserade alla icke-nätverks- eller icke-tcp-inställningar, kvar med bergsinställningarna net.core. Sedan gick jag till var värdbehörigheterna fanns i den virtuella datorn och började tillämpa inställningarna en efter en, en efter en, med den trasiga virtuella datorn, tills jag hittade den skyldige:

net.core.rmem_default = 2147483647

Här är den, en DNS-brytande konfiguration! Jag hittade mordvapnet. Men varför händer detta? Jag behövde fortfarande ett motiv.

Den grundläggande DNS-paketbuffertstorleken konfigureras via net.core.rmem_default. Ett typiskt värde är någonstans runt 200KiB, men om din server tar emot många DNS-paket kanske du vill öka buffertstorleken. Om bufferten är full när ett nytt paket kommer, till exempel för att applikationen inte bearbetar det tillräckligt snabbt, kommer du att börja förlora paket. Vår klient ökade korrekt buffertstorleken eftersom han var rädd för dataförlust, eftersom han använde en applikation för att samla in mätvärden genom DNS-paket. Värdet han satte var det maximala möjliga: 231-1 (om satt till 231 kommer kärnan att returnera "OVALID ARGUMENT").

Plötsligt insåg jag varför nmap och scapy fungerade korrekt: de använde råa uttag! Raw sockets skiljer sig från vanliga sockets: de kringgår iptables och de är inte buffrade!

Men varför orsakar "för stor buffert" problem? Det fungerar helt klart inte som tänkt.

Vid det här laget kunde jag reproducera problemet på flera kärnor och flera distributioner. Problemet dök redan upp på 3.x-kärnan och nu dök det också upp på 5.x-kärnan.

Faktiskt, vid uppstart

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

DNS slutade fungera.

Jag började leta efter arbetsvärden genom en enkel binär sökalgoritm och fann att systemet fungerade med 2147481343, men detta nummer var en meningslös uppsättning siffror för mig. Jag föreslog att klienten skulle prova det här numret, och han svarade att systemet fungerade med google.com, men gav fortfarande ett fel med andra domäner, så jag fortsatte min undersökning.

Jag har installerat dropwatch, ett verktyg som borde ha använts tidigare: det visar exakt var i kärnan ett paket hamnar. Boven var funktionen udp_queue_rcv_skb. Jag laddade ner kärnkällorna och la till några funktion printk för att spåra var exakt paketet hamnar. Jag hittade snabbt rätt tillstånd if, och bara stirrade på det ett tag, för det var då allt äntligen samlades till en hel bild: 231-1, ett meningslöst nummer, en icke-fungerande domän... Det var en kodbit i __udp_enqueue_schedule_skb:

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

Var uppmärksam:

  • rmem är av typ int
  • size är av typen u16 (osignerad sexton-bitars int) och lagrar paketstorleken
  • sk->sk_rcybuf är av typen int och lagrar buffertstorleken som per definition är lika med värdet i net.core.rmem_default

När sk_rcvbuf närmar sig 231, kan summering av paketstorleken resultera i heltals överflöde. Och eftersom det är en int blir dess värde negativt, så villkoret blir sant när det ska vara falskt (du kan läsa mer om detta på länk).

Felet kan korrigeras på ett trivialt sätt: genom gjutning unsigned int. Jag tillämpade fixen och startade om systemet och DNS fungerade igen.

Smak av seger

Jag vidarebefordrade mina resultat till klienten och skickade LKML kernel patch. Jag är nöjd: varje pusselbit passar ihop, jag kan förklara exakt varför vi observerade det vi observerade, och viktigast av allt, vi kunde hitta en lösning på problemet tack vare vårt lagarbete!

Det är värt att inse att fallet visade sig vara sällsynt, och lyckligtvis får vi sällan så komplexa förfrågningar från användare.

En berättelse om saknade DNS-paket från teknisk support för Google Cloud


Källa: will.com

Lägg en kommentar