Step deeply into NDIS6 LightWeight Filter, part 1

  ———-

[NOTE]: 本文基于WIN7,基于一个NDIS LWF Monitor项目。 

  我们从NDIS的驱动栈开始,系统在启动的时候(IoInitSystem…)会加载系统驱动,典型的比如卷过滤驱动Volsnap是从IopInitializeBootDrivers的IopInitializeBuiltinDriver中启动的,一般的Filter Driver则是在IopInitializeSystemDrivers的时候加载的,而各层的DriverEntry的顺序不一定按栈自底向上走(比如TCPIP调用NdisRegisterProtocolDriver,比Miniport的注册早),但是各自初始化完了,NDIS会接管开始调整栈结构,依次调用Miniport的MiniportInitializeEx,LightWeight Filter的FilterAttach,到最后的Protocol的ProtocolBindAdapterEx。

 

  {DriverEntry}

  挑我们感兴趣的,LWF的DriverEntry,略过初始化全局变量,读注册表等等,剩下关键点有两个:

  1) NdisFRegisterFilterDriver

       NdisFRegisterFilterDriver函数本身到底做了什么?

        1) 填充一个NDIS_FILTER_DRIVER_BLOCK;
        2) 调用SetOptionsHandler,返回成功则继续把填充的结构链接进ndisFilterDriverList;
        3) 检查下层Miniport是否可以把Filter Attach上去,如果Filter都加载了,那么Queue一个BindWorkitem,让Protocol有机会进行绑定。

       [NOTE1]: SetOptionsHandler先于FilterAttach,是第一个被调用的入口函数。但是实际上SetOptionsHandler在目前的WIN版本中只是STUB,能做什么大家自己发掘。
       [NOTE2]: NdisFRegisterFilterDriver的第四个参数是个OUT宏修饰的类型,它返回的实际上就是填充的PNDIS_FILTER_DRIVER_BLOCK。

  2) NdisRegisterDeviceEx

        一般情况下,驱动开发人员会在DriverEntry里创建Device方便与应用层通讯。
        而在LWF的DriverEntry里,你可以使用NdisRegisterDeviceEx创建一个Root Device以供通信,典型的用途是IRP_MJ_DEVICE_CONTROL枚举所有的网络接口。

        值得大书一笔的是,NdisRegisterDeviceEx创建的Device,要获取DeviceExtension(开发人员定义的那部分)必须使用NdisGetDeviceReservedExtension。原因在于,NdisRegisterDeviceEx在申请Extension的空间后,会自己预先创建一个类似DeviceExtension Header的结构加在你定义的DeviceExtension前面。

        具体NdisRegisterDeviceEx会做什么?略过其他情况,我们只关心Filter Driver里调用这个函数。

        首先,它从第一个参数NdisObjectHandle(记得刚才我们说过,这个参数实际上就是一个PNDIS_FILTER_DRIVER_BLOCK)里获取DriverObject;
        然后,IoCreateDevice,IoCreateSymbolicLink,之后拿到DeviceObject->DeviceExtension,填充它的开头的0xA0部分,0xA0之后是用户自己定义的部分,在偏移0×14的存放着一个指针,指向0xA0之后,而NdisGetDeviceReservedExtension正是获取DeviceObject->DeviceExtension + 0×14这个指针。
        另外,0×0偏移是个Type,被赋值为9(我不太清楚到底是什么,应该是和CDO关联的),而传入的参数DeviceAttribute.MajorFunctions将被拷贝到偏移0×18处。
        在这个函数内部有个地方是要值得注意的,如果是Filter Driver调用的这个函数,那么会有这么一句:
          memset(DriverObject->MajorFunction, ndisDummyIrpHandler, sizeof(DriverObject->MajorFunction));
        作用显而易见,而在ndisDummyIrpHandler函数里会判断Type,是17的放过,是9的则调用DeviceExtension + 0×18处的MajorFunction。

        之后在FilterAttach里我们会再解释上面没提到的其他偏移值,这里先Mark。

  /////////////////////////////////

  对于NDIS6 Filter来说,NDIS_FILTER_DRIVER_CHARACTERISTICS是整个Filter的主体。

  [NOTE]: 关于Filter状态的入口函数是必须提供的,包括:FilterAttach,FilterDetach,FilterRestart,FilterPause。

  {FilterAttach}

  Q1: FilterAttach函数的调用过程?

   撇开问题,我们先回过头去看NDIS驱动栈的构造初期,有几个函数,我们看看他们都具体做了什么:

     NdisRegisterProtocolDriver(NDIS6),NdisFRegisterFilterDriver(NDIS6),NdisMRegisterMiniportDriver(NDIS6),NdisMRegisterMiniport,NdisIMRegisterLayeredMiniport,NdisRegisterProtocol等

   首先看Miniport,我们以NdisMRegisterMiniportDriver为例,NDIS5或者之前的NdisMRegisterMiniport等会调用ndisRegisterMiniportDriver,但是函数内部过程都是类似的。

    1) 调用IoAllocateDriverObjectExtension创建一个NDIS_M_DRIVER_BLOCK结构,填充之;
    2) 填充DriverObject->MajorFunction,将ndisPnPAddDevice赋值给DriverObject->DriverExtension->AddDevice;
    3) 调用SetOptionsHandler,将NDIS_M_DRIVER_BLOCK链接进ndisMiniDriverList。

    [NOTE]: 同样的,NdisMRegisterMiniportDriver的第四个参数NdisMiniportDriverHandle其实就是PNDIS_M_DRIVER_BLOCK*类型。

    而在系统启动的过程中,将由PNP Mgr负责遍历并调用各PNP驱动的AddDevice,如:

      nt!KiThreadStartup+0×19
      nt!PspSystemThreadStartup+0x9e
      nt!ExpWorkerThread+0x10d
      nt!PnpDeviceActionWorker+0×241
      nt!PiProcessStartSystemDevices+0x6d
      nt!PipProcessDevNodeTree+0x15d
      nt!PipCallDriverAddDevice+0×565
      nt!PnpCallAddDevice+0xb9
      nt!PpvUtilCallAddDevice+0×19
      ndis!ndisPnPAddDevice+0x5db
      ndis!ndisAddDevice+0x6e4

    我们跟进ndisAddDevice,它做的事很多很繁琐:

     1) IoGetDriverObjectExtension获取Driver Extension,实际上就是之前的NDIS_M_DRIVER_BLOCK结构;
     2) IoCreateDevice创建设备,然后IoAttachDeviceToDeviceStack,再IoCreateSymbolicLink;
     3) 获取DeviceObject->DeviceExtension,按照NDIS_MINIPORT_BLOCK结构填充之,注意这里Type被赋值为17;
     4) 调用ndisInitializeConfiguration进行配置信息的填充,其内部再调用ndisReadMiniportFilterList填充LWFilterList;
     5) IoRegisterDeviceInterface注册一个设备接口;
     6) 将填充完的NDIS_MINIPORT_BLOCK链接进ndisMiniportList。

   然后我们看NdisRegisterProtocolDriver:

    1) 分配一个NDIS_PROTOCOL_BLOCK结构,填充之;
    2) 调用SetOptionsHandler,将填充完的NDIS_PROTOCOL_BLOCK链接进ndisProtocolList;
    3) 调用ndisQueueWorkItem,入队一个工作者例程ndisCheckProtocolBindings。

   现在我们回到问题,FilterAttach是什么时候被调用的?  

    nt!KiThreadStartup+0×19
    nt!PspSystemThreadStartup+0x9e
    ndis!ndisWorkerThread+0xa4
    ndis!ndisCheckProtocolBindings+0x11b
    ndis!ndisCheckMiniportFilters+0×105
    ndis!ndisAttachFilterToMiniport+0xa9b
     ndisFindFilterPosition
     xxx!xxxFilterAttach

    我们看ndisAttachFilterToMiniport,它被调用的时候是这样的:
      ndisAttachFilterToMiniport(CurFilterDriver, NULL, MiniBlock);
    其中,MiniBlock来自ndisMiniDriverList里的MiniportQueue,CurFilterDriver来自ndisFilterDriverList。

    ndisFindFilterPosition原型如下:
     BOOLEAN ndisFindFilterPosition(
         IN PNDIS_MINIPORT_BLOCK NdisMiniBlock,
         IN PUNICODE_STRING LowerFilterName,
         IN PUNICODE_STRING UniqueName,
         IN UCHAR FilterFlag,
         OUT PUNICODE_STRING *RetName,        
         OUT PNDIS_FILTER_BLOCK *LowerFilter,
         OUT PNDIS_FILTER_BLOCK *HigherFilter
         );
-
其中RetName依次经历MiniBlock->LWFilterList -> FilterInstanceName -> FilterModuleGuidName。

    ndisAttachFilterToMiniport会创建一个NDIS_FILTER_BLOCK结构,填充之后链接进ndisGlobalFilterList。
    接下来是填充FilterAttach所需要的第三个参数NDIS_FILTER_ATTACH_PARAMETERS,基本上都是来自于MiniBlock,有兴趣的可以自己跟一下。
    然后调用CurFilterDriver->DefaultFilterCharacteristics.AttachHandler,第一个参数就是创建的NDIS_FILTER_BLOCK,第二个参数是CurFilterDriver->FilterDriverContext,也就是NdisFRegisterFilterDriver的第二个参数返回值。

  Q2: FilterAttach函数该怎么写?

   代码只为演示,请勿直接使用。

-
    NDIS_STATUS
    NetmonFilterAttach(
        IN NDIS_HANDLE NdisFilterHandle,
        IN NDIS_HANDLE FilterDriverContext,
        IN PNDIS_FILTER_ATTACH_PARAMETERS AttachParameters
        )
    /*++

    Routine Description:

    Arguments:

    Return Value:

    –*/

    {
        PMS_FILTER pFilter = NULL;
        NDIS_STATUS Status = NDIS_STATUS_FAILURE;
        NDIS_FILTER_ATTRIBUTES FilterAttributes;
        WCHAR FilterLevel = L’0′;

        KdDebugIn(2);

        do
        {
            if (AttachParameters->FilterModuleGuidName->Length)
            {
                FilterLevel = AttachParameters->FilterModuleGuidName->Buffer[\
                    ((AttachParameters->FilterModuleGuidName->Length - 1) >> 1)];
            }

            if (FilterDriverContext != (NDIS_HANDLE)g_FilterDriverObject)
            {
                Status = NDIS_STATUS_INVALID_PARAMETER;
                break;
            }

            if (!g_AttachUpperLayers && FilterLevel != L’0′)
            {
                if (g_NmDebug)
                {
                    DbgPrintEx(
                        DPFLTR_IHVNETWORK_ID,
                        0,
                        “Discarding:%ws”,
                        AttachParameters->FilterModuleGuidName->Buffer
                        );
                }

                Status = NDIS_STATUS_INVALID_PARAMETER;
                break;
            }

            if (AttachParameters->MiniportMediaType == NdisMediumWan)
            {
                if ((AttachParameters->BaseMiniportName->MaximumLength != 0×24) ||
                    (RtlCompareMemory(
                    L”\\DEVICE\\NDISWANBH”,
                    AttachParameters->BaseMiniportName->Buffer,
                    0×24) != 0×24))
                {
                    break;
                }
            }
            else
            {
                if (AttachParameters->MiniportMediaType == NdisMediumNative802_11 &&
                    (FilterLevel != L’0′))
                {
                    Status = NDIS_STATUS_FAILURE;
                    break;
                }
            }

            // Let’s Create Filter Modules..
            //
            {
                if (NmCreateFilterModule(NdisFilterHandle, AttachParameters, &pFilter)
                    != NDIS_STATUS_SUCCESS)
                {
                    Status = NDIS_STATUS_RESOURCES;
                    break;
                }
                else
                {
                    NdisZeroMemory(&FilterAttributes, sizeof(NDIS_FILTER_ATTRIBUTES));

                    FilterAttributes.Header.Revision = NDIS_FILTER_ATTRIBUTES_REVISION_1;
                    FilterAttributes.Header.Size = sizeof(NDIS_FILTER_ATTRIBUTES);
                    FilterAttributes.Header.Type = NDIS_OBJECT_TYPE_FILTER_ATTRIBUTES;
                    FilterAttributes.Flags = 0; 

                    Status = NdisFSetAttributes(
                        NdisFilterHandle,
                        pFilter,
                        &FilterAttributes
                        );
                    if (Status != NDIS_STATUS_SUCCESS)
                    {
                        break;
                    }

                    NmInitializeMinipInfo(AttachParameters->MiniportMediaType, pFilter);

                    pFilter->State = FilterPaused;
                    NmInitializeTimerSystem(pFilter);

                    FILTER_ACQUIRE_LOCK(&g_FilterListLock, FALSE);
                    InsertHeadList(&g_FilterModuleList, &pFilter->FilterModuleLink);
                    FILTER_RELEASE_LOCK(&g_FilterListLock, FALSE);
                }
            }

        } while (FALSE);

        return Status;
    }
———————–

    1) FilterLevel是什么意思?

       FilterLevel取的是FilterModuleGuidName(形如{xxx-xxxx}-0001)的最后一个字符,它代表的是Filter所在的位置,或者粗略地理解成Filter的第几个实例。
       在LWF的INF安装文件里一般要设置一个注册表值,如 HKR, Ndi,FilterClass,,XXX,FilterClass决定了LWF在整个栈里的位置,具体含义大家可以参考WDK。而在WIN7上默认有另外两个LWF存在,WfpLwf是最高层的ms_firewall_upper,另一个Psched是第二层的scheduler。当再一个LWF加入的时候,可能会插在整个栈的各层,比如某LWF Monitor就可能产生如下的布局:
         {701D0081-81F3-494C-BEBB-B944C752E841}-{6E022F38-AB31-44C5-8206-2EB023EFF145}-0000
         {701D0081-81F3-494C-BEBB-B944C752E841}-{B5F4D659-7DAA-4565-8E41-BE220ED60542}-0000 // Psched
         {701D0081-81F3-494C-BEBB-B944C752E841}-{6E022F38-AB31-44C5-8206-2EB023EFF145}-0001
         {701D0081-81F3-494C-BEBB-B944C752E841}-{B70D6460-3635-4D42-B866-B8AB1A24454C}-0000 // WfpLwf
         {701D0081-81F3-494C-BEBB-B944C752E841}-{6E022F38-AB31-44C5-8206-2EB023EFF145}-0002

这个可以在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\XXX\Linkage\FilterList下看到。

        从上到下对应的是驱动栈的自底向上。{6E022F38-…}对应的是某LWF Monitor,{701D0081-…}是物理网卡。

       这样,FilterLevel对应的就是最后的那个字符,0、1或者2。

    2) FilterDriverContext之前已经解释过了,就是NdisFRegisterFilterDriver的第二个参数值,可具体灵活运用。

    3) g_AttachUpperLayers是在DriverEntry里初始化全局变量时从注册表读取的值,由用户设置,它标志着是不是启用类似{xxx}-0001的LWF实例。一般情况下,{xxx}-0000最贴近Miniport,如果只是监视或者嗅探Miniport上的数据,那么不要上层的那些Filter也是可以的。当然,如果您的Filter不只是Monitor,那就另当别论了。

    4) 接下来是两个判断,如果MiniportMediaType是NdisMediumWan,我们只Attach到NDISWANBH上,其他的不关心;
       而另外一种情况,NdisMediumNative802_11,无线设备没有UpperLayer,如果FilterLevel不是0,那么我们返回。

    5) 排除上面的几种,剩下的Miniport都是我们关心的了,我们对每个都创建一个Filter Device,Attach上去。
       创建成功之后,将DeviceExtension返回,也就是pFilter。然后必须调用NdisFSetAttributes,通知Lwf Miniport,这样pFilter才能被传给其他FilterXXX入口函数。

    6) InitializeMinipInfo会发OID向各个Miniport查询信息,保存到pFilter,并且向Miniport发OID设置Packet Filter以捕获数据;InitializeTimerSystem设置Timer,及时报告数据接收发送的状态信息以及其他的一些定时信息。

    7) g_FilterModuleList链表保存着每个Filter Device的上下文信息pFilter,也就是DeviceExtension。它可以用来给Root Device的IRP_MJ_DEVICE_CONTROL派发例程提供比如枚举所有网络接口的时候需要的相关信息。

   至于CreateFilterModule创建Filter Device,有几点想说明的:

    1) Filter Device的名字可以采用”前缀+[FilterModuleGuidName]“的形式,这样方便以后区分和处理;
    2) 对AttachParameters->MiniportMediaType是NdisMediumNative802_11的情况,可以在这里处理,为无线设备创建专门的结构体记录;
    3) Create Device的时候,你可以使用NdisRegisterDeviceEx,这样步骤会简单很多,但是以后访问内部成员会很费力;

       如果想自己IoCreateDevice,那就要注意仿造NdisRegisterDeviceEx的实现,注意DeviceExtensionSize的大小。

       在{DriverEntry}里,已经提到几个重点,DeviceExtension Header类似下面:

/*0×000*/NDIS_OBJECT_HEADER Header;
/*0×004*/LIST_ENTRY FilterModuleLink;
/*0x00C*/NDIS_HANDLE FilterHandle;
/*0×010*/PDEVICE_OBJECT FilterDeviceObject;
/*0×014*/PVOID UserDeviceExtention;

/*0×018*/PDRIVER_DISPATCH DispatchTable[IRP_MJ_MAXIMUM_FUNCTION + 1];

/*0×088*/NDIS_STRING DeviceName;
/*0×090*/NDIS_STRING SymbolicLinkName;
/*0×098*/NDIS_STRING GuidName;
 

       (1) Header.Type 必须是 9,而且DispatchTable必须在DeviceExtension + 0×18处,否则派发例程将得不到执行;
       (2) pFilter必须包含以上除0×14外的内容,UserExtention的就具体问题具体设计了;
       (3) 什么时候DestroyFilterModule,这个可以使用引用数。

   当然,由于本驱动的特殊性,只是个Monitor而不是Modifier,所以比如NetBufferLists、NetBuffers pool的分配管理,读取Configuration等等,都没有涉及到,可以参考WDK的内容:Attaching a Filter Module。

下篇继续补完其他的FilterXXX入口函数,以及接受发送数据包时候的处理。

———


2 Responses to “Step deeply into NDIS6 LightWeight Filter, part 1”

  1. Lewis 说道:

    膜拜

    [回复]

  2. Luke 说道:

    可以做一些Modifier这方面的讨论与研究,比如IpDir,或者虚拟网关。

    [回复]

Post a Comment