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

Linux Ubuntu下C语言SOCK_RAW发送二层广播包errno=22问题排查

问题

在Ubuntu Linux下用C语言做二层Socket编程,尝试仅通过MAC地址通信,先发送目标MAC为ff:ff:ff:ff:ff:ff的广播包。以sudo权限运行代码时返回errno=22(无效参数),但将socket调用里的SOCK_RAW改为SOCK_DGRAM后就能正常运行,请问这是为什么?

运行输出

Address setted:
address[0]: ffffffff
address[1]: ffffffff
address[2]: ffffffff
address[3]: ffffffff
address[4]: ffffffff
address[5]: ffffffff
InterfaceIndex detected: 2
Opening a socket done.
Setting multicast address done.
Result: -1
errno: 22

代码

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <linux/filter.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <errno.h>

#define true 1
#define false 0

typedef char bool;

struct socket_address
{
    int sll_ifindex;
    int sll_hatype;
    int sll_pkttype;
    int sll_halen;
};

struct sEthernetSocket {
    int rawSocket;
    bool isBind;
    struct sockaddr_ll socketAddress;
};

typedef struct sEthernetSocket *EthernetSocket;



static int getInterfaceIndex(int sock, const char* deviceName)
{
    struct ifreq ifr;
    memset(&ifr, 0, sizeof(struct ifreq));

    strncpy(ifr.ifr_name, deviceName, IFNAMSIZ - 1);

    if (ioctl(sock, SIOCGIFINDEX, &ifr) == -1) {
        printf("ETHERNET_LINUX: Failed to get interface index\n");
        return -1;
    }
    else {
        printf("InterfaceIndex detected: %d\n", ifr.ifr_ifindex);
    }

    int interfaceIndex = ifr.ifr_ifindex;

    return interfaceIndex;
}

void Ethernet_destroySocket(EthernetSocket ethSocket)
{
    close(ethSocket->rawSocket);
    free(ethSocket);
}

EthernetSocket Ethernet_createSocket(const char* interfaceId, uint8_t* destAddress)
{
    EthernetSocket ethernetSocket = calloc(1, sizeof(struct sEthernetSocket));

    if (ethernetSocket) {
        ethernetSocket->rawSocket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

        if (ethernetSocket->rawSocket == -1) {
            printf("Error creating raw socket!\n");
            free(ethernetSocket);
            return NULL;
        }

        ethernetSocket->socketAddress.sll_family = PF_PACKET;
        ethernetSocket->socketAddress.sll_protocol = htons(ETH_P_ALL);

        int ifcIdx =  getInterfaceIndex(ethernetSocket->rawSocket, interfaceId);

        if (ifcIdx == -1) {
            Ethernet_destroySocket(ethernetSocket);
            return NULL;
        }

        ethernetSocket->socketAddress.sll_ifindex = ifcIdx;

        ethernetSocket->socketAddress.sll_hatype =  ARPHRD_ETHER;
        ethernetSocket->socketAddress.sll_pkttype = PACKET_HOST | PACKET_MULTICAST;

        ethernetSocket->socketAddress.sll_halen = ETH_ALEN;

        memset(ethernetSocket->socketAddress.sll_addr, 0, 8);

        if (destAddress != NULL){
            memcpy(ethernetSocket->socketAddress.sll_addr, destAddress, 6);
        }

        ethernetSocket->isBind = false;
    }

    return ethernetSocket;
}

int Ethernet_sendPacket(EthernetSocket ethSocket, uint8_t* buffer, int packetSize)
{
    return sendto(ethSocket->rawSocket, buffer, packetSize,
               0, (struct sockaddr*) &(ethSocket->socketAddress), sizeof(ethSocket->socketAddress));
    // return write(ethSocket->rawSocket, buffer, strlen(buffer)); 

}

void
Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr)
{
    struct ifreq buffer;

    int sock = socket(PF_INET, SOCK_DGRAM, 0);

    memset(&buffer, 0x00, sizeof(buffer));

    strncpy(buffer.ifr_name, interfaceId, IFNAMSIZ - 1);

    ioctl(sock, SIOCGIFHWADDR, &buffer);

    close(sock);

    int i;

    for(i = 0; i < 6; i++ )
    {
        addr[i] = (unsigned char)buffer.ifr_hwaddr.sa_data[i];
    }
}

void
Ethernet_addMulticastAddress(EthernetSocket ethSocket, uint8_t* multicastAddress)
{
    struct packet_mreq mreq;
    mreq.mr_ifindex = ethSocket->socketAddress.sll_ifindex;
    mreq.mr_alen = ETH_ALEN;
    mreq.mr_type = PACKET_MR_MULTICAST;
    mreq.mr_address[0] = multicastAddress[0];
    mreq.mr_address[1] = multicastAddress[1];
    mreq.mr_address[2] = multicastAddress[2];
    mreq.mr_address[3] = multicastAddress[3];
    mreq.mr_address[4] = multicastAddress[4];
    mreq.mr_address[5] = multicastAddress[5];

    int res = setsockopt(ethSocket->rawSocket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));


    if (res != 0) {
        printf("ETHERNET_LINUX: Setting multicast address failed");
    }
}


int main(){
    //-------------------------Setting destination address-------------------------//
    char address[100];
    //Ethernet_getInterfaceMACAddress("enp2s0", &address);
    for(int i = 0; i < 6; i++)address[i] = 0xff;
    printf("Address setted:\n");
    for(int i = 0; i < 6; i++)printf("address[%d]: %x\n",i , address[i]);
    //-------------------------------Opening a socket------------------------------//
    EthernetSocket ethSocket = Ethernet_createSocket("enp2s0", address);
    if(ethSocket) printf("Opening a socket done.\n");
    else printf("Error opening a socket.\n");
    //--------------------------Setting multicast address--------------------------//
    Ethernet_addMulticastAddress(ethSocket, address);
    printf("Setting multicast address done.\n");
    //---------------------------------Sending data--------------------------------//
    int res = Ethernet_sendPacket(ethSocket, "Hello", 6);
    printf("Result: %d\nerrno: %d\n", res, errno);

   
}

原因及解决方案

核心区别:SOCK_RAW vs SOCK_DGRAM(AF_PACKET域下)

在Linux的AF_PACKET套接字域中,这两种套接字类型的职责完全不同:

  • SOCK_RAW:要求你手动构造完整的以太网帧,包括目的MAC、源MAC、以太网类型(EtherType)这14字节的帧头,再加上上层数据。内核不会帮你处理任何帧头的组装,只会把你传入的buffer原封不动地发送到网络上。如果传入的buffer长度不足(至少要14字节帧头+数据)或者格式不符合以太网帧规范,内核就会返回EINVAL(错误码22,无效参数)。
  • SOCK_DGRAM:由内核负责组装以太网帧头。你只需要传入上层数据,内核会自动填充目的MAC(从sockaddr_ll中获取)、源MAC(接口的物理地址)、合法的EtherType,再把数据封装成完整帧发送,所以不需要你手动处理帧头,自然不会出现参数错误。

你的代码问题

你用SOCK_RAW时,sendto传入的buffer只是"Hello"(6字节),没有包含任何以太网帧头,长度甚至连帧头的14字节都达不到,内核校验时直接判定为无效数据包,返回错误。

修复SOCK_RAW发送的方法

要让SOCK_RAW正常发送,需要构造完整的以太网帧:

  1. 分配足够的缓冲区,大小为ETH_HLEN(14字节,以太网帧头长度)加上数据长度。
  2. 填充帧头:
    • 前6字节:目的MAC地址(广播地址ff:ff:ff:ff:ff:ff)
    • 接下来6字节:源MAC地址(可以通过Ethernet_getInterfaceMACAddress获取接口的MAC)
    • 最后2字节:以太网类型(比如htons(ETH_P_IP)或者自定义合法的16位值)
  3. 在帧头之后拼接你的数据(比如"Hello")
  4. 调用sendto发送完整的帧缓冲区

以下是修改后的Ethernet_sendPacket示例(需要先获取源MAC):

int Ethernet_sendPacket(EthernetSocket ethSocket, uint8_t* buffer, int packetSize)
{
    // 构造完整以太网帧
    uint8_t frame[ETH_HLEN + packetSize];
    uint8_t src_mac[6];
    Ethernet_getInterfaceMACAddress("enp2s0", src_mac);

    // 填充目的MAC
    memcpy(frame, ethSocket->socketAddress.sll_addr, 6);
    // 填充源MAC
    memcpy(frame + 6, src_mac, 6);
    // 填充以太网类型(自定义类型,比如0x1234测试)
    *(uint16_t*)(frame + 12) = htons(0x1234);
    // 填充数据
    memcpy(frame + ETH_HLEN, buffer, packetSize);

    return sendto(ethSocket->rawSocket, frame, ETH_HLEN + packetSize,
               0, (struct sockaddr*) &(ethSocket->socketAddress), sizeof(ethSocket->socketAddress));
}

内容的提问来源于stack exchange,提问作者Mohammad Mahdi Moeini Manesh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 22:24:20