Қолданбаңыз VpnService.prepare шақырады, null алады, туннель құрады, дескриптор жарамды болып көрінеді, бірақ серверге ештеңе жетпейді. Немесе prepare Intent қайтарады, пайдаланушы оны мақұлдайды, ал establish әлі де сәтсіз аяқталады. Екі нәтиже де әдетте бір мағынаны білдіреді: басқа қолданба бұл пайдаланушының VPN интерфейсін әлдеқашан иемденген, ал сіздің protect шақыруларыңыз туннеліңіз пайдалана алмайтын сокеттерді белгілеп жатыр. Төмендегі тексерулерді ретімен орындап, бірінші сәтсіз болған жерде тоқтаңыз, себебі кейінгі қадам бұрынғыны түзете алмайды.

protect-қа тимес бұрын VPN орнын тексеріңіз

Android әр пайдаланушыға дәл бір белсенді VPN интерфейсін береді. Бұл платформа ережесі, қолданбаңыз сұрай алатын рұқсат емес, сондықтан екеуі де туннель болғысы келетін екі клиент қатар өмір сүре алмайды.

Басқа қолданба үшін орнатылған үнемі қосулы VPN ең қиын жағдай, өйткені жүйе сол қолданбаны қосылып тұрған күйде ұстайды және басып алудан бас тартады. AdGuard сияқты трафик сүзгісі, корпоративтік клиент немесе тұтынушылық VPN пайдаланушы оның орнатылғанын ұмытып кеткен кезде орынды ұстап тұра алады. Пайдаланушыдан параметрлерді ашып, VPN астында көрсетілген жазбаны оқуды сұраңыз; ондағы атау — сіз бәсекелесетін иесі. Сол жазба ажыратылмайынша немесе үнемі қосулы параметрі жойылмайынша, сіздің VpnService интерфейс бола алмайды.

Қадам 1: prepare не қайтаратынын оқыңыз

VpnService.prepare бір сұраққа жауап береді: бұл қолданба VPN бола ала ма? Келісім әлдеқашан болса, ол null қайтарады, ал пайдаланушы оны әлі мақұлдауы керек болса, Intent қайтарады, оны startActivityForResult арқылы іске қосу керек. null қайтаруын жалғастыруға рұқсат деп қабылдаңыз, establish сәтті болады деген дәлел ретінде емес.

Intent intent = VpnService.prepare(context);
if (intent != null) {
    startActivityForResult(intent, REQUEST_VPN);
}

Егер Intent жолы қайтып, пайдаланушы бас тартса, prepare келесі әрекетте қайтадан Intent қайтара береді. Циклде қайта көру орнына нәтиже кодын жазып қойыңыз, өйткені бас тартылған келісім — пайдаланушының шешімі, өтпелі қате емес.

Қадам 2: интерфейс сіздің қолданбаңызға тиесілі екенін растаңыз

establish-тен кейін алынған ParcelFileDescriptor — сіздің меншік дәлеліңіз, және кез келген сокет жұмысынан бұрын оны тексерген жөн. null немесе әлдеқашан жабық дескриптор интерфейстің ешқашан жасалмағанын білдіреді, тіпті ешқандай ерекшелік шықпаса да. Builder-дің интерфейс атауын жүйенің белсенді VPN үшін хабарлайтын атауымен салыстырыңыз, сынақ құрылғысында dumpsys пайдаланып. Егер олар өзгеше болса, сіз құрған туннель трафикті тасымалдайтын туннель емес.

adb shell dumpsys connectivity

Сол сәйкессіздік қосылған белгішені өлі трафикпен түсіндіреді. Жүйе басқа қолданбаның туннелін көрсетеді, ал сіздің кодыңыз өзіне тиесілі емес интерфейстің дескрипторын ұстап тұр.

Қадам 3: protect-ты connect алдында шақырыңыз

Интерфейс пайда болғаннан кейін қолданбаңыз ашатын сокеттер әдепкі бойынша туннельге бағытталады. Сыртқы тасымалдау бұл ережені ұстана алмайды, әйтпесе инкапсуляцияланған пакет интерфейске қайта оралар еді, сондықтан VpnService protect сокетті VPN-ді айналып өтіп, астыңғы желіні пайдалану үшін белгілейді. Шақыру boolean қайтарады, оны елемеу — дені сау көрінетін, бірақ ештеңе тасымалдамайтын сокетке жетудің жолы.

protect-ты connect немесе bind алдында шақырыңыз, ешқашан кейін емес. Қорғаныс сокет қосылғанға немесе байланғанға дейін орнында болуы керек, өйткені қосылған сокет өз маршрутын әлдеқашан таңдаған және кейінгі protect оны сенімді түрде жылжытпайды. Егер protect false қайтарса, тоқтап, меншікті қайта тексеріңіз: ол интерфейс сіздің қолданбаңызға тиесілі болмағанда сәтсіз болады, бұл дәл басқа VPN қалдыратын күй. false қайтару — Қадам 1-ге оралу белгісі, сол сокетті қайта көру емес.

DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
    return;
}
socket.connect(serverAddress, serverPort);

Қадам 4: сокет қолайсыз болғанда процесті байланыстырыңыз

Кейбір тасымалдаулар бір сокет емес: кітапхана бірнешеуін ашуы мүмкін, немесе көмекші процестегі әр қосылым бір шығысты пайдалануын қалауыңыз мүмкін. ConnectivityManager.bindProcessToNetwork процесті таңдалған желіге байланыстырады, ал ескі setProcessDefaultNetwork ескіргенге дейін дәл сол істі атқарды. Астыңғы қосылымды NET_CAPABILITY_NOT_VPN арқылы сұраңыз, байланыстыру туннель астындағы желіні таңдасын, туннельдің өзін емес.

NetworkRequest request = new NetworkRequest.Builder()
    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();

Процесті байланыстыру екінші VPN емес. Ол сол процесс үшін шығысты таңдайды, бұл сіздің тасымалдауыңызға пайдалы және бір уақытта екі туннель жүргізуге пайдасыз.

Пайдаланушының құрылғысын түзетіңіз, код жолын емес

Екі қолданбаға VPN орнын бөлісуге рұқсат беретін API жоқ, сондықтан түзету құрылғы күйіне қатысты. Пайдаланушыға екі нақты жол ұсыныңыз: басқа қолданбаның қосылымын үзіңіз немесе үнемі қосулы параметрін жойыңыз, сонда prepare қабылдай алады, немесе сол қолданбаның қолданба бойынша параметрлерін ашып, өз қолданбаңызды оның туннелінен шығарыңыз. Шығару сүзгіні қалған барлық нәрсе үшін жұмыс істеп тұрғызады және қолданбаңызға қарапайым қосылымды пайдалануға мүмкіндік береді, бұл көбіне пайдаланушы шынымен қалайтын нәтиже.

Екі жолды да қолдау құжаттамаңызға жазыңыз, өйткені пайдаланушылар қай қолданба VPN-ге ие екенін сирек біледі. Олардан параметрлерден белсенді VPN атауын және журналдарыңыздан prepare, establish және protect нәтижелерін жіберуді сұраңыз, сонда бір минут ішінде қай қадам сәтсіз болғанын айта аласыз. Содан кейін нақты трафикпен растаңыз: атау іздеу, TCP қосылымы және туннель арқылы бір сұраныс, тек қосылған белгіше емес. Жарамды дескриптор мен жасыл индикатор ең жақсы жағдайда меншікті дәлелдейді, ешқашан тасымалдаудың маршруттау ережелерінен өткенін емес.

Егер төрт тексерудің бәрі өтсе де, трафик әлі де өлсе, меншік енді сіздің мәселеңіз емес. Маршруттау мен серверге қолжетімділікке көшіңіз және сол тіркелгі деректерін VPN қолданбасы орнатылмаған құрылғыдан тексеріңіз.

Қатысты мақалалар