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

来源:互联网 发布:淘宝厂家供货商 编辑:程序博客网 时间:2024/06/05 16:43

庖丁解牛-----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 topcap_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函数,定义如下:

intpcap_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;下面看看这个函数是怎么设置内核缓冲区的。

staticint pcap_setbuff_win32(pcap_t *p,int dim)

{

#ifdefHAVE_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文件中。

//设置内核缓冲区大小

BOOLEANPacketSetBuff(LPADAPTERAdapterObject,intdim)

{

    DWORD BytesReturned;

    BOOLEAN Result;

    TRACE_ENTER("PacketSetBuff");

#ifdefHAVE_WANPACKET_API

    if (AdapterObject->Flags ==INFO_FLAG_NDISWAN_ADAPTER)

    {

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

        TRACE_EXIT("PacketSetBuff");

        return Result;

    }

#endif

#ifdefHAVE_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

#ifdefHAVE_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

#ifdefHAVE_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函数源码如下:

    Intpcap_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=(unsignedchar*)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函数

VOIDPacketInitPacket(LPPACKETlpPacket,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函数进行设置。

Intpcap_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*/

staticint 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缓冲区:

BOOLEANPacketSetMinToCopy(LPADAPTERAdapterObject,intnbytes)

{

    DWORD BytesReturned;

    BOOLEAN Result;

    TRACE_ENTER("PacketSetMinToCopy");

#ifdefHAVE_WANPACKET_API

    if (AdapterObject->Flags ==INFO_FLAG_NDISWAN_ADAPTER)

    {

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

        TRACE_EXIT("PacketSetMinToCopy");

        return Result;

    }

#endif//HAVE_WANPACKET_API

#ifdefHAVE_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

#ifdefHAVE_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

#ifdefHAVE_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 )

 

 

  
原创粉丝点击