Başka bir VPN zaten etkinken VpnService koruması başarısız oluyor
Android kullanıcı başına bir VPN arayüzüne izin verir. prepare, establish, protect ve sahiplik denetimlerini sırayla yapın, ardından iptal veya uygulama bazında hariç tutmayı seçin.
Uygulamanız VpnService.prepare çağırır, null alır, bir tünel kurar ve tanıtıcı geçerli görünür, ama sunucuya hiçbir şey ulaşmaz. Ya da prepare bir Intent döndürür, kullanıcı onaylar ve establish yine başarısız olur. İki sonuç genellikle aynı anlama gelir: başka bir uygulama bu kullanıcının VPN arayüzüne zaten sahiptir ve protect çağrılarınız tünelinizin kullanamayacağı soketleri işaretliyordur. Aşağıdaki denetimleri sırayla yapın ve ilk başarısız olanda durun, çünkü sonraki adım öncekini onaramaz.
protect'e dokunmadan önce VPN yuvasını denetleyin
Android her kullanıcıya tam olarak bir etkin VPN arayüzü verir. Bu bir platform kuralıdır, uygulamanızın isteyebileceği bir izin değildir, bu nedenle ikisi de tünel olmak isteyen iki istemci bir arada var olamaz.
Başka bir uygulama için ayarlanmış sürekli açık VPN en zor durumdur, çünkü sistem o uygulamayı bağlı tutar ve devralmayı reddeder. AdGuard gibi bir trafik filtresi, kurumsal istemci veya tüketici VPN'i, kullanıcı kurulu olduğunu unutmuşken yuvayı tutabilir. Kullanıcıdan Ayarlar'ı açıp VPN altında listelenen girdiyi okumasını isteyin; oradaki ad, rekabet ettiğiniz sahiptir. O girdi bağlantısı kesilene veya sürekli açık ayarı silinene kadar VpnService arayüz olamaz.
Adım 1: prepare'in ne döndürdüğünü okuyun
VpnService.prepare tek bir soruya yanıt verir: bu uygulama VPN olabilir mi? Onay zaten varsa null, kullanıcı hâlâ onaylamak zorundaysa Intent döndürür ve bunu startActivityForResult ile başlatmanız gerekir. null dönüşünü devam etme izni olarak görün, establish'in başarılı olacağının kanıtı olarak değil.
Intent intent = VpnService.prepare(context);
if (intent != null) {
startActivityForResult(intent, REQUEST_VPN);
}Intent yolu dönerse ve kullanıcı reddettiyse, prepare bir sonraki denemede yine Intent döndürmeye devam eder. Döngüde yeniden denemek yerine sonuç kodunu günlüğe yazın, çünkü reddedilen onay kullanıcı kararıdır, geçici bir hata değildir.
Adım 2: arayüzün uygulamanıza ait olduğunu doğrulayın
establish sonrası aldığınız ParcelFileDescriptor sahiplik kanıtınızdır ve herhangi bir soket işinden önce denetlemeye değer. null veya zaten kapatılmış bir tanıtıcı, bir istisna görünmese bile arayüzün hiç oluşturulmadığı anlamına gelir. builder'daki arayüz adını sistemin etkin VPN için bildirdiği adla karşılaştırın, bir test cihazında dumpsys kullanarak. Farklıysa, kurduğunuz tünel trafiği taşıyan tünel değildir.
adb shell dumpsys connectivityAynı uyumsuzluk, bağlı bir simgeyle ölü trafiği açıklar. Sistem diğer uygulamanın tünelini gösterirken kodunuz sahip olmadığı bir arayüzün tanıtıcısını tutar.
Adım 3: connect'ten önce protect çağırın
Arayüz var olduğunda, uygulamanızın açtığı soketler varsayılan olarak tünele yönlendirilir. Dış taşıma bu kurala uyamaz, aksi halde kapsüllenmiş paket arayüze geri döner, bu yüzden VpnService protect bir soketi VPN'i atlayıp alttaki ağı kullanmak üzere işaretler. Çağrı bir boolean döndürür ve onu yok saymak, sağlıklı görünen ama hiçbir şey taşımayan bir soketle sonuçlanmanın yoludur.
protect'i connect veya bind'dan önce çağırın, asla sonra değil. Koruma soket bağlanmadan önce yerinde olmalıdır, çünkü bağlanmış bir soket rotasını zaten seçmiştir ve daha sonraki bir protect onu güvenilir biçimde taşımaz. protect false döndürürse durun ve sahipliği yeniden denetleyin: arayüz uygulamanıza ait olmadığında başarısız olur ve bu tam olarak başka bir VPN'in bıraktığı durumdur. false dönüş, Adım 1'e geri dönme sinyalidir, aynı soketi yeniden deneme değil.
DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
return;
}
socket.connect(serverAddress, serverPort);Adım 4: soket zahmetli olduğunda bir süreci bağlayın
Bazı taşımalar tek bir soket değildir: bir kütüphane birkaç tane açabilir veya yardımcı bir süreçteki her bağlantının aynı çıkışı kullanmasını isteyebilirsiniz. ConnectivityManager.bindProcessToNetwork süreci seçilen bir ağa bağlar ve daha eski setProcessDefaultNetwork kullanımdan kaldırılmadan önce aynı işi yapardı. Alttaki bağlantıyı NET_CAPABILITY_NOT_VPN ile isteyin, böylece bağlama tünelinizin altındaki ağı seçer, tünelin kendisini değil.
NetworkRequest request = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();Bir süreci bağlamak ikinci bir VPN değildir. O süreç için bir çıkış seçer; bu taşımanız için yararlıdır ve aynı anda iki tünel çalıştırmak için yararsızdır.
Kod yolunu değil, kullanıcının cihazını düzeltin
İki uygulamanın VPN yuvasını paylaşmasına izin veren bir API yok, bu nedenle düzeltme cihazın durumuna bağlıdır. Kullanıcıya iki somut yol sunun: diğer uygulamanın bağlantısını kesin veya sürekli açık ayarını silin ki prepare devralabilsin, ya da o uygulamanın uygulama bazlı ayarlarını açıp uygulamanızı tünelinden hariç tutun. Hariç tutma, filtrenin diğer her şey için çalışmaya devam etmesini sağlar ve uygulamanızın olağan bağlantıyı kullanmasına izin verir; bu genellikle kullanıcının gerçekten istediği sonuçtur.
İki yolu destek belgelerinize yazın, çünkü kullanıcılar hangi uygulamanın VPN'e sahip olduğunu nadiren bilir. Onlardan Ayarlar'dan etkin VPN adını ve günlüklerinizden prepare, establish ve protect sonuçlarını göndermelerini isteyin, bir dakika içinde hangi adımın başarısız olduğunu söyleyebilirsiniz. Ardından gerçek trafikle doğrulayın: bir ad çözümlemesi, bir TCP bağlantısı ve tünelden geçen bir istek, yalnızca bağlı bir simge değil. Geçerli bir tanıtıcı ve yeşil bir gösterge en fazla sahipliği kanıtlar, taşımanın yönlendirme kurallarından sağ çıktığını asla.
Dört denetim de geçerse ve trafik hâlâ ölüyorsa sahiplik artık sizin sorununuz değildir. Yönlendirme ve sunucuya erişilebilirliğe geçin ve aynı kimlik bilgilerini VPN uygulaması kurulu olmayan bir cihazdan test edin.