summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--mullvad-cli/src/cmds/relay.rs208
-rw-r--r--mullvad-cli/src/cmds/status.rs15
-rw-r--r--mullvad-daemon/Cargo.toml5
-rw-r--r--mullvad-daemon/src/bin/list-relays.rs29
-rw-r--r--mullvad-daemon/src/main.rs215
-rw-r--r--mullvad-daemon/src/management_interface.rs91
-rw-r--r--mullvad-daemon/src/relays.rs330
-rw-r--r--mullvad-daemon/src/settings.rs40
-rw-r--r--mullvad-rpc/src/lib.rs14
-rw-r--r--mullvad-types/src/relay_constraints.rs133
-rw-r--r--talpid-types/src/net.rs2
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),
}