Постоје два начина да се пакети самог тунела задрже изван тунела: заштитите транспортни сокет или извадите целу апликацију из VPN-а. Они решавају различите проблеме, а избор ширег само зато што се лакше повезује управо је оно због чега апликације неприметно изгубе приступ локалној мрежи и подељени DNS. Почните од онога што мора да остане изван тунела, па изаберите најмањи обим који то постиже.

Шта protect() уклања из тунела

VpnService.protect() је позив за појединачни сокет. Предате му сокет или дескриптор који носи тунел, а Android тај сокет означава тако да његов саобраћај користи основну мрежу уместо VPN интерфејса. Све остало што апликација пошаље и даље иде уобичајеном VPN путањом.

Управо тај узак обим је поента. Заштићени сокет не мења како ваш HTTP клијент разрешава имена, који интерфејс користе ваши аналитички позиви, ни шта се дешава са другом везом отвореном касније.

Три услова која одређују обим

Први услов је да ли само транспорт тунела треба да побегне. Ако остали саобраћај апликације треба и даље да иде кроз VPN, заштита сокета је прави обим. Други услов је да ли ви контролишете стварање сокета. Библиотека која отвара сопствени сокет дубоко у себи не оставља вам место за позив protect() осим ако излаже куку или прихвата унапред направљен сокет. Трећи услов је да ли апликација мора директно да дохвати локалну мрежу или пријавни портал, што је други захтев од одржавања тунела у животу.

Изузимање апликације помоћу addDisallowedApplication() је тупо оруђе. Оно уклања из VPN-а сваку везу коју апликација успостави, укључујући оне које сте хтели да прегледате, а на неким уређајима мења и понашање локалног откривања.

Трећа опција је да транспорт вежете за изабрану мрежу помоћу bindProcessToNetwork() или мрежне ручке на нивоу сокета. То је корисно када уређај истовремено има Wi-Fi и мобилну мрежу и желите да тунел иде одређеним успонским путем.

Позовите га док се транспорт ствара

Позовите protect() у истом блоку који ствара транспорт, пре connect() или send(). Ако позив врати false, затворите сокет и нека подешавање не успе; наставак вам оставља транспорт чији пакети могу поново ући у ваш сопствени интерфејс.

Облик је довољно мали да остане на једном месту:

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

Три детаља овде гризу. Прво, логичка вредност је једини сигнал који добијате, зато неуспех забележите са довољно контекста да разликујете опозвани пристанак од одбијеног позива. Друго, protect() покрива сокет, не број дескриптора: ако затворите сокет и касније исти цели број употребите за други сокет, нови сокет није заштићен. Треће, када нативна библиотека отвори транспорт, заштите дескриптор пре него што се нативни код повеже, иначе рута можда већ је изабрана. Референца VpnService је место где потврђујете која преоптерећења ваша циљна API разина заиста пружа.

Тестирајте блокирану путању, не само срећну

Проверавајте са подигнутим тунелом, јер је то једино стање у ком две путање могу да се разликују. Пошаљите један захтев кроз заштићени транспорт и један из кода који никада није позвао protect(), па упоредите шта види далеки крај: заштићена веза треба да стигне са основном мрежом као извором, док незаштићена показује адресу самог тунела и помера његове бројаче бајтова. На уређају ss или netstat показује исту поделу пре него што било шта напусти радио.

Опозив тестирајте одвојено, јер промена пристанка руши интерфејс без додиривања вашег кода сокета. Опозовите пристанак за VPN или зауставите услугу, поново изградите транспорт и потврдите да protect() и даље враћа true и да се транспорт поново повезује када пристанак поново буде дат.

Ако заштићени захтев ипак прати тунел, позив заштите вероватно се дешава пошто је сокет већ рутиран. Померите га раније и поново тестирајте.

Одлуку држите записану поред подешавања транспорта: који сокети су заштићени, који саобраћај апликације је изузет и шта апликација ради када заштита не успе. Та линија спречава каснији рефактор да прошири изузеће на целу апликацију зато што је један тест падао. Она такође даје следећој особи начин да провери намеру без реверс инжењеринга манифеста.

Повезани чланци