Simple UDP hole punching using an IPIP tunnel as an example

Good time!

In this article I want to tell how I implemented (only one) a Bash script for connecting two computers behind NAT using UDP hole punching technology using the Ubuntu/Debian OS as an example.

Establishing a 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. Transferring the external IP address and UDP port to the remote host;
  4. Obtaining an external IP address and UDP port from a remote host;
  5. Organization of an IPIP tunnel;
  6. Connection monitoring;
  7. If the connection is broken, delete the IPIP tunnel.

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

  • Firstly, it is ease of use - you need 3 actions: create, read, delete. With curl it is:
    Create:

    curl -s -X MKCOL --user "$usename:$password" https://webdav.yandex.ru/$folder

    Read:

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

    Delete:

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

To determine the external IP address and UDP port, use the stun-client command:

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

Installation by command:

apt install stun-client

To organize the tunnel, regular OS tools from the iproute2 package are used. Exists many tunnels which can be raised by regular means (L2TPv3, GRE, etc.), but I chose IPIP because it creates a minimal additional load on the system. I tried L2TPv3 over UDP and was disappointed, the speed dropped by 10 times, but this may be various restrictions associated with providers or something else. Since the IPIP tunnel works at the IP level, the FOU tunnel is used to work at the UDP port level. To organize an IPIP tunnel, you need:

β€” load the FOU module:

modprobe fou

- listen on local port:

ip fou add port $localport ipproto 4

- create a tunnel:

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

- raise the tunnel interface:

ip link set up dev fou$name

β€” assign the internal local and internal remote IP addresses of the tunnel:

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

Remove tunnel:

ip link del dev fou$name

ip fou del port $localport

The tunnel state is monitored by periodically pinging the internal IP address of the tunnel of a remote node, using the command:

ping -c 1 $peerip -s 0

Periodic ping is needed primarily to maintain the channel, otherwise, if the tunnel is idle, the NAT tables on the routers may be cleared and then the connection will be terminated.

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

Actually the script itself:

#!/bin/bash
username="[email protected]"
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 ΠΈ folder should be the same on both sides, but peek - 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 &

I draw your attention to the fact that the IPIP tunnel is not secure in terms of the fact that the traffic is not encrypted, but this is easily solved using IPsec by this articleShe seemed simple and clear to me.

I have been using this script to connect to a working PC for several weeks and have not noticed any problems. Convenient in terms of what you set up and forget.

Perhaps you will have comments and suggestions, I will be glad to listen.

Thank you for attention!

Source: habr.com

Add a comment