diff --git a/src/client/mod.rs b/src/client/mod.rs index e3536b7..90ec7fb 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -334,6 +334,7 @@ async fn spawn_ssh( cmd.arg("sudo"); } cmd.arg(format!("FWD_LOG={log_filter}")) + .arg("FWD_SEND_ANONYMOUS=1") .arg("fwd") .arg("--server"); diff --git a/src/client/ui.rs b/src/client/ui.rs index 4e1b42a..4ba2b04 100644 --- a/src/client/ui.rs +++ b/src/client/ui.rs @@ -1178,4 +1178,30 @@ mod tests { drop(sender); } + + #[test] + fn empty_port_desc_disabled_on_refresh() { + let (sender, receiver) = mpsc::channel(64); + let config = ServerConfig::default(); + let mut ui = UI::new(receiver, config); + + ui.handle_internal_event(Some(UIEvent::Ports(vec![PortDesc { + port: 8080, + desc: "".to_string(), + }]))); + + let listener = ui.ports.get(&8080).unwrap(); + assert_eq!(listener.state(), State::Disabled); + + // Just do it again, make sure we haven't broken the refresh path. + ui.handle_internal_event(Some(UIEvent::Ports(vec![PortDesc { + port: 8080, + desc: "".to_string(), + }]))); + + let listener = ui.ports.get(&8080).unwrap(); + assert_eq!(listener.state(), State::Disabled); + + drop(sender); + } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 42b8799..101cbc4 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -24,6 +24,15 @@ async fn server_loop( reader: &mut MessageReader, writer: &mut mpsc::Sender, ) -> Result<()> { + // NOTE: The client needs to opt in to getting anonymous ports because it + // does not feel safe to automatically enable port forwarding by default + // for random system ports. The way we keep it from being unsafe is that + // the client leaves anonymous ports disabled by default. Older clients + // did not do this, and so we cannot send older clients anonymous ports. + let send_anonymous = std::env::var("FWD_SEND_ANONYMOUS") + .map(|v| v == "1") + .unwrap_or(false); + // The first message we send must be an announcement. writer.send(Message::Hello(0, 2, vec![])).await?; let mut version_reported = false; @@ -46,7 +55,7 @@ async fn server_loop( version_reported = true; } - let ports = match refresh::get_entries().await { + let ports = match refresh::get_entries(send_anonymous).await { Ok(ports) => ports, Err(e) => { error!("Error scanning: {:?}", e); diff --git a/src/server/refresh.rs b/src/server/refresh.rs index fcb47e3..fb1a6b7 100644 --- a/src/server/refresh.rs +++ b/src/server/refresh.rs @@ -12,7 +12,7 @@ mod procfs; #[cfg(unix)] mod docker; -pub async fn get_entries() -> Result> { +pub async fn get_entries(_send_anonymous: bool) -> Result> { #[cfg_attr(not(target_os = "linux"), allow(unused_mut))] let mut attempts = 0; @@ -35,7 +35,7 @@ pub async fn get_entries() -> Result> { #[cfg(target_os = "linux")] { attempts += 1; - match procfs::get_entries() { + match procfs::get_entries(_send_anonymous) { Ok(m) => { for (p, d) in m { result.entry(p).or_insert(d); diff --git a/src/server/refresh/procfs.rs b/src/server/refresh/procfs.rs index aa1a79f..9749dde 100644 --- a/src/server/refresh/procfs.rs +++ b/src/server/refresh/procfs.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::message::PortDesc; -pub fn get_entries() -> Result> { +pub fn get_entries(send_anonymous: bool) -> Result> { let all_procs = procfs::process::all_processes()?; // build up a map between socket inodes and process stat info. Ignore any @@ -38,12 +38,21 @@ pub fn get_entries() -> Result> { || tcp_entry.local_address.ip().is_unspecified()) && !h.contains_key(&tcp_entry.local_address.port()) { - if let Some(cmd) = map.get(&tcp_entry.inode) { + // If the process is not one that we can identify, then we return + // the port but leave the description empty so that it can be + // identified by the client as "anonymous". + let desc = if let Some(cmd) = map.get(&tcp_entry.inode) { + cmd.clone() + } else { + String::new() + }; + + if send_anonymous || !desc.is_empty() { h.insert( tcp_entry.local_address.port(), PortDesc { port: tcp_entry.local_address.port(), - desc: cmd.clone(), + desc, }, ); }