Prośba zawsze jest wariantem tego samego zdania: zachowaj VPN do przeglądania, ale pozwól SSH albo zadaniu kopii zapasowej korzystać ze zwykłego połączenia. Krążąca na ten temat porada, żeby przekierować port regułami NAT w iptables, tego nie robi. Reguła NAT przepisuje adresy i porty wewnątrz przepływu; nigdy nie wybiera interfejsu, którym pakiet wychodzi. Ta decyzja należy do wyszukiwania trasy, a wyszukiwanie trasy potrzebuje pomocy, by zobaczyć port.

Dlaczego zwykła trasa nie dopasuje portu

Linux wybiera trasę, wyszukując adres docelowy w tabeli, po odfiltrowaniu tabel kandydujących przez reguły polityki. Te reguły mogą dopasować znacznik firewalla, adres źródłowy, interfejs przychodzący albo UID. Wpis trasy sam nie ma pola portu, bo trasa jest wybierana, zanim nagłówek transportowy zaczyna mieć znaczenie.

Obecne iproute2 i jądra rozszerzają selektor reguł: ipproto, sport i dport są akceptowane, więc ip rule add ipproto tcp dport 22 table 100 wyraża zamiar w jednej linii. Uruchom ip rule add help na rzeczywistym hoście, aby sprawdzić, czy twoja kompilacja wymienia dport. Jeśli tak, załatwiasz to jedną komendą; jeśli nie, opisana niżej ścieżka ze znacznikiem jest równoważna.

Tak czy inaczej reguła to tylko wskaźnik. Tabela 100 nadal potrzebuje trasy, i to jest ten element, o którym ludzie zapominają.

Trzy elementy albo jeden

Nowoczesna postać to jedna reguła powyżej plus trasa w tabeli alternatywnej. Postać klasyczna dodaje po drodze netfilter: oznacz pakiet, a potem dopasuj znacznik w regule. Obie kończą się na tym samym wyszukiwaniu; różnią się miejscem, w którym czytany jest port.

Podejście ze znacznikiem istnieje, bo netfilter widzi nagłówek transportowy, a kod routingu historycznie go nie widział. To cały mechanizm i wyjaśnia, dlaczego wariant zapasowy to trzy współpracujące elementy, a nie jedna sprytna komenda.

Trzy elementy po kolei

Najpierw oznacz przepływy, które chcesz skierować inaczej, w łańcuchu uruchamianym wystarczająco wcześnie, by znacznik istniał przed decyzją o trasie:

nft add table inet vpnexempt
nft 'add chain inet vpnexempt out { type route hook output priority mangle; }'
nft add rule inet vpnexempt out tcp dport 22 meta mark set 0x1

Po drugie, wyślij oznaczone pakiety do osobnej tabeli:

ip rule add fwmark 0x1/0x1 table 100

Po trzecie, daj tej tabeli trasę domyślną przez łącze fizyczne. Adresy są przykładowe; wstaw własne:

ip route add default via 192.168.1.1 dev wlan0 table 100

Pomiń trzeci krok, a oznaczony pakiet nie znajdzie trasy w tabeli 100, więc zawiedzie. Objaw wygląda tak, jakby reguła zepsuła sieć, choć w rzeczywistości usunęła jedyną drogę, jaka pakietowi została.

Co utrzymuje to w działaniu po pierwszym pakiecie

Znaczniki dotyczą pakietów wychodzących. Odpowiedź przychodzi na interfejs fizyczny z adresem przydzielonym przez to łącze, a tabela główna może nadal preferować tunel dla tego celu. Dwa mechanizmy utrzymują spójność drogi powrotnej: reguła oparta na źródle dla adresu łącza oraz connection tracking, który zachowuje decyzję przez cały czas życia przepływu. Zapisanie znacznika w conntrack i odtworzenie go przy odpowiedzi to zwykły sposób na stabilny wyjątek stanowy.

Filtrowanie odwrotnej ścieżki to drugi cichy zabójca. Jeśli jądro ściśle weryfikuje drogę powrotną, a odpowiedź przychodzi interfejsem, którego tabela główna by nie wybrała, pakiet jest odrzucany bez komunikatu. rp_filter wartości domyślne różnią się między dystrybucjami i zależą od tego, czy interfejs jest przekazywany dalej, więc czytaj wartość efektywną, zamiast zakładać, że jest wyłączona.

Nie oznaczaj własnego transportu tunelu. Tunel WireGuard albo UDP OpenVPN to nie TCP 22, więc powyższy przepis jest bezpieczny, ale wyjątek zapisany jako wszystko poza celem tunelu może przechwycić zaszyfrowane pakiety i zepsuć tunel w sposób, który wygląda jak pętla routingu.

Sprawdź to, zamiast zakładać

Zapytaj jądro, którą drogę by wybrało, dla celu i dla portu, a potem wykonaj prawdziwe przesyłanie:

ip route get 203.0.113.10
ip rule show
nft list ruleset

Jeśli znacznik istnieje, ale liczniki nigdy się nie zmieniają, reguła nie dopasowuje. Liczniki to różnica między konfiguracją, którą napisałeś, a polityką, którą jądro stosuje.

Kiedy to jest niewłaściwe narzędzie

Wyjątki oparte na porcie są z natury kruche. Usługi przenoszą porty, HTTP/3 przychodzi jako UDP 443, a jedna aplikacja może otworzyć kilka przepływów różnego rodzaju. Jeśli prawdziwym wymaganiem jest to, że ten program korzysta ze zwykłego łącza, warstwa routingu jest niewłaściwym miejscem; lepiej pasuje przestrzeń nazw sieci albo proxy dla aplikacji.

Jeśli wymaganiem jest to, że ta podsieć korzysta ze zwykłego łącza, pomiń znaczniki całkowicie i dodaj bardziej szczegółową trasę. Jest prościej, jest widoczna w jednej tabeli, a następna osoba może to sprawdzić bez czytania zestawu reguł.

Zapisz, które z trzech wymagań naprawdę masz, zanim dotkniesz firewalla. Powyższy mechanizm jest precyzyjny, a precyzja zastosowana do niewłaściwej warstwy to tylko dłuższa awaria.

Powiązane artykuły