Du har to måter å holde tunnelens egne pakker utenfor tunnelen: beskytte transportsocketen, eller ta hele appen ut av VPN-en. De løser ulike problemer, og å velge den bredere fordi den er lettere å koble opp, er nettopp måten apper mister tilgang til det lokale nettet og delt DNS uten å merke det. Start med det som må bli utenfor tunnelen, og velg så det minste omfanget som oppnår det.

Hva protect() fjerner fra tunnelen

VpnService.protect() er et kall per socket. Du gir den socketen eller deskriptoren som bærer tunnelen, og Android merker socketen slik at trafikken bruker det underliggende nettet i stedet for VPN-grensesnittet. Alt annet appen sender, følger fortsatt den vanlige VPN-ruten.

Nettopp dette smale omfanget er poenget. En beskyttet socket endrer ikke hvordan HTTP-klienten din slår opp navn, hvilket grensesnitt analysekallene dine bruker, eller hva som skjer med en andre tilkobling som åpnes senere.

Tre betingelser som avgjør omfanget

Den første betingelsen er om bare tunneltransporten må slipped ut. Hvis appens øvrige trafikk fortsatt skal gå gjennom VPN-en, er socketbeskyttelse riktig omfang. Den andre betingelsen er om du styrer opprettelsen av socketen. Et bibliotek som åpner sin egen socket dypt inne, gir deg ingen plass til å kalle protect(), med mindre det eksponerer en hook eller godtar en ferdigbygd socket. Den tredje betingelsen er om appen må nå det lokale nettet eller en captive portal direkte, noe som er et annet krav enn å holde tunnelen levende.

Å utelukke appen med addDisallowedApplication() er det grove verktøyet. Det fjerner hver tilkobling appen gjør fra VPN-en, også de du ønsket å inspisere, og på noen enheter endrer det også hvordan lokal oppdagelse oppfører seg.

Et tredje alternativ er å binde transporten til et valgt nettverk med bindProcessToNetwork() eller et nettverkshåndtak på socketnivå. Det er nyttig når enheten har Wi-Fi og mobil samtidig og du vil at tunnelen skal gå over en bestemt oppstrømslenke.

Kall det mens transporten opprettes

Kall protect() i samme blokk som oppretter transporten, før connect() eller send(). Hvis kallet returnerer false, lukk socketen og la oppsettet feile; å fortsette etterlater deg med en transport der pakkene kan komme tilbake i ditt eget grensesnitt.

Formen er liten nok til å holdes på ett sted:

DatagramSocket transport = new DatagramSocket();
if (!protect(transport)) {
    transport.close();
    throw new IOException("transport not protected");
}
transport.connect(serverAddress);

Tre detaljer biter her. For det første,booleanen er det eneste signalet du får, så logg feilen med nok kontekst til å skille en tilbakekalt samtykke fra et avvist kall. For det andre,protect() dekker socketen, ikke deskriptornummeret: hvis du lukker socketen og senere gjenbruker samme heltall for en annen socket, er den nye socketen ikke beskyttet. For det tredje, når et nativt bibliotek åpner transporten, beskytter du deskriptoren før den native koden kobler til, ellers kan ruten allerede være valgt.VpnService-referansen er stedet der du bekrefter hvilke overbelastninger ditt mål-API-nivå faktisk tilbyr.

Test den blokkerte veien, ikke bare den glade

Verifiser med tunnelen oppe, fordi det er den eneste tilstanden der de to veiene kan skille seg. Send én forespørsel gjennom den beskyttede transporten og én fra en kodevei som aldri kalte protect(), og sammenlign så hva den fjerne enden ser: den beskyttede tilkoblingen skal ankomme med det underliggende nettet som kilde, mens den ubeskyttede viser tunnelens egen adresse og flytter tunnelens byttetellere. På enheten viser ss eller netstat den samme delingen før noe forlater radioen.

Test tilbakekalling separat, fordi en endring i samtykke river ned grensesnittet uten å røre socketkoden din. Tilbakekall VPN-samtykket eller stopp tjenesten, bygg transporten på nytt, og bekreft at protect() fortsatt returnerer true og at transporten kobler til igjen så snart samtykket gis på nytt.

Hvis den beskyttede forespørselen likevel følger tunnelen, skjer beskyttelseskallet sannsynligvis etter at socketen allerede er rutet. Flytt det tidligere og test igjen.

Hold beslutningen skrevet ned ved siden av transportoppsettet: hvilke sockets som er beskyttet, hvilken apptrafikk som er utelukket, og hva appen gjør når beskyttelsen feiler. Nettopp den linjen hindrer en senere refaktorering i å utvide utelukkelsen til hele appen fordi én test feilet. Den gir også neste person en måte å sjekke hensikten på uten å reversere manifestet.

Relaterte artikler