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正常发送,需要构造完整的以太网帧:
- 分配足够的缓冲区,大小为
ETH_HLEN(14字节,以太网帧头长度)加上数据长度。 - 填充帧头:
- 前6字节:目的MAC地址(广播地址
ff:ff:ff:ff:ff:ff) - 接下来6字节:源MAC地址(可以通过
Ethernet_getInterfaceMACAddress获取接口的MAC) - 最后2字节:以太网类型(比如
htons(ETH_P_IP)或者自定义合法的16位值)
- 前6字节:目的MAC地址(广播地址
- 在帧头之后拼接你的数据(比如
"Hello") - 调用
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
相关产品推荐
相关产品推荐

