Du lade till en domän i tillåtelselistan, frågan når din app, matcharen returnerar tillåten, och sidan laddas ändå aldrig. I det läget börjar de flesta skriva om matcharen och lägga till jokerteckenjämförelser. Enligt min erfarenhet är matcharen oftast korrekt, och felet sitter ett lager längre ner: i DNS-svaret du syntetiserade, i rutten du installerade, eller i socketen du använde för att vidarebefordra frågan. Listan är den enkla delen; paketvägen är där tillåtelselistor går sönder.

Matcharen är sällan boven

Det första att skilja på är blockerat från trasigt. Ett blockerat namn bör misslyckas snabbt och synligt, eftersom webbläsaren får ett svar att namnet inte finns och stannar. Ett trasigt namn hänger sig, försöker igen över IPv6, faller tillbaka på en annan transport och lämnar dig med en snurrande indikator. Ser du en snurrande indikator svarar din tillåtelselista förmodligen med något som klienten inte kan göra något åt.

En tom A-post är det klassiska misstaget. Att returnera NOERROR med noll svar ser ut som ett giltigt svar, så stub-resolvern accepterar det, webbläsaren försöker igen, prövar AAAA, prövar en annan resolver och ger upp mycket senare. En svarskod 3, det vill säga NXDOMAIN, med frågesektionen upprepad och transaktions-ID:t kopierat avslutar samtalet omedelbart. Använd den för varje namn du medvetet avvisar.

IPv6 förtjänar samma beslut i stället för ett standardvärde. Om du svarar NXDOMAIN på A och släpper igenom AAAA kan klienten nå det blockerade namnet över IPv6 och filtret läcker. Vissa implementationer svarar på blockerade AAAA-frågor med en loopback-adress så att anslutningen misslyckas direkt i stället för att falla tillbaka. Välj en policy, tillämpa den på båda posttyperna och logga båda.

En rutt som fångar allt äger allt

En design med bara DNS lägger aldrig till addRoute("0.0.0.0", 0), så bara frågor kommer in i TUN-gränssnittet och allt annat fortsätter använda det underliggande nätet. Det är den säkra formen, och det förklarar också varför vissa tillåtelselistor verkar fungera på Wi-Fi och misslyckas i mobilnätet.

I samma stund som en standardrutt finns är varje flöde ditt. Ett TCP-paket som kommer till gränssnittet och varken vidarebefordras eller besvaras ligger i kön tills klienten får timeout, och det finns inget fel att logga eftersom inget gick fel. Jag har sett någon lägga en eftermiddag på en trasig tillåtelselista som egentligen var en saknad vidarebefordrare för port 443. Logga protokollet och målet för varje paket du väljer att släppa.

Resolver-socketen måste lämna tunneln

Vidarebefordran är där nästa fälla sitter. Operatörens resolvrar bor ofta på en privat adress som tunneln inte når, så frågan du vidarebefordrar går ingenstans och appen rapporterar läsetimeout. Skicka godkända frågor till en resolver du vet är nåbar, och ge den socketen till VpnService.protect() först. En oskyddad socket skickar frågan tillbaka in i din egen tunnel, där den når appen igen och cirkulerar tills timeouten slår till.

Felet ser identiskt ut med ett nätavbrott, och därför överlever det så länge i felrapporter. Håll räknare för skickade frågor och mottagna svar: om det första talet stiger och det andra stannar på noll tittar du på socketen, inte på listan.

Storlek och kontrollsummor misslyckas utan meddelande

Tysta tapp kommer också från storlek. Ett DNS-svar med en stor svarsuppsättning, eller ett TCP-handskakning med generösa alternativ, kan överskrida en väg-MTU som du aldrig ställt in uttryckligen; fragmentet försvinner uppströms och klienten försöker antingen igen mindre eller slutar försöka. Att sätta gränssnittets MTU till 1280 är ett konservativt val som överlever de flesta mobilvägar, och att vara uttrycklig är bättre än att ärva ett standardvärde som ingen mätt.

Om du bygger paketen själv är kontrollsumman den andra tysta mördaren. En UDP-kontrollsumma beräknad över ett pseudohuvud som utelämnar UDP-längden eller protokollfältet ger ett paket som ser välformat ut hos dig och kastas av mottagaren utan spår. Jämför ditt pseudohuvud med specifikationen fält för fält, och skriv ut den beräknade kontrollsumman för ett känt flöde medan du felsöker.

Vad en VpnService inte gör för dig

Två gränser är värda att känna till innan du designar runt dem. Ingen vanlig app kan installera en systemomfattande proxy eller PAC-fil utanför Wi-Fi, och den delen har inte ändrats. Vägen per app har det: före API 29 kunde VpnService.Builder inte bära proxy alls, och från API 29 ger setHttpProxy(ProxyInfo.buildPacProxy(uri)) plattformen ett PAC-skript att utvärdera för VPN-nätet. Utvärdera regler i din egen kod när du riktar in dig på äldre API:er eller när PAC-modellen inte kan uttrycka regeln. UDP är inte heller gratis: webbläsare skickar bara TCP genom en SOCKS-proxy, och de flesta SOCKS5-klienter implementerar aldrig UDP ASSOCIATE, så en proxybaserad design tappar eller läcker UDP medan TCP ser friskt ut.

Den andra gränsen är anledningen till att ett UDP-baserat protokoll kan fungera i ett vanligt nät och misslyckas i samma stund som din VPN ansluter. Om tunneln förväntas bära UDP måste vidarebefordraren implementera vidarebefordringsvägen för det, inte bara TCP-vägen med en annan sockettyp.

Kontrollera i den här ordningen

Arbeta igenom listan i ordning och stanna vid det första steget som misslyckas, eftersom varje senare kontroll är brus tills den föregående håller.

  1. Lista de installerade rutterna och bekräfta om icke-DNS-paket överhuvudtaget kommer in i TUN-gränssnittet.
  2. Logga frågenamnet, beslutet och svarskoden för ett blockerat och ett godkänt namn.
  3. Bekräfta att resolver-socketen är skyddad och att den valda resolvern svarar inifrån tunneln.
  4. Bekräfta gränssnittets MTU och kontrollsumman för ett svar du byggt för hand.
  5. Upprepa samma sida på Wi-Fi och i mobilnätet, eftersom operatörens resolvrar och väg-MTU skiljer sig.

Om alla fem håller och sidan fortfarande hänger sig är nästa misstänkta vidarebefordraren själv: jämför en fångad begäran med ett fångat svar och leta efter ett saknat byteantal eller en halvstängd socket. VpnService.Builder-referensen täcker kontrakten för rutt, DNS och MTU, och protect() är ett separat VpnService-anrop och inte ett builder-alternativ; ett anslutet gränssnitt betyder bara att det första kontraktet är uppfyllt.

Relaterade artiklar