Exemple simple de UDP hole punching avec un tunnel IPIP

Bonjour !

Dans cet article, je voudrais partager comment j'ai réalisé (encore un) script Bash pour connecter deux ordinateurs situés derrière NAT, en utilisant la technologie UDP hole punching, avec un exemple sur les systèmes Ubuntu/Debian.

L'organisation de la connexion se compose de plusieurs étapes :

  1. Lancement du nœud et attente de la disponibilité du nœud distant ;
  2. Détermination de l'adresse IP externe et du port UDP ;
  3. Transmission de l'adresse IP externe et du port UDP au nœud distant ;
  4. Réception de l'adresse IP externe et du port UDP du nœud distant ;
  5. Mise en place d'un tunnel IPIP ;
  6. Surveillance de la connexion ;
  7. En cas de rupture de la connexion, supprimer le tunnel IPIP.

J'ai longtemps réfléchi, et je continue de penser à ce que je peux utiliser pour échanger des données entre les nœuds. La solution la plus simple et rapide pour moi actuellement est d'utiliser Yandex.Disk.

  • Tout d'abord, c'est la simplicité d'utilisation — il suffit de 3 actions : créer, lire, supprimer. Avec curl, cela donne :
    Créer :
    curl -s -X MKCOL --user "$usename:$password" https://webdav.yandex.ru/$folder

    Lire :

    curl -s --user "$usename:$password" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/$folder

    Supprimer :

    curl -s -X DELETE --user "$usename:$password" https://webdav.yandex.ru/$folder
  • Deuxièmement, c'est la facilité d'installation :
    apt install curl

Pour déterminer l'adresse IP externe et le port UDP, on utilise le client stun avec la commande :

stun stun.sipnet.ru -v -p $1 2>&1 | grep "MappedAddress"

Installation avec la commande :

apt install stun-client

Pour mettre en place le tunnel, on utilise les outils natifs de l'OS du paquet iproute2. Il existe de nombreux tunnels qui peuvent être créés avec les outils standards (L2TPv3, GRE, etc.), mais j'ai choisi IPIP parce qu'il crée une charge supplémentaire minimale sur le système. J'ai essayé L2TPv3 sur UDP et j'ai été déçu, la vitesse a chuté de 10 fois, mais cela peut être dû à diverses limitations liées aux fournisseurs ou à d'autres facteurs. Comme le tunnel IPIP fonctionne au niveau IP, un tunnel FOU est utilisé pour opérer au niveau des ports UDP. Pour organiser un tunnel IPIP, il faut :

— charger le module FOU :

modprobe fou

— écouter le port local :

ip fou add port $localport ipproto 4

— créer le tunnel :

ip link add name fou$name type ipip remote $remoteip local $localip encap fou  encap-sport $localport encap-dport $remoteport

— activer l'interface du tunnel :

ip link set up dev fou$name

— attribuer l'adresse IP locale interne et l'adresse IP externe interne du tunnel :

ip addr add $intIP peer $peerip dev fou$name

Supprimer le tunnel :

ip link del dev fou$name

ip fou del port $localport

La surveillance de l'état du tunnel se fait à l'aide d'un ping périodique de l'adresse IP interne du tunnel du nœud distant, avec la commande :

ping -c 1 $peerip -s 0

Le ping périodique est nécessaire principalement pour maintenir le canal, sinon en cas d'inactivité du tunnel, les tables NAT des routeurs peuvent être effacées et la connexion sera alors rompue.

Si le ping disparaît, le tunnel IPIP est supprimé et attend que le nœud distant soit prêt.

Voici le script :

#!/bin/bash
username="username@yandex.ru"
password="password"
folder="vpnid"
intip="10.0.0.1"
localport=`shuf -i 10000-65000 -n 1`
cid=`shuf -i 10000-99999 -n 1`
tid=`shuf -i 10-99 -n 1`
function yaread {
        curl -s --user "$1:$2" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/$3 | sed 's/></>n</g' | grep "displayname" | sed 's/<d:displayname>//g' | sed 's/</d:displayname>//g' | grep -v $3 | grep -v $4 | sort -r
}
function yacreate {
        curl -s -X MKCOL --user "$1:$2" https://webdav.yandex.ru/$3
}
function yadelete {
        curl -s -X DELETE --user "$1:$2" https://webdav.yandex.ru/$3
}
function myipport {
        stun stun.sipnet.ru -v -p $1 2>&1 | grep "MappedAddress" | sort | uniq | awk '{print $3}' | head -n1
}
function tunnel-up {
	modprobe fou
	ip fou add port $4 ipproto 4
	ip link add name fou$7 type ipip remote $1 local $3 encap fou encap-sport $4 encap-dport $2
	ip link set up dev fou$7
	ip addr add $6 peer $5 dev fou$7
}
function tunnel-check {
	sleep 10
        pings=0
        until [[ $pings == 4 ]]; do
                if ping -c 1 $1 -s 0 &>/dev/null;
                        then    echo -n .; n=0
                        else    echo -n !; ((pings++))
                fi
		sleep 15
        done
}
function tunnel-down {
	ip link del dev fou$1
	ip fou del port $2
}
trap 'echo -e "nDisconnecting..." && yadelete $username $password $folder; tunnel-down $tunnelid $localport; echo "IPIP tunnel disconnected!"; exit 1' 1 2 3 8 9 14 15
until [[ -n $end ]]; do
    yacreate $username $password $folder
    until [[ -n $ip ]]; do
        mydate=`date +%s`
        timeout="60"
        list=`yaread $username $password $folder $cid | head -n1`
        yacreate $username $password $folder/$mydate:$cid
        for l in $list; do
                if [ `echo $l | sed 's/:/ /g' | awk {'print $1'}` -ge $(($mydate-65)) ]; then
			#echo $list
                        myipport=`myipport $localport`
                        yacreate $username $password $folder/$mydate:$cid:$myipport:$intip:$tid
                        timeout=$(( $timeout + `echo $l | sed 's/:/ /g' | awk {'print $1'}` - $mydate + 3 ))
                        ip=`echo $l | sed 's/:/ /g' | awk '{print $3}'`
                        port=`echo $l | sed 's/:/ /g' | awk '{print $4}'`
                        peerip=`echo $l | sed 's/:/ /g' | awk '{print $5}'`
			peerid=`echo $l | sed 's/:/ /g' | awk '{print $6}'`
			if [[ -n $peerid ]]; then tunnelid=$(($peerid*$tid)); fi
                fi
        done
        if ( [[ -z "$ip" ]] && [ "$timeout" -gt 0 ] ) ; then
                echo -n "!"
                sleep $timeout
        fi
    done
    localip=`ip route get $ip | head -n1 | sed 's|.*src ||' | cut -d' ' -f1`
    tunnel-up $ip $port $localip $localport $peerip $intip $tunnelid
    tunnel-check $peerip
    tunnel-down $tunnelid $localport
    yadelete $username $password $folder
    unset ip port myipport
done
exit 0

Variables username, password et dossier doivent être identiques des deux côtés, tandis que intip peuvent être différents, par exemple : 10.0.0.1 et 10.0.0.2. L'heure sur les nœuds doit être synchronisée. Le script peut être lancé ainsi :

nohup script.sh &

Je tiens à souligner que le tunnel IPIP n'est pas sécurisé du point de vue que le trafic n'est pas chiffré, mais cela peut être facilement résolu avec IPsec grâce à cet article, que je trouve simple et compréhensible.

J'utilise ce script pour me connecter à mon PC de travail depuis plusieurs semaines et je n'ai rencontré aucun problème. C'est pratique dans le sens où je l'ai configuré et oublié.

Peut-être aurez-vous des remarques et des suggestions, je serais heureux de vous écouter.

Merci de votre attention !

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster