步骤2:打开连接上下文
第二步是打开连接上下文。在你建立的连接中,执行后续的操作会用到这个句柄。这也是由ZwCreateFile完成,也是在相同的设备"\Device\Tcp"上执行。实际上这个设备允许你打开三个不同的句柄。这三个句柄是传输句柄,连接上下文句柄和控制句柄。需要注意一个常犯的错误的是,一个句柄打开成功,但实际上确是一个错误打开的句柄。这是由于他们是由"Extended Attributes"来决定打开哪个句柄的。显然,如果驱动程序不能识别EA("Extended Attributes")的值,那么它就会打开一个默认的句柄类型,控制句柄。这是MSDN中关于创建部分的文档描述的。
下面的代码演示了打开一个连接上下文。注意你需要制定一个"CONNECTION_CONTEXT"类型的指针,使用这个指针指向用户定义的数据。后面你会注意到一些事件回调会提供这个指针给你。这也是你可以使用这个上下文值的本质。
NTSTATUS TdiFuncs_OpenConnection(PHANDLE pTdiHandle, PFILE_OBJECT *pFileObject)
{
NTSTATUS NtStatus = STATUS_INSUFFICIENT_RESOURCES;
UNICODE_STRING usTdiDriverNameString;
OBJECT_ATTRIBUTES oaTdiDriverNameAttributes;
IO_STATUS_BLOCK IoStatusBlock;
char DataBlob[sizeof(FILE_FULL_EA_INFORMATION) +
TDI_CONNECTION_CONTEXT_LENGTH + 300] = {0};
PFILE_FULL_EA_INFORMATION pExtendedAttributesInformation =
(PFILE_FULL_EA_INFORMATION)&DataBlob;
UINT dwEASize = 0;
/*
* Initialize the name of the device to be opened. ZwCreateFile
* takes an OBJECT_ATTRIBUTES structure as the name of the device
* to open. This is then a two step process.
*
* 1 - Create a UNICODE_STRING data structure from a unicode string.
* 2 - Create a OBJECT_ATTRIBUTES data structure from a UNICODE_STRING.
*
*/
RtlInitUnicodeString(&usTdiDriverNameString, L"\\Device\\Tcp");
InitializeObjectAttributes(&oaTdiDriverNameAttributes,
&usTdiDriverNameString,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL, NULL);
/*
* The second step is to initialize the Extended Attributes data structure.
*
* EaName = TdiConnectionContext, 0, Your User Defined Context Data
* (Actually a pointer to it)
* EaNameLength = Length of TdiConnectionContext
* EaValueLength = Entire Length
*/
RtlCopyMemory(&pExtendedAttributesInformation->EaName,
TdiConnectionContext, TDI_CONNECTION_CONTEXT_LENGTH);
pExtendedAttributesInformation->EaNameLength =
TDI_CONNECTION_CONTEXT_LENGTH;
pExtendedAttributesInformation->EaValueLength =
TDI_CONNECTION_CONTEXT_LENGTH;
/* Must be at least TDI_CONNECTION_CONTEXT_LENGTH */
dwEASize = sizeof(DataBlob);
NtStatus = ZwCreateFile(pTdiHandle,
FILE_READ_EA | FILE_WRITE_EA, &oaTdiDriverNameAttributes,
&IoStatusBlock, NULL,
FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0,
pExtendedAttributesInformation, dwEASize);
if(NT_SUCCESS(NtStatus))
{
NtStatus = ObReferenceObjectByHandle(*pTdiHandle,
GENERIC_READ | GENERIC_WRITE,
NULL, KernelMode,
(PVOID *)pFileObject, NULL);
if(!NT_SUCCESS(NtStatus))
{
ZwClose(*pTdiHandle);
}
}
return NtStatus;
}
步骤3:关联传输地址和连接上下文
在你执行任何操作之前,你需要关联这两个句柄,传输句柄和连接上下文句柄。这个通过给设备发送一个IOCTL来完成。是否你还记得先前我们是怎样发送IOCTL的?我们需要分配一个IRP,设置参数和发送它到设备。由于TDI头文件提供了宏和其他的函数可以帮助我们简化这个步骤。TdiBuildInternalDeviceControlIrp实际是一个宏,它内部调用了IoBuildDeviceIoControlRequest. 给这个宏的一些参数实际被忽略了,但是对于注释来讲还是有用的(就像提供的IOCTL一样)。这个API很简单,我们为了演示目的使用它。然而使用其它机制来创建IRP,就像IoAllocateIrp,有一些优点,这个将在后面叙述。其他的宏被用于给底层的驱动简单的设置IO_STACK_LOCATION的参数。
这里有一件事情你需要注意,这里跟我们上次谈到的有些不同的是"STATUS_PENDING"。本篇后面将会讨论这个。
下面的代码演示如何使用它。
NTSTATUS TdiFuncs_AssociateTransportAndConnection(HANDLE hTransportAddress,
PFILE_OBJECT pfoConnection)
{
NTSTATUS NtStatus = STATUS_INSUFFICIENT_RESOURCES;
PIRP pIrp;
IO_STATUS_BLOCK IoStatusBlock = {0};
PDEVICE_OBJECT pTdiDevice;
TDI_COMPLETION_CONTEXT TdiCompletionContext;
KeInitializeEvent(&TdiCompletionContext.kCompleteEvent,
NotificationEvent, FALSE);
/*
* The TDI Device Object is required to send these
* requests to the TDI Driver.
*/
pTdiDevice = IoGetRelatedDeviceObject(pfoConnection);
/*
* Step 1: Build the IRP. TDI defines several macros and functions
* that can quickly create IRP's, etc. for variuos purposes.
* While this can be done manually it's easiest to use the macros.
*
* http://msdn.microsoft.com/library/en-us/network/hh/network/
* 34bldmac_f430860a-9ae2-4379-bffc-6b0a81092e7c.xml.asp?frame=true
*/
pIrp = TdiBuildInternalDeviceControlIrp(TDI_ASSOCIATE_ADDRESS,
pTdiDevice, pfoConnection, &TdiCompletionContext.kCompleteEvent,
&IoStatusBlock);
if(pIrp)
{
/*
* Step 2: Add the correct parameters into the IRP.
*/
TdiBuildAssociateAddress(pIrp, pTdiDevice,
pfoConnection, NULL, NULL, hTransportAddress);
NtStatus = IoCallDriver(pTdiDevice, pIrp);
/*
* If the status returned is STATUS_PENDING this means that the IRP
* will not be completed synchronously and the driver has queued the
* IRP for later processing. This is fine but we do not want
* to return this thread, we are a synchronous call so we want
* to wait until it has completed. The EVENT that we provided will
* be set when the IRP completes.
*/
if(NtStatus == STATUS_PENDING)
{
KeWaitForSingleObject(&TdiCompletionContext.kCompleteEvent,
Executive, KernelMode, FALSE, NULL);
/*
* Find the Status of the completed IRP
*/
NtStatus = IoStatusBlock.Status;
}
}
return NtStatus;
}