Napraw ruch Dockera i WSL, który ignoruje VPN hosta
Kontenery i WSL trzymają własne trasy, resolver i ścieżkę przekazywania, więc działający tunel hosta zostawia je ze starym DNS lub bez wyjścia. Napraw każdą warstwę.
Tej wiosny spędziłem większość poranka na tropieniu kontenera, który nie potrafił rozwiązać ani jednej nazwy wewnętrznej, podczas gdy przeglądarka obok otwierała wiki bez skarg. Tunel był podniesiony, przeglądarka to udowadniała, a kontener siedział w innej przestrzeni nazw sieci, z inną tablicą tras i innym resolverem. Poniżej znajduje się kolejność, którą teraz sprawdzam: najpierw tablice tras, potem rozwiązywanie nazw, następnie przekazywanie, a na końcu platforma hosta. Zatrzymaj się na pierwszym kroku, który już zawodzi, bo dodawanie reguł niżej w stosie ukrywa granicę zamiast ją przesuwać.
Porównaj trzy tablice tras, zanim zmienisz regułę
Kontener na domyślnym bridge'u Dockera ma jedną domyślną trasę prowadzącą do bramy docker0 i nie ma pojęcia, że tun0 istnieje. Host ma tun0, trasy wypchnięte przez VPN i być może regułę polityki, która wysyła ruch próbny przez inny interfejs, niż się spodziewasz. WSL dodaje trzecią tablicę za własnym wirtualnym adapterem. Wydrukuj wszystkie trzy, zanim edytujesz którąkolwiek.
| Context | Command | What a wrong answer looks like |
|---|---|---|
| Host | ip route get 10.8.0.20 | Leaves a physical interface instead of tun0 |
| Container | ip route | Only 172.17.0.0/16 via 172.17.0.1 |
| Container DNS | cat /etc/resolv.conf | A public resolver, or an upstream the tunnel never pushed |
| WSL | ip addr; ip route | Only the WSL subnet, no Windows VPN adapter |
Jeśli jedyną trasą kontenera jest bridge, a tun0 nigdy nie pojawia się w jego przestrzeni nazw, nic poniżej nie pomoże: żadna zmiana serwera nazw nie tworzy trasy, a żadna trasa nie tworzy tożsamości tunelu, którą zaakceptuje peer. Kolejność jest ważna, bo awaria DNS i awaria trasy wyglądają identycznie od środka powłoki.
Skieruj resolver na tunel, a nie na publiczny
Na hoście, resolvectl status wylicza serwery DNS na połączenie, a readlink -f /etc/resolv.conf mówi, czy plik jest stubem systemd, plikiem NetworkManager czy czymś, co przepisał klient dostawcy. Wewnątrz kontenera w sieci zdefiniowanej przez użytkownika, /etc/resolv.conf nazywa 127.0.0.11, wbudowany resolver Dockera, który przekazuje do tego upstreamu, z którym skonfigurowano demona. Kontener na domyślnym bridge'u dostał natomiast przy tworzeniu kopię pliku hosta i trzyma ją.
Ta kopia jest powodem, dla którego tunel podniesiony po starcie kontenera zostawia kontener pytający stary upstream. Albo przekaż resolver VPN jawnie przez docker run --dns 10.8.0.53, albo ustaw "dns": ["10.8.0.53"] w /etc/docker/daemon.json i zrestartuj demona, albo odtwórz kontener, gdy tunel jest podniesiony. Odtworzenie jest zwykle tańsze niż debugowanie resolvera, który godzinę temu był poprawny.
Zmiana na publiczny resolver to naprawa, która zachowuje objaw. Rozwiązuje internet i zawodzi w każdej strefie prywatnej, a także może po cichu ominąć filtrowanie, dla którego tunel istnieje. Jeśli spróbujesz pisać w /etc/resolv.conf działającego kontenera, spodziewaj się Device or resource busy: Docker bind-montuje ten plik, więc edycja wymaga najpierw odmontowania. Ustawienie --dns przy tworzeniu to sekwencja, która przetrwa restart.
Przekazuj i tłumacz, gdy kontenery pożyczają tunel
Z domyślnym bridge'em host jest routerem między docker0 a tun0 i domyślnie nie wykonuje żadnego z tych zadań. Włącz przekazywanie i przetłumacz adres źródłowy, aby peer widział adres tunelu hosta, a nie adres, do którego nie ma trasy powrotnej (zastąp tun0 tym, jak twój klient nazywa interfejs):
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
iptables -A FORWARD -i docker0 -o tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -o docker0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTReguły FORWARD mają znaczenie na hostach, których polityka przekazywania to DROP. Docker wstawia własne łańcuchy, więc testuj pakietem, a nie czytaniem iptables -S: z kontenera, ip route get 10.8.0.20 powinno nazwać bramę bridge'u, a nc -vz 10.8.0.20 443 powinno połączyć się lub przekroczyć limit czasu na tunelu, a nie na resolverze.
Bez MASQUERADE peer potrzebuje trasy powrotnej do 172.17.0.0/16, której większość VPN-ów dostępu zdalnego nie wypchnie. Tu także klient oparty na politykach psuje plan: jeśli przechwytuje tylko ruch z określonych UID lub cgroups, twoje reguły przekazywania nigdy nie widzą pakietu wewnątrz tunelu, a poprawka należy do polityki routingu klienta, a nie do iptables. Reguły dodane ręcznie znikają po restarcie, więc umieść je w jednostce systemd lub w tabeli netfilter-persistent i przetestuj ponownie po restarcie, ponieważ tunel podniesiony późno może je zmyć.
Umieść tunel wewnątrz kontenera, gdy celem jest izolacja
Jeśli jeden kontener musi wychodzić przez konkretny węzeł, uruchom klienta w tym kontenerze. Potrzebuje --cap-add NET_ADMIN i --device /dev/net/tun, a wtedy ma własny tun0, trasy i zaporę. Jeśli inne kontenery przechodzą przez niego, potrzebuje także przekazywania i reguły MASQUERADE wewnątrz kontenera, a ci peerzy potrzebują jawnej trasy lub proxy wskazującego na niego; domyślny bridge Dockera nie dodaje tej trasy za ciebie.
Sieć hosta, docker run --network host, to tania wersja: kontener dołącza do przestrzeni nazw hosta i widzi trasy tunelu bezpośrednio. Porzuca też izolację sieciową, zaprasza kolizje portów i w praktyce jest opcją Linuksa. Opublikowany port nie jest zamiennikiem. -p 8080:80 udostępnia usługę z hosta; nie udostępnia wewnętrznych adresów 10.x z wnętrza kontenera, a mieszanie tych dwóch rzeczy to droga, na której problem routingu staje się wystawionym portem bazy danych.
Odróżnij awarię nazwy od nakładającej się podsieci
Domyślny bridge Dockera to 172.17.0.0/16, a wiele firmowych pul VPN leży w 172.16.0.0/12. Gdy tunel posiada ten zakres, ruch przeznaczony dla kontenera może zostać skierowany do tunelu. Nazwy zawodzą, błąd wygląda jak DNS, a problemem są adresy. Sprawdź cel, nie resolver: ip route get 172.17.0.5 na hoście, następnie porównaj prefiks bridge'u z pulą VPN i z docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'.
Naprawa polega na przesunięciu jednego z zakresów: albo innej domyślnej puli adresów dla Dockera w daemon.json , albo innej puli na koncentratorze. Dodanie kolejnego serwera nazw nic tu nie daje, podobnie jak ssh -b: przypisanie klienta do interfejsu zmienia adres źródłowy, a nie trasę docelową prowadzącą do tunelu. Szybki podział pracy: nazwa, która rozwiązuje się na hoście, ale nie w kontenerze, to problem resolvera, natomiast bezpośrednie połączenie IP, które wisi w kontenerze, ale działa na hoście, to problem trasy lub łańcucha przekazywania.
WSL i Docker Desktop nie przestrzegają reguł Linuksa
W swoim domyślnym trybie NAT WSL siedzi za własnym wirtualnym adapterem i nie otrzymuje wewnętrznych tras ani sufiksów DNS VPN-a Windows, dlatego przeglądarka działa, a ping wewnątrz dystrybucji nie. Sieć lustrzana, dostępna w nowszych kompilacjach Windows 11, współdzieli zamiast tego interfejsy hosta, ale trzeba ją włączyć w .wslconfig i zrestartować dystrybucję poleceniem wsl --shutdown. Potwierdź aktywny tryb poleceniem wslinfo --networking-mode przed zastosowaniem poprawki, bo odpowiedź NAT jest błędna dla trybu lustrzanego i odwrotnie.
Docker Desktop dodaje kolejną warstwę. Kontenery Linuksa na Windowsie siedzą za implementacją NAT historycznie zwaną vpnkit, a ich ścieżka różni się od tej na natywnym hoście Linuksa. Kontenery Windowsa łączą się przez wirtualny przełącznik Hyper-V, a firmowy klient może blokować ruch z tego przełącznika, więc kontenery Linuksa na tej samej maszynie mogą docierać do sieci, podczas gdy kontenery Windowsa nie mają nic. Zewnętrzny przełącznik powiązany z fizycznym adapterem to zwykłe obejście i zmienia tożsamość sieciową kontenera w sposób, na który polityka może nie pozwalać.
Zanim napiszesz kolejną regułę, przejdź cztery warstwy po kolei i zatrzymaj się tam, gdzie któraś jest błędna:
- W kontenerze: czy
ip routedociera do prefiksu prywatnego i czy/etc/resolv.confnazywa upstream, który to rozwiązuje? - Na hoście: czy przekazywanie jest włączone, czy NAT jest powiązany z interfejsem tunelu i czy łańcuch przekazywania akceptuje przejście z bridge'u do tunelu?
- Po stronie peera: czy zna drogę powrotną, czy liczysz na to, że NAT ukryje podsieć kontenera?
- Na platformie: czy WSL jest w trybie NAT czy lustrzanym i czy kontener to Linux czy Windows?
Wykonaj każdy test ponownie w świeżym kontenerze lub powłoce. Długo działający proces z zbuforowaną odpowiedzią lub nawiązanym połączeniem będzie nadal zawodził po tym, jak konfiguracja będzie poprawna, a ta awaria wygląda dokładnie jak poprawka, która nie zadziałała.