Anonymous ports
This is the other way to allow ports to work when the processes themselves cannot be enumerated: just report the port with an empty description. We need to do some work to make sure this is safe for the client; see comments.
This commit is contained in:
parent
69b9bc9824
commit
a4745c92e2
5 changed files with 51 additions and 6 deletions
|
|
@ -334,6 +334,7 @@ async fn spawn_ssh(
|
||||||
cmd.arg("sudo");
|
cmd.arg("sudo");
|
||||||
}
|
}
|
||||||
cmd.arg(format!("FWD_LOG={log_filter}"))
|
cmd.arg(format!("FWD_LOG={log_filter}"))
|
||||||
|
.arg("FWD_SEND_ANONYMOUS=1")
|
||||||
.arg("fwd")
|
.arg("fwd")
|
||||||
.arg("--server");
|
.arg("--server");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1178,4 +1178,30 @@ mod tests {
|
||||||
|
|
||||||
drop(sender);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,15 @@ async fn server_loop<Reader: AsyncRead + Unpin>(
|
||||||
reader: &mut MessageReader<Reader>,
|
reader: &mut MessageReader<Reader>,
|
||||||
writer: &mut mpsc::Sender<Message>,
|
writer: &mut mpsc::Sender<Message>,
|
||||||
) -> Result<()> {
|
) -> 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.
|
// The first message we send must be an announcement.
|
||||||
writer.send(Message::Hello(0, 2, vec![])).await?;
|
writer.send(Message::Hello(0, 2, vec![])).await?;
|
||||||
let mut version_reported = false;
|
let mut version_reported = false;
|
||||||
|
|
@ -46,7 +55,7 @@ async fn server_loop<Reader: AsyncRead + Unpin>(
|
||||||
version_reported = true;
|
version_reported = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ports = match refresh::get_entries().await {
|
let ports = match refresh::get_entries(send_anonymous).await {
|
||||||
Ok(ports) => ports,
|
Ok(ports) => ports,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error scanning: {:?}", e);
|
error!("Error scanning: {:?}", e);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ mod procfs;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
mod docker;
|
mod docker;
|
||||||
|
|
||||||
pub async fn get_entries() -> Result<Vec<PortDesc>> {
|
pub async fn get_entries(_send_anonymous: bool) -> Result<Vec<PortDesc>> {
|
||||||
#[cfg_attr(not(target_os = "linux"), allow(unused_mut))]
|
#[cfg_attr(not(target_os = "linux"), allow(unused_mut))]
|
||||||
let mut attempts = 0;
|
let mut attempts = 0;
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ pub async fn get_entries() -> Result<Vec<PortDesc>> {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
attempts += 1;
|
attempts += 1;
|
||||||
match procfs::get_entries() {
|
match procfs::get_entries(_send_anonymous) {
|
||||||
Ok(m) => {
|
Ok(m) => {
|
||||||
for (p, d) in m {
|
for (p, d) in m {
|
||||||
result.entry(p).or_insert(d);
|
result.entry(p).or_insert(d);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::message::PortDesc;
|
use crate::message::PortDesc;
|
||||||
|
|
||||||
pub fn get_entries() -> Result<HashMap<u16, PortDesc>> {
|
pub fn get_entries(send_anonymous: bool) -> Result<HashMap<u16, PortDesc>> {
|
||||||
let all_procs = procfs::process::all_processes()?;
|
let all_procs = procfs::process::all_processes()?;
|
||||||
|
|
||||||
// build up a map between socket inodes and process stat info. Ignore any
|
// build up a map between socket inodes and process stat info. Ignore any
|
||||||
|
|
@ -38,12 +38,21 @@ pub fn get_entries() -> Result<HashMap<u16, PortDesc>> {
|
||||||
|| tcp_entry.local_address.ip().is_unspecified())
|
|| tcp_entry.local_address.ip().is_unspecified())
|
||||||
&& !h.contains_key(&tcp_entry.local_address.port())
|
&& !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(
|
h.insert(
|
||||||
tcp_entry.local_address.port(),
|
tcp_entry.local_address.port(),
|
||||||
PortDesc {
|
PortDesc {
|
||||||
port: tcp_entry.local_address.port(),
|
port: tcp_entry.local_address.port(),
|
||||||
desc: cmd.clone(),
|
desc,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue