summaryrefslogtreecommitdiffhomepage
path: root/mullvad_daemon/src
diff options
context:
space:
mode:
authorLinus Färnstrand <linus@mullvad.net>2017-06-16 15:29:15 +0200
committerLinus Färnstrand <linus@mullvad.net>2017-06-16 15:29:15 +0200
commit29de1387c9cdcd2a06bbb6ee1c4c9f36734f6107 (patch)
tree11f80ee610fb2e3ee73a8361ef5d03491bdb8830 /mullvad_daemon/src
parentc2aaa4eb26af015ef949785668751cded783675d (diff)
parent44346a5d28264ef15915336096a4e0d622d662ec (diff)
downloadmullvadvpn-29de1387c9cdcd2a06bbb6ee1c4c9f36734f6107.tar.xz
mullvadvpn-29de1387c9cdcd2a06bbb6ee1c4c9f36734f6107.zip
Merge branch 'master-new-daemon'
Diffstat (limited to 'mullvad_daemon/src')
-rw-r--r--mullvad_daemon/src/ipc_api.rs90
-rw-r--r--mullvad_daemon/src/main.rs325
-rw-r--r--mullvad_daemon/src/management_interface.rs301
-rw-r--r--mullvad_daemon/src/states.rs21
4 files changed, 631 insertions, 106 deletions
diff --git a/mullvad_daemon/src/ipc_api.rs b/mullvad_daemon/src/ipc_api.rs
deleted file mode 100644
index 84932a0b8f..0000000000
--- a/mullvad_daemon/src/ipc_api.rs
+++ /dev/null
@@ -1,90 +0,0 @@
-use jsonrpc_core::Error;
-use jsonrpc_core::futures::BoxFuture;
-use jsonrpc_macros::pubsub;
-use jsonrpc_pubsub::SubscriptionId;
-
-use std::collections::HashMap;
-use std::net::IpAddr;
-
-pub type AccountToken = String;
-pub type CountryCode = String;
-
-build_rpc_trait! {
- pub trait IpcApi {
- type Metadata;
-
- /// Fetches and returns metadata about an account. Returns an error on non-existing
- /// accounts.
- #[rpc(name = "get_account_data")]
- fn get_account_data(&self, AccountToken) -> Result<AccountData, Error>;
-
- /// Returns available countries.
- #[rpc(name = "get_countries")]
- fn get_countries(&self) -> Result<HashMap<CountryCode, String>, Error>;
-
- /// Set which account to connect with
- #[rpc(name = "set_account")]
- fn set_account(&self, AccountToken) -> Result<(), Error>;
-
- /// Set which country to connect to
- #[rpc(name = "set_country")]
- fn set_country(&self, CountryCode) -> Result<(), Error>;
-
- /// Set if the backend should automatically establish a tunnel on start or not.
- #[rpc(name = "set_autoconnect")]
- fn set_autoconnect(&self, bool) -> Result<(), Error>;
-
- /// Try to connect if disconnected, or do nothing if already connecting/connected.
- #[rpc(name = "connect")]
- fn connect(&self) -> Result<(), Error>;
-
- /// Disconnect the VPN tunnel if it is connecting/connected. Does nothing if already
- /// disconnected.
- #[rpc(name = "disconnect")]
- fn disconnect(&self) -> Result<(), Error>;
-
- /// Returns the current security state of the Mullvad client. Changes to this state will
- /// be announced to subscribers of `event`.
- #[rpc(name = "get_state")]
- fn get_state(&self) -> Result<SecurityState, Error>;
-
- /// Returns the current public IP of this computer.
- #[rpc(name = "get_ip")]
- fn get_ip(&self) -> Result<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>;
-
- #[pubsub(name = "event")] {
- /// Subscribes to the `event` notifications.
- #[rpc(name = "event_subscribe")]
- fn subscribe(&self, Self::Metadata, pubsub::Subscriber<String>);
-
- /// Unsubscribes from the `event` notifications.
- #[rpc(name = "event_unsubscribe")]
- fn unsubscribe(&self, SubscriptionId) -> BoxFuture<bool, Error>;
- }
- }
-}
-
-#[derive(Serialize)]
-pub struct AccountData {
- pub paid_until: String,
-}
-
-#[derive(Serialize)]
-pub struct Location {
- pub latlong: [f64; 2],
- pub country: String,
- pub city: String,
-}
-
-#[derive(Serialize)]
-pub enum SecurityState {
- Unsecured,
- Securing,
- Secured,
- Unsecuring,
-}
diff --git a/mullvad_daemon/src/main.rs b/mullvad_daemon/src/main.rs
index 95033b1564..fa1db7765c 100644
--- a/mullvad_daemon/src/main.rs
+++ b/mullvad_daemon/src/main.rs
@@ -8,37 +8,330 @@ extern crate serde;
#[macro_use]
extern crate serde_derive;
-extern crate talpid_ipc;
-
extern crate jsonrpc_core;
extern crate jsonrpc_pubsub;
#[macro_use]
extern crate jsonrpc_macros;
extern crate jsonrpc_ws_server;
+extern crate uuid;
+#[macro_use]
+extern crate lazy_static;
+
+extern crate talpid_core;
+extern crate talpid_ipc;
+
+mod management_interface;
+mod states;
+
+use management_interface::{ManagementInterfaceServer, TunnelCommand};
+use states::{SecurityState, TargetState};
+
+use std::sync::{Arc, Mutex, mpsc};
+use std::thread;
+
+use talpid_core::net::RemoteAddr;
+use talpid_core::tunnel::{self, TunnelEvent, TunnelMonitor};
+
+error_chain!{
+ errors {
+ /// The client is in the wrong state for the requested operation. Optimally the code should
+ /// be written in such a way so such states can't exist.
+ InvalidState {
+ description("Client is in an invalid state for the requested operation")
+ }
+ TunnelError(msg: &'static str) {
+ description("Error in the tunnel monitor")
+ display("Tunnel monitor error: {}", msg)
+ }
+ ManagementInterfaceError(msg: &'static str) {
+ description("Error in the management interface")
+ display("Management interface error: {}", msg)
+ }
+ }
+}
+
+lazy_static! {
+ // Temporary store of hardcoded remotes.
+ static ref REMOTES: [RemoteAddr; 3] = [
+ RemoteAddr::new("se5.mullvad.net", 1300),
+ RemoteAddr::new("se6.mullvad.net", 1300),
+ RemoteAddr::new("se7.mullvad.net", 1300),
+ ];
+}
+
+pub enum DaemonEvent {
+ TunnelEvent(TunnelEvent),
+ TunnelExit(tunnel::Result<()>),
+ ManagementInterfaceEvent(TunnelCommand),
+ ManagementInterfaceExit(talpid_ipc::Result<()>),
+}
+
+impl From<TunnelEvent> for DaemonEvent {
+ fn from(tunnel_event: TunnelEvent) -> Self {
+ DaemonEvent::TunnelEvent(tunnel_event)
+ }
+}
+
+impl From<TunnelCommand> for DaemonEvent {
+ fn from(tunnel_command: TunnelCommand) -> Self {
+ DaemonEvent::ManagementInterfaceEvent(tunnel_command)
+ }
+}
+
+/// Represents the internal state of the actual tunnel.
+// TODO(linus): Put the tunnel::CloseHandle into this state, so it can't exist when not running.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum TunnelState {
+ /// No tunnel is running.
+ NotRunning,
+ /// The tunnel has been started, but it is not established/functional.
+ Down,
+ /// The tunnel is up and working.
+ Up,
+}
+
+impl TunnelState {
+ pub fn as_security_state(&self) -> SecurityState {
+ match *self {
+ TunnelState::Up => SecurityState::Secured,
+ _ => SecurityState::Unsecured,
+ }
+ }
+}
+
+
+struct Daemon {
+ state: TunnelState,
+ last_broadcasted_state: SecurityState,
+ target_state: TargetState,
+ rx: mpsc::Receiver<DaemonEvent>,
+ tx: mpsc::Sender<DaemonEvent>,
+ tunnel_close_handle: Option<tunnel::CloseHandle>,
+ management_interface_broadcaster: management_interface::EventBroadcaster,
+
+ // Just for testing. A cyclic iterator iterating over the hardcoded remotes,
+ // picking a new one for each retry.
+ remote_iter: std::iter::Cycle<std::iter::Cloned<std::slice::Iter<'static, RemoteAddr>>>,
+}
+
+impl Daemon {
+ pub fn new() -> Result<Self> {
+ let (tx, rx) = mpsc::channel();
+ let management_interface_broadcaster = Self::start_management_interface(tx.clone())?;
+ Ok(
+ Daemon {
+ state: TunnelState::NotRunning,
+ last_broadcasted_state: SecurityState::Unsecured,
+ target_state: TargetState::Unsecured,
+ rx,
+ tx,
+ tunnel_close_handle: None,
+ management_interface_broadcaster,
+ remote_iter: REMOTES.iter().cloned().cycle(),
+ },
+ )
+ }
+
+ // 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>)
+ -> Result<management_interface::EventBroadcaster> {
+ let server = Self::start_management_interface_server(event_tx.clone())?;
+ 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: mpsc::Sender<DaemonEvent>)
+ -> Result<ManagementInterfaceServer> {
+ let server =
+ ManagementInterfaceServer::start(event_tx.clone())
+ .chain_err(|| ErrorKind::ManagementInterfaceError("Failed to start server"),)?;
+ info!(
+ "Mullvad management interface listening on {}",
+ server.address()
+ );
+ Ok(server)
+ }
+
+ fn spawn_management_interface_wait_thread(server: ManagementInterfaceServer,
+ exit_tx: mpsc::Sender<DaemonEvent>) {
+ thread::spawn(
+ move || {
+ let result = server.wait();
+ debug!("Mullvad management interface shut down");
+ let _ = exit_tx.send(DaemonEvent::ManagementInterfaceExit(result));
+ },
+ );
+ }
+
+ /// Consume the `Daemon` and run the main event loop. Blocks until an error happens.
+ pub fn run(mut self) -> Result<()> {
+ while let Ok(event) = self.rx.recv() {
+ self.handle_event(event)?;
+ }
+ Ok(())
+ }
+
+ fn handle_event(&mut self, event: DaemonEvent) -> Result<()> {
+ use DaemonEvent::*;
+ match event {
+ TunnelEvent(event) => Ok(self.handle_tunnel_event(event)),
+ TunnelExit(result) => self.handle_tunnel_exit(result),
+ ManagementInterfaceEvent(event) => self.handle_management_interface_event(event),
+ ManagementInterfaceExit(result) => self.handle_management_interface_exit(result),
+ }
+ }
+
+ fn handle_tunnel_event(&mut self, tunnel_event: TunnelEvent) {
+ info!("Tunnel event: {:?}", tunnel_event);
+ let new_state = match tunnel_event {
+ TunnelEvent::Up => TunnelState::Up,
+ TunnelEvent::Down => TunnelState::Down,
+ };
+ self.set_state(new_state);
+ }
+
+ fn handle_tunnel_exit(&mut self, result: tunnel::Result<()>) -> Result<()> {
+ self.tunnel_close_handle = None;
+ if let Err(e) = result {
+ log_error("Tunnel exited in an unexpected way", e);
+ }
+ self.set_state(TunnelState::NotRunning);
+ self.apply_target_state()
+ }
+
+ fn handle_management_interface_event(&mut self, event: TunnelCommand) -> Result<()> {
+ match event {
+ TunnelCommand::SetTargetState(state) => self.set_target_state(state)?,
+ TunnelCommand::GetState(tx) => {
+ if let Err(_) = tx.send(self.last_broadcasted_state) {
+ warn!("Unable to send current state to management interface client",);
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn handle_management_interface_exit(&self, result: talpid_ipc::Result<()>) -> Result<()> {
+ let error = ErrorKind::ManagementInterfaceError("Server exited unexpectedly");
+ match result {
+ Ok(()) => Err(error.into()),
+ e => e.chain_err(|| error),
+ }
+ }
+
+ /// Update the state of the client. If it changed, notify the subscribers.
+ fn set_state(&mut self, new_state: TunnelState) {
+ if new_state != self.state {
+ self.state = new_state;
+ let new_security_state = self.state.as_security_state();
+ if self.last_broadcasted_state != new_security_state {
+ self.last_broadcasted_state = new_security_state;
+ self.management_interface_broadcaster.notify_new_state(new_security_state);
+ }
+ }
+ }
+
+ /// Set the target state of the client. If it changed trigger the operations needed to progress
+ /// towards that state.
+ fn set_target_state(&mut self, new_state: TargetState) -> Result<()> {
+ if new_state != self.target_state {
+ self.target_state = new_state;
+ self.apply_target_state()
+ } else {
+ Ok(())
+ }
+ }
-pub mod ipc_api;
-pub mod mock_ipc;
+ fn apply_target_state(&mut self) -> Result<()> {
+ match (self.target_state, self.state) {
+ (TargetState::Secured, TunnelState::NotRunning) => {
+ debug!("Triggering tunnel start");
+ self.start_tunnel()
+ }
+ (TargetState::Unsecured, TunnelState::Down) |
+ (TargetState::Unsecured, TunnelState::Up) => {
+ if let Some(close_handle) = self.tunnel_close_handle.take() {
+ debug!("Triggering tunnel stop");
+ // This close operation will block until the tunnel is dead.
+ close_handle
+ .close()
+ .chain_err(|| ErrorKind::TunnelError("Unable to kill tunnel"))
+ } else {
+ Ok(())
+ }
+ }
+ (target_state, state) => {
+ trace!(
+ "apply_target_state does nothing on TargetState::{:?} TunnelState::{:?}",
+ target_state,
+ state
+ );
+ Ok(())
+ }
+ }
+ }
+
+ fn start_tunnel(&mut self) -> Result<()> {
+ ensure!(
+ self.state == TunnelState::NotRunning,
+ ErrorKind::InvalidState
+ );
+ let remote = self.remote_iter.next().unwrap();
+ let tunnel_monitor = self.spawn_tunnel_monitor(remote)?;
+ self.tunnel_close_handle = Some(tunnel_monitor.close_handle());
+ self.spawn_tunnel_monitor_wait_thread(tunnel_monitor);
+
+ self.set_state(TunnelState::Down);
+ Ok(())
+ }
+
+ fn spawn_tunnel_monitor(&self, remote: RemoteAddr) -> Result<TunnelMonitor> {
+ // Must wrap the channel in a Mutex because TunnelMonitor forces the closure to be Sync
+ let event_tx = Arc::new(Mutex::new(self.tx.clone()));
+ let on_tunnel_event = move |event| {
+ let _ = event_tx.lock().unwrap().send(DaemonEvent::TunnelEvent(event));
+ };
+ TunnelMonitor::new(remote, on_tunnel_event)
+ .chain_err(|| ErrorKind::TunnelError("Unable to start tunnel monitor"))
+ }
+
+ fn spawn_tunnel_monitor_wait_thread(&self, tunnel_monitor: TunnelMonitor) {
+ let error_tx = self.tx.clone();
+ thread::spawn(
+ move || {
+ let result = tunnel_monitor.wait();
+ let _ = error_tx.send(DaemonEvent::TunnelExit(result));
+ trace!("Tunnel monitor thread exit");
+ },
+ );
+ }
+}
+
+
+fn log_error<E>(msg: &str, error: E)
+ where E: error_chain::ChainedError
+{
+ error!("{}: {}", msg, error);
+ for e in error.iter().skip(1) {
+ error!("Caused by {}", e);
+ }
+}
-error_chain!{}
quick_main!(run);
fn run() -> Result<()> {
init_logger()?;
- let server = start_ipc()?;
- info!("Mullvad daemon listening on {}", server.address());
- main_loop(server)
+ let daemon = Daemon::new().chain_err(|| "Unable to initialize daemon")?;
+ daemon.run()?;
+
+ debug!("Mullvad daemon is quitting");
+ Ok(())
}
fn init_logger() -> Result<()> {
env_logger::init().chain_err(|| "Failed to bootstrap logging system")
}
-
-fn start_ipc() -> Result<mock_ipc::IpcServer> {
- mock_ipc::IpcServer::start().chain_err(|| "Failed to start IPC server")
-}
-
-fn main_loop(server: mock_ipc::IpcServer) -> Result<()> {
- server.wait().chain_err(|| "Error while waiting for server to process")
-}
diff --git a/mullvad_daemon/src/management_interface.rs b/mullvad_daemon/src/management_interface.rs
new file mode 100644
index 0000000000..7e66205862
--- /dev/null
+++ b/mullvad_daemon/src/management_interface.rs
@@ -0,0 +1,301 @@
+use jsonrpc_core::{Error, ErrorCode, Metadata};
+use jsonrpc_core::futures::{BoxFuture, Future, future, sync};
+use jsonrpc_macros::pubsub;
+use jsonrpc_pubsub::{PubSubHandler, PubSubMetadata, Session, SubscriptionId};
+use jsonrpc_ws_server;
+
+use states::{SecurityState, TargetState};
+
+use std::collections::HashMap;
+use std::collections::hash_map::Entry;
+use std::net::{IpAddr, Ipv4Addr};
+use std::sync::{Arc, Mutex, RwLock, mpsc};
+
+use talpid_ipc;
+use uuid;
+
+
+pub type AccountToken = String;
+pub type CountryCode = String;
+
+#[derive(Serialize)]
+pub struct AccountData {
+ pub paid_until: String,
+}
+
+#[derive(Serialize)]
+pub struct Location {
+ pub latlong: [f64; 2],
+ pub country: String,
+ pub city: String,
+}
+
+
+build_rpc_trait! {
+ pub trait ManagementInterfaceApi {
+ type Metadata;
+
+ /// Fetches and returns metadata about an account. Returns an error on non-existing
+ /// accounts.
+ #[rpc(name = "get_account_data")]
+ fn get_account_data(&self, AccountToken) -> Result<AccountData, Error>;
+
+ /// Returns available countries.
+ #[rpc(name = "get_countries")]
+ fn get_countries(&self) -> Result<HashMap<CountryCode, String>, Error>;
+
+ /// Set which account to connect with
+ #[rpc(name = "set_account")]
+ fn set_account(&self, AccountToken) -> Result<(), Error>;
+
+ /// Set which country to connect to
+ #[rpc(name = "set_country")]
+ fn set_country(&self, CountryCode) -> Result<(), Error>;
+
+ /// Set if the client should automatically establish a tunnel on start or not.
+ #[rpc(name = "set_autoconnect")]
+ fn set_autoconnect(&self, bool) -> Result<(), Error>;
+
+ /// Try to connect if disconnected, or do nothing if already connecting/connected.
+ #[rpc(name = "connect")]
+ fn connect(&self) -> Result<(), Error>;
+
+ /// Disconnect the VPN tunnel if it is connecting/connected. Does nothing if already
+ /// disconnected.
+ #[rpc(name = "disconnect")]
+ fn disconnect(&self) -> Result<(), Error>;
+
+ /// Returns the current security state of the Mullvad client. Changes to this state will
+ /// be announced to subscribers of `event`.
+ #[rpc(async, name = "get_state")]
+ fn get_state(&self) -> BoxFuture<SecurityState, Error>;
+
+ /// Returns the current public IP of this computer.
+ #[rpc(name = "get_ip")]
+ fn get_ip(&self) -> Result<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>;
+
+ #[pubsub(name = "new_state")] {
+ /// Subscribes to the `new_state` event notifications.
+ #[rpc(name = "new_state_subscribe")]
+ fn new_state_subscribe(&self, Self::Metadata, pubsub::Subscriber<SecurityState>);
+
+ /// Unsubscribes from the `new_state` event notifications.
+ #[rpc(name = "new_state_unsubscribe")]
+ fn new_state_unsubscribe(&self, SubscriptionId) -> BoxFuture<(), Error>;
+ }
+ }
+}
+
+
+/// Enum representing commands coming in on the management interface.
+#[derive(Debug)]
+pub enum TunnelCommand {
+ /// Change target state.
+ SetTargetState(TargetState),
+ /// Request the current state.
+ GetState(sync::oneshot::Sender<SecurityState>),
+}
+
+type ActiveSubscriptions = Arc<RwLock<HashMap<SubscriptionId, pubsub::Sink<SecurityState>>>>;
+
+pub struct ManagementInterfaceServer {
+ server: talpid_ipc::IpcServer,
+ active_subscriptions: ActiveSubscriptions,
+}
+
+impl ManagementInterfaceServer {
+ pub fn start(tunnel_tx: mpsc::Sender<::DaemonEvent>) -> talpid_ipc::Result<Self> {
+ let rpc = ManagementInterface::new(tunnel_tx);
+ let active_subscriptions = rpc.active_subscriptions.clone();
+
+ let mut io = PubSubHandler::default();
+ io.extend_with(rpc.to_delegate());
+ let server = talpid_ipc::IpcServer::start_with_metadata(io.into(), meta_extractor)?;
+ Ok(
+ ManagementInterfaceServer {
+ server,
+ active_subscriptions,
+ },
+ )
+ }
+
+ pub fn address(&self) -> &str {
+ self.server.address()
+ }
+
+ pub fn event_broadcaster(&self) -> EventBroadcaster {
+ EventBroadcaster { active_subscriptions: self.active_subscriptions.clone() }
+ }
+
+ /// Consumes the server and waits for it to finish. Returns an error if the server exited
+ /// due to an error.
+ pub fn wait(self) -> talpid_ipc::Result<()> {
+ self.server.wait()
+ }
+}
+
+
+/// A handle that allows broadcasting messages to all subscribers of the management interface.
+pub struct EventBroadcaster {
+ active_subscriptions: ActiveSubscriptions,
+}
+
+impl EventBroadcaster {
+ /// Sends an event to all subscribers of the management interface.
+ pub fn notify_new_state(&self, event: SecurityState) {
+ let active_subscriptions = self.active_subscriptions.read().unwrap();
+ for sink in active_subscriptions.values() {
+ let _ = sink.notify(Ok(event)).wait();
+ }
+ }
+}
+
+struct ManagementInterface {
+ active_subscriptions: ActiveSubscriptions,
+ tx: Mutex<mpsc::Sender<::DaemonEvent>>,
+}
+
+impl ManagementInterface {
+ pub fn new(tx: mpsc::Sender<::DaemonEvent>) -> Self {
+ ManagementInterface {
+ active_subscriptions: Default::default(),
+ tx: Mutex::new(tx),
+ }
+ }
+}
+
+impl ManagementInterfaceApi for ManagementInterface {
+ type Metadata = Meta;
+
+ fn get_account_data(&self, _account_token: AccountToken) -> Result<AccountData, Error> {
+ trace!("get_account_data");
+ Ok(AccountData { paid_until: "2018-12-31T16:00:00.000Z".to_owned() },)
+ }
+
+ fn get_countries(&self) -> Result<HashMap<CountryCode, String>, Error> {
+ trace!("get_countries");
+ Ok(HashMap::new())
+ }
+
+ fn set_account(&self, _account_token: AccountToken) -> Result<(), Error> {
+ trace!("set_account");
+ Ok(())
+ }
+
+ fn set_country(&self, _country_code: CountryCode) -> Result<(), Error> {
+ trace!("set_country");
+ Ok(())
+ }
+
+ fn set_autoconnect(&self, _autoconnect: bool) -> Result<(), Error> {
+ trace!("set_autoconnect");
+ Ok(())
+ }
+
+ fn connect(&self) -> Result<(), Error> {
+ trace!("connect");
+ self.tx
+ .lock()
+ .unwrap()
+ .send(TunnelCommand::SetTargetState(TargetState::Secured).into())
+ .map_err(|_| Error::internal_error())
+ }
+
+ fn disconnect(&self) -> Result<(), Error> {
+ trace!("disconnect");
+ self.tx
+ .lock()
+ .unwrap()
+ .send(TunnelCommand::SetTargetState(TargetState::Unsecured).into())
+ .map_err(|_| Error::internal_error())
+ }
+
+ fn get_state(&self) -> BoxFuture<SecurityState, Error> {
+ trace!("get_state");
+ let (state_tx, state_rx) = sync::oneshot::channel();
+ match self.tx.lock().unwrap().send(TunnelCommand::GetState(state_tx).into()) {
+ Ok(()) => state_rx.map_err(|_| Error::internal_error()).boxed(),
+ Err(_) => future::err(Error::internal_error()).boxed(),
+ }
+ }
+
+ fn get_ip(&self) -> Result<IpAddr, Error> {
+ trace!("get_ip");
+ Ok(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)))
+ }
+
+ fn get_location(&self) -> Result<Location, Error> {
+ trace!("get_location");
+ Ok(
+ Location {
+ latlong: [1.0, 2.0],
+ country: "narnia".to_owned(),
+ city: "Le city".to_owned(),
+ },
+ )
+ }
+
+ fn new_state_subscribe(&self,
+ _meta: Self::Metadata,
+ subscriber: pubsub::Subscriber<SecurityState>) {
+ trace!("new_state_subscribe");
+ let mut active_subscriptions = self.active_subscriptions.write().unwrap();
+ loop {
+ let id = SubscriptionId::String(uuid::Uuid::new_v4().to_string());
+ if let Entry::Vacant(entry) = active_subscriptions.entry(id.clone()) {
+ if let Ok(sink) = subscriber.assign_id(id.clone()) {
+ debug!("Accepting new subscription with id {:?}", id);
+ entry.insert(sink);
+ }
+ break;
+ }
+ }
+ }
+
+ fn new_state_unsubscribe(&self, id: SubscriptionId) -> BoxFuture<(), Error> {
+ trace!("new_state_unsubscribe");
+ let was_removed = self.active_subscriptions.write().unwrap().remove(&id).is_some();
+ let result = if was_removed {
+ debug!("Unsubscribing id {:?}", id);
+ future::ok(())
+ } else {
+ future::err(
+ Error {
+ code: ErrorCode::InvalidParams,
+ message: "Invalid subscription".to_owned(),
+ data: None,
+ },
+ )
+ };
+ result.boxed()
+ }
+}
+
+
+/// The metadata type. There is one instance associated with each connection. In this pubsub
+/// scenario they are created by `meta_extractor` by the server on each new incoming
+/// connection.
+#[derive(Clone, Debug, Default)]
+pub struct Meta {
+ session: Option<Arc<Session>>,
+}
+
+/// Make the `Meta` type possible to use as jsonrpc metadata type.
+impl Metadata for Meta {}
+
+/// Make the `Meta` type possible to use as a pubsub metadata type.
+impl PubSubMetadata for Meta {
+ fn session(&self) -> Option<Arc<Session>> {
+ self.session.clone()
+ }
+}
+
+/// Metadata extractor function for `Meta`.
+fn meta_extractor(context: &jsonrpc_ws_server::RequestContext) -> Meta {
+ Meta { session: Some(Arc::new(Session::new(context.sender()))) }
+}
diff --git a/mullvad_daemon/src/states.rs b/mullvad_daemon/src/states.rs
new file mode 100644
index 0000000000..10e543c58a
--- /dev/null
+++ b/mullvad_daemon/src/states.rs
@@ -0,0 +1,21 @@
+/// Security state of the computer.
+/// TODO(linus): There is a difference between lockdown(firewall) and tunnel functionality. The
+/// firewall can be set to prevent any leaks but the tunnel is not connected. Then we are secured,
+/// but disconnected. The frontend should probably reflect these states in some way. I think it
+/// be reasonable to have three states, since unsecured but tunnel is up is probably an invalid
+/// state.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub enum SecurityState {
+ Unsecured,
+ Secured,
+}
+
+/// Represents the state the client strives towards.
+/// When in `Secured`, the client should keep the computer from leaking and try to
+/// establish a VPN tunnel if it is not up.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum TargetState {
+ Unsecured,
+ Secured,
+}