天津恒隆地产有限公司:庖丁解牛-----winpcap源码彻底解密
来源:百度文库 编辑:中财网 时间:2024/05/04 06:32:15
最近忙一个项目,需要使用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(struct bpf_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(struct bpf_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。
查找到网卡后,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;
}
上一篇讲了怎么发送数据包,这次接着讲怎么接收数据包,数据包过滤后,就被复制到内核缓冲区(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 { 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 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 { 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=(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 { 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 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 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; } (1) 如何设置内核缓冲区的大小,前面已经谈过设置内核缓冲区的函数是pcap_setbuff,查看winpcap的开发文档,pcap_setbuff的定义如下: int pcap_setbuff(pcap_t *p,int dim); Set the size of the kernel buffer associated with an adapter. dim specifies the size of the buffer in bytes. The return value is 0 when the call succeeds, -1 otherwise. If an old buffer was already created with a previous call to pcap_setbuff(), it is deleted and its content is discarded. pcap_open_live() creates a 1 MByte buffer by default. 下面主要讲解pcap_setbuff是怎样设置内核缓冲区的,在wpcap.dll中的pcap.c文件中定义了pcap_setbuff函数,定义如下: int pcap_setbuff(pcap_t *p, int dim) { return p->setbuff_op(p, dim); } 其中setbuff_op是一个回调函数,其实调用的是pcap_setbuff_win32,在pcap-win32.c中定义 p->setbuff_op = pcap_setbuff_win32;下面看看这个函数是怎么设置内核缓冲区的。 static int pcap_setbuff_win32(pcap_t *p, int dim) { #ifdef HAVE_REMOTE if (p->rmt_clientside) { /* Currently, this is a bug: the capture buffer cannot be set with remote capture */ return 0; } #endif /* HAVE_REMOTE */ if(PacketSetBuff(p->adapter,dim)==FALSE) { snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "driver error: not enough memory to allocate the kernel buffer"); return -1; } return 0; } 从pcap_setbuff_win32的源码可以出,它是调用PacketSetBuff将应用程序对应的网卡的内核缓冲区的大小dim传递到了parket.dll下一层。如果要相知道PacketSetBuff怎么讲内核缓冲区的大小传递到驱动程序npf.sys中,就要取跟踪PacketSetBuff的源码了。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 Occupation += (Open->Size - Open->CpuData[i].Free); //计算出已经占用的内核缓冲区 if( Occupation <= Open->MinToCopy*g_NCpu || Open->mode & MODE_DUMP ) 有人问我,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派遣函数和卸载例程*/ 在上面的章节中已经讲到对于应用程序都是通过ReadFile,WriteFile,DeviceIoControl与驱动通信的,比如设置内核缓冲区,用户缓冲区最终都是调用的DeviceIoControl,而DeviceIoControl对应的IRP就是IRP_MJ_DEVICE_CONTROL,这样就调用到了NPF_IoControl,这条路就通了,同样对于发包函数pcap_sendpacket最终也是调用的WriteFile将包发送出去,这里就可以看到和NPF_Write联系起来了,同理对于读函数,大家肯定都知道是怎么回事了,嘻嘻,好了,讲解完毕,希望对你有用。
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;