c - C (Linux) 中的 TUNTAP 接口(interface) : Can't capture UDP packets sent on the TUNTAP with sendto()

标签 c networking udp tunneling

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

我所做的是从克隆设备/dev/net/tun 分配接口(interface),打开它并给它一个 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;
}

然后我创建一个线程,它将在创建的接口(interface)的文件描述符上执行 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 套接字绑定(bind)到这个 TUNTAP 接口(interface)的 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 接口(interface)上的事件。

我可以使用 ping -I tun0 [some destination](tun0 = TUNTAP 接口(interface)的名称)通过此接口(interface)执行 ping 操作

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

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

这很可能是 ip route 配置问题...

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

最佳答案

你是怎么设置tun tap接口(interface)的IP地址的?使用 "ip addr add"命令?您是否已检查/配置 tun tap 启动,即 "ip link set <<tun tap interface name>> up" ?最后,您确定您的 UDP 套接字 sendto 代码在满足前面提到的两个条件后运行,即它具有正确的 ip 地址和 tun tap 接口(interface)已启动。

更新

我认为您的概念有误,或者我不太了解您的意思。 AFAIK 你不需要做这么多事情。 tun tap 设备的概念是无论 tun tap 接口(interface)接收到什么数据包都会发送到用户空间程序,无论用户空间程序向 tun tap 设备写入什么数据包,它都会发送到网络。话虽如此,并阅读到您的要求是将 UDP 数据包通过隧道传输到串行接口(interface),您应该做的如下

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

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

2) 您还需要添加一个带有串行接口(interface)地址的主机规则。这样,上面提到的串行接口(interface)的封装数据包被发送到串行接口(interface),并且由于默认规则,它们不会再次返回到 TUN TAP 设备。 route add -host <<serial ip address>> dev <<serial device>>

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

注意: 您也可以仅从数据包中提取有效负载,并使用 RAW 套接字创建一个自定义 UDP header ,并将目标作为串行接口(interface)的 IP 地址,并使用 TUN TAP 设备写入。该数据包将被视为属于串行接口(interface)的内核路由实例,并且由于主机规则将被转发到那里。如果您要处理这种情况,您需要创建一个自定义 IP 和 UDP header 以及 CRC 计算。

关于c - C (Linux) 中的 TUNTAP 接口(interface) : Can't capture UDP packets sent on the TUNTAP with sendto(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9374165/

相关文章:

Java语音聊天

tcp - 哪些 header 字段将数据包描述为唯一的?

c - 在 C 中“声明为函数”

c - 编写我自己的 float 解析器

c# - 如何使用udp从C#客户端与C服务器通信?

通过全局公共(public) IP 地址连接的 Python 套接字

无法使用 Infineon XMC 4500 RelaxKit 发送 UDP 数据包

c - BSP Sieve Of Erastothenes C 实现出现段错误(核心已转储)

c++ - 类未注册 0x80040154

linux - 如何在Linux中创建套接字?