Exemplo simples de UDP hole punching com túnel IPIP

Boa tarde!

Neste artigo, quero contar como implementei (mais um) script em Bash para conectar dois computadores que estão atrás de NAT, utilizando a tecnologia UDP hole punching com o sistema operacional Ubuntu/Debian.

A organização da conexão consiste em várias etapas:

  1. Iniciar o nó e esperar o nó remoto estar pronto;
  2. Determinar o endereço IP externo e a porta UDP;
  3. Enviar o endereço IP externo e a porta UDP para o nó remoto;
  4. Receber o endereço IP externo e a porta UDP do nó remoto;
  5. Organizar o túnel IPIP;
  6. Monitorar a conexão;
  7. Ao se desconectar, deletar o túnel IPIP.

Pensei por muito tempo e ainda estou pensando no que pode ser utilizado para troca de dados entre os nós, o mais simples e rápido para mim no momento é trabalhar através do Yandex.Disk.

  • Em primeiro lugar, é simples de usar — são necessárias 3 ações: criar, ler, deletar. Com curl isso é:
    Criar:
    curl -s -X MKCOL --user "$usename:$password" https://webdav.yandex.ru/$folder

    Ler:

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

    Deletar:

    curl -s -X DELETE --user "$usename:$password" https://webdav.yandex.ru/$folder
  • Em segundo lugar, é simples de instalar:
    apt install curl

Para determinar o endereço IP externo e a porta UDP, usa-se o stun-client com o comando:

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

Instalar com o comando:

apt install stun-client

Para organizar o túnel, são usados os recursos padrão do sistema operacional do pacote iproute2. Existem vários túneis que podem ser configurados com ferramentas padrão (L2TPv3, GRE, etc.), mas eu escolhi IPIP porque ele cria uma carga adicional mínima para o sistema. Eu tentei L2TPv3 sobre UDP e me decepcionei, a velocidade caiu 10 vezes, mas isso pode ser limitações diversas relacionadas aos provedores ou outras questões. Como o túnel IPIP opera no nível IP, usa-se o túnel FOU para operar no nível das portas UDP. Para organizar o túnel IPIP, é necessário:

— carregar o módulo FOU:

modprobe fou

— escutar a porta local:

ip fou add port $localport ipproto 4

— criar o túnel:

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

— ativar a interface do túnel:

ip link set up dev fou$name

— atribuir o endereço IP interno local e o IP interno remoto do túnel:

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

Deletar o túnel:

ip link del dev fou$name

ip fou del port $localport

O monitoramento do estado do túnel é realizado através de um ping periódico ao IP interno do túnel do nó remoto, com o comando:

ping -c 1 $peerip -s 0

O ping periódico é necessário principalmente para manter o canal. Caso contrário, se o túnel ficar ocioso, as tabelas NAT nos roteadores podem ser limpas e a conexão será interrompida.

Se o ping falhar, o túnel IPIP é removido e aguarda a disponibilidade do nó remoto.

O próprio 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

Variáveis nome de usuário, senha e folder devem ser iguais em ambos os lados, enquanto intip pode ser diferente, por exemplo: 10.0.0.1 e 10.0.0.2. O tempo nos nós deve estar sincronizado. O script pode ser executado assim:

nohup script.sh &

Ressalto que o túnel IPIP não é seguro em termos de que o tráfego não é criptografado, mas isso pode ser facilmente resolvido com IPsec, este artigo, que eu considerava simples e compreensível.

Tenho usado este script para me conectar ao meu PC de trabalho há várias semanas e não notei problemas. É conveniente no sentido de que configurei e esqueci.

Talvez você tenha comentários e sugestões, ficarei feliz em ouvi-los.

Obrigado pela atenção!

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster