ARTICLE DETAIL

资讯详情

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

局域网IP/MAC扫描:ARP协议与多线程并发实现解析

局域网IP/MAC扫描:ARP协议与多线程并发实现解析 简介这是一份基于Visual C开发的局域网设备扫描工具源码能够快速探测同一网段内所有设备的IP与MAC地址适合网络管理员、安全测试人员以及希望学习网络编程的开发者。扫描逻辑借助ARP或ICMP协议实现可在30秒内完成一个C类网段255台主机的探测有助于设备盘点、IP冲突排查和非法接入识别。压缩包共15个文件以h头文件、cpp源文件为主辅以rc、ico等界面资源及dsw、dsp工程文件源码组织结构清晰便于阅读与二次修改。整个包仅13KB内容紧凑但覆盖了MFC对话框程序的核心骨架。目前已有733人学习下载对想掌握局域网扫描原理和MFC网络编程的读者而言是一份轻量且实用的参考资料。1. 局域网IP/MAC扫描30秒扫完255台设备的ARP与并发设计如果哪天领导让你统计办公室里有多少台电脑、谁家的MAC地址跟IP对不上你总不能一台台跑去看ipconfig。用ScanLan这类工具30秒内把192.168.1.0/24的255个IP全探测一遍活着的设备拿到IP和MAC对着台账一查谁偷偷接进来了、哪两台IP冲突了一眼就清楚。我要拆的这套代码正是干这个的——Visual C 6.0写的MFC对话框程序核心是ARP请求加ICMP存活检测。它适合三类人被领导追着要设备台账的网管、做内网安全排查的IT、想学Windows网络编程的学生。扫完源码在手编译、运行、二次改造都方便。2. ARP与ICMP的组合拳扫描速度从10分钟压到30秒的三个开关2.1 为什么核心用ARP而不是纯Ping很多人在网上搜局域网扫描工具第一个想到的是Ping。Ping走ICMP协议探测的是IP层通不通。问题是目标机器一旦开了防火墙、禁了ICMP回显——这是很常见的默认设置——Ping直接丢包机器其实活着但扫不出来。ARP则完全不同只要这台机器在同一个二层网段并且想跟别人正常通信它就必然要处理ARP请求。所以拿ARP广播去问“谁是192.168.1.100”活着的机器基本都会回应顺便把MAC地址交出来。这里有个细节值得注意收到ARP请求的主机如果发现问的是自己会在协议栈层面无条件回ARP应答防火墙一般拦不住。所以ARP在局域网设备发现这件事上比ICMP可靠得多。ScanLan的做法正是两条腿走路对每个候选IP发一个ARP请求拿MAC再补一个Ping确认主机当前在线状态。一个回答“你到底叫不叫这个名字”一个回答“你现在喘不喘气”。2.2 SendARPWindows下拿MAC地址的最短路径在VC 6.0里最省事的做法是直接调用系统自带的SendARP。它封装了ARP请求的构造、发送和应答解析不用自己硬啃协议头。来看核心代码// ScanLan 扫描线程内的一段核心逻辑 #include winsock2.h #include iphlpapi.h #pragma comment(lib, ws2_32.lib) #pragma comment(lib, iphlpapi.lib) void ScanOneIP(const CString strIP, CString strMAC, BOOL bAlive) { // 把 192.168.1.100 转成网络字节序的ULONG ULONG ulDestIP inet_addr(strIP); ULONG ulMac[2] { 0 }; // 6字节MAC用两个ULONG装 ULONG ulLen 6; // 缓冲区长度单位字节 // 同步等待目标机器回ARP应答 DWORD dwRet SendARP(ulDestIP, 0, ulMac, ulLen); if (dwRet NO_ERROR ulLen 6) { BYTE* p (BYTE*)ulMac; // 格式化成 AA-BB-CC-DD-EE-FF strMAC.Format(_T(%02X-%02X-%02X-%02X-%02X-%02X), p[0], p[1], p[2], p[3], p[4], p[5]); bAlive TRUE; } else { // 返回错误码非NO_ERROR目标不在线或不在同一广播域 strMAC _T(); bAlive FALSE; } }inet_addr把点分十进制的IP字符串转成u_long这是SendARP要求的入参格式。ulMac缓冲区按6字节准备正好装一个物理地址。返回值NO_ERROR代表收到了ARP应答此时ulLen会被系统改写为实际收到的MAC长度所以判断要同时看dwRet和ulLen双保险。参数里第二个地址填0表示用本机默认网卡发包如果机器有多个网卡想指定从哪块网卡发ARP就把对应网卡的IP填进去。SendARP自身是同步阻塞的目标无应答时系统会按协议栈的重试策略等上一阵这个时间点后面会专门讲。2.3 多线程分段扫描8个线程各扫32个IP如果一台接一台地串行调用SendARP255个IP扫完最坏情况要十几分钟离“30秒”差太远。常见做法是把网段切段用8个线程并发扫每段32个IP总耗时基本取决于最慢的那段// 用 8 个线程扫描每线程分一段 #define THREAD_COUNT 8 #define IP_RANGE 254 // 1~254.0 和 .255 不扫 typedef struct { int nStart; // 起始地址最后一段 int nEnd; // 结束地址最后一段 CString strBase; // 比如 192.168.1. } SCAN_TASK, *PSCAN_TASK; UINT ScanWorker(LPVOID lpParam) { PSCAN_TASK pTask (PSCAN_TASK)lpParam; for (int i pTask-nStart; i pTask-nEnd; i) { CString strIP; strIP.Format(_T(%s%d), pTask-strBase, i); ProcessOneIP(strIP, i); // 单IP处理结果写入共享列表 } return 0; } void StartScan() { SCAN_TASK tasks[THREAD_COUNT]; int seg IP_RANGE / THREAD_COUNT; // 每段32个IP for (int t 0; t THREAD_COUNT; t) { tasks[t].strBase _T(192.168.1.); tasks[t].nStart t * seg 1; // 1, 33, 65... tasks[t].nEnd (t THREAD_COUNT - 1) ? 254 : (t 1) * seg; AfxBeginThread(ScanWorker, tasks[t]); // 启动线程 } }分段逻辑是重点8个线程254个地址每段32个最后一个线程扫到254补齐余数不漏地址。AfxBeginThread是MFC封装的线程创建函数这里不需要保留句柄扫完线程自己退出。线程数不是越多越好——16个线程同时往交换机砸ARP广播交换机和网卡负载升高反而丢包扫出来都是漏的。我在千兆有线网里实测8线程最佳无线局域网降到4线程更稳。还有一个共享资源的事得提醒多个线程同时往ListView里插入条目不加临界区轻则丢数据重则界面闪退。老一点的做法是给列表操作加CRITICAL_SECTION我一般倾向于用PostMessage把结果抛回UI线程让主线程独占写列表既安全又省心。2.4 超时与重试100ms是底线不是上限SendARP的超时策略受系统协议栈控制Windows对ARP请求默认会做多次重传整体等待时间在1到3秒波动。如果255个IP全无应答线程全在等超时30秒根本收不了场。常见做法是加一层快速预判先用Socket往目标IP发一个TCP连接请求端口随便挑个冷门的如果对方秒回RST说明主机在线只有“看起来在线”的IP才去调SendARP拿MAC。网段边界也要记住.0是网络地址.255是广播地址这两个别放进扫描范围发了ARP也是全网广播泛洪纯属给自己找麻烦。另外建议把默认网关的IP先单独SendARP一次确认二层边界是通的再开始全段扫描——网关MAC都拿不到的话后面结果列表基本可以预判是空的。3. 拆解ScanLanByIPC的VC6源码从StdAfx到IPCSendFile的主控链路3.1 拿到源码包先看哪几个文件把ScanLanByIPC源码包解压后里面是一整套VC 6.0工程。先别急着双击dsw按下面这个顺序把结构捋清楚文件作用需要动吗ScanLanByIPC.dsp / .dswVC 6.0工程文件和工作区一般不动ScanLanByIPC.clwClassWizard类信息文件不用动ScanLanByIPC.cpp / .hApp类入口InitInstance里创建对话框入口看一眼ScanLanByIPCDlg.cpp / .h主对话框按钮事件和扫描逻辑主力主要改这里IPCSendFile.cpp / .h附加模块名字带IPC按需改StdAfx.cpp / .hMFC预编译头一般不动Resource.h / ScanLanByIPC.rc / .rc2资源ID和对话框定义改界面时动ScanLanByIPC.ico程序图标要换图标时替换这个清单是按“从入口到逻辑”排的。.dsw是工作区文件双击它会把关联的.dsp一起打开.clw删了的话类向导会提示重建不影响编译。StdAfx.h里通常预编译了afxwin.h、afxext.h这些MFC核心头文件后面每个cpp编译速度都会快不少这就是预编译头存在的意义。3.2 ScanLanByIPCDlg.cpp扫码按钮后面发生了什么主对话框是这个程序的心脏。OnInitDialog初始化界面和列表控件用户点“扫描”按钮触发消息映射进入扫码处理函数。典型流程是读取编辑框里的网段前缀比如192.168.1.→ 清空上一次结果 → 启动多线程扫描 → 轮询线程结束后刷新列表。// 扫码按钮的处理函数主流程参考 void CScanLanByIPCDlg::OnBnClickedScan() { // 1. 清空上一次的扫描结果避免新旧数据混在一起 m_listResult.DeleteAllItems(); // 2. 从界面控件读入网段前缀 CString strPrefix; GetDlgItemText(IDC_EDIT_PREFIX, strPrefix); if (strPrefix.IsEmpty()) { AfxMessageBox(_T(网段前缀不能为空)); return; } // 3. 禁用按钮防止扫描期间被再次触发 GetDlgItem(IDC_BTN_SCAN)-EnableWindow(FALSE); // 4. 启动多线程分段扫描 StartScan(); // 5. 开一个定时器轮询线程状态扫完自动恢复按钮 SetTimer(1, 500, NULL); }这个流程里最容易翻车的是第三步和第五步的组合线程是异步的按钮不临时禁用的话用户手滑又点一次扫描两个扫描任务同时写ListView不崩才怪。定时器每次触发检查“剩余任务数”这个全局计数器减到0就停止定时器、恢复按钮这是MFC工程里最朴素的异步完成通知方式。还有个新手常踩的坑后台扫描线程不能直接调CListCtrl的InsertItem更新界面。控件对象绑定的是主线程的消息循环跨线程写控件轻则闪烁、重则直接退出。正确做法是自定义一条消息比如WM_USER 101在线程里PostMessage(hWnd, WM_USER 101, ipAddr, macAddr)然后主窗口的消息响应函数里统一更新ListView。老代码里如果看到直接把CListCtrl指针传进线程函数的写法多半就是闪退的根源。3.3 IPCSendFile.cpp这模块到底干什么这是不少人第一次看源码时最困惑的文件。文件名里的IPC按常规理解应该是Inter-Process Communication但在局域网扫描的场景里我更倾向于把它看成“IP控制”或“IP配置”相关的辅助模块。从工程依赖看IPCSendFile.cpp没有引用ScanLanByIPCDlg.h说明它是个相对独立的辅助类接口靠参数接收IP、MAC字符串。它大概率承担的功能是扫描线程拿到一个IP和MAC后把“IP、MAC、主机状态”这些拼接好的数据流交给IPCSendFile里的处理函数由它负责写日志文件或者通过Socket把结果推给管理端。看名字带SendFile甚至可能承担了把指定文件发给目标主机的职责。这套模块化分工在老VC工程里很典型界面归界面、业务归业务。二次开发时想把扫描结果上报给运维系统改这个文件比在主对话框里堆代码干净得多如果只想本地看结果把它从工程里排除掉也能正常编译。3.4 在更高版本Visual Studio上编译的三处改动这份源码在VS2010以上的环境编译通常会碰到三个典型报错。第一个是字符集冲突。VC 6.0年代很多工程默认ANSIVS2015及以上默认Unicode直接编译会冒出一堆C2664。到项目属性“常规→字符集”改成“使用多字节字符集”即可或者统一改成Unicode并把所有CString写法对齐。第二个是Winsock初始化。新版SDK默认带Winsock2如果报一堆socket未定义的错检查是不是漏了#include winsock2.h以及链接库是否少了ws2_32.lib。第三个是CString::Format格式化字符串时%s和%S的坑。老代码用%s格式化char*在Unicode工程里会输出乱码需要按_T宏统一途别。这三处改完编译基本就顺了。4. 避坑指南MAC全零、虚拟机扫不到、跨网段全灭的五个处置案例4.1 现象虚拟机一台都扫不到物理机上跑ScanLanVMware或VirtualBox里的虚拟机全都不出现。原因分两种。第一种是网卡模式NAT模式下虚拟机走宿主机虚拟网卡对外表现为宿主机的一个进程工具从二层看不到它桥接模式有自己的MAC但虚拟网络编辑器里“允许虚拟机混合连接”没开的话ARP依然会被隔离。第二种是虚拟机防火墙它一样会拦掉不必要的入站探测。解决先确认虚拟机网卡是桥接模式在虚拟机里ping一下网关IP能通说明二层通了再临时关掉虚拟机防火墙重新扫描。VMware还有个细节默认对虚拟网卡做MAC随机化每次重建虚拟机MAC都会变台账里的MAC要记得按新值更新。4.2 现象扫出来的MAC全是00-00-00-00-00-00这个值得单独说。MAC全零意味着ARP应答虽然收到了但MAC字段没有解析出来。常见原因有两个。一是缓冲区长度没初始化好ulLen传进去不是6函数返回后长度校验失败二是请求走错了网卡——本机装了虚拟网卡驱动或者代理类软件默认接口不是物理网卡SendARP的请求从虚拟路径出去应答自然不含真实MAC。解决调SendARP前先枚举本机网卡接口找到“IP在同一网段且状态是Up”的物理网卡把它的IP作为第二个参数传进去。还有一点MAC前三位是OUI厂商标识拿到后可以反查网卡品牌排查陌生设备时能帮你快速判断是打印机还是手机。4.3 现象跨网段扫描一片空白这是原理层面的限制不是代码bug。ARP广播只能在同一个广播域里传播。本机IP在192.168.50.10却去扫192.168.1.0/24中间隔着路由器ARP请求到不了目标网段SDK自然什么都拿不回来。跨网段扫描是三层路由的事二层探测工具干不了这活。解决临时把本机IP改到目标网段或者在核心交换机上看要不要开ARP代理。企业网络里最好的方式其实是直接到三层交换机上执行show arp把整张ARP表导出来比任何工具都全。4.4 现象杀毒软件直接杀掉进程老VC程序没有数字签名行为上又有“遍历局域网ARP 主动连目标端口”的特征Windows Defender和第三方杀软都容易误报。这事挺玄学的同样的代码换个版本编译可能就不报了。加上IPCSendFile.cpp里如果有文件发送相关的Socket操作误报率会更高。解决把程序目录加进杀软白名单在公司管控机器上跑先找IT确认策略更实际的做法是只保留扫描和导出功能把“发送文件”这类容易被拦截的模块拆掉留空降低行为特征的敏感度。4.5 现象扫描速度比预期慢很多千兆内网环境255台扫了七八分钟。除了多线程没生效最常见原因是网段前缀填错。比如本机网关是192.168.2.1你在工具里填了192.168.1255个ARP请求全在空等超时自然慢得离谱。解决扫描前先ping一下网关确认网段再把SendARP的目标IP打印到调试窗口确认实际扫描范围。另外注意无线网络环境——WLAN的广播延迟远高于有线8线程并发容易把无线带宽打满降到4线程反而更快。这属于参数跟着环境走的典型案例。5. 把ScanLan用成生产工具CSV台账导出与DHCP静态绑定联动5.1 扫完直接导出Excel能开的CSV界面里看结果没法留底维护设备台账必须导出。把ListView循环读出来按IP、MAC、主机名写文件注意编码用ANSIExcel打开才不乱码。void CScanLanByIPCDlg::ExportCSV(LPCTSTR lpszPath) { CStdioFile file; if (!file.Open(lpszPath, CFile::modeCreate | CFile::modeWrite)) { AfxMessageBox(_T(导出文件失败)); return; } file.WriteString(_T(IP,MAC,HostName\r\n)); for (int i 0; i m_listResult.GetItemCount(); i) { CString strLine; strLine.Format(_T(%s,%s,%s\r\n), m_listResult.GetItemText(i, 0), m_listResult.GetItemText(i, 1), m_listResult.GetItemText(i, 2)); file.WriteString(strLine); } file.Close(); }CStdioFile是MFC里处理文本文件最顺手的类\r\n是Windows换行符导出后Excel、Notepad都能直接开。文件名我习惯带日期比如ip_mac_20250608.csv一天一个文件后面做设备增删对比才有依据。5.2 把结果喂给DHCP静态绑定小网段环境里出口路由器或核心交换机基本都支持DHCP静态绑定。把CSV里每一行IP和MAC的对应关系登录路由器管理界面找到“DHCP服务器→静态地址分配”按行填入。填的时候注意MAC分隔符有的设备要求XX-XX-XX-XX-XX-XX有的要求XX:XX:XX:XX:XX:XX先看管理界面里的示例再动手。绑定生效后未登记设备接入就分不到合法IP等于从DHCP层面直接断网。5.3 定时扫描的增量对比才是真价值这是我自己最深的体会。以前是出了问题才扫一次抓到非法设备就拉黑以为完事了。后来发现设备是有规律冒出来的——今天没有明天就有了。后来改成每天凌晨用系统计划任务跑一次扫描自动导出CSV再写个小脚本跟昨天的文件做diff多出来的就是新接入设备少掉的就是下线设备。从那以后每次做网络变更我都强制先跑一遍扫描做基线这个习惯帮我拦住过好几次改配置改出的事故。希望帮到你。本文还有配套的精品资源点击获取
返回列表