addDnsServer Bad Address: que valida realmente Android
O erro Bad address de addDnsServer é unha comprobación local de formato, non unha caída de DNS. Analiza o literal, descarta loopback e wildcards e despois proba a accesibilidade.
O valor chegou a VpnService.Builder.addDnsServer como cadea e o builder rexeitouno alí antes de que nada se establecese. Ese rexeitamento é local: non se contactou ningún resolver, non hai túnel e o que fallou é unha comprobación de formato, non unha caída de DNS.
Isto importa porque unha app VPN típica ten tres entradas DNS parecidas: un valor escrito ou importado no perfil, un valor que o servidor envía durante a configuración e Private DNS no nivel do sistema. Só as dúas primeiras chegan a esta chamada.
Un obxecto de preparación comproba o argumento ao instante
VpnService.Builder non é o túnel. É un obxecto de preparación que reúne enderezos, rutas e servidores, e só establish() converte esas listas nun ParcelFileDescriptor vivo e nunha interface. A entrada DNS compróbase ao chamar addDnsServer, non ao establecer.
Séguense dúas consecuencias. A traza é curta e inútil a propósito: o valor chegou como cadea simple. E un catch descoidado arredor de toda a construción deixa un túnel con enderezos e rutas pero sen resolver.
O builder conserva calquera lista que lle deas. Engadir un servidor que pasa a comprobación só demostra que a cadea se analizou como literal de enderezo na construción.
Que pode ser o argumento
O parámetro é un enderezo, non un endpoint. A referencia de Builder acepta un literal IPv4 ou IPv6, e a análise é só numérica: android.net.InetAddresses.parseNumericAddress é a cara pública dese analizador desde API 29. Un nome de host aquí non é un enderezo lento senón unha cadea que non se analiza.
O mapeo práctico é así:
| 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 |
A versión afecta a como validas, non ao que acepta o builder. InetAddresses está dispoñible desde API 29, así que en Android 10 e posteriores a análise é unha chamada. addDnsServer(InetAddress) é a forma orixinal e String a incorporación máis nova. Por debaixo de API 29, InetAddress.getByName resolverá un nome de host cunha consulta de rede bloqueante e converterá unha errata nun arranque lento.
Loopback rexéitase aquí, un wildcard non
Os clientes VPN de escritorio adoitan apuntar o seu stub resolver a 127.0.0.1:53. Copiar iso nun perfil Android importa un valor que describe a máquina local, e aquí non hai máquina local. O builder rexeita só este caso: un enderezo null ou loopback lanza IllegalArgumentException.
Un wildcard é o caso oposto, e aquí fallan a maioría das lecturas. 0.0.0.0 e :: non son loopback, así que o builder os acepta; non se lanza nada na construción. A interface sobe cunha entrada que non nomea ningún destino. Detectar iso é traballo do teu validador: trata isAnyLocalAddress como entrada non válida.
Analiza unha vez no límite da configuración
A solución barata é validar onde a cadea entra no teu modelo:
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 }
}
Un resultado null convértese nun erro de campo co texto culpable, e é a única forma sobre a que pode actuar o soporte. Na ruta de éxito o obxecto vai directo a builder.addDnsServer(address). Captura IllegalArgumentException concretamente, non Exception: unha captura ampla tamén traga erros de punteiro nulo.
Non substitúas por un resolver público cando se rexeita o valor desexado. Un perfil que cambia en silencio a un DNS público conéctase, pero os nomes internos deixan de resolverse.
Confirma a accesibilidade cando exista a interface
A aceptación polo builder é o primeiro punto de control, non o último. Coa interface arriba, LinkProperties.getDnsServers() informa do que a rede recibiu realmente. adb shell dumpsys connectivity imprime as mesmas propiedades desde fóra.
Private DNS é un control aparte e non viaxa por este argumento. O nome de host dun provedor DoT pertence ao axuste do sistema, non a un campo IP.
Antes de cambiar outra vez o analizador do perfil, traballa nesta orde: rexistra a cadea crúa e a súa orixe, confirma que é un literal e non un nome de host, que non é loopback nin wildcard, e pasa o obxecto analizado.