ProHoster > Blog > administracja > Historia o brakujących pakietach DNS od pomocy technicznej Google Cloud
Historia o brakujących pakietach DNS od pomocy technicznej Google Cloud
Z Edytora bloga Google: Czy zastanawiałeś się kiedyś, jak inżynierowie Google Cloud Technical Solutions (TSE) radzą sobie z Twoimi prośbami o pomoc? Inżynierowie pomocy technicznej TSE są odpowiedzialni za identyfikowanie i korygowanie źródeł problemów zgłaszanych przez użytkowników. Niektóre z tych problemów są dość proste, ale czasami można spotkać się z problemem wymagającym uwagi kilku inżynierów jednocześnie. W tym artykule jeden z pracowników TSE opowie nam o jednym bardzo trudnym problemie ze swojej niedawnej praktyki - w przypadku brakujących pakietów DNS. W tej historii zobaczymy, jak inżynierom udało się rozwiązać tę sytuację i jakich nowych rzeczy nauczyli się podczas naprawiania błędu. Mamy nadzieję, że ta historia nie tylko poinformuje Cię o głęboko zakorzenionym błędzie, ale także umożliwi wgląd w procesy związane ze złożeniem zgłoszenia do pomocy technicznej w Google Cloud.
Rozwiązywanie problemów jest zarówno nauką, jak i sztuką. Wszystko zaczyna się od zbudowania hipotezy na temat przyczyny niestandardowego zachowania systemu, po czym jest on testowany pod kątem wytrzymałości. Zanim jednak postawimy hipotezę, musimy jasno zdefiniować i precyzyjnie sformułować problem. Jeśli pytanie brzmi zbyt niejasno, będziesz musiał wszystko dokładnie przeanalizować; Na tym polega „sztuka” rozwiązywania problemów.
W Google Cloud takie procesy stają się wykładniczo bardziej złożone, ponieważ Google Cloud dokłada wszelkich starań, aby zagwarantować prywatność swoich użytkowników. Z tego powodu inżynierowie TSE nie mają dostępu do edycji systemów ani możliwości przeglądania konfiguracji w tak szerokim zakresie, jak robią to użytkownicy. Dlatego też, aby przetestować którąkolwiek z naszych hipotez, my (inżynierowie) nie możemy szybko modyfikować systemu.
Niektórzy użytkownicy uważają, że naprawimy wszystko jak mechanik w serwisie samochodowym i po prostu prześlemy nam identyfikator wirtualnej maszyny, podczas gdy w rzeczywistości proces odbywa się w formie konwersacyjnej: zbieranie informacji, formułowanie i potwierdzanie (lub obalanie) hipotez, i ostatecznie problemy decyzyjne opierają się na komunikacji z klientem.
Przedmiotowy problem
Dziś mamy historię z dobrym zakończeniem. Jednym z powodów pomyślnego rozstrzygnięcia proponowanej sprawy jest bardzo szczegółowy i dokładny opis problemu. Poniżej możesz zobaczyć kopię pierwszego biletu (zredagowanego w celu ukrycia poufnych informacji):
Ta wiadomość zawiera wiele przydatnych dla nas informacji:
Określono konkretną maszynę wirtualną
Wskazany jest sam problem - DNS nie działa
Wskazane jest, gdzie objawia się problem - maszyna wirtualna i kontener
Wskazane są kroki podjęte przez użytkownika w celu zidentyfikowania problemu.
Zgłoszenie zostało zarejestrowane jako „P1: Wpływ krytyczny – Usługa nie nadaje się do użytku produkcyjnego”, co oznacza stały monitoring sytuacji 24/7 zgodnie ze schematem „Follow the Sun” (więcej można przeczytać nt. priorytety żądań użytkowników), z jego przeniesieniem z jednego zespołu wsparcia technicznego do drugiego przy każdej zmianie strefy czasowej. Tak naprawdę, zanim problem dotarł do naszego zespołu w Zurychu, okrążył już cały świat. Do tego czasu użytkownik podjął działania łagodzące, ale obawiał się powtórzenia sytuacji na produkcji, ponieważ pierwotna przyczyna nie została jeszcze odkryta.
Zanim bilet dotarł do Zurychu, mieliśmy już pod ręką następujące informacje:
Treść /etc/hosts
Treść /etc/resolv.conf
Wniosek iptables-save
Zmontowane przez zespół ngrep plik pcap
Mając te dane, mogliśmy rozpocząć fazę „dochodzenia” i rozwiązywania problemów.
Nasze pierwsze kroki
W pierwszej kolejności sprawdziliśmy logi i status serwera metadanych oraz upewniliśmy się, że działa on poprawnie. Serwer metadanych odpowiada na adres IP 169.254.169.254 i odpowiada m.in. za kontrolowanie nazw domen. Sprawdziliśmy również dwukrotnie, czy zapora sieciowa działa poprawnie z maszyną wirtualną i nie blokuje pakietów.
To był jakiś dziwny problem: sprawdzenie nmap obaliło naszą główną hipotezę o utracie pakietów UDP, więc w myślach wymyśliliśmy kilka dodatkowych opcji i sposobów ich sprawdzenia:
Czy pakiety są odrzucane selektywnie? => Sprawdź reguły iptables
Czy nie jest za mały? MTU? => Sprawdź wyjście ip a show
Czy problem dotyczy tylko pakietów UDP, czy też TCP? => Odjedź dig +tcp
Czy pakiety wygenerowane przez dig są zwracane? => Odjedź tcpdump
Czy libdns działa poprawnie? => Odjedź strace aby sprawdzić transmisję pakietów w obu kierunkach
Tutaj decydujemy się zadzwonić do użytkownika, aby rozwiązać problemy na żywo.
Podczas rozmowy jesteśmy w stanie sprawdzić kilka rzeczy:
Po kilku sprawdzeniach wykluczamy reguły iptables z listy przyczyn
Sprawdzamy interfejsy sieciowe i tablice routingu oraz dwukrotnie sprawdzamy, czy MTU jest prawidłowe
Odkrywamy to dig +tcp google.com (TCP) działa tak, jak powinien, ale dig google.com (UDP) nie działa
Po odjechaniu tcpdump podczas pracy dig, stwierdzamy, że pakiety UDP są zwracane
Odjeżdżamy strace dig google.com i widzimy, jak dig poprawnie wywołuje sendmsg() и recvms(), jednakże drugi zostaje przerwany przez przekroczenie limitu czasu
Niestety nadchodzi koniec zmiany i zmuszeni jesteśmy eskalować problem do kolejnej strefy czasowej. Zgłoszenie wzbudziło jednak zainteresowanie naszego zespołu i kolega sugeruje utworzenie początkowego pakietu DNS przy użyciu modułu 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())
Ten fragment tworzy pakiet DNS i wysyła żądanie do serwera metadanych.
Użytkownik uruchamia kod, zwracana jest odpowiedź DNS, a aplikacja ją odbiera, potwierdzając, że na poziomie sieci nie ma problemu.
Po kolejnej „podróży dookoła świata” prośba wraca do naszego zespołu, a ja całkowicie przekazuję ją sobie, myśląc, że dla użytkownika będzie wygodniej, jeśli prośba przestanie krążyć z miejsca na miejsce.
W międzyczasie użytkownik wyraża zgodę na dostarczenie migawki obrazu systemu. To bardzo dobra wiadomość: możliwość samodzielnego przetestowania systemu znacznie przyspiesza rozwiązywanie problemów, ponieważ nie muszę już prosić użytkownika o uruchamianie poleceń, przesyłanie mi wyników i ich analizę, wszystko mogę zrobić sam!
Koledzy zaczynają mi trochę pozazdrościć. Podczas lunchu omawiamy konwersję, ale nikt nie ma pojęcia, co się dzieje. Na szczęście użytkownik sam podjął już działania mające na celu złagodzenie skutków i nie spieszy się, więc mamy czas na przeanalizowanie problemu. A skoro mamy obraz, to możemy przeprowadzić dowolne testy, które nas interesują. Świetnie!
Cofając się o krok
Jedno z najpopularniejszych pytań podczas rozmów kwalifikacyjnych na stanowiska inżynierów systemów brzmi: „Co się stanie, gdy wykonasz polecenie ping www.google.com? Pytanie jest świetne, ponieważ kandydat musi opisać wszystko, od powłoki po przestrzeń użytkownika, jądro systemu, a następnie sieć. Uśmiecham się: czasami pytania na rozmowie kwalifikacyjnej okazują się przydatne w prawdziwym życiu...
Postanawiam zastosować to pytanie HR do bieżącego problemu. Z grubsza rzecz biorąc, gdy próbujesz określić nazwę DNS, dzieje się co następuje:
Aplikacja wywołuje bibliotekę systemową, taką jak libdns
libdns sprawdza konfigurację systemu, z którym serwerem DNS ma się skontaktować (na schemacie jest to 169.254.169.254, serwer metadanych)
libdns używa wywołań systemowych do utworzenia gniazda UDP (SOKET_DGRAM) i wysyłania pakietów UDP z zapytaniem DNS w obu kierunkach
Poprzez interfejs sysctl możesz skonfigurować stos UDP na poziomie jądra
Jądro współdziała ze sprzętem, aby przesyłać pakiety w sieci za pośrednictwem interfejsu sieciowego
Hypervisor przechwytuje i przesyła pakiet do serwera metadanych po kontakcie z nim
Serwer metadanych w magiczny sposób określa nazwę DNS i zwraca odpowiedź w ten sam sposób
Przypomnę, jakie hipotezy już rozważaliśmy:
Hipoteza: uszkodzone biblioteki
Test 1: uruchom strace w systemie, sprawdź, czy dig wywołuje właściwe wywołania systemowe
Wynik: wywoływane są prawidłowe wywołania systemowe
Test 2: za pomocą srapy sprawdzimy, czy uda nam się ustalić nazwy z pominięciem bibliotek systemowych
Wynik: możemy
Test 3: uruchom polecenierpm –V na plikach pakietu libdns i biblioteki md5sum
Wynik: kod biblioteki jest całkowicie identyczny z kodem w działającym systemie operacyjnym
Test 4: zamontuj obraz systemu root użytkownika na maszynie wirtualnej bez takiego zachowania, uruchom chroot i sprawdź, czy DNS działa
Wynik: DNS działa poprawnie
Wnioski na podstawie testów: problem nie leży w bibliotekach
Hipoteza: Wystąpił błąd w ustawieniach DNS
Test 1: sprawdź tcpdump i sprawdź, czy pakiety DNS są poprawnie wysyłane i zwracane po uruchomieniu dig
Wynik: pakiety są przesyłane poprawnie
Test 2: sprawdź dwukrotnie serwer /etc/nsswitch.conf и /etc/resolv.conf
Wynik: wszystko jest w porządku
Wnioski na podstawie testów: problem nie leży w konfiguracji DNS
Hipoteza: uszkodzony rdzeń
Testuj: zainstaluj nowe jądro, sprawdź podpis, uruchom ponownie
Wynik: podobne zachowanie
Wnioski na podstawie testów: jądro nie jest uszkodzone
Hipoteza: nieprawidłowe zachowanie sieci użytkownika (lub interfejsu sieciowego hypervisora)
Test 1: Sprawdź ustawienia zapory sieciowej
Wynik: zapora przekazuje pakiety DNS zarówno na hoście, jak i na GCP
Test 2: przechwytuje ruch i monitoruje poprawność transmisji oraz zwrot żądań DNS
Wynik: tcpdump potwierdza, że host odebrał pakiety zwrotne
Wnioski na podstawie testów: problem nie leży w sieci
Hipoteza: serwer metadanych nie działa
Test 1: sprawdź dzienniki serwera metadanych pod kątem anomalii
Wynik: w logach nie ma żadnych anomalii
Test 2: Omiń serwer metadanych poprzez dig @8.8.8.8
Wynik: rozdzielczość jest przerywana nawet bez użycia serwera metadanych
Wnioski na podstawie testów: problem nie dotyczy serwera metadanych
Podsumowując: przetestowaliśmy wszystkie podsystemy z wyjątkiem ustawienia czasu wykonania!
Zanurz się w ustawieniach środowiska wykonawczego jądra
Aby skonfigurować środowisko wykonawcze jądra, możesz użyć opcji wiersza poleceń (grub) lub interfejsu sysctl. Zajrzałem /etc/sysctl.conf i pomyśl, odkryłem kilka ustawień niestandardowych. Czując, że się czegoś złapałem, odrzuciłem wszystkie ustawienia inne niż sieciowe lub inne niż TCP, pozostając przy ustawieniach górskich net.core. Następnie udałem się do miejsca, gdzie w maszynie wirtualnej znajdowały się uprawnienia hosta i zacząłem stosować ustawienia jeden po drugim, na uszkodzonej maszynie wirtualnej, aż znalazłem winowajcę:
net.core.rmem_default = 2147483647
Oto konfiguracja łamiąca DNS! Znalazłem narzędzie zbrodni. Ale dlaczego tak się dzieje? Nadal potrzebowałem motywu.
Podstawowy rozmiar bufora pakietów DNS konfiguruje się za pomocą net.core.rmem_default. Typowa wartość wynosi około 200 KB, ale jeśli Twój serwer odbiera dużo pakietów DNS, możesz chcieć zwiększyć rozmiar bufora. Jeśli bufor będzie pełny w momencie nadejścia nowego pakietu, na przykład dlatego, że aplikacja nie przetwarza go wystarczająco szybko, zaczniesz tracić pakiety. Nasz klient poprawnie zwiększył rozmiar bufora, gdyż obawiał się utraty danych, gdyż korzystał z aplikacji do zbierania metryk poprzez pakiety DNS. Wartość, którą ustawił, była maksymalna możliwa: 231-1 (jeśli ustawiona na 231, jądro zwróci „NIEPRAWIDŁOWY ARGUMENT”).
Nagle zdałem sobie sprawę, dlaczego nmap i scapy działały poprawnie: używały surowych gniazd! Surowe gniazda różnią się od zwykłych gniazd: omijają iptables i nie są buforowane!
Ale dlaczego „bufor zbyt duży” powoduje problemy? To wyraźnie nie działa zgodnie z przeznaczeniem.
W tym momencie mogłem odtworzyć problem w wielu jądrach i wielu dystrybucjach. Problem pojawił się już w jądrze 3.x, a teraz pojawił się także w jądrze 5.x.
Rzeczywiście, przy uruchomieniu
sysctl -w net.core.rmem_default=$((2**31-1))
DNS przestał działać.
Zacząłem szukać wartości roboczych za pomocą prostego algorytmu wyszukiwania binarnego i odkryłem, że system działał z 2147481343, ale ta liczba była dla mnie bezsensownym zbiorem liczb. Zasugerowałem klientowi wypróbowanie tego numeru, a on odpowiedział, że system działa z google.com, ale nadal wyświetla błąd w innych domenach, więc kontynuowałem dochodzenie.
zainstalowałem dropwatch, narzędzie, którego należało użyć wcześniej: pokazuje dokładnie, gdzie w jądrze trafia pakiet. Winowajcą była funkcja udp_queue_rcv_skb. Pobrałem źródła jądra i dodałem kilka funkcjeprintk aby śledzić, gdzie dokładnie trafia pakiet. Szybko znalazłem odpowiedni stan if, i po prostu przez jakiś czas się na to gapiłem, bo wtedy wszystko w końcu ułożyło się w całość: 231-1, liczba bez znaczenia, niedziałająca domena... To był kawałek kodu w __udp_enqueue_schedule_skb:
if (rmem > (size + sk->sk_rcvbuf))
goto uncharge_drop;
Proszę zwrócić uwagę:
rmem jest typu int
size jest typu u16 (szesnastobitowy int bez znaku) i przechowuje rozmiar pakietu
sk->sk_rcybuf jest typu int i przechowuje rozmiar bufora, który z definicji jest równy wartości in net.core.rmem_default
Kiedy sk_rcvbuf zbliża się do 231, co może skutkować zsumowaniem rozmiaru pakietu całkowitą przepełnienie. A ponieważ jest to int, jego wartość staje się ujemna, więc warunek staje się prawdziwy, gdy powinien być fałszywy (więcej na ten temat możesz przeczytać na stronie powiązanie).
Błąd można skorygować w banalny sposób: poprzez rzutowanie unsigned int. Zastosowałem poprawkę i ponownie uruchomiłem system, a DNS znów zaczął działać.
Smak zwycięstwa
Wyniki przekazałem klientowi i wysłałem LKML poprawka do jądra. Jestem zadowolony: każdy element układanki pasuje do siebie, potrafię dokładnie wyjaśnić, dlaczego zaobserwowaliśmy to, co zaobserwowaliśmy, a co najważniejsze, dzięki pracy zespołowej udało nam się znaleźć rozwiązanie problemu!
Warto przyznać, że przypadek okazał się rzadki i na szczęście rzadko otrzymujemy od użytkowników tak skomplikowane żądania.