Зв'язати два клієнти OpenVPN на одному хості Linux за допомогою fwmark
Обидва клієнти хочуть маршрут за замовчуванням. Закріпіть внутрішній сервер host-маршрутом, не дайте другому клієнту переписувати таблиці, а потім спрямуйте потоки через fwmark.
Додавання другого клієнта OpenVPN на один хост Linux зривається в одному з двох місць. Або внутрішній клієнт не може дістатися до свого сервера, бо зовнішній тунель захопив цей транспорт, або другий клієнт переписує таблицю маршрутів і перший тунель перестає працювати. Обидві проблеми в маршрутизації, не в шифруванні, і обидві вирішуються до того, як новий тунель понесе будь-який прикладний трафік. Адреси тут ілюстративні: tun0 — зовнішній тунель, 203.0.113.9 — внутрішній сервер, а TCP 9999 — потік, який має йти внутрішнім шляхом.
Два режими відмови, одна таблиця маршрутів
Внутрішній клієнт відкриває UDP-сокет до свого сервера, і цей сокет дотримується поточної таблиці маршрутів, як будь-який інший. Якщо зовнішній тунель уже володіє маршрутом за замовчуванням, пакети рукостискання йдуть у зовнішній тунель. Це працює лише тоді, коли зовнішній сервер може дістатися внутрішнього і протилежна сторона погоджується нести потік; інакше рукостискання застрягає і клієнт повторює спробу.
Друга відмова приходить із push сервера. Звичайний push — redirect-gateway def1, який встановлює 0.0.0.0/1 і 128.0.0.0/1 замість заміни маршруту за замовчуванням. Тому два клієнти, кожен з яких ставить свою пару /1, стикаються, і найновіша пара виграє майже для кожного призначення. Шукайте ці два префікси в таблиці, коли тунель падає після підключення другого, бо сам маршрут за замовчуванням міг взагалі не змінюватися.
Позначте потік і дайте йому таблицю
Спрямуйте вибраний трафік, позначивши його в таблиці mangle і зіставивши позначку в правилі політики. Правило вказує на виділену таблицю, маршрут за замовчуванням якої — внутрішній пристрій, тож лише позначені пакети йдуть внутрішнім шляхом.
iptables -t mangle -A PREROUTING -p tcp -m multiport --dport 9999 -j MARK --set-mark 0x9999
ip rule add fwmark 0x9999 table 200 pref 1000
ip route add default dev tun1 table 200Більшість відмов у цьому наборі правил походить із двох помилок. Значення позначки, яке задає міжмережевий екран, і значення, з яким зіставляє правило, мають бути однакові, бо розбіжність не дає ні помилки, ні ефекту. А PREROUTING бачить лише пакети, що приходять ззовні, тому з'єднанню, розпочатому на цьому хості, потрібне правило OUTPUT з тим самим зіставленням, перш ніж його взагалі позначять.
Заберіть володіння маршрутами в другого клієнта
Два варіанти не дають другому клієнту редагувати таблицю, і вони діють у різних точках. route-nopull ігнорує надіслані опції маршрутів, тож нічого не встановлюється. route-noexec приймає надіслані опції в середовище клієнта, але зміну таблиці лишає hook, і це саме те, що потрібно, коли треба побачити, що запропонував сервер.
Hook запускається з надісланими значеннями у своєму середовищі, і він має додавати лише схвалені вами записи. Зробіть його ідемпотентним: очищуйте таблицю, якою він володіє, перед перебудовою, і не припускайте, що попередній запуск залишив чистий стан. Перепідключення знову викликає hook, а hook, який додає без очищення, накопичує дублікати маршрутів.
Виберіть одного власника і запишіть його. Профіль, що задає обидва варіанти, або hook, який конкурує з ручним скриптом, породжує таблицю, яку потім ніхто не пояснить.
Закріпіть обидві адреси серверів
Внутрішньому серверу потрібен host-маршрут через зовнішній пристрій до підключення внутрішнього клієнта. Саме цей запис утримує внутрішній транспорт усередині зовнішнього тунелю, а не дозволяє йому йти за маршрутом за замовчуванням.
ip route add 203.0.113.9/32 dev tun0
ip route get 203.0.113.9Додайте також відповідний host-маршрут для зовнішнього сервера в головну таблицю, інакше перепідключення може спрямувати транспорт самого зовнішнього клієнта в його власний тунель. Порядок не факультативний: додавайте ці записи після появи потрібного інтерфейсу, тобто в hook або скрипті, що запускається після підключення. Маршрут, що вказує на вимкнений пристрій, видаляється або ігнорується, тож запис, зроблений під час завантаження, не переживає послідовність, яку ви припустили.
Перевіряйте пошук, а не читайте всю таблицю. Вивід пошуку маршруту називає пристрій і адресу джерела, які використає ядро, і це саме те, що клієнт отримує, відкриваючи свій сокет.
Перевірте ланцюг і зробіть відмову видимою
Знімайте трафік на зовнішньому інтерфейсі, поки внутрішній клієнт підключається. Внутрішнє рукостискання має з'явитися як зашифроване навантаження всередині зовнішнього тунелю; якщо воно видно як звичайний UDP до 203.0.113.9, host-маршруту немає і ланцюг ніколи не будувався.
Найгірший результат — ланцюг, що тихо повертається на фізичний uplink: стан інтерфейсу нічого не оголошує, і внутрішній трафік виходить із неправильної адреси джерела. Якщо другий клієнт може підключитися напряму, коли зовнішній тунель падає, вимкніть цей шлях або прив'яжіть його до зовнішнього інтерфейсу, щоб відмова була видимою.
Завершіть двома потоками й одним дампом правил: позначене і непозначене з'єднання мають іти різними шляхами, а ip rule show має перелічити задуманий пріоритет. Якщо обидва потоки йдуть одним шляхом, позначка застосовується не там, де ви думаєте.