当前位置: 首页 > 内核 > 正文

NDIS小端口驱动

1.概述

NDIS小端口驱动又称NIC驱动,即真正的网卡驱动,其绑定于网卡,包括物理网卡与虚拟网卡,在NDIS层次中处于最底层。上层是NDIS协议驱动或者是中间层驱动。和协议驱动一样,小端口驱动也提供了一套特征接口,主要用于发送数据包,以及获取或者设置一些网卡信息。

2.代码实现

2.1 DriverEntry函数

和wdm驱动一样,以wdf实现的NDIS小端口驱动同样具有着DriverEntry驱动入口函数,在入口函数中主要注册了一套自己实现的特征函数。

NTSTATUS
DriverEntry(
	PDRIVER_OBJECT DriverObject,
	PUNICODE_STRING RegistryPath
)
/*++
Routine Description:

	In the context of its DriverEntry function, a miniport driver associates
	itself with NDIS, specifies the NDIS version that it is using, and
	registers its entry points.


Arguments:
	DriverObject - pointer to the driver object.
	RegistryPath - pointer to the driver registry path.

	Return Value:

	NDIS_STATUS_xxx code

--*/
{

	NDIS_STATUS                     Status;
	NDIS_MINIPORT_CHARACTERISTICS   MPChar;
	WDF_DRIVER_CONFIG               config;
	NTSTATUS ntStatus;

	DEBUGP(MP_INFO, ("---> NDISWDF sample built on "__DATE__" at "__TIME__ "\n"));
	DEBUGP(MP_INFO, ("---> Sample is built with NDIS Version %d.%d\n",
		MP_NDIS_MAJOR_VERSION, MP_NDIS_MINOR_VERSION));

	// 检查版本号。这里主版本号为5,次版本号为0,要求最低版本为5.0
	if (NdisGetVersion() < ((MP_NDIS_MAJOR_VERSION << 16) | MP_NDIS_MINOR_VERSION)) {
		DEBUGP(MP_ERROR, ("This version of driver is not support on this OS\n"));
		// 如果版本太低,则直接返回失败即可。
		return NDIS_STATUS_FAILURE;
	}


	WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK);
	//
	// Set WdfDriverInitNoDispatchOverride flag to tell the framework
	// not to provide dispatch routines for the driver. In other words,
	// the framework must not intercept IRPs that the I/O manager has
	// directed to the driver. In this case, it will be handled by NDIS
	// port driver.
	//
	config.DriverInitFlags |= WdfDriverInitNoDispatchOverride;

	ntStatus = WdfDriverCreate(DriverObject,
		RegistryPath,
		WDF_NO_OBJECT_ATTRIBUTES,
		&config,
		WDF_NO_HANDLE);
	if (!NT_SUCCESS(ntStatus)) {
		DEBUGP(MP_ERROR, ("WdfDriverCreate failed\n"));
		return NDIS_STATUS_FAILURE;
	}

	// 初始化包装句柄。这个句柄是注册小端口必须的。但是对小端口
	// 驱动的开发者而言,除了调用一些NDIS函数需要提供这个句柄之
	// 外,并没有什么实质的意义。
	NdisMInitializeWrapper(
		&NdisWrapperHandle,
		DriverObject,
		RegistryPath,
		NULL
	);
	if (!NdisWrapperHandle) {
		DEBUGP(MP_ERROR, ("NdisMInitializeWrapper failed\n"));
		return NDIS_STATUS_FAILURE;
	}

	// 把小端口特征清0。
	NdisZeroMemory(&MPChar, sizeof(MPChar));

	// 然后开始填写小端口特征。
	MPChar.MajorNdisVersion = MP_NDIS_MAJOR_VERSION;
	MPChar.MinorNdisVersion = MP_NDIS_MINOR_VERSION;
	MPChar.InitializeHandler = MPInitialize;
	MPChar.HaltHandler = MPHalt;
	MPChar.SetInformationHandler = MPSetInformation;
	MPChar.QueryInformationHandler = MPQueryInformation;
	MPChar.SendPacketsHandler = MPSendPackets;
	MPChar.ReturnPacketHandler = MPReturnPacket;
	MPChar.ResetHandler = NULL;//MPReset;
	MPChar.CheckForHangHandler = MPCheckForHang; //optional
#ifdef NDIS51_MINIPORT
	MPChar.CancelSendPacketsHandler = MPCancelSendPackets;
	MPChar.PnPEventNotifyHandler = MPPnPEventNotify;
	MPChar.AdapterShutdownHandler = MPShutdown;
#endif

	DEBUGP(MP_LOUD, ("Calling NdisMRegisterMiniport...\n"));

	// 注册小端口。注意需要包装句柄与小端口特征。
	Status = NdisMRegisterMiniport(
		NdisWrapperHandle,
		&MPChar,
		sizeof(NDIS_MINIPORT_CHARACTERISTICS));
	if (Status != NDIS_STATUS_SUCCESS) {

		DEBUGP(MP_ERROR, ("Status = 0x%08x\n", Status));
		NdisTerminateWrapper(NdisWrapperHandle, NULL);

	}
	else {

		// 初始化全局变量。这些全局变量是在整个驱动中使用的
		NdisAllocateSpinLock(&GlobalData.Lock);
		NdisInitializeListHead(&GlobalData.AdapterList);

		// 注册一个Unload函数。请注意Unload是整个驱动卸载的时候调用。
		// 而协议特征中的MPHalt则是每个实例(网卡)卸载的时候调用的。
		NdisMRegisterUnloadHandler(NdisWrapperHandle, MPUnload);
	}

	DEBUGP(MP_TRACE, ("<--- DriverEntry\n"));
	return Status;

DriverEntry所做的事情主要如下:
1.首先生成一个wdf驱动对象,虽然没有对象输出。

2.NdisMInitializeWrapper初始化包装句柄,后面可用于注册小端口

3.填写小端口特征

4.NdisMRegisterMiniport注册小端口

5.NdisMRegisterUnloadHandler注册一个卸载函数,此函数是在驱动卸载时调用。

2.2 MPInitialize

NDIS_STATUS
MPInitialize(
	OUT PNDIS_STATUS OpenErrorStatus,
	OUT PUINT        SelectedMediumIndex,
	IN PNDIS_MEDIUM  MediumArray,
	IN UINT          MediumArraySize,
	IN NDIS_HANDLE   MiniportAdapterHandle,
	IN NDIS_HANDLE   WrapperConfigurationContext
)
/*++
Routine Description:

	The MiniportInitialize function is a required function that sets up a
	NIC (or virtual NIC) for network I/O operations, claims all hardware
	resources necessary to the NIC in the registry, and allocates resources
	the driver needs to carry out network I/O operations.

	MiniportInitialize runs at IRQL = PASSIVE_LEVEL.

Arguments:

	Return Value:

	NDIS_STATUS_xxx code

--*/
{
	NDIS_STATUS            Status = NDIS_STATUS_SUCCESS;
	PMP_ADAPTER            Adapter = NULL;
	UINT                   index;
	NTSTATUS               ntStatus;
	ULONG                  nameLength;
	WDF_OBJECT_ATTRIBUTES  doa;


	UNREFERENCED_PARAMETER(OpenErrorStatus);

	DEBUGP(MP_TRACE, ("---> MPInitialize\n"));

	PAGED_CODE();

	do {

		// 检查可选的媒质类型中有没有NdisMedium802_3.
		for (index = 0; index < MediumArraySize; ++index)
		{
			if (MediumArray[index] == NIC_MEDIA_TYPE) {
				break;
			}
		}

		// 如果没有当然就直接返回不支持了。
		if (index == MediumArraySize)
		{
			DEBUGP(MP_ERROR, ("Expected media is not in MediumArray.\n"));
			Status = NDIS_STATUS_UNSUPPORTED_MEDIA;
			break;
		}

		// 填写选中的媒质。
		*SelectedMediumIndex = index;

		// 下面是分配初始化适配器结构。非常简单,不详述。
		Status = NICAllocAdapter(&Adapter);
		if (Status != NDIS_STATUS_SUCCESS)
		{
			break;
		}

		// 增加引用计数。这个引用计数对应的在MPHalt中减少。
		MP_INC_REF(Adapter);

		//
		// NdisMGetDeviceProperty function enables us to get the:
		// PDO - created by the bus driver to represent our device.
		// FDO - created by NDIS to represent our miniport as a function
		//              driver.
		// NextDeviceObject - deviceobject of another driver (filter)
		//                      attached to us at the bottom.
		// Since our driver is talking to NDISPROT, the NextDeviceObject
		// is not useful. But if we were to talk to a driver that we
		// are attached to as part of the devicestack then NextDeviceObject
		// would be our target DeviceObject for sending read/write Requests.
		//

		NdisMGetDeviceProperty(MiniportAdapterHandle,
			&Adapter->Pdo,
			&Adapter->Fdo,
			&Adapter->NextDeviceObject,
			NULL,
			NULL);

		//请求设备相关信息,这里是用来获取描述设备的字符串
		ntStatus = IoGetDeviceProperty(Adapter->Pdo,
			DevicePropertyDeviceDescription,
			NIC_ADAPTER_NAME_SIZE,
			Adapter->AdapterDesc,
			&nameLength);

		if (!NT_SUCCESS(ntStatus))
		{
			DEBUGP(MP_ERROR, ("IoGetDeviceProperty failed (0x%x)\n",
				ntStatus));
			Status = NDIS_STATUS_FAILURE;
			break;
		}

		Adapter->AdapterHandle = MiniportAdapterHandle;

		MP_SET_FLAG(Adapter, fMP_INIT_IN_PROGRESS);

		//
		// Read Advanced configuration information from the registry
		//
		//从注册表中读取信息放置在Adapter中
		//主要读取了网络地址,mac地址,混杂过滤器值和miniport名称.
		//并将以上读取的值写入到Adapter中
		Status = NICReadRegParameters(Adapter, WrapperConfigurationContext);
		if (Status != NDIS_STATUS_SUCCESS)
		{
			break;
		}

		//
		// Inform NDIS about significant features of the NIC. A
		// MiniportInitialize function must call NdisMSetAttributesEx
		// (or NdisMSetAttributes) before calling any other NdisMRegisterXxx
		// or NdisXxx function that claims hardware resources. If your
		// hardware supports busmaster DMA, you must specify
		// NDIS_ATTRIBUTE_BUS_MASTER.
		// If this is NDIS51 miniport, it should use safe APIs. But if this
		// is NDIS 5.0, NDIS might assume that we are not using safe APIs
		// and try to map every send buffer MDLs. As a result, let us tell
		// NDIS, even though we are NDIS 5.0, we use safe buffer APIs by
		// setting the flag NDIS_ATTRIBUTE_USES_SAFE_BUFFER_APIS
		//设置适配器上下文
		NdisMSetAttributesEx(
			MiniportAdapterHandle,
			(NDIS_HANDLE)Adapter,
			0,
#ifdef NDIS50_MINIPORT
			NDIS_ATTRIBUTE_DESERIALIZE |
			NDIS_ATTRIBUTE_USES_SAFE_BUFFER_APIS,
#else
			NDIS_ATTRIBUTE_DESERIALIZE,
#endif
			NIC_INTERFACE_TYPE);

		WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&doa, WDF_DEVICE_INFO);

		//生成小端口设备对象
		ntStatus = WdfDeviceMiniportCreate(WdfGetDriver(),
			&doa,
			Adapter->Fdo,
			Adapter->NextDeviceObject,
			Adapter->Pdo,
			&Adapter->WdfDevice);//小端口设备对象
		if (!NT_SUCCESS(ntStatus))
		{
			DEBUGP(MP_ERROR, ("WdfDeviceMiniportCreate failed (0x%x)\n",
				ntStatus));
			Status = NDIS_STATUS_FAILURE;
			break;
		}

		GetWdfDeviceInfo(Adapter->WdfDevice)->Adapter = Adapter;

		//
		// Get the Adapter Resources & Initialize the hardware.
		//
		//启动网卡
		Status = NICInitializeAdapter(Adapter, WrapperConfigurationContext);
		if (Status != NDIS_STATUS_SUCCESS) {
			Status = NDIS_STATUS_FAILURE;
			break;
		}

	} WHILE(FALSE);
	if (Status == NDIS_STATUS_SUCCESS) {
		//
		// Attach this Adapter to the global list of adapters managed by
		// this driver.
		//将Adapter指针保存在全局链表中
		NICAttachAdapter(Adapter);
	}
	else {
		if (Adapter) {
			//释放资源
			MPHalt(Adapter);
		}
	}

	DEBUGP(MP_TRACE, ("<--- MPInitialize Status = 0x%08x%\n", Status));

	return Status;

}

在这里涉及到了适配器上下文的概念,适配器上下文是指向适配器结构的指针,而适配器结构里面主要成员大都是关于收发数据包相关的一些资源,在之后收发数据包的特征函数中,只有知道了适配器上下文才会知道通过哪个网卡进行收发数据包。不管是协议驱动中的打开上下文,还是小端口驱动中的适配器,都是通过我们自己申请内存来实现的

这里的MPInitialize函数做的都是收发数据包之前的一些准备工作,如下:
1.分配初始化适配器结构,其内部申请空间,并初始化了适配器结构中的锁Lock,链表结点List,用于同步的事件

2.NdisMGetDeviceProperty获取小端口驱动通信所需的设备对象

3.NICReadRegParameters从注册表中网络地址,mac地址,混杂过滤器值和miniport名称并存储在适配器结构中

4.NdisMSetAttributesEx设置适配器上下文

5.WdfDeviceMiniportCreate生成小端口设备对象

6.NICInitializeAdapter启动网卡

7.NICAttachAdapter将适配器指针保存在全局链表中

其中第八步NICInitializeAdapter中还有着更为重要的操作,其内部主要创建了一个工作任务,以及调用NICInitAdapterWorker。

NTSTATUS
NICInitAdapterWorker(
    PMP_ADAPTER Adapter
    )
/*++
Routine Description:

    This is the worker routine for doing all the initialization work.
    It opens the target device, allocates send & receive side resources
    and spawns a worker thread to start receiving packets from the
    target device.

Arguments:

    Adapter     Pointer to our adapter

Return Value:

    NT Status code

--*/
{
    NTSTATUS            ntStatus = STATUS_UNSUCCESSFUL;
    NDIS_STATUS         Status;
    ULONG               unUsed;
    WDF_OBJECT_ATTRIBUTES attributes;

   
    DEBUGP(MP_WARNING, ("--> NICInitAdapterWorker %p\n", Adapter));

    PAGED_CODE();

    //
    // Let us make sure only one thread is executing this routine by
    // waiting on this synchronization event.
    //
    DEBUGP(MP_WARNING, ("Waiting on the InitEvent...\n"));
    NdisWaitEvent(&Adapter->InitEvent, 0);

    do {

        if (!MP_TEST_FLAG(Adapter, fMP_INIT_IN_PROGRESS)) {
            //
            // Adapter has been initialized successfully.
            //
            ntStatus = STATUS_SUCCESS;
            break;
        }

#ifdef INTERFACE_WITH_NDISPROT

        // 打开协议驱动的接口。
        ntStatus = NICOpenNdisProtocolInterface(Adapter);
        if (!NT_SUCCESS(ntStatus)) {
            break;
        }

        Adapter->IsTargetSupportChainedMdls = FALSE;

#else
        //
        // To retrieve a handle to a device's local I/O target, the driver calls WdfDeviceGetIoTarget.
        //返回本地IO目标
        Adapter->IoTarget = WdfDeviceGetIoTarget(Adapter->WdfDevice);
        if (Adapter->IoTarget == NULL) {
            DEBUGP(MP_ERROR, ("WdfDeviceGetIoTarget failed \n"));
            break;
        }
#endif

        Status = NICAllocSendResources(Adapter);
        if (Status != NDIS_STATUS_SUCCESS)
        {
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
            DEBUGP(MP_ERROR, ("Failed to allocate send side resources\n"));
            break;
        }

        //生成读包工作任务,并把这个工作任务保存在Adapter->ReadWordItem中
        Status = NICAllocRecvResources(Adapter);
        if (Status != NDIS_STATUS_SUCCESS)
        {
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
            DEBUGP(MP_ERROR, ("Failed to send side resources\n"));
            break;
        }

        ntStatus = NICSendRequest(Adapter,
                        NdisRequestQueryInformation,
                        OID_GEN_LINK_SPEED,
                        &Adapter->ulLinkSpeed,
                        sizeof(ULONG),
                        &unUsed,
                        &unUsed
                        );

        if (!NT_SUCCESS(ntStatus)){
            DEBUGP(MP_ERROR, ("-->Unable to get link speed %x\n", ntStatus));
            break;
        }

        //
        // If the device property is set to promiscuous, we will set the
        // target NIC to promiscuous mode and use a locally administered
        // MAC address. Otherwise, we will assume the MAC address of the
        // target NIC (We are assuming that user must have disabled the
        // binding of TCP/IP with the target NIC).
        //
        if (Adapter->Promiscuous) {

            ULONG PacketFilter = NDIS_PACKET_TYPE_PROMISCUOUS;

            DEBUGP(MP_TRACE, ("Set the physical NIC to promiscuous mode\n"));

            ntStatus = NICSendRequest(Adapter,
                            NdisRequestSetInformation,
                            OID_GEN_CURRENT_PACKET_FILTER,
                            &PacketFilter,
                            sizeof(ULONG),
                            &unUsed,
                            &unUsed
                            );

            if (!NT_SUCCESS(ntStatus)){
                DEBUGP(MP_ERROR, ("-->Unable to set the NIC to promiscuous mode %x\n", ntStatus));
                break;
            }
        } else {

            //
            // Then get the physical NICs MAC address and use that as our
            // MAC address. Hoping that user must have disabled TCP/IP
            // bindings with the physcial NIC.
            //

            ntStatus = NICSendRequest(Adapter,
                        NdisRequestQueryInformation,
                        OID_802_3_CURRENT_ADDRESS,
                        Adapter->PhyNicMacAddress,
                        ETH_LENGTH_OF_ADDRESS,
                        &unUsed,
                        &unUsed
                        );

            if (!NT_SUCCESS(ntStatus)){
                DEBUGP(MP_ERROR, ("-->Unable to get mac address %x\n", ntStatus));
                break;
            }

            ETH_COPY_NETWORK_ADDRESS(Adapter->CurrentAddress,
                                            Adapter->PhyNicMacAddress);

            DEBUGP(MP_WARNING, ("Current Address = %02x-%02x-%02x-%02x-%02x-%02x\n",
                                    Adapter->CurrentAddress[0],
                                    Adapter->CurrentAddress[1],
                                    Adapter->CurrentAddress[2],
                                    Adapter->CurrentAddress[3],
                                    Adapter->CurrentAddress[4],
                                    Adapter->CurrentAddress[5]));

        }

        MP_CLEAR_FLAG(Adapter, fMP_INIT_IN_PROGRESS);
        MP_CLEAR_FLAG(Adapter, fMP_DISCONNECTED);
        MP_SET_FLAG(Adapter, fMP_POST_WRITES);
        MP_SET_FLAG(Adapter, fMP_POST_READS);

        //
        // Finally schedule a workitme to start reading packets from
        // the target device. .
        // 将读工作任务放入到windows的工作队列中
        WdfWorkItemEnqueue(Adapter->ReadWorkItem);

        //
        // Post a request to get media change notification.
        //
        WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
        attributes.ParentObject = Adapter->IoTarget;


        ntStatus = WdfRequestCreate(
            WDF_NO_OBJECT_ATTRIBUTES,
            Adapter->IoTarget,
            &Adapter->StatusIndicationRequest);

        if (!NT_SUCCESS(ntStatus)) {
            DEBUGP(MP_ERROR, ("WdfRequestCreate for status-indication failed\n"));
            NT_STATUS_TO_NDIS_STATUS(ntStatus, &Status);
            break;
        }

        WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
        attributes.ParentObject = Adapter->StatusIndicationRequest;

        ntStatus = WdfMemoryCreatePreallocated(&attributes,
                                    &Adapter->NdisProtIndicateStatus,
                                    sizeof(NDISPROT_INDICATE_STATUS),
                                    &Adapter->WdfStatusIndicationBuffer);
        if (!NT_SUCCESS(ntStatus)) {

            DEBUGP(MP_ERROR, ("WdfMemoryCreatePreallocated failed 0x%x\n",
                                        ntStatus));
            NT_STATUS_TO_NDIS_STATUS(ntStatus, &Status);
            break;
        }


        ntStatus = NICPostAsynchronousStatusIndicationRequest(Adapter);
        if (!NT_SUCCESS(ntStatus)) {

            DEBUGP(MP_ERROR, ("NICPostAsynchronousStatusIndicationRequest failed 0x%x\n",
                                        ntStatus));
            NT_STATUS_TO_NDIS_STATUS(ntStatus, &Status);
            break;
        }

    } WHILE (FALSE);

    if (!NT_SUCCESS(ntStatus)){
        NICFreeSendResources(Adapter);
        NICFreeRecvResources(Adapter);
    }

    //
    // Signal the event so another thread can proceed.
    //
    NdisSetEvent(&Adapter->InitEvent);

    DEBUGP(MP_TRACE, ("<-- NICInitAdapterWorker %x\n", ntStatus));

    return ntStatus;
}

此函数所做事情如下:
1.NICOpenNdisProtocolInterface调用打开协议驱动接口。

2.NICAllocSendResources申请发送请求所使用的资源

3.NICAllocRecvResources创建读工作任务,申请读所需要的空间资源

4.通过OID查询一些Adapter程序并设置

5.WdfWorkItemEnqueue将读工作任务入工作队列

6.创建wdf请求

NICOpenNdisProtocolInterface函数细节如下:

//此函数作用是打开ndisprot协议驱动.
//首先生成远程IO并将其与协议驱动联系起来
//然后发送IOCTL代码等待绑定,IOCTL获取设备
//名随后打开设备,这样ndisprot就和ndisedge
//联系起来
NTSTATUS
NICOpenNdisProtocolInterface(
    PMP_ADAPTER Adapter
    )
/*++
Routine Description:

    This routine opens the NDISPROT device and sends buch of IOCTLs
    to get the names of NICs that it's bound to and pick the first
    available one as our target phycial device.

Arguments:

    Adapter     Pointer to our adapter

Return Value:

    NT Status code

--*/
{
    UCHAR           Buf[512];//should be enough to get name and description of one adapter
    ULONG           BufLength = sizeof(Buf);
    ULONG           i;
    NTSTATUS        status;
    PWCHAR          deviceName = NULL;
    PWCHAR          deviceDesc = NULL;
    UNICODE_STRING  fileName;
    ULONG           unUsed;
    PNDISPROT_QUERY_BINDING pQueryBinding = NULL;
    WDF_IO_TARGET_OPEN_PARAMS  openParams;

    PAGED_CODE();

    DEBUGP(MP_TRACE, ("--> NICOpenNdisProtocolInterface\n"));

    RtlInitUnicodeString ( &fileName, (PCWSTR) PROTOCOL_INTERFACE_NAME );

    //生成一个远程IO目标
    status = WdfIoTargetCreate(Adapter->WdfDevice,  //小端口设备对象句柄
                        WDF_NO_OBJECT_ATTRIBUTES,
                        &Adapter->IoTarget);
    if (!NT_SUCCESS(status)) {
        DEBUGP(MP_ERROR, ("WdfIoTargetCreate failed 0x%x\n", status));
        return status;
    }

    WDF_IO_TARGET_OPEN_PARAMS_INIT_CREATE_BY_NAME(&openParams,
                                &fileName,
                                STANDARD_RIGHTS_ALL
                                );
    //将远程IO目标与远程设别对象联系起来
    status = WdfIoTargetOpen(Adapter->IoTarget,
                        &openParams);
    if (!NT_SUCCESS(status)) {
        DEBUGP(MP_ERROR, ("WdfIoTargetOpen failed 0x%x\n", status));
        return status;
    }

    DEBUGP (MP_INFO, ( "Successfully opened the protocol interface\n"));

    //
    // Make sure the target device supports direct I/O operations.
    //
    if (!((WdfIoTargetWdmGetTargetDeviceObject(Adapter->IoTarget))->Flags
                            & DO_DIRECT_IO)) {
        ASSERTMSG("Target device doesn't support direct I/O\n", FALSE);
        return STATUS_INVALID_DEVICE_REQUEST;
    }

    Adapter->FileObject = WdfIoTargetWdmGetTargetFileObject(Adapter->IoTarget);

    //
    // Wait for the NDISPROT to completly bind to all the miniport
    // instances.
    // 发送IOCTL请求
    status = NICMakeSynchronousIoctl(
                Adapter->IoTarget,
                Adapter->FileObject,
                IOCTL_NDISPROT_BIND_WAIT,
                NULL,
                0,
                NULL,
                0,
                &unUsed
                );
    if (!NT_SUCCESS(status)) {
        DEBUGP(MP_ERROR, ("IOCTL_NDISIO_BIND_WAIT failed, error %x\n", status));
        return status;
    }

    pQueryBinding = (PNDISPROT_QUERY_BINDING)Buf;

    //
    // Query the binding of NDISPROT one at a time and see if you can open
    // the bindings and use that as our physical adapter.
    //
    i = 0;

    for (pQueryBinding->BindingIndex = i;
         /* NOTHING */;
         pQueryBinding->BindingIndex = ++i)
    {

        status = NICMakeSynchronousIoctl(
                Adapter->IoTarget,
                Adapter->FileObject,
                IOCTL_NDISPROT_QUERY_BINDING,
                pQueryBinding,
                sizeof(NDISPROT_QUERY_BINDING),
                Buf,
                BufLength,
                &unUsed
                );
        if (NT_SUCCESS(status))
        {
            deviceName = (PWCHAR)((PUCHAR)pQueryBinding +
                                    pQueryBinding->DeviceNameOffset);
            deviceDesc = (PWCHAR)((PUCHAR )pQueryBinding +
                                    pQueryBinding->DeviceDescrOffset);

            DEBUGP(MP_WARNING, ("%2d. %ws\n     - %ws\n",
                pQueryBinding->BindingIndex, deviceName, deviceDesc));

            //
            // Make sure we are not opening our device or another instance
            // of NDISWDM miniport if more than one instances is installed.
            // We can just use the AdapterDesc to decide this all the time.
            // There is no need to get devicename - just an illustration.
            //
            if (wcscmp(deviceName, Adapter->AdapterName) &&
                    !wcsstr(deviceDesc, Adapter->AdapterDesc))
            {

                Adapter->FileObject->FsContext = NULL;

                status = NICMakeSynchronousIoctl(
                            Adapter->IoTarget,
                            Adapter->FileObject,
                            IOCTL_NDISPROT_OPEN_DEVICE,
                            (PVOID)deviceName,
                            pQueryBinding->DeviceNameLength-sizeof(WCHAR),
                            NULL,
                            0,
                            &unUsed
                            );
                if (!NT_SUCCESS(status)) {
                    DEBUGP(MP_ERROR, ("Failed to open NDIS Device %ws %x\n", deviceDesc, status));
                } else {
                    DEBUGP(MP_WARNING, ("Successfully opened NDIS Device: %ws\n", deviceDesc));
                    break;
                }
            }
            RtlZeroMemory(Buf, BufLength);
        }
        else
        {
            if (status != STATUS_NO_MORE_ENTRIES)
            {
                DEBUGP(MP_ERROR, ("EnumerateDevices: terminated abnormally, error %x\n", status));
            }
            break;
        }
    }

    DEBUGP(MP_TRACE, ("<-- NICOpenNdisProtocolInterface\n"));

    return status;
}

其中发送IOCTL_NDISPROT_QUERY_BINDING也只是获取协议驱动所绑定的网卡信息,而发送IOCTL_NDISPROT_OPEN_DEVICE才是真正指定打开上下文指定网卡,原理是:在协议驱动中通过网卡名称进而确定打开上下文。将指定IO目标(其实就是句柄的包装)所对应的irp的pFileObject->FsContext设置为pOpenContext,所以在发送或者接收函数中可以通过irp来获取打开上下文选择网卡就行发送。我们可能会疑惑发送或者接收irp怎么会保存上下文,其实只要确保了irp所对应的句柄不变,那么这个句柄所对应的不论是那个irp->pFileObject->FsContext都是确定的

通过irp来获取打开上下文

//上层驱动程序发包时,最终会调用小端口的此函数
VOID
MPSendPackets(
    IN  NDIS_HANDLE             MiniportAdapterContext,
    IN  PPNDIS_PACKET           PacketArray,
    IN  UINT                    NumberOfPackets)
/*++

Routine Description:

    Send Packet Array handler. Called by NDIS whenever a protocol
    bound to our miniport sends one or more packets.

    The input packet descriptor pointers have been ordered according
    to the order in which the packets should be sent over the network
    by the protocol driver that set up the packet array. The NDIS
    library preserves the protocol-determined ordering when it submits
    each packet array to MiniportSendPackets

    As a deserialized driver, we are responsible for holding incoming send
    packets in our internal queue until they can be transmitted over the
    network and for preserving the protocol-determined ordering of packet
    descriptors incoming to its MiniportSendPackets function.
    A deserialized miniport driver must complete each incoming send packet
    with NdisMSendComplete, and it cannot call NdisMSendResourcesAvailable.

    Runs at IRQL <= DISPATCH_LEVEL

Arguments:

    MiniportAdapterContext    Pointer to our adapter context
    PacketArray               Set of packets to send
    NumberOfPackets           Length of above array

Return Value:

    None

--*/
{
    PMP_ADAPTER       Adapter;
    UINT              PacketCount;
    PNDIS_PACKET      Packet = NULL;
    NDIS_STATUS       Status = NDIS_STATUS_FAILURE;
    PTCB              pTCB = NULL;
    NTSTATUS          ntStatus;

    DEBUGP(MP_TRACE, ("---> MPSendPackets\n"));

    Adapter = (PMP_ADAPTER)MiniportAdapterContext;

    for(PacketCount=0; PacketCount < NumberOfPackets; PacketCount++)
    {
        //
        // Check for a zero pointer
        //

        Packet = PacketArray[PacketCount];

        if (!Packet){
            ASSERTMSG("Packet pointer is NULL\n", FALSE);
            continue;
        }

        DEBUGP(MP_LOUD, ("Adapter %p, Packet %p\n", Adapter, Packet));

        Adapter->nPacketsArrived++;

        if (MP_IS_READY(Adapter) && MP_TEST_FLAG(Adapter, fMP_POST_WRITES))
        {
            //
            // Get a free TCB block
            //
            pTCB = (PTCB) NdisInterlockedRemoveHeadList(
                         &Adapter->SendFreeList,
                         &Adapter->SendLock);
            if (pTCB == NULL)
            {
                DEBUGP(MP_LOUD, ("Can't allocate a TCB......!\n"));

                Status = NDIS_STATUS_PENDING;

                //
                // Not able to get TCB block for this send. So queue
                // it for later transmission and return NDIS_STATUS_PENDING .
                //
                NdisInterlockedInsertTailList(
                    &Adapter->SendWaitList,
                    (PLIST_ENTRY)&Packet->MiniportReserved[0],
                    &Adapter->SendLock);
            }
            else
            {
                NdisInterlockedIncrement(&Adapter->nBusySend);
                ASSERT(Adapter->nBusySend <= NIC_MAX_BUSY_SENDS);

                //
                // Copy the content of the packet to a TCB block
                // buffer. We need to do this because the target driver
                // is not capable of handling chained buffers.
                // 将包的数据拷贝到tcb中并插入到发送链表中
                if (NICCopyPacket(Adapter, pTCB, Packet))
                {
                    Adapter->nWritesPosted++;
                    //
                    // Post a asynchronouse write request.
                    //构建和发送写请求
                    ntStatus = NICPostWriteRequest(Adapter, pTCB);

                    if ( !NT_SUCCESS ( ntStatus ) ) {
                        DEBUGP (MP_ERROR, ( "NICPostWriteRequest failed %x\n", ntStatus ));
                    }
                    NT_STATUS_TO_NDIS_STATUS(ntStatus, &Status);

                }else {
                    DEBUGP(MP_ERROR, ("NICCopyPacket returned error\n"));
                    Status = NDIS_STATUS_FAILURE;
                }
            }
        }

        if (!NT_SUCCESS(Status)) // yes, NT_SUCCESS macro can be used on NDIS_STATUS
        {

            DEBUGP(MP_LOUD, ("Calling NdisMSendComplete %x \n", Status));

            NDIS_SET_PACKET_STATUS(Packet, Status);
            //通知上层协议驱动发送完毕以便调用上层协议驱动的SendCompleteHandler
            NdisMSendComplete(
                Adapter->AdapterHandle,
                Packet,
                Status);

            if (pTCB && (NdisInterlockedDecrement(&pTCB->Ref) == 0))
            {
                NICFreeSendTCB(Adapter, pTCB);
            }
        }


    }

    DEBUGP(MP_TRACE, ("<--- MPSendPackets\n"));

    return;
}

根据函数参数NumberOfPackets我们能够确定此函数是可以发送一组包的。在小端口驱动程序中,还存在着只能发送单包的接口,如果这两个接口共存,则只有发送多包的起作用。

这个函数做的事情也简单,大致就是从发送链表中获取TCB,然后将包中的数据拷贝到TCB中,然后构建和发送写请求,循环重复上面的操作,直到所有的包发送完成。其中NICPostWriteRequest代码如下:

NTSTATUS
NICPostWriteRequest(
    PMP_ADAPTER Adapter,
    PTCB    pTCB
    )
/*++

Routine Description:

    This routine posts a write Request to the target device to send
    the network packet out to the device.

Arguments:

    Adapter    - pointer to the MP_ADAPTER structure
    PTCB    -  Pointer to the TCB block that contains the Request and data.


Return Value:

    NT status code

--*/
{

    PIRP            irp = NULL;
    PIO_STACK_LOCATION  nextStack;
    NTSTATUS    status = STATUS_SUCCESS;

    DEBUGP(MP_TRACE, ("--> NICPostWriteRequest\n"));

    // 准备一个写请求。实际上这里后面的参数不是OutputBuffer
    // 而是InputBuffer。但是原始的代码注释就写成了OuputBuffer。
    status = WdfIoTargetFormatRequestForWrite(
                    Adapter->IoTarget,
                    pTCB->Request,
                    NULL, //OutputBuffer
                    NULL, // OutputBufferOffsets
                    NULL); // StartingOffset

    if (!NT_SUCCESS(status)) {
        return status;
    }

    // 从WdfRequest获得IRP.
    irp = WdfRequestWdmGetIrp(pTCB->Request);

    // 获得下一个栈空间。然后填写输入缓冲。
    nextStack = IoGetNextIrpStackLocation( irp );
    irp->MdlAddress = pTCB->Mdl;
    nextStack->Parameters.Write.Length = pTCB->ulSize;

    // 设置一个完成回调函数。
    WdfRequestSetCompletionRoutine(pTCB->Request,
                   NICWriteRequestCompletion,
                   pTCB);

    // 发送请求。我们不关心发送请求的状态。因为请求
    // 发送完毕之后我们从完成函数中获得请求完成状态。
    if (WdfRequestSend(pTCB->Request, Adapter->IoTarget, WDF_NO_SEND_OPTIONS) == FALSE) {
        status = WdfRequestGetStatus(pTCB->Request);
    }

    DEBUGP(MP_TRACE, ("<-- NICPostWriteRequest\n"));

    return status;
}

函数做的事情是:1.WdfIoTargetFormatRequestForWrite准备写请求 2.从wdf请求获取irp并设置irp属性 3.WdfRequestSetCompletionRoutine设置wdf请求完成函数 4.WdfRequestSend发送写请求。

在驱动的特征中,我们并没有见到接收数据包的函数。其实接收工作是以工作任务的形式存在。接收工作任务的回调函数NICPostReadsWorkItemCallBack是在NICAllocRecvResources中注册的,而任务的开启是在NICInitAdapterWorker中。可见接收数据包的操作是在准备阶段就已经做的。

NICPostReadsWorkItemCallBack细节


VOID
NICPostReadsWorkItemCallBack(
    WDFWORKITEM  WorkItem
    )
/*++

Routine Description:

   Workitem to post all the free read requests to the target
   driver. This workitme is scheduled from the MiniportInitialize
   worker routine during start and also from the NICFreeRCB whenever
   the outstanding read requests to the target driver goes below
   the NIC_SEND_LOW_WATERMARK.

Arguments:

    WorkItem - Pointer to workitem

    Dummy - Unused

Return Value:

    None.

--*/
{
    PMP_ADAPTER     Adapter;
    NTSTATUS        ntStatus;
    PRCB            pRCB=NULL;

    DEBUGP(MP_TRACE, ("--->NICPostReadsWorkItemCallBack\n"));
    
    //我们把工作任务的父对象设置为wdf设备句柄(适配器中保存的),
    //现在我们通过这个句柄获得设备信息,设备信息中有适配器结构指针
    Adapter = GetWdfDeviceInfo(WdfWorkItemGetParentObject(WorkItem))->Adapter;

    NdisAcquireSpinLock(&Adapter->RecvLock);

    while (MP_IS_READY(Adapter) && !IsListEmpty(&Adapter->RecvFreeList))
    {
        pRCB = (PRCB) RemoveHeadList(&Adapter->RecvFreeList);

        ASSERT(pRCB);// cannot be NULL

        //
        // Insert the RCB in the recv busy queue
        //
        NdisInterlockedIncrement(&Adapter->nBusyRecv);
        ASSERT(Adapter->nBusyRecv <= NIC_MAX_BUSY_RECVS);

        InsertTailList(&Adapter->RecvBusyList, &pRCB->List);

        NdisReleaseSpinLock(&Adapter->RecvLock);

        Adapter->nReadsPosted++;

        ntStatus = NICPostReadRequest(Adapter, pRCB);

        NdisAcquireSpinLock(&Adapter->RecvLock);

        if (!NT_SUCCESS ( ntStatus ) ) {
            DEBUGP (MP_ERROR, ( "NICPostReadRequest failed %x\n", ntStatus ));
            break;
        }

    }
    NdisReleaseSpinLock(&Adapter->RecvLock);

    //
    // Clear the flag to let the WorkItem structure be reused for
    // scheduling another one.
    //
    InterlockedExchange(&Adapter->IsReadWorkItemQueued, FALSE);

    DEBUGP(MP_TRACE, ("<---NICPostReadsWorkItemCallBack\n"));
}

首先从接收链表中获取到RCB再将RCB插入到发送读请求链表中,最后调用NICPostReadRequest构建和发送读请求。

NICPostReadRequest代码如下,和上面发送写请求一样,不再赘述。


NTSTATUS
NICPostReadRequest(
    PMP_ADAPTER Adapter,
    PRCB    pRCB
    )
/*++

Routine Description:

    This routine sends a read Request to the target device to get
    the incoming network packet from the device.

Arguments:

    Adapter    - pointer to the MP_ADAPTER structure
    pRCB    -  Pointer to the RCB block that contains the Request.


Return Value:

    NT status code

--*/
{
    NTSTATUS        status = STATUS_SUCCESS;
    PIRP            irp = NULL;
    PIO_STACK_LOCATION  nextStack;

    DEBUGP(MP_LOUD, ("--> NICPostReadRequest\n"));

    status = WdfIoTargetFormatRequestForRead(
                    Adapter->IoTarget,
                    pRCB->Request,
                    NULL, // InputBuffer
                    NULL, // InputBufferOffsets
                    NULL); // StartingOffset
    if (!NT_SUCCESS(status)) {
        return status;
    }

    irp = WdfRequestWdmGetIrp(pRCB->Request);
    //
    // Obtain a pointer to the stack location of the first driver that will be
    // invoked.  This is where the function codes and the parameters are set.
    //
    nextStack = IoGetNextIrpStackLocation( irp );
    irp->MdlAddress = (PMDL) pRCB->Buffer;
    nextStack->Parameters.Read.Length = NIC_BUFFER_SIZE;

    pRCB->Ref = 1;

    WdfRequestSetCompletionRoutine(pRCB->Request,
                   NICReadRequestCompletion,
                   pRCB);

    //
    // We are making an asynchronous request, so we don't really care
    // about the return status of IoCallDriver.
    //
    if (WdfRequestSend(pRCB->Request, Adapter->IoTarget, WDF_NO_SEND_OPTIONS) == FALSE) {
        status = WdfRequestGetStatus(pRCB->Request);
    }

    DEBUGP(MP_LOUD, ("<-- NICPostReadRequest\n"));

    return status;
}

在这里向上层协议驱动提交包,并且如果包的引用为0,则释放包。

在读请求完成时,数据包才被完整接收到,此时可以操作

VOID
NICReadRequestCompletion(
    IN WDFREQUEST                  Request,
    IN WDFIOTARGET                 Target,
    PWDF_REQUEST_COMPLETION_PARAMS CompletionParams,
    IN WDFCONTEXT                  Context
    )
/*++

Routine Description:

    Completion routine for the read request. This routine
    indicates the received packet from the WDM driver to
    NDIS. This routine also handles the case where another
    thread has canceled the read request.

Arguments:

    DeviceObject    -  not used. Should be NULL
    Request    -   Pointer to our read Request
    Context - pointer to our adapter context structure

Return Value:

    STATUS_MORE_PROCESSING_REQUIRED - because this is an asynchronouse Request
    and we want to reuse it.

--*/
{
    PRCB         pRCB = (PRCB)Context;
    PMP_ADAPTER  Adapter = pRCB->Adapter;
    ULONG        bytesRead = 0;
    BOOLEAN      bIndicateReceive = FALSE;
    NTSTATUS     status;

    UNREFERENCED_PARAMETER(Target);
    UNREFERENCED_PARAMETER(Request);

    DEBUGP(MP_TRACE, ("--> NICReadRequestCompletion\n"));

    status = CompletionParams->IoStatus.Status;


    if (!NT_SUCCESS(status)) {

        Adapter->nReadsCompletedWithError++;
        DEBUGP (MP_LOUD, ("Read request failed %x\n", status));

        //
        // Clear the flag to prevent any more reads from being
        // posted to the target device.
        //
        MP_CLEAR_FLAG(Adapter, fMP_POST_READS);

    } else {

        Adapter->GoodReceives++;
        bytesRead = (ULONG)CompletionParams->IoStatus.Information;
        DEBUGP (MP_VERY_LOUD, ("Read %d bytes\n", bytesRead));
        Adapter->nBytesRead += bytesRead;
        bIndicateReceive = TRUE;
    }

    if (bIndicateReceive) {
        //向上层提交包
        NICIndicateReceivedPacket(pRCB, bytesRead);
    }

    if (NdisInterlockedDecrement(&pRCB->Ref) == 0)
    {
        NICFreeRCB(pRCB);
    }

    return;
}

这里向上提交包是小端口驱动向上通知包已经接收到。

本文固定链接: https://www.socarates.online/index.php/2024/01/12/ndis%e5%b0%8f%e7%ab%af%e5%8f%a3%e9%a9%b1%e5%8a%a8/ | 安全技术研究

avatar
该日志由 Socarates 于2024年01月12日发表在 内核 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: NDIS小端口驱动 | 安全技术研究
关键字: ,

NDIS小端口驱动:等您坐沙发呢!

发表评论


快捷键:Ctrl+Enter