Príbeh o chýbajúcich paketoch DNS od technickej podpory Google Cloud
Z editora blogu Google: Zamysleli ste sa niekedy nad tým, ako inžinieri Google Cloud Technical Solutions (TSE) riešia vaše žiadosti o podporu? Technici technickej podpory TSE sú zodpovední za identifikáciu a nápravu zdrojov problémov nahlásených používateľmi. Niektoré z týchto problémov sú celkom jednoduché, no niekedy narazíte na lístok, ktorý si vyžaduje pozornosť niekoľkých inžinierov naraz. V tomto článku nám jeden zo zamestnancov TSE povie o jednom veľmi ošemetnom probléme zo svojej nedávnej praxe - prípad chýbajúcich DNS paketov. V tomto príbehu uvidíme, ako sa inžinierom podarilo situáciu vyriešiť a čo nové sa naučili pri oprave chyby. Dúfame, že tento príbeh vás nielen poučí o hlboko zakorenenej chybe, ale tiež vám poskytne prehľad o procesoch, ktoré súvisia s podaním žiadosti o podporu v službe Google Cloud.
Riešenie problémov je veda aj umenie. Všetko to začína vytvorením hypotézy o dôvode neštandardného správania systému, po ktorom sa testuje pevnosť. Kým však sformulujeme hypotézu, musíme jasne definovať a presne sformulovať problém. Ak otázka znie príliš vágne, budete musieť všetko dôkladne analyzovať; Toto je „umenie“ odstraňovania problémov.
V rámci služby Google Cloud sa takéto procesy stávajú exponenciálne zložitejšími, pretože služba Google Cloud sa snaží zo všetkých síl zaručiť súkromie svojich používateľov. Z tohto dôvodu nemajú inžinieri TSE prístup k úprave vašich systémov, ani možnosť prezerať konfigurácie tak široko, ako to robia používatelia. Preto, aby sme otestovali niektorú z našich hypotéz, my (inžinieri) nemôžeme rýchlo upraviť systém.
Niektorí používatelia veria, že všetko opravíme ako mechanici v autoservise a jednoducho nám pošleme ID virtuálneho stroja, zatiaľ čo v skutočnosti tento proces prebieha v konverzačnom formáte: zbieranie informácií, vytváranie a potvrdzovanie (alebo vyvracanie) hypotéz, a v konečnom dôsledku sú rozhodovacie problémy založené na komunikácii s klientom.
Predmetný problém
Dnes tu máme príbeh s dobrým koncom. Jedným z dôvodov úspešného vyriešenia navrhovaného prípadu je veľmi podrobný a presný popis problému. Nižšie môžete vidieť kópiu prvého tiketu (upravený tak, aby skryl dôverné informácie):
Táto správa obsahuje pre nás množstvo užitočných informácií:
Zadaný konkrétny VM
Je indikovaný samotný problém - DNS nefunguje
Je uvedené, kde sa problém prejavuje - VM a kontajner
Sú uvedené kroky, ktoré používateľ vykonal na identifikáciu problému.
Požiadavka bola zaregistrovaná ako „P1: Critical Impact – Service Unusable in production“, čo znamená neustále monitorovanie situácie 24/7 podľa schémy „Follow the Sun“ (viac si môžete prečítať o priority požiadaviek používateľov), s jeho presunom z jedného tímu technickej podpory do druhého pri každom posune časového pásma. V skutočnosti, keď sa problém dostal k nášmu tímu v Zürichu, už obletel celý svet. Používateľ medzitým prijal opatrenia na zmiernenie, ale obával sa opakovania situácie vo výrobe, pretože hlavná príčina ešte nebola odhalená.
V čase, keď letenka dorazila do Zürichu, sme už mali po ruke nasledujúce informácie:
obsah /etc/hosts
obsah /etc/resolv.conf
Výkon iptables-save
Zostavené tímom ngrep súbor pcap
S týmito údajmi sme boli pripravení začať fázu „vyšetrovania“ a riešenia problémov.
Naše prvé kroky
Najprv sme skontrolovali denníky a stav servera metadát a ubezpečili sa, že funguje správne. Server metadát odpovedá na IP adresu 169.254.169.254 a okrem iného je zodpovedný za kontrolu doménových mien. Tiež sme dvakrát skontrolovali, či firewall funguje správne s VM a neblokuje pakety.
Bol to nejaký zvláštny problém: kontrola nmap vyvrátila našu hlavnú hypotézu o strate paketov UDP, takže sme v duchu prišli s niekoľkými ďalšími možnosťami a spôsobmi, ako ich skontrolovať:
Sú pakety zahadzované selektívne? => Skontrolujte pravidlá iptables
Nie je to príliš malé? MTU? => Skontrolujte výstup ip a show
Ovplyvňuje problém iba pakety UDP alebo TCP? => Odvezte sa dig +tcp
Vrátia sa pakety vygenerované digom? => Odvezte sa tcpdump
Funguje libdns správne? => Odvezte sa strace na kontrolu prenosu paketov v oboch smeroch
Tu sa rozhodneme zavolať používateľovi na riešenie problémov naživo.
Počas hovoru sme schopní skontrolovať niekoľko vecí:
Po niekoľkých kontrolách vylúčime pravidlá iptables zo zoznamu dôvodov
Skontrolujeme sieťové rozhrania a smerovacie tabuľky a dvakrát skontrolujeme, či je MTU správna
Zisťujeme to dig +tcp google.com (TCP) funguje ako má, ale dig google.com (UDP) nefunguje
Po odjazde tcpdump stále to funguje dig, zistíme, že sa vracajú UDP pakety
Odvezieme sa strace dig google.com a vidíme, ako správne volá sendmsg() и recvms(), druhý je však prerušený časovým limitom
Žiaľ, prichádza koniec smeny a my sme nútení problém eskalovať do ďalšieho časového pásma. Požiadavka však vzbudila záujem v našom tíme a kolega navrhuje vytvorenie počiatočného balíka DNS pomocou špinavého modulu Python.
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())
Tento fragment vytvorí paket DNS a odošle požiadavku na server metadát.
Používateľ spustí kód, vráti sa odpoveď DNS a aplikácia ju prijme, čím potvrdí, že na úrovni siete nie je žiadny problém.
Po ďalšej „ceste okolo sveta“ sa požiadavka vráti nášmu tímu a ja ju úplne prenášam na seba, mysliac si, že pre používateľa bude pohodlnejšie, ak žiadosť prestane krúžiť z miesta na miesto.
Používateľ medzitým láskavo súhlasí s poskytnutím snímky obrazu systému. To je veľmi dobrá správa: možnosť otestovať si systém sám výrazne urýchľuje riešenie problémov, pretože už nemusím žiadať používateľa, aby spúšťal príkazy, posielal mi výsledky a analyzoval ich, všetko môžem urobiť sám!
Kolegovia mi začínajú trochu závidieť. Počas obeda diskutujeme o konverzii, ale nikto netuší, čo sa deje. Našťastie samotný používateľ už prijal opatrenia na zmiernenie následkov a nikam sa neponáhľa, takže máme čas problém rozobrať. A keďže máme obrázok, môžeme spustiť akékoľvek testy, ktoré nás zaujímajú. Skvelé!
Urobte krok späť
Jedna z najpopulárnejších otázok na pohovore pre pozície systémových inžinierov je: „Čo sa stane, keď pingnete www.google.com? Otázka je skvelá, pretože kandidát potrebuje opísať všetko od shellu cez užívateľský priestor, jadro systému a potom sieť. Usmejem sa: niekedy sa otázky na pohovore ukážu ako užitočné v reálnom živote...
Rozhodol som sa aplikovať túto HR otázku na aktuálny problém. Zhruba povedané, keď sa pokúsite určiť názov DNS, stane sa toto:
Aplikácia volá systémovú knižnicu, napríklad libdns
libdns skontroluje konfiguráciu systému, na ktorý server DNS sa má obrátiť (v diagrame je to 169.254.169.254, server metadát)
libdns používa systémové volania na vytvorenie zásuvky UDP (SOKET_DGRAM) a odosielanie paketov UDP s dotazom DNS v oboch smeroch
Prostredníctvom rozhrania sysctl môžete nakonfigurovať zásobník UDP na úrovni jadra
Jadro interaguje s hardvérom na prenos paketov cez sieť cez sieťové rozhranie
Hypervízor zachytí a odošle paket na server metadát pri kontakte s ním
Server metadát prostredníctvom svojej mágie určí názov DNS a vráti odpoveď pomocou rovnakej metódy
Dovoľte mi pripomenúť, aké hypotézy sme už zvážili:
Hypotéza: Rozbité knižnice
Test 1: spustite sledovanie v systéme, skontrolujte, či dig volá správne systémové volania
Výsledok: Vyvolajú sa správne systémové volania
Test 2: pomocou srapy skontrolujte, či dokážeme určiť mená obchádzajúce systémové knižnice
Výsledok: môžeme
Test 3: spustite rpm –V na balíku libdns a súboroch knižnice md5sum
Výsledok: kód knižnice je úplne identický s kódom vo funkčnom operačnom systéme
Test 4: pripojte obraz koreňového systému používateľa na VM bez tohto správania, spustite chroot a skontrolujte, či DNS funguje
Výsledok: DNS funguje správne
Záver na základe testov: problém nie je v knižniciach
Hypotéza: V nastaveniach DNS je chyba
Test 1: skontrolujte tcpdump a skontrolujte, či sa DNS pakety odosielajú a vracajú správne po spustení dig
Výsledok: pakety sa prenášajú správne
Test 2: dvakrát skontrolujte server /etc/nsswitch.conf и /etc/resolv.conf
Výsledok: všetko je správne
Záver na základe testov: problém nie je v konfigurácii DNS
Hypotéza: jadro poškodené
Test: nainštalujte nové jadro, skontrolujte podpis, reštartujte
Výsledok: podobné správanie
Záver na základe testov: jadro nie je poškodené
Hypotéza: nesprávne správanie používateľskej siete (alebo sieťového rozhrania hypervízora)
Test 1: Skontrolujte nastavenia brány firewall
Výsledok: brána firewall odovzdáva pakety DNS na hostiteľovi aj GCP
Test 2: zachyťte prevádzku a sledujte správnosť prenosu a návratu DNS požiadaviek
Výsledok: tcpdump potvrdzuje, že hostiteľ prijal spätné pakety
Záver na základe testov: problém nie je v sieti
Hypotéza: server metadát nefunguje
Test 1: skontrolujte, či protokoly servera metadát neobsahujú anomálie
Výsledok: v protokoloch nie sú žiadne anomálie
Test 2: Obíďte server metadát cez dig @8.8.8.8
Výsledok: Rozlíšenie je narušené aj bez použitia servera metadát
Záver na základe testov: problém nie je so serverom metadát
Zrátané a podčiarknuté: testovali sme všetky podsystémy okrem nastavenia runtime!
Ponorenie sa do nastavení Kernel Runtime
Ak chcete nakonfigurovať prostredie vykonávania jadra, môžete použiť voľby príkazového riadka (grub) alebo rozhranie sysctl. Pozrel som sa do /etc/sysctl.conf a len si pomyslite, objavil som niekoľko vlastných nastavení. S pocitom, akoby som sa niečoho chytil, som zahodil všetky nastavenia, ktoré nie sú sieťou alebo protokolom TCP, a zostal som pri horských nastaveniach net.core. Potom som išiel na miesto, kde boli povolenia hostiteľa vo virtuálnom počítači, a začal som aplikovať nastavenia jedno po druhom, jedno po druhom, s poškodeným virtuálnym počítačom, kým som nenašiel vinníka:
net.core.rmem_default = 2147483647
Tu to je, konfigurácia prerušujúca DNS! Našiel som vražednú zbraň. Ale prečo sa to deje? Stále som potreboval motív.
Základná veľkosť vyrovnávacej pamäte DNS paketov sa konfiguruje cez net.core.rmem_default. Typická hodnota je niekde okolo 200 kB, ale ak váš server prijíma veľa paketov DNS, možno budete chcieť zväčšiť veľkosť vyrovnávacej pamäte. Ak je vyrovnávacia pamäť plná, keď príde nový paket, napríklad preto, že ho aplikácia nespracováva dostatočne rýchlo, potom začnete strácať pakety. Náš klient správne zväčšil veľkosť vyrovnávacej pamäte, pretože sa obával straty dát, keďže používal aplikáciu na zber metrík cez DNS pakety. Hodnota, ktorú nastavil, bola maximálna možná hodnota: 231-1 (ak je nastavená na 231, jadro vráti „NEPLATNÝ ARGUMENT“).
Zrazu som si uvedomil, prečo nmap a scapy fungovali správne: používali surové zásuvky! Raw sokety sa líšia od bežných soketov: obchádzajú iptables a nie sú ukladané do vyrovnávacej pamäte!
Prečo však „príliš veľký nárazník“ spôsobuje problémy? Očividne to nefunguje podľa predstáv.
V tomto bode by som mohol problém reprodukovať na viacerých jadrách a viacerých distribúciách. Problém sa objavil už na jadre 3.x a teraz sa objavil aj na jadre 5.x.
Pravdaže pri štarte
sysctl -w net.core.rmem_default=$((2**31-1))
DNS prestal fungovať.
Začal som hľadať pracovné hodnoty pomocou jednoduchého binárneho vyhľadávacieho algoritmu a zistil som, že systém fungoval s 2147481343, ale toto číslo bolo pre mňa nezmyselnou množinou čísel. Navrhol som klientovi, aby vyskúšal toto číslo, a on odpovedal, že systém funguje s google.com, ale stále zobrazuje chybu s inými doménami, takže som pokračoval vo vyšetrovaní.
Nainštaloval som dropwatch, nástroj, ktorý sa mal použiť skôr: presne ukazuje, kde v jadre paket končí. Na vine bola funkcia udp_queue_rcv_skb. Stiahol som si zdrojové kódy jadra a pridal som ich funkcieprintk sledovať, kde presne paket končí. Rýchlo som našiel správny stav if, a chvíľu sa na to len díval, pretože vtedy sa všetko konečne spojilo do jedného celku: 231-1, nezmyselné číslo, nefunkčná doména... Bol to kúsok kódu v __udp_enqueue_schedule_skb:
if (rmem > (size + sk->sk_rcvbuf))
goto uncharge_drop;
Upozornenie:
rmem je typu int
size je typu u16 (unsigned sixteen-bit int) a ukladá veľkosť paketu
sk->sk_rcybuf je typu int a ukladá veľkosť vyrovnávacej pamäte, ktorá sa podľa definície rovná hodnote in net.core.rmem_default
Kedy sk_rcvbuf blíži 231, výsledkom môže byť súčet veľkosti paketu pretečenie celého čísla. A keďže ide o int, jeho hodnota sa stane zápornou, takže podmienka sa stane pravdivou, keď by mala byť nepravdivá (viac si o tom môžete prečítať na odkaz).
Chybu je možné opraviť triviálnym spôsobom: odliatím unsigned int. Použil som opravu a reštartoval systém a DNS znova fungoval.
Chuť víťazstva
Svoje zistenia som postúpil klientovi a odoslal LKML kernel patch. Teší ma: každý kúsok skladačky do seba zapadá, viem presne vysvetliť, prečo sme pozorovali to, čo sme pozorovali, a čo je najdôležitejšie, vďaka tímovej práci sme dokázali nájsť riešenie problému!
Stojí za to uznať, že prípad sa ukázal ako zriedkavý a našťastie len zriedka dostávame takéto zložité požiadavky od používateľov.