NDIS协议驱动
题外话
白天睡多了,晚上睡不着.晚上要是熬夜晚的话,白天又得睡,浪费时间,还不如把晚上这段时间利用起来.这几天在复习NDIS协议驱动,在此记录.
0x01 介绍
NDIS全称网卡驱动接口标准(Network Driver Interface Specification),是微软和一些网卡厂商共同定制的一组内核网络相关的函数接口集合.其中又分为NDIS协议驱动,中间层驱动IM(通常用来做过滤器,过滤网络流量,常用的应用为防火墙),以及小端口驱动NIC.这些微软都会在wdk中提供项目模板
协议驱动上层提供直接供应用层使用的Socket接口.下层通常绑定小端口.有时下层绑定中间层驱动.协议驱动可以通过绑定多个网卡以便于接受网卡的信息.所以协议能够获取到所有的发送到本机上的网络数据.但是一些数据是通过其他的协议驱动所发送,比如TCP等.这些网络数据我们自己编写的协议驱动是获取不到的.所以说协议驱动可以用来实现发包器,嗅探器.但却不适合用来实现防火墙.
0x02 代码解析
协议驱动和普通的wdk驱动一样,都有一个DriverEntry入口.
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath
)
/*++
Routine Description:
Called on loading. We create a device object to handle user-mode requests
on, and register ourselves as a protocol with NDIS.
Arguments:
pDriverObject - Pointer to driver object created by system.
pRegistryPath - Pointer to the Unicode name of the registry path
for this driver.
Return Value:
NT Status code
--*/
{
NDIS_PROTOCOL_CHARACTERISTICS protocolChar;
NTSTATUS status = STATUS_SUCCESS;
NDIS_STRING protoName = NDIS_STRING_CONST("NdisProt");
UNICODE_STRING ntDeviceName;
UNICODE_STRING win32DeviceName;
BOOLEAN fSymbolicLink = FALSE;
PDEVICE_OBJECT deviceObject = NULL;
UNREFERENCED_PARAMETER(pRegistryPath);
DEBUGP(DL_LOUD, ("DriverEntry\n"));
KdPrint(("-----------------------"));
Globals.pDriverObject = pDriverObject;
NPROT_INIT_EVENT(&Globals.BindsComplete);
do
{
//
// Create our device object using which an application can
// access NDIS devices.
//
RtlInitUnicodeString(&ntDeviceName, NT_DEVICE_NAME);
#ifdef WIN9X
status = IoCreateDeviceSecure(pDriverObject,
0,
&ntDeviceName,
FILE_DEVICE_NETWORK,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&SDDL_DEVOBJ_SYS_ALL_ADM_ALL,
NULL,
&deviceObject);
#else
status = IoCreateDevice(pDriverObject,
0,
&ntDeviceName,
FILE_DEVICE_NETWORK,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&deviceObject);
#endif
if (!NT_SUCCESS (status))
{
//
// Either not enough memory to create a deviceobject or another
// deviceobject with the same name exits. This could happen
// if you install another instance of this device.
//
break;
}
RtlInitUnicodeString(&win32DeviceName, DOS_DEVICE_NAME);
status = IoCreateSymbolicLink(&win32DeviceName, &ntDeviceName);
if (!NT_SUCCESS(status))
{
break;
}
fSymbolicLink = TRUE;
deviceObject->Flags |= DO_DIRECT_IO;
Globals.ControlDeviceObject = deviceObject;
NPROT_INIT_LIST_HEAD(&Globals.OpenList);
NPROT_INIT_LOCK(&Globals.GlobalLock);
//
// Initialize the protocol characterstic structure
//
NdisZeroMemory(&protocolChar,sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
protocolChar.MajorNdisVersion = 4;
protocolChar.MinorNdisVersion = 0;
protocolChar.Name = protoName;
protocolChar.OpenAdapterCompleteHandler = NdisProtOpenAdapterComplete;
protocolChar.CloseAdapterCompleteHandler = NdisProtCloseAdapterComplete;
protocolChar.SendCompleteHandler = NdisProtSendComplete;
protocolChar.TransferDataCompleteHandler = NdisProtTransferDataComplete;
protocolChar.ResetCompleteHandler = NdisProtResetComplete;
protocolChar.RequestCompleteHandler = NdisProtRequestComplete;
protocolChar.ReceiveHandler = NdisProtReceive;
protocolChar.ReceiveCompleteHandler = NdisProtReceiveComplete;
protocolChar.StatusHandler = NdisProtStatus;
protocolChar.StatusCompleteHandler = NdisProtStatusComplete;
protocolChar.BindAdapterHandler = NdisProtBindAdapter;
protocolChar.UnbindAdapterHandler = NdisProtUnbindAdapter;
protocolChar.UnloadHandler = NULL;
protocolChar.ReceivePacketHandler = NdisProtReceivePacket;
protocolChar.PnPEventHandler = NdisProtPnPEventHandler;
//
// Register as a protocol driver
//
NdisRegisterProtocol(
(PNDIS_STATUS)&status,
&Globals.NdisProtocolHandle,
&protocolChar,
sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
if (status != NDIS_STATUS_SUCCESS)
{
DEBUGP(DL_WARN, ("Failed to register protocol with NDIS\n"));
status = STATUS_UNSUCCESSFUL;
break;
}
#ifdef NDIS51
Globals.PartialCancelId = NdisGeneratePartialCancelId();
Globals.PartialCancelId <<= ((sizeof(PVOID) - 1) * 8);
DEBUGP(DL_LOUD, ("DriverEntry: CancelId %lx\n", Globals.PartialCancelId));
#endif
//
// Now set only the dispatch points we would like to handle.
// 仅用于控制设备
pDriverObject->MajorFunction[IRP_MJ_CREATE] = NdisProtOpen;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = NdisProtClose;
pDriverObject->MajorFunction[IRP_MJ_READ] = NdisProtRead;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = NdisProtWrite;
pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = NdisProtCleanup;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = NdisProtIoControl;
pDriverObject->DriverUnload = NdisProtUnload;
status = STATUS_SUCCESS;
}
while (FALSE);
if (!NT_SUCCESS(status))
{
if (deviceObject)
{
IoDeleteDevice(deviceObject);
Globals.ControlDeviceObject = NULL;
}
if (fSymbolicLink)
{
IoDeleteSymbolicLink(&win32DeviceName);
}
}
return status;
}
可以看到,在入口函数的最开始,我们创建了设备以及符号链接,方便与应用层进行通信以达到控制的目的.
随后就是NDIS所特有的一系列协议特征回调函数,之后调用NdisRegisterProtocol进行注册.之后Irp派遣函数则是为了能够控制.其中最重要的函数包括NdisProtBindAdapter与NdisProtReceive.NdisProtBindAdapter用来绑定网卡.而NdisProtReceive用来接收数据包.
一 NdisProtBindAdapter
在刨析绑定函数之前,需要了解一下何为打开上下文.打开也就是对网卡(专业术语称适配器),或者绑定网卡.
每绑定一个网卡,就会分配一个内存空间来对这次的绑定相关的一些信息进行保存.比如用于发送接收数据包的包池和缓冲池.还有用于记录未决读写请求的LIST_ENTRY结构等等.结构如下:
typedef struct _NDISPROT_OPEN_CONTEXT
{
LIST_ENTRY Link; // Link into global list
ULONG Flags; // State information
ULONG RefCount;
NPROT_LOCK Lock;
PFILE_OBJECT pFileObject; // Set on OPEN_DEVICE
NDIS_HANDLE BindingHandle;
NDIS_HANDLE SendPacketPool;
NDIS_HANDLE SendBufferPool;
NDIS_HANDLE RecvPacketPool;
NDIS_HANDLE RecvBufferPool;
ULONG MacOptions;
ULONG MaxFrameSize;
LIST_ENTRY PendedWrites; // pended Write IRPs
ULONG PendedSendCount;
LIST_ENTRY PendedReads; // pended Read IRPs
ULONG PendedReadCount;
LIST_ENTRY RecvPktQueue; // queued rcv packets
ULONG RecvPktCount;
NET_DEVICE_POWER_STATE PowerState;
NDIS_EVENT PoweredUpEvent; // signalled iff PowerState is D0
NDIS_STRING DeviceName; // used in NdisOpenAdapter
NDIS_STRING DeviceDescr; // friendly name
NDIS_STATUS BindStatus; // for Open/CloseAdapter
NPROT_EVENT BindEvent; // for Open/CloseAdapter
BOOLEAN bRunningOnWin9x;// TRUE if Win98/SE/ME, FALSE if NT
ULONG oc_sig; // Signature for sanity
UCHAR CurrentAddress[NPROT_MAC_ADDR_LEN];
PIRP StatusIndicationIrp;
} NDISPROT_OPEN_CONTEXT, *PNDISPROT_OPEN_CONTEXT;
这就是我们之前所说的打开上下文结构,这个结构非常重要.在我们的协议特征函数集中会频繁地出现.
现在我们来看看绑定函数都干了什么事.代码如下
VOID
NdisProtBindAdapter(
OUT PNDIS_STATUS pStatus,
IN NDIS_HANDLE BindContext,
IN PNDIS_STRING pDeviceName,
IN PVOID SystemSpecific1,
IN PVOID SystemSpecific2
)
/*++
Routine Description:
Protocol Bind Handler entry point called when NDIS wants us
to bind to an adapter. We go ahead and set up a binding.
An OPEN_CONTEXT structure is allocated to keep state about
this binding.
Arguments:
pStatus - place to return bind status
BindContext - handle to use with NdisCompleteBindAdapter
DeviceName - adapter to bind to
SystemSpecific1 - used to access protocol-specific registry
key for this binding
SystemSpecific2 - unused
Return Value:
None
--*/
{
PNDISPROT_OPEN_CONTEXT pOpenContext;
NDIS_STATUS Status, ConfigStatus;
NDIS_HANDLE ConfigHandle;
UNREFERENCED_PARAMETER(BindContext);
UNREFERENCED_PARAMETER(SystemSpecific2);
do
{
//
// Allocate our context for this open.
//
KdPrint(("-----%wZ", pDeviceName));
// 分配空间给每个打开上下文。所谓打开上下文就是每次绑定,
// 用户分配的一片空间,用来保存这次绑定相关的信息。这里
// 用宏NPROT_ALLOC_MEM分配内存是为了调试的方便。实际上本
// 质是用NdisAllocateMemoryWithTag分配空间。读者如果用
// ExAllocatePoolWithTag代替也是可行的。只是要注意必须是
// Nonpaged空间。
NPROT_ALLOC_MEM(pOpenContext, sizeof(NDISPROT_OPEN_CONTEXT));
if (pOpenContext == NULL)
{
Status = NDIS_STATUS_RESOURCES;
break;
}
// 内存清0。同样用宏。实际上用的NdisZeroMemory。
NPROT_ZERO_MEM(pOpenContext, sizeof(NDISPROT_OPEN_CONTEXT));
// 给这个空间写一个特征数据便于识别判错。
NPROT_SET_SIGNATURE(pOpenContext, oc);
// 初始化几个用到的数据成员。锁、读队列、写对队列、包队列
// 电源打开事件
NPROT_INIT_LOCK(&pOpenContext->Lock);
NPROT_INIT_LIST_HEAD(&pOpenContext->PendedReads);
NPROT_INIT_LIST_HEAD(&pOpenContext->PendedWrites);
NPROT_INIT_LIST_HEAD(&pOpenContext->RecvPktQueue);
NPROT_INIT_EVENT(&pOpenContext->PoweredUpEvent);
//
// Start off by assuming that the device below is powered up.
//
// 认为开始的时候电源是打开的。
NPROT_SIGNAL_EVENT(&pOpenContext->PoweredUpEvent);
//
// Determine the platform we are running on.
//
// 下面开始检测我们运行在什么平台。首先假定是Win9x.
// 但是为了去掉多余的部分,实际上我已经去掉了对Win9x
// 的支持。所以下面这一段已经没有意义了。但是下面的
// 代码依然有参考价值。实际上是在读取注册表的配置。
//pOpenContext->bRunningOnWin9x = TRUE;
//NdisOpenProtocolConfiguration(
// &ConfigStatus,
// &ConfigHandle,
// (PNDIS_STRING)SystemSpecific1);
//
//if (ConfigStatus == NDIS_STATUS_SUCCESS)
//{
// PNDIS_CONFIGURATION_PARAMETER pParameter;
// NDIS_STRING VersionKey = NDIS_STRING_CONST("Environment");
// NdisReadConfiguration(
// &ConfigStatus,
// &pParameter,
// ConfigHandle,
// &VersionKey,
// NdisParameterInteger);
//
// if ((ConfigStatus == NDIS_STATUS_SUCCESS) &&
// ((pParameter->ParameterType == NdisParameterInteger) ||
// (pParameter->ParameterType == NdisParameterHexInteger)))
// {
// pOpenContext->bRunningOnWin9x =
// (pParameter->ParameterData.IntegerData == NdisEnvironmentWindows);
// }
// NdisCloseConfiguration(ConfigHandle);
//}
//给打开上下文增加一个引用计数
NPROT_REF_OPEN(pOpenContext);
//
// Add it to the global list.
//
// 因为打开上下文已经被分配好。所以这里将这个打开上下文
// 保存到全局链表里以便日后检索。注意这个操作要加锁。实
// 际上这里用的就是读者前面学过的自旋锁。
NPROT_ACQUIRE_LOCK(&Globals.GlobalLock);
NPROT_INSERT_TAIL_LIST(&Globals.OpenList,
&pOpenContext->Link);
NPROT_RELEASE_LOCK(&Globals.GlobalLock);
// 正式的绑定过程。
Status = ndisprotCreateBinding(
pOpenContext,
(PUCHAR)pDeviceName->Buffer,
pDeviceName->Length);
if (Status != NDIS_STATUS_SUCCESS)
{
break;
}
}
while (FALSE);
*pStatus = Status;
return;
}
这段代码做的事情主要有如下几点:
1.申请打开上下文所使用的空间
2.初始化了一些打开上下文所需要使用的成员.有锁,读写队列,还有包队列
3.最后调用ndisprotCreateBinding.大部分绑定操作都在这个函数中.而这里只是做了一些预备工作.
需要注意的细节有:打开上下文创建的时候就需要增加引用计数.如果引用计数为0,系统会进行销毁.
还有就是对一些全局性的资源进行写入的时候要注意加锁.以防竞争问题.
类型为PNDIS_STRING的参数是我们要绑定的网卡的名称
再来看看ndisprotCreateBinding这个函数
NDIS_STATUS
ndisprotCreateBinding(
IN PNDISPROT_OPEN_CONTEXT pOpenContext,
__in_bcount(BindingInfoLength) IN PUCHAR pBindingInfo,
IN ULONG BindingInfoLength
)
{
NDIS_STATUS Status;
NDIS_STATUS OpenErrorCode;
NDIS_MEDIUM MediumArray[1] = {NdisMedium802_3};
UINT SelectedMediumIndex;
PNDISPROT_OPEN_CONTEXT pTmpOpenContext;
BOOLEAN fDoNotDisturb = FALSE;
BOOLEAN fOpenComplete = FALSE;
ULONG BytesProcessed;
ULONG GenericUlong = 0;
// 输出一句调试信息。
DEBUGP(DL_LOUD, ("CreateBinding: open %p/%x, device [%ws]\n",
pOpenContext, pOpenContext->Flags, pBindingInfo));
Status = NDIS_STATUS_SUCCESS;
do
{
// 检查看看是否已经绑定了这个网卡。如果已经绑定的话,就没有必
// 要再次绑定了,直接返回成功即可。请注意,ndisprotLookupDevice
// 会给这个打开上下文增加一个引用。
pTmpOpenContext = ndisprotLookupDevice(pBindingInfo, BindingInfoLength);
// 如果没有找到的话,就返回NULL了。
if (pTmpOpenContext != NULL)
{
DEBUGP(DL_WARN,
("CreateBinding: Binding to device %ws already exists on open %p\n",
pTmpOpenContext->DeviceName.Buffer, pTmpOpenContext));
// 减少这个打开上下文的一个引用。
NPROT_DEREF_OPEN(pTmpOpenContext);
Status = NDIS_STATUS_FAILURE;
break;
}
// 获得锁。为什么这里要用锁?这是因为只有对空闲的打开上下文
NPROT_ACQUIRE_LOCK(&pOpenContext->Lock);
// 通过标记来检查...如果绑定标志不是空闲状态,或者解除绑定信
// 息收到了解除绑定的要求,那就直接返回失败。
if (!NPROT_TEST_FLAGS(pOpenContext->Flags, NUIOO_BIND_FLAGS, NUIOO_BIND_IDLE) ||
NPROT_TEST_FLAGS(pOpenContext->Flags, NUIOO_UNBIND_FLAGS, NUIOO_UNBIND_RECEIVED))
{
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
Status = NDIS_STATUS_NOT_ACCEPTED;
// 设置了标记,表示
fDoNotDisturb = TRUE;
break;
}
// 设置标记,表示我们已经开始绑定了。别人勿操作它。
NPROT_SET_FLAGS(pOpenContext->Flags, NUIOO_BIND_FLAGS, NUIOO_BIND_OPENING);
// 释放锁。
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
// 分配名字。到这里开始绑定了。先分配设备名字符串。
NPROT_ALLOC_MEM(pOpenContext->DeviceName.Buffer, BindingInfoLength + sizeof(WCHAR));
if (pOpenContext->DeviceName.Buffer == NULL)
{
DEBUGP(DL_WARN, ("CreateBinding: failed to alloc device name buf (%d bytes)\n",
BindingInfoLength + sizeof(WCHAR)));
Status = NDIS_STATUS_RESOURCES;
break;
}
// 从pBindingInfo中把字符串拷贝出来。
NPROT_COPY_MEM(pOpenContext->DeviceName.Buffer, pBindingInfo, BindingInfoLength);
#pragma prefast(suppress: 12009, "DeviceName length will not cause overflow")
*(PWCHAR)((PUCHAR)pOpenContext->DeviceName.Buffer + BindingInfoLength) = L'\0';
NdisInitUnicodeString(&pOpenContext->DeviceName, pOpenContext->DeviceName.Buffer);
// 分配包池。用来做发送缓冲区,容纳将要发送出去的包。
NdisAllocatePacketPoolEx(
&Status,
&pOpenContext->SendPacketPool,
MIN_SEND_PACKET_POOL_SIZE,
MAX_SEND_PACKET_POOL_SIZE - MIN_SEND_PACKET_POOL_SIZE,
sizeof(NPROT_SEND_PACKET_RSVD));
if (Status != NDIS_STATUS_SUCCESS)
{
DEBUGP(DL_WARN, ("CreateBinding: failed to alloc"
" send packet pool: %x\n", Status));
break;
}
// 分配包池,用来容纳
NdisAllocatePacketPoolEx(
&Status,
&pOpenContext->RecvPacketPool,
MIN_RECV_PACKET_POOL_SIZE,
MAX_RECV_PACKET_POOL_SIZE - MIN_RECV_PACKET_POOL_SIZE,
sizeof(NPROT_RECV_PACKET_RSVD));
if (Status != NDIS_STATUS_SUCCESS)
{
DEBUGP(DL_WARN, ("CreateBinding: failed to alloc"
" recv packet pool: %x\n", Status));
break;
}
// 包池。用来做接收缓冲区。
NdisAllocateBufferPool(
&Status,
&pOpenContext->RecvBufferPool,
MAX_RECV_PACKET_POOL_SIZE);
if (Status != NDIS_STATUS_SUCCESS)
{
DEBUGP(DL_WARN, ("CreateBinding: failed to alloc"
" recv buffer pool: %x\n", Status));
break;
}
// 电源状态是打开着的。
pOpenContext->PowerState = NetDeviceStateD0;
// 初始化一个打开事件。(打开就是绑定!)
NPROT_INIT_EVENT(&pOpenContext->BindEvent);
NdisOpenAdapter(
&Status,
&OpenErrorCode,
&pOpenContext->BindingHandle, //重要,表示协议和网卡之间绑定关系的存在,发送数据的时候,以此判断发送到哪个网卡
&SelectedMediumIndex,
&MediumArray[0],
sizeof(MediumArray) / sizeof(NDIS_MEDIUM),
Globals.NdisProtocolHandle,
(NDIS_HANDLE)pOpenContext,
&pOpenContext->DeviceName,
0,
NULL);
// 等待请求完成。
if (Status == NDIS_STATUS_PENDING)
{
NPROT_WAIT_EVENT(&pOpenContext->BindEvent, 0);
Status = pOpenContext->BindStatus;
}
// 如果不成功
if (Status != NDIS_STATUS_SUCCESS)
{
DEBUGP(DL_WARN, ("CreateBinding: NdisOpenAdapter (%ws) failed: %x\n",
pOpenContext->DeviceName.Buffer, Status));
break;
}
// 记住我们已经成功的绑定了。但是我们还没有更新打开
// 状态。这是为了避免别的线程开始关闭这个绑定。
fOpenComplete = TRUE;
// 发请求,获得一个可阅读的名字。不过这并不是非
// 成功不可的。所以不检查返回值。
(VOID)NdisQueryAdapterInstanceName(
&pOpenContext->DeviceDescr,
pOpenContext->BindingHandle
);
// 获得下面网卡的Mac地址
Status = ndisprotDoRequest(
pOpenContext,
NdisRequestQueryInformation,
OID_802_3_CURRENT_ADDRESS,
&pOpenContext->CurrentAddress[0],
NPROT_MAC_ADDR_LEN,
&BytesProcessed
);
if (Status != NDIS_STATUS_SUCCESS)
{
DEBUGP(DL_WARN, ("CreateBinding: qry current address failed: %x\n",
Status));
break;
}
// 获得网卡选项
Status = ndisprotDoRequest(
pOpenContext,
NdisRequestQueryInformation,
OID_GEN_MAC_OPTIONS,
&pOpenContext->MacOptions,
sizeof(pOpenContext->MacOptions),
&BytesProcessed
);
if (Status != NDIS_STATUS_SUCCESS)
{
DEBUGP(DL_WARN, ("CreateBinding: qry MAC options failed: %x\n",
Status));
break;
}
// 获得最大帧长
Status = ndisprotDoRequest(
pOpenContext,
NdisRequestQueryInformation,
OID_GEN_MAXIMUM_FRAME_SIZE,
&pOpenContext->MaxFrameSize,
sizeof(pOpenContext->MaxFrameSize),
&BytesProcessed
);
if (Status != NDIS_STATUS_SUCCESS)
{
DEBUGP(DL_WARN, ("CreateBinding: qry max frame failed: %x\n",
Status));
break;
}
// 获得下层连接状态。
Status = ndisprotDoRequest(
pOpenContext,
NdisRequestQueryInformation,
OID_GEN_MEDIA_CONNECT_STATUS,
&GenericUlong,
sizeof(GenericUlong),
&BytesProcessed
);
if (Status != NDIS_STATUS_SUCCESS)
{
DEBUGP(DL_WARN, ("CreateBinding: qry media connect status failed: %x\n",
Status));
break;
}
if (GenericUlong == NdisMediaStateConnected)
{
NPROT_SET_FLAGS(pOpenContext->Flags, NUIOO_MEDIA_FLAGS, NUIOO_MEDIA_CONNECTED);
}
else
{
NPROT_SET_FLAGS(pOpenContext->Flags, NUIOO_MEDIA_FLAGS, NUIOO_MEDIA_DISCONNECTED);
}
// 设置标记
NPROT_ACQUIRE_LOCK(&pOpenContext->Lock);
NPROT_SET_FLAGS(pOpenContext->Flags, NUIOO_BIND_FLAGS, NUIOO_BIND_ACTIVE);
// 检测是否这时候出现了一个解除绑定请求
if (NPROT_TEST_FLAGS(pOpenContext->Flags, NUIOO_UNBIND_FLAGS, NUIOO_UNBIND_RECEIVED))
{
// 出现了则这次绑定失败
Status = NDIS_STATUS_FAILURE;
}
// 标记测试完之后就可以解锁了。
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
}
while (FALSE);
// 如果没有成功,而且
if ((Status != NDIS_STATUS_SUCCESS) && !fDoNotDisturb)
{
NPROT_ACQUIRE_LOCK(&pOpenContext->Lock);
// 如果是已经成功的绑定了
if (fOpenComplete)
{
// 如果已经绑定结束了,设置已经绑定标记。
NPROT_SET_FLAGS(pOpenContext->Flags, NUIOO_BIND_FLAGS, NUIOO_BIND_ACTIVE);
}
else if (NPROT_TEST_FLAGS(pOpenContext->Flags, NUIOO_BIND_FLAGS, NUIOO_BIND_OPENING))
{
// 如果是正在绑定过程中,设置绑定失败了。
NPROT_SET_FLAGS(pOpenContext->Flags, NUIOO_BIND_FLAGS, NUIOO_BIND_FAILED);
}
// 释放锁。
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
// 调用停止绑定函数。这里会释放所有资源。
ndisprotShutdownBinding(pOpenContext);
}
DEBUGP(DL_INFO, ("CreateBinding: OpenContext %p, Status %x\n",
pOpenContext, Status));
return (Status);
}
这个函数做的事情主要如下:
1.判断这个网卡是否已经被绑定.通过遍历打开上下文队列比较网卡的名称有无相同相同则表明已经被绑定.反之则不然.
2.判断上下文的标记.看看是否不是空闲状态,或者是收到解绑请求.如果是则直接返回
3.走到这一步满足了绑定的前提.所以设置上下文标记NUIOO_BIND_OPENING,表示我们正在绑定.并且从第二部开始还要加锁进行互斥,直至这一步完成之后解锁.防止其他线程重复操作.
4.对上下文中的设备名称进行复制
5.分配发送接收数据包包池.分配接收缓冲池,这里只有分配接收缓冲池.没有发送缓冲池.是因为既然是发送的数据,那么肯定已经有地方保存了.所以就不用再分配发送缓冲池.直接链接即可.
6.调用NdisOpenAdapter正式绑定网卡.如果返回未决(NDIS_STATUS_PENDING),则一直等待.直到绑定完成调用绑定完成函数NdisProtOpenAdapterComplete内部设置事件激活方可继续执行.
7.调用ndisprotDoRequest查询一些信息MAC等.方便进行后续发送接收.
8.设置NUIOO_BIND_ACTIVE表示网卡已经绑定,正在使用中
9.如果绑定失败,则调用ndisprotShutdownBinding释放资源
二 NdisProtReceive
NDIS_STATUS
NdisProtReceive(
IN NDIS_HANDLE ProtocolBindingContext,
IN NDIS_HANDLE MacReceiveContext,
__in_bcount(HeaderBufferSize) IN PVOID pHeaderBuffer,
IN UINT HeaderBufferSize,
__in_bcount(LookaheadBufferSize) IN PVOID pLookaheadBuffer,
IN UINT LookaheadBufferSize,
IN UINT PacketSize
)
{
PNDISPROT_OPEN_CONTEXT pOpenContext;
NDIS_STATUS Status;
PNDIS_PACKET pRcvPacket;
PUCHAR pRcvData;
UINT BytesTransferred;
PNDIS_BUFFER pOriginalNdisBuffer, pPartialNdisBuffer;
// 获得绑定句柄。
pOpenContext = (PNDISPROT_OPEN_CONTEXT)ProtocolBindingContext;
NPROT_STRUCT_ASSERT(pOpenContext, oc);
pRcvPacket = NULL;
pRcvData = NULL;
Status = NDIS_STATUS_SUCCESS;
do
{
// 如果头长度不是以太网包头的长度,则不接收这个包。
// 本协议只接收以太网包。
if (HeaderBufferSize != sizeof(NDISPROT_ETH_HEADER))
{
Status = NDIS_STATUS_NOT_ACCEPTED;
break;
}
// 这个比较比较奇怪。难道头长度是负数?
if ((PacketSize + HeaderBufferSize) < PacketSize)
{
Status = NDIS_STATUS_NOT_ACCEPTED;
break;
}
// 分配一个包。包括包描述符和缓冲描述符,以及内存
// 空间,一次性分配好。
pRcvPacket = ndisprotAllocateReceivePacket(
pOpenContext,
PacketSize + HeaderBufferSize,
&pRcvData
);
// 如果分配失败了,就不再接收这个包了。
if ((pRcvPacket == NULL) || (pRcvData == NULL))
{
Status = NDIS_STATUS_NOT_ACCEPTED;
break;
}
// 内存拷贝。先拷贝以太网包头。
NdisMoveMappedMemory(pRcvData, pHeaderBuffer, HeaderBufferSize);
// 检查前视区里是否包含了完整包的数据。
if (PacketSize == LookaheadBufferSize)
{
// 如果前视区已经包括了整个数据包,那么调用NdisCopyLookaheadData
// 就得到了完整的包,然后调用ndisprotQueueReceivePacket将这个包
// 插入队列即可。
NdisCopyLookaheadData(pRcvData+HeaderBufferSize,
pLookaheadBuffer,
LookaheadBufferSize,
pOpenContext->MacOptions);
ndisprotQueueReceivePacket(pOpenContext, pRcvPacket);
}
else
{
// 否则的话,需要分配一个新的缓冲描述符。请注意这个描述
// 符号对应的是从包缓冲区开始之后HeaderBufferSize个字节之
// 后处开始的空间(pRcvData + HeaderBufferSize)。
NdisAllocateBuffer(
&Status,
&pPartialNdisBuffer,
pOpenContext->RecvBufferPool,
pRcvData + HeaderBufferSize,
PacketSize);
if (Status == NDIS_STATUS_SUCCESS)
{
// 如果成功了,就把包上原有的缓冲解链。使原来的缓冲描述
// 符脱离包描述符。
NdisUnchainBufferAtFront(pRcvPacket, &pOriginalNdisBuffer);
// 现在把原来的包描述符保存在包描述符中(保留以备后用)
NPROT_RCV_PKT_TO_ORIGINAL_BUFFER(pRcvPacket) = pOriginalNdisBuffer;
// 然后把新的缓冲描述符连接到包上。
NdisChainBufferAtBack(pRcvPacket, pPartialNdisBuffer);
DEBUGP(DL_LOUD, ("Receive: setting up for TransferData:"
" Pkt %p, OriginalBuf %p, PartialBuf %p\n",
pRcvPacket, pOriginalNdisBuffer, pPartialNdisBuffer));
// 然后调用NdisTransferData来传输数据包剩余的部分。这个
// 调用完成之后,协议特征中的NdisProtTransferDataComplete
// 会被调用。
NdisTransferData(
&Status,
pOpenContext->BindingHandle,
MacReceiveContext,
0, // ByteOffset
PacketSize,
pRcvPacket,
&BytesTransferred);
}
else
{
// 如果失败了,就不会调用NdisTransferData。但是我们还是
// 要在NdisProtTransferDataComplete中做最后的处理。所以
// 自己填写BytesTransferred。
BytesTransferred = 0;
}
if (Status != NDIS_STATUS_PENDING)
{
// 如果前面就失败了,我们自己调用NdisProtTransferDataComplete。
NdisProtTransferDataComplete(
(NDIS_HANDLE)pOpenContext,
pRcvPacket,
Status,
BytesTransferred);
}
}
}
while (FALSE);
DEBUGP(DL_LOUD, ("Receive: Open %p, Pkt %p, Size %d\n",
pOpenContext, pRcvPacket, PacketSize));
return Status;
}
此函数用来接收数据包.函数参数pLookaheadBuffer代表的是数据包前视区,所以说这个函数接收到的数据包并不完整.需要后续调用NdisTransferData获取完整的数据包.函数流程如下:
1.判断数据包是否符合要求
2.分配包描述符和缓冲区描述符
3.拷贝以太网数据包包头
4,判断前视区是否包含完整包的数据.如果是,那么说明前视区已经包含了完整的数据包,拷贝数据到包中.随后将包插入队列.如果否,重新申请新的缓冲描述符,将原本的缓冲描述符解链,给包链接新的缓冲描述符.之后调用NdisTransferData获取数据包剩余的部分.NdisTransferData调用成功之后,NdisProtTransferDataComplete会被调用,如果NdisTransferData调用失败,就需要我们自己手动调用NdisProtTransferDataComplete,注意NdisTransferData并不会复制以太网包头.
//这个函数被调用意味着数据包传输完成
//这里得到了完整的数据包
VOID
NdisProtTransferDataComplete(
IN NDIS_HANDLE ProtocolBindingContext,
IN PNDIS_PACKET pNdisPacket,
IN NDIS_STATUS TransferStatus,
IN UINT BytesTransferred
)
{
PNDISPROT_OPEN_CONTEXT pOpenContext;
PNDIS_BUFFER pOriginalBuffer, pPartialBuffer;
UNREFERENCED_PARAMETER(BytesTransferred);
pOpenContext = (PNDISPROT_OPEN_CONTEXT)ProtocolBindingContext;
NPROT_STRUCT_ASSERT(pOpenContext, oc);
// 得到保存过的旧的缓冲描述符。要记得在传输之前,为了让传输
// 的内容正确的写到以太网包头后,我们分配了一个新的缓冲描述
// 符替换了旧的缓冲描述符。现在要恢复它了。
pOriginalBuffer = NPROT_RCV_PKT_TO_ORIGINAL_BUFFER(pNdisPacket);
if (pOriginalBuffer != NULL)
{
// 和前面的替换时的操作一样,先Unchain,然后再调用Chain。
// 调用之后已经恢复了使用旧的包描述符。
NdisUnchainBufferAtFront(pNdisPacket, &pPartialBuffer);
NdisChainBufferAtBack(pNdisPacket, pOriginalBuffer);
DEBUGP(DL_LOUD, ("TransferComp: Pkt %p, OrigBuf %p, PartialBuf %p\n",
pNdisPacket, pOriginalBuffer, pPartialBuffer));
ASSERT(pPartialBuffer != NULL);
// 那么那个新的包描述符已经没用了,调用NdisFreeBuffer释放它。
if (pPartialBuffer != NULL)
{
NdisFreeBuffer(pPartialBuffer);
}
}
if (TransferStatus == NDIS_STATUS_SUCCESS)
{
// 如果传输是成功的,将包保存到接收队列中。
ndisprotQueueReceivePacket(pOpenContext, pNdisPacket);
}
else
{
// 如果传输失败了,直接释放这个包。
ndisprotFreeReceivePacket(pOpenContext, pNdisPacket);
}
}
在调用NdisTransferData之后,NdisProtTransferDataComplete完成函数调用之时,数据包才完整地接收到.这个时候我们将包保存到接收队列中.如果传输时失败的,则直接释放这个包.
三 ndisprotDoRequest
NDIS_STATUS
ndisprotDoRequest(
IN PNDISPROT_OPEN_CONTEXT pOpenContext,
IN NDIS_REQUEST_TYPE RequestType,
IN NDIS_OID Oid,
IN PVOID InformationBuffer,
IN ULONG InformationBufferLength,
OUT PULONG pBytesProcessed
)
{
NDISPROT_REQUEST ReqContext;
PNDIS_REQUEST pNdisRequest = &ReqContext.Request;
NDIS_STATUS Status;
// 初始化一个事件。这个事件会在请求完成函数中被设置,
// 以便通知请求完成了。
NPROT_INIT_EVENT(&ReqContext.ReqEvent);
// 请求的类型。如果只是查询信息,只要用NdisRequestQueryInformation
// 就可以了。
pNdisRequest->RequestType = RequestType;
// 根据不同的请求类型,填写OID和输入输出缓冲区。
switch (RequestType)
{
case NdisRequestQueryInformation:
pNdisRequest->DATA.QUERY_INFORMATION.Oid = Oid;
pNdisRequest->DATA.QUERY_INFORMATION.InformationBuffer =
InformationBuffer;
pNdisRequest->DATA.QUERY_INFORMATION.InformationBufferLength =
InformationBufferLength;
break;
case NdisRequestSetInformation:
pNdisRequest->DATA.SET_INFORMATION.Oid = Oid;
pNdisRequest->DATA.SET_INFORMATION.InformationBuffer =
InformationBuffer;
pNdisRequest->DATA.SET_INFORMATION.InformationBufferLength =
InformationBufferLength;
break;
default:
NPROT_ASSERT(FALSE);
break;
}
// 发送请求
NdisRequest(&Status,
pOpenContext->BindingHandle,
pNdisRequest);
// 如果是未决,则等待事件。这个事件会在完成函数中设置。
if (Status == NDIS_STATUS_PENDING)
{
NPROT_WAIT_EVENT(&ReqContext.ReqEvent, 0);
Status = ReqContext.Status;
}
// 如果成功了的话...
if (Status == NDIS_STATUS_SUCCESS)
{
// 获得结果的长度。这个结果的长度是实际需要的长度。可
// 能比我们实际提供的长度要长。
*pBytesProcessed = (RequestType == NdisRequestQueryInformation)?
pNdisRequest->DATA.QUERY_INFORMATION.BytesWritten:
pNdisRequest->DATA.SET_INFORMATION.BytesRead;
// 如果结果长度比实际上我们提供的缓冲区要长,那么就简
// 单的设置为输入参数中缓冲区的最大长度。
if (*pBytesProcessed > InformationBufferLength)
{
*pBytesProcessed = InformationBufferLength;
}
}
return (Status);
}
这个函数还是挺简单的,通过设置NDIS_REQUEST结构,其中OID决定设置或者查询信息.通过调用NdisRequest进行请求发送.事件等待发送请求的完成,最终返回结果的长度.
NdisProtReceivePacket函数,最好是NdisProtReceivePacket和NdisProtReceive都提供
INT
NdisProtReceivePacket(
IN NDIS_HANDLE ProtocolBindingContext,
IN PNDIS_PACKET pNdisPacket
)
/*++
Routine Description:
Protocol entry point called by NDIS if the driver below
uses NDIS 4 style receive packet indications.
If the miniport allows us to hold on to this packet, we
use it as is, otherwise we make a copy.
Arguments:
ProtocolBindingContext - pointer to open context
pNdisPacket - the packet being indicated up.
Return Value:
None
--*/
{
PNDISPROT_OPEN_CONTEXT pOpenContext;
PNDIS_BUFFER pNdisBuffer;
UINT BufferLength;
PNDISPROT_ETH_HEADER pEthHeader;
PNDIS_PACKET pCopyPacket;
PUCHAR pCopyBuf;
UINT TotalPacketLength;
UINT BytesCopied;
INT RefCount = 0;
NDIS_STATUS Status;
pOpenContext = (PNDISPROT_OPEN_CONTEXT)ProtocolBindingContext;
NPROT_STRUCT_ASSERT(pOpenContext, oc);
#ifdef NDIS51
NdisGetFirstBufferFromPacketSafe(
pNdisPacket,
&pNdisBuffer,
&pEthHeader,
&BufferLength,
&TotalPacketLength,
NormalPagePriority);
if (pEthHeader == NULL)
{
//
// The system is low on resources. Set up to handle failure
// below.
//
BufferLength = 0;
}
#else
// 从包描述符中得到第一个缓冲描述符的相关信息。
NdisGetFirstBufferFromPacket(
pNdisPacket,
&pNdisBuffer, //数据包描述符中初始缓冲区描述符的地址
&pEthHeader, //初始缓冲区的va
&BufferLength, //初始缓冲区描述符映射的字节数
&TotalPacketLength);//所有缓冲区描述符映射的字节数
#endif
do
{
// 如果这个包的长度比以太网包头还要小,丢弃之。
if (BufferLength < sizeof(NDISPROT_ETH_HEADER))
{
DEBUGP(DL_WARN,
("ReceivePacket: Open %p, runt pkt %p, first buffer length %d\n",
pOpenContext, pNdisPacket, BufferLength));
Status = NDIS_STATUS_NOT_ACCEPTED;
break;
}
DEBUGP(DL_LOUD, ("ReceivePacket: Open %p, interesting pkt %p\n",
pOpenContext, pNdisPacket));
// 如果这个包有NDIS_STATUS_RESOURCES状态,则必须拷贝
// 而不能重用该包。当然这样就比较消耗时间和资源了。
if ((NDIS_GET_PACKET_STATUS(pNdisPacket) == NDIS_STATUS_RESOURCES) ||
pOpenContext->bRunningOnWin9x)
{
// 下面是分配一个包并拷贝其内容。读者可以参考前面讲过
// 的内容来理解。
pCopyPacket = ndisprotAllocateReceivePacket(
pOpenContext,
TotalPacketLength,
&pCopyBuf
);
if (pCopyPacket == NULL)
{
DEBUGP(DL_FATAL, ("ReceivePacket: Open %p, failed to"
" alloc copy, %d bytes\n", pOpenContext, TotalPacketLength));
break;
}
// 调用NdisCopyFromPacketToPacket来拷贝包。当然在拷贝之
// 前调用者必须确保目标包的缓冲区长度是足够的。
NdisCopyFromPacketToPacket(
pCopyPacket,
0,
TotalPacketLength,
pNdisPacket,
0,
&BytesCopied);
NPROT_ASSERT(BytesCopied == TotalPacketLength);
// 那么现在开始就用新的包了。
pNdisPacket = pCopyPacket;
}
else
{
// 返回值。返回值表示的是我们已经一次引用了这个包。
// 当处理完结的时候,我们就可以调用NdisReturnPackets
// 来要求下层驱动释放这个包了。本函数把RefCount当做
// 返回值。如果返回了0,那么下层驱动会认为我们不再
// 需要这个数据包。
RefCount = 1;
}
// 将数据包放入队列里。
ndisprotQueueReceivePacket(pOpenContext, pNdisPacket);
}
while (FALSE);
return (RefCount);
}
此函数返回值代表包的引用计数,当引用计数为0,包会被销毁.当一个包被处理完成再无用的时候,我们可以调用NdisReturnPackets来要求下层驱动释放这个包.这里需要注意的是我们需要判断接收的包是否有NDIS_STATUS_RESOURCES状态,如果有就必须重申请而不是重利用.最后和NdisProtReceive一样将包入队列.
四 派遣函数
NTSTATUS
NdisProtIoControl(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
/*++
Routine Description:
This is the dispatch routine for handling device ioctl requests.
Arguments:
pDeviceObject - Pointer to the device object.
pIrp - Pointer to the request packet.
Return Value:
Status is returned.
--*/
{
PIO_STACK_LOCATION pIrpSp;
ULONG FunctionCode;
NTSTATUS NtStatus;
NDIS_STATUS Status;
PNDISPROT_OPEN_CONTEXT pOpenContext;
ULONG BytesReturned;
USHORT EthType;
#if !DBG
UNREFERENCED_PARAMETER(pDeviceObject);
#endif
DEBUGP(DL_LOUD, ("IoControl: DevObj %p, Irp %p\n", pDeviceObject, pIrp));
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
FunctionCode = pIrpSp->Parameters.DeviceIoControl.IoControlCode;
pOpenContext = (PNDISPROT_OPEN_CONTEXT)pIrpSp->FileObject->FsContext;
BytesReturned = 0;
switch (FunctionCode)
{
case IOCTL_NDISPROT_BIND_WAIT:
// 所有的DeviceIoControl请求的都应该是用的缓冲方式。这里
// 只是确认一下。
NPROT_ASSERT((FunctionCode & 0x3) == METHOD_BUFFERED);
// 非常简单。等待一个全局事件。这个全局变量会在绑定完成
// 的时候被设置。如果等待到了或者超时了(5秒)则返回。
if (NPROT_WAIT_EVENT(&Globals.BindsComplete, 5000))
{
NtStatus = STATUS_SUCCESS;
}
else
{
NtStatus = STATUS_TIMEOUT;
}
DEBUGP(DL_INFO, ("IoControl: BindWait returning %x\n", NtStatus));
break;
case IOCTL_NDISPROT_QUERY_BINDING:
NPROT_ASSERT((FunctionCode & 0x3) == METHOD_BUFFERED);
Status = ndisprotQueryBinding(
pIrp->AssociatedIrp.SystemBuffer,
pIrpSp->Parameters.DeviceIoControl.InputBufferLength,
pIrpSp->Parameters.DeviceIoControl.OutputBufferLength,
&BytesReturned
);
NDIS_STATUS_TO_NT_STATUS(Status, &NtStatus);
DEBUGP(DL_LOUD, ("IoControl: QueryBinding returning %x\n", NtStatus));
break;
case IOCTL_NDISPROT_OPEN_DEVICE:
NPROT_ASSERT((FunctionCode & 0x3) == METHOD_BUFFERED);
if (pOpenContext != NULL)
{
NPROT_STRUCT_ASSERT(pOpenContext, oc);
DEBUGP(DL_WARN, ("IoControl: OPEN_DEVICE: FileObj %p already"
" associated with open %p\n", pIrpSp->FileObject, pOpenContext));
NtStatus = STATUS_DEVICE_BUSY;
break;
}
NtStatus = ndisprotOpenDevice(
pIrp->AssociatedIrp.SystemBuffer,
pIrpSp->Parameters.DeviceIoControl.InputBufferLength,
pIrpSp->FileObject,
&pOpenContext
);
if (NT_SUCCESS(NtStatus))
{
DEBUGP(DL_VERY_LOUD, ("IoControl OPEN_DEVICE: Open %p <-> FileObject %p\n",
pOpenContext, pIrpSp->FileObject));
}
break;
case IOCTL_NDISPROT_QUERY_OID_VALUE:
NPROT_ASSERT((FunctionCode & 0x3) == METHOD_BUFFERED);
if (pOpenContext != NULL)
{
Status = ndisprotQueryOidValue(
pOpenContext,
pIrp->AssociatedIrp.SystemBuffer,
pIrpSp->Parameters.DeviceIoControl.OutputBufferLength,
&BytesReturned
);
NDIS_STATUS_TO_NT_STATUS(Status, &NtStatus);
}
else
{
NtStatus = STATUS_DEVICE_NOT_CONNECTED;
}
break;
case IOCTL_NDISPROT_SET_OID_VALUE:
NPROT_ASSERT((FunctionCode & 0x3) == METHOD_BUFFERED);
if (pOpenContext != NULL)
{
Status = ndisprotSetOidValue(
pOpenContext,
pIrp->AssociatedIrp.SystemBuffer,
pIrpSp->Parameters.DeviceIoControl.InputBufferLength
);
BytesReturned = 0;
NDIS_STATUS_TO_NT_STATUS(Status, &NtStatus);
}
else
{
NtStatus = STATUS_DEVICE_NOT_CONNECTED;
}
break;
case IOCTL_NDISPROT_INDICATE_STATUS:
NPROT_ASSERT((FunctionCode & 0x3) == METHOD_BUFFERED);
if (pOpenContext != NULL)
{
NtStatus = ndisprotQueueStatusIndicationIrp(
pOpenContext,
pIrp,
&BytesReturned
);
}
else
{
NtStatus = STATUS_DEVICE_NOT_CONNECTED;
}
break;
default:
NtStatus = STATUS_NOT_SUPPORTED;
break;
}
if (NtStatus != STATUS_PENDING)
{
pIrp->IoStatus.Information = BytesReturned;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
return NtStatus;
}
在3环,发送和接收之前,我们需要等待绑定,而这里的实现是通过绑定网卡时的事件等待来实现.其次在3环发送数据的时候,需要知道通过哪个网卡来发送.而设置通信的网卡是由ndisprotOpenDevice来实现的.
/*外层调用
NtStatus = ndisprotOpenDevice(
pIrp->AssociatedIrp.SystemBuffer,
pIrpSp->Parameters.DeviceIoControl.InputBufferLength,
pIrpSp->FileObject,
&pOpenContext
);
*/
// pDeviceName 设备对象名
// DeviceNameLength 设备对象的长度
// pFileObject 文件对象指针
NTSTATUS
ndisprotOpenDevice(
__in_bcount(DeviceNameLength) IN PUCHAR pDeviceName,
IN ULONG DeviceNameLength,
IN PFILE_OBJECT pFileObject,
OUT PNDISPROT_OPEN_CONTEXT * ppOpenContext
)
{
PNDISPROT_OPEN_CONTEXT pOpenContext;
NTSTATUS NtStatus;
ULONG PacketFilter;
NDIS_STATUS NdisStatus;
ULONG BytesProcessed;
PNDISPROT_OPEN_CONTEXT pCurrentOpenContext = NULL;
pOpenContext = NULL;
do
{
// 根据设备名找到打开上下文。请注意这个中间会调用增加打开
// 上下文引用计数,所以后面要解引用。
pOpenContext = ndisprotLookupDevice(
pDeviceName,
DeviceNameLength
);
// 如果找不到打开上下文,说明没绑定过...
if (pOpenContext == NULL)
{
DEBUGP(DL_WARN, ("ndisprotOpenDevice: couldn't find device\n"));
NtStatus = STATUS_OBJECT_NAME_NOT_FOUND;
break;
}
NPROT_ACQUIRE_LOCK(&pOpenContext->Lock);
// 如果找到了,但是不是打开空闲状态,则返回设备忙。
if (!NPROT_TEST_FLAGS(pOpenContext->Flags, NUIOO_OPEN_FLAGS, NUIOO_OPEN_IDLE))
{
NPROT_ASSERT(pOpenContext->pFileObject != NULL);
DEBUGP(DL_WARN, ("ndisprotOpenDevice: Open %p/%x already associated"
" with another FileObject %p\n",
pOpenContext, pOpenContext->Flags, pOpenContext->pFileObject));
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
// 注意解引用。
NPROT_DEREF_OPEN(pOpenContext); // ndisprotOpenDevice failure
NtStatus = STATUS_DEVICE_BUSY;
break;
}
// 比较交换。首先比较pFileObject->FsContext和NULL.如果是NULL,则用
// pFileObject->FsContext设置为pOpenContext,然后返回NULL。如果不
// 是NULL,则不交换,并返回pFileObject->FsContext
if ((pCurrentOpenContext =
InterlockedCompareExchangePointer (& (pFileObject->FsContext), pOpenContext, NULL)) != NULL)
{
// 到了这里,说明另一个打开已经使用了这个文件对象。这个设备
// 不支持两次打开。到这里直接返回失败即可。
DEBUGP(DL_WARN, ("ndisprotOpenDevice: FileObject %p already associated"
" with another Open %p/%x\n",
pFileObject, pCurrentOpenContext, pCurrentOpenContext->Flags)); //BUG
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
NPROT_DEREF_OPEN(pOpenContext); // ndisprotOpenDevice failure
NtStatus = STATUS_INVALID_DEVICE_REQUEST;
break;
}
// 这个打开上下文被打开了,保存在这个文件对象的FsContext中。这
// 里也保存
pOpenContext->pFileObject = pFileObject;
NPROT_SET_FLAGS(pOpenContext->Flags, NUIOO_OPEN_FLAGS, NUIOO_OPEN_ACTIVE);
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
// 设置PacketFilter,使之能收到包。
PacketFilter = NUIOO_PACKET_FILTER;
NdisStatus = ndisprotValidateOpenAndDoRequest(
pOpenContext,
NdisRequestSetInformation,
OID_GEN_CURRENT_PACKET_FILTER,
&PacketFilter,
sizeof(PacketFilter),
&BytesProcessed,
TRUE // Do wait for power on
);
// 不成功的话
if (NdisStatus != NDIS_STATUS_SUCCESS)
{
DEBUGP(DL_WARN, ("openDevice: Open %p: set packet filter (%x) failed: %x\n",
pOpenContext, PacketFilter, NdisStatus));
NPROT_ACQUIRE_LOCK(&pOpenContext->Lock);
// 不成功的话,去掉FileObject->FsContext设置,如果pFileObject->FsContext是
// pOpenContext,则去掉。
pCurrentOpenContext = InterlockedCompareExchangePointer(
& (pFileObject->FsContext), NULL, pOpenContext);
NPROT_ASSERT(pCurrentOpenContext == pOpenContext);
NPROT_SET_FLAGS(pOpenContext->Flags, NUIOO_OPEN_FLAGS, NUIOO_OPEN_IDLE);
pOpenContext->pFileObject = NULL;
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
NPROT_DEREF_OPEN(pOpenContext); // ndisprotOpenDevice failure
NDIS_STATUS_TO_NT_STATUS(NdisStatus, &NtStatus);
break;
}
// 返回打开上下文。
*ppOpenContext = pOpenContext;
NtStatus = STATUS_SUCCESS;
}
while (FALSE);
return (NtStatus);
}
此函数所作的事情如下:
1.首先根据3环传递进来的设备名称来获取到绑定指定设备的绑定上下文.(通过比较全局上下文链表项中的设备名)
2.判断网卡是否空闲.忙碌则不能进行网卡的指定
3.如果传递进来的pFileObject->FsContext为空,则设置为绑定上下文.如果不为空,则返回失败.
4.将pFileObject->FsContext保存到绑定上下文中.
5.通过OID设置设置PacketFilter,使之能接收到包.
6.如果上面任何一步失败的话,则回退释放资源.最后返回绑定上下文.
这里的话,设置指定网卡实质是将上下文和irp联系起来,网卡的绑定其实早就已经做了,我们只需要选择使用哪个绑定上下文,而发送与接收控制函数可以通过irp来获取到所要发送的上下文.还有一点就是irpSp->FileObject->FsContext可以由驱动开发人员自定义,也就是想保存什么就保存什么.
// 分发函数,处理写请求(也就是发包请求)。
NTSTATUS
NdisProtWrite(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
ULONG DataLength;
NTSTATUS NtStatus;
NDIS_STATUS Status;
PNDISPROT_OPEN_CONTEXT pOpenContext;
PNDIS_PACKET pNdisPacket;
PNDIS_BUFFER pNdisBuffer;
NDISPROT_ETH_HEADER UNALIGNED *pEthHeader;
// NDIS51支持写请求取消。但是本书不讨论请求取消的话题。
#ifdef NDIS51
PVOID CancelId;
#endif
UNREFERENCED_PARAMETER(pDeviceObject);
// 首先得到打开上下文。以确认是用哪个网卡发包。
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
pOpenContext = pIrpSp->FileObject->FsContext;
pNdisPacket = NULL;
do
{
// 检查打开上下文的可靠性。
if (pOpenContext == NULL)
{
DEBUGP(DL_WARN, ("Write: FileObject %p not yet associated with a device\n",
pIrpSp->FileObject));
NtStatus = STATUS_INVALID_HANDLE;
break;
}
NPROT_STRUCT_ASSERT(pOpenContext, oc);
// 确认输入缓冲的可靠性。
if (pIrp->MdlAddress == NULL)
{
DEBUGP(DL_FATAL, ("Write: NULL MDL address on IRP %p\n", pIrp));
NtStatus = STATUS_INVALID_PARAMETER;
break;
}
// 得到输入缓冲的虚拟地址。之后进行一系列的检查。第一,
// 输入缓冲虚拟地址不能为NULL,第二,缓冲的长度,至少必须
// 比一个以太网包头要长。否则无法填写以太网包头。第三,发
// 包的长度不能超过这个网卡的最大帧长。第四,
pEthHeader = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
if (pEthHeader == NULL)
{
DEBUGP(DL_FATAL, ("Write: MmGetSystemAddr failed for"
" IRP %p, MDL %p\n",
pIrp, pIrp->MdlAddress));
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
break;
}
DataLength = MmGetMdlByteCount(pIrp->MdlAddress);
if (DataLength < sizeof(NDISPROT_ETH_HEADER))
{
DEBUGP(DL_WARN, ("Write: too small to be a valid packet (%d bytes)\n",
DataLength));
NtStatus = STATUS_BUFFER_TOO_SMALL;
break;
}
if (DataLength > (pOpenContext->MaxFrameSize + sizeof(NDISPROT_ETH_HEADER)))
{
DEBUGP(DL_WARN, ("Write: Open %p: data length (%d)"
" larger than max frame size (%d)\n",
pOpenContext, DataLength, pOpenContext->MaxFrameSize));
NtStatus = STATUS_INVALID_BUFFER_SIZE;
break;
}
// 下面开始检查,缓冲中是否已经填写了伪造的MAC地址。
// 方法很简单,取得已填写的地址和网卡的MAC地址比较。
// 如果不符合则返回失败。很多情况,网络攻击工具是不会
// 拷贝这段代码的。
if ((pIrp->RequestorMode == UserMode) &&
!NPROT_MEM_CMP(pEthHeader->SrcAddr, pOpenContext->CurrentAddress, NPROT_MAC_ADDR_LEN))
{
DEBUGP(DL_WARN, ("Write: Failing with invalid Source address"));
NtStatus = STATUS_INVALID_PARAMETER;
break;
}
// 确认包可以发送了。下面开始真实的准备发送一个包,首先
// 获得锁,并判断当前网卡是否处于可以发包的状态。
NPROT_ACQUIRE_LOCK(&pOpenContext->Lock);
if (!NPROT_TEST_FLAGS(pOpenContext->Flags, NUIOO_BIND_FLAGS, NUIOO_BIND_ACTIVE))
{
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
DEBUGP(DL_FATAL, ("Write: Open %p is not bound"
" or in low power state\n", pOpenContext));
NtStatus = STATUS_INVALID_HANDLE;
break;
}
// 从前面绑定时分配的发送包池中分配一个包描述符。
NPROT_ASSERT(pOpenContext->SendPacketPool != NULL);
NdisAllocatePacket(
&Status,
&pNdisPacket,
pOpenContext->SendPacketPool);
if (Status != NDIS_STATUS_SUCCESS)
{
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
DEBUGP(DL_FATAL, ("Write: open %p, failed to alloc send pkt\n",
pOpenContext));
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
break;
}
// 下面的代码为Win9x编写,本书不讨论。
if (pOpenContext->bRunningOnWin9x)
{
NdisAllocateBuffer(
&Status,
&pNdisBuffer,
pOpenContext->SendBufferPool,
pEthHeader,
DataLength);
if (Status != NDIS_STATUS_SUCCESS)
{
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
NdisFreePacket(pNdisPacket);
DEBUGP(DL_FATAL, ("Write: open %p, failed to alloc send buf\n",
pOpenContext));
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
break;
}
}
else
{
pNdisBuffer = pIrp->MdlAddress;
}
// 记录发送包又增加了一个。
NdisInterlockedIncrement((PLONG)&pOpenContext->PendedSendCount);
// 打开上下文引用计数加1,这是为了防止发包过程中这个绑定被解除。
NPROT_REF_OPEN(pOpenContext); // pended send
IoMarkIrpPending(pIrp);
// 初始化包引用计数。这个包会在计数为0的时候释放掉。
NPROT_SEND_PKT_RSVD(pNdisPacket)->RefCount = 1;
#ifdef NDIS51
// NDIS5.1支持取消发送。我们给每个包设置一个取消ID。每个
// 包和一个写IRP关联,把包的指针保存在IRP中。如果IRP获得
// 取消通知,则使用NdisCancelSendPackets去取消包。
CancelId = NPROT_GET_NEXT_CANCEL_ID();
NDIS_SET_PACKET_CANCEL_ID(pNdisPacket, CancelId);
pIrp->Tail.Overlay.DriverContext[0] = (PVOID)pOpenContext;
pIrp->Tail.Overlay.DriverContext[1] = (PVOID)pNdisPacket;
NPROT_INSERT_TAIL_LIST(&pOpenContext->PendedWrites, &pIrp->Tail.Overlay.ListEntry);
IoSetCancelRoutine(pIrp, NdisProtCancelWrite);
#endif // NDIS51
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
// 记下irp的指针放在包描述符里,以备后用。
NPROT_IRP_FROM_SEND_PKT(pNdisPacket) = pIrp;
// 把缓冲和包联系起来。状态设置为pending。
NtStatus = STATUS_PENDING;
pNdisBuffer->Next = NULL;
NdisChainBufferAtFront(pNdisPacket, pNdisBuffer);
// 下面的代码仅供调试使用。
#if SEND_DBG
{
PUCHAR pData;
pData = MmGetSystemAddressForMdlSafe(pNdisBuffer, NormalPagePriority);
NPROT_ASSERT(pEthHeader == pData);
DEBUGP(DL_VERY_LOUD,
("Write: MDL %p, MdlFlags %x, SystemAddr %p, %d bytes\n",
pIrp->MdlAddress, pIrp->MdlAddress->MdlFlags, pData, DataLength));
DEBUGPDUMP(DL_VERY_LOUD, pData, MIN(DataLength, 48));
}
#endif // SEND_DBG
// 发送包。非常简单。包发送完之后会自动调用协议特征中的一个
// 回调函数NdisProtSendComplete。在其中再完成IRP即可。
NdisSendPackets(pOpenContext->BindingHandle, &pNdisPacket, 1);
}
while (FALSE);
// 如果正常发送包是STATUS_PENDING。否则是有错的,可以在这里
// 直接完成。
if (NtStatus != STATUS_PENDING)
{
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
return (NtStatus);
}
写请求函数的流程是:
1.通过FsContext获取绑定上下文
2.得到输入缓冲的地址,进行一系列的检查包括输入缓冲地址不能为NULL,缓冲的长度必须比包头长以及不能大于网卡的最大帧长
3.从绑定网卡时分配的发送包池中分配一个包描述符,将pIrp->MdlAddress赋给缓冲描述符,并将之前的包描述符和缓冲描述符链接起来
4.使用NdisSendPacket进行发包.这是状态为STATUS_PENDING,在NdisProtSendComplete中再完成IRP.
// 分发函数之一,处理读请求。
NTSTATUS
NdisProtRead(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus;
PNDISPROT_OPEN_CONTEXT pOpenContext;
UNREFERENCED_PARAMETER(pDeviceObject);
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
pOpenContext = pIrpSp->FileObject->FsContext;
do
{
// 检测打开上下文的可靠性
if (pOpenContext == NULL)
{
DEBUGP(DL_FATAL, ("Read: NULL FsContext on FileObject %p\n",
pIrpSp->FileObject));
NtStatus = STATUS_INVALID_HANDLE;
break;
}
NPROT_STRUCT_ASSERT(pOpenContext, oc);
// Read和Write都是使用的直接IO操作,所以应该使用MdlAddress
// 来传递缓冲。如果不是,返回非法参数错误。
if (pIrp->MdlAddress == NULL)
{
DEBUGP(DL_FATAL, ("Read: NULL MDL address on IRP %p\n", pIrp));
NtStatus = STATUS_INVALID_PARAMETER;
break;
}
// 得到缓冲的虚拟地址。
if (MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority) == NULL)
{
DEBUGP(DL_FATAL, ("Read: MmGetSystemAddr failed for IRP %p, MDL %p\n",
pIrp, pIrp->MdlAddress));
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
break;
}
NPROT_ACQUIRE_LOCK(&pOpenContext->Lock);
if (!NPROT_TEST_FLAGS(pOpenContext->Flags, NUIOO_BIND_FLAGS, NUIOO_BIND_ACTIVE))
{
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
NtStatus = STATUS_INVALID_HANDLE;
break;
}
// 将这个请求插入处理队列里。并把打开上下文引用计数增加就1.
// 把未处理读请求数目增加1.
NPROT_INSERT_TAIL_LIST(&pOpenContext->PendedReads, &pIrp->Tail.Overlay.ListEntry);
NPROT_REF_OPEN(pOpenContext); // pended read IRP
pOpenContext->PendedReadCount++;
// 标记IRP未决。给IRP设置一个取消函数,使之变得可取消。
pIrp->Tail.Overlay.DriverContext[0] = (PVOID)pOpenContext;
IoMarkIrpPending(pIrp);
IoSetCancelRoutine(pIrp, NdisProtCancelRead);
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
NtStatus = STATUS_PENDING;
// 调用一个处理例程来处理所有未决的读请求。
ndisprotServiceReads(pOpenContext);
}
while (FALSE);
// 读请求只返回STATUS_PENDING.如果不是,则说明出错,
// 按错误返回。
if (NtStatus != STATUS_PENDING)
{
NPROT_ASSERT(NtStatus != STATUS_SUCCESS);
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
return (NtStatus);
}
读请求处理函数流程如下:
1.FsContext获取绑定上下文
2.判断网卡是否已经绑定.
3.将这个irp请求插入到打开上下文的未决读请求队列中,并将打开上下文引用加1,未决读请求数目加1
4.标记IRP未决
5.处理所有的未决读请求
VOID
ndisprotServiceReads(
IN PNDISPROT_OPEN_CONTEXT pOpenContext
)
/*++
Routine Description:
Utility routine to copy received data into user buffers and
complete READ IRPs.
Arguments:
pOpenContext - pointer to open context
Return Value:
None
--*/
{
PIRP pIrp = NULL;
PLIST_ENTRY pIrpEntry;
PNDIS_PACKET pRcvPacket;
PLIST_ENTRY pRcvPacketEntry;
PUCHAR pSrc, pDst;
ULONG BytesRemaining; // at pDst
PNDIS_BUFFER pNdisBuffer;
ULONG BytesAvailable;
BOOLEAN FoundPendingIrp;
DEBUGP(DL_VERY_LOUD, ("ServiceReads: open %p/%x\n",
pOpenContext, pOpenContext->Flags));
NPROT_REF_OPEN(pOpenContext); // temp ref - service reads
NPROT_ACQUIRE_LOCK(&pOpenContext->Lock);
// 只要读请求队列和接收包队列同时不为空,则可以做出队列和完成读请求
while (!NPROT_IS_LIST_EMPTY(&pOpenContext->PendedReads) &&
!NPROT_IS_LIST_EMPTY(&pOpenContext->RecvPktQueue))
{
FoundPendingIrp = FALSE;
// 获得第一个未决读请求
pIrpEntry = pOpenContext->PendedReads.Flink;
while (pIrpEntry != &pOpenContext->PendedReads)
{
// 从链表节点得到IRP
pIrp = CONTAINING_RECORD(pIrpEntry, IRP, Tail.Overlay.ListEntry);
// 检查这个请求是否正在被取消。
//
if (IoSetCancelRoutine(pIrp, NULL))
{
// 把这个IRP出列。
NPROT_REMOVE_ENTRY_LIST(pIrpEntry);
FoundPendingIrp = TRUE;
break;
}
else
{
// 如果是在去掉,则跳过这个IRP即可。使用下一个。
DEBUGP(DL_INFO, ("ServiceReads: open %p, skipping cancelled IRP %p\n",
pOpenContext, pIrp));
pIrpEntry = pIrpEntry->Flink;
}
}
// 如果没有IRP,直接跳出结束。
if (FoundPendingIrp == FALSE)
{
break;
}
// 得到第一个包(最旧的),出队列。
pRcvPacketEntry = pOpenContext->RecvPktQueue.Flink;
NPROT_REMOVE_ENTRY_LIST(pRcvPacketEntry);
pOpenContext->RecvPktCount --;
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
NPROT_DEREF_OPEN(pOpenContext);
// 从节点获得包。
pRcvPacket = NPROT_LIST_ENTRY_TO_RCV_PKT(pRcvPacketEntry);
//
// Copy as much data as possible from the receive packet to
// the IRP MDL.
//
// 得到IRP的输出缓冲地址。然后尽量拷贝更多的数据。
pDst = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
NPROT_ASSERT(pDst != NULL); // since it was already mapped
BytesRemaining = MmGetMdlByteCount(pIrp->MdlAddress);
pNdisBuffer = NDIS_PACKET_FIRST_NDIS_BUFFER(pRcvPacket);
// 请注意,每个PNDIS_BUFFER都是一个PMDL,同时PNDIS_BUFFER
// 本身都是链表。用NdisGetNextBuffer可以从一个得到它的下面一个。
// 包的数据实际上是保存在一个缓冲描述符链表里的。
while (BytesRemaining && (pNdisBuffer != NULL))
{
#ifndef WIN9X
NdisQueryBufferSafe(pNdisBuffer,
&pSrc, //缓冲区描述符的数据范围的基地址
&BytesAvailable, //范围的字节数
NormalPagePriority);
if (pSrc == NULL)
{
DEBUGP(DL_FATAL,
("ServiceReads: Open %p, QueryBuffer failed for buffer %p\n",
pOpenContext, pNdisBuffer));
break;
}
#else
NdisQueryBuffer(pNdisBuffer, &pSrc, &BytesAvailable);
#endif
// 如果还可以继续拷贝,就继续拷贝。
if (BytesAvailable)
{
ULONG BytesToCopy = MIN(BytesAvailable, BytesRemaining);
NPROT_COPY_MEM(pDst, pSrc, BytesToCopy);
BytesRemaining -= BytesToCopy;
pDst += BytesToCopy;
}
NdisGetNextBuffer(pNdisBuffer, &pNdisBuffer);
}
// 拷贝好数据之后,结束IRP即可。
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = MmGetMdlByteCount(pIrp->MdlAddress) - BytesRemaining;
DEBUGP(DL_INFO, ("ServiceReads: Open %p, IRP %p completed with %d bytes\n",
pOpenContext, pIrp, pIrp->IoStatus.Information));
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
// 如果这个包描述符不是从接收包池里分配的,那么就是从
// 网卡驱动里重用的。如果是重用的,调用NdisReturnPackets
// 归还给网卡驱动,让它释放。
if (NdisGetPoolFromPacket(pRcvPacket) != pOpenContext->RecvPacketPool)
{
NdisReturnPackets(&pRcvPacket, 1);
}
else
{
// 否则的话自己释放。
ndisprotFreeReceivePacket(pOpenContext, pRcvPacket);
}
NPROT_DEREF_OPEN(pOpenContext); // took out pended Read
NPROT_ACQUIRE_LOCK(&pOpenContext->Lock);
pOpenContext->PendedReadCount--;
}
NPROT_RELEASE_LOCK(&pOpenContext->Lock);
NPROT_DEREF_OPEN(pOpenContext); // temp ref - service reads
}
此函数用于处理所有的未决读请求.其流程如下:
1.判断读请求队列与接受包队列是否都为空.
2.从接收包队列中得到第一个包并且出队列,从出队列的结点中获取包
3.得到Irp的输出缓冲地址pDst,并从包中获取到缓冲区描述符
4.循环地将缓冲区描述符指向的数据区数据复制到pDst中
5.IoCompleteRequest完成请求.
6.判断当前的包描述符是否是从包池中分配的还是重利用的.如果是重利用的,那么就调用NdisReturnPackets将包归还给网卡,释放包的所有权,如果是重分配的就自己释放,最后绑定上下文引用减1.
本文固定链接: https://www.socarates.online/index.php/2024/01/06/ndis%e5%8d%8f%e8%ae%ae%e9%a9%b1%e5%8a%a8/ | 安全技术研究
NDIS协议驱动:等您坐沙发呢!
发表评论
