VpnService Android tiada laluan pengecualian, dan berapa banyak pun bacaan API tidak akan menghasilkannya. Builder.addRoute() sentiasa hanya menambah destinasi ke terowong, jadi mengekalkan segmen LAN secara setempat bermakna segmen itu tidak pernah masuk ke senarai laluan. Yang anda tambah sebaliknya ialah pelengkap segmen itu, dinyatakan sebagai awalan biasa. Kerjanya ialah aritmetik, dan ia gagal di beberapa tempat yang boleh dijangka.

Lakukan semakan di bawah mengikut urutan. Setiap satu menganggap yang sebelumnya bertahan, jadi berhenti pada kegagalan pertama daripada menampal gejala di bawah.

Mulakan dengan apa yang didakwa oleh terowong

Menghantar 0.0.0.0/0 ke addRoute() memberitahu Android bahawa setiap destinasi IPv4 milik terowong. Itulah sebabnya begitu banyak klien kelihatan tidak mampu melakukan terowong berpecah: mereka mendakwa seluruh ruang alamat dan kemudian cuba menolaknya. Tiada apa dalam pembina yang menolak. Laluan yang lebih khusus untuk rangkaian yang anda mahu kekalkan setempat melakukan sebaliknya daripada yang dijangka, kerana awalan terpanjang yang sepadan menang dan rangkaian itu mendarat di dalam terowong.

Senarai laluan ialah seluruh dasar. Tiada veto setiap destinasi di belakangnya, dan rujukan Builder tidak menyenaraikan sebarang kaedah yang akan menyediakannya.

Jana pelengkap daripada aritmetik awalan

Pelengkap bagi awalan yang dikecualikan ialah set adik-beradiknya di laluan menuju akar pokok alamat. Untuk setiap panjang dari 1 hingga panjang yang dikecualikan, ambil blok panjang itu yang mengandungi awalan anda, balikkan bit tepat di bawah awalan dan keluarkan hasilnya. Ia kedengaran abstrak sehingga anda menjalankannya.

Mengecualikan 10.0.0.0/8 menghasilkan lapan laluan: 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. Tiada satu pun mengandungi 10.0.0.0/8, dan bersama-sama ia meliputi segala yang lain.

Lapan baris menjana senarai itu untuk mana-mana awalan:

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))

Bilangan laluan sama dengan panjang awalan, dan itulah nombor yang perlu diperhatikan. Satu pengecualian /21 menelan 21 laluan dan kekal boleh dibaca. Sepuluh pengecualian boleh menelan beberapa ratus, yang biasanya bermakna reka bentuk rangkaian perlu diubah, bukan penjana.

Semak keluarga alamat dan skop terowong

Laluan IPv4 tidak melakukan apa-apa untuk IPv6. Jika terowong mendakwa alamat IPv6, awalan LAN IPv6 memerlukan pelengkapnya sendiri, dijana dengan aritmetik yang sama atas 128 bit. Punca biasa pengecualian berfungsi di atas kertas sementara peranti setempat kekal tidak dapat dicapai ialah ini: hos dicapai melalui IPv6, dan hanya IPv4 telah dipotong. Semak kedua-dua keluarga sebelum membuat kesimpulan bahawa matematik itu salah.

Skop ialah separuh lagi semakan. Android menjejak penghalaan setiap rangkaian dan setiap aplikasi, jadi awalan yang sama boleh melalui terowong untuk satu aplikasi dan terus untuk yang lain bergantung pada cara VPN diisytiharkan.

Letakkan peraturan DNS dan setiap aplikasi di tempat yang sepatutnya

Pengecualian laluan tidak mengubah penyelesai. Nama hos setempat masih boleh dijawab oleh pelayan DNS terowong sementara alamatnya dihalakan secara setempat, dan ketidakpadanan muncul sebagai nama yang menyelesaikan kepada sesuatu yang LAN tidak dapat capai. Tetapkan dasar penyelesai secara sengaja, dengan domain carian atau peraturan DNS berpecah. Jangan anggap laluan telah memutuskannya untuk anda.

addDisallowedApplication() menyimpan satu aplikasi di luar terowong; ia tidak menyimpan destinasi di luar untuk aplikasi yang kekal di dalam. protect() membenarkan soket yang anda buka memintas terowong, dan begitulah terowong mengelak daripada menangkap pengangkutannya sendiri. Kedua-duanya tidak menggantikan pelengkap.

Sahkan dengan carian, bukan dengan senarai anda sendiri

Setelah terowong naik, tanya peranti ke mana sesuatu alamat benar-benar pergi daripada membaca semula senarai yang anda bina. Pada binaan yang menyertakan iproute2, adb shell ip route get 10.1.2.3 menamakan antara muka yang dipilih oleh kernel. Jalankan arahan yang sama untuk alamat awam dan sahkan ia menamakan terowong.

Dua lagi semakan menutup gelung. Selesaikan dahulu nama setempat, kemudian jalankan adb shell ip route get pada alamat yang diselesaikan dan tuntut antara muka yang bukan terowong, kerana adb shell ping -c 1 host.lan yang berjaya membuktikan kebolehtcapaian dan bukan laluan. Ulang ujian keluarga alamat pada IPv6 jika terowong mendakwanya. Uji pada tahap API yang anda hantar, kerana cara rangkaian VPN didedahkan kepada jadual penghalaan telah berubah antara keluaran. Senarai laluan yang kelihatan betul di atas kertas dan carian yang memilih antara muka salah ialah pepijat yang berbeza, dan hanya yang kedua sampai kepada pengguna.

Jika senarai pengecualian terus membesar, berhenti menjana laluan dan nomborkan semula rangkaian jauh atau sempitkan terowong sebaliknya. Penjana ialah jalan pintas untuk API yang hilang, bukan alat reka bentuk.

Artikel berkaitan