diff options
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/lib/daemon-rpc.js | 41 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/tunnel.rs | 370 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 65 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 34 | ||||
| -rw-r--r-- | mullvad-ipc-client/src/lib.rs | 6 | ||||
| -rw-r--r-- | mullvad-types/src/settings.rs | 26 | ||||
| -rw-r--r-- | talpid-core/src/process/openvpn.rs | 47 | ||||
| -rw-r--r-- | talpid-core/src/security/linux/mod.rs | 8 | ||||
| -rw-r--r-- | talpid-core/src/security/macos/mod.rs | 8 | ||||
| -rw-r--r-- | talpid-core/src/security/mod.rs | 20 | ||||
| -rw-r--r-- | talpid-core/src/security/windows/mod.rs | 8 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/mod.rs | 59 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 16 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 33 | ||||
| -rw-r--r-- | talpid-types/src/net.rs | 62 |
16 files changed, 663 insertions, 141 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index dc3abcec59..555c02e52d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Line wrap the file at 100 chars. Th - Add new system and in-app notifications to inform the user when the app becomes outdated, unsupported or may have security issues. - Allow the user to view the relay in/out IP address in the GUI. +- Add OpenVPN proxy support via CLI. ### Fixed - Pick new random relay for each reconnect attempt instead of just retrying with the same one. diff --git a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js b/gui/packages/desktop/src/renderer/lib/daemon-rpc.js index 594a33fdf6..8022aedfac 100644 --- a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js +++ b/gui/packages/desktop/src/renderer/lib/daemon-rpc.js @@ -234,12 +234,53 @@ export type TunnelOptions = { openvpn: { mssfix: ?number, }, + proxy: ?ProxySettings, }; +export type ProxySettings = LocalProxySettings | RemoteProxySettings; + +export type LocalProxySettings = { + port: number, + peer: string, +}; + +export type RemoteProxySettings = { + address: string, + auth: ?RemoteProxyAuth, +}; + +export type RemoteProxyAuth = { + username: string, + password: string, +}; + +const OpenVpnProxySchema = maybe( + oneOf( + object({ + local: object({ + port: number, + peer: string, + }), + }), + object({ + remote: object({ + address: string, + auth: maybe( + object({ + username: string, + password: string, + }), + ), + }), + }), + ), +); + const TunnelOptionsSchema = object({ enable_ipv6: boolean, openvpn: object({ mssfix: maybe(number), + proxy: OpenVpnProxySchema, }), }); diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index 8d601cd345..329f72f6bd 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -1,7 +1,12 @@ -use clap; +use clap::{self, value_t}; use {new_rpc_client, Command, Result}; -use talpid_types::net::{OpenVpnTunnelOptions, TunnelOptions}; +use talpid_types::net::{ + LocalOpenVpnProxySettings, OpenVpnProxyAuth, OpenVpnProxySettings, + OpenVpnProxySettingsValidation, RemoteOpenVpnProxySettings, TunnelOptions, +}; + +use std::net::{IpAddr, SocketAddr}; pub struct Tunnel; @@ -14,134 +19,313 @@ impl Command for Tunnel { clap::SubCommand::with_name(self.name()) .about("Manage tunnel specific options") .setting(clap::AppSettings::SubcommandRequired) - .subcommand( - clap::SubCommand::with_name("openvpn") - .about("Manage options for OpenVPN tunnels") - .setting(clap::AppSettings::SubcommandRequired) - .subcommand( - clap::SubCommand::with_name("set") - .subcommand( - clap::SubCommand::with_name("mssfix").arg( - clap::Arg::with_name("mssfix") - .help( - "Sets the optional mssfix parameter. \ - Set an empty string to clear it.", - ) - .required(true), - ), - ) - .setting(clap::AppSettings::SubcommandRequired), - ) - .subcommand( - clap::SubCommand::with_name("get") - .help("Retrieves the current setting for mssfix"), - ), - ) - .subcommand( - clap::SubCommand::with_name("set") - .subcommand( - clap::SubCommand::with_name("ipv6").arg( - clap::Arg::with_name("enable") - .required(true) - .takes_value(true) - .possible_values(&["on", "off"]), - ), - ) - .setting(clap::AppSettings::SubcommandRequired), - ) - .subcommand( - clap::SubCommand::with_name("get") - .help("Retrieves the current setting for common tunnel options"), - ) + .subcommand(create_openvpn_subcommand()) + .subcommand(create_ipv6_subcommand()) } fn run(&self, matches: &clap::ArgMatches) -> Result<()> { if let Some(openvpn_matches) = matches.subcommand_matches("openvpn") { Self::handle_openvpn_cmd(openvpn_matches) - } else if let Some(set_matches) = matches.subcommand_matches("set") { - Self::set_tunnel_option(set_matches) - } else if let Some(_) = matches.subcommand_matches("get") { - let tunnel_options = Self::get_tunnel_options()?; - Self::print_common_tunnel_options(&tunnel_options); - Ok(()) + } else if let Some(ipv6_matches) = matches.subcommand_matches("ipv6") { + Self::handle_ipv6_cmd(ipv6_matches) } else { - unreachable!("No tunnel command given") + unreachable!("unhandled command"); } } } +fn create_openvpn_subcommand() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("openvpn") + .about("Manage options for OpenVPN tunnels") + .setting(clap::AppSettings::SubcommandRequired) + .subcommand(create_openvpn_mssfix_subcommand()) + .subcommand(create_openvpn_proxy_subcommand()) +} + +fn create_openvpn_mssfix_subcommand() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("mssfix") + .about("Configure the optional mssfix parameter") + .setting(clap::AppSettings::SubcommandRequired) + .subcommand(clap::SubCommand::with_name("get")) + .subcommand(clap::SubCommand::with_name("unset")) + .subcommand( + clap::SubCommand::with_name("set").arg(clap::Arg::with_name("mssfix").required(true)), + ) +} + +fn create_openvpn_proxy_subcommand() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("proxy") + .about("Configure a SOCKS5 proxy") + .setting(clap::AppSettings::SubcommandRequired) + .subcommand(clap::SubCommand::with_name("get")) + .subcommand(clap::SubCommand::with_name("unset")) + .subcommand( + clap::SubCommand::with_name("set") + .setting(clap::AppSettings::SubcommandRequired) + .subcommand( + clap::SubCommand::with_name("local") + .about("Registers a local SOCKS5 proxy") + .arg( + clap::Arg::with_name("local-port") + .help("Specifies the port the local proxy server is listening on") + .required(true) + .index(1), + ) + .arg( + clap::Arg::with_name("remote-ip") + .help("Specifies the IP of the proxy server peer") + .required(true) + .index(2), + ) + .arg( + clap::Arg::with_name("remote-port") + .help("Specifies the port of the proxy server peer") + .required(true) + .index(3), + ), + ) + .subcommand( + clap::SubCommand::with_name("remote") + .about("Registers a remote SOCKS5 proxy") + .arg( + clap::Arg::with_name("remote-ip") + .help("Specifies the IP of the remote proxy server") + .required(true) + .index(1), + ) + .arg( + clap::Arg::with_name("remote-port") + .help("Specifies the port the remote proxy server is listening on") + .required(true) + .index(2), + ) + .arg( + clap::Arg::with_name("username") + .help("Specifies the username for remote authentication") + .index(3), + ) + .arg( + clap::Arg::with_name("password") + .help("Specifies the password for remote authentication") + .index(4), + ), + ), + ) +} + +fn create_ipv6_subcommand() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("ipv6") + .setting(clap::AppSettings::SubcommandRequired) + .subcommand(clap::SubCommand::with_name("get")) + .subcommand( + clap::SubCommand::with_name("set").arg( + clap::Arg::with_name("enable") + .required(true) + .takes_value(true) + .possible_values(&["on", "off"]), + ), + ) +} + impl Tunnel { - fn set_tunnel_option(matches: &clap::ArgMatches) -> Result<()> { - if let Some(ipv6_args) = matches.subcommand_matches("ipv6") { - Self::set_enable_ipv6_option(ipv6_args) + fn handle_openvpn_cmd(matches: &clap::ArgMatches) -> Result<()> { + if let Some(m) = matches.subcommand_matches("mssfix") { + Self::handle_openvpn_mssfix_cmd(m) + } else if let Some(m) = matches.subcommand_matches("proxy") { + Self::handle_openvpn_proxy_cmd(m) } else { - unreachable!("Invalid option passed to 'tunnel set'"); + unreachable!("unhandled command"); } } - fn set_enable_ipv6_option(args: &clap::ArgMatches) -> Result<()> { - let enabled = args.value_of("enable").unwrap() == "on"; + fn handle_openvpn_mssfix_cmd(matches: &clap::ArgMatches) -> Result<()> { + if let Some(_) = matches.subcommand_matches("get") { + Self::process_openvpn_mssfix_get() + } else if let Some(_) = matches.subcommand_matches("unset") { + Self::process_openvpn_mssfix_unset() + } else if let Some(m) = matches.subcommand_matches("set") { + Self::process_openvpn_mssfix_set(m) + } else { + unreachable!("unhandled command"); + } + } - let mut rpc = new_rpc_client()?; - rpc.set_enable_ipv6(enabled)?; - println!("IPv6 {}", if enabled { "on" } else { "off" }); - Ok(()) + fn handle_openvpn_proxy_cmd(matches: &clap::ArgMatches) -> Result<()> { + if let Some(_) = matches.subcommand_matches("get") { + Self::process_openvpn_proxy_get() + } else if let Some(_) = matches.subcommand_matches("unset") { + Self::process_openvpn_proxy_unset() + } else if let Some(m) = matches.subcommand_matches("set") { + Self::process_openvpn_proxy_set(m) + } else { + unreachable!("unhandled command"); + } } - fn handle_openvpn_cmd(matches: &clap::ArgMatches) -> Result<()> { - if let Some(set_matches) = matches.subcommand_matches("set") { - Self::set_openvpn_option(set_matches) - } else if let Some(_) = matches.subcommand_matches("get") { - let tunnel_options = Self::get_tunnel_options()?; - Self::print_openvpn_tunnel_options(tunnel_options.openvpn); - Ok(()) + fn handle_ipv6_cmd(matches: &clap::ArgMatches) -> Result<()> { + if let Some(_) = matches.subcommand_matches("get") { + Self::process_ipv6_get() + } else if let Some(m) = matches.subcommand_matches("set") { + Self::process_ipv6_set(m) } else { - unreachable!("Unrecognized subcommand"); + unreachable!("unhandled command"); } } - fn set_openvpn_option(matches: &clap::ArgMatches) -> Result<()> { - if let Some(mssfix_args) = matches.subcommand_matches("mssfix") { - Self::set_openvpn_mssfix_option(mssfix_args) + fn process_openvpn_mssfix_get() -> Result<()> { + let tunnel_options = Self::get_tunnel_options()?; + println!( + "mssfix: {}", + tunnel_options + .openvpn + .mssfix + .map_or_else(|| "unset".to_string(), |v| v.to_string()) + ); + Ok(()) + } + + fn get_tunnel_options() -> Result<TunnelOptions> { + let mut rpc = new_rpc_client()?; + Ok(rpc.get_settings()?.get_tunnel_options().clone()) + } + + fn process_openvpn_mssfix_unset() -> Result<()> { + let mut rpc = new_rpc_client()?; + rpc.set_openvpn_mssfix(None)?; + println!("mssfix parameter has been unset"); + Ok(()) + } + + fn process_openvpn_mssfix_set(matches: &clap::ArgMatches) -> Result<()> { + let new_value = value_t!(matches.value_of("mssfix"), u16).unwrap_or_else(|e| e.exit()); + let mut rpc = new_rpc_client()?; + rpc.set_openvpn_mssfix(Some(new_value))?; + println!("mssfix parameter has been updated"); + Ok(()) + } + + fn process_openvpn_proxy_get() -> Result<()> { + let tunnel_options = Self::get_tunnel_options()?; + if let Some(proxy) = tunnel_options.openvpn.proxy { + if let OpenVpnProxySettings::Local(local_proxy) = proxy { + Self::print_local_proxy(&local_proxy) + } else if let OpenVpnProxySettings::Remote(remote_proxy) = proxy { + Self::print_remote_proxy(&remote_proxy) + } else { + unreachable!("unhandled proxy type"); + } } else { - unreachable!("Invalid option passed to 'openvpn set'"); + println!("proxy: unset"); } + Ok(()) } - fn set_openvpn_mssfix_option(args: &clap::ArgMatches) -> Result<()> { - let mssfix_str = args.value_of("mssfix").unwrap(); - let mssfix: Option<u16> = if mssfix_str == "" { - None + fn print_local_proxy(proxy: &LocalOpenVpnProxySettings) { + println!("proxy: local"); + println!(" local port: {}", proxy.port); + println!(" peer IP: {}", proxy.peer.ip()); + println!(" peer port: {}", proxy.peer.port()); + } + + fn print_remote_proxy(proxy: &RemoteOpenVpnProxySettings) { + println!("proxy: remote"); + println!(" server IP: {}", proxy.address.ip()); + println!(" server port: {}", proxy.address.port()); + + if let Some(ref auth) = proxy.auth { + println!(" auth username: {}", auth.username); + println!(" auth password: {}", auth.password); } else { - Some(mssfix_str.parse()?) - }; + println!(" auth: none"); + } + } + fn process_openvpn_proxy_unset() -> Result<()> { let mut rpc = new_rpc_client()?; - rpc.set_openvpn_mssfix(mssfix)?; - println!("mssfix parameter updated"); + rpc.set_openvpn_proxy(None)?; + println!("proxy details have been unset"); Ok(()) } - fn get_tunnel_options() -> Result<TunnelOptions> { - let mut rpc = new_rpc_client()?; - Ok(rpc.get_settings()?.get_tunnel_options().clone()) + fn process_openvpn_proxy_set(matches: &clap::ArgMatches) -> Result<()> { + if let Some(args) = matches.subcommand_matches("local") { + let local_port = + value_t!(args.value_of("local-port"), u16).unwrap_or_else(|e| e.exit()); + let remote_ip = + value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit()); + let remote_port = + value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit()); + + let proxy = LocalOpenVpnProxySettings { + port: local_port, + peer: SocketAddr::new(remote_ip, remote_port), + }; + + let packed_proxy = OpenVpnProxySettings::Local(proxy); + + if let Err(error) = OpenVpnProxySettingsValidation::validate(&packed_proxy) { + panic!(error); + } + + let mut rpc = new_rpc_client()?; + rpc.set_openvpn_proxy(Some(packed_proxy))?; + } else if let Some(args) = matches.subcommand_matches("remote") { + let remote_ip = + value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit()); + let remote_port = + value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit()); + let username = args.value_of("username"); + let password = args.value_of("password"); + + let auth = match (username, password) { + (Some(username), Some(password)) => Some(OpenVpnProxyAuth { + username: username.to_string(), + password: password.to_string(), + }), + _ => None, + }; + + let proxy = RemoteOpenVpnProxySettings { + address: SocketAddr::new(remote_ip, remote_port), + auth: auth, + }; + + let packed_proxy = OpenVpnProxySettings::Remote(proxy); + + if let Err(error) = OpenVpnProxySettingsValidation::validate(&packed_proxy) { + panic!(error); + } + + let mut rpc = new_rpc_client()?; + rpc.set_openvpn_proxy(Some(packed_proxy))?; + } else { + unreachable!("unhandled proxy type"); + } + + println!("proxy details have been updated"); + println!("note: The OpenVPN tunnel constraints have been updated to use TCP"); + Ok(()) } - fn print_common_tunnel_options(options: &TunnelOptions) { - println!("Common tunnel options"); + fn process_ipv6_get() -> Result<()> { + let tunnel_options = Self::get_tunnel_options()?; println!( - "\tIPv6: {}", - if options.enable_ipv6 { "on" } else { "off" } + "IPv6: {}", + if tunnel_options.enable_ipv6 { + "on" + } else { + "off" + } ); + Ok(()) } - fn print_openvpn_tunnel_options(options: OpenVpnTunnelOptions) { - println!("OpenVPN tunnel options"); - println!( - "\tmssfix: {}", - options - .mssfix - .map_or_else(|| "UNSET".to_string(), |v| v.to_string()) - ); + fn process_ipv6_set(matches: &clap::ArgMatches) -> Result<()> { + let enabled = matches.value_of("enable").unwrap() == "on"; + + let mut rpc = new_rpc_client()?; + rpc.set_enable_ipv6(enabled)?; + println!("IPv6 setting has been updated"); + Ok(()) } } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 13ae19e7a7..058466b04e 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -53,8 +53,12 @@ use mullvad_rpc::{AccountsProxy, AppVersionProxy, HttpHandle}; use mullvad_types::{ account::{AccountData, AccountToken}, location::GeoIpLocation, - relay_constraints::{RelaySettings, RelaySettingsUpdate}, + relay_constraints::{ + Constraint, OpenVpnConstraints, RelayConstraintsUpdate, RelaySettings, RelaySettingsUpdate, + TunnelConstraints, + }, relay_list::{Relay, RelayList}, + settings, settings::Settings, states::TargetState, version::{AppVersion, AppVersionInfo}, @@ -64,7 +68,10 @@ use talpid_core::{ mpsc::IntoSender, tunnel_state_machine::{self, TunnelCommand, TunnelParameters, TunnelParametersGenerator}, }; -use talpid_types::tunnel::{BlockReason, TunnelStateTransition}; +use talpid_types::{ + net::{OpenVpnProxySettings, TransportProtocol}, + tunnel::{BlockReason, TunnelStateTransition}, +}; error_chain!{ @@ -369,7 +376,7 @@ impl Daemon { tunnel_parameters_tx .send(TunnelParameters { endpoint, - options: self.settings.get_tunnel_options(), + options: self.settings.get_tunnel_options().clone(), username: account_token, }) .map_err(|_| Error::from("Tunnel parameters receiver stopped listening")) @@ -417,6 +424,7 @@ impl Daemon { SetAllowLan(tx, allow_lan) => self.on_set_allow_lan(tx, allow_lan), SetAutoConnect(tx, auto_connect) => self.on_set_auto_connect(tx, auto_connect), SetOpenVpnMssfix(tx, mssfix_arg) => self.on_set_openvpn_mssfix(tx, mssfix_arg), + SetOpenVpnProxy(tx, proxy) => self.on_set_openvpn_proxy(tx, proxy), SetEnableIpv6(tx, enable_ipv6) => self.on_set_enable_ipv6(tx, enable_ipv6), GetSettings(tx) => self.on_get_settings(tx), GetVersionInfo(tx) => self.on_get_version_info(tx), @@ -619,6 +627,57 @@ impl Daemon { } } + fn on_set_openvpn_proxy( + &mut self, + tx: oneshot::Sender<::std::result::Result<(), settings::Error>>, + proxy: Option<OpenVpnProxySettings>, + ) { + let constraints_result = match proxy { + Some(_) => self.apply_proxy_constraints(), + _ => Ok(false), + }; + let proxy_result = self.settings.set_openvpn_proxy(proxy); + + match (proxy_result, constraints_result) { + (Ok(proxy_changed), Ok(constraints_changed)) => { + Self::oneshot_send(tx, Ok(()), "set_openvpn_proxy response"); + if proxy_changed || constraints_changed { + self.management_interface_broadcaster + .notify_settings(&self.settings); + info!("Initiating tunnel restart because the OpenVPN proxy setting changed"); + self.reconnect_tunnel(); + } + } + (Ok(_), Err(error)) | (Err(error), Ok(_)) => { + error!("{}", error.display_chain()); + Self::oneshot_send(tx, Err(error), "set_openvpn_proxy response"); + } + (Err(error), Err(_)) => { + error!("{}", error.display_chain()); + Self::oneshot_send(tx, Err(error), "set_openvpn_proxy response"); + } + } + } + + // Set the OpenVPN tunnel to use TCP. + fn apply_proxy_constraints(&mut self) -> settings::Result<bool> { + let openvpn_constraints = OpenVpnConstraints { + port: Constraint::Any, + protocol: Constraint::Only(TransportProtocol::Tcp), + }; + + let tunnel_constraints = TunnelConstraints::OpenVpn(openvpn_constraints); + + let constraints_update = RelayConstraintsUpdate { + location: None, + tunnel: Some(Constraint::Only(tunnel_constraints)), + }; + + let settings_update = RelaySettingsUpdate::Normal(constraints_update); + + self.settings.update_relay_settings(settings_update) + } + fn on_set_enable_ipv6(&mut self, tx: oneshot::Sender<()>, enable_ipv6: bool) { let save_result = self.settings.set_enable_ipv6(enable_ipv6); match save_result.chain_err(|| "Unable to save settings") { diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 07fd603e2a..3e204245ae 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -11,6 +11,7 @@ use mullvad_types::account::{AccountData, AccountToken}; use mullvad_types::location::GeoIpLocation; use mullvad_types::relay_constraints::RelaySettingsUpdate; use mullvad_types::relay_list::RelayList; +use mullvad_types::settings; use mullvad_types::settings::Settings; use mullvad_types::states::TargetState; use mullvad_types::version; @@ -24,7 +25,7 @@ use std::sync::{Arc, Mutex, RwLock}; use talpid_core::mpsc::IntoSender; use talpid_ipc; -use talpid_types::tunnel::TunnelStateTransition; +use talpid_types::{net::OpenVpnProxySettings, tunnel::TunnelStateTransition}; use uuid; use account_history::{AccountHistory, Error as AccountHistoryError}; @@ -101,6 +102,10 @@ build_rpc_trait! { #[rpc(meta, name = "set_openvpn_mssfix")] fn set_openvpn_mssfix(&self, Self::Metadata, Option<u16>) -> BoxFuture<(), Error>; + /// Sets proxy details for OpenVPN + #[rpc(meta, name = "set_openvpn_proxy")] + fn set_openvpn_proxy(&self, Self::Metadata, Option<OpenVpnProxySettings>) -> BoxFuture<(), Error>; + /// Set if IPv6 is enabled in the tunnel #[rpc(meta, name = "set_enable_ipv6")] fn set_enable_ipv6(&self, Self::Metadata, bool) -> BoxFuture<(), Error>; @@ -170,6 +175,11 @@ pub enum ManagementCommand { SetAutoConnect(OneshotSender<()>, bool), /// Set the mssfix argument for OpenVPN SetOpenVpnMssfix(OneshotSender<()>, Option<u16>), + /// Set proxy details for OpenVPN + SetOpenVpnProxy( + OneshotSender<Result<(), settings::Error>>, + Option<OpenVpnProxySettings>, + ), /// Set if IPv6 should be enabled in the tunnel SetEnableIpv6(OneshotSender<()>, bool), /// Get the daemon settings @@ -537,6 +547,28 @@ impl<T: From<ManagementCommand> + 'static + Send> ManagementInterfaceApi Box::new(future) } + fn set_openvpn_proxy( + &self, + _: Self::Metadata, + proxy: Option<OpenVpnProxySettings>, + ) -> BoxFuture<(), Error> { + log::debug!("set_openvpn_proxy({:?})", proxy); + let (tx, rx) = sync::oneshot::channel(); + let future = self + .send_command_to_daemon(ManagementCommand::SetOpenVpnProxy(tx, proxy)) + .and_then(|_| rx.map_err(|_| Error::internal_error())) + .and_then(|settings_result| { + settings_result.map_err(|err| match err.kind() { + settings::ErrorKind::InvalidProxyData(msg) => { + Error::invalid_params(msg.to_owned()) + } + _ => Error::internal_error(), + }) + }); + + Box::new(future) + } + fn set_enable_ipv6(&self, _: Self::Metadata, enable_ipv6: bool) -> BoxFuture<(), Error> { log::debug!("set_enable_ipv6({})", enable_ipv6); let (tx, rx) = sync::oneshot::channel(); diff --git a/mullvad-ipc-client/src/lib.rs b/mullvad-ipc-client/src/lib.rs index a98ab74f92..9b20899ab2 100644 --- a/mullvad-ipc-client/src/lib.rs +++ b/mullvad-ipc-client/src/lib.rs @@ -26,7 +26,7 @@ use mullvad_types::relay_list::RelayList; use mullvad_types::settings::Settings; use mullvad_types::version::AppVersionInfo; use serde::{Deserialize, Serialize}; -use talpid_types::net::TunnelOptions; +use talpid_types::net::{OpenVpnProxySettings, TunnelOptions}; use talpid_types::tunnel::TunnelStateTransition; use futures::stream::{self, Stream}; @@ -199,6 +199,10 @@ impl DaemonRpcClient { self.call("set_openvpn_mssfix", &[mssfix]) } + pub fn set_openvpn_proxy(&mut self, proxy: Option<OpenVpnProxySettings>) -> Result<()> { + self.call("set_openvpn_proxy", &[proxy]) + } + pub fn shutdown(&mut self) -> Result<()> { self.call("shutdown", &NO_ARGS) } diff --git a/mullvad-types/src/settings.rs b/mullvad-types/src/settings.rs index 34bc00e820..e78ab3c422 100644 --- a/mullvad-types/src/settings.rs +++ b/mullvad-types/src/settings.rs @@ -4,10 +4,11 @@ use log::{debug, info}; use relay_constraints::{ Constraint, LocationConstraint, RelayConstraints, RelaySettings, RelaySettingsUpdate, }; + use std::fs::File; use std::io; use std::path::PathBuf; -use talpid_types::net::TunnelOptions; +use talpid_types::net::{OpenVpnProxySettings, OpenVpnProxySettingsValidation, TunnelOptions}; error_chain! { errors { @@ -25,6 +26,10 @@ error_chain! { ParseError { description("Malformed settings") } + InvalidProxyData(reason: String) { + description("Invalid proxy configuration was rejected") + display("Invalid proxy configuration was rejected: {}", reason) + } } } @@ -182,6 +187,21 @@ impl Settings { } } + pub fn set_openvpn_proxy(&mut self, proxy: Option<OpenVpnProxySettings>) -> Result<bool> { + if let Some(ref settings) = proxy { + if let Err(validation_error) = OpenVpnProxySettingsValidation::validate(settings) { + bail!(ErrorKind::InvalidProxyData(validation_error)); + } + } + + if self.tunnel_options.openvpn.proxy != proxy { + self.tunnel_options.openvpn.proxy = proxy; + self.save().map(|_| true) + } else { + Ok(false) + } + } + pub fn set_enable_ipv6(&mut self, enable_ipv6: bool) -> Result<bool> { if self.tunnel_options.enable_ipv6 != enable_ipv6 { self.tunnel_options.enable_ipv6 = enable_ipv6; @@ -191,7 +211,7 @@ impl Settings { } } - pub fn get_tunnel_options(&self) -> TunnelOptions { - self.tunnel_options + pub fn get_tunnel_options(&self) -> &TunnelOptions { + &self.tunnel_options } } diff --git a/talpid-core/src/process/openvpn.rs b/talpid-core/src/process/openvpn.rs index 761d4e6206..014e475524 100644 --- a/talpid-core/src/process/openvpn.rs +++ b/talpid-core/src/process/openvpn.rs @@ -46,6 +46,7 @@ pub struct OpenVpnCommand { config: Option<PathBuf>, remote: Option<net::Endpoint>, user_pass_path: Option<PathBuf>, + proxy_auth_path: Option<PathBuf>, ca: Option<PathBuf>, crl: Option<PathBuf>, iproute_bin: Option<OsString>, @@ -65,6 +66,7 @@ impl OpenVpnCommand { config: None, remote: None, user_pass_path: None, + proxy_auth_path: None, ca: None, crl: None, iproute_bin: None, @@ -95,6 +97,13 @@ impl OpenVpnCommand { self } + /// Sets the path to the file where the username and password for proxy authentication + /// is stored. + pub fn proxy_auth<P: AsRef<Path>>(&mut self, path: P) -> &mut Self { + self.proxy_auth_path = Some(path.as_ref().to_path_buf()); + self + } + /// Sets the path to the CA certificate file. pub fn ca<P: AsRef<Path>>(&mut self, path: P) -> &mut Self { self.ca = Some(path.as_ref().to_path_buf()); @@ -133,7 +142,7 @@ impl OpenVpnCommand { /// Sets extra options pub fn tunnel_options(&mut self, tunnel_options: &net::OpenVpnTunnelOptions) -> &mut Self { - self.tunnel_options = *tunnel_options; + self.tunnel_options = tunnel_options.clone(); self } @@ -208,6 +217,7 @@ impl OpenVpnCommand { } args.extend(Self::security_arguments().iter().map(OsString::from)); + args.extend(self.proxy_arguments().iter().map(OsString::from)); args } @@ -252,6 +262,41 @@ impl OpenVpnCommand { } args } + + fn proxy_arguments(&self) -> Vec<String> { + let mut args = vec![]; + match self.tunnel_options.proxy { + Some(net::OpenVpnProxySettings::Local(ref local_proxy)) => { + args.push("--socks-proxy".to_owned()); + args.push("127.0.0.1".to_owned()); + args.push(local_proxy.port.to_string()); + args.push("--route".to_owned()); + args.push(local_proxy.peer.ip().to_string()); + args.push("255.255.255.255".to_owned()); + args.push("net_gateway".to_owned()); + } + Some(net::OpenVpnProxySettings::Remote(ref remote_proxy)) => { + args.push("--socks-proxy".to_owned()); + args.push(remote_proxy.address.ip().to_string()); + args.push(remote_proxy.address.port().to_string()); + + if let Some(ref _auth) = remote_proxy.auth { + if let Some(ref auth_file) = self.proxy_auth_path { + args.push(auth_file.to_string_lossy().to_string()); + } else { + log::error!("Proxy credentials present but credentials file missing"); + } + } + + args.push("--route".to_owned()); + args.push(remote_proxy.address.ip().to_string()); + args.push("255.255.255.255".to_owned()); + args.push("net_gateway".to_owned()); + } + None => {} + }; + args + } } impl fmt::Display for OpenVpnCommand { diff --git a/talpid-core/src/security/linux/mod.rs b/talpid-core/src/security/linux/mod.rs index 83b0816988..9641830f2a 100644 --- a/talpid-core/src/security/linux/mod.rs +++ b/talpid-core/src/security/linux/mod.rs @@ -227,18 +227,18 @@ impl<'a> PolicyBatch<'a> { fn add_policy_specific_rules(&mut self, policy: &SecurityPolicy) -> Result<()> { let allow_lan = match policy { SecurityPolicy::Connecting { - relay_endpoint, + peer_endpoint, allow_lan, } => { - self.add_allow_endpoint_rules(relay_endpoint)?; + self.add_allow_endpoint_rules(peer_endpoint)?; *allow_lan } SecurityPolicy::Connected { - relay_endpoint, + peer_endpoint, tunnel, allow_lan, } => { - self.add_allow_endpoint_rules(relay_endpoint)?; + self.add_allow_endpoint_rules(peer_endpoint)?; self.add_dns_rule(tunnel, TransportProtocol::Udp)?; self.add_dns_rule(tunnel, TransportProtocol::Tcp)?; self.add_allow_tunnel_rules(tunnel)?; diff --git a/talpid-core/src/security/macos/mod.rs b/talpid-core/src/security/macos/mod.rs index f83567f91e..d588d21177 100644 --- a/talpid-core/src/security/macos/mod.rs +++ b/talpid-core/src/security/macos/mod.rs @@ -87,17 +87,17 @@ impl NetworkSecurity { ) -> Result<Vec<pfctl::FilterRule>> { match policy { SecurityPolicy::Connecting { - relay_endpoint, + peer_endpoint, allow_lan, } => { - let mut rules = vec![Self::get_allow_relay_rule(relay_endpoint)?]; + let mut rules = vec![Self::get_allow_relay_rule(peer_endpoint)?]; if allow_lan { rules.append(&mut Self::get_allow_lan_rules()?); } Ok(rules) } SecurityPolicy::Connected { - relay_endpoint, + peer_endpoint, tunnel, allow_lan, } => { @@ -139,7 +139,7 @@ impl NetworkSecurity { allow_udp_dns_to_relay_rule, block_tcp_dns_rule, block_udp_dns_rule, - Self::get_allow_relay_rule(relay_endpoint)?, + Self::get_allow_relay_rule(peer_endpoint)?, Self::get_allow_tunnel_rule(tunnel.interface.as_str())?, ]; diff --git a/talpid-core/src/security/mod.rs b/talpid-core/src/security/mod.rs index a18ecc26da..6e8149e87b 100644 --- a/talpid-core/src/security/mod.rs +++ b/talpid-core/src/security/mod.rs @@ -43,18 +43,18 @@ lazy_static! { /// A enum that describes network security strategy #[derive(Debug, Clone, Eq, PartialEq)] pub enum SecurityPolicy { - /// Allow traffic only to relay server + /// Allow traffic only to server Connecting { - /// The relay endpoint that should be allowed. - relay_endpoint: Endpoint, + /// The peer endpoint that should be allowed. + peer_endpoint: Endpoint, /// Flag setting if communication with LAN networks should be possible. allow_lan: bool, }, - /// Allow traffic only to relay server and over tunnel interface + /// Allow traffic only to server and over tunnel interface Connected { - /// The relay endpoint that should be allowed. - relay_endpoint: Endpoint, + /// The peer endpoint that should be allowed. + peer_endpoint: Endpoint, /// Metadata about the tunnel and tunnel interface. tunnel: ::tunnel::TunnelMetadata, /// Flag setting if communication with LAN networks should be possible. @@ -72,22 +72,22 @@ impl fmt::Display for SecurityPolicy { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self { SecurityPolicy::Connecting { - relay_endpoint, + peer_endpoint, allow_lan, } => write!( f, "Connecting to {}, {} LAN", - relay_endpoint, + peer_endpoint, if *allow_lan { "Allowing" } else { "Blocking" } ), SecurityPolicy::Connected { - relay_endpoint, + peer_endpoint, tunnel, allow_lan, } => write!( f, "Connected to {} over \"{}\" (ip: {}, gw: {}), {} LAN", - relay_endpoint, + peer_endpoint, tunnel.interface, tunnel.ip, tunnel.gateway, diff --git a/talpid-core/src/security/windows/mod.rs b/talpid-core/src/security/windows/mod.rs index 75961aa69f..65590f6f19 100644 --- a/talpid-core/src/security/windows/mod.rs +++ b/talpid-core/src/security/windows/mod.rs @@ -87,19 +87,19 @@ impl NetworkSecurityT for NetworkSecurity { fn apply_policy(&mut self, policy: SecurityPolicy) -> Result<()> { match policy { SecurityPolicy::Connecting { - relay_endpoint, + peer_endpoint, allow_lan, } => { let cfg = &WinFwSettings::new(allow_lan); - self.set_connecting_state(&relay_endpoint, &cfg) + self.set_connecting_state(&peer_endpoint, &cfg) } SecurityPolicy::Connected { - relay_endpoint, + peer_endpoint, tunnel, allow_lan, } => { let cfg = &WinFwSettings::new(allow_lan); - self.set_connected_state(&relay_endpoint, &cfg, &tunnel) + self.set_connected_state(&peer_endpoint, &cfg, &tunnel) } SecurityPolicy::Blocked { allow_lan } => { let cfg = &WinFwSettings::new(allow_lan); diff --git a/talpid-core/src/tunnel/mod.rs b/talpid-core/src/tunnel/mod.rs index 3084341af5..beb6b78116 100644 --- a/talpid-core/src/tunnel/mod.rs +++ b/talpid-core/src/tunnel/mod.rs @@ -8,13 +8,16 @@ use std::fs; use std::io::{self, Write}; use std::net::Ipv4Addr; use std::path::{Path, PathBuf}; +use std::result::Result as StdResult; #[cfg(target_os = "linux")] use failure::ResultExt as FailureResultExt; #[cfg(target_os = "linux")] use which; -use talpid_types::net::{Endpoint, TunnelEndpoint, TunnelEndpointData, TunnelOptions}; +use talpid_types::net::{ + Endpoint, OpenVpnProxySettings, TunnelEndpoint, TunnelEndpointData, TunnelOptions, +}; /// A module for all OpenVPN related tunnel management. pub mod openvpn; @@ -141,6 +144,8 @@ pub struct TunnelMonitor { monitor: OpenVpnMonitor, /// Keep the `TempFile` for the user-pass file in the struct, so it's removed on drop. _user_pass_file: mktemp::TempFile, + /// Keep the 'TempFile' for the proxy user-pass file in the struct, so it's removed on drop. + _proxy_auth_file: Option<mktemp::TempFile>, } impl TunnelMonitor { @@ -161,22 +166,41 @@ impl TunnelMonitor { Self::ensure_endpoint_is_openvpn(&tunnel_endpoint)?; Self::ensure_ipv6_can_be_used_if_enabled(tunnel_options)?; - let user_pass_file = - Self::create_user_pass_file(username).chain_err(|| ErrorKind::CredentialsWriteError)?; + let user_pass_file = Self::create_credentials_file(username, "-") + .chain_err(|| ErrorKind::CredentialsWriteError)?; + + let proxy_auth_file = Self::create_proxy_auth_file(&tunnel_options.openvpn.proxy) + .chain_err(|| ErrorKind::CredentialsWriteError)?; + let cmd = Self::create_openvpn_cmd( tunnel_endpoint.to_endpoint(), tunnel_alias, &tunnel_options, user_pass_file.as_ref(), + match proxy_auth_file { + Some(ref file) => Some(file.as_ref()), + _ => None, + }, log, resource_dir, )?; let user_pass_file_path = user_pass_file.to_path_buf(); + + let proxy_auth_file_path = match proxy_auth_file { + Some(ref file) => Some(file.to_path_buf()), + _ => None, + }; + let on_openvpn_event = move |event, env| { if event == OpenVpnPluginEvent::Up { // The user-pass file has been read. Try to delete it early. let _ = fs::remove_file(&user_pass_file_path); + + // The proxy auth file has been read. Try to delete it early. + if let Some(ref file_path) = &proxy_auth_file_path { + let _ = fs::remove_file(file_path); + } } match TunnelEvent::from_openvpn_event(event, &env) { Some(tunnel_event) => on_event(tunnel_event), @@ -193,6 +217,7 @@ impl TunnelMonitor { Ok(TunnelMonitor { monitor, _user_pass_file: user_pass_file, + _proxy_auth_file: proxy_auth_file, }) } @@ -216,6 +241,7 @@ impl TunnelMonitor { tunnel_alias: Option<OsString>, options: &TunnelOptions, user_pass_file: &Path, + proxy_auth_file: Option<&Path>, log: Option<&Path>, resource_dir: &Path, ) -> Result<OpenVpnCommand> { @@ -239,6 +265,10 @@ impl TunnelMonitor { if let Some(log) = log { cmd.log(log); } + if let Some(proxy_auth_file) = proxy_auth_file { + cmd.proxy_auth(proxy_auth_file); + } + Ok(cmd) } @@ -271,18 +301,29 @@ impl TunnelMonitor { } } - fn create_user_pass_file(username: &str) -> io::Result<mktemp::TempFile> { + fn create_credentials_file(username: &str, password: &str) -> io::Result<mktemp::TempFile> { let temp_file = mktemp::TempFile::new(); - log::debug!( - "Writing user-pass credentials to {}", - temp_file.as_ref().display() - ); + log::debug!("Writing credentials to {}", temp_file.as_ref().display()); let mut file = fs::File::create(&temp_file)?; Self::set_user_pass_file_permissions(&file)?; - write!(file, "{}\n-\n", username)?; + write!(file, "{}\n{}\n", username, password)?; Ok(temp_file) } + fn create_proxy_auth_file( + proxy: &Option<OpenVpnProxySettings>, + ) -> StdResult<Option<mktemp::TempFile>, io::Error> { + if let Some(OpenVpnProxySettings::Remote(ref remote_proxy)) = proxy { + if let Some(ref proxy_auth) = remote_proxy.auth { + return Ok(Some(Self::create_credentials_file( + &proxy_auth.username, + &proxy_auth.password, + )?)); + } + } + Ok(None) + } + #[cfg(unix)] fn set_user_pass_file_permissions(file: &fs::File) -> io::Result<()> { use std::os::unix::fs::PermissionsExt; diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index c9004556c5..abb5c3cad5 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -1,6 +1,7 @@ use error_chain::ChainedError; use futures::sync::{mpsc, oneshot}; use futures::{Async, Future, Stream}; +use talpid_types::net::{Endpoint, OpenVpnProxySettings, TransportProtocol}; use talpid_types::tunnel::BlockReason; use super::{ @@ -40,8 +41,21 @@ impl ConnectedState { } fn set_security_policy(&self, shared_values: &mut SharedTunnelStateValues) -> Result<()> { + // If a proxy is specified we need to pass it on as the peer endpoint. + let peer_endpoint = match self.tunnel_parameters.options.openvpn.proxy { + Some(OpenVpnProxySettings::Local(ref local_proxy)) => Endpoint { + address: local_proxy.peer, + protocol: TransportProtocol::Tcp, + }, + Some(OpenVpnProxySettings::Remote(ref remote_proxy)) => Endpoint { + address: remote_proxy.address, + protocol: TransportProtocol::Tcp, + }, + _ => self.tunnel_parameters.endpoint.to_endpoint(), + }; + let policy = SecurityPolicy::Connected { - relay_endpoint: self.tunnel_parameters.endpoint.to_endpoint(), + peer_endpoint, tunnel: self.metadata.clone(), allow_lan: shared_values.allow_lan, }; diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index cc30d88ac9..873eaa9933 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -7,7 +7,9 @@ use error_chain::ChainedError; use futures::sync::{mpsc, oneshot}; use futures::{Async, Future, Stream}; use log::{debug, error, info, trace, warn}; -use talpid_types::net::{TunnelEndpoint, TunnelEndpointData}; +use talpid_types::net::{ + Endpoint, OpenVpnProxySettings, TransportProtocol, TunnelEndpoint, TunnelEndpointData, +}; use talpid_types::tunnel::BlockReason; use super::{ @@ -53,10 +55,24 @@ pub struct ConnectingState { impl ConnectingState { fn set_security_policy( shared_values: &mut SharedTunnelStateValues, + proxy: &Option<OpenVpnProxySettings>, endpoint: TunnelEndpoint, ) -> Result<()> { + // If a proxy is specified we need to pass it on as the peer endpoint. + let peer_endpoint = match proxy { + Some(OpenVpnProxySettings::Local(ref local_proxy)) => Endpoint { + address: local_proxy.peer, + protocol: TransportProtocol::Tcp, + }, + Some(OpenVpnProxySettings::Remote(ref remote_proxy)) => Endpoint { + address: remote_proxy.address, + protocol: TransportProtocol::Tcp, + }, + _ => endpoint.to_endpoint(), + }; + let policy = SecurityPolicy::Connecting { - relay_endpoint: endpoint.to_endpoint(), + peer_endpoint, allow_lan: shared_values.allow_lan, }; shared_values @@ -172,7 +188,11 @@ impl ConnectingState { match try_handle_event!(self, commands.poll()) { Ok(TunnelCommand::AllowLan(allow_lan)) => { shared_values.allow_lan = allow_lan; - match Self::set_security_policy(shared_values, self.tunnel_parameters.endpoint) { + match Self::set_security_policy( + shared_values, + &self.tunnel_parameters.options.openvpn.proxy, + self.tunnel_parameters.endpoint, + ) { Ok(()) => SameState(self), Err(error) => { error!("{}", error.display_chain()); @@ -284,8 +304,11 @@ impl TunnelState for ConnectingState { None => BlockedState::enter(shared_values, BlockReason::NoMatchingRelay), Some(tunnel_parameters) => { let tunnel_endpoint = tunnel_parameters.endpoint; - - if let Err(error) = Self::set_security_policy(shared_values, tunnel_endpoint) { + if let Err(error) = Self::set_security_policy( + shared_values, + &tunnel_parameters.options.openvpn.proxy, + tunnel_endpoint, + ) { error!("{}", error.display_chain()); BlockedState::enter(shared_values, BlockReason::StartTunnelError) } else { diff --git a/talpid-types/src/net.rs b/talpid-types/src/net.rs index e871e7a93e..8798ecf1d9 100644 --- a/talpid-types/src/net.rs +++ b/talpid-types/src/net.rs @@ -161,7 +161,7 @@ impl Error for TransportProtocolParseError { /// TunnelOptions holds optional settings for tunnels, that are to be applied to any tunnel of the /// appropriate type. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(default)] pub struct TunnelOptions { /// openvpn holds OpenVPN specific tunnel options. @@ -184,10 +184,68 @@ impl Default for TunnelOptions { /// OpenVpnTunnelOptions contains options for an openvpn tunnel that should be applied irrespective /// of the relay parameters - i.e. have nothing to do with the particular OpenVPN server, but do /// affect the connection. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] #[serde(default)] pub struct OpenVpnTunnelOptions { /// Optional argument for openvpn to try and limit TCP packet size, /// as discussed [here](https://openvpn.net/archive/openvpn-users/2003-11/msg00154.html) pub mssfix: Option<u16>, + /// Proxy settings, for when the relay connection should be via a proxy. + pub proxy: Option<OpenVpnProxySettings>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum OpenVpnProxySettings { + Local(LocalOpenVpnProxySettings), + Remote(RemoteOpenVpnProxySettings), +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] +pub struct LocalOpenVpnProxySettings { + pub port: u16, + pub peer: SocketAddr, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] +pub struct RemoteOpenVpnProxySettings { + pub address: SocketAddr, + pub auth: Option<OpenVpnProxyAuth>, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] +pub struct OpenVpnProxyAuth { + pub username: String, + pub password: String, +} + +pub struct OpenVpnProxySettingsValidation; + +impl OpenVpnProxySettingsValidation { + pub fn validate(proxy: &OpenVpnProxySettings) -> Result<(), String> { + match proxy { + OpenVpnProxySettings::Local(local) => { + if local.port == 0 { + return Err(String::from("Invalid local port number")); + } + if local.peer.ip().is_loopback() { + return Err(String::from( + "localhost is not a valid peer in this context", + )); + } + if local.peer.port() == 0 { + return Err(String::from("Invalid remote port number")); + } + } + OpenVpnProxySettings::Remote(remote) => { + if remote.address.port() == 0 { + return Err(String::from("Invalid port number")); + } + if remote.address.ip().is_loopback() { + return Err(String::from("localhost is not a valid remote server")); + } + } + }; + Ok(()) + } } |
