Ingenting i VpnService fångar trafik av sig självt. Det ber systemet om ett tun-gränssnitt, ger det gränssnittet en uppsättning adresser och rutter, och från det ögonblicket kopierar kärnan varje paket som matchar dessa rutter till en filbeskrivning som appen kan läsa. Appens kod ser aldrig paketen som rutterna inte valde, så omfattningen av en tunnel är en fråga om routingtabellen innan den är en programmeringsfråga. Därför kan samma tjänsteimplementering svälja varje flöde på en telefon och bara en del på en annan, och därför reparerar en extra rutt ibland en tunnel som såg trasig ut. Det du får är det du deklarerade, minus allt byggaren tyst avvisade.

Rutter avgör vilka paket som anländer

Builder.addRoute(prefix, prefixLength) installerar ett prefix i routningstabellen för VPN-nätet. Systemet behandlar det nätet som standard för apparna inom omfattningen, så dess poster läses före det fysiska nätets standardrutt och ett matchande prefix räcker för att avleda ett flöde. Därför tar en rutt för 0.0.0.0/0 IPv4 i sin helhet, inklusive skrivaren, cast-målet och routerns administratörssida.

Klienter som vill ha den täckningen utan att radera den fysiska standardposten använder i stället paret 0.0.0.0/1 och 128.0.0.0/1. Två mer specifika rutter slår en standardrutt, tillsammans täcker de hela adressutrymmet, och telefonens egen standardpost ligger kvar i tabellen för de sockets som fortfarande är bundna till det fysiska nätet.

Adresser och DNS-poster kommer från samma byggare och svarar på en annan fråga. addAddress ger gränssnittet en adress med prefixlängd, och addDnsServer installerar en resolver som apparna inom omfattningen kommer att fråga via gränssnittet. Båda familjerna kan sitta på ett gränssnitt, så en uppsättning rutter med bara IPv4 bredvid en IPv6-adress är en konfiguration som läses korrekt och ändå läcker. Om du routar en familj där gränssnittet inte har någon adress har kärnan ingen källadress för de paketen och de dyker aldrig upp vid beskrivningen.

Vad establish() lämnar tillbaka

establish() returnerar en ParcelFileDescriptor, eller null när appen inte är auktoriserad. Null är ett samtyckestillstånd och inte ett nätverksfel: VpnService.prepare() måste redan ha returnerat null, eller så måste användaren ha godkänt systemdialogen före anropet. Samtycket kan dras tillbaka medan tjänsten körs, och gränssnittet stängs under fötterna på dig utan någon callback till din lässlinga.

Beskrivningen är icke-blockerande om du inte säger annat, så en läsning återvänder direkt utan data i stället för att parkera tråden. En slinga skriven för en blockerande socket bränner en kärna, och en slinga som behandlar en tom läsning som fatal sliter sönder tunneln under en inaktiv sekund. Builder.setBlocking(true) ändrar det beteendet, och priset är en tråd som väntar inne i läsningen.

Rutt- och adressargument valideras som LinkAddress-värden, och multicast-mål som 224.0.0.0/4 avvisas direkt. Ett punkt-till-punkt tun-gränssnitt har inte heller någon broadcast-domän, så multicast-upptäckt däröver kräver en annan design i stället för ännu en rutt. Referensen VpnService.Builder är listan du bör kontrollera innan du drar slutsatsen att ett anrop ignorerades.

Omfattningen per app snävar in mängden, inte tunneln

Från API 21 accepterar byggaren addAllowedApplication och addDisallowedApplication, och de två listorna utesluter varandra inom en byggare. En tillåtelselista gör tunneln befintlig för de paketen medan allt annat behåller det fysiska nätet; en blockeringslista fångar allt utom de namngivna paketen, vilket en klient erbjuder som delad tunnling. Före API 21 finns ingen plattformsmekanism alls, och beteendet per app kom från tredjepartsklienter som filtrerade på UID i sin egen vidarebefordrare. Skillnaden spelar roll vid felsökning, eftersom en app-tunnel med en tillåtelselista på ett paket nästan inte ger någon trafik och det är korrekt beteende, inte en trasig rutt.

Vilken omfattning det än gäller får socketen som din app öppnar mot VPN-servern inte färdas genom tunneln som den bygger. VpnService.protect(socket) binder den socketen till det underliggande nätet så att dess paket hoppar över tun-enheten. Om du hoppar över anropet slukas handslaget av gränssnittet som skulle ha rest det.

VpnService.setUnderlyingNetworks sluter kretsen genom att berätta för systemet vilket fysiskt nät som bär tunneln. Det värdet avgör hur VPN rapporterar anslutning till andra appar, och ett null- eller inaktuellt värde är en vanlig orsak till en ansluten tunnel som resten av telefonen behandlar som offline. Uppdatera det när det underliggande nätet ändras, inte bara vid start.

Kontrollsummor och MTU: där en korrekt rutt ändå misslyckas

När paketen väl anländer avgör två detaljer på paketnivå om något fungerar från ände till ände. Den första är källadressen: med flera gränssnittsadresser kan kärnan skicka paket som bär den första, så en vidarebefordrare som skriver om huvuden måste rätta adressen och den kontrollsumma som täcker den.

Den andra är kontrollsummans omfattning. När appen själv bygger TCP- eller UDP-paket täcker kontrollsumman ett pseudohuvud med källadress, destinationsadress, protokollnummer och transportlängd, och att utelämna något av dem ger paket som anländer och kastas. Det felet ser ut som en tidsgräns efter ett svar som aldrig kommer, vilket skickar de flesta tillbaka till rutterna. setMtu sätter gränssnittets MTU, och varje läsning returnerar exakt ett komplett IP-paket, så en buffert skriven för en byteström fragmenterar din tolkning, inte paketet.

En verifieringsrunda som skiljer lagren

För att hitta var omfattningen gick fel testar du lagren i ordning. Fråga enheten vilka rutter VPN-nätet faktiskt håller, kontrollera sedan om ett flöde till en känd adress dyker upp som en läsning vid beskrivningen, och kontrollera sedan om paketet som din vidarebefordrare skickar är välformat. Ett ruttproblem visar inget vid beskrivningen, medan ett kontrollsummeproblem visar ett paket som anländer och försvinner.

Om inget når beskrivningen är det ruttmängden som är misstänkt och inte läsaren: bekräfta prefixen, bekräfta att det finns en adress i samma familj och bekräfta att appen ligger inom den omfattning du satte. Om paketen når beskrivningen men inget kommer tillbaka, undersök protect() och värdet för det underliggande nätet innan du rör en rutt igen. Om den bortre sidan rapporterar felaktiga paket är kontrollsumman och MTU:n ställena att titta på. En sond per lager sparar en eftermiddag av gissningar.

Relaterade artiklar