Android VpnService: zaseknuté čítanie TUN verzus chyby zápisu ENOBUFS
Čítanie, ktoré sa nikdy nevráti, a zápis, ktorý zlyhá s ENOBUFS, majú opačné príčiny. Zmerajte inzerované okno a dĺžku paketu, aby ste vybrali opravu.
V tej istej slučke VpnService sa objavujú dva príznaky a oba sa zvaľujú na to isté: čítač, ktorý sa nikdy nevráti, a zápis, ktorý zlyhá s ENOBUFS. Nie sú to varianty jednej chyby. Zaseknuté čítanie je nesúhlas o riadení toku s partnerom; neúspešný zápis je paket, ktorý rozhranie neprijme. Ak ich budete riešiť ako jeden problém, strávite deň ladením bufferov, ktoré v tom nikdy neboli. Rozdelenie nižšie je spôsob, akým sa rozhodujem, ktorú polovicu slučky instrumentovať najskôr.
Jedna slučka, dva nezávislé body zlyhania
Vaša služba čítá IP paket z deskriptora súboru TUN, prehliadne ho a zapíše odpoveď späť do toho istého deskriptora. Všetko, čo sa na tej ceste pokazí, prejaví sa pri jednom z týchto dvoch volaní, preto sa obe hlásia ako rozbitý tunel. Volania majú rôzne kontrakty. Čítanie čaká, kým vám jadro odovzdá paket, a zápis odovzdáva jadru paket, ktorý smie odmietnuť.
Čítanie je tá ľahšia polovica na uvažovanie. Čítanie, ktoré nič nevracia, buď čaká na prevádzku, ktorá nikdy nepríde, alebo čaká, lebo partner prestal posielať.
Zaseknuté čítanie je nesúhlas o riadení toku
Čítanie zariadenia TUN blokuje, kým nie je paket dostupný. To je zamýšľané správanie, a preto vlákno zaparkované v read vyzerá na nečinnom spoji normálne. Zaseknutím sa to stane, keď váš vlastný TCP stack ďalej prijíma dáta z lokálnej aplikácie, ale nikdy neinzeruje dostatočné okno, aby vzdialený partner mohol poslať viac. Partner vyplní okno, ktoré ste inzerovali, počká na ACK a aktualizáciu okna a zastaví sa. Vaša čítačka potom nemá čo vrátiť a aplikácia vidí spojenie, ktoré je otvorené a tiché.
Časté príčiny sú stack, ktorý zapisuje do socketu bez sledovania inzerovaného okna, a taký, ktorý zabudne po vyprázdnení buffera poslať aktualizáciu okna. Zvonku vyzerajú rovnako: bajty odchádzajú, nič sa nevracia, žiadna chyba sa nevyhodí. Zablokovanie čítacieho vlákna na parsovaní, DNS rozlíšení alebo diskovom I/O vytvára rovnaký tvar príznaku z nesúvisiaceho dôvodu, preto pred obvinením riadenia toku overte, že vlákno je skutočne vnútri read.
Skontrolujte odosielaciu frontu partnera a okno, ktoré naposledy videl. Ak partner drží dáta, pretože vaše okno je nula, oprava je vo vašej ceste ACK, nie v čítaní TUN.
ENOBUFS je problém veľkosti, nie nedostatok buffera
Zápis, ktorý na rozhraní TUN vracia ENOBUFS, zvyčajne odmieta dĺžku paketu. Rozhranie má MTU, väčší paket sa nedá zaradiť do fronty a opakovanie toho istého paketu zlyháva zakaždým rovnako. Tá determinovanosť je vodítko. Skutočný nedostatok buffera sa vyčistí, keď tlak klesne, zatiaľ čo odmietnutie veľkosti nezaujíma, ako dlho čakáte.
TCP má mechanizmus presne na to a je to krok, ktorý ľudia preskakujú: MSS clamp. Keď syntetizujete segmenty v používateľskom priestore, vyberáte veľkosť payloadu, a výber z MTU lokálneho rozhrania namiesto z MTU tunela vydáva segmenty, ktoré tunel neunesie. Obmedzte na menšiu z dvoch. Clamp pokrýva len TCP, takže príliš veľký UDP datagram musí váš kód fragmentovať alebo zahodiť.
Pri ladení logujte dĺžku každého paketu, ktorý zapíšete. Dĺžka, ktorá zlyhá, je odpoveď, a medzi pokusmi sa nezmení.
Tri merania, ktoré oddeľujú prípady
Pre ani jedno z týchto zlyhaní nepotrebujete debugger. Tri pozorovania stačia na nasmerovanie do správnej sekcie a zaberú menej času ako rebuild.
| 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 |
Ak sedí tretí riadok, slučka samotná je v poriadku a problém je tam, kde ho tunel nevidí: nechránený transportný socket, ktorý nesie vašu vlastnú VPN prevádzku späť do tunela, čo sa zvyčajne prejaví ako spojenie, ktoré uspeje a potom nič nedoručí.
Opravte potrubie, na ktorom závisia obe cesty
Žiadny príznak nemá cenu prenasledovať, kým nie je rozhranie správne postavené. Volania builderu, na ktorých záleží, sú adresa, trasa, MTU a súhlas, v tomto poradí:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()Nastavte MTU na skutočnú hodnotu tunela namiesto toho, aby ste ho nechali na tom, čo použil príklad. Všetko, čo neskôr zapíšete, je obmedzené týmto číslom, a nesúlad medzi MTU, ktoré nastavíte, a MSS, na ktoré obmedzujete, je najbežnejší spôsob, ako po oprave znova zaviesť chybu zápisu. Potom chráňte socket, ktorý nesie prevádzku k samotnému VPN serveru. Bez protect sa tieto pakety smerujú späť do vášho vlastného tunela a spojenie sa nikdy nedokončí, čo vyzerá ako zaseknuté čítanie, ale je to slučka smerovania.
Súhlas nemožno obísť. Služba spustená bez toho, aby prepare vrátil null, nedostane rozhranie, a establish vracia null namiesto vyhodenia výnimky, preto pred spustením slučky skontrolujte deskriptor. Referenčná príručka VpnService je miesto, kde potvrdiť kontrakt builderu namiesto kopírovania príkladu.
Overujte jeden smer po druhom
Pošlite tunelom UDP echo a sledujte dĺžky paketov, než sa dotknete TCP. Potom otvorte handshake a potvrďte, že segmenty zostávajú pod MSS, ktoré ste vypočítali. Počítajte pakety, nie spojenia, aby sa zaseknutie prejavilo ako medzera v počítadle namiesto zaseknutého socketu. Ak sa čítania po dávke zápisov zastavia, podozrivé je okno; ak zápisy zlyhávajú okamžite, je to dĺžka.
Potom zariadenie reštartujte a raz zopakujte, pretože VpnService, ktorý funguje len do prvej zmeny siete, je problém smerovania, nie riadenia toku.