aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rozman <simon@rozman.si>2020-10-24 08:28:17 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2020-10-30 16:51:01 +0100
commit4b8f879fd6e41b06da2b580b0444e9ad31063f1e (patch)
tree81903eda7fee0f7fcf0a05bdad5f159df43d2c7b
parentapi: allow Debug build packaging (diff)
downloadwintun-4b8f879fd6e41b06da2b580b0444e9ad31063f1e.tar.xz
wintun-4b8f879fd6e41b06da2b580b0444e9ad31063f1e.zip
api: add ring management
Rather than every client reinvent the art of using the Wintun and its ring buffers, we offer helper structs and functions to unify and simplify Wintun usage. Signed-off-by: Simon Rozman <simon@rozman.si>
-rw-r--r--api/adapter.h7
-rw-r--r--api/api.vcxproj2
-rw-r--r--api/api.vcxproj.filters6
-rw-r--r--api/atomic.h26
-rw-r--r--api/exports.def6
-rw-r--r--api/pch.h1
-rw-r--r--api/session.c213
-rw-r--r--api/wintun.h123
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
diff --git a/api/pch.h b/api/pch.h
index 2c89a90..a4320e4 100644
--- a/api/pch.h
+++ b/api/pch.h
@@ -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);