数据包的发送
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 ;
}