Het pakket van de container bereikt nooit tun0 , en extra mogelijkheden op de container veranderen dat niet. Tussen de gast en de tunnel staan twee routeopzoekingen: één in de netwerknaamruimte van de gast, één op de host nadat het frame op de bridge landt, en de tweede raadpleegt een andere tabel met andere matchregels. Mijn referentieopstelling is een Linux-host met een OpenVPN client die 10.8.0.10 op tun0 houdt, een Docker-brug op 172.18.0.0/16 , en een privéadres dat alleen de tunnelkant kan bereiken. Het doel blijft smal: dat gastsubnet gaat door de tunnel naar buiten terwijl de host zijn gewone uplink houdt.

De VPN-client in de container draaien is een ander ontwerp , geen sluiproute. De container bezit dan zijn eigen tunnel en zijn eigen inloggegevens, en het werk verhuist naar een Dockerfile in plaats van in iptables te blijven.

Doorsturen kiest een route op bestemming, daarna herschrijft NAT de bron

Als net.ipv4.ip_forward=1 is ingesteld, wordt een doorgestuurd pakket alleen op bestemming gerouteerd, in de hoofdtabel. De bron speelt geen rol in die beslissing. Pas nadat de route is gekozen, voert de kernel POSTROUTING uit, en daar zit Docker's eigen regel: -s 172.18.0.0/16 ! -o br-appnet -j MASQUERADE . Alles wat de bridge naar een andere interface verlaat, wordt vertaald, en het adres waarin het verandert hangt af van de interface waar de route het naartoe stuurde.

Die regel verklaart de meeste symptomen waar ik over word gevraagd. Als de hoofdtabel geen route heeft voor de privébestemming, verlaat het pakket de uplink met het fysieke adres van de host. Je provider laat de bestemming vallen, of de externe site laat een RFC 1918-bron vallen die hij nooit verwachtte, en de container loopt in een time-out terwijl de tunnel overeind en gezond blijft. Een opname op tun0 laat niets zien, en dat is het teken. Als de route wel naar tun0 wijst, herschrijft MASQUERADE de bron naar 10.8.0.10 , en dezelfde container werkt zonder dat je iets aanraakt.

Vraag de opzoeking die de bron van de gast noemt. Zonder from en iif antwoordt de host voor zijn eigen socket en meldt een pad dat het doorgestuurde pakket nooit neemt:

ip route get 192.168.11.222 from 172.18.0.5 iif br-appnet

Als dat antwoord de uplink noemt, is de tunnel niet in beeld en geen instelling aan de tunnelkant helpt.

Zet de gast op het eigen adres van de tunnel

De kleinste werkende wijziging is een expliciete bronherschrijving bij het naar buiten gaan. Voeg die in in plaats van hem toe te voegen: Docker zit al aan het hoofd van POSTROUTING , dus een regel die met -A is toegevoegd, wordt achter die van Docker geëvalueerd en gaat nooit af.

iptables -t nat -I POSTROUTING 1 -s 172.18.0.0/16 -o tun0 -j SNAT --to-source 10.8.0.10

Twee voorwaarden bepalen of dit blijft werken. Het adres moet er een zijn die de client echt heeft, dus zet het vast met een vermelding per client die ifconfig-push 10.8.0.10 255.255.255.0 bevat; een regel die een adres noemt dat nooit is toegewezen, levert stille drops op, geen fout. En de externe kant moet die bron accepteren en antwoorden terug routeren, daarom noemt een allowlist daar het tunneladres en niet het gastsubnet. Als het clientadres dynamisch is, -j MASQUERADE met -o tun0 volgt wat de interface heeft, tegen de prijs van extern beleid waar je geen vaste waarde tegen kunt schrijven.

Filteren is de andere helft. Docker zet het FORWARD -beleid op drop en maakt bij elke daemon-herstart de ketens leeg die het bezit, dus zet lokaal beleid in DOCKER-USER , dat overleeft:

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 ACCEPT

De retourregel is belangrijk: een antwoord dat op tun0 binnenkomt voor een verbinding die de bridge opende, wordt niet geaccepteerd alleen omdat de richting naar buiten dat was.

Houd de host direct met een bronregel

Als de hele bridge de tunnel moet gebruiken maar niets anders op de host, sla NAT over en geef die bron zijn eigen tabel. Gastadressen blijven dan van begin tot eind zichtbaar.

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 vpnbox

Regels worden vóór de hoofdtabel geraadpleegd, en from komt overeen met de bron. De eigen buitenste pakketten van de tunnel dragen het fysieke adres van de host, dus ze komen nooit overeen met from 172.18.0.0/16 , en de tunnel kan zichzelf niet terug in zichzelf routeren. Die zelfuitsluiting maakt de opstelling stabiel.

De retourweg heeft nu hulp nodig. De externe kant ziet een onvertaald 172.18.0.x en heeft geen reden om te weten waar het woont. Op de server heeft een bestand per client iroute 172.18.0.0 255.255.0.0 nodig, zodat de interne tabel van de server dat prefix aan deze client geeft, en een gepushte route 172.18.0.0 255.255.0.0 , zodat andere clients weten dat het bestaat.

Twee controles voordat je erop vertrouwt. Voer ip route show default uit: met redirect-gateway def1 houdt de tabel 0.0.0.0/1 en 128.0.0.0/1 via tun0 , en de host is helemaal niet direct, omdat die halve standaardroutes winnen zonder default te vervangen. Regels die met ip rule add zijn toegevoegd, verdwijnen ook bij een herstart, dus draag ze in een up-script.

Pakketgrootte is de tweede fout die je tegenkomt

Een bridge-interface op 1500 bytes geeft de tunnel meer dan hij kan dragen zodra de buitenste UDP- en IP-headers erbij komen. Kleine uitwisselingen gaan door, dus DNS lost op en de TLS-handshake rondt af, daarna blijft een grotere upload hangen zonder ergens een fout. De afzender blijft dezelfde segmentgrootte herhalen omdat het ICMP-bericht over fragmentatiebehoefte, dat hem zou zeggen te krimpen, nooit aankomt.

Repareer het op de interface, op de MSS of beide. Knijpen vraagt geen medewerking van de gast:

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

Het knijpen herschrijft alleen TCP. UDP, QUIC inbegrepen, heeft in plaats daarvan een lagere gastinterface nodig, met --opt com.docker.network.driver.mtu=1400 bij het aanmaken van het netwerk of de bijbehorende compose-sleutel.

DNS en de controles die het waard zijn te herhalen

Naamresolutie volgt dezelfde weg. De ingebouwde resolver van Docker op 127.0.0.11 stuurt door naar de geconfigureerde servers van de host, dus een container erft wat de host gebruikt. Als de privézone alleen wordt beantwoord door een resolver die via de tunnel bereikbaar is, wijs de gast dan op dat adres, en test met getent hosts in de container in plaats van op de host.

  • Voer het mislukkende verzoek in de gast uit; een test aan de hostkant gebruikt een ander bronadres en verbergt het probleem.
  • Lees de tellers op de NAT- en filterregels die je hebt toegevoegd. Een regel zonder treffers is een routeringsfout, geen beleidsfout.
  • Bevestig dat het tunneladres in je SNAT-regel er een is die de client echt heeft.
  • Verlaag de MTU van de gast of knijp de MSS voordat je de server de schuld geeft van een stilstand.

Werk die lijst af en stop bij de eerste fout. Die laag is degene die moet worden gerepareerd; alles wat verder naar beneden wordt toegevoegd, verbergt alleen de grens die breekt.

Gerelateerde artikelen