diff options
Diffstat (limited to 'api')
-rw-r--r-- | api/adapter.h | 7 | ||||
-rw-r--r-- | api/api.vcxproj | 2 | ||||
-rw-r--r-- | api/api.vcxproj.filters | 6 | ||||
-rw-r--r-- | api/atomic.h | 26 | ||||
-rw-r--r-- | api/exports.def | 6 | ||||
-rw-r--r-- | api/pch.h | 1 | ||||
-rw-r--r-- | api/session.c | 213 | ||||
-rw-r--r-- | api/wintun.h | 123 |
8 files changed, 383 insertions, 1 deletions
diff --git a/api/adapter.h b/api/adapter.h index bacaf8b..7284002 100644 --- a/api/adapter.h +++ b/api/adapter.h @@ -97,6 +97,12 @@ void WINAPI WintunFreeAdapter(_In_ WINTUN_ADAPTER *Adapter); /** + * @copydoc WINTUN_GET_ADAPTER_DEVICE_OBJECT_FUNC + */ +WINTUN_STATUS WINAPI +WintunGetAdapterDeviceObject(_In_ const WINTUN_ADAPTER *Adapter, _Out_ HANDLE *Handle); + +/** * @copydoc WINTUN_CREATE_ADAPTER_FUNC */ WINTUN_STATUS WINAPI @@ -112,4 +118,3 @@ WintunCreateAdapter( */ WINTUN_STATUS WINAPI WintunDeleteAdapter(_In_ const WINTUN_ADAPTER *Adapter, _Inout_ BOOL *RebootRequired); - diff --git a/api/api.vcxproj b/api/api.vcxproj index 17756e8..ed0dea6 100644 --- a/api/api.vcxproj +++ b/api/api.vcxproj @@ -195,6 +195,7 @@ <ItemGroup> <ClInclude Include="api.h" /> <ClInclude Include="adapter.h" /> + <ClInclude Include="atomic.h" /> <ClInclude Include="driver.h" /> <ClInclude Include="logger.h" /> <ClInclude Include="namespace.h" /> @@ -216,6 +217,7 @@ </ClCompile> <ClCompile Include="registry.c" /> <ClCompile Include="resource.c" /> + <ClCompile Include="session.c" /> <ClCompile Include="rundll32.c" /> </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> diff --git a/api/api.vcxproj.filters b/api/api.vcxproj.filters index 682b11f..cda6b16 100644 --- a/api/api.vcxproj.filters +++ b/api/api.vcxproj.filters @@ -55,6 +55,9 @@ <ClInclude Include="wintun.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="atomic.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="api.c"> @@ -87,5 +90,8 @@ <ClCompile Include="adapter.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="session.c"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> </Project>
\ No newline at end of file diff --git a/api/atomic.h b/api/atomic.h new file mode 100644 index 0000000..011e53b --- /dev/null +++ b/api/atomic.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2020 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include <Windows.h> + +static __forceinline VOID +InterlockedSetU(_Inout_ _Interlocked_operand_ ULONG volatile *Target, _In_ ULONG Value) +{ + *Target = Value; +} + +static __forceinline LONG +InterlockedGet(_In_ _Interlocked_operand_ LONG volatile *Value) +{ + return *Value; +} + +static __forceinline ULONG +InterlockedGetU(_In_ _Interlocked_operand_ ULONG volatile *Value) +{ + return *Value; +} diff --git a/api/exports.def b/api/exports.def index 4305f47..85e4453 100644 --- a/api/exports.def +++ b/api/exports.def @@ -1,6 +1,7 @@ EXPORTS WintunCreateAdapter WintunDeleteAdapter + WintunEndSession WintunEnumAdapters WintunFreeAdapter WintunGetAdapter @@ -9,5 +10,10 @@ EXPORTS WintunGetAdapterLUID WintunGetAdapterName WintunGetVersion + WintunIsPacketAvailable + WintunReceivePackets + WintunSendPackets WintunSetAdapterName WintunSetLogger + WintunStartSession + WintunWaitForPacket @@ -6,6 +6,7 @@ #pragma once #include "adapter.h" +#include "atomic.h" #include "api.h" #include "driver.h" #include "logger.h" diff --git a/api/session.c b/api/session.c new file mode 100644 index 0000000..5822cb6 --- /dev/null +++ b/api/session.c @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2020 WireGuard LLC. All Rights Reserved. + */ + +#include "pch.h" + +#pragma warning(disable : 4200) /* nonstandard: zero-sized array in struct/union */ + +#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))) +#define TUN_MAX_PACKET_SIZE TUN_ALIGN(sizeof(TUN_PACKET) + WINTUN_MAX_IP_PACKET_SIZE) +#define TUN_RING_CAPACITY(Size) ((Size) - sizeof(TUN_RING) - (TUN_MAX_PACKET_SIZE - TUN_ALIGNMENT)) +#define TUN_RING_SIZE(Capacity) (sizeof(TUN_RING) + (Capacity) + (TUN_MAX_PACKET_SIZE - TUN_ALIGNMENT)) +#define TUN_RING_WRAP(Value, Capacity) ((Value) & (Capacity - 1)) + +typedef struct _TUN_PACKET +{ + ULONG Size; + UCHAR _Field_size_bytes_(Size) + Data[]; +} TUN_PACKET; + +typedef struct _TUN_RING +{ + volatile ULONG Head; + volatile ULONG Tail; + volatile LONG Alertable; + UCHAR Data[]; +} TUN_RING; + +#define TUN_IOCTL_REGISTER_RINGS CTL_CODE(51820U, 0x970U, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) + +typedef struct _TUN_REGISTER_RINGS +{ + struct + { + ULONG RingSize; + TUN_RING *Ring; + HANDLE TailMoved; + } Send, Receive; +} TUN_REGISTER_RINGS; + +typedef struct _TUN_SESSION +{ + ULONG Capacity; + TUN_REGISTER_RINGS Descriptor; + HANDLE Handle; +} TUN_SESSION; + +WINTUN_STATUS WINAPI +WintunStartSession(_In_ const WINTUN_ADAPTER *Adapter, _In_ DWORD Capacity, _Out_ TUN_SESSION **Session) +{ + HANDLE Heap = GetProcessHeap(); + *Session = HeapAlloc(Heap, 0, sizeof(TUN_SESSION)); + if (!*Session) + return LOG(WINTUN_LOG_ERR, L"Out of memory"), ERROR_OUTOFMEMORY; + const ULONG RingSize = TUN_RING_SIZE(Capacity); + DWORD Result; + BYTE *AllocatedRegion = VirtualAlloc(0, (size_t)RingSize * 2, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (!AllocatedRegion) + { + Result = LOG_LAST_ERROR(L"Failed to allocate ring memory"); + goto cleanupRings; + } + (*Session)->Descriptor.Send.RingSize = RingSize; + (*Session)->Descriptor.Send.Ring = (TUN_RING *)AllocatedRegion; + (*Session)->Descriptor.Send.TailMoved = CreateEventW(SecurityAttributes, FALSE, FALSE, NULL); + if (!(*Session)->Descriptor.Send.TailMoved) + { + Result = LOG_LAST_ERROR(L"Failed to create send event"); + goto cleanupAllocatedRegion; + } + + (*Session)->Descriptor.Receive.RingSize = RingSize; + (*Session)->Descriptor.Receive.Ring = (TUN_RING *)(AllocatedRegion + RingSize); + (*Session)->Descriptor.Receive.TailMoved = CreateEvent(SecurityAttributes, FALSE, FALSE, NULL); + if (!(*Session)->Descriptor.Receive.TailMoved) + { + Result = LOG_LAST_ERROR(L"Failed to create receive event"); + goto cleanupSendTailMoved; + } + + Result = WintunGetAdapterDeviceObject(Adapter, &(*Session)->Handle); + if (Result != ERROR_SUCCESS) + { + LOG(WINTUN_LOG_ERR, L"Failed to open adapter device object"); + goto cleanupReceiveTailMoved; + } + DWORD BytesReturned; + if (!DeviceIoControl( + (*Session)->Handle, + TUN_IOCTL_REGISTER_RINGS, + &(*Session)->Descriptor, + sizeof(TUN_REGISTER_RINGS), + NULL, + 0, + &BytesReturned, + NULL)) + { + Result = LOG_LAST_ERROR(L"Failed to perform ioctl"); + goto cleanupHandle; + } + (*Session)->Capacity = Capacity; + return ERROR_SUCCESS; +cleanupHandle: + CloseHandle((*Session)->Handle); +cleanupReceiveTailMoved: + CloseHandle((*Session)->Descriptor.Receive.TailMoved); +cleanupSendTailMoved: + CloseHandle((*Session)->Descriptor.Send.TailMoved); +cleanupAllocatedRegion: + VirtualFree(AllocatedRegion, 0, MEM_RELEASE); +cleanupRings: + HeapFree(Heap, 0, *Session); + *Session = NULL; + return Result; +} + +void WINAPI +WintunEndSession(_In_ TUN_SESSION *Session) +{ + SetEvent(Session->Descriptor.Send.TailMoved); // wake the reader if it's sleeping + CloseHandle(Session->Handle); + CloseHandle(Session->Descriptor.Send.TailMoved); + CloseHandle(Session->Descriptor.Receive.TailMoved); + VirtualFree(Session->Descriptor.Send.Ring, 0, MEM_RELEASE); + HeapFree(GetProcessHeap(), 0, Session); +} + +BOOL WINAPI +WintunIsPacketAvailable(_In_ TUN_SESSION *Session) +{ + return InterlockedGetU(&Session->Descriptor.Send.Ring->Head) != + InterlockedGetU(&Session->Descriptor.Send.Ring->Tail); +} + +WINTUN_STATUS WINAPI +WintunWaitForPacket(_In_ TUN_SESSION *Session, _In_ DWORD Milliseconds) +{ + return WaitForSingleObject(Session->Descriptor.Send.TailMoved, Milliseconds); +} + +WINTUN_STATUS WINAPI +WintunReceivePackets(_In_ TUN_SESSION *Session, _Inout_ WINTUN_PACKET *Queue) +{ + ULONG BuffHead = InterlockedGetU(&Session->Descriptor.Send.Ring->Head); + if (BuffHead >= Session->Capacity) + return ERROR_HANDLE_EOF; + + for (; Queue; Queue = Queue->Next) + { + const ULONG BuffTail = InterlockedGetU(&Session->Descriptor.Send.Ring->Tail); + if (BuffTail >= Session->Capacity) + return ERROR_HANDLE_EOF; + + if (BuffHead == BuffTail) + return ERROR_NO_MORE_ITEMS; + + const ULONG BuffContent = TUN_RING_WRAP(BuffTail - BuffHead, Session->Capacity); + if (BuffContent < sizeof(TUN_PACKET)) + return ERROR_INVALID_DATA; + + const TUN_PACKET *Packet = (TUN_PACKET *)&Session->Descriptor.Send.Ring->Data[BuffHead]; + if (Packet->Size > WINTUN_MAX_IP_PACKET_SIZE) + return ERROR_INVALID_DATA; + + const ULONG AlignedPacketSize = TUN_ALIGN(sizeof(TUN_PACKET) + Packet->Size); + if (AlignedPacketSize > BuffContent) + return ERROR_INVALID_DATA; + + Queue->Size = Packet->Size; + memcpy(Queue->Data, Packet->Data, Packet->Size); + BuffHead = TUN_RING_WRAP(BuffHead + AlignedPacketSize, Session->Capacity); + InterlockedSetU(&Session->Descriptor.Send.Ring->Head, BuffHead); + } + + return ERROR_SUCCESS; +} + +WINTUN_STATUS WINAPI +WintunSendPackets(_In_ TUN_SESSION *Session, _In_ const WINTUN_PACKET *Queue) +{ + ULONG BuffTail = InterlockedGetU(&Session->Descriptor.Receive.Ring->Tail); + if (BuffTail >= Session->Capacity) + return ERROR_HANDLE_EOF; + + for (; Queue; Queue = Queue->Next) + { + const ULONG PacketSize = Queue->Size; + const ULONG AlignedPacketSize = TUN_ALIGN(sizeof(TUN_PACKET) + PacketSize); + + const ULONG BuffHead = InterlockedGetU(&Session->Descriptor.Receive.Ring->Head); + if (BuffHead >= Session->Capacity) + return ERROR_HANDLE_EOF; + + const ULONG BuffSpace = TUN_RING_WRAP(BuffHead - BuffTail - TUN_ALIGNMENT, Session->Capacity); + if (AlignedPacketSize > BuffSpace) + return ERROR_BUFFER_OVERFLOW; /* Dropping when ring is full. */ + + TUN_PACKET *Packet = (TUN_PACKET *)&Session->Descriptor.Receive.Ring->Data[BuffTail]; + Packet->Size = PacketSize; + memcpy(Packet->Data, Queue->Data, PacketSize); + BuffTail = TUN_RING_WRAP(BuffTail + AlignedPacketSize, Session->Capacity); + InterlockedSetU(&Session->Descriptor.Receive.Ring->Tail, BuffTail); + + if (InterlockedGet(&Session->Descriptor.Receive.Ring->Alertable)) + SetEvent(Session->Descriptor.Receive.TailMoved); + } + + return ERROR_SUCCESS; +} diff --git a/api/wintun.h b/api/wintun.h index dadac10..1ee089d 100644 --- a/api/wintun.h +++ b/api/wintun.h @@ -211,3 +211,126 @@ typedef BOOL(CALLBACK *WINTUN_LOGGER_FUNC)(_In_ WINTUN_LOGGER_LEVEL Level, _In_z * NewLogger. */ typedef void(WINAPI *WINTUN_SET_LOGGER_FUNC)(_In_ WINTUN_LOGGER_FUNC NewLogger); + +/** + * Minimum ring capacity. + */ +#define WINTUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */ + +/** + * Maximum ring capacity. + */ +#define WINTUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */ + +/** + * A handle representing Wintun session + */ +typedef void *WINTUN_SESSION_HANDLE; + +/** + * Starts Wintun session. + * + * @param Adapter Adapter handle obtained with WintunGetAdapter or WintunCreateAdapter + * + * @param Capacity Rings capacity. Must be between WINTUN_MIN_RING_CAPACITY and WINTUN_MAX_RING_CAPACITY (incl.) + * Must be a power of two. + * + * @param Session Pointer to a variable to receive Wintun session handle + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise. + */ +typedef WINTUN_STATUS(WINAPI *WINTUN_START_SESSION_FUNC)( + _In_ WINTUN_ADAPTER_HANDLE Adapter, + _In_ DWORD Capacity, + _Out_ WINTUN_SESSION_HANDLE *Session); + +/** + * Ends Wintun session. + * + * @param Session Wintun session handle obtained with WintunStartSession + */ +typedef void(WINAPI *WINTUN_END_SESSION_FUNC)(_In_ WINTUN_SESSION_HANDLE Session); + +/** + * Maximum IP packet size + */ +#define WINTUN_MAX_IP_PACKET_SIZE 0xFFFF + +/** + * Packet with data + */ +typedef struct _WINTUN_PACKET +{ + /** + * Pointer to next packet in queue + */ + struct _WINTUN_PACKET *Next; + + /** + * Size of packet (max WINTUN_MAX_IP_PACKET_SIZE). + */ + DWORD Size; + + /** + * Pointer to layer 3 IPv4 or IPv6 packet + */ + BYTE *Data; +} WINTUN_PACKET; + +/** + * Peeks if there is a packet available for reading. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @return Non-zero if there is a packet available; zero otherwise. + */ +BOOL(WINAPI *WINTUN_IS_PACKET_AVAILABLE_FUNC)(_In_ WINTUN_SESSION_HANDLE Session); + +/** + * Waits for a packet to become available for reading. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param Milliseconds The time-out interval, in milliseconds. If a nonzero value is specified, the function waits + * until a packet is available or the interval elapses. If Milliseconds is zero, the function + * does not enter a wait state if a packet is not available; it always returns immediately. + * If Milliseconds is INFINITE, the function will return only when a packet is available. + * + * @return See WaitForSingleObject() for return values. + */ +WINTUN_STATUS(WINAPI *WINTUN_WAIT_FOR_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ DWORD Milliseconds); + +/** + * Reads one or more packets. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param Queue A linked list of nodes to fill with packets read. The list must be NULL terminated. May + * contain only one node to read one packet at a time. All nodes should be big enough to + * accommodate WINTUN_MAX_IP_PACKET_SIZE packet. The Size field of every node should be + * initialized to something bigger than WINTUN_MAX_IP_PACKET_SIZE allowing post-festum + * detection which nodes were actually filled with packets. + * + * @return + * ERROR_HANDLE_EOF Wintun adapter is terminating. + * ERROR_NO_MORE_ITEMS Wintun buffer is exhausted. + * ERROR_INVALID_DATA Wintun buffer is corrupt. + * ERROR_SUCCESS Requested amount of packets was read successfully. + * Regardless, if the error was returned, some packets might have been read nevertheless. + */ +WINTUN_STATUS(WINAPI *WINTUN_RECEIVE_PACKETS_FUNC) +(_In_ WINTUN_SESSION_HANDLE Session, _Inout_ WINTUN_PACKET *Queue); + +/** + * Sends packets. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param Queue Linked list of packets to send. The list must be NULL terminated. + * + * @return + * ERROR_HANDLE_EOF Wintun adapter is terminating. + * ERROR_BUFFER_OVERFLOW Wintun buffer is full. One or more packets were dropped. + * ERROR_SUCCESS All packets were sent successfully. + */ +WINTUN_STATUS(WINAPI *WINTUN_SEND_PACKETS_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const WINTUN_PACKET *Queue); |