Registering a Winsock Kernel Application
WSK Client Object Registration
WSK应用必须通过调用WskRegister函数注册作为一个WSK客户端。WskRegister需要WSK应用初始化并传递一个指向WSK客户的NPI(WSK_CLIENT_NPI结构)一个WSK注册对象(WSK_REGISTRATION结构)被WskRegister初始化并返回。
下面的代码演示了WSK应用镇压个去注册一个WSK客户端。
// Include the WSK header file
#include "wsk.h"
// WSK Client Dispatch table that denotes the WSK version
// that the WSK application wants to use and optionally a pointer
// to the WskClientEvent callback function
const WSK_CLIENT_DISPATCH WskAppDispatch = {
MAKE_WSK_VERSION(1,0), // Use WSK version 1.0
0, // Reserved
NULL // WskClientEvent callback not required for WSK version 1.0
};
// WSK Registration object
WSK_REGISTRATION WskRegistration;
// DriverEntry function
NTSTATUS
DriverEntry(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath
)
{
NTSTATUS Status;
WSK_CLIENT_NPI wskClientNpi;
.
.
.
// Register the WSK application
wskClientNpi.ClientContext = NULL;
wskClientNpi.Dispatch = &WskAppDispatch;
Status = WskRegister(&wskClientNpi, &WskRegistration);
if(!NT_SUCCESS(Status)) {
.
.
.
return Status;
}
.
.
.
}
WSK应用不需要在其DriverEntry函数中,调用WskRegister。举例来说,如果WSK应用只是一个复杂驱动的一个子部件,注册应用的操作可能发生在WSK应用子部件激活的时候。
WSK应用必须保证传递给WskRegister的参数WSK_CLIENT_DISPATCH结构体的有效和一直驻留在内存中,直到WskDeregister被调用,注册不再有效。WSK_REGISTRATION结构体也必须保持其有效和一直驻留在内存中直到WSK应用停止调用其他的WSK注册函数。预前的代码将这两个结构体放在驱动的全局变量中,从而保证结构体一直驻留在内存中直到驱动被卸载。
WSK Provider NPI Capture
在WSK应用使用WskRegister已经注册为WSK客户端后,它为了使用WSK接口必须调WskCaptureProvideNPI函数去捕获WSK从WSK子系统提供的NPI.
因为WSK子系统在WSK应用试图去捕获WSK提供的NPI的时候可能还没有准备好。WskCaptureProvideNPI函数允许WSK应用投票或等待WSK子系统准备好。
如果WaitTimeout参数是WSK_NO_WAIT,这个函数不等待总是直接返回。
如果WaitTimeout是WSK_INFINITE_WAIT,函数将等待直到WSK子系统准备好。
如果WaitTimeout是其他值,这个函数将在WSK变成准备好以后返回,或在等待了时间到了之后返回。
为了避免影响到其他的驱动的服务的启动,WSK 应用在DriverEntry函数中调用WskCaptureProviderNPI的时候,不应该设置WaitTimeout参数为WSK_INFINITE,或一个有限的等待时间。如果,WSK应用在系统的启动很早的阶段开启,它应该在不同于DriverEntry的工作线程中等待WSK子系统准备好。
如果调用WskCaptureProviderNPI 失败返回STATUS_NOINTERFACE,WSK应用应该使用WskQueryProviderCharacteristics函数去发现WSK子系统支持的WSK NPI的范围。WSK应用可以调用WskDeregister去卸载当前注册过的实例,然后使用不同的WSK NPI版本支持的WSK_CLIENT_DISPATCH实例重新注册。
当WskCaptureProviderNPI返回成功,它的WskProviderNpi参数指向WSK提供的NPI(WSK_PROVIDER_NPI)准备好给WSK应用使用的接口。WSK_RPOVIDER_NPI结构包含一个指向WSK客户对象的指针(WSK_CLIENT)和WSK_PROVIDER_DISPATCH派遣函数指针列表的指针,可以在WSK客户对象上使用这些函数创建WSK的套接字并执行一些操作。WSK应用在结束使用WSK_PROVIDER_DISPATCH函数以后,它必须调用WskReleaseProviderNPI去释放WSK提供的NPI.
如下的例子展示了WSK应用怎样去捕获WSK提供的NPI,使用它去创建套接字,然后将其释放。
// WSK application routine that waits for WSK subsystem
// to become ready and captures the WSK Provider NPI
NTSTATUS
WskAppWorkerRoutine(
)
{
NTSTATUS Status;
WSK_PROVIDER_NPI wskProviderNpi;
// Capture the WSK Provider NPI. If WSK subsystem is not ready yet,
// wait until it becomes ready.
Status = WskCaptureProviderNPI(
&WskRegistration, // must have been initialized with WskRegister
WSK_INFINITE_WAIT,
&wskProviderNpi
);
if(!NT_SUCCESS(Status))
{
// The WSK Provider NPI could not be captured.
if( Status == STATUS_NOINTERFACE ) {
// WSK application's requested version is not supported
}
else if( status == STATUS_DEVICE_NOT_READY ) {
// WskDeregister was invoked in another thread thereby causing
// WskCaptureProviderNPI to be canceled.
}
else {
// Some other unexpected failure has occurred
}
return Status;
}
// The WSK Provider NPI has been captured.
// Create and set up a listening socket that accepts
// incoming connections.
Status = CreateListeningSocket(&wskProviderNpi, ...);
// The WSK Provider NPI will not be used any more.
// So, release it here immediately.
WskReleaseProviderNPI(&WskRegistration);
// Return result of socket creation routine
return Status;
}
WSK应用可以调用WskCaptureProviderNPI多次,每一次的成功调用WskCaptureProviderNPI,都需要相应的调用WskReleaseProviderNPI.WSK应用在调用WskReleaseProviderNPI以后不应该调用任何在WSK_PROVIDER_DISPATCH的函数。
Performing Control Operations on a Client Object
WSK应用在已经成功附加在WSK子系统以后,它可以在其往WSK子系统附加操作中返回的客户对象(WSK_CLIENT)上执行一些控制操作。这些控制操作不需要指定特定的套接字,但是有很多通用的功能。
执行客户对象的控制操作可以通过调用WSK_PROVIDER_DISPATCH结构体中的WskControlClient成员,它是一个指向WskControlClient函数的指针。
如下的代码演示了WSK应用如何使用WSK_TRANSPORT_LIST_QUERY客户控制操作在创建一个新的套接字的时候得到一组可用的指定的网络传输列表。
// Function to retrieve a list of available network transports
NTSTATUS
GetTransportList(
PWSK_PROVIDER_NPI WskProviderNpi,
PWSK_TRANSPORT TransportList,
ULONG MaxTransports,
PULONG TransportsRetrieved
)
{
SIZE_T BytesRetrieved;
NTSTATUS Status;
// Perform client control operation
Status =
WskProviderNpi->Dispatch->
WskControlClient(
WskProviderNpi->Client,
WSK_TRANSPORT_LIST_QUERY,
0,
NULL,
MaxTransports * sizeof(WSK_TRANSPORT),
TransportList,
&BytesRetrieved,
NULL // No IRP for this control operation
);
// Convert bytes retrieved to transports retrieved
TransportsRetrieved = BytesRetrieved / sizeof(WSK_TRANSPORT);
// Return status of client control operation
return Status;
}
Creating Sockets
在WSK已经成功附加在WSK子系统上以后,它可以创建套接字来完成网络IO操作。WSK应用通过调用WskSocket函数来创建套接字。这个函数指针通过WSK_PROVIDER_DISPATCH结构的WskSocket成员得到。
WSK应用应该在其创建一个新的套接字的时候,指定套接字的类型。
WSK应用在其创建新的套接字前也应该指定其地址,套接字种类,协议等。
当创建一个新的套接字的时候,如果应用需要在套接字上使能事件回调函数,它必须提供一个套接字上下文和一个客户分发列表结构。
如果套接字创建成功,IRP的IoStatus.Information域包含一个指向新套接字对象结构(WSK_SOCKET)的指针。
如下的代码演示了WSK应用如何创建一个监听套接字。
// Context structure for each socket
typedef struct _WSK_APP_SOCKET_CONTEXT {
PWSK_SOCKET Socket;
.
. // Other application-specific members
.
} WSK_APP_SOCKET_CONTEXT, *PWSK_APP_SOCKET_CONTEXT;
// Prototype for the socket creation IoCompletion routine
NTSTATUS
CreateListeningSocketComplete(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
);
// Function to create a new listening socket
NTSTATUS
CreateListeningSocket(
PWSK_PROVIDER_NPI WskProviderNpi,
PWSK_APP_SOCKET_CONTEXT SocketContext,
PWSK_CLIENT_LISTEN_DISPATCH Dispatch,
)
{
PIRP Irp;
NTSTATUS Status;
// Allocate an IRP
Irp =
IoAllocateIrp(
1,
FALSE
);
// Check result
if (!Irp)
{
// Return error
return STATUS_INSUFFICIENT_RESOURCES;
}
// Set the completion routine for the IRP
IoSetCompletionRoutine(
Irp,
CreateListeningSocketComplete,
SocketContext,
TRUE,
TRUE,
TRUE
);
// Initiate the creation of the socket
Status =
WskProviderNpi->Dispatch->
WskSocket(
WskProviderNpi->Client,
AF_INET,
SOCK_STREAM,
IPPROTO_TCP,
WSK_FLAG_LISTEN_SOCKET,
SocketContext,
Dispatch,
NULL,
NULL,
NULL,
Irp
);
// Return the status of the call to WskSocket()
return Status;
}
// Socket creation IoCompletion routine
NTSTATUS
CreateListeningSocketComplete(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
)
{
UNREFERENCED_PARAMETER(DeviceObject);
PWSK_APP_SOCKET_CONTEXT SocketContext;
// Check the result of the socket creation
if (Irp->IoStatus.Status == STATUS_SUCCESS)
{
// Get the pointer to the socket context
SocketContext =
(PWSK_APP_SOCKET_CONTEXT)Context;
// Save the socket object for the new socket
SocketContext->Socket =
(PWSK_SOCKET)(Irp->IoStatus.Information);
// Set any socket options for the new socket
...
// Enable any event callback functions on the new socket
...
// Perform any other initializations
...
}
// Error status
else
{
// Handle error
...
}
// Free the IRP
IoFreeIrp(Irp);
// Always return STATUS_MORE_PROCESSING_REQUIRED to
// terminate the completion processing of the IRP.
return STATUS_MORE_PROCESSING_REQUIRED;
}
对于面向连接的套接字,WSK应用可以调用WskSocketConnect函数去创建,绑定,连接套接字在一个单独函数的调用中。
Performing Control Operations on a Socket
WSK应用在成功创建套接字以后,它就可以在套接上进行一些控制操作。这些操作包括设置并得到套接字的设定和执行一些套接字IOCTL操作。
WSK应用通过调用WskControlSocket函数来在套接字上执行一些控制操作。这个函数由套接字提供的派遣函数列表结构的成员WskControlSocket指定。派遣函数列表结构通过套接字对象结构(WSK_SOCKET)的成员Dispatch指定,WSK_SOCKET在创建套接字的操作中由WSK子系统返回。
下面的代码演示了WSK应用如何在数据报套接字上设置SO_EXCLUSIVEADDRUSE套接字设定。
// Prototype for the control socket IoCompletion routine
NTSTATUS
ControlSocketComplete(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
);
// Function to set the SO_EXCLUSIVEADDRUSE socket option
// on a datagram socket
NTSTATUS
SetExclusiveAddrUse(
PWSK_SOCKET Socket
)
{
PWSK_PROVIDER_DATAGRAM_DISPATCH Dispatch;
PIRP Irp;
ULONG SocketOptionState;
NTSTATUS Status;
// Get pointer to the socket's provider dispatch structure
Dispatch =
(PWSK_PROVIDER_DATAGRAM_DISPATCH)(Socket->Dispatch);
// Allocate an IRP
Irp =
IoAllocateIrp(
1,
FALSE
);
// Check result
if (!Irp)
{
// Return error
return STATUS_INSUFFICIENT_RESOURCES;
}
// Set the completion routine for the IRP
IoSetCompletionRoutine(
Irp,
ControlSocketComplete,
Socket, // Use the socket object for the context
TRUE,
TRUE,
TRUE
);
// Set the socket option state to 1 to set the socket option
SocketOptionState = 1;
// Initiate the control operation on the socket
Status =
Dispatch->WskControlSocket(
Socket,
WskSetOption,
SO_EXCLUSIVEADDRUSE,
SOL_SOCKET,
sizeof(ULONG),
&SocketOptionState,
0,
NULL,
NULL,
Irp
);
// Return the status of the call to WskControlSocket()
return Status;
}
// Control socket IoCompletion routine
NTSTATUS
ControlSocketComplete(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
)
{
UNREFERENCED_PARAMETER(DeviceObject);
PWSK_SOCKET Socket;
// Check the result of the control operation
if (Irp->IoStatus.Status == STATUS_SUCCESS)
{
// Get the socket object from the context
Socket = (PWSK_SOCKET)Context;
// Perform the next operation on the socket
...
}
// Error status
else
{
// Handle error
...
}
// Free the IRP
IoFreeIrp(Irp);
// Always return STATUS_MORE_PROCESSING_REQUIRED to