使用C++向指定IP发送完整以太网包失败,求技术解决方案
解决原始完整数据包发送的问题
看起来你想直接发送已经包含所有层级头部的完整二进制数据包,不想让系统添加任何额外封装,但你的代码在connect步骤失败了——这很正常,因为你用错了套接字类型和地址结构。让我一步步帮你修正:
原代码的核心问题
SOCK_PACKET是Linux上过时的原始套接字接口,现在推荐使用SOCK_RAWconnect()函数只适用于TCP/UDP这类传输层套接字,而SOCK_PACKET/SOCK_RAW工作在链路层,无法通过connect指定目标IP来建立连接- 你用了
struct sockaddr_in(网络层地址结构),但链路层套接字需要用struct sockaddr_ll来指定网络接口和目标MAC地址
正确的实现思路
要发送完整的数据包(包含链路层、IP层、传输层所有头部),你需要:
- 创建链路层的原始套接字
- 确保系统不会修改你的数据包(比如设置
IP_HDRINCL选项,如果你的数据包包含IP头部) - 获取目标IP对应的MAC地址(链路层必须用MAC地址来寻址)
- 使用
sendto()直接发送完整的二进制数据包到指定的网络接口
完整代码示例
#include <iostream> #include <string> #include <cstring> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/udp.h> #include <net/if.h> #include <net/ethernet.h> #include <arpa/inet.h> #include <unistd.h> #include <sys/ioctl.h> // 辅助函数:通过IP地址获取对应的MAC地址(需要root权限) bool get_mac_address(const std::string& ip, unsigned char* mac, const std::string& iface) { int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("socket"); return false; } struct arpreq arp_req; memset(&arp_req, 0, sizeof(arp_req)); strncpy(arp_req.arp_dev, iface.c_str(), IFNAMSIZ-1); arp_req.arp_pa.sa_family = AF_INET; inet_pton(AF_INET, ip.c_str(), &((struct sockaddr_in*)&arp_req.arp_pa)->sin_addr); if (ioctl(sock, SIOCGARP, &arp_req) < 0) { perror("ioctl SIOCGARP"); close(sock); return false; } memcpy(mac, arp_req.arp_ha.sa_data, 6); close(sock); return true; } bool send_raw_packet(const std::string& dest_ip, const std::string& iface, const unsigned char* packet_data, size_t packet_size) { // 1. 创建链路层原始套接字 int sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (sock_fd < 0) { perror("socket creation failed"); return false; } // 2. 获取目标MAC地址 unsigned char dest_mac[6]; if (!get_mac_address(dest_ip, dest_mac, iface)) { std::cerr << "Failed to get MAC address for " << dest_ip << std::endl; close(sock_fd); return false; } // 3. 准备链路层地址结构 struct sockaddr_ll sock_addr; memset(&sock_addr, 0, sizeof(sock_addr)); sock_addr.sll_family = AF_PACKET; sock_addr.sll_protocol = htons(ETH_P_ALL); sock_addr.sll_ifindex = if_nametoindex(iface.c_str()); // 获取网络接口索引 sock_addr.sll_halen = ETH_ALEN; memcpy(sock_addr.sll_addr, dest_mac, 6); // 4. 发送完整数据包 ssize_t sent_bytes = sendto(sock_fd, packet_data, packet_size, 0, (struct sockaddr*)&sock_addr, sizeof(sock_addr)); if (sent_bytes < 0) { perror("sendto failed"); close(sock_fd); return false; } std::cout << "Successfully sent " << sent_bytes << " bytes" << std::endl; close(sock_fd); return true; } int main() { std::string dest_ip = "127.0.0.1"; std::string iface = "lo"; // 本地回环接口,根据你的实际网络接口修改(比如eth0、wlan0) unsigned char your_full_packet[] = { /* 这里填入你的完整二进制数据包内容 */ }; size_t packet_size = sizeof(your_full_packet); if (!send_raw_packet(dest_ip, iface, your_full_packet, packet_size)) { return 1; } return 0; }
关键注意事项
- 必须以root权限运行:原始套接字需要管理员权限才能创建
- 回环接口的特殊情况:如果发送到
127.0.0.1,使用回环接口lo时,链路层头部会被系统忽略(因为回环不需要实际的以太网帧),你可以直接发送包含IP+传输层头部的数据包,不需要链路层头部 - 数据包完整性:确保你的
your_full_packet包含了所有必要的头部(如果是以太网帧,需要包含目标MAC、源MAC、以太网类型;如果是IP数据包,需要包含IP头部+传输层头部+数据) - 跨平台差异:这个代码是针对Linux的,BSD/macOS的原始套接字实现会有差异,比如需要用
AF_LINK而不是PF_PACKET
内容的提问来源于stack exchange,提问作者Ahmed Hussein
相关产品推荐
相关产品推荐

