Як накіраваць толькі трафік кантэйнераў Docker праз OpenVPN
Пакіньце хост на звычайным знешнім канале і змясціце ў тунель адну падсетку кантэйнераў: парадак NAT, правілы крыніцы, iroute OpenVPN і праверкі MSS.
Пакет кантэйнера ніколі не дасягае tun0 і дадатковыя магчымасці кантэйнера гэтага не зменяць. Паміж госцем і тунелем стаяць два пошукі маршруту: адзін унутры сеткавай прасторы імёнаў госця, другі на хосце пасля таго, як кадр трапіць на маст, і другі звяртаецца да іншай табліцы з іншымі правіламі супастаўлення. Мая эталонная схема — хост Linux з кліентам OpenVPN, які трымае 10.8.0.10 на tun0, мастом Docker на 172.18.0.0/16, і прыватным адрасам, да якога можа дасягнуць толькі бок тунеля. Мэта застаецца вузкай: гэтая падсетка госця выходзіць праз тунель, а хост захоўвае звычайны знешні канал.
Запуск VPN-кліента ўнутры кантэйнера — іншая архітэктура, а не абыходны шлях. Тады кантэйнер валодае сваім тунелем і ўліковымі дадзенымі, а праца пераносіцца ў Dockerfile, замест таго каб заставацца ў iptables.
Перасылка выбірае маршрут па прызначэнні, потым NAT перапісвае крыніцу
Калі зададзены net.ipv4.ip_forward=1 усталяваны, перасылаемы пакет маршрутызуецца толькі па прызначэнні, у галоўнай табліцы. Крыніца не ўдзельнічае ў гэтым рашэнні. Толькі пасля выбару маршруту ядро запускае POSTROUTING, і менавіта там стаіць уласнае правіла Docker: -s 172.18.0.0/16 ! -o br-appnet -j MASQUERADE. Усё, што пакідае маст для іншага інтэрфейса, перакладаецца, і адрас, якім яно становіцца, залежыць ад інтэрфейса, куды яго адправіў маршрут.
Гэтае правіла тлумачыць большасць сімптомаў, пра якія мяне пытаюць. Калі ў галоўнай табліцы няма маршруту для прыватнага прызначэння, пакет выходзіць праз знешні канал з фізічным адрасам хоста. Ваш правайдэр скідае прызначэнне, або аддалены сайт скідае крыніцу 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Калі гэты адказ называе знешні канал, тунель не ў гульні, і ніякая налада на баку тунеля не дапаможа.
Перанясіце госця на ўласны адрас тунеля
Найменшая працоўная змена — гэта яўная перапіска крыніцы на выхадзе. Устаўце яе, а не дапісвайце: 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-рукапацісканне завяршаецца, а затым большая загрузка завісае без памылкі. Адпраўнік паўтарае той жа памер сегмента, бо паведамленне 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 і фільтра, якія вы дадалі. Правіла з нулём супадзенняў — гэта памылка маршрутызацыі, а не палітыкі.
- Пераканайцеся, што адрас тунеля ў вашым правіле SNAT — той, які кліент сапраўды трымае.
- Панізьце MTU госця або зацісніце MSS, перш чым вінаваціць сервер у застоі.
Прайдзіце па спісе і спыніцеся на першым збоі. Менавіта гэты пласт трэба выпраўляць; усё, што дадаецца ніжэй, толькі хавае мяжу, якая ламаецца.