Very simple IP tunnel, based on DTLS.
If there is no need for strong encryption, you can consider using the more lightweight utun?
go install github.com/taoso/dtun/cmd/dtun
# 服务端
dtun -key foo
# 客户端
dtun -connect addr:port -key foo
Here’s a quick list of a few factors to consider.
It is not easy to achieve stable and reliable transport layer data encryption, so I have always used TLS as the underlying protocol.
TLS uses TCP, and the encrypted data is also TCP data to a large extent. In this way, transmitting an upper-layer data packet requires confirmation of the inner and outer TCP connections. The implementation problem of this kind of TCP over TCP is not small. For details, see: Reference http://sites.inka.de/~bigred/devel/tcp-tcp.html
Therefore, it is best to use UDP transmission. Naturally, DTLS is used for encryption. Currently, DTLS does not support 1.3, and the go language does not officially support it. You can only use this third-party implementation https://github.com/pion/dtls
Whether it is TLS or DTLS, you generally need to create a certificate. This process is now free of charge, but it is still complicated to configure. In addition, the certificate only solves the encryption problem and does not solve the authentication problem. Usually only the client verifies the server certificate. You can also let the server verify the client certificate, but this is too troublesome.
In addition, DTLS is connectionless and cannot only perform authentication when creating a connection like a TCP connection.
Therefore, it is best to complete the authentication of both ends at the same time during the DTLS handshake. So I choose Pre-Shared Key (PSK) mode. We only need to use -key
to specify the master key at both ends to complete double-end authentication. A DTLS session cannot be established if the client does not know the PSK.
In addition, PSK also needs to specify a hint parameter. You can simply think of it as the name of PSK. DTLS is not connected, so it is difficult to determine whether the client has been offline. I decided to have a unique hint parameter for each client. The server allocates tun devices for hints. If the client is disconnected and reconnected, multiple tun devices will not be created. But the side effect is that clients of the same hinit cannot log in at the same time.
In order to support macos, the tun device can only be set to point-to-point mode. If we want to do transparent routing and forwarding, look at the picture below
pc <-----------> router <====== dtun ======> pc2 <---------> www
10.0.0.2/16 10.0.0.1/16 10.1.0.1/16 10.1.0.2/16
We hope that the packets sent by pc will be forwarded to pc2 through the router and then forwarded to the external network. Generally, we will do NAT once on the router and then once on pc2. The advantage of this is that pc2 does not need to know the network configuration of the pc to the router. But the disadvantages are also obvious, there are two nat. The performance of routers is generally not strong, so NAT should be avoided as much as possible.
So my plan is to directly push the network segment 10.0.0.0/16 where the pc is located to pc2 and add a route on pc2
ip route add 10.0.0.0/16 via 10.1.0.1
In this way, the router can forward the packets from pc to pc2 intact, and only need to do NAT once on pc2.
Sometimes we need to specify a routing whitelist. Use the default route on the network segments in the whitelist, and forward the others through the tunnel.
We can add a whitelist route to the router first, and set the next hop to the router's default route. Then specify the public IP of pc2 to take the default route of the router (key!). last added
ip route add 0.0.0.0/1 via 10.1.0.2
ip route add 128.0.0.0/1 via 10.1.0.2
0.0.0.0/1
and 128.0.0.1/1
here just cover the entire network segment. The effect is equivalent to the default, but it will not overwrite the default route. If the tunnel is closed abnormally, all related routes will be automatically deleted, which is very stable.
You can write it as a script and run it using the -up
parameter. My script is as follows:
#! /bin/sh
# curl -S https://cdn.jsdelivr.net/gh/misakaio/chnroutes2@master/chnroutes.txt|grep -v '#'|xargs -I % ip route add % via $DEFAULT_GW 2>/dev/null
VPN_IP= $( ping your-server-name -c 1 | grep from | cut -d ' ' -f4 | cut -d: -f1 )
DEFAULT_GW= $( ip route | grep default | cut -d ' ' -f3 )
ip route add $VPN_IP /32 via $DEFAULT_GW
ip route add 0.0.0.0/1 via $PEER_IP
ip route add 128.0.0.0/1 via $PEER_IP