Skip to content

Commit

Permalink
chore: Update wireguard-rs (#21)
Browse files Browse the repository at this point in the history
* Use updated wireguard api
  • Loading branch information
dzania authored Oct 2, 2023
1 parent ae28388 commit 3df04a7
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 52 deletions.
30 changes: 18 additions & 12 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Binary file modified src-tauri/defguard.db
Binary file not shown.
33 changes: 25 additions & 8 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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",
Expand All @@ -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::<AppState>();
loop {
match api.read_host() {
match api.read_interface_data() {
Ok(host) => {
let peers = host.peers;
for (_, peer) in peers {
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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(())
}

Expand Down Expand Up @@ -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())?;
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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())
}
}
6 changes: 4 additions & 2 deletions src-tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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")]
Expand All @@ -26,4 +26,6 @@ pub enum Error {
AddrParse(#[from] AddrParseError),
#[error("Local Ip Error")]
LocalIpError(#[from] LocalIpError),
#[error("Internal error")]
InternalError,
}
3 changes: 2 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
127 changes: 99 additions & 28 deletions src-tauri/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,30 @@
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::{
database::{DbPool, Location, WireguardKeys},
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);
Expand All @@ -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) => {
Expand All @@ -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<u16> {
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::<u16>() % (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<WGApi, Error> {
let is_macos = cfg!(target_os = "macos");
Ok(WGApi::new(interface_name.to_string(), is_macos)?)
}

0 comments on commit 3df04a7

Please sign in to comment.