Příběh o chybějících paketech DNS z technické podpory Google Cloud

Z editoru blogu Google: Přemýšleli jste někdy o tom, jak inženýři Google Cloud Technical Solutions (TSE) zpracovávají vaše požadavky na podporu? Technici technické podpory TSE jsou odpovědní za identifikaci a nápravu zdrojů problémů hlášených uživateli. Některé z těchto problémů jsou docela jednoduché, ale někdy narazíte na lístek, který vyžaduje pozornost několika inženýrů najednou. V tomto článku nám jeden ze zaměstnanců TSE řekne o jednom velmi ošemetném problému ze své nedávné praxe - případ chybějících DNS paketů. V tomto příběhu uvidíme, jak se inženýrům podařilo situaci vyřešit a co nového se při opravě chyby naučili. Doufáme, že vás tento příběh nejen poučí o hluboce zakořeněné chybě, ale také vám poskytne přehled o procesech, které se týkají podání žádosti o podporu ve službě Google Cloud.

Příběh o chybějících paketech DNS z technické podpory Google Cloud

Odstraňování problémů je věda i umění. Vše začíná vytvořením hypotézy o důvodu nestandardního chování systému, načež je testován na pevnost. Než však zformulujeme hypotézu, musíme jasně definovat a přesně formulovat problém. Pokud otázka zní příliš vágně, budete muset vše pečlivě analyzovat; Toto je „umění“ odstraňování problémů.

V rámci Google Cloud se tyto procesy stávají exponenciálně složitějšími, protože Google Cloud se snaží ze všech sil zaručit soukromí svých uživatelů. Z tohoto důvodu nemají inženýři TSE přístup k úpravám vašich systémů ani možnost prohlížet konfigurace tak široce jako uživatelé. Proto, abychom otestovali jakoukoli z našich hypotéz, nemůžeme my (inženýři) rychle upravit systém.

Někteří uživatelé se domnívají, že vše opravíme jako mechanici v autoservisu a jednoduše nám pošlete ID virtuálního stroje, zatímco ve skutečnosti proces probíhá v konverzačním formátu: shromažďování informací, vytváření a potvrzování (nebo vyvracení) hypotéz, a nakonec, rozhodovací problémy jsou založeny na komunikaci s klientem.

Dotyčný problém

Dnes tu máme příběh s dobrým koncem. Jedním z důvodů úspěšného vyřešení navrhovaného případu je velmi podrobný a přesný popis problému. Níže můžete vidět kopii prvního lístku (upravený tak, aby skryl důvěrné informace):
Příběh o chybějících paketech DNS z technické podpory Google Cloud
Tato zpráva obsahuje pro nás mnoho užitečných informací:

  • Zadán konkrétní virtuální počítač
  • Je indikován samotný problém - DNS nefunguje
  • Je uvedeno, kde se problém projevuje - VM a kontejner
  • Jsou uvedeny kroky, které uživatel provedl k identifikaci problému.

Požadavek byl zaregistrován jako „P1: Critical Impact - Service Unusable in production“, což znamená neustálé sledování situace 24/7 podle schématu „Follow the Sun“ (více o priority uživatelských požadavků), s jeho přesunem z jednoho týmu technické podpory do druhého s každým posunem časového pásma. Ve skutečnosti, když se problém dostal k našemu týmu v Curychu, už obletěl zeměkouli. Do této doby uživatel přijal zmírňující opatření, ale obával se opakování situace ve výrobě, protože hlavní příčina dosud nebyla objevena.

V době, kdy letenka dorazila do Curychu, jsme již měli po ruce následující informace:

  • Obsah /etc/hosts
  • Obsah /etc/resolv.conf
  • Výkon iptables-save
  • Sestaveno týmem ngrep pcap soubor

S těmito údaji jsme byli připraveni zahájit fázi „vyšetřování“ a odstraňování problémů.

Naše první kroky

Nejprve jsme zkontrolovali protokoly a stav serveru metadat a ujistili se, že funguje správně. Server metadat odpovídá na IP adresu 169.254.169.254 a mimo jiné je zodpovědný za kontrolu doménových jmen. Také jsme dvakrát zkontrolovali, že firewall funguje správně s virtuálním počítačem a neblokuje pakety.

Byl to nějaký zvláštní problém: kontrola nmap vyvrátila naši hlavní hypotézu o ztrátě paketů UDP, takže jsme v duchu přišli s několika dalšími možnostmi a způsoby, jak je zkontrolovat:

  • Jsou pakety zahazovány selektivně? => Zkontrolujte pravidla iptables
  • Není to moc malé? MTU? => Zkontrolujte výstup ip a show
  • Ovlivňuje problém pouze pakety UDP nebo také TCP? => Odjet dig +tcp
  • Jsou vráceny pakety generované dig? => Odjet tcpdump
  • Funguje libdns správně? => Odjet strace pro kontrolu přenosu paketů v obou směrech

Zde se rozhodneme zavolat uživateli k řešení problémů živě.

Během hovoru jsme schopni zkontrolovat několik věcí:

  • Po několika kontrolách vyřadíme pravidla iptables ze seznamu důvodů
  • Zkontrolujeme síťová rozhraní a směrovací tabulky a dvakrát zkontrolujeme správnost MTU
  • Zjišťujeme to dig +tcp google.com (TCP) funguje jak má, ale dig google.com (UDP) nefunguje
  • Po odjetí tcpdump během práce dig, zjistíme, že se vracejí UDP pakety
  • Jedeme pryč strace dig google.com a vidíme, jak správně volá dig sendmsg() и recvms(), druhý je však přerušen časovým limitem

Bohužel přichází konec směny a my jsme nuceni problém eskalovat do dalšího časového pásma. Požadavek však v našem týmu vzbudil zájem a kolega navrhuje vytvořit počáteční DNS balíček pomocí scrapy 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 vytvoří paket DNS a odešle požadavek na server metadat.

Uživatel spustí kód, vrátí se odpověď DNS a aplikace ji přijme, čímž potvrdí, že na úrovni sítě není žádný problém.

Po další „cestě kolem světa“ se požadavek vrací našemu týmu a já ho zcela přenáším na sebe v domnění, že pro uživatele bude pohodlnější, když požadavek přestane kroužit z místa na místo.

Mezitím uživatel laskavě souhlasí s poskytnutím snímku obrazu systému. To je velmi dobrá zpráva: možnost testovat systém sám výrazně urychluje odstraňování problémů, protože již nemusím žádat uživatele, aby spouštěl příkazy, posílal mi výsledky a analyzoval je, vše mohu udělat sám!

Kolegové mi začínají trochu závidět. U oběda diskutujeme o konverzi, ale nikdo netuší, co se děje. Naštěstí sám uživatel již přijal opatření ke zmírnění následků a nikam nespěchá, takže máme čas problém rozpitvat. A protože máme obrázek, můžeme spustit jakékoli testy, které nás zajímají. Skvělý!

Krok zpět

Jedna z nejoblíbenějších otázek na pohovorech pro pozice systémových inženýrů zní: „Co se stane, když pingnete www.google.com? Otázka je skvělá, protože kandidát potřebuje popsat vše od shellu po uživatelský prostor, jádro systému a pak síť. Usmívám se: někdy se otázky na pohovoru ukáží jako užitečné v reálném životě...

Rozhodl jsem se aplikovat tuto otázku HR na aktuální problém. Zhruba řečeno, když se pokusíte určit název DNS, stane se následující:

  1. Aplikace volá systémovou knihovnu, jako je libdns
  2. libdns zkontroluje konfiguraci systému, na který server DNS by se měl obrátit (v diagramu je to 169.254.169.254, server metadat)
  3. libdns používá systémová volání k vytvoření soketu UDP (SOKET_DGRAM) a odesílání paketů UDP s dotazem DNS v obou směrech
  4. Prostřednictvím rozhraní sysctl můžete nakonfigurovat zásobník UDP na úrovni jádra
  5. Jádro spolupracuje s hardwarem a přenáší pakety po síti přes síťové rozhraní
  6. Hypervizor zachytí a odešle paket na server metadat při kontaktu s ním
  7. Server metadat prostřednictvím své magie určí název DNS a vrátí odpověď pomocí stejné metody

Příběh o chybějících paketech DNS z technické podpory Google Cloud
Dovolte mi připomenout, jaké hypotézy jsme již zvažovali:

Hypotéza: Rozbité knihovny

  • Test 1: spusťte strace v systému, zkontrolujte, zda dig volá správná systémová volání
  • Výsledek: Jsou volána správná systémová volání
  • Test 2: pomocí srapy zkontrolujte, zda dokážeme určit jména, která obcházejí systémové knihovny
  • Výsledek: můžeme
  • Test 3: Spusťte rpm –V na balíčku libdns a souborech knihovny md5sum
  • Výsledek: kód knihovny je zcela identický s kódem v pracovním operačním systému
  • Test 4: připojte obraz kořenového systému uživatele na virtuální počítač bez tohoto chování, spusťte chroot a zjistěte, zda DNS funguje
  • Výsledek: DNS funguje správně

Závěr na základě testů: problém není v knihovnách

Hypotéza: V nastavení DNS je chyba

  • Test 1: po spuštění dig zkontrolujte tcpdump a zjistěte, zda se pakety DNS odesílají a vracejí správně
  • Výsledek: pakety jsou přenášeny správně
  • Test 2: dvakrát zkontrolujte na serveru /etc/nsswitch.conf и /etc/resolv.conf
  • Výsledek: vše je správně

Závěr na základě testů: problém není v konfiguraci DNS

Hypotéza: jádro poškozené

  • Test: nainstalujte nové jádro, zkontrolujte podpis, restartujte
  • Výsledek: podobné chování

Závěr na základě testů: jádro není poškozeno

Hypotéza: nesprávné chování uživatelské sítě (nebo síťového rozhraní hypervizoru)

  • Test 1: Zkontrolujte nastavení brány firewall
  • Výsledek: firewall předává pakety DNS na hostiteli i GCP
  • Test 2: zachyťte provoz a sledujte správnost přenosu a vrácení DNS požadavků
  • Výsledek: tcpdump potvrzuje, že hostitel přijal zpětné pakety

Závěr na základě testů: problém není v síti

Hypotéza: server metadat nefunguje

  • Test 1: Zkontrolujte, zda protokoly serveru metadat neobsahují anomálie
  • Výsledek: v protokolech nejsou žádné anomálie
  • Test 2: Vynechejte server metadat přes dig @8.8.8.8
  • Výsledek: Rozlišení je narušeno i bez použití serveru metadat

Závěr na základě testů: problém není v serveru metadat

Sečteno a podtrženo: testovali jsme všechny subsystémy kromě nastavení runtime!

Ponoření se do nastavení jádra

Pro konfiguraci spouštěcího prostředí jádra můžete použít volby příkazového řádku (grub) nebo rozhraní sysctl. Podíval jsem se dovnitř /etc/sysctl.conf a jen si představte, objevil jsem několik vlastních nastavení. S pocitem, jako bych se něčeho chytil, jsem zahodil všechna nastavení, která nejsou síťová nebo neTCP, a zůstal jsem u nastavení hor. net.core. Pak jsem šel tam, kde byla oprávnění hostitele ve virtuálním počítači, a začal jsem používat nastavení jedno po druhém, jedno po druhém, s poškozeným virtuálním počítačem, dokud jsem nenašel viníka:

net.core.rmem_default = 2147483647

Tady to je, konfigurace prolomení DNS! Našel jsem vražednou zbraň. Ale proč se to děje? Ještě jsem potřeboval motiv.

Základní velikost vyrovnávací paměti paketů DNS se konfiguruje pomocí net.core.rmem_default. Typická hodnota je někde kolem 200 kB, ale pokud váš server přijímá velké množství paketů DNS, možná budete chtít zvětšit velikost vyrovnávací paměti. Pokud je vyrovnávací paměť plná, když přijde nový paket, například proto, že jej aplikace nezpracovává dostatečně rychle, pak začnete ztrácet pakety. Náš klient správně zvětšil velikost vyrovnávací paměti, protože se obával ztráty dat, protože používal aplikaci pro sběr metrik prostřednictvím paketů DNS. Hodnota, kterou nastavil, byla maximální možná: 231-1 (pokud je nastaven na 231, jádro vrátí „NEPLATNÝ ARGUMENT“).

Najednou jsem si uvědomil, proč nmap a scapy fungovaly správně: používaly raw sockety! Raw sockety se liší od běžných socketů: obcházejí iptables a nejsou ukládány do vyrovnávací paměti!

Proč ale „příliš velký buffer“ způsobuje problémy? Evidentně to nefunguje tak, jak bylo zamýšleno.

V tomto okamžiku bych mohl problém reprodukovat na více jádrech a více distribucích. Problém se objevil již na jádře 3.x a nyní se objevil i na jádře 5.x.

Ostatně při startu

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

DNS přestal fungovat.

Začal jsem hledat pracovní hodnoty pomocí jednoduchého binárního vyhledávacího algoritmu a zjistil jsem, že systém fungoval s 2147481343, ale toto číslo pro mě byla nesmyslná sada čísel. Navrhl jsem klientovi, aby zkusil toto číslo, a on odpověděl, že systém funguje s google.com, ale stále zobrazuje chybu u jiných domén, takže jsem pokračoval ve vyšetřování.

nainstaloval jsem dropwatch, nástroj, který měl být použit dříve: přesně ukazuje, kde v jádře paket končí. Na vině byla funkce udp_queue_rcv_skb. Stáhl jsem si zdrojové kódy jádra a pár jich přidal funkce printk sledovat, kde přesně paket končí. Rychle jsem našel správný stav if, a prostě na to nějakou dobu zíral, protože tehdy se všechno konečně sešlo do celku: 231-1, nic neříkající číslo, nefunkční doména... Byl to kus kódu v __udp_enqueue_schedule_skb:

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

Vezměte prosím na vědomí:

  • rmem je typu int
  • size je typu u16 (unsigned šestnáctibitový int) a ukládá velikost paketu
  • sk->sk_rcybuf je typu int a ukládá velikost vyrovnávací paměti, která se podle definice rovná hodnotě in net.core.rmem_default

Kdy sk_rcvbuf se blíží 231, výsledkem může být sečtení velikosti paketu přetečení celého čísla. A protože je to int, jeho hodnota se stane zápornou, takže podmínka se stane pravdivou, když by měla být nepravda (více o tom můžete přečíst na odkaz).

Chybu lze opravit triviálním způsobem: castingem unsigned int. Použil jsem opravu a restartoval systém a DNS znovu fungoval.

Chuť vítězství

Své poznatky jsem předal klientovi a odeslal LKML kernel patch. Těší mě: každý dílek skládačky do sebe zapadá, dokážu přesně vysvětlit, proč jsme pozorovali to, co jsme pozorovali, a co je nejdůležitější, díky týmové práci jsme dokázali najít řešení problému!

Stojí za to uznat, že se případ ukázal jako vzácný a naštěstí tak složité požadavky od uživatelů dostáváme jen zřídka.

Příběh o chybějících paketech DNS z technické podpory Google Cloud


Zdroj: www.habr.com

Přidat komentář