2019-03-08 22:33:15 +01:00
/* SPDX-License-Identifier: GPL-2.0
*
* Copyright ( C ) 2018 - 2019 WireGuard LLC . All Rights Reserved .
*/
# include <stdio.h>
# include <string.h>
2019-05-30 21:39:33 +02:00
# include <ntifs.h>
2019-03-08 22:33:15 +01:00
# include <wdm.h>
2019-05-31 00:50:07 +02:00
# include <wdmguid.h>
2019-03-08 22:33:15 +01:00
# include <wdmsec.h>
# include <ndis.h>
2019-05-31 00:50:07 +02:00
# include <ndisguid.h>
2019-03-08 22:33:15 +01:00
# include <bcrypt.h>
# include <ntstrsafe.h>
2019-05-30 21:39:33 +02:00
# include "undocumented.h"
2019-03-08 22:33:15 +01:00
# pragma warning(disable : 4100) // unreferenced formal parameter
# pragma warning(disable : 4200) // nonstandard extension used: zero-sized array in struct/union
# pragma warning(disable : 4204) // nonstandard extension used: non-constant aggregate initializer
# pragma warning(disable : 4221) // nonstandard extension used: <member>: cannot be initialized using address of automatic variable <variable>
2019-03-29 06:04:45 +01:00
# define TUN_DEVICE_NAME L"WINTUN%u"
2019-03-08 22:33:15 +01:00
2019-03-29 06:04:45 +01:00
# define TUN_VENDOR_NAME "Wintun Tunnel"
# define TUN_VENDOR_ID 0xFFFFFF00
# define TUN_LINK_SPEED 100000000000ULL // 100gbps
2019-03-08 22:33:15 +01:00
2019-06-05 11:51:26 +02:00
# define TUN_EXCH_MAX_PACKETS 256 // Maximum number of full-sized exchange packets that can be exchanged in a single read/write
2019-03-29 06:04:45 +01:00
# define TUN_EXCH_MAX_PACKET_SIZE 0xF000 // Maximum exchange packet size - empirically determined by net buffer list (pool) limitations
# define TUN_EXCH_ALIGNMENT 16 // Memory alignment in exchange buffers
# define TUN_EXCH_MAX_IP_PACKET_SIZE (TUN_EXCH_MAX_PACKET_SIZE - sizeof(TUN_PACKET)) // Maximum IP packet size (headers + payload)
# define TUN_EXCH_MAX_BUFFER_SIZE (TUN_EXCH_MAX_PACKETS * TUN_EXCH_MAX_PACKET_SIZE) // Maximum size of read/write exchange buffer
# define TUN_EXCH_MIN_BUFFER_SIZE_READ TUN_EXCH_MAX_PACKET_SIZE // Minimum size of read exchange buffer
# define TUN_EXCH_MIN_BUFFER_SIZE_WRITE (sizeof(TUN_PACKET)) // Minimum size of write exchange buffer
# define TUN_QUEUE_MAX_NBLS 1000
2019-03-08 22:33:15 +01:00
typedef struct _TUN_PACKET {
2019-03-29 06:04:45 +01:00
ULONG Size ; // Size of packet data (TUN_EXCH_MAX_IP_PACKET_SIZE max)
2019-03-08 22:33:15 +01:00
_Field_size_bytes_ ( Size )
__declspec ( align ( TUN_EXCH_ALIGNMENT ) )
2019-03-29 06:04:45 +01:00
UCHAR Data [ ] ; // Packet data
2019-03-08 22:33:15 +01:00
} TUN_PACKET ;
typedef enum _TUN_STATE {
2019-03-29 06:04:45 +01:00
TUN_STATE_HALTED = 0 , // The Halted state is the initial state of all adapters. When an adapter is in the Halted state, NDIS can call the driver's MiniportInitializeEx function to initialize the adapter.
TUN_STATE_SHUTDOWN , // In the Shutdown state, a system shutdown and restart must occur before the system can use the adapter again
TUN_STATE_INITIALIZING , // In the Initializing state, a miniport driver completes any operations that are required to initialize an adapter.
2019-04-10 13:42:16 +02:00
TUN_STATE_HALTING , // In the Halting state, a miniport driver completes any operations that are required to halt an adapter.
2019-03-29 06:04:45 +01:00
TUN_STATE_PAUSED , // In the Paused state, the adapter does not indicate received network data or accept send requests.
TUN_STATE_RESTARTING , // In the Restarting state, a miniport driver completes any operations that are required to restart send and receive operations for an adapter.
TUN_STATE_RUNNING , // In the Running state, a miniport driver performs send and receive processing for an adapter.
TUN_STATE_PAUSING , // In the Pausing state, a miniport driver completes any operations that are required to stop send and receive operations for an adapter.
2019-03-08 22:33:15 +01:00
} TUN_STATE ;
typedef struct _TUN_CTX {
2019-03-29 06:04:45 +01:00
volatile TUN_STATE State ;
2019-04-03 05:46:50 +02:00
volatile NDIS_DEVICE_POWER_STATE PowerState ;
2019-06-03 12:56:50 +02:00
/* Used like RCU. When we're making use of queues, we take a shared lock. When we want to
* drain the queues and toggle the state , we take an exclusive lock before toggling the
* atomic and then releasing . It ' s similar to setting the atomic and then calling rcu_barrier ( ) . */
2019-05-24 14:18:18 +02:00
EX_SPIN_LOCK TransitionLock ;
2019-03-08 22:33:15 +01:00
2019-03-29 06:04:45 +01:00
NDIS_HANDLE MiniportAdapterHandle ;
NDIS_STATISTICS_INFO Statistics ;
2019-03-08 22:33:15 +01:00
2019-04-03 05:46:50 +02:00
volatile LONG64 ActiveTransactionCount ;
2019-03-08 22:33:15 +01:00
2019-05-31 00:50:07 +02:00
volatile struct {
FILE_OBJECT * FileObject ;
PVOID Handle ;
} PnPNotifications ;
2019-03-08 22:33:15 +01:00
struct {
2019-03-29 06:04:45 +01:00
NDIS_HANDLE Handle ;
2019-04-10 13:42:16 +02:00
volatile LONG64 RefCount ;
2019-04-11 19:28:33 +02:00
IO_REMOVE_LOCK RemoveLock ;
2019-04-10 13:42:16 +02:00
struct {
KSPIN_LOCK Lock ;
IO_CSQ Csq ;
LIST_ENTRY List ;
} ReadQueue ;
2019-05-30 21:39:33 +02:00
DEVICE_OBJECT * Object ;
2019-03-08 22:33:15 +01:00
} Device ;
2019-04-10 13:42:16 +02:00
struct {
KSPIN_LOCK Lock ;
NET_BUFFER_LIST * FirstNbl , * LastNbl ;
NET_BUFFER * NextNb ;
LONG NumNbl ;
} PacketQueue ;
2019-03-29 06:04:45 +01:00
NDIS_HANDLE NBLPool ;
2019-05-31 14:40:49 +02:00
ULONG NetLuidIndex ;
2019-03-08 22:33:15 +01:00
} TUN_CTX ;
2019-04-03 03:01:28 +02:00
static UINT NdisVersion ;
2019-05-31 00:50:07 +02:00
static PVOID TunNotifyInterfaceChangeHandle ;
static NDIS_HANDLE NdisMiniportDriverHandle ;
2019-05-31 14:40:49 +02:00
static volatile LONG64 AdapterCount ;
2019-03-08 22:33:15 +01:00
# if REG_DWORD == REG_DWORD_BIG_ENDIAN
2019-03-29 06:04:45 +01:00
# define TUN_MEMORY_TAG 'wtun'
# define TunHtons(x) ((USHORT)(x))
# define TunHtonl(x) ((ULONG)(x))
2019-03-08 22:33:15 +01:00
# elif REG_DWORD == REG_DWORD_LITTLE_ENDIAN
2019-03-29 06:04:45 +01:00
# define TUN_MEMORY_TAG 'nutw'
# define TunHtons(x) RtlUshortByteSwap(x)
# define TunHtonl(x) RtlUlongByteSwap(x)
2019-03-08 22:33:15 +01:00
# else
# error "Unable to determine endianess"
# endif
2019-03-29 06:04:45 +01:00
# define TUN_CSQ_INSERT_HEAD ((PVOID)TRUE)
# define TUN_CSQ_INSERT_TAIL ((PVOID)FALSE)
2019-03-27 09:00:19 +01:00
2019-03-29 06:04:45 +01:00
# define InterlockedGet(val) (InterlockedAdd((val), 0))
# define InterlockedGet64(val) (InterlockedAdd64((val), 0))
# define InterlockedGetPointer(val) (InterlockedCompareExchangePointer((val), NULL, NULL))
# define TunPacketAlign(size) (((UINT)(size) + (UINT)(TUN_EXCH_ALIGNMENT - 1)) & ~(UINT)(TUN_EXCH_ALIGNMENT - 1))
# define TunInitUnicodeString(str, buf) { (str)->Length = 0; (str)->MaximumLength = sizeof(buf); (str)->Buffer = buf; }
2019-03-08 22:33:15 +01:00
_IRQL_requires_max_ ( DISPATCH_LEVEL )
_IRQL_requires_same_
2019-04-09 13:16:40 +02:00
static void TunIndicateStatus ( _In_ NDIS_HANDLE MiniportAdapterHandle , _In_ NDIS_MEDIA_CONNECT_STATE MediaConnectState )
2019-03-08 22:33:15 +01:00
{
NDIS_LINK_STATE state = {
. Header = {
. Type = NDIS_OBJECT_TYPE_DEFAULT ,
. Revision = NDIS_LINK_STATE_REVISION_1 ,
. Size = NDIS_SIZEOF_LINK_STATE_REVISION_1
} ,
2019-04-09 13:16:40 +02:00
. MediaConnectState = MediaConnectState ,
2019-03-08 22:33:15 +01:00
. MediaDuplexState = MediaDuplexStateFull ,
. XmitLinkSpeed = TUN_LINK_SPEED ,
. RcvLinkSpeed = TUN_LINK_SPEED ,
. PauseFunctions = NdisPauseFunctionsUnsupported
} ;
NDIS_STATUS_INDICATION t = {
. Header = {
. Type = NDIS_OBJECT_TYPE_STATUS_INDICATION ,
. Revision = NDIS_STATUS_INDICATION_REVISION_1 ,
. Size = NDIS_SIZEOF_STATUS_INDICATION_REVISION_1
} ,
2019-04-09 13:16:40 +02:00
. SourceHandle = MiniportAdapterHandle ,
2019-03-08 22:33:15 +01:00
. StatusCode = NDIS_STATUS_LINK_STATE ,
. StatusBuffer = & state ,
. StatusBufferSize = sizeof ( state )
} ;
2019-04-09 13:16:40 +02:00
NdisMIndicateStatusEx ( MiniportAdapterHandle , & t ) ;
2019-03-08 22:33:15 +01:00
}
_IRQL_requires_max_ ( DISPATCH_LEVEL )
2019-05-31 12:49:31 +02:00
static void TunCompleteRequest ( _Inout_ TUN_CTX * ctx , _Inout_ IRP * irp , _In_ NTSTATUS status , _In_ CCHAR priority_boost )
2019-03-08 22:33:15 +01:00
{
2019-05-31 12:49:31 +02:00
irp - > IoStatus . Status = status ;
IoCompleteRequest ( irp , priority_boost ) ;
IoReleaseRemoveLock ( & ctx - > Device . RemoveLock , irp ) ;
2019-03-08 22:33:15 +01:00
}
2019-03-22 13:27:35 +01:00
_IRQL_requires_same_
2019-03-28 11:21:51 +01:00
_Must_inspect_result_
2019-05-24 14:18:18 +02:00
_Requires_lock_held_ ( ctx - > TransitionLock )
2019-04-05 20:11:08 +02:00
static NTSTATUS TunCheckForPause ( _Inout_ TUN_CTX * ctx )
2019-03-22 13:27:35 +01:00
{
2019-04-05 20:11:08 +02:00
ASSERT ( InterlockedGet64 ( & ctx - > ActiveTransactionCount ) < MAXLONG64 ) ;
InterlockedIncrement64 ( & ctx - > ActiveTransactionCount ) ;
2019-03-22 13:27:35 +01:00
return
2019-05-24 14:18:18 +02:00
InterlockedGet64 ( & ctx - > Device . RefCount ) < = 0 ? NDIS_STATUS_SEND_ABORTED :
InterlockedGet ( ( LONG * ) & ctx - > State ) ! = TUN_STATE_RUNNING ? STATUS_NDIS_PAUSED :
InterlockedGet ( ( LONG * ) & ctx - > PowerState ) > = NdisDeviceStateD1 ? STATUS_NDIS_LOW_POWER_STATE :
2019-03-27 09:00:19 +01:00
STATUS_SUCCESS ;
2019-03-22 13:27:35 +01:00
}
2019-03-08 22:33:15 +01:00
_IRQL_requires_max_ ( DISPATCH_LEVEL )
2019-04-05 20:11:08 +02:00
static NDIS_STATUS TunCompletePause ( _Inout_ TUN_CTX * ctx , _In_ BOOLEAN async_completion )
2019-03-08 22:33:15 +01:00
{
2019-04-05 20:11:08 +02:00
ASSERT ( InterlockedGet64 ( & ctx - > ActiveTransactionCount ) > 0 ) ;
if ( ! InterlockedDecrement64 ( & ctx - > ActiveTransactionCount ) & &
2019-04-03 05:46:50 +02:00
InterlockedCompareExchange ( ( LONG * ) & ctx - > State , TUN_STATE_PAUSED , TUN_STATE_PAUSING ) = = TUN_STATE_PAUSING ) {
if ( async_completion )
2019-03-29 21:42:46 +01:00
NdisMPauseComplete ( ctx - > MiniportAdapterHandle ) ;
2019-04-03 05:46:50 +02:00
return NDIS_STATUS_SUCCESS ;
2019-03-27 13:44:55 +01:00
}
2019-04-03 05:46:50 +02:00
return NDIS_STATUS_PENDING ;
2019-03-08 22:33:15 +01:00
}
2019-03-27 09:00:19 +01:00
static IO_CSQ_INSERT_IRP_EX TunCsqInsertIrpEx ;
_Use_decl_annotations_
static NTSTATUS TunCsqInsertIrpEx ( IO_CSQ * Csq , IRP * Irp , PVOID InsertContext )
{
2019-04-10 13:42:16 +02:00
TUN_CTX * ctx = CONTAINING_RECORD ( Csq , TUN_CTX , Device . ReadQueue . Csq ) ;
( InsertContext = = TUN_CSQ_INSERT_HEAD ? InsertHeadList : InsertTailList ) ( & ctx - > Device . ReadQueue . List , & Irp - > Tail . Overlay . ListEntry ) ;
2019-03-27 09:00:19 +01:00
return STATUS_SUCCESS ;
}
static IO_CSQ_REMOVE_IRP TunCsqRemoveIrp ;
_Use_decl_annotations_
static VOID TunCsqRemoveIrp ( IO_CSQ * Csq , IRP * Irp )
{
RemoveEntryList ( & Irp - > Tail . Overlay . ListEntry ) ;
}
static IO_CSQ_PEEK_NEXT_IRP TunCsqPeekNextIrp ;
_Use_decl_annotations_
static IRP * TunCsqPeekNextIrp ( IO_CSQ * Csq , IRP * Irp , _In_ PVOID PeekContext )
{
2019-04-10 13:42:16 +02:00
TUN_CTX * ctx = CONTAINING_RECORD ( Csq , TUN_CTX , Device . ReadQueue . Csq ) ;
2019-03-27 09:00:19 +01:00
/* If the IRP is non-NULL, we will start peeking from that IRP onwards, else
* we will start from the listhead . This is done under the assumption that
* new IRPs are always inserted at the tail . */
for ( LIST_ENTRY
2019-04-10 13:42:16 +02:00
* head = & ctx - > Device . ReadQueue . List ,
2019-03-27 15:41:21 +01:00
* next = Irp ? Irp - > Tail . Overlay . ListEntry . Flink : head - > Flink ;
next ! = head ;
next = next - > Flink )
2019-03-27 09:00:19 +01:00
{
2019-03-27 15:41:21 +01:00
IRP * irp_next = CONTAINING_RECORD ( next , IRP , Tail . Overlay . ListEntry ) ;
2019-03-27 09:00:19 +01:00
if ( ! PeekContext )
2019-03-27 15:41:21 +01:00
return irp_next ;
2019-03-27 09:00:19 +01:00
2019-03-27 15:41:21 +01:00
IO_STACK_LOCATION * stack = IoGetCurrentIrpStackLocation ( irp_next ) ;
2019-03-27 09:00:19 +01:00
if ( stack - > FileObject = = ( FILE_OBJECT * ) PeekContext )
2019-03-27 15:41:21 +01:00
return irp_next ;
2019-03-27 09:00:19 +01:00
}
return NULL ;
}
_IRQL_raises_ ( DISPATCH_LEVEL )
_IRQL_requires_max_ ( DISPATCH_LEVEL )
2019-05-31 08:42:36 +02:00
_Requires_lock_not_held_ ( CONTAINING_RECORD ( Csq , TUN_CTX , Device . ReadQueue . Csq ) - > Device . ReadQueue . Lock )
2019-04-10 13:42:16 +02:00
_Acquires_lock_ ( CONTAINING_RECORD ( Csq , TUN_CTX , Device . ReadQueue . Csq ) - > Device . ReadQueue . Lock )
2019-03-27 09:00:19 +01:00
static VOID TunCsqAcquireLock ( _In_ IO_CSQ * Csq , _Out_ _At_ ( * Irql , _Post_ _IRQL_saves_ ) KIRQL * Irql )
{
2019-05-31 08:42:36 +02:00
KeAcquireSpinLock ( & CONTAINING_RECORD ( Csq , TUN_CTX , Device . ReadQueue . Csq ) - > Device . ReadQueue . Lock , Irql ) ;
2019-03-27 09:00:19 +01:00
}
_IRQL_requires_ ( DISPATCH_LEVEL )
2019-05-31 08:42:36 +02:00
_Requires_lock_held_ ( CONTAINING_RECORD ( Csq , TUN_CTX , Device . ReadQueue . Csq ) - > Device . ReadQueue . Lock )
2019-04-10 13:42:16 +02:00
_Releases_lock_ ( CONTAINING_RECORD ( Csq , TUN_CTX , Device . ReadQueue . Csq ) - > Device . ReadQueue . Lock )
2019-03-27 09:00:19 +01:00
static VOID TunCsqReleaseLock ( _In_ IO_CSQ * Csq , _In_ _IRQL_restores_ KIRQL Irql )
{
2019-05-31 08:42:36 +02:00
KeReleaseSpinLock ( & CONTAINING_RECORD ( Csq , TUN_CTX , Device . ReadQueue . Csq ) - > Device . ReadQueue . Lock , Irql ) ;
2019-03-27 09:00:19 +01:00
}
static IO_CSQ_COMPLETE_CANCELED_IRP TunCsqCompleteCanceledIrp ;
_Use_decl_annotations_
static VOID TunCsqCompleteCanceledIrp ( IO_CSQ * Csq , IRP * Irp )
{
2019-04-11 19:28:33 +02:00
TUN_CTX * ctx = CONTAINING_RECORD ( Csq , TUN_CTX , Device . ReadQueue . Csq ) ;
2019-05-31 12:49:31 +02:00
TunCompleteRequest ( ctx , Irp , STATUS_CANCELLED , IO_NO_INCREMENT ) ;
2019-03-27 09:00:19 +01:00
}
2019-03-08 22:33:15 +01:00
_IRQL_requires_same_
2019-03-27 09:00:19 +01:00
static void TunSetNBLStatus ( _Inout_opt_ NET_BUFFER_LIST * nbl , _In_ NDIS_STATUS status )
2019-03-08 22:33:15 +01:00
{
2019-03-27 09:00:19 +01:00
for ( ; nbl ; nbl = NET_BUFFER_LIST_NEXT_NBL ( nbl ) )
2019-03-08 22:33:15 +01:00
NET_BUFFER_LIST_STATUS ( nbl ) = status ;
}
_IRQL_requires_max_ ( DISPATCH_LEVEL )
2019-03-28 11:11:43 +01:00
_Must_inspect_result_
static NTSTATUS TunGetIrpBuffer ( _In_ IRP * Irp , _Out_ UCHAR * * buffer , _Out_ ULONG * size )
2019-03-08 22:33:15 +01:00
{
/* Get and validate request parameters. */
ULONG priority ;
IO_STACK_LOCATION * stack = IoGetCurrentIrpStackLocation ( Irp ) ;
switch ( stack - > MajorFunction ) {
case IRP_MJ_READ :
* size = stack - > Parameters . Read . Length ;
priority = NormalPagePriority ;
break ;
case IRP_MJ_WRITE :
* size = stack - > Parameters . Write . Length ;
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
/* If we use MdlMappingNoWrite flag and call NdisMIndicateReceiveNetBufferLists without
* NDIS_RECEIVE_FLAGS_RESOURCES flag we ' ve got a ATTEMPTED_WRITE_TO_READONLY_MEMORY page
* fault . */
priority = NormalPagePriority /*| MdlMappingNoWrite*/ ;
2019-03-08 22:33:15 +01:00
break ;
default :
return STATUS_INVALID_PARAMETER ;
}
/* Get buffer size and address. */
if ( ! Irp - > MdlAddress )
return STATUS_INVALID_PARAMETER ;
2019-03-27 09:00:19 +01:00
ULONG size_mdl ;
NdisQueryMdl ( Irp - > MdlAddress , buffer , & size_mdl , priority ) ;
2019-03-08 22:33:15 +01:00
if ( ! buffer )
return STATUS_INSUFFICIENT_RESOURCES ;
2019-03-27 09:00:19 +01:00
if ( size_mdl < * size )
* size = size_mdl ;
2019-03-08 22:33:15 +01:00
2019-03-28 11:47:30 +01:00
if ( * size > TUN_EXCH_MAX_BUFFER_SIZE )
return STATUS_INVALID_USER_BUFFER ;
2019-03-28 12:07:55 +01:00
switch ( stack - > MajorFunction ) {
case IRP_MJ_READ :
if ( * size < TUN_EXCH_MIN_BUFFER_SIZE_READ )
return STATUS_INVALID_USER_BUFFER ;
break ;
case IRP_MJ_WRITE :
if ( * size < TUN_EXCH_MIN_BUFFER_SIZE_WRITE )
return STATUS_INVALID_USER_BUFFER ;
break ;
}
2019-03-08 22:33:15 +01:00
return STATUS_SUCCESS ;
}
2019-03-28 08:30:13 +01:00
_IRQL_requires_max_ ( DISPATCH_LEVEL )
2019-03-28 11:11:43 +01:00
_Must_inspect_result_
2019-04-10 13:42:16 +02:00
static _Return_type_success_ ( return ! = NULL ) IRP * TunRemoveNextIrp ( _Inout_ TUN_CTX * ctx , _Out_ UCHAR * * buffer , _Out_ ULONG * size )
2019-03-28 08:30:13 +01:00
{
2019-03-28 11:11:43 +01:00
IRP * irp ;
retry :
2019-04-10 13:42:16 +02:00
irp = IoCsqRemoveNextIrp ( & ctx - > Device . ReadQueue . Csq , NULL ) ;
2019-03-28 11:11:43 +01:00
if ( ! irp )
return NULL ;
NTSTATUS status = TunGetIrpBuffer ( irp , buffer , size ) ;
if ( ! NT_SUCCESS ( status ) ) {
2019-05-31 12:49:31 +02:00
TunCompleteRequest ( ctx , irp , status , IO_NO_INCREMENT ) ;
2019-03-28 11:11:43 +01:00
goto retry ;
}
ASSERT ( irp - > IoStatus . Information < = ( ULONG_PTR ) * size ) ;
return irp ;
2019-03-28 08:30:13 +01:00
}
2019-03-28 11:45:57 +01:00
_IRQL_requires_same_
2019-04-12 13:51:33 +02:00
static BOOLEAN TunWontFitIntoIrp ( _In_ IRP * Irp , _In_ ULONG size , _In_ NET_BUFFER * nb )
2019-03-28 11:45:57 +01:00
{
return ( ULONG_PTR ) size < Irp - > IoStatus . Information + TunPacketAlign ( sizeof ( TUN_PACKET ) + NET_BUFFER_DATA_LENGTH ( nb ) ) ;
}
2019-03-08 22:33:15 +01:00
_IRQL_requires_max_ ( DISPATCH_LEVEL )
2019-03-28 11:21:51 +01:00
_Must_inspect_result_
2019-04-09 13:16:40 +02:00
static NTSTATUS TunWriteIntoIrp ( _Inout_ IRP * Irp , _Inout_ UCHAR * buffer , _In_ NET_BUFFER * nb , _Inout_ NDIS_STATISTICS_INFO * statistics )
2019-03-08 22:33:15 +01:00
{
2019-03-27 09:00:19 +01:00
ULONG p_size = NET_BUFFER_DATA_LENGTH ( nb ) ;
2019-03-28 11:11:43 +01:00
TUN_PACKET * p = ( TUN_PACKET * ) ( buffer + Irp - > IoStatus . Information ) ;
2019-03-08 22:33:15 +01:00
2019-03-27 09:00:19 +01:00
p - > Size = p_size ;
void * ptr = NdisGetDataBuffer ( nb , p_size , p - > Data , 1 , 0 ) ;
2019-03-29 16:22:52 +01:00
if ( ! ptr ) {
2019-04-09 13:16:40 +02:00
if ( statistics )
InterlockedIncrement64 ( ( LONG64 * ) & statistics - > ifOutErrors ) ;
2019-03-27 09:00:19 +01:00
return NDIS_STATUS_RESOURCES ;
2019-03-29 16:22:52 +01:00
}
2019-03-27 09:00:19 +01:00
if ( ptr ! = p - > Data )
NdisMoveMemory ( p - > Data , ptr , p_size ) ;
2019-03-08 22:33:15 +01:00
2019-03-28 11:11:43 +01:00
Irp - > IoStatus . Information + = TunPacketAlign ( sizeof ( TUN_PACKET ) + p_size ) ;
2019-03-29 16:22:52 +01:00
2019-04-09 13:16:40 +02:00
InterlockedAdd64 ( ( LONG64 * ) & statistics - > ifHCOutOctets , p_size ) ;
InterlockedAdd64 ( ( LONG64 * ) & statistics - > ifHCOutUcastOctets , p_size ) ;
InterlockedIncrement64 ( ( LONG64 * ) & statistics - > ifHCOutUcastPkts ) ;
2019-03-27 09:00:19 +01:00
return STATUS_SUCCESS ;
}
2019-03-08 22:33:15 +01:00
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
# define NET_BUFFER_LIST_REFCOUNT(nbl) ((volatile LONG64 *)NET_BUFFER_LIST_MINIPORT_RESERVED(nbl))
2019-03-08 22:33:15 +01:00
2019-03-27 09:00:19 +01:00
_IRQL_requires_same_
2019-04-10 13:42:16 +02:00
static void TunNBLRefInit ( _Inout_ TUN_CTX * ctx , _Inout_ NET_BUFFER_LIST * nbl )
2019-03-27 09:00:19 +01:00
{
2019-04-11 15:08:45 +02:00
InterlockedIncrement64 ( & ctx - > ActiveTransactionCount ) ;
2019-04-12 07:58:03 +02:00
InterlockedIncrement ( & ctx - > PacketQueue . NumNbl ) ;
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
InterlockedExchange64 ( NET_BUFFER_LIST_REFCOUNT ( nbl ) , 1 ) ;
2019-03-27 09:00:19 +01:00
}
2019-03-08 22:33:15 +01:00
2019-03-27 09:00:19 +01:00
_IRQL_requires_same_
static void TunNBLRefInc ( _Inout_ NET_BUFFER_LIST * nbl )
{
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
ASSERT ( InterlockedGet64 ( NET_BUFFER_LIST_REFCOUNT ( nbl ) ) ) ;
InterlockedIncrement64 ( NET_BUFFER_LIST_REFCOUNT ( nbl ) ) ;
2019-03-27 09:00:19 +01:00
}
2019-03-27 09:09:58 +01:00
_When_ ( ( SendCompleteFlags & NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL ) , _IRQL_requires_ ( DISPATCH_LEVEL ) )
_When_ ( ! ( SendCompleteFlags & NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL ) , _IRQL_requires_max_ ( DISPATCH_LEVEL ) )
2019-04-10 13:42:16 +02:00
static BOOLEAN TunNBLRefDec ( _Inout_ TUN_CTX * ctx , _Inout_ NET_BUFFER_LIST * nbl , _In_ ULONG SendCompleteFlags )
2019-03-27 09:00:19 +01:00
{
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
ASSERT ( InterlockedGet64 ( NET_BUFFER_LIST_REFCOUNT ( nbl ) ) ) ;
if ( ! InterlockedDecrement64 ( NET_BUFFER_LIST_REFCOUNT ( nbl ) ) ) {
2019-03-27 09:00:19 +01:00
NET_BUFFER_LIST_NEXT_NBL ( nbl ) = NULL ;
2019-04-10 13:42:16 +02:00
NdisMSendNetBufferListsComplete ( ctx - > MiniportAdapterHandle , nbl , SendCompleteFlags ) ;
2019-04-12 07:58:03 +02:00
InterlockedDecrement ( & ctx - > PacketQueue . NumNbl ) ;
2019-04-10 13:42:16 +02:00
TunCompletePause ( ctx , TRUE ) ;
2019-03-27 09:00:19 +01:00
return TRUE ;
2019-03-08 22:33:15 +01:00
}
2019-03-27 09:00:19 +01:00
return FALSE ;
}
_IRQL_requires_same_
static void TunAppendNBL ( _Inout_ NET_BUFFER_LIST * * head , _Inout_ NET_BUFFER_LIST * * tail , __drv_aliasesMem _In_ NET_BUFFER_LIST * nbl )
{
* ( * tail ? & NET_BUFFER_LIST_NEXT_NBL ( * tail ) : head ) = nbl ;
* tail = nbl ;
NET_BUFFER_LIST_NEXT_NBL ( nbl ) = NULL ;
}
2019-04-10 13:42:16 +02:00
_Requires_lock_not_held_ ( ctx - > PacketQueue . Lock )
2019-03-27 09:00:19 +01:00
_IRQL_requires_max_ ( DISPATCH_LEVEL )
2019-04-10 13:42:16 +02:00
static void TunQueueAppend ( _Inout_ TUN_CTX * ctx , _In_ NET_BUFFER_LIST * nbl , _In_ UINT max_nbls )
2019-03-27 09:00:19 +01:00
{
for ( NET_BUFFER_LIST * nbl_next ; nbl ; nbl = nbl_next ) {
nbl_next = NET_BUFFER_LIST_NEXT_NBL ( nbl ) ;
if ( ! NET_BUFFER_LIST_FIRST_NB ( nbl ) ) {
NET_BUFFER_LIST_NEXT_NBL ( nbl ) = NULL ;
2019-04-10 13:42:16 +02:00
NdisMSendNetBufferListsComplete ( ctx - > MiniportAdapterHandle , nbl , 0 ) ;
2019-03-27 09:00:19 +01:00
continue ;
}
2019-03-08 22:33:15 +01:00
2019-03-27 09:00:19 +01:00
KLOCK_QUEUE_HANDLE lqh ;
2019-04-10 13:42:16 +02:00
KeAcquireInStackQueuedSpinLock ( & ctx - > PacketQueue . Lock , & lqh ) ;
TunNBLRefInit ( ctx , nbl ) ;
TunAppendNBL ( & ctx - > PacketQueue . FirstNbl , & ctx - > PacketQueue . LastNbl , nbl ) ;
2019-03-08 22:33:15 +01:00
2019-04-10 13:42:16 +02:00
while ( ( UINT ) InterlockedGet ( & ctx - > PacketQueue . NumNbl ) > max_nbls & & ctx - > PacketQueue . FirstNbl ) {
NET_BUFFER_LIST * nbl_second = NET_BUFFER_LIST_NEXT_NBL ( ctx - > PacketQueue . FirstNbl ) ;
2019-03-08 22:33:15 +01:00
2019-04-10 13:42:16 +02:00
NET_BUFFER_LIST_STATUS ( ctx - > PacketQueue . FirstNbl ) = NDIS_STATUS_SEND_ABORTED ;
TunNBLRefDec ( ctx , ctx - > PacketQueue . FirstNbl , NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL ) ;
2019-03-27 09:00:19 +01:00
2019-04-10 13:42:16 +02:00
ctx - > PacketQueue . NextNb = NULL ;
ctx - > PacketQueue . FirstNbl = nbl_second ;
if ( ! ctx - > PacketQueue . FirstNbl )
ctx - > PacketQueue . LastNbl = NULL ;
2019-03-27 09:00:19 +01:00
}
2019-03-08 22:33:15 +01:00
2019-03-27 09:00:19 +01:00
KeReleaseInStackQueuedSpinLock ( & lqh ) ;
2019-03-08 22:33:15 +01:00
}
}
2019-04-10 13:42:16 +02:00
_Requires_lock_held_ ( ctx - > PacketQueue . Lock )
2019-03-27 09:00:19 +01:00
_IRQL_requires_ ( DISPATCH_LEVEL )
2019-03-28 11:21:51 +01:00
_Must_inspect_result_
2019-04-10 13:42:16 +02:00
static _Return_type_success_ ( return ! = NULL ) NET_BUFFER * TunQueueRemove ( _Inout_ TUN_CTX * ctx , _Out_ NET_BUFFER_LIST * * nbl )
2019-03-08 22:33:15 +01:00
{
2019-03-27 09:00:19 +01:00
NET_BUFFER_LIST * nbl_top ;
NET_BUFFER * ret ;
2019-03-28 09:05:29 +01:00
retry :
2019-04-10 13:42:16 +02:00
nbl_top = ctx - > PacketQueue . FirstNbl ;
2019-03-27 09:00:19 +01:00
* nbl = nbl_top ;
if ( ! nbl_top )
return NULL ;
2019-04-10 13:42:16 +02:00
if ( ! ctx - > PacketQueue . NextNb )
ctx - > PacketQueue . NextNb = NET_BUFFER_LIST_FIRST_NB ( nbl_top ) ;
ret = ctx - > PacketQueue . NextNb ;
ctx - > PacketQueue . NextNb = NET_BUFFER_NEXT_NB ( ret ) ;
if ( ! ctx - > PacketQueue . NextNb ) {
ctx - > PacketQueue . FirstNbl = NET_BUFFER_LIST_NEXT_NBL ( nbl_top ) ;
if ( ! ctx - > PacketQueue . FirstNbl )
ctx - > PacketQueue . LastNbl = NULL ;
2019-03-27 09:00:19 +01:00
NET_BUFFER_LIST_NEXT_NBL ( nbl_top ) = NULL ;
} else
TunNBLRefInc ( nbl_top ) ;
2019-03-28 09:05:29 +01:00
if ( ret & & NET_BUFFER_DATA_LENGTH ( ret ) > TUN_EXCH_MAX_IP_PACKET_SIZE ) {
NET_BUFFER_LIST_STATUS ( nbl_top ) = NDIS_STATUS_INVALID_LENGTH ;
2019-04-10 13:42:16 +02:00
TunNBLRefDec ( ctx , nbl_top , NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL ) ;
InterlockedIncrement64 ( ( LONG64 * ) & ctx - > Statistics . ifOutDiscards ) ;
2019-03-28 09:05:29 +01:00
goto retry ; /* A for (;;) and a break would be fine, but this is clearer actually. */
}
2019-03-27 09:00:19 +01:00
return ret ;
}
2019-03-08 22:33:15 +01:00
2019-04-10 13:42:16 +02:00
/* Note: Must be called immediately after TunQueueRemove without dropping ctx->PacketQueue.Lock. */
_Requires_lock_held_ ( ctx - > PacketQueue . Lock )
2019-03-27 09:00:19 +01:00
_IRQL_requires_ ( DISPATCH_LEVEL )
2019-04-10 13:42:16 +02:00
static void TunQueuePrepend ( _Inout_ TUN_CTX * ctx , _In_ NET_BUFFER * nb , _In_ NET_BUFFER_LIST * nbl )
2019-03-27 09:00:19 +01:00
{
2019-04-10 13:42:16 +02:00
ctx - > PacketQueue . NextNb = nb ;
2019-03-27 09:00:19 +01:00
2019-04-10 13:42:16 +02:00
if ( ! nbl | | nbl = = ctx - > PacketQueue . FirstNbl )
2019-03-08 22:33:15 +01:00
return ;
2019-03-27 09:00:19 +01:00
2019-03-28 08:30:13 +01:00
TunNBLRefInc ( nbl ) ;
2019-04-10 13:42:16 +02:00
if ( ! ctx - > PacketQueue . FirstNbl )
ctx - > PacketQueue . FirstNbl = ctx - > PacketQueue . LastNbl = nbl ;
2019-03-27 09:00:19 +01:00
else {
2019-04-10 13:42:16 +02:00
NET_BUFFER_LIST_NEXT_NBL ( nbl ) = ctx - > PacketQueue . FirstNbl ;
ctx - > PacketQueue . FirstNbl = nbl ;
2019-03-08 22:33:15 +01:00
}
2019-03-27 09:00:19 +01:00
}
2019-03-08 22:33:15 +01:00
2019-04-10 13:42:16 +02:00
_Requires_lock_not_held_ ( ctx - > PacketQueue . Lock )
2019-04-02 13:41:02 +02:00
_IRQL_requires_max_ ( DISPATCH_LEVEL )
2019-05-24 14:18:18 +02:00
static void TunQueueClear ( _Inout_ TUN_CTX * ctx , _In_ NDIS_STATUS status )
2019-04-02 13:41:02 +02:00
{
KLOCK_QUEUE_HANDLE lqh ;
2019-04-10 13:42:16 +02:00
KeAcquireInStackQueuedSpinLock ( & ctx - > PacketQueue . Lock , & lqh ) ;
for ( NET_BUFFER_LIST * nbl = ctx - > PacketQueue . FirstNbl , * nbl_next ; nbl ; nbl = nbl_next ) {
2019-04-02 13:41:02 +02:00
nbl_next = NET_BUFFER_LIST_NEXT_NBL ( nbl ) ;
2019-05-24 14:18:18 +02:00
NET_BUFFER_LIST_STATUS ( nbl ) = status ;
2019-04-10 13:42:16 +02:00
TunNBLRefDec ( ctx , nbl , NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL ) ;
2019-04-02 13:41:02 +02:00
}
2019-04-10 13:42:16 +02:00
ctx - > PacketQueue . FirstNbl = NULL ;
ctx - > PacketQueue . LastNbl = NULL ;
ctx - > PacketQueue . NextNb = NULL ;
InterlockedExchange ( & ctx - > PacketQueue . NumNbl , 0 ) ;
2019-04-02 13:41:02 +02:00
KeReleaseInStackQueuedSpinLock ( & lqh ) ;
}
2019-04-10 13:42:16 +02:00
_Requires_lock_not_held_ ( ctx - > PacketQueue . Lock )
2019-03-27 09:00:19 +01:00
_IRQL_requires_max_ ( DISPATCH_LEVEL )
2019-04-10 13:42:16 +02:00
static void TunQueueProcess ( _Inout_ TUN_CTX * ctx )
2019-03-27 09:00:19 +01:00
{
2019-03-27 15:41:21 +01:00
IRP * irp = NULL ;
2019-03-28 11:11:43 +01:00
UCHAR * buffer = NULL ;
ULONG size = 0 ;
2019-03-27 09:00:19 +01:00
NET_BUFFER * nb ;
KLOCK_QUEUE_HANDLE lqh ;
for ( ; ; ) {
NET_BUFFER_LIST * nbl ;
2019-04-10 13:42:16 +02:00
KeAcquireInStackQueuedSpinLock ( & ctx - > PacketQueue . Lock , & lqh ) ;
2019-03-28 11:11:43 +01:00
/* Get head NB (and IRP). */
2019-03-27 15:41:21 +01:00
if ( ! irp ) {
2019-04-10 13:42:16 +02:00
nb = TunQueueRemove ( ctx , & nbl ) ;
2019-03-27 09:00:19 +01:00
if ( ! nb ) {
KeReleaseInStackQueuedSpinLock ( & lqh ) ;
return ;
}
2019-04-10 13:42:16 +02:00
irp = TunRemoveNextIrp ( ctx , & buffer , & size ) ;
2019-03-27 15:41:21 +01:00
if ( ! irp ) {
2019-04-10 13:42:16 +02:00
TunQueuePrepend ( ctx , nb , nbl ) ;
2019-03-27 09:00:19 +01:00
KeReleaseInStackQueuedSpinLock ( & lqh ) ;
2019-03-28 08:30:13 +01:00
if ( nbl )
2019-04-10 13:42:16 +02:00
TunNBLRefDec ( ctx , nbl , 0 ) ;
2019-03-27 09:00:19 +01:00
return ;
}
2019-04-12 14:37:01 +02:00
_Analysis_assume_ ( buffer ) ;
_Analysis_assume_ ( irp - > IoStatus . Information < = size ) ;
2019-03-28 07:35:23 +01:00
} else
2019-04-10 13:42:16 +02:00
nb = TunQueueRemove ( ctx , & nbl ) ;
2019-03-28 08:30:13 +01:00
2019-03-28 11:11:43 +01:00
/* If the NB won't fit in the IRP, return it. */
2019-04-12 13:51:33 +02:00
if ( nb & & TunWontFitIntoIrp ( irp , size , nb ) ) {
2019-04-10 13:42:16 +02:00
TunQueuePrepend ( ctx , nb , nbl ) ;
2019-03-28 08:30:13 +01:00
if ( nbl )
2019-04-10 13:42:16 +02:00
TunNBLRefDec ( ctx , nbl , NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL ) ;
2019-03-28 08:30:13 +01:00
nbl = NULL ;
nb = NULL ;
}
2019-03-27 09:00:19 +01:00
KeReleaseInStackQueuedSpinLock ( & lqh ) ;
2019-03-28 11:11:43 +01:00
/* Process NB and IRP. */
if ( nb ) {
2019-04-10 13:42:16 +02:00
NTSTATUS status = TunWriteIntoIrp ( irp , buffer , nb , & ctx - > Statistics ) ;
2019-03-28 11:11:43 +01:00
if ( ! NT_SUCCESS ( status ) ) {
if ( nbl )
NET_BUFFER_LIST_STATUS ( nbl ) = status ;
2019-04-10 13:42:16 +02:00
IoCsqInsertIrpEx ( & ctx - > Device . ReadQueue . Csq , irp , NULL , TUN_CSQ_INSERT_HEAD ) ;
2019-03-28 11:45:57 +01:00
irp = NULL ;
2019-03-28 11:11:43 +01:00
}
} else {
2019-05-31 12:49:31 +02:00
TunCompleteRequest ( ctx , irp , STATUS_SUCCESS , IO_NETWORK_INCREMENT ) ;
2019-03-28 11:45:57 +01:00
irp = NULL ;
2019-03-27 09:00:19 +01:00
}
2019-03-28 11:11:43 +01:00
2019-03-27 09:00:19 +01:00
if ( nbl )
2019-04-10 13:42:16 +02:00
TunNBLRefDec ( ctx , nbl , 0 ) ;
2019-03-27 09:00:19 +01:00
}
2019-03-08 22:33:15 +01:00
}
2019-06-05 07:49:15 +02:00
# define IRP_REFCOUNT(irp) ((volatile LONG *)&(irp)->Tail.Overlay.DriverContext[0])
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
# define NET_BUFFER_LIST_IRP(nbl) (NET_BUFFER_LIST_MINIPORT_RESERVED(nbl)[0])
2019-04-09 13:16:40 +02:00
_IRQL_requires_max_ ( DISPATCH_LEVEL )
_Must_inspect_result_
static NTSTATUS TunWriteFromIrp ( _Inout_ TUN_CTX * ctx , _Inout_ IRP * Irp )
2019-03-08 22:33:15 +01:00
{
2019-05-24 14:18:18 +02:00
NTSTATUS status ;
KIRQL irql = ExAcquireSpinLockShared ( & ctx - > TransitionLock ) ;
2019-03-08 22:33:15 +01:00
2019-05-31 09:57:02 +02:00
if ( ! NT_SUCCESS ( TunCheckForPause ( ctx ) ) ) {
status = STATUS_CANCELLED ;
2019-04-03 05:46:50 +02:00
goto cleanup_TunCompletePause ;
2019-05-31 09:57:02 +02:00
}
2019-03-22 13:47:17 +01:00
2019-03-28 11:11:43 +01:00
UCHAR * buffer ;
ULONG size ;
2019-05-31 09:57:02 +02:00
if ( ! NT_SUCCESS ( status = TunGetIrpBuffer ( Irp , & buffer , & size ) ) )
2019-04-03 05:46:50 +02:00
goto cleanup_TunCompletePause ;
2019-03-08 22:33:15 +01:00
const UCHAR * b = buffer , * b_end = buffer + size ;
ULONG nbl_count = 0 ;
NET_BUFFER_LIST * nbl_head = NULL , * nbl_tail = NULL ;
2019-05-31 09:57:02 +02:00
while ( b + sizeof ( TUN_PACKET ) < = b_end ) {
2019-06-05 11:51:26 +02:00
if ( nbl_count > = MAXULONG ) {
status = STATUS_INVALID_USER_BUFFER ;
goto cleanup_nbl_head ;
}
2019-03-08 22:33:15 +01:00
TUN_PACKET * p = ( TUN_PACKET * ) b ;
2019-05-31 09:57:02 +02:00
if ( p - > Size > TUN_EXCH_MAX_IP_PACKET_SIZE ) {
status = STATUS_INVALID_USER_BUFFER ;
goto cleanup_nbl_head ;
}
2019-03-08 22:33:15 +01:00
UINT p_size = TunPacketAlign ( sizeof ( TUN_PACKET ) + p - > Size ) ;
2019-05-31 09:57:02 +02:00
if ( b + p_size > b_end ) {
status = STATUS_INVALID_USER_BUFFER ;
goto cleanup_nbl_head ;
}
2019-03-08 22:33:15 +01:00
ULONG nbl_flags ;
USHORT nbl_proto ;
if ( p - > Size > = 20 & & p - > Data [ 0 ] > > 4 = = 4 ) {
nbl_flags = NDIS_NBL_FLAGS_IS_IPV4 ;
nbl_proto = NDIS_ETH_TYPE_IPV4 ;
} else if ( p - > Size > = 40 & & p - > Data [ 0 ] > > 4 = = 6 ) {
nbl_flags = NDIS_NBL_FLAGS_IS_IPV6 ;
nbl_proto = NDIS_ETH_TYPE_IPV6 ;
} else {
2019-05-31 09:57:02 +02:00
status = STATUS_INVALID_USER_BUFFER ;
goto cleanup_nbl_head ;
2019-03-08 22:33:15 +01:00
}
MDL * mdl = NdisAllocateMdl ( ctx - > MiniportAdapterHandle , p - > Data , p - > Size ) ;
2019-05-31 09:57:02 +02:00
if ( ! mdl ) {
status = STATUS_INSUFFICIENT_RESOURCES ;
goto cleanup_nbl_head ;
}
2019-03-08 22:33:15 +01:00
NET_BUFFER_LIST * nbl = NdisAllocateNetBufferAndNetBufferList ( ctx - > NBLPool , 0 , 0 , mdl , 0 , p - > Size ) ;
2019-05-31 09:57:02 +02:00
if ( ! nbl ) {
NdisFreeMdl ( mdl ) ;
status = STATUS_INSUFFICIENT_RESOURCES ;
goto cleanup_nbl_head ;
}
2019-03-08 22:33:15 +01:00
nbl - > SourceHandle = ctx - > MiniportAdapterHandle ;
NdisSetNblFlag ( nbl , nbl_flags ) ;
NET_BUFFER_LIST_INFO ( nbl , NetBufferListFrameType ) = ( PVOID ) TunHtons ( nbl_proto ) ;
NET_BUFFER_LIST_STATUS ( nbl ) = NDIS_STATUS_SUCCESS ;
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
NET_BUFFER_LIST_IRP ( nbl ) = Irp ;
2019-03-08 22:33:15 +01:00
TunAppendNBL ( & nbl_head , & nbl_tail , nbl ) ;
nbl_count + + ;
b + = p_size ;
}
2019-05-31 09:57:02 +02:00
if ( ( ULONG ) ( b - buffer ) ! = size ) {
status = STATUS_INVALID_USER_BUFFER ;
goto cleanup_nbl_head ;
}
Irp - > IoStatus . Information = size ;
2019-03-08 22:33:15 +01:00
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
if ( ! nbl_head ) {
status = STATUS_SUCCESS ;
goto cleanup_TunCompletePause ;
2019-03-21 13:30:03 +01:00
}
2019-06-05 07:49:15 +02:00
InterlockedExchange ( IRP_REFCOUNT ( Irp ) , nbl_count ) ;
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
IoMarkIrpPending ( Irp ) ;
2019-03-08 22:33:15 +01:00
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
NdisMIndicateReceiveNetBufferLists ( ctx - > MiniportAdapterHandle , nbl_head , NDIS_DEFAULT_PORT_NUMBER , nbl_count , 0 ) ;
ExReleaseSpinLockShared ( & ctx - > TransitionLock , irql ) ;
return STATUS_PENDING ;
2019-03-22 13:47:17 +01:00
2019-05-31 09:57:02 +02:00
cleanup_nbl_head :
for ( NET_BUFFER_LIST * nbl = nbl_head , * nbl_next ; nbl ; nbl = nbl_next ) {
nbl_next = NET_BUFFER_LIST_NEXT_NBL ( nbl ) ;
NET_BUFFER_LIST_NEXT_NBL ( nbl ) = NULL ;
NdisFreeMdl ( NET_BUFFER_FIRST_MDL ( NET_BUFFER_LIST_FIRST_NB ( nbl ) ) ) ;
NdisFreeNetBufferList ( nbl ) ;
}
2019-04-03 05:46:50 +02:00
cleanup_TunCompletePause :
2019-04-05 20:11:08 +02:00
TunCompletePause ( ctx , TRUE ) ;
2019-05-24 14:18:18 +02:00
ExReleaseSpinLockShared ( & ctx - > TransitionLock , irql ) ;
2019-03-08 22:33:15 +01:00
return status ;
}
2019-05-30 21:39:33 +02:00
_IRQL_requires_max_ ( PASSIVE_LEVEL )
static void TunForceHandlesClosed ( _Inout_ TUN_CTX * ctx )
2019-05-31 00:50:07 +02:00
{
2019-05-30 21:39:33 +02:00
NTSTATUS status ;
PEPROCESS process ;
KAPC_STATE apc_state ;
PVOID object ;
OBJECT_HANDLE_INFORMATION handle_info ;
SYSTEM_HANDLE_INFORMATION_EX * table = NULL ;
for ( ULONG size = 0 , req ; ( status = ZwQuerySystemInformation ( SystemExtendedHandleInformation , table , size , & req ) ) = = STATUS_INFO_LENGTH_MISMATCH ; size = req ) {
if ( table )
ExFreePoolWithTag ( table , TUN_MEMORY_TAG ) ;
table = ExAllocatePoolWithTag ( PagedPool , req , TUN_MEMORY_TAG ) ;
if ( ! table )
return ;
}
if ( ! NT_SUCCESS ( status ) | | ! table )
goto out ;
for ( ULONG_PTR i = 0 ; i < table - > NumberOfHandles ; + + i ) {
FILE_OBJECT * file = table - > Handles [ i ] . Object ; //XXX: We should probably first look at table->Handles[i].ObjectTypeIndex, but the value changes lots between NT versions.
if ( ! file | | file - > Type ! = 5 | | file - > DeviceObject ! = ctx - > Device . Object )
continue ;
status = PsLookupProcessByProcessId ( table - > Handles [ i ] . UniqueProcessId , & process ) ;
if ( ! NT_SUCCESS ( status ) )
continue ;
KeStackAttachProcess ( process , & apc_state ) ;
status = ObReferenceObjectByHandle ( table - > Handles [ i ] . HandleValue , 0 , NULL , UserMode , & object , & handle_info ) ;
if ( NT_SUCCESS ( status ) ) {
if ( object = = file )
ObCloseHandle ( table - > Handles [ i ] . HandleValue , UserMode ) ;
ObfDereferenceObject ( object ) ;
}
KeUnstackDetachProcess ( & apc_state ) ;
ObfDereferenceObject ( process ) ;
}
out :
if ( table )
ExFreePoolWithTag ( table , TUN_MEMORY_TAG ) ;
2019-05-31 00:50:07 +02:00
}
2019-04-09 13:16:40 +02:00
static DRIVER_DISPATCH TunDispatch ;
2019-03-27 09:00:19 +01:00
_Use_decl_annotations_
2019-04-09 13:16:40 +02:00
static NTSTATUS TunDispatch ( DEVICE_OBJECT * DeviceObject , IRP * Irp )
2019-03-27 09:00:19 +01:00
{
2019-04-10 11:21:51 +02:00
NTSTATUS status ;
2019-05-24 14:18:18 +02:00
KIRQL irql ;
2019-03-27 09:00:19 +01:00
2019-04-09 13:16:40 +02:00
Irp - > IoStatus . Information = 0 ;
2019-04-10 13:42:16 +02:00
TUN_CTX * ctx = NdisGetDeviceReservedExtension ( DeviceObject ) ;
if ( ! ctx ) {
2019-03-27 09:00:19 +01:00
status = STATUS_INVALID_HANDLE ;
goto cleanup_complete_req ;
}
IO_STACK_LOCATION * stack = IoGetCurrentIrpStackLocation ( Irp ) ;
2019-04-09 13:16:40 +02:00
switch ( stack - > MajorFunction ) {
case IRP_MJ_READ :
2019-04-11 19:28:33 +02:00
if ( ( status = STATUS_FILE_FORCED_CLOSED , InterlockedGet ( ( LONG * ) & ctx - > State ) < TUN_STATE_PAUSED ) | |
! NT_SUCCESS ( status = IoAcquireRemoveLock ( & ctx - > Device . RemoveLock , Irp ) ) )
goto cleanup_complete_req ;
2019-04-10 11:21:51 +02:00
2019-04-11 19:28:33 +02:00
if ( ! NT_SUCCESS ( status = IoCsqInsertIrpEx ( & ctx - > Device . ReadQueue . Csq , Irp , NULL , TUN_CSQ_INSERT_TAIL ) ) )
goto cleanup_complete_req_and_release_remove_lock ;
2019-04-09 13:16:40 +02:00
2019-04-10 13:42:16 +02:00
TunQueueProcess ( ctx ) ;
2019-04-09 13:16:40 +02:00
return STATUS_PENDING ;
case IRP_MJ_WRITE :
2019-04-11 19:28:33 +02:00
if ( ( status = STATUS_FILE_FORCED_CLOSED , InterlockedGet ( ( LONG * ) & ctx - > State ) < TUN_STATE_PAUSED ) | |
! NT_SUCCESS ( status = IoAcquireRemoveLock ( & ctx - > Device . RemoveLock , Irp ) ) )
goto cleanup_complete_req ;
2019-04-10 11:21:51 +02:00
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
if ( ( status = TunWriteFromIrp ( ctx , Irp ) ) = = STATUS_PENDING )
return STATUS_PENDING ;
2019-04-11 19:28:33 +02:00
goto cleanup_complete_req_and_release_remove_lock ;
2019-04-09 13:16:40 +02:00
case IRP_MJ_CREATE :
2019-04-11 19:28:33 +02:00
if ( ( status = STATUS_DELETE_PENDING , InterlockedGet ( ( LONG * ) & ctx - > State ) < TUN_STATE_PAUSED ) | |
! NT_SUCCESS ( status = IoAcquireRemoveLock ( & ctx - > Device . RemoveLock , Irp ) ) )
goto cleanup_complete_req ;
2019-04-10 11:21:51 +02:00
2019-05-31 00:50:07 +02:00
if ( ! NT_SUCCESS ( status = IoAcquireRemoveLock ( & ctx - > Device . RemoveLock , stack - > FileObject ) ) )
goto cleanup_complete_req_and_release_remove_lock ;
2019-04-10 13:42:16 +02:00
ASSERT ( InterlockedGet64 ( & ctx - > Device . RefCount ) < MAXLONG64 ) ;
if ( InterlockedIncrement64 ( & ctx - > Device . RefCount ) > 0 )
2019-04-09 13:16:40 +02:00
TunIndicateStatus ( ctx - > MiniportAdapterHandle , MediaConnectStateConnected ) ;
2019-04-10 11:21:51 +02:00
status = STATUS_SUCCESS ;
2019-04-11 19:28:33 +02:00
goto cleanup_complete_req_and_release_remove_lock ;
2019-04-09 13:16:40 +02:00
case IRP_MJ_CLOSE :
2019-05-24 14:18:18 +02:00
irql = ExAcquireSpinLockExclusive ( & ctx - > TransitionLock ) ;
2019-04-10 13:42:16 +02:00
ASSERT ( InterlockedGet64 ( & ctx - > Device . RefCount ) > 0 ) ;
2019-05-24 14:18:18 +02:00
if ( InterlockedDecrement64 ( & ctx - > Device . RefCount ) < = 0 ) {
2019-06-04 11:55:13 +02:00
ExReleaseSpinLockExclusive ( & ctx - > TransitionLock , irql ) ;
2019-05-24 14:18:18 +02:00
if ( ctx - > MiniportAdapterHandle )
TunIndicateStatus ( ctx - > MiniportAdapterHandle , MediaConnectStateDisconnected ) ;
TunQueueClear ( ctx , NDIS_STATUS_SEND_ABORTED ) ;
2019-06-04 11:55:13 +02:00
} else
ExReleaseSpinLockExclusive ( & ctx - > TransitionLock , irql ) ;
2019-04-11 19:28:33 +02:00
IoReleaseRemoveLock ( & ctx - > Device . RemoveLock , stack - > FileObject ) ;
2019-05-31 00:50:07 +02:00
2019-04-10 11:21:51 +02:00
status = STATUS_SUCCESS ;
2019-04-11 19:28:33 +02:00
goto cleanup_complete_req ;
2019-04-09 13:16:40 +02:00
case IRP_MJ_CLEANUP :
2019-04-10 13:42:16 +02:00
for ( IRP * pending_irp ; ( pending_irp = IoCsqRemoveNextIrp ( & ctx - > Device . ReadQueue . Csq , stack - > FileObject ) ) ! = NULL ; )
2019-05-31 12:49:31 +02:00
TunCompleteRequest ( ctx , pending_irp , STATUS_CANCELLED , IO_NO_INCREMENT ) ;
2019-04-10 11:21:51 +02:00
status = STATUS_SUCCESS ;
2019-04-11 19:28:33 +02:00
goto cleanup_complete_req ;
2019-04-09 13:16:40 +02:00
default :
status = STATUS_INVALID_PARAMETER ;
2019-04-11 19:28:33 +02:00
goto cleanup_complete_req ;
2019-04-09 13:16:40 +02:00
}
2019-03-27 09:00:19 +01:00
2019-04-11 19:28:33 +02:00
cleanup_complete_req_and_release_remove_lock :
2019-05-31 12:49:31 +02:00
TunCompleteRequest ( ctx , Irp , status , IO_NO_INCREMENT ) ;
2019-04-11 19:28:33 +02:00
return status ;
2019-03-27 09:00:19 +01:00
cleanup_complete_req :
2019-04-09 13:16:40 +02:00
Irp - > IoStatus . Status = status ;
IoCompleteRequest ( Irp , IO_NO_INCREMENT ) ;
2019-03-27 09:00:19 +01:00
return status ;
}
2019-03-08 22:33:15 +01:00
static MINIPORT_PAUSE TunPause ;
_Use_decl_annotations_
static NDIS_STATUS TunPause ( NDIS_HANDLE MiniportAdapterContext , PNDIS_MINIPORT_PAUSE_PARAMETERS MiniportPauseParameters )
{
TUN_CTX * ctx = ( TUN_CTX * ) MiniportAdapterContext ;
2019-03-22 13:47:17 +01:00
2019-05-24 14:18:18 +02:00
KIRQL irql = ExAcquireSpinLockExclusive ( & ctx - > TransitionLock ) ;
2019-04-10 14:35:50 +02:00
InterlockedExchange ( ( LONG * ) & ctx - > State , TUN_STATE_PAUSING ) ;
2019-05-24 14:18:18 +02:00
ExReleaseSpinLockExclusive ( & ctx - > TransitionLock , irql ) ;
TunQueueClear ( ctx , STATUS_NDIS_PAUSED ) ;
2019-03-27 09:00:19 +01:00
2019-04-05 20:11:08 +02:00
return TunCompletePause ( ctx , FALSE ) ;
2019-03-08 22:33:15 +01:00
}
static MINIPORT_RESTART TunRestart ;
_Use_decl_annotations_
static NDIS_STATUS TunRestart ( NDIS_HANDLE MiniportAdapterContext , PNDIS_MINIPORT_RESTART_PARAMETERS MiniportRestartParameters )
{
TUN_CTX * ctx = ( TUN_CTX * ) MiniportAdapterContext ;
2019-04-11 15:08:45 +02:00
InterlockedExchange ( ( LONG * ) & ctx - > State , TUN_STATE_RESTARTING ) ;
InterlockedExchange64 ( & ctx - > ActiveTransactionCount , 1 ) ;
2019-03-08 22:33:15 +01:00
InterlockedExchange ( ( LONG * ) & ctx - > State , TUN_STATE_RUNNING ) ;
2019-05-24 14:18:18 +02:00
2019-03-08 22:33:15 +01:00
return NDIS_STATUS_SUCCESS ;
}
static MINIPORT_RETURN_NET_BUFFER_LISTS TunReturnNetBufferLists ;
_Use_decl_annotations_
static void TunReturnNetBufferLists ( NDIS_HANDLE MiniportAdapterContext , PNET_BUFFER_LIST NetBufferLists , ULONG ReturnFlags )
{
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
TUN_CTX * ctx = ( TUN_CTX * ) MiniportAdapterContext ;
LONG64 stat_size = 0 , stat_p_ok = 0 , stat_p_err = 0 ;
for ( NET_BUFFER_LIST * nbl = NetBufferLists , * nbl_next ; nbl ; nbl = nbl_next ) {
nbl_next = NET_BUFFER_LIST_NEXT_NBL ( nbl ) ;
NET_BUFFER_LIST_NEXT_NBL ( nbl ) = NULL ;
IRP * irp = NET_BUFFER_LIST_IRP ( nbl ) ;
MDL * mdl = NET_BUFFER_FIRST_MDL ( NET_BUFFER_LIST_FIRST_NB ( nbl ) ) ;
if ( NT_SUCCESS ( NET_BUFFER_LIST_STATUS ( nbl ) ) ) {
ULONG p_size = MmGetMdlByteCount ( mdl ) ;
stat_size + = p_size ;
stat_p_ok + + ;
} else
stat_p_err + + ;
NdisFreeMdl ( mdl ) ;
NdisFreeNetBufferList ( nbl ) ;
2019-06-05 07:49:15 +02:00
ASSERT ( InterlockedGet ( IRP_REFCOUNT ( irp ) ) ) ;
if ( ! InterlockedDecrement ( IRP_REFCOUNT ( irp ) ) ) {
2019-05-31 12:49:31 +02:00
TunCompleteRequest ( ctx , irp , STATUS_SUCCESS , IO_NETWORK_INCREMENT ) ;
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
TunCompletePause ( ctx , TRUE ) ;
}
}
InterlockedAdd64 ( ( LONG64 * ) & ctx - > Statistics . ifHCInOctets , stat_size ) ;
InterlockedAdd64 ( ( LONG64 * ) & ctx - > Statistics . ifHCInUcastOctets , stat_size ) ;
InterlockedAdd64 ( ( LONG64 * ) & ctx - > Statistics . ifHCInUcastPkts , stat_p_ok ) ;
InterlockedAdd64 ( ( LONG64 * ) & ctx - > Statistics . ifInErrors , stat_p_err ) ;
2019-03-08 22:33:15 +01:00
}
static MINIPORT_CANCEL_SEND TunCancelSend ;
_Use_decl_annotations_
static void TunCancelSend ( NDIS_HANDLE MiniportAdapterContext , PVOID CancelId )
{
TUN_CTX * ctx = ( TUN_CTX * ) MiniportAdapterContext ;
2019-03-27 09:00:19 +01:00
KLOCK_QUEUE_HANDLE lqh ;
2019-03-08 22:33:15 +01:00
2019-04-10 13:42:16 +02:00
KeAcquireInStackQueuedSpinLock ( & ctx - > PacketQueue . Lock , & lqh ) ;
2019-03-08 22:33:15 +01:00
2019-04-10 13:42:16 +02:00
NET_BUFFER_LIST * nbl_last = NULL , * * nbl_last_link = & ctx - > PacketQueue . FirstNbl ;
for ( NET_BUFFER_LIST * nbl = ctx - > PacketQueue . FirstNbl , * nbl_next ; nbl ; nbl = nbl_next ) {
2019-03-08 22:33:15 +01:00
nbl_next = NET_BUFFER_LIST_NEXT_NBL ( nbl ) ;
if ( NDIS_GET_NET_BUFFER_LIST_CANCEL_ID ( nbl ) = = CancelId ) {
2019-04-05 12:25:04 +02:00
NET_BUFFER_LIST_STATUS ( nbl ) = NDIS_STATUS_SEND_ABORTED ;
2019-03-27 10:10:08 +01:00
* nbl_last_link = nbl_next ;
2019-04-10 13:42:16 +02:00
TunNBLRefDec ( ctx , nbl , NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL ) ;
2019-03-27 09:00:19 +01:00
} else {
2019-03-27 10:10:08 +01:00
nbl_last = nbl ;
nbl_last_link = & NET_BUFFER_LIST_NEXT_NBL ( nbl ) ;
2019-03-27 09:00:19 +01:00
}
2019-03-08 22:33:15 +01:00
}
2019-04-10 13:42:16 +02:00
ctx - > PacketQueue . LastNbl = nbl_last ;
2019-03-08 22:33:15 +01:00
2019-03-27 09:00:19 +01:00
KeReleaseInStackQueuedSpinLock ( & lqh ) ;
2019-03-08 22:33:15 +01:00
}
static MINIPORT_DEVICE_PNP_EVENT_NOTIFY TunDevicePnPEventNotify ;
_Use_decl_annotations_
static void TunDevicePnPEventNotify ( NDIS_HANDLE MiniportAdapterContext , PNET_DEVICE_PNP_EVENT NetDevicePnPEvent )
{
}
static MINIPORT_SHUTDOWN TunShutdownEx ;
_Use_decl_annotations_
static void TunShutdownEx ( NDIS_HANDLE MiniportAdapterContext , NDIS_SHUTDOWN_ACTION ShutdownAction )
{
TUN_CTX * ctx = ( TUN_CTX * ) MiniportAdapterContext ;
if ( ShutdownAction = = NdisShutdownBugCheck )
return ;
InterlockedExchange ( ( LONG * ) & ctx - > State , TUN_STATE_SHUTDOWN ) ;
}
static MINIPORT_CANCEL_DIRECT_OID_REQUEST TunCancelDirectOidRequest ;
_Use_decl_annotations_
static void TunCancelDirectOidRequest ( NDIS_HANDLE MiniportAdapterContext , PVOID RequestId )
{
}
static MINIPORT_CANCEL_OID_REQUEST TunCancelOidRequest ;
_Use_decl_annotations_
static void TunCancelOidRequest ( NDIS_HANDLE MiniportAdapterContext , PVOID RequestId )
{
}
2019-05-31 00:50:07 +02:00
static DRIVER_NOTIFICATION_CALLBACK_ROUTINE TunPnPNotifyDeviceChange ;
_Use_decl_annotations_
static NTSTATUS TunPnPNotifyDeviceChange ( PVOID NotificationStruct , PVOID Context )
{
TARGET_DEVICE_REMOVAL_NOTIFICATION * notification = NotificationStruct ;
TUN_CTX * ctx = Context ;
if ( ! ctx )
return STATUS_SUCCESS ;
if ( IsEqualGUID ( & notification - > Event , & GUID_TARGET_DEVICE_QUERY_REMOVE ) ) {
KIRQL irql = ExAcquireSpinLockExclusive ( & ctx - > TransitionLock ) ;
InterlockedExchange ( ( LONG * ) & ctx - > State , TUN_STATE_PAUSING ) ;
ExReleaseSpinLockExclusive ( & ctx - > TransitionLock , irql ) ;
/* The entire purpose of this PnP notification infrastructure is so that we can get here.
* The idea is that if there are un - returned NBLs , TunPause & TunHalt will never be called .
* So we clear them here after setting the paused state , which then frees up NDIS to do
* the right thing later on in the shutdown procedure . */
TunQueueClear ( ctx , STATUS_NDIS_REQUEST_ABORTED ) ;
FILE_OBJECT * file = ctx - > PnPNotifications . FileObject ;
ctx - > PnPNotifications . FileObject = NULL ;
if ( file )
ObDereferenceObject ( file ) ;
} else if ( IsEqualGUID ( & notification - > Event , & GUID_TARGET_DEVICE_REMOVE_COMPLETE ) | |
IsEqualGUID ( & notification - > Event , & GUID_TARGET_DEVICE_REMOVE_CANCELLED ) ) {
PVOID handle = ctx - > PnPNotifications . Handle ;
/* We unregister in the cancelled case too, because the initial remove request puts us
* in pausing state , so we won ' t pile up any further NBLs . */
ctx - > PnPNotifications . Handle = NULL ;
if ( handle )
IoUnregisterPlugPlayNotificationEx ( handle ) ;
}
return STATUS_SUCCESS ;
}
static DRIVER_NOTIFICATION_CALLBACK_ROUTINE TunPnPNotifyInterfaceChange ;
_Use_decl_annotations_
static NTSTATUS TunPnPNotifyInterfaceChange ( PVOID NotificationStruct , PVOID Context )
{
DEVICE_INTERFACE_CHANGE_NOTIFICATION * notification = NotificationStruct ;
DRIVER_OBJECT * driver_object = ( DRIVER_OBJECT * ) Context ;
DEVICE_OBJECT * device_object ;
FILE_OBJECT * file_object ;
TUN_CTX * ctx ;
_Analysis_assume_ ( driver_object ) ;
if ( ! IsEqualGUID ( & notification - > InterfaceClassGuid , & GUID_DEVINTERFACE_NET ) | |
! IsEqualGUID ( & notification - > Event , & GUID_DEVICE_INTERFACE_ARRIVAL ) )
return STATUS_SUCCESS ;
if ( ! NT_SUCCESS ( IoGetDeviceObjectPointer ( notification - > SymbolicLinkName ,
STANDARD_RIGHTS_ALL , & file_object , & device_object ) ) )
return STATUS_SUCCESS ;
if ( device_object - > DriverObject ! = driver_object ) {
ObDereferenceObject ( file_object ) ;
return STATUS_SUCCESS ;
}
# pragma warning(suppress: 28175)
ctx = device_object - > Reserved ;
ASSERT ( ! ctx - > PnPNotifications . FileObject ) ;
ctx - > PnPNotifications . FileObject = file_object ;
ASSERT ( ! ctx - > PnPNotifications . Handle ) ;
# pragma warning(suppress: 6014) /* Leaking memory 'ctx->PnPNotifications.Handle'. Note: 'ctx->PnPNotifications.Handle' is unregistered in TunPnPNotifyDeviceChange(GUID_TARGET_DEVICE_REMOVE_COMPLETE/GUID_TARGET_DEVICE_REMOVE_CANCELLED); or on failure. */
if ( ! NT_SUCCESS ( IoRegisterPlugPlayNotification ( EventCategoryTargetDeviceChange , 0 ,
ctx - > PnPNotifications . FileObject , driver_object , TunPnPNotifyDeviceChange ,
ctx , ( PVOID * ) & ctx - > PnPNotifications . Handle ) ) ) {
ctx - > PnPNotifications . FileObject = NULL ;
ObDereferenceObject ( file_object ) ;
}
return STATUS_SUCCESS ;
}
2019-03-08 22:33:15 +01:00
static MINIPORT_INITIALIZE TunInitializeEx ;
_Use_decl_annotations_
static NDIS_STATUS TunInitializeEx ( NDIS_HANDLE MiniportAdapterHandle , NDIS_HANDLE MiniportDriverContext , PNDIS_MINIPORT_INIT_PARAMETERS MiniportInitParameters )
{
NDIS_STATUS status ;
if ( ! MiniportAdapterHandle )
return NDIS_STATUS_FAILURE ;
2019-04-10 13:42:16 +02:00
/* Register device first.
* Having only one device per adapter allows us to store adapter context inside device extension . */
2019-05-30 21:39:33 +02:00
WCHAR device_name [ sizeof ( L " \\ Device \\ " TUN_DEVICE_NAME ) / sizeof ( WCHAR ) + 10 /*MAXULONG as string*/ ] = { 0 } ;
2019-04-10 13:42:16 +02:00
UNICODE_STRING unicode_device_name ;
TunInitUnicodeString ( & unicode_device_name , device_name ) ;
RtlUnicodeStringPrintf ( & unicode_device_name , L " \\ Device \\ " TUN_DEVICE_NAME , ( ULONG ) MiniportInitParameters - > NetLuid . Info . NetLuidIndex ) ;
2019-05-30 21:39:33 +02:00
WCHAR symbolic_name [ sizeof ( L " \\ DosDevices \\ " TUN_DEVICE_NAME ) / sizeof ( WCHAR ) + 10 /*MAXULONG as string*/ ] = { 0 } ;
2019-04-10 13:42:16 +02:00
UNICODE_STRING unicode_symbolic_name ;
TunInitUnicodeString ( & unicode_symbolic_name , symbolic_name ) ;
RtlUnicodeStringPrintf ( & unicode_symbolic_name , L " \\ DosDevices \\ " TUN_DEVICE_NAME , ( ULONG ) MiniportInitParameters - > NetLuid . Info . NetLuidIndex ) ;
static PDRIVER_DISPATCH dispatch_table [ IRP_MJ_MAXIMUM_FUNCTION + 1 ] = {
TunDispatch , /* IRP_MJ_CREATE */
NULL , /* IRP_MJ_CREATE_NAMED_PIPE */
TunDispatch , /* IRP_MJ_CLOSE */
TunDispatch , /* IRP_MJ_READ */
TunDispatch , /* IRP_MJ_WRITE */
NULL , /* IRP_MJ_QUERY_INFORMATION */
NULL , /* IRP_MJ_SET_INFORMATION */
NULL , /* IRP_MJ_QUERY_EA */
NULL , /* IRP_MJ_SET_EA */
NULL , /* IRP_MJ_FLUSH_BUFFERS */
NULL , /* IRP_MJ_QUERY_VOLUME_INFORMATION */
NULL , /* IRP_MJ_SET_VOLUME_INFORMATION */
NULL , /* IRP_MJ_DIRECTORY_CONTROL */
NULL , /* IRP_MJ_FILE_SYSTEM_CONTROL */
NULL , /* IRP_MJ_DEVICE_CONTROL */
NULL , /* IRP_MJ_INTERNAL_DEVICE_CONTROL */
NULL , /* IRP_MJ_SHUTDOWN */
NULL , /* IRP_MJ_LOCK_CONTROL */
TunDispatch , /* IRP_MJ_CLEANUP */
} ;
NDIS_DEVICE_OBJECT_ATTRIBUTES t = {
. Header = {
2019-04-12 08:09:06 +02:00
. Type = NDIS_OBJECT_TYPE_DEVICE_OBJECT_ATTRIBUTES ,
. Revision = NDIS_DEVICE_OBJECT_ATTRIBUTES_REVISION_1 ,
. Size = NDIS_SIZEOF_DEVICE_OBJECT_ATTRIBUTES_REVISION_1
} ,
2019-04-10 13:42:16 +02:00
. DeviceName = & unicode_device_name ,
. SymbolicName = & unicode_symbolic_name ,
. MajorFunctions = dispatch_table ,
. ExtensionSize = sizeof ( TUN_CTX ) ,
. DefaultSDDLString = & SDDL_DEVOBJ_SYS_ALL /* Kernel, and SYSTEM: full control. Others: none */
} ;
NDIS_HANDLE handle ;
DEVICE_OBJECT * object ;
2019-05-31 00:50:07 +02:00
if ( ! NT_SUCCESS ( status = NdisRegisterDeviceEx ( NdisMiniportDriverHandle , & t , & object , & handle ) ) )
2019-03-08 22:33:15 +01:00
return NDIS_STATUS_FAILURE ;
2019-04-10 13:42:16 +02:00
object - > Flags & = ~ DO_BUFFERED_IO ;
object - > Flags | = DO_DIRECT_IO ;
TUN_CTX * ctx = NdisGetDeviceReservedExtension ( object ) ;
if ( ! ctx ) {
status = NDIS_STATUS_FAILURE ;
goto cleanup_NdisDeregisterDeviceEx ;
}
2019-05-31 00:50:07 +02:00
DEVICE_OBJECT * functional_device ;
NdisMGetDeviceProperty ( MiniportAdapterHandle , NULL , & functional_device , NULL , NULL , NULL ) ;
# pragma warning(suppress: 28175)
ASSERT ( ! functional_device - > Reserved ) ;
# pragma warning(suppress: 28175)
functional_device - > Reserved = ctx ;
2019-03-08 22:33:15 +01:00
NdisZeroMemory ( ctx , sizeof ( * ctx ) ) ;
2019-05-31 00:50:07 +02:00
InterlockedExchange ( ( LONG * ) & ctx - > State , TUN_STATE_INITIALIZING ) ;
InterlockedExchange ( ( LONG * ) & ctx - > PowerState , NdisDeviceStateD0 ) ;
2019-03-08 22:33:15 +01:00
ctx - > MiniportAdapterHandle = MiniportAdapterHandle ;
2019-05-31 14:40:49 +02:00
ctx - > NetLuidIndex = ( ULONG ) MiniportInitParameters - > NetLuid . Info . NetLuidIndex ;
2019-03-08 22:33:15 +01:00
ctx - > Statistics . Header . Type = NDIS_OBJECT_TYPE_DEFAULT ;
ctx - > Statistics . Header . Revision = NDIS_STATISTICS_INFO_REVISION_1 ;
ctx - > Statistics . Header . Size = NDIS_SIZEOF_STATISTICS_INFO_REVISION_1 ;
ctx - > Statistics . SupportedStatistics =
NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_RCV |
NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_RCV |
NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_RCV |
NDIS_STATISTICS_FLAGS_VALID_BYTES_RCV |
NDIS_STATISTICS_FLAGS_VALID_RCV_DISCARDS |
NDIS_STATISTICS_FLAGS_VALID_RCV_ERROR |
NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_XMIT |
NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_XMIT |
NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_XMIT |
NDIS_STATISTICS_FLAGS_VALID_BYTES_XMIT |
NDIS_STATISTICS_FLAGS_VALID_XMIT_ERROR |
NDIS_STATISTICS_FLAGS_VALID_XMIT_DISCARDS |
NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_RCV |
NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_RCV |
NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_RCV |
NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_XMIT |
NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_XMIT |
NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_XMIT ;
2019-04-10 13:42:16 +02:00
ctx - > Device . Handle = handle ;
2019-05-30 21:39:33 +02:00
ctx - > Device . Object = object ;
2019-04-11 19:28:33 +02:00
IoInitializeRemoveLock ( & ctx - > Device . RemoveLock , TUN_MEMORY_TAG , 0 , 0 ) ;
2019-04-10 13:42:16 +02:00
KeInitializeSpinLock ( & ctx - > Device . ReadQueue . Lock ) ;
IoCsqInitializeEx ( & ctx - > Device . ReadQueue . Csq ,
TunCsqInsertIrpEx ,
TunCsqRemoveIrp ,
TunCsqPeekNextIrp ,
TunCsqAcquireLock ,
TunCsqReleaseLock ,
TunCsqCompleteCanceledIrp ) ;
InitializeListHead ( & ctx - > Device . ReadQueue . List ) ;
KeInitializeSpinLock ( & ctx - > PacketQueue . Lock ) ;
NET_BUFFER_LIST_POOL_PARAMETERS nbl_pool_param = {
2019-04-12 08:09:06 +02:00
. Header = {
2019-04-10 13:42:16 +02:00
. Type = NDIS_OBJECT_TYPE_DEFAULT ,
. Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1 ,
. Size = NDIS_SIZEOF_NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1
} ,
. ProtocolId = NDIS_PROTOCOL_ID_DEFAULT ,
. fAllocateNetBuffer = TRUE ,
. PoolTag = TUN_MEMORY_TAG
} ;
2019-05-31 00:50:07 +02:00
# pragma warning(suppress: 6014) /* Leaking memory 'ctx->NBLPool'. Note: 'ctx->NBLPool' is freed in TunHaltEx; or on failure. */
2019-05-24 14:18:18 +02:00
ctx - > NBLPool = NdisAllocateNetBufferListPool ( MiniportAdapterHandle , & nbl_pool_param ) ;
2019-04-10 13:42:16 +02:00
if ( ! ctx - > NBLPool ) {
status = NDIS_STATUS_FAILURE ;
goto cleanup_NdisDeregisterDeviceEx ;
}
2019-03-08 22:33:15 +01:00
NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES attr = {
. Header = {
. Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES ,
2019-04-03 04:45:42 +02:00
. Revision = NdisVersion < NDIS_RUNTIME_VERSION_630 ? NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1 : NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_2 ,
2019-04-03 03:01:28 +02:00
. Size = NdisVersion < NDIS_RUNTIME_VERSION_630 ? NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1 : NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_2
2019-03-08 22:33:15 +01:00
} ,
2019-04-03 05:46:50 +02:00
. AttributeFlags = NDIS_MINIPORT_ATTRIBUTES_NO_HALT_ON_SUSPEND ,
2019-03-08 22:33:15 +01:00
. InterfaceType = NdisInterfaceInternal ,
. MiniportAdapterContext = ctx
} ;
2019-04-10 13:42:16 +02:00
if ( ! NT_SUCCESS ( status = NdisMSetMiniportAttributes ( MiniportAdapterHandle , ( PNDIS_MINIPORT_ADAPTER_ATTRIBUTES ) & attr ) ) ) {
2019-03-08 22:33:15 +01:00
status = NDIS_STATUS_FAILURE ;
2019-04-10 13:42:16 +02:00
goto cleanup_NdisFreeNetBufferListPool ;
2019-03-08 22:33:15 +01:00
}
NDIS_PM_CAPABILITIES pmcap = {
. Header = {
. Type = NDIS_OBJECT_TYPE_DEFAULT ,
2019-04-03 04:45:42 +02:00
. Revision = NdisVersion < NDIS_RUNTIME_VERSION_630 ? NDIS_PM_CAPABILITIES_REVISION_1 : NDIS_PM_CAPABILITIES_REVISION_2 ,
. Size = NdisVersion < NDIS_RUNTIME_VERSION_630 ? NDIS_SIZEOF_NDIS_PM_CAPABILITIES_REVISION_1 : NDIS_SIZEOF_NDIS_PM_CAPABILITIES_REVISION_2
2019-03-08 22:33:15 +01:00
} ,
. MinMagicPacketWakeUp = NdisDeviceStateUnspecified ,
. MinPatternWakeUp = NdisDeviceStateUnspecified ,
. MinLinkChangeWakeUp = NdisDeviceStateUnspecified
} ;
static NDIS_OID suported_oids [ ] = {
OID_GEN_MAXIMUM_TOTAL_SIZE ,
OID_GEN_CURRENT_LOOKAHEAD ,
OID_GEN_TRANSMIT_BUFFER_SPACE ,
OID_GEN_RECEIVE_BUFFER_SPACE ,
OID_GEN_TRANSMIT_BLOCK_SIZE ,
OID_GEN_RECEIVE_BLOCK_SIZE ,
OID_GEN_VENDOR_DESCRIPTION ,
OID_GEN_VENDOR_ID ,
OID_GEN_VENDOR_DRIVER_VERSION ,
OID_GEN_XMIT_OK ,
OID_GEN_RCV_OK ,
OID_GEN_CURRENT_PACKET_FILTER ,
OID_GEN_STATISTICS ,
OID_GEN_INTERRUPT_MODERATION ,
OID_GEN_LINK_PARAMETERS ,
OID_PNP_SET_POWER ,
OID_PNP_QUERY_POWER
} ;
NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES gen = {
. Header = {
. Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES ,
. Revision = NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_2 ,
. Size = NDIS_SIZEOF_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_2
} ,
. MediaType = NdisMediumIP ,
. PhysicalMediumType = NdisPhysicalMediumUnspecified ,
. MtuSize = TUN_EXCH_MAX_IP_PACKET_SIZE ,
. MaxXmitLinkSpeed = TUN_LINK_SPEED ,
. MaxRcvLinkSpeed = TUN_LINK_SPEED ,
. RcvLinkSpeed = TUN_LINK_SPEED ,
. XmitLinkSpeed = TUN_LINK_SPEED ,
. MediaConnectState = MediaConnectStateDisconnected ,
. LookaheadSize = TUN_EXCH_MAX_IP_PACKET_SIZE ,
. MacOptions =
NDIS_MAC_OPTION_TRANSFERS_NOT_PEND |
NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA |
NDIS_MAC_OPTION_NO_LOOPBACK ,
. SupportedPacketFilters =
NDIS_PACKET_TYPE_DIRECTED |
NDIS_PACKET_TYPE_ALL_MULTICAST |
NDIS_PACKET_TYPE_BROADCAST |
NDIS_PACKET_TYPE_ALL_LOCAL |
NDIS_PACKET_TYPE_ALL_FUNCTIONAL ,
. AccessType = NET_IF_ACCESS_BROADCAST ,
. DirectionType = NET_IF_DIRECTION_SENDRECEIVE ,
. ConnectionType = NET_IF_CONNECTION_DEDICATED ,
. IfType = IF_TYPE_PROP_VIRTUAL ,
. IfConnectorPresent = FALSE ,
. SupportedStatistics = ctx - > Statistics . SupportedStatistics ,
. SupportedPauseFunctions = NdisPauseFunctionsUnsupported ,
. AutoNegotiationFlags =
NDIS_LINK_STATE_XMIT_LINK_SPEED_AUTO_NEGOTIATED |
NDIS_LINK_STATE_RCV_LINK_SPEED_AUTO_NEGOTIATED |
NDIS_LINK_STATE_DUPLEX_AUTO_NEGOTIATED |
NDIS_LINK_STATE_PAUSE_FUNCTIONS_AUTO_NEGOTIATED ,
. SupportedOidList = suported_oids ,
. SupportedOidListLength = sizeof ( suported_oids ) ,
. PowerManagementCapabilitiesEx = & pmcap
} ;
2019-04-10 13:42:16 +02:00
if ( ! NT_SUCCESS ( status = NdisMSetMiniportAttributes ( MiniportAdapterHandle , ( PNDIS_MINIPORT_ADAPTER_ATTRIBUTES ) & gen ) ) ) {
2019-03-08 22:33:15 +01:00
status = NDIS_STATUS_FAILURE ;
2019-04-10 13:42:16 +02:00
goto cleanup_NdisFreeNetBufferListPool ;
2019-03-08 22:33:15 +01:00
}
/* A miniport driver can call NdisMIndicateStatusEx after setting its
* registration attributes even if the driver is still in the context
* of the MiniportInitializeEx function .
*/
2019-04-10 13:42:16 +02:00
TunIndicateStatus ( MiniportAdapterHandle , MediaConnectStateDisconnected ) ;
2019-05-31 14:40:49 +02:00
InterlockedIncrement64 ( & AdapterCount ) ;
2019-05-31 00:50:07 +02:00
InterlockedExchange ( ( LONG * ) & ctx - > State , TUN_STATE_PAUSED ) ;
2019-03-08 22:33:15 +01:00
return NDIS_STATUS_SUCCESS ;
cleanup_NdisFreeNetBufferListPool :
NdisFreeNetBufferListPool ( ctx - > NBLPool ) ;
2019-04-10 13:42:16 +02:00
cleanup_NdisDeregisterDeviceEx :
NdisDeregisterDeviceEx ( handle ) ;
2019-03-08 22:33:15 +01:00
return status ;
}
2019-04-09 14:29:21 +02:00
static MINIPORT_UNLOAD TunUnload ;
2019-03-08 22:33:15 +01:00
_Use_decl_annotations_
2019-04-09 14:29:21 +02:00
static VOID TunUnload ( PDRIVER_OBJECT DriverObject )
2019-03-08 22:33:15 +01:00
{
2019-05-31 00:50:07 +02:00
IoUnregisterPlugPlayNotificationEx ( TunNotifyInterfaceChangeHandle ) ;
2019-03-08 22:33:15 +01:00
NdisMDeregisterMiniportDriver ( NdisMiniportDriverHandle ) ;
}
2019-05-31 14:40:49 +02:00
_IRQL_requires_max_ ( APC_LEVEL )
static void TunWaitForReferencesToDropToZero ( _Inout_ TUN_CTX * ctx )
{
/* The sleep loop isn't pretty, but we don't have a choice. This is an NDIS bug we're working around. */
enum { SleepTime = 50 , TotalTime = 2 * 60 * 1000 , MaxTries = TotalTime / SleepTime } ;
# pragma warning(suppress: 28175)
for ( int i = 0 ; i < MaxTries & & ctx - > Device . Object - > ReferenceCount ; + + i )
NdisMSleep ( SleepTime ) ;
}
2019-03-08 22:33:15 +01:00
static MINIPORT_HALT TunHaltEx ;
_Use_decl_annotations_
static void TunHaltEx ( NDIS_HANDLE MiniportAdapterContext , NDIS_HALT_ACTION HaltAction )
{
TUN_CTX * ctx = ( TUN_CTX * ) MiniportAdapterContext ;
Switch to pending writes
Commentary from Jason:
Problem statement:
We call IoCompleteRequest(Irp) immediately after
NdisMIndicateReceiveNetBufferLists, which frees Irp->MdlAddress.
Since we've just given the same memory to
NdisMIndicateReceiveNetBufferLists (in a different MDL), we wind up
freeing the memory before NDIS finishes processing them.
Fix possibility 1:
Move IoCompleteRequest(Irp) to TunReturnNetBufferLists. This requires
reference counting how many NBLs are currently in flight that are
using an IRP. When that drops to zero, we can call IoCompleteRequest
(Irp).
Problem:
This means we have to block future wireguard-go Writes until *all*
NBLs have completed processing in the networking stack. Is that safe
to do? Will that introduce latency? Can userspace processes sabotage
it by refusing to read from a TCP socket buffer? We don't know enough
about how NdisMIndicateReceiveNetBufferLists works to assess its
characteristics here.
Fix possibility 2:
Use NDIS_RECEIVE_FLAGS_RESOURCES, so that
NdisMIndicateReceiveNetBufferLists makes a copy, and then we'll simply
free everything immediately after. This is slow, and it could
potentially lead to wireguard-go making the kernel allocate lots of
memory in the case that NdisAllocateNetBufferAndNetBufferList doesn't
ratelimit its creation in the same way Linux's skb_alloc does.
However, it does make the lifetime of Irps shorter, which is easier to
analyze, and it might lead to better latency, since we don't need to
wait until userspace sends its next packets, so long as Ndis'
ingestion queue doesn't become too large.
This commit switches from (2) to (1).
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-05-30 21:12:55 +02:00
ASSERT ( ! InterlockedGet64 ( & ctx - > ActiveTransactionCount ) ) ; /* Adapter should not be halted if it wasn't fully paused first. */
2019-06-04 11:55:13 +02:00
InterlockedExchange ( ( LONG * ) & ctx - > State , TUN_STATE_HALTING ) ;
2019-04-05 20:11:08 +02:00
2019-05-31 00:50:07 +02:00
if ( ctx - > PnPNotifications . Handle ) {
PVOID h = ctx - > PnPNotifications . Handle ;
ctx - > PnPNotifications . Handle = NULL ;
IoUnregisterPlugPlayNotificationEx ( h ) ;
}
if ( ctx - > PnPNotifications . FileObject ) {
FILE_OBJECT * fo = ctx - > PnPNotifications . FileObject ;
ctx - > PnPNotifications . FileObject = NULL ;
ObDereferenceObject ( fo ) ;
}
2019-04-10 13:42:16 +02:00
for ( IRP * pending_irp ; ( pending_irp = IoCsqRemoveNextIrp ( & ctx - > Device . ReadQueue . Csq , NULL ) ) ! = NULL ; )
2019-05-31 12:49:31 +02:00
TunCompleteRequest ( ctx , pending_irp , STATUS_FILE_FORCED_CLOSED , IO_NO_INCREMENT ) ;
2019-06-03 14:08:06 +02:00
/* It's a bit annoying to reconstruct this here, but it's better than storing it, and
* although we could just get it from ndishandle + 288 , that ' s probably a bit dirty . */
WCHAR symbolic_name [ sizeof ( L " \\ DosDevices \\ " TUN_DEVICE_NAME ) / sizeof ( WCHAR ) + 10 /*MAXULONG as string*/ ] = { 0 } ;
UNICODE_STRING unicode_symbolic_name ;
TunInitUnicodeString ( & unicode_symbolic_name , symbolic_name ) ;
RtlUnicodeStringPrintf ( & unicode_symbolic_name , L " \\ DosDevices \\ " TUN_DEVICE_NAME , ctx - > NetLuidIndex ) ;
/* We first get rid of the symbolic link, to prevent userspace from accidently reopening
* this while we ' re waiting for the refcount to drop to zero . It might still be possible to
* open it from the real path , in which case , maybe we should consider setting a deny - all DACL . */
IoDeleteSymbolicLink ( & unicode_symbolic_name ) ;
2019-05-30 21:39:33 +02:00
if ( InterlockedGet64 ( & ctx - > Device . RefCount ) )
TunForceHandlesClosed ( ctx ) ;
2019-04-10 11:21:51 +02:00
2019-05-31 00:50:07 +02:00
/* Wait for processing IRP(s) to complete. */
IoAcquireRemoveLock ( & ctx - > Device . RemoveLock , NULL ) ;
IoReleaseRemoveLockAndWait ( & ctx - > Device . RemoveLock , NULL ) ;
2019-04-10 13:42:16 +02:00
NdisFreeNetBufferListPool ( ctx - > NBLPool ) ;
2019-05-31 00:50:07 +02:00
/* MiniportAdapterHandle must not be used in TunDispatch(). After TunHaltEx() returns it is invalidated. */
2019-04-10 13:42:16 +02:00
ctx - > MiniportAdapterHandle = NULL ;
2019-06-04 11:55:13 +02:00
InterlockedExchange ( ( LONG * ) & ctx - > PowerState , NdisDeviceStateUnspecified ) ;
InterlockedExchange ( ( LONG * ) & ctx - > State , TUN_STATE_HALTED ) ;
2019-03-27 13:44:55 +01:00
2019-05-31 14:40:49 +02:00
if ( ! InterlockedDecrement64 ( & AdapterCount ) )
TunWaitForReferencesToDropToZero ( ctx ) ;
2019-04-11 19:28:33 +02:00
/* Deregister device _after_ we are done writing to ctx not to risk an UaF. The ctx is hosted by device extension. */
2019-03-08 22:33:15 +01:00
NdisDeregisterDeviceEx ( ctx - > Device . Handle ) ;
}
_IRQL_requires_max_ ( PASSIVE_LEVEL )
2019-04-02 14:17:53 +02:00
static NDIS_STATUS TunOidSet ( _Inout_ TUN_CTX * ctx , _Inout_ NDIS_OID_REQUEST * OidRequest )
2019-03-08 22:33:15 +01:00
{
2019-04-02 14:17:53 +02:00
ASSERT ( OidRequest - > RequestType = = NdisRequestSetInformation ) ;
2019-03-08 22:33:15 +01:00
2019-04-05 11:04:13 +02:00
OidRequest - > DATA . SET_INFORMATION . BytesNeeded =
2019-04-02 14:17:53 +02:00
OidRequest - > DATA . SET_INFORMATION . BytesRead = 0 ;
switch ( OidRequest - > DATA . SET_INFORMATION . Oid ) {
2019-03-08 22:33:15 +01:00
case OID_GEN_CURRENT_PACKET_FILTER :
case OID_GEN_CURRENT_LOOKAHEAD :
2019-04-02 14:17:53 +02:00
if ( OidRequest - > DATA . SET_INFORMATION . InformationBufferLength ! = 4 ) {
OidRequest - > DATA . SET_INFORMATION . BytesNeeded = 4 ;
2019-03-08 22:33:15 +01:00
return NDIS_STATUS_INVALID_LENGTH ;
}
2019-04-02 14:17:53 +02:00
OidRequest - > DATA . SET_INFORMATION . BytesRead = 4 ;
2019-03-08 22:33:15 +01:00
return NDIS_STATUS_SUCCESS ;
case OID_GEN_LINK_PARAMETERS :
2019-04-02 14:17:53 +02:00
OidRequest - > DATA . SET_INFORMATION . BytesRead = OidRequest - > DATA . SET_INFORMATION . InformationBufferLength ;
2019-03-08 22:33:15 +01:00
return NDIS_STATUS_SUCCESS ;
case OID_GEN_INTERRUPT_MODERATION :
return NDIS_STATUS_INVALID_DATA ;
case OID_PNP_SET_POWER :
2019-04-02 14:17:53 +02:00
if ( OidRequest - > DATA . SET_INFORMATION . InformationBufferLength ! = sizeof ( NDIS_DEVICE_POWER_STATE ) ) {
OidRequest - > DATA . SET_INFORMATION . BytesNeeded = sizeof ( NDIS_DEVICE_POWER_STATE ) ;
2019-03-08 22:33:15 +01:00
return NDIS_STATUS_INVALID_LENGTH ;
}
2019-04-02 14:17:53 +02:00
OidRequest - > DATA . SET_INFORMATION . BytesRead = sizeof ( NDIS_DEVICE_POWER_STATE ) ;
2019-05-24 14:18:18 +02:00
NDIS_DEVICE_POWER_STATE state = * ( NDIS_DEVICE_POWER_STATE * ) OidRequest - > DATA . SET_INFORMATION . InformationBuffer ;
2019-06-04 11:55:13 +02:00
KIRQL irql = ExAcquireSpinLockExclusive ( & ctx - > TransitionLock ) ;
InterlockedExchange ( ( LONG * ) & ctx - > PowerState , state ) ;
2019-05-24 14:18:18 +02:00
ExReleaseSpinLockExclusive ( & ctx - > TransitionLock , irql ) ;
2019-06-04 11:55:13 +02:00
if ( state > = NdisDeviceStateD1 )
TunQueueClear ( ctx , STATUS_NDIS_LOW_POWER_STATE ) ;
2019-05-24 14:18:18 +02:00
2019-03-08 22:33:15 +01:00
return NDIS_STATUS_SUCCESS ;
}
2019-04-12 15:27:45 +02:00
return NDIS_STATUS_NOT_SUPPORTED ;
2019-03-08 22:33:15 +01:00
}
2019-04-05 11:04:13 +02:00
_IRQL_requires_max_ ( APC_LEVEL )
_Must_inspect_result_
static NDIS_STATUS TunOidQueryWrite ( _Inout_ NDIS_OID_REQUEST * OidRequest , _In_ ULONG value )
{
if ( OidRequest - > DATA . QUERY_INFORMATION . InformationBufferLength < sizeof ( ULONG ) ) {
OidRequest - > DATA . QUERY_INFORMATION . BytesNeeded = sizeof ( ULONG ) ;
OidRequest - > DATA . QUERY_INFORMATION . BytesWritten = 0 ;
2019-04-12 15:27:45 +02:00
return NDIS_STATUS_BUFFER_TOO_SHORT ;
2019-04-05 11:04:13 +02:00
}
OidRequest - > DATA . QUERY_INFORMATION . BytesNeeded =
OidRequest - > DATA . QUERY_INFORMATION . BytesWritten = sizeof ( ULONG ) ;
* ( ULONG * ) OidRequest - > DATA . QUERY_INFORMATION . InformationBuffer = value ;
return NDIS_STATUS_SUCCESS ;
}
_IRQL_requires_max_ ( APC_LEVEL )
_Must_inspect_result_
static NDIS_STATUS TunOidQueryWrite32or64 ( _Inout_ NDIS_OID_REQUEST * OidRequest , _In_ ULONG64 value )
{
if ( OidRequest - > DATA . QUERY_INFORMATION . InformationBufferLength < sizeof ( ULONG ) ) {
OidRequest - > DATA . QUERY_INFORMATION . BytesNeeded = sizeof ( ULONG64 ) ;
OidRequest - > DATA . QUERY_INFORMATION . BytesWritten = 0 ;
2019-04-12 15:27:45 +02:00
return NDIS_STATUS_BUFFER_TOO_SHORT ;
2019-04-05 11:04:13 +02:00
}
if ( OidRequest - > DATA . QUERY_INFORMATION . InformationBufferLength < sizeof ( ULONG64 ) ) {
OidRequest - > DATA . QUERY_INFORMATION . BytesNeeded = sizeof ( ULONG64 ) ;
OidRequest - > DATA . QUERY_INFORMATION . BytesWritten = sizeof ( ULONG ) ;
* ( ULONG * ) OidRequest - > DATA . QUERY_INFORMATION . InformationBuffer = ( ULONG ) ( value & 0xffffffff ) ;
return NDIS_STATUS_SUCCESS ;
}
OidRequest - > DATA . QUERY_INFORMATION . BytesNeeded =
OidRequest - > DATA . QUERY_INFORMATION . BytesWritten = sizeof ( ULONG64 ) ;
* ( ULONG64 * ) OidRequest - > DATA . QUERY_INFORMATION . InformationBuffer = value ;
return NDIS_STATUS_SUCCESS ;
}
_IRQL_requires_max_ ( APC_LEVEL )
_Must_inspect_result_
static NDIS_STATUS TunOidQueryWriteBuf ( _Inout_ NDIS_OID_REQUEST * OidRequest , _In_bytecount_ ( size ) const void * buf , _In_ UINT size )
{
if ( OidRequest - > DATA . QUERY_INFORMATION . InformationBufferLength < size ) {
OidRequest - > DATA . QUERY_INFORMATION . BytesNeeded = size ;
OidRequest - > DATA . QUERY_INFORMATION . BytesWritten = 0 ;
2019-04-12 15:27:45 +02:00
return NDIS_STATUS_BUFFER_TOO_SHORT ;
2019-04-05 11:04:13 +02:00
}
OidRequest - > DATA . QUERY_INFORMATION . BytesNeeded =
OidRequest - > DATA . QUERY_INFORMATION . BytesWritten = size ;
NdisMoveMemory ( OidRequest - > DATA . QUERY_INFORMATION . InformationBuffer , buf , size ) ;
return NDIS_STATUS_SUCCESS ;
}
2019-03-08 22:33:15 +01:00
_IRQL_requires_max_ ( APC_LEVEL )
2019-03-28 11:21:51 +01:00
_Must_inspect_result_
2019-04-02 14:17:53 +02:00
static NDIS_STATUS TunOidQuery ( _Inout_ TUN_CTX * ctx , _Inout_ NDIS_OID_REQUEST * OidRequest )
2019-03-08 22:33:15 +01:00
{
2019-04-02 14:17:53 +02:00
ASSERT ( OidRequest - > RequestType = = NdisRequestQueryInformation | | OidRequest - > RequestType = = NdisRequestQueryStatistics ) ;
switch ( OidRequest - > DATA . QUERY_INFORMATION . Oid ) {
2019-03-08 22:33:15 +01:00
case OID_GEN_MAXIMUM_TOTAL_SIZE :
case OID_GEN_TRANSMIT_BLOCK_SIZE :
case OID_GEN_RECEIVE_BLOCK_SIZE :
2019-04-05 11:04:13 +02:00
return TunOidQueryWrite ( OidRequest , TUN_EXCH_MAX_IP_PACKET_SIZE ) ;
2019-03-08 22:33:15 +01:00
case OID_GEN_TRANSMIT_BUFFER_SPACE :
2019-04-05 20:19:13 +02:00
return TunOidQueryWrite ( OidRequest , TUN_EXCH_MAX_IP_PACKET_SIZE * TUN_QUEUE_MAX_NBLS ) ;
2019-03-08 22:33:15 +01:00
case OID_GEN_RECEIVE_BUFFER_SPACE :
2019-04-05 11:04:13 +02:00
return TunOidQueryWrite ( OidRequest , TUN_EXCH_MAX_IP_PACKET_SIZE * TUN_EXCH_MAX_PACKETS ) ;
2019-03-08 22:33:15 +01:00
case OID_GEN_VENDOR_ID :
2019-04-05 11:04:13 +02:00
return TunOidQueryWrite ( OidRequest , TunHtonl ( TUN_VENDOR_ID ) ) ;
2019-03-08 22:33:15 +01:00
case OID_GEN_VENDOR_DESCRIPTION :
2019-04-05 11:04:13 +02:00
return TunOidQueryWriteBuf ( OidRequest , TUN_VENDOR_NAME , ( UINT ) sizeof ( TUN_VENDOR_NAME ) ) ;
2019-03-08 22:33:15 +01:00
case OID_GEN_VENDOR_DRIVER_VERSION :
2019-04-05 11:04:13 +02:00
return TunOidQueryWrite ( OidRequest , ( WINTUN_VERSION_MAJ < < 16 ) | WINTUN_VERSION_MIN ) ;
2019-03-08 22:33:15 +01:00
case OID_GEN_XMIT_OK :
2019-04-05 11:04:13 +02:00
return TunOidQueryWrite32or64 ( OidRequest ,
2019-03-08 22:33:15 +01:00
InterlockedGet64 ( ( LONG64 * ) & ctx - > Statistics . ifHCOutUcastPkts ) +
InterlockedGet64 ( ( LONG64 * ) & ctx - > Statistics . ifHCOutMulticastPkts ) +
InterlockedGet64 ( ( LONG64 * ) & ctx - > Statistics . ifHCOutBroadcastPkts ) ) ;
case OID_GEN_RCV_OK :
2019-04-05 11:04:13 +02:00
return TunOidQueryWrite32or64 ( OidRequest ,
2019-03-08 22:33:15 +01:00
InterlockedGet64 ( ( LONG64 * ) & ctx - > Statistics . ifHCInUcastPkts ) +
InterlockedGet64 ( ( LONG64 * ) & ctx - > Statistics . ifHCInMulticastPkts ) +
InterlockedGet64 ( ( LONG64 * ) & ctx - > Statistics . ifHCInBroadcastPkts ) ) ;
case OID_GEN_STATISTICS :
2019-04-05 11:04:13 +02:00
return TunOidQueryWriteBuf ( OidRequest , & ctx - > Statistics , ( UINT ) sizeof ( ctx - > Statistics ) ) ;
2019-03-08 22:33:15 +01:00
case OID_GEN_INTERRUPT_MODERATION : {
static const NDIS_INTERRUPT_MODERATION_PARAMETERS intp = {
. Header = {
. Type = NDIS_OBJECT_TYPE_DEFAULT ,
. Revision = NDIS_INTERRUPT_MODERATION_PARAMETERS_REVISION_1 ,
. Size = NDIS_SIZEOF_INTERRUPT_MODERATION_PARAMETERS_REVISION_1
} ,
. InterruptModeration = NdisInterruptModerationNotSupported
} ;
2019-04-05 11:04:13 +02:00
return TunOidQueryWriteBuf ( OidRequest , & intp , ( UINT ) sizeof ( intp ) ) ;
2019-03-08 22:33:15 +01:00
}
case OID_PNP_QUERY_POWER :
2019-04-05 11:04:13 +02:00
OidRequest - > DATA . QUERY_INFORMATION . BytesNeeded =
2019-04-02 14:17:53 +02:00
OidRequest - > DATA . QUERY_INFORMATION . BytesWritten = 0 ;
2019-04-05 11:04:13 +02:00
return NDIS_STATUS_SUCCESS ;
2019-03-08 22:33:15 +01:00
}
2019-04-05 11:04:13 +02:00
OidRequest - > DATA . QUERY_INFORMATION . BytesWritten = 0 ;
2019-04-12 15:27:45 +02:00
return NDIS_STATUS_NOT_SUPPORTED ;
2019-03-08 22:33:15 +01:00
}
static MINIPORT_OID_REQUEST TunOidRequest ;
_Use_decl_annotations_
static NDIS_STATUS TunOidRequest ( NDIS_HANDLE MiniportAdapterContext , PNDIS_OID_REQUEST OidRequest )
{
switch ( OidRequest - > RequestType ) {
case NdisRequestQueryInformation :
case NdisRequestQueryStatistics :
2019-04-02 14:17:53 +02:00
return TunOidQuery ( MiniportAdapterContext , OidRequest ) ;
2019-03-08 22:33:15 +01:00
case NdisRequestSetInformation :
2019-04-02 14:17:53 +02:00
return TunOidSet ( MiniportAdapterContext , OidRequest ) ;
2019-03-08 22:33:15 +01:00
default :
2019-04-12 15:27:45 +02:00
return NDIS_STATUS_INVALID_OID ;
2019-03-08 22:33:15 +01:00
}
}
static MINIPORT_DIRECT_OID_REQUEST TunDirectOidRequest ;
_Use_decl_annotations_
static NDIS_STATUS TunDirectOidRequest ( NDIS_HANDLE MiniportAdapterContext , PNDIS_OID_REQUEST OidRequest )
{
switch ( OidRequest - > RequestType ) {
case NdisRequestQueryInformation :
case NdisRequestQueryStatistics :
case NdisRequestSetInformation :
2019-04-12 15:27:45 +02:00
return NDIS_STATUS_NOT_SUPPORTED ;
2019-03-08 22:33:15 +01:00
default :
2019-04-12 15:27:45 +02:00
return NDIS_STATUS_INVALID_OID ;
2019-03-08 22:33:15 +01:00
}
}
static MINIPORT_SEND_NET_BUFFER_LISTS TunSendNetBufferLists ;
_Use_decl_annotations_
static void TunSendNetBufferLists ( NDIS_HANDLE MiniportAdapterContext , NET_BUFFER_LIST * NetBufferLists , NDIS_PORT_NUMBER PortNumber , ULONG SendFlags )
{
TUN_CTX * ctx = ( TUN_CTX * ) MiniportAdapterContext ;
2019-05-24 14:18:18 +02:00
KIRQL irql = ExAcquireSpinLockShared ( & ctx - > TransitionLock ) ;
2019-03-08 22:33:15 +01:00
NDIS_STATUS status ;
2019-04-10 13:42:16 +02:00
if ( ! NT_SUCCESS ( status = TunCheckForPause ( ctx ) ) ) {
2019-03-08 22:33:15 +01:00
TunSetNBLStatus ( NetBufferLists , status ) ;
2019-06-04 09:04:17 +02:00
NdisMSendNetBufferListsComplete ( ctx - > MiniportAdapterHandle , NetBufferLists , NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL ) ;
2019-04-03 05:46:50 +02:00
goto cleanup_TunCompletePause ;
2019-03-08 22:33:15 +01:00
}
2019-04-10 13:42:16 +02:00
TunQueueAppend ( ctx , NetBufferLists , TUN_QUEUE_MAX_NBLS ) ;
2019-05-24 14:18:18 +02:00
2019-04-10 13:42:16 +02:00
TunQueueProcess ( ctx ) ;
2019-03-08 22:33:15 +01:00
2019-04-03 05:46:50 +02:00
cleanup_TunCompletePause :
2019-04-05 20:11:08 +02:00
TunCompletePause ( ctx , TRUE ) ;
2019-05-24 14:18:18 +02:00
ExReleaseSpinLockShared ( & ctx - > TransitionLock , irql ) ;
2019-03-08 22:33:15 +01:00
}
DRIVER_INITIALIZE DriverEntry ;
_Use_decl_annotations_
NTSTATUS DriverEntry ( DRIVER_OBJECT * DriverObject , UNICODE_STRING * RegistryPath )
{
2019-05-31 00:50:07 +02:00
NTSTATUS status ;
2019-04-03 03:01:28 +02:00
NdisVersion = NdisGetVersion ( ) ;
if ( NdisVersion < NDIS_RUNTIME_VERSION_620 )
return NDIS_STATUS_UNSUPPORTED_REVISION ;
if ( NdisVersion > NDIS_RUNTIME_VERSION_630 )
NdisVersion = NDIS_RUNTIME_VERSION_630 ;
2019-05-31 00:50:07 +02:00
if ( ! NT_SUCCESS ( status = IoRegisterPlugPlayNotification ( EventCategoryDeviceInterfaceChange , 0 ,
( PVOID ) & GUID_DEVINTERFACE_NET , DriverObject , TunPnPNotifyInterfaceChange , DriverObject ,
& TunNotifyInterfaceChangeHandle ) ) )
return status ;
2019-03-08 22:33:15 +01:00
NDIS_MINIPORT_DRIVER_CHARACTERISTICS miniport = {
. Header = {
. Type = NDIS_OBJECT_TYPE_MINIPORT_DRIVER_CHARACTERISTICS ,
. Revision = NDIS_MINIPORT_DRIVER_CHARACTERISTICS_REVISION_2 ,
. Size = NDIS_SIZEOF_MINIPORT_DRIVER_CHARACTERISTICS_REVISION_2
} ,
2019-04-03 04:33:16 +02:00
. MajorNdisVersion = ( UCHAR ) ( ( NdisVersion & 0x00ff0000 ) > > 16 ) ,
. MinorNdisVersion = ( UCHAR ) ( NdisVersion & 0x000000ff ) ,
2019-03-08 22:33:15 +01:00
. MajorDriverVersion = WINTUN_VERSION_MAJ ,
. MinorDriverVersion = WINTUN_VERSION_MIN ,
. InitializeHandlerEx = TunInitializeEx ,
. HaltHandlerEx = TunHaltEx ,
2019-04-09 14:29:21 +02:00
. UnloadHandler = TunUnload ,
2019-03-08 22:33:15 +01:00
. PauseHandler = TunPause ,
. RestartHandler = TunRestart ,
. OidRequestHandler = TunOidRequest ,
. SendNetBufferListsHandler = TunSendNetBufferLists ,
. ReturnNetBufferListsHandler = TunReturnNetBufferLists ,
. CancelSendHandler = TunCancelSend ,
. DevicePnPEventNotifyHandler = TunDevicePnPEventNotify ,
. ShutdownHandlerEx = TunShutdownEx ,
. CancelOidRequestHandler = TunCancelOidRequest ,
. DirectOidRequestHandler = TunDirectOidRequest ,
. CancelDirectOidRequestHandler = TunCancelDirectOidRequest
} ;
2019-05-31 00:50:07 +02:00
if ( ! NT_SUCCESS ( status = NdisMRegisterMiniportDriver ( DriverObject , RegistryPath , NULL , & miniport , & NdisMiniportDriverHandle ) ) ) {
IoUnregisterPlugPlayNotificationEx ( TunNotifyInterfaceChangeHandle ) ;
return status ;
}
return STATUS_SUCCESS ;
2019-03-08 22:33:15 +01:00
}