diff options
Diffstat (limited to 'embeddable-dll-service')
20 files changed, 861 insertions, 154 deletions
diff --git a/embeddable-dll-service/.gitignore b/embeddable-dll-service/.gitignore new file mode 100644 index 00000000..9e3c52ab --- /dev/null +++ b/embeddable-dll-service/.gitignore @@ -0,0 +1,4 @@ +# Build Output +/x86 +/amd64 +/arm64 diff --git a/embeddable-dll-service/README.md b/embeddable-dll-service/README.md index 3ac0dbae..a326dc70 100644 --- a/embeddable-dll-service/README.md +++ b/embeddable-dll-service/README.md @@ -1,19 +1,21 @@ ## Embeddable WireGuard Tunnel Library -This allows embedding WireGuard as a service inside of another application. Build `tunnel.dll` by running `./build.bat` in this folder. The first time you run it, it will invoke `..\build.bat` simply for downloading dependencies. After, you should have `amd64/tunnel.dll` and `x86/tunnel.dll`. +This allows embedding WireGuard as a service inside of another application. Build `tunnel.dll` by running `./build.bat` in this folder. The first time you run it, it will invoke `..\build.bat` simply for downloading dependencies. After, you should have `amd64/tunnel.dll`, `x86/tunnel.dll`, and `arm64/tunnel.dll`. In addition, `tunnel.dll` requires `wireguard.dll`, which can be downloaded from [the wireguard-nt download server](https://download.wireguard.com/wireguard-nt/). The basic setup to use `tunnel.dll` is: ##### 1. Install a service with these parameters: - Service Name: "SomeServiceName" - Display Name: "Some Service Name" - Service Type: SERVICE_WIN32_OWN_PROCESS - Start Type: StartAutomatic - Error Control: ErrorNormal, - Dependencies: [ "Nsi" ] - Sid Type: SERVICE_SID_TYPE_UNRESTRICTED - Executable: "C:\path\to\example\vpnclient.exe /service configfile.conf" +```text +Service Name: "WireGuardTunnel$SomeTunnelName" +Display Name: "Some Service Name" +Service Type: SERVICE_WIN32_OWN_PROCESS +Start Type: StartAutomatic +Error Control: ErrorNormal, +Dependencies: [ "Nsi", "TcpIp" ] +Sid Type: SERVICE_SID_TYPE_UNRESTRICTED +Executable: "C:\path\to\example\vpnclient.exe /service configfile.conf" +``` Some of these may have to be changed with `ChangeServiceConfig2` after the initial call to `CreateService` The `SERVICE_SID_TYPE_UNRESTRICTED` parameter @@ -21,22 +23,19 @@ is absolutely essential; do not forget it. ##### 2. Have your program's main function handle the `/service` switch: - if (!strcmp(argv[1], "/service") && argc == 3) { - HMODULE tunnel_lib = LoadLibrary("tunnel.dll"); - if (!tunnel_lib) - abort(); - tunnel_proc_t tunnel_proc = (tunnel_proc_t)GetProcAddress(tunnel_lib, "WireGuardTunnelService"); - if (!tunnel_proc) - abort(); - struct go_string conf_file = { - .str = argv[2], - .n = strlen(argv[2]) - }; - return tunnel_proc(conf_file); - } +```c +if (wargc == 3 && !wcscmp(wargv[1], L"/service")) { + HMODULE tunnel_lib = LoadLibrary("tunnel.dll"); + if (!tunnel_lib) + abort(); + BOOL (_cdecl *tunnel_proc)(_In_ LPCWSTR conf_file); + *(FARPROC*)&tunnel_proc = GetProcAddress(tunnel_lib, "WireGuardTunnelService"); + if (!tunnel_proc) + abort(); + return tunnel_proc(wargv[2]); +} +``` ##### 3. Scoop up logs by implementing a ringlogger format reader. -##### 4. Talk to the service over its named pipe. - There is a sample implementation of bits and pieces of this inside of the `csharp\` directory. diff --git a/embeddable-dll-service/build.bat b/embeddable-dll-service/build.bat index cae35f70..f1001192 100644 --- a/embeddable-dll-service/build.bat +++ b/embeddable-dll-service/build.bat @@ -1,10 +1,10 @@ @echo off rem SPDX-License-Identifier: MIT -rem Copyright (C) 2019 WireGuard LLC. All Rights Reserved. +rem Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. setlocal set BUILDDIR=%~dp0 -set PATH=%BUILDDIR%..\.deps\go\bin;%BUILDDIR%..\.deps;%PATH% +set PATH=%BUILDDIR%..\.deps\llvm-mingw\bin;%BUILDDIR%..\.deps\go\bin;%PATH% set PATHEXT=.exe cd /d %BUILDDIR% || exit /b 1 @@ -14,21 +14,27 @@ if exist ..\.deps\prepared goto :build :build set GOOS=windows + set GOARM=7 set GOPATH=%BUILDDIR%..\.deps\gopath set GOROOT=%BUILDDIR%..\.deps\go set CGO_ENABLED=1 set CGO_CFLAGS=-O3 -Wall -Wno-unused-function -Wno-switch -std=gnu11 -DWINVER=0x0601 - set CGO_LDFLAGS=-Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols call :build_plat x86 i686 386 || goto :error - set CGO_LDFLAGS=%CGO_LDFLAGS% -Wl,--high-entropy-va call :build_plat amd64 x86_64 amd64 || goto :error + call :build_plat arm64 aarch64 arm64 || goto :error + +:sign + if exist ..\sign.bat call ..\sign.bat + if "%SigningProvider%"=="" goto :success + if "%TimestampServer%"=="" goto :success + echo [+] Signing + signtool sign %SigningProvider% /fd sha256 /tr "%TimestampServer%" /td sha256 /d "WireGuard Tunnel" x86\tunnel.dll amd64\tunnel.dll arm64\tunnel.dll || goto :error :success echo [+] Success exit /b 0 :build_plat - set PATH=%BUILDDIR%..\.deps\%~2-w64-mingw32-native\bin;%PATH% set CC=%~2-w64-mingw32-gcc set GOARCH=%~3 mkdir %1 >NUL 2>&1 diff --git a/embeddable-dll-service/csharp/.gitignore b/embeddable-dll-service/csharp/.gitignore new file mode 100644 index 00000000..28bcb4ab --- /dev/null +++ b/embeddable-dll-service/csharp/.gitignore @@ -0,0 +1,3 @@ +/.vs +/bin +/obj diff --git a/embeddable-dll-service/csharp/DemoUI/MainWindow.Designer.cs b/embeddable-dll-service/csharp/DemoUI/MainWindow.Designer.cs new file mode 100644 index 00000000..5aad1b97 --- /dev/null +++ b/embeddable-dll-service/csharp/DemoUI/MainWindow.Designer.cs @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +namespace DemoUI +{ + partial class MainWindow + { + /// <summary> + /// Required designer variable. + /// </summary> + private System.ComponentModel.IContainer components = null; + + /// <summary> + /// Clean up any resources being used. + /// </summary> + /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// <summary> + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// </summary> + private void InitializeComponent() + { + this.connectButton = new System.Windows.Forms.Button(); + this.logBox = new System.Windows.Forms.TextBox(); + this.SuspendLayout(); + // + // connectButton + // + this.connectButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.connectButton.Location = new System.Drawing.Point(12, 12); + this.connectButton.Name = "connectButton"; + this.connectButton.Size = new System.Drawing.Size(1137, 46); + this.connectButton.TabIndex = 5; + this.connectButton.Text = "&Connect"; + this.connectButton.UseVisualStyleBackColor = true; + this.connectButton.Click += new System.EventHandler(this.connectButton_Click); + // + // logBox + // + this.logBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.logBox.Location = new System.Drawing.Point(12, 64); + this.logBox.Multiline = true; + this.logBox.Name = "logBox"; + this.logBox.ReadOnly = true; + this.logBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.logBox.Size = new System.Drawing.Size(1137, 561); + this.logBox.TabIndex = 4; + this.logBox.TabStop = false; + // + // MainWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1161, 637); + this.Controls.Add(this.logBox); + this.Controls.Add(this.connectButton); + this.Name = "MainWindow"; + this.Text = "WireGuard Demo Client"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainWindow_FormClosing); + this.Load += new System.EventHandler(this.MainWindow_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.Button connectButton; + private System.Windows.Forms.TextBox logBox; + } +} + diff --git a/embeddable-dll-service/csharp/DemoUI/MainWindow.cs b/embeddable-dll-service/csharp/DemoUI/MainWindow.cs new file mode 100644 index 00000000..1df17181 --- /dev/null +++ b/embeddable-dll-service/csharp/DemoUI/MainWindow.cs @@ -0,0 +1,215 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +using System; +using System.Text; +using System.Threading.Tasks; +using System.IO; +using System.Windows.Forms; +using System.Threading; +using System.IO.Pipes; +using System.Diagnostics; +using System.Net.Sockets; +using System.Security.AccessControl; + +namespace DemoUI +{ + public partial class MainWindow : Form + { + private static readonly string userDirectory = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "Config"); //TODO: put in Program Files in real code. + private static readonly string configFile = Path.Combine(userDirectory, "demobox.conf"); + private static readonly string logFile = Path.Combine(userDirectory, "log.bin"); + + private Tunnel.Ringlogger log; + private Thread logPrintingThread, transferUpdateThread; + private volatile bool threadsRunning; + private bool connected; + + public MainWindow() + { + makeConfigDirectory(); + InitializeComponent(); + Application.ApplicationExit += Application_ApplicationExit; + try { File.Delete(logFile); } catch { } + log = new Tunnel.Ringlogger(logFile, "GUI"); + logPrintingThread = new Thread(new ThreadStart(tailLog)); + transferUpdateThread = new Thread(new ThreadStart(tailTransfer)); + } + + private void makeConfigDirectory() + { + var ds = new DirectorySecurity(); + ds.SetSecurityDescriptorSddlForm("O:BAG:BAD:PAI(A;OICI;FA;;;BA)(A;OICI;FA;;;SY)"); + FileSystemAclExtensions.CreateDirectory(ds, userDirectory); + } + + private void tailLog() + { + var cursor = Tunnel.Ringlogger.CursorAll; + while (threadsRunning) + { + var lines = log.FollowFromCursor(ref cursor); + foreach (var line in lines) + logBox.Invoke(new Action<string>(logBox.AppendText), new object[] { line + "\r\n" }); + try + { + Thread.Sleep(300); + } + catch + { + break; + } + } + } + + private void tailTransfer() + { + Tunnel.Driver.Adapter adapter = null; + while (threadsRunning) + { + if (adapter == null) + { + while (threadsRunning) + { + try + { + adapter = Tunnel.Service.GetAdapter(configFile); + break; + } + catch + { + try + { + Thread.Sleep(1000); + } + catch { } + } + } + } + if (adapter == null) + continue; + try + { + ulong rx = 0, tx = 0; + var config = adapter.GetConfiguration(); + foreach (var peer in config.Peers) + { + rx += peer.RxBytes; + tx += peer.TxBytes; + } + Invoke(new Action<ulong, ulong>(updateTransferTitle), new object[] { rx, tx }); + Thread.Sleep(1000); + } + catch { adapter = null; } + } + } + + private void Application_ApplicationExit(object sender, EventArgs e) + { + Tunnel.Service.Remove(configFile, true); + try { File.Delete(logFile); } catch { } + try { File.Delete(configFile); } catch { } + } + + private void MainWindow_Load(object sender, EventArgs e) + { + threadsRunning = true; + logPrintingThread.Start(); + transferUpdateThread.Start(); + } + + private void MainWindow_FormClosing(object sender, FormClosingEventArgs e) + { + threadsRunning = false; + logPrintingThread.Interrupt(); + transferUpdateThread.Interrupt(); + try { logPrintingThread.Join(); } catch { } + try { transferUpdateThread.Join(); } catch { } + } + + private static string formatBytes(ulong bytes) + { + decimal d = bytes; + string selectedUnit = null; + foreach (string unit in new string[] { "B", "KiB", "MiB", "GiB", "TiB" }) + { + selectedUnit = unit; + if (d < 1024) + break; + d /= 1024; + } + return string.Format("{0:0.##} {1}", d, selectedUnit); + } + + private void updateTransferTitle(ulong rx, ulong tx) + { + var titleBase = Text; + var idx = titleBase.IndexOf(" - "); + if (idx != -1) + titleBase = titleBase.Substring(0, idx); + if (rx == 0 && tx == 0) + Text = titleBase; + else + Text = string.Format("{0} - rx: {1}, tx: {2}", titleBase, formatBytes(rx), formatBytes(tx)); + } + + private async Task<string> generateNewConfig() + { + log.Write("Generating keys"); + var keys = Tunnel.Keypair.Generate(); + log.Write("Exchanging keys with demo server"); + var client = new TcpClient(); + await client.ConnectAsync("demo.wireguard.com", 42912); + var stream = client.GetStream(); + var reader = new StreamReader(stream, Encoding.UTF8); + var pubKeyBytes = Encoding.UTF8.GetBytes(keys.Public + "\n"); + await stream.WriteAsync(pubKeyBytes, 0, pubKeyBytes.Length); + await stream.FlushAsync(); + var ret = (await reader.ReadLineAsync()).Split(':'); + client.Close(); + var status = ret.Length >= 1 ? ret[0] : ""; + var serverPubkey = ret.Length >= 2 ? ret[1] : ""; + var serverPort = ret.Length >= 3 ? ret[2] : ""; + var internalIP = ret.Length >= 4 ? ret[3] : ""; + if (status != "OK") + throw new InvalidOperationException(string.Format("Server status is {0}", status)); + return string.Format("[Interface]\nPrivateKey = {0}\nAddress = {1}/24\nDNS = 8.8.8.8, 8.8.4.4\n\n[Peer]\nPublicKey = {2}\nEndpoint = demo.wireguard.com:{3}\nAllowedIPs = 0.0.0.0/0\n", keys.Private, internalIP, serverPubkey, serverPort); + } + + private async void connectButton_Click(object sender, EventArgs e) + { + if (connected) + { + connectButton.Enabled = false; + await Task.Run(() => + { + Tunnel.Service.Remove(configFile, true); + try { File.Delete(configFile); } catch { } + }); + updateTransferTitle(0, 0); + connectButton.Text = "&Connect"; + connectButton.Enabled = true; + connected = false; + return; + } + + connectButton.Enabled = false; + try + { + var config = await generateNewConfig(); + await File.WriteAllBytesAsync(configFile, Encoding.UTF8.GetBytes(config)); + await Task.Run(() => Tunnel.Service.Add(configFile, true)); + connected = true; + connectButton.Text = "&Disconnect"; + } + catch (Exception ex) + { + log.Write(ex.Message); + try { File.Delete(configFile); } catch { } + } + connectButton.Enabled = true; + } + } +} diff --git a/embeddable-dll-service/csharp/DemoUI/MainWindow.resx b/embeddable-dll-service/csharp/DemoUI/MainWindow.resx new file mode 100644 index 00000000..f298a7be --- /dev/null +++ b/embeddable-dll-service/csharp/DemoUI/MainWindow.resx @@ -0,0 +1,60 @@ +<root> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> +</root>
\ No newline at end of file diff --git a/embeddable-dll-service/csharp/DemoUI/Program.cs b/embeddable-dll-service/csharp/DemoUI/Program.cs new file mode 100644 index 00000000..3649ab93 --- /dev/null +++ b/embeddable-dll-service/csharp/DemoUI/Program.cs @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +using System; +using System.Threading; +using System.Diagnostics; +using System.Windows.Forms; + +namespace DemoUI +{ + static class Program + { + [STAThread] + static void Main(string[] args) + { + if (args.Length == 3 && args[0] == "/service") + { + var t = new Thread(() => + { + try + { + var currentProcess = Process.GetCurrentProcess(); + var uiProcess = Process.GetProcessById(int.Parse(args[2])); + if (uiProcess.MainModule.FileName != currentProcess.MainModule.FileName) + return; + uiProcess.WaitForExit(); + Tunnel.Service.Remove(args[1], false); + } + catch { } + }); + t.Start(); + Tunnel.Service.Run(args[1]); + t.Interrupt(); + return; + } + Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainWindow()); + } + } +} diff --git a/embeddable-dll-service/csharp/DemoUI/app.manifest b/embeddable-dll-service/csharp/DemoUI/app.manifest new file mode 100644 index 00000000..616ab5b9 --- /dev/null +++ b/embeddable-dll-service/csharp/DemoUI/app.manifest @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> + <assemblyIdentity version="1.0.0.0" name="demo-client"/> + <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"> + <security> + <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"> + <requestedExecutionLevel level="requireAdministrator" uiAccess="false" /> + </requestedPrivileges> + </security> + </trustInfo> + + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <!-- Windows 7 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /> + + <!-- Windows 8 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /> + + <!-- Windows 8.1 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /> + + <!-- Windows 10 --> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> + + </application> + </compatibility> + + <application xmlns="urn:schemas-microsoft-com:asm.v3"> + <windowsSettings> + <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> + </windowsSettings> + </application> + + <dependency> + <dependentAssembly> + <assemblyIdentity + type="win32" + name="Microsoft.Windows.Common-Controls" + version="6.0.0.0" + processorArchitecture="*" + publicKeyToken="6595b64144ccf1df" + language="*" + /> + </dependentAssembly> + </dependency> + +</assembly> diff --git a/embeddable-dll-service/csharp/Program.cs b/embeddable-dll-service/csharp/Program.cs deleted file mode 100644 index d99e66fd..00000000 --- a/embeddable-dll-service/csharp/Program.cs +++ /dev/null @@ -1,92 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. - */ - -using System; -using System.Net.Sockets; -using System.IO; -using System.Text; -using System.Diagnostics; -using System.Threading; -using System.Runtime.InteropServices; - -namespace Tunnel -{ - class Program - { - - [DllImport("kernel32.dll")] - private static extern bool SetConsoleCtrlHandler(SetConsoleCtrlEventHandler handler, bool add); - private delegate bool SetConsoleCtrlEventHandler(UInt32 signal); - - public static void Main(string[] args) - { - if (args.Length == 2 && args[0] == "/service") - { - Service.Run(args[1]); - return; - } - - var baseDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); - var configFile = Path.Combine(baseDirectory, "demobox.conf"); - var logFile = Path.Combine(baseDirectory, "log.bin"); - - try { File.Delete(logFile); } catch { } - Ringlogger log = new Ringlogger(logFile, "GUI"); - - var logPrintingThread = new Thread(() => - { - var cursor = Ringlogger.CursorAll; - while (Thread.CurrentThread.IsAlive) - { - var lines = log.FollowFromCursor(ref cursor); - foreach (var line in lines) - Console.WriteLine(line); - Thread.Sleep(300); - } - }); - logPrintingThread.Start(); - - log.Write("Generating keys"); - var keys = Keypair.Generate(); - log.Write("Exchanging keys with demo server"); - var client = new TcpClient("demo.wireguard.com", 42912); - var stream = client.GetStream(); - var reader = new StreamReader(stream, Encoding.UTF8); - var pubKeyBytes = Encoding.UTF8.GetBytes(keys.Public + "\n"); - stream.Write(pubKeyBytes, 0, pubKeyBytes.Length); - stream.Flush(); - var ret = reader.ReadLine().Split(':'); - client.Close(); - var status = ret.Length >= 1 ? ret[0] : ""; - var serverPubkey = ret.Length >= 2 ? ret[1] : ""; - var serverPort = ret.Length >= 3 ? ret[2] : ""; - var internalIP = ret.Length >= 4 ? ret[3] : ""; - - if (status != "OK") - throw new InvalidOperationException(String.Format("Server status is {0}", status)); - - SetConsoleCtrlHandler(delegate - { - Service.Remove(configFile); - Environment.Exit(0); - return true; - }, true); - - log.Write("Writing config file to disk"); - var configFileContents = String.Format("[Interface]\nPrivateKey = {0}\nAddress = {1}/24\nDNS = 8.8.8.8, 8.8.4.4\n\n[Peer]\nPublicKey = {2}\nEndpoint = demo.wireguard.com:{3}\nAllowedIPs = 0.0.0.0/0\n", keys.Private, internalIP, serverPubkey, serverPort); - File.WriteAllText(configFile, configFileContents); - - try - { - Service.Add(configFile); - logPrintingThread.Join(); - } - finally - { - Service.Remove(configFile); - } - } - } -} diff --git a/embeddable-dll-service/csharp/README.md b/embeddable-dll-service/csharp/README.md new file mode 100644 index 00000000..24b36563 --- /dev/null +++ b/embeddable-dll-service/csharp/README.md @@ -0,0 +1,7 @@ +# Example WireGuard Demo Client for Windows + +This is a simple client for demo.wireguard.com, which brings up WireGuard tunnels using the [embeddable-dll-service](https://git.zx2c4.com/wireguard-windows/about/embeddable-dll-service/README.md). + +## Building + +The code in this repository can be built in Visual Studio 2019 by opening the .sln and pressing build. However, it requires [`tunnel.dll` and `wireguard.dll`](../README.md). diff --git a/embeddable-dll-service/csharp/TunnelDll/Driver.cs b/embeddable-dll-service/csharp/TunnelDll/Driver.cs new file mode 100644 index 00000000..69911ec8 --- /dev/null +++ b/embeddable-dll-service/csharp/TunnelDll/Driver.cs @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +using System; +using System.ComponentModel; +using System.Net; +using System.Runtime.InteropServices; + +namespace Tunnel +{ + public class Driver + { + [DllImport("wireguard.dll", EntryPoint = "WireGuardOpenAdapter", CallingConvention = CallingConvention.StdCall, SetLastError = true)] + private static extern IntPtr openAdapter([MarshalAs(UnmanagedType.LPWStr)] string name); + [DllImport("wireguard.dll", EntryPoint = "WireGuardCloseAdapter", CallingConvention = CallingConvention.StdCall)] + private static extern void freeAdapter(IntPtr adapter); + [DllImport("wireguard.dll", EntryPoint = "WireGuardGetConfiguration", CallingConvention = CallingConvention.StdCall, SetLastError = true)] + private static extern bool getConfiguration(IntPtr adapter, byte[] iface, ref UInt32 bytes); + + public class Adapter + { + private IntPtr _handle; + private UInt32 _lastGetGuess; + public Adapter(string name) + { + _lastGetGuess = 1024; + _handle = openAdapter(name); + if (_handle == IntPtr.Zero) + throw new Win32Exception(); + } + ~Adapter() + { + freeAdapter(_handle); + } + public unsafe Interface GetConfiguration() + { + var iface = new Interface(); + byte[] bytes; + for (; ; ) + { + bytes = new byte[_lastGetGuess]; + if (getConfiguration(_handle, bytes, ref _lastGetGuess)) + break; + if (Marshal.GetLastWin32Error() != 234 /* ERROR_MORE_DATA */) + throw new Win32Exception(); + } + fixed (void* start = bytes) + { + var ioctlIface = (IoctlInterface*)start; + if ((ioctlIface->Flags & IoctlInterfaceFlags.HasPublicKey) != 0) + iface.PublicKey = new Key(ioctlIface->PublicKey); + if ((ioctlIface->Flags & IoctlInterfaceFlags.HasPrivateKey) != 0) + iface.PrivateKey = new Key(ioctlIface->PrivateKey); + if ((ioctlIface->Flags & IoctlInterfaceFlags.HasListenPort) != 0) + iface.ListenPort = ioctlIface->ListenPort; + var peers = new Peer[ioctlIface->PeersCount]; + var ioctlPeer = (IoctlPeer*)((byte*)ioctlIface + sizeof(IoctlInterface)); + for (UInt32 i = 0; i < peers.Length; ++i) + { + var peer = new Peer(); + if ((ioctlPeer->Flags & IoctlPeerFlags.HasPublicKey) != 0) + peer.PublicKey = new Key(ioctlPeer->PublicKey); + if ((ioctlPeer->Flags & IoctlPeerFlags.HasPresharedKey) != 0) + peer.PresharedKey = new Key(ioctlPeer->PresharedKey); + if ((ioctlPeer->Flags & IoctlPeerFlags.HasPersistentKeepalive) != 0) + peer.PersistentKeepalive = ioctlPeer->PersistentKeepalive; + if ((ioctlPeer->Flags & IoctlPeerFlags.HasEndpoint) != 0) + { + if (ioctlPeer->Endpoint.si_family == Win32.ADDRESS_FAMILY.AF_INET) + { + var ip = new byte[4]; + Marshal.Copy((IntPtr)ioctlPeer->Endpoint.Ipv4.sin_addr.bytes, ip, 0, 4); + peer.Endpoint = new IPEndPoint(new IPAddress(ip), (ushort)IPAddress.NetworkToHostOrder((short)ioctlPeer->Endpoint.Ipv4.sin_port)); + } + else if (ioctlPeer->Endpoint.si_family == Win32.ADDRESS_FAMILY.AF_INET6) + { + var ip = new byte[16]; + Marshal.Copy((IntPtr)ioctlPeer->Endpoint.Ipv6.sin6_addr.bytes, ip, 0, 16); + peer.Endpoint = new IPEndPoint(new IPAddress(ip), (ushort)IPAddress.NetworkToHostOrder((short)ioctlPeer->Endpoint.Ipv6.sin6_port)); + } + } + peer.TxBytes = ioctlPeer->TxBytes; + peer.RxBytes = ioctlPeer->RxBytes; + if (ioctlPeer->LastHandshake != 0) + peer.LastHandshake = DateTime.FromFileTimeUtc((long)ioctlPeer->LastHandshake); + var allowedIPs = new AllowedIP[ioctlPeer->AllowedIPsCount]; + var ioctlAllowedIP = (IoctlAllowedIP*)((byte*)ioctlPeer + sizeof(IoctlPeer)); + for (UInt32 j = 0; j < allowedIPs.Length; ++j) + { + var allowedIP = new AllowedIP(); + if (ioctlAllowedIP->AddressFamily == Win32.ADDRESS_FAMILY.AF_INET) + { + var ip = new byte[4]; + Marshal.Copy((IntPtr)ioctlAllowedIP->V4.bytes, ip, 0, 4); + allowedIP.Address = new IPAddress(ip); + } + else if (ioctlAllowedIP->AddressFamily == Win32.ADDRESS_FAMILY.AF_INET6) + { + var ip = new byte[16]; + Marshal.Copy((IntPtr)ioctlAllowedIP->V6.bytes, ip, 0, 16); + allowedIP.Address = new IPAddress(ip); + } + allowedIP.Cidr = ioctlAllowedIP->Cidr; + allowedIPs[j] = allowedIP; + ioctlAllowedIP = (IoctlAllowedIP*)((byte*)ioctlAllowedIP + sizeof(IoctlAllowedIP)); + } + peer.AllowedIPs = allowedIPs; + peers[i] = peer; + ioctlPeer = (IoctlPeer*)ioctlAllowedIP; + } + iface.Peers = peers; + } + return iface; + } + + public class Key + { + private byte[] _bytes; + public byte[] Bytes + { + get + { + return _bytes; + } + set + { + if (value == null || value.Length != 32) + throw new ArgumentException("Keys must be 32 bytes"); + _bytes = value; + } + } + public Key(byte[] bytes) + { + Bytes = bytes; + } + public unsafe Key(byte* bytes) + { + _bytes = new byte[32]; + Marshal.Copy((IntPtr)bytes, _bytes, 0, 32); + } + public override String ToString() + { + return Convert.ToBase64String(_bytes); + } + } + + public class Interface + { + public UInt16 ListenPort { get; set; } + public Key PrivateKey { get; set; } + public Key PublicKey { get; set; } + public Peer[] Peers { get; set; } + } + + public class Peer + { + public Key PublicKey { get; set; } + public Key PresharedKey { get; set; } + public UInt16 PersistentKeepalive { get; set; } + public IPEndPoint Endpoint { get; set; } + public UInt64 TxBytes { get; set; } + public UInt64 RxBytes { get; set; } + public DateTime LastHandshake { get; set; } + public AllowedIP[] AllowedIPs { get; set; } + } + + public class AllowedIP + { + public IPAddress Address { get; set; } + public byte Cidr { get; set; } + } + + private enum IoctlInterfaceFlags : UInt32 + { + HasPublicKey = 1 << 0, + HasPrivateKey = 1 << 1, + HasListenPort = 1 << 2, + ReplacePeers = 1 << 3 + }; + + [StructLayout(LayoutKind.Sequential, Pack = 8, Size = 80)] + private unsafe struct IoctlInterface + { + public IoctlInterfaceFlags Flags; + public UInt16 ListenPort; + public fixed byte PrivateKey[32]; + public fixed byte PublicKey[32]; + public UInt32 PeersCount; + }; + + private enum IoctlPeerFlags : UInt32 + { + HasPublicKey = 1 << 0, + HasPresharedKey = 1 << 1, + HasPersistentKeepalive = 1 << 2, + HasEndpoint = 1 << 3, + ReplaceAllowedIPs = 1 << 5, + Remove = 1 << 6, + UpdateOnly = 1 << 7 + }; + + [StructLayout(LayoutKind.Sequential, Pack = 8, Size = 136)] + private unsafe struct IoctlPeer + { + public IoctlPeerFlags Flags; + public UInt32 Reserved; + public fixed byte PublicKey[32]; + public fixed byte PresharedKey[32]; + public UInt16 PersistentKeepalive; + public Win32.SOCKADDR_INET Endpoint; + public UInt64 TxBytes, RxBytes; + public UInt64 LastHandshake; + public UInt32 AllowedIPsCount; + }; + + [StructLayout(LayoutKind.Explicit, Pack = 8, Size = 24)] + private unsafe struct IoctlAllowedIP + { + [FieldOffset(0)] + [MarshalAs(UnmanagedType.Struct)] + public Win32.IN_ADDR V4; + [FieldOffset(0)] + [MarshalAs(UnmanagedType.Struct)] + public Win32.IN6_ADDR V6; + [FieldOffset(16)] + public Win32.ADDRESS_FAMILY AddressFamily; + [FieldOffset(20)] + public byte Cidr; + } + } + } +} diff --git a/embeddable-dll-service/csharp/Keypair.cs b/embeddable-dll-service/csharp/TunnelDll/Keypair.cs index e5764fbd..59847b98 100644 --- a/embeddable-dll-service/csharp/Keypair.cs +++ b/embeddable-dll-service/csharp/TunnelDll/Keypair.cs @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: MIT * - * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. */ using System; @@ -13,7 +13,7 @@ namespace Tunnel public readonly string Public; public readonly string Private; - private Keypair(string pub, string priv) + public Keypair(string pub, string priv) { Public = pub; Private = priv; diff --git a/embeddable-dll-service/csharp/Ringlogger.cs b/embeddable-dll-service/csharp/TunnelDll/Ringlogger.cs index d0957926..9db01fc8 100644 --- a/embeddable-dll-service/csharp/Ringlogger.cs +++ b/embeddable-dll-service/csharp/TunnelDll/Ringlogger.cs @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: MIT * - * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. */ using System; diff --git a/embeddable-dll-service/csharp/Service.cs b/embeddable-dll-service/csharp/TunnelDll/Service.cs index db600819..74e1a888 100644 --- a/embeddable-dll-service/csharp/Service.cs +++ b/embeddable-dll-service/csharp/TunnelDll/Service.cs @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: MIT * - * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. */ using System; @@ -9,36 +9,30 @@ using System.IO.Pipes; using System.Runtime.InteropServices; using System.ComponentModel; using System.Diagnostics; -using System.Security.Principal; using System.Threading; namespace Tunnel { public class Service { - private const string LongName = "Example WireGuard Tunnel Client"; - private const string Description = "A WireGuard tunnel created by example code."; + private const string LongName = "WireGuard Demo Box"; + private const string Description = "Demonstration tunnel for testing WireGuard"; [DllImport("tunnel.dll", EntryPoint = "WireGuardTunnelService", CallingConvention = CallingConvention.Cdecl)] public static extern bool Run([MarshalAs(UnmanagedType.LPWStr)] string configFile); - public static NamedPipeClientStream GetPipe(string configFile) + public static Driver.Adapter GetAdapter(string configFile) { - var pipepath = "ProtectedPrefix\\Administrators\\WireGuard\\" + Path.GetFileNameWithoutExtension(configFile); - return new NamedPipeClientStream(pipepath); + return new Driver.Adapter(Path.GetFileNameWithoutExtension(configFile)); } - public static void Add(string configFile) + public static void Add(string configFile, bool ephemeral) { var tunnelName = Path.GetFileNameWithoutExtension(configFile); var shortName = String.Format("WireGuardTunnel${0}", tunnelName); var longName = String.Format("{0}: {1}", LongName, tunnelName); var exeName = Process.GetCurrentProcess().MainModule.FileName; - var pathAndArgs = String.Format("\"{0}\" /service \"{1}\"", exeName, configFile); //TODO: This is not the proper way to escape file args. - - var accessControl = File.GetAccessControl(configFile); //TODO: TOCTOU! - accessControl.SetOwner(new NTAccount(Environment.UserDomainName, Environment.UserName)); - File.SetAccessControl(configFile, accessControl); + var pathAndArgs = String.Format("\"{0}\" /service \"{1}\" {2}", exeName, configFile, Process.GetCurrentProcess().Id); //TODO: This is not the proper way to escape file args. var scm = Win32.OpenSCManager(null, null, Win32.ScmAccessRights.AllAccess); if (scm == IntPtr.Zero) @@ -49,9 +43,9 @@ namespace Tunnel if (service != IntPtr.Zero) { Win32.CloseServiceHandle(service); - Remove(configFile); + Remove(configFile, true); } - service = Win32.CreateService(scm, shortName, longName, Win32.ServiceAccessRights.AllAccess, Win32.ServiceType.Win32OwnProcess, Win32.ServiceStartType.Demand, Win32.ServiceError.Normal, pathAndArgs, null, IntPtr.Zero, "Nsi", null, null); + service = Win32.CreateService(scm, shortName, longName, Win32.ServiceAccessRights.AllAccess, Win32.ServiceType.Win32OwnProcess, Win32.ServiceStartType.Demand, Win32.ServiceError.Normal, pathAndArgs, null, IntPtr.Zero, "Nsi\0TcpIp\0", null, null); if (service == IntPtr.Zero) throw new Win32Exception(Marshal.GetLastWin32Error()); try @@ -66,6 +60,9 @@ namespace Tunnel if (!Win32.StartService(service, 0, null)) throw new Win32Exception(Marshal.GetLastWin32Error()); + + if (ephemeral && !Win32.DeleteService(service)) + throw new Win32Exception(Marshal.GetLastWin32Error()); } finally { @@ -78,7 +75,7 @@ namespace Tunnel } } - public static void Remove(string configFile) + public static void Remove(string configFile, bool waitForStop) { var tunnelName = Path.GetFileNameWithoutExtension(configFile); var shortName = String.Format("WireGuardTunnel${0}", tunnelName); @@ -90,19 +87,16 @@ namespace Tunnel { var service = Win32.OpenService(scm, shortName, Win32.ServiceAccessRights.AllAccess); if (service == IntPtr.Zero) - { - Win32.CloseServiceHandle(service); return; - } try { var serviceStatus = new Win32.ServiceStatus(); Win32.ControlService(service, Win32.ServiceControl.Stop, serviceStatus); - for (int i = 0; i < 180 && Win32.QueryServiceStatus(service, serviceStatus) && serviceStatus.dwCurrentState != Win32.ServiceState.Stopped; ++i) + for (int i = 0; waitForStop && i < 180 && Win32.QueryServiceStatus(service, serviceStatus) && serviceStatus.dwCurrentState != Win32.ServiceState.Stopped; ++i) Thread.Sleep(1000); - if (!Win32.DeleteService(service)) + if (!Win32.DeleteService(service) && Marshal.GetLastWin32Error() != 0x00000430) throw new Win32Exception(Marshal.GetLastWin32Error()); } finally diff --git a/embeddable-dll-service/csharp/Win32.cs b/embeddable-dll-service/csharp/TunnelDll/Win32.cs index 76395f7e..8e7f986d 100644 --- a/embeddable-dll-service/csharp/Win32.cs +++ b/embeddable-dll-service/csharp/TunnelDll/Win32.cs @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: MIT * - * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. */ using System; @@ -135,6 +135,58 @@ namespace Tunnel SidInfo = 5 } + [StructLayout(LayoutKind.Sequential)] + public unsafe struct IN_ADDR + { + public fixed byte bytes[4]; + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct IN6_ADDR + { + public fixed byte bytes[16]; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SOCKADDR_IN + { + public ushort sin_family; + public ushort sin_port; + public IN_ADDR sin_addr; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SOCKADDR_IN6 + { + public ushort sin6_family; + public ushort sin6_port; + public uint sin6_flowinfo; + public IN6_ADDR sin6_addr; + public uint sin6_scope_id; + } + + [StructLayout(LayoutKind.Explicit)] + public struct SOCKADDR_INET + { + [FieldOffset(0)] + [MarshalAs(UnmanagedType.Struct)] + public SOCKADDR_IN Ipv4; + + [FieldOffset(0)] + [MarshalAs(UnmanagedType.Struct)] + public SOCKADDR_IN6 Ipv6; + + [FieldOffset(0)] + public ADDRESS_FAMILY si_family; + } + + public enum ADDRESS_FAMILY : UInt16 + { + AF_UNSPEC = 0, + AF_INET = 2, + AF_INET6 = 23 + } + [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr OpenSCManager(string machineName, string databaseName, ScmAccessRights dwDesiredAccess); diff --git a/embeddable-dll-service/csharp/demo-client.csproj b/embeddable-dll-service/csharp/demo-client.csproj new file mode 100644 index 00000000..00339ee2 --- /dev/null +++ b/embeddable-dll-service/csharp/demo-client.csproj @@ -0,0 +1,14 @@ +<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> + + <PropertyGroup> + <OutputType>WinExe</OutputType> + <TargetFramework>net5.0-windows</TargetFramework> + <RootNamespace>DemoUI</RootNamespace> + <UseWindowsForms>true</UseWindowsForms> + <AssemblyName>demo-client</AssemblyName> + <Platforms>x64</Platforms> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <ApplicationManifest>DemoUI\app.manifest</ApplicationManifest> + </PropertyGroup> + +</Project> diff --git a/embeddable-dll-service/csharp/demo-client.csproj.user b/embeddable-dll-service/csharp/demo-client.csproj.user new file mode 100644 index 00000000..27d1a581 --- /dev/null +++ b/embeddable-dll-service/csharp/demo-client.csproj.user @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Compile Update="DemoUI\MainWindow.cs"> + <SubType>Form</SubType> + </Compile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/embeddable-dll-service/csharp/demo-client.sln b/embeddable-dll-service/csharp/demo-client.sln new file mode 100644 index 00000000..a7d8b0ee --- /dev/null +++ b/embeddable-dll-service/csharp/demo-client.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30804.86 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "demo-client", "demo-client.csproj", "{AADC81E1-0294-483B-ABAE-63DBE82436E9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AADC81E1-0294-483B-ABAE-63DBE82436E9}.Debug|x64.ActiveCfg = Debug|x64 + {AADC81E1-0294-483B-ABAE-63DBE82436E9}.Debug|x64.Build.0 = Debug|x64 + {AADC81E1-0294-483B-ABAE-63DBE82436E9}.Release|x64.ActiveCfg = Release|x64 + {AADC81E1-0294-483B-ABAE-63DBE82436E9}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E410FD53-4E4A-4299-B6BD-CE91685DF7BE} + EndGlobalSection +EndGlobal diff --git a/embeddable-dll-service/main.go b/embeddable-dll-service/main.go index 27fca4ee..f313ae7f 100644 --- a/embeddable-dll-service/main.go +++ b/embeddable-dll-service/main.go @@ -1,28 +1,27 @@ /* SPDX-License-Identifier: MIT * - * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. */ package main import ( "C" + "crypto/rand" + "log" + "path/filepath" + "unsafe" "golang.org/x/crypto/curve25519" "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/windows/conf" "golang.zx2c4.com/wireguard/windows/tunnel" - - "crypto/rand" - "log" - "path/filepath" - "unsafe" ) //export WireGuardTunnelService func WireGuardTunnelService(confFile16 *uint16) bool { - confFile := windows.UTF16ToString((*[(1 << 30) - 1]uint16)(unsafe.Pointer(confFile16))[:]) + confFile := windows.UTF16PtrToString(confFile16) conf.PresetRootDirectory(filepath.Dir(confFile)) tunnel.UseFixedGUIDInsteadOfDeterministic = true err := tunnel.Run(confFile) @@ -33,7 +32,7 @@ func WireGuardTunnelService(confFile16 *uint16) bool { } //export WireGuardGenerateKeypair -func WireGuardGenerateKeypair(publicKey *byte, privateKey *byte) { +func WireGuardGenerateKeypair(publicKey, privateKey *byte) { publicKeyArray := (*[32]byte)(unsafe.Pointer(publicKey)) privateKeyArray := (*[32]byte)(unsafe.Pointer(privateKey)) n, err := rand.Read(privateKeyArray[:]) |