NDIS中间层驱动的开发在Win7系统上和Windows XP系统上有差别。
我把NDIS中间层的讨论分成2块。 windows 7系统和Windows XP系统。
(一)在 Windows XP系统上进行开发
平时很多朋友包括我在内,我们都在XP系统上使用NDIS5.1的框架来进行程序开发。我们都使用
Microsoft WDK提供的 NDIS 的 Passthru例子,在这个例子上做进一步的修改,来达到我们的目地。
在Passthru工程的 DriverEntry函数里面,我们都看见如下的代码:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
NDIS_STATUS Status;
NDIS_PROTOCOL_CHARACTERISTICS PChars;
NDIS_MINIPORT_CHARACTERISTICS MChars;
PNDIS_CONFIGURATION_PARAMETER Param;
NDIS_STRING Name;
NDIS_HANDLE WrapperHandle;
UNICODE_STRING nameString, linkString;
UINT FuncIndex;
PDEVICE_OBJECT MyDeviceObject;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
Status = NDIS_STATUS_SUCCESS;
//申请自旋锁 以到达资源共享的同步访问
NdisAllocateSpinLock(&GlobalLock);
//调用NdisMInitializeWrapper函数来保存在NdisWrapperHandle返回的句柄
NdisMInitializeWrapper(&NdisWrapperHandle, DriverObject, RegistryPath, NULL);
do
{
//调用NdisMInitializeWrapper函数来保存在WrapperHandle返回的句柄
NdisMInitializeWrapper(&WrapperHandle, DriverObject, RegistryPath, NULL);
//对于MiniportCharacteristics组件,如果驱动程序不用导出MiniportXxx这样的函数,则必须赋值为NULL。
//如果要导出任何新版本的V4.0或V5.0的MiniportXxx函数,那么中间层驱动程序的主版本必须是V4.0,并且提供4.0或5.0版本的MiniportCharacteristics组件.
NdisZeroMemory(&MChars, sizeof(NDIS_MINIPORT_CHARACTERISTICS));
MChars.MajorNdisVersion = PASSTHRU_MAJOR_NDIS_VERSION;
MChars.MinorNdisVersion = PASSTHRU_MINOR_NDIS_VERSION;
/***************************************************
下面开始注册中间层驱动程序的 MiniportXxx函数
***************************************************/
MChars.HaltHandler = MPHalt;
MChars.InitializeHandler = MPInitialize;
MChars.QueryInformationHandler = MPQueryInformation;
MChars.SetInformationHandler = MPSetInformation;
MChars.ResetHandler = MPReset;
MChars.SendHandler = NULL;
MChars.SendPacketsHandler = MPSendPackets;
MChars.TransferDataHandler = MPTransferData;
MChars.ReturnPacketHandler = MPReturnPacket;
MChars.CheckForHangHandler = NULL;
#ifdef NDIS51_MINIPORT
MChars.CancelSendPacketsHandler = MPCancelSendPackets;
MChars.PnPEventNotifyHandler = MPDevicePnPEvent;
MChars.AdapterShutdownHandler = MPAdapterShutdown;
#endif // NDIS51_MINIPORT
/*
传递上一步保存的句柄,并调用NdisIMRegisterLayeredMiniport函数来注册驱动程序的MiniportXxx系列函数。其中句柄NdisWrapperHandle是由先前的NdisMInitializeWrapper函数返回的。
当驱动程序调用NdisIMInitializeDeviceInstance函数,请求中间层驱动程序的MiniportInitialize函数对虚拟NIC进行初始化时,需要把句柄NdisWrapperHandle传入NDIS
当中间层驱动程序成功地绑定到一个或者多个NIC驱动程序上的时候, 或者是绑定到一个非NIC驱动程序上的时候也会调用NdisIMInitializeDeviceInstance函数。这样做使得中间层驱动程序可以初始化Miniport组件来接受虚拟NIC上的I/O请求
*/
Status = NdisIMRegisterLayeredMiniport(NdisWrapperHandle, &MChars, sizeof(MChars), &DriverHandle);
if (Status != NDIS_STATUS_SUCCESS)
{
break;
}
#ifndef WIN9X
NdisMRegisterUnloadHandler(NdisWrapperHandle, PtUnload);
#endif
//中间层驱动程序要做的事情:
//初始化NDIS_PROTOCOL_CHARACTERISTICS类型的结构. NDIS不再支持3.0版本的协议驱动.
//能够使用4.0或5.0版本的ProtocolCharacteristic结构体,协议驱动必须支持PNP即插即用功能.
NdisZeroMemory(&PChars, sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
PChars.MajorNdisVersion = PASSTHRU_PROT_MAJOR_NDIS_VERSION;
PChars.MinorNdisVersion = PASSTHRU_PROT_MINOR_NDIS_VERSION;
NdisInitUnicodeString(&Name, L"Passthru");
PChars.Name = Name;
/*******************************************************
下面开始注册中间层驱动程序的 ProtocolXxx函数
这里主要是注册---下边界面向无连接的中间层驱动程序的ProtocolXxx函数
*******************************************************/
PChars.OpenAdapterCompleteHandler = PtOpenAdapterComplete;
PChars.CloseAdapterCompleteHandler = PtCloseAdapterComplete;
PChars.SendCompleteHandler = PtSendComplete;
PChars.TransferDataCompleteHandler = PtTransferDataComplete;
PChars.ResetCompleteHandler = PtResetComplete;
PChars.RequestCompleteHandler = PtRequestComplete;
PChars.ReceiveHandler = PtReceive;
PChars.ReceivePacketHandler = PtReceivePacket;
PChars.ReceiveCompleteHandler = PtReceiveComplete;
PChars.StatusHandler = PtStatus;
PChars.StatusCompleteHandler = PtStatusComplete;
PChars.BindAdapterHandler = PtBindAdapter;
PChars.UnbindAdapterHandler = PtUnbindAdapter;
PChars.UnloadHandler = PtUnloadProtocol;
PChars.PnPEventHandler= PtPNPHandler;
/*
如果驱动程序随后要绑定到底层的NDIS驱动程序上,则调用NdisRegisterProtocol函数来注册驱动程序的ProtocolXxx函数。
全局变量ProtHandle是在NDIS协议驱动里面的NdisRegisterProtocol函数里面初始化的,然后中间层驱动必须保存protHandle的值,并在将来NDIS中间层驱动程序的协议部分的函数调用中作为输入参数来传递.
*/
NdisRegisterProtocol(&Status, &ProtHandle,&PChars,
sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
if (Status != NDIS_STATUS_SUCCESS)
{
NdisIMDeregisterLayeredMiniport(DriverHandle);
break;
}
//如果驱动程序导出了MiniportXxx和ProtocolXxx这一些列的函数,那么就调用NdisIMAssociateMiniport函数向NDIS通告有关驱动程序的微端口低边界和协议高边界信息
NdisIMAssociateMiniport(DriverHandle, ProtHandle);
}
while (FALSE);
//----------------- 创建设备对象与符号连接----------------------------
RtlInitUnicodeString(&nameString, L"\\Device\\MyPassthru" );
RtlInitUnicodeString(&linkString, L"\\??\\MyPassthru");
for(FuncIndex = 0;
FuncIndex <=IRP_MJ_MAXIMUM_FUNCTION;
FuncIndex++)
{
MajorFunction[FuncIndex] = NULL;
}
MajorFunction[IRP_MJ_CREATE] = MydrvDispatch;
MajorFunction[IRP_MJ_CLOSE] = MydrvDispatch;
MajorFunction[IRP_MJ_DEVICE_CONTROL] = MydrvDispatchIoctl;
Status = NdisMRegisterDevice(
WrapperHandle,
&nameString,
&linkString,
MajorFunction,
&MyDeviceObject,
&NdisDeviceHandle
);
if(Status != STATUS_SUCCESS)
{
DbgPrint("NdisMRegisterDevice failed!\n");
}
return(Status);
}
然后,我们就开始实现相应的回调函数。 然而为了收、发网络数据包,对它们进行控制,我们基本上只会去修改以下的回调函数:
下面我把相关的函数的功能给罗列出来。
/***************************************************************
MPSend函数的功能是: 发送单个数据包。
若中间层驱动不支持MiniportSendPackets,那么MPSend函数必须实现。
参数说明:
MiniportAdapterContext 它是适配器。
PacketArray 它是包描述符指针。
Flags 它未被使用。
***************************************************************/
NDIS_STATUS
MPSend(
IN NDIS_HANDLE MiniportAdapterContext,
IN PNDIS_PACKET Packet,
IN UINT Flags
)
/***************************************************************
MPSendPackets函数的功能是:
用于指定网络上传输数据包的包描述符指针数组。该函数可以用来发送多个数据包。而不是只发送单个数据包。除非中间层驱动程序绑定到低层WAN NIC驱动程序上,并提供MiniportWanSend函数,否则驱动程序应提供对MPSendPackets函数的支持, 而不是对MPSend函数的支持。
参数说明:
MiniportAdapterContext 它是适配器。
PacketArray 它是包描述符数组。
NumberOfPackets 它是PacketArray的长度。
每个数据包发送完成后无论是否成功都要调用 NdisMSendComplete函数。让上层的协议驱动能够收到SendCompleteHandler来判断数据包的完成状态。
***************************************************************/
VOID
MPSendPackets(
IN NDIS_HANDLE MiniportAdapterContext,
IN PPNDIS_PACKET PacketArray,
IN UINT NumberOfPackets
)
/***************************************************************
MPTransferData函数的功能:
该函数用于传输在前视缓冲区中没有指示的接收数据包的剩余部分。前视缓冲区由
中间层驱动程序传递给 NdisMXxxIndicateReceive函数。这个被指示的数据包可以是
中间层驱动程序的ProtocolReceive函数或者是ProtocolReceivePackets处理程序接收的
转换数据包。
如果中间层驱动程序通过调用NdisMXxxIndicateReceive函数向上层驱动程序指示接收
数据包,那么MPTransferData函数必须提供。
如果中间层通过调用驱动程序总是NdisMIndicateReceive函数向上层驱动程序指示接收
数据包,那么MPTransferData函数可以不必提供。即 NdisMIndicateReceivePacket指示
接收数据包,那么传递给ProtocoltReceive函数 的前视缓冲区将总是获得完整的数据包。
****************************************************************/
NDIS_STATUS
MPTransferData(
OUT PNDIS_PACKET Packet, //目的数据包
OUT PUINT BytesTransferred,//复制了多少数据
IN NDIS_HANDLE MiniportAdapterContext,//适配器结构体
IN NDIS_HANDLE MiniportReceiveContext,//上下文
IN UINT ByteOffset,//指向拷贝数据包的偏移
IN UINT BytesToTransfer//实际传输了多少字节的数据
)
/***************************************************************
PtTransferDataComplete函数的功能:
它是MPTransferData()的异步完成函数。
当ProtocolReceive函数需要调用NdisTransferData函数,那么我们必须实现
PtTransferDataComplete的代码。
当NdisTransferData调用之后,并且返回值是NDIS_STATUS_PENDING,在数据包传输完成了以后,会去调用PtTransferDataComplete函数用来表明一个数据包的传输完成。这时就会得到一个完整的数据包。
**************************************************************/
VOID
PtTransferDataComplete(
IN NDIS_HANDLE ProtocolBindingContext,
IN PNDIS_PACKET Packet,
IN NDIS_STATUS Status,
IN UINT BytesTransferred
)
/***************************************************************
PtReceive函数的功能是:
该函数以指向包含网络接收数据的前视缓冲区的指针为参数被调用执行。如果这个前视缓冲区没有包含完整的网络数据包,那么ProtocolReceive函数将以包描述符作为参数,调用NdisTransferData函数。
当NdisTransferData调用之后,数据包传输完成了以后,会去调用
ProtocolTransferDataComplete函数以用来表明一个数据包的传输完成。这时就会得到一个完整的数据包。
如果低层驱动程序调用了 NdisMIndicateReceivePacket指示接收数据包,那么传递给PtReceive函数的前视缓冲区将总是获得完整的数据包。
如果中间层驱动不需要完整的数据包时,可以调用NdisMXxxIndicateReceice函数向上通知,这样就省略了获取完整数据包的过程。
前视缓冲区:考虑数据包的接收,如果我们只需要看见数据内容的前几个字节(如TCP头)就可以决定这个包是否是本协议所需要处理的,那么显然下层驱动就没有必要提交整个数据包,只提供一个包开始的几个字节就可以了。
参数说明:
ProtocolBindingContext 在绑定是得到的绑定句柄,即它是在NdisOpenAdapter被调用时设置的。
MacReceiveContext 它指向一个不透明的环境变量,由底层NIC驱动传入,它用此句柄与从网络上收到的Packet关联。当调用NdisGetReceivePacket函数取得低层驱动上面的包描述符时,需要用到这个参数。当NDIS协议驱动的ProtocolReceive函数的实现中,可能要调用NdisTransferData函数,那么也会用到这个参数。
HeaderBuffer 它是一个以太网帧,就是以太网的包头。它是个虚拟的地址,只能在当前函数中有效, 它不能被存储而作为全局变量来使用。
HeaderBufferSize 它是包头的长度,它的值一般为14
LookAheadBuffer 它是前视缓冲区指针。
LookAheadBufferSize LookAheadBuffer的大小。
PacketSize 它是完整的数据包长度(不包括包头)。
当PtReceive函数被调用了以后,系统将会去调用完成函数PtReceiveComplete
如果传递给PtReceive的数据是通过NdisMXxxIndicateReceive进行指示的,那么传递给PtReceive的前视缓冲区的长度不会超过用OID_GEN_CURRENT_LOOKAHEAD调用NdisRequest返回的值。
如果PtReceive的调用执行在NdisMIndicateReceivePacket之前进行,那么底层驱动程序把包数组中的一个或者多个包状态设置为 NDIS_STATUS_RESOURCES,那么前视缓冲区的大小总是等于整个网络数据包的大小。所以中间层驱动程序可以不必调用NdisTransferData函数。
***************************************************************/
NDIS_STATUS
PtReceive(
IN NDIS_HANDLE ProtocolBindingContext,
IN NDIS_HANDLE MacReceiveContext,
IN PVOID HeaderBuffer,
IN UINT HeaderBufferSize,
IN PVOID LookAheadBuffer,
IN UINT LookAheadBufferSize,
IN UINT PacketSize
)
/**************************************************************
PtReceivePacket函数的功能是:
这是一个可选函数。 如果中间层驱动程序所基于的NIC驱动程序指示的是数据包描述符指针数组,或者调用NdisMIndicateReceivePacket函数指示接收带外数据,那么驱动程序应该提供PtReceivePacket函数。
如果开发者不能够确定中间层驱动程序的执行环境,也应该提供该函数,因为在能够产生多包指示的底层NIC驱动程序上,中间层驱动程序将获得更好的性能。
**************************************************************/
INT
PtReceivePacket(
IN NDIS_HANDLE ProtocolBindingContext,
IN PNDIS_PACKET Packet
)
我们可以在这几个回调函数里面来处理、过滤、分析 捕捉到的数据包。防火墙软件一般也都是在这几个回调函数里面进行编程。
其它的回调函数的代码,我们基本上不用去修改。
NDIS Passthru的例子很多,在网络上搜索一下,就有很多源代码例子,也有很多相关的技术说明。
下面,我想重点的说明在Windows 7系统下,(当然是Win7 32/64位系统)如何做NDIS中间层驱动程序的开发,代码和之前老的Windows操作系统提供的Passthru例子的区别。
我把NDIS中间层的讨论分成2块。 windows 7系统和Windows XP系统。
(一)在 Windows XP系统上进行开发
平时很多朋友包括我在内,我们都在XP系统上使用NDIS5.1的框架来进行程序开发。我们都使用
Microsoft WDK提供的 NDIS 的 Passthru例子,在这个例子上做进一步的修改,来达到我们的目地。
在Passthru工程的 DriverEntry函数里面,我们都看见如下的代码:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
NDIS_STATUS Status;
NDIS_PROTOCOL_CHARACTERISTICS PChars;
NDIS_MINIPORT_CHARACTERISTICS MChars;
PNDIS_CONFIGURATION_PARAMETER Param;
NDIS_STRING Name;
NDIS_HANDLE WrapperHandle;
UNICODE_STRING nameString, linkString;
UINT FuncIndex;
PDEVICE_OBJECT MyDeviceObject;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
Status = NDIS_STATUS_SUCCESS;
//申请自旋锁 以到达资源共享的同步访问
NdisAllocateSpinLock(&GlobalLock);
//调用NdisMInitializeWrapper函数来保存在NdisWrapperHandle返回的句柄
NdisMInitializeWrapper(&NdisWrapperHandle, DriverObject, RegistryPath, NULL);
do
{
//调用NdisMInitializeWrapper函数来保存在WrapperHandle返回的句柄
NdisMInitializeWrapper(&WrapperHandle, DriverObject, RegistryPath, NULL);
//对于MiniportCharacteristics组件,如果驱动程序不用导出MiniportXxx这样的函数,则必须赋值为NULL。
//如果要导出任何新版本的V4.0或V5.0的MiniportXxx函数,那么中间层驱动程序的主版本必须是V4.0,并且提供4.0或5.0版本的MiniportCharacteristics组件.
NdisZeroMemory(&MChars, sizeof(NDIS_MINIPORT_CHARACTERISTICS));
MChars.MajorNdisVersion = PASSTHRU_MAJOR_NDIS_VERSION;
MChars.MinorNdisVersion = PASSTHRU_MINOR_NDIS_VERSION;
/***************************************************
下面开始注册中间层驱动程序的 MiniportXxx函数
***************************************************/
MChars.HaltHandler = MPHalt;
MChars.InitializeHandler = MPInitialize;
MChars.QueryInformationHandler = MPQueryInformation;
MChars.SetInformationHandler = MPSetInformation;
MChars.ResetHandler = MPReset;
MChars.SendHandler = NULL;
MChars.SendPacketsHandler = MPSendPackets;
MChars.TransferDataHandler = MPTransferData;
MChars.ReturnPacketHandler = MPReturnPacket;
MChars.CheckForHangHandler = NULL;
#ifdef NDIS51_MINIPORT
MChars.CancelSendPacketsHandler = MPCancelSendPackets;
MChars.PnPEventNotifyHandler = MPDevicePnPEvent;
MChars.AdapterShutdownHandler = MPAdapterShutdown;
#endif // NDIS51_MINIPORT
/*
传递上一步保存的句柄,并调用NdisIMRegisterLayeredMiniport函数来注册驱动程序的MiniportXxx系列函数。其中句柄NdisWrapperHandle是由先前的NdisMInitializeWrapper函数返回的。
当驱动程序调用NdisIMInitializeDeviceInstance函数,请求中间层驱动程序的MiniportInitialize函数对虚拟NIC进行初始化时,需要把句柄NdisWrapperHandle传入NDIS
当中间层驱动程序成功地绑定到一个或者多个NIC驱动程序上的时候, 或者是绑定到一个非NIC驱动程序上的时候也会调用NdisIMInitializeDeviceInstance函数。这样做使得中间层驱动程序可以初始化Miniport组件来接受虚拟NIC上的I/O请求
*/
Status = NdisIMRegisterLayeredMiniport(NdisWrapperHandle, &MChars, sizeof(MChars), &DriverHandle);
if (Status != NDIS_STATUS_SUCCESS)
{
break;
}
#ifndef WIN9X
NdisMRegisterUnloadHandler(NdisWrapperHandle, PtUnload);
#endif
//中间层驱动程序要做的事情:
//初始化NDIS_PROTOCOL_CHARACTERISTICS类型的结构. NDIS不再支持3.0版本的协议驱动.
//能够使用4.0或5.0版本的ProtocolCharacteristic结构体,协议驱动必须支持PNP即插即用功能.
NdisZeroMemory(&PChars, sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
PChars.MajorNdisVersion = PASSTHRU_PROT_MAJOR_NDIS_VERSION;
PChars.MinorNdisVersion = PASSTHRU_PROT_MINOR_NDIS_VERSION;
NdisInitUnicodeString(&Name, L"Passthru");
PChars.Name = Name;
/*******************************************************
下面开始注册中间层驱动程序的 ProtocolXxx函数
这里主要是注册---下边界面向无连接的中间层驱动程序的ProtocolXxx函数
*******************************************************/
PChars.OpenAdapterCompleteHandler = PtOpenAdapterComplete;
PChars.CloseAdapterCompleteHandler = PtCloseAdapterComplete;
PChars.SendCompleteHandler = PtSendComplete;
PChars.TransferDataCompleteHandler = PtTransferDataComplete;
PChars.ResetCompleteHandler = PtResetComplete;
PChars.RequestCompleteHandler = PtRequestComplete;
PChars.ReceiveHandler = PtReceive;
PChars.ReceivePacketHandler = PtReceivePacket;
PChars.ReceiveCompleteHandler = PtReceiveComplete;
PChars.StatusHandler = PtStatus;
PChars.StatusCompleteHandler = PtStatusComplete;
PChars.BindAdapterHandler = PtBindAdapter;
PChars.UnbindAdapterHandler = PtUnbindAdapter;
PChars.UnloadHandler = PtUnloadProtocol;
PChars.PnPEventHandler= PtPNPHandler;
/*
如果驱动程序随后要绑定到底层的NDIS驱动程序上,则调用NdisRegisterProtocol函数来注册驱动程序的ProtocolXxx函数。
全局变量ProtHandle是在NDIS协议驱动里面的NdisRegisterProtocol函数里面初始化的,然后中间层驱动必须保存protHandle的值,并在将来NDIS中间层驱动程序的协议部分的函数调用中作为输入参数来传递.
*/
NdisRegisterProtocol(&Status, &ProtHandle,&PChars,
sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
if (Status != NDIS_STATUS_SUCCESS)
{
NdisIMDeregisterLayeredMiniport(DriverHandle);
break;
}
//如果驱动程序导出了MiniportXxx和ProtocolXxx这一些列的函数,那么就调用NdisIMAssociateMiniport函数向NDIS通告有关驱动程序的微端口低边界和协议高边界信息
NdisIMAssociateMiniport(DriverHandle, ProtHandle);
}
while (FALSE);
//----------------- 创建设备对象与符号连接----------------------------
RtlInitUnicodeString(&nameString, L"\\Device\\MyPassthru" );
RtlInitUnicodeString(&linkString, L"\\??\\MyPassthru");
for(FuncIndex = 0;
FuncIndex <=IRP_MJ_MAXIMUM_FUNCTION;
FuncIndex++)
{
MajorFunction[FuncIndex] = NULL;
}
MajorFunction[IRP_MJ_CREATE] = MydrvDispatch;
MajorFunction[IRP_MJ_CLOSE] = MydrvDispatch;
MajorFunction[IRP_MJ_DEVICE_CONTROL] = MydrvDispatchIoctl;
Status = NdisMRegisterDevice(
WrapperHandle,
&nameString,
&linkString,
MajorFunction,
&MyDeviceObject,
&NdisDeviceHandle
);
if(Status != STATUS_SUCCESS)
{
DbgPrint("NdisMRegisterDevice failed!\n");
}
return(Status);
}
然后,我们就开始实现相应的回调函数。 然而为了收、发网络数据包,对它们进行控制,我们基本上只会去修改以下的回调函数:
下面我把相关的函数的功能给罗列出来。
/***************************************************************
MPSend函数的功能是: 发送单个数据包。
若中间层驱动不支持MiniportSendPackets,那么MPSend函数必须实现。
参数说明:
MiniportAdapterContext 它是适配器。
PacketArray 它是包描述符指针。
Flags 它未被使用。
***************************************************************/
NDIS_STATUS
MPSend(
IN NDIS_HANDLE MiniportAdapterContext,
IN PNDIS_PACKET Packet,
IN UINT Flags
)
/***************************************************************
MPSendPackets函数的功能是:
用于指定网络上传输数据包的包描述符指针数组。该函数可以用来发送多个数据包。而不是只发送单个数据包。除非中间层驱动程序绑定到低层WAN NIC驱动程序上,并提供MiniportWanSend函数,否则驱动程序应提供对MPSendPackets函数的支持, 而不是对MPSend函数的支持。
参数说明:
MiniportAdapterContext 它是适配器。
PacketArray 它是包描述符数组。
NumberOfPackets 它是PacketArray的长度。
每个数据包发送完成后无论是否成功都要调用 NdisMSendComplete函数。让上层的协议驱动能够收到SendCompleteHandler来判断数据包的完成状态。
***************************************************************/
VOID
MPSendPackets(
IN NDIS_HANDLE MiniportAdapterContext,
IN PPNDIS_PACKET PacketArray,
IN UINT NumberOfPackets
)
/***************************************************************
MPTransferData函数的功能:
该函数用于传输在前视缓冲区中没有指示的接收数据包的剩余部分。前视缓冲区由
中间层驱动程序传递给 NdisMXxxIndicateReceive函数。这个被指示的数据包可以是
中间层驱动程序的ProtocolReceive函数或者是ProtocolReceivePackets处理程序接收的
转换数据包。
如果中间层驱动程序通过调用NdisMXxxIndicateReceive函数向上层驱动程序指示接收
数据包,那么MPTransferData函数必须提供。
如果中间层通过调用驱动程序总是NdisMIndicateReceive函数向上层驱动程序指示接收
数据包,那么MPTransferData函数可以不必提供。即 NdisMIndicateReceivePacket指示
接收数据包,那么传递给ProtocoltReceive函数 的前视缓冲区将总是获得完整的数据包。
****************************************************************/
NDIS_STATUS
MPTransferData(
OUT PNDIS_PACKET Packet, //目的数据包
OUT PUINT BytesTransferred,//复制了多少数据
IN NDIS_HANDLE MiniportAdapterContext,//适配器结构体
IN NDIS_HANDLE MiniportReceiveContext,//上下文
IN UINT ByteOffset,//指向拷贝数据包的偏移
IN UINT BytesToTransfer//实际传输了多少字节的数据
)
/***************************************************************
PtTransferDataComplete函数的功能:
它是MPTransferData()的异步完成函数。
当ProtocolReceive函数需要调用NdisTransferData函数,那么我们必须实现
PtTransferDataComplete的代码。
当NdisTransferData调用之后,并且返回值是NDIS_STATUS_PENDING,在数据包传输完成了以后,会去调用PtTransferDataComplete函数用来表明一个数据包的传输完成。这时就会得到一个完整的数据包。
**************************************************************/
VOID
PtTransferDataComplete(
IN NDIS_HANDLE ProtocolBindingContext,
IN PNDIS_PACKET Packet,
IN NDIS_STATUS Status,
IN UINT BytesTransferred
)
/***************************************************************
PtReceive函数的功能是:
该函数以指向包含网络接收数据的前视缓冲区的指针为参数被调用执行。如果这个前视缓冲区没有包含完整的网络数据包,那么ProtocolReceive函数将以包描述符作为参数,调用NdisTransferData函数。
当NdisTransferData调用之后,数据包传输完成了以后,会去调用
ProtocolTransferDataComplete函数以用来表明一个数据包的传输完成。这时就会得到一个完整的数据包。
如果低层驱动程序调用了 NdisMIndicateReceivePacket指示接收数据包,那么传递给PtReceive函数的前视缓冲区将总是获得完整的数据包。
如果中间层驱动不需要完整的数据包时,可以调用NdisMXxxIndicateReceice函数向上通知,这样就省略了获取完整数据包的过程。
前视缓冲区:考虑数据包的接收,如果我们只需要看见数据内容的前几个字节(如TCP头)就可以决定这个包是否是本协议所需要处理的,那么显然下层驱动就没有必要提交整个数据包,只提供一个包开始的几个字节就可以了。
参数说明:
ProtocolBindingContext 在绑定是得到的绑定句柄,即它是在NdisOpenAdapter被调用时设置的。
MacReceiveContext 它指向一个不透明的环境变量,由底层NIC驱动传入,它用此句柄与从网络上收到的Packet关联。当调用NdisGetReceivePacket函数取得低层驱动上面的包描述符时,需要用到这个参数。当NDIS协议驱动的ProtocolReceive函数的实现中,可能要调用NdisTransferData函数,那么也会用到这个参数。
HeaderBuffer 它是一个以太网帧,就是以太网的包头。它是个虚拟的地址,只能在当前函数中有效, 它不能被存储而作为全局变量来使用。
HeaderBufferSize 它是包头的长度,它的值一般为14
LookAheadBuffer 它是前视缓冲区指针。
LookAheadBufferSize LookAheadBuffer的大小。
PacketSize 它是完整的数据包长度(不包括包头)。
当PtReceive函数被调用了以后,系统将会去调用完成函数PtReceiveComplete
如果传递给PtReceive的数据是通过NdisMXxxIndicateReceive进行指示的,那么传递给PtReceive的前视缓冲区的长度不会超过用OID_GEN_CURRENT_LOOKAHEAD调用NdisRequest返回的值。
如果PtReceive的调用执行在NdisMIndicateReceivePacket之前进行,那么底层驱动程序把包数组中的一个或者多个包状态设置为 NDIS_STATUS_RESOURCES,那么前视缓冲区的大小总是等于整个网络数据包的大小。所以中间层驱动程序可以不必调用NdisTransferData函数。
***************************************************************/
NDIS_STATUS
PtReceive(
IN NDIS_HANDLE ProtocolBindingContext,
IN NDIS_HANDLE MacReceiveContext,
IN PVOID HeaderBuffer,
IN UINT HeaderBufferSize,
IN PVOID LookAheadBuffer,
IN UINT LookAheadBufferSize,
IN UINT PacketSize
)
/**************************************************************
PtReceivePacket函数的功能是:
这是一个可选函数。 如果中间层驱动程序所基于的NIC驱动程序指示的是数据包描述符指针数组,或者调用NdisMIndicateReceivePacket函数指示接收带外数据,那么驱动程序应该提供PtReceivePacket函数。
如果开发者不能够确定中间层驱动程序的执行环境,也应该提供该函数,因为在能够产生多包指示的底层NIC驱动程序上,中间层驱动程序将获得更好的性能。
**************************************************************/
INT
PtReceivePacket(
IN NDIS_HANDLE ProtocolBindingContext,
IN PNDIS_PACKET Packet
)
我们可以在这几个回调函数里面来处理、过滤、分析 捕捉到的数据包。防火墙软件一般也都是在这几个回调函数里面进行编程。
其它的回调函数的代码,我们基本上不用去修改。
NDIS Passthru的例子很多,在网络上搜索一下,就有很多源代码例子,也有很多相关的技术说明。
下面,我想重点的说明在Windows 7系统下,(当然是Win7 32/64位系统)如何做NDIS中间层驱动程序的开发,代码和之前老的Windows操作系统提供的Passthru例子的区别。