Dodałeś domenę do listy dozwolonych, zapytanie dociera do aplikacji, dopasowywacz zwraca dozwolone, a strona nadal nigdy się nie ładuje. W tym momencie większość zaczyna przepisywać dopasowywacz i dodawać porównania z wieloznacznikami. Z mojego doświadczenia dopasowywacz jest zwykle poprawny, a usterka leży warstwę niżej: w odpowiedzi DNS, którą zsyntetyzowałeś, w trasie, którą zainstalowałeś, albo w gnieździe, którego użyłeś do przekazania zapytania. Lista to łatwa część; ścieżka pakietu to miejsce, w którym listy dozwolonych się psują.

Dopasowywacz rzadko jest winny

Najpierw trzeba oddzielić zablokowane od zepsutego. Zablokowana nazwa powinna zawieść szybko i widocznie, bo przeglądarka dostaje odpowiedź, że nazwa nie istnieje, i się zatrzymuje. Zepsuta nazwa wisi, ponawia próby przez IPv6, przechodzi na inny transport i zostawia cię z kręcącym się wskaźnikiem. Jeśli widzisz kręcący się wskaźnik, twoja lista dozwolonych odpowiada czymś, z czym klient nie może nic zrobić.

Pusty rekord A to klasyczny błąd. Zwracanie NOERROR z zerem odpowiedzi wygląda jak prawidłowa odpowiedź, więc resolver stub ją przyjmuje, przeglądarka próbuje ponownie, próbuje AAAA, próbuje innego resolvera i poddaje się znacznie później. Kod odpowiedzi 3, czyli NXDOMAIN, z powtórzoną sekcją pytania i skopiowanym identyfikatorem transakcji kończy rozmowę natychmiast. Używaj go dla każdej nazwy, którą świadomie odrzucasz.

IPv6 zasługuje na tę samą decyzję, a nie na wartość domyślną. Jeśli odpowiadasz NXDOMAIN na A i przepuszczasz AAAA, klient może dotrzeć do zablokowanej nazwy przez IPv6 i filtr przecieka. Niektóre implementacje odpowiadają na zablokowane zapytania AAAA adresem loopback, aby połączenie natychmiast zawiodło, zamiast przechodzić na wariant zapasowy. Wybierz jedną politykę, zastosuj ją do obu typów rekordów i loguj oba.

Trasa, która przechwytuje wszystko, posiada wszystko

Projekt oparty wyłącznie na DNS nigdy nie dodaje addRoute("0.0.0.0", 0), więc do interfejsu TUN trafiają tylko zapytania, a wszystko inne nadal korzysta z sieci bazowej. To bezpieczny kształt i wyjaśnia, dlaczego niektóre listy dozwolonych wydają się działać w Wi-Fi, a zawodzą w sieci komórkowej.

W momencie, gdy istnieje trasa domyślna, każdy przepływ jest twój. Pakiet TCP, który dociera do interfejsu i nie jest ani przekazywany, ani obsługiwany odpowiedzią, siedzi w kolejce, aż klientowi skończy się czas, i nie ma błędu do zalogowania, bo nic nie zawiodło. Widziałem, jak ktoś spędził popołudnie nad zepsutą listą dozwolonych, która w rzeczywistości była brakującym przekazującym dla portu 443. Loguj protokół i cel każdego pakietu, który zdecydujesz się odrzucić.

Gniazdo resolvera musi opuścić tunel

Przekazywanie to miejsce następnej pułapki. Resolvery operatora często mieszkają pod adresem prywatnym, do którego tunel nie dociera, więc przekazywane zapytanie idzie donikąd, a aplikacja zgłasza przekroczenie limitu czasu odczytu. Wysyłaj zatwierdzone zapytania do resolvera, o którym wiesz, że jest osiągalny, i najpierw przekaż to gniazdo do VpnService.protect(). Niechronione gniazdo odsyła zapytanie z powrotem do twojego własnego tunelu, gdzie dociera do aplikacji ponownie i krąży, aż zadziała limit czasu.

Awaria wygląda identycznie jak awaria sieci, i dlatego tak długo przeżywa w raportach błędów. Prowadź liczniki wysłanych zapytań i odebranych odpowiedzi: jeśli pierwsza liczba rośnie, a druga pozostaje na zerze, patrzysz na gniazdo, nie na listę.

Rozmiar i sumy kontrolne zawodzą bez komunikatu

Ciche odrzucenia biorą się także z rozmiaru. Odpowiedź DNS z dużym zbiorem odpowiedzi albo uścisk TCP z hojnymi opcjami może przekroczyć MTU ścieżki, którego nigdy jawnie nie ustawiłeś; fragment znika wyżej i klient albo ponawia mniejszym, albo przestaje próbować. Ustawienie MTU interfejsu na 1280 to konserwatywny wybór, który przetrwa większość ścieżek komórkowych, a bycie jawnym jest lepsze niż dziedziczenie wartości domyślnej, której nikt nie zmierzył.

Jeśli sam budujesz pakiety, suma kontrolna jest drugim cichym zabójcą. Suma kontrolna UDP obliczona po pseudo-nagłówku, który pomija długość UDP albo pole protokołu, daje pakiet wyglądający poprawnie po twojej stronie i odrzucany przez odbiorcę bez śladu. Porównaj swój pseudo-nagłówek ze specyfikacją pole po polu i wypisz obliczoną sumę kontrolną dla jednego znanego przepływu podczas debugowania.

Czego VpnService za ciebie nie zrobi

Dwa ograniczenia warto znać, zanim zaprojektujesz wokół nich. Żadna zwykła aplikacja nie może zainstalować systemowego proxy ani pliku PAC poza Wi-Fi i ta część się nie zmieniła. Ścieżka dla poszczególnych aplikacji tak: przed API 29 VpnService.Builder w ogóle nie mógł nieść proxy, a od API 29 setHttpProxy(ProxyInfo.buildPacProxy(uri)) przekazuje platformie skrypt PAC do oceny dla sieci VPN. Oceniaj reguły we własnym kodzie, gdy celujesz w starsze API albo gdy model PAC nie potrafi wyrazić reguły. UDP też nie jest darmowe: przeglądarki wysyłają przez proxy SOCKS tylko TCP, a większość klientów SOCKS5 nigdy nie implementuje UDP ASSOCIATE, więc projekt oparty na proxy porzuca lub przecieka UDP, podczas gdy TCP wygląda zdrowo.

To drugie ograniczenie jest powodem, dla którego protokół oparty na UDP może działać w zwykłej sieci i zawieść w momencie, gdy łączy się twoje VPN. Jeśli tunel ma przenosić UDP, przekazujący musi zaimplementować dla niego ścieżkę przekazywania, nie tylko ścieżkę TCP z innym typem gniazda.

Sprawdzaj w tej kolejności

Przejdź listę po kolei i zatrzymaj się na pierwszym etapie, który zawiedzie, bo każdy późniejszy test to szum, dopóki poprzedni się nie utrzyma.

  1. Wypisz zainstalowane trasy i potwierdź, czy pakiety inne niż DNS w ogóle wchodzą do interfejsu TUN.
  2. Zaloguj nazwę zapytania, decyzję i kod odpowiedzi dla jednej nazwy zablokowanej i jednej zatwierdzonej.
  3. Potwierdź, że gniazdo resolvera jest chronione, a wybrany resolver odpowiada od wewnątrz tunelu.
  4. Potwierdź MTU interfejsu i sumę kontrolną jednej odpowiedzi zbudowanej ręcznie.
  5. Powtórz tę samą stronę w Wi-Fi i w sieci komórkowej, bo resolvery operatora i MTU ścieżki się różnią.

Jeśli wszystkie pięć się utrzyma, a strona nadal wisi, następnym podejrzanym jest sam przekazujący: porównaj przechwycone żądanie z przechwyconą odpowiedzią i szukaj brakującej liczby bajtów albo na wpół zamkniętego gniazda. Dokumentacja VpnService.Builder obejmuje kontrakty trasy, DNS i MTU, a protect() jest osobnym wywołaniem VpnService, a nie opcją buildera; połączony interfejs oznacza tylko, że pierwszy kontrakt został spełniony.

Powiązane artykuły