介绍

WinDivert最简单的接入教程,以及使用的一些心得


前言

-最近开发的实时战斗游戏,在国外某国测试了,有很多其他国家的也想体验,通过梯子来到了我们游戏,我们发现他们在直播玩的时候,拉扯很严重,延迟较高,我们当时在这种极度弱网下表现并不是很好,因此打算找一个弱网工具模拟这种情况。经过分析,我发现主要原因在于UDP包的堆积,然后大量同时到达导致。
曾经用过一个叫NEWT(Network Emulator for Windows Toolkit)模拟,效果挺好,但是只支持win7,现在我们公司以及全部普及win10了,win10下只能断网,但是再难找到这么好用的工具了,因此,打算自己造轮子,做一个弱网工具。那么难点主要在于怎么捕获通过windows的包?WinDivert就是一个最好的工具


提示:以下是本篇文章正文内容,下面案例可供参考

一、WinDivert是什么?

WinDivert是一个功能强大的用户模式capture/sniffing/modification/blocking/re-injection工具,适用于Windows 7、Windows 8和Windows 10。WinDivert可用于实现用户模式包过滤器、包嗅探器、防火墙、NAT、VPN、隧道应用程序等,而无需编写内核模式代码。

二、使用步骤

由于还没上升到需要改源码的程度,因此以下为直接使用库的方法,并没有去自己编译源码,后续如果有需求可以自己编译

1.下载库

主页地址:https://reqrypt.org/windivert.html
进入网站以后点击如下,直接下载WinDivert-2.2.0-A.zip,当然你也可以下载源码查看,编译
在这里插入图片描述
下载解压后内容如下
在这里插入图片描述

2.接入项目

代码如下(示例):

  • 新建一个C++控制台应用程序

  • 在项目目录下新增Include文件夹Src文件夹与Libs文件夹
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210630221526393.png

  • 在项目中包含Include文件夹Src文件夹,方法如下:
    在这里插入图片描述

  • 在链接器中包含Libs文件夹:

在这里插入图片描述

  • 我这里使用x64进行测试,因此把x64/WinDivert.lib 拷贝到Libs文件夹,然后在将Lib库包含在链接器中:
    在这里插入图片描述
  • 我这里选择在64位下测试,因此把调试模式设置问debug x64:
    在这里插入图片描述
  • 预先生成一次,会在项目指定的生成目录下生成x64文件,以及.exe,如下,
    在这里插入图片描述
  • 把对应的x64/WinDivert.dll,x64/WinDivert64.sys拷贝到该执行目录下:
  • 把include/windivert.h 放到前面配置的include目录,然后在项目中把此文件包含在项目,配置完成。

3.使用

先包含头文件:

#include "windivert.h"

创建一个带网络包筛选器的WinDivert handle:

    HANDLE divertHandle = WinDivertOpen("inbound and udp and ip.SrcAddr == 10.234.36.130", WINDIVERT_LAYER_NETWORK, 1, 0);
	if (divertHandle == INVALID_HANDLE_VALUE)
	{
		std::cout << "divertHandle == INVALID_HANDLE_VALUE : error " << GetLastError();
		return 0;
	}

第一个参数筛选方式,这里有很多筛选方式,用的最多的就是:

  • inbound / outbound :表示处理收包 跟发包
  • udp / tcp 筛选不同的传输层协议
  • ip.* ip地址筛选
  • udp.* / tcp.* 针对某一传输层进行更具体的筛选

第二个参数,枚举类型,layer选择如下:

/*
* WinDivert layers.
*/
typedef enum
{
   WINDIVERT_LAYER_NETWORK = 0,        /* Network layer. */
   WINDIVERT_LAYER_NETWORK_FORWARD = 1,/* Network layer (forwarded packets) */
   WINDIVERT_LAYER_FLOW = 2,           /* Flow layer. */
   WINDIVERT_LAYER_SOCKET = 3,         /* Socket layer. */
   WINDIVERT_LAYER_REFLECT = 4,        /* Reflect layer. */
} WINDIVERT_LAYER, *PWINDIVERT_LAYER;

第三个参数,优先级,用于定义多个handle,值越大,优先级越高:
第四个参数,flag,可以对抓取的包进行更精确的控制,暂不详讲:

 /*
* WinDivert flags.
*/
#define WINDIVERT_FLAG_SNIFF            0x0001
#define WINDIVERT_FLAG_DROP             0x0002
#define WINDIVERT_FLAG_RECV_ONLY        0x0004
#define WINDIVERT_FLAG_READ_ONLY        WINDIVERT_FLAG_RECV_ONLY
#define WINDIVERT_FLAG_SEND_ONLY        0x0008
#define WINDIVERT_FLAG_WRITE_ONLY       WINDIVERT_FLAG_SEND_ONLY
#define WINDIVERT_FLAG_NO_INSTALL       0x0010
#define WINDIVERT_FLAG_FRAGMENTS        0x0020

更多细节,可以下载源码,通过看examples理解:
在这里插入图片描述
这里有一些坑,可以通过打印GetLastError() 的错误号处理:
在这里插入图片描述
可以对照错误,知道为什么,我遇到的主要是要管理员权限,错误号5,可以通过使用用管理员权限运行VS解决。还有就是第一个参数筛选方式,错误号87

接下来接收捕获的数据的class:

#define DIVERT_MAX_PACKETSIZE 0xFFFF
#define DIVERT_INIT_PACKETSIZE 128
class DivertPacket
{
public:
   DivertPacket()
   {
   	Reset();
   }
   ~DivertPacket()
   {

   }

   char packet[DIVERT_MAX_PACKETSIZE];
   UINT packetLength;
   WINDIVERT_ADDRESS addr;

   void Reset()
   {
   	packetLength = 0;
   	memset(packet, 0, DIVERT_MAX_PACKETSIZE);
   }
};

数据处理的代码:

DivertPacket* dPacket = new DivertPacket();
	while (true)
   {
   	if (WinDivertRecv(divertHandle, dPacket->packet, DIVERT_MAX_PACKETSIZE, &dPacket->packetLength, &dPacket->addr))
   	{
   		/*
   		PWINDIVERT_IPHDR ppIpHdr;
   		PWINDIVERT_UDPHDR udpHDR;
   		WinDivertHelperParsePacket(dPacket->packet, dPacket->packetLength, &ppIpHdr, nullptr, nullptr, nullptr, nullptr, nullptr, &udpHDR, nullptr, nullptr, nullptr, nullptr);
   		if (ppIpHdr != NULL && udpHDR != NULL)
   		{
   			memset(buffer, 0, 64);
   			WinDivertHelperFormatIPv4Address(ppIpHdr->SrcAddr, buffer, 64);
   			spdlog::debug("ip src", buffer);
   			memset(buffer, 0, 64);
   			WinDivertHelperFormatIPv4Address(ppIpHdr->DstAddr, buffer, 64);
   			spdlog::debug("ip dst", buffer);
   			spdlog::debug("port srt", to_string(udpHDR->SrcPort));
   			spdlog::debug("port dst", to_string(udpHDR->DstPort));
   		}
   		*/

   		WinDivertHelperCalcChecksums(dPacket->packet, dPacket->packetLength, &dPacket->addr, 0);
   		if (!WinDivertSend(divertHandle, dPacket->packet, dPacket->packetLength, NULL, &dPacket->addr))
   		{
   			// Handle send error
   			std::cout << "WinDivertSend : error " << GetLastError();
   			continue;
   		}
   		spdlog::debug("packege len : {}", dPacket->packetLength);
   		spdlog::debug("Timestamp : {}", dPacket->addr.Timestamp);
   		spdlog::debug("now : {}", GetTicks());

   		//std::cout <<"packege len : " << dPacket->packetLength <<"\n";
   		memset(dPacket, 0, DIVERT_MAX_PACKETSIZE);
   	}
   	else
   	{
   		std::cout << "WinDivertRecv : error " << GetLastError();
   		continue;
   	}


   	Sleep(10);
   }

spdlog可以看我之前一篇文章,接入的c++日志库,注释部分为具体解析包的代码
WinDivertRecv 与 WinDivertSend 分别为捕获与再次转发包的代码。
具体我们对包的处理还在做,属于机密就不公开了,思路是:链表保存捕获的数据包,增加一个多模块的处理,通过后的,转发给本地应用或者发出,否则保存直到满足每一个module的条件了。

更加详细的说明可以去官方查看:
官方:https://reqrypt.org/windivert.html
github:https://github.com/basil00/Divert/tree/master/examples
文档:file:///E:/study/Document/WinDivert-2.2.0-A/WinDivert-2.2.0-A/doc/WinDivert.html

测试:

修改筛选器,14.215.177.38 是百度的网址:

HANDLE divertHandle = WinDivertOpen("inbound and ip.SrcAddr == 14.215.177.38", WINDIVERT_LAYER_NETWORK, 1, 0);

然后我们在cmd中:ping 14.215.177.38
打印如下:
在这里插入图片描述

总结

本文主要用于学习使用,具体的业务逻辑可以根据自己的需求去做,接入流程挺容易出问题的,欢迎留言,看到定会回复。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐