Direkter VPN-Tunnel zwischen Computern über NAT-Anbieter (ohne VPS, mit einem STUN-Server und Yandex.Disk)

Die Fortsetzung des Artikels darüber, wie es mir gelungen ist, einen direkten VPN-Tunnel zwischen zwei Computern zu organisieren, die sich hinter NAT-Anbietern befinden. Der letzte Artikel beschrieb den Prozess des Organisierens einer Verbindung unter Verwendung eines Dritten - eines Vermittlers (eines gemieteten VPS, der wie ein STUN-Server und ein Sender von Knotendaten für eine Verbindung fungiert). In diesem Artikel erkläre ich Ihnen, wie man ohne VPS auskommt, aber die Vermittler blieben und sie waren der STUN-Server und Yandex.Disk ...


Einleitung


Nachdem ich die Kommentare des letzten Posts gelesen hatte, stellte ich fest, dass der Hauptnachteil der Implementierung die Verwendung eines Vermittlers war - eines Drittanbieters (Third Party, VPS), der die aktuellen Parameter des Knotens angibt, wo und wie eine Verbindung hergestellt werden soll. Angesichts der Empfehlungen zur Verwendung dieser STUN (es gibt viele von ihnen ), um die aktuellen Verbindungsparameter zu bestimmen. Zunächst habe ich beschlossen, den Inhalt von Paketen mit TCPDump zu untersuchen, als der STUN-Server mit Clients arbeitete und vollständig unlesbaren Inhalt erhielt. Beim Googeln des Protokolls wurde ein Artikel gefunden, der das Protokoll beschreibt . Ich habe festgestellt, dass ich die Anforderung nicht selbst auf dem STUN-Server implementieren kann, und habe die Idee in das Feld "Weit" gestellt.

Theorie


Ich musste kürzlich einen STUN-Server unter Debian aus einem Paket installieren
# apt install stun-server 
und in den abhängigkeiten habe ich das stun-client paket gesehen, aber irgendwie hat das nichts damit zu tun. Aber später erinnerte ich mich an das Betäubungs-Client-Paket und beschloss herauszufinden, wie es funktioniert, indem ich googelte und Poindeksiv erhielt:

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

Als Antwort erhielt ich:

STUN Client Version 0.97
Port 21234 mit fd 3 geöffnet
Port 21235 mit fd 4 geöffnet
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 0

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 4

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 2

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Empfangene Betäubungsnachricht: 92 Bytes
MappedAddress = <Meine IP-Adresse>: 2885
SourceAddress = 216.93.246.18//478
ChangedAddress = 216.93.246.17lla479
Unbekanntes Attribut: 32800
Servername = Vovida.org 0.98-CPC
Erhaltene Nachricht vom Typ 257 id = 1
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 0

Kurz vor dem Senden der Nachricht von Len 28 an 216.93.246.17lla478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 4

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 2

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 0

Nachricht von len 28 an <My IP> senden: 2885
Empfangene Betäubungsnachricht: 28 Bytes
ChangeRequest = 0
Erhaltene Nachricht vom Typ 1 id = 11
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 0

Kurz vor dem Senden der Nachricht von Len 28 an 216.93.246.17lla478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 4

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 2

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Empfangene Betäubungsnachricht: 92 Bytes
MappedAddress = <Meine IP-Adresse>: 2885
SourceAddress = 216.93.246.17lla479
ChangedAddress = 216.93.246.18 {478
Unbekanntes Attribut: 32800
Servername = Vovida.org 0.98-CPC
Erhaltene Nachricht vom Typ 257 id = 10
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 4

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 2

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 4

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 2

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 4

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 2

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 4

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 2

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 4

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Codierung der Betäubungsnachricht:
Encoding ChangeRequest: 2

Über den Versand der Nachricht von len 28 an 216.93.246.18 opinion478
Test I = 1
Test II = 0
Test III = 0
Test I (2) = 1
ist nat = 1
zugeordnete IP gleich = 1
Haarnadel = 1
Preserver-Port = 0
Primär: Unabhängiges Mapping, Portabhängiger Filter, zufälliger Port, Haarnadelkurve
Der Rückgabewert ist 0x000006

String mit Wert
MappedAddress = <Meine IP-Adresse>: 2885

genau das, was du brauchst! Es wurde der aktuelle Status der Verbindung auf dem lokalen UDP-Port 21234 angezeigt. Dies ist jedoch nur die halbe Miete. Es stellte sich die Frage, wie diese Daten an einen Remote-Host übertragen und eine VPN-Verbindung hergestellt werden können. Verwenden Sie das Mail-Protokoll, vielleicht Telegramm?! Es gibt viele Möglichkeiten und ich habe mich für Yandex.Disk entschieden, da mir ein Artikel über die Funktionsweise von Curl über WebDav mit Yandex.Disk begegnet ist . Nachdem ich über die Implementierung nachgedacht hatte, kam ich zu diesem Schema:

  1. Zu signalisieren, dass die Knoten bereit sind, eine Verbindung herzustellen, indem eine bestimmte Datei mit einem Zeitstempel auf Yandex.disk vorhanden ist;
  2. Wenn die Knoten bereit sind, rufen Sie die aktuellen Parameter vom STUN-Server ab.
  3. Laden Sie die aktuellen Parameter auf Yandex.Disk hoch.
  4. Überprüfen Sie die Verfügbarkeit und lesen Sie die Parameter eines Remote-Standorts aus einer Datei auf Yandex.Disk.
  5. Stellen Sie mit OpenVPN eine Verbindung zu einem Remote-Host her.

Übe


Nach einigem Überlegen, unter Berücksichtigung der Erfahrungen des vorherigen Artikels, schrieb ich ein schnelles Skript. Wir werden brauchen:
 # apt install openvpn stun-client curl 

Eigentlich das Skript selbst:
Anfängliche Option
 # 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 

Um das Skript auszuführen, benötigen Sie:
  1. In die Zwischenablage kopieren und in den Editor einfügen, zum Beispiel:
     # nano vpn8.sh 
  2. Geben Sie den Benutzernamen und das Passwort von Yandex.Disk an.
  3. Geben Sie im Feld "--ifconfig 10.45.54. (1 oder 2) 255.255.255.252" die interne IP-Adresse der Schnittstelle an
  4. erstelle secret.key mit dem Befehl:
     # openvpn --genkey --secret secret.key 
  5. mache das Skript ausführbar:
     # chmod +x vpn8.sh 
  6. Führen Sie das Skript aus:
     # ./vpn8.sh nZbVGBuX5dtturD 

    Dabei ist nZbVGBuX5dtturD die hier generierte Verbindungs-ID

Führen Sie auf dem Remote-Knoten dasselbe aus, mit Ausnahme der Generierung von secret.key und der ID-Verbindung. Diese müssen identisch sein.

Aktualisierte Version (für einen korrekten Betrieb sollte die Uhrzeit synchronisiert werden):

 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 

Um das Skript auszuführen, benötigen Sie:
  1. In die Zwischenablage kopieren und in den Editor einfügen, zum Beispiel:
     # nano vpn10.sh 
  2. Geben Sie Login (2. Zeile) und Passwort von Yandex.Disk (3. Zeile) an.
  3. Geben Sie die interne IP-Adresse des Tunnels an (4. Zeile).
  4. mache das Skript ausführbar:
     # chmod +x vpn10.sh 
  5. Führen Sie das Skript aus:
     # ./vpn10.sh nZbVGBuX5dtturD 

    Dabei ist nZbVGBuX5dtturD die hier generierte Verbindungs-ID

Gehen Sie auf dem Remote-Knoten genauso vor und geben Sie die entsprechende interne IP-Adresse des Tunnels und der ID-Verbindung an.

Um das Skript beim Start zu starten, verwende ich den Befehl "nohup / <Pfad zum Skript> /vpn10.sh nZbVGBuX5dtturD> /var/log/vpn10.log 2> / dev / null &", der in der Datei /etc/rc.local enthalten ist

Fazit


Das Skript funktioniert, getestet auf Ubuntu 18.04 und Debian 9. Sie können jeden anderen Dienst als Sender verwenden, aber aus Erfahrung habe ich Yandex.Disk verwendet.
Im Verlauf der Experimente wurde festgestellt, dass einige Arten von NAT-Anbietern keine Verbindung zulassen. Meist bei Mobilfunkbetreibern, bei denen Torrents blockiert sind.

Ich plane den Abschluss in Bezug auf:
  • Die automatische Generierung von secret.key bei jedem Start, Verschlüsseln und Kopieren auf Yandex.Disk zur Übertragung auf einen Remote-Host (in der aktualisierten Version berücksichtigt)
  • Automatische Zuweisung von Schnittstellen-IP-Adressen
  • Datenverschlüsselung vor dem Hochladen auf Yandex.Disk
  • Code-Optimierung

Möge es IPv6 in jedem Zuhause geben!

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


All Articles