Jätkub artikleid kuidas mul õnnestus korraldada otsene VPN-tunnel kahe vahelise arvuti, mis asuvad teenusepakkujate NAT-ide taga. Eelmisel artiklil kirjeldati kolmanda osapoole kaudu ühenduse loomise protsessi — vahelase (üüritud VPS mille rolliks on midagi nagu STUN-server ja andmeülekande sõlmed. Selles artiklis räägin, kuidas sain hakkama ilma VPS-ta, kuid vahelased jäid ja neid olid STUN-server ja Yandex.Disk...
Sissejuhatus
Eelmise postituse kommentaare lugedes mõistsin, et rakenduse peamine puudus oli vahelase - kolmanda osapoole (VPS) kasutamine, mis osutas praegused ühenduse parameetrid, kuhu ja kuidas ühenduda. Arvestades soovitusi kasutada tõelist STUN (millel on üpris palju) ühenduse praeguste parameetrite määramiseks. Esimene asi, mida ma tegin, oli vaadata TCPDump abil STUN-serveri ja klientide vahel liikuvate pakettide sisu ning sain täiesti loetamatut sisu. Otsides protokolli, sattusin artiklile protokolli kirjeldamisega. Mõistsin, et ma ei saa iseseisvalt STUN-serveriga päringut teostada ja panin selle mõtte "kaugesse sahtlisse".
Teooria
Hiljuti pidin Debianis STUN-serverit installima ja pakettide seas märkasin paketti stun-client, kuid ei pööranud sellele palju tähelepanu. Hiljem meenutasin stun-client paketti ja otsustasin aru saada, kuidas see töötab, otsides Google'ist ja Yandex'ist, sain:
# apt install stun-serverVastuseks sain:
# apt install stun-client
# stun stun.ekiga.net -p 21234 -v
STUN client version 0.97
Avatud port 21234 fd-ga 3
Avatud port 21235 fd-ga 4
Kodeerimine stun-sõnum:
Kodeerimine ChangeRequest: 0
Saatmine msg pikkusega 28 aadressile 216.93.246.18:3478Kodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Kodeerimine ChangeRequest: 2Kodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Sain stun-sõnumi: 92 baitiKodeerimine ChangeRequest: 4
MappedAddress = :2885
SourceAddress = 216.93.246.18:3478
ChangedAddress = 216.93.246.17:3479
Tundmatu atribuut: 32800
ServerName = Vovida.org 0.98-CPC
Sain sõnumi tüübiga 257 id=1
Saatmine msg pikkusega 28 aadressile 216.93.246.17:3478
Kodeerimine ChangeRequest: 0
Saatmine msg pikkusega 28 aadressile 216.93.246.18:3478Saatmine msg pikkusega 28 aadressile :2885
Kodeerimine ChangeRequest: 0
Kodeerimine ChangeRequest: 2Kodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Sain stun-sõnumi: 92 baitiKodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Saatmine msg pikkusega 28 aadressile 216.93.246.18:3478Sain stun-sõnumi: 28 baiti
ChangeRequest = 0
Sain sõnumi tüübiga 1 id=11
SourceAddress = 216.93.246.17:3479
Kodeerimine ChangeRequest: 0
Saatmine msg pikkusega 28 aadressile 216.93.246.18:3478Saatmine msg pikkusega 28 aadressile :2885
Kodeerimine ChangeRequest: 0
Kodeerimine ChangeRequest: 2Kodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Sain stun-sõnumi: 92 baitiKodeerimine ChangeRequest: 4
MappedAddress = :2885
SourceAddress = 216.93.246.18:3478
ChangedAddress = 216.93.246.18:3478
Sain sõnumi tüübiga 257 id=10
ServerName = Vovida.org 0.98-CPC
Sain sõnumi tüübiga 257 id=1
test I = 1
Kodeerimine ChangeRequest: 0
Kodeerimine ChangeRequest: 2Kodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Sain stun-sõnumi: 92 baitiKodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Kodeerimine ChangeRequest: 2Kodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Sain stun-sõnumi: 92 baitiKodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Kodeerimine ChangeRequest: 2Kodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Sain stun-sõnumi: 92 baitiKodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Kodeerimine ChangeRequest: 2Kodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Sain stun-sõnumi: 92 baitiKodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Kodeerimine ChangeRequest: 2Kodeerimine ChangeRequest: 4
Kodeerimine ChangeRequest: 0
Sain stun-sõnumi: 92 baitiKodeerimine ChangeRequest: 4
test II = 0
test III = 0
test I(2) = 1
kas NAT = 1
kaardistatud IP sama = 1
hairpin = 1
porti säilitamine = 0
Põhiline: Iseseisev kaardistamine, Port sõltuv filter, juhuslik port, hakkab hairpin
Tagastatav väärtus on 0x000006
Rea väärtus
String väärtusega
SourceAddress = 216.93.246.18:3478
just what I needed! It displayed the current status for the connection on local UDP port 21234. But that's only half the task; the question arose on how to transmit this data to a remote node and establish a VPN connection. Using the mail protocol, or maybe Telegram?! There are many options, and I decided to use Yandex.Disk, as it came up for me. an article on working with Curl through WebDav with Yandex.Disk. After pondering the implementation, I came up with the following scheme:
- Signal the readiness of nodes to establish a connection by the presence of a specific file with a timestamp on Yandex.Disk;
- If the nodes are ready, then retrieve the current parameters from the STUN server;
- Upload the current parameters to Yandex.Disk;
- Check for the existence and read the parameters of the remote node from the file on Yandex.Disk;
- Establish a connection with the remote node using OpenVPN.
Praktika
After some thought, considering the experience from the previous article, I quickly wrote a script. We will need:
# apt install openvpn stun-client curl The script itself:
Initial variant
# cat vpn8.sh
#!/bin/bash
######################## Задаем цветной текст ###
WARN='33[37;1;41m' #
END='33[0m' #
RED='33[0;31m' # ${RED} #
GREEN='33[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
doneTo run the script, you need to:
- Copy to the clipboard and paste into an editor, for example:
# nano vpn8.sh - specify the login and password for Yandex.Disk.
- in the field "--ifconfig 10.45.54.(1 or 2) 255.255.255.252" indicate the internal IP address of the interface
- create secret.key with the command:
# openvpn --genkey --secret secret.key - make the script executable:
# chmod +x vpn8.sh - run the script:
# ./vpn8.sh nZbVGBuX5dtturDwhere nZbVGBuX5dtturD is the connection ID generated siit
On the remote node, perform the same actions except for generating secret.key and the connection ID, they must be identical.
Updated version (for correct operation, the time must be synchronized):
cat vpn10.sh
#!/bin/bash
stuns="stun.sipnet.ru stun.ekiga.net" # Список STUN серверов через пробел
username=" Login " # Логин от Яндекс.диска
password=" Password " # Пароль от Яндекс.диска
intip="10.23.22.1" # IP-адрес внутреннего интерфейса
WARN='33[37;1;41m'
END='33[0m'
RED='33[0;31m'
GREEN='33[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
To run the script, you need to:
- Copy to the clipboard and paste into an editor, for example:
# nano vpn10.sh - specify the login (2nd line) and password for Yandex.Disk (3rd line).
- specify the internal IP address of the tunnel (4th line).
- make the script executable:
# chmod +x vpn10.sh - run the script:
# ./vpn10.sh nZbVGBuX5dtturDwhere nZbVGBuX5dtturD is the connection ID generated siit
On the remote node, perform the same actions, specifying the corresponding internal IP-aadress tunnel and connection ID.
For auto-starting the script on boot, I use the command "nohup //vpn10.sh nZbVGBuX5dtturD > /var/log/vpn10.log 2>/dev/null &" contained in the file /etc/rc.local
Kokkuvõte
The script works, tested on Ubuntu (18.04, 19.10, 20.04) and Debian 9. Any other service can be used as a transmitter, but for the experience, I used Yandex.Disk.
During experiments, it was found that some types of NAT providers do not allow establishing a connection. Mainly with cellular operators where torrents are blocked.
I plan to refine in terms of:
- Automaatne secret.key genereerimine iga kord käivitamisel, krüpteerimine ja kopeerimine Yandex.diskile edastamiseks eemalolevale sõlmele (arvestatud uuendatud versioonis)
- Automaatne IP-aadresside määramine liideste jaoks
- Andmete krüpteerimine enne Yandex.diskile üleslaadimist
- Koodi optimeerimine
Olgu IPv6 igas kodus!
Uuendatud! Viimased failid ja DEB-pakett siin — yandex.disk
Allikas: habr.com
