ipipou: więcej niż tylko nieszyfrowany tunel

Co mówimy bogu IPv6?

ipipou: więcej niż tylko nieszyfrowany tunel
Prawda, a bogu szyfrowania dziś powiemy to samo.

Tutaj będzie mowa o nieszyfrowanym tunelu IPv4, ale nie o «ciepłym lampowym», a o nowoczesnym « LED-owym». A ponadto pojawiają się surowe gniazda, i trwa praca z pakietami w przestrzeni użytkownika.

Są N protokołów tunelowania na każdy gust i kolor:

  • stylowe, modne, młodzieżowe WireGuard
  • wielofunkcyjne, jak szwajcarskie noże, OpenVPN i SSH
  • stary i niegroźny GRE
  • maksymalnie prosty, szybki, zupełnie nieszyfrowany IPIP
  • aktywnie rozwijający się GENEVE
  • wiele innych.

Ale ja jestem programistą, więc zwiększę N jedynie o odrobinę, a rozwój prawdziwych protokołów zostawię Ъ-deweloperom.

W jednym jeszcze nienarodzonym projekcie, którym obecnie się zajmuję, muszę dotrzeć do hostów za NAT-em z zewnątrz. Używając do tego protokołów z dorosłą kryptografią, wciąż miałem wrażenie, że to jak strzelanie z armaty do wróbli. Ponieważ tunel używany jest głównie do przebicia dziury w NAT, wewnętrzny ruch również zazwyczaj jest szyfrowany, wciąż promują HTTPS.

Badając różne protokoły tunelowania, uwaga mojego wewnętrznego perfekcjonisty za każdym razem zwracała się ku IPIP z powodu jego minimalnych kosztów operacyjnych. Ale ma on półtora istotnego niedostatku dla moich zadań:

  • wymaga publicznych IP po obu stronach,
  • i żadnej autoryzacji.

Dlatego perfekcjonista wrócił w mroczny kąt czaszki, albo gdziekolwiek tam siedzi.

I oto pewnego razu czytając artykuły o natywnie wspieranych tunelach w Linuksie natknąłem się na FOU (Foo-over-UDP), tzn. coś przypadkowego, opakowanego w UDP. Jak na razie z czego przypadkowego wspierane są tylko IPIP i GUE (Generic UDP Encapsulation).

«Oto ona, srebrna kula! Mnie i prostego IPIP wystarczy.» — myślałem.

W rzeczywistości kula okazała się nie do końca srebrna. Inkluzja w UDP rozwiązuje pierwszy problem — do klientów za NAT-em można podłączyć się z zewnątrz, używając wcześniej nawiązanego połączenia, ale tutaj połowa następnego niedostatku IPIP rozkwita w nowym świetle — za widocznymi publicznymi IP i portem klienta może ukrywać się kto tylko chce z prywatnej sieci (w czystym IPIP tego problemu nie ma).

Aby rozwiązać ten półtorowy problem powstała utilita ipipouZrealizowano w niej własny mechanizm autoryzacji zdalnego hosta, jednocześnie nie zakłócając działania jądrowego FOU, który sprawnie i efektywnie przetwarza pakiety w przestrzeni jądra.

Twój skrypt nie jest potrzebny!

Ok, jeśli znasz publiczny port i IP klienta (na przykład za NAT maper, który próbuje mapować porty 1-do-1), możesz stworzyć tunel IPIP-over-FOU za pomocą poniższych poleceń, bez użycia jakichkolwiek 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

# Opcje local, peer, peer_port, dev mogą nie być obsługiwane przez starsze jądra, można je pominąć.
# peer i peer_port są używane do stworzenia połączenia natychmiast przy tworzeniu FOU-listernera.
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 tunelowego interfejsu sieciowego
  • 203.0.113.1 — publiczny IP serwera
  • 198.51.100.2 — publiczny IP klienta
  • 192.168.0.2 — 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 — opcja do dodania sumy kontrolnej UDP do enkapsulowanych pakietów UDP; można zastąpić noencap-csum, aby nie obliczać, integralność będzie kontrolowana przez zewnętrzną warstwę enkapsulacji (dopóki pakiet znajduje się wewnątrz tunelu)
  • eth0 — lokalny interfejs, do którego będzie przypisany tunel ipip
  • 172.28.0.1 — IP interfejsu tunelowego klienta (prywatny)
  • 172.28.0.0 — IP interfejsu tunelowego serwera (prywatny)

Dopóki UDP połączenie jest aktywne, tunel będzie działał, a gdy zostanie przerwane, to jak będzie — jeśli IP: port klienta pozostaną te same — będzie funkcjonować, zmienią się — przerwie.

Najprościej jest cofnąć wszystko, wyładowując moduły jądra: modprobe -r fou ipip

Nawet jeśli autoryzacja nie jest wymagana, publiczne IP i port klienta nie zawsze są znane i często są nieprzewidywalne lub zmienne (w zależności od rodzaju NAT). Jeśli pominiesz encap-dport na stronie serwera, tunel nie będzie działać, nie jest na tyle inteligentny, aby pobierać zdalny port połączenia. W takim przypadku ipipou również może pomóc, a może WireGuard i inne podobne narzędzia będą ci pomocne.

Jak to działa?

Klient (co zazwyczaj jest za NAT-em) tworzy tunel (jak w powyższym przykładzie) i wysyła pakiet z uwierzytelnieniem do serwera, aby ten skonfigurował tunel po swojej stronie. W zależności od ustawień pakiet ten może być pusty (po prostu, aby serwer zobaczył publiczne IP: port połączenia) lub zawierać dane, na podstawie których serwer zidentyfikuje klienta. Dane mogą być zwykłym hasłem w formie otwartego tekstu (przychodzi mi na myśl analogia z HTTP Basic Auth) lub podpisanymi danymi sformatowanymi w sposób szczególny z użyciem klucza prywatnego (analogicznie do HTTP Digest Auth, tylko bardziej zaawansowane, patrz funkcja client_auth w kodzie).

Na serwerze (strona z publicznym IP) podczas uruchamiania ipipou tworzy przetwornik kolejki nfqueue i konfiguruje netfilter tak, aby potrzebne pakiety były kierowane tam, gdzie należy: pakiety inicjujące połączenie do kolejki nfqueue, a [niemal] wszystkie pozostałe bezpośrednio do listenera FOU.

Dla osób niewtajemniczonych, nfqueue (lub NetfilterQueue) to specjalna funkcja dla amatorów, którzy nie potrafią rozwijać modułów jądra, która przy użyciu netfilter (nftables/iptables) umożliwia przekierowywanie pakietów sieciowych do przestrzeni użytkownika i przetwarzanie ich tam przy pomocy prostych narzędzi: modyfikowanie (opcjonalnie) i oddawanie ich z powrotem do jądra lub odrzucanie.

Dla niektórych języków programowania istnieją bindy do pracy z nfqueue, ale nie znalazłem ich dla bash (heh, nic dziwnego), musiałem użyć Pythona: ipipou korzysta z NetfilterQueue.

Jeśli wydajność nie jest krytyczna, przy użyciu tej funkcji można relatywnie szybko i łatwo stworzyć własną logikę działania z pakietami na dość niskim poziomie, na przykład opracowując eksperymentalne protokoły przesyłania danych lub trollując lokalne i zdalne usługi nietypowym zachowaniem.

Ręka w rękę z nfqueue działają surowe gniazda (raw sockets), na przykład gdy tunel jest już skonfigurowany, a FOU nasłuchuje na odpowiednim porcie, wysłanie pakietu z tego samego portu w zwykły sposób nie będzie możliwe — zajęte, ale można wziąć i wprowadzić dowolnie wygenerowany pakiet prosto do interfejsu sieciowego, używając surowego gniazda, choć generowanie takiego pakietu będzie wymagało nieco więcej wysiłku. Tak właśnie w ipipou tworzy się pakiety z uwierzytelnieniem.

Ponieważ ipipou przetwarza tylko pierwsze pakiety z połączenia (i te, które zdążyły się w kolejce przed nawiązaniem połączenia), wydajność prawie nie ucierpi.

Gdy serwer ipipou otrzyma pakiet, który przeszedł autoryzację, tunel zostaje utworzony i wszystkie dalsze pakiety w połączeniu są przetwarzane przez jądro, omijając nfqueue. Jeśli połączenie wygaśnie, pierwszy pakiet następnego połączenia zostanie skierowany do nfqueue, w zależności od ustawień. Jeśli nie jest to pakiet z autoryzacją, ale z ostatnio zapamiętanego IP i portu klienta, może zostać albo przepuszczony dalej, albo odrzucony. Jeśli autoryzowany pakiet przychodzi z nowych IP i portu, tunel zostaje dostosowany do ich użycia.

Zwykłe IPIP-over-FOU ma jeszcze jeden problem podczas pracy z NAT — nie można utworzyć dwóch tuneli IPIP enkapsulowanych w UDP z tymi samymi IP, ponieważ moduły FOU i IPIP są dość odseparowane od siebie. Oznacza to, że para klientów za jednym publicznym IP nie będzie mogła jednocześnie połączyć się z jednym serwerem w ten sposób. W przyszłości, być może, zostanie to rozwiązane na poziomie jądra, ale nie jest to pewne. A póki co, problemy z NAT można rozwiązać przy użyciu 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 IP, voilà! — można tworzyć tuneli, dopóki porty się nie skończą.

Ponieważ nie wszystkie pakiety w połączeniu są podpisane, taka prosta ochrona jest podatna na MITM, więc jeśli przy ścieżce między klientem a serwerem czai się złośliwiec, który może podsłuchiwać ruch i nim zarządzać, może on przekierowywać pakiety z autoryzacją przez inny adres i utworzyć tunel z niewiarygodnego hosta.

Jeśli ktoś ma pomysły, jak to naprawić, pozostawiając główną część ruchu w jądrze, nie krępować się — wyrażać swoje zdanie.

A jeśli chodzi o enkapsulację w UDP, bardzo dobrze się sprawdziła. W porównaniu do enkapsulacji nad IP, jest znacznie bardziej stabilna i często szybsza, mimo dodatkowych kosztów związanych z nagłówkiem UDP. Wynika to z faktu, że w Internecie większość hostów działa poprawnie tylko z trzema najbardziej popularnymi protokołami: TCP, UDP, ICMP. Znacząca część może w ogóle odrzucać wszystko inne lub przetwarzać wolniej, ponieważ została zoptymalizowana tylko pod te trzy.

Na przykład, dlatego QUICK, na którym oparty jest HTTP/3, został zbudowany na bazie UDP, a nie na IP.

Czas już przestać gadać, pora zobaczyć, jak to działa w "prawdziwym świecie".

Bitwa

Do symulacji prawdziwego świata używa się iperf3. Poziom zbliżenia do rzeczywistości przypomina symulację prawdziwego świata w Minecrafcie, ale na razie wystarczy.

W zawodach biorą udział:

  • referencyjny główny kanał
  • bohater tego artykułu ipipou
  • OpenVPN z autoryzacją, ale bez szyfrowania
  • OpenVPN w trybie "wszystko w jednym"
  • WireGuard bez PresharedKey, z MTU=1440 (bo IPv4 tylko)

Dane techniczne dla geeków
Metryki są zbierane za pomocą takich 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"
# Gdzie "-b 12M" to przepustowość głównego kanału podzielona przez liczbę strumieni "-P", aby nie generować zbędnych pakietów i nie obniżać wydajności.

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 (uruchamiane równocześ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 tuneli

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

客户端
/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 autoryzacją)
serwer

openvpn --genkey --secret ovpn.key  # Następnie trzeba przekazać ovpn.key klientowi
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 (z szyfrowaniem, autoryzacją, przez UDP, wszystko jak należy)
Skonfigurowano przy użyciu openvpn-manage

wireguard
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

客户端
/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

Surowa, brzydka tabela
Obciążenie CPU serwera nie jest zbyt miarodajne, ponieważ działa tam wiele innych usług, które czasami zużywają zasoby:

protokół pasma[Mbps] CPU_idle_client[%] CPU_idle_server[%]
# 20 Mbps kanał z mikrokomputera (4 rdzenie) do VPS (1 rdzeń) przez Atlantyk
# czysty
UDP 20.4      99.80 93.34
TCP 19.2      99.67 96.68
ICMP opóźnienie min/średnie/maks/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 opóźnienie min/średnie/maks/mdev = 199.562/208.919/220.222/7.905 ms
# openvpn0 (tylko autoryzacja, bez szyfrowania)
UDP 19.3      99.89 72.90
TCP 16.1      95.95 88.46
ICMP opóźnienie min/średnie/maks/mdev = 191.631/193.538/198.724/2.520 ms
# openvpn (pełne szyfrowanie, autoryzacja itp.)
UDP 19.6      99.75 72.35
TCP 17.0      94.47 87.99
ICMP opóźnienie min/średnie/maks/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 opóźnienie min/średnie/maks/mdev = 217.925/223.601/230.696/3.266 ms

## około-1Gbps kanał między VPS w Europie a USA (1 rdzeń)
# czysty
UDP 729      73.40 39.93
TCP 363      96.95 90.40
ICMP opóźnienie min/średnie/maks/mdev = 106.867/106.994/107.126/0.066 ms
# ipipou
UDP 714      63.10 23.53
TCP 431      95.65 64.56
ICMP opóźnienie min/średnie/maks/mdev = 107.444/107.523/107.648/0.058 ms
# openvpn0 (tylko autoryzacja, bez szyfrowania)
UDP 193      17.51  1.62
TCP  12      95.45 92.80
ICMP opóźnienie min/średnie/maks/mdev = 107.191/107.334/107.559/0.116 ms
# wireguard
UDP 629      22.26  2.62
TCP 198      77.40 55.98
ICMP opóźnienie min/średnie/maks/mdev = 107.616/107.788/108.038/0.128 ms

kanał 20 Mbps

ipipou: więcej niż tylko nieszyfrowany tunel

ipipou: więcej niż tylko nieszyfrowany tunel

kanał 1 optymistyczny Gbps

ipipou: więcej niż tylko nieszyfrowany tunel

ipipou: więcej niż tylko nieszyfrowany tunel

We wszystkich przypadkach ipipou jest dość bliski wartościom podstawowego kanału, co jest doskonałe!

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

Jeśli ktoś zdecyduje się przetestować, chętnie usłyszymy opinie.

Niech z nami będzie IPv6 i NetPrickle!

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster