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. // } 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了。
五 Ring 0 和 Ring 3的通信问题
也许你并 不只是想修改过往的数据,你也想把这些数据直接传到Ring3上去,或有一些其它的理由需要在你的NDIS中间层驱动和应用程序通信, 2003 DDK中的PassThru也提供了这个功能,它已经创建好了设备对像和符号链接,你只需要修改驱动对象的DispatchTable就行了。假如,你希 望Ring3的程序通能Read NDIS中间层截获的数据,你需要维护两个队列,一个用于缓存NDIS收到的数据,另一个存放Ring3的IRP请求队列等等,是的,这个已经没有什么好 说的了,这一切对于你来说,也经不是难题了。但是,在兴奋之时,需要注意的是,现在是位于任何协议栈的下方,如果没有任何验证机制的话,别人只要在你的局 域网中向你发送链路层的广播,或目标MAC地址是你的网卡的MAC的数据包时,你的程序都要受到影响,也就是说,别人随便发个包,你的系统都要为他操劳 了,从安全的角度来说,你的系统应该尽可能减小受外部影响的程度,所以,你在加上这个功能的时候,有必要考虑这一点。
后记:
也 许,和很多人一样,我曾经试图看着DDK中的文档学习NDIS驱动开发,但是总是不顺利,到头来似乎感觉NDIS很难,DDK的文档没用,但是,在动手写 起程序来时,发现了自己还是离不开DDK的文档,刚开始试图从看DDK文档中的描述,进而想掌握NDIS,这似乎是不容易的,至少对我来说是这样的,等你 真正的写起程序来,遇上一些具体的问题,受到一些挫折后,再带着这些问题来看DDK文档就会有收获了。本文描述了自己学习过程中的一些理解,写出来希望能 与大家交流,由于自己水平有限,错误之错在所难免,欢迎诸位指正。
QQ: 22517257. Email: [email protected], MSN: [email protected]
参考资料:
DDK开发文档。
驱动开发网NDIS板块的讨论。
http://www.xfocus.net/articles/200307/568.html