addDnsServer Bad Address: hvad Android faktisk validerer
Bad address-fejlen fra addDnsServer er et lokalt formattjek, ikke et DNS-nedbrud. Parse literalet, frasortér loopback og wildcard, og test derefter rækkevidde.
Værdien nåede VpnService.Builder.addDnsServer som en streng, og builderen afviste den der, før noget blev etableret. Afvisningen er lokal: ingen resolver er kontaktet, der findes ingen tunnel, og det der fejlede er et formattjek, ikke et DNS-nedbrud.
Det betyder noget, fordi en typisk VPN-app har tre ens DNS-input: en værdi skrevet eller importeret i profilen, en værdi serveren sender under konfiguration, og Private DNS på systemniveau. Kun de to første kan nå dette kald.
Et forberedelsesobjekt tjekker argumentet med det samme
VpnService.Builder er ikke tunnelen. Det er et forberedelsesobjekt der samler adresser, ruter og servere, og kun establish() gør de lister til et levende ParcelFileDescriptor og et interface. DNS-posten tjekkes når du kalder addDnsServer, ikke når du etablerer.
Der følger to ting. Stack trace er kort og med vilje intetsigende: værdien kom som en almindelig streng. Og en skødesløs catch omkring hele bygget efterlader en tunnel med adresser og ruter men uden resolver.
Builderen beholder enhver liste, du giver den. At tilføje en server der består tjekket beviser kun, at strengen blev parset som adresseliteral ved byggetid.
Hvad argumentet må være
Parameteren er en adresse, ikke et endpoint. Builder-referencen tager et IPv4- eller IPv6-literal, og parsingen er kun numerisk: android.net.InetAddresses.parseNumericAddress er det offentlige ansigt af den parser fra API 29. Et værtsnavn er her ikke en langsom adresse men en streng der ikke kan parses.
Den praktiske mapping ser sådan ud:
| 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 |
Versionen betyder noget for hvordan du validerer, ikke for hvad builderen accepterer. InetAddresses findes fra API 29, så på Android 10 og nyere er parsingen et kald. addDnsServer(InetAddress) er den oprindelige form, String den nyere tilføjelse. Under API 29 vil InetAddress.getByName løse et værtsnavn med et blokerende netværksopslag og gøre en tastefejl til en langsom start.
Loopback afvises her, wildcard gør ikke
Desktop VPN-klienter peger ofte deres egen stub resolver på 127.0.0.1:53. Kopieres det til en Android-profil, importeres en værdi der beskriver den lokale maskine, og der er ingen lokal maskine her. Builderen afviser kun dette tilfælde: en null- eller loopback-adresse kaster IllegalArgumentException.
Et wildcard er det modsatte tilfælde, og her går de fleste læsninger galt. 0.0.0.0 og :: er ikke loopback, så builderen accepterer dem; intet kastes ved byggetid. Interfacet kommer så op med en post uden destination. At fange det er din validators opgave: behandl isAnyLocalAddress som ugyldigt input.
Parse én gang ved konfigurationsgrænsen
Den billige løsning er at validere hvor strengen kommer ind i din 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 }
}
Et null-resultat bliver en feltfejl med den skyldige tekst, og det er den eneste form support kan handle på. På succesvejen går objektet direkte i builder.addDnsServer(address). Fang IllegalArgumentException specifikt, ikke Exception: en bred fangst sluger også nul-pointer-fejl.
Erstat ikke med en offentlig resolver, når den tilsigtede værdi afvises. En profil der stille skifter til en offentlig DNS forbinder stadig, men interne navne holder op med at blive løst.
Bekræft rækkevidde når interfacet findes
Builderens accept er det første kontrolpunkt, ikke det sidste. Når interfacet er oppe, viser LinkProperties.getDnsServers() hvad netværket faktisk modtog. adb shell dumpsys connectivity udskriver de samme egenskaber udefra.
Private DNS er en separat kontrol og går ikke gennem dette argument. Et DoT-udbyderes værtsnavn hører til systemindstillingen, ikke til et IP-felt.
Før du ændrer profil-parseren igen, arbejd i denne rækkefølge: log den rå streng og dens kilde, bekræft at det er et literal og ikke et værtsnavn, at det hverken er loopback eller wildcard, og send det parsede objekt videre.