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 - лакальны інтэрфейс да якога будзе прывязаны 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:

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

Дадаць каментар