Semplice UDP hole punching con l'esempio di un tunnel IPIP

Buon pomeriggio!

In questo articolo voglio raccontare come ho realizzato (un altro) script in Bash per collegare due computer che si trovano dietro NAT, utilizzando la tecnologia UDP hole punching con esempio su OS Ubuntu/Debian.

L'organizzazione della connessione si compone di vari passaggi:

  1. Avvio del nodo e attesa della disponibilità del nodo remoto;
  2. Determinazione dell'indirizzo IP esterno e della porta UDP;
  3. Trasmissione dell'indirizzo IP esterno e della porta UDP al nodo remoto;
  4. Ricezione dell'indirizzo IP esterno e della porta UDP dal nodo remoto;
  5. Organizzazione di un tunnel IPIP;
  6. Monitoraggio della connessione;
  7. In caso di interruzione della connessione eliminare il tunnel IPIP.

Ho pensato a lungo e sto ancora pensando a cosa si può utilizzare per lo scambio di dati tra i nodi, la cosa più semplice e veloce per me al momento è l'uso di Yandex.Disk.

  • In primo luogo, è semplice da usare: sono necessari 3 passaggi: creare, leggere, eliminare. Con curl ciò significa:
    Creare:
    curl -s -X MKCOL --user "$usename:$password" https://webdav.yandex.ru/$folder

    Leggere:

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

    Eliminare:

    curl -s -X DELETE --user "$usename:$password" https://webdav.yandex.ru/$folder
  • In secondo luogo, è semplice da installare:
    apt install curl

Per determinare l'indirizzo IP esterno e la porta UDP si utilizza stun-client con il comando:

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

Installare con il comando:

apt install stun-client

Per organizzare il tunnel si utilizzano gli strumenti standard del sistema operativo dal pacchetto iproute2. Ci sono moltissimi tunnel che possono essere creati con strumenti standard (L2TPv3, GRE, ecc.), ma ho scelto IPIP perché crea un carico addizionale minimo sul sistema. Ho provato L2TPv3 sopra UDP e sono rimasto deluso, la velocità è diminuita di 10 volte, ma potrebbero esserci vari limiti legati ai fornitori o ad altro. Poiché il tunnel IPIP funziona a livello di IP, si utilizza un tunnel FOU per lavorare a livello delle porte UDP. Per organizzare un tunnel IPIP sono necessari:

— caricare il modulo FOU:

modprobe fou

— ascoltare la porta locale:

ip fou add port $localport ipproto 4

— creare un tunnel:

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

— attivare l'interfaccia del tunnel:

ip link set up dev fou$name

— assegnare l'indirizzo IP interno locale e l'indirizzo IP interno remoto del tunnel:

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

Eliminare il tunnel:

ip link del dev fou$name

ip fou del port $localport

Il monitoraggio delle condizioni del tunnel avviene mediante il ping periodico dell'indirizzo IP interno del tunnel del nodo remoto, con il comando:

ping -c 1 $peerip -s 0

Il ping periodico è necessario principalmente per mantenere attivo il canale, altrimenti durante un inattività del tunnel sui router potrebbero svuotarsi le tabelle NAT e allora la connessione si interromperà.

Se il ping viene perso, il tunnel IPIP viene rimosso e attende che il nodo remoto sia pronto.

Lo script stesso:

#!/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

Variabili username, password e folder devono essere identici su entrambi i lati, mentre intip — sono diversi, per esempio: 10.0.0.1 e 10.0.0.2. L'ora sui nodi deve essere sincronizzata. Lo script può essere avviato in questo modo:

nohup script.sh &

Voglio sottolineare che il tunnel IPIP non è sicuro in termini di traffico in quanto non è criptato, ma questo può essere facilmente risolto con IPsec da questo articolo, mi è sembrato semplice e chiaro.

Utilizzo questo script per connettermi al PC di lavoro da diverse settimane e non ho riscontrato problemi. È comodo in quanto l'ho configurato e dimenticato.

Forse avrete commenti e suggerimenti, sarò felice di ascoltarli.

Grazie per l'attenzione!

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster