How to interface with the Linux tun driver

rmrobins picture rmrobins · Jun 16, 2009 · Viewed 40.9k times · Source

I'm having a hard time figuring this problem out - I am trying to write a program that will interact with the Linux tunnel driver. At a very basic level, I simply want to create an application that is able to transfer data over a network tunnel. However, I am completely at a loss as to how to properly set up the tunnel driver in order to accomplish this.

I am developing on Ubuntu 9.04, and I have the tunnel driver kernel module loaded.

There exists the device /dev/net/tun, however there are no /dev/tunX devices. I am unable to create these devices using ifconfig - whenever I run /sbin/ifconfig tun0 up, for example, I get the following error:

tun0: ERROR while getting interface flags: No such device.

If I attempt to look at the /dev/net/tun device, the following error is presented:

cat: /dev/net/tun: File descriptor in bad state.

Attempting to open /dev/tunX via a small program, basically, a simple

tun_fd = open( "/dev/tun0", O_RDWR )

returns -1: the application is running as root and still cannot open this tunnel device. It is possible to open /dev/net/tun, however this does not appear to generate a new /dev/tunX device to use instead.

So, in summary - how does one go about writing an application that wishes to use the Linux tunnel driver? Any insights would be greatly appreciated.

Thanks; ~Robert

Answer

RubenLaguna picture RubenLaguna · Mar 2, 2016

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:

sudo ip tuntap add mode tun dev tun0
ip addr add 10.0.0.0/24 dev tun0  # give it an ip
ip link set dev tun0 up  # bring the if up
ip route get 10.0.0.2  # check that packets to 10.0.0.x are going through tun0
ping 10.0.0.2  # leave this running in another shell to be able to see the effect of the next example

Now we have tun0 created. 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() */

/* 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;
}