Android VpnService: užstrigęs TUN skaitymas prieš ENOBUFS rašymo klaidas
Skaitymas, kuris niekada negrįžta, ir rašymas, kuris nepavyksta su ENOBUFS, turi priešingas priežastis. Išmatuokite paskelbtą langą ir paketo ilgį, kad pasirinktumėte taisymą.
Tame pačiame VpnService cikle pasirodo du simptomai ir abiem kaltinama tas pats: skaitytuvas, kuris niekada negrįžta, ir rašymas, kuris nepavyksta su ENOBUFS. Tai ne vienos klaidos variantai. Užstrigęs skaitymas - tai nesutarimas dėl srauto valdymo su partneriu; nepavykęs rašymas - tai paketas, kurio sąsaja nepriims. Jei juos traktuosite kaip vieną problemą, praleisite dieną derindami buferius, kurie niekada nedalyvavo. Toliau pateiktas skirstymas - tai, kaip nusprendžiu, kurią ciklo pusę instrumentuoti pirmiausia.
Vienas ciklas, du nepriklausomi gedimo taškai
Jūsų tarnyba nuskaito IP paketą iš TUN failo deskriptoriaus, jį patikrina ir įrašo atsakymą atgal į tą patį deskriptorių. Viskas, kas šiame kelyje sugenda, pasireiškia viename iš tų dviejų kvietimų, todėl abu pranešami kaip sugadintas tunelis. Kvietimai turi skirtingas sutartis. Skaitymas laukia, kol branduolys duos jums paketą, o rašymas perduoda branduoliui paketą, kurį jis turi teisę atmesti.
Skaitymai yra lengvesnė pusė mąstyti. Skaitymas, kuris nieko negrąžina, arba laukia eismo, kuris niekada neatvyks, arba laukia, nes partneris nustojo siųsti.
Užstrigęs skaitymas - tai srauto valdymo nesutarimas
TUN įrenginio skaitymas blokuojamas, kol paketas tampa prieinamas. Tai suprojektuotas elgesys, ir todėl read viduje stovinti gija atrodo normaliai tuščioje jungtyje. Tai tampa pakibimu, kai jūsų pačių TCP stekas toliau priima duomenis iš vietinės programos, bet niekada neskelbia pakankamo lango, kad nuotolinis partneris galėtų siųsti daugiau. Partneris užpildo jūsų paskelbtą langą, laukia ACK ir lango atnaujinimo, ir sustoja. Tada jūsų skaitytuvas neturi ko grąžinti, o programa mato atvirą ir tylų ryšį.
Dažnos priežastys - stekas, kuris rašo į lizdą nesekdamas paskelbto lango, ir kitas, kuris pamiršta išsiųsti lango atnaujinimą ištuštinęs savo buferį. Iš išorės jie atrodo identiškai: baitai išeina, nieko negrįžta, klaida neiškeliama. Skaitymo gijos blokavimas analizuojant, sprendžiant DNS ar atliekant disko I/O sukuria tokią pačią simptomo formą dėl nesusijusios priežasties, todėl prieš kaltindami srauto valdymą patvirtinkite, kad gija tikrai yra read viduje.
Patikrinkite partnerio siuntimo eilę ir langą, kurį jis paskutinį kartą matė. Jei partneris laiko duomenis, nes jūsų langas yra nulis, taisymas yra jūsų ACK kelyje, o ne TUN skaityme.
ENOBUFS yra dydžio problema, o ne buferio trūkumas
Rašymas, kuris TUN sąsajoje grąžina ENOBUFS, paprastai atmeta paketo ilgį. Sąsaja turi MTU, didesnis paketas negali būti įtrauktas į eilę, o kartojant tą patį paketą kiekvieną kartą nepavyksta taip pat. Tas determinizmas yra požymis. Tikras buferio trūkumas išnyksta mažėjant spaudimui, o dydžio atmetimas nekreipia dėmesio į tai, kiek lauksite.
TCP turi mechanizmą būtent tam, ir tai yra žingsnis, kurį žmonės praleidžia: MSS apribojimas. Kai vartotojo erdvėje sintetinate segmentus, jūs pasirenkate naudingosios apkrovos dydį, o ėmimas iš vietinės sąsajos MTU, o ne iš tunelio MTU, išleidžia segmentus, kurių tunelis negali pernešti. Apribokite iki mažesniojo iš dviejų. Apribojimas apima tik TCP, todėl per didelį UDP duomenų gramą jūsų kodas turi suskaidyti arba atmesti.
Derindami registruokite kiekvieno rašomo paketo ilgį. Ilgis, kuris nepavyksta, yra atsakymas, ir jis nesikeis tarp bandymų.
Trys matavimai, atskiriantys atvejus
Jums nereikia derintuvo nė vienam iš dviejų gedimų. Trijų stebėjimų pakanka nukreipti save į tinkamą skyrių, ir jie užima mažiau laiko nei perrinkimas.
| What you see | Where to look |
|---|---|
| Reader parked, peer send queue empty, last advertised window zero | Window updates in your ACK path |
| Write fails on one length and succeeds on a smaller one | MSS clamp and tunnel MTU |
| Both directions look fine, app still sees no data | Protected socket and routing loop |
Jei trečioji eilutė sutampa, pats ciklas yra tvarkingas, o problema yra ten, kur tunelis negali jos matyti: neapsaugotas transporto lizdas, kuris neša jūsų pačių VPN srautą atgal į tunelį, o tai paprastai pasirodo kaip ryšys, kuris pavyksta ir tada nieko nepristato.
Sutaisykite vamzdyną, nuo kurio priklauso abu keliai
Nė vienas simptomas nevertas vytis, kol sąsaja nėra teisingai pastatyta. Svarbūs builder kvietimai yra adresas, maršrutas, MTU ir sutikimas, tokia tvarka:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()Nustatykite MTU į tikrąją tunelio reikšmę, o ne palikite tą, kurią naudojo pavyzdys. Viskas, ką vėliau rašysite, yra ribojama to skaičiaus, o nustatyto MTU ir apriboto MSS neatitikimas yra labiausiai paplitęs būdas vėl įvesti rašymo gedimą po taisymo. Tada apsaugokite lizdą, kuris neša srautą į patį VPN serverį. Be protect tie paketai nukreipiami atgal į jūsų pačių tunelį ir ryšys niekada nebaigiamas, o tai atrodo kaip užstrigęs skaitymas, bet yra maršrutizavimo kilpa.
Sutikimo negalima apeiti. Tarnyba, paleista be prepare grąžinto null, negauna sąsajos, o establish grąžina null, o ne meta, todėl patikrinkite deskriptorių prieš pradėdami ciklą. VpnService nuoroda yra vieta patvirtinti builder sutartį, o ne kopijuoti pavyzdį.
Tikrinkite po vieną kryptį
Prieš liesdami TCP, siųskite UDP aidą per tunelį ir stebėkite paketų ilgius. Tada atidarykite rankos paspaudimą ir patvirtinkite, kad segmentai lieka žemiau jūsų apskaičiuoto MSS. Skaičiuokite paketus, o ne ryšius, kad užstrigimas pasirodytų kaip spraga skaitiklyje, o ne pakibęs lizdas. Jei skaitymai sustoja po rašymų pliūpsnio, langas yra įtariamasis; jei rašymai nepavyksta iš karto, tai ilgis.
Tada perkraukite įrenginį ir pakartokite vieną kartą, nes VpnService, kuris veikia tik iki pirmo tinklo pakeitimo, yra maršrutizavimo, o ne srauto valdymo problema.