diff --git a/.github/workflows/msbuild.yml b/.github/workflows/msbuild.yml index f8dfea1..e63d5e1 100644 --- a/.github/workflows/msbuild.yml +++ b/.github/workflows/msbuild.yml @@ -55,7 +55,7 @@ jobs: Copy-Item -Path ..\${{ matrix.arch }}\${{ matrix.build_conf }}\ovpn-dco\* -Destination dist\${{ matrix.arch }}\win11\ -Recurse .\build.ps1 -Arch ${{ matrix.arch }} -Wix ..\wix - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: ovpn-dco_${{ matrix.arch }}_${{ matrix.build_conf }} path: | diff --git a/.gitignore b/.gitignore index 336c1bf..0c87796 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ Thumbs.db Debug Release x64 +x86 Win32 sdv ARM64 @@ -72,3 +73,9 @@ smvstats.txt *.cab *.vcxproj.user + +**/out +**/build +**/.vscode +**/signed +msm/tmp \ No newline at end of file diff --git a/Driver.cpp b/Driver.cpp index 6175809..8540e5d 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -27,6 +27,7 @@ #include #include "bufferpool.h" +#include "control.h" #include "driver.h" #include "trace.h" #include "peer.h" @@ -50,6 +51,9 @@ OvpnEvtDriverUnload(_In_ WDFDRIVER driver) { UNREFERENCED_PARAMETER(driver); + LOG_ENTER(); + LOG_EXIT(); + TraceLoggingUnregister(g_hOvpnEtwProvider); // tail call optimization incorrectly eliminates TraceLoggingUnregister() call @@ -57,6 +61,17 @@ OvpnEvtDriverUnload(_In_ WDFDRIVER driver) __nop(); } +EVT_WDF_OBJECT_CONTEXT_CLEANUP OvpnEvtDriverCleanup; + +_Use_decl_annotations_ +VOID OvpnEvtDriverCleanup(_In_ WDFOBJECT driver) +{ + UNREFERENCED_PARAMETER(driver); + + LOG_ENTER(); + LOG_EXIT(); +} + EXTERN_C DRIVER_INITIALIZE DriverEntry; #ifdef ALLOC_PRAGMA @@ -81,6 +96,7 @@ DriverEntry(_In_ PDRIVER_OBJECT driverObject, _In_ PUNICODE_STRING registryPath) WDF_OBJECT_ATTRIBUTES driverAttrs; WDF_OBJECT_ATTRIBUTES_INIT(&driverAttrs); WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE(&driverAttrs, OVPN_DRIVER); + driverAttrs.EvtCleanupCallback = OvpnEvtDriverCleanup; WDF_DRIVER_CONFIG driverConfig; WDF_DRIVER_CONFIG_INIT(&driverConfig, OvpnEvtDeviceAdd); @@ -171,7 +187,7 @@ OvpnEvtIoWrite(WDFQUEUE queue, WDFREQUEST request, size_t length) // acquire spinlock, since we access device->TransportSocket KIRQL kiqrl = ExAcquireSpinLockShared(&device->SpinLock); - OVPN_TX_BUFFER* buffer = NULL; + OVPN_TX_BUFFER* txBuf = NULL; if (device->Socket.Socket == NULL) { status = STATUS_INVALID_DEVICE_STATE; @@ -179,31 +195,67 @@ OvpnEvtIoWrite(WDFQUEUE queue, WDFREQUEST request, size_t length) goto error; } - // fetch tx buffer - GOTO_IF_NOT_NT_SUCCESS(error, status, OvpnTxBufferPoolGet(device->TxBufferPool, &buffer)); - // get request buffer - PVOID requestBuffer; - size_t requestBufferLength; - GOTO_IF_NOT_NT_SUCCESS(error, status, WdfRequestRetrieveInputBuffer(request, 0, &requestBuffer, &requestBufferLength)); + PVOID buf; + size_t bufLen; + GOTO_IF_NOT_NT_SUCCESS(error, status, WdfRequestRetrieveInputBuffer(request, 0, &buf, &bufLen)); + + PSOCKADDR sa = NULL; + + if (device->Mode == OVPN_MODE_MP) { + // buffer is prepended with SOCKADDR + + sa = (PSOCKADDR)buf; + switch (sa->sa_family) { + case AF_INET: + if (bufLen <= sizeof(SOCKADDR_IN)) { + status = STATUS_INVALID_MESSAGE; + LOG_ERROR("Message too short", TraceLoggingValue(bufLen, "msgLen"), TraceLoggingValue(sizeof(SOCKADDR_IN), "minLen")); + goto error; + } + + buf = (char*)buf + sizeof(SOCKADDR_IN); + bufLen -= sizeof(SOCKADDR_IN); + break; + + case AF_INET6: + if (bufLen <= sizeof(SOCKADDR_IN6)) { + status = STATUS_INVALID_MESSAGE; + LOG_ERROR("Message too short", TraceLoggingValue(bufLen, "msgLen"), TraceLoggingValue(sizeof(SOCKADDR_IN6), "minLen")); + goto error; + } + + buf = (char*)buf + sizeof(SOCKADDR_IN6); + bufLen -= sizeof(SOCKADDR_IN6); + break; + + default: + LOG_ERROR("Invalid address family", TraceLoggingValue(sa->sa_family, "AF")); + status = STATUS_INVALID_ADDRESS; + goto error; + } + } + + // fetch tx buffer + GOTO_IF_NOT_NT_SUCCESS(error, status, OvpnTxBufferPoolGet(device->TxBufferPool, &txBuf)); // copy data from request to tx buffer - PUCHAR buf = OvpnTxBufferPut(buffer, requestBufferLength); - RtlCopyMemory(buf, requestBuffer, requestBufferLength); + PUCHAR data = OvpnBufferPut(txBuf, bufLen); + RtlCopyMemory(data, buf, bufLen); - buffer->IoQueue = device->PendingWritesQueue; + txBuf->IoQueue = device->PendingWritesQueue; // move request to manual queue GOTO_IF_NOT_NT_SUCCESS(error, status, WdfRequestForwardToIoQueue(request, device->PendingWritesQueue)); // send - LOG_IF_NOT_NT_SUCCESS(status = OvpnSocketSend(&device->Socket, buffer)); + LOG_IF_NOT_NT_SUCCESS(status = OvpnSocketSend(&device->Socket, txBuf, sa)); goto done_not_complete; error: - if (buffer != NULL) { - OvpnTxBufferPoolPut(buffer); + if (txBuf != NULL) { + OvpnTxBufferPoolPut(txBuf); } ULONG_PTR bytesCopied = 0; @@ -214,24 +266,200 @@ OvpnEvtIoWrite(WDFQUEUE queue, WDFREQUEST request, size_t length) } NTSTATUS -OvpnGetVersion(WDFREQUEST request, _Out_ ULONG_PTR* bytesReturned) +OvpnSetMode(POVPN_DEVICE device, WDFREQUEST request) { - *bytesReturned = 0; + POVPN_SET_MODE mode; + NTSTATUS status = WdfRequestRetrieveInputBuffer(request, sizeof(OVPN_SET_MODE), (PVOID*)&mode, NULL); + if (!NT_SUCCESS(status)) { + return status; + } - NTSTATUS status; - POVPN_VERSION version = NULL; - GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveOutputBuffer(request, sizeof(OVPN_VERSION), (PVOID*)&version, NULL)); + if (device->Mode != OVPN_MODE_P2P) { + LOG_ERROR("mode already set"); + return STATUS_ALREADY_INITIALIZED; + } + + status = STATUS_SUCCESS; + + LOG_INFO("Set mode", TraceLoggingValue(static_cast(mode->Mode), "mode")); + + switch (mode->Mode) { + case OVPN_MODE_P2P: + case OVPN_MODE_MP: + device->Mode = mode->Mode; + break; + + default: + status = STATUS_INVALID_PARAMETER; + break; + } + + return status; +} + +static BOOLEAN +OvpnDeviceCheckMode(OVPN_MODE mode, ULONG code) +{ + if (mode == OVPN_MODE_MP) { + switch (code) { + // all those IOCTLs are only for P2P mode + case OVPN_IOCTL_NEW_PEER: + case OVPN_IOCTL_DEL_PEER: + case OVPN_IOCTL_SWAP_KEYS: + case OVPN_IOCTL_SET_PEER: + case OVPN_IOCTL_START_VPN: + return FALSE; + } + } + else if (mode == OVPN_MODE_P2P) { + switch (code) { + // those IOCTLs are for MP mode + case OVPN_IOCTL_MP_START_VPN: + case OVPN_IOCTL_MP_NEW_PEER: + case OVPN_IOCTL_MP_SET_PEER: + return FALSE; + } + } + + return TRUE; +} + +static NTSTATUS +OvpnStopVPN(_In_ POVPN_DEVICE device) +{ + LOG_ENTER(); + + OvpnFlushPeers(device); + + KIRQL kirql = ExAcquireSpinLockExclusive(&device->SpinLock); + PWSK_SOCKET socket = device->Socket.Socket; + device->Socket.Socket = NULL; + + device->Mode = OVPN_MODE_P2P; + + RtlZeroMemory(&device->Socket.TcpState, sizeof(OvpnSocketTcpState)); + RtlZeroMemory(&device->Socket.UdpState, sizeof(OvpnSocketUdpState)); + + ExReleaseSpinLockExclusive(&device->SpinLock, kirql); - version->Major = OVPN_DCO_VERSION_MAJOR; - version->Minor = OVPN_DCO_VERSION_MINOR; - version->Patch = OVPN_DCO_VERSION_PATCH; + if (socket != NULL) { + LOG_IF_NOT_NT_SUCCESS(OvpnSocketClose(socket)); + } + + // flush buffers in control queue so that client won't get control channel messages from previous session + while (LIST_ENTRY* entry = OvpnBufferQueueDequeue(device->ControlRxBufferQueue)) { + OVPN_RX_BUFFER* buffer = CONTAINING_RECORD(entry, OVPN_RX_BUFFER, QueueListEntry); + // return buffer back to pool + OvpnRxBufferPoolPut(buffer); + } + + WDFREQUEST request; + while (NT_SUCCESS(WdfIoQueueRetrieveNextRequest(device->PendingReadsQueue, &request))) { + ULONG_PTR bytesCopied = 0; + LOG_INFO("Cancel pending read requests"); + WdfRequestCompleteWithInformation(request, STATUS_CANCELLED, bytesCopied); + } + + while (NT_SUCCESS(WdfIoQueueRetrieveNextRequest(device->PendingNotificationRequestsQueue, &request))) { + ULONG_PTR bytesCopied = 0; + LOG_INFO("Cancel pending notifications"); + WdfRequestCompleteWithInformation(request, STATUS_CANCELLED, bytesCopied); + } - *bytesReturned = sizeof(OVPN_VERSION); + device->PendingNotificationsQueue.FlushEvents(); + + LOG_EXIT(); + + return STATUS_SUCCESS; +} + +_Must_inspect_result_ +_IRQL_requires_(PASSIVE_LEVEL) +_IRQL_requires_same_ +NTSTATUS +OvpnMPStartVPN(POVPN_DEVICE device, WDFREQUEST request, ULONG_PTR* bytesReturned) +{ + NTSTATUS status = STATUS_SUCCESS; + + LOG_ENTER(); + + KIRQL kirql = ExAcquireSpinLockExclusive(&device->SpinLock); + if (device->Socket.Socket != NULL) { + ExReleaseSpinLockExclusive(&device->SpinLock, kirql); + + status = STATUS_ALREADY_INITIALIZED; + + goto done; + } + else { + ExReleaseSpinLockExclusive(&device->SpinLock, kirql); + + POVPN_MP_START_VPN addrIn = NULL; + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveInputBuffer(request, sizeof(OVPN_MP_START_VPN), (PVOID*)&addrIn, NULL)); + + PWSK_SOCKET socket = NULL; + POVPN_DRIVER driver = OvpnGetDriverContext(WdfGetDriver()); + + // Bind to the address provided + status = OvpnSocketInit(&driver->WskProviderNpi, &driver->WskRegistration, + addrIn->ListenAddress.Addr4.sin_family, false, + (PSOCKADDR)&addrIn->ListenAddress, NULL, + 0, device, &socket); + if (!NT_SUCCESS(status)) { + LOG_ERROR("Socket create failed", TraceLoggingValue((UINT32)status), + TraceLoggingHexUInt32(*(UINT32*)(&addrIn->ListenAddress.Addr4.sin_addr), "addr")); + goto done; + } + + kirql = ExAcquireSpinLockExclusive(&device->SpinLock); + device->Socket.Socket = socket; + ExReleaseSpinLockExclusive(&device->SpinLock, kirql); + + // we might bind the socket to port 0 and we want to get actual port back to userspace + POVPN_MP_START_VPN addrOut = NULL; + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveOutputBuffer(request, sizeof(OVPN_MP_START_VPN), (PVOID*)&addrOut, NULL)); + RtlCopyMemory(addrOut, addrIn, sizeof(OVPN_MP_START_VPN)); + *bytesReturned = sizeof(OVPN_MP_START_VPN); + } + + OvpnAdapterSetLinkState(OvpnGetAdapterContext(device->Adapter), MediaConnectStateConnected); done: + LOG_EXIT(); + return status; } +NTSTATUS +OvpnNotifyEvent(POVPN_DEVICE device, WDFREQUEST request, _Out_ ULONG_PTR* bytesReturned) { + LOG_ENTER(); + + NTSTATUS status = STATUS_SUCCESS; + + *bytesReturned = 0; + + // do we have pending notifications? + auto event = device->PendingNotificationsQueue.GetEvent(); + if (event != nullptr) { + OVPN_NOTIFY_EVENT* evt; + LOG_IF_NOT_NT_SUCCESS(status = WdfRequestRetrieveOutputBuffer(request, sizeof(OVPN_NOTIFY_EVENT), (PVOID*)&evt, nullptr)); + if (NT_SUCCESS(status)) { + evt->Cmd = evt->Cmd; + evt->PeerId = evt->PeerId; + evt->DelPeerReason = evt->DelPeerReason; + *bytesReturned = sizeof(OVPN_NOTIFY_EVENT); + } + device->PendingNotificationsQueue.FreeEvent(event); + } + else { + LOG_IF_NOT_NT_SUCCESS(WdfRequestForwardToIoQueue(request, device->PendingNotificationRequestsQueue)); + status = STATUS_PENDING; + } + + LOG_EXIT(); + + return status; +} EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL OvpnEvtIoDeviceControl; @@ -247,12 +475,15 @@ OvpnEvtIoDeviceControl(WDFQUEUE queue, WDFREQUEST request, size_t outputBufferLe ULONG_PTR bytesReturned = 0; - KIRQL kirql = 0; + if (!OvpnDeviceCheckMode(device->Mode, ioControlCode)) + { + WdfRequestCompleteWithInformation(request, STATUS_INVALID_DEVICE_STATE, bytesReturned); + return; + } + switch ((long)ioControlCode) { case OVPN_IOCTL_GET_STATS: - kirql = ExAcquireSpinLockShared(&device->SpinLock); status = OvpnPeerGetStats(device, request, &bytesReturned); - ExReleaseSpinLockShared(&device->SpinLock, kirql); break; case OVPN_IOCTL_NEW_PEER: @@ -260,7 +491,7 @@ OvpnEvtIoDeviceControl(WDFQUEUE queue, WDFREQUEST request, size_t outputBufferLe break; case OVPN_IOCTL_DEL_PEER: - status = OvpnPeerDel(device); + status = OvpnStopVPN(device); break; case OVPN_IOCTL_START_VPN: @@ -268,27 +499,45 @@ OvpnEvtIoDeviceControl(WDFQUEUE queue, WDFREQUEST request, size_t outputBufferLe break; case OVPN_IOCTL_NEW_KEY: - kirql = ExAcquireSpinLockExclusive(&device->SpinLock); status = OvpnPeerNewKey(device, request); - ExReleaseSpinLockExclusive(&device->SpinLock, kirql); + break; + + case OVPN_IOCTL_NEW_KEY_V2: + status = OvpnPeerNewKeyV2(device, request); break; case OVPN_IOCTL_SWAP_KEYS: - kirql = ExAcquireSpinLockExclusive(&device->SpinLock); status = OvpnPeerSwapKeys(device); - ExReleaseSpinLockExclusive(&device->SpinLock, kirql); break; case OVPN_IOCTL_SET_PEER: - kirql = ExAcquireSpinLockExclusive(&device->SpinLock); status = OvpnPeerSet(device, request); - ExReleaseSpinLockExclusive(&device->SpinLock, kirql); break; case OVPN_IOCTL_GET_VERSION: status = OvpnGetVersion(request, &bytesReturned); break; + case OVPN_IOCTL_SET_MODE: + status = OvpnSetMode(device, request); + break; + + case OVPN_IOCTL_MP_START_VPN: + status = OvpnMPStartVPN(device, request, &bytesReturned); + break; + + case OVPN_IOCTL_MP_NEW_PEER: + status = OvpnMPPeerNew(device, request); + break; + + case OVPN_IOCTL_MP_SET_PEER: + status = OvpnMPPeerSet(device, request); + break; + + case OVPN_IOCTL_NOTIFY_EVENT: + status = OvpnNotifyEvent(device, request, &bytesReturned); + break; + default: LOG_WARN("Unknown ", TraceLoggingValue(ioControlCode, "ioControlCode")); status = STATUS_INVALID_DEVICE_REQUEST; @@ -307,8 +556,7 @@ VOID OvpnEvtFileCleanup(WDFFILEOBJECT fileObject) { POVPN_DEVICE device = OvpnGetDeviceContext(WdfFileObjectGetDevice(fileObject)); - // peer might already be deleted - (VOID)OvpnPeerDel(device); + (VOID)OvpnStopVPN(device); if (device->Adapter != NULL) { OvpnAdapterSetLinkState(OvpnGetAdapterContext(device->Adapter), MediaConnectStateDisconnected); @@ -339,6 +587,15 @@ VOID OvpnEvtDeviceCleanup(WDFOBJECT obj) { // it requires PASSIVE_LEVEL. OvpnCryptoUninitAlgHandles(device->AesAlgHandle, device->ChachaAlgHandle); + // delete control device if there are no devices left + POVPN_DRIVER driverCtx = OvpnGetDriverContext(WdfGetDriver()); + LONG deviceCount = InterlockedDecrement(&driverCtx->DeviceCount); + LOG_INFO("Device count", TraceLoggingValue(deviceCount, "deviceCount")); + if ((deviceCount == 0) && (driverCtx->ControlDevice != NULL)) { + LOG_INFO("Delete control device"); + WdfObjectDelete(driverCtx->ControlDevice); + driverCtx->ControlDevice = NULL; + } LOG_EXIT(); } @@ -436,6 +693,17 @@ OvpnEvtDeviceAdd(WDFDRIVER wdfDriver, PWDFDEVICE_INIT deviceInit) { WDFDEVICE wdfDevice; GOTO_IF_NOT_NT_SUCCESS(done, status, WdfDeviceCreate(&deviceInit, &objAttributes, &wdfDevice)); + POVPN_DRIVER driverCtx = OvpnGetDriverContext(WdfGetDriver()); + InterlockedIncrement(&driverCtx->DeviceCount); + + LOG_INFO("Device count", TraceLoggingValue(driverCtx->DeviceCount, "count")); + + if (driverCtx->DeviceCount == 1) + { + // create non-exclusive control device to get the version information + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnCreateControlDevice(wdfDriver)); + } + // this will fail if one device has already been created but that's ok, since // openvpn2/3 accesses devices via Device Interface GUID, and symlink is used only by test client. LOG_IF_NOT_NT_SUCCESS(WdfDeviceCreateSymbolicLink(wdfDevice, &symLink)); @@ -467,16 +735,25 @@ OvpnEvtDeviceAdd(WDFDRIVER wdfDriver, PWDFDEVICE_INIT deviceInit) { WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchManual); GOTO_IF_NOT_NT_SUCCESS(done, status, WdfIoQueueCreate(wdfDevice, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &device->PendingNewPeerQueue)); + // create manual queue which handles userspace notification requests + WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchManual); + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfIoQueueCreate(wdfDevice, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &device->PendingNotificationRequestsQueue)); + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnTxBufferPoolCreate(&device->TxBufferPool, device)); GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnRxBufferPoolCreate(&device->RxBufferPool)); GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnBufferQueueCreate(&device->ControlRxBufferQueue)); GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnBufferQueueCreate(&device->DataRxBufferQueue)); + // constructors are not called for the members of WDF object context, so we use Init() method + device->PendingNotificationsQueue.Init(); + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnCryptoInitAlgHandles(&device->AesAlgHandle, &device->ChachaAlgHandle)); - // Initialize peers tree + // Initialize peers tables RtlInitializeGenericTable(&device->Peers, OvpnPeerCompareByPeerIdRoutine, OvpnPeerAllocateRoutine, OvpnPeerFreeRoutine, NULL); + RtlInitializeGenericTable(&device->PeersByVpn4, OvpnPeerCompareByVPN4Routine, OvpnPeerAllocateRoutine, OvpnPeerFreeRoutine, NULL); + RtlInitializeGenericTable(&device->PeersByVpn6, OvpnPeerCompareByVPN6Routine, OvpnPeerAllocateRoutine, OvpnPeerFreeRoutine, NULL); LOG_IF_NOT_NT_SUCCESS(status = OvpnAdapterCreate(device)); @@ -488,46 +765,211 @@ OvpnEvtDeviceAdd(WDFDRIVER wdfDriver, PWDFDEVICE_INIT deviceInit) { _Use_decl_annotations_ NTSTATUS -OvpnAddPeer(POVPN_DEVICE device, OvpnPeerContext* peer) +OvpnAddPeerToTable(POVPN_DEVICE device, RTL_GENERIC_TABLE* table, OvpnPeerContext* peer) { NTSTATUS status; BOOLEAN newElem; - RtlInsertElementGenericTable(&device->Peers, (PVOID)&peer, sizeof(OvpnPeerContext*), &newElem); + auto irql = ExAcquireSpinLockExclusive(&device->SpinLock); + + RtlInsertElementGenericTable(table, (PVOID)&peer, sizeof(OvpnPeerContext*), &newElem); if (newElem) { status = STATUS_SUCCESS; + InterlockedIncrement(&peer->RefCounter); } else { LOG_ERROR("Unable to add new peer"); status = STATUS_NO_MEMORY; } + + ExReleaseSpinLockExclusive(&device->SpinLock, irql); + return status; } _Use_decl_annotations_ VOID OvpnFlushPeers(POVPN_DEVICE device) { - OvpnCleanupPeerTable(&device->Peers); + OvpnCleanupPeerTable(device, &device->PeersByVpn6); + OvpnCleanupPeerTable(device, &device->PeersByVpn4); + OvpnCleanupPeerTable(device, &device->Peers); } _Use_decl_annotations_ VOID -OvpnCleanupPeerTable(RTL_GENERIC_TABLE* peers) +OvpnCleanupPeerTable(POVPN_DEVICE device, RTL_GENERIC_TABLE* peers) { + auto irql = ExAcquireSpinLockExclusive(&device->SpinLock); + while (!RtlIsGenericTableEmpty(peers)) { PVOID ptr = RtlGetElementGenericTable(peers, 0); OvpnPeerContext* peer = *(OvpnPeerContext**)ptr; RtlDeleteElementGenericTable(peers, ptr); - OvpnPeerCtxFree(peer); + OvpnPeerCtxRelease(peer); + } + + ExReleaseSpinLockExclusive(&device->SpinLock, irql); +} + +_Use_decl_annotations_ +OvpnPeerContext* +OvpnGetFirstPeer(POVPN_DEVICE device) +{ + auto irql = ExAcquireSpinLockShared(&device->SpinLock); + + OvpnPeerContext** ptr = (OvpnPeerContext**)RtlGetElementGenericTable(&device->Peers, 0); + OvpnPeerContext* peer = ptr ? (OvpnPeerContext*)*ptr : nullptr; + + if (peer != nullptr) { + InterlockedIncrement(&peer->RefCounter); + } + + ExReleaseSpinLockShared(&device->SpinLock, irql); + + return peer; +} + +_Use_decl_annotations_ +OvpnPeerContext* +OvpnFindPeer(POVPN_DEVICE device, INT32 PeerId) +{ + OvpnPeerContext* peer = nullptr; + OvpnPeerContext** ptr = nullptr; + + auto kirql = ExAcquireSpinLockShared(&device->SpinLock); + + if (device->Mode == OVPN_MODE_P2P) { + ptr = (OvpnPeerContext**)RtlGetElementGenericTable(&device->Peers, 0); + } + else { + OvpnPeerContext p{}; + p.PeerId = PeerId; + + auto* pp = &p; + ptr = (OvpnPeerContext**)RtlLookupElementGenericTable(&device->Peers, &pp); + } + + peer = ptr ? (OvpnPeerContext*)*ptr : nullptr; + + if (peer) { + InterlockedIncrement(&peer->RefCounter); + } + + ExReleaseSpinLockShared(&device->SpinLock, kirql); + + return peer; +} + +_Use_decl_annotations_ +OvpnPeerContext* +OvpnFindPeerVPN4(POVPN_DEVICE device, IN_ADDR addr) +{ + OvpnPeerContext* peer = nullptr; + OvpnPeerContext** ptr = nullptr; + + auto kirql = ExAcquireSpinLockShared(&device->SpinLock); + + if (device->Mode == OVPN_MODE_P2P) { + ptr = (OvpnPeerContext**)RtlGetElementGenericTable(&device->Peers, 0); + } + else { + OvpnPeerContext p{}; + p.VpnAddrs.IPv4 = addr; + + auto* pp = &p; + ptr = (OvpnPeerContext**)RtlLookupElementGenericTable(&device->PeersByVpn4, &pp); + } + + peer = ptr ? (OvpnPeerContext*)*ptr : nullptr; + if (peer) { + InterlockedIncrement(&peer->RefCounter); } + + ExReleaseSpinLockShared(&device->SpinLock, kirql); + + return peer; } _Use_decl_annotations_ OvpnPeerContext* -OvpnGetFirstPeer(RTL_GENERIC_TABLE* peers) +OvpnFindPeerVPN6(POVPN_DEVICE device, IN6_ADDR addr) { - OvpnPeerContext** ptr = (OvpnPeerContext**)RtlGetElementGenericTable(peers, 0); - return ptr ? (OvpnPeerContext*)*ptr : NULL; -} \ No newline at end of file + OvpnPeerContext* peer = nullptr; + OvpnPeerContext** ptr = nullptr; + + auto kirql = ExAcquireSpinLockShared(&device->SpinLock); + + if (device->Mode == OVPN_MODE_P2P) { + ptr = (OvpnPeerContext**)RtlGetElementGenericTable(&device->Peers, 0); + } + else { + OvpnPeerContext p{}; + p.VpnAddrs.IPv6 = addr; + + auto* pp = &p; + ptr = (OvpnPeerContext**)RtlLookupElementGenericTable(&device->PeersByVpn6, &pp); + } + + peer = ptr ? (OvpnPeerContext*)*ptr : nullptr; + if (peer) { + InterlockedIncrement(&peer->RefCounter); + } + + ExReleaseSpinLockShared(&device->SpinLock, kirql); + + return peer; +} + +VOID +OvpnDeletePeerFromTable(POVPN_DEVICE device, RTL_GENERIC_TABLE *table, OvpnPeerContext *peer, char* tableName) +{ + auto peerId = peer->PeerId; + auto pp = &peer; + + auto kirql = ExAcquireSpinLockShared(&device->SpinLock); + + if (RtlDeleteElementGenericTable(table, pp)) { + LOG_INFO("Peer deleted", TraceLoggingValue(tableName, "table"), TraceLoggingValue(peerId, "peer-id")); + + if (InterlockedDecrement(&peer->RefCounter) == 0) { + OvpnPeerCtxFree(peer); + LOG_INFO("Peer freed", TraceLoggingValue(peerId, "peer-id")); + } + } + else { + LOG_INFO("Peer not found", TraceLoggingValue(tableName, "table"), TraceLoggingValue(peerId, "peer-id")); + } + + ExReleaseSpinLockShared(&device->SpinLock, kirql); +} + +_Use_decl_annotations_ +NTSTATUS +OvpnDeletePeer(POVPN_DEVICE device, INT32 peerId) +{ + NTSTATUS status = STATUS_SUCCESS; + + LOG_INFO("Deleting peer", TraceLoggingValue(peerId, "peer-id")); + + // get peer from main table + OvpnPeerContext* peer = OvpnFindPeer(device, peerId); + if (peer == NULL) { + status = STATUS_NOT_FOUND; + LOG_WARN("Peer not found", TraceLoggingValue(peerId, "peer-id")); + } + else { + auto irql = ExAcquireSpinLockShared(&peer->SpinLock); + + OvpnDeletePeerFromTable(device, &device->PeersByVpn4, peer, "vpn4"); + OvpnDeletePeerFromTable(device, &device->PeersByVpn6, peer, "vpn6"); + OvpnDeletePeerFromTable(device, &device->Peers, peer, "peers"); + + ExReleaseSpinLockShared(&peer->SpinLock, irql); + + OvpnPeerCtxRelease(peer); + } + + return status; +} diff --git a/Driver.h b/Driver.h index f33fc0e..881cb7e 100644 --- a/Driver.h +++ b/Driver.h @@ -31,6 +31,7 @@ #include "adapter.h" #include "bufferpool.h" #include "crypto.h" +#include "notifyqueue.h" #include "socket.h" #include "uapi\ovpn-dco.h" @@ -47,6 +48,8 @@ EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL OvpnEvtIoDeviceControl; typedef struct _OVPN_DRIVER { WSK_PROVIDER_NPI WskProviderNpi; WSK_REGISTRATION WskRegistration; + WDFDEVICE ControlDevice; + LONG DeviceCount; } OVPN_DRIVER, * POVPN_DRIVER; WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(OVPN_DRIVER, OvpnGetDriverContext) @@ -58,6 +61,7 @@ struct OVPN_DEVICE { WDFQUEUE PendingReadsQueue; WDFQUEUE PendingWritesQueue; + WDFQUEUE PendingNotificationRequestsQueue; // NEW_PEER request may be enqueued here if TCP connect doesn't finish immediatelly WDFQUEUE PendingNewPeerQueue; @@ -74,15 +78,14 @@ struct OVPN_DEVICE { // buffer pool for encrypted data channel and control channel packets to be sent OVPN_TX_BUFFER_POOL TxBufferPool; + // queue to store pending userspace notifications + NotifyQueue PendingNotificationsQueue; + OVPN_STATS Stats; BCRYPT_ALG_HANDLE AesAlgHandle; BCRYPT_ALG_HANDLE ChachaAlgHandle; - // set from the userspace, defines TCP Maximum Segment Size - _Guarded_by_(SpinLock) - UINT16 MSS; - _Guarded_by_(SpinLock) OvpnSocket Socket; @@ -92,32 +95,47 @@ struct OVPN_DEVICE { _Guarded_by_(SpinLock) RTL_GENERIC_TABLE Peers; - SIZE_T CryptoOverhead; + _Guarded_by_(SpinLock) + RTL_GENERIC_TABLE PeersByVpn4; + + _Guarded_by_(SpinLock) + RTL_GENERIC_TABLE PeersByVpn6; + + OVPN_MODE Mode; }; typedef OVPN_DEVICE * POVPN_DEVICE; WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(OVPN_DEVICE, OvpnGetDeviceContext) -static inline -BOOLEAN -OvpnHasPeers(_In_ POVPN_DEVICE device) -{ - return !RtlIsGenericTableEmpty(&device->Peers); -} - struct OvpnPeerContext; _Must_inspect_result_ NTSTATUS -OvpnAddPeer(_In_ POVPN_DEVICE device, _In_ OvpnPeerContext* PeerCtx); +OvpnAddPeerToTable(POVPN_DEVICE device, _In_ RTL_GENERIC_TABLE* table, _In_ OvpnPeerContext* peer); VOID OvpnFlushPeers(_In_ POVPN_DEVICE device); VOID -OvpnCleanupPeerTable(_In_ RTL_GENERIC_TABLE*); +OvpnCleanupPeerTable(_In_ POVPN_DEVICE device, _In_ RTL_GENERIC_TABLE*); _Must_inspect_result_ OvpnPeerContext* -OvpnGetFirstPeer(_In_ RTL_GENERIC_TABLE*); +OvpnGetFirstPeer(_In_ POVPN_DEVICE device); + +_Must_inspect_result_ +OvpnPeerContext* +OvpnFindPeer(_In_ POVPN_DEVICE device, INT32 PeerId); + +_Must_inspect_result_ +OvpnPeerContext* +OvpnFindPeerVPN4(_In_ POVPN_DEVICE device, _In_ IN_ADDR addr); + +_Must_inspect_result_ +OvpnPeerContext* +OvpnFindPeerVPN6(_In_ POVPN_DEVICE device, _In_ IN6_ADDR addr); + +_Must_inspect_result_ +NTSTATUS +OvpnDeletePeer(_In_ POVPN_DEVICE device, INT32 peerId); diff --git a/PropertySheet.props b/PropertySheet.props index f63952e..6056166 100644 --- a/PropertySheet.props +++ b/PropertySheet.props @@ -3,8 +3,8 @@ 2 - 0 - 1 + 4 + 0 @@ -29,4 +29,4 @@ true - \ No newline at end of file + diff --git a/bufferpool.cpp b/bufferpool.cpp index 926c783..d1653ff 100644 --- a/bufferpool.cpp +++ b/bufferpool.cpp @@ -27,7 +27,7 @@ #include "bufferpool.h" #include "trace.h" -#define OVPN_BUFFER_HEADROOM 26 // we prepend TCP packet size (2 bytes) and crypto overhead (24 bytes) +#define OVPN_BUFFER_HEADROOM 30 // we prepend TCP packet size (2 bytes) and max crypto overhead (28 bytes) // good enough limit for in-flight packets constexpr auto MAX_POOL_SIZE = 100'000; @@ -205,6 +205,8 @@ OvpnRxBufferPoolGet(OVPN_RX_BUFFER_POOL handle, OVPN_RX_BUFFER** buffer) if (*buffer == NULL) return STATUS_INSUFFICIENT_RESOURCES; + (*buffer)->Data = (*buffer)->Head; + (*buffer)->Tail = (*buffer)->Data; (*buffer)->Pool = handle; (*buffer)->Len = 0; @@ -270,17 +272,6 @@ OvpnTxBufferPoolDelete(OVPN_BUFFER_POOL handle) OvpnBufferPoolDelete(handle); } -_Use_decl_annotations_ -UCHAR* -OvpnTxBufferPut(OVPN_TX_BUFFER* buffer, SIZE_T len) -{ - UCHAR* tmp = buffer->Tail; - buffer->Tail += len; - buffer->Len += len; - - return tmp; -} - _Use_decl_annotations_ UCHAR* OvpnTxBufferPush(OVPN_TX_BUFFER* buffer, SIZE_T len) @@ -295,7 +286,7 @@ _Use_decl_annotations_ NTSTATUS OvpnRxBufferPoolCreate(OVPN_RX_BUFFER_POOL* handle) { - return OvpnBufferPoolCreate((OVPN_BUFFER_POOL*)handle, sizeof(OVPN_RX_BUFFER), "rx", NULL); + return OvpnBufferPoolCreate((OVPN_BUFFER_POOL*)handle, sizeof(OVPN_RX_BUFFER) + OVPN_SOCKET_RX_PACKET_BUFFER_SIZE, "rx", NULL); } VOID diff --git a/bufferpool.h b/bufferpool.h index fcde323..fa742af 100644 --- a/bufferpool.h +++ b/bufferpool.h @@ -63,20 +63,51 @@ struct OVPN_TX_BUFFER struct OVPN_RX_BUFFER { - LIST_ENTRY PoolListEntry; + // points to the beginning of data + PUCHAR Data; - LIST_ENTRY QueueListEntry; + // points to the end of data + PUCHAR Tail; + // data length SIZE_T Len; + LIST_ENTRY PoolListEntry; + + LIST_ENTRY QueueListEntry; + OVPN_RX_BUFFER_POOL Pool; - UCHAR Data[OVPN_SOCKET_RX_PACKET_BUFFER_SIZE]; + #pragma warning(suppress:4200) //nonstandard extension used: zero-sized array in struct/union + UCHAR Head[]; }; -_Must_inspect_result_ +template UCHAR* -OvpnTxBufferPut(_In_ OVPN_TX_BUFFER* work, SIZE_T len); +OvpnBufferPut(_In_ BUF* buf, SIZE_T len) +{ + UCHAR* tmp = buf->Tail; + buf->Tail += len; + buf->Len += len; + + return tmp; +} + +template +VOID +OvpnBufferTrim(_In_ BUF* buf, SIZE_T len) +{ + buf->Len = len; + buf->Tail = buf->Data + len; +} + +template +VOID +OvpnBufferPull(_In_ BUF* buf, SIZE_T len) +{ + buf->Len -= len; + buf->Data += len; +} UCHAR* OvpnTxBufferPush(_In_ OVPN_TX_BUFFER* work, SIZE_T len); diff --git a/control.cpp b/control.cpp new file mode 100644 index 0000000..32f30a5 --- /dev/null +++ b/control.cpp @@ -0,0 +1,128 @@ +/* + * ovpn-dco-win OpenVPN protocol accelerator for Windows + * + * Copyright (C) 2024- OpenVPN Inc + * + * Author: Lev Stipakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "control.h" +#include "Driver.h" +#include "uapi\ovpn-dco.h" +#include "trace.h" + +_Use_decl_annotations_ +NTSTATUS +OvpnGetVersion(WDFREQUEST request, ULONG_PTR* bytesReturned) +{ + LOG_ENTER(); + + *bytesReturned = 0; + + NTSTATUS status; + POVPN_VERSION version = NULL; + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveOutputBuffer(request, sizeof(OVPN_VERSION), (PVOID*)&version, NULL)); + + version->Major = OVPN_DCO_VERSION_MAJOR; + version->Minor = OVPN_DCO_VERSION_MINOR; + version->Patch = OVPN_DCO_VERSION_PATCH; + + LOG_INFO("Version", TraceLoggingValue(version->Major, "Major"), TraceLoggingValue(version->Minor, "Minor"), TraceLoggingValue(version->Patch, "Patch")); + + *bytesReturned = sizeof(OVPN_VERSION); + +done: + LOG_EXIT(); + + return status; +} + +EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL OvpnEvtControlDeviceIOControl; + +VOID +OvpnEvtControlDeviceIOControl(WDFQUEUE queue, WDFREQUEST request, size_t outputBufferLength, size_t inputBufferLength, ULONG ioControlCode) +{ + UNREFERENCED_PARAMETER(queue); + UNREFERENCED_PARAMETER(inputBufferLength); + UNREFERENCED_PARAMETER(outputBufferLength); + + NTSTATUS status = STATUS_SUCCESS; + ULONG_PTR bytesReturned = 0; + + switch (ioControlCode) + { + case OVPN_IOCTL_GET_VERSION: + status = OvpnGetVersion(request, &bytesReturned); + break; + + default: + status = STATUS_INVALID_DEVICE_REQUEST; + break; + } + + WdfRequestCompleteWithInformation(request, status, bytesReturned); +} + +NTSTATUS +OvpnCreateControlDevice(WDFDRIVER wdfDriver) +{ + LOG_ENTER(); + + DECLARE_CONST_UNICODE_STRING(symLink, L"\\DosDevices\\ovpn-dco-ver"); // this will be used by CreateFile + DECLARE_CONST_UNICODE_STRING(deviceName, L"\\Device\\ovpn-dco-ver"); // this is required tp create symlink + + // allocate control device initialization structure + PWDFDEVICE_INIT deviceInit = WdfControlDeviceInitAllocate(wdfDriver, &SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_R_RES_R); + if (deviceInit == NULL) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + // create the control device + WDF_OBJECT_ATTRIBUTES deviceAttributes; + WDF_OBJECT_ATTRIBUTES_INIT(&deviceAttributes); + WDFDEVICE controlDevice; + NTSTATUS status; + + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfDeviceInitAssignName(deviceInit, &deviceName)); + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfDeviceCreate(&deviceInit, &deviceAttributes, &controlDevice)); + + POVPN_DRIVER driverCtx = OvpnGetDriverContext(WdfGetDriver()); + driverCtx->ControlDevice = controlDevice; + + // symlink for control device + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfDeviceCreateSymbolicLink(controlDevice, &symLink)); + + // queue to handle IO + WDF_IO_QUEUE_CONFIG queueConfig; + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel); + queueConfig.EvtIoDeviceControl = OvpnEvtControlDeviceIOControl; + WDFQUEUE queue; + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfIoQueueCreate(controlDevice, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &queue)); + + // Complete the control device initialization + WdfControlFinishInitializing(controlDevice); + + done: + if (deviceInit) + { + WdfDeviceInitFree(deviceInit); + } + + LOG_EXIT(); + + return status; +} diff --git a/control.h b/control.h new file mode 100644 index 0000000..8827ed2 --- /dev/null +++ b/control.h @@ -0,0 +1,31 @@ +/* + * ovpn-dco-win OpenVPN protocol accelerator for Windows + * + * Copyright (C) 2024- OpenVPN Inc + * + * Author: Lev Stipakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include +#include + +NTSTATUS +OvpnGetVersion(WDFREQUEST request, _Out_ ULONG_PTR* bytesReturned); + +NTSTATUS +OvpnCreateControlDevice(WDFDRIVER wdfDriver); diff --git a/crypto.cpp b/crypto.cpp index 75005f4..0f915fa 100644 --- a/crypto.cpp +++ b/crypto.cpp @@ -48,11 +48,14 @@ OvpnProtoOp32Compose(UINT opcode, UINT keyId, UINT opPeerId) OVPN_CRYPTO_DECRYPT OvpnCryptoDecryptNone; _Use_decl_annotations_ -NTSTATUS OvpnCryptoDecryptNone(OvpnCryptoKeySlot* keySlot, UCHAR* bufIn, SIZE_T len, UCHAR* bufOut) +NTSTATUS OvpnCryptoDecryptNone(OvpnCryptoKeySlot* keySlot, UCHAR* bufIn, SIZE_T len, UCHAR* bufOut, INT32 cryptoOptions) { UNREFERENCED_PARAMETER(keySlot); - if (len < NONE_CRYPTO_OVERHEAD) { + BOOLEAN pktId64bit = cryptoOptions & CRYPTO_OPTIONS_64BIT_PKTID; + BOOLEAN cryptoOverhead = OVPN_DATA_V2_LEN + pktId64bit ? 8 : 4; + + if (len < cryptoOverhead) { LOG_WARN("Packet too short", TraceLoggingValue(len, "len")); return STATUS_DATA_ERROR; } @@ -66,10 +69,11 @@ OVPN_CRYPTO_ENCRYPT OvpnCryptoEncryptNone; _Use_decl_annotations_ NTSTATUS -OvpnCryptoEncryptNone(OvpnCryptoKeySlot* keySlot, UCHAR* buf, SIZE_T len) +OvpnCryptoEncryptNone(OvpnCryptoKeySlot* keySlot, UCHAR* buf, SIZE_T len, INT32 cryptoOptions) { UNREFERENCED_PARAMETER(keySlot); UNREFERENCED_PARAMETER(len); + UNREFERENCED_PARAMETER(cryptoOptions); // prepend with opcode, key-id and peer-id UINT32 op = OvpnProtoOp32Compose(OVPN_OP_DATA_V2, 0, 0); @@ -121,74 +125,116 @@ OvpnCryptoUninitAlgHandles(_In_ BCRYPT_ALG_HANDLE aesAlgHandle, BCRYPT_ALG_HANDL static NTSTATUS -OvpnCryptoAEADDoWork(BOOLEAN encrypt, OvpnCryptoKeySlot* keySlot, UCHAR *bufIn, SIZE_T len, UCHAR* bufOut) +OvpnCryptoAEADDoWork(BOOLEAN encrypt, OvpnCryptoKeySlot* keySlot, UCHAR *bufIn, SIZE_T len, UCHAR* bufOut, INT32 cryptoOptions) { /* AEAD Nonce : [Packet ID] [HMAC keying material] - [4 bytes ] [8 bytes ] + [4/8 bytes] [8/4 bytes ] [AEAD nonce total : 12 bytes ] TLS wire protocol : + Packet ID is 8 bytes long with CRYPTO_OPTIONS_64BIT_PKTID. + [DATA_V2 opcode] [Packet ID] [AEAD Auth tag] [ciphertext] - [4 bytes ] [4 bytes ] [16 bytes ] + [4 bytes ] [4/8 bytes] [16 bytes ] + [AEAD additional data(AD) ] + + With CRYPTO_OPTIONS_AEAD_TAG_END AEAD Auth tag is placed after ciphertext: + + [DATA_V2 opcode] [Packet ID] [ciphertext] [AEAD Auth tag] + [4 bytes ] [4/8 bytes] [16 bytes ] [AEAD additional data(AD) ] */ NTSTATUS status = STATUS_SUCCESS; - if (len < AEAD_CRYPTO_OVERHEAD) { + BOOLEAN pktId64bit = cryptoOptions & CRYPTO_OPTIONS_64BIT_PKTID; + + SIZE_T cryptoOverhead = OVPN_DATA_V2_LEN + AEAD_AUTH_TAG_LEN + (pktId64bit ? 8 : 4); + + if (len < cryptoOverhead) { LOG_WARN("Packet too short", TraceLoggingValue(len, "len")); return STATUS_DATA_ERROR; } - UCHAR nonce[OVPN_PKTID_LEN + OVPN_NONCE_TAIL_LEN]; + UCHAR nonce[12]; if (encrypt) { // prepend with opcode, key-id and peer-id UINT32 op = OvpnProtoOp32Compose(OVPN_OP_DATA_V2, keySlot->KeyId, keySlot->PeerId); op = RtlUlongByteSwap(op); - *(UINT32*)(bufOut) = op; + *reinterpret_cast(bufOut) = op; - // calculate pktid - UINT32 pktid; - GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnPktidXmitNext(&keySlot->PktidXmit, &pktid)); - ULONG pktidNetwork = RtlUlongByteSwap(pktid); + if (pktId64bit) + { + // calculate pktid + UINT64 pktid; + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnPktidXmitNext(&keySlot->PktidXmit, &pktid, true)); + ULONG64 pktidNetwork = RtlUlonglongByteSwap(pktid); + + // calculate nonce, which is pktid + nonce_tail + RtlCopyMemory(nonce, &pktidNetwork, 8); + RtlCopyMemory(nonce + 8, keySlot->EncNonceTail, 4); + + // prepend with pktid + *reinterpret_cast(bufOut + OVPN_DATA_V2_LEN) = pktidNetwork; + } + else + { + // calculate pktid + UINT32 pktid; + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnPktidXmitNext(&keySlot->PktidXmit, &pktid, false)); + ULONG pktidNetwork = RtlUlongByteSwap(pktid); - // calculate nonce, which is pktid + nonce_tail - RtlCopyMemory(nonce, &pktidNetwork, OVPN_PKTID_LEN); - RtlCopyMemory(nonce + OVPN_PKTID_LEN, keySlot->EncNonceTail, OVPN_NONCE_TAIL_LEN); + // calculate nonce, which is pktid + nonce_tail + RtlCopyMemory(nonce, &pktidNetwork, 4); + RtlCopyMemory(nonce + 4, keySlot->EncNonceTail, 8); - // prepend with pktid - *(UINT32*)(bufOut + OVPN_DATA_V2_LEN) = pktidNetwork; + // prepend with pktid + *reinterpret_cast(bufOut + OVPN_DATA_V2_LEN) = pktidNetwork; + } } else { - RtlCopyMemory(nonce, bufIn + OVPN_DATA_V2_LEN, OVPN_PKTID_LEN); - RtlCopyMemory(nonce + OVPN_PKTID_LEN, &keySlot->DecNonceTail, sizeof(keySlot->DecNonceTail)); + ULONG64 pktId; + + RtlCopyMemory(nonce, bufIn + OVPN_DATA_V2_LEN, pktId64bit ? 8 : 4); + RtlCopyMemory(nonce + (pktId64bit ? 8 : 4), &keySlot->DecNonceTail, pktId64bit ? 4 : 8); + if (pktId64bit) + { + pktId = RtlUlonglongByteSwap(*reinterpret_cast(nonce)); + } + else + { + pktId = static_cast(RtlUlongByteSwap(*reinterpret_cast(nonce))); + } - UINT32 pktId = RtlUlongByteSwap(*(UINT32*)nonce); status = OvpnPktidRecvVerify(&keySlot->PktidRecv, pktId); if (!NT_SUCCESS(status)) { - LOG_ERROR("Invalid pktId", TraceLoggingUInt32(pktId, "pktId")); + LOG_ERROR("Invalid pktId", TraceLoggingUInt64(pktId, "pktId")); return STATUS_DATA_ERROR; } } + // we prepended buf with crypto overhead + len -= cryptoOverhead; + + BOOLEAN aeadTagEnd = cryptoOptions & CRYPTO_OPTIONS_AEAD_TAG_END; + BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo; BCRYPT_INIT_AUTH_MODE_INFO(authInfo); authInfo.pbNonce = nonce; authInfo.cbNonce = sizeof(nonce); - authInfo.pbTag = (encrypt ? bufOut : bufIn) + OVPN_DATA_V2_LEN + OVPN_PKTID_LEN; + authInfo.pbTag = (encrypt ? bufOut : bufIn) + OVPN_DATA_V2_LEN + (pktId64bit ? 8 : 4) + (aeadTagEnd ? len : 0); authInfo.cbTag = AEAD_AUTH_TAG_LEN; authInfo.pbAuthData = (encrypt ? bufOut : bufIn); - authInfo.cbAuthData = OVPN_DATA_V2_LEN + OVPN_PKTID_LEN; - - bufOut += AEAD_CRYPTO_OVERHEAD; - bufIn += AEAD_CRYPTO_OVERHEAD; + authInfo.cbAuthData = OVPN_DATA_V2_LEN + (pktId64bit ? 8 : 4); - len -= AEAD_CRYPTO_OVERHEAD; + auto payloadOffset = OVPN_DATA_V2_LEN + (pktId64bit ? 8 : 4) + (aeadTagEnd ? 0 : AEAD_AUTH_TAG_LEN); + bufOut += payloadOffset; + bufIn += payloadOffset; // non-chaining mode ULONG bytesDone = 0; @@ -205,27 +251,29 @@ OVPN_CRYPTO_DECRYPT OvpnCryptoDecryptAEAD; _Use_decl_annotations_ NTSTATUS -OvpnCryptoDecryptAEAD(OvpnCryptoKeySlot* keySlot, UCHAR* bufIn, SIZE_T len, UCHAR* bufOut) +OvpnCryptoDecryptAEAD(OvpnCryptoKeySlot* keySlot, UCHAR* bufIn, SIZE_T len, UCHAR* bufOut, INT32 cryptoOptions) { - return OvpnCryptoAEADDoWork(FALSE, keySlot, bufIn, len, bufOut); + return OvpnCryptoAEADDoWork(FALSE, keySlot, bufIn, len, bufOut, cryptoOptions); } OVPN_CRYPTO_ENCRYPT OvpnCryptoEncryptAEAD; _Use_decl_annotations_ NTSTATUS -OvpnCryptoEncryptAEAD(OvpnCryptoKeySlot* keySlot, UCHAR* buf, SIZE_T len) +OvpnCryptoEncryptAEAD(OvpnCryptoKeySlot* keySlot, UCHAR* buf, SIZE_T len, INT32 cryptoOptions) { - return OvpnCryptoAEADDoWork(TRUE, keySlot, buf, len, buf); + return OvpnCryptoAEADDoWork(TRUE, keySlot, buf, len, buf, cryptoOptions); } _Use_decl_annotations_ NTSTATUS -OvpnCryptoNewKey(OvpnCryptoContext* cryptoContext, POVPN_CRYPTO_DATA cryptoData, BCRYPT_ALG_HANDLE algHandle) +OvpnCryptoNewKey(OvpnCryptoContext* cryptoContext, POVPN_CRYPTO_DATA_V2 cryptoDataV2, BCRYPT_ALG_HANDLE algHandle) { OvpnCryptoKeySlot* keySlot = NULL; NTSTATUS status = STATUS_SUCCESS; + POVPN_CRYPTO_DATA cryptoData = &cryptoDataV2->V1; + if (cryptoData->KeySlot == OVPN_KEY_SLOT::OVPN_KEY_SLOT_PRIMARY) { keySlot = &cryptoContext->Primary; } @@ -237,6 +285,15 @@ OvpnCryptoNewKey(OvpnCryptoContext* cryptoContext, POVPN_CRYPTO_DATA cryptoData, return STATUS_INVALID_DEVICE_REQUEST; } + if (cryptoDataV2->CryptoOptions & CRYPTO_OPTIONS_64BIT_PKTID) + { + cryptoContext->CryptoOptions |= CRYPTO_OPTIONS_64BIT_PKTID; + } + if (cryptoDataV2->CryptoOptions & CRYPTO_OPTIONS_AEAD_TAG_END) + { + cryptoContext->CryptoOptions |= CRYPTO_OPTIONS_AEAD_TAG_END; + } + if ((cryptoData->CipherAlg == OVPN_CIPHER_ALG_AES_GCM) || (cryptoData->CipherAlg == OVPN_CIPHER_ALG_CHACHA20_POLY1305)) { // destroy previous keys if (keySlot->EncKey) { diff --git a/crypto.h b/crypto.h index ee35cba..73d286b 100644 --- a/crypto.h +++ b/crypto.h @@ -29,19 +29,14 @@ #include "uapi\ovpn-dco.h" #include "socket.h" -#define AEAD_CRYPTO_OVERHEAD 24 // 4 + 4 + 16 data_v2 + pktid + auth_tag -#define NONE_CRYPTO_OVERHEAD 8 // 4 + 4 data_v2 + pktid -#define OVPN_PKTID_LEN 4 -#define OVPN_NONCE_TAIL_LEN 8 #define OVPN_DATA_V2_LEN 4 #define AEAD_AUTH_TAG_LEN 16 -#define AES_BLOCK_SIZE 16 -#define AES_GCM_NONCE_LEN 12 // packet opcode (high 5 bits) and key-id (low 3 bits) are combined in one byte #define OVPN_OP_DATA_V2 9 #define OVPN_KEY_ID_MASK 0x07 #define OVPN_OPCODE_SHIFT 3 +#define OVPN_PEER_ID_MASK 0x00FFFFFF struct OvpnCryptoKeySlot { @@ -63,7 +58,7 @@ _IRQL_requires_max_(DISPATCH_LEVEL) _Must_inspect_result_ typedef NTSTATUS -OVPN_CRYPTO_ENCRYPT(_In_ OvpnCryptoKeySlot* keySlot, _In_ UCHAR* buf, _In_ SIZE_T len); +OVPN_CRYPTO_ENCRYPT(_In_ OvpnCryptoKeySlot* keySlot, _In_ UCHAR* buf, _In_ SIZE_T len, _In_ INT32 CryptoOptions); typedef OVPN_CRYPTO_ENCRYPT* POVPN_CRYPTO_ENCRYPT; _Function_class_(OVPN_CRYPTO_DECRYPT) @@ -71,7 +66,7 @@ _IRQL_requires_max_(DISPATCH_LEVEL) _Must_inspect_result_ typedef NTSTATUS -OVPN_CRYPTO_DECRYPT(_In_ OvpnCryptoKeySlot* keySlot, _In_ UCHAR* bufIn, _In_ SIZE_T len, _In_ UCHAR* bufOut); +OVPN_CRYPTO_DECRYPT(_In_ OvpnCryptoKeySlot* keySlot, _In_ UCHAR* bufIn, _In_ SIZE_T len, _In_ UCHAR* bufOut, _In_ INT32 CryptoOptions); typedef OVPN_CRYPTO_DECRYPT* POVPN_CRYPTO_DECRYPT; struct OvpnCryptoContext @@ -82,7 +77,7 @@ struct OvpnCryptoContext POVPN_CRYPTO_ENCRYPT Encrypt; POVPN_CRYPTO_DECRYPT Decrypt; - SIZE_T CryptoOverhead; + INT32 CryptoOptions; }; _Must_inspect_result_ @@ -99,7 +94,7 @@ OvpnCryptoUninit(_In_ OvpnCryptoContext* cryptoContext); _Must_inspect_result_ NTSTATUS -OvpnCryptoNewKey(_In_ OvpnCryptoContext* cryptoContext, _In_ POVPN_CRYPTO_DATA cryptoData, _In_opt_ BCRYPT_ALG_HANDLE algHandle); +OvpnCryptoNewKey(_In_ OvpnCryptoContext* cryptoContext, _In_ POVPN_CRYPTO_DATA_V2 cryptoData, _In_opt_ BCRYPT_ALG_HANDLE algHandle); _Must_inspect_result_ OvpnCryptoKeySlot* @@ -119,4 +114,4 @@ static inline UCHAR OvpnCryptoOpcodeExtract(UCHAR op) { return op >> OVPN_OPCODE_SHIFT; -} \ No newline at end of file +} diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt new file mode 100644 index 0000000..074aa7e --- /dev/null +++ b/gui/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.10) + +project(gui) + +set(CMAKE_CXX_STANDARD 17) # Enable C++17 +set(CMAKE_CXX_STANDARD_REQUIRED YES) # Ensure the compiler strictly requires C++17 + +add_executable (gui WIN32 "gui.cpp") + +add_definitions(-DUNICODE -D_UNICODE) +target_link_libraries(gui PRIVATE user32 gdi32 Ws2_32 Crypt32) diff --git a/gui/gui.cpp b/gui/gui.cpp new file mode 100644 index 0000000..453737e --- /dev/null +++ b/gui/gui.cpp @@ -0,0 +1,717 @@ +#include "..\uapi\ovpn-dco.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define DEV_NAME L"\\\\.\\ovpn-dco" +#define VER_DEV_NAME L"\\\\.\\ovpn-dco-ver" + +#define BTN_SEND_CC 100 +#define BTN_SUBSCRIBE_NOTIF 101 + +LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM); + +HWND hMPListenAddress, hMPListenPort, + hP2PLocalAddress, hP2PLocalPort, + hP2PRemoteAddress, hP2PRemotePort, + hCCMessage, hCCRemoteAddress, hCCRemotePort, + hMPNewPeerLocalIP, hMPNewPeerLocalPort, hMPNewPeerRemoteIP, hMPNewPeerRemotePort, hMPNewPeerVPNIP, hMPNewPeerPeerId, + hNewKeyPeerId, + hSetPeerPeerId, hSetPeerInterval, hSetPeerTimeout, hSetPeerMSS; + +HWND hLogArea; +std::unordered_map buttons = { + {OVPN_IOCTL_NEW_PEER, L"P2P New Peer"}, + {OVPN_IOCTL_GET_STATS, L"Get Stats"}, + {OVPN_IOCTL_NEW_KEY, L"New Key"}, + {OVPN_IOCTL_SWAP_KEYS, L"Swap Keys"}, + {OVPN_IOCTL_SET_PEER, L"Set Peer"}, + {OVPN_IOCTL_START_VPN, L"P2P Start VPN"}, + {OVPN_IOCTL_DEL_PEER, L"Del Peer"}, + {OVPN_IOCTL_GET_VERSION, L"Get Version"}, + {OVPN_IOCTL_NEW_KEY_V2, L"New Key V2"}, + {OVPN_IOCTL_SET_MODE, L"Set Mode"}, + {OVPN_IOCTL_MP_START_VPN, L"MP Start VPN"}, + {OVPN_IOCTL_MP_NEW_PEER, L"MP New Peer"}, + {OVPN_IOCTL_NEW_KEY, L"New Key"}, + {OVPN_IOCTL_SET_PEER, L"Set Peer"} +}; + +#define MIN_FUNCTION_CODE 1 +#define MAX_FUNCTION_CODE 20 + +#define GET_IOCTL_FUNCTION_CODE(ioctl) (((ioctl) >> 2) & 0xFFF) + +unsigned long GetIoctlFromFunctionCode(unsigned long functionCode) { + return CTL_CODE(FILE_DEVICE_UNKNOWN, functionCode, METHOD_BUFFERED, FILE_ANY_ACCESS); +} + +std::vector> modeData = { + {OVPN_MODE_P2P, L"P2P"}, + {OVPN_MODE_MP, L"MP"} +}; + +std::vector hModes; + +std::vector notifCmds = { + "peer deleted", + "key rotation" +}; + +std::vector delPeerReasons = { + "teardown", + "userspace", + "expired", + "trtansport error", + "transport disconnect" +}; + +template +void Log(Args... args) { + std::wstringstream stream; + + // Using a fold expression to insert all arguments into the stream + (stream << ... << args); + + // Move the caret to the end of the text + int textLength = GetWindowTextLength(hLogArea); + SendMessage(hLogArea, EM_SETSEL, (WPARAM)textLength, (LPARAM)textLength); + + // Add a newline character before the new text (if needed) + std::wstring textToAppend = (textLength > 0 ? L"\r\n" : L"") + stream.str(); + + // Insert the new text at the current caret position + SendMessage(hLogArea, EM_REPLACESEL, FALSE, (LPARAM)textToAppend.c_str()); +} + +HANDLE hDev; +char readBuffer[4096] = {0}; +OVERLAPPED ovRead = {0}, ovWrite = {0}, ovNotif = {0}; + +bool StartOverlappedRead() { + ZeroMemory(readBuffer, sizeof(readBuffer)); + BOOL result = ReadFile(hDev, readBuffer, sizeof(readBuffer), NULL, &ovRead); + if (!result && GetLastError() != ERROR_IO_PENDING) { + Log("ReadFile failed: ", GetLastError()); + return false; + } + return true; +} + +OVPN_NOTIFY_EVENT notifEvent = {0}; + +bool StartOverlappedNotif() { + if (!DeviceIoControl(hDev, OVPN_IOCTL_NOTIFY_EVENT, NULL, 0, ¬ifEvent, sizeof(notifEvent), NULL, &ovNotif)) { + if (GetLastError() != ERROR_IO_PENDING) { + Log("Failed to start notification read: ", GetLastError()); + return false; + } + } + return true; +} + +bool OnReadCompleted() +{ + DWORD bytesRead; + if (GetOverlappedResult(hDev, &ovRead, &bytesRead, FALSE)) { + if (bytesRead > 0) { + bool mp = SendMessage(hModes[1], BM_GETCHECK, 0, 0) == BST_CHECKED; + + // if we're in server mode, we've received CC message prepended with sockaddr + if (mp) { + SOCKADDR_IN *sa = (SOCKADDR_IN *)readBuffer; + + char ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(sa->sin_addr), ip, sizeof(ip)); + + int port = ntohs(sa->sin_port); + + Log("CC[", ip, ":", port, "]> ", readBuffer + sizeof(*sa)); + } else { + Log("CC[]> ", readBuffer); + } + } + } else { + Log("Overlapped read failed: ", GetLastError()); + } + + return StartOverlappedRead(); +} + +bool OnNotifyCompleted() +{ + DWORD bytesRead; + if (GetOverlappedResult(hDev, &ovNotif, &bytesRead, FALSE)) { + if (bytesRead > 0) { + Log("Notification: ", + "Cmd: ", notifCmds[notifEvent.Cmd], + ", peer-id: ", notifEvent.PeerId, + ", del reason: ", delPeerReasons[notifEvent.DelPeerReason]); + } + } else { + Log("Notif read failed: ", GetLastError()); + } + + return StartOverlappedNotif(); +} + +void OnWriteCompleted() +{ + DWORD bytesWrote; + if (GetOverlappedResult(hDev, &ovWrite, &bytesWrote, FALSE)) { + if (bytesWrote > 0) { + Log("Wrote ", bytesWrote, " bytes"); + } + } else { + Log("Overlapped write failed: ", GetLastError()); + } +} + +int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR args, int ncmdshow) +{ + // Create a Window Class + WNDCLASSW wc = {0}; + + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hInstance = hInst; + wc.lpszClassName = L"myWindowClass"; + wc.lpfnWndProc = WindowProcedure; + + // Register the Window Class + if (!RegisterClassW(&wc)) + return -1; + + // Create the Window + HWND hwnd = CreateWindowW(L"myWindowClass", L"ovpn-dco-win GUI", WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 900, 600, NULL, NULL, NULL, NULL); + + HANDLE hEvRead = CreateEventW(NULL, FALSE, FALSE, NULL); + ovRead.hEvent = hEvRead; + + HANDLE hEvWrite = CreateEventW(NULL, FALSE, FALSE, NULL); + ovWrite.hEvent = hEvWrite; + + HANDLE hEvNotif = CreateEventW(NULL, FALSE, FALSE, NULL); + ovNotif.hEvent = hEvNotif; + + StartOverlappedRead(); + + while (true) { + HANDLE events[] = { hEvRead, hEvWrite, hEvNotif }; + DWORD waitResult = MsgWaitForMultipleObjects(3, events, FALSE, INFINITE, QS_ALLINPUT); + + if (waitResult == WAIT_OBJECT_0) { + if (!OnReadCompleted()) { + break; + } + } if (waitResult == WAIT_OBJECT_0 + 1) { + OnWriteCompleted(); + } if (waitResult == WAIT_OBJECT_0 + 2) { + if (!OnNotifyCompleted()) { + break; + } + } + else if (waitResult == WAIT_OBJECT_0 + 3) { + // window messaging loop + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + CloseHandle(hDev); + CloseHandle(hEvRead); + CloseHandle(hEvWrite); + return 0; + } + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + + return 0; +} + +void OpenDevice(const std::wstring& devName) +{ + hDev = CreateFileW(devName.c_str(), GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, NULL); + if (hDev == INVALID_HANDLE_VALUE) { + Log(L"CreateFile(", devName, ") failed with code ", GetLastError()); + } + else{ + Log(L"Device ", devName, " opened: ", hDev); + } +} + +void DcoGetVersion() +{ + // try version device + HANDLE h = CreateFileW(VER_DEV_NAME, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); + if (h == INVALID_HANDLE_VALUE) { + h = hDev; + } + + OVPN_VERSION v{}; + DWORD bytesReturned; + + if (!DeviceIoControl(h, OVPN_IOCTL_GET_VERSION, NULL, 0, &v, sizeof(v), &bytesReturned, NULL)) { + Log("DeviceIoControl(OVPN_IOCTL_GET_VERSION) failed with code ", GetLastError()); + } + else { + Log("Version: ", v.Major, ".", v.Minor, ".", v.Patch); + } +} + +void SetMode() +{ + OVPN_MODE m; + + if (SendMessage(hModes[0], BM_GETCHECK, 0, 0) == BST_CHECKED) { + m = OVPN_MODE_P2P; + } else { + m = OVPN_MODE_MP; + } + + DWORD bytesReturned; + if (!DeviceIoControl(hDev, OVPN_IOCTL_SET_MODE, &m, sizeof(m), NULL, 0, &bytesReturned, NULL)) { + Log("DeviceIoControl(OVPN_IOCTL_SET_MODE) failed with code ", GetLastError()); + } + else { + Log("Mode set: ", m); + } +} + +// Function to convert sockaddr_in to a wstring containing IP and port +std::wstring sockAddrToString(const sockaddr_in& addr) { + wchar_t ipAddress[INET_ADDRSTRLEN]; // Buffer to hold the IP address + + // Convert the binary IP address to a string (wide-char) + InetNtopW(AF_INET, &(addr.sin_addr), ipAddress, INET_ADDRSTRLEN); + + // Convert the port number from network byte order to host byte order + int port = ntohs(addr.sin_port); + + // Convert port to wstring and concatenate with IP address + std::wstring result = ipAddress; + result += L":"; + result += std::to_wstring(port); // Append the port number + + return result; +} + +void MPStartVPN() +{ + wchar_t ipAddress[16]; // Buffer to store IP address + wchar_t portNumber[6]; // Buffer to store port number + + // Get the content of the IP address edit box + GetWindowText(hMPListenAddress, ipAddress, 16); + + // Get the content of the port number edit box + GetWindowText(hMPListenPort, portNumber, 6); + + // Initialize sockaddr_in structure + sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; // IPv4 + + // Convert IP address string to binary form using InetPtonW + InetPtonW(AF_INET, ipAddress, &(addr.sin_addr)); + + // Convert port number string to integer and set it + int port = _wtoi(portNumber); // _wtoi for wide character conversion + addr.sin_port = htons(port); // Convert port to network byte order + + OVPN_MP_START_VPN in, out; + in.ListenAddress.Addr4 = addr; + + DWORD bytesReturned; + if (!DeviceIoControl(hDev, OVPN_IOCTL_MP_START_VPN, &in, sizeof(in), &out, sizeof(out), &bytesReturned, NULL)) { + Log("DeviceIoControl(OVPN_IOCTL_MP_START_VPN) failed with code ", GetLastError()); + } + else { + Log("MP Start VPN: Listen on ", sockAddrToString(out.ListenAddress.Addr4)); + } +} + +void P2PNewPeer() +{ + wchar_t localAddress[16], remoteAddress[16]; + wchar_t localPort[6], remotePort[6]; + + GetWindowText(hP2PLocalAddress, localAddress, 16); + GetWindowText(hP2PLocalPort, localPort, 6); + GetWindowText(hP2PRemoteAddress, remoteAddress, 16); + GetWindowText(hP2PRemotePort, remotePort, 6); + + sockaddr_in localAddr; + memset(&localAddr, 0, sizeof(localAddr)); + localAddr.sin_family = AF_INET; + InetPtonW(AF_INET, localAddress, &(localAddr.sin_addr)); + localAddr.sin_port = htons(_wtoi(localPort)); + + sockaddr_in remoteAddr; + memset(&remoteAddr, 0, sizeof(remoteAddr)); + remoteAddr.sin_family = AF_INET; + InetPtonW(AF_INET, remoteAddress, &(remoteAddr.sin_addr)); + remoteAddr.sin_port = htons(_wtoi(remotePort)); + + OVPN_NEW_PEER newPeer; + newPeer.Local.Addr4 = localAddr; + newPeer.Remote.Addr4 = remoteAddr; + newPeer.Proto = OVPN_PROTO_UDP; + + DWORD bytesReturned; + if (!DeviceIoControl(hDev, OVPN_IOCTL_NEW_PEER, &newPeer, sizeof(newPeer), NULL, 0, &bytesReturned, NULL)) { + Log("DeviceIoControl(OVPN_IOCTL_NEW_PEER) failed with code ", GetLastError()); + } + else { + Log("P2P peer added"); + } +} + +void P2PStartVPN() +{ + DWORD bytesReturned; + if (!DeviceIoControl(hDev, OVPN_IOCTL_START_VPN, NULL, 0, NULL, 0, &bytesReturned, NULL)) { + Log("DeviceIoControl(OVPN_IOCTL_START_VPN) failed with code ", GetLastError()); + } + else { + Log("P2P VPN Started"); + } +} + +void MPNewPeer() +{ + wchar_t localIP[16], localPort[6]; + wchar_t remoteIP[16], remotePort[6], vpnIP[16]; + wchar_t peerId[6]; + + GetWindowText(hMPNewPeerLocalIP, localIP, 16); + GetWindowText(hMPNewPeerLocalPort, localPort, 6); + GetWindowText(hMPNewPeerRemoteIP, remoteIP, 16); + GetWindowText(hMPNewPeerRemotePort, remotePort, 6); + GetWindowText(hMPNewPeerVPNIP, vpnIP, 16); + GetWindowText(hMPNewPeerPeerId, peerId, 6); + + sockaddr_in localAddr = {}; + localAddr.sin_family = AF_INET; + InetPtonW(AF_INET, localIP, &(localAddr.sin_addr)); + localAddr.sin_port = htons(_wtoi(localPort)); + + sockaddr_in remoteAddr = {}; + remoteAddr.sin_family = AF_INET; + InetPtonW(AF_INET, remoteIP, &(remoteAddr.sin_addr)); + remoteAddr.sin_port = htons(_wtoi(remotePort)); + + in_addr vpnAddress; + InetPtonW(AF_INET, vpnIP, &vpnAddress); + + OVPN_MP_NEW_PEER newPeer = {}; + newPeer.Local.Addr4 = localAddr; + newPeer.Remote.Addr4 = remoteAddr; + newPeer.VpnAddr4 = vpnAddress; + newPeer.PeerId = _wtoi(peerId); + + DWORD bytesReturned; + if (!DeviceIoControl(hDev, OVPN_IOCTL_MP_NEW_PEER, &newPeer, sizeof(newPeer), NULL, 0, &bytesReturned, NULL)) { + Log("DeviceIoControl(OVPN_IOCTL_MP_NEW_PEER) failed with code ", GetLastError()); + } + else { + Log("MP peer added"); + } +} + +void +NewKey() +{ + wchar_t peerId[6]; + GetWindowText(hNewKeyPeerId, peerId, 6); + + std::ifstream file("data64.key"); + if (!file) return; + + std::string b64str{(std::istreambuf_iterator(file)), std::istreambuf_iterator()}; + + DWORD binarySize = 0; + if (!CryptStringToBinaryA(b64str.c_str(), 0, CRYPT_STRING_BASE64, nullptr, &binarySize, nullptr, nullptr)) + return; + + std::vector buf(binarySize); + if (!CryptStringToBinaryA(b64str.c_str(), 0, CRYPT_STRING_BASE64, buf.data(), &binarySize, nullptr, nullptr)) + return; + + OVPN_CRYPTO_DATA crypto_data = {}; + constexpr int keyLen = sizeof(crypto_data.Encrypt.Key); + + bool mp = SendMessage(hModes[1], BM_GETCHECK, 0, 0) == BST_CHECKED; + bool keyDir = mp ? 1 : 0; + if (keyDir) { + CopyMemory(crypto_data.Encrypt.Key, buf.data() + keyLen, keyLen); + CopyMemory(crypto_data.Decrypt.Key, buf.data(), keyLen); + } + else { + CopyMemory(crypto_data.Encrypt.Key, buf.data(), keyLen); + CopyMemory(crypto_data.Decrypt.Key, buf.data() + keyLen, keyLen); + } + + crypto_data.Encrypt.KeyLen = keyLen; // hardcode 256bit key size + crypto_data.Decrypt.KeyLen = keyLen; // hardcode 256bit key size + + constexpr int nonceTailLen = sizeof(crypto_data.Encrypt.NonceTail); + // for test purposes decrypt and encrypt nonces are same + CopyMemory(crypto_data.Encrypt.NonceTail, buf.data() + keyLen * 2, nonceTailLen); + CopyMemory(crypto_data.Decrypt.NonceTail, buf.data() + keyLen * 2, nonceTailLen); + + crypto_data.CipherAlg = OVPN_CIPHER_ALG::OVPN_CIPHER_ALG_AES_GCM; + crypto_data.PeerId = _wtoi(peerId); + + DWORD bytesReturned; + if (!DeviceIoControl(hDev, OVPN_IOCTL_NEW_KEY, &crypto_data, sizeof(crypto_data), NULL, 0, &bytesReturned, NULL)) { + Log("DeviceIoControl(OVPN_IOCTL_NEW_KEY) failed with code ", GetLastError()); + } + else { + Log("New key added"); + } +} + +void +SetPeer() +{ + wchar_t peerId[6], interval[6], timeout[6], mss[6]; + + GetWindowText(hSetPeerPeerId, peerId, 16); + GetWindowText(hSetPeerInterval, interval, 16); + GetWindowText(hSetPeerTimeout, timeout, 16); + GetWindowText(hSetPeerMSS, mss, 16); + + bool mp = SendMessage(hModes[1], BM_GETCHECK, 0, 0) == BST_CHECKED; + + if (mp) { + OVPN_MP_SET_PEER set_peer = {}; + set_peer.PeerId = _wtoi(peerId); + set_peer.KeepaliveInterval = _wtoi(interval); + set_peer.KeepaliveTimeout = _wtoi(timeout); + set_peer.MSS = _wtoi(mss); + + DWORD bytesReturned; + if (!DeviceIoControl(hDev, OVPN_IOCTL_MP_SET_PEER, &set_peer, sizeof(set_peer), NULL, 0, &bytesReturned, NULL)) { + Log("DeviceIoControl(OVPN_IOCTL_MP_SET_PEER) failed with code ", GetLastError()); + } + else { + Log("MP Peer set", peerId); + } + } else { + OVPN_SET_PEER set_peer = {}; + set_peer.KeepaliveInterval = _wtoi(interval); + set_peer.KeepaliveTimeout = _wtoi(timeout); + set_peer.MSS = _wtoi(mss); + + DWORD bytesReturned; + if (!DeviceIoControl(hDev, OVPN_IOCTL_SET_PEER, &set_peer, sizeof(set_peer), NULL, 0, &bytesReturned, NULL)) { + Log("DeviceIoControl(OVPN_IOCTL_SET_PEER) failed with code ", GetLastError()); + } + else { + Log("Peer set"); + } + } + + +} + +void +CreatePushButton(HWND hWnd, DWORD ioctl, int x, int y) +{ + CreateWindowW(L"Button", buttons[ioctl].c_str(), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, x, y, 100, 30, + hWnd, (HMENU)(INT_PTR)(GET_IOCTL_FUNCTION_CODE(ioctl)), NULL, NULL); +} + +void +CreatePushButton(HWND hWnd, wchar_t* title, HMENU hMenu, int x, int y) +{ + CreateWindowW(L"Button", title, WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, x, y, 100, 30, hWnd, hMenu, NULL, NULL); +} + +HWND +CreateEditBox(HWND hWnd, WCHAR* text, int x, int y, int width) +{ + return CreateWindowW(L"Edit", text, WS_VISIBLE | WS_CHILD | WS_BORDER | ES_LEFT, x, y, width, 20, hWnd, NULL, NULL, NULL); +} + +void +SendCC() +{ + bool mp = SendMessage(hModes[1], BM_GETCHECK, 0, 0) == BST_CHECKED; + + sockaddr_in sa; + char text[1024], remoteAddress[16], remotePort[6]; + GetWindowTextA(hCCMessage, text, 1024); + GetWindowTextA(hCCRemoteAddress, remoteAddress, 16); + GetWindowTextA(hCCRemotePort, remotePort, 6); + + char data[1024]; + DWORD dataLen = (DWORD)strlen(text); + if (mp) { + // in multipeer, we prepend CC message with sockaddr + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + InetPtonA(AF_INET, remoteAddress, &(sa.sin_addr)); + sa.sin_port = htons(atoi(remotePort)); + + // prepend with sockaddr + memcpy(data, &sa, sizeof(sa)); + memcpy(data + sizeof(sa), text, strlen(text)); + + dataLen += sizeof(sa); + } else { + memcpy(data, text, strlen(text)); + } + + DWORD bytesWritten = 0; + BOOL res = WriteFile(hDev, data, dataLen, &bytesWritten, &ovWrite); + if (!res && GetLastError() != ERROR_IO_PENDING) { + Log("WriteFile failed: ", GetLastError()); + } +} + +// Window Procedure Function +LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) +{ + switch (msg) { + case WM_CREATE: + CreatePushButton(hwnd, OVPN_IOCTL_GET_VERSION, 10, 10); + CreatePushButton(hwnd, OVPN_IOCTL_SET_MODE, 150, 10); + + for (auto i = 0; i < modeData.size(); ++i) { + auto style = WS_VISIBLE | WS_CHILD | BS_AUTORADIOBUTTON; + if (i == 0) style |= WS_GROUP; + auto hMode = CreateWindowW(L"Button", modeData[i].second.c_str(), style, + 270 + 50 * i, 10, 50, 30, hwnd, (HMENU)(INT_PTR)(1000 + modeData[i].first), NULL, NULL); + hModes.push_back(hMode); + } + + CreatePushButton(hwnd, OVPN_IOCTL_MP_START_VPN, 10, 60); + hMPListenAddress = CreateEditBox(hwnd, L"0.0.0.0", 150, 60, 120); + hMPListenPort = CreateEditBox(hwnd, L"1194", 290, 60, 60); + + CreatePushButton(hwnd, OVPN_IOCTL_NEW_PEER, 10, 110); + hP2PLocalAddress = CreateEditBox(hwnd, L"192.168.100.1", 150, 110, 120); + hP2PLocalPort = CreateEditBox(hwnd, L"1194", 290, 110, 60); + hP2PRemoteAddress = CreateEditBox(hwnd, L"192.168.100.2", 400, 110, 120); + hP2PRemotePort = CreateEditBox(hwnd, L"1194", 540, 110, 60); + + CreatePushButton(hwnd, OVPN_IOCTL_START_VPN, 640, 110); + + CreatePushButton(hwnd, L"Send CC", (HMENU)BTN_SEND_CC, 10, 160); + hCCMessage = CreateEditBox(hwnd, L"hello, dco-win", 150, 160, 120); + hCCRemoteAddress = CreateEditBox(hwnd, L"192.168.100.1", 290, 160, 120); + hCCRemotePort = CreateEditBox(hwnd, L"1194", 430, 160, 60); + + CreatePushButton(hwnd, L"Subscribe notif", (HMENU)BTN_SUBSCRIBE_NOTIF, 640, 160); + + CreatePushButton(hwnd, OVPN_IOCTL_MP_NEW_PEER, 10, 210); + hMPNewPeerLocalIP = CreateEditBox(hwnd, L"192.168.100.2", 150, 210, 120); + hMPNewPeerLocalPort = CreateEditBox(hwnd, L"1194", 290, 210, 60); + hMPNewPeerRemoteIP = CreateEditBox(hwnd, L"192.168.100.1", 400, 210, 120); + hMPNewPeerRemotePort = CreateEditBox(hwnd, L"1194", 540, 210, 60); + hMPNewPeerVPNIP = CreateEditBox(hwnd, L"10.8.0.6", 650, 210, 120); + hMPNewPeerPeerId = CreateEditBox(hwnd, L"1", 790, 210, 60); + + CreatePushButton(hwnd, OVPN_IOCTL_NEW_KEY, 10, 260); + hNewKeyPeerId = CreateEditBox(hwnd, L"1", 150, 260, 60); + + CreatePushButton(hwnd, OVPN_IOCTL_SET_PEER, 10, 310); + hSetPeerPeerId = CreateEditBox(hwnd, L"1", 150, 310, 60); + hSetPeerInterval = CreateEditBox(hwnd, L"5", 240, 310, 60); + hSetPeerTimeout = CreateEditBox(hwnd, L"30", 330, 310, 60); + hSetPeerMSS = CreateEditBox(hwnd, L"-1", 420, 310, 60); + + SendMessage(hModes[0], BM_SETCHECK, BST_CHECKED, 0); + + // log area + hLogArea = CreateWindowW(L"Edit", L"", + WS_VISIBLE | WS_CHILD | WS_BORDER | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | + WS_VSCROLL | WS_HSCROLL | ES_READONLY, + 0, 0, 600, 100, hwnd, (HMENU)3, NULL, NULL); + + OpenDevice(DEV_NAME); + + break; + + case WM_COMMAND: + { + if ((wp >= MIN_FUNCTION_CODE) && (wp < MAX_FUNCTION_CODE)) + { + auto ioctl = GetIoctlFromFunctionCode((ULONG)wp); + + switch (ioctl) { + case OVPN_IOCTL_GET_VERSION: + DcoGetVersion(); + break; + + case OVPN_IOCTL_SET_MODE: + SetMode(); + break; + + case OVPN_IOCTL_MP_START_VPN: + MPStartVPN(); + break; + + case OVPN_IOCTL_NEW_PEER: + P2PNewPeer(); + break; + + case OVPN_IOCTL_START_VPN: + P2PStartVPN(); + break; + + case OVPN_IOCTL_MP_NEW_PEER: + MPNewPeer(); + break; + + case OVPN_IOCTL_NEW_KEY: + NewKey(); + break; + + case OVPN_IOCTL_SET_PEER: + SetPeer(); + } + } + else if ((ULONG)wp == BTN_SEND_CC) { + SendCC(); + } else if ((ULONG)wp == BTN_SUBSCRIBE_NOTIF) { + StartOverlappedNotif(); + } + + } + + break; + + case WM_SIZE: + { + // Get the new width and height of the window + int width = LOWORD(lp); + int height = HIWORD(lp); + + // Resize the edit control (log area) to be at the bottom + SetWindowPos(hLogArea, NULL, 0, height - 110, width, 100, SWP_NOZORDER); + } + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + default: + return DefWindowProcW(hwnd, msg, wp, lp); + } + return 0; +} \ No newline at end of file diff --git a/notifyqueue.cpp b/notifyqueue.cpp new file mode 100644 index 0000000..95d9d93 --- /dev/null +++ b/notifyqueue.cpp @@ -0,0 +1,86 @@ +/* + * ovpn-dco-win OpenVPN protocol accelerator for Windows + * + * Copyright (C) 2024- OpenVPN Inc + * + * Author: Lev Stipakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "notifyqueue.h" + +#include "trace.h" + +VOID +NotifyQueue::Init() +{ + LOG_ENTER(); + + InitializeListHead(&Head); + KeInitializeSpinLock(&Lock); + + LOG_EXIT(); +} + +NTSTATUS +NotifyQueue::AddEvent(OVPN_NOTIFY_CMD cmd, int peerId, OVPN_DEL_PEER_REASON delPeerReason) +{ + NotifyEvent* event = (NotifyEvent*)ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(NotifyEvent), 'ovpn'); + if (!event) { + return STATUS_MEMORY_NOT_ALLOCATED; + } + + RtlZeroMemory(event, sizeof(NotifyEvent)); + + event->Cmd = cmd; + event->PeerId = peerId; + event->DelPeerReason = delPeerReason; + + ExInterlockedInsertTailList(&Head, &event->ListEntry, &Lock); + + return STATUS_SUCCESS; +} + +NotifyEvent* +NotifyQueue::GetEvent() +{ + PLIST_ENTRY entry = ExInterlockedRemoveHeadList(&Head, &Lock); + if (entry == nullptr) { + return nullptr; + } + + return CONTAINING_RECORD(entry, NotifyEvent, ListEntry); +} + +VOID +NotifyQueue::FreeEvent(NotifyEvent* event) +{ + if (event != nullptr) { + ExFreePoolWithTag(event, 'ovpn'); + } +} + +VOID +NotifyQueue::FlushEvents() +{ + LOG_ENTER(); + + NotifyEvent* event = nullptr; + while ((event = GetEvent()) != nullptr) { + FreeEvent(event); + } + + LOG_EXIT(); +} diff --git a/notifyqueue.h b/notifyqueue.h new file mode 100644 index 0000000..f1b0733 --- /dev/null +++ b/notifyqueue.h @@ -0,0 +1,53 @@ +/* + * ovpn-dco-win OpenVPN protocol accelerator for Windows + * + * Copyright (C) 2024- OpenVPN Inc + * + * Author: Lev Stipakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include + +#include "uapi/ovpn-dco.h" + +struct NotifyEvent { + LIST_ENTRY ListEntry; + + OVPN_NOTIFY_CMD Cmd; + int PeerId; + OVPN_DEL_PEER_REASON DelPeerReason; +}; + +class NotifyQueue { +private: + LIST_ENTRY Head; + KSPIN_LOCK Lock; + +public: + NotifyQueue() = delete; + + VOID Init(); + + NTSTATUS AddEvent(OVPN_NOTIFY_CMD cmd, int peerId, OVPN_DEL_PEER_REASON delPeerReason=OVPN_DEL_PEER_REASON_EXPIRED); + + NotifyEvent* GetEvent(); + + VOID FreeEvent(NotifyEvent* event); + + VOID FlushEvents(); +}; diff --git a/ovpn-dco-win.vcxproj b/ovpn-dco-win.vcxproj index 3fc5a88..42c635a 100644 --- a/ovpn-dco-win.vcxproj +++ b/ovpn-dco-win.vcxproj @@ -69,9 +69,11 @@ + + @@ -82,10 +84,12 @@ + + @@ -437,10 +441,10 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) @@ -453,10 +457,10 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) @@ -469,7 +473,7 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) false @@ -478,7 +482,7 @@ $(ProjectDir)$(Platform)\$(ConfigurationName)\ovpn-dco.DVL.XML - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) /Brepro %(AdditionalOptions) DebugFull false @@ -496,7 +500,7 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) false @@ -505,7 +509,7 @@ $(ProjectDir)$(Platform)\$(ConfigurationName)\ovpn-dco.DVL.XML - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) /Brepro %(AdditionalOptions) DebugFull false @@ -523,10 +527,10 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) @@ -547,10 +551,10 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) @@ -571,7 +575,7 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) false @@ -580,7 +584,7 @@ $(ProjectDir)$(Platform)\$(ConfigurationName)\ovpn-dco.DVL.XML - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) /Brepro %(AdditionalOptions) DebugFull false @@ -598,7 +602,7 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) false @@ -607,7 +611,7 @@ $(ProjectDir)$(Platform)\$(ConfigurationName)\ovpn-dco.DVL.XML - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) /Brepro %(AdditionalOptions) DebugFull false @@ -625,10 +629,10 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) @@ -641,10 +645,10 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) @@ -657,11 +661,11 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) false - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) /Brepro %(AdditionalOptions) DebugFull false @@ -679,11 +683,11 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) false - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) /Brepro %(AdditionalOptions) DebugFull false @@ -701,7 +705,7 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) false @@ -710,7 +714,7 @@ $(ProjectDir)$(Platform)\$(ConfigurationName)\ovpn-dco.DVL.XML - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) /Brepro %(AdditionalOptions) DebugFull false @@ -728,7 +732,7 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) false @@ -737,7 +741,7 @@ $(ProjectDir)$(Platform)\$(ConfigurationName)\ovpn-dco.DVL.XML - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) /Brepro %(AdditionalOptions) DebugFull false @@ -755,10 +759,10 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) @@ -771,10 +775,10 @@ true %(AdditionalIncludeDirectories) - OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) + RTL_USE_AVL_TABLES=0;OVPN_DCO_VERSION_MAJOR=$(OVPN_DCO_VERSION_MAJOR);OVPN_DCO_VERSION_MINOR=$(OVPN_DCO_VERSION_MINOR);OVPN_DCO_VERSION_PATCH=$(OVPN_DCO_VERSION_PATCH);OVPN_DCO_VERSION_STR=$(OVPN_DCO_VERSION_MAJOR).$(OVPN_DCO_VERSION_MINOR).$(OVPN_DCO_VERSION_PATCH);NETADAPTER_VERSION_MAJOR=$(NETADAPTER_VERSION_MAJOR);NETADAPTER_VERSION_MINOR=$(NETADAPTER_VERSION_MINOR);%(PreprocessorDefinitions) - uuid.lib;Netio.lib;cng.lib;%(AdditionalDependencies) + uuid.lib;Netio.lib;cng.lib;wdmsec.lib%(AdditionalDependencies) diff --git a/ovpn-dco-win.vcxproj.filters b/ovpn-dco-win.vcxproj.filters index a4603a6..7d0f4cf 100644 --- a/ovpn-dco-win.vcxproj.filters +++ b/ovpn-dco-win.vcxproj.filters @@ -67,6 +67,9 @@ Header Files + + Header Files + @@ -102,6 +105,9 @@ Source Files + + Source Files + diff --git a/peer.cpp b/peer.cpp index a7cdd07..fddcb7c 100644 --- a/peer.cpp +++ b/peer.cpp @@ -35,23 +35,38 @@ OvpnPeerCtxAlloc() if (peer != NULL) { RtlZeroMemory(peer, sizeof(OvpnPeerContext)); } + InterlockedIncrement(&peer->RefCounter); return peer; } +_Use_decl_annotations_ +VOID +OvpnPeerCtxRelease(OvpnPeerContext* peer) +{ + if (InterlockedDecrement(&peer->RefCounter) == 0) { + auto peerId = peer->PeerId; + OvpnPeerCtxFree(peer); + LOG_INFO("Peer freed", TraceLoggingValue(peerId, "peer-id")); + } +} + _Use_decl_annotations_ VOID OvpnPeerCtxFree(OvpnPeerContext* peer) { + auto irql = ExAcquireSpinLockExclusive(&peer->SpinLock); + OvpnCryptoUninit(&peer->CryptoContext); - OvpnTimerDestroy(&peer->KeepaliveXmitTimer); - OvpnTimerDestroy(&peer->KeepaliveRecvTimer); + OvpnTimerDestroy(&peer->Timer); + + ExReleaseSpinLockExclusive(&peer->SpinLock, irql); ExFreePoolWithTag(peer, 'ovpn'); } _Use_decl_annotations_ PVOID -OvpnPeerAllocateRoutine(_RTL_GENERIC_TABLE* table, CLONG size) +OvpnPeerAllocateRoutine(RTL_GENERIC_TABLE* table, CLONG size) { UNREFERENCED_PARAMETER(table); @@ -60,20 +75,61 @@ OvpnPeerAllocateRoutine(_RTL_GENERIC_TABLE* table, CLONG size) _Use_decl_annotations_ VOID -OvpnPeerFreeRoutine(_RTL_GENERIC_TABLE* table, PVOID buffer) +OvpnPeerFreeRoutine(RTL_GENERIC_TABLE* table, PVOID buffer) { UNREFERENCED_PARAMETER(table); ExFreePoolWithTag(buffer, 'ovpn'); } -RTL_GENERIC_COMPARE_RESULTS OvpnPeerCompareByPeerIdRoutine(_RTL_GENERIC_TABLE* table, PVOID first, PVOID second) +RTL_GENERIC_COMPARE_RESULTS +OvpnPeerCompareByPeerIdRoutine(RTL_GENERIC_TABLE* table, PVOID first, PVOID second) { UNREFERENCED_PARAMETER(table); - UNREFERENCED_PARAMETER(first); - UNREFERENCED_PARAMETER(second); - return GenericEqual; + OvpnPeerContext* peer1 = *(OvpnPeerContext**)first; + OvpnPeerContext* peer2 = *(OvpnPeerContext**)second; + + if (peer1->PeerId == peer2->PeerId) + return GenericEqual; + else if (peer1->PeerId < peer2->PeerId) + return GenericLessThan; + else + return GenericGreaterThan; +} + +RTL_GENERIC_COMPARE_RESULTS +OvpnPeerCompareByVPN4Routine(RTL_GENERIC_TABLE* table, PVOID first, PVOID second) +{ + UNREFERENCED_PARAMETER(table); + + OvpnPeerContext* peer1 = *(OvpnPeerContext**)first; + OvpnPeerContext* peer2 = *(OvpnPeerContext**)second; + + int n = memcmp(&peer1->VpnAddrs.IPv4, &peer2->VpnAddrs.IPv4, sizeof(IN_ADDR)); + if (n == 0) + return GenericEqual; + else if (n < 0) + return GenericLessThan; + else + return GenericGreaterThan; +} + +RTL_GENERIC_COMPARE_RESULTS +OvpnPeerCompareByVPN6Routine(RTL_GENERIC_TABLE* table, PVOID first, PVOID second) +{ + UNREFERENCED_PARAMETER(table); + + OvpnPeerContext* peer1 = *(OvpnPeerContext**)first; + OvpnPeerContext* peer2 = *(OvpnPeerContext**)second; + + int n = memcmp(&peer1->VpnAddrs.IPv6, &peer2->VpnAddrs.IPv6, sizeof(IN6_ADDR)); + if (n == 0) + return GenericEqual; + else if (n < 0) + return GenericLessThan; + else + return GenericGreaterThan; } static @@ -102,59 +158,70 @@ OvpnPeerNew(POVPN_DEVICE device, WDFREQUEST request) POVPN_NEW_PEER peer = NULL; NTSTATUS status; - GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveInputBuffer(request, sizeof(OVPN_NEW_PEER), (PVOID*)&peer, nullptr)); - KIRQL kirql = ExAcquireSpinLockExclusive(&device->SpinLock); - const BOOLEAN peerExists = OvpnHasPeers(device); - ExReleaseSpinLockExclusive(&device->SpinLock, kirql); - if (peerExists) { + auto peerCtx = OvpnGetFirstPeer(device); + if (peerCtx != nullptr) { LOG_WARN("Peer already exists"); status = STATUS_OBJECTID_EXISTS; goto done; } + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveInputBuffer(request, sizeof(OVPN_NEW_PEER), (PVOID*)&peer, nullptr)); + + if ((peer->Remote.Addr4.sin_family != AF_INET) && (peer->Remote.Addr4.sin_family != AF_INET6)) + { + status = STATUS_INVALID_DEVICE_REQUEST; + LOG_ERROR("Unknown address family in peer->Remote", TraceLoggingValue(peer->Remote.Addr4.sin_family, "AF")); + goto done; + } + POVPN_DRIVER driver = OvpnGetDriverContext(WdfGetDriver()); PWSK_SOCKET socket = NULL; BOOLEAN proto_tcp = peer->Proto == OVPN_PROTO_TCP; SIZE_T remoteAddrSize = peer->Remote.Addr4.sin_family == AF_INET ? sizeof(peer->Remote.Addr4) : sizeof(peer->Remote.Addr6); - OvpnPeerContext* peerCtx = OvpnPeerCtxAlloc(); + peerCtx = OvpnPeerCtxAlloc(); if (peerCtx == NULL) { status = STATUS_NO_MEMORY; goto done; } + // assign remote transport address + if (peer->Remote.Addr4.sin_family == AF_INET) { + peerCtx->TransportAddrs.Remote.IPv4 = peer->Remote.Addr4; + } + else { + peerCtx->TransportAddrs.Remote.IPv6 = peer->Remote.Addr6; + } + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnSocketInit(&driver->WskProviderNpi, &driver->WskRegistration, peer->Local.Addr4.sin_family, proto_tcp, (PSOCKADDR)&peer->Local, (PSOCKADDR)&peer->Remote, remoteAddrSize, device, &socket)); - kirql = ExAcquireSpinLockExclusive(&device->SpinLock); + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnAddPeerToTable(device, &device->Peers, peerCtx)); - LOG_IF_NOT_NT_SUCCESS(status = OvpnAddPeer(device, peerCtx)); - if (status != STATUS_SUCCESS) { - ExReleaseSpinLockExclusive(&device->SpinLock, kirql); - OvpnPeerCtxFree(peerCtx); - LOG_IF_NOT_NT_SUCCESS(OvpnSocketClose(socket)); - } - else { - device->Socket.Socket = socket; - device->Socket.Tcp = proto_tcp; - RtlZeroMemory(&device->Socket.TcpState, sizeof(OvpnSocketTcpState)); - RtlZeroMemory(&device->Socket.UdpState, sizeof(OvpnSocketUdpState)); - ExReleaseSpinLockExclusive(&device->SpinLock, kirql); - - OvpnPeerZeroStats(&device->Stats); - - if (proto_tcp) { - LOG_IF_NOT_NT_SUCCESS(status = WdfRequestForwardToIoQueue(request, device->PendingNewPeerQueue)); - // start async connect - status = OvpnSocketTcpConnect(socket, device, (PSOCKADDR)&peer->Remote); - } + device->Socket.Socket = socket; + device->Socket.Tcp = proto_tcp; + RtlZeroMemory(&device->Socket.TcpState, sizeof(OvpnSocketTcpState)); + RtlZeroMemory(&device->Socket.UdpState, sizeof(OvpnSocketUdpState)); + + OvpnPeerZeroStats(&device->Stats); + + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnTimerCreate(device->WdfDevice, peerCtx, &peerCtx->Timer)); + + if (proto_tcp) { + LOG_IF_NOT_NT_SUCCESS(status = WdfRequestForwardToIoQueue(request, device->PendingNewPeerQueue)); + // start async connect + status = OvpnSocketTcpConnect(socket, device, (PSOCKADDR)&peer->Remote); } done: + if (peerCtx != nullptr) { + OvpnPeerCtxRelease(peerCtx); + } + LOG_EXIT(); return status; @@ -162,42 +229,114 @@ OvpnPeerNew(POVPN_DEVICE device, WDFREQUEST request) _Use_decl_annotations_ NTSTATUS -OvpnPeerDel(POVPN_DEVICE device) +OvpnMPPeerNew(POVPN_DEVICE device, WDFREQUEST request) { LOG_ENTER(); - KIRQL kirql = ExAcquireSpinLockExclusive(&device->SpinLock); + const struct in6_addr ovpn_in6addr_any = { { 0 } }; - PWSK_SOCKET socket = device->Socket.Socket; + NTSTATUS status = STATUS_SUCCESS; - device->Socket.Socket = NULL; - OvpnFlushPeers(device); + POVPN_MP_NEW_PEER peer; + OvpnPeerContext* peerCtx = nullptr; - RtlZeroMemory(&device->Socket.TcpState, sizeof(OvpnSocketTcpState)); - RtlZeroMemory(&device->Socket.UdpState, sizeof(OvpnSocketUdpState)); + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveInputBuffer(request, sizeof(OVPN_MP_NEW_PEER), (PVOID*)&peer, nullptr)); - // OvpnSocketClose requires PASSIVE_LEVEL, so must release lock - ExReleaseSpinLockExclusive(&device->SpinLock, kirql); + // check if we already have a peer with the same peer-id + peerCtx = OvpnFindPeer(device, peer->PeerId); + if (peerCtx != nullptr) { + status = STATUS_OBJECTID_EXISTS; + goto done; + } - LOG_IF_NOT_NT_SUCCESS(OvpnSocketClose(socket)); + // ensure local/remote address is AF_INET or AF_INET6 + if ((peer->Local.Addr4.sin_family != AF_INET) && (peer->Local.Addr4.sin_family != AF_INET6)) + { + status = STATUS_INVALID_DEVICE_REQUEST; + LOG_ERROR("Unknown address family in peer->Local", TraceLoggingValue(peer->Local.Addr4.sin_family, "AF")); + goto done; + } + if ((peer->Remote.Addr4.sin_family != AF_INET) && (peer->Remote.Addr4.sin_family != AF_INET6)) + { + status = STATUS_INVALID_DEVICE_REQUEST; + LOG_ERROR("Unknown address family in peer->Remote", TraceLoggingValue(peer->Remote.Addr4.sin_family, "AF")); + goto done; + } - // flush buffers in control queue so that client won't get control channel messages from previous session - while (LIST_ENTRY* entry = OvpnBufferQueueDequeue(device->ControlRxBufferQueue)) { - OVPN_RX_BUFFER* buffer = CONTAINING_RECORD(entry, OVPN_RX_BUFFER, QueueListEntry); - // return buffer back to pool - OvpnRxBufferPoolPut(buffer); + // allocate peer + peerCtx = OvpnPeerCtxAlloc(); + if (peerCtx == NULL) { + status = STATUS_NO_MEMORY; + goto done; } - WDFREQUEST request; - while (NT_SUCCESS(WdfIoQueueRetrieveNextRequest(device->PendingReadsQueue, &request))) { - ULONG_PTR bytesCopied = 0; - LOG_INFO("Cancel IO request from manual queue"); - WdfRequestCompleteWithInformation(request, STATUS_CANCELLED, bytesCopied); + // assign local transport address + if (peer->Local.Addr4.sin_family == AF_INET) { + peerCtx->TransportAddrs.Local.IPv4 = peer->Local.Addr4.sin_addr; + } + else { + peerCtx->TransportAddrs.Local.IPv6 = peer->Local.Addr6.sin6_addr; + } + + // assign remote transport address + if (peer->Remote.Addr4.sin_family == AF_INET) { + peerCtx->TransportAddrs.Remote.IPv4 = peer->Remote.Addr4; + } + else { + peerCtx->TransportAddrs.Remote.IPv6 = peer->Remote.Addr6; + } + + peerCtx->VpnAddrs.IPv4 = peer->VpnAddr4; + peerCtx->VpnAddrs.IPv6 = peer->VpnAddr6; + + peerCtx->PeerId = peer->PeerId; + + // create peer-specific timer + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnTimerCreate(device->WdfDevice, peerCtx, &peerCtx->Timer)); + + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnAddPeerToTable(device, &device->Peers, peerCtx)); + + if (peer->VpnAddr4.S_un.S_addr != INADDR_ANY) { + LOG_IF_NOT_NT_SUCCESS(status = OvpnAddPeerToTable(device, &device->PeersByVpn4, peerCtx)); + } + + if (RtlCompareMemory(&peer->VpnAddr6, &ovpn_in6addr_any, sizeof(IN6_ADDR)) != sizeof(IN6_ADDR)) { + LOG_IF_NOT_NT_SUCCESS(status = OvpnAddPeerToTable(device, &device->PeersByVpn6, peerCtx)); + } + +done: + if (peerCtx != nullptr) { + OvpnPeerCtxRelease(peerCtx); } LOG_EXIT(); - return STATUS_SUCCESS; + return status; +} + +VOID OvpnPeerSetDoWork(OvpnPeerContext *peer, LONG keepaliveInterval, LONG keepaliveTimeout, LONG mss) +{ + auto irql = ExAcquireSpinLockExclusive(&peer->SpinLock); + + if (mss != -1) { + peer->MSS = (UINT16)mss; + } + + if (keepaliveInterval != -1) { + peer->KeepaliveInterval = keepaliveInterval; + + // keepalive xmit timer, sends ping packets + OvpnTimerSetXmitInterval(peer->Timer, peer->KeepaliveInterval); + } + + if (keepaliveTimeout != -1) { + peer->KeepaliveTimeout = keepaliveTimeout; + + // keepalive recv timer, detects keepalive timeout + OvpnTimerSetRecvTimeout(peer->Timer, peer->KeepaliveTimeout); + } + + ExReleaseSpinLockExclusive(&peer->SpinLock, irql); } _Use_decl_annotations_ @@ -207,9 +346,8 @@ NTSTATUS OvpnPeerSet(POVPN_DEVICE device, WDFREQUEST request) NTSTATUS status = STATUS_SUCCESS; - OvpnPeerContext* peer = OvpnGetFirstPeer(&device->Peers); - - if (peer == NULL) { + OvpnPeerContext* peer = OvpnGetFirstPeer(device); + if (peer == nullptr) { LOG_ERROR("Peer not added"); status = STATUS_INVALID_DEVICE_REQUEST; goto done; @@ -222,39 +360,48 @@ NTSTATUS OvpnPeerSet(POVPN_DEVICE device, WDFREQUEST request) TraceLoggingValue(set_peer->KeepaliveTimeout, "timeout"), TraceLoggingValue(set_peer->MSS, "MSS")); - if (set_peer->MSS != -1) { - device->MSS = (UINT16)set_peer->MSS; + OvpnPeerSetDoWork(peer, set_peer->KeepaliveInterval, set_peer->KeepaliveTimeout, set_peer->MSS); + +done: + if (peer != nullptr) { + OvpnPeerCtxRelease(peer); } - if (set_peer->KeepaliveInterval != -1) { - peer->KeepaliveInterval = set_peer->KeepaliveInterval; + LOG_EXIT(); + return status; +} + +_Use_decl_annotations_ +NTSTATUS OvpnMPPeerSet(POVPN_DEVICE device, WDFREQUEST request) +{ + LOG_ENTER(); - if (peer->KeepaliveInterval > 0) { - // keepalive xmit timer, sends ping packets - GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnTimerXmitCreate(device->WdfDevice, peer, peer->KeepaliveInterval, &peer->KeepaliveXmitTimer)); - OvpnTimerReset(peer->KeepaliveXmitTimer, peer->KeepaliveInterval); - } - else { - LOG_INFO("Destroy xmit timer"); - OvpnTimerDestroy(&peer->KeepaliveXmitTimer); - } - } + NTSTATUS status = STATUS_SUCCESS; - if (peer->KeepaliveTimeout != -1) { - peer->KeepaliveTimeout = set_peer->KeepaliveTimeout; + OvpnPeerContext* peer = nullptr; - if (peer->KeepaliveTimeout > 0) { - // keepalive recv timer, detects keepalive timeout - GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnTimerRecvCreate(device->WdfDevice, peer, &peer->KeepaliveRecvTimer)); - OvpnTimerReset(peer->KeepaliveRecvTimer, peer->KeepaliveTimeout); - } - else { - LOG_INFO("Destroy recv timer"); - OvpnTimerDestroy(&peer->KeepaliveRecvTimer); - } + POVPN_MP_SET_PEER set_peer = NULL; + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveInputBuffer(request, sizeof(OVPN_MP_SET_PEER), (PVOID*)&set_peer, nullptr)); + + LOG_INFO("MP Set peer", TraceLoggingValue(set_peer->PeerId, "peer-id"), + TraceLoggingValue(set_peer->KeepaliveInterval, "interval"), + TraceLoggingValue(set_peer->KeepaliveTimeout, "timeout"), + TraceLoggingValue(set_peer->MSS, "MSS")); + + peer = OvpnFindPeer(device, set_peer->PeerId); + if (peer == nullptr) { + LOG_ERROR("Peer not found", TraceLoggingValue(set_peer->PeerId, "peer-id")); + status = STATUS_INVALID_DEVICE_REQUEST; + goto done; } + OvpnPeerSetDoWork(peer, set_peer->KeepaliveInterval, set_peer->KeepaliveTimeout, set_peer->MSS); + done: + if (peer != nullptr) { + OvpnPeerCtxRelease(peer); + } + LOG_EXIT(); return status; } @@ -265,13 +412,6 @@ OvpnPeerGetStats(POVPN_DEVICE device, WDFREQUEST request, ULONG_PTR* bytesReturn { NTSTATUS status = STATUS_SUCCESS; - OvpnPeerContext* peer = OvpnGetFirstPeer(&device->Peers); - if (peer == NULL) { - LOG_ERROR("Peer not added"); - status = STATUS_INVALID_DEVICE_REQUEST; - goto done; - } - POVPN_STATS stats = NULL; GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveOutputBuffer(request, sizeof(OVPN_STATS), (PVOID*)&stats, NULL)); @@ -302,7 +442,8 @@ OvpnPeerStartVPN(POVPN_DEVICE device) NTSTATUS status = STATUS_SUCCESS; - if (!OvpnHasPeers(device)) { + auto peer = OvpnGetFirstPeer(device); + if (peer == nullptr) { LOG_ERROR("Peer not added"); status = STATUS_INVALID_DEVICE_REQUEST; goto done; @@ -311,34 +452,23 @@ OvpnPeerStartVPN(POVPN_DEVICE device) OvpnAdapterSetLinkState(OvpnGetAdapterContext(device->Adapter), MediaConnectStateConnected); done: + if (peer != nullptr) { + OvpnPeerCtxRelease(peer); + } + LOG_EXIT(); return status; } -_Use_decl_annotations_ -NTSTATUS -OvpnPeerNewKey(POVPN_DEVICE device, WDFREQUEST request) +static NTSTATUS +OvpnPeerGetAlgHandle(POVPN_DEVICE device, OVPN_CIPHER_ALG cipherAlg, BCRYPT_ALG_HANDLE& algHandle) { - LOG_ENTER(); - NTSTATUS status = STATUS_SUCCESS; - if (!OvpnHasPeers(device)) { - LOG_ERROR("Peer not added"); - status = STATUS_INVALID_DEVICE_REQUEST; - goto done; - } - - POVPN_CRYPTO_DATA cryptoData = NULL; - - GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveInputBuffer(request, sizeof(OVPN_CRYPTO_DATA), (PVOID*)&cryptoData, nullptr)); - - BCRYPT_ALG_HANDLE algHandle = NULL; - switch (cryptoData->CipherAlg) { + switch (cipherAlg) { case OVPN_CIPHER_ALG_AES_GCM: algHandle = device->AesAlgHandle; - device->CryptoOverhead = AEAD_CRYPTO_OVERHEAD; break; case OVPN_CIPHER_ALG_CHACHA20_POLY1305: @@ -346,24 +476,47 @@ OvpnPeerNewKey(POVPN_DEVICE device, WDFREQUEST request) if (algHandle == NULL) { LOG_ERROR("CHACHA20-POLY1305 is not available"); status = STATUS_INVALID_DEVICE_REQUEST; - goto done; } - device->CryptoOverhead = AEAD_CRYPTO_OVERHEAD; + break; default: - device->CryptoOverhead = NONE_CRYPTO_OVERHEAD; break; } - OvpnPeerContext* peer = OvpnGetFirstPeer(&device->Peers); - if (peer == NULL) { + return status; +} + +_Use_decl_annotations_ +NTSTATUS +OvpnPeerNewKey(POVPN_DEVICE device, WDFREQUEST request) +{ + LOG_ENTER(); + + NTSTATUS status = STATUS_SUCCESS; + + POVPN_CRYPTO_DATA cryptoData = NULL; + OVPN_CRYPTO_DATA_V2 cryptoDataV2{}; + OvpnPeerContext* peer = nullptr; + + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveInputBuffer(request, sizeof(OVPN_CRYPTO_DATA), (PVOID*)&cryptoData, nullptr)); + + BCRYPT_ALG_HANDLE algHandle = NULL; + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnPeerGetAlgHandle(device, cryptoData->CipherAlg, algHandle)); + + peer = OvpnFindPeer(device, cryptoData->PeerId); + if (peer == nullptr) { status = STATUS_OBJECTID_NOT_FOUND; goto done; } - GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnCryptoNewKey(&peer->CryptoContext, cryptoData, algHandle)); + RtlCopyMemory(&cryptoDataV2.V1, cryptoData, sizeof(OVPN_CRYPTO_DATA)); + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnCryptoNewKey(&peer->CryptoContext, &cryptoDataV2, algHandle)); done: + if (peer != nullptr) { + OvpnPeerCtxRelease(peer); + } + LOG_EXIT(); return status; @@ -371,28 +524,64 @@ OvpnPeerNewKey(POVPN_DEVICE device, WDFREQUEST request) _Use_decl_annotations_ NTSTATUS -OvpnPeerSwapKeys(POVPN_DEVICE device) +OvpnPeerNewKeyV2(POVPN_DEVICE device, WDFREQUEST request) { LOG_ENTER(); NTSTATUS status = STATUS_SUCCESS; + OvpnPeerContext* peer = nullptr; - if (!OvpnHasPeers(device)) { - LOG_ERROR("Peer not added"); - status = STATUS_INVALID_DEVICE_REQUEST; + POVPN_CRYPTO_DATA_V2 cryptoDataV2 = NULL; + GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveInputBuffer(request, sizeof(OVPN_CRYPTO_DATA_V2), (PVOID*)&cryptoDataV2, nullptr)); + + BCRYPT_ALG_HANDLE algHandle = NULL; + GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnPeerGetAlgHandle(device, cryptoDataV2->V1.CipherAlg, algHandle)); + + peer = OvpnFindPeer(device, cryptoDataV2->V1.PeerId); + + if (peer == nullptr) { + status = STATUS_OBJECTID_NOT_FOUND; goto done; } - OvpnPeerContext* peer = OvpnGetFirstPeer(&device->Peers); - if (peer == NULL) { + KIRQL irql = ExAcquireSpinLockExclusive(&peer->SpinLock); + LOG_IF_NOT_NT_SUCCESS(status = OvpnCryptoNewKey(&peer->CryptoContext, cryptoDataV2, algHandle)); + ExReleaseSpinLockExclusive(&peer->SpinLock, irql); + +done: + if (peer != nullptr) { + OvpnPeerCtxRelease(peer); + } + + LOG_EXIT(); + + return status; +} + +_Use_decl_annotations_ +NTSTATUS +OvpnPeerSwapKeys(POVPN_DEVICE device) +{ + LOG_ENTER(); + + NTSTATUS status = STATUS_SUCCESS; + + OvpnPeerContext* peer = OvpnGetFirstPeer(device); + if (peer == nullptr) { LOG_ERROR("Peer not found"); status = STATUS_INVALID_DEVICE_REQUEST; goto done; } + KIRQL irql = ExAcquireSpinLockExclusive(&peer->SpinLock); OvpnCryptoSwapKeys(&peer->CryptoContext); + ExReleaseSpinLockExclusive(&peer->SpinLock, irql); done: + if (peer != nullptr) { + OvpnPeerCtxRelease(peer); + } + LOG_EXIT(); return status; diff --git a/peer.h b/peer.h index 4495171..800649a 100644 --- a/peer.h +++ b/peer.h @@ -29,6 +29,8 @@ struct OvpnPeerContext { + EX_SPIN_LOCK SpinLock; + OvpnCryptoContext CryptoContext; INT32 PeerId; @@ -39,11 +41,30 @@ struct OvpnPeerContext // keepalive timeout in seconds LONG KeepaliveTimeout; - // timer used to send periodic ping messages to the server if no data has been sent within the past KeepaliveInterval seconds - WDFTIMER KeepaliveXmitTimer; + // 1-sec timer which handles ping intervals and keepalive timeouts + WDFTIMER Timer; + + UINT16 MSS; + + struct { + IN_ADDR IPv4; + IN6_ADDR IPv6; + } VpnAddrs; + + struct { + union { + IN_ADDR IPv4; + IN6_ADDR IPv6; + } Local; - // timer used to report keepalive timeout error to userspace when no data has been received for KeepaliveTimeout seconds - WDFTIMER KeepaliveRecvTimer; + union { + SOCKADDR_IN IPv4; + SOCKADDR_IN6 IPv6; + } Remote; + + } TransportAddrs; + + LONG RefCounter; }; _Must_inspect_result_ @@ -53,9 +74,14 @@ OvpnPeerCtxAlloc(); VOID OvpnPeerCtxFree(_In_ OvpnPeerContext*); +VOID +OvpnPeerCtxRelease(_In_ OvpnPeerContext*); + RTL_GENERIC_ALLOCATE_ROUTINE OvpnPeerAllocateRoutine; RTL_GENERIC_FREE_ROUTINE OvpnPeerFreeRoutine; RTL_GENERIC_COMPARE_ROUTINE OvpnPeerCompareByPeerIdRoutine; +RTL_GENERIC_COMPARE_ROUTINE OvpnPeerCompareByVPN4Routine; +RTL_GENERIC_COMPARE_ROUTINE OvpnPeerCompareByVPN6Routine; _Must_inspect_result_ _IRQL_requires_(PASSIVE_LEVEL) @@ -65,13 +91,18 @@ OvpnPeerNew(_In_ POVPN_DEVICE device, WDFREQUEST request); _Must_inspect_result_ _IRQL_requires_(PASSIVE_LEVEL) NTSTATUS -OvpnPeerDel(_In_ POVPN_DEVICE device); +OvpnMPPeerNew(_In_ POVPN_DEVICE device, WDFREQUEST request); _Must_inspect_result_ _Requires_exclusive_lock_held_(device->SpinLock) NTSTATUS OvpnPeerSet(_In_ POVPN_DEVICE device, WDFREQUEST request); +_Must_inspect_result_ +_Requires_exclusive_lock_held_(device->SpinLock) +NTSTATUS +OvpnMPPeerSet(_In_ POVPN_DEVICE device, WDFREQUEST request); + _Must_inspect_result_ NTSTATUS _Requires_shared_lock_held_(device->SpinLock) @@ -88,6 +119,11 @@ _Requires_exclusive_lock_held_(device->SpinLock) NTSTATUS OvpnPeerNewKey(_In_ POVPN_DEVICE device, WDFREQUEST request); +_Must_inspect_result_ +_Requires_exclusive_lock_held_(device->SpinLock) +NTSTATUS +OvpnPeerNewKeyV2(_In_ POVPN_DEVICE device, WDFREQUEST request); + _Must_inspect_result_ _Requires_exclusive_lock_held_(device->SpinLock) NTSTATUS diff --git a/pktid.cpp b/pktid.cpp index b7f365d..4d94ee3 100644 --- a/pktid.cpp +++ b/pktid.cpp @@ -28,24 +28,29 @@ #define PKTID_WRAP_WARN 0xf0000000ULL _Use_decl_annotations_ -NTSTATUS OvpnPktidXmitNext(OvpnPktidXmit* px, UINT32* pktId) +NTSTATUS OvpnPktidXmitNext(OvpnPktidXmit* px, VOID* pktId, BOOLEAN pktId64bit) { ULONG64 seqNum = InterlockedIncrementNoFence64(&px->SeqNum); - *pktId = (UINT32)seqNum; - if (seqNum < PKTID_WRAP_WARN) { - return STATUS_SUCCESS; - } - else { - LOG_ERROR("Pktid wrapped"); - return STATUS_INTEGER_OVERFLOW; - } + if (pktId64bit) { + *static_cast(pktId) = seqNum; + } + else + { + *static_cast(pktId) = static_cast(seqNum); + if (seqNum >= PKTID_WRAP_WARN) { + LOG_ERROR("Pktid wrapped"); + return STATUS_INTEGER_OVERFLOW; + } + } + + return STATUS_SUCCESS; } #define PKTID_RECV_EXPIRE ((30 * WDF_TIMEOUT_TO_SEC) / KeQueryTimeIncrement()) _Use_decl_annotations_ -NTSTATUS OvpnPktidRecvVerify(OvpnPktidRecv* pr, UINT32 pktId) +NTSTATUS OvpnPktidRecvVerify(OvpnPktidRecv* pr, UINT64 pktId) { LARGE_INTEGER now; KeQueryTickCount(&now); @@ -69,16 +74,16 @@ NTSTATUS OvpnPktidRecvVerify(OvpnPktidRecv* pr, UINT32 pktId) } else if (pktId > pr->Id) { /* ID jumped forward by more than one */ - UINT32 delta = pktId - pr->Id; + const auto delta = pktId - pr->Id; if (delta < REPLAY_WINDOW_SIZE) { pr->Base = REPLAY_INDEX(pr->Base, -(INT32)delta); pr->History[pr->Base / 8] |= (1 << (pr->Base % 8)); - pr->Extent += delta; + pr->Extent += static_cast(delta); if (pr->Extent > REPLAY_WINDOW_SIZE) pr->Extent = REPLAY_WINDOW_SIZE; - for (UINT32 i = 1; i < delta; ++i) { - unsigned int newb = REPLAY_INDEX(pr->Base, i); + for (auto i = 1; i < delta; ++i) { + const auto newb = REPLAY_INDEX(pr->Base, i); pr->History[newb / 8] &= ~BIT(newb % 8); } @@ -93,10 +98,8 @@ NTSTATUS OvpnPktidRecvVerify(OvpnPktidRecv* pr, UINT32 pktId) } else { /* ID backtrack */ - UINT32 delta = pr->Id - pktId; + const auto delta = pr->Id - pktId; - if (delta > pr->MaxBacktrack) - pr->MaxBacktrack = delta; if (delta < pr->Extent) { if (pktId > pr->IdFloor) { UINT32 ri = REPLAY_INDEX(pr->Base, delta); diff --git a/pktid.h b/pktid.h index b0d2325..dcc4be8 100644 --- a/pktid.h +++ b/pktid.h @@ -50,17 +50,17 @@ struct OvpnPktidRecv LARGE_INTEGER Expire; /* highest sequence number received */ - UINT32 Id; + UINT64 Id; /* we will only accept backtrack IDs > id_floor */ - UINT32 IdFloor; - UINT32 MaxBacktrack; + UINT64 IdFloor; }; /* Get the next packet ID for xmit */ -NTSTATUS OvpnPktidXmitNext(_In_ OvpnPktidXmit* px, _Out_ UINT32* pktId); +NTSTATUS OvpnPktidXmitNext(_In_ OvpnPktidXmit* px, _Out_ VOID* pktId, BOOLEAN pktId64bit); + /* Packet replay detection. * Allows ID backtrack of up to REPLAY_WINDOW_SIZE - 1. */ -NTSTATUS OvpnPktidRecvVerify(_In_ OvpnPktidRecv* pid, UINT32 pktId); +NTSTATUS OvpnPktidRecvVerify(_In_ OvpnPktidRecv* pid, UINT64 pktId); diff --git a/rxqueue.cpp b/rxqueue.cpp index ce7d71b..2a85ffd 100644 --- a/rxqueue.cpp +++ b/rxqueue.cpp @@ -27,6 +27,7 @@ #include "driver.h" #include "bufferpool.h" +#include "peer.h" #include "rxqueue.h" #include "netringiterator.h" #include "trace.h" @@ -115,7 +116,7 @@ OvpnEvtRxQueueAdvance(NETPACKETQUEUE netPacketQueue) fragment->ValidLength = buffer->Len; fragment->Offset = 0; NET_FRAGMENT_VIRTUAL_ADDRESS* virtualAddr = NetExtensionGetFragmentVirtualAddress(&queue->VirtualAddressExtension, NetFragmentIteratorGetIndex(&fi)); - RtlCopyMemory(virtualAddr->VirtualAddress, buffer->Data + device->CryptoOverhead, buffer->Len); + RtlCopyMemory(virtualAddr->VirtualAddress, buffer->Data, buffer->Len); InterlockedExchangeAddNoFence64(&device->Stats.TunBytesReceived, buffer->Len); diff --git a/socket.cpp b/socket.cpp index ef57e37..9c2f515 100644 --- a/socket.cpp +++ b/socket.cpp @@ -95,10 +95,30 @@ OvpnSocketSyncOp(_In_z_ CHAR* opName, OP op, SUCCESS success) } static -_Requires_shared_lock_held_(device->SpinLock) VOID -OvpnSocketControlPacketReceived(_In_ POVPN_DEVICE device, _In_reads_(len) PUCHAR buf, SIZE_T len) +OvpnSocketControlPacketReceived(_In_ POVPN_DEVICE device, _In_reads_(len) PUCHAR buf, SIZE_T len, _In_opt_ PSOCKADDR remote) { + SIZE_T hdrLen = 0, totalLen = len; + + // in UDP and MP mode we prepend CC packet with remote sockaddr before pushing it to userspace + if (device->Mode == OVPN_MODE_MP && remote != NULL) { + switch (remote->sa_family) { + case AF_INET: + hdrLen = sizeof(SOCKADDR_IN); + break; + + case AF_INET6: + hdrLen = sizeof(SOCKADDR_IN6); + break; + + default: + LOG_ERROR("Invalid remote address family", TraceLoggingValue(remote->sa_family, "AF")); + InterlockedIncrementNoFence(&device->Stats.LostInControlPackets); + return; + } + totalLen += hdrLen; + } + WDFREQUEST request; NTSTATUS status = WdfIoQueueRetrieveNextRequest(device->PendingReadsQueue, &request); if (!NT_SUCCESS(status)) { @@ -113,17 +133,21 @@ OvpnSocketControlPacketReceived(_In_ POVPN_DEVICE device, _In_reads_(len) PUCHAR return; } - if (sizeof(buffer->Data) >= len) { - // copy control packet to buffer - RtlCopyMemory(buffer->Data, buf, len); - buffer->Len = len; + if (totalLen <= OVPN_SOCKET_RX_PACKET_BUFFER_SIZE) { + if (hdrLen > 0) { + // prepend with sockaddr + RtlCopyMemory(OvpnBufferPut(buffer, hdrLen), remote, hdrLen); + } + + // copy control packet payload + RtlCopyMemory(OvpnBufferPut(buffer, len), buf, len); // enqueue buffer, it will be dequeued when read request arrives OvpnBufferQueueEnqueue(device->ControlRxBufferQueue, &buffer->QueueListEntry); } else { LOG_ERROR("Buffer too small, packet len , buf len ", - TraceLoggingValue(len, "pktlen"), TraceLoggingValue(sizeof(buffer->Data), "buflen")); + TraceLoggingValue(totalLen, "pktlen"), TraceLoggingValue(sizeof(buffer->Data), "buflen")); OvpnRxBufferPoolPut(buffer); } @@ -133,19 +157,26 @@ OvpnSocketControlPacketReceived(_In_ POVPN_DEVICE device, _In_reads_(len) PUCHAR PVOID readBuffer; size_t readBufferLength; - ULONG_PTR bytesSent = len; + ULONG_PTR bytesSent = totalLen; - LOG_IF_NOT_NT_SUCCESS(status = WdfRequestRetrieveOutputBuffer(request, len, &readBuffer, &readBufferLength)); + LOG_IF_NOT_NT_SUCCESS(status = WdfRequestRetrieveOutputBuffer(request, totalLen, &readBuffer, &readBufferLength)); if (NT_SUCCESS(status)) { - // copy control packet to read request buffer - RtlCopyMemory(readBuffer, buf, len); + + if (hdrLen > 0) { + // prepend with sockaddr + RtlCopyMemory(readBuffer, remote, hdrLen); + } + + // copy control packet payload + RtlCopyMemory((PCHAR)readBuffer + hdrLen, buf, totalLen - hdrLen); + InterlockedIncrementNoFence(&device->Stats.ReceivedControlPackets); } else { InterlockedIncrementNoFence(&device->Stats.LostInControlPackets); if (status == STATUS_BUFFER_TOO_SMALL) { LOG_ERROR("Buffer too small, packet len , buf len ", - TraceLoggingValue(len, "pktlen"), TraceLoggingValue(readBufferLength, "buflen")); + TraceLoggingValue(totalLen, "pktlen"), TraceLoggingValue(readBufferLength, "buflen")); } bytesSent = 0; @@ -156,14 +187,13 @@ OvpnSocketControlPacketReceived(_In_ POVPN_DEVICE device, _In_reads_(len) PUCHAR } static -_Requires_shared_lock_held_(device->SpinLock) -VOID OvpnSocketDataPacketReceived(_In_ POVPN_DEVICE device, UCHAR op, _In_reads_(len) PUCHAR cipherTextBuf, SIZE_T len) +VOID OvpnSocketDataPacketReceived(_In_ POVPN_DEVICE device, UCHAR op, UINT32 peerId, _In_reads_(len) PUCHAR cipherTextBuf, SIZE_T len, BOOLEAN irqlDispatch) { InterlockedExchangeAddNoFence64(&device->Stats.TransportBytesReceived, len); - OvpnPeerContext* peer = OvpnGetFirstPeer(&device->Peers); - if (peer == NULL) { - LOG_WARN("No peer"); + OvpnPeerContext* peer = OvpnFindPeer(device, peerId); + if (peer == nullptr) { + LOG_WARN("Peer not found", TraceLoggingValue(peerId, "peerId")); InterlockedIncrementNoFence(&device->Stats.LostInDataPackets); return; } @@ -175,21 +205,47 @@ VOID OvpnSocketDataPacketReceived(_In_ POVPN_DEVICE device, UCHAR op, _In_reads_ if (!NT_SUCCESS(status)) { LOG_ERROR("RxBufferPool exhausted"); InterlockedIncrementNoFence(&device->Stats.LostInDataPackets); + OvpnPeerCtxRelease(peer); return; } - if (peer->CryptoContext.Decrypt) { + // If we're at dispatch level, we can use a small optimization and use function + // which is not calling KeRaiseIRQL to raise the IRQL to DISPATCH_LEVEL before attempting to acquire the lock + KIRQL kirql = 0; + if (irqlDispatch) { + ExAcquireSpinLockSharedAtDpcLevel(&peer->SpinLock); + } + else { + kirql = ExAcquireSpinLockShared(&peer->SpinLock); + } + + OvpnCryptoContext* cryptoContext = &peer->CryptoContext; + + if (cryptoContext->Decrypt) { UCHAR keyId = OvpnCryptoKeyIdExtract(op); - OvpnCryptoKeySlot* keySlot = OvpnCryptoKeySlotFromKeyId(&peer->CryptoContext, keyId); + OvpnCryptoKeySlot* keySlot = OvpnCryptoKeySlotFromKeyId(cryptoContext, keyId); if (!keySlot) { status = STATUS_INVALID_DEVICE_STATE; LOG_ERROR("keyId not found", TraceLoggingValue(keyId, "keyId")); } else { + // extend data area in the buffer for plaintext and crypto overhead + OvpnBufferPut(buffer, len); + // decrypt into plaintext buffer - status = peer->CryptoContext.Decrypt(keySlot, cipherTextBuf, len, buffer->Data); - buffer->Len = len - device->CryptoOverhead; + status = cryptoContext->Decrypt(keySlot, cipherTextBuf, len, buffer->Data, cryptoContext->CryptoOptions); + + // trim AEAD tag an the end + auto aeadTagEnd = cryptoContext->CryptoOptions & CRYPTO_OPTIONS_AEAD_TAG_END; + if (aeadTagEnd) { + OvpnBufferTrim(buffer, len - AEAD_AUTH_TAG_LEN); + } + + // remove crypto overhead in front + auto pktId64bit = cryptoContext->CryptoOptions & CRYPTO_OPTIONS_64BIT_PKTID; + auto cryptoOverheadFront = OVPN_DATA_V2_LEN + (pktId64bit ? 8 : 4) + (aeadTagEnd ? 0 : AEAD_AUTH_TAG_LEN); + OvpnBufferPull(buffer, cryptoOverheadFront); } } else { @@ -198,64 +254,59 @@ VOID OvpnSocketDataPacketReceived(_In_ POVPN_DEVICE device, UCHAR op, _In_reads_ // LOG_WARN("CryptoContext not yet initialized"); } - if (!NT_SUCCESS(status)) { + if (NT_SUCCESS(status)) { + OvpnTimerResetRecv(peer->Timer); + } + else { OvpnRxBufferPoolPut(buffer); - return; } - OvpnTimerReset(peer->KeepaliveRecvTimer, peer->KeepaliveTimeout); - - // points to the beginning of plaintext - UCHAR* buf = buffer->Data + device->CryptoOverhead; + auto mss = peer->MSS; - // ping packet? - if (OvpnTimerIsKeepaliveMessage(buf, buffer->Len)) { - LOG_INFO("Ping received"); - - // no need to inject ping packet into OS, return buffer to the pool - OvpnRxBufferPoolPut(buffer); + // don't forget to release spinlock + if (irqlDispatch) { + ExReleaseSpinLockSharedFromDpcLevel(&peer->SpinLock); } else { - if (OvpnMssIsIPv4(buf, buffer->Len)) { - OvpnMssDoIPv4(buf, buffer->Len, device->MSS); - } else if (OvpnMssIsIPv6(buf, buffer->Len)) { - OvpnMssDoIPv6(buf, buffer->Len, device->MSS); + ExReleaseSpinLockShared(&peer->SpinLock, kirql); + } + + OvpnPeerCtxRelease(peer); + + if (NT_SUCCESS(status)) { + // ping packet? + if (OvpnTimerIsKeepaliveMessage(buffer->Data, buffer->Len)) { + LOG_INFO("Ping received", TraceLoggingValue(peerId, "peer-id")); + + // no need to inject ping packet into OS, return buffer to the pool + OvpnRxBufferPoolPut(buffer); } + else { + if (OvpnMssIsIPv4(buffer->Data, buffer->Len)) { + OvpnMssDoIPv4(buffer->Data, buffer->Len, mss); + } + else if (OvpnMssIsIPv6(buffer->Data, buffer->Len)) { + OvpnMssDoIPv6(buffer->Data, buffer->Len, mss); + } - // enqueue plaintext buffer, it will be dequeued by NetAdapter RX datapath - OvpnBufferQueueEnqueue(device->DataRxBufferQueue, &buffer->QueueListEntry); + // enqueue plaintext buffer, it will be dequeued by NetAdapter RX datapath + OvpnBufferQueueEnqueue(device->DataRxBufferQueue, &buffer->QueueListEntry); - OvpnAdapterNotifyRx(device->Adapter); + OvpnAdapterNotifyRx(device->Adapter); + } } } VOID -OvpnSocketProcessIncomingPacket(_In_ POVPN_DEVICE device, _In_reads_(packetLength) PUCHAR buf, SIZE_T packetLength, BOOLEAN irqlDispatch) +OvpnSocketProcessIncomingPacket(_In_ POVPN_DEVICE device, _In_reads_(packetLength) PUCHAR buf, SIZE_T packetLength, BOOLEAN irqlDispatch, _In_opt_ PSOCKADDR remoteAddr) { - // If we're at dispatch level, we can use a small optimization and use function - // which is not calling KeRaiseIRQL to raise the IRQL to DISPATCH_LEVEL before attempting to acquire the lock - KIRQL kirql = 0; - if (irqlDispatch) { - ExAcquireSpinLockSharedAtDpcLevel(&device->SpinLock); - } - else { - kirql = ExAcquireSpinLockShared(&device->SpinLock); - } - UCHAR op = RtlUlongByteSwap(*(ULONG*)(buf)) >> 24; if (OvpnCryptoOpcodeExtract(op) == OVPN_OP_DATA_V2) { - OvpnSocketDataPacketReceived(device, op, buf, packetLength); - } - else { - OvpnSocketControlPacketReceived(device, buf, packetLength); - } - - // don't forget to release spinlock - if (irqlDispatch) { - ExReleaseSpinLockSharedFromDpcLevel(&device->SpinLock); + UINT32 peerId = RtlUlongByteSwap(*(ULONG*)(buf)) & OVPN_PEER_ID_MASK; + OvpnSocketDataPacketReceived(device, op, peerId, buf, packetLength, irqlDispatch); } else { - ExReleaseSpinLockShared(&device->SpinLock, kirql); + OvpnSocketControlPacketReceived(device, buf, packetLength, remoteAddr); } } @@ -322,7 +373,7 @@ OvpnSocketUdpReceiveFromEvent(_In_ PVOID socketContext, ULONG flags, _In_opt_ PW buf = packetBuf; } - OvpnSocketProcessIncomingPacket(device, buf, dataIndication->Buffer.Length, flags & WSK_FLAG_AT_DISPATCH_LEVEL); + OvpnSocketProcessIncomingPacket(device, buf, dataIndication->Buffer.Length, flags & WSK_FLAG_AT_DISPATCH_LEVEL, dataIndication->RemoteAddress); dataIndication = dataIndication->Next; } @@ -404,7 +455,7 @@ OvpnSocketTcpReceiveEvent(_In_opt_ PVOID socketContext, _In_ ULONG flags, _In_op buf = tcpState->PacketBuf; } - OvpnSocketProcessIncomingPacket(device, buf, tcpState->PacketLength, flags & WSK_FLAG_AT_DISPATCH_LEVEL); + OvpnSocketProcessIncomingPacket(device, buf, tcpState->PacketLength, flags & WSK_FLAG_AT_DISPATCH_LEVEL, NULL); mdlDataLen -= bytesRemained; dataIndicationLen -= bytesRemained; @@ -505,6 +556,11 @@ OvpnSocketInit(WSK_PROVIDER_NPI* wskProviderNpi, WSK_REGISTRATION* wskRegistrati }, [](PIRP) {})); // connect will be done later + + BOOLEAN tcpNoDelay = TRUE; + SIZE_T outputSizeReturned = 0; + GOTO_IF_NOT_NT_SUCCESS(error, status, connectionDispatch->Basic.WskControlSocket(*socket, WskSetOption, TCP_NODELAY, IPPROTO_TCP, + sizeof(tcpNoDelay), &tcpNoDelay, 0, NULL, &outputSizeReturned, NULL)); } else { // bind @@ -514,13 +570,20 @@ OvpnSocketInit(WSK_PROVIDER_NPI* wskProviderNpi, WSK_REGISTRATION* wskRegistrati return datagramDispatch->WskBind(*socket, localAddr, 0, irp); }, [](PIRP) {})); - // set remote - PWSK_PROVIDER_BASIC_DISPATCH basicDispatch = (PWSK_PROVIDER_BASIC_DISPATCH)(*socket)->Dispatch; - - GOTO_IF_NOT_NT_SUCCESS(error, status, OvpnSocketSyncOp("SetRemote", [basicDispatch, socket, remoteAddrSize, remoteAddr](PIRP irp) { - return basicDispatch->WskControlSocket(*socket, WskIoctl, SIO_WSK_SET_REMOTE_ADDRESS, 0, remoteAddrSize, remoteAddr, 0, NULL, NULL, irp); + // get the locally bound address for the socket + GOTO_IF_NOT_NT_SUCCESS(error, status, OvpnSocketSyncOp("GetLocalAddr", [datagramDispatch, socket, localAddr](PIRP irp) { + return datagramDispatch->WskGetLocalAddress(*socket, localAddr, irp); }, [](PIRP) {})); + if (remoteAddr != NULL) { + // set remote + PWSK_PROVIDER_BASIC_DISPATCH basicDispatch = (PWSK_PROVIDER_BASIC_DISPATCH)(*socket)->Dispatch; + + GOTO_IF_NOT_NT_SUCCESS(error, status, OvpnSocketSyncOp("SetRemote", [basicDispatch, socket, remoteAddrSize, remoteAddr](PIRP irp) { + return basicDispatch->WskControlSocket(*socket, WskIoctl, SIO_WSK_SET_REMOTE_ADDRESS, 0, remoteAddrSize, remoteAddr, 0, NULL, NULL, irp); + }, [](PIRP) {})); + } + // enable ReceiveFrom event eventCallbackControl.NpiId = &NPI_WSK_INTERFACE_ID; eventCallbackControl.EventMask = WSK_EVENT_RECEIVE_FROM; @@ -689,7 +752,7 @@ OvpnSocketSendComplete(_In_ PDEVICE_OBJECT deviceObj, _In_ PIRP irp, _In_ PVOID NTSTATUS _Use_decl_annotations_ -OvpnSocketSend(OvpnSocket* ovpnSocket, OVPN_TX_BUFFER* buffer) { +OvpnSocketSend(OvpnSocket* ovpnSocket, OVPN_TX_BUFFER* buffer, SOCKADDR* sa) { OVPN_DEVICE* device = (OVPN_DEVICE*)OvpnTxBufferPoolGetContext(buffer->Pool); PWSK_SOCKET socket = ovpnSocket->Socket; @@ -727,11 +790,11 @@ OvpnSocketSend(OvpnSocket* ovpnSocket, OVPN_TX_BUFFER* buffer) { } else if (buffer->WskBufList.Buffer.Length != 0) { PWSK_PROVIDER_DATAGRAM_DISPATCH datagramDispatch = (PWSK_PROVIDER_DATAGRAM_DISPATCH)socket->Dispatch; - LOG_IF_NOT_NT_SUCCESS(status = datagramDispatch->WskSendMessages(socket, &buffer->WskBufList, 0, NULL, 0, NULL, irp)); + LOG_IF_NOT_NT_SUCCESS(status = datagramDispatch->WskSendMessages(socket, &buffer->WskBufList, 0, sa, 0, NULL, irp)); } else { WSK_BUF wskBuf{ buffer->Mdl, FIELD_OFFSET(OVPN_TX_BUFFER, Head) + (ULONG)(buffer->Data - buffer->Head), buffer->Len }; PWSK_PROVIDER_DATAGRAM_DISPATCH datagramDispatch = (PWSK_PROVIDER_DATAGRAM_DISPATCH)socket->Dispatch; - LOG_IF_NOT_NT_SUCCESS(status = datagramDispatch->WskSendTo(socket, &wskBuf, 0, NULL, 0, NULL, irp)); + LOG_IF_NOT_NT_SUCCESS(status = datagramDispatch->WskSendTo(socket, &wskBuf, 0, sa, 0, NULL, irp)); } return status; diff --git a/socket.h b/socket.h index b6dee5a..0a6fc34 100644 --- a/socket.h +++ b/socket.h @@ -60,7 +60,7 @@ _Must_inspect_result_ _IRQL_requires_(PASSIVE_LEVEL) NTSTATUS OvpnSocketInit(_In_ WSK_PROVIDER_NPI* wskProviderNpi, _In_ WSK_REGISTRATION* wskRegistration, ADDRESS_FAMILY addrFamily, - BOOLEAN tcp, _In_ PSOCKADDR localAddr, _In_ PSOCKADDR remoteAddr, SIZE_T remoteAddrSize, + BOOLEAN tcp, _In_ PSOCKADDR localAddr, _In_opt_ PSOCKADDR remoteAddr, SIZE_T remoteAddrSize, _In_ PVOID deviceContext, _Out_ PWSK_SOCKET* socket); _Must_inspect_result_ @@ -70,7 +70,7 @@ OvpnSocketClose(_In_opt_ PWSK_SOCKET socket); _Must_inspect_result_ NTSTATUS -OvpnSocketSend(_In_ OvpnSocket* ovpnSocket, _In_ OVPN_TX_BUFFER* buffer); +OvpnSocketSend(_In_ OvpnSocket* ovpnSocket, _In_ OVPN_TX_BUFFER* buffer, _In_opt_ SOCKADDR* sa); _Must_inspect_result_ NTSTATUS diff --git a/timer.cpp b/timer.cpp index bac0e9b..e1839e5 100644 --- a/timer.cpp +++ b/timer.cpp @@ -37,6 +37,13 @@ static const UCHAR OvpnKeepaliveMessage[] = { // Context added to a timer's attributes typedef struct _OVPN_PEER_TIMER_CONTEXT { OvpnPeerContext* Peer; + + LARGE_INTEGER lastXmit; + LARGE_INTEGER lastRecv; + + // 0 means "not set" + LONG recvTimeout; + LONG xmitInterval; } OVPN_PEER_TIMER_CONTEXT, * POVPN_PEER_TIMER_CONTEXT; WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(OVPN_PEER_TIMER_CONTEXT, OvpnGetPeerTimerContext); @@ -47,11 +54,8 @@ BOOLEAN OvpnTimerIsKeepaliveMessage(const PUCHAR buf, SIZE_T len) return RtlCompareMemory(buf, OvpnKeepaliveMessage, len) == sizeof(OvpnKeepaliveMessage); } -_Function_class_(EVT_WDF_TIMER) static VOID OvpnTimerXmit(WDFTIMER timer) { - LOG_ENTER(); - POVPN_DEVICE device = OvpnGetDeviceContext(WdfTimerGetParentObject(timer)); POVPN_PEER_TIMER_CONTEXT timerCtx = OvpnGetPeerTimerContext(timer); OVPN_TX_BUFFER* buffer; @@ -65,57 +69,90 @@ static VOID OvpnTimerXmit(WDFTIMER timer) } // copy keepalive magic message to the buffer - RtlCopyMemory(OvpnTxBufferPut(buffer, sizeof(OvpnKeepaliveMessage)), OvpnKeepaliveMessage, sizeof(OvpnKeepaliveMessage)); + RtlCopyMemory(OvpnBufferPut(buffer, sizeof(OvpnKeepaliveMessage)), OvpnKeepaliveMessage, sizeof(OvpnKeepaliveMessage)); OvpnPeerContext* peer = timerCtx->Peer; - KIRQL kiqrl = ExAcquireSpinLockShared(&device->SpinLock); - if (peer->CryptoContext.Encrypt) { + + ExAcquireSpinLockSharedAtDpcLevel(&peer->SpinLock); + + auto peerId = peer->PeerId; + auto addr = peer->TransportAddrs.Remote; + + OvpnCryptoContext* cryptoContext = &peer->CryptoContext; + if (cryptoContext->Encrypt) { // make space to crypto overhead - OvpnTxBufferPush(buffer, device->CryptoOverhead); + BOOLEAN pktId64bit = cryptoContext->CryptoOptions & CRYPTO_OPTIONS_64BIT_PKTID; + BOOLEAN aeadTagEnd = cryptoContext->CryptoOptions & CRYPTO_OPTIONS_AEAD_TAG_END; + + OvpnTxBufferPush(buffer, OVPN_DATA_V2_LEN + (pktId64bit ? 8 : 4) + (aeadTagEnd ? 0 : AEAD_AUTH_TAG_LEN)); // in-place encrypt, always with primary key - status = peer->CryptoContext.Encrypt(&peer->CryptoContext.Primary, buffer->Data, buffer->Len); + status = cryptoContext->Encrypt(&cryptoContext->Primary, buffer->Data, buffer->Len, cryptoContext->CryptoOptions); } else { status = STATUS_INVALID_DEVICE_STATE; // LOG_WARN("CryptoContext not initialized"); } + ExReleaseSpinLockSharedFromDpcLevel(&peer->SpinLock); if (NT_SUCCESS(status)) { // start async send, completion handler will return ciphertext buffer to the pool - LOG_IF_NOT_NT_SUCCESS(status = OvpnSocketSend(&device->Socket, buffer)); + SOCKADDR* sa = (SOCKADDR*)&(addr); + LOG_IF_NOT_NT_SUCCESS(status = OvpnSocketSend(&device->Socket, buffer, sa)); if (NT_SUCCESS(status)) { - LOG_INFO("Ping sent"); + LOG_INFO("Ping sent", TraceLoggingValue(peerId, "peer-id")); } } else { OvpnTxBufferPoolPut(buffer); } - ExReleaseSpinLockShared(&device->SpinLock, kiqrl); - - LOG_EXIT(); } -_Function_class_(EVT_WDF_TIMER) -static VOID OvpnTimerRecv(WDFTIMER timer) +static BOOLEAN OvpnTimerRecv(WDFTIMER timer) { - LOG_ENTER(); - - LOG_WARN("Keepalive timeout"); - POVPN_DEVICE device = OvpnGetDeviceContext(WdfTimerGetParentObject(timer)); + POVPN_PEER_TIMER_CONTEXT timerCtx = OvpnGetPeerTimerContext(timer); + auto peerId = timerCtx->Peer->PeerId; + LOG_INFO("Keepalive timeout", TraceLoggingValue(peerId, "peer-id")); + WDFREQUEST request; - NTSTATUS status = WdfIoQueueRetrieveNextRequest(device->PendingReadsQueue, &request); - if (!NT_SUCCESS(status)) { - LOG_WARN("No pending request for keepalive timeout notification"); - } - else { + NTSTATUS status = STATUS_SUCCESS; + + if (device->Mode == OVPN_MODE_P2P) { + status = WdfIoQueueRetrieveNextRequest(device->PendingReadsQueue, &request); + if (!NT_SUCCESS(status)) { + LOG_INFO("No pending request for keepalive timeout notification"); + return FALSE; + } + ULONG_PTR bytesSent = 0; WdfRequestCompleteWithInformation(request, STATUS_CONNECTION_DISCONNECTED, bytesSent); } + else { + (VOID)OvpnDeletePeer(device, peerId); - LOG_EXIT(); + status = WdfIoQueueRetrieveNextRequest(device->PendingNotificationRequestsQueue, &request); + if (!NT_SUCCESS(status)) { + LOG_INFO("Adding keepalive timeout notification to the queue"); + return NT_SUCCESS(device->PendingNotificationsQueue.AddEvent(OVPN_NOTIFY_DEL_PEER, peerId, OVPN_DEL_PEER_REASON_EXPIRED)); + } + else { + LOG_INFO("Notify userspace about expired peer"); + OVPN_NOTIFY_EVENT *evt; + ULONG_PTR bytesSent = 0; + LOG_IF_NOT_NT_SUCCESS(status = WdfRequestRetrieveOutputBuffer(request, sizeof(OVPN_NOTIFY_EVENT), (PVOID*)&evt, nullptr)); + if (NT_SUCCESS(status)) { + evt->Cmd = OVPN_NOTIFY_DEL_PEER; + evt->PeerId = peerId; + evt->DelPeerReason = OVPN_DEL_PEER_REASON_EXPIRED; + bytesSent = sizeof(OVPN_NOTIFY_EVENT); + } + WdfRequestCompleteWithInformation(request, status, bytesSent); + } + } + + return NT_SUCCESS(status); } _Use_decl_annotations_ @@ -129,7 +166,32 @@ VOID OvpnTimerDestroy(WDFTIMER* timer) } } -static NTSTATUS OvpnTimerCreate(WDFOBJECT parent, OvpnPeerContext* peer, ULONG period, PFN_WDF_TIMER func, _Inout_ WDFTIMER* timer) +_Function_class_(EVT_WDF_TIMER) +static VOID OvpnTimerTick(WDFTIMER timer) +{ + LARGE_INTEGER now; + KeQuerySystemTime(&now); + + POVPN_PEER_TIMER_CONTEXT timerCtx = OvpnGetPeerTimerContext(timer); + + if ((timerCtx->xmitInterval > 0) && (((now.QuadPart - timerCtx->lastXmit.QuadPart) / WDF_TIMEOUT_TO_SEC) > timerCtx->xmitInterval)) + { + OvpnTimerXmit(timer); + timerCtx->lastXmit = now; + } + + if ((timerCtx->recvTimeout > 0) && (((now.QuadPart - timerCtx->lastRecv.QuadPart) / WDF_TIMEOUT_TO_SEC) > timerCtx->recvTimeout)) + { + // have we have completed pending read request? + if (OvpnTimerRecv(timer)) + { + timerCtx->recvTimeout = 0; // one-off timer + } + } +} + +_Use_decl_annotations_ +NTSTATUS OvpnTimerCreate(WDFOBJECT parent, OvpnPeerContext* peer, _Inout_ WDFTIMER* timer) { LOG_ENTER(); @@ -141,8 +203,9 @@ static NTSTATUS OvpnTimerCreate(WDFOBJECT parent, OvpnPeerContext* peer, ULONG p } WDF_TIMER_CONFIG timerConfig; - WDF_TIMER_CONFIG_INIT(&timerConfig, func); - timerConfig.Period = period * 1000; + WDF_TIMER_CONFIG_INIT(&timerConfig, OvpnTimerTick); + timerConfig.TolerableDelay = TolerableDelayUnlimited; + timerConfig.Period = 1000; WDF_OBJECT_ATTRIBUTES timerAttributes; WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); @@ -155,36 +218,51 @@ static NTSTATUS OvpnTimerCreate(WDFOBJECT parent, OvpnPeerContext* peer, ULONG p if (NT_SUCCESS(status)) { POVPN_PEER_TIMER_CONTEXT pTimerContext = OvpnGetPeerTimerContext(*timer); pTimerContext->Peer = peer; + WdfTimerStart(*timer, WDF_REL_TIMEOUT_IN_SEC(1)); } LOG_EXIT(); return status; } -_Use_decl_annotations_ -NTSTATUS OvpnTimerXmitCreate(WDFOBJECT parent, OvpnPeerContext* peer, ULONG period, WDFTIMER* timer) +#define CHECK_TIMER_HANDLE(timer) \ + do { \ + if ((timer) == WDF_NO_HANDLE) { \ + LOG_ERROR("Timer handle is not initialized"); \ + return; \ + } \ + } while (0) + +VOID OvpnTimerSetXmitInterval(WDFTIMER timer, LONG xmitInterval) { - NTSTATUS status; - LOG_INFO("Create xmit timer", TraceLoggingValue(period, "period")); - LOG_IF_NOT_NT_SUCCESS(status = OvpnTimerCreate(parent, peer, period, OvpnTimerXmit, timer)); + CHECK_TIMER_HANDLE(timer); - return status; + POVPN_PEER_TIMER_CONTEXT timerCtx = OvpnGetPeerTimerContext(timer); + timerCtx->xmitInterval = xmitInterval; + KeQuerySystemTime(&timerCtx->lastXmit); } -_Use_decl_annotations_ -NTSTATUS OvpnTimerRecvCreate(WDFOBJECT parent, OvpnPeerContext* peer, WDFTIMER* timer) +VOID OvpnTimerSetRecvTimeout(WDFTIMER timer, LONG recvTimeout) { - NTSTATUS status; - LOG_INFO("Create recv timer"); - LOG_IF_NOT_NT_SUCCESS(status = OvpnTimerCreate(parent, peer, 0, OvpnTimerRecv, timer)); + CHECK_TIMER_HANDLE(timer); - return status; + POVPN_PEER_TIMER_CONTEXT timerCtx = OvpnGetPeerTimerContext(timer); + timerCtx->recvTimeout = recvTimeout; + KeQuerySystemTime(&timerCtx->lastRecv); } -VOID OvpnTimerReset(WDFTIMER timer, ULONG dueTime) +VOID OvpnTimerResetXmit(WDFTIMER timer) { - if (timer != WDF_NO_HANDLE) { - // if timer has already been created this will reset "due time" value to the new one - WdfTimerStart(timer, WDF_REL_TIMEOUT_IN_SEC(dueTime)); - } + CHECK_TIMER_HANDLE(timer); + + POVPN_PEER_TIMER_CONTEXT timerCtx = OvpnGetPeerTimerContext(timer); + KeQuerySystemTime(&timerCtx->lastXmit); +} + +VOID OvpnTimerResetRecv(WDFTIMER timer) +{ + CHECK_TIMER_HANDLE(timer); + + POVPN_PEER_TIMER_CONTEXT timerCtx = OvpnGetPeerTimerContext(timer); + KeQuerySystemTime(&timerCtx->lastRecv); } diff --git a/timer.h b/timer.h index b9f8bfc..88ee019 100644 --- a/timer.h +++ b/timer.h @@ -26,15 +26,20 @@ #include VOID -OvpnTimerReset(WDFTIMER timer, ULONG dueTime); +OvpnTimerResetXmit(WDFTIMER timer); -_Must_inspect_result_ -NTSTATUS -OvpnTimerXmitCreate(WDFOBJECT parent, OvpnPeerContext* peer, ULONG period, _Inout_ WDFTIMER* timer); +VOID +OvpnTimerResetRecv(WDFTIMER timer); _Must_inspect_result_ NTSTATUS -OvpnTimerRecvCreate(WDFOBJECT parent, OvpnPeerContext* peer, _Inout_ WDFTIMER* timer); +OvpnTimerCreate(WDFOBJECT parent, OvpnPeerContext* peer, _Inout_ WDFTIMER* timer); + +VOID +OvpnTimerSetXmitInterval(WDFTIMER timer, LONG xmitInterval); + +VOID +OvpnTimerSetRecvTimeout(WDFTIMER timer, LONG recvTimeout); VOID OvpnTimerDestroy(_Inout_ WDFTIMER* timer); diff --git a/txqueue.cpp b/txqueue.cpp index a93a470..e1bc3a2 100644 --- a/txqueue.cpp +++ b/txqueue.cpp @@ -36,15 +36,57 @@ #include "socket.h" #include "peer.h" +template +static +VOID +OvpnTxCopyRemoteToSockaddr(T& remote, SOCKADDR* sockaddr) { + // Copy the appropriate address based on the family + if (remote.IPv4.sin_family == AF_INET) { + RtlCopyMemory(sockaddr, &remote.IPv4, sizeof(SOCKADDR_IN)); + } + else if (remote.IPv6.sin6_family == AF_INET6) { + RtlCopyMemory(sockaddr, &remote.IPv6, sizeof(SOCKADDR_IN6)); + } +} + +static +BOOLEAN +OvpnTxAreSockaddrEqual(const SOCKADDR* addr1, const SOCKADDR* addr2) { + // First, check if the address families are the same + if (addr1->sa_family != addr2->sa_family) { + return 0; // Not equal if the families are different + } + + if (addr1->sa_family == AF_INET) { + // Compare IPv4 addresses + SOCKADDR_IN* ipv4_1 = (SOCKADDR_IN*)addr1; + SOCKADDR_IN* ipv4_2 = (SOCKADDR_IN*)addr2; + return (ipv4_1->sin_addr.s_addr == ipv4_2->sin_addr.s_addr && + ipv4_1->sin_port == ipv4_2->sin_port); + } + else if (addr1->sa_family == AF_INET6) { + // Compare IPv6 addresses + SOCKADDR_IN6* ipv6_1 = (SOCKADDR_IN6*)addr1; + SOCKADDR_IN6* ipv6_2 = (SOCKADDR_IN6*)addr2; + SIZE_T result = RtlCompareMemory(&ipv6_1->sin6_addr, &ipv6_2->sin6_addr, sizeof(ipv6_1->sin6_addr)); + return (result == sizeof(ipv6_1->sin6_addr) && + ipv6_1->sin6_port == ipv6_2->sin6_port); + } + + // If the address family is neither AF_INET nor AF_INET6, return not equal + return 0; +} + _Must_inspect_result_ -_Requires_shared_lock_held_(device->SpinLock) static NTSTATUS OvpnTxProcessPacket(_In_ POVPN_DEVICE device, _In_ POVPN_TXQUEUE queue, _In_ NET_RING_PACKET_ITERATOR *pi, - _Inout_ OVPN_TX_BUFFER **head, _Inout_ OVPN_TX_BUFFER** tail) + _Inout_ OVPN_TX_BUFFER **head, _Inout_ OVPN_TX_BUFFER** tail, _Inout_ SOCKADDR *headSockaddr) { NET_RING_FRAGMENT_ITERATOR fi = NetPacketIteratorGetFragments(pi); + OvpnPeerContext* peer = NULL; + // get buffer into which we gather plaintext fragments and do in-place encryption OVPN_TX_BUFFER* buffer; NTSTATUS status; @@ -71,44 +113,70 @@ OvpnTxProcessPacket(_In_ POVPN_DEVICE device, _In_ POVPN_TXQUEUE queue, _In_ NET NET_FRAGMENT_VIRTUAL_ADDRESS* virtualAddr = NetExtensionGetFragmentVirtualAddress( &queue->VirtualAddressExtension, NetFragmentIteratorGetIndex(&fi)); - RtlCopyMemory(OvpnTxBufferPut(buffer, fragment->ValidLength), + RtlCopyMemory(OvpnBufferPut(buffer, fragment->ValidLength), (UCHAR const*)virtualAddr->VirtualAddress + fragment->Offset, fragment->ValidLength); NetFragmentIteratorAdvance(&fi); } if (OvpnMssIsIPv4(buffer->Data, buffer->Len)) { - OvpnMssDoIPv4(buffer->Data, buffer->Len, device->MSS); + auto addr = ((IPV4_HEADER*)buffer->Data)->DestinationAddress; + + peer = OvpnFindPeerVPN4(device, addr); + if (peer != nullptr) { + OvpnMssDoIPv4(buffer->Data, buffer->Len, peer->MSS); + } } else if (OvpnMssIsIPv6(buffer->Data, buffer->Len)) { - OvpnMssDoIPv6(buffer->Data, buffer->Len, device->MSS); + auto addr = ((IPV6_HEADER*)buffer->Data)->DestinationAddress; + + peer = OvpnFindPeerVPN6(device, addr); + if (peer != nullptr) { + OvpnMssDoIPv6(buffer->Data, buffer->Len, peer->MSS); + } } - OvpnPeerContext* peer = OvpnGetFirstPeer(&device->Peers); - if (peer == NULL) { + if (peer == nullptr) { status = STATUS_ADDRESS_NOT_ASSOCIATED; OvpnTxBufferPoolPut(buffer); - LOG_WARN("No peer"); goto out; } InterlockedExchangeAddNoFence64(&device->Stats.TunBytesSent, buffer->Len); - if (peer->CryptoContext.Encrypt) { + auto irql = ExAcquireSpinLockShared(&peer->SpinLock); + + OvpnCryptoContext* cryptoContext = &peer->CryptoContext; + auto remoteAddr = peer->TransportAddrs.Remote; + auto timer = peer->Timer; + + if (cryptoContext->Encrypt) { + auto aeadTagEnd = cryptoContext->CryptoOptions & CRYPTO_OPTIONS_AEAD_TAG_END; + auto pktId64bit = cryptoContext->CryptoOptions & CRYPTO_OPTIONS_64BIT_PKTID; + // make space to crypto overhead - OvpnTxBufferPush(buffer, device->CryptoOverhead); + OvpnTxBufferPush(buffer, OVPN_DATA_V2_LEN + (pktId64bit ? 8 : 4) + (aeadTagEnd ? 0 : AEAD_AUTH_TAG_LEN)); + if (aeadTagEnd) + { + OvpnBufferPut(buffer, AEAD_AUTH_TAG_LEN); + } // in-place encrypt, always with primary key - status = peer->CryptoContext.Encrypt(&peer->CryptoContext.Primary, buffer->Data, buffer->Len); + status = cryptoContext->Encrypt(&cryptoContext->Primary, buffer->Data, buffer->Len, cryptoContext->CryptoOptions); } else { status = STATUS_INVALID_DEVICE_STATE; // LOG_WARN("CryptoContext not initialized"); } + ExReleaseSpinLockShared(&peer->SpinLock, irql); + + if (peer != nullptr) { + OvpnPeerCtxRelease(peer); + } if (NT_SUCCESS(status)) { // start async send, this will return ciphertext buffer to the pool if (device->Socket.Tcp) { - status = OvpnSocketSend(&device->Socket, buffer); + status = OvpnSocketSend(&device->Socket, buffer, NULL); } else { // for UDP we use SendMessages to send multiple datagrams at once @@ -118,17 +186,29 @@ OvpnTxProcessPacket(_In_ POVPN_DEVICE device, _In_ POVPN_TXQUEUE queue, _In_ NET buffer->WskBufList.Buffer.Mdl = buffer->Mdl; buffer->WskBufList.Buffer.Offset = FIELD_OFFSET(OVPN_TX_BUFFER, Head) + (ULONG)(buffer->Data - buffer->Head); - if (*head == NULL) { + // If this peer is different (head sockaddr != peer sockaddr) to the previous buffer chain peers, + // then flush those and restart with a new buffer list. + + if ((*head != NULL) && !(OvpnTxAreSockaddrEqual(headSockaddr, (const SOCKADDR*)&remoteAddr))) + { + LOG_IF_NOT_NT_SUCCESS(OvpnSocketSend(&device->Socket, *head, headSockaddr)); *head = buffer; + *tail = buffer; + OvpnTxCopyRemoteToSockaddr(remoteAddr, headSockaddr); + } else { + if (*head == NULL) { + *head = buffer; + OvpnTxCopyRemoteToSockaddr(remoteAddr, headSockaddr); + } + else { + (*tail)->WskBufList.Next = &buffer->WskBufList; + } + + *tail = buffer; } - else { - (*tail)->WskBufList.Next = &buffer->WskBufList; - } - - *tail = buffer; } - OvpnTimerReset(peer->KeepaliveXmitTimer, peer->KeepaliveInterval); + OvpnTimerResetXmit(timer); } else { OvpnTxBufferPoolPut(buffer); @@ -152,18 +232,17 @@ OvpnEvtTxQueueAdvance(NETPACKETQUEUE netPacketQueue) POVPN_TXQUEUE queue = OvpnGetTxQueueContext(netPacketQueue); NET_RING_PACKET_ITERATOR pi = NetRingGetAllPackets(queue->Rings); POVPN_DEVICE device = OvpnGetDeviceContext(queue->Adapter->WdfDevice); - bool packetSent = false; - - KIRQL kirql = ExAcquireSpinLockShared(&device->SpinLock); + BOOLEAN packetSent = false; OVPN_TX_BUFFER* txBufferHead = NULL; OVPN_TX_BUFFER* txBufferTail = NULL; + SOCKADDR headSockaddr = {0}; while (NetPacketIteratorHasAny(&pi)) { NET_PACKET* packet = NetPacketIteratorGetPacket(&pi); NTSTATUS status = STATUS_SUCCESS; if (!packet->Ignore && !packet->Scratch) { - status = OvpnTxProcessPacket(device, queue, &pi, &txBufferHead, &txBufferTail); + status = OvpnTxProcessPacket(device, queue, &pi, &txBufferHead, &txBufferTail, &headSockaddr); if (!NT_SUCCESS(status)) { InterlockedIncrementNoFence(&device->Stats.LostOutDataPackets); } @@ -179,12 +258,12 @@ OvpnEvtTxQueueAdvance(NETPACKETQUEUE netPacketQueue) } NetPacketIteratorSet(&pi); - if (packetSent && !device->Socket.Tcp) { - // this will use WskSendMessages to send buffers list which we constructed before - LOG_IF_NOT_NT_SUCCESS(OvpnSocketSend(&device->Socket, txBufferHead)); + if (packetSent) { + if (!device->Socket.Tcp) { + // this will use WskSendMessages to send buffers list which we constructed before + LOG_IF_NOT_NT_SUCCESS(OvpnSocketSend(&device->Socket, txBufferHead, &headSockaddr)); + } } - - ExReleaseSpinLockShared(&device->SpinLock, kirql); } _Use_decl_annotations_ diff --git a/uapi/ovpn-dco.h b/uapi/ovpn-dco.h index ea2a733..5a8201c 100644 --- a/uapi/ovpn-dco.h +++ b/uapi/ovpn-dco.h @@ -47,6 +47,23 @@ typedef struct _OVPN_NEW_PEER { OVPN_PROTO Proto; } OVPN_NEW_PEER, * POVPN_NEW_PEER; +typedef struct _OVPN_MP_NEW_PEER { + union { + SOCKADDR_IN Addr4; + SOCKADDR_IN6 Addr6; + } Local; + + union { + SOCKADDR_IN Addr4; + SOCKADDR_IN6 Addr6; + } Remote; + + IN_ADDR VpnAddr4; + IN6_ADDR VpnAddr6; + + int PeerId; +} OVPN_MP_NEW_PEER, * POVPN_MP_NEW_PEER; + typedef struct _OVPN_STATS { LONG LostInControlPackets; LONG LostOutControlPackets; @@ -94,10 +111,25 @@ typedef struct _OVPN_CRYPTO_DATA { int PeerId; } OVPN_CRYPTO_DATA, * POVPN_CRYPTO_DATA; +#define CRYPTO_OPTIONS_AEAD_TAG_END (1<<1) +#define CRYPTO_OPTIONS_64BIT_PKTID (1<<2) + +typedef struct _OVPN_CRYPTO_DATA_V2 { + OVPN_CRYPTO_DATA V1; + UINT32 CryptoOptions; +} OVPN_CRYPTO_DATA_V2, * POVPN_CRYPTO_DATA_V2; + +typedef struct _OVPN_MP_SET_PEER { + int PeerId; + LONG KeepaliveInterval; + LONG KeepaliveTimeout; + LONG MSS; +} OVPN_MP_SET_PEER, * POVPN_MP_SET_PEER; + typedef struct _OVPN_SET_PEER { - LONG KeepaliveInterval; - LONG KeepaliveTimeout; - LONG MSS; + LONG KeepaliveInterval; + LONG KeepaliveTimeout; + LONG MSS; } OVPN_SET_PEER, * POVPN_SET_PEER; typedef struct _OVPN_VERSION { @@ -106,6 +138,41 @@ typedef struct _OVPN_VERSION { LONG Patch; } OVPN_VERSION, * POVPN_VERSION; +typedef enum { + OVPN_MODE_P2P, + OVPN_MODE_MP +} OVPN_MODE; + +typedef struct _OVPN_SET_MODE { + OVPN_MODE Mode; +} OVPN_SET_MODE, * POVPN_SET_MODE; + +typedef struct _OVPN_MP_START_VPN { + union { + SOCKADDR_IN Addr4; + SOCKADDR_IN6 Addr6; + } ListenAddress; +} OVPN_MP_START_VPN, * POVPN_MP_START_VPN; + +typedef enum { + OVPN_NOTIFY_DEL_PEER, + OVPN_NOTIFY_ROTATE_KEY +} OVPN_NOTIFY_CMD; + +typedef enum { + OVPN_DEL_PEER_REASON_TEARDOWN, + OVPN_DEL_PEER_REASON_USERSPACE, + OVPN_DEL_PEER_REASON_EXPIRED, + OVPN_DEL_PEER_REASON_TRANSPORT_ERROR, + OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT +} OVPN_DEL_PEER_REASON; + +typedef struct _OVPN_NOTIFY_EVENT { + OVPN_NOTIFY_CMD Cmd; + int PeerId; + OVPN_DEL_PEER_REASON DelPeerReason; +} OVPN_NOTIFY_EVENT, * POVPN_NOTIFY_EVENT; + #define OVPN_IOCTL_NEW_PEER CTL_CODE(FILE_DEVICE_UNKNOWN, 1, METHOD_BUFFERED, FILE_ANY_ACCESS) #define OVPN_IOCTL_GET_STATS CTL_CODE(FILE_DEVICE_UNKNOWN, 2, METHOD_BUFFERED, FILE_ANY_ACCESS) #define OVPN_IOCTL_NEW_KEY CTL_CODE(FILE_DEVICE_UNKNOWN, 3, METHOD_BUFFERED, FILE_ANY_ACCESS) @@ -114,3 +181,11 @@ typedef struct _OVPN_VERSION { #define OVPN_IOCTL_START_VPN CTL_CODE(FILE_DEVICE_UNKNOWN, 6, METHOD_BUFFERED, FILE_ANY_ACCESS) #define OVPN_IOCTL_DEL_PEER CTL_CODE(FILE_DEVICE_UNKNOWN, 7, METHOD_BUFFERED, FILE_ANY_ACCESS) #define OVPN_IOCTL_GET_VERSION CTL_CODE(FILE_DEVICE_UNKNOWN, 8, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define OVPN_IOCTL_NEW_KEY_V2 CTL_CODE(FILE_DEVICE_UNKNOWN, 9, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define OVPN_IOCTL_SET_MODE CTL_CODE(FILE_DEVICE_UNKNOWN, 10, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define OVPN_IOCTL_MP_START_VPN CTL_CODE(FILE_DEVICE_UNKNOWN, 11, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define OVPN_IOCTL_MP_NEW_PEER CTL_CODE(FILE_DEVICE_UNKNOWN, 12, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define OVPN_IOCTL_MP_SET_PEER CTL_CODE(FILE_DEVICE_UNKNOWN, 13, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define OVPN_IOCTL_NOTIFY_EVENT CTL_CODE(FILE_DEVICE_UNKNOWN, 14, METHOD_BUFFERED, FILE_ANY_ACCESS)