WindivertDotnet快速發Ping

1 前言WindivertDotnet是面向對象的WinDivert的dotnet異步封裝 , 其提供如下的發送數據方法:
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是否為出口方向// 使用dstAddr創建routervar router = new WinDivertRouter(dstAddr);2.2 創建WinDivertAddressWinDivertAddress的如下屬性必須要設置正確 , 它是IP數據包構建鏈路數據包必須的項:
屬性說明WinDivertAddress.NetWork->IfIdx發包的網絡適配器的索引WinDivertAddress.Flags.OutboundFlag是否為出口方向WinDivertAddress.Flags.LoopbackFlag是否為回環// 使用router創建WinDivertAddressusing WinDivertAddress addr = router.CreateAddress();2.3 創建WinDivertPacket因為從router里知道了源IP和目標IP,所以創建ICMP ping功能的WinDivertPacket就比較容易 。
/// <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兩個對象發送出去:
/// <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,并且數據是入口方向,必要不必要的數據到達我們的應用層而增加了處理負擔:
// 只接受進入系統的icmpvar filter = Filter.True.And(f => f.IsIcmp && f.Network.Inbound);this.divert = new WinDivert(filter, WinDivertLayer.Network);3.2 接收數據接收數據這個就簡單了,這是WindivertDotnet最擅長的技能:
/// <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/// <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 = https://www.huyubaike.com/biancheng/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;}

推薦閱讀