Jak wykryć aktywny tunel VPN z kodu na dowolnej platformie
Czy tunel istnieje, czy ta trasa przechodzi przez niego, a może odpowiada drugi koniec? To trzy różne kontrole i trzy różne sygnały.
„Czy VPN jest włączony?” to tak naprawdę trzy pytania w jednym zdaniu. Którego z nich potrzebuje wasz kod, jest pierwszą decyzją, i właśnie ją pomija większość implementacji. Czy na tym urządzeniu istnieje tunel, czy ruch do tej trasy wychodzi przez niego, czy drugi koniec odpowiada tożsamością, której oczekujecie. Wybierzcie właściwość, potem sygnał, który ją mierzy, i na końcu napiszcie w logu, który z nich faktycznie zmierzyliście.
Trzy właściwości, trzy sygnały
Ściśnięte do jednej linii każda, trzy pytania mają trzy różne odpowiedzi. Tabela poniżej jest samą decyzją, a nie jej streszczeniem.
| Property | Signal that answers it | What it still leaves open |
|---|---|---|
| Existence | The platform's own connection state: RAS on Windows, NetworkManager on D-Bus on Linux, scutil on macOS | Tunnels of a type the platform does not manage, which includes most third-party clients |
| Path | A route lookup for the one destination, or a scoped proxy or interface key that names the tunnel | Whether the far end is the peer you trust |
| Identity | An authenticated response from the far end, or a source-address policy applied on the server | Which local interface carried the packets |
Przeczytajcie ostatni wiersz powoli. Prywatny host, który odpowiada, dowodzi, że coś odpowiedziało, a nie że odpowiedź przyszła przez tunel, i nic nie mówi o tym, kto jest po drugiej stronie. Jeśli właściwością, której potrzebujecie, jest ścieżka, mierzcie ścieżkę.
Istnienie: własny stan platformy
Windows odpowiada na to dla profili natywnych przez Remote Access Service API. RasEnumConnections wymienia, co jest połączone, RasGetConnectStatus zgłasza stan jednego wpisu, a RasGetLinkStatistics dodaje prędkość, liczniki bajtów i wiek połączenia. Granice są tu sednem: OpenVPN, WireGuard i klienci producentów nigdy nie rejestrują połączenia RAS, więc zdrowy tunel tego typu pokazuje pustą listę, a naiwny kod zgłasza go jako wyłączony.
Pulpity Linuksa udostępniają tę samą właściwość przez NetworkManager na D-Bus, gdzie PropertiesChanged na obiekcie połączenia VPN zamienia kontrolę w zdarzenie, a właściwość State zgłasza, kiedy łącze osiąga stan połączony. Z powłoki nmcli connection show --active czyta te same informacje, i to jest to, czego chcecie podczas debugowania. macOS trzyma tę listę w frameworku System Configuration, a scutil jest jego widokiem z wiersza poleceń. Wszystkie trzy opisują połączenie zarządzane przez platformę, i wszystkie trzy milkną o tunelu, którego nie zarządza.
Ścieżka: dokąd pakiet naprawdę trafia
Dopasowanie nazw to skrót, po który wszyscy sięgają najpierw: wyliczcie interfejsy, sprawdźcie ciąg wobec tun, tap, ppp lub ipsec i potraktujcie trafienie jako dowód. To heurystyka, a zbiór interfejsów może wyglądać tak samo niezależnie od tego, czy coś przez niego płynie. Starsze wersje iOS odpowiadały w ten sposób, a późniejsze przestały, bo widoczne interfejsy nie rozróżniały już tych dwóch stanów.
Tym, co to tam zastąpiło, jest słownik serwera proxy systemu. Dopóki VPN jest połączony, wywołanie ustawień proxy zwraca klucze ograniczone do interfejsów powiązanych z tunelem, a gdy nic nie jest połączone, słownik wraca pusty. To twierdzenie słabsze, niż brzmi, ponieważ zapisuje przekonanie systemu, że tunel istnieje, a nie to, która trasa go używa.
Android ma własną wersję tej pułapki: rozgłoszenie vpn.connectivity dociera do odbiorników zadeklarowanych w manifeście w niektórych kompilacjach, a w innych do odbiorników zarejestrowanych w czasie działania. Traktujcie je jako najlepszy wysiłek i trzymajcie za nim bezpośrednią sondę.
Kiedy liczy się trasa, pytajcie tablicę routingu, a nie listę adapterów. Heurystyki bramy domyślnej psują się na maszynach z wieloma sieciami i na tunelach, które instalują własną bramę.
Tożsamość: zbadaj drugi koniec
Pętla wysyłająca echo ICMP na drugi koniec co kilka sekund to najtańsza kontrola tego rodzaju, i zawodzi w użytecznym kierunku. Kierujcie ją na adres, który istnieje tylko wewnątrz sieci prywatnej, bo publiczny odpowiada przez dowolne działające łącze w górę i niczego nie dowodzi. Tam, gdzie ICMP jest filtrowany, krótkie połączenie TCP z portem usługi robi to samo.
Kontrola po stronie serwera ma dokładnie jeden sygnał: adres źródłowy na połączeniu. Utrzymywanie inwentarza zakresów to praca konserwacyjna, i nie potrafi oddzielić tunelu od dowolnej innej sieci, która przypadkiem używa tego samego bloku.
Zgrubna polityka może z tym żyć. Tożsamość nie może, więc uwierzytelniajcie klienta, zamiast ufać temu, skąd pozornie przychodzą jego pakiety.
Zapisujcie kategorię, nie wyrok
Trzymajcie kategorie awarii osobno w logu: brak jakiegokolwiek interfejsu, interfejs jest, ale trasa nieosiągalna, trasa osiągalna, ale uwierzytelnianie odrzucone. To trzej różni właściciele, i każdy otwiera inne śledztwo.
Zlepienie ich w jedno „VPN padł” wysyła raport do zespołu, który nie może tego naprawić. Kategoria jest użytecznym wynikiem; wyrok jest tym, co czyni go błędnym.