From 18ea6c214ae26ba6167bef315b003ce44434813f Mon Sep 17 00:00:00 2001 From: James McMurray Date: Sun, 26 Nov 2023 12:24:10 +0100 Subject: [PATCH] Add create-netns-only argument for debugging + firejail usage --- USERGUIDE.md | 16 ++++++ src/args.rs | 4 ++ src/exec.rs | 73 +++++++++++++++++--------- vopono_core/src/network/shadowsocks.rs | 9 ++-- vopono_core/src/util/mod.rs | 3 +- 5 files changed, 72 insertions(+), 33 deletions(-) diff --git a/USERGUIDE.md b/USERGUIDE.md index 418309f..4b62b73 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -21,6 +21,22 @@ entire shell sessions inside vopono. Note that the order of command-line arguments matters, as the `--dns` argument can take a list of DNS servers for example. +### Creating only Network Namespace + +You can run vopono to create only the network namespace using the +`--create-netns-only` argument, the application related arguments are +then ignored (pass anything as the application name). This can be useful +for debugging connection issues. + +This can also be used to launch an application without sudo via firejail +- e.g. (where `none` is passed as the dummy application): + +```bash +$ vopono -v exec --provider protonvpn --server japan --protocol openvpn --create-netns-only none +2023-11-26T11:17:52.623Z INFO vopono::exec > Created netns vo_pr_japan - will leave network namespace alive until ctrl+C received +$ firejail --netns=vo_pr_japan firefox-developer-edition +``` + ### Configuration file You can save default configuration options in the config file diff --git a/src/args.rs b/src/args.rs index 23668ac..d607b46 100644 --- a/src/args.rs +++ b/src/args.rs @@ -216,6 +216,10 @@ pub struct ExecCommand { /// Enable port forwarding for ProtonVPN connections #[clap(long = "protonvpn-port-forwarding")] pub protonvpn_port_forwarding: bool, + + /// Only create network namespace (does not run application) + #[clap(long = "create-netns-only")] + pub create_netns_only: bool, } #[derive(Parser)] diff --git a/src/exec.rs b/src/exec.rs index f7d0871..a037e1e 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -150,6 +150,17 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()> command.protonvpn_port_forwarding }; + // Create netns only + let create_netns_only = if !command.create_netns_only { + vopono_config_settings + .get("create-netns-only") + .map_err(|_e| anyhow!("Failed to read config file")) + .ok() + .unwrap_or(false) + } else { + command.create_netns_only + }; + // Assign DNS server from args or vopono config file let base_dns = command.dns.clone().or_else(|| { vopono_config_settings @@ -551,15 +562,6 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()> vopono_core::util::open_ports(&ns, &[pmpc.local_port], firewall)?; } - let application = ApplicationWrapper::new( - &ns, - &command.application, - user, - group, - working_directory.map(PathBuf::from), - natpmpc, - )?; - // Launch TCP proxy server on other threads if forwarding ports // TODO: Fix when running as root let mut proxy = Vec::new(); @@ -580,27 +582,46 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()> } } - let pid = application.handle.id(); - info!( - "Application {} launched in network namespace {} with pid {}", - &command.application, &ns.name, pid - ); + if !create_netns_only { + let application = ApplicationWrapper::new( + &ns, + &command.application, + user, + group, + working_directory.map(PathBuf::from), + natpmpc, + )?; - if let Some(pmpc) = application.protonvpn_port_forwarding.as_ref() { - info!("ProtonVPN Port Forwarding on port {}", pmpc.local_port) - } - let output = application.wait_with_output()?; - io::stdout().write_all(output.stdout.as_slice())?; + let pid = application.handle.id(); + info!( + "Application {} launched in network namespace {} with pid {}", + &command.application, &ns.name, pid + ); - // Allow daemons to leave namespace open - if vopono_core::util::check_process_running(pid) { + if let Some(pmpc) = application.protonvpn_port_forwarding.as_ref() { + info!("ProtonVPN Port Forwarding on port {}", pmpc.local_port) + } + let output = application.wait_with_output()?; + io::stdout().write_all(output.stdout.as_slice())?; + + // Allow daemons to leave namespace open + if vopono_core::util::check_process_running(pid) { + info!( + "Process {} still running, assumed to be daemon - will leave network namespace {} alive until ctrl+C received", + pid, &ns.name + ); + stay_alive(Some(pid), signals); + } else if command.keep_alive { + info!( + "Keep-alive flag active - will leave network namespace {} alive until ctrl+C received", &ns.name + ); + stay_alive(None, signals); + } + } else { info!( - "Process {} still running, assumed to be daemon - will leave network namespace alive until ctrl+C received", - pid + "Created netns {} - will leave network namespace alive until ctrl+C received", + &ns.name ); - stay_alive(Some(pid), signals); - } else if command.keep_alive { - info!("Keep-alive flag active - will leave network namespace alive until ctrl+C received"); stay_alive(None, signals); } diff --git a/vopono_core/src/network/shadowsocks.rs b/vopono_core/src/network/shadowsocks.rs index 9b2c496..30da5bb 100644 --- a/vopono_core/src/network/shadowsocks.rs +++ b/vopono_core/src/network/shadowsocks.rs @@ -16,7 +16,6 @@ use log::{debug, error}; use rand::seq::SliceRandom; use regex::Regex; use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; use std::fs::read_to_string; use std::net::{IpAddr, Ipv4Addr}; use std::path::Path; @@ -95,9 +94,9 @@ pub fn uses_shadowsocks(openvpn_config: &Path) -> anyhow::Result()?, ))) } @@ -113,9 +112,9 @@ pub fn get_routes_from_config(path: &Path) -> anyhow::Result> { let caps = re.captures_iter(&file_string); for cap in caps { - output_vec.push(IpAddr::try_from(Ipv4Addr::from_str( + output_vec.push(IpAddr::from(Ipv4Addr::from_str( cap.get(1).unwrap().as_str(), - )?)?); + )?)); } if output_vec.is_empty() { diff --git a/vopono_core/src/util/mod.rs b/vopono_core/src/util/mod.rs index d1e36f5..2bc989c 100644 --- a/vopono_core/src/util/mod.rs +++ b/vopono_core/src/util/mod.rs @@ -353,8 +353,7 @@ pub fn elevate_privileges(askpass: bool) -> anyhow::Result<()> { .status() .context(format!("Executing sudo {} {:?}", sudo_flags, &args))?; - // Deprecated - do we need to handle flag here? - // cleanup::cleanup_signal(SIGINT)?; + // TODO: Could handle executing with non-sudo firejail here if terminated.load(Ordering::SeqCst) { // we received a sigint,