典型的基於tcpip協議套接字方式的網絡通訊模塊層次:ios
應用程序windows
socket apiapi
WS2_32.dll數組
socket irp網絡
Afd.sysapp
tdi irp框架
Tcpip.sys異步
回調函數接口socket
各Ndis中間層過濾驅動tcp
回調函數接口
小端口驅動
中斷交互操做
網卡
應用程序調用WS2_32.dll中的socket api,socket api在內部生成socket irp發給afd.sys這個中間輔助驅動層,afd.sys將socket irp轉換成tdi irp發給tcpip協議驅動,協議驅動經過註冊的回調函數與小端口驅動(中間可能穿插N箇中間層過濾驅動),小端口驅動最終經過中斷與網卡交互,操做硬件。
其中,協議驅動、中間層驅動、小端口驅動三者之間的交互是經過ndis.sys這個庫函數模塊實現的,或者說ndis.sys提供了ndis框架,協議驅動、中間層驅動、小端口驅動三者都得遵循這個框架。
爲何網絡通訊須要這麼複雜的分層?答案是爲了減輕開發維護管理工做的須要,分層可以提供最大的靈活性。各層的設計人員只需專一自身模塊的設計工做,無需擔憂其餘模塊是怎麼實現的,只需保持接口一致便可。
如應用程序能夠調用socket api就能夠實現網絡通訊,而無論底層是如何實現的。使用socket api還可使得windows上能兼容運行Unix系統上的網絡通訊程序,ws2_32.dll這個模塊中實現了socket接口。
Afd.sys其實是一個適配層,他能夠適配N種協議驅動。
Tcpip.sys是一種協議驅動(實際上是一個協議棧驅動),它內部實現了一套協議棧,決定了如何解析從網卡接收到的包,以及以什麼格式將應用程序數據發到網卡。只不過tcpip.sys將收到的包按鏈路層、網絡層、傳輸層分層三層逐層解析。事實上咱們能夠徹底能夠自定義、自編寫一個協議驅動,按照咱們本身的協議來發包、收包(咱們的這個自定義協議驅動能夠採用分層機制,也能夠採用簡單的單層機制),這樣在發送方電腦和接收方電腦都安裝咱們的自定義協議驅動後,發送方就能夠按照自定義協議發包,接收方就按照約定的格式解包。
若是不考慮中間驅動,協議驅動是直接與小端口驅動交互的。協議驅動從小端口驅動收包,協議驅動發包給小端口驅動,這就是兩者之間的交互。他們之間的交互經過ndis框架預定的一套回調函數接口來實現。
下面咱們看各層驅動的實現:
一個協議驅動須要在DriverEntry中將本身註冊爲一個協議驅動,向ndis框架登記、聲明本身的協議特徵。
一個協議特徵記錄了協議的名稱以及它提供的各個回調函數
4.0版本的ndis協議特徵結構以下定義:
typedef struct _NDIS40_PROTOCOL_CHARACTERISTICS
{
UCHAR MajorNdisVersion;
UCHAR MinorNdisVersion;
__MINGW_EXTENSION union {
UINT Reserved;
UINT Flags;
};
OPEN_ADAPTER_COMPLETE_HANDLER OpenAdapterCompleteHandler;//綁定完成回調函數
CLOSE_ADAPTER_COMPLETE_HANDLER CloseAdapterCompleteHandler;//解除綁定完成回調函數
SEND_COMPLETE_HANDLER SendCompleteHandler;//發送完成回調函數
TRANSFER_DATA_COMPLETE_HANDLER TransferDataCompleteHandler;//轉移數據完成回調函數
RESET_COMPLETE_HANDLER ResetCompleteHandler;
REQUEST_COMPLETE_HANDLER RequestCompleteHandler;//ndis請求完成回調函數
RECEIVE_HANDLER ReceiveHandler;//接收函數
RECEIVE_COMPLETE_HANDLER ReceiveCompleteHandler;//接收完成回調函數
STATUS_HANDLER StatusHandler;//狀態變換通知回調函數
STATUS_COMPLETE_HANDLER StatusCompleteHandler;//狀態變換完成通知回調函數
NDIS_STRING Name;//協議名
RECEIVE_PACKET_HANDLER ReceivePacketHandler;//接收包函數
BIND_HANDLER BindAdapterHandler;//綁定通知回調函數
UNBIND_HANDLER UnbindAdapterHandler;//解除綁定通知回調函數
PNP_EVENT_HANDLER PnPEventHandler;//Pnp事件回調函數
UNLOAD_PROTOCOL_HANDLER UnloadHandler;//協議驅動的卸載例程
} NDIS40_PROTOCOL_CHARACTERISTICS;
下面的函數用於將一個驅動註冊爲ndis協議驅動
VOID
NdisRegisterProtocol(
OUT PNDIS_STATUS Status,//返回狀態
OUT PNDIS_HANDLE NdisProtocolHandle,//返回註冊的協議驅動句柄
IN PNDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics,
IN UINT CharacteristicsLength)
{
PPROTOCOL_BINDING Protocol;
NTSTATUS NtStatus;
UINT MinSize;
PNET_PNP_EVENT PnPEvent;
*NdisProtocolHandle = NULL;
switch (ProtocolCharacteristics->MajorNdisVersion)
{
case 0x03:
MinSize = sizeof(NDIS30_PROTOCOL_CHARACTERISTICS);
break;
case 0x04:
MinSize = sizeof(NDIS40_PROTOCOL_CHARACTERISTICS);
break;
case 0x05:
MinSize = sizeof(NDIS50_PROTOCOL_CHARACTERISTICS);
break;
default:
*Status = NDIS_STATUS_BAD_VERSION;
return;
}
if (CharacteristicsLength < MinSize) //結構體的長度必須與聲明的ndis版本一致
{
*Status = NDIS_STATUS_BAD_CHARACTERISTICS;
return;
}
//協議驅動句柄其實是一個PROTOCOL_BINDING結構體指針
Protocol = ExAllocatePool(NonPagedPool, sizeof(PROTOCOL_BINDING));//一個協議驅動描述符
RtlZeroMemory(Protocol, sizeof(PROTOCOL_BINDING));
RtlCopyMemory(&Protocol->Chars, ProtocolCharacteristics, MinSize);//關鍵。記錄協議特徵
KeInitializeSpinLock(&Protocol->Lock);
InitializeListHead(&Protocol->AdapterListHead);//該協議驅動綁定的網卡列表初始爲空
*NdisProtocolHandle = Protocol;//返回協議驅動的句柄
ndisBindMiniportsToProtocol(Status, Protocol);//關鍵。剛一註冊就在此綁定全部現有網卡
PnPEvent = ProSetupPnPEvent(NetEventBindsComplete, NULL, 0);//構造一個全部綁定完成事件
if (PnPEvent)
{
if (Protocol->Chars.PnPEventHandler)
NtStatus = (*Protocol->Chars.PnPEventHandler)(NULL,PnPEvent);
}
if (*Status == NDIS_STATUS_SUCCESS)
{
ExInterlockedInsertTailList(&ProtocolListHead, &Protocol->ListEntry, &ProtocolListLock);//插入全局的協議驅動鏈表
}
}
上面最主要的工做即是登記協議特徵到驅動描述符中,而後附帶綁定現有的已有網卡。下面的函數就是用來綁定全部現有網卡的。
VOID ndisBindMiniportsToProtocol(OUT PNDIS_STATUS Status, IN PPROTOCOL_BINDING Protocol)
{
HANDLE DriverKeyHandle = NULL;
PKEY_VALUE_PARTIAL_INFORMATION KeyInformation = NULL;
PNDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics = &Protocol->Chars;
RegistryPathStr = ExAllocatePoolWithTag(PagedPool, sizeof(SERVICES_KEY) + ProtocolCharacteristics->Name.Length + sizeof(LINKAGE_KEY), NDIS_TAG + __LINE__);
wcscpy(RegistryPathStr, SERVICES_KEY);
wcsncat(RegistryPathStr, ((WCHAR *)ProtocolCharacteristics->Name.Buffer), ProtocolCharacteristics->Name.Length / sizeof(WCHAR));
RegistryPathStr[wcslen(SERVICES_KEY)+ProtocolCharacteristics->Name.Length/sizeof(WCHAR)] = NULL;
wcscat(RegistryPathStr, LINKAGE_KEY);
//通過上面的拼湊,RegistryPathStr最終拼成…\Services\協議名\Linkage
RtlInitUnicodeString(&RegistryPath, RegistryPathStr);
InitializeObjectAttributes(&ObjectAttributes, &RegistryPath, OBJ_CASE_INSENSITIVE, NULL, NULL);
NtStatus = ZwOpenKey(&DriverKeyHandle, KEY_READ, &ObjectAttributes);//打開Linkage鍵
ExFreePool(RegistryPathStr);
if(!NT_SUCCESS(NtStatus))
{
*Status = NDIS_STATUS_FAILURE;
return;
}
{
UNICODE_STRING ValueName;
ULONG ResultLength;
RtlInitUnicodeString(&ValueName, L"Bind");
NtStatus = ZwQueryValueKey(DriverKeyHandle, &ValueName, KeyValuePartialInformation, NULL, 0, &ResultLength);
KeyInformation = ExAllocatePoolWithTag(PagedPool, sizeof(KEY_VALUE_PARTIAL_INFORMATION) + ResultLength, NDIS_TAG + __LINE__);
//查詢Linkage鍵下的Bind值(多個網卡設備對象名稱組成的一條字符串)
NtStatus = ZwQueryValueKey(DriverKeyHandle, &ValueName, KeyValuePartialInformation, KeyInformation,sizeof(KEY_VALUE_PARTIAL_INFORMATION) + ResultLength, &ResultLength);
ZwClose(DriverKeyHandle);
}
*Status = NDIS_STATUS_SUCCESS;
//遍歷每一個網卡
for (DataPtr = (WCHAR *)KeyInformation->Data;
*DataPtr != 0; DataPtr += wcslen(DataPtr) + 1)
{
VOID *BindContext = NULL;
NDIS_STRING DeviceName;
NDIS_STRING RegistryPath;
WCHAR *RegistryPathStr = NULL;
ULONG PathLength = 0;
// DeviceName爲‘\Device\小端口設備對象名稱’形式
RtlInitUnicodeString(&DeviceName, DataPtr);
if (!MiniLocateDevice(&DeviceName))//if 那個網卡還沒有啓動
continue;
if (LocateAdapterBindingByName(Protocol, &DeviceName)) //if 本協議已綁定了那塊網卡
continue;
PathLength = sizeof(SERVICES_KEY) +
wcslen( DataPtr + 8 ) * sizeof(WCHAR) +
sizeof(PARAMETERS_KEY) +
ProtocolCharacteristics->Name.Length + sizeof(WCHAR);
RegistryPathStr = ExAllocatePool(PagedPool, PathLength);
wcscpy(RegistryPathStr, SERVICES_KEY);
wcscat(RegistryPathStr, DataPtr + 8 );
wcscat(RegistryPathStr, PARAMETERS_KEY);
wcsncat(RegistryPathStr, ProtocolCharacteristics->Name.Buffer, ProtocolCharacteristics->Name.Length / sizeof(WCHAR) );
RegistryPathStr[PathLength/sizeof(WCHAR) - 1] = 0;
RtlInitUnicodeString(&RegistryPath, RegistryPathStr);
//RegistryPath最終變成…\Services\小端口設備對象名 \Parameters\協議名 形式
{
BIND_HANDLER BindHandler = ProtocolCharacteristics->BindAdapterHandler;
if(BindHandler) //關鍵,通知協議驅動綁定網卡列表中的每塊網卡
BindHandler(Status, BindContext, &DeviceName, &RegistryPath, 0);
}
ExFreePool(KeyInformation);
}
一個驅動註冊爲協議驅動後,ndis內部會爲這個驅動建立一個協議驅動描述符,返回的句柄就是這個結構指針。Typedef PVOID NDIS_HANDLE,可見ndis句柄其實就是一個指針。
typedef struct _PROTOCOL_BINDING { //協議驅動描述符
LIST_ENTRY ListEntry; 用來掛入全局協議驅動鏈表
KSPIN_LOCK Lock;
NDIS_PROTOCOL_CHARACTERISTICS Chars; //關鍵。本協議驅動的特徵
WORK_QUEUE_ITEM WorkItem;
LIST_ENTRY AdapterListHead; //本協議驅動綁定的全部網卡
} PROTOCOL_BINDING, *PPROTOCOL_BINDING;
一樣:小端口驅動也須要在其DriverEntry中將本身註冊爲一個ndis小端口驅動。
Struct NDIS40_MINIPORT_CHARACTERISTICS //4.0版的小端口驅動特徵結構
{
UCHAR MajorNdisVersion;
UCHAR MinorNdisVersion;
UINT Reserved;
W_CHECK_FOR_HANG_HANDLER CheckForHangHandler;
W_DISABLE_INTERRUPT_HANDLER DisableInterruptHandler;//禁用來自特定網卡的中斷
W_ENABLE_INTERRUPT_HANDLER EnableInterruptHandler; //啓用來自特定網卡的中斷
W_HALT_HANDLER HaltHandler;
W_HANDLE_INTERRUPT_HANDLER HandleInterruptHandler;//isr的後半部
W_INITIALIZE_HANDLER InitializeHandler; //IRP_MN_START_DEVICE中調用的啓動初始化函數
W_ISR_HANDLER ISRHandler; //咱們的isr
W_QUERY_INFORMATION_HANDLER QueryInformationHandler;//處理查詢請求的函數
W_RECONFIGURE_HANDLER ReconfigureHandler;
W_RESET_HANDLER ResetHandler;
W_SEND_HANDLER SendHandler; //發送函數
W_SET_INFORMATION_HANDLER SetInformationHandler;//處理設置請求的函數
W_TRANSFER_DATA_HANDLER TransferDataHandler;//處理協議驅動發下來的轉移數據請求的函數
W_RETURN_PACKET_HANDLER ReturnPacketHandler; //歸還包函數
W_SEND_PACKETS_HANDLER SendPacketsHandler;//發送包函數
W_ALLOCATE_COMPLETE_HANDLER AllocateCompleteHandler;
}
typedef struct _NDIS_M_DRIVER_BLOCK //小端口驅動描述符、句柄
{
LIST_ENTRY ListEntry; //用來掛入全局小端口驅動鏈表
KSPIN_LOCK Lock;
NDIS_MINIPORT_CHARACTERISTICS MiniportCharacteristics; //特徵
WORK_QUEUE_ITEM WorkItem;
PDRIVER_OBJECT DriverObject; //小端口驅動對象
LIST_ENTRY DeviceList; //本驅動中建立的全部適配器設備
PUNICODE_STRING RegistryPath; //本驅動的服務鍵路徑
} NDIS_M_DRIVER_BLOCK, *PNDIS_M_DRIVER_BLOCK;
下面的函數用於將一個驅動註冊爲ndis小端口驅動
NDIS_STATUS
NdisMRegisterMiniport(
IN NDIS_HANDLE NdisWrapperHandle,//小端口驅動句柄
IN PNDIS_MINIPORT_CHARACTERISTICS MiniportCharacteristics,
IN UINT CharacteristicsLength)
{
UINT MinSize;
PNDIS_M_DRIVER_BLOCK Miniport = (PNDIS_M_DRIVER_BLOCK)NdisWrapperHandle;
PNDIS_M_DRIVER_BLOCK *MiniportPtr;
NTSTATUS Status;
switch (MiniportCharacteristics->MajorNdisVersion)
{
case 0x03:
MinSize = sizeof(NDIS30_MINIPORT_CHARACTERISTICS);
break;
case 0x04:
MinSize = sizeof(NDIS40_MINIPORT_CHARACTERISTICS);
break;
case 0x05:
MinSize = sizeof(NDIS50_MINIPORT_CHARACTERISTICS);
break;
default:
return NDIS_STATUS_BAD_VERSION;
}
if (CharacteristicsLength < MinSize)
return NDIS_STATUS_BAD_CHARACTERISTICS;
//這三個回調函數在任何ndis版本都必須提供
if ((!MiniportCharacteristics->HaltHandler) ||
(!MiniportCharacteristics->InitializeHandler)||
(!MiniportCharacteristics->ResetHandler))
{
return NDIS_STATUS_BAD_CHARACTERISTICS;
}
if (MiniportCharacteristics->MajorNdisVersion < 0x05)
{
if ((!MiniportCharacteristics->QueryInformationHandler) ||
(!MiniportCharacteristics->SetInformationHandler))
{
return NDIS_STATUS_BAD_CHARACTERISTICS;
}
}
else
{
if (((!MiniportCharacteristics->QueryInformationHandler) ||
(!MiniportCharacteristics->SetInformationHandler)) &&
(!MiniportCharacteristics->CoRequestHandler))
{
return NDIS_STATUS_BAD_CHARACTERISTICS;
}
}
if (MiniportCharacteristics->MajorNdisVersion == 0x03)
{
if (!MiniportCharacteristics->SendHandler)
return NDIS_STATUS_BAD_CHARACTERISTICS;
}
else if (MiniportCharacteristics->MajorNdisVersion == 0x04)
{
if ((!MiniportCharacteristics->SendHandler) &&
(!MiniportCharacteristics->SendPacketsHandler))
{
return NDIS_STATUS_BAD_CHARACTERISTICS;
}
}
else if (MiniportCharacteristics->MajorNdisVersion == 0x05)
{
if ((!MiniportCharacteristics->SendHandler) &&
(!MiniportCharacteristics->SendPacketsHandler) &&
(!MiniportCharacteristics->CoSendPacketsHandler))
{
return NDIS_STATUS_BAD_CHARACTERISTICS;
}
}
//關鍵。記錄該小端口驅動的特徵到驅動描述符中
RtlCopyMemory(&Miniport->MiniportCharacteristics, MiniportCharacteristics, MinSize);
Status = IoAllocateDriverObjectExtension(Miniport->DriverObject, 'NMID',
sizeof(PNDIS_M_DRIVER_BLOCK), &MiniportPtr);
*MiniportPtr = Miniport;//驅動擴展指向小端口驅動描述符
//這些irp派遣函數都被ndis託管了。若是咱們在註冊小端口前設置了這些派遣函數,將會被覆蓋。
若是在註冊小端口後再設置,能夠hook ndis內部設置的那些派遣函數。(I表示Internal內部未導出函數)
Miniport->DriverObject->MajorFunction[IRP_MJ_CREATE] = NdisICreateClose;
Miniport->DriverObject->MajorFunction[IRP_MJ_CLOSE] = NdisICreateClose;
Miniport->DriverObject->MajorFunction[IRP_MJ_PNP] = NdisIDispatchPnp;
Miniport->DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = NdisIShutdown;
Miniport->DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = NdisIDeviceIoControl;
//關鍵。Ndis內部託管了AddDevice,它會在內部建立小端口設備對象,綁定在硬件pdo上
Miniport->DriverObject->DriverExtension->AddDevice = NdisIAddDevice;
return NDIS_STATUS_SUCCESS;
}
協議驅動經過NdisRegisterProtocol,小端口驅動經過NdisMRegisterMiniport向ndis框架註冊了本身的回調函數後,協議驅動就能夠與小端口驅動在ndis框架下經過這兩組回調函數進行交互通訊了。Ndis.sys起着橋樑中介的做用,除此以外,ndis.sys模塊還提供了大量的ndis運行庫函數。所以又能夠說ndis.sys是一個函數庫。
在NdisMRegisterMiniport以前,須要一個ndis小端口驅動句柄(其實是一個驅動描述符結構),下面的宏就是用來建立一個小端口驅動句柄的。
#define NdisMInitializeWrapper NdisInitializeWrapper
VOID
NdisInitializeWrapper(
OUT PNDIS_HANDLE NdisWrapperHandle, //返回建立的小端口驅動句柄
IN PVOID SystemSpecific1,//必須傳DriverObject
IN PVOID SystemSpecific2,//必須傳RegisterPath
IN PVOID SystemSpecific3)//無用
{
PNDIS_M_DRIVER_BLOCK Miniport;
PUNICODE_STRING RegistryPath;
WCHAR *RegistryBuffer;
*NdisWrapperHandle = NULL;
//建立一個小端口驅動描述符,也即句柄
Miniport = ExAllocatePool(NonPagedPool, sizeof(NDIS_M_DRIVER_BLOCK));
RtlZeroMemory(Miniport, sizeof(NDIS_M_DRIVER_BLOCK));
KeInitializeSpinLock(&Miniport->Lock);
Miniport->DriverObject = (PDRIVER_OBJECT)SystemSpecific1;
RegistryPath = ExAllocatePool(PagedPool, sizeof(UNICODE_STRING));
RegistryPath->Length = ((PUNICODE_STRING)SystemSpecific2)->Length;
RegistryPath->MaximumLength = RegistryPath->Length + sizeof(WCHAR)
RegistryBuffer = ExAllocatePool(PagedPool, RegistryPath->MaximumLength);
RtlCopyMemory(RegistryBuffer, ((PUNICODE_STRING)SystemSpecific2)->Buffer, RegistryPath->Length);
RegistryBuffer[RegistryPath->Length/sizeof(WCHAR)] = 0;
RegistryPath->Buffer = RegistryBuffer;
Miniport->RegistryPath = RegistryPath;//記錄這個小端口驅動的服務鍵路徑
InitializeListHead(&Miniport->DeviceList);//初始爲空
//將本小端口驅動掛入全局鏈表(貌似在NdisMRegisterMiniport中作這項工做更合理)
ExInterlockedInsertTailList(&MiniportListHead, &Miniport->ListEntry, &MiniportListLock);
*NdisWrapperHandle = Miniport;//返回建立的小端口驅動句柄給用戶
}
這樣,在小端口驅動的描述符中有一個指針指向其驅動對象,而在驅動對象的標準擴展部中也有一個指針指向了小端口驅動的描述符。兩者互相指向。
前面說過:ndis內部設置的AddDevice即NdisIAddDevice函數會在內部自動建立一個小端口設備對象,而後加入堆棧。咱們看:
NTSTATUS
NdisIAddDevice( //中間的I表示Internal,即ndis.sys內部使用,未導出的函數
IN PDRIVER_OBJECT DriverObject,//ndis小端口驅動對象
IN PDEVICE_OBJECT PhysicalDeviceObject)//表明網卡的硬件pdo
{
static const WCHAR ClassKeyName[] = {'C','l','a','s','s','\\'};
static const WCHAR LinkageKeyName[] = {'\\','L','i','n','k','a','g','e',0};
MiniportPtr = IoGetDriverObjectExtension(DriverObject, (PVOID)'NMID');
Miniport = *MiniportPtr;//得到小端口驅動描述符
//獲取該硬件pdo的驅動鍵屬性
Status = IoGetDeviceProperty(PhysicalDeviceObject, DevicePropertyDriverKeyName,
0, NULL, &DriverKeyLength);
LinkageKeyBuffer = ExAllocatePool(PagedPool, DriverKeyLength +
sizeof(ClassKeyName) + sizeof(LinkageKeyName));
Status = IoGetDeviceProperty(PhysicalDeviceObject, DevicePropertyDriverKeyName,
DriverKeyLength, LinkageKeyBuffer +
(sizeof(ClassKeyName) / sizeof(WCHAR)),&DriverKeyLength);
RtlCopyMemory(LinkageKeyBuffer, ClassKeyName, sizeof(ClassKeyName));
RtlCopyMemory(LinkageKeyBuffer + ((sizeof(ClassKeyName) + DriverKeyLength) /
sizeof(WCHAR)) - 1, LinkageKeyName, sizeof(LinkageKeyName));
// LinkageKeyBuffer最終爲:‘Class\DriverKeyName\Linkage’
RtlZeroMemory(QueryTable, sizeof(QueryTable));
RtlInitUnicodeString(&ExportName, NULL);
QueryTable[0].Flags = RTL_QUERY_REGISTRY_REQUIRED | RTL_QUERY_REGISTRY_DIRECT;
QueryTable[0].Name = L"Export";
QueryTable[0].EntryContext = &ExportName;
//查詢該硬件pdo的ExportName,做爲其端口設備對象名稱
Status = RtlQueryRegistryValues(RTL_REGISTRY_CONTROL, LinkageKeyBuffer,
QueryTable, NULL, NULL);
//關鍵。Ndis內部自動爲其建立了小端口設備
Status = IoCreateDevice(Miniport->DriverObject, sizeof(LOGICAL_ADAPTER),
&ExportName, FILE_DEVICE_PHYSICAL_NETCARD,0, FALSE, &DeviceObject);
//關鍵。Ndis爲咱們建立的小端口設備對象使用標準的LOGICAL_ADAPTER結構設備擴展
Adapter = (PLOGICAL_ADAPTER)DeviceObject->DeviceExtension;
KeInitializeSpinLock(&Adapter->NdisMiniportBlock.Lock);
InitializeListHead(&Adapter->ProtocolListHead);//初始爲空
Status = IoRegisterDeviceInterface(PhysicalDeviceObject,&GUID_DEVINTERFACE_NET,
NULL,&Adapter->NdisMiniportBlock.SymbolicLinkName);
Adapter->NdisMiniportBlock.DriverHandle = Miniport;
Adapter->NdisMiniportBlock.MiniportName = ExportName;//小端口設備對象名
Adapter->NdisMiniportBlock.DeviceObject = DeviceObject;
Adapter->NdisMiniportBlock.PhysicalDeviceObject = PhysicalDeviceObject;//該網卡的硬件pdo
//關鍵。Ndis內部自動建立一個相應的小端口設備,並加入堆棧。(這些操做對用戶透明)
Adapter->NdisMiniportBlock.NextDeviceObject =
IoAttachDeviceToDeviceStack(Adapter->NdisMiniportBlock.DeviceObject,PhysicalDeviceObject);
Adapter->NdisMiniportBlock.OldPnPDeviceState = 0;
Adapter->NdisMiniportBlock.PnPDeviceState = NdisPnPDeviceAdded;//標記已建立設備加入堆棧
KeInitializeTimer(&Adapter->NdisMiniportBlock.WakeUpDpcTimer.Timer);
KeInitializeDpc(&Adapter->NdisMiniportBlock.WakeUpDpcTimer.Dpc, MiniportHangDpc, Adapter);
DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
總之,Ndis內部託管的AddDevice會爲咱們自動建立小端口設備,加入堆棧。可是協議驅動的AddDevice就沒有了,所以協議驅動沒有形式堆棧到設備棧中。協議驅動與小端口驅動之間斷層了,irp只能最終下發到協議驅動這一層就再也傳不下去了,協議驅動與小端口驅動之間的交互就不能使用傳統的irp方式,而只能藉助ndis框架和回調函數進行通訊。
Ndis內部的小端口設備對象的設備擴展結構
typedef struct _LOGICAL_ADAPTER //標準的小端口設備擴展
{
//這個字段內部有一個自定義小端口設備擴展,用戶設置的自定義設備擴展就放在那裏
NDIS_MINIPORT_BLOCK NdisMiniportBlock;
PNDIS_MINIPORT_WORK_ITEM WorkQueueHead; /* Head of work queue */
PNDIS_MINIPORT_WORK_ITEM WorkQueueTail; /* Tail of work queue */
LIST_ENTRY ListEntry; //用來掛入全局的小端口設備鏈表
LIST_ENTRY MiniportListEntry; //用來掛入本驅動中的小端口設備鏈表
LIST_ENTRY ProtocolListHead; //綁定着本小端口設備的全部協議驅動
ULONG MediumHeaderSize; //鏈路層頭部長度(即鏈路層類型)
HARDWARE_ADDRESS Address; //物理地址(以太網卡爲MAC)
ULONG AddressLength; //物理地址長度(以太網卡爲6B)
PMINIPORT_BUGCHECK_CONTEXT BugcheckContext;
} LOGICAL_ADAPTER, *PLOGICAL_ADAPTER;
當小端口驅動加載執行了DriverEntry、AddDevice後,系統就會發出一個IRP_MN_START_DEVICE的pnp irp來啓動設備。Pnp irp 派遣函數也被ndis託管了,固定爲:
NdisIDispatchPnp。咱們看他是如何處理pnp irp的
NTSTATUS
NdisIDispatchPnp(IN PDEVICE_OBJECT DeviceObject,PIRP Irp)
{
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
PLOGICAL_ADAPTER Adapter = (PLOGICAL_ADAPTER)DeviceObject->DeviceExtension;
NTSTATUS Status;
switch (Stack->MinorFunction)
{
case IRP_MN_START_DEVICE:
Status = NdisIForwardIrpAndWait(Adapter, Irp);//向下層轉發直至完成
if (NT_SUCCESS(Status) && NT_SUCCESS(Irp->IoStatus.Status))
Status = NdisIPnPStartDevice(DeviceObject, Irp);//執行通用的設備啓動操做
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
break;
…
}
return Status;
}
實際的設備啓動工做由NdisIPnPStartDevice完成。這是一個通用函數,用來完成一些通用的ndis網卡設備的啓動工做。
NTSTATUS NdisIPnPStartDevice(IN PDEVICE_OBJECT DeviceObject,PIRP Irp)
{ …
//加入全局的適配器列表
ExInterlockedInsertTailList(&AdapterListHead, &Adapter->ListEntry, &AdapterListLock);
//關鍵。回調用戶本身提供的啓動初始化函數,執行用戶自定義的初始化工做
NdisStatus = (*Adapter->NdisMiniportBlock.DriverHandle->MiniportCharacteristics.InitializeHandler)
(
&OpenErrorStatus, &SelectedMediumIndex, &MediaArray[0],
MEDIA_ARRAY_SIZE, Adapter, (NDIS_HANDLE)&WrapperContext
);
…
Adapter->NdisMiniportBlock.MediaType = MediaArray[SelectedMediumIndex];//記錄介質類型
…
//加入所屬小端口驅動內部的適配器鏈表
ExInterlockedInsertTailList(&Adapter->NdisMiniportBlock.DriverHandle->DeviceList, &Adapter->MiniportListEntry, &Adapter->NdisMiniportBlock.DriverHandle->Lock);
//關鍵。新網卡啓動初始化完成後,通知全部協議驅動進行綁定
CurrentEntry = ProtocolListHead.Flink;
while (CurrentEntry != &ProtocolListHead)
{
ProtocolBinding = CONTAINING_RECORD(CurrentEntry, PROTOCOL_BINDING, ListEntry);
ndisBindMiniportsToProtocol(&NdisStatus, ProtocolBinding);
CurrentEntry = CurrentEntry->Flink;
}
…
}
小端口驅動都必須提供一個自定義的啓動初始化回調函數,用來在網卡啓動時執行某些初始化工做。下面是一個典型的啓動初始化函數,咱們看看通常要作哪些初始化工做。(注意下文所說的示例函數都來自於ne2000.sys這個通用的以太網卡小端口驅動)
//示例函數(這個示例回調函數作了初始化硬件、註冊中斷向量 等工做)
NDIS_STATUS NTAPI MiniportInitialize(
OUT PNDIS_STATUS OpenErrorStatus,
OUT PUINT SelectedMediumIndex,//返回該網卡的介質類型
IN PNDIS_MEDIUM MediumArray,
IN UINT MediumArraySize,
IN NDIS_HANDLE MiniportAdapterHandle,//實際上就是內置的標準小端口設備擴展
IN NDIS_HANDLE WrapperConfigurationContext)//硬件pdo的一些屬性信息和配置鍵
{
UINT *RegNetworkAddress = 0;
UINT RegNetworkAddressLength = 0;
for (i = 0; i < MediumArraySize; i++)
{
if (MediumArray[i] == NdisMedium802_3)
break;
}
if (i == MediumArraySize)
return NDIS_STATUS_UNSUPPORTED_MEDIA;
*SelectedMediumIndex = i;//返回介質類型
//分配一個自定義的小端口設備對象擴展
Status = NdisAllocateMemory(&Adapter,sizeof(NIC_ADAPTER),0,HighestAcceptableMax);
NdisZeroMemory(Adapter, sizeof(NIC_ADAPTER));
Adapter->MiniportAdapterHandle = MiniportAdapterHandle;//記錄標準設備擴展
//下面是默認的資源配置
Adapter->IoBaseAddress = DRIVER_DEFAULT_IO_BASE_ADDRESS;//即port基地址
Adapter->InterruptLevel = DRIVER_DEFAULT_INTERRUPT_NUMBER;
Adapter->InterruptVector = DRIVER_DEFAULT_INTERRUPT_NUMBER;
Adapter->InterruptShared = DRIVER_DEFAULT_INTERRUPT_SHARED;
Adapter->InterruptMode = DRIVER_DEFAULT_INTERRUPT_MODE;
Adapter->MaxMulticastListSize = DRIVER_MAX_MULTICAST_LIST_SIZE;
Adapter->InterruptMask = DRIVER_INTERRUPT_MASK;
Adapter->LookaheadSize = DRIVER_MAXIMUM_LOOKAHEAD;//負載前視區長度
//查詢系統爲該網卡分配的資源(irq、port),記錄到自定義設備擴展中
MiQueryResources(&Status, Adapter, WrapperConfigurationContext);
//若是分配失敗或查詢失敗,就從註冊表中配置該網卡須要的資源
if (Status != NDIS_STATUS_SUCCESS)
{
PNDIS_CONFIGURATION_PARAMETER ConfigurationParameter;
UNICODE_STRING Keyword;
//打開配置鍵
NdisOpenConfiguration(&Status, &ConfigurationHandle, WrapperConfigurationContext);
if (Status == NDIS_STATUS_SUCCESS)
{
//查詢irq
NdisInitUnicodeString(&Keyword, L"Irq");
NdisReadConfiguration(&Status, &ConfigurationParameter, ConfigurationHandle, &Keyword, NdisParameterHexInteger);
if(Status == NDIS_STATUS_SUCCESS)
{
Adapter->InterruptLevel =
Adapter->InterruptVector = ConfigurationParameter->ParameterData.IntegerData;
}
//查詢port
NdisInitUnicodeString(&Keyword, L"Port");
NdisReadConfiguration(&Status, &ConfigurationParameter, ConfigurationHandle, &Keyword, NdisParameterHexInteger);
if(Status == NDIS_STATUS_SUCCESS)
Adapter->IoBaseAddress = ConfigurationParameter->ParameterData.IntegerData;
NdisCloseConfiguration(ConfigurationHandle);
}
}
//關鍵。設置自定義設備擴展
NdisMSetAttributes(MiniportAdapterHandle,
(NDIS_HANDLE)Adapter,//記錄這個自定義的小端口設備擴展 到 標準設備擴展內部
FALSE,NdisInterfaceIsa);
Status = NdisMRegisterIoPortRange(&Adapter->IOBase,MiniportAdapterHandle,
Adapter->IoBaseAddress,0x20);
if (Status != NDIS_STATUS_SUCCESS) 。。。
Adapter->IOPortRangeRegistered = TRUE;
#ifndef NOCARD
Status = NICInitialize(Adapter);//初始化網卡內部的硬件寄存器
if (Status != NDIS_STATUS_SUCCESS) 。。。
NdisOpenConfiguration(&Status, &ConfigurationHandle, WrapperConfigurationContext);
if (Status == NDIS_STATUS_SUCCESS)
{ //從註冊表中讀取軟配置的MAC地址
NdisReadNetworkAddress(&Status, (PVOID *)&RegNetworkAddress, &RegNetworkAddressLength, ConfigurationHandle);
if(Status == NDIS_STATUS_SUCCESS && RegNetworkAddressLength == 6)
{
for(i = 0; i < 6; i++)
Adapter->StationAddress[i] = RegNetworkAddress[i];
}
NdisCloseConfiguration(ConfigurationHandle);
}
if (Status != NDIS_STATUS_SUCCESS || RegNetworkAddressLength !=6)
{
for (i = 0; i < 6; i++) //使用固定的MAC地址
Adapter->StationAddress[i] = Adapter->PermanentAddress[i];
}
。。。
NICSetup(Adapter); //設置網卡內部的硬件寄存器
#endif
//註冊中斷向量
Status = NdisMRegisterInterrupt(&Adapter->Interrupt, MiniportAdapterHandle,
Adapter->InterruptVector,Adapter->InterruptLevel,FALSE,
Adapter->InterruptShared,Adapter->InterruptMode);
if (Status != NDIS_STATUS_SUCCESS) 。。。
Adapter->InterruptRegistered = TRUE;
#ifndef NOCARD
NICStart(Adapter); //設置網卡內部的硬件寄存器
#endif
NdisMRegisterAdapterShutdownHandler(MiniportAdapterHandle, Adapter, MiniportShutdown);
Adapter->ShutdownHandlerRegistered = TRUE;
InsertTailList(&DriverInfo.AdapterListHead, &Adapter->ListEntry);
return NDIS_STATUS_SUCCESS;
}
如上,能夠看出,一塊網卡的啓動初始化工做是比較複雜的。上面的示例函數分配了一個自定義的小端口設備對象擴展,初始化網卡內部的硬件寄存器,註冊中斷向量,最後返回網卡的介質類型告訴給ndis框架(前3工做是可選的,最後的告訴工做是必須的)
事實上,通常的網卡在啓動初始化時都要作這些工做:【硬件、注斷、自擴展】
硬件:指初始化硬件
注斷:註冊中斷isr
自擴展:在標準小端口設備擴展以外再另行分配一個自定義設備擴展
題外話:
爲何要分配一個自定義設備擴展呢? 咱們知道,ndis內部提供託管的AddDevice會爲咱們自動建立一個小端口設備對象,而這個設備對象的設備擴展是ndis內部預置的一個結構。以往咱們手動調用IoCreateDevice時都是本身定義的設備擴展來保存自定義信息,但如今被ndis託管了,若是咱們但願仍舊保存一些自定義信息怎麼辦?Ndis框架不傻,那個預置的小端口設備擴展內部就提供了一個字段(即適配器上下文),用來存放用戶自定義的設備擴展。用戶只需分配一個設備擴展,而後調用NdisMSetAttributes設置一下便可。
到時候ndis調用咱們的回調函數時,會傳入這個自定義設備擴展的。
如上,咱們說了,在網卡啓動初始化階段,通常須要註冊一箇中斷向量,下面的函數就是幹這個的。
NDIS_STATUS
NdisMRegisterInterrupt(
OUT PNDIS_MINIPORT_INTERRUPT Interrupt,//返回
IN NDIS_HANDLE MiniportAdapterHandle,
IN UINT InterruptVector,
IN UINT InterruptLevel,
IN BOOLEAN RequestIsr,
IN BOOLEAN SharedInterrupt,
IN NDIS_INTERRUPT_MODE InterruptMode)
{
NTSTATUS Status;
ULONG MappedIRQ;
KIRQL DIrql;
KAFFINITY Affinity;
PLOGICAL_ADAPTER Adapter = (PLOGICAL_ADAPTER)MiniportAdapterHandle;
RtlZeroMemory(Interrupt, sizeof(NDIS_MINIPORT_INTERRUPT));
KeInitializeSpinLock(&Interrupt->DpcCountLock);
// HandleDeferredProcessing爲DPC
KeInitializeDpc(&Interrupt->InterruptDpc, HandleDeferredProcessing, Adapter);
KeInitializeEvent(&Interrupt->DpcsCompletedEvent, NotificationEvent, FALSE);
Interrupt->SharedInterrupt = SharedInterrupt;
Interrupt->IsrRequested = RequestIsr;
Interrupt->Miniport = &Adapter->NdisMiniportBlock;
MappedIRQ = HalGetInterruptVector(Adapter->NdisMiniportBlock.BusType, Adapter->NdisMiniportBlock.BusNumber,InterruptLevel, InterruptVector, &DIrql,&Affinity);
//關鍵。註冊中斷向量。Isr爲ServiceRoutine,是ndis本身內部提供的isr
Status = IoConnectInterrupt(&Interrupt->InterruptObject, ServiceRoutine, Interrupt, &Interrupt->DpcCountLock, MappedIRQ,DIrql, DIrql, InterruptMode, SharedInterrupt, Affinity, FALSE);
if (NT_SUCCESS(Status)) {
Adapter->NdisMiniportBlock.Interrupt = Interrupt;
Adapter->NdisMiniportBlock.RegisteredInterrupts++;
return NDIS_STATUS_SUCCESS;
}
return NDIS_STATUS_FAILURE;
}
這樣,一旦有中斷髮生,就會進入ServiceRoutine這個isr。這個isr是ndis內部本身提供的,咱們看它作了什麼
BOOLEAN ServiceRoutine(IN PKINTERRUPT Interrupt, IN PVOID ServiceContext)
{
BOOLEAN InterruptRecognized = FALSE;
BOOLEAN QueueMiniportHandleInterrupt = FALSE;
PNDIS_MINIPORT_INTERRUPT NdisInterrupt = ServiceContext;
PNDIS_MINIPORT_BLOCK NdisMiniportBlock = NdisInterrupt->Miniport;
if (NdisInterrupt->IsrRequested)//是否要執行isr
{
//調用咱們註冊小端口特徵時登記的isr,簡稱咱們的isr
(*NdisMiniportBlock->DriverHandle->MiniportCharacteristics.ISRHandler)(
&InterruptRecognized,
&QueueMiniportHandleInterrupt, //返回是否要執行isr的後半部
NdisMiniportBlock->MiniportAdapterContext);
}
else if (NdisMiniportBlock->DriverHandle->MiniportCharacteristics.DisableInterruptHandler)
{
(*NdisMiniportBlock->DriverHandle->MiniportCharacteristics.DisableInterruptHandler)(
NdisMiniportBlock->MiniportAdapterContext);
QueueMiniportHandleInterrupt = TRUE;
InterruptRecognized = TRUE;
}
if (QueueMiniportHandleInterrupt) //執行HandleDeferredProcessing這個DPC,即isr的後半部
KeInsertQueueDpc(&NdisInterrupt->InterruptDpc, NULL, NULL);
return InterruptRecognized;
}
//咱們的isr。(這是一個示例函數)
VOID NTAPI MiniportISR(
OUT PBOOLEAN InterruptRecognized,
OUT PBOOLEAN QueueMiniportHandleInterrupt,//返回時
IN NDIS_HANDLE MiniportAdapterContext)
{
//屏蔽來自這個網卡的後續中斷。注意與cli指令不同。
NICDisableInterrupts((PNIC_ADAPTER)MiniportAdapterContext);
*InterruptRecognized = TRUE;
*QueueMiniportHandleInterrupt = TRUE;
}
如上,咱們編寫的這個示例isr很簡單,它僅僅暫時屏蔽來自這個網卡的後續中斷,而後,QueueMiniportHandleInterrupt置爲TRUE,表示將實質的中斷處理工做歸入到DPC中去執行。因爲DPC都是在開中斷的條件下執行的,因此必須先屏蔽掉來自同一網卡的其它後續中斷,防止嵌套。而這個DPC就是ndis內部本身提供的下面函數,咱們看它作了什麼工做。
VOID HandleDeferredProcessing(
IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2)
{
PLOGICAL_ADAPTER Adapter = GET_LOGICAL_ADAPTER(DeferredContext);
//關鍵。調用用戶本身註冊小端口時提供的HandleInterruptHandler例程(*Adapter->NdisMiniportBlock.DriverHandle->MiniportCharacteristics.HandleInterruptHandler)(
Adapter->NdisMiniportBlock.MiniportAdapterContext);
//從新啓用中斷後執行這個函數
if(Adapter->NdisMiniportBlock.DriverHandle->MiniportCharacteristics.EnableInterruptHandler)
(*Adapter->NdisMiniportBlock.DriverHandle->MiniportCharacteristics.EnableInterruptHandler)
(Adapter->NdisMiniportBlock.MiniportAdapterContext);
}
如上,用戶本身註冊小端口時提供的HandleInterruptHandler例程其實就是咱們isr的後半部。當ndis框架回調執行了isr的後半部後,全部中斷處理工做都處理完畢了,而後就能夠開啓來自這個網卡的後續中斷了,也即撤銷屏蔽。
下面就是一個示例HandleInterruptHandler,來自於ne2000驅動,咱們看看那個驅動的isr後半部工做到底作了什麼。
VOID NTAPI MiniportHandleInterrupt(IN NDIS_HANDLE MiniportAdapterContext)//自定義設備擴展
{
UCHAR ISRValue;
UCHAR ISRMask;
UCHAR Mask;
PNIC_ADAPTER Adapter = (PNIC_ADAPTER)MiniportAdapterContext;
UINT i = 0;
ISRMask = Adapter->InterruptMask;//通常爲0xFF
//全部網卡內部都配備有一箇中斷狀態寄存器,即PG0_ISR
NdisRawReadPortUchar(Adapter->IOBase + PG0_ISR, &ISRValue);//讀取當前網卡的狀態
Adapter->InterruptStatus |= (ISRValue & ISRMask);
Mask = 0x01;//mask表示位置掩碼
while (Adapter->InterruptStatus != 0x00 && i++ < INTERRUPT_LIMIT)
{
if (ISRValue != 0x00) {
NdisRawWritePortUchar(Adapter->IOBase + PG0_ISR, ISRValue);
Mask = 0x01;//從新回到最低位
}
//逐位向高位掃描
while (((Adapter->InterruptStatus & Mask) == 0) && (Mask < ISRMask))
Mask = (Mask << 1);
switch (Adapter->InterruptStatus & Mask)
{
case ISR_OVW://每當芯片中的接收緩衝區溢出時會觸發這種中斷
Adapter->BufferOverflow = TRUE;
if(Adapter->MiniportAdapterHandle)
HandleReceive(Adapter); //接出全部幀,提交給上層
Adapter->InterruptStatus &= ~ISR_OVW;
break;
case ISR_RXE://每當收到一個錯誤幀時觸發這種中斷
NICUpdateCounters(Adapter);
Adapter->ReceiveError = TRUE;
case ISR_PRX://每當芯片收到一個以太網幀時,觸發這種中斷
if(Adapter->MiniportAdapterHandle)
HandleReceive(Adapter); //接出全部幀,提交給上層
Adapter->InterruptStatus &= ~(ISR_PRX | ISR_RXE);
break;
case ISR_TXE://每當芯片發送一幀失敗時,觸發這種中斷
NICUpdateCounters(Adapter);
Adapter->TransmitError = TRUE;
case ISR_PTX://每當芯片中的發送緩衝區變空時觸發這種中斷
HandleTransmit(Adapter);
Adapter->InterruptStatus &= ~(ISR_PTX | ISR_TXE);
break;
case ISR_CNT://每當芯片中的計數器溢出時觸發這種中斷
NICUpdateCounters(Adapter);
Adapter->InterruptStatus &= ~ISR_CNT;
break;
default:
Adapter->InterruptStatus &= ~Mask;
break;
}
Mask = (Mask << 1);
NdisRawReadPortUchar(Adapter->IOBase + PG0_ISR, &ISRValue);
Adapter->InterruptStatus |= (ISRValue & ISRMask);//狀態可能又變了,讀取最新的狀態
}
}
如上,網卡的物理狀態發生上述變化時,都會觸發一次中斷,同時記錄在狀態寄存器對應的位。
咱們的isr每獲得一次中斷時,都要掃描狀態寄存器中的全部狀態位,一一處理(由於咱們在處理中斷時,屏蔽了來自這個網卡的中斷,所以會形成中斷累積。因此必須在每次中斷的處理函數中處理全部可能發生的狀態)
網卡芯片中有一個硬件發送緩衝區和一個硬件接收緩衝區。當從網絡電纜來到一幀時,就會存放到芯片內部的接收緩衝區。芯片會將內部的發送緩衝區中的幀注入到電纜上,這個過程也比較費時。芯片與電纜的數據交換速度受網卡製造工藝限制,通常的網卡也不過是百兆、千兆帶寬而已。即便交換速度慢,可是若是應用程序收幀的速度沒有幀從網絡電纜抵達網卡的速度快的話,網卡內部的接收緩衝區就會逐漸變滿而溢出,從而致使觸發中斷。同理,當網卡終於把內部發送緩衝區中的數據發出到網絡電纜後,發送緩衝區變成空閒時,也會觸發中斷。
Ne2000以太網卡的小端口驅動提供了HandleReceive這個函數,用於從網卡內部的接收緩衝區中讀出全部幀,提交給上層(其實是提交給綁定着這塊網卡的全部協議驅動)。HandleReceive這個函數內部使用了NdisMEthIndicateReceive宏完成提交工做。這個宏實際上調用了下面的函數來作提交工做
VOID
EthFilterDprIndicateReceive(
IN PETH_FILTER Filter,
IN NDIS_HANDLE MacReceiveContext,
IN PCHAR Address,
IN PVOID HeaderBuffer,
IN UINT HeaderBufferSize,
IN PVOID LookaheadBuffer,
IN UINT LookaheadBufferSize,
IN UINT PacketSize)
{
MiniIndicateData((PLOGICAL_ADAPTER)((PETHI_FILTER)Filter)->Miniport,
MacReceiveContext,HeaderBuffer,HeaderBufferSize,
LookaheadBuffer,LookaheadBufferSize,PacketSize);
}
VOID
MiniIndicateData( //向上提交部分幀
PLOGICAL_ADAPTER Adapter,//目標網卡設備的標準設備擴展
NDIS_HANDLE MacReceiveContext,
PVOID HeaderBuffer,//幀頭
UINT HeaderBufferSize,//幀頭長度
PVOID LookaheadBuffer,//負載部分的前N字節,又叫前視區
UINT LookaheadBufferSize,// 前視區長度
UINT PacketSize)//負載部分的總長
{
KIRQL OldIrql;
PLIST_ENTRY CurrentEntry;
PADAPTER_BINDING AdapterBinding;
MiniDisplayPacket2(HeaderBuffer, HeaderBufferSize, LookaheadBuffer, LookaheadBufferSize);
KeAcquireSpinLock(&Adapter->NdisMiniportBlock.Lock, &OldIrql);
{
CurrentEntry = Adapter->ProtocolListHead.Flink;
//遍歷綁定了本網卡的那些協議驅動
while (CurrentEntry != &Adapter->ProtocolListHead)
{
AdapterBinding = CONTAINING_RECORD(CurrentEntry, ADAPTER_BINDING, AdapterListEntry);
//看到沒,調用其提供的接收函數
(*AdapterBinding->ProtocolBinding->Chars.ReceiveHandler)(
AdapterBinding->NdisOpenBlock.ProtocolBindingContext,MacReceiveContext,
HeaderBuffer,HeaderBufferSize,
LookaheadBuffer,LookaheadBufferSize,PacketSize);
CurrentEntry = CurrentEntry->Flink;
}
}
KeReleaseSpinLock(&Adapter->NdisMiniportBlock.Lock, OldIrql);
}
如上,這個函數用來向上層綁定的全部協議提交幀(可能不是完整的幀)
前面說過了,當一個新的網卡插入機器時,ndis框架會通知全部現有的協議驅動進行綁定。當一個新的協議驅動安裝加載到系統時,ndis框架也會讓這個協議綁定現有的全部網卡(協議驅動與網卡之間必須綁定後才能通訊)。當ndis框架通知協議驅動進行綁定時,會調用各協議驅動註冊的綁定回調函數,在這個函數中,咱們應該調用NdisOpenAdapter打開那個新插入到系統的網卡,進行綁定。
VOID
NdisOpenAdapter(
OUT PNDIS_STATUS Status,//返回
OUT PNDIS_STATUS OpenErrorStatus,//返回
OUT PNDIS_HANDLE NdisBindingHandle,//返回生成的綁定句柄(即標準的綁定上下文)
OUT PUINT SelectedMediumIndex,//返回目標網卡的介質類型
IN PNDIS_MEDIUM MediumArray,//目標協議支持的全部介質類型
IN UINT MediumArraySize,
IN NDIS_HANDLE NdisProtocolHandle,//目標協議
IN NDIS_HANDLE ProtocolBindingContext,//自定義的綁定上下文
IN PNDIS_STRING AdapterName,//小端口設備對象名(即目標網卡)
IN UINT OpenOptions,
IN PSTRING AddressingInformation OPTIONAL)
{
UINT i;
BOOLEAN Found;
PLOGICAL_ADAPTER Adapter;
PADAPTER_BINDING AdapterBinding;
PPROTOCOL_BINDING Protocol = GET_PROTOCOL_BINDING(NdisProtocolHandle);
Adapter = MiniLocateDevice(AdapterName);//根據名稱找到目標網卡
Found = FALSE;
for (i = 0; i < MediumArraySize; i++)
{
if (Adapter->NdisMiniportBlock.MediaType == MediumArray[i])
{
*SelectedMediumIndex = i;
Found = TRUE;
break;
}
}
//一種協議能夠支持不少種網卡的,如tcpip協議能夠承載在以太網卡、令牌環網卡、FDDI網卡、ATM網卡等多種鏈路類型的網卡
if (!Found)//if目標協議不支持目標網卡
{
*Status = NDIS_STATUS_UNSUPPORTED_MEDIA;
return;
}
//分配一個標準的綁定上下文(即綁定句柄)
AdapterBinding = ExAllocatePool(NonPagedPool, sizeof(ADAPTER_BINDING));
RtlZeroMemory(AdapterBinding, sizeof(ADAPTER_BINDING));
//在綁定上下文中記錄誰綁定了誰
AdapterBinding->ProtocolBinding = Protocol;
AdapterBinding->Adapter = Adapter;
//關鍵。在標準綁定上下文中記錄自定義綁定上下文
AdapterBinding->NdisOpenBlock.ProtocolBindingContext = ProtocolBindingContext;
AdapterBinding->NdisOpenBlock.BindingHandle = (NDIS_HANDLE)AdapterBinding;
//pro開頭的都是ndis內部函數,某些宏須要這些函數
AdapterBinding->NdisOpenBlock.RequestHandler = ProRequest;
AdapterBinding->NdisOpenBlock.ResetHandler = ProReset;
AdapterBinding->NdisOpenBlock.SendHandler = ProSend;
AdapterBinding->NdisOpenBlock.SendPacketsHandler = ProSendPackets;
AdapterBinding->NdisOpenBlock.TransferDataHandler = ProTransferData;
AdapterBinding->NdisOpenBlock.RequestCompleteHandler =
Protocol->Chars.RequestCompleteHandler;
//互相插入各自的綁定列表中
ExInterlockedInsertTailList(&Protocol->AdapterListHead, &AdapterBinding->ProtocolListEntry, &Protocol->Lock);
ExInterlockedInsertTailList(&Adapter->ProtocolListHead, &AdapterBinding->AdapterListEntry, &Adapter->NdisMiniportBlock.Lock);
*NdisBindingHandle = (NDIS_HANDLE)AdapterBinding;//返回綁定句柄(即內部建立的標準綁定上下文)
*Status = NDIS_STATUS_SUCCESS;
}
TCP/IP、IPX/SPX都是協議驅動,現在的時代,tcpip佔據了市場主導地位,咱們看下這個協議驅動(tcpip.sys)的部分實現
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
NTSTATUS Status;
UNICODE_STRING strIpDeviceName = RTL_CONSTANT_STRING(L「\\Device\\Ip」);
UNICODE_STRING strRawDeviceName = RTL_CONSTANT_STRING(L「\\Device\\RawIp」);
UNICODE_STRING strUdpDeviceName = RTL_CONSTANT_STRING(L「\\Device\\Udp」);
UNICODE_STRING strTcpDeviceName = RTL_CONSTANT_STRING(L「\\Device\\Tcp」);
UNICODE_STRING strNdisDeviceName = RTL_CONSTANT_STRING(L「Tcpip」);
NDIS_STATUS NdisStatus;
LARGE_INTEGER DueTime;
KeInitializeDpc(&IPTimeoutDpc, IPTimeoutDpcFn, NULL);
KeInitializeTimer(&IPTimer);
/* Create IP device object */
Status = IoCreateDevice(DriverObject, 0, &strIpDeviceName,
FILE_DEVICE_NETWORK, 0, FALSE, &IPDeviceObject);
ChewInit( IPDeviceObject );
/* Create RawIP device object */
Status = IoCreateDevice(DriverObject, 0, &strRawDeviceName,
FILE_DEVICE_NETWORK, 0, FALSE, &RawIPDeviceObject);
/* Create UDP device object */
Status = IoCreateDevice(DriverObject, 0, &strUdpDeviceName,
FILE_DEVICE_NETWORK, 0, FALSE, &UDPDeviceObject);
/* Create TCP device object */
Status = IoCreateDevice(DriverObject, 0, &strTcpDeviceName,
FILE_DEVICE_NETWORK, 0, FALSE, &TCPDeviceObject);
/* Setup network layer and transport layer entities */
KeInitializeSpinLock(&EntityListLock);
EntityList = ExAllocatePoolWithTag(NonPagedPool,sizeof(TDIEntityID) * MAX_TDI_ENTITIES);
EntityCount = 0;
EntityMax = MAX_TDI_ENTITIES;
//分配全局包描述符池
NdisAllocatePacketPool(&NdisStatus, &GlobalPacketPool, 100, sizeof(PACKET_CONTEXT));
//分配全局緩衝描述符池
NdisAllocateBufferPool(&NdisStatus, &GlobalBufferPool, 100);
//初始化地址文件對象列表
InitializeListHead(&AddressFileListHead);
KeInitializeSpinLock(&AddressFileListLock);
//初始化鏈接端點列表
InitializeListHead(&ConnectionEndpointListHead);
KeInitializeSpinLock(&ConnectionEndpointListLock);
//初始化本協議的綁定網卡列表
InitializeListHead(&InterfaceListHead);
KeInitializeSpinLock(&InterfaceListLock);
IPStartup(RegistryPath); //啓動初始化網絡層
RawIPStartup();//啓動初始化RawIp協議
UDPStartup();//啓動初始化Udp協議
TCPStartup();//啓動初始化Tcp協議
ICMPStartup();//啓動初始化Icmp協議
//各類協議層設備都使用直接mdl io方式
IPDeviceObject->Flags |= DO_DIRECT_IO; RawIPDeviceObject->Flags |= DO_DIRECT_IO;
UDPDeviceObject->Flags |= DO_DIRECT_IO; TCPDeviceObject->Flags |= DO_DIRECT_IO;
DriverObject->MajorFunction[IRP_MJ_CREATE] = TiDispatchOpenClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = TiDispatchOpenClose;
DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = TiDispatchInternal;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = TiDispatch;
DriverObject->DriverUnload = TiUnload;
//註冊迴環網卡(127.0.0.1是一個特殊的虛擬網卡),加入綁定網卡列表和路由表
Status = LoopRegisterAdapter(NULL, NULL);
Status = LANRegisterProtocol(&strNdisDeviceName);//關鍵。註冊協議驅動特徵
DueTime.QuadPart = -(LONGLONG)IP_TIMEOUT * 10000;
KeSetTimerEx(&IPTimer, DueTime, IP_TIMEOUT, &IPTimeoutDpc);
return STATUS_SUCCESS;
}
如上,這個協議驅動內部會建立一個網絡層設備對象和三個傳輸層設備對象(RawIp也是傳輸層),這樣,應用程序就能夠直接打開這些設備,收發報文(不過,不多有應用程序這樣作,通常都是經過socket
間接打開這些設備進行通訊的)。
下面的函數用來將tcpip.sys註冊爲一個協議驅動
NTSTATUS LANRegisterProtocol(PNDIS_STRING Name)//協議名
{
NDIS_STATUS NdisStatus;
NDIS_PROTOCOL_CHARACTERISTICS ProtChars;
InitializeListHead(&AdapterListHead);
KeInitializeSpinLock(&AdapterListLock);
RtlZeroMemory(&ProtChars, sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
ProtChars.MajorNdisVersion = NDIS_VERSION_MAJOR;
ProtChars.MinorNdisVersion = NDIS_VERSION_MINOR;
ProtChars.Name.Length = Name->Length;
ProtChars.Name.Buffer = Name->Buffer;
ProtChars.Name.MaximumLength = Name->MaximumLength;
ProtChars.OpenAdapterCompleteHandler = ProtocolOpenAdapterComplete;
ProtChars.CloseAdapterCompleteHandler = ProtocolCloseAdapterComplete;
ProtChars.ResetCompleteHandler = ProtocolResetComplete;
ProtChars.RequestCompleteHandler = ProtocolRequestComplete;
ProtChars.SendCompleteHandler = ProtocolSendComplete;
ProtChars.TransferDataCompleteHandler = ProtocolTransferDataComplete;
ProtChars.ReceiveHandler = ProtocolReceive;//關鍵
ProtChars.ReceiveCompleteHandler = ProtocolReceiveComplete;
ProtChars.StatusHandler = ProtocolStatus;
ProtChars.StatusCompleteHandler = ProtocolStatusComplete;
ProtChars.BindAdapterHandler = ProtocolBindAdapter;
ProtChars.PnPEventHandler = ProtocolPnPEvent;
ProtChars.UnbindAdapterHandler = ProtocolUnbindAdapter;
ProtChars.UnloadHandler = LANUnregisterProtocol;
NdisRegisterProtocol(&NdisStatus,&NdisProtocolHandle,&ProtChars,
sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
if (NdisStatus != NDIS_STATUS_SUCCESS)
return (NTSTATUS)NdisStatus;
ProtocolRegistered = TRUE;
return STATUS_SUCCESS;
}
當註冊爲協議驅動後,該協議驅動會打開全部現有適配器,進行綁定。一旦某個網卡收到了數據,觸發中斷,小端口驅動就會調用上層各綁定協議註冊的接收函數,將收到的幀提交給它們。Tcpip協議驅動註冊的接收函數是ProtocolReceive,咱們看它是如何接收處理的。
NDIS_STATUS NTAPI ProtocolReceive(
NDIS_HANDLE BindingContext,//傳進來的是的自定義綁定上下文
NDIS_HANDLE MacReceiveContext,
PVOID HeaderBuffer,//幀頭
UINT HeaderBufferSize,
PVOID LookaheadBuffer,//負載部分前視區部分
UINT LookaheadBufferSize,
UINT PacketSize)//負載部分的總長
{
PLAN_ADAPTER Adapter = (PLAN_ADAPTER)BindingContext;
PETH_HEADER EHeader = (PETH_HEADER)HeaderBuffer;
USHORT EType;
if (Adapter->State != LAN_STATE_STARTED)
return NDIS_STATUS_NOT_ACCEPTED;
if (HeaderBufferSize < Adapter->HeaderSize)
return NDIS_STATUS_NOT_ACCEPTED;
if (Adapter->Media == NdisMedium802_3)
{
if ((EType != ETYPE_IPv4) && (EType != ETYPE_ARP))
return NDIS_STATUS_NOT_ACCEPTED;//目前不支持其餘報文
PacketType = EType;//承載的是IP/ARP報文
}
else
return NDIS_STATUS_NOT_ACCEPTED; //目前只支持以太網卡
//分配一個包描述符用來接收負載部分,注意一個包描述符內部能夠包含多個緩衝描述符,組合起來描述一個邏輯上的連續緩衝
NdisStatus = AllocatePacketWithBuffer( &NdisPacket, NULL,PacketSize );
PC(NdisPacket)->PacketType = PacketType;
IPPacket.NdisPacket = NdisPacket;
IPPacket.Position = 0;
TransferDataCalled++;
if (LookaheadBufferSize == PacketSize)//if恰好收到一個完整的幀
{
GetDataPtr( NdisPacket, 0, &BufferData, &temp );//得到包描述符的緩衝區地址
NdisCopyLookaheadData(BufferData,LookaheadBuffer,LookaheadBufferSize,
Adapter->MacOptions);
}
Else //不要這個殘幀,向下層小端口驅動投遞一個接收包下去,請求轉交完整的負載上來
{
NdisTransferData(&NdisStatus, Adapter->NdisHandle,MacReceiveContext, 0,
PacketSize,//完整負載
NdisPacket,//投遞下去,至關於一個容器
&BytesTransferred);//返回實際轉交的字節數
}
if (NdisStatus != NDIS_STATUS_PENDING)//手動調用完成回調函數
ProtocolTransferDataComplete(BindingContext,NdisPacket,NdisStatus,PacketSize);
return NDIS_STATUS_SUCCESS;
}
當本次接收操做完成後,也即接收到一個完整的包後,就調用ProtocolTransferDataComplete進行處理,咱們看具體是是如何處理接收到的包的。(此處的包爲IP或ARP報文,咱們看它是如何處理的)
VOID NTAPI ProtocolTransferDataComplete(
NDIS_HANDLE BindingContext,//自定義的綁定上下文
PNDIS_PACKET Packet,
NDIS_STATUS Status,
UINT BytesTransferred)
{
TransferDataCompleteCalled++;
if( Status != NDIS_STATUS_SUCCESS ) return;
LanSubmitReceiveWork( BindingContext, Packet, Status, BytesTransferred );//實質函數
}
VOID LanSubmitReceiveWork(
NDIS_HANDLE BindingContext, //自定義的綁定上下文
PNDIS_PACKET Packet,//IP/ARP報文
NDIS_STATUS Status,
UINT BytesTransferred)
{
PLAN_WQ_ITEM WQItem = ExAllocatePoolWithTag(NonPagedPool, sizeof(LAN_WQ_ITEM));
PLAN_ADAPTER Adapter = (PLAN_ADAPTER)BindingContext;
WQItem->Packet = Packet;
WQItem->Adapter = Adapter;
WQItem->BytesTransferred = BytesTransferred;
ChewCreate( LanReceiveWorker, WQItem );//建立一條接收處理工做項
}
咱們看到,收到一個報文後,以工做項的形式進行處理,最後進入LanReceiveWorker這個函數進行接收處理
VOID LanReceiveWorker( PVOID Context )
{
UINT PacketType;
PLAN_WQ_ITEM WorkItem = (PLAN_WQ_ITEM)Context;
PNDIS_PACKET Packet;
PLAN_ADAPTER Adapter;
UINT BytesTransferred;
PNDIS_BUFFER NdisBuffer;
IP_PACKET IPPacket;
Packet = WorkItem->Packet;
Adapter = WorkItem->Adapter;
BytesTransferred = WorkItem->BytesTransferred;
ExFreePoolWithTag(WorkItem, WQ_CONTEXT_TAG);
IPInitializePacket(&IPPacket, 0);
IPPacket.NdisPacket = Packet;
NdisGetFirstBufferFromPacket(Packet,&NdisBuffer,&IPPacket.Header,
&IPPacket.ContigSize,&IPPacket.TotalSize);
IPPacket.ContigSize = IPPacket.TotalSize = BytesTransferred;
PacketType = PC(IPPacket.NdisPacket)->PacketType;
IPPacket.Position = 0;
switch (PacketType)
{
case ETYPE_IPv4:
case ETYPE_IPv6:
IPReceive(Adapter->Context, &IPPacket);//上交給IP層去接收、解析處理
break;
case ETYPE_ARP:
ARPReceive(Adapter->Context, &IPPacket);//上交給ARP層去接收、解析處理
default:
IPPacket.Free(&IPPacket);
break;
}
FreeNdisPacket( Packet );
}
ARP的就不看了,看IP報文是如何在IP層接收的
VOID IPReceive( PIP_INTERFACE IF, PIP_PACKET IPPacket )
{
UINT Version = (((PIPv4_HEADER)IPPacket->Header)->VerIHL >> 4);
switch (Version) {
case 4:
IPPacket->Type = IP_ADDRESS_V4;
IPv4Receive(IF, IPPacket);
break;
case 6:
IPPacket->Type = IP_ADDRESS_V6;
break;
default:
break;
}
IPPacket->Free(IPPacket);
}
VOID IPv4Receive(PIP_INTERFACE IF, PIP_PACKET IPPacket)
{
IPPacket->HeaderSize = (((PIPv4_HEADER)IPPacket->Header)->VerIHL & 0x0F) << 2;
if (IPPacket->HeaderSize > IPv4_MAX_HEADER_SIZE) //錯誤的包,丟棄
return;
if (!IPv4CorrectChecksum(IPPacket->Header, IPPacket->HeaderSize)) //頭部校驗失敗,丟棄
return;
IPPacket->TotalSize = WN2H(((PIPv4_HEADER)IPPacket->Header)->TotalLength);
AddrInitIPv4(&IPPacket->SrcAddr, ((PIPv4_HEADER)IPPacket->Header)->SrcAddr);
AddrInitIPv4(&IPPacket->DstAddr, ((PIPv4_HEADER)IPPacket->Header)->DstAddr);
IPPacket->Position += IPPacket->HeaderSize;//負載部分的偏移位置
IPPacket->Data = (PVOID)((ULONG_PTR)IPPacket->Header + IPPacket->HeaderSize);
ProcessFragment(IF, IPPacket);//拼接ip報文片斷(IPPacket多是一個片斷)
}
當ProcessFragment拼接成一個完整的數據報後,內部就會調用IPDispatchProtocol函數,將IP報文上交給上層相應的協議去接收處理(上層的協議多是tcp、udp、icmp、igmp等協議)
VOID IPDispatchProtocol(
PIP_INTERFACE Interface,//來自網卡
PIP_PACKET IPPacket)//完整IP報文
{
UINT Protocol;
IP_ADDRESS SrcAddress;
switch (IPPacket->Type) {
case IP_ADDRESS_V4:
Protocol = ((PIPv4_HEADER)(IPPacket->Header))->Protocol;//上層協議
AddrInitIPv4(&SrcAddress, ((PIPv4_HEADER)(IPPacket->Header))->SrcAddr);
break;
case IP_ADDRESS_V6:
return;
default:
return;
}
NBResetNeighborTimeout(&SrcAddress);
if (Protocol < IP_PROTOCOL_TABLE_SIZE)
(*ProtocolTable[Protocol])(Interface, IPPacket);//關鍵。上交給相應的上層協議去接收處理
}
Tcp協議的接收處理函數是TcpReceive,Udp協議的接收處理函數是UdpReceive,咱們看看是如何接收、解析udp報文的。
VOID UDPReceive(PIP_INTERFACE Interface, PIP_PACKET IPPacket)//udp報文
{
AF_SEARCH SearchContext;
PIPv4_HEADER IPv4Header;
PADDRESS_FILE AddrFile;
PUDP_HEADER UDPHeader;
PIP_ADDRESS DstAddress, SrcAddress;
UINT DataSize, i;
switch (IPPacket->Type) {
case IP_ADDRESS_V4:
IPv4Header = IPPacket->Header;
DstAddress = &IPPacket->DstAddr;
SrcAddress = &IPPacket->SrcAddr;
break;
case IP_ADDRESS_V6:
return;
default:
return;
}
UDPHeader = (PUDP_HEADER)IPPacket->Data;
i = UDPv4ChecksumCalculate(IPv4Header, (PUCHAR)UDPHeader,WH2N(UDPHeader->Length));
if (i != DH2N(0x0000FFFF) && UDPHeader->Checksum != 0)//校驗失敗,簡單丟棄
return;
i = WH2N(UDPHeader->Length);//i=udp報文總長
if ((i < sizeof(UDP_HEADER)) || (i > IPPacket->TotalSize - IPPacket->Position))
return;//錯誤報文簡單丟棄
DataSize = i - sizeof(UDP_HEADER);//負載部分的長度
IPPacket->Data = (PVOID)((ULONG_PTR)IPPacket->Data + sizeof(UDP_HEADER));//負載位置
AddrFile = AddrSearchFirst(DstAddress,UDPHeader->DestPort,IPPROTO_UDP,&SearchContext);
if (AddrFile)
{
do {
DGDeliverData(AddrFile,//投遞給目標socket
SrcAddress,DstAddress,
UDPHeader->SourcePort,UDPHeader->DestPort,
IPPacket,DataSize);
} while ((AddrFile = AddrSearchNext(&SearchContext)) != NULL);//查找下一個目標socket
}
}
如上,udp層是怎麼處理接收到的報文的呢?它先檢查校驗和,不正確的話就簡單丟包(所以,udp協議不可靠)。而後,將將這個udp報文投遞給全部符合的socket(一個DstAddr:DstPort可能對應多個socket)
DGDeliverData函數暫時就不看了。
總結一下:每當網卡收到一個包後的處理流程爲:isr->dpc->工做項->各協議層的接收處理函數
下面咱們看IP報文的發送過程:
NTSTATUS IPSendDatagram(PIP_PACKET IPPacket,//完整ip報文
PNEIGHBOR_CACHE_ENTRY NCE,//根據路由表得出的目標鄰接點
PIP_TRANSMIT_COMPLETE Complete, PVOID Context)//完成例程
{
//超出MTU將分紅片斷髮出去
return SendFragments(IPPacket, NCE, NCE->Interface->MTU,Complete, Context);
}
NTSTATUS SendFragments(
PIP_PACKET IPPacket,//完整IP報文
PNEIGHBOR_CACHE_ENTRY NCE,//目標鄰接點
UINT PathMTU,//MTU
PIP_TRANSMIT_COMPLETE Complete,
PVOID Context)
{
PIPFRAGMENT_CONTEXT IFC;
NDIS_STATUS NdisStatus;
PVOID Data;
UINT BufferSize = PathMTU, InSize;
PCHAR InData;
GetDataPtr( IPPacket->NdisPacket, 0, &InData, &InSize );
if( InSize < BufferSize ) BufferSize = InSize;//分割成一個最大爲MTU片斷包
//IFC就表示一個片斷包的發送上下文
IFC = ExAllocatePoolWithTag(NonPagedPool, sizeof(IPFRAGMENT_CONTEXT), IFC_TAG);
//NdisPacket就是一個片斷包
NdisStatus = AllocatePacketWithBuffer ( &IFC->NdisPacket, NULL, BufferSize );
GetDataPtr( IFC->NdisPacket, 0, (PCHAR *)&Data, &InSize );
IFC->Header = ((PCHAR)Data);
IFC->Datagram = IPPacket->NdisPacket;//所屬完整包
IFC->DatagramData = ((PCHAR)IPPacket->Header) + IPPacket->HeaderSize;
IFC->HeaderSize = IPPacket->HeaderSize;
IFC->PathMTU = PathMTU;
IFC->NCE = NCE;//目標鄰接點
IFC->Position = 0;
IFC->BytesLeft = IPPacket->TotalSize - IPPacket->HeaderSize;
IFC->Data = (PVOID)((ULONG_PTR)IFC->Header + IPPacket->HeaderSize);
IFC->Complete = Complete;
IFC->Context = Context;
RtlCopyMemory( IFC->Header, IPPacket->Header, IPPacket->HeaderSize );
PrepareNextFragment(IFC));
NdisStatus = IPSendFragment(IFC->NdisPacket, NCE, IFC);//將片斷包發給指定鄰接點
return NdisStatus;
}
NTSTATUS IPSendFragment(
PNDIS_PACKET NdisPacket,
PNEIGHBOR_CACHE_ENTRY NCE,
PIPFRAGMENT_CONTEXT IFC)
{
return NBQueuePacket(NCE, NdisPacket, IPSendComplete, IFC);//掛入指定鄰接點的發送隊列
}
接着看:
BOOLEAN NBQueuePacket(
PNEIGHBOR_CACHE_ENTRY NCE,
PNDIS_PACKET NdisPacket,//片斷包
PNEIGHBOR_PACKET_COMPLETE PacketComplete,
PVOID PacketContext)
{
KIRQL OldIrql;
PNEIGHBOR_PACKET Packet;
UINT HashValue;
//鄰接點發送隊列中的包結構
Packet = ExAllocatePoolWithTag( NonPagedPool, sizeof(NEIGHBOR_PACKET),NEIGHBOR_PACKET_TAG );
HashValue = *(PULONG)(&NCE->Address.Address);
HashValue ^= HashValue >> 16;
HashValue ^= HashValue >> 8;
HashValue ^= HashValue >> 4;
HashValue &= NB_HASHMASK;
TcpipAcquireSpinLock(&NeighborCache[HashValue].Lock, &OldIrql);
Packet->Complete = PacketComplete;
Packet->Context = PacketContext;
Packet->Packet = NdisPacket;//片斷包
InsertTailList( &NCE->PacketQueue, &Packet->Next );//掛入隊列
TcpipReleaseSpinLock(&NeighborCache[HashValue].Lock, OldIrql);
if( !(NCE->State & NUD_INCOMPLETE) )
NBSendPackets( NCE );//當即調用下層小端口驅動提供的發送函數進行發送
return TRUE;
}
VOID NBSendPackets( PNEIGHBOR_CACHE_ENTRY NCE )
{
PLIST_ENTRY PacketEntry;
PNEIGHBOR_PACKET Packet;
UINT HashValue;
HashValue = *(PULONG)(&NCE->Address.Address);
HashValue ^= HashValue >> 16;
HashValue ^= HashValue >> 8;
HashValue ^= HashValue >> 4;
HashValue &= NB_HASHMASK;
//發送隊列中全部待發包
while ((PacketEntry = ExInterlockedRemoveHeadList(&NCE->PacketQueue,
&NeighborCache[HashValue].Lock)) != NULL)
{
Packet = CONTAINING_RECORD( PacketEntry, NEIGHBOR_PACKET, Next );
PC(Packet->Packet)->DLComplete = NBCompleteSend;
PC(Packet->Packet)->Context = Packet;
NCE->Interface->Transmit(
NCE->Interface->Context,//目標網卡
Packet->Packet,//片斷包
0,
NCE->LinkAddress,//目標鄰接點的MAC地址
LAN_PROTO_IPv4 );
}
}
實際上,Transmit最終調用了小端口驅動自身提供的發送函數,將包給網卡。至於小端口驅動是怎麼發出去的?通常小端口驅動會檢查網卡芯片內部的硬件發送緩衝區是否空閒,如果,就當即寫入硬件發送緩衝區中,不然,小端口驅動內部也維護一個發送隊列,將暫時不能發的包儲存在那個隊列中。
鄰接點是什麼?鄰接點就是發往目標機器的中途路徑上的一臺機器,通常就是默認網關。若是本機裝了多個網卡,tcpip就會自動根據主機的路由表選擇路由,將幀經過合適的網卡發給合適的鄰接點。
Tcpip.sys內部建立了好幾個設備對象,應用程序能夠直接打開那些設備收發報文,不過,並非經過IRM_MJ_READ、IRP_MJ_WRITE來收發報文的,而是經過IRP_MJ_INTERNAL_DEVICE_CONTROL進行。可是這樣很麻煩,很差控制,應用程序通常藉助socket來收發報文(驅動型木馬每每直接使用IRP_MJ_INTERNAL_DEVICE_CONTROL來收發報文悄悄進行網絡通訊)。
Windows中的socket機制不一樣於unix,Windows中,socket api並非系統調用,它的實現機制分爲用戶空間和內核空間。用戶空間即是ws2_32.dll,內核空間即是afd.sys這個‘通用socket驅動’。爲何說是通用的呢?由於socket分爲好幾種socket:tcpip、ipx、netbios、AppleTalk等等。Afd驅動下層能夠搭配任意協議驅動,只要那個協議驅動對afd提供tdi接口便可。在Windows中,各類協議驅動又叫服務提供者,afd驅動又叫服務使用者。下面咱們看afd的DriverEntry。
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
PDEVICE_OBJECT DeviceObject;
UNICODE_STRING wstrDeviceName = RTL_CONSTANT_STRING(L"\\Device\\Afd");
PAFD_DEVICE_EXTENSION DeviceExt;
NTSTATUS Status;
//均爲AfdDispatch
DriverObject->MajorFunction[IRP_MJ_CLOSE] = AfdDispatch;
DriverObject->MajorFunction[IRP_MJ_CREATE] = AfdDispatch;
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = AfdDispatch;
DriverObject->MajorFunction[IRP_MJ_WRITE] = AfdDispatch;
DriverObject->MajorFunction[IRP_MJ_READ] = AfdDispatch;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = AfdDispatch;
DriverObject->DriverUnload = AfdUnload;
//建立了一個afd套接字驅動設備對象
Status = IoCreateDevice( DriverObject,sizeof(AFD_DEVICE_EXTENSION),&wstrDeviceName,
FILE_DEVICE_NAMED_PIPE,0,FALSE,&DeviceObject );
DeviceExt = DeviceObject->DeviceExtension;
KeInitializeSpinLock( &DeviceExt->Lock );
InitializeListHead( &DeviceExt->Polls );
return (Status);
}
Socket api內部轉換成socket irp發給afd設備,看看afd驅動是如何處理各類socket irp的
NTSTATUS AfdDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS Status = STATUS_NOT_IMPLEMENTED;
Irp->IoStatus.Information = 0;
switch(IrpSp->MajorFunction)
{
case IRP_MJ_CREATE:
return AfdCreateSocket(DeviceObject, Irp, IrpSp);
case IRP_MJ_CLOSE:
return AfdCloseSocket(DeviceObject, Irp, IrpSp);
case IRP_MJ_CLEANUP:
return AfdCleanupSocket(DeviceObject, Irp, IrpSp);
case IRP_MJ_WRITE:
return AfdConnectedSocketWriteData( DeviceObject, Irp, IrpSp, TRUE );
case IRP_MJ_READ:
return AfdConnectedSocketReadData( DeviceObject, Irp, IrpSp, TRUE );
case IRP_MJ_DEVICE_CONTROL:
{
switch( IrpSp->Parameters.DeviceIoControl.IoControlCode ) {
case IOCTL_AFD_BIND:
return AfdBindSocket( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_CONNECT:
return AfdStreamSocketConnect( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_START_LISTEN:
return AfdListenSocket( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_RECV:
return AfdConnectedSocketReadData( DeviceObject, Irp, IrpSp,FALSE );
case IOCTL_AFD_SELECT:
return AfdSelect( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_EVENT_SELECT:
return AfdEventSelect( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_ENUM_NETWORK_EVENTS:
return AfdEnumEvents( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_RECV_DATAGRAM:
return AfdPacketSocketReadData( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_SEND:
return AfdConnectedSocketWriteData( DeviceObject, Irp, IrpSp,FALSE );
case IOCTL_AFD_SEND_DATAGRAM:
return AfdPacketSocketWriteData( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_GET_INFO:
return AfdGetInfo( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_SET_INFO:
return AfdSetInfo( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_GET_CONTEXT_SIZE:
return AfdGetContextSize( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_GET_CONTEXT:
return AfdGetContext( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_SET_CONTEXT:
return AfdSetContext( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_WAIT_FOR_LISTEN:
return AfdWaitForListen( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_ACCEPT:
return AfdAccept( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_DISCONNECT:
return AfdDisconnect( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_GET_SOCK_NAME:
return AfdGetSockName( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_GET_PEER_NAME:
return AfdGetPeerName( DeviceObject, Irp, IrpSp );
case IOCTL_AFD_GET_CONNECT_DATA:
return AfdGetConnectData(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_SET_CONNECT_DATA:
return AfdSetConnectData(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_SET_DISCONNECT_DATA:
return AfdSetDisconnectData(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_GET_DISCONNECT_DATA:
return AfdGetDisconnectData(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_SET_CONNECT_DATA_SIZE:
return AfdSetConnectDataSize(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_SET_DISCONNECT_DATA_SIZE:
return AfdSetDisconnectDataSize(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_SET_CONNECT_OPTIONS:
return AfdSetConnectOptions(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_SET_DISCONNECT_OPTIONS:
return AfdSetDisconnectOptions(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_GET_CONNECT_OPTIONS:
return AfdGetConnectOptions(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_GET_DISCONNECT_OPTIONS:
return AfdGetDisconnectOptions(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_SET_CONNECT_OPTIONS_SIZE:
return AfdSetConnectOptionsSize(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_SET_DISCONNECT_OPTIONS_SIZE:
return AfdSetDisconnectOptionsSize(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_GET_TDI_HANDLES:
return AfdGetTdiHandles(DeviceObject, Irp, IrpSp);
case IOCTL_AFD_DEFER_ACCEPT:
DbgPrint("IOCTL_AFD_DEFER_ACCEPT is UNIMPLEMENTED!\n");
break;
case IOCTL_AFD_GET_PENDING_CONNECT_DATA:
DbgPrint("IOCTL_AFD_GET_PENDING_CONNECT_DATA is UNIMPLEMENTED!\n");
break;
case IOCTL_AFD_VALIDATE_GROUP:
DbgPrint("IOCTL_AFD_VALIDATE_GROUP is UNIMPLEMENTED!\n");
break;
default:
Status = STATUS_NOT_SUPPORTED;
break;
}
break;
}
default:
{
Status = STATUS_NOT_IMPLEMENTED;
break;
}
}
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return (Status);
}
實際上,應用程序能夠直接打開這個套接字驅動設備對象進行通訊,可是應用程序不多這樣作,由於不方便。爲此,微軟爲afd套接字驅動提供了用戶空間匹配的模塊ws2_32.dll,經過socket api 來間接打開afd設備與afd驅動進行交互。Socket api除了方便外,另外一個好處即是兼容unix,可移植。咱們看下ws2_32.dll的DllMain
BOOL
DllMain(HANDLE hInstDll,
ULONG dwReason,
LPVOID lpReserved)
{
PWINSOCK_THREAD_BLOCK p;
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
{
GlobalHeap = GetProcessHeap();
g_hInstDll = hInstDll;
CreateCatalog();
InitProviderHandleTable();//初始化提供者的處理函數表
UpcallTable.lpWPUCloseEvent = WPUCloseEvent;
UpcallTable.lpWPUCloseSocketHandle = WPUCloseSocketHandle;
UpcallTable.lpWPUCreateEvent = WPUCreateEvent;
UpcallTable.lpWPUCreateSocketHandle = WPUCreateSocketHandle;
UpcallTable.lpWPUFDIsSet = WPUFDIsSet;
UpcallTable.lpWPUGetProviderPath = WPUGetProviderPath;
UpcallTable.lpWPUModifyIFSHandle = WPUModifyIFSHandle;
UpcallTable.lpWPUPostMessage = PostMessageW;
UpcallTable.lpWPUQueryBlockingCallback = WPUQueryBlockingCallback;
UpcallTable.lpWPUQuerySocketHandleContext = WPUQuerySocketHandleContext;
UpcallTable.lpWPUQueueApc = WPUQueueApc;
UpcallTable.lpWPUResetEvent = WPUResetEvent;
UpcallTable.lpWPUSetEvent = WPUSetEvent;
UpcallTable.lpWPUOpenCurrentThread = WPUOpenCurrentThread;
UpcallTable.lpWPUCloseThread = WPUCloseThread;
}
case DLL_THREAD_ATTACH://重點
{
p = HeapAlloc(GlobalHeap, 0, sizeof(WINSOCK_THREAD_BLOCK));
p->Hostent = NULL;
p->LastErrorValue = NO_ERROR;//每一個線程的socket LastError
p->Getservbyname = NULL;
p->Getservbyport = NULL;
NtCurrentTeb()->WinSockData = p;//每一個線程有一個socket信息塊
}
break;
case DLL_PROCESS_DETACH:
{
DestroyCatalog();
FreeProviderHandleTable();
}
break;
case DLL_THREAD_DETACH:
{
p = NtCurrentTeb()->WinSockData;
if (p)
HeapFree(GlobalHeap, 0, p);
}
break;
}
return TRUE;
}
Socket的建立:
SOCKET
socket(IN INT af,//家族
IN INT type,//類型(報式/流式)
IN INT protocol)//協議
{
return WSASocketW(af,type,protocol,NULL,0,0);
}
SOCKET
WSASocketW(IN INT af,IN INT type,IN INT protocol,
IN LPWSAPROTOCOL_INFOW lpProtocolInfo,IN GROUP g,IN DWORD dwFlags)
{
INT Status;
SOCKET Socket;
PCATALOG_ENTRY Provider;
WSAPROTOCOL_INFOW ProtocolInfo;
if (!WSAINITIALIZED)
{
WSASetLastError(WSANOTINITIALISED);
return INVALID_SOCKET;
}
if (!lpProtocolInfo)
{
lpProtocolInfo = &ProtocolInfo;
ZeroMemory(&ProtocolInfo, sizeof(WSAPROTOCOL_INFOW));
ProtocolInfo.iAddressFamily = af;
ProtocolInfo.iSocketType = type;
ProtocolInfo.iProtocol = protocol;
}
Provider = LocateProvider(lpProtocolInfo);//查找相應的服務提供者
if (!Provider)
{
WSASetLastError(WSAEAFNOSUPPORT);
return INVALID_SOCKET;
}
Status = LoadProvider(Provider, lpProtocolInfo);//加載服務提供者
//調用相應提供者的套接字建立函數,tcpip的是WSPSocket函數
Socket = Provider->ProcTable.lpWSPSocket(af,type,protocol,lpProtocolInfo,
g,dwFlags,&Status);
return Socket;//返回套接字句柄
}
SOCKET
WSPSocket(int AddressFamily,int SocketType,int Protocol,
LPWSAPROTOCOL_INFOW lpProtocolInfo,GROUP g,
DWORD dwFlags,LPINT lpErrno)
{
PSOCKET_INFORMATION Socket = NULL;
PFILE_FULL_EA_INFORMATION EABuffer = NULL;
//根據該套接字的家族、類型、協議 匹配決定出要使用哪一種下層協議驅動和傳輸層設備對象,返回到TransportName參數中
Status = SockGetTdiName (&AddressFamily,&SocketType,&Protocol,g,dwFlags,
&TransportName,//OUT
&HelperDLLContext,// OUT
&HelperData,//OUT
&HelperEvents);//OUT
RtlInitUnicodeString(&DevName, L"\\Device\\Afd\\Endpoint");//端點管理設備
Socket = HeapAlloc(GlobalHeap, 0, sizeof(*Socket));//socket信息,將會加入全局鏈表進行維護
RtlZeroMemory(Socket, sizeof(*Socket));
Socket->RefCount = 2;
Socket->Handle = -1;//無效句柄
Socket->SharedData.Listening = FALSE;
Socket->SharedData.State = SocketOpen;
Socket->SharedData.AddressFamily = AddressFamily;
Socket->SharedData.SocketType = SocketType;
Socket->SharedData.Protocol = Protocol;
Socket->HelperContext = HelperDLLContext;
Socket->HelperData = HelperData;
Socket->HelperEvents = HelperEvents;
Socket->LocalAddress = &Socket->WSLocalAddress;
Socket->SharedData.SizeOfLocalAddress = HelperData->MaxWSAddressLength;
Socket->RemoteAddress = &Socket->WSRemoteAddress;
Socket->SharedData.SizeOfRemoteAddress = HelperData->MaxWSAddressLength;
Socket->SharedData.UseDelayedAcceptance = HelperData->UseDelayedAcceptance;
Socket->SharedData.CreateFlags = dwFlags;
Socket->SharedData.CatalogEntryId = lpProtocolInfo->dwCatalogEntryId;
Socket->SharedData.ServiceFlags1 = lpProtocolInfo->dwServiceFlags1;
Socket->SharedData.ProviderFlags = lpProtocolInfo->dwProviderFlags;
Socket->SharedData.GroupID = g;
Socket->SharedData.GroupType = 0;
Socket->SharedData.UseSAN = FALSE;
Socket->SharedData.NonBlocking = FALSE;//默認爲阻塞方式
Socket->SanData = NULL;
if( Socket->SharedData.SocketType == SOCK_DGRAM ||
Socket->SharedData.SocketType == SOCK_RAW )
{
Socket->SharedData.ServiceFlags1 |= XP1_CONNECTIONLESS;
}
SizeOfPacket = TransportName.Length + sizeof(AFD_CREATE_PACKET) + sizeof(WCHAR);
SizeOfEA = sizeof(FILE_FULL_EA_INFORMATION) + AFD_PACKET_COMMAND_LENGTH + SizeOfPacket;
EABuffer = HeapAlloc(GlobalHeap, 0, SizeOfEA);//EA附加屬性就是一個AFD_CREATE_PACKET結構體
RtlZeroMemory(EABuffer, SizeOfEA);
EABuffer->NextEntryOffset = 0;
EABuffer->Flags = 0;
EABuffer->EaNameLength = AFD_PACKET_COMMAND_LENGTH;
RtlCopyMemory (EABuffer->EaName, AfdCommand, AFD_PACKET_COMMAND_LENGTH + 1);
EABuffer->EaValueLength = SizeOfPacket;
AfdPacket = (PAFD_CREATE_PACKET)(EABuffer->EaName + EABuffer->EaNameLength + 1);
AfdPacket->SizeOfTransportName = TransportName.Length;
//記錄該套接字下層使用的傳輸層設備對象名
RtlCopyMemory (AfdPacket->TransportName,TransportName.Buffer,
TransportName.Length + sizeof(WCHAR));
AfdPacket->GroupID = g;
if ((Socket->SharedData.ServiceFlags1 & XP1_CONNECTIONLESS) != 0)
{
if ((SocketType != SOCK_DGRAM) && (SocketType != SOCK_RAW))
goto error;
AfdPacket->EndpointFlags |= AFD_ENDPOINT_CONNECTIONLESS;
}
if ((Socket->SharedData.ServiceFlags1 & XP1_MESSAGE_ORIENTED) != 0)
{
if (SocketType == SOCK_STREAM)
{
if ((Socket->SharedData.ServiceFlags1 & XP1_PSEUDO_STREAM) == 0)
goto error;
}
AfdPacket->EndpointFlags |= AFD_ENDPOINT_MESSAGE_ORIENTED;
}
if (SocketType == SOCK_RAW) AfdPacket->EndpointFlags |= AFD_ENDPOINT_RAW;
InitializeObjectAttributes (&Object,&DevName,OBJ_CASE_INSENSITIVE | OBJ_INHERIT,0,0);
//關鍵。打開afd驅動中的設備,建立一個套接字文件對象,返回套接字句柄到Sock參數中
Status = NtCreateFile(&Sock,GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,&Object,
&IOSB,NULL,0,FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_OPEN_IF,0,EABuffer,SizeOfEA);
HeapFree(GlobalHeap, 0, EABuffer);
Socket->Handle = (SOCKET)Sock;//記錄句柄
if (g != 0) …
//即FCB->Send.Size:該套接字的UDP發送緩衝區大小,默認爲16384B
GetSocketInformation (Socket,AFD_INFO_SEND_WINDOW_SIZE,
&Socket->SharedData.SizeOfSendBuffer,NULL);
//即FCB->Recv.Size:該套接字的UDP接收緩衝區大小,默認爲16384B
GetSocketInformation (Socket,AFD_INFO_RECEIVE_WINDOW_SIZE,
&Socket->SharedData.SizeOfRecvBuffer,NULL);
EnterCriticalSection(&SocketListLock);
Socket->NextSocket = SocketListHead;
SocketListHead = Socket;//將新建立的套接字加入全局鏈表
LeaveCriticalSection(&SocketListLock);
CreateContext(Socket);
Upcalls.lpWPUModifyIFSHandle(1, (SOCKET)Sock, lpErrno);
return (SOCKET)Sock;//返回套接字句柄
error:…
}
實際上,上面的函數會調用NtCreateFile打開設備,建立一個套接字文件對象,而後返回該文件對象的句柄(即套接字句柄)給用戶。NtCreateFile內部在IopParseDevice中會建立一個文件對象,而後,會打開目標afd設備,生成IRP_MJ_CREATE發給目標設備,最終進入AfdDispatch這個派遣例程中處理該irp,前面看到,具體處理這種IRP的是下面的函數
NTSTATUS
AfdCreateSocket(PDEVICE_OBJECT DeviceObject, PIRP Irp,PIO_STACK_LOCATION IrpSp)
{
PAFD_FCB FCB;
PFILE_OBJECT FileObject;
PAFD_DEVICE_EXTENSION DeviceExt;
PFILE_FULL_EA_INFORMATION EaInfo;
PAFD_CREATE_PACKET ConnectInfo = NULL;
ULONG EaLength;
PWCHAR EaInfoValue = NULL;
UINT Disposition, i;
NTSTATUS Status = STATUS_SUCCESS;
DeviceExt = DeviceObject->DeviceExtension;
FileObject = IrpSp->FileObject;
Disposition = (IrpSp->Parameters.Create.Options >> 24) & 0xff;
Irp->IoStatus.Information = 0;
//IRP_MJ_CREATE這種irp的SystemBuffer就是EA附加屬性
EaInfo = Irp->AssociatedIrp.SystemBuffer;
if( EaInfo )
{
ConnectInfo = (PAFD_CREATE_PACKET)(EaInfo->EaName + EaInfo->EaNameLength + 1);
EaInfoValue = (PWCHAR)(((PCHAR)ConnectInfo) + sizeof(AFD_CREATE_PACKET));
EaLength = sizeof(FILE_FULL_EA_INFORMATION) +EaInfo->EaNameLength +EaInfo->EaValueLength;
}
//分配一個socket FCB,用來記錄socket文件對象信息
FCB = ExAllocatePool(NonPagedPool, sizeof(AFD_FCB));
RtlZeroMemory( FCB, sizeof( *FCB ) );
FileObject->FsContext = FCB;//關鍵。該文件對象的FCB指向這個socket FCB
FCB->Flags = ConnectInfo ? ConnectInfo->EndpointFlags : 0;
FCB->GroupID = ConnectInfo ? ConnectInfo->GroupID : 0;
FCB->State = SOCKET_STATE_CREATED;
FCB->FileObject = FileObject;//關聯文件對象
FCB->DeviceExt = DeviceExt;//關聯的afd設備對象
FCB->AddressFile.Handle = INVALID_HANDLE_VALUE;//本套接字綁定的本地地址
FCB->Connection.Handle = INVALID_HANDLE_VALUE;
KeInitializeMutex( &FCB->Mutex, 0 );
for( i = 0; i < 6; i++ )
InitializeListHead( &FCB->PendingIrpList[i] );//關聯6個irp隊列(用於異步模式)
InitializeListHead( &FCB->DatagramList );//初始化udp收包隊列
InitializeListHead( &FCB->PendingConnections );//初始化tcp收包隊列
if( ConnectInfo ) {
FCB->TdiDeviceName.Length = ConnectInfo->SizeOfTransportName;
FCB->TdiDeviceName.MaximumLength = FCB->TdiDeviceName.Length;
FCB->TdiDeviceName.Buffer = ExAllocatePool( NonPagedPool, FCB->TdiDeviceName.Length );
RtlCopyMemory( FCB->TdiDeviceName.Buffer,ConnectInfo->TransportName,
FCB->TdiDeviceName.Length );
}
if( FCB->Flags & AFD_ENDPOINT_CONNECTIONLESS ) {
FCB->PollState |= AFD_EVENT_SEND;//套接字狀態爲:可發送
FCB->PollStatus[FD_WRITE_BIT] = STATUS_SUCCESS;
PollReeval( FCB->DeviceExt, FCB->FileObject );
}
if( !NT_SUCCESS(Status) ) 。。。
Irp->IoStatus.Status = Status;
IoCompleteRequest( Irp, IO_NETWORK_INCREMENT );
return Status;
}
如上,每當建立一個套接字的時候,就爲其準備一個irp隊列、udp收包隊列、tcp收包隊列,分配關聯一個FCB記錄其餘方面的套接字信息。
Afd相關的概念有:套接字驅動、套接字設備、套接字文件、套接字FCB
套接字驅動:afd.sys
套接字設備:\Device\Afd\Endpoint
套接字文件對象:每打開一次套接字設備生成一個套接字文件對象
套接字FCB:每一個套接字文件對象關聯的FCB
套接字建立完畢後,還須要綁定IP地址與端口號。注意套接字是afd驅動中的概念和術語,傳輸層並無這種說法,傳輸層中對應會建立一個地址對象,來表示afd中的socket。Afd中的socket綁定的就是傳輸層中的地址對象。傳輸層中有一個地址對象列表,維護記錄着全部建立的地址對象。下面看一下套接字的綁定過程。「創綁地址文件」。(一個地址文件就表明一個地址對象)
INT
bind(IN SOCKET s,
IN CONST struct sockaddr *name,
IN INT namelen)
{
PCATALOG_ENTRY Provider;
INT Status;
INT Errno;
if (!WSAINITIALIZED)
{
WSASetLastError(WSANOTINITIALISED);
return SOCKET_ERROR;
}
//得到該套接字使用的服務提供者
ReferenceProviderByHandle((HANDLE)s,&Provider);
// lpWSPBind在tcpip下其實是WSPBind函數
Status = Provider->ProcTable.lpWSPBind(s,name,namelen,&Errno);
DereferenceProviderByPointer(Provider);
if (Status == SOCKET_ERROR)
WSASetLastError(Errno);
return Status;
}
INT
WSPBind(SOCKET Handle,
const struct sockaddr *SocketAddress,
int SocketAddressLength,
LPINT lpErrno)
{
IO_STATUS_BLOCK IOSB;
PAFD_BIND_DATA BindData;
PSOCKET_INFORMATION Socket = NULL;
NTSTATUS Status;
SOCKADDR_INFO SocketInfo;
HANDLE SockEvent;
BindData = HeapAlloc(GlobalHeap, 0, 0xA + SocketAddressLength);
Status = NtCreateEvent(&SockEvent,GENERIC_READ | GENERIC_WRITE,NULL,1,FALSE);
Socket = GetSocketStructure(Handle);//根據套接字句柄查找socket結構
BindData->Address.TAAddressCount = 1;
BindData->Address.Address[0].AddressLength = SocketAddressLength - sizeof(SocketAddress->sa_family);
BindData->Address.Address[0].AddressType = SocketAddress->sa_family;
RtlCopyMemory (BindData->Address.Address[0].Address, SocketAddress->sa_data,
SocketAddressLength - sizeof(SocketAddress->sa_family));
Socket->HelperData->WSHGetSockaddrType ((PSOCKADDR)SocketAddress,
SocketAddressLength,&SocketInfo);
if (Socket->SharedData.ExclusiveAddressUse)
BindData->ShareType = AFD_SHARE_EXCLUSIVE;
else if (SocketInfo.EndpointInfo == SockaddrEndpointInfoWildcard)
BindData->ShareType = AFD_SHARE_WILDCARD;
else if (Socket->SharedData.ReuseAddresses)
BindData->ShareType = AFD_SHARE_REUSE;
else
BindData->ShareType = AFD_SHARE_UNIQUE;
//向afd中的套接字設備發送一個‘綁定請求’irp
Status = NtDeviceIoControlFile((HANDLE)Socket->Handle,SockEvent,NULL,NULL,&IOSB,
IOCTL_AFD_BIND,BindData,
0xA + Socket->SharedData.SizeOfLocalAddress, BindData,
0xA + Socket->SharedData.SizeOfLocalAddress);
if (Status == STATUS_PENDING)
{
WaitForSingleObject(SockEvent, INFINITE);
Status = IOSB.Status;
}
NtClose( SockEvent );
HeapFree(GlobalHeap, 0, BindData);
if (Status != STATUS_SUCCESS)
return MsafdReturnWithErrno ( Status, lpErrno, 0, NULL );
Socket->SharedData.State = SocketBound;//已完成綁定
Socket->TdiAddressHandle = (HANDLE)IOSB.Information;
if (Socket->HelperEvents & WSH_NOTIFY_BIND)
{
Status = Socket->HelperData->WSHNotify(Socket->HelperContext,Socket->Handle,
Socket->TdiAddressHandle,
Socket->TdiConnectionHandle,
WSH_NOTIFY_BIND);
if (Status)
{
if (lpErrno) *lpErrno = Status;
return SOCKET_ERROR;
}
}
return MsafdReturnWithErrno ( Status, lpErrno, 0, NULL );
}
看看afd驅動是如何處理綁定請求的
NTSTATUS
AfdBindSocket(PDEVICE_OBJECT DeviceObject, PIRP Irp,PIO_STACK_LOCATION IrpSp)
{
NTSTATUS Status = STATUS_SUCCESS;
PFILE_OBJECT FileObject = IrpSp->FileObject;//地址文件對象
PAFD_FCB FCB = FileObject->FsContext;//套接字FCB
PAFD_BIND_DATA BindReq;
if( !SocketAcquireStateLock( FCB ) ) return LostSocket( Irp );
if( !(BindReq = LockRequest( Irp, IrpSp )) )
return UnlockAndMaybeComplete( FCB, STATUS_NO_MEMORY,Irp, 0 );
FCB->LocalAddress = TaCopyTransportAddress( &BindReq->Address );//記錄該套接字的本地地址
//初始鏈接向自身,使得send操做進行環回,發給本身
Status = TdiBuildConnectionInfo( &FCB->AddressFrom,FCB->LocalAddress );
if( NT_SUCCESS(Status) ) //關鍵。在下層的傳輸層驅動中爲本套接字‘創綁一個地址文件’
Status = WarmSocketForBind( FCB ); //建立、綁定 一個地址文件
//if UDP 套接字,當即向傳輸層設備投遞一個接收請求。(爲何要這樣作?後文有解釋)
if( FCB->Flags & AFD_ENDPOINT_CONNECTIONLESS )
{
Status = TdiReceiveDatagram
( &FCB->ReceiveIrp.InFlightRequest,FCB->AddressFile.Object,0,
FCB->Recv.Window,FCB->Recv.Size,//Window表示UDP接收緩衝區
FCB->AddressFrom,&FCB->ReceiveIrp.Iosb,PacketSocketRecvComplete,FCB );
if( Status == STATUS_PENDING ) Status = STATUS_SUCCESS;
}
if (NT_SUCCESS(Status))
FCB->State = SOCKET_STATE_BOUND;//標記已完成綁定
return UnlockAndMaybeComplete( FCB, Status, Irp, (ULONG_PTR)FCB->AddressFile.Handle );
}
如上,上面最關鍵的操做即是在下層的傳輸層驅動中建立一個地址對象,而後讓afd驅動中的套接字與傳輸層驅動中的這個地址對象進行綁定。具體是由下面的函數完成的。
NTSTATUS WarmSocketForBind( PAFD_FCB FCB ) //套接字FCB
{
NTSTATUS Status;
//在傳輸層建立一個地址對象進行綁定
Status = TdiOpenAddressFile(&FCB->TdiDeviceName,//目標下層傳輸層設備 tcp\udp\RawIP之一
FCB->LocalAddress,//要綁定的目標地址
&FCB->AddressFile.Handle,//返回綁定的地址文件句柄
&FCB->AddressFile.Object );//返回綁定的地址文件對象
if (!NT_SUCCESS(Status))
return Status;
if (FCB->Flags & AFD_ENDPOINT_CONNECTIONLESS)
{ //查詢那種傳輸層協議支持的最大udp報文長度
TdiQueryMaxDatagramLength(FCB->AddressFile.Object,&FCB->Recv.Size);
FCB->Recv.Window = ExAllocatePool(PagedPool, FCB->Recv.Size);//分配udp接收緩衝區
}
return Status;
}
因爲udp協議是面向報文的,是以報文爲單位進行收發的,因此要接收完整的udp報文就必須分配足夠大的接收緩衝區。
NTSTATUS TdiOpenAddressFile(
PUNICODE_STRING DeviceName,//傳輸層的tdi設備名
PTRANSPORT_ADDRESS Name,//要綁定的地址
PHANDLE AddressHandle,//返回地址文件句柄
PFILE_OBJECT *AddressObject) 返回地址文件句柄
{
PFILE_FULL_EA_INFORMATION EaInfo;
NTSTATUS Status;
ULONG EaLength;
PTRANSPORT_ADDRESS Address;
EaLength = sizeof(FILE_FULL_EA_INFORMATION) +TDI_TRANSPORT_ADDRESS_LENGTH +
TaLengthOfTransportAddress( Name ) + 1;
EaInfo = (PFILE_FULL_EA_INFORMATION)ExAllocatePool(NonPagedPool, EaLength);
RtlZeroMemory(EaInfo, EaLength);
EaInfo->EaNameLength = TDI_TRANSPORT_ADDRESS_LENGTH;
RtlCopyMemory(EaInfo->EaName,TdiTransportAddress,TDI_TRANSPORT_ADDRESS_LENGTH);
EaInfo->EaValueLength = sizeof(TA_IP_ADDRESS);
Address = (PTRANSPORT_ADDRESS)(EaInfo->EaName + TDI_TRANSPORT_ADDRESS_LENGTH + 1);
TaCopyTransportAddressInPlace( Address, Name );
//關鍵。打開對應傳輸層的tdi設備,建立一個地址文件對象,並記錄到套接字FCB中進行綁定
Status = TdiOpenDevice(DeviceName,EaLength,EaInfo,AddressHandle,AddressObject);
ExFreePool(EaInfo);
return Status;
}
NTSTATUS TdiOpenDevice(
PUNICODE_STRING DeviceName,
ULONG EaLength,
PFILE_FULL_EA_INFORMATION EaInfo,
PHANDLE Handle,//返回地址文件句柄
PFILE_OBJECT *Object)//返回地址文件對象
{
OBJECT_ATTRIBUTES Attr;
IO_STATUS_BLOCK Iosb;
NTSTATUS Status;
InitializeObjectAttributes(&Attr, DeviceName,OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,NULL);
//關鍵。打開對應的傳輸層設備,生成一個地址文件對象
Status = ZwCreateFile(Handle,GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
&Attr,&Iosb,0,FILE_ATTRIBUTE_NORMAL,0,FILE_OPEN_IF,0,
EaInfo,EaLength);
if (NT_SUCCESS(Status))
{
Status = ObReferenceObjectByHandle(*Handle,GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, IoFileObjectType,KernelMode, (PVOID*)Object,NULL);
}
return Status;
}
ZwCreateFile將給傳輸層的設備發送一個IRP_MJ_CREATE,程序流從afd.sys驅動進入tcpip.sys傳輸層驅動中的irp派遣函數中。看看下面傳輸層是如何處理這種irp請求的(建立、綁定地址對象請求)
NTSTATUS TiCreateFileObject(PDEVICE_OBJECT DeviceObject,PIRP Irp)
{
EaInfo = Irp->AssociatedIrp.SystemBuffer;
Context = ExAllocatePoolWithTag(NonPagedPool, sizeof(TRANSPORT_CONTEXT),TRANS_CONTEXT_TAG);
Context->CancelIrps = FALSE;
IrpSp = IoGetCurrentIrpStackLocation(Irp);
IrpSp->FileObject->FsContext = Context;
Request.RequestContext = Irp;
//if 是afd層發下來的‘創綁地址文件對象請求’
if (EaInfo && (EaInfo->EaNameLength == TDI_TRANSPORT_ADDRESS_LENGTH) &&
(RtlCompareMemory (&EaInfo->EaName, TdiTransportAddress,
TDI_TRANSPORT_ADDRESS_LENGTH) == TDI_TRANSPORT_ADDRESS_LENGTH))
{
Address = (PTA_IP_ADDRESS)(EaInfo->EaName + EaInfo->EaNameLength + 1);
if (DeviceObject == TCPDeviceObject)
Protocol = IPPROTO_TCP;
else if (DeviceObject == UDPDeviceObject)
Protocol = IPPROTO_UDP;
else if (DeviceObject == IPDeviceObject)
Protocol = IPPROTO_RAW;
else if (DeviceObject == RawIPDeviceObject)
Status = TiGetProtocolNumber(&IrpSp->FileObject->FileName, &Protocol);
else
{
ExFreePoolWithTag(Context, TRANS_CONTEXT_TAG);
return STATUS_INVALID_PARAMETER;
}
//關鍵。建立一個傳輸層的地址對象,返回到Request參數中
Status = FileOpenAddress(&Request, Address, Protocol, NULL);
if (NT_SUCCESS(Status))
{ // FsContext2標記是Context->Handle是一個傳輸層地址
IrpSp->FileObject->FsContext2 = (PVOID)TDI_TRANSPORT_ADDRESS_FILE;
Context->Handle.AddressHandle = Request.Handle.AddressHandle;//記錄到地址文件對象的FCB
}
}
//afd層發下來鏈接請求
else if (EaInfo && (EaInfo->EaNameLength == TDI_CONNECTION_CONTEXT_LENGTH) &&
(RtlCompareMemory (&EaInfo->EaName, TdiConnectionContext,
TDI_CONNECTION_CONTEXT_LENGTH) == TDI_CONNECTION_CONTEXT_LENGTH))
{
if (DeviceObject != TCPDeviceObject)
{
ExFreePoolWithTag(Context, TRANS_CONTEXT_TAG);
return STATUS_INVALID_PARAMETER;
}
ClientContext = *((PVOID*)(EaInfo->EaName + EaInfo->EaNameLength));
Status = FileOpenConnection(&Request, ClientContext);
if (NT_SUCCESS(Status))
{
IrpSp->FileObject->FsContext2 = (PVOID)TDI_CONNECTION_FILE;
Context->Handle.ConnectionContext = Request.Handle.ConnectionContext;
}
}
else …
Irp->IoStatus.Status = Status;
return Status;
}
NTSTATUS FileOpenAddress(
PTDI_REQUEST Request,//OUT
PTA_IP_ADDRESS Address,//IP地址:端口號
USHORT Protocol,//TCP/UDP/RawIp
PVOID Options)
{
PADDRESS_FILE AddrFile;
//最關鍵、爲每一個afd中的socket在傳輸層建立一個地址對象與其綁定
AddrFile = ExAllocatePoolWithTag(NonPagedPool, sizeof(ADDRESS_FILE),ADDR_FILE_TAG);
RtlZeroMemory(AddrFile, sizeof(ADDRESS_FILE));
AddrFile->RefCount = 1;
AddrFile->Free = AddrFileFree;
AddrFile->TTL = 128;
AddrFile->DF = 0;
AddrFile->BCast = 1;
AddrFile->HeaderIncl = 1;
AddrFile->Family = Address->Address[0].AddressType;
AddrFile->Address.Address.IPv4Address = Address->Address[0].Address[0].in_addr;
AddrFile->Address.Type = IP_ADDRESS_V4;
//若是用戶給定了一個非法IP地址(全部網卡均不符合)
if (!AddrIsUnspecified(&AddrFile->Address) && !AddrLocateInterface(&AddrFile->Address))
{
ExFreePoolWithTag(AddrFile, ADDR_FILE_TAG);
return STATUS_INVALID_ADDRESS;
}
switch (Protocol)
{
case IPPROTO_TCP:
//檢測端口衝突、分配空閒的tcp端口號
AddrFile->Port = TCPAllocatePort(Address->Address[0].Address[0].sin_port);
if ((Address->Address[0].Address[0].sin_port &&
AddrFile->Port != Address->Address[0].Address[0].sin_port) ||
AddrFile->Port == 0xffff)
{
ExFreePoolWithTag(AddrFile, ADDR_FILE_TAG);
return STATUS_ADDRESS_ALREADY_EXISTS;
}
AddEntity(CO_TL_ENTITY, AddrFile, CO_TL_TCP);
AddrFile->Send = NULL; /* TCPSendData */
break;
case IPPROTO_UDP:
//檢測端口衝突、分配空閒的udp端口號
AddrFile->Port = UDPAllocatePort(Address->Address[0].Address[0].sin_port);
if ((Address->Address[0].Address[0].sin_port &&
AddrFile->Port != Address->Address[0].Address[0].sin_port) ||
AddrFile->Port == 0xffff)
{
ExFreePoolWithTag(AddrFile, ADDR_FILE_TAG);
return STATUS_ADDRESS_ALREADY_EXISTS;
}
AddEntity(CL_TL_ENTITY, AddrFile, CL_TL_UDP);
AddrFile->Send = UDPSendDatagram;
break;
case IPPROTO_ICMP:
AddrFile->Port = 0;
AddrFile->Send = ICMPSendDatagram;
AddEntity(ER_ENTITY, AddrFile, ER_ICMP);
break;
default:
/* Use raw IP for all other protocols */
AddrFile->Port = 0;
AddrFile->Send = RawIPSendDatagram;
AddEntity(CL_TL_ENTITY, AddrFile, 0);
break;
}
AddrFile->Protocol = Protocol;
InitializeListHead(&AddrFile->ReceiveQueue);//每一個地址對象有一個接收irp請求隊列
InitializeListHead(&AddrFile->TransmitQueue); //每一個地址對象有一個發送irp請求隊列
KeInitializeSpinLock(&AddrFile->Lock);
Request->Handle.AddressHandle = AddrFile;//返回建立的地址對象給用戶
//關鍵。將建立好的地址對象加入tcpip內部維護的地址對象列表。這樣,當收到一個報文時,tcpip就能根據頭部的協議、目標地址、目標端口號找打對應的地址對象,而後將報文上交給afd中全部綁定了這個地址對象的套接字。
ExInterlockedInsertTailList(&AddressFileListHead,&AddrFile->ListEntry,&AddressFileListLock);
return STATUS_SUCCESS;
}
看看UDP是如何檢測、分配端口號的
UINT UDPAllocatePort( UINT HintPort )
{
if( HintPort ) //若是用戶指定了端口號,就檢測衝突
{
if( AllocatePort( &UDPPorts, HintPort ) ) return HintPort;//if 空閒
else return (UINT)-1;
}
//若是用戶指定了端口號爲0,就自動分配一個空閒未用的端口號
else return AllocatePortFromRange ( &UDPPorts, UDP_STARTING_PORT,
UDP_STARTING_PORT + UDP_DYNAMIC_PORTS );
}
看了這麼多,總結一下套接字的綁定過程其實是將Afd中建立的套接字文件對象 綁定到 tcpip傳輸層驅動中建立的地址對象。實際上,咱們能夠簡單理解爲:afd.套接字 綁定 傳輸層中的地址
其本質是經過afd給傳輸層設備發送一個「創綁地址文件對象請求」實現的。
一個udp套接字在綁定了傳輸層地址後,就能夠開始收發數據報了,不用事先創建鏈接.咱們看看udp報文的發送過程
INT
sendto(IN SOCKET s,
IN CONST CHAR FAR* buf,
IN INT len,
IN INT flags,
IN CONST struct sockaddr *to,//目標ip:port
IN INT tolen)
{
DWORD Error;
DWORD BytesSent;
WSABUF WSABuf;
WSABuf.len = len;
WSABuf.buf = buf;
Error = WSASendTo(s,&WSABuf,1,&BytesSent,flags,to,tolen,NULL,NULL);
if( Error )
return -1;//即SOCKET_ERROR
else
return BytesSent;
}
INT
WSASendTo(IN SOCKET s,
IN LPWSABUF lpBuffers,
IN DWORD dwBufferCount,
OUT LPDWORD lpNumberOfBytesSent,
IN DWORD dwFlags,
IN CONST struct sockaddr *lpTo,
IN INT iToLen,
IN LPWSAOVERLAPPED lpOverlapped,
IN LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
{
PCATALOG_ENTRY Provider;
INT Errno;
INT Code;
ReferenceProviderByHandle((HANDLE)s, &Provider);//得到服務提供者
// lpWSPSendTo實際上是WSPSendto
Code = Provider->ProcTable.lpWSPSendTo(s,lpBuffers,dwBufferCount,lpNumberOfBytesSent,
dwFlags, (CONST LPSOCKADDR)lpTo,iToLen,
lpOverlapped,lpCompletionRoutine,NULL&Errno);
DereferenceProviderByPointer(Provider);
if (Code == SOCKET_ERROR)
WSASetLastError(Errno);
else
WSASetLastError(0);
return Code;
}
int
WSPSendTo(SOCKET Handle,
LPWSABUF lpBuffers,//緩衝數組
DWORD dwBufferCount,//緩衝個數
LPDWORD lpNumberOfBytesSent,//OUT
DWORD iFlags,
const struct sockaddr *SocketAddress,//目標ip:port
int SocketAddressLength,
LPWSAOVERLAPPED lpOverlapped,//NULL就表示同步模式
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine,//APC方式
LPWSATHREADID lpThreadId,
LPINT lpErrno)
{
HANDLE Event = NULL;
PSOCKADDR BindAddress = NULL;
Socket = GetSocketStructure(Handle);
if (Socket->SharedData.State == SocketOpen)//if 還沒有綁定,就綁定在通派地址上(即全0地址)
{
BindAddressLength = Socket->HelperData->MaxWSAddressLength;
BindAddress = HeapAlloc(GlobalHeap, 0, BindAddressLength);
//即全0地址
Socket->HelperData->WSHGetWildcardSockaddr(Socket->HelperContext,
BindAddress,&BindAddressLength);
if (WSPBind(Handle, BindAddress, BindAddressLength, lpErrno) == SOCKET_ERROR)
return SOCKET_ERROR;
}
RemoteAddress = HeapAlloc(GlobalHeap, 0, 0x6 + SocketAddressLength);
Status = NtCreateEvent(&SockEvent,GENERIC_READ | GENERIC_WRITE,NULL, 1, FALSE);
//將目標地址封裝成TDI格式
RemoteAddress->TAAddressCount = 1;
RemoteAddress->Address[0].AddressLength = SocketAddressLength - sizeof(SocketAddress->sa_family);
RtlCopyMemory(&RemoteAddress->Address[0].AddressType, SocketAddress, SocketAddressLength);
SendInfo.BufferArray = (PAFD_WSABUF)lpBuffers;
SendInfo.AfdFlags = Socket->SharedData.NonBlocking ? AFD_IMMEDIATE : 0;
SendInfo.BufferCount = dwBufferCount;
SendInfo.TdiConnection.RemoteAddress = RemoteAddress;
SendInfo.TdiConnection.RemoteAddressLength = Socket->HelperData->MaxTDIAddressLength;
if (lpOverlapped == NULL)//if NULL,就表示同步模式發送數據包
{
APCContext = NULL;
APCFunction = NULL;
Event = SockEvent;//使用內部事件
IOSB = &DummyIOSB;
}
else
{
if (lpCompletionRoutine == NULL)//重疊模式發送數據包
{
APCContext = lpOverlapped;
APCFunction = NULL;
Event = lpOverlapped->hEvent;//看到沒,使用重疊結構中用戶傳入的事件對象
}
else
{
APCFunction = NULL;
APCContext = lpCompletionRoutine;//這個完成例程其實是一個APC
SendInfo.AfdFlags |= AFD_SKIP_FIO;
}
IOSB = (PIO_STATUS_BLOCK)&lpOverlapped->Internal;//採用這個IO狀態塊
SendInfo.AfdFlags |= AFD_OVERLAPPED;//標誌含有重疊結構,使用異步方式
}
//關鍵。向afd中的套接字設備發送一個irp,請求發送udp報文
Status = NtDeviceIoControlFile((HANDLE)Handle,//套接字句柄
Event,APCFunction,APCContext,IOSB,
IOCTL_AFD_SEND_DATAGRAM,//控制碼
&SendInfo,sizeof(SendInfo),NULL,0);
//若是用戶要求同步方式發送,就一直等待完成
if (Status == STATUS_PENDING && lpOverlapped == NULL)
{
WaitForSingleObject(SockEvent, INFINITE);
Status = IOSB->Status;
}
if (Status != STATUS_PENDING)
SockReenableAsyncSelectEvent(Socket, FD_WRITE);
return MsafdReturnWithErrno(Status, lpErrno, IOSB->Information, lpNumberOfBytesSent);
}
看看afd驅動是如何處理應用層發下來的udp報文發送請求這種irp的
NTSTATUS
AfdPacketSocketWriteData(PDEVICE_OBJECT DeviceObject, PIRP Irp,PIO_STACK_LOCATION IrpSp)
{
NTSTATUS Status = STATUS_SUCCESS;
PTDI_CONNECTION_INFORMATION TargetAddress;
PFILE_OBJECT FileObject = IrpSp->FileObject;
PAFD_FCB FCB = FileObject->FsContext;
PAFD_SEND_INFO_UDP SendReq;
ULONG Information;
if( !SocketAcquireStateLock( FCB ) ) return LostSocket( Irp );
if( FCB->State != SOCKET_STATE_BOUND )
return UnlockAndMaybeComplete ( FCB, STATUS_INVALID_PARAMETER, Irp, 0 );
if( !(SendReq = LockRequest( Irp, IrpSp )) )
return UnlockAndMaybeComplete ( FCB, STATUS_NO_MEMORY, Irp, 0 );
SendReq->BufferArray = LockBuffers( SendReq->BufferArray,SendReq->BufferCount,
NULL, NULL,FALSE, FALSE );
if( !SendReq->BufferArray )
return UnlockAndMaybeComplete( FCB, STATUS_ACCESS_VIOLATION,Irp, 0 );
Status = TdiBuildConnectionInfo( &TargetAddress, SendReq->TdiConnection.RemoteAddress );
if( NT_SUCCESS(Status) )
{
FCB->PollState &= ~AFD_EVENT_SEND;
//關鍵。構造一個tdi irp發往傳輸層設備,請求發送udp報文,返回傳輸層的處理結果
Status = TdiSendDatagram
( &FCB->SendIrp.InFlightRequest,
FCB->AddressFile.Object,//綁定的地址文件
SendReq->BufferArray[0].buf,SendReq->BufferArray[0].len,
TargetAddress,//目標IP:PORT
&FCB->SendIrp.Iosb, PacketSocketSendComplete, FCB );
ExFreePool( TargetAddress );
}
if( Status == STATUS_PENDING )
Status = STATUS_SUCCESS;//即便傳輸層還沒有完成處理,也標誌成功?不明白
//下面的操做直接就完成了該 socket irp,致使永遠不可能返回STATUS_PENDING,也即永遠不支持異步方式,這彷佛有問題!
Information = SendReq->BufferArray[0].len;
UnlockBuffers(SendReq->BufferArray, SendReq->BufferCount, FALSE);
return UnlockAndMaybeComplete( FCB, Status, Irp, Information );
}
如上,afd驅動處理udp報文發送請求irp時,僅僅是將該irp轉換成tdi irp,發給下層的傳輸層驅動而已。
NTSTATUS TdiSendDatagram(
PIRP *Irp,//OUT
PFILE_OBJECT TransportObject,//傳輸層的地址文件對象
PCHAR Buffer,
UINT BufferLength,
PTDI_CONNECTION_INFORMATION Addr,//目標地址
PIO_STATUS_BLOCK Iosb,
PIO_COMPLETION_ROUTINE CompletionRoutine,
PVOID CompletionContext)
{
PDEVICE_OBJECT DeviceObject;
NTSTATUS Status;
PMDL Mdl;
DeviceObject = IoGetRelatedDeviceObject(TransportObject);//TCP/UDP/RawIp三者之一
*Irp = TdiBuildInternalDeviceControlIrp
( TDI_SEND_DATAGRAM,DeviceObject,TransportObject,NULL, Iosb );
Mdl = IoAllocateMdl(Buffer,BufferLength,FALSEFALSE,NULL);
_SEH2_TRY {
MmProbeAndLockPages(Mdl, (*Irp)->RequestorMode, IoModifyAccess);//鎖定在內存
} _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
IoFreeMdl(Mdl);
IoCompleteRequest(*Irp, IO_NO_INCREMENT);
*Irp = NULL;
_SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES);
} _SEH2_END;
//構造一個報文發送請求irp
TdiBuildSendDatagram (*Irp,DeviceObject,TransportObject,
CompletionRoutine,CompletionContext,Mdl,BufferLength,Addr);
Status = TdiCall(*Irp, DeviceObject, NULL, Iosb);//將tdi irp發給下層傳輸層設備
return Status;
}
如上,afd層會將發下來的socket irp 轉換成tdi irp 發給下層的傳輸層驅動。
NTSTATUS TdiCall(PIRP Irp,PDEVICE_OBJECT DeviceObject,PKEVENT Event,PIO_STATUS_BLOCK Iosb)
{
NTSTATUS Status;
Status = IoCallDriver(DeviceObject, Irp);
if ((Status == STATUS_PENDING) && (Event != NULL)) //此例Event傳入的爲NULL
{
KeWaitForSingleObject(Event,Executive,KernelMode,FALSE,NULL);
Status = Iosb->Status;
}
return Status;//返回傳輸層的處理結果
}
下面看看傳輸層處理這種irp的過程
NTSTATUS DispTdiSendDatagram(PIRP Irp) //傳輸層處理udp報文發送請求irp的函數
{
PIO_STACK_LOCATION IrpSp;
TDI_REQUEST Request;
PTDI_REQUEST_KERNEL_SENDDG DgramInfo;
PTRANSPORT_CONTEXT TranContext;
NTSTATUS Status;
IrpSp = IoGetCurrentIrpStackLocation(Irp);
DgramInfo = (PTDI_REQUEST_KERNEL_SENDDG)&(IrpSp->Parameters);
TranContext = IrpSp->FileObject->FsContext;//傳輸層的地址描述符
Request.Handle.AddressHandle = TranContext->Handle.AddressHandle;//地址對象句柄
Request.RequestNotifyObject = DispDataRequestComplete;
Request.RequestContext = Irp;
Status = DispPrepareIrpForCancel(IrpSp->FileObject->FsContext,
Irp, (PDRIVER_CANCEL)DispCancelRequest);
if (NT_SUCCESS(Status))
{
PVOID DataBuffer;
UINT BufferSize;
//查詢緩衝的地址、長度
NdisQueryBuffer( (PNDIS_BUFFER)Irp->MdlAddress,&DataBuffer,&BufferSize );
if( (*((PADDRESS_FILE)Request.Handle.AddressHandle)->Send != NULL) )
{
ULONG DataUsed = 0;
//Send其實是UDPSendDatagram函數。這個函數會自動根據目標地址選擇合適的本地網卡,將udp報文發給合適的鄰接點去
Status = (*((PADDRESS_FILE)Request.Handle.AddressHandle)->Send)(
Request.Handle.AddressHandle,//源地址
DgramInfo->SendDatagramInformation,//目標地址
DataBuffer,BufferSize,&DataUsed);
Irp->IoStatus.Information = DataUsed;
}
else Status = STATUS_UNSUCCESSFUL;
}
done:
if (Status != STATUS_PENDING)
DispDataRequestComplete(Irp, Status, Irp->IoStatus.Information);
else
IoMarkIrpPending(Irp);
return Status;
}
下面是UDP協議處理udp報文發送請求irp的函數
NTSTATUS UDPSendDatagram(
PADDRESS_FILE AddrFile,//源地址
PTDI_CONNECTION_INFORMATION ConnInfo,//目標地址
PCHAR BufferData,
ULONG DataSize,
PULONG DataUsed )
{
IP_PACKET Packet;
PTA_IP_ADDRESS RemoteAddressTa = (PTA_IP_ADDRESS)ConnInfo->RemoteAddress;
IP_ADDRESS RemoteAddress;
IP_ADDRESS LocalAddress;
USHORT RemotePort;
NTSTATUS Status;
PNEIGHBOR_CACHE_ENTRY NCE;
KIRQL OldIrql;
LockObject(AddrFile, &OldIrql);
switch( RemoteAddressTa->Address[0].AddressType ) {
case TDI_ADDRESS_TYPE_IP:
RemoteAddress.Type = IP_ADDRESS_V4;
RemoteAddress.Address.IPv4Address = RemoteAddressTa->Address[0].Address[0].in_addr;
RemotePort = RemoteAddressTa->Address[0].Address[0].sin_port;
break;
default:
UnlockObject(AddrFile, OldIrql);
return STATUS_UNSUCCESSFUL;
}
LocalAddress = AddrFile->Address;
if (AddrIsUnspecified(&LocalAddress))//若是沒有指定源地址(即全0的通配地址,很常見)
{
//就根據目標地址和路由表自動算出要將該包發往哪個鄰接點(決定了鄰接點,就決定了經由網卡)
NCE = RouteGetRouteToDestination( &RemoteAddress );
LocalAddress = NCE->Interface->Unicast;//該網卡的IP地址(單播地址)
}
Else 。。。//若是用戶指定了源地址(也即顯式指定了從某塊網卡發出去,這種狀況不多見)
//構造一個UDP報文(也即加上一個UDP頭部)
Status = BuildUDPPacket( AddrFile,&Packet,&RemoteAddress,RemotePort,
&LocalAddress,AddrFile->Port,BufferData,DataSize );
UnlockObject(AddrFile, OldIrql);
//調用IPSendDatagram將該UDP報文發給指定鄰接點(經由指定的本地網卡),這個函數前面看過,它將報文掛入指定鄰接點的發包隊列中,時機成熟後再由網卡的小端口驅動提供的發送函數將報文寫入網卡內部的硬件發送緩衝區中完成發送。
if (!NT_SUCCESS(Status = IPSendDatagram( &Packet, NCE, UDPSendPacketComplete, NULL )))
{
FreeNdisPacket(Packet.NdisPacket);
return Status;
}
return STATUS_SUCCESS;
}
鄰接點是什麼意思呢?咱們知道,目標機器可能很遠很遠,也可能與主機位於同一局域網中。主機發包時不可能直接發送給遠程機器,中間通過的第一個路由器就是鄰接點。而主機能夠安裝多塊網卡,分屬多個不一樣的局域網,經過多個路由器(網關)連向Internet。所以,發包時,必須算出咱們的直接鄰接點,首先將包(其實是幀)發給它。當目標機器就是同局域網內的另一臺機器時,主機就能夠直接發給他,不經由路由器,那麼,目標機器就是鄰接點。當目標機器很遠很遠不在同一局域網時,就必須選擇一個路由器將報文轉發出去,這個路由器此時就是咱們的鄰接點。總之,凡是主機直接相連(指可直接到達)的那些計算機、路由器都叫鄰接點。
typedef struct NEIGHBOR_CACHE_ENTRY { //鄰接點描述符
struct NEIGHBOR_CACHE_ENTRY *Next; //下一個
UCHAR State; //鄰接點狀態
UINT EventTimer; /* Ticks since last event */
UINT EventCount; /* Number of events */
PIP_INTERFACE Interface; //關鍵。主機通往該鄰接點的經由網卡
UINT LinkAddressLength; //通常爲6B
PVOID LinkAddress; //該鄰接點的MAC地址
IP_ADDRESS Address; //該鄰接點的IP地址
LIST_ENTRY PacketQueue; //該鄰接點的發包隊列(至關於網卡的發包隊列)
} NEIGHBOR_CACHE_ENTRY, *PNEIGHBOR_CACHE_ENTRY;
上面的結構就是ARP協議的基礎,緩衝記錄了局域網內每一個鄰接點的IP地址與MAC地址映射狀況。
理解了udp報文的發送過程,再看一下udp報文的接收過程。RecvFrom這個API最終進入下面的函數
int
WSPRecvFrom(SOCKET Handle,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRead,
LPDWORD ReceiveFlags,
struct sockaddr *SocketAddress,
int *SocketAddressLength,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine,
LPWSATHREADID lpThreadId,
LPINT lpErrno )
{
HANDLE Event = NULL;
Socket = GetSocketStructure(Handle);
Status = NtCreateEvent( &SockEvent, GENERIC_READ | GENERIC_WRITE,NULL, 1, FALSE );
RecvInfo.BufferArray = (PAFD_WSABUF)lpBuffers;
RecvInfo.BufferCount = dwBufferCount;
RecvInfo.TdiFlags = 0;
RecvInfo.AfdFlags = Socket->SharedData.NonBlocking ? AFD_IMMEDIATE : 0;
RecvInfo.AddressLength = SocketAddressLength;
RecvInfo.Address = SocketAddress;
if (*ReceiveFlags == 0)
RecvInfo.TdiFlags |= TDI_RECEIVE_NORMAL;
else
{
if (*ReceiveFlags & MSG_OOB)
RecvInfo.TdiFlags |= TDI_RECEIVE_EXPEDITED;
if (*ReceiveFlags & MSG_PEEK)
RecvInfo.TdiFlags |= TDI_RECEIVE_PEEK;
if (*ReceiveFlags & MSG_PARTIAL)//是否容許截斷接收,用於UDP報文的接收標誌
RecvInfo.TdiFlags |= TDI_RECEIVE_PARTIAL;
}
if (lpOverlapped == NULL)
{
APCContext = NULL;
APCFunction = NULL;
Event = SockEvent;
IOSB = &DummyIOSB;
}
else
{
if (lpCompletionRoutine == NULL)
{
APCContext = lpOverlapped;
APCFunction = NULL;
Event = lpOverlapped->hEvent;
}
else
{
APCFunction = NULL;
APCContext = lpCompletionRoutine;
RecvInfo.AfdFlags |= AFD_SKIP_FIO;
}
IOSB = (PIO_STATUS_BLOCK)&lpOverlapped->Internal;
RecvInfo.AfdFlags |= AFD_OVERLAPPED;
}
IOSB->Status = STATUS_PENDING;
//向套接字設備發送‘接收請求’這種socket irp
Status = NtDeviceIoControlFile((HANDLE)Handle,Event,APCFunction,APCContext,IOSB,
IOCTL_AFD_RECV_DATAGRAM,//接收UDP數據報
&RecvInfo,sizeof(RecvInfo),NULL,0);
if (Status == STATUS_PENDING && lpOverlapped == NULL)
{
WaitForSingleObject(SockEvent, INFINITE);
Status = IOSB->Status;
}
NtClose( SockEvent );
*ReceiveFlags = 0;
switch (Status)
{
case STATUS_RECEIVE_EXPEDITED: *ReceiveFlags = MSG_OOB;
break;
case STATUS_RECEIVE_PARTIAL_EXPEDITED:
*ReceiveFlags = MSG_PARTIAL | MSG_OOB;
break;
case STATUS_RECEIVE_PARTIAL:
*ReceiveFlags = MSG_PARTIAL;
break;
}
/* Re-enable Async Event */
SockReenableAsyncSelectEvent(Socket, FD_READ);
return MsafdReturnWithErrno ( Status, lpErrno, IOSB->Information, lpNumberOfBytesRead );
}
看看afd驅動是如何處理‘接收請求’irp的
NTSTATUS
AfdPacketSocketReadData(PDEVICE_OBJECT DeviceObject, PIRP Irp,PIO_STACK_LOCATION IrpSp )
{
NTSTATUS Status = STATUS_SUCCESS;
PFILE_OBJECT FileObject = IrpSp->FileObject;
PAFD_FCB FCB = FileObject->FsContext;
PAFD_RECV_INFO_UDP RecvReq;
PLIST_ENTRY ListEntry;
PAFD_STORED_DATAGRAM DatagramRecv;
if( !SocketAcquireStateLock( FCB ) ) return LostSocket( Irp );
if( FCB->State != SOCKET_STATE_BOUND )
return UnlockAndMaybeComplete( FCB, STATUS_INVALID_PARAMETER, Irp, 0 );
if( !(RecvReq = LockRequest( Irp, IrpSp )) )
return UnlockAndMaybeComplete( FCB, STATUS_NO_MEMORY, Irp, 0 );
RecvReq->BufferArray = LockBuffers( RecvReq->BufferArray,RecvReq->BufferCount,
RecvReq->Address,RecvReq->AddressLength,TRUE, TRUE );
//若是這個套接字的接包隊列中有現成的包(最好不過)
if( !IsListEmpty( &FCB->DatagramList ) )
{
ListEntry = RemoveHeadList( &FCB->DatagramList );//將包摘下來
DatagramRecv = CONTAINING_RECORD( ListEntry, AFD_STORED_DATAGRAM, ListEntry );
if( DatagramRecv->Len > RecvReq->BufferArray[0].len &&
!(RecvReq->TdiFlags & TDI_RECEIVE_PARTIAL) ) //if 用戶不許截斷接收
{
InsertHeadList( &FCB->DatagramList,&DatagramRecv->ListEntry );//掛回去
Status = Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
Irp->IoStatus.Information = DatagramRecv->Len;
if( !IsListEmpty( &FCB->DatagramList ) )
{
FCB->PollState |= AFD_EVENT_RECEIVE;//標記可接收
FCB->PollStatus[FD_READ_BIT] = STATUS_SUCCESS;
PollReeval( FCB->DeviceExt, FCB->FileObject );
}
else FCB->PollState &= ~AFD_EVENT_RECEIVE;
UnlockBuffers( RecvReq->BufferArray, RecvReq->BufferCount, TRUE );
return UnlockAndMaybeComplete( FCB, Status, Irp, Irp->IoStatus.Information );
}
else //若是用戶提供的接收緩衝區夠大 或者 用戶准許截斷接收
{
Status = SatisfyPacketRecvRequest( FCB, Irp, DatagramRecv,
(PUINT)&Irp->IoStatus.Information );
if( !IsListEmpty( &FCB->DatagramList ) )
{
FCB->PollState |= AFD_EVENT_RECEIVE;
FCB->PollStatus[FD_READ_BIT] = STATUS_SUCCESS;
PollReeval( FCB->DeviceExt, FCB->FileObject );
}
else FCB->PollState &= ~AFD_EVENT_RECEIVE;
UnlockBuffers( RecvReq->BufferArray, RecvReq->BufferCount, TRUE );
return UnlockAndMaybeComplete( FCB, Status, Irp, Irp->IoStatus.Information );
}
}
//若是當時沒有包
else if( RecvReq->AfdFlags & AFD_IMMEDIATE ) //if能夠當即失敗完成返回
{
Status = STATUS_CANT_WAIT;
FCB->PollState &= ~AFD_EVENT_RECEIVE;
UnlockBuffers( RecvReq->BufferArray, RecvReq->BufferCount, TRUE );
return UnlockAndMaybeComplete( FCB, Status, Irp, 0 );
}
else
{
FCB->PollState &= ~AFD_EVENT_RECEIVE;
return LeaveIrpUntilLater( FCB, Irp, FUNCTION_RECV );//掛入該套接字的irp隊列
}
}
//若是當時不能知足irp,就掛入隊列
NTSTATUS LeaveIrpUntilLater( PAFD_FCB FCB, PIRP Irp, UINT Function )
{
InsertTailList( &FCB->PendingIrpList[Function],&Irp->Tail.Overlay.ListEntry );//掛入隊列
IoMarkIrpPending(Irp);
(void)IoSetCancelRoutine(Irp, AfdCancelHandler);
SocketStateUnlock( FCB );
return STATUS_PENDING;
}
如上,當應用程序調用RecvFrom向套接字發出收包請求時,afd驅動會檢查那個套接字的收包隊列中是否有包,如有,就知足請求當即返回,不然,將irp掛入隊列等待之後完成該irp請求。那麼,何時該irp會知足完成呢?要想獲得這個問題的答案,回顧一下,當網卡收到包時,會觸發中斷進入小端口驅動的isr,最後小端口驅動會將包上交給各個綁定協議。當上交給tcpip時,tcpip會根據報文頭部的協議、目標ip地址、目標端口號找到全部符合條件的傳輸層地址對象,而後調用DGDeliverData函數將報文投遞給那些符合條件的地址對象。
VOID DGDeliverData( //將收到的報文投遞給目標地址文件
PADDRESS_FILE AddrFile,//符合條件的目標地址文件對象
PIP_ADDRESS SrcAddress,//報文頭部中的源地址
PIP_ADDRESS DstAddress, //報文頭部中的目標地址
USHORT SrcPort, //報文頭部中的源端口
USHORT DstPort, //報文頭部中的目標端口
PIP_PACKET IPPacket,//下層小端口提交上來的報文
UINT DataSize)//報文的長度
{
LockObject(AddrFile, &OldIrql);
if (AddrFile->Protocol == IPPROTO_UDP)
DataBuffer = IPPacket->Data;
else
{
if (AddrFile->HeaderIncl)
DataBuffer = IPPacket->Header;
else
{
DataBuffer = IPPacket->Data;
DataSize -= IPPacket->HeaderSize;
}
}
//若是該地址文件的接收請求隊列不空,將收到的包知足 給 接收請求
if (!IsListEmpty(&AddrFile->ReceiveQueue))
{
PLIST_ENTRY CurrentEntry;
PDATAGRAM_RECEIVE_REQUEST Current = NULL;
PTA_IP_ADDRESS RTAIPAddress;
CurrentEntry = AddrFile->ReceiveQueue.Flink;
while(CurrentEntry != &AddrFile->ReceiveQueue)
{
Current = CONTAINING_RECORD(CurrentEntry, DATAGRAM_RECEIVE_REQUEST, ListEntry);
CurrentEntry = CurrentEntry->Flink;
if( DstPort == AddrFile->Port && (AddrIsEqual(DstAddress, &AddrFile->Address) ||
AddrIsUnspecified(&AddrFile->Address) || AddrIsUnspecified(DstAddress)))
{
RemoveEntryList(&Current->ListEntry);//摘下一個請求給予知足
RtlCopyMemory( Current->Buffer,DataBuffer,MIN(Current->BufferSize, DataSize) );
RTAIPAddress = (PTA_IP_ADDRESS)Current->ReturnInfo->RemoteAddress;
RTAIPAddress->TAAddressCount = 1;
RTAIPAddress->Address->AddressType = TDI_ADDRESS_TYPE_IP;
RTAIPAddress->Address->Address->sin_port = SrcPort;
RtlCopyMemory( &RTAIPAddress->Address->Address->in_addr,
&SrcAddress->Address.IPv4Address,sizeof(SrcAddress->Address.IPv4Address) );
if (Current->BufferSize < DataSize)
Current->Complete(Current->Context,STATUS_BUFFER_OVERFLOW,Current->BufferSize);
Else //關鍵。調用那個接收請求的完成函數,以繼續發出一個接收請求。這樣,沒知足一個請求後,就當即再發出一個接收請求,從而使得傳輸層可以源源不斷收到接收請求。
Current->Complete(Current->Context, STATUS_SUCCESS, DataSize);
Break;//僅僅知足一個接收請求
}
}
}
else … //若是收到包的時候,那個地址對象沒有任何接收請求,就丟棄包,這就是爲何UDP協議是不可靠的,即便網絡線路100%不出故障,也會由於目標機器接收速度 搞不過 發送速度 而 丟包。
}
看到沒,傳輸層一收到udp報文,就會把報文知足給接收請求。每一個地址對象內部維護着一個接收請求隊列,當udp套接字一綁定地址對象時,就會立馬向傳輸層發出一個接收請求,掛入相應地址對象的接收請求隊列中,這就是第一個udp接收請求的產生時機(第一個接收請求是在AfdBindSocket函數內部調用TdiReceiveDatagram發出的)
回顧一下綁定過程:
AfdBindSocket(PDEVICE_OBJECT DeviceObject, PIRP Irp,PIO_STACK_LOCATION IrpSp)
{ 。。。
if( FCB->Flags & AFD_ENDPOINT_CONNECTIONLESS )
{
Status = TdiReceiveDatagram
( &FCB->ReceiveIrp.InFlightRequest,//InFlightRequest表示當前生成的irp,用於複用
FCB->AddressFile.Object,//要發往的目標傳輸層地址對象
0,//flags
FCB->Recv.Window,//udp接收緩衝區,這是一箇中間緩衝,用於複用
FCB->Recv.Size,//默認總爲16384B
FCB->AddressFrom, &FCB->ReceiveIrp.Iosb,
PacketSocketRecvComplete,FCB );//關鍵。完成例程
if( Status == STATUS_PENDING ) Status = STATUS_SUCCESS;
}
。。。
}
如上,剛一完成綁定,就當即向傳輸層對應的地址對象投遞一個接收請求
NTSTATUS TdiReceiveDatagram( //構造一個irp,發往傳輸層
PIRP *Irp,//生成一個要發往傳輸層的tdi irp
PFILE_OBJECT TransportObject,//傳輸層的地址文件對象
USHORT Flags,
PCHAR Buffer,//接收緩衝
UINT BufferLength,
PTDI_CONNECTION_INFORMATION Addr,//from地址
PIO_STATUS_BLOCK Iosb,
PIO_COMPLETION_ROUTINE CompletionRoutine,
PVOID CompletionContext)
{
PDEVICE_OBJECT DeviceObject;
NTSTATUS Status;
PMDL Mdl;
DeviceObject = IoGetRelatedDeviceObject(TransportObject);//\Device\Udp
*Irp = TdiBuildInternalDeviceControlIrp
( TDI_RECEIVE_DATAGRAM,DeviceObject,TransportObject,NULL,Iosb );
Mdl = IoAllocateMdl(Buffer,BufferLength,FALSE,FALSE,NULL);
_SEH2_TRY {
MmProbeAndLockPages(Mdl, (*Irp)->RequestorMode, IoModifyAccess);
} _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
IoFreeMdl(Mdl);
IoCompleteRequest(*Irp, IO_NO_INCREMENT);
*Irp = NULL;
_SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES);
} _SEH2_END;
TdiBuildReceiveDatagram (*Irp,DeviceObjectTransportObject,
CompletionRoutine,CompletionContext,
Mdl,BufferLength,Addr,Addr,Flags);
Status = TdiCall(*Irp, DeviceObject, NULL, Iosb);//將irp發往傳輸層
return Status;
}
看看傳輸層是如何處理afd層發下來的udp報文接收請求的
NTSTATUS DispTdiReceiveDatagram(PIRP Irp) //處理udp接收請求的函數
{
PIO_STACK_LOCATION IrpSp;
PTDI_REQUEST_KERNEL_RECEIVEDG DgramInfo;
PTRANSPORT_CONTEXT TranContext;
TDI_REQUEST Request;
NTSTATUS Status;
ULONG BytesReceived = 0;
IrpSp = IoGetCurrentIrpStackLocation(Irp);
DgramInfo = (PTDI_REQUEST_KERNEL_RECEIVEDG)&(IrpSp->Parameters);
TranContext = IrpSp->FileObject->FsContext;
/* Initialize a receive request */
Request.Handle.AddressHandle = TranContext->Handle.AddressHandle;
Request.RequestNotifyObject = DispDataRequestComplete;
Request.RequestContext = Irp;
Status = DispPrepareIrpForCancel(IrpSp->FileObject->FsContext,Irp,
(PDRIVER_CANCEL)DispCancelRequest);
if (NT_SUCCESS(Status))
{
PVOID DataBuffer;
UINT BufferSize;
NdisQueryBuffer( (PNDIS_BUFFER)Irp->MdlAddress,&DataBuffer,&BufferSize );
Status = DGReceiveDatagram(
Request.Handle.AddressHandle,//生成一個接收請求(非irp)掛入這個地址對象的請求隊列中
DgramInfo->ReceiveDatagramInformation,
DataBuffer,//其實是window
DgramInfo->ReceiveLength,//16384B
DgramInfo->ReceiveFlags,//0
DgramInfo->ReturnDatagramInformation,
&BytesReceived,
(PDATAGRAM_COMPLETION_ROUTINE)DispDataRequestComplete,
Irp,Irp);
}
done:
if (Status != STATUS_PENDING)
DispDataRequestComplete(Irp, Status, BytesReceived);
else
IoMarkIrpPending(Irp);
return Status;
}
實際的處理工做在DGReceiveDatagram函數中,咱們看
NTSTATUS DGReceiveDatagram(
PADDRESS_FILE AddrFile,//將生產的接收請求掛入這個地址對象的接收請求隊列中
PTDI_CONNECTION_INFORMATION ConnInfo,
PCHAR BufferData,
ULONG ReceiveLength,
ULONG ReceiveFlags,
PTDI_CONNECTION_INFORMATION ReturnInfo,
PULONG BytesReceived,
PDATAGRAM_COMPLETION_ROUTINE Complete,
PVOID Context,
PIRP Irp)
{
NTSTATUS Status;
PDATAGRAM_RECEIVE_REQUEST ReceiveRequest;
KIRQL OldIrql;
LockObject(AddrFile, &OldIrql);
//關鍵。分配構造一個接收請求(非irp)
ReceiveRequest = ExAllocatePoolWithTag(NonPagedPool, sizeof(DATAGRAM_RECEIVE_REQUEST));
if (ReceiveRequest)
{
if ((ConnInfo->RemoteAddressLength != 0) && (ConnInfo->RemoteAddress))
{
Status = AddrGetAddress(ConnInfo->RemoteAddress,
&ReceiveRequest->RemoteAddress, &ReceiveRequest->RemotePort);
}
else
{
ReceiveRequest->RemotePort = 0;
AddrInitIPv4(&ReceiveRequest->RemoteAddress, 0);
}
IoMarkIrpPending(Irp);
ReceiveRequest->ReturnInfo = ReturnInfo;
ReceiveRequest->Buffer = BufferData;
ReceiveRequest->BufferSize = ReceiveLength;
ReceiveRequest->UserComplete = Complete;//指DispDataRequestComplete
ReceiveRequest->UserContext = Context;//指 irp
ReceiveRequest->Complete = (PDATAGRAM_COMPLETION_ROUTINE)DGReceiveComplete;//完成函數
ReceiveRequest->Context = ReceiveRequest;
ReceiveRequest->AddressFile = AddrFile;
ReceiveRequest->Irp = Irp;
//關鍵,掛入目標地址對象的接收請求隊列
InsertTailList(&AddrFile->ReceiveQueue, &ReceiveRequest->ListEntry);
return STATUS_PENDING;
}
return Status;
}
前面咱們看到,每當知足一個接收請求後,會調用它的完成函數,以繼續發出接收請求。咱們看看那個完成函數是否是這樣作的。
VOID DGReceiveComplete(PVOID Context, NTSTATUS Status, ULONG Count)
{
PDATAGRAM_RECEIVE_REQUEST ReceiveRequest = Context;
//調用用戶設置的完成函數,即DispDataRequestComplete函數
ReceiveRequest->UserComplete( ReceiveRequest->UserContext, Status, Count );
ExFreePoolWithTag( ReceiveRequest, DATAGRAM_RECV_TAG );
}
VOID DispDataRequestComplete(
PVOID Context,
NTSTATUS Status,
ULONG Count)
{
PIRP Irp = Context;
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = Count;
IRPFinish(Irp, Status);
}
NTSTATUS IRPFinish( PIRP Irp, NTSTATUS Status )
{
KIRQL OldIrql;
Irp->IoStatus.Status = Status;
if( Status == STATUS_PENDING )
IoMarkIrpPending( Irp );
else {
IoAcquireCancelSpinLock(&OldIrql);
(void)IoSetCancelRoutine( Irp, NULL );
IoReleaseCancelSpinLock(OldIrql);
IoCompleteRequest( Irp, IO_NETWORK_INCREMENT );//關鍵。完成該irp
}
return Status;
}
在IoCompleteRequest中會調用最初設置的完成例程,即PacketSocketRecvComplete函數
NTSTATUS //每當完成了一個udp接收請求後調用執行這個函數
PacketSocketRecvComplete(PDEVICE_OBJECT DeviceObject,PIRP Irp,PVOID Context )
{
NTSTATUS Status = STATUS_SUCCESS;
PAFD_FCB FCB = Context;
PIRP NextIrp;
PIO_STACK_LOCATION NextIrpSp;
PLIST_ENTRY ListEntry;
PAFD_RECV_INFO RecvReq;
PAFD_STORED_DATAGRAM DatagramRecv;
UINT DGSize = Irp->IoStatus.Information + sizeof( AFD_STORED_DATAGRAM );
PLIST_ENTRY NextIrpEntry, DatagramRecvEntry;
if( !SocketAcquireStateLock( FCB ) ) return STATUS_FILE_CLOSED;
FCB->ReceiveIrp.InFlightRequest = NULL; //當前irp置空
if( FCB->State == SOCKET_STATE_CLOSED ) 。。。
//關鍵。分配一個AFD_STORED_DATAGRAM結構,即套接字接包隊列中的結構
DatagramRecv = ExAllocatePool( NonPagedPool, DGSize );
DatagramRecv->Len = Irp->IoStatus.Information;
//全部接下來的udp報文都臨時寄存在Window中,能夠看出這個Window這僅僅用做中轉
RtlCopyMemory( DatagramRecv->Buffer, FCB->Recv.Window,DatagramRecv->Len );
DatagramRecv->Address = TaCopyTransportAddress( FCB->AddressFrom->RemoteAddress );
//關鍵。將包掛入套接字的接包隊列中,這樣,當上層應用程序發出接收請求時就直接從這個隊列取出包來知足它。
InsertTailList( &FCB->DatagramList, &DatagramRecv->ListEntry );
//下面的循環,順帶檢查一下當前是否有irp在等候,如有,就當即知足它
while( !IsListEmpty( &FCB->DatagramList ) &&
!IsListEmpty( &FCB->PendingIrpList[FUNCTION_RECV] ) )
{
ListEntry = RemoveHeadList( &FCB->DatagramList );
DatagramRecv = CONTAINING_RECORD( ListEntry, AFD_STORED_DATAGRAM,ListEntry );
ListEntry = RemoveHeadList( &FCB->PendingIrpList[FUNCTION_RECV] );
NextIrp = CONTAINING_RECORD( ListEntry, IRP, Tail.Overlay.ListEntry );
NextIrpSp = IoGetCurrentIrpStackLocation( NextIrp );
RecvReq = NextIrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
if( DatagramRecv->Len > RecvReq->BufferArray[0].len &&
!(RecvReq->TdiFlags & TDI_RECEIVE_PARTIAL) )
{
InsertHeadList( &FCB->DatagramList,&DatagramRecv->ListEntry );//掛回去
Status = NextIrp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
NextIrp->IoStatus.Information = DatagramRecv->Len;
UnlockBuffers( RecvReq->BufferArray, RecvReq->BufferCount, TRUE );
if ( NextIrp->MdlAddress ) UnlockRequest( NextIrp,IoGetCurrentIrpStackLocation( NextIrp ) );
IoSetCancelRoutine(NextIrp, NULL);
IoCompleteRequest( NextIrp, IO_NETWORK_INCREMENT );
}
else
{
Status = SatisfyPacketRecvRequest ( FCB, NextIrp, DatagramRecv,
(PUINT)&NextIrp->IoStatus.Information );
UnlockBuffers( RecvReq->BufferArray, RecvReq->BufferCount, TRUE );
if ( NextIrp->MdlAddress )
UnlockRequest( NextIrp, IoGetCurrentIrpStackLocation( NextIrp ) );
IoCompleteRequest( NextIrp, IO_NETWORK_INCREMENT );
}
}
if( !IsListEmpty( &FCB->DatagramList ) )
{
FCB->PollState |= AFD_EVENT_RECEIVE;
FCB->PollStatus[FD_READ_BIT] = STATUS_SUCCESS;
PollReeval( FCB->DeviceExt, FCB->FileObject );
}
Else FCB->PollState &= ~AFD_EVENT_RECEIVE;
//關鍵。果真,又立馬向傳輸層發出一個接收請求。由於傳輸層收到包後,若發現當前沒有接收請求,就會丟包。因此必須在afd驅動層接收到一個udp包後,趕忙當即再向傳輸層發請求過去。
if( NT_SUCCESS(Irp->IoStatus.Status) )
{
Status = TdiReceiveDatagram
( &FCB->ReceiveIrp.InFlightRequest,
FCB->AddressFile.Object,
0,
FCB->Recv.Window,
FCB->Recv.Size,
FCB->AddressFrom,
&FCB->ReceiveIrp.Iosb,
PacketSocketRecvComplete,
FCB );
}
SocketStateUnlock( FCB );
return STATUS_SUCCESS;
}
總結一下協議驅動、小端口驅動之間的交互步驟:
一、 系統啓動時加載ndis.sys模塊,創建起ndis基礎運行環境
二、 安裝加載各類協議驅動,在DriverEntry中註冊協議特徵,即各類回調函數
三、 安裝網卡,加載小端口驅動,在DriverEntry中註冊小端口特徵,即各類回調函數
四、 進入小端口驅動的AddDevice,ndis自動爲咱們建立一個小端口設備對象,加入堆棧
五、 系統爲這個網卡分配端口、中斷號等資源
六、 啓動網卡設備,進入小端口驅動註冊的初始化例程
七、 在小端口驅動的初始化例程中:初始化硬件寄存器、註冊中斷向量、分配自定義設備擴展等典型工做
八、 網卡啓動初始化完畢後,ndis框架調用各協議驅動提供的綁定回調函數,通知綁定
九、 進入各協議驅動提供的綁定回調函數,咱們要調用NdisOpenAdapter打開網卡進行綁定
十、 Ndis框架調用綁定完成回調函數 或 咱們本身手動模擬調用
十一、 網卡收到一個數據包,觸發中斷,進入ndis託管的isr
十二、 託管Isr進入咱們註冊的isr和後半部
1三、 咱們的isr調用NdisMEthIndicateReceive這個宏,調用各綁定協議提供的接收回調函數,向上提交
1四、 進入各個協議的接收回調函數(ReceivePacketHandler和ReceiveHandler)
1五、 後面怎麼處理收到的包自行決定