Проверката што повеќето Android апликации ја користат за откривање VPN е име на интерфејс, и таа никогаш не била проверка за VPN. Таа известува дека постои некој интерфејс со низата што развивачот ја очекувал, што на еден уред значи OpenVPN, а на друг значи нешто сосема неповрзано. ping е ист вид доказ: докажува дека едно одредиште одговорило, а не дека тунел го пренел пакетот. Она што платформата всушност го изложува е множество мрежи и транспортите што секоја од нив ги декларира, и тоа е друго прашање со друг одговор.

Мит: ако постои tun0, VPN е вклучен

Android VPN апликациите се имплементации на VpnService што на системот му предаваат дескриптор на датотека tun. Името на интерфејсот доаѓа од тој што го создал: OpenVPN обично создава tun0, PPTP и L2TP создаваат ppp0, WireGuard го именува интерфејсот по тунелот, а IPsec тунелот може воопшто да нема конвенционално име. Затоа читањето на /proc/net/dev или набројувањето на објекти NetworkInterface бара конвенција, а не факт.

Двете насоки на грешка се живи. Уредот може да има tun0 за локален тунел што нема никаква врска со VPN-от на корисникот, а сосема здрав WireGuard тунел може да биде невидлив за проверка што ја знае само низата tun0. Лажно позитивните и лажно негативните резултати во истата функција ја прават неупотреблива како дијагностика, бидејќи на тикет за поддршка што вели „не е откриен VPN“ не може да му се верува во ниту една насока.

Пристапот исто така се стесни. Поновите верзии на Android ограничуваат што може обична апликација да чита од /proc, па проверка заснована на име што работела на Android 8 може да врати празна листа на подоцнежен уред без никаква промена на кодот. Кога проверката тивко не успева кон отворената страна, апликацијата заклучува дека нема VPN на хардвер каде тунелот е во ред.

Мит: успешен ping значи дека тунелот пренесува сообраќај

ping кон внатрешен хост успева секогаш кога постои рута кон тој хост. Со поделен тунел, хостот може да биде достапен преку Wi-Fi додека тунелот е вклучен и прави нешто сосема друго. Со VPN по апликација, апликацијата што го прави ping може да биде исклучена од тунелот, па ICMP ехото излегува надвор од него и се враќа, што го докажува спротивното од она што проверката го претпоставува.

ICMP и TCP исто така се филтрираат одделно. Тунел што дозволува барања за ехо, но го отфрла TCP портот што му е потребен на услугата, ќе го помине ping тестот и ќе не успее во апликацијата, а тоа е правило на огнената брана од другата страна, а не својство на уредот. Единственото чесно читање на успешен ping е дека ова одредиште одговорило на овој протокол во овој момент.

Што всушност известува платформата

Од API 21, ConnectivityManager.getAllNetworks() враќа секоја мрежа што системот ја знае, а getNetworkCapabilities(network) известува што декларира таа мрежа. VPN мрежата носи TRANSPORT_VPN. Поместувањето низ сите мрежи одговара на едно прашање: дали во моментов е вклучен некој VPN на овој уред?

Второто, потесно прашање бара друг повик. getActiveNetwork() враќа мрежата што сообраќајот на апликацијата би ја користел сега, а проверката на истото својство на неа одговара: дали мојот сообраќај би поминал низ VPN? Двете може да не се совпаѓаат, и тоа несогласување е информативно. VPN што е вклучен но не е тековен укажува на рутирање по апликација или на конфигурација на поделен тунел, а не на тунел што недостасува.

Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean viaVpn = caps != null
    && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);

Заштитете ги двата повика. Својствата може да бидат null за мрежа што исчезнала помеѓу набројувањето и прашањето, а активната мрежа може да биде null за време на предавање. NET_CAPABILITY_NOT_VPN е корисен во спротивната насока, за да потврдите дека мрежата дефинитивно не е тунел, наместо само да ѝ недостига знамето за VPN транспорт.

NetworkInfo беше истиот мит со API

ConnectivityManager.TYPE_VPN со NetworkInfo е втората генерација на оваа проверка, и таа е застарена со причина. На современите верзии објектот што се враќа за VPN може да биде null додека тунелот е поврзан, па кодот што null го третира како „нема VPN“ тивко известува лажно негативен резултат на уредите каде одговорот е најважен.

Ниту емитувањето што обично го придружува не е подобро. CONNECTIVITY_CHANGE има документирана историја на пропуштени VPN премини, вклучително и градби каде што се активирало само кога VPN се поврзувал или исклучувал, а не при секоја промена на мрежата. Слушател изграден на него создава променлива на состојба што се оддалечува од гледиштето на платформата до следното целосно повторно поврзување.

Реагирајте на повратни повици наместо да анкетирате

Регистрирајте NetworkRequest што бара TRANSPORT_VPN и го отстранува NET_CAPABILITY_NOT_VPN, потоа повикајте registerNetworkCallback со тоа барање. Тогаш onAvailable и onLost ви кажуваат кога се менува декларацијата, и тоа е настанот што навистина ве интересира. Откажете ја регистрацијата на повратниот повик кога компонентата запира, инаку апликацијата држи референца на мртов екран и повратниот повик продолжува да се активира.

NetworkRequest req = new NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();
cm.registerNetworkCallback(req, callback);

Ова бара ACCESS_NETWORK_STATE, што е нормална дозвола: без root, без дијалог за согласност на корисникот, без VPN дозвола. Анкетирањето со тајмер е алтернативата кон која луѓето прибегнуваат, и тоа троши батерија додека сè уште пропушта премин пократок од интервалот, а тоа е токму преминот што корисникот го пријавува.

Што одговорот сè уште не може да ви каже

Ниту еден од овие API не известува дали тунелот е здрав, на кој сервер завршува или дали сообраќајот на одредена апликација е внатре во него. VPN мрежа може да постои без употребливи рути, а тунел по апликација може да ја исклучи апликацијата што го поставува прашањето.

Затоа запишете што виде проверката, не само буловата вредност. Запишете го идентификаторот на мрежата, декларираните транспорти и дали таа мрежа била активна. Извештај што вели „не е откриен VPN“ е неупотреблив, додека оној што вели дека мрежа декларирала TRANSPORT_VPN но getActiveNetwork() вратил Wi-Fi укажува директно на рутирање или на список за исклучување, и тоа е поправка на која некој може да работи.

Поврзани статии