Заштитата на 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 или веќе затворен значи дека интерфејсот никогаш не бил создаден, дури и ако не се појавила исклучок. Споредете го името на интерфејсот од 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. Избира излез за тој процес, што е корисно за вашиот транспорт и бескорисно за работа со два тунела одеднаш.
Поправете го уредот на корисникот, а не патеката на кодот
Нема API што дозволува две апликации да го делат местото за VPN, затоа поправката и припаѓа на состојбата на уредот. Понудете му на корисникот два конкретни патишта: исклучете или избришете секогаш вклучено за другата апликација за да може вашиот prepare да преземе, или отворете ги поставките по апликација на таа апликација и исклучете ја вашата апликација од нејзиниот тунел. Исклучувањето го одржува филтерот активен за сите други и и дозволува на вашата апликација да ја користи обичната врска, што често е резултатот што корисникот навистина го сака.
Запишете ги двата патишта во вашата документација за поддршка, бидејќи корисниците ретко знаат која апликација го поседува VPN. Побарајте од нив да го испратат името на активниот VPN од Поставките и резултатите од prepare, establish и protect од вашите дневници, и за една минута можете да кажете кој чекор не успеал. Потоа проверете со вистински сообраќај: барање име, TCP врска и едно барање низ тунелот, не само поврзана икона. Валиден дескриптор и зелениот индикатор докажуваат сопственост во најдобар случај, но никогаш дека транспортот ги преживеал правилата за рутирање.
Ако сите четири проверки поминат, а сообраќајот и понатаму умира, сопственоста веќе не е ваш проблем. Преминете на рутирање и достапност на серверот, и тестирајте ги истите акредитиви од уред без инсталирана VPN апликација.