Դուք չեք կարող հարցնել Android-ին՝ արդյոք VPN-ը միացած է։ VpnService-ը չի բացահայտում միացման getter, իսկ կարգավիճակի սանդղակի բանալու պատկերակը նկարում է համակարգը, ոչ թե ինչ-որ բան, որ ձեր հավելվածը կարող է կարդալ։ Ինչ կարող եք անել՝ միացվածության կույտին ավելի նեղ հարց տալն է՝ որ ցանցն է օգտագործում այս հավելվածի թրաֆիկը, և արդյոք այդ ցանցը կրում է VPN transport-ը։ Այս ուղեցույցն անցնում է ստուգումները կեղծ դրական արդյունքներից խուսափող հերթականությամբ՝ սարքի վրա shell հրամանից մինչև հավելվածում աշխատող callback-ը։

Ստուգեք սարքը shell-ից՝ նախքան հավելվածի կոդին դիպչելը

Նախ ստացեք հիմնային գիծ։ adb shell dumpsys connectivity-ը տպում է համակարգին հայտնի յուրաքանչյուր ցանց, և յուրաքանչյուր բլոկ թվարկում է իր transport-ները, այնպես որ VPN ցանցը հայտնվում է այդ ցանկում VPN-ով։ Կարդացեք լռելյայն ցանցի բլոկը ամեն ինչից առաջ, ապա փնտրեք նույն transport-ը կրող այլ ցանցեր։

Երկու ելք պատասխանում են տարբեր հարցերի։ VPN ցանցի գոյությունը նույնը չէ, ինչ ձեր հավելվածի կողմից դրա օգտագործումը, և աշխատանքային պրոֆիլով կամ երկրորդ օգտատիրոջով սարքում երկուսն էլ կարող են ճշմարիտ լինել միաժամանակ, մինչ ձեր պրոցեսը ուղղակիորեն խոսում է ինտերնետի հետ։ Ակտիվ ցանցի տողն է, որ որոշում է, թե ինչ են անում ձեր socket-ները։

Հարցրեք այն ցանցին, որն օգտագործում է հավելվածը

Կոդում հնարավորությունների թեստը չորս տող է։ Հայցեք ACCESS_NETWORK_STATE-ը manifest-ում, ապա համեմատեք ակտիվ ցանցի հնարավորությունները VPN transport-ի հետ։

ConnectivityManager cm = (ConnectivityManager)
    context.getSystemService(Context.CONNECTIVITY_SERVICE);
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean vpnInUse = caps != null
    && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);

Պատասխանը միտումնավոր նեղ է՝ արդյոք հավելվածի լռելյայն ցանցը հենց հիմա VPN է։ Այն ոչինչ չի ասում այն մասին, թե որ VPN-ն է, ձերն է արդյոք, կամ հասնում է արդյոք թունելը իր հեռավոր ծայրին։ Վերաբերվեք true-ին որպես «թրաֆիկը դուրս է գալիս ինչ-որ թունելով» և ձեր սեփական հաշվառումը պահեք առանձին։ Boolean-ը, որը լուռ նշանակում է երեք բան միաժամանակ, այն է, ինչի պատճառով կարգավորումների էկրանը ի վերջո հայտնում է VPN-ը որպես անջատված, մինչ օգտատերը զննում է դրա միջով։

Երբ փոխարենը պետք է իմանաք՝ արդյոք որևէ VPN ցանց գոյություն ունի, կրկնեք cm.getAllNetworks()-ի վրա և փորձարկեք յուրաքանչյուր հնարավորությունների հավաքածու։ Երկու պատասխանները տարբերվում են այն սարքում, որը գործարկում է յուրաքանչյուր հավելվածի VPN, որը ծածկում է այլ հավելվածներ, բայց ոչ ձերը, և այդ տարբերությունը սկզբնաղբյուրն է այն սխալի հաշվետվությունների մեծամասնության, որոնք ասում են, թե հավելվածը չի տեսնում ակնհայտորեն միացված VPN-ը։

Հետևեք փոփոխություններին՝ հարցումների փոխարեն

Ժամաչափով հարցումները սխալ ձև է այս հարցի համար, քանի որ վիճակի փոփոխությունները գալիս են որպես իրադարձություններ։ Գրանցեք հարցում, որը համապատասխանում է VPN transport-ին, և թողեք framework-ը ձեզ ասի, թե երբ համապատասխան ցանց է հայտնվում, անհետանում կամ փոխվում։

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

VPN ցանցը երբեք չի կրում NET_CAPABILITY_NOT_VPN, ուստի բացահայտ հեռացումը փաստագրում է մտադրությունը։ Առանց դրա հարցումը դեռ համապատասխանում է, բայց կոդը կարդացող հաջորդ մարդը պետք է ինքը դա հասկանա։

Գրանցումը պատկանում է այն շրջանակին, որին անհրաժեշտ է դիտարկումը՝ սովորաբար էկրանին կամ առաջին պլանի ծառայությանը։ Չեղարկեք գրանցումը onStop-ում կամ onDestroy-ում, և callback մի թողեք կապված արդեն գոյություն չունեցող ակտիվության հետ։ Տեսանելի բաղադրիչ չունեցող հավելվածը վատ տանտեր է մշտական դիտորդի համար, և ձեր մոռացած callback-ներն են, որ փլուզվում են կարգավորման փոփոխությունից հետո։

Երեք հարցերը պահեք առանձին

Այս ոլորտի խառնաշփոթի մեծ մասը գալիս է երեք տարբեր ստուգումներ որպես մեկը դիտարկելուց։ Նրանք օգտագործում են տարբեր API-ներ, և յուրաքանչյուրը կարող է ճշմարիտ լինել, մինչ մյուսները կեղծ են։

QuestionHow you answer itWhat a yes means
Did the user grant VPN consent to my app?VpnService.prepare() returns nullConsent exists; the tunnel may still be down
Is the device sending default traffic through a VPN?TRANSPORT_VPN on the active networkSomething is tunneling, not necessarily your app
Did this request arrive through the VPN?Source address seen by the serverProof for that connection, nothing wider

Երրորդ տողն այն է, ինչ մարդիկ փորձում են պատասխանել client-ում, և այնտեղ հնարավոր չէ պատասխանել։ Տվեք VPN client-ներին իրենց սեփական հասցեների լողավազանը և կարդացեք աղբյուրի հասցեն սերվերում. դա միակ ապացույցն է, որ դիմանում է, երբ օգտատերը կես նստաշրջանում փոխում է թունելը։

Ստուգման հերթականություն, որը բացառում է կեղծ դրական արդյունքները

Շրջանառության մեջ դեռ գտնվող հին օրինակելի կոդը կոչում է getNetworkInfo(ConnectivityManager.TYPE_VPN)։ Այդ կանչը հնացած է, և մի քանի թողարկումներում այն վերադարձնում էր null նույնիսկ միացված VPN-ի դեպքում, դրա համար էլ շատ պատճենված հատվածներ կոտրված են թվում աշխատող սարքում։ Հնարավորությունների ստուգումները խուսափում են ծուղակից և նույն կերպ են վարվում Android 5.0-ից սկսած։ Եթե պահպանում եք կոդային բազա, որը դեռ կանչում է այն, փոխարինեք կանչը փոխարենը այն fallback-ով փաթաթելու, քանի որ fallback-ն էր, որ սկզբում արտադրեց սխալ պատասխանը։

Մեկ կարճ ուղի արժանի է զգուշացման։ Արտաքին ծառայության հաղորդած հանրային հասցեն VPN-ի ելքի հասցեի հետ համեմատելը ինչ-որ բան ապացուցում է ուղու մասին, բայց ամեն ստուգման ավելացնում է երրորդ կողմի հարցում, կախված է այդ ծառայության աշխատունակությունից և կարող է քեշավորվել կամ արգելափակվել։ Պահեք այն որպես ձեռքով ախտորոշում, երբեք որպես հավելվածի ներսի վիճակի ստուգում։

Կատարեք ստուգումները այս հերթականությամբ և կանգ առեք առաջինի վրա, որը չի համընկնում օգտատիրոջ հաշվետվության հետ՝ transport-ների ցանկը shell-ում, ակտիվ ցանցի հնարավորությունները, callback իրադարձությունները, մինչ միացնում և անջատում եք թունելը, ապա աղբյուրի հասցեն սերվերի կողմում։ Եթե բոլոր չորսը համընկնում են, և հավելվածը դեռ սխալ է վարվում, սխալը ձեր սեփական վիճակի մեքենայում է, ոչ թե Android-ի ցանցի ընկալման մեջ։

Առնչվող հոդվածներ