Priča o nedostajućim DNS paketima iz Google Cloud tehničke podrške
Iz Google Blog Editora: Jeste li se ikada zapitali kako inženjeri Google Cloud Technical Solutions (TSE) rješavaju vaše zahtjeve za podršku? TSE inženjeri tehničke podrške odgovorni su za identifikaciju i ispravljanje izvora problema koje su prijavili korisnici. Neki od ovih problema su prilično jednostavni, ali ponekad naiđete na kartu koja zahtijeva pažnju nekoliko inženjera odjednom. U ovom članku, jedan od zaposlenih u TSE-u će nam ispričati jedan veoma škakljiv problem iz svoje nedavne prakse - slučaj da nedostaju DNS paketi. U ovoj priči ćemo vidjeti kako su inženjeri uspjeli riješiti nastalu situaciju, te šta su novo naučili dok su otklanjali grešku. Nadamo se da vas ova priča ne samo obrazuje o duboko ukorijenjenoj grešci, već vam daje i uvid u procese koji se odnose na podnošenje zahtjeva za podršku u Google Cloud.
Rješavanje problema je i nauka i umjetnost. Sve počinje izgradnjom hipoteze o razlogu nestandardnog ponašanja sistema, nakon čega se testira na snagu. Međutim, prije nego što formulišemo hipotezu, moramo jasno definirati i precizno formulirati problem. Ako pitanje zvuči previše nejasno, onda ćete morati sve pažljivo analizirati; Ovo je "umetnost" rešavanja problema.
Pod Google Cloud-om, takvi procesi postaju eksponencijalno složeniji, jer Google Cloud pokušava da garantuje privatnost svojih korisnika. Zbog toga, TSE inženjeri nemaju pristup uređivanju vaših sistema, niti mogućnost pregleda konfiguracija tako široko kao korisnici. Stoga, da bismo testirali bilo koju od naših hipoteza, mi (inženjeri) ne možemo brzo modificirati sistem.
Neki korisnici vjeruju da ćemo sve popraviti kao mehaniku u autoservisu, i jednostavno nam poslati id virtuelne mašine, dok se u stvarnosti proces odvija u konverzacijskom formatu: prikupljanje informacija, formiranje i potvrđivanje (ili pobijanje) hipoteza, i, na kraju, problemi odlučivanja se zasnivaju na komunikaciji sa klijentom.
Problem je u pitanju
Danas imamo priču sa dobrim završetkom. Jedan od razloga za uspješno rješavanje predloženog slučaja je vrlo detaljan i tačan opis problema. Ispod možete vidjeti kopiju prve ulaznice (uređene da sakriju povjerljive informacije):
Ova poruka sadrži mnogo korisnih informacija za nas:
Specifičan VM je specificiran
Sam problem je naznačen - DNS ne radi
Naznačeno je gdje se problem manifestira - VM i kontejner
Naznačeni su koraci koje je korisnik poduzeo da identifikuje problem.
Zahtjev je zaveden kao “P1: Kritičan uticaj - usluga neupotrebljiva u proizvodnji”, što znači stalno praćenje situacije 24/7 po šemi “Prati sunce” (više o prioriteti korisničkih zahtjeva), sa njegovim prijenosom iz jednog tima tehničke podrške u drugi sa svakim pomjeranjem vremenske zone. Zapravo, dok je problem stigao do našeg tima u Cirihu, već je obišao svijet. Do tog trenutka korisnik je poduzeo mjere ublažavanja, ali se bojao ponavljanja situacije u proizvodnji, jer uzrok još nije otkriven.
Dok je karta stigla u Cirih, već smo imali pri ruci sljedeće informacije:
Sadržaj /etc/hosts
Sadržaj /etc/resolv.conf
zaključak iptables-save
Sastavio tim ngrep pcap fajl
Sa ovim podacima bili smo spremni da započnemo fazu “istrage” i otklanjanja problema.
Naši prvi koraci
Prije svega, provjerili smo logove i status servera metapodataka i uvjerili se da radi ispravno. Server metapodataka odgovara na IP adresu 169.254.169.254 i, između ostalog, odgovoran je za kontrolu imena domena. Također smo dvaput provjerili da li firewall radi ispravno sa VM-om i da ne blokira pakete.
Bio je to nekakav čudan problem: nmap provjera je opovrgla našu glavnu hipotezu o gubitku UDP paketa, pa smo mentalno smislili još nekoliko opcija i načina da ih provjerimo:
Da li se paketi ispuštaju selektivno? => Provjerite iptables pravila
Nije li premalo? MTU? => Provjerite izlaz ip a show
Da li problem utiče samo na UDP pakete ili TCP? => Odvezite se dig +tcp
Da li se vraćaju dig generisani paketi? => Odvezite se tcpdump
Da li libdns radi ispravno? => Odvezite se strace za provjeru prijenosa paketa u oba smjera
Ovdje odlučujemo da pozovemo korisnika za rješavanje problema uživo.
Tokom poziva u mogućnosti smo provjeriti nekoliko stvari:
Nakon nekoliko provjera isključujemo iptables pravila sa liste razloga
Provjeravamo mrežna sučelja i tablice rutiranja i još jednom provjeravamo da li je MTU ispravan
Otkrivamo to dig +tcp google.com (TCP) radi kako treba, ali dig google.com (UDP) ne radi
Odvezavši se tcpdump još uvijek radi dig, nalazimo da se vraćaju UDP paketi
Odvezemo se strace dig google.com i vidimo kako dig ispravno zove sendmsg() и recvms(), međutim, drugi je prekinut timeoutom
Nažalost, dolazi kraj smjene i primorani smo eskalirati problem na sljedeću vremensku zonu. Zahtjev je, međutim, izazvao interesovanje u našem timu, pa kolega predlaže kreiranje početnog DNS paketa pomoću skrapy Python modula.
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())
Ovaj fragment kreira DNS paket i šalje zahtjev serveru metapodataka.
Korisnik pokreće kod, vraća se DNS odgovor, a aplikacija ga prima, potvrđujući da nema problema na nivou mreže.
Nakon još jednog „putovanja oko svijeta“, zahtjev se vraća našem timu, a ja ga potpuno prenosim na sebe, misleći da će korisniku biti zgodnije ako zahtjev prestane da kruži od mjesta do mjesta.
U međuvremenu, korisnik ljubazno pristaje da pruži snimak slike sistema. Ovo je jako dobra vijest: mogućnost da sam testiram sistem čini rješavanje problema mnogo bržim, jer više ne moram tražiti od korisnika da pokreće komande, šalje mi rezultate i analizira ih, sve mogu sam!
Moje kolege počinju pomalo da mi zavide. Za ručkom razgovaramo o konverziji, ali niko nema pojma šta se dešava. Na sreću, sam korisnik je već poduzeo mjere za ublažavanje posljedica i ne žuri, tako da imamo vremena da seciramo problem. A pošto imamo sliku, možemo pokrenuti sve testove koji nas zanimaju. Odlično!
Pravi korak unazad
Jedno od najpopularnijih pitanja za intervju za pozicije sistemskog inženjera je: „Šta se dešava kada pingujete www.google.com? Pitanje je odlično, jer kandidat treba da opiše sve od ljuske do korisničkog prostora, do sistemskog kernela i zatim do mreže. Smiješim se: ponekad se pitanja za intervju pokažu korisnima u stvarnom životu...
Odlučujem primijeniti ovo HR pitanje na trenutni problem. Grubo govoreći, kada pokušate da odredite DNS ime, dešava se sledeće:
Aplikacija poziva sistemsku biblioteku kao što je libdns
libdns provjerava konfiguraciju sistema sa kojim DNS serverom treba da kontaktira (na dijagramu je ovo 169.254.169.254, server metapodataka)
libdns koristi sistemske pozive za kreiranje UDP utičnice (SOKET_DGRAM) i slanje UDP paketa sa DNS upitom u oba smjera
Preko sysctl interfejsa možete konfigurisati UDP stek na nivou kernela
Kernel stupa u interakciju s hardverom za prijenos paketa preko mreže preko mrežnog sučelja
Hipervizor hvata i prenosi paket na server metapodataka nakon kontakta s njim
Server metapodataka, svojom magijom, određuje DNS ime i vraća odgovor koristeći isti metod
Dozvolite mi da vas podsjetim koje smo hipoteze već razmatrali:
Hipoteza: Pokvarene biblioteke
Test 1: pokrenite strace u sistemu, provjerite da li dig poziva ispravne sistemske pozive
Rezultat: Pozivaju se ispravni sistemski pozivi
Test 2: pomoću srapy-a provjerimo možemo li odrediti imena zaobilazeći sistemske biblioteke
Rezultat: možemo
Test 3: pokrenite rpm –V na libdns paketu i datotekama biblioteke md5sum
Rezultat: kod biblioteke je potpuno identičan kodu u operativnom sistemu koji radi
Test 4: montirajte korisničku sliku root sistema na VM bez ovog ponašanja, pokrenite chroot, pogledajte radi li DNS
Rezultat: DNS radi ispravno
Zaključak na osnovu testova: problem nije u bibliotekama
Hipoteza: Postoji greška u postavkama DNS-a
Test 1: provjerite tcpdump i vidite da li se DNS paketi šalju i vraćaju ispravno nakon pokretanja dig-a
Rezultat: paketi se prenose ispravno
Test 2: dvostruka provjera na serveru /etc/nsswitch.conf и /etc/resolv.conf
Rezultat: sve je ispravno
Zaključak na osnovu testova: problem nije u DNS konfiguraciji
Hipoteza: oštećeno jezgro
Test: instalirajte novi kernel, provjerite potpis, restartujte
Rezultat: slično ponašanje
Zaključak na osnovu testova: kernel nije oštećen
Hipoteza: pogrešno ponašanje korisničke mreže (ili hipervizorskog mrežnog interfejsa)
Test 1: Provjerite postavke zaštitnog zida
Rezultat: firewall prosljeđuje DNS pakete i na hostu i na GCP
Test 2: presretanje saobraćaja i praćenje ispravnosti prenosa i vraćanja DNS zahteva
Rezultat: tcpdump potvrđuje da je host primio povratne pakete
Zaključak na osnovu testova: problem nije u mreži
Hipoteza: server metapodataka ne radi
Test 1: provjerite dnevnike servera metapodataka da li postoje anomalije
Rezultat: nema anomalija u evidenciji
Test 2: Zaobiđite server metapodataka preko dig @8.8.8.8
Rezultat: Rezolucija je narušena čak i bez korištenja servera metapodataka
Zaključak na osnovu testova: problem nije u serveru metapodataka
Bottom line: testirali smo sve podsisteme osim postavke vremena izvođenja!
Zaroniti u postavke kernela runtime
Da biste konfigurisali okruženje za izvršavanje kernela, možete koristiti opcije komandne linije (grub) ili sysctl interfejs. Pogledao sam unutra /etc/sysctl.conf i samo pomislite, otkrio sam nekoliko prilagođenih postavki. Osjećajući se kao da sam se uhvatio za nešto, odbacio sam sve postavke koje nisu mrežne ili ne-tcp, ostajući pri planinskim postavkama net.core. Zatim sam otišao do mjesta gdje su dozvole hosta bile u VM-u i počeo primjenjivati postavke jednu po jednu, jednu za drugom, sa pokvarenim VM-om, dok nisam pronašao krivca:
net.core.rmem_default = 2147483647
Evo je, konfiguracija za razbijanje DNS-a! Našao sam oružje ubistva. Ali zašto se to dešava? I dalje mi je trebao motiv.
Osnovna veličina bafera DNS paketa se konfiguriše putem net.core.rmem_default. Tipična vrijednost je negdje oko 200KiB, ali ako vaš server prima mnogo DNS paketa, možda ćete htjeti povećati veličinu bafera. Ako je bafer pun kada stigne novi paket, na primjer zato što ga aplikacija ne obrađuje dovoljno brzo, tada ćete početi gubiti pakete. Naš klijent je ispravno povećao veličinu bafera jer se bojao gubitka podataka, jer je koristio aplikaciju za prikupljanje metrike preko DNS paketa. Vrijednost koju je postavio bila je maksimalna moguća: 231-1 (ako je postavljeno na 231, kernel će vratiti “INVALID ARGUMENT”).
Odjednom sam shvatio zašto nmap i scapy rade ispravno: koristili su sirove utičnice! Sirovi utičnici se razlikuju od običnih utičnica: zaobilaze iptables i nisu baferovani!
Ali zašto "bafer prevelik" uzrokuje probleme? Očigledno ne radi kako je zamišljeno.
U ovom trenutku mogao bih reproducirati problem na više kernela i više distribucija. Problem se već pojavio na kernelu 3.x, a sada se pojavio i na kernelu 5.x.
Zaista, nakon pokretanja
sysctl -w net.core.rmem_default=$((2**31-1))
DNS je prestao da radi.
Počeo sam tražiti radne vrijednosti kroz jednostavan algoritam binarnog pretraživanja i otkrio da sistem radi sa 2147481343, ali ovaj broj je za mene bio besmislen skup brojeva. Predložio sam klijentu da proba ovaj broj, a on je odgovorio da sistem radi sa google.com, ali je ipak dao grešku sa drugim domenima, pa sam nastavio istragu.
Instalirao sam dropwatch, alat koji je trebao biti korišten ranije: pokazuje gdje tačno u kernelu završava paket. Krivac je bila funkcija udp_queue_rcv_skb. Skinuo sam izvore kernela i dodao nekoliko funkcijeprintk za praćenje gdje tačno paket završava. Brzo sam pronašao pravo stanje if, i jednostavno zurio u to neko vrijeme, jer se tada sve konačno spojilo u jednu sliku: 231-1, besmislen broj, neradni domen... Bio je to dio koda u __udp_enqueue_schedule_skb:
if (rmem > (size + sk->sk_rcvbuf))
goto uncharge_drop;
Imajte na umu:
rmem je tipa int
size je tipa u16 (nepotpisan šesnaestobitni int) i pohranjuje veličinu paketa
sk->sk_rcybuf je tipa int i pohranjuje veličinu bafera koja je, po definiciji, jednaka vrijednosti u net.core.rmem_default
Kada sk_rcvbuf približava se 231, sabiranje veličine paketa može rezultirati prekoračenje cijelog broja. A pošto je int, njegova vrijednost postaje negativna, tako da uvjet postaje istinit kada bi trebao biti lažan (više o tome možete pročitati na link).
Greška se može ispraviti na trivijalan način: ubacivanjem unsigned int. Primijenio sam ispravku i ponovo pokrenuo sistem i DNS je ponovo radio.
Okus pobjede
Svoje nalaze sam prosledio klijentu i poslao LKML kernel patch. Zadovoljan sam: svaki deo slagalice se uklapa, mogu tačno da objasnim zašto smo primetili ono što smo primetili, i što je najvažnije, uspeli smo da pronađemo rešenje problema zahvaljujući našem timskom radu!
Vrijedi priznati da se slučaj pokazao rijetkim, a na sreću rijetko primamo ovako složene zahtjeve korisnika.