ipipou : bien plus qu'un simple tunnel en clair

Que disons-nous au Dieu d’IPv6 ?

ipipou : bien plus qu'un simple tunnel en clair
C’est vrai, nous dirons la même chose au dieu du cryptage aujourd’hui.

Nous parlerons ici d'un tunnel IPv4 non crypté, mais pas d'un tunnel « lampe chaude », mais d'un tunnel « LED » moderne. Et il y a aussi des sockets bruts qui clignotent ici, et des travaux sont en cours avec des paquets dans l'espace utilisateur.

Il existe N protocoles de tunneling pour tous les goûts et toutes les couleurs :

  • élégant, à la mode, jeunesse WireGuard
  • multifonctionnel, comme les couteaux suisses, OpenVPN et SSH
  • GRE vieux et pas méchant
  • l'IPIP le plus simple, le plus rapide et totalement non crypté
  • se développant activement GENEVE
  • beaucoup d'autres.

Mais je suis programmeur, je n'augmenterai donc N que d'une fraction et laisserai le développement de vrais protocoles aux développeurs de Kommersant.

Dans celui qui n'est pas encore né projetCe que je fais maintenant, c'est atteindre les hôtes derrière NAT depuis l'extérieur. En utilisant pour cela des protocoles avec une cryptographie adulte, je ne pouvais pas m'empêcher de penser que c'était comme tirer des moineaux avec un canon. Parce que le tunnel est utilisé en grande partie uniquement pour percer des trous dans NAT-e, le trafic interne est généralement également crypté, mais il est toujours noyé dans HTTPS.

Lors de mes recherches sur divers protocoles de tunneling, l'attention de mon perfectionniste intérieur a été attirée à maintes reprises sur IPIP en raison de sa surcharge minimale. Mais il présente un inconvénient et demi important pour mes tâches :

  • cela nécessite des adresses IP publiques des deux côtés,
  • et aucune authentification pour vous.

Par conséquent, le perfectionniste a été refoulé dans le coin sombre du crâne, ou là où il se trouve.

Et puis un jour, en lisant des articles sur tunnels pris en charge nativement sous Linux, je suis tombé sur FOU (Foo-over-UDP), c'est-à-dire peu importe, enveloppé dans UDP. Jusqu'à présent, seuls IPIP et GUE (Generic UDP Encapsulation) sont pris en charge.

« Voici la solution miracle ! Un simple IPIP me suffit. - Je pensais.

En fait, la balle s’est avérée n’être pas entièrement argentée. L'encapsulation dans UDP résout le premier problème - vous pouvez vous connecter aux clients derrière NAT depuis l'extérieur en utilisant une connexion préétablie, mais ici la moitié du prochain inconvénient d'IPIP apparaît sous un nouveau jour - n'importe qui d'un réseau privé peut se cacher derrière le visible. IP publique et port client (en IPIP pur ce problème n'existe pas).

Pour résoudre ce problème et demi, l'utilitaire est né ipipou. Il implémente un mécanisme maison pour authentifier un hôte distant, sans perturber le fonctionnement du FOU du noyau, qui traitera rapidement et efficacement les paquets dans l'espace du noyau.

Nous n'avons pas besoin de votre script !

Ok, si vous connaissez le port public et l'adresse IP du client (par exemple, tout le monde derrière ne va nulle part, NAT essaie de mapper les ports 1-en-1), vous pouvez créer un tunnel IPIP-over-FOU avec le commandes suivantes, sans aucun script.

sur le serveur :

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

sur le 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

# Опции 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* — nom de l'interface réseau du tunnel local
  • 203.0.113.1 — serveur IP public
  • 198.51.100.2 — IP publique du client
  • 192.168.0.2 — IP client attribuée à l'interface eth0
  • 10001 — port client local pour FOU
  • 20001 — port client public pour FOU
  • 10000 — port de serveur public pour FOU
  • encap-csum — option pour ajouter une somme de contrôle UDP aux paquets UDP encapsulés ; peut être remplacé par noencap-csum, sans oublier que l'intégrité est déjà contrôlée par la couche d'encapsulation externe (tandis que le paquet est à l'intérieur du tunnel)
  • eth0 — interface locale à laquelle le tunnel ipip sera lié
  • 172.28.0.1 — IP de l'interface du tunnel client (privée)
  • 172.28.0.0 — Interface serveur tunnel IP (privée)

Tant que la connexion UDP est active, le tunnel fonctionnera, mais s'il tombe en panne, vous aurez de la chance - si le port IP: du client reste le même - il vivra, s'il change - il se brisera.

Le moyen le plus simple de tout restaurer est de décharger les modules du noyau : modprobe -r fou ipip

Même si l'authentification n'est pas requise, l'adresse IP publique et le port du client ne sont pas toujours connus et sont souvent imprévisibles ou variables (selon le type de NAT). Si vous oubliez encap-dport côté serveur, le tunnel ne fonctionnera pas, il n'est pas assez intelligent pour prendre le port de connexion distant. Dans ce cas, ipipou peut également vous aider, ou WireGuard et d'autres similaires peuvent vous aider.

Comment ça marche?

Le client (qui est généralement derrière NAT) ouvre un tunnel (comme dans l'exemple ci-dessus), et envoie un paquet d'authentification au serveur pour qu'il configure le tunnel de son côté. Selon les paramètres, il peut s'agir d'un paquet vide (juste pour que le serveur puisse voir l'adresse IP publique : port de connexion), ou avec des données grâce auxquelles le serveur peut identifier le client. Les données peuvent être une simple phrase secrète en texte clair (l'analogie avec HTTP Basic Auth vient à l'esprit) ou des données spécialement conçues et signées avec une clé privée (similaire à HTTP Digest Auth mais plus fort, voir fonction client_auth dans le code).

Sur le serveur (côté avec l'IP publique), quand ipipou démarre, il crée un gestionnaire de file d'attente nfqueue et configure netfilter pour que les paquets nécessaires soient envoyés là où ils doivent être : paquets initialisant la connexion à la file d'attente nfqueue, et [presque] tout le reste va directement au FOU auditeur.

Pour ceux qui ne sont pas au courant, nfqueue (ou NetfilterQueue) est une chose spéciale pour les amateurs qui ne savent pas développer des modules de noyau, qui, à l'aide de netfilter (nftables/iptables), permet de rediriger les paquets réseau vers l'espace utilisateur et de les y traiter en utilisant moyens primitifs à portée de main : modifier (facultatif) et le rendre au noyau, ou le supprimer.

Pour certains langages de programmation il y a des liaisons pour travailler avec nfqueue, pour bash il n'y en avait pas (heh, pas surprenant), j'ai dû utiliser python : ipipou utilise NetfilterQueue.

Si les performances ne sont pas critiques, en utilisant cet élément, vous pouvez concocter relativement rapidement et facilement votre propre logique pour travailler avec des paquets à un niveau assez bas, par exemple, créer des protocoles de transfert de données expérimentaux ou troller des services locaux et distants avec un comportement non standard.

Les sockets bruts fonctionnent main dans la main avec nfqueue, par exemple, lorsque le tunnel est déjà configuré et que FOU écoute sur le port souhaité, vous ne pourrez pas envoyer de paquet depuis le même port de la manière habituelle - il est occupé, mais vous pouvez prendre et envoyer un paquet généré aléatoirement directement à l'interface réseau à l'aide d'un socket brut, bien que la génération d'un tel paquet nécessitera un peu plus de bricolage. C'est ainsi que sont créés les paquets avec authentification dans ipipou.

Étant donné qu'ipipou ne traite que les premiers paquets de la connexion (et ceux qui ont réussi à s'infiltrer dans la file d'attente avant l'établissement de la connexion), les performances n'en souffrent presque pas.

Dès que le serveur ipipou reçoit un paquet authentifié, un tunnel est créé et tous les paquets suivants dans la connexion sont déjà traités par le noyau en contournant nfqueue. Si la connexion échoue, alors le premier paquet du suivant sera envoyé à la file d'attente nfqueue, en fonction des paramètres, s'il ne s'agit pas d'un paquet avec authentification, mais à partir de la dernière IP et du dernier port client mémorisés, il peut soit être transmis allumé ou jeté. Si un paquet authentifié provient d'une nouvelle adresse IP et d'un nouveau port, le tunnel est reconfiguré pour les utiliser.

L'IPIP-over-FOU habituel présente un problème supplémentaire lorsque l'on travaille avec NAT : il est impossible de créer deux tunnels IPIP encapsulés dans UDP avec la même IP, car les modules FOU et IPIP sont assez isolés les uns des autres. Ceux. une paire de clients derrière la même IP publique ne pourra pas se connecter simultanément au même serveur de cette manière. Dans le futur, peut-être, cela sera résolu au niveau du noyau, mais ce n'est pas certain. En attendant, les problèmes de NAT peuvent être résolus par le NAT - s'il arrive qu'une paire d'adresses IP soit déjà occupée par un autre tunnel, ipipou fera le NAT du public vers une IP privée alternative, le tour est joué ! - vous pouvez créer des tunnels jusqu'à épuisement des ports.

Parce que Tous les paquets de la connexion ne sont pas signés, alors cette simple protection est vulnérable au MITM, donc s'il y a un méchant qui se cache sur le chemin entre le client et le serveur et qui peut écouter le trafic et le manipuler, il peut rediriger les paquets authentifiés via une autre adresse et créez un tunnel à partir d'un hôte non fiable.

Si quelqu’un a des idées sur la façon de résoudre ce problème tout en laissant la majeure partie du trafic dans le cœur, n’hésitez pas à en parler.

D'ailleurs, l'encapsulation dans UDP a fait ses preuves. Par rapport à l'encapsulation sur IP, elle est beaucoup plus stable et souvent plus rapide malgré la surcharge supplémentaire de l'en-tête UDP. Cela est dû au fait que la plupart des hôtes sur Internet ne fonctionnent bien qu'avec les trois protocoles les plus populaires : TCP, UDP, ICMP. La partie tangible peut complètement rejeter tout le reste, ou le traiter plus lentement, car elle est optimisée uniquement pour ces trois éléments.

Par exemple, c'est pourquoi QUICK, sur lequel est basé HTTP/3, a été créé au-dessus d'UDP, et non au-dessus d'IP.

Bon, assez de mots, il est temps de voir comment cela fonctionne dans le « monde réel ».

Bataille

Utilisé pour imiter le monde réel iperf3. En termes de degré de proximité avec la réalité, cela équivaut à peu près à émuler le monde réel dans Minecraft, mais pour l'instant, cela fera l'affaire.

Participants au concours :

  • canal principal de référence
  • le héros de cet article est ipipou
  • OpenVPN avec authentification mais pas de cryptage
  • OpenVPN en mode tout compris
  • WireGuard sans PresharedKey, avec MTU=1440 (depuis IPv4 uniquement)

Données techniques pour les geeks
Les métriques sont prises avec les commandes suivantes :

sur le 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"
# Где "-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"

Latence ICMP

ping -c 10 SERVER_IP | tail -1

sur le serveur (s'exécute simultanément avec le client) :

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"

Configuration des tunnels

ipipou
serveur
/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 of auth-key-b64: eQYNhD/Xwl6Zaq+z3QXDzNI77x8CEKqY1n5kt9bKeEI=
auth-key-b64 RuBZkT23na2Q4QH1xfmZCfRgSgPt5s362UPAFbecTso=
auth-secret topsecret
keepalive 27
verb 3

systemctl start ipipou@client

openvpn (pas de cryptage, avec authentification)
serveur

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

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 (avec cryptage, authentification, via UDP, tout comme prévu)
Configuré à l'aide openvpn-gérer

protège-fil
serveur
/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

résultats

Signe laid et humide
La charge du processeur du serveur n'est pas très indicative, car... Il existe de nombreux autres services qui y fonctionnent, parfois ils consomment des ressources :

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

Canal 20 Mbit/s

ipipou : bien plus qu'un simple tunnel en clair

ipipou : bien plus qu'un simple tunnel en clair

canal par 1 Gbit/s optimiste

ipipou : bien plus qu'un simple tunnel en clair

ipipou : bien plus qu'un simple tunnel en clair

Dans tous les cas, ipipou est assez proche en performances du canal de base, ce qui est génial !

Le tunnel openvpn non chiffré s’est comporté de manière assez étrange dans les deux cas.

Si quelqu'un veut le tester, il sera intéressant d'entendre ses commentaires.

Que IPv6 et NetPrickle soient avec nous !

Source: habr.com

Ajouter un commentaire