Прост UDP hole punching на примера на IPIP тунела.

Добър ден!

В тази статия искам да споделя как реализирах (още един) скрипт на Bash за свързване на два компютъра, намиращи се зад NAT, с използване на технологията UDP hole punching на примера на ОС Ubuntu/Debian.

Организацията на връзката се състои от няколко стъпки:

  1. Стартиране на възел и изчакване на готовност на отдалечения възел;
  2. Определяне на външния IP адрес и UDP порта;
  3. Предаване на външния IP адрес и UDP порта на отдалечения възел;
  4. Получаване на външния IP адрес и UDP порта от отдалечения възел;
  5. Организация на IPIP тунела;
  6. Мониторинг на връзката;
  7. При прекъсване на връзката, да се премахне IPIP тунелът.

Много мислех и все още мисля какво може да се използва за обмен на данни между възлите, най-простото и бързо за мен в момента е работа чрез Yandex.Disk.

  • На първо място, това е простота в използването — нужни са 3 действия: създаване, четене, изтриване. С помощта на curl това е:
    Създаване:
    curl -s -X MKCOL --user "$usename:$password" https://webdav.yandex.ru/$folder

    Четене:

    curl -s --user "$usename:$password" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/$folder

    Изтриване:

    curl -s -X DELETE --user "$usename:$password" https://webdav.yandex.ru/$folder
  • На второ място, това е простота в инсталацията:
    apt install curl

За определяне на външния IP адрес и UDP порта се използва stun-client с командата:

stun stun.sipnet.ru -v -p $1 2>&1 | grep "MappedAddress"

Инсталацията с командата:

apt install stun-client

За организация на тунела се използват вградените инструменти на ОС от пакета iproute2. Съществуват множество тунели които могат да бъдат конфигурирани с вградените средства (L2TPv3, GRE и т.н.), но избрах IPIP, защото създава минимална допълнителна натовареност на системата. Опитах L2TPv3 над UDP и се разочаровах, скоростта спадна десетократно, но това може да бъде свързано с различни ограничения от страна на доставчиците или нещо друго. Тъй като IPIP тунелът работи на ниво IP, се използва FOU тунел, за да работи на ниво UDP портове. За организиране на IPIP тунел е необходимо:

— да заредите модула FOU:

modprobe fou

— да слушате локалния порт:

ip fou add port $localport ipproto 4

— да създадете тунел:

ip link add name fou$name type ipip remote $remoteip local $localip encap fou  encap-sport $localport encap-dport $remoteport

— да активирате интерфейса на тунела:

ip link set up dev fou$name

— да зададете вътрешните локални и отдалечени IP адреси на тунела:

ip addr add $intIP peer $peerip dev fou$name

Изтриване на тунела:

ip link del dev fou$name

ip fou del port $localport

Мониторинг на състоянието на тунела се извършва чрез периодично пингване на вътрешния IP адрес на тунела на отдалечения възел с командата:

ping -c 1 $peerip -s 0

Периодичното пингване е необходимо най-вече за поддържане на канала, в противен случай при бездействие на тунела на рутерите NAT таблиците могат да се изчистят и тогава връзката ще се прекъсне.

Ако пингът изчезне, то IPIP тунелът се премахва и очаква готовност от отдалечения възел.

Самият скрипт:

#!/bin/bash
username="username@yandex.ru"
password="password"
folder="vpnid"
intip="10.0.0.1"
localport=`shuf -i 10000-65000 -n 1`
cid=`shuf -i 10000-99999 -n 1`
tid=`shuf -i 10-99 -n 1`
function yaread {
        curl -s --user "$1:$2" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/$3 | sed 's/></>n</g' | grep "displayname" | sed 's/<d:displayname>//g' | sed 's/</d:displayname>//g' | grep -v $3 | grep -v $4 | sort -r
}
function yacreate {
        curl -s -X MKCOL --user "$1:$2" https://webdav.yandex.ru/$3
}
function yadelete {
        curl -s -X DELETE --user "$1:$2" https://webdav.yandex.ru/$3
}
function myipport {
        stun stun.sipnet.ru -v -p $1 2>&1 | grep "MappedAddress" | sort | uniq | awk '{print $3}' | head -n1
}
function tunnel-up {
	modprobe fou
	ip fou add port $4 ipproto 4
	ip link add name fou$7 type ipip remote $1 local $3 encap fou encap-sport $4 encap-dport $2
	ip link set up dev fou$7
	ip addr add $6 peer $5 dev fou$7
}
function tunnel-check {
	sleep 10
        pings=0
        until [[ $pings == 4 ]]; do
                if ping -c 1 $1 -s 0 &>/dev/null;
                        then    echo -n .; n=0
                        else    echo -n !; ((pings++))
                fi
		sleep 15
        done
}
function tunnel-down {
	ip link del dev fou$1
	ip fou del port $2
}
trap 'echo -e "nDisconnecting..." && yadelete $username $password $folder; tunnel-down $tunnelid $localport; echo "IPIP tunnel disconnected!"; exit 1' 1 2 3 8 9 14 15
until [[ -n $end ]]; do
    yacreate $username $password $folder
    until [[ -n $ip ]]; do
        mydate=`date +%s`
        timeout="60"
        list=`yaread $username $password $folder $cid | head -n1`
        yacreate $username $password $folder/$mydate:$cid
        for l in $list; do
                if [ `echo $l | sed 's/:/ /g' | awk {'print $1'}` -ge $(($mydate-65)) ]; then
			#echo $list
                        myipport=`myipport $localport`
                        yacreate $username $password $folder/$mydate:$cid:$myipport:$intip:$tid
                        timeout=$(( $timeout + `echo $l | sed 's/:/ /g' | awk {'print $1'}` - $mydate + 3 ))
                        ip=`echo $l | sed 's/:/ /g' | awk '{print $3}'`
                        port=`echo $l | sed 's/:/ /g' | awk '{print $4}'`
                        peerip=`echo $l | sed 's/:/ /g' | awk '{print $5}'`
			peerid=`echo $l | sed 's/:/ /g' | awk '{print $6}'`
			if [[ -n $peerid ]]; then tunnelid=$(($peerid*$tid)); fi
                fi
        done
        if ( [[ -z "$ip" ]] && [ "$timeout" -gt 0 ] ) ; then
                echo -n "!"
                sleep $timeout
        fi
    done
    localip=`ip route get $ip | head -n1 | sed 's|.*src ||' | cut -d' ' -f1`
    tunnel-up $ip $port $localip $localport $peerip $intip $tunnelid
    tunnel-check $peerip
    tunnel-down $tunnelid $localport
    yadelete $username $password $folder
    unset ip port myipport
done
exit 0

Променливи username, password и folder трябва да бъдат еднакви от двете страни, а intip — различни, например: 10.0.0.1 и 10.0.0.2. Времето на възлите трябва да бъде синхронизирано. Скриптът може да се стартира по следния начин:

nohup script.sh &

Обърнете внимание, че IPIP тунелът не е безопасен от гледна точка на това, че трафикът не е шифрован, но това лесно се решава с помощта на IPsec. тази статия, той ми се стори прост и разбираем.

Използвам този скрипт за свързване с работния си компютър вече няколко седмици и не съм имал проблеми. Удобно е, защото го настроих и забравих.

Възможно е да имате коментари и предложения, ще се радвам да ги чуя.

Благодаря за вниманието!

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster