aboutsummaryrefslogtreecommitdiffstats
path: root/api/session.c
diff options
context:
space:
mode:
Diffstat (limited to 'api/session.c')
-rw-r--r--api/session.c213
1 files changed, 213 insertions, 0 deletions
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;
+}