Android'de VPN algılama: tun0 ve ping neden yanıltıyor
Arayüz adı bir gelenektir; ping bir ana bilgisayarın erişilebilir olduğunu kanıtlar. NetworkCapabilities'e ağın ne bildirdiğini sorun, sonra geri çağrılara tepki verin.
Çoğu Android uygulamasının VPN algılamak için kullandığı kontrol bir arayüz adıdır ve hiçbir zaman bir VPN kontrolü olmadı. Geliştiricinin beklediği dizeye sahip bir arayüzün var olduğunu bildirir; bu bir cihazda OpenVPN anlamına gelirken başka bir cihazda tamamen ilgisiz bir şey anlamına gelir. ping de aynı türden bir kanıttır: bir hedefin yanıt verdiğini kanıtlar, bir tünelin paketi taşıdığını değil. Platformun gerçekte sunduğu şey bir ağlar kümesi ve her birinin bildirdiği taşıma türleridir ve bu, başka bir yanıtı olan başka bir sorudur.
Efsane: tun0 varsa VPN açıktır
Android VPN uygulamaları, sisteme bir tun dosya tanımlayıcısı veren VpnService uygulamalarıdır. Arayüz adı onu oluşturan taraftan gelir: OpenVPN genellikle tun0 üretir, PPTP ve L2TP ppp0 üretir, WireGuard arayüzünü tünele göre adlandırır ve bir IPsec tüneli hiç geleneksel bir ad sunmayabilir. Bu nedenle /proc/net/dev okumak veya NetworkInterface nesnelerini saymak bir gerçeği değil, bir geleneği arar.
Hatanın iki yönü de canlıdır. Bir cihazda kullanıcının VPN'iyle hiçbir ilgisi olmayan yerel bir tünel için tun0 bulunabilir ve tamamen sağlıklı bir WireGuard tüneli yalnızca tun0 dizesini bilen bir kontrol için görünmez olabilir. Aynı işlevdeki yanlış pozitifler ve yanlış negatifler onu tanı aracı olarak kullanılamaz kılar, çünkü «VPN algılanmadı» diyen bir destek bileti hiçbir yönde güvenilir değildir.
Erişim de daraldı. Yeni Android sürümleri sıradan bir uygulamanın /proc'tan neler okuyabileceğini sınırlar, bu yüzden Android 8'de çalışan ada dayalı bir kontrol, kod değişikliği olmadan daha sonraki bir cihazda boş liste döndürebilir. Kontrol sessizce açık tarafa başarısız olduğunda uygulama, tünelin sorunsuz olduğu donanımda VPN olmadığı sonucuna varır.
Efsane: başarılı bir ping tünelin trafik taşıdığı anlamına gelir
Dahili bir ana bilgisayara ping, o ana bilgisayara bir yol olduğunda başarılı olur. Bölünmüş tünelde ana bilgisayara Wi-Fi üzerinden erişilebilirken tünel açık ve tamamen başka bir şey yapıyor olabilir. Uygulama başına VPN'de ping yapan uygulama tünelden hariç tutulabilir, bu yüzden ICMP yankısı tünelin dışına çıkıp geri döner; bu da kontrolün varsaydığının tam tersini kanıtlar.
ICMP ve TCP de ayrı ayrı filtrelenir. Yankı isteklerine izin verip bir hizmetin ihtiyaç duyduğu TCP bağlantı noktasını düşüren bir tünel ping testini geçer ve uygulamada başarısız olur; bu cihazın bir özelliği değil, karşı taraftaki bir güvenlik duvarı kuralıdır. Başarılı bir ping'in tek dürüst okuması, bu hedefin bu anda bu protokole yanıt verdiğidir.
Platform gerçekte ne bildirir
API 21'den itibaren ConnectivityManager.getAllNetworks() sistemin bildiği her ağı döndürür ve getNetworkCapabilities(network) o ağın ne bildirdiğini raporlar. Bir VPN ağı TRANSPORT_VPN taşır. Tüm ağlar üzerinde döngü yapmak tek bir soruyu yanıtlar: bu cihazda şu anda herhangi bir VPN açık mı?
İkinci, daha dar soru başka bir çağrı gerektirir. getActiveNetwork() uygulamanın trafiğinin şu anda kullanacağı ağı döndürür ve aynı yeteneği onun üzerinde kontrol etmek şunu yanıtlar: trafiğim bir VPN üzerinden geçer mi? İkisi çelişebilir ve bu çelişki bilgilendiricidir. Açık ama güncel olmayan bir VPN, uygulama başına yönlendirmeye veya bölünmüş tünel yapılandırmasına işaret eder, eksik bir tünele değil.
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean viaVpn = caps != null
&& caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
Her iki çağrıyı da koruyun. Yetenekler, listeleme ile sorgulama arasında kaybolan bir ağ için null olabilir ve etkin ağ bir devir sırasında null olabilir. NET_CAPABILITY_NOT_VPN diğer yönde yararlıdır: bir ağın yalnızca VPN taşıma bayrağından yoksun olduğunu değil, kesinlikle tünel olmadığını doğrulamak için.
NetworkInfo API ile aynı efsaneydi
NetworkInfo ile ConnectivityManager.TYPE_VPN bu kontrolün ikinci neslidir ve bir nedenden dolayı kullanımdan kaldırılmıştır. Modern sürümlerde tünel bağlıyken VPN için döndürülen nesne null olabilir, bu yüzden null'ı «VPN yok» olarak ele alan kod, yanıtın en çok önemli olduğu cihazlarda sessizce yanlış negatif bildirir.
Genellikle ona eşlik eden yayın da daha iyi değildir. CONNECTIVITY_CHANGE, her ağ değişikliğinde değil yalnızca bir VPN bağlandığında veya kesildiğinde tetiklenen derlemeler de dahil olmak üzere VPN geçişlerini kaçırmasıyla belgelenmiş bir geçmişe sahiptir. Onun üzerine kurulu bir dinleyici, bir sonraki tam yeniden bağlantıya kadar platformun görüşünden uzaklaşan bir durum değişkeni üretir.
Yoklama yerine geri çağrılara tepki verin
TRANSPORT_VPN gerektiren ve NET_CAPABILITY_NOT_VPN'i kaldıran bir NetworkRequest kaydedin, ardından bu istekle registerNetworkCallback çağırın. Sonra onAvailable ve onLost bildirimin ne zaman değiştiğini söyler ve bu gerçekten önemsediğiniz olaydır. Bileşen durduğunda geri çağrının kaydını iptal edin, yoksa uygulama ölü bir ekrana referans tutar ve geri çağrı tetiklenmeye devam eder.
NetworkRequest req = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();
cm.registerNetworkCallback(req, callback);
Bu, normal bir izin olan ACCESS_NETWORK_STATE gerektirir: root yok, kullanıcı onayı iletişim kutusu yok, VPN izni yok. Zamanlayıcıyla yoklama, insanların başvurduğu alternatiftir ve pil tüketirken aralıktan kısa bir geçişi yine de kaçırır; bu da tam olarak bir kullanıcının bildirdiği geçiştir.
Yanıtın hâlâ size söyleyemediği şey
Bu API'lerin hiçbiri tünelin sağlıklı olup olmadığını, hangi sunucuda sonlandığını veya belirli bir uygulamanın trafiğinin içinde olup olmadığını bildirmez. Bir VPN ağı kullanılabilir yollar olmadan var olabilir ve uygulama başına bir tünel soruyu soran uygulamayı hariç tutabilir.
Bu yüzden kontrolün ne gördüğünü kaydedin, yalnızca boolean'ı değil. Ağ tanımlayıcısını, bildirilen taşıma türlerini ve o ağın etkin olup olmadığını günlüğe yazın. «VPN algılanmadı» diyen bir rapor kullanılamaz, oysa bir ağın TRANSPORT_VPN bildirdiğini ama getActiveNetwork()'ün Wi-Fi döndürdüğünü söyleyen bir rapor doğrudan yönlendirmeye veya bir dışlama listesine işaret eder ve bu, birinin üzerinde çalışabileceği bir düzeltmedir.