Split tunneling di Android VpnService: apa yang benar-benar bisa dirutekan
VpnService hanya meneruskan paket yang dikirim builder ke deskriptornya. Periksa prefiks, autentikasi proxy, DNS, dan daftar aplikasi sebelum menyalahkan split tunneling.
Split tunneling di aplikasi VpnService rusak di beberapa titik tertentu, dan perbaikannya berbeda untuk masing-masing. Klien hanya menampilkan satu tujuan yang gagal, jadi tugas pertama adalah menentukan batas mana yang Anda kena: rute yang diterima builder, proxy yang tidak bisa diautentikasi, resolver yang hanya Anda umumkan, atau daftar aplikasi yang diterapkan platform per pengguna. Karena itulah saya menyimpan pemeriksaan empat langkah. Urutannya seperti di bawah dan berhenti pada kegagalan pertama.
Pastikan apa yang builder serahkan ke platform
VpnService.Builder adalah daftar deklarasi. establish() mengembalikan ParcelFileDescriptor untuk antarmuka virtual, dan platform hanya mengirim lalu lintas yang cocok dengan prefiks addAddress dan addRoute ke deskriptor itu; addDnsServer memberi tahu klien resolver mana yang harus ditanyai. Tujuan di luar semua prefiks yang Anda tambahkan tidak pernah mencapai aplikasi Anda, apa pun janji antarmuka. Fakta tunggal ini menjelaskan sebagian besar laporan split tunneling rusak sebelum kode diubah.
Catat daftar alamat, rute, dan DNS yang disusun builder Anda, lalu bandingkan dengan tujuan yang gagal. Satu /32 yang hilang untuk host manajemen jauh lebih umum daripada tunnel yang rusak. Jika daftarnya benar, lanjutkan; jika salah, sisa daftar periksa adalah usaha sia-sia.
excludeRoute hanya ada sejak API 33
Pengecualian berbasis tujuan adalah permintaan yang paling sering menjadi utas dukungan. builder.excludeRoute(IpPrefix) hadir di API 33; sebelumnya satu-satunya pendekatan yang didukung adalah menambahkan rute untuk semuanya kecuali prefiks yang dikecualikan, yang berarti Anda harus menghitung sendiri prefiks pelengkapnya. Kedua panggilan bukan bayangan cermin: satu mengurangi dari kumpulan rute antarmuka, yang lain membangun kumpulan dari nol, dan perangkat lama akan melempar atau diam-diam mengabaikan panggilan yang tidak dikenalnya. Bercabanglah pada Build.VERSION.SDK_INT dan uji kedua jalur di perangkat keras nyata.
Saat menghitung pelengkap, perhatikan keselarasan prefiks. Pengecualian /24 di dalam rute /16 memerlukan prefiks penutup yang benar-benar menjelaskan sisanya, bukan satu /8 yang cacat. Catat daftar akhir dan pastikan host yang dikecualikan masih terselesaikan lewat jalur di luar tunnel.
Kredensial proxy tidak dapat dinyatakan
setHttpProxy(ProxyInfo) mengarahkan antarmuka ke proxy HTTP atau SOCKS, dan URL PAC dapat memilih di antara keduanya per tujuan. ProxyInfo maupun format PAC tidak membawa nama pengguna dan kata sandi, jadi proxy yang memerlukan autentikasi berada di luar API platform. Aplikasi yang membutuhkannya biasanya menyematkan tumpukan ruang pengguna, tun2socks, atau mesin serupa, dan menjawab sendiri tantangan 407.
Pilihan itu punya konsekuensi. Tumpukan ruang pengguna hanya melihat lalu lintas yang Anda rutekan ke sana, jadi ia harus hidup di dalam batas deskriptor yang sama, dan mewarisi persyaratan protect() yang sama untuk soket uplink-nya sendiri. Lewatkan protect() dan tunnel menangkap transportnya sendiri, yang tampil sebagai jabat tangan yang macet setelah paket pertama. Uji terhadap proxy yang menuntut kredensial, karena proxy terbuka lolos di jalur platform dan tidak membuktikan apa pun.
Daftar per aplikasi dan DNS juga deklarasi
Builder memiliki addAllowedApplication dan addDisallowedApplication, dan keduanya saling meniadakan: memanggil keduanya melempar pengecualian. Bagaimanapun daftar diterapkan per pengguna saat penyiapan antarmuka, jadi aplikasi profil kerja diatur oleh tunnel profil kerja, bukan tunnel Anda. Yang tidak diberikan API mana pun adalah kendali atas keputusan aplikasi lain: Anda tidak bisa mendorong paket ke tunnel dari luar, dan tidak bisa menutup atau mengonfigurasi ulang VPN yang sudah aktif.
DNS mengikuti pola yang sama. addDnsServer mengumumkan alamat resolver; itu tidak membuat aplikasi Anda menjawab kueri. Jika ingin menyelesaikan nama sendiri, jalankan resolver dan rutekan alamatnya ke tunnel. Aplikasi yang memakai DoT atau DoH ke nama host tetap dapat melewati alamat yang Anda umumkan, jadi perlakukan resolusi nama sebagai upaya terbaik kecuali Anda juga mencegat jalur kueri.
Manifest, always-on, dan uji empat langkah
Kelayakan dideklarasikan di manifest: BIND_VPN_SERVICE, filter intent android.net.VpnService, dan layanan latar depan jika tunnel harus bertahan di latar belakang. Always-on VPN juga menuntut onRevoke yang bersih, karena sistem memanggilnya saat pengguna mengubah setelan atau aplikasi lain mengambil alih.
Jalankan pemeriksaan berurutan dan berhenti pada kegagalan pertama. Pertama, cetak daftar alamat, rute, dan DNS yang diberikan ke builder. Kedua, pastikan establish() mengembalikan deskriptor dan soket transport terlindungi. Ketiga, minta satu tujuan di dalam prefiks yang disertakan dan satu di dalam prefiks yang dikecualikan. Keempat, periksa resolver mana yang benar-benar menjawab dengan menanyakan nama host yang hanya bisa diselesaikan resolver Anda.
Jabat tangan yang selesai tidak membuktikan satu pun langkah berikutnya. Setiap pemeriksaan mengisolasi satu lapisan, jadi lolos di langkah kedua tidak mengatakan apa pun tentang langkah keempat.
Referensi VpnService.Builder mencantumkan seluruh permukaan builder. Apa pun yang Anda butuhkan di luarnya adalah API lain, aplikasi lain, atau tumpukan ruang pengguna Anda sendiri, dan itu keputusan desain, bukan bug split tunnel.