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:

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

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.

Související články