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创建router
var router = new WinDivertRouter(dstAddr);

2.2 创建WinDivertAddress

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

属性 说明
WinDivertAddress.NetWork->IfIdx 发包的网络适配器的索引
WinDivertAddress.Flags.OutboundFlag 是否为出口方向
WinDivertAddress.Flags.LoopbackFlag 是否为回环
// 使用router创建WinDivertAddress
using 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,并且数据是入口方向,必要不必要的数据到达我们的应用层而增加了处理负担:

// 只接受进入系统的icmp
var 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 = 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求交集,就是我们需要的结果。


/// <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是非常轻松的。

WindivertDotnet快速发Ping的更多相关文章

  1. linux ping 命令解析

    不管在windows平台,还是在linux平台,ping都是非常常用的网络命令:ping命令通过ICMP(Internet控制消息协议)工作:ping可以用来测试本机与目标主机是否联通.联通速度如何. ...

  2. Ubuntu CEPH快速安装

    一.CEPH简介 不管你是想为云平台提供Ceph 对象存储和/或 Ceph 块设备,还是想部署一个 Ceph 文件系统或者把 Ceph 作为他用,所有 Ceph 存储集群的部署都始于部署一个个 Cep ...

  3. Linux下ping命令参数详细解析

    -a Audible ping. #Audible ping. -A Adaptive ping. Interpacket interval adapts to round-trip time, so ...

  4. Python ping 模块

    使用socket模块也可以获得域名对应的ip,参考:https://blog.csdn.net/c465869935/article/details/50850598 print socket.get ...

  5. Linux命令ping

    原文 ping命令用来测试主机之间网络的连通性.执行ping指令会使用ICMP传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就会回应该信息,因而得知该主机运作正常. 语法 ping(选项 ...

  6. 阿里云移动研发平台 EMAS 助力银行业打造测试中台,提升发版效能

    随着移动互联网的发展,手机银行凭借低成本.操作简单.不受时间空间约束等优势,正逐步替代传统的网银交易方式.越来越多的银行开始了“业务移动化”转型之路,“手机APP”已经成为企业价值传递和关系维护的关键 ...

  7. 简单实现一个快速传输电子书到kindle的小项目

    前言 最近翻出来好久没有看的kindle,准备继续我的阅读之路.当然,也是因为发现了一个非常好的获取电子书资源的网站,又燃起了我的阅读兴趣. 然而,往kindle里传输电子书的方式一共有四种: 直接在 ...

  8. openfire极限优化

    日志优化   默认是 用info 级别,最好不用openfire原生的打日志方式.   离线消息用存储不打回方式,不要用打回方式   xmpp.offline.type=store_and_drop ...

  9. HTML5的Websocket(理论篇 I)

    HTML5的Websocket(理论篇 I) ** 先请来TA的邻居:** http:无状态.基于tcp请求/响应模式的应用层协议 (A:哎呀,上次你请我吃饭了么? B:我想想, 上次请你吃了么) t ...

随机推荐

  1. 开发Chrome插件,实现网站自动登录

    近期被一个事情困扰着,我们采购了一款软件,里面有一个数据大屏页,当登录过期后,数据就会保持原状,不再更新.和供应商反馈了很多次,都无法彻底解决数据显示的问题,没办法,自己周末在家研究,网站自动登录的事 ...

  2. Luogu3379 【模板】最近公共祖先(LCA) (倍增LCA)

    蒟蒻又来复习模板了.还WA了两次 #include <iostream> #include <cstdio> #include <cstring> #include ...

  3. Postman如何做接口测试,那些不得不知道的技巧

    Postman如何做接口测试1:如何导入 swagger 接口文档 在使用 postman 做接口测试过程中,测试工程师会往界面中填入非常多的参数,包括 url 地址,请求方法,消息头和消息体等一系列 ...

  4. 从C过渡到C++——换一个视角深入数组[真的存在高效吗?](2)

    从C过渡到C++--换一个视角深入数组[真的存在高效吗?](2) C风格高效的数组遍历 在过渡到C++之前我还是想谈一谈如何书写高效的C的代码,这里的高效指的是C代码的高效,也就是在不开启编译器优化下 ...

  5. C#.NET ORM FreeSql 读取使用 US7ASCII 的 Oracle 数据库中文显示乱码问题

    前言 关于 Oracle US7ASCII 中文乱码的问题,Ado.Net 和 Odbc 无法解决.包括最新的.Net Core..NET6..NET7 都无法解决这个问题. FreeSql 对 Or ...

  6. JS判断两个数组的元素是否完全相等

    1.使用ES6 新增的扩展运算符和Set新数据类型判断两个数组是否包含有相同的元素 var arr1 = ['green' , 'yellow' ,'blue' ,'red']; var arr2 = ...

  7. 【NOI P模拟赛】最短路(树形DP,树的直径)

    题面 给定一棵 n n n 个结点的无根树,每条边的边权均为 1 1 1 . 树上标记有 m m m 个互不相同的关键点,小 A \tt A A 会在这 m m m 个点中等概率随机地选择 k k k ...

  8. 青源Talk第8期|苗旺:因果推断,观察性研究和2021年诺贝尔经济学奖

    biobank 英国的基金数据因果推断和不同的研究互相论证,而非一个研究得到的接了就行.数据融合,data fusion,同一个因果问题不同数据不同结论,以及历史上的数据,来共同得到更稳健.更高效的推 ...

  9. ClickHouse与Elasticsearch压测实践

    1 需求分析 1.1 分析压测对象 1)什么是ClickHouse 和Elasticsearch ClickHouse 是一个真正的列式数据库管理系统(DBMS).在 ClickHouse 中,数据始 ...

  10. 第九十七篇:CSS的选择器及优先级

    好家伙,来补一点关于CSS的基础 1.id选择器 id 选择器可以为标有特定 id 的 HTML 元素指定特定的样式 使用#号来定义样式 2.class选择器 class 选择器用于描述一组元素的样式 ...