Remediați traficul Docker și WSL care ignoră VPN-ul gazdei
Containerele și WSL își păstrează propriile rute, resolver și cale de redirecționare, așa că un tunel funcțional al gazdei le lasă cu DNS învechit sau fără ieșire. Remediați fiecare strat.
Am petrecut cea mai mare parte a unei dimineți din această primăvară urmărind un container care nu putea rezolva nici un nume intern, în timp ce browserul de alături deschidea wiki-ul fără nicio reclamație. Tunelul era activ, browserul demonstra asta, iar containerul se afla în alt spațiu de nume de rețea, cu alt tabel de rute și alt resolver. Urmează ordinea pe care o verific acum: mai întâi tabelele de rute, apoi rezolvarea numelor, apoi redirecționarea și, la final, platforma gazdă. Opriți-vă la primul pas care eșuează deja, pentru că adăugarea de reguli mai jos în stivă ascunde granița în loc să o mute.
Comparați cele trei tabele de rute înainte de a schimba o regulă
Un container pe puntea implicită a Docker are o singură rută implicită către gateway-ul docker0 și nu știe că tun0 există. Gazda are tun0, rutele împinse de VPN și poate o regulă de politică ce trimite traficul de test printr-o interfață diferită de cea așteptată. WSL adaugă un al treilea tabel în spatele propriului adaptor virtual. Afișați toate trei înainte de a edita vreunul.
| 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 |
Dacă singura rută a containerului este puntea și tun0 nu apare niciodată în spațiul său de nume, nimic de mai jos nu va ajuta: nicio schimbare de server de nume nu creează o rută, iar nicio rută nu creează identitatea de tunel pe care o va accepta partenerul. Ordinea contează pentru că o defecțiune DNS și una de rută arată identic din interiorul unui shell.
Îndreptați resolverul spre tunel, nu spre unul public
Pe gazdă, resolvectl status listează serverele DNS pe legătură, iar readlink -f /etc/resolv.conf vă spune dacă fișierul este stub-ul systemd, un fișier NetworkManager sau ceva rescris de clientul unui furnizor. În interiorul unui container pe o rețea definită de utilizator, /etc/resolv.conf numește 127.0.0.11, resolverul încorporat al Docker, care redirecționează către upstream-ul cu care a fost configurat demonul. În schimb, un container de pe puntea implicită a primit la creare o copie a fișierului gazdei și o păstrează.
Acea copie este motivul pentru care un tunel care apare după pornirea containerului lasă containerul să interogheze upstream-ul vechi. Fie transmiteți resolverul VPN explicit cu docker run --dns 10.8.0.53, fie setați "dns": ["10.8.0.53"] în /etc/docker/daemon.json și reporniți demonul, fie recreați containerul când tunelul este activ. Recrearea este de obicei mai ieftină decât depanarea unui resolver care era corect acum o oră.
Înlocuirea cu un resolver public este reparația care păstrează simptomul. Rezolvă internetul și eșuează în fiecare zonă privată și poate ocoli în tăcere filtrarea pentru care există tunelul. Dacă încercați să scrieți în /etc/resolv.conf unui container care rulează, așteptați-vă la Device or resource busy: Docker montează acel fișier prin bind, așa că editarea necesită mai întâi o demontare. Setarea --dns la creare este secvența care supraviețuiește unei reporniri.
Redirecționați și traduceți când containerele împrumută tunelul
Cu puntea implicită, gazda este un router între docker0 și tun0 și, implicit, nu face niciuna dintre cele două sarcini. Activați redirecționarea și traduceți adresa sursă pentru ca partenerul să vadă o adresă de tunel a gazdei în locul uneia spre care nu are rută de întoarcere (înlocuiți tun0 cu numele pe care clientul îl dă interfeței):
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 ACCEPTRegulile FORWARD contează pe gazdele a căror politică de redirecționare este DROP. Docker inserează propriile lanțuri, așa că testați cu un pachet în loc să citiți iptables -S: din container, ip route get 10.8.0.20 ar trebui să numească gateway-ul punții, iar nc -vz 10.8.0.20 443 ar trebui să se conecteze sau să expire la tunel, nu la resolver.
Fără MASQUERADE partenerul are nevoie de o rută de întoarcere spre 172.17.0.0/16, pe care majoritatea VPN-urilor de acces la distanță nu o împing. Tot aici un client bazat pe politici strică planul: dacă interceptează doar traficul de la anumite UID-uri sau cgroups, regulile de redirecționare nu văd niciodată pachetul în interiorul tunelului, iar corecția aparține politicii de rutare a clientului, nu iptables. Regulile adăugate manual dispar la repornire, așa că puneți-le într-o unitate systemd sau într-un tabel netfilter-persistent și retestați după o repornire, deoarece un tunel care apare târziu le poate mătura.
Puneți tunelul în interiorul containerului când scopul este izolarea
Dacă un container trebuie să iasă printr-un nod anume, rulați clientul în acel container. Are nevoie de --cap-add NET_ADMIN și de --device /dev/net/tun, iar apoi deține propriul tun0, propriile rute și propriul firewall. Dacă alte containere trec prin el, are nevoie și de redirecționare și de o regulă MASQUERADE în interiorul containerului, iar acele perechi au nevoie de o rută sau un proxy explicit care să indice spre el; puntea implicită a Docker nu adaugă acea rută pentru dumneavoastră.
Rețeaua gazdei, docker run --network host, este versiunea ieftină: containerul intră în spațiul de nume al gazdei și vede direct rutele tunelului. Renunță însă la izolarea rețelei, invită la coliziuni de porturi și, în practică, este o opțiune Linux. Un port publicat nu este un substitut. -p 8080:80 face un serviciu accesibil din gazdă; nu face adresele interne 10.x accesibile din interiorul containerului, iar confundarea celor două este modul în care o problemă de rutare devine un port de bază de date expus.
Distingeți o defecțiune de nume de o subrețea suprapusă
Puntea implicită a Docker este 172.17.0.0/16, iar multe pool-uri VPN corporative stau în 172.16.0.0/12. Odată ce tunelul deține acel interval, traficul destinat unui container poate fi rutat în tunel. Numele eșuează, eroarea arată ca DNS, iar problema sunt adresele. Verificați destinația, nu resolverul: ip route get 172.17.0.5 pe gazdă, apoi comparați prefixul punții cu pool-ul VPN și cu docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'.
Reparația este să mutați unul dintre intervale: fie alt pool de adrese implicit pentru Docker în daemon.json fie alt pool pe concentrator. Adăugarea unui alt server de nume nu face nimic aici, și nici ssh -b nu face: legarea clientului de o interfață schimbă adresa sursă, nu o rută de destinație care duce în tunel. Împărțire rapidă a muncii: un nume care se rezolvă pe gazdă dar nu în container este o problemă de resolver, în timp ce o conexiune IP directă care se blochează în container dar funcționează pe gazdă este o problemă de rută sau de lanț de redirecționare.
WSL și Docker Desktop nu respectă regulile Linux
În modul său NAT implicit, WSL stă în spatele propriului adaptor virtual și nu primește rutele interne sau sufixele DNS ale VPN-ului Windows, motiv pentru care browserul funcționează și ping în interiorul distribuției nu. Rețeaua oglindă, disponibilă în build-urile recente de Windows 11, partajează în schimb interfețele gazdei, dar trebuie activată în .wslconfig și distribuția repornită cu wsl --shutdown. Confirmați modul activ cu wslinfo --networking-mode înainte de a aplica o corecție, pentru că răspunsul NAT este greșit pentru modul oglindă și invers.
Docker Desktop adaugă un alt strat. Containerele Linux pe Windows stau în spatele unei implementări NAT numită istoric vpnkit, iar calea lor diferă de cea a unei gazde Linux native. Containerele Windows se conectează printr-un switch virtual Hyper-V, iar un client corporativ poate bloca traficul din acel switch, așa că containerele Linux de pe aceeași mașină pot ajunge la rețea în timp ce containerele Windows nu au nimic. Un switch extern legat de adaptorul fizic este soluția obișnuită și schimbă identitatea de rețea a containerului în moduri pe care politica poate să nu le permită.
Înainte de a scrie altă regulă, parcurgeți cele patru straturi în ordine și opriți-vă unde unul dintre ele este greșit:
- În container:
ip routeajunge la prefixul privat și/etc/resolv.confnumește un upstream care îl rezolvă? - Pe gazdă: redirecționarea este activă, NAT este legat de interfața tunelului și lanțul de redirecționare acceptă puntea spre tunel?
- La partener: cunoaște o cale de întoarcere sau vă bazați pe NAT să ascundă subrețeaua containerului?
- Pe platformă: WSL este în modul NAT sau oglindă și containerul este Linux sau Windows?
Rulați din nou fiecare test într-un container sau shell noi. Un proces de lungă durată cu un răspuns în cache sau o conexiune stabilită va continua să eșueze după ce configurația este corectă, iar acea defecțiune arată exact ca o reparație care nu a funcționat.