Како усмерити само саобраћај Docker контејнера кроз OpenVPN
Оставите хост на његовом уобичајеном uplink-у и ставите једну контејнерску подмрежу у тунел: редослед NAT-а, правила извора, OpenVPN iroute и MSS провере.
Пакет контејнера никада не стиже до tun0 , а додатне способности на контејнеру то неће променити. Између госта и тунела стоје две претраге руте: једна унутар мрежног именског простора госта, једна на хосту након што оквир слети на мост, а друга пита другу табелу са другачијим правилима подударања. Моја референтна поставка је Linux хост са OpenVPN клијентом који држи 10.8.0.10 на tun0 , Docker мостом на 172.18.0.0/16 , и приватном адресом до које само страна тунела може да дође. Циљ остаје узак: та гостујућа подмрежа излази кроз тунел док хост задржава свој уобичајени uplink.
Покретање VPN клијента унутар контејнера је други дизајн , а не пречица. Контејнер тада поседује свој тунел и своје акредитиве, а посао се сели у Dockerfile уместо да остане у iptables-у.
Прослеђивање бира руту по одредишту, затим NAT преписује извор
Када је постављено net.ipv4.ip_forward=1 , прослеђени пакет се рутира само по одредишту, у главној табели. Извор не игра никакву улогу у тој одлуци. Тек након што се рута одабере, кернел покреће POSTROUTING , и ту стоји Docker-ово сопствено правило: -s 172.18.0.0/16 ! -o br-appnet -j MASQUERADE . Све што напусти мост према другом интерфејсу бива преведено, а адреса коју добије зависи од интерфејса на који га је рута послала.
То правило објашњава већину симптома о којима ме питају. Ако главна табела нема руту за приватно одредиште, пакет излази кроз uplink носећи физичку адресу хоста. Ваш провајдер одбацује одредиште, или удаљени сајт одбацује извор RFC 1918 који никада није очекивао, и контејнеру истекне време док тунел остаје подигнут и здрав. Снимак на tun0 не показује ништа и то је знак. Ако рута ипак показује на tun0 , MASQUERADE преписује извор на 10.8.0.10 , и исти контејнер ради без тога да било шта дирате.
Тражите претрагу која именује извор госта. Без from и iif хост одговара за сопствени сокет и пријављује путању коју прослеђени пакет никада неће узети:
ip route get 192.168.11.222 from 172.18.0.5 iif br-appnetАко тај одговор именује uplink, тунел није у слици и никаква поставка на страни тунела неће помоћи.
Преведите госта на сопствену адресу тунела
Најмања промена која ради је изричито преписивање извора на излазу. Уметните га, уместо да га додајете: Docker већ седи на челу POSTROUTING , па се правило додато са -A процењује иза Docker-овог и никада се не покреће.
iptables -t nat -I POSTROUTING 1 -s 172.18.0.0/16 -o tun0 -j SNAT --to-source 10.8.0.10Два услова одлучују да ли ће се то одржати. Адреса мора бити она коју клијент заиста држи, зато је прикујте записом по клијенту који садржи ifconfig-push 10.8.0.10 255.255.255.0 ; правило које именује адресу која никада није додељена производи немо одбацивање, не грешку. И даља страна мора прихватити тај извор и усмерити одговоре назад, зато листа дозвола тамо именује адресу тунела, а не гостујућу подмрежу. Када је адреса клијента динамичка, -j MASQUERADE са -o tun0 следи оно што интерфејс држи, по цени удаљене политике против које не можете писати фиксну вредност.
Филтрирање је друга половина. Docker поставља политику FORWARD на drop и брише ланце које поседује при сваком рестарту демона, зато ставите локалну политику у DOCKER-USER , која преживљава:
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Правило повратка је важно: одговор који стигне на tun0 за везу отворену кроз мост није прихваћен само зато што је одлазни смер био.
Држите хост директним правилом извора
Када цео мост треба да користи тунел, а ништа друго на хосту не треба, прескочите NAT и дајте том извору сопствену табелу. Адресе гостију тада остају видљиве од почетка до краја.
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Правила се консултују пре главне табеле, и from се подудара са извором. Сопствени спољни пакети тунела носе физички извор хоста, па се никада не подударају са from 172.18.0.0/16 , и тунел не може усмерити себе назад у себе. То самоискључивање чини поставку стабилном.
Путања одговора сада треба помоћ. Даља страна види непреведену 172.18.0.x и нема разлога знати где живи. На серверу, датотека по клијенту треба iroute 172.18.0.0 255.255.0.0 да би серверова унутрашња табела предала тај префикс овом клијенту, и гурнуту route 172.18.0.0 255.255.0.0 да би други клијенти знали да постоји.
Две провере пре него му поверујете. Покрените ip route show default : са redirect-gateway def1 табела држи 0.0.0.0/1 и 128.0.0.0/1 преко tun0 , а хост уопште није директан, јер те половине подразумеваног побеђују без замене default . Правила додата са ip rule add такође нестају при рестарту, зато их носите у up скрипти.
Величина пакета је други квар на који налете
Мост на 1500 бајтова даје тунелу више него што може да носи када се додају спољна UDP и IP заглавља. Мале размене пролазе, па се DNS разреши и TLS руковање заврши, а затим већи upload виси без икакве грешке. Пошиљалац стално понавља исту величину сегмента јер ICMP порука о потреби фрагментације, која би му рекла да се смањи, никада не стиже.
Поправите то на интерфејсу, на MSS-у или обоје. Стезање не тражи сарадњу госта:
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Стезање преписује само TCP. UDP, укључујући QUIC, треба уместо тога спуштен интерфејс госта, са --opt com.docker.network.driver.mtu=1400 при креирању мреже или одговарајућим compose кључем.
DNS и провере које вреди понављати
Разрешавање имена следи исту путању. Docker-ов уграђени резолвер на 127.0.0.11 прослеђује на конфигурисане сервере хоста, па контејнер наслеђује оно што хост користи. Када приватну зону одговара само резолвер доступан кроз тунел, усмерите госта на ту адресу и тестирајте са getent hosts унутар контејнера, а не на хосту.
- Покрените неуспешни захтев унутар госта; тест на страни хоста користи другу изворну адресу и скрива проблем.
- Читајте бројаче на NAT и filter правилима која сте додали. Правило са нула подударања је грешка рутирања, не политике.
- Потврдите да је адреса тунела у вашем SNAT правилу она коју клијент заиста држи.
- Снизите MTU госта или стегните MSS пре него што окривите сервер за застој.
Прођите кроз тај списак и зауставите се на првом квар. Тај слој треба поправити; све што се дода даље само скрива границу која се ломи.