Simple UDP hole punching using an IPIP tunnel as an example

Good day!

In this article, I want to explain how I implemented (another) script in Bash to connect two computers behind NAT using UDP hole punching technology on the example of Ubuntu/Debian OS.

Establishing the connection consists of several steps:

  1. Starting the node and waiting for the remote node to be ready;
  2. Determining the external IP address and UDP port;
  3. Transmitting the external IP address and UDP port to the remote node;
  4. Receiving the external IP address and UDP port from the remote node;
  5. Creating an IPIP tunnel;
  6. Monitoring the connection;
  7. If the connection is lost, delete the IPIP tunnel.

I thought for a long time and still think about what can be used for data exchange between nodes; the simplest and fastest for me at the moment is working through Yandex.Disk.

  • First of all, it is easy to use — it requires 3 actions: create, read, delete. With curl, this would be:
    Create:
    curl -s -X MKCOL --user "$username:$password" https://webdav.yandex.ru/$folder

    Read:

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

    Delete:

    curl -s -X DELETE --user "$username:$password" https://webdav.yandex.ru/$folder
  • Secondly, it is easy to install:
    apt install curl

To determine the external IP address and UDP port, the stun-client is used with the command:

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

Installation with the command:

apt install stun-client

To establish the tunnel, the standard tools from the iproute2 package are used. There are many tunnels that can be created with standard tools (L2TPv3, GRE, etc.), but I chose IPIP because it creates minimal additional load on the system. I tried L2TPv3 over UDP and was disappointed as the speed dropped tenfold, but this might be due to various limitations from providers or something else. Since the IPIP tunnel operates at the IP level, the FOU tunnel is used to work at the UDP ports level. To establish an IPIP tunnel, you need to:

— load the FOU module:

modprobe fou

— listen on the local port:

ip fou add port $localport ipproto 4

— create the tunnel:

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

— bring the tunnel interface up:

ip link set up dev fou$name

— assign the internal local and remote internal IP addresses of the tunnel:

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

Delete the tunnel:

ip link del dev fou$name

ip fou del port $localport

The tunnel status monitoring is performed by periodically pinging the internal IP address of the remote node's tunnel using the command:

ping -c 1 $peerip -s 0

The periodic ping is primarily needed to maintain the channel; otherwise, if the tunnel is idle, the NAT tables on the routers may be cleared, and the connection will be interrupted.

If the ping fails, the IPIP tunnel is removed and waits for readiness from the remote node.

The script itself:

#!/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

Variables username, password and folder must be the same on both sides, while intip — should be different, for example: 10.0.0.1 and 10.0.0.2. The time on the nodes must be synchronized. You can run the script like this:

nohup script.sh &

Please note that the IPIP tunnel is not secure in terms of traffic encryption, but this can be easily resolved using IPsec; from one of the authors.it seemed simple and clear to me.

I have been using this script to connect to my work PC for several weeks and haven't noticed any problems. It's convenient in that you set it up and forget about it.

If you have any comments or suggestions, I would be happy to hear them.

Thank you for your attention!

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster