简介这是一套用于扫描局域网内全部设备IP与MAC地址的VC源码工程ScanLanByIPC面向网络管理员、安全测试人员以及初学Windows网络编程的开发者。工具基于ARP/ICMP探测原理能够在约30秒内完成一个网段255台主机的地址发现并借助IPC通信机制扩展文件发送功能兼顾实用与教学。压缩包共15个文件整体仅13KB包含5个头文件、4个C源文件以及rc、dsp、ico等工程辅助文件完整呈现MFC对话框程序的目录结构便于对照学习界面搭建、网络探测与通信模块的分离写法。已有733人学习下载。通过这套代码读者既能掌握局域网主机发现、MAC地址获取与ARP请求构造的具体实现又能理解MAC地址在数据链路层通信中的角色以及DHCP环境下基于MAC识别设备的思路在此基础上还可继续改造为资产盘点或接入审计工具服务日常网络管理与安全排查。1. 半夜被 IP 冲突逼疯之后我靠 ScanLan 把整层楼的设备翻了出来公司新搬的办公室网络隔三差五掉线一查 DHCP 日志全是地址冲突。两台打印机一台工控机各抢了一个 IP光靠ping和arp -a一台台猜能折腾到后半夜。后来我把整层楼的网段扫了一遍用 ScanLan 这类主动探测手段把每一个活着的 IP 和对应的 MAC 地址拉成一张表十分钟定位到那台手配了静态 IP 的旧服务器。所谓 ScanLan本质就是站在局域网里喊一嗓子让所有设备报上名来——IP 是名字MAC 是身份证号。这篇文章要解决的就是三件事局域网扫描靠什么原理拿到地址、用哪些现成工具最快、以及自己写扫描脚本时会在哪些地方翻车。适合被 IP 冲突、资产盘点、找陌生设备折磨过的运维和网络管理员也适合想搞懂 ARP 探测底层逻辑的入门者。2. 为什么局域网里能扫出 IP 和 MAC先搞懂 ARP 这张「通信录」2.1 一台电脑怎么在局域网里「喊话」广播与 IP-MAC 映射TCP/IP 协议栈里IP 地址是逻辑地址真正把数据从网卡发出去靠的是 MAC 地址。两台机器通信之前必须知道对方的 MAC这个「知道」的过程由 ARP地址解析协议完成。发一个广播帧到ff:ff:ff:ff:ff:ff内容大致是「谁有这个 IP请把 MAC 告诉我」收到请求的目标设备会单播回应。这一问一答被操作系统记录在一张 ARP 缓存表里表里每一行就是一个 IP 和 MAC 的对应关系学过网络的都知道它是「通信录」——但没人告诉你这张通信录是可以反向利用的。ScanLan 的核心思路就是主动翻开这张表而不是被动等别人来填。当扫描机向整个网段广播 ARP 请求时所有活着的设备都会响应这些响应会同时落在两个地方对方的网卡收到了探测包扫描机自己的 ARP 缓存里则多了一行记录。于是你只需要三条命令发广播 ARP、等响应、读arp -aWindows或ip neighLinux。原理简单到像按门铃但真正做扫描时你会发现门铃也有门铃的讲究。2.2 ping 扫描和 ARP 扫描的差别为什么 ping 通不代表能拿到 MAC很多人的第一反应是用 ping 去探测网段——写个循环把192.168.1.1到192.168.1.254全部 ping 一遍然后再arp -a。这种做法能工作但有三个盲区。第一对方开启了防火墙ICMP 回包被丢弃但 ARP 请求是链路层行为防火墙默认不拦第二ping 扫描需要等超时一个网段完整跑下来要几分钟而纯 ARP 探测毫秒级就能收到响应第三部分系统尤其 Windows对陌生 ICMP 的处理策略跟 ARP 不一致导致你 ping 通了却拿不到 MAC。最典型的情况是你看 ARP 缓存里有一行显示 IP 是通的但 MAC 是incomplete——响应根本没到网卡层面。所以真正好用的局域网扫描器比如 arp-scan、nmap 的-sn模式、以及自己写脚本时都在做同一件事绕过 ICMP直接用 ARP 请求来「点名」。这也解释了为什么网工调试时说的那句「ping 不通不代表主机不存在ARP 能通就说明它活着」是对的。换个说法就是ICMP 是名片递没递到ARP 是身份证验没验过后者更可靠。2.3 交换机在这个过程里扮演什么角色为什么广播能到所有机器现代交换机不是 Hub它不会无脑把每个帧转发给所有端口——它维护一张 MAC 地址表记录每个端口连着哪些 MAC。但有一个例外目的地址是ff:ff:ff:ff:ff:ff的广播帧交换机会泛洪到所有端口。这意味着 ARP 请求能到达同一个广播域内的每一台设备而这就是 ScanLan 能工作的前提。理解这点很重要后面踩坑部分会提到如果你接在隔离端口上交换机把 ARP 广播挡掉了扫描结果就会「一片荒凉」。这里顺带交代一个排查 IP 冲突的技巧当同一 IP 有两个设备响应 ARP扫描结果里就能看到两个不同的 MAC 对应同一个 IP这是最直接的证据。ScanLan 产物里的「异常」往往是网络故障的答案不止是资产清单。3. 用现成工具扫一遍Windows 命令、arp-scan、nmap 三套打法3.1 Windows 下不求人for 循环 ping 全段再用 arp -aWindows 没有内置 arp-scan但 PowerShell 可以模拟整个流程。最朴素的做法是先遍历 ping 整个网段把 ARP 缓存「喂饱」再读缓存表。打开管理员身份的 CMD输入for /L %i in (1,1,254) do ping -n 1 -w 100 192.168.1.%i nul arp -a这段代码里for /L %i in (1,1,254)是让变量%i从 1 递增到 254每次递增 1ping -n 1表示只发一个 ICMP 回显请求-w 100是等待 100 毫秒超时 nul把 ping 的显示输出丢弃让命令静默执行。执行完再arp -a就能列出缓存中的所有 IP-MAC 对应关系。这样扫完整个 C 类网段大概耗时 40 秒左右因为 254 次 ping 是串行的。注意arp -a的输出格式在不同 Windows 版本有差异但都有 IP 和 MAC 两列。这种做法有个明显短板只能扫出「愿意回 ICMP」的设备比如 Windows 防火墙默认禁 ping 的机器会漏掉。改进办法是用 PowerShell 的并行能力把扫描时间压到几秒同时改用 ARP 请求1..254 | ForEach-Object -Parallel { Test-Connection -ComputerName 192.168.1.$_ -Count 1 -Quiet } -ThrottleLimit 50 arp -aForEach-Object -Parallel把 254 个 ping 分成多路并行执行-ThrottleLimit 50限制并发数防止把 CPU 跑满整体时间能压到 5 到 8 秒。但核心问题还在——这段代码本质上还是 ICMP 探测不是纯 ARP 扫描遇到禁 ping 的机器依然看不到。Windows 下想深度扫真得借用外部工具。3.2 Linux 下最省事arp-scan 的安装、参数与典型输出Linux 下扫局域网我一般首选 arp-scan它就是为这个场景设计的发 ARP 请求然后收集响应。安装方式一条命令Debian/Ubuntu 系是sudo apt install arp-scanRedHat/CentOS 系是sudo yum install arp-scanmacOS 用户可以用 Homebrew 装名字一样。核心用法sudo arp-scan --local --interface eth0 --retry 3 --timeout 300--local让程序自动探测本机所在网段和广播地址不用手算子网掩码--interface eth0指定走哪块网卡多网卡机器必须指定否则可能在错误的接口上扫描--retry 3表示每个 ARP 请求最多重试 3 次应对丢包--timeout 300是单次等待响应的超时毫秒数。三条参数是血泪经验换来的——默认值在无线网络里经常漏设备无线网卡省电模式会吞广播帧必须加重试。输出格式非常友好每行一个设备从左到右是 IP、MAC、厂商 OUI 说明192.168.1.1 aa:bb:cc:dd:ee:ff (Unknown) 192.168.1.28 00:23:24:xx:xx:xx Dell Inc. 192.168.1.105 48:5b:39:xx:xx:xx Microsoft Corporation第三列的厂商信息是 arp-scan 内置的 OUI 数据库匹配出来的对识别设备类型非常有用——看到 Microsoft 就知道大概率是台 PC看到 Dell 可能是服务器也可能是台式机。如果显示(Unknown)代表网卡厂家的 OUI 不在库里通常是小厂设备或虚拟网卡。这个细节在资产盘点时能省不少事。3.3 nmap 的 -sn 模式不扫端口、只做主机发现nmap 不止是端口扫描器它的-sn模式专门做主机发现不探测端口。在同网段下-sn默认会发送 ICMP Echo 请求和 TCP SYN 到 443、80 端口加上 ARP 请求在以 root 运行时。但要让扫描结果里的 MAC 地址完整最好直接指定 ARP 探测sudo nmap -sn -PR 192.168.1.0/24-sn表示只做主机发现不做端口扫描-PR强制走 ARP ping这是同网段扫描最准的方式对所有设备包括禁 ping 的有效。加-v可以看实时进度设备多的时候很有用。nmap 的优势是脚本化能力强后面会讲到怎么把 XML 输出整合进监控系统。缺点是输出可读性不如 arp-scan 的表格而且参数比 arp-scan 多一截新手容易误加-p端口参数导致变成全端口扫描慢到怀疑人生。3.4 三种工具怎么选按场景而不是按「哪个更高级」如果只需要一次性排查Windows 自带命令够用如果是例行资产盘点arp-scan 的输出最直观如果要长期监控、脚本化集成nmap 的 XML 输出最方便。我的习惯是临时排障用 arp-scan写自动化采集脚本用 nmapWindows 应急用系统原生命令兜底。值得强调的是所有工具都依赖本机和目标处于同一个二层广播域——跨 VLAN 是扫不到的这是后面避坑章节的核心主题之一。4. 自己写一个 ScanLanPython 构造 ARP 请求五十行拿下一张表4.1 为什么自研可定制输出和持续集成是现成工具给不了你的工具再好也是别人的场景一变就难受。比如你想把扫描结果自动写入 CMDB 数据库或者每天凌晨扫一次、把新增设备推送企业微信告警又或者要按网段分组输出自定义格式这个时候脚本的价值就出来了。自研 ScanLan 的核心模块只有三步构造 ARP 请求包、广播发送、捕获响应。Python 生态里最常用的是 Scapy它把以太网帧和 ARP 报文的构造压在了一行代码内非常适合快速实现。4.2 完整代码广播 ARP 请求并采集 IP-MAC 映射下面这段代码基于 Scapy逻辑很直白逐段拆开看#!/usr/bin/env python3 轻量 ScanLan用 ARP 广播扫描局域网内所有活跃设备 import sys import time from scapy.all import Ether, ARP, srp def scan_lan(network: str, iface: str None, timeout: float 2.0) - list: 向指定网段发送 ARP 广播请求返回 (ip, mac, oui) 列表。 network: 如 192.168.1.0/24 iface: 绑定网卡名多网卡机器建议显式指定 timeout: 等待响应总超时秒 # 构造以太网帧 ARP 请求pdst 是目标 IP设置成广播网段 arp_request ARP(pdstnetwork) # Ether(dstff:ff:ff:ff:ff:ff) 表示二层广播帧 broadcast Ether(dstff:ff:ff:ff:ff:ff) packet broadcast / arp_request # srp 是发送并接收在第 2 层工作timeout 即收包等待时间 ans, _ srp(packet, timeouttimeout, ifaceiface, verboseFalse) devices [] for sent, received in ans: # received 是目标设备的 ARP 响应包 ip received.psrc # 响应方 IP mac received.hwsrc # 响应方 MAC oui mac[:8].upper() # 取 OUI 前 3 字节用于厂商识别 devices.append((ip, mac, oui)) return devices if __name__ __main__: if len(sys.argv) 2: print(f用法: {sys.argv[0]} 192.168.1.0/24 [网卡名]) sys.exit(1) net sys.argv[1] dev sys.argv[2] if len(sys.argv) 2 else None start time.time() result scan_lan(net, ifacedev, timeout3) print(f发现 {len(result)} 台设备耗时 {time.time() - start:.1f}s) for ip, mac, oui in result: print(f{ip}\t{mac}\t{oui})这段代码的核心逻辑是ARP(pdstnetwork)构造一个 ARP 请求pdst设成网段地址而不是单个 IPScapy 会自动展开成对网段内所有地址的请求Ether(dstff:ff:ff:ff:ff:ff)保证广播到整个广播域。srp函数发送数据包并监听响应收到的响应包中psrc是对方 IPhwsrc是对方 MAC。代码里把 OUI 单独提出来方便后续做厂商分类实际采集时可以扩展成查 IEEE OUI 数据库来显示品牌比如00:0C:29是 VMware52:54:00是 QEMU/KVM 虚拟机的默认段。4.3 参数怎么调timeout 和重试是扫描完整度的关键代码里最值得调的参数是timeout。有线网络下 2 秒够用无线网络建议放到 3 到 5 秒因为 Wi-Fi 网卡的省电模式会让部分设备延迟响应 ARP。另一个容易被忽略的细节是iface参数——如果你的机器有多个网卡比如「有线 无线 VMware 虚拟网卡 Docker0」不指定接口时 Scapy 可能选错网卡结果扫出来只有虚拟网卡网段里的东西。肉眼检查输出的 IP 段是不是预期的网段是每次扫描之后的第一件事。如果扫描结果明显偏少原因是丢包——ARP 请求是 UDP 一样的无连接机制没有重传就丢了。解决方案是分段扫把/24拆成四个/26每段各扫一次比整体扫一遍再重试更稳。落实到代码上就是把ARP(pdstnetwork)换成ARP(pdstf192.168.1.{start}-{end})这种范围写法。4.4 升级版思路用表格和文件落盘让扫描结果留存可追溯脚本的边界不只在「扫出来」更在「留得住」。这里给一个低成本的落地思路把结果写成 CSV文件名带时间戳这样每次扫描都是独立的快照import csv from datetime import datetime def save_csv(devices: list, filename: str None) - str: if filename is None: filename fscanlan_{datetime.now().strftime(%Y%m%d_%H%M%S)}.csv with open(filename, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([ip, mac, oui, timestamp]) ts datetime.now().isoformat(timespecseconds) for ip, mac, oui in devices: writer.writerow([ip, mac, oui, ts]) return filename拿这个 CSV 再做一层加工就能和后面要讲的 IP-MAC 基线监控对接起来。需要注意编码问题Windows 下用 Excel 打开 CSV 时中文注释可能乱码encoding参数建议改成utf-8-sig兼容性最好。5. 局域网扫描避坑指南五条血泪经验照着少走一夜弯路5.1 扫出来只有网关一台设备其他全消失现象ARP 扫描返回结果里只有路由器的 IP 和 MAC别的机器一概不见。原因最常见的是交换机开启了端口隔离Port Isolation或者你连的是 AP 的访客 SSID。访客网络这种隔离网络中设备之间被强制隔离广播帧只有 AP 能收到所有 ARP 请求都石沉大海。另一种可能是所在端口被划入了私有 VLANPVLAN同样会拦截同网段设备之间的广播。解决换到普通交换机端口或者确认自己所在网络的 AP 隔离设置。用网线直连交换机试试如果直连后设备能扫全那基本就是无线隔离的问题。另外记得先看本机 IP 是不是目标网段——有的人配了静态 IP 不在既定网段扫描结果自然全是「不在场」。5.2 扫了一大堆 00:50:56 开头的地址物理机反而看不见现象结果里大量00:50:56VMware、00:0C:29VMware 另一段、52:54:00QEMU开头的 MAC真机没见几台。原因这台安装扫描工具的机器不是物理机而是某台宿主机上的虚拟机。虚拟网卡把自己的虚拟 MAC 广播出去了扫描机看到的自然全是虚拟设备。类似的现象还出现在同时装了 VirtualBox、Docker 的机器上——docker0 网桥的 MAC 也会出现在结果里。解决判断扫描机本身的运行环境。在 VMware/Proxmox 等虚拟化平台上做局域网扫描建议把探测网卡的混杂模式打开或者在脚本里过滤虚拟网卡 OUI。过滤不意味着忽略——如果企业环境里真存在 VMware 虚拟机它们的 MAC 仍然有效只是在排障时要注意区分「虚拟设备」和「物理设备」两批资产。5.3 arp -a 显示 incomplete说明设备「半死不活」现象扫描到 IP 但 MAC 是incompleteWindows 和 Linux 的 arp 命令都会出现这个字段。原因ARP 请求发出去了系统在超时前没有收到响应——目标设备的网卡没有回或者回包被防火墙策略丢弃。目标设备本身开着但网卡驱动异常、IP 冲突导致协议栈混乱时也会出现这种情况。解决先手动ping一次目标 IP 再重新扫描确认目标是不是响应 ICMP 但不应答 ARP。如果 ping 通而 ARP 拿不到基本可以判断目标设备的系统协议栈出了问题常见于 Windows 上杀毒软件劫持了网络驱动或者第三方拨号软件修改了 ARP 表。这种设备靠空中扫描无法完整收集只能到物理机上查看。5.4 扫描结果里有 IP 冲突一个 IP 对应两个 MAC现象表里出现了两行同样 IP、不同 MAC 的记录通常是 IP 冲突的最直接证据。原因网络里有两台设备配置了相同的静态 IP或者一台设备配置了静态 IP另一台从 DHCP 获取了同一个地址。两台设备同时响应 ARP 请求时扫描脚本会把两条响应都记录下来。解决把这两条记录导出来拔掉其中一台设备的网线——先看 DHCP 服务器地址池里有没有预留再排查哪些设备手动设置了静态 IP。扫描给出的两个 MAC 直接缩小了排查范围后续只需要根据 OUI 和交换机端口定位物理位置不需要再全网广播喊话。这也是 ScanLan 最有价值的一张「排错表」。5.5 跨 VLAN 扫描什么都得不到ARP 广播到不了别的网段现象扫描机在 VLAN 10却拿不到 VLAN 20 里设备的 IP-MAC 表。原因ARP 广播被 VLAN 边界隔离VLAN 20 的机器收不到请求自然也没有响应。这是正常的二层隔离设计。如果跨 VLAN 也要做资产盘点需要在 VLAN 20 里也放置一台扫描机或者用交换机的 SNMP 读取 MAC 地址表。解决把扫描机接入多个网段或者用支持 802.1Q 的网卡打 trunk 口为每个 VLAN 建立虚拟接口分别扫。更规范的方案是启用交换机的 DHCP snooping 和 MAC 地址表查询功能从交换机侧拉取全网 MAC 信息。这里也引出一个思路单机再快也有边界扫描机补不到的位置要让网络设备自己去「交代」。6. 把 ScanLan 变成持续监控IP-MAC 基线和变更告警的落地写法扫描一次只是快照持续监控才防患未然。把前几章里保存的 CSV 拿来和上次扫描结果做对比一旦出现「新的 IP-MAC 对」或「同一个 IP 换了 MAC」立刻推送到告警这事就算闭环了。基线数据维护在 SQLite 里最轻量这里给一个简单但能直接跑起来的变更检测思路import sqlite3, csv, sys from datetime import datetime def load_csv(path: str) - dict: mapping {} with open(path, newline, encodingutf-8) as f: for row in csv.DictReader(f): mapping[row[ip]] row[mac] return mapping def update_baseline(new_mapping: dict, db_path: str lan_baseline.db): conn sqlite3.connect(db_path) conn.execute(CREATE TABLE IF NOT EXISTS baseline ( ip TEXT PRIMARY KEY, mac TEXT, first_seen TEXT, last_seen TEXT)) time_now datetime.now().isoformat(timespecseconds) for ip, mac in new_mapping.items(): cur conn.execute(SELECT mac FROM baseline WHERE ip ?, (ip,)) row cur.fetchone() if row is None: conn.execute(INSERT INTO baseline VALUES (?, ?, ?, ?), (ip, mac, time_now, time_now)) print(f[新设备] {ip} - {mac}) elif row[0] ! mac: print(f[变更] {ip} 的 MAC 从 {row[0]} 变为 {mac}) conn.execute(UPDATE baseline SET mac ?, last_seen ? WHERE ip ?, (mac, time_now, ip)) else: conn.execute(UPDATE baseline SET last_seen ? WHERE ip ?, (time_now, ip)) conn.commit() conn.close()load_csv把扫描结果加载成以 IP 为键的字典update_baseline对比数据库中的旧 MAC发现新增或变更就打印告警并更新last_seen时间戳。跑一次全量对比把告警输出到一个日志文件里再配个 cron 定时任务一个极简的局域网设备监控盘就成型了。做这件事之前我想得很美直到发现打印机一晚自动休眠、第二天重新上线MAC 没变但 IP 被 DHCP 换掉了——从此我学乖了基线对比永远以「IP 为键」IP 变了就是新事件。不折腾什么复杂的联动几行代码把每天的变化记录下来排进周报比等故障爆发后翻交换机日志省力得多。希望帮到你。本文还有配套的精品资源点击获取
