addDnsServer Bad Address: šta Android zaista provjerava
Greška Bad address iz addDnsServer je lokalna provjera formata, a ne DNS ispad. Raščlanite literal, uklonite loopback i wildcard, pa testirajte dostupnost.
Vrijednost je stigla u VpnService.Builder.addDnsServer kao string i builder ju je tamo odbio prije nego što je išta uspostavljeno. To odbijanje je lokalno: nijedan resolver nije kontaktiran, tunela nema, a ono što je palo je provjera formata, ne DNS.
To je važno jer tipična VPN aplikacija ima tri slična DNS ulaza: vrijednost u profilu, vrijednost koju server pošalje i Private DNS na nivou sistema. Samo prva dva stižu do ovog poziva.
Pripremni objekat provjerava argument odmah
VpnService.Builder nije tunel. To je pripremni objekat koji skuplja adrese, rute i servere, a samo establish() pretvara te liste u živi ParcelFileDescriptor i interfejs. DNS unos se provjerava pri pozivu addDnsServer.
Dvije posljedice: trag je kratak i namjerno neinformativan, a nepažljiv catch ostavlja tunel bez resolvera.
Builder čuva svaku listu; dodavanje servera koji prođe provjeru dokazuje samo da je string raščlanjen kao adresni literal pri gradnji.
Šta argument smije biti
Parametar je adresa, ne endpoint. Referenca Builder prima literal IPv4 ili IPv6, a raščlanjivanje je samo numeričko: android.net.InetAddresses.parseNumericAddress je javno lice tog parsera od API 29.
Praktično mapiranje izgleda ovako:
| 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 |
Verzija utiče na način provjere, ne na ono što builder prima. InetAddresses je dostupan od API 29, pa je na Androidu 10 raščlanjivanje jedan poziv; addDnsServer(InetAddress) je izvorna forma, String noviji dodatak. Ispod API 29 InetAddress.getByName će raščlaniti ime hosta blokirajućom mrežnom pretragom.
Loopback se ovdje odbija, wildcard ne
Desktop VPN klijenti često usmjeravaju svoj stub resolver na 127.0.0.1:53; kopiranje toga u Android profil unosi vrijednost koja opisuje lokalnu mašinu, a lokalne mašine ovdje nema. Builder odbija samo taj slučaj: null ili loopback adresa baca IllegalArgumentException.
Wildcard je suprotan slučaj i tu se većina čitanja ove greške zabuni. 0.0.0.0 i :: nisu loopback, pa ih builder prihvata; pri gradnji se ništa ne baca. Interfejs se onda diže s unosom koji ne imenuje odredište. Hvatanje toga je posao vašeg validatora: tretirajte isAnyLocalAddress kao nevažeći ulaz.
Raščlanite jednom na granici konfiguracije
Jeftino rješenje je validirati tamo gdje string ulazi u vaš model:
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 }
}
Null rezultat postaje greška na nivou polja s krivim tekstom i to je jedini oblik na koji podrška može djelovati. Na putu uspjeha objekat ide pravo u builder.addDnsServer(address), a IllegalArgumentException hvatajte konkretno, ne Exception.
Ne zamjenjujte javnim resolverom kad je željena vrijednost odbijena; profil koji tiho ubaci javni DNS se povezuje, ali interna imena prestaju da se razrješavaju.
Potvrdite dostupnost nakon što interfejs postoji
Prihvatanje od buildera je prva kontrolna tačka, ne posljednja. Nakon dizanja interfejsa LinkProperties.getDnsServers() pokazuje šta je mreža stvarno primila, a adb shell dumpsys connectivity ispisuje ista svojstva spolja.
Private DNS je odvojena kontrola i ne prolazi kroz ovaj argument; ime hosta DoT provajdera pripada sistemskom podešavanju, ne polju IP.
Prije nego opet promijenite parser profila, zabilježite sirovi string i njegov izvor, potvrdite da je literal a ne ime hosta, da nije loopback ni wildcard, i proslijedite raščlanjeni objekat.