因此,结合使用 caf 的两种解决方案,我能够吃到我的蛋糕。我也深受
的影响
Python/iptables: Capturing all UDP packets and their original destination
这是一个 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 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