庖丁解牛-----winpcap原始碼徹底解密(四)
庖丁解牛-----winpcap原始碼徹底解密(四)
版權申明: 原創文章,轉貼請註明出處!!!!!!!!
(1)如何設定核心緩衝區的大小,前面已經談過設定核心緩衝區的函式是pcap_setbuff,檢視winpcap的開發文件,pcap_setbuff的定義如下:
int pcap_setbuff(pcap_t *p,int dim);
Set the size of the kernel buffer associated with an adapter.
dim specifies the size of the buffer in bytes. The return value is 0 when the call succeeds, -1 otherwise. If an old buffer was already created with a previous call to
下面主要講解pcap_setbuff是怎樣設定核心緩衝區的,在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
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
{
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為開啟上下文的全域性變數,該變數在讀資料包的時候會使用,用來判斷核心緩