Telefonen startar om, always-on är på, «blockera anslutningar utan VPN» är på, och tunneln kommer aldrig upp. Ingenting är felkonfigurerat: blockeringen gör sitt jobb, och klientens eget försök att nå sin slutpunkt omfattas också av den. Slutpunktens namn måste lösas innan tunneln finns, av en klient som får prata med nätet medan alla andra appar inte får det. Att förstå vilken komponent som gör det, och i vilken ordning, är det som gör detta mysterium till en konfigurationsfråga.

Vad VPN-byggaren faktiskt ber om

En Android VPN-app ger inte plattformen en serveradress. Den bygger ett lokalt gränssnitt: adresser och rutter för tunneln, resolvrarna som apparna ska använda inuti den, och sedan returnerar establish() en fildeskriptor som appen läser och skriver som råa IP-paket. Den fjärranslutna slutpunkten förekommer inte alls i den byggaren.

Det är det centrala strukturella faktumet. Plattformen skapar ett tun-gränssnitt och tillämpar policy på det; appen implementerar transporten i användarutrymmet, vilket betyder att appen själv löser sitt slutpunktsnamn och öppnar den krypterade anslutningen. En slutpunkt i form av ett värdnamn är därför möjlig — inte för att plattformen stöder namn, utan för att plattformen aldrig ser slutpunkten.

Ett fält i byggaren tar dock adresser: DNS-servrarna. De måste vara numeriska resolveradresser, vanligtvis de som operatören vill ska frågas genom tunneln. Det fältet beskriver vart apparna skickar frågor efter att tunneln är uppe. Det säger ingenting om hur klienten hittar slutpunkten, och att behandla de två som samma inställning är där förvirringen börjar.

Två DNS-frågor, inte en

Den första frågan är: vart apparna skickar frågor när tunneln är uppe? Svaret är en resolveradress inne i tunneln, nåbar via tun-gränssnittet, och numerisk genom konstruktionen.

Den andra frågan är: hur hittar klienten sin egen slutpunkt innan den tunneln finns? Den uppslagningen sker utanför tunneln, på det underliggande nätet, i klientens process. Den får använda ett namn, och den måste använda en resolver som enheten når utan VPN.

StageWho resolvesPathFailure looks like
Endpoint lookupThe VPN appUnderlying network, protected socketNever connects; no tunnel interface traffic
Peer authenticationClient and serverTunnel transportConnects then drops; certificate or key error
Private name lookupApps via the tunnelResolver inside the tunTunnel up, internal sites do not resolve

Tre steg, tre olika symtom. Ett enda felmeddelande som säger «kan inte ansluta» slår ihop dem, och det är därför folk börjar redigera DNS-serverfältet för att fixa ett problem som inträffar innan fältet ens läses.

Varför bootstrappen kan låsa sig

Always-on med blockeringen på betyder att trafik som inte är inne i VPN:en släpps, inklusive trafik från appen som äger VPN:en. Utvägen är explicit: appen anropar protect() på en socket, vilket binder den till det underliggande nätet så att den kringgår tunneln och får gå ut. Varje VPN-klient måste göra detta för sin egen transport, annars skulle den aldrig nå en server.

Tänk dig nu en driftsättning där den enda resolver som känner till slutpunktens namn själv ligger inne i tunneln. Klienten behöver tunneln för att lösa namnet, och namnet för att bygga tunneln. Det är ett verkligt cirkelberoende, och ingen klientkonfiguration löser det. Det är ett designfel, inte en bugg.

Tidpunkten avslöjar samma beroende i mildare former. Vid start före första upplåsningen kan enheten ännu inte ha ett användbart nät; på en captive portal finns nätet men blockerar allt utom portalen; vid övergång från Wi-Fi till mobilnät ändras den skyddade socketens underliggande nät och klienten måste märka det och lösa om. Var och en av dessa är en bootstrap-händelse, och var och en är värd att testa medvetet i stället för att upptäcka klockan 3 på natten.

Varför det inte hjälper att låsa svaret

Den frestande genvägen är att lösa namnet en gång och klistra in den resulterande adressen i ett numeriskt fält, eller hårdkoda den i en egen build. Det verkar fungera, och det förvandlar ett namn till en ögonblicksbild. När operatören flyttar slutpunkten — en migrering, en anycast-ändring, en ny regional adress — fortsätter klienten att ringa den gamla och ingenting i gränssnittet förklarar varför.

DNS-poster har en livstid just för att svaren ska kunna ändras; cachelagring definieras utifrån den livstiden, inte för evigt. Att låsa ett svar kastar bort mekanismen som gör ett namn bättre än en adress, samtidigt som driftkostnaden för att ha använt ett namn finns kvar.

Låsning brukar också gå hand i hand med en andra dålig vana: att lätta på identitetskontrollen eftersom anslutningen nu misslyckas annorlunda. Verifiering av serverns identitet är inte en del av bootstrap-problemet, och att försvaga den byter en DNS-olägenhet mot en avlyssningsrisk.

Design som överlever en omstart

Den hållbara ordningen är inte glamorös. Använd en klient som implementerar skyddade sockets korrekt och löser sin slutpunkt på det underliggande nätet, och ge den ett slutpunktsnamn som löses publikt, eller via en resolver som är nåbar före tunneln. Låt blockeringen vara på; att stänga av den helt för att få DNS att fungera tar bort egenskapen som driftsättningen finns för.

Där slutpunkten måste förbli privat, erbjud en explicit bootstrap-väg som plattformen kan använda — en resolver nåbar utan tunneln, eller en administrationsanslutning som operatören underhåller — och håll det undantaget smalt och ägt av policy, inte av en användartoggle. Android VpnService-dokumentationen är referensen för vad plattformen garanterar om skydd och livscykel, och VpnService.Builder-referensen bekräftar att DNS-fältet tar numeriska adresser.

Verifiera sedan de tre stegen, i ordning, på en ny anslutning efter en omstart: slutpunktens namn löses, tunneln etableras och motparten autentiseras, och ett privat namn löses via tunnelns resolver. Om det första steget misslyckas kan ingenting senare fungera, och rättelsen hör hemma i bootstrap-vägen — aldrig i resolverfältet.

  • Upplösningen av slutpunkten sker i appen, utanför tunneln, på en skyddad socket.
  • DNS-fältet i byggaren är för appar inne i tunneln, inte för klientens egen slutpunkt.
  • En resolver som bara nås via tunneln är en låsning, inte en långsam start.
  • Testa slutpunkt, autentisering och privat uppslagning separat efter en omstart.

Relaterade artiklar