Правильно, і богу шифрування сьогодні скажемо те саме.
Тут буде про нешифрований IPv4 тунель, але не про «теплий ламповий», а про модерновий «світлодіодний». А ще тут з'являються сирі сокети, і йде робота з пакетами в просторі користувача.
Є N протоколів тунелювання на будь-який смак та колір:
Але яжпрограміст, тому збільшу N лише на дещицю, а розробку цих протоколів залишу Ъ-девелоперам.
В одному ще не народженому проекті, Яким зараз займаюся, треба достукатися до хостів за NAT ззовні. Використовуючи для цього протоколи з дорослою криптографією, мене ніяк не залишало відчуття, що це як гармата по горобцях. Т.к. тунель використовується здебільшого тільки для проколупування дірки в NAT-e, внутрішній трафік зазвичай теж зашифрований, все ж топлять за HTTPS.
Досліджуючи різні протоколи тунелювання увагу мого внутрішнього перфекціоніста щоразу привертав IPIP через його мінімальні накладні витрати. Але в нього є півтора суттєвих недоліків для моїх завдань:
він вимагає публічні IP на обох сторонах,
і жодної тобі автентифікації.
Тому перфекціоніст заганявся назад у темний кут черепної коробки, або де він там сидить.
І ось якось читаючи статті з тунелям, що нативно підтримуються у Linux натрапив на FOU (Foo-over-UDP), тобто. будь-що, загорнуте в UDP. Поки з чого-небудь підтримуються тільки IPIP і GUE (Generic UDP Encapsulation).
«Ось вона срібна куля! Мені й простого IPIP за очі». - думав я.
Насправді куля виявилася не до кінця срібною. Інкапсуляція в UDP вирішує першу проблему - до клієнтів за NAT-ом можна підключатися зовні використовуючи заздалегідь встановлене з'єднання, але тут половинка наступного недоліку IPIP розквітає в новому світлі - за видимими публічними IP і портом клієнта може ховатися будь-хто з приватної мережі (у чистому IPIP цієї проблеми немає).
Для вирішення цієї проблеми і народилася утиліта ipipou. У ній реалізований самопальний механізм аутентифікації віддаленого хоста, при цьому не порушуючи роботи ядреного FOU, який швидко і ефективно обробляти пакети в просторі ядра.
Не потрібен твій скрипт!
Ок, якщо тобі відомі публічні порт та IP клієнта (наприклад за ним усі свої, куди попало не ходять, NAT намагається мапити порти 1-в-1), можеш створити IPIP-over-FOU тунель наступними командами, без жодних скриптів.
на сервері:
# Подгрузить модуль ядра FOU
modprobe fou
# Создать IPIP туннель с инкапсуляцией в FOU.
# Модуль ipip подгрузится автоматически.
ip link add name ipipou0 type ipip
remote 198.51.100.2 local 203.0.113.1
encap fou encap-sport 10000 encap-dport 20001
mode ipip dev eth0
# Добавить порт на котором будет слушать FOU для этого туннеля
ip fou add port 10000 ipproto 4 local 203.0.113.1 dev eth0
# Назначить IP адрес туннелю
ip address add 172.28.0.0 peer 172.28.0.1 dev ipipou0
# Поднять туннель
ip link set ipipou0 up
на клієнті:
modprobe fou
ip link add name ipipou1 type ipip
remote 203.0.113.1 local 192.168.0.2
encap fou encap-sport 10001 encap-dport 10000 encap-csum
mode ipip dev eth0
# Опции local, peer, peer_port, dev могут не поддерживаться старыми ядрами, можно их опустить.
# peer и peer_port используются для создания соединения сразу при создании FOU-listener-а.
ip fou add port 10001 ipproto 4 local 192.168.0.2 peer 203.0.113.1 peer_port 10000 dev eth0
ip address add 172.28.0.1 peer 172.28.0.0 dev ipipou1
ip link set ipipou1 up
де
ipipou* - Ім'я локального тунельного мережного інтерфейсу
203.0.113.1 - Публічний IP сервера
198.51.100.2 - Публічний IP клієнта
192.168.0.2 - IP клієнта, призначений інтерфейсу eth0
10001 - локальний порт клієнта для FOU
20001 - Публічний порт клієнта для FOU
10000 - Публічний порт сервера для FOU
encap-csum — опція для додавання контрольної суми UDP до інкапсульованих UDP пакетів; можна замінити на noencap-csum, Щоб не рахувати, цілісність і так контролюється зовнішнім шаром інкапсуляції (поки пакет знаходиться всередині тунелю)
eth0 — локальний інтерфейс, до якого буде прив'язаний тунель.
172.28.0.1 - IP тунельного інтерфейсу клієнта (приватний)
172.28.0.0 - IP тунельного інтерфейсу сервера (приватний)
Поки жваво UDP-з'єднання, тунель буде у працездатному стані, а як порветься те, як пощастить — якщо IP: порт клієнта залишаться колишніми — житиме, зміняться — порветься.
Повертати все назад найпростіше вивантаживши модулі ядра: modprobe -r fou ipip
Навіть якщо автентифікація не потрібна публічні IP і порт клієнта не завжди відомі та часто непередбачувані чи мінливі (залежно від типу NAT). Якщо опустити encap-dport на боці сервера, тунель не запрацює, не настільки він розумний, щоб купувати віддалений порт з'єднання. У цьому випадку ipipou теж може допомогти, ну або WireGuard і що з ним тобі на допомогу.
Як це працює?
Клієнт (що зазвичай за NAT-ом) піднімає тунель (як у прикладі вище), і надсилає пакет з автентифікацією на сервер, щоб той налаштував тунель зі свого боку. Залежно від налаштувань це може бути порожній пакет (просто, щоб сервер побачив публічні IP: порт з'єднання), або з даними, за якими сервер зможе ідентифікувати клієнта. Дані можуть бути простою парольною фразою відкритим текстом (на думку спадає аналогія з HTTP Basic Auth) або підписані приватним ключем спеціально оформлені дані (за аналогією з HTTP Digest Auth тільки сильніше, див. функцію client_auth у коді).
На сервері (сторона з громадським IP) при запуску ipipou створює обробник черги nfqueue і налаштовує netfilter так, щоб необхідні пакети прямували куди слід: пакети ініціалізують з'єднання в чергу nfqueue, а [майже] всі інші прямо в listener FOU.
Хто не в темі, nfqueue (або NetfilterQueue) - це така спеціальна штука для дилетантів, які не вміють розробляти модулі ядра, яка засобами netfilter (nftables/iptables) дозволяє перенаправляти мережеві пакети в простір користувача і обробляти їх там примітивними підручними засобами: ) і віддавати назад ядру, чи відкидати.
Для деяких мов програмування є біндінги для роботи з nfqueue, для bash не знайшлося (хех, не дивно), довелося використовувати python: ipipou використовує NetfilterQueue.
Якщо продуктивність не критична, за допомогою цієї штуки можна відносно швидко і просто куховарити власну логіку роботи з пакетами на досить низькому рівні, наприклад ваять експериментальні протоколи передачі даних, або тролити локальні та віддалені сервіси нестандартною поведінкою.
Пліч-о-пліч з nfqueue працюють сирі сокети (raw sockets), наприклад коли тунель вже налаштований, і FOU слухає на потрібному порту, звичайним способом відправити пакет з цього ж порту не вийде - зайнято, зате можна взяти і запулити довільно згенерований пакет прямо в мережевий інтерфейс використовуючи сирий сокет, хоч над генерацією такого пакета і доведеться повозитися трохи більше. Так і створюються в ipipou пакети з автентифікацією.
Так як ipipou обробляє тільки перші пакети зі з'єднання (ну і ті, які встигли проникнути в чергу до встановлення з'єднання), продуктивність майже не страждає.
Як тільки ipipou-сервер отримує пакет, що пройшов автентифікацію, тунель створюється і всі наступні пакети в з'єднанні вже обробляються ядром nfqueue. Якщо з'єднання протухло, то перший пакет наступного буде направлений в чергу nfqueue, залежно від налаштувань, якщо це не пакет з автентифікацією, але з останнього IP і порту клієнта, він може бути або пропущений далі або відкинутий. Якщо автентифікований пакет надходить з нових IP та порту, тунель переналаштовується на їх використання.
У звичайного IPIP-over-FOU є ще одна проблема при роботі з NAT — не можна створити два IPIP тунелі інкапсульовані в UDP з однаковими IP, оскільки модулі FOU та IPIP досить ізольовані один від одного. Тобто. пара клієнтів за одним публічним IP не зможе одночасно підключитися до одного сервера в такий спосіб. В майбутньому, можливо, її вирішать лише на рівні ядра, але ці не точно. А поки проблеми NAT-а можна вирішити NAT-ом - якщо трапляється так, що пара IP адрес вже зайнята іншим тунелем, ipipou зробить NAT з публічного на альтернативний приватний IP, вуаля! — можна створювати тунелі доки порти не закінчаться.
Т.к. не всі пакети в з'єднанні підписані, то такий простий захист вразливий до MITM, так що якщо на шляху між клієнтом і сервером причаївся лиходій, який може слухати трафік і керувати ним, він може перенаправляти пакети з автентифікацією через іншу адресу і створити тунель з недовіреного хоста .
Якщо у кого є ідеї, як це виправити, залишаючи основну частину трафіку в ядрі, не соромтеся — висловлюйтеся.
Інкапсуляція в UDP дуже добре себе зарекомендувала. У порівнянні з інкапсуляцією поверх IP вона набагато стабільніша і частіше швидше, незважаючи на додаткові накладні витрати на заголовок UDP. Це з тим, що у Інтернеті більша частина хостів стерпно працює лише з трьома найпопулярнішими протоколами: TCP, UDP, ICMP. Відчутна частина може взагалі відкидати решту, або обробляти повільніше, бо оптимізована тільки під ці три.
Наприклад, тому QUICK, з урахуванням якого створено HTTP/3, створювався саме поверх UDP, а чи не поверх IP.
Ну та вистачить слів, настав час подивитися як це працює в «реальному світі».
Батл
Для емуляції реального світу використовується iperf3. За рівнем наближеності до реальності це приблизно як емуляція реального світу в Майнкрафті, але поки що зійде.
У змаганні беруть участь:
еталонний основний канал
герой цієї статті ipipou
OpenVPN з автентифікацією, але без шифрування
OpenVPN у режимі "все включено"
WireGuard без PresharedKey, з MTU=1440 (бо IPv4-only)
Технічні дані для гіків Метрики знімаються такими командами
на клієнті:
UDP
CPULOG=NAME.udp.cpu.log; sar 10 6 >"$CPULOG" & iperf3 -c SERVER_IP -4 -t 60 -f m -i 10 -B LOCAL_IP -P 2 -u -b 12M; tail -1 "$CPULOG"
# Где "-b 12M" это пропускная способность основного канала, делённая на число потоков "-P", чтобы лишние пакеты не плодить и не портить производительность.