ipipou: більше ніж просто нешифрований тунель

Що ми говоримо Богові IPv6?

ipipou: більше ніж просто нешифрований тунель
Правильно, і богу шифрування сьогодні скажемо те саме.

Тут буде про нешифрований IPv4 тунель, але не про «теплий ламповий», а про модерновий «світлодіодний». А ще тут з'являються сирі сокети, і йде робота з пакетами в просторі користувача.

Є N протоколів тунелювання на будь-який смак та колір:

  • стильний, модний, молодіжний WireGuard
  • мультифункціональні, як швейцарські ножі, OpenVPN та SSH
  • старий і не злий GRE
  • максимально простий, спритний, зовсім не шифрований IPIP
  • активно розвивається ЖЕНЕВА
  • безліч інших.

Але яжпрограміст, тому збільшу 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", чтобы лишние пакеты не плодить и не портить производительность.

TCP

CPULOG=NAME.tcp.cpu.log; sar 10 6 >"$CPULOG" & iperf3 -c SERVER_IP -4 -t 60 -f m -i 10 -B LOCAL_IP -P 2; tail -1 "$CPULOG"

ICMP latency

ping -c 10 SERVER_IP | tail -1

на сервері (запускається одночасно з клієнтом):

UDP

CPULOG=NAME.udp.cpu.log; sar 10 6 >"$CPULOG" & iperf3 -s -i 10 -f m -1; tail -1 "$CPULOG"

TCP

CPULOG=NAME.tcp.cpu.log; sar 10 6 >"$CPULOG" & iperf3 -s -i 10 -f m -1; tail -1 "$CPULOG"

Конфігурація тунелів

ipipou
сервер
/etc/ipipou/server.conf:

server
number 0
fou-dev eth0
fou-local-port 10000
tunl-ip 172.28.0.0
auth-remote-pubkey-b64 eQYNhD/Xwl6Zaq+z3QXDzNI77x8CEKqY1n5kt9bKeEI=
auth-secret topsecret
auth-lifetime 3600
reply-on-auth-ok
verb 3

systemctl start ipipou@server

клієнт
/etc/ipipou/client.conf:

client
number 0
fou-local @eth0
fou-remote SERVER_IP:10000
tunl-ip 172.28.0.1
# pubkey of auth-key-b64: eQYNhD/Xwl6Zaq+z3QXDzNI77x8CEKqY1n5kt9bKeEI=
auth-key-b64 RuBZkT23na2Q4QH1xfmZCfRgSgPt5s362UPAFbecTso=
auth-secret topsecret
keepalive 27
verb 3

systemctl start ipipou@client

openvpn (без шифрування, з автентифікацією)
сервер

openvpn --genkey --secret ovpn.key  # Затем надо передать ovpn.key клиенту
openvpn --dev tun1 --local SERVER_IP --port 2000 --ifconfig 172.16.17.1 172.16.17.2 --cipher none --auth SHA1 --ncp-disable --secret ovpn.key

клієнт

openvpn --dev tun1 --local LOCAL_IP --remote SERVER_IP --port 2000 --ifconfig 172.16.17.2 172.16.17.1 --cipher none --auth SHA1 --ncp-disable --secret ovpn.key

openvpn (з шифруванням, автентифікацією, через UDP, все як належить)
Настроєно використовуючи openvpn-manage

дротяник
сервер
/etc/wireguard/server.conf:

[Interface]
Address=172.31.192.1/18
ListenPort=51820
PrivateKey=aMAG31yjt85zsVC5hn5jMskuFdF8C/LFSRYnhRGSKUQ=
MTU=1440

[Peer]
PublicKey=LyhhEIjVQPVmr/sJNdSRqTjxibsfDZ15sDuhvAQ3hVM=
AllowedIPs=172.31.192.2/32

systemctl start wg-quick@server

клієнт
/etc/wireguard/client.conf:

[Interface]
Address=172.31.192.2/18
PrivateKey=uCluH7q2Hip5lLRSsVHc38nGKUGpZIUwGO/7k+6Ye3I=
MTU=1440

[Peer]
PublicKey=DjJRmGvhl6DWuSf1fldxNRBvqa701c0Sc7OpRr4gPXk=
AllowedIPs=172.31.192.1/32
Endpoint=SERVER_IP:51820

systemctl start wg-quick@client

Результати

Сира страшна табличка
Завантаження CPU сервера дуже показова, т.к. там крутиться багато інших сервісів, іноді вони жеруть ресурси:

proto bandwidth[Mbps] CPU_idle_client[%] CPU_idle_server[%]
# 20 Mbps канал с микрокомпьютера (4 core) до VPS (1 core) через Атлантику
# pure
UDP 20.4      99.80 93.34
TCP 19.2      99.67 96.68
ICMP latency min/avg/max/mdev = 198.838/198.997/199.360/0.372 ms
# ipipou
UDP 19.8      98.45 99.47
TCP 18.8      99.56 96.75
ICMP latency min/avg/max/mdev = 199.562/208.919/220.222/7.905 ms
# openvpn0 (auth only, no encryption)
UDP 19.3      99.89 72.90
TCP 16.1      95.95 88.46
ICMP latency min/avg/max/mdev = 191.631/193.538/198.724/2.520 ms
# openvpn (full encryption, auth, etc)
UDP 19.6      99.75 72.35
TCP 17.0      94.47 87.99
ICMP latency min/avg/max/mdev = 202.168/202.377/202.900/0.451 ms
# wireguard
UDP 19.3      91.60 94.78
TCP 17.2      96.76 92.87
ICMP latency min/avg/max/mdev = 217.925/223.601/230.696/3.266 ms

## около-1Gbps канал между VPS Европы и США (1 core)
# pure
UDP 729      73.40 39.93
TCP 363      96.95 90.40
ICMP latency min/avg/max/mdev = 106.867/106.994/107.126/0.066 ms
# ipipou
UDP 714      63.10 23.53
TCP 431      95.65 64.56
ICMP latency min/avg/max/mdev = 107.444/107.523/107.648/0.058 ms
# openvpn0 (auth only, no encryption)
UDP 193      17.51  1.62
TCP  12      95.45 92.80
ICMP latency min/avg/max/mdev = 107.191/107.334/107.559/0.116 ms
# wireguard
UDP 629      22.26  2.62
TCP 198      77.40 55.98
ICMP latency min/avg/max/mdev = 107.616/107.788/108.038/0.128 ms

канал на 20 Mbps

ipipou: більше ніж просто нешифрований тунель

ipipou: більше ніж просто нешифрований тунель

канал на 1 оптимістичний Gbps

ipipou: більше ніж просто нешифрований тунель

ipipou: більше ніж просто нешифрований тунель

У всіх випадках ipipou досить близький за показниками до базового каналу, і це чудово!

Нешифрований тунель openvpn поводився досить дивно в обох випадках.

Якщо хтось збереться потестити, буде цікаво почути відгуки.

Хай буде з нами IPv6 і NetPrickle!

Джерело: habr.com

Додати коментар або відгук