Delad tunnling i en VpnService-app går sönder på några få konkreta ställen, och lagningen skiljer sig för varje. Klienten visar bara en destination som misslyckas, så första uppgiften är att ta reda på vilken gräns du träffar: rutterna som builder fick, proxyn du inte kan autentisera, resolvern du bara annonserade eller applistan som plattformen tillämpar per användare. Just därför håller jag en kontroll i fyra steg. Den går i ordningen nedan och stannar vid första felet.

Bekräfta vad builder gav plattformen

VpnService.Builder är en deklarationslista. establish() returnerar en ParcelFileDescriptor för ett virtuellt gränssnitt, och plattformen skickar bara trafik som matchar prefixen från addAddress och addRoute in i den deskriptorn; addDnsServer talar om för klienterna vilken resolver de ska fråga. En destination utanför alla prefix du lade till når aldrig din app, vad gränssnittet än lovar. Detta enda faktum förklarar de flesta rapporterna om trasig delad tunnling innan någon kod ändras.

Logga adress-, rutt- och DNS-listorna som din builder satte ihop och jämför dem med destinationerna som misslyckas. Ett saknat /32 för en administrationsvärd är mycket vanligare än en trasig tunnel. Är listan rätt, gå vidare; är den fel är resten av checklistan bortkastat arbete.

excludeRoute finns först från API 33

Destinationsbaserat undantag är den förfrågan som oftast blir en supporttråd. builder.excludeRoute(IpPrefix) kom i API 33; före det är den enda stödda approximationen att lägga till rutter för allt utom det undantagna prefixet, vilket innebär att du själv måste beräkna de komplementära prefixen. De två anropen är inte spegelbilder: det ena subtraherar från gränssnittets ruttmängd, det andra bygger mängden från noll, och en äldre enhet kastar eller ignorerar tyst ett anrop den inte känner igen. Förgrena på Build.VERSION.SDK_INT och testa båda vägarna på riktig hårdvara.

När du beräknar komplement, håll koll på prefixjusteringen. Ett /24-undantag inom en /16-rutt kräver de täckande prefix som faktiskt beskriver resten, inte en enda felformad /8. Logga den slutliga listan och verifiera att den undantagna värden fortfarande löses via en väg utanför tunneln.

Proxyuppgifter går inte att uttrycka

setHttpProxy(ProxyInfo) pekar gränssnittet mot en HTTP- eller SOCKS-proxy, och en PAC-URL kan välja mellan dem per destination. Varken ProxyInfo eller PAC-formatet bär användarnamn och lösenord, så en proxy som kräver autentisering ligger utanför plattformens API. Appar som behöver det bäddar oftast in en userspace-stack, tun2socks eller en liknande motor och svarar själva på 407-utmaningen.

Det valet har konsekvenser. En userspace-stack ser bara trafik du routar till den, så den måste leva inom samma deskriptorgräns, och den ärver samma protect()-krav för sin egen uplink-socket. Hoppar du över protect() fångar tunneln sin egen transport, vilket syns som ett handskak som fastnar efter det första paketet. Testa mot en proxy som kräver uppgifter, för en öppen proxy passerar på plattformens väg och bevisar ingenting.

Applistor och DNS är också deklarationer

Builder har både addAllowedApplication och addDisallowedApplication, och de utesluter varandra: att anropa båda kastar ett undantag. Hur som helst tillämpas listan per användare vid gränssnittsuppsättningen, så en app i arbetsprofilen styrs av arbetsprofilens tunnel och inte din. Det ingen API ger dig är kontroll över en annan apps beslut: du kan inte trycka in ett paket i din tunnel utifrån, och du kan inte stänga eller omkonfigurera en VPN som redan är aktiv.

DNS följer samma mönster. addDnsServer annonserar en resolveradress; det låter inte din app svara på frågor. Vill du lösa namn själv, kör en resolver och routa dess adress in i tunneln. Appar som använder DoT eller DoH mot ett fast värdnamn kan kringgå adressen du annonserade, så behandla namnlösning som bästa ansträngning om du inte också fångar upp frågevägen.

Manifest, always-on och fyrastegetstestet

Behörighet deklareras i manifestet: BIND_VPN_SERVICE, intent-filtret android.net.VpnService och en förgrundstjänst om tunneln måste överleva i bakgrunden. Always-on VPN kräver dessutom en ren onRevoke, eftersom systemet anropar den när användaren ändrar inställningar eller en annan app tar över kontrollen.

Kör kontrollerna i ordning och stanna vid första felet. Först: dumpa adress-, rutt- och DNS-listorna som gavs till builder. Sedan: bekräfta att establish() returnerade en deskriptor och att transportsocketen är skyddad. Tredje: begär en destination inom ett inkluderat prefix och en inom ett undantaget prefix. Fjärde: kontrollera vilken resolver som faktiskt svarade genom att fråga efter ett värdnamn som bara din resolver kan lösa.

Ett slutfört handskak bevisar inget av de senare stegen. Varje kontroll isolerar ett lager, så en passering vid steg två säger ingenting om steg fyra.

VpnService.Builder-referensen listar hela builder-ytan. Allt du behöver utöver det är ett annat API, en annan app eller din egen userspace-stack, och det är ett designbeslut snarare än en bugg i delad tunnling.

Relaterade artiklar