局域网IP-MAC扫描全攻略:ARP原理、工具选型与Python脚本实战
简介面向需要快速盘点局域网设备的管理与运维人员这份资源提供了一套基于 C 编写的局域网扫描工具源码。程序通过地址解析、网络探测等方式获取同网段主机的 IP 与 MAC 地址可用于网络管理、IP 冲突排查、设备准入识别等场景压缩包共 15 个文件、约 13KB含 5 个头文件、4 个 C 源文件及工程配置、资源脚本与图标文件结构精简便于直接打开工程研读与二次改造。ScanLanByIPC 相关代码展示了网段扫描、结果读取与界面集成的完整思路能帮助初学者理解局域网二层通信与地址解析原理也为有经验的开发者提供轻量实现参考。目前已有 733 人学习下载适合入门网络编程或需要快速搭建扫描工具的读者。1. ScanLan能给你什么三分钟出一张局域网IP-MAC对应表同事跑来求助路由器后台只显示一堆MAC分不清哪台是哪台又或者办公室打印机总“失联”想找出它现在拿到哪个IP。这种时候我就直接ScanLan扫一整段得到一张“IP ↔ MAC”对应表几分钟内就知道谁在线、它是哪块网卡。这篇笔记要把这条链路讲透为什么最终要靠ARP协议而不是ping来拿MAC现成工具有哪几个、命令怎么写以及一个可复现的Python脚本和五个常见翻车点。适合三类人——被IP冲突折腾到头疼的办公用户、刚接手公司网络资产清单的初级网管、想看看网段里有没有陌生设备的网络值守。开篇先放结论ping通不等于能拿到MACMAC是二层身份只有ARP会老老实实告诉我。2. 扫描原理与选型为什么ARP才是局域网扫IP的硬通货2.1 先理解“网络地址翻译官”IP是门牌MAC是身份证局域网里两台机器的对话表面看靠IP地址但以太网线上运送的数据帧认的是MAC地址——帧头的目的地址必须是某个物理网卡的地址否则交换机会把它丢进垃圾桶。所以机器A要发给机器B第一步永远是查ARP缓存查得到就直接封装查不到就喊一嗓子“谁是192.168.1.20告诉我你的MAC”这一声广播整个同网段都听得到目标机器听到后单独回一句“我是192.168.1.20我的MAC是00:1A:2B:xx:xx:xx”。这个“问”与“答”就是ARP协议的核心也是局域网扫描最底层的命根子。这个协议行为带来两个对扫描至关重要的结论。第一只要设备在线、且和你在同一个广播域绝大多数设备都会响应ARP——因为协议栈自己也要用ARP来维持通信这是“生存需求”不是“安全策略”。第二回应是直接发回给你的单播帧不是广播所以你能精准地拿到“这个IP背后那块网卡的物理地址”。顺便解决那个高频搜索“mac地址怎么查”本机的MACWindows用ipconfig /allLinux用ip link但查别人机器的MAC最标准的手段就是ARP请求。工具也好、脚本也好本质都是“向别人要MAC”。而路由器DHCP页面里给出的MAC是租约记录不是“此刻在线”的实时证据这一点后面还要专门踩它一脚。2.2 为什么ping扫一遍必然有漏网之鱼新手最容易用ping实现扫描脚本把C段的1到254挨个ping能收到回包就记“在线”。实际跑过的人都知道这种结果又慢又漏。漏在两头一头是很多设备根本不应答ICMP——打印机、摄像头、智能插座这类固件出于省电或安全策略直接丢弃ping包另一头是有些Windows机器的防火墙策略被改过把“回显请求”拦了还有一些服务器做了限流你等3秒超时它第5秒才回。这里我要强调一个认知误区用ping做局域网主机发现等于默认“所有设备都必须遵守ICMP的礼仪”而事实上这个前提就不成立。慢在机制上逐个发ICMP、逐个等超时254个地址每个等1秒就是4分多钟。更要命的是ping只告诉你在不在不告诉你在哪个MAC上你还得再用ARP去问一遍。所以它的角色只能是辅助不能当主扫描器。反过来看ARP设备只要在线并参与局域网通信它就必然回应ARP——若它不回应它自己也发不出任何包。这就是2.1里说的协议栈底线行为。我一般把“用ICMP保底、用ARP定真值”作为一条金线工具选型也围绕这一条展开。2.3 三种扫描方式的选型ICMP、ARP、TCP探测的边界扫描方式原理是否拿到真实MAC是否适合跨网段代表工具ICMP ping发echo等回应拿不到勉强可用ping、nmap -sn跨网段时退化为其他ARP请求广播问MAC单播答复能拿到不能出不了广播域arp-scan、nmap -sn同网段TCP connect探测尝试握手知名端口拿到的是网关MAC可以nmap -sT这个表里最容易被忽略的是第4列“跨网段”。ARP是二层协议广播出不了路由器你用自己电脑去扫另一个网段例如从192.168.1.0/24扫192.168.2.0/24能看到的回应基本只有网关的ARP应答。nmap在这种场景会智能地退化为“ICMPTCP 80/443探测”但它给不了那个网段里各主机真正的MAC地址。想拿到跨网段的IP-MAC映射正解是登到那台路由器或三层交换机上看它的ARP表命令通常是show arp或arp -a。这便是ScanLan类工具最常见的边界它能扫透的只是你广播域覆盖得到的范围。选型上我的一般原则是只问“谁在线”nmap -sn最快要一份干净整洁的IP-MAC-厂商三列清单arp-scan最顺手怀疑路由器开了AP隔离或客户端隔离、主动扫描被过滤时用netdiscover被动模式抓邻居的通信来“拼图”虽然有点玄学式聚沙成塔但野外场景还真得靠它。2.4 扫描完立刻沉淀“时间点快照”才能当排障依据有个小习惯必须养成任何一轮扫描结束立刻把本机ARP缓存导出成文件。因为扫描器工作的过程本身就是“问一遍然后等回应”系统会顺手把每个回应写进ARP缓存但ARP缓存有时效Linux邻居表默认几秒到几分钟Windows通常也是几十秒到几分钟过一会儿再查缓存里面已经空空如也。实操是nmap -sn或arp-scan跑完马上执行arp -aWindows或ip neigh showLinux并保存。这份“此刻快照”的价值远大于工具终端的滚动输出。排IP冲突、判断谁在抢占地址、回看某时段有哪些设备在线靠的就是这种带时间戳的台账。配合DHCP页面看差异就更明显了DHCP记录“分配过给谁”ARP快照记录“此刻正在通信”。两种数据都不是谎言只是哪一个更接近“真实”你要会挑。3. 现成工具跑通nmap、arp-scan、netdiscover三套命令3.1 nmap -sn先点名再顺手帮你把ARP缓存喂满nmap是网络诊断的瑞士军刀这里只取它的主机发现功能。安装方式大家都会背Linux下apt install nmapmacOS用brew install nmapWindows去官网下安装包或者直接在WSL里跑Linux版。最小命令nmap -sn 192.168.1.0/24-sn表示只做主机发现不做端口扫描。nmap很聪明检测到目标IP与自己同网段时默认就用ARP请求探测这样能顺带拿MAC目标跨网段时自动转向ICMPTCP探测这种场景拿不到对方MAC。扫完输出类似Nmap scan report for 192.168.1.1 Host is up (0.0021s latency). MAC Address: 00:16:3E:00:11:22 (VMware)带厂商名这一列省事——一眼看到“VMware”就知道是宿主机虚拟网卡占了“坑位”。日常我习惯加一串参数nmap -sn -n --exclude 192.168.1.1,192.168.1.100 192.168.1.0/24-n表示跳过DNS反解在无内部DNS的办公网里能去掉一大段等待和误报--exclude把网关、打印服务器、自己机器的IP排除掉输出更干净。做自动化时用-oX落成XMLnmap -sn -oX scan_$(date %F).xml 192.168.1.0/24-oX的好处是旁边写个Python脚本就能解析出“IP、MAC、状态、时间”的JSON后面接告警或报表都不用再造轮子。如果你发现结果里全是“Host is up”却没有MAC Address字段多半是扫了跨网段目标先自查网段配置这个前面已经提示过。nmap这一轮跑完主机活跃名单已经躺进本机ARP缓存。此刻马上存一份快照用的是2.4的流程先扫描、再快照、后分析。有人喜欢扫完顺手telnet一下某IP的80端口看通不通这时可以留着用于验证在线状态但别把端口探测当主机发现的辅助手段。3.2 arp-scan输出最像“资产清单”的命令如果nmap是“侦察兵”arp-scan就是“户籍警”它不发那么多探测只专注于ARP广播输出就是一张标准的三列清单——IP、MAC、厂商名。安装同样简单Debian/Ubuntu用apt install arp-scanmacOS用brew install arp-scanWindows原生没有建议在WSL里跑。最省事的命令是让它自动识别网段sudo arp-scan --localnet--localnet会读取当前网卡的IP和掩码来计算所在网段避免手抖扫错段。输出很干净192.168.1.1 00:16:3E:00:11:22 VMware, Inc. 192.168.1.23 cc:2f:71:xx:xx:xx (Unknown)如果你想严格控制行为再给足参数sudo arp-scan 192.168.1.0/24 --interface eth0 --retry3 --timeout500参数说明--interface指定网卡多网卡机器必须选否则可能拿默认网卡去扫另一个网段--retry3对每个IP重复发三次ARP请求能扛一阵无线网络丢包--timeout500是每次等待500毫秒超时。我的常用组合是retry2、timeout400扫254个地址约花15秒效率和完整性平衡得刚好。如果某次扫描“Unknown”特别多说明系统的OUI数据库老了更新一下系统包把OUI数据库一起升级即可。3.3 netdiscover主动扫之外还有被动监听这种“笨”办法netdiscover常被忽略但我在两种场景里必用它一是路由器开AP隔离后主动扫描全军覆没时二是无线网络里手机休眠、几十秒内不应答ARP主动探测漏掉一批移动设备时。它同时提供主动和被动两种模式。主动模式sudo netdiscover -r 192.168.1.0/24 -t 100-r指定网段-t是每轮探测的延迟毫秒数。它会以文本面板的形态滚动显示发现的主机谁在线、谁的IP和MAC一个个弹出来实时感很强。被动模式才见功力sudo netdiscover -p -i wlan0-p表示不主动发任何包只抓网卡收到的一切ARP请求和应答然后从这些“对话记录”里反向推出谁在跟谁说话。被动模式要等挂在那里五分钟到十分钟回来再看看到的是“正在通信”的设备子集对于那些不跟别人说话的主机它无能为力。两种模式配合才是完整方案先被动抓一会儿再用主动模式补全很多无线网下的翻车现场靠这一套组合扳回来。3.4 一套参数速查动手前照抄就行工具一条命令示例最常搭配的参数典型场景nmapnmap -sn 192.168.1.0/24-n / --exclude / -oX快速点名、自动化出XMLarp-scanarp-scan --localnet--interface / --retry / --timeout要干净IP-MAC-厂商清单netdiscovernetdiscover -r 192.168.1.0/24-p被动 / -t延迟AP隔离、无线休眠设备兜底裸命令arp -a / ip neigh show无查ARP快照留底补充一个容易被Windows用户卡住的点Windows的arp -a输出里MAC是用“-”分隔比如09-09-80-aa-bb-ccLinux的ip neigh输出是用冒号分隔09:09:80:aa:bb:cc。写跨平台脚本比较两张表之前先把“-”替换成“:”或去掉所有分隔符否则永远比对不上。这类问题在CSDN的讨论串里反复出现。4. 自己写一个ScanLan从读ARP表到多线程发包4.1 最朴素的实现直接翻系统ARP表不装任何库很多对Python有点底子的人不知道第一行scapy怎么写心里没底。没关系先来个30秒能落地的做法在跑过一轮扫描之后直接读系统自己的ARP缓存。这个方法只需要标准库import subprocess from collections import OrderedDict def read_arp_snapshot(): # Windows 用 arp -aLinux 用 ip neigh result subprocess.run([ip, neigh], capture_outputTrue, textTrue, checkTrue) hosts OrderedDict() for line in result.stdout.splitlines(): parts line.split() if lladdr in parts: ip parts[0] mac parts[parts.index(lladdr) 1] hosts[ip] mac.upper() # MAC统一大写省得后面比对麻烦 return hosts for ip, mac in read_arp_snapshot().items(): print(f{ip}\t{mac})逻辑说明ip neigh是Linux查看邻居缓存ARP表的命令标准输出是“192.168.1.1 dev eth0 lladdr 00:16:3e:... STALE”这样的行。解析时只取包含lladdr关键字的条目它表示“我已学到它的MAC”FAILED状态说明请求过但没回应排除掉。Windows用户把[ip,neigh]换成[arp,-a]然后用正则从“192.168.1.1 09-09-80-aa-bb-cc”这类行里抠内容。为了方便后面和别人的表做比对统一用upper()把MAC变大写。这个方案局限明显本机ARP缓存是“最近通信过”的记录缓存会老化扫完只靠它可能漏掉“刚才没碰过头”的设备。所以它更适合做“查快照”而不是“发起扫描”。核心流程是先主动扫一轮让系统学一遍再在这里读出来这也就是2.4快照法的代码版。4.2 主动发ARP包scapy的最小实现四行就够主动探测要引入scapy这个造包框架。安装一句命令Windows还多一步Npcap驱动pip install scapyNpcap是Windows上发包的底层驱动装好才能让scapy发出二层以太网帧。最小实现直接看代码from scapy.all import ARP, Ether, srp target 192.168.1.0/24 packet Ether(dstff:ff:ff:ff:ff:ff) / ARP(pdsttarget) ans, _ srp(packet, timeout3, verbose0) for sent_pkt, recv_pkt in ans: print(f{recv_pkt.psrc}\t{recv_pkt.hwsrc})逻辑说明Ether这一层的dst设成广播地址ff:ff:ff:ff:ff:ff让整个广播域都接到这个请求ARP层的pdst是一个字符串CIDR“192.168.1.0/24”scapy会自动展开为254个目标IP分别发包。srp是“send and receive at layer 2”——发二层帧并等二层应答。返回值里每一对元素中第一个是发出的请求包第二个是接收到的应答包从应答包里取psrc源IP和hwsrc源MAC即是“某IP背后对应哪个网卡”。timeout3是指整个srp调用最多等待3秒的收包窗口verbose0关掉滚动输出。注意如果全段一个答复都没有先检查两件事——一是scapy装在了哪个Python环境、Npcap有没有装二是目标网段确实能在广播域内到达别跑着192.168.1.x的网卡去扫192.168.2.x跨段时二层广播根本出不去。4.3 多线程完整版线程池、并发数、超时是个三因素调校台网段设备少时串行就够了到了几十台设备的办公室串行发一轮254个ARP要几十秒。多线程是必然选择。下面是一个能直接另存为scanlan.py的版本#!/usr/bin/env python3 import ipaddress import subprocess import re from concurrent.futures import ThreadPoolExecutor from collections import OrderedDict try: from scapy.all import ARP, Ether, srp, conf except ImportError: conf None def arp_probe(ip, timeout2): 对单个IP发ARP请求返回 (IP, MAC) 或 None packet Ether(dstff:ff:ff:ff:ff:ff) / ARP(pdststr(ip)) ans, _ srp(packet, timeouttimeout, verbose0) if ans: recv ans[0][1] return (str(recv.psrc), recv.hwsrc.upper()) return None def system_arp_fallback(): 没有scapy时直接读系统ARP表兜底 try: out subprocess.check_output([ip, neigh], textTrue) except FileNotFoundError: out subprocess.check_output([arp, -a], textTrue) hosts OrderedDict() pattern re.compile(r(\d\.\d\.\d\.\d).*?([0-9a-fA-F]{2}(?:[:-][0-9a-fA-F]{2}){5})) for line in out.splitlines(): m pattern.search(line) if m: hosts[m.group(1)] m.group(2).upper() return hosts def main(network, threads32, timeout2): if conf is None: for ip, mac in system_arp_fallback().items(): print(f{ip}\t{mac}) return conf.verb 0 targets list(ipaddress.ip_network(network, strictFalse).hosts()) found {} with ThreadPoolExecutor(max_workersthreads) as pool: for item in pool.map(lambda ip: arp_probe(ip, timeout), targets): if item: ip, mac item found[ip] mac print(fFound {len(found)} hosts) for ip, mac in sorted(found.items(), keylambda x: ipaddress.ip_address(x[0])): print(f{ip}\t{mac}) if __name__ __main__: main(192.168.1.0/24, threads32, timeout2)参数说明targets用ip_network(...).hosts()生成它自动跳过网络地址和广播地址不浪费等待。threads32是并发数家用路由器实际扛得住在企业网络建议降到16避免被网关当作内网探测攻击触发IPS告警。timeout2是单个IP等待2秒配合32并发254个地址约10-20秒扫完。所有MAC统一转大写解决Windows“-”和Linux“:”格式互比不过去的老大难。scapy在多线程下共享同一个二层socket可能出现偶发丢包如果发现“总应答数忽多忽少、同一网段两次扫结果差异大”把threads降到8再试多数能稳定。提示在严格管理的办公网每天全量ARP扫描别超过两次频率太高容易被IPS识别成内网探测行为给自己惹来安全通告。4.4 把结果落成CSV资产台账的第一行终端里打印一遍只是热身真正要紧的是把数据变成“下一次还能用的文件”。在main()的for循环结束后追加import csv with open(lan_snapshot.csv, w, newline) as f: writer csv.writer(f) writer.writerow([ip, mac]) for ip, mac in sorted(found.items(), keylambda x: ipaddress.ip_address(x[0])): writer.writerow([ip, mac])CSV就是最简单的台账。把这个脚本配合crontab或Windows任务计划每天跑一次、文件名带日期存档就得到一张“时间—IP—MAC”流水账。后面做IP冲突排查、WOL远程开机Wake on LAN需要目标MAC才能发包、以及和路由器DHCP列表比对都是从这份CSV起步。在CSDN被问得最多的“怎么让脚本每天自动跑”本质就是给它配一个计划任务不是脚本本身的事。5. 避坑实录五次翻车与修复路径5.1 路由器开了AP隔离扫来扫去只有自己现象在访客网络下测试手机或电脑上扫整个网段结果只有自己这台机器在线连网关都时有时无。原因AP隔离/客户端隔离开启后无线客户端之间的二层帧被AP和接入交换机丢弃ARP广播和应答都在这里被拦了。你广播“谁在”别人收不到别人回复你也收不到——通信被单向静默。解决改用netdiscover -p被动模式监听几分钟利用他人之间的广播/组播通信拼出活跃主机根治则在路由器/AP后台关掉“客户端隔离”开关。遇到“扫出来只有自己”先别急着怀疑工具停下来查后台开关。5.2 DHCP列表是“租约”不是“在场名单”现象路由器后台DHCP客户端列表里躺着一台已关机三天的打印机和一台刚连上WiFi但列表里没有的新手机。原因DHCP租约记录了“这个MAC在某个时间被分配了某个IP”租约时间可以长达24小时甚至更久设备下线时不会主动通知路由器路由器也不会勤快探测租约是否还在用。解决把DHCP列表当“分配历史”把ARP扫描快照当“现场真值”。排IP冲突时优先看ARP快照里哪两个MAC在抢同一IP想清点某段“此刻在线”的设备也以扫描结果为准不要直接抄路由器页面。5.3 虚拟机和Docker容器制造一堆“假MAC”物理机清单被污染现象一个C段的扫描结果里三分之一是“VMware, Inc.”“Microsoft Hyper-V”看起来像整个办公室全是虚拟机根本找不到那台多媒体电脑。原因宿主机上的VMware/VirtualBox虚拟网卡会正常参与ARP应答Docker如果用了bridge网络模式容器也在这个广播域里回应。它们占用IP且响应ARP但都不是“人用的物理机”。解决用arp-scan输出的厂商OUI列先把虚拟化厂商过滤掉更彻底的办法是把虚拟机、Docker容器规划到独立网段和办公网物理隔离。扫描前的网络规划省下的时间远比扫描后清洗数据的时间多。5.4 以为扫描靠ping其实靠的是ARP于是得出错误结论现象Windows机器上自己写的脚本只有ICMP探测发现某台设备“明明在用却扫不到”换用nmap -sn却能看见于是怀疑nmap有“黑科技”。原因nmap在扫描同网段时默认用的是ARP请求几乎不依赖ICMP。而自己的脚本只发ping碰上Windows防火墙禁ICMP回显的设备就自然漏报。不是nmap弱是协议选对了。解决写扫描脚本把ARP当主探测ICMP只当辅助信息。要判断“该设备是不是活着”ARP应答已经足够对ICMP回显不应抱期待它可有可无。5.5 设备休眠唤醒后上一轮的清单全变了现象前一天晚上用脚本扫描存好CSV第二天早上醒来照着清单连远程桌面三台机器全部失败IP全变了。原因手机、平板这类移动设备休眠时会主动断开WiFi释放DHCP租约路由器正好把租约分给了其他设备唤醒后重新申请拿到一个新IP。笔记本合盖一夜也可能出现同类现象但概率低。解决把扫描脚本配成任务计划每天早高峰前和午休后各跑一次用4.4的CSV加日期做diffIP变化的设备一眼就能找出来。对需要长期稳定访问的设备打印机、NAS、监控在路由器DHCP里做静态绑定把MAC“钉”在固定IP上这才是真正的后悔药。6. 验证与进阶别让扫描结果躺在终端里6.1 十分钟验证手机断网重连看它有没有出现在清单上拿到一份扫描结果后第一件要做的不是急着抄进Excel而是验证“这份表可信”。最朴素的验证法拿一台手机连同一个WiFi关掉屏幕让它休眠一会儿再跑一次扫描如果手机不在结果里先别下结论因为移动设备休眠期间对ARP应答可能有策略性延迟。这时把手机亮屏解锁用“局域网传输文件”功能往电脑传一个文件——电脑必须和手机通信ARP缓存会被强制要求填充刷新扫描结果或直接查电脑ARP表手机马上现身。这个实验验证了一个原则扫描结果是时间点快照不是永久事实“当时不在线”和“当时在线但不应答”是两种状态排查时要会区分。补充一个交叉验证的技巧如果某台服务器IP开放了80或443端口可以用telnet ip port或nc试连通性来确认“这IP确实被某台活着的主机占用”。但记住这是验证工具不是主机发现工具——只有开放端口的服务才探得到对扫描清单里那些无监听端口的设备没什么用。6.2 从清单到动手IP冲突排查、WOL远程开机、资产台账扫出来的IP-MAC表最值钱的三个落地场景一是IP冲突排查当办公室有人手动把电脑IP改成和打印机共用地址时ARP快照里清楚写着两个MAC抢同一个IP对照路由器后台绑定表几分钟定位“谁在抢”。二是WOL远程开机——Wake on LAN需要目标机的MAC地址才能发送唤包没有清单就只能爬到路由器后台翻半天拿着ARP快照一条命令就把它叫醒。三是资产盘点把CSV按厂商、按工位慢慢补备注一个月下来就是公司网络的动态地图比任何“网络拓扑工具”都贴合现实。6.3 脚本还能怎么长增量diff与陌生MAC告警给脚本加diff是第一道进阶取昨天和今天两份CSV比对“新增、消失、IP变更”三种事件输出一个三行摘要。第二道是给MAC加OUI前缀分类把虚拟网卡、摄像头、手机厂商分成几个大类。第三道才是告警发现没记录过的陌生MAC就推一条通知到工作群。做到这里ScanLan从“工具”长成了“简易内网准入”虽然谈不上多强但足够让一个五十人的办公室不再为“谁在偷偷上网”吵架。最后说点个人的血泪经验我最早自己写扫描器用ping打底连续两次把“没回ICMP”误判成“设备关机”后来才明白要拿ARP定真值。现在无论手头有什么工具我坚持先确认“我要的是IP还是IPMAC”再决定用哪个协议层的探测每次跨网段扫之前先确认自己没扫错方向。养成这套习惯之后踩坑次数显著下降。希望帮到你。本文还有配套的精品资源点击获取