diff options
Diffstat (limited to 'network/wsk')
| -rw-r--r-- | network/wsk/echosrv/ReadMe.md | 58 | ||||
| -rw-r--r-- | network/wsk/echosrv/echosrv.ctl | 1 | ||||
| -rw-r--r-- | network/wsk/echosrv/echosrv.sln | 28 | ||||
| -rw-r--r-- | network/wsk/echosrv/echosrv.vcxproj | 188 | ||||
| -rw-r--r-- | network/wsk/echosrv/echosrv.vcxproj.Filters | 31 | ||||
| -rw-r--r-- | network/wsk/echosrv/wsksmple.c | 1361 | ||||
| -rw-r--r-- | network/wsk/echosrv/wsksmple.rc | 11 |
7 files changed, 1678 insertions, 0 deletions
diff --git a/network/wsk/echosrv/ReadMe.md b/network/wsk/echosrv/ReadMe.md new file mode 100644 index 00000000..826e09f4 --- /dev/null +++ b/network/wsk/echosrv/ReadMe.md @@ -0,0 +1,58 @@ +WSK TCP Echo Server +=================== + +This sample driver is a minimal driver meant to demonstrate the usage of the Winsock Kernel (WSK) programming interface. + +The sample implements a simple kernel-mode application by using the Winsock Kernel (WSK) programming interface. The application accepts incoming TCP connection requests on port 40007 over both IPv4 and IPv6 and, on each connection, it echoes all received data back to the peer until the connection is closed by the peer. The application is designed to use a single worker thread to perform all of its processing. For better performance on a multi-processor computer, the sample can be enhanced to use more worker threads. This sample is designed such that operations on a given connection should always be processed by the same worker thread. This provides a simple form of synchronization that ensures proper socket closure in a setting where multiple operations might be outstanding and completed asynchronously on a given connection. For the sake of simplicity, this sample does not enforce any limit on the number of connections accepted (other than the natural limit imposed by the available system memory) or on the amount of time that a connection stays alive. A production server application should be designed with these security points in mind. + +This sample is not intended for use in a production environment. + + +WPP SOFTWARE TRACING +-------------------- + +This sample driver uses WPP Software Tracing in order to log its actions. You can find detailed information on WPP Software Tracing in the WDK documentation. Here is a quick overview of one way to collect trace logs from the sample driver by using the tracing tools that are available in the \\tools\\tracing directory in the WDK. All code for this sample is located in the src\\network\\WSK\\echosrv directory. + +1. In a Command Prompt window, copy Echosrv.ctl and Echosrv.pdb into a directory and change to that directory (cd). +2. Start software tracing for the sample driver by typing the following command: + + **tracelog -start echosrvtrace -guid echosrv.ctl -f logfile.etl -flags 0x3** + + The value that is provided for the -flags option determines which events will be logged by the sample driver. The sample currently has two event types denoted by the TRCERROR and TRCINFO macros where TRCERROR is 0x1 and TRCINFO is 0x2. Thus, a flag value of 0x3 (0x1 combined in a bitwise OR with 0x2) in the previous tracelog command tells the sample driver to log both TRCERROR and TRCINFO events. + +3. In order to stop tracing, type the following command: + + tracelog -stop echosrvtrace + +4. Convert the trace logs in Logfile.etl into a human-readable format by typing the following command: + + **tracefmt -o logfile.txt -f logfile.etl -r . -i \\***full-path***\\ echosrv.sys** + +5. Open Logfile.txt to view the trace logs. + +Be aware that tracing for the sample driver can be started at any time before the driver is started or while the driver is already running. + +To run the sample +----------------- + +Install and run this sample driver by using the following steps: + +1. Copy the Echosrv.sys file to a directory on the test machine. +2. In a Command Prompt window, type the following command: + + **sc create echosrv type= kernel binpath= \\***full-path***\\ echosrv.sys** + + where \\*full-path*\\ is the directory that contains the Echosrv.sys file. + +3. To start the driver, type: + + **sc start echosrv** + +4. To stop the driver, type: + + **sc stop echosrv** + +After the driver is installed and started, it will listen for incoming TCP connection requests on port 40007 over both IPv4 and IPv6 protocols until the driver is stopped. On each connection, the driver will echo all the received data back to the peer until the connection is closed by the peer. + +For more information on the usage of the Winsock Kernel (WSK) programming interface, see [Winsock Kernel](http://msdn.microsoft.com/en-us/library/windows/hardware/ff571084). + diff --git a/network/wsk/echosrv/echosrv.ctl b/network/wsk/echosrv/echosrv.ctl new file mode 100644 index 00000000..c66661ee --- /dev/null +++ b/network/wsk/echosrv/echosrv.ctl @@ -0,0 +1 @@ +998bdf51-0349-4fbc-870c-d6130a955a5f WskSampleCtlGuid
\ No newline at end of file diff --git a/network/wsk/echosrv/echosrv.sln b/network/wsk/echosrv/echosrv.sln new file mode 100644 index 00000000..ce0d54fc --- /dev/null +++ b/network/wsk/echosrv/echosrv.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0 +MinimumVisualStudioVersion = 12.0 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "echosrv", "echosrv.vcxproj", "{FFEE1E8D-35BB-4B3B-A59E-CC8D6DE39B5D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FFEE1E8D-35BB-4B3B-A59E-CC8D6DE39B5D}.Debug|Win32.ActiveCfg = Debug|Win32 + {FFEE1E8D-35BB-4B3B-A59E-CC8D6DE39B5D}.Debug|Win32.Build.0 = Debug|Win32 + {FFEE1E8D-35BB-4B3B-A59E-CC8D6DE39B5D}.Release|Win32.ActiveCfg = Release|Win32 + {FFEE1E8D-35BB-4B3B-A59E-CC8D6DE39B5D}.Release|Win32.Build.0 = Release|Win32 + {FFEE1E8D-35BB-4B3B-A59E-CC8D6DE39B5D}.Debug|x64.ActiveCfg = Debug|x64 + {FFEE1E8D-35BB-4B3B-A59E-CC8D6DE39B5D}.Debug|x64.Build.0 = Debug|x64 + {FFEE1E8D-35BB-4B3B-A59E-CC8D6DE39B5D}.Release|x64.ActiveCfg = Release|x64 + {FFEE1E8D-35BB-4B3B-A59E-CC8D6DE39B5D}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/network/wsk/echosrv/echosrv.vcxproj b/network/wsk/echosrv/echosrv.vcxproj new file mode 100644 index 00000000..670bbe52 --- /dev/null +++ b/network/wsk/echosrv/echosrv.vcxproj @@ -0,0 +1,188 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{FFEE1E8D-35BB-4B3B-A59E-CC8D6DE39B5D}</ProjectGuid> + <RootNamespace>$(MSBuildProjectName)</RootNamespace> + <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration> + <Platform Condition="'$(Platform)' == ''">Win32</Platform> + <SampleGuid>{356832D6-4003-4E04-991A-8885A867DFF1}</SampleGuid> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>False</UseDebugLibraries> + <DriverTargetPlatform>Desktop</DriverTargetPlatform> + <DriverType>WDM</DriverType> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + </PropertyGroup> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>True</UseDebugLibraries> + <DriverTargetPlatform>Desktop</DriverTargetPlatform> + <DriverType>WDM</DriverType> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + </PropertyGroup> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>False</UseDebugLibraries> + <DriverTargetPlatform>Desktop</DriverTargetPlatform> + <DriverType>WDM</DriverType> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + </PropertyGroup> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>True</UseDebugLibraries> + <DriverTargetPlatform>Desktop</DriverTargetPlatform> + <DriverType>WDM</DriverType> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <PropertyGroup> + <OutDir>$(IntDir)</OutDir> + </PropertyGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" /> + </ImportGroup> + <ItemGroup Label="WrappedTaskItems"> + <ClCompile Include="wsksmple.c"> + <WppEnabled>true</WppEnabled> + <WppKernelMode>true</WppKernelMode> + </ClCompile> + <OtherWpp Include="wsksmple.rc"> + <WppEnabled>true</WppEnabled> + <WppKernelMode>true</WppKernelMode> + </OtherWpp> + </ItemGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <TargetName>echosrv</TargetName> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <TargetName>echosrv</TargetName> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <TargetName>echosrv</TargetName> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <TargetName>echosrv</TargetName> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <TreatWarningAsError>true</TreatWarningAsError> + <WarningLevel>Level4</WarningLevel> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <ExceptionHandling> + </ExceptionHandling> + </ClCompile> + <ResourceCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </ResourceCompile> + <Midl> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </Midl> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\netio.lib;$(SDK_LIB_PATH)\uuid.lib</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <TreatWarningAsError>true</TreatWarningAsError> + <WarningLevel>Level4</WarningLevel> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <ExceptionHandling> + </ExceptionHandling> + </ClCompile> + <ResourceCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </ResourceCompile> + <Midl> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </Midl> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\netio.lib;$(SDK_LIB_PATH)\uuid.lib</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <TreatWarningAsError>true</TreatWarningAsError> + <WarningLevel>Level4</WarningLevel> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <ExceptionHandling> + </ExceptionHandling> + </ClCompile> + <ResourceCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </ResourceCompile> + <Midl> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </Midl> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\netio.lib;$(SDK_LIB_PATH)\uuid.lib</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <TreatWarningAsError>true</TreatWarningAsError> + <WarningLevel>Level4</WarningLevel> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <ExceptionHandling> + </ExceptionHandling> + </ClCompile> + <ResourceCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </ResourceCompile> + <Midl> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </Midl> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\netio.lib;$(SDK_LIB_PATH)\uuid.lib</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ResourceCompile Include="wsksmple.rc" /> + </ItemGroup> + <ItemGroup> + <Inf Exclude="@(Inf)" Include="*.inf" /> + <FilesToPackage Include="$(TargetPath)" Condition="'$(ConfigurationType)'=='Driver' or '$(ConfigurationType)'=='DynamicLibrary'" /> + <FilesToPackage Include="@(Inf->'%(CopyOutput)')" Condition="'@(Inf)'!=''" /> + </ItemGroup> + <ItemGroup> + <None Exclude="@(None)" Include="*.txt;*.htm;*.html" /> + <None Exclude="@(None)" Include="*.ico;*.cur;*.bmp;*.dlg;*.rct;*.gif;*.jpg;*.jpeg;*.wav;*.jpe;*.tiff;*.tif;*.png;*.rc2" /> + <None Exclude="@(None)" Include="*.def;*.bat;*.hpj;*.asmx" /> + </ItemGroup> + <ItemGroup> + <ClInclude Exclude="@(ClInclude)" Include="*.h;*.hpp;*.hxx;*.hm;*.inl;*.xsd" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> +</Project>
\ No newline at end of file diff --git a/network/wsk/echosrv/echosrv.vcxproj.Filters b/network/wsk/echosrv/echosrv.vcxproj.Filters new file mode 100644 index 00000000..f0757e26 --- /dev/null +++ b/network/wsk/echosrv/echosrv.vcxproj.Filters @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;*</Extensions> + <UniqueIdentifier>{7A5FF825-82E8-4B8C-A384-AFE5DDF0241F}</UniqueIdentifier> + </Filter> + <Filter Include="Header Files"> + <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions> + <UniqueIdentifier>{7BD60EFA-8AB3-4BA8-8624-F096458DF6FD}</UniqueIdentifier> + </Filter> + <Filter Include="Resource Files"> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms;man;xml</Extensions> + <UniqueIdentifier>{02D631B3-0A60-43EF-A6F1-92F34CD3105C}</UniqueIdentifier> + </Filter> + <Filter Include="Driver Files"> + <Extensions>inf;inv;inx;mof;mc;</Extensions> + <UniqueIdentifier>{81350963-697B-4F08-9AEA-8A5426D14C60}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ClCompile Include="wsksmple.c"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="wsksmple.rc"> + <Filter>Resource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/network/wsk/echosrv/wsksmple.c b/network/wsk/echosrv/wsksmple.c new file mode 100644 index 00000000..a16d0a3e --- /dev/null +++ b/network/wsk/echosrv/wsksmple.c @@ -0,0 +1,1361 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + +Module Name: + + wsksample.c + +Abstract: + + This module implements a simple kernel-mode application by using the + Winsock Kernel (WSK) programming interface. The application accepts + incoming connection requests and, on each connection, echoes all received + data back to the peer until the connection is closed by the peer. + The application is designed to use a single worker thread to perform all of + its processing. For better performance on MP machines, the sample may be + enhanced to use more worker threads. Operations on a given connection + should always be processed by the same worker thread. This provides a + simple form of synchronization ensuring proper socket closure in a setting + where multiple operations may be outstanding and completed asynchronously + on a given connection. For the sake of simplicty, this sample does not + enforce any limit on the number of connections accepted (other than the + natural limit imposed by the available system memory) or on the amount of + time a connection stays around. A full-fledged server application should be + designed with these points in mind from a security viewpoint. + +Author: + +Environment: + + Kernel-Mode only + +Revision History: + +--*/ + +#pragma warning(push) +#pragma warning(disable:4201) // nameless struct/union +#pragma warning(disable:4214) // bit field types other than int + +#include <ntddk.h> +#include <wsk.h> + +#pragma warning(pop) + +// Software Tracing definitions +#define WPP_CONTROL_GUIDS \ + WPP_DEFINE_CONTROL_GUID(WskSampleCtlGuid, \ + (998bdf51, 0349, 4fbc, 870c, d6130a955a5f), \ + WPP_DEFINE_BIT(TRCERROR) \ + WPP_DEFINE_BIT(TRCINFO) ) + +#include "wsksmple.tmh" + +// Pool tags used for memory allocations +#define WSKSAMPLE_SOCKET_POOL_TAG ((ULONG)'sksw') +#define WSKSAMPLE_BUFFER_POOL_TAG ((ULONG)'bksw') +#define WSKSAMPLE_GENERIC_POOL_TAG ((ULONG)'xksw') + +// Default length for data buffers used in send and receive operations +#define WSKSAMPLE_DATA_BUFFER_LENGTH 2048 + +// Forward declaration for the socket context structure +typedef struct _WSKSAMPLE_SOCKET_CONTEXT *PWSKSAMPLE_SOCKET_CONTEXT; + +// Forward declaration for the socket operation context structure +typedef struct _WSKSAMPLE_SOCKET_OP_CONTEXT *PWSKSAMPLE_SOCKET_OP_CONTEXT; + +// Function prototype for socket operation handling routines +typedef +VOID +(* PWSKSAMPLE_OP_HANDLER_FN)( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext); + +// Structure that represents a work queue processed by a dedicated worker thread +typedef struct _WSKSAMPLE_WORK_QUEUE { + + // List head + SLIST_HEADER Head; + + // Wake up event + KEVENT Event; + + // Worker thread can exit safely only if queue is empty and Stop is TRUE + BOOLEAN Stop; + + // Worker thread pointer + PETHREAD Thread; + +} WSKSAMPLE_WORK_QUEUE, *PWSKSAMPLE_WORK_QUEUE; + +// Structure that represents the context for a WSK socket operation. +typedef struct _WSKSAMPLE_SOCKET_OP_CONTEXT { + + // Work queue linkage + SLIST_ENTRY QueueEntry; + + // Pointer to the function that will handle the operation + PWSKSAMPLE_OP_HANDLER_FN OpHandler; + + // Pointer to the WSK socket context. + PWSKSAMPLE_SOCKET_CONTEXT SocketContext; + + // IRP to use for the operation + PIRP Irp; + + // Data buffer and MDL used by send and receive operations + _Field_size_bytes_part_(BufferLength,DataLength) PVOID DataBuffer; + PMDL DataMdl; + SIZE_T BufferLength; // size of the buffer + SIZE_T DataLength; // length of actual data stored in the buffer + +} WSKSAMPLE_SOCKET_OP_CONTEXT; + +// Maximum number of operations that can be outstanding on a socket at any time +#define WSKSAMPLE_OP_COUNT 2 + +// Structure that represents the context for a WSK socket. +typedef struct _WSKSAMPLE_SOCKET_CONTEXT { + + // Pointer to the WSK socket. + PWSK_SOCKET Socket; + + // Work queue used for enqueueing operations on the socket + PWSKSAMPLE_WORK_QUEUE WorkQueue; + + // Socket is being closed. + BOOLEAN Closing; + + // Peer has gracefully disconnected its half of the connection and we are + // about to disconnect (or have disconnected) our half. + BOOLEAN Disconnecting; + + // Stop accepting incoming connections. Valid for listening sockets only. + BOOLEAN StopListening; + + // Embedded array of contexts for outstanding operations on the socket. + // Note that operation contexts could also be allocated separately. This + // sample preallocates a fixed number of operation contexts along with + // the socket context for a new socket. + WSKSAMPLE_SOCKET_OP_CONTEXT OpContext[WSKSAMPLE_OP_COUNT]; + +} WSKSAMPLE_SOCKET_CONTEXT; + +// Forward declaration for WskAcceptEvent in WSK_CLIENT_LISTEN_DISPATCH +NTSTATUS +WSKAPI +WskSampleAcceptEvent( + _In_ PVOID SocketContext, + _In_ ULONG Flags, + _In_ PSOCKADDR LocalAddress, + _In_ PSOCKADDR RemoteAddress, + _In_opt_ PWSK_SOCKET AcceptSocket, + _Outptr_result_maybenull_ PVOID *AcceptSocketContext, + _Outptr_result_maybenull_ CONST WSK_CLIENT_CONNECTION_DISPATCH **AcceptSocketDispatch + ); + +// Client-level callback table +const WSK_CLIENT_DISPATCH WskSampleClientDispatch = { + MAKE_WSK_VERSION(1, 0), // This sample uses WSK version 1.0 + 0, // Reserved + NULL // WskClientEvent callback is not required in WSK version 1.0 +}; + +// WSK Registration object +WSK_REGISTRATION WskSampleRegistration; + +// Socket-level callback table for listening sockets +const WSK_CLIENT_LISTEN_DISPATCH WskSampleClientListenDispatch = { + WskSampleAcceptEvent, + NULL, // WskInspectEvent is required only if conditional-accept is used. + NULL // WskAbortEvent is required only if conditional-accept is used. +}; + +// Global reference to the socket context for the listening socket +PWSKSAMPLE_SOCKET_CONTEXT WskSampleListeningSocketContext; + +// Global work queue used for enqueueing all socket operations +WSKSAMPLE_WORK_QUEUE WskSampleWorkQueue; + +// IPv6 wildcard address and port number 40007 to listen on +SOCKADDR_IN6 IPv6ListeningAddress = { + AF_INET6, + 0x479c, // 40007 in hex in network byte order + 0, + IN6ADDR_ANY_INIT, + 0}; + +// Forward function declarations + +DRIVER_INITIALIZE DriverEntry; +DRIVER_UNLOAD WskSampleUnload; +KSTART_ROUTINE WskSampleWorkerThread; +IO_COMPLETION_ROUTINE WskSampleSyncIrpCompletionRoutine; +IO_COMPLETION_ROUTINE WskSampleReceiveIrpCompletionRoutine; +IO_COMPLETION_ROUTINE WskSampleSendIrpCompletionRoutine; +IO_COMPLETION_ROUTINE WskSampleDisconnectIrpCompletionRoutine; +IO_COMPLETION_ROUTINE WskSampleCloseIrpCompletionRoutine; + +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ); + +_Must_inspect_result_ +__drv_allocatesMem(Mem) +_Success_(return != NULL) +PWSKSAMPLE_SOCKET_CONTEXT +WskSampleAllocateSocketContext( + _In_ PWSKSAMPLE_WORK_QUEUE WorkQueue, + _In_ ULONG BufferLength + ); + +_At_(SocketContext, __drv_freesMem(Mem)) +VOID +WskSampleFreeSocketContext( + _In_ PWSKSAMPLE_SOCKET_CONTEXT SocketContext + ); + +_At_(SocketOpContext, __drv_aliasesMem) +VOID +WskSampleEnqueueOp( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext, + _In_ PWSKSAMPLE_OP_HANDLER_FN OpHandler + ); + +VOID +WskSampleWorkerThread ( + _In_ PVOID Context + ); + +VOID +WskSampleSetupListeningSocket( + _In_ PWSK_PROVIDER_NPI WskProviderNpi, + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ); + +VOID +WskSampleOpStartListen( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ); + +VOID +WskSampleOpStopListen( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ); + +VOID +WskSampleOpReceive( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ); + +VOID +WskSampleOpSend( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ); + +VOID +WskSampleOpDisconnect( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ); + +VOID +WskSampleOpClose( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ); + +VOID +WskSampleOpFree( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ); + +NTSTATUS +WskSampleStartWorkQueue( + _Out_ PWSKSAMPLE_WORK_QUEUE WorkQueue + ); + +VOID +WskSampleStopWorkQueue( + _In_ PWSKSAMPLE_WORK_QUEUE WorkQueue + ); + +VOID +WskSampleUnload( + _In_ PDRIVER_OBJECT DriverObject + ); + +#ifdef ALLOC_PRAGMA + +#pragma alloc_text(INIT, DriverEntry) +#pragma alloc_text(INIT, WskSampleStartWorkQueue) +#pragma alloc_text(PAGE, WskSampleUnload) +#pragma alloc_text(PAGE, WskSampleStopWorkQueue) +#pragma alloc_text(PAGE, WskSampleWorkerThread) +#pragma alloc_text(PAGE, WskSampleOpStartListen) +#pragma alloc_text(PAGE, WskSampleOpStopListen) +#pragma alloc_text(PAGE, WskSampleSetupListeningSocket) +#pragma alloc_text(PAGE, WskSampleOpReceive) +#pragma alloc_text(PAGE, WskSampleOpSend) +#pragma alloc_text(PAGE, WskSampleOpDisconnect) +#pragma alloc_text(PAGE, WskSampleOpClose) +#pragma alloc_text(PAGE, WskSampleOpFree) + +#endif + +// Driver entry routine +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ) +{ + NTSTATUS status; + WSK_CLIENT_NPI wskClientNpi; + + UNREFERENCED_PARAMETER(RegistryPath); + + PAGED_CODE(); + + // Allocate a socket context that will be used for queueing an operation + // to setup a listening socket that will accept incoming connections + WskSampleListeningSocketContext = WskSampleAllocateSocketContext( + &WskSampleWorkQueue, 0); + + if(WskSampleListeningSocketContext == NULL) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Register with WSK. + wskClientNpi.ClientContext = NULL; + wskClientNpi.Dispatch = &WskSampleClientDispatch; + status = WskRegister(&wskClientNpi, &WskSampleRegistration); + + if(!NT_SUCCESS(status)) { + WskSampleFreeSocketContext(WskSampleListeningSocketContext); + return status; + } + + // Initialize and start the global work queue + status = WskSampleStartWorkQueue(&WskSampleWorkQueue); + + if(!NT_SUCCESS(status)) { + WskDeregister(&WskSampleRegistration); + WskSampleFreeSocketContext(WskSampleListeningSocketContext); + return status; + } + + // Enqueue the first operation to setup the listening socket + WskSampleEnqueueOp(&WskSampleListeningSocketContext->OpContext[0], + WskSampleOpStartListen); + + // Everything has been initiated successfully. Now we can enable the + // unload routine and return. + DriverObject->DriverUnload = WskSampleUnload; + + // Initialize software tracing + WPP_INIT_TRACING(DriverObject, RegistryPath); + + DoTraceMessage(TRCINFO, "LOADED"); + + return STATUS_SUCCESS; +} + +// Driver unload routine +VOID +WskSampleUnload( + _In_ PDRIVER_OBJECT DriverObject + ) +{ + C_ASSERT(WSKSAMPLE_OP_COUNT >= 2); + + UNREFERENCED_PARAMETER(DriverObject); + + PAGED_CODE(); + + DoTraceMessage(TRCINFO, "UNLOAD START"); + + // Enqueue an operation to stop listening for any new connections. + // This will close the listening socket, but there may still be + // connection-oriented sockets that are open. The WskDeregister call + // below will block until all sockets including the connection-oriented + // ones that were accepted over the listening socket are closed. Since + // this sample currently closes the connection-oriented sockets + // only when an error occurs or when the remote peer disconnects, the + // unload of the driver will be blocked until the last connection is + // disconnected by the peer. If the requirements for a full-fledged + // application are such that the unload of the driver (or any other + // similar event) proactively closes all the sockets (that may + // otherwise stay open indefinitely), then the application must keep + // track of all the sockets in a fashion that allows enumerating them and + // closing each socket. In such a scheme, the application must still ensure + // that a socket is closed only when other calls can NOT anymore be issued + // on the socket. + WskSampleEnqueueOp(&WskSampleListeningSocketContext->OpContext[1], + WskSampleOpStopListen); + + // Deregister with WSK. This call will wait until all the references to + // the WSK provider NPI are released and all the sockets are closed. Note + // that if the worker thread has not started yet, then when it eventually + // starts, its WskCaptureProviderNPI call will fail and the work queue + // will be flushed and cleaned up properly. + WskDeregister(&WskSampleRegistration); + + // WskDeregister returns only if all the sockets are closed. Thus, at this + // point, it's guaranteed that all socket are closed, which also means that + // there can not be any further outstanding operations on any socket. So, + // the worker thread can now safely stop processing the work queue if there + // are no queued items. Signal the worker thread to stop and wait for it. + WskSampleStopWorkQueue(&WskSampleWorkQueue); + + DoTraceMessage(TRCINFO, "UNLOAD END"); + + WPP_CLEANUP(DriverObject); +} + +// Initialize a given work queue and start the worker thread for it +NTSTATUS +WskSampleStartWorkQueue( + _Out_ PWSKSAMPLE_WORK_QUEUE WorkQueue + ) +{ + NTSTATUS status; + HANDLE threadHandle; + + PAGED_CODE(); + + InitializeSListHead(&WorkQueue->Head); + KeInitializeEvent(&WorkQueue->Event, SynchronizationEvent, FALSE); + WorkQueue->Stop = FALSE; + + status = PsCreateSystemThread( + &threadHandle, THREAD_ALL_ACCESS, NULL, NULL, NULL, + WskSampleWorkerThread, WorkQueue); + + if(!NT_SUCCESS(status)) { + return status; + } + + status = ObReferenceObjectByHandle( + threadHandle, THREAD_ALL_ACCESS, NULL, KernelMode, + &WorkQueue->Thread, NULL); + + ZwClose(threadHandle); + + if(!NT_SUCCESS(status)) { + WorkQueue->Stop = TRUE; + KeSetEvent(&WorkQueue->Event, 0, FALSE); + } + + return status; +} + +// Stop a given work queue and wait for its worker thread to exit +VOID +WskSampleStopWorkQueue( + _In_ PWSKSAMPLE_WORK_QUEUE WorkQueue + ) +{ + PAGED_CODE(); + + ASSERT(WorkQueue->Stop == FALSE); + ASSERT(WorkQueue->Thread); + WorkQueue->Stop = TRUE; + KeSetEvent(&WorkQueue->Event, 0, FALSE); + KeWaitForSingleObject(WorkQueue->Thread, Executive, KernelMode, FALSE,NULL); + ObDereferenceObject(WorkQueue->Thread); +} + +// Allocate and setup a socket context +_Must_inspect_result_ +__drv_allocatesMem(Mem) +_Success_(return != NULL) +PWSKSAMPLE_SOCKET_CONTEXT +WskSampleAllocateSocketContext( + _In_ PWSKSAMPLE_WORK_QUEUE WorkQueue, + _In_ ULONG BufferLength + ) +{ + PWSKSAMPLE_SOCKET_CONTEXT socketContext; + + // Allocate and setup a socket context with optional data buffers, and + // attach the socket to the given work queue. Even though this sample uses + // only a single global work queue, the sample is designed to be easily + // adaptable to using multiple work queues. If the sample is changed to + // use multiple work queues (say a work queue per processor), a given + // socket will/must always use the same work queue. + + socketContext = ExAllocatePoolWithTag( + NonPagedPool, sizeof(*socketContext), WSKSAMPLE_SOCKET_POOL_TAG); + + if(socketContext != NULL) { + + ULONG i; + + RtlZeroMemory(socketContext, sizeof(WSKSAMPLE_SOCKET_CONTEXT)); + + socketContext->WorkQueue = WorkQueue; + + for(i = 0; i < WSKSAMPLE_OP_COUNT; i++) { + + socketContext->OpContext[i].SocketContext = socketContext; + + socketContext->OpContext[i].Irp = IoAllocateIrp(1, FALSE); + if(socketContext->OpContext[i].Irp == NULL) { + goto failure; + } + + if(BufferLength > 0) { + socketContext->OpContext[i].DataBuffer = ExAllocatePoolWithTag( + NonPagedPool, BufferLength, WSKSAMPLE_BUFFER_POOL_TAG); + if(socketContext->OpContext[i].DataBuffer == NULL) { + goto failure; + } + socketContext->OpContext[i].DataMdl = IoAllocateMdl( + socketContext->OpContext[i].DataBuffer, + BufferLength, FALSE, FALSE, NULL); + if(socketContext->OpContext[i].DataMdl == NULL) { + goto failure; + } + MmBuildMdlForNonPagedPool(socketContext->OpContext[i].DataMdl); + socketContext->OpContext[i].BufferLength = BufferLength; + } + } + + DoTraceMessage(TRCINFO, "AllocateSocketContext: %p", socketContext); + + return socketContext; + } + +failure: + + DoTraceMessage(TRCERROR, "AllocateSocketContext: FAIL"); + + if(socketContext) { + WskSampleFreeSocketContext(socketContext); + } + + return NULL; +} + +// Cleanup and free a socket context +_At_(SocketContext, __drv_freesMem(Mem)) +VOID +WskSampleFreeSocketContext( + _In_ PWSKSAMPLE_SOCKET_CONTEXT SocketContext + ) +{ + ULONG i; + + // Socket context is freed only after all the WSK calls on the + // socket are completed and all enqueued operations for the socket + // are dequeued. So, we can safely free the Irp, Mdl, and data buffer + // pointed by the socket operation contexts. + + for(i = 0; i < WSKSAMPLE_OP_COUNT; i++) { + + if(SocketContext->OpContext[i].Irp != NULL) { + IoFreeIrp(SocketContext->OpContext[i].Irp); + SocketContext->OpContext[i].Irp = NULL; + } + if(SocketContext->OpContext[i].DataMdl != NULL) { + IoFreeMdl(SocketContext->OpContext[i].DataMdl); + SocketContext->OpContext[i].DataMdl = NULL; + } + if(SocketContext->OpContext[i].DataBuffer != NULL) { + ExFreePool(SocketContext->OpContext[i].DataBuffer); + SocketContext->OpContext[i].DataBuffer = NULL; + } + } + + DoTraceMessage(TRCINFO, "FreeSocketContext: %p", SocketContext); + + ExFreePool(SocketContext); +} + +// Enqueue an operation on a socket +_At_(SocketOpContext, __drv_aliasesMem) +VOID +WskSampleEnqueueOp( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext, + _In_ PWSKSAMPLE_OP_HANDLER_FN OpHandler + ) +{ + PWSKSAMPLE_WORK_QUEUE workQueue = SocketOpContext->SocketContext->WorkQueue; + + SocketOpContext->OpHandler = OpHandler; + + #pragma warning(disable:4054) // turn off typecast warning + + if(NULL == InterlockedPushEntrySList( + &workQueue->Head, + &SocketOpContext->QueueEntry)) { + DoTraceMessage(TRCINFO, "EnqueueOp: %p %p (%p) SetEvent", + SocketOpContext->SocketContext, SocketOpContext, (PVOID)OpHandler); + // Work queue was empty. So, signal the work queue event in case the + // worker thread is waiting on the event for more operations. + KeSetEvent(&workQueue->Event, 0, FALSE); + } + else + DoTraceMessage(TRCINFO, "EnqueueOp: %p %p (%p)", + SocketOpContext->SocketContext, SocketOpContext, (PVOID)OpHandler); + + #pragma warning(default:4054) // restore typecast warning +} + +// Worker thread which drains and processes a given work queue +VOID +WskSampleWorkerThread ( + _In_ PVOID Context + ) +{ + PWSKSAMPLE_WORK_QUEUE workQueue; + PSLIST_ENTRY listEntryRev, listEntry, next; + + PAGED_CODE(); + + workQueue = (PWSKSAMPLE_WORK_QUEUE)Context; + + for(;;) { + + // Flush all the queued operations into a local list + listEntryRev = InterlockedFlushSList(&workQueue->Head); + + if(listEntryRev == NULL) { + + // There's no work to do. If we are allowed to stop, then stop. + if(workQueue->Stop) { + DoTraceMessage(TRCINFO, "WorkerThread: WQ %p exit", workQueue); + break; + } + + DoTraceMessage(TRCINFO, "WorkerThread: WQ %p wait", workQueue); + + // Otherwise, wait for more operations to be enqueued. + KeWaitForSingleObject(&workQueue->Event, + Executive, KernelMode, FALSE, 0); + continue; + } + + DoTraceMessage(TRCINFO, "WorkerThread: WQ %p process", workQueue); + + // Need to reverse the flushed list in order to preserve the FIFO order + listEntry = NULL; + while (listEntryRev != NULL) { + next = listEntryRev->Next; + listEntryRev->Next = listEntry; + listEntry = listEntryRev; + listEntryRev = next; + } + + // Now process the correctly ordered list of operations one by one + while(listEntry) { + + PWSKSAMPLE_SOCKET_OP_CONTEXT socketOpContext = + CONTAINING_RECORD(listEntry, + WSKSAMPLE_SOCKET_OP_CONTEXT, QueueEntry); + PWSKSAMPLE_OP_HANDLER_FN opHandler = socketOpContext->OpHandler; + + listEntry = listEntry->Next; + + opHandler(socketOpContext); + } + } + + PsTerminateSystemThread(STATUS_SUCCESS); +} + +// Operation handler for creating and setting up a listening socket +VOID +WskSampleOpStartListen( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ) +{ + NTSTATUS status; + WSK_PROVIDER_NPI wskProviderNpi; + + PAGED_CODE(); + + // This is the very first operation that runs in the worker thread. + // This operation is made up of multiple WSK calls, and no other + // operations will be processed until this compound operation is + // finished synchronously in the context of the worker thread. + + // Capture the WSK Provider NPI + status = WskCaptureProviderNPI( + &WskSampleRegistration, + WSK_INFINITE_WAIT, + &wskProviderNpi); + + if(NT_SUCCESS(status)) { + + // Create a listening socket + WskSampleSetupListeningSocket(&wskProviderNpi, SocketOpContext); + + // Release the WSK provider NPI since we won't use it anymore + WskReleaseProviderNPI(&WskSampleRegistration); + } + else { + // WskCaptureProviderNPI will fail if WskDeregister is called + // from the Driver Unload routine before the WSK subsystem + // becomes ready. + DoTraceMessage(TRCINFO, + "OpStartListen: WskCaptureProviderNPI failed 0x%lx", status); + } +} + +// Create and prepare a listening socket for accepting incoming connections +VOID +WskSampleSetupListeningSocket( + _In_ PWSK_PROVIDER_NPI WskProviderNpi, + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ) +{ + NTSTATUS status; + WSK_EVENT_CALLBACK_CONTROL callbackControl; + KEVENT compEvent; + ULONG optionValue; + PWSK_SOCKET listeningSocket = NULL; + PWSK_PROVIDER_LISTEN_DISPATCH dispatch = NULL; + PWSKSAMPLE_SOCKET_CONTEXT socketContext = SocketOpContext->SocketContext; + PIRP irp = SocketOpContext->Irp; + + PAGED_CODE(); + + KeInitializeEvent(&compEvent, SynchronizationEvent, FALSE); + + if(socketContext->Socket != NULL) { + + // If there's already a socket then we must be getting called because + // the WskAcceptevent indicated a NULL AcceptSocket, i.e. the listening + // socket is no longer functional. So, we will close the existing + // listening socket and try to create a new one. + + if(socketContext->StopListening) { + // Listening socket is already being closed because the driver + // is being unloaded. So, there's no need to recreate it. + return; + } + + IoReuseIrp(irp, STATUS_UNSUCCESSFUL); + + IoSetCompletionRoutine(irp, + WskSampleSyncIrpCompletionRoutine, + &compEvent, TRUE, TRUE, TRUE); + + ((PWSK_PROVIDER_LISTEN_DISPATCH) + socketContext->Socket->Dispatch)-> + WskCloseSocket(socketContext->Socket, irp); + + KeWaitForSingleObject(&compEvent, Executive, KernelMode, FALSE, NULL); + + ASSERT(NT_SUCCESS(irp->IoStatus.Status)); + socketContext->Socket = NULL; + + IoReuseIrp(irp, STATUS_UNSUCCESSFUL); + } + else { + // First, configure the WSK client such that WskAcceptEvent callback + // will be automatically enabled on the listening socket upon creation. + callbackControl.NpiId = (PNPIID)&NPI_WSK_INTERFACE_ID; + callbackControl.EventMask = WSK_EVENT_ACCEPT; + status = WskProviderNpi->Dispatch->WskControlClient( + WskProviderNpi->Client, + WSK_SET_STATIC_EVENT_CALLBACKS, + sizeof(callbackControl), + &callbackControl, + 0, + NULL, + NULL, + NULL); + if(!NT_SUCCESS(status)) { + DoTraceMessage(TRCERROR, + "SetupListeningSocket: WSK_SET_STATIC_EVENT_CALLBACKS FAIL 0x%lx", + status); + goto failexit; + } + } + + // Create a listening socket over AF_INET6 address family. Put the socket + // into dual-family mode by setting IPV6_V6ONLY option to FALSE so that + // AF_INET traffic is also handled over the same socket. + + IoSetCompletionRoutine(irp, + WskSampleSyncIrpCompletionRoutine, + &compEvent, TRUE, TRUE, TRUE); + + // We do not need to check the return status since the actual completion + // status will be captured from the IRP after the IRP is completed. + WskProviderNpi->Dispatch->WskSocket( + WskProviderNpi->Client, + AF_INET6, + SOCK_STREAM, + IPPROTO_TCP, + WSK_FLAG_LISTEN_SOCKET, + socketContext, + &WskSampleClientListenDispatch, + NULL, // Process + NULL, // Thread + NULL, // SecurityDescriptor + irp); + + KeWaitForSingleObject(&compEvent, Executive, KernelMode, FALSE, NULL); + + if(!NT_SUCCESS(irp->IoStatus.Status)) { + DoTraceMessage(TRCERROR, "SetupListeningSocket: WskSocket FAIL 0x%lx", + irp->IoStatus.Status); + goto failexit; + } + + listeningSocket = (PWSK_SOCKET)irp->IoStatus.Information; + dispatch = (PWSK_PROVIDER_LISTEN_DISPATCH)listeningSocket->Dispatch; + + // Set IPV6_V6ONLY to FALSE before the bind operation + + optionValue = 0; + + IoReuseIrp(irp, STATUS_UNSUCCESSFUL); + IoSetCompletionRoutine(irp, + WskSampleSyncIrpCompletionRoutine, + &compEvent, TRUE, TRUE, TRUE); + + // We do not need to check the return status since the actual completion + // status will be captured from the IRP after the IRP is completed. + dispatch->WskControlSocket( + listeningSocket, + WskSetOption, + IPV6_V6ONLY, + IPPROTO_IPV6, + sizeof(optionValue), + &optionValue, + 0, + NULL, + NULL, + irp); + + KeWaitForSingleObject(&compEvent, Executive, KernelMode, FALSE, NULL); + + if(!NT_SUCCESS(irp->IoStatus.Status)) { + DoTraceMessage(TRCERROR, "SetupListeningSocket: IPV6_V6ONLY FAIL 0x%lx", + irp->IoStatus.Status); + goto failexit; + } + + // Bind the socket to the wildcard address. Once bind is completed, + // WSK provider will make WskAcceptEvent callbacks as connections arrive. + IoReuseIrp(irp, STATUS_UNSUCCESSFUL); + IoSetCompletionRoutine(irp, + WskSampleSyncIrpCompletionRoutine, + &compEvent, TRUE, TRUE, TRUE); + + // We do not need to check the return status since the actual completion + // status will be captured from the IRP after the IRP is completed. + dispatch->WskBind( + listeningSocket, + (PSOCKADDR)&IPv6ListeningAddress, + 0, + irp); + + KeWaitForSingleObject(&compEvent, Executive, KernelMode, FALSE, NULL); + + if(!NT_SUCCESS(irp->IoStatus.Status)) { + DoTraceMessage(TRCERROR, "SetupListeningSocket: WskBind FAIL 0x%lx", + irp->IoStatus.Status); + goto failexit; + } + + // Store the WSK socket pointer in the socket context only if everthing + // has succeeded. Otherwise, the listening WSK socket, if one was created + // successfully, will be closed inline below. + ASSERT(socketContext->Socket == NULL); + socketContext->Socket = listeningSocket; + + DoTraceMessage(TRCINFO, "SetupListeningSocket: %p %p", + socketContext, SocketOpContext); + + return; + +failexit: + + if(listeningSocket) { + + IoReuseIrp(irp, STATUS_UNSUCCESSFUL); + IoSetCompletionRoutine(irp, + WskSampleSyncIrpCompletionRoutine, + &compEvent, TRUE, TRUE, TRUE); + + dispatch->WskCloseSocket(listeningSocket, irp); + KeWaitForSingleObject(&compEvent, Executive, KernelMode, FALSE, NULL); + } +} + +// IRP completion routine used for synchronously waiting for completion +_Use_decl_annotations_ +NTSTATUS +WskSampleSyncIrpCompletionRoutine( + PDEVICE_OBJECT Reserved, + PIRP Irp, + PVOID Context + ) +{ + PKEVENT compEvent = (PKEVENT)Context; + _Analysis_assume_(Context != NULL); + UNREFERENCED_PARAMETER(Reserved); + UNREFERENCED_PARAMETER(Irp); + KeSetEvent(compEvent, 2, FALSE); + return STATUS_MORE_PROCESSING_REQUIRED; +} + +// Operation handler for stopping listening +VOID +WskSampleOpStopListen( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ) +{ + PWSKSAMPLE_SOCKET_CONTEXT socketContext; + + PAGED_CODE(); + + socketContext = SocketOpContext->SocketContext; + + DoTraceMessage(TRCINFO, "OpStopListen: %p %p", + socketContext, SocketOpContext); + + socketContext->StopListening = TRUE; + + if(socketContext->Socket == NULL) { + // Listening socket was NOT created due to some failure. + // All we need to do is to free up the socket context. + WskSampleEnqueueOp(SocketOpContext, WskSampleOpFree); + } + else { + // Enqueue an operation to close the listening socket + WskSampleEnqueueOp(SocketOpContext, WskSampleOpClose); + } +} + +// Listening socket callback which is invoked whenever a new connection arrives. +NTSTATUS +WSKAPI +WskSampleAcceptEvent( + _In_ PVOID SocketContext, + _In_ ULONG Flags, + _In_ PSOCKADDR LocalAddress, + _In_ PSOCKADDR RemoteAddress, + _In_opt_ PWSK_SOCKET AcceptSocket, + _Outptr_result_maybenull_ PVOID *AcceptSocketContext, + _Outptr_result_maybenull_ CONST WSK_CLIENT_CONNECTION_DISPATCH **AcceptSocketDispatch + ) +{ + PWSKSAMPLE_SOCKET_CONTEXT socketContext = NULL; + PWSKSAMPLE_SOCKET_CONTEXT listeningSocketContext; + ULONG i; + + UNREFERENCED_PARAMETER(Flags); + UNREFERENCED_PARAMETER(LocalAddress); + UNREFERENCED_PARAMETER(RemoteAddress); + + listeningSocketContext = (PWSKSAMPLE_SOCKET_CONTEXT)SocketContext; + + if(AcceptSocket == NULL) { + // If WSK provider makes a WskAcceptEvent callback with NULL + // AcceptSocket, this means that the listening socket is no longer + // functional. The WSK client may handle this situation by trying + // to create a new listening socket or by restarting the driver, etc. + // In this sample, we will attempt to close the existing listening + // socket and create a new one. Note that the WskSampleAcceptEvent + // callback is guaranteed to be invoked with a NULL AcceptSocket + // by the WSK subsystem only *once*. So, we can safely use the same + // operation context that was originally used for enqueueing the first + // WskSampleStartListen operation on the listening socket. The + // WskSampleStartListen operation will close the existing listening + // socket and create a new one. + WskSampleEnqueueOp(&listeningSocketContext->OpContext[0], + WskSampleOpStartListen); + return STATUS_REQUEST_NOT_ACCEPTED; + } + + // Allocate socket context for the newly accepted socket. + socketContext = WskSampleAllocateSocketContext( + &WskSampleWorkQueue, WSKSAMPLE_DATA_BUFFER_LENGTH); + + if(socketContext == NULL) { + return STATUS_REQUEST_NOT_ACCEPTED; + } + + socketContext->Socket = AcceptSocket; + + DoTraceMessage(TRCINFO, "AcceptEvent: %p", socketContext); + + // Enqueue receive operations on the accepted socket. Whenever a receive + // operation is completed successfully, the received data will be echoed + // back to the peer via a send operation. Whenever a send operation is + // completed, a new receive request will be issued over the connection. + // This will continue until the connection is closed by the peer. + for(i = 0; i < WSKSAMPLE_OP_COUNT; i++) { + _Analysis_assume_(socketContext == socketContext->OpContext[i].SocketContext); + WskSampleEnqueueOp(&socketContext->OpContext[i], WskSampleOpReceive); + } + + // Since we will not use any callbacks on the accepted socket, we specify no + // socketContext or callback dispatch table pointer for the accepted socket. + *AcceptSocketContext = NULL; + *AcceptSocketDispatch = NULL; + + return STATUS_SUCCESS; +} + +// Operation handler for issuing a receive request on a connected socket +VOID +WskSampleOpReceive( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ) +{ + PWSKSAMPLE_SOCKET_CONTEXT socketContext; + + PAGED_CODE(); + + socketContext = SocketOpContext->SocketContext; + + if(socketContext->Closing || socketContext->Disconnecting) { + // Do not call WskReceive if socket is being disconnected + // or closed. The operation context will not be used any more. + DoTraceMessage(TRCINFO, "OpReceive: %p %p SKIP", + socketContext, SocketOpContext); + } + else { + WSK_BUF wskbuf; + CONST WSK_PROVIDER_CONNECTION_DISPATCH *dispatch; + + dispatch = socketContext->Socket->Dispatch; + + wskbuf.Offset = 0; + wskbuf.Length = SocketOpContext->BufferLength; + wskbuf.Mdl = SocketOpContext->DataMdl; + + IoReuseIrp(SocketOpContext->Irp, STATUS_UNSUCCESSFUL); + IoSetCompletionRoutine(SocketOpContext->Irp, + WskSampleReceiveIrpCompletionRoutine, + SocketOpContext, TRUE, TRUE, TRUE); + + DoTraceMessage(TRCINFO, "OpReceive: %p %p %Iu", + socketContext, SocketOpContext, wskbuf.Length); + + // No need to check the return status here. The IRP completion + // routine will take action based on the completion status. + dispatch->WskReceive( + socketContext->Socket, + &wskbuf, + 0, + SocketOpContext->Irp); + } +} + +// IRP completion routine for WskReceive requests +_Use_decl_annotations_ +NTSTATUS +WskSampleReceiveIrpCompletionRoutine( + PDEVICE_OBJECT Reserved, + PIRP Irp, + PVOID Context + ) +{ + PWSKSAMPLE_SOCKET_OP_CONTEXT socketOpContext; + PWSKSAMPLE_SOCKET_CONTEXT socketContext; + + UNREFERENCED_PARAMETER(Reserved); + + _Analysis_assume_(Context != NULL); + + socketOpContext = (PWSKSAMPLE_SOCKET_OP_CONTEXT)Context; + socketContext = socketOpContext->SocketContext; + + DoTraceMessage(TRCINFO, + "ReceiveIrpCompletionRoutine: %p %p 0x%lx %Iu", + socketContext, socketOpContext, Irp->IoStatus.Status, + Irp->IoStatus.Information); + + if(!NT_SUCCESS(Irp->IoStatus.Status)) { + // Receive failed. Enqueue an operation to close the socket + WskSampleEnqueueOp(socketOpContext, WskSampleOpClose); + } + else { + if(Irp->IoStatus.Information == 0) { + // Successful receive completion with 0 bytes means the peer + // has gracefully disconnected its half of the connection. + // So, we enqueue an operation to disconnect our half. + WskSampleEnqueueOp(socketOpContext, WskSampleOpDisconnect); + } + else { + // Receive has completed with some data. So, we enqueue an + // operation to send the data back. Note that the data + // buffer is attached to the operation context that is being + // queued. We just need to remember the actual length of + // data received into the buffer. + socketOpContext->DataLength = Irp->IoStatus.Information; + WskSampleEnqueueOp(socketOpContext, WskSampleOpSend); + } + } + + return STATUS_MORE_PROCESSING_REQUIRED; +} + +// Operation handler for issuing a send request on a connected socket +VOID +WskSampleOpSend( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ) +{ + PWSKSAMPLE_SOCKET_CONTEXT socketContext; + + PAGED_CODE(); + + socketContext = SocketOpContext->SocketContext; + + if(socketContext->Closing || socketContext->Disconnecting) { + // Do not call WskSend if socket is being disconnected + // or closed. The operation context will not be used any more. + DoTraceMessage(TRCINFO, "OpSend: %p %p SKIP", + socketContext, SocketOpContext); + } + else { + WSK_BUF wskbuf; + CONST WSK_PROVIDER_CONNECTION_DISPATCH *dispatch; + + dispatch = socketContext->Socket->Dispatch; + + wskbuf.Offset = 0; + wskbuf.Length = SocketOpContext->DataLength; + wskbuf.Mdl = SocketOpContext->DataMdl; + + IoReuseIrp(SocketOpContext->Irp, STATUS_UNSUCCESSFUL); + IoSetCompletionRoutine(SocketOpContext->Irp, + WskSampleSendIrpCompletionRoutine, + SocketOpContext, TRUE, TRUE, TRUE); + + DoTraceMessage(TRCINFO, "OpSend: %p %p %Iu", + socketContext, SocketOpContext, wskbuf.Length); + + // No need to check the return status here. The IRP completion + // routine will take action based on the completion status. + dispatch->WskSend( + socketContext->Socket, + &wskbuf, + 0, + SocketOpContext->Irp); + } +} + +// IRP completion routine for WskSend requests +_Use_decl_annotations_ +NTSTATUS +WskSampleSendIrpCompletionRoutine( + PDEVICE_OBJECT Reserved, + PIRP Irp, + PVOID Context + ) +{ + PWSKSAMPLE_SOCKET_OP_CONTEXT socketOpContext; + PWSKSAMPLE_SOCKET_CONTEXT socketContext; + + UNREFERENCED_PARAMETER(Reserved); + + _Analysis_assume_(Context != NULL); + + socketOpContext = (PWSKSAMPLE_SOCKET_OP_CONTEXT)Context; + socketContext = socketOpContext->SocketContext; + + DoTraceMessage(TRCINFO, + "SendIrpCompletionRoutine: %p %p 0x%lx %Iu", socketContext, + socketOpContext, Irp->IoStatus.Status, Irp->IoStatus.Information); + + if(!NT_SUCCESS(Irp->IoStatus.Status)) { + // Send failed. Enqueue an operation to close the socket. + WskSampleEnqueueOp(socketOpContext, WskSampleOpClose); + } + else { + // Send succeeded. Enqueue an operation to receive more data. + WskSampleEnqueueOp(socketOpContext, WskSampleOpReceive); + } + + return STATUS_MORE_PROCESSING_REQUIRED; +} + +// Operation handler for issuing a disconnect request on a connected socket +VOID +WskSampleOpDisconnect( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ) +{ + PWSKSAMPLE_SOCKET_CONTEXT socketContext; + + PAGED_CODE(); + + socketContext = SocketOpContext->SocketContext; + + if(socketContext->Closing || socketContext->Disconnecting) { + // Do not call WskDisconnect if socket is already being + // disconnected or closed. A disconnect operation may get + // enqueued multiple times as a result of multiple outstanding + // receive requests getting completed with success and 0 bytes. + DoTraceMessage(TRCINFO, "OpDisconnect: %p %p SKIP", + socketContext, SocketOpContext); + } + else { + CONST WSK_PROVIDER_CONNECTION_DISPATCH *dispatch; + + dispatch = socketContext->Socket->Dispatch; + + socketContext->Disconnecting = TRUE; + + IoReuseIrp(SocketOpContext->Irp, STATUS_UNSUCCESSFUL); + IoSetCompletionRoutine(SocketOpContext->Irp, + WskSampleDisconnectIrpCompletionRoutine, + SocketOpContext, TRUE, TRUE, TRUE); + + DoTraceMessage(TRCINFO, "OpDisconnect: %p %p", + socketContext, SocketOpContext); + + // No need to check the return status here. The IRP completion + // routine will take action based on the completion status. + dispatch->WskDisconnect( + socketContext->Socket, + NULL, + 0, + SocketOpContext->Irp); + } +} + +// IRP completion routine for WskDisconnect requests +_Use_decl_annotations_ +NTSTATUS +WskSampleDisconnectIrpCompletionRoutine( + PDEVICE_OBJECT Reserved, + PIRP Irp, + PVOID Context + ) +{ + PWSKSAMPLE_SOCKET_OP_CONTEXT socketOpContext; + PWSKSAMPLE_SOCKET_CONTEXT socketContext; + + UNREFERENCED_PARAMETER(Reserved); + UNREFERENCED_PARAMETER(Irp); + + _Analysis_assume_(Context != NULL); + + socketOpContext = (PWSKSAMPLE_SOCKET_OP_CONTEXT)Context; + socketContext = socketOpContext->SocketContext; + + DoTraceMessage(TRCINFO, "DisconnectIrpCompletionRoutine: %p %p 0x%lx", + socketContext, socketOpContext, Irp->IoStatus.Status); + + // Disconnect completed. Enqueue an operation to close the socket. + WskSampleEnqueueOp(socketOpContext, WskSampleOpClose); + + return STATUS_MORE_PROCESSING_REQUIRED; +} + +// Operation handler for closing a socket +VOID +WskSampleOpClose( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ) +{ + PWSKSAMPLE_SOCKET_CONTEXT socketContext; + + PAGED_CODE(); + + socketContext = SocketOpContext->SocketContext; + + if(socketContext->Closing) { + // Do not call WskClose if socket is already being closed. + // A close operation may get enqueued multiple times as a result + // of multiple outstanding send/receive/disconnect operations + // getting completed with failure. + DoTraceMessage(TRCINFO, "OpClose: %p %p SKIP", + socketContext, SocketOpContext); + } + else { + CONST WSK_PROVIDER_BASIC_DISPATCH *dispatch; + + socketContext->Closing = TRUE; + + dispatch = socketContext->Socket->Dispatch; + + IoReuseIrp(SocketOpContext->Irp, STATUS_UNSUCCESSFUL); + IoSetCompletionRoutine(SocketOpContext->Irp, + WskSampleCloseIrpCompletionRoutine, + SocketOpContext, TRUE, TRUE, TRUE); + + DoTraceMessage(TRCINFO,"OpClose: %p %p", socketContext,SocketOpContext); + + // No need to check the return status here. The IRP completion + // routine will take action based on the completion status. + dispatch->WskCloseSocket( + socketContext->Socket, + SocketOpContext->Irp); + } +} + +// IRP completion routine for WskCloseSocket requests +_Use_decl_annotations_ +NTSTATUS +WskSampleCloseIrpCompletionRoutine( + PDEVICE_OBJECT Reserved, + PIRP Irp, + PVOID Context + ) +{ + PWSKSAMPLE_SOCKET_OP_CONTEXT socketOpContext; + PWSKSAMPLE_SOCKET_CONTEXT socketContext; + + UNREFERENCED_PARAMETER(Reserved); + UNREFERENCED_PARAMETER(Irp); + + _Analysis_assume_(Context != NULL); + + socketOpContext = (PWSKSAMPLE_SOCKET_OP_CONTEXT)Context; + socketContext = socketOpContext->SocketContext; + + // WskCloseSocket can never fail. + ASSERT(NT_SUCCESS(Irp->IoStatus.Status)); + + DoTraceMessage(TRCINFO, "CloseIrpCompletionRoutine: %p %p 0x%lx", + socketContext, socketOpContext, Irp->IoStatus.Status); + + // Enqueue an operation to free the socket context. Since the + // completion of WskCloseSocket guarantees that there are no + // outstanding requests or callbacks on the socket, we can + // safely free the socket context. However, we still can NOT + // free the socket context directly here since there may still be + // queued operations not yet issued over the socket. Thus, we need + // to queue this "free" operation behind any other queued + // operations (which will not be issued over the socket since the + // socket context is marked as "closing"). Note that NO new + // operations can be queued behind the "free" operation for a + // given socket since we enqueue new operations only from the + // context of IRP completions on the socket. + WskSampleEnqueueOp(socketOpContext, WskSampleOpFree); + + return STATUS_MORE_PROCESSING_REQUIRED; +} + +// Operation handler for freeing the context block for a closed socket +VOID +WskSampleOpFree( + _In_ PWSKSAMPLE_SOCKET_OP_CONTEXT SocketOpContext + ) +{ + PWSKSAMPLE_SOCKET_CONTEXT socketContext; + + PAGED_CODE(); + + socketContext = SocketOpContext->SocketContext; + + DoTraceMessage(TRCINFO, "OpFree: %p %p", socketContext, SocketOpContext); + + ASSERT(socketContext->Closing || socketContext->StopListening); + WskSampleFreeSocketContext(socketContext); +} + diff --git a/network/wsk/echosrv/wsksmple.rc b/network/wsk/echosrv/wsksmple.rc new file mode 100644 index 00000000..572afe1c --- /dev/null +++ b/network/wsk/echosrv/wsksmple.rc @@ -0,0 +1,11 @@ +#include <windows.h> +#include <ntverp.h> + +#define VER_FILETYPE VFT_DRV +#define VER_FILESUBTYPE VFT2_DRV_SYSTEM +#define VER_FILEDESCRIPTION_STR "WSK Sample TCP Echo Server" +#define VER_INTERNALNAME_STR "wsksample.sys" +#define VER_ORIGINALFILENAME_STR "wsksample.sys" + +#include "common.ver" + |
