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, не при установяване.
Следват две последици. Трасето е кратко и умишлено неинформативно: стойността е пристигнала като обикновен низ и рамката не може да знае дали идва от QR импорт, от сървър или от настройки. А небрежен 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, се свързва, но вътрешните имена спират да се разрешават.
Потвърдете достъпността след създаването на интерфейса
Приемането от builder е първата контролна точка, не последната. След вдигане на интерфейса LinkProperties.getDnsServers() показва какво реално е получила мрежата. adb shell dumpsys connectivity отпечатва същите свойства отвън.
Private DNS е отделно управление и не минава през този аргумент. Името на DoT доставчик принадлежи на системната настройка, а не на поле за IP.
Преди да промените парсера на профила отново, минавате в този ред: запишете суровия низ и източника му, потвърдете, че е литерал, а не име на хост, че не е loopback или wildcard, и подайте анализирания обект.