VpnService di Android tidak punya rute pengecualian, dan berapa pun banyaknya membaca API tidak akan menghasilkannya. Builder.addRoute() hanya menambahkan tujuan ke terowongan, jadi mempertahankan segmen LAN tetap lokal berarti segmen itu tidak pernah masuk daftar rute sama sekali. Yang Anda tambahkan sebagai gantinya adalah komplemen segmen tersebut, dinyatakan sebagai prefiks biasa. Pekerjaannya adalah aritmetika, dan ia gagal di beberapa tempat yang bisa diprediksi.

Kerjakan pemeriksaan di bawah secara berurutan. Masing-masing mengasumsikan yang sebelumnya bertahan, jadi berhentilah pada kegagalan pertama alih-alih menambal gejala di bawahnya.

Mulai dari apa yang diklaim terowongan

Memberikan 0.0.0.0/0 ke addRoute() memberi tahu Android bahwa setiap tujuan IPv4 milik terowongan. Itulah sebabnya begitu banyak klien tampak tidak mampu melakukan tunel terpisah: mereka mengklaim seluruh ruang alamat lalu mencoba menguranginya. Tidak ada yang mengurangi di dalam pembangun. Rute yang lebih spesifik untuk jaringan yang ingin Anda pertahankan secara lokal melakukan kebalikan dari yang diharapkan, karena prefiks terpanjang yang cocok menang dan jaringan itu mendarat di dalam terowongan.

Daftar rute adalah seluruh kebijakan. Tidak ada veto per tujuan di belakangnya, dan referensi Builder tidak mencantumkan metode yang akan menyediakannya.

Hasilkan komplemen dari aritmetika prefiks

Komplemen prefiks yang dikecualikan adalah himpunan saudaranya di jalur menuju akar pohon alamat. Untuk setiap panjang dari 1 hingga panjang yang dikecualikan, ambil blok dengan panjang itu yang memuat prefiks Anda, balik bit tepat di bawah prefiks, dan keluarkan hasilnya. Kedengarannya abstrak sampai Anda menjalankannya.

Mengecualikan 10.0.0.0/8 menghasilkan delapan rute: 128.0.0.0/1, 64.0.0.0/2, 32.0.0.0/3, 16.0.0.0/4, 0.0.0.0/5, 12.0.0.0/6, 8.0.0.0/7, 11.0.0.0/8. Tidak satu pun memuat 10.0.0.0/8, dan bersama-sama mereka mencakup semua yang lain.

Delapan baris menghasilkan daftar itu untuk prefiks apa pun:

import ipaddress
def complement(addr, length):
    base = int(ipaddress.IPv4Address(addr))
    out = []
    for k in range(1, length + 1):
        block = base & (0xFFFFFFFF << (32 - k))
        sibling = block ^ (1 << (32 - k))
        out.append(f"{ipaddress.IPv4Address(sibling)}/{k}")
    return out
print(complement("10.0.0.0", 8))

Jumlah rute sama dengan panjang prefiks, dan itulah angka yang perlu diperhatikan. Satu pengecualian /21 memakan 21 rute dan tetap terbaca. Sepuluh pengecualian bisa memakan beberapa ratus, yang biasanya berarti desain jaringan yang perlu diubah, bukan generatornya.

Periksa keluarga alamat dan cakupan terowongan

Rute IPv4 tidak melakukan apa pun untuk IPv6. Jika terowongan mengklaim alamat IPv6, prefiks LAN IPv6 memerlukan komplemennya sendiri, dihasilkan dengan aritmetika yang sama atas 128 bit. Penyebab umum pengecualian bekerja di atas kertas sementara perangkat lokal tetap tak terjangkau adalah ini: host dijangkau melalui IPv6, dan hanya IPv4 yang dipotong. Periksa kedua keluarga sebelum menyimpulkan bahwa matematikanya salah.

Cakupan adalah separuh lain dari pemeriksaan. Android melacak perutean per jaringan dan per aplikasi, jadi prefiks yang sama bisa melalui terowongan untuk satu aplikasi dan langsung untuk aplikasi lain tergantung bagaimana VPN dideklarasikan.

Tempatkan aturan DNS dan per aplikasi di tempat yang seharusnya

Pengecualian rute tidak mengubah resolver. Nama host lokal masih bisa dijawab oleh server DNS terowongan sementara alamatnya dirutekan secara lokal, dan ketidakcocokan muncul sebagai nama yang me-resolve ke sesuatu yang tidak dapat dijangkau LAN. Tetapkan kebijakan resolver dengan sengaja, dengan domain pencarian atau aturan DNS terpisah. Jangan menganggap rute sudah memutuskannya untuk Anda.

addDisallowedApplication() menjaga satu aplikasi di luar terowongan; ia tidak menjaga tujuan tetap di luar bagi aplikasi yang tetap di dalam. protect() memungkinkan soket yang Anda buka melewati terowongan, dan begitulah terowongan menghindari menangkap transportnya sendiri. Keduanya tidak menggantikan komplemen.

Verifikasi dengan pencarian, bukan dengan daftar Anda sendiri

Setelah terowongan menyala, tanyakan kepada perangkat ke mana sebuah alamat benar-benar pergi alih-alih membaca ulang daftar yang Anda bangun. Pada build yang menyertakan iproute2, adb shell ip route get 10.1.2.3 menyebut antarmuka yang dipilih kernel. Jalankan perintah yang sama untuk alamat publik dan pastikan ia menyebut terowongan.

Dua pemeriksaan lagi menutup lingkaran. Selesaikan dulu nama lokal, lalu jalankan adb shell ip route get pada alamat yang sudah di-resolve dan tuntut antarmuka yang bukan terowongan, karena adb shell ping -c 1 host.lan yang berhasil membuktikan keterjangkauan, bukan jalurnya. Ulangi uji keluarga alamat pada IPv6 jika terowongan mengklaimnya. Uji pada level API yang Anda kirimkan, karena cara jaringan VPN diekspos ke tabel perutean telah berubah antar rilis. Daftar rute yang terlihat benar di atas kertas dan pencarian yang memilih antarmuka salah adalah bug yang berbeda, dan hanya yang kedua sampai ke pengguna.

Jika daftar pengecualian terus bertambah, berhenti menghasilkan rute dan nomori ulang jaringan jarak jauh atau persempit terowongan sebagai gantinya. Generator adalah jalan pintas untuk API yang hilang, bukan alat desain.

Artikel terkait