VpnService protect() или исклучување на апликацијата: што го поправа рутирањето
Заштитете го сокетот што го носи тунелот или исклучете ја целата апликација: трите услови што одредуваат кој опсег му е потребен на вашиот Android VPN.
Имате два начина да ги држите сопствените пакети на тунелот надвор од тунелот: да го заштитите транспортниот сокет или да ја извадите целата апликација од VPN. Тие решаваат различни проблеми, а изборот на поширокиот затоа што е полесно да се поврзе е токму начинот на кој апликациите го губат пристапот до локалната мрежа и поделениот DNS без да забележат. Започнете од она што мора да остане надвор од тунелот, потоа изберете го најмалиот опсег што го постигнува тоа.
Што отстранува protect() од тунелот
VpnService.protect() е повик за поединечен сокет. Му го предавате сокетот или дескрипторот што го носи тунелот, и Android го означува тој сокет така што неговиот сообраќај ја користи долната мрежа наместо интерфејсот на VPN. Сето друго што апликацијата го испраќа уште ја следи нормалната VPN рута.
Токму овој тесен опсег е поентата. Заштитениот сокет не менува како вашиот HTTP клиент ги решава имињата, кој интерфејс го користат вашите аналитички повици или што се случува со втора врска отворена подоцна.
Три услови што го одредуваат опсегот
Првиот услов е дали само транспортот на тунелот треба да излезе. Ако останатиот сообраќај на апликацијата треба да продолжи да оди преку VPN, заштитата на сокетот е правилниот опсег. Вториот услов е дали вие го контролирате создавањето на сокетот. Библиотека што отвора свој сокет длабоко внатре не ви остава место да повикате protect(), освен ако не изложува hook или не прифаќа готов сокет. Третиот услов е дали апликацијата мора директно да дојде до локалната мрежа или до captive portal, што е различен услов од одржувањето на тунелот жив.
Исклучувањето на апликацијата со 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 и дека транспортот повторно се поврзува штом согласноста повторно се даде.
Ако заштитеното барање сепак го следи тунелот, повикот за заштита најверојатно се случува откако сокетот веќе е рутиран. Преместете го порано и тестирајте повторно.
Држете ја одлуката запишана до поставувањето на транспортот: кои сокети се заштитени, кој сообраќај на апликацијата е исклучен и што прави апликацијата кога заштитата не успева. Токму таа линија спречува подоцнежно преуредување да го прошири исклучувањето на целата апликација затоа што еден тест паѓал. Таа му дава и на следниот човек начин да ја провери намерата без обратно инженерство на манифестот.