Chinaunix首页 | 论坛 | 博客
  • 博客访问: 289111
  • 博文数量: 55
  • 博客积分: 2545
  • 博客等级: 少校
  • 技术积分: 937
  • 用 户 组: 普通用户
  • 注册时间: 2006-08-10 06:51
文章分类

全部博文(55)

文章存档

2012年(1)

2011年(2)

2010年(6)

2009年(18)

2008年(28)

我的朋友

分类:

2009-06-20 17:00:30

早就该写这篇文章了。只是最近毕设搞得我天昏地暗。现在抽点时间总结一下吧。省着老是挂念。
   
    由于主要研究了下对于接收数据包的过滤,所以本文就只是讲讲数据包的接收,对于发送数据包的过滤,期待以后的文章吧。嘿嘿。
   
    关于Passthru的介绍的文章,大家可以看看我之前转载的那几篇文章。大牛写的文章就是牛。我这里只谈谈我自己的学习体会。还有以下我的理解也许并不正确,仅供参考。
   
    打开ddk里面的Passthru源代码,首先我们来看两个重要的函数PtReceive和PtReceivePacket函数,这两个函数在Protocol.c文件里。当下层驱动Indicate有一个数据包到来时,就会调用这两个函数中的一个,至于具体调用哪个,要看你的网卡和环境。当然,我在VMWare中测试的时候,两个函数都会调用到。
   
    接下来,对于接收数据包的处理序幕就由此拉开。在PtReceive和PtReceivePacket这两个函数中,你可以简单的选择将数据包丢掉,或者发往上层。我们先来看看Passthru本身是怎么实现的。
   
    我们先来看PtReceive函数,它先调用NdisGetReceivedPacket得到一个Packet,然后将这个Packet的内容复制到自己定义的一个MyPacket里面,接下来调用PtQueueReceivedPacket将MyPacket存放到一个数组中保存起来。当然,这是最成功的情况。在NdisGetReceivedPacket得到数据包失败的情况下,它会调用NdisMEthIndicateReceive或NdisMTrIndicateReceive或NdisMFddiIndicateReceive做进一步的工作,以便接收一个完整的数据包。
   
    PtReceivePacket函数与PtReceive功能是类似的,只不过它不需要调用NdisGetReceivedPacket,从函数的参数中直接就有一个Packet,它的主要工作也是调用PtQueueReceivedPacket将MyPacket存放到一个数组中保存起来。
   
    有人不禁要问,PtReceive里面的像NdisDprAllocatePacket这样的函数是干什么的呢?说到这里,我们还要从PNDIS_PACKET这个结构说起了。准确的说应该是NDIS_PACKET,PNDIS_PACKET自然就是指向这个结构的指针啦。那么这个NDIS_PACKET到底是个什么东西呢?PACKET到底是什么意思呢?先看看DDK里它的定义:
  view plaincopy to clipboardprint?
typedef struct _NDIS_PACKET {  
    NDIS_PACKET_PRIVATE  Private;  
    union {  
        struct {  
             UCHAR       MiniportReserved[2*sizeof(PVOID)];  
             UCHAR       WrapperReserved[2*sizeof(PVOID)];  
        };  
        struct {  
             UCHAR       MiniportReservedEx[3*sizeof(PVOID)];  
             UCHAR       WrapperReservedEx[sizeof(PVOID)];  
        };  
        struct {  
             UCHAR       MacReserved[4*sizeof(PVOID)];  
        };  
    };  
    ULONG_PTR            Reserved[2];  
    UCHAR                ProtocolReserved[1];  
} NDIS_PACKET, *PNDIS_PACKET, **PPNDIS_PACKET; 
typedef struct _NDIS_PACKET {
    NDIS_PACKET_PRIVATE  Private;
    union {
        struct {
             UCHAR       MiniportReserved[2*sizeof(PVOID)];
             UCHAR       WrapperReserved[2*sizeof(PVOID)];
        };
        struct {
             UCHAR       MiniportReservedEx[3*sizeof(PVOID)];
             UCHAR       WrapperReservedEx[sizeof(PVOID)];
        };
        struct {
             UCHAR       MacReserved[4*sizeof(PVOID)];
        };
    };
    ULONG_PTR            Reserved[2];
    UCHAR                ProtocolReserved[1];
} NDIS_PACKET, *PNDIS_PACKET, **PPNDIS_PACKET; 
   
    好像也看不出什么名堂,那个Private蛮神秘的。这里我个人的理解,故名思义,它就是我们驱动接收到一个数据包的结构体,但它并没有保存数据包的地方啊,实际上,在那个Private里面,它有一个指向数据缓冲的链表。
   
    说到这个链表,我们就自然的要说到NDIS_BUFFER这个结构,这个链表就是由NDIS_BUFFER链接而成。有人又会问,为什么一个数据包的NDIS_PACKET需要好多这样的NDIS_BUFFER链起来呢。额。。。那你只有往下层再看看吧。我猜可能是下层的数据是一小片一小片到来的,每到一部分,驱动就申请一个NDIS_BUFFER,把数据存到里面,然后链接到NDIS_PACKET里面。
   
    现在你应该对于数据的接收有一个大致的了解了吧。那我们再来看看NDIS_PACKET和NDIS_BUFFER是怎么申请和释放的。通过看Passthru和DDK,我们可以知道,它是用一个Pool来管理的,首先要NdisAllocatePacketPool和NdisAllocateBufferPool申请一个池,然后用NdisAllocatePacket和NdisAllocateBuffer来从相应的池中申请。当然NdisDprAllocatePacket和NdisAllocatePacket其实是一回事,只不过NdisDprAllocatePacket运行在DISPATCH_LEVEL上。
   
    还有,在驱动中,可以通过NdisChainBufferAtBack和NdisChainBufferAtFront将一个NDIS_BUFFER链到NDIS_PACKET上。
   
    好了,有了这些预备知识,PtReceive和PtReceivePacket函数就没有什么难度了。假如你想在PtReceive和PtReceivePacket函数中看到数据包的内容,你可以用下面的代码:
    view plaincopy to clipboardprint?
    //---------------------------------------------------------  
    int        PacketSize;  
    PUCHAR        pPacketContent;  
    PUCHAR        pBuf;  
    UINT         BufLength;  
    MDL    *     pNext;  
    UINT         i;  
      
    //把数据包内容从Packet拷贝到pPacketContent  
 
    NdisQueryPacket( Packet,NULL,NULL,NULL,&PacketSize);  
      
    Status= NdisAllocateMemory( &pPacketContent, 2000, 0,HighestAcceptableMax);  
    if (Status!=NDIS_STATUS_SUCCESS ) return Status;  
    NdisZeroMemory (pPacketContent, 2000);  
      
    NdisQueryBufferSafe(Packet->Private.Head, &pBuf, &BufLength, 32 );  
    NdisMoveMemory(pPacketContent, pBuf, BufLength);  
 
    i = BufLength;  
    pNext = Packet->Private.Head;  
      
    for(;;)  
    {  
        if(pNext == Packet->Private.Tail)  
            break;  
            pNext = pNext->Next;   //指针后移  
            if(pNext == NULL)   
                break;  
 
            NdisQueryBufferSafe(pNext,&pBuf,&BufLength,32);  
            NdisMoveMemory(pPacketContent+i,pBuf,BufLength);  
            i+=BufLength;  
    }  
      
    //数据拷贝完毕  
    //--------------------------------------------------------- 
    //---------------------------------------------------------
    int        PacketSize;
    PUCHAR        pPacketContent;
    PUCHAR        pBuf;
    UINT         BufLength;
    MDL    *     pNext;
    UINT         i;
   
    //把数据包内容从Packet拷贝到pPacketContent
    NdisQueryPacket( Packet,NULL,NULL,NULL,&PacketSize);
   
    Status= NdisAllocateMemory( &pPacketContent, 2000, 0,HighestAcceptableMax);
    if (Status!=NDIS_STATUS_SUCCESS ) return Status;
    NdisZeroMemory (pPacketContent, 2000);
   
    NdisQueryBufferSafe(Packet->Private.Head, &pBuf, &BufLength, 32 );
    NdisMoveMemory(pPacketContent, pBuf, BufLength);
    i = BufLength;
    pNext = Packet->Private.Head;
   
    for(;;)
    {
        if(pNext == Packet->Private.Tail)
            break;
            pNext = pNext->Next;   //指针后移
            if(pNext == NULL)
                break;
            NdisQueryBufferSafe(pNext,&pBuf,&BufLength,32);
            NdisMoveMemory(pPacketContent+i,pBuf,BufLength);
            i+=BufLength;
    }
   
    //数据拷贝完毕
    //---------------------------------------------------------   
    这段代码来自我之前转载的xiaobai的文章。代码意思就是申请一部分内存到pPacketContent,然后将NDIS_PACKET里的NDIS_BUFFER里面的内容复制到一起存入pPacketContent里。这样,pPacketContent就是一个完整数据包的内容。
   
    获得内容后,接下来,你就可以开始你简单的判断啦,要不要将这个包放行。假如要阻拦掉这个包,那么直接在PtReceive和PtReceivePacket函数返回就可以啦。不过要记得清理申请的资源哦~
   
    一个简单的防火墙就这样实现啦!
   
    假如你还想实现比较复杂的功能,那么接着往下看吧。下面是我在bbs.driverdevelop.com发的一个帖子。貌似这个论坛没有什么人气了。国内也没找到几个研究驱动比较牛X的论坛了。郁闷。难道是我不知道?
   
引用
大家好,刚开始学NDIS。想做一个类似防火墙的东西,所以看了Passthru的源代码。现在已经知道了通过修改PtReceive和PtReceivePacket函数,能够得到发往机器的数据包的内容。也可以通过简单的过滤规则将包丢掉或者发往上层协议。
我的设想是想在应用程序(Ring3)中实现更强大的包过滤规则。因此需要驱动将数据包的内容发往应用层,由应用层作出决定是否丢掉,然后再通知驱动是否接收或者丢掉。我的设想是采用Event让驱动等待,可结果是,我发现在PtReceive和PtReceivePacket函数执行的时候,都是处于DISPATCH_LEVEL级别。当我尝试在PtReceive函数里面KeWaitForSingleObject的时候,不久就会蓝屏。
这个问题困扰很久。不知在这种情况下程序该如何阻塞或者等待。请各位大侠不吝赐教!谢谢。
    正如我上面所说,PtReceive和PtReceivePacket函数都是在DISPATCH_LEVEL级别运行的,你阻塞它的话就会出问题。那这个时候该如何解决呢?
   
    我的办法是这样的,
   
引用
我现在的想法是先建一个自己维护的队列,每当有数据的时候就放到队列里面。系统单独开一个线程将队列里面的包不停的发往应用层。应用层处理后通过IOCTL再通知驱动层,然后驱动从队列中取出相应的包,作丢掉或者是indicate的操作。
  应用层和驱动通信不是本文讨论的内容。你可以看看其它的文章。那么如何建一个自己维护的队列保存数据包呢?
 
  我们先来看看PtQueueReceivedPacket这个函数吧,它实际上已经做了我们想要做的工作。它就是将发给它的数据包保存到数组里。但有一个问题需要注意,大家请看PtReceive函数中,
NDIS_PACKET_FIRST_NDIS_BUFFER(MyPacket) = NDIS_PACKET_FIRST_NDIS_BUFFER(Packet);
NDIS_PACKET_LAST_NDIS_BUFFER(MyPacket) = NDIS_PACKET_LAST_NDIS_BUFFER(Packet);
这两句上面的这段注释:
引用
                //
                // Make our packet point to data from the original
                // packet. NOTE: this works only because we are
                // indicating a receive directly from the context of
                // our receive indication. If we need to queue this
                // packet and indicate it from another thread context,
                // we will also have to allocate a new buffer and copy
                // over the packet contents, OOB data and per-packet
                // information. This is because the packet data
                // is available only for the duration of this
                // receive indication call.
                //
在Passthru中,它仅仅是把MyPacket里面的Buffer数据指针指向Packet的Buffer数据。当然,这样做更有效率,但是我们不能长时间占用下层驱动的Buffer资源嘛,占着人家茅坑不拉屎,人家要跟你急的^_^。把下层搞得没资源了那可是要出问题的。我们用下面的代码自己申请空间并把它链到MyPacket:view plaincopy to clipboardprint?
      NdisAllocateBuffer(&Status,  
              &pndisbuf,   
              pAdapt->RecvBufferPoolHandle,  
              pPacketContent,  
              i);        
      if (Status == NDIS_STATUS_SUCCESS )  
      {  
        NdisChainBufferAtBack(MyPacket,pndisbuf);  
      }  
      else 
      {  
        DbgPrint("NdisAllocateBuffer failed!\n");  
        break;  
      } 
      NdisAllocateBuffer(&Status,
              &pndisbuf,
              pAdapt->RecvBufferPoolHandle,
              pPacketContent,
              i);     
      if (Status == NDIS_STATUS_SUCCESS )
      {
        NdisChainBufferAtBack(MyPacket,pndisbuf);
      }
      else
      {
        DbgPrint("NdisAllocateBuffer failed!\n");
        break;
      }  当然,实现上面代码之前,你需要先NdisAllocateBufferPool申请一个池,然后才能NdisAllocateBuffer。在这里,我把之前的pPacketContent的内容作为数据,这样,就只调用一次NdisChainBufferAtBack就把数据链到MyPacket了。
 
  这些做完后,就可以调用NdisReturnPackets(&Packet, 1);通知下层,这个包的资源可以释放啦。因为我们复制了一份嘛。拿到这个MyPacket后,我们就可以为所欲为啦。哈哈哈。想把它在列表中保存多长时间就保存多少时间。我的地盘听我的!
 
  要向上层提交这个数据包,我们可以调用NdisMIndicateReceivePacket函数。如果要拦截它,嘿嘿,扔掉它就可以啦。不过记得回收资源哦~
 
  这样,数据包的过滤操作基本就可以告一段路了。一些资源的释放,可以在上层收到数据后调用的MPReturnPacket(Mimiport.c文件中)函数中释放。Passthru默认调用了NDIS_SET_PACKET_STATUS(MyPacket, NDIS_STATUS_RESOURCES);让上层驱动自己再拷贝一份数据,这样,实际上在调用了NdisMIndicateReceivePacket函数后我们就可以把我们的Packet释放掉了。否则还要到MPReturnPacket中释放。
 
  最后再来说点闲话。WinDbg双机内核调试很好很强大。它能够在源代码上单步调试驱动。使用的时候注意安装好相应操作系统的Symbol。在调试的过程中,曾经有一个死锁问题我调试了很久。开始一直以为是自己的知识水平不够,对内核编程不了解。于是看了大量内核同步的资料。最后尝试换了n种同步方式还是死锁。我一直以为DISPATCH_LEVEL和PASSIVE_LEVEL同步会有什么问题。后来直到突然灵光一现,我发现了我程序中很隐蔽的一个死循环才恍然大悟。
 
  那感觉,整个天空都明朗了!原来天气是这么的好,温度是这么的舒适。每个人都那么的可爱。哈哈哈哈。赶紧拉着friddy大哥msn了一通,hiahiahia。
 
  其实同步用自旋锁就蛮好的啦。不过记得尽快释放哦~还有PASSIVE_LEVEL的线程要用NdisAcquireSpinLock,不要像PtQueueReceivedPacket里面一样调用NdisDprAcquireSpinLock,这个是在DISPATCH_LEVEL调用的,貌似它没有做提升IRQL,这样我们的线程有可能被抢占哦~
 
  先写到这。OMG,赶紧接着搞毕业论文,预祝我一切顺利吧~
本文来源:单克隆抗体's blog   
原文地址:
阅读(1535) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~