windows 驱动实例分析系列: NDIS 6.0的Filter 驱动改造(三)

embedded/2024/10/30 15:27:34/

 数据包的发送

NDIS数据包的发送是一件非常麻烦的事情,当然,在应用层,使用socket库感觉不到这一点,但是在内核中,内核主要实现是的七层结构中的下面4层,并且Filter驱动和协议驱动不一样,它完全就是裸数据,需要从Mac数据包首部进行构建。

TCP/IP协议的详细解释自行查阅资料,这里不做详细介绍,本文中的数据包格式可能存在问题,但是这和本文的目的无关~本文的含义是详细阐述网络数据包的收发本质,不是网络协议的普及文,文中仅仅依赖了Mac层的协议知识,从数据包的Mac地址填充来实现数据包的收发。

和发送数据相关的回调例程是下面的回调例程:

// 发送缓冲区的回调例程
FILTER_SEND_NET_BUFFER_LISTS_HANDLER            SendNetBufferListsHandler;// 发送完成回调
FILTER_SEND_NET_BUFFER_LISTS_COMPLETE_HANDLER   SendNetBufferListsCompleteHandler;

这个例程可能在两个地方使用,一个地方是调用NdisFRegisterFilterDriver函数时候注册回调例程;另一个地方则是FILTER_SET_FILTER_MODULE_OPTIONS_HANDLER,事实上可以在Filter运行时候动态的切换挂接的例程。

这里不会对NET_BUFFER_LIST结构进行解释,可以参考这部分文章: NET_BUFFER_LIST体系

分配缓冲区

分配NET_BUFFER_LIST的代码如下:

// 分配一个NET_BUFFER_LIST
PNET_BUFFER_LIST AllocateNetBuffList(
IN NDIS_HANDLE pParam, 
IN PUCHAR pBuffer, 
IN ULONG ulBufferSize
)
{PMS_FILTER              pFilter = (PMS_FILTER)pParam;PMDL                       pMDL = NULL;PUCHAR              pMDLAddress = NULL;PNET_BUFFER_LIST pNetBufferList = NULL;if (pFilter->SendNetBufferListPool == NULL){return NULL;}// 1. 分配一块内存pMDLAddress = (PUCHAR)NdisAllocateMemoryWithTagPriority(pFilter->FilterHandle, ulBufferSize, FILTER_ALLOC_TAG, LowPoolPriority);NdisZeroMemory(pMDLAddress, ulBufferSize);// 2. 根据分配的虚拟内存创建MDLpMDL = NdisAllocateMdl(pFilter->FilterHandle, pMDLAddress, ulBufferSize);if (pMDL == NULL){return NULL;}// 3. 将需要复制的数据拷贝到MDLNdisMoveMemory(pMDLAddress, pBuffer, ulBufferSize);// 4. 根据MDL来分配一个NET_BUFFER_LISTpNetBufferList = NdisAllocateNetBufferAndNetBufferList(pFilter->SendNetBufferListPool,0/*sizeof(FILTER_SEND_NETBUFLIST_RSVD)*/, 0, pMDL, 0, ulBufferSize);if (pNetBufferList == NULL){NdisFreeMemoryWithTagPriority(pFilter->FilterHandle, pMDLAddress, FILTER_ALLOC_TAG);NdisFreeMdl(pMDL);return NULL;}// 5. 将分配池的句柄指向Filter;pNetBufferList->SourceHandle = pFilter->FilterHandle;return pNetBufferList;
}

注意到这个过程非常麻烦,分配内存地址-> 分配MDL->分配NET_BUFFER_LIST结构;最后还将句柄赋予NET_BUFFER_LIST结构的SourceHandle字段,这是因为内存分配遵循"谁分配、谁释放"的原则,接下来会在特定的地方释放它。

克隆数据包

另外一项技术则是拷贝技术,这项技术并未在代码中演示,不过还是介绍一下,在一些网络应用中,驱动将会暂存下上层传递下来的NET_BUFFER_LIST,然后使用自己分配的NET_BUFFER_LIST,有一对函数可以快捷的完成这个操作:

NDIS_EXPORTED_ROUTINE NET_BUFFER_LIST * NdisAllocateCloneNetBufferList([in]           NET_BUFFER_LIST *OriginalNetBufferList,[in, optional] NDIS_HANDLE     NetBufferListPoolHandle,[in, optional] NDIS_HANDLE     NetBufferPoolHandle,[in]           ULONG           AllocateCloneFlags
);

调用 NdisAllocateCloneNetBufferList 以创建克隆 NET_BUFFER_LIST 结构,该结构可用于在单独的数据路径上发送重复数据。

原始 NET_BUFFER_LIST 结构中的每个 NET_BUFFER 结构仅从已用数据空间的开头克隆,而不是从整个数据空间的开头克隆。 若要获取从数据空间开头到已用数据空间开头的偏移量,请使用 NET_BUFFER_DATA_OFFSET 宏。

如果克隆的 NET_BUFFER_LIST 结构应具有与给定池关联的属性,则调用方必须在 NetBufferListPoolHandle 或 NetBufferPoolHandle 参数中指定池句柄。 例如,NET_BUFFER_LIST 结构的 ProtocolType 成员与池相关联。

克隆NET_BUFFER_LIST结构描述的数据与 OriginalNetBufferList 中的 NET_BUFFER_LIST 结构所描述的数据相同。 NDIS 不会将原始 MDL 描述的数据复制到新的数据缓冲区。 相反,克隆的结构引用原始数据缓冲区。 克隆 NET_BUFFER_LIST 结构不包括初始 NET_BUFFER_LIST_CONTEXT 结构。

可以调用 NdisFreeCloneNetBufferList 函数,用于释放 NET_BUFFER_LIST 结构和所有通过调用 NdisAllocateCloneNetBufferList 分配的关联结构和 MDL 链。

	pClone = NdisAllocateCloneNetBufferList(pSendData->pNetBufferLists, pFilter->SendNetBufferListPool, pFilter->SendNetBufferPool, NDIS_CLONE_FLAGS_USE_ORIGINAL_MDLS);if(NULL == pClone){return NULL;}pClone->SourceHandle        = pFilter->FilterHandle;pClone->ParentNetBufferList = pSendData->pNetBufferLists;

发送缓冲区

发送缓冲区非常简单:

NdisFSendNetBufferLists(
pFilter->FilterHandle, 
pNetBufferList, 
NDIS_DEFAULT_PORT_NUMBER, 
NDIS_SEND_FLAGS_DISPATCH_LEVEL| NDIS_SEND_FLAGS_CHECK_FOR_LOOPBACK);

真正麻烦的地方在于,在内核中,需要填充Mac首部、IP首部、TCP/UDP/ICMP首部,下面是这些首部的定义:

// IP数据包的MAC首部
typedef struct _tagMac
{UCHAR   mac_des[6];UCHAR   mac_src[6];USHORT  mac_type;
}MAC_HEADER, * PMAC_HEADER;// IP数据包的首部
typedef struct _tagIPHeader 
{UCHAR     ip_verlen;             // Version and length.UCHAR     ip_tos;                // Type of service.USHORT    ip_length;             // Total length of datagram.USHORT    ip_id;                 // Identification.USHORT    ip_offset;             // Flags and fragment offset.UCHAR     ip_ttl;                // Time to live.UCHAR     ip_protocol;           // Protocol.USHORT    ip_xsum;               // IP校验和UINT      ip_src;                // Source address.UINT      ip_dest;               // Destination address.
}IP_HEADER, * PIP_HEADER;// UDP数据包首部
typedef struct _tagUDPHeader 
{USHORT  src_port;USHORT  des_port;USHORT  udp_length;USHORT  udp_sum;        // UDP校验和
}UDP_HEADER, * PUDP_HEADER;// ICMP包首部
typedef struct _tagICMPHead
{UCHAR   type;       // 类型UCHAR   code;       // 操作码USHORT  checksum;   // ICMP校验和USHORT  id;USHORT  seq;        
}ICMPHead, *PICMPHead;

这里解释一下,为什么需要一个单独的结构来传递:

// 发送ICMP结构时候用于构建UDP信息的结构
// 注意,这里没有包含UDP和TCP需要的端口信息,这部分不在本代码的演示范围内
typedef struct _tagICMP_INFO
{UCHAR  des_mac[6];        // 目标MAC地址UCHAR  src_mac[6];        // 源MAC地址ULONG  des_ip;            // 目标IP地址, ULONG  src_ip;            // 源IP地址PCHAR pData;             // 数据地址USHORT usSize;            // 数据长度
}ICMP_INFO, * PICMP_INFO;

当需要收发数据的时候,必须指定网络适配器,所以传入的这结构中的src_mac将用于从链表中找到需要发送的网络适配器:

PMS_FILTER FilterFindObject(_In_reads_bytes_(BufferLength)PUCHAR                   Buffer,_In_ ULONG                    BufferLength
)
{PMS_FILTER              pFilter;PLIST_ENTRY             Link;BOOLEAN                 False = FALSE;FILTER_ACQUIRE_LOCK(&FilterListLock, False);Link = FilterModuleList.Flink;while (Link != &FilterModuleList){// 这个CONTAINING_RECORD用于从结构对象中的结构成员反向解析出结构对象的地址pFilter = CONTAINING_RECORD(Link, MS_FILTER, FilterModuleLink);if (BufferLength == _MAC_LENGTH){if (NdisEqualMemory(Buffer, pFilter->Mac, _MAC_LENGTH)){FILTER_RELEASE_LOCK(&FilterListLock, False);return pFilter;}}Link = Link->Flink;}FILTER_RELEASE_LOCK(&FilterListLock, False);return NULL;
}

这样调用NdisFSendNetBufferLists函数的时候,第一个参数就是对应的网络适配器,驱动会在调用FilterAttach的时候将底层网络适配器的Handle传递进来,这个时候就需要保存下来用于后续NDIS函数调用。

注意: 在此处,将发送的缓冲区取整了,实际上使用了74字节,但是为了对齐使用了80字节,事实证明换回74字节,驱动并不会异常,不过并未修改此处。

释放缓冲区

根据内核内存分配的惯例,需要自行释放在驱动中分配的缓冲区,所以需要添加SendNetBufferListsCompleteHandler对应的例程,下面是案例的代码。

VOID
FilterSendNetBufferListsComplete(NDIS_HANDLE         FilterModuleContext,PNET_BUFFER_LIST    NetBufferLists,ULONG               SendCompleteFlags)
/*++Routine Description:发送完成处理程序每当下层完成处理发送的 NET_BUFFER_LIST 时,就会调用此例程。如果 Filter 不需要参与发送路径,则应删除此例程和 FilterSendNetBufferLists
例程。NDIS 将比 Filter 更有效地代表您的 Filter 传递发送数据包。Arguments:FilterModuleContext     - Filter上下文NetBufferLists          - 数据包SendCompleteFlags       - 发送标志Return Value:NONE--*/
{PMS_FILTER         pFilter = (PMS_FILTER)FilterModuleContext;//PNET_BUFFER_LIST   CurrNetBufferList = NULL;//PNET_BUFFER_LIST   NextNetBufferList;ULONG              NumOfSendCompletes = 0;BOOLEAN            DispatchLevel;PNET_BUFFER_LIST   CurrNbl;DEBUGP(DL_TRACE, "===>SendNBLComplete, NetBufferList: %p.\n", NetBufferLists);// 如果您的 Filter 将任何发送数据包注入到要发送的数据路径中,// 您必须在此处识别他们的NBL并将其从链中删除。不要尝试将完整的NBL发送到更高层;// 如果您的 Filter 在FilterSendNetBufferLists处理程序修改了您的数据库中的任何NBL// 或NBs、MDL等),您必须在此处撤消修改。// 一般来说,NBL必须以与您之前相同的条件归还,// 你收到了它们。例外情况:NBL可以在链接上重新使用列表,划痕字段为“NO”// 发送完整的NBL。如果您从链中删除了任何NBL,请确保链不是空的(即NetBufferLists!=NULL)#ifdef FILTER_TEST// NetBufferLists是个链表,但是在 以太网 级别的内核驱动,基本都是单独一个NET_BUFFER;// 稳妥的写法还是遍历链表,下面以简便为主;//for (CurrNetBufferList = NetBufferLists;//    CurrNetBufferList != NULL;//    CurrNetBufferList = NextNetBufferList)//{//    NextNetBufferList = NET_BUFFER_LIST_NEXT_NBL(CurrNetBufferList);if (pFilter->FilterHandle == NetBufferLists->SourceHandle){// 释放自己分配的数据包ReleaseNetBufferList(pFilter->SendNetBufferListPool, NetBufferLists);}else{NdisFSendNetBufferListsComplete(pFilter->FilterHandle, NetBufferLists, SendCompleteFlags);}//}
#elseNdisFSendNetBufferListsComplete(pFilter->FilterHandle, NetBufferLists, SendCompleteFlags);
#endifDEBUGP(DL_TRACE, "<===SendNBLComplete.\n");
}

当数据包发送到下层驱动之后,这个函数会调用,然后进行处理,filter驱动的数据包来源可能是自己分配的,也可能不是自己分配的,所以需要使用下面的代码来判断:

	if(pFilter->FilterHandle == pNetBufferList->SourceHandle){// 是自行分配的数据包if(pNetBufferList->ParentNetBufferList == NULL){// 自行分配的数据包}else{// 自行克隆的数据包// 一般除了释放克隆数据包之外,还需要完成之前暂存的数据包NdisFSendNetBufferListsComplete(...)}}

注意到里面调用了 ReleaseNetBufferList,这个函数的代码如下:

void ReleaseNetBufferList(IN NDIS_HANDLE pNdisPoolHandle, IN PNET_BUFFER_LIST NetBufferLists)
{PNET_BUFFER_LIST	pCurrNbl;PNET_BUFFER			pCurrBuff;PMDL				pMdl;PUCHAR              pCopyData = NULL;ULONG               BufferLength;pCurrNbl = NetBufferLists;pCurrBuff = NET_BUFFER_LIST_FIRST_NB(pCurrNbl);// 获取MDLpMdl = NET_BUFFER_FIRST_MDL(pCurrBuff);if (pMdl != NULL){// 获取MDL指向的内存NdisQueryMdl(pMdl, (PVOID*)&pCopyData, &BufferLength, NormalPagePriority);if (pCopyData != NULL){NdisFreeMdl(pMdl);//Free MDL  NdisFreeMemoryWithTagPriority(pNdisPoolHandle, pCopyData, FILTER_ALLOC_TAG);}}NdisFreeNetBufferList(pCurrNbl);return ;
}

http://www.ppmy.cn/embedded/133643.html

相关文章

XCode16.0 Command PhaseScriptExecution failed with a nonzero exit code 的错误

环境 : XCode 16.0 pod --version 1.15.2把ENABLE_USER_SCRIPT_SANDBOXING 设置为NO OK 如果pod的版本<1.14.3 试一试修改这里

Hexo提交部署命令与Git Bash Here控制终端中按下Ctrl+C无法中断hexo s的解决办法

一、hexo提交命令如下 hexo clean #清理缓存文件 hexo g #生成文件 hexo s #运行本地服务器 hexo d #推送博客源码到托管代码仓库所在的服务器二、Git Bash Here控制终端中按下CtrlC无法中断hexo s的解决方法 1、打开电脑CMD控制台&#xff1b; 2、复制以下命…

开发了一个成人学位英语助考微信小程序

微信小程序名称&#xff1a;石榴英语 全称&#xff1a;石榴英语真题助手 功能定位 北京成人学士学位英语辅助学习工具&#xff0c;包含记高频单词&#xff0c;高频词组&#xff0c;专项练习&#xff0c;模拟考试等功能。 开发背景 个人工作需要提高学习英文水平&#xff…

昇思25天学习打卡营第1天|快速入门

昇思25天学习打卡营第1天|快速入门 目录 昇思25天学习打卡营第1天|快速入门实操教程 一、MindSpore内容简介 主要特点&#xff1a; MindSpore的组成部分&#xff1a; 二、入门实操步骤 1. 安装必要的依赖包 2. 下载并处理数据集 3. 构建网络模型 4. 训练模型 5. 测试…

微信小程序如何实现地图轨迹回放?

要在Uni-app中实现微信小程序的地图轨迹回放功能&#xff0c;你可以按照以下步骤进行操作&#xff1a; 在Uni-app项目中引入地图组件&#xff1a;在页面中使用uni-app提供的map组件&#xff0c;可以使用uni.createMapContext方法获取地图上下文对象&#xff0c;以便后续操作地图…

搭建 python 连接 DM 环境

1&#xff09;先安装好 达梦数据库 安装数据库【docker】 语雀去官网申请下载下载链接&#xff1a;https://www.dam...https://www.yuque.com/heyyall/zwitgw/wx6khq9x6e3l55x5 安装数据库【window】 语雀官网下载https://eco.dameng.com/...https://www.yuque.com/heyyall…

什么是标准差?详解

文章目录 一、什么是标准差&#xff1f;二、公式三、举个例子&#x1f330;参考 一、什么是标准差&#xff1f; 在统计学中&#xff0c;标准差&#xff08;Standard Deviation&#xff09;是用于衡量变量值围绕其平均值变化程度的指标。低标准差表示这些值通常接近平均值&…

SpringMVC学习(3)

目录 一、控制器Controller 二、RESTful风格 2.1 实际应用 三、结果跳转方式 3.1 ModelAndView 3.2 SpringMVC 3.2.1 无需视图解析器 3.2.2 需要视图解析器 3.3 ServletAPI 四、数据处理 4.1 处理提交数据 4.1.1 提交的域名称和处理方法的参数名一致 4.1.2 提交的…