Vērtība nonāca VpnService.Builder.addDnsServer kā virkne, un būvētājs to noraidīja turpat, pirms vēl kaut kas tika izveidots. Šis noraidījums ir lokāls: neviens izšķirtājs nav sazinājies, tuneļa nav, un neizdevās formāta pārbaude, nevis DNS avārija. Ja izsekojumu lasa kā enerģijas pārvaldības konfliktu vai servera puses problēmu, jūs nonākat nepareizajā slānī. Kadrs nosauc argumentu, kas tika noraidīts, nevis iestatījumu, kas to radīja.

Tas ir svarīgi, jo tipiskai VPN lietotnei ir trīs DNS ievades, kas izskatās līdzīgi: vērtība, kas ierakstīta vai importēta profilā, vērtība, ko serveris nosūta konfigurācijas laikā, un Private DNS, kas konfigurēts sistēmas līmenī. Tikai pirmie divi var sasniegt šo izsaukumu, un parasti tikai viens no tiem ir vieta, no kurienes nāk sliktā virkne.

Starpobjekts pārbauda argumentu uz vietas

VpnService.Builder nav tunelis. Tas ir starpobjekts, kas apkopo adreses, maršrutus un serverus, un tikai establish() pārvērš šos sarakstus dzīvā ParcelFileDescriptor un saskarnē. DNS ieraksts tiek pārbaudīts, kad jūs izsaucat addDnsServer, nevis kad jūs izveidojat savienojumu, tāpēc izņēmums tiek izmests vairākas rindas virs koda, kuru jūs faktiski atkļūdojāt.

No tā izriet divas sekas. Steka izsekojums ir īss un apzināti neizpalīdzīgs: vērtība ieradās kā vienkārša virkne, un ietvars nevar zināt, vai tā nāca no QR importa, servera nosūtītā satura vai iestatījumu pārrakstīšanas. Un tā kā viss pārējais būvētājā pārdzīvo neizdevušos izsaukumu, nevīžīgs catch ap visu būvēšanu atstāj jums tuneli, kuram ir adreses un maršruti, bet vispār nav izšķirtāja.

Būvētājs arī patur jebkuru sarakstu, ko tam dodat. Servera pievienošana, kas iztur pārbaudi, pierāda tikai to, ka virkne būvēšanas laikā tika parsēta kā adreses literālis, un aizstājējzīmes gadījums tālāk parāda, ka tas nemaz nepierāda vērtības lietojamību.

Kas argumentam ir atļauts būt

Parametrs ir adrese, nevis galapunkts. Android Būvētāja atsauce pieņem IPv4 vai IPv6 literāli, un aiz tā esošā parsēšana ir tikai ciparu: android.net.InetAddresses.parseNumericAddress ir šī parsera publiskā seja no API 29, un paša ietvara izsaukums nonāk tajā pašā rutīnā. Resursdatora nosaukums šeit nav lēna adrese; tā ir neparsējama virkne, un tieši šī parsēšanas kļūda izpaužas kā sliktas adreses izņēmums.

Praktiskā atbilstība izskatās šādi:

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

Versija ir svarīga tam, kā jūs validējat, nevis tam, ko būvētājs pieņem. InetAddresses ir pieejams no API 29, tāpēc Android 10 un jaunākās versijās literāļa parsēšana ir viens izsaukums. Pārslodze arī ir svarīga: addDnsServer(InetAddress) ir sākotnējā forma, bet String pārslodze ir jaunākais papildinājums, tāpēc ielīmēts virknes paraugs nekompilēsies pret vecāku SDK. Zem API 29 acīmredzamais aizstājējs, InetAddress.getByName, labprāt atrisinās resursdatora nosaukumu ar bloķējošu tīkla uzmeklēšanu, kas pārvērš drukas kļūdu lēnā startā, nevis skaidrā kļūdā. Ja jūs joprojām piegādājat šīm versijām, pirms aiztikt vecāko API palaidiet skaidru literāļa testu.

Loopback šeit tiek noraidīts, aizstājējzīme — nē

Darbvirsmas VPN klienti bieži vien savu stub izšķirtāju vērš uz 127.0.0.1:53 un liek tuneļa klientam pārsūtīt vaicājumus. Šīs kārtības kopēšana Android profilā importē vērtību, kas apraksta vietējo mašīnu, un šeit nav vietējās mašīnas, kam to nodot: tuneļa izšķirtājam jābūt sasniedzamam saskarnes otrā pusē. Šis ir vienīgais gadījums, ko būvētājs pats noraida: null vai loopback adrese izmet IllegalArgumentException, kā nosaka atsauces līgums.

Aizstājējzīme ir pretējais gadījums, un tieši tur lielākā daļa šī izņēmuma interpretāciju kļūdās. 0.0.0.0 un :: nav loopback, tāpēc būvētājs tos pieņem un pievieno kā jebkuru citu literāli; būvēšanas laikā nekas netiek izmests. Saskarne tad paceļas ar izšķirtāja ierakstu, kas nenosauc galamērķi, un kļūme pāriet no izņēmuma uz vaicājumiem, kas klusi aiziet nekur. To noķert ir jūsu validētāja, nevis ietvara darbs: uzskatiet isAnyLocalAddress par nederīgu ievadi tālāk esošajā palīgā un parādiet to kā lauka līmeņa kļūdu.

Parsējiet vienreiz pie konfigurācijas robežas

Lētais risinājums ir validēt tur, kur virkne nonāk jūsu modelī, un nodot parsēto objektu tālāk, lai vērtība, ko redz būvētājs, būtu tieši tā vērtība, kuru jūs validējāt:

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

Nulles rezultāts tad kļūst par lauka līmeņa kļūdu, kas nes vainīgo tekstu, un tas ir vienīgais veids, ar ko atbalsta saruna var kaut ko darīt. Veiksmes ceļā atgrieztais objekts nonāk tieši builder.addDnsServer(address), tāpēc vērtība, kuru jūs validējāt, ir tā vērtība, kas tiek nosūtīta. Noķeriet IllegalArgumentException konkrēti, nevis Exception: plašs uztvērējs pār konfigurācijas kodu norij arī nulles rādītāja un stāvokļa kļūdas, kuras vēlaties redzēt atkļūdošanas būvējā.

Nepakļaujieties kārdinājumam aizstāt ar publisku izšķirtāju, kad paredzētā vērtība tiek noraidīta. Profils, kas klusi nomaina pret publisku DNS pakalpojumu, joprojām savienojas, bet iekšējie nosaukumi pārstāj atrisināties un katrs vaicājums iziet caur izšķirtāju, kuru lietotājs nav izvēlējies. Redzama kļūda maksā vienu atbalsta atbildi; klusā aizstāšana maksā uzticību tam, ka tunelis tur vaicājumus tur, kur saka profils.

Apstipriniet sasniedzamību pēc tam, kad saskarne pastāv

Tas, ka būvētājs vērtību pieņem, ir pirmais kontrolpunkts, nevis pēdējais. Kad saskarne ir augšā, LinkProperties.getDnsServers() ziņo, ko tīkls faktiski saņēma, un tieši tur parādās neatbilstība: tukšs saraksts, mazāk ierakstu nekā pievienojāt, vai saime, kuru jūsu maršruti nepārnes. adb shell dumpsys connectivity izdrukā tās pašas katra tīkla īpašības, kad jums jāskatās ārpus lietotnes.

Private DNS ir atsevišķa vadība un neiet caur šo argumentu. DoT pakalpojumu sniedzēja resursdatora nosaukums pieder sistēmas iestatījumam vai jūsu pašu šifrētā izšķirtāja ceļam; tā nav vērtība, ko ielīmēt IP laukā, un Private DNS iestatījuma labošana neattīrīs būvētāja izmesto izņēmumu.

Pirms vēlreiz maināt profila parseri, izpildiet šo secību: reģistrējiet neapstrādāto virkni un tās avotu, apstipriniet, ka tas ir literālis, nevis resursdatora nosaukums vai URL, apstipriniet, ka tas nav ne loopback, ne aizstājējzīme, nododiet parsēto objektu, nevis parsējiet tekstu vēlreiz, un tikai tad salīdziniet izveidoto saskarni ar vērtību, kuru bijāt iecerējis.

Saistītie raksti