diff options
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/relay.rs | 208 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/status.rs | 15 | ||||
| -rw-r--r-- | mullvad-daemon/Cargo.toml | 5 | ||||
| -rw-r--r-- | mullvad-daemon/src/bin/list-relays.rs | 29 | ||||
| -rw-r--r-- | mullvad-daemon/src/main.rs | 215 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 91 | ||||
| -rw-r--r-- | mullvad-daemon/src/relays.rs | 330 | ||||
| -rw-r--r-- | mullvad-daemon/src/settings.rs | 40 | ||||
| -rw-r--r-- | mullvad-rpc/src/lib.rs | 14 | ||||
| -rw-r--r-- | mullvad-types/src/relay_constraints.rs | 133 | ||||
| -rw-r--r-- | talpid-types/src/net.rs | 2 |
12 files changed, 829 insertions, 254 deletions
diff --git a/Cargo.lock b/Cargo.lock index 717f93bddc..69ceab80f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -691,6 +691,7 @@ dependencies = [ "talpid-core 0.1.0", "talpid-ipc 0.1.0", "talpid-types 0.1.0", + "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index 817ae17b3a..4f81af9192 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -2,11 +2,15 @@ use {Command, Result, ResultExt}; use clap; use std::str::FromStr; -use mullvad_types::relay_constraints::{Constraint, OpenVpnConstraintsUpdate, RelayConstraints, - RelayConstraintsUpdate, TunnelConstraintsUpdate}; +use mullvad_types::CustomTunnelEndpoint; +use mullvad_types::relay_constraints::{Constraint, LocationConstraint, OpenVpnConstraints, + RelayConstraintsUpdate, RelaySettings, RelaySettingsUpdate, + TunnelConstraints}; +use mullvad_types::relay_list::RelayList; use rpc; -use talpid_types::net::TransportProtocol; +use talpid_types::net::{OpenVpnParameters, TransportProtocol, TunnelParameters, + WireguardParameters}; pub struct Relay; @@ -23,26 +27,77 @@ impl Command for Relay { clap::SubCommand::with_name("set") .setting(clap::AppSettings::SubcommandRequired) .subcommand( - clap::SubCommand::with_name("host") - .about("Set host") - .arg(clap::Arg::with_name("host").required(true)), + clap::SubCommand::with_name("custom") + .about("Set a custom VPN relay") + .arg( + clap::Arg::with_name("tunnel") + .required(true) + .index(1) + .possible_values(&["openvpn", "wireguard"]), + ) + .arg( + clap::Arg::with_name("host") + .help("Hostname or IP") + .required(true) + .index(2), + ) + .arg( + clap::Arg::with_name("port") + .help("Remote network port") + .required(true) + .index(3), + ) + .arg( + clap::Arg::with_name("protocol") + .help("Transport protocol. For Wireguard this is ignored.") + .index(4) + .default_value("udp") + .possible_values(&["udp", "tcp"]), + ), ) .subcommand( - clap::SubCommand::with_name("port") - .about("Set port") - .arg(clap::Arg::with_name("port").required(true)), + clap::SubCommand::with_name("location") + .about( + "Set country or city to select relays from. Use the 'list' \ + command to show available alternatives.", + ) + .arg( + clap::Arg::with_name("country") + .help( + "The two letter country code, or 'any' for no preference.", + ) + .required(true) + .index(1) + .validator(country_code_validator), + ) + .arg( + clap::Arg::with_name("city") + .help("The three letter city code") + .index(2) + .validator(city_code_validator), + ), ) .subcommand( - clap::SubCommand::with_name("protocol") - .about("Set protocol") + clap::SubCommand::with_name("tunnel") + .about("Set tunnel constraints") + .arg(clap::Arg::with_name("port").required(true).index(1)) .arg( clap::Arg::with_name("protocol") .required(true) + .index(2) .possible_values(&["any", "udp", "tcp"]), ), ), ) .subcommand(clap::SubCommand::with_name("get")) + .subcommand( + clap::SubCommand::with_name("list") + .setting(clap::AppSettings::SubcommandRequired) + .subcommand( + clap::SubCommand::with_name("locations") + .about("List available countries and cities"), + ), + ) } fn run(&self, matches: &clap::ArgMatches) -> Result<()> { @@ -50,6 +105,8 @@ impl Command for Relay { self.set(set_matches) } else if let Some(_) = matches.subcommand_matches("get") { self.get() + } else if let Some(list_matches) = matches.subcommand_matches("list") { + self.list(list_matches) } else { unreachable!("No relay command given"); } @@ -57,57 +114,98 @@ impl Command for Relay { } impl Relay { - fn update_constraints(&self, constraints_update: RelayConstraintsUpdate) -> Result<()> { - rpc::call("update_relay_constraints", &[constraints_update]) + fn update_constraints(&self, update: RelaySettingsUpdate) -> Result<()> { + rpc::call("update_relay_settings", &[update]) .map(|_: Option<()>| println!("Relay constraints updated")) } fn set(&self, matches: &clap::ArgMatches) -> Result<()> { - if let Some(host_matches) = matches.subcommand_matches("host") { - let host = value_t_or_exit!(host_matches.value_of("host"), String); - - self.update_constraints(RelayConstraintsUpdate { - host: Some(Constraint::Only(host)), - tunnel: TunnelConstraintsUpdate::OpenVpn(OpenVpnConstraintsUpdate { - port: None, - protocol: None, - }), - }) - } else if let Some(port_matches) = matches.subcommand_matches("port") { - let port = parse_port(port_matches.value_of("port").unwrap())?; - - self.update_constraints(RelayConstraintsUpdate { - host: None, - tunnel: TunnelConstraintsUpdate::OpenVpn(OpenVpnConstraintsUpdate { - port: Some(port), - protocol: None, - }), - }) - } else if let Some(protocol_matches) = matches.subcommand_matches("protocol") { - let protocol = parse_protocol(protocol_matches.value_of("protocol").unwrap()); - - self.update_constraints(RelayConstraintsUpdate { - host: None, - tunnel: TunnelConstraintsUpdate::OpenVpn(OpenVpnConstraintsUpdate { - port: None, - protocol: Some(protocol), - }), - }) + if let Some(custom_matches) = matches.subcommand_matches("custom") { + self.set_custom(custom_matches) + } else if let Some(location_matches) = matches.subcommand_matches("location") { + self.set_location(location_matches) + } else if let Some(tunnel_matches) = matches.subcommand_matches("tunnel") { + self.set_tunnel(tunnel_matches) } else { unreachable!("No set relay command given"); } } + fn set_custom(&self, matches: &clap::ArgMatches) -> Result<()> { + let host = value_t!(matches.value_of("host"), String).unwrap_or_else(|e| e.exit()); + let port = value_t!(matches.value_of("port"), u16).unwrap_or_else(|e| e.exit()); + let tunnel = match matches.value_of("tunnel").unwrap() { + "openvpn" => TunnelParameters::OpenVpn(OpenVpnParameters { + port, + protocol: value_t!(matches.value_of("protocol"), TransportProtocol).unwrap(), + }), + "wireguard" => TunnelParameters::Wireguard(WireguardParameters { port }), + _ => unreachable!("Invalid tunnel protocol"), + }; + self.update_constraints(RelaySettingsUpdate::CustomTunnelEndpoint( + CustomTunnelEndpoint { host, tunnel }, + )) + } + + fn set_location(&self, matches: &clap::ArgMatches) -> Result<()> { + let country = matches.value_of("country").unwrap(); + let city = matches.value_of("city"); + + let location_constraint = match (country, city) { + ("any", None) => Constraint::Any, + ("any", _) => clap::Error::with_description( + "City can't be given when selecting 'any' country", + clap::ErrorKind::InvalidValue, + ).exit(), + (country, None) => Constraint::Only(LocationConstraint::Country(country.to_owned())), + (country, Some(city)) => Constraint::Only(LocationConstraint::City( + country.to_owned(), + city.to_owned(), + )), + }; + + self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { + location: Some(location_constraint), + tunnel: None, + })) + } + + fn set_tunnel(&self, matches: &clap::ArgMatches) -> Result<()> { + let port = parse_port_constraint(matches.value_of("port").unwrap())?; + let protocol = parse_protocol_constraint(matches.value_of("protocol").unwrap()); + + self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { + location: None, + tunnel: Some(Constraint::Only(TunnelConstraints::OpenVpn( + OpenVpnConstraints { port, protocol }, + ))), + })) + } + fn get(&self) -> Result<()> { - let constraints: RelayConstraints = rpc::call("get_relay_constraints", &[] as &[u8; 0])?; - println!("Current constraints: {:?}", constraints); + let constraints: RelaySettings = rpc::call("get_relay_settings", &[] as &[u8; 0])?; + println!("Current constraints: {:#?}", constraints); Ok(()) } + + fn list(&self, _matches: &clap::ArgMatches) -> Result<()> { + let mut locations: RelayList = rpc::call("get_relay_locations", &[] as &[u8; 0])?; + locations.countries.sort_by(|c1, c2| c1.name.cmp(&c2.name)); + for mut country in locations.countries { + country.cities.sort_by(|c1, c2| c1.name.cmp(&c2.name)); + println!("{} ({})", country.name, country.code); + for city in &country.cities { + println!("\t{} ({}) @ {:?}", city.name, city.code, city.position); + } + println!(""); + } + Ok(()) + } } -fn parse_port(raw_port: &str) -> Result<Constraint<u16>> { +fn parse_port_constraint(raw_port: &str) -> Result<Constraint<u16>> { match raw_port.to_lowercase().as_str() { "any" => Ok(Constraint::Any), port => Ok(Constraint::Only( @@ -118,7 +216,7 @@ fn parse_port(raw_port: &str) -> Result<Constraint<u16>> { /// Parses a protocol constraint string. Can be infallible because the possible values are limited /// with clap. -fn parse_protocol(raw_protocol: &str) -> Constraint<TransportProtocol> { +fn parse_protocol_constraint(raw_protocol: &str) -> Constraint<TransportProtocol> { match raw_protocol.to_lowercase().as_str() { "any" => Constraint::Any, "udp" => Constraint::Only(TransportProtocol::Udp), @@ -126,3 +224,19 @@ fn parse_protocol(raw_protocol: &str) -> Constraint<TransportProtocol> { _ => unreachable!(), } } + +fn country_code_validator(code: String) -> ::std::result::Result<(), String> { + if code.len() == 2 || code == "any" { + Ok(()) + } else { + Err(String::from("Country codes must be two letters, or 'any'.")) + } +} + +fn city_code_validator(code: String) -> ::std::result::Result<(), String> { + if code.len() == 3 { + Ok(()) + } else { + Err(String::from("City codes must be three letters")) + } +} diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs index e02773285f..7690c6d8eb 100644 --- a/mullvad-cli/src/cmds/status.rs +++ b/mullvad-cli/src/cmds/status.rs @@ -2,9 +2,12 @@ use Command; use Result; use clap; +use mullvad_types::location::Location; use mullvad_types::states::{DaemonState, SecurityState, TargetState}; use rpc; +use std::net::IpAddr; + pub struct Status; impl Command for Status { @@ -18,12 +21,24 @@ impl Command for Status { fn run(&self, _matches: &clap::ArgMatches) -> Result<()> { let state: DaemonState = rpc::call("get_state", &[] as &[u8; 0])?; + print!("Tunnel status: "); match (state.state, state.target_state) { (SecurityState::Unsecured, TargetState::Unsecured) => println!("Disconnected"), (SecurityState::Unsecured, TargetState::Secured) => println!("Connecting..."), (SecurityState::Secured, TargetState::Unsecured) => println!("Disconnecting..."), (SecurityState::Secured, TargetState::Secured) => println!("Connected"), } + + let location: Location = rpc::call("get_current_location", &[] as &[u8; 0])?; + println!("Location: {}, {}", location.city, location.country); + println!( + "Position: {:.5}°N, {:.5}°W", + location.position[0], + location.position[1] + ); + + let ip: IpAddr = rpc::call("get_public_ip", &[] as &[u8; 0])?; + println!("IP: {}", ip); Ok(()) } } diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index 7971054df0..82ae9b021a 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -14,6 +14,10 @@ path = "src/main.rs" name = "problem-report" path = "src/bin/problem-report.rs" +[[bin]] +name = "list-relays" +path = "src/bin/list-relays.rs" + [dependencies] app_dirs = "1.1" chrono = { version = "0.4", features = ["serde"] } @@ -32,6 +36,7 @@ jsonrpc-ws-server = { git = "https://github.com/paritytech/jsonrpc", tag = "v7.1 uuid = { version = "0.5", features = ["v4"] } lazy_static = "0.2" rand = "0.3" +tokio-timer = "0.1" mullvad-types = { path = "../mullvad-types" } mullvad-rpc = { path = "../mullvad-rpc" } diff --git a/mullvad-daemon/src/bin/list-relays.rs b/mullvad-daemon/src/bin/list-relays.rs new file mode 100644 index 0000000000..2718c05396 --- /dev/null +++ b/mullvad-daemon/src/bin/list-relays.rs @@ -0,0 +1,29 @@ +//! # License +//! +//! Copyright (C) 2017 Amagicom AB +//! +//! This program is free software: you can redistribute it and/or modify it under the terms of the +//! GNU General Public License as published by the Free Software Foundation, either version 3 of +//! the License, or (at your option) any later version. + +#[macro_use] +extern crate error_chain; + +extern crate mullvad_rpc; +extern crate serde_json; + +error_chain!{} + +quick_main!(run); + +fn run() -> Result<()> { + let rpc_http_handle = mullvad_rpc::connect().chain_err(|| "Unable to connect RPC")?; + let mut client = mullvad_rpc::RelayListProxy::new(rpc_http_handle); + + let relays = client + .relay_list() + .call() + .chain_err(|| "Error during RPC call")?; + println!("{}", serde_json::to_string_pretty(&relays).unwrap()); + Ok(()) +} diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs index b5ec71b2a3..c9493d3778 100644 --- a/mullvad-daemon/src/main.rs +++ b/mullvad-daemon/src/main.rs @@ -6,6 +6,7 @@ //! GNU General Public License as published by the Free Software Foundation, either version 3 of //! the License, or (at your option) any later version. + extern crate app_dirs; extern crate chrono; #[macro_use] @@ -20,6 +21,7 @@ extern crate log; extern crate serde; #[macro_use] extern crate serde_derive; +extern crate serde_json; extern crate jsonrpc_core; #[macro_use] @@ -29,6 +31,7 @@ extern crate jsonrpc_ws_server; #[macro_use] extern crate lazy_static; extern crate rand; +extern crate tokio_timer; extern crate uuid; extern crate mullvad_rpc; @@ -39,28 +42,31 @@ extern crate talpid_types; mod cli; mod management_interface; +mod relays; mod rpc_info; mod settings; mod shutdown; mod account_history; + use app_dirs::AppInfo; use error_chain::ChainedError; use futures::Future; use jsonrpc_core::futures::sync::oneshot::Sender as OneshotSender; + use management_interface::{BoxFuture, ManagementInterfaceServer, TunnelCommand}; use mullvad_rpc::{AccountsProxy, HttpHandle}; -use mullvad_types::CustomTunnelEndpoint; + use mullvad_types::account::{AccountData, AccountToken}; -use mullvad_types::relay_constraints::{Constraint, OpenVpnConstraints, RelayConstraints, - RelayConstraintsUpdate, TunnelConstraints}; +use mullvad_types::location::Location; +use mullvad_types::relay_constraints::{RelaySettings, RelaySettingsUpdate}; +use mullvad_types::relay_list::{Relay, RelayList}; use mullvad_types::states::{DaemonState, SecurityState, TargetState}; -use rand::Rng; use std::env; use std::io; -use std::net::Ipv4Addr; -use std::path::PathBuf; +use std::net::{IpAddr, Ipv4Addr}; +use std::path::{Path, PathBuf}; use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; @@ -68,13 +74,8 @@ use std::time::{Duration, Instant}; use talpid_core::firewall::{Firewall, FirewallProxy, SecurityPolicy}; use talpid_core::mpsc::IntoSender; use talpid_core::tunnel::{self, TunnelEvent, TunnelMetadata, TunnelMonitor}; -use talpid_types::net::{Endpoint, OpenVpnParameters, TransportProtocol, TunnelEndpoint, - TunnelParameters}; +use talpid_types::net::TunnelEndpoint; -pub static APP_INFO: AppInfo = AppInfo { - name: ::CRATE_NAME, - author: "Mullvad", -}; error_chain!{ errors { @@ -105,20 +106,18 @@ error_chain!{ } lazy_static! { - // Temporary store of hardcoded relays. - static ref RELAYS: [Endpoint; 3] = [ - // se2.mullvad.net - Endpoint::new(Ipv4Addr::new(185, 213, 152, 132), 1300, TransportProtocol::Udp), - // se5.mullvad.net - Endpoint::new(Ipv4Addr::new(193, 138, 218, 135), 1300, TransportProtocol::Udp), - // se23.mullvad.net - Endpoint::new(Ipv4Addr::new(185, 65, 135, 143), 1300, TransportProtocol::Udp), - ]; static ref MIN_TUNNEL_ALIVE_TIME_MS: Duration = Duration::from_millis(1000); + static ref MAX_RELAY_CACHE_AGE: Duration = Duration::from_secs(3600); + static ref RELAY_CACHE_UPDATE_TIMEOUT: Duration = Duration::from_millis(3000); } const CRATE_NAME: &str = "mullvadd"; +static APP_INFO: AppInfo = AppInfo { + name: CRATE_NAME, + author: "Mullvad", +}; + const DATE_TIME_FORMAT_STR: &str = "%Y-%m-%d %H:%M:%S%.3f"; @@ -188,24 +187,26 @@ struct Daemon { management_interface_broadcaster: management_interface::EventBroadcaster, settings: settings::Settings, accounts_proxy: AccountsProxy<HttpHandle>, + relay_selector: relays::RelaySelector, firewall: FirewallProxy, - relay_endpoint: Option<TunnelEndpoint>, + current_relay: Option<Relay>, + tunnel_endpoint: Option<TunnelEndpoint>, tunnel_metadata: Option<TunnelMetadata>, tunnel_log: Option<PathBuf>, - - // Just for testing. A cyclic iterator iterating over the hardcoded relays, - // picking a new one for each retry. - relay_iter: std::iter::Cycle<std::iter::Cloned<std::slice::Iter<'static, Endpoint>>>, resource_dir: PathBuf, } impl Daemon { pub fn new(tunnel_log: Option<PathBuf>) -> Result<Self> { + let resource_dir = get_resource_dir(); + let rpc_http_handle = mullvad_rpc::connect().chain_err(|| "Unable to connect to RPC API")?; + + let relay_selector = Self::create_relay_selector(rpc_http_handle.clone(), &resource_dir)?; + let (tx, rx) = mpsc::channel(); let management_interface_broadcaster = Self::start_management_interface(tx.clone())?; let state = TunnelState::NotRunning; let target_state = TargetState::Unsecured; - let resource_dir = get_resource_dir(); Ok(Daemon { state, tunnel_close_handle: None, @@ -219,16 +220,33 @@ impl Daemon { tx, management_interface_broadcaster, settings: settings::Settings::load().chain_err(|| "Unable to read settings")?, - accounts_proxy: AccountsProxy::connect().chain_err(|| "Unable to connect RPC client")?, + accounts_proxy: AccountsProxy::new(rpc_http_handle), + relay_selector, firewall: FirewallProxy::new().chain_err(|| ErrorKind::FirewallError)?, - relay_endpoint: None, + current_relay: None, + tunnel_endpoint: None, tunnel_metadata: None, tunnel_log: tunnel_log, - relay_iter: RELAYS.iter().cloned().cycle(), resource_dir, }) } + fn create_relay_selector( + rpc_http_handle: mullvad_rpc::HttpHandle, + resource_dir: &Path, + ) -> Result<relays::RelaySelector> { + let mut relay_selector = relays::RelaySelector::new(rpc_http_handle, &resource_dir) + .chain_err(|| "Unable to initialize relay list cache")?; + if let Ok(elapsed) = relay_selector.get_last_updated().elapsed() { + if elapsed > *MAX_RELAY_CACHE_AGE { + if let Err(e) = relay_selector.update(*RELAY_CACHE_UPDATE_TIMEOUT) { + error!("Unable to update relay cache: {}", e.display_chain()); + } + } + } + Ok(relay_selector) + } + // Starts the management interface and spawns a thread that will process it. // Returns a handle that allows notifying all subscribers on events. fn start_management_interface( @@ -314,7 +332,8 @@ impl Daemon { if let Err(e) = result.chain_err(|| "Tunnel exited in an unexpected way") { error!("{}", e.display_chain()); } - self.relay_endpoint = None; + self.current_relay = None; + self.tunnel_endpoint = None; self.tunnel_metadata = None; self.tunnel_close_handle = None; self.set_state(TunnelState::NotRunning) @@ -329,13 +348,14 @@ impl Daemon { match event { SetTargetState(state) => self.on_set_target_state(state), GetState(tx) => Ok(self.on_get_state(tx)), + GetPublicIp(tx) => Ok(self.on_get_ip(tx)), + GetCurrentLocation(tx) => Ok(self.on_get_current_location(tx)), GetAccountData(tx, account_token) => Ok(self.on_get_account_data(tx, account_token)), + GetRelayLocations(tx) => Ok(self.on_get_relay_locations(tx)), SetAccount(tx, account_token) => self.on_set_account(tx, account_token), GetAccount(tx) => Ok(self.on_get_account(tx)), - UpdateRelayConstraints(tx, constraints_update) => { - self.on_update_relay_constraints(tx, constraints_update) - } - GetRelayConstraints(tx) => Ok(self.on_get_relay_constraints(tx)), + UpdateRelaySettings(tx, update) => self.on_update_relay_settings(tx, update), + GetRelaySettings(tx) => Ok(self.on_get_relay_settings(tx)), Shutdown => self.handle_trigger_shutdown_event(), } } @@ -353,6 +373,30 @@ impl Daemon { Self::oneshot_send(tx, self.last_broadcasted_state, "current state"); } + fn on_get_ip(&self, tx: OneshotSender<IpAddr>) { + let ip = if let Some(ref relay) = self.current_relay { + IpAddr::V4(relay.ipv4_addr_exit) + } else { + IpAddr::V4(Ipv4Addr::new(1, 3, 3, 7)) + }; + Self::oneshot_send(tx, ip, "current ip"); + } + + fn on_get_current_location(&self, tx: OneshotSender<Location>) { + let location = if let Some(ref relay) = self.current_relay { + relay.location.as_ref().cloned().unwrap() + } else { + Location { + country: String::from("Narnia"), + country_code: String::from("na"), + city: String::from("Le City"), + city_code: String::from("le"), + position: [13.37, 0.0], + } + }; + Self::oneshot_send(tx, location, "current location"); + } + fn on_get_account_data( &mut self, tx: OneshotSender<BoxFuture<AccountData, mullvad_rpc::Error>>, @@ -364,6 +408,14 @@ impl Daemon { Self::oneshot_send(tx, Box::new(rpc_call), "account data") } + fn on_get_relay_locations(&mut self, tx: OneshotSender<RelayList>) { + Self::oneshot_send( + tx, + self.relay_selector.get_locations().clone(), + "relay locations", + ); + } + fn on_set_account( &mut self, @@ -391,22 +443,22 @@ impl Daemon { Self::oneshot_send(tx, self.settings.get_account_token(), "current account") } - fn on_update_relay_constraints( + fn on_update_relay_settings( &mut self, tx: OneshotSender<()>, - constraints: RelayConstraintsUpdate, + update: RelaySettingsUpdate, ) -> Result<()> { - let save_result = self.settings.update_relay_constraints(constraints); + let save_result = self.settings.update_relay_settings(update); match save_result.chain_err(|| "Unable to save settings") { - Ok(constraints_changed) => { - Self::oneshot_send(tx, (), "update_relay_constraints response"); + Ok(changed) => { + Self::oneshot_send(tx, (), "update_relay_settings response"); let tunnel_needs_restart = self.state == TunnelState::Connecting || self.state == TunnelState::Connected; - if constraints_changed && tunnel_needs_restart { - info!("Initiating tunnel restart because the relay constraints changed"); + if changed && tunnel_needs_restart { + info!("Initiating tunnel restart because the relay settings changed"); self.kill_tunnel()?; } } @@ -416,12 +468,8 @@ impl Daemon { Ok(()) } - fn on_get_relay_constraints(&self, tx: OneshotSender<RelayConstraints>) { - Self::oneshot_send( - tx, - self.settings.get_relay_constraints(), - "relay constraints", - ) + fn on_get_relay_settings(&self, tx: OneshotSender<RelaySettings>) { + Self::oneshot_send(tx, self.settings.get_relay_settings(), "relay settings") } fn oneshot_send<T>(tx: OneshotSender<T>, t: T, msg: &'static str) { @@ -505,7 +553,8 @@ impl Daemon { debug!("Triggering tunnel start"); if let Err(e) = self.start_tunnel().chain_err(|| "Failed to start tunnel") { error!("{}", e.display_chain()); - self.relay_endpoint = None; + self.current_relay = None; + self.tunnel_endpoint = None; self.management_interface_broadcaster.notify_error(&e); self.set_target_state(TargetState::Unsecured)?; } @@ -524,16 +573,30 @@ impl Daemon { ErrorKind::InvalidState ); - let relay_endpoint = self.get_relay().chain_err(|| ErrorKind::NoRelay)?; + match self.settings.get_relay_settings() { + RelaySettings::CustomTunnelEndpoint(custom_relay) => { + let tunnel_endpoint = custom_relay + .to_tunnel_endpoint() + .chain_err(|| ErrorKind::NoRelay)?; + self.tunnel_endpoint = Some(tunnel_endpoint); + } + RelaySettings::Normal(constraints) => { + let (relay, tunnel_endpoint) = self.relay_selector + .get_tunnel_endpoint(&constraints) + .chain_err(|| ErrorKind::NoRelay)?; + self.tunnel_endpoint = Some(tunnel_endpoint); + self.current_relay = Some(relay); + } + } let account_token = self.settings .get_account_token() .ok_or(ErrorKind::InvalidSettings("No account token"))?; - self.relay_endpoint = Some(relay_endpoint); self.set_security_policy()?; - let tunnel_monitor = self.spawn_tunnel_monitor(relay_endpoint, &account_token)?; + let tunnel_monitor = + self.spawn_tunnel_monitor(self.tunnel_endpoint.unwrap(), &account_token)?; self.tunnel_close_handle = Some(tunnel_monitor.close_handle()); self.spawn_tunnel_monitor_wait_thread(tunnel_monitor); @@ -541,40 +604,6 @@ impl Daemon { Ok(()) } - fn get_relay(&mut self) -> Result<TunnelEndpoint> { - let relay_constraints = self.settings.get_relay_constraints(); - - let host = match relay_constraints.host { - Constraint::Any => format!("{}", self.relay_iter.next().unwrap().address.ip()), - Constraint::Only(host) => host, - }; - - match relay_constraints.tunnel { - TunnelConstraints::OpenVpn(constraints) => self.get_openvpn_relay(host, constraints), - } - } - - fn get_openvpn_relay( - &mut self, - host: String, - constraints: OpenVpnConstraints, - ) -> Result<TunnelEndpoint> { - let protocol = match constraints.protocol { - Constraint::Any => TransportProtocol::Udp, - Constraint::Only(protocol) => protocol, - }; - let port = match constraints.port { - Constraint::Any => randomize_port(protocol), - Constraint::Only(port) => port, - }; - - CustomTunnelEndpoint { - host, - tunnel: TunnelParameters::OpenVpn(OpenVpnParameters { port, protocol }), - }.to_tunnel_endpoint() - .chain_err(|| "Unable to construct a valid relay") - } - fn spawn_tunnel_monitor( &self, tunnel_endpoint: TunnelEndpoint, @@ -633,7 +662,7 @@ impl Daemon { } fn set_security_policy(&mut self) -> Result<()> { - let policy = match (self.relay_endpoint, self.tunnel_metadata.as_ref()) { + let policy = match (self.tunnel_endpoint, self.tunnel_metadata.as_ref()) { (Some(relay), None) => SecurityPolicy::Connecting(relay.to_endpoint()), (Some(relay), Some(tunnel_metadata)) => { SecurityPolicy::Connected(relay.to_endpoint(), tunnel_metadata.clone()) @@ -736,18 +765,6 @@ fn log_version() { ) } -fn randomize_port(protocol: TransportProtocol) -> u16 { - let pool: Vec<u16> = if protocol == TransportProtocol::Udp { - vec![1194, 1195, 1196, 1197, 1300, 1301, 1302] - } else { - vec![80, 443] - }; - - *rand::thread_rng() - .choose(&pool) - .expect("no ports to randomize from") -} - fn get_resource_dir() -> PathBuf { match env::current_exe() { Ok(mut path) => { diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 95e5c99833..1f56d5ba0d 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -9,16 +9,17 @@ use jsonrpc_pubsub::{PubSubHandler, PubSubMetadata, Session, SubscriptionId}; use jsonrpc_ws_server; use mullvad_rpc; use mullvad_types::account::{AccountData, AccountToken}; -use mullvad_types::location::{CountryCode, Location}; +use mullvad_types::location::Location; -use mullvad_types::relay_constraints::{RelayConstraints, RelayConstraintsUpdate}; +use mullvad_types::relay_constraints::{RelaySettings, RelaySettingsUpdate}; +use mullvad_types::relay_list::RelayList; use mullvad_types::states::{DaemonState, TargetState}; use serde; use std::collections::HashMap; use std::collections::hash_map::Entry; -use std::net::{IpAddr, Ipv4Addr}; +use std::net::IpAddr; use std::sync::{Arc, Mutex, RwLock}; use std::sync::atomic::{AtomicBool, Ordering}; use talpid_core::mpsc::IntoSender; @@ -47,8 +48,8 @@ build_rpc_trait! { fn get_account_data(&self, Self::Metadata, AccountToken) -> BoxFuture<AccountData, Error>; /// Returns available countries. - #[rpc(name = "get_countries")] - fn get_countries(&self) -> Result<HashMap<CountryCode, String>, Error>; + #[rpc(meta, name = "get_relay_locations")] + fn get_relay_locations(&self, Self::Metadata) -> BoxFuture<RelayList, Error>; /// Set which account to connect with. #[rpc(meta, name = "set_account")] @@ -59,18 +60,18 @@ build_rpc_trait! { fn get_account(&self, Self::Metadata) -> BoxFuture<Option<AccountToken>, Error>; /// Update constraints put on the type of tunnel connection to use - #[rpc(meta, name = "update_relay_constraints")] - fn update_relay_constraints( + #[rpc(meta, name = "update_relay_settings")] + fn update_relay_settings( &self, - Self::Metadata, RelayConstraintsUpdate + Self::Metadata, RelaySettingsUpdate ) -> BoxFuture<(), Error>; /// Update constraints put on the type of tunnel connection to use - #[rpc(meta, name = "get_relay_constraints")] - fn get_relay_constraints( + #[rpc(meta, name = "get_relay_settings")] + fn get_relay_settings( &self, Self::Metadata - ) -> BoxFuture<RelayConstraints, Error>; + ) -> BoxFuture<RelaySettings, Error>; /// Set if the client should automatically establish a tunnel on start or not. #[rpc(meta, name = "set_autoconnect")] @@ -91,13 +92,13 @@ build_rpc_trait! { fn get_state(&self, Self::Metadata) -> BoxFuture<DaemonState, Error>; /// Returns the current public IP of this computer. - #[rpc(name = "get_ip")] - fn get_ip(&self) -> Result<IpAddr, Error>; + #[rpc(meta, name = "get_public_ip")] + fn get_public_ip(&self, Self::Metadata) -> BoxFuture<IpAddr, Error>; /// Performs a geoIP lookup and returns the current location as perceived by the public /// internet. - #[rpc(name = "get_location")] - fn get_location(&self) -> Result<Location, Error>; + #[rpc(meta, name = "get_current_location")] + fn get_current_location(&self, Self::Metadata) -> BoxFuture<Location, Error>; /// Makes the daemon exit its main loop and quit. #[rpc(meta, name = "shutdown")] @@ -140,19 +141,25 @@ pub enum TunnelCommand { SetTargetState(TargetState), /// Request the current state. GetState(OneshotSender<DaemonState>), + /// Get the current IP as viewed from the internet. + GetPublicIp(OneshotSender<IpAddr>), + /// Get the current geographical location. + GetCurrentLocation(OneshotSender<Location>), /// Request the metadata for an account. GetAccountData( OneshotSender<BoxFuture<AccountData, mullvad_rpc::Error>>, AccountToken, ), + /// Get the list of countries and cities where there are relays. + GetRelayLocations(OneshotSender<RelayList>), /// Set which account token to use for subsequent connection attempts. SetAccount(OneshotSender<()>, Option<AccountToken>), /// Request the current account token being used. GetAccount(OneshotSender<Option<AccountToken>>), /// Place constraints on the type of tunnel and relay - UpdateRelayConstraints(OneshotSender<()>, RelayConstraintsUpdate), + UpdateRelaySettings(OneshotSender<()>, RelaySettingsUpdate), /// Read the constraints put on the tunnel and relay - GetRelayConstraints(OneshotSender<RelayConstraints>), + GetRelaySettings(OneshotSender<RelaySettings>), /// Makes the daemon exit the main loop and quit. Shutdown, } @@ -373,9 +380,13 @@ impl<T: From<TunnelCommand> + 'static + Send> ManagementInterfaceApi for Managem Box::new(future) } - fn get_countries(&self) -> Result<HashMap<CountryCode, String>, Error> { - trace!("get_countries"); - Ok(HashMap::new()) + fn get_relay_locations(&self, meta: Self::Metadata) -> BoxFuture<RelayList, Error> { + trace!("get_relay_locations"); + try_future!(self.check_auth(&meta)); + let (tx, rx) = sync::oneshot::channel(); + let future = self.send_command_to_daemon(TunnelCommand::GetRelayLocations(tx)) + .and_then(|_| rx.map_err(|_| Error::internal_error())); + Box::new(future) } fn set_account( @@ -413,26 +424,26 @@ impl<T: From<TunnelCommand> + 'static + Send> ManagementInterfaceApi for Managem Box::new(future) } - fn update_relay_constraints( + fn update_relay_settings( &self, meta: Self::Metadata, - constraints_update: RelayConstraintsUpdate, + constraints_update: RelaySettingsUpdate, ) -> BoxFuture<(), Error> { - trace!("update_relay_constraints"); + trace!("update_relay_settings"); try_future!(self.check_auth(&meta)); let (tx, rx) = sync::oneshot::channel(); - let message = TunnelCommand::UpdateRelayConstraints(tx, constraints_update); + let message = TunnelCommand::UpdateRelaySettings(tx, constraints_update); let future = self.send_command_to_daemon(message) .and_then(|_| rx.map_err(|_| Error::internal_error())); Box::new(future) } - fn get_relay_constraints(&self, meta: Self::Metadata) -> BoxFuture<RelayConstraints, Error> { - trace!("get_relay_constraints"); + fn get_relay_settings(&self, meta: Self::Metadata) -> BoxFuture<RelaySettings, Error> { + trace!("get_relay_settings"); try_future!(self.check_auth(&meta)); let (tx, rx) = sync::oneshot::channel(); - let future = self.send_command_to_daemon(TunnelCommand::GetRelayConstraints(tx)) + let future = self.send_command_to_daemon(TunnelCommand::GetRelaySettings(tx)) .and_then(|_| rx.map_err(|_| Error::internal_error())); Box::new(future) } @@ -464,20 +475,22 @@ impl<T: From<TunnelCommand> + 'static + Send> ManagementInterfaceApi for Managem Box::new(future) } - fn get_ip(&self) -> Result<IpAddr, Error> { - trace!("get_ip"); - Ok(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4))) + fn get_public_ip(&self, meta: Self::Metadata) -> BoxFuture<IpAddr, Error> { + trace!("get_public_ip"); + try_future!(self.check_auth(&meta)); + let (tx, rx) = sync::oneshot::channel(); + let future = self.send_command_to_daemon(TunnelCommand::GetPublicIp(tx)) + .and_then(|_| rx.map_err(|_| Error::internal_error())); + Box::new(future) } - fn get_location(&self) -> Result<Location, Error> { - trace!("get_location"); - Ok(Location { - country: String::from("narnia"), - country_code: String::from("na"), - city: String::from("Le city"), - city_code: String::from("le"), - position: [1.0, 2.0], - }) + fn get_current_location(&self, meta: Self::Metadata) -> BoxFuture<Location, Error> { + trace!("get_current_location"); + try_future!(self.check_auth(&meta)); + let (tx, rx) = sync::oneshot::channel(); + let future = self.send_command_to_daemon(TunnelCommand::GetCurrentLocation(tx)) + .and_then(|_| rx.map_err(|_| Error::internal_error())); + Box::new(future) } fn shutdown(&self, meta: Self::Metadata) -> BoxFuture<(), Error> { diff --git a/mullvad-daemon/src/relays.rs b/mullvad-daemon/src/relays.rs new file mode 100644 index 0000000000..b15e32ae07 --- /dev/null +++ b/mullvad-daemon/src/relays.rs @@ -0,0 +1,330 @@ +use app_dirs; +use chrono::{DateTime, Local}; +use error_chain::ChainedError; +use futures::Future; + +use mullvad_rpc::{HttpHandle, RelayListProxy}; +use mullvad_types::location::Location; +use mullvad_types::relay_constraints::{Constraint, LocationConstraint, Match, OpenVpnConstraints, + RelayConstraints, TunnelConstraints}; +use mullvad_types::relay_list::{Relay, RelayList, RelayTunnels}; + +use serde_json; + +use talpid_types::net::{TransportProtocol, TunnelEndpoint, TunnelParameters}; + +use std::fs::File; +use std::io; +use std::net::IpAddr; +use std::path::{Path, PathBuf}; +use std::time::{Duration, SystemTime}; + +use rand::{self, Rng, ThreadRng}; +use rand::distributions::{IndependentSample, Range}; +use tokio_timer::{TimeoutError, Timer}; + + +error_chain! { + errors { + RelayCacheError { description("Error with relay cache on disk") } + DownloadError { description("Error when trying to download the list of relays") } + TimeoutError { description("Timed out when trying to download the list of relays") } + NoRelay { description("No relays matching current constraints") } + SerializationError { description("Error in serialization of relaylist") } + } +} + +impl<F> From<TimeoutError<F>> for Error { + fn from(_: TimeoutError<F>) -> Error { + Error::from_kind(ErrorKind::TimeoutError) + } +} + +pub struct RelaySelector { + locations: RelayList, + relays: Vec<Relay>, + last_updated: SystemTime, + rng: ThreadRng, + rpc_client: RelayListProxy<HttpHandle>, +} + +impl RelaySelector { + /// Returns a new `RelaySelector` backed by relays cached on disk. Use the `update` method + /// to refresh the relay list from the internet. + pub fn new(rpc_handle: HttpHandle, resource_dir: &Path) -> Result<Self> { + let (last_updated, relay_list) = Self::read_cached_relays(resource_dir)?; + let (locations, relays) = Self::process_relay_list(relay_list); + debug!( + "Initialized with {} cached relays from {}", + relays.len(), + DateTime::<Local>::from(last_updated).format(::DATE_TIME_FORMAT_STR) + ); + Ok(RelaySelector { + locations, + relays, + last_updated, + rng: rand::thread_rng(), + rpc_client: RelayListProxy::new(rpc_handle), + }) + } + + /// Returns all countries and cities. The cities in the object returned does not have any + /// relays in them. + pub fn get_locations(&mut self) -> &RelayList { + &self.locations + } + + /// Returns the time when the relay list backing this selector was last fetched from the + /// internet. + pub fn get_last_updated(&self) -> SystemTime { + self.last_updated + } + + /// Returns a random relay and relay endpoint matching the given constraints and with + /// preferences applied. + pub fn get_tunnel_endpoint( + &mut self, + constraints: &RelayConstraints, + ) -> Result<(Relay, TunnelEndpoint)> { + // Highest priority preference. Where we prefer OpenVPN using UDP. But without changing + // any constraints that are explicitly specified. + let tunnel_constraints1 = match constraints.tunnel { + Constraint::Any => TunnelConstraints::OpenVpn(OpenVpnConstraints { + port: Constraint::Any, + protocol: Constraint::Only(TransportProtocol::Udp), + }), + Constraint::Only(TunnelConstraints::OpenVpn(ref openvpn_constraints)) => { + TunnelConstraints::OpenVpn(OpenVpnConstraints { + port: openvpn_constraints.port.clone(), + protocol: Constraint::Only( + openvpn_constraints + .protocol + .clone() + .unwrap_or(TransportProtocol::Udp), + ), + }) + } + Constraint::Only(ref tunnel_constraints) => tunnel_constraints.clone(), + }; + let relay_constraints1 = RelayConstraints { + location: constraints.location.clone(), + tunnel: Constraint::Only(tunnel_constraints1), + }; + + if let Some((relay, endpoint)) = self.get_tunnel_endpoint_internal(&relay_constraints1) { + debug!("Relay matched on highest preference"); + Ok((relay, endpoint)) + } else if let Some((relay, endpoint)) = self.get_tunnel_endpoint_internal(constraints) { + debug!("Relay matched on second preference"); + Ok((relay, endpoint)) + } else { + bail!(ErrorKind::NoRelay); + } + } + + /// Returns a random relay endpoint if any is matching the given constraints. + fn get_tunnel_endpoint_internal( + &mut self, + constraints: &RelayConstraints, + ) -> Option<(Relay, TunnelEndpoint)> { + let matching_relays: Vec<Relay> = self.relays + .iter() + .filter_map(|relay| Self::matching_relay(relay, constraints)) + .collect(); + + self.pick_random_relay(&matching_relays) + .and_then(|selected_relay| { + info!( + "Selected relay {} at {}", + selected_relay.hostname, + selected_relay.ipv4_addr_in + ); + self.get_random_tunnel(&selected_relay.tunnels) + .map(|tunnel_parameters| { + let endpoint = TunnelEndpoint { + address: IpAddr::V4(selected_relay.ipv4_addr_in), + tunnel: tunnel_parameters, + }; + (selected_relay.clone(), endpoint) + }) + }) + } + + /// Takes a `Relay` and a corresponding `RelayConstraints` and returns a new `Relay` if the + /// given relay matches the constraints. + fn matching_relay(relay: &Relay, constraints: &RelayConstraints) -> Option<Relay> { + let matches_location = match constraints.location { + Constraint::Any => true, + Constraint::Only(LocationConstraint::Country(ref country)) => { + relay + .location + .as_ref() + .map_or(false, |loc| loc.country_code == *country) + && relay.include_in_country + } + Constraint::Only(LocationConstraint::City(ref country, ref city)) => { + relay.location.as_ref().map_or(false, |loc| { + loc.country_code == *country && loc.city_code == *city + }) + } + }; + if !matches_location { + return None; + } + let relay = match constraints.tunnel { + Constraint::Any => relay.clone(), + Constraint::Only(ref tunnel_constraints) => { + let mut relay = relay.clone(); + relay.tunnels = Self::matching_tunnels(&relay.tunnels, tunnel_constraints); + relay + } + }; + if relay.tunnels.openvpn.is_empty() { + None + } else { + Some(relay) + } + } + + /// Takes a `RelayTunnels` object which in turn is a collection of tunnel configurations for + /// a given relay. Then returns a new `RelayTunnels` instance with only the entries that + /// matches the given `TunnelConstraints`. + fn matching_tunnels( + tunnels: &RelayTunnels, + tunnel_constraints: &TunnelConstraints, + ) -> RelayTunnels { + RelayTunnels { + openvpn: tunnels + .openvpn + .iter() + .filter(|endpoint| tunnel_constraints.matches(*endpoint)) + .cloned() + .collect(), + wireguard: tunnels + .wireguard + .iter() + .filter(|endpoint| tunnel_constraints.matches(*endpoint)) + .cloned() + .collect(), + } + } + + /// Pick a random relay from the given slice. Will return `None` if the given slice is empty + /// or all relays in it has zero weight. + fn pick_random_relay<'a>(&mut self, relays: &'a [Relay]) -> Option<&'a Relay> { + let total_weight: u64 = relays.iter().map(|relay| relay.weight).sum(); + debug!( + "Selecting among {} relays with combined weight {}", + relays.len(), + total_weight + ); + if total_weight == 0 { + None + } else { + // Pick a random number in the range 0 - total_weight. This choses the relay. + let mut i: u64 = Range::new(0, total_weight + 1).ind_sample(&mut self.rng); + Some( + relays + .iter() + .find(|relay| { + i = i.saturating_sub(relay.weight); + i == 0 + }) + .unwrap(), + ) + } + } + + fn get_random_tunnel(&mut self, tunnels: &RelayTunnels) -> Option<TunnelParameters> { + self.rng + .choose(&tunnels.openvpn) + .cloned() + .map(|openvpn_endpoint| { + TunnelParameters::OpenVpn(openvpn_endpoint) + }) + } + + /// Downloads the latest relay list and caches it. This operation is blocking. + pub fn update(&mut self, timeout: Duration) -> Result<()> { + info!("Downloading list of relays"); + let download_future = self.rpc_client + .relay_list() + .map_err(|e| Error::with_chain(e, ErrorKind::DownloadError)); + let relay_list = Timer::default().timeout(download_future, timeout).wait()?; + if let Err(e) = Self::cache_relays(&relay_list) { + error!("Unable to save relays to cache: {}", e.display_chain()); + } + let (locations, relays) = Self::process_relay_list(relay_list); + debug!("Downloaded relay inventory has {} relays", relays.len()); + self.locations = locations; + self.relays = relays; + self.last_updated = SystemTime::now(); + Ok(()) + } + + // Extracts all relays from their corresponding cities and return them as a separate vector. + fn process_relay_list(mut relay_list: RelayList) -> (RelayList, Vec<Relay>) { + let mut relays = Vec::new(); + for country in &mut relay_list.countries { + let country_name = country.name.clone(); + let country_code = country.code.clone(); + for city in &mut country.cities { + let city_name = city.name.clone(); + let city_code = city.code.clone(); + let position = city.position; + relays.extend(city.relays.drain(..).map(|mut relay| { + relay.location = Some(Location { + country: country_name.clone(), + country_code: country_code.clone(), + city: city_name.clone(), + city_code: city_code.clone(), + position, + }); + relay + })); + } + } + (relay_list, relays) + } + + /// Write a `RelayList` to the cache file. + fn cache_relays(relays: &RelayList) -> Result<()> { + let file = File::create(Self::get_cache_path()?).chain_err(|| ErrorKind::RelayCacheError)?; + serde_json::to_writer_pretty(file, relays).chain_err(|| ErrorKind::SerializationError) + } + + /// Try to read the relays, first from cache and if that fails from the `resource_dir`. + fn read_cached_relays(resource_dir: &Path) -> Result<(SystemTime, RelayList)> { + match Self::get_cache_path().and_then(|path| Self::read_relays(&path)) { + Ok(value) => Ok(value), + Err(read_cache_error) => match Self::read_relays(&resource_dir.join("relays.json")) { + Ok(value) => Ok(value), + Err(read_resource_error) => Err(read_cache_error.chain_err(|| read_resource_error)), + }, + } + } + + /// Read and deserialize a `RelayList` from a given path. + /// Returns the file modification time and the relays. + fn read_relays(path: &Path) -> Result<(SystemTime, RelayList)> { + debug!( + "Trying to read relays cache from {}", + path.to_string_lossy() + ); + let (last_modified, file) = Self::read_file(path).chain_err(|| ErrorKind::RelayCacheError)?; + let relay_list = serde_json::from_reader(file).chain_err(|| ErrorKind::SerializationError)?; + Ok((last_modified, relay_list)) + } + + fn read_file(path: &Path) -> io::Result<(SystemTime, File)> { + let file = File::open(path)?; + let last_modified = file.metadata()?.modified()?; + Ok((last_modified, file)) + } + + fn get_cache_path() -> Result<PathBuf> { + let dir = app_dirs::app_root(app_dirs::AppDataType::UserCache, &::APP_INFO) + .chain_err(|| ErrorKind::RelayCacheError)?; + Ok(dir.join("relays.json")) + } +} diff --git a/mullvad-daemon/src/settings.rs b/mullvad-daemon/src/settings.rs index 4053c36c19..3bb120c544 100644 --- a/mullvad-daemon/src/settings.rs +++ b/mullvad-daemon/src/settings.rs @@ -1,9 +1,10 @@ extern crate serde_json; -use app_dirs::{self, AppDataType}; +use app_dirs; + +use mullvad_types::relay_constraints::{Constraint, RelayConstraints, RelaySettings, + RelaySettingsUpdate}; -use mullvad_types::relay_constraints::{Constraint, OpenVpnConstraints, RelayConstraints, - RelayConstraintsUpdate, TunnelConstraints}; use std::fs::File; use std::io; use std::path::PathBuf; @@ -33,7 +34,7 @@ static SETTINGS_FILE: &str = "settings.json"; #[serde(default)] pub struct Settings { account_token: Option<String>, - relay_constraints: RelayConstraints, + relay_settings: RelaySettings, } impl Default for Settings { @@ -44,13 +45,10 @@ impl Default for Settings { const DEFAULT_SETTINGS: Settings = Settings { account_token: None, - relay_constraints: RelayConstraints { - host: Constraint::Any, - tunnel: TunnelConstraints::OpenVpn(OpenVpnConstraints { - port: Constraint::Any, - protocol: Constraint::Any, - }), - }, + relay_settings: RelaySettings::Normal(RelayConstraints { + location: Constraint::Any, + tunnel: Constraint::Any, + }), }; impl Settings { @@ -84,7 +82,7 @@ impl Settings { } fn get_settings_path() -> Result<PathBuf> { - let dir = app_dirs::app_root(AppDataType::UserConfig, &::APP_INFO) + let dir = app_dirs::app_root(app_dirs::AppDataType::UserConfig, &::APP_INFO) .chain_err(|| ErrorKind::DirectoryError)?; Ok(dir.join(SETTINGS_FILE)) } @@ -119,20 +117,20 @@ impl Settings { } } - pub fn get_relay_constraints(&self) -> RelayConstraints { - self.relay_constraints.clone() + pub fn get_relay_settings(&self) -> RelaySettings { + self.relay_settings.clone() } - pub fn update_relay_constraints(&mut self, update: RelayConstraintsUpdate) -> Result<bool> { - let new_constraints = self.relay_constraints.merge(update); - if self.relay_constraints != new_constraints { + pub fn update_relay_settings(&mut self, update: RelaySettingsUpdate) -> Result<bool> { + let new_settings = self.relay_settings.merge(update); + if self.relay_settings != new_settings { debug!( - "changing relay constraints from {:?} to {:?}", - self.relay_constraints, - new_constraints + "changing relay settings from {:?} to {:?}", + self.relay_settings, + new_settings ); - self.relay_constraints = new_constraints; + self.relay_settings = new_settings; self.save().map(|_| true) } else { Ok(false) diff --git a/mullvad-rpc/src/lib.rs b/mullvad-rpc/src/lib.rs index f0d32ad9ee..bfbf4c6ab6 100644 --- a/mullvad-rpc/src/lib.rs +++ b/mullvad-rpc/src/lib.rs @@ -36,13 +36,6 @@ jsonrpc_client!(pub struct AccountsProxy { pub fn get_expiry(&mut self, account_token: AccountToken) -> RpcRequest<DateTime<Utc>>; }); -impl AccountsProxy<HttpHandle> { - pub fn connect() -> Result<Self, HttpError> { - let transport = HttpTransport::new()?.handle(MASTER_API_URI)?; - Ok(AccountsProxy::new(transport)) - } -} - jsonrpc_client!(pub struct ProblemReportProxy { pub fn problem_report(&mut self, email: &str, message: &str, log: &str) -> RpcRequest<()>; }); @@ -57,10 +50,3 @@ impl ProblemReportProxy<HttpHandle> { jsonrpc_client!(pub struct RelayListProxy { pub fn relay_list(&mut self) -> RpcRequest<RelayList>; }); - -impl RelayListProxy<HttpHandle> { - pub fn connect() -> Result<Self, HttpError> { - let transport = HttpTransport::new()?.handle(MASTER_API_URI)?; - Ok(RelayListProxy::new(transport)) - } -} diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs index aa55e55c22..3d446b2541 100644 --- a/mullvad-types/src/relay_constraints.rs +++ b/mullvad-types/src/relay_constraints.rs @@ -1,6 +1,14 @@ +use CustomTunnelEndpoint; +use location::{CityCode, CountryCode}; + use std::fmt; -use talpid_types::net::TransportProtocol; +use talpid_types::net::{OpenVpnParameters, TransportProtocol, WireguardParameters}; + + +pub trait Match<T> { + fn matches(&self, other: &T) -> bool; +} #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] @@ -9,6 +17,15 @@ pub enum Constraint<T: fmt::Debug + Clone + Eq + PartialEq> { Only(T), } +impl<T: fmt::Debug + Clone + Eq + PartialEq> Constraint<T> { + pub fn unwrap_or(self, other: T) -> T { + match self { + Constraint::Any => other, + Constraint::Only(value) => value, + } + } +} + impl<T: fmt::Debug + Clone + Eq + PartialEq> Default for Constraint<T> { fn default() -> Self { Constraint::Any @@ -17,19 +34,56 @@ impl<T: fmt::Debug + Clone + Eq + PartialEq> Default for Constraint<T> { impl<T: Copy + fmt::Debug + Clone + Eq + PartialEq> Copy for Constraint<T> {} +impl<T: fmt::Debug + Clone + Eq + PartialEq> Match<T> for Constraint<T> { + fn matches(&self, other: &T) -> bool { + match *self { + Constraint::Any => true, + Constraint::Only(ref value) => value == other, + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum RelaySettings { + CustomTunnelEndpoint(CustomTunnelEndpoint), + Normal(RelayConstraints), +} + +impl Default for RelaySettings { + fn default() -> Self { + RelaySettings::Normal(RelayConstraints::default()) + } +} + +impl RelaySettings { + pub fn merge(&mut self, update: RelaySettingsUpdate) -> Self { + match update { + RelaySettingsUpdate::CustomTunnelEndpoint(relay) => { + RelaySettings::CustomTunnelEndpoint(relay) + } + RelaySettingsUpdate::Normal(constraint_update) => RelaySettings::Normal(match *self { + RelaySettings::CustomTunnelEndpoint(_) => { + RelayConstraints::default().merge(constraint_update) + } + RelaySettings::Normal(ref constraint) => constraint.merge(constraint_update), + }), + } + } +} #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub struct RelayConstraints { - pub host: Constraint<String>, - pub tunnel: TunnelConstraints, + pub location: Constraint<LocationConstraint>, + pub tunnel: Constraint<TunnelConstraints>, } impl RelayConstraints { - pub fn merge(&mut self, update: RelayConstraintsUpdate) -> Self { + pub fn merge(&self, update: RelayConstraintsUpdate) -> Self { RelayConstraints { - host: update.host.unwrap_or_else(|| self.host.clone()), - tunnel: self.tunnel.merge(update.tunnel), + location: update.location.unwrap_or_else(|| self.location.clone()), + tunnel: update.tunnel.unwrap_or_else(|| self.tunnel.clone()), } } } @@ -37,61 +91,72 @@ impl RelayConstraints { #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] +pub enum LocationConstraint { + /// A country is represented by its two letter country code. + Country(CountryCode), + /// A city is composed of a country code and a city code. + City(CountryCode, CityCode), +} + + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] pub enum TunnelConstraints { #[serde(rename = "openvpn")] OpenVpn(OpenVpnConstraints), + #[serde(rename = "wireguard")] Wireguard(WireguardConstraints), } -impl Default for TunnelConstraints { - fn default() -> Self { - TunnelConstraints::OpenVpn(OpenVpnConstraints::default()) +impl Match<OpenVpnParameters> for TunnelConstraints { + fn matches(&self, endpoint: &OpenVpnParameters) -> bool { + match *self { + TunnelConstraints::OpenVpn(ref constraints) => constraints.matches(endpoint), + _ => false, + } } } -impl TunnelConstraints { - pub fn merge(&mut self, update: TunnelConstraintsUpdate) -> Self { +impl Match<WireguardParameters> for TunnelConstraints { + fn matches(&self, endpoint: &WireguardParameters) -> bool { match *self { - TunnelConstraints::OpenVpn(ref mut current) => match update { - TunnelConstraintsUpdate::OpenVpn(openvpn_update) => { - TunnelConstraints::OpenVpn(current.merge(openvpn_update)) - } - }, + TunnelConstraints::Wireguard(ref constraints) => constraints.matches(endpoint), + _ => false, } } } #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] pub struct OpenVpnConstraints { pub port: Constraint<u16>, pub protocol: Constraint<TransportProtocol>, } -impl OpenVpnConstraints { - pub fn merge(&mut self, update: OpenVpnConstraintsUpdate) -> Self { - OpenVpnConstraints { - port: update.port.unwrap_or_else(|| self.port.clone()), - protocol: update.protocol.unwrap_or(self.protocol), - } +impl Match<OpenVpnParameters> for OpenVpnConstraints { + fn matches(&self, endpoint: &OpenVpnParameters) -> bool { + self.port.matches(&endpoint.port) && self.protocol.matches(&endpoint.protocol) } } +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] +pub struct WireguardConstraints { + pub port: Constraint<u16>, +} -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -pub struct RelayConstraintsUpdate { - pub host: Option<Constraint<String>>, - pub tunnel: TunnelConstraintsUpdate, +impl Match<WireguardParameters> for WireguardConstraints { + fn matches(&self, endpoint: &WireguardParameters) -> bool { + self.port.matches(&endpoint.port) + } } + #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] -pub enum TunnelConstraintsUpdate { - #[serde(rename = "openvpn")] OpenVpn(OpenVpnConstraintsUpdate), +pub enum RelaySettingsUpdate { + CustomTunnelEndpoint(CustomTunnelEndpoint), + Normal(RelayConstraintsUpdate), } #[derive(Debug, Default, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -pub struct OpenVpnConstraintsUpdate { - pub port: Option<Constraint<u16>>, - pub protocol: Option<Constraint<TransportProtocol>>, +#[serde(default)] +pub struct RelayConstraintsUpdate { + pub location: Option<Constraint<LocationConstraint>>, + pub tunnel: Option<Constraint<TunnelConstraints>>, } diff --git a/talpid-types/src/net.rs b/talpid-types/src/net.rs index 9d84be0ea2..da6bbbaef4 100644 --- a/talpid-types/src/net.rs +++ b/talpid-types/src/net.rs @@ -24,8 +24,10 @@ impl TunnelEndpoint { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)] pub enum TunnelParameters { /// Extra parameters for an OpenVPN tunnel endpoint. + #[serde(rename = "openvpn")] OpenVpn(OpenVpnParameters), /// Extra parameters for a Wireguard tunnel endpoint. + #[serde(rename = "wireguard")] Wireguard(WireguardParameters), } |
