I samme VpnService-løkke dukker det opp to symptomer som begge får skylden for det samme: en leser som aldri vender tilbake, og en skriving som feiler med ENOBUFS. Dette er ikke to varianter av samme feil. En lesing som står fast er uenighet om flytkontroll med motparten; en skriving som feiler er en pakke grensesnittet ikke vil ta imot. Ser du på dem som ett problem, bruker du en dag på å justere buffere som aldri var involvert. Inndelingen nedenfor viser hvordan jeg velger hvilken halvdel av løkken jeg måler først.

Én løkke, to uavhengige feilpunkter

Tjenesten din leser en IP-pakke fra TUN-filbeskrivelsen, inspiserer den og skriver et svar tilbake til samme beskrivelse. Alt som ryker på den veien, viser seg ved en av de to kallene, og derfor rapporteres begge som en ødelagt tunnel. Kallene har forskjellige kontrakter. En lesing venter på at kjernen overleverer en pakke, og en skriving overleverer en pakke kjernen har rett til å avvise.

Lesing er den enkle halvdelen å resonnere om. En lesing som ikke returnerer noe, venter enten på trafikk som aldri kommer, eller fordi motparten har sluttet å sende.

En lesing som står fast er uenighet om flytkontroll

Lesing fra en TUN-enhet blokkerer til en pakke er tilgjengelig. Det er tilsiktet atferd, og derfor ser en tråd som parkerer i read normal ut på en ledig linje. Det blir en heng når din egen TCP-stack fortsetter å ta imot data fra den lokale appen, men aldri annonserer et vindu som er stort nok for den eksterne motparten til å sende mer. Motparten fyller vinduet du annonserte, venter på ACK og vindusoppdatering, og stopper. Da har leseren din ingenting å levere, og appen ser en forbindelse som er åpen og stille.

De vanlige årsakene er en stack som skriver til socketen uten å spore det annonserte vinduet, og en som glemmer å sende vindusoppdatering etter å ha tømt bufferen. Sett utenfra er de identiske: byte går ut, ingenting kommer tilbake, ingen feil kastes. Blokkering av lesetråden på parsering, DNS-oppslag eller disk-I/O gir samme symptomform av en urelatert grunn, så bekreft at tråden faktisk står i read før du klandrer flytkontrollen.

Sjekk sendekøen til motparten og vinduet den sist så. Holder motparten tilbake data fordi vinduet ditt er null, ligger rettelsen i ACK-løypa di, ikke i TUN-lesingen.

ENOBUFS er et størrelsesproblem, ikke buffermangel

En skriving som returnerer ENOBUFS på et TUN-grensesnitt, avviser som regel pakkelengden. Grensesnittet har en MTU, en større pakke kan ikke legges i kø, og nye forsøk på samme pakke feiler likt hver gang. Denne forutsigbarheten er hintet. Ekte buffermangel gir seg når trykket faller, mens en størrelsesavvisning ikke bryr seg om hvor lenge du venter.

TCP har en mekanisme for nettopp dette, og det er trinnet folk hopper over: MSS-clampen. Når du syntetiserer segmenter i brukerrommet, velger du nyttelaststørrelsen, og å hente den fra det lokale grensesnittets MTU i stedet for tunnelens MTU sender ut segmenter tunnelen ikke kan bære. Klem til den minste av de to. Clampen dekker bare TCP, så et for stort UDP-datagram må fragmenteres eller droppes av koden din.

Logg lengden på hver pakke du skriver mens du feilsøker. Lengden som feiler er svaret, og den flytter seg ikke mellom forsøk.

Tre målinger som skiller tilfellene

Du trenger ingen debugger for noen av de to feilene. Tre observasjoner er nok til å lede deg til riktig avsnitt, og de tar kortere tid enn en ny bygging.

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

Passer tredje rad, er løkken selv i orden, og problemet ligger der tunnelen ikke kan se det: en ubeskyttet transport-socket som fører din egen VPN-trafikk tilbake i tunnelen, noe som vanligvis viser seg som en forbindelse som lykkes og deretter ikke leverer noe.

Reparer røropplegget begge veier er avhengige av

Ingen symptomer er verdt å jage før grensesnittet er riktig bygget. Builder-kallene som betyr noe, er adressen, ruten, MTU-en og samtykket, i den rekkefølgen:

Builder()
  .addAddress(10.8.0.2, 32)
  .addRoute(0.0.0.0, 0)
  .setMtu(1400)
  .establish()

Sett MTU-en til tunnelens virkelige verdi i stedet for å la den stå på det eksempelet brukte. Alt du skriver senere er begrenset av dette tallet, og avvik mellom MTU-en du setter og MSS-en du klemmer til, er den vanligste måten å gjeninnføre skrivefeilen etter en rettelse. Beskytt deretter socketen som fører trafikk til selve VPN-serveren. Uten protect rutes disse pakkene tilbake i din egen tunnel, og forbindelsen fullføres aldri, noe som ser ut som en lesing som står fast, men er en rutingløkke.

Samtykke kan ikke omgås. En tjeneste som startes uten at prepare returnerer null, får ikke noe grensesnitt, og establish returnerer null i stedet for å kaste, så sjekk beskrivelsen før du starter løkken. VpnService-referansen er stedet for å bekrefte builder-kontrakten i stedet for å kopiere et eksempel.

Verifiser én retning av gangen

Send et UDP-ekko gjennom tunnelen og følg med på pakkelengdene før du berører TCP. Åpne deretter en håndtrykk og bekreft at segmentene holder seg under MSS-en du regnet ut. Tell pakker i stedet for forbindelser, slik at en stopp vises som et hull i telleren i stedet for en hengende socket. Stopper lesingene etter en byge med skrivinger, er vinduet mistenkt; feiler skrivingene umiddelbart, er det lengden.

Deretter restarter du enheten og gjentar én gang, fordi en VpnService som bare virker til den første nettverksendringen, er et rutingproblem og ikke et flytkontrollproblem.

Relaterte artikler