addDnsServer Bad Address: шта Android заиста проверава
Грешка Bad address из addDnsServer је локална провера формата, а не DNS испад. Разложите литерал, уклоните loopback и wildcard, па тестирајте доступност.
Вредност је стигла у 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.
Практично пресликавање изгледа овако:
| 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 |
Верзија утиче на начин провере, не на оно што 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, и проследите разложени објекат.