Нищо във VpnService не прихваща трафик само по себе си. Той иска от системата tun интерфейс, дава на този интерфейс набор от адреси и маршрути и от този момент ядрото копира всеки пакет, съответстващ на тези маршрути, във файлов дескриптор, който приложението може да чете. Кодът на приложението никога не вижда пакетите, които маршрутите не са избрали, така че обхватът на тунела е въпрос на маршрутна таблица преди да е въпрос на програмиране. Затова една и съща реализация на услугата може да погълне всеки поток на един телефон и само част на друг и затова добавянето на маршрут понякога поправя тунел, който изглеждаше счупен. Това, което получавате, е това, което сте декларирали, минус всичко, от което конструкторът тихо е отказал.

Маршрутите решават кои пакети пристигат

Builder.addRoute(prefix, prefixLength) инсталира префикс в маршрутната таблица на VPN мрежата. Системата третира тази мрежа като подразбираща се за приложенията в обхвата, затова записите ѝ се проверяват преди подразбиращия се маршрут на физическата мрежа и съвпадащ префикс е достатъчен, за да отклони поток. Затова маршрут за 0.0.0.0/0 поема IPv4 изцяло, включително принтера, cast целта и администраторската страница на рутера.

Клиентите, които искат това покритие без да изтриват физическия подразбиращ се запис, използват вместо това двойката 0.0.0.0/1 и 128.0.0.0/1. Два по-специфични маршрута побеждават подразбиращ се маршрут, заедно те обхващат цялото адресно пространство, а собственият подразбиращ се запис на телефона остава в таблицата за сокетите, които все още са вързани към физическата мрежа.

Адресите и DNS записите идват от същия конструктор и отговарят на друг въпрос. addAddress дава на интерфейса адрес с дължина на префикса, а addDnsServer инсталира резолвер, който приложенията в обхвата ще питат през интерфейса. И двете семейства могат да стоят на един интерфейс, така че набор от маршрути само за IPv4 до адрес IPv6 е конфигурация, която се чете правилно и все пак изтича. Ако маршрутизирате семейство, в което интерфейсът няма адрес, ядрото няма адрес на източника за тези пакети и те никога не се появяват при дескриптора.

Какво връща establish()

establish() връща ParcelFileDescriptor или null, когато приложението не е оторизирано. Null е състояние на съгласие, а не мрежова грешка: VpnService.prepare() трябва вече да е върнал null или потребителят трябва да е одобрил системния диалог преди извикването. Съгласието може да бъде оттеглено, докато услугата работи, и интерфейсът се затваря под вас без обратно извикване във вашия цикъл за четене.

Дескрипторът е неблокиращ, освен ако не кажете друго, така че четенето се връща веднага без данни, вместо да паркира нишката. Цикъл, написан за блокиращ сокет, изгаря ядро, а цикъл, който приема празно четене за фатално, разкъсва тунела за една празна секунда. Builder.setBlocking(true) променя това поведение и цената е нишка, която чака вътре в четенето.

Аргументите за маршрут и адрес се валидират като стойности LinkAddress, а multicast дестинации като 224.0.0.0/4 се отхвърлят направо. Интерфейс tun точка-до-точка също няма broadcast домейн, така че multicast откриването през него изисква различен дизайн, а не още един маршрут. Справочникът VpnService.Builder е списъкът, който да проверите, преди да заключите, че извикване е било игнорирано.

Обхватът по приложения стеснява набора, не тунела

От API 21 конструкторът приема addAllowedApplication и addDisallowedApplication и двата списъка са взаимно изключващи се в един конструктор. Списъкът за разрешаване прави тунела съществуващ за тези пакети, докато всичко останало запазва физическата мрежа; списъкът за забрана улавя всичко освен посочените пакети, което клиентът предлага като разделно тунелиране. Преди API 21 няма платформен механизъм изобщо и поведението по приложения идваше от клиенти на трети страни, филтриращи по UID вътре в собствения си препращач. Разликата има значение при отстраняване на грешки, защото тунел по приложения със списък за разрешаване от едно приложение произвежда почти никакъв трафик и това е правилно поведение, не счупен маршрут.

Какъвто и да е обхватът, сокетът, който приложението ви отваря към VPN сървъра, не трябва да минава през тунела, който изгражда. VpnService.protect(socket) връзва този сокет към подлежащата мрежа, така че пакетите му да прескачат tun устройството. Пропуснете извикването и ръкостискането се поглъща от интерфейса, който е трябвало да го вдигне.

VpnService.setUnderlyingNetworks затваря цикъла, като казва на системата коя физическа мрежа носи тунела. Тази стойност определя как VPN съобщава свързаност на други приложения и null или остаряла стойност е обичайна причина за свързан тунел, който останалата част от телефона смята за офлайн. Актуализирайте я при промяна на подлежащата мрежа, а не само при стартиране.

Контролни суми и MTU: където правилен маршрут пак се проваля

След като пакетите пристигнат, две подробности на ниво пакет решават дали нещо работи от край до край. Първата е адресът на източника: при няколко адреса на интерфейса ядрото може да излъчва пакети с първия, затова препращач, който пренаписва заглавки, трябва да поправи адреса и контролната сума, която го покрива.

Втората е обхватът на контролната сума. Когато приложението само изгражда TCP или UDP пакети, контролната сума покрива псевдо-заглавка с адреса на източника, адреса на дестинацията, номера на протокола и дължината на транспорта, и пропускането на което и да е от тях дава пакети, които пристигат и се изхвърлят. Този отказ изглежда като изтичане на времето след отговор, който никога не идва, което връща повечето хора обратно към маршрутите. setMtu фиксира MTU на интерфейса и всяко четене връща точно един пълен IP пакет, така че буфер, писан за байтов поток, ще фрагментира вашия анализ, а не пакета.

Проверка, която разделя слоевете

За да намерите къде обхватът е сгрешил, тествайте слоевете по ред. Попитайте устройството кои маршрути всъщност държи VPN мрежата, после проверете дали поток към известен адрес се появява като четене при дескриптора, после проверете дали пакетът, който вашият препращач излъчва, е добре оформен. Проблем с маршрут не показва нищо при дескриптора, докато проблем с контролната сума показва пакет, който пристига и изчезва.

Ако нищо не достига дескриптора, заподозряният е наборът от маршрути, а не четящият: потвърдете префиксите, потвърдете, че има адрес в същото семейство, и потвърдете, че приложението е в зададения от вас обхват. Ако пакетите достигат дескриптора, но нищо не се връща, инспектирайте protect() и стойността на подлежащата мрежа, преди да пипате маршрут отново. Ако далечната страна съобщава за повредени пакети, контролната сума и MTU са местата за гледане. Една проверка на слой спестява един следобед догадки.

Подобни статии