简介:这是一份面向网络管理初学者与运维人员的VC++局域网扫描工具源码,核心功能是快速发现同一网段内所有主机的IP与MAC地址,可用于网络设备盘点、IP冲突排查与未授权设备识别等场景。压缩包共15个文件,约13KB,以C++源码为主体,包含5个h头文件与4个cpp实现文件,另有rc、rc2资源脚本、ico图标、dsw与dsp工程文件及clw类向导文件,构成一套可直接用Visual C++打开编译的完整工程。据描述,该工具借助ARP或ICMP协议探测,约30秒即可扫完一个网段的255台机器,并可能通过进程间通信机制提升扫描效率。目前已有733人学习下载,适合想理解局域网二层通信原理、动手实践网络扫描编程的读者参考源码结构与实现思路。
1. ScanLan 扫局域网:为什么 ARP 表比 ping 扫段更靠谱
很多人第一次写局域网扫描,思路都很直接:拿到本机网段,比如 192.168.1.0/24,然后 for 循环 ping 一遍,能通的就记下来。跑完发现只扫到七八台,可路由器后台明明挂着二十多个设备。这不是代码写错了,是方向选偏了——ping 依赖 ICMP 回显,而大量设备(手机、打印机、杂牌摄像头、部分 IoT 模块)默认就把 ICMP 关了,你 ping 它,它装死。
ScanLan 这类工具要解决的核心问题,是在一个局域网里把「当前活着的设备」连同它们的 IP 和 MAC 地址一起捞出来。IP 告诉你它在网络层的身份,MAC 告诉你它在链路层的物理身份,两者配对之后,你才能做后续的事:ip冲突排查、找不到对方设备时定位、局域网 ip 查询、识别杂牌摄像头、给静态绑定做台账。适合谁?运维、做局域网联机调试的开发者、家里设备多到记不清的人,以及需要给设备做资产登记的安全从业者。
关键认知只有一句:同一广播域内,想知道谁在线,问 ARP 比问 ICMP 有效得多。因为只要设备接在同一个二层网络里、协议栈正常,它就会响应 ARP 请求,哪怕它屏蔽了 ping。这就是 ScanLan 这类扫描器能比 ping 扫段多扫出一倍设备的原因。
2. ScanLan 的底层逻辑:ARP、ICMP 与网段边界
2.1 为什么 ARP 能扫到 ping 扫不到的机器
ARP(地址解析协议)的工作是:我知道一个 IP,但不知道对应 MAC,于是向广播域发一个 ARP Request「谁是这个 IP,请把 MAC 告诉我」,持有该 IP 的设备回一个 ARP Reply。这个过程发生在链路层,绝大多数设备的协议栈都会无条件响应,因为它不响应就没法正常通信。
ping 走的是 ICMP,属于网络层,是否回应由操作系统策略决定。Windows 防火墙默认就拦入站 ICMP 回显,很多 Linux 发行版也默认关闭。所以你会看到「能上网、能共享文件,但 ping 不通」的设备,这不是故障,是策略。
提示:ARP 只能在同一广播域(同一 VLAN、同一网段)内工作。跨网段扫描必须靠路由或 SNMP,这是 ScanLan 的天然边界,别指望它扫穿三层。
2.2 主动扫描和被动监听的区别
主动扫描:自己构造 ARP Request 发出去,收 Reply,速度快、可控,ScanLan 默认走这条路。被动监听:把网卡设为混杂模式,只听不主动发,靠抓别人通信时的 ARP 广播来建表。被动方式隐蔽、不打扰网络,但慢,且设备不说话就抓不到。
实操里我一般用主动扫描做全量发现,用被动监听做补充验证。两者结合,漏报率能压到很低。
2.3 网段边界怎么确定
扫描前必须先算清楚自己所在的网段和掩码。常见误区是直接假设 /24,但企业网里 /22、/23 很常见,你按 /24 扫就漏掉一大半。用下面这段 Python 拿到本机网段:
import ipaddress import socket import fcntl import struct def get_ip_mask(ifname="eth0"): """通过 ioctl 获取指定网卡的 IP 和掩码,Linux 环境""" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 0x8915 = SIOCGIFADDR,取 IP ip = socket.inet_ntoa(fcntl.ioctl( s.fileno(), 0x8915, struct.pack('256s', ifname[:15].encode()))[20:24]) # 0x891b = SIOCGIFNETMASK,取掩码 mask = socket.inet_ntoa(fcntl.ioctl( s.fileno(), 0x891b, struct.pack('256s', ifname[:15].encode()))[20:24]) return ip, mask ip, mask = get_ip_mask("eth0") net = ipaddress.IPv4Network(f"{ip}/{mask}", strict=False) print(f"本机 {ip},网段 {net},可用主机数 {net.num_addresses - 2}")逻辑说明:SIOCGIFADDR和SIOCGIFNETMASK是两个标准 ioctl 请求码,分别取网卡 IP 和子网掩码。拿到后交给ipaddress模块算出网络号。参数上,ifname要换成你实际的网卡名,Windows 上得用socket.gethostbyname配合netifaces库,ioctl 那套不通用。
算出网段后,主机地址范围就是net.hosts(),遍历它逐个发 ARP 请求即可。注意 /16 这种大网段有六万多地址,全量扫会拖很久,实际要分片或加超时控制。
3. 用 Python 手搓一个 ScanLan:从发包到出表
3.1 环境准备与依赖选择
自己实现 ARP 扫描,核心依赖是scapy,它能让你在用户态直接构造和发送二层帧。安装:
pip install scapyLinux 下需要 root 或CAP_NET_RAW权限,因为要操作原始套接字。Windows 上要装 Npcap,并把它的驱动选上。这一步翻车的人不少,报PermissionError基本都是权限或驱动没到位。
3.2 构造并发送 ARP 请求
from scapy.all import ARP, Ether, srp def arp_scan(network): """对指定网段发 ARP 请求,返回 IP-MAC 列表""" # Ether 的 dst 设为广播地址,ARP 的 pdst 设为目标网段 pkt = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=network) # srp 在二层收发,timeout 控制等待,retry 控制重试 answered, _ = srp(pkt, timeout=2, retry=1, verbose=False) result = [] for sent, recv in answered: result.append({"ip": recv.psrc, "mac": recv.hwsrc}) return result for item in arp_scan("192.168.1.0/24"): print(f"{item['ip']:<16} {item['mac']}")逻辑说明:Ether(dst="ff:ff:ff:ff:ff:ff")把帧发往广播地址,保证同网段所有设备都能收到。ARP(pdst=network)里pdst是目标协议地址,填网段时 scapy 会自动展开成逐个地址发。srp是二层收发函数,返回「已应答」和「未应答」两组。
参数说明:timeout=2是每轮等待秒数,局域网内 1~2 秒足够;retry=1表示超时后再补发一次,能显著降低丢包导致的漏报;verbose=False关掉 scapy 的刷屏输出。网段越大,timeout要适当放大,否则尾部地址还没回包就结束了。
3.3 结果去重、排序与导出
扫出来的结果可能有重复(重试导致),也可能顺序乱。整理一下:
import csv def dedup_and_export(records, path="scan_result.csv"): seen = {} for r in records: seen[r["ip"]] = r["mac"] # 同 IP 以最后一次为准 rows = sorted(seen.items(), key=lambda x: tuple(map(int, x[0].split(".")))) with open(path, "w", newline="") as f: w = csv.writer(f) w.writerow(["IP", "MAC"]) w.writerows(rows) return rows逻辑说明:用字典按 IP 去重,天然覆盖重复项。排序时把 IP 按点分段转成整数元组,避免字符串排序出现192.168.1.100排在192.168.1.2前面的问题。导出 CSV 方便后续做资产台账或和路由器 DHCP 列表比对。
参数说明:path是输出路径,newline=""是 Windows 下防止 CSV 多空行的标准写法。如果要做 ip冲突排查,把这份结果和路由器后台的绑定表对照,同一 MAC 出现两个 IP、或同一 IP 出现两个 MAC,就是冲突信号。
3.4 厂商识别:从 MAC 前缀看设备是谁
MAC 地址前三个字节是 OUI,能查出厂商。这一步对识别杂牌摄像头、判断设备类型很有用:
# OUI 库可用 pip install mac-vendor-lookup from mac_vendor_lookup import MacLookup lookup = MacLookup() # 首次使用需更新数据库 # lookup.update_vendors() def vendor_of(mac): try: return lookup.lookup(mac) except Exception: return "未知厂商" print(vendor_of("b8:27:eb:11:22:33")) # 树莓派基金会逻辑说明:MacLookup内部维护 OUI 到厂商的映射表,lookup传入完整 MAC 返回厂商名。首次使用要update_vendors()拉一次数据库,之后可离线用。
参数说明:MAC 格式要统一,大小写和分隔符不一致会查不到,建议先mac.lower().replace("-", ":")归一化。查不到不代表设备有问题,很多白牌设备用的是未注册或复用的 OUI。
4. 避坑与排查:ScanLan 扫不全、扫不准的 5 个真实原因
4.1 现象:只扫到网关和本机,其他全无
原因:网卡选错了。多网卡机器(有线+无线+虚拟网卡)上,scapy 默认可能从错误的接口发包,包发到了没有目标设备的网络里。
解决:显式指定接口。srp(pkt, iface="eth0", timeout=2),接口名用ip link或ifconfig确认。虚拟机里尤其要注意,NAT 模式和桥接模式扫出来的结果完全不同,桥接才能看到物理网段。
4.2 现象:能扫到 IP,但 MAC 全是同一个
原因:跨了三层,或者中间有代理 ARP。典型场景是你在 NAT 网络里扫外部网段,回包的都是网关的 MAC。
解决:确认自己在同一广播域。如果必须跨网段,改用 SNMP 查路由器的 ARP 表,或者登录交换机看 MAC 地址表,别硬用 ARP 扫。
4.3 现象:结果时多时少,不稳定
原因:timeout太短,或者网段太大导致尾部地址没等到回包。无线网络下丢包更明显。
解决:把timeout提到 3~5 秒,retry加到 2。大网段分片扫,比如 /16 拆成 256 个 /24 逐个来。无线环境建议多扫几轮取并集。
4.4 现象:扫到了但设备名、厂商全是未知
原因:OUI 数据库没更新,或者设备用了随机 MAC。手机和部分 IoT 设备为了隐私会启用 MAC 随机化,每次连接都换。
解决:更新 OUI 库;对随机 MAC 设备,靠 IP、开放端口、DHCP 主机名综合判断,别只依赖 MAC。这也是为什么资产台账要 IP、MAC、主机名三列一起记。
4.5 现象:扫描把网络搞卡了
原因:发包速率太高,广播风暴。在设备多、交换机性能差的网络里,全速 ARP 扫描会明显影响通信。
解决:加发包间隔。scapy 的srp支持inter参数控制包间隔,inter=0.01表示每包间隔 10 毫秒。生产网络里宁可慢一点,也别把业务网扫挂。
5. 进阶:把 ScanLan 做成能长期用的资产发现习惯
单次扫描只是快照,真正有价值的是把它变成周期性动作,配合对比找出「新出现的设备」和「消失的设备」。我一般会写一个对比脚本,把本次结果和上次结果做差集:
def diff_scan(old_rows, new_rows): old = {ip: mac for ip, mac in old_rows} new = {ip: mac for ip, mac in new_rows} added = [(ip, new[ip]) for ip in new if ip not in old] removed = [(ip, old[ip]) for ip in old if ip not in new] changed = [(ip, old[ip], new[ip]) for ip in new if ip in old and old[ip] != new[ip]] return added, removed, changed逻辑说明:三个差集分别对应「新上线」「已下线」「IP 没变但 MAC 变了」。第三种最值得警惕,可能是 IP 被重新分配,也可能是有人手动改了配置,做 ip冲突排查时这是第一手线索。
参数说明:old_rows和new_rows都是(ip, mac)元组列表,直接来自前面的扫描结果。实际用的时候把每次结果按日期存 CSV,对比时读最近两次即可。
再往上走一层,可以把扫描结果和路由器的 DHCP 租约表、交换机的 MAC 地址表做三方比对。三者一致,说明台账干净;不一致的地方,就是你要去现场确认的点。我踩过最深的坑,是曾经完全信任扫描结果,结果一台设备因为 MAC 随机化,每次扫描都显示成新设备,台账里堆了几十条重复记录。后来加了「按主机名+开放端口聚类」的逻辑才压下去。
验证扫描是否可信,有个简单办法:拿一台你知道 MAC 的设备,手动在路由器上做静态绑定,然后扫描,看结果里的 MAC 和绑定的是否一致。一致,说明链路层抓取没问题;不一致,先查接口和网段,别急着怀疑代码。
这套东西的价值不在于扫一次,而在于把它变成习惯——每周跑一次,存好历史,设备一有异动你第一时间就知道。我自己是把它挂在树莓派上定时跑,结果推到本地表格里,省了太多「找不到对方设备」的来回折腾。希望帮到你。
本文还有配套的精品资源,点击获取