Овог пролећа провео сам већи део једног јутра јурећи контејнер који није могао да разреши ниједно интерно име, док је прегледач поред њега отварао вики без жалбе. Тунел је био подигнут, прегледач је то доказивао, а контејнер је стајао у другом мрежном именском простору са другом табелом рута и другим резолвером. Следи редослед који сада проверавам: прво табеле рута, затим разрешавање имена, потом прослеђивање и на крају платформа хоста. Зауставите се на првом кораку који већ пада, јер додавање правила ниже у стеку скрива границу уместо да је помери.

Упоредите три табеле рута пре него што промените правило

Контејнер на подразумеваном мосту Docker-а има једну подразумевану руту ка капији docker0 и појма нема да tun0 постоји. Хост има tun0, руте које је VPN гурнуо и можда правило политике које шаље пробни саобраћај кроз друго сучеље од оног које очекујете. WSL додаје трећу табелу иза сопственог виртуелног адаптера. Испишите све три пре него што уредите било коју.

ContextCommandWhat a wrong answer looks like
Hostip route get 10.8.0.20Leaves a physical interface instead of tun0
Containerip routeOnly 172.17.0.0/16 via 172.17.0.1
Container DNScat /etc/resolv.confA public resolver, or an upstream the tunnel never pushed
WSLip addr; ip routeOnly the WSL subnet, no Windows VPN adapter

Ако је једина рута контејнера мост и tun0 се никада не појави у његовом именском простору, ништа испод неће помоћи: никаква промена сервера имена не ствара руту, а ниједна рута не ствара идентитет тунела који ће партнер прихватити. Редослед је важан јер отказ DNS-а и отказ руте изгледају исто изнутра љуске.

Усмерите резолвер ка тунелу уместо ка јавном

На хосту, resolvectl status наводи DNS сервере по вези, а readlink -f /etc/resolv.conf вам каже да ли је датотека systemd stub, датотека NetworkManager-а или нешто што је преписао клијент добављача. Унутар контејнера на мрежи коју је дефинисао корисник, /etc/resolv.conf именује 127.0.0.11, уграђени резолвер Docker-а, који прослеђује оном upstream-у са којим је демон био конфигурисан. А контејнер на подразумеваном мосту је при стварању добио копију датотеке хоста и чува је.

Та копија је разлог зашто тунел који се подигне после покретања контејнера оставља контејнер да пита стари upstream. Или предајте резолвер VPN-а експлицитно са docker run --dns 10.8.0.53, или поставите "dns": ["10.8.0.53"] у /etc/docker/daemon.json и поново покрените демон, или поново створите контејнер када је тунел подигнут. Поновно стварање је обично јефтиније од дебагирања резолвера који је био тачан пре сат времена.

Замена јавним резолвером је поправка која задржава симптом. Он разрешава интернет и пада у свакој приватној зони, и може тихо да заобиђе филтрирање због којег тунел постоји. Ако покушате да пишете унутар /etc/resolv.conf контејнера који ради, очекујте Device or resource busy: Docker прави bind-mount те датотеке, па измена прво захтева одмонтиравање. Постављање --dns при стварању је редослед који преживљава рестарт.

Прослеђујте и преводите када контејнери позајмљују тунел

Са подразумеваним мостом, хост је рутер између docker0 и tun0 и подразумевано не обавља ниједан од та два посла. Укључите прослеђивање и преведите изворну адресу да партнер види адресу тунела хоста уместо оне којој нема повратну руту (замените tun0 оним како ваш клијент назива сучеље):

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 ACCEPT

Правила FORWARD су важна на хостоцима чија је политика прослеђивања DROP. Docker убацује сопствене ланце, па тестирајте пакетом уместо читањем iptables -S: из контејнера, ip route get 10.8.0.20 треба да именује капију моста, а nc -vz 10.8.0.20 443 треба да се повеже или да истекне на тунелу, а не на резолверу.

Без MASQUERADE партнеру је потребна повратна рута до 172.17.0.0/16, коју већина VPN-ова за даљински приступ неће гурнути. Овде се такође ломи план код клијента заснованог на политикама: ако хвата само саобраћај одређених UID-ова или cgroups, ваша правила прослеђивања никада не виде пакет унутар тунела, и поправка припада политици рутирања клијента, а не iptables-у. Правила додата ручно нестају при рестарту, па их ставите у systemd јединицу или табелу netfilter-persistent и поново тестирајте после рестарта, јер тунел који се подигне касно може да их однесе.

Ставите тунел унутар контејнера када је циљ изолација

Ако један контејнер мора да изађе кроз одређени чвор, покрените клијент у том контејнеру. Потребни су му --cap-add NET_ADMIN и --device /dev/net/tun, и тада поседује сопствени tun0, руте и заштитни зид. Ако други контејнери пролазе кроз њега, потребни су му и прослеђивање и правило MASQUERADE унутар контејнера, а тим партнерима је потребна експлицитна рута или прокси који показује ка њему; подразумевани мост Docker-а не додаје ту руту уместо вас.

Мрежа хоста, docker run --network host, јефтина је верзија: контејнер се придружује именском простору хоста и види руте тунела директно. Такође одбацује мрежну изолацију, позива на сударе портова и у пракси је опција Linux-а. Објављен порт није замена. -p 8080:80 чини услугу доступном са хоста; не чини унутрашње 10.x адресе доступним из унутрашњости контејнера, а мешање тога двоје је пут којим проблем рутирања постаје изложен порт базе података.

Разликујте отказ имена од преклопљене подмреже

Подразумевани мост Docker-а је 172.17.0.0/16, а многи корпоративни VPN базени леже у 172.16.0.0/12. Када тунел поседује тај опсег, саобраћај намењен контејнеру може се усмерити у тунел. Имена падају, грешка изгледа као DNS, а проблем је у адресама. Проверите одредиште, не резолвер: ip route get 172.17.0.5 на хосту, затим упоредите префикс моста са базеном VPN-а и са docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'.

Поправка је да се помери један од опсега: или други подразумевани базен адреса за Docker у daemon.json или други базен на концентратору. Додавање још једног сервера имена овде ништа не ради, а ни ssh -b такође: везивање клијента за сучеље мења изворну адресу, не одредишну руту која води у тунел. Брза подела посла: име које се разрешава на хосту али не у контејнеру је проблем резолвера, док је директна IP веза која виси у контејнеру али ради на хосту проблем руте или ланца прослеђивања.

WSL и Docker Desktop не следе правила Linux-а

У свом подразумеваном NAT режиму, WSL стоји иза сопственог виртуелног адаптера и не прима унутрашње руте или DNS суфиксе VPN-а Windows-а, зато прегледач ради, а ping унутар дистрибуције не. Огледалско умрежавање, доступно у новијим изградњама Windows-а 11, уместо тога дели сучеља хоста, али мора да се укључи у .wslconfig и дистрибуција поново покрене са wsl --shutdown. Потврдите активни режим са wslinfo --networking-mode пре него што примените поправку, јер је одговор за NAT погрешан за огледалски режим и обрнуто.

Docker Desktop додаје још један слој. Linux контејнери на Windows-у стоје иза NAT имплементације која се историјски зове vpnkit, и њихов пут се разликује од оног на изворном Linux хосту. Windows контејнери се повезују преко виртуелног прекидача Hyper-V, и корпоративни клијент може да блокира саобраћај са тог прекидача, па Linux контејнери на истом рачунару могу да стигну до мреже док Windows контејнери немају ништа. Спољашњи прекидач везан за физички адаптер је уобичајено решење и мења мрежни идентитет контејнера на начине које политика можда не дозвољава.

Пре него што напишете још једно правило, прођите четири слоја по реду и зауставите се тамо где је један од њих погрешан:

  • Унутар контејнера: да ли ip route стиже до приватног префикса и да ли /etc/resolv.conf именује upstream који га разрешава?
  • На хосту: да ли је прослеђивање укључено, да ли је NAT везан за сучеље тунела и да ли ланац прослеђивања прихвата мост ка тунелу?
  • Код партнера: да ли зна повратну путању или се ослањате на NAT да сакрије подмрежу контејнера?
  • На платформи: да ли је WSL у NAT или огледалском режиму и да ли је контејнер Linux или Windows?

Покрените поново сваки тест у новом контејнеру или љусци. Дуготрајни процес са кешираним одговором или успостављеном везом наставиће да пада и после тога што конфигурација буде тачна, и тај отказ изгледа тачно као поправка која није прорадила.

Повезани чланци