Ubuntu 22.04虚拟机RAW包Ethernet Header目标MAC配置异常
以太网帧头目的MAC设置问题排查与修正
我在VMware Workstation中部署了两台Ubuntu 22.04虚拟机,能在Wireshark中捕获到RAW数据包,但以太网帧头(Ethernet Header)的配置不符合预期。我期望的帧头格式是:目的MAC 源MAC 数据包长度 内容,但目前无法正确设置目的MAC地址,相关C语言代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/ioctl.h> #include <net/if.h> #include <linux/if_packet.h> #include <net/ethernet.h> #include <arpa/inet.h> #include <linux/filter.h> #include <fcntl.h> int main() { int s, stat; ssize_t cc; unsigned char buf[ETH_FRAME_LEN]; struct sockaddr_ll saddr = {}; struct ifreq ifopts; // for promiscuous mode struct ifreq ifr = {}; const char *interface = "enp2s1"; s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (s < 0) { perror("socket"); exit(EXIT_FAILURE); } // Set Promiscuous Mode strncpy(ifopts.ifr_name, interface, IFNAMSIZ-1); ioctl(s, SIOCGIFFLAGS, &ifopts); ifopts.ifr_flags |= IFF_PROMISC; ioctl(s, SIOCSIFFLAGS, &ifopts); strncpy(ifr.ifr_name, interface, IFNAMSIZ-1); if (ioctl(s, SIOGIFINDEX, &ifr)) { perror("ioctl"); close(s); exit(EXIT_FAILURE); } // Source MAC address saddr.sll_family = AF_PACKET; saddr.sll_ifindex = ifr.ifr_ifindex; saddr.sll_protocol = htons(ETH_P_ALL); saddr.sll_halen = ETH_ALEN; unsigned char src_mac[ETH_ALEN] = {0x00, 0x0d, 0x16, 0xb8, 0x36, 0x99}; memcpy(saddr.sll_addr, src_mac, ETH_ALEN); stat = fcntl(s, F_SETFL, O_NONBLOCK); if (stat < 0) perror("bloqueante"); const char *message = "Hello World"; size_t message_len = strlen(message); struct sockaddr_ll daddr = {}; struct ethhdr *eth = (struct ethhdr *)buf; // Destination MAC address daddr.sll_family = AF_PACKET; daddr.sll_protocol = htons(ETH_P_ALL); daddr.sll_ifindex = ifr.ifr_ifindex; daddr.sll_halen = ETH_ALEN; unsigned char dest_mac[ETH_ALEN] = {0x00, 0x0d, 0x29, 0x26, 0xad, 0x98}; memcpy(daddr.sll_addr, dest_mac, ETH_ALEN); if (bind(s, (struct sockaddr *)&daddr, sizeof(daddr)) < 0) { perror("bind"); exit(EXIT_FAILURE); } // Prepare Ethernet header memcpy(eth->h_dest, daddr.sll_addr, ETH_ALEN); memcpy(eth->h_source, saddr.sll_addr, ETH_ALEN); eth->h_proto = htons(ETH_P_ALL); const char *message2 = "Byebye Moon"; size_t message_len2 = strlen(message2); // Prepare message payload memcpy(buf + sizeof(struct ethhdr), message, message_len); memcpy(buf + sizeof(struct ethhdr) + message_len, message2, message_len2); // Send the packet cc = sendto(s, buf, sizeof(struct ethhdr) + message_len + message_len2, 0, (struct sockaddr *)&saddr, sizeof(saddr)); if (cc < 0) { perror("sendto"); exit(EXIT_FAILURE); } close(s); return 0; }
问题核心原因
- 错误的bind调用:
AF_PACKET类型的SOCK_RAW套接字,bind操作是用来指定接收数据包的过滤规则,而非设置发送的目的地址。绑定到目的MAC会导致套接字只能接收该MAC的数据包,对发送逻辑毫无帮助,甚至可能干扰发送。 - sendto参数错误:发送时传入的
sockaddr应该是包含目的MAC的daddr结构体,而非源MAC的saddr。内核需要这个结构体来确定数据包的发送目标。 - 以太网类型字段非法:
ETH_P_ALL是用于捕获所有数据包的过滤值,不能作为实际发送的以太网类型。需要使用合法的类型,比如自定义私有类型htons(0x88B5),或者标准类型如IPv4的htons(0x0800)。
修正后的代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/ioctl.h> #include <net/if.h> #include <linux/if_packet.h> #include <net/ethernet.h> #include <arpa/inet.h> #include <fcntl.h> int main() { int s; ssize_t cc; unsigned char buf[ETH_FRAME_LEN]; struct sockaddr_ll daddr = {}; struct ifreq ifopts; struct ifreq ifr = {}; const char *interface = "enp2s1"; // 创建原始套接字,指定发送的以太网类型为自定义私有类型 s = socket(AF_PACKET, SOCK_RAW, htons(0x88B5)); if (s < 0) { perror("socket"); exit(EXIT_FAILURE); } // 设置混杂模式(如果需要捕获数据包,否则可以移除) strncpy(ifopts.ifr_name, interface, IFNAMSIZ-1); ioctl(s, SIOCGIFFLAGS, &ifopts); ifopts.ifr_flags |= IFF_PROMISC; ioctl(s, SIOCSIFFLAGS, &ifopts); // 获取接口索引 strncpy(ifr.ifr_name, interface, IFNAMSIZ-1); if (ioctl(s, SIOGIFINDEX, &ifr)) { perror("ioctl"); close(s); exit(EXIT_FAILURE); } // 配置目的地址结构体 daddr.sll_family = AF_PACKET; daddr.sll_ifindex = ifr.ifr_ifindex; daddr.sll_halen = ETH_ALEN; unsigned char dest_mac[ETH_ALEN] = {0x00, 0x0d, 0x29, 0x26, 0xad, 0x98}; memcpy(daddr.sll_addr, dest_mac, ETH_ALEN); // 准备以太网帧头 struct ethhdr *eth = (struct ethhdr *)buf; memcpy(eth->h_dest, dest_mac, ETH_ALEN); // 获取接口实际的源MAC(也可以手动指定,但推荐用系统接口MAC) strncpy(ifr.ifr_name, interface, IFNAMSIZ-1); if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0) { perror("SIOCGIFHWADDR"); close(s); exit(EXIT_FAILURE); } memcpy(eth->h_source, ifr.ifr_hwaddr.sa_data, ETH_ALEN); eth->h_proto = htons(0x88B5); // 匹配套接字创建时的以太网类型 // 准备负载数据 const char *message = "Hello World"; size_t message_len = strlen(message); const char *message2 = "Byebye Moon"; size_t message_len2 = strlen(message2); size_t total_len = sizeof(struct ethhdr) + message_len + message_len2; memcpy(buf + sizeof(struct ethhdr), message, message_len); memcpy(buf + sizeof(struct ethhdr) + message_len, message2, message_len2); // 发送数据包,传入目的地址结构体 cc = sendto(s, buf, total_len, 0, (struct sockaddr *)&daddr, sizeof(daddr)); if (cc < 0) { perror("sendto"); close(s); exit(EXIT_FAILURE); } printf("Sent %zd bytes\n", cc); close(s); return 0; }
关键修改说明
- 移除了错误的
bind调用,不再绑定目的MAC sendto调用时传入目的MAC的sockaddr_ll结构体daddr,让内核明确发送目标- 替换了非法的
ETH_P_ALL以太网类型为合法的自定义私有类型0x88B5 - 改为获取系统接口的实际源MAC,避免手动指定可能的不匹配问题
- 明确计算数据包总长度,确保发送长度正确
内容的提问来源于stack exchange,提问作者Anthony 12
相关产品推荐
相关产品推荐

