NDIS中间层驱动程序

概要:开发一个NDIS驱动是一项相对复杂的工作,这一方面是由于核心驱动本身有更多的限制和要求,有更多的“游戏规则”要求开发者理解和掌 握,NDIS的复杂性把难度更是提高了,本文以PassThru为例,加上自己的理解,讲述了NDIS驱动的处理过程和在PassThru的基础上进行扩 展的基本方法,本文并不是一个入门读物,所以没有提及任何核心驱动开发的相关知识,本文主要讲述的是NDIS中间层对数据包处理的流程。在阅读过程中,关 于相关API的用法,或其它信息,请参看DDK文档。

一  NDIS驱动程序分类.
    NDIS(Network Driver Interface Specification)是Windows网络驱动程序接口标准,NDIS驱动程序分为三类:
1. NDIS Miniport NIC Driver: 底层的微端口NIC驱动,这就是网络设备的物理的驱动程序了。
2. NDIS Protocol Driver: 高层的协议驱动,用来实现某个具体的协议栈,如TCP/IP协议栈,
   并向上导出TDI接口。
3. NDIS Intermediate Driver: 中间层驱动,位于Miniport Driver和Protocol Driver之间。

二  NDIS驱动结构简介.

 
                                 TDI(Transport Driver Interface)
          _______________________________________________________
                           |                     |  
                   ________|__________    _______|_______  
                  |                   |  |               |
           _____  |  LAN Protocols    |  |               |
           |    | |___________________|  |               |
           |    |_____________________   |    Native     |
           |                          |  |    Media      |
           | N       LAN Media Type   |  |    Aware      |
           | D   _____________________|  |    Protocol   |
           | I  |   __________________   |               |
           | S  |  |                  |  |               |
           |    |  |NDIS Intermediate |  |               |
           | I  |  |__________________|  |_______________|
           | N  |_________________________________________
           | T                                            |
           | E             Native Media Type              |    
           | R  __________________________________________|
           | F  |  _________________    __________________
           | A  | |                 |  |                  |
           | Cnbsp; | | NDIS Miniport   |  |  NDIS Miniport   |
           | E  | |_________________|  |__________________|
           |    |_________________________________________
           |                                              |
           |                NDIS Interface                |
           |______________________________________________|
                    ________________    ______________
                   |                |  |              |
                   |    NetCard     |  |   NetCard    |
                   |________________|  |______________|
                                    
图一
     其中,最上层是一个NDIS Protocol Driver,它向上提供一个Transport Driver Interface(TDI),向下通过NDIS接口与下面的NDIS中间层的上边界交互,NDIS中间层的下边界通过NDIS接口与下层的NDIS Miniport Driver交互。最后,由NDIS Miniport Driver利用NDIS接口与物理网络设备NetCard交互。NetCard是由不同的网卡设备产商提供的,而NDIS接口库是由Microsoft 开发好的,为什么NDIS Miniport Driver不是直接与物理网卡交互,而是通过NDIS接口与下物理网卡交互呢?(我想很多人都会和我当初一样,有这个疑问)。
    事实上, 这是由于Windows系统为了提高可移植性,而设计出一个硬件抽象层(HAL),硬件抽象层在内部处理不同的硬件之间的差异,并且暴露出一个统一的接口 给核心驱动开发者。例如:在Intel构架的系统中,内存和外部设备的端口采用分别编址,如果要从某个外部设备的端口上读写数据的话,可能要通过专用指令 IN/OUT读写,而在Alpha构架的系统上,采用的是统一编址的方式,所以对外部设备的IO端口进行读写的话还是通过访问内存的指令,HAL提供一组 服务支持函数,如果要访问外部设备上的端口数据可以使用READ_PORT_UCHAR/WRITE_PORT_UCHAR等等,核心驱动开发者不用去考 虑不同硬件构架的之间的差异。在NDIS Miniport Driver中,NetCard驱动的程序,正是这样通过NDIS接口提供的一组类似功能的函数,与物理的网络设备进行交互。其中,最上层是一个NDIS Protocol Driver,它向上提供一个Transport Driver Interface(TDI),向下通过NDIS接口与下面的NDIS中间层的上边界交互,NDIS中间层的下边界通过NDIS接口与下层的NDIS Miniport Driver交互。最后,由NDIS Miniport Driver利用NDIS接口与物理网络设备NetCard交互。

三 NDIS驱动程序的数据处理流程
         ________________________    _____
        |                        |  |     |
        |    Transport Driver    |  |     |
        |________________________|  |     |  
        | Protocol Xxx - Media X |  |     |
        +------------------------+  |     |
         ___________________________|     |
        |___________________________      |                        
                                    |     |          
         ________________________   |     |
        | Miniport Xxx - Media X |  |     |
        +------------------------|  |     |
        |                        |  |     |
        |  Intermediate Driver   |  |     |
        |________________________|  |     |  
        | Protocol Xxx - Media Y |  |     |
        +------------------------+  |     |
                                    |     |
         ___________________________|     |
        |___________________________      |
                                    |     |
         ________________________   |     |
        | Miniport Xxx - Media Y |  |     |
        +------------------------|  |     |
        |                        |  |     |
        |      NIC Driver        |  |     |
        |________________________|  |     |  
                     nbsp;              |     |
         ___________________________|     |
        |_________________________________|  
             _________________
            |                 |
            |       NIC       |
            |_________________|
                   图二    

3.1 三种NDIS驱动程序的关系。                    
     通常一个NDIS Protocol Driver 的上边沿导出TDI接口,并在其下边沿向NDIS注册一组Protocolxxx操作例程;一个NDIS Miniport Driver则在其下边沿通过NDIS接口操作物理网络设备,并在其上边沿向NDIS注册一组Miniportxxx操作例程。当一个中间层介入的时候, 必需遵守这个规则,因此,中间层驱动对上层来说,扮演一个Miniport Driver的角色,它在上边沿向NDIS注册一组Miniportxxx函数;对于下层Miniport Driver来说,中间层驱动扮演一个Protocol Driver的角色,因此它在下边沿向NDIS注册一组Protocolxxx函数。Miniport Driver通过调用NdisMRegisterMiniport向NDIS注册一组MiniportXxx函数。其中原型如下:
    NDIS_STATUS NdisMRegisterMiniport(
        IN NDIS_HANDLE  NdisWrapperHandle,
         IN PNDIS_MINIPORT_CHARACTERISTICS  MiniportCharacteristics,
         IN UINT  CharacteristicsLength
         );
    其中,NdisWrapperHandls是之前通过调用NdisMInitializeWrapper取得的句柄MiniportCharacteristics包含一组MiniportXxx函数指针。

    Protocol Driver 通过调用 NdisRegisterProtocol向NDIS注册一组ProtocolXxx函数。其中原型如下:
    VOID NdisRegisterProtocol(
        OUT PNDIS_STATUS  Status,
        OUT PNDIS_HANDLE  NdisProtocolHandle,
        IN PNDIS_PROTOCOL_CHARACTERISTICS  ProtocolCharacteristics,
        IN UINT  CharacteristicsLength
        );
     其中,ProtocolCharacteristics包含一组ProtocolXxx函数。由于NDIS Intermediate Driver的双重性,它需要调用 NdisIMRegisterLayeredMiniport 向NDIS注册,并向上层导出一组MiniportXxx函数,之后,调用NdisRegisterProtocol向NIDS注册,并向下层导出一组 ProtocolXxx函数。当一个NDIS中间层介入后,如图二所示。

3.2 NDIS数据发送流程:
    当上层协议驱动 要发数据时,调用NdisSend/NdisSendPackets请求NDIS发送数据包,NDIS将会调用紧接其下的中间层驱动的 MiniportSend/MiniportSendPackets,中间层驱动MiniportSend/MiniportSendPackets有机 会在这里对包进行必要的操作,然后,中间层驱动再次调用NdisSend/NdisSendPackets请NDIS发送数据包,NDIS将调用其下层的 Miniport Driver的MiniportSend/MiniportSendPackets,底层 MiniportSend/MiniportSendPackets通过NDIS接口控制物理网络设备,将数据发送出去。在上层请求发送数据包时,上层分 配了相关的资源(如内存),希望在下层完成发送动作后,能够及时的收回相关的资源,所以,当上层调用NdisSend/NdisSendPackets返 回 NDIS_STATUS_PENDING以外的任何值时,上层就可以释放资源了,如果得到返回的结果是NDIS_STATUS_PENDING话,说明下 层还没有完成发送请求,以后,等下层最终完成发送请求时,下层调用NdisMSendComplete请求NDIS通知上层可以释放资源了,于是NDIS 调用上层注册的ProtocolSendComplete函数,上层在这它的ProtocolSendComplete中释放了资源后,再次调用 NdisMSendComplete请求NDIS通知更上层。

3.3 NDIS数据接收流程:
    当底层网络设备有数据到来 的时候,将触发中断,相应的中断处理程序接管中断后,将可能调用Miniport Driver所注册的中断处理例程(ISR),Miniport Driver通常在这里把网卡上的数据考贝到Miniport Driver缓冲区队列中去,出于效率的考虑,Miniport Driver这时可能不会立即通知上层处理新的数据,因为很可能,马上还有随后的新的数据到来,当接收到的包的数量达到一定程度的时候,Miniport Driver会调用NdisMIndicateReceivePacket指示新的NDIS新数据的到来,这时候, NdisMIndicateReceivePacket的调用将导致NDIS调用位于Miniport上层的中间层向NDIS注册的下边沿 ProtocolReceivePacket函数。中间层驱动程序的ProtocolReceivePacket可以对收到的数据包进行相应的处理,之 后,可以选择再次调用NdisMIndicateReceivePacket请求NDIS通知更上层数据包的到来,这时,NDIS调用更上层注册的 ProtocolReceive函数,上层的ProtocolReceive对数据包进行必要的处理后,继续调用 NdisMIndicateReceivePacket请求NDIS,通知更上层,最终数据包传到协议驱动中,由相关的协议栈进行处理。
     有时候,在这种级连的上传过程中,并不是那么的顺利,例如,由于某种原因,如:网络设备的驱动程序的可用缓冲区数量减少到某个指定的数量时,网络设备的驱 动程序调用NdisMxxxIndicateReceivePacket请求NDIS通知上层数据的到来,这时NDIS将调用上层注册的 ProtocolReceive,上层在ProtocolReceive中进行必要的处理后,进一步调用 NdisMxxxIndicateReceivePacket使得NDIS调用上层的协议驱动注册的ProtocolReceive。在一些不太理想的情 况下,一次中断,从网络设备中接收到的数据对某个协议来说并不是一个完整的协议数据包。(一般情况下,其余的数据bit已经在链路途中了,并随后立即就会 到达网络设备,这有可能就发生在一个CPU在处理网络设备中断的同时,网络设备的板载存储器上就已经收到了其余的数据了,甚至DMA控制器可能已经把这些 数据传到了系统的主存储器上了),这时,上层的ProtocolReceive都无法进行正常的处理(一般对某个包进行处理,都要以相关的协议为依据,进 行分析)。这时,在上传到某一层时,可以调用NdisTransferData请求NDIS把随后的信息传上来,这时,NDIS又将在向上传递的途中回过 头来向下调用下面的MiniportTransferData,下层重复调用NdisTransferData把这个请求传送到底程的Miniport Driver。如果在上层调用NdisTransferData时不是返回NDIS_STATUS_PENDING,则上层可以继续它的处理,而如果返回 NDIS_STATUS_PENDING,底程在最终完成请求时,调用NdisMTransferDataComplete请求NDIS通知上层传送完 成,这将导致上层注册的ProtocolTransferDataComplete被调用,上层调用NdisMTransferDataComplete 请求NDIS通知更上层。由于硬件技术的发展,网络设备板载存储的增加,系统主存储器的增加,以及网络传输能力的改善,NDIS那么迂回的通过 MdisMxxxIndicateReceivePacket,ProtocolReceive,MiniportTransferData这一条坎坷的 路径进行数据处理的情况似乎越来越少见了。
    接收过程是由下层传递上去的,同样,底层的Miniport分配了一些资源,如用于存储这个数 据包内容的内存,我们希望这些资源最终能极时的被归还,以供以后使用。一个包在从下到上的传递过程时,如果某一层的 ProtocolReceive/ProtocolReceivePacket有兴趣对这个包进行处理的话,则需要检查这个包的OOB信息段是不是携带 NDIS_STATUS_RESOURCES,如果是的话,说明其下层资源紧缺,希望上层在处理的时候,自己考贝一份副本,以供自己使用,因为下层希望自 己能够尽快收回这个包的资源,在这里,上层以后可以用自己的那份拷贝指示上层数据包的到来,这样的话,以后,中间层希望上层处理完后,能够收回所有权。另 一方面,底层的Miniport并不是每一次都会在OOB信息段中设置NDIS_STATUS_RESOURCES标志的,这在很多情况下是不必要的。当 上层协议驱动完成处理时,可以调用NdisReturnPackets通知NDIS,相应包已经处理完成,可以安全的释放相关资源了,于是NDIS将调用 其下层注册的MiniportReturnPackets,下层在这里释放与这个包相关的资源,并继续调用NdisReturnPackets,请求 NDIS把这个通知传给更下层。
    
    这样一来,发数据由上层发起请求往下传,而接收数据从下层往上传,有没有可能一次接收的 过层是由上面发起往下传的呢?这是没有必要的,为什么?我的应用程序不就主动调用WSARecv或WSARecvFrom等函数主动向下传递的一次收数据 的请求的吗?事实上是这样的,你的接收请求经过SPI,TDI,到了最终的协议驱动时,如果协议驱动中,指定的套接字上(最重要的是端口和目的I了)有相 应的数据的能满足你的这一次的读请求的话,就完成你的请求,如果不能的话,则阻塞了。当下层有数据来的时候,数据传递到协议驱动时,协议驱动会检查包头的 信息并查看当前不是有应用程序打开过相应的端口,以及和对应的目标建立过连接,如果有的话,就把数据存到协议栈中的缓冲区中去,如果对应套接字上有阻塞的 接收请求的话,就判断是不是能完成它的请求了,如果能了,就完成他,如果不能,继续等以后下层传上来再重复这个过程。否则就抛弃了,另外,如果,一个应用 程序建立一个套接字,并与一台机器建立连接,对方发送了数据,协议栈会把数据保存起来,也许连接超时后,协议栈的缓冲区中还有应用程序没有接收的数据的 话,也被抛弃了。所以,协议驱动从来都没有必要主动往下传递一个接收数据的请求。这一点,对于有一点网络程序调试经验的人来说,似乎可以直接找到一个的证 据,在调试器的调用WSARecv前下一个断点,用Sniffer抓包,可能数据已经到来了,然后,再回到调试器中执行WSARecv,可以看到收到的就 是先前Sniffer下来的数据了。
(注:用一个工作在NDIS协议层或是NDIS中间层的Sniffer来观查。)

3.4 NDIS 的数据包结构。
     发送NDIS Protocol Driver分配相关的包资源,请求NDIS向下传递发送动作,接收时,NDIS Miniport Driver分配相关的包资源请求NDIS向上传递接收动作,中间层要对包进行处理,首先通过包描述符查询出这个包中的所有Buffer描述符,然后,从 每一个Buffer描述符中取得相应的数据。一个包描述符中包含了一个或多个Buffer描述符,每一个Buffer描述符中包含了这个Buffer中数 据的缓冲区首地址及其大小等信息,另外,一个包描述符中还包括了一些保留给开发者自己使用的Reserved字段,其于的一些字段,并没有被公开。另外 NDIS提供了一些宏和一些函数对这相应的描述符进行操作。为什么要把这个结构弄得这么复杂呢?这是由于面对分层的协议处理的时候,避免过多的数据考贝, 比如对于TCP/IP协议来说,上层传递下来的数据,在经过TCP,IP层时,把TCP,IP协议头部那个Buffer分别加入这个Packet来就可以 了,如果,要它们在一个Buffer中,则需要在经过TCP层时,TCP层建立一个新的Buffer,把头部放到这个Buffer中来,并把数据考贝到这 个Buffer中来;到了IP层还要继续这一动作......

四 基于PassThru框架的中间层驱动程序的扩展实现
    Microsoft 在DDK中附带PassThru提供了一个的中间层驱动框架,使得开发者能够相对容易的在这个基础实现自己的NDIS中间层驱动扩展。我们将在 PassThru的基础上实现一个基本的数据包操作的扩展。对于发送出去的数据包处理,只要在PassThru中的MiniportSend和 MiniportSendPackets中加入必要的操作代码,而对于接收的数据包时,则需要在ProtocolReceive和 ProtocolReceviePackets中加入必要的操作代码,在这里,我将以windows 2003 DDK的PassThru为例,进行扩展。

4.1 发送处理
VOID MPSendPackets(
    IN NDIS_HANDLE             MiniportAdapterContext,
    IN PPNDIS_PACKET           PacketArray,
    IN UINT                    NumberOfPackets
    )
{
    // 省略代码若干,请参看PassThru的源代码。
    
    // 分配一个新的包描述符。
    NdisAllocatePacket(&Status, &MyPacket, pAdapt->SendPacketPoolHandle);
    if (Status == NDIS_STATUS_SUCCESS)
    {
        PSEND_RSVD        SendRsvd;
        SendRsvd = (PSEND_RSVD)(MyPacket->ProtocolReserved);
        // 把原来的包描述符保存在新分配的包描述符中的Reserved字段中,原因在后面描述。
        SendRsvd->OriginalPkt = Packet;
    }  
    // 调用BuildMyPacket对包进行自己的处理(更改包的内容,或其它动作)。
    if (BuildMyPacket(pAdapt, Packet, MyPacket) == FALSE)
    {
        // 如果处理失败,则把原始的包信息Copy到MyPacket。
        // 这是为了在处理失败的情况下,也让原始的信息能发出去。
        NDIS_PACKET_FIRST_NDIS_BUFFER(MyPacket) = NDIS_PACKET_FIRST_NDIS_BUFFER(Packet);
        NDIS_PACKET_LAST_NDIS_BUFFER(MyPacket) = NDIS_PACKET_LAST_NDIS_BUFFER(Packet);
    }
    // ......
    // 请求NDIS向下层传递发送动作。
    NdisSend(&Status, pAdapt->BindingHandle, MyPacket);
    // 如果NdisSend返回非NDIS_STATUS_PENDING则释放自己的资源。
    if (Status != NDIS_STATUS_PENDING)
    {
        DestroyMyPacket(MyPacket);
        NdisFreePacket(MyPacket);        
    }
      
    // .......
    // 如果NdisSend返回非NDIS_STATUS_PENDING,说明下层已成功发送完成
    // 调用NdisMSendComplete请求NDIS通知上层释放资源,这将导致上层注册的
    // ProtocolSendComplete被调用,上层在ProtocolSendComplete中释放资源后
    // 将继续调用NdisMSendComplete请求NDIS把这个通知往上传递。
    if (Status != NDIS_STATUS_PENDING)
    {
        NdisMSendComplete(ADAPT_MINIPORT_HANDLE(pAdapt), Packet, Status);
    }
    // 如果返回了 NDIS_STATUS_PENDING,则在该层的资源还不能释放,在底层
    // 完成发送时,底层将调用NdisMSendComplete,请求NDIS向上传递这个通知。
    // 该层在自己的ProtocolSendComplete释放相应的资源。
}
    
VOID PtSendComplete(
    IN  NDIS_HANDLE            ProtocolBindingContext,
    IN  PNDIS_PACKET           Packet,
    IN  NDIS_STATUS            Status
    )
{  
    // .......
    // 上面发送的时候,我们分配了一个新的包描述符,并把上层的包描述符保存在
    // ProtocolReserved中,现在,把这个上层的包描述符还原出来。
    PSEND_RSVD        SendRsvd;
    SendRsvd = (PSEND_RSVD)(Packet->ProtocolReserved);
    Pkt = SendRsvd->OriginalPkt;
    
    // .......
    // 释放自己的资源
    DestroyMyPacket(Packet);
    NdisDprFreePacket(Packet);
    // 用上层的包描述符请求NDIS通知上层释放资源
    NdisMSendComplete(pAdapt->MiniportHandle, Pkt, Status);
}


BOOLEAN BuildMyPacket(
    IN PADAPT              pAdapt,
    IN PNDIS_PACKET        original_packet,
    OUT PNDIS_PACKET       MyPacket
    )
{
    PSEND_RSVD  SendRsvd;
    NDIS_STATUS Status;
    NDIS_PHYSICAL_ADDRESS phyaddr = {-1};
    PVOID pcontent = NULL;
    ULONG total_length = 0, current_length = ;
    PNDIS_BUFFER MyBuffer;

    // 分配新的内存
    Status = NdisAllocateMemory((PVOID)&pcontent, 2014, 0, phyaddr);
    if (NDIS_STATUS_SUCCESS != Status)
        return FALSE;
    NdisZeroMemory(pcontent, 2014);
    
    // 把包中的数据Copy到自己的Buffer中来。
    NdisQueryPacket(packet, NULL, NULL, &ndis_buffer, &total_length);
    while (NULL != ndis_buffer)
    {
        NdisQueryBufferSafe(ndis_buffer, &address, &current_length, NormalPagePriority);
        NdisMoveMemory(pcontent, address, current_length);
        (PUCHAR)pcontent += current_length;
        NdisGetNextBuffer(ndis_buffer, &ndis_buffer);
    }
    
    // 分配新的一个Buffer描述符                    
    NdisAllocateBuffer(&Status, &MyBuffer, pAdapt->SendPacketPoolHandle,
        pcontent, total_length);                    
    if (NDIS_STATUS_SUCCESS != Status)
    {
        NdisFreeMemory(pcontent, 2014, 0);
        return FALSE;
    }
    
    // 在这里对包的内容进行你自己的处理,如果修改了内容的话,由于这是在协议栈之下,
    // 所以要重新修正CheckSum。调整MyBuffer,和MyPacket相关信息,如长度等。
    // 注意,这里的数据已经是网络字节数据,所以在x86的处理器上要注意字节顺序的问题
    // 对于少量内容的修改,重新扫描整个Buffer修正CheckSum是不值得的。
    // RFC关于Nat的文档中详细描述了基于差异分析的修正CheckSum的方法,并给出了具体算法实现。
    
    // 把新的包描述符存放到新的包描述符中的MiniportReserved中去,原因在DestroyMyPacket中解释
    SendRsvd = (SEND_RSVD)MyPacket->MiniportReserved;
    SendRsvd->OriginalPkt = MyPacket;
    NdisChainBufferAtFront(MyPacket, MyBuffer);
    return TRUE;
}


VOID DestroyMyPacket(PNDIS_PACKET MyPacket)
{
    PNDIS_BUFFER MyBuffer;
    PVOID address;
    PNDIS_BUFFER tmpBuffer, MyBuffer;
    ULONG current_length;
    PSEND_RSVD  SendRsvd;

    // 由于在 BuildMyPacket,分配资源失败的情况下,仍然用原包发送出去,
    // 在这种情况下,是不用释放相关的Buffer资源的
    // BuildMyPacket 当分配成功时,我把新包中的 MiniportReserved 字段
    // 指向了新的包描述符,我以这个为依据判断是否需要释放相关的Buffer资源。
    SendRsvd = (PSEND_RSVD)MyPacket->MiniportReserved;
    if (MySendRsvd->OriginalPkt != MyPacket)
        return;
    NdisUnchainBufferAtFront(MyPacket ,&MyBuffer);     
    while (NULL != MyBuffer)
    {
        NdisQueryBufferSafe(MyBuffer, &address, &current_length, NormalPagePriority);
        NdisFreeMemory(address, current_length, 0);
        tmpBuffer = MyBuffer;
        NdisGetNextBuffer(tmpBuffer, &MyBuffer);
        NdisFreeBuffer(tempBuffer);
    }
}

4.2 接收处理
     接收的时候,由于那个TransferData的曲折过程,使得接收处理要相对复杂一点点,在ProtocolReceive和 ProtocolReceivePacket中的处理不同。但是由于2003 DDK中的PassThru中,没有对数据进行任何处理,所以,它的ProtocolReceive的处理相对来说,简单了一些。
NDIS_STATUS PtReceive(
    IN  NDIS_HANDLE         ProtocolBindingContext,
    IN  NDIS_HANDLE         MacReceiveContext,
    IN  PVOID               HeaderBuffer,
    IN  UINT                HeaderBufferSize,
    IN  PVOID               LookAheadBuffer,
    IN  UINT                LookAheadBufferSize,
    IN  UINT                PacketSize
    )
{
    // .......

    do
    {
        // 分配一个新的包描述符
        NdisDprAllocatePacket(&Status, &MyPacket, pAdapt->RecvPacketPoolHandle);
        if (Status == NDIS_STATUS_SUCCESS)
        {    
            // PassThru维护了一个自己的接收队列,当收到下层的包时,PassThru并不是立刻
            // 调用NdisMIndicateReceive/NdisMXxxIndicateReceive请求NDIS通知上层新数据
            // 的到来,而是在自己的队列中缓冲起来,当自己的队列满了以后,PassThru将一
            // 次性调用NdisMIndicateReceive请求NDIS通知上层数据的到来。
            // 通常最底程的Miniport Driver也有同样的处理机制。
            PtQueueReceivedPacket(pAdapt, MyPacket, TRUE);
            // MyPacket的一个副本被Copy到PassThru中的队列去了,现在可以释放这一个Packet了。
            NdisDprFreePacket(MyPacket);
            break;      // 注意这里,跳出了整个do-while了。
        }
       else
       {
            //
            // The miniport below us uses the old-style (not packet)
            // receive indication. Fall through.
&nbp;           //
       }

        if (Packet != NULL)
        {
            // 当执行到这里,说明前面没有跳出do-while循环,也就是说,包申请失败
            // 进一步说明,PassThru的缓冲队列满了,于是有必要,调用 NdisMIndicateReceive
            // 请求NDIS通知上层数据包的到来了,这将导致上层注册的 ProtocolReceivePacket
            // 被调用.
            PtFlushReceiveQueue(pAdapt);
        }
        
        // ......
        // 把队列中的包通知了给上层接收,但是这一个包,由于分配新的描述符失败
        // 所以并没有通知给上层,因此,在这里,进行单独的处理。
        
        pAdapt->IndicateRcvComplete = TRUE;
        // 以下是进行各种协议相关的处理。
        switch (pAdapt->Medium)
        {
        case NdisMedium802_3:
        case NdisMediumWan:
            NdisMEthIndicateReceive(pAdapt->MiniportHandle,
                                MacReceiveContext,
                                HeaderBuffer,
                                HeaderBufferSize,
                                LookAheadBuffer,
                                LookAheadBufferSize,
                                PacketSize);
        break;

        case NdisMedium802_5:
            NdisMTrIndicateReceive(pAdapt->MiniportHandle,
                                MacReceiveContext,
                                HeaderBuffer,
                                HeaderBufferSize,
                                LookAheadBuffer,
                                LookAheadBufferSize,
                                PacketSize);
        break;
    case NdisMediumFddi:
        NdisMFddiIndicateReceive(pAdapt->MiniportHandle,
                                 MacReceiveContext,
                                 HeaderBuffer,
                                 HeaderBufferSize,
                                 LookAheadBuffer,
                                 LookAheadBufferSize,
                                 PacketSize);
         break;

    default:
         ASSERT(FALSE);
         break;
        }
    } while(FALSE);

    return Status;
}

     注意,PassThru 并没有对收到的包进行任何处理,因此,在它的ProtocolReceive中,没有调用NdisTransferData,请求NDIS传送这个包其余 的数据,它直接Indicate,把这个工作交给了上层去处理,如果,你的中间层要处理的话,就得在放入队列前面,调用 NdisTransferData,如果返回成功,则在紧接其下进行处理,如果返回 NDIS_STATUS_PENDING 的话,就把处理放到ProtocolTransferDataComplete中处理。所以,你的ProtocolReceive应该看起来是这样的过 程:
1.  在把包加入PassThru的队列前,调用NdisTransferData.请求NDIS通知下层传递其余的数据。
    (这里回头走了一段曲折的路了)。
2A. 如果返回成功,则进行处理,如修改数据,重新修正CheckSum,
    在X86平台上可要注意字节顺序的问题了。
2B. 如果返回 NDIS_STATUS_PENDING 就在 ProtocolTransferDataComplete进行2A的处理。
3.  处理完成后,排入PassThru的队列。
    由于,现在网络设备硬件的发展,内存容量的提高,底程的Miniport Driver通常有一个类似PassThru的缓存处理机制,走这条曲折的线路上来,似乎很少见了。

    在搞懂了ProtocolReceive后,rotocolReceivePacket就更简单了。
INT
PtReceivePacket(
    IN NDIS_HANDLE            ProtocolBindingContext,
    IN PNDIS_PACKET           Packet
    )

{
    // .......
    // 进行你自己的处理,修改包的内容,修正CheckSum等操作,参考前面接收过程
    // 取得Packet中的内容
    Status = NDIS_GET_PACKET_STATUS(Packet);
    if (Status == NDIS_STATUS_RESOURCES)
    {
        // 如果下层设置了NDIS_STATUS_RESOURCES,说明下层由于资源
        // 紧缺等原因,要求上层经快处理,于是产生一个考贝的
        // 包描述符排入队列,马上Indicate,并调用NdisReturnPacket请求
        // NDIS归还下层的资源。这是PassThru的处理方法
        // 事实上,如果,你自己要修改这个包,你已经有一个新的Packet和Buffer
        // 以及相关内容了,只要把你的这个新的包排入队列,并调用 NdisReturnPacket
        // 归还下层的资源,而不立即Indicate上层,也是可以的。
        PtQueueReceivedPacket(pAdapt, Packet, TRUE);
    }
    else
    {
        PtQueueReceivedPacket(pAdapt, MyPacket, FALSE);
    }
    
    if (Status == NDIS_STATUS_RESOURCES)
    {
        NdisDprFreePacket(MyPacket);
    }
    // ......
}

    当上层处理完后,调用NdisReturnPacket请求NDIS归还下层的资源时,NDIS调用下层注册的MiniportReturnPacket在这里释放资源后,再调用NdisReturnPackets通知更下层。
VOID MPReturnPacket(
    IN NDIS_HANDLE             MiniportAdapterContext,
    IN PNDIS_PACKET            Packet
    )

{
    // 如果你和我一样,在接收的时候,自己处理不成功的情况下,就把原来的数据往上传
    // 你就可以像我处理发送的方法一样,利用那几个Reserve的字段,在这里判断,
    // 并进行必要的资源释放。
    // ......
    // 通知下层释放资源
    NdisReturnPackets(&Packet, 1);
    // ......
}
     注意:对于一个像TCP这样面像连接的苏方的数据包来说,你要是改变了它的长度的话,那问题就更复杂了,你在协议栈的下方,上层协议栈不知道这个修改,而 你把它发到目标计算机去,目标计算机得到的长度是修改后的,那双方的SEQ,ACK就不能同步了,这样的话,你必须记录下你的改动,并对以后的通信,做相 应的修正。不然,你一改的话,接下来的通信就RST了。
已标记关键词 清除标记
相关推荐
1 NDIS中间层驱动程序 2 1.1 NDIS中间层驱动程序(NDIS Intermediate Drivers)概述 2 1.2 NDIS中间层驱动程序的用途 4 1.3 NDIS中间层驱动程序的开发环境 4 2 NDIS中间层驱动程序的开发 4 2.1 可分页和可丢弃代码 4 2.2 共享资源的访问同步 5 2.3 中间层驱动程序的DriverEntry函数 5 2.3.1 注册NDIS中间层驱动程序 6 2.3.1.1 注册中间层驱动程序的Miniport 6 2.3.1.2 注册中间层驱动程序的协议 8 2.4 中间层驱动程序的动态绑定 11 2.4.1 打开中间层驱动程序下层的适配器 12 2.4.2 微端口(Miniport)初始化 12 2.4.3 中间层驱动程序查询和设置操作 13 2.4.3.1 发布设置和查询请求 14 2.4.3.2 响应设置和查询请求 15 2.4.4 作为面向连接客户程序注册中间层驱动程序 15 2.5 中间层驱动程序数据包管理 17 2.5.1.1 重用数据包 18 2.6 中间层驱动程序的限制 19 2.7 中间层驱动程序接收数据 19 2.7.1 下边界面向无连接的中间层驱动程序接收数据 19 2.7.1.1 在中间层驱动程序中实现ProtocolReceivePacket处理程序 20 2.7.1.2 在中间层驱动程序中实现ProtocolReceive处理程序 21 2.7.1.3 下边界面向无连接中间层驱动程序接收OOB数据信息 22 2.7.2 下边界面向连接的中间层驱动程序接收数据 22 2.7.2.1 在中间层驱动程序中实现ProtocolCoReceivePacket处理程序 23 2.7.2.2 在下边界面向连接的中间层驱动程序中接收OOB数据信息 23 2.7.3 向高层驱动程序指示接收数据包 23 2.8 通过中间层驱动程序传输数据包 23 2.8.1 传递介质相关信息 25 2.9 处理中间层驱动程序的PnP事件和PM事件 26 2.9.1 处理OID_PNP_XXX查询和设置 26 2.9.2 中间层驱动程序ProtocolPnPEvent处理程序的实现 27 2.9.3 处理规定的电源请求 28 2.9.3.1 睡眠状态的电源设置请求 28 2.9.3.2 工作状态的电源设置请求 29 2.10 中间层驱动程序复位操作 29 2.11 中间层驱动程序拆除绑定操作 30 2.12 中间层驱动程序状态指示 31 3 负载平衡和失效替换 31 3.1 关于LBFO 31 3.2 指定对LBFO的支持 32 3.3 在微端口驱动程序上实现LBFO 32 3.3.1 初始化微端口束 33 3.3.2 平衡微端口驱动程序的工作量 33 3.3.3 在主微端口失效后提升一个次微端口 34 4 安装网络组件 34 4.1 用于安装网络组件的组件和文件 34 4.2 创建网络INF文件 35 4.2.1 网络INFS文件名的约定 35 4.2.2 网络INF文件的版本节 35 4.2.3 网络INF文件的模型节 36 4.2.4 INF文件的DDInstall节 37 4.2.5 删除节 38 4.2.6 ControlFlags节 39 4.2.7 网络INF文件的add-registry-sections 39
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页