Android VpnService туннеліңіз трафикті маршруттауды неге тоқтатады
Өндірісте бұзылатын VpnService туралы төрт сенім: бір уақытта бір туннель, маршрут ауқымы, қолданба бойынша тізімдер және өшіру тәртібі, әрқайсысына тексеру қоса.
Android VpnService туралы төрт болжам маған бірде аптаның көп бөлігін жұмсатты, және олардың ешқайсысы желілік протокол туралы емес еді. Қолданба қосылды деді, кілт белгішесі күй жолағында отырды, ал трафик әлі де әдеттегі маршрутпен шығып жатты. Мен туннельді өзім тоқтатқанша өзімдікі деп, қосқан маршруттарым алынған маршруттар деп, ал stopSelf шақыруы интерфейсті өзімен бірге түсіреді деп ойлай бердім. Қызмет дәл құжатталғандай әрекет етті. Бұзылған бөлік менің ой үлгім еді.
Аңыз 1: туннель оны тоқтатқанша тұрады
Android бір уақытта бір VPN интерфейсін ұстайды. Екіншісін орнатыңыз, және платформа оны кезекке қоймайды: сіздікін өшіреді және қызметіңізде onRevoke() шақырады. Егер кодыңыз тек түйме басылғанда бәрін бұзатын болса, басып алу артында дескриптор мен оқу ағынын қалдырады, келесі іске қосу екеуін де мұра етеді.
VpnService анықтамасы prepare() қолданбада келісім болса null қайтаратынын, ал пайдаланушы әлі бекітуі керек болса Intent қайтаратынын анық айтады. Бұл келісім ниетті startActivityForResult() арқылы жеткізсеңіз ғана жетеді. Жеткізуді өткізіп жіберсеңіз, establish() null қайтарады, бұл рұқсат мәселесі емес, үнсіз сәтсіздік ретінде оқылады.
Always-on VPN сіз басқармайтын иесін қосады. Жүйе туннельді жүктеуден кейін немесе құлаудан кейін қайтаруы мүмкін, және ол пайдаланушы қолмен іске қосқан VPN қолданбасынан озып кетеді. onRevoke()-ді әдеттегі өшіруіңіз сияқты ресурстарды жабатындай етіп құрыңыз және оны шеткі жағдай емес, негізгі тоқтату жолы деп қарастырыңыз. Пайдаланушылар VPN қолданбаларын ауыстырады; кодыңыз бұған төзуі керек.
Аңыз 2: addRoute дәл тізімдегеніңізді ұстайды
Маршруттар қандай пакеттердің сіздің дескрипторыңызға жететінін шешеді, ал ауқым қатесі трафиктің бір бөлігі туннельден өтіп, бір бөлігі өтпейтін ашуландыратын жағдайды жасайды. addRoute("0.0.0.0", 0) бүкіл IPv4-ті алады, яғни енді әр пакет сізге келеді және оны әрі қарай жіберу немесе тастау керек. Бір хост үшін /32 ештеңе дерлік алмайды, ал қалғанының бәрі VPN ешқашан орнатылмағандай әрекет етеді.
Отбасылар бөлек саналады. Тек IPv4 маршруты IPv6-ны негізгі желіде қалдырады, сондықтан қос стекті хост туннельдің ішінде толық қосылған болып көрінгенімен, сыртта әлі де атауды шешіп, қосыла алады.
DNS сол логиканы ұстанады. addDnsServer() арқылы қосылған серверлер туннель шешушісін пайдаланатын қолданбаларға барады, ал өзінің шифрланған DNS соңғы нүктесі немесе хост атына қойылған Private DNS бар қолданба оларды мүлдем елемейді. Атаулар бір қолданба үшін сәтсіз болып, екіншісі үшін жұмыс істесе, бір маршрутқа да тимес бұрын шешуші жолын тексеріңіз.
Аңыз 3: қолданба бойынша ережелер екі жаққа да бұруға болатын ажыратқыш
addDisallowedApplication() мен addAllowedApplication() екі сүзгі емес, екі режим. Құрастырушы бір туннельге бір модель қабылдайды, сондықтан үш пакеттен басқаның бәрін қалайтын қолданба тыйым салынған тізімді пайдаланып, рұқсат етілген тізімді бос қалдырады. Екеуі бірін-бірі жоққа шығарады, және адамдар мұны параметрлер экраны екі ажыратқышты да ұсынғаннан кейін жұмыс уақытында біледі.
Пакет көрінуі де тістеп алады. Android 11 және одан кейінгі нұсқаларда тек көретін пакеттеріңізді атай аласыз, сондықтан қолданбаңызға орындауға рұқсат етілмеген сұраудан құрылған ерекшелік тізімі жазбаларды үнсіз өткізіп жібереді. Оған сенбес бұрын тізімді пакет менеджері шын мәнінде қайтаратынмен салыстырыңыз.
Жұмыс профильдері аудиторияны қайта бөледі. Профиль ішінде басталған туннель сол профильді және оның қолданбаларын қамтиды, ал жеке жақ өз байланысын сақтайды. Бұл бір рет қабылданатын ауқым шешімі, және оның орны интерфейсте, қолдау билетінде емес.
Аңыз 4: stopSelf туннельді аяқтайды
Жоқ. Интерфейс establish() қайтаратын дескрипторда өмір сүреді, ал қызмет нысаны сол дескриптор ашық тұрғанда өлуі мүмкін. Алдымен ParcelFileDescriptor-ді жабыңыз, сілтемені жіберіңіз, оқу және жазу ағындарын қосыңыз немесе үзіңіз, содан кейін stopForeground(true) мен stopSelf()-ді осы тәртіппен шақырыңыз.
Ұмытылған дескриптор қызметтің өзінің тоқтату сұранысынан аман қалғандай көрінуінің әдеттегі себебі, және ол өзін қосқан қолданбадан ұзақ өмір сүретін ескірген маршрутты түсіндіреді.
Кері қате де соншалықты жиі: оқу ағыны оқудың ортасында тұрғанда дескрипторды жабу, содан кейін сол ағынды ешкім өңдеу жазбаған ерекшелікпен айналысуға қалдыру. Өшіру ретін бір жерде ұстаңыз және пайдаланушы әрекеті де, onRevoke() де соған шақырсын.
Туннель өлі көрінгенде нені тексеру керек
Ұзақ өмір сүретін сокеттерді туннель көтерілмес бұрын protect()-мен қорғаңыз. Онсыз туннельдің өз тасымалы қайта туннельге бағытталып, қосылым дәл сервер ақауына ұқсайтын түрде тұрып қалады. Тек алыс желінің артында бар мекенжайға қарсы сынаңыз, кэштен немесе негізгі жолдан жауап беретін ашық сайтқа қарсы емес.
establish() нәтижесін, маршруттарды, ерекшелінген пакеттерді және өшіру себебін тіркеңіз. Сол төрт жол пакет ұстамай-ақ келісім мәселесін ауқым мәселесінен және өмірлік цикл мәселесінен ажыратады.