庖丁解牛-----winpcap源码彻底解密(四)

庖丁解牛-----winpcap源码彻底解密(四)

版权申明: 原创文章,转贴请注明出处!!!!!!!!

(1)       如何设置内核缓冲区的大小,前面已经谈过设置内核缓冲区的函数是pcap_setbuff,查看winpcap的开发文档,pcap_setbuff的定义如下:

int pcap_setbuff(pcap_t *p,int dim)

Set the size of the kernel buffer associated with an adapter. dim specifies the size of the buffer in bytes. The return value is 0 when the call succeeds, -1 otherwise. If an old buffer was already created with a previous call to pcap_setbuff(), it is deleted and its content is discarded. pcap_open_live() creates a 1 MByte buffer by default.

下面主要讲解pcap_setbuff是怎样设置内核缓冲区的,在wpcap.dll中的pcap.c文件中定义了pcap_setbuff函数,定义如下:

int pcap_setbuff(pcap_t *p, int dim)

{

     return p->setbuff_op(p, dim);

}

其中setbuff_op是一个回调函数,其实调用的是pcap_setbuff_win32,在pcap-win32.c中定义

p->setbuff_op = pcap_setbuff_win32;下面看看这个函数是怎么设置内核缓冲区的。

static int pcap_setbuff_win32(pcap_t *p, int dim)

{

#ifdef HAVE_REMOTE

     if (p->rmt_clientside)

     {

         /* Currently, this is a bug: the capture buffer cannot be set with remote capture */

         return 0;

     }

 #endif        /* HAVE_REMOTE */

     if(PacketSetBuff(p->adapter,dim)==FALSE)

     {

         snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "driver error: not enough memory to allocate the kernel buffer");

         return -1;

     }

     return 0;

}

pcap_setbuff_win32的源码可以出,它是调用PacketSetBuff将应用程序对应的网卡的内核缓冲区的大小dim传递到了parket.dll 下一层。如果要相知道PacketSetBuff怎么讲内核缓冲区的大小传递到驱动程序npf.sys中,就要取跟踪PacketSetBuff的源码了。PacketSetBuffparket32.c文件中。

//设置内核缓冲区大小

BOOLEAN PacketSetBuff(LPADAPTER AdapterObject,int dim)

{

     DWORD BytesReturned;

     BOOLEAN Result;

     TRACE_ENTER("PacketSetBuff");

#ifdef HAVE_WANPACKET_API

     if (AdapterObject->Flags == INFO_FLAG_NDISWAN_ADAPTER)

     {

        Result = WanPacketSetBufferSize(AdapterObject->pWanAdapter, dim);

         TRACE_EXIT("PacketSetBuff");

         return Result;

     }

#endif

#ifdef HAVE_AIRPCAP_API

     if(AdapterObject->Flags == INFO_FLAG_AIRPCAP_CARD)

     {

         Result = (BOOLEAN)g_PAirpcapSetKernelBuffer(AdapterObject->AirpcapAd, dim);

         TRACE_EXIT("PacketSetBuff");

         return Result;

     }

#endif // HAVE_AIRPCAP_API

#ifdef HAVE_NPFIM_API

     if(AdapterObject->Flags == INFO_FLAG_NPFIM_DEVICE)

     {

         Result = (BOOLEAN)g_NpfImHandlers.NpfImSetCaptureBufferSize(AdapterObject->NpfImHandle, dim);

         TRACE_EXIT("PacketSetBuff");

         return Result;

     }

#endif // HAVE_NPFIM_API

#ifdef HAVE_DAG_API

     if(AdapterObject->Flags == INFO_FLAG_DAG_CARD)

     {

         // We can't change DAG buffers

         TRACE_EXIT("PacketSetBuff");

         return TRUE;

     }

#endif // HAVE_DAG_API

     if (AdapterObject->Flags == INFO_FLAG_NDIS_ADAPTER)

     {

         Result = (BOOLEAN)DeviceIoControl(AdapterObject->hFile,BIOCSETBUFFERSIZE,&dim,sizeof(dim),NULL,0,&BytesReturned,NULL);

     }

     else

     {

         TRACE_PRINT1("Request to set buf size on an unknown device type (%u)", AdapterObject->Flags);

         Result = FALSE;

     }

     TRACE_EXIT("PacketSetBuff");

     return Result;

}

通过PacketSetBuff 的源码可以看到它是调用DeviceIoControl将设置内核缓冲区的大小的命令发送到npf.sys,上面我们已经多次提到,应用程序和驱动的通信,无论你怎么封装,到了底层都是调用DeviceIoControl,WriteFileReadFile函数,一般设置命令使用DeviceIoControl,发送数据包使用WriteFile,但是你要使用DeviceIoControl发送数据包也是可以的,读取驱动中的数据包就使用ReadFile了。设置内核缓冲区的控制码为:BIOCSETBUFFERSIZE,在NTSTATUS NPF_IoControl(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)函数可以看到不同的控制码的处理方式。NPF_IoControl的源码在前面已经说了,这里主要看看内核缓冲区在驱动是怎么设置的。设置内核缓冲区的源码主要如下:

         for (i = 0 ; i < g_NCpu ; i++)

         {

              if (dim > 0)

                   Open->CpuData[i].Buffer=(PUCHAR)tpointer + (dim/g_NCpu)*i;

              else

              Open->CpuData[i].Buffer = NULL;

              Open->CpuData[i].Free = dim/g_NCpu;

              Open->CpuData[i].P = 0;     //生产者

              Open->CpuData[i].C = 0;     //消费者

              Open->CpuData[i].Accepted = 0;

              Open->CpuData[i].Dropped = 0;

              Open->CpuData[i].Received = 0;

         }

         Open->ReaderSN=0;

         Open->WriterSN=0;

         Open->Size = dim/g_NCpu;

     从上面的源码可以看出,winpcap的高明之处在于,它充分的使用了每个cpu,这样的话,你的cpu有几个核,性能就可以明显的体现出来。每个cpu的缓冲区设置为dim/g_NCpu,其中Open是一个全局变量,保存的是一些和绑定网卡相关的信息。CpuData[i].PCpuData[i].C在读取数据包是非常有用的,他可以用来判断内核缓冲区的数据是否大于pcap_setmintocopy的最小copysize。这个函数我会在后面的讲道。讲道这里,我们知道pcap_setbuff是怎么设置内核缓冲区的了,主要是调用DeviceIoControl将用户要设置的size传递到内核,而在内核中将它保存一个全局变量中,这样就设置好了内核缓冲区。

   

(2)如何设置用户缓冲区的大小?下面讲解怎样设置用户缓冲区的大小,linux下面的libcap是没有提供设置用户缓冲区大小(user buffer)api,要设置用户缓冲区,必须修改libcap的源码,但是winpcap的高版本是提供了设置用户缓冲区的函数,在wpcap.dllwin32-Extensions.c文件中有一个pcap_setuserbuffer函数,在用户使用时必须添加win32-Extensions.h头文件。pcap_setuserbuffer函数源码如下:

     Int pcap_setuserbuffer(pcap_t *p, int size)

{

     unsigned char *new_buff;

     if (!p->adapter) {

         sprintf(p->errbuf,"Impossible to set user buffer while reading from a file or on a TurboCap port");

         return -1;

     }

     if (size<=0) {

         /* Bogus parameter */

         sprintf(p->errbuf,"Error: invalid size %d",size);

         return -1;

     }

     /* Allocate the buffer */

     new_buff=(unsigned char*)malloc(sizeof(char)*size);

     if (!new_buff) {

         sprintf(p->errbuf,"Error: not enough memory");

         return -1;

     }

     free(p->buffer);

     p->buffer=new_buff;

     p->bufsize=size;

     /* Associate the buffer with the capture packet */

     PacketInitPacket(p->Packet,(BYTE*)p->buffer,p->bufsize);

     return 0;

}

从上面的源码可以看出,pcap_setuserbuffer调用的是PacketInitPacket函数

VOID PacketInitPacket(LPPACKET lpPacket,PVOID Buffer,UINT Length)

{

     TRACE_ENTER("PacketInitPacket");

     lpPacket->Buffer = Buffer;

     lpPacket->Length = Length;

     lpPacket->ulBytesReceived = 0;

     lpPacket->bIoComplete = FALSE;

     TRACE_EXIT("PacketInitPacket");

}

PacketInitPacket源码和pcap_setuserbuffer的源码可以看出,设置用户缓冲区相对容易,因为它不涉及到内核,就是对应用程序对应的网卡,pcap_t *p,设置它的用户缓冲区的大小,在设置前清空原来的缓冲区,然后再分配一个size,完成用户缓冲区的设置。

 

(3)设置内核缓冲区到用户缓冲区最小的copy数据的size,采用pcap_setmintocopy函数进行设置。

Int pcap_setmintocopy(pcap_t *p, int size)

{

     return p->setmintocopy_op(p, size);

}

pcap_setmintocopy的源码可以看出,它和设置内核缓冲区大小有点相似,调用的是setmintocopy_op回调函数。在pcap_win32.c中有:

     p->setmintocopy_op = pcap_setmintocopy_win32;

/*set the minimum amount of data that will release a read call*/

static int pcap_setmintocopy_win32(pcap_t *p, int size)

{

     if(PacketSetMinToCopy(p->adapter, size)==FALSE)

     {

         snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "driver error: unable to set the requested mintocopy size");

         return -1;

     }

     return 0;

}

Pcap_setmintocopy_win32调用PacketSetMinToCopy函数设置最小的copy缓冲区:

BOOLEAN PacketSetMinToCopy(LPADAPTER AdapterObject,int nbytes)

{

     DWORD BytesReturned;

     BOOLEAN Result;

     TRACE_ENTER("PacketSetMinToCopy");

#ifdef HAVE_WANPACKET_API

     if (AdapterObject->Flags == INFO_FLAG_NDISWAN_ADAPTER)

     {

         Result = WanPacketSetMinToCopy(AdapterObject->pWanAdapter, nbytes);

         TRACE_EXIT("PacketSetMinToCopy");

         return Result;

     }

#endif //HAVE_WANPACKET_API

#ifdef HAVE_NPFIM_API

     if(AdapterObject->Flags == INFO_FLAG_NPFIM_DEVICE)

     {

         Result = (BOOLEAN)g_NpfImHandlers.NpfImSetMinToCopy(AdapterObject->NpfImHandle, nbytes);

         TRACE_EXIT("PacketSetMinToCopy");

         return Result;

     }

#endif // HAVE_NPFIM_API

#ifdef HAVE_AIRPCAP_API

     if(AdapterObject->Flags == INFO_FLAG_AIRPCAP_CARD)

     {

         Result = (BOOLEAN)g_PAirpcapSetMinToCopy(AdapterObject->AirpcapAd, nbytes);

         TRACE_EXIT("PacketSetMinToCopy");

         return Result;

     }

#endif // HAVE_AIRPCAP_API

#ifdef HAVE_DAG_API

     if(AdapterObject->Flags & INFO_FLAG_DAG_CARD)

     {

         TRACE_EXIT("PacketSetMinToCopy");

         // No mintocopy with DAGs

         return TRUE;

     }

#endif // HAVE_DAG_API

     if (AdapterObject->Flags == INFO_FLAG_NDIS_ADAPTER)

     {

         Result = (BOOLEAN)DeviceIoControl(AdapterObject->hFile,BIOCSMINTOCOPY,&nbytes,4,NULL,0,&BytesReturned,NULL);

     }

     else

     {

         TRACE_PRINT1("Request to set mintocopy on an unknown device type (%u)", AdapterObject->Flags);

         Result = FALSE;

     }

     TRACE_EXIT("PacketSetMinToCopy");

     return Result;        

}

和设置内核缓冲区类似,该函数又是调用的DeviceIoControl函数将nbytes传递到内核npf.sys中,传递码为:BIOCSMINTOCOPY,对应驱动中的源码如下,每个cpuMintoCopy(*((PULONG)Irp->AssociatedIrp.SystemBuffer))/g_NCpu; 其中SystemBuffer的大小为应用程序传递过来的缓冲区size

 

case BIOCSMINTOCOPY: //set the minimum buffer's size to copy to the application

         TRACE_MESSAGE(PACKET_DEBUG_LOUD, "BIOCSMINTOCOPY");

         if(IrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(ULONG))

         {            

              SET_FAILURE_BUFFER_SMALL();

              break;

         }

//An hack to make the NCPU-buffers behave like a larger one

         Open->MinToCopy = (*((PULONG)Irp->AssociatedIrp.SystemBuffer))/g_NCpu;

         SET_RESULT_SUCCESS(0);

         break;

 

其中Open->MinToCopy为打开上下文的全局变量,该变量在读数据包的时候会使用,用来判断内核缓冲区的大小是不是已经满足最小的copy size。如果是就将数据copy到用户缓冲区中,对应的部分源码如下:

     for(i=0;i<g_NCpu;i++)

         Occupation += (Open->Size - Open->CpuData[i].Free);   //计算出已经占用的内核缓冲区

     if( Occupation <= Open->MinToCopy*g_NCpu || Open->mode & MODE_DUMP )

 

 

  

你可能感兴趣的:(庖丁解牛-----winpcap源码彻底解密(四))