WindivertDotnet快速发Ping

1 前言#

WindivertDotnet是面向对象的WinDivert的dotnet异步封装,其提供如下的发送数据方法:

Copy
ValueTask<int> SendAsync( WinDivertPacket packet, WinDivertAddress addr, CancellationToken cancellationToken)

在修改包的场景,我们通过RecvAsync()方法获取具有内容的WinDivertPacketWinDivertAddress对象实例,简单修改这两个对象的一些值之后,就可以发送出去。

但在注入的场景,我们需要无中生成WinDivertPacketWinDivertAddress两个对象,前者是IP包的完整数据,后者主要指示数据要经过的网络适配器的索引、数据是入口还是出口方向、是否为loopback等信息,下面我将使用WindivertDotnet来开发一个批量Ping功能的示例来教大家怎么注入数据包。

2 发出Ping包#

2.1 路由计算#

在发Ping的场景中,我们只知道目的地IP地址,WinDivertRouter对象可以帮们提前算出路由信息,得到以下表格的内容:

属性 说明
IPAddress DstAddress 目的地IP地址
IPAddress SrcAddress 源IP地址
int InterfaceIndex 经过的网络适配器的索引
bool IsOutbound 是否为出口方向
Copy
// 使用dstAddr创建router var router = new WinDivertRouter(dstAddr);

2.2 创建WinDivertAddress#

WinDivertAddress的如下属性必须要设置正确,它是IP数据包构建链路数据包必须的项:

属性 说明
WinDivertAddress.NetWork->IfIdx 发包的网络适配器的索引
WinDivertAddress.Flags.OutboundFlag 是否为出口方向
WinDivertAddress.Flags.LoopbackFlag 是否为回环
Copy
// 使用router创建WinDivertAddress using WinDivertAddress addr = router.CreateAddress();

2.3 创建WinDivertPacket#

因为从router里知道了源IP和目标IP,所以创建ICMP ping功能的WinDivertPacket就比较容易。

Copy
/// <summary> /// 创建icmp的echo包 /// </summary> /// <param name="srcAddr"></param> /// <param name="dstAddr"></param> /// <returns></returns> private unsafe WinDivertPacket CreateIPV4EchoPacket(IPAddress srcAddr, IPAddress dstAddr) { // ipv4头 var ipHeader = new IPV4Header { TTL = 128, Version = 4, DstAddr = dstAddr, SrcAddr = srcAddr, Protocol = ProtocolType.Icmp, HdrLength = (byte)(sizeof(IPV4Header) / 4), Id = ++this.id, Length = (ushort)(sizeof(IPV4Header) + sizeof(IcmpV4Header)) }; // icmp头 var icmpHeader = new IcmpV4Header { Type = IcmpV4MessageType.EchoRequest, Code = default, Identifier = ipHeader.Id, SequenceNumber = ++this.sequenceNumber, }; // 将数据写到packet缓冲区 var packet = new WinDivertPacket(ipHeader.Length); var writer = packet.GetWriter(); writer.Write(ipHeader); writer.Write(icmpHeader); return packet; }

2.4 发出数据包#

现在我们可使用Windivert对象,将为每个目的地IP创建的WinDivertPacketWinDivertAddress两个对象发送出去:

Copy
/// <summary> /// 发送icmp的echo请求包 /// </summary> /// <param name="dstAddrs"></param> /// <returns></returns> private async Task SendEchoRequestAsync(IEnumerable<IPAddress> dstAddrs) { foreach (var address in dstAddrs) { // 使用router计算将进行通讯的本机地址 var router = new WinDivertRouter(address); using var addr = router.CreateAddress(); using var packet = this.CreateIPV4EchoPacket(router.SrcAddress, router.DstAddress); packet.CalcChecksums(addr); // 计算checksums,因为创建包时没有计算 await this.divert.SendAsync(packet, addr); } }

3 接收回复包#

3.1 Filter#

我们可以使用过滤器,将接收的内容过滤为icmp,并且数据是入口方向,必要不必要的数据到达我们的应用层而增加了处理负担:

Copy
// 只接受进入系统的icmp var filter = Filter.True.And(f => f.IsIcmp && f.Network.Inbound); this.divert = new WinDivert(filter, WinDivertLayer.Network);

3.2 接收数据#

接收数据这个就简单了,这是WindivertDotnet最擅长的技能:

Copy
/// <summary> /// 监听ping的回复 /// </summary> /// <param name="cancellationToken">取消令牌</param> /// <returns></returns> private async Task<HashSet<IPAddress>> RecvEchoReplyAsync(CancellationToken cancellationToken) { var results = new HashSet<IPAddress>(); using var packet = new WinDivertPacket(); using var addr = new WinDivertAddress(); while (cancellationToken.IsCancellationRequested == false) { try { await this.divert.RecvAsync(packet, addr, cancellationToken); if (TryGetEchoReplyAddr(packet, out var value)) { results.Add(value); } // 把packet发出,避免系统其它软件此刻也有ping而收不到回复 await this.divert.SendAsync(packet, addr, cancellationToken); } catch (OperationCanceledException) { break; } } return results; }

3.3 解析回复的IP#

Copy
/// <summary> /// 解析出icmp回复信息 /// </summary> /// <param name="packet">数据包</param> /// <param name="value">回复的IP</param> /// <returns></returns> private unsafe static bool TryGetEchoReplyAddr(WinDivertPacket packet, [MaybeNullWhen(false)] out IPAddress value) { var result = packet.GetParseResult(); if (result.IcmpV4Header != null && result.IcmpV4Header->Type == IcmpV4MessageType.EchoReply) { value = result.IPV4Header->SrcAddr; return true; } else if (result.IcmpV6Header != null && result.IcmpV6Header->Type == IcmpV6MessageType.EchoReply) { value = result.IPV6Header->SrcAddr; return true; } value = null; return false; }

4 整合数据#

我们需要一个线程来开启接收ping回复,同时另一个线程把所有ping发出去,最后拿ping的所有IP和ping回复的所有IP求交集,就是我们需要的结果。

Copy
/// <summary> /// Ping所有地址 /// 占用两个线程 /// </summary> /// <param name="dstAddrs">目标地址</param> /// <param name="delay">最后一个IP发出ping之后的等待回复时长</param> /// <returns></returns> public async Task<IPAddress[]> PingAllAsync(IEnumerable<IPAddress> dstAddrs, TimeSpan delay) { // 开始监听ping的回复 using var cts = new CancellationTokenSource(); var recvTask = this.RecvEchoReplyAsync(cts.Token); // 对所有ip发ping await this.SendEchoRequestAsync(dstAddrs); // 延时取消监听 cts.CancelAfter(delay); var results = await recvTask; // 清洗数据 return results.Intersect(dstAddrs).ToArray(); }

后记#

通过WindivertDotnet的路由,无中生有IP数据包,并可以将其正确的发送的指定的目的地IP地址。像本示例的这个Ping方式,10秒ping完1万个IP并拿到其回复的IP是非常轻松的。

posted @   jiulang  阅读(1087)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
历史上的今天:
2015-10-19 关于.Net的面试遐想
点击右上角即可分享
微信分享提示
CONTENTS