资讯详情

C#与SharpPcap实战:局域网ARP抓包监控与IP冲突检测

📅 2026/10/16 1:42:11 | 华诺云谱 👁 阅读
C#与SharpPcap实战:局域网ARP抓包监控与IP冲突检测
简介这是一份面向C#网络编程开发者与运维人员的实用工具类资源聚焦局域网设备发现与IP冲突排查场景。它通过抓取ARP数据包的方式实时侦测网络中的在线设备并整理出存在IP地址冲突的设备列表适合需要做网络监控、故障定位或安全审计的中级开发者参考。资源包共36个文件约1002KB以cs源码文件为主辅以exe可执行程序、config配置、dll动态库、resx资源及sln解决方案等结构完整可直接在VS2015与.NET 4.5.2环境下编译运行。作者已将抓ARP包的核心逻辑封装成独立类并附上使用示例同时打包了WinPcap_4_1_3安装程序、SharpPcap.dll与PacketDotNet.dll依赖省去自行搜集环境组件的麻烦。目前已有560人学习下载读者可借此快速理解ARP抓包原理、掌握设备发现与冲突检测的实现思路并在此基础上扩展自己的网络监测工具。1. 局域网设备发现用 SharpPcap 抓 ARP 包摸清谁在线上、谁在打架机房巡检时最怕什么不是服务器宕机而是 IP 冲突。两台设备配了同一个地址网络时通时断业务方催着排查你登录交换机一层层翻 MAC 表半小时过去了还没定位到人。更麻烦的是有些设备是临时接入的笔记本或摄像头DHCP 租约里根本查不到。这种场景下与其被动等报障不如主动监听局域网里的 ARP 流量——谁在线、谁和谁撞了 IPARP 包会老老实实告诉你。这个项目就是干这件事的基于 C# 和 SharpPcap 抓取局域网 ARP 报文解析出设备 IP-MAC 映射表并自动识别同一 IP 对应多个 MAC 的冲突情况。适合做网络运维工具、资产扫描器或教学演示的开发者代码量不大但涉及网卡抓包、协议解析和冲突判定三个关键环节跑通之后能直接嵌进自己的运维面板里。2. 环境搭建与抓包入口从零把 SharpPcap 跑起来2.1 依赖选型与项目初始化这个项目用 C# 写核心依赖是 SharpPcap它是对 libpcap/Npcap 的 .NET 封装跨 Windows 和 Linux 都能用。Windows 上需要先装 Npcap 驱动安装时勾选 WinPcap 兼容模式Linux 上装 libpcap-dev 即可。新建一个 .NET 控制台项目通过 NuGet 引入三个包SharpPcap、PacketDotNet 和 System.CommandLine可选用于命令行参数。我一般会把目标框架设成 net6.0 或 net8.0前者兼容性好后者性能更优。dotnet new console -n ArpMonitor cd ArpMonitor dotnet add package SharpPcap --version 6.2.0 dotnet add package PacketDotNet --version 1.4.7装完之后先别急着写业务代码跑一个最小示例确认能列出网卡。SharpPcap 的CaptureDeviceList.Instance会返回当前机器所有可抓包的接口如果列表为空九成是 Npcap 没装好或者权限不够。Linux 下需要用 sudo 运行或者给 dotnet 进程赋 CAP_NET_RAW 能力。using SharpPcap; // 列出所有可抓包网卡 var devices CaptureDeviceList.Instance; if (devices.Count 0) { Console.WriteLine(未发现网卡请检查 Npcap/libpcap 是否安装); return; } foreach (var dev in devices) { // 打印网卡名称和描述方便选择 Console.WriteLine(${dev.Name} - {dev.Description}); }这段代码的作用是枚举本机网卡。dev.Name在 Windows 上是\Device\NPF_{GUID}格式Linux 上是eth0、ens33这类名字。参数方面没有需要调的重点看输出里有没有你实际连接局域网的那块网卡。常见坑是虚拟机里有多块虚拟网卡选错了就抓不到真实流量。2.2 打开网卡并设置过滤器确定网卡后下一步是打开设备并设置 BPF 过滤器。ARP 报文在以太网层的类型字段是 0x0806用过滤器arp就能只抓 ARP 包避免把 HTTP、DNS 流量全捞进来导致内存暴涨。打开设备时把ReadTimeout设成 1000 毫秒这样主循环不会死等。var device devices[0]; // 实际使用时根据名称选择 device.Open(new DeviceConfiguration { Mode DeviceModes.Promiscuous, // 混杂模式能抓到非本机目标的 ARP ReadTimeout 1000 }); // 只抓 ARP 包减少无关流量 device.Filter arp;DeviceModes.Promiscuous是混杂模式交换机环境下如果端口没做隔离能收到广播域内其他设备的 ARP 请求和应答。ReadTimeout控制GetNextPacket的阻塞时长设太小会空转耗 CPU设太大退出时不够灵敏。过滤器字符串遵循 BPF 语法除了arp还可以写arp and src net 192.168.1.0/24来限定网段。2.3 抓包主循环与事件回调SharpPcap 有两种消费方式事件回调和主动轮询。事件回调适合长时间后台运行轮询适合需要控制节奏的场景。这个项目用轮询更直观主循环里不断取包取到就交给解析函数。while (true) { var status device.GetNextPacket(out PacketCapture capture); if (status ! GetPacketStatus.PacketRead) continue; // 超时或出错继续下一轮 var rawPacket capture.GetPacket(); // 交给 ARP 解析模块处理 ArpParser.Process(rawPacket); }GetNextPacket返回状态枚举只有PacketRead才表示拿到了有效包。capture.GetPacket()拿到的是原始字节后面用 PacketDotNet 解析。这里要注意如果 CtrlC 退出需要调用device.Close()释放句柄否则下次运行可能报“设备已被占用”。我一般会注册Console.CancelKeyPress事件来做清理。3. 核心模块拆解ARP 报文解析与 IP-MAC 映射表维护3.1 从原始字节到 ARP 结构体拿到原始包之后用 PacketDotNet 逐层剥开。以太网帧头之后如果是 ARP就转成ArpPacket。ARP 报文里关键字段有四个发送方 IP、发送方 MAC、目标 IP、目标 MAC。请求包的 TargetMac 是全零应答包的 TargetMac 是请求方的 MAC。using PacketDotNet; public static void Process(RawCapture rawPacket) { var packet Packet.ParsePacket(rawPacket.LinkLayerType, rawPacket.Data); var arp packet.ExtractArpPacket(); if (arp null) return; // 操作码 1请求 2应答 bool isReply arp.Operation ArpOperation.Response; string senderIp arp.SenderProtocolAddress.ToString(); string senderMac arp.SenderHardwareAddress.ToString(); string targetIp arp.TargetProtocolAddress.ToString(); // 更新映射表 ArpTable.Update(senderIp, senderMac, isReply); }arp.Operation是 ushort 类型1 表示 who-has 请求2 表示 is-at 应答。SenderProtocolAddress是 IPAddress 类型ToString()得到点分十进制。SenderHardwareAddress是物理地址格式化成AA-BB-CC-DD-EE-FF。这里有个细节ARP 请求包里 SenderMac 是发起方的真实 MACTargetMac 是全零应答包里 SenderMac 是应答方的 MAC。两种包都能用来学习映射关系但请求包的可靠性更高因为它是广播的不容易被中间设备篡改。3.2 映射表的数据结构与冲突判定映射表用ConcurrentDictionarystring, HashSetstringkey 是 IPvalue 是该 IP 出现过的所有 MAC 集合。正常情况一个 IP 只对应一个 MAC集合大小是 1如果集合大小大于 1说明有冲突。用 ConcurrentDictionary 是因为抓包回调和定时清理可能在不同线程。public static class ArpTable { // IP - 多个 MAC private static readonly ConcurrentDictionarystring, HashSetstring _table new(); public static void Update(string ip, string mac, bool isReply) { var macs _table.GetOrAdd(ip, _ new HashSetstring()); lock (macs) { macs.Add(mac); if (macs.Count 1) { // 冲突同一 IP 出现多个 MAC Console.WriteLine($[冲突] IP {ip} 对应 MAC: {string.Join(, , macs)}); } } } public static IReadOnlyDictionarystring, HashSetstring Snapshot() _table; }GetOrAdd保证 IP 首次出现时创建集合lock保护 HashSet 的并发写入。冲突判定逻辑很简单集合元素超过 1 就报警。但实际场景里要区分“真冲突”和“设备换网卡”——比如一台笔记本先插网线后连 WiFiMAC 变了但 IP 没变这不算冲突。我的做法是加一个时间窗口比如 5 分钟内同一 IP 出现两个 MAC 才判定为冲突超过窗口的旧 MAC 自动过期。这个逻辑可以放在定时任务里做每 30 秒清理一次超过 5 分钟未更新的条目。3.3 定时输出与设备列表持久化抓包是持续的但人不能一直盯着控制台。加一个定时器每 10 秒把当前映射表快照输出到控制台或写入 CSV。CSV 字段包括 IP、MAC 列表、首次发现时间、最后更新时间、是否冲突。private static Timer _reportTimer; public static void StartReporting() { _reportTimer new Timer(_ { var snapshot ArpTable.Snapshot(); foreach (var kv in snapshot) { bool conflict kv.Value.Count 1; // 写入 CSV 或打印 Console.WriteLine(${kv.Key},{string.Join(|, kv.Value)},{conflict}); } }, null, TimeSpan.Zero, TimeSpan.FromSeconds(10)); }Timer的回调在 ThreadPool 线程执行和抓包线程不冲突。输出格式用逗号分隔方便后续用 Excel 或脚本处理。如果设备多控制台刷屏太快可以改成只在有变化时输出或者写文件。我一般会同时写一份 JSON 快照方便前端面板读取。4. 避坑与排查抓不到包、误报冲突、性能抖动的处理4.1 抓不到任何 ARP 包现象程序启动后控制台一直空白映射表始终为空。原因通常是网卡选错或权限不足。Windows 上如果没装 Npcap 或者装了但没勾选“WinPcap 兼容模式”CaptureDeviceList可能返回空列表。Linux 上非 root 用户没有 raw socket 权限device.Open会抛异常。解决Windows 重装 Npcap 并勾选兼容模式Linux 用sudo dotnet run或者setcap cap_net_rawep /usr/bin/dotnet赋权。另外确认选中的网卡确实连接了目标局域网虚拟机里默认网卡可能是 NAT 模式抓不到物理局域网的广播包。4.2 同一 IP 频繁出现两个 MAC 但并非冲突现象映射表里某个 IP 对应两个 MAC但网络实际正常。原因可能是设备同时开启了有线网卡和无线网卡或者做了网卡绑定bonding。ARP 请求会从两个网卡分别发出导致映射表里出现两个 MAC。解决在冲突判定里加时间窗口和 MAC 厂商前缀过滤。如果两个 MAC 的 OUI 相同同一厂商大概率是同一台设备的多网卡如果 OUI 不同才更可能是真冲突。另外可以只统计 ARP 应答包因为应答包通常只从主用网卡发出。4.3 长时间运行内存持续增长现象程序跑几个小时后内存占用从几十 MB 涨到几百 MB。原因映射表只增不减过期条目没有清理。解决加定时清理任务遍历映射表把最后更新时间超过阈值比如 10 分钟的 MAC 从集合里移除集合为空时删除整个 IP 条目。清理频率不用太高每 30 秒一次足够。另外注意RawCapture对象用完即弃不要缓存否则 GC 压力很大。4.4 混杂模式下抓到大量无关包导致 CPU 高现象CPU 占用率长期在 20% 以上风扇狂转。原因过滤器没设或者设得太宽抓到了整个广播域的所有流量。解决确保device.Filter arp生效如果还高可以进一步限定arp and ether broadcast只抓广播 ARP。另外ReadTimeout不要设成 0无限阻塞设 500 到 1000 毫秒比较平衡。如果网段里设备特别多超过 500 台可以考虑用arp and src net 192.168.1.0/24限定网段。4.5 在 Windows 服务里运行时报会话错误现象把程序注册成 Windows 服务后启动就报“无法打开设备”或“会话失败”。原因Windows 服务默认运行在 Session 0没有桌面交互权限Npcap 驱动不允许 Session 0 进程直接抓包。解决不要用 Windows 服务方式运行改用计划任务Task Scheduler并勾选“不管用户是否登录都要运行”或者用 NSSM 这类工具把控制台程序包装成服务并指定用户会话。如果必须用服务考虑换用基于 WinPcap 的旧接口或者改用 ETW 事件追踪。5. 二次开发与扩展从 ARP 监控到资产测绘面板5.1 把控制台输出改成 Web API控制台程序适合调试但实际运维更想要一个能远程访问的面板。改造思路是把抓包逻辑封装成一个后台服务类用 ASP.NET Core 的IHostedService托管然后暴露两个接口GET /api/devices返回当前设备列表GET /api/conflicts只返回冲突条目。映射表用单例注册抓包服务在ExecuteAsync里启动循环StopAsync里关闭网卡。public class ArpMonitorService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken token) { var device CaptureDeviceList.Instance[0]; device.Open(new DeviceConfiguration { ReadTimeout 1000 }); device.Filter arp; while (!token.IsCancellationRequested) { var status device.GetNextPacket(out PacketCapture capture); if (status GetPacketStatus.PacketRead) ArpParser.Process(capture.GetPacket()); await Task.Delay(10, token); // 让出线程 } device.Close(); } }BackgroundService是 .NET 里托管长任务的标配ExecuteAsync在应用启动后自动运行。注意Task.Delay不能省否则循环会占满一个 CPU 核心。token用于优雅退出StopAsync里调device.Close()释放句柄。接口层用Controller或Minimal API都行返回 JSON 时把 HashSet 转成数组。5.2 增加 OUI 厂商识别光有 MAC 地址不够直观运维更想知道“这个 MAC 是哪家厂商的”。MAC 前三个字节是 OUI可以离线维护一份 OUI 映射表网上有公开的 CSV加载到内存里做前缀匹配。改造点在ArpTable.Update里拿到 MAC 后查表得到厂商名一起存进设备记录。private static readonly Dictionarystring, string _ouiMap LoadOuiCsv(oui.csv); public static string GetVendor(string mac) { // MAC 格式 AA-BB-CC-DD-EE-FF取前 8 个字符 string prefix mac.Substring(0, 8).Replace(-, :).ToUpper(); return _ouiMap.TryGetValue(prefix, out var vendor) ? vendor : 未知; }OUI 表大概几万条用 Dictionary 加载后查询是 O(1)。注意 MAC 格式要统一SharpPcap 输出的是AA-BB-CC-DD-EE-FFOUI 表通常是AA:BB:CC需要做分隔符转换。这个功能加上之后冲突告警里就能显示“IP 192.168.1.100 被某厂商设备占用”排查效率提升明显。5.3 冲突告警推送到内部通知渠道发现冲突后光打印日志不够得让人第一时间知道。可以接内部的通知接口比如企业微信机器人或邮件 SMTP。改造点在冲突判定分支里当macs.Count 1且超过时间窗口时触发一次告警。告警内容包含 IP、冲突 MAC 列表、发现时间。为了避免重复告警加一个告警冷却期同一 IP 5 分钟内只告警一次。private static readonly Dictionarystring, DateTime _lastAlert new(); private static void SendAlert(string ip, IEnumerablestring macs) { if (_lastAlert.TryGetValue(ip, out var last) (DateTime.Now - last).TotalMinutes 5) return; // 冷却期内不重复告警 _lastAlert[ip] DateTime.Now; string msg $IP冲突: {ip} 对应 MAC {string.Join(, , macs)}; // 调用通知接口 Notifier.Send(msg); }冷却字典也要定期清理否则长时间运行会积累大量过期 key。可以在清理映射表的同一个定时任务里顺带清理_lastAlert把超过 30 分钟的条目删掉。5.4 验证改造效果的方法改完之后怎么确认没跑偏我的习惯是搭一个最小测试环境找两台机器手动配同一个静态 IP然后运行程序看 10 秒内是否报冲突。再改回不同 IP确认告警消失。对于 OUI 识别拿几个已知厂商的设备比如某品牌路由器、某品牌手机连上看厂商名是否匹配。对于 Web API用 curl 或 Postman 请求/api/conflicts确认返回结构符合预期。最后跑一个 24 小时稳定性测试观察内存曲线是否平稳、有没有漏报。从那以后我每次做抓包类工具都强制走一遍“最小冲突环境验证 24 小时内存观察”的流程不然上线后半夜被叫起来查问题血泪经验。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑