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);Три подробности хапят тук. Първо,boolean е единственият сигнал, който получавате, затова записвайте неуспеха с достатъчно контекст, за да различите оттеглено съгласие от отказано извикване. Второ,protect() покрива сокета, не номера на дескриптора: ако затворите сокета и по-късно използвате същото цяло число за друг сокет, новият сокет не е защитен. Трето, когато native библиотека отваря транспорта, защитете дескриптора преди native кодът да се свърже, иначе маршрутът може вече да е избран.Справката за VpnService е мястото, където да потвърдите кои претоварвания действително предоставя вашият целеви API уровен.
Тествайте блокирания път, не само щастливия
Проверете при вдигнат тунел, защото това е единственото състояние, в което двата пътя могат да се различат. Изпратете една заявка през защитения транспорт и една от код, който никога не е извиквал protect(), после сравнете какво вижда далечният край: защитената връзка трябва да пристигне с подлежащата мрежа като източник, а незащитената показва собствения адрес на тунела и движи байтовите му броячи. На устройството ss или netstat показва същото разделение, преди нещо да напусне радиото.
Тествайте оттеглянето отделно, защото промяната на съгласието събаря интерфейса, без да пипа вашия код за сокета. Оттеглете съгласието за VPN или спрете услугата, изградете транспорта наново и потвърдете, че protect() все още връща true и че транспортът се свързва отново, щом съгласието бъде дадено пак.
Ако защитената заявка все пак следва тунела, извикването на защита вероятно се случва, след като сокетът вече е маршрутизиран. Преместете го по-рано и тествайте отново.
Дръжте решението записано до настройката на транспорта: кои сокети са защитени, кой трафик на приложението е изключен и какво прави приложението, когато защитата се провали. Точно този ред пречи на по-късен рефакторинг да разшири изключването до цялото приложение, защото един тест се е провалял. Той също дава на следващия човек начин да провери намерението, без да реконструира манифеста.