C(Linux)中的TUNTAP接口:无法使用sendto()捕获TUNTAP上发送的UDP数据包

时间:2012-02-21 08:13:37

标签: c networking udp tunneling

我正在尝试在C中编写一个隧道程序,它将从TUNTAP接口获取UDP数据包并将它们发送到串行接口。

我所做的是从克隆设备/ dev / net / tun分配接口,打开它并给它一个ip地址:

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

然后我创建一个线程,它将对创建的接口的文件描述符进行poll(),并在事件发生时执行read()+其他一些操作。

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

在程序的另一部分,我将UDP套接字绑定到此TUNTAP接口的IP地址。

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");
  }
}

在另一个函数中,我使用sendto()来发送带有此套接字的数据包。我应该使用poll()+ read()线程捕获这些数据包,然后在串行端口上发送它们,但poll()从不捕获TUNTAP接口上的事件。

我可以通过使用ping -I tun0 [某个目标](tun0 = TUNTAP接口的名称)来ping通此界面

但是如果我使用ping -I 192.168.2.1 [某个目的地](192.168.2.1 = TUNTAP接口地址),它将通过默认接口(eth0,物理网卡)。

我能够通过Wireshark验证这一点。

这很可能是ip路由配置问题......

如果有人能帮助我,我会很高兴的。

3 个答案:

答案 0 :(得分:1)

您是如何设置tun tap界面的IP地址的?使用"ip addr add"命令?您是否检查/配置了tun tap,即"ip link set <<tun tap interface name>> up"?最后你确定你的UDP套接字sendto代码在满足前面说的两个条件之后运行,即它有正确的ip地址和tun tap接口。

<强>更新

我认为你的概念错了,或者我不太了解你。 AFAIK你不需要做很多事情。 tun tap设备的概念是,无论tun tap接口接收到的发送到用户空间程序的数据包,以及用户空间程序写入tun tap设备的任何数据包,都将其发送到网络。说完这个并且读到你的要求是将UDP数据包隧道传输到串行接口,你应该做的是以下

1)将默认路由设置为tun tap IP地址。这样,所有数据包都将由用户空间程序获取。     route add default gw 192.168.2.1 tun0

现在,在用户空间程序中,您将获得具有IP和UDP标头的整个数据包。现在整个数据包将是您要通过串行接口传输的消息。因此,当我们使用UDP或TCP(无论您喜欢哪个)通过串行接口发送它时,我们会再次使用一个UDP和IP标头再次封装整个数据包。

2)您还需要添加带有串行接口地址的主机规则。以这种方式,用于串行接口的上述封装分组被发送到串行接口,并且由于默认规则它们不会再次返回到TUN TAP设备。     route add -host <<serial ip address>> dev <<serial device>>

route -n并检查路由是否正确。确保您没有其他不需要的路线。如果是,请删除它们。

注意:的 您还可以从数据包中仅提取有效负载,并使用RAW套接字创建自定义UDP标头,目标作为串行接口的IP地址,并使用TUN TAP设备进行写入。此数据包将被视为属于串行接口的内核路由实例,并将由于主机规则而转发到此处。如果您要使用此案例,则需要创建自定义IP和UDP标头以及CRC计算。

答案 1 :(得分:1)

您似乎只为tun设备分配了一个IP地址(192.168.2.1),但没有指定子网掩码或设置路由。如果没有为192.168.2.0/24网络定义特定路由,IP层将认为此数据包旨在通过默认路由发送。

您可以在为tun设备设置IP地址时指定子网掩码(/ 24),也可以手动设置192.168.2.0/24网络的路由。

其中一种方法是使用system()函数调用“ip”。要指定带子网掩码的地址(已包含标题):

// assuming the tun device name is "tun0"
system("ip addr add 192.168.2.1/24 dev tun0"); 

此外,如果您已经为设备分配了IP地址,则可以使用ip route comamnd设置路由:

ip route add 192.168.2.0/24 dev tun0

答案 2 :(得分:0)

您是否清除了pollfd上的事件? FDS [0] .revents = 0〜

我们需要清除收到的事件。

Src:http://www.ulduzsoft.com/2014/01/select-poll-epoll-practical-difference-for-system-architects/