├── .gitignore ├── appx ├── StoreLogo.png ├── SplashScreen.png ├── Square44x44Logo.png ├── Square150x150Logo.png └── AppxManifest.xml ├── plugin ├── src │ ├── lib.rs │ ├── logging.rs │ ├── config.rs │ ├── background.rs │ ├── utils.rs │ └── plugin.rs └── Cargo.toml ├── app ├── Cargo.toml └── src │ └── main.rs ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── LICENSE-APACHE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | /target 4 | -------------------------------------------------------------------------------- /appx/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqmana/wireguard-uwp-rs/HEAD/appx/StoreLogo.png -------------------------------------------------------------------------------- /appx/SplashScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqmana/wireguard-uwp-rs/HEAD/appx/SplashScreen.png -------------------------------------------------------------------------------- /appx/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqmana/wireguard-uwp-rs/HEAD/appx/Square44x44Logo.png -------------------------------------------------------------------------------- /appx/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqmana/wireguard-uwp-rs/HEAD/appx/Square150x150Logo.png -------------------------------------------------------------------------------- /plugin/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains the `IVpnPlugIn` implementation for our UWP VPN plugin app. 2 | 3 | #![windows_subsystem = "windows"] 4 | #![allow(non_snake_case)] // Windows naming conventions 5 | 6 | mod background; 7 | mod config; 8 | mod logging; 9 | mod plugin; 10 | mod utils; 11 | -------------------------------------------------------------------------------- /app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wireguard-uwp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | description = "Foreground App for managing WireGuard UWP VPN profiles." 7 | 8 | [dependencies] 9 | 10 | [dependencies.windows] 11 | version = "0.28" 12 | features = [ 13 | "alloc", 14 | "build", 15 | "std", 16 | "ApplicationModel_Activation", 17 | "Foundation_Collections", 18 | "UI_Xaml_Controls", 19 | "UI_Xaml_Documents", 20 | "UI_Xaml_Media", 21 | "UI_Xaml", 22 | "Win32_System_Com", 23 | ] 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "app", 5 | "plugin", 6 | ] 7 | 8 | [profile.release] 9 | # Enable link-time optimization, eliminates more code and inlines across crate boundaries. 10 | lto = true 11 | 12 | # codegen-units of 1 gives best optimization, but disables parallel building. 13 | codegen-units = 1 14 | 15 | # Includes debug information in release builds. Necessary for profiling. Does not 16 | # slow down the executable. 17 | debug = true 18 | 19 | # The default optimization level is 3 for release mode builds. 20 | # 0 means disable optimization and is the default for debug mode buids. 21 | # (Setting opt-level=1 for debug builds is a good way of speeding them up a bit.) 22 | # "s" means optimize for size, "z" reduces size even more. 23 | opt-level = 3 -------------------------------------------------------------------------------- /plugin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wireguard-uwp-plugin" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | description = "WireGuard UWP VPN plugin." 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | base64 = "0.13" 13 | boringtun = "0.3" 14 | ipnetwork = "0.18" 15 | quick-xml = { version = "0.22", features = ["serialize"] } 16 | serde = { version = "1.0", features = ["derive"] } 17 | serde_with = "1.11" 18 | win_etw_macros = "0.1" 19 | win_etw_provider = "0.1" 20 | 21 | [dependencies.windows] 22 | version = "0.28" 23 | features = [ 24 | "alloc", 25 | "build", 26 | "std", 27 | "ApplicationModel_Background", 28 | "ApplicationModel_Core", 29 | "Foundation_Collections", 30 | "Networking_Sockets", 31 | "Networking_Vpn", 32 | "Storage_Streams", 33 | "Win32_Foundation", 34 | "Win32_System_Diagnostics_Debug", 35 | "Win32_System_WinRT", 36 | ] 37 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Luqman Aden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /plugin/src/logging.rs: -------------------------------------------------------------------------------- 1 | //! Logging primitives along with our ETW Trace Provider. 2 | 3 | use win_etw_macros::trace_logging_provider; 4 | 5 | /// The collection of ETW events our plugin emits. 6 | #[allow(non_snake_case)] 7 | #[trace_logging_provider(guid = "c4522a55-401f-4b81-93f9-aa0d1db734c4")] 8 | pub trait WireGuardUWPEvents { 9 | /// `Connect` event emitted once we've successfully connected 10 | #[event(level = "info")] 11 | fn connected(remote_host: &str, remote_port: u16); 12 | /// Event emitted if we've failed during `Connect` 13 | #[event(level = "error")] 14 | fn connect_fail(code: u32, msg: &str); 15 | 16 | /// Event emitted for `Disconnect` 17 | #[event(level = "warn")] 18 | fn disconnect(code: u32, msg: &str); 19 | 20 | // Noisy packet encap/decap events 21 | 22 | /// Packet encap begin event. 23 | /// Indicates how many outgoing packets are ready to be encapsulated. 24 | #[event(level = "verbose")] 25 | fn encapsulate_begin(packets: u32); 26 | /// Packet encap end event. 27 | /// Indicates how many frames we sent to the remote endpoint. 28 | #[event(level = "verbose")] 29 | fn encapsulate_end(frames: u32); 30 | 31 | /// Frame decap begin event. 32 | /// Indicates the size of the frame received from the remote endpoint. 33 | #[event(level = "verbose")] 34 | fn decapsulate_begin(frame_sz: u32); 35 | /// Frame decap end event. 36 | /// Indicates how many packets were decapsulated and how many frames sent to the remote. 37 | #[event(level = "verbose")] 38 | fn decapsulate_end(packets: u32, control_frames: u32); 39 | 40 | /// KeepAlive packet event. 41 | /// Indicates how many bytes destined for remote. 42 | #[event(level = "info")] 43 | fn keepalive(packet_sz: u32); 44 | } 45 | -------------------------------------------------------------------------------- /appx/AppxManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | WireGuard UWP VPN 13 | Publisher 14 | StoreLogo.png 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | wireguard_uwp_plugin.dll 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /plugin/src/config.rs: -------------------------------------------------------------------------------- 1 | //! Config parsing. 2 | 3 | use std::net::IpAddr; 4 | 5 | use boringtun::crypto::x25519::{X25519PublicKey, X25519SecretKey}; 6 | use ipnetwork::IpNetwork; 7 | use serde::Deserialize; 8 | use serde_with::{serde_as, DisplayFromStr}; 9 | 10 | /// A fully-parsed config 11 | #[derive(Deserialize)] 12 | #[serde(rename_all = "PascalCase")] 13 | pub struct WireGuardConfig { 14 | /// Local interface configuration 15 | pub interface: InterfaceConfig, 16 | 17 | /// Remote peer configuration 18 | pub peer: PeerConfig, 19 | } 20 | 21 | impl WireGuardConfig { 22 | /// Parse the config from the given string or return an error. 23 | pub fn from_str(s: &str) -> Result { 24 | quick_xml::de::from_str(s) 25 | } 26 | } 27 | 28 | /// Local VPN interface specific configuration 29 | #[serde_as] 30 | #[derive(Deserialize)] 31 | #[serde(rename_all = "PascalCase")] 32 | pub struct InterfaceConfig { 33 | /// Our local private key 34 | #[serde_as(as = "DisplayFromStr")] 35 | pub private_key: X25519SecretKey, 36 | 37 | /// Addresses to assign to local VPN interface 38 | pub address: Vec, 39 | 40 | /// DNS servers 41 | #[serde(default)] 42 | #[serde(rename = "DNS")] 43 | pub dns_servers: Vec, 44 | 45 | /// DNS Search Domains 46 | #[serde(default)] 47 | #[serde(rename = "DNSSearch")] 48 | pub search_domains: Vec, 49 | } 50 | 51 | /// Remote peer specific configuration 52 | #[serde_as] 53 | #[derive(Deserialize)] 54 | #[serde(rename_all = "PascalCase")] 55 | pub struct PeerConfig { 56 | /// The remote endpoint's public key 57 | #[serde_as(as = "DisplayFromStr")] 58 | pub public_key: X25519PublicKey, 59 | 60 | /// The port the remote endpoint is listening 61 | pub port: u16, 62 | 63 | /// The list of addresses that will get routed to the remote endpoint 64 | #[serde(rename = "AllowedIPs")] 65 | pub allowed_ips: Vec, 66 | 67 | /// The list of addresses that won't get routed to the remote endpoint 68 | #[serde(default)] 69 | #[serde(rename = "ExcludedIPs")] 70 | pub excluded_ips: Vec, 71 | 72 | /// The interval at which to send KeepAlive packets. 73 | pub persistent_keepalive: Option, 74 | 75 | /// An optional pre-shared key to enable an additional layer of security 76 | #[serde(default)] 77 | #[serde(deserialize_with = "from_base64")] 78 | pub preshared_key: Option<[u8; 32]>, 79 | } 80 | 81 | /// Try to parse the base64 encoded pre-shared key from the config 82 | /// into the raw bytes it represents. 83 | fn from_base64<'de, D>(deserializer: D) -> Result, D::Error> 84 | where 85 | D: serde::Deserializer<'de>, 86 | { 87 | use serde::de::Error; 88 | match Option::::deserialize(deserializer) { 89 | Ok(s) => match s { 90 | Some(s) => match base64::decode(&s) { 91 | Ok(b) => match b.try_into() { 92 | Ok(b) => Ok(Some(b)), 93 | Err(_) => Err(Error::custom("invalid pre-shared key")), 94 | }, 95 | Err(e) => Err(Error::custom(e.to_string())), 96 | }, 97 | None => Ok(None), 98 | }, 99 | Err(e) => Err(e), 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains the foreground portion of our UWP VPN plugin app. 2 | //! 3 | //! We use XAML programmatically to generate the UI. 4 | 5 | #![windows_subsystem = "windows"] 6 | #![allow(non_snake_case)] // Windows naming conventions 7 | 8 | use windows::{ 9 | self as Windows, 10 | core::*, 11 | ApplicationModel::Activation::LaunchActivatedEventArgs, 12 | Foundation::Uri, 13 | Win32::System::Com::{CoInitializeEx, COINIT_MULTITHREADED}, 14 | UI::Xaml::{Application, ApplicationInitializationCallback}, 15 | }; 16 | 17 | /// Encapsulates our app and overrides the relevant lifecycle management methods. 18 | #[implement( 19 | extend Windows::UI::Xaml::Application, 20 | override OnLaunched 21 | )] 22 | struct App; 23 | 24 | impl App { 25 | /// This method get invoked when the app is initially launched. 26 | fn OnLaunched(&self, _args: &Option) -> Result<()> { 27 | use Windows::{ 28 | UI::Xaml::Controls::{Grid, Page, TextBlock}, 29 | UI::Xaml::Documents::{Hyperlink, LineBreak, Run}, 30 | UI::Xaml::Media::SolidColorBrush, 31 | UI::Xaml::Thickness, 32 | UI::Xaml::Window, 33 | }; 34 | 35 | // Create the initial UI 36 | let content = TextBlock::new()?; 37 | let inline_content = content.Inlines()?; 38 | 39 | inline_content.Append({ 40 | let run = Run::new()?; 41 | run.SetFontSize(32.)?; 42 | let color = SolidColorBrush::new()?; 43 | color.SetColor(Windows::UI::Color { 44 | A: 0xFF, 45 | R: 0xFC, 46 | G: 51, 47 | B: 0x85, 48 | })?; 49 | run.SetForeground(color)?; 50 | run.SetText("WireGuard + UWP + Rust")?; 51 | run 52 | })?; 53 | inline_content.Append(LineBreak::new()?)?; 54 | inline_content.Append(LineBreak::new()?)?; 55 | inline_content.Append({ 56 | let run = Run::new()?; 57 | run.SetText("No profiles found ")?; 58 | run 59 | })?; 60 | inline_content.Append({ 61 | let add_link = Hyperlink::new()?; 62 | add_link.Inlines()?.Append({ 63 | let run = Run::new()?; 64 | run.SetText("add one")?; 65 | run 66 | })?; 67 | add_link.SetNavigateUri(Uri::CreateUri("ms-settings:network-vpn")?)?; 68 | add_link 69 | })?; 70 | inline_content.Append({ 71 | let run = Run::new()?; 72 | run.SetText("!")?; 73 | run 74 | })?; 75 | 76 | let root = Page::new()?; 77 | root.SetContent({ 78 | let grid = Grid::new()?; 79 | grid.SetPadding(Thickness { 80 | Left: 40., 81 | Top: 40., 82 | Right: 40., 83 | Bottom: 40., 84 | })?; 85 | grid.Children()?.Append(content)?; 86 | grid 87 | })?; 88 | 89 | // Grab the ambient Window created for our UWP app and set the content 90 | let window = Window::Current()?; 91 | window.SetContent(root)?; 92 | window.Activate() 93 | } 94 | } 95 | 96 | fn main() -> Result<()> { 97 | // We must initialize a COM MTA before initializing the rest of the App 98 | unsafe { 99 | CoInitializeEx(std::ptr::null_mut(), COINIT_MULTITHREADED)?; 100 | } 101 | 102 | // Go ahead with the XAML application initialization. 103 | // `Windows::UI::Xaml::Application` (which `App` derives from) is responsible for setting up 104 | // the CoreWindow and Dispatcher for us before calling our overridden OnLaunched/OnActivated. 105 | Application::Start(ApplicationInitializationCallback::new(|_| { 106 | App.new().map(|_| ()) 107 | })) 108 | } 109 | -------------------------------------------------------------------------------- /plugin/src/background.rs: -------------------------------------------------------------------------------- 1 | //! The entrypoint for the background task where our actual VPN plugin runs. 2 | 3 | use std::mem::ManuallyDrop; 4 | 5 | use windows::{ 6 | self as Windows, 7 | core::*, 8 | ApplicationModel::Background::IBackgroundTaskInstance, 9 | ApplicationModel::Core::CoreApplication, 10 | Networking::Vpn::{IVpnPlugIn, VpnChannel}, 11 | Win32::Foundation::{E_INVALIDARG, E_NOINTERFACE, E_UNEXPECTED, S_OK}, 12 | Win32::System::WinRT::IActivationFactory, 13 | }; 14 | 15 | /// The WinRT Activatable Class which acts as the entrypoint for the background tasks 16 | /// which get invoked to handle the actual VPN tunnel. 17 | #[implement(Windows::ApplicationModel::Background::IBackgroundTask)] 18 | pub struct VpnBackgroundTask; 19 | 20 | impl VpnBackgroundTask { 21 | fn Run(&self, task: &Option) -> Result<()> { 22 | let task = task.as_ref().ok_or(Error::from(E_UNEXPECTED))?; 23 | let deferral = task.GetDeferral()?; 24 | 25 | // Grab existing plugin instance from in-memory app properties or create a new one 26 | let app_props = CoreApplication::Properties()?; 27 | let plugin = if app_props.HasKey("plugin")? { 28 | app_props.Lookup("plugin")?.cast()? 29 | } else { 30 | let plugin: IVpnPlugIn = super::plugin::VpnPlugin::new().into(); 31 | app_props.Insert("plugin", plugin.clone())?; 32 | plugin 33 | }; 34 | 35 | // Call into VPN platform with the plugin object 36 | VpnChannel::ProcessEventAsync(plugin, task.TriggerDetails()?)?; 37 | 38 | deferral.Complete()?; 39 | 40 | Ok(()) 41 | } 42 | } 43 | 44 | /// A factory object to generate `VpnBackgroundTask`. 45 | /// 46 | /// Returned by `DllGetActivationFactory` when the system attempts to get an 47 | /// instance of `VpnBackgroundTask`. 48 | #[implement(Windows::Win32::System::WinRT::IActivationFactory)] 49 | struct VpnBackgroundTaskFactory; 50 | 51 | impl VpnBackgroundTaskFactory { 52 | /// Creates and returns a new instance of `VpnBackgroundTask`. 53 | fn ActivateInstance(&self) -> Result { 54 | Ok(VpnBackgroundTask.into()) 55 | } 56 | } 57 | 58 | /// Called by any consumers of this library attempting to get instances of any activatable 59 | /// Windows Runtime classes we support. 60 | /// 61 | /// When the system is ready to launch our VPN background task, it needs to get a reference 62 | /// to our `VpnBackgroundTask` object. It can do so because as part of our `AppxManifest.xml` 63 | /// we list out which Activatable Classes (VpnBackgroundTask) we want registered during App 64 | /// installation. Furthermore, we specify that the component is hosted in our DLL. From there, 65 | /// it knows to query us via the `DllGetActivationFactory` function we export to get some 66 | /// object implementing `IActivationFactory` which knows how to create new instances of the 67 | /// target WinRT runtime class. 68 | /// 69 | /// Since `activatableClassId` is an _In_ parameter, the caller is responsible for freeing it. 70 | /// But the HSTRING wrapper from the windows crate has a `Drop` impl which will attempt to free 71 | /// it once it goes out of scope. Unfortunately, that would be a double-free once we've returned 72 | /// back to the caller who would also attempt to free it. Hence, we transparently wrap the HSTRING 73 | /// with ManuallyDrop to skip any free'ing on the Rust side. 74 | #[no_mangle] 75 | pub unsafe extern "system" fn DllGetActivationFactory( 76 | activatableClassId: ManuallyDrop, 77 | factory: *mut Option, 78 | ) -> HRESULT { 79 | if activatableClassId.is_empty() || factory.is_null() { 80 | return E_INVALIDARG; 81 | } 82 | 83 | // Return the appropriate factory based on which class was requested 84 | if *activatableClassId == "WireGuard-UWP.VpnBackgroundTask" { 85 | *factory = Some(VpnBackgroundTaskFactory.into()); 86 | } else { 87 | *factory = None; 88 | return E_NOINTERFACE; 89 | } 90 | 91 | S_OK 92 | } 93 | -------------------------------------------------------------------------------- /plugin/src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Utilities and helper types that don't quite fit anywhere else. 2 | 3 | use std::sync::atomic::{AtomicU32, Ordering}; 4 | 5 | use windows::{ 6 | self as Windows, 7 | core::*, 8 | Foundation::Collections::{IIterable, IIterator, IVector, IVectorView}, 9 | Networking::Vpn::VpnPacketBuffer, 10 | Win32::Foundation::{E_BOUNDS, E_NOTIMPL}, 11 | Win32::System::WinRT::IBufferByteAccess, 12 | }; 13 | 14 | /// A simple wrapper around `Vec` which implements the `IVector`, `IVectorView` and 15 | /// `IIterable` interfaces. 16 | #[implement( 17 | Windows::Foundation::Collections::IIterable, 18 | Windows::Foundation::Collections::IVector, 19 | Windows::Foundation::Collections::IVectorView 20 | )] 21 | pub struct Vector(Vec); 22 | 23 | impl Vector { 24 | pub fn new(v: Vec) -> Vector { 25 | Vector(v) 26 | } 27 | 28 | fn First(&self) -> Result> { 29 | Ok(VectorIterator:: { 30 | it: self.cast()?, 31 | curr: AtomicU32::new(0), 32 | } 33 | .into()) 34 | } 35 | 36 | fn GetView(&self) -> Result> { 37 | Ok(self.cast()?) 38 | } 39 | 40 | fn GetAt(&self, index: u32) -> Result { 41 | self.0 42 | .get(index as usize) 43 | // SAFETY: `DefaultType` is a super trait of `RuntimeType`. 44 | .map(|el| unsafe { DefaultType::from_default(el) }) 45 | .transpose()? 46 | .ok_or(Error::from(E_BOUNDS)) 47 | } 48 | 49 | fn Size(&self) -> Result { 50 | u32::try_from(self.0.len()).map_err(|_| Error::from(E_BOUNDS)) 51 | } 52 | 53 | fn IndexOf(&self, value: &T::DefaultType, index: &mut u32) -> Result { 54 | if let Some(idx) = self.0.iter().position(|el| el == value) { 55 | *index = u32::try_from(idx).map_err(|_| Error::from(E_BOUNDS))?; 56 | Ok(true) 57 | } else { 58 | Ok(false) 59 | } 60 | } 61 | 62 | fn GetMany(&self, start: u32, items: &mut [T::DefaultType]) -> Result { 63 | let sz = u32::try_from(self.0.len()).map_err(|_| Error::from(E_BOUNDS))?; 64 | 65 | if start >= sz { 66 | return Err(Error::from(E_BOUNDS)); 67 | } 68 | 69 | let mut count = 0; 70 | for (item, el) in items.into_iter().zip(self.0[start as usize..].iter()) { 71 | *item = el.clone(); 72 | count += 1; 73 | } 74 | Ok(count) 75 | } 76 | 77 | fn SetAt(&self, _index: u32, _value: &T::DefaultType) -> Result<()> { 78 | Err(E_NOTIMPL.into()) 79 | } 80 | 81 | fn InsertAt(&self, _index: u32, _value: &T::DefaultType) -> Result<()> { 82 | Err(E_NOTIMPL.into()) 83 | } 84 | 85 | fn RemoveAt(&self, _index: u32) -> Result<()> { 86 | Err(E_NOTIMPL.into()) 87 | } 88 | 89 | fn Append(&self, _value: &T::DefaultType) -> Result<()> { 90 | Err(E_NOTIMPL.into()) 91 | } 92 | 93 | fn RemoveAtEnd(&self) -> Result<()> { 94 | Err(E_NOTIMPL.into()) 95 | } 96 | 97 | fn Clear(&self) -> Result<()> { 98 | Err(E_NOTIMPL.into()) 99 | } 100 | 101 | fn ReplaceAll(&self, _values: &[T::DefaultType]) -> Result<()> { 102 | Err(E_NOTIMPL.into()) 103 | } 104 | } 105 | 106 | impl<'a, T: RuntimeType + 'static> IntoParam<'a, IVectorView> for Vector { 107 | fn into_param(self) -> Param<'a, IVectorView> { 108 | Param::Owned(self.into()) 109 | } 110 | } 111 | 112 | impl<'a, T: RuntimeType + 'static> IntoParam<'a, IVector> for Vector { 113 | fn into_param(self) -> Param<'a, IVector> { 114 | Param::Owned(self.into()) 115 | } 116 | } 117 | 118 | /// `IIterator` wrapper for `Vector` 119 | #[implement(Windows::Foundation::Collections::IIterator)] 120 | struct VectorIterator { 121 | /// The underlying object we're iteratoring over 122 | it: IIterable, 123 | /// The current position of the iterator 124 | curr: AtomicU32, 125 | } 126 | 127 | impl VectorIterator { 128 | fn Current(&self) -> Result { 129 | // SAFETY: We know this must be our `Vector` type 130 | let vec = unsafe { Vector::to_impl(&self.it) }; 131 | vec.GetAt(self.curr.load(Ordering::Relaxed)) 132 | } 133 | 134 | fn HasCurrent(&self) -> Result { 135 | // SAFETY: We know this must be our `Vector` type 136 | let vec = unsafe { Vector::to_impl(&self.it) }; 137 | Ok(vec.0.len() > self.curr.load(Ordering::Relaxed) as usize) 138 | } 139 | 140 | fn MoveNext(&self) -> Result { 141 | // SAFETY: We know this must be our `Vector` type 142 | let vec = unsafe { Vector::to_impl(&self.it) }; 143 | let old = self.curr.fetch_add(1, Ordering::Relaxed) as usize; 144 | Ok(vec.0.len() > old + 1) 145 | } 146 | 147 | fn GetMany(&self, items: &mut [T::DefaultType]) -> Result { 148 | // SAFETY: We know this must be our `Vector` type 149 | let vec = unsafe { Vector::to_impl(&self.it) }; 150 | vec.GetMany(0, items) 151 | } 152 | } 153 | 154 | pub trait IBufferExt { 155 | /// Get a slice to an `IBuffer`'s underlying buffer. 156 | /// 157 | /// NOTE: This returns a slice with the length set to the IBuffer's Length and not Capacity. 158 | fn get_buf(&self) -> Result<&[u8]>; 159 | 160 | /// Get a mutable slice to an `IBuffer`'s underlying buffer. 161 | /// 162 | /// NOTE: This returns a slice with the length set to the IBuffer's Capacity and not Length. 163 | /// 164 | /// TODO: Is this safe? 165 | /// For `VpnPacketBuffer` at least, the buffer should be initialized & zeroed. 166 | fn get_buf_mut(&mut self) -> Result<&mut [u8]>; 167 | } 168 | 169 | impl IBufferExt for VpnPacketBuffer { 170 | fn get_buf(&self) -> Result<&[u8]> { 171 | let buffer = self.Buffer()?; 172 | let len = buffer.Length()?; 173 | let rawBuffer = buffer.cast::()?; 174 | Ok(unsafe { 175 | // SAFETY: Any type that implements `IBuffer` must also implement `IBufferByteAccess` 176 | // to return the buffer as an array of bytes. 177 | std::slice::from_raw_parts(rawBuffer.Buffer()?, len as usize) 178 | }) 179 | } 180 | 181 | fn get_buf_mut(&mut self) -> Result<&mut [u8]> { 182 | let buffer = self.Buffer()?; 183 | let cap = buffer.Capacity()?; 184 | let rawBuffer = buffer.cast::()?; 185 | Ok(unsafe { 186 | // SAFETY: Any type that implements `IBuffer` must also implement `IBufferByteAccess` 187 | // to return the buffer as an array of bytes. 188 | std::slice::from_raw_parts_mut(rawBuffer.Buffer()?, cap as usize) 189 | }) 190 | } 191 | } 192 | 193 | macro_rules! debug_log { 194 | ($fmt:tt) => { 195 | unsafe { 196 | use windows::Win32::Foundation::PSTR; 197 | use windows::Win32::System::Diagnostics::Debug::OutputDebugStringA; 198 | let mut msg = format!(concat!($fmt, "\n\0")); 199 | OutputDebugStringA(PSTR(msg.as_mut_ptr())); 200 | } 201 | }; 202 | ($fmt:tt, $($arg:tt)*) => { 203 | unsafe { 204 | use windows::Win32::Foundation::PSTR; 205 | use windows::Win32::System::Diagnostics::Debug::OutputDebugStringA; 206 | let mut msg = format!(concat!($fmt, "\n\0"), $($arg)*); 207 | OutputDebugStringA(PSTR(msg.as_mut_ptr())); 208 | } 209 | }; 210 | } 211 | 212 | pub(crate) use debug_log; 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WireGuard UWP 2 | 3 | A Universal Windows Platform (UWP) [VPN Plug-in](https://docs.microsoft.com/en-us/uwp/api/windows.networking.vpn.ivpnplugin) for [WireGuard®](https://www.wireguard.com/) written in [Rust](https://www.rust-lang.org/). 4 | 5 | Windows provides a plug-in based model for adding 3rd-party VPN protocols. VPN profiles 6 | backed by such a plugin are referred to as **Plugin**/**3rd-party**/**UWP** profiles, as opposed 7 | to **Native** profiles (i.e. built-in SSTP, IKEv2). 8 | 9 | **WireGuard** is a VPN protocol that aims to be: Fast, Modern and Secure. Principles 10 | which dovetail quite nicely with the Rust programming language. The actual noise-based WireGuard 11 | implementation comes from Cloudflare's [boringtun](https://github.com/cloudflare/boringtun). 12 | 13 | With the rapidly maturing [Rust for Windows](https://github.com/microsoft/windows-rs) bindings, 14 | this projects serve as a fun experiment in putting all the above together. 15 | 16 | ## Building 17 | 18 | Make sure you have Rust installed. Then, once you've cloned the repo just simply run: 19 | ```console 20 | $ cargo build --release 21 | ``` 22 | 23 | The project currently only builds on Windows but given the Windows-specific nature, that's not 24 | considered a limitation. 25 | 26 | ## Installing 27 | 28 | Once you've successfully built the project, you can install it by running the following commands 29 | in a powershell prompt from the repo root: 30 | ```powershell 31 | copy appx\* target\release 32 | Add-AppxPackage -Register .\target\release\AppxManifest.xml 33 | ``` 34 | 35 | **NOTE:** This does an in-place sort of installation in that the installed app will refer to 36 | the binaries in your `target\release` folder. So you may encounter issues if you modify those 37 | after installation. This is just a stop-gap until a proper `.appx` can be generated. 38 | 39 | ## Running 40 | 41 | To get your VPN tunnel up and running: 42 | 43 | 1. Open Windows Settings and navigate to the VPN page: 44 | `Network & Internet > VPN`. 45 | 2. Select `Add a VPN connection`. 46 | 3. From the `VPN provider` dropdown select **WireGuard UWP VPN**. 47 | 4. Give your new VPN profile a name under `Connection name`. 48 | 5. Enter the remote endpoint hostname or IP address under `Server name or address`. 49 | 6. Hit `Save`. 50 | 51 | The settings you can tweak from the Windows Settings UI are limited to just the profile name 52 | and remote endpoint's hostname. To modify the private key, public key, remote port etc we must 53 | set those values manually. From a powershell prompt: 54 | 55 | ```powershell 56 | $vpnConfig = @' 57 | 58 | 59 | ... 60 |
10.0.0.2/32
61 |
2001:db8::2/64
62 | 1.1.1.1 63 | vpn.example.com 64 | foo.corp.example.com 65 |
66 | 67 | ... 68 | 51000 69 | 10.0.0.0/24 70 | 10.10.0.0/24 71 | 10.20.0.0/24 72 | 2001:db8::/64 73 | 25 74 | 75 |
76 | '@ 77 | 78 | Set-VpnConnection -Name ProfileNameHere -CustomConfiguration $vpnConfig 79 | ``` 80 | 81 | The only required values are `PrivateKey`, `Address`, `PublicKey`, & `Port`. The rest are optional. 82 | You may repeat `Address` multiple times to assign multiple IPv4 & IPv6 addresses to the virtual 83 | interface. Similarly, you may specify `AllowedIPs` multiple times to define the routes that 84 | should go over the virtual interface. 85 | 86 | You should now be able to select the new profile and hit `Connect`. 87 | 88 | **NOTE:** Ideally, you could just specify `Port` colon separated with the hostname but the 89 | corresponding API for retrieving that value is statically typed as a HostName. 90 | 91 | **NOTE:** You should make sure to set a `PersistentKeepalive` value on the remote 92 | side for each **WireGuard UWP**-based client because the UWP VPN plugin model 93 | offers limited options for the plugin to perform periodic actions. Generally, 94 | the plugin will only be woken if there are packets that need to be encapsulated or 95 | if there are incoming buffers from the remote that need to be decapsulated. The 96 | platform does provide a `GetKeepAlivePayload` it will call on occasion but the 97 | interval in which it will be called cannot be controlled by the plugin author or 98 | the user but rather the platform itself. Hence it's important to make sure the 99 | server will keep the tunnel alive by sending the periodic keep alives in-band. 100 | 101 | **NOTE:** The main foreground app is planned to offer a simple UI for setting and modifying these 102 | values. 103 | 104 | This has only been tested on Windows 10 21H1 (19043.1348) but should work on any updated 105 | Windows 10 or 11 release. It'll probably work on older versions but no guarantees. 106 | 107 | ### Address 108 | 109 | You must specify one or more IPv4 and/or IPv6 addresses to assign to the virtual interface. 110 | 111 | ### DNS 112 | 113 | DNS servers are plumbed via [Name Resolution Policy Table](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/dn593632(v=ws.11)) (NRPT) Rules. 114 | You can print the current set of rules applied while a VPN profile backed by the plugin 115 | is connected via either: 116 | 117 | CMD: 118 | ```cmd 119 | netsh namespace show effectivepolicy 120 | ``` 121 | 122 | PowerShell: 123 | ```powershell 124 | Get-DnsClientNrptPolicy -Effective 125 | ``` 126 | 127 | If any number of DNS servers are specified in the config, the plugin will plumb one 128 | NRPT rule with the namespace set to `.`; this means the rule will apply to all 129 | domains. While NRPT rules do support matching based on domain suffixes/prefixes 130 | to apply domain-specific DNS servers, we just use a single wildcard rule. 131 | 132 | You will note another rule plumbed while the plugin is connected, for the specific 133 | domain used to connect to the remote endpoint. This is added automatically by the 134 | platform so that if the tunnel is interrupted and needs to be re-established, we 135 | don't end up in a situation wherein we can't resolve the remote's hostname. 136 | 137 | You can also specify one more optional search domains. These will be added to the 138 | VPN interface's **Connection-specific DNS Suffix Search List**. The first specified 139 | search domain will also be the primary Connection-specific DNS Suffix, as can be 140 | confirmed with `ipconfig /all` after connecting. You'll see an additional suffix-type 141 | NRPT rule for each such search domain configured, with the specific DNS servers set 142 | to whatever was configured. 143 | 144 | ### Routing 145 | 146 | If you'd like all traffic to flow over the VPN interface while connected, you can 147 | just add a catch-all route (`0.0.0.0/0` or `::/0` as appropriate): 148 | 149 | ```powershell 150 | $vpnConfig = @' 151 | 152 | 153 | ... 154 |
10.0.0.2/32
155 | 1.1.1.1 156 |
157 | 158 | ... 159 | 51000 160 | 0.0.0.0/0 161 | 25 162 | 163 |
164 | '@ 165 | 166 | Set-VpnConnection -Name ProfileNameHere -CustomConfiguration $vpnConfig 167 | ``` 168 | 169 | If you'd like to exclude certain routes from going over the VPN interface, you 170 | can specify one or more `ExcludedIPs` elements: 171 | 172 | ```powershell 173 | $vpnConfig = @' 174 | 175 | 176 | ... 177 |
10.0.0.2/32
178 | 1.1.1.1 179 |
180 | 181 | ... 182 | 51000 183 | 0.0.0.0/0 184 | 192.168.1.0/24 185 | 25 186 | 187 |
188 | '@ 189 | 190 | Set-VpnConnection -Name ProfileNameHere -CustomConfiguration $vpnConfig 191 | ``` 192 | 193 | This usually only makes sense if you have a catch-all route to carve out some 194 | exceptions. Also note that there will usually already be a specific route 195 | entry for your local subnet so no need to explicitly exclude it. 196 | 197 | To print the routing table in `cmd.exe` run: 198 | 199 | ```cmd 200 | route print 201 | ``` 202 | 203 | ## Tracing 204 | 205 | The plugin emits a number of [ETW](https://docs.microsoft.com/en-us/windows/win32/etw/event-tracing-portal) 206 | under the Event Provider identified by a specific GUID (`c4522a55-401f-4b81-93f9-aa0d1db734c4`). The events 207 | are emitted with the help of the [rust_win_etw](https://github.com/microsoft/rust_win_etw) crate which 208 | provides a way to define & emit ETW events from your Rust code. 209 | 210 | To consume these events, there are a number of different tools which can be used. **rust_win_etw** provides 211 | a quick rundown on how to capture them: https://github.com/microsoft/rust_win_etw#how-to-capture-and-view-events 212 | 213 | ## License 214 | 215 | Licensed under either of 216 | 217 | * Apache License, Version 2.0 218 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 219 | * MIT license 220 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 221 | 222 | at your option. 223 | 224 | ## Contribution 225 | 226 | Unless you explicitly state otherwise, any contribution intentionally submitted 227 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 228 | dual licensed as above, without any additional terms or conditions. 229 | 230 | --- 231 | "WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld. -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /plugin/src/plugin.rs: -------------------------------------------------------------------------------- 1 | //! Our implementation of `IVpnPlugIn` which is the bulk of the UWP VPN plugin. 2 | 3 | use std::sync::{Arc, RwLock}; 4 | use std::time::Duration; 5 | 6 | use boringtun::noise::{Tunn, TunnResult}; 7 | use ipnetwork::IpNetwork; 8 | use windows::{ 9 | self as Windows, 10 | core::*, 11 | Networking::Sockets::*, 12 | Networking::Vpn::*, 13 | Networking::*, 14 | Win32::Foundation::{E_BOUNDS, E_INVALIDARG, E_UNEXPECTED}, 15 | }; 16 | 17 | use crate::config::WireGuardConfig; 18 | use crate::logging::WireGuardUWPEvents; 19 | use crate::utils::{debug_log, IBufferExt, Vector}; 20 | 21 | struct Inner { 22 | tunn: Option>, 23 | } 24 | 25 | impl Inner { 26 | fn new() -> Self { 27 | Self { tunn: None } 28 | } 29 | } 30 | 31 | /// The VPN plugin object which provides the hooks that the UWP VPN platform will call into. 32 | #[implement(Windows::Networking::Vpn::IVpnPlugIn)] 33 | pub struct VpnPlugin { 34 | inner: RwLock, 35 | etw_logger: WireGuardUWPEvents, 36 | } 37 | 38 | impl VpnPlugin { 39 | pub fn new() -> Self { 40 | Self { 41 | inner: RwLock::new(Inner::new()), 42 | etw_logger: WireGuardUWPEvents::new(), 43 | } 44 | } 45 | 46 | /// Called by the platform so that we may connect and setup the VPN tunnel. 47 | fn Connect(&self, channel: &Option) -> Result<()> { 48 | // Call out to separate method so that we can capture any errors 49 | if let Err(err) = self.connect_inner(channel) { 50 | self.etw_logger 51 | .connect_fail(None, err.code().0, &err.to_string()); 52 | Err(err) 53 | } else { 54 | Ok(()) 55 | } 56 | } 57 | 58 | /// Internal `Connect` implementation. 59 | fn connect_inner(&self, channel: &Option) -> Result<()> { 60 | let channel = channel.as_ref().ok_or(Error::from(E_UNEXPECTED))?; 61 | let mut inner = self.inner.write().unwrap(); 62 | 63 | let config = channel.Configuration()?; 64 | 65 | // Grab custom config field from VPN profile and try to parse the config 66 | // In theory this would totally be fine to deal with as INI to match 67 | // most other wireguard config, but it's a bit of pain since a number of 68 | // places assume this will be XML... 69 | let wg_config = match WireGuardConfig::from_str(&config.CustomField()?.to_string()) { 70 | Ok(conf) => conf, 71 | Err(err) => { 72 | channel.SetErrorMessage(format!("failed to parse config: {}", err))?; 73 | return Err(Error::from(E_INVALIDARG)); 74 | } 75 | }; 76 | 77 | let static_private = Arc::new(wg_config.interface.private_key); 78 | let peer_static_public = Arc::new(wg_config.peer.public_key); 79 | let persistent_keepalive = wg_config.peer.persistent_keepalive; 80 | let preshared_key = wg_config.peer.preshared_key; 81 | 82 | // Grab interface addresses 83 | let iface_addrs = wg_config.interface.address; 84 | // Now massage em into the right form 85 | let (ipv4, ipv6) = iface_addrs 86 | .into_iter() 87 | .partition::, _>(IpNetwork::is_ipv4); 88 | let ipv4_addrs = ipv4 89 | .into_iter() 90 | .map(|ip| HostName::CreateHostName(ip.ip().to_string())) 91 | .collect::>>()? 92 | .into_iter() 93 | .map(Some) 94 | .collect::>(); 95 | let ipv4_addrs = if ipv4_addrs.is_empty() { 96 | None 97 | } else { 98 | Some(Vector::new(ipv4_addrs).into()) 99 | }; 100 | let ipv6_addrs = ipv6 101 | .into_iter() 102 | .map(|ip| HostName::CreateHostName(ip.ip().to_string())) 103 | .collect::>>()? 104 | .into_iter() 105 | .map(Some) 106 | .collect::>(); 107 | let ipv6_addrs = if ipv6_addrs.is_empty() { 108 | None 109 | } else { 110 | Some(Vector::new(ipv6_addrs).into()) 111 | }; 112 | 113 | let build_routes = |routes: Vec| -> Result<_> { 114 | let mut ipv4 = vec![]; 115 | let mut ipv6 = vec![]; 116 | 117 | for ip in routes { 118 | let route = VpnRoute::CreateVpnRoute( 119 | HostName::CreateHostName(ip.network().to_string())?, 120 | ip.prefix(), 121 | )?; 122 | if ip.is_ipv4() { 123 | ipv4.push(Some(route)); 124 | } else { 125 | ipv6.push(Some(route)); 126 | } 127 | } 128 | 129 | Ok((ipv4, ipv6)) 130 | }; 131 | 132 | let routes = VpnRouteAssignment::new()?; 133 | 134 | // Grab AllowedIPs and build routes from it 135 | let (allowed_ipv4, allowed_ipv6) = build_routes(wg_config.peer.allowed_ips)?; 136 | 137 | if !allowed_ipv4.is_empty() { 138 | routes.SetIpv4InclusionRoutes(Vector::new(allowed_ipv4))?; 139 | } 140 | if !allowed_ipv6.is_empty() { 141 | routes.SetIpv6InclusionRoutes(Vector::new(allowed_ipv6))?; 142 | } 143 | 144 | // Grab ExcludedIPs to determine exclusion routes 145 | let (excluded_ipv4, excluded_ipv6) = build_routes(wg_config.peer.excluded_ips)?; 146 | 147 | if !excluded_ipv4.is_empty() { 148 | routes.SetIpv4ExclusionRoutes(Vector::new(excluded_ipv4))?; 149 | } 150 | if !excluded_ipv6.is_empty() { 151 | routes.SetIpv6ExclusionRoutes(Vector::new(excluded_ipv6))?; 152 | } 153 | 154 | // Setup DNS 155 | let namespace_assignment = VpnNamespaceAssignment::new()?; 156 | let dns_servers = wg_config 157 | .interface 158 | .dns_servers 159 | .into_iter() 160 | .map(|server| HostName::CreateHostName(server.to_string())) 161 | .collect::>>()? 162 | .into_iter() 163 | .map(Some) 164 | .collect::>(); 165 | let search_domains = wg_config.interface.search_domains; 166 | 167 | let namespace_count = search_domains.len() + !dns_servers.is_empty() as usize; 168 | let mut namespaces = Vec::with_capacity(namespace_count); 169 | 170 | // Add the search domains as suffix NRPT rules so that 171 | // they get added to the virtual interface's 172 | // Connection-Specific DNS Suffix Search List. 173 | for mut search_domain in search_domains { 174 | // Prefix with . to make it a suffix rule 175 | search_domain.insert(0, '.'); 176 | let dns_servers = Vector::new(dns_servers.clone()); 177 | let namespace = 178 | VpnNamespaceInfo::CreateVpnNamespaceInfo(search_domain, dns_servers, None)?; 179 | namespaces.push(Some(namespace)); 180 | } 181 | 182 | if !dns_servers.is_empty() { 183 | // We set the namespace name to '.' so it applies to everything instead of 184 | // a specific set of domains (see NRPT) 185 | let dns_servers = Vector::new(dns_servers); 186 | let namespace = VpnNamespaceInfo::CreateVpnNamespaceInfo(".", dns_servers, None)?; 187 | namespaces.push(Some(namespace)); 188 | } 189 | 190 | namespace_assignment.SetNamespaceList(Vector::new(namespaces))?; 191 | 192 | // Create WG tunnel object 193 | let tunn = Tunn::new( 194 | static_private, 195 | peer_static_public, 196 | preshared_key, 197 | persistent_keepalive, 198 | 0, // Peer index. we only have one peer 199 | None, // TODO: No rate limiter 200 | ) 201 | // TODO: is E_UNEXPECTED the right error here? 202 | .map_err(|e| Error::new(E_UNEXPECTED, e.into()))?; 203 | 204 | // Stuff it into our inner state 205 | // Just forget the previous tunn state and start over (if one exists at all) 206 | if let Some(_) = std::mem::replace(&mut inner.tunn, Some(tunn)) { 207 | debug_log!("Replacing leftover tunn state."); 208 | } 209 | 210 | // Create socket and register with VPN platform 211 | let sock = DatagramSocket::new()?; 212 | channel.AddAndAssociateTransport(&sock, None)?; 213 | 214 | // Just use the first server listed to connect to remote endpoint 215 | let server = config.ServerHostNameList()?.GetAt(0)?; 216 | let port = wg_config.peer.port; 217 | 218 | debug_log!("Server: {} Port: {}", server.ToString()?.to_string(), port); 219 | 220 | // We "block" here with the call to `.get()` but given this is a UDP socket 221 | // connect isn't actually something that will hang (DNS aside perhaps?). 222 | sock.ConnectAsync(&server, port.to_string())?.get()?; 223 | 224 | // Kick off the VPN setup 225 | channel.Start( 226 | ipv4_addrs, 227 | ipv6_addrs, 228 | None, // Interface ID portion of IPv6 address for VPN tunnel 229 | routes, 230 | namespace_assignment, 231 | 1500, // MTU size of VPN tunnel interface 232 | 1600, // Max frame size of incoming buffers from remote endpoint 233 | false, // Disable low cost network monitoring 234 | sock, // Pass in the socket to the remote endpoint 235 | None, // No secondary socket used. 236 | )?; 237 | 238 | // Log successful connection 239 | self.etw_logger 240 | .connected(None, &server.ToString()?.to_string(), port); 241 | 242 | Ok(()) 243 | } 244 | 245 | /// Called by the platform to indicate we should disconnect and cleanup the VPN tunnel. 246 | fn Disconnect(&self, channel: &Option) -> Result<()> { 247 | // Call out to separate method so that we can capture any errors 248 | if let Err(err) = self.disconnect_inner(channel) { 249 | self.etw_logger 250 | .disconnect(None, err.code().0, &err.to_string()); 251 | Err(err) 252 | } else { 253 | self.etw_logger.disconnect(None, 0, "Operation successful."); 254 | Ok(()) 255 | } 256 | } 257 | 258 | /// Internal `Disconnect` implementation. 259 | fn disconnect_inner(&self, channel: &Option) -> Result<()> { 260 | let channel = channel.as_ref().ok_or(Error::from(E_UNEXPECTED))?; 261 | 262 | let mut inner = self.inner.write().unwrap(); 263 | inner.tunn = None; 264 | 265 | channel.Stop()?; 266 | 267 | Ok(()) 268 | } 269 | 270 | /// Called by the platform to indicate there are outgoing packets ready to be encapsulated. 271 | /// 272 | /// `packets` contains outgoing L3 IP packets that we should encapsulate in whatever protocol 273 | /// dependant manner before placing them in `encapsulatedPackets` so that they may be sent to 274 | /// the remote endpoint. 275 | fn Encapsulate( 276 | &self, 277 | channel: &Option, 278 | packets: &Option, 279 | encapsulatedPackets: &Option, 280 | ) -> Result<()> { 281 | let channel = channel.as_ref().ok_or(Error::from(E_UNEXPECTED))?; 282 | let packets = packets.as_ref().ok_or(Error::from(E_UNEXPECTED))?; 283 | let encapsulatedPackets = encapsulatedPackets 284 | .as_ref() 285 | .ok_or(Error::from(E_UNEXPECTED))?; 286 | 287 | let inner = self.inner.read().unwrap(); 288 | let tunn = if let Some(tunn) = &inner.tunn { 289 | &**tunn 290 | } else { 291 | // We haven't initalized tunn yet, just return 292 | return Ok(()); 293 | }; 294 | 295 | let mut ret_buffers = vec![]; 296 | let mut encap_err = None; 297 | 298 | // Usually this would be called in the background by some periodic timer 299 | // but a UWP VPN plugin will get suspended if there's no traffic and that 300 | // includes any background threads or such we could create. 301 | // So we may find ourselves with a stale session and need to do a new 302 | // handshake. Thus, we just call this opportunistically here before 303 | // trying to encapsulate. 304 | if tunn.time_since_last_handshake() >= Some(Duration::from_millis(250)) { 305 | const HANDSHAKE_INIT_SZ: usize = 148; 306 | let mut handshake_buf = [0u8; HANDSHAKE_INIT_SZ]; 307 | match tunn.update_timers(&mut handshake_buf) { 308 | // Session still valid, nothing more to do. 309 | TunnResult::Done => (), 310 | 311 | // Encountered an error, bail out 312 | TunnResult::Err(err) => { 313 | return Err(Error::new( 314 | E_UNEXPECTED, 315 | format!("update_timers error: {:?}", err).into(), 316 | )); 317 | } 318 | 319 | // Looks like we need to get things updated 320 | TunnResult::WriteToNetwork(packet) => { 321 | // Request a new buffer 322 | let mut handshake_buffer = channel.GetVpnSendPacketBuffer()?; 323 | 324 | // Copy data over and update length on WinRT buffer 325 | handshake_buffer.get_buf_mut()?[..packet.len()].copy_from_slice(packet); 326 | let new_len = u32::try_from(packet.len()).map_err(|_| Error::from(E_BOUNDS))?; 327 | handshake_buffer.Buffer()?.SetLength(new_len)?; 328 | 329 | // Now queue it up to be sent 330 | encapsulatedPackets.Append(handshake_buffer)?; 331 | } 332 | 333 | // Impossible cases for update_timers 334 | TunnResult::WriteToTunnelV4(_, _) | TunnResult::WriteToTunnelV6(_, _) => { 335 | panic!("unexpected result from update_timers"); 336 | } 337 | } 338 | } 339 | 340 | let packets_sz = packets.Size()?; 341 | self.etw_logger.encapsulate_begin(None, packets_sz); 342 | 343 | // Process outgoing packets from VPN tunnel. 344 | // TODO: Not using the simpler `for packet in packets` because 345 | // `packets.First()?` fails with E_NOINTERFACE for some reason. 346 | for _ in 0..packets_sz { 347 | let packet = packets.RemoveAtBegin()?; 348 | let src = packet.get_buf()?; 349 | 350 | // Grab a destination buffer for the encapsulated packet 351 | let mut encapPacket = channel.GetVpnSendPacketBuffer()?; 352 | let dst = encapPacket.get_buf_mut()?; 353 | 354 | // Try to encapsulate packet 355 | let res = tunn.encapsulate(src, dst); 356 | 357 | if let TunnResult::WriteToNetwork(packet) = res { 358 | // Packet was encap'd successfully, make sure to update length on the WinRT side 359 | let new_len = u32::try_from(packet.len()).map_err(|_| Error::from(E_BOUNDS))?; 360 | drop(packet); 361 | encapPacket.Buffer()?.SetLength(new_len)?; 362 | 363 | // Now, tack it onto `encapsulatedPackets` to send to remote endpoint 364 | encapsulatedPackets.Append(encapPacket)?; 365 | } else { 366 | match res { 367 | // Handled above 368 | TunnResult::WriteToNetwork(_) => {} 369 | 370 | // Packet was queued while we complete the handshake 371 | TunnResult::Done => {} 372 | 373 | // Encountered an error while trying to encapsulate 374 | TunnResult::Err(err) => { 375 | if encap_err.is_none() { 376 | encap_err = Some(Error::new( 377 | E_UNEXPECTED, 378 | format!("encap error: {:?}", err).into(), 379 | )); 380 | } 381 | } 382 | 383 | // Impossible cases for encapsulate 384 | TunnResult::WriteToTunnelV4(_, _) | TunnResult::WriteToTunnelV6(_, _) => { 385 | panic!("unexpected result from encapsulate") 386 | } 387 | } 388 | 389 | // We must return the `encapPacket` we requested 390 | ret_buffers.push(encapPacket); 391 | } 392 | 393 | // Note: this loop does not consume the items in packets which is important 394 | // as ANY `VpnPacketBuffer` we get (whether as some argument to a `IVpnPlugIn` 395 | // method or via methods on `VpnChannel`) we are expected to return to the 396 | // platform. Since we're not en/decapsulating in-place, it works out to leave 397 | // the buffers in `packets` so that the platform may clean them up. 398 | packets.Append(packet)?; 399 | } 400 | 401 | self.etw_logger 402 | .encapsulate_end(None, encapsulatedPackets.Size()?); 403 | 404 | // Just stick the unneeded buffers onto `packets` so the platform can clean them up 405 | for packet in ret_buffers { 406 | packets.Append(packet)?; 407 | } 408 | 409 | // If we encountered an error, return it 410 | if let Some(err) = encap_err { 411 | Err(err) 412 | } else { 413 | Ok(()) 414 | } 415 | } 416 | 417 | /// Called by the platform to indicate we've received a frame from the remote endpoint. 418 | /// 419 | /// `buffer` will contain whatever data we received from the remote endpoint which may 420 | /// either contain control or data payloads. For data payloads, we will decapsulate into 421 | /// 1 (or more) L3 IP packet(s) before returning them to the platform by placing them in 422 | /// `decapsulatedPackets`, making them ready to be injected into the virtual tunnel. If 423 | /// we need to send back control payloads or otherwise back to the remote endpoint, we 424 | /// may place such frames into `controlPackets`. 425 | fn Decapsulate( 426 | &self, 427 | channel: &Option, 428 | buffer: &Option, 429 | decapsulatedPackets: &Option, 430 | controlPackets: &Option, 431 | ) -> Result<()> { 432 | let channel = channel.as_ref().ok_or(Error::from(E_UNEXPECTED))?; 433 | let buffer = buffer.as_ref().ok_or(Error::from(E_UNEXPECTED))?; 434 | let decapsulatedPackets = decapsulatedPackets 435 | .as_ref() 436 | .ok_or(Error::from(E_UNEXPECTED))?; 437 | let controlPackets = controlPackets.as_ref().ok_or(Error::from(E_UNEXPECTED))?; 438 | 439 | let inner = self.inner.read().unwrap(); 440 | let tunn = if let Some(tunn) = &inner.tunn { 441 | &**tunn 442 | } else { 443 | // We haven't initalized tunn yet, just return 444 | return Ok(()); 445 | }; 446 | 447 | self.etw_logger 448 | .decapsulate_begin(None, buffer.Buffer()?.Length()?); 449 | 450 | // Allocate a buffer for the decapsulate packet 451 | let mut decapPacket = channel.GetVpnReceivePacketBuffer()?; 452 | let dst = decapPacket.get_buf_mut()?; 453 | 454 | // Get a slice to the datagram we just received from the remote endpoint and try to decap 455 | let datagram = buffer.get_buf()?; 456 | let res = tunn.decapsulate(None, datagram, dst); 457 | 458 | match res { 459 | // Nothing to do with this decap result 460 | TunnResult::Done => { 461 | // TODO: Return unused `decapPacket` buffer 462 | } 463 | 464 | // Encountered an error while trying to decapsulate 465 | TunnResult::Err(err) => { 466 | // TODO: Return unused `decapPacket` buffer 467 | return Err(Error::new( 468 | E_UNEXPECTED, 469 | format!("encap error: {:?}", err).into(), 470 | )); 471 | } 472 | 473 | // We need to send response back to remote endpoint 474 | TunnResult::WriteToNetwork(packet) => { 475 | // Make sure to update length on WinRT buffer 476 | let new_len = u32::try_from(packet.len()).map_err(|_| Error::from(E_BOUNDS))?; 477 | drop(packet); 478 | 479 | // TODO: technically, we really should've used `GetVpnSendPacketBuffer` for this 480 | // buffer but boringtun doesn't really have a way to know in advance if it'll 481 | // be giving back control packets instead of data packets. 482 | // We could just use temp buffers and copy as appropriate? 483 | let controlPacket = decapPacket; 484 | controlPacket.Buffer()?.SetLength(new_len)?; 485 | 486 | // Tack onto `controlPackets` so that they get sent to remote endpoint 487 | controlPackets.Append(controlPacket)?; 488 | 489 | // We need to probe for any more packets queued to send 490 | loop { 491 | // Allocate a buffer for control packet 492 | let mut controlPacket = channel.GetVpnSendPacketBuffer()?; 493 | let dst = controlPacket.get_buf_mut()?; 494 | 495 | let res = tunn.decapsulate(None, &[], dst); 496 | if let TunnResult::WriteToNetwork(packet) = res { 497 | // Make sure to update length on WinRT buffer 498 | let new_len = 499 | u32::try_from(packet.len()).map_err(|_| Error::from(E_BOUNDS))?; 500 | drop(packet); 501 | controlPacket.Buffer()?.SetLength(new_len)?; 502 | controlPackets.Append(controlPacket)?; 503 | } else { 504 | // TODO: Return unused `controlPacket` buffer 505 | // Nothing more to do 506 | break; 507 | } 508 | } 509 | } 510 | 511 | // Successfully decapsulated data packet 512 | TunnResult::WriteToTunnelV4(packet, _) | TunnResult::WriteToTunnelV6(packet, _) => { 513 | // Make sure to update length on WinRT buffer 514 | let new_len = u32::try_from(packet.len()).map_err(|_| Error::from(E_BOUNDS))?; 515 | drop(packet); 516 | decapPacket.Buffer()?.SetLength(new_len)?; 517 | 518 | // Tack onto `decapsulatedPackets` to inject into VPN interface 519 | decapsulatedPackets.Append(decapPacket)?; 520 | } 521 | } 522 | 523 | self.etw_logger 524 | .decapsulate_end(None, decapsulatedPackets.Size()?, controlPackets.Size()?); 525 | 526 | Ok(()) 527 | } 528 | 529 | /// Called by the platform from time to time so that we may send some keepalive payload. 530 | /// 531 | /// If we decide we want to send any keepalive payload, we place it in `keepAlivePacket`. 532 | fn GetKeepAlivePayload( 533 | &self, 534 | channel: &Option, 535 | keepAlivePacket: &mut Option, 536 | ) -> Result<()> { 537 | let channel = channel.as_ref().ok_or(Error::from(E_UNEXPECTED))?; 538 | 539 | let inner = self.inner.read().unwrap(); 540 | let tunn = if let Some(tunn) = &inner.tunn { 541 | &**tunn 542 | } else { 543 | // We haven't initalized tunn yet, just return 544 | return Ok(()); 545 | }; 546 | 547 | *keepAlivePacket = None; 548 | 549 | // Grab a buffer for the keepalive packet 550 | let mut kaPacket = channel.GetVpnSendPacketBuffer()?; 551 | let dst = kaPacket.get_buf_mut()?; 552 | 553 | // Any packets we need to send out? 554 | match tunn.update_timers(dst) { 555 | // Nothing to do right now 556 | TunnResult::Done => { 557 | // TODO: Return unused `kaPacket` buffer 558 | } 559 | 560 | // Encountered an error, bail out 561 | TunnResult::Err(err) => { 562 | // TODO: Return unused `kaPacket` buffer 563 | return Err(Error::new( 564 | // TODO: Better error than `E_UNEXPECTED`? 565 | E_UNEXPECTED, 566 | format!("update_timers error: {:?}", err).into(), 567 | )); 568 | } 569 | 570 | // We got something to send to the remote 571 | TunnResult::WriteToNetwork(packet) => { 572 | // Make sure to update length on WinRT buffer 573 | let new_len = u32::try_from(packet.len()).map_err(|_| Error::from(E_BOUNDS))?; 574 | drop(packet); 575 | kaPacket.Buffer()?.SetLength(new_len)?; 576 | 577 | self.etw_logger.keepalive(None, new_len); 578 | 579 | // Place the packet in the out param to send to remote 580 | *keepAlivePacket = Some(kaPacket); 581 | } 582 | 583 | // Impossible cases for update_timers 584 | TunnResult::WriteToTunnelV4(_, _) | TunnResult::WriteToTunnelV6(_, _) => { 585 | panic!("unexpected result from update_timers") 586 | } 587 | } 588 | 589 | Ok(()) 590 | } 591 | } 592 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.17.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "ascii" 22 | version = "0.9.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" 25 | 26 | [[package]] 27 | name = "autocfg" 28 | version = "1.0.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 31 | 32 | [[package]] 33 | name = "backtrace" 34 | version = "0.3.63" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" 37 | dependencies = [ 38 | "addr2line", 39 | "cc", 40 | "cfg-if", 41 | "libc", 42 | "miniz_oxide", 43 | "object", 44 | "rustc-demangle", 45 | ] 46 | 47 | [[package]] 48 | name = "base64" 49 | version = "0.12.3" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 52 | 53 | [[package]] 54 | name = "base64" 55 | version = "0.13.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 58 | 59 | [[package]] 60 | name = "bitflags" 61 | version = "1.3.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 64 | 65 | [[package]] 66 | name = "boringtun" 67 | version = "0.3.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "d62ad6ff2f841f576887ffd3e94a4ae438784dc566c44b3fbd783e5d31e04c99" 70 | dependencies = [ 71 | "base64 0.12.3", 72 | "chrono", 73 | "clap", 74 | "daemonize", 75 | "hex", 76 | "jni", 77 | "libc", 78 | "ring", 79 | "spin", 80 | "untrusted", 81 | ] 82 | 83 | [[package]] 84 | name = "boxfnonce" 85 | version = "0.1.1" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "5988cb1d626264ac94100be357308f29ff7cbdd3b36bda27f450a4ee3f713426" 88 | 89 | [[package]] 90 | name = "bumpalo" 91 | version = "3.8.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" 94 | 95 | [[package]] 96 | name = "byteorder" 97 | version = "1.4.3" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 100 | 101 | [[package]] 102 | name = "cc" 103 | version = "1.0.72" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 106 | 107 | [[package]] 108 | name = "cesu8" 109 | version = "1.1.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 112 | 113 | [[package]] 114 | name = "cfg-if" 115 | version = "1.0.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 118 | 119 | [[package]] 120 | name = "chrono" 121 | version = "0.4.19" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 124 | dependencies = [ 125 | "libc", 126 | "num-integer", 127 | "num-traits", 128 | "time", 129 | "winapi", 130 | ] 131 | 132 | [[package]] 133 | name = "clap" 134 | version = "2.34.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 137 | dependencies = [ 138 | "bitflags", 139 | "strsim 0.8.0", 140 | "textwrap", 141 | "unicode-width", 142 | ] 143 | 144 | [[package]] 145 | name = "combine" 146 | version = "3.8.1" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" 149 | dependencies = [ 150 | "ascii", 151 | "byteorder", 152 | "either", 153 | "memchr", 154 | "unreachable", 155 | ] 156 | 157 | [[package]] 158 | name = "daemonize" 159 | version = "0.4.1" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "70c24513e34f53b640819f0ac9f705b673fcf4006d7aab8778bee72ebfc89815" 162 | dependencies = [ 163 | "boxfnonce", 164 | "libc", 165 | ] 166 | 167 | [[package]] 168 | name = "darling" 169 | version = "0.13.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" 172 | dependencies = [ 173 | "darling_core", 174 | "darling_macro", 175 | ] 176 | 177 | [[package]] 178 | name = "darling_core" 179 | version = "0.13.1" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" 182 | dependencies = [ 183 | "fnv", 184 | "ident_case", 185 | "proc-macro2", 186 | "quote", 187 | "strsim 0.10.0", 188 | "syn", 189 | ] 190 | 191 | [[package]] 192 | name = "darling_macro" 193 | version = "0.13.1" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" 196 | dependencies = [ 197 | "darling_core", 198 | "quote", 199 | "syn", 200 | ] 201 | 202 | [[package]] 203 | name = "either" 204 | version = "1.6.1" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 207 | 208 | [[package]] 209 | name = "error-chain" 210 | version = "0.12.4" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 213 | dependencies = [ 214 | "backtrace", 215 | "version_check", 216 | ] 217 | 218 | [[package]] 219 | name = "fnv" 220 | version = "1.0.7" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 223 | 224 | [[package]] 225 | name = "gimli" 226 | version = "0.26.1" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" 229 | 230 | [[package]] 231 | name = "hex" 232 | version = "0.4.3" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 235 | 236 | [[package]] 237 | name = "ident_case" 238 | version = "1.0.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 241 | 242 | [[package]] 243 | name = "ipnetwork" 244 | version = "0.18.0" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "4088d739b183546b239688ddbc79891831df421773df95e236daf7867866d355" 247 | dependencies = [ 248 | "serde", 249 | ] 250 | 251 | [[package]] 252 | name = "jni" 253 | version = "0.10.2" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "1ecfa3b81afc64d9a6539c4eece96ac9a93c551c713a313800dade8e33d7b5c1" 256 | dependencies = [ 257 | "cesu8", 258 | "combine", 259 | "error-chain", 260 | "jni-sys", 261 | "log", 262 | "walkdir", 263 | ] 264 | 265 | [[package]] 266 | name = "jni-sys" 267 | version = "0.3.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 270 | 271 | [[package]] 272 | name = "js-sys" 273 | version = "0.3.55" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 276 | dependencies = [ 277 | "wasm-bindgen", 278 | ] 279 | 280 | [[package]] 281 | name = "lazy_static" 282 | version = "1.4.0" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 285 | 286 | [[package]] 287 | name = "libc" 288 | version = "0.2.109" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01" 291 | 292 | [[package]] 293 | name = "log" 294 | version = "0.4.14" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 297 | dependencies = [ 298 | "cfg-if", 299 | ] 300 | 301 | [[package]] 302 | name = "memchr" 303 | version = "2.4.1" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 306 | 307 | [[package]] 308 | name = "miniz_oxide" 309 | version = "0.4.4" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 312 | dependencies = [ 313 | "adler", 314 | "autocfg", 315 | ] 316 | 317 | [[package]] 318 | name = "num-integer" 319 | version = "0.1.44" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 322 | dependencies = [ 323 | "autocfg", 324 | "num-traits", 325 | ] 326 | 327 | [[package]] 328 | name = "num-traits" 329 | version = "0.2.14" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 332 | dependencies = [ 333 | "autocfg", 334 | ] 335 | 336 | [[package]] 337 | name = "object" 338 | version = "0.27.1" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" 341 | dependencies = [ 342 | "memchr", 343 | ] 344 | 345 | [[package]] 346 | name = "once_cell" 347 | version = "1.8.0" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 350 | 351 | [[package]] 352 | name = "proc-macro2" 353 | version = "1.0.33" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" 356 | dependencies = [ 357 | "unicode-xid", 358 | ] 359 | 360 | [[package]] 361 | name = "quick-xml" 362 | version = "0.22.0" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" 365 | dependencies = [ 366 | "memchr", 367 | "serde", 368 | ] 369 | 370 | [[package]] 371 | name = "quote" 372 | version = "1.0.10" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 375 | dependencies = [ 376 | "proc-macro2", 377 | ] 378 | 379 | [[package]] 380 | name = "ring" 381 | version = "0.16.20" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 384 | dependencies = [ 385 | "cc", 386 | "libc", 387 | "once_cell", 388 | "spin", 389 | "untrusted", 390 | "web-sys", 391 | "winapi", 392 | ] 393 | 394 | [[package]] 395 | name = "rustc-demangle" 396 | version = "0.1.21" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" 399 | 400 | [[package]] 401 | name = "rustversion" 402 | version = "1.0.6" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" 405 | 406 | [[package]] 407 | name = "same-file" 408 | version = "1.0.6" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 411 | dependencies = [ 412 | "winapi-util", 413 | ] 414 | 415 | [[package]] 416 | name = "serde" 417 | version = "1.0.131" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1" 420 | dependencies = [ 421 | "serde_derive", 422 | ] 423 | 424 | [[package]] 425 | name = "serde_derive" 426 | version = "1.0.131" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2" 429 | dependencies = [ 430 | "proc-macro2", 431 | "quote", 432 | "syn", 433 | ] 434 | 435 | [[package]] 436 | name = "serde_with" 437 | version = "1.11.0" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "ad6056b4cb69b6e43e3a0f055def223380baecc99da683884f205bf347f7c4b3" 440 | dependencies = [ 441 | "rustversion", 442 | "serde", 443 | "serde_with_macros", 444 | ] 445 | 446 | [[package]] 447 | name = "serde_with_macros" 448 | version = "1.5.1" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "12e47be9471c72889ebafb5e14d5ff930d89ae7a67bbdb5f8abb564f845a927e" 451 | dependencies = [ 452 | "darling", 453 | "proc-macro2", 454 | "quote", 455 | "syn", 456 | ] 457 | 458 | [[package]] 459 | name = "spin" 460 | version = "0.5.2" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 463 | 464 | [[package]] 465 | name = "strsim" 466 | version = "0.8.0" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 469 | 470 | [[package]] 471 | name = "strsim" 472 | version = "0.10.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 475 | 476 | [[package]] 477 | name = "syn" 478 | version = "1.0.82" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 481 | dependencies = [ 482 | "proc-macro2", 483 | "quote", 484 | "unicode-xid", 485 | ] 486 | 487 | [[package]] 488 | name = "synstructure" 489 | version = "0.12.6" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" 492 | dependencies = [ 493 | "proc-macro2", 494 | "quote", 495 | "syn", 496 | "unicode-xid", 497 | ] 498 | 499 | [[package]] 500 | name = "textwrap" 501 | version = "0.11.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 504 | dependencies = [ 505 | "unicode-width", 506 | ] 507 | 508 | [[package]] 509 | name = "time" 510 | version = "0.1.44" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 513 | dependencies = [ 514 | "libc", 515 | "wasi", 516 | "winapi", 517 | ] 518 | 519 | [[package]] 520 | name = "unicode-width" 521 | version = "0.1.9" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 524 | 525 | [[package]] 526 | name = "unicode-xid" 527 | version = "0.2.2" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 530 | 531 | [[package]] 532 | name = "unreachable" 533 | version = "1.0.0" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 536 | dependencies = [ 537 | "void", 538 | ] 539 | 540 | [[package]] 541 | name = "untrusted" 542 | version = "0.7.1" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 545 | 546 | [[package]] 547 | name = "uuid" 548 | version = "0.8.2" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" 551 | 552 | [[package]] 553 | name = "version_check" 554 | version = "0.9.3" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 557 | 558 | [[package]] 559 | name = "void" 560 | version = "1.0.2" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 563 | 564 | [[package]] 565 | name = "w32-error" 566 | version = "1.0.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "fa7c61a6bd91e168c12fc170985725340f6b458eb6f971d1cf6c34f74ffafb43" 569 | dependencies = [ 570 | "winapi", 571 | ] 572 | 573 | [[package]] 574 | name = "walkdir" 575 | version = "2.3.2" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 578 | dependencies = [ 579 | "same-file", 580 | "winapi", 581 | "winapi-util", 582 | ] 583 | 584 | [[package]] 585 | name = "wasi" 586 | version = "0.10.0+wasi-snapshot-preview1" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 589 | 590 | [[package]] 591 | name = "wasm-bindgen" 592 | version = "0.2.78" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 595 | dependencies = [ 596 | "cfg-if", 597 | "wasm-bindgen-macro", 598 | ] 599 | 600 | [[package]] 601 | name = "wasm-bindgen-backend" 602 | version = "0.2.78" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 605 | dependencies = [ 606 | "bumpalo", 607 | "lazy_static", 608 | "log", 609 | "proc-macro2", 610 | "quote", 611 | "syn", 612 | "wasm-bindgen-shared", 613 | ] 614 | 615 | [[package]] 616 | name = "wasm-bindgen-macro" 617 | version = "0.2.78" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 620 | dependencies = [ 621 | "quote", 622 | "wasm-bindgen-macro-support", 623 | ] 624 | 625 | [[package]] 626 | name = "wasm-bindgen-macro-support" 627 | version = "0.2.78" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 630 | dependencies = [ 631 | "proc-macro2", 632 | "quote", 633 | "syn", 634 | "wasm-bindgen-backend", 635 | "wasm-bindgen-shared", 636 | ] 637 | 638 | [[package]] 639 | name = "wasm-bindgen-shared" 640 | version = "0.2.78" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 643 | 644 | [[package]] 645 | name = "web-sys" 646 | version = "0.3.55" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" 649 | dependencies = [ 650 | "js-sys", 651 | "wasm-bindgen", 652 | ] 653 | 654 | [[package]] 655 | name = "widestring" 656 | version = "0.4.3" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" 659 | 660 | [[package]] 661 | name = "win_etw_macros" 662 | version = "0.1.3" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "e5272131985e3dbd7132fa3f1f00bdbe451dc4e72427187fee9bcef046691ad2" 665 | dependencies = [ 666 | "proc-macro2", 667 | "quote", 668 | "syn", 669 | "uuid", 670 | "win_etw_metadata", 671 | ] 672 | 673 | [[package]] 674 | name = "win_etw_metadata" 675 | version = "0.1.1" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "cc81c079f09df5fee4590d1c6efe869ce40f268cd7f078843ca236c52b9eda99" 678 | dependencies = [ 679 | "bitflags", 680 | ] 681 | 682 | [[package]] 683 | name = "win_etw_provider" 684 | version = "0.1.5" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "79f4e7cc3e1ec899825422d3d86e4fa516ab950267a945e19672fb41f0b3752b" 687 | dependencies = [ 688 | "w32-error", 689 | "widestring", 690 | "win_etw_metadata", 691 | "winapi", 692 | "zerocopy", 693 | ] 694 | 695 | [[package]] 696 | name = "winapi" 697 | version = "0.3.9" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 700 | dependencies = [ 701 | "winapi-i686-pc-windows-gnu", 702 | "winapi-x86_64-pc-windows-gnu", 703 | ] 704 | 705 | [[package]] 706 | name = "winapi-i686-pc-windows-gnu" 707 | version = "0.4.0" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 710 | 711 | [[package]] 712 | name = "winapi-util" 713 | version = "0.1.5" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 716 | dependencies = [ 717 | "winapi", 718 | ] 719 | 720 | [[package]] 721 | name = "winapi-x86_64-pc-windows-gnu" 722 | version = "0.4.0" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 725 | 726 | [[package]] 727 | name = "windows" 728 | version = "0.28.0" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "054d31561409bbf7e1ee4a4f0a1233ac2bb79cfadf2a398438a04d8dda69225f" 731 | dependencies = [ 732 | "windows-sys", 733 | "windows_gen", 734 | "windows_macros", 735 | "windows_reader", 736 | ] 737 | 738 | [[package]] 739 | name = "windows-sys" 740 | version = "0.28.0" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6" 743 | dependencies = [ 744 | "windows_aarch64_msvc", 745 | "windows_i686_gnu", 746 | "windows_i686_msvc", 747 | "windows_x86_64_gnu", 748 | "windows_x86_64_msvc", 749 | ] 750 | 751 | [[package]] 752 | name = "windows_aarch64_msvc" 753 | version = "0.28.0" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" 756 | 757 | [[package]] 758 | name = "windows_gen" 759 | version = "0.28.0" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "7035f7cd53a34e7f3f8eca1bc073cfc18e0c3a40e3c240c40af88694c0f1066d" 762 | dependencies = [ 763 | "windows_quote", 764 | "windows_reader", 765 | ] 766 | 767 | [[package]] 768 | name = "windows_i686_gnu" 769 | version = "0.28.0" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" 772 | 773 | [[package]] 774 | name = "windows_i686_msvc" 775 | version = "0.28.0" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" 778 | 779 | [[package]] 780 | name = "windows_macros" 781 | version = "0.28.0" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "8223a8c4fd6813d0f0d4223611f2a28507eeb009701d13a029b0241176a678b1" 784 | dependencies = [ 785 | "syn", 786 | "windows_gen", 787 | "windows_quote", 788 | "windows_reader", 789 | ] 790 | 791 | [[package]] 792 | name = "windows_quote" 793 | version = "0.28.0" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "8d7dc698fe6170adc3cb1339f317b2579698364125b5f7a6c20239fa586001ea" 796 | 797 | [[package]] 798 | name = "windows_reader" 799 | version = "0.28.0" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "00e45d55a9b5ab200ba4412eafff0b25be0d2638003b1510edd4ca71a3566d1a" 802 | 803 | [[package]] 804 | name = "windows_x86_64_gnu" 805 | version = "0.28.0" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" 808 | 809 | [[package]] 810 | name = "windows_x86_64_msvc" 811 | version = "0.28.0" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" 814 | 815 | [[package]] 816 | name = "wireguard-uwp" 817 | version = "0.1.0" 818 | dependencies = [ 819 | "windows", 820 | ] 821 | 822 | [[package]] 823 | name = "wireguard-uwp-plugin" 824 | version = "0.1.0" 825 | dependencies = [ 826 | "base64 0.13.0", 827 | "boringtun", 828 | "ipnetwork", 829 | "quick-xml", 830 | "serde", 831 | "serde_with", 832 | "win_etw_macros", 833 | "win_etw_provider", 834 | "windows", 835 | ] 836 | 837 | [[package]] 838 | name = "zerocopy" 839 | version = "0.3.0" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46" 842 | dependencies = [ 843 | "byteorder", 844 | "zerocopy-derive", 845 | ] 846 | 847 | [[package]] 848 | name = "zerocopy-derive" 849 | version = "0.2.0" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb" 852 | dependencies = [ 853 | "proc-macro2", 854 | "syn", 855 | "synstructure", 856 | ] 857 | --------------------------------------------------------------------------------