Skip to content

Commit

Permalink
Fix interface name; switch to ServiceManager on Windows (#375)
Browse files Browse the repository at this point in the history
  • Loading branch information
moubctez authored Jan 15, 2025
1 parent 0f30e9a commit 660e6be
Show file tree
Hide file tree
Showing 8 changed files with 536 additions and 599 deletions.
612 changes: 328 additions & 284 deletions src-tauri/Cargo.lock

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ base64 = "0.22"
clap = { version = "4.5", features = ["cargo", "derive", "env"] }
chrono = { version = "0.4", features = ["serde"] }
common = { path = "common" }
dark-light = "1.1"
dark-light = "2.0"
defguard_wireguard_rs = { workspace = true }
dirs-next = "2.0"
lazy_static = "1.5"
Expand Down Expand Up @@ -87,12 +87,11 @@ x25519-dalek = { version = "2", features = [
"static_secrets",
] }

[target.'cfg(target_os = "windows")'.dependencies]
windows-service = "0.7"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winsvc", "winerror"] }
widestring = "1.1"
windows-service = "0.7"

[target.'cfg(target_os = "macos")'.dependencies]
[target.'cfg(unix)'.dependencies]
nix = { version = "0.29", features = ["net"] }

[features]
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ rust-version = "1.80"

[dependencies]

[target.'cfg(target_os = "macos")'.dependencies]
[target.'cfg(unix)'.dependencies]
nix = { version = "0.29", features = ["net"] }
15 changes: 10 additions & 5 deletions src-tauri/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ pub fn find_free_tcp_port() -> Option<u16> {
.map(|local_addr| local_addr.port())
}

#[cfg(target_os = "macos")]
/// Find next available `utun` interface.
#[cfg(not(windows))]
/// Find next available interface. On macOS, search for available `utun` interface.
/// On other UNIX, search for available `wg` interface.
#[must_use]
pub fn get_interface_name(_name: &str) -> String {
#[cfg(target_os = "macos")]
let base_ifname = "utun";
#[cfg(not(target_os = "macos"))]
let base_ifname = "wg";
if let Ok(interfaces) = nix::net::if_::if_nameindex() {
for index in 0..=u16::MAX {
let ifname = format!("utun{index}");
let ifname = format!("{base_ifname}{index}");
if !interfaces
.iter()
.any(|interface| interface.name().to_string_lossy() == ifname)
Expand All @@ -27,11 +32,11 @@ pub fn get_interface_name(_name: &str) -> String {
}
}

"utun0".into()
format!("{base_ifname}0")
}

/// Strips location name of all non-alphanumeric characters returning usable interface name.
#[cfg(not(target_os = "macos"))]
#[cfg(windows)]
#[must_use]
pub fn get_interface_name(name: &str) -> String {
name.chars().filter(|c| c.is_alphanumeric()).collect()
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ use crate::{
};

#[derive(Clone, Serialize)]
pub struct Payload {
pub(crate) struct Payload {
pub message: String,
}

Expand Down
12 changes: 9 additions & 3 deletions src-tauri/src/log_watcher/service_log_watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,10 @@ pub async fn spawn_log_watcher_task(
"Location"
};
let event_topic = format!("log-update-{connection_type_str}-{location_id}");
debug!("Using the following event topic for the service log watcher for communicating with the frontend: {event_topic}");
debug!(
"Using the following event topic for the service log watcher for communicating with the \
frontend: {event_topic}"
);

// explicitly clone before topic is moved into the closure
let topic_clone = event_topic.clone();
Expand Down Expand Up @@ -273,8 +276,11 @@ pub async fn spawn_log_watcher_task(
}

let name = get_tunnel_or_location_name(location_id, connection_type, &app_state).await;
info!("A background task has been spawned to watch the defguard service log file for {connection_type} {name} (interface {interface_name}), \
location's specific collected logs will be displayed in the {connection_type}'s detailed view.");
info!(
"A background task has been spawned to watch the defguard service log file for \
{connection_type} {name} (interface {interface_name}), location's specific collected logs \
will be displayed in the {connection_type}'s detailed view."
);
Ok(event_topic)
}

Expand Down
123 changes: 74 additions & 49 deletions src-tauri/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ impl DesktopDaemonService for DaemonService {
debug!("Done creating a new interface {ifname}");
}

// The WireGuard DNS config value can be a list of IP addresses and domain names, which will be
// used as DNS servers and search domains respectively.
// The WireGuard DNS config value can be a list of IP addresses and domain names, which will
// be used as DNS servers and search domains respectively.
debug!("Preparing DNS configuration for interface {ifname}");
let dns_string = request.dns.unwrap_or_default();
let dns_entries = dns_string.split(',').map(str::trim).collect::<Vec<&str>>();
Expand All @@ -137,7 +137,10 @@ impl DesktopDaemonService for DaemonService {
search_domains.push(entry);
}
}
debug!("DNS configuration for interface {ifname}: DNS: {dns:?}, Search domains: {search_domains:?}");
debug!(
"DNS configuration for interface {ifname}: DNS: {dns:?}, Search domains: \
{search_domains:?}"
);

#[cfg(not(windows))]
let configure_interface_result = wgapi.configure_interface(&config);
Expand All @@ -161,9 +164,15 @@ impl DesktopDaemonService for DaemonService {
})?;

if dns.is_empty() {
debug!("No DNS configuration provided for interface {ifname}, skipping DNS configuration");
debug!(
"No DNS configuration provided for interface {ifname}, skipping DNS \
configuration"
);
} else {
debug!("The following DNS servers will be set: {dns:?}, search domains: {search_domains:?}");
debug!(
"The following DNS servers will be set: {dns:?}, search domains: \
{search_domains:?}"
);
wgapi.configure_dns(&dns, &search_domains).map_err(|err| {
let msg =
format!("Failed to configure DNS for WireGuard interface {ifname}: {err}");
Expand Down Expand Up @@ -221,7 +230,8 @@ impl DesktopDaemonService for DaemonService {
let request = request.into_inner();
let ifname = request.interface_name;
debug!(
"Received a request to start a new network usage stats data stream for interface {ifname}"
"Received a request to start a new network usage stats data stream for interface \
{ifname}"
);
let span = info_span!("read_interface_data", interface_name = &ifname);

Expand All @@ -234,58 +244,73 @@ impl DesktopDaemonService for DaemonService {
info!("Spawning statistics collector task for interface {ifname}");
});

tokio::spawn(async move {
// Helper map to track if peer data is actually changing to avoid sending duplicate stats.
let mut peer_map = HashMap::new();

loop {
// Loop delay
interval.tick().await;
debug!("Gathering network usage statistics for client's network activity on {ifname}");
match wgapi.read_interface_data() {
Ok(mut host) => {
let peers = &mut host.peers;
debug!(
"Found {} peers configured on WireGuard interface",
peers.len()
);
// Filter out never connected peers.
peers.retain(|_, peer| {
// Last handshake time-stamp must exist...
if let Some(last_hs) = peer.last_handshake {
// ...and not be UNIX epoch.
if last_hs != SystemTime::UNIX_EPOCH &&
match peer_map.get(&peer.public_key) {
Some(last_peer) => last_peer != peer,
None => true,
} {
debug!("Peer {} statistics changed; keeping it.", peer.public_key);
peer_map.insert(peer.public_key.clone(), peer.clone());
return true;
tokio::spawn(
async move {
// Helper map to track if peer data is actually changing to avoid sending duplicate
// stats.
let mut peer_map = HashMap::new();

loop {
// Loop delay
interval.tick().await;
debug!(
"Gathering network usage statistics for client's network activity on {ifname}");
match wgapi.read_interface_data() {
Ok(mut host) => {
let peers = &mut host.peers;
debug!(
"Found {} peers configured on WireGuard interface",
peers.len()
);
// Filter out never connected peers.
peers.retain(|_, peer| {
// Last handshake time-stamp must exist...
if let Some(last_hs) = peer.last_handshake {
// ...and not be UNIX epoch.
if last_hs != SystemTime::UNIX_EPOCH
&& match peer_map.get(&peer.public_key) {
Some(last_peer) => last_peer != peer,
None => true,
}
{
debug!(
"Peer {} statistics changed; keeping it.",
peer.public_key
);
peer_map.insert(peer.public_key.clone(), peer.clone());
return true;
}
}
debug!(
"Peer {} statistics didn't change; ignoring it.",
peer.public_key
);
false
});
if let Err(err) = tx.send(Ok(host.into())).await {
error!(
"Couldn't send network usage stats update for {ifname}: {err}"
);
break;
}
debug!("Peer {} statistics didn't change; ignoring it.", peer.public_key);
false
});
if let Err(err) = tx.send(Ok(host.into())).await {
}
Err(err) => {
error!(
"Couldn't send network usage stats update for {ifname}: {err}"
"Failed to retrieve network usage stats for interface {ifname}: \
{err}"
);
break;
}
}
Err(err) => {
error!("Failed to retrieve network usage stats for interface {ifname}: {err}");
break;
}
debug!("Network activity statistics for interface {ifname} sent to the client");
}
debug!("Network activity statistics for interface {ifname} sent to the client");
}
debug!(
"The client has disconnected from the network usage statistics data stream \
debug!(
"The client has disconnected from the network usage statistics data stream \
for interface {ifname}, stopping the statistics data collection task."
);
}.instrument(span));
);
}
.instrument(span),
);

let output_stream = ReceiverStream::new(rx);
Ok(Response::new(
Expand Down
Loading

0 comments on commit 660e6be

Please sign in to comment.