From 3865f7b8f6ff8b1a289a30c9b1dea1841f481b5c Mon Sep 17 00:00:00 2001 From: James McMurray Date: Sun, 9 May 2021 16:22:36 +0200 Subject: [PATCH 1/9] Add ability to specify vopono config file path --- src/args.rs | 5 +++++ src/exec.rs | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/args.rs b/src/args.rs index aed58f9..97fb129 100644 --- a/src/args.rs +++ b/src/args.rs @@ -126,6 +126,11 @@ pub struct ExecCommand { /// before shutting down the namespace #[structopt(long = "predown")] pub predown: Option, + + /// Path to vopono config TOML file (will be created if it does not exist) + /// Default: ~/.config/vopono/config.toml + #[structopt(long = "vopono-config")] + pub vopono_config: Option, } #[derive(StructOpt)] diff --git a/src/exec.rs b/src/exec.rs index 63c895c..bd55082 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -27,8 +27,12 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> { let protocol: Protocol; // TODO: Refactor this part - DRY + // Check if we have config file path passed on command line // Create empty config file if does not exist - let config_path = vopono_dir()?.join("config.toml"); + let config_path = command + .vopono_config + .ok_or_else(|| anyhow!("No config file passed")) + .or_else::(|_| Ok(vopono_dir()?.join("config.toml")))?; { std::fs::OpenOptions::new() .write(true) From 227795718c2a1cbb23fff570e532dc94e9ce7b7f Mon Sep 17 00:00:00 2001 From: James McMurray Date: Thu, 13 May 2021 13:21:56 +0200 Subject: [PATCH 2/9] OpenFortiVPN WIP --- src/exec.rs | 11 +++++++ src/main.rs | 1 + src/netns.rs | 20 +++++++++++++ src/openfortivpn.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++ src/vpn.rs | 1 + 5 files changed, 104 insertions(+) create mode 100644 src/openfortivpn.rs diff --git a/src/exec.rs b/src/exec.rs index bd55082..ad2d7ec 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -186,6 +186,7 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> { Protocol::OpenVpn => provider.get_dyn_openvpn_provider()?.openvpn_dir(), Protocol::Wireguard => provider.get_dyn_wireguard_provider()?.wireguard_dir(), Protocol::OpenConnect => bail!("OpenConnect must use Custom provider"), + Protocol::OpenFortiVpn => bail!("OpenFortiVpn must use Custom provider"), }?; if !cdir.exists() || cdir.read_dir()?.next().is_none() { info!( @@ -221,6 +222,7 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> { Protocol::OpenVpn => provider.get_dyn_openvpn_provider()?.openvpn_dir(), Protocol::Wireguard => provider.get_dyn_wireguard_provider()?.wireguard_dir(), Protocol::OpenConnect => bail!("OpenConnect must use Custom provider"), + Protocol::OpenFortiVpn => bail!("OpenFortiVpn must use Custom provider"), }?; Some(get_config_from_alias(&cdir, &server_name)?) } else { @@ -356,6 +358,15 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> { &server_name, )?; } + Protocol::OpenFortiVpn => { + // TODO: DNS handled by OpenFortiVpn directly? + ns.run_openfortivpn( + config_file.expect("No OpenFortiVPN config file provided"), + command.open_ports.as_ref(), + command.forward_ports.as_ref(), + firewall, + )?; + } } // Run PostUp script (if any) diff --git a/src/main.rs b/src/main.rs index 4c117d0..661ac3c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ mod list_configs; mod netns; mod network_interface; mod openconnect; +mod openfortivpn; mod openvpn; mod providers; mod pulseaudio; diff --git a/src/netns.rs b/src/netns.rs index 2d08aea..665dcea 100644 --- a/src/netns.rs +++ b/src/netns.rs @@ -3,6 +3,7 @@ use super::firewall::Firewall; use super::host_masquerade::HostMasquerade; use super::network_interface::NetworkInterface; use super::openconnect::OpenConnect; +use super::openfortivpn::OpenFortiVpn; use super::openvpn::OpenVpn; use super::shadowsocks::Shadowsocks; use super::util::{config_dir, set_config_permissions, sudo_command}; @@ -33,6 +34,7 @@ pub struct NetworkNamespace { pub shadowsocks: Option, pub veth_pair_ips: Option, pub openconnect: Option, + pub openfortivpn: Option, pub provider: VpnProvider, pub protocol: Protocol, pub firewall: Firewall, @@ -87,6 +89,7 @@ impl NetworkNamespace { shadowsocks: None, veth_pair_ips: None, openconnect: None, + openfortivpn: None, provider, protocol, firewall, @@ -253,6 +256,23 @@ impl NetworkNamespace { Ok(()) } + pub fn run_openfortivpn( + &mut self, + config_file: PathBuf, + open_ports: Option<&Vec>, + forward_ports: Option<&Vec>, + firewall: Firewall, + ) -> anyhow::Result<()> { + self.openfortivpn = Some(OpenFortiVpn::run( + &self, + config_file, + open_ports, + forward_ports, + firewall, + )?); + Ok(()) + } + pub fn run_shadowsocks( &mut self, config_file: &Path, diff --git a/src/openfortivpn.rs b/src/openfortivpn.rs new file mode 100644 index 0000000..8441341 --- /dev/null +++ b/src/openfortivpn.rs @@ -0,0 +1,71 @@ +use super::firewall::Firewall; +use super::netns::NetworkNamespace; +use anyhow::{anyhow, Context}; +use log::{debug, error, info}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Serialize, Deserialize)] +pub struct OpenFortiVpn { + pid: u32, +} + +impl OpenFortiVpn { + #[allow(clippy::too_many_arguments)] + pub fn run( + netns: &NetworkNamespace, + config_file: PathBuf, + open_ports: Option<&Vec>, + forward_ports: Option<&Vec>, + firewall: Firewall, + ) -> anyhow::Result { + if let Err(x) = which::which("openfortivpn") { + error!("OpenFortiVPN not found. Is OpenFortiVPN installed and on PATH?"); + return Err(anyhow!( + "OpenFortiVPN not found. Is OpenFortiVPN installed and on PATH?: {:?}", + x + )); + } + + let handle; + + info!("Launching OpenFortiVPN..."); + // TODO: DNS + default route + // Must run as root - https://github.com/adrienverge/openfortivpn/issues/650 + let command_vec = (&[ + "openfortivpn", + "-c", + config_file.to_str().expect("Invalid config path"), + ]) + .to_vec(); + + handle = netns + .exec_no_block(&command_vec, None, false, None) + .context("Failed to launch OpenFortiVPN - is openfortivpn installed?")?; + let id = handle.id(); + + // Allow input to and output from open ports (for port forwarding in tunnel) + if let Some(opens) = open_ports { + super::util::open_ports(&netns, opens.as_slice(), firewall)?; + } + + // Allow input to and output from forwarded ports + if let Some(forwards) = forward_ports { + super::util::open_ports(&netns, forwards.as_slice(), firewall)?; + } + + Ok(Self { pid: id }) + } +} + +impl Drop for OpenFortiVpn { + fn drop(&mut self) { + match nix::sys::signal::kill( + nix::unistd::Pid::from_raw(self.pid as i32), + nix::sys::signal::Signal::SIGKILL, + ) { + Ok(_) => debug!("Killed OpenFortiVPN (pid: {})", self.pid), + Err(e) => error!("Failed to kill OpenFortiVPN (pid: {}): {:?}", self.pid, e), + } + } +} diff --git a/src/vpn.rs b/src/vpn.rs index 3b48f99..6d72d15 100644 --- a/src/vpn.rs +++ b/src/vpn.rs @@ -67,6 +67,7 @@ pub enum Protocol { OpenVpn, Wireguard, OpenConnect, + OpenFortiVpn, } } From 7db602d91be3841781f93ba2637d287e9869c4f3 Mon Sep 17 00:00:00 2001 From: James McMurray Date: Mon, 17 May 2021 00:27:36 +0200 Subject: [PATCH 3/9] OpenFortiVPN WIP TODO: - Fix DNS setting from inside namespace - Fix setting default route inside namespace --- src/list_configs.rs | 1 + src/network_interface.rs | 2 +- src/openfortivpn.rs | 5 +++++ src/sync.rs | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/list_configs.rs b/src/list_configs.rs index be8bc74..f376dbf 100644 --- a/src/list_configs.rs +++ b/src/list_configs.rs @@ -21,6 +21,7 @@ pub fn print_configs(cmd: ServersCommand) -> anyhow::Result<()> { Protocol::OpenVpn => provider.get_dyn_openvpn_provider()?.openvpn_dir(), Protocol::Wireguard => provider.get_dyn_wireguard_provider()?.wireguard_dir(), Protocol::OpenConnect => bail!("Config listing not implemented for OpenConnect"), + Protocol::OpenFortiVpn => bail!("Config listing not implemented for OpenFortiVPN"), }?; if !cdir.exists() || cdir.read_dir()?.next().is_none() { bail!( diff --git a/src/network_interface.rs b/src/network_interface.rs index 456a30a..8640e0d 100644 --- a/src/network_interface.rs +++ b/src/network_interface.rs @@ -44,7 +44,7 @@ pub fn get_active_interfaces() -> anyhow::Result> { .filter(|x| x.contains("state UP")) .map(|x| x.split_whitespace().nth(1)) .filter(|x| x.is_some()) - .map(|x| x.unwrap()) + .flatten() .map(|x| String::from(&x[..x.len() - 1])) .collect::>(); diff --git a/src/openfortivpn.rs b/src/openfortivpn.rs index 8441341..9c09f21 100644 --- a/src/openfortivpn.rs +++ b/src/openfortivpn.rs @@ -34,16 +34,21 @@ impl OpenFortiVpn { // Must run as root - https://github.com/adrienverge/openfortivpn/issues/650 let command_vec = (&[ "openfortivpn", + "-v", + "-v", "-c", config_file.to_str().expect("Invalid config path"), ]) .to_vec(); + // TODO - better handle blocking for input and waiting until connection established handle = netns .exec_no_block(&command_vec, None, false, None) .context("Failed to launch OpenFortiVPN - is openfortivpn installed?")?; let id = handle.id(); + // TODO: Handle default route + // sudo ip route | grep "ppp0 proto kernel" // Allow input to and output from open ports (for port forwarding in tunnel) if let Some(opens) = open_ports { super::util::open_ports(&netns, opens.as_slice(), firewall)?; diff --git a/src/sync.rs b/src/sync.rs index d8bfdd1..7fa229d 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -49,6 +49,9 @@ pub fn synch(provider: VpnProvider, protocol: Option) -> anyhow::Resul Some(Protocol::OpenConnect) => { error!("vopono sync not supported for OpenConnect protocol"); } + Some(Protocol::OpenFortiVpn) => { + error!("vopono sync not supported for OpenFortiVpn protocol"); + } // TODO: Fix this asking for same credentials twice None => { if let Ok(p) = provider.get_dyn_wireguard_provider() { From a990597ae6588f2cf29be0b7a229115359ed47b7 Mon Sep 17 00:00:00 2001 From: James McMurray Date: Tue, 18 May 2021 22:35:16 +0200 Subject: [PATCH 4/9] Add more debug info to paths TODO: - Test config dir creation from scratch - Fix port forwarding when running as root - Fix DNS and route setting for OpenFortiVPN --- src/exec.rs | 7 ++++++- src/util/mod.rs | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/exec.rs b/src/exec.rs index ad2d7ec..649c20b 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -14,8 +14,11 @@ use super::vpn::{verify_auth, Protocol}; use anyhow::{anyhow, bail}; use log::{debug, error, info, warn}; use signal_hook::{consts::SIGINT, iterator::Signals}; -use std::io::{self, Write}; use std::net::{IpAddr, Ipv4Addr}; +use std::{ + fs::create_dir_all, + io::{self, Write}, +}; pub fn exec(command: ExecCommand) -> anyhow::Result<()> { // this captures all sigint signals @@ -29,6 +32,7 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> { // TODO: Refactor this part - DRY // Check if we have config file path passed on command line // Create empty config file if does not exist + create_dir_all(vopono_dir()?)?; let config_path = command .vopono_config .ok_or_else(|| anyhow!("No config file passed")) @@ -389,6 +393,7 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> { let application = ApplicationWrapper::new(&ns, &command.application, user)?; // Launch TCP proxy server on other threads if forwarding ports + // TODO: Fix when running as root let mut proxy = Vec::new(); if let Some(f) = command.forward_ports { if !(command.no_proxy || f.is_empty()) { diff --git a/src/util/mod.rs b/src/util/mod.rs index 2cd47d0..4bdad59 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -25,12 +25,20 @@ use walkdir::WalkDir; pub fn config_dir() -> anyhow::Result { let mut pathbuf = PathBuf::new(); let _res: () = if let Some(base_dirs) = BaseDirs::new() { + debug!( + "Using config dir from XDG dirs: {}", + base_dirs.config_dir().to_string_lossy() + ); pathbuf.push(base_dirs.config_dir()); Ok(()) } else if let Ok(user) = std::env::var("SUDO_USER") { // TODO: DRY let confpath = format!("/home/{}/.config", user); let path = Path::new(&confpath); + debug!( + "Using config dir from $SUDO_USER config: {}", + path.to_string_lossy() + ); if path.exists() { pathbuf.push(path); Ok(()) @@ -40,6 +48,10 @@ pub fn config_dir() -> anyhow::Result { } else if let Some(user) = get_user_by_uid(get_current_uid()) { let confpath = format!("/home/{}/.config", user.name().to_str().unwrap()); let path = Path::new(&confpath); + debug!( + "Using config dir from current user config: {}", + path.to_string_lossy() + ); if path.exists() { pathbuf.push(path); Ok(()) From 0013d01d909419b364054d84e2290d15817f4244 Mon Sep 17 00:00:00 2001 From: James McMurray Date: Wed, 19 May 2021 22:08:26 +0200 Subject: [PATCH 5/9] OpenFortiVPN WIP - pppd issues TODO: Fix pppd piping issues with OpenFortiVPN - maybe read pppd log file like we do with OpenVPN? Also removed privilege escalation for vopono sync and made SUDO_USER take priority over XDG dirs (so root XDG settings do not conflict with sudo usage) --- src/application_wrapper.rs | 2 +- src/main.rs | 1 - src/netns.rs | 8 +++- src/openconnect.rs | 2 +- src/openfortivpn.rs | 79 +++++++++++++++++++++++++++++++++++--- src/openvpn.rs | 2 +- src/shadowsocks.rs | 2 +- src/util/mod.rs | 16 ++++---- 8 files changed, 92 insertions(+), 20 deletions(-) diff --git a/src/application_wrapper.rs b/src/application_wrapper.rs index 24ad228..af7e5d0 100644 --- a/src/application_wrapper.rs +++ b/src/application_wrapper.rs @@ -34,7 +34,7 @@ impl ApplicationWrapper { } // TODO: Could allow user to set custom working directory here - let handle = netns.exec_no_block(app_vec.as_slice(), user, false, None)?; + let handle = netns.exec_no_block(app_vec.as_slice(), user, false, false, None)?; Ok(Self { handle }) } diff --git a/src/main.rs b/src/main.rs index 661ac3c..0a10f70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,7 +76,6 @@ fn main() -> anyhow::Result<()> { output_list(listcmd)?; } args::Command::Synch(synchcmd) => { - elevate_privileges()?; // If provider given then sync that, else prompt with menu if synchcmd.vpn_provider.is_none() { sync_menu()?; diff --git a/src/netns.rs b/src/netns.rs index 665dcea..993cb6b 100644 --- a/src/netns.rs +++ b/src/netns.rs @@ -103,6 +103,7 @@ impl NetworkNamespace { command: &[&str], user: Option, silent: bool, + capture_output: bool, set_dir: Option, ) -> anyhow::Result { let mut handle = Command::new("ip"); @@ -120,6 +121,10 @@ impl NetworkNamespace { handle.stdout(Stdio::null()); handle.stderr(Stdio::null()); } + if capture_output { + handle.stdout(Stdio::piped()); + handle.stderr(Stdio::piped()); + } debug!( "ip netns exec {}{} {}", @@ -132,7 +137,8 @@ impl NetworkNamespace { } pub fn exec(&self, command: &[&str]) -> anyhow::Result<()> { - self.exec_no_block(command, None, false, None)?.wait()?; + self.exec_no_block(command, None, false, false, None)? + .wait()?; Ok(()) } diff --git a/src/openconnect.rs b/src/openconnect.rs index 291905c..fb8975f 100644 --- a/src/openconnect.rs +++ b/src/openconnect.rs @@ -46,7 +46,7 @@ impl OpenConnect { let command_vec = (&["openconnect", &user_arg, "--passwd-on-stdin", server]).to_vec(); handle = netns - .exec_no_block(&command_vec, None, false, None) + .exec_no_block(&command_vec, None, false, false, None) .context("Failed to launch OpenConnect - is openconnect installed?")?; let id = handle.id(); diff --git a/src/openfortivpn.rs b/src/openfortivpn.rs index 9c09f21..1f1f108 100644 --- a/src/openfortivpn.rs +++ b/src/openfortivpn.rs @@ -2,8 +2,13 @@ use super::firewall::Firewall; use super::netns::NetworkNamespace; use anyhow::{anyhow, Context}; use log::{debug, error, info}; +use regex::Regex; use serde::{Deserialize, Serialize}; +use std::io::{Read, Write}; +use std::net::Ipv4Addr; use std::path::PathBuf; +use std::process::Command; +use std::str::FromStr; #[derive(Serialize, Deserialize)] pub struct OpenFortiVpn { @@ -27,15 +32,13 @@ impl OpenFortiVpn { )); } - let handle; + let mut handle; info!("Launching OpenFortiVPN..."); // TODO: DNS + default route // Must run as root - https://github.com/adrienverge/openfortivpn/issues/650 let command_vec = (&[ "openfortivpn", - "-v", - "-v", "-c", config_file.to_str().expect("Invalid config path"), ]) @@ -43,12 +46,45 @@ impl OpenFortiVpn { // TODO - better handle blocking for input and waiting until connection established handle = netns - .exec_no_block(&command_vec, None, false, None) + .exec_no_block(&command_vec, None, false, true, None) .context("Failed to launch OpenFortiVPN - is openfortivpn installed?")?; + let mut stdout = handle.stdout.take().unwrap(); + let mut stderr = handle.stderr.take().unwrap(); let id = handle.id(); - // TODO: Handle default route - // sudo ip route | grep "ppp0 proto kernel" + info!("Waiting for OpenFortiVPN to establish connection - you may be prompted on your 2FA device"); + let mut buffer: [u8; 8192] = [0; 8192]; + stdout.read(&mut buffer)?; + // let mut errbuffer: [u8; 8192] = [0; 8192]; + // stderr.read(&mut buffer)?; + + let mut remote_peer = None; + while !std::str::from_utf8(&buffer)?.contains("Tunnel is up and running") { + // debug!("{}", std::str::from_utf8(&buffer)?); + + // TODO: remote peer is returned by pppd directly and this is NOT captured in the + // stdout pipe + remote_peer = remote_peer.or_else(|| { + get_remote_peer(std::str::from_utf8(&errbuffer).expect("Non UTF8 stderr")) + }); + std::thread::sleep(std::time::Duration::new(1, 0)); + stdout.read(&mut buffer)?; + // stderr.read(&mut buffer)?; + } + + remote_peer = remote_peer + .or_else(|| get_remote_peer(std::str::from_utf8(&buffer).expect("Non UTF8 stdout"))); + + debug!("Found OpenFortiVPN route: {:?}", remote_peer); + netns.exec(&["ip", "route", "del", "default"])?; + netns.exec(&[ + "ip", + "route", + "add", + "default", + "via", + &remote_peer.expect("No remote peer found").to_string(), + ])?; // Allow input to and output from open ports (for port forwarding in tunnel) if let Some(opens) = open_ports { super::util::open_ports(&netns, opens.as_slice(), firewall)?; @@ -74,3 +110,34 @@ impl Drop for OpenFortiVpn { } } } + +// Cannot use in network namespace - at least if pppd is running outside? +pub fn get_peer_route() -> anyhow::Result { + let output = Command::new("ip").args(&["route"]).output()?.stdout; + let output = std::str::from_utf8(&output)?; + debug!("OpenFortiVPN ip routes: {}", output); + + // sudo ip route | grep "ppp0 proto kernel" + let re = + Regex::new(r"(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) dev ppp0 proto kernel").unwrap(); + let mut ips = Vec::new(); + for caps in re.captures_iter(output) { + ips.push(Ipv4Addr::from_str(&caps["ip"])?); + } + debug!("Found OpenFortiVPN routes: {:?}", &ips); + debug!("Using last route as default gateway"); + ips.pop() + .ok_or_else(|| anyhow!("No route found for gateway")) +} + +pub fn get_remote_peer(stdout: &str) -> Option { + let re = Regex::new(r"remote IP address (?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})").unwrap(); + let mut ips = Vec::new(); + for caps in re.captures_iter(stdout) { + ips.push(Ipv4Addr::from_str(&caps["ip"]).expect("Failed to parse IP address in stdout")); + } + ips.pop() +} + +// DNS pppd: +// INFO: Got addresses: [x.x.x.x], ns [y.y.y.y, y.y.y.y], ns_suffix [host.net;host2.com;host.com] diff --git a/src/openvpn.rs b/src/openvpn.rs index 4a40c6a..5e8b3d4 100644 --- a/src/openvpn.rs +++ b/src/openvpn.rs @@ -71,7 +71,7 @@ impl OpenVpn { let working_dir = PathBuf::from(config_file_path.parent().unwrap()); handle = netns - .exec_no_block(&command_vec, None, true, Some(working_dir)) + .exec_no_block(&command_vec, None, true, false, Some(working_dir)) .context("Failed to launch OpenVPN - is openvpn installed?")?; let id = handle.id(); let mut buffer = String::with_capacity(1024); diff --git a/src/shadowsocks.rs b/src/shadowsocks.rs index 127a730..183fd59 100644 --- a/src/shadowsocks.rs +++ b/src/shadowsocks.rs @@ -67,7 +67,7 @@ impl Shadowsocks { ]; let handle = netns - .exec_no_block(&command_vec, None, true, None) + .exec_no_block(&command_vec, None, true, false, None) .context("Failed to launch Shadowsocks - is shadowsocks-libev installed?")?; Ok(Self { pid: handle.id() }) diff --git a/src/util/mod.rs b/src/util/mod.rs index 4bdad59..509fadd 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -24,14 +24,7 @@ use walkdir::WalkDir; pub fn config_dir() -> anyhow::Result { let mut pathbuf = PathBuf::new(); - let _res: () = if let Some(base_dirs) = BaseDirs::new() { - debug!( - "Using config dir from XDG dirs: {}", - base_dirs.config_dir().to_string_lossy() - ); - pathbuf.push(base_dirs.config_dir()); - Ok(()) - } else if let Ok(user) = std::env::var("SUDO_USER") { + let _res: () = if let Ok(user) = std::env::var("SUDO_USER") { // TODO: DRY let confpath = format!("/home/{}/.config", user); let path = Path::new(&confpath); @@ -45,6 +38,13 @@ pub fn config_dir() -> anyhow::Result { } else { Err(anyhow!("Could not find valid config directory!")) } + } else if let Some(base_dirs) = BaseDirs::new() { + debug!( + "Using config dir from XDG dirs: {}", + base_dirs.config_dir().to_string_lossy() + ); + pathbuf.push(base_dirs.config_dir()); + Ok(()) } else if let Some(user) = get_user_by_uid(get_current_uid()) { let confpath = format!("/home/{}/.config", user.name().to_str().unwrap()); let path = Path::new(&confpath); From 309988403327e302fe9c8fd4cd74a5d6adebd6c6 Mon Sep 17 00:00:00 2001 From: James McMurray Date: Thu, 20 May 2021 08:15:02 +0200 Subject: [PATCH 6/9] Make build succeed --- src/openfortivpn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openfortivpn.rs b/src/openfortivpn.rs index 1f1f108..c09c72f 100644 --- a/src/openfortivpn.rs +++ b/src/openfortivpn.rs @@ -65,7 +65,7 @@ impl OpenFortiVpn { // TODO: remote peer is returned by pppd directly and this is NOT captured in the // stdout pipe remote_peer = remote_peer.or_else(|| { - get_remote_peer(std::str::from_utf8(&errbuffer).expect("Non UTF8 stderr")) + get_remote_peer(std::str::from_utf8(&buffer).expect("Non UTF8 stderr")) }); std::thread::sleep(std::time::Duration::new(1, 0)); stdout.read(&mut buffer)?; From 5913dd1aab8b2f2672135b3b11feb6fc793e5ec4 Mon Sep 17 00:00:00 2001 From: James McMurray Date: Sun, 23 May 2021 15:11:47 +0200 Subject: [PATCH 7/9] Finished OpenFortiVPN, better debug info --- Cargo.toml | 2 +- README.md | 7 +++ USERGUIDE.md | 55 +++++++++++++++++ src/dns_config.rs | 12 +++- src/exec.rs | 9 ++- src/netns.rs | 6 +- src/openconnect.rs | 5 +- src/openfortivpn.rs | 142 +++++++++++++++++++++++++++++--------------- src/openvpn.rs | 3 +- src/shadowsocks.rs | 6 +- src/util/mod.rs | 10 +++- src/wireguard.rs | 66 +++++++++++--------- 12 files changed, 234 insertions(+), 89 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 59eacd3..60ae8a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "vopono" description = "Launch applications via VPN tunnels using temporary network namespaces" -version = "0.7.1" +version = "0.8.0" authors = ["James McMurray "] edition = "2018" license = "GPL-3.0-or-later" diff --git a/README.md b/README.md index 706ba86..8053aab 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ Currently Mullvad, AzireVPN, MozillaVPN, TigerVPN, ProtonVPN, iVPN, NordVPN, and PrivateInternetAccess are supported directly, with custom configuration files also supported with the `--custom` argument. +For custom connections the OpenConnect and OpenFortiVPN protocols are +also supported (e.g. for enterprise VPNs). See the [vopono User Guide](USERGUIDE.md) for more details. + ## Screenshot Screenshot showing an example with firefox, google-chrome-stable and @@ -46,6 +49,10 @@ Norway: $ vopono exec --provider azirevpn --server norway firefox ``` +You should run vopono as your own user (not using sudo) as it will +handle privilege escalation where necessary. For more details around +running as a systemd service, etc. see the [User Guide](USERGUIDE.md). + vopono can handle up to 255 separate network namespaces (i.e. different VPN server connections - if your VPN provider allows it). Commands launched with the same server prefix and VPN provider will share the same network diff --git a/USERGUIDE.md b/USERGUIDE.md index ea6fc03..397560f 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -180,6 +180,40 @@ directory as the config file itself. So any accompanying files (CA certificates, files, etc.) must be in the same directory with the file if using relative paths in the config file. +### OpenFortiVPN + +OpenFortiVPN is supported as a custom provider, allowing you to connect +to Fortinet VPN servers. + +To use it, first create an [OpenFortiVPN](https://github.com/adrienverge/openfortivpn) config file for your +connection, such as: + +`myvpn.conf`: +``` +host = vpn.company.net +port = 443 +username = myuser +password = mypassword +set-dns = 0 +pppd-use-peerdns = 0 +pppd-log = /tmp/pppd.log +``` + +You must set `set-dns` and `pppd-use-peerdns` to `0` so that +OpenFortiVPN does not try to change the global DNS settings (vopono will +set them within the network namespace). You __must__ include the line: +`pppd-log = /tmp/pppd.log` as vopono uses this to read the pppd output +directly. + +Then run vopono using this as the custom config file and specifying +`OpenFortiVPN` as the protocol. Note that if you do not specify your +password in the OpenFortiVPN config file then you must enter it when it +is waiting to connect (you will not be prompted). + +```bash +vopono -v exec --protocol OpenFortiVPN --custom /home/user/myvpn.conf firefox +``` + ### Firefox Note if running multiple Firefox sessions, they need to run separate @@ -224,6 +258,27 @@ By default, vopono runs a small TCP proxy to proxy the ports on your host machine to the ports on the network namespace - if you do not want this to run use the `--no-proxy` flag. +#### systemd service + +For the above you may want to run vopono as a systemd service. If your +user has passwordless sudo access you can use a user service, such as: + +`/etc/systemd/user/vopono.service`: +``` +[Service] +ExecStart=/usr/bin/vopono -v exec -k -f 9091 --protocol wireguard --provider mullvad --server romania "transmission-daemon -a *.*.*.*" +``` + +And then start it with (no sudo): +``` +systemctl start --user vopono +``` + +If you do not have passwordless sudo access (i.e. privilege escalation +requires entering the password) then you could use a root service and +set up vopono on the root account. But note [this issue](https://github.com/jamesmcm/vopono/issues/84) currently +makes this problematic for forwarding ports. + #### Privoxy A popular use case is to run a proxy server like Privoxy inside the diff --git a/src/dns_config.rs b/src/dns_config.rs index 60c40d8..10f3f43 100644 --- a/src/dns_config.rs +++ b/src/dns_config.rs @@ -10,7 +10,7 @@ pub struct DnsConfig { } impl DnsConfig { - pub fn new(ns_name: String, servers: &[IpAddr]) -> anyhow::Result { + pub fn new(ns_name: String, servers: &[IpAddr], suffixes: &[&str]) -> anyhow::Result { std::fs::create_dir_all(format!("/etc/netns/{}", ns_name)) .with_context(|| format!("Failed to create directory: /etc/netns/{}", ns_name))?; @@ -32,6 +32,16 @@ impl DnsConfig { .join(", ") ); + let suffix = suffixes.join(" "); + if !suffix.is_empty() { + writeln!(f, "search {}", suffix).with_context(|| { + format!( + "Failed to overwrite resolv.conf: /etc/netns/{}/resolv.conf", + ns_name + ) + })?; + } + for dns in servers { writeln!(f, "nameserver {}", dns).with_context(|| { format!( diff --git a/src/exec.rs b/src/exec.rs index 649c20b..a142270 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -286,7 +286,8 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> { }) .unwrap_or_else(|| vec![IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))]); - ns.dns_config(&dns)?; + // TODO: DNS suffixes? + ns.dns_config(&dns, &[])?; // Check if using Shadowsocks if let Some((ss_host, ss_lport)) = uses_shadowsocks(config_file.as_ref().expect("No config file provided"))? @@ -336,7 +337,8 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> { if let Some(newdns) = ns.openvpn.as_ref().unwrap().openvpn_dns { let old_dns = ns.dns_config.take(); std::mem::forget(old_dns); - ns.dns_config(&[newdns])?; + // TODO: DNS suffixes? + ns.dns_config(&[newdns], &[])?; } } } @@ -353,7 +355,8 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> { } Protocol::OpenConnect => { let dns = base_dns.unwrap_or_else(|| vec![IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))]); - ns.dns_config(&dns)?; + // TODO: DNS suffixes? + ns.dns_config(&dns, &[])?; ns.run_openconnect( config_file, command.open_ports.as_ref(), diff --git a/src/netns.rs b/src/netns.rs index 993cb6b..1c680e9 100644 --- a/src/netns.rs +++ b/src/netns.rs @@ -212,8 +212,8 @@ impl NetworkNamespace { Ok(()) } - pub fn dns_config(&mut self, server: &[IpAddr]) -> anyhow::Result<()> { - self.dns_config = Some(DnsConfig::new(self.name.clone(), &server)?); + pub fn dns_config(&mut self, server: &[IpAddr], suffixes: &[&str]) -> anyhow::Result<()> { + self.dns_config = Some(DnsConfig::new(self.name.clone(), server, suffixes)?); Ok(()) } @@ -270,7 +270,7 @@ impl NetworkNamespace { firewall: Firewall, ) -> anyhow::Result<()> { self.openfortivpn = Some(OpenFortiVpn::run( - &self, + self, config_file, open_ports, forward_ports, diff --git a/src/openconnect.rs b/src/openconnect.rs index fb8975f..1ba601d 100644 --- a/src/openconnect.rs +++ b/src/openconnect.rs @@ -65,7 +65,10 @@ impl OpenConnect { } fn get_creds_from_file(auth_file: &Path) -> anyhow::Result<(String, String)> { - let s = std::fs::read_to_string(auth_file)?; + let s = std::fs::read_to_string(auth_file).context(format!( + "Reading from OpenConnect authentication file: {:?}", + auth_file + ))?; let mut iter = s.split('\n'); let user = iter.next().expect("No username in auth file"); let pass = iter.next().expect("No password in auth file"); diff --git a/src/openfortivpn.rs b/src/openfortivpn.rs index c09c72f..4ffe450 100644 --- a/src/openfortivpn.rs +++ b/src/openfortivpn.rs @@ -4,10 +4,9 @@ use anyhow::{anyhow, Context}; use log::{debug, error, info}; use regex::Regex; use serde::{Deserialize, Serialize}; -use std::io::{Read, Write}; -use std::net::Ipv4Addr; -use std::path::PathBuf; -use std::process::Command; +use std::io::{BufRead, BufReader, Write}; +use std::net::{IpAddr, Ipv4Addr}; +use std::path::{Path, PathBuf}; use std::str::FromStr; #[derive(Serialize, Deserialize)] @@ -18,7 +17,7 @@ pub struct OpenFortiVpn { impl OpenFortiVpn { #[allow(clippy::too_many_arguments)] pub fn run( - netns: &NetworkNamespace, + netns: &mut NetworkNamespace, config_file: PathBuf, open_ports: Option<&Vec>, forward_ports: Option<&Vec>, @@ -44,36 +43,44 @@ impl OpenFortiVpn { ]) .to_vec(); - // TODO - better handle blocking for input and waiting until connection established + // TODO: Remove need for log file (and hardcoded path!) + // Delete log file if exists + let pppd_log = std::path::PathBuf::from_str("/tmp/pppd.log")?; + std::fs::remove_file(&pppd_log).ok(); + + // TODO - better handle forwarding output when blocking on password entry (no newline!) handle = netns .exec_no_block(&command_vec, None, false, true, None) .context("Failed to launch OpenFortiVPN - is openfortivpn installed?")?; - let mut stdout = handle.stdout.take().unwrap(); - let mut stderr = handle.stderr.take().unwrap(); + let stdout = handle.stdout.take().unwrap(); let id = handle.id(); info!("Waiting for OpenFortiVPN to establish connection - you may be prompted on your 2FA device"); - let mut buffer: [u8; 8192] = [0; 8192]; - stdout.read(&mut buffer)?; - // let mut errbuffer: [u8; 8192] = [0; 8192]; - // stderr.read(&mut buffer)?; - - let mut remote_peer = None; - while !std::str::from_utf8(&buffer)?.contains("Tunnel is up and running") { - // debug!("{}", std::str::from_utf8(&buffer)?); - - // TODO: remote peer is returned by pppd directly and this is NOT captured in the - // stdout pipe - remote_peer = remote_peer.or_else(|| { - get_remote_peer(std::str::from_utf8(&buffer).expect("Non UTF8 stderr")) - }); - std::thread::sleep(std::time::Duration::new(1, 0)); - stdout.read(&mut buffer)?; - // stderr.read(&mut buffer)?; + info!("If your VPN password is not in the OpenFortiVPN config file then enter it here now"); + let mut bufreader = BufReader::with_capacity(16000, stdout); + let mut buffer = String::with_capacity(16000); + let mut bufcount: usize = 0; + + let newbytes = bufreader.read_line(&mut buffer)?; + if newbytes > 0 { + print!("{}", &buffer[bufcount..(bufcount + newbytes)]); + std::io::stdout().flush()?; + bufcount += newbytes; + } + + while !buffer.contains("Tunnel is up and running") { + let newbytes = bufreader.read_line(&mut buffer)?; + if newbytes > 0 { + print!("{}", &buffer[bufcount..(bufcount + newbytes)]); + std::io::stdout().flush()?; + bufcount += newbytes; + } + std::thread::sleep(std::time::Duration::from_millis(200)); } - remote_peer = remote_peer - .or_else(|| get_remote_peer(std::str::from_utf8(&buffer).expect("Non UTF8 stdout"))); + debug!("Full OpenFortiVPN stdout: {:?}", &buffer); + + let remote_peer = get_remote_peer(&pppd_log)?; debug!("Found OpenFortiVPN route: {:?}", remote_peer); netns.exec(&["ip", "route", "del", "default"])?; @@ -83,8 +90,14 @@ impl OpenFortiVpn { "add", "default", "via", - &remote_peer.expect("No remote peer found").to_string(), + &remote_peer.to_string(), ])?; + + let dns = get_dns(&buffer)?; + let dns_ip: Vec = (dns.0).into_iter().map(IpAddr::from).collect(); + // TODO: Avoid this meaningless collect + let suffixes: Vec<&str> = (dns.1).iter().map(|x| x.as_str()).collect(); + netns.dns_config(dns_ip.as_slice(), suffixes.as_slice())?; // Allow input to and output from open ports (for port forwarding in tunnel) if let Some(opens) = open_ports { super::util::open_ports(&netns, opens.as_slice(), firewall)?; @@ -112,32 +125,65 @@ impl Drop for OpenFortiVpn { } // Cannot use in network namespace - at least if pppd is running outside? -pub fn get_peer_route() -> anyhow::Result { - let output = Command::new("ip").args(&["route"]).output()?.stdout; - let output = std::str::from_utf8(&output)?; - debug!("OpenFortiVPN ip routes: {}", output); - - // sudo ip route | grep "ppp0 proto kernel" - let re = - Regex::new(r"(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) dev ppp0 proto kernel").unwrap(); - let mut ips = Vec::new(); - for caps in re.captures_iter(output) { - ips.push(Ipv4Addr::from_str(&caps["ip"])?); - } - debug!("Found OpenFortiVPN routes: {:?}", &ips); - debug!("Using last route as default gateway"); - ips.pop() - .ok_or_else(|| anyhow!("No route found for gateway")) -} - -pub fn get_remote_peer(stdout: &str) -> Option { +// pub fn get_peer_route() -> anyhow::Result { +// let output = Command::new("ip").args(&["route"]).output()?.stdout; +// let output = std::str::from_utf8(&output)?; +// debug!("OpenFortiVPN ip routes: {}", output); + +// // sudo ip route | grep "ppp0 proto kernel" +// let re = +// Regex::new(r"(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) dev ppp0 proto kernel").unwrap(); +// let mut ips = Vec::new(); +// for caps in re.captures_iter(output) { +// ips.push(Ipv4Addr::from_str(&caps["ip"])?); +// } +// debug!("Found OpenFortiVPN routes: {:?}", &ips); +// debug!("Using last route as default gateway"); +// ips.pop() +// .ok_or_else(|| anyhow!("No route found for gateway")) +// } + +pub fn get_remote_peer(pppd_log: &Path) -> anyhow::Result { + let stdout = std::fs::read_to_string(pppd_log) + .context(format!("Opening pppd log file: {:?}", pppd_log))?; let re = Regex::new(r"remote IP address (?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})").unwrap(); let mut ips = Vec::new(); - for caps in re.captures_iter(stdout) { + for caps in re.captures_iter(&stdout) { ips.push(Ipv4Addr::from_str(&caps["ip"]).expect("Failed to parse IP address in stdout")); } ips.pop() + .ok_or_else(|| anyhow!("Could not find remote IP address in pppd log")) } // DNS pppd: // INFO: Got addresses: [x.x.x.x], ns [y.y.y.y, y.y.y.y], ns_suffix [host.net;host2.com;host.com] +pub fn get_dns(stdout: &str) -> anyhow::Result<(Vec, Vec)> { + // sudo ip route | grep "ppp0 proto kernel" + let re = Regex::new(r"ns \[(?P[^\]]+)\]").unwrap(); + let mut ips = Vec::new(); + for caps in re.captures_iter(stdout) { + for ip_raw in caps["ip"].split(", ").into_iter() { + let ip = IpAddr::from_str(ip_raw)?; + if !ips.contains(&ip) && ip != IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) { + ips.push(ip); + } + } + } + + let re = Regex::new(r"ns_suffix \[(?P[^\]]+)\]").unwrap(); + let mut suffixes = Vec::new(); + for caps in re.captures_iter(stdout) { + for suffix_raw in caps["suffix"].split(';').into_iter() { + let suffix = suffix_raw.to_string(); + if !suffixes.contains(&suffix) { + suffixes.push(suffix); + } + } + } + + debug!( + "Found OpenFortiVPN DNS ips: {:?}, ns suffixes: {:?}", + &ips, &suffixes + ); + Ok((ips, suffixes)) +} diff --git a/src/openvpn.rs b/src/openvpn.rs index 5e8b3d4..f7eb65d 100644 --- a/src/openvpn.rs +++ b/src/openvpn.rs @@ -526,7 +526,8 @@ pub fn killswitch( } pub fn get_remotes_from_config(path: &Path) -> anyhow::Result> { - let file_string = std::fs::read_to_string(path)?; + let file_string = std::fs::read_to_string(path) + .context(format!("Reading OpenVPN config file: {:?}", path))?; let mut output_vec = Vec::new(); // Regex extract let re = Regex::new(r"remote ([^\s]+) ([0-9]+)\s?(tcp|udp|tcp-client)?")?; diff --git a/src/shadowsocks.rs b/src/shadowsocks.rs index 183fd59..372cdc1 100644 --- a/src/shadowsocks.rs +++ b/src/shadowsocks.rs @@ -76,7 +76,8 @@ impl Shadowsocks { pub fn uses_shadowsocks(openvpn_config: &Path) -> anyhow::Result> { // TODO: We assume all socks-proxy are Shadowsocks - let config_str = read_to_string(openvpn_config)?; + let config_str = read_to_string(openvpn_config) + .context(format!("Reading OpenVPN config file: {:?}", openvpn_config))?; let re = Regex::new(r"socks-proxy ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) ([0-9]+)")?; let cap = re.captures(&config_str); @@ -94,7 +95,8 @@ pub fn uses_shadowsocks(openvpn_config: &Path) -> anyhow::Result anyhow::Result> { - let file_string = std::fs::read_to_string(path)?; + let file_string = std::fs::read_to_string(path) + .context(format!("Reading OpenVPN config file: {:?}", path))?; let mut output_vec = Vec::new(); // Regex extract let re = Regex::new( diff --git a/src/util/mod.rs b/src/util/mod.rs index 509fadd..896aac7 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -283,7 +283,11 @@ pub fn elevate_privileges() -> anyhow::Result<()> { debug!("Args: {:?}", &args); // status blocks until the process has ended - let _status = Command::new("sudo").arg("-E").args(args).status()?; + let _status = Command::new("sudo") + .arg("-E") + .args(args.clone()) + .status() + .context(format!("Executing sudo -E {:?}", &args))?; // Deprecated - do we need to handle flag here? // cleanup::cleanup_signal(SIGINT)?; @@ -368,7 +372,9 @@ pub fn get_config_from_alias(list_path: &Path, alias: &str) -> anyhow::Result Protocol { - let content = fs::read_to_string(config_file).unwrap(); + let content = fs::read_to_string(config_file) + .context(format!("Reading VPN config file: {:?}", config_file)) + .unwrap(); if content.contains(&"[Interface]") { Protocol::Wireguard } else { diff --git a/src/wireguard.rs b/src/wireguard.rs index 89f61c0..623b596 100644 --- a/src/wireguard.rs +++ b/src/wireguard.rs @@ -39,7 +39,8 @@ impl Wireguard { )); } - let config_string = std::fs::read_to_string(&config_file)?; + let config_string = std::fs::read_to_string(&config_file) + .context(format!("Reading Wireguard config file: {:?}", &config_file))?; // Create temp conf file { let skip_keys = vec![ @@ -54,7 +55,8 @@ impl Wireguard { "SaveConfig", ]; - let mut f = std::fs::File::create("/tmp/vopono_nft.conf")?; + let mut f = std::fs::File::create("/tmp/vopono_nft.conf") + .context("Creating file: /tmp/vopono_nft.conf")?; write!( f, "{}", @@ -85,7 +87,9 @@ impl Wireguard { namespace .exec(&["wg", "setconf", &if_name, "/tmp/vopono_nft.conf"]) .context("Failed to run wg setconf - is wireguard-tools installed?")?; - std::fs::remove_file("/tmp/vopono_nft.conf")?; + std::fs::remove_file("/tmp/vopono_nft.conf") + .context("Deleting file: /tmp/vopono_nft.conf") + .ok(); // Extract addresses for address in config.interface.address.iter() { match address { @@ -118,7 +122,8 @@ impl Wireguard { namespace.exec(&["ip", "link", "set", "mtu", "1420", "up", "dev", &if_name])?; let dns = dns.unwrap_or(&config.interface.dns); - namespace.dns_config(&dns)?; + // TODO: DNS suffixes? + namespace.dns_config(&dns, &[])?; let fwmark = "51820"; namespace.exec(&["wg", "set", &if_name, "fwmark", fwmark])?; @@ -219,12 +224,15 @@ impl Wireguard { let nftcmd = nftcmd.join("\n"); { - let mut f = std::fs::File::create("/tmp/vopono_nft.sh")?; + let mut f = std::fs::File::create("/tmp/vopono_nft.sh") + .context("Creating file: /tmp/vopono_nft.sh")?; write!(f, "{}", nftcmd)?; } namespace.exec(&["nft", "-f", "/tmp/vopono_nft.sh"])?; - std::fs::remove_file("/tmp/vopono_nft.sh")?; + std::fs::remove_file("/tmp/vopono_nft.sh") + .context("Deleting file: /tmp/vopono_nft.sh") + .ok(); } Firewall::IpTables => { for address in config.interface.address.iter() { @@ -341,26 +349,28 @@ pub fn killswitch( debug!("Setting Wireguard killswitch...."); match firewall { Firewall::IpTables => { - netns.exec(&[ - "iptables", - "-A", - "OUTPUT", - "!", - "-o", - ifname, - "-m", - "mark", - "!", - "--mark", - fwmark, - "-m", - "addrtype", - "!", - "--dst-type", - "LOCAL", - "-j", - "REJECT", - ])?; + netns + .exec(&[ + "iptables", + "-A", + "OUTPUT", + "!", + "-o", + ifname, + "-m", + "mark", + "!", + "--mark", + fwmark, + "-m", + "addrtype", + "!", + "--dst-type", + "LOCAL", + "-j", + "REJECT", + ]) + .context("Executing ip6tables")?; netns.exec(&[ "ip6tables", @@ -384,7 +394,9 @@ pub fn killswitch( ])?; } Firewall::NfTables => { - netns.exec(&["nft", "add", "table", "inet", &netns.name])?; + netns + .exec(&["nft", "add", "table", "inet", &netns.name]) + .context("Executing nft")?; netns.exec(&[ "nft", "add", From 1c735bf1ecd8dfabee8e33c7923e9efa31de7122 Mon Sep 17 00:00:00 2001 From: James McMurray Date: Sun, 23 May 2021 15:21:15 +0200 Subject: [PATCH 8/9] Update docs --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 8053aab..754630a 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ Set up VPN provider configuration files: $ vopono sync ``` +Note when creating and uploading new Wireguard keypairs there may be a slight delay +until they are usable (about 30-60 seconds on Mullvad for example). + Run Firefox through an AzireVPN Wireguard connection to a server in Norway: @@ -164,6 +167,10 @@ $ rustc --version keypairs) - unlike Mullvad this _cannot_ be done on the webpage. I recommend using [MozWire](https://github.com/NilsIrl/MozWire) to manage this. - `gnome-terminal` will not run in the network namespace due to the client-server model - see issue [#48](https://github.com/jamesmcm/vopono/issues/48) +- Port forwarding from inside the network namespace to the host (e.g. + for running `transmission-daemon`) does not work correctly when vopono + is run as root - see issue [#84](https://github.com/jamesmcm/vopono/issues/84) + ## License From f6f115903d3856d2488b9046dd04f3814342d0fb Mon Sep 17 00:00:00 2001 From: James McMurray Date: Sun, 23 May 2021 15:23:57 +0200 Subject: [PATCH 9/9] Remove completed TODO --- src/openfortivpn.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/openfortivpn.rs b/src/openfortivpn.rs index 4ffe450..53b073b 100644 --- a/src/openfortivpn.rs +++ b/src/openfortivpn.rs @@ -34,7 +34,6 @@ impl OpenFortiVpn { let mut handle; info!("Launching OpenFortiVPN..."); - // TODO: DNS + default route // Must run as root - https://github.com/adrienverge/openfortivpn/issues/650 let command_vec = (&[ "openfortivpn",