Vrijednost je došla u VpnService.Builder.addDnsServer kao znakovni niz i builder ju je ondje odbio prije nego što je išta uspostavljeno. To je odbijanje lokalno: nijedan resolver nije kontaktiran, tunela nema, a pala je provjera oblika, ne DNS.

To je važno jer tipična VPN aplikacija ima tri slična DNS ulaza: vrijednost u profilu, vrijednost koju poslužitelj pošalje tijekom konfiguracije i Private DNS na razini sustava. Samo prva dva stižu do ovog poziva.

Pripremni objekt provjerava argument odmah

VpnService.Builder nije tunel. To je pripremni objekt koji skuplja adrese, rute i poslužitelje, a samo establish() pretvara te popise u živi ParcelFileDescriptor i sučelje. DNS unos provjerava se pri pozivu addDnsServer, ne pri uspostavi.

Dvije stvari slijede: trag je kratak i namjerno nekoristan, a nemaran catch ostavlja tunel s adresama i rutama ali bez resolvera.

Builder zadržava svaki popis koji mu date; dodani poslužitelj koji prođe provjeru dokazuje samo da je niz rastavljen kao adresni literal.

Što argument smije biti

Parametar je adresa, ne endpoint. Referenca Builder prima literal IPv4 ili IPv6, a rastavljanje je samo brojčano: android.net.InetAddresses.parseNumericAddress je javno lice tog parsera od API 29.

Praktično preslikavanje izgleda ovako:

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

Verzija utječe na način provjere, ne na ono što builder prima. InetAddresses dostupan je od API 29, pa je na Androidu 10 rastavljanje jedan poziv; addDnsServer(InetAddress) je izvorni oblik, String noviji dodatak. Ispod API 29 InetAddress.getByName rastavit će ime hosta blokirajućom mrežnom pretragom.

Loopback se ovdje odbija, wildcard ne

Desktop VPN klijenti šalju svoj stub resolver na 127.0.0.1:53; kopiranje toga u Android profil unosi vrijednost koja opisuje lokalno računalo, a lokalnog računala ovdje nema. Builder odbija samo taj slučaj: null ili loopback adresa baca IllegalArgumentException.

Wildcard je obrnut slučaj i tu se većina čitanja ove pogreške prevari. 0.0.0.0 i :: nisu loopback, pa ih builder prihvaća; pri gradnji se ništa ne baca. Sučelje se diže s unosom koji ne imenuje odredište. To je posao vašeg validatora: isAnyLocalAddress tretirajte kao nevaljan ulaz.

Rastavite jednom na granici konfiguracije

Jeftino rješenje je provjeravati tamo gdje niz ulazi u vaš 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 }
}

Null rezultat postaje pogreška na razini polja s krivim tekstom i to je jedini oblik na koji podrška može djelovati. Na uspješnom putu objekt ide ravno u builder.addDnsServer(address), a IllegalArgumentException hvatajte ciljano, ne Exception.

Ne zamjenjujte javnim resolverom kad je željena vrijednost odbijena; profil koji tiho ubaci javni DNS povezuje se, ali imena unutar mreže prestaju se razrješavati.

Potvrdite dostupnost nakon što sučelje postoji

Prihvat buildera prva je kontrolna točka, ne posljednja. Nakon dizanja sučelja LinkProperties.getDnsServers() pokazuje što je mreža stvarno primila, a adb shell dumpsys connectivity ispisuje ista svojstva izvana.

Private DNS odvojena je kontrola i ne prolazi kroz ovaj argument; ime hosta DoT pružatelja pripada sistemskoj postavci, ne polju IP.

Prije nove promjene parsera profila zabilježite sirovi niz i njegov izvor, potvrdite da je literal a ne ime hosta, da nije loopback ni wildcard, i proslijedite rastavljeni objekt.

Povezani članci