Tətbiqiniz VpnService.prepare çağırır, null alır, tunel qurur və deskriptor etibarlı görünür, amma serverə heç nə çatmır. Ya da prepare Intent qaytarır, istifadəçi onu təsdiqləyir və establish yenə uğursuz olur. Hər iki nəticə adətən eyni şeyi bildirir: başqa tətbiq bu istifadəçi üçün VPN interfeysinə artıq sahibdir və protect çağırışlarınız tunelinizin istifadə edə bilmədiyi soketləri işarələyir. Aşağıdakı yoxlamaları ardıcıl keçin və uğursuz olan ilk yoxlamada dayanın, çünki sonrakı addım əvvəlkini bərpa edə bilməz.

protect-ə toxunmazdan əvvəl VPN yerini yoxlayın

Android hər istifadəçiyə yalnız bir aktiv VPN interfeysi. Bu platforma qaydasıdır, tətbiqinizin tələb edə biləcəyi icazə deyil, ona görə də hər ikisi tunel olmaq istəyən iki müştəri birgə mövcud ola bilməz.

Başqa tətbiq üçün qurulmuş həmişə aktiv VPN ən çətin haldır, çünki sistem həmin tətbiqi qoşulmuş saxlayır və ələ keçirməni rədd edir. AdGuard kimi trafik filtri, korporativ müştəri və ya istehlakçı VPN-i istifadəçi quraşdırıldığını unutmuş halda yeri tuta bilər. İstifadəçidən Tənzimləmələri açmasını və VPN altında göstərilən qeydi oxumasını xahiş edin; oradakı ad rəqabət etdiyiniz sahibdir. Həmin qeyd ayrılmadıqca və ya həmişə aktiv parametri silinmədikcə VpnService-iniz interfeys ola bilməz.

Addım 1: prepare-in nə qaytardığını oxuyun

VpnService.prepare tək bir suala cavab verir: bu tətbiq VPN ola bilərmi? Razılıq artıq varsa null, istifadəçi hələ təsdiqləməlidirsə Intent qaytarır və onu startActivityForResult ilə işə salmalısınız. null qaytarılmasını davam etmək icazəsi kimi qəbul edin, establish-in uğur qazanacağına sübut kimi deyil.

Intent intent = VpnService.prepare(context);
if (intent != null) {
    startActivityForResult(intent, REQUEST_VPN);
}

Intent yolu qayıdırsa və istifadəçi imtina edibsə, prepare növbəti cəhddə yenə Intent qaytaracaq. Döngüdə təkrar cəhd etmək əvəzinə nəticə kodunu qeyd edin, çünki rədd edilmiş razılıq istifadəçi qərarıdır, keçici xəta deyil.

Addım 2: interfeysin tətbiqinizə aid olduğunu təsdiqləyin

establish-dən sonra aldığınız ParcelFileDescriptor sahibliyinizin sübutudur və hər hansı soket işindən əvvəl onu yoxlamağa dəyər. null olan və ya artıq bağlanmış deskriptor interfeysin heç vaxt yaradılmadığını bildirir, heç bir istisna görünməsə belə. Builder-dən gələn interfeys adını sistemin aktiv VPN üçün bildirdiyi adla müqayisə edin, test cihazında dumpsys istifadə edin. Fərqlənirlərsə, qurduğunuz tunel trafiki daşıyan tunel deyil.

adb shell dumpsys connectivity

Eyni uyğunsuzluq qoşulmuş ikona ilə ölü trafiki izah edir. Sistem digər tətbiqin tunelini göstərir, kodunuz isə sahib olmadığı interfeys üçün deskriptor saxlayır.

Addım 3: connect-dən əvvəl protect çağırın

İnterfeys yarandıqdan sonra tətbiqinizin açdığı soketlər standart olaraq tunelə yönləndirilir. Xarici transport bu qaydaya əməl edə bilməz, əks halda kapsullanmış paket interfeysə geri dönərdi, buna görə VpnService protect bir soketi VPN-i keçərək əsas şəbəkədən istifadə etmək üçün işarələyir. Çağırış boolean qaytarır və onu nəzərə almamaq sağlam görünən, amma heç nə daşımayan soketlə nəticələnmənin yoludur.

protect-i connect və ya bind-dən əvvəl çağırın, heç vaxt sonra deyil. Qoruma soket qoşulmadan və ya bağlanmadan əvvəl mövcud olmalıdır, çünki qoşulmuş soket artıq marşrutunu seçib və sonrakı protect onu etibarlı şəkildə dəyişmir. protect false qaytarırsa, dayanın və sahibliyi yenidən yoxlayın: o, interfeys tətbiqinizə aid olmadıqda uğursuz olur, bu da başqa VPN-in geridə qoyduğu vəziyyətdir. false qaytarılması Addım 1-ə qayıtmaq üçün siqnaldır, eyni soketi təkrar yoxlamaq üçün deyil.

DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
    return;
}
socket.connect(serverAddress, serverPort);

Addım 4: soket əlverişsiz olduqda prosesi bağlayın

Bəzi transportlar tək soket deyil: kitabxana bir neçəsini aça bilər və ya köməkçi prosesdəki hər bağlantının eyni çıxışdan istifadə etməsini istəyə bilərsiniz. ConnectivityManager.bindProcessToNetwork prosesi seçilmiş şəbəkəyə bağlayır və köhnə setProcessDefaultNetwork ləğv edilməzdən əvvəl eyni işi görürdü. Əsas bağlantını NET_CAPABILITY_NOT_VPN ilə tələb edin ki, bağlama tunelinizin altındakı şəbəkəni seçsin, tunelin özünü deyil.

NetworkRequest request = new NetworkRequest.Builder()
    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();

Prosesi bağlamaq ikinci VPN deyil. O, həmin proses üçün çıxış seçir, bu sizin transportunuz üçün faydalı, eyni anda iki tunel işlətmək üçün isə faydasızdır.

İstifadəçinin cihazını düzəldin, kod yolunu deyil

İki tətbiqin VPN yerini paylaşmasına imkan verən API yoxdur, buna görə düzəliş cihaz vəziyyətinə aiddir. İstifadəçiyə iki konkret yol təklif edin: prepare-inizin ələ keçirməsi üçün digər tətbiqi ayırın və ya həmişə aktiv parametrini silin, ya da həmin tətbiqin tətbiq üzrə parametrlərini açıb tətbiqinizi onun tunelindən istisna edin. İstisna filtri qalan hər şey üçün işlək saxlayır və tətbiqinizin adi bağlantıdan istifadə etməsinə imkan verir, bu da çox vaxt istifadəçinin həqiqətən istədiyi nəticədir.

Hər iki yolu dəstək sənədlərinə yazın, çünki istifadəçilər VPN-ə hansı tətbiqin sahib olduğunu nadir hallarda bilirlər. Onlardan Tənzimləmələrdən aktiv VPN adını və jurnallarınızdan prepare, establish və protect nəticələrini göndərmələrini xahiş edin, bir dəqiqə içində hansı addımın uğursuz olduğunu deyə bilərsiniz. Sonra real trafiklə yoxlayın: ad axtarışı, TCP bağlantısı və tuneldən keçən bir sorğu, yalnız qoşulmuş ikona deyil. Etibarlı deskriptor və yaşıl göstərici ən yaxşı halda sahibliyi sübut edir, transportun marşrut qaydalarından sağ çıxdığını heç vaxt.

Dörd yoxlama da keçsə və trafik yenə ölərsə, sahiblik artıq sizin probleminiz deyil. Marşrutlaşdırma və server əlçatanlığına keçin və eyni etimadnamələri VPN tətbiqi quraşdırılmamış cihazdan yoxlayın.

Əlaqəli məqalələr