El valor va arribar a VpnService.Builder.addDnsServer com a cadena i el builder el va rebutjar allà abans que res no s’establís. Aquest rebuig és local: no s’ha contactat cap resolver, no hi ha túnel i el que ha fallat és una comprovació de format, no una caiguda de DNS.

Això importa perquè una aplicació VPN típica té tres entrades DNS semblants: un valor escrit o importat al perfil, un valor que el servidor envia durant la configuració i Private DNS al nivell del sistema. Només les dues primeres poden arribar a aquesta crida.

Un objecte de preparació comprova l’argument a l’instant

VpnService.Builder no és el túnel. És un objecte de preparació que recull adreces, rutes i servidors, i només establish() converteix aquestes llistes en un ParcelFileDescriptor viu i una interfície. L’entrada DNS es comprova quan crideu addDnsServer, no quan establiu.

Se’n segueixen dues conseqüències. La traça és curta i inútil a propòsit: el valor ha arribat com a cadena simple. I un catch descuidat al voltant de tota la construcció us deixa un túnel amb adreces i rutes però sense cap resolver.

El builder conserva qualsevol llista que li doneu. Afegir un servidor que passa la comprovació només demostra que la cadena s’ha analitzat com a literal d’adreça en temps de construcció.

Què pot ser l’argument

El paràmetre és una adreça, no un endpoint. La referència de Builder accepta un literal IPv4 o IPv6, i l’anàlisi és només numèrica: android.net.InetAddresses.parseNumericAddress és la cara pública d’aquest analitzador des de l’API 29. Un nom d’amfitrió aquí no és una adreça lenta sinó una cadena que no s’analitza.

El mapatge pràctic és així:

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

La versió afecta com valideu, no què accepta el builder. InetAddresses està disponible des de l’API 29, així que a Android 10 i posteriors l’anàlisi és una crida. addDnsServer(InetAddress) és la forma original i String l’addició més nova. Per sota de l’API 29, InetAddress.getByName resoldrà un nom d’amfitrió amb una consulta de xarxa bloquejant i convertirà una errata en un inici lent.

Loopback es rebutja aquí, un wildcard no

Els clients VPN d’escriptori sovint apunten el seu stub resolver a 127.0.0.1:53. Copiar-ho a un perfil Android importa un valor que descriu la màquina local, i aquí no hi ha màquina local. El builder rebutja només aquest cas: una adreça null o loopback llança IllegalArgumentException.

Un wildcard és el cas oposat, i aquí fallen la majoria de lectures. 0.0.0.0 i :: no són loopback, així que el builder els accepta; no es llança res en construir. La interfície puja amb una entrada que no nomena cap destinació. Detectar-ho és feina del vostre validador: tracteu isAnyLocalAddress com a entrada no vàlida.

Analitzeu una vegada a la frontera de configuració

La solució barata és validar on la cadena entra al vostre 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 }
}

Un resultat null es converteix en un error de camp amb el text culpable, i és l’única forma sobre la qual pot actuar el suport. En el camí d’èxit l’objecte va directe a builder.addDnsServer(address). Captureu IllegalArgumentException concretament, no Exception: una captura àmplia també empassa errors de punter nul.

No substituïu per un resolver públic quan el valor volgut es rebutja. Un perfil que canvia silenciosament a un DNS públic es connecta, però els noms interns deixen de resoldre.

Confirmeu l’abastabilitat quan la interfície existeix

L’acceptació pel builder és el primer punt de control, no l’últim. Quan la interfície és amunt, LinkProperties.getDnsServers() informa del que la xarxa ha rebut realment. adb shell dumpsys connectivity imprimeix les mateixes propietats des de fora.

Private DNS és un control separat i no viatja per aquest argument. El nom d’amfitrió d’un proveïdor DoT pertany a la configuració del sistema, no a un camp IP.

Abans de tornar a canviar l’analitzador del perfil, seguiu aquest ordre: registreu la cadena crua i la seva font, confirmeu que és un literal i no un nom d’amfitrió, que no és loopback ni wildcard, i passeu l’objecte analitzat.

Articles relacionats