TUNTAP interface in C (Linux) : Can't capture UDP packets sent on the TUNTAP with sendto()

Alex On The Moon picture Alex On The Moon · Feb 21, 2012 · Viewed 7.8k times · Source

I am trying to write a tunneling program in C that will take UDP packets from a TUNTAP interface and send them to a serial interface.

What I do is allocate the interface from the clone device /dev/net/tun, turn it on and give it an ip address :

int tun_setup(char *dev, int flags) {

  struct sockaddr_in  my_addr;
  struct ifreq ifr;
  int fd, err;
  string clonedev = "/dev/net/tun";

  // Open clone device file descriptor
  if( (fd = open(clonedev.c_str() , O_RDWR)) < 0 ) {
    perror("Opening /dev/net/tun");
    return fd;
  }

  // Initialise interface parameters structure
  memset(&ifr, 0, sizeof(ifr));

  // Set up flags
  ifr.ifr_flags = flags;

  // Set up interface name
  if (*dev) {
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);
  }

  // Put interface in TUN mode
  if( (err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0 ) {
    perror("ioctl(TUNSETIFF)");
    close(fd);
    return err;
  }

  strcpy(dev, ifr.ifr_name);

  // Create a socket
  if ( (s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
  perror("socket");
      exit(1);
  }

  // Get interface flags
  if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) {
    perror("cannot get interface flags");
    exit(1);
  }

  // Turn on interface
  ifr.ifr_flags |= IFF_UP;
  if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) {
    fprintf(stderr, "ifup: failed ");
    perror(ifr.ifr_name);
    exit(1);
  }

  // Set interface address
  bzero((char *) &my_addr, sizeof(my_addr));
  my_addr.sin_family = AF_INET;
  my_addr.sin_addr.s_addr = htonl(inet_network("192.168.2.1"));
  memcpy(&ifr.ifr_addr, &my_addr, sizeof(struct sockaddr));

  if (ioctl(s, SIOCSIFADDR, &ifr) < 0) {
    fprintf(stderr, "Cannot set IP address. ");
    perror(ifr.ifr_name);
    exit(1);
  }

  // Return interface file descriptor
  return fd;
}

Then I create a thread that will poll() on the file descriptor of the created interface and do read() + some other stuff when an event occurs.

void* tun_readThreadProc (void* param) {
  struct pollfd fds[1];
  int nread;
  unsigned char buffer[BUFFERSIZE];
  fds[0].fd = tun_fd;
  fds[0].events = POLLIN;

  printf("%s : Entered. tun_fd = %d \n",__FUNCTION__,tun_fd);

  for(;;)
  {
    printf("%s : Entered loop\n",__FUNCTION__);
    if((poll(fds, 1, -1)) == -1)
    {
      perror("poll");
      exit(1);
    }

    printf("%s : Poll sensed something\n",__FUNCTION__);

    if((nread = read(tun_fd, buffer, BUFFERSIZE)) < 0)
    {
      perror("read");
      close(tun_fd);
      exit(1);
    }

    printf("%s : Read something : %d bytes\n",__FUNCTION__,nread);
  }
  return 0;
}

On an other part of the program, I bind a UDP socket to the IP address of this TUNTAP interface.

void socketInit( void )
{
  int                 on = 1;
  struct sockaddr_in  my_addr;
  unsigned short DefaultPort = 47808;

  // Create a socket
  if ( (s1 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    perror("socket");
    exit(1);
  }

  // Bind to it
  bzero((char *) &my_addr, sizeof(my_addr));
  my_addr.sin_family = AF_INET;
  my_addr.sin_addr.s_addr = htonl(inet_network("192.168.2.1"));
  my_addr.sin_port = htons(DefaultPort);

  if ( (bind(s, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) ) {
    perror("bind");
  }
  // Allow it to broadcast
  if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *)&on, sizeof(on)) < 0) {
    perror("setsockopt");
  }
}

In another function, I use sendto() to send packets with this socket. I am supposed to capture those packets with the poll() + read() thread and then send them on a serial port but poll() never captures events on the TUNTAP interface.

I can ping through this interface by using ping -I tun0 [some destination] (tun0 = name of the TUNTAP interface)

But if I use ping -I 192.168.2.1 [some destination] (192.168.2.1 = TUNTAP interface address) it goes through the default interface (eth0, the physical NIC).

I was able to verify that with Wireshark.

This is most probably an ip route configuration problem...

I would be really glad if anybody can help me.

Answer

snibu picture snibu · Feb 21, 2012

How did you set the IP address of the tun tap interface? Using "ip addr add" command? Have you checked/configured the tun tap to be up ie "ip link set <<tun tap interface name>> up"? Lastly are you sure your UDP socket sendto code runs after the previous said two conditions are satisfied ie it has the correct ip address and tun tap interface is up.

Update

I think you are getting the concept wrong or I didnt understand you very well. AFAIK you dont need to do these many things. The concept of the tun tap device is that whatever packets the tun tap interface receives its sent to the userspace program and whatever the userspace program writes to the tun tap device, its sent to the network. Having said this and reading that your requirement is to tunnel the UDP packets to the serial interface, what you should do is the following

1) Set the default route to the tun tap IP address. In this way all the packets will be got by userspace program. route add default gw 192.168.2.1 tun0

So now in the userspace program you get the whole packet with the IP and UDP headers. Now this whole packet will be the message that you want to transfer via the serial interface. So when we send it via the serial interface using UDP or TCP(whichever you prefer), we are automatically again encapsulting the whole packet with one more UDP and IP header.

2) You also need to add a host rule with the serial interface address. In this way the above said encapsulated packets for the serial interface are sent to the serial interface and they dont come back again to the TUN TAP device due to the default rule. route add -host <<serial ip address>> dev <<serial device>>

Give route -n and check if the routing is right. Make sure that you dont have other unwanted routes. If yes delete them.

Note: Also you can extract just the payload from the packet and using RAW sockets create a custom UDP header with the destination as the serial interface's IP address and write using the TUN TAP device. This packet will be seen as the kernel routing instance as belonging to the serial interface and will be forwarded to there because of the host rule. If you are going for this case you need to create a do a custom IP and UDP header and also CRC calculation.