ProHoster > Blog > Amministrazione > Tunnel VPN direttu trà i computer attraversu NAT di u fornitore (senza VPS, utilizendu u servitore STUN è Yandex.disk)
Tunnel VPN direttu trà i computer attraversu NAT di u fornitore (senza VPS, utilizendu u servitore STUN è Yandex.disk)
Continuazione articuli circa cumu aghju sappiutu urganizà un tunnel VPN direttu trà dui computer situati daretu à i fornituri NAT. L'articulu precedente hà descrittu u prucessu di urganizà una cunnessione cù l'aiutu di un terzu - un intermediariu (un VPS affittu chì agisce cum'è qualcosa cum'è un servitore STUN è un trasmettitore di dati di node per a cunnessione). In questu articulu vi dicu cumu aghju gestitu senza VPS, ma l'intermediarii sò stati è eranu u servitore STUN è Yandex.Disk ...
Introduzione
Dopu avè lettu i cumenti di u post precedente, aghju realizatu chì u principal inconveniente di l'implementazione era l'usu di un intermediariu - un terzu (VPS) chì indicava i paràmetri attuali di u node, induve è cumu cunnette. Cunsiderendu i cunsiglii per aduprà stu STUN (di quale ci sò assai) per determinà i paràmetri di cunnessione attuale. Prima di tuttu, aghju decisu d'utilizà TPCDump per fighjà u cuntenutu di i pacchetti quandu u servitore STUN travagliava cù i clienti è riceve un cuntenutu completamente illegibile. Google u protocolu ch'e aghju scontru articulu chì descrive u protocolu. Aghju realizatu chì ùn pudia micca implementà una dumanda à u servore STUN per mè stessu è mette l'idea in una "scatola distante".
Teoria
Ricertamenti aghju avutu à stallà u servitore STUN in Debian da u pacchettu
# apt install stun-server
è in i dependenzii aghju vistu u pacchettu stun-client, ma in qualchì manera ùn aghju micca attentu à questu. Ma dopu mi ricurdava di u pacchettu stun-client è decisu di capisce cumu funziona, dopu avè cercatu in Google è in Yandex aghju avutu:
Versione client STUN 0.97
Apertu u portu 21234 cù fd 3
Apertu u portu 21235 cù fd 4
Encoding stun message:
Encoding ChangeRequest: 0
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 4
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Missaghju stunatu ricevutu: 92 bytes
MappedAddress = <U mo IP>: 2885
SourceAddress = 216.93.246.18:3478
Indirizzu cambiatu = 216.93.246.17:3479
Attributu scunnisciutu: 32800
ServerName = Vovida.org 0.98-CPC
Missaghju ricevutu di tipu 257 id=1
Encoding stun message:
Encoding ChangeRequest: 0
A propositu di mandà msg di len 28 à 216.93.246.17:3478
Encoding stun message:
Encoding ChangeRequest: 4
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 0
Circa à mandà msg di len 28 à <My IP>: 2885
Missaghju stunatu ricevutu: 28 bytes
ChangeRequest = 0
Missaghju ricevutu di tipu 1 id=11
Encoding stun message:
Encoding ChangeRequest: 0
A propositu di mandà msg di len 28 à 216.93.246.17:3478
Encoding stun message:
Encoding ChangeRequest: 4
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Missaghju stunatu ricevutu: 92 bytes
MappedAddress = <U mo IP>: 2885
SourceAddress = 216.93.246.17:3479
Indirizzu cambiatu = 216.93.246.18:3478
Attributu scunnisciutu: 32800
ServerName = Vovida.org 0.98-CPC
Missaghju ricevutu di tipu 257 id=10
Encoding stun message:
Encoding ChangeRequest: 4
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 4
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 4
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 4
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 4
A propositu di mandà msg di len 28 à 216.93.246.18:3478
Encoding stun message:
Encoding ChangeRequest: 2
A propositu di mandà msg di len 28 à 216.93.246.18:3478
test I = 1
test II = 0
prova III = 0
test I(2) = 1
hè nat = 1
IP mappatu u stessu = 1
forcina = 1
portu di preservazione = 0
Primariu: Mappatura Indipendente, Filtru Dipendente di Portu, portu aleatoriu, sarà hairpin
U valore di ritornu hè 0x000006
Stringa cù valore
MappedAddress = <U mo IP>: 2885
ghjustu ciò chì avete bisognu! Hè mostratu u statu attuale per a cunnessione nantu à u portu UDP locale 21234. Ma questu hè solu a mità di a battaglia, a quistione hè stata di cumu trasfirià sta dati à l'ospiti remota è urganizà una cunnessione VPN. Aduprà u protocolu di mail, o forse Telegram ?! Ci hè parechje scelte è aghju decisu di utilizà Yandex.disk, postu chì aghju scontru articulu circa u travagliu Curl via WebDav cù Yandex.disk. Dopu avè pensatu à l'implementazione, aghju fattu u schema seguente:
Segnale chì i nodi sò pronti per stabilisce una cunnessione da a presenza di un schedariu specificu cù un timestamp in Yandex.disk;
Se i nodi sò pronti, allora riceve i paràmetri attuali da u servore STUN;
Caricate i paràmetri attuali à Yandex.disk;
Verificate a presenza è leghje i paràmetri di un node remotu da un schedariu in Yandex.disk;
Stabbilimentu di una cunnessione cù un host remotu cù OpenVPN.
Prutizzioni
Dopu avè pensatu un pocu, tenendu in contu l'esperienza di l'ultimu articulu, aghju scrittu rapidamente un script. Avemu bisognu di:
# apt install openvpn stun-client curl
U script stessu:
Versione iniziale
# 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
done
Per u script per travaglià avete bisognu:
Copia in u clipboard è incollà in l'editore, per esempiu:
# nano vpn8.sh
specifica u nome d'utilizatore è a password per Yandex.disk.
in u campu "—ifconfig 10.45.54.(1 o 2) 255.255.255.252" specifica l'indirizzu IP internu di l'interfaccia
crià sicretu.chiavi cumanda:
# openvpn --genkey --secret secret.key
rende u script eseguibile:
# chmod +x vpn8.sh
eseguite u script:
# ./vpn8.sh nZbVGBuX5dtturD
induve nZbVGBuX5dtturD hè l'ID di cunnessione generatu ccà
Nantu à u node remotu, fate tuttu u listessu, eccettu per generà secret.key è ID di cunnessione, devenu esse identica.
Versione aghjurnata (u tempu deve esse sincronizatu per u funziunamentu currettu):
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
Per u script per travaglià avete bisognu:
Copia in u clipboard è incollà in l'editore, per esempiu:
# nano vpn10.sh
indicà u login (2nd line) è password per Yandex.disk (3rd line).
specifica l'indirizzu IP internu di u tunnel (4a linea).
rende u script eseguibile:
# chmod +x vpn10.sh
eseguite u script:
# ./vpn10.sh nZbVGBuX5dtturD
induve nZbVGBuX5dtturD hè l'ID di cunnessione generatu ccà
Nantu à u node remotu, fate u listessu, specificate l'indirizzu IP internu currispundente di u tunnel è l'ID di cunnessione.
Per autorun u script quandu hè attivatu, aghju utilizatu u cumandimu "nohup /<path to the script>/vpn10.sh nZbVGBuX5dtturD> /var/log/vpn10.log 2>/dev/null &" cuntenutu in u schedariu /etc/ rc.local
cunchiusioni
U travagliu di scrittura, pruvatu in Ubuntu (18.04, 19.10, 20.04) è Debian 9. Pudete utilizà qualsiasi altru serviziu cum'è trasmettitore, ma per l'esperienza aghju utilizatu Yandex.disk.
Duranti l'esperimenti, hè statu scupertu chì certi tipi di fornituri NAT ùn permettenu micca stabilisce una cunnessione. Principalmente da l'operatori mobili induve i torrenti sò bluccati.
Pensu di migliurà in termini di:
Generazione automatica di secret.key ogni volta chì cuminciate, criptate è copiate in Yandex.disk per u trasferimentu à un node remotu (Purtendu in contu in a versione aghjurnata)
Assegnazione automatica di l'indirizzi IP di l'interfaccia
Cifrare i dati prima di caricare in Yandex.disk
Ottimisazione di u codice
Chì ci sia IPv6 in ogni casa!
Aghjurnatu! Ultimi fugliali è pacchettu DEB quì - yandex.disk