Nilai itu sampai ke VpnService.Builder.addDnsServer sebagai rentetan, dan pembina menolaknya di situ, sebelum apa-apa diwujudkan. Penolakan itu bersifat setempat: tiada penyelesai dihubungi, tiada terowong wujud, dan yang gagal ialah semakan format, bukannya gangguan DNS. Membaca surih itu sebagai konflik pengurusan kuasa atau masalah sebelah pelayan membawa anda ke lapisan yang salah. Bingkai itu menamakan argumen yang ditolak, bukan tetapan yang menghasilkannya.

Perkara itu penting kerana aplikasi VPN biasa mempunyai tiga input DNS yang kelihatan serupa: nilai yang ditaip atau diimport ke dalam profil, nilai yang ditolak pelayan semasa konfigurasi, dan Private DNS yang dikonfigurasikan pada peringkat sistem. Hanya dua yang pertama boleh sampai ke panggilan ini, dan biasanya hanya satu daripadanya menjadi punca rentetan tidak sah itu.

Objek persediaan menyemak argumen serta-merta

VpnService.Builder bukan terowong. Ia ialah objek persediaan yang mengumpul alamat, laluan dan pelayan, dan hanya establish() yang menukar senarai itu menjadi ParcelFileDescriptor yang hidup dan satu antara muka. Entri DNS disemak apabila anda memanggil addDnsServer, bukan apabila anda establish, sebab itulah pengecualian dilempar beberapa baris di atas kod yang sebenarnya anda nyahpepijat.

Dua akibat menyusul. Surih tindanan itu pendek dan tidak membantu secara sengaja: nilai itu tiba sebagai rentetan biasa, dan rangka kerja tidak dapat tahu sama ada ia datang daripada import QR, muatan pelayan atau pengatasan tetapan. Dan kerana segala-galanya dalam pembina bertahan daripada panggilan yang gagal itu, catch yang cuai di sekeliling keseluruhan pembinaan meninggalkan anda dengan terowong yang mempunyai alamat dan laluan tetapi tiada penyelesai sama sekali.

Pembina juga menyimpan apa sahaja senarai yang anda berikan. Menambah pelayan yang lulus semakan hanya membuktikan bahawa rentetan itu dihurai sebagai literal alamat semasa pembinaan, dan kes wildcard di bawah menunjukkan ia tidak membuktikan nilainya boleh digunakan.

Apa yang dibenarkan sebagai argumen

Parameter itu ialah alamat, bukan titik akhir. Rujukan Builder Android menerima literal IPv4 atau IPv6, dan penghuraian di belakangnya berangka semata-mata: android.net.InetAddresses.parseNumericAddress ialah muka awam penghurai itu dari API 29, dan panggilan rangka kerja sendiri mendarat pada rutin yang sama. Nama hos bukan alamat yang lambat di sini; ia rentetan yang tidak boleh dihurai, dan kegagalan huraian itulah yang muncul sebagai pengecualian alamat tidak sah.

Pemetaan praktikalnya kelihatan seperti ini:

InputResultReason
10.8.0.1acceptedIPv4 literal
2606:4700:4700::1111acceptedIPv6 literal
dns.example.comrejectedhostname; numeric parsing does not resolve it
10.8.0.1:53rejectedport belongs to your own config model
https://dns.example.com/dns-queryrejectedDoH URL, a different configuration path
127.0.0.1rejectedloopback is not a remote resolver
0.0.0.0 or ::accepted by the builderparses fine, but names no destination; only your own validator rejects it

Versi penting untuk cara anda mengesahkan, bukan untuk apa yang diterima pembina. InetAddresses tersedia dari API 29, jadi pada Android 10 dan seterusnya penghuraian literal ialah satu panggilan. Overload juga penting: addDnsServer(InetAddress) ialah bentuk asal, manakala overload String ialah tambahan yang lebih baharu, jadi sampel rentetan yang ditampal tidak akan dikompil dengan SDK lama. Di bawah API 29, pengganti yang jelas, InetAddress.getByName, akan dengan senang hati menyelesaikan nama hos dengan carian rangkaian yang menyekat, yang menukar salah taip menjadi permulaan yang lambat dan bukannya ralat yang jelas. Jika anda masih menghantar kepada versi tersebut, jalankan ujian literal yang eksplisit sebelum anda menyentuh API lama itu.

Loopback ditolak di sini, wildcard tidak

Klien VPN desktop sering menunjuk penyelesai stub mereka sendiri ke 127.0.0.1:53 dan membiarkan klien terowong memajukan pertanyaan. Menyalin susunan itu ke dalam profil Android mengimport nilai yang menerangkan mesin setempat, dan tiada mesin setempat untuk menyerahkannya di sini: penyelesai terowong mesti boleh dicapai di sebelah lain antara muka. Ini satu-satunya kes yang pembina sendiri tolak: alamat null atau loopback melempar IllegalArgumentException, seperti yang dinyatakan kontrak rujukan.

Wildcard ialah kes yang bertentangan, dan di situlah kebanyakan pembacaan pengecualian ini tersasar. 0.0.0.0 dan :: bukan loopback, jadi pembina menerimanya dan menambahnya seperti literal lain; tiada apa yang dilempar semasa pembinaan. Antara muka kemudian muncul dengan entri penyelesai yang tidak menamakan destinasi, dan kegagalan itu berpindah daripada pengecualian kepada pertanyaan yang senyap-senyap tiada ke mana. Menangkapnya ialah tugas pengesah anda, bukan rangka kerja: anggap isAnyLocalAddress sebagai input tidak sah dalam pembantu di bawah dan paparkannya sebagai ralat peringkat medan.

Hurai sekali pada sempadan konfigurasi

Pembetulan murah ialah mengesahkan di tempat rentetan memasuki model anda dan menghantar objek yang dihurai ke hadapan, supaya nilai yang dilihat pembina ialah nilai tepat yang anda sahkan:

import android.net.InetAddresses
import java.net.InetAddress

private fun dnsLiteralOrNull(raw: String?): InetAddress? {
    val text = raw?.trim().orEmpty()
    if (text.isEmpty()) return null
    val address = try {
        InetAddresses.parseNumericAddress(text)
    } catch (e: IllegalArgumentException) {
        return null
    }
    return address.takeUnless { it.isAnyLocalAddress || it.isLoopbackAddress }
}

Keputusan null kemudian menjadi ralat peringkat medan yang membawa teks yang menyalahi, iaitu satu-satunya bentuk yang boleh ditindaklanjuti oleh perbualan sokongan. Pada laluan kejayaan, objek yang dikembalikan terus masuk ke builder.addDnsServer(address), jadi nilai yang anda sahkan ialah nilai yang dihantar. Tangkap IllegalArgumentException secara khusus dan bukan Exception: tangkapan luas ke atas kod konfigurasi juga menelan pepijat penuding null dan keadaan yang anda mahu lihat dalam binaan nyahpepijat.

Jangan sekali-kali menggunakan penggantian penyelesai awam apabila nilai yang dimaksudkan ditolak. Profil yang senyap-senyap menukar kepada perkhidmatan DNS awam masih bersambung, tetapi nama dalaman berhenti diselesaikan dan setiap pertanyaan keluar melalui penyelesai yang tidak dipilih pengguna. Ralat yang kelihatan menelan satu balasan sokongan; penggantian senyap menelan kepercayaan bahawa terowong mengekalkan pertanyaan di tempat yang dikatakan profil.

Sahkan kebolehcapaian selepas antara muka wujud

Penerimaan oleh pembina ialah pusat pemeriksaan pertama, bukan yang terakhir. Setelah antara muka naik, LinkProperties.getDnsServers() melaporkan apa yang sebenarnya diterima rangkaian, dan di situlah ketidakserasian muncul: senarai kosong, entri lebih sedikit daripada yang anda tambah, atau keluarga yang tidak dibawa laluan anda. adb shell dumpsys connectivity mencetak sifat setiap rangkaian yang sama apabila anda perlu melihat dari luar aplikasi.

Private DNS ialah kawalan berasingan dan tidak melalui argumen ini. Nama hos penyedia DoT tergolong dalam tetapan sistem, atau dalam laluan penyelesai tersulit anda sendiri; ia bukan nilai untuk ditampal ke dalam medan IP, dan membetulkan tetapan Private DNS tidak akan menghapuskan pengecualian yang dilempar oleh pembina.

Sebelum anda mengubah penghurai profil sekali lagi, ikut susunan ini: log rentetan mentah dan puncanya, sahkan ia literal dan bukan nama hos atau URL, sahkan ia bukan loopback dan bukan wildcard, hantar objek yang dihurai dan bukannya menghurai semula teks, dan hanya kemudian bandingkan antara muka yang diwujudkan dengan nilai yang anda maksudkan.

Artikel berkaitan