ProHoster > בלוג > מינהל > מנהרת VPN ישירה בין מחשבים באמצעות NAT של ספק (ללא VPS, באמצעות שרת STUN ו-Yandex.disk)
מנהרת VPN ישירה בין מחשבים באמצעות NAT של ספק (ללא VPS, באמצעות שרת STUN ו-Yandex.disk)
הארכה מאמרים על איך הצלחתי לארגן מנהרת VPN ישירה בין שני מחשבים הממוקמים מאחורי ספקי NAT. המאמר הקודם תיאר את תהליך ארגון החיבור בעזרת צד שלישי - מתווך (VPS שכור הפועל כמשהו כמו שרת STUN ומשדר נתוני צומת לחיבור). במאמר זה אספר לכם איך הסתדרתי בלי VPS, אבל המתווכים נשארו והם היו שרת STUN ו-Yandex.Disk...
מבוא
לאחר קריאת התגובות של הפוסט הקודם, הבנתי שהחיסרון העיקרי ביישום הוא השימוש במתווך – צד שלישי (VPS) שציין את הפרמטרים העדכניים של הצומת, היכן וכיצד להתחבר. בהתחשב בהמלצות להשתמש ב-STUN זה (מהם יש הרבה) כדי לקבוע את פרמטרי החיבור הנוכחיים. קודם כל, החלטתי להשתמש בTCPDump כדי להסתכל על תוכן החבילות כאשר שרת STUN עבד עם לקוחות וקיבל תוכן בלתי קריא לחלוטין. בגוגל את הפרוטוקול שנתקלתי בו מאמר המתאר את הפרוטוקול. הבנתי שאני לא יכול ליישם בקשה לשרת STUN לבד ושמתי את הרעיון ב"קופסה רחוקה".
Теория
לאחרונה הייתי צריך להתקין שרת STUN על Debian מהחבילה
# apt install stun-server
ובתלות ראיתי את חבילת ההמם-קליינט, אבל איכשהו לא שמתי לב לזה. אבל מאוחר יותר נזכרתי בחבילת ההמם-לקוח והחלטתי להבין איך זה עובד, לאחר שגוגלתי וחיפושי ב-Yandex קיבלתי:
גרסת לקוח STUN 0.97
פתח יציאה 21234 עם fd 3
פתח יציאה 21235 עם fd 4
קידוד הודעת הלם:
קידוד ChangeRequest: 0
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 4
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 2
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
הודעת הלם שהתקבלה: 92 בתים
MappedAddress = <ה-IP שלי>:2885
כתובת מקור = 216.93.246.18:3478
ChangedAddress = 216.93.246.17:3479
תכונה לא ידועה: 32800
שם שרת = Vovida.org 0.98-CPC
התקבלה הודעה מסוג 257 id=1
קידוד הודעת הלם:
קידוד ChangeRequest: 0
עומד לשלוח הודעת len 28 ל-216.93.246.17:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 4
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 2
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 0
עומד לשלוח הודעת len 28 ל<My IP>:2885
הודעת הלם שהתקבלה: 28 בתים
ChangeRequest = 0
התקבלה הודעה מסוג 1 id=11
קידוד הודעת הלם:
קידוד ChangeRequest: 0
עומד לשלוח הודעת len 28 ל-216.93.246.17:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 4
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 2
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
הודעת הלם שהתקבלה: 92 בתים
MappedAddress = <ה-IP שלי>:2885
כתובת מקור = 216.93.246.17:3479
ChangedAddress = 216.93.246.18:3478
תכונה לא ידועה: 32800
שם שרת = Vovida.org 0.98-CPC
התקבלה הודעה מסוג 257 id=10
קידוד הודעת הלם:
קידוד ChangeRequest: 4
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 2
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 4
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 2
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 4
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 2
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 4
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 2
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 4
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
קידוד הודעת הלם:
קידוד ChangeRequest: 2
עומד לשלוח הודעת len 28 ל-216.93.246.18:3478
מבחן I = 1
מבחן II = 0
מבחן III = 0
מבחן I(2) = 1
הוא נאט = 1
IP ממופה זהה = 1
סיכת ראש = 1
יציאת משמר = 0
ראשי: מיפוי עצמאי, מסנן תלוי יציאות, יציאה אקראית, יסגור ראש
ערך ההחזרה הוא 0x000006
מחרוזת עם ערך
MappedAddress = <ה-IP שלי>:2885
בדיוק מה שאתה צריך! הוא הציג את הסטטוס הנוכחי של החיבור ביציאת UDP מקומית 21234. אבל זה רק חצי מהקרב; עלתה השאלה איך להעביר את הנתונים האלה למארח המרוחק ולארגן חיבור VPN. שימוש בפרוטוקול הדואר, או אולי בטלגרם?! יש הרבה אפשרויות והחלטתי להשתמש ב-Yandex.disk, מאז שנתקלתי מאמר על עבודת Curl באמצעות WebDav עם Yandex.disk. לאחר שחשבתי על היישום, הגעתי לתכנית הבאה:
אות שצמתים מוכנים ליצור חיבור על ידי נוכחות של קובץ ספציפי עם חותמת זמן על Yandex.disk;
אם הצמתים מוכנים, קבל פרמטרים נוכחיים משרת STUN;
העלה את ההגדרות הנוכחיות אל Yandex.disk;
בדוק את הנוכחות וקריאה של פרמטרים של צומת מרוחק מקובץ ב- Yandex.disk;
יצירת חיבור עם מארח מרוחק באמצעות 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
ציין את שם המשתמש והסיסמה עבור Yandex.disk.
בשדה "—ifconfig 10.45.54.(1 או 2) 255.255.255.252" ציין את כתובת ה-IP הפנימית של הממשק
בצומת המרוחק, עשה את אותו הדבר, ציין את כתובת ה-IP הפנימית המתאימה של המנהרה ומזהה החיבור.
כדי להפעיל אוטומטית את הסקריפט כשהוא מופעל, אני משתמש בפקודה "nohup /<path to the script>/vpn10.sh nZbVGBuX5dtturD > /var/log/vpn10.log 2>/dev/null &" הכלולה בקובץ /etc/ rc.local
מסקנה
הסקריפט עובד, נבדק על אובונטו (18.04, 19.10, 20.04) ודביאן 9. אתה יכול להשתמש בכל שירות אחר בתור משדר, אבל בשביל הניסיון השתמשתי ב- Yandex.disk.
במהלך הניסויים, התגלה שחלק מסוגי ספקי NAT אינם מאפשרים יצירת חיבור. בעיקר ממפעילי סלולר שבהם הטורנטים חסומים.
אני מתכנן להשתפר מבחינת:
יצירה אוטומטית של secret.key בכל פעם שאתה מפעיל, הצפנה והעתקה ל-Yandex.disk להעברה לצומת מרוחק (בהתחשב בגרסה המעודכנת)
הקצאה אוטומטית של כתובות IP של ממשקים
הצפנת נתונים לפני העלאה ל-Yandex.disk
אופטימיזציה של קוד
שיהיה IPv6 בכל בית!
מְעוּדכָּן! הקבצים האחרונים וחבילת DEB כאן - yandex.disk