Два пътя могат да поставят IP интерфейс пред Android приложение и двата завършват при един и същ вид файлов дескриптор, но с много различни собственици. Единият минава през VpnService и неговия builder, другият през root shell и /dev/net/tun. Всяко четене от този дескриптор е изходящ пакет, а всяко записване — входящ. Това, което разделя пътищата, е кой държи политиката, кой оцелява при ъпгрейд на ОС и колко от таблицата за маршрутизация можеш да пипаш.

Избери пътя на платформата, освен ако не можеш да назовеш конкретното нещо, което тя отказва да направи. Този отказ обикновено е TAP, прихващане извън твоя набор приложения или правило за маршрутизация, което framework-ът никога не разкрива.

По пътя на платформата декларираш намерение вместо команди. Подаваш поне един адрес, маршрутите, които искаш, DNS сървъри и незадължителен списък с приложения, след което системната услуга на Android извършва привилегированата настройка под собствена идентичност. Никога не научаваш името на интерфейса предварително и никога не издаваш ioctl, който го създава — точно това е смисълът.

Два начина да получиш IP интерфейс

Пътят root е по-старата рецепта. Отваряш /dev/net/tun, извикваш TUNSETIFF с IFF_TUN, вдигаш интерфейса с ip link и добавяш адреси и маршрути на ръка. Всяка стъпка трябва да я направиш правилно сам, включително стъпките, които платформата би свършила мълчаливо. Един липсващ маршрут дава на потребителя същия доклад като мъртъв сървър.

Отстраняването на грешки се различава точно толкова, колкото и настройката. По пътя root интерфейсът или съществува, или ioctl ти е върнал грешка, която четеш директно. По пътя на платформата интерфейсът съществува само след успешен establish(), затова провалът идва като изключение или null дескриптор, а не като възел на устройство, в който да ровичкаш.

Какво всъщност връща establish()

Builder.establish() има три изхода и те изискват три различни реакции. Дескриптор означава, че интерфейсът е конфигуриран и е твой за ползване, null означава, че заявката не е минала, а хвърлено изключение — че системната услуга я е отхвърлила. Събирането на null и успех в един boolean е най-честият начин приложение да съобщи за работещ тунел, който никога не пренася пакет. Справката за Builder посочва изискването за адрес и си струва да се прочете, преди да обвиниш сървъра.

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

Binder е една стъпка, JNI е друга ос

Заявката на builder преминава в системата през Binder и някъде под това платформата извиква native код, за да конфигурира устройството. Това са два различни механизма, струпани в едно извикване. Binder е междупроцесна комуникация между приложението ти и системна услуга, докато JNI е мостът от Java към native код в рамките на процеса. Четенето на AOSP дърво за Android 9 прави стека да изглежда буквален и си струва да помниш, че вътрешните имена на класове и пътищата на файловете се менят между версиите.

Това, което не се мени, е договорът, на който можеш да разчиташ: builder-ът, дескрипторът, който връща, и жизненият цикъл на услугата около него. Никое приложение не бива да вика вътрешна услуга-транзакция или да пресъздава възел на устройство, за да получи интерфейс. Ако кодът ти зависи от име на клас от AOSP, той ще се счупи при версия, която не си тествал.

TUN носи IP, TAP носи Ethernet

TUN е IP интерфейс. TAP е Ethernet интерфейс и тази разлика решава дали мостовото свързване е възможно изобщо. Устройство TUN няма MAC адрес и няма Ethernet заглавка за препращане, затова TUN клиент не може да се включи в layer-2 мост, както и да са написани маршрутите.

VpnService ти дава IP варианта, който е нужен на VPN на ниво приложение и който може да се поддържа без суров Ethernet стек. Ако дизайнът ти изисква мост, пътят на платформата няма да те отведе там, а пътят root — само ако ядрото и разрешенията на устройството си съдействат.

Къде пътят root все още печели

Три задачи избутват хората от пътя на платформата. Мостово свързване на layer-2 изисква TAP. Прихващането на трафик от приложения извън обхвата на твоя VPN изисква видимост, която платформата не дава. Политическото маршрутизиране, което builder-ът никога не разкрива, например отделна таблица за една дестинация, изисква правила, които инсталираш сам.

Разходите идват по-късно: отключен bootloader или root-нат образ, SELinux политика, която се променя под теб, и устройство, което спира да се държи правилно след OTA. Root рецепта също не може да се разпространява през нормалното разпространение на приложения, затова повечето екипи я пазят като вътрешен диагностичен път, а не като продукт.

Избор между двете

Започни от интерфейса, който ти трябва, а не от кода, който имаш. Ако отговорът е TUN устройство с маршрути в обхвата на приложенията, пътят на платформата е по-кратък и оцелява при ъпгрейди. Ако отговорът включва Ethernet кадри, прихващане на други приложения или правила за маршрутизация, които builder-ът отхвърля, ти си на територия root и трябва да предвидиш бюджет за това като оперативен разход.

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

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