You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

问题核心原因

  1. 错误的bind调用:AF_PACKET类型的SOCK_RAW套接字,bind操作是用来指定接收数据包的过滤规则,而非设置发送的目的地址。绑定到目的MAC会导致套接字只能接收该MAC的数据包,对发送逻辑毫无帮助,甚至可能干扰发送。
  2. sendto参数错误:发送时传入的sockaddr应该是包含目的MAC的daddr结构体,而非源MAC的saddr。内核需要这个结构体来确定数据包的发送目标。
  3. 以太网类型字段非法: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 10:18:08