ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

C++与WinPcap实现ARP协议获取局域网活动主机物理地址

2026/10/8 12:57:50 拓冰建站 浏览量
C++与WinPcap实现ARP协议获取局域网活动主机物理地址 简介这份PDF是广东工业大学计算机网络课程设计的完整项目文档面向计算机相关专业学生及希望深入理解网络协议底层实现的开发者围绕使用ARP协议获取局域网内活动主机物理地址这一课题展开。文档系统梳理了ARP协议工作原理、以太网帧与ARP帧结构、IP与MAC地址映射机制并给出基于Visual C与Winpcap库的完整实现思路涵盖ARP请求帧构造、数据帧发送、响应接收解析及结果展示等关键环节。资源包共1个PDF文件大小约458KB内容包含设计题目、技术参数、设计要求、网络基本理论简介、运行结果与代码分析等模块结构完整便于对照学习。目前已有169人学习下载。读者可借此掌握网络层与数据链路层的交互过程理解地址解析、网卡工作模式与混杂模式抓包等知识点并获得一份可直接参考的课程设计说明书与代码框架适合作为网络协议实验与课程设计的实践范本。1. 从一份课程设计说起ARP 抓物理地址到底在解决什么问题同一间机房里老师让你统计当前局域网里到底有哪些机器在线你打开路由器后台看到的是一串 IP 和 MAC 的对应表但那是路由器自己维护的未必实时也未必完整。你想自己写个程序主动发一轮 ARP 请求把每个 IP 对应的物理地址收回来这就是「使用 ARP 协议获取局域网内部活动主机物理地址」这件事的本质。它不依赖任何管理权限只要你在同一个广播域里就能靠协议本身的问答机制把活动主机摸出来。这份广工计算机网络课程设计的标题里几个关键词其实已经把技术栈框死了ARP 是协议C 是语言局域网是场景物理地址是目标。真正动手时你会发现C 标准库根本不碰网卡必须借助 WinPcap 这类抓包库直接读写链路层帧。所以整件事的落地路径是理解 ARP 报文结构 → 用 WinPcap 打开网卡 → 手工构造 ARP 请求帧 → 发出去并抓回应 → 解析出 MAC。适合正在做课设、想搞懂底层协议、或者需要写局域网扫描工具的人。下面按这条路径一层层拆开。2. ARP 报文结构与 WinPcap 选型为什么不能只用 socket2.1 ARP 请求和应答的字段布局ARP 报文并不复杂它固定 28 字节以太网 IPv4 场景直接封装在以太网帧里。理解字段是后面手工填包的前提否则你连哪个字节该填什么都不知道。字段长度请求帧取值应答帧取值以太网目的地址6 字节ff:ff:ff:ff:ff:ff请求方的 MAC以太网源地址6 字节本机 MAC本机 MAC帧类型2 字节0x08060x0806硬件类型2 字节1以太网1协议类型2 字节0x0800IPv40x0800硬件地址长度1 字节66协议地址长度1 字节44操作码2 字节1请求2应答发送方 MAC6 字节本机 MAC目标主机 MAC发送方 IP4 字节本机 IP目标主机 IP目标 MAC6 字节全 0未知请求方 MAC目标 IP4 字节待查询 IP请求方 IP请求帧里目标 MAC 填全 0 是常见做法因为此时你还不知道对方物理地址这正是要问的东西。应答帧则把发送方和目标方对调操作码变成 2。抓包时判断一个帧是不是 ARP先看帧类型是不是 0x0806再看操作码是 1 还是 2。2.2 为什么标准 socket 做不到必须上 WinPcap很多人第一反应是用socket(AF_INET, SOCK_RAW, IPPROTO_RAW)发原始套接字。这条路在 Windows 上走不通原因很直接原始套接字工作在 IP 层及以上你拿到的是 IP 包而 ARP 是链路层协议压根不经过 IP 层封装。你要构造的是以太网帧必须绕过协议栈直接操作网卡这就是 WinPcap或它的后继 Npcap存在的意义。WinPcap 提供的能力正好对上需求pcap_findalldevs枚举网卡pcap_open_live打开指定网卡进入混杂模式pcap_sendpacket发送原始帧pcap_next_ex抓取经过网卡的帧。它把网卡变成一个可以自由读写的管道你填什么字节它就发什么字节。选型上课设环境一般用 WinPcap 4.1.3新系统上装 Npcap 并勾选兼容模式也能跑头文件和链接库名字基本一致。提示安装抓包库时务必勾选「WinPcap 兼容模式」否则pcap.h里的函数签名可能对不上编译期就报链接错误。2.3 开发环境搭起来的最小步骤环境这一步翻车的人不少尤其是库路径没配对。按下面顺序来基本一次过。第一步装抓包库驱动装完在设备管理器里能看到对应的网络服务。第二步把 SDK 里的Include和Lib目录加到工程里。第三步工程属性里链接wpcap.lib和ws2_32.lib前者是抓包库后者是 Winsock解析 IP 时要用。第四步定义WPCAP和HAVE_REMOTE两个宏否则某些结构体不完整。// 工程预处理器定义里加上这两个宏 #define WPCAP #define HAVE_REMOTE #include pcap.h #include winsock2.h #pragma comment(lib, wpcap.lib) #pragma comment(lib, ws2_32.lib)这段不是可执行逻辑而是编译配置的代码化表达。WPCAP宏决定pcap.h是否暴露完整接口HAVE_REMOTE影响远程抓包相关结构体。ws2_32.lib是为了后面用inet_addr、inet_ntoa做 IP 字符串和网络字节序的转换。少了任何一个报错信息往往指向头文件内部很难一眼看出是配置问题。3. 枚举网卡并构造 ARP 请求帧把字节填对3.1 找到正确的网卡设备一台机器上通常有好几块网卡虚拟网卡、回环、无线都在列。程序第一步是把它们列出来让使用者选或者按 IP 自动匹配。pcap_findalldevs返回一个链表每个节点带名字、描述和地址列表。pcap_if_t *alldevs nullptr, *d nullptr; char errbuf[PCAP_ERRBUF_SIZE]; if (pcap_findalldevs(alldevs, errbuf) -1) { printf(枚举网卡失败: %s\n, errbuf); return -1; } int idx 0; for (d alldevs; d ! nullptr; d d-next) { printf(%d. %s\n, idx, d-name); if (d-description) printf( 描述: %s\n, d-description); }pcap_findalldevs的第二个参数是错误缓冲区失败时里面是可读的错误原因。返回的链表用完必须用pcap_freealldevs释放否则内存泄漏。d-name是设备标识形如\Device\NPF_{GUID}后面pcap_open_live就靠它定位网卡。实际课设里建议把描述也打出来因为光看 GUID 根本分不清哪块是有线网卡。3.2 打开网卡并拿到本机 MAC 和 IP选定网卡后打开它同时要拿到本机的 MAC 和 IP因为 ARP 请求帧里必须填发送方信息。MAC 可以通过pcap_open_live之后配合适配器信息获取IP 则用 Winsock 的gethostnamegethostbyname拿。pcap_t *adhandle pcap_open_live(d-name, 65536, 1, 1000, errbuf); if (adhandle nullptr) { printf(打开网卡失败: %s\n, errbuf); pcap_freealldevs(alldevs); return -1; } // 获取本机 IP char hostname[256]; gethostname(hostname, sizeof(hostname)); struct hostent *hp gethostbyname(hostname); struct in_addr localIP; memcpy(localIP, hp-h_addr_list[0], sizeof(struct in_addr)); printf(本机 IP: %s\n, inet_ntoa(localIP));pcap_open_live的四个关键参数设备名、抓包缓冲区大小65536 足够、是否混杂模式填 1这样能收到发给别人的帧、超时毫秒数1000 表示读超时 1 秒返回。混杂模式对 ARP 扫描不是必须的因为应答是单播发给你的但开着无害。本机 MAC 的获取稍微绕常见做法是通过pcap_findalldevs返回的地址列表里找 AF_LINK 类型或者直接读适配器信息课设里用前者更省事。3.3 手工拼一个 ARP 请求帧这是整个程序的核心。定义一个结构体把 28 字节 ARP 数据装好前面再拼 14 字节以太网头凑成完整帧。#pragma pack(push, 1) // 关键禁止结构体对齐填充 struct EthernetHeader { unsigned char destMAC[6]; unsigned char srcMAC[6]; unsigned short etherType; // 0x0806 }; struct ARPHeader { unsigned short hwType; // 1 unsigned short protoType; // 0x0800 unsigned char hwLen; // 6 unsigned char protoLen; // 4 unsigned short opCode; // 1 请求 2 应答 unsigned char senderMAC[6]; unsigned char senderIP[4]; unsigned char targetMAC[6]; unsigned char targetIP[4]; }; #pragma pack(pop) struct ARPPacket { EthernetHeader eth; ARPHeader arp; };#pragma pack(push, 1)是血泪经验不加它编译器会按默认对齐给结构体塞填充字节导致你发出去的帧长度和字段位置全错抓包看就是一堆乱码。以太网类型0x0806和操作码这些多字节字段要注意字节序网络字节序是大端x86 是小端所以赋值时用htons转换。目标 MAC 填全 0目标 IP 填你要探测的那个地址。ARPPacket pkt; memset(pkt, 0, sizeof(pkt)); memset(pkt.eth.destMAC, 0xff, 6); // 广播 memcpy(pkt.eth.srcMAC, localMAC, 6); pkt.eth.etherType htons(0x0806); pkt.arp.hwType htons(1); pkt.arp.protoType htons(0x0800); pkt.arp.hwLen 6; pkt.arp.protoLen 4; pkt.arp.opCode htons(1); memcpy(pkt.arp.senderMAC, localMAC, 6); memcpy(pkt.arp.senderIP, localIP, 4); memset(pkt.arp.targetMAC, 0, 6); pkt.arp.targetIP[0] 192; pkt.arp.targetIP[1] 168; pkt.arp.targetIP[2] 1; pkt.arp.targetIP[3] 100;memset先把整包清零避免栈上残留数据混进帧里。广播地址全 0xff 保证交换机把帧泛洪到所有端口。发送方 MAC 和 IP 填本机对方收到后才知道该把应答发给谁。目标 IP 这里写死成 192.168.1.100实际扫描时用一个循环遍历网段即可。4. 发送请求、抓取应答并解析物理地址4.1 把帧发出去构造好帧用pcap_sendpacket直接扔到网卡上。它不关心内容只负责把这段字节送进链路层。if (pcap_sendpacket(adhandle, (const u_char *)pkt, sizeof(pkt)) ! 0) { printf(发送失败: %s\n, pcap_geterr(adhandle)); }pcap_sendpacket第二个参数是缓冲区指针第三个是长度。这里长度必须是sizeof(ARPPacket)也就是 42 字节14 以太网头 28 ARP多一个少一个对方都解析不了。失败时用pcap_geterr拿具体原因常见的是网卡没打开或权限不足。发送本身不阻塞发完立刻返回应答要靠后面的抓包循环去收。4.2 抓应答并过滤出 ARP 帧发出去之后要等回应。用pcap_next_ex循环读每读到一个帧先判断是不是 ARP 应答再判断是不是发给本机的。struct pcap_pkthdr *header; const u_char *pktData; int res; while ((res pcap_next_ex(adhandle, header, pktData)) 0) { if (res 0) continue; // 超时继续等 EthernetHeader *eth (EthernetHeader *)pktData; if (ntohs(eth-etherType) ! 0x0806) continue; // 不是 ARP ARPHeader *arp (ARPHeader *)(pktData 14); if (ntohs(arp-opCode) ! 2) continue; // 不是应答 printf(IP: %d.%d.%d.%d - MAC: %02X:%02X:%02X:%02X:%02X:%02X\n, arp-senderIP[0], arp-senderIP[1], arp-senderIP[2], arp-senderIP[3], arp-senderMAC[0], arp-senderMAC[1], arp-senderMAC[2], arp-senderMAC[3], arp-senderMAC[4], arp-senderMAC[5]); break; // 收到一个就退出扫描时改成计数 }pcap_next_ex返回值有三种1 表示抓到包0 表示超时-1 表示出错-2 表示读到文件尾。循环里先跳过超时再检查帧类型。pktData 14是跳过以太网头定位到 ARP 头这个偏移量固定因为以太网头就是 14 字节。操作码用ntohs转回主机字节序再比较直接和 2 比会因字节序问题永远不相等这是新手最容易踩的坑之一。解析出来的senderMAC就是我们要的物理地址。4.3 扫描整个网段的循环写法单发一个 IP 没意义课设要的是扫一段。把发送和接收拆成两段先批量发再统一收效率更高。// 先发一轮请求 for (int i 1; i 255; i) { pkt.arp.targetIP[3] i; pcap_sendpacket(adhandle, (const u_char *)pkt, sizeof(pkt)); Sleep(1); // 稍微错开避免瞬间打爆 } // 再收一轮应答 time_t start time(nullptr); while (time(nullptr) - start 3) { // 收 3 秒 if (pcap_next_ex(adhandle, header, pktData) 1) { // 解析逻辑同上 } }Sleep(1)是给网卡和交换机一点喘息时间全速发 254 个帧虽然也能跑但某些低端交换机会丢包。收包窗口给 3 秒是经验值局域网内应答基本在毫秒级回来3 秒足够覆盖慢设备。扫描时把结果存进一个 mapIP 做键 MAC 做值最后统一打印避免重复输出。5. 避坑与排查那些让程序「看起来能跑但结果不对」的细节5.1 抓到的 MAC 全是广播地址现象打印出来的物理地址全是FF:FF:FF:FF:FF:FF。原因是你把请求帧本身也抓进来了请求帧的以太网目的地址就是广播。解决解析前先判断操作码是不是 2只处理应答帧请求帧直接跳过。这个坑的本质是没区分收发方向。5.2 一个应答都收不到现象帧发出去了抓包循环跑满超时也没结果。原因通常有三个目标 IP 不在本网段ARP 请求根本不会被路由网卡选错了发到了虚拟网卡上或者目标主机开了防火墙屏蔽 ARP。解决先用arp -a看系统缓存里有没有目标确认在同一网段再确认pcap_open_live打开的是真实有线网卡。5.3 结构体长度对不上现象sizeof(ARPPacket)打印出来是 44 或 48 而不是 42。原因就是前面说的结构体对齐编译器为了访问效率插了填充字节。解决#pragma pack(push, 1)必须加且要包住所有网络协议结构体。验证方法是打印sizeof42 才对。5.4 字节序导致操作码判断失效现象明明收到了应答ntohs(arp-opCode) ! 2却一直成立帧被过滤掉。原因网络字节序是大端x86 是小端opCode字段读出来是 0x0200 而不是 0x0002。解决所有多字节字段比较前一律ntohs发送时一律htons。IP 地址是字节数组不涉及这个问题但端口和类型字段都要转。5.5 程序跑一次后网卡状态异常现象反复运行后抓不到包或者系统网络变慢。原因pcap_open_live打开的句柄没关混杂模式没退出。解决每次结束前调用pcap_close(adhandle)枚举的链表用pcap_freealldevs释放。资源不释放下次打开同一网卡可能失败。6. 进阶技巧把扫描结果做成可复用的资产基础版本能跑通后值得再往前走一步。我一般会把结果落成结构化数据方便后续比对。比如每次扫描输出一个 CSV字段是 IP、MAC、时间戳下次扫描时和上次对比新出现的 MAC 就是新入网设备。这个思路在局域网资产管理里很实用比每次从零扫一遍有价值。// 把结果写成 CSV方便 diff FILE *fp fopen(arp_scan.csv, a); fprintf(fp, %s,%02X:%02X:%02X:%02X:%02X:%02X,%ld\n, inet_ntoa(*(struct in_addr *)arp-senderIP), arp-senderMAC[0], arp-senderMAC[1], arp-senderMAC[2], arp-senderMAC[3], arp-senderMAC[4], arp-senderMAC[5], time(nullptr)); fclose(fp);追加模式打开每次扫描往里加行时间戳做区分。之后用脚本按 MAC 去重就能看出哪些设备是常驻的、哪些是临时接入的。另一个进阶点是超时重试单次请求丢包是常态对没回应的 IP 再补发一到两轮命中率能明显提升。重试间隔给 200 毫秒左右太密没意义太疏拖慢整体速度。验证程序对不对最直接的办法是拿arp -a的输出做对照。系统缓存里的条目是操作系统自己发 ARP 得到的和你程序的结果应该高度重合。如果差得多先查网段和网卡再查过滤逻辑。我自己的习惯是每次改完过滤条件先只打印原始帧的操作码和发送方 IP确认收包正常了再开解析这样出问题能快速定位是收不到还是解析错。这套流程走下来课设拿分是小事真正值钱的是你搞懂了链路层怎么被程序直接操控。希望帮到你。本文还有配套的精品资源点击获取