0%

在驱动中实现WSK套接字客户端(1)

前言

在Vista及以后的系统中,微软提供了一系列内核中使用的socket套接字接口 Winsock Kernel (简称WSK),
以进行内核中的网络开发,整体使用风格与应用层socket基本一致,这里先介绍客户端如何实现

WSK客户端

在WDK示例工程源码中,只提供了一个简易服务端的实现,所以在写客户端的过程中碰到了各种问题,
只能依靠相关API的MSDN说明进行摸索,最后实现了一个简易的客户端例子来给大家作为参考

相关数据定义

如下为所用到的相关信息的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <ntddk.h>
#include <wsk.h>
// 设备名称
#define CTRL_DEVICE_NAME L"\\Device\\WskClient"
#define CTRL_SYMLINK_NAME L"\\??\\WskClient"
// 控制码定义
#define IOCTL_INIT_SOCKET CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA00, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_EXIT_SOCKET CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA01, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_SEND_DATA CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA02, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_RECV_DATA CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA03, METHOD_BUFFERED, FILE_ANY_ACCESS)
// 结构体定义
typedef enum _SOCKET_STATE {
None = 0,
Creating, // 创建中
Closing, // 关闭中
Ready, // 就绪状态
Sending, // 发送中
Receiving, // 接收中
} SOCKET_STATE, *PSOCKET_STATE;
typedef struct _SOCKET_CONTEXT {
PWSK_SOCKET Socket; // 套接字
SOCKET_STATE State; // 套接字状态
BOOLEAN IsUnload; // 是否在卸载
KEVENT ExitEvent; // 卸载所用事件
SOCKADDR_IN LocalAddr; // 本地IP相关信息
SOCKADDR_IN RemoteAddr; // 远程IP相关信息
PIRP Irp; // 公共资源指针
PMDL DataMdl; // 公共资源指针
PVOID DataBuffer; // 公共资源指针
ULONG DataLength; // 公共资源指针
} SOCKET_CONTEXT, *PSOCKET_CONTEXT;
// 全局变量
WSK_CLIENT_DISPATCH gClientDispatch = { MAKE_WSK_VERSION(1, 0), 0, NULL };
WSK_REGISTRATION gRegistration = { 0 };
PSOCKET_CONTEXT gConnectContext = NULL; // 套接字上下文
PDEVICE_OBJECT gControlDevice = NULL; // 控制设备
驱动初始化操作

注册WSK框架需要用到 WskRegister 这个函数,如下所示

1
2
3
4
NTSTATUS WskRegister(
_In_ PWSK_CLIENT_NPI WskClientNpi,
_Out_ PWSK_REGISTRATION WskRegistration
);

这个注册函数需要用到 WSK_CLIENT_DISPATCH WSK_REGISTRATION WSK_CLIENT_NPI 三个结构体。
其中 WSK_CLIENT_DISPATCH 指定版本号和回调函数,这个回调可以在创建结束后提供一些详细的信息,
这里指定为空。而 WSK_REGISTRATION 相当于一个句柄,可以用于提供卸载时调用

驱动的入口函数如下所示,我们在这里做一些初始化操作,该例子只支持 单线程 + 单Socket 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
ULONG Index;
NTSTATUS Status;
UNICODE_STRING DeviceName;
UNICODE_STRING SymLinkName;
WSK_CLIENT_NPI WskClientNpi;
UNREFERENCED_PARAMETER(RegistryPath);
// 申请套接字所用的公共资源
gConnectContext = AllocateSocketContext();
if (!gConnectContext)
return STATUS_INSUFFICIENT_RESOURCES;
// 注册套接字框架
WskClientNpi.ClientContext = NULL;
WskClientNpi.Dispatch = &gClientDispatch;
Status = WskRegister(&WskClientNpi, &gRegistration);
if (!NT_SUCCESS(Status))
{
FreeSocketContext(gConnectContext);
return Status;
}
// 设置派遣函数
for (Index = 0; Index <= IRP_MJ_MAXIMUM_FUNCTION; Index++)
DriverObject->MajorFunction[Index] = DispatchCommon;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl;
DriverObject->DriverUnload = DriverUnload;
// 创建控制设备
RtlInitUnicodeString(&DeviceName, CTRL_DEVICE_NAME);
RtlInitUnicodeString(&SymLinkName, CTRL_SYMLINK_NAME);
Status = IoCreateDevice(
DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN, FALSE, &gControlDevice);
if (!NT_SUCCESS(Status))
{
WskDeregister(&gRegistration);
FreeSocketContext(gConnectContext);
return Status;
}
// 绑定符号链接
Status = IoCreateSymbolicLink(&SymLinkName, &DeviceName);
if (!NT_SUCCESS(Status))
{
IoDeleteDevice(gControlDevice);
WskDeregister(&gRegistration);
FreeSocketContext(gConnectContext);
return Status;
}
return STATUS_SUCCESS;
}

其中 AllocateSocketContext 函数用来申请所有的公共资源,使用公共资源是为了简化逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PSOCKET_CONTEXT AllocateSocketContext()
{
PSOCKET_CONTEXT SocketContext = NULL;
SocketContext = ExAllocatePool(NonPagedPool, sizeof(SOCKET_CONTEXT));
if (!SocketContext) return NULL;
RtlZeroMemory(SocketContext, sizeof(SOCKET_CONTEXT));
SocketContext->Irp = IoAllocateIrp(1, FALSE);
if (SocketContext->Irp == NULL)
{
FreeSocketContext(SocketContext);
return NULL;
}
KeInitializeEvent(&SocketContext->ExitEvent, NotificationEvent, FALSE);
SocketContext->LocalAddr.sin_family = AF_INET;
return SocketContext;
}

对应的 FreeSocketContext 是用来释放公共资源

1
2
3
4
5
6
7
8
9
10
VOID FreeSocketContext(IN PSOCKET_CONTEXT SocketContext)
{
if (SocketContext->Irp)
{
IoFreeIrp(SocketContext->Irp);
SocketContext->Irp = NULL;
}
FreeSocketBuffer(SocketContext);
ExFreePool(SocketContext);
}

接收和发送的缓冲区根据实际内容来进行申请

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
NTSTATUS AllocateSocketBuffer(IN PSOCKET_CONTEXT SocketContext, IN ULONG Length)
{
SocketContext->DataBuffer = ExAllocatePool(NonPagedPoolNx, Length);
if (!SocketContext->DataBuffer)
{
FreeSocketBuffer(SocketContext);
return STATUS_INSUFFICIENT_RESOURCES;
}
SocketContext->DataMdl = IoAllocateMdl(
SocketContext->DataBuffer, Length, FALSE, FALSE, NULL);
if (!SocketContext->DataMdl)
{
FreeSocketBuffer(SocketContext);
return STATUS_INSUFFICIENT_RESOURCES;
}
MmBuildMdlForNonPagedPool(SocketContext->DataMdl);
memset(SocketContext->DataBuffer, 0, Length);
SocketContext->DataLength = Length;
return STATUS_SUCCESS;
}

释放接收和发送缓冲区的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
VOID FreeSocketBuffer(IN PSOCKET_CONTEXT SocketContext)
{
if (SocketContext->DataMdl)
{
IoFreeMdl(SocketContext->DataMdl);
SocketContext->DataMdl = NULL;
}
if (SocketContext->DataBuffer)
{
ExFreePool(SocketContext->DataBuffer);
SocketContext->DataBuffer = NULL;
}
SocketContext->DataLength = 0;
}
派遣函数定义

因为我们这里只处理 IRP_MJ_DEVICE_CONTROL 操作,其他的统一直接完成返回成功不做处理

1
2
3
4
5
6
7
8
NTSTATUS DispatchCommon(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
NTSTATUS DispatchControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS Status = STATUS_INVALID_DEVICE_REQUEST;
PIO_STACK_LOCATION IrpSp = NULL;
PVOID DataBuffer = NULL;
ULONG ControlCode = 0;
ULONG OutputSize = 0;
ULONG InputSize = 0;
ULONG DataSize = 0;
UNREFERENCED_PARAMETER(DeviceObject);
IrpSp = IoGetCurrentIrpStackLocation(Irp);
DataBuffer = Irp->AssociatedIrp.SystemBuffer;
ControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;
OutputSize = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
InputSize = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
// 判断控制码
switch (ControlCode)
{
case IOCTL_INIT_SOCKET: // 发起连接
if (!DataBuffer || (InputSize != sizeof(SOCKADDR_IN))) break;
Status = IoCtrlInitSocket((PSOCKADDR_IN)DataBuffer);
break;
case IOCTL_EXIT_SOCKET: // 关闭连接
Status = IoCtrlExitSocket();
break;
case IOCTL_SEND_DATA: // 发送数据
if (!DataBuffer || !InputSize) break;
Status = IoCtrlSendData(DataBuffer, InputSize, &DataSize);
break;
case IOCTL_RECV_DATA: // 接收数据
if (!DataBuffer || !OutputSize) break;
Status = IoCtrlRecvData(DataBuffer, OutputSize, &DataSize);
break;

default:
break;
}
Irp->IoStatus.Information = DataSize;
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
控制操作封装函数

这里对如上所示的四种操作进行了二次封装,对应函数如下所示

1
2
3
4
5
6
NTSTATUS IoCtrlInitSocket(IN PSOCKADDR_IN SrvAddr)
{
if (!SrvAddr)
return STATUS_INVALID_PARAMETER;
return OperationCreate(gConnectContext, SrvAddr);
}
1
2
3
4
NTSTATUS IoCtrlExitSocket()
{
return OperationClose(gConnectContext);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NTSTATUS IoCtrlSendData(IN PVOID Buffer, IN ULONG Size, OUT PULONG Real)
{
NTSTATUS Status;
if (!Buffer || !Size)
return STATUS_INVALID_PARAMETER;
Status = AllocateSocketBuffer(gConnectContext, Size);
if (!NT_SUCCESS(Status))
return Status;
memcpy(gConnectContext->DataBuffer, Buffer, Size);
Status = OperationSend(gConnectContext);
FreeSocketBuffer(gConnectContext);
if (!NT_SUCCESS(Status))
return Status;
if (Real)
*Real = (ULONG)gConnectContext->Irp->IoStatus.Information;
return STATUS_SUCCESS;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
NTSTATUS IoCtrlRecvData(IN PVOID Buffer, IN ULONG Size, OUT PULONG Real)
{
NTSTATUS Status;
if (!Buffer || !Size)
return STATUS_INVALID_PARAMETER;
Status = AllocateSocketBuffer(gConnectContext, Size);
if (!NT_SUCCESS(Status))
return Status;
Status = OperationReceive(gConnectContext);
if (!NT_SUCCESS(Status))
{
FreeSocketBuffer(gConnectContext);
return Status;
}
memcpy(Buffer, gConnectContext->DataBuffer,
(ULONG)gConnectContext->Irp->IoStatus.Information);
if (Real)
*Real = (ULONG)gConnectContext->Irp->IoStatus.Information;
FreeSocketBuffer(gConnectContext);
return STATUS_SUCCESS;
}
套接字操作函数

所有的套接字操作都需要设置完成函数,我们这里使用统一的完成函数来处理

1
2
3
4
5
6
7
8
NTSTATUS SyncIrpCompRoutine(PDEVICE_OBJECT Reserved, PIRP Irp, PVOID Context)
{
UNREFERENCED_PARAMETER(Reserved);
UNREFERENCED_PARAMETER(Irp);
// 设置等待的事件为信号态
KeSetEvent((PKEVENT)Context, IO_NO_INCREMENT, FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}

在WSK中提供了一个同时创建和连接套接字的API函数 WskSocketConnect 用以简化操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
NTSTATUS OperationCreate(IN PSOCKET_CONTEXT SocketContext, IN PSOCKADDR_IN SrvAddr)
{
NTSTATUS Status;
KEVENT CompEvent;
WSK_PROVIDER_NPI WskProviderNpi;
SOCKET_STATE State;
BOOLEAN IsReleaseNPI = FALSE;
// 检查参数和套接字状态
if (SrvAddr == NULL)
return STATUS_INVALID_PARAMETER;
if (SocketContext->IsUnload || (SocketContext->State != None))
return STATUS_REQUEST_NOT_ACCEPTED;
State = SocketContext->State;
SocketContext->State = Creating;
do
{
// 获取使用套接字功能的组件
Status = WskCaptureProviderNPI(
&gRegistration, WSK_INFINITE_WAIT, &WskProviderNpi);
if (!NT_SUCCESS(Status)) break;
IsReleaseNPI = TRUE;
// 初始化事件并绑定完成函数
KeInitializeEvent(&CompEvent, NotificationEvent, FALSE);
IoReuseIrp(SocketContext->Irp, STATUS_UNSUCCESSFUL);
IoSetCompletionRoutine(
SocketContext->Irp, SyncIrpCompRoutine, &CompEvent, TRUE, TRUE, TRUE);
// 创建并连接套接字,本地IP和端口可以不指定
SocketContext->LocalAddr.sin_family = AF_INET;
WskProviderNpi.Dispatch->WskSocketConnect(
WskProviderNpi.Client, SOCK_STREAM, IPPROTO_TCP,
(PSOCKADDR)&SocketContext->LocalAddr, (PSOCKADDR)SrvAddr,
0, NULL, NULL, NULL, NULL, NULL, SocketContext->Irp);
// 等待套接字创建完毕
KeWaitForSingleObject(&CompEvent, Executive, KernelMode, FALSE, NULL);
if (!NT_SUCCESS(SocketContext->Irp->IoStatus.Status))
{
Status = SocketContext->Irp->IoStatus.Status;
break;
}
// 保存套接字信息
SocketContext->Socket = (PWSK_SOCKET)SocketContext->Irp->IoStatus.Information;
Status = STATUS_SUCCESS;
} while (0);
// 收尾数据处理
if (IsReleaseNPI)
WskReleaseProviderNPI(&gRegistration);
if (NT_SUCCESS(Status))
SocketContext->State = Ready;
else
SocketContext->State = State;
// 检查驱动是否要卸载
if (SocketContext->IsUnload)
{
OperationClose(SocketContext);
KeSetEvent(&SocketContext->ExitEvent, IO_NO_INCREMENT, FALSE);
}
return Status;
}

套接字的关闭流程如下,首先调用 WskDisconnect 断开连接,再调用 WskCloseSocket 关闭套接字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
NTSTATUS OperationClose(IN PSOCKET_CONTEXT SocketContext)
{
KEVENT CompEvent;
PWSK_PROVIDER_BASIC_DISPATCH Dispatch1;
PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch2;
// 检查套接字的状态
if (SocketContext->State <= Closing)
return STATUS_REQUEST_NOT_ACCEPTED;
SocketContext->State = Closing;
// 获取使用套接字功能的组件
KeInitializeEvent(&CompEvent, NotificationEvent, FALSE);
Dispatch2 = (PWSK_PROVIDER_CONNECTION_DISPATCH)SocketContext->Socket->Dispatch;
// 绑定完成函数
IoReuseIrp(SocketContext->Irp, STATUS_UNSUCCESSFUL);
IoSetCompletionRoutine(
SocketContext->Irp, SyncIrpCompRoutine, &CompEvent, TRUE, TRUE, TRUE);
// 断开套接字连接
Dispatch2->WskDisconnect(
SocketContext->Socket, NULL, 0, SocketContext->Irp);
KeWaitForSingleObject(&CompEvent, Executive, KernelMode, FALSE, NULL);
// 绑定完成函数
Dispatch1 = (PWSK_PROVIDER_BASIC_DISPATCH)SocketContext->Socket->Dispatch;
KeClearEvent(&CompEvent);
IoReuseIrp(SocketContext->Irp, STATUS_UNSUCCESSFUL);
IoSetCompletionRoutine(
SocketContext->Irp, SyncIrpCompRoutine, &CompEvent, TRUE, TRUE, TRUE);
// 关闭套接字
Dispatch1->WskCloseSocket(SocketContext->Socket, SocketContext->Irp);
KeWaitForSingleObject(&CompEvent, Executive, KernelMode, FALSE, NULL);
// 检查驱动是否要卸载
if (SocketContext->IsUnload)
KeSetEvent(&SocketContext->ExitEvent, IO_NO_INCREMENT, FALSE);
// 设置套接字状态
SocketContext->State = None;
return STATUS_SUCCESS;
}

发送数据函数如下,使用 WskSend 发送,注意这里是阻塞操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
NTSTATUS OperationSend(IN PSOCKET_CONTEXT SocketContext)
{
NTSTATUS Status;
WSK_BUF WskBuf;
KEVENT CompEvent;
PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
SOCKET_STATE State;
// 检查套接字的状态
if (SocketContext->IsUnload || (SocketContext->State != Ready))
return STATUS_REQUEST_NOT_ACCEPTED;
State = SocketContext->State;
SocketContext->State = Sending;
do
{
// 获取使用套接字功能的组件
KeInitializeEvent(&CompEvent, NotificationEvent, FALSE);
Dispatch = (PWSK_PROVIDER_CONNECTION_DISPATCH)SocketContext->Socket->Dispatch;
// 初始化发送数据
WskBuf.Offset = 0;
WskBuf.Length = SocketContext->DataLength;
WskBuf.Mdl = SocketContext->DataMdl;
// 绑定完成函数
IoReuseIrp(SocketContext->Irp, STATUS_UNSUCCESSFUL);
IoSetCompletionRoutine(
SocketContext->Irp, SyncIrpCompRoutine, &CompEvent, TRUE, TRUE, TRUE);
// 发送数据
Dispatch->WskSend(
SocketContext->Socket, &WskBuf, 0, SocketContext->Irp);
// 等待发送完成
KeWaitForSingleObject(&CompEvent, Executive, KernelMode, FALSE, NULL);
if (!NT_SUCCESS(SocketContext->Irp->IoStatus.Status))
{
Status = SocketContext->Irp->IoStatus.Status;
OperationClose(SocketContext);
break;
}
// 修改套接字状态
Status = STATUS_SUCCESS;
SocketContext->State = Ready;
} while (0);
// 检查驱动是否要卸载
if (SocketContext->IsUnload)
{
OperationClose(SocketContext);
KeSetEvent(&SocketContext->ExitEvent, IO_NO_INCREMENT, FALSE);
}
return Status;
}

接收数据函数如下,使用 WskReceive 接收,同样也为阻塞函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
NTSTATUS OperationReceive(IN PSOCKET_CONTEXT SocketContext)
{
NTSTATUS Status;
WSK_BUF WskBuf;
KEVENT CompEvent;
PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
SOCKET_STATE State;
// 检查套接字的状态
if (SocketContext->IsUnload || (SocketContext->State != Ready))
return STATUS_REQUEST_NOT_ACCEPTED;
State = SocketContext->State;
SocketContext->State = Sending;
do
{
// 获取使用套接字功能的组件
KeInitializeEvent(&CompEvent, NotificationEvent, FALSE);
Dispatch = (PWSK_PROVIDER_CONNECTION_DISPATCH)SocketContext->Socket->Dispatch;
// 清空接收缓冲区
WskBuf.Offset = 0;
WskBuf.Length = SocketContext->DataLength;
WskBuf.Mdl = SocketContext->DataMdl;
memset(SocketContext->DataBuffer, 0, SocketContext->BufferLength);
// 绑定完成函数
IoReuseIrp(SocketContext->Irp, STATUS_UNSUCCESSFUL);
IoSetCompletionRoutine(
SocketContext->Irp, SyncIrpCompRoutine, &CompEvent, TRUE, TRUE, TRUE);
// 接收数据
Dispatch->WskReceive(
SocketContext->Socket, &WskBuf, 0, SocketContext->Irp);
// 等待接收完成
KeWaitForSingleObject(&CompEvent, Executive, KernelMode, FALSE, NULL);
if (!NT_SUCCESS(SocketContext->Irp->IoStatus.Status))
{
Status = SocketContext->Irp->IoStatus.Status;
OperationClose(SocketContext);
break;
}
// 修改套接字状态
Status = STATUS_SUCCESS;
SocketContext->State = Ready;
} while (0);
// 检查驱动是否要卸载
if (SocketContext->IsUnload)
{
OperationClose(SocketContext);
KeSetEvent(&SocketContext->ExitEvent, IO_NO_INCREMENT, FALSE);
}
return Status;
}

通过以上函数的内容,可以看到WSK函数操作方式与应用层套接字基本一致