Przypisanie jednej aplikacji Linux do VPN za pomocą reguł UID
Reguła UID widzi tylko gniazda lokalne, a zwykły wyjątek loopback zabija tunel. Najpierw skieruj konto, potem filtruj i weryfikuj każdy krok.
Przypisanie jednego programu do VPN w systemie Linux to przede wszystkim problem routingu, a reguła firewalla decyduje tylko o tym, co dzieje się po wybraniu trasy. Jeśli dopasujesz UID programu w OUTPUT, zanim tunel przejmie trasę domyślną, reguła odrzuci dokładnie ten ruch, który chciałeś chronić. Ta sama reguła nie widzi też procesu resolvera, który odpowiada na zapytania DNS programu. Ustaw kolejność poprawnie, a jedno konto bez uprawnień stanie się niezawodnym selektorem podzielonego tunelu.
Daj kontu trasę przed wyrokiem
Dopasowanie właściciela odpowiada na jedno pytanie: czy pakiet zbudowało lokalne gniazdo należące do tego UID? Nie wybiera interfejsu i nie może przenieść ruchu, który główna tabela już wysyła przez fizyczną kartę sieciową. Dlatego reguła UID bez pasującej trasy blokuje program zamiast go przekierowywać.
Najpierw przeprowadź konto przez jego własną tabelę, a potem pozwól firewallowi potwierdzić wynik. Działa to na hoście, którego iproute2 rozumie uidrange, co ip rule help zgłasza w jednej linii. Tabela 100 trzyma trasę domyślną przez tunel, a reguła wysyła tylko UID 1000 do tej tabeli. Wszystko inne zostaje w tabeli głównej i nadal korzysta z łącza fizycznego.
ip rule add uidrange 1000-1000 table 100 ip route add default dev tun0 table 100
Drugie konto, które potrzebuje innego tunelu, dostaje tabelę 101 i własną regułę, bo dwa profile nie mogą jednocześnie mieć trasy domyślnej w tej samej tabeli. Profile NetworkManager są systemowe, więc ta część pozostaje ręczna, a reguły znikają przy ponownym uruchomieniu, jeśli nic ich nie odtworzy. Zapisz tabele w skrypcie startowym obok definicji tunelu, aby ponowne połączenie nie zostawiło jednego konta na złej ścieżce.
Dlaczego wyjątek loopback zabija tunel
Większość skopiowanych wersji reguły odrzuca każdy pakiet, który nie wychodzi przez loopback. Tunel nie jest loopbackiem, więc ta forma odrzuca też ruch tunelu, a wynik wygląda jak martwy VPN, a nie jak błąd firewalla.
Gdy OUTPUT działa, jądro wybrało już interfejs wyjściowy, więc -o porównuje się z odpowiedzią trasy, a nie z nagłówkiem pakietu. Dopasowanie właściciela jest sprawdzane tylko w OUTPUT i POSTROUTING, a przekazywane pakiety nie niosą gniazda, które mogłoby przeczytać. Ten zestaw oznacza, że wyjątek musi nazwać interfejs tunelu i zaprzeczyć temu dopasowaniu, a nie zaprzeczać loopbackowi.
Loopback nadal zasługuje na własny ACCEPT ponad DROP, bo pierwszy pakiet DNS z programu idzie do lokalnego stuba przez lo. Ustaw dwie reguły w tej kolejności, a konto dotrze do stuba, potem do tunelu, i nigdzie indziej.
iptables -I OUTPUT 1 -o lo -m owner --uid-owner vpnapp -j ACCEPT iptables -A OUTPUT -m owner --uid-owner vpnapp ! -o tun0 -j DROP
IPv6 to osobny zestaw reguł, a para tylko dla v4 zostawia ip6tables nietknięty. Program, który preferuje rekordy AAAA, wyszedłby wtedy przez łącze fizyczne, nigdy nie docierając do DROP. Odbij obie reguły w ip6tables, albo wyłącz IPv6 na hoście, gdy tunel nie niesie adresu v6. Reguły dodane ręcznie też znikają przy ponownym uruchomieniu, więc zapisz je za pomocą netfilter-persistent save lub odtwórz tę samą logikę jako tabelę nftables ładowaną przez jednostkę startową.
Odpowiedzi DNS przychodzą z innego procesu
Zapytanie programu do 127.0.0.53 przechodzi przez ACCEPT loopback i właśnie tam dopasowanie UID się kończy. Resolver stub przekazuje dalej w górę ze swojego konta usługi, więc reguła powiązana z vpnapp nigdy nie widzi drugiego zapytania. Jeśli resolver wybierze serwer LAN dla łącza, odpowiedzi wracają przez fizyczną kartę sieciową, podczas gdy własne pakiety programu zostają w tunelu. Widoczny wynik to działający tunel z zepsutymi nazwami albo nazwy odpowiadane przez serwer, którego nie wybrałeś.
Napraw rozwiązywanie nazw na łączu tunelu, a nie w aplikacji. Daj profilowi tunelu serwer DNS i domenę routingu, a potem potwierdź za pomocą resolvectl query i resolvectl status tun0 że resolver używa go dla nazw, które się liczą. W przestrzeni nazw sieci problem znika, bo resolver tej przestrzeni zna tylko tunel.
Sprawdź regułę z samego konta
Zapytaj jądro, jaką trasę dostaje UID, potem wykonaj żądanie jako to konto i porównaj adres wyjściowy z adresem własnego konta. Jeśli oba zgłoszą ten sam adres, reguła dopasowuje coś innego niż testowany ruch. Pierwsze polecenie poniżej pokazuje wyszukanie trasy bez wysyłania pakietu, co oddziela błąd routingu od błędu firewalla.
ip route get 1.1.1.1 uid 1000 sudo -u vpnapp curl -s https://ifconfig.co
Liczniki pokazują, czy reguła kiedykolwiek zadziałała. Uruchom iptables -vnL OUTPUT, odczytaj liczbę pakietów obok każdej reguły i potraktuj zero obok DROP po rzeczywistym ruchu jako błąd dopasowania.
Ruch przekazywany jest poza tym projektem. Kontener lub VM za mostem nie ma lokalnego gniazda należącego do vpnapp, więc dopasowanie właściciela nigdy nie zadziała, a te pakiety idą zgodnie z polityką FORWARD. Jeśli ten ładunek ma być objęty, przetestuj go osobno i napisz politykę powiązaną z mostem lub adresem kontenera. Projekt się trzyma, gdy tabela, wyjątek interfejsu, resolver i reguły v6 wskazują w tę samą stronę; pomiń którykolwiek z nich, a konto albo przecieka, albo traci łączność.