Хост продолжает отвечать по SSH после поднятия VPN с полным туннелем, а сервис, опубликованный через Docker на той же машине, замолкает. Со слушателем всё в порядке: контейнер работает, сопоставление портов существует, и пакет приходит. Контейнер здоров, и запрос действительно до него доходит. Разница в том, откуда начинается ответ, когда Linux выбирает маршрут, и эта разница решает, какая из двух схем ниже вам нужна.

Проследите кортеж через NAT назначения

Возьмите хост на 192.0.2.10, публикующий TCP 2222 на контейнер на 172.30.240.5:22. Запрос от 198.51.100.25 преобразуется до того, как достичь контейнера, и контейнер отвечает как он сам.

WAN request:      198.51.100.25 -> 192.0.2.10:2222
after DNAT:       198.51.100.25 -> 172.30.240.5:22
container reply:  172.30.240.5:22 -> 198.51.100.25

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

Почему правило только для хоста это упускает

Правило, выбирающее таблицу WAN только для пакетов от 192.0.2.10, охватывает собственные демоны хоста и полностью упускает этот пересланный ответ. Адрес источника в ответе — 172.30.240.5, а не хост.

Избыточная коррекция — направлять всё из контейнера через WAN, что также освобождает несвязанные исходящие соединения контейнера от VPN. Это другая политика, чем запрошенная, и обычно кого-то удивляет позже.

Сопоставление после DNAT — другой hook

Документация Docker по межсетевым экранам отмечает, что обычная пользовательская фильтрация видит назначение после DNAT, поэтому сопоставление опубликованного порта 2222 в обычном правиле — не то же самое, что сопоставление порта 22 контейнера. Если исходный кортеж важен, используйте поддерживаемый механизм исходного назначения вместо догадок о том, какой порт виден.

Эта путаница с hook-ами порождает правила, которые выглядят правильными и никогда не совпадают. Проверьте счётчики на самом правиле, прежде чем переписывать политику. Правило без единого попадания говорит вам, что оно вычисляется там, куда пакет никогда не доходит.

Схема A: нести решение как состояние соединения

Метка соединения может пронести решение о входе через преобразование, но она должна быть возвращена пакету до того поиска маршрута, который имеет значение. Этапы таковы:

  1. Зарезервируйте поле метки и приоритет правила, которые не конфликтуют с Docker, VPN или другим владельцем политики.
  2. Определите только предполагаемое новое соединение WAN в точке до DNAT, где доступно отслеживание соединений.
  3. Запишите решение в метку соединения, не заставляя сам исходный запрос идти в таблицу WAN.
  4. В направлении ответа восстановите на пакете только свои биты метки перед маршрутизацией.
  5. Выберите полную таблицу WAN достаточно рано, чтобы правило VPN не могло победить первым.

Этой таблице нужны реальные подключённые маршруты и шлюз. Ей также нужно определённое поведение, когда маршрут отсутствует. Тихий провал в туннель — худший исход, потому что конфигурация всё ещё выглядит рабочей. Вместо этого откажите закрыто и запишите это.

Схема B: завершать на хосте

Намеренно настроенный фронтенд на стороне хоста, например TCP-прокси или обратный прокси перед контейнером, удерживает ответ исходящим с адреса хоста. Тогда правила, написанные для хоста, ведут себя как ожидалось, а контейнер остаётся в своей частной сети.

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

Проверяйте изменение, а не намерение

Откройте новое авторизованное соединение с публичной стороны и убедитесь, что ответ уходит через нужный интерфейс. Затем проверьте, что несвязанный запрос, инициированный контейнером, по-прежнему идёт через VPN.

Не очищайте таблицу соединений машины, чтобы проверить одно правило: существующие записи сохраняют прежнее сопоставление, а очистка ломает всё остальное на хосте. Публикация порта определяет доступность. Она не определяет путь ответа. Запишите выбранную схему с именами её таблиц, чтобы следующему человеку не пришлось открывать её заново во время инцидента.

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