C语言ARP扫描程序开发疑问及无输出代码调试求助
问题解答与代码修复
一、ARP相关问题解答
使用ARP请求实现此需求是否合理?
完全合理。ARP(地址解析协议)的核心作用就是实现IP地址与MAC地址的映射,通过向子网内发送ARP广播请求,活跃的设备会返回ARP响应包,其中包含自身的MAC地址。这种方式不需要依赖任何命令行工具,纯底层代码实现,完全符合需求。是否能够获取非当前所在子网的设备MAC地址?
不能。ARP是链路层协议,仅能在同一广播域(即同一子网)内工作。当目标IP不在当前子网时,系统会将ARP请求发送给本地网关,而非直接广播到目标子网,网关只会返回自身的MAC地址,不会转发ARP请求到其他子网,因此无法通过ARP获取跨子网设备的MAC地址。
二、代码问题分析与修复
你的代码无输出主要由多个关键问题导致,下面是修复后的完整代码及问题说明:
核心问题点
- 无效MAC硬编码:
0xQS是非法十六进制值,会导致编译或运行错误;硬编码网卡信息也容易与实际环境不符,建议自动获取。 - 重复发送请求:原代码连续调用两次
sendto,导致重复发包且第一次长度参数错误。 - 接收逻辑错误:错误将响应的sender IP与自身IP对比,应该判断响应IP是否为请求的目标IP。
- 无限接收无超时:原接收函数为死循环,会一直阻塞程序,需设置超时退出逻辑。
- 缓冲区过小:过小的缓冲区可能导致数据包截断,需适当增大。
修复后的完整代码
#include <linux/if_ether.h> #include <sys/ioctl.h> #include <net/if.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <netpacket/packet.h> #include <arpa/inet.h> #include <unistd.h> #include <netinet/ether.h> #include <errno.h> #define BUFFER_SIZE 1024 #define TIMEOUT_SEC 5 // 接收超时时间 struct arp_header { uint16_t hardware_type; uint16_t protocol_type; uint8_t hardware_len; uint8_t protocol_len; uint16_t opcode; uint8_t sender_mac[6]; uint8_t sender_ip[4]; uint8_t target_mac[6]; uint8_t target_ip[4]; }; // 获取指定网卡的MAC和IP地址 int get_interface_info(const char* ifname, uint8_t* mac, uint8_t* ip) { int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("socket failed"); return -1; } struct ifreq ifr; strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); // 获取MAC地址 if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) { perror("SIOCGIFHWADDR failed"); close(sock); return -1; } memcpy(mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN); // 获取IP地址 if (ioctl(sock, SIOCGIFADDR, &ifr) < 0) { perror("SIOCGIFADDR failed"); close(sock); return -1; } struct sockaddr_in* addr = (struct sockaddr_in*)&ifr.ifr_addr; memcpy(ip, &addr->sin_addr.s_addr, 4); // 转换为主机字节序 for (int i = 0; i < 4; i++) { ip[i] = ((uint8_t*)&addr->sin_addr.s_addr)[i]; } close(sock); return 0; } void create_arp_request(uint8_t *sender_mac, uint8_t *sender_ip, uint8_t *target_ip, uint8_t *packet) { // 填充以太网头部 struct ethhdr *eth = (struct ethhdr *) packet; uint8_t broadcast_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; memcpy(eth->h_dest, broadcast_mac, ETH_ALEN); memcpy(eth->h_source, sender_mac, ETH_ALEN); eth->h_proto = htons(ETH_P_ARP); // 填充ARP头部 struct arp_header arp; memset(&arp, 0, sizeof(struct arp_header)); arp.hardware_type = htons(ARPHRD_ETHER); arp.protocol_type = htons(ETH_P_IP); arp.hardware_len = ETH_ALEN; arp.protocol_len = sizeof(in_addr_t); arp.opcode = htons(ARPOP_REQUEST); memcpy(arp.sender_mac, sender_mac, ETH_ALEN); memcpy(arp.sender_ip, sender_ip, sizeof(in_addr_t)); memset(arp.target_mac, 0, ETH_ALEN); memcpy(arp.target_ip, target_ip, sizeof(in_addr_t)); memcpy(packet + sizeof(struct ethhdr), &arp, sizeof(struct arp_header)); } void receive_arp_responses(int socket_fd) { struct timeval timeout; timeout.tv_sec = TIMEOUT_SEC; timeout.tv_usec = 0; // 设置socket接收超时 if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { perror("setsockopt SO_RCVTIMEO failed"); return; } while (1) { uint8_t buffer[BUFFER_SIZE]; struct sockaddr_ll recvaddr; socklen_t recvaddrlen = sizeof(recvaddr); ssize_t packet_len = recvfrom(socket_fd, buffer, sizeof(buffer), 0, (struct sockaddr *) &recvaddr, &recvaddrlen); if (packet_len == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { printf("接收超时,结束监听\n"); break; } else { perror("recvfrom failed"); break; } } struct ethhdr *eth = (struct ethhdr *) buffer; // 只处理ARP包 if (ntohs(eth->h_proto) != ETH_P_ARP) { continue; } struct arp_header *arp = (struct arp_header *) (buffer + sizeof(struct ethhdr)); // 只处理ARP响应 if (ntohs(arp->opcode) != ARPOP_REPLY) { continue; } // 按照要求格式输出:ip = mac printf("%d.%d.%d.%d = %02x:%02x:%02x:%02x:%02x:%02x\n", arp->sender_ip[0], arp->sender_ip[1], arp->sender_ip[2], arp->sender_ip[3], arp->sender_mac[0], arp->sender_mac[1], arp->sender_mac[2], arp->sender_mac[3], arp->sender_mac[4], arp->sender_mac[5]); } } int main() { const char* ifname = "eth0"; // 替换为你的实际网卡名称,比如ens33、wlan0等 uint8_t sender_mac[ETH_ALEN]; uint8_t sender_ip[4]; uint8_t packet[sizeof(struct ethhdr) + sizeof(struct arp_header)]; // 获取网卡信息 if (get_interface_info(ifname, sender_mac, sender_ip) != 0) { fprintf(stderr, "获取网卡信息失败\n"); return 1; } // 创建原始socket int socket_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (socket_fd == -1) { perror("socket failed"); return 1; } // 绑定到指定网卡 struct sockaddr_ll sa; memset(&sa, 0, sizeof(struct sockaddr_ll)); sa.sll_family = AF_PACKET; sa.sll_protocol = htons(ETH_P_ARP); sa.sll_ifindex = if_nametoindex(ifname); if (sa.sll_ifindex == 0) { perror("if_nametoindex failed"); close(socket_fd); return 1; } if (bind(socket_fd, (struct sockaddr *)&sa, sizeof(struct sockaddr_ll)) == -1) { perror("bind failed"); close(socket_fd); return 1; } // 发送ARP请求:覆盖192.168.0.1-192.168.255.255网段 for (int subnet = 0; subnet <= 255; subnet++) { for (int host = 1; host <= 255; host++) { uint8_t target_ip[4] = {192, 168, subnet, host}; // 跳过自身IP if (memcmp(target_ip, sender_ip, 4) == 0) { continue; } create_arp_request(sender_mac, sender_ip, target_ip, packet); ssize_t sent_len = sendto(socket_fd, packet, sizeof(packet), 0, (struct sockaddr *)&sa, sizeof(sa)); if (sent_len == -1) { perror("sendto failed"); close(socket_fd); return 1; } usleep(10000); // 避免发包过快导致丢包 } } printf("开始接收ARP响应...\n"); receive_arp_responses(socket_fd); close(socket_fd); return 0; }
使用说明
- 替换代码中的
ifname为你的实际网卡名称(可通过ip addr命令查看)。 - 必须以root权限运行程序,因为原始socket需要管理员权限。
- 代码自动获取网卡的MAC和IP,无需硬编码,适配不同环境。
- 发送请求时加入短延迟,避免发包过快导致网络丢包。
- 接收时设置超时,超时后自动停止监听,避免程序无限阻塞。
内容的提问来源于stack exchange,提问作者Igor Khanaev
相关产品推荐
相关产品推荐

