From 3df04a7d1eabad14ef359fc0cfba5be5919ec1ed Mon Sep 17 00:00:00 2001 From: Artur Kantorczyk Date: Mon, 2 Oct 2023 12:21:20 +0200 Subject: [PATCH] chore: Update wireguard-rs (#21) * Use updated wireguard api --- src-tauri/Cargo.lock | 30 +++++---- src-tauri/Cargo.toml | 1 + src-tauri/defguard.db | Bin 36864 -> 36864 bytes src-tauri/src/commands.rs | 33 +++++++--- src-tauri/src/error.rs | 6 +- src-tauri/src/main.rs | 3 +- src-tauri/src/utils.rs | 127 +++++++++++++++++++++++++++++--------- src-tauri/wireguard-rs | 2 +- 8 files changed, 150 insertions(+), 52 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 5353100d..7ad58bcd 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -268,12 +268,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "base64" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" - [[package]] name = "base64" version = "0.21.4" @@ -789,6 +783,7 @@ dependencies = [ "log", "prost", "prost-build", + "rand 0.8.5", "serde", "serde_json", "sqlx", @@ -2420,7 +2415,18 @@ dependencies = [ "cfg-if", "libc", "memoffset 0.7.1", - "pin-utils", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "libc", + "memoffset 0.9.0", ] [[package]] @@ -5251,16 +5257,16 @@ dependencies = [ name = "wireguard_rs" version = "0.1.0" dependencies = [ - "base64 0.20.0", + "base64 0.21.4", "log", "netlink-packet-core", "netlink-packet-generic", "netlink-packet-route", + "netlink-packet-utils", "netlink-packet-wireguard", "netlink-sys", - "nix", + "nix 0.27.1", "thiserror", - "tokio", ] [[package]] @@ -5349,7 +5355,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" dependencies = [ - "nix", + "nix 0.26.4", "winapi", ] @@ -5377,7 +5383,7 @@ dependencies = [ "futures-sink", "futures-util", "hex", - "nix", + "nix 0.26.4", "once_cell", "ordered-stream", "rand 0.8.5", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index efc93ab2..e40dabb8 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -37,6 +37,7 @@ tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", br local-ip-address = "0.5.5" tokio = { version = "1.32", features = ["full"] } log = "0.4.20" +rand = "0.8.5" [features] # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. diff --git a/src-tauri/defguard.db b/src-tauri/defguard.db index f4ecdf0ba72c34b10e64e3949249619ff33ab0c9..752057a4c158a50c09cc2dbc73f72e32e32f00ca 100644 GIT binary patch delta 196 zcmZozz|^pSX+oCJ3I@Jz{_VW4cxUiPb3Ngl&#_{&pnwj?=5&rrjO?C%A+GMOL6aT1 zq&P!7eO-e?9DM^O%W+CguH{naG+|>GS5#zdF`m4Z>y$EFUct{lM8PlA$47@tK_N9S zMFB1Y)45rOXPK~_k-36_g_Ws=m5Irl_+Pd+V$R;QjeS-8=0)OqH~C*IhHHD|j_xxT hW3n#Ne_wj~+^-Wm*4NI}I=_C~)|#s*dfCg%*-$X!srC211yFh-P3LNd_y#z9#j_4RkE5)Vda{4>_> aNQ*xI_W%0+jP#$jH3ppqPx%%p6aWB(%v_29 diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 63063796..f12dd166 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use tauri::{Manager, State}; use tokio; use tokio::time::{sleep, Duration}; -use wireguard_rs::{netlink::delete_interface, wgapi::WGApi}; +use wireguard_rs::{netlink::delete_interface, wgapi::WGApi, WireguardInterfaceApi}; #[derive(Clone, serde::Serialize)] struct Payload { @@ -37,7 +37,10 @@ pub async fn connect(location_id: i64, handle: tauri::AppHandle) -> Result<(), S let address = local_ip().map_err(|err| err.to_string())?; let connection = Connection::new(location_id, address.to_string()); state.active_connections.lock().unwrap().push(connection); - println!("{:#?}", state.active_connections.lock().unwrap()); + debug!( + "Active connections: {:#?}", + state.active_connections.lock().unwrap() + ); handle .emit_all( "connection-changed", @@ -47,11 +50,12 @@ pub async fn connect(location_id: i64, handle: tauri::AppHandle) -> Result<(), S ) .unwrap(); // Spawn stats threads - let api = WGApi::new(remove_whitespace(&location.name), false); + let api = + WGApi::new(remove_whitespace(&location.name), false).map_err(|e| e.to_string())?; tokio::spawn(async move { let state = handle.state::(); loop { - match api.read_host() { + match api.read_interface_data() { Ok(host) => { let peers = host.peers; for (_, peer) in peers { @@ -60,11 +64,21 @@ pub async fn connect(location_id: i64, handle: tauri::AppHandle) -> Result<(), S .await .map_err(|err| err.to_string()) .unwrap(); + debug!("Saving location stats: {:#?}", location_stats); let _ = location_stats.save(&state.get_pool()).await; + debug!("Saved location stats: {:#?}", location_stats); } } - Err(_) => { - debug!("Stopped stats thread for location: {}", location.name); + Err(e) => { + error!( + "Error {} while reading data for interface: {}", + e, location.name + ); + debug!( + "Stopped stats thread for location: {}. Error: {}", + location.name, + e.to_string() + ); break; } } @@ -181,7 +195,7 @@ pub async fn save_device_config( .map_err(|err| err.to_string())?; } transaction.commit().await.map_err(|err| err.to_string())?; - info!("Instance created"); + info!("Instance created."); Ok(()) } @@ -272,6 +286,7 @@ pub async fn update_instance( response: CreateDeviceResponse, app_state: State<'_, AppState>, ) -> Result<(), String> { + debug!("Received following response: {:#?}", response); let instance = Instance::find_by_id(&app_state.get_pool(), instance_id) .await .map_err(|err| err.to_string())?; @@ -312,6 +327,7 @@ pub async fn update_instance( } } transaction.commit().await.map_err(|err| err.to_string())?; + info!("Updated instance."); Ok(()) } else { Err("Instance not found".into()) @@ -346,9 +362,10 @@ pub async fn last_connection( .await .map_err(|err| err.to_string())? { - println!("Returning connection: {:#?}", connection); + debug!("Returning last connection: {:#?}", connection); Ok(connection) } else { + error!("No connections for location: {}", location_id); Err("No connections for this device".into()) } } diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs index 453ac784..2110b166 100644 --- a/src-tauri/src/error.rs +++ b/src-tauri/src/error.rs @@ -4,7 +4,7 @@ use base64; use local_ip_address::Error as LocalIpError; use sqlx; use thiserror::Error; -use wireguard_rs::{error::WireguardError, IpAddrParseError}; +use wireguard_rs::{error::WireguardInterfaceError, IpAddrParseError}; #[derive(Debug, Error)] pub enum Error { @@ -17,7 +17,7 @@ pub enum Error { #[error("Migrate error: {0}")] Migration(#[from] sqlx::migrate::MigrateError), #[error("Wireguard error")] - WireguardError(#[from] WireguardError), + WireguardError(#[from] WireguardInterfaceError), #[error("WireGuard key error")] KeyDecode(#[from] base64::DecodeError), #[error("IP address/mask error")] @@ -26,4 +26,6 @@ pub enum Error { AddrParse(#[from] AddrParseError), #[error("Local Ip Error")] LocalIpError(#[from] LocalIpError), + #[error("Internal error")] + InternalError, } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a7cee2f4..880dad99 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -14,7 +14,8 @@ use tauri_plugin_log::LogTarget; use tauri::SystemTrayEvent; mod tray; use crate::commands::{ - all_instances, all_locations, connect, disconnect, location_stats, save_device_config, update_instance, last_connection, + all_instances, all_locations, connect, disconnect, last_connection, location_stats, + save_device_config, update_instance, }; use crate::tray::create_tray_menu; diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 65fa46f5..4ae9e2a6 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -1,9 +1,10 @@ -use std::{net::SocketAddr, str::FromStr}; +use std::{ + net::{SocketAddr, TcpListener}, + str::FromStr, +}; use wireguard_rs::{ - netlink::{address_interface, create_interface}, - wgapi::WGApi, - IpAddrMask, Key, Peer, + wgapi::WGApi, InterfaceConfiguration, IpAddrMask, Key, Peer, WireguardInterfaceApi, }; use crate::{ @@ -11,26 +12,19 @@ use crate::{ error::Error, }; -// TODO: Learn how to run tauri app with sudo permissions to setup interface /// Setup client interface pub async fn setup_interface(location: &Location, pool: &DbPool) -> Result<(), Error> { let interface_name = remove_whitespace(&location.name); - debug!("Creating interface: {}", interface_name); - create_interface(&interface_name)?; - info!("Created interface: {}", interface_name); - address_interface(&interface_name, &IpAddrMask::from_str(&location.address)?)?; - info!("Adressed interface: {}", interface_name); + debug!("Creating new interface: {}", interface_name); + let api = create_api(&interface_name)?; - let api = WGApi::new(interface_name.clone(), false); + api.create_interface()?; - let mut host = api.read_host()?; if let Some(keys) = WireguardKeys::find_by_instance_id(pool, location.instance_id).await? { // TODO: handle unwrap - let private_key: Key = Key::from_str(&keys.prvkey).unwrap(); - host.private_key = Some(private_key); let peer_key: Key = Key::from_str(&location.pubkey).unwrap(); let mut peer = Peer::new(peer_key); - println!("{}", location.endpoint); + debug!("Creating interface for location: {:#?}", location); let endpoint: SocketAddr = location.endpoint.parse().unwrap(); peer.endpoint = Some(endpoint); peer.persistent_keepalive_interval = Some(25); @@ -44,14 +38,12 @@ pub async fn setup_interface(location: &Location, pool: &DbPool) -> Result<(), E match IpAddrMask::from_str(&allowed_ip) { Ok(addr) => { peer.allowed_ips.push(addr); - // TODO: Handle other OS than linux + // TODO: Handle windows later // Add a route for the allowed IP using the `ip -4 route add` command - if let Err(err) = std::process::Command::new("ip") - .args(["-4", "route", "add", &allowed_ip, "dev", &interface_name]) - .output() - { - // Handle the error if the ip command fails - eprintln!("Error adding route for {}: {}", allowed_ip, err); + if let Err(err) = add_route(&allowed_ip, &interface_name) { + error!("Error adding route for {}: {}", allowed_ip, err); + } else { + info!("Added route for {}", allowed_ip); } } Err(err) => { @@ -62,13 +54,92 @@ pub async fn setup_interface(location: &Location, pool: &DbPool) -> Result<(), E } } } - api.write_host(&host)?; - api.write_peer(&peer)?; - info!("created peer {:#?}", peer); - }; - - Ok(()) + if let Some(port) = find_random_free_port() { + let interface_config = InterfaceConfiguration { + name: interface_name.clone(), + prvkey: keys.prvkey, + address: location.address.clone(), + port: port.into(), + peers: vec![peer.clone()], + }; + debug!("Creating interface {:#?}", interface_config); + if let Err(err) = api.configure_interface(&interface_config) { + error!("Failed to configure interface: {}", err.to_string()); + Err(Error::InternalError) + } else { + if let Err(err) = api.configure_peer(&peer) { + error!("Failed to configure peer: {}", err.to_string()); + return Err(Error::InternalError); + } + info!("created interface {:#?}", interface_config); + Ok(()) + } + } else { + error!("Error finding free port"); + Err(Error::InternalError) + } + } else { + error!("No keys found for instance: {}", location.instance_id); + error!("Removing interface: {}", location.name); + api.remove_interface()?; + Err(Error::InternalError) + } } + +/// Helper function to remove whitespace from location name pub fn remove_whitespace(s: &str) -> String { s.chars().filter(|c| !c.is_whitespace()).collect() } + +#[cfg(target_os = "linux")] +fn add_route(allowed_ip: &str, interface_name: &str) -> Result<(), std::io::Error> { + std::process::Command::new("ip") + .args(["-4", "route", "add", allowed_ip, "dev", interface_name]) + .output()?; + Ok(()) +} + +#[cfg(target_os = "macos")] +fn add_route(allowed_ip: &str, interface_name: &str) -> Result<(), std::io::Error> { + std::process::Command::new("route") + .args([ + "-n", + "add", + "-net", + allowed_ip, + "-interface", + interface_name, + ]) + .output()?; + Ok(()) +} + +fn find_random_free_port() -> Option { + const MAX_PORT: u16 = 65535; + const MIN_PORT: u16 = 6000; + + // Create a TcpListener to check for port availability + for _ in 0..(MAX_PORT - MIN_PORT + 1) { + let port = rand::random::() % (MAX_PORT - MIN_PORT) + MIN_PORT; + if is_port_free(port) { + return Some(port); + } + } + + None // No free port found in the specified range +} + +fn is_port_free(port: u16) -> bool { + if let Ok(listener) = TcpListener::bind(format!("127.0.0.1:{}", port)) { + // Port is available; close the listener + drop(listener); + true + } else { + false + } +} + +pub fn create_api(interface_name: &str) -> Result { + let is_macos = cfg!(target_os = "macos"); + Ok(WGApi::new(interface_name.to_string(), is_macos)?) +} diff --git a/src-tauri/wireguard-rs b/src-tauri/wireguard-rs index a281dca8..1f5e3f49 160000 --- a/src-tauri/wireguard-rs +++ b/src-tauri/wireguard-rs @@ -1 +1 @@ -Subproject commit a281dca8b2cdbf90aeaa3c73f6a08ab58b9be2a0 +Subproject commit 1f5e3f49eb369d5b0fcd95daef7ade2ec69af2ab