Good day!

In this article I want to tell how I implemented ( another ) Bash script for connecting two computers located behind NAT, with Using UDP hole punching with Ubuntu/Debian as an example.

Organization of the connection consists of several steps:

  1. Starting the node and waiting for the remote node to be ready;
  2. Determination of the external IP address and UDP port;
  3. Transmit external IP address and UDP port to a 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 that it can be used to exchange data between nodes, the simplest and fastest for me at the moment is working through Yandex.Disk.

  • Firstly, it’s ease of use - you need 3 actions: create, read, delete. Using curl is:

    curl -s -X MKCOL --user "$usename:$password"$folder 

    curl -s --user "$usename:$password" -X PROPFIND -H "Depth: 1"$folder 


    curl -s -X DELETE --user "$usename:$password"$folder 
  • Secondly, it’s easy to install:

    apt install curl 

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

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

Installation by command:

apt install stun-client 

To organize the tunnel, standard OS tools from the iproute2 package are used. There is many tunnels that can be raised using regular means (L2TPv3, GRE, etc.), but I I chose IPIP because it creates minimal additional load on the system. I tried L2TPv3 on top of UDP and was disappointed, the speed dropped 10 times, but it could be various restrictions associated with providers or something else. Since the IPIP tunnel operates at the IP level, a FOU tunnel is used to operate at the UDP port level. To organize an IPIP tunnel, you need:

- download the FOU module:

modprobe fou 

- listen to the 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 address 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 

Monitoring the state of the tunnel is done by periodically pinging the internal IP address of the tunnel of the 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 on the routers, the NAT tables may be cleared and then the connection will break.

If the ping disappears, the IPIP tunnel is deleted and waits for readiness from the remote host.

Actually the script itself:

#!/bin/bash username="" password="password" folder="vpnid" intip="" 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"$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"$3 } function yadelete { curl -s -X DELETE --user "$1:$2"$3 } function myipport { stun -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 

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

nohup & 

I draw your attention to the fact that the IPIP tunnel is unsafe from the point of view that traffic is not encrypted, but this can be easily solved with IPsec by this article , it seemed simple and straightforward to me.

I have been using this script to connect to working PCs for several weeks and have not noticed any problems. Convenient in terms of what I tuned and forgot.

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

Thanks for your attention!.