庖丁解牛-----winpcap源码彻底解密

时间:2021-05-28 18:01:11

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

本文系转载:原文出处为:http://blog.csdn.net/smilestone322/article/details/6084620

庖丁解牛-----winpcap源码彻底解密

 

       最近忙一个项目,需要使用winpcap抓取网络数据包,调用winpcap提供的api进行抓包后,发现丢包情况比较严重,而且cpu占用率比较大。所以下定决心,对winpcap源码进行分析,因为对驱动和对Ndis网络驱动比较熟悉,所以分析源码还不是很费劲,其实winpcap底层的npf不过是一个Ndis协议驱动,所以它能做的工作就是捕获数据包,而不能做防火墙等使用, 要做防火墙,一般使用的都是Ndis中间层过滤驱动,呵呵,不多说了,超出了本文的范围。下面分析源代码的一点经验,供大家分享,共同进步。

       总的来说,winpcap主要有3个文件需要关注,wpcap.dll, parket.dll, npf.sys。其中wpcap和packet属于应用层的程序,npf属于内核层的程序,即npf就是一个Ndis的协议驱动。在本文中主要分析这几个文件和他们之间是怎么通信的,对于驱动的基础知识和Ndis驱动程序不在进行重点讲解,如果对本文的内容看不明白,大家可以在回过头学习驱动的知识,也可以和我交流。

       首先我们从上往下层层分析,即先分析winpcap的wpcap.dll,然后分析parket.dll,最后分析底层的npf。对于大多数应用开发的人来说,使用wpcap.dll就足够了,因为它提供的api函数足够你捕获网络数据包,从而分析网络数据包,解析网络数据包。下面讲解下数据包的捕获流程。

数据包的捕获流程一般都分为以下几步:

1)  查找设备,也就是说查找网卡设备,调用的函数是pcap_findalldevs

2)  打开对应的网卡设备,调用的函数为pcap_open_live

3)  过滤数据包,在解析之前过滤数据包,可以减少解析的数据包数,增强数据包解析的能力,主要调用2个函数,pcap_compile编译过滤器,采用pcap_setfiter;

4)  获取数据包,主要的函数有pcap_loop,pcap_dispatch,pcap_next,pcap_next_ex这4个函数,这四个函数的区别可以参考winpcap的手册。

5)  关闭设备,pcap_freealldevs;

 

这是基本的数据包捕获流程,对一般的应用层的开发者来说,知道这些已经足够了。

但是当网络数据很大的时候,比如大块的文件传输,等,就需要考虑丢包情况了。下面介绍改善性能的api。怎样设置内核缓冲,怎样设置用户缓冲区,如何设置内核缓冲区到用户缓冲区的拷贝数据的最小值,即当内核缓冲区的数据达到这个值时,才能被上层的应用程序读走。在分析这些之前,必先知道数据包是怎么从网卡到达应用程序的。对于linux和windows系统,稍有不同,linux下采用libcap抓取网络数据包,windows采用winpcap抓取网络数据包。数据包从网卡到应用程序的过程如下:

庖丁解牛-----winpcap源码彻底解密

 

 

图1:linux下数据包捕获流程

庖丁解牛-----winpcap源码彻底解密

 

 

图2:winpcap数据包捕获流程

从linux和windows抓包过程可以看出,libcap和winpcap的不同之处如下:

(1)Libcap在BPF中采用2个缓冲区,即store buffer和hold buffer,从网卡拷贝的数据首先放入到store buffer ,然后再复制到hold buffer,就是一个乒乓操作 经过hold buffer拷贝到应用程序的user buffer 中,这两个缓冲区的大小一般为32k。而winpcap采用的是一个循环缓冲区,即内核缓冲区,kernel buffer,内核缓冲区的默认大小为1M,可以采用pcap_setbuff对内核缓冲区进行修改。

(2)libcap的内核缓冲区和用户缓冲区大小在应用层是不可以更改的,如果需要更改,必须修改内核代码,然后重新编译,而winpcap提供了修改内核缓冲区和用户缓冲区的api函数,用户可以方便的设置缓冲区的大小,用户缓冲区的默认大小为256k,可以使用pcap_setuserbuffer进行设置。

(3)从图中可以看出,libcap只有2层,内核层和用户层,而winpcap有3层,在用户层又分为packet.dll和wpcap.dll,这样层次更加清晰,方便用户的理解,当然用户可以直接调用wpcap.dll的api进行数据包操作,也可以调用packet.dll进行数据包操作。

(4)对数据包的捕获一般要经过3个内存的拷贝,首先是网卡拷贝到内核缓冲区,然后内核缓冲区拷贝到用户缓冲区,然后通过api将用户缓冲区的数据包读走,进行协议分析,然后把数据保存起来,放入应用程序开辟的内存中。这一点它们都是类似的。

(5) winpcap提供了直接从内核缓冲区将数据写入文件的方法,这样减少了数据包的拷贝次数,提高了收包的速度。

(6)如何将数据包从内核缓冲区直接提供给应用程序,这是提高数据包捕获性能的好方法?

读者可以尝试这样去做,可以降低cpu占用率和丢包率。

(7)libcap老的版本没有发送数据包的函数,winpcap有pcap_sendpacket发送数据包;

(8)winpcap采用pcap_setmintocopy设置每次从内核缓冲区拷贝到用户缓冲区的最小值,当内核缓冲区的数据小于这个值时,读操作不返回,直到超时为止;

 

  下面由上到下对各个函数进行解析:

(1)       pcap_findalldevs

int pcap_findalldevs(pcap_if_t **alldevsp, char *errbuf)

{

     pcap_if_t *devlist = NULL;

     int ret = 0;

     const char *desc;

     char *AdaptersName;

     ULONG NameLength;

     char *name;

     if (!PacketGetAdapterNames(NULL, &NameLength))

     {

         DWORD last_error = GetLastError();

         if (last_error != ERROR_INSUFFICIENT_BUFFER)

         {

              snprintf(errbuf, PCAP_ERRBUF_SIZE,

                   "PacketGetAdapterNames: %s",

                   pcap_win32strerror());

              return (-1);

         }

     }

     if (NameLength > 0)

         AdaptersName = (char*) malloc(NameLength);

     else

     {

         *alldevsp = NULL;

         return 0;

     }

     if (AdaptersName == NULL)

     {

         snprintf(errbuf, PCAP_ERRBUF_SIZE, "Cannot allocate enough memory to list the adapters.");

         return (-1);

     }            

     if (!PacketGetAdapterNames(AdaptersName, &NameLength)) {

         snprintf(errbuf, PCAP_ERRBUF_SIZE,

              "PacketGetAdapterNames: %s",

              pcap_win32strerror());

         free(AdaptersName);

         return (-1);

     }

     /*

      * "PacketGetAdapterNames()" returned a list of

      * null-terminated ASCII interface name strings,

      * terminated by a null string, followed by a list

      * of null-terminated ASCII interface description

      * strings, terminated by a null string.

      * This means there are two ASCII nulls at the end

      * of the first list.

      *

      * Find the end of the first list; that's the

      * beginning of the second list.

      */

     desc = &AdaptersName[0];

     while (*desc != '/0' || *(desc + 1) != '/0')

         desc++;

     /*

      * Found it - "desc" points to the first of the two

      * nulls at the end of the list of names, so the

      * first byte of the list of descriptions is two bytes

      * after it.

      */

     desc += 2;

    

     /*

      * Loop over the elements in the first list.

      */

     name = &AdaptersName[0];

     while (*name != '/0') {

         /*

          * Add an entry for this interface.

          */

         if (pcap_add_if_win32(&devlist, name, desc, errbuf) == -1) {

              /*

               * Failure.

               */

              ret = -1;

              break;

         }

         name += strlen(name) + 1;

         desc += strlen(desc) + 1;

     }

     if (ret != -1) {

         /*

          * We haven't had any errors yet; do any platform-specific

          * operations to add devices.

          */

         if (pcap_platform_finddevs(&devlist, errbuf) < 0)

              ret = -1;

     }

     if (ret == -1) {

         /*

          * We had an error; free the list we've been constructing.

          */

         if (devlist != NULL) {

              pcap_freealldevs(devlist);

              devlist = NULL;

         }

     }

     *alldevsp = devlist;

     free(AdaptersName);

     return (ret);

}

 

/获取可用网络适配器的一个列表与它们的描述

BOOLEAN PacketGetAdapterNames(PTSTR pStr,PULONG  BufferSize)

{

     PADAPTER_INFO TAdInfo;

     ULONG    SizeNeeded = 0;

     ULONG    SizeNames = 0;

     ULONG    SizeDesc;

     ULONG    OffDescriptions;

     TRACE_ENTER("PacketGetAdapterNames");

     TRACE_PRINT_OS_INFO();

     TRACE_PRINT2("Packet DLL version %s, Driver version %s", PacketLibraryVersion, PacketDriverVersion);

     TRACE_PRINT1("PacketGetAdapterNames: BufferSize=%u", *BufferSize);

     //

     // Check the presence on some libraries we rely on, and load them if we found them

     //f

     PacketLoadLibrariesDynamically();

     //d

     // Create the adapter information list

     //

     TRACE_PRINT("Populating the adapter list...");

     PacketPopulateAdaptersInfoList();

     WaitForSingleObject(g_AdaptersInfoMutex, INFINITE);

     if(!g_AdaptersInfoList)

     {

         ReleaseMutex(g_AdaptersInfoMutex);

         *BufferSize = 0;

         TRACE_PRINT("No adapters found in the system. Failing.");

         SetLastError(ERROR_INSUFFICIENT_BUFFER);

         TRACE_EXIT("PacketGetAdapterNames");

         return FALSE;      // No adapters to return

     }

     //

     // First scan of the list to calculate the offsets and check the sizes

     //

     for(TAdInfo = g_AdaptersInfoList; TAdInfo != NULL; TAdInfo = TAdInfo->Next)

     {

         if(TAdInfo->Flags != INFO_FLAG_DONT_EXPORT)

         {

              // Update the size variables

              SizeNeeded += (ULONG)strlen(TAdInfo->Name) + (ULONG)strlen(TAdInfo->Description) + 2;

              SizeNames += (ULONG)strlen(TAdInfo->Name) + 1;

         }

     }

 

     // Check that we don't overflow the buffer.

     // Note: 2 is the number of additional separators needed inside the list

     if(SizeNeeded + 2 > *BufferSize || pStr == NULL)

     {

         ReleaseMutex(g_AdaptersInfoMutex);

         TRACE_PRINT1("PacketGetAdapterNames: input buffer too small, we need %u bytes", *BufferSize);

         *BufferSize = SizeNeeded + 2;  // Report the required size

         TRACE_EXIT("PacketGetAdapterNames");

         SetLastError(ERROR_INSUFFICIENT_BUFFER);

         return FALSE;

     }

     OffDescriptions = SizeNames + 1;

 

     //

     // Second scan of the list to copy the information

     //

     for(TAdInfo = g_AdaptersInfoList, SizeNames = 0, SizeDesc = 0; TAdInfo != NULL; TAdInfo = TAdInfo->Next)

     {

         if(TAdInfo->Flags != INFO_FLAG_DONT_EXPORT)

         {

              // Copy the data

              StringCchCopyA(

                   ((PCHAR)pStr) + SizeNames,

                   *BufferSize - SizeNames,

                   TAdInfo->Name);

              StringCchCopyA(

                   ((PCHAR)pStr) + OffDescriptions + SizeDesc,

                   *BufferSize - OffDescriptions - SizeDesc,

                   TAdInfo->Description);

 

              // Update the size variables

              SizeNames += (ULONG)strlen(TAdInfo->Name) + 1;

              SizeDesc += (ULONG)strlen(TAdInfo->Description) + 1;

         }

     }

     // Separate the two lists

     ((PCHAR)pStr)[SizeNames] = 0;

     // End the list with a further /0

     ((PCHAR)pStr)[SizeNeeded + 1] = 0;

     ReleaseMutex(g_AdaptersInfoMutex);

     TRACE_EXIT("PacketGetAdapterNames");

     return TRUE;

}

pcap_findalldevs为wpcap.dll中查找设备列表的函数,里面调用Packet.dll中的PacketGetAdapterNames获取设备列表。它调用PacketPopulateAdaptersInfoList函数,函数PacketPopulateAdaptersInfoList() 创建适配器的链表g_AdaptersInfoList。该函数先释放掉g_AdaptersInfoList中旧的内容,然后调用PacketGetAdaptersNPF()函数用新的信息填充该链表。而PacketGetAdaptersNPF调用PacketAddAdapterNPF,从注册表中获取设备信息。PacketAddAdapterNPF又调用PacketRequest,将请求发送到NPF,

{

 

     Result=(BOOLEAN)DeviceIoControl(AdapterObject->hFile,(DWORD) Set ? (DWORD)BIOCSETOID : (DWORD)BIOCQUERYOID,OidData,sizeof(PACKET_OID_DATA)-1+OidData->Length,OidData,

 sizeof(PACKET_OID_DATA)-1+OidData->Length,&BytesReturned,NULL);

}

  所有的应用程序和驱动之间通信,最终都是调用DeviceIoControl,WriteFile和ReadFile,Npf中和DeviceIoControl对应的就是NPF_IoControl。

 

(2)       pcap_open_live

pcap_t *

pcap_open_live(const char *source, int snaplen, int promisc, int to_ms, char *errbuf)

{

     pcap_t *p;

     int status;

     p = pcap_create(source, errbuf);

     if (p == NULL)

         return (NULL);

     status = pcap_set_snaplen(p, snaplen);         //设置最大包长

     if (status < 0)

         goto fail;

     status = pcap_set_promisc(p, promisc);         //是否混杂模式

     if (status < 0)

         goto fail;

     status = pcap_set_timeout(p, to_ms);           //设置超时

     if (status < 0)

         goto fail;

     /*

      * Mark this as opened with pcap_open_live(), so that, for

      * example, we show the full list of DLT_ values, rather

      * than just the ones that are compatible with capturing

      * when not in monitor mode.  That allows existing applications

      * to work the way they used to work, but allows new applications

      * that know about the new open API to, for example, find out the

      * DLT_ values that they can select without changing whether

      * the adapter is in monitor mode or not.

      */

     p->oldstyle = 1;

     status = pcap_activate(p);                             

     if (status < 0)

         goto fail;

     return (p);

fail:

     if (status == PCAP_ERROR || status == PCAP_ERROR_NO_SUCH_DEVICE ||

         status == PCAP_ERROR_PERM_DENIED)

         strlcpy(errbuf, p->errbuf, PCAP_ERRBUF_SIZE);

     else

         snprintf(errbuf, PCAP_ERRBUF_SIZE, "%s: %s", source,

             pcap_statustostr(status));

     pcap_close(p);

     return (NULL);

}

该函数设置最大的包长,设置超时时间,设置混杂模式等。Pcap_open_live调用pcap_activate,pcap_activate定义如下:

Int pcap_activate(pcap_t *p)

{

     int status;

     status = p->activate_op(p);

     if (status >= 0)

         p->activated = 1;

     return (status);

}

Pcap_activate调用activate_op,该函数是回调函数,  p->activate_op = pcap_activate_win32;即pcap_activate调用pcap_activate_win32函数,pcap_activate_win32函数调用PacketOpenAdapter,同时函数设置PacketSetBuff和if(PacketSetMinToCopy(p->adapter,16000)==FALSE),设置设置最小copysize=16k。PacketOpenAdapter中调用使用NPF device driver打开网卡(adapter),同样调用PacketRequest,调用DeviceIoControl,即对应驱动中的NPF_IoControl。

 

 

(3) pcap_setfilter

Int pcap_setfilter(pcap_t *p, struct bpf_program *fp)

{

     return p->setfilter_op(p, fp);

}

p->setfilter_op = pcap_setfilter_win32_npf;

 

pcap_setfilter调用pcap_setfilter_win32_npf设置过滤器;

static int

pcap_setfilter_win32_npf(pcap_t *p, struct bpf_program *fp)

{

     if(PacketSetBpf(p->adapter,fp)==FALSE){

         /*

          * Kernel filter not installed.

          * XXX - fall back on userland filtering, as is done

          * on other platforms?

          */

         snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "Driver error: cannot set bpf filter: %s", pcap_win32strerror());

         return (-1);

     }

 

     /*

      * Discard any previously-received packets, as they might have

      * passed whatever filter was formerly in effect, but might

      * not pass this filter (BIOCSETF discards packets buffered

      * in the kernel, so you can lose packets in any case).

      */

     p->cc = 0;

     return (0);

}

 

BOOLEAN PacketSetBpf(LPADAPTER AdapterObject, struct bpf_program *fp)

{

     DWORD BytesReturned;

     BOOLEAN Result;

     TRACE_ENTER("PacketSetBpf");

#ifdef HAVE_WANPACKET_API

     if (AdapterObject->Flags == INFO_FLAG_NDISWAN_ADAPTER)

     {

         Result = WanPacketSetBpfFilter(AdapterObject->pWanAdapter, (PUCHAR)fp->bf_insns, fp->bf_len * sizeof(structbpf_insn));

         TRACE_EXIT("PacketSetBpf");

         return Result;

     }

#endif

#ifdef HAVE_AIRPCAP_API

     if(AdapterObject->Flags == INFO_FLAG_AIRPCAP_CARD)

     {

         Result = (BOOLEAN)g_PAirpcapSetFilter(AdapterObject->AirpcapAd,

              (char*)fp->bf_insns,

              fp->bf_len * sizeof(struct bpf_insn));

         TRACE_EXIT("PacketSetBpf");

         return Result;

     }

#endif // HAVE_AIRPCAP_API

#ifdef HAVE_NPFIM_API

     if(AdapterObject->Flags == INFO_FLAG_NPFIM_DEVICE)

     {

         Result = (BOOLEAN)g_NpfImHandlers.NpfImSetBpfFilter(AdapterObject->NpfImHandle,

              fp->bf_insns,

              fp->bf_len * sizeof(struct bpf_insn));

         TRACE_EXIT("PacketSetBpf");

         return TRUE;

     }

#endif // HAVE_NPFIM_API

 

#ifdef HAVE_DAG_API

     if(AdapterObject->Flags & INFO_FLAG_DAG_CARD)

     {

         // Delegate the filtering to higher layers since it's too expensive here

         TRACE_EXIT("PacketSetBpf");

         return TRUE;

     }

#endif // HAVE_DAG_API

 

     if (AdapterObject->Flags == INFO_FLAG_NDIS_ADAPTER)

     {

         //调用DeviceIoControl 设置过滤器

         Result = (BOOLEAN)DeviceIoControl(AdapterObject->hFile,BIOCSETF,(char*)fp->bf_insns,fp->bf_len*sizeof(structbpf_insn),NULL,0,&BytesReturned,NULL);

     }

     else

     {

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

         Result = FALSE;

     }

     TRACE_EXIT("PacketSetBpf");

     return Result;

}

 

对应驱动的NPF_IoControl。


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

查找到网卡后,open网卡,设置过滤器,然后就该读写数据包了,下面就讲讲怎么发送和接收数据包了。

(4) pcap_sendpacket

pcap_sendpacket用来发送数据包,该函数只能发送单个的数据包,

int pcap_sendpacket(pcap_t *p, const u_char *buf, int size)

{

     if (p->inject_op(p, buf, size) == -1)

         return (-1);

     return (0);

}

p->inject_op = pcap_inject_win32;

 

/* Send a packet to the network */

static int pcap_inject_win32(pcap_t *p, const void *buf, size_t size){

     LPPACKET PacketToSend;

     PacketToSend=PacketAllocatePacket();

     if (PacketToSend == NULL)

     {

         snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "send error: PacketAllocatePacket failed");

         return -1;

     }

     PacketInitPacket(PacketToSend,(PVOID)buf,size);

     if(PacketSendPacket(p->adapter,PacketToSend,TRUE) == FALSE){

         snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "send error: PacketSendPacket failed");

         PacketFreePacket(PacketToSend);

         return -1;

     }

     PacketFreePacket(PacketToSend);

     /*

      * We assume it all got sent if "PacketSendPacket()" succeeded.

      * "pcap_inject()" is expected to return the number of bytes

      * sent.

      */

     return size;

}

 

Pcap_inject_win32函数调用ParketSendPacket将数据包转发到Parket.dll,下面就看看ParketSendPacket的源码:

// 发送单个包

BOOLEAN PacketSendPacket(LPADAPTER AdapterObject,LPPACKET lpPacket,BOOLEAN Sync)

{

    DWORD        BytesTransfered;

     BOOLEAN       Result;   

     TRACE_ENTER("PacketSendPacket");

 

     UNUSED(Sync);

 

#ifdef HAVE_AIRPCAP_API

     if(AdapterObject->Flags == INFO_FLAG_AIRPCAP_CARD)

     {

         if(g_PAirpcapWrite)

         {

              Result = (BOOLEAN)g_PAirpcapWrite(AdapterObject->AirpcapAd, lpPacket->Buffer, lpPacket->Length);

              TRACE_EXIT("PacketSetMinToCopy");

             

              return Result;

         }

         else

         {

              TRACE_EXIT("PacketSetMinToCopy");

              TRACE_PRINT("Transmission not supported with this version of AirPcap");

              return FALSE;

         }

     }

#endif // HAVE_AIRPCAP_API

 

#ifdef HAVE_WANPACKET_API

     if(AdapterObject->Flags == INFO_FLAG_NDISWAN_ADAPTER)

     {

         TRACE_PRINT("PacketSendPacket: packet sending not allowed on wan adapters");

         TRACE_EXIT("PacketSendPacket");

         return FALSE;

     }

#endif // HAVE_WANPACKET_API

        

#ifdef HAVE_NPFIM_API

     if(AdapterObject->Flags == INFO_FLAG_NPFIM_DEVICE)

     {

         TRACE_PRINT("PacketSendPacket: packet sending not allowed on NPFIM adapters");

 

         TRACE_EXIT("PacketSendPacket");

         return FALSE;

     }

#endif //HAVE_NPFIM_API

 

     if (AdapterObject->Flags == INFO_FLAG_NDIS_ADAPTER)

     {

         Result = (BOOLEAN)WriteFile(AdapterObject->hFile,lpPacket->Buffer,lpPacket->Length,&BytesTransfered,NULL);

     }

     else

     {

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

         Result = FALSE;

     }

     TRACE_EXIT("PacketSendPacket");

     return Result;

}

PacketSendPacket通过 WriteFile将数据包转发出去。PacketSendPacket是parket.dll,WriteFile对应驱动中

NTSTATUS

NPF_Write(

                        IN PDEVICE_OBJECT DeviceObject,

    IN PIRP Irp

    )

 

{

    POPEN_INSTANCE          Open;

    PIO_STACK_LOCATION  IrpSp;

    PNDIS_PACKET            pPacket;

    NDIS_STATUS                 Status;

                        ULONG                  NumSends;

                        ULONG                  numSentPackets;

 

                        TRACE_ENTER();

 

                        IrpSp = IoGetCurrentIrpStackLocation(Irp);

 

    Open=IrpSp->FileObject->FsContext;

                       

                        if (NPF_StartUsingOpenInstance(Open) == FALSE)

                        {

                            //

                            // an IRP_MJ_CLEANUP was received, just fail the request

                            //

                            Irp->IoStatus.Information = 0;

                            Irp->IoStatus.Status = STATUS_CANCELLED;

                            IoCompleteRequest(Irp, IO_NO_INCREMENT);

                            TRACE_EXIT();

                            return STATUS_CANCELLED;

                        }

                        NumSends = Open->Nwrites;

                        //

                        // validate the send parameters set by the IOCTL

                        //

                        if (NumSends == 0)

                        {

                            NPF_StopUsingOpenInstance(Open);

                            Irp->IoStatus.Information = 0;

                            Irp->IoStatus.Status = STATUS_SUCCESS;

                            IoCompleteRequest(Irp, IO_NO_INCREMENT);

                            TRACE_EXIT();

                            return STATUS_SUCCESS;

                        }

                        //

                        // Validate input parameters:

                        // 1. The packet size should be greater than 0,

                        // 2. less-equal than max frame size for the link layer and

                        // 3. the maximum frame size of the link layer should not be zero.

                        //

                        if(IrpSp->Parameters.Write.Length == 0 ||     // Check that the buffer provided by the user is not empty

                            Open->MaxFrameSize == 0 ||  // Check that the MaxFrameSize is correctly initialized

                            Irp->MdlAddress == NULL ||

                            IrpSp->Parameters.Write.Length > Open->MaxFrameSize) // Check that the fame size is smaller that the MTU

                        {

                            TRACE_MESSAGE(PACKET_DEBUG_LOUD,"Frame size out of range, or maxFrameSize = 0. Send aborted");

                            NPF_StopUsingOpenInstance(Open);

                            Irp->IoStatus.Information = 0;

                            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;

                            IoCompleteRequest(Irp, IO_NO_INCREMENT);

                            TRACE_EXIT();

                            return STATUS_UNSUCCESSFUL;

                        }

                        //

                        // Increment the ref counter of the binding handle, if possible

                        //

                        if(NPF_StartUsingBinding(Open) == FALSE)

                        {

                            TRACE_MESSAGE(PACKET_DEBUG_LOUD,"Adapter is probably unbinding, cannot send packets");

                            NPF_StopUsingOpenInstance(Open);

                            Irp->IoStatus.Information = 0;

                            Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;

                            IoCompleteRequest(Irp, IO_NO_INCREMENT);

                            TRACE_EXIT();

                            return STATUS_INVALID_DEVICE_REQUEST;

                        }

                        NdisAcquireSpinLock(&Open->WriteLock);

                        if(Open->WriteInProgress)

                        {

                            // Another write operation is currently in progress

                            NdisReleaseSpinLock(&Open->WriteLock);

                            NPF_StopUsingBinding(Open);

                            TRACE_MESSAGE(PACKET_DEBUG_LOUD,"Another Send operation is in progress, aborting.");

                            NPF_StopUsingOpenInstance(Open);

                            Irp->IoStatus.Information = 0;

                            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;

                            IoCompleteRequest(Irp, IO_NO_INCREMENT);

                            TRACE_EXIT();

                            return STATUS_UNSUCCESSFUL;

                        }

                        else

                        {

                            Open->WriteInProgress = TRUE;

                            NdisResetEvent(&Open->NdisWriteCompleteEvent);

                        }

                        NdisReleaseSpinLock(&Open->WriteLock);

                        TRACE_MESSAGE2(PACKET_DEBUG_LOUD,"Max frame size = %u, packet size = %u", Open->MaxFrameSize, IrpSp->Parameters.Write.Length);

                        //

                        // reset the number of packets pending the SendComplete

                        //

                        Open->TransmitPendingPackets = 0;

                        NdisResetEvent(&Open->WriteEvent);

                        numSentPackets = 0;

                        while( numSentPackets < NumSends )

                        {

                            NdisAllocatePacket(

                                 &Status,

                                 &pPacket,

                                 Open->PacketPool

                                 );

                            if (Status == NDIS_STATUS_SUCCESS)

                            {

                                 //

                                 // packet is available, prepare it and send it with NdisSend.

                                 //

                                 //

                                 // If asked, set the flags for this packet.

                                 // Currently, the only situation in which we set the flags is to disable the reception of loopback

                                 // packets, i.e. of the packets sent by us.

                                 //

                                 if(Open->SkipSentPackets)

                                 {

                                     NdisSetPacketFlags(

                                          pPacket,

                                          g_SendPacketFlags);

                                 }

                                 // The packet hasn't a buffer that needs not to be freed after every single write

                                 RESERVED(pPacket)->FreeBufAfterWrite = FALSE;

//                               // Save the IRP associated with the packet

//                               RESERVED(pPacket)->Irp=Irp;

 

                                 //  Attach the writes buffer to the packet

                                 NdisChainBufferAtFront(pPacket,Irp->MdlAddress);

                            InterlockedIncrement(&Open->TransmitPendingPackets);

                                 NdisResetEvent(&Open->NdisWriteCompleteEvent);

                                 //

                                 //  Call the MAC  调用NdisSend发送包

                                 //

                                 NdisSend(

                                     &Status,

                                     Open->AdapterHandle,

                                     pPacket);

                                 if (Status != NDIS_STATUS_PENDING)

                                 {

                            //  The send didn't pend so call the completion handler now

                                     NPF_SendComplete(

                                          Open,

                                          pPacket,

                                          Status

                                          );

                                 }

                                 numSentPackets ++;

                            }

                            else

                            {

                                 //

                        // no packets are available in the Transmit pool, wait some time. The

                        // event gets signalled when at least half of the TX packet pool packets

                        // are available

                                 //

                                 NdisWaitEvent(&Open->WriteEvent,1); 

                            }

                        }

                        //

                      // when we reach this point, all the packets have been enqueued to NdisSend,

                    // we just need to wait for all the packets to be completed by the SendComplete

                     // (if any of the NdisSend requests returned STATUS_PENDING)

                        //

                        NdisWaitEvent(&Open->NdisWriteCompleteEvent, 0);

                        //

// all the packets have been transmitted, release the use of the adapter binding

                        //

                        NPF_StopUsingBinding(Open);

                        //

                        // no more writes are in progress

                        //

                        NdisAcquireSpinLock(&Open->WriteLock);

                        Open->WriteInProgress = FALSE;

                        NdisReleaseSpinLock(&Open->WriteLock);

                        NPF_StopUsingOpenInstance(Open);

                        //

                        // Complete the Irp and return success

                        //

                        Irp->IoStatus.Status = STATUS_SUCCESS;

                        Irp->IoStatus.Information = IrpSp->Parameters.Write.Length;

                        IoCompleteRequest(Irp, IO_NO_INCREMENT);

                        TRACE_EXIT();

                        return STATUS_SUCCESS;

}

 


 

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


   上一篇讲了怎么发送数据包,这次接着讲怎么接收数据包,数据包过滤后,就被复制到内核缓冲区(kernel buffer),接收数据包的方式有2种,使用回调函数接收数据包,比如pcap_loop,pcap_dispatch,二是非回调函数的方式来接收数据包,如pcap_ next, pcap_next_ex。这一篇讲讲怎么发送数据包。

(1)       pcap_loop函数调用read_op(p,cnt,callback,user);

int pcap_loop(pcap_t *p,int cnt, pcap_handler callback,u_char* user) 调用read_op(p,cnt,callback,user),该函数是一个回调函数,在pcap_win32.c中有p->read_op=pcap_read_win32_npf;

 

//pcap_loop,pcap_dispatch,pcap_next,pcap_next_ex中调用的函数

static int pcap_read_win32_npf(pcap_t *p, int cnt, pcap_handler callback, u_char *user)

{

     int cc;

     int n = 0;

     register u_char *bp, *ep;

#ifdef HAVE_REMOTE

     static int samp_npkt;                // parameter needed for sampling, with '1 out of N' method has been requested

     static struct timeval samp_time; // parameter needed for sampling, with '1 every N ms' method has been requested

#endif   /* HAVE_REMOTE */

     cc = p->cc;

     if (p->cc == 0) {

         /*

          * Has "pcap_breakloop()" been called?

          */

         if (p->break_loop) {

              /*

               * Yes - clear the flag that indicates that it

               * has, and return -2 to indicate that we were

               * told to break out of the loop.

               */

              p->break_loop = 0;

              return (-2);

         }

         /* capture the packets 接收包*/

         if(PacketReceivePacket(p->adapter,p->Packet,TRUE)==FALSE){

              snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "read error: PacketReceivePacket failed");

              return (-1);

         }

         cc = p->Packet->ulBytesReceived;

         bp = p->Packet->Buffer;

     }

     else

         bp = p->bp;

     /*

      * Loop through each packet.

      */

#define bhp ((struct bpf_hdr *)bp)

     ep = bp + cc;                   

     while (1) {

         register int caplen, hdrlen;

         /*

          * Has "pcap_breakloop()" been called?

          * If so, return immediately - if we haven't read any

          * packets, clear the flag and return -2 to indicate

          * that we were told to break out of the loop, otherwise

          * leave the flag set, so that the *next* call will break

          * out of the loop without having read any packets, and

          * return the number of packets we've processed so far.

          */

         if (p->break_loop) {

              if (n == 0) {

                   p->break_loop = 0;         

                   return (-2);

              } else {

                   p->bp = bp;

                   p->cc = ep - bp;                

                   return (n);           

              }

         }

         if (bp >= ep)                   

              break;

         caplen = bhp->bh_caplen;        

         hdrlen = bhp->bh_hdrlen;        

#ifdef HAVE_REMOTE

         if (p->rmt_samp.method == PCAP_SAMP_1_EVERY_N)

         {

              samp_npkt= (samp_npkt + 1) % p->rmt_samp.value;

              // Discard all packets that are not '1 out of N'

              if (samp_npkt != 0)

              {

                   bp += BPF_WORDALIGN(caplen + hdrlen);

                   continue;

              }

         }

 

         if (p->rmt_samp.method == PCAP_SAMP_FIRST_AFTER_N_MS)

         {

         struct pcap_pkthdr *pkt_header= (struct pcap_pkthdr*) bp;

              // Check if the timestamp of the arrived packet is smaller than our target time

              if ( (pkt_header->ts.tv_sec < samp_time.tv_sec) ||

                       ( (pkt_header->ts.tv_sec == samp_time.tv_sec) && (pkt_header->ts.tv_usec < samp_time.tv_usec) ) )

              {

                   bp += BPF_WORDALIGN(caplen + hdrlen);

                   continue;

              }

              // The arrived packet is suitable for being sent to the remote host

              // So, let's update the target time

              samp_time.tv_usec= pkt_header->ts.tv_usec + p->rmt_samp.value * 1000;

              if (samp_time.tv_usec > 1000000)

              {

                   samp_time.tv_sec= pkt_header->ts.tv_sec + samp_time.tv_usec / 1000000;

                   samp_time.tv_usec= samp_time.tv_usec % 1000000;

              }

         }

#endif   /* HAVE_REMOTE */

         /*

          * XXX A bpf_hdr matches a pcap_pkthdr.

          */

         (*callback)(user, (struct pcap_pkthdr*)bp, bp + hdrlen);    

         bp += BPF_WORDALIGN(caplen + hdrlen);

         if (++n >= cnt && cnt > 0) {        

              p->bp = bp;

              p->cc = ep - bp;

              return (n);                              

         }

     }

#undef bhp

     p->cc = 0;

     return (n);

Pcap_loop函数和pcap_dispatch函数类似,都是调用pcap_read_win32_npf函数pcap_loop 函数和pcap_dispatch函数的区别是,pcap_dispatch 当超时时,函数就返回,pcap_loop函数只有读到cnt个数据包才返回。Pcap_next函数和pcap_next_ex函数都是调用pcap_read_win32_npf函数,pcap_next函数和pcap_next_ex函数的区别是pcap_next调用pcap_dispatch函数后才调用pcap_read_win32_npf函数,而pcap_next_ex直接调用pcap_read_win32_npf函数,增加了远程模式和读文件模式。pcap_read_win32_npf函数调用parket 中PacketRecivePacket函数。

BOOLEAN PacketReceivePacket(LPADAPTER AdapterObject,LPPACKET lpPacket,BOOLEAN Sync)  //Sync=TRUE

{

     BOOLEAN res;

     UNUSED(Sync);

     TRACE_ENTER("PacketReceivePacket");

#ifdef HAVE_WANPACKET_API

     if (AdapterObject->Flags == INFO_FLAG_NDISWAN_ADAPTER)

     {

         lpPacket->ulBytesReceived = WanPacketReceivePacket(AdapterObject->pWanAdapter, lpPacket->Buffer, lpPacket->Length);

         TRACE_EXIT("PacketReceivePacket");

         return TRUE;

     }

#endif //HAVE_WANPACKET_API

#ifdef HAVE_AIRPCAP_API

     if(AdapterObject->Flags == INFO_FLAG_AIRPCAP_CARD)

     {

         //

         // Wait for data, only if the user requested us to do that

         //

         if((int)AdapterObject->ReadTimeOut != -1)

         {

              WaitForSingleObject(AdapterObject->ReadEvent, (AdapterObject->ReadTimeOut==0)? INFINITE: AdapterObject->ReadTimeOut);

         }

         //

         // Read the data.

         // g_PAirpcapRead always returns immediately.

         //

         res = (BOOLEAN)g_PAirpcapRead(AdapterObject->AirpcapAd,

                   lpPacket->Buffer,

                   lpPacket->Length,

                   &lpPacket->ulBytesReceived);

         TRACE_EXIT("PacketReceivePacket");

         return res;

     }

#endif // HAVE_AIRPCAP_API

#ifdef HAVE_NPFIM_API

     if(AdapterObject->Flags == INFO_FLAG_NPFIM_DEVICE)

     {

         //

         // Read the data.

         // NpfImReceivePacket performs its own wait internally.

         //

         res = (BOOLEAN)g_NpfImHandlers.NpfImReceivePackets(AdapterObject->NpfImHandle,

                   lpPacket->Buffer,

                   lpPacket->Length,

                   &lpPacket->ulBytesReceived);

         TRACE_EXIT("PacketReceivePacket");

         return res;

     }

#endif // HAVE_NPFIM_API

#ifdef HAVE_DAG_API

     if((AdapterObject->Flags & INFO_FLAG_DAG_CARD) || (AdapterObject->Flags & INFO_FLAG_DAG_FILE))

     {

         g_p_dagc_wait(AdapterObject->pDagCard, &AdapterObject->DagReadTimeout);

         res = (BOOLEAN)(g_p_dagc_receive(AdapterObject->pDagCard, (u_char**)&AdapterObject->DagBuffer, (u_int*)&lpPacket->ulBytesReceived) == 0);

         TRACE_EXIT("PacketReceivePacket");

         return res;

     }

#endif // HAVE_DAG_API

     if (AdapterObject->Flags == INFO_FLAG_NDIS_ADAPTER)

     {

         if((int)AdapterObject->ReadTimeOut != -1)

              WaitForSingleObject(AdapterObject->ReadEvent, (AdapterObject->ReadTimeOut==0)?INFINITE:AdapterObject->ReadTimeOut);

         //从驱动中读取数据包

         res = (BOOLEAN)ReadFile(AdapterObject->hFile, lpPacket->Buffer, lpPacket->Length, &lpPacket->ulBytesReceived,NULL);

     }

     else

     {

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

         res = FALSE;

     }

     TRACE_EXIT("PacketReceivePacket");

     return res;

}

 

PacketReceivePacket函数ReadFile从驱动中读取数据包,应用程序和驱动进行通信到最下一层都是通过DeviceIoControl,ReadFile,WriteFile函数。这些函数在上面都已经介绍了。ReadFile对应驱动程序中

NTSTATUS NPF_Read(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)

{

  POPEN_INSTANCE      Open;

  PIO_STACK_LOCATION  IrpSp;

  PUCHAR               packp;

     ULONG                  Input_Buffer_Length;

     UINT               Thead;

     UINT               Ttail;

     UINT               TLastByte;

     PUCHAR                 CurrBuff;

     LARGE_INTEGER      CapTime;

     LARGE_INTEGER      TimeFreq;

     struct bpf_hdr         *header;

     KIRQL                  Irql;

     PUCHAR                 UserPointer;

     ULONG                  bytecopy;

     UINT               SizeToCopy;

     UINT               PktLen;

     ULONG                  copied,count,current_cpu,av,plen,increment,ToCopy,available;

     CpuPrivateData         *LocalData;

     ULONG                  i;

     ULONG                  Occupation;

     IF_LOUD(DbgPrint("NPF: Read/n");)

     IrpSp = IoGetCurrentIrpStackLocation(Irp);         //获取Irp当前堆栈

    Open=IrpSp->FileObject->FsContext;                  //得到打开上下文

     if (NPF_StartUsingOpenInstance(Open) == FALSE)

     {

         //

         // an IRP_MJ_CLEANUP was received, just fail the request

         //

         Irp->IoStatus.Information = 0;

         Irp->IoStatus.Status = STATUS_CANCELLED;

         IoCompleteRequest(Irp, IO_NO_INCREMENT);       //完成irp请求

         TRACE_EXIT();

         return STATUS_CANCELLED;

     }

     //

     // we need to test if the device is still bound(绑定) to the Network adapter,

     // so we perform a start/stop using binding.

     // This is not critical, since we just want to have a quick way to have the

     // dispatch read fail in case the adapter has been unbound

     if(NPF_StartUsingBinding(Open) == FALSE)

     {

         NPF_StopUsingOpenInstance(Open);

         // The Network adapter has been removed or diasabled

         EXIT_FAILURE(0);

     }

     NPF_StopUsingBinding(Open);

     if (Open->Size == 0)  

     {

         NPF_StopUsingOpenInstance(Open);

         EXIT_FAILURE(0);

     }

     if( Open->mode & MODE_DUMP && Open->DumpFileHandle == NULL ){ 

         // this instance is in dump mode, but the dump file has still not been opened

         NPF_StopUsingOpenInstance(Open);

         EXIT_FAILURE(0);

     }

     Occupation=0;

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

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

     //See if the buffer is full enough to be copied 判断缓冲区是否enough

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

     {

         if (Open->ReadEvent != NULL)

         {

              //wait until some packets arrive or the timeout expires     

              if(Open->TimeOut.QuadPart != (LONGLONG)IMMEDIATE)

                   KeWaitForSingleObject(Open->ReadEvent,

                       UserRequest,

                       KernelMode,

                       TRUE,

                       (Open->TimeOut.QuadPart == (LONGLONG)0)? NULL: &(Open->TimeOut));

              KeClearEvent(Open->ReadEvent);

         }       

         //统计模式

         if(Open->mode & MODE_STAT)

         {   //this capture instance is in statistics mode

#ifdef NDIS50

              CurrBuff=(PUCHAR)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);

#else

              CurrBuff=(PUCHAR)MmGetSystemAddressForMdl(Irp->MdlAddress);

#endif

              if (CurrBuff == NULL)

              {

                   NPF_StopUsingOpenInstance(Open);

                   EXIT_FAILURE(0);

              }

              if (Open->mode & MODE_DUMP)

              {

                   if (IrpSp->Parameters.Read.Length < sizeof(struct bpf_hdr) + 24)

                   {

                       NPF_StopUsingOpenInstance(Open);

                       Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;

                       IoCompleteRequest(Irp, IO_NO_INCREMENT);

                       return STATUS_BUFFER_TOO_SMALL;

                   }

              }

              else

              {

                   if (IrpSp->Parameters.Read.Length < sizeof(struct bpf_hdr) + 16)

                   {

                       NPF_StopUsingOpenInstance(Open);

                       Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;

                       IoCompleteRequest(Irp, IO_NO_INCREMENT);

                       return STATUS_BUFFER_TOO_SMALL;

                   }

              }

              //fill the bpf header for this packet

              header=(struct bpf_hdr*)CurrBuff;

              GET_TIME(&header->bh_tstamp,&G_Start_Time);

              if(Open->mode & MODE_DUMP){

                   *(LONGLONG*)(CurrBuff+sizeof(struct bpf_hdr)+16)=Open->DumpOffset.QuadPart;

                   header->bh_caplen=24;

                   header->bh_datalen=24;

                   Irp->IoStatus.Information = 24 + sizeof(struct bpf_hdr);

              }

              else{

                   header->bh_caplen=16;

                   header->bh_datalen=16;

                   header->bh_hdrlen=sizeof(struct bpf_hdr);

                   Irp->IoStatus.Information = 16 + sizeof(struct bpf_hdr);

              }

              *(LONGLONG*)(CurrBuff+sizeof(struct bpf_hdr))=Open->Npackets.QuadPart;

              *(LONGLONG*)(CurrBuff+sizeof(struct bpf_hdr)+8)=Open->Nbytes.QuadPart;

              //reset the countetrs

              NdisAcquireSpinLock( &Open->CountersLock );

              Open->Npackets.QuadPart=0;

              Open->Nbytes.QuadPart=0;

              NdisReleaseSpinLock( &Open->CountersLock );

              NPF_StopUsingOpenInstance(Open);

              Irp->IoStatus.Status = STATUS_SUCCESS;

              IoCompleteRequest(Irp, IO_NO_INCREMENT);

              return STATUS_SUCCESS;

         }

//  监控模式

// The MONITOR_MODE (aka TME extensions) is not supported on

// 64 bit architectures

//

#ifdef HAVE_BUGGY_TME_SUPPORT

         if(Open->mode==MODE_MON)   //this capture instance is in monitor mode

         {  

              PTME_DATA data;

              ULONG cnt;

              ULONG block_size;

              PUCHAR tmp;

#ifdef NDIS50

              UserPointer=MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);

#else

              UserPointer=MmGetSystemAddressForMdl(Irp->MdlAddress);

#endif

              if (UserPointer == NULL)

              {

                   NPF_StopUsingOpenInstance(Open);

                   EXIT_FAILURE(0);

              }

              if ((!IS_VALIDATED(Open->tme.validated_blocks,Open->tme.active_read))||(IrpSp->Parameters.Read.Length<sizeof(struct bpf_hdr)))

              {   

                   NPF_StopUsingOpenInstance(Open);

                   EXIT_FAILURE(0);

              }

              header=(struct bpf_hdr*)UserPointer;

              GET_TIME(&header->bh_tstamp,&G_Start_Time);

              header->bh_hdrlen=sizeof(struct bpf_hdr);

              //moves user memory pointer

              UserPointer+=sizeof(struct bpf_hdr);

              //calculus of data to be copied

              //if the user buffer is smaller than data to be copied,

              //only some data will be copied

              data=&Open->tme.block_data[Open->tme.active_read];

              if (data->last_read.tv_sec!=0)

                   data->last_read=header->bh_tstamp;

              bytecopy=data->block_size*data->filled_blocks;

              if ((IrpSp->Parameters.Read.Length-sizeof(struct bpf_hdr))<bytecopy)

                   bytecopy=(IrpSp->Parameters.Read.Length-sizeof(struct bpf_hdr))/ data->block_size;

              else

                   bytecopy=data->filled_blocks;

              tmp=data->shared_memory_base_address;

              block_size=data->block_size;

              for (cnt=0;cnt<bytecopy;cnt++)

              {

                   NdisAcquireSpinLock(&Open->MachineLock);

                   RtlCopyMemory(UserPointer,tmp,block_size);

                   NdisReleaseSpinLock(&Open->MachineLock);

                   tmp+=block_size;

                   UserPointer+=block_size;

              }       

              bytecopy*=block_size;

              header->bh_caplen=bytecopy;

              header->bh_datalen=header->bh_caplen;

              NPF_StopUsingOpenInstance(Open);

              EXIT_SUCCESS(bytecopy+sizeof(struct bpf_hdr));

         }

//end of //this capture instance is in monitor mode

         Occupation=0;

         //重新计算内核缓冲区的占用;

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

              Occupation += (Open->Size - Open->CpuData[i].Free);

         if ( Occupation == 0 || Open->mode & MODE_DUMP)

              // The timeout has expired, but the buffer is still empty (or the packets must be written to file).

              // We must awake the application, returning an empty buffer.

         {

              NPF_StopUsingOpenInstance(Open);

              EXIT_SUCCESS(0);

         }   

#else // not HAVE_BUGGY_TME_SUPPORT

         if(Open->mode==MODE_MON)   //this capture instance is in monitor mode

         {  

              NPF_StopUsingOpenInstance(Open);

              EXIT_FAILURE(0);

         }

#endif // HAVE_BUGGY_TME_SUPPORT

     }

//------------------------------------------------------------------------------

     copied=0;

     count=0;

     current_cpu=0;

     available = IrpSp->Parameters.Read.Length;

#ifdef NDIS50

     packp=(PUCHAR)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);

#else

     packp=(PUCHAR)MmGetSystemAddressForMdl(Irp->MdlAddress);

#endif

     if (packp == NULL)

     {

         NPF_StopUsingOpenInstance(Open);

         EXIT_FAILURE(0);

     }

     if (Open->ReadEvent != NULL)

         KeClearEvent(Open->ReadEvent);

     while (count < g_NCpu) //round robin on the CPUs, if count = NCpu there are no packets left to be copied

     {

         if (available == copied)

         {

              NPF_StopUsingOpenInstance(Open);

              EXIT_SUCCESS(copied);

         }

         LocalData = &Open->CpuData[current_cpu];

         if (LocalData->Free < Open->Size) 

         {  //there are some packets in the selected (aka LocalData) buffer

              struct PacketHeader *Header = (struct PacketHeader*)(LocalData->Buffer + LocalData->C);

              if ( Header->SN == Open->ReaderSN)

              {   //check if it the next one to be copied

                   plen = Header->header.bh_caplen;

                   if (plen + sizeof (struct bpf_hdr) > available - copied) 

                   {  //if the packet does not fit into the user buffer, we've ended copying packets 

                       //如果包没有拷贝到用户缓冲区,结束拷贝数据包

                       NPF_StopUsingOpenInstance(Open);

                       EXIT_SUCCESS(copied);

                   }

//                 FIX_TIMESTAMPS(&Header->header.bh_tstamp);

                   *((struct bpf_hdr*)(&packp[copied]))=Header->header;

                   copied += sizeof(struct bpf_hdr);

                   LocalData->C += sizeof(struct PacketHeader);

                   if (LocalData->C == Open->Size)

                       LocalData->C = 0;

                   if (Open->Size - LocalData->C < plen)

                   {

                       //the packet is fragmented in the buffer (i.e. it skips the buffer boundary)

                       ToCopy = Open->Size - LocalData->C;

                       RtlCopyMemory(packp + copied,LocalData->Buffer + LocalData->C,ToCopy);

                       RtlCopyMemory(packp + copied + ToCopy,LocalData->Buffer,plen-ToCopy);

                       LocalData->C = plen-ToCopy;

                   }

                   else

                   {

                       //the packet is not fragmented

                       RtlCopyMemory(packp + copied ,LocalData->Buffer + LocalData->C ,plen);

                       LocalData->C += plen;

              //       if (c==size)  inutile, contemplato nell "header atomico"

              //            c=0;

                   }

                   Open->ReaderSN++;

                   copied+=Packet_WORDALIGN(plen);

                   increment = plen + sizeof(struct PacketHeader);

                   if ( Open->Size - LocalData->C < sizeof(struct PacketHeader))

                   {   //the next packet would be saved at the end of the buffer, but the NewHeader struct would be fragmented

                       //so the producer (--> the consumer) skips to the beginning of the buffer

                       increment += Open->Size-LocalData->C;

                       LocalData->C=0;

                   }

                   InterlockedExchangeAdd(&Open->CpuData[current_cpu].Free,increment);

                   count=0;

              }

              else

              {

                   current_cpu=(current_cpu+1)%g_NCpu;

                   count++;

              }

        

         }

         else

         {

              current_cpu=(current_cpu+1)%g_NCpu;

              count++;

         }

     }

     {

         NPF_StopUsingOpenInstance(Open);

         EXIT_SUCCESS(copied);

     }

}

讲到这里,大家都知道,应用程序怎么从驱动中读取数据了,但是数据怎么到达内核缓冲区呢,下面介绍一个回调函数,NPF_tap()

 

NDIS_STATUS NPF_tap (IN NDIS_HANDLE ProtocolBindingContext,IN NDIS_HANDLE MacReceiveContext,

                        IN PVOID HeaderBuffer,IN UINT HeaderBufferSize,IN PVOID LookaheadBuffer,

                        IN UINT LookaheadBufferSize,IN UINT PacketSize)

{

    POPEN_INSTANCE      Open;

    PNDIS_PACKET        pPacket;

    ULONG               SizeToTransfer;

    NDIS_STATUS         Status;

    UINT                BytesTransfered;

    ULONG               BufferLength;

    PMDL                pMdl1,pMdl2;

     LARGE_INTEGER      CapTime;

     LARGE_INTEGER      TimeFreq;

     UINT               fres;

     USHORT                 NPFHdrSize;

 

     CpuPrivateData         *LocalData;

     ULONG                  Cpu;

     struct PacketHeader    *Header;

     ULONG                  ToCopy;

     ULONG                  increment;

     ULONG                  i;

     BOOLEAN                ShouldReleaseBufferLock;

 

    IF_VERY_LOUD(DbgPrint("NPF: tap/n");)

     IF_VERY_LOUD(DbgPrint("HeaderBufferSize=%u, LookAheadBuffer=%p, LookaheadBufferSize=%u, PacketSize=%u/n",

     HeaderBufferSize,

     LookaheadBuffer,

     LookaheadBufferSize,

     PacketSize);)

 

     Open= (POPEN_INSTANCE)ProtocolBindingContext;

    

    Cpu = KeGetCurrentProcessorNumber();

     LocalData = &Open->CpuData[Cpu];

 

     LocalData->Received++;

     IF_LOUD(DbgPrint("Received on CPU %d /t%d/n",Cpu,LocalData->Received);)

//   Open->Received++;      // Number of packets received by filter ++

 

 

     NdisAcquireSpinLock(&Open->MachineLock);

 

     //

     //Check if the lookahead buffer follows the mac header.

     //If the data follow the header (i.e. there is only a buffer) a normal bpf_filter() is

     //executed on the packet.

     //Otherwise if there are 2 separate buffers (this could be the case of LAN emulation or

     //things like this) bpf_filter_with_2_buffers() is executed.

     //

     if((UINT)((PUCHAR)LookaheadBuffer-(PUCHAR)HeaderBuffer) != HeaderBufferSize)

     {

#ifdef  HAVE_BUGGY_TME_SUPPORT

         fres=bpf_filter_with_2_buffers((struct bpf_insn*)(Open->bpfprogram),

                                             HeaderBuffer,

                                             LookaheadBuffer,

                                             HeaderBufferSize,

                                             PacketSize+HeaderBufferSize,

                                             LookaheadBufferSize+HeaderBufferSize,

                                             &Open->mem_ex,

                                             &Open->tme,

                                             &G_Start_Time);

#else // HAVE_BUGGY_TME_SUPPORT

         fres=bpf_filter_with_2_buffers((struct bpf_insn*)(Open->bpfprogram),

                                             HeaderBuffer,

                                             LookaheadBuffer,

                                             HeaderBufferSize,

                                             PacketSize+HeaderBufferSize,

                                             LookaheadBufferSize+HeaderBufferSize);

#endif // HAVE_BUGGY_TME_SUPPORT

     }   

     else

//

// the jit filter is available on x86 (32 bit) only

//

#ifdef _X86_

 

         if(Open->Filter != NULL)

         {

              if (Open->bpfprogram != NULL)

              {

                   fres=Open->Filter->Function(HeaderBuffer,

                                          PacketSize+HeaderBufferSize,

                                          LookaheadBufferSize+HeaderBufferSize);

              }

              else

                   fres = -1;

         }

         else

#endif //_X86_

    

#ifdef HAVE_BUGGY_TME_SUPPORT

              fres=bpf_filter((struct bpf_insn*)(Open->bpfprogram),

                         HeaderBuffer,

                            PacketSize+HeaderBufferSize,

                            LookaheadBufferSize+HeaderBufferSize,

                            &Open->mem_ex,

                            &Open->tme,

                            &G_Start_Time);

#else //HAVE_BUGGY_TME_SUPPORT

              fres=bpf_filter((struct bpf_insn*)(Open->bpfprogram),

                         HeaderBuffer,

                            PacketSize+HeaderBufferSize,

                            LookaheadBufferSize+HeaderBufferSize);

#endif //HAVE_BUGGY_TME_SUPPORT

 

     NdisReleaseSpinLock(&Open->MachineLock);

 

//

// The MONITOR_MODE (aka TME extensions) is not supported on

// 64 bit architectures

//

#ifdef HAVE_BUGGY_TME_SUPPORT

     if(Open->mode==MODE_MON)

     // we are in monitor mode

     {

         if (fres==1)

         {

              if (Open->ReadEvent != NULL)

              {

                   KeSetEvent(Open->ReadEvent,0,FALSE);

              }

         }

         return NDIS_STATUS_NOT_ACCEPTED;

 

     }

#endif //HAVE_BUGGY_TME_SUPPORT

 

     if(fres==0)

     {

         // Packet not accepted by the filter, ignore it.

         return NDIS_STATUS_NOT_ACCEPTED;

     }

 

     //if the filter returns -1 the whole packet must be accepted

     if(fres == -1 || fres > PacketSize+HeaderBufferSize)

         fres = PacketSize+HeaderBufferSize;

 

     if(Open->mode & MODE_STAT)

     {

     // we are in statistics mode

         NdisAcquireSpinLock( &Open->CountersLock );

 

         Open->Npackets.QuadPart++;

        

         if(PacketSize+HeaderBufferSize<60)

              Open->Nbytes.QuadPart+=60;

         else

              Open->Nbytes.QuadPart+=PacketSize+HeaderBufferSize;

         // add preamble+SFD+FCS to the packet

         // these values must be considered because are not part of the packet received from NDIS

         Open->Nbytes.QuadPart+=12;

 

         NdisReleaseSpinLock( &Open->CountersLock );

        

         if(!(Open->mode & MODE_DUMP))

         {

              return NDIS_STATUS_NOT_ACCEPTED;

         }

     }

     if(Open->Size == 0)

     {

         LocalData->Dropped++;

         return NDIS_STATUS_NOT_ACCEPTED;

     }

 

     if(Open->mode & MODE_DUMP && Open->MaxDumpPacks)

     {

         ULONG Accepted=0;

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

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

         if(  Accepted > Open->MaxDumpPacks)

         {

              // Reached the max number of packets to save in the dump file. Discard the packet and stop the dump thread.

              Open->DumpLimitReached = TRUE; // This stops the thread

              // Awake the dump thread

              NdisSetEvent(&Open->DumpEvent);

              // Awake the application

              if (Open->ReadEvent != NULL)

                   KeSetEvent(Open->ReadEvent,0,FALSE);

 

              return NDIS_STATUS_NOT_ACCEPTED;

         }

     }

 

     //////////////////////////////COPIA.C//////////////////////////////////////////77

     ShouldReleaseBufferLock = TRUE;

     NdisDprAcquireSpinLock(&LocalData->BufferLock);

     do

     {

         if (fres + sizeof(struct PacketHeader) > LocalData->Free)

         {

              LocalData->Dropped++;

              break;

         }

         if (LocalData->TransferMdl1 != NULL)

         {

              //

              //if TransferMdl is not NULL, there is some TransferData pending (i.e. not having called TransferDataComplete, yet)

              //in order to avoid buffer corruption, we drop the packet

              //

              LocalData->Dropped++;

              break;

         }

         if (LookaheadBufferSize + HeaderBufferSize >= fres)

         {

              //

              // we do not need to call NdisTransferData, either because we need only the HeaderBuffer, or because the LookaheadBuffer

              // contains what we need

              //

              Header = (struct PacketHeader*)(LocalData->Buffer + LocalData->P);

              LocalData->Accepted++;

              GET_TIME(&Header->header.bh_tstamp,&G_Start_Time);

              Header->SN = InterlockedIncrement(&Open->WriterSN) - 1;

              Header->header.bh_caplen = fres;

              Header->header.bh_datalen = PacketSize + HeaderBufferSize;

              Header->header.bh_hdrlen=sizeof(struct bpf_hdr);

              LocalData->P +=sizeof(struct PacketHeader);

              if (LocalData->P == Open->Size)

                   LocalData->P = 0;

              if ( fres <= HeaderBufferSize || (UINT)( (PUCHAR)LookaheadBuffer - (PUCHAR)HeaderBuffer ) ==HeaderBufferSize )

              {

                   //

                   //we can consider the buffer contiguous, either because we use only the data

                   //present in the HeaderBuffer, or because HeaderBuffer and LookaheadBuffer are contiguous

                   // ;-))))))

                   //

                   if (Open->Size - LocalData->P < fres)

                   {

                       //the packet will be fragmented in the buffer (aka, it will skip the buffer boundary)

                       //two copies!!

                       ToCopy = Open->Size - LocalData->P;

                       NdisMoveMappedMemory(LocalData->Buffer + LocalData->P,HeaderBuffer, ToCopy);

                       NdisMoveMappedMemory(LocalData->Buffer + 0 , (PUCHAR)HeaderBuffer + ToCopy, fres - ToCopy);

                       LocalData->P = fres-ToCopy;

                   }

                   else

                   {

                       //the packet does not need to be fragmented in the buffer (aka, it doesn't skip the buffer boundary)

                       // ;-)))))) only ONE copy

                       NdisMoveMappedMemory(LocalData->Buffer + LocalData->P, HeaderBuffer, fres);

                       LocalData->P += fres;

                   }

              }

              else

              {

                   //HeaderBuffer and LookAhead buffer are NOT contiguous,

                   //AND, we need some bytes from the LookaheadBuffer, too

                   if (Open->Size - LocalData->P < fres)

                   {

                       //the packet will be fragmented in the buffer (aka, it will skip the buffer boundary)

                       if (Open->Size - LocalData->P >= HeaderBufferSize)

                       {

                            //HeaderBuffer is NOT fragmented

                            NdisMoveMappedMemory(LocalData->Buffer + LocalData->P, HeaderBuffer, HeaderBufferSize);

                            LocalData->P += HeaderBufferSize;

                            if (LocalData->P == Open->Size)

                            {

                                 //the fragmentation of the packet in the buffer is the same fragmentation

                                 //in HeaderBuffer+LookaheadBuffer

                                 LocalData->P=0;   

                                 NdisMoveMappedMemory(LocalData->Buffer + 0, LookaheadBuffer, fres -HeaderBufferSize);

                                 LocalData->P += (fres - HeaderBufferSize);

                            }

                            else

                            {

                                 //LookAheadBuffer is fragmented, two copies

                                 ToCopy = Open->Size - LocalData->P;

                                 NdisMoveMappedMemory(LocalData->Buffer + LocalData->P, LookaheadBuffer, ToCopy);

                                 LocalData->P=0;

                                 NdisMoveMappedMemory(LocalData->Buffer + 0, (PUCHAR)LookaheadBuffer+ ToCopy, fres -HeaderBufferSize - ToCopy);

                                 LocalData->P = fres - HeaderBufferSize - ToCopy;

                            }

                       }

                       else

                       {

                            //HeaderBuffer is fragmented in the buffer (aka, it will skip the buffer boundary)

                            //two copies to copy the HeaderBuffer

                            ToCopy = Open->Size - LocalData->P;

                            NdisMoveMappedMemory(LocalData->Buffer + LocalData->P, HeaderBuffer, ToCopy);

                            LocalData->P = 0;

                            NdisMoveMappedMemory(LocalData->Buffer + 0, (PUCHAR)HeaderBuffer + ToCopy,HeaderBufferSize - ToCopy);

                            LocalData->P = HeaderBufferSize - ToCopy;

 

                            //only one copy to copy the LookaheadBuffer

                            NdisMoveMappedMemory(LocalData->Buffer + LocalData->P, LookaheadBuffer, fres-HeaderBufferSize);

                            LocalData->P += (fres - HeaderBufferSize);

                       }

                   }

                   else

                   {   

                       //the packet won't be fragmented in the destination buffer (aka, it won't skip the buffer boundary)

                       //two copies, the former to copy the HeaderBuffer, the latter to copy the LookaheadBuffer

                       NdisMoveMappedMemory(LocalData->Buffer + LocalData->P, HeaderBuffer, HeaderBufferSize);

                       LocalData->P += HeaderBufferSize;

                       NdisMoveMappedMemory(LocalData->Buffer + LocalData->P, LookaheadBuffer, fres -HeaderBufferSize);

                       LocalData->P += (fres - HeaderBufferSize);

                   }       

              }       

              increment = fres + sizeof(struct PacketHeader);

              if (Open->Size - LocalData->P < sizeof(struct PacketHeader))  //we check that the available, AND contiguous, space in the buffer will fit

              {                                                                   //the NewHeader structure, at least, otherwise we skip the producer

                   increment += Open->Size-LocalData->P;                 //at the beginning of the buffer (p = 0), and decrement the free bytes appropriately

                   LocalData->P = 0;

              }

              InterlockedExchangeAdd(&LocalData->Free, (ULONG)(-(LONG)increment));

              if(Open->Size - LocalData->Free >= Open->MinToCopy)

              {

                   if(Open->mode & MODE_DUMP)

                       NdisSetEvent(&Open->DumpEvent);

                   else

                   {       

                       if (Open->ReadEvent != NULL)

                       {

                            KeSetEvent(Open->ReadEvent,0,FALSE);

                       }

                   }

              }

              break;

         }

         else

         {

              IF_LOUD(DbgPrint("TransferData!!/n");)

                   //ndisTransferData required

                   LocalData->NewP = LocalData->P;

              LocalData->NewP +=sizeof(struct PacketHeader);

              if (LocalData->NewP == Open->Size)

                   LocalData->NewP = 0;

              //first of all, surely the header must be copied

              if (Open->Size-LocalData->NewP >= HeaderBufferSize)

              {

                   //1 copy!

                   NdisMoveMappedMemory(LocalData->Buffer + LocalData->NewP, HeaderBuffer, HeaderBufferSize);

                   LocalData->NewP += HeaderBufferSize;

                   if (LocalData->NewP == Open->Size)

                       LocalData->NewP = 0;

              }

              else

              {

                   ToCopy = Open->Size - LocalData->NewP;

                   NdisMoveMappedMemory(LocalData->Buffer + LocalData->NewP, HeaderBuffer, ToCopy);

                   NdisMoveMappedMemory(LocalData->Buffer + 0, (PUCHAR)HeaderBuffer + ToCopy, HeaderBufferSize -ToCopy);

                   LocalData->NewP = HeaderBufferSize - ToCopy;

              }

              //then we copy the Lookahead buffer

              if (Open->Size-LocalData->NewP >= LookaheadBufferSize)

              {

                   //1 copy!

                   NdisMoveMappedMemory(LocalData->Buffer + LocalData->NewP, LookaheadBuffer, LookaheadBufferSize);

                   LocalData->NewP += LookaheadBufferSize;

                   if (LocalData->NewP == Open->Size)

                       LocalData->NewP = 0;

              }

              else

              {

                   ToCopy = Open->Size - LocalData->NewP;

                   NdisMoveMappedMemory(LocalData->Buffer + LocalData->NewP, LookaheadBuffer, ToCopy);

                   NdisMoveMappedMemory(LocalData->Buffer + 0, (PUCHAR)LookaheadBuffer + ToCopy, LookaheadBufferSize -ToCopy);

                   LocalData->NewP = LookaheadBufferSize - ToCopy;

              }

 

              //Now we must prepare the buffer(s) for the NdisTransferData

              if ((Open->Size - LocalData->NewP) >= (fres - HeaderBufferSize - LookaheadBufferSize))

              {

                   //only 1 buffer

                   pMdl1 = IoAllocateMdl(

                       LocalData->Buffer + LocalData->NewP,

                       fres - HeaderBufferSize - LookaheadBufferSize,

                       FALSE,

                       FALSE,

                       NULL);

                   if (pMdl1 == NULL)

                   {

                       IF_LOUD(DbgPrint("Error allocating Mdl1/n");)

                            LocalData->Dropped++;

                       break;

                   }

                   MmBuildMdlForNonPagedPool(pMdl1);

                   pMdl2=NULL;

                   LocalData->NewP += fres - HeaderBufferSize - LookaheadBufferSize;

              }

              else

              {

                   //2 buffers

                   pMdl1 = IoAllocateMdl(

                       LocalData->Buffer + LocalData->NewP,

                       Open->Size - LocalData->NewP,

                       FALSE,

                       FALSE,

                       NULL);

                   if (pMdl1 == NULL)

                   {

                       IF_LOUD(DbgPrint("Error allocating Mdl1/n");)

                            LocalData->Dropped++;

                       break;

                   }

                   pMdl2 = IoAllocateMdl(

                       LocalData->Buffer + 0,

                       fres - HeaderBufferSize - LookaheadBufferSize - (Open->Size - LocalData->NewP),

                       FALSE,

                       FALSE,

                       NULL);

                   if (pMdl2 == NULL)

                   {

                       IF_LOUD(DbgPrint("Error allocating Mdl2/n");)

                            IoFreeMdl(pMdl1);

                       LocalData->Dropped++;

                       break;

                   }

                   LocalData->NewP = fres - HeaderBufferSize - LookaheadBufferSize - (Open->Size - LocalData->NewP);

 

                   MmBuildMdlForNonPagedPool(pMdl1);

                   MmBuildMdlForNonPagedPool(pMdl2);

              }

              NdisAllocatePacket(&Status, &pPacket, Open->PacketPool);

              if (Status != NDIS_STATUS_SUCCESS)

              {

                   IF_LOUD(DbgPrint("NPF: Tap - No free packets/n");)

                       IoFreeMdl(pMdl1);

                   if (pMdl2 != NULL)

                       IoFreeMdl(pMdl2);

                   LocalData->Dropped++;

                   break;

              }

              if (pMdl2 != NULL)

                   NdisChainBufferAtFront(pPacket,pMdl2);

              NdisChainBufferAtFront(pPacket,pMdl1);

              RESERVED(pPacket)->Cpu = Cpu;

              LocalData->TransferMdl1 = pMdl1;

              LocalData->TransferMdl2 = pMdl2;

              Header = (struct PacketHeader*)(LocalData->Buffer + LocalData->P);

              Header->header.bh_caplen = fres;

              Header->header.bh_datalen = PacketSize + HeaderBufferSize;

              Header->header.bh_hdrlen=sizeof(struct bpf_hdr);

     /*调用NdisTransferData重新获取剩余的所需数据包,从网卡的Nic memory copy to kernel buffer。*/

              NdisTransferData(

                   &Status,

                   Open->AdapterHandle,

                   MacReceiveContext,

                   LookaheadBufferSize,

                   fres - HeaderBufferSize - LookaheadBufferSize,

                   pPacket,

                   &BytesTransfered);

              if (Status != NDIS_STATUS_PENDING)

              {

                   IF_LOUD(DbgPrint("NdisTransferData, not pending!/n");) 

                       LocalData->TransferMdl1 = NULL;

                   LocalData->TransferMdl2 = NULL;

                   IoFreeMdl(pMdl1);

                   if ( pMdl2 != NULL )

                       IoFreeMdl(pMdl2);

                   NdisReinitializePacket(pPacket);

                   // Put the packet on the free queue

                   NdisFreePacket(pPacket);

                   LocalData->P = LocalData->NewP;

                   LocalData->Accepted++;

                   GET_TIME(&Header->header.bh_tstamp,&G_Start_Time);

                   Header->SN = InterlockedIncrement(&Open->WriterSN) - 1;

                   increment = fres + sizeof(struct PacketHeader);

                   if (Open->Size - LocalData->P < sizeof(struct PacketHeader))

                   {

                       increment += Open->Size-LocalData->P;

                       LocalData->P = 0;

                   }

                   InterlockedExchangeAdd(&LocalData->Free, (ULONG)(-(LONG)increment));

                   if(Open->Size - LocalData->Free >= Open->MinToCopy)

                   {

                       if(Open->mode & MODE_DUMP)

                            NdisSetEvent(&Open->DumpEvent);

                       else

                       {

                            if (Open->ReadEvent != NULL)

                            {

                                 KeSetEvent(Open->ReadEvent,0,FALSE);

                            }

                       }

                   }

                   break;

              }

              else

              {

                   IF_LOUD(DbgPrint("NdisTransferData, pending!/n");)

                       ShouldReleaseBufferLock = FALSE;

              }

         }

     }

     while(FALSE);

     if (ShouldReleaseBufferLock)

     {

         NdisDprReleaseSpinLock(&LocalData->BufferLock);

     }

     return NDIS_STATUS_NOT_ACCEPTED;

}

 

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

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的源码了。PacketSetBuff在parket32.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,WriteFile,ReadFile函数,一般设置命令使用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].P和CpuData[i].C在读取数据包是非常有用的,他可以用来判断内核缓冲区的数据是否大于pcap_setmintocopy的最小copy的size。这个函数我会在后面的讲道。讲道这里,我们知道pcap_setbuff是怎么设置内核缓冲区的了,主要是调用DeviceIoControl将用户要设置的size传递到内核,而在内核中将它保存一个全局变量中,这样就设置好了内核缓冲区。

   

(2)如何设置用户缓冲区的大小?下面讲解怎样设置用户缓冲区的大小,linux下面的libcap是没有提供设置用户缓冲区大小(user buffer)的api的,要设置用户缓冲区,必须修改libcap的源码,但是winpcap的高版本是提供了设置用户缓冲区的函数,在wpcap.dll的win32-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,对应驱动中的源码如下,每个cpu的MintoCopy为(*((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源码彻底解密(五)

 

有人问我,wpcap.dll,packet.dll和npf.sys是怎么关联起来的,即通过调用wpcap.dll的api,怎么调用到驱动中的函数的呢?

 今天我就在这里讲讲应用程序和驱动之间的通信,windows提供以下win32 api函数和驱动通信的。

Win32 API

对文件操作

对设备操作

CreateFile

打开或创建文件操作

打开或创建设备

CloseHandle

关闭文件

关闭设备

ReadFile

读文件

读设备

WriteFile

写文件

写设备

CancelIo

取消读写文件操作

取消读写设备操作

DeviceIoControl

对设备进行特殊操作

 

 

 

以 CreateFile api为例子进行讲解,应用程序调用CreateFile Api --->win 32 子系统(kernel32.dll)调用NtCreateFile Native Api(ntdll.dll)----->调用NtCreateFile系统服务进入内核模式---->通过I/O管理器创建并发送IRP-->设备驱动--->硬件

 

NtCreateFile函数的作用是穿越用户模式的边界,进入到内核模式,这是通过软中断实现的,进入到内核模式后,会调用系统的服务函数,调用同名的系统服务NtCreateFile,NtCreateFile系统函数通过调用I/O管理器,创建IRP并传输到设备驱动程序中。

 

驱动程序根据IRP,进行相应的操作。

 

好,将到这里,就可以回到winpcap源码了,通过前面的几节的学习,大家应该对winpcap源码有一定的了解了,对于wpcap.dll和packet.dll ,就不讲解了。这里主要针对这个问题,稍微讲解下:

 

在npf的packet.c文件中有以下IRP

 

 /*设置IRP派遣函数和卸载例程*/
    DriverObject->MajorFunction[IRP_MJ_CREATE] = NPF_Open;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]  = NPF_Close;
  DriverObject->MajorFunction[IRP_MJ_CLEANUP]= NPF_Cleanup; 
    DriverObject->MajorFunction[IRP_MJ_READ]   = NPF_Read;
    DriverObject->MajorFunction[IRP_MJ_WRITE]  = NPF_Write;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]  = NPF_IoControl;
    DriverObject->DriverUnload = NPF_Unload;

在上面的章节中已经讲到对于应用程序都是通过ReadFile,WriteFile,DeviceIoControl与驱动通信的,比如设置内核缓冲区,用户缓冲区最终都是调用的DeviceIoControl,而DeviceIoControl对应的IRP就是IRP_MJ_DEVICE_CONTROL,这样就调用到了NPF_IoControl,这条路就通了,同样对于发包函数pcap_sendpacket最终也是调用的WriteFile将包发送出去,这里就可以看到和NPF_Write联系起来了,同理对于读函数,大家肯定都知道是怎么回事了,嘻嘻,好了,讲解完毕,希望对你有用。