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:

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

Artikel terkait