【问题标题】:Receiving Data for Multiple Hosts via Linux Sockets通过 Linux 套接字接收多个主机的数据
【发布时间】:2013-11-21 03:59:07
【问题描述】:

我有一个很奇怪的问题。最近,我的任务是开发软件来模拟大型(数百个节点及以上)网络。长话短说,我们有一个前端服务器,它通过可预测的 IP 寻址方案通过 Linux 套接字使用广播和单播的混合方式与每个主机进行通信。头端将向给定客户端发出请求,并且(有时)将接收与执行的命令有关的数据。所有数据/命令都通过 UDP 在明确定义的端口上发送。

现在,出于测试目的,我们想在虚拟环境中使用原始服务器二进制文件,并且仍然可以接收合理的数据。例如,我们想向特定节点发出重置命令并收到虚假通知。广播位很容易,因为我只需要监听正确的广播地址并采取相应的行动。单播让我陷入了困境。

问题

是否可以通过单个(或减少的)数量的 Linux 套接字接收大量离散主机的 UDP 请求?所有主机都在同一个子网上,并且所有 IP 地址/主机/网络拓扑都是提前知道的。

期望的输出

最终,我们希望有一个应用程序在网络上的主机上运行,​​并根据输入数据报像这些离散的“虚拟化”主机一样做出响应。

请注意,我并不是在要求某人为我编写程序。我只是在寻找可以实现这一目标的“车辆”的方向。

可能的解决方案

  • RAW Sockets:这很有希望,因为我可以通过 单个套接字并将其关闭到工作线程进行处理和 回复。不幸的是,我只收到 发往我的主机 IP,没有任何“假”IP。

  • 在 Linux 上滥用 IP 别名,每个主机一个:这似乎是最直接的方法,但感觉就像用火箭筒打鸭子一样。它还有一个额外的好处,那就是看起来“成为”任何其他形式的通信的主机,我只是担心创建 400 多个别名对于我们 Linux 环境的混蛋来说可能有点多。更复杂的是,主机确实会根据配置进行更改,并且可以处于任何状态(启动、关闭、命令处理等)。

为了我们的测试目的,服务器的源代码将被视为不可变的。我完全希望在给定的限制下这是不可能的,但有人可能知道如何实现这一点,坦率地说,我以前从未做过这种事情。

提前感谢您的帮助。

【问题讨论】:

    标签: c++ c linux sockets


    【解决方案1】:

    就个人而言,我会使用您的第二个选项 - 将所有 IP 地址添加到主机,然后绑定到 INADDR_ANY 地址。这意味着您只能使用一个套接字。

    另一种方法是在您的套接字上设置IP_TRANSPARENT 套接字选项,这将允许您的应用程序绑定到非本地地址(您可以通过运行您的应用程序的机器路由包含这些地址的网络) .不过,这种方法确实需要每个地址一个套接字。

    【讨论】:

    • 有趣。感谢您的回复。我假设,使用您的第二个选项,没有办法指定多个绑定地址?我见过人们重新绑定套接字的例子,但我认为这不能同时发生?我也看了一些,看来 IP_TRANSPARENT 不适用于原始套接字。这有技术原因吗?似乎通过原始套接字获取所有非本地数据包可以让我吃蛋糕。
    • 据我所知,您不能将一个套接字同时绑定到多个地址。不过,我真的不认为有很多套接字有问题。只需使用epoll() 等待他们的事件。
    【解决方案2】:

    因此,结合使用 caf 的两种解决方案,我能够吃到我的蛋糕。我也深受

    的影响

    Python/iptables: Capturing all UDP packets and their original destination

    这是一个 Python 示例,但确实显示了我如何将数据包“欺骗”回单个接口,从而无需维护许多套接字。这个问题非常值得一读,并且包含很多有用的信息。不过,为了简洁起见,我将在下面重述部分内容。

    希望它可以帮助其他人。

    第 1 部分 - 主机配置

    如上问题所述,我们可以使用iptablesip路由的组合将数据包重定向到环回处理。这在我最初的问题中没有说明,但是“模拟器”可以在头端主机本身上运行,而不是网络上的离散节点。为此,我们通过iptables 标记每个数据包,然后根据所述标记将其路由到lo

    iptables -A OUTPUT -t mangle -p udp --dport 27333 -j MARK --set-mark 1
    ip rule add fwmark 1 lookup 100
    ip route add local 0.0.0.0/0 dev lo table 100
    

    在我的情况下,我只需要到某个端口的流量,因此我的 iptables 规则已根据原始规则进行了相应调整。

    第 2 部分 - 软件

    正如 caf 在他的帖子中所说,真正的诀窍是使用 IP_TRANSPARENT 和原始套接字。原始套接字是获取原始源/目标 IP 地址所必需的。我花了一段时间的一个问题是在对 socket() 的调用中使用了IPPROTO_UDP。即使这是一个原始套接字,它也会去掉以太网头。网上很多代码显示了使用类似于以下内容的IP头偏移量的计算:

    struct iphdr* ipHeader = (struct iphdr *)(buf + sizeof(ethhdr));
    

    通过 ethhdr 进行偏移(被剥离)会给你一些相当有趣的垃圾数据。删除该特定标头后,必要的 IP 标头只是缓冲区中的第一个结构。

    测试代码

    您将在下面找到一个概念验证示例。它不是功能齐全或完整的。特别是,没有对传入数据包进行恶意数据检查(例如,有效负载中的格式字符串漏洞、指针数学问题、格式错误/恶意数据包等)。

    请注意,代码专门绑定到lo。这并不意味着我们只会收到发往我们的“假”主机之一的数据包(其他服务也使用 loobpack)。 我们想要的数据包需要额外的检查/过滤。

    #include <arpa/inet.h>
    #include <netinet/if_ether.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <netinet/udp.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <string>
    
    int main(int argc, char *argv[]) {
    
      //Set up listening socket
      struct sockaddr_in serverAddr;
      struct iphdr* ipHeader;
      struct udphdr* udpHeader;
      int listenSock = 0;
      char data[65536];
      static int is_transparent = 1;
      std::string device = "lo";
    
      //Initialize listening socket
      if ((listenSock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP)) < 0) {
          printf("Error creating socket\n");
          return 1;
      }
    
      setsockopt(listenSock, SOL_IP, IP_TRANSPARENT, &is_transparent, sizeof(is_transparent));
      setsockopt(listensock, SOL_SOCKET, SO_BINDTO_DEVICE, device.c_str(), device.size());
      memset(&serverAddr, 0x00, sizeof(serverAddr));
      memset(&data, 0x00, sizeof(data));
    
      //Setup server address
      serverAddr.sin_family = AF_INET;
      serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
      serverAddr.sin_port = htons(27333);
    
      //Bind and listen
      if (bind(listenSock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0) {
          printf("Error binding socket\n");
          return 1;
      }
    
      while (1) {
    
          //Accept connection
          recv(listenSock, data, 65536, 0);
    
          //Get IP header
          ipHeader = (struct iphdr*)(data);
    
          //Only grab UDP packets (17 is the magic number for UDP protocol)
          if ((unsigned int)ipHeader->protocol == 17) {
    
            //Get UDP header information
            udpHeader = (struct udphdr*)(data + (ipHeader->ihl * 4));
    
            //DEBUG
            struct sockaddr_in tempDest;
            struct sockaddr_in tempSource;
            char* payload = (char*)(data + ipHeader->ihl * 4) + sizeof(struct udphdr));
    
            memset(&tempSource, 0x00, sizeof(tempSource));
            memset(&tempDest, 0x00, sizeof(tempDest));
            tempSource.sin_addr.s_addr = ipHeader->saddr;
            tempDest.sin_addr.s_addr = ipHeader->daddr;
    
            printf("Datagram received\n");
            printf("Source IP: %s\n", inet_ntoa(tempSource.sin_addr));
            printf("Dest IP  : %s\n", inet_ntoa(tempDest.sin_addr));
            printf("Data     : %s\n", payload);
            printf("Port     : %d\n\n", ntohs(udpHeader->dest));
          }
      }
    }
    

    进一步阅读

    下面是一些非常有用的链接。

    http://www.binarytides.com/packet-sniffer-code-in-c-using-linux-sockets-bsd-part-2/

    http://bert-hubert.blogspot.com/2012/10/on-binding-datagram-udp-sockets-to-any.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-02-12
      • 2022-01-01
      • 2012-06-13
      • 2013-03-29
      • 1970-01-01
      • 2017-11-07
      • 1970-01-01
      相关资源
      最近更新 更多