diff options
| author | Linus Färnstrand <linus@mullvad.net> | 2018-09-24 16:26:39 +0200 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2018-09-24 16:26:39 +0200 |
| commit | e0619be7d66bd3db2136d640e15131685d6084b9 (patch) | |
| tree | 89f264e82883d73fdd99c327e8c9e4833758a226 | |
| parent | cd6675854025b3866a931ba4d42205ffbf7613a0 (diff) | |
| parent | 967bf662fe56017dcb7cb9211c0e7f8440458cb2 (diff) | |
| download | mullvadvpn-e0619be7d66bd3db2136d640e15131685d6084b9.tar.xz mullvadvpn-e0619be7d66bd3db2136d640e15131685d6084b9.zip | |
Merge branch 'split-daemon-into-library'
| -rw-r--r-- | Cargo.lock | 24 | ||||
| -rwxr-xr-x | build.sh | 2 | ||||
| -rw-r--r-- | mullvad-daemon/Cargo.toml | 10 | ||||
| -rw-r--r-- | mullvad-daemon/build.rs | 24 | ||||
| -rw-r--r-- | mullvad-daemon/resources.rc | 8 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 678 | ||||
| -rw-r--r-- | mullvad-daemon/src/logging.rs | 2 | ||||
| -rw-r--r-- | mullvad-daemon/src/main.rs | 661 | ||||
| -rw-r--r-- | mullvad-daemon/src/relays.rs | 3 | ||||
| -rw-r--r-- | mullvad-daemon/src/system_service.rs | 19 | ||||
| -rw-r--r-- | mullvad-daemon/version.rc | 25 |
11 files changed, 756 insertions, 700 deletions
diff --git a/Cargo.lock b/Cargo.lock index 44a7c4bf64..5418b80e24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -946,7 +946,7 @@ dependencies = [ "uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "windows-service 0.1.0 (git+https://github.com/mullvad/windows-service-rs.git?rev=55c5dfb372e6b3f5607a3159c5388d27b6b84ff6)", - "windres 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winres 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1217,7 +1217,7 @@ source = "git+https://github.com/nikvolf/parity-tokio-ipc#306ea3e6ff8b8c1bb03081 dependencies = [ "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "mio-named-pipes 0.1.6 (git+https://github.com/alexcrichton/mio-named-pipes)", "miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1234,7 +1234,7 @@ source = "git+https://github.com/NikVolf/parity-tokio-ipc?rev=master#306ea3e6ff8 dependencies = [ "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "mio-named-pipes 0.1.6 (git+https://github.com/alexcrichton/mio-named-pipes)", "miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1978,6 +1978,14 @@ dependencies = [ ] [[package]] +name = "toml" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "try-lock" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2157,6 +2165,14 @@ dependencies = [ ] [[package]] +name = "winres" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "ws2_32-sys" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2357,6 +2373,7 @@ dependencies = [ "checksum tokio-timer 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1c76b4e97a4f61030edff8bd272364e4f731b9f54c7307eb4eb733c3926eb96a" "checksum tokio-udp 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "43eb534af6e8f37d43ab1b612660df14755c42bd003c5f8d2475ee78cc4600c0" "checksum tokio-uds 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "424c1ed15a0132251813ccea50640b224c809d6ceafb88154c1a8775873a0e89" +"checksum toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a0263c6c02c4db6c8f7681f9fd35e90de799ebd4cfdeab77a38f4ff6b3d8c0d9" "checksum try-lock 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2aa4715743892880f70885373966c83d73ef1b0838a664ef0c76fffd35e7c2" "checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" "checksum unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284b6d3db520d67fbe88fd778c21510d1b0ba4a551e5d0fbb023d33405f6de8a" @@ -2384,4 +2401,5 @@ dependencies = [ "checksum windows-service 0.1.0 (git+https://github.com/mullvad/windows-service-rs.git?rev=55c5dfb372e6b3f5607a3159c5388d27b6b84ff6)" = "<none>" "checksum windres 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dd6e13ff705d7fab032bdf857354035c56c8c889e364029e4300779eb6e1d729" "checksum winreg 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a" +"checksum winres 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f07dabda4e79413ecac65bc9a2234ad3d85dc49f9d289f868cd9d8611d88f28d" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" @@ -117,7 +117,7 @@ if [[ "$(uname -s)" == "MINGW"* ]]; then fi echo "Building Rust code in release mode using $RUSTC_VERSION..." -cargo +stable build --release +MULLVAD_ADD_MANIFEST="1" cargo +stable build --release ################################################################################ # Other work to prepare the release. diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index 89d7a7aa2e..2892869152 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -44,4 +44,12 @@ windows-service = { git = "https://github.com/mullvad/windows-service-rs.git", r winapi = "0.3" [target.'cfg(windows)'.build-dependencies] -windres = "0.2" +winres = "0.1" +winapi = "0.3" + +[package.metadata.winres] +ProductName = "Mullvad VPN" +CompanyName = "Amagicom AB" +LegalCopyright = "(c) 2018 Amagicom AB" +InternalName = "mullvad-daemon" +OriginalFilename = "mullvad-daemon.exe" diff --git a/mullvad-daemon/build.rs b/mullvad-daemon/build.rs index 45ce36bdba..f35e2fa4a1 100644 --- a/mullvad-daemon/build.rs +++ b/mullvad-daemon/build.rs @@ -4,18 +4,36 @@ use std::path::PathBuf; use std::process::Command; #[cfg(windows)] -extern crate windres; +extern crate winapi; +#[cfg(windows)] +extern crate winres; fn main() { let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let product_version = env!("CARGO_PKG_VERSION").replacen(".0", "", 1); - fs::write(out_dir.join("product-version.txt"), product_version).unwrap(); + fs::write(out_dir.join("product-version.txt"), &product_version).unwrap(); fs::write(out_dir.join("git-commit-date.txt"), commit_date()).unwrap(); #[cfg(windows)] { - windres::Build::new().compile("resources.rc").unwrap(); + let mut res = winres::WindowsResource::new(); + res.set("ProductVersion", &product_version); + res.set_icon("../dist-assets/icon.ico"); + res.set_language(winapi::um::winnt::MAKELANGID( + winapi::um::winnt::LANG_ENGLISH, + winapi::um::winnt::SUBLANG_ENGLISH_US, + )); + println!("cargo:rerun-if-env-changed=MULLVAD_ADD_MANIFEST"); + if env::var("MULLVAD_ADD_MANIFEST") + .map(|s| s != "0") + .unwrap_or(false) + { + res.set_manifest_file("mullvad-daemon.manifest"); + } else { + println!("cargo:warning=Skipping mullvad-daemon manifest"); + } + res.compile().expect("Unable to generate windows resources"); } } diff --git a/mullvad-daemon/resources.rc b/mullvad-daemon/resources.rc deleted file mode 100644 index d55f0136e3..0000000000 --- a/mullvad-daemon/resources.rc +++ /dev/null @@ -1,8 +0,0 @@ -IDI_MAIN ICON "../dist-assets/icon.ico" - -#include "version.rc" - -#define RT_MANIFEST 24 -#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 - -CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "mullvad-daemon.manifest" diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs new file mode 100644 index 0000000000..9856ee5856 --- /dev/null +++ b/mullvad-daemon/src/lib.rs @@ -0,0 +1,678 @@ +//! # 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. + +extern crate chrono; +#[macro_use] +extern crate error_chain; +extern crate futures; +#[cfg(unix)] +extern crate libc; +#[macro_use] +extern crate log; + +#[macro_use] +extern crate serde; +extern crate serde_json; + +extern crate jsonrpc_core; +#[macro_use] +extern crate jsonrpc_macros; +extern crate jsonrpc_ipc_server; +extern crate jsonrpc_pubsub; +extern crate rand; +extern crate tokio_core; +extern crate tokio_timer; +extern crate uuid; + +extern crate mullvad_ipc_client; +extern crate mullvad_paths; +extern crate mullvad_rpc; +extern crate mullvad_types; +extern crate talpid_core; +extern crate talpid_ipc; +extern crate talpid_types; + +mod account_history; +mod geoip; +mod management_interface; +mod relays; +mod rpc_uniqueness_check; + +use error_chain::ChainedError; +use futures::sync::mpsc::UnboundedSender; +use futures::{Future, Sink}; +use jsonrpc_core::futures::sync::oneshot::{self, Sender as OneshotSender}; + +use management_interface::{BoxFuture, ManagementCommand, ManagementInterfaceServer}; +use mullvad_rpc::{AccountsProxy, AppVersionProxy, HttpHandle}; + +use mullvad_types::{ + account::{AccountData, AccountToken}, + location::GeoIpLocation, + relay_constraints::{RelaySettings, RelaySettingsUpdate}, + relay_list::{Relay, RelayList}, + settings::Settings, + states::TargetState, + version::{AppVersion, AppVersionInfo}, +}; + +use std::{mem, net::IpAddr, path::PathBuf, sync::mpsc, thread, time::Duration}; + +use talpid_core::{ + mpsc::IntoSender, + tunnel_state_machine::{self, TunnelCommand, TunnelParameters}, +}; +use talpid_types::{ + net::TunnelEndpoint, + tunnel::{BlockReason, TunnelStateTransition}, +}; + + +error_chain!{ + errors { + NoCacheDir { + description("Unable to create cache directory") + } + DaemonIsAlreadyRunning { + description("Another instance of the daemon is already running") + } + ManagementInterfaceError(msg: &'static str) { + description("Error in the management interface") + display("Management interface error: {}", msg) + } + } + links { + TunnelError(tunnel_state_machine::Error, tunnel_state_machine::ErrorKind); + } +} + +type SyncUnboundedSender<T> = ::futures::sink::Wait<UnboundedSender<T>>; + +/// All events that can happen in the daemon. Sent from various threads and exposed interfaces. +pub enum DaemonEvent { + /// Tunnel has changed state. + TunnelStateTransition(TunnelStateTransition), + /// An event coming from the JSONRPC-2.0 management interface. + ManagementInterfaceEvent(ManagementCommand), + /// Triggered if the server hosting the JSONRPC-2.0 management interface dies unexpectedly. + ManagementInterfaceExited, + /// Daemon shutdown triggered by a signal, ctrl-c or similar. + TriggerShutdown, +} + +impl From<TunnelStateTransition> for DaemonEvent { + fn from(tunnel_state_transition: TunnelStateTransition) -> Self { + DaemonEvent::TunnelStateTransition(tunnel_state_transition) + } +} + +impl From<ManagementCommand> for DaemonEvent { + fn from(command: ManagementCommand) -> Self { + DaemonEvent::ManagementInterfaceEvent(command) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum DaemonExecutionState { + Running, + Exiting, + Finished, +} + +impl DaemonExecutionState { + pub fn shutdown(&mut self, tunnel_state: &TunnelStateTransition) { + use self::DaemonExecutionState::*; + + match self { + Running => { + match tunnel_state { + TunnelStateTransition::Disconnected => mem::replace(self, Finished), + _ => mem::replace(self, Exiting), + }; + } + Exiting | Finished => {} + }; + } + + pub fn disconnected(&mut self) { + use self::DaemonExecutionState::*; + + match self { + Exiting => { + mem::replace(self, Finished); + } + Running | Finished => {} + }; + } + + pub fn is_running(&mut self) -> bool { + use self::DaemonExecutionState::*; + + match self { + Running => true, + Exiting | Finished => false, + } + } +} + + +pub struct Daemon { + tunnel_command_tx: SyncUnboundedSender<TunnelCommand>, + tunnel_state: TunnelStateTransition, + target_state: TargetState, + state: DaemonExecutionState, + rx: mpsc::Receiver<DaemonEvent>, + tx: mpsc::Sender<DaemonEvent>, + management_interface_broadcaster: management_interface::EventBroadcaster, + #[cfg(unix)] + management_interface_socket_path: String, + settings: Settings, + accounts_proxy: AccountsProxy<HttpHandle>, + version_proxy: AppVersionProxy<HttpHandle>, + https_handle: mullvad_rpc::rest::RequestSender, + tokio_remote: tokio_core::reactor::Remote, + relay_selector: relays::RelaySelector, + current_relay: Option<Relay>, + log_dir: Option<PathBuf>, + resource_dir: PathBuf, + version: String, +} + +impl Daemon { + pub fn new( + log_dir: Option<PathBuf>, + resource_dir: PathBuf, + cache_dir: PathBuf, + version: String, + ) -> Result<Self> { + ensure!( + !rpc_uniqueness_check::is_another_instance_running(), + ErrorKind::DaemonIsAlreadyRunning + ); + let ca_path = resource_dir.join(mullvad_paths::resources::API_CA_FILENAME); + + let mut rpc_manager = mullvad_rpc::MullvadRpcFactory::with_cache_dir(&cache_dir, &ca_path); + + let (rpc_handle, https_handle, tokio_remote) = + mullvad_rpc::event_loop::create(move |core| { + let handle = core.handle(); + let rpc = rpc_manager.new_connection_on_event_loop(&handle); + let https_handle = mullvad_rpc::rest::create_https_client(&ca_path, &handle); + let remote = core.remote(); + (rpc, https_handle, remote) + }).chain_err(|| "Unable to initialize network event loop")?; + let rpc_handle = rpc_handle.chain_err(|| "Unable to create RPC client")?; + let https_handle = https_handle.chain_err(|| "Unable to create am.i.mullvad client")?; + + let relay_selector = + relays::RelaySelector::new(rpc_handle.clone(), &resource_dir, &cache_dir); + + let (tx, rx) = mpsc::channel(); + let tunnel_command_tx = + tunnel_state_machine::spawn(cache_dir.clone(), IntoSender::from(tx.clone()))?; + + let target_state = TargetState::Unsecured; + let management_interface_result = + Self::start_management_interface(tx.clone(), cache_dir.clone())?; + + // Attempt to download a fresh relay list + relay_selector.update(); + + Ok(Daemon { + tunnel_command_tx: Sink::wait(tunnel_command_tx), + tunnel_state: TunnelStateTransition::Disconnected, + target_state, + state: DaemonExecutionState::Running, + rx, + tx, + management_interface_broadcaster: management_interface_result.0, + #[cfg(unix)] + management_interface_socket_path: management_interface_result.1, + settings: Settings::load().chain_err(|| "Unable to read settings")?, + accounts_proxy: AccountsProxy::new(rpc_handle.clone()), + version_proxy: AppVersionProxy::new(rpc_handle), + https_handle, + tokio_remote, + relay_selector, + current_relay: None, + log_dir, + resource_dir, + version, + }) + } + + // 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( + event_tx: mpsc::Sender<DaemonEvent>, + cache_dir: PathBuf, + ) -> Result<(management_interface::EventBroadcaster, String)> { + let multiplex_event_tx = IntoSender::from(event_tx.clone()); + let server = Self::start_management_interface_server(multiplex_event_tx, cache_dir)?; + let event_broadcaster = server.event_broadcaster(); + let socket_path = server.socket_path().to_owned(); + Self::spawn_management_interface_wait_thread(server, event_tx); + Ok((event_broadcaster, socket_path)) + } + + fn start_management_interface_server( + event_tx: IntoSender<ManagementCommand, DaemonEvent>, + cache_dir: PathBuf, + ) -> Result<ManagementInterfaceServer> { + let server = ManagementInterfaceServer::start(event_tx, cache_dir) + .chain_err(|| ErrorKind::ManagementInterfaceError("Failed to start server"))?; + info!( + "Mullvad management interface listening on {}", + server.socket_path() + ); + + Ok(server) + } + + fn spawn_management_interface_wait_thread( + server: ManagementInterfaceServer, + exit_tx: mpsc::Sender<DaemonEvent>, + ) { + thread::spawn(move || { + server.wait(); + error!("Mullvad management interface shut down"); + let _ = exit_tx.send(DaemonEvent::ManagementInterfaceExited); + }); + } + + /// Consume the `Daemon` and run the main event loop. Blocks until an error happens or a + /// shutdown event is received. + pub fn run(mut self) -> Result<()> { + if self.settings.get_auto_connect() { + info!("Automatically connecting since auto-connect is turned on"); + if self.set_target_state(TargetState::Secured).is_err() { + warn!("Aborting auto-connect since no account token is set"); + } + } + while let Ok(event) = self.rx.recv() { + self.handle_event(event)?; + if self.state == DaemonExecutionState::Finished { + break; + } + } + Ok(()) + } + + fn handle_event(&mut self, event: DaemonEvent) -> Result<()> { + use DaemonEvent::*; + match event { + TunnelStateTransition(transition) => { + Ok(self.handle_tunnel_state_transition(transition)) + } + ManagementInterfaceEvent(event) => Ok(self.handle_management_interface_event(event)), + ManagementInterfaceExited => self.handle_management_interface_exited(), + TriggerShutdown => Ok(self.handle_trigger_shutdown_event()), + } + } + + fn handle_tunnel_state_transition(&mut self, tunnel_state: TunnelStateTransition) { + use self::TunnelStateTransition::*; + + debug!("New tunnel state: {:?}", tunnel_state); + match tunnel_state { + Disconnected => { + self.state.disconnected(); + self.current_relay = None; + } + Blocked(ref reason) => { + info!("Blocking all network connections, reason: {}", reason); + + match reason { + BlockReason::AuthFailed(_) => self.schedule_reconnect(Duration::from_secs(60)), + _ => {} + } + } + _ => {} + } + + self.tunnel_state = tunnel_state.clone(); + self.management_interface_broadcaster + .notify_new_state(tunnel_state); + } + + fn schedule_reconnect(&mut self, delay: Duration) { + let command_tx = self.tx.clone(); + + thread::spawn(move || { + let (result_tx, _result_rx) = oneshot::channel(); + + thread::sleep(delay); + debug!("Attempting to reconnect"); + let _ = command_tx.send(DaemonEvent::ManagementInterfaceEvent( + ManagementCommand::SetTargetState(result_tx, TargetState::Secured), + )); + }); + } + + fn handle_management_interface_event(&mut self, event: ManagementCommand) { + use ManagementCommand::*; + match event { + SetTargetState(tx, state) => self.on_set_target_state(tx, state), + GetState(tx) => self.on_get_state(tx), + GetCurrentLocation(tx) => self.on_get_current_location(tx), + GetAccountData(tx, account_token) => self.on_get_account_data(tx, account_token), + GetRelayLocations(tx) => self.on_get_relay_locations(tx), + SetAccount(tx, account_token) => self.on_set_account(tx, account_token), + UpdateRelaySettings(tx, update) => self.on_update_relay_settings(tx, update), + SetAllowLan(tx, allow_lan) => self.on_set_allow_lan(tx, allow_lan), + SetAutoConnect(tx, auto_connect) => self.on_set_auto_connect(tx, auto_connect), + SetOpenVpnMssfix(tx, mssfix_arg) => self.on_set_openvpn_mssfix(tx, mssfix_arg), + SetEnableIpv6(tx, enable_ipv6) => self.on_set_enable_ipv6(tx, enable_ipv6), + GetSettings(tx) => self.on_get_settings(tx), + GetVersionInfo(tx) => self.on_get_version_info(tx), + GetCurrentVersion(tx) => self.on_get_current_version(tx), + Shutdown => self.handle_trigger_shutdown_event(), + } + } + + fn on_set_target_state( + &mut self, + tx: OneshotSender<::std::result::Result<(), ()>>, + new_target_state: TargetState, + ) { + if self.state.is_running() { + Self::oneshot_send(tx, self.set_target_state(new_target_state), "targe state"); + } else { + warn!("Ignoring target state change request due to shutdown"); + Self::oneshot_send(tx, Ok(()), "targe state"); + } + } + + fn on_get_state(&self, tx: OneshotSender<TunnelStateTransition>) { + Self::oneshot_send(tx, self.tunnel_state.clone(), "current state"); + } + + fn on_get_current_location(&self, tx: OneshotSender<GeoIpLocation>) { + if let Some(ref relay) = self.current_relay { + let location = relay.location.as_ref().cloned().unwrap(); + let geo_ip_location = GeoIpLocation { + ip: IpAddr::V4(relay.ipv4_addr_exit), + country: location.country, + city: Some(location.city), + latitude: location.latitude, + longitude: location.longitude, + mullvad_exit_ip: true, + }; + Self::oneshot_send(tx, geo_ip_location, "current location"); + } else { + let https_handle = self.https_handle.clone(); + self.tokio_remote.spawn(move |_| { + geoip::send_location_request(https_handle) + .map(move |location| Self::oneshot_send(tx, location, "current location")) + .map_err(|e| { + warn!("Unable to fetch GeoIP location: {}", e.display_chain()); + }) + }); + } + } + + fn on_get_account_data( + &mut self, + tx: OneshotSender<BoxFuture<AccountData, mullvad_rpc::Error>>, + account_token: AccountToken, + ) { + let rpc_call = self + .accounts_proxy + .get_expiry(account_token) + .map(|expiry| AccountData { expiry }); + 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(), "relay locations"); + } + + + fn on_set_account(&mut self, tx: OneshotSender<()>, account_token: Option<String>) { + let account_token_cleared = account_token.is_none(); + let save_result = self.settings.set_account_token(account_token); + + match save_result.chain_err(|| "Unable to save settings") { + Ok(account_changed) => { + Self::oneshot_send(tx, (), "set_account response"); + if account_changed { + self.management_interface_broadcaster + .notify_settings(&self.settings); + if account_token_cleared { + info!("Disconnecting because account token was cleared"); + let _ = self.set_target_state(TargetState::Unsecured); + } else { + info!("Initiating tunnel restart because the account token changed"); + self.reconnect_tunnel(); + } + } + } + Err(e) => error!("{}", e.display_chain()), + } + } + + fn on_get_version_info( + &mut self, + tx: OneshotSender<BoxFuture<AppVersionInfo, mullvad_rpc::Error>>, + ) { + let fut = self + .version_proxy + .latest_app_version() + .join(self.version_proxy.is_app_version_supported(&self.version)) + .map(|(latest_versions, is_supported)| AppVersionInfo { + current_is_supported: is_supported, + latest: latest_versions, + }); + Self::oneshot_send(tx, Box::new(fut), "get_version_info response"); + } + + fn on_get_current_version(&mut self, tx: OneshotSender<AppVersion>) { + Self::oneshot_send(tx, self.version.clone(), "get_current_version response"); + } + + fn on_update_relay_settings(&mut self, tx: OneshotSender<()>, update: RelaySettingsUpdate) { + let save_result = self.settings.update_relay_settings(update); + match save_result.chain_err(|| "Unable to save settings") { + Ok(settings_changed) => { + Self::oneshot_send(tx, (), "update_relay_settings response"); + if settings_changed { + self.management_interface_broadcaster + .notify_settings(&self.settings); + info!("Initiating tunnel restart because the relay settings changed"); + self.reconnect_tunnel(); + } + } + Err(e) => error!("{}", e.display_chain()), + } + } + + fn on_set_allow_lan(&mut self, tx: OneshotSender<()>, allow_lan: bool) { + let save_result = self.settings.set_allow_lan(allow_lan); + match save_result.chain_err(|| "Unable to save settings") { + Ok(settings_changed) => { + Self::oneshot_send(tx, (), "set_allow_lan response"); + if settings_changed { + self.management_interface_broadcaster + .notify_settings(&self.settings); + self.send_tunnel_command(TunnelCommand::AllowLan(allow_lan)); + } + } + Err(e) => error!("{}", e.display_chain()), + } + } + + fn on_set_auto_connect(&mut self, tx: OneshotSender<()>, auto_connect: bool) { + let save_result = self.settings.set_auto_connect(auto_connect); + match save_result.chain_err(|| "Unable to save settings") { + Ok(settings_changed) => { + Self::oneshot_send(tx, (), "set auto-connect response"); + if settings_changed { + self.management_interface_broadcaster + .notify_settings(&self.settings); + } + } + Err(e) => error!("{}", e.display_chain()), + } + } + + fn on_set_openvpn_mssfix(&mut self, tx: OneshotSender<()>, mssfix_arg: Option<u16>) { + let save_result = self.settings.set_openvpn_mssfix(mssfix_arg); + match save_result.chain_err(|| "Unable to save settings") { + Ok(settings_changed) => { + Self::oneshot_send(tx, (), "set_openvpn_mssfix response"); + if settings_changed { + self.management_interface_broadcaster + .notify_settings(&self.settings); + } + } + Err(e) => error!("{}", e.display_chain()), + } + } + + fn on_set_enable_ipv6(&mut self, tx: OneshotSender<()>, enable_ipv6: bool) { + let save_result = self.settings.set_enable_ipv6(enable_ipv6); + match save_result.chain_err(|| "Unable to save settings") { + Ok(settings_changed) => { + Self::oneshot_send(tx, (), "set_enable_ipv6 response"); + if settings_changed { + self.management_interface_broadcaster + .notify_settings(&self.settings); + info!("Initiating tunnel restart because the enable IPv6 setting changed"); + self.reconnect_tunnel(); + } + } + Err(e) => error!("{}", e.display_chain()), + } + } + + fn on_get_settings(&self, tx: OneshotSender<Settings>) { + Self::oneshot_send(tx, self.settings.clone(), "get_settings response"); + } + + fn oneshot_send<T>(tx: OneshotSender<T>, t: T, msg: &'static str) { + if let Err(_) = tx.send(t) { + warn!("Unable to send {} to management interface client", msg); + } + } + + fn handle_management_interface_exited(&self) -> Result<()> { + Err(ErrorKind::ManagementInterfaceError("Server exited unexpectedly").into()) + } + + fn handle_trigger_shutdown_event(&mut self) { + self.state.shutdown(&self.tunnel_state); + self.disconnect_tunnel(); + } + + /// Set the target state of the client. If it changed trigger the operations needed to + /// progress towards that state. + /// Returns an error if trying to set secured state, but no account token is present. + fn set_target_state(&mut self, new_state: TargetState) -> ::std::result::Result<(), ()> { + if new_state != self.target_state || self.tunnel_state.is_blocked() { + debug!("Target state {:?} => {:?}", self.target_state, new_state); + self.target_state = new_state; + match self.target_state { + TargetState::Secured => match self.settings.get_account_token() { + Some(account_token) => self.connect_tunnel(account_token), + None => { + self.set_target_state(TargetState::Unsecured)?; + return Err(()); + } + }, + TargetState::Unsecured => self.disconnect_tunnel(), + } + } + Ok(()) + } + + fn connect_tunnel(&mut self, account_token: AccountToken) { + let command = match self.settings.get_relay_settings() { + RelaySettings::CustomTunnelEndpoint(custom_relay) => custom_relay + .to_tunnel_endpoint() + .chain_err(|| "Custom tunnel endpoint could not be resolved"), + RelaySettings::Normal(constraints) => self + .relay_selector + .get_tunnel_endpoint(&constraints) + .chain_err(|| "No valid relay servers match the current settings") + .map(|(relay, endpoint)| { + self.current_relay = Some(relay); + endpoint + }), + }.map(|endpoint| self.build_tunnel_parameters(account_token, endpoint)) + .map(|parameters| TunnelCommand::Connect(parameters)) + .unwrap_or_else(|error| { + error!("{}", error.display_chain()); + TunnelCommand::Block(BlockReason::NoMatchingRelay, self.settings.get_allow_lan()) + }); + self.send_tunnel_command(command); + } + + fn disconnect_tunnel(&mut self) { + self.send_tunnel_command(TunnelCommand::Disconnect); + } + + fn reconnect_tunnel(&mut self) { + if self.target_state == TargetState::Secured { + if let Some(account_token) = self.settings.get_account_token() { + self.connect_tunnel(account_token); + } + } + } + + fn build_tunnel_parameters( + &self, + account_token: AccountToken, + endpoint: TunnelEndpoint, + ) -> TunnelParameters { + TunnelParameters { + endpoint, + options: self.settings.get_tunnel_options().clone(), + log_dir: self.log_dir.clone(), + resource_dir: self.resource_dir.clone(), + username: account_token, + allow_lan: self.settings.get_allow_lan(), + } + } + + fn send_tunnel_command(&mut self, command: TunnelCommand) { + self.tunnel_command_tx + .send(command) + .expect("Tunnel state machine has stopped"); + } + + pub fn shutdown_handle(&self) -> DaemonShutdownHandle { + DaemonShutdownHandle { + tx: self.tx.clone(), + } + } +} + +pub struct DaemonShutdownHandle { + tx: mpsc::Sender<DaemonEvent>, +} + +impl DaemonShutdownHandle { + pub fn shutdown(&self) { + let _ = self.tx.send(DaemonEvent::TriggerShutdown); + } +} + +impl Drop for Daemon { + fn drop(&mut self) { + #[cfg(unix)] + { + use std::fs; + if let Err(e) = fs::remove_file(&self.management_interface_socket_path) { + error!( + "Failed to remove RPC socket {}: {}", + self.management_interface_socket_path, e + ); + } + } + } +} diff --git a/mullvad-daemon/src/logging.rs b/mullvad-daemon/src/logging.rs index 24b60bb5e9..3f8f4f59dc 100644 --- a/mullvad-daemon/src/logging.rs +++ b/mullvad-daemon/src/logging.rs @@ -58,7 +58,7 @@ const LINE_SEPARATOR: &str = "\n"; #[cfg(windows)] const LINE_SEPARATOR: &str = "\r\n"; -pub const DATE_TIME_FORMAT_STR: &str = "[%Y-%m-%d %H:%M:%S%.3f]"; +const DATE_TIME_FORMAT_STR: &str = "[%Y-%m-%d %H:%M:%S%.3f]"; pub fn init_logger( log_level: log::LevelFilter, diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs index 661da885dc..e8bd24ccc1 100644 --- a/mullvad-daemon/src/main.rs +++ b/mullvad-daemon/src/main.rs @@ -11,77 +11,31 @@ extern crate chrono; extern crate clap; #[macro_use] extern crate error_chain; -extern crate futures; #[cfg(unix)] extern crate libc; #[macro_use] extern crate log; extern crate log_panics; - -#[macro_use] -extern crate serde; -extern crate serde_json; - -extern crate jsonrpc_core; -#[macro_use] -extern crate jsonrpc_macros; -extern crate jsonrpc_ipc_server; -extern crate jsonrpc_pubsub; -extern crate rand; -extern crate tokio_core; -extern crate tokio_timer; -extern crate uuid; - -extern crate mullvad_ipc_client; +extern crate mullvad_daemon; extern crate mullvad_paths; -extern crate mullvad_rpc; -extern crate mullvad_types; extern crate talpid_core; -extern crate talpid_ipc; -extern crate talpid_types; #[cfg(windows)] #[macro_use] extern crate windows_service; -mod account_history; +use error_chain::ChainedError; +use mullvad_daemon::Daemon; +use std::{thread, time::Duration}; + mod cli; -mod geoip; mod logging; -mod management_interface; -mod relays; -mod rpc_uniqueness_check; mod shutdown; +#[cfg(windows)] mod system_service; mod version; -use error_chain::ChainedError; -use futures::sync::mpsc::UnboundedSender; -use futures::{Future, Sink}; -use jsonrpc_core::futures::sync::oneshot::{self, Sender as OneshotSender}; - -use management_interface::{BoxFuture, ManagementCommand, ManagementInterfaceServer}; -use mullvad_rpc::{AccountsProxy, AppVersionProxy, HttpHandle}; - -use mullvad_types::account::{AccountData, AccountToken}; -use mullvad_types::location::GeoIpLocation; -use mullvad_types::relay_constraints::{RelaySettings, RelaySettingsUpdate}; -use mullvad_types::relay_list::{Relay, RelayList}; -use mullvad_types::settings::Settings; -use mullvad_types::states::TargetState; -use mullvad_types::version::{AppVersion, AppVersionInfo}; - -use std::net::IpAddr; -use std::path::PathBuf; -use std::sync::mpsc; -use std::time::Duration; -use std::{mem, thread}; - -use talpid_core::mpsc::IntoSender; -use talpid_core::tunnel_state_machine::{self, TunnelCommand, TunnelParameters}; -use talpid_types::net::TunnelEndpoint; -use talpid_types::tunnel::{BlockReason, TunnelStateTransition}; - +const DAEMON_LOG_FILENAME: &str = "daemon.log"; error_chain!{ errors { @@ -89,604 +43,12 @@ error_chain!{ description("Error setting up log") display("Error setting up log: {}", msg) } - NoCacheDir { - description("Unable to create cache directory") - } - DaemonIsAlreadyRunning { - description("Another instance of the daemon is already running") - } - ManagementInterfaceError(msg: &'static str) { - description("Error in the management interface") - display("Management interface error: {}", msg) - } } links { - TunnelError(tunnel_state_machine::Error, tunnel_state_machine::ErrorKind); - } -} - -const DAEMON_LOG_FILENAME: &str = "daemon.log"; - -type SyncUnboundedSender<T> = ::futures::sink::Wait<UnboundedSender<T>>; - -/// All events that can happen in the daemon. Sent from various threads and exposed interfaces. -pub enum DaemonEvent { - /// Tunnel has changed state. - TunnelStateTransition(TunnelStateTransition), - /// An event coming from the JSONRPC-2.0 management interface. - ManagementInterfaceEvent(ManagementCommand), - /// Triggered if the server hosting the JSONRPC-2.0 management interface dies unexpectedly. - ManagementInterfaceExited, - /// Daemon shutdown triggered by a signal, ctrl-c or similar. - TriggerShutdown, -} - -impl From<TunnelStateTransition> for DaemonEvent { - fn from(tunnel_state_transition: TunnelStateTransition) -> Self { - DaemonEvent::TunnelStateTransition(tunnel_state_transition) - } -} - -impl From<ManagementCommand> for DaemonEvent { - fn from(command: ManagementCommand) -> Self { - DaemonEvent::ManagementInterfaceEvent(command) - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -enum DaemonExecutionState { - Running, - Exiting, - Finished, -} - -impl DaemonExecutionState { - pub fn shutdown(&mut self, tunnel_state: &TunnelStateTransition) { - use self::DaemonExecutionState::*; - - match self { - Running => { - match tunnel_state { - TunnelStateTransition::Disconnected => mem::replace(self, Finished), - _ => mem::replace(self, Exiting), - }; - } - Exiting | Finished => {} - }; - } - - pub fn disconnected(&mut self) { - use self::DaemonExecutionState::*; - - match self { - Exiting => { - mem::replace(self, Finished); - } - Running | Finished => {} - }; - } - - pub fn is_running(&mut self) -> bool { - use self::DaemonExecutionState::*; - - match self { - Running => true, - Exiting | Finished => false, - } + DaemonError(mullvad_daemon::Error, mullvad_daemon::ErrorKind); } } - -struct Daemon { - tunnel_command_tx: SyncUnboundedSender<TunnelCommand>, - tunnel_state: TunnelStateTransition, - target_state: TargetState, - state: DaemonExecutionState, - rx: mpsc::Receiver<DaemonEvent>, - tx: mpsc::Sender<DaemonEvent>, - management_interface_broadcaster: management_interface::EventBroadcaster, - settings: Settings, - accounts_proxy: AccountsProxy<HttpHandle>, - version_proxy: AppVersionProxy<HttpHandle>, - https_handle: mullvad_rpc::rest::RequestSender, - tokio_remote: tokio_core::reactor::Remote, - relay_selector: relays::RelaySelector, - current_relay: Option<Relay>, - log_dir: Option<PathBuf>, - resource_dir: PathBuf, -} - -impl Daemon { - pub fn new( - log_dir: Option<PathBuf>, - resource_dir: PathBuf, - cache_dir: PathBuf, - ) -> Result<Self> { - ensure!( - !rpc_uniqueness_check::is_another_instance_running(), - ErrorKind::DaemonIsAlreadyRunning - ); - let ca_path = resource_dir.join(mullvad_paths::resources::API_CA_FILENAME); - - let mut rpc_manager = mullvad_rpc::MullvadRpcFactory::with_cache_dir(&cache_dir, &ca_path); - - let (rpc_handle, https_handle, tokio_remote) = - mullvad_rpc::event_loop::create(move |core| { - let handle = core.handle(); - let rpc = rpc_manager.new_connection_on_event_loop(&handle); - let https_handle = mullvad_rpc::rest::create_https_client(&ca_path, &handle); - let remote = core.remote(); - (rpc, https_handle, remote) - }).chain_err(|| "Unable to initialize network event loop")?; - let rpc_handle = rpc_handle.chain_err(|| "Unable to create RPC client")?; - let https_handle = https_handle.chain_err(|| "Unable to create am.i.mullvad client")?; - - let relay_selector = - relays::RelaySelector::new(rpc_handle.clone(), &resource_dir, &cache_dir); - - let (tx, rx) = mpsc::channel(); - let tunnel_command_tx = - tunnel_state_machine::spawn(cache_dir.clone(), IntoSender::from(tx.clone()))?; - - let target_state = TargetState::Unsecured; - let management_interface_broadcaster = - Self::start_management_interface(tx.clone(), cache_dir.clone())?; - - // Attempt to download a fresh relay list - relay_selector.update(); - - Ok(Daemon { - tunnel_command_tx: Sink::wait(tunnel_command_tx), - tunnel_state: TunnelStateTransition::Disconnected, - target_state, - state: DaemonExecutionState::Running, - rx, - tx, - management_interface_broadcaster, - settings: Settings::load().chain_err(|| "Unable to read settings")?, - accounts_proxy: AccountsProxy::new(rpc_handle.clone()), - version_proxy: AppVersionProxy::new(rpc_handle), - https_handle, - tokio_remote, - relay_selector, - current_relay: None, - log_dir, - resource_dir, - }) - } - - // 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( - event_tx: mpsc::Sender<DaemonEvent>, - cache_dir: PathBuf, - ) -> Result<management_interface::EventBroadcaster> { - let multiplex_event_tx = IntoSender::from(event_tx.clone()); - let server = Self::start_management_interface_server(multiplex_event_tx, cache_dir)?; - let event_broadcaster = server.event_broadcaster(); - Self::spawn_management_interface_wait_thread(server, event_tx); - Ok(event_broadcaster) - } - - fn start_management_interface_server( - event_tx: IntoSender<ManagementCommand, DaemonEvent>, - cache_dir: PathBuf, - ) -> Result<ManagementInterfaceServer> { - let server = ManagementInterfaceServer::start(event_tx, cache_dir) - .chain_err(|| ErrorKind::ManagementInterfaceError("Failed to start server"))?; - info!( - "Mullvad management interface listening on {}", - server.socket_path() - ); - - Ok(server) - } - - fn spawn_management_interface_wait_thread( - server: ManagementInterfaceServer, - exit_tx: mpsc::Sender<DaemonEvent>, - ) { - thread::spawn(move || { - server.wait(); - error!("Mullvad management interface shut down"); - let _ = exit_tx.send(DaemonEvent::ManagementInterfaceExited); - }); - } - - /// Consume the `Daemon` and run the main event loop. Blocks until an error happens or a - /// shutdown event is received. - pub fn run(mut self) -> Result<()> { - if self.settings.get_auto_connect() { - info!("Automatically connecting since auto-connect is turned on"); - if self.set_target_state(TargetState::Secured).is_err() { - warn!("Aborting auto-connect since no account token is set"); - } - } - while let Ok(event) = self.rx.recv() { - self.handle_event(event)?; - if self.state == DaemonExecutionState::Finished { - break; - } - } - Ok(()) - } - - fn handle_event(&mut self, event: DaemonEvent) -> Result<()> { - use DaemonEvent::*; - match event { - TunnelStateTransition(transition) => { - Ok(self.handle_tunnel_state_transition(transition)) - } - ManagementInterfaceEvent(event) => Ok(self.handle_management_interface_event(event)), - ManagementInterfaceExited => self.handle_management_interface_exited(), - TriggerShutdown => Ok(self.handle_trigger_shutdown_event()), - } - } - - fn handle_tunnel_state_transition(&mut self, tunnel_state: TunnelStateTransition) { - use self::TunnelStateTransition::*; - - debug!("New tunnel state: {:?}", tunnel_state); - match tunnel_state { - Disconnected => { - self.state.disconnected(); - self.current_relay = None; - } - Blocked(ref reason) => { - info!("Blocking all network connections, reason: {}", reason); - - match reason { - BlockReason::AuthFailed(_) => self.schedule_reconnect(Duration::from_secs(60)), - _ => {} - } - } - _ => {} - } - - self.tunnel_state = tunnel_state.clone(); - self.management_interface_broadcaster - .notify_new_state(tunnel_state); - } - - fn schedule_reconnect(&mut self, delay: Duration) { - let command_tx = self.tx.clone(); - - thread::spawn(move || { - let (result_tx, _result_rx) = oneshot::channel(); - - thread::sleep(delay); - debug!("Attempting to reconnect"); - let _ = command_tx.send(DaemonEvent::ManagementInterfaceEvent( - ManagementCommand::SetTargetState(result_tx, TargetState::Secured), - )); - }); - } - - fn handle_management_interface_event(&mut self, event: ManagementCommand) { - use ManagementCommand::*; - match event { - SetTargetState(tx, state) => self.on_set_target_state(tx, state), - GetState(tx) => self.on_get_state(tx), - GetCurrentLocation(tx) => self.on_get_current_location(tx), - GetAccountData(tx, account_token) => self.on_get_account_data(tx, account_token), - GetRelayLocations(tx) => self.on_get_relay_locations(tx), - SetAccount(tx, account_token) => self.on_set_account(tx, account_token), - UpdateRelaySettings(tx, update) => self.on_update_relay_settings(tx, update), - SetAllowLan(tx, allow_lan) => self.on_set_allow_lan(tx, allow_lan), - SetAutoConnect(tx, auto_connect) => self.on_set_auto_connect(tx, auto_connect), - SetOpenVpnMssfix(tx, mssfix_arg) => self.on_set_openvpn_mssfix(tx, mssfix_arg), - SetEnableIpv6(tx, enable_ipv6) => self.on_set_enable_ipv6(tx, enable_ipv6), - GetSettings(tx) => self.on_get_settings(tx), - GetVersionInfo(tx) => self.on_get_version_info(tx), - GetCurrentVersion(tx) => self.on_get_current_version(tx), - Shutdown => self.handle_trigger_shutdown_event(), - } - } - - fn on_set_target_state( - &mut self, - tx: OneshotSender<::std::result::Result<(), ()>>, - new_target_state: TargetState, - ) { - if self.state.is_running() { - Self::oneshot_send(tx, self.set_target_state(new_target_state), "targe state"); - } else { - warn!("Ignoring target state change request due to shutdown"); - Self::oneshot_send(tx, Ok(()), "targe state"); - } - } - - fn on_get_state(&self, tx: OneshotSender<TunnelStateTransition>) { - Self::oneshot_send(tx, self.tunnel_state.clone(), "current state"); - } - - fn on_get_current_location(&self, tx: OneshotSender<GeoIpLocation>) { - if let Some(ref relay) = self.current_relay { - let location = relay.location.as_ref().cloned().unwrap(); - let geo_ip_location = GeoIpLocation { - ip: IpAddr::V4(relay.ipv4_addr_exit), - country: location.country, - city: Some(location.city), - latitude: location.latitude, - longitude: location.longitude, - mullvad_exit_ip: true, - }; - Self::oneshot_send(tx, geo_ip_location, "current location"); - } else { - let https_handle = self.https_handle.clone(); - self.tokio_remote.spawn(move |_| { - geoip::send_location_request(https_handle) - .map(move |location| Self::oneshot_send(tx, location, "current location")) - .map_err(|e| { - warn!("Unable to fetch GeoIP location: {}", e.display_chain()); - }) - }); - } - } - - fn on_get_account_data( - &mut self, - tx: OneshotSender<BoxFuture<AccountData, mullvad_rpc::Error>>, - account_token: AccountToken, - ) { - let rpc_call = self - .accounts_proxy - .get_expiry(account_token) - .map(|expiry| AccountData { expiry }); - 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(), "relay locations"); - } - - - fn on_set_account(&mut self, tx: OneshotSender<()>, account_token: Option<String>) { - let account_token_cleared = account_token.is_none(); - let save_result = self.settings.set_account_token(account_token); - - match save_result.chain_err(|| "Unable to save settings") { - Ok(account_changed) => { - Self::oneshot_send(tx, (), "set_account response"); - if account_changed { - self.management_interface_broadcaster - .notify_settings(&self.settings); - if account_token_cleared { - info!("Disconnecting because account token was cleared"); - let _ = self.set_target_state(TargetState::Unsecured); - } else { - info!("Initiating tunnel restart because the account token changed"); - self.reconnect_tunnel(); - } - } - } - Err(e) => error!("{}", e.display_chain()), - } - } - - fn on_get_version_info( - &mut self, - tx: OneshotSender<BoxFuture<AppVersionInfo, mullvad_rpc::Error>>, - ) { - let current_version = version::CURRENT.to_owned(); - let fut = self - .version_proxy - .latest_app_version() - .join( - self.version_proxy - .is_app_version_supported(¤t_version), - ).map(|(latest_versions, is_supported)| AppVersionInfo { - current_is_supported: is_supported, - latest: latest_versions, - }); - Self::oneshot_send(tx, Box::new(fut), "get_version_info response"); - } - - fn on_get_current_version(&mut self, tx: OneshotSender<AppVersion>) { - let current_version = version::CURRENT.to_owned(); - Self::oneshot_send(tx, current_version, "get_current_version response"); - } - - fn on_update_relay_settings(&mut self, tx: OneshotSender<()>, update: RelaySettingsUpdate) { - let save_result = self.settings.update_relay_settings(update); - match save_result.chain_err(|| "Unable to save settings") { - Ok(settings_changed) => { - Self::oneshot_send(tx, (), "update_relay_settings response"); - if settings_changed { - self.management_interface_broadcaster - .notify_settings(&self.settings); - info!("Initiating tunnel restart because the relay settings changed"); - self.reconnect_tunnel(); - } - } - Err(e) => error!("{}", e.display_chain()), - } - } - - fn on_set_allow_lan(&mut self, tx: OneshotSender<()>, allow_lan: bool) { - let save_result = self.settings.set_allow_lan(allow_lan); - match save_result.chain_err(|| "Unable to save settings") { - Ok(settings_changed) => { - Self::oneshot_send(tx, (), "set_allow_lan response"); - if settings_changed { - self.management_interface_broadcaster - .notify_settings(&self.settings); - self.send_tunnel_command(TunnelCommand::AllowLan(allow_lan)); - } - } - Err(e) => error!("{}", e.display_chain()), - } - } - - fn on_set_auto_connect(&mut self, tx: OneshotSender<()>, auto_connect: bool) { - let save_result = self.settings.set_auto_connect(auto_connect); - match save_result.chain_err(|| "Unable to save settings") { - Ok(settings_changed) => { - Self::oneshot_send(tx, (), "set auto-connect response"); - if settings_changed { - self.management_interface_broadcaster - .notify_settings(&self.settings); - } - } - Err(e) => error!("{}", e.display_chain()), - } - } - - fn on_set_openvpn_mssfix(&mut self, tx: OneshotSender<()>, mssfix_arg: Option<u16>) { - let save_result = self.settings.set_openvpn_mssfix(mssfix_arg); - match save_result.chain_err(|| "Unable to save settings") { - Ok(settings_changed) => { - Self::oneshot_send(tx, (), "set_openvpn_mssfix response"); - if settings_changed { - self.management_interface_broadcaster - .notify_settings(&self.settings); - } - } - Err(e) => error!("{}", e.display_chain()), - } - } - - fn on_set_enable_ipv6(&mut self, tx: OneshotSender<()>, enable_ipv6: bool) { - let save_result = self.settings.set_enable_ipv6(enable_ipv6); - match save_result.chain_err(|| "Unable to save settings") { - Ok(settings_changed) => { - Self::oneshot_send(tx, (), "set_enable_ipv6 response"); - if settings_changed { - self.management_interface_broadcaster - .notify_settings(&self.settings); - info!("Initiating tunnel restart because the enable IPv6 setting changed"); - self.reconnect_tunnel(); - } - } - Err(e) => error!("{}", e.display_chain()), - } - } - - fn on_get_settings(&self, tx: OneshotSender<Settings>) { - Self::oneshot_send(tx, self.settings.clone(), "get_settings response"); - } - - fn oneshot_send<T>(tx: OneshotSender<T>, t: T, msg: &'static str) { - if let Err(_) = tx.send(t) { - warn!("Unable to send {} to management interface client", msg); - } - } - - fn handle_management_interface_exited(&self) -> Result<()> { - Err(ErrorKind::ManagementInterfaceError("Server exited unexpectedly").into()) - } - - fn handle_trigger_shutdown_event(&mut self) { - self.state.shutdown(&self.tunnel_state); - self.disconnect_tunnel(); - } - - /// Set the target state of the client. If it changed trigger the operations needed to - /// progress towards that state. - /// Returns an error if trying to set secured state, but no account token is present. - fn set_target_state(&mut self, new_state: TargetState) -> ::std::result::Result<(), ()> { - if new_state != self.target_state || self.tunnel_state.is_blocked() { - debug!("Target state {:?} => {:?}", self.target_state, new_state); - self.target_state = new_state; - match self.target_state { - TargetState::Secured => match self.settings.get_account_token() { - Some(account_token) => self.connect_tunnel(account_token), - None => { - self.set_target_state(TargetState::Unsecured)?; - return Err(()); - } - }, - TargetState::Unsecured => self.disconnect_tunnel(), - } - } - Ok(()) - } - - fn connect_tunnel(&mut self, account_token: AccountToken) { - let command = match self.settings.get_relay_settings() { - RelaySettings::CustomTunnelEndpoint(custom_relay) => custom_relay - .to_tunnel_endpoint() - .chain_err(|| "Custom tunnel endpoint could not be resolved"), - RelaySettings::Normal(constraints) => self - .relay_selector - .get_tunnel_endpoint(&constraints) - .chain_err(|| "No valid relay servers match the current settings") - .map(|(relay, endpoint)| { - self.current_relay = Some(relay); - endpoint - }), - }.map(|endpoint| self.build_tunnel_parameters(account_token, endpoint)) - .map(|parameters| TunnelCommand::Connect(parameters)) - .unwrap_or_else(|error| { - error!("{}", error.display_chain()); - TunnelCommand::Block(BlockReason::NoMatchingRelay, self.settings.get_allow_lan()) - }); - self.send_tunnel_command(command); - } - - fn disconnect_tunnel(&mut self) { - self.send_tunnel_command(TunnelCommand::Disconnect); - } - - fn reconnect_tunnel(&mut self) { - if self.target_state == TargetState::Secured { - if let Some(account_token) = self.settings.get_account_token() { - self.connect_tunnel(account_token); - } - } - } - - fn build_tunnel_parameters( - &self, - account_token: AccountToken, - endpoint: TunnelEndpoint, - ) -> TunnelParameters { - TunnelParameters { - endpoint, - options: self.settings.get_tunnel_options().clone(), - log_dir: self.log_dir.clone(), - resource_dir: self.resource_dir.clone(), - username: account_token, - allow_lan: self.settings.get_allow_lan(), - } - } - - fn send_tunnel_command(&mut self, command: TunnelCommand) { - self.tunnel_command_tx - .send(command) - .expect("Tunnel state machine has stopped"); - } - - pub fn shutdown_handle(&self) -> DaemonShutdownHandle { - DaemonShutdownHandle { - tx: self.tx.clone(), - } - } -} - -struct DaemonShutdownHandle { - tx: mpsc::Sender<DaemonEvent>, -} - -impl DaemonShutdownHandle { - pub fn shutdown(&self) { - let _ = self.tx.send(DaemonEvent::TriggerShutdown); - } -} - -impl Drop for Daemon { - fn drop(self: &mut Daemon) { - #[cfg(unix)] - { - use std::fs; - if let Err(e) = fs::remove_file(mullvad_paths::get_rpc_socket_path()) { - error!("Failed to remove RPC socket: {}", e); - } - } - } -} - - fn main() { let exit_code = match run() { Ok(_) => 0, @@ -779,7 +141,12 @@ fn create_daemon(config: cli::Config) -> Result<Daemon> { let resource_dir = mullvad_paths::get_resource_dir(); let cache_dir = mullvad_paths::cache_dir().chain_err(|| "Unable to get cache dir")?; - Daemon::new(log_dir, resource_dir, cache_dir).chain_err(|| "Unable to initialize daemon") + Daemon::new( + log_dir, + resource_dir, + cache_dir, + version::CURRENT.to_owned(), + ).chain_err(|| "Unable to initialize daemon") } fn log_version() { diff --git a/mullvad-daemon/src/relays.rs b/mullvad-daemon/src/relays.rs index 0f35a25625..b55cfad22b 100644 --- a/mullvad-daemon/src/relays.rs +++ b/mullvad-daemon/src/relays.rs @@ -23,6 +23,7 @@ use std::{io, thread}; use rand::{self, Rng, ThreadRng}; use tokio_timer::{TimeoutError, Timer}; +const DATE_TIME_FORMAT_STR: &str = "[%Y-%m-%d %H:%M:%S%.3f]"; const RELAYS_FILENAME: &str = "relays.json"; const DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(15); const UPDATE_INTERVAL: Duration = Duration::from_secs(60 * 60); @@ -142,7 +143,7 @@ impl RelaySelector { "Initialized with {} cached relays from {}", unsynchronized_parsed_relays.relays().len(), DateTime::<Local>::from(unsynchronized_parsed_relays.last_updated()) - .format(::logging::DATE_TIME_FORMAT_STR) + .format(DATE_TIME_FORMAT_STR) ); let parsed_relays = Arc::new(Mutex::new(unsynchronized_parsed_relays)); let updater = RelayListUpdater::spawn(rpc_handle, cache_path, parsed_relays.clone()); diff --git a/mullvad-daemon/src/system_service.rs b/mullvad-daemon/src/system_service.rs index 5f3b1299b4..ee08cae841 100644 --- a/mullvad-daemon/src/system_service.rs +++ b/mullvad-daemon/src/system_service.rs @@ -1,5 +1,3 @@ -#![cfg(windows)] - use std::ffi::OsString; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{mpsc, Arc}; @@ -18,7 +16,8 @@ use windows_service::service_control_handler::{ use windows_service::service_dispatcher; use windows_service::service_manager::{ServiceManager, ServiceManagerAccess}; -use super::{DaemonShutdownHandle, Result, ResultExt}; +use super::{Result, ResultExt}; +use mullvad_daemon::DaemonShutdownHandle; static SERVICE_NAME: &'static str = "MullvadVPN"; static SERVICE_DISPLAY_NAME: &'static str = "Mullvad VPN Service"; @@ -34,15 +33,15 @@ pub fn run() -> Result<()> { define_windows_service!(service_main, handle_service_main); -pub fn handle_service_main(arguments: Vec<OsString>) { +pub fn handle_service_main(_arguments: Vec<OsString>) { info!("Service started."); - match run_service(arguments) { - Ok(_) => info!("Service stopped."), - Err(ref e) => error!("{}", e.display_chain()), + match run_service() { + Ok(()) => info!("Service stopped."), + Err(error) => error!("{}", error.display_chain()), }; } -fn run_service(_arguments: Vec<OsString>) -> Result<()> { +fn run_service() -> Result<()> { let (event_tx, event_rx) = mpsc::channel(); // Register service event handler @@ -76,11 +75,11 @@ fn run_service(_arguments: Vec<OsString>) -> Result<()> { persistent_service_status.set_running().unwrap(); - daemon.run() + Ok(daemon.run()?) }); let exit_code = match result { - Ok(_) => ServiceExitCode::default(), + Ok(()) => ServiceExitCode::default(), Err(_) => ServiceExitCode::ServiceSpecific(1), }; diff --git a/mullvad-daemon/version.rc b/mullvad-daemon/version.rc deleted file mode 100644 index 1c7360bbc0..0000000000 --- a/mullvad-daemon/version.rc +++ /dev/null @@ -1,25 +0,0 @@ -#include "../dist-assets/windows/version.h" - -1 VERSIONINFO -FILEVERSION MAJOR_VERSION,MINOR_VERSION,PATCH_VERSION,0 -PRODUCTVERSION MAJOR_VERSION,MINOR_VERSION,PATCH_VERSION,0 -BEGIN -BLOCK "StringFileInfo" -BEGIN - BLOCK "040904E4" - BEGIN - VALUE "CompanyName", "Amagicom AB" - VALUE "FileDescription", "Daemon that manages the VPN tunnel and system security" - VALUE "FileVersion", PRODUCT_VERSION - VALUE "InternalName", "mullvad-daemon" - VALUE "LegalCopyright", "(c) 2018 Amagicom AB" - VALUE "OriginalFilename", "mullvad-daemon.exe" - VALUE "ProductName", "Mullvad VPN" - VALUE "ProductVersion", PRODUCT_VERSION - END -END -BLOCK "VarFileInfo" -BEGIN - VALUE "Translation", 0x409, 1252 -END -END |
