ipipou: mehr als nur ein unverschlüsselter Tunnel

Was sagen wir Gott IPv6?

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

Hier wird es um unverschlüsselte IPv4-Tunnel gehen, aber nicht um einen "wärmenden Lampe", sondern um einen modernen "LED". Zudem kommen rohe Sockets vor und es wird mit Paketen im Benutzerspace 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 und ganz unverschlüsselt IPIP
  • aktiv in der Entwicklung GENEVE
  • viele andere.

Aber ich bin ja Programmierer, deshalb werde ich N nur um einen Bruchteil erhöhen und die Entwicklung echter Protokolle den Ī-Entwicklern überlassen.

In einem noch nicht geborenen Projekt, an dem ich gerade arbeite, muss ich von außen zu Hosts hinter NAT gelangen. Unter Verwendung von Protokollen mit erwachsener Kryptografie hatte ich ständig das Gefühl, dass es wie mit einer Kanone auf Spatzen ist. Da der Tunnel hauptsächlich zum Durchstechen eines Lochs im NAT verwendet wird, ist der interne Datenverkehr normalerweise auch verschlüsselt, da man dennoch für HTTPS einsteht.

Bei der Erkundung verschiedener Tunneling-Protokolle wurde die Aufmerksamkeit meines inneren Perfektionisten immer wieder auf IPIP gelenkt, aufgrund seiner minimalen Overheads. Doch es gibt anderthalb entscheidende Nachteile für meine Aufgaben:

  • Es erfordert öffentliche IP-Adressen auf beiden Seiten,
  • und es gibt keine Authentifizierung.

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

Eines Tages stieß ich beim Lesen von Artikeln über nativ unterstützte Tunnel in Linux auf FOU (Foo-over-UDP), sprich, irgendwas, das in UDP verpackt ist. Momentan werden nur IPIP und GUE (Generic UDP Encapsulation) unterstützt.

„Da ist sie, die Silbermunition! Für mich reicht auch simples IPIP völlig aus.“ — dachte ich.

In der Praxis entpuppte sich die Munition jedoch als nicht ganz silbern. Die Kapselung in UDP löst das erste Problem – Clients hinter einem NAT können von außen über eine vorher eingerichtete Verbindung erreicht werden, aber hier blüht die Hälfte des nächsten Nachteils von IPIP in neuem Licht – hinter den sichtbaren öffentlichen IP-Adressen und dem Port des Clients kann sich jeder aus dem privaten Netz verbergen (bei purem IPIP gibt es dieses Problem nicht).

Um dieses halbe Problem zu lösen, entstand das Tool ipipou. Ein selbst gebastelter Authentifizierungsmechanismus für den Remote-Host wurde implementiert, ohne die Funktionalität des Kerns des FOU zu beeinträchtigen, der effizient und schnell 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 wenn er NAT verwendet, um Ports 1-zu-1 zu mappen und nicht willkürlich zu anderen Orten zu gehen), kannst du mit folgenden Befehlen einen IPIP-over-FOU Tunnel ohne 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 können von älteren Kernen nicht unterstützt werden und können weggelassen werden.
# peer und peer_port werden verwendet, um die 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

wobei

  • ipipou* — Name der lokalen Tunnel-Netzwerkschnittstelle
  • 203.0.113.1 — Öffentliche IP des Servers
  • 198.51.100.2 — Öffentliche IP des Clients
  • 192.168.0.2 — IP des Clients, die dem eth0-Interface 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 zur Hinzufügung der UDP-Prüfziffer zu gekapselten UDP-Paketen; kann ersetzt werden durch noencap-csum, um die Integrität nicht zu prüfen, die wird bereits durch die äußere Kapselung kontrolliert (während das Paket sich im Tunnel befindet)
  • eth0 — lokales Interface, an das der IPIP-Tunnel gebunden wird
  • 172.28.0.1 — IP des Tunnel-Client-Interfaces (privat)
  • 172.28.0.0 — IP des Tunnel-Server-Interfaces (privat)

Solange die UDP-Verbindung aktiv ist, bleibt der Tunnel funktionsfähig; wenn sie abbricht, hängt es vom Glück ab — wenn IP: Port des Clients gleich bleiben, wird er weiter bestehen, andernfalls wird er abreißen.

Es ist am einfachsten, alles rückgängig zu machen, indem man die Kernel-Module entlädt: modprobe -r fou ipip

Selbst wenn Authentifizierung nicht erforderlich ist, sind die öffentlichen IPs und Ports der Clients nicht immer bekannt und häufig unvorhersehbar oder wechselhaft (je nach Art des NAT). Wenn man die encap-dport auf der Serverseite weglässt, wird der Tunnel nicht funktionieren, da er nicht so intelligent ist, um den entfernten Verbindungsport zu übernehmen. In diesem Fall kann ipipou auch helfen, oder WireGuard und ähnliche Lösungen stehen dir zur Verfügung.

Wie funktioniert das?

Der Client (normalerweise hinter einem NAT) stellt eine Verbindung (wie im obigen Beispiel) her und sendet ein Authentifizierungspaket an den Server, um den Tunnel von seiner Seite aus einzurichten. Je nach Konfiguration kann es sich um ein leeres Paket handeln (einfach damit der Server die öffentliche IP: Portverbindung sieht) oder um Daten, anhand derer der Server den Client identifizieren kann. Diese Daten können ein einfacher Passwortsatz im Klartext sein (vergleichbar mit HTTP Basic Auth) oder spezifisch formatierte, mit einem privaten Schlüssel signierte Daten (ähnlich wie HTTP Digest Auth, jedoch robuster, siehe Funktion client_auth im Code).

Auf dem Server (Seite mit der öffentlichen IP) erstellt ipipou beim Start einen Handler für die nfqueue und konfiguriert netfilter so, dass die erforderlichen Pakete an die richtige Stelle weitergeleitet werden: die das Verbindungsaufbau initierenden Pakete gehen in die nfqueue und [fast] alle anderen direkt zum FOU-Listener.

Für diejenigen, die nicht im Thema sind: nfqueue (oder NetfilterQueue) ist ein spezielles Werkzeug für Anfänger, die keine Kernel-Module entwickeln können. Mit den Mitteln von netfilter (nftables/iptables) ermöglicht es, Netzwerkpakete in den Benutzerspeicher umzuleiten und dort mit einfachen Mitteln zu verarbeiten: sie zu modifizieren (optional) und zurück an den Kernel zu übergeben oder sie abzulehnen.

Für einige Programmiersprachen gibt es Bindungen für die Arbeit mit nfqueue, für Bash fanden sich keine (wie überraschend), also musste ich Python verwenden: ipipou verwendet NetfilterQueue.

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

Zusammen mit nfqueue arbeiten rohe Sockets (raw sockets), beispielsweise wenn ein Tunnel bereits konfiguriert ist und FOU auf dem richtigen Port lauscht. Man kann normalerweise kein Paket von diesem Port senden — er ist besetzt. Stattdessen kann man ein beliebig generiertes Paket direkt in das Netzwerkinterface einpflegen, indem man einen rohen Socket verwendet, auch wenn man für die Erstellung eines solchen Pakets etwas mehr Aufwand betreiben muss. So entstehen in ipipou Pakete mit Authentifizierung.

Da ipipou nur die ersten Pakete aus einer Verbindung verarbeitet (und die, die vor der Verbindungsherstellung in die Warteschlange geraten 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 vom Kernel verarbeitet, ohne nfq zu durchlaufen. Wenn die Verbindung abgelaufen ist, wird das erste Paket des nächsten Verbindungsversuchs in die nfq-Warteschlange geleitet. Je nach den Einstellungen kann das, sofern es sich nicht um ein Authentifizierungspaket handelt, mit der zuletzt gespeicherten IP und dem Port des Clients entweder weitergeleitet oder verworfen werden. Wenn ein authentifiziertes Paket von einer neuen IP und einem neuen Port ankommt, wird der Tunnel auf deren Verwendung umkonfiguriert.

Bei herkömmlichem IPIP-over-FOU gibt es ein weiteres Problem beim Betrieb mit NAT — es ist nicht möglich, zwei IPIP-Tunnel, die in UDP gekapselt sind, mit identischen IPs zu erstellen, da die FOU- und IPIP-Module ausreichend voneinander isoliert sind. Das heißt, ein Paar Clients hinter einer öffentlichen IP kann nicht gleichzeitig auf denselben Server zugreifen. In Zukunft, möglich, wird dies auf der Kernel-Ebene gelöst, aber das ist nicht sicher. Bis dahin können NAT-Probleme durch NAT gelöst werden — wenn es passiert, dass ein Paar IP-Adressen bereits von einem anderen Tunnel belegt ist, wird ipipou NAT von der öffentlichen auf die alternative private IP durchführen, voilà! — man kann Tunnel erstellen, bis die Ports erschöpft sind.

Da nicht alle Pakete in der Verbindung signiert sind, ist dieser einfache Schutz anfällig für MITM-Angriffe. Wenn ein Angreifer zwischen dem Client und dem Server lauert und den Datenverkehr abhören sowie steuern kann, kann er die Pakete mit der Authentifizierung über eine andere Adresse umleiten und einen Tunnel von einem nicht vertrauenswürdigen Host erstellen.

Wenn jemand Ideen hat, wie man das beheben kann, während der Großteil des Verkehrs im Kernel bleibt, 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 Overheads durch den UDP-Header. Das liegt daran, dass im Internet die meisten Hosts nur mit den drei populärsten Protokollen: TCP, UDP und ICMP, angemessen arbeiten. Ein spürbarer Teil kann alles andere komplett ignorieren oder langsamer verarbeiten, da sie nur für diese drei optimiert sind.

Beispielsweise wurde QUICK, auf dem HTTP/3 basiert, genau über UDP und nicht über IP entwickelt.

Genug der Worte, es ist Zeit zu sehen, wie das in der 'realen Welt' funktioniert.

Schlacht

Zur Emulation der realen Welt wird verwendet iperf3. In Bezug auf die Realität ist das etwa so wie die Simulation der realen Welt in Minecraft, aber vorläufig wird es das tun.

Die Teilnehmer des Wettbewerbs sind:

  • der Referenz-Hauptkanal
  • der Held 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 Nerds
Die Metriken werden mit diesen Befehlen erfasst

auf dem Client:

UDP nachzudenken

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"
# wobei "-b 12M" die Bandbreite des Hauptkanals geteilt durch die Anzahl der Streams "-P" ist, 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 nachzudenken

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

einen Client
/etc/ipipou/client.conf:

Kunde
Nummer 0
fou-local @eth0
fou-remote SERVER_IP:10000
tunl-ip 172.28.0.1
# Öffentlicher Schlüssel des 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)
der Server

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

einen 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 es sein sollte)
Konfiguriert mit Hilfe von openvpn-manage

wireguard
der 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

einen 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 unschöne, rohe Tabelle
Die CPU-Auslastung des Servers ist nicht sehr aussagekräftig, da viele andere Dienste aktiv sind, die manchmal Ressourcen verbrauchen:

proto Bandbreite[Mbps] CPU_idle_client[%] CPU_idle_server[%]
# 20 Mbps Kanal von einem Mikrocomputer (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 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 in Bezug auf die Werte ziemlich nah am Basis-Kanal, und das ist großartig!

Der unverschlüsselte Tunnel von openvpn hat sich in beiden Fällen ziemlich seltsam verhalten.

Wenn jemand testet, wäre es interessant, die Rückmeldungen zu hören.

Möge IPv6 und NetPrickle mit uns sein!

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster