1. 程式人生 > >庖丁解牛-----winpcap原始碼徹底解密(四)

庖丁解牛-----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(), 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的原始碼了。PacketSetBuffparket32.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,WriteFileReadFile函式,一般設定命令使用DeviceIoControl,傳送資料包使用WriteFile,但是你要使用DeviceIoControl傳送資料包也是可以的,讀取驅動中的資料包就使用ReadFile了。設定核心緩衝區的控制碼為:BIOCSETBUFFERSIZE,在NTSTATUS NPF_IoControl(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)函式可以看到不同的控制碼的處理方式。NPF_IoControl的原始碼在前面已經說了,這裡主要看看核心緩衝區在驅動是怎麼設定的。設定核心緩衝區的原始碼主要如下:

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

{

if (dim > 0)

Open->CpuData[i].Buffer=(PUCHAR)tpointer + (dim/g_NCpu)*i;

else

Open->CpuData[i].Buffer = NULL;

Open->CpuData[i].Free = dim/g_NCpu;

Open->CpuData[i].P = 0;//生產者

Open->CpuData[i].C = 0;//消費者

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

Open->CpuData[i].Dropped = 0;

Open->CpuData[i].Received = 0;

}

Open->ReaderSN=0;

Open->WriterSN=0;

Open->Size = dim/g_NCpu;

從上面的原始碼可以看出,winpcap的高明之處在於,它充分的使用了每個cpu,這樣的話,你的cpu有幾個核,效能就可以明顯的體現出來。每個cpu的緩衝區設定為dim/g_NCpu,其中Open是一個全域性變數,儲存的是一些和繫結網絡卡相關的資訊。CpuData[i].PCpuData[i].C在讀取資料包是非常有用的,他可以用來判斷核心緩衝區的資料是否大於pcap_setmintocopy的最小copysize。這個函式我會在後面的講道。講道這裡,我們知道pcap_setbuff是怎麼設定核心緩衝區的了,主要是呼叫DeviceIoControl將使用者要設定的size傳遞到核心,而在核心中將它儲存一個全域性變數中,這樣就設定好了核心緩衝區。

(2)如何設定使用者緩衝區的大小?下面講解怎樣設定使用者緩衝區的大小,linux下面的libcap是沒有提供設定使用者緩衝區大小(user buffer)api,要設定使用者緩衝區,必須修改libcap的原始碼,但是winpcap的高版本是提供了設定使用者緩衝區的函式,在wpcap.dllwin32-Extensions.c檔案中有一個pcap_setuserbuffer函式,在使用者使用時必須新增win32-Extensions.h標頭檔案。pcap_setuserbuffer函式原始碼如下:

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,對應驅動中的原始碼如下,每個cpuMintoCopy(*((PULONG)Irp->AssociatedIrp.SystemBuffer))/g_NCpu; 其中SystemBuffer的大小為應用程式傳遞過來的緩衝區size

case BIOCSMINTOCOPY: //set the minimum buffer's size to copy to the application

TRACE_MESSAGE(PACKET_DEBUG_LOUD, "BIOCSMINTOCOPY");

if(IrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(ULONG))

{

SET_FAILURE_BUFFER_SMALL();

break;

}

//An hack to make the NCPU-buffers behave like a larger one

Open->MinToCopy = (*((PULONG)Irp->AssociatedIrp.SystemBuffer))/g_NCpu;

SET_RESULT_SUCCESS(0);

break;

其中Open->MinToCopy為開啟上下文的全域性變數,該變數在讀資料包的時候會使用,用來判斷核心緩