ipipou: więcej niż tylko niezaszyfrowany tunel

Co mówimy Bogu IPv6?

ipipou: więcej niż tylko niezaszyfrowany tunel
Zgadza się, dzisiaj powiemy to samo bogu szyfrowania.

Tutaj będziemy mówić o niezaszyfrowanym tunelu IPv4, ale nie o tunelu z „ciepłą lampą”, ale o nowoczesnym tunelu „LED”. I tu też flashują surowe gniazda, i trwają prace z pakietami w przestrzeni użytkownika.

Istnieje N protokołów tunelowania dla każdego smaku i koloru:

  • stylowy, modny, młodzieżowy WireGuard
  • wielofunkcyjne, takie jak noże szwajcarskie, OpenVPN i SSH
  • stary i niezły GRE
  • najprostszy, najszybszy, całkowicie niezaszyfrowany protokół IPIP
  • aktywnie się rozwija GENEVE
  • wiele innych.

Ale jestem programistą, więc zwiększę N tylko o ułamek, a rozwój prawdziwych protokołów pozostawię programistom Kommersant.

W jednym nienarodzonym projektTeraz zajmuję się docieraniem z zewnątrz do hostów korzystających z NAT. Wykorzystując do tego protokoły z kryptografią dla dorosłych, nie mogłem oprzeć się wrażeniu, że to było jak strzelanie do wróbli z armaty. Ponieważ tunel jest używany w większości tylko do robienia dziur w NAT-e, ruch wewnętrzny jest zwykle również szyfrowany, ale nadal tonie w HTTPS.

Badając różne protokoły tunelowania, uwagę mojego wewnętrznego perfekcjonisty raz po raz zwracano na protokół IPIP ze względu na jego minimalny narzut. Ale ma półtora istotne wady dla moich zadań:

  • wymaga publicznych adresów IP po obu stronach,
  • i nie ma żadnego uwierzytelnienia dla Ciebie.

Dlatego perfekcjonista został zepchnięty z powrotem w ciemny kąt czaszki lub gdziekolwiek tam siedzi.

A potem pewnego dnia, czytając artykuły na temat natywnie obsługiwane tunele w Linuksie natknąłem się na FOU (Foo-over-UDP), czyli tzw. cokolwiek, zapakowane w UDP. Jak dotąd obsługiwane są tylko protokoły IPIP i GUE (Generic UDP Encapsulation).

„Oto złoty środek! Mi wystarczy prosty IPIP.” - Myślałem.

W rzeczywistości kula okazała się nie całkowicie srebrna. Enkapsulacja w UDP rozwiązuje pierwszy problem - z klientami za NATem można się łączyć z zewnątrz za pomocą wcześniej ustalonego połączenia, ale tutaj połowa kolejnej wady IPIP kwitnie w nowym świetle - każdy z sieci prywatnej może ukryć się za widzialnym publiczny adres IP i port klienta (w czystym IPIP ten problem nie występuje).

Aby rozwiązać ten półtora problemu, narodziło się narzędzie ipipou. Implementuje domowy mechanizm uwierzytelniania zdalnego hosta, bez zakłócania pracy jądra FOU, który szybko i sprawnie będzie przetwarzał pakiety w przestrzeni jądra.

Nie potrzebujemy Twojego scenariusza!

Ok, jeśli znasz publiczny port i adres IP klienta (na przykład wszyscy za nim nigdzie nie idą, NAT próbuje mapować porty 1 w 1), możesz utworzyć tunel IPIP-over-FOU za pomocą następujące polecenia, bez żadnych skryptów.

na serwerze:

# Подгрузить модуль ядра 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

na kliencie:

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

gdzie

  • ipipou* — nazwa lokalnego interfejsu sieciowego tunelu
  • 203.0.113.1 — publiczny serwer IP
  • 198.51.100.2 — publiczny adres IP klienta
  • 192.168.0.2 — adres IP klienta przypisany do interfejsu eth0
  • 10001 — lokalny port klienta dla FOU
  • 20001 — publiczny port klienta dla FOU
  • 10000 — publiczny port serwera dla FOU
  • encap-csum — możliwość dodania sumy kontrolnej UDP do kapsułkowanych pakietów UDP; można zastąpić noencap-csumnie wspominając, że integralność jest już kontrolowana przez zewnętrzną warstwę enkapsulacji (kiedy pakiet znajduje się wewnątrz tunelu)
  • eth0 — interfejs lokalny, z którym będzie powiązany tunel ipip
  • 172.28.0.1 — IP interfejsu tunelu klienta (prywatny)
  • 172.28.0.0 — Interfejs serwera tunelu IP (prywatny)

Dopóki połączenie UDP będzie aktywne, tunel będzie działał, ale jeśli się zepsuje, będziesz miał szczęście - jeśli adres IP klienta: port pozostanie taki sam - będzie działał, jeśli się zmienią - zepsuje się.

Najprostszym sposobem, aby wszystko odwrócić, jest wyładowanie modułów jądra: modprobe -r fou ipip

Nawet jeśli uwierzytelnianie nie jest wymagane, publiczny adres IP i port klienta nie zawsze są znane i często są nieprzewidywalne lub zmienne (w zależności od typu NAT). Jeśli pominiesz encap-dport po stronie serwera tunel nie będzie działał, nie jest wystarczająco inteligentny, aby zająć port połączenia zdalnego. W tym przypadku ipipou może również pomóc lub WireGuard i inne podobne mogą Ci pomóc.

Jak to działa?

Klient (zwykle znajdujący się za NATem) otwiera tunel (jak w powyższym przykładzie) i wysyła pakiet uwierzytelniający do serwera, aby ten skonfigurował tunel po swojej stronie. W zależności od ustawień może to być pusty pakiet (tylko po to, aby serwer mógł zobaczyć publiczny adres IP: port połączenia) lub z danymi, dzięki którym serwer może zidentyfikować klienta. Dane mogą mieć postać prostego hasła zapisanego w postaci zwykłego tekstu (przychodzi na myśl analogia z HTTP Basic Auth) lub specjalnie zaprojektowanych danych podpisanych kluczem prywatnym (podobnie jak HTTP Digest Auth tylko mocniej, patrz funkcja client_auth w kodzie).

Na serwerze (strona z publicznym adresem IP) po uruchomieniu ipipou tworzy procedurę obsługi kolejki nfqueue i konfiguruje netfilter tak, aby niezbędne pakiety były wysyłane tam, gdzie powinny być: pakiety inicjujące połączenie z kolejką nfqueue i [prawie] cała reszta trafia bezpośrednio do FOU słuchacza.

Dla niewtajemniczonych, nfqueue (lub NetfilterQueue) to specjalna rzecz dla amatorów, którzy nie wiedzą, jak tworzyć moduły jądra, które za pomocą netfilter (nftables/iptables) pozwalają przekierowywać pakiety sieciowe do przestrzeni użytkownika i tam je przetwarzać za pomocą prymitywne środki pod ręką: zmodyfikuj (opcjonalnie) i oddaj je jądru lub odrzuć.

Dla niektórych języków programowania istnieją powiązania do pracy z nfqueue, dla basha nie było żadnych (heh, nic dziwnego), musiałem użyć Pythona: ipipou używa Kolejka Netfilter.

Jeśli wydajność nie jest krytyczna, za pomocą tej rzeczy możesz stosunkowo szybko i łatwo wymyślić własną logikę pracy z pakietami na dość niskim poziomie, na przykład stworzyć eksperymentalne protokoły przesyłania danych lub trollować lokalne i zdalne usługi za pomocą niestandardowego zachowania.

Surowe gniazda współpracują z nfqueue, np. gdy tunel jest już skonfigurowany i FOU nasłuchuje na żądanym porcie, nie będziesz mógł w zwykły sposób wysłać pakietu z tego samego portu - jest zajęty, ale możesz pobrać i wysłać losowo wygenerowany pakiet bezpośrednio do interfejsu sieciowego za pomocą surowego gniazda, chociaż wygenerowanie takiego pakietu będzie wymagało nieco więcej majsterkowania. Tak powstają pakiety z uwierzytelnianiem w ipipou.

Ponieważ ipipou przetwarza tylko pierwsze pakiety z połączenia (oraz te, którym udało się przedostać do kolejki przed nawiązaniem połączenia), wydajność prawie nie spada.

Gdy tylko serwer ipipou odbierze uwierzytelniony pakiet, tworzony jest tunel i wszystkie kolejne pakiety w połączeniu są już przetwarzane przez jądro z pominięciem nfqueue. Jeżeli połączenie się nie powiedzie, to do kolejki nfqueue zostanie wysłany pierwszy pakiet następnego, w zależności od ustawień, jeśli nie jest to pakiet z uwierzytelnianiem, ale z ostatniego zapamiętanego adresu IP i portu klienta, można go albo przekazać włączone lub wyrzucone. Jeśli uwierzytelniony pakiet pochodzi z nowego adresu IP i nowego portu, tunel jest ponownie konfigurowany tak, aby z nich korzystał.

Zwykły IPIP-over-FOU ma jeszcze jeden problem podczas pracy z NAT - nie da się utworzyć dwóch tuneli IPIP enkapsulowanych w UDP z tym samym IP, ponieważ moduły FOU i IPIP są od siebie dość odizolowane. Te. para klientów korzystających z tego samego publicznego adresu IP nie będzie mogła w ten sposób jednocześnie połączyć się z tym samym serwerem. W przyszłości, być może, zostanie to rozwiązane na poziomie jądra, ale nie jest to pewne. W międzyczasie problemy z NAT można rozwiązać za pomocą NAT - jeśli zdarzy się, że para adresów IP jest już zajęta przez inny tunel, ipipou wykona NAT z publicznego na alternatywny prywatny adres IP, voila! - możesz tworzyć tunele aż do wyczerpania się portów.

Ponieważ Nie wszystkie pakiety w połączeniu są podpisane, wówczas ta prosta ochrona jest podatna na MITM, więc jeśli na ścieżce między klientem a serwerem czai się złoczyńca, który może podsłuchiwać ruch i nim manipulować, może przekierować uwierzytelnione pakiety przez inny adres i utwórz tunel z niezaufanego hosta.

Jeśli ktoś ma pomysł, jak rozwiązać ten problem, pozostawiając większość ruchu w rdzeniu, nie wahaj się zabrać głosu.

Nawiasem mówiąc, enkapsulacja w UDP sprawdziła się bardzo dobrze. W porównaniu z enkapsulacją przez IP jest ona znacznie stabilniejsza i często szybsza pomimo dodatkowego obciążenia nagłówka UDP. Wynika to z faktu, że większość hostów w Internecie działa dobrze tylko z trzema najpopularniejszymi protokołami: TCP, UDP, ICMP. Część materialna może całkowicie odrzucić wszystko inne lub przetwarzać ją wolniej, ponieważ jest zoptymalizowana tylko pod kątem tych trzech.

Na przykład dlatego QUICK, na którym opiera się HTTP/3, został stworzony na bazie UDP, a nie na IP.

No cóż, dość słów, czas zobaczyć, jak to działa w „realu”.

Bitwa

Używany do emulacji prawdziwego świata iperf3. Pod względem stopnia zbliżenia do rzeczywistości jest to mniej więcej tyle samo, co emulowanie prawdziwego świata w Minecrafcie, ale na razie wystarczy.

Uczestnicy konkursu:

  • referencyjny główny kanał
  • bohaterem tego artykułu jest ipipou
  • OpenVPN z uwierzytelnianiem, ale bez szyfrowania
  • OpenVPN w trybie all-inclusive
  • WireGuard bez PresharedKey, z MTU = 1440 (tylko dla IPv4)

Dane techniczne dla maniaków
Metryki są pobierane za pomocą następujących poleceń:

na kliencie:

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"

Opóźnienie ICMP

ping -c 10 SERVER_IP | tail -1

na serwerze (działa jednocześnie z klientem):

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"

Konfiguracja tunelu

ipipou
Serwer
/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

klient
/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 (bez szyfrowania, z uwierzytelnianiem)
Serwer

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

klient

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 (z szyfrowaniem, uwierzytelnianiem, przez UDP, wszystko zgodnie z oczekiwaniami)
Skonfigurowany za pomocą zarządzanie openvpn

osłona drutu
Serwer
/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

klient
/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

wyniki

Wilgotny, brzydki znak
Obciążenie procesora serwera nie jest zbyt orientacyjne, ponieważ... Działa tam wiele innych usług, czasami pochłaniają zasoby:

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

Kanał 20Mbps

ipipou: więcej niż tylko niezaszyfrowany tunel

ipipou: więcej niż tylko niezaszyfrowany tunel

kanał na 1 optymistyczny Gbps

ipipou: więcej niż tylko niezaszyfrowany tunel

ipipou: więcej niż tylko niezaszyfrowany tunel

We wszystkich przypadkach ipipou ma wydajność zbliżoną do kanału bazowego, co jest świetne!

Nieszyfrowany tunel openvpn zachowywał się dość dziwnie w obu przypadkach.

Jeśli ktoś będzie to testował, ciekawe będzie poznanie opinii.

Niech IPv6 i NetPrickle będą z nami!

Źródło: www.habr.com

Dodaj komentarz