Android VPN: VpnService дескриптор наспроти root tun уред
Android апликациите не можат сами да создадат tun0: што враќа establish(), каде запира Binder и каде почнува JNI, и кога победува уред создаден од root.
Две патеки можат да постават IP интерфејс пред Android апликација, и двете завршуваат на ист вид датотечен дескриптор со многу различни сопственици. Едната оди преку VpnService и неговиот builder, другата преку root shell и /dev/net/tun. Секое читање на тој дескриптор е излезен пакет, а секое пишување е влезен. Она што ги дели патеките е кој ја држи политиката, кој преживува надградба на ОС и колку од табелата за рутирање смееш да допреш.
Избери ја патеката на платформата освен ако не можеш да ја именуваш конкретната работа што таа одбива да ја направи. Тоа одбивање обично е TAP, фаќање надвор од твоето множество апликации или правило за рутирање што рамката никогаш не го открива.
На патеката на платформата декларираш намера наместо команди. Даваш барем една адреса, саканите рути, 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, а некаде под тоа платформата повикува нативен код за да го конфигурира уредот. Тоа се два различни механизми наредени во еден повик. Binder е меѓупроцесна комуникација помеѓу твојата апликација и системска служба, додека JNI е мостот од Java кон нативен код внатре во процес. Читањето на AOSP дрво за Android 9 го прави стекот буквален, и вреди да се запомни дека внатрешните имиња на класи и патеките на датотеки се менуваат меѓу изданијата.
Она што не се менува е договорот на кој смееш да се потпреш: builder-от, дескрипторот што го враќа и животниот циклус на службата околу него. Ниту една апликација не треба да повикува внатрешна трансакција на служба или повторно да создава јазол на уред за да добие интерфејс. Ако твојот код зависи од име на класа од AOSP, ќе се скрши на издание што не си го тестирал.
TUN носи IP, TAP носи Ethernet
TUN е IP интерфејс. TAP е Ethernet интерфејс, и таа разлика одлучува дали премостувањето е воопшто можно. TUN уредот нема MAC адреса и нема Ethernet заглавие за проследување, па TUN клиентот не може да се приклучи на мост од слој 2, како и да се напишани рутите.
VpnService ти ја дава IP варијантата, која е она што му треба на VPN на ниво на апликација и што може да го поддржи без суров Ethernet стек. Ако твојот дизајн бара премостување, патеката на платформата нема да те однесе таму, а root патеката ќе те однесе само ако кернелот и дозволите на уредот соработуваат.
Каде root патеката сѐ уште победува
Три работи ги туркаат луѓето од патеката на платформата. Премостувањето на слој 2 бара TAP. Фаќањето сообраќај од апликации надвор од твојот VPN опсег бара видливост што рамката не ја дава. Рутирањето по политика што builder-от никогаш не го открива, како посебна табела за една дестинација, бара правила што сам ги инсталираш.
Трошоците доаѓаат подоцна: отклучен bootloader или root-иран имиџ, SELinux политика што се поместува под тебе и уред што престанува да се однесува правилно по OTA. Root-иран рецепт исто така не може да се испорача преку нормалната дистрибуција на апликации, па повеќето тимови го држат како внатрешна дијагностичка патека, а не како производ.
Избор помеѓу двете
Почни од интерфејсот што ти треба, а не од кодот што го имаш. Ако одговорот е TUN уред со рути ограничени на апликацијата, патеката на платформата е пократка и преживува надградби. Ако одговорот вклучува Ethernet рамки, фаќање на други апликации или правила за рутирање што builder-от ги одбива, ти си на root територија и треба да го предвидиш како оперативен трошок.
Проверка што ги решава повеќето расправии: прво изгради ја патеката на платформата, запиши го исходот на establish() одделно од состојбата на далечинската сесија и потврди еден пакет во секој правец. Само тогаш одлучи дали недостапната можност го оправдува напуштањето на поддржаната патека.