为何Linux原始套接字有时返回h_source和h_dest全零?
嘿,我来帮你梳理这个问题——你遇到的MAC全零数据包并不是程序Bug,而是Linux网络栈的特殊处理逻辑导致的,结合你的捕获数据和代码,咱们一步步说清楚:
为什么会出现MAC全零的数据包?
你捕获的那个数据包里,IP地址是127.0.0.1(本地回环)和127.0.0.53(本地DNS服务端口),这是典型的本地环回通信数据包。Linux内核为了优化本地通信,不会把这类数据包送到物理网卡,而是直接在网络栈内部完成转发。对于AF_PACKET类型的原始套接字来说,这类环回包的以太网帧头会被内核填充成全零——因为它们根本没经过物理网卡的MAC层处理,自然没有真实的源/目的MAC地址。
另外,你的代码还有一个小疏漏:你创建的原始套接字没有绑定到指定的物理网卡(比如你的wlp2s0),默认会监听所有网络接口,包括环回接口lo。这就导致你的程序会接收到环回接口的数据包,而这类数据包的MAC头就是全零的。
顺带提一句,你打印的十六进制里出现ffffffxx是因为char在Linux默认是有符号类型,当字节值大于0x7F时会被解析为负数,用%x打印就会补全成32位的负数值。这个是打印格式的问题,不是数据包本身的问题。
怎么解决这个问题?
1. 绑定到指定物理网卡
修改代码,在获取网卡索引后,把套接字绑定到该网卡上,这样就只会接收该物理网卡的数据包,不会收到环回接口的包:
// 在获取ifreq_i.ifr_ifindex之后添加这段代码 struct sockaddr_ll sll; memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; sll.sll_protocol = htons(ETH_P_ALL); sll.sll_ifindex = ifreq_i.ifr_ifindex; // 使用你获取的wlp2s0索引 if (bind(sockfd, (struct sockaddr *)&sll, sizeof(sll)) == -1) { error_print("Failed to bind to specified interface"); }
2. 过滤环回接口的数据包
如果不想绑定网卡,也可以在接收数据包后,检查来源接口的索引,跳过环回接口(通常环回接口的索引是1,可通过ip link命令确认):
// 在recvfrom之后,获取来源接口的索引 struct sockaddr_ll *from_addr = (struct sockaddr_ll *)&sockaddr_details; if (from_addr->sll_ifindex == 1) { printf("Skipping loopback packet\n"); // 跳过处理逻辑 } else { print_mac(buffer, recv_size); }
3. 修复打印格式问题
把打印缓冲区时的buffer[j]转换成unsigned char,避免负数导致的ffffffxx前缀:
// 替换原来的打印行 printf("%02x ", (unsigned char)buffer[j]);
验证一下
你可以手动发起一个本地DNS查询(比如dig @127.0.0.1 google.com),然后看你的程序是否会捕获到MAC全零的数据包——如果绑定了物理网卡,就不会再收到这类包了;如果没绑定,过滤环回接口后也能跳过它们。
内容的提问来源于stack exchange,提问作者KMG

