Правила для портів 80 і 443 не відправляють браузер через VPN
Збіг за портом вибирає TCP-потоки, а не браузер. HTTP/3 працює поверх UDP, DNS може піти іншим шляхом, а засіб оновлення потрапляє під те саме правило, яке ви написали.
Хтось просить, щоб браузер використовував тунель, і відповідь повертається як правило міжмережевого екрана, що збігається з портами призначення 80 і 443. Воно виглядає точним і таким не є. Збіг за портом вибирає пакети за заголовком транспорту, а не за тим, яка програма відкрила сокет, і сучасний перегляд не обмежується цими двома портами на TCP. Правило пропустить частину трафіку, який ви хотіли включити, і зловить трафік, який ви ніколи не збиралися чіпати. Перш ніж писати його, вирішіть, яка з трьох різних меж вам справді потрібна.
Правило описує клас транспорту, а не застосунок
У Linux збіг за портом призначення перевіряє заголовок TCP і нічого вище нього. Позначення цих пакетів і скерування їх політичним маршрутом працює точно так, як описано, і жоден із цих механізмів не знає, що означає корисне навантаження і який процес його надіслав. Позначка — це число, прикріплене до пакета, а не ідентичність, прикріплена до застосунку.
nft add rule inet mangle output tcp dport { 80, 443 } meta mark set 0x1
ip rule add fwmark 0x1 table 100
ip route add default dev wg0 table 100
Одразу випливають два наслідки. Засіб оновлення програм, який звертається до TCP 443, отримує таке саме ставлення, як браузер, і це може бути нормально, а може бути збоєм, що чекає наступного циклу оновлення. А вебслужба, навмисно опублікована на іншому порту, скажімо 8443, повністю випадає з правила, хоч це все ще той вебтрафік, який був важливий користувачеві.
Числа є законними селекторами, коли вимога справді стосується тих портів. Вони стають вигадкою, коли в заявці написано «браузер», а в наборі правил реалізовано діапазон портів. Ця прогалина не є дрібницею документації. Це різниця між правилом, яке ви можете захистити на розборі, і таким, яке ніхто не може перевірити.
HTTP/3 тихо залишає ваше правило TCP
HTTP/3 працює поверх QUIC, а QUIC переноситься в UDP. Правило, що збігається лише з TCP, не може його побачити, тому той самий браузер, який проходить вашу тестову сторінку, може надіслати пізніший запит через UDP і обійти тунель, і ніхто при цьому не змінює налаштування. RFC 9114 описує HTTP/3 як протокол на основі QUIC, а QUIC використовує UDP з портом за замовчуванням 443 у звичайному розгортанні.
Чи спробує конкретний браузер HTTP/3, залежить від його конфігурації, від сайту і від того, чи вдалася попередня спроба. Тому міркувати про це з пам'яті погано. Запитайте інструмент:
curl --http3 -sS -o /dev/null -w '%{http_version} %{time_total}\n' https://example.com
ss -tunp | grep -E ':443\b'
Рядок curl друкує узгоджену версію, тому 3 підтверджує, що використано шлях через UDP. Далі список сокетів показує, чи прив'язане це з'єднання до інтерфейсу тунелю чи до фізичного. Якщо HTTP/3 немає у вашій збірці curl, прапорець гучно зазнає невдачі, а не тихо відступає, і це його власна маленька милість.
DNS — окреме рішення, яке треба ухвалити
Правило порту для вебтрафіку нічого не каже про розв'язання імен. Браузер може використовувати системний розв'язувач, розв'язувач, налаштований усередині браузера, або той, який підсунуло останнє з'єднання. Кожен із них може надсилати запити іншим шляхом, ніж той, який вибирає ваше правило, і це дає класичний розкол: сторінка завантажується, ім'я розв'язується десь, де ви не чекали, а внутрішній сайт розв'язується в публічну адресу або не розв'язується зовсім.
Вирішіть це явно. Якщо внутрішні імена мають розв'язуватися через тунель, розв'язувач повинен бути всередині нього, а набір правил має дозволяти DNS до цього розв'язувача, тримаючи інші запити зовні. Якщо браузер використовує DNS over HTTPS до публічного постачальника, запит повністю обходить ваш розв'язувач, і правило порту не може допомогти. Запишіть вибір у той самий журнал змін, що й правило маршрутизації, бо обидва читає разом той, хто зневаджуватиме наступний збій.
Потім перевірте розв'язувачем, а не сторінкою:
resolvectl query intranet.example.internal
resolvectl status | grep -A3 'Current DNS Server'
Відсутня відповідь — це проблема розв'язання, навіть коли повідомлення про помилку браузера звинувачує мережу. Ці два збої виглядають для користувача однаково і є цілком різними заявками.
Три межі, три різні механізми
Більшість плутанини зникає, щойно ви назвете потрібну межу. Вони не взаємозамінні, і кожна має інший режим відмови під час обриву тунелю.
| What you actually want | Mechanism that fits | What it does not cover |
|---|---|---|
| These transport ports use this exit | Firewall mark plus policy route | Applications, HTTP/3 over UDP, DNS |
| This browsing session stays separate | Application-aware VPN, or a separate network namespace plus a proxy | Other applications in the same session |
| Only these private sites are reachable | Destination-based routes with private DNS | Public sites on the same address family |
Окремий профіль браузера — це пастка в середньому рядку. Він розділяє файли cookie та історію і нічого не змінює в таблиці маршрутизації хоста, тож обидва профілі йдуть тим самим шляхом. Ізоляція потребує того, що забезпечує ядро, тобто простору імен, контейнера або застосунку з власним інтерфейсом тунелю.
Який би рядок не стосувався, запишіть, що має відбуватися, коли тунель падає. Маршрут на основі позначки, що зникає разом з інтерфейсом, зазвичай повертається до фізичного маршруту за замовчуванням, а це поведінка fail-open. Це може бути прийнятно для перегляду і неприйнятно для всього іншого, і це рішення, а не деталь реалізації.
Перевіряйте потоки, які нібито охоплюєте
Приймальний тест має йти за межею, яку ви записали, а не за зручними номерами портів. Виберіть один потік, який має йти через тунель, один, який не має, і один, що використовує транспорт, про який ви не думали:
ip route get 203.0.113.40
curl -sS -o /dev/null -w 'v4 %{http_version} %{remote_ip}\n' https://example.com
nft list ruleset | grep -A2 'dport'
Пошук маршруту повідомляє, який інтерфейс призначення справді використовує, і це той факт, який більшість звітів зазначають неправильно. Рядок curl підтверджує узгоджену версію і родину адрес. Вивантаження набору правил ловить правило, додане під час тестування і забуте обмежити, і так тимчасова позначка стає постійною маршрутизацією fail-open.
Зафіксуйте всі три виводи з позначкою часу і зберігайте їх разом зі зміною. Коли пізніше хтось повідомить, що «VPN зламав інтранет», різниця між задуманою і реалізованою межею буде видна в цьому записі, і виправленням стане зміна правила, а не суперечка.