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í:

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

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.

Artigos relacionados