Android VpnService: blokkert TUN-lesing mot ENOBUFS-skrivefeil
Ei lesing som aldri kjem attende, og ei skriving som feilar med ENOBUFS, har motsette årsaker. Mål det kunngjorde vindauget og pakkelengda for å velje retting.
To symptom dukkar opp i same VpnService-løkka og får skulda for det same: ein lesar som aldri kjem attende, og ei skriving som feilar med ENOBUFS. Dei er ikkje variantar av éin feil. Ei blokkert lesing er usemje om straumkontroll med peer-en; ei mislukka skriving er ein pakke grensesnittet ikkje vil godta. Behandlar du dei som eitt problem, brukar du ein dag på å justere bufferar som aldri var med. Oppdelinga nedanfor er korleis eg avgjer kva halvdel av løkka eg instrumenterer først.
Éi løkke, to uavhengige feilpunkt
Tenesta di les ein IP-pakke frå TUN-filskildringa, granskar han og skriv eit svar attende til same skildring. Alt som bryt på den vegen dukkar opp ved ein av dei to kalla, og difor vert begge rapporterte som ein øydelagd tunnel. Kalla har ulike kontraktar. Ei lesing ventar på at kjernen gir deg ein pakke, og ei skriving gir kjernen ein pakke han har lov til å avvise.
Lesingar er den enkle halvdelen å tenkje på. Ei lesing som ikkje returnerer noko, ventar anten på trafikk som aldri kjem, eller ventar fordi peer-en slutta å sende.
Ei blokkert lesing er usemje om straumkontroll
Lesing av ein TUN-eining blokkerer til ein pakke er tilgjengeleg. Det er designa åtferd, og difor ser ein tråd parkert i read normal ut på ei ledig lenkje. Det vert ein heng når din eigen TCP-stakk held fram med å ta imot data frå den lokale appen men aldri kunngjer eit vindauge stort nok til at den eksterne peer-en kan sende meir. Peer-en fyller vindauget du kunngjorde, ventar på ein ACK og ei vindaugeoppdatering, og stoppar. Då har lesaren din ingenting å returnere, og appen ser ei tilkopling som er open og stille.
Dei vanlege årsakene er ein stakk som skriv til socketen utan å følgje det kunngjorde vindauget, og ein som gløymer å sende ei vindaugeoppdatering etter å ha tømt bufferen. Utanfrå ser dei like ut: byte går ut, ingenting kjem attende, ingen feil vert reist. Blokkering av lesetråden på parsing, DNS-oppslag eller disk-I/O gir same form for symptom av ein urelatert årsak, så stadfest at tråden faktisk er inne i read før du gir straumkontrollen skulda.
Sjekk sendekøa til peer-en og vindauget han såg sist. Held peer-en på data fordi vindauget ditt er null, ligg rettinga i ACK-vegen din, ikkje i TUN-lesinga.
ENOBUFS er eit storleiksproblem, ikkje buffermangel
Ei skriving som returnerer ENOBUFS på eit TUN-grensesnitt avviser vanlegvis pakkelengda. Grensesnittet har ein MTU, ein større pakke kan ikkje setjast i kø, og gjentekne forsøk med same pakke feilar på same måte kvar gong. Den determinismen er teiknet. Ein ekte buffermangel klarnar når trykket søkk, medan ei storleiksavvising ikkje bryr seg om kor lenge du ventar.
TCP har ein mekanisme nettopp for dette, og det er trinnet folk hoppar over: MSS-clampen. Når du syntetiserer segment i brukarrommet, vel du nyttelaststorleiken, og å ta han frå MTU-en til det lokale grensesnittet i staden for MTU-en til tunnelen sender ut segment tunnelen ikkje kan bere. Avgrens til den minste av dei to. Clampen dekkjer berre TCP, så eit for stort UDP-datagram må fragmenterast eller droppast av koden din.
Logg lengda på kvar pakke du skriv under feilsøking. Lengda som feilar er svaret, og ho endrar seg ikkje mellom forsøk.
Tre målingar som skil tilfella
Du treng ikkje ein debuggar for nokon av dei to feila. Tre observasjonar er nok til å leie deg til rett avsnitt, og dei tek kortare tid enn ein ombygging.
| 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 |
Passar den tredje rada, er løkka sjølv i orden og problemet sit der tunnelen ikkje kan sjå det: ein uverna transport-socket som ber din eigen VPN-trafikk attende i tunnelen, noko som vanlegvis viser seg som ei tilkopling som lukkast og deretter ikkje leverer noko.
Reparer røyrlegginga begge vegar er avhengige av
Ingen symptom er verdt å jage før grensesnittet er bygt rett. Builder-kalla som betyr noko er adressa, ruta, MTU-en og samtykket, i den rekkjefølgja:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()Set MTU-en til den reelle verdien til tunnelen i staden for å la han stå på det dømet brukte. Alt du skriv seinare er avgrensa av det talet, og avvik mellom MTU-en du set og MSS-en du avgrensar til er den vanlegaste måten å føre inn skrivefeilen att etter ei retting. Ver så socketen som ber trafikk til VPN-tenaren sjølv. Utan protect vert desse pakkane ruta attende i din eigen tunnel og tilkoplinga vert aldri fullført, noko som ser ut som ei blokkert lesing men er ei ruting-løkke.
Samtykke kan ikkje omgåast. Ei teneste starta utan at prepare returnerer null får ingen grensesnitt, og establish returnerer null i staden for å kaste, så sjekk skildringa før du startar løkka. VpnService-referansen er staden for å stadfeste builder-kontrakten i staden for å kopiere eit døme.
Stadfest éin retning om gongen
Send eit UDP-ekko gjennom tunnelen og følg med på pakkelengdene før du rører TCP. Opne så eit handtrykk og stadfest at segmenta held seg under MSS-en du rekna ut. Tel pakkar i staden for tilkoplingar, så ei blokkering syner seg som eit hol i teljaren i staden for ein fastlåst socket. Stoppar lesingar etter ein byge med skrivingar, er vindauget den mistenkte; feilar skrivingar med ein gong, er det lengda.
Start så eininga på nytt og gjer det om éin gong, fordi ein VpnService som berre verkar til den første nettverksendringa er eit rutingproblem og ikkje eit straumkontrollproblem.