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 seeWhere to look
Reader parked, peer send queue empty, last advertised window zeroWindow updates in your ACK path
Write fails on one length and succeeds on a smaller oneMSS clamp and tunnel MTU
Both directions look fine, app still sees no dataProtected 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.

Relaterte artiklar