Почему ваша публичная служба Linux отвечает через VPN
Локальная служба может принимать на eth0 и отвечать через VPN с полным туннелем. Правило источника с полной таблицей WAN удерживает эти ответы на исходном пути.
Ничто в сокете не помнит, какой интерфейс перенёс запрос, поэтому маршрут по умолчанию VPN с полным туннелем может перехватить ответ, который принадлежит WAN. Служба не игнорирует входящий интерфейс; Linux маршрутизирует локально созданный ответ по назначению и политике источника. Правило на основе источника может вместо этого отправлять только ответы службы через полную таблицу WAN, оставляя обычный выход в туннеле. Порт остаётся открытым, пока ответ уходит неверным путём, и поэтому симптом выглядит односторонним.
Заблуждение: ответы следуют за интерфейсом, на который пришли
Кажется логичным, что пакет, пришедший на eth0, должен уйти через eth0. Фильтры пакетов без состояния можно так написать, и tcpdump на входящей стороне показывает запрос именно там, где его ждут. Маршрутизация так не работает для трафика, который создаёт сам хост.
Как только служба принимает соединение, её ответ — это новый локально созданный пакет. Ядро смотрит правила маршрутизации для назначения и источника ответа, и маршрут по умолчанию VPN — вполне допустимый ответ. Ничто в сокете не помнит, какой интерфейс перенёс запрос.
Поэтому симптом выглядит односторонним: запросы приходят, ответы исчезают. Поэтому и правило брандмауэра для входящего порта ничего не меняет. Пакет не блокируется на пути внутрь; он маршрутизируется куда-то ещё на пути наружу. Проследите ответ поиском с указанием источника, и обходной путь станет виден.
Что ядро на самом деле маршрутизирует: адрес источника
Нужная зацепка — это локальный адрес, который служба использует для ответов. На хосте с иллюстративным адресом eth0 192.0.2.10/24 и шлюзом 192.0.2.1 этот адрес присутствует на интерфейсе. На облачной ВМ публичный адрес может быть транслирован выше и отсутствовать у гостя, поэтому используйте адрес, который хост действительно имеет.
Проверьте текущее состояние, прежде чем резервировать номер таблицы или приоритет правила. Существующие правила политики могут заслонить новое, а старая таблица с тем же номером может уже нести несвязанные маршруты:
ip -4 addr show dev eth0
ip -4 rule show
ip -4 route show table all
Затем выберите таблицу по источнику. Правило совпадает с ответами, которые уже несут 192.0.2.10, — именно с локально завершённым трафиком, который вы хотите исключить. Пересылаемый трафик и пакеты с других локальных адресов не совпадают.
Стройте полную таблицу возврата, а не половину маршрута по умолчанию
Таблицы только с маршрутом по умолчанию недостаточно. Ядру всё ещё нужно разрешить шлюз, а для этого нужен подключённый префикс в той же таблице. Таблица без такого префикса не работает при разрешении шлюза, даже если её маршрут по умолчанию выглядит правильно. Добавьте оба, затем направьте правило источника на таблицу:
ip -4 route add table 100 192.0.2.0/24 dev eth0 src 192.0.2.10
ip -4 route add table 100 default via 192.0.2.1 dev eth0
ip -4 rule add priority 1000 from 192.0.2.10/32 lookup 100
Используйте настоящий префикс и шлюз, а не копируйте пример. Облачному интерфейсу, настроенному как /32 со шлюзом на канале, нужен другой подключённый маршрут, а таблица, построенная из неверного префикса, отказывает на первом шаге. Атрибут src указывает предпочтительный источник для этого маршрута; он не заменяет правило источника.
Порядок тоже важен. Широкое правило с меньшим номером приоритета оценивается первым, поэтому до правила источника нужно добраться раньше любого правила, которое вернуло бы этот трафик в главную таблицу. Проверьте приоритеты с помощью ip rule show после добавления, а не только при загрузке.
Проверьте поиском с указанием источника
Попросите ядро маршрутизировать тестовое назначение с адреса службы. Ответ должен назвать eth0 и шлюз WAN, а не туннель:
ip -4 route get 198.51.100.25 from 192.0.2.10
Затем установите новое авторизованное соединение и наблюдайте оба направления. Поиск, выбирающий правильную таблицу, но живое соединение всё ещё не работает, указывает на фильтрацию обратного пути или правило брандмауэра, а не на таблицу. Проверьте эти механизмы, прежде чем что-то отключать глобально.
Поместите итоговые маршруты и правило под сетевой менеджер, которому принадлежит интерфейс. Изменения адреса, обновление DHCP и переподключения VPN переписывают состояние ядра, а правило, добавленное вручную, исчезает или указывает на устаревший шлюз после следующего события.
Где заканчивается это исключение
Правило исключает локально завершённый трафик с одного адреса источника. Оно не охватывает порты, публикуемые Docker, DNAT или службы, пересылаемые через хост, потому что такие пакеты маршрутизируются как пересылаемый трафик и никогда не несут локальный источник хоста тем же способом. Такие схемы обычно требуют метки, установленной на входе, и правила, совпадающего с меткой.
Оно также не заставляет публичный адрес хоста появляться на интерфейсе. Когда вышестоящий NAT владеет этим адресом, ответ всё равно должен уйти путём, которого ждёт этот NAT, поэтому таблица WAN должна отражать исходный шлюз и префикс, а не угаданный публичный диапазон.
Держите исключение задокументированным с номером таблицы, приоритетом правила и точным адресом источника. Следующее изменение в VPN или интерфейсе может тихо его сломать, а заметка с тремя значениями проверяется быстрее, чем повторное обнаружение симптома из отчёта клиента.
Если служба всё ещё отвечает неверным путём после успешного поиска с указанием источника, сравните адрес источника живого сокета с адресом в правиле. Служба, привязанная к адресу-шаблону, может выбрать другой источник, чем проверенный адрес, и это расхождение — оставшийся пробел.