Русский
Русский
English
Статистика
Реклама

Ipipou больше чем просто нешифрованный туннель

Что мы говорим богу IPv6?

IPv6? Not today

Верно, и богу шифрования сегодня скажем то же.

Здесь будет о нешифрованном IPv4 туннеле, но не о тёплом ламповом, а о модерновом светодиодном. А ещё тут мелькают сырые сокеты, и идёт работа с пакетами в пространстве пользователя.

Есть N протоколов туннелирования на любой вкус и цвет:

  • стильный, модный, молодёжный WireGuard
  • мультифункциональные, как швейцарские ножи, OpenVPN и SSH
  • старый и не злой GRE
  • максимально простой, шустрый, совсем не шифрованный IPIP
  • активно развивающийся GENEVE
  • множество других.

Но яжпрограммист, поэтому увеличу 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 туннель следующими командами, без всяких скриптов.

на сервере:
# Подгрузить модуль ядра FOUmodprobe 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 fouip 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 eth0ip address add 172.28.0.1 peer 172.28.0.0 dev ipipou1ip 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 локальный интерфейс к которому будет привязан ipip туннель
  • 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:
servernumber 0fou-dev eth0fou-local-port 10000tunl-ip 172.28.0.0auth-remote-pubkey-b64 eQYNhD/Xwl6Zaq+z3QXDzNI77x8CEKqY1n5kt9bKeEI=auth-secret topsecretauth-lifetime 3600reply-on-auth-okverb 3

systemctl start ipipou@server

клиент
/etc/ipipou/client.conf:
clientnumber 0fou-local @eth0fou-remote SERVER_IP:10000tunl-ip 172.28.0.1# pubkey of auth-key-b64: eQYNhD/Xwl6Zaq+z3QXDzNI77x8CEKqY1n5kt9bKeEI=auth-key-b64 RuBZkT23na2Q4QH1xfmZCfRgSgPt5s362UPAFbecTso=auth-secret topsecretkeepalive 27verb 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 (c шифрованием, аутентификацией, через UDP, всё как положено)
Настроено используя openvpn-manage

wireguard
сервер
/etc/wireguard/server.conf:
[Interface]Address=172.31.192.1/18ListenPort=51820PrivateKey=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/18PrivateKey=uCluH7q2Hip5lLRSsVHc38nGKUGpZIUwGO/7k+6Ye3I=MTU=1440[Peer]PublicKey=DjJRmGvhl6DWuSf1fldxNRBvqa701c0Sc7OpRr4gPXk=AllowedIPs=172.31.192.1/32Endpoint=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) через Атлантику# pureUDP 20.4      99.80 93.34TCP 19.2      99.67 96.68ICMP latency min/avg/max/mdev = 198.838/198.997/199.360/0.372 ms# ipipouUDP 19.8      98.45 99.47TCP 18.8      99.56 96.75ICMP latency min/avg/max/mdev = 199.562/208.919/220.222/7.905 ms# openvpn (auth only, no encryption)UDP 19.3      99.89 72.90TCP 16.1      95.95 88.46ICMP latency min/avg/max/mdev = 191.631/193.538/198.724/2.520 ms# openvpn (auth only, no encryption)UDP 19.6      99.75 72.35TCP 17.0      94.47 87.99ICMP latency min/avg/max/mdev = 202.168/202.377/202.900/0.451 ms# wireguardUDP 19.3      91.60 94.78TCP 17.2      96.76 92.87ICMP latency min/avg/max/mdev = 217.925/223.601/230.696/3.266 ms## около-1Gbps канал между VPS Европы и США (1 core)# pureUDP 729      73.40 39.93TCP 363      96.95 90.40ICMP latency min/avg/max/mdev = 106.867/106.994/107.126/0.066 ms# ipipouUDP 714      63.10 23.53TCP 431      95.65 64.56ICMP latency min/avg/max/mdev = 107.444/107.523/107.648/0.058 ms# openvpn (auth only, no encryption)UDP 193      17.51  1.62TCP  12      95.45 92.80ICMP latency min/avg/max/mdev = 107.191/107.334/107.559/0.116 ms# wireguardUDP 629      22.26  2.62TCP 198      77.40 55.98ICMP latency min/avg/max/mdev = 107.616/107.788/108.038/0.128 ms


канал на 20 Mbps

сравнение пропускной способности на 20 Mbps

сравнение задержки на 20 Mbps

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

сравнение пропускной способности на 1 Gbps

сравнение эффективности использования CPU: Mbps/CPU_usage

Во всех случаях ipipou довольно близок по показателям к базовому каналу, и это прекрасно!

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

Если кто соберётся потестить, будет интересно услышать отзывы.

Да пребудет с нами IPv6 и NetPrickle!
Источник: habr.com
К списку статей
Опубликовано: 07.10.2020 18:18:13
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

*nix

Open source

Python

Настройка linux

Сетевые технологии

Ipip

Fou

Nfqueue

Linux

Туннелирование

Аутентификация

Сетевое программирование

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru