Telefon się restartuje, always-on jest włączony, «blokuj połączenia bez VPN» jest włączone, a tunel nigdy nie wstaje. Nic nie jest źle skonfigurowane: blokada robi swoje, a próba dotarcia klienta do własnego punktu końcowego także jej podlega. Nazwa punktu końcowego musi zostać rozwiązana, zanim tunel powstanie, przez klienta, któremu wolno rozmawiać z siecią, podczas gdy pozostałym aplikacjom nie. Zrozumienie, który komponent to robi i w jakiej kolejności, zamienia tę zagadkę w pytanie o konfigurację.

Czego naprawdę żąda builder VPN

Aplikacja VPN na Androida nie przekazuje platformie adresu serwera. Buduje lokalny interfejs: adresy i trasy dla tunelu, resolvery, których aplikacje mają używać w jego wnętrzu, a następnie establish() zwraca deskryptor pliku, który aplikacja czyta i zapisuje jako surowe pakiety IP. Zdalny punkt końcowy w ogóle nie pojawia się w tym builderze.

To jest kluczowy fakt strukturalny. Platforma tworzy interfejs tun i egzekwuje na nim politykę; aplikacja implementuje transport w przestrzeni użytkownika, co oznacza, że sama rozwiązuje nazwę punktu końcowego i otwiera zaszyfrowane połączenie. Punkt końcowy w postaci nazwy hosta jest więc możliwy — nie dlatego, że platforma obsługuje nazwy, ale dlatego, że platforma nigdy nie widzi punktu końcowego.

Jedno pole buildera przyjmuje jednak adresy: serwery DNS. Muszą to być numeryczne adresy resolverów, zwykle te, które operator chce odpytywać przez tunel. To pole opisuje, dokąd aplikacje wysyłają zapytania po podniesieniu tunelu. Nie mówi nic o tym, jak klient znajduje punkt końcowy, i właśnie traktowanie obu tych rzeczy jako jednego ustawienia jest początkiem zamieszania.

Dwa pytania DNS, nie jedno

Pierwsze pytanie brzmi: dokąd aplikacje wysyłają zapytania, gdy tunel jest już podniesiony? Odpowiedzią jest adres resolvera wewnątrz tunelu, osiągalny przez interfejs tun i z założenia numeryczny.

Drugie pytanie brzmi: jak klient znajduje swój własny punkt końcowy, zanim ten tunel powstanie? To wyszukiwanie odbywa się poza tunelem, w sieci bazowej, w procesie klienta. Może używać nazwy i musi używać resolvera, do którego urządzenie dociera bez 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

Trzy etapy, trzy różne objawy. Jeden komunikat błędu «nie można się połączyć» skleja je razem, i dlatego ludzie zaczynają edytować pole serwera DNS, żeby naprawić problem, który występuje, zanim to pole w ogóle zostanie odczytane.

Dlaczego bootstrap może się zablokować

Always-on z włączoną blokadą oznacza, że ruch spoza VPN jest odrzucany, także ruch aplikacji, która jest właścicielem VPN. Wyjście jest jawne: aplikacja wywołuje protect() na gnieździe, co wiąże je z siecią bazową, dzięki czemu omija tunel i może wyjść na zewnątrz. Każdy klient VPN musi to robić dla własnego transportu, inaczej nigdy nie dotrze do serwera.

Rozważmy teraz wdrożenie, w którym jedyny resolver znający nazwę punktu końcowego sam znajduje się wewnątrz tunelu. Klient potrzebuje tunelu, aby rozwiązać nazwę, a nazwy, aby zbudować tunel. To prawdziwa zależność cykliczna i żadna konfiguracja klienta jej nie rozwiąże. To błąd projektowy, nie błąd programu.

Czas ujawnia tę samą zależność w łagodniejszych postaciach. Przy starcie przed pierwszym odblokowaniem urządzenie może nie mieć jeszcze użytecznej sieci; w captive portalu sieć istnieje, ale blokuje wszystko poza portalem; przy przejściu z Wi-Fi na sieć komórkową zmienia się sieć bazowa chronionego gniazda i klient musi to zauważyć oraz rozwiązać nazwę ponownie. Każde z nich jest zdarzeniem bootstrapowym i każde warto testować celowo, zamiast odkrywać o 3 w nocy.

Dlaczego przypięcie odpowiedzi nie jest rozwiązaniem

Kuszącym obejściem jest rozwiązanie nazwy raz i wklejenie wynikowego adresu w pole numeryczne albo zapisanie go na stałe we własnej kompilacji. Wygląda, że działa, i zamienia nazwę w migawkę. Gdy operator przenosi punkt końcowy — migracja, zmiana anycast, nowy adres regionalny — klient nadal łączy się ze starym, a nic w interfejsie tego nie wyjaśnia.

Rekordy DNS mają czas życia właśnie po to, aby odpowiedzi mogły się zmieniać; buforowanie jest definiowane przez ten czas, a nie na zawsze. Przypięcie odpowiedzi wyrzuca mechanizm, który czyni nazwę lepszą od adresu, zachowując przy tym koszt operacyjny korzystania z nazwy.

Przypinanie idzie zwykle w parze z drugim złym nawykiem: luzowaniem weryfikacji tożsamości, bo połączenie teraz zawodzi inaczej. Weryfikacja tożsamości serwera nie jest częścią problemu bootstrapu, a jej osłabianie wymienia niedogodność DNS na ryzyko przechwycenia.

Projekty odporne na restart

Trwałe rozwiązanie jest mało efektowne. Używaj klienta, który poprawnie implementuje chronione gniazda i rozwiązuje punkt końcowy w sieci bazowej, i daj mu nazwę punktu końcowego, która rozwiązuje się publicznie lub przez resolver osiągalny przed tunelem. Zostaw blokadę włączoną; wyłączenie jej w całości, aby DNS działał, usuwa właściwość, dla której wdrożenie istnieje.

Tam, gdzie punkt końcowy musi pozostać prywatny, zapewnij jawną ścieżkę bootstrapu, której platforma może użyć — resolver osiągalny bez tunelu albo połączenie zarządzające utrzymywane przez operatora — i trzymaj ten wyjątek wąski oraz należący do polityki, a nie do przełącznika użytkownika. Dokumentacja Android VpnService jest odniesieniem do tego, co platforma gwarantuje w zakresie ochrony i cyklu życia, a odniesienie VpnService.Builder potwierdza, że pole DNS przyjmuje adresy numeryczne.

Następnie zweryfikuj trzy etapy, w kolejności, na nowym połączeniu po restarcie: nazwa punktu końcowego się rozwiązuje, tunel się nawiązuje i następuje uwierzytelnienie peera, a nazwa prywatna rozwiązuje się przez resolver tunelu. Jeśli pierwszy etap zawiedzie, nic późniejsze nie może działać, a poprawka należy do ścieżki bootstrapu — nigdy do pola resolvera.

  • Rozwiązywanie punktu końcowego odbywa się w aplikacji, poza tunelem, na chronionym gnieździe.
  • Pole DNS w builderze jest dla aplikacji wewnątrz tunelu, nie dla własnego punktu końcowego klienta.
  • Resolver osiągalny tylko przez tunel to zablokowanie, a nie wolny start.
  • Testuj punkt końcowy, uwierzytelnianie i prywatne wyszukiwanie osobno po restarcie.

Powiązane artykuły