Jak kierować tylko ruch kontenerów Docker przez OpenVPN
Zostaw host na jego zwykłym uplinku i umieść jedną podsieć kontenerów w tunelu: kolejność NAT, reguły źródła, OpenVPN iroute i kontrole MSS.
Pakiet kontenera nigdy nie dociera do tun0 , a dodatkowe uprawnienia kontenera tego nie zmienią. Między gościem a tunelem stoją dwa wyszukania trasy: jedno w przestrzeni nazw sieci gościa, jedno na hoście po tym, jak ramka wyląduje na moście, a drugie pyta innej tabeli z innymi regułami dopasowania. Moja konfiguracja referencyjna to host Linux z OpenVPN klientem, który trzyma 10.8.0.10 na tun0 , mostem Docker na 172.18.0.0/16 , i prywatnym adresem, do którego dotrze tylko strona tunelu. Cel pozostaje wąski: ta podsieć gościa wychodzi przez tunel, a host zachowuje zwykły uplink.
Uruchamianie klienta VPN w kontenerze to inny projekt , nie skrót. Wtedy kontener posiada własny tunel i własne dane logowania, a praca przenosi się do Dockerfile zamiast zostawać w iptables.
Przekazywanie wybiera trasę według celu, potem NAT przepisuje źródło
Gdy ustawione jest net.ipv4.ip_forward=1 , przekazywany pakiet jest routowany tylko według celu, w tabeli głównej. Źródło nie odgrywa w tej decyzji żadnej roli. Dopiero po wybraniu trasy jądro uruchamia POSTROUTING , i tam właśnie siedzi własna reguła Dockera: -s 172.18.0.0/16 ! -o br-appnet -j MASQUERADE . Wszystko, co opuszcza most w stronę innego interfejsu, jest tłumaczone, a adres, którym się staje, zależy od interfejsu, do którego trasa je wysłała.
Ta reguła wyjaśnia większość objawów, o które mnie pytają. Jeśli tabela główna nie ma trasy do prywatnego celu, pakiet opuszcza uplink z fizycznym adresem hosta. Twój dostawca odrzuca cel, albo zdalna witryna odrzuca źródło RFC 1918, którego się nie spodziewała, i kontener kończy się przekroczeniem czasu, podczas gdy tunel stoi i jest zdrowy. Przechwycenie na tun0 nic nie pokazuje, i to jest wskazówka. Jeśli trasa rzeczywiście wskazuje na tun0 , MASQUERADE przepisuje źródło na 10.8.0.10 , i ten sam kontener działa bez dotykania czegokolwiek.
Poproś o wyszukanie, które nazywa źródło gościa. Bez from i iif host odpowiada za własne gniazdo i zgłasza ścieżkę, której przekazywany pakiet nigdy nie obierze:
ip route get 192.168.11.222 from 172.18.0.5 iif br-appnetJeśli ta odpowiedź nazywa uplink, tunel nie jest w grze i żadne ustawienie po stronie tunelu nie pomoże.
Przenieś gościa na własny adres tunelu
Najmniejsza działająca zmiana to jawne przepisanie źródła na wyjściu. Wstaw je, zamiast dopisywać: Docker siedzi już na czele POSTROUTING , więc reguła dodana przez -A jest oceniana za tą Dockera i nigdy nie zadziała.
iptables -t nat -I POSTROUTING 1 -s 172.18.0.0/16 -o tun0 -j SNAT --to-source 10.8.0.10Dwa warunki decydują, czy to się utrzyma. Adres musi być taki, który klient naprawdę ma, więc przypnij go wpisem na klienta zawierającym ifconfig-push 10.8.0.10 255.255.255.0 ; reguła nazywająca adres, który nigdy nie został przydzielony, daje ciche odrzucenia, nie błąd. A zdalna strona musi przyjąć to źródło i routować odpowiedzi z powrotem, dlatego lista dozwolonych tam nazywa adres tunelu, a nie podsieć gościa. Gdy adres klienta jest dynamiczny, -j MASQUERADE z -o tun0 podąża za tym, co ma interfejs, kosztem zdalnej polityki, przeciw której nie da się zapisać stałej wartości.
Filtrowanie to druga połowa. Docker ustawia politykę FORWARD na drop i czyści łańcuchy, które posiada, przy każdym restarcie demona, więc umieść lokalną politykę w DOCKER-USER , która przetrwa:
iptables -I DOCKER-USER -i br-appnet -o tun0 -j ACCEPT
iptables -I DOCKER-USER -i tun0 -o br-appnet -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTReguła powrotu ma znaczenie: odpowiedź przychodząca na tun0 dla połączenia otwartego przez most nie jest akceptowana tylko dlatego, że kierunek wyjścia był.
Trzymaj host bezpośrednio regułą źródła
Kiedy cały most ma korzystać z tunelu, ale nic innego na hoście nie powinno, pomiń NAT i daj temu źródłu własną tabelę. Adresy gościa pozostają wtedy widoczne od początku do końca.
echo "200 vpnbox" >> /etc/iproute2/rt_tables
ip rule add from 172.18.0.0/16 lookup vpnbox pref 1000
ip route add default dev tun0 table vpnboxReguły są sprawdzane przed tabelą główną, a from pasuje do źródła. Własne zewnętrzne pakiety tunelu niosą fizyczny adres hosta, więc nigdy nie pasują do from 172.18.0.0/16 , a tunel nie może routować sam siebie z powrotem do siebie. To właśnie to samo-wykluczenie czyni konfigurację stabilną.
Ścieżka powrotna potrzebuje teraz pomocy. Zdalna strona widzi nieprzetłumaczony 172.18.0.x i nie ma powodu wiedzieć, gdzie on mieszka. Na serwerze plik na klienta potrzebuje iroute 172.18.0.0 255.255.0.0 , aby wewnętrzna tabela serwera przekazała ten prefiks temu klientowi, oraz wypchniętego route 172.18.0.0 255.255.0.0 , aby inni klienci wiedzieli, że istnieje.
Dwie kontrole, zanim mu zaufasz. Uruchom ip route show default : z redirect-gateway def1 tabela trzyma 0.0.0.0/1 i 128.0.0.0/1 przez tun0 , a host wcale nie jest bezpośredni, bo te połówkowe domyślne wygrywają bez zastępowania default . Reguły dodane przez ip rule add również znikają po restarcie, więc noś je w skrypcie up.
Rozmiar pakietu to druga awaria, na którą trafisz
Interfejs mostu na 1500 bajtów daje tunelowi więcej, niż może unieść, gdy dojdą zewnętrzne nagłówki UDP i IP. Małe wymiany przechodzą, więc DNS się rozwiązuje i uścisk TLS kończy, potem większe wysyłanie wisi bez żadnego błędu. Nadawca powtarza ten sam rozmiar segmentu, bo komunikat ICMP o potrzebie fragmentacji, który kazałby mu zmniejszyć, nigdy nie dociera.
Napraw to na interfejsie, na MSS albo na obu. Ściskanie nie wymaga współpracy gościa:
ip link set dev tun0 mtu 1400
iptables -t mangle -A FORWARD -o tun0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtuŚciskanie przepisuje tylko TCP. UDP, w tym QUIC, potrzebuje zamiast tego obniżonego interfejsu gościa, z --opt com.docker.network.driver.mtu=1400 przy tworzeniu sieci albo odpowiednim kluczem compose.
DNS i kontrole, które warto powtarzać
Rozwiązywanie nazw idzie tą samą drogą. Wbudowany resolver Dockera na 127.0.0.11 przekazuje do skonfigurowanych serwerów hosta, więc kontener dziedziczy to, czego używa host. Gdy strefę prywatną odpowiada tylko resolver osiągalny przez tunel, skieruj gościa na ten adres, i testuj przez getent hosts wewnątrz kontenera, a nie na hoście.
- Uruchom zawodzące żądanie wewnątrz gościa; test po stronie hosta używa innego adresu źródłowego i ukrywa problem.
- Czytaj liczniki na dodanych regułach NAT i filtra. Reguła bez ani jednego dopasowania to błąd routingu, nie polityki.
- Potwierdź, że adres tunelu w twojej regule SNAT jest taki, który klient naprawdę ma.
- Obniż MTU gościa albo ściśnij MSS, zanim obwinisz serwer o zawieszenie.
Przejdź tę listę i zatrzymaj się na pierwszej awarii. To ta warstwa wymaga naprawy; cokolwiek dodane niżej tylko ukrywa granicę, która się łamie.