Mengapa Terowong Android VpnService Anda Berhenti Menghala Trafik
Empat kepercayaan tentang VpnService yang runtuh dalam pengeluaran: satu terowong pada satu masa, skop laluan, senarai setiap aplikasi dan urutan penutupan, dengan semakan bagi setiap satu.
Empat andaian tentang Android VpnService pernah menghabiskan sebahagian besar seminggu saya, dan tiada satu pun tentang protokol rangkaian. Aplikasi berkata bersambung, ikon kunci duduk di bar status, dan trafik masih keluar melalui laluan biasa. Saya terus menganggap terowong itu milik saya sehingga saya menghentikannya, bahawa laluan yang saya tambah ialah laluan yang saya dapat, dan bahawa memanggil stopSelf akan menurunkan antara muka bersamanya. Perkhidmatan itu berkelakuan tepat seperti yang didokumenkan. Model mental sayalah bahagian yang rosak.
Mitos 1: terowong kekal sehingga anda menghentikannya
Android memegang satu antara muka VPN pada satu masa. Tubuhkan yang kedua dan platform tidak meletakkannya dalam baris gilir: ia menyahaktifkan milik anda dan memanggil onRevoke() pada perkhidmatan anda. Jika kod anda hanya membongkar apabila butang ditekan, pengambilalihan meninggalkan deskriptor dan benang pembaca, dan permulaan seterusnya mewarisi kedua-duanya.
Rujukan VpnService menyatakan dengan jelas bahawa prepare() mengembalikan null apabila aplikasi sudah mempunyai persetujuan, dan Intent apabila pengguna masih perlu meluluskannya. Persetujuan itu hanya tiba jika anda menyampaikan hasrat melalui startActivityForResult(). Langkau penyerahan itu dan establish() mengembalikan null, yang dibaca sebagai kegagalan senyap dan bukan masalah kebenaran.
Always-on VPN menambah pemilik yang anda tidak kawal. Sistem boleh mengembalikan terowong selepas but atau selepas ranap, dan ia mendahului aplikasi VPN yang dimulakan pengguna secara manual. Bina onRevoke() supaya ia menutup sumber yang sama seperti penutupan biasa anda, dan anggap ia sebagai laluan henti utama dan bukan kes sudut. Pengguna bertukar aplikasi VPN; kod anda mesti tahan dengan itu.
Mitos 2: addRoute menangkap tepat apa yang anda senaraikan
Laluan menentukan paket mana yang sampai ke deskriptor anda, dan ralat skop menghasilkan kes menjengkelkan apabila sebahagian trafik melalui terowong dan sebahagian tidak. addRoute("0.0.0.0", 0) mengambil seluruh IPv4, bermakna setiap paket kini tiba kepada anda dan mesti dihantar atau digugurkan. /32 untuk satu hos hampir tidak mengambil apa-apa, dan semua yang lain berkelakuan seperti VPN anda tidak pernah dipasang.
Keluarga dikira secara berasingan. Laluan IPv4 sahaja membiarkan IPv6 pada rangkaian asas, jadi hos dwi-tindanan masih boleh menyelesaikan nama dan bersambung di luar terowong sambil kelihatan bersambung sempurna di dalamnya.
DNS mengikut logik yang sama. Pelayan yang ditambah dengan addDnsServer() pergi kepada aplikasi yang menggunakan penyelesai terowong, manakala aplikasi dengan titik akhir DNS tersulit sendiri atau Private DNS yang ditetapkan pada nama hos mengabaikannya sepenuhnya. Apabila nama gagal untuk satu aplikasi dan berjaya untuk yang lain, periksa laluan penyelesai sebelum menyentuh satu laluan pun.
Mitos 3: peraturan setiap aplikasi ialah suis yang boleh anda balik ke mana-mana arah
addDisallowedApplication() dan addAllowedApplication() ialah dua mod, bukan dua penapis. Pembina menerima satu model bagi setiap terowong, jadi aplikasi yang mahu segala-galanya kecuali tiga pakej menggunakan senarai larangan dan membiarkan senarai kebenaran kosong. Kedua-duanya saling eksklusif, dan orang mengetahuinya semasa runtime selepas skrin tetapan sudah menawarkan kedua-dua togol.
Keterlihatan pakej juga menggigit. Pada Android 11 dan seterusnya anda hanya boleh menamakan pakej yang anda lihat, jadi senarai pengecualian yang dibina daripada pertanyaan yang aplikasi anda tidak dibenarkan jalankan akan terlepas entri secara senyap. Bandingkan senarai itu dengan apa yang sebenarnya dikembalikan pengurus pakej sebelum mempercayainya.
Profil kerja membahagikan khalayak sekali lagi. Terowong yang dimulakan dalam profil meliputi profil itu dan aplikasinya, manakala bahagian peribadi mengekalkan sambungannya sendiri. Itu keputusan skop yang dibuat sekali, dan tempatnya di antara muka, bukan dalam tiket sokongan.
Mitos 4: stopSelf menamatkan terowong
Tidak. Antara muka hidup dalam deskriptor yang dikembalikan establish(), dan objek perkhidmatan boleh mati sementara deskriptor itu kekal terbuka. Tutup ParcelFileDescriptor dahulu, lepaskan rujukan, sertai atau ganggu benang pembaca dan penulis, kemudian panggil stopForeground(true) dan stopSelf() dalam urutan itu.
Deskriptor yang terlupa ialah sebab biasa sesuatu perkhidmatan kelihatan terselamat daripada permintaan hentinya sendiri, dan ia menjelaskan laluan lapuk yang hidup lebih lama daripada aplikasi yang menambahnya.
Kesilapan sebaliknya sama biasa: menutup deskriptor semasa benang pembaca di tengah bacaan, kemudian membiarkan benang itu mengendalikan pengecualian yang tiada siapa tulis kodnya. Simpan urutan penutupan di satu tempat dan biarkan tindakan pengguna serta onRevoke() memanggilnya.
Apa yang perlu diperiksa apabila terowong kelihatan mati
Lindungi soket panjang hayat dengan protect() sebelum terowong naik. Tanpanya, pengangkutan terowong itu sendiri dihalakan semula ke dalam terowong dan sambungan tersekat dengan cara yang kelihatan tepat seperti kerosakan pelayan. Uji terhadap alamat yang hanya wujud di sebalik rangkaian jauh, bukan laman awam yang menjawab dari cache atau dari laluan asas.
Log hasil establish(), laluan, pakej yang dikecualikan dan sebab penutupan. Empat baris itu memisahkan masalah persetujuan daripada masalah skop dan daripada masalah kitaran hayat tanpa menangkap paket.