Ստուգումը, որն օգտագործում են 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, ուղղակիորեն մատնանշում է երթուղավորումը կամ բացառումների ցանկը, և դա ուղղում է, որի վրա ինչ-որ մեկը կարող է աշխատել։

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