addDnsServer Bad Address: apa yang sebenarnya divalidasi Android
Error Bad address dari addDnsServer adalah pemeriksaan format lokal, bukan gangguan DNS. Uraikan literal, buang loopback dan wildcard, lalu uji keterjangkauan.
Nilai sampai ke VpnService.Builder.addDnsServer sebagai string dan builder menolaknya di sana sebelum apa pun dibangun. Penolakan itu lokal: tidak ada resolver yang dihubungi, tidak ada tunnel, dan yang gagal adalah pemeriksaan format, bukan gangguan DNS.
Ini penting karena aplikasi VPN biasa punya tiga masukan DNS serupa: nilai di profil, nilai yang dikirim server saat konfigurasi, dan Private DNS di tingkat sistem. Hanya dua yang pertama mencapai panggilan ini.
Objek persiapan memeriksa argumen saat itu juga
VpnService.Builder bukan tunnel. Itu objek persiapan yang mengumpulkan alamat, rute, dan server, dan hanya establish() mengubah daftar itu menjadi ParcelFileDescriptor hidup dan antarmuka. Entri DNS diperiksa saat memanggil addDnsServer, bukan saat membangun.
Ada dua akibat: jejak tumpukan pendek dan sengaja tidak informatif, dan catch yang ceroboh meninggalkan tunnel dengan alamat serta rute tetapi tanpa resolver.
Builder menyimpan daftar apa pun yang Anda berikan; menambah server yang lolos pemeriksaan hanya membuktikan string terurai sebagai literal alamat saat pembangunan.
Apa yang boleh menjadi argumen
Parameternya alamat, bukan endpoint. Referensi Builder menerima literal IPv4 atau IPv6, dan penguraiannya hanya numerik: android.net.InetAddresses.parseNumericAddress adalah wajah publik parser itu sejak API 29.
Pemetaan praktisnya seperti ini:
| Input | Result | Reason |
|---|---|---|
10.8.0.1 | accepted | IPv4 literal |
2606:4700:4700::1111 | accepted | IPv6 literal |
dns.example.com | rejected | hostname; numeric parsing does not resolve it |
10.8.0.1:53 | rejected | port belongs to your own config model |
https://dns.example.com/dns-query | rejected | DoH URL, a different configuration path |
127.0.0.1 | rejected | loopback is not a remote resolver |
0.0.0.0 or :: | accepted by the builder | parses fine, but names no destination; only your own validator rejects it |
Versi memengaruhi cara Anda memvalidasi, bukan apa yang diterima builder. InetAddresses tersedia sejak API 29, jadi di Android 10 penguraian adalah satu panggilan; addDnsServer(InetAddress) bentuk asli, String tambahan yang lebih baru. Di bawah API 29 InetAddress.getByName akan menyelesaikan nama host dengan pencarian jaringan yang memblokir.
Loopback ditolak di sini, wildcard tidak
Klien VPN desktop sering mengarahkan stub resolver ke 127.0.0.1:53; menyalinnya ke profil Android memasukkan nilai yang menggambarkan mesin lokal, dan di sini tidak ada mesin lokal. Builder hanya menolak kasus ini: alamat null atau loopback melempar IllegalArgumentException.
Wildcard adalah kasus sebaliknya dan di sinilah sebagian besar pembacaan keliru. 0.0.0.0 dan :: bukan loopback, jadi builder menerimanya; tidak ada yang dilempar saat pembangunan. Antarmuka lalu naik dengan entri yang tidak menyebut tujuan. Menangkapnya tugas validator Anda: perlakukan isAnyLocalAddress sebagai masukan tidak valid.
Uraikan sekali di batas konfigurasi
Perbaikan murahnya adalah memvalidasi di tempat string masuk ke model Anda:
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 }
}
Hasil null menjadi kesalahan tingkat kolom dengan teks penyebabnya, dan hanya bentuk itu yang bisa ditindaklanjuti dukungan. Di jalur sukses objek langsung masuk ke builder.addDnsServer(address), dan tangkap IllegalArgumentException secara khusus, bukan Exception.
Jangan ganti dengan resolver publik saat nilai yang dimaksud ditolak; profil yang diam-diam memakai DNS publik tetap terhubung, tetapi nama internal berhenti diselesaikan.
Pastikan keterjangkauan setelah antarmuka ada
Penerimaan builder adalah titik pemeriksaan pertama, bukan terakhir. Setelah antarmuka naik, LinkProperties.getDnsServers() melaporkan apa yang benar-benar diterima jaringan, dan adb shell dumpsys connectivity mencetak properti yang sama dari luar.
Private DNS adalah kontrol terpisah dan tidak lewat argumen ini; nama host penyedia DoT milik pengaturan sistem, bukan bidang IP.
Sebelum mengubah parser profil lagi, catat string mentah dan sumbernya, pastikan itu literal bukan nama host, bukan loopback atau wildcard, lalu teruskan objek yang sudah diurai.