Esempio di semplice UDP hole punching utilizzando un tunnel IPIP

Buon pomeriggio!

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

L'organizzazione della connessione consiste in diversi passaggi:

  1. Avvio del nodo e attesa che il nodo remoto sia pronto;
  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 continuo a pensare a cosa si può utilizzare per lo scambio di dati tra i nodi, per me la cosa più semplice e veloce al momento è l'utilizzo di Yandex.Disk.

  • In primo luogo, è semplice da usare: ci sono 3 azioni: creare, leggere, eliminare. Con curl questo è:
    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, utilizzare il client stun con il comando:

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

Installazione con il comando:

apt install stun-client

Per organizzare un tunnel, si utilizzano gli strumenti di sistema di base forniti nel pacchetto iproute2. Esistono numerosi tunnel che possono essere creati utilizzando gli strumenti di base (L2TPv3, GRE, ecc.), ma ho scelto IPIP perché genera un carico aggiuntivo minimo sul sistema. Ho provato L2TPv3 sopra UDP e sono rimasto deluso, la velocità è diminuita di 10 volte, ma potrebbero esserci vari vincoli legati ai fornitori o qualcos'altro. Poiché il tunnel IPIP opera a livello IP, utilizza un tunnel FOU per lavorare a livello delle porte UDP. Per organizzare un tunnel IPIP è necessario:

— 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 locale interno e l'indirizzo IP remoto interno del tunnel:

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

Rimuovere il tunnel:

ip link del dev fou$name

ip fou del port $localport

Il monitoraggio dello stato del tunnel avviene tramite un ping periodico all'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, in caso di inattività del tunnel, le tabelle NAT sui router potrebbero essere svuotate e, di conseguenza, la connessione si interromperà.

Se il ping non risponde, il tunnel IPIP viene rimosso e attende la disponibilità dal nodo remoto.

In effetti, 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 cartella devono essere identici su entrambi i lati, mentre intip devono essere diversi, ad esempio: 10.0.0.1 e 10.0.0.2. L'ora sui nodi deve essere sincronizzata. Puoi avviare lo script così:

nohup script.sh &

Vorrei sottolineare che il tunnel IPIP non è sicuro dal punto di vista della crittografia del traffico, ma questo può essere facilmente risolto con IPsec. questo articolo, mi è sembrato semplice e chiaro.

Uso questo script per connettermi al computer di lavoro da diverse settimane e non ho riscontrato problemi. È comodo in quanto lo configuro e poi dimentico.

Se hai commenti o suggerimenti, sarei felice di ascoltarli.

Grazie per l'attenzione!

Fonte: habr.com

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