• 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
    /// /// 创建icmp的echo包 /// /// /// /// 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
    /// /// 发送icmp的echo请求包 /// /// /// private async Task SendEchoRequestAsync(IEnumerable 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
    /// /// 监听ping的回复 /// /// 取消令牌 /// private async Task> RecvEchoReplyAsync(CancellationToken cancellationToken) { var results = new HashSet(); 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
    /// /// 解析出icmp回复信息 /// /// 数据包 /// 回复的IP /// 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
    /// /// Ping所有地址 /// 占用两个线程 /// /// 目标地址 /// 最后一个IP发出ping之后的等待回复时长 /// public async Task PingAllAsync(IEnumerable 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是非常轻松的。

  • 相关阅读:
    linux shell中 if else以及大于、小于、等于逻辑表达式
    记一次 .NET 某手术室行为信息系统 内存泄露分析
    三级网络技术总结
    win操作系统切换窗口
    [ Linux 长征路第四篇 ] 开发工具 vim的使用 gcc/g++的使用
    构建高效稳定的短视频直播系统架构
    多线程之任务调度线程池
    Selenium基础 — POM设计模式(一)
    MySQL索引原理和实现
    json入门教程+在java中的一些便捷操作
  • 原文地址:https://www.cnblogs.com/kewei/p/16807709.html