Представьте хост Linux, который завершает VPN и одновременно запускает контейнер в сети macvlan. LAN — это 192.168.60.0/24, контейнер имеет 192.168.60.210, а VPN-клиенты приходят из 10.95.0.0/24. Клиент доходит до сервиса, ответ никогда не возвращается, и каждое правило брандмауэра на хосте выглядит правильным. Этот симптом создают два отдельных ограничения, и исправление одного оставляет другое на месте.

Два ограничения, один симптом

Первое ограничение — изоляция: драйвер macvlan в Docker не позволяет контейнеру общаться с собственным хостом через родительский интерфейс. У трафика между стеком хоста и контейнером нет пути, как бы ни были записаны маршруты на хосте.

Второе ограничение — обратный путь. Шлюз по умолчанию у контейнера обычно LAN-маршрутизатор, поэтому ответ, адресованный VPN-клиенту, уходит через этот маршрутизатор. Если у маршрутизатора нет маршрута назад в пул VPN, ответ отбрасывается где-то посередине.

Оба отказа выглядят со стороны клиента одинаково: запрос приходит, а ответ никогда не приходит. Им нужны разные исправления, а широкое правило masquerade может скрыть, какое из них всё ещё сломано. Поэтому первый тест — это захват на стороне контейнера, а не очередное изменение брандмауэра. Если запрос там никогда не появляется, значит действует ограничение изоляции.

Постройте shim на стороне хоста, узко

Второй интерфейс macvlan на хосте, подключённый к тому же родительскому интерфейсу в режиме моста, даёт стеку хоста собственную идентичность в этом сегменте LAN. Зарезервируйте для него адрес вне пула DHCP и вне диапазона контейнера и направляйте через него только контейнер.

ip link add macvlan-vpn link eth0 type macvlan mode bridge
ip address add 192.168.60.250/32 dev macvlan-vpn
ip link set macvlan-vpn up
ip route add 192.168.60.210/32 dev macvlan-vpn src 192.168.60.250

Первая команда создаёт интерфейс в режиме моста, что является документированным значением по умолчанию для этого драйвера и тем режимом, который нужен для трафика между хостом и контейнером. Вторая назначает /32, чтобы адрес не конфликтовал с префиксом LAN, а четвёртая отправляет через shim только адрес контейнера.

Не перенаправляйте всю LAN через новый интерфейс. Обычный подключённый маршрут остаётся как есть, а маршрут хоста намеренно узкий, чтобы один контейнер получил путь, не меняя поведение остальной части сегмента. Маршрут по умолчанию в сторону shim был бы другим решением с другими последствиями. Держите его конкретным, и радиус поражения останется небольшим.

Эти команды меняют сетевые настройки на работающем хосте. На устройстве или управляемой платформе используйте поддерживаемый механизм конфигурации этой платформы, потому что интерактивное изменение в root-shell может не пережить обновление.

Решите, как сохраняется источник

Когда shim на месте, контейнер может добраться до хоста, и вопрос становится в том, какой адрес источника несёт ответ. Сохранение исходного источника VPN — более чистый вариант: контейнер видит настоящий адрес клиента, и логи и правила доступа сервиса могут его использовать.

Чтобы это работало, сетевая конфигурация контейнера для этого пути должна отправлять трафик для 10.95.0.0/24 на 192.168.60.250 как на свой шлюз. Это поддерживаемая настройка маршрута или шлюза внутри собственной конфигурации контейнера, а не уловка на стороне хоста. Подтвердите её изнутри контейнера, а не предполагайте, что настройка вступила в силу. Шлюз по умолчанию, который всё ещё указывает на LAN-маршрутизатор, будет продолжать отправлять ответы в ту же дыру.

Если утверждённый дизайн, наоборот, скрывает источники VPN, ограничьте перевод узко: реальный пул VPN, shim как исходящий интерфейс и конкретный контейнер и сервис.

iptables -t nat -A POSTROUTING -s 10.95.0.0/24 -o macvlan-vpn -j SNAT --to-source 192.168.60.250

Сопоставьте shim как исходящий интерфейс, а не туннель. Правило, которое называет туннель исходящим интерфейсом, не совпадёт с пакетом, идущим к контейнеру, и именно эта ошибка обычно присутствует в такой схеме. Держите правило таким же узким, как предложение выше, и пересматривайте его при изменении пула.

Проверяйте со стороны клиента, а не хоста

Запрос с самого хоста, привязанный к адресу shim, доказывает, что shim может добраться до контейнера. Он не доказывает, что контейнер может вернуть трафик сохранённому источнику 10.95.0.x, потому что адрес источника в этом тесте — собственный адрес shim.

curl -sS --interface 192.168.60.250 -o /dev/null -w '%{http_code}\n' http://192.168.60.210:8080/health
sudo tcpdump -ni macvlan-vpn host 192.168.60.210

Первая команда проверяет участок от хоста до контейнера, а вторая показывает, что фактически проходит через shim, пока настоящий VPN-клиент делает запрос. Посмотрите на адрес источника на входящем пути и на то, появляется ли ответ с тем же потоком.

На хосте должна быть включена пересылка, а пути пересылки нужно разрешение для пула VPN к shim на порту сервиса плюс установленные ответы. Правило INPUT на хосте не разрешает пересылаемый трафик, и это частая причина того, что пакет отбрасывается после того, как маршрутизация выглядит правильной.

Запустите настоящее приложение с настоящими учётными данными, затем проверьте, что второй, несвязанный сервис на том же контейнере по-прежнему вам отказывает. Если единственным способом заставить это работать было открыть каждое правило пересылки, дизайн не завершён. Отметьте также, какой адрес источника записал сервис, потому что именно его будут использовать его собственные правила доступа.

Похожие статьи