ipipou: mehr als nur ein unverschlüsselter Tunnel

Was sagen wir Gott IPv6?

ipipou: mehr als nur ein unverschlüsselter Tunnel
Richtig, und Gott der Verschlüsselung werden wir heute dasselbe sagen.

Hier wird es um das unverschlüsselte IPv4-Tunnel gehen, aber nicht um das "warme, gemütliche", sondern um das moderne "LED". Außerdem gibt es hier rohe Sockets, und es wird an Paketen im Benutzerspeicher gearbeitet.

Es gibt N Tunnelprotokolle für jeden Geschmack:

  • stylisch, modisch, jugendlich WireGuard
  • multifunktional, wie Schweizer Taschenmesser, OpenVPN und SSH
  • alt und nicht böse GRE
  • maximal einfach, schnell, völlig unverschlüsselt IPIP
  • aktiv in der Entwicklung GENEVE
  • viele andere.

Aber ich bin ja Programmierer, also werde ich N nur um einen kleinen Teil erhöhen, während ich die Entwicklung echter Protokolle den Ъ-Entwicklern überlasse.

In einem noch nicht geborenen dem ProjektProjekt, an dem ich gerade arbeite, muss ich von außen auf Hosts hinter NAT zugreifen. Dabei benutze ich Protokolle mit erwachsener Kryptographie; ich hatte jedoch ständig das Gefühl, es wäre wie mit einer Kanone auf Spatzen zu schießen. Da der Tunnel größtenteils nur für das Durchbohren eines Lochs im NAT verwendet wird, ist der interne Verkehr normalerweise auch verschlüsselt, da alle für HTTPS sind.

Bei der Untersuchung verschiedener Tunnelprotokolle zog IPIP aufgrund seiner minimalen Overheadkosten die Aufmerksamkeit meines inneren Perfektionisten immer wieder auf sich. Aber es hat anderthalb wesentliche Nachteile für meine Aufgaben:

  • es erfordert öffentliche IPs auf beiden Seiten,
  • und keine Authentifizierung.

Deshalb hat sich der Perfektionist wieder in die dunkle Ecke des Schädels zurückgezogen, oder wo auch immer er sitzt.

Und eines Tages, beim Lesen von Artikeln über nativ unterstützte Tunnel in Linux stieß ich auf FOU (Foo-over-UDP), d.h. irgendein Kram, verpackt in UDP. Bislang werden von diesem Kram nur IPIP und GUE (Generic UDP Encapsulation) unterstützt.

"Hier ist die silberne Kugel! Mir reicht ein einfaches IPIP vollkommen." — dachte ich.

In der Tat stellte sich die Kugel als nicht ganz silbern heraus. Die Kapselung in UDP löst das erste Problem - man kann von außen eine Verbindung zu Clients hinter NAT herstellen, indem man eine zuvor eingerichtete Verbindung nutzt, aber hier blüht die Hälfte des nächsten Nachteils von IPIP in neuem Licht auf - hinter der sichtbaren öffentlichen IP und dem Port des Clients kann sich jemand aus dem privaten Netzwerk verstecken (dieses Problem gibt es im reinen IPIP nicht).

Zur Lösung dieses anderthalb Problems wurde das Dienstprogramm ipipou entwickelt.Es implementiert einen selbstgebauten Mechanismus zur Authentifizierung des Remote-Hosts, ohne die Funktionsweise des robusten FOU zu stören, der schnell und effizient Pakete im Kernelraum verarbeitet.

Dein Skript wird nicht benötigt!

Okay, wenn dir der öffentliche Port und die IP des Clients bekannt sind (zum Beispiel, weil sie sich nicht überall hin bewegen, NAT versucht, Ports 1-zu-1 zu mappen), kannst du einen IPIP-over-FOU-Tunnel mit den folgenden Befehlen ohne irgendwelche Skripte erstellen.

auf dem Server:

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

auf dem Client:

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

# Die Optionen local, peer, peer_port, dev werden möglicherweise von älteren Kernen nicht unterstützt, sie können weggelassen werden.
# peer und peer_port werden verwendet, um eine Verbindung sofort beim Erstellen des FOU-Listeners herzustellen.
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

wo

  • ipipou* — Name des lokalen Tunnel-Netzwerkschnittstelle
  • 203.0.113.1 — Öffentliche IP des Servers
  • 198.51.100.2 — Öffentliche IP des Clients
  • 192.168.0.2 — Client-IP, die der Schnittstelle eth0 zugewiesen ist
  • 10001 — Lokaler Port des Clients für FOU
  • 20001 — Öffentlicher Port des Clients für FOU
  • 10000 — Öffentlicher Port des Servers für FOU
  • encap-csum — Option zum Hinzufügen einer Prüfziffer für UDP in die gekapselten UDP-Pakete; kann ersetzt werden durch noencap-csum, um die Integrität nicht zu prüfen, da diese durch die äußere Schicht der Kapselung gewährleistet wird (solange das Paket im Tunnel ist)
  • eth0 — Lokale Schnittstelle, an die der ipip-Tunnel gebunden wird
  • 172.28.0.1 — IP-Adresse der Tunnel-Schnittstelle des Clients (privat)
  • 172.28.0.0 — IP-Adresse der Tunnel-Schnittstelle des Servers (privat)

Solange die UDP-Verbindung aktiv ist, bleibt der Tunnel funktionsfähig, und wenn sie unterbrochen wird, ist es wie es ist – wenn die IP: der Port des Clients gleich bleibt – wird er weiter bestehen, ändert sich etwas – wird es unterbrochen.

Es ist am einfachsten, alles zurückzugeben, indem man die Kernelmodule entlädt: modprobe -r fou ipip

Selbst wenn keine Authentifizierung erforderlich ist, sind öffentliche IPs und Ports des Clients nicht immer bekannt und oft unvorhersehbar oder wechselhaft (abhängig von der Art des NAT). Wenn du weglässt encap-dport auf der Serverseite, wird der Tunnel nicht funktionieren, er ist nicht so intelligent, dass er den entfernten Verbindungsport nimmt. In diesem Fall kann ipipou ebenfalls hilfreich sein, oder WireGuard und Konsorten helfen dir dabei.

Wie funktioniert das?

Der Client (was normalerweise hinter einem NAT steht) hebt einen Tunnel (wie im obigen Beispiel) an und sendet ein Authentifizierungspaket an den Server, damit dieser den Tunnel seinerseits einrichtet. Je nach Konfiguration kann dies ein leeres Paket sein (einfach damit der Server die öffentliche IP: den Verbindungsport sieht), oder es enthält Daten, anhand derer der Server den Client identifizieren kann. Die Daten können ein einfaches Passwort im Klartext sein (ich denke an die Analogie zur HTTP Basic Auth) oder speziell formatierte, mit einem privaten Schlüssel signierte Daten (ähnlich wie bei der HTTP Digest Auth, aber robuster, siehe Funktion client_auth im Code).

Auf dem Server (die Seite mit der öffentlichen IP) erstellt ipipou beim Start einen Verarbeitungshandler für die Warteschlange nfqueue und konfiguriert netfilter so, dass die erforderlichen Pakete dorthin geleitet werden: die Pakete, die die Verbindung initiieren, in die nfqueue-Warteschlange und [fast] alle anderen direkt zum FOU-Listener.

Für die, die nicht im Thema sind: nfqueue (oder NetfilterQueue) ist eine spezielle Funktion für Laien, die keine Kernelmodule entwickeln können, und es ermöglicht mithilfe von netfilter (nftables/iptables), Netzwerkpakete in den Benutzermodus umzuleiten und dort mit einfachen Mitteln zu verarbeiten: sie optional zu modifizieren und ans Kernel zurückzugeben oder sie zu verwerfen.

Für einige Programmiersprachen gibt es Bindings zur Arbeit mit nfqueue, für Bash gab es nichts (heh, nicht überraschend), also musste Python verwendet werden: ipipou verwendet NetfilterQueue.

Wenn die Leistung nicht kritisch ist, kann man mit diesem Tool relativ schnell und einfach eine eigene Logik zur Verarbeitung von Paketen auf einem recht niedrigen Niveau erstellen, beispielsweise experimentelle Datenübertragungsprotokolle entwickeln oder lokale und entfernte Dienste mit unkonventionellem Verhalten trollen.

Hand in Hand mit nfqueue arbeiten rohe Sockets (raw sockets), zum Beispiel wenn der Tunnel bereits eingerichtet ist und FOU auf dem benötigten Port lauscht, kann man auf herkömmliche Weise kein Paket von diesem Port senden — er ist belegt, aber man kann ein beliebig generiertes Paket direkt in die Netzwerkschnittstelle mit einem Rohsocket schicken, obwohl es etwas mehr Aufwand erfordert, ein solches Paket zu generieren. So entstehen in ipipou Authentifizierungspakete.

Da die ipipou nur die ersten Pakete einer Verbindung verarbeitet (sowie die, die vor dem Verbindungsaufbau in die Warteschlange gerutscht sind), leidet die Leistung kaum.

Sobald der ipipou-Server ein authentifiziertes Paket erhält, wird der Tunnel erstellt und alle nachfolgenden Pakete in der Verbindung werden bereits vom Kernel verarbeitet, ohne die nfqueue zu durchlaufen. Wenn die Verbindung abgelaufen ist, wird das erste Paket des nächsten Zugriffs in die nfqueue geleitet, abhängig von den Einstellungen. Wenn es sich nicht um ein Authentifizierungspaket handelt, sondern um das letzte gespeicherte IP und den Port des Clients, kann es entweder weitergeleitet oder verworfen werden. Wenn ein authentifiziertes Paket von neuen IPs und Ports kommt, wird der Tunnel neu konfiguriert, um diese zu nutzen.

Ein reguläres IPIP-over-FOU hat ein weiteres Problem bei der Arbeit mit NAT – es ist nicht möglich, zwei IPIP-Tunnel, die in UDP eingekapselt sind, mit derselben IP zu erstellen, da die FOU- und IPIP-Module ausreichend voneinander isoliert sind. Das bedeutet, dass zwei Clients hinter einer öffentlichen IP nicht gleichzeitig auf denselben Server auf diese Weise zugreifen können. In Zukunft, es könnte, wird dies wahrscheinlich auf Kernel-Ebene gelöst, aber das ist nicht sicher. Bis dahin können die NAT-Probleme mit NAT behoben werden – falls es passiert, dass zwei IP-Adressen bereits durch einen anderen Tunnel belegt sind, wird ipipou NAT von der öffentlichen auf die alternative private IP durchführen, voilà! – man kann Tunnel erstellen, bis die Ports ausgehen.

Da nicht alle Pakete in der Verbindung signiert sind, ist dieser einfache Schutz anfällig für MITM, sodass, wenn sich ein Bösewicht zwischen dem Client und dem Server versteckt, der den Datenverkehr abhören und steuern kann, er die Authentifizierungspakete an eine andere Adresse umleiten und einen Tunnel von einem nicht vertrauenswürdigen Host erstellen kann.

Wenn jemand Ideen hat, wie man das beheben kann und gleichzeitig den Großteil des Verkehrs im Kernel belässt, zögert nicht – äußert euch.

Übrigens hat sich die Kapselung in UDP sehr gut bewährt. Im Vergleich zur Kapselung über IP ist sie viel stabiler und oft schneller, trotz der zusätzlichen Overhead-Kosten für den UDP-Header. Das liegt daran, dass im Internet der Großteil der Hosts nur mit den drei beliebtesten Protokollen: TCP, UDP, ICMP, einigermaßen gut funktioniert. Ein spürbarer Teil kann sogar alles andere vollständig ignorieren oder langsamer verarbeiten, da es nur für diese drei optimiert ist.

Zum Beispiel wurde QUICK, auf dessen Basis HTTP/3 entwickelt wurde, genau über UDP und nicht über IP aufgebaut.

Genug der Worte, schauen wir uns an, wie es in der „realen Welt“ funktioniert.

Battle

Zur Simulation der realen Welt wird verwendet iperf3. In Bezug auf die Nähe zur Realität ist das etwa wie die Simulation der realen Welt in Minecraft, aber es ist schon mal okay.

Im Wettbewerb beteiligt sind:

  • der Referenz-Hauptkanal
  • der Protagonist dieses Artikels ipipou
  • OpenVPN mit Authentifizierung, aber ohne Verschlüsselung
  • OpenVPN im „All-Inclusive“-Modus
  • WireGuard ohne PresharedKey, mit MTU=1440 (da nur IPv4)

Technische Daten für Geek
Die Messwerte werden mit diesen Befehlen erfasst

auf dem Client:

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"
# Wo "-b 12M" die Bandbreite des Hauptkanals ist, geteilt durch die Anzahl der Streams "-P", um überflüssige Pakete zu vermeiden und die Leistung nicht zu beeinträchtigen.

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-Latenz

ping -c 10 SERVER_IP | tail -1

auf dem Server (wird gleichzeitig mit dem Client gestartet):

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"

Tunnelkonfiguration

ipipou entwickelt.
Server
/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

Client
/etc/ipipou/client.conf:

client
number 0
fou-local @eth0
fou-remote SERVER_IP:10000
tunl-ip 172.28.0.1
# pubkey von auth-key-b64: eQYNhD/Xwl6Zaq+z3QXDzNI77x8CEKqY1n5kt9bKeEI=
auth-key-b64 RuBZkT23na2Q4QH1xfmZCfRgSgPt5s362UPAFbecTso=
auth-secret topsecret
keepalive 27
verb 3

systemctl start ipipou@client

openvpn (ohne Verschlüsselung, mit Authentifizierung)
Server

openvpn --genkey --secret ovpn.key  # Dann muss ovpn.key an den Client übertragen werden
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

Client

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 (mit Verschlüsselung, Authentifizierung, über UDP, alles wie vorgesehen)
Eingestellt mit openvpn-manage

wireguard
Server
/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

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

Ergebnisse

Eine rohe, hässliche Tabelle
Die CPU-Auslastung des Servers ist nicht sehr aussagekräftig, da dort viele andere Dienste laufen, die manchmal Ressourcen verbrauchen:

Proto-Bandbreite[Mbps] CPU_idle_client[%] CPU_idle_server[%]
# 20 Mbps Kanal von einem Mikrokontroller (4 Kerne) zu VPS (1 Kern) über den Atlantik
# pure
UDP 20.4      99.80 93.34
TCP 19.2      99.67 96.68
ICMP-Latenz 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-Latenz min/avg/max/mdev = 199.562/208.919/220.222/7.905 ms
# openvpn0 (nur Authentifizierung, keine Verschlüsselung)
UDP 19.3      99.89 72.90
TCP 16.1      95.95 88.46
ICMP-Latenz min/avg/max/mdev = 191.631/193.538/198.724/2.520 ms
# openvpn (vollständige Verschlüsselung, Authentifizierung usw.)
UDP 19.6      99.75 72.35
TCP 17.0      94.47 87.99
ICMP-Latenz 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-Latenz min/avg/max/mdev = 217.925/223.601/230.696/3.266 ms

## etwa-1Gbps Kanal zwischen VPS in Europa und den USA (1 Kern)
# pure
UDP 729      73.40 39.93
TCP 363      96.95 90.40
ICMP-Latenz 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-Latenz min/avg/max/mdev = 107.444/107.523/107.648/0.058 ms
# openvpn0 (nur Authentifizierung, keine Verschlüsselung)
UDP 193      17.51  1.62
TCP  12      95.45 92.80
ICMP-Latenz 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-Latenz min/avg/max/mdev = 107.616/107.788/108.038/0.128 ms

Kanal von 20 Mbps

ipipou: mehr als nur ein unverschlüsselter Tunnel

ipipou: mehr als nur ein unverschlüsselter Tunnel

Kanal von 1 optimistischem Gbps

ipipou: mehr als nur ein unverschlüsselter Tunnel

ipipou: mehr als nur ein unverschlüsselter Tunnel

In allen Fällen ist ipipou ziemlich nahe an den Basisbandwerten, und das ist großartig!

Der unverschlüsselte OpenVPN-Tunnel hat sich in beiden Fällen ziemlich merkwürdig verhalten.

Wenn jemand testen möchte, würde ich mich über Feedback freuen.

Möge IPv6 und NetPrickle mit uns sein!

Quelle: habr.com

60GB SSD 8Gb DDR4