Twee symptomen duiken op in dezelfde VpnService-lus en krijgen dezelfde schuld: een lezer die nooit terugkeert, en een schrijfbewerking die faalt met ENOBUFS. Het zijn geen varianten van een bug. Een vastgelopen leesbewerking is onenigheid over flow control met de peer; een mislukte schrijfbewerking is een pakket dat de interface niet accepteert. Behandel je ze als een probleem, dan besteed je een dag aan het afstellen van buffers die er nooit bij betrokken waren. De splitsing hieronder is hoe ik bepaal welke helft van de lus ik eerst instrumenteer.

Een lus, twee onafhankelijke faalpunten

Je service leest een IP-pakket uit de TUN-bestandsdescriptor, inspecteert het en schrijft een antwoord terug naar dezelfde descriptor. Alles wat op dat pad breekt, komt aan het licht bij een van die twee aanroepen, en daarom worden beide gemeld als een kapotte tunnel. De aanroepen hebben verschillende contracten. Een leesbewerking wacht tot de kernel je een pakket geeft, en een schrijfbewerking geeft de kernel een pakket dat hij mag weigeren.

Leesbewerkingen zijn de makkelijke helft om over na te denken. Een leesbewerking die niets teruggeeft, wacht ofwel op verkeer dat nooit aankomt ofwel omdat de peer is gestopt met zenden.

Een vastgelopen leesbewerking is onenigheid over flow control

Het lezen van een TUN-apparaat blokkeert tot een pakket beschikbaar is. Dat is ontworpen gedrag, en daarom ziet een thread die in read parkeert er normaal uit op een inactieve verbinding. Het wordt een hang wanneer je eigen TCP-stack doorgaat met het accepteren van gegevens van de lokale app maar nooit een venster aankondigt dat groot genoeg is voor de externe peer om meer te sturen. De peer vult het venster dat je hebt aangekondigd, wacht op een ACK en een vensterupdate, en stopt. Dan heeft je lezer niets terug te geven, en de app ziet een verbinding die open en stil is.

De gebruikelijke oorzaken zijn een stack die naar de socket schrijft zonder het aangekondigde venster te volgen, en een die vergeet een vensterupdate te sturen nadat hij zijn buffer heeft geleegd. Van buitenaf zien ze er identiek uit: bytes gaan eruit, er komt niets terug, er wordt geen fout opgeworpen. Het blokkeren van de lees-thread op parsing, DNS-resolutie of schijf-I/O levert dezelfde vorm van symptoom op om een ongerelateerde reden, dus bevestig dat de thread echt in read zit voordat je flow control de schuld geeft.

Controleer de zendwachtrij van de peer en het venster dat hij het laatst zag. Als de peer gegevens vasthoudt omdat jouw venster nul is, zit de oplossing in je ACK-pad, niet in de TUN-leesbewerking.

ENOBUFS is een grootteprobleem, geen buffertekort

Een schrijfbewerking die ENOBUFS retourneert op een TUN-interface wijst meestal de pakketlengte af. De interface heeft een MTU, een groter pakket kan niet in de wachtrij, en het opnieuw proberen van hetzelfde pakket faalt elke keer op dezelfde manier. Dat determinisme is de aanwijzing. Een echt buffertekort verdwijnt wanneer de druk afneemt, terwijl een grootteafwijzing er niet om geeft hoe lang je wacht.

TCP heeft een mechanisme precies hiervoor, en dat is de stap die mensen overslaan: de MSS-clamp. Wanneer je segmenten in user space synthetiseert, kies je de payloadgrootte, en die uit de MTU van de lokale interface halen in plaats van uit de MTU van de tunnel levert segmenten op die de tunnel niet kan dragen. Beperk tot de kleinste van de twee. De clamp dekt alleen TCP, dus een te groot UDP-datagram moet door je code worden gefragmenteerd of weggegooid.

Log de lengte van elk pakket dat je schrijft tijdens het debuggen. De lengte die faalt is het antwoord, en die verandert niet tussen pogingen.

Drie metingen die de gevallen scheiden

Je hebt voor geen van beide storingen een debugger nodig. Drie observaties zijn genoeg om je naar de juiste sectie te leiden, en ze kosten minder tijd dan een rebuild.

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

Als de derde rij klopt, is de lus zelf in orde en zit het probleem waar de tunnel het niet kan zien: een onbeschermde transport-socket die je eigen VPN-verkeer terug de tunnel in draagt, wat zich meestal voordoet als een verbinding die slaagt en daarna niets aflevert.

Repareer de leidingen waarvan beide paden afhangen

Geen enkel symptoom is het najagen waard voordat de interface correct is gebouwd. De builder-aanroepen die ertoe doen zijn het adres, de route, de MTU en de toestemming, in die volgorde:

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

Zet de MTU op de werkelijke waarde van de tunnel in plaats van hem te laten staan op wat het voorbeeld gebruikte. Alles wat je later schrijft wordt door dat getal begrensd, en een verschil tussen de MTU die je instelt en de MSS waarop je begrenst is de meest voorkomende manier om de schrijffout na een oplossing opnieuw te introduceren. Bescherm daarna de socket die verkeer naar de VPN-server zelf draagt. Zonder protect worden die pakketten terug de eigen tunnel in gerouteerd en de verbinding komt nooit tot stand, wat lijkt op een vastgelopen leesbewerking maar een routeringslus is.

Toestemming kan niet worden omzeild. Een service die start zonder dat prepare null retourneert, krijgt geen interface, en establish retourneert null in plaats van te gooien, dus controleer de descriptor voordat je de lus start. De VpnService-referentie is de plek om het builder-contract te bevestigen in plaats van een voorbeeld te kopiëren.

Verifieer een richting tegelijk

Stuur een UDP-echo door de tunnel en kijk naar pakketlengtes voordat je TCP aanraakt. Open dan een handshake en bevestig dat de segmenten onder de MSS blijven die je hebt berekend. Tel pakketten in plaats van verbindingen, zodat een stilstand verschijnt als een gat in de teller in plaats van een hangende socket. Als leesbewerkingen stoppen na een uitbarsting van schrijfbewerkingen, is het venster de verdachte; als schrijfbewerkingen onmiddellijk falen, is het de lengte.

Herstart daarna het apparaat en herhaal een keer, want een VpnService die alleen werkt tot de eerste netwerkwijziging is een routeringsprobleem en geen flow-controlprobleem.

Gerelateerde artikelen