IP over UDP tunnel with socat

A simple way to create IP over UDP tunnels using socat.

This is the protocol stack we're going to implement:

[ IP  ]
[ UDP ]
[ IP  ]

In order to create a tunnel, we must create a TUN device interface. A TUN device is a network device managed by a userspace program:

  • when the kernel sends an IP packet to this device, it is sent to the userspace program which can do anything with it (like sending it in a tunnel);

  • conversely, the userpace program can send packets to the kernel through this network device.

We'd like to have a simple program which manages such a TUN device by encapsulating them over a UDP socket.

Using socat

It turns out socat can do this already!

On the first host:

sudo socat UDP:192.0.2.2:9000,bind=192.0.2.1:9000 \
  TUN:10.0.1.1/24,tun-name=tundudp,iff-no-pi,tun-type=tun,su=$USER,iff-up

On the second one:

sudo socat UDP:192.0.2.1:9000,bind=192.0.2.2:9000 \
  TUN:10.0.1.2/24,tun-name=tundudp,iff-no-pi,tun-type=tun,su=$USER,iff-up

Explanations:

  • the UDP type creates a connect()-ed UDP socket which means only the packets coming from this specific remote UDP endpoint will be accepted;

  • iff-no-pi sets the IFF_NO_PI flag which disables some encapsulation;

  • tun-type=tun asks a IP-based devices (as opposed for tun-type=tap for an Ethernet-based device which would implement an Ethernet over UDP tunnel);

  • iff-up sets the interface up;

  • su=$USER changes the user of the process (instead of using root).

Now we can ping over the tunnel:

host1:~$ ping 10.0.1.2
PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
64 bytes from 10.0.1.2: icmp_seq=1 ttl=64 time=39.3 ms
64 bytes from 10.0.1.2: icmp_seq=2 ttl=64 time=40.1 ms

host1:~$ ip route get 10.0.1.2
10.0.1.2 dev tunudp  src 10.0.1.1 
    cache

You can add IPv6 addresses to the tunnel and it works as expected: both IPv4 and IPv6 packets are sent over the same UDP socket and the version field is used to distinguish them.

Using ip fou

The other solution is to use ip fou (for foo over UDP):

modprobe fou
ip fou add port 9000 ipproto 4
ip link add name tunudp type ipip \
       remote 192.168.2.2 local 192.168.2.1 ttl 225 \
       encap fou \
       encap-sport auto encap-dport 9000

We can expect better performance as its handled completely in the kernel side. The downside is that you have to create two different tunnels (one for encapsulating IPv4 and the other for IPv6).