庖丁解牛-----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抓取网络数据包。数据包从网卡到应用程序的过程如下:
图1:linux下数据包捕获流程
图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联系起来了,同理对于读函数,大家肯定都知道是怎么回事了,嘻嘻,好了,讲解完毕,希望对你有用。