diff --git a/README.md b/README.md index 98107d3..0745d17 100644 --- a/README.md +++ b/README.md @@ -5,158 +5,123 @@ This is a layer 3 TUN driver for Windows 7, 8, 8.1, and 10. Originally created f ## Installation -The following snippet may be used inside of a WiX `` element for including Wintun inside of a Windows Installer. Note that **MSI is the only supported method of installing Wintun**; if you're using a different install system (such as NSIS) and cannot bother to switch to MSI/WiX, then simply bundle an embedded MSI that you can execute with `msiexec.exe`. - -```xml - - - - - - -``` - -It is advisable to use the prebuilt and Microsoft-signed MSM files from [Wintun.net](https://www.wintun.net/). +Wintun is deployed as a platform-specific `wintun.dll` file. Install the `wintun.dll` file side-by-side with your application. ## Usage -After loading the driver and creating a network interface the typical way using [SetupAPI](https://docs.microsoft.com/en-us/windows-hardware/drivers/install/setupapi), open the NDIS device object associated with the PnPInstanceId, enabling all forms of file sharing: +Include `wintun.h` file in your project 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). + +Each function has its function typedef in `wintun.h` with additional usage documentation. ```C -TCHAR *InterfaceList = NULL; +#include "wintun.h" +⋮ +HMODULE Wintun = LoadLibraryExW(L"wintun.dll", NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR); +if (!Wintun) + return GetLastError(); +WINTUN_CREATE_ADAPTER_FUNC WintunCreateAdapter = (WINTUN_CREATE_ADAPTER_FUNC)GetProcAddress(Wintun, "WintunCreateAdapter"); +WINTUN_DELETE_ADAPTER_FUNC WintunDeleteAdapter = (WINTUN_DELETE_ADAPTER_FUNC)GetProcAddress(Wintun, "WintunDeleteAdapter"); +``` + +### Adapter management + +Adapters are grouped together in pools to allow various clients on the same machine. Each client vendor should pick own unique pool name. + +Manage the network adapters using following functions: + +- `WintunCreateAdapter()` creates a new adapter. +- `WintunDeleteAdapter()` deletes the adapter. +- `WintunEnumAdapters()` enumerates all existing adapters. +- `WintunGetAdapter()` gets existing adapter handle. +- `WintunFreeAdapter()` frees adapter handle. +- `WintunGetAdapterDeviceObject()` opens adapter device object. +- `WintunGetAdapterGUID()` gets adapter GUID. +- `WintunGetAdapterLUID()` gets adapter LUID. +- `WintunGetAdapterName()` gets adapter name. +- `WintunSetAdapterName()` sets adapter name. + +Example: + +```C +DWORD Result; +WINTUN_ADAPTER_HANDLE Adapter; +BOOL RebootRequired; +Result = WintunCreateAdapter(L"com.contoso.myapp", "My VPN adapter", NULL, &Adapter, &RebootRequired); +if (Result != ERROR_SUCCESS) + return Result; +``` + +### Session management + +Once adapter is created, use the following functions to start a session and transfer packets: + +- `WintunStartSession()` starts a session. One adapter may have only one session. +- `WintunEndSession()` ends and frees the session. +- `WintunIsPacketAvailable()` checks if there is a receive packet available. +- `WintunWaitForPacket()` waits for a receive packet to become available. +- `WintunReceivePackets()` receives one or more packets. +- `WintunSendPackets()` sends one or more packets. + +#### Writing to and from rings + +Reading packets from the adapter may be done as: + +```C +// TODO: Preallocate WINTUN_PACKET *Queue linked list with WINTUN_MAX_IP_PACKET_SIZE packets. for (;;) { - free(InterfaceList); - DWORD RequiredBytes; - if (CM_Get_Device_Interface_List_Size(&RequiredBytes, (LPGUID)&GUID_DEVINTERFACE_NET, - InstanceId, CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS) - return FALSE; - InterfaceList = calloc(sizeof(*InterfaceList), RequiredBytes); - if (!InterfaceList) - return FALSE; - CONFIGRET Ret = CM_Get_Device_Interface_List((LPGUID)&GUID_DEVINTERFACE_NET, InstanceId, - InterfaceList, RequiredBytes, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); - if (Ret == CR_SUCCESS) - break; - if (Ret != CR_BUFFER_SMALL) { - free(InterfaceList); - return FALSE; + if (!WintunIsPacketAvailable(Session)) + WintunWaitForPacket(Session, INFINITE); + for (WINTUN_PACKET *p = Queue; p && p->Size <= WINTUN_MAX_IP_PACKET_SIZE; p = p->Next) + p->Size = DWORD_MAX; + DWORD Result = WintunReceivePackets(Session, Queue); + if (Result != ERROR_SUCCESS && Result != ERROR_NO_MORE_ITEMS) + return Result; + for (WINTUN_PACKET *p = Queue; p && p->Size <= WINTUN_MAX_IP_PACKET_SIZE; p = p->Next) { + // TODO: Process packet p. } } -HANDLE WintunHandle = CreateFile(InterfaceList, GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, 0, NULL); -free(InterfaceList); -... ``` -### Ring layout +It may be desirable to spin on `WintunIsPacketAvailable()` for some time under heavy use before waiting with `WintunWaitForPacket()`, in order to reduce latency. -You must allocate two ring structs, one for receiving and one for sending: +Writing packets to the adapter may be done as: ```C -typedef struct _TUN_RING { - volatile ULONG Head; - volatile ULONG Tail; - volatile LONG Alertable; - UCHAR Data[]; -} TUN_RING; +// TODO: Prepare WINTUN_PACKET *Queue linked list with packets. +DWORD Result = WintunSendPackets(Session, Queue); ``` -- `Head`: Byte offset of the first packet in the ring. Its value must be a multiple of 4 and less than ring capacity. +### Misc functions -- `Tail`: Byte offset of the start of free space in the ring. Its value must be multiple of 4 and less than ring capacity. +Other `wintun.dll` functions are: -- `Alertable`: Zero when the consumer is processing packets, non-zero when the consumer has processed all packets and is waiting for `TailMoved` event. +- `WintunGetVersion()` returns driver and NDIS major and minor versions. +- `WintunSetLogger()` sets global logging callback function. -- `Data`: The ring data. - -In order to determine the size of the `Data` array: - -1. Pick a ring capacity ranging from 128kiB to 64MiB bytes. This capacity must be a power of two (e.g. 1MiB). The ring can hold up to this much data. -2. Add 0x10000 trailing bytes to the capacity, in order to allow for always-contigious packet segments. - -The total ring size memory is then `sizeof(TUN_RING) + capacity + 0x10000`. - -Each packet is stored in the ring aligned to `sizeof(ULONG)` as: +Example: ```C -typedef struct _TUN_PACKET { - ULONG Size; - UCHAR Data[]; -} TUN_PACKET; -``` - -- `Size`: Size of packet (max 0xFFFF). - -- `Data`: Layer 3 IPv4 or IPv6 packet. - -### Registering rings - -In order to register the two `TUN_RING`s, prepare a registration struct as: - -```C -typedef struct _TUN_REGISTER_RINGS { - struct { - ULONG RingSize; - TUN_RING *Ring; - HANDLE TailMoved; - } Send, Receive; -} TUN_REGISTER_RINGS; -``` - -- `Send.RingSize`, `Receive.RingSize`: Sizes of the rings (`sizeof(TUN_RING) + capacity + 0x10000`, as above). - -- `Send.Ring`, `Receive.Ring`: Pointers to the rings. - -- `Send.TailMoved`: A handle to an [`auto-reset event`](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventa) created by the client that Wintun signals after it moves the `Tail` member of the send ring. - -- `Receive.TailMoved`: A handle to an [`auto-reset event`](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventa) created by the client that the client will signal when it changes `Receive.Ring->Tail` and `Receive.Ring->Alertable` is non-zero. - -With events created, send and receive rings allocated, and registration struct populated, [`DeviceIoControl`](https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol)(`TUN_IOCTL_REGISTER_RINGS`: 0xca6ce5c0) with pointer and size of descriptor struct specified as `lpInBuffer` and `nInBufferSize` parameters. You may call `TUN_IOCTL_REGISTER_RINGS` on one handle only. - - -### Writing to and from rings - -Reading packets from the send ring may be done as: - -```C -for (;;) { - TUN_PACKET *Next = PopFromRing(Rings->Send.Ring); - if (!Next) { - Rings->Send.Ring->Alertable = TRUE; - Next = PopFromRing(Rings->Send.Ring); - if (!Next) { - WaitForSingleObject(Rings->Send.TailMoved, INFINITE); - Rings->Send.Ring->Alertable = FALSE; - continue; - } - Rings->Send.Ring->Alertable = FALSE; - ResetEvent(Rings->Send.TailMoved); +static BOOL CALLBACK +ConsoleLogger(_In_ WINTUN_LOGGER_LEVEL Level, _In_ const WCHAR *LogLine) +{ + const WCHAR *Template; + switch (Level) + { + case WINTUN_LOG_INFO: Template = L"[+] %s\n"; break; + case WINTUN_LOG_WARN: Template = L"[-] %s\n"; break; + case WINTUN_LOG_ERR: Template = L"[!] %s\n"; break; + default: return FALSE; } - SendToClientProgram(Next); + fwprintf(stderr, Template, LogLine); + return TRUE; } +⋮ +WintunSetLogger(ConsoleLogger); ``` -It may be desirable to spin for some time under heavy use before waiting on the `TailMoved` event, in order to reduce latency. - -When closing the handle, Wintun will set the `Tail` to 0xFFFFFFFF and set the `TailMoved` event to unblock the waiting user process. - -Writing packets to the receive ring may be done as: - -```C -for (;;) { - TUN_PACKET *Next = ReceiveFromClientProgram(); - WriteToRing(Rings->Receive.Ring, Next); - if (Rings->Receive.Ring->Alertable) - SetEvent(Rings->Recieve.TailMoved); -} -``` - -Wintun will abort reading the receive ring on invalid `Head` or `Tail` or on a bogus packet. In this case, Wintun will set the `Head` to 0xFFFFFFFF. In order to restart it, reopen the handle and call `TUN_IOCTL_REGISTER_RINGS` again. However, it should be entirely possible to avoid feeding Wintun bogus packets and invalid offsets. - ## Building -**Do not distribute drivers named "Wintun", as they will most certainly clash with official deployments. Instead distribute [the signed MSMs from Wintun.net](https://www.wintun.net/).** If you are unable to use MSMs, [consult the MSI creation instructions](msi-example/README.md). +**Do not distribute drivers named "Wintun", as they will most certainly clash with official deployments. Instead distribute `wintun.dll`.** General requirements: diff --git a/msi-example/README.md b/msi-example/README.md deleted file mode 100644 index 0a42201..0000000 --- a/msi-example/README.md +++ /dev/null @@ -1,46 +0,0 @@ -## Example Standalone MSI - -The best way to include Wintun in your software is by including the MSMs in your final MSI, -as described by [the main README](../README.md). However, if you're stuck with an installation -system such as NSIS, which can not bundle MSM files, then you must build your own MSI, which -NSIS can then invoke. ***Do not use an MSI from elsewhere. You must build it yourself and -distribute only the MSI that you yourself build.*** Otherwise different projects will wind up -uninstalling each other by accident and disturbing the MSM reference counting. The steps in -this file should only be taken if you're not able to include an MSM into a MSI, something that -is easily possible using WiX or most commercial installation solutions. - -This `msi-example` folder contains a WiX skeleton and a build script that handles all -dependencies. use it as follows below. - -#### Steps: - -1. Generate a UUID using uuidgen.exe and replace `{{{FIXED AMD64 UUID}}}` in exampletun.wxs -with that UUID. For the life time of your entire product, even across versions, do not change -that UUID. - -2. Generate a UUID using uuidgen.exe and replace `{{{FIXED ARM64 UUID}}}` in exampletun.wxs -with that UUID. For the life time of your entire product, even across versions, do not change -that UUID. - -3. Generate another UUID using uuidgen.exe and replace `{{{FIXED X86 UUID}}}` in -exampletun.wxs with that UUID. For the life time of your entire product, even across versions, -do not change that UUID. - -4. Go to [Wintun.net](https://www.wintun.net/) and look at what the latest version is (`0.6`, -for example). Replace `{{{VERSION}}}` in build.bat with that version. - -5. Download the amd64 MSM from [Wintun.net](https://www.wintun.net/) and compute its SHA2-256 -sum in all lowercase hex digits using `CertUtil -hashfile "path/to/file" SHA256`, and replace -`{{{64BIT HASH}}}` in build.bat with that value. - -6. Download the arm64 MSM from [Wintun.net](https://www.wintun.net/) and compute its SHA2-256 -sum in all lowercase hex digits using `CertUtil -hashfile "path/to/file" SHA256`, and replace -`{{{64BIT HASH}}}` in build.bat with that value. - -7. Download the x86 MSM from [Wintun.net](https://www.wintun.net/) and compute its SHA2-256 -sum in all lowercase hex digits using `CertUtil -hashfile "path/to/file" SHA256`, and replace -`{{{32BIT HASH}}}` in build.bat with that value. - -8. Run build.bat. - -9. Distribute dist\exampletun-*.msi for your own software only. diff --git a/msi-example/build.bat b/msi-example/build.bat deleted file mode 100644 index 08a83ee..0000000 --- a/msi-example/build.bat +++ /dev/null @@ -1,62 +0,0 @@ -@echo off -rem SPDX-License-Identifier: GPL-2.0 -rem Copyright (C) 2019 WireGuard LLC. All Rights Reserved. - -setlocal -set PATHEXT=.exe -set BUILDDIR=%~dp0 -cd /d %BUILDDIR% || exit /b 1 - -set WIX_CANDLE_FLAGS=-nologo -set WIX_LIGHT_FLAGS=-nologo -spdb -sice:ICE71 -sice:ICE61 - -if exist .deps\prepared goto :build -:installdeps - rmdir /s /q .deps 2> NUL - mkdir .deps || goto :error - cd .deps || goto :error - call :download wintun-x86.msm https://www.wintun.net/builds/wintun-x86-{{{VERSION}}}.msm {{{32BIT HASH}}} || goto :error - call :download wintun-amd64.msm https://www.wintun.net/builds/wintun-amd64-{{{VERSION}}}.msm {{{64BIT HASH}}} || goto :error - call :download wintun-arm64.msm https://www.wintun.net/builds/wintun-arm64-{{{VERSION}}}.msm {{{64BIT HASH}}} || goto :error - call :download wix-binaries.zip https://wixtoolset.org/downloads/v3.14.0.3910/wix314-binaries.zip 0904a88a4bcd9dd3c2274caabe73989cd72767ee90c8fa0bf813d004eec90d32 || goto :error - echo [+] Extracting wix-binaries.zip - mkdir wix\bin || goto :error - tar -xf wix-binaries.zip -C wix\bin || goto :error - echo [+] Cleaning up wix-binaries.zip - del wix-binaries.zip || goto :error - copy /y NUL prepared > NUL || goto :error - cd .. || goto :error - -:build - set WIX=%BUILDDIR%.deps\wix\ - call :msi x86 x86 || goto :error - call :msi amd64 x64 || goto :error - call :msi arm64 arm64 || goto :error - if exist ..\sign.bat call ..\sign.bat - if "%SigningCertificate%"=="" goto :success - if "%TimestampServer%"=="" goto :success - echo [+] Signing - signtool sign /sha1 "%SigningCertificate%" /fd sha256 /tr "%TimestampServer%" /td sha256 /d "ExampleTun Setup" "dist\exampletun-*.msi" || goto :error - -:success - echo [+] Success. - exit /b 0 - -:download - echo [+] Downloading %1 - curl -#fLo %1 %2 || exit /b 1 - echo [+] Verifying %1 - for /f %%a in ('CertUtil -hashfile %1 SHA256 ^| findstr /r "^[0-9a-f]*$"') do if not "%%a"=="%~3" exit /b 1 - goto :eof - -:msi - if not exist "%~1" mkdir "%~1" - echo [+] Compiling %1 - "%WIX%bin\candle" %WIX_CANDLE_FLAGS% -dEXAMPLETUN_PLATFORM="%~1" -out "%~1\exampletun.wixobj" -arch %2 exampletun.wxs || exit /b %errorlevel% - echo [+] Linking %1 - "%WIX%bin\light" %WIX_LIGHT_FLAGS% -out "dist\exampletun-%~1.msi" "%~1\exampletun.wixobj" || exit /b %errorlevel% - goto :eof - -:error - echo [-] Failed with error #%errorlevel%. - cmd /c exit %errorlevel% diff --git a/msi-example/exampletun.wxs b/msi-example/exampletun.wxs deleted file mode 100644 index 2dab6a0..0000000 --- a/msi-example/exampletun.wxs +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -