summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--gui/packages/desktop/src/renderer/lib/daemon-rpc.js41
-rw-r--r--mullvad-cli/src/cmds/tunnel.rs370
-rw-r--r--mullvad-daemon/src/lib.rs65
-rw-r--r--mullvad-daemon/src/management_interface.rs34
-rw-r--r--mullvad-ipc-client/src/lib.rs6
-rw-r--r--mullvad-types/src/settings.rs26
-rw-r--r--talpid-core/src/process/openvpn.rs47
-rw-r--r--talpid-core/src/security/linux/mod.rs8
-rw-r--r--talpid-core/src/security/macos/mod.rs8
-rw-r--r--talpid-core/src/security/mod.rs20
-rw-r--r--talpid-core/src/security/windows/mod.rs8
-rw-r--r--talpid-core/src/tunnel/mod.rs59
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs16
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs33
-rw-r--r--talpid-types/src/net.rs62
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(())
+ }
}