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

Ale*_*oon 5 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;
}
Run Code Online (Sandbox Code Playgroud)

然后我创建一个线程,它将在创建的接口的文件描述符上进行 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;
}
Run Code Online (Sandbox Code Playgroud)

在程序的另一部分,我将 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");
  }
}
Run Code Online (Sandbox Code Playgroud)

在另一个函数中,我使用 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 路由配置问题...

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

sni*_*ibu 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 计算。