Cum direcționăm doar traficul containerelor Docker prin OpenVPN
Lăsați gazda pe uplink-ul obișnuit și puneți o singură subrețea de containere în tunel: ordinea NAT, regulile de sursă, iroute OpenVPN și verificările MSS.
Pachetul containerului nu ajunge niciodată la tun0 , iar capabilități suplimentare pe container nu vor schimba asta. Între oaspete și tunel stau două căutări de rută: una în spațiul de nume de rețea al oaspeteului, una pe gazdă după ce cadrul aterizează pe pod, iar a doua consultă un alt tabel cu reguli de potrivire diferite. Configurația mea de referință este o gazdă Linux cu un OpenVPN client care deține 10.8.0.10 pe tun0 , un pod Docker pe 172.18.0.0/16 , și o adresă privată la care doar partea tunelului poate ajunge. Scopul rămâne îngust: acea subrețea oaspete iese prin tunel în timp ce gazda își păstrează uplink-ul obișnuit.
Rularea clientului VPN în container este alt proiect , nu o scurtătură. Atunci containerul deține propriul tunel și propriile credențiale, iar munca se mută într-un Dockerfile în loc să rămână în iptables.
Forwardarea alege o rută după destinație, apoi NAT rescrie sursa
Când este setat net.ipv4.ip_forward=1 , un pachet forwardat este rutat doar după destinație, în tabelul principal. Sursa nu joacă niciun rol în acea decizie. Abia după ce ruta este aleasă, kernelul rulează POSTROUTING , și acolo se află regula proprie a Docker: -s 172.18.0.0/16 ! -o br-appnet -j MASQUERADE . Tot ce părăsește podul spre altă interfață este tradus, iar adresa în care se transformă depinde de interfața către care ruta l-a trimis.
Această regulă explică majoritatea simptomelor despre care sunt întrebat. Dacă tabelul principal nu are rută către destinația privată, pachetul iese pe uplink cu adresa fizică a gazdei. Furnizorul aruncă destinația, sau site-ul îndepărtat aruncă o sursă RFC 1918 la care nu s-a așteptat, iar containerul expiră în timp ce tunelul rămâne sus și sănătos. O captură pe tun0 nu arată nimic, și acesta este indiciul. Dacă ruta indică într-adevăr spre tun0 , MASQUERADE rescrie sursa în 10.8.0.10 , iar același container funcționează fără să atingeți nimic.
Cereți căutarea care numește sursa oaspeteului. Fără from și iif gazda răspunde pentru propriul socket și raportează o cale pe care pachetul forwardat nu o va lua niciodată:
ip route get 192.168.11.222 from 172.18.0.5 iif br-appnetDacă acel răspuns numește uplink-ul, tunelul nu este în imagine și nicio setare din partea tunelului nu va ajuta.
Mutați oaspetele pe adresa proprie a tunelului
Cea mai mică schimbare care funcționează este o rescriere explicită a sursei la ieșire. Introduceți-o în loc să o adăugați: Docker stă deja în capul POSTROUTING , așa că o regulă adăugată cu -A este evaluată în spatele celei a Docker și nu se declanșează niciodată.
iptables -t nat -I POSTROUTING 1 -s 172.18.0.0/16 -o tun0 -j SNAT --to-source 10.8.0.10Două condiții decid dacă acest lucru rezistă. Adresa trebuie să fie una pe care clientul o are cu adevărat, așa că fixați-o cu o înregistrare per client care conține ifconfig-push 10.8.0.10 255.255.255.0 ; o regulă care numește o adresă ce nu a fost niciodată alocată produce pierderi silențioase, nu o eroare. Și partea îndepărtată trebuie să accepte acea sursă și să ruteze răspunsurile înapoi, de aceea o listă de permisiuni de acolo numește adresa tunelului, nu subrețeaua oaspete. Când adresa clientului este dinamică, -j MASQUERADE cu -o tun0 urmează ce are interfața, cu prețul unei politici îndepărtate împotriva căreia nu puteți scrie o valoare fixă.
Filtrarea este cealaltă jumătate. Docker pune politica FORWARD pe drop și golește lanțurile pe care le deține la fiecare repornire a daemonului, așa că puneți politica locală în DOCKER-USER , care supraviețuiește:
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 ACCEPTRegula de retur contează: un răspuns care sosește pe tun0 pentru o conexiune deschisă de pod nu este acceptat doar pentru că direcția de ieșire a fost.
Țineți gazda directă cu o regulă de sursă
Când tot podul trebuie să folosească tunelul, dar nimic altceva de pe gazdă nu trebuie, săriți peste NAT și dați acelei surse propriul tabel. Adresele oaspete rămân atunci vizibile de la un capăt la altul.
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 vpnboxRegulile sunt consultate înaintea tabelului principal, iar from se potrivește cu sursa. Pachetele exterioare ale tunelului poartă sursa fizică a gazdei, așa că nu se potrivesc niciodată cu from 172.18.0.0/16 , iar tunelul nu se poate ruta înapoi în el însuși. Această autoexcludere face configurația stabilă.
Calea de retur are acum nevoie de ajutor. Partea îndepărtată vede o 172.18.0.x netradusă și nu are niciun motiv să știe unde locuiește. Pe server, un fișier per client are nevoie de iroute 172.18.0.0 255.255.0.0 pentru ca tabelul intern al serverului să dea acel prefix acestui client, și de un route 172.18.0.0 255.255.0.0 împins pentru ca ceilalți clienți să știe că există.
Două verificări înainte să aveți încredere. Rulați ip route show default : cu redirect-gateway def1 tabelul ține 0.0.0.0/1 și 128.0.0.0/1 prin tun0 , iar gazda nu este deloc directă, pentru că acele semi-implicite câștigă fără să înlocuiască default . Regulile adăugate cu ip rule add dispar și ele la repornire, așa că purtați-le într-un script up.
Dimensiunea pachetului este a doua defecțiune pe care o întâlniți
O interfață de pod la 1500 de octeți dă tunelului mai mult decât poate duce după ce se adaugă antetele UDP și IP exterioare. Schimburile mici trec, așa că DNS se rezolvă și strângerea de mână TLS se termină, apoi un upload mai mare se blochează fără nicio eroare nicăieri. Emițătorul repetă aceeași dimensiune de segment pentru că mesajul ICMP despre necesitatea fragmentării, care i-ar spune să micșoreze, nu ajunge niciodată.
Remediați la interfață, la MSS sau ambele. Strângerea nu are nevoie de cooperarea oaspeteului:
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-pmtuStrângerea rescrie doar TCP. UDP, inclusiv QUIC, are nevoie în schimb de o interfață oaspete mai joasă, cu --opt com.docker.network.driver.mtu=1400 la crearea rețelei sau cheia compose corespunzătoare.
DNS și verificările care merită repetate
Rezolvarea numelor urmează aceeași cale. Resolverul încorporat al Docker de la 127.0.0.11 trimite către serverele configurate ale gazdei, așa că un container moștenește ce folosește gazda. Când zona privată este răspunsă doar de un resolver accesibil prin tunel, îndreptați oaspetele către acea adresă, și testați cu getent hosts în interiorul containerului, nu pe gazdă.
- Rulați cererea care eșuează în interiorul oaspeteului; un test pe partea gazdei folosește altă adresă de sursă și ascunde problema.
- Citiți contoarele de pe regulile NAT și de filtrare pe care le-ați adăugat. O regulă fără nicio potrivire este o greșeală de rutare, nu de politică.
- Confirmați că adresa tunelului din regula SNAT este una pe care clientul o are cu adevărat.
- Coborâți MTU-ul oaspeteului sau strângeți MSS înainte de a da vina pe server pentru o blocare.
Parcurgeți lista și opriți-vă la prima defecțiune. Acel strat este cel care trebuie reparat; orice adăugat mai jos ascunde doar granița care se rupe.