Tunnelen kommer op, nøgleikonet vises, og hver anmodning i browseren udløber stadig. I en VpnService-app betyder den kombination normalt, at læseløkken lever, og at output-halvdelen er brudt, ikke at serveren afviste håndtrykket. Android har allerede oprettet grænsefladen, flyttet appens trafik ind i den og givet din proces en filbeskrivelse. Alt efter den beskrivelse er din kode, og ét manglende kald er nok til at sluge hele strømmen.

Hvad systemet giver dig, og hvad det ikke giver

establish() returnerer en ParcelFileDescriptor til en tun-grænseflade. Systemet skriver udgående IP-pakker i den og forventer, at din proces læser dem, beslutter hvor de skal hen, og sender dem gennem en almindelig socket. Der er ingen kernel-videresendelse under, så når ingen læser, tabes pakkerne, og grænsefladens tællere flytter sig aldrig.

Begynd med at tælle i stedet for at gætte. Forøg en tæller ved hver vellykket læsning, og log destinationen og protokollen for de første par pakker. Hvis tælleren bliver på nul, mens der genereres trafik, ligger fejlen før din videresendelseskode, hvilket indsnævrer søgningen til builder-konfigurationen og læsetilstanden. Hvis tælleren flytter sig, og browseren stadig fejler, ligger fejlen i sendestien eller på vejen tilbage.

Blokerende læsning afgør, om løkken flytter sig

setBlocking(true) sætter beskrivelsen i blokerende tilstand og har eksisteret siden API 21. På ældre udgivelser er løsningen en lille JNI-hjælper, der rydder O_NONBLOCK med fcntl på den rå beskrivelse. Tilstanden er vigtig, fordi den afgør, hvad en læsning uden ventende pakke må gøre.

I ikke-blokerende tilstand vender den læsning straks tilbage med en negativ værdi, og en løkke, der ignorerer resultatet, kører en kerne rundt uden at flytte nogen trafik. I blokerende tilstand venter læsningen, hvilket en videresendelsestråd ønsker, men tråden kan derefter ikke længere afbrydes. Lukning af beskrivelsen er den understøttede måde at få læsningen til at fejle, så løkken kan afsluttes. At dræbe processen eller afbryde tråden kan i stedet efterlade næste start med en beskrivelse, der ikke længere svarer til grænsefladen.

Læs én pakke pr. kald ind i en buffer med størrelse til grænsefladens MTU, og behandl nul eller en negativ returværdi som en fejl i stedet for en tom pakke.

protect() før den første afsendelse, ikke efter

Hver socket, der bærer en indkapslet pakke, skal beskyttes først. En ubeskyttet socket adlyder de samme routingregler, du netop har installeret, så dens pakker går tilbage i tunnelen, og din egen læseløkke samler dem op igen. Tunnelen videresender derefter sit eget output i det uendelige, og udefra ligner det en forbindelse, der åbner og aldrig svarer.

Rækkefølgen er protect, derefter connect eller bind, derefter send. Kald af protect efter connect kan lade den tidligere routebeslutning stå, så socketen fortsætter med at bruge den forkerte vej, selv om kaldet returnerede true. Tjek den boolean, den returnerer, og fejl højt, når den er false, i stedet for at gå ind i en afsendelse, der ikke kan virke.

En tilladelsesfejl fra sendto på en socket, der aldrig har rørt protect(), er det sædvanlige kendetegn på denne fejl.

UDP-eksemplet bærer ikke HTTPS

Det videresendelseseksempel, som de fleste tutorials kopierer, håndterer UDP og intet andet. TCP kræver et håndtryk, sekvensnumre, bekræftelser og gensendelse, og en løkke, der kopierer nyttelast mellem en tun-beskrivelse og en socket, giver ingen af dem. Derfor kan en UDP-DNS-forespørgsel komme igennem, mens hver HTTPS-anmodning går i stå. Kør enten en TCP-stak i brugerniveau eller giv forbindelserne til en lokal proxy, der afslutter TCP på enheden og videresender strømmen.

Test opdelingen, før du omskriver noget: send én UDP-forespørgsel, og åbn én TCP-forbindelse til samme vært. Hvis UDP-stien virker, og TCP-stien hænger ved det første SYN, er videresendelseskoden problemet og ikke serveren. Den ene sammenligning sparer en masse tid brugt på at læse serverlogs, der ikke indeholder noget nyttigt.

Rutelisten og filtre pr. app begrænser, hvad du ser

addAddress, addRoute og addDns definerer, hvad systemet sender i tunnelen. En destination uden for rutelisten når aldrig din beskrivelse, så ingen mængde videresendelseslogik kan rette det. Log de builder-kald, du laver, og sammenlign dem med de adresser, appen faktisk prøver at nå.

Valg pr. app har sit eget gulv: addAllowedApplication og addDisallowedApplication kræver API 21, og på ældre udgivelser mangler metoderne ganske enkelt. Et design, der filtrerer efter pakke, kan slet ikke køre der.

Genoprettelse efterlader forældede beskrivelser

Et nyt kald til establish() returnerer en ny beskrivelse og lukker den forrige grænseflade. Den gamle beskrivelse er nu ugyldig, så en tråd, der stadig blokerer på den, får en fejl, og sockets oprettet til den gamle sti skal muligvis udskiftes. onRevoke er tilbagekaldet for en tunnel, som brugeren eller systemet har taget væk. At holde livscyklussen hos én ejer, der stopper læseren, før der etableres igen, forhindrer de fleste tilsluttet-men-død-tilstande.

Læs hver fejl efter det trin, den hører til. En nulstilling af forbindelsen fra recvfrom betyder, at den fjerne ende lukkede, mens din kode stadig læste. En fejl om dårlig adresse peger på header-parsing eller en offset-fejl, ikke på netværket.

Arbejdsrækkefølge: blokerende tilstand til, én pakke pr. læsning, header parset, socket beskyttet, nyttelast sendt, ren lukning ved overdragelse og en ruteliste, der dækker destinationen.

Relaterede artikler