diff --git a/CHANGELOG.md b/CHANGELOG.md index ce520a4..be410f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Build with CGO_ENABLED=0 set, to create more portable binaries. This could have an affect on DNS resolution if you rely on anything non-standard. (#421) +- Windows now uses the [wintun](https://www.wintun.net/) driver which does not require installation. This driver + is a large improvement over the TAP driver that was used in previous versions. If you had a previous version + of `nebula` running, you will want to disable the tap driver in Control Panel, or uninstall the `tap0901` driver + before running this version. (#289) + ### Deprecated - The `preferred_ranges` option has been supported as a replacement for diff --git a/control.go b/control.go index 8c93be2..c905d23 100644 --- a/control.go +++ b/control.go @@ -62,6 +62,9 @@ func (c *Control) Start() { func (c *Control) Stop() { //TODO: stop tun and udp routines, the lock on hostMap effectively does that though c.CloseAllTunnels(false) + if err := c.f.Close(); err != nil { + c.l.WithError(err).Error("Close interface failed") + } c.cancel() c.l.Info("Goodbye") } diff --git a/dist/windows/wintun/LICENSE.txt b/dist/windows/wintun/LICENSE.txt new file mode 100644 index 0000000..bc8d3f0 --- /dev/null +++ b/dist/windows/wintun/LICENSE.txt @@ -0,0 +1,84 @@ +Prebuilt Binaries License +------------------------- + +1. DEFINITIONS. "Software" means the precise contents of the "wintun.dll" + files that are included in the .zip file that contains this document as + downloaded from wintun.net/builds. + +2. LICENSE GRANT. WireGuard LLC grants to you a non-exclusive and + non-transferable right to use Software for lawful purposes under certain + obligations and limited rights as set forth in this agreement. + +3. RESTRICTIONS. Software is owned and copyrighted by WireGuard LLC. It is + licensed, not sold. Title to Software and all associated intellectual + property rights are retained by WireGuard. You must not: + a. reverse engineer, decompile, disassemble, extract from, or otherwise + modify the Software; + b. modify or create derivative work based upon Software in whole or in + parts, except insofar as only the API interfaces of the "wintun.h" file + distributed alongside the Software (the "Permitted API") are used; + c. remove any proprietary notices, labels, or copyrights from the Software; + d. resell, redistribute, lease, rent, transfer, sublicense, or otherwise + transfer rights of the Software without the prior written consent of + WireGuard LLC, except insofar as the Software is distributed alongside + other software that uses the Software only via the Permitted API; + e. use the name of WireGuard LLC, the WireGuard project, the Wintun + project, or the names of its contributors to endorse or promote products + derived from the Software without specific prior written consent. + +4. LIMITED WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF + ANY KIND. WIREGUARD LLC HEREBY EXCLUDES AND DISCLAIMS ALL IMPLIED OR + STATUTORY WARRANTIES, INCLUDING ANY WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE, QUALITY, NON-INFRINGEMENT, TITLE, RESULTS, + EFFORTS, OR QUIET ENJOYMENT. THERE IS NO WARRANTY THAT THE PRODUCT WILL BE + ERROR-FREE OR WILL FUNCTION WITHOUT INTERRUPTION. YOU ASSUME THE ENTIRE + RISK FOR THE RESULTS OBTAINED USING THE PRODUCT. TO THE EXTENT THAT + WIREGUARD LLC MAY NOT DISCLAIM ANY WARRANTY AS A MATTER OF APPLICABLE LAW, + THE SCOPE AND DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER + SUCH LAW. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR + A PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE DISCLAIMED, EXCEPT TO THE + EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID. + +5. LIMITATION OF LIABILITY. To the extent not prohibited by law, in no event + WireGuard LLC or any third-party-developer will be liable for any lost + revenue, profit or data or for special, indirect, consequential, incidental + or punitive damages, however caused regardless of the theory of liability, + arising out of or related to the use of or inability to use Software, even + if WireGuard LLC has been advised of the possibility of such damages. + Solely you are responsible for determining the appropriateness of using + Software and accept full responsibility for all risks associated with its + exercise of rights under this agreement, including but not limited to the + risks and costs of program errors, compliance with applicable laws, damage + to or loss of data, programs or equipment, and unavailability or + interruption of operations. The foregoing limitations will apply even if + the above stated warranty fails of its essential purpose. You acknowledge, + that it is in the nature of software that software is complex and not + completely free of errors. In no event shall WireGuard LLC or any + third-party-developer be liable to you under any theory for any damages + suffered by you or any user of Software or for any special, incidental, + indirect, consequential or similar damages (including without limitation + damages for loss of business profits, business interruption, loss of + business information or any other pecuniary loss) arising out of the use or + inability to use Software, even if WireGuard LLC has been advised of the + possibility of such damages and regardless of the legal or quitable theory + (contract, tort, or otherwise) upon which the claim is based. + +6. TERMINATION. This agreement is affected until terminated. You may + terminate this agreement at any time. This agreement will terminate + immediately without notice from WireGuard LLC if you fail to comply with + the terms and conditions of this agreement. Upon termination, you must + delete Software and all copies of Software and cease all forms of + distribution of Software. + +7. SEVERABILITY. If any provision of this agreement is held to be + unenforceable, this agreement will remain in effect with the provision + omitted, unless omission would frustrate the intent of the parties, in + which case this agreement will immediately terminate. + +8. RESERVATION OF RIGHTS. All rights not expressly granted in this agreement + are reserved by WireGuard LLC. For example, WireGuard LLC reserves the + right at any time to cease development of Software, to alter distribution + details, features, specifications, capabilities, functions, licensing + terms, release dates, APIs, ABIs, general availability, or other + characteristics of the Software. diff --git a/dist/windows/wintun/README.md b/dist/windows/wintun/README.md new file mode 100644 index 0000000..16e6a68 --- /dev/null +++ b/dist/windows/wintun/README.md @@ -0,0 +1,339 @@ +# [Wintun Network Adapter](https://www.wintun.net/) +### TUN Device Driver for Windows + +This is a layer 3 TUN driver for Windows 7, 8, 8.1, and 10. Originally created for [WireGuard](https://www.wireguard.com/), it is intended to be useful to a wide variety of projects that require layer 3 tunneling devices with implementations primarily in userspace. + +## Installation + +Wintun is deployed as a platform-specific `wintun.dll` file. Install the `wintun.dll` file side-by-side with your application. Download the dll from [wintun.net](https://www.wintun.net/), alongside the header file for your application described below. + +## Usage + +Include the [`wintun.h` file](https://git.zx2c4.com/wintun/tree/api/wintun.h) in your project simply by copying it there and dynamically load the `wintun.dll` using [`LoadLibraryEx()`](https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexa) and [`GetProcAddress()`](https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress) to resolve each function, using the typedefs provided in the header file. The [`InitializeWintun` function in the example.c code](https://git.zx2c4.com/wintun/tree/example/example.c) provides this in a function that you can simply copy and paste. + +With the library setup, Wintun can then be used by first creating an adapter, configuring it, and then setting its status to "up". Adapters have names (e.g. "OfficeNet") and types (e.g. "Wintun"). + +```C +WINTUN_ADAPTER_HANDLE Adapter1 = WintunCreateAdapter(L"OfficeNet", L"Wintun", &SomeFixedGUID1); +WINTUN_ADAPTER_HANDLE Adapter2 = WintunCreateAdapter(L"HomeNet", L"Wintun", &SomeFixedGUID2); +WINTUN_ADAPTER_HANDLE Adapter3 = WintunCreateAdapter(L"Data Center", L"Wintun", &SomeFixedGUID3); +``` + +After creating an adapter, we can use it by starting a session: + +```C +WINTUN_SESSION_HANDLE Session = WintunStartSession(Adapter2, 0x400000); +``` + +Then, the `WintunAllocateSendPacket` and `WintunSendPacket` functions can be used for sending packets ([used by `SendPackets` in the example.c code](https://git.zx2c4.com/wintun/tree/example/example.c)): + +```C +BYTE *OutgoingPacket = WintunAllocateSendPacket(Session, PacketDataSize); +if (OutgoingPacket) +{ + memcpy(OutgoingPacket, PacketData, PacketDataSize); + WintunSendPacket(Session, OutgoingPacket); +} +else if (GetLastError() != ERROR_BUFFER_OVERFLOW) // Silently drop packets if the ring is full + Log(L"Packet write failed"); +``` + +And the `WintunReceivePacket` and `WintunReleaseReceivePacket` functions can be used for receiving packets ([used by `ReceivePackets` in the example.c code](https://git.zx2c4.com/wintun/tree/example/example.c)): + +```C +for (;;) +{ + DWORD IncomingPacketSize; + BYTE *IncomingPacket = WintunReceivePacket(Session, &IncomingPacketSize); + if (IncomingPacket) + { + DoSomethingWithPacket(IncomingPacket, IncomingPacketSize); + WintunReleaseReceivePacket(Session, IncomingPacket); + } + else if (GetLastError() == ERROR_NO_MORE_ITEMS) + WaitForSingleObject(WintunGetReadWaitEvent(Session), INFINITE); + else + { + Log(L"Packet read failed"); + break; + } +} +``` + +Some high performance use cases may want to spin on `WintunReceivePackets` for a number of cycles before falling back to waiting on the read-wait event. + +You are **highly encouraged** to read the [**example.c short example**](https://git.zx2c4.com/wintun/tree/example/example.c) to see how to put together a simple userspace network tunnel. + +The various functions and definitions are [documented in the reference below](#Reference). + +## Reference + +### Macro Definitions + +#### WINTUN\_MAX\_POOL + +`#define WINTUN_MAX_POOL 256` + +Maximum pool name length including zero terminator + +#### WINTUN\_MIN\_RING\_CAPACITY + +`#define WINTUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */` + +Minimum ring capacity. + +#### WINTUN\_MAX\_RING\_CAPACITY + +`#define WINTUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */` + +Maximum ring capacity. + +#### WINTUN\_MAX\_IP\_PACKET\_SIZE + +`#define WINTUN_MAX_IP_PACKET_SIZE 0xFFFF` + +Maximum IP packet size + +### Typedefs + +#### WINTUN\_ADAPTER\_HANDLE + +`typedef void* WINTUN_ADAPTER_HANDLE` + +A handle representing Wintun adapter + +#### WINTUN\_ENUM\_CALLBACK + +`typedef BOOL(* WINTUN_ENUM_CALLBACK) (WINTUN_ADAPTER_HANDLE Adapter, LPARAM Param)` + +Called by WintunEnumAdapters for each adapter in the pool. + +**Parameters** + +- *Adapter*: Adapter handle, which will be freed when this function returns. +- *Param*: An application-defined value passed to the WintunEnumAdapters. + +**Returns** + +Non-zero to continue iterating adapters; zero to stop. + +#### WINTUN\_LOGGER\_CALLBACK + +`typedef void(* WINTUN_LOGGER_CALLBACK) (WINTUN_LOGGER_LEVEL Level, DWORD64 Timestamp, const WCHAR *Message)` + +Called by internal logger to report diagnostic messages + +**Parameters** + +- *Level*: Message level. +- *Timestamp*: Message timestamp in in 100ns intervals since 1601-01-01 UTC. +- *Message*: Message text. + +#### WINTUN\_SESSION\_HANDLE + +`typedef void* WINTUN_SESSION_HANDLE` + +A handle representing Wintun session + +### Enumeration Types + +#### WINTUN\_LOGGER\_LEVEL + +`enum WINTUN_LOGGER_LEVEL` + +Determines the level of logging, passed to WINTUN\_LOGGER\_CALLBACK. + +- *WINTUN\_LOG\_INFO*: Informational +- *WINTUN\_LOG\_WARN*: Warning +- *WINTUN\_LOG\_ERR*: Error + +Enumerator + +### Functions + +#### WintunCreateAdapter() + +`WINTUN_ADAPTER_HANDLE WintunCreateAdapter (const WCHAR * Name, const WCHAR * TunnelType, const GUID * RequestedGUID)` + +Creates a new Wintun adapter. + +**Parameters** + +- *Name*: The requested name of the adapter. Zero-terminated string of up to MAX\_ADAPTER\_NAME-1 characters. +- *Name*: Name of the adapter tunnel type. Zero-terminated string of up to MAX\_ADAPTER\_NAME-1 characters. +- *RequestedGUID*: The GUID of the created network adapter, which then influences NLA generation deterministically. If it is set to NULL, the GUID is chosen by the system at random, and hence a new NLA entry is created for each new adapter. It is called "requested" GUID because the API it uses is completely undocumented, and so there could be minor interesting complications with its usage. + +**Returns** + +If the function succeeds, the return value is the adapter handle. Must be released with WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call GetLastError. + +#### WintunOpenAdapter() + +`WINTUN_ADAPTER_HANDLE WintunOpenAdapter (const WCHAR * Name)` + +Opens an existing Wintun adapter. + +**Parameters** + +- *Name*: The requested name of the adapter. Zero-terminated string of up to MAX\_ADAPTER\_NAME-1 characters. + +**Returns** + +If the function succeeds, the return value is adapter handle. Must be released with WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call GetLastError. + +#### WintunCloseAdapter() + +`void WintunCloseAdapter (WINTUN_ADAPTER_HANDLE Adapter)` + +Releases Wintun adapter resources and, if adapter was created with WintunCreateAdapter, removes adapter. + +**Parameters** + +- *Adapter*: Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter. + +#### WintunDeleteDriver() + +`BOOL WintunDeleteDriver ()` + +Deletes the Wintun driver if there are no more adapters in use. + +**Returns** + +If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error information, call GetLastError. + +#### WintunGetAdapterLuid() + +`void WintunGetAdapterLuid (WINTUN_ADAPTER_HANDLE Adapter, NET_LUID * Luid)` + +Returns the LUID of the adapter. + +**Parameters** + +- *Adapter*: Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter +- *Luid*: Pointer to LUID to receive adapter LUID. + +#### WintunGetRunningDriverVersion() + +`DWORD WintunGetRunningDriverVersion (void )` + +Determines the version of the Wintun driver currently loaded. + +**Returns** + +If the function succeeds, the return value is the version number. If the function fails, the return value is zero. To get extended error information, call GetLastError. Possible errors include the following: ERROR\_FILE\_NOT\_FOUND Wintun not loaded + +#### WintunSetLogger() + +`void WintunSetLogger (WINTUN_LOGGER_CALLBACK NewLogger)` + +Sets logger callback function. + +**Parameters** + +- *NewLogger*: Pointer to callback function to use as a new global logger. NewLogger may be called from various threads concurrently. Should the logging require serialization, you must handle serialization in NewLogger. Set to NULL to disable. + +#### WintunStartSession() + +`WINTUN_SESSION_HANDLE WintunStartSession (WINTUN_ADAPTER_HANDLE Adapter, DWORD Capacity)` + +Starts Wintun session. + +**Parameters** + +- *Adapter*: Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter +- *Capacity*: Rings capacity. Must be between WINTUN\_MIN\_RING\_CAPACITY and WINTUN\_MAX\_RING\_CAPACITY (incl.) Must be a power of two. + +**Returns** + +Wintun session handle. Must be released with WintunEndSession. If the function fails, the return value is NULL. To get extended error information, call GetLastError. + +#### WintunEndSession() + +`void WintunEndSession (WINTUN_SESSION_HANDLE Session)` + +Ends Wintun session. + +**Parameters** + +- *Session*: Wintun session handle obtained with WintunStartSession + +#### WintunGetReadWaitEvent() + +`HANDLE WintunGetReadWaitEvent (WINTUN_SESSION_HANDLE Session)` + +Gets Wintun session's read-wait event handle. + +**Parameters** + +- *Session*: Wintun session handle obtained with WintunStartSession + +**Returns** + +Pointer to receive event handle to wait for available data when reading. Should WintunReceivePackets return ERROR\_NO\_MORE\_ITEMS (after spinning on it for a while under heavy load), wait for this event to become signaled before retrying WintunReceivePackets. Do not call CloseHandle on this event - it is managed by the session. + +#### WintunReceivePacket() + +`BYTE* WintunReceivePacket (WINTUN_SESSION_HANDLE Session, DWORD * PacketSize)` + +Retrieves one or packet. After the packet content is consumed, call WintunReleaseReceivePacket with Packet returned from this function to release internal buffer. This function is thread-safe. + +**Parameters** + +- *Session*: Wintun session handle obtained with WintunStartSession +- *PacketSize*: Pointer to receive packet size. + +**Returns** + +Pointer to layer 3 IPv4 or IPv6 packet. Client may modify its content at will. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Possible errors include the following: ERROR\_HANDLE\_EOF Wintun adapter is terminating; ERROR\_NO\_MORE\_ITEMS Wintun buffer is exhausted; ERROR\_INVALID\_DATA Wintun buffer is corrupt + +#### WintunReleaseReceivePacket() + +`void WintunReleaseReceivePacket (WINTUN_SESSION_HANDLE Session, const BYTE * Packet)` + +Releases internal buffer after the received packet has been processed by the client. This function is thread-safe. + +**Parameters** + +- *Session*: Wintun session handle obtained with WintunStartSession +- *Packet*: Packet obtained with WintunReceivePacket + +#### WintunAllocateSendPacket() + +`BYTE* WintunAllocateSendPacket (WINTUN_SESSION_HANDLE Session, DWORD PacketSize)` + +Allocates memory for a packet to send. After the memory is filled with packet data, call WintunSendPacket to send and release internal buffer. WintunAllocateSendPacket is thread-safe and the WintunAllocateSendPacket order of calls define the packet sending order. + +**Parameters** + +- *Session*: Wintun session handle obtained with WintunStartSession +- *PacketSize*: Exact packet size. Must be less or equal to WINTUN\_MAX\_IP\_PACKET\_SIZE. + +**Returns** + +Returns pointer to memory where to prepare layer 3 IPv4 or IPv6 packet for sending. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Possible errors include the following: ERROR\_HANDLE\_EOF Wintun adapter is terminating; ERROR\_BUFFER\_OVERFLOW Wintun buffer is full; + +#### WintunSendPacket() + +`void WintunSendPacket (WINTUN_SESSION_HANDLE Session, const BYTE * Packet)` + +Sends the packet and releases internal buffer. WintunSendPacket is thread-safe, but the WintunAllocateSendPacket order of calls define the packet sending order. This means the packet is not guaranteed to be sent in the WintunSendPacket yet. + +**Parameters** + +- *Session*: Wintun session handle obtained with WintunStartSession +- *Packet*: Packet obtained with WintunAllocateSendPacket + +## Building + +**Do not distribute drivers or files named "Wintun", as they will most certainly clash with official deployments. Instead distribute [`wintun.dll` as downloaded from wintun.net](https://www.wintun.net).** + +General requirements: + +- [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) with Windows SDK +- [Windows Driver Kit](https://docs.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk) + +`wintun.sln` may be opened in Visual Studio for development and building. Be sure to run `bcdedit /set testsigning on` and then reboot before to enable unsigned driver loading. The default run sequence (F5) in Visual Studio will build the example project and its dependencies. + +## License + +The entire contents of [the repository](https://git.zx2c4.com/wintun/), including all documentation and example code, is "Copyright © 2018-2021 WireGuard LLC. All Rights Reserved." Source code is licensed under the [GPLv2](COPYING). Prebuilt binaries from [wintun.net](https://www.wintun.net/) are released under a more permissive license suitable for more forms of software contained inside of the .zip files distributed there. diff --git a/dist/windows/wintun/bin/amd64/wintun.dll b/dist/windows/wintun/bin/amd64/wintun.dll new file mode 100644 index 0000000..aee04e7 Binary files /dev/null and b/dist/windows/wintun/bin/amd64/wintun.dll differ diff --git a/dist/windows/wintun/bin/arm/wintun.dll b/dist/windows/wintun/bin/arm/wintun.dll new file mode 100644 index 0000000..0017794 Binary files /dev/null and b/dist/windows/wintun/bin/arm/wintun.dll differ diff --git a/dist/windows/wintun/bin/arm64/wintun.dll b/dist/windows/wintun/bin/arm64/wintun.dll new file mode 100644 index 0000000..dc4e4ae Binary files /dev/null and b/dist/windows/wintun/bin/arm64/wintun.dll differ diff --git a/dist/windows/wintun/bin/x86/wintun.dll b/dist/windows/wintun/bin/x86/wintun.dll new file mode 100644 index 0000000..2ab97db Binary files /dev/null and b/dist/windows/wintun/bin/x86/wintun.dll differ diff --git a/dist/windows/wintun/include/wintun.h b/dist/windows/wintun/include/wintun.h new file mode 100644 index 0000000..55d441f --- /dev/null +++ b/dist/windows/wintun/include/wintun.h @@ -0,0 +1,270 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ALIGNED +# if defined(_MSC_VER) +# define ALIGNED(n) __declspec(align(n)) +# elif defined(__GNUC__) +# define ALIGNED(n) __attribute__((aligned(n))) +# else +# error "Unable to define ALIGNED" +# endif +#endif + +/* MinGW is missing this one, unfortunately. */ +#ifndef _Post_maybenull_ +# define _Post_maybenull_ +#endif + +#pragma warning(push) +#pragma warning(disable : 4324) /* structure was padded due to alignment specifier */ + +/** + * A handle representing Wintun adapter + */ +typedef struct _WINTUN_ADAPTER *WINTUN_ADAPTER_HANDLE; + +/** + * Creates a new Wintun adapter. + * + * @param Name The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1 + * characters. + * + * @param TunnelType Name of the adapter tunnel type. Zero-terminated string of up to MAX_ADAPTER_NAME-1 + * characters. + * + * @param RequestedGUID The GUID of the created network adapter, which then influences NLA generation deterministically. + * If it is set to NULL, the GUID is chosen by the system at random, and hence a new NLA entry is + * created for each new adapter. It is called "requested" GUID because the API it uses is + * completely undocumented, and so there could be minor interesting complications with its usage. + * + * @return If the function succeeds, the return value is the adapter handle. Must be released with + * WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call + * GetLastError. + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +WINTUN_ADAPTER_HANDLE(WINAPI WINTUN_CREATE_ADAPTER_FUNC) +(_In_z_ LPCWSTR Name, _In_z_ LPCWSTR TunnelType, _In_opt_ const GUID *RequestedGUID); + +/** + * Opens an existing Wintun adapter. + * + * @param Name The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1 + * characters. + * + * @return If the function succeeds, the return value is the adapter handle. Must be released with + * WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call + * GetLastError. + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +WINTUN_ADAPTER_HANDLE(WINAPI WINTUN_OPEN_ADAPTER_FUNC)(_In_z_ LPCWSTR Name); + +/** + * Releases Wintun adapter resources and, if adapter was created with WintunCreateAdapter, removes adapter. + * + * @param Adapter Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter. + */ +typedef VOID(WINAPI WINTUN_CLOSE_ADAPTER_FUNC)(_In_opt_ WINTUN_ADAPTER_HANDLE Adapter); + +/** + * Deletes the Wintun driver if there are no more adapters in use. + * + * @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To + * get extended error information, call GetLastError. + */ +typedef _Return_type_success_(return != FALSE) +BOOL(WINAPI WINTUN_DELETE_DRIVER_FUNC)(VOID); + +/** + * Returns the LUID of the adapter. + * + * @param Adapter Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter + * + * @param Luid Pointer to LUID to receive adapter LUID. + */ +typedef VOID(WINAPI WINTUN_GET_ADAPTER_LUID_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _Out_ NET_LUID *Luid); + +/** + * Determines the version of the Wintun driver currently loaded. + * + * @return If the function succeeds, the return value is the version number. If the function fails, the return value is + * zero. To get extended error information, call GetLastError. Possible errors include the following: + * ERROR_FILE_NOT_FOUND Wintun not loaded + */ +typedef _Return_type_success_(return != 0) +DWORD(WINAPI WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC)(VOID); + +/** + * Determines the level of logging, passed to WINTUN_LOGGER_CALLBACK. + */ +typedef enum +{ + WINTUN_LOG_INFO, /**< Informational */ + WINTUN_LOG_WARN, /**< Warning */ + WINTUN_LOG_ERR /**< Error */ +} WINTUN_LOGGER_LEVEL; + +/** + * Called by internal logger to report diagnostic messages + * + * @param Level Message level. + * + * @param Timestamp Message timestamp in in 100ns intervals since 1601-01-01 UTC. + * + * @param Message Message text. + */ +typedef VOID(CALLBACK *WINTUN_LOGGER_CALLBACK)( + _In_ WINTUN_LOGGER_LEVEL Level, + _In_ DWORD64 Timestamp, + _In_z_ LPCWSTR Message); + +/** + * Sets logger callback function. + * + * @param NewLogger Pointer to callback function to use as a new global logger. NewLogger may be called from various + * threads concurrently. Should the logging require serialization, you must handle serialization in + * NewLogger. Set to NULL to disable. + */ +typedef VOID(WINAPI WINTUN_SET_LOGGER_FUNC)(_In_ WINTUN_LOGGER_CALLBACK NewLogger); + +/** + * Minimum ring capacity. + */ +#define WINTUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */ + +/** + * Maximum ring capacity. + */ +#define WINTUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */ + +/** + * A handle representing Wintun session + */ +typedef struct _TUN_SESSION *WINTUN_SESSION_HANDLE; + +/** + * Starts Wintun session. + * + * @param Adapter Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter + * + * @param Capacity Rings capacity. Must be between WINTUN_MIN_RING_CAPACITY and WINTUN_MAX_RING_CAPACITY (incl.) + * Must be a power of two. + * + * @return Wintun session handle. Must be released with WintunEndSession. If the function fails, the return value is + * NULL. To get extended error information, call GetLastError. + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +WINTUN_SESSION_HANDLE(WINAPI WINTUN_START_SESSION_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _In_ DWORD Capacity); + +/** + * Ends Wintun session. + * + * @param Session Wintun session handle obtained with WintunStartSession + */ +typedef VOID(WINAPI WINTUN_END_SESSION_FUNC)(_In_ WINTUN_SESSION_HANDLE Session); + +/** + * Gets Wintun session's read-wait event handle. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @return Pointer to receive event handle to wait for available data when reading. Should + * WintunReceivePackets return ERROR_NO_MORE_ITEMS (after spinning on it for a while under heavy + * load), wait for this event to become signaled before retrying WintunReceivePackets. Do not call + * CloseHandle on this event - it is managed by the session. + */ +typedef HANDLE(WINAPI WINTUN_GET_READ_WAIT_EVENT_FUNC)(_In_ WINTUN_SESSION_HANDLE Session); + +/** + * Maximum IP packet size + */ +#define WINTUN_MAX_IP_PACKET_SIZE 0xFFFF + +/** + * Retrieves one or packet. After the packet content is consumed, call WintunReleaseReceivePacket with Packet returned + * from this function to release internal buffer. This function is thread-safe. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param PacketSize Pointer to receive packet size. + * + * @return Pointer to layer 3 IPv4 or IPv6 packet. Client may modify its content at will. If the function fails, the + * return value is NULL. To get extended error information, call GetLastError. Possible errors include the + * following: + * ERROR_HANDLE_EOF Wintun adapter is terminating; + * ERROR_NO_MORE_ITEMS Wintun buffer is exhausted; + * ERROR_INVALID_DATA Wintun buffer is corrupt + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +_Post_writable_byte_size_(*PacketSize) +BYTE *(WINAPI WINTUN_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _Out_ DWORD *PacketSize); + +/** + * Releases internal buffer after the received packet has been processed by the client. This function is thread-safe. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param Packet Packet obtained with WintunReceivePacket + */ +typedef VOID( + WINAPI WINTUN_RELEASE_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet); + +/** + * Allocates memory for a packet to send. After the memory is filled with packet data, call WintunSendPacket to send + * and release internal buffer. WintunAllocateSendPacket is thread-safe and the WintunAllocateSendPacket order of + * calls define the packet sending order. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param PacketSize Exact packet size. Must be less or equal to WINTUN_MAX_IP_PACKET_SIZE. + * + * @return Returns pointer to memory where to prepare layer 3 IPv4 or IPv6 packet for sending. If the function fails, + * the return value is NULL. To get extended error information, call GetLastError. Possible errors include the + * following: + * ERROR_HANDLE_EOF Wintun adapter is terminating; + * ERROR_BUFFER_OVERFLOW Wintun buffer is full; + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +_Post_writable_byte_size_(PacketSize) +BYTE *(WINAPI WINTUN_ALLOCATE_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ DWORD PacketSize); + +/** + * Sends the packet and releases internal buffer. WintunSendPacket is thread-safe, but the WintunAllocateSendPacket + * order of calls define the packet sending order. This means the packet is not guaranteed to be sent in the + * WintunSendPacket yet. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param Packet Packet obtained with WintunAllocateSendPacket + */ +typedef VOID(WINAPI WINTUN_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet); + +#pragma warning(pop) + +#ifdef __cplusplus +} +#endif diff --git a/go.mod b/go.mod index c4cda83..21ae4d7 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,9 @@ require ( github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 - golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 + golang.org/x/sys v0.0.0-20211103235746-7861aae1554b + golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 + golang.zx2c4.com/wireguard/windows v0.5.1 google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index b991d7e..866bf7e 100644 --- a/go.sum +++ b/go.sum @@ -160,6 +160,8 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= +github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= @@ -226,6 +228,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -270,6 +273,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -301,6 +305,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU= golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -357,14 +363,17 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc= -golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -374,6 +383,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -419,11 +429,16 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= +golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +golang.zx2c4.com/wireguard/windows v0.5.1 h1:OnYw96PF+CsIMrqWo5QP3Q59q5hY1rFErk/yN3cS+JQ= +golang.zx2c4.com/wireguard/windows v0.5.1/go.mod h1:EApyTk/ZNrkbZjurHL1nleDYnsPpJYBO7LZEBCyDAHk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= diff --git a/interface.go b/interface.go index c95a354..bf79f0f 100644 --- a/interface.go +++ b/interface.go @@ -7,6 +7,7 @@ import ( "net" "os" "runtime" + "sync/atomic" "time" "github.com/rcrowley/go-metrics" @@ -72,6 +73,7 @@ type Interface struct { routines int caPool *cert.NebulaCAPool disconnectInvalid bool + closed int32 // rebindCount is used to decide if an active tunnel should trigger a punch notification through a lighthouse rebindCount int8 @@ -173,6 +175,7 @@ func (f *Interface) activate() { } if err := f.inside.Activate(); err != nil { + f.inside.Close() f.l.Fatal(err) } } @@ -218,6 +221,10 @@ func (f *Interface) listenIn(reader io.ReadWriteCloser, i int) { for { n, err := reader.Read(packet) if err != nil { + if errors.Is(err, os.ErrClosed) && atomic.LoadInt32(&f.closed) != 0 { + return + } + f.l.WithError(err).Error("Error while reading outbound packet") // This only seems to happen when something fatal happens to the fd, so exit. os.Exit(2) @@ -325,3 +332,10 @@ func (f *Interface) emitStats(ctx context.Context, i time.Duration) { } } } + +func (f *Interface) Close() error { + atomic.StoreInt32(&f.closed, 1) + + // Release the tun device + return f.inside.Close() +} diff --git a/tun_water_windows.go b/tun_water_windows.go new file mode 100644 index 0000000..5544471 --- /dev/null +++ b/tun_water_windows.go @@ -0,0 +1,107 @@ +package nebula + +import ( + "fmt" + "io" + "net" + "os/exec" + "strconv" + + "github.com/songgao/water" +) + +type WindowsWaterTun struct { + Device string + Cidr *net.IPNet + MTU int + UnsafeRoutes []route + + *water.Interface +} + +func newWindowsWaterTun(deviceName string, cidr *net.IPNet, defaultMTU int, unsafeRoutes []route, txQueueLen int) (ifce *WindowsWaterTun, err error) { + // NOTE: You cannot set the deviceName under Windows, so you must check tun.Device after calling .Activate() + return &WindowsWaterTun{ + Cidr: cidr, + MTU: defaultMTU, + UnsafeRoutes: unsafeRoutes, + }, nil +} + +func (c *WindowsWaterTun) Activate() error { + var err error + c.Interface, err = water.New(water.Config{ + DeviceType: water.TUN, + PlatformSpecificParams: water.PlatformSpecificParams{ + ComponentID: "tap0901", + Network: c.Cidr.String(), + }, + }) + if err != nil { + return fmt.Errorf("Activate failed: %v", err) + } + + c.Device = c.Interface.Name() + + // TODO use syscalls instead of exec.Command + err = exec.Command( + `C:\Windows\System32\netsh.exe`, "interface", "ipv4", "set", "address", + fmt.Sprintf("name=%s", c.Device), + "source=static", + fmt.Sprintf("addr=%s", c.Cidr.IP), + fmt.Sprintf("mask=%s", net.IP(c.Cidr.Mask)), + "gateway=none", + ).Run() + if err != nil { + return fmt.Errorf("failed to run 'netsh' to set address: %s", err) + } + err = exec.Command( + `C:\Windows\System32\netsh.exe`, "interface", "ipv4", "set", "interface", + c.Device, + fmt.Sprintf("mtu=%d", c.MTU), + ).Run() + if err != nil { + return fmt.Errorf("failed to run 'netsh' to set MTU: %s", err) + } + + iface, err := net.InterfaceByName(c.Device) + if err != nil { + return fmt.Errorf("failed to find interface named %s: %v", c.Device, err) + } + + for _, r := range c.UnsafeRoutes { + err = exec.Command( + "C:\\Windows\\System32\\route.exe", "add", r.route.String(), r.via.String(), "IF", strconv.Itoa(iface.Index), "METRIC", strconv.Itoa(r.metric), + ).Run() + if err != nil { + return fmt.Errorf("failed to add the unsafe_route %s: %v", r.route.String(), err) + } + } + + return nil +} + +func (c *WindowsWaterTun) CidrNet() *net.IPNet { + return c.Cidr +} + +func (c *WindowsWaterTun) DeviceName() string { + return c.Device +} + +func (c *WindowsWaterTun) WriteRaw(b []byte) error { + _, err := c.Write(b) + return err +} + +func (c *WindowsWaterTun) Close() error { + if c.Interface == nil { + return nil + } + + return c.Interface.Close() +} + +func (t *WindowsWaterTun) NewMultiQueueReader() (io.ReadWriteCloser, error) { + return nil, fmt.Errorf("TODO: multiqueue not implemented for windows") +} diff --git a/tun_windows.go b/tun_windows.go index a06070d..5c73459 100644 --- a/tun_windows.go +++ b/tun_windows.go @@ -7,28 +7,16 @@ import ( "fmt" "io" "net" - "os/exec" - "strconv" + "os" + "path/filepath" + "runtime" + "syscall" "github.com/sirupsen/logrus" - "github.com/songgao/water" ) type Tun struct { - Device string - Cidr *net.IPNet - MTU int - UnsafeRoutes []route - l *logrus.Logger - - *water.Interface -} - -func (c *Tun) Close() error { - if c.Interface != nil { - return c.Interface.Close() - } - return nil + Inside } func newTunFromFd(l *logrus.Logger, deviceFd int, cidr *net.IPNet, defaultMTU int, routes []route, unsafeRoutes []route, txQueueLen int) (ifce *Tun, err error) { @@ -40,78 +28,44 @@ func newTun(l *logrus.Logger, deviceName string, cidr *net.IPNet, defaultMTU int return nil, fmt.Errorf("route MTU not supported in Windows") } - // NOTE: You cannot set the deviceName under Windows, so you must check tun.Device after calling .Activate() - return &Tun{ - Cidr: cidr, - MTU: defaultMTU, - UnsafeRoutes: unsafeRoutes, - l: l, - }, nil -} - -func (c *Tun) Activate() error { - var err error - c.Interface, err = water.New(water.Config{ - DeviceType: water.TUN, - PlatformSpecificParams: water.PlatformSpecificParams{ - ComponentID: "tap0901", - Network: c.Cidr.String(), - }, - }) - if err != nil { - return fmt.Errorf("Activate failed: %v", err) + useWintun := true + if err = checkWinTunExists(); err != nil { + l.WithError(err).Warn("Check Wintun driver failed, fallback to wintap driver") + useWintun = false } - c.Device = c.Interface.Name() - - // TODO use syscalls instead of exec.Command - err = exec.Command( - `C:\Windows\System32\netsh.exe`, "interface", "ipv4", "set", "address", - fmt.Sprintf("name=%s", c.Device), - "source=static", - fmt.Sprintf("addr=%s", c.Cidr.IP), - fmt.Sprintf("mask=%s", net.IP(c.Cidr.Mask)), - "gateway=none", - ).Run() - if err != nil { - return fmt.Errorf("failed to run 'netsh' to set address: %s", err) - } - err = exec.Command( - `C:\Windows\System32\netsh.exe`, "interface", "ipv4", "set", "interface", - c.Device, - fmt.Sprintf("mtu=%d", c.MTU), - ).Run() - if err != nil { - return fmt.Errorf("failed to run 'netsh' to set MTU: %s", err) - } - - iface, err := net.InterfaceByName(c.Device) - if err != nil { - return fmt.Errorf("failed to find interface named %s: %v", c.Device, err) - } - - for _, r := range c.UnsafeRoutes { - err = exec.Command( - "C:\\Windows\\System32\\route.exe", "add", r.route.String(), r.via.String(), "IF", strconv.Itoa(iface.Index), "METRIC", strconv.Itoa(r.metric), - ).Run() + var inside Inside + if useWintun { + inside, err = newWinTun(deviceName, cidr, defaultMTU, unsafeRoutes, txQueueLen) if err != nil { - return fmt.Errorf("failed to add the unsafe_route %s: %v", r.route.String(), err) + return nil, fmt.Errorf("Create Wintun interface failed, %w", err) + } + } else { + inside, err = newWindowsWaterTun(deviceName, cidr, defaultMTU, unsafeRoutes, txQueueLen) + if err != nil { + return nil, fmt.Errorf("Create wintap driver failed, %w", err) } } - return nil + return &Tun{ + Inside: inside, + }, nil } -func (c *Tun) CidrNet() *net.IPNet { - return c.Cidr -} +func checkWinTunExists() error { + myPath, err := os.Executable() + if err != nil { + return err + } -func (c *Tun) DeviceName() string { - return c.Device -} + arch := runtime.GOARCH + switch arch { + case "386": + //NOTE: wintun bundles 386 as x86 + arch = "x86" + } -func (c *Tun) WriteRaw(b []byte) error { - _, err := c.Write(b) + _, err = syscall.LoadDLL(filepath.Join(filepath.Dir(myPath), "dist", "windows", "wintun", "bin", arch, "wintun.dll")) return err } diff --git a/tun_wintun_windows.go b/tun_wintun_windows.go new file mode 100644 index 0000000..b39c06c --- /dev/null +++ b/tun_wintun_windows.go @@ -0,0 +1,153 @@ +package nebula + +import ( + "crypto" + "fmt" + "io" + "net" + "unsafe" + + "github.com/slackhq/nebula/wintun" + "golang.org/x/sys/windows" + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" +) + +const tunGUIDLabel = "Fixed Nebula Windows GUID v1" + +type WinTun struct { + Device string + Cidr *net.IPNet + MTU int + UnsafeRoutes []route + + tun *wintun.NativeTun +} + +func generateGUIDByDeviceName(name string) (*windows.GUID, error) { + // GUID is 128 bit + hash := crypto.MD5.New() + + _, err := hash.Write([]byte(tunGUIDLabel)) + if err != nil { + return nil, err + } + + _, err = hash.Write([]byte(name)) + if err != nil { + return nil, err + } + + sum := hash.Sum(nil) + + return (*windows.GUID)(unsafe.Pointer(&sum[0])), nil +} + +func newWinTun(deviceName string, cidr *net.IPNet, defaultMTU int, unsafeRoutes []route, txQueueLen int) (ifce *WinTun, err error) { + guid, err := generateGUIDByDeviceName(deviceName) + if err != nil { + return nil, fmt.Errorf("Generate GUID failed: %w", err) + } + + tunDevice, err := wintun.CreateTUNWithRequestedGUID(deviceName, guid, defaultMTU) + if err != nil { + return nil, fmt.Errorf("Create TUN device failed: %w", err) + } + + ifce = &WinTun{ + Device: deviceName, + Cidr: cidr, + MTU: defaultMTU, + UnsafeRoutes: unsafeRoutes, + + tun: tunDevice.(*wintun.NativeTun), + } + + return ifce, nil +} + +func (c *WinTun) Activate() error { + luid := winipcfg.LUID(c.tun.LUID()) + + if err := luid.SetIPAddresses([]net.IPNet{*c.Cidr}); err != nil { + return fmt.Errorf("failed to set address: %w", err) + } + + foundDefault4 := false + routes := make([]*winipcfg.RouteData, 0, len(c.UnsafeRoutes)+1) + + for _, r := range c.UnsafeRoutes { + if !foundDefault4 { + if cidr, bits := r.route.Mask.Size(); cidr == 0 && bits != 0 { + foundDefault4 = true + } + } + + // Add our unsafe route + routes = append(routes, &winipcfg.RouteData{ + Destination: *r.route, + NextHop: *r.via, + Metric: uint32(r.metric), + }) + } + + if err := luid.AddRoutes(routes); err != nil { + return fmt.Errorf("failed to add routes: %w", err) + } + + ipif, err := luid.IPInterface(windows.AF_INET) + if err != nil { + return fmt.Errorf("failed to get ip interface: %w", err) + } + + ipif.NLMTU = uint32(c.MTU) + if foundDefault4 { + ipif.UseAutomaticMetric = false + ipif.Metric = 0 + } + + if err := ipif.Set(); err != nil { + return fmt.Errorf("failed to set ip interface: %w", err) + } + + return nil +} + +func (c *WinTun) CidrNet() *net.IPNet { + return c.Cidr +} + +func (c *WinTun) DeviceName() string { + return c.Device +} + +func (c *WinTun) Read(b []byte) (int, error) { + return c.tun.Read(b, 0) +} + +func (c *WinTun) Write(b []byte) (int, error) { + return c.tun.Write(b, 0) +} + +func (c *WinTun) WriteRaw(b []byte) error { + _, err := c.Write(b) + return err +} + +func (c *WinTun) NewMultiQueueReader() (io.ReadWriteCloser, error) { + return nil, fmt.Errorf("TODO: multiqueue not implemented for windows") +} + +func (c *WinTun) Close() error { + // It seems that the Windows networking stack doesn't like it when we destroy interfaces that have active routes, + // so to be certain, just remove everything before destroying. + luid := winipcfg.LUID(c.tun.LUID()) + _ = luid.FlushRoutes(windows.AF_INET) + _ = luid.FlushIPAddresses(windows.AF_INET) + /* We don't support IPV6 yet + _ = luid.FlushRoutes(windows.AF_INET6) + _ = luid.FlushIPAddresses(windows.AF_INET6) + */ + _ = luid.FlushDNS(windows.AF_INET) + + return c.tun.Close() +} diff --git a/wintun/device.go b/wintun/device.go new file mode 100644 index 0000000..34bc290 --- /dev/null +++ b/wintun/device.go @@ -0,0 +1,23 @@ +//go:build windows + +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. + */ + +//NOTE: this file was forked from https://git.zx2c4.com/wireguard-go/tree/tun/tun.go?id=851efb1bb65555e0f765a3361c8eb5ac47435b19 + +package wintun + +import ( + "os" +) + +type Device interface { + File() *os.File // returns the file descriptor of the device + Read([]byte, int) (int, error) // read a packet from the device (without any additional headers) + Write([]byte, int) (int, error) // writes a packet to the device (without any additional headers) + Flush() error // flush all previous writes to the device + Name() (string, error) // fetches and returns the current name + Close() error // stops the device and closes the event channel +} diff --git a/wintun/tun.go b/wintun/tun.go new file mode 100644 index 0000000..0c677e5 --- /dev/null +++ b/wintun/tun.go @@ -0,0 +1,212 @@ +//go:build windows + +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +//NOTE: This file was forked from https://git.zx2c4.com/wireguard-go/tree/tun/tun_windows.go?id=851efb1bb65555e0f765a3361c8eb5ac47435b19 +// Mainly to shed functionality we won't be using and to fix names that display in the system + +package wintun + +import ( + "errors" + "fmt" + "os" + "sync" + "sync/atomic" + "time" + _ "unsafe" + + "golang.org/x/sys/windows" + + "golang.zx2c4.com/wintun" +) + +const ( + rateMeasurementGranularity = uint64((time.Second / 2) / time.Nanosecond) + spinloopRateThreshold = 800000000 / 8 // 800mbps + spinloopDuration = uint64(time.Millisecond / 80 / time.Nanosecond) // ~1gbit/s +) + +type rateJuggler struct { + current uint64 + nextByteCount uint64 + nextStartTime int64 + changing int32 +} + +type NativeTun struct { + wt *wintun.Adapter + name string + handle windows.Handle + rate rateJuggler + session wintun.Session + readWait windows.Handle + running sync.WaitGroup + closeOnce sync.Once + close int32 +} + +var WintunTunnelType = "Nebula" +var WintunStaticRequestedGUID *windows.GUID + +//go:linkname procyield runtime.procyield +func procyield(cycles uint32) + +//go:linkname nanotime runtime.nanotime +func nanotime() int64 + +// +// CreateTUN creates a Wintun interface with the given name. Should a Wintun +// interface with the same name exist, it is reused. +// +func CreateTUN(ifname string, mtu int) (Device, error) { + return CreateTUNWithRequestedGUID(ifname, WintunStaticRequestedGUID, mtu) +} + +// +// CreateTUNWithRequestedGUID creates a Wintun interface with the given name and +// a requested GUID. Should a Wintun interface with the same name exist, it is reused. +// +func CreateTUNWithRequestedGUID(ifname string, requestedGUID *windows.GUID, mtu int) (Device, error) { + wt, err := wintun.CreateAdapter(ifname, WintunTunnelType, requestedGUID) + if err != nil { + return nil, fmt.Errorf("Error creating interface: %w", err) + } + + tun := &NativeTun{ + wt: wt, + name: ifname, + handle: windows.InvalidHandle, + } + + tun.session, err = wt.StartSession(0x800000) // Ring capacity, 8 MiB + if err != nil { + tun.wt.Close() + return nil, fmt.Errorf("Error starting session: %w", err) + } + tun.readWait = tun.session.ReadWaitEvent() + return tun, nil +} + +func (tun *NativeTun) Name() (string, error) { + return tun.name, nil +} + +func (tun *NativeTun) File() *os.File { + return nil +} + +func (tun *NativeTun) Close() error { + var err error + tun.closeOnce.Do(func() { + atomic.StoreInt32(&tun.close, 1) + windows.SetEvent(tun.readWait) + tun.running.Wait() + tun.session.End() + if tun.wt != nil { + tun.wt.Close() + } + }) + return err +} + +// Note: Read() and Write() assume the caller comes only from a single thread; there's no locking. + +func (tun *NativeTun) Read(buff []byte, offset int) (int, error) { + tun.running.Add(1) + defer tun.running.Done() +retry: + if atomic.LoadInt32(&tun.close) == 1 { + return 0, os.ErrClosed + } + start := nanotime() + shouldSpin := atomic.LoadUint64(&tun.rate.current) >= spinloopRateThreshold && uint64(start-atomic.LoadInt64(&tun.rate.nextStartTime)) <= rateMeasurementGranularity*2 + for { + if atomic.LoadInt32(&tun.close) == 1 { + return 0, os.ErrClosed + } + packet, err := tun.session.ReceivePacket() + switch err { + case nil: + packetSize := len(packet) + copy(buff[offset:], packet) + tun.session.ReleaseReceivePacket(packet) + tun.rate.update(uint64(packetSize)) + return packetSize, nil + case windows.ERROR_NO_MORE_ITEMS: + if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration { + windows.WaitForSingleObject(tun.readWait, windows.INFINITE) + goto retry + } + procyield(1) + continue + case windows.ERROR_HANDLE_EOF: + return 0, os.ErrClosed + case windows.ERROR_INVALID_DATA: + return 0, errors.New("Send ring corrupt") + } + return 0, fmt.Errorf("Read failed: %w", err) + } +} + +func (tun *NativeTun) Flush() error { + return nil +} + +func (tun *NativeTun) Write(buff []byte, offset int) (int, error) { + tun.running.Add(1) + defer tun.running.Done() + if atomic.LoadInt32(&tun.close) == 1 { + return 0, os.ErrClosed + } + + packetSize := len(buff) - offset + tun.rate.update(uint64(packetSize)) + + packet, err := tun.session.AllocateSendPacket(packetSize) + if err == nil { + copy(packet, buff[offset:]) + tun.session.SendPacket(packet) + return packetSize, nil + } + switch err { + case windows.ERROR_HANDLE_EOF: + return 0, os.ErrClosed + case windows.ERROR_BUFFER_OVERFLOW: + return 0, nil // Dropping when ring is full. + } + return 0, fmt.Errorf("Write failed: %w", err) +} + +// LUID returns Windows interface instance ID. +func (tun *NativeTun) LUID() uint64 { + tun.running.Add(1) + defer tun.running.Done() + if atomic.LoadInt32(&tun.close) == 1 { + return 0 + } + return tun.wt.LUID() +} + +// RunningVersion returns the running version of the Wintun driver. +func (tun *NativeTun) RunningVersion() (version uint32, err error) { + return wintun.RunningVersion() +} + +func (rate *rateJuggler) update(packetLen uint64) { + now := nanotime() + total := atomic.AddUint64(&rate.nextByteCount, packetLen) + period := uint64(now - atomic.LoadInt64(&rate.nextStartTime)) + if period >= rateMeasurementGranularity { + if !atomic.CompareAndSwapInt32(&rate.changing, 0, 1) { + return + } + atomic.StoreInt64(&rate.nextStartTime, now) + atomic.StoreUint64(&rate.current, total*uint64(time.Second/time.Nanosecond)/period) + atomic.StoreUint64(&rate.nextByteCount, 0) + atomic.StoreInt32(&rate.changing, 0) + } +}