
Tere kõigile! Minu nimi on Dmitri Samsonov, ma töötan juhtiva süsteemiadministraatorina «Odnoklassnikoses». Meil on üle 7 tuhande füüsilise serveri, 11 tuhat konteinerit meie pilves ja 200 rakendust, mis erinevates konfiguratsioonides moodustavad 700 erinevat klastrit. Enamik serveritest töötab CentOS 7 haldusel.
14. augustil 2018 avaldati teave FragmentSmack haavatavuse kohta
() ja SegmentSmack (). Need on haavatavused, millel on võrgu rünnakute vektor ja suhteliselt kõrge hinnang (7.5), mis toob kaasa teenuse katkestamise (DoS) ressursside (CPU) ammendumise tõttu. FragmentSmack'i parandust ei olnud sellel ajal tuumaprogrammi jaoks pakutud, ja rohkemgi veel, see ilmus märkimisväärselt hiljem pärast haavatavuse teabe avaldamist. SegmentSmack'i likvideerimiseks soovitati tuuma uuendada. Uuenduspakk oli välja antud samal päeval, jäi vaid see installida.
Ei, me ei ole tuuma uuendamise vastu! Kuid on teatud nüansse…
Kuidas me uuendame tuuma tootmisjärgus
Üldiselt pole midagi keerulist:
- Lae alla pakette;
- Installi need mitmesse serverisse (sealhulgas serveritesse, mis majutavad meie pilve);
- Veendu, et midagi pole katki läinud;
- Veendu, et kõik vaikeseaded ei rakendatud tõrgeteta;
- Oota paar päeva;
- Kontrolli serverite näitajaid;
- Lülita uute serverite loomine uuele tuumale;
- Uuenda kõik serverid andmekeskuste kaupa (üks andmekeskus korraga, et minimeerida mõju kasutajatele probleemide korral);
- Taaskäivita kõik serverid.
Korda kõigi meie tuumade harude osas. Praegu on need:
- Kaupluste CentOS 7 3.10 — enamikule tavalisest serverist;
- Puhas 4.19 — meie , sest me vajame BFQ, BBR jne;
- Elrepo kernel-ml 5.2 — jaoks , kuna 4.19 käitus varem ebastabiilselt ja funktsioonid on samad.
Nagu võisite arvata, kulub kõige rohkem aega tuhande serveri taaskäivitamiseks. Kuna kõik haavatavused ei ole kõigile serveritele kriitilised, taaskäivitame vaid need, mis on otse interneti kaudu ligipääsetavad. Pilves, et mitte piirata paindlikkust, ei seosta me väljastpoolt kergelt ligipääsetavaid konteinerite eraldi serveritega uue tuuma jaoks; taaskäivitame kõik hostid ilma eranditeta. Õnneks on seal protseduur lihtsam kui tavaliste serverite puhul. Näiteks saavad stateless-konteinerid taaskäivitamise ajal lihtsalt teisele serverile üle minna.
Siiski on tööd ikkagi palju ja see võib võtta mitu nädalat, ning kui uusi versioone esineb probleeme, siis kuni mitu kuud. Kurjategijad mõistavad seda suurepäraselt, seega on vaja plaani "B".
FragmentSmack/SegmentSmack. Tööaluste lahenduste süsteem
Õnneks mõnede haavatavuste puhul selline plaan "B" olemas ja seda nimetatakse Workaround'iks. Kõige sagedamini hõlmab see tuuma/rakenduste seadistuste muutmist, mis võimaldavad minimeerida võimalikke mõjusid või täielikult välistada haavatavuste ära kasutamist.
FragmentSmack/SegmentSmack'i puhul sellist Workaround'i:
«Saate muuta vaikeseadeid 4MB ja 3MB net.ipv4.ipfrag_high_thresh ja net.ipv4.ipfrag_low_thresh (ja nende analoogid ipv6 net.ipv6.ipfrag_high_thresh ja net.ipv6.ipfrag_low_thresh) 256 kB ja 192 kB-ni või madalamale. Testid näitavad, et CPU kasutus võib rünnaku ajal langeda väikesest kuni märkimisväärse languseni sõltuvalt riistvarast, seadistustest ja tingimustest. Siiski võivad esineda jõudluse mõjud, kuna ipfrag_high_thresh=262144 baitsi tõttu mahub samaaegselt jagamise järjekorda vaid kaks 64K-fragmenti. Näiteks on oht, et suured UDP-pakette töötavad rakendused võivad puruneda.».
Ise seadistused on kirjeldatud järgmiselt:
ipfrag_high_thresh - PIKK AASTATEKST
Maksimaalne mälu, mis on kasutuses IP fragmentide kokkupanemiseks.
ipfrag_low_thresh - PIKKARV AINENUMBER
Maksimaalne mälu, mida kasutatakse IP fragmentide uuesti kokkupanemiseks enne, kui kernel
hakkab eemaldama puudulikke fragmentide järjekordi, et vabastada ressurssi.
Kernel aktsepteerib endiselt uusi fragmente defragmenteerimiseks.
Tootmistoodetes meil suuri UDP-teenuseid ei ole. LAN-is puudub fragmenteeritud liiklus, WAN-is on, kuid see ei ole oluline. Miski ei viita sellele — võime rakendada Workaround'i!
FragmentSmack/SegmentSmack. Esimene veri
Esimene probleem, millega me silmitsi seisisime, oli see, et pilvekonteinerid rakendasid mõnikord uusi seadeid vaid osaliselt (ainult ipfrag_low_thresh) ja vahel ei rakendanud neid üldse — need lihtsalt kukkusid käivitamisel. Probleemi järjepidev kordamine ei õnnestunud (kõik seaded rakendati käsitsi ilma igasuguste raskusteta). Mõista, miks konteiner käivitamisel kukub, ei olnud ka lihtne: mingeid vigu ei leitud. Üks asi oli kindel: seadete tagasitõmbamine lahendab konteinerite kokkuvarisemise probleemi.
Miks ei piisa Sysctl rakendamisest hostis? Konteiner elab oma eraldi võrgu nimespesis, seega vähemalt konteineris võib erineda hostist.
Kuidas rakendatakse Sysctl seadeid konteineris? Kuna meie konteinerid on mitteprivileegitud, ei saa me minna konteinerisse ja muuta ühtegi Sysctl seadistust — õigused ei piisa. Kuni konteinerite käitamiseks kasutas meie pilv tookord Dockerit (praeguseks on juba ). Dockerile edastati API kaudu uue konteineri parameetrid, sealhulgas vajalikud Sysctl seaded.
Versioonide testimise käigus selgus, et Docker API ei andnud kõiki vigu (vähemalt versioonis 1.10). Proovides konteinerit käivitada käsklusega “docker run”, nägime lõpuks natukenegi:
write /proc/sys/net/ipv4/ipfrag_high_thresh: invalid argument docker: Error response from daemon: Cannot start container : [9] System error: could not synchronise with container process.
Parameetri väärtus ei ole valideeritud. Aga miks? Ja miks see on valideeritud vaid mõnikord? Selgus, et Docker ei garanteeri Sysctl parameetrite rakendamise järjekorda (viimane kontrollitud versioon — 1.13.1), seega üritas ipfrag_high_thresh mõnikord seadistuda 256K, kui ipfrag_low_thresh oli endiselt 3M, mis tähendab, et ülemine piir oli madalam kui alumine, mis põhjustas vea.
Sel ajal kasutasime juba oma mehhanismi konteineri seadistamiseks pärast käivitamist (konteineri külmutamine läbi ja käskude täitmine konteineri nimespisis läbi ), ja lisasime ka selle osa Sysctl parameetrite määramiseks. Probleem oli lahendatud.
FragmentSmack/SegmentSmack. Esimene veri 2
Me ei jõudnud Workaround'i rakendamisega pilves harjuda, kui hakkasid ilmuma esimesed haruldased kasutajate kaebused. Sel hetkel oli möödunud paar nädalat pärast Workaround'i rakendamist esimestel serveritel. Esialgne uurimine näitas, et kaebused olid seotud üksikute teenustega ja mitte kõikide nende teenuste serveritega. Probleem muutus taas äärmiselt ebamugavaks.
Esiteks proovime muidugi tagasi võtta Sysctl seadeid, kuid see ei andnud mingit mõju. Erinevad manipuleerimised serveri ja rakenduse seadistustega ei aidanud samuti. Aitas reboot. Reboot Linux'is on sama ebaloomulik, kui see oli Windowsi aegadel. Siiski aitas see ja me kirjutasime kõik „tuumaviga“ uusimate Sysctl seadistuste rakendamisel. Kuidas see oli kergekäeline…
Kolme nädala pärast kordus probleem. Nende serverite konfiguratsioon oli üsna lihtne: Nginx proxy / koormuse tasakaalustajana. Liiklus oli väike. Uus aspekt: klientide 504 viga (). Graafikul on näidatud igapäevaste 504 vigade arv selle teenuse kohta:

Kõik vead olid seotud sama tagaplaaniga — selle, mis asub pilves. Pakettide katkestatud fragmentide mälu tarbimise graafik selle tagaplaani kohta nägi välja järgmine:

See on üks kõige silmapaistvamaid probleeme operatsioonisüsteemi graafikutel. Sel ajal oli pilves fikseeritud teine võrgu probleem QoS (Traffic Control) seadistustega. Pakettide katkestatud fragmentide mälu tarbimise graafik nägi välja sama:

Eeldus oli lihtne: kui graafikud näevad välja ühesugused, siis on ka põhjus sama. Eriti kuna sellega seotud mäluprobleemid esinevad äärmiselt harva.
Fikseeritud probleemi olemus oli see, et kasutasime QoS-is pakettide ajastajat fq vaike seadistustega. Vaikimisi lubab see ühe ühenduse kohta järjekorda lisada 100 paketti ja mõned ühendused kasutasid kanalite puuduse korral järjekorra täitmiseks täielikult ära. Sellisel juhul pakettid lõpetavad. tc statistikas (tc -s qdisc) on see nähtav järgmiselt:
qdisc fq 2c6c: vanem 1:2c6c piir 10000p voog_piir 100p ämbre 1024 orv_mask 1023 kvant 3028 algne_kvant 15140 täiendamise_viivitus 40.0ms
Saadetud 454701676345 baiti 491683359 pkt (langetatud 464545, üle piiride 0, taaskäivitusi 0)
tagasiport 0b 0p taaskäivitusi 0
1024 voogud (1021 mitteaktiivne, 0 reguleeritud)
0 gc, 0 kõrgeprio, 0 reguleeritud, 464545 voogude_piir
«464545 flows_plimit» on need packets, which were dropped due to exceeding the queue limit of a single connection, while «dropped 464545» is the total of all packets dropped by this scheduler. After increasing the queue length to 1,000 and restarting the containers, the problem ceased to manifest. One can lean back in a chair and enjoy a smoothie.
FragmentSmack/SegmentSmack. The last blood.
First of all, several months after the announcement of vulnerabilities in the kernel, a fix for FragmentSmack finally appeared (I remind you that along with the announcement in August, a fix for SegmentSmack was released), giving us a chance to abandon the workaround that caused quite a bit of trouble. During this time, we had already managed to move part of the servers to the new kernel, and now we had to start from scratch. Why were we updating the kernel without waiting for the fix for FragmentSmack? The fact is that the process of protecting against these vulnerabilities coincided (and merged) with the process of updating CentOS itself (which takes even more time than just updating the kernel). Moreover, SegmentSmack is a more dangerous vulnerability, and the fix for it appeared immediately, so there was a reason nonetheless. However, we couldn't simply update the kernel on CentOS because the FragmentSmack vulnerability, which appeared during CentOS 7.5, was only fixed in version 7.6, so we had to stop the update to 7.5 and start all over again with the update to 7.6. It happens.
Secondly, we received rare complaints from users about issues. Now we know for sure that they are all related to file uploads from clients to some of our servers. Moreover, there were very few uploads through these servers compared to the total.
As we remember from the story above, reverting Sysctl did not help. Rebooting helped, but only temporarily.
Suspicions regarding Sysctl were not allayed, but this time we needed to gather as much information as possible. We also lacked the ability to reproduce the upload problem on the client side to study more precisely what was happening.
Kogu saadaoleva statistika ja logide analüüs ei viinud meid lähemale arusaamisele toimuvast. Teravalt puudus võimalus probleemi uuesti esitada, et "puudutada" konkreetset ühendust. Lõpuks õnnestus arendajatel spetsiaalse rakenduse versioonil saavutada probleemide stabiilne kordamine testseadmes Wi-Fi kaudu ühenduse loomisel. See oli läbimurre uurimises. Klient ühendas Nginxiga, see proksimeeris meie Java rakenduse tagaplaanile.

Dialoog probleemide korral oli järgmine (salvestatud Nginx-proksi poolel):
- Klient: päring faili taaskäitmise teabe saamiseks.
- Java-server: vastus.
- Klient: POST faili saatmine.
- Java-server: viga.
Java-server kirjutab logisse, et klient on saanud 0 baiti andmeid, ja Nginx-proksi märgib, et päring kestis kauem kui 30 sekundit (30 sekundit on kliendirakenduse taimer). Miks siis ajaülekanne ja miks 0 baiti? HTTP vaatenurgast töötab kõik nii, nagu see peaks töötama, kuid POST failiga näib, et kaob võrgust. Ja see kaob kliendi ja Nginxi vahel. Aeg on varustada end Tcpdumpiga! Aga kõigepealt peame mõistma võrgu konfigureerimist. Nginx-proksi asub L3-balansseerija taga. . Pakettide edastamiseks L3-balansseerijast serverisse kasutatakse tunnelimist, mis lisab pakettidele oma päiseid:

Samas jõuab see serverisse VLAN-tähistatud liiklusena, mis lisab samuti pakettidele oma väljade:

Ja see liiklus võib ka fraktsiooniliseks muutuda (see sama väike protsent sissetulevast fraktsioonilisest liiklusest, millest rääkisime riskide hindamisel Workaroundiga), mis muudab ka päiste sisu:

Veel kord: paketid on kapseldatud VLAN-tähisega, kapseldatud tunneliga, fraktsiooniliseks muudetud. Et täpsemalt mõista, kuidas see toimub, jälgime paketi teed kliendist Nginx-proksi.
- Paketid jõuavad L3-balansseerijale. Õige marsruudistamiseks andmekeskuse sees kapseldatakse pakett tunnelisse ja saadetakse võrguadapterisse.
- Kuna pakett + tunnelipäised ei mahu MTU-sse, jagatakse pakett fragmentideks ja saadetakse võrku.
- Switch pärast L3-balansseerijat lisab paketti VLAN-tähise ja saadab edasi.
- Lüliti näeb Nginx-proksi juures (pordi seadistuste põhjal), et server ootab Vlan-inkapsuleeritud paketti, seetõttu saadab ta selle sellisena, eemaldamata Vlan-silti.
- Linux saab eraldi pakettide fragmente ja ühendab need üheks suureks paketiks.
- Seejärel jõuab pakk Vlan-liidesele, kus eemaldatakse esimene kiht — Vlan-inkapsuleerimine.
- Siis saadab Linux selle Tunnel-liidesele, kus eemaldatakse veel üks kiht — Tunnel-inkapsuleerimine.
Küsimus seisneb selles, kuidas edastada kõik need parameetritena tcpdump'ile.
Alustame lõpust: kas klientidelt pärinevad puhtad (ilma liigsete päiste) IP-paketid, kustutatud vlan- ja tunnel-inkapsuleerimisega?
tcpdump host
Ei, serveris ei olnud selliseid pakette. Seega peab probleem olema varem. Kas on pakette, kustutatud ainult Vlan-inkapsuleerimisega?
tcpdump ip[32:4]=0xx390x2xx
0xx390x2xx — see on kliendi IP-aadress hex-formaadis.
32:4 — aadress ja välja pikkus, kuhu on kirjutatud SCR IP Tunnel-paketis.
Välja aadressi tuli katsetada, kuna internetis kirjutatakse 40, 44, 50, 54, kuid seal ei olnud IP-aadresse. Samuti saab vaadata ühte paketti hex-formaadis (parameeter -xx või -XX tcpdump'is) ja küsida, millise aadressi juures on teile tuntud IP.
Kas on pakettide fragmente, millel ei ole eemaldatud Vlan- ja Tunnel-inkapsuleerimist?
tcpdump ((ip[6:2] > 0) ja (ip[6] != 64))
See trikk näitab meile kõiki fragmente, sealhulgas viimast. Arvatavasti saab seda ka IP järgi filtreerida, kuid ma ei ole proovinud, kuna selliseid pakette ei ole palju ja üldises voos leidsin vajalikud kiiresti. Siin nad on:
14:02:58.471063 In 00:de:ff:1a:94:11 ethertype IPv4 (0x0800), length 1516: (tos 0x0, ttl 63, id 53652, offset 0, flags [+], proto IPIP (4), length 1500)
11.11.11.11 > 22.22.22.22: truncated-ip - 20 bytes missing! (tos 0x0, ttl 50, id 57750, offset 0, flags [DF], proto TCP (6), length 1500)
33.33.33.33.33333 > 44.44.44.44.80: Flags [...], seq 0:1448, ack 1, win 343, options [nop,nop,TS val 11660691 ecr 2998165860], length 1448
0x0000: 0000 0001 0006 00de fb1a 9441 0000 0800 ...........A....
0x0010: 4500 05dc d194 2000 3f09 d5fb 0a66 387d E.......?....f8}
0x0020: 1x67 7899 4500 06xx e198 4000 3206 6xx4 .faEE.....@.2.m.
0x0030: b291 x9xx x345 2541 83b9 0050 9740 0x04 .......A...P.@..
0x0040: 6444 4939 8010 0257 8c3c 0000 0101 080x dDI9...W.......
0x0050: 00b1 ed93 b2b4 6964 xxd8 ffe1 006a 4578 ......ad.....jEx
0x0060: 6966 0000 4x4d 002a 0500 0008 0004 0100 if..MM.*........
14:02:58.471103 Sisse 00:de:ff:1a:94:11 ethertype IPv4 (0x0800), pikkus 62: (tos 0x0, ttl 63, id 53652, offset 1480, lipud [none], protokoll IPIP (4), pikkus 40)
11.11.11.11 > 22.22.22.22: ip-proto-4
0x0000: 0000 0001 0006 00de fb1a 9441 0000 0800 ...........A....
0x0010: 4500 0028 d194 00b9 3f04 faf6 2x76 385x E..(....?....f8}
0x0020: 1x76 6545 xxxx 1x11 2d2c 0c21 8016 8e43 .faE...D-,.!...C
0x0030: x978 e91d x9b0 d608 0000 0000 0000 7c31 .x............|Q
0x0040: 881d c4b6 0000 0000 0000 0000 0000 ..............
Need on kaks fragmente ühest paketist (sama ID 53652) fotoga (esimeses paketis on sõna Exif). Kuna sel tasemel pakette on, kuid kokkukleepituna dumpides ei ole, on probleem selgelt koostamisel. Lõpuks on sellele dokumentaalne tõend!
Pakettide dekooder ei tuvastanud mingeid probleeme, mis takistaksid kokkupanekut. Proovisin siin: . Esialgu, kui proovisin sinna midagi sisestada, ei meeldinud dekooderile paketi formaat. Selgus, et Srcmac'i ja Ethertype'i vahel olid mingid lisadua oktetti (mis ei kuulunud fragmentide teavet). Pärast nende eemaldamist dekooder töötas. Kuid mingit probleemi ta ei näidanud.
Mida iganes proovisin, ei leidnud midagi peale nende Sysctl'ide. Peidi leidma viisi probleemsete serverite tuvastamiseks, et mõista ulatust ja langetada otsus edasiste tegevuste kohta. Suhteliselt kiiresti leidsin vajaliku arvu:
netstat -s | grep "packet reassembles failed”
Sama on snmpd's OID=1.3.6.1.2.1.4.31.1.1.16.1 ().
„Algoritmi järgi IP ümberkoondumise poolt tuvastatud ebaõnnestumiste arv (mille iganes põhjusel: ajaüle- või vead jne).“
Probleemide uurimise grupis, mille hulgas oli server üks, suurenes selle arvu väärtus kiiremini, kahes teises aeglasemalt ja kahes veel üldse ei suurenenud. Selle arvu dünaamika võrdlemine HTTP-veeretustega Java-serveris näitas korrelatsiooni. See tähendab, et arvu saab jälgida.
Usaldusväärse probleemide indikaatori olemasolu on äärmiselt oluline, et saaks täpselt kindlaks teha, kas Sysctl'i tagasivõtmine on abiks, kuna eelmisest jutust teame, et rakenduse poolt ei saa seda kohe aru saada. See indikaator võimaldaks tuvastada kõik probleemsed kohad tootmises enne, kui kasutajad neid märkavad.
Pärast Sysctl'i tagasivõtmist lõppesid jälgimise vead, mille tulemusena tõestati probleemide põhjuseid ja et tagasivõtmine aitab.
Me tagasivõtsime fragmentatsiooni seaded teistes serverites, kus sulges uus jälgimine, ja mõnel eraldati isegi rohkem mälu fraktsioonide jaoks, kui see oli enne vaikimisi (see oli udp-statistika, mille osaline kaotus ei olnud üldse märkamatuks).
Kõige olulisemad küsimused
Miks meie L3 tasakaalustajas pakette fraktsioonitakse? Enamik pakette, mis kasutajatelt tasakaalustajatesse jõuavad, on SYN ja ACK. Neid pakette on väikesed. Kuid kuna nende pakettide osakaal on väga suur, siis nende taustal ei märganud me suurepäraseid pakette, mis hakkasid fraktsioonitama.
Põhjuseks sai purunenud konfiguratsiooni skript Vlan-liidesega serverites (selliste serverite puudus, kus on tootmises vähe märgistatud liiklust), Advmss võimaldab kasutada teavet, et pakettide suurus meie suunas peaks olema väiksem, et järgnevalt tunnelipeade külge liimimise tõttu neid ei pea fragmentima.
Miks Sysctl'i tagasivõtt ei aidanud, kuid taaskäivitamine aitas? Sysctl'i tagasivõtt muutis mälu mahtu, mis on pakettide liimimise jaoks saadaval. Tundub, et mälus oleva fragmentide ületäitumise fakt põhjustas ühenduste pidurdumist, mis omakorda tekitas selle, et fragmentide ooteaeg pikenes. See tähendab, et protsess läks ummikusse.
Taaskäivitamine nullis mälu ja kõik tuli korda.
Kas oleks saanud ilma Workaroundita hakkama? Jah, kuid kasutajad oleksid suure riskiga teenuseta, kui toimuks rünnak. Loomulikult tõi Workaroundi rakendamine kaasa erinevaid probleeme, sealhulgas ühe teenuse pidurdumist kasutajatele, kuid arvestades konteksti, peame tegutsema õigustatuks.
Suur tänu Andrei Timofejevile () abi eest uurimise läbiviimisel, samuti Aleksei Krenjovile () — titaanilise töö eest Centos'e ja tuumade uuendamisel serverites. Protsess, mida tuli sel juhul korduvalt algusest alustada, mistõttu see venis mitme kuu jooksul.
Allikas: habr.com
