Подељено тунелирање у Android VpnService: шта се заиста може рутирати
VpnService прослеђује само пакете које builder ставља у свој дескриптор. Проверите префиксе, аутентификацију проксија, DNS и листе апликација пре него што оптужите подељено тунелирање.
Подељено тунелирање у апликацији са VpnService квари се на неколико конкретних места, а поправка је различита за свако. Клијент приказује само једну одредишну тачку која не пролази, па је први посао да утврдите на коју границу ударате: руте које је builder добио, прокси који не можете да аутентификујете, resolver који сте само објавили или листу апликација коју платформа примењује по кориснику. Зато држим проверу у четири корака. Она иде по реду испод и зауставља се на првој грешци.
Потврдите шта је builder предао платформи
VpnService.Builder је листа декларација. establish() враћа ParcelFileDescriptor за виртуелни интерфејс, а платформа у тај дескриптор шаље само саобраћај који одговара префиксима из addAddress и addRoute; addDnsServer говори клијентима који resolver да питају. Одредиште изван свих префикса које сте додали никада не стиже до ваше апликације, шта год интерфејс обећавао. Ова једна чињеница објашњава већину пријава о поквареном подељеном тунелирању пре било какве промене кода.
Запишите у дневник листе адреса, рута и DNS-а које је builder саставио, па их упоредите са одредиштима која не пролазе. Недостајући /32 за административни хост много је чешћи од поквареног тунела. Ако је листа исправна, идите даље; ако није, остатак контролне листе је узалудан труд.
excludeRoute постоји само од API 33
Искључивање по одредишту је захтев који најчешће заврши као тема подршке. builder.excludeRoute(IpPrefix) стигао је у API 33; пре тога једина подржана апроксимација је додавање рута за све осим искљученог префикса, што значи да комплементарне префиксе морате сами израчунати. Два позива нису огледалне слике: један одузима од скупа рута интерфејса, други гради скуп од нуле, а старији уређај ће бацити изузетак или тихо игнорисати позив који не познаје. Гранајте се по Build.VERSION.SDK_INT и тестирајте оба пута на стварном хардверу.
Када рачунате комплементе, пазите на поравнање префикса. Искључивање /24 унутар руте /16 тражи покривајуће префиксе који стварно описују остатак, а не један деформисани /8. Запишите коначну листу и проверите да се искључени хост још разрешава путем изван тунела.
Прокси акредитиви не могу да се изразе
setHttpProxy(ProxyInfo) усмерава интерфејс на HTTP или SOCKS прокси, а PAC URL може да бира између њих по одредишту. Ни ProxyInfo ни PAC формат не носе корисничко име и лозинку, па прокси који тражи аутентификацију остаје изван API-ја платформе. Апликације којима је потребан обично уграђују кориснички стек, tun2socks или сличан мотор и саме одговарају на изазов 407.
Тај избор има последице. Кориснички стек види само саобраћај који му усмеравате, па мора да живи у истим границама дескриптора и наслеђује исти захтев за protect() за сопствени uplink сокет. Прескочите protect() и тунел хвата сопствени транспорт, што се види као руковање које стане после првог пакета. Тестирајте против проксија који тражи акредитиве, јер отворени прокси пролази путем платформе и ништа не доказује.
Листе по апликацији и DNS су такође декларације
Builder има и addAllowedApplication и addDisallowedApplication, а оне се међусобно искључују: позив обема баца изузетак. У сваком случају листа се примењује по кориснику при подешавању интерфејса, па апликацијом радног профила управља тунел радног профила, а не ваш. Оно што ниједан API не даје јесте контрола над одлуком друге апликације: не можете споља да угурате пакет у свој тунел и не можете да затворите или преконфигуришете већ активан VPN.
DNS следи исти образац. addDnsServer објављује адресу resolver-а; то не дозвољава вашој апликацији да одговара на упите. Ако желите сами да разрешавате имена, покрените resolver и усмерите његову адресу у тунел. Апликације које користе DoT или DoH ка фиксном имену хоста могу да заобиђу објављену адресу, па разрешавање имена третирајте као најбољи покушај, осим ако не пресрећете и пут упита.
Манифест, always-on и тест у четири корака
Подобност се декларише у манифесту: BIND_VPN_SERVICE, intent филтер android.net.VpnService и foreground услуга ако тунел мора да преживи у позадини. Always-on VPN додатно тражи чист onRevoke, јер га систем позива када корисник промени подешавања или друга апликација преузме контролу.
Извршите провере по реду и зауставите се на првој грешци. Прво, испишите листе адреса, рута и DNS-а предате builder-у. Друго, потврдите да је establish() вратио дескриптор и да је транспортни сокет заштићен. Треће, затражите једно одредиште унутар укљученог префикса и једно унутар искљученог. Четврто, проверите који resolver је стварно одговорио питајући име хоста које само ваш resolver може да разреши.
Завршено руковање не доказује ниједан од каснијих корака. Свака провера изолује један слој, па успех на другом кораку ништа не говори о четвртом.
Референца за VpnService.Builder набраја целу површину builder-а. Све што вам треба изван ње је други API, друга апликација или ваш сопствени кориснички стек, а то је дизајнерска одлука, не дефект у подељеном тунелирању.