Ejemplo simple de apertura de agujeros UDP usando un túnel IPIP

¡Buen día!

En este artículo quiero contarles cómo implementé (otro) un script en Bash para conectar dos computadoras que están detrás de un NAT, utilizando la tecnología de UDP hole punching, en el ejemplo de los sistemas operativos Ubuntu/Debian.

La organización de la conexión consta de varios pasos:

  1. Iniciar el nodo y esperar a que el nodo remoto esté listo;
  2. Determinar la dirección IP externa y el puerto UDP;
  3. Enviar la dirección IP externa y el puerto UDP al nodo remoto;
  4. Recibir la dirección IP externa y el puerto UDP del nodo remoto;
  5. Organizar un túnel IPIP;
  6. Monitorear la conexión;
  7. Al romperse la conexión, borrar el túnel IPIP.

He estado pensando durante mucho tiempo y todavía pienso, qué se puede usar para intercambiar datos entre nodos, lo más simple y rápido para mí en este momento es trabajar a través de Yandex.Disk.

  • Primero, es sencillo de usar: solo se necesitan 3 acciones: crear, leer, eliminar. Con curl esto es:
    Crear:
    curl -s -X MKCOL --user "$usename:$password" https://webdav.yandex.ru/$folder

    Leer:

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

    Eliminar:

    curl -s -X DELETE --user "$usename:$password" https://webdav.yandex.ru/$folder
  • En segundo lugar, es fácil de instalar:
    apt install curl

Para determinar la dirección IP externa y el puerto UDP se usa el cliente stun con el comando:

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

Instalar con el comando:

apt install stun-client

Para organizar un túnel se utilizan las herramientas estándar del sistema operativo del paquete iproute2. Existen varios túneles que se pueden crear con las herramientas estándar (L2TPv3, GRE, etc.), pero elegí IPIP porque genera una carga adicional mínima en el sistema. Probé L2TPv3 sobre UDP y me decepcioné, la velocidad se redujo diez veces, pero esto puede deberse a diversas limitaciones relacionadas con los proveedores u otras razones. Dado que el túnel IPIP funciona a nivel IP, se utiliza un túnel FOU para operar a nivel de puertos UDP. Para organizar el túnel IPIP, se necesita:

— cargar el módulo FOU:

modprobe fou

— escuchar el puerto local:

ip fou add port $localport ipproto 4

— crear el túnel:

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

— activar la interfaz del túnel:

ip link set up dev fou$name

— asignar la dirección IP interna local y la dirección IP interna remota del túnel:

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

Eliminar el túnel:

ip link del dev fou$name

ip fou del port $localport

El monitoreo del estado del túnel se realiza mediante un ping periódico a la dirección IP interna del túnel del nodo remoto, con el comando:

ping -c 1 $peerip -s 0

El ping periódico es necesario principalmente para mantener el canal, de lo contrario, en caso de inactividad del túnel, las tablas NAT en los routers pueden limpiarse y la conexión se romperá.

Si el ping se pierde, el túnel IPIP se elimina y espera la disponibilidad del nodo remoto.

El script en sí:

#!/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 y folder deben ser iguales en ambos lados, mientras que intip serán diferentes, por ejemplo: 10.0.0.1 y 10.0.0.2. La hora en los nodos debe estar sincronizada. El script se puede iniciar así:

nohup script.sh &

Quiero señalar que el túnel IPIP no es seguro en términos de que el tráfico no está cifrado, pero esto se puede resolver fácilmente con IPsec por este artículo, me pareció simple y comprensible.

He estado usando este script para conectarme a mi PC de trabajo durante varias semanas y no he notado problemas. Es conveniente en el sentido de que lo configuré y me olvidé.

Puede que tenga comentarios y sugerencias, estaré encantado de escucharlos.

¡Gracias por su atención!

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster