Good day!
In this article, I want to explain how I implemented () 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:
- Starting the node and waiting for the remote node to be ready;
- Determining the external IP address and UDP port;
- Transmitting the external IP address and UDP port to the remote node;
- Receiving the external IP address and UDP port from the remote node;
- Creating an IPIP tunnel;
- Monitoring the connection;
- 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/$folderRead:
curl -s --user "$username:$password" -X PROPFIND -H "Depth: 1" https://webdav.yandex.ru/$folderDelete:
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-clientTo establish the tunnel, the standard tools from the iproute2 package are used. There are 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$nameDelete the tunnel:
ip link del dev fou$nameip fou del port $localportThe 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 0The 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 0Variables 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; 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
