Поправете го сообраќајот на Docker и WSL што го игнорира VPN-от на домаќинот
Контејнерите и WSL ги чуваат сопствените рути, резолвер и пат за препраќање, па работен тунел на домаќинот ги остава со застарени DNS или без излез. Поправете го секој слој.
Оваа пролет го поминав поголемиот дел од едно утро бркајќи контејнер што не можеше да реши ниту едно внатрешно име, додека браузерот до него ја отвораше викито без жалба. Тунелот беше кренат, браузерот го докажуваше тоа, а контејнерот стоеше во друг мрежен именски простор со друга табела на рути и друг резолвер. Следново е редоследот што сега го проверувам: прво табелите на рути, второ решавањето имиња, трето препраќањето и на крајот платформата на домаќинот. Запрете на првиот чекор што веќе паѓа, бидејќи додавањето правила подолу во стекот ја крие границата наместо да ја помести.
Споредете ги трите табели на рути пред да смените правило
Контејнер на стандардниот мост на Docker има една стандардна рута кон портата docker0 и нема поим дека tun0 постои. Домаќинот има tun0, рутите што ги турна VPN-от и можеби правило за политика што испраќа пробен сообраќај низ друг интерфејс од оној што го очекувате. WSL додава трета табела зад сопствениот виртуелен адаптер. Испечатете ги сите три пред да уредите било која.
| 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 |
Ако единствената рута на контејнерот е мостот и 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 суфикси на Windows VPN-от, затоа браузерот работи, а 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?
Стартувајте го секој тест повторно во нов контејнер или школка. Долготрајниот процес со кеширан одговор или воспоставена врска ќе продолжува да паѓа откако конфигурацијата е точна, и тој отказ изгледа точно како поправка што не проработела.