Du har två sätt att hålla tunnelns egna paket utanför tunneln: skydda transportsocketen, eller ta ut hela appen ur VPN:en. De löser olika problem, och att välja den bredare bara för att den är lättare att koppla in är precis så appar tappar lokal nätverksåtkomst och delad DNS utan att märka det. Börja med det som måste stanna utanför tunneln och välj sedan den minsta omfattning som åstadkommer det.

Vad protect() tar bort från tunneln

VpnService.protect() är ett anrop per socket. Du ger den socketen eller deskriptorn som bär tunneln, och Android märker den socketen så att dess trafik använder det underliggande nätverket i stället för VPN-gränssnittet. Allt annat appen skickar följer fortfarande den vanliga VPN-vägen.

Just den snäva omfattningen är poängen. En skyddad socket ändrar inte hur din HTTP-klient löser namn, vilket gränssnitt dina analysanrop använder, eller vad som händer med en andra anslutning som öppnas senare.

Tre villkor som avgör omfattningen

Villkor ett är om bara tunnelns transport behöver slippa ut. Om appens övriga trafik fortfarande ska gå genom VPN:en är socketsskydd rätt omfattning. Villkor två är om du styr socketsskapandet. Ett bibliotek som öppnar sin egen socket djupt inne ger dig ingen plats att anropa protect() om det inte exponerar en krok eller tar emot en färdigbyggd socket. Villkor tre är om appen måste nå det lokala nätverket eller en inloggningsportal direkt, vilket är ett annat krav än att hålla tunneln vid liv.

Att utesluta appen med addDisallowedApplication() är det trubbiga verktyget. Det tar bort varje anslutning appen gör från VPN:en, inklusive dem du ville granska, och på vissa enheter ändrar det också hur lokal upptäckt beter sig.

Ett tredje alternativ är att binda transporten till ett valt nätverk med bindProcessToNetwork() eller ett nätverkshandtag på socketnivå. Det är användbart när enheten har Wi-Fi och mobilnät samtidigt och du vill att tunneln ska gå över en viss upplänk.

Anropa det medan transporten skapas

Anropa protect() i samma block som skapar transporten, före connect() eller send(). Om anropet returnerar false stänger du socketen och låter konfigurationen misslyckas; att fortsätta lämnar dig med en transport vars paket kan komma in i ditt eget gränssnitt igen.

Formen är liten nog att hålla på ett ställe:

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

Tre detaljer biter här. För det första, det booleska värdet är den enda signal du får, så logga felet med tillräckligt sammanhang för att skilja ett återkallat samtycke från ett avvisat anrop. För det andra täcker protect() socketen, inte deskriptornumret: om du stänger socketen och senare återanvänder samma heltal för en annan socket är den nya socketen inte skyddad. För det tredje, när ett inbyggt bibliotek öppnar transporten, skydda deskriptorn innan den inbyggda koden ansluter, annars kan vägen redan vara vald. VpnService-referensen är stället där du bekräftar vilka överlagringar din API-nivå faktiskt erbjuder.

Testa den blockerade vägen, inte bara den lyckliga

Verifiera med tunneln uppe, eftersom det är det enda tillstånd där de två vägarna kan skilja sig. Skicka en begäran genom den skyddade transporten och en från en kodväg som aldrig anropade protect(), jämför sedan vad den bortre änden ser: den skyddade anslutningen bör anlända med det underliggande nätverket som källa, medan den oskyddade visar tunnelns egen adress och flyttar tunnelns byte-räknare. På enheten visar ss eller netstat samma uppdelning innan något lämnar radion.

Testa återkallelse separat, eftersom en samtyckesändring river gränssnittet utan att röra din socketkod. Återkalla VPN-samtycket eller stoppa tjänsten, bygg om transporten och bekräfta att protect() fortfarande returnerar true och att transporten återansluter när samtycket ges igen.

Om den skyddade begäran ändå följer tunneln sker skyddsanropet förmodligen efter att socketen redan routats. Flytta det tidigare och testa igen.

Håll beslutet nedskrivet bredvid transportkonfigurationen: vilka sockets som är skyddade, vilken apptrafik som utesluts och vad appen gör när skyddet misslyckas. Den raden är det som hindrar en senare refaktorering från att bredda uteslutningen till hela appen därför att ett enda test misslyckades. Den ger också nästa person ett sätt att kontrollera avsikten utan att reverse-engineera manifestet.

Relaterade artiklar