Android VpnService qoşulur, lakin şəbəkəyə heç bir paket çatmır
Tunel qalxıb, səhifələr hələ də açılmır: serveri günahlandırmazdan öncə oxuma rejimini, soket qorumasını, TCP dəstəyini və marşrut siyahısını yoxlayın.
Tunel qalxır, açar işarəsi görünür, brauzerdəki hər sorğu isə hələ də vaxt aşımına uğrayır. VpnService tətbiqində bu birləşmə adətən oxuma dövrəsinin işlədiyini və çıxış yarısının sındığını bildirir, serverin əl sıxışmasını rədd etdiyini deyil. Android interfeysi artıq yaradıb, tətbiq trafikini ona köçürüb və prosesinizə fayl deskriptoru verib. Həmin deskriptordan sonrakı hər şey sizin kodunuzdur və bir çatışmayan çağırış bütün axını udmaq üçün kifayətdir.
Sistem sizə nə verir və nə vermir
establish() tun interfeysi üçün ParcelFileDescriptor qaytarır. Sistem xaricə gedən IP paketlərini ona yazır və prosesinizin onları oxumasını, hara getdiklərini qərarlaşdırmasını və adi soket vasitəsilə göndərməsini gözləyir. Altında kernel yönləndirməsi yoxdur, ona görə heç kim oxumayanda paketlər atılır və interfeys sayğacları heç tərpənmir.
Təxmin etmək əvəzinə saymaqla başlayın. Hər uğurlu oxumada sayğacı artırın və ilk bir neçə paketin təyinatını və protokolunu qeyd edin. Trafik yaranarkən sayğac sıfırda qalırsa, nasazlıq yönləndirmə kodunuzdan yuxarıdadır və axtarışı qurucu konfiqurasiyasına və oxuma rejiminə daraldır. Sayğac tərpənir və brauzer yenə uğursuz olursa, nasazlıq göndərmə yolunda və ya geri dönüş yolundadır.
Bloklayan oxuma dövrənin tərpənməsini həll edir
setBlocking(true) deskriptoru bloklayan rejimə qoyur və API 21-dən bəri mövcuddur. Köhnə buraxılışlarda həll yolu O_NONBLOCK-u xam deskriptorda fcntl ilə silən kiçik JNI köməkçisidir. Rejim vacibdir, çünki gözləyən paketi olmayan oxumanın nə edə biləcəyini məhz o müəyyən edir.
Bloklamayan rejimdə həmin oxuma dərhal mənfi qiymətlə qayıdır və nəticəni nəzərə almayan dövrə heç trafik hərəkət etdirmədən bir nüvəni fırladır. Bloklayan rejimdə oxuma gözləyir, yönləndirmə ipinin istədiyi də budur, lakin ip artıq kəsilə bilməz. Deskriptoru bağlamaq oxumanı uğursuz etməyin dəstəklənən yoludur ki, dövrə çıxa bilsin. Əvəzində prosesi öldürmək və ya ipi kəsmək növbəti başlanğıcı artıq interfeysə uyğun gəlməyən deskriptorla qoya bilər.
Hər çağırışda interfeysin MTU ölçüsünə uyğun buferə bir paket oxuyun və sıfırı və ya mənfi dönüşü boş paket deyil, xəta kimi qəbul edin.
protect() ilk göndərişdən əvvəl, sonra deyil
Kapsullanmış paket daşıyan hər soket əvvəlcə qorunmalıdır. Qorunmamış soket indicə qurduğunuz eyni marşrut qaydalarına tabe olur, ona görə paketləri yenidən tunelə qayıdır və öz oxuma dövrəniz onları təkrar götürür. Sonra tunel öz çıxışını sonsuz olaraq yönləndirir və kənardan bu, açılan və heç vaxt cavab verməyən bağlantı kimi görünür.
Sıra belədir: protect, sonra connect və ya bind, sonra send. protect-i connect-dən sonra çağırmaq əvvəlki marşrut qərarını yerində qoya bilər, ona görə çağırış true qaytarsa da soket səhv yoldan istifadə etməyə davam edir. Qaytardığı booleanı yoxlayın və false olduqda işləyə bilməyəcək göndərişə doğru getmək əvəzinə ucadan uğursuz olun.
Heç vaxt protect() görməmiş soketdə sendto-dan gələn icazə xətası bu səhvin adi imzasıdır.
UDP nümunəsi HTTPS daşımır
Dərsliklərin çoxunun kopyaladığı yönləndirmə nümunəsi UDP ilə işləyir və başqa heç nə ilə deyil. TCP əl sıxışması, ardıcıllıq nömrələri, təsdiqlər və yenidən göndərmə tələb edir, yükü tun deskriptoru ilə soket arasında kopyalayan dövrə isə bunların heç birini təqdim etmir. Buna görə UDP DNS sorğusu keçə bilir, hər HTTPS sorğusu isə dayanır. Ya istifadəçi məkanında TCP yığını işlədin, ya da bağlantıları cihazda TCP-ni sonlandıran və axını yönləndirən yerli proksiyə verin.
Heç nəyi yenidən yazmazdan əvvəl bölgünü sınayın: bir UDP sorğusu göndərin və eyni hosta bir TCP bağlantısı açın. UDP yolu işləyirsə və TCP yolu ilk SYN-də ilişirsə, problem yönləndirmə kodundadır, serverdə deyil. Bu tək müqayisə faydalı heç nə ehtiva etməyən server jurnallarını oxumağa sərf olunan çox vaxtı qənaət edir.
Marşrut siyahısı və tətbiq üzrə filtrlər gördüklərinizi məhdudlaşdırır
addAddress, addRoute və addDns sistemin tunelə nə göndərəcəyini müəyyən edir. Marşrut siyahısından kənarda olan təyinat heç vaxt deskriptorunuza çatmır, ona görə heç bir yönləndirmə məntiqi bunu düzəltməyəcək. Etdiyiniz qurucu çağırışlarını qeyd edin və onları tətbiqin həqiqətən çatmağa çalışdığı ünvanlarla müqayisə edin.
Tətbiq üzrə seçimin öz mərtəbəsi var: addAllowedApplication və addDisallowedApplication API 21 tələb edir, köhnə buraxılışlarda isə metodlar sadəcə yoxdur. Paketə görə filtr edən dizayn orada ümumiyyətlə işləyə bilməz.
Yenidən qurma köhnə deskriptorları geridə qoyur
establish()-i yenidən çağırmaq yeni deskriptor qaytarır və əvvəlki interfeysi bağlayır. Köhnə deskriptor artıq etibarsızdır, ona görə hələ onu gözləyən ip xəta alır və köhnə yol üçün yaradılmış soketlər dəyişdirilməli ola bilər. onRevoke istifadəçinin və ya sistemin əlindən aldığı tunel üçün geri çağırışdır. Həyat dövrəsini bir sahibdə saxlamaq, yenidən qurmadan əvvəl oxuyucunu dayandırır və qoşulmuş-amma-ölü halların çoxunun qarşısını alır.
Hər xətanı aid olduğu addıma görə oxuyun. recvfrom-dan gələn bağlantı sıfırlanması kodunuz hələ oxuyarkən uzaq tərəfin bağlandığını bildirir. Pis ünvan xətası şəbəkəyə deyil, başlıq təhlilinə və ya ofset səhvinə işarə edir.
İş sırası: bloklayan rejim açıq, hər oxumada bir paket, başlıq təhlil edilib, soket qorunub, yük göndərilib, təhvil zamanı təmiz bağlanma və təyinatı əhatə edən marşrut siyahısı.