XDP-eBPF无法捕获多播IPv4数据包及MAC地址打印咨询
sender程序可正常发送多播数据包,listener程序也能正常接收,但XDP内核代码无法正确识别该多播数据包。仅关注IPv4协议,XDP追踪日志显示多播包的以太网协议ID为0xC0A8,而普通IPv4数据包的以太网协议ID为0x800。需解答:
- 导致该问题的原因是什么?
- 如何在XDP代码中打印以太网头部的
h_dest(MAC地址)?
问题1:原因分析
0xC0A8是IPv4地址段192.168.x.x的十六进制表示(C0=192,A8=168),这说明你在XDP代码中错误解析了以太网头部。
核心原因是:处理发送方向的多播包时,XDP挂载的网卡上,数据包的以太网头部可能未被硬件/内核完全组装,或者你混淆了数据包方向(发送/接收),导致解析时eth->h_proto读取到的是MAC地址的后两个字节,而非真正的以太网协议字段。
从日志数据看,多播包长度为45字节,普通IPv4包为60字节(包含14字节以太网头部),多播包的长度计算异常,进一步佐证了解析偏移错误的问题。
问题2:打印以太网目标MAC地址的方法
XDP中bpf_printk不直接支持数组格式化输出,需手动遍历h_dest数组,逐个打印每个字节的十六进制值。修改XDP代码的以太网头部处理部分,添加如下代码:
if ((eth + 1) <= data_end) { // 打印目标MAC地址 bpf_printk("Dest MAC: %02x:%02x:%02x:%02x:%02x:%02x", eth->h_dest[0], eth->h_dest[1], eth->h_dest[2], eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]); // 原有协议打印逻辑 bpf_printk("ETH %ld 0x%X",data_end-data,bpf_ntohs(eth->h_proto)); // ... 后续原有代码 }
注意:bpf_printk的参数数量存在限制(通常最多10个),上述代码的参数数量符合要求。
附:相关代码
以太网头部结构示例
struct ethhdr { unsigned char h_dest[ETH_ALEN]; /* 目标MAC地址 */ unsigned char h_source[ETH_ALEN]; /* 源MAC地址 */ __be16 h_proto; /* 协议类型ID字段 */ } __attribute__((packed));
xdp_kernel.c(修改后)
#include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> #include <linux/if_ether.h> #include <linux/ip.h> #include <linux/udp.h> #include <linux/in.h> SEC("xdp") int xdp_test(struct xdp_md *ctx) { bpf_printk("PACKET RECIEVED"); void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; void *pos = data; struct ethhdr *eth = (struct ethhdr *)(pos); if ((eth + 1) <= data_end) { // 新增打印MAC地址的代码 bpf_printk("Dest MAC: %02x:%02x:%02x:%02x:%02x:%02x", eth->h_dest[0], eth->h_dest[1], eth->h_dest[2], eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]); bpf_printk("ETH %ld 0x%X",data_end-data,bpf_ntohs(eth->h_proto)); if (bpf_ntohs(eth->h_proto) == ETH_P_IP) { //bpf_printk("IP"); pos += sizeof(struct ethhdr); struct iphdr *iph = (struct iphdr *)(pos); if ((iph + 1) <= data_end) { if (iph->protocol == IPPROTO_UDP) { bpf_printk("UDP"); const __u16 iph_sz_in_bytes = iph->ihl * 4; if (((void *)iph + iph_sz_in_bytes) <= data_end) { pos += iph_sz_in_bytes; struct udphdr *udh = (struct udphdr *)(pos); if ((udh + 1) <= data_end) { } } } } } } return XDP_PASS; } char _license[] SEC("license") = "GPL";
sender.c
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <stdlib.h> int main() { const int delay_secs = 1; const char *message2 = "Hello, World!"; unsigned long counter = 0; char message[8192] = {0}; int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return 1; } struct ip_mreq mreq; memset(&mreq, 0, sizeof(mreq)); mreq.imr_interface.s_addr = inet_addr("192.168.2.6"); // 本地WiFi接口地址 if ( setsockopt( fd, IPPROTO_IP, IP_MULTICAST_IF, (char *)&mreq, sizeof(mreq)) < 0) { perror("setsockopt"); return 1; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("239.0.0.0"); addr.sin_port = htons(1234); while (1) { char ch = 0; snprintf(message, 8192, "%s-%lu", message2, ++counter); int nbytes = sendto( fd, message, strlen(message), 0, (struct sockaddr *)&addr, sizeof(addr)); if (nbytes < 0) { perror("sendto"); return 1; } sleep(delay_secs); } return 0; }
listener.c
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <time.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #define MSGBUFSIZE 8192 int main() { int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return 1; } u_int yes = 1; if ( setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)) < 0) { perror("Reusing ADDR failed"); return 1; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("239.0.0.0"); addr.sin_port = htons(1234); if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; } struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr("239.0.0.0"); mreq.imr_interface.s_addr = inet_addr("192.168.2.6"); // 本地WiFi地址 if ( setsockopt( fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) < 0) { perror("setsockopt"); return 1; } while (1) { char msgbuf[MSGBUFSIZE]; int addrlen = sizeof(addr); int nbytes = recvfrom( fd, msgbuf, MSGBUFSIZE, 0, (struct sockaddr *)&addr, &addrlen); if (nbytes < 0) { perror("recvfrom"); return 1; } msgbuf[nbytes] = '\0'; puts(msgbuf); } return 0; }
附:XDP追踪日志
多播包日志
sender-28811 [002] ..s21 11044.326101: bpf_trace_printk: PACKET RECIEVED
sender-28811 [002] ..s21 11044.326122: bpf_trace_printk: ETH 45 0xC0A8
普通IPv4包日志
irq/168-iwlwifi-596 [006] ..s21 11041.649288: bpf_trace_printk: PACKET RECIEVED
irq/168-iwlwifi-596 [006] ..s21 11041.649298: bpf_trace_printk: ETH 60 0x800
内容的提问来源于stack exchange,提问作者Nagaraj

