Збой аховы VpnService, калі іншы VPN ужо актыўны
Android дазваляе адзін інтэрфейс VPN на карыстальніка. Выканайце праверкі prepare, establish, protect і ўласнасці па парадку, затым выберыце адмену або выключэнне для праграмы.
Ваша праграма выклікае VpnService.prepare, атрымлівае null, будуе тунель, і дэскрыптар выглядае слушным, але нічога не даходзіць да сервера. Або prepare вяртае Intent, карыстальнік ухваляе яго, і establish усё роўна не ўдаецца. Абодва вынікі звычайна азначаюць адно і тое ж: іншая праграма ўжо валодае інтэрфейсам VPN для гэтага карыстальніка, а вашыя выклікі protect пазначаюць сокеты, якія ваш тунель не можа выкарыстоўваць. Прайдзіце праверкі ніжэй па парадку і спыніцеся на першай, якая не ўдалася, бо наступны крок не можа выправіць папярэдні.
Праверце месца VPN, перш чым чапаць protect
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 або ўжо зачынены, азначае, што інтэрфейс ніколі не ствараўся, нават калі выключэнне не ўзнікла. Параўнайце імя інтэрфейса з будаўніка з тым, якое сістэма паведамляе для актыўнага 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. Яно выбірае выхад для гэтага працэсу, што карысна для вашага транспарту і бескарысна для запуску двух тунеляў адначасова.
Выпраўце прыладу карыстальніка, а не шлях кода
Няма API, які дазваляе дзвюм праграмам дзяліць месца VPN, таму выпраўленне адносіцца да стану прылады. Прапануйце карыстальніку два канкрэтныя шляхі: адключыце або ачысціце пастаяннае ўключэнне для іншай праграмы, каб ваш prepare мог перахапіць, або адкрыйце налады іншай праграмы для асобных праграм і выключыце сваю праграму з яе тунеля. Выключэнне пакідае фільтр працуючым для ўсяго астатняга і дазваляе вашай праграме выкарыстоўваць звычайнае злучэнне, што часта і ёсць вынік, якога карыстальнік насамрэч хоча.
Запішыце абодва шляхі ў дакументацыю падтрымкі, бо карыстальнікі рэдка ведаюць, якая праграма валодае VPN. Папрасіце іх даслаць імя актыўнага VPN з Налад і вынікі prepare, establish і protect з вашых журналаў, і вы зможаце за хвіліну сказаць, які крок не ўдаўся. Затым праверце рэальным трафікам: пошук імя, TCP-злучэнне і адзін запыт праз тунель, а не толькі значок падлучэння. Слушны дэскрыптар і зялёны індыкатар даказваюць уласнасць у лепшым выпадку, але ніколі — што транспарт перажыў правілы маршрутызацыі.
Калі ўсе чатыры праверкі пройдзены, а трафік усё яшчэ памірае, уласнасць больш не ваша праблема. Пераходзьце да маршрутызацыі і даступнасці сервера, і праверце тыя ж уліковыя даныя з прылады без усталяванай праграмы VPN.