How to interface with the Linux tun driver
There are no /dev/tunX
device files. Instead, you open the /dev/net/tun
and configure it via ioctl()
to "point" to tun0
. To show the basic procedure, I will create the TUN interface using the command line tool ip tun tap
and then show the C code to read from that TUN device. So to create the tun interface via commands line:
ip addr show # my eth0 inet address is 10.0.2.15/24 as Im running on a VirtualBox vm with Ubuntu 18.04 guest
sudo ip tuntap add mode tun dev tun0
sudo ip addr add 10.0.3.0/24 dev tun0 # give it an address (that does not conflict with existing IP)
sudo ip link set dev tun0 up # bring the if up
ip route get 10.0.3.50 # check that packets to 10.0.3.x are going through tun0
# 10.0.3.50 dev tun0 src 10.0.3.0 uid 1000
ping 10.0.3.50 # leave this running in another shell to be able to see the effect of the next example, nobody is responding to the ping
The tun0
is created and all packets to destination IP address 10.0.3.x will be routed to tun0
.
To read / write packets to this interface from an user space program you need to interact with the /dev/net/tun
device file using ioctl()
. Here is an example that will read the packets arriving at the tun0
interface and print the size:
#include <fcntl.h> /* O_RDWR */
#include <string.h> /* memset(), memcpy() */
#include <stdio.h> /* perror(), printf(), fprintf() */
#include <stdlib.h> /* exit(), malloc(), free() */
#include <sys/ioctl.h> /* ioctl() */
#include <unistd.h> /* read(), close() */
/* includes for struct ifreq, etc */
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <linux/if_tun.h>
int tun_open(char *devname)
{
struct ifreq ifr;
int fd, err;
if ( (fd = open("/dev/net/tun", O_RDWR)) == -1 ) {
perror("open /dev/net/tun");exit(1);
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TUN;
strncpy(ifr.ifr_name, devname, IFNAMSIZ); // devname = "tun0" or "tun1", etc
/* ioctl will use ifr.if_name as the name of TUN
* interface to open: "tun0", etc. */
if ( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) == -1 ) {
perror("ioctl TUNSETIFF");close(fd);exit(1);
}
/* After the ioctl call the fd is "connected" to tun device specified
* by devname ("tun0", "tun1", etc)*/
return fd;
}
int main(int argc, char *argv[])
{
int fd, nbytes;
char buf[1600];
fd = tun_open("tun0"); /* devname = ifr.if_name = "tun0" */
printf("Device tun0 opened\n");
while(1) {
nbytes = read(fd, buf, sizeof(buf));
printf("Read %d bytes from tun0\n", nbytes);
}
return 0;
}
If you have the ping 10.0.3.1
or ping 10.0.3.40
running, you will see Read 88 bytes from tun0
periodically.
You can also test using netcat UDP with nc -u 10.0.3.3 2222
and typing text + Enter.
If nothing is being printed is most likely that the id address / ip range assigned to the tun0 is not reachable/routable/addressable. Make sure that the ip route get 10.0.3.4
shows 10.0.3.4 dev tun0
indicating that the linux kernels knows that packets to 10.0.3.4 should be sent to the tun0 device.
To delete the tun0
do
sudo ip link set dev tun0 down
sudo ip tuntap del mode tun dev tun0
Read /usr/src/linux/Documentation/networking/tuntap.rst
.
You are supposed to open
the /dev/net/tun
device. A subsequent ioctl
on the open fd will create the tun0
(or whatever you wish to name it) network interface. Linux's network interfaces do not correspond to any /dev/*
device.