5

我正在尝试用 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 [some destination] (tun0 = TUNTAP 接口的名称)来 ping 这个接口

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

我能够使用 Wireshark 验证这一点。

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

如果有人可以帮助我,我会非常高兴。

4

3 回答 3

2

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 计算。

于 2012-02-21T14:33:32.290 回答
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 routecomamnd 设置路由:

ip route add 192.168.2.0/24 dev tun0
于 2012-09-20T21:13:25.980 回答
0

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

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

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

于 2015-02-26T10:41:47.470 回答