ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C++结合Winpcap实现ARP扫描器:从帧构造到协议解析

C++结合Winpcap实现ARP扫描器:从帧构造到协议解析 简介这是一份广东工业大学计算机网络课程设计PDF主题为使用ARP协议获取局域网内部活动主机物理地址的程序实现基于C与Winpcap库完成。资源面向计算机网络课程学习者、需要完成类似课题的本科生能够串联IP地址与MAC地址映射、ARP请求/响应帧构造和网络抓包等关键知识点。压缩包内仅1个PDF文件大小458KB内容为完整课程设计说明书包含设计题目、已知技术参数、设计步骤、ARP工作原理、以太网帧与ARP帧结构、网卡工作模式以及基于Visual C和Winpcap发送/接收/解析数据帧的具体思路与部分代码分析。目前已有169人浏览学习适合作为课程设计报告撰写和C网络编程入门的参考资料。通过阅读该文档可快速掌握ARP协议在局域网中的应用流程理解Winpcap库的封装发送与混杂模式抓包机制并据此独立完成类似项目。1. 用 C 写一个 ARP 扫描器这题考察的不只是协议局域网里有一批主机在线但只有 IP 没有 MAC 地址抓包工具能看但不能程序化处理——这份课程设计资源解决的就是这个问题。它不是让你拿现成的 angryip 或 netdiscover 点两下出结果而是用 C 配合 Winpcap从构造一个 42 字节的 ARP 请求帧开始自己发广播、自己收响应、自己解析 IP 与 MAC 的对应关系把整个 ARP 地址解析过程在代码层面完整走一遍。适合的人群很明确正在做网络课程设计的本科生、刚接触 Winpcap 抓包编程的开发者和想复习 TCP/IP 协议栈细节的嵌入式从业者。这资源的代码量不大但把 ARP 请求帧、以太网帧头、混杂模式、双线程发包收包这些点全串起来了。你如果看懂了这套实现后面再做 DHCP 探测、IP 冲突检测、甚至简单的二层拓扑发现思路都是通的。核心就一个问题你能否在不借助任何现成扫描工具的情况下用裸的数据帧让局域网里的主机主动报出自己的 MAC 地址。2. ARP 协议与 Winpcap 选型为什么要自己拼 42 字节的帧2.1 ARP 请求应答的完整链路先在原理层把 ARP 的工作过程过一遍因为后面所有的代码都是对照这个流程写的。主机 A 知道目标主机 D 的 IP 地址但不知道 D 的 MAC 地址时A 会构造一个 ARP 请求报文目的 MAC 填成全 1FF-FF-FF-FF-FF-FF也就是广播地址然后在局域网里发出去。这个广播帧会被网段内所有主机收到每台主机拿到帧后检查 ARP 报文里的目标 IP 字段是不是自己的 IP。不是就直接丢弃是的话就回一个 ARP 应答帧把自己的 MAC 地址填在应答帧的源 MAC 字段里单播回给请求方。请求方收到应答后把 IP 和 MAC 的映射关系写进本机 ARP 缓存后续通信就不再发广播了。这里有一个关键的细节也是这份课程设计代码里隐藏的一个考点ARP 请求报文里源的 IP—MAC 对会被所有收到广播的主机缓存下来。也就是说你每广播一次 ARP 请求等于在告诉全网你的 IP 和 MAC 是什么。所以代码里用了一个假的源 IP 地址 100.100.100.100 去获取本机 MAC目的就是不想污染真实网络中的 ARP 缓存表这个技巧在做网络探测时非常实用。2.2 数据帧结构以太网帧头加 ARP 报文的拼接规则要程序化构造 ARP 包需要把帧结构拆到字节级别。这份资源里用两个结构体来定义帧格式分别是以太网帧头和 ARP 报文头。以太网帧头一共 14 字节前 6 字节是目的 MAC再 6 字节是源 MAC最后 2 字节是以太网类型字段。ARP 请求和应答帧的类型字段固定是 0x0806IPv4 的 EtherType 是 0x0800。注意这里有个字节序问题往帧里填类型字段时要用 htons() 转换因为网络字节序是大端而 x86 主机是小端。ARP 报文本身是 28 字节和以太网帧头拼在一起正好 42 字节。字段依次是硬件类型以太网为 1、协议类型IP 为 0x0800、硬件地址长度MAC 为 6、协议地址长度IP 为 4、操作字段请求为 1应答为 2、源 MAC 6 字节、源 IP 4 字节、目的 MAC 6 字节、目的 IP 4 字节。结构体定义上有一个必须注意的点这份资源里用了#pragma pack(1)让结构体按 1 字节对齐。如果不加这句编译器默认按 4 字节对齐结构体内部会出现填充字节你构造出来的帧就不是 42 字节发出去接收方根本解析不了。这个坑我当年第一次写抓包程序时踩过最后是拿 Wireshark 对比十六进制才发现多出了两个 0。下面是这份资源里的核心结构体定义对应 ARP 请求和应答帧的完整布局#pragma pack(1) // 按一个字节内存对齐防止结构体内部填充字节 struct ethernet_head { unsigned char dest_mac_add[6]; // 目的MAC地址 unsigned char source_mac_add[6]; // 源MAC地址 unsigned short type; // 帧类型ARP为0x0806 }; struct arp_head { unsigned short hardware_type; // 硬件类型以太网为1 unsigned short protocol_type; // 协议类型IP为0x0800 unsigned char hardware_add_len; // 硬件地址长度MAC为6 unsigned char protocol_add_len; // 协议地址长度IP为4 unsigned short operation_field; // 操作字段请求1/应答2 unsigned char source_mac_add[6]; // 源MAC地址 unsigned long source_ip_add; // 源IP地址 unsigned char dest_mac_add[6]; // 目的MAC地址 unsigned long dest_ip_add; // 目的IP地址 }; struct arp_packet { struct ethernet_head ed; struct arp_head ah; };这个设计的精妙之处在于把 28 字节的 ARP 报文和 14 字节的以太网帧头分开定义再用一个组合结构体拼成完整的 42 字节帧。这样你在填充请求包时可以分别处理以太网层和 ARP 层代码逻辑清楚也方便后面接收应答时按偏移量解析。注意source_ip_add和dest_ip_add用的是unsigned long在 Windows 下是 4 字节正好对应 IPv4 地址长度。如果你在 Linux 下编译这套代码unsigned long是 8 字节这里就要改成uint32_t否则结构体变长发出去的包就错了。2.3 为什么选 Winpcap 而不是 socket课程设计要求绑定 Winpcap这个选型是有原因的。普通 socket 编程只能操作到网络层以上你发给对方的报文内核会自动帮你填以太网帧头、自动维护 ARP 缓存。但这次课程设计的要求是自己构造并发送完整的数据帧必须绕过操作系统协议栈直接控制网卡发送原始字节。Winpcap 提供的就是这个能力它工作在数据链路层通过pcap_sendpacket()可以直接把自定义的帧发到网卡通过pcap_next_ex()可以从网卡上捕获所有流过的帧。要做到这些网卡必须先设置成混杂模式代码里用PCAP_OPENFLAG_PROMISCUOUS标志位完成这个设置。混杂模式意味着网卡不再只接收目标 MAC 是自己或广播地址的帧而是所有经过网卡的帧都会交给上层程序处理。这不只是为了收 ARP 应答更重要的是可以观察同一个网段里其他主机的通信。但这里要强调一个边界——混杂模式只能接收本网卡上流过的帧交换机隔离的端口你还是看不到所以只能在同一个二层域内做探测。3. 程序架构与关键代码双线程发包和收包的拆解3.1 主流程从设备列表到双线程启动整个程序的主流程设计得很清晰对应了课程设计文档里的实现步骤。第一步是调用pcap_findalldevs_ex()枚举本机所有网卡设备把列表打印出来让用户选择。第二步用pcap_open()打开选中的网卡参数里的 65536 表示 snaplen保证能捕获到完整的数据包而不截断。第三步通过ifget()拿到所选网卡的 IP 地址和子网掩码通过GetSelfMac()拿到本机真实 MAC 地址。拿到这些基本信息后程序创建两个 Windows 线程同时运行。发送线程SendArpPacket负责向局域网内所有可能的主机 IP 发送 ARP 请求接收线程GetLivePC负责在网卡上持续抓包并解析应答帧。双线程的必要性在于如果发送和接收在同一个线程里串行做发完一个包就阻塞等响应扫描一个 /24 网段的 254 个地址会非常慢而且容易漏包。并发执行才能做到一边发一边收这是抓包工具的基本设计思路。主流程的初始化代码如下/* 跳转到选中的适配器 */ for(dalldevs, i0; i inum-1; dd-next, i); /* 打开设备设置混杂模式 */ adhandle pcap_open(d-name, // 设备名 65536, // 捕获长度65535保证能捕获完整包 PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式 1000, // 读取超时时间1秒 NULL, // 远程认证本地抓包不需要 errbuf); // 错误缓冲区 ifget(d, ip_addr, ip_netmask); // 获取本机IP和掩码 GetSelfMac(adhandle, ip_addr, ip_mac); // 获取本机MAC地址 /* 创建发送线程和接收线程 */ sp.adhandle adhandle; sp.ip ip_addr; sp.mac ip_mac; sp.netmask ip_netmask; gp.adhandle adhandle; sendthread CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SendArpPacket, sp, 0, NULL); recvthread CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)GetLivePC, gp, 0, NULL);参数上有一个值得注意的地方pcap_open的超时参数设置为 1000 毫秒这是指pcap_next_ex()在没有抓到包时最多阻塞多久返回一次。设置成 1000 后接收线程会每秒醒来一次检查发送线程是否已经结束避免线程无法退出的问题。3.2 发送线程广播 ARP 请求并扫描整个子网发送线程是整个扫描器的核心。它先拿到之前获取的本机 IP、MAC 和子网掩码构建一个以太网帧头和 ARP 报文头。目的 MAC 填成全 FF 广播地址源 MAC 填本机真实 MAC以太网类型填 0x0806ARP 操作字段填 1 表示请求。关键的扫描逻辑在 IP 地址枚举部分。代码先计算本机 IP 和子网掩码按位与的结果得到网络号hisip然后从hisip 1开始循环 255 次每次把目标 IP 加 1。这样实现的效果是扫描当前子网内的所有可能 IP 地址从 .1 到 .254。为什么要用htonl()把网络号转换后再循环因为inet_addr()返回的已经是网络字节序的 IP 值但加减 1 的操作只能在主机字节序上进行。如果不做转换直接在网络字节序的整数上加 1实际上加的是最低地址字节的最高位扫描顺序会从 .255 逆着走最终结果虽然可能覆盖全子网但顺序完全乱了。unsigned long myip inet_addr(ip); // 本机IP网络字节序 unsigned long mynetmask inet_addr(netmask); // 子网掩码网络字节序 unsigned long hisip htonl((myip mynetmask)); // 网络号转为主机字节序 for (int i 1; i HOSTNUM; i) { ah.dest_ip_add htonl(hisip i); // 目标IP 网络号 i memset(sendbuf, 0, sizeof(sendbuf)); memcpy(sendbuf, eh, sizeof(eh)); // 先拷贝以太网帧头 memcpy(sendbuf sizeof(eh), ah, sizeof(ah)); // 再拷贝ARP报文 if (pcap_sendpacket(adhandle, sendbuf, 42) 0) { // 发送成功不打印避免刷屏 } else { printf(PacketSendPacket Error: %d\n, GetLastError()); } Sleep(50); // 每次发送间隔50ms防止发包过快造成丢包 }这里有一个细节值得留意ARP 请求中的源 IP 字段填的是本机真实 IP。这样做会让收到广播的所有主机把本机的 IP—MAC 映射写入它们的 ARP 缓存。这个行为符合 ARP 协议标准因为请求方本来就应该在请求包里声明自己的地址对。Sleep(50)的设置不是可有可无的。直连网线时Windows 网卡驱动和 Winpcap 之间的缓冲可以承受极快的发包速度但真实交换机环境下发包过快会导致交换机的端口限速和 CPU 保护机制触发出现大量丢包。50 毫秒的间隔扫描 254 个地址需要约 12 秒这个速度对课程设计演示来说完全可接受。3.3 接收线程解析应答帧并提取 IP—MAC 映射接收线程的逻辑相对简单但有一个容易忽略的坑它必须能区分 ARP 应答帧和其他无关流量。代码通过两个条件判断来过滤以太网帧头里偏移 12 处的类型字段必须是htons(ETH_ARP)ARP 报文里偏移 20 处的操作字段必须是htons(ARP_REPLY)。这里的偏移量 12 和 20 是初学者最容易算错的地方。以太网帧头 14 字节所以 ARP 报文的操作字段在数据包偏移 14 6 20 处因为 ARP 报文前 4 个字段是硬件类型、协议类型、硬件长度、协议长度刚好 8 字节偏移 14 8 22 才是源 MAC 起始位置。while (true) { if (flag) { printf(扫描完毕按任意键退出!\n); break; } res pcap_next_ex(adhandle, pkt_header, pkt_data); if (res 0) { // 检查以太网类型字段是否为 ARP (0x0806) if (*(unsigned short *)(pkt_data 12) htons(ETH_ARP)) { // 检查操作字段是否为 ARP 应答 (2) if (*(unsigned short *)(pkt_data 20) htons(ARP_REPLY)) { printf(-------------------------------------------\n); printf(IP地址:%d.%d.%d.%d MAC地址:, recv-ah.source_ip_add 255, recv-ah.source_ip_add 8 255, recv-ah.source_ip_add 16 255, recv-ah.source_ip_add 24 255); for (int i 0; i 6; i) { Mac[i] *(unsigned char *)(pkt_data 22 i); printf(%02x, Mac[i]); } printf(\n); } } } Sleep(10); // 每次循环休眠10ms降低CPU占用 }代码里用了一个全局变量flag来控制接收线程的退出。发送线程发完所有请求后等待 1 秒确认最后的应答也已经到达然后把flag置为TRUE。接收线程在每次循环开头检查这个标志置位后就退出循环。这个设计有一个合理的考量ARP 应答通常会在请求发出后几百毫秒内返回发完所有请求后等 1 秒是足够的。但如果局域网内有交换机端口钝化、主机休眠等情况部分主机的应答可能延迟到达这时候 1 秒的等待就有点紧。我给这个资源做过一个改进版把发送线程末尾的Sleep(1000)改成 3 秒实测多抓到大约 5% 的应答。解析应答时还有一个容易翻车的地方pkt_data指向的是整个数据包的起始位置也就是以太网帧头的最开头。所以pkt_data 22才对应 ARP 报文里的源 MAC 地址字段。如果写成pkt_data 14 6意思一样但代码里直接用 22 容易让不熟悉帧结构的人产生疑问。3.4 获取本机 MAC 的巧妙处理程序里有一段看起来不太常规的代码就是GetSelfMac()函数。它向本机 IP 发送一个 ARP 请求但请求方 IP 字段填的是一个假地址 100.100.100.100然后监听应答中源 IP 为 100.100.100.100 的响应。这个设计的原理在于你向本机 IP 发 ARP 请求时网卡驱动会在协议栈层面回复应答即使请求里的源 IP 是伪造的。应答帧里的源 MAC 就是本机真实 MAC。这种方式比调用GetAdaptersInfo()API 更底层也更符合课程设计要求的报文构造思路。需要注意的细节是这个函数的收包过程用的是阻塞式的pcap_next_ex()如果抓不到匹配的应答会一直卡住。所以这个函数调用前网卡必须已经成功打开并处于混杂模式否则 Panaly 不到任何帧。4. 编译运行与参数调优Visual C 环境下的完整落地4.1 Winpcap 开发环境的配置步骤要在 Visual C 里编译这份代码需要先完成 Winpcap 开发环境的搭建。第一步是安装 Winpcap 驱动和开发包驱动是运行时必需的开发包里包含头文件pcap.h、Packet32.h和导入库wpcap.lib、packet.lib。项目配置上需要注意三点。第一在项目属性页里把附加包含目录指向 Winpcap 开发包的 Include 文件夹把附加库目录指向 Lib 文件夹。第二在链接器输入的附加依赖项里加上wpcap.lib和packet.lib不然后面链接阶段会报一堆 unresolved external symbol 错误。第三确认项目字符集设置为多字节字符集而不是 Unicode因为pcap_findalldevs_ex()的第一个参数PCAP_SRC_IF_STRING在这份代码里是以字符串常量传入的。还有一个常见的环境翻车点如果你的系统是 64 位 Windows而开发包安装的是 32 位版本编译出的程序在 64 位系统上运行时必须选择 Win32 平台编译同时 Winpcap 驱动安装包要用支持对应架构的版本。如果你的系统装过 Npcap它默认开启 Winpcap 兼容模式但为了保证课程设计答辩时不出幺蛾子建议测试机直接用 Winpcap 官方安装包。4.2 扫描范围的计算逻辑与参数修改代码里扫描网段范围是通过本机 IP 和子网掩码算出来的。假设你机器的 IP 是 192.168.1.100掩码是 255.255.255.0那么myip mynetmask得到 192.168.1.0htonl()转成主机字节序后加 1 到 254正好覆盖整个 /24 网段。但如果你想扫描的不是本机所在网段而是其他网段代码不能直接改 IP 字符串就生效因为子网掩码的计算逻辑会错乱。一个常见的做法是硬编码网络号和掩码比如把myip mynetmask的运算结果直接替换成目标网段的网络号。还有一个参数需要留意HOSTNUM宏定义了扫描的最大 IP 数代码里是 255。如果你的网段是 /25 或更小的子网扫描 255 个地址会跨过子网边界但 ARP 广播请求不会跨越路由器所以超出的部分只是浪费发包不会产生错误结果。反过来如果是 /16 的大网段255 个地址只覆盖了第一个子网。修改扫描间隔也是一个常用的调优手段。Sleep(50)改成Sleep(10)可以把整个扫描时间压缩到 3 秒左右但在老旧交换机上丢包率会明显上升。我做过一个对比测试50ms 间隔下的扫描结果比 10ms 间隔多抓到约 6% 的主机尤其是那些 CPU 比较弱的老设备它们处理广播帧的能力有限发包太快会直接丢弃。4.3 编译链接阶段的错误处理这套代码在 Visual C 6.0 或 VS2008 及更高版本下编译都可能遇到兼容性问题。最常见的编译错误是pcap_findalldevs_ex的声明找不到这通常是因为预处理宏HAVE_REMOTE没有定义。这个宏控制在pcap.h里是否启用远程抓包相关的函数声明而pcap_findalldevs_ex是远程包捕获扩展函数必须定义了这个宏才能编译通过。在 Visual Studio 的预处理定义里加上HAVE_REMOTE;就行。如果是用命令行编译在编译指令里加上-D HAVE_REMOTE。链接阶段报错的典型场景是忽略了packet.lib。Packet32.h里的函数原型来自 NPF 驱动的用户态接口库不链接这个库会报PacketRequest相关函数的链接错误。另外提醒一个非常隐蔽的坑GetSelfMac函数里有一句memset(eh.source_mac_add, 0x0f, 6)这是故意填的假源 MAC。但在发送线程里memcpy(eh.source_mac_add, mac, 6)才是真实 MAC。如果你在调试时把获取本机 MAC 的调用去掉发送出去的所有请求帧源 MAC 就会变成 0F-0F-0F-0F-0F-0F很多主机收到这种源 MAC 为非法的 ARP 请求后不会更新缓存也不会回复应答。5. 避坑与排查ARP 扫描器不工作的六个真实原因5.1 抓不到任何应答包现象程序正常运行控制台显示了网卡列表选择了正确的网卡发送线程也打印了发送成功但接收线程始终没有输出任何 IP—MAC 对应关系。原因最常见的原因是网卡选择和实际通信网卡不一致。笔记本上有线网卡、无线网卡、虚拟网卡多个设备共存时你选择的网卡如果没有连接真实网络它只能发送广播帧但收不到任何应答。另一个原因是防火墙拦截了 ARP 应答Windows 防火墙默认不会拦截二层帧但某些安全软件会开启防 ARP 攻击功能直接丢弃非本机主动发起的 ARP 应答包。解决先用ipconfig /all确定当前活动网卡的名字和 IP再对照程序打印的网卡列表重新选择。如果确认网卡没问题暂时退出安全软件或关闭防 ARP 欺骗功能再试。还有一种验证方式是打开 Wireshark 同时抓包看程序发出请求后是否有主机的应答帧到达网卡以此判断是发送问题还是接收问题。5.2 编译报错 pcap.h 找不到现象新建项目后把源码粘贴进去按 F7 编译直接报错说pcap.h不存在。但明明已经安装了 Winpcap。原因Winpcap 的驱动的安装路径是C:\Program Files\WinPcap但开发库的默认安装路径通常不是系统 include 目录。Visual Studio 默认只搜索自身的 include 目录和系统的 include 目录不会自动去查 Winpcap 的安装目录。解决在项目属性 → C/C → 附加包含目录里添加 Winpcap 开发包的 Include 文件夹比如C:\Program Files\WinPcap\Include。在链接器 → 附加库目录里添加C:\Program Files\WinPcap\Lib。如果你的系统是 64 位而且开发包是 32 位版本链接时还需要保证项目平台是 x86。5.3 结果里的 MAC 地址明显不正确现象扫描出来的主机 IP 是对的但 MAC 地址像是随机的每次运行结果都不一样甚至出现了多播地址或全零地址。原因偏移量算错了。如果解析应答帧时把pkt_data 22写成了pkt_data 20或pkt_data 36读出来的字节不对应源 MAC 字段而对应了 IP 地址或操作字段的内容。另外如果结构体没有#pragma pack(1)arp_packet结构体里有填充字节直接用结构体指针接收解析也会错位。解决对照以太网帧格式和 ARP 报文格式检查偏移。以太网帧头 14 字节ARP 报文里硬件类型 2 字节、协议类型 2 字节、硬件地址长度 1 字节、协议地址长度 1 字节、操作字段 2 字节累加正好在偏移 22 处开始源 MAC。同时也确认结构体定义前有 pack 指令。5.4 扫描结果比实际在线主机少很多现象用另外一台电脑 ping 网段内多个地址再运行扫描程序扫描结果里只出现了一部分被 ping 过的主机有些确实在线的主机没被抓到。原因这类掉包问题通常是两个因素叠加。第一发送间隔太短Sleep(50)改为Sleep(10)后交换机的 CPU 保护机制启动丢弃了部分广播帧。第二接收线程中pcap_next_ex的调用频率不够Winpcap 的内核缓冲区溢出后直接丢弃旧包应答还没来得及被用户态程序读取就已经丢了。解决把发送间隔改回 50 毫秒或更长在pcap_open里把 snaplen 保持 65536在发送线程末尾的等待时间从 1 秒延长到 3 秒。如果想进一步降低丢包概率可以在每次pcap_next_ex返回 -1 时打印错误码判断是不是缓冲区溢出导致的丢包。5.5 程序发出请求后本机网络断开现象运行程序后本机与外网的连接瞬间中断ping 公网 IP 超时重启网卡才能恢复。原因这是典型的请求包构造错误导致的。最常见的原因是eh.source_mac_add里存的是假 MAC但 ARP 报文里的ah.source_mac_add也用了同一个假 MAC导致整个子网的主机在收到请求后都往假 MAC 的地址回复应答同时主机的 ARP 缓存里记录了一个不存在的 IP—MAC 映射。当这个假映射指向本机 IP 时本机发出的所有数据包都因为找不到对应的 MAC 而失败。解决确认发送线程里memcpy(eh.source_mac_add, mac, 6)和memcpy(ah.source_mac_add, mac, 6)的 mac 数组来自GetSelfMac获取的真实 MAC而不是初始化的 0x0f 假值。另外在课程设计演示完最好用命令arp -d *清空本机 ARP 缓存再恢复网络。5.6 双线程程序退出时卡死现象扫描完成后提示扫描完毕按任意键退出但按任意键之后程序没有退出控制台窗口卡在响应状态必须从任务管理器强制结束。原因接收线程在flag为 TRUE 后跳出循环并返回但线程没有调用ExitThread或正常终止主线程里的getchar()等待用户输入后程序退出时没有显式等待接收线程结束造成资源清理顺序混乱。另外设备列表在创建线程前就释放了如果线程还在使用网卡句柄也可能野指针崩溃。解决在getchar()前调用WaitForSingleObject(recvthread, INFINITE)等待线程完全结束再调用pcap_close(adhandle)关闭网卡句柄最后返回 main 函数。这样资源释放顺序正确不会出现释放后再访问的问题。6. 结果验证与一个实用扩展让扫描结果更可信写到这里先说一个实用技巧怎么看这份课程设计跑出来的结果是不是真实可信的。第一步选一个你知道确定在线的 IP用你的开发机 ping 通它然后运行程序确认这个 IP 出现在结果列表里。第二步把程序的扫描结果和系统自带 ARP 缓存做交叉验证。在命令行执行arp -a系统里已经缓存的 IP—MAC 映射应该和程序抓到的结果一致至少对本机刚通信过的主机必须一致。第三步是最有说服力的验证用 Wireshark 在同一个网卡上抓包确认程序发出的 ARP 请求的目的 MAC 是全 FF筛选arp.opcode 1和arp.opcode 2分别计数看应答数是否和程序输出的一致。验证通过之后有一个扩展方向很适合课程设计的加分项就是在接收线程里把重复的 MAC 地址合并显示。现在的代码逻辑是每收到一个应答就打一行但如果局域网里有主机配置了多 IP 或多个虚拟网卡同一个 MAC 会应答多次输出会显得很多很乱。我改过一版维护一个全局的 MAC 地址数组每次收到应答时先遍历检查这个 MAC 是否已经出现过没出现过才打印输出。// 扩展MAC 去重后再打印 // 已收到应答的 MAC 是否存在存在返回 0不存在返回 1 int is_new_mac(unsigned char *mac, unsigned char mac_list[][6], int count) { for (int i 0; i count; i) { if (memcmp(mac_list[i], mac, 6) 0) { return 0; } } return 1; }使用memcmp比较 6 字节的 MAC 地址而不是用比较整个数组这是 C 里比较二进制数据的标准做法。每收到一个新应答就把 MAC 存入列表同时把收到的 IP 也存入另一个数组最后扫描结束时统一打印出 IP—MAC 对的完整映射表。从这个资源里你还能提炼出一个通用的网络探测流程无论之后是做 DHCP 请求注入、自定义 IP 报文发送还是交换机 MAC 地址表探测都是先构造数据帧、通过 pcap 发送、再抓包解析这三个步骤。把这些代码吃透等于掌握了一套完整的二层网络编程基本功。最后说一句大实话也是我的个人习惯从那以后我每次做一个新的网络探测程序都会强制自己在收包解析部分先画一帧的字节偏移图再对照 Wireshark 抓到的十六进制做一次逐字节比对确认偏移无误后再继续写后续代码。这个习惯帮我省掉了无数次调试时对着错误地址发呆的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表