/* SPDX-License-Identifier: GPL-2.0 * * Copyright (C) 2018-2019 WireGuard LLC. All Rights Reserved. */ #include #include #include #include #include #include "undocumented.h" #include "atomic.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; /* 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) /* Force close all open handles to allow for updating. */ #define TUN_IOCTL_FORCE_CLOSE_HANDLES CTL_CODE(51820U, 0x971U, METHOD_NEITHER, 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 { FILE_OBJECT *volatile Owner; 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; static ERESOURCE TunDispatchCtxGuard; static SECURITY_DESCRIPTOR *TunDispatchSecurityDescriptor; _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); } 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; for (NET_BUFFER_LIST *Nbl = NetBufferLists, *NextNbl; Nbl; Nbl = NextNbl) { NextNbl = NET_BUFFER_LIST_NEXT_NBL(Nbl); /* Measure NBL. */ ULONG PacketsCount = 0, RequiredRingSpace = 0; 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) RequiredRingSpace += TUN_ALIGN(sizeof(TUN_PACKET) + PacketSize); } KIRQL Irql = ExAcquireSpinLockShared(&Ctx->TransitionLock); NDIS_STATUS Status; if ((Status = NDIS_STATUS_PAUSED, !InterlockedGet(&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 packet(s) in the ring. */ ULONG RingHead = InterlockedGetU(&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(Nbl, Ctx->Device.Send.RingTail); *(Ctx->Device.Send.ActiveNbls.Head ? &NET_BUFFER_LIST_NEXT_NBL(Ctx->Device.Send.ActiveNbls.Tail) : &Ctx->Device.Send.ActiveNbls.Head) = Nbl; Ctx->Device.Send.ActiveNbls.Tail = Nbl; KeReleaseInStackQueuedSpinLock(&LockHandle); /* Copy packet(s). */ 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++; } 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(Nbl)); /* Adjust the ring tail. */ TunNblMarkCompleted(Nbl); 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(CompletedNbl); InterlockedSetU(&Ring->Tail, TunNblGetOffset(CompletedNbl)); KeSetEvent(Ctx->Device.Send.TailMoved, IO_NETWORK_INCREMENT, FALSE); NET_BUFFER_LIST_NEXT_NBL(CompletedNbl) = NULL; NdisMSendNetBufferListsComplete( Ctx->MiniportAdapterHandle, CompletedNbl, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL); } KeReleaseInStackQueuedSpinLock(&LockHandle); ExReleaseSpinLockShared(&Ctx->TransitionLock, Irql); continue; cleanupKeReleaseInStackQueuedSpinLock: KeReleaseInStackQueuedSpinLock(&LockHandle); skipNbl: NET_BUFFER_LIST_STATUS(Nbl) = Status; NET_BUFFER_LIST_NEXT_NBL(Nbl) = NULL; NdisMSendNetBufferListsComplete(Ctx->MiniportAdapterHandle, Nbl, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL); ExReleaseSpinLockShared(&Ctx->TransitionLock, Irql); DiscardedPacketsCount += PacketsCount; } InterlockedAdd64((LONG64 *)&Ctx->Statistics.ifHCOutOctets, SentPacketsSize); InterlockedAdd64((LONG64 *)&Ctx->Statistics.ifHCOutUcastOctets, SentPacketsSize); InterlockedAdd64((LONG64 *)&Ctx->Statistics.ifHCOutUcastPkts, SentPacketsCount); InterlockedAdd64((LONG64 *)&Ctx->Statistics.ifOutErrors, ErrorPacketsCount); InterlockedAdd64((LONG64 *)&Ctx->Statistics.ifOutDiscards, DiscardedPacketsCount); } static MINIPORT_CANCEL_SEND TunCancelSend; _Use_decl_annotations_ static VOID TunCancelSend(NDIS_HANDLE MiniportAdapterContext, PVOID CancelId) { } /* 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 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); InterlockedSetU(&Ring->Head, TunNblGetOffset(CompletedNbl)); NdisFreeNetBufferList(CompletedNbl); } } InterlockedAdd64((LONG64 *)&Ctx->Statistics.ifHCInOctets, ReceivedPacketsSize); InterlockedAdd64((LONG64 *)&Ctx->Statistics.ifHCInUcastOctets, ReceivedPacketsSize); InterlockedAdd64((LONG64 *)&Ctx->Statistics.ifHCInUcastPkts, ReceivedPacketsCount); InterlockedAdd64((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 = InterlockedGetU(&Ring->Head); if (RingHead >= RingCapacity) goto cleanup; while (!KeReadStateEvent(&Ctx->Device.Disconnected)) { /* Get next packet from the ring. */ ULONG RingTail = InterlockedGetU(&Ring->Tail); if (RingHead == RingTail) { LARGE_INTEGER SpinStart = KeQueryPerformanceCounter(NULL); for (;;) { RingTail = InterlockedGetU(&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) { InterlockedSet(&Ring->Alertable, TRUE); RingTail = InterlockedGetU(&Ring->Tail); if (RingHead == RingTail) { KeWaitForMultipleObjects( RTL_NUMBER_OF(Events), Events, WaitAny, Executive, KernelMode, FALSE, NULL, NULL); InterlockedSet(&Ring->Alertable, FALSE); continue; } InterlockedSet(&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 = Packet->Size; if (PacketSize > TUN_MAX_IP_PACKET_SIZE) break; ULONG AlignedPacketSize = TUN_ALIGN(sizeof(TUN_PACKET) + PacketSize); if (AlignedPacketSize > RingContent) break; 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 break; RingHead = TUN_RING_WRAP(RingHead + AlignedPacketSize, RingCapacity); NET_BUFFER_LIST *Nbl = NdisAllocateNetBufferAndNetBufferList( Ctx->NblPool, 0, 0, Ctx->Device.Receive.Mdl, (ULONG)(Packet->Data - (UCHAR *)Ring), PacketSize); if (!Nbl) goto skipNbl; 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 (!InterlockedGet(&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); skipNbl: InterlockedIncrement64((LONG64 *)&Ctx->Statistics.ifInDiscards); KeWaitForSingleObject(&Ctx->Device.Receive.ActiveNbls.Empty, Executive, KernelMode, FALSE, NULL); InterlockedSetU(&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: InterlockedSetU(&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; IO_STACK_LOCATION *Stack = IoGetCurrentIrpStackLocation(Irp); if (InterlockedCompareExchangePointer(&Ctx->Device.Owner, Stack->FileObject, NULL) != NULL) return STATUS_ALREADY_INITIALIZED; ASSERT(InterlockedGet(&Ctx->Running)); TUN_REGISTER_RINGS *Rrb = Irp->AssociatedIrp.SystemBuffer; if (Status = STATUS_INVALID_PARAMETER, Stack->Parameters.DeviceIoControl.InputBufferLength != sizeof(*Rrb)) 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, UserMode, &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, UserMode, 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 = InterlockedGetU(&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, UserMode, &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, UserMode, 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; 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: InterlockedSetPointer(&Ctx->Device.Owner, NULL); return Status; } _IRQL_requires_max_(PASSIVE_LEVEL) static VOID TunUnregisterBuffers(_Inout_ TUN_CTX *Ctx, _In_ FILE_OBJECT *Owner) { if (InterlockedCompareExchangePointer(&Ctx->Device.Owner, NULL, Owner) != Owner) return; 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); InterlockedSetU(&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); } _IRQL_requires_max_(PASSIVE_LEVEL) static void TunForceHandlesClosed(_Inout_ DEVICE_OBJECT *DeviceObject) { NTSTATUS Status; PEPROCESS Process; KAPC_STATE ApcState; PVOID Object = NULL; ULONG VerifierFlags = 0; OBJECT_HANDLE_INFORMATION HandleInfo; SYSTEM_HANDLE_INFORMATION_EX *HandleTable = NULL; MmIsVerifierEnabled(&VerifierFlags); 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 = ExAllocatePoolWithTag(PagedPool, RequestedSize, TUN_MEMORY_TAG); if (!HandleTable) return; } if (!NT_SUCCESS(Status) || !HandleTable) goto cleanup; HANDLE CurrentProcessId = PsGetCurrentProcessId(); for (ULONG_PTR Index = 0; Index < HandleTable->NumberOfHandles; ++Index) { FILE_OBJECT *FileObject = HandleTable->Handles[Index].Object; if (!FileObject || FileObject->Type != 5 || FileObject->DeviceObject != DeviceObject) continue; HANDLE ProcessId = HandleTable->Handles[Index].UniqueProcessId; if (ProcessId == CurrentProcessId) continue; Status = PsLookupProcessByProcessId(ProcessId, &Process); if (!NT_SUCCESS(Status)) continue; KeStackAttachProcess(Process, &ApcState); if (!VerifierFlags) 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); } cleanup: if (HandleTable) ExFreePoolWithTag(HandleTable, TUN_MEMORY_TAG); } static NTSTATUS TunInitializeDispatchSecurityDescriptor(VOID) { NTSTATUS Status; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; SID LocalSystem = { 0 }; if (!NT_SUCCESS(Status = RtlInitializeSid(&LocalSystem, &NtAuthority, 1))) return Status; LocalSystem.SubAuthority[0] = 18; struct { ACL Dacl; ACCESS_ALLOWED_ACE AceFiller; SID SidFiller; } DaclStorage = { 0 }; if (!NT_SUCCESS(Status = RtlCreateAcl(&DaclStorage.Dacl, sizeof(DaclStorage), ACL_REVISION))) return Status; ACCESS_MASK AccessMask = GENERIC_ALL; RtlMapGenericMask(&AccessMask, IoGetFileObjectGenericMapping()); if (!NT_SUCCESS(Status = RtlAddAccessAllowedAce(&DaclStorage.Dacl, ACL_REVISION, AccessMask, &LocalSystem))) return Status; SECURITY_DESCRIPTOR SecurityDescriptor = { 0 }; if (!NT_SUCCESS(Status = RtlCreateSecurityDescriptor(&SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION))) return Status; if (!NT_SUCCESS(Status = RtlSetDaclSecurityDescriptor(&SecurityDescriptor, TRUE, &DaclStorage.Dacl, FALSE))) return Status; SecurityDescriptor.Control |= SE_DACL_PROTECTED; ULONG RequiredBytes = 0; Status = RtlAbsoluteToSelfRelativeSD(&SecurityDescriptor, NULL, &RequiredBytes); if (Status != STATUS_BUFFER_TOO_SMALL) return NT_SUCCESS(Status) ? STATUS_INSUFFICIENT_RESOURCES : Status; TunDispatchSecurityDescriptor = ExAllocatePoolWithTag(NonPagedPoolNx, RequiredBytes, TUN_MEMORY_TAG); if (!TunDispatchSecurityDescriptor) return STATUS_INSUFFICIENT_RESOURCES; Status = RtlAbsoluteToSelfRelativeSD(&SecurityDescriptor, TunDispatchSecurityDescriptor, &RequiredBytes); if (!NT_SUCCESS(Status)) return Status; return STATUS_SUCCESS; } _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 && Stack->Parameters.DeviceIoControl.IoControlCode != TUN_IOCTL_FORCE_CLOSE_HANDLES) 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; } case TUN_IOCTL_FORCE_CLOSE_HANDLES: TunForceHandlesClosed(Stack->FileObject->DeviceObject); Status = STATUS_SUCCESS; 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); } 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; InterlockedSet(&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; InterlockedSet(&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) TUN_CTX *Ctx = ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(*Ctx), TUN_MEMORY_TAG); if (!Ctx) return NDIS_STATUS_FAILURE; NdisZeroMemory(Ctx, sizeof(*Ctx)); 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); 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, .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; /* A miniport driver can call NdisMIndicateStatusEx after setting its * registration attributes even if the driver is still in the context * of the MiniportInitializeEx function. */ TunIndicateStatus(Ctx->MiniportAdapterHandle, MediaConnectStateDisconnected); 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; ExReleaseSpinLockExclusive( &Ctx->TransitionLock, ExAcquireSpinLockExclusive(&Ctx->TransitionLock)); /* Ensure above change is visible to all readers. */ NdisFreeNetBufferListPool(Ctx->NblPool); InterlockedSetPointer(&Ctx->MiniportAdapterHandle, NULL); #pragma warning(suppress : 28175) InterlockedSetPointer(&Ctx->FunctionalDeviceObject->Reserved, NULL); KeEnterCriticalRegion(); ExAcquireResourceExclusiveLite(&TunDispatchCtxGuard, TRUE); /* Ensure above change is visible to all readers. */ ExReleaseResourceLite(&TunDispatchCtxGuard); KeLeaveCriticalRegion(); 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, InterlockedGet64((LONG64 *)&Ctx->Statistics.ifHCOutUcastPkts) + InterlockedGet64((LONG64 *)&Ctx->Statistics.ifHCOutMulticastPkts) + InterlockedGet64((LONG64 *)&Ctx->Statistics.ifHCOutBroadcastPkts)); case OID_GEN_RCV_OK: return TunOidQueryWrite32or64( OidRequest, InterlockedGet64((LONG64 *)&Ctx->Statistics.ifHCInUcastPkts) + InterlockedGet64((LONG64 *)&Ctx->Statistics.ifHCInMulticastPkts) + InterlockedGet64((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) { NdisMDeregisterMiniportDriver(NdisMiniportDriverHandle); ExDeleteResourceLite(&TunDispatchCtxGuard); ExFreePoolWithTag(TunDispatchSecurityDescriptor, TUN_MEMORY_TAG); } DRIVER_INITIALIZE DriverEntry; _Use_decl_annotations_ NTSTATUS DriverEntry(DRIVER_OBJECT *DriverObject, UNICODE_STRING *RegistryPath) { NTSTATUS Status; if (!NT_SUCCESS(Status = TunInitializeDispatchSecurityDescriptor())) return Status; 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); 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 = NdisMRegisterMiniportDriver(DriverObject, RegistryPath, NULL, &miniport, &NdisMiniportDriverHandle); if (!NT_SUCCESS(Status)) { ExDeleteResourceLite(&TunDispatchCtxGuard); ExFreePoolWithTag(TunDispatchSecurityDescriptor, TUN_MEMORY_TAG); return Status; } NdisDispatchDeviceControl = DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]; NdisDispatchClose = DriverObject->MajorFunction[IRP_MJ_CLOSE]; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = TunDispatchDeviceControl; DriverObject->MajorFunction[IRP_MJ_CLOSE] = TunDispatchClose; return STATUS_SUCCESS; }