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 до нативног кода унутар процеса. Читање Android 9 AOSP стабла чини стек дословним, и вреди запамтити да се интерна имена класа и путање датотека померају између издања.
Оно што се не помера је уговор на који можеш да се ослониш: 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() одвојено од стања удаљене сесије и потврди један пакет у сваком смеру. Тек онда одлучи да ли недостајућа могућност оправдава напуштање подржаног пута.