/* SPDX-License-Identifier: GPL-2.0 * * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. */ #include #include #include #include #include #include "undocumented.h" #pragma warning(disable : 4100) /* unreferenced formal parameter */ #pragma warning(disable : 4200) /* nonstandard: zero-sized array in struct/union */ #pragma warning(disable : 4204) /* nonstandard: non-constant aggregate initializer */ #pragma warning(disable : 4221) /* nonstandard: cannot be initialized using address of automatic variable */ #pragma warning(disable : 6320) /* exception-filter expression is the constant EXCEPTION_EXECUTE_HANDLER */ #define NDIS_MINIPORT_VERSION_MIN ((NDIS_MINIPORT_MINIMUM_MAJOR_VERSION << 16) | NDIS_MINIPORT_MINIMUM_MINOR_VERSION) #define NDIS_MINIPORT_VERSION_MAX ((NDIS_MINIPORT_MAJOR_VERSION << 16) | NDIS_MINIPORT_MINOR_VERSION) #define TUN_VENDOR_NAME "Wintun Tunnel" #define TUN_VENDOR_ID 0xFFFFFF00 #define TUN_LINK_SPEED 100000000000ULL /* 100gbps */ /* Memory alignment of packets and rings */ #define TUN_ALIGNMENT sizeof(ULONG) #define TUN_ALIGN(Size) (((ULONG)(Size) + ((ULONG)TUN_ALIGNMENT - 1)) & ~((ULONG)TUN_ALIGNMENT - 1)) #define TUN_IS_ALIGNED(Size) (!((ULONG)(Size) & ((ULONG)TUN_ALIGNMENT - 1))) /* Maximum IP packet size */ #define TUN_MAX_IP_PACKET_SIZE 0xFFFF /* Maximum packet size */ #define TUN_MAX_PACKET_SIZE TUN_ALIGN(sizeof(TUN_PACKET) + TUN_MAX_IP_PACKET_SIZE) /* Minimum ring capacity. */ #define TUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */ /* Maximum ring capacity. */ #define TUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */ /* Calculates ring capacity */ #define TUN_RING_CAPACITY(Size) ((Size) - sizeof(TUN_RING) - (TUN_MAX_PACKET_SIZE - TUN_ALIGNMENT)) /* Calculates ring offset modulo capacity */ #define TUN_RING_WRAP(Value, Capacity) ((Value) & (Capacity - 1)) #if REG_DWORD == REG_DWORD_BIG_ENDIAN # define HTONS(x) ((USHORT)(x)) # define HTONL(x) ((ULONG)(x)) #elif REG_DWORD == REG_DWORD_LITTLE_ENDIAN # define HTONS(x) ((((USHORT)(x)&0x00ff) << 8) | (((USHORT)(x)&0xff00) >> 8)) # define HTONL(x) \ ((((ULONG)(x)&0x000000ff) << 24) | (((ULONG)(x)&0x0000ff00) << 8) | (((ULONG)(x)&0x00ff0000) >> 8) | \ (((ULONG)(x)&0xff000000) >> 24)) #else # error "Unable to determine endianess" #endif #define TUN_MEMORY_TAG HTONL('wtun') typedef struct _TUN_PACKET { /* Size of packet data (TUN_MAX_IP_PACKET_SIZE max) */ ULONG Size; /* Packet data */ UCHAR _Field_size_bytes_(Size) Data[]; } TUN_PACKET; typedef struct _TUN_RING { /* Byte offset of the first packet in the ring. Its value must be a multiple of TUN_ALIGNMENT and less than ring * capacity. */ volatile ULONG Head; /* Byte offset of the first free space in the ring. Its value must be multiple of TUN_ALIGNMENT and less than ring * capacity. */ volatile ULONG Tail; /* Non-zero when consumer is in alertable state. */ volatile LONG Alertable; /* Ring data. Its capacity must be a power of 2 + extra TUN_MAX_PACKET_SIZE-TUN_ALIGNMENT space to * eliminate need for wrapping. */ UCHAR Data[]; } TUN_RING; typedef struct _TUN_REGISTER_RINGS { struct { /* Size of the ring */ ULONG RingSize; /* Pointer to client allocated ring */ TUN_RING *Ring; /* On send: An event created by the client the Wintun signals after it moves the Tail member of the send ring. * On receive: An event created by the client the client will signal when it moves the Tail member of * the receive ring if receive ring is alertable. */ HANDLE TailMoved; } Send, Receive; } TUN_REGISTER_RINGS; #ifdef _WIN64 typedef struct _TUN_REGISTER_RINGS_32 { struct { /* Size of the ring */ ULONG RingSize; /* 32-bit addres of client allocated ring */ ULONG Ring; /* On send: An event created by the client the Wintun signals after it moves the Tail member of the send ring. * On receive: An event created by the client the client will signal when it moves the Tail member of * the receive ring if receive ring is alertable. */ ULONG TailMoved; } Send, Receive; } TUN_REGISTER_RINGS_32; #endif /* Register rings hosted by the client. * The lpInBuffer and nInBufferSize parameters of DeviceIoControl() must point to an TUN_REGISTER_RINGS struct. * Client must wait for this IOCTL to finish before adding packets to the ring. */ #define TUN_IOCTL_REGISTER_RINGS CTL_CODE(51820U, 0x970U, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) typedef struct _TUN_CTX { volatile LONG Running; /* Used like RCU. When we're making use of rings, we take a shared lock. When we want to register or release the * rings 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(). */ EX_SPIN_LOCK TransitionLock; NDIS_HANDLE MiniportAdapterHandle; /* This is actually a pointer to NDIS_MINIPORT_BLOCK struct. */ DEVICE_OBJECT *FunctionalDeviceObject; NDIS_STATISTICS_INFO Statistics; struct { LIST_ENTRY Entry; ERESOURCE RegistrationLock; FILE_OBJECT *OwningFileObject; HANDLE OwningProcessId; KEVENT Disconnected; struct { MDL *Mdl; TUN_RING *Ring; ULONG Capacity; KEVENT *TailMoved; KSPIN_LOCK Lock; ULONG RingTail; struct { NET_BUFFER_LIST *Head, *Tail; } ActiveNbls; } Send; struct { MDL *Mdl; TUN_RING *Ring; ULONG Capacity; KEVENT *TailMoved; HANDLE Thread; KSPIN_LOCK Lock; struct { NET_BUFFER_LIST *Head, *Tail; KEVENT Empty; } ActiveNbls; } Receive; } Device; NDIS_HANDLE NblPool; } TUN_CTX; static UINT NdisVersion; static NDIS_HANDLE NdisMiniportDriverHandle; static DRIVER_DISPATCH *NdisDispatchDeviceControl, *NdisDispatchClose, *NdisDispatchPnp; static ERESOURCE TunDispatchCtxGuard, TunDispatchDeviceListLock; static RTL_STATIC_LIST_HEAD(TunDispatchDeviceList); /* Binary representation of O:SYD:P(A;;FA;;;SY)(A;;FA;;;BA)S:(ML;;NWNRNX;;;HI) */ static SECURITY_DESCRIPTOR *TunDispatchSecurityDescriptor = (SECURITY_DESCRIPTOR *)(__declspec(align(8)) UCHAR[]){ 0x01, 0x00, 0x14, 0x90, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x14, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x00, 0x02, 0x00, 0x34, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0xff, 0x01, 0x1f, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xff, 0x01, 0x1f, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x20, 0x00, 0x00, 0x00, 0x20, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00 }; _IRQL_requires_max_(DISPATCH_LEVEL) static VOID TunIndicateStatus(_In_ NDIS_HANDLE MiniportAdapterHandle, _In_ NDIS_MEDIA_CONNECT_STATE MediaConnectState) { NDIS_LINK_STATE State = { .Header = { .Type = NDIS_OBJECT_TYPE_DEFAULT, .Revision = NDIS_LINK_STATE_REVISION_1, .Size = NDIS_SIZEOF_LINK_STATE_REVISION_1 }, .MediaConnectState = MediaConnectState, .MediaDuplexState = MediaDuplexStateFull, .XmitLinkSpeed = TUN_LINK_SPEED, .RcvLinkSpeed = TUN_LINK_SPEED, .PauseFunctions = NdisPauseFunctionsUnsupported }; NDIS_STATUS_INDICATION Indication = { .Header = { .Type = NDIS_OBJECT_TYPE_STATUS_INDICATION, .Revision = NDIS_STATUS_INDICATION_REVISION_1, .Size = NDIS_SIZEOF_STATUS_INDICATION_REVISION_1 }, .SourceHandle = MiniportAdapterHandle, .StatusCode = NDIS_STATUS_LINK_STATE, .StatusBuffer = &State, .StatusBufferSize = sizeof(State) }; NdisMIndicateStatusEx(MiniportAdapterHandle, &Indication); } /* Send: We should not modify NET_BUFFER_LIST_NEXT_NBL(Nbl) to prevent fragmented NBLs to separate. * Receive: NDIS may change NET_BUFFER_LIST_NEXT_NBL(Nbl) at will between the NdisMIndicateReceiveNetBufferLists() and * MINIPORT_RETURN_NET_BUFFER_LISTS calls. Therefore, we use our own ->Next pointer for book-keeping. */ #define NET_BUFFER_LIST_NEXT_NBL_EX(Nbl) (NET_BUFFER_LIST_MINIPORT_RESERVED(Nbl)[1]) static VOID TunNblSetOffsetAndMarkActive(_Inout_ NET_BUFFER_LIST *Nbl, _In_ ULONG Offset) { ASSERT(TUN_IS_ALIGNED(Offset)); /* Alignment ensures bit 0 will be 0 (0=active, 1=completed). */ NET_BUFFER_LIST_MINIPORT_RESERVED(Nbl)[0] = (VOID *)Offset; } static ULONG TunNblGetOffset(_In_ NET_BUFFER_LIST *Nbl) { return (ULONG)((ULONG_PTR)(NET_BUFFER_LIST_MINIPORT_RESERVED(Nbl)[0]) & ~((ULONG_PTR)TUN_ALIGNMENT - 1)); } static VOID TunNblMarkCompleted(_Inout_ NET_BUFFER_LIST *Nbl) { *(ULONG_PTR *)&NET_BUFFER_LIST_MINIPORT_RESERVED(Nbl)[0] |= 1; } static BOOLEAN TunNblIsCompleted(_In_ NET_BUFFER_LIST *Nbl) { return (ULONG_PTR)(NET_BUFFER_LIST_MINIPORT_RESERVED(Nbl)[0]) & 1; } 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; LONG64 SentPacketsCount = 0, SentPacketsSize = 0, ErrorPacketsCount = 0, DiscardedPacketsCount = 0; /* Measure NBLs. */ ULONG PacketsCount = 0, RequiredRingSpace = 0; for (NET_BUFFER_LIST *Nbl = NetBufferLists; Nbl; Nbl = NET_BUFFER_LIST_NEXT_NBL(Nbl)) { for (NET_BUFFER *Nb = NET_BUFFER_LIST_FIRST_NB(Nbl); Nb; Nb = NET_BUFFER_NEXT_NB(Nb)) { PacketsCount++; UINT PacketSize = NET_BUFFER_DATA_LENGTH(Nb); if (PacketSize > TUN_MAX_IP_PACKET_SIZE) continue; /* The same condition holds down below, where we `goto skipPacket`. */ RequiredRingSpace += TUN_ALIGN(sizeof(TUN_PACKET) + PacketSize); } } KIRQL Irql = ExAcquireSpinLockShared(&Ctx->TransitionLock); NDIS_STATUS Status; if ((Status = NDIS_STATUS_PAUSED, !ReadAcquire(&Ctx->Running)) || (Status = NDIS_STATUS_MEDIA_DISCONNECTED, KeReadStateEvent(&Ctx->Device.Disconnected))) goto skipNbl; TUN_RING *Ring = Ctx->Device.Send.Ring; ULONG RingCapacity = Ctx->Device.Send.Capacity; /* Allocate space for packets in the ring. */ ULONG RingHead = ReadULongAcquire(&Ring->Head); if (Status = NDIS_STATUS_ADAPTER_NOT_READY, RingHead >= RingCapacity) goto skipNbl; KLOCK_QUEUE_HANDLE LockHandle; KeAcquireInStackQueuedSpinLock(&Ctx->Device.Send.Lock, &LockHandle); ULONG RingTail = Ctx->Device.Send.RingTail; ASSERT(RingTail < RingCapacity); ULONG RingSpace = TUN_RING_WRAP(RingHead - RingTail - TUN_ALIGNMENT, RingCapacity); if (Status = NDIS_STATUS_BUFFER_OVERFLOW, RingSpace < RequiredRingSpace) goto cleanupKeReleaseInStackQueuedSpinLock; Ctx->Device.Send.RingTail = TUN_RING_WRAP(RingTail + RequiredRingSpace, RingCapacity); TunNblSetOffsetAndMarkActive(NetBufferLists, Ctx->Device.Send.RingTail); *(Ctx->Device.Send.ActiveNbls.Head ? &NET_BUFFER_LIST_NEXT_NBL_EX(Ctx->Device.Send.ActiveNbls.Tail) : &Ctx->Device.Send.ActiveNbls.Head) = NetBufferLists; Ctx->Device.Send.ActiveNbls.Tail = NetBufferLists; KeReleaseInStackQueuedSpinLock(&LockHandle); /* Copy packets. */ for (NET_BUFFER_LIST *Nbl = NetBufferLists; Nbl; Nbl = NET_BUFFER_LIST_NEXT_NBL(Nbl)) { for (NET_BUFFER *Nb = NET_BUFFER_LIST_FIRST_NB(Nbl); Nb; Nb = NET_BUFFER_NEXT_NB(Nb)) { UINT PacketSize = NET_BUFFER_DATA_LENGTH(Nb); if (Status = NDIS_STATUS_INVALID_LENGTH, PacketSize > TUN_MAX_IP_PACKET_SIZE) goto skipPacket; TUN_PACKET *Packet = (TUN_PACKET *)(Ring->Data + RingTail); Packet->Size = PacketSize; void *NbData = NdisGetDataBuffer(Nb, PacketSize, Packet->Data, 1, 0); if (!NbData) { /* The space for the packet has already been allocated in the ring. Write a zero-packet rather than * fixing the gap in the ring. */ NdisZeroMemory(Packet->Data, PacketSize); DiscardedPacketsCount++; NET_BUFFER_LIST_STATUS(Nbl) = NDIS_STATUS_FAILURE; } else { if (NbData != Packet->Data) NdisMoveMemory(Packet->Data, NbData, PacketSize); SentPacketsCount++; SentPacketsSize += PacketSize; } RingTail = TUN_RING_WRAP(RingTail + TUN_ALIGN(sizeof(TUN_PACKET) + PacketSize), RingCapacity); continue; skipPacket: ErrorPacketsCount++; NET_BUFFER_LIST_STATUS(Nbl) = Status; } } ASSERT(RingTail == TunNblGetOffset(NetBufferLists)); TunNblMarkCompleted(NetBufferLists); /* Adjust the ring tail. */ KeAcquireInStackQueuedSpinLock(&Ctx->Device.Send.Lock, &LockHandle); while (Ctx->Device.Send.ActiveNbls.Head && TunNblIsCompleted(Ctx->Device.Send.ActiveNbls.Head)) { NET_BUFFER_LIST *CompletedNbl = Ctx->Device.Send.ActiveNbls.Head; Ctx->Device.Send.ActiveNbls.Head = NET_BUFFER_LIST_NEXT_NBL_EX(CompletedNbl); WriteULongRelease(&Ring->Tail, TunNblGetOffset(CompletedNbl)); KeSetEvent(Ctx->Device.Send.TailMoved, IO_NETWORK_INCREMENT, FALSE); NdisMSendNetBufferListsComplete( Ctx->MiniportAdapterHandle, CompletedNbl, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL); } KeReleaseInStackQueuedSpinLock(&LockHandle); ExReleaseSpinLockShared(&Ctx->TransitionLock, Irql); goto updateStatistics; cleanupKeReleaseInStackQueuedSpinLock: KeReleaseInStackQueuedSpinLock(&LockHandle); skipNbl: for (NET_BUFFER_LIST *Nbl = NetBufferLists; Nbl; Nbl = NET_BUFFER_LIST_NEXT_NBL(Nbl)) NET_BUFFER_LIST_STATUS(Nbl) = Status; DiscardedPacketsCount += PacketsCount; ExReleaseSpinLockShared(&Ctx->TransitionLock, Irql); NdisMSendNetBufferListsComplete(Ctx->MiniportAdapterHandle, NetBufferLists, 0); updateStatistics: InterlockedAddNoFence64((LONG64 *)&Ctx->Statistics.ifHCOutOctets, SentPacketsSize); InterlockedAddNoFence64((LONG64 *)&Ctx->Statistics.ifHCOutUcastOctets, SentPacketsSize); InterlockedAddNoFence64((LONG64 *)&Ctx->Statistics.ifHCOutUcastPkts, SentPacketsCount); InterlockedAddNoFence64((LONG64 *)&Ctx->Statistics.ifOutErrors, ErrorPacketsCount); InterlockedAddNoFence64((LONG64 *)&Ctx->Statistics.ifOutDiscards, DiscardedPacketsCount); } static MINIPORT_CANCEL_SEND TunCancelSend; _Use_decl_annotations_ static VOID TunCancelSend(NDIS_HANDLE MiniportAdapterContext, PVOID CancelId) { } static MINIPORT_RETURN_NET_BUFFER_LISTS TunReturnNetBufferLists; _Use_decl_annotations_ static VOID TunReturnNetBufferLists(NDIS_HANDLE MiniportAdapterContext, PNET_BUFFER_LIST NetBufferLists, ULONG ReturnFlags) { TUN_CTX *Ctx = (TUN_CTX *)MiniportAdapterContext; TUN_RING *Ring = Ctx->Device.Receive.Ring; LONG64 ReceivedPacketsCount = 0, ReceivedPacketsSize = 0, ErrorPacketsCount = 0; for (NET_BUFFER_LIST *Nbl = NetBufferLists, *NextNbl; Nbl; Nbl = NextNbl) { NextNbl = NET_BUFFER_LIST_NEXT_NBL(Nbl); if (NT_SUCCESS(NET_BUFFER_LIST_STATUS(Nbl))) { ReceivedPacketsCount++; ReceivedPacketsSize += NET_BUFFER_LIST_FIRST_NB(Nbl)->DataLength; } else ErrorPacketsCount++; TunNblMarkCompleted(Nbl); for (;;) { KLOCK_QUEUE_HANDLE LockHandle; KeAcquireInStackQueuedSpinLock(&Ctx->Device.Receive.Lock, &LockHandle); NET_BUFFER_LIST *CompletedNbl = Ctx->Device.Receive.ActiveNbls.Head; if (!CompletedNbl || !TunNblIsCompleted(CompletedNbl)) { KeReleaseInStackQueuedSpinLock(&LockHandle); break; } Ctx->Device.Receive.ActiveNbls.Head = NET_BUFFER_LIST_NEXT_NBL_EX(CompletedNbl); if (!Ctx->Device.Receive.ActiveNbls.Head) KeSetEvent(&Ctx->Device.Receive.ActiveNbls.Empty, IO_NO_INCREMENT, FALSE); KeReleaseInStackQueuedSpinLock(&LockHandle); WriteULongRelease(&Ring->Head, TunNblGetOffset(CompletedNbl)); const MDL *TargetMdl = Ctx->Device.Receive.Mdl; for (MDL *Mdl = NET_BUFFER_FIRST_MDL(NET_BUFFER_LIST_FIRST_NB(CompletedNbl)); Mdl; Mdl = Mdl->Next) { if (MmGetMdlVirtualAddress(Mdl) < MmGetMdlVirtualAddress(TargetMdl) || (UCHAR *)MmGetMdlVirtualAddress(Mdl) + MmGetMdlByteCount(Mdl) > (UCHAR *)MmGetMdlVirtualAddress(TargetMdl) + MmGetMdlByteCount(TargetMdl)) continue; IoFreeMdl(Mdl); break; } NdisFreeNetBufferList(CompletedNbl); } } InterlockedAddNoFence64((LONG64 *)&Ctx->Statistics.ifHCInOctets, ReceivedPacketsSize); InterlockedAddNoFence64((LONG64 *)&Ctx->Statistics.ifHCInUcastOctets, ReceivedPacketsSize); InterlockedAddNoFence64((LONG64 *)&Ctx->Statistics.ifHCInUcastPkts, ReceivedPacketsCount); InterlockedAddNoFence64((LONG64 *)&Ctx->Statistics.ifInErrors, ErrorPacketsCount); } _IRQL_requires_max_(PASSIVE_LEVEL) _Function_class_(KSTART_ROUTINE) static VOID TunProcessReceiveData(_Inout_ TUN_CTX *Ctx) { KeSetPriorityThread(KeGetCurrentThread(), 1); TUN_RING *Ring = Ctx->Device.Receive.Ring; ULONG RingCapacity = Ctx->Device.Receive.Capacity; LARGE_INTEGER Frequency; KeQueryPerformanceCounter(&Frequency); ULONG64 SpinMax = Frequency.QuadPart / 1000 / 10; /* 1/10 ms */ VOID *Events[] = { &Ctx->Device.Disconnected, Ctx->Device.Receive.TailMoved }; ASSERT(RTL_NUMBER_OF(Events) <= THREAD_WAIT_OBJECTS); ULONG RingHead = ReadULongAcquire(&Ring->Head); if (RingHead >= RingCapacity) goto cleanup; while (!KeReadStateEvent(&Ctx->Device.Disconnected)) { /* Get next packet from the ring. */ ULONG RingTail = ReadULongAcquire(&Ring->Tail); if (RingHead == RingTail) { LARGE_INTEGER SpinStart = KeQueryPerformanceCounter(NULL); for (;;) { RingTail = ReadULongAcquire(&Ring->Tail); if (RingTail != RingHead) break; if (KeReadStateEvent(&Ctx->Device.Disconnected)) break; LARGE_INTEGER SpinNow = KeQueryPerformanceCounter(NULL); if ((ULONG64)SpinNow.QuadPart - (ULONG64)SpinStart.QuadPart >= SpinMax) break; ZwYieldExecution(); } if (RingHead == RingTail) { WriteRelease(&Ring->Alertable, TRUE); RingTail = ReadULongAcquire(&Ring->Tail); if (RingHead == RingTail) { KeWaitForMultipleObjects( RTL_NUMBER_OF(Events), Events, WaitAny, Executive, KernelMode, FALSE, NULL, NULL); WriteRelease(&Ring->Alertable, FALSE); continue; } WriteRelease(&Ring->Alertable, FALSE); KeClearEvent(Ctx->Device.Receive.TailMoved); } } if (RingTail >= RingCapacity) break; ULONG RingContent = TUN_RING_WRAP(RingTail - RingHead, RingCapacity); if (RingContent < sizeof(TUN_PACKET)) break; TUN_PACKET *Packet = (TUN_PACKET *)(Ring->Data + RingHead); ULONG PacketSize = *(volatile ULONG *)&Packet->Size; if (PacketSize > TUN_MAX_IP_PACKET_SIZE) break; ULONG AlignedPacketSize = TUN_ALIGN(sizeof(TUN_PACKET) + PacketSize); if (AlignedPacketSize > RingContent) break; RingHead = TUN_RING_WRAP(RingHead + AlignedPacketSize, RingCapacity); ULONG NblFlags; USHORT NblProto; if (PacketSize >= 20 && Packet->Data[0] >> 4 == 4) { NblFlags = NDIS_NBL_FLAGS_IS_IPV4; NblProto = HTONS(NDIS_ETH_TYPE_IPV4); } else if (PacketSize >= 40 && Packet->Data[0] >> 4 == 6) { NblFlags = NDIS_NBL_FLAGS_IS_IPV6; NblProto = HTONS(NDIS_ETH_TYPE_IPV6); } else goto skipNbl; VOID *PacketAddr = (UCHAR *)MmGetMdlVirtualAddress(Ctx->Device.Receive.Mdl) + (ULONG)(Packet->Data - (UCHAR *)Ring); MDL *Mdl = IoAllocateMdl(PacketAddr, PacketSize, FALSE, FALSE, NULL); if (!Mdl) goto skipNbl; IoBuildPartialMdl(Ctx->Device.Receive.Mdl, Mdl, PacketAddr, PacketSize); NET_BUFFER_LIST *Nbl = NdisAllocateNetBufferAndNetBufferList(Ctx->NblPool, 0, 0, Mdl, 0, PacketSize); if (!Nbl) goto cleanupMdl; Nbl->SourceHandle = Ctx->MiniportAdapterHandle; NdisSetNblFlag(Nbl, NblFlags); NET_BUFFER_LIST_INFO(Nbl, NetBufferListFrameType) = (PVOID)NblProto; NET_BUFFER_LIST_STATUS(Nbl) = NDIS_STATUS_SUCCESS; TunNblSetOffsetAndMarkActive(Nbl, RingHead); KIRQL Irql = ExAcquireSpinLockShared(&Ctx->TransitionLock); if (!ReadAcquire(&Ctx->Running)) goto cleanupNbl; KLOCK_QUEUE_HANDLE LockHandle; KeAcquireInStackQueuedSpinLock(&Ctx->Device.Receive.Lock, &LockHandle); if (Ctx->Device.Receive.ActiveNbls.Head) NET_BUFFER_LIST_NEXT_NBL_EX(Ctx->Device.Receive.ActiveNbls.Tail) = Nbl; else { KeClearEvent(&Ctx->Device.Receive.ActiveNbls.Empty); Ctx->Device.Receive.ActiveNbls.Head = Nbl; } Ctx->Device.Receive.ActiveNbls.Tail = Nbl; KeReleaseInStackQueuedSpinLock(&LockHandle); NdisMIndicateReceiveNetBufferLists( Ctx->MiniportAdapterHandle, Nbl, NDIS_DEFAULT_PORT_NUMBER, 1, NDIS_RECEIVE_FLAGS_DISPATCH_LEVEL | NDIS_RECEIVE_FLAGS_SINGLE_ETHER_TYPE); ExReleaseSpinLockShared(&Ctx->TransitionLock, Irql); continue; cleanupNbl: ExReleaseSpinLockShared(&Ctx->TransitionLock, Irql); NdisFreeNetBufferList(Nbl); cleanupMdl: IoFreeMdl(Mdl); skipNbl: InterlockedIncrementNoFence64((LONG64 *)&Ctx->Statistics.ifInDiscards); KeWaitForSingleObject(&Ctx->Device.Receive.ActiveNbls.Empty, Executive, KernelMode, FALSE, NULL); WriteULongRelease(&Ring->Head, RingHead); } /* Wait for all NBLs to return: 1. To prevent race between proceeding and invalidating ring head. 2. To have * TunDispatchUnregisterBuffers() implicitly wait before releasing ring MDL used by NBL(s). */ KeWaitForSingleObject(&Ctx->Device.Receive.ActiveNbls.Empty, Executive, KernelMode, FALSE, NULL); cleanup: WriteULongRelease(&Ring->Head, MAXULONG); } #define IS_POW2(x) ((x) && !((x) & ((x)-1))) _IRQL_requires_max_(PASSIVE_LEVEL) _Must_inspect_result_ static NTSTATUS TunRegisterBuffers(_Inout_ TUN_CTX *Ctx, _Inout_ IRP *Irp) { NTSTATUS Status = STATUS_ALREADY_INITIALIZED; IO_STACK_LOCATION *Stack = IoGetCurrentIrpStackLocation(Irp); if (!ExAcquireResourceExclusiveLite(&Ctx->Device.RegistrationLock, FALSE)) return Status; if (Ctx->Device.OwningFileObject) goto cleanupMutex; Ctx->Device.OwningFileObject = Stack->FileObject; TUN_REGISTER_RINGS Rrb; if (Stack->Parameters.DeviceIoControl.InputBufferLength == sizeof(Rrb)) NdisMoveMemory(&Rrb, Irp->AssociatedIrp.SystemBuffer, sizeof(Rrb)); #ifdef _WIN64 else if ( IoIs32bitProcess(Irp) && Stack->Parameters.DeviceIoControl.InputBufferLength == sizeof(TUN_REGISTER_RINGS_32)) { TUN_REGISTER_RINGS_32 *Rrb32 = Irp->AssociatedIrp.SystemBuffer; Rrb.Send.RingSize = Rrb32->Send.RingSize; Rrb.Send.Ring = (TUN_RING *)Rrb32->Send.Ring; Rrb.Send.TailMoved = (HANDLE)Rrb32->Send.TailMoved; Rrb.Receive.RingSize = Rrb32->Receive.RingSize; Rrb.Receive.Ring = (TUN_RING *)Rrb32->Receive.Ring; Rrb.Receive.TailMoved = (HANDLE)Rrb32->Receive.TailMoved; } #endif else { Status = STATUS_INVALID_PARAMETER; goto cleanupResetOwner; } Ctx->Device.Send.Capacity = TUN_RING_CAPACITY(Rrb.Send.RingSize); if (Status = STATUS_INVALID_PARAMETER, (Ctx->Device.Send.Capacity < TUN_MIN_RING_CAPACITY || Ctx->Device.Send.Capacity > TUN_MAX_RING_CAPACITY || !IS_POW2(Ctx->Device.Send.Capacity) || !Rrb.Send.TailMoved || !Rrb.Send.Ring)) goto cleanupResetOwner; if (!NT_SUCCESS( Status = ObReferenceObjectByHandle( Rrb.Send.TailMoved, /* We will not wait on send ring tail moved event. */ EVENT_MODIFY_STATE, *ExEventObjectType, Irp->RequestorMode, &Ctx->Device.Send.TailMoved, NULL))) goto cleanupResetOwner; Ctx->Device.Send.Mdl = IoAllocateMdl(Rrb.Send.Ring, Rrb.Send.RingSize, FALSE, FALSE, NULL); if (Status = STATUS_INSUFFICIENT_RESOURCES, !Ctx->Device.Send.Mdl) goto cleanupSendTailMoved; try { Status = STATUS_INVALID_USER_BUFFER; MmProbeAndLockPages(Ctx->Device.Send.Mdl, Irp->RequestorMode, IoWriteAccess); } except(EXCEPTION_EXECUTE_HANDLER) { goto cleanupSendMdl; } Ctx->Device.Send.Ring = MmGetSystemAddressForMdlSafe(Ctx->Device.Send.Mdl, NormalPagePriority | MdlMappingNoExecute); if (Status = STATUS_INSUFFICIENT_RESOURCES, !Ctx->Device.Send.Ring) goto cleanupSendUnlockPages; Ctx->Device.Send.RingTail = ReadULongAcquire(&Ctx->Device.Send.Ring->Tail); if (Status = STATUS_INVALID_PARAMETER, Ctx->Device.Send.RingTail >= Ctx->Device.Send.Capacity) goto cleanupSendUnlockPages; Ctx->Device.Receive.Capacity = TUN_RING_CAPACITY(Rrb.Receive.RingSize); if (Status = STATUS_INVALID_PARAMETER, (Ctx->Device.Receive.Capacity < TUN_MIN_RING_CAPACITY || Ctx->Device.Receive.Capacity > TUN_MAX_RING_CAPACITY || !IS_POW2(Ctx->Device.Receive.Capacity) || !Rrb.Receive.TailMoved || !Rrb.Receive.Ring)) goto cleanupSendUnlockPages; if (!NT_SUCCESS( Status = ObReferenceObjectByHandle( Rrb.Receive.TailMoved, /* We need to clear receive ring TailMoved event on transition to non-alertable state. */ SYNCHRONIZE | EVENT_MODIFY_STATE, *ExEventObjectType, Irp->RequestorMode, &Ctx->Device.Receive.TailMoved, NULL))) goto cleanupSendUnlockPages; Ctx->Device.Receive.Mdl = IoAllocateMdl(Rrb.Receive.Ring, Rrb.Receive.RingSize, FALSE, FALSE, NULL); if (Status = STATUS_INSUFFICIENT_RESOURCES, !Ctx->Device.Receive.Mdl) goto cleanupReceiveTailMoved; try { Status = STATUS_INVALID_USER_BUFFER; MmProbeAndLockPages(Ctx->Device.Receive.Mdl, Irp->RequestorMode, IoWriteAccess); } except(EXCEPTION_EXECUTE_HANDLER) { goto cleanupReceiveMdl; } Ctx->Device.Receive.Ring = MmGetSystemAddressForMdlSafe(Ctx->Device.Receive.Mdl, NormalPagePriority | MdlMappingNoExecute); if (Status = STATUS_INSUFFICIENT_RESOURCES, !Ctx->Device.Receive.Ring) goto cleanupReceiveUnlockPages; KeClearEvent(&Ctx->Device.Disconnected); OBJECT_ATTRIBUTES ObjectAttributes; InitializeObjectAttributes(&ObjectAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); if (Status = NDIS_STATUS_FAILURE, !NT_SUCCESS(PsCreateSystemThread( &Ctx->Device.Receive.Thread, THREAD_ALL_ACCESS, &ObjectAttributes, NULL, NULL, TunProcessReceiveData, Ctx))) goto cleanupFlagsConnected; Ctx->Device.OwningProcessId = PsGetCurrentProcessId(); InitializeListHead(&Ctx->Device.Entry); ExAcquireResourceExclusiveLite(&TunDispatchDeviceListLock, TRUE); InsertTailList(&TunDispatchDeviceList, &Ctx->Device.Entry); ExReleaseResourceLite(&TunDispatchDeviceListLock); ExReleaseResourceLite(&Ctx->Device.RegistrationLock); TunIndicateStatus(Ctx->MiniportAdapterHandle, MediaConnectStateConnected); return STATUS_SUCCESS; cleanupFlagsConnected: KeSetEvent(&Ctx->Device.Disconnected, IO_NO_INCREMENT, FALSE); ExReleaseSpinLockExclusive( &Ctx->TransitionLock, ExAcquireSpinLockExclusive(&Ctx->TransitionLock)); /* Ensure above change is visible to all readers. */ cleanupReceiveUnlockPages: MmUnlockPages(Ctx->Device.Receive.Mdl); cleanupReceiveMdl: IoFreeMdl(Ctx->Device.Receive.Mdl); cleanupReceiveTailMoved: ObDereferenceObject(Ctx->Device.Receive.TailMoved); cleanupSendUnlockPages: MmUnlockPages(Ctx->Device.Send.Mdl); cleanupSendMdl: IoFreeMdl(Ctx->Device.Send.Mdl); cleanupSendTailMoved: ObDereferenceObject(Ctx->Device.Send.TailMoved); cleanupResetOwner: Ctx->Device.OwningFileObject = NULL; cleanupMutex: ExReleaseResourceLite(&Ctx->Device.RegistrationLock); return Status; } #define TUN_FORCE_UNREGISTRATION ((FILE_OBJECT *)-1) _IRQL_requires_max_(PASSIVE_LEVEL) static VOID TunUnregisterBuffers(_Inout_ TUN_CTX *Ctx, _In_ FILE_OBJECT *Owner) { if (!Owner) return; ExAcquireResourceExclusiveLite(&Ctx->Device.RegistrationLock, TRUE); if (!Ctx->Device.OwningFileObject || (Owner != TUN_FORCE_UNREGISTRATION && Ctx->Device.OwningFileObject != Owner)) { ExReleaseResourceLite(&Ctx->Device.RegistrationLock); return; } Ctx->Device.OwningFileObject = NULL; ExAcquireResourceExclusiveLite(&TunDispatchDeviceListLock, TRUE); RemoveEntryList(&Ctx->Device.Entry); ExReleaseResourceLite(&TunDispatchDeviceListLock); TunIndicateStatus(Ctx->MiniportAdapterHandle, MediaConnectStateDisconnected); KeSetEvent(&Ctx->Device.Disconnected, IO_NO_INCREMENT, FALSE); ExReleaseSpinLockExclusive( &Ctx->TransitionLock, ExAcquireSpinLockExclusive(&Ctx->TransitionLock)); /* Ensure above change is visible to all readers. */ PKTHREAD ThreadObject; if (NT_SUCCESS( ObReferenceObjectByHandle(Ctx->Device.Receive.Thread, SYNCHRONIZE, NULL, KernelMode, &ThreadObject, NULL))) { KeWaitForSingleObject(ThreadObject, Executive, KernelMode, FALSE, NULL); ObDereferenceObject(ThreadObject); } ZwClose(Ctx->Device.Receive.Thread); WriteULongRelease(&Ctx->Device.Send.Ring->Tail, MAXULONG); KeSetEvent(Ctx->Device.Send.TailMoved, IO_NO_INCREMENT, FALSE); MmUnlockPages(Ctx->Device.Receive.Mdl); IoFreeMdl(Ctx->Device.Receive.Mdl); ObDereferenceObject(Ctx->Device.Receive.TailMoved); MmUnlockPages(Ctx->Device.Send.Mdl); IoFreeMdl(Ctx->Device.Send.Mdl); ObDereferenceObject(Ctx->Device.Send.TailMoved); ExReleaseResourceLite(&Ctx->Device.RegistrationLock); } _IRQL_requires_max_(PASSIVE_LEVEL) static VOID TunProcessNotification(HANDLE ParentId, HANDLE ProcessId, BOOLEAN Create) { if (Create) return; ExAcquireSharedStarveExclusive(&TunDispatchDeviceListLock, TRUE); TUN_CTX *Ctx = NULL; for (LIST_ENTRY *Entry = TunDispatchDeviceList.Flink; Entry != &TunDispatchDeviceList; Entry = Entry->Flink) { TUN_CTX *Candidate = CONTAINING_RECORD(Entry, TUN_CTX, Device.Entry); if (Candidate->Device.OwningProcessId == ProcessId) { Ctx = Candidate; break; } } ExReleaseResourceLite(&TunDispatchDeviceListLock); if (!Ctx) return; TunUnregisterBuffers(Ctx, TUN_FORCE_UNREGISTRATION); } _Dispatch_type_(IRP_MJ_DEVICE_CONTROL) static DRIVER_DISPATCH_PAGED TunDispatchDeviceControl; _Use_decl_annotations_ static NTSTATUS TunDispatchDeviceControl(DEVICE_OBJECT *DeviceObject, IRP *Irp) { IO_STACK_LOCATION *Stack = IoGetCurrentIrpStackLocation(Irp); if (Stack->Parameters.DeviceIoControl.IoControlCode != TUN_IOCTL_REGISTER_RINGS) return NdisDispatchDeviceControl(DeviceObject, Irp); SECURITY_SUBJECT_CONTEXT SubjectContext; SeCaptureSubjectContext(&SubjectContext); NTSTATUS Status; ACCESS_MASK GrantedAccess; BOOLEAN HasAccess = SeAccessCheck( TunDispatchSecurityDescriptor, &SubjectContext, FALSE, FILE_WRITE_DATA, 0, NULL, IoGetFileObjectGenericMapping(), Irp->RequestorMode, &GrantedAccess, &Status); SeReleaseSubjectContext(&SubjectContext); if (!HasAccess) goto cleanup; switch (Stack->Parameters.DeviceIoControl.IoControlCode) { case TUN_IOCTL_REGISTER_RINGS: { KeEnterCriticalRegion(); ExAcquireResourceSharedLite(&TunDispatchCtxGuard, TRUE); #pragma warning(suppress : 28175) TUN_CTX *Ctx = DeviceObject->Reserved; Status = NDIS_STATUS_ADAPTER_NOT_READY; if (Ctx) Status = TunRegisterBuffers(Ctx, Irp); ExReleaseResourceLite(&TunDispatchCtxGuard); KeLeaveCriticalRegion(); break; } } cleanup: Irp->IoStatus.Status = Status; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return Status; } _Dispatch_type_(IRP_MJ_CLOSE) static DRIVER_DISPATCH_PAGED TunDispatchClose; _Use_decl_annotations_ static NTSTATUS TunDispatchClose(DEVICE_OBJECT *DeviceObject, IRP *Irp) { KeEnterCriticalRegion(); ExAcquireResourceSharedLite(&TunDispatchCtxGuard, TRUE); #pragma warning(suppress : 28175) TUN_CTX *Ctx = DeviceObject->Reserved; if (Ctx) TunUnregisterBuffers(Ctx, IoGetCurrentIrpStackLocation(Irp)->FileObject); ExReleaseResourceLite(&TunDispatchCtxGuard); KeLeaveCriticalRegion(); return NdisDispatchClose(DeviceObject, Irp); } _Dispatch_type_(IRP_MJ_PNP) static DRIVER_DISPATCH_PAGED TunDispatchPnp; _Use_decl_annotations_ static NTSTATUS TunDispatchPnp(DEVICE_OBJECT *DeviceObject, IRP *Irp) { IO_STACK_LOCATION *Stack = IoGetCurrentIrpStackLocation(Irp); if (Stack->MinorFunction != IRP_MN_QUERY_REMOVE_DEVICE && Stack->MinorFunction != IRP_MN_SURPRISE_REMOVAL) goto ndisDispatch; #pragma warning(suppress : 28175) TUN_CTX *Ctx = DeviceObject->Reserved; if (!Ctx) goto ndisDispatch; ExAcquireResourceExclusiveLite(&Ctx->Device.RegistrationLock, TRUE); if (!Ctx->Device.OwningFileObject || Ctx->Device.OwningFileObject == Stack->FileObject) goto cleanupLock; NTSTATUS Status; PEPROCESS Process; KAPC_STATE ApcState; PVOID Object = NULL; OBJECT_HANDLE_INFORMATION HandleInfo; SYSTEM_HANDLE_INFORMATION_EX *HandleTable = NULL; ULONG VerifierFlags = 0; for (ULONG Size = 0, RequestedSize; (Status = ZwQuerySystemInformation(SystemExtendedHandleInformation, HandleTable, Size, &RequestedSize)) == STATUS_INFO_LENGTH_MISMATCH; Size = RequestedSize) { if (HandleTable) ExFreePoolWithTag(HandleTable, TUN_MEMORY_TAG); HandleTable = ExAllocatePoolUninitialized(PagedPool, RequestedSize, TUN_MEMORY_TAG); if (!HandleTable) break; } if (!NT_SUCCESS(Status) || !HandleTable) goto cleanupHandleTable; MmIsVerifierEnabled(&VerifierFlags); for (ULONG_PTR Index = 0; Index < HandleTable->NumberOfHandles; ++Index) { FILE_OBJECT *FileObject = HandleTable->Handles[Index].Object; if (FileObject != Ctx->Device.OwningFileObject) continue; Status = PsLookupProcessByProcessId(HandleTable->Handles[Index].UniqueProcessId, &Process); if (!NT_SUCCESS(Status)) continue; KeStackAttachProcess(Process, &ApcState); if (!VerifierFlags) #pragma warning(suppress : 28126) Status = ObReferenceObjectByHandle( HandleTable->Handles[Index].HandleValue, 0, NULL, UserMode, &Object, &HandleInfo); if (NT_SUCCESS(Status)) { if (VerifierFlags || Object == FileObject) ObCloseHandle(HandleTable->Handles[Index].HandleValue, UserMode); if (!VerifierFlags) ObfDereferenceObject(Object); } KeUnstackDetachProcess(&ApcState); ObfDereferenceObject(Process); } cleanupHandleTable: if (HandleTable) ExFreePoolWithTag(HandleTable, TUN_MEMORY_TAG); cleanupLock: ExReleaseResourceLite(&Ctx->Device.RegistrationLock); ndisDispatch: return NdisDispatchPnp(DeviceObject, Irp); } 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; WriteRelease(&Ctx->Running, TRUE); return NDIS_STATUS_SUCCESS; } 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; WriteRelease(&Ctx->Running, FALSE); ExReleaseSpinLockExclusive( &Ctx->TransitionLock, ExAcquireSpinLockExclusive(&Ctx->TransitionLock)); /* Ensure above change is visible to all readers. */ KeWaitForSingleObject(&Ctx->Device.Receive.ActiveNbls.Empty, Executive, KernelMode, FALSE, NULL); return NDIS_STATUS_SUCCESS; } static MINIPORT_DEVICE_PNP_EVENT_NOTIFY TunDevicePnPEventNotify; _Use_decl_annotations_ static VOID TunDevicePnPEventNotify(NDIS_HANDLE MiniportAdapterContext, PNET_DEVICE_PNP_EVENT NetDevicePnPEvent) { } 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; /* Leaking memory 'Ctx'. Note: 'Ctx' is freed in TunHaltEx or on failure. */ #pragma warning(suppress : 6014) #pragma warning(suppress : 28160) TUN_CTX *Ctx = ExAllocatePoolZero(NonPagedPool, sizeof(*Ctx), TUN_MEMORY_TAG); if (!Ctx) return NDIS_STATUS_FAILURE; Ctx->MiniportAdapterHandle = MiniportAdapterHandle; NdisMGetDeviceProperty(MiniportAdapterHandle, NULL, &Ctx->FunctionalDeviceObject, NULL, NULL, NULL); if (Status = NDIS_STATUS_FAILURE, !Ctx->FunctionalDeviceObject) goto cleanupFreeCtx; #pragma warning(suppress : 28175) ASSERT(!Ctx->FunctionalDeviceObject->Reserved); /* Reverse engineering indicates that we'd be better off calling * NdisWdfGetAdapterContextFromAdapterHandle(functional_device), * which points to our TUN_CTX object directly, but this isn't * available before Windows 10, so for now we just stick it into * this reserved field. Revisit this when we drop support for old * Windows versions. */ #pragma warning(suppress : 28175) Ctx->FunctionalDeviceObject->Reserved = Ctx; 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; KeInitializeEvent(&Ctx->Device.Disconnected, NotificationEvent, TRUE); KeInitializeSpinLock(&Ctx->Device.Send.Lock); KeInitializeSpinLock(&Ctx->Device.Receive.Lock); KeInitializeEvent(&Ctx->Device.Receive.ActiveNbls.Empty, NotificationEvent, TRUE); ExInitializeResourceLite(&Ctx->Device.RegistrationLock); NET_BUFFER_LIST_POOL_PARAMETERS NblPoolParameters = { .Header = { .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 }; /* Leaking memory 'Ctx->NblPool'. Note: 'Ctx->NblPool' is freed in TunHaltEx or on failure. */ #pragma warning(suppress : 6014) Ctx->NblPool = NdisAllocateNetBufferListPool(MiniportAdapterHandle, &NblPoolParameters); if (Status = NDIS_STATUS_FAILURE, !Ctx->NblPool) goto cleanupFreeCtx; NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES AdapterRegistrationAttributes = { .Header = { .Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES, .Revision = NdisVersion < NDIS_RUNTIME_VERSION_630 ? NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1 : NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_2, .Size = NdisVersion < NDIS_RUNTIME_VERSION_630 ? NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1 : NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_2 }, .AttributeFlags = NDIS_MINIPORT_ATTRIBUTES_NO_HALT_ON_SUSPEND | NDIS_MINIPORT_ATTRIBUTES_SURPRISE_REMOVE_OK, .InterfaceType = NdisInterfaceInternal, .MiniportAdapterContext = Ctx }; if (Status = NDIS_STATUS_FAILURE, !NT_SUCCESS(NdisMSetMiniportAttributes( MiniportAdapterHandle, (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&AdapterRegistrationAttributes))) goto cleanupFreeNblPool; NDIS_PM_CAPABILITIES PmCapabilities = { .Header = { .Type = NDIS_OBJECT_TYPE_DEFAULT, .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 }, .MinMagicPacketWakeUp = NdisDeviceStateUnspecified, .MinPatternWakeUp = NdisDeviceStateUnspecified, .MinLinkChangeWakeUp = NdisDeviceStateUnspecified }; static NDIS_OID SupportedOids[] = { 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 AdapterGeneralAttributes = { .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_MAX_IP_PACKET_SIZE, .MaxXmitLinkSpeed = TUN_LINK_SPEED, .MaxRcvLinkSpeed = TUN_LINK_SPEED, .RcvLinkSpeed = TUN_LINK_SPEED, .XmitLinkSpeed = TUN_LINK_SPEED, .MediaConnectState = MediaConnectStateDisconnected, .MediaDuplexState = MediaDuplexStateFull, .LookaheadSize = TUN_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 = SupportedOids, .SupportedOidListLength = sizeof(SupportedOids), .PowerManagementCapabilitiesEx = &PmCapabilities }; if (Status = NDIS_STATUS_FAILURE, !NT_SUCCESS(NdisMSetMiniportAttributes( MiniportAdapterHandle, (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&AdapterGeneralAttributes))) goto cleanupFreeNblPool; return NDIS_STATUS_SUCCESS; cleanupFreeNblPool: NdisFreeNetBufferListPool(Ctx->NblPool); cleanupFreeCtx: ExFreePoolWithTag(Ctx, TUN_MEMORY_TAG); return Status; } static MINIPORT_HALT TunHaltEx; _Use_decl_annotations_ static VOID TunHaltEx(NDIS_HANDLE MiniportAdapterContext, NDIS_HALT_ACTION HaltAction) { TUN_CTX *Ctx = (TUN_CTX *)MiniportAdapterContext; TunUnregisterBuffers(Ctx, TUN_FORCE_UNREGISTRATION); ExReleaseSpinLockExclusive( &Ctx->TransitionLock, ExAcquireSpinLockExclusive(&Ctx->TransitionLock)); /* Ensure above change is visible to all readers. */ NdisFreeNetBufferListPool(Ctx->NblPool); #pragma warning(suppress : 6387) WritePointerNoFence(&Ctx->MiniportAdapterHandle, NULL); #pragma warning(suppress : 6387 28175) WritePointerNoFence(&Ctx->FunctionalDeviceObject->Reserved, NULL); KeEnterCriticalRegion(); ExAcquireResourceExclusiveLite(&TunDispatchCtxGuard, TRUE); /* Ensure above change is visible to all readers. */ ExReleaseResourceLite(&TunDispatchCtxGuard); KeLeaveCriticalRegion(); ExDeleteResourceLite(&Ctx->Device.RegistrationLock); ExFreePoolWithTag(Ctx, TUN_MEMORY_TAG); } static MINIPORT_SHUTDOWN TunShutdownEx; _Use_decl_annotations_ static VOID TunShutdownEx(NDIS_HANDLE MiniportAdapterContext, NDIS_SHUTDOWN_ACTION ShutdownAction) { } _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; return NDIS_STATUS_BUFFER_TOO_SHORT; } 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; return NDIS_STATUS_BUFFER_TOO_SHORT; } 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_ ULONG Size) { if (OidRequest->DATA.QUERY_INFORMATION.InformationBufferLength < Size) { OidRequest->DATA.QUERY_INFORMATION.BytesNeeded = Size; OidRequest->DATA.QUERY_INFORMATION.BytesWritten = 0; return NDIS_STATUS_BUFFER_TOO_SHORT; } OidRequest->DATA.QUERY_INFORMATION.BytesNeeded = OidRequest->DATA.QUERY_INFORMATION.BytesWritten = Size; NdisMoveMemory(OidRequest->DATA.QUERY_INFORMATION.InformationBuffer, Buf, Size); return NDIS_STATUS_SUCCESS; } _IRQL_requires_max_(APC_LEVEL) _Must_inspect_result_ static NDIS_STATUS TunOidQuery(_Inout_ TUN_CTX *Ctx, _Inout_ NDIS_OID_REQUEST *OidRequest) { ASSERT( OidRequest->RequestType == NdisRequestQueryInformation || OidRequest->RequestType == NdisRequestQueryStatistics); switch (OidRequest->DATA.QUERY_INFORMATION.Oid) { case OID_GEN_MAXIMUM_TOTAL_SIZE: case OID_GEN_TRANSMIT_BLOCK_SIZE: case OID_GEN_RECEIVE_BLOCK_SIZE: return TunOidQueryWrite(OidRequest, TUN_MAX_IP_PACKET_SIZE); case OID_GEN_TRANSMIT_BUFFER_SPACE: return TunOidQueryWrite(OidRequest, TUN_MAX_RING_CAPACITY); case OID_GEN_RECEIVE_BUFFER_SPACE: return TunOidQueryWrite(OidRequest, TUN_MAX_RING_CAPACITY); case OID_GEN_VENDOR_ID: return TunOidQueryWrite(OidRequest, HTONL(TUN_VENDOR_ID)); case OID_GEN_VENDOR_DESCRIPTION: return TunOidQueryWriteBuf(OidRequest, TUN_VENDOR_NAME, (ULONG)sizeof(TUN_VENDOR_NAME)); case OID_GEN_VENDOR_DRIVER_VERSION: return TunOidQueryWrite(OidRequest, (WINTUN_VERSION_MAJ << 16) | WINTUN_VERSION_MIN); case OID_GEN_XMIT_OK: return TunOidQueryWrite32or64( OidRequest, ReadNoFence64((LONG64 *)&Ctx->Statistics.ifHCOutUcastPkts) + ReadNoFence64((LONG64 *)&Ctx->Statistics.ifHCOutMulticastPkts) + ReadNoFence64((LONG64 *)&Ctx->Statistics.ifHCOutBroadcastPkts)); case OID_GEN_RCV_OK: return TunOidQueryWrite32or64( OidRequest, ReadNoFence64((LONG64 *)&Ctx->Statistics.ifHCInUcastPkts) + ReadNoFence64((LONG64 *)&Ctx->Statistics.ifHCInMulticastPkts) + ReadNoFence64((LONG64 *)&Ctx->Statistics.ifHCInBroadcastPkts)); case OID_GEN_STATISTICS: return TunOidQueryWriteBuf(OidRequest, &Ctx->Statistics, (ULONG)sizeof(Ctx->Statistics)); case OID_GEN_INTERRUPT_MODERATION: { static const NDIS_INTERRUPT_MODERATION_PARAMETERS InterruptParameters = { .Header = { .Type = NDIS_OBJECT_TYPE_DEFAULT, .Revision = NDIS_INTERRUPT_MODERATION_PARAMETERS_REVISION_1, .Size = NDIS_SIZEOF_INTERRUPT_MODERATION_PARAMETERS_REVISION_1 }, .InterruptModeration = NdisInterruptModerationNotSupported }; return TunOidQueryWriteBuf(OidRequest, &InterruptParameters, (ULONG)sizeof(InterruptParameters)); } case OID_PNP_QUERY_POWER: OidRequest->DATA.QUERY_INFORMATION.BytesNeeded = OidRequest->DATA.QUERY_INFORMATION.BytesWritten = 0; return NDIS_STATUS_SUCCESS; } OidRequest->DATA.QUERY_INFORMATION.BytesWritten = 0; return NDIS_STATUS_NOT_SUPPORTED; } _IRQL_requires_max_(PASSIVE_LEVEL) static NDIS_STATUS TunOidSet(_Inout_ TUN_CTX *Ctx, _Inout_ NDIS_OID_REQUEST *OidRequest) { ASSERT(OidRequest->RequestType == NdisRequestSetInformation); OidRequest->DATA.SET_INFORMATION.BytesNeeded = OidRequest->DATA.SET_INFORMATION.BytesRead = 0; switch (OidRequest->DATA.SET_INFORMATION.Oid) { case OID_GEN_CURRENT_PACKET_FILTER: case OID_GEN_CURRENT_LOOKAHEAD: if (OidRequest->DATA.SET_INFORMATION.InformationBufferLength != 4) { OidRequest->DATA.SET_INFORMATION.BytesNeeded = 4; return NDIS_STATUS_INVALID_LENGTH; } OidRequest->DATA.SET_INFORMATION.BytesRead = 4; return NDIS_STATUS_SUCCESS; case OID_GEN_LINK_PARAMETERS: OidRequest->DATA.SET_INFORMATION.BytesRead = OidRequest->DATA.SET_INFORMATION.InformationBufferLength; return NDIS_STATUS_SUCCESS; case OID_GEN_INTERRUPT_MODERATION: return NDIS_STATUS_INVALID_DATA; case OID_PNP_SET_POWER: if (OidRequest->DATA.SET_INFORMATION.InformationBufferLength != sizeof(NDIS_DEVICE_POWER_STATE)) { OidRequest->DATA.SET_INFORMATION.BytesNeeded = sizeof(NDIS_DEVICE_POWER_STATE); return NDIS_STATUS_INVALID_LENGTH; } OidRequest->DATA.SET_INFORMATION.BytesRead = sizeof(NDIS_DEVICE_POWER_STATE); return NDIS_STATUS_SUCCESS; } return NDIS_STATUS_NOT_SUPPORTED; } 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: return TunOidQuery(MiniportAdapterContext, OidRequest); case NdisRequestSetInformation: return TunOidSet(MiniportAdapterContext, OidRequest); default: return NDIS_STATUS_INVALID_OID; } } static MINIPORT_CANCEL_OID_REQUEST TunCancelOidRequest; _Use_decl_annotations_ static VOID TunCancelOidRequest(NDIS_HANDLE MiniportAdapterContext, PVOID RequestId) { } 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: return NDIS_STATUS_NOT_SUPPORTED; default: return NDIS_STATUS_INVALID_OID; } } static MINIPORT_CANCEL_DIRECT_OID_REQUEST TunCancelDirectOidRequest; _Use_decl_annotations_ static VOID TunCancelDirectOidRequest(NDIS_HANDLE MiniportAdapterContext, PVOID RequestId) { } static MINIPORT_SYNCHRONOUS_OID_REQUEST TunSynchronousOidRequest; _Use_decl_annotations_ static NDIS_STATUS TunSynchronousOidRequest(NDIS_HANDLE MiniportAdapterContext, PNDIS_OID_REQUEST OidRequest) { switch (OidRequest->RequestType) { case NdisRequestQueryInformation: case NdisRequestQueryStatistics: case NdisRequestSetInformation: return NDIS_STATUS_NOT_SUPPORTED; default: return NDIS_STATUS_INVALID_OID; } } static MINIPORT_UNLOAD TunUnload; _Use_decl_annotations_ static VOID TunUnload(PDRIVER_OBJECT DriverObject) { PsSetCreateProcessNotifyRoutine(TunProcessNotification, TRUE); NdisMDeregisterMiniportDriver(NdisMiniportDriverHandle); ExDeleteResourceLite(&TunDispatchCtxGuard); ExDeleteResourceLite(&TunDispatchDeviceListLock); } DRIVER_INITIALIZE DriverEntry; #ifdef ALLOC_PRAGMA # pragma alloc_text(INIT, DriverEntry) #endif _Use_decl_annotations_ NTSTATUS DriverEntry(DRIVER_OBJECT *DriverObject, UNICODE_STRING *RegistryPath) { NTSTATUS Status; ExInitializeDriverRuntime(DrvRtPoolNxOptIn); NdisVersion = NdisGetVersion(); if (NdisVersion < NDIS_MINIPORT_VERSION_MIN) return NDIS_STATUS_UNSUPPORTED_REVISION; if (NdisVersion > NDIS_MINIPORT_VERSION_MAX) NdisVersion = NDIS_MINIPORT_VERSION_MAX; ExInitializeResourceLite(&TunDispatchCtxGuard); ExInitializeResourceLite(&TunDispatchDeviceListLock); NDIS_MINIPORT_DRIVER_CHARACTERISTICS miniport = { .Header = { .Type = NDIS_OBJECT_TYPE_MINIPORT_DRIVER_CHARACTERISTICS, .Revision = NdisVersion < NDIS_RUNTIME_VERSION_680 ? NDIS_MINIPORT_DRIVER_CHARACTERISTICS_REVISION_2 : NDIS_MINIPORT_DRIVER_CHARACTERISTICS_REVISION_3, .Size = NdisVersion < NDIS_RUNTIME_VERSION_680 ? NDIS_SIZEOF_MINIPORT_DRIVER_CHARACTERISTICS_REVISION_2 : NDIS_SIZEOF_MINIPORT_DRIVER_CHARACTERISTICS_REVISION_3 }, .MajorNdisVersion = (UCHAR)((NdisVersion & 0x00ff0000) >> 16), .MinorNdisVersion = (UCHAR)(NdisVersion & 0x000000ff), .MajorDriverVersion = WINTUN_VERSION_MAJ, .MinorDriverVersion = WINTUN_VERSION_MIN, .InitializeHandlerEx = TunInitializeEx, .HaltHandlerEx = TunHaltEx, .UnloadHandler = TunUnload, .PauseHandler = TunPause, .RestartHandler = TunRestart, .OidRequestHandler = TunOidRequest, .SendNetBufferListsHandler = TunSendNetBufferLists, .ReturnNetBufferListsHandler = TunReturnNetBufferLists, .CancelSendHandler = TunCancelSend, .DevicePnPEventNotifyHandler = TunDevicePnPEventNotify, .ShutdownHandlerEx = TunShutdownEx, .CancelOidRequestHandler = TunCancelOidRequest, .DirectOidRequestHandler = TunDirectOidRequest, .CancelDirectOidRequestHandler = TunCancelDirectOidRequest, .SynchronousOidRequestHandler = TunSynchronousOidRequest }; Status = PsSetCreateProcessNotifyRoutine(TunProcessNotification, FALSE); if (!NT_SUCCESS(Status)) goto cleanupResources; Status = NdisMRegisterMiniportDriver(DriverObject, RegistryPath, NULL, &miniport, &NdisMiniportDriverHandle); if (!NT_SUCCESS(Status)) goto cleanupNotifier; NdisDispatchDeviceControl = DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]; NdisDispatchClose = DriverObject->MajorFunction[IRP_MJ_CLOSE]; NdisDispatchPnp = DriverObject->MajorFunction[IRP_MJ_PNP]; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = TunDispatchDeviceControl; DriverObject->MajorFunction[IRP_MJ_CLOSE] = TunDispatchClose; DriverObject->MajorFunction[IRP_MJ_PNP] = TunDispatchPnp; return STATUS_SUCCESS; cleanupNotifier: PsSetCreateProcessNotifyRoutine(TunProcessNotification, TRUE); cleanupResources: ExDeleteResourceLite(&TunDispatchCtxGuard); ExDeleteResourceLite(&TunDispatchDeviceListLock); return Status; }