Чому ваше правило 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 ніколи його не побачить, яку б родину ви не обрали. Три випадки, дві родини, один урок: шлях визначає хук, а хук визначає, чи існує ваше правило взагалі.