Articles

Debugging war story: The mystery of NXDOMAIN

následující blogový příspěvek popisuje dobrodružství ladění na clusteru založeném na Cloudflare. Tento interní cluster se primárně používá ke zpracování informací o souboru protokolu tak, aby zákazníci Cloudflare měli analytiku, a pro naše systémy, které detekují útoky a reagují na ně.

problém neměl žádný vliv na naše zákazníky, ale to mají inženýři poškrábání jejich hlavy…

V určitém okamžiku v jednom z našich clusteru jsme začali vidět chyby, jako je tento (NXDOMAIN pro existující domény na naše DNS interní):

lookup some.existing.internal.host on 10.1.0.9:53: no such host

To se zdálo velmi divné, protože doména skutečně existuje. Byla to jedna z našich interních domén! Inženýři se zmínili, že viděli toto chování, tak jsme se rozhodli prozkoumat hlouběji. Dotazy spouštějící tuto chybu byly různé a pohybovaly se od dynamických záznamů SRV spravovaných mesos-dns až po externí domény vzhlížené zevnitř clusteru.

Naše první naivní pokus byl spusťte následující ve smyčce:

while true; do dig some.existing.internal.host > /tmp/dig.txt || break; done

spuštění této chvíli na jednom serveru ani reprodukovat problém: všechny dotazy byly úspěšné. Pak jsme si vzali naše servisní protokoly na jeden den a udělali grep pro „žádný takový hostitel“ a podobné zprávy. Chyby se děly sporadicky. Byly hodiny mezi chybami a žádný zřejmý vzorec, který by nás mohl vést k nějakému závěru. Naše šetření vyřazeny možnost, že chybová ležel v Cestách, které používáme pro spoustu našich služeb, protože chyby byly z Java služby.

do králičí nory

Spustili jsme nevázané na jedné IP přes několik počítačů pro náš cluster DNS resolver. BGP je pak zodpovědný za oznamování Interních tras ze strojů do routeru. Rozhodli jsme se zkusit najít vzor zasláním spousty požadavků z různých strojů a chyb při nahrávání. Tady je to, co naše zatížení testovací program vypadal na první pohled:

package mainimport ("flag""fmt""net""os""time")func main() {n := flag.String("n", "", "domain to look up")p := flag.Duration("p", time.Millisecond*10, "pause between lookups")flag.Parse()if *n == "" {flag.PrintDefaults()os.Exit(1)}for {_, err := net.LookupHost(*n)if err != nil {fmt.Println("error:", err)}time.Sleep(*p)}}

spustit net.LookupHost ve smyčce s malými pauzami a chyby protokolu; to je ono. Balení do kontejneru Docker a běh na maratonu byla pro nás jasnou volbou, protože tak stejně provozujeme jiné služby. Protokoly se posílají do Kafky a pak do Kibany, kde je můžeme analyzovat. Běží to program na 65 strojů dělá vyhledávání každých 50ms ukazuje následující chybová distribuce (od vysoké k nízké) po hostitelé:

Jsme viděli, žádná silná korelace s regály nebo konkrétní stroje. Chyby se staly na mnoha hostitelích, ale ne na všech a v různých časových chybách systému windows se vyskytují na různých strojích. Uvedení času na ose X a počet chyb na ose Y ukázal následující:

zjistit, jestli některé konkrétní DNS recursor se zbláznili, přestali jsme všechny generátory zatížení na pravidelné strojů a začal zatížení generace nástroj na recursors sami. Během několika hodin nedošlo k žádným chybám, což naznačuje, že nevázaný byl naprosto zdravý.

začali jsme mít podezření, že ztráta paketů byl problém, ale proč by „žádný takový hostitel“ dojít? Mělo by se to stát pouze tehdy, když je chyba NXDOMAIN v odpovědi DNS, ale naše teorie byla, že odpovědi se vůbec nevrátily.

Chybí

testovat hypotézu, že ztráty paketů může vést k „žádný takový hostitel“ chyba, že jsme se poprvé pokusili blokování odchozí provoz na port 53:

sudo iptables -A OUTPUT -p udp --dport 53 -j DROP

V tomto případě, vykopat a podobné nástroje jen čas, ale nechci vrátit „žádný takový hostitel“:

; <<>> DiG 9.9.5-9+deb8u3-Debian <<>> cloudflare.com;; global options: +cmd;; connection timed out; no servers could be reached

Go je trochu chytřejší a řekne vám více o tom, co se děje, ale nevrátí „žádný takový hostitel“ buď:

error: lookup cloudflare.com on 10.1.0.9:53: write udp 10.1.14.20:47442->10.1.0.9:53: write: operation not permitted

Od jádra Linuxu řekne, odesílatele, které je vyřazené pakety, měli jsme na bod nameserver do nějaké černé díry v síti, která nemá nic s pakety napodobovat ztráty paketů. Stále žádné štěstí:

error: lookup cloudflare.com on 10.1.2.9:53: read udp 10.1.14.20:39046->10.1.2.9:53: i/o timeout

pokračovat v obviňování sítě, museli jsme podpořit naše předpoklady nějak, tak jsme přidali informace o časování, aby naše vyhledávání:

s := time.Now()_, err := net.LookupHost(*n)e := time.Now().Sub(s).Seconds()if err != nil { log.Printf("error after %.4fs: %s", e, err)} else if e > 1 { log.Printf("success after %.4fs", e)}

Abych byl upřímný, jsme začali tím, že chyby v časování a přidal úspěch načasování později. Chyby se děly po 10s, poměrně mnoho úspěšných odpovědí přicházelo po 5s. Vypadá to jako ztráta paketů, ale stále nám neříká, proč se“ žádný takový hostitel “ nestane.

Od teď jsme byli na místě, když jsme věděli, které hostí byly více pravděpodobné, že bude ovlivněna, jsme provedli následující dva příkazy paralelně ve dvou screen relace:

while true; do dig cloudflare.com > /tmp/dig.log || break; done; date; sudo killall tcpdumpsudo tcpdump -w /state/wtf.cap port 53

cílem bylo dostat síti skládku se nepodařilo řeší. Tam jsme viděli následující dotazy:

00.00s A cloudflare.com05.00s A cloudflare.com10.00s A cloudflare.com.in.our.internal.domain

Dva dotazy časový limit bez odpovědi, ale ten třetí dostane štěstí a uspěje. Samozřejmě nemáme cloudflare.com v naší interní doméně, tak nevázaný oprávněně dává nxdomain v odpovědi, 10s po zahájení vyhledávání.

Bingo

podívejme se na / etc / resolv.conf pochopit více:

nameserver 10.1.0.9search in.our.internal.domain

Pomocí vyhledávání klíčových slov nám umožňuje použít krátké názvy hostitelů namísto FQDN, takže myhost transparentně ekvivalent myhost.in.our.internal.domain.

Pro překládání DNS to znamená následující: pro libovolný DNS dotaz zeptejte nameserver 10.1.0.9, pokud se to nezdaří, připojit .in.our.internal.domain dotaz a zkuste to znovu. Nezáleží na tom, jaké selhání nastane u původního dotazu DNS. Obvykle je to NXDOMAIN, ale v našem případě je to časový limit čtení kvůli ztrátě paketů.

zdálo se, že musí dojít k následujícím událostem, aby se objevila chyba „žádný takový hostitel:

  1. původní požadavek DNS musí být ztracena
  2. opakování, která je odeslána po 5 sekundách musí být ztracena
  3. následné dotaz pro interní domény (způsobené search varianta), musí uspět a vrátit NXDOMAIN

Na druhou stranu, pozorovat vypršel časový limit dotazu služby DNS namísto NXDOMAIN, můžete ztratit čtyři pakety odeslané 5 sekund jednu po druhé (2 pro původní dotaz a 2 pro vnitřní verze vaší domény), což je mnohem menší pravděpodobnost. Ve skutečnosti jsme viděli jen NXDOMAIN po 15s jednou a nikdy neviděl chybu po 20s.

potvrďte, že předpoklad, postavili jsme proof-of-concept server DNS, který kapky všechny požadavky pro cloudflare.com, ale pošle NXDOMAIN pro stávající domény:

package mainimport ("github.com/miekg/dns""log")func main() {server := &dns.Server{Addr: ":53", Net: "udp"}dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {m := &dns.Msg{}m.SetReply(r)for _, q := range r.Question {log.Printf("checking %s", q.Name)if q.Name == "cloudflare.com." {log.Printf("ignoring %s", q.Name)// just ignorereturn}}w.WriteMsg(m)})log.Printf("listening..")if err := server.ListenAndServe(); err != nil {log.Fatalf("error listening: %s", err)}}

Konečně jsme našli to, co se děje, a měl možnost spolehlivě replikovat, že chování.

řešení

Zamysleme se nad tím, jak můžeme zlepšit našeho klienta, aby lépe zvládl tyto přechodné problémy se sítí, čímž je odolnější. Manuálová stránka pro resolv.conf vám řekne, že máte dva knoflíky: volby timeout a retries. Výchozí hodnoty jsou 5 a 2.

Pokud nebudete mít server DNS velmi zaneprázdněn, je velmi nepravděpodobné, že by odpověď trvala déle než 1 sekundu. Ve skutečnosti, pokud máte náhodou síťové zařízení na Měsíci, můžete očekávat, že odpoví za 3 sekundy. Pokud váš jmenný server žije v dalším stojanu a je dosažitelný přes vysokorychlostní síť, můžete bezpečně předpokládat, že pokud po 1 sekundě nedojde k odpovědi, váš server DNS nedostal váš dotaz. Pokud chcete mít méně podivné chyby „žádná taková doména“, které vás poškrábají na hlavě,můžete také zvýšit opakování. Čím vícekrát opakujete přechodnou ztrátu paketů, tím menší je šance na selhání. Čím častěji opakujete, tím vyšší je šance na rychlejší dokončení.

Představte si, že máte skutečně náhodnou ztrátu 1% paketů.

  • 2 opakování, 5s timeout: max 10s čekat, než chyba, 0.001% šance na selhání
  • 5 opakování, 1s timeout: max 5s čekat, než chyba, 0.000001% šanci na selhání

V reálném životě, rozdělení by bylo jinak vzhledem k tomu, že ztráta paketů není náhodné, ale můžete očekávat, že čekat mnohem méně pro DNS odpověď s tímto typem změnit.

Jak víte, mnoho systémových knihoven, které poskytují rozlišení DNS, jako je glibc, nscd, systemd-resolved, nejsou odolné tak, aby zvládnout to, že na internetu nebo v prostředí s ztráty paketů. Několikrát jsme čelili výzvě vytvoření spolehlivého a rychlého prostředí rozlišení DNS, jak jsme rostli, až později jsme zjistili, že řešení není dokonalé.

k vám

vzhledem k tomu, co jste četli v tomto článku o ztrátě paketů a rozdělení DNS/private-namespace, jak byste navrhli rychlé a spolehlivé nastavení rozlišení? Jaký software byste použili a proč? Jaké změny ladění ze standardní konfigurace byste použili?