Android'e bir VPN'in bağlı olup olmadığını soramazsınız. VpnService bağlantı durumu için hiçbir getter sunmaz ve durum çubuğundaki anahtar simgesini sistem çizer, uygulamanızın okuyabileceği bir şey değil. Yapabileceğiniz şey, bağlantı yığınına daha dar bir soru sormaktır: bu uygulamanın trafiği hangi ağı kullanıyor ve o ağ VPN taşımasını taşıyor mu. Bu kılavuz, yanlış pozitifleri önleyen sırayla denetimleri yürütür; cihazdaki bir shell komutundan uygulamada çalışır bıraktığınız callback'e kadar.

Uygulama koduna dokunmadan önce cihazı bir shell'den denetleyin

Önce bir temel durum alın. adb shell dumpsys connectivity sistemin bildiği her ağı yazdırır ve her blok kendi taşımalarını listeler, bu yüzden bir VPN ağı o listede VPN ile görünür. Her şeyden önce varsayılan ağ bloğunu okuyun, sonra aynı taşımayı taşıyan diğer ağları arayın.

İki çıktı farklı soruları yanıtlar. Bir VPN ağının var olması, uygulamanızın onu kullanmasıyla aynı şey değildir ve iş profili ya da ikinci kullanıcı bulunan bir cihazda, süreciniz doğrudan internetle konuşurken ikisi aynı anda doğru olabilir. Soketlerinizin ne yaptığına karar veren, etkin ağ satırıdır.

Uygulamanın kullandığı ağı sorun

Kodda yetenek testi dört satırdır. Manifestte ACCESS_NETWORK_STATE isteyin, sonra etkin ağın yeteneklerini VPN taşımasıyla karşılaştırın:

ConnectivityManager cm = (ConnectivityManager)
    context.getSystemService(Context.CONNECTIVITY_SERVICE);
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean vpnInUse = caps != null
    && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);

Yanıt kasten dardır: uygulamanın varsayılan ağı şu anda bir VPN mi. Hangi VPN olduğu, sizin olup olmadığı veya tünelin karşı uca ulaşıp ulaşmadığı hakkında hiçbir şey söylemez. true değerini “trafik bir tünelden çıkıyor” olarak ele alın ve kendi kayıtlarınızı ayrı tutun. Sessizce aynı anda üç şey anlamına gelen bir boolean, kullanıcı VPN üzerinden gezinirken ayarlar ekranının VPN'i kapalı bildirmesinin nedenidir.

Bunun tersine, herhangi bir VPN ağı olup olmadığını bilmeniz gerektiğinde cm.getAllNetworks() üzerinde yineleyin ve her yetenek kümesini test edin. İki yanıt, diğer uygulamaları kapsayan ama sizinkini kapsamayan uygulama bazlı bir VPN çalışan cihazda ayrışır ve bu ayrışma, uygulamanın açıkça açık olan bir VPN'i görmediğini söyleyen hata raporlarının çoğunun kaynağıdır.

Yoklama yerine değişiklikleri izleyin

Zamanlayıcıyla yoklama bu soru için yanlış biçimdir, çünkü durum değişiklikleri olay olarak gelir. VPN taşımasıyla eşleşen bir istek kaydedin ve eşleşen bir ağ göründüğünde, kaybolduğunda ya da değiştiğinde çerçevenin size söylemesine izin verin:

NetworkRequest request = new NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();
cm.registerNetworkCallback(request, callback);

Bir VPN ağı hiçbir zaman NET_CAPABILITY_NOT_VPN taşımaz, bu yüzden açıkça kaldırma niyeti belgeler. O olmadan da istek eşleşir, ama kodu okuyan sonraki kişi bunu kendi başına çözmelidir.

Kayıt, gözlemi gerektiren kapsama aittir, genellikle bir ekran ya da ön plan hizmeti. onStop veya onDestroy içinde kaydı iptal edin ve çoktan gitmiş bir etkinliğe callback bırakmayın. Görünür bileşeni olmayan bir uygulama kalıcı bir gözlemci için kötü bir ev sahibidir ve unuttuğunuz callback'ler yapılandırma değişikliğinden sonra çökenlerdir.

Üç soruyu ayrı tutun

Bu alandaki karışıklığın çoğu, üç farklı denetimi tek bir denetim gibi ele almaktan gelir. Farklı API'ler kullanırlar ve biri doğruyken diğerleri yanlış olabilir.

QuestionHow you answer itWhat a yes means
Did the user grant VPN consent to my app?VpnService.prepare() returns nullConsent exists; the tunnel may still be down
Is the device sending default traffic through a VPN?TRANSPORT_VPN on the active networkSomething is tunneling, not necessarily your app
Did this request arrive through the VPN?Source address seen by the serverProof for that connection, nothing wider

Üçüncü satır, insanların istemcide yanıtlamaya çalıştığı satırdır ve orada yanıtlanamaz. VPN istemcilerine kendi adres havuzlarını verin ve sunucuda kaynak adresini okuyun; oturum ortasında kullanıcı tünel değiştirse bile ayakta kalan tek kanıt budur.

Yanlış pozitifleri dışlayan doğrulama sırası

Hâlâ dolaşımda olan eski örnek kod getNetworkInfo(ConnectivityManager.TYPE_VPN) çağırır. Bu çağrı kullanımdan kaldırılmıştır ve birkaç sürümde VPN bağlıyken bile null döndürdü, bu yüzden kopyalanmış pek çok kesit çalışan bir cihazda bozuk görünür. Yetenek denetimleri bu tuzağı atlar ve Android 5.0'dan itibaren aynı davranır. Hâlâ onu çağıran bir kod tabanını sürdürüyorsanız, çağrıyı bir yedek yola sarmak yerine değiştirin, çünkü yanlış yanıtı başta üreten o yedek yoldu.

Bir kısayol uyarıyı hak ediyor. Harici bir hizmetin bildirdiği genel adresi VPN'in çıkış adresiyle karşılaştırmak yol hakkında bir şey kanıtlar, ama her denetime üçüncü taraf isteği ekler, o hizmetin ayakta kalmasına bağlıdır ve önbelleğe alınabilir ya da engellenebilir. Elle yapılan bir tanılama olarak tutun, asla uygulama içindeki durum denetimi olarak değil.

Denetimleri bu sırayla çalıştırın ve kullanıcının bildirimiyle çelişen ilkinde durun: shell'deki taşıma listesi, etkin ağ yetenekleri, tüneli açıp kapatırken callback olayları, sonra sunucu tarafındaki kaynak adresi. Dördü de tutarlıysa ve uygulama hâlâ yanlış davranıyorsa hata Android'in ağ görüşünde değil, kendi durum makinenizdedir.

İlgili makaleler