addDnsServer Bad Address: co Android skutečně ověřuje
Chyba Bad address z addDnsServer je místní kontrola formátu, ne výpadek DNS. Rozparsujte literál, vyřaďte loopback a wildcard a pak otestujte dostupnost.
Hodnota dorazila do VpnService.Builder.addDnsServer jako řetězec a builder ji tam odmítl dřív, než se cokoli ustavilo. Odmítnutí je místní: žádný resolver nebyl kontaktován, tunel neexistuje a selhala kontrola formátu, ne DNS.
Vadí to proto, že běžná VPN aplikace má tři podobné vstupy DNS: hodnotu zadanou či importovanou do profilu, hodnotu, kterou server pošle při konfiguraci, a Private DNS na úrovni systému. K tomuto volání se dostanou jen první dva.
Přípravný objekt kontroluje argument okamžitě
VpnService.Builder není tunel. Je to přípravný objekt, který sbírá adresy, trasy a servery, a jen establish() převede tyto seznamy na živý ParcelFileDescriptor a rozhraní. Záznam DNS se kontroluje při volání addDnsServer, ne při vytváření.
Platí dvě věci. Stopa je krátká a záměrně nic neříká: hodnota přišla jako prostý řetězec. A nedbalý catch kolem celé stavby nechá tunel s adresami a trasami, ale bez resolveru.
Builder si podrží každý seznam, který mu dáte. Přidání serveru, který projde kontrolou, dokazuje jen to, že se řetězec při stavbě rozparsoval jako literál adresy.
Co argumentem může být
Parametr je adresa, ne endpoint. Reference Builder bere literál IPv4 nebo IPv6 a parsování je pouze číselné: android.net.InetAddresses.parseNumericAddress je veřejná tvář tohoto parseru od API 29. Název hostitele tu není pomalá adresa, ale neparsovatelný řetězec.
Praktické mapování vypadá takto:
| 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 |
Verze ovlivňuje, jak validujete, ne co builder přijme. InetAddresses je dostupný od API 29, takže na Androidu 10 a novějším je parsování jedno volání. addDnsServer(InetAddress) je původní forma, String novější přídavek. Pod API 29 InetAddress.getByName rád vyřeší název hostitele blokujícím síťovým dotazem a překlep se změní v pomalý start.
Loopback se zde odmítá, wildcard ne
Desktopoví VPN klienti často míří svůj stub resolver na 127.0.0.1:53. Zkopírování do profilu Android vnese hodnotu popisující místní stroj, jenže místní stroj tu není. Builder odmítá jen tento případ: null nebo loopback adresa vyhodí IllegalArgumentException.
Wildcard je opačný případ a právě zde se většina čtení této chyby mýlí. 0.0.0.0 a :: nejsou loopback, takže je builder přijme; při stavbě se nic nevyhodí. Rozhraní pak nastoupí se záznamem, který neuvádí cíl. Zachytit to je práce vašeho validátoru: berte isAnyLocalAddress jako neplatný vstup.
Parsujte jednou na hranici konfigurace
Levná oprava je validovat tam, kde řetězec vstupuje do vašeho modelu:
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 výsledek se stane chybou na úrovni pole s viným textem a to je jediná forma, na kterou podpora může reagovat. V úspěšné cestě jde objekt přímo do builder.addDnsServer(address). Chytejte IllegalArgumentException konkrétně, ne Exception: široký catch spolkne i chyby nulového ukazatele.
Nenahrazujte veřejným resolverem, když je zamýšlená hodnota odmítnuta. Profil, který tiše dosadí veřejné DNS, se připojí, ale interní jména přestanou fungovat.
Po vzniku rozhraní ověřte dostupnost
Přijetí builderem je první kontrolní bod, ne poslední. Po nastartování rozhraní LinkProperties.getDnsServers() ukáže, co síť skutečně dostala. adb shell dumpsys connectivity vypíše stejné vlastnosti zvenčí.
Private DNS je samostatné nastavení a tímto argumentem neprochází. Název hostitele DoT poskytovatele patří do systémového nastavení, ne do pole IP.
Než znovu změníte parser profilu, jděte v tomto pořadí: zalogujte surový řetězec a jeho zdroj, potvrďte, že jde o literál a ne název hostitele, že není loopback ani wildcard, a předejte rozparsovaný objekt.