Reguły dla portów 80 i 443 nie kierują przeglądarki przez VPN
Dopasowanie portu wybiera strumienie TCP, nie przeglądarkę. HTTP/3 działa po UDP, DNS może pójść inną drogą, a aktualizator trafia na tę samą regułę, którą napisałeś.
Ktoś prosi, żeby przeglądarka korzystała z tunelu, a odpowiedź wraca jako reguła firewalla dopasowująca porty docelowe 80 i 443. Wygląda precyzyjnie i taka nie jest. Dopasowanie portu wybiera pakiety po nagłówku transportowym, a nie po tym, który program otworzył gniazdo, a współczesne przeglądanie nie ogranicza się do tych dwóch portów na TCP. Reguła pominie część ruchu, który chciałeś objąć, i złapie ruch, którego nigdy nie zamierzałeś dotykać. Zanim ją napiszesz, zdecyduj, którą z trzech różnych granic naprawdę chcesz.
Reguła opisuje klasę transportu, nie aplikację
W Linuksie dopasowanie portu docelowego sprawdza nagłówek TCP i nic ponad nim. Oznaczanie tych pakietów i kierowanie nimi trasą polityki działa dokładnie tak, jak opisano, i żaden z tych mechanizmów nie wie, co oznacza ładunek ani który proces go wysłał. Znacznik to numer przypięty do pakietu, nie tożsamość przypięta do aplikacji.
nft add rule inet mangle output tcp dport { 80, 443 } meta mark set 0x1
ip rule add fwmark 0x1 table 100
ip route add default dev wg0 table 100
Od razu wynikają dwie konsekwencje. Aktualizator oprogramowania, który łączy się z TCP 443, dostaje takie samo traktowanie jak przeglądarka, co może być w porządku albo może być awarią czekającą na następny cykl aktualizacji. A usługa webowa świadomie opublikowana na innym porcie, powiedzmy 8443, wypada całkowicie poza regułę, choć nadal jest tym ruchem webowym, na którym zależało użytkownikowi.
Liczby są uprawnionymi selektorami, gdy wymaganie naprawdę dotyczy tych portów. Stają się fikcją, gdy w zgłoszeniu jest napisane „przeglądarka”, a w zestawie reguł implementowany jest zakres portów. Ta luka nie jest drobiazgiem dokumentacyjnym. To różnica między regułą, której możesz bronić na przeglądzie, a taką, której nikt nie potrafi przetestować.
HTTP/3 po cichu omija twoją regułę TCP
HTTP/3 działa po QUIC, a QUIC jest przenoszony w UDP. Reguła dopasowująca tylko TCP nie widzi go, więc ta sama przeglądarka, która przechodzi twoją stronę testową, może wysłać późniejsze żądanie przez UDP i ominąć tunel, bez zmiany jakiegokolwiek ustawienia. RFC 9114 opisuje HTTP/3 jako protokół oparty na QUIC, a QUIC używa UDP z domyślnym portem 443 w typowym wdrożeniu.
Czy dana przeglądarka spróbuje HTTP/3, zależy od jej konfiguracji, od witryny i od tego, czy poprzednia próba się udała. Dlatego źle się o tym rozumuje z pamięci. Zapytaj narzędzie:
curl --http3 -sS -o /dev/null -w '%{http_version} %{time_total}\n' https://example.com
ss -tunp | grep -E ':443\b'
Wiersz curl wypisuje wynegocjowaną wersję, więc 3 potwierdza, że użyto ścieżki UDP. Potem lista gniazd pokazuje, czy to połączenie jest przypięte do interfejsu tunelu, czy do fizycznego. Jeśli w twojej kompilacji curl nie ma HTTP/3, flaga zawodzi głośno zamiast po cichu się wycofywać, co jest jej własnym małym miłosierdziem.
DNS to osobna decyzja, którą musisz podjąć
Reguła portu dla ruchu webowego nie mówi nic o rozwiązywaniu nazw. Przeglądarka może używać resolvera systemowego, resolvera skonfigurowanego w przeglądarce albo tego, który wepchnęło ostatnie połączenie. Każdy z nich może wysyłać zapytania inną drogą niż ta, którą wybiera twoja reguła, co daje klasyczny rozłam: strona się ładuje, nazwa jest rozwiązywana gdzieś, gdzie się nie spodziewałeś, a witryna wewnętrzna rozwiązuje się na adres publiczny albo wcale.
Zdecyduj to wprost. Jeśli nazwy wewnętrzne mają się rozwiązywać przez tunel, resolver musi być w środku, a zestaw reguł musi zezwalać na DNS do tego resolvera, trzymając inne zapytania na zewnątrz. Jeśli przeglądarka używa DNS over HTTPS do publicznego dostawcy, zapytanie całkowicie omija twój resolver i reguła portu nie może pomóc. Zapisz wybór w tym samym rekordzie zmiany co regułę routingu, bo oba czyta razem ten, kto będzie debugował następną awarię.
Potem sprawdź resolverem, a nie stroną:
resolvectl query intranet.example.internal
resolvectl status | grep -A3 'Current DNS Server'
Brak odpowiedzi to problem rozwiązywania nazw, nawet gdy komunikat błędu przeglądarki obwinia sieć. Te dwie awarie wyglądają dla użytkownika identycznie i są całkowicie różnymi zgłoszeniami.
Trzy granice, trzy różne mechanizmy
Większość zamieszania znika, gdy nazwiesz granicę, którą chcesz. Nie są wymienne, a każda ma inny tryb awarii podczas przerwy w tunelu.
| What you actually want | Mechanism that fits | What it does not cover |
|---|---|---|
| These transport ports use this exit | Firewall mark plus policy route | Applications, HTTP/3 over UDP, DNS |
| This browsing session stays separate | Application-aware VPN, or a separate network namespace plus a proxy | Other applications in the same session |
| Only these private sites are reachable | Destination-based routes with private DNS | Public sites on the same address family |
Osobny profil przeglądarki to pułapka w środkowym wierszu. Oddziela ciasteczka i historię i niczego nie zmienia w tabeli routingu hosta, więc oba profile idą tą samą drogą. Izolacja wymaga czegoś, co wymusza jądro, czyli przestrzeni nazw, kontenera albo aplikacji mającej własny interfejs tunelu.
Który wiersz by nie obowiązywał, zapisz, co ma się stać, gdy tunel padnie. Trasa oparta na znaczniku, która znika wraz z interfejsem, zwykle wraca do fizycznej trasy domyślnej, co jest zachowaniem fail-open. To może być akceptowalne dla przeglądania i nieakceptowalne dla wszystkiego innego, i to jest decyzja, a nie szczegół implementacji.
Testuj strumienie, które rzekomo obejmujesz
Test akceptacyjny musi podążać za granicą, którą zapisałeś, a nie za wygodnymi numerami portów. Wybierz jeden strumień, który musi iść przez tunel, jeden, który nie może, i jeden, który używa transportu, o którym nie pomyślałeś:
ip route get 203.0.113.40
curl -sS -o /dev/null -w 'v4 %{http_version} %{remote_ip}\n' https://example.com
nft list ruleset | grep -A2 'dport'
Zapytanie o trasę mówi, którego interfejsu cel naprawdę używa, i to jest fakt, który większość raportów podaje błędnie. Wiersz curl potwierdza wynegocjowaną wersję i rodzinę adresów. Zrzut zestawu reguł łapie regułę dodaną podczas testów i zapomnianą ograniczyć, i tak tymczasowy znacznik staje się trwałym routingiem fail-open.
Zapisz wszystkie trzy wyniki ze znacznikiem czasu i trzymaj je razem ze zmianą. Gdy później ktoś zgłosi, że „VPN zepsuł intranet”, różnica między zamierzoną a wdrożoną granicą będzie widoczna w tym zapisie, a poprawka będzie zmianą reguły, nie dyskusją.