通过Linux套接字接收多个主机的数据
Receiving Data for Multiple Hosts via Linux Sockets
我有一个很奇怪的问题。最近,我的任务是开发软件来模拟一个大型(数百个节点和以上)网络。长话短说,我们有一个头端服务器,它通过可预测的IP寻址方案,通过混合使用广播和单播的Linux套接字与每个主机通信。头端将向给定的客户端发出请求,并(有时)接收与执行的命令有关的数据。所有数据/命令都通过UDP在定义良好的端口上发送。
现在,出于测试目的,我们希望在虚拟环境中使用原始服务器二进制文件,以便仍然接收合理的数据。例如,我们希望向特定节点发出重置命令,并接收假通知。广播部分很容易,因为我只需要收听正确的广播地址并采取相应的行动。单播让我陷入困境。
问题
是否可以通过单个(或减少)数量的Linux套接字接收大量离散主机的UDP请求?所有主机都在同一个子网上,并且所有IP地址/主机/网络拓扑都提前知道。
所需输出
最终,我们希望有一个应用程序在网络上的主机上运行,并根据输入数据报做出响应,就好像它是这些离散的"虚拟化"主机一样。
请注意,我并不是要求别人给我写一个程序。我只是在寻找实现这一目标的"工具"的一些方向。
可能的解决方案
-
RAW套接字:这很有希望,因为我可以通过单个套接字,并将其转移到工作线程以进行处理和回答不幸的是,我只收到目的地是我的主机IP,而不是"假"IP。
-
在Linux上滥用IP别名,每个主机一个:这似乎是最直接的方法,但感觉就像用火箭筒猎鸭。它还有一个额外的好处,那就是看起来"是"任何其他形式通信的主机,我只是担心创建400多个别名可能对我们Linux环境的私生子来说有点过分。作为一个额外的复杂性,主机会根据配置进行更改,并且可以处于任何状态(向上、向下、命令处理等)
为了进行测试,服务器的源代码将被视为不可变的。我完全预计,在给定的限制条件下,这是不可能的,但有人可能知道如何实现这一点,因为坦率地说,我以前从未做过这样的事情。
提前感谢您的帮助。
就我个人而言,我会使用您的第二个选项-将所有IP地址添加到主机,然后绑定到INADDR_ANY
地址。这意味着您可以只使用一个套接字。
另一种选择是在套接字上设置IP_TRANSPARENT
套接字选项,这将允许应用程序绑定到非本地地址(您将通过运行应用程序的计算机路由包含这些地址的网络)。不过,这种方法确实需要每个地址有一个套接字。
因此,结合使用caf的两种解决方案,我可以既吃蛋糕又吃蛋糕。我也深受的影响
Python/iptables:捕获所有UDP数据包及其原始目的地
这是一个Python示例,但它确实展示了我如何将数据包"欺骗"回单个接口,从而消除了维护许多套接字的需要。这个问题很值得一读,包含了很多好的信息。不过,为了简洁起见,我将在下面重述部分内容。
希望它能帮助其他人。
第1部分-主机配置
如上所述,我们可以使用iptables
和ip
路由的组合来将数据包重定向到环回进行处理。这在我最初的问题中没有说明,但"模拟器"在头端主机本身上运行,而不是网络上的离散节点,这是可以接受的。为此,我们通过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
- 防止主数据类型C++的隐式转换
- 用于访问容器<T>数据成员的正确 API
- 嵌套在类中时无法设置成员数据
- 使用流处理接收到的数据
- Cuda C++:设备上的Malloc类,并用来自主机的数据填充它
- 来自复杂主机数据的阵列火力阵列
- 通过套接字一致地丢失数据(但在使用本地主机连接时不会)
- C++ 如何接收从互联网站点到本地主机的流数据包
- 将数据保存到空闲主机上的文件中
- 将二进制数据从Qt/C++DLL传递到Delphi主机应用程序中
- 通过Linux套接字接收多个主机的数据
- 如何将设备内存中分配的结构化数据从设备复制到主机
- 在将复杂数据从主机传输到设备的简单 CUDA 代码中出现问题
- MPI_Gather-向主机发送数据
- UDP发送()到本地主机在Winsock下丢弃数据包
- 保持主机数据完整,同时传输到CUDA GPU
- 从Oracle PL/SQL中获取数据到主机阵列
- Gstreamer:为什么我不能在本地主机上通过UDP发送数据?
- 从主机内部通过网络发送midi数据
- CUDA扩展std::vector来管理主机和设备数据