Una storia sui pacchetti DNS mancanti dal supporto tecnico di Google Cloud

Dall'editor del blog di Google: Ti sei mai chiesto in che modo gli ingegneri di Google Cloud Technical Solutions (TSE) gestiscono le tue richieste di supporto? I tecnici del supporto tecnico TSE sono responsabili dell'identificazione e della correzione delle fonti di problemi segnalate dagli utenti. Alcuni di questi problemi sono piuttosto semplici, ma a volte ti imbatti in un ticket che richiede l'attenzione di più ingegneri contemporaneamente. In questo articolo, uno dei dipendenti TSE ci parlerà di un problema molto complicato riscontrato nel suo recente studio: caso di pacchetti DNS mancanti. In questa storia vedremo come gli ingegneri sono riusciti a risolvere la situazione e quali cose nuove hanno imparato risolvendo l'errore. Ci auguriamo che questa storia non solo ti illustri un bug profondamente radicato, ma ti dia anche informazioni sui processi necessari per presentare un ticket di supporto con Google Cloud.

Una storia sui pacchetti DNS mancanti dal supporto tecnico di Google Cloud

La risoluzione dei problemi è sia una scienza che un'arte. Tutto inizia con la costruzione di un'ipotesi sul motivo del comportamento non standard del sistema, dopodiché ne viene testata la resistenza. Tuttavia, prima di formulare un'ipotesi, dobbiamo definire chiaramente e formulare con precisione il problema. Se la domanda ti sembra troppo vaga, allora dovrai analizzare tutto con attenzione; Questa è l'"arte" della risoluzione dei problemi.

Con Google Cloud, tali processi diventano esponenzialmente più complessi, poiché Google Cloud fa del suo meglio per garantire la privacy dei suoi utenti. Per questo motivo, gli ingegneri TSE non hanno accesso per modificare i tuoi sistemi, né la possibilità di visualizzare le configurazioni in modo così ampio come fanno gli utenti. Pertanto, per testare qualsiasi delle nostre ipotesi, noi (ingegneri) non possiamo modificare rapidamente il sistema.

Alcuni utenti credono che sistemeremo tutto come i meccanici in un servizio di auto, e ci invieremo semplicemente l'ID di una macchina virtuale, mentre in realtà il processo avviene in formato conversazionale: raccogliendo informazioni, formando e confermando (o confutando) ipotesi, e, alla fine, i problemi decisionali si basano sulla comunicazione con il cliente.

Problema in questione

Oggi abbiamo una storia con un bel finale. Uno dei motivi per la riuscita risoluzione del caso proposto è una descrizione molto dettagliata e accurata del problema. Di seguito puoi vedere una copia del primo ticket (modificato per nascondere le informazioni riservate):
Una storia sui pacchetti DNS mancanti dal supporto tecnico di Google Cloud
Questo messaggio contiene molte informazioni utili per noi:

  • VM specifica specificata
  • Il problema stesso è indicato: il DNS non funziona
  • Viene indicato dove si manifesta il problema: VM e contenitore
  • Vengono indicati i passaggi eseguiti dall'utente per identificare il problema.

La richiesta è stata registrata come “P1: Impatto Critico - Servizio Inutilizzabile in produzione”, che significa monitoraggio costante della situazione 24 ore su 7, XNUMX giorni su XNUMX secondo lo schema “Follow the Sun” (puoi leggere di più su priorità delle richieste degli utenti), con il relativo trasferimento da un team di supporto tecnico all'altro ad ogni cambio di fuso orario. In effetti, quando il problema ha raggiunto il nostro team a Zurigo, aveva già fatto il giro del mondo. A questo punto l'utente aveva adottato misure di mitigazione, ma temeva che la situazione si ripetesse nella produzione, poiché la causa principale non era stata ancora scoperta.

Quando il biglietto è arrivato a Zurigo avevamo già a portata di mano le seguenti informazioni:

  • Contenuto /etc/hosts
  • Contenuto /etc/resolv.conf
  • conclusione iptables-save
  • Assemblato dal team ngrep pcap

Con questi dati eravamo pronti per iniziare la fase di “indagine” e di risoluzione dei problemi.

I nostri primi passi

Prima di tutto, abbiamo controllato i log e lo stato del server dei metadati e ci siamo assicurati che funzionasse correttamente. Il server dei metadati risponde all'indirizzo IP 169.254.169.254 ed è responsabile, tra le altre cose, del controllo dei nomi di dominio. Abbiamo anche ricontrollato che il firewall funzioni correttamente con la VM e non blocchi i pacchetti.

Si è trattato di un problema strano: il controllo nmap ha confutato la nostra ipotesi principale sulla perdita di pacchetti UDP, quindi abbiamo pensato mentalmente a molte altre opzioni e modi per controllarli:

  • I pacchetti vengono eliminati in modo selettivo? => Controlla le regole di iptables
  • Non è troppo piccolo? MTU? => Controlla l'output ip a show
  • Il problema riguarda solo i pacchetti UDP o anche TCP? => Allontanati dig +tcp
  • Vengono restituiti i pacchetti generati dal dig? => Allontanati tcpdump
  • libdns funziona correttamente? => Allontanati strace per verificare la trasmissione dei pacchetti in entrambe le direzioni

Qui decidiamo di chiamare l'utente per risolvere i problemi in diretta.

Durante la chiamata possiamo verificare diverse cose:

  • Dopo diversi controlli escludiamo le regole iptables dall'elenco dei motivi
  • Controlliamo le interfacce di rete e le tabelle di routing e ricontrolliamo che la MTU sia corretta
  • Lo scopriamo dig +tcp google.com (TCP) funziona come dovrebbe, ma dig google.com (UDP) non funziona
  • Essendosi allontanato tcpdump mentre lavoro dig, scopriamo che vengono restituiti pacchetti UDP
  • Andiamo via strace dig google.com e vediamo come dig chiama correttamente sendmsg() и recvms(), tuttavia il secondo viene interrotto da un timeout

Purtroppo arriva la fine del turno e siamo costretti a intensificare il problema al fuso orario successivo. La richiesta, tuttavia, ha suscitato interesse nel nostro team e un collega suggerisce di creare il pacchetto DNS iniziale utilizzando il modulo scrapy 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())

Questo frammento crea un pacchetto DNS e invia la richiesta al server dei metadati.

L'utente esegue il codice, viene restituita la risposta DNS e l'applicazione la riceve, confermando che non ci sono problemi a livello di rete.

Dopo un altro "viaggio intorno al mondo", la richiesta ritorna al nostro team e la trasferisco completamente a me stesso, pensando che sarà più conveniente per l'utente se la richiesta smetterà di circolare da un posto all'altro.

Nel frattempo, l'utente si impegna gentilmente a fornire un'istantanea dell'immagine del sistema. Questa è un'ottima notizia: la possibilità di testare personalmente il sistema rende la risoluzione dei problemi molto più rapida, perché non devo più chiedere all'utente di eseguire comandi, inviarmi i risultati e analizzarli, posso fare tutto da solo!

I miei colleghi cominciano un po’ a invidiarmi. Durante il pranzo discutiamo della conversione, ma nessuno ha idea di cosa stia succedendo. Fortunatamente, l'utente stesso ha già adottato misure per mitigare le conseguenze e non ha fretta, quindi abbiamo tempo per analizzare il problema. E poiché abbiamo un'immagine, possiamo eseguire tutti i test che ci interessano. Grande!

Fare un passo indietro

Una delle domande più popolari nei colloqui per le posizioni di ingegnere di sistema è: “Cosa succede quando esegui il ping www.google.com? La domanda è fantastica, poiché il candidato deve descrivere tutto, dalla shell allo spazio utente, al kernel del sistema e quindi alla rete. Sorrido: a volte le domande dei colloqui si rivelano utili nella vita reale...

Decido di applicare questa domanda sulle risorse umane a un problema attuale. In parole povere, quando si tenta di determinare un nome DNS, accade quanto segue:

  1. L'applicazione richiama una libreria di sistema come libdns
  2. libdns controlla la configurazione del sistema a quale server DNS deve contattare (nel diagramma questo è 169.254.169.254, server di metadati)
  3. libdns utilizza le chiamate di sistema per creare un socket UDP (SOKET_DGRAM) e inviare pacchetti UDP con una query DNS in entrambe le direzioni
  4. Attraverso l'interfaccia sysctl è possibile configurare lo stack UDP a livello del kernel
  5. Il kernel interagisce con l'hardware per trasmettere pacchetti sulla rete tramite l'interfaccia di rete
  6. L'hypervisor cattura e trasmette il pacchetto al server dei metadati non appena entra in contatto con esso
  7. Il server dei metadati, per magia, determina il nome DNS e restituisce una risposta utilizzando lo stesso metodo

Una storia sui pacchetti DNS mancanti dal supporto tecnico di Google Cloud
Permettetemi di ricordarvi quali ipotesi abbiamo già considerato:

Ipotesi: librerie danneggiate

  • Test 1: esegui strace nel sistema, controlla che dig chiami le chiamate di sistema corrette
  • Risultato: vengono chiamate le chiamate di sistema corrette
  • Test 2: utilizzare srapy per verificare se è possibile determinare i nomi ignorando le librerie di sistema
  • Risultato: possiamo
  • Test 3: esegui rpm –V sul pacchetto libdns e sui file di libreria md5sum
  • Risultato: il codice della libreria è completamente identico al codice nel sistema operativo funzionante
  • Test 4: monta l'immagine del sistema root dell'utente su una VM senza questo comportamento, esegui chroot, verifica se il DNS funziona
  • Risultato: il DNS funziona correttamente

Conclusione basata sui test: il problema non è nelle biblioteche

Ipotesi: c'è un errore nelle impostazioni DNS

  • Test 1: controlla tcpdump e verifica se i pacchetti DNS vengono inviati e restituiti correttamente dopo aver eseguito dig
  • Risultato: i pacchetti vengono trasmessi correttamente
  • Test 2: doppio controllo sul server /etc/nsswitch.conf и /etc/resolv.conf
  • Risultato: tutto è corretto

Conclusione basata sui test: il problema non riguarda la configurazione DNS

Ipotesi: nucleo danneggiato

  • Test: installa il nuovo kernel, controlla la firma, riavvia
  • Risultato: comportamento simile

Conclusione basata sui test: il kernel non è danneggiato

Ipotesi: comportamento errato della rete utente (o interfaccia di rete hypervisor)

  • Test 1: controlla le impostazioni del firewall
  • Risultato: il firewall passa i pacchetti DNS sia sull'host che su GCP
  • Test 2: intercettare il traffico e monitorare la correttezza della trasmissione e restituzione delle richieste DNS
  • Risultato: tcpdump conferma che l'host ha ricevuto i pacchetti di ritorno

Conclusione basata sui test: il problema non è nella rete

Ipotesi: il server dei metadati non funziona

  • Test 1: verificare la presenza di anomalie nei log del server dei metadati
  • Risultato: non ci sono anomalie nei log
  • Test 2: bypassare il server dei metadati tramite dig @8.8.8.8
  • Risultato: la risoluzione viene interrotta anche senza utilizzare un server di metadati

Conclusione basata sui test: il problema non riguarda il server dei metadati

La linea di fondo: abbiamo testato tutti i sottosistemi tranne impostazioni di esecuzione!

Immersione nelle impostazioni di runtime del kernel

Per configurare l'ambiente di esecuzione del kernel, è possibile utilizzare le opzioni della riga di comando (grub) o l'interfaccia sysctl. Ho esaminato /etc/sysctl.conf e pensa, ho scoperto diverse impostazioni personalizzate. Sentendomi come se mi fossi aggrappato a qualcosa, ho scartato tutte le impostazioni non di rete o non TCP, rimanendo con le impostazioni di montagna net.core. Poi sono andato dove si trovavano le autorizzazioni dell'host nella VM e ho iniziato ad applicare le impostazioni una per una, una dopo l'altra, con la VM rotta, finché non ho trovato il colpevole:

net.core.rmem_default = 2147483647

Eccola, una configurazione che rompe i DNS! Ho trovato l'arma del delitto. Ma perché sta accadendo questo? Avevo ancora bisogno di un motivo.

La dimensione del buffer dei pacchetti DNS di base viene configurata tramite net.core.rmem_default. Un valore tipico è intorno ai 200 KiB, ma se il tuo server riceve molti pacchetti DNS, potresti voler aumentare la dimensione del buffer. Se il buffer è pieno quando arriva un nuovo pacchetto, ad esempio perché l'applicazione non lo elabora abbastanza velocemente, inizierai a perdere pacchetti. Il nostro cliente ha correttamente aumentato la dimensione del buffer perché temeva la perdita di dati, poiché stava utilizzando un'applicazione per la raccolta di parametri tramite pacchetti DNS. Il valore impostato era il massimo possibile: 231-1 (se impostato su 231, il kernel restituirà “INVALID ARGUMENT”).

All'improvviso ho capito perché nmap e scapy funzionavano correttamente: utilizzavano socket grezzi! I socket grezzi sono diversi dai socket normali: bypassano iptables e non sono bufferizzati!

Ma perché il "buffer troppo grande" causa problemi? Chiaramente non funziona come previsto.

A questo punto potrei riprodurre il problema su più kernel e più distribuzioni. Il problema era già apparso sul kernel 3.x e ora è comparso anche sul kernel 5.x.

Infatti, all'avvio

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

Il DNS ha smesso di funzionare.

Ho iniziato a cercare valori funzionanti attraverso un semplice algoritmo di ricerca binaria e ho scoperto che il sistema funzionava con 2147481343, ma questo numero per me era un insieme di numeri senza significato. Ho suggerito al cliente di provare questo numero e lui ha risposto che il sistema funzionava con google.com, ma dava ancora un errore con altri domini, quindi ho continuato la mia indagine.

Ho installato dropwatch, uno strumento che avrebbe dovuto essere usato prima: mostra esattamente dove finisce un pacchetto nel kernel. Il colpevole era la funzione udp_queue_rcv_skb. Ho scaricato i sorgenti del kernel e ne ho aggiunti alcuni funzione printk per tracciare dove finisce esattamente il pacchetto. Ho trovato subito la condizione giusta if, e l'ho semplicemente fissato per un po', perché è stato allora che tutto si è finalmente riunito in un quadro completo: 231-1, un numero senza significato, un dominio non funzionante... Era un pezzo di codice in __udp_enqueue_schedule_skb:

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

Si prega di notare:

  • rmem è di tipo int
  • size è di tipo u16 (int a sedici bit senza segno) e memorizza la dimensione del pacchetto
  • sk->sk_rcybuf è di tipo int e memorizza la dimensione del buffer che, per definizione, è uguale al valore in net.core.rmem_default

Quando sk_rcvbuf si avvicina a 231, la somma delle dimensioni del pacchetto potrebbe risultare overflow di numeri interi. E poiché è un int, il suo valore diventa negativo, quindi la condizione diventa vera quando dovrebbe essere falsa (puoi leggere di più a riguardo su collegamento).

L'errore si può correggere in modo banale: tramite casting unsigned int. Ho applicato la correzione e riavviato il sistema e il DNS ha funzionato di nuovo.

Sapore di vittoria

Ho inoltrato i miei risultati al cliente e ho inviato LKM patch del kernel. Sono soddisfatto: ogni pezzo del puzzle si incastra, posso spiegare esattamente perché abbiamo osservato ciò che abbiamo osservato e, soprattutto, siamo riusciti a trovare una soluzione al problema grazie al nostro lavoro di squadra!

Vale la pena riconoscere che il caso si è rivelato raro e, fortunatamente, raramente riceviamo richieste così complesse da parte degli utenti.

Una storia sui pacchetti DNS mancanti dal supporto tecnico di Google Cloud


Fonte: habr.com

Aggiungi un commento