Android VpnService hakkındaki dört varsayım bir keresinde bana neredeyse bir haftaya mal oldu ve hiçbiri ağ protokolüyle ilgili değildi. Uygulama bağlı diyordu, anahtar simgesi durum çubuğunda oturuyordu ve trafik yine normal rotadan çıkıyordu. Tüneli ben durdurana kadar onu kendi tünelim sandım, eklediğim rotaların aldığım rotalar olduğunu sandım ve stopSelf çağırmanın arayüzü de beraberinde indireceğini sandım. Hizmet tam olarak belgelendiği gibi davranıyordu. Kırık olan kısım benim zihinsel modelimdi.

Efsane 1: tünel siz durdurana kadar ayakta kalır

Android aynı anda tek bir VPN arayüzü tutar. İkincisini kurun, platform onu kuyruğa almaz: sizinkini devre dışı bırakır ve hizmetinizde onRevoke() çağırır. Kodunuz yalnızca düğmeye basınca her şeyi söküyorsa, bir devralma arkasında bir tanımlayıcı ve bir okuyucu iş parçacığı bırakır, sonraki başlatma ikisini de devralır.

VpnService başvurusu, uygulamanın zaten onayı varsa prepare() öğesinin null döndürdüğünü, kullanıcının hâlâ onaylaması gerekiyorsa Intent döndürdüğünü açıkça söyler. Bu onay ancak niyeti startActivityForResult() üzerinden iletirseniz gelir. Aktarımı atlayın ve establish() null döndürür; bu, izin sorunu değil sessiz bir başarısızlık olarak okunur.

Always-on VPN, kontrol etmediğiniz bir sahip ekler. Sistem tüneli açılıştan veya çökmeden sonra geri getirebilir ve kullanıcının elle başlattığı bir VPN uygulamasının önüne geçer. onRevoke() öğesini normal kapatmanızla aynı kaynakları kapatacak şekilde kurun ve ona köşe durumu değil, birincil durdurma yolu olarak bakın. Kullanıcılar VPN uygulaması değiştirir; kodunuz bunu atlatmalı.

Efsane 2: addRoute tam olarak listelediğinizi yakalar

Hangi paketlerin tanımlayıcınıza ulaştığına rotalar karar verir ve kapsam hatası, trafiğin bir kısmının tünelden geçip bir kısmının geçmediği o sinir bozucu durumu yaratır. addRoute("0.0.0.0", 0) tüm IPv4'ü alır, yani artık her paket size gelir ve iletilmesi ya da düşürülmesi gerekir. Tek bir ana bilgisayar için /32 neredeyse hiçbir şey almaz ve geri kalan her şey VPN'iniz hiç kurulmamış gibi davranır.

Aileler ayrı ayrı sayılır. Yalnızca IPv4 rotası IPv6'yı alttaki ağda bırakır, bu yüzden çift yığınlı bir ana bilgisayar tünelin içinde tamamen bağlı görünürken dışında hâlâ ad çözebilir ve bağlanabilir.

DNS aynı mantığı izler. addDnsServer() ile eklenen sunucular tünelin çözümleyicisini kullanan uygulamalara gider, kendi şifreli DNS uç noktası ya da bir ana bilgisayar adına ayarlanmış Private DNS olan bir uygulama ise onları tamamen yok sayar. Adlar bir uygulamada başarısız olup diğerinde çalıştığında, tek bir rotaya dokunmadan önce çözümleyici yolunu kontrol edin.

Efsane 3: uygulama başına kurallar iki yöne de çevrilebilen bir anahtar

addDisallowedApplication() ve addAllowedApplication() iki filtre değil, iki kiptir. Oluşturucu tünel başına tek bir model kabul eder, bu yüzden üç paket dışında her şeyi isteyen bir uygulama yasaklı listeyi kullanır ve izinli listeyi boş bırakır. İkisi birbirini dışlar ve insanlar bunu, ayarlar ekranı her iki anahtarı da sunduktan sonra çalışma zamanında öğrenir.

Paket görünürlüğü de ısırır. Android 11 ve sonrasında yalnızca görebildiğiniz paketleri adlandırabilirsiniz, bu yüzden uygulamanızın çalıştırmasına izin verilmeyen bir sorgudan oluşturulan dışlama listesi girdileri sessizce kaçırır. Listeye güvenmeden önce paket yöneticisinin gerçekte ne döndürdüğüyle karşılaştırın.

İş profilleri kitleyi yeniden böler. Profil içinde başlatılan bir tünel o profili ve uygulamalarını kapsar, kişisel taraf ise kendi bağlantısını korur. Bu bir kez verilen bir kapsam kararıdır ve yeri arayüzdür, destek bileti değil.

Efsane 4: stopSelf tüneli bitirir

Bitirmez. Arayüz establish() öğesinin döndürdüğü tanımlayıcıda yaşar ve hizmet nesnesi o tanımlayıcı açıkken ölebilir. Önce ParcelFileDescriptor öğesini kapatın, referansı bırakın, okuyucu ve yazıcı iş parçacıklarını birleştirin ya da kesin, ardından stopForeground(true) ve stopSelf() öğelerini bu sırayla çağırın.

Unutulan bir tanımlayıcı, bir hizmetin kendi durdurma isteğini atlattığı izlenimini veren olağan nedendir ve onu ekleyen uygulamadan daha uzun yaşayan eski bir rotayı açıklar.

Ters hata da aynı ölçüde yaygındır: bir okuyucu iş parçacığı okumanın ortasındayken tanımlayıcıyı kapatmak ve sonra o iş parçacığını kimsenin kod yazmadığı bir istisnayla baş başa bırakmak. Kapatma sırasını tek bir yerde tutun ve hem kullanıcı eylemi hem de onRevoke() oraya çağırsın.

Tünel ölü göründüğünde ne kontrol edilmeli

Tünel açılmadan önce uzun ömürlü soketleri protect() ile koruyun. Bu olmadan tünelin kendi taşıması yeniden tünelin içine yönlendirilir ve bağlantı tam olarak sunucu arızasına benzeyen biçimde takılır. Yalnızca uzak ağın arkasında var olan bir adrese karşı test edin, önbellekten ya da alttaki yoldan yanıt veren bir genel siteye karşı değil.

establish() sonucunu, rotaları, dışlanan paketleri ve kapatma nedenini günlüğe yazın. Bu dört satır, paket yakalamadan onay sorununu kapsam sorunundan ve yaşam döngüsü sorunundan ayırır.

İlgili makaleler