网络数据由通过网络发送或接收的数据包组成。 NDIS 提供数据结构来描述和组织此类数据。 NDIS 6.0 及更高版本的主要网络数据结构包括:
- NET_BUFFER
- NET_BUFFER LIST
- NET_BUFFER_LIST_CONTEXT
它们之间的关系如下:
在 NDIS 6.0 及更高版本中, NET_BUFFER 是打包网络数据的基本构建基块。 每个NET_BUFFER结构都有一个 MDL 链。 MDL 将数据缓冲区的地址映射到NET_BUFFER结构指定的数据空间。 此数据映射与 NDIS 5.x 的 MDL 链相同, 更早版本的驱动程序在 NDIS_PACKET 结构中使用 。 NDIS 也提供用于操作 MDL 链的函数。
可以将多个NET_BUFFER结构附加到NET_BUFFER_LIST结构。 NET_BUFFER结构组织为以 NULL 结尾的单独链接列表。 只有源自 NET_BUFFER_LIST 结构的驱动程序(即 NDIS)应直接修改链接列表以插入和删除NET_BUFFER结构。
NET_BUFFER LIST 结构包含描述附加到列表的所有 NET_BUFFER 结构的信息。 如果驱动程序需要额外的空间来存储上下文信息,驱动程序可以将此类信息存储在NET_BUFFER_LIST_CONTEXT结构中。 NDIS 提供用于分配、释放和访问NET_BUFFER_LIST_CONTEXT结构中的数据的函数。
可以附加多个NET_BUFFER_LIST结构以形成NET_BUFFER_LIST结构的列表。 NET_BUFFER_LIST结构组织为以 NULL 结尾的单独链接列表。 驱动程序可以直接修改链接列表以插入和删除NET_BUFFER_LIST结构。
NET_BUFFER 结构
NDIS 6.0 及更高版本的 NET_BUFFER 结构类似于 NDIS 5 使用的 NDIS_PACKET 结构。x 和更早版本的驱动程序。 每个NET_BUFFER结构打包一个网络数据包。
下图显示了NET_BUFFER结构中的字段:
NET_BUFFER结构包括 NetBufferHeader 成员中的NET_BUFFER_HEADER结构。 NET_BUFFER_HEADER 结构包括 NetBufferData 成员中的NET_BUFFER_DATA结构, 应使用 NDIS 宏访问NET_BUFFER结构成员。
某些 NET_BUFFER 结构成员仅由 NDIS 使用。 驱动程序通常使用的成员包括:
- ProtocolReserved:保留供协议驱动程序使用;
- MiniportReserved:保留供微型端口驱动程序使用;
- NdisPoolHandle:指定池句柄,该句柄标识从中分配NET_BUFFER结构的NET_BUFFER池;
- NEXT:指定指向NET_BUFFER结构链接列表中的下一个NET_BUFFER结构的指针。 如果这是列表中的最后一个NET_BUFFER结构,则此成员为 NULL;
- DataLength:指定 MDL 链中网络数据的长度(以字节为单位);
- DataOffset:指定从 MDL 链中内存开始到 MDL 链中网络数据开始的偏移量(以字节为单位);
- CurrentMdl:指定指向当前驱动程序正在使用的第一个 MDL 的指针。 此指针提供一种优化,通过跳过当前驱动程序未使用的任何 MDL 来提高性能;
- CurrentMdlOffset:指定 MDL 中已用数据空间开头的偏移量(以字节为单位),该偏移量由 NET_BUFFER 结构的 CurrentMdl 成员指定;
下图显示了 CurrentMdl、 CurrentMdlOffset、 DataOffset 和 DataLength 成员与数据空间之间的关系:
NDIS 提供用于管理 MDL 链中的数据空间的函数。 驱动程序如何使用数据空间与当前驱动程序一起动态更改。 有时,当前驱动程序当前未使用的数据空间。 尽管未使用的数据空间当前未使用,但它可以包含有效数据。 例如,在接收路径上, 未使用的数据空间可以包含较低级别驱动程序使用的信息。
以下术语和定义描述了 NET_BUFFER 数据空间的元素:
- 已用数据空间:已用数据空间 包含当前驱动程序当前使用的数据。 驱动程序通过撤退操作增加 已用数据空间 ,并通过高级操作减少 已用数据空间;
- 未使用的数据空间:当前驱动程序当前未使用此数据空间;
- 数据总大小:总数据大小是 已用数据空间 与 未使用数据空间大小之和。 若要计算总大小,请将 DataOffset 添加到 DataLength ;
- 撤退:撤退操作会增加 已用数据空间的大小;
- 前进:前进操作会减小 已用数据空间的大小;
NET_BUFFER_LIST结构
NET_BUFFER_LIST结构包含NET_BUFFER结构的链接列表。下图显示了NET_BUFFER_LIST结构中的字段:
NET_BUFFER_LIST结构包括 NetBufferListHeader 成员中的NET_BUFFER_LIST_HEADER结构。 NET_BUFFER_LIST_HEADER结构包括 NetBufferListData 成员中的NET_BUFFER_LIST_DATA结构。 应使用 NDIS 宏访问NET_BUFFER_LIST结构成员。
某些成员仅由 NDIS 使用。 驱动程序最有可能使用的成员在以下列表中定义:
- ParentNetBufferList:如果 NET_BUFFER_LIST 结构是从克隆、分段或重新组合的父类派生的子级, 则 ParentNetBufferList 指定指向父NET_BUFFER_LIST结构的指针。 否则,此参数为 NULL;
- NdisPoolHandle:指定池句柄,该句柄标识从中分配NET_BUFFER_LIST结构的NET_BUFFER_LIST池;
- ProtocolReserved:保留供协议驱动程序使用;
- MiniportReserved:保留供微型端口驱动程序使用;
- SourceHandle:NDIS 使用以下驱动程序提供的例程之一在绑定或附加操作中提供给驱动程序的句柄:微型端口驱动程序:MiniportInitializeEx、协议驱动程序:ProtocolBindAdapterEx、Filter驱动程序:FilterAttach。NDIS 使用 SourceHandle 将NET_BUFFER_LIST结构返回到发送NET_BUFFER_LIST结构的驱动程序。 NDIS 驱动程序不应读取此句柄;
- ChildRefCount:如果 NET_BUFFER_LIST 结构是父类(具有由克隆、片段或重新组合操作) 派生的子级, 则 ChildRefCount 指定现有子级的数目。 否则此参数为零;
- Flags:保留用于NET_BUFFER_LIST结构的属性的未来规范。 目前没有可用于驱动程序的标志;
- Status:指定此NET_BUFFER_LIST结构的网络数据操作的最终完成状态。 微型端口驱动程序在完成发送操作之前写入此值;
- NetBufferListInfo:指定 列表中 所有NET_BUFFER结构通用 NET_BUFFER_LIST 结构信息。 此信息通常称为“带外 (OOB) 数据”;
- Next: 指定指向NET_BUFFER_LIST结构链接列表中的下一个NET_BUFFER_LIST结构的指针。 如果NET_BUFFER_LIST结构是列表中的最后一个结构,则此成员为 NULL;
- FirstNetBuffer:指定指向与此NET_BUFFER_LIST结构关联的NET_BUFFER结构链接列表中的第一个NET_BUFFER结构的指针;
注意Context 是指向 NET_BUFFER_LIST_CONTEXT 结构的指针。 NDIS 提供宏和函数来操作上下文中的数据。
NET_BUFFER_LIST_CONTEXT结构
NDIS 驱动程序使用 NET_BUFFER_LIST_CONTEXT 结构来存储与 NET_BUFFER_LIST 结构关联的其他数据。 NET_BUFFER_LIST 结构的 Context 成员是指向NET_BUFFER_LIST_CONTEXT结构的指针。 存储在NET_BUFFER_LIST_CONTEXT结构中的信息对堆栈中的 NDIS 和其他驱动程序是不透明的。
下图显示了NET_BUFFER_LIST_CONTEXT结构中的字段:
NET_BUFFER_LIST_CONTEXT结构包括包含上下文数据的 ContextData 成员。 此数据可以是驱动程序 NET_BUFFER_LIST结构所需的 任何上下文信息。
驱动程序应使用以下 NDIS 宏和函数来访问和操作NET_BUFFER_LIST_CONTEXT结构中的成员:
NdisAllocateNetBufferListContext
NdisFreeNetBufferListContext
NET_BUFFER_LIST_CONTEXT_DATA_START
NET_BUFFER_LIST_CONTEXT_DATA_SIZE
发送网络数据
下图演示了一个基本的发送操作,该操作涉及协议驱动程序、NDIS 和微型端口驱动程序:
协议驱动程序调用 NdisSendNetBufferLists 函数以发送绑定上的 NET_BUFFER_LIST 结构。 NDIS 调用微型端口驱动程序的 MiniportSendNetBufferLists 函数,将NET_BUFFER_LIST结构转发到基础微型端口驱动程序。
所有基于NET_BUFFER的发送操作都是异步的。 完成时,微型端口驱动程序使用适当的状态代码调用 NdisMSendNetBufferListsComplete 函数。 每个NET_BUFFER_LIST结构的发送可以单独完成。 每次微型端口驱动程序调用 NdisMSendNetBufferListsComplete 时,NDIS 都会调用协议驱动程序的 ProtocolSendNetBufferListsComplete 函数。
一旦 NDIS 调用协议驱动程序的 ProtocolSendNetBufferListsComplete 函数,协议驱动程序就可以回收NET_BUFFER_LIST结构以及所有相关结构和数据的所有权。
微型端口驱动程序或 NDIS 可以按任意顺序返回 NET_BUFFER_LIST 结构。 协议驱动程序保证未修改附加到每个 NET_BUFFER_LIST 结构的NET_BUFFER结构列表。
任何 NDIS 驱动程序都可以在NET_BUFFER_LIST结构中分离NET_BUFFER结构。 任何 NDIS 驱动程序还可以在NET_BUFFER结构中分离 MDL。 但是,驱动程序必须始终返回具有原始形式的NET_BUFFER结构和 MDL 的NET_BUFFER_LIST结构。 例如,中间驱动程序可以将NET_BUFFER_LIST分成两个新的NET_BUFFER_LIST结构,并将部分原始数据传递给下一个驱动程序。 但是,当中间驱动程序完成原始NET_BUFFER_LIST它必须返回包含原始NET_BUFFER结构和 MDL 的完整NET_BUFFER_LIST。
协议驱动程序将 NET_BUFFER_LIST 结构中的 SourceHandle 成员设置为 NDIS 在调用 NdisOpenAdapterEx 函数时提供的 NdisBindingHandle。 NDIS 使用 SourceHandle 成员将NET_BUFFER_LIST结构返回到发送NET_BUFFER_LIST结构的协议驱动程序。
中间驱动程序还会将 NET_BUFFER_LIST 结构中的 SourceHandle 成员设置为 NDIS 在调用 NdisOpenAdapterEx 时提供的 NdisBindingHandle 值。 如果中间驱动程序转发发送请求,则驱动程序必须在写入 SourceHandle 成员之前保存过度驱动程序提供的 SourceHandle 值。 当 NDIS 将转发NET_BUFFER_LIST结构返回到中间驱动程序时,中间驱动程序必须还原它保存的 SourceHandle 。
接收网络数据
下图演示了一个基本的接收操作,该操作涉及微型端口驱动程序、NDIS 和协议驱动程序。
微型端口驱动程序调用 NdisMIndicateReceiveNetBufferLists 函数,以指示更高级别驱动程序 NET_BUFFER 结构。 每个NET_BUFFER结构通常应附加到单独的 NET_BUFFER_LIST 结构。 这样,协议驱动程序就可以创建NET_BUFFER_LIST结构的原始列表的子集,并将其转发到不同的客户端。 某些驱动程序(例如本机 IEEE 802.11 微型端口驱动程序)可能会将多个NET_BUFFER结构附加到NET_BUFFER_LIST结构。
链接所有NET_BUFFER_LIST结构后,微型端口驱动程序会将指向列表中第一个NET_BUFFER_LIST结构的指针传递给 NdisMIndicateReceiveNetBufferLists 函数。 NDIS 检查NET_BUFFER_LIST结构,并调用与NET_BUFFER_LIST结构关联的每个协议驱动程序的 ProtocolReceiveNetBufferLists 函数。 NDIS 传递列表的子集,该子集仅包含与每个协议驱动程序的正确绑定关联的NET_BUFFER_LIST结构。 NDIS 将 NET_BUFFER_LIST 结构中指定的 NetBufferListFrameType 值与每个协议驱动程序注册的帧类型匹配。
如果设置了传递给协议驱动程序的 ProtocolReceiveNetBufferLists 函数的 ReceiveFlags 参数中的NDIS_RECEIVE_FLAGS_RESOURCES标志,则 NDIS 会在 ProtocolReceiveNetBufferLists 调用返回后立即重新获得NET_BUFFER_LIST结构的所有权。
注意 如果设置了NDIS_RECEIVE_FLAGS_RESOURCES标志,则协议驱动程序必须在链接列表中保留原始NET_BUFFER_LIST结构集。 例如,设置此标志时,驱动程序可能会处理这些结构,并指示它们一次上堆栈一个,但在函数返回之前,它必须还原原始链接列表。
如果未设置传递给协议驱动程序 ProtocolReceiveNetBufferLists 函数的 ReceiveFlags 参数中的NDIS_RECEIVE_FLAGS_RESOURCES标志,则协议驱动程序可以保留NET_BUFFER_LIST结构的所有权。 在这种情况下,协议驱动程序必须通过调用 NdisReturnNetBufferLists 函数返回NET_BUFFER_LIST结构。
如果微型端口驱动程序在接收资源上运行不足,则可以在调用 NdisMIndicateReceiveNetBufferLists 时在 ReceiveFlags 参数中设置NDIS_RECEIVE_FLAGS_RESOURCES标志。 在这种情况下,驱动程序可以在 NdisMIndicateReceiveNetBufferLists 返回后立即回收所有指示NET_BUFFER_LIST结构和嵌入NET_BUFFER结构的所有权。 指示设置了NDIS_RECEIVE_FLAGS_RESOURCES标志的NET_BUFFER结构会强制协议驱动程序复制数据,因此应避免这样做。 微型端口驱动程序应检测即将耗尽接收资源的时间,并采取任何必要的步骤来避免这种情况。
NDIS 在协议驱动程序调用 NdisReturnNetBufferLists 后,调用微型端口驱动程序的 MiniportReturnNetBufferLists 函数。
注意 如果微型端口驱动程序指示NET_BUFFER_LIST结构并设置了NDIS_RECEIVE_FLAGS_RESOURCES标志,这并不意味着 NDIS 会向具有相同状态的协议驱动程序指示NET_BUFFER_LIST结构。 例如,NDIS 可以复制设置了 NDIS_RECEIVE_FLAGS_RESOURCES 标志的NET_BUFFER_LIST结构,并在清除标志的情况下指示复制到协议驱动程序。
NDIS 可以任意顺序 和 任意组合将NET_BUFFER_LIST结构返回到微型端口驱动程序。 也就是说,通过调用 MiniportReturnNetBufferLists 函数返回给微型端口驱动程序的NET_BUFFER_LIST结构的链接列表可以具有以前对 NdisMIndicateReceiveNetBufferLists 的不同调用NET_BUFFER_LIST结构。
微型端口驱动程序应将 NET_BUFFER_LIST 结构中的 SourceHandle 成员设置为 NDIS 在 MiniportInitializeEx 函数中提供给微型端口驱动程序的 MiniportAdapterHandle。 筛选器驱动程序必须将筛选器驱动程序源自的每个NET_BUFFER_LIST结构的 SourceHandle 成员设置为筛选器的 NdisFilterHandle ,NDIS 在 FilterAttach 函数中提供给筛选器驱动程序。 筛选器驱动程序不得修改任何非筛选器驱动程序产生的NET_BUFFER_LIST结构中的 SourceHandle 成员。
中间驱动程序还将 NET_BUFFER_LIST 结构中的 SourceHandle 成员设置为 MiniportAdapterHandle 值,NDIS 在 MiniportInitializeEx 函数中提供给中间驱动程序。 如果中间驱动程序转发接收指示,则驱动程序必须在写入 SourceHandle 成员之前保存基础驱动程序提供的 SourceHandle 值。 当 NDIS 将转发NET_BUFFER_LIST结构返回到中间驱动程序时,中间驱动程序必须还原它保存的 SourceHandle 。
收发网络数据包的代码
下面是实际使用的一个发送数据包的例子:
PNET_BUFFER_LIST CreateBroadCast(
IN NDIS_HANDLE FilterHandle,
IN UCHAR Buffer[MAX_BOASTCAST]
)
{
PUCHAR pData = NULL;
PUCHAR pMdlData = NULL;
PMDL pMdl = NULL;
PNET_BUFFER_LIST pNetBufferList = NULL;
// 分配一个内存
pData = (PUCHAR)NdisAllocateMemoryWithTagPriority(
FilterHandle,
MAX_BOASTCAST,
ALLOC_TAG,
LowPoolPriority);
// 分配MDL
pMdl = NdisAllocateMdl(FilterHandle, pData, FILTER_ALLOC_TAG);
// 获取MDL的地址
pMdlData = (PUCHAR)MmGetSystemAddressForMdlSafe(pMdl, LowPoolPriority);
// 将数据复制到MDL
NdisMoveMemory(pMdlData, &Buffer[0], MAX_BOASTCAST);
// 创建一个NetBufferList
pNetBufferList = NdisAllocateNetBufferAndNetBufferList(
pFilter->SendNetBufferListPool,
0,
0, // back fill size
pMdl,
0, // Data offset
MAX_BOASTCAST);
pNetBufferList->SourceHandle = pFilter->FilterHandle;
return pNetBufferList;
}
以及接收数据包的例子:
ULONG AnalysisNetBuffer(
IN NDIS_HANDLE pFilter,
IN PNET_BUFFER_LIST NetBufferLists
)
{
PUCHAR pData = NULL;
PNET_BUFFER_LIST pCurrNbl = NetBufferLists;
PNET_BUFFER pCurrBuff = NULL;
PNET_BUFFER pNextBuff = NULL;
PMDL pMdl = NULL;
ULONG ulOffset = 0;
int nDataLen = 0;
ULONG ulError = FC_GIGE_SUCCESS;
while (pCurrNbl)
{
pCurrBuff = NET_BUFFER_LIST_FIRST_NB(pCurrNbl);
while(pCurrBuff)
{
ulOffset = NET_BUFFER_DATA_OFFSET(pCurrBuff);
pMdl = NET_BUFFER_FIRST_MDL(pCurrBuff);
nDataLen = NET_BUFFER_DATA_LENGTH(pCurrBuff);
if(pMdl == NULL || nDataLen == 0) continue;
pData = (UCHAR*)MmGetSystemAddressForMdlSafe(pMdl, NormalPagePriority);
pNextBuff = NET_BUFFER_NEXT_NB(pCurrBuff);
if(NULL == pNextBuff)
{
// 获取真正的数据地址,此处的pData指向的就是数据地址
pData = pData + ulOffset;
nDataLen = nDataLen - ulOffset;
}
else
{
;
}
pCurrBuff = pNextBuff;
}
pCurrNbl = NET_BUFFER_LIST_NEXT_NBL(pCurrNbl);
}
return ulError;
}