Продужетак Чланак о томе како сам успео да организујем директан ВПН- тунел између два рачунара која се налазе иза NAT-ова провајдера. У претходном чланку је описан процес успостављања везе коришћењем треће стране - посредника (изнајмљеног ВПС (Ово делује као нека врста STUN сервера и предајника података за повезивање чворова.) У овом чланку ћу вам рећи како сам се снашао без VPS-а, али су посредници остали: STUN сервер и Yandex.Disk.
Увод
Након што сам прочитао коментаре претходног поста, схватио сам да је главни недостатак имплементације био коришћење посредника - треће стране (ВПС) који је указивао на тренутне параметре чвора, где и како да се повеже. Узимајући у обзир препоруке за коришћење овог СТУН (којих има доста) за одређивање тренутних параметара везе. Пре свега, одлучио сам да користим ТЦПДумп да погледам садржај пакета када СТУН сервер ради са клијентима и добија потпуно нечитљив садржај. Гуглајући протокол на који сам наишао чланак који описује протокол. Схватио сам да не могу сам да имплементирам захтев на СТУН сервер и ставим идеју у „удаљену кутију“.
Теорија
Недавно сам морао да инсталирам STUN сервер на Debian из пакета
# apt install stun-serverа у зависностима сам видео пакет стун-цлиент, али некако нисам обраћао пажњу на то. Али касније сам се сетио пакета стун-цлиент и одлучио да схватим како функционише, након гуглања и претраживања у Иандек-у, добио сам:
# apt install stun-client
# stun stun.ekiga.net -p 21234 -v
Као одговор добио сам:
СТУН клијент верзија 0.97
Отворен порт 21234 са фд 3
Отворен порт 21235 са фд 4
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 0Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 4Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 2Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Примљена омамљујућа порука: 92 бајта
Мапирана адреса = <Мој ИП>:2885
СоурцеАддресс = 216.93.246.18:3478
Промењена Адреса = 216.93.246.17:3479
Непознати атрибут: 32800
Име сервера = Вовида.орг 0.98-ЦПЦ
Примљена порука типа 257 ид=1
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 0Спрема се слање поруке од лен 28 на 216.93.246.17:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 4Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 2Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 0Спрема се слање поруке од лен 28 на <Мој ИП>:2885
Примљена омамљујућа порука: 28 бајта
Захтев за промену = 0
Примљена порука типа 1 ид=11
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 0Спрема се слање поруке од лен 28 на 216.93.246.17:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 4Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 2Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Примљена омамљујућа порука: 92 бајта
Мапирана адреса = <Мој ИП>:2885
СоурцеАддресс = 216.93.246.17:3479
Промењена Адреса = 216.93.246.18:3478
Непознати атрибут: 32800
Име сервера = Вовида.орг 0.98-ЦПЦ
Примљена порука типа 257 ид=10
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 4Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 2Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 4Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 2Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 4Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 2Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 4Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 2Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 4Спрема се слање поруке од лен 28 на 216.93.246.18:3478
Кодирање поруке запрепашћења:
Захтев за промену кодирања: 2Спрема се слање поруке од лен 28 на 216.93.246.18:3478
тест И = 1
тест ИИ = 0
тест ИИИ = 0
тест И(2) = 1
је нат = 1
мапирани ИП исти = 1
укосница = 1
порт за чување = 0
Примарни: Независно мапирање, филтер зависан од порта, насумични порт, укосница
Повратна вредност је 0к000006
Стринг са вредношћу
Мапирана адреса = <Мој ИП>:2885
само оно што вам треба! Приказао је тренутни статус везе на локалном УДП порту 21234. Али ово је само пола битке, поставило се питање како пренети ове податке на удаљени хост и организовати ВПН везу. Користећи маил протокол, или можда Телеграм?! Постоји много опција и одлучио сам да користим Иандек.диск, пошто сам наишао чланак о раду Цурл преко ВебДав-а са Иандек.диск. Након размишљања о имплементацији, дошао сам до следеће шеме:
- Сигнализирајте да су чворови спремни да успоставе везу присуством одређене датотеке са временском ознаком на Иандек.диск;
- Ако су чворови спремни, примајте тренутне параметре са СТУН сервера;
- Отпремите тренутна подешавања на Иандек.диск;
- Проверите присуство и прочитајте параметре удаљеног чвора из датотеке на Иандек.диск;
- Успостављање везе са удаљеним хостом помоћу OpenVPN.
Пракса
После малог размишљања, узимајући у обзир искуство из прошлог чланка, брзо сам написао сценарио. Ми требамо:
# apt install openvpn stun-client curl Сама скрипта:
оригинална верзија
# 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Да би скрипта функционисала потребно вам је:
- Копирајте у међуспремник и налепите у уређивач, на пример:
# nano vpn8.sh - наведите корисничко име и лозинку за Иандек.диск.
- У пољу „—ifconfig 10.45.54.(1 или 2) 255.255.255.252“ наведите интерну IP адресу интерфејса
- Креирај тајни кључ по команди:
# openvpn --genkey --secret secret.key - учините скрипту извршном:
# chmod +x vpn8.sh - покрените скрипту:
# ./vpn8.sh nZbVGBuX5dtturDгде је нЗбВГБуКс5дттурД генерисани ИД везе овде
На удаљеном чвору урадите све исто осим генерисања сецрет.кеи-а и ИД-а везе, они морају бити идентични.
Ажурирана верзија (време мора бити синхронизовано за исправан рад):
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
Да би скрипта функционисала потребно вам је:
- Копирајте у међуспремник и налепите у уређивач, на пример:
# nano vpn10.sh - наведите пријаву (2. ред) и лозинку за Иандек.диск (3. ред).
- наведите интерну ИП адресу тунела (4. ред).
- учините скрипту извршном:
# chmod +x vpn10.sh - покрените скрипту:
# ./vpn10.sh nZbVGBuX5dtturDгде је нЗбВГБуКс5дттурД генерисани ИД везе овде
На удаљеном чвору урадите исто, наведите одговарајући интерни ИП адреса ИД тунела и везе.
За аутоматско покретање скрипте када је укључена, користим команду „нохуп /<пут до скрипте>/впн10.сх нЗбВГБуКс5дттурД > /вар/лог/впн10.лог 2>/дев/нулл &” која се налази у датотеци /етц/ рц.лоцал
Закључак
Скрипта ради, тестирана је Ubuntu (18.04, 19.10, 20.04) и Debian 9. Можете користити било коју другу услугу као предајник, али за експеримент сам користио Јандекс.Диск.
Током експеримената је откривено да неки типови НАТ провајдера не дозвољавају успостављање везе. Углавном од мобилних оператера код којих су торенти блокирани.
Планирам да се побољшам у смислу:
- Аутоматско генерисање сецрет.кеи сваки пут када покренете, шифрујте и копирајте на Иандек.диск за пренос на удаљени чвор (узимајући у обзир у ажурираној верзији)
- Аутоматско додељивање ИП адреса интерфејсима
- Шифровање података пре отпремања на Иандек.диск
- Оптимизација кода
Нека ИПв6 буде у сваком дому!
Ажуриран! Најновији фајлови и ДЕБ пакет овде - иандек.диск
Извор: ввв.хабр.цом
