Bypass cgroup w nftables wymaga też poprawki adresu źródłowego
Oznaczenie jednej cgroup wysyła ją do innej tabeli, a gniazdo zachowuje adres, który już miało. Klasyfikacja, routing i translacja to trzy osobne kroki.
Oznaczanie ruchu z jednej cgroup to łatwa część. Trzy osobne decyzje muszą się zgrać, zanim jedna aplikacja wyjdzie przez interfejs fizyczny, a wszystko inne zostanie w tunelu: które gniazdo należy do wyjątku, które wyszukanie trasy uruchamia znacznik i jaki adres źródłowy niesie pakiet, gdy wychodzi. Większość nieudanych prób robi pierwszą dobrze, drugą połowicznie, a na trzecią nigdy nie patrzy.
Trzy decyzje, nie jedna
Klasyfikacja odpowiada, kto jest zwolniony. Routing odpowiada, dokąd zamiast tego trafia oznaczony pakiet. Wybór źródła odpowiada, co zobaczy drugi koniec i dokąd ma wrócić odpowiedź.
Te rzeczy zawodzą niezależnie. Pakiet może zostać sklasyfikowany, przekierowany na interfejs fizyczny i nadal nieść prywatny adres, którego żaden router nadrzędny nie może odesłać, co daje przekroczenie czasu wyglądające dokładnie jak blokada zapory. Nic w kroku klasyfikacji cię o tym nie ostrzega. Nic w kroku routingu też nie.
Kolejność też ma znaczenie. Klasyfikacja dzieje się, gdy pakiet jest tworzony, routing potem, a translacja w punkcie wyjścia.
Jeśli nie potrafisz wskazać, który z trzech etapów jest zepsuty, nie debugujesz, tylko przestawiasz reguły i liczysz na szczęście.
Dopasuj cgroup, która będzie istnieć także jutro
nftables potrafi sprawdzić przynależność gniazda do cgroup v2, co jest naprawdę przydatne, bo podąża za procesem, a nie za celem. Haczyk tkwi w identyfikatorze. Aplikacja desktopowa uruchomiona z menu często żyje w tymczasowym scope, którego nazwa zmienia się przy następnym starcie.
nft list ruleset
systemctl status session.slice 2>/dev/null
cat /proc/self/cgroupSkopiowanie nazwy scope z jednej sesji i wpisanie jej na stałe daje regułę, która działa dziś, a jutro po cichu przestaje pasować. Zamiast tego umieść aplikację w świadomym, stabilnym kontekście wykonania, na przykład w dedykowanym scope lub unicie, który kontrolujesz, i dopasuj właśnie ten. Reguła przetrwa wtedy restarty i aktualizacje pakietów. Staje się też czymś, o czym współpracownik może myśleć bez twojej historii sesji.
Bądź też precyzyjny co do mechanizmu, którego używasz. Klasyfikacja net_cls w cgroup v1 i dopasowanie gniazd w cgroup v2 to różne rzeczy, a podobnie nazwane pole w dokumentacji nie czyni ich wymiennymi.
Drugie wyszukanie uruchamia łańcuch route
Dla ruchu generowanego lokalnie typ łańcucha decyduje, czy coś się stanie po ustawieniu znacznika. Łańcuch output typu filter może oznaczyć pakiet, a jądro nadal będzie używać trasy, którą już wybrało. Łańcuch typu route to mechanizm, który prosi o nowe wyszukanie.
table inet bypass {
chain mark {
type route hook output priority mangle; policy accept;
socket cgroupv2 level 2 "app.slice" meta mark set 0x1 comment "app bypass"
}
}
ip rule add fwmark 0x1 lookup 200
ip route add default via 203.0.113.1 dev wlan0 table 200Znacznik potrzebuje wtedy reguły polityki, która wyśle go do tabeli, a ta tabela musi być kompletna: trasa domyślna, cele lokalne, których aplikacja nadal potrzebuje, oraz brama nadrzędna, która rzeczywiście odpowiada.
Wybierz wartość znacznika świadomie. WireGuard używa własnego fwmark dla zewnętrznego ruchu transportowego, a przypadkowe trafienie w ten sam numer może zakłócić wyjątek routingu samego tunelu w sposób trudny do zdiagnozowania.
Adres źródłowy nie podąża za trasą
Gniazdo, które już wybrało adres źródłowy dla ścieżki tunelu, zachowuje go po przekierowaniu. Nic w zmianie trasy go nie przepisuje, dlatego pakiet wychodzi przez interfejs fizyczny z prywatnym źródłem, którego sieć nadrzędna nie może odesłać.
Widocznym objawem jest aplikacja, która zdaje się wysyłać dane i nigdy nie otrzymuje odpowiedzi, oraz log zapory, który nic nie pokazuje, bo pakiet nigdy nie wrócił.
Wąska poprawka to reguła translacji źródła ograniczona do znacznika tej aplikacji i jej rzeczywistego interfejsu wyjściowego, zintegrowana z istniejącym NAT, a nie zastępująca go:
table inet bypass {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
meta mark 0x1 oifname "wlan0" masquerade
}
}Transluj wyjątek i nic więcej. Ogólna reguła masquerade naprawia objaw i niszczy możliwość odróżnienia, który ruch poszedł którą drogą. Dopasuj znacznik, rzeczywisty interfejs wyjściowy i, jeśli możesz, adres samej aplikacji. Szerokie reguły łatwo napisać i prawie niemożliwe później zaudytować.
Co wciąż wymyka się wyjątkowi
Rozwiązywanie nazw może pochodzić z osobnego procesu resolvera w innej cgroup, więc sklasyfikowanie gniazda przeglądarki nie klasyfikuje jej zapytań. Jeśli zapytania resolvera zostają w tunelu, a połączenie wychodzi poza niego, odpowiedzi split-horizon mogą nie pasować do ścieżki. Ta niezgodność daje awarie wyglądające jak błędy DNS, którymi nie są.
Procesy potomne dziedziczą cgroup w większości konfiguracji, ale helper, który przenosi się do własnego scope, nie. Sprawdzaj, zamiast zakładać, zwłaszcza w przypadku wszystkiego, co uruchamia się ponownie.
Na koniec zdecyduj, co ma się stać, gdy tunel jest wyłączony. Wyjątek, który po cichu staje się pełnym bypassem, to inna postawa bezpieczeństwa niż ten, który zamyka się przy awarii, i tylko jedna z nich jest tym, co miałeś na myśli.