Почему ваше правило iptables никогда не видит трафик VPN через мост
Кадры, проходящие через мост, могут обходить цепочку IP, за которой вы наблюдаете. Сравните встроенное семейство bridge со старым br_netfilter, прежде чем добавлять ещё одно правило.
Две виртуальные машины на одном хосте попадают в LAN через мост, а правило, помещённое в цепочку ip forward, показывает счётчик, который никогда не двигается. Кадры реальны, трафик идёт, и правило просто не находится на его пути. Сравнение двух способов, которыми Linux показывает трафик через мост, объясняет это молчание и объясняет также, почему загрузка одного старого модуля — худший ответ, чем кажется.
Два пути через один хост
Пакет может покинуть этот хост двумя очень разными способами, и разница определяет, какие хуки его увидят. Если назначение находится за физическим портом и кадр коммутируется между двумя портами моста, хост пересылает кадр Ethernet; код маршрутизации не спрашивается, и ни одного маршрутизируемого перехода не происходит. Если назначение — сам хост или сеть, доступная через туннельный интерфейс, пакет маршрутизируется, и хуки уровня IP действительно применяются. Держите эту развилку в голове, потому что каждый последующий вопрос о цепочках и семействах вытекает из того, какой из двух путей выберет кадр.
Это различие — вся история за правилом, которое ничего не считает. Кадр через мост от veth9a1 к enp3s0 пересекает мост, а не стек IP, поэтому цепочка ip forward не видит ничего для сопоставления. Имена интерфейсов в более старом примере не определяют, какой путь у вас, и поэтому первый шаг — смотреть на топологию, а не на набор правил.
Туннель добавляет третий случай, достойный названия. Устройство TUN доставляет пакеты IP хосту, поэтому они маршрутизируются и действительно проходят через цепочки IP, тогда как устройство TAP, подключённое к мосту, доставляет кадры и ведёт себя как случай моста. Тип устройства, а не название продукта, решает.
Сравните два семейства, которые могут видеть кадр через мост
Linux поддерживает встроенную фильтрацию моста через семейство bridge в nftables и не нуждается ни в каком старом модуле, чтобы проверять и отбрасывать кадры на лету. Собственная документация по мосту ядра даёт эту рекомендацию напрямую, что стоит знать до того, как результат поиска скажет вам загрузить модуль.
| Observation point | What it sees | What it costs |
|---|---|---|
nftables bridge family chains | Ethernet frames forwarded between bridge ports, in both directions | Rules must be written in bridge syntax with link-layer matches |
Legacy br_netfilter plus an ip chain | Bridged IPv4 and IPv6 packets, re-exposed through IP-layer hooks | Existing host rules may begin matching traffic they never saw before |
inet chain for routed traffic | Packets the host routes, including traffic arriving from a TUN device | No visibility into purely switched frames |
Использование команды nft вместо iptables само по себе не помещает правило в правильное семейство. Цепочка, объявленная в семействе bridge, видит кадры; цепочка, объявленная в ip, ip6 или inet, их не видит, независимо от того, какой двоичный файл её создал. Семейство — свойство цепочки, а не инструмента, который её набрал.
Старый путь работает иначе, и именно поэтому стоит сравнивать, а не выбирать сторону по привычке. Загрузка br_netfilter и включение настроек bridge-nf-call снова открывает трафик IPv4 и IPv6 через мост через хуки уровня IP, так что обычное правило iptables наконец может с ним совпасть. Кадры не меняются; меняется набор хуков, которые они проходят, а значит, у каждого существующего правила хоста теперь новая аудитория.
Прочитайте фактический путь, прежде чем добавлять правило
Три команды устанавливают, где вы находитесь: к чему подключён мост, активен ли старый модуль и какие цепочки уже существуют в семействе bridge. Запустите их, прежде чем что-либо писать, потому что ответ определяет, какой из двух проектов вы расширяете. Пропуск этого шага — это то, как правильное правило оказывается в цепочке, через которую ничего не проходит.
bridge link show
lsmod | grep br_netfilter
nft list ruleset | grep -A4 "chain forward"
Вывод чётко разделяет случаи. Порты, перечисленные под мостом, говорят, какие интерфейсы коммутируются; пустой результат lsmod означает полное отсутствие видимости на уровне IP для кадров через мост; существующая цепочка forward семейства bridge означает, что владелец политики уже есть и ваше правило относится туда, а не в новую собственную цепочку.
Затем добавьте одно легко читаемое правило, со счётчиком и без изменения вердикта, и пропустите через мост один представительный поток. Движущийся счётчик доказывает, что кадр дошёл до правила. Это всё, что он доказывает, поэтому пройдите и по запрещённому пути, ведь политика, принимающая всё, тоже даёт движущийся счётчик.
nft add rule bridge filter forward iifname "veth9a1" oifname "enp3s0" counter acceptГде старый модуль меняет поведение
Включение старого пути на работающем мосте — не нейтральное действие по добавлению видимости. Правила, которые раньше были недостижимы для трафика через мост, становятся достижимыми, включая правила хоста, написанные для другого интерфейса, а политика drop по умолчанию в цепочке ip forward может внезапно примениться к кадрам, которые раньше проходили нетронутыми. На хосте, которым управляют через этот мост, результат — блокировка без консоли. Это не гипотетический режим отказа; это обычное следствие расширения набора хуков на машине, до которой вы добираетесь по только что расширенному пути.
Более безопасная последовательность — решить, какое семейство владеет политикой, записать правила там и сохранить независимый путь восстановления, прежде чем менять любой sysctl. Если два семейства держат правила, задокументируйте, какое из них главное; две пересекающиеся политики — это то, как пакет принимается в одном месте и отбрасывается в другом без единого файла для чтения.
Изоляция сама по себе всё ещё требует полного проекта. Разрешение одному источнику достигать одного назначения доказывает только то, что один поток работает, тогда как требование обычно состоит в том, что ВМ не может достичь соседей или хоста. Счётчики на правилах отказа важнее счётчика на правиле приёма, потому что счётчик приёма — тот, движение которого вы уже ожидали.
Наконец, держите случай входного трафика хоста отдельно. Трафик, адресованный собственному IP моста, приходит на input, а не на forward, и правило в forward никогда его не увидит, какое бы семейство вы ни выбрали. Три случая, два семейства, один урок: путь определяет хук, а хук определяет, существует ли ваше правило вообще.