Mendeteksi VPN di Android: mengapa tun0 dan ping menyesatkan
Nama antarmuka hanyalah konvensi; ping membuktikan satu host dapat dijangkau. Tanyakan kepada NetworkCapabilities apa yang dideklarasikan jaringan, lalu bereaksi terhadap callback.
Pemeriksaan yang dipakai sebagian besar aplikasi Android untuk mendeteksi VPN adalah nama antarmuka, dan itu tidak pernah menjadi pemeriksaan VPN. Pemeriksaan itu melaporkan bahwa ada antarmuka dengan string yang diharapkan pengembang, yang pada satu perangkat berarti OpenVPN dan pada perangkat lain berarti sesuatu yang sama sekali tidak terkait. Ping adalah jenis bukti yang sama: ia membuktikan bahwa satu tujuan menjawab, bukan bahwa sebuah terowongan membawa paket itu. Yang sebenarnya diekspos platform adalah sekumpulan jaringan dan transport yang dideklarasikan masing-masing, dan itu pertanyaan lain dengan jawaban lain.
Mitos: jika tun0 ada, VPN aktif
Aplikasi VPN Android adalah implementasi VpnService yang menyerahkan deskriptor berkas tun kepada sistem. Nama antarmuka berasal dari siapa pun yang membuatnya: OpenVPN biasanya menghasilkan tun0, PPTP dan L2TP menghasilkan ppp0, WireGuard menamai antarmukanya menurut terowongan, dan terowongan IPsec mungkin tidak menampilkan nama konvensional sama sekali. Jadi membaca /proc/net/dev atau menghitung objek NetworkInterface mencari konvensi, bukan fakta.
Kedua arah kesalahan itu hidup. Perangkat dapat memiliki tun0 untuk terowongan lokal yang tidak ada hubungannya dengan VPN pengguna, dan terowongan WireGuard yang sepenuhnya sehat bisa tak terlihat bagi pemeriksaan yang hanya mengenal string tun0. Positif palsu dan negatif palsu dalam fungsi yang sama membuatnya tidak berguna sebagai diagnostik, karena tiket dukungan yang menyatakan «VPN tidak terdeteksi» tidak dapat dipercaya ke arah mana pun.
Akses juga menyempit. Rilis Android yang lebih baru membatasi apa yang boleh dibaca aplikasi biasa dari /proc, sehingga pemeriksaan berbasis nama yang berfungsi di Android 8 dapat mengembalikan daftar kosong pada perangkat yang lebih baru tanpa perubahan kode apa pun. Ketika pemeriksaan gagal secara diam-diam ke arah terbuka, aplikasi menyimpulkan tidak ada VPN pada perangkat keras yang terowongannya baik-baik saja.
Mitos: ping yang berhasil berarti terowongan membawa lalu lintas
Ping ke host internal berhasil setiap kali ada rute ke host itu. Dengan tunnelling terpisah, host mungkin dapat dijangkau lewat Wi-Fi sementara terowongan aktif dan melakukan hal yang sama sekali lain. Dengan VPN per aplikasi, aplikasi yang melakukan ping mungkin dikecualikan dari terowongan, sehingga gema ICMP keluar di luarnya dan kembali, yang membuktikan kebalikan dari yang diasumsikan pemeriksaan.
ICMP dan TCP juga disaring secara terpisah. Terowongan yang mengizinkan permintaan gema tetapi membuang port TCP yang dibutuhkan suatu layanan akan lulus uji ping dan gagal di aplikasi, dan itu aturan firewall di sisi jauh, bukan sifat perangkat. Satu-satunya bacaan jujur dari ping yang berhasil adalah bahwa tujuan ini menjawab protokol ini pada saat ini.
Apa yang sebenarnya dilaporkan platform
Sejak API 21, ConnectivityManager.getAllNetworks() mengembalikan setiap jaringan yang dikenal sistem, dan getNetworkCapabilities(network) melaporkan apa yang dideklarasikan jaringan itu. Jaringan VPN membawa TRANSPORT_VPN. Mengulang semua jaringan menjawab satu pertanyaan: apakah ada VPN yang aktif di perangkat ini sekarang?
Pertanyaan kedua yang lebih sempit memerlukan panggilan lain. getActiveNetwork() mengembalikan jaringan yang akan dipakai lalu lintas aplikasi sekarang, dan memeriksa kemampuan yang sama padanya menjawab: apakah lalu lintas saya akan melewati VPN? Keduanya bisa tidak sepakat, dan ketidaksepakatan itu informatif. VPN yang aktif tetapi bukan yang sekarang menunjuk ke perutean per aplikasi atau konfigurasi terowongan terpisah, bukan ke terowongan yang hilang.
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean viaVpn = caps != null
&& caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
Lindungi kedua panggilan. Kemampuan bisa null untuk jaringan yang menghilang antara pendaftaran dan penanyaan, dan jaringan aktif bisa null selama peralihan. NET_CAPABILITY_NOT_VPN berguna ke arah lain, untuk memastikan suatu jaringan jelas bukan terowongan alih-alih hanya tidak memiliki bendera transport VPN.
NetworkInfo adalah mitos yang sama dengan API
ConnectivityManager.TYPE_VPN dengan NetworkInfo adalah generasi kedua pemeriksaan ini, dan sudah usang karena suatu alasan. Pada rilis modern objek yang dikembalikan untuk VPN bisa null saat terowongan tersambung, sehingga kode yang memperlakukan null sebagai «tidak ada VPN» diam-diam melaporkan negatif palsu pada perangkat yang jawabannya paling penting.
Siaran yang biasanya menyertainya juga tidak lebih baik. CONNECTIVITY_CHANGE memiliki riwayat terdokumentasi melewatkan transisi VPN, termasuk build yang hanya memicunya saat VPN tersambung atau terputus alih-alih pada setiap perubahan jaringan. Pendengar yang dibangun di atasnya menghasilkan variabel keadaan yang menjauh dari pandangan platform sampai penyambungan ulang penuh berikutnya.
Bereaksilah pada callback alih-alih melakukan polling
Daftarkan NetworkRequest yang memerlukan TRANSPORT_VPN dan menghapus NET_CAPABILITY_NOT_VPN, lalu panggil registerNetworkCallback dengan permintaan itu. onAvailable dan onLost kemudian memberi tahu Anda kapan deklarasi berubah, dan itulah peristiwa yang benar-benar Anda pedulikan. Batalkan pendaftaran callback saat komponen berhenti, atau aplikasi menyimpan referensi ke layar mati dan callback terus terpicu.
NetworkRequest req = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();
cm.registerNetworkCallback(req, callback);
Ini memerlukan ACCESS_NETWORK_STATE, yang merupakan izin normal: tanpa root, tanpa dialog persetujuan pengguna, tanpa izin VPN. Polling dengan pengatur waktu adalah alternatif yang orang pakai, dan itu menghabiskan baterai sambil tetap melewatkan transisi yang lebih pendek dari interval, yang justru transisi yang dilaporkan pengguna.
Apa yang masih tidak dapat diberitahukan jawabannya
Tidak satu pun dari API ini melaporkan apakah terowongan sehat, di server mana ia berakhir, atau apakah lalu lintas aplikasi tertentu berada di dalamnya. Jaringan VPN bisa ada tanpa rute yang dapat dipakai, dan terowongan per aplikasi bisa mengecualikan aplikasi yang mengajukan pertanyaan.
Jadi catat apa yang dilihat pemeriksaan, bukan hanya boolean. Catat pengenal jaringan, transport yang dideklarasikan, dan apakah jaringan itu aktif. Laporan yang menyatakan «VPN tidak terdeteksi» tidak berguna, sedangkan laporan yang menyatakan suatu jaringan mendeklarasikan TRANSPORT_VPN tetapi getActiveNetwork() mengembalikan Wi-Fi menunjuk langsung ke perutean atau ke daftar pengecualian, dan itu perbaikan yang bisa dikerjakan seseorang.