ipipou: повече от просто некодирана тунелна връзка

Какво казваме на бога IPv6?

ipipou: повече от просто некодирана тунелна връзка
Верно, и на бога на шифроването днес ще му кажем същото.

Тук ще стане дума за нешифрован IPv4 тунел, но не за "топъл лампов", а за модерен "LED". И тук ще се появят сурови сокети, и ще се работи с пакети в потребителското пространство.

Има N протоколи за тунелиране за всеки вкус и цвят:

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

Но аз съм програмист, затова ще увелича N само с малко, а разработката на истински протоколи я оставям на Ъ-разработчиците.

В един още не родил се проект, с който в момента се занимавам, трябва да достигна до хостовете зад NAT отвън. Използвайки за целта протоколи с добра криптография, ме напускаше усещането, че това е като да стреляш по врабчета с пушката. Тъй като тунелът основно се използва за пробиване на дупка в NAT, вътрешният трафик обикновено също е шифрован, все пак отбелязват за HTTPS.

Изследвайки различни протоколи за тунелиране, вниманието на вътрешния ми перфекционист многократно привличаше IPIP заради минималните разходи. Но за моите нужди той има един и половина съществени недостатъка:

  • изисква публични IP адреси от двете страни,
  • и никаква аутентификация.

Затова перфекционистът се завръщаше обратно в тъмен ъгъл на черепната кутия, или там, където се крие.

И ето как веднъж, четейки статии за нативно поддържани тунели в Linux, се натъкнах на FOU (Foo-over-UDP), т.е. нещо, завито в UDP. В момента от нещата, поддържани, само IPIP и GUE (Generic UDP Encapsulation) са факт.

"Ето я сребърната куршум! И простият IPIP ми стига" — мислех аз.

На практика куршумът се оказа не напълно сребърен. Инкапсулацията в UDP решава първия проблем — клиентите зад NAT могат да се свържат отвън, използвайки предварително установена връзка, но тук половината от следващия недостатък на IPIP цъфти в нова светлина — зад видимите публични IP и порта на клиента може да се крие кого ли не от частната мрежа (в чистият IPIP този проблем не съществува).

За решаването на този полуабсолютен проблем и се роди утилитата ipipouВ него е реализиран самоделен механизъм за удостоверяване на отдалечен хост, без да се нарушава работата на ядрения FOU, който бързо и ефективно ще обработва пакетите в ядрото.

Не ти трябва сценарият!

Добре, ако знаеш публичния порт и IP адреса на клиента (например, след него не ходят произволно, NAT се опитва да картира портовете един към един), можеш да създадеш 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 — локалният интерфейс, с който ще бъде свързан 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"

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 задержка

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

wireguard
сървър
/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 сервера не является самым точным показателем, так как там работают много других сервисов и иногда они используют ресурсы:

прото пропускна способност[Mbps] CPU_idle_client[%] CPU_idle_server[%]
# 20 Mbps канал с микрокомпютър (4 ядра) до VPS (1 ядро) през Атлантика
# чист
UDP 20.4      99.80 93.34
TCP 19.2      99.67 96.68
ICMP латентност мин/ср/макс/мdev = 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 латентност мин/ср/макс/мdev = 199.562/208.919/220.222/7.905 ms
# openvpn0 (само автентикация, без криптиране)
UDP 19.3      99.89 72.90
TCP 16.1      95.95 88.46
ICMP латентност мин/ср/макс/мdev = 191.631/193.538/198.724/2.520 ms
# openvpn (пълно криптиране, автентикация и т.н.)
UDP 19.6      99.75 72.35
TCP 17.0      94.47 87.99
ICMP латентност мин/ср/макс/мdev = 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 латентност мин/ср/макс/мdev = 217.925/223.601/230.696/3.266 ms

## около-1Gbps канал между VPS Европа и САЩ (1 ядро)
# чист
UDP 729      73.40 39.93
TCP 363      96.95 90.40
ICMP латентност мин/ср/макс/мdev = 106.867/106.994/107.126/0.066 ms
# ipipou
UDP 714      63.10 23.53
TCP 431      95.65 64.56
ICMP латентност мин/ср/макс/мdev = 107.444/107.523/107.648/0.058 ms
# openvpn0 (само автентикация, без криптиране)
UDP 193      17.51  1.62
TCP  12      95.45 92.80
ICMP латентност мин/ср/макс/мdev = 107.191/107.334/107.559/0.116 ms
# wireguard
UDP 629      22.26  2.62
TCP 198      77.40 55.98
ICMP латентност мин/ср/макс/мdev = 107.616/107.788/108.038/0.128 ms

канал от 20 Mbps

ipipou: повече от просто некодирана тунелна връзка

ipipou: повече от просто некодирана тунелна връзка

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

ipipou: повече от просто некодирана тунелна връзка

ipipou: повече от просто некодирана тунелна връзка

Във всички случаи ipipou е доста близък по показатели до основния канал и това е прекрасно!

Нешифрованият тунел openvpn се държеше доста странно и в двата случая.

Ако някой реши да тества, ще бъде интересно да чуя отзивите.

Да пребъде с нас IPv6 и NetPrickle!

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster