Гісторыя аб зніклых DNS-пакетах ад тэхпадтрымкі Google Cloud

Ад рэдактара блога Google: Ці цікавіліся вы калі-небудзь тым, як інжынеры Google Cloud Technical Solutions (TSE) займаюцца вашымі зваротамі ў тэхпадтрымку? У сферы адказнасці інжынераў тэхнічнай падтрымкі TSE ляжыць выяўленне і ўхіленне паказаных карыстачамі крыніц праблем. Некаторыя з гэтых праблем даволі простыя, але часам трапляецца зварот, які патрабуе ўвагі адразу некалькіх інжынераў. У гэтым артыкуле адзін з супрацоўнікаў TSE раскажа нам пра адну вельмі выкручастую праблему са сваёй нядаўняй практыкі. выпадак з знікаючымі пакетамі DNS. У ходзе гэтага апавядання мы ўбачым, якім чынам інжынерам удалося вырашыць сітуацыю, і што новага яны даведаліся падчас ухілення памылкі. Мы спадзяемся, што гэтая гісторыя не толькі раскажа вам пра глыбока які ўкараніўся багу, але і дасць разуменне працэсаў, якія праходзяць пры падачы звароту ў падтрымку Google Cloud.

Гісторыя аб зніклых DNS-пакетах ад тэхпадтрымкі Google Cloud

Устараненне непаладак гэта адначасова і навука, і мастацтва. Усё пачынаецца з пабудовы гіпотэзы аб чынніку нестандартных паводзін сістэмы, пасля чаго яна правяраецца на трываласць. Аднак, перш чым сфармуляваць гіпотэзу, мы павінны дакладна вызначыць і дакладна сфармуляваць праблему. Калі пытанне гучыць занадта расплывіста то вам давядзецца як след усё прааналізаваць; у гэтым і складаецца «мастацтва» ухіленні непаладак.

Ва ўмовах Google Cloud падобныя працэсы ўскладняюцца ў разы, бо Google Cloud з усіх сіл імкнецца гарантаваць канфідэнцыяльнасць сваіх карыстачоў. З-за гэтага ў інжынераў TSE няма ні доступу да рэдагавання вашых сістэм, ні магчымасці гэтак жа шырока аглядаць канфігурацыі, як гэта робяць карыстачы. Таму для праверкі якой-небудзь з нашых гіпотэз мы (інжынеры) не можам хутка мадыфікаваць сістэму.

Некаторыя карыстачы мяркуюць, што мы выправім усё быццам механікі ў аўтасэрвісе, і папросту высылаюць нам id віртуальнай машыны, тады як у рэальнасці працэс працякае ў фармаце гутаркі: збор інфармацыі, фармаванне і пацверджанне (або абвяржэнне) гіпотэз, і, у выніку, рашэнне праблемы будуецца на зносінах з кліентам.

Разгляданая праблема

Сёння перад намі гісторыя з добрым канцом. Адна з прычын паспяховага дазволу прапанаванага кейса заключаецца ў вельмі дэталёвым і дакладным апісанні праблемы. Ніжэй можна ўбачыць копію першага цікета (адрэдагаванага, з мэтай схаваць канфідэнцыйную інфармацыю):
Гісторыя аб зніклых DNS-пакетах ад тэхпадтрымкі Google Cloud
У гэтым паведамленні вельмі шмат карыснай для нас інфармацыі:

  • Указана канкрэтная VM
  • Пазначана сама праблема - не працуе DNS
  • Указана дзе праблема сябе праяўляе - VM і кантэйнер
  • Указаны крокі, якія здзейсніў карыстальнік для вызначэння праблемы

Зварот быў зарэгістраваны як "P1: Critical Impact – Service Unusable in production", што азначае пастаянны кантроль сітуацыі 24/7 па схеме "Follow the Sun" (па спасылцы можна больш падрабязна пачытаць пра прыярытэты карыстацкіх зваротаў), з перадачай яе ад адной каманды тэхпадтрымкі да іншай пры кожным зруху гадзінных паясоў. Па сутнасці, да таго моманту як праблема дайшла да нашай каманды ў Цюрыху, яна паспела абмінуць зямны шар. Да гэтага часу карыстач прыняў меры па зніжэнні наступстваў, аднак баяўся паўтарэння сітуацыі на прадакшне, бо асноўная прычына ўсё яшчэ не была выяўлена.

Да моманту, калі тыкет дайшоў да Цюрыха, у нас на руках ужо была наступная інфармацыя:

  • змесціва /etc/hosts
  • змесціва /etc/resolv.conf
  • Выснова iptables-save
  • Сабраны камандай ngrep файл pcap

З гэтымі дадзенымі мы былі гатовыя прыступіць да этапу "расследавання" і ўхіленні непаладак.

Нашы першыя крокі

У першую чаргу мы праверылі логі і статут сервера метададзеных і пераканаліся што ён працуе карэктна. Сервер метададзеных адказвае IP адрасу 169.254.169.254 і, акрамя ўсяго іншага, адказвае за кантроль над імёнамі даменаў. Мы гэтак жа пераправерылі, што фаервол карэктна працуе з VM і не блакуе пакеты.

Гэта была нейкая дзіўная праблема: праверка nmap абвергла нашу асноўную гіпотэзу аб страце UDP пакетаў, таму мы ў думках вывелі яшчэ некалькі варыянтаў і спосабаў іх праверкі:

  • Ці знікаюць пакеты выбарачна? => Праверыць правілы iptables
  • Ці не занадта малы MTU? => Праверыць выснову ip a show
  • Ці закранае праблема толькі UDP-пакеты ці ж і TCP? => Прагнаць dig +tcp
  • Ці вяртаюцца згенераваныя dig пакеты? => Прагнаць tcpdump
  • Ці карэктна працуе libdns? => Прагнаць strace для праверкі перадачы пакетаў у абодва бакі

Тут мы вырашаем стэлефанавацца з карыстачом для ўхілення непаладак ужывую.

У ходзе званка нам удаецца праверыць некалькі рэчаў:

  • Пасля некалькіх праверак мы выключаем правілы iptables са спісу прычын
  • Мы правяраем інтэрфейсы сеткі і табліцы маршрутызацыі, і пераправярае карэктнасць MTU
  • Мы выяўляем што dig +tcp google.com (TCP) працуе як трэба, але вось dig google.com (UDP) не працуе
  • Прагнаўшы tcpdump пакуль працуе dig, мы выяўляем што UDP пакеты вяртаюцца
  • Мы праганяем strace dig google.com і бачым як dig карэктна выклікае sendmsg() и recvms(), аднак другі перарываецца па таймаўце

Да няшчасця, надыходзіць канец змены і мы змушаныя перадаць праблему ў наступную часавую зону. Зварот, тым не менш, выклікала ў нашай камандзе цікавасць, і калега прапануе стварыць зыходны DNS пакет пітонаўскім модулем scrapy.

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())

Гэты фрагмент стварае DNS пакет і адпраўляе запыце серверу метададзеных.

Карыстальнік запускае код, DNS адказ вяртаецца, і дадатак яго атрымлівае, што пацвярджае адсутнасць праблемы на ўзроўні сеткі.

Пасля чарговага «кругасветнага вандравання» зварот вяртаецца да нашай каманды, і я цалкам перакладаю яго на сябе, палічыўшы, што карыстачу будзе зручней, калі зварот перастане кружыць з месца на месца.

Тым часам, карыстач ласкава згаджаецца падаць здымак выявы сістэмы. Гэта вельмі добрыя навіны: магчымасць самому тэставаць сістэму значна паскарае ўхіленне непаладак, бо больш не трэба прасіць карыстача запускаць каманды, высылаць мне вынікі і аналізаваць іх, я магу ўсё зрабіць сам!

Калегі пачынаюць мне пакрысе зайздросціць. За абедам мы абмяркоўваем зварот, аднак ні ў кога няма ідэй аб тым, што ж адбываецца. На шчасце, сам карыстач ужо прыняў меры па змякчэнні наступстваў і нікуды не спяшаецца, так што ў нас ёсць час прэпараваць праблему. І так як у нас ёсць вобраз, мы можам правесці любыя якія цікавяць нас тэсты. Выдатна!

Вяртаючыся на крок назад

Адно з самых папулярных пытанняў на інтэрв'ю на пасаду сістэмнага інжынера гучыць так: «Што адбываецца, калі вы пінгуеце www.google.com?» Пытанне шыкоўнае, бо кандыдату неабходна апісаць няхай ад абалонкі да карыстацкай прасторы, да ядра сістэмы і далей да сеткі. Я ўсміхаюся: часам пытанні з інтэрв'ю аказваюцца карыснымі і ў рэальным жыцці…

Я вырашаю прымяніць гэтае эйчарскае пытанне да бягучай праблемы. Грубіянска кажучы, калі вы спрабуеце вызначыць DNS імя, адбываецца наступнае:

  1. Прыкладанне выклікае сістэмную бібліятэку, напрыклад libdns
  2. libdns правярае канфігурацыю сістэмы да якога DNS серверу ёй звяртацца (на дыяграме гэта 169.254.169.254, сервер метададзеных)
  3. libdns выкарыстоўвае сістэмныя выклікі для стварэння UDP сокета (SOKET_DGRAM) і перадачы UDP пакетаў з DNS запытам у абодва бакі
  4. Праз інтэрфейс sysctl можна наладзіць UDP стэк на ўзроўні ядра
  5. Ядро ўзаемадзейнічае з жалезам для перадачы пакетаў па сетцы праз сеткавы інтэрфейс
  6. Гіпервізар ловіць і перадае пакет серверу метададзеных пры кантакце з ім
  7. Сервер метададзеных сваім вядзьмарствам вызначае DNS імя і такім жа метадам вяртае адказ

Гісторыя аб зніклых DNS-пакетах ад тэхпадтрымкі Google Cloud
Нагадаю, якія гіпотэзы мы ўжо паспелі разгледзець:

Гіпотэза: Зламаныя бібліятэкі

  • Тэст 1: прагнаць у сістэме strace, праверыць што dig выклікае карэктныя сістэмныя выклікі
  • Вынік: выклікаюцца карэктныя сістэмныя выклікі
  • Тэст 2: праз srapy праверыць ці можам мы вызначаць імёны ў абыход сістэмных бібліятэк
  • Вынік: можам
  • Тэст 3: прагнаць rpm -V на пакеце libdns і md5sum файлах бібліятэкі
  • Вынік: код бібліятэкі цалкам ідэнтычны коду ва ў працоўнай аперацыйнай сістэме
  • Тэст 4: мантаваць выяву каранёвай сістэмы карыстача на VM без падобных паводзін, прагнаць chroot, паглядзець ці працуе DNS
  • Вынік: DNS працуе карэктна

Выснова на аснове тэстаў: праблема не ў бібліятэках

Гіпотэза: Прысутнічае памылка ў наладах DNS

  • Тэст 1: праверыць tcpdump і праназіраць карэктна ці адпраўляюцца і вяртаюцца DNS пакеты пасля запуску dig
  • Вынік: пакеты перадаюцца карэктна
  • Тэст 2: пераправерыць на серверы /etc/nsswitch.conf и /etc/resolv.conf
  • Вынік: усё карэктна

Выснова на аснове тэстаў: праблема не ў канфігурацыі DNS

Гіпотэза: пашкоджана ядро

  • Тэст: усталяваць новае ядро, праверыць подпіс, перазапусціць
  • Вынік: аналагічныя паводзіны

Выснова на аснове тэстаў: ядро не пашкоджана

Гіпотэза: некарэктныя паводзіны карыстацкай сеткі (або інтэрфейсу сеткі гіпервізара)

  • Тэст 1: праверыць налады фаервола
  • Вынік: фаервол прапускае DNS пакеты і на хасце, і на GCP
  • Тэст 2: перахапіць трафік і адсачыць карэктнасць перадачы і вяртання DNS запытаў
  • Вынік: tcpdump пацвярджае атрыманне зваротных пакетаў хастом

Выснова на аснове тэстаў: праблема не ў сетцы

Гіпотэза: не працуе сервер метададзеных

  • Тэст 1: праверыць логіі сервера метададзеных на анамаліі
  • Вынік: у логах анамалій няма
  • Тэст 2: абыйсці сервер метададзеных праз dig @8.8.8.8
  • Вынік: дазвол парушаецца нават без выкарыстання сервера метададзеных

Выснова на аснове тэстаў: праблема не ў сэрвэры метададзеных

Вынік: мы пратэставалі ўсе падсістэмы акрамя налад асяроддзя выканання!

Апускаючыся ў налады асяроддзя выканання ядра

Для налады асяроддзя выканання ядра вы можаце скарыстацца опцыямі каманднага радка (grub) або інтэрфейсам sysctl. Я зазірнуў у /etc/sysctl.conf і падумаць толькі, выявіў некалькі кастамных налад. Адчуваючы быццам я ўхапіўся за нешта, я адмёў усе нясеткавыя ці не-tcp наладкі, застаўшыся з горскай налад net.core. Затым я звярнуўся туды, дзе ў VM ляжаць дазволы хаста і пачаў ужываць сябар за сябрам, адна за іншы налады са зламанай VM, пакуль не выйшаў на злачынца:

net.core.rmem_default = 2147483647

Вось яна, якая ламае DNS канфігурацыя! Я знайшоў прыладу злачынства. Але чаму гэта здараецца? Мне ўсё яшчэ патрэбен быў матыў.

Настройка базавага памеру буфера DNS пакетаў адбываецца праз net.core.rmem_default. Тыповае значэнне вар'іруецца дзесьці ў межах 200КіБ, аднак калі ваш сервер атрымлівае шмат DNS пакетаў, вы можаце павялічыць памер буфера. Калі ў момант паступлення новага пакета буфер поўны, напрыклад таму што прыкладанне нядосыць хутка яго апрацоўвае, тыя вы пачнеце губляць пакеты. Наш кліент правільна павялічыў памер буфера так як баяўся страт дадзеных, паколькі карыстаўся дадаткам па зборы метрык праз DNS пакеты. Значэнне, якое ён выставіў, было максімальна магчымым: 231-1 (калі выставіць 231, ядро ​​верне "INVALID ARGUMENT").

Раптам я зразумеў чаму nmap і scapy працавалі карэктна: яны выкарыстоўвалі волкія сокеты! Волкія сокеты адрозніваюцца ад звычайных: яны працуюць у абыход iptables, і яны не буферызуюцца!

Але чаму "занадта вялікі буфер" выклікае праблемы? Ён відавочна працуе не бо задумана.

Да гэтага моманту я мог прайграць праблему на некалькіх ядрах і мностве дыстрыбутываў. Праблема ўжо выяўлялася на ядры 3.х і зараз гэтак жа выяўлялася на ядры 5.х.

Сапраўды, пры запуску

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

DNS пераставаў працаваць.

Я пачаў шукаць працоўныя значэнні праз просты алгарытм двайковага пошуку і выявіў што з 2147481343 сістэма працуе, аднак гэты лік быў для мяне бессэнсоўным наборам лічбаў. Я прапанаваў кліенту паспрабаваць гэты лік, і ён адказаў што сістэма зарабіла з google.com, але ўсё яшчэ выдавала памылку з іншымі даменамі, таму я працягнуў маё расследаванне.

Я ўсталяваў dropwatch, інструмент, якім каштавала скарыстацца раней: ён паказвае куды менавіта ў ядры пападае пакет. Вінаватай аказалася функцыя udp_queue_rcv_skb. Я запампаваў зыходнікі ядра і дадаў некалькі функцый printk каб адсочваць куды канкрэтна пападае пакет. Я хутка знайшоў патрэбную ўмову if, і некаторы час папросту тарашчыўся на яго, бо менавіта тады ўсё нарэшце сышлося ў суцэльную карціну: 231-1, бессэнсоўны лік, непрацуючы дамен… Справа была ў кавалку кода ў __udp_enqueue_schedule_skb:

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

Звярніце ўвагу:

  • rmem мае тып int
  • size мае тып u16 (непадпісаны шаснаццацібітны int) і захоўвае памер пакета
  • sk->sk_rcybuf мае тып int і захоўвае памер буфера які па вызначэнні роўны значэнню ў net.core.rmem_default

Калі sk_rcvbuf набліжаецца да 231, сумаванне памеру пакета можа прывесці да цэлалікаваму перапаўненню. І бо гэта int, яго значэнне становіцца адмоўным, такім чынам умова становіцца праўдзівым калі павінна быць ілжывым (больш пра гэта можна пазнаць па спасылцы).

Памылка выпраўляецца трывіяльнай выявай: прывядзеннем да unsigned int. Я ўжыў выпраўленне і перазапусціў сістэму, пасля чаго DNS ізноў зарабіў.

Густ перамогі

Я пераслаў мае знаходкі кліенту і выслаў LKML патч ядра. Я задаволены: кожны кавалачак галаваломкі сышоўся ў адзінае цэлае, я магу дакладна растлумачыць чаму мы назіралі тое, што мы назіралі, і што самае галоўнае, мы змаглі знайсці рашэнне праблемы дзякуючы сумеснай працы!

Варта прызнаць, што выпадак аказаўся рэдкім, і на шчасце да нас ад карыстальнікаў рэдка паступаюць настолькі складаныя звароты.

Гісторыя аб зніклых DNS-пакетах ад тэхпадтрымкі Google Cloud


Крыніца: habr.com

Дадаць каментар