Запит завжди є варіацією однієї й тієї самої фрази: залиште VPN для браузера, але дозвольте SSH або завданню резервного копіювання використовувати звичайне з'єднання. Порада, що ходить із цього приводу, — пробросити порт правилами NAT у iptables, — цього не робить. Правило NAT переписує адреси й порти всередині потоку; воно ніколи не вибирає інтерфейс, через який пакет виходить. Це рішення належить пошуку маршруту, а пошуку маршруту потрібна допомога, щоб побачити порт.

Чому звичайний маршрут не може збігтися з портом

Linux вибирає маршрут, шукаючи адресу призначення в таблиці, після фільтрації таблиць-кандидатів правилами політики. Ці правила можуть збігатися з міткою firewall, адресою джерела, вхідним інтерфейсом або UID. Сам запис маршруту не має поля порту, бо маршрут вибирається раніше, ніж заголовок транспорту починає мати значення.

Поточні iproute2 і ядра розширюють селектор правил: ipproto, sport і dport приймаються, тож ip rule add ipproto tcp dport 22 table 100 виражає намір одним рядком. Запустіть ip rule add help на реальному хості, щоб побачити, чи перелічує ваша збірка dport. Якщо так, ви обходитеся однією командою; якщо ні, шлях через мітку нижче є еквівалентом.

Так чи інакше, правило — лише вказівник. Таблиці 100 все ще потрібен маршрут, і саме цю частину люди забувають.

Три частини або одна

Сучасна форма — одне правило вище плюс маршрут в альтернативній таблиці. Класична форма додає netfilter посередині: позначте пакет, потім зіставте мітку в правилі. Обидві закінчуються одним і тим самим пошуком; різняться вони тим, де читається порт.

Підхід із міткою існує тому, що netfilter бачить заголовок транспорту, а код маршрутизації історично його не бачив. Це весь механізм, і він пояснює, чому запасний шлях — три взаємодійні частини, а не одна хитра команда.

Три частини по порядку

Спершу позначте потоки, які хочете відвести, у ланцюжку, що запускається достатньо рано, щоб мітка існувала до рішення про маршрут:

nft add table inet vpnexempt
nft 'add chain inet vpnexempt out { type route hook output priority mangle; }'
nft add rule inet vpnexempt out tcp dport 22 meta mark set 0x1

По-друге, надішліть позначені пакети в окрему таблицю:

ip rule add fwmark 0x1/0x1 table 100

По-третє, дайте цій таблиці маршрут за замовчуванням через фізичний канал. Адреси наведено для прикладу; підставте свої:

ip route add default via 192.168.1.1 dev wlan0 table 100

Пропустіть третій крок, і позначений пакет не знайде маршруту в таблиці 100, тому не пройде. Симптом виглядає так, ніби правило зламало мережу, хоча насправді воно прибрало єдиний шлях, що залишався в пакета.

Що утримує це в роботі після першого пакета

Мітки застосовуються до вихідних пакетів. Відповідь приходить на фізичний інтерфейс з адресою, яку призначив цей канал, і основна таблиця може досі віддавати перевагу тунелю для цього призначення. Два механізми зберігають узгодженість зворотного шляху: правило на основі джерела для адреси каналу і connection tracking, який зберігає рішення протягом усього життя потоку. Збереження мітки в conntrack і її відновлення у відповіді — звичний спосіб зробити виняток зі станом стійким.

Фільтрація зворотного шляху — другий мовчазний убивця. Якщо ядро строго перевіряє зворотний шлях, а відповідь приходить на інтерфейс, який основна таблиця не вибрала б, пакет відкидається без повідомлення. rp_filter значення за замовчуванням різняться за дистрибутивами й залежно від того, чи інтерфейс пересилається, тому читайте дійсне значення, а не припускайте, що воно вимкнене.

Не позначайте власний транспорт тунелю. Тунель WireGuard або UDP OpenVPN — це не TCP 22, тому рецепт вище безпечний, але виняток, записаний як усе, крім призначення тунелю, може захопити зашифровані пакети й зламати тунель так, що це виглядатиме як петля маршрутизації.

Перевіряйте, а не припускайте

Запитайте ядро, який шлях воно вибере, для призначення і для порту, потім виконайте реальне передавання:

ip route get 203.0.113.10
ip rule show
nft list ruleset

Якщо мітка є, але лічильники ніколи не рухаються, правило не збігається. Лічильники — це різниця між конфігурацією, яку ви написали, і політикою, яку застосовує ядро.

Коли це неправильний інструмент

Винятки за портом тендітні за своєю природою. Служби змінюють порти, HTTP/3 приходить як UDP 443, а одна програма може відкрити кілька потоків різних типів. Якщо справжня вимога — щоб ця програма використовувала звичайний канал, рівень маршрутизації — неправильне місце; простір імен мережі або проксі на програму підходять краще.

Якщо вимога — щоб ця підмережа використовувала звичайний канал, зовсім відмовтеся від міток і додайте конкретніший маршрут. Це простіше, це видно в одній таблиці, і наступна людина може перевірити це, не читаючи набір правил.

Запишіть, яку з трьох вимог ви насправді маєте, перш ніж торкатися firewall. Механізм вище точний, а точність, застосована не до того рівня, — це просто довший простій.

Схожі статті