T煤nel VPN directo entre computadoras a trav茅s de proveedores NAT (sin VPS, usando un servidor STUN y Yandex.Disk)

La continuaci贸n del art铆culo sobre c贸mo logr茅 organizar un t煤nel VPN directo entre dos computadoras ubicadas detr谩s de los proveedores de NAT. El 煤ltimo art铆culo describi贸 el proceso de organizar una conexi贸n con un tercero: un intermediario (un VPS alquilado que act煤a como un servidor STUN y un transmisor de datos de nodo para una conexi贸n). En este art铆culo, le dir茅 c贸mo prescindir de VPS, pero los intermediarios permanecieron y fueron el servidor STUN y Yandex.Disk ...


Introduccion


Despu茅s de leer los comentarios de la 煤ltima publicaci贸n, me di cuenta de que el principal inconveniente de la implementaci贸n era el uso de un intermediario, un tercero (VPS) que indicaba los par谩metros actuales del nodo, d贸nde y c贸mo conectarse. Dadas las recomendaciones para usar este STUN (hay muchas ) para determinar los par谩metros de conexi贸n actuales. En primer lugar, decid铆 mirar el contenido de los paquetes usando TCPDump cuando el servidor STUN estaba trabajando con clientes y recib铆a contenido completamente ilegible. Googleando el protocolo se encontr贸 con un art铆culo que describe el protocolo . Me di cuenta de que no puedo implementar la solicitud al servidor STUN yo mismo y puse la idea en el "cuadro lejano".

Teor铆a


Recientemente tuve que instalar un servidor STUN en Debian desde un paquete
# apt install stun-server 
y en las dependencias vi el paquete aturdidor-cliente, pero de alguna manera no le di importancia a esto. Pero m谩s tarde, record茅 sobre el paquete aturdidor-cliente y decid铆 averiguar c贸mo funciona, buscando en Google y Poindeksiv que recib铆:

 # apt install stun-client # stun stun.ekiga.net -p 21234 -v 

En respuesta, recib铆:

Cliente STUN versi贸n 0.97
Puerto abierto 21234 con fd 3
Puerto abierto 21235 con fd 4
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 0

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 4

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 2

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Mensaje de aturdimiento recibido: 92 bytes
MappedAddress = <Mi IP>: 2885
SourceAddress = 216.93.246.18//478
ChangedAddress = 216.93.246.17lla479
Atributo desconocido: 32800
ServerName = Vovida.org 0.98-CPC
Mensaje recibido del tipo 257 id = 1
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 0

A punto de enviar un mensaje de len 28 a 216.93.246.17lla478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 4

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 2

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 0

A punto de enviar un mensaje de len 28 a <Mi IP>: 2885
Mensaje de aturdimiento recibido: 28 bytes
ChangeRequest = 0
Mensaje recibido de tipo 1 id = 11
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 0

A punto de enviar un mensaje de len 28 a 216.93.246.17lla478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 4

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 2

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Mensaje de aturdimiento recibido: 92 bytes
MappedAddress = <Mi IP>: 2885
SourceAddress = 216.93.246.17lla479
ChangedAddress = 216.93.246.18 {478
Atributo desconocido: 32800
ServerName = Vovida.org 0.98-CPC
Mensaje recibido del tipo 257 id = 10
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 4

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 2

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 4

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 2

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 4

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 2

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 4

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 2

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 4

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
Codificar mensaje de aturdimiento:
Solicitud de cambio de codificaci贸n: 2

A punto de enviar un mensaje de len 28 a 216.93.246.18 opin478
prueba I = 1
prueba II = 0
prueba III = 0
prueba I (2) = 1
es nat = 1
IP asignada igual = 1
horquilla = 1
puerto de conservador = 0
Primario: mapeo independiente, filtro dependiente del puerto, puerto aleatorio, horquilla
El valor de retorno es 0x000006

Cadena con valor
MappedAddress = <Mi IP>: 2885

justo lo que necesitas! Mostraba el estado actual de la conexi贸n en el puerto UDP local 21234. Pero esto es solo la mitad de la batalla, surgi贸 la pregunta de c贸mo transferir estos datos a un host remoto y establecer una conexi贸n VPN. Usando el protocolo de correo, 驴tal vez Telegram? Hay muchas opciones y decid铆 usar Yandex.Disk, ya que encontr茅 un art铆culo sobre c贸mo Curl funciona a trav茅s de WebDav con Yandex.Disk . Despu茅s de pensar en la implementaci贸n, llegu茅 a este esquema:

  1. Para indicar que los nodos est谩n listos para establecer una conexi贸n por la presencia de un archivo espec铆fico con una marca de tiempo en Yandex.disk;
  2. Si los nodos est谩n listos, obtenga los par谩metros actuales del servidor STUN;
  3. Suba los par谩metros actuales a Yandex.Disk;
  4. Verifique la disponibilidad y lea los par谩metros de un sitio remoto desde un archivo en Yandex.Disk;
  5. Establezca una conexi贸n a un host remoto utilizando OpenVPN.

Practica


Despu茅s de pensar un poco, teniendo en cuenta la experiencia del art铆culo anterior, escrib铆 un gui贸n r谩pido. Necesitaremos:
 # apt install openvpn stun-client curl 

En realidad, el gui贸n en s铆 mismo:
Opci贸n inicial
 # cat vpn8.sh 

 #!/bin/bash ########################    ### WARN='\033[37;1;41m' # END='\033[0m' # RED='\033[0;31m' # ${RED} # GREEN='\033[0;32m' # ${GREEN} # ################################################# #######################     ######################################################### al="echo readlink dirname grep awk md5sum shuf nc curl sleep openvpn cat stun" ch=0 for i in $al; do which $i > /dev/null || echo -e "${WARN}   $i ${END}"; which $i > /dev/null || ch=1; done if (( $ch > 0 )); then echo -e "${WARN},      ${END}"; exit; fi ####################################################################################################################### if [[ $1 == '' ]]; then echo -e "${WARN}   (  ,      !) ${END} \t ${GREEN}           /etc/rc.local  nohup /<  >/vpn8.sh > /var/log/vpn8.log 2>/dev/hull & ${END}"; exit; fi ABSOLUTE_FILENAME=`readlink -f "$0"` #     DIR=`dirname "$ABSOLUTE_FILENAME"` #      ###############################     ################################## key="$DIR/secret.key" if [ ! -f "$key" ]; then echo -e "${WARN}  VPN-  ,    : \ openvpn --genkey --secret secret.key :       \     !!!${END} # ls -l secret.key -rw------- 1 root root 637  27 11:12 secret.key # chmod 600 secret.key"; exit; fi ######################################################################################################################## ABSOLUTE_FILENAME=`readlink -f "$0"` #     DIR=`dirname "$ABSOLUTE_FILENAME"` #      name=$(uname -n | md5sum | awk '{print $1}') vpn=$(echo $1 | md5sum | awk '{print $1}') stun="stun.ekiga.net" # STUN  username="Yandex" #   . password="Password" #   . localport=`shuf -i 20000-65000 -n 1` #    echo "$(date)    ." curl -X MKCOL --user "${username}:${password}" https://webdav.yandex.ru/vpn-$vpn echo "$(date)     " for i in `curl --silent --user "$username:$password" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></\n/g' | grep "d:displayname" | sed 's/d:displayname//g' | sed 's/>//g' | sed 's/<//' | sed 's/\///g' | grep -v $(date +%Y-%m-%d-%H-%M)`; do echo "$(date) Delete: $i" curl -X DELETE --user "${username}:${password}" https://webdav.yandex.ru/vpn-$vpn/$i done until [ $c ];do until [[ $b ]]; do echo "$(date)  " date=`date +%Y-%m-%d-%H-%M` mydata=`curl --silent --user "${username}:${password}" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></>\n</g' | grep $name | grep $date | grep "d:displayname"` if [[ -z $mydata ]]; then echo "$(date)   " echo "$date" > "/tmp/$date-$name-ready.txt" curl -T "/tmp/$date-$name-ready.txt" --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$date-$name-ready.txt else echo "$(date)     - $date" fi remote=`curl --silent --user "${username}:${password}" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></>\n</g' | grep -v $name | grep $date | grep "d:displayname"` if [[ -z $remote ]]; then echo -e "$(date) ${RED}     ${END}" echo "$(date) " sleep 20 else echo -e "$(date) ${GREEN}    ${END}" b=1 a='' fi done until [ $a ]; do echo "$(date)      STUN : $stun" mydata=`stun $stun -p $localport -v 2>&1 | grep MappedAddress | sort | uniq` echo -e "$(date) ${GREEN}  : $mydata${END}" echo "$mydata" > "$DIR/mydata" echo "$(date)    ." curl -T "$DIR/mydata" --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$name.txt echo "$(date)     " filename=$(curl --silent --user "${username}:${password}" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></\n/g' | grep "d:displayname>" | grep "txt" | grep -v "$name" | grep -v "ready" | sed 's|.*d:displayname>||' | sed 's/</ /g' | awk '{print $1}') echo "$(date)     : $filename" address=$(curl --silent --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$filename | sort | uniq | head -n1 | sed 's/:/ /g') echo "$(date)  IP-  " ip=$(echo "$address" | awk '{print $3}') port=$(echo "$address" | awk '{print $4}') if [[ -n "$ip" && -n "$port" ]]; then echo -e "$(date) ${GREEN}  $ip $port ${END}" openvpn --remote $ip --rport $port --lport $localport \ --proto udp --dev tap --float --auth-nocache --verb 3 --mute 20 \ --ifconfig 10.45.54.2 255.255.255.252 \ --secret "$DIR/secret.key" \ --auth SHA256 --cipher AES-256-CBC \ --ncp-disable --ping 10 --ping-exit 30 \ --comp-lzo yes echo -e "$(date) ${WARN}  ${END}" a=1 b='' else a=1 b='' fi done done 

Para ejecutar el script necesitas:
  1. Copie en el portapapeles y p茅guelo en el editor, por ejemplo:
     # nano vpn8.sh 
  2. especifique el nombre de usuario y la contrase帽a de Yandex.Disk.
  3. en el campo "--ifconfig 10.45.54. (1 o 2) 255.255.255.252" especifique la direcci贸n IP interna de la interfaz
  4. crear secret.key con el comando:
     # openvpn --genkey --secret secret.key 
  5. hacer que el script sea ejecutable:
     # chmod +x vpn8.sh 
  6. ejecuta el script:
     # ./vpn8.sh nZbVGBuX5dtturD 

    donde nZbVGBuX5dtturD es la ID de conexi贸n generada aqu铆

En el nodo remoto, haga lo mismo excepto por la generaci贸n de secret.key y la conexi贸n de ID, deben ser id茅nticos.

Versi贸n actualizada (para un funcionamiento correcto, la hora debe estar sincronizada):

 cat vpn10.sh 

 #!/bin/bash stuns="stun.sipnet.ru stun.ekiga.net" #  STUN    username=" Login " #   . password=" Password " #   . intip="10.23.22.1" # IP-   WARN='\033[37;1;41m' END='\033[0m' RED='\033[0;31m' GREEN='\033[0;32m' al="ip echo readlink dirname grep awk md5sum openssl sha256sum shuf curl sleep openvpn cat stun" ch=0 for i in $al; do which $i > /dev/null || echo -e "${WARN}   $i ${END}"; which $i > /dev/null || ch=1; done if (( $ch > 0 )); then echo -e "${WARN},      ${END}"; exit; fi if [[ $1 == '' ]]; then echo -e "${WARN}   (  ,      !) ${END} \t ${GREEN}           /etc/rc.local  nohup /<  >/vpn10.sh > /var/log/vpn10.log 2>/dev/hull & ${END}" exit fi ABSOLUTE_FILENAME=`readlink -f "$0"` #     DIR=`dirname "$ABSOLUTE_FILENAME"` #      key="$DIR/secret.key" until [[ -n "$iftosrv" ]] do echo "$(date)   "; iftosrv=`ip route get 8.8.8.8 | head -n 1 | sed 's|.*dev ||' | awk '{print $1}'` sleep 5 done timedatectl name=$(uname -n | md5sum | awk '{print $1}') vpn=$(echo $1 | md5sum | awk '{print $1}') echo "$(date)    ." curl -X MKCOL --user "${username}:${password}" https://webdav.yandex.ru/vpn-$vpn echo "$(date) ID  : $vpn" until [ $c ];do echo "$(date)     " for i in `curl --silent --user "$username:$password" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></\n/g' | grep "d:displayname" | sed 's/d:displayname//g' | sed 's/>//g' | sed 's/<//' | sed 's/\///g' | grep -v $(date +%Y-%m-%d-%H-%M)` do echo -e "$(date)${RED}   : $i${END}" curl -X DELETE --user "${username}:${password}" https://webdav.yandex.ru/vpn-$vpn/$i done echo "$(date) ID  : $vpn" openvpn --genkey --secret "$key" passwd=`echo "$vpn-tt" | sha256sum | awk '{print $1}'` openssl AES-256-CBC -e -in "$key" -out "$DIR/file.enc" -k "$passwd" -base64 curl -T "$DIR/file.enc" --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/key.enc rm "$DIR"/file.enc echo -e "$(date) ${GREEN} 1 -    ${END}" go=3 localport=`shuf -i 20000-65000 -n 1` #    start='' remote='' timeout1='' nextcheck='' timestart='' until [[ $b ]] do echo "$(date)  " date=`date +%s` timeout1=60 echo "$(date)    $date" echo "$date" > "/tmp/ready-$date-$name.txt" curl -T "/tmp/ready-$date-$name.txt" --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/ready-$name.txt readyfile=`curl --silent --user "${username}:${password}" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></>\n</g' | grep -v $name | grep "ready" | grep "d:displayname" | sed 's/<d:displayname>//g' | sed 's/<\/d:displayname>//g'` if [[ -z $readyfile ]] then echo -e "$(date) ${RED}     ${END}" echo "$(date)  60 " sleep $timeout1 else remote=$(curl --silent --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$readyfile) echo -e "$(date) ${GREEN}    ${END}" start=`curl --silent --user "${username}:${password}" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></>\n</g' | grep "start" | grep "d:displayname" | sed 's/-/ /g' | awk '{print $2}'` if [[ -z $start ]] then let nextcheck=$timeout1-$date+$remote let timestart=$date+$timeout1-$nextcheck go=$nextcheck echo "$timestart" > "/tmp/start-$date-$name.txt" curl -T "/tmp/start-$date-$name.txt" --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/start-$date-$name.txt else echo "$(date)  $go " sleep $go b=1 a='' fi fi done echo -e "$(date) ${GREEN} 2 -     ${END}" mydata='' filename='' address='' myip='' ip='' port='' ex=0 until [ $a ]; do until [[ -n "$mydata" ]]; do k=`echo "$stuns" | wc -w` x=1 z=`shuf -i 1-$k -n 1` for st in $stuns; do if [[ $x == $z ]]; then stun=$st; fi; (( x++ )); done echo "$(date)      STUN : $stun" sleep 5 && for pid in $(ps xa | grep "stun "$stun" 1 -p "$localport" -v" | grep -v grep | awk '{print $1}'); do kill $pid; done & mydata=`stun "$stun" 1 -p "$localport" -v 2>&1 | grep "MappedAddress" | sort | uniq` done echo -e "$(date) ${GREEN}  : $mydata${END}" echo "$(date)    ." echo "$mydata" > "$DIR/mydata" echo "IntIP $intip" >> "$DIR/mydata" curl -T "$DIR/mydata" --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$name-ipport.txt rm "$DIR/mydata" sleep 5 echo "$(date)     " filename=$(curl --silent --user "${username}:${password}" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/vpn-$vpn/ | sed 's/></\n/g' | grep "d:displayname>" | grep "ipport" | grep -v "$name" | sed 's|.*d:displayname>||' | sed 's/</ /g' | awk '{print $1}') if [[ -n "$filename" ]] then echo "$(date)     : $filename" address=$(curl --silent --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$filename | grep "MappedAddress" | head -n1 | sed 's/:/ /g') intip2=$(curl --silent --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/$filename | grep "IntIP" | head -n1 | awk '{print $2}') echo "$(date)  IP-  : $address $sesid2 $tunid2" ip=$(echo "$address" | awk '{print $3}') port=$(echo "$address" | awk '{print $4}') myip=`ip route get "$ip" | head -n 1 | sed 's|.*src ||' | awk '{print $1}'` if [[ -n "$ip" && -n "$port" && -n "$myip" && -n "$localport" ]]; then echo -e "$(date) ${GREEN}  $ip $port ${END}" echo -e "`date` ${GREEN} $myip:$localport -> $ip:$port ${END}" curl --silent --user "$username:$password" https://webdav.yandex.ru/vpn-$vpn/key.enc > "$DIR/secret.enc" openssl AES-256-CBC -d -in "$DIR/secret.enc" -out "$key" -k "$passwd" -base64 chmod 600 "$key" rm "$DIR/secret.enc" openvpn --remote $ip --rport $port --lport $localport \ --proto udp --dev tun --float --auth-nocache --verb 3 --mute 20 \ --ifconfig "$intip" "$intip2" \ --secret "$key" \ --auth SHA256 --cipher AES-256-CBC \ --ncp-disable --ping 10 --ping-exit 20 \ --comp-lzo yes a=1 b='' fi else if (( $ex >= 5 )) then echo "$(date) " a=1 b='' fi (( ex++ )) sleep 5 fi done done 

Para ejecutar el script necesitas:
  1. Copie en el portapapeles y p茅guelo en el editor, por ejemplo:
     # nano vpn10.sh 
  2. especifique inicio de sesi贸n (segunda l铆nea) y contrase帽a de Yandex.Disk (tercera l铆nea).
  3. especifique la direcci贸n IP interna del t煤nel (cuarta l铆nea).
  4. hacer que el script sea ejecutable:
     # chmod +x vpn10.sh 
  5. ejecuta el script:
     # ./vpn10.sh nZbVGBuX5dtturD 

    donde nZbVGBuX5dtturD es la ID de conexi贸n generada aqu铆

En el nodo remoto, haga lo mismo, especifique la direcci贸n IP interna correspondiente del t煤nel y la conexi贸n de ID.

Para iniciar el script al inicio, uso el comando "nohup / <ruta al script> /vpn10.sh nZbVGBuX5dtturD> /var/log/vpn10.log 2> / dev / null &" contenido en el archivo /etc/rc.local

Conclusi贸n


El script funciona, probado en Ubuntu 18.04 y Debian 9. Puede usar cualquier otro servicio como transmisor, pero por la experiencia utilic茅 Yandex.Disk.
En el curso de los experimentos, se descubri贸 que algunos tipos de proveedores de NAT no permiten una conexi贸n. Principalmente con operadores m贸viles donde los torrents est谩n bloqueados.

Planeo finalizar en t茅rminos de:
  • La generaci贸n autom谩tica de secret.key cada vez que inicie, cifre y copie a Yandex.Disk para transferir a un host remoto (tomado en cuenta en la versi贸n actualizada)
  • Asignar autom谩ticamente direcciones IP de interfaz
  • Cifrado de datos antes de subir a Yandex.Disk
  • Optimizaci贸n de c贸digo

隆Que haya IPv6 en cada hogar!

Source: https://habr.com/ru/post/481034/


All Articles