Arvo saapui VpnService.Builder.addDnsServer-metodiin merkkijonona ja builder hylkäsi sen siinä ennen kuin mitään pystytettiin. Hylkäys on paikallinen: yhtään resolveria ei tavoitettu, tunnelia ei ole ja epäonnistunut asia on muototarkistus, ei DNS-katkos.

Tällä on väliä, koska tyypillisellä VPN-sovelluksella on kolme samannäköistä DNS-syötettä: profiiliin kirjoitettu tai tuotu arvo, palvelimen konfiguroinnin aikana lähettämä arvo ja järjestelmätason Private DNS. Vain kaksi ensimmäistä voivat päätyä tähän kutsuun.

Valmisteluobjekti tarkistaa argumentin heti

VpnService.Builder ei ole tunneli. Se on valmisteluobjekti, joka kerää osoitteet, reitit ja palvelimet, ja vain establish() muuttaa ne eläväksi ParcelFileDescriptor-olioksi ja liitännäksi. DNS-merkintä tarkistetaan kutsuttaessa addDnsServer, ei pystytettäessä.

Seuraa kaksi asiaa. Pinon jälki on lyhyt ja tarkoituksella hyödytön: arvo saapui tavallisena merkkijonona. Ja huolimaton catch koko rakennelman ympärillä jättää tunnelin, jossa on osoitteet ja reitit mutta ei resolveria.

Builder säilyttää minkä tahansa antamasi listan. Tarkistuksen läpäisevän palvelimen lisääminen todistaa vain, että merkkijono jäsennettiin osoiteliteraaliksi rakennusvaiheessa.

Mitä argumentti saa olla

Parametri on osoite, ei endpoint. Builder-viite ottaa IPv4- tai IPv6-literaalin, ja jäsennys on vain numeerinen: android.net.InetAddresses.parseNumericAddress on tuon jäsentimen julkinen kasvo API 29:stä alkaen. Isäntänimi ei ole tässä hidas osoite vaan merkkijono, jota ei voi jäsentää.

Käytännön kuvaus näyttää tältä:

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

Versio vaikuttaa siihen, miten validoit, ei siihen, mitä builder hyväksyy. InetAddresses on saatavilla API 29:stä, joten Android 10:ssä ja uudemmissa jäsennys on yksi kutsu. addDnsServer(InetAddress) on alkuperäinen muoto, String uudempi lisäys. Alle API 29:n InetAddress.getByName ratkaisee isäntänimen estävällä verkkohaulla ja muuttaa kirjoitusvirheen hitaaksi käynnistykseksi.

Loopback hylätään täällä, wildcardia ei

Työpöytä-VPN-asiakkaat osoittavat oman stub resolverinsa usein osoitteeseen 127.0.0.1:53. Sen kopioiminen Android-profiiliin tuo arvon, joka kuvaa paikallista konetta, eikä paikallista konetta ole täällä. Builder hylkää vain tämän tapauksen: null- tai loopback-osoite heittää IllegalArgumentException.

Wildcard on päinvastainen tapaus, ja tässä useimmat tämän virheen luennat menevät pieleen. 0.0.0.0 ja :: eivät ole loopback, joten builder hyväksyy ne; rakennusvaiheessa ei heitetä mitään. Liitäntä nousee merkinnällä, joka ei nimeä kohdetta. Sen kiinni ottaminen on validaattorisi työ: käsittele isAnyLocalAddress virheellisenä syötteenä.

Jäsennä kerran konfiguraation rajalla

Halpa korjaus on validoida siinä, missä merkkijono tulee malliisi:

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-tuloksesta tulee kenttätason virhe, joka kantaa syyllisen tekstin, ja se on ainoa muoto, jolla tuki voi toimia. Onnistumispolulla olio menee suoraan builder.addDnsServer(address)-kutsuun. Ota kiinni IllegalArgumentException täsmällisesti, ei Exception: laaja kiinniotto nielee myös nollaviittausvirheet.

Älä korvaa julkisella resolverilla, kun aiottu arvo hylätään. Profiili, joka vaihtaa hiljaa julkiseen DNS:ään, yhdistyy edelleen, mutta sisäiset nimet lakkaavat ratkeamasta.

Vahvista tavoitettavuus, kun liitäntä on olemassa

Builderin hyväksyntä on ensimmäinen tarkistuspiste, ei viimeinen. Kun liitäntä on ylhäällä, LinkProperties.getDnsServers() näyttää, mitä verkko todella sai. adb shell dumpsys connectivity tulostaa samat ominaisuudet ulkopuolelta.

Private DNS on erillinen ohjaus eikä kulje tämän argumentin kautta. DoT-tarjoajan isäntänimi kuuluu järjestelmäasetukseen, ei IP-kenttään.

Ennen kuin muutat profiilijäsennintä uudelleen, etene tässä järjestyksessä: kirjaa raaka merkkijono ja sen lähde, vahvista että se on literaali eikä isäntänimi, ettei se ole loopback eikä wildcard, ja välitä jäsennetty olio.

Aiheeseen liittyvät artikkelit