Вредност је стигла у VpnService.Builder.addDnsServer као ниска и builder ју је тамо одбио пре него што је ишта успостављено. То одбијање је локално: ниједан resolver није контактиран, тунела нема, а оно што је пало је провера формата, не DNS.

То је важно јер типична VPN апликација има три слична DNS улаза: вредност у профилу, вредност коју сервер пошаље и Private DNS на нивоу система. Само прва два стижу до овог позива.

Припремни објекат одмах проверава аргумент

VpnService.Builder није тунел. То је припремни објекат који сакупља адресе, руте и сервере, а само establish() претвара те листе у живи ParcelFileDescriptor и интерфејс. DNS унос се проверава при позиву addDnsServer.

Две последице: траг је кратак и намерно неинформативан, а немаран catch оставља тунел без resolver-а.

Builder чува сваку листу; додавање сервера који прође проверу доказује само да је ниска разложена као адресни литерал при градњи.

Шта аргумент сме бити

Параметар је адреса, не endpoint. Референца Builder прима литерал IPv4 или IPv6, а разлагање је само нумеричко: android.net.InetAddresses.parseNumericAddress је јавно лице тог парсера од API 29.

Практично пресликавање изгледа овако:

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

Верзија утиче на начин провере, не на оно што builder прима. InetAddresses је доступан од API 29, па је на Android-у 10 разлагање један позив; addDnsServer(InetAddress) је изворни облик, String новији додатак. Испод API 29 InetAddress.getByName ће разложити име хоста блокирајућом мрежном претрагом.

Loopback се овде одбија, wildcard не

Десктоп VPN клијенти често усмеравају свој stub resolver на 127.0.0.1:53; копирање тога у Android профил уноси вредност која описује локалну машину, а локалне машине овде нема. Builder одбија само тај случај: null или loopback адреса баца IllegalArgumentException.

Wildcard је супротан случај и ту се већина читања ове грешке збуни. 0.0.0.0 и :: нису loopback, па их builder прихвата; при градњи се ништа не баца. Интерфејс се затим диже са уносом који не именује одредиште. Хватање тога је посао вашег валидатора: третирајте isAnyLocalAddress као неважећи улаз.

Разложите једном на граници конфигурације

Јефтино решење је валидирати тамо где ниска улази у ваш модел:

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 резултат постаје грешка на нивоу поља са кривим текстом и то је једини облик на који подршка може деловати. На путу успеха објекат иде право у builder.addDnsServer(address), а IllegalArgumentException хватајте конкретно, не Exception.

Не замењујте јавним resolver-ом када је жељена вредност одбијена; профил који тихо убаци јавни DNS се повезује, али интерна имена престају да се разрешавају.

Потврдите доступност када интерфејс постоји

Прихватање од buildera је прва контролна тачка, не последња. Након дизања интерфејса LinkProperties.getDnsServers() показује шта је мрежа стварно примила, а adb shell dumpsys connectivity исписује иста својства споља.

Private DNS је одвојена контрола и не пролази кроз овај аргумент; име хоста DoT провајдера припада системском подешавању, не пољу IP.

Пре него што поново промените парсер профила, забележите сирову ниску и њен извор, потврдите да је литерал а не име хоста, да није loopback ни wildcard, и проследите разложени објекат.

Повезани чланци