?? 網(wǎng)絡(luò)驅(qū)動(dòng)教程.txt
字號(hào):
對(duì)于這個(gè)設(shè)備,應(yīng)用程序可以打開、讀、寫、發(fā)出控制命令、關(guān)閉。
--------------示例3-----------------
// 打開
HANDLE Handle=CreateFile("\\.\MyNdisDevice",
GENERIC_WRITE|GENERIC_READ,
FILE_SHARE_WRITE|FILE_SHARE_READ,
NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,NULL);
if(Handle==INVALID_HANDLE_VALUE)
{}// 失敗了
else
{}// 成功了
// 寫
if(WriteFile(Handle,buf,len,&dlen,NULL))
{}// 成功了
else
{}//失敗了
其他的函數(shù)調(diào)用涉及CloseHandle(),DeviceIoControl(),ReadFile(),使用參數(shù)請(qǐng)閱讀MSDN,與讀寫文件并無不同之處。主要要注意的是CreateFile的參數(shù),建議您直接用上邊的示例。我不太清楚每個(gè)參數(shù)的準(zhǔn)確含義,但是我用上邊的參數(shù)總是可以成功。
我不知道有沒有更好的辦法,我見過的驅(qū)動(dòng)主動(dòng)通知應(yīng)用程序的辦法是應(yīng)用程序用一個(gè)線程來讀驅(qū)動(dòng)。驅(qū)動(dòng)把要通知應(yīng)用程序的東西讓應(yīng)用程序讀出。這需要一個(gè)無盡循環(huán)的循環(huán)來讀這個(gè)驅(qū)動(dòng)。當(dāng)無數(shù)據(jù)可讀的時(shí)候可以阻塞。當(dāng)驅(qū)動(dòng)想通知應(yīng)用(如丟出一筆日志)的時(shí)候,寫一些東西讓應(yīng)用程序的ReadFile返回即可。
為了處理io請(qǐng)求我們現(xiàn)在來寫那個(gè)IoDispatch.這個(gè)函數(shù)在WDM驅(qū)動(dòng)中一般稱為分發(fā)例程。這個(gè)函數(shù)在Passive Level運(yùn)行,因此非常安全,幾乎可以調(diào)用絕大部分的系統(tǒng)內(nèi)核服務(wù)。
請(qǐng)結(jié)合下邊的例子,可以看到最簡(jiǎn)單的是IRP_MJ_CREATE和IRP_MJ_CLOSE調(diào)用。如果返回STATUS_SUCCESS則表示成功,STATUS_UNSUCCESSFULE則表示失敗。最簡(jiǎn)單的方法是只讓一個(gè)進(jìn)程打開自己,你可以設(shè)想一下應(yīng)該怎樣實(shí)現(xiàn),并作為一個(gè)小練習(xí)。
這里設(shè)備采用緩沖模式,這是最為簡(jiǎn)單的一種方式,其他方式我們不討論。為了設(shè)置為緩沖模式,我們回到示例代碼5.1,在注冊(cè)設(shè)備之后,加上這句:
m_pDeviceObject->Flags |= DO_BUFFERED_IO;
ReadFile的處理主要是輸出數(shù)據(jù)。用戶提供輸出緩沖及其長(zhǎng)度。你寫入數(shù)據(jù),并說明你寫入的長(zhǎng)度。在IrpStack->Parameters.Read.Length中得到輸出緩沖長(zhǎng)度。數(shù)據(jù)寫入Irp->AssociatedIrp.SystemBuffer中。實(shí)際輸出數(shù)據(jù)長(zhǎng)度請(qǐng)寫到Irp->IoStatus.Information中即可。
WriteFile的處理與ReadFile類似。不用的是Irp->AssociatedIrp.SystemBuffer成了輸入緩沖,而長(zhǎng)度在IrpStack->Parameters.Write.Length中。
DeviceIoControl的情況稍微復(fù)雜,一般先要得到一個(gè)功能碼,用戶程序一般要輸入數(shù)據(jù)(在輸入緩沖中),同時(shí)要獲得輸出(請(qǐng)你寫入輸出緩沖中),并指明了這些緩沖區(qū)的長(zhǎng)度。你還必須指明你輸出數(shù)據(jù)的真實(shí)長(zhǎng)度。
功能碼在IrpStack->Parameters.DeviceIoControl.IoControlCode;
緩沖模式,輸入緩沖長(zhǎng)度為IrpStack->Parameters.DeviceIoControl.InputBufferLength;
緩沖模式,輸出緩沖長(zhǎng)度為IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
共用緩沖區(qū)為Irp->AssociatedIrp.SystemBuffer;
實(shí)際輸出數(shù)據(jù)長(zhǎng)度請(qǐng)寫到Irp->IoStatus.Information中。
用下邊的方法返回失敗最為完整:注意指定了為參數(shù)錯(cuò)誤。你也可以指定其他錯(cuò)誤。請(qǐng)查閱ddk幫助。
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_INVALID_PARAMETER;
用下邊的方法返回成功:
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
//------------示例4--------------
NTSTATUS IoDispatch(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
BOOLEAN ret = FALSE;
KIrp I(Irp);
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
switch(I.MajorFunction())
{
case IRP_MJ_CREATE:
// 如果有進(jìn)程調(diào)用了CreateFile
ret = TRUE;
break;
case IRP_MJ_CLOSE:
// 如果有進(jìn)程調(diào)用了CloseFile
ret = TRUE;
break;
case IRP_MJ_CLEANUP:
ret = TRUE;
break;
case IRP_MJ_READ:
// 有進(jìn)程調(diào)用了ReadFile()
ret = FALSE;
break;
case IRP_MJ_WRITE:
// 如果調(diào)用了WriteFile()
ret = FALSE;
break;
case IRP_MJ_DEVICE_CONTROL:
ret = FALSE;
break;
default:
ret = FALSE;
};
if(ret)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
else
{
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_INVALID_PARAMETER;
}
}
好,希望我已經(jīng)說明白了驅(qū)動(dòng)與應(yīng)用之間如何交互。如果你還不是很理解,請(qǐng)留意下一章的實(shí)際例子。我們將做一個(gè)簡(jiǎn)單的中間層驅(qū)動(dòng),您可以通過應(yīng)用程序來配置這個(gè)驅(qū)動(dòng)的一些行為(如允許所有的包通過,或是禁止所有包通過)。
第六課 一個(gè)實(shí)際的中間層驅(qū)動(dòng)
第O課中我們已經(jīng)建立了一個(gè)框架,現(xiàn)在在瀏覽幾個(gè)重要的函數(shù)。最重要的是兩個(gè)OnReceive和一個(gè)OnSend.
先看框架生成的第一個(gè)OnRecevie.注意我的工程名為My.
//---------------示例6.1--------------------
NDIS_STATUS MyAdapter::OnReceive
(const KNdisPacket& Original, KNdisPacket& Repackaged)
{
TRACE("MyAdapter::OnReceive() %u bytesn", Original.QueryTotalLength());
HEADER* Content = (HEADER*) Original.QueryFirstBuffer();
Repackaged.CloneUp(Original);
return NDIS_STATUS_SUCCESS;
}
這個(gè)函數(shù)似乎比較好理解。當(dāng)計(jì)算機(jī)接受到網(wǎng)絡(luò)數(shù)據(jù)包的時(shí)候,我們的OnReceive被調(diào)用。Original是就是數(shù)據(jù)包。請(qǐng)結(jié)合第一課中的內(nèi)容。KNdisPacket是一個(gè)網(wǎng)絡(luò)數(shù)據(jù)包,但是其實(shí)際內(nèi)容放在KNdisBuffer中。一個(gè)KNdisPacket擁有一個(gè)KNdisBuffer鏈。
KNdisBuffer可以直接轉(zhuǎn)成任何地址來使用,也可以用KNdisBuffer::Address()來得到地址。
那么如上邊的示例,Original.QueryFirstBuffer()得到數(shù)據(jù)包頭。
注意,第一個(gè)緩沖區(qū)的長(zhǎng)度至少為14個(gè)字節(jié)(其實(shí)我并不這么肯定,但是我每次都恰好至少得到了14個(gè)字節(jié)),但是不要指望第一個(gè)KNdisBuffer就直接幫你搞定后邊的ip頭,tcp頭數(shù)據(jù)。一個(gè)鏈?zhǔn)降慕Y(jié)構(gòu)非常的不好處理,所以我建議費(fèi)點(diǎn)力氣把頭的部分復(fù)制到一個(gè)連續(xù)緩沖區(qū)中。一般都處理到tcp頭即可,也不過14+20+20才54個(gè)字節(jié)。拷貝起來是不費(fèi)多少資源的。
假設(shè)有一個(gè)KNdisPacket Packet,你可以用下邊的例子把前54個(gè)字節(jié)的數(shù)據(jù)拷貝到MyAheadBuf中。
//----------------示例6.2---------------------
UINT nBufCnt = Packet.QueryBufferCount();
KNdisBuffer Buf = Packet.QueryFirstBuffer();
if (!Buf.IsValid())
return;
unsigned char MyAheadBuf[14+20+20];
unsigned long MyAheadBufLen = 0,WantLen = 14+20+20;
// 拷貝足夠的長(zhǎng)度
while(WantLen > MyAheadBufLen)
{
if(WantLen - MyAheadBufLen > Buf.Length())
{
memcpy(&MyAheadBuf[MyAheadBufLen],Buf.Address(),Buf.Length());
MyAheadBufLen += Buf.Length();
Buf = Buf.GetNext();
if(!Buf.IsValid())
break;
}
else
{
memcpy(&MyAheadBuf[MyAheadBufLen],Buf.Address(),WantLen-MyAheadBufLen);
MyAheadBufLen = WantLen;
}
}
不幸的是并不是所有的計(jì)算機(jī)上都只調(diào)用上一個(gè)OnRecevie.另一個(gè)可能被調(diào)用的OnReceive函數(shù)如下。這可能和下層的微端口驅(qū)動(dòng)的特性有關(guān)。下邊這個(gè)函數(shù)HeaderBuffer是以太網(wǎng)包頭,長(zhǎng)度一般為14.LookAheadBuffer是以太網(wǎng)包頭之后的部分,如果這是個(gè)ip包,那就是ip頭了。我們能否驅(qū)動(dòng)到我們關(guān)心的ip頭和tcp頭?這需要LookAheadBufferLength至少為40.我認(rèn)為L(zhǎng)ookAheadBufferLength長(zhǎng)度至少為40.因?yàn)槲宜坪踹€沒有發(fā)現(xiàn)過少于40的情況。
如果不是,請(qǐng)發(fā)郵件給我MFC_Tan_Wen@163.com,非常感謝。
這導(dǎo)致我們可以直接用LookAheadBuffer來得到足夠的信息做大多數(shù)的工作。但是并不總是這樣的。比如我要對(duì)整個(gè)數(shù)據(jù)包加密,我有必要得到整個(gè)數(shù)據(jù)包。但是LookAheadBuffer可能小于PacketSize.這種情況,你必須調(diào)用TransferData()函數(shù),并在MyAdpater::OnTransferComplete()中處理這個(gè)數(shù)據(jù)包。但是OnTransferComplete()這種情況恰好和上一種OnReceive()的情況類似,所以這里不再詳述了。
//------------------示例6.3---------------------------
NDIS_STATUS MyAdapter::OnReceive(
IN OUT KNdisPartialPacket& PacketToAccept,
IN PVOID HeaderBuffer, IN UINT HeaderBufferSize,
IN PVOID LookAheadBuffer, IN UINT LookaheadBufferSize,
IN UINT PacketSize)
{
TRACE("MyAdapter::OnReceive() Partial %u/%u%/%u bytesn",
HeaderBufferSize, LookaheadBufferSize, PacketSize);
UNREFERENCED_PARAMETER(PacketToAccept);
return NDIS_STATUS_SUCCESS;
}
OnSend()在有數(shù)據(jù)包發(fā)出的情況下被調(diào)用,處理方法應(yīng)該與第一種OnReceive的方法相同。
如果我想阻止數(shù)據(jù)包的接收或者發(fā)送,我直接return NNDIS_STATUS_NOT_ACCEPTED即可。并且我不向上或者向下復(fù)制數(shù)據(jù)包。
請(qǐng)定義一個(gè)全局的變量BOOLEAN Allow.并假定Allow如果為TRUE,我則不干涉所有數(shù)據(jù)包的發(fā)送接受。而如果為FALSE,我丟棄接收到的所有數(shù)據(jù)包,并阻止所有的發(fā)送包。
回到示例6.1,內(nèi)容應(yīng)該改為
TRACE("MyAdapter::OnReceive() %u bytesn", Original.QueryTotalLength());
if(Allow)
{
HEADER* Content = (HEADER*) Original.QueryFirstBuffer();
Repackaged.CloneUp(Original);
return NDIS_STATUS_SUCCESS;
}
else
return NDIS_STATUS_NOT_ACCEPTED;
其他的兩個(gè)函數(shù)請(qǐng)自己修改。
現(xiàn)在的問題是我必須用應(yīng)用程序來設(shè)置這個(gè)變量。以便控制我的網(wǎng)絡(luò)是否連通。這樣的一個(gè)防火墻只有兩個(gè)選項(xiàng),全開或者全關(guān),當(dāng)然這是一個(gè)無實(shí)際用處的防火墻。
回到上一章節(jié)的敘述,我們?cè)谶@個(gè)驅(qū)動(dòng)中加入注冊(cè)一個(gè)WDM設(shè)備。但是僅僅做了上邊的事情無法通過編譯。DriverNetworks的幫助指出,要注冊(cè)WDM設(shè)備必須在向?qū)е羞x中NDIS WDM選項(xiàng)。但是實(shí)際上只有微端口驅(qū)動(dòng)才有這個(gè)選項(xiàng)。因此必須用我下邊所說的方法:
首先包含頭文件<kndisvdw.h>。然后編譯預(yù)定義宏定義增加這幾個(gè):NTVERSION='WDM',NDIS_WDM=1.此時(shí)編譯ok,但是連接未必能搞定。
為此建議建立一個(gè)微端口驅(qū)動(dòng),選中wdm,編譯過程中會(huì)跳出來要你編譯很多l(xiāng)ib,這時(shí)全部編譯之即可。
回頭來,您的中間層驅(qū)動(dòng)已經(jīng)成功的加入了WDM設(shè)備。
現(xiàn)在考慮如何用WDM設(shè)備來控制開與斷。我設(shè)計(jì)一個(gè)DeviceIoCtrl來做這個(gè)事情:
定義:
#define IOCTL_SET_NET_OPEN_OR_CLOSE CTL_CODE(
FILE_DEVICE_UNKNOWN,
0x802,
METHOD_BUFFERED,
FILE_ANY_ACCESS)
這個(gè)指令帶一個(gè)字節(jié)的參數(shù)保存在輸入緩沖中。如果為0則全斷,反之全開。
然后修改IoDispatch函數(shù),加入以下的處理:
...
ULONG ControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
ULONG InputLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
ULONG OutputLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
switch(ControlCode)
{
case IOCTL_SET_NET_OPEN_OR_CLOSE:
{
// 主要的任務(wù)是首先檢查長(zhǎng)度是否足夠
if( 1 > OutputLength)
{
ret = FALSE;
break;;
}
// 得到輸入?yún)?shù)并設(shè)置自己全開全閉
UCHAR cAllow = *((UCHAR *)Irp->AssociatedIrp.SystemBuffer);
Allow = (cAllow==0)?FALSE:TRUE;
DWW_RULE_S *pDst = (DWW_RULE_S *)Buffer;
Irp->IoStatus.Information
ret = TRUE;
break;
}
...
}
至于應(yīng)用程序如何調(diào)用DeviceIoControl,請(qǐng)參照上一課的例子。
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -