Värdet nådde VpnService.Builder.addDnsServer som en sträng och buildern avvisade den där innan något etablerades. Avvisningen är lokal: ingen resolver kontaktades, det finns ingen tunnel, och det som misslyckades är en formatkontroll, inte DNS.

Det spelar roll eftersom en typisk VPN-app har tre liknande DNS-ingångar: ett värde i profilen, ett värde servern skickar och Private DNS på systemnivå. Bara de två första når detta anrop.

Ett förberedelseobjekt kontrollerar argumentet direkt

VpnService.Builder är inte tunneln. Det är ett förberedelseobjekt som samlar adresser, rutter och servrar, och bara establish() gör dessa listor till ett levande ParcelFileDescriptor och ett gränssnitt. DNS-posten kontrolleras när du anropar addDnsServer.

Två följder: spåret är kort och avsiktligt intetsägande, och en slarvig catch lämnar en tunnel utan resolver.

Buildern behåller varje lista du ger den; att lägga till en server som klarar kontrollen bevisar bara att strängen parsades som adressliteral vid byggtiden.

Vad argumentet får vara

Parametern är en adress, inte en endpoint. Builder-referensen tar en IPv4- eller IPv6-literal, och parsningen är bara numerisk: android.net.InetAddresses.parseNumericAddress är det offentliga ansiktet för den parsern från API 29.

Den praktiska mappningen ser ut så här:

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

Versionen påverkar hur du validerar, inte vad buildern accepterar. InetAddresses finns från API 29, så på Android 10 är parsningen ett anrop; addDnsServer(InetAddress) är den ursprungliga formen, String det nyare tillägget. Under API 29 löser InetAddress.getByName ett värdnamn med en blockernade nätverkssökning.

Loopback avvisas här, wildcard inte

Skrivbords-VPN-klienter pekar ofta sin stub resolver mot 127.0.0.1:53; kopieras det till en Android-profil införs ett värde som beskriver den lokala maskinen, och här finns ingen lokal maskin. Buildern avvisar bara detta fall: en null- eller loopback-adress kastar IllegalArgumentException.

Ett wildcard är det motsatta fallet och här går de flesta tolkningar av felet fel. 0.0.0.0 och :: är inte loopback, så buildern accepterar dem; inget kastas vid byggtiden. Gränssnittet kommer då upp med en post som inte namnger något mål. Att fånga det är din validators jobb: behandla isAnyLocalAddress som ogiltig indata.

Parsa en gång vid konfigurationsgränsen

Den billiga lösningen är att validera där strängen kommer in i din modell:

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 }
}

Ett null-resultat blir ett fältfel med den skyldiga texten, och det är den enda formen supporten kan agera på. På framgångsvägen går objektet rakt in i builder.addDnsServer(address), och fånga IllegalArgumentException specifikt, inte Exception.

Byt inte ut mot en offentlig resolver när det avsedda värdet avvisas; en profil som tyst sätter in en offentlig DNS ansluter ändå, men interna namn slutar lösas.

Bekräfta nåbarheten när gränssnittet finns

Builderns accept är den första kontrollpunkten, inte den sista. När gränssnittet är uppe visar LinkProperties.getDnsServers() vad nätverket faktiskt tog emot, och adb shell dumpsys connectivity skriver ut samma egenskaper utifrån.

Private DNS är en separat kontroll och går inte genom detta argument; värdnamnet för en DoT-leverantör hör till systeminställningen, inte ett IP-fält.

Innan du ändrar profilparsern igen, logga den råa strängen och dess källa, bekräfta att det är en literal och inte ett värdnamn, att det varken är loopback eller wildcard, och skicka vidare det parsade objektet.

Relaterade artiklar