通过Linux套接字接收多个主机的数据

Receiving Data for Multiple Hosts via Linux Sockets

本文关键字:主机 数据 Linux 套接字 通过      更新时间:2023-10-16

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

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

问题

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

所需输出

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

请注意,我并不是要求别人给我写一个程序。我只是在寻找实现这一目标的"工具"的一些方向。

可能的解决方案

  • RAW套接字:这很有希望,因为我可以通过单个套接字,并将其转移到工作线程以进行处理和回答不幸的是,我只收到目的地是我的主机IP,而不是"假"IP。

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

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

提前感谢您的帮助。

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

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

因此,结合使用caf的两种解决方案,我可以既吃蛋糕又吃蛋糕。我也深受的影响

Python/iptables:捕获所有UDP数据包及其原始目的地

这是一个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 socketn");
      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 socketn");
      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 receivedn");
        printf("Source IP: %sn", inet_ntoa(tempSource.sin_addr));
        printf("Dest IP  : %sn", inet_ntoa(tempDest.sin_addr));
        printf("Data     : %sn", payload);
        printf("Port     : %dnn", 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