Android always-on VPN: jak najpierw rozwiązywana jest nazwa punktu końcowego
Android blokuje ruch, zanim tunel powstanie, a klient i tak musi rozwiązać nazwę swojego punktu końcowego. Oto kolejność i miejsce, w którym się to blokuje.
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.
| Stage | Who resolves | Path | Failure looks like |
|---|---|---|---|
| Endpoint lookup | The VPN app | Underlying network, protected socket | Never connects; no tunnel interface traffic |
| Peer authentication | Client and server | Tunnel transport | Connects then drops; certificate or key error |
| Private name lookup | Apps via the tunnel | Resolver inside the tun | Tunnel 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.