Så dirigerar du bara Docker-containrars trafik genom OpenVPN
Låt värden vara kvar på sin vanliga uplink och lägg ett containernät i tunneln: NAT-ordning, källregler, OpenVPN iroute och MSS-kontroller.
Containerens paket når aldrig tun0 , och extra behörigheter på containern ändrar inte det. Mellan gästen och tunneln finns två ruttuppslagningar: en inne i gästens nätverksnamnrymd, en på värden efter att ramen landar på bryggan, och den andra frågar en annan tabell med andra matchningsregler. Min referensuppställning är en Linux-värd med en OpenVPN klient som håller 10.8.0.10 på tun0 , en Docker-brygga på 172.18.0.0/16 , och en privat adress som bara tunnelsidan kan nå. Målet är fortfarande snävt: det gästnätet går ut genom tunneln medan värden behåller sin vanliga uplink.
Att köra VPN-klienten inne i containern är en annan design , inte en genväg. Då äger containern sin egen tunnel och sina egna uppgifter, och arbetet flyttar in i en Dockerfile i stället för att stanna i iptables.
Vidarebefordran väljer en rutt efter destination, sedan skriver NAT om källan
Med net.ipv4.ip_forward=1 satt routas ett vidarebefordrat paket bara efter destination, i huvudtabellen. Källan spelar ingen roll i det beslutet. Först efter att rutten valts kör kärnan POSTROUTING , och det är där Dockers egen regel sitter: -s 172.18.0.0/16 ! -o br-appnet -j MASQUERADE . Allt som lämnar bryggan mot ett annat gränssnitt översätts, och adressen det blir beror på gränssnittet rutten skickade det till.
Den regeln förklarar de flesta symptom jag får frågor om. Om huvudtabellen saknar rutt till den privata destinationen lämnar paketet uplinken med värdens fysiska adress. Din leverantör släpper destinationen, eller fjärrplatsen släpper en RFC 1918-källa som den aldrig väntade sig, och containern får tidsutdräkt medan tunneln står uppe och är frisk. En avlyssning på tun0 visar ingenting, och det är tecknet. Om rutten faktiskt pekar på tun0 , skriver MASQUERADE om källan till 10.8.0.10 , och samma container fungerar utan att du rör något.
Begär uppslagningen som namnger gästens källa. Utan from och iif svarar värden för sin egen socket och rapporterar en väg som det vidarebefordrade paketet aldrig tar:
ip route get 192.168.11.222 from 172.18.0.5 iif br-appnetOm svaret namnger uplinken är tunneln inte med i bilden och ingen inställning på tunnelsidan hjälper.
Flytta gästen till tunnelns egen adress
Den minsta förändring som fungerar är en uttrycklig källomskrivning på väg ut. Infoga den i stället för att lägga till den: Docker sitter redan i spetsen av POSTROUTING , så en regel tillagd med -A utvärderas bakom Dockers och utlöses aldrig.
iptables -t nat -I POSTROUTING 1 -s 172.18.0.0/16 -o tun0 -j SNAT --to-source 10.8.0.10Två villkor avgör om detta håller. Adressen måste vara en som klienten verkligen har, så fäst den med en post per klient som innehåller ifconfig-push 10.8.0.10 255.255.255.0 ; en regel som namnger en adress som aldrig tilldelats ger tysta dropp, inte ett fel. Och fjärrsidan måste acceptera den källan och routa svar tillbaka, därför namnger en tillåtelselista där tunneladressen och inte gästnätet. När klientadressen är dynamisk, -j MASQUERADE med -o tun0 följer det gränssnittet har, till priset av en fjärrpolitik som du inte kan skriva mot ett fast värde.
Filtrering är den andra halvan. Docker sätter FORWARD -policyn till drop och rensar de kedjor den äger vid varje daemon-omstart, så lägg lokal policy i DOCKER-USER , som överlever:
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 ACCEPTReturregeln är viktig: ett svar som anländer på tun0 för en anslutning som bryggan öppnat accepteras inte bara för att riktningen ut var det.
Håll värden direkt med en källregel
När hela bryggan ska använda tunneln men inget annat på värden ska, hoppa över NAT och ge den källan sin egen tabell. Gästadresser förblir då synliga hela vägen.
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 vpnboxRegler konsulteras före huvudtabellen, och from matchar källan. Tunnels egna yttre paket bär värdens fysiska källa, så de matchar aldrig from 172.18.0.0/16 , och tunneln kan inte routa sig själv tillbaka i sig själv. Den självuteslutningen gör uppställningen stabil.
Returvägen behöver nu hjälp. Fjärrsidan ser en oöversatt 172.18.0.x och har ingen anledning att veta var den bor. På servern behöver en fil per klient iroute 172.18.0.0 255.255.0.0 , så att serverns interna tabell ger det prefixet till den här klienten, och en pushad route 172.18.0.0 255.255.0.0 , så att andra klienter vet att den finns.
Två kontroller innan du litar på det. Kör ip route show default : med redirect-gateway def1 håller tabellen 0.0.0.0/1 och 128.0.0.0/1 via tun0 , och värden är inte alls direkt, eftersom de halva standardrutterna vinner utan att ersätta default . Regler tillagda med ip rule add försvinner också vid omstart, så bär dem i ett up-skript.
Paketstorlek är det andra felet du möter
Ett bryggränssnitt på 1500 byte ger tunneln mer än den kan bära när de yttre UDP- och IP-huvudena tillkommer. Små utbyten går igenom, så DNS löses och TLS-handskakningen fullbordas, sedan hänger en större uppladdning utan något fel någonstans. Avsändaren upprepar samma segmentstorlek eftersom ICMP-meddelandet om fragmenteringsbehov, som skulle säga åt den att krympa, aldrig anländer.
Åtgärda det på gränssnittet, på MSS eller båda. Klämningen kräver inget samarbete från gästen:
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-pmtuKlämningen skriver bara om TCP. UDP, inklusive QUIC, behöver i stället ett lägre gästgränssnitt, med --opt com.docker.network.driver.mtu=1400 vid nätverksskapandet eller motsvarande compose-nyckel.
DNS och kontrollerna som är värda att upprepa
Namnuppslagningen följer samma väg. Dockers inbyggda resolver på 127.0.0.11 vidarebefordrar till värdens konfigurerade servrar, så en container ärver det värden använder. När den privata zonen bara besvaras av en resolver som nås genom tunneln, peka gästen på den adressen, och testa med getent hosts inne i containern i stället för på värden.
- Kör den misslyckade begäran inne i gästen; ett test på värdsidan använder en annan källadress och döljer problemet.
- Läs räknarna på de NAT- och filterregler du lagt till. En regel utan någon träff är ett routingfel, inte ett policyfel.
- Bekräfta att tunneladressen i din SNAT-regel är en som klienten verkligen har.
- Sänk gästens MTU eller kläm MSS innan du skyller servern för en avstängning.
Gå nedåt listan och stanna vid första felet. Det lagret är det som ska rättas; allt som läggs till längre ned döljer bara gränsen som brister.