Android VpnService kobler til, men ingen pakker når nettverket
Tunnelen er oppe og sidene feiler fortsatt: sjekk lesemodus, socket-beskyttelse, TCP-støtte og rutelisten før du skylder på VPN-serveren.
Tunnelen kommer opp, nøkkelikonet vises, og hver forespørsel i nettleseren timer fortsatt ut. I en VpnService-app betyr den kombinasjonen vanligvis at leseløkken lever og utgangshalvdelen er ødelagt, ikke at serveren avviste håndtrykket. Android har allerede opprettet grensesnittet, flyttet apptrafikken inn i det og gitt prosessen din en filbeskrivelse. Alt etter den beskrivelsen er koden din, og ett manglende kall er nok til å svelge hele strømmen.
Hva systemet gir deg, og hva det ikke gir
establish() returnerer en ParcelFileDescriptor for et tun-grensesnitt. Systemet skriver utgående IP-pakker inn i det og forventer at prosessen din leser dem, bestemmer hvor de skal og sender dem gjennom en vanlig socket. Under ligger det ingen kjernesending, så når ingen leser, droppes pakkene og grensesnittets tellere beveger seg aldri.
Begynn med å telle i stedet for å gjette. Øk en teller ved hver vellykket lesing og logg destinasjon og protokoll for de første pakkene. Hvis telleren blir stående på null mens trafikk genereres, ligger feilen før videresendingskoden din, noe som snevrer søket inn til builder-konfigurasjonen og lesemodusen. Hvis telleren beveger seg og nettleseren fortsatt feiler, ligger feilen i sendeveien eller på veien tilbake.
Blokkerende lesing avgjør om løkken beveger seg
setBlocking(true) setter beskrivelsen i blokkerende modus og har eksistert siden API 21. På eldre utgivelser er løsningen en liten JNI-hjelper som fjerner O_NONBLOCK med fcntl på den rå beskrivelsen. Modusen betyr noe fordi den avgjør hva en lesing uten ventende pakke har lov til å gjøre.
I ikke-blokkerende modus returnerer den lesingen umiddelbart med en negativ verdi, og en løkke som ignorerer resultatet spinner en kjerne uten å flytte trafikk i det hele tatt. I blokkerende modus venter lesingen, som er det en videresendingstråd vil, men tråden kan da ikke lenger avbrytes. Å lukke beskrivelsen er den støttede måten å få lesingen til å feile slik at løkken kan avsluttes. Å drepe prosessen eller avbryte tråden kan i stedet etterlate neste oppstart med en beskrivelse som ikke lenger passer til grensesnittet.
Les én pakke per kall inn i en buffer på størrelse med grensesnittets MTU, og behandle null eller en negativ retur som en feil, ikke som en tom pakke.
protect() før den første sendingen, ikke etter
Hver socket som bærer en innkapslet pakke må beskyttes først. En ubeskyttet socket følger de samme rutereglene du nettopp installerte, så pakkene går tilbake i tunnelen og din egen leseløkke plukker dem opp igjen. Tunnelen videresender da sin egen utgang i det uendelige, og utenfra ser det ut som en tilkobling som åpnes og aldri svarer.
Rekkefølgen er protect, deretter connect eller bind, deretter send. Å kalle protect etter connect kan la den tidligere rutebeslutningen stå, så socketen fortsetter å bruke feil vei selv om kallet returnerte true. Sjekk booleanen den returnerer og feil høylytt når den er false, i stedet for å gå inn i en sending som ikke kan virke.
En tillatelsesfeil fra sendto på en socket som aldri har rørt protect(), er den vanlige signaturen på denne feilen.
UDP-eksempelet bærer ikke HTTPS
Videresendingseksempelet som de fleste veiledninger kopierer, håndterer UDP og ingenting annet. TCP trenger et håndtrykk, sekvensnumre, bekreftelser og omsending, og en løkke som kopierer nyttelast mellom en tun-beskrivelse og en socket gir ingen av delene. Derfor kan en UDP-DNS-forespørsel komme gjennom mens hver HTTPS-forespørsel stopper. Kjør enten en TCP-stack i brukerrommet eller overlat tilkoblingene til en lokal proxy som avslutter TCP på enheten og videresender strømmen.
Test delingen før du skriver om noe: send én UDP-forespørsel og åpne én TCP-tilkobling til samme vert. Hvis UDP-veien virker og TCP-veien henger ved første SYN, er videresendingskoden problemet og ikke serveren. Den ene sammenligningen sparer mye tid som brukes på å lese serverlogger uten noe nyttig innhold.
Rutelisten og filtre per app begrenser hva du ser
addAddress, addRoute og addDns definerer hva systemet sender inn i tunnelen. En destinasjon utenfor rutelisten når aldri beskrivelsen din, så ingen mengde videresendingslogikk vil rette det. Logg builder-kallene du gjør og sammenlign dem med adressene appen faktisk prøver å nå.
Valg per app har sin egen nedre grense: addAllowedApplication og addDisallowedApplication krever API 21, og på eldre utgivelser mangler metodene rett og slett. Et design som filtrerer på pakke kan ikke kjøre der i det hele tatt.
Gjenoppretting etterlater utdaterte beskrivelser
Et nytt kall til establish() returnerer en ny beskrivelse og lukker forrige grensesnitt. Den gamle beskrivelsen er nå ugyldig, så en tråd som fortsatt blokkerer på den får en feil, og sockets laget for den gamle veien må kanskje byttes ut. onRevoke er tilbakemeldingen for en tunnel som brukeren eller systemet har tatt bort. Å holde livssyklusen hos én eier, som stopper leseren før ny oppretting, hindrer de fleste tilkoblet-men-død-tilstander.
Les hver feil etter trinnet den hører til. En tilbakestilling av tilkoblingen fra recvfrom betyr at den fjerne enden lukket mens koden din fortsatt leste. En feil om dårlig adresse peker på header-parsing eller en offset-feil, ikke på nettverket.
Arbeidsrekkefølge: blokkerende modus på, én pakke per lesing, header parset, socket beskyttet, nyttelast sendt, ren lukking ved overlevering og en ruteliste som dekker destinasjonen.