文章目录
- NDIS协议驱动开发指南
-
- [1. 技术概览](#1. 技术概览)
- [2. NDIS协议驱动](#2. NDIS协议驱动)
-
- [2.1 BindAdapterHandlerEx](#2.1 BindAdapterHandlerEx)
- [2.2 SendNetBufferListsCompleteHandler](#2.2 SendNetBufferListsCompleteHandler)
- [2.3 ReceiveNetBufferListsHandler](#2.3 ReceiveNetBufferListsHandler)
- [2.4 ProtocolNetPnpEvent](#2.4 ProtocolNetPnpEvent)
- [3. NET_BUFFER_LIST](#3. NET_BUFFER_LIST)
- [4. ndisprot实例](#4. ndisprot实例)
- [5. 总结](#5. 总结)
NDIS协议驱动开发指南
我们知道,在以太网中所有的数据包都是通过以太网帧来发送的;但是在网络上面的应用程序如果需要通过网络数据包交互,就需要依赖网络协议来保障通信。平时我们用的最多的协议就是TCPIP协议。
其实在Windows中,我们可以注册自己的协议,开发自己的协议解析和封装驱动,实现以太网帧的通信,这就是本文的NDIS协议驱动。
以太网的以太包格式都是固定的,格式如下:
6字节 | 6字节 | 2字节 | 其他长度 |
---|---|---|---|
源MAC | 目的MAC | 类型 | 数据部分 |
NDIS协议驱动就是针对以太包的协议封装和解析过程,对上层提供一个稳定的数据包通信的协议,对下层提供一个可以供以太网发送的以太数据包。本文我们来看一下NDIS协议驱动的开发原理。
1. 技术概览
在Windows下面,网络栈的基本架构如下:
对于NDSI提供了三种功能(能力)的驱动:
- 上层的协议驱动。
- 中层的过滤驱动。
- 下层的小端口驱动。
由于NDIS早期并没有直接提供过滤层的基本框架,因此对于早期(XP系统下面)版本如果需要对NDIS层的数据包进行过滤,需要在中间层实现协议驱动和小端口驱动:
- 对上层,创建小端口驱动来和上层协议层通信(主要过滤数据包的发送)。
- 对下层,创建协议驱动来和小端口驱动通信(主要过滤数据包的接收)。
对于协议层驱动,实现比较简单,只需要设置和处理好NDIS相关的协议层回调函数即可,下面我们看一下协议层驱动的具体实现。
2. NDIS协议驱动
上面我们知道NDIS协议驱动主要处理NDIS的相关协议回调例程,向NDIS注册回调例程的函数为NdisRegisterProtocolDriver
,该例程声明如下:
c
NDIS_STATUS NdisRegisterProtocolDriver(
NDIS_HANDLE ProtocolDriverContext,
PNDIS_PROTOCOL_DRIVER_CHARACTERISTICS ProtocolCharacteristics,
PNDIS_HANDLE NdisProtocolHandle
);
其中NDIS_PROTOCOL_DRIVER_CHARACTERISTICS
就是协议驱动的回调函数结构体,该结构体如下:
c
typedef struct _NDIS_PROTOCOL_DRIVER_CHARACTERISTICS {
NDIS_OBJECT_HEADER Header;
UCHAR MajorNdisVersion;
UCHAR MinorNdisVersion;
UCHAR MajorDriverVersion;
UCHAR MinorDriverVersion;
ULONG Flags;
NDIS_STRING Name;
SET_OPTIONS_HANDLER SetOptionsHandler;
BIND_HANDLER_EX BindAdapterHandlerEx;
UNBIND_HANDLER_EX UnbindAdapterHandlerEx;
OPEN_ADAPTER_COMPLETE_HANDLER_EX OpenAdapterCompleteHandlerEx;
CLOSE_ADAPTER_COMPLETE_HANDLER_EX CloseAdapterCompleteHandlerEx;
NET_PNP_EVENT_HANDLER NetPnPEventHandler;
UNINSTALL_PROTOCOL_HANDLER UninstallHandler;
OID_REQUEST_COMPLETE_HANDLER OidRequestCompleteHandler;
STATUS_HANDLER_EX StatusHandlerEx;
RECEIVE_NET_BUFFER_LISTS_HANDLER ReceiveNetBufferListsHandler;
SEND_NET_BUFFER_LISTS_COMPLETE_HANDLER SendNetBufferListsCompleteHandler;
DIRECT_OID_REQUEST_COMPLETE_HANDLER DirectOidRequestCompleteHandler;
} NDIS_PROTOCOL_DRIVER_CHARACTERISTICS, *PNDIS_PROTOCOL_DRIVER_CHARACTERISTICS;
在上述结构体中间,对于一个简要的协议驱动,只需要实现部分主要的回调函数即可,包括:
BindAdapterHandlerEx
:绑定回调函数,当小端口驱动和协议驱动进行绑定的时候调用该函数通知协议驱动。UnbindAdapterHandlerEx
:解除绑定的回调函数,和BindAdapterHandlerEx
相反。OpenAdapterCompleteHandlerEx
:当使用NdisOpenAdapterEx
绑定小端口驱动完成的时候被调用(相当IRP的完成例程)。CloseAdapterCompleteHandlerEx
:当使用NdisCloseAdapterEx
解除协议驱动和小端口驱动完成的时候被调用。OidRequestCompleteHandler
:NdisOidRequest
请求完成的时候被调用的函数。SendNetBufferListsCompleteHandler
:表示使用NdisSendNetBufferLists
发送完成数据包之后被调用的回调函数。ReceiveNetBufferListsHandler
:当小端口驱动接收到数据的时候就会通过该回调函数通知协议驱动数据包的到来。
2.1 BindAdapterHandlerEx
协议驱动是对网络数据包的封装,当将网络数据包按照协议封装为以太网数据包之后,就需要通过网卡发送出去,那么协议驱动就需要和网卡驱动进行关联(协议驱动的数据包知道如何发送给网卡驱动)。
有两种情况需要进行协议的绑定:
- 当协议驱动使用
NdisRegisterProtocolDriver
注册驱动的时候,NDIS框架就会遍历当前系统所有的小端口驱动,对每个小端口驱动调用BindAdapterHandlerEx
回调函数。 - 当有新的网卡设备插入并启动的时候(
IRP_MN_START
),就会对该小端口驱动遍历所有的协议驱动,然后调用其BindAdapterHandlerEx
回调函数进行绑定。
BindAdapterHandlerEx
只是绑定的回调函数,该函数声明如下:
c
PROTOCOL_BIND_ADAPTER_EX ProtocolBindAdapterEx;
NDIS_STATUS ProtocolBindAdapterEx(
NDIS_HANDLE ProtocolDriverContext,
NDIS_HANDLE BindContext,
PNDIS_BIND_PARAMETERS BindParameters
)
{...}
该函数只是将小端口驱动和协议驱动的信息当作回调函数的参数传递过来,小端口驱动的信息通过PNDIS_BIND_PARAMETERS
进行描述。真实的绑定是通过NdisOpenAdapterEx
函数来完成的,该函数如下:
c
NDIS_STATUS NdisOpenAdapterEx(
NDIS_HANDLE NdisProtocolHandle,
NDIS_HANDLE ProtocolBindingContext,
PNDIS_OPEN_PARAMETERS OpenParameters,
NDIS_HANDLE BindContext,
PNDIS_HANDLE NdisBindingHandle
);
NdisOpenAdapterEx
其实是建立小端口和协议驱动的桥梁,大致如下:
这样小端口的数据可以在NDIS框架中通过NDIS_OPEN_BLOCK
回调给协议驱动,协议驱动也可以通过NDIS_OPEN_BLOCK
调用小端口驱动。
2.2 SendNetBufferListsCompleteHandler
在协议驱动中,我们通过NdisSendNetBufferLists
将以太包发送数据,该函数声明如下:
c
void NdisSendNetBufferLists(
NDIS_HANDLE NdisBindingHandle,
__drv_aliasesMem PNET_BUFFER_LIST NetBufferLists,
NDIS_PORT_NUMBER PortNumber,
ULONG SendFlags
);
NET_BUFFER_LIST
描述着我们需要发送的数据包集合,当小端口驱动将数据包发送成功之后,就会调用NdisMSendNetBufferListsComplete
来通知协议驱动数据包被发送完成,该函数如下:
c
void NdisMSendNetBufferListsComplete(
NDIS_HANDLE MiniportAdapterHandle,
PNET_BUFFER_LIST NetBufferList,
ULONG SendCompleteFlags
);
NdisMSendNetBufferListsComplete
完成回调的函数就是SendNetBufferListsCompleteHandler
,该函数如下:
c
PROTOCOL_SEND_NET_BUFFER_LISTS_COMPLETE ProtocolSendNetBufferListsComplete;
void ProtocolSendNetBufferListsComplete(
NDIS_HANDLE ProtocolBindingContext,
PNET_BUFFER_LIST NetBufferList,
ULONG SendCompleteFlags
)
{...}
SendNetBufferListsCompleteHandler
这个函数将会重新获取NdisSendNetBufferLists
发送的数据包,在该函数中可以释放发送分配的NET_BUFFER_LIST
内存。
不过这里需要注意的是NET_BUFFER_LIST
是一个链表结构,在底层可能会被断链发送;因此SendNetBufferListsCompleteHandler
这个函数中的NET_BUFFER_LIST
可能是NdisSendNetBufferLists
中的子链。
2.3 ReceiveNetBufferListsHandler
当我们的网卡接收到数据的时候,就会通过硬件方式(例如中断等)通知数据包到来,然后小端口驱动通过NdisMIndicateReceiveNetBufferLists
将数据包传送给协议层驱动进行协议解析和数据包的传递,该函数如下:
c
void NdisMIndicateReceiveNetBufferLists(
NDIS_HANDLE MiniportAdapterHandle,
PNET_BUFFER_LIST NetBufferList,
NDIS_PORT_NUMBER PortNumber,
ULONG NumberOfNetBufferLists,
ULONG ReceiveFlags
);
在NdisMIndicateReceiveNetBufferLists
内部就会调用ReceiveNetBufferListsHandler
将接收到的网络数据包通过NET_BUFFER_LIST
进行传递,该函数声明如下:
c
PROTOCOL_RECEIVE_NET_BUFFER_LISTS ProtocolReceiveNetBufferLists;
void ProtocolReceiveNetBufferLists(
NDIS_HANDLE ProtocolBindingContext,
PNET_BUFFER_LIST NetBufferLists,
NDIS_PORT_NUMBER PortNumber,
ULONG NumberOfNetBufferLists,
ULONG ReceiveFlags
)
{...}
在该函数中NetBufferLists
表示数据包,通过解析该数据包我们就可以进行TCPIP网络栈的协议解析了。
2.4 ProtocolNetPnpEvent
改例程是NDIS框架对于网络PNP事件响应的回调函数,该函数声明如下:
c
PROTOCOL_NET_PNP_EVENT ProtocolNetPnpEvent;
NDIS_STATUS ProtocolNetPnpEvent(
NDIS_HANDLE ProtocolBindingContext,
PNET_PNP_EVENT_NOTIFICATION NetPnPEventNotification
)
{...}
NET_PNP_EVENT_NOTIFICATION
描述了一个PNP事件的信息,该结构如下:
c
typedef struct _NET_PNP_EVENT_NOTIFICATION {
NDIS_OBJECT_HEADER Header;
NDIS_PORT_NUMBER PortNumber;
NET_PNP_EVENT NetPnPEvent;
ULONG Flags;
NDIS_NIC_SWITCH_ID SwitchId;
NDIS_NIC_SWITCH_VPORT_ID VPortId;
} NET_PNP_EVENT_NOTIFICATION, *PNET_PNP_EVENT_NOTIFICATION;
typedef struct _NET_PNP_EVENT {
NET_PNP_EVENT_CODE NetEvent;
PVOID Buffer;
ULONG BufferLength;
ULONG_PTR NdisReserved[4];
ULONG_PTR TransportReserved[4];
ULONG_PTR TdiReserved[4];
ULONG_PTR TdiClientReserved[4];
} NET_PNP_EVENT, *PNET_PNP_EVENT;
NET_PNP_EVENT_CODE
描述网络PNP事件的类型,有如下:
c
typedef enum _NET_PNP_EVENT_CODE
{
NetEventSetPower,
NetEventQueryPower,
NetEventQueryRemoveDevice,
NetEventCancelRemoveDevice,
NetEventReconfigure,
NetEventBindList,
NetEventBindsComplete,
NetEventPnPCapabilities,
NetEventPause,
NetEventRestart,
NetEventPortActivation,
NetEventPortDeactivation,
NetEventIMReEnableDevice,
NetEventNDKEnable,
NetEventNDKDisable,
NetEventFilterPreDetach,
NetEventBindFailed,
NetEventSwitchActivate,
NetEventAllowBindsAbove,
NetEventInhibitBindsAbove,
NetEventAllowStart,
NetEventRequirePause,
NetEventUploadGftFlowEntries,
NetEventMaximum
} NET_PNP_EVENT_CODE, *PNET_PNP_EVENT_CODE;
NET_PNP_EVENT_CODE
的具体值,参见MSDN(例如NetEventSetPower
类型Buffer
表示了NDIS_DEVICE_POWER_STATE
结构,描述电源状态)。
3. NET_BUFFER_LIST
在NDIS中,网络数据包通过NET_BUFFER_LIST
来进行抽象,这个结构表示着网络数据包的集合;该数据结构如下:
NET_BUFFER_LIST
是一个NET_BUFFER_LIST
的链表集合;单个NET_BUFFER_LIST
是NET_BUFFER
的集合,NET_BUFFER
表示一个数据包,该结构如下:
NET_BUFFER
其实就是使用MDL来描述数据包的真实类容,因此对于NET_BUFFER_LIST
的全部结构可以描述为如下:
对于NET_BUFFER_LIST
和NET_BUFFER
提供了如下宏来操作该结构的成员:
c
#define NET_BUFFER_LIST_NEXT_NBL(_NBL) ((_NBL)->Next)
#define NET_BUFFER_LIST_FIRST_NB(_NBL) ((_NBL)->FirstNetBuffer)
#define NET_BUFFER_NEXT_NB(_NB) ((_NB)->Next)
#define NET_BUFFER_FIRST_MDL(_NB) ((_NB)->MdlChain)
#define NET_BUFFER_DATA_LENGTH(_NB) ((_NB)->DataLength)
#define NET_BUFFER_DATA_OFFSET(_NB) ((_NB)->DataOffset)
#define NET_BUFFER_CURRENT_MDL(_NB) ((_NB)->CurrentMdl)
#define NET_BUFFER_CURRENT_MDL_OFFSET(_NB) ((_NB)->CurrentMdlOffset)
我们需要对接收或者发送的数据包进行处理,都是解析NET_BUFFER_LIST
的过程。
4. ndisprot实例
对于NDIS协议驱动,WDK提供了一个示例ndisprot,该示例展示了协议驱动的工作原理,该实例提供如下功能:
- ndisprot驱动可以绑定到网卡上面。
- 通过
NdisprotReceiveNetBufferLists
接收底层的网络数据包,并将其放入队列中。 - 用户层程序可以通过
ReadFile
读取协议驱动的网络数据包。 - 用户层程序可以通过
WriteFile
往网络协议驱动写入数据包,协议驱动通过NdisSendNetBufferLists
将数据包发送到底层小端口驱动。
该协议否是可以支持网络通信呢?本人没有进行实验验证,但是从原理上来说是可行的,只是它是一个面向非连接,并且没有校验的原始通信手段的协议。
对于该驱动我们可以简单的使用如下方式手动安装和验证:
5. 总结
对于NDIS协议层驱动平时我们的使用场景不多,我们也没有能力(也没必要)设计一个完整的网络协议驱动。但是协议驱动是我们后面NDIS过滤驱动的基础,NDIS过滤驱动可以帮助我们获取本机接收到的网络帧数据包,并且对以太帧数据包进行过滤(例如ARP数据包等)。
因此NDIS协议驱动还是非常值得我们去学习的,它是NDIS过滤驱动的基础,也是以太帧数据包过滤的重要手段。