C# SharpPcap与PacketDotNet局域网ARP抓包及IP冲突检测
简介:这份压缩包面向局域网运维人员、网络安全的入门开发者以及需要排查 IP 冲突的 C# 学习者,核心是用 SharpPcap 抓取 ARP 报文来侦测在线设备并汇总冲突设备列表。代码基于 VS2015 与 .NET 4.5.2 编写,将抓包、报文解析与冲突判断封装为可复用类,并提供调用示例,便于在现有管理工具或监测程序中快速接入,理解 ARP 请求与应答的识别逻辑及局域网设备发现思路。压缩包共 36 个文件,约 1002KB,包含 11 个 cs 源码、2 个 dll 依赖、4 个 exe 及 config、resx、csproj、sln 等工程与配置文件,完整解决方案便于对照编译和调试。已有 560 人学习,适合作为局域网设备巡检、IP 冲突定位和 C# 网络抓包实践的可运行参考。
1. 局域网里谁在抢 IP:从抓一帧 ARP 开始
机房角落那台打印机三天两头从网络上消失,交换机面板上看不出任何异常,十有八九是 IP 被人抢了。这类故障用ping查不出来:冲突的另一台设备很多时候根本不回 ICMP,只有蹲在二层看 ARP 报文才能抓到证据。C# 里做这件事最省事的组合是 SharpPcap 加 PacketDotNet,前者把网卡切进混杂模式收帧,后者把0x0806的载荷解成可读字段。这份资源就是围绕这个组合写的一整套东西:被动监听局域网 ARP 流量、主动构造请求扫网段、把 IP 和 MAC 加时间戳攒成设备清单,再把同一 IP 下挂着多个 MAC 的记录单独拎出来列为冲突设备。适合天天跟内网打交道的运维、做桌面资产盘点的开发者,以及对二层协议只在课本上见过、想动手写一遍的 .NET 同学。
2. 选型与依赖:为什么 ARP 比 ICMP 轮询更适合查冲突
扫内网设备,很多人的第一反应是写个for循环把整个网段 ping 一遍。这个思路在真实办公网里失败率极高:Windows 默认防火墙就拦 ICMP,一堆打印机、摄像头、瘦客户机根本不回;更关键的是,ping通了也只能说明"这个 IP 上有个东西活着",拿不到 MAC,就没法判断是不是两台设备在抢同一个地址。ARP 不一样,只要设备在同一个广播域里想通信,它就必须应答 ARP 请求;哪怕它不主动说话,你发一个 Request 过去,它也得回。二层身份信息是顺带就拿到的,这才是查冲突的正确切入点。
2.1 一帧 ARP 里哪些字段真正有用
ARP 帧在以太网上的结构非常固定,去掉 14 字节以太头之后是 28 字节的 ARP 载荷。字段偏移和用途如下,写解析代码时对着这张表看,比翻 RFC 快得多。
| 字段 | 偏移(含以太头) | 长度 | 在冲突检测里的用途 |
|---|---|---|---|
| 目的 MAC | 0 | 6 | 全 FF 是广播,判断这条是不是广播请求 |
| 源 MAC | 6 | 6 | 二层身份的直接来源,可与 ARP 载荷互校 |
| 以太类型 | 12 | 2 | 只有0x0806才是 ARP |
| 硬件类型 | 14 | 2 | 以太网固定为 1 |
| 协议类型 | 16 | 2 | IPv4 为0x0800 |
| 硬件地址长度 | 18 | 1 | 6 |
| 协议地址长度 | 19 | 1 | 4 |
| 操作码 | 20 | 2 | 1 是请求,2 是应答 |
| 发送方 MAC | 22 | 6 | 设备台账的主键 |
| 发送方 IP | 28 | 4 | 与主键配对的地址 |
| 目标 MAC | 32 | 6 | 请求里通常全 0 |
| 目标 IP | 38 | 4 | 你要探测的那个地址 |
真正决定判定逻辑的是操作码和发送方、目标地址的组合关系。请求且发送方 IP 等于目标 IP,这是 ARP Probe,设备在上线前用它探测地址有没有被别人占用;应答且发送方 IP 等于目标 IP,这是免费 ARP(Gratuitous ARP),设备在广播"这个地址归我了"。这两种报文出现的时机和频率,是后面做冲突打分的核心输入。普通请求是"我在找谁",普通应答是"我就是谁",前者广播后者单播,这个差异直接决定了被动监听能看到多少东西。
2.2 SharpPcap 与 Npcap:依赖链别装反
SharpPcap 本身只是 libpcap 的 .NET 封装,Windows 上它需要底层的抓包驱动。常见做法是先装 Npcap,安装向导里务必勾上兼容模式那一项,否则老的 API 调用会直接返回空设备列表且不报错。NuGet 上装两个包:抓包主包 SharpPcap,解析包 PacketDotNet,两者版本要配套,混用相差太远的老版本会在Packet.ParsePacket处抛找不到方法的异常。macOS 和 Linux 上不需要额外驱动,但要把运行用户加进能访问抓包设备的组,否则Open会抛权限异常。
还有一个容易被忽略的前提:程序必须以管理员权限运行。用管理员权限运行不只是为了抓包,虚拟机里跑的时候还要确认网卡是不是被 host 抢走了。我一般在app.manifest里直接写死requireAdministrator,省得每次右键。
2.3 最小可运行版本:打开网卡、挂上 arp 过滤器
先把"能收到包"这件事跑通,再谈业务逻辑。下面这段是整套工具的骨架,二十几行,但参数一个都不能乱填。
using SharpPcap; using SharpPcap.LibPcap; using PacketDotNet; // 1. 列出本机所有可抓包网卡,名字和描述都打出来,出错时靠这个定位 foreach (var dev in CaptureDeviceList.Instance) { Console.WriteLine($"{dev.Name}\t{dev.Description}"); } // 2. 选中要监听的网卡;实际项目里按描述含关键字或按本机 IP 段匹配来挑 if (CaptureDeviceList.Instance.Count == 0) throw new InvalidOperationException("设备列表为空,先确认 Npcap 已安装且勾了兼容模式"); var device = CaptureDeviceList.Instance[0] as LibPcapLiveDevice ?? throw new InvalidOperationException("网卡类型不匹配"); // 3. 混杂模式 + 1 秒读超时;超时太短 CPU 空转,太长会导致停止捕获时卡住 device.Open(new DeviceConfiguration { Mode = DeviceModes.Promiscuous, ReadTimeout = 1000, Snaplen = 65535 }); // 4. 过滤器下沉到内核层,只把 ARP 帧送上来,避免全量流量把用户态打满 device.Filter = "arp"; device.OnPacketArrival += (s, e) => { var raw = e.GetPacket(); var packet = Packet.ParsePacket(raw.LinkLayerType, raw.Data); var arp = packet.Extract<ArpPacket>(); if (arp == null) return; // 极少数带 VLAN tag 的帧需要先剥 802.1Q 再解 Console.WriteLine($"{arp.Operation}\t{arp.SenderProtocolAddress}\t" + $"{arp.SenderHardwareAddress}\t{arp.TargetProtocolAddress}"); }; device.StartCapture(); Console.ReadLine(); // 真实项目里换成 CancellationToken device.StopCapture(); device.Close();Snaplen设 65535 是保守做法,ARP 帧本身不到 64 字节,但带 VLAN tag 或者被交换机加了额外头的帧会被截断,截断后ParsePacket解析出来的字段就是垃圾。DeviceModes.Promiscuous这一项在交换网络里其实收益有限,原因在下一章展开。回调函数跑在 libpcap 的捕获线程上,里面千万不要做磁盘写入或者网络请求,否则丢包率会肉眼可见地上涨。
3. 设备列表怎么攒:被动监听加主动探测的两条腿
只靠被动监听,设备表永远是残缺的。原因是交换机的转发行为:一个接入口只会收到广播帧、组播帧,以及目的 MAC 是自己的单播帧。ARP 请求是广播的,所以你能看到"谁在问谁";ARP 应答是单播的,交换机不会转给你,所以你看不到"谁回答了谁"。结果就是静默设备完全缺席,只有那些主动发起过通信的机器才会出现在表里。补齐的办法是主动探测,两条腿一起走,覆盖率才上得去。
3.1 被动模式:把广播流量榨干
被动监听的价值在于"零打扰",不用往网络里注入任何东西,适合长期挂在镜像口上做资产台账。要榨干它的信息量,解析时不要只看发送方,目标方也要看:一条广播 ARP 请求虽然只带了发送方的 MAC 和 IP,但目标 IP 告诉你"这个地址正在被寻找",如果同一个目标 IP 在短时间里被不同源问了很多次,本身就是一个值得留意的信号,常见于网关地址被反复探测。
落台账时用 MAC 做主键,IP 作为集合挂上去。这个设计选择很关键,用 IP 做主键的话,冲突场景里后来的 MAC 会直接覆盖前面的,恰好把你要找的信息弄丢。
// 一条设备记录:MAC 是身份,IP 是它声明过或使用过的地址集合 public sealed class DeviceEntry { public string Mac { get; init; } = ""; public ConcurrentDictionary<string, IpRecord> Ips { get; } = new(); public DateTime FirstSeen { get; init; } public DateTime LastSeen { get; set; } public string Vendor { get; set; } = "Unknown"; public bool IsVirtual { get; set; } // 虚拟网卡标记,不参与冲突判定 } public sealed class IpRecord { public string Ip { get; init; } = ""; public DateTime FirstSeen { get; init; } public DateTime LastSeen { get; set; } public int Count { get; set; } } // 台账容器,用并发字典是因为回调线程和主动扫描线程会同时写 private readonly ConcurrentDictionary<string, DeviceEntry> _devices = new(StringComparer.OrdinalIgnoreCase); private void Record(string mac, string ip, DateTime ts) { var entry = _devices.GetOrAdd(mac, m => new DeviceEntry { Mac = m, FirstSeen = ts }); entry.LastSeen = ts; entry.Ips.AddOrUpdate(ip, _ => new IpRecord { Ip = ip, FirstSeen = ts, LastSeen = ts, Count = 1 }, (_, old) => { old.LastSeen = ts; old.Count++; return old; }); }Vendor字段靠 MAC 前三字节查 OUI 表,常见做法是提前下载一份 IEEE 的 oui.csv 放本地,运行时只读内存。不要在主循环里联网查,除了慢,内网环境根本出不去。IsVirtual这个标记后面在避坑一章会用到,Hyper-V 和 VMware 的虚拟适配器会把台账搅得很难看。
3.2 主动模式:手搓 ARP Request 扫整个 /24
主动探测的收益远高于 ping 扫描,因为主机防火墙基本不管 ARP,回包率能到九成以上。代价是你要自己拼帧。SharpPcap 提供了SendPacket,但它接收的是原始字节,所以以太头和 ARP 载荷都得手工填。
// 构造 42 字节 ARP 请求帧:14 字节以太头 + 28 字节 ARP 载荷 static byte[] BuildArpRequest(PhysicalAddress localMac, IPAddress localIp, IPAddress targetIp) { var frame = new byte[42]; // 以太头:目的 MAC 全 FF 表示广播,源 MAC 填本机 for (int i = 0; i < 6; i++) frame[i] = 0xFF; var mac = localMac.GetAddressBytes(); Array.Copy(mac, 0, frame, 6, 6); frame[12] = 0x08; frame[13] = 0x06; // EtherType = ARP // ARP 载荷 frame[14] = 0x00; frame[15] = 0x01; // 硬件类型:以太网 frame[16] = 0x08; frame[17] = 0x00; // 协议类型:IPv4 frame[18] = 6; // 硬件地址长度 frame[19] = 4; // 协议地址长度 frame[20] = 0x00; frame[21] = 0x01; // 操作码 1 = 请求 Array.Copy(mac, 0, frame, 22, 6); // 发送方 MAC Array.Copy(localIp.GetAddressBytes(), 0, frame, 28, 4); // 目标 MAC 保持全 0(未知),目标 IP 填要探测的地址 Array.Copy(targetIp.GetAddressBytes(), 0, frame, 38, 4); return frame; }发送侧按网段循环。间隔别贪快,5 毫秒一条对 254 个地址来说是 1.3 秒一轮,这个节奏低端交换机和老旧打印机都扛得住;调到 1 毫秒以内,廉价交换机的 ARP 缓存会溢出,丢包率反而上升,这是典型的欲速不达。
// 假设网卡实际地址是 192.168.1.10/24 var localIp = IPAddress.Parse("192.168.1.10"); var localMac = device.MacAddress; for (int host = 1; host <= 254; host++) { var target = IPAddress.Parse($"192.168.1.{host}"); if (target.Equals(localIp)) continue; // 跳过自己 var frame = BuildArpRequest(localMac, localIp, target); device.SendPacket(frame); Thread.Sleep(5); }localIp必须和网卡上真实配置的地址一致,否则系统协议栈和抓包结果会对不上,偶尔还会看到系统自己回一个不可达。网段掩码按实际的填,别硬编码/24,遇到/22的超网就得把循环范围算出来。SendPacket是同步调用,失败会抛PcapException,批量扫描时用try/catch包住单条发送,不要让一台设备的异常中断整轮扫描。
3.3 两条腿合并:谁先到谁写,冲突留给后面判
被动和主动产生的观测走同一个入口Record(mac, ip, ts),天然就合并了。主动扫描的好处是"确定性"——你发了 254 个请求,正常情况下就该收到 200 个以上的应答;收到的比例可以作为这一轮扫描的健康度指标,低于七成说明网卡选错了、网段填错了或者链路有环路。被动的好处是"持续性"——它能看到主动扫描间隙里发生的事,比如某台设备刚上线发的免费 ARP。
两路数据合并之后,设备表里的每一条记录都带着首次出现时间、最后出现时间和观测次数。这三个字段在做资产盘点时比单纯的 IP-MAC 列表有用得多,能区分出"一直在线的核心设备"和"偶尔冒个头的访客设备"。
4. 冲突判定:从重复应答里拎出同 IP 多 MAC 的那一对
设备表建好之后,冲突检测其实就是一个反查:对每个 IP,看它在某个时间窗口内被多少个不同的 MAC 声明过。听起来简单,真做起来误报会多到让人放弃,因为 DHCP 换租约、笔记本换网卡、虚拟网卡的 MAC 漂移,全都会命中这个条件。得给不同的观测来源加权,把硬证据和弱信号分开。
4.1 三个信号,权重完全不一样
| 信号 | 触发条件 | 置信度 | 处置建议 |
|---|---|---|---|
| 同 IP 多 MAC | 时间窗口内同一 IP 出现 ≥2 个 MAC | 高 | 直接记入冲突列表并告警 |
| 同 MAC 多 IP | 同一 MAC 短时间声明多个 IP | 低 | 只记录,多为别名地址或虚拟网卡 |
| 免费 ARP 密集 | 同一 IP 一分钟内多次 Gratuitous ARP | 中高 | 结合 MAC 差异判断,通常意味着抢占 |
第一个信号是硬证据,前提是时间窗口要合理。窗口设得太小,两次 ARP 之间隔了几分钟就漏判;设得太大,DHCP 换租约的会被算进来。我一般设 300 秒,冲突双方在抢地址的时候 ARP 频率是很高的,几十秒内必定有多次交互,5 分钟的窗口足够抓现行,又不至于把上午和下午的两台设备混为一谈。
第二个信号单独看几乎没用,但配合第一个信号能帮你排除误报:如果一台机器声明了多个 IP,而每个 IP 都只有它一个 MAC,那基本是虚拟网卡或者多网卡,不是冲突。
第三个信号往往是冲突的"前兆"。设备插上网线、网卡驱动重载、系统从休眠恢复,都会发免费 ARP 或者 ARP Probe。正常设备发一轮就安静了;两个设备在抢同一个地址时,会反复互相覆盖,形成密集的免费 ARP 流量。这个特征在抓包文件里非常显眼,用 Wireshark 打开一眼就能看到。
4.2 冲突记录去重:A 撞 B 和 B 撞 A 是同一件事
冲突记录如果用List硬扫,很快就会重复:第一次看到 A 先声明,记一条 (A, B);第二次 B 先声明,又记一条 (B, A)。判定逻辑本身没错,输出就变得没法看。
去重的关键是把 MAC 集合排序后再拼 key,顺序就被抹平了。
public sealed class ConflictRecord { public string Ip { get; init; } = ""; public string[] Macs { get; init; } = Array.Empty<string>(); public DateTime FirstSeen { get; init; } public DateTime LastSeen { get; set; } public int Hits { get; set; } public int Score { get; set; } // 置信度,低于阈值不输出 public bool HasGratuitous { get; set; } } // key 里 MAC 排序,保证 (A,B) 和 (B,A) 落到同一条记录上 static string ConflictKey(string ip, IEnumerable<string> macs) => ip + "|" + string.Join(",", macs.Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(m => m, StringComparer.OrdinalIgnoreCase)); private readonly ConcurrentDictionary<string, ConflictRecord> _conflicts = new();用Distinct是因为反查时当前 MAC 会被加进候选集合,如果集合里本来就有它,重复加入会让 key 变形,去重就失效了。这个坑我在第一版里踩过,表现是同一个冲突对在列表里出现了三次,查了半天才发现是 key 拼接的问题。
4.3 打分与输出:控制台看趋势,文件留给下游
打分规则简单粗暴但有效:每命中一次冲突加 10 分,如果这次观测是免费 ARP 或者 ARP Probe,额外再加 10 分。阈值定在 30,也就是同一对冲突至少要被观测到 2 到 3 次才输出,单次偶发不算。
public void Observe(string ip, string mac, DateTime ts, bool isGratuitous) { // 先更新设备台账(略),再反查这个 IP 在窗口内的其他声明者 var rivals = _devices.Values .Where(d => !d.IsVirtual && d.Ips.TryGetValue(ip, out var r) && (ts - r.LastSeen).TotalSeconds < WindowSeconds) .Select(d => d.Mac) .Where(m => !m.Equals(mac, StringComparison.OrdinalIgnoreCase)) .ToList(); if (rivals.Count == 0) return; rivals.Add(mac); var key = ConflictKey(ip, rivals); var record = _conflicts.GetOrAdd(key, _ => new ConflictRecord { Ip = ip, Macs = rivals.OrderBy(m => m, StringComparer.OrdinalIgnoreCase).ToArray(), FirstSeen = ts }); record.LastSeen = ts; record.Hits++; record.Score += isGratuitous ? 20 : 10; record.HasGratuitous |= isGratuitous; }输出用两种格式:控制台只打Score >= 30的记录,按分数倒序,方便当场判断;落盘同时写 JSON 和 CSV,JSON 给下游系统消费,CSV 给不写代码的同事用 Excel 看。写 CSV 时 MAC 别用冒号分隔,Excel 会把它当时间格式吃掉,换成短横线最省事。
// JSON 落盘:UnsafeRelaxedJsonEscaping 避免中文和 IP 里的字符被转义成 \uXXXX var json = JsonSerializer.Serialize( _conflicts.Values.Where(c => c.Score >= 30).OrderByDescending(c => c.Score), new JsonSerializerOptions { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); File.WriteAllText($"arp-conflicts-{DateTime.Now:yyyyMMdd-HHmmss}.json", json);5. 避坑排查:抓不到包、看错网卡、把 DHCP 当冲突
这一章是整套工具里最花时间的部分。原理看一遍就懂,代码写两天也能跑,真正让人卡住的是环境差异和误判。下面五条是我在不同内网里反复遇到的,按"现象 → 原因 → 解决"写,照着排查能省掉大量试错。
5.1 抓包侧:设备列表是空的,或者只有自己
坑一:CaptureDeviceList.Instance返回 0 个设备,还不报错。
现象:程序启动后设备列表为空,没有任何异常抛出,foreach直接跳过。
原因:Npcap 没装,或者装的时候没勾兼容模式那一项;也可能是当前进程不是管理员,底层枚举直接被拒。
解决:重装 Npcap 并勾上兼容模式,app.manifest里把权限级别设成requireAdministrator。排查时先把CaptureDeviceList.Instance.Count打出来,再打印 Npcap 的安装路径,两者对照就能定位。
坑二:能抓到包,但设备表里只有自己和一个网关。
现象:挂机半小时,台账里寥寥几条记录,全是本机发出的请求。
原因:程序跑在接入交换机的一个普通端口上。混杂模式在交换网络里没有想象中那么神,交换机只往这个端口转发目的 MAC 是你的帧加上广播帧。ARP 请求是广播所以能看到,ARP 应答是单播所以看不到,于是只看到"谁在问",看不到"谁在答"。
解决:把程序挪到能接镜像口的机器上,或者在交换机上配端口镜像把上行口复制过来;没有镜像条件时,用主动探测补齐应答,覆盖率能拉到八成以上。这一条是最容易被误判成"代码有 bug"的,实际上是网络拓扑决定的。
坑三:加了Filter = "arp"之后回调一次都不进。
现象:不加过滤器能收到包,加上之后回调静默。
原因:一是网卡选错,选到了虚拟网卡或者已断开的适配器;二是部分环境下过滤器表达式编译失败但异常被上层吞掉。
解决:先不加过滤器跑通最小闭环,确认能收到包之后再逐个加条件;Filter赋值放在Open之后、StartCapture之前;给赋值语句套try/catch把PcapException的 Message 打出来,表达式错在哪会写得很清楚。
5.2 判定侧:把 DHCP 换租约和虚拟网卡当成冲突
坑四:每天早上九点准时报一批冲突。
现象:冲突列表里同一批 IP 每天固定时间冒出来两个 MAC,过一会儿又自己消失。
原因:办公区笔记本集中开机,DHCP 租约到期续不上,重新申请时拿到的地址自然换了 MAC;另一种情况是员工把笔记本插到闲置工位,机器的静态 IP 正好和别人的 DHCP 地址撞了。
解决:给观测加存活期,超过 24 小时没再出现的 MAC 从候选集合里剔除;同时把 Score 阈值提到 30 以上,只在多次命中时才输出。真正在抢地址的两个设备会持续刷 ARP,不会只出现一次就消失。
坑五:一台物理机冒出五六个 MAC,全绑在同一个 IP 上。
现象:冲突列表里出现"严重冲突",点进去一看全是同一台机器。
原因:Hyper-V、VMware、容器平台的虚拟适配器会共用或伪造 MAC,还有 WSL 的虚拟交换机适配器,都会往 ARP 流量里掺沙子。
解决:维护一份虚拟 OUI 前缀黑名单(VMware 的00:50:56、00:0C:29、00:05:69,Hyper-V 的00:15:5D是常见几个),命中就把IsVirtual置位,只记录不参与判定;另外检查 MAC 第一个字节的0x02位,这一位是本地管理地址标志,虚拟适配器大部分会置位。加上这两道过滤之后,误报率能降一个数量级。
6. 验证与进阶:用两台机器手工撞一次 IP
判定逻辑写完,别急着上生产。最有效的验证方式是人为制造一次冲突:找两台机器 A 和 B,先把 B 正常上网,然后在 A 上把静态 IP 手工设成 B 的地址,再在 A 上ping一次网关。这个动作会让 A 发出 ARP Probe 和免费 ARP,B 也会回应,两边开始互相覆盖。如果你的程序在 10 秒内没有报出这一对,说明窗口或者打分逻辑有问题,别怀疑网络。
| 验证场景 | 预期输出 | 常见偏差 |
|---|---|---|
| 手工设置相同静态 IP | 10 秒内出现该 IP 的冲突记录,Score > 40 | 窗口设成 60 秒以内才能这么快命中 |
| 只改一台机器的网卡 | 台账里多一个 MAC,不产生冲突 | 若误报,检查窗口是否过大 |
| 插拔网线触发免费 ARP | Hits 增加,Score 上升但不越阈值 | 阈值过低会把正常上线当成冲突 |
| 虚拟网卡环境 | 不产生冲突记录 | 未过滤 OUI 时会大量误报 |
交叉验证还有一招:抓包结果和系统自带 ARP 缓存对一遍,差异项就是重点排查对象。系统缓存里有的、你的台账里没有,说明被动监听漏了单播;台账里有、系统缓存里没有,多半是历史记录没清理。
// 快速回归:调试期把窗口压到 10 秒,手工撞一次 IP 就能立刻看到判定结果 private int WindowSeconds = 10; // 生产环境改回 300 private int ScoreThreshold = 30; // 调试期可降到 10,观察命中过程进阶用法是把这套东西从"一次性排查工具"改成"长期台账":进程常驻,每小时落一次全量快照,按天归档,配合简单的差异对比就能做出资产变更记录——哪台设备换了 IP、哪个地址被两台机器轮流使用、哪个 MAC 消失了三天又回来了,都能查出来。落盘频率别太高,ARP 流量在大型办公网里不小,频繁 IO 会影响抓包线程。
从项目经验看,这套工具真正的价值不在于"发现冲突"这个动作,而在于把二层的 IP-MAC 关系沉淀成可回溯的数据。从那以后我每次改判定逻辑,都会先把窗口调到 10 秒、拿两台机器手工撞一次 IP,确认能在 10 秒内报出来,再调回 300 秒上生产,这个习惯帮我挡掉了好几次"改一行代码引入一堆误报"的翻车。希望帮到你。
本文还有配套的精品资源,点击获取