代码之家  ›  专栏  ›  技术社区  ›  Gene Vincent

为UDP套接字设置源IP

  •  26
  • Gene Vincent  · 技术社区  · 15 年前

    我有一个UDP套接字,它绑定到我的服务器上的所有ip上侦听数据包。我通过同一个插座发送回复。

    有没有办法做到这一点而不必为每个IP创建单独的套接字?

    4 回复  |  直到 15 年前
        1
  •  30
  •   Jimbo1987    7 年前

    Nikolai认为,为每个地址使用单独的套接字和绑定(2)或搞乱路由表通常是不可行的选择,例如使用动态地址。单身汉 IP_ADDRANY -绑定的UDP服务器应该能够在接收数据包的同一个动态分配的IP地址上响应。

    幸运的是,还有另一种方法。根据系统的支持,您可以使用 IP_PKTINFO cmsg(3) )网上很多地方都有报道 comp.os.linux.development.system 有一个完整的代码样本 IP地址 .

    链接中的代码使用 (或 IP_RECVDSTADDR 根据平台)从辅助服务器获取UDP消息的目标地址 cmsg(3) 数据。此处解释如下:

    struct msghdr msg;
    struct cmsghdr *cmsg;
    struct in_addr addr;
    // after recvmsg(sd, &msg, flags);
    for(cmsg = CMSG_FIRSTHDR(&msg);
        cmsg != NULL;
        cmsg = CMSG_NXTHDR(&msg, cmsg)) {
      if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
        addr = ((struct in_pktinfo*)CMSG_DATA(cmsg))->ipi_addr;
        printf("message received on address %s\n", inet_ntoa(addr));
      }
    }
    

    吉恩,你的问题是如何设置发送数据包的源地址。与 可以设置 ipi_spec_dst 磁场 struct in_pktinfo sendmsg(2) . 见上面提到的帖子, cmsg(3) struct msghdr . 例如(此处不作保证):

    struct msghdr msg;
    struct cmsghdr *cmsg;
    struct in_pktinfo *pktinfo;
    // after initializing msghdr & control data to CMSG_SPACE(sizeof(struct in_pktinfo))
    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = IPPROTO_IP;
    cmsg->cmsg_type = IP_PKTINFO;
    cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
    pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);
    pktinfo->ipi_ifindex = src_interface_index;
    pktinfo->ipi_spec_dst = src_addr;
    // bytes_sent = sendmsg(sd, &msg, flags);
    

    struct in6_pktinfo::ipi6_addr 在recvmsg和sendmsg两种情况下。

    另请注意,Windows不支持in_pktinfo结构中与ipi_spec_dst等效的,因此不能使用此方法设置传出winsock2数据包的源地址。

    (参考手册页-约1个超链接限制)

    http:// linux.die.net/man/2/sendmsg
    http:// linux.die.net/man/3/cmsg
    
        2
  •  20
  •   Russell Stuart    12 年前

    我想我应该扩展杰里米的关于如何在IPv6上做到这一点的内容。Jeremy遗漏了很多细节,一些文档(比如Linux的ipv6手册页)完全是错的。首先,在某些发行版上,您必须定义\u GNU \u SOURCE,否则一些IPv6内容没有定义:

    #define _GNU_SOURCE
    #include <netinet/in.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    

    const int on=1, off=0;
    int result;
    struct sockaddr_in6 sin6;
    int soc;
    
    soc = socket(AF_INET6, SOCK_DGRAM, 0);
    setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    setsockopt(soc, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
    setsockopt(soc, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on));
    setsockopt(soc, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off));
    memset(&sin6, '\0', sizeof(sin6));
    sin6.sin6_family = htons(AF_INET6);
    sin6.sin6_port = htons(MY_UDP_PORT);
    result = bind(soc, (struct sockaddr*)&sin6, sizeof(sin6));
    

    注意上面的代码为IPv6套接字设置了IP和IPv6选项。结果表明,如果数据包到达IPv4地址,即使它是IPv6套接字,您也将获得IP地址信息(即IPv4)cmsg,如果您不启用它们,它们将不会被发送。另外请注意,IPV6\u RECPKTINFO选项已设置(中未提及) ),而不是IPV6\u PKTINFO(在 城域网7 ipv6

    int bytes_received;
    struct sockaddr_in6 from;
    struct iovec iovec[1];
    struct msghdr msg;
    char msg_control[1024];
    char udp_packet[1500];
    
    iovec[0].iov_base = udp_packet;
    iovec[0].iov_len = sizeof(udp_packet);
    msg.msg_name = &from;
    msg.msg_namelen = sizeof(from);
    msg.msg_iov = iovec;
    msg.msg_iovlen = sizeof(iovec) / sizeof(*iovec);
    msg.msg_control = msg_control;
    msg.msg_controllen = sizeof(msg_control);
    msg.msg_flags = 0;
    bytes_received = recvmsg(soc, &msg, 0);
    

    下一步是从cmsg中提取接收到的UDP数据包的接口和地址:

    struct in_pktinfo in_pktinfo;
    struct in6_pktinfo in6_pktinfo;
    int have_in_pktinfo = 0;
    int have_in6_pktinfo = 0;
    struct cmsghdr* cmsg;
    
    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != 0; cmsg = CMSG_NXTHDR(&msg, cmsg))
    {
      if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO)
      {
        in_pktinfo = *(struct in_pktinfo*)CMSG_DATA(cmsg);
        have_in_pktinfo = 1;
      }
      if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO)
      {
        in6_pktinfo = *(struct in6_pktinfo*)CMSG_DATA(cmsg);
        have_in6_pktinfo = 1;
      }
    }
    

    最后,我们可以使用相同的目的地将响应发送回。

    int cmsg_space;
    
    iovec[0].iov_base = udp_response;
    iovec[0].iov_len = udp_response_length;
    msg.msg_name = &from;
    msg.msg_namelen = sizeof(from);
    msg.msg_iov = iovec;
    msg.msg_iovlen = sizeof(iovec) / sizeof(*iovec);
    msg.msg_control = msg_control;
    msg.msg_controllen = sizeof(msg_control);
    msg.msg_flags = 0;
    cmsg_space = 0;
    cmsg = CMSG_FIRSTHDR(&msg);
    if (have_in6_pktinfo)
    {
      cmsg->cmsg_level = IPPROTO_IPV6;
      cmsg->cmsg_type = IPV6_PKTINFO;
      cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
      *(struct in6_pktinfo*)CMSG_DATA(cmsg) = in6_pktinfo;
      cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo));
    }
    if (have_in_pktinfo)
    {
      cmsg->cmsg_level = IPPROTO_IP;
      cmsg->cmsg_type = IP_PKTINFO;
      cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
      *(struct in_pktinfo*)CMSG_DATA(cmsg) = in_pktinfo;
      cmsg_space += CMSG_SPACE(sizeof(in_pktinfo));
    }
    msg.msg_controllen = cmsg_space;
    ret = sendmsg(soc, &msg, 0);
    

    再次注意,如果数据包是通过IPv4传入的,那么我们必须将IPv4选项放入cmsg,即使它是AF\u INET6套接字。至少,这是您必须为Linux做的事情。

    这是一个惊人的工作量,但至少你要做的是使一个健壮的UDP服务器在所有可能的Linux环境中工作。TCP不需要它,因为它透明地处理多宿主。

        3
  •  4
  •   Nikolai Fetissov    15 年前

    你也是 bind(2) 到每个接口地址并管理多个套接字,或者让内核使用 INADDR_ANY

    我的问题是-你为什么需要这个?正常的IP路由对您不起作用吗?

        4
  •  0
  •   Chih-Ying Lin    9 年前

    我解决这个问题的方法是

    1. 将套接字绑定到特定接口
    2. 解开插座

      struct ifreq ifr;
      ...
      recvmsg(fd, &msg...)
      ...      
      if (msg.msg_controllen >= sizeof(struct cmsghdr))
        for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
          if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO)
          {
            iface_index = ((struct in_pktinfo *)CMSG_DATA(cmptr))->ipi_ifindex;
          }
      if_indextoname(iface_index , ifr.ifr_name);
      mret=setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr));
    
      sendmsg(...);
    
      memset(&ifr, 0, sizeof(ifr));
      snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "");
      mret=setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr));
    
    推荐文章