Rozdělené tunelování v Android VpnService: co lze skutečně směrovat
VpnService předá jen pakety, které builder pošle svému deskriptoru. Zkontrolujte prefixy, autentizaci proxy, DNS a seznamy aplikací, než obviníte rozdělené tunelování.
Rozdělené tunelování v aplikaci s VpnService se láme na několika konkrétních místech a oprava je pro každé jiná. Klient zobrazí jen jednu nefunkční destinaci, takže prvním úkolem je zjistit, na kterou hranici narážíte: trasy, které builder dostal, proxy, kterou nedokážete autentizovat, resolver, který jste jen oznámili, nebo seznam aplikací, který platforma uplatňuje pro každého uživatele. Právě proto držím kontrolu ve čtyřech krocích. Jde v pořadí níže a zastaví se na první chybě.
Ověřte, co builder předal platformě
VpnService.Builder je seznam deklarací. establish() vrací ParcelFileDescriptor pro virtuální rozhraní a platforma do tohoto deskriptoru posílá jen provoz odpovídající prefixům z addAddress a addRoute; addDnsServer říká klientům, který resolver mají dotazovat. Destinace mimo všechny přidané prefixy se do vaší aplikace nikdy nedostane, ať rozhraní slibuje cokoli. Tento jediný fakt vysvětluje většinu hlášení o rozbitém rozděleném tunelování ještě před jakoukoli změnou kódu.
Zalogujte seznamy adres, tras a DNS, které builder sestavil, a porovnejte je s destinacemi, které selhávají. Chybějící /32 pro správcovský host je mnohem častější než rozbitý tunel. Pokud je seznam správný, pokračujte; pokud ne, zbytek kontrolního seznamu je zbytečná práce.
excludeRoute existuje až od API 33
Vyloučení podle destinace je požadavek, který nejčastěji skončí jako vlákno podpory. builder.excludeRoute(IpPrefix) přišel v API 33; předtím je jediná podporovaná aproximace přidat trasy pro vše kromě vyloučeného prefixu, což znamená dopočítat doplňkové prefixy sami. Obě volání nejsou zrcadlové obrazy: jedno odečítá ze sady tras rozhraní, druhé sadu staví od nuly, a starší zařízení volání, které nezná, buď vyhodí, nebo tiše ignoruje. Větvěte podle Build.VERSION.SDK_INT a obě cesty testujte na skutečném hardwaru.
Při výpočtu doplňků hlídejte zarovnání prefixů. Vyloučení /24 uvnitř trasy /16 potřebuje krycí prefixy, které skutečně popisují zbytek, ne jeden špatně tvarovaný /8. Zalogujte konečný seznam a ověřte, že vyloučený host se stále překládá cestou mimo tunel.
Přihlašovací údaje proxy nelze vyjádřit
setHttpProxy(ProxyInfo) nasměruje rozhraní na HTTP nebo SOCKS proxy a URL PAC může mezi nimi vybírat podle destinace. Ani ProxyInfo, ani formát PAC nenese uživatelské jméno a heslo, takže autentizovaná proxy zůstává mimo API platformy. Aplikace, které ji potřebují, obvykle vkládají uživatelský stack, tun2socks nebo podobný engine a na výzvu 407 odpovídají samy.
Tato volba má důsledky. Uživatelský stack vidí jen provoz, který do něj nasměrujete, takže musí žít uvnitř stejné hranice deskriptoru a dědí stejný požadavek na protect() pro svůj vlastní uplink socket. Vynechejte protect() a tunel zachytí vlastní transport, což se projeví jako handshake, který se po prvním paketu zasekne. Testujte proti proxy, která vyžaduje přihlašovací údaje, protože otevřená proxy projde cestou platformy a nic nedokazuje.
Seznamy aplikací i DNS jsou také deklarace
Builder má addAllowedApplication i addDisallowedApplication a navzájem se vylučují: volání obou vyhodí výjimku. V obou případech se seznam uplatní pro každého uživatele při nastavení rozhraní, takže aplikaci v pracovním profilu řídí tunel pracovního profilu, ne ten váš. Žádné API vám nedá kontrolu nad rozhodnutím jiné aplikace: nemůžete zvenčí dotlačit balíček do svého tunelu a nemůžete zavřít nebo překonfigurovat už aktivní VPN.
DNS sleduje stejný vzor. addDnsServer oznamuje adresu resolveru; neumožňuje vaší aplikaci odpovídat na dotazy. Pokud chcete názvy překládat sami, spusťte resolver a nasměrujte jeho adresu do tunelu. Aplikace používající DoT nebo DoH k pevnému názvu hostitele mohou oznámenou adresu obejít, takže překlad názvů berte jako nejlepší snahu, dokud nezachytíte i cestu dotazu.
Manifest, always-on a test ve čtyřech krocích
Způsobilost se deklaruje v manifestu: BIND_VPN_SERVICE, filtr intentu android.net.VpnService a foreground služba, pokud má tunel přežít na pozadí. Always-on VPN navíc vyžaduje čistý onRevoke, protože systém ho volá, když uživatel změní nastavení nebo převezme kontrolu jiná aplikace.
Proveďte kontroly v pořadí a zastavte se na první chybě. Za prvé, vypište seznamy adres, tras a DNS předané builderu. Za druhé, ověřte, že establish() vrátil deskriptor a že transportní socket je chráněný. Za třetí, vyžádejte jednu destinaci uvnitř zahrnutého prefixu a jednu uvnitř vyloučeného. Za čtvrté, zjistěte, který resolver skutečně odpověděl, dotazem na název hostitele, který umí přeložit jen váš resolver.
Dokončený handshake nedokazuje žádný z dalších kroků. Každá kontrola izoluje jednu vrstvu, takže úspěch ve druhém kroku nic neříká o čtvrtém.
Referenční příručka VpnService.Builder uvádí celé rozhraní builderu. Všechno, co potřebujete nad rámec, je jiné API, jiná aplikace nebo váš vlastní uživatelský stack, a to je rozhodnutí o návrhu, ne chyba rozděleného tunelování.