Android VPN: deskriptor VpnService berbanding peranti tun root
Aplikasi Android tidak boleh mencipta tun0 sendiri: apa yang dipulangkan establish(), di mana Binder berhenti dan JNI bermula, dan bila peranti yang dicipta root menang.
Dua laluan boleh meletakkan antara muka IP di hadapan aplikasi Android, dan kedua-duanya berakhir pada jenis deskriptor fail yang sama dengan pemilik yang sangat berbeza. Satu melalui VpnService dan buildernya, satu lagi melalui shell root dan /dev/net/tun. Setiap bacaan pada deskriptor itu ialah paket keluar dan setiap tulisan ialah paket masuk. Yang memisahkan laluan ialah siapa memegang dasar, siapa bertahan daripada naik taraf OS, dan berapa banyak jadual penghalaan yang anda boleh sentuh.
Ambil laluan platform melainkan anda boleh menamakan perkara khusus yang enggan dilakukannya. Penolakan itu biasanya TAP, tangkapan di luar set aplikasi anda sendiri, atau peraturan penghalaan yang tidak pernah didedahkan oleh rangka kerja.
Pada laluan platform anda mengisytiharkan niat dan bukan arahan. Anda membekalkan sekurang-kurangnya satu alamat, laluan yang dikehendaki, pelayan DNS dan senarai aplikasi pilihan, kemudian perkhidmatan sistem Android melakukan persediaan istimewa di bawah identitinya sendiri. Anda tidak pernah mengetahui nama antara muka terlebih dahulu, dan tidak pernah mengeluarkan ioctl yang menciptanya, dan itulah intinya.
Dua cara untuk mendapatkan antara muka IP
Laluan root ialah resipi yang lebih lama. Anda membuka /dev/net/tun, memanggil TUNSETIFF dengan IFF_TUN, menaikkan antara muka dengan ip link, dan menambah alamat serta laluan secara manual. Setiap langkah perlu anda lakukan dengan betul, termasuk yang akan dikendalikan oleh rangka kerja secara senyap. Satu laluan yang hilang menghasilkan laporan pengguna yang sama seperti pelayan mati.
Penyahpepijatan berbeza sama banyak dengan persediaan. Pada laluan root antara muka sama ada wujud atau ioctl anda memulangkan ralat yang boleh anda baca terus. Pada laluan platform antara muka hanya wujud selepas establish() berjaya, jadi kegagalan datang sebagai pengecualian atau deskriptor null dan bukannya nod peranti yang boleh anda korek.
Apa yang sebenarnya dipulangkan oleh establish()
Builder.establish() mempunyai tiga hasil dan ketiga-tiganya memerlukan tindak balas berbeza. Deskriptor bermakna antara muka telah dikonfigurasikan dan milik anda untuk digunakan, null bermakna permintaan tidak melepasi, dan pengecualian yang dilemparkan bermakna perkhidmatan sistem menolaknya. Menggabungkan null dan kejayaan ke dalam satu boolean ialah cara paling biasa aplikasi melaporkan terowong yang berfungsi yang tidak pernah membawa paket. Rujukan Builder menyatakan keperluan alamat, dan ia wajar dibaca sebelum anda menyalahkan pelayan.
Arah ditentukan oleh deskriptor, bukan protokol anda. Bacaan memberi anda paket keluar yang telah dipilih untuk VPN, dan tulisan menyuntik paket masuk kembali ke antara muka. Pengangkutan anda sendiri yang menyulitkan, mengesahkan dan membingkaikan paket itu, kerana platform berhenti pada sempadan paket.
Binder ialah satu lompatan, JNI ialah paksi lain
Permintaan builder melintasi ke dalam sistem melalui Binder, dan di suatu tempat di bawahnya platform memanggil kod asli untuk mengkonfigurasi peranti. Itu dua mekanisme berbeza yang ditindan dalam satu panggilan. Binder ialah komunikasi antara proses antara aplikasi anda dan perkhidmatan sistem, manakala JNI ialah jambatan Java-ke-asli di dalam sesuatu proses. Membaca pepohon AOSP Android 9 membuat tindanan kelihatan literal, dan wajar diingat bahawa nama kelas dalaman dan laluan fail berubah antara keluaran.
Apa yang tidak berubah ialah kontrak yang boleh anda harapkan: builder, deskriptor yang dipulangkannya, dan kitaran hayat perkhidmatan di sekelilingnya. Tiada aplikasi patut memanggil transaksi perkhidmatan dalaman atau mencipta semula nod peranti untuk mendapatkan antara muka. Jika kod anda bergantung pada nama kelas AOSP, ia akan rosak pada keluaran yang tidak anda uji.
TUN membawa IP, TAP membawa Ethernet
TUN ialah antara muka IP. TAP ialah antara muka Ethernet, dan perbezaan itu menentukan sama ada penjambatan mungkin. Peranti TUN tidak mempunyai alamat MAC dan tiada pengepala Ethernet untuk diteruskan, jadi klien TUN tidak boleh menyertai jambatan lapisan 2, walau bagaimana pun laluan ditulis.
VpnService memberi anda varian IP, iaitu apa yang diperlukan oleh VPN peringkat aplikasi dan apa yang boleh disokongnya tanpa tindanan Ethernet mentah. Jika reka bentuk anda memerlukan penjambatan, laluan platform tidak akan membawa anda ke sana, dan laluan root hanya akan berbuat demikian jika kernel dan kebenaran peranti bekerjasama.
Di mana laluan root masih menang
Tiga kerja menolak orang keluar dari laluan platform. Penjambatan lapisan 2 memerlukan TAP. Menangkap trafik daripada aplikasi di luar skop VPN anda memerlukan keterlihatan yang tidak diberikan rangka kerja. Penghalaan dasar yang tidak pernah didedahkan builder, seperti jadual berasingan untuk satu destinasi, memerlukan peraturan yang anda pasang sendiri.
Kosnya datang kemudian: bootloader yang tidak dikunci atau imej yang di-root, dasar SELinux yang beralih di bawah anda, dan peranti yang berhenti berkelakuan betul selepas OTA. Resipi yang di-root juga tidak boleh dihantar melalui pengedaran aplikasi biasa, jadi kebanyakan pasukan menyimpannya sebagai laluan diagnostik dalaman dan bukannya produk.
Memilih antara keduanya
Mulakan dengan antara muka yang anda perlukan, bukan kod yang anda ada. Jika jawapannya ialah peranti TUN dengan laluan terhad kepada aplikasi, laluan platform lebih pendek dan bertahan daripada naik taraf. Jika jawapannya melibatkan bingkai Ethernet, tangkapan aplikasi lain, atau peraturan penghalaan yang ditolak builder, anda berada di wilayah root dan patut menganggarkannya sebagai kos operasi.
Semakan yang menyelesaikan kebanyakan perdebatan: bina laluan platform dahulu, rekod hasil establish() secara berasingan daripada keadaan sesi jauh, dan sahkan satu paket pada setiap arah. Hanya selepas itu tentukan sama ada keupayaan yang hilang mewajarkan meninggalkan laluan yang disokong.