Sunday, June 12, 2016

TUN/TAP devices on Linux

An interesting feature available on Linux is the ability to create virtual network interfaces. Usually we have different networking interfaces such as eth0 and wlan0 which directly maps to a network interface card available on our machine either wired or wireless. In addition to such real networking interfaces, we can create those virtual network interfaces so that our programs can communicate with such a networking interface just like it is communicating with a real networking interface.

There are two types of them. First type is TAP which represents an interface in layer 2 while the second type is TUN which represents a layer 3 interface which can be configured to have an IP address. We can have a user program which will read from and write to these TUN/TAP interfaces from the back end so that any software tool dealing with the virtual networking interface will feel like it is communicating with somebody over a network which the real communication occurs with the program running behind the TUN/TAP interface. This architecture can be illustrated as follows.

control program <--read/write--> TUN interface <--send/receive IP packets--> any networking program 

(1) Setting up a virtual TUN interface.

    # create a tun interface called 'asa0'
    sudo ip tuntap add dev asa0 mode tun

    # set an ip address
    sudo ip addr add 10.0.0.1/24 dev asa0

    # bring the interface up
    sudo ip link set dev asa0 up

    # let's see the settings now and then ping to our IP address of the TUN
    # interface
    ip addr show
    ping 10.0.0.1

    We should be able to see that ICMP responses are coming back when we ping to the correct IP address of the TUN interface. So, it is working. If we try to ping to any other IP address in the same network, we will not get any response.

    ping 10.0.0.2

    No response right? The packets destined to the same network are still directed to the newly created TUN network interface called asa0. But since this interface is virtual, there's nowhere to send these packet from the asa0 interface that's why we are not receiving any ping response back. That's fine. To get to know more clearly that all the packets for that network are directed to asa0 interface, we can check the routing table.

     route

My output looks like the below content. It is clear that the packets for the 10.0.0.0/24 network are directed to asa0 interface according to the routing table.

routing table
 That means, we are ready to use this TUN interface now.

(2) Open a text editor and write the following C program there. Save it with whatever the name you prefer. In my case, I saved it as tun-reader.c since this program opens the file descriptor of the TUN interface so that we can grab all the packets sent to the asa0 interface from the application layer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#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);

    /* ioctl will use 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 */

    return fd;
}

int main(int argc, char *argv[])
{
    int fd, nbytes;
    char buf[1600];

    fd = tun_open("asa0") ;
    printf("Device asa0 opened\n");

    while(1) {
        nbytes = read(fd, buf, sizeof(buf));
        printf("Read %d bytes from asa0\n", nbytes);
    }
    return 0;
}

Compile the above program as follows.

gcc tun-reader.c

(3) Now we are ready to try our TUN interface with a back-end reading program. Let's start our tun-reader.c program which is now compiled into the executable called a.out.

./a.out

It will open the file descriptor of the asa0 interface and will wait for any packets. Let's send some packets to the asa0 interface as follows to see whether our program receive those packets.

ping 10.0.0.1

In the above case, we are pinging to the exact IP address of the asa0 interface. Therefore we receive ping responses from the interface directly without directing the ICMP packets to the back-end program. Now, let's ping to some other IP address in the same network.

ping 10.0.0.2

This time, you should see that the packets are now moving beyond the asa0 TUN interface and they are received by our back-end program. Our program will print the receiving packets as follows.

program output

3 comments: