توجيه نفق VPN بين أجهزة الكمبيوتر من خلال موفري NAT (بدون VPS ، باستخدام خادم STUN و Yandex.Disk)

استمرار المقال حول كيف تمكنت من تنظيم نفق VPN مباشر بين جهازي كمبيوتر موجودين خلف موفري NAT. وصفت المقالة الأخيرة عملية تنظيم اتصال باستخدام جهة خارجية - وسيط (VPS مستأجر يعمل كشيء مثل خادم STUN وجهاز إرسال بيانات العقدة للاتصال). في هذه المقالة ، سوف أخبرك بكيفية الاستغناء عن VPS ، ولكن بقي الوسطاء وكانوا خادم STUN و Yandex.Disk ...


مقدمة


بعد قراءة تعليقات آخر مشاركة ، أدركت أن العيب الرئيسي للتنفيذ هو استخدام وسيط - طرف ثالث (VPS) أشار إلى المعلمات الحالية للعقدة حيث وكيفية الاتصال. نظرًا لتوصيات استخدام STUN (هناك الكثير منها ) لتحديد معلمات الاتصال الحالية. بادئ ذي بدء ، قررت إلقاء نظرة على محتويات الحزم باستخدام TCPDump عندما كان خادم STUN يعمل مع العملاء وتلقى محتوى غير قابل للقراءة تمامًا. غوغلينغ البروتوكول جاء عبر مقال يصف البروتوكول . أدركت أنه لا يمكنني تنفيذ الطلب على خادم STUN بنفسي ووضع الفكرة في "المربع البعيد".

نظرية


اضطررت مؤخرًا إلى تثبيت خادم STUN على دبيان من خلال حزمة
# apt install stun-server 
وفي التبعيات رأيت حزمة العميل الصاعقة ، ولكن بطريقة ما لم تعلق أي أهمية على هذا. لكن في وقت لاحق ، تذكرت عن حزمة العملاء الصاعقة وقررت أن أعرف كيف تعمل ، عن طريق googling و Poindeksiv التي تلقيتها:

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

استجابة لذلك ، تلقيت:

الإصدار STUN العميل 0.97
تم فتح المنفذ 21234 مع رقم 3
فتح منفذ 21235 مع 4 يوم
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 0

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 4

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 2

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
تلقى رسالة صاعقة: 92 بايت
MappedAddress = <My IP>: 2885
SourceAddress = 216.93.246.18//478
ChangedAddress = 216.93.246.17lla479
سمة غير معروفة: 32800
ServerName = Vovida.org 0.98-CPC
الرسالة المستلمة من النوع 257 معرف = 1
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 0

بصدد إرسال رسالة ms l of 28 إلى 216.93.246.17lla478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 4

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 2

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 0

بصدد إرسال رسالة ms l of 28 إلى <My IP>: 2885
تلقى رسالة صاعقة: 28 بايت
ChangeRequest = 0
رسالة مستلمة من النوع 1 معرف = 11
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 0

بصدد إرسال رسالة ms l of 28 إلى 216.93.246.17lla478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 4

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 2

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
تلقى رسالة صاعقة: 92 بايت
MappedAddress = <My IP>: 2885
SourceAddress = 216.93.246.17lla479
ChangedAddress = 216.93.246.18 {478
سمة غير معروفة: 32800
ServerName = Vovida.org 0.98-CPC
الرسالة المستلمة من النوع 257 معرف = 10
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 4

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 2

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 4

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 2

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 4

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 2

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 4

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 2

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 4

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
ترميز رسالة الصاعقة:
ترميز ChangeRequest: 2

بصدد إرسال msg of len 28 إلى 216.93.246.18 think478
اختبار I = 1
اختبار الثاني = 0
اختبار الثالث = 0
اختبار I (2) = 1
هو نات = 1
تعيين IP نفسه = 1
دبوس الشعر = 1
منفذ الحفظ = 0
الأساسي: رسم الخرائط المستقلة ، تصفية المنفذ التابع ، منفذ عشوائي ، سوف دبوس الشعر
قيمة الإرجاع هي 0x000006

سلسلة مع القيمة
MappedAddress = <My IP>: 2885

فقط ما تحتاجه! عرض الحالة الحالية للاتصال على منفذ UDP المحلي 21234. ولكن هذه ليست سوى نصف المعركة ، نشأ السؤال حول كيفية نقل هذه البيانات إلى مضيف بعيد وإنشاء اتصال VPN. باستخدام بروتوكول البريد ، ربما برقية؟! هناك العديد من الخيارات وقررت استخدام Yandex.Disk ، حيث صادفت مقالًا حول كيفية عمل Curl من خلال WebDav مع Yandex.Disk . بعد التفكير في التنفيذ ، جئت إلى هذا المخطط:

  1. للإشارة إلى أن العقد جاهزة لتأسيس اتصال عن طريق وجود ملف معين مع طابع زمني على Yandex.disk ؛
  2. إذا كانت العقد جاهزة ، فاحصل على المعلمات الحالية من خادم STUN ؛
  3. تحميل المعلمات الحالية إلى Yandex.Disk ؛
  4. تحقق من توفر وقراءة معلمات موقع بعيد من ملف على Yandex.Disk ؛
  5. تأسيس اتصال بمضيف بعيد باستخدام OpenVPN.

ممارسة


بعد قليل من التفكير ، مع مراعاة تجربة المقالة السابقة ، كتبت نصًا سريعًا. سنحتاج:
 # apt install openvpn stun-client curl 

في الواقع السيناريو نفسه:
الخيار الأولي
 # 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 

لتشغيل البرنامج النصي الذي تحتاجه:
  1. انسخ إلى الحافظة والصقها في المحرر ، على سبيل المثال:
     # nano vpn8.sh 
  2. تحديد اسم المستخدم وكلمة المرور من Yandex.Disk.
  3. في الحقل "--ifconfig 10.45.54. (1 أو 2) 255.255.255.252" حدد عنوان IP الداخلي للواجهة
  4. إنشاء secret.key باستخدام الأمر:
     # openvpn --genkey --secret secret.key 
  5. اجعل البرنامج قابل للتنفيذ:
     # chmod +x vpn8.sh 
  6. قم بتشغيل البرنامج النصي:
     # ./vpn8.sh nZbVGBuX5dtturD 

    حيث nZbVGBuX5dtturD هو معرف الاتصال الذي تم إنشاؤه هنا

على العقدة البعيدة ، قم بالشيء نفسه باستثناء إنشاء secret.key واتصال المعرّف ، يجب أن يكونا متطابقين.

نسخة محدثة (للتشغيل الصحيح ، يجب مزامنة الوقت):

 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 

لتشغيل البرنامج النصي الذي تحتاجه:
  1. انسخ إلى الحافظة والصقها في المحرر ، على سبيل المثال:
     # nano vpn10.sh 
  2. حدد تسجيل الدخول (السطر الثاني) وكلمة المرور من Yandex.Disk (السطر الثالث).
  3. تحديد عنوان IP الداخلي للنفق (الخط الرابع).
  4. اجعل البرنامج قابل للتنفيذ:
     # chmod +x vpn10.sh 
  5. قم بتشغيل البرنامج النصي:
     # ./vpn10.sh nZbVGBuX5dtturD 

    حيث nZbVGBuX5dtturD هو معرف الاتصال الذي تم إنشاؤه هنا

على العقدة البعيدة ، قم بالشيء نفسه ، وحدد عنوان IP الداخلي المقابل لاتصال النفق والمعرف.

لبدء البرنامج النصي عند بدء التشغيل ، استخدم الأمر "nohup / <path to script> /vpn10.sh nZbVGBuX5dtturD> /var/log/vpn10.log 2> / dev / null &" الموجود في الملف /etc/rc.local

استنتاج


يعمل البرنامج النصي ، الذي تم اختباره على Ubuntu 18.04 و Debian 9. ، يمكنك استخدام أي خدمة أخرى كجهاز إرسال ، ولكن للتجربة ، استخدمت Yandex.Disk.
في أثناء التجارب ، تبين أن بعض أنواع موفري NAT لا تسمح بالاتصال. في الغالب مع مشغلي شبكات الهاتف المحمول حيث يتم حظر السيول.

أخطط للانتهاء من حيث:
  • الجيل التلقائي من secret.key في كل مرة تبدأ ، تشفير ونسخ إلى Yandex.Disk لنقل إلى مضيف بعيد (تؤخذ في الاعتبار في الإصدار المحدث)
  • تعيين عناوين IP للواجهة تلقائيًا
  • تشفير البيانات قبل التحميل على Yandex.Disk
  • رمز التحسين

قد يكون هناك IPv6 في كل منزل!

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


All Articles