Tunnel menyala, ikon kunci muncul, dan setiap permintaan di browser tetap kehabisan waktu. Di aplikasi VpnService, kombinasi itu biasanya berarti loop baca masih hidup dan separuh keluaran rusak, bukan bahwa server menolak jabat tangan. Android sudah membuat antarmuka, memindahkan lalu lintas aplikasi ke dalamnya, dan menyerahkan deskriptor berkas kepada proses Anda. Semua setelah deskriptor itu adalah kode Anda, dan satu panggilan yang hilang cukup untuk menelan seluruh aliran.

Apa yang sistem berikan dan apa yang tidak

establish() mengembalikan ParcelFileDescriptor untuk antarmuka tun. Sistem menulis paket IP keluar ke dalamnya dan mengharapkan proses Anda membacanya, memutuskan ke mana perginya, dan mengirimnya melalui socket biasa. Tidak ada penerusan kernel di bawahnya, jadi ketika tidak ada yang membaca, paket dibuang dan penghitung antarmuka tidak pernah bergerak.

Mulailah dengan menghitung, bukan menebak. Tambah penghitung pada setiap pembacaan yang berhasil dan catat tujuan serta protokol beberapa paket pertama. Jika penghitung tetap nol sementara lalu lintas dihasilkan, kesalahan ada di atas kode penerusan Anda, yang mempersempit pencarian ke konfigurasi builder dan mode baca. Jika penghitung bergerak dan browser tetap gagal, kesalahan ada di jalur kirim atau jalur kembali.

Pembacaan blocking menentukan apakah loop bergerak

setBlocking(true) menempatkan deskriptor dalam mode blocking dan sudah ada sejak API 21. Pada rilis lama, solusinya adalah pembantu JNI kecil yang membersihkan O_NONBLOCK dengan fcntl pada deskriptor mentah. Mode ini penting karena menentukan apa yang boleh dilakukan pembacaan tanpa paket tertunda.

Dalam mode non-blocking, pembacaan itu langsung kembali dengan nilai negatif, dan loop yang mengabaikan hasilnya memutar inti tanpa memindahkan lalu lintas sama sekali. Dalam mode blocking, pembacaan menunggu, yang diinginkan thread penerusan, tetapi thread itu lalu tidak bisa lagi diinterupsi. Menutup deskriptor adalah cara yang didukung untuk membuat pembacaan gagal agar loop bisa keluar. Membunuh proses atau menginterupsi thread malah bisa meninggalkan start berikutnya dengan deskriptor yang tidak lagi cocok dengan antarmuka.

Bacalah satu paket per panggilan ke buffer seukuran MTU antarmuka, dan perlakukan nol atau hasil negatif sebagai kesalahan, bukan paket kosong.

protect() sebelum pengiriman pertama, bukan sesudahnya

Setiap socket yang membawa paket terenkapsulasi harus dilindungi lebih dulu. Socket yang tidak terlindungi mengikuti aturan perutean yang baru saja Anda pasang, sehingga paketnya kembali ke tunnel dan loop baca Anda sendiri mengambilnya lagi. Tunnel lalu meneruskan keluarannya sendiri tanpa henti, dan dari luar tampak seperti koneksi yang terbuka dan tidak pernah menjawab.

Urutannya protect, lalu connect atau bind, lalu send. Memanggil protect setelah connect bisa meninggalkan keputusan rute sebelumnya, sehingga socket terus memakai jalur yang salah meskipun panggilan mengembalikan true. Periksa boolean yang dikembalikan dan gagal dengan lantang saat false, alih-alih masuk ke pengiriman yang tidak mungkin bekerja.

Kesalahan izin dari sendto pada socket yang tidak pernah menyentuh protect() adalah tanda biasa kesalahan ini.

Contoh UDP tidak membawa HTTPS

Contoh penerusan yang disalin sebagian besar tutorial hanya menangani UDP. TCP membutuhkan jabat tangan, nomor urut, pengakuan, dan pengiriman ulang, dan loop yang menyalin muatan antara deskriptor tun dan socket tidak menyediakan satu pun. Karena itu kueri DNS lewat UDP bisa lolos sementara setiap permintaan HTTPS tersendat. Jalankan tumpukan TCP di ruang pengguna atau serahkan koneksi ke proxy lokal yang mengakhiri TCP di perangkat dan meneruskan aliran.

Uji pemisahan sebelum menulis ulang apa pun: kirim satu kueri UDP dan buka satu koneksi TCP ke host yang sama. Jika jalur UDP bekerja dan jalur TCP macet pada SYN pertama, kode penerusan yang bermasalah, bukan server. Perbandingan tunggal itu menghemat banyak waktu membaca log server yang tidak berisi apa pun yang berguna.

Daftar rute dan filter per aplikasi membatasi apa yang Anda lihat

addAddress, addRoute, dan addDns menentukan apa yang dikirim sistem ke tunnel. Tujuan di luar daftar rute tidak pernah mencapai deskriptor Anda, jadi tidak ada jumlah logika penerusan yang bisa memperbaikinya. Catat panggilan builder yang Anda lakukan dan bandingkan dengan alamat yang benar-benar coba dijangkau aplikasi.

Pemilihan per aplikasi punya batas bawahnya sendiri: addAllowedApplication dan addDisallowedApplication memerlukan API 21, dan pada rilis lama metode itu tidak ada sama sekali. Rancangan yang memfilter per paket tidak bisa berjalan di sana.

Membangun ulang meninggalkan deskriptor usang

Memanggil establish() lagi mengembalikan deskriptor baru dan menutup antarmuka sebelumnya. Deskriptor lama kini tidak valid, sehingga thread yang masih terblokir di sana mendapat kesalahan, dan socket yang dibuat untuk jalur lama mungkin perlu diganti. onRevoke adalah callback untuk tunnel yang diambil pengguna atau sistem. Menjaga siklus hidup pada satu pemilik, yang menghentikan pembaca sebelum membangun ulang, mencegah sebagian besar keadaan terhubung-tetapi-mati.

Baca setiap kesalahan menurut langkahnya. Reset koneksi dari recvfrom berarti ujung jauh menutup saat kode Anda masih membaca. Kesalahan alamat buruk menunjuk ke penguraian header atau bug offset, bukan ke jaringan.

Urutan kerja: mode blocking aktif, satu paket per pembacaan, header diurai, socket dilindungi, muatan dikirim, penutupan bersih saat serah terima, dan daftar rute yang mencakup tujuan.

Artikel terkait