diff options
| author | Linus Färnstrand <linus@mullvad.net> | 2018-01-11 10:47:41 +0100 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2018-01-15 15:54:16 +0100 |
| commit | fc4178d7e3329fb1fd0014e4ce349a79eafeca51 (patch) | |
| tree | e6e557827dd417a626ba1a44a9045c3a8d95b435 | |
| parent | c50febb59f6f36f829fe56935fd2b0cdcf3ad92f (diff) | |
| download | mullvadvpn-fc4178d7e3329fb1fd0014e4ce349a79eafeca51.tar.xz mullvadvpn-fc4178d7e3329fb1fd0014e4ce349a79eafeca51.zip | |
Download geoip data from am.i.mullvad on request
| -rw-r--r-- | mullvad-cli/src/cmds/relay.rs | 5 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/status.rs | 19 | ||||
| -rw-r--r-- | mullvad-daemon/Cargo.toml | 1 | ||||
| -rw-r--r-- | mullvad-daemon/src/geoip.rs | 32 | ||||
| -rw-r--r-- | mullvad-daemon/src/main.rs | 76 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 24 | ||||
| -rw-r--r-- | mullvad-daemon/src/relays.rs | 6 | ||||
| -rw-r--r-- | mullvad-rpc/Cargo.toml | 5 | ||||
| -rw-r--r-- | mullvad-rpc/src/event_loop.rs | 13 | ||||
| -rw-r--r-- | mullvad-rpc/src/lib.rs | 7 | ||||
| -rw-r--r-- | mullvad-rpc/src/rest.rs | 70 | ||||
| -rw-r--r-- | mullvad-types/src/location.rs | 15 | ||||
| -rw-r--r-- | mullvad-types/src/relay_list.rs | 3 |
13 files changed, 202 insertions, 74 deletions
diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index cbd18f1f03..86dfd7763c 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -196,7 +196,10 @@ impl Relay { 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!( + "\t{} ({}) @ {:.5}°N, {:.5}°W", + city.name, city.code, city.latitude, city.longitude + ); } println!(""); } diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs index db63978798..b904f4df4a 100644 --- a/mullvad-cli/src/cmds/status.rs +++ b/mullvad-cli/src/cmds/status.rs @@ -2,12 +2,10 @@ use Command; use Result; use clap; -use mullvad_types::location::Location; +use mullvad_types::location::GeoIpLocation; use mullvad_types::states::{DaemonState, SecurityState, TargetState}; use rpc; -use std::net::IpAddr; - pub struct Status; impl Command for Status { @@ -29,15 +27,18 @@ impl Command for Status { (SecurityState::Secured, TargetState::Secured) => println!("Connected"), } - let location: Location = rpc::call("get_current_location", &[] as &[u8; 0])?; - println!("Location: {}, {}", location.city, location.country); + let location: GeoIpLocation = rpc::call("get_current_location", &[] as &[u8; 0])?; + let city_and_country = if let Some(city) = location.city { + format!("{}, {}", city, location.country) + } else { + format!("{}", location.country) + }; + println!("Location: {}", city_and_country); println!( "Position: {:.5}°N, {:.5}°W", - location.position[0], location.position[1] + location.latitude, location.longitude ); - - let ip: IpAddr = rpc::call("get_public_ip", &[] as &[u8; 0])?; - println!("IP: {}", ip); + println!("IP: {}", location.ip); Ok(()) } } diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index 473454a94a..48bd3c59af 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -23,6 +23,7 @@ jsonrpc-ws-server = { git = "https://github.com/paritytech/jsonrpc", tag = "v7.1 uuid = { version = "0.5", features = ["v4"] } lazy_static = "1.0" rand = "0.3" +tokio-core = "0.1" tokio-timer = "0.1" regex = "0.2" diff --git a/mullvad-daemon/src/geoip.rs b/mullvad-daemon/src/geoip.rs new file mode 100644 index 0000000000..4118ed7871 --- /dev/null +++ b/mullvad-daemon/src/geoip.rs @@ -0,0 +1,32 @@ +use futures::{self, Future}; +use mullvad_rpc; +use mullvad_types::location::GeoIpLocation; +use serde_json; + + +static URI: &str = "https://am.i.mullvad.net/json"; + +error_chain! { + errors { + NoResponse { description("The request was dropped without any response") } + } + links { + Transport(mullvad_rpc::rest::Error, mullvad_rpc::rest::ErrorKind); + } + foreign_links { + Deserialize(serde_json::error::Error); + } +} + +pub fn send_location_request( + request_sender: mullvad_rpc::rest::RequestSender, +) -> Box<Future<Item = GeoIpLocation, Error = Error>> { + let (response_tx, response_rx) = futures::sync::oneshot::channel(); + let request = mullvad_rpc::rest::create_get_request(URI.parse().unwrap()); + let future = futures::Sink::send(request_sender, (request, response_tx)) + .map_err(|e| Error::with_chain(e, ErrorKind::NoResponse)) + .and_then(|_| response_rx.map_err(|e| Error::with_chain(e, ErrorKind::NoResponse))) + .and_then(|response_result| response_result.map_err(Error::from)) + .and_then(|response| serde_json::from_slice(&response).map_err(Error::from)); + Box::new(future) +} diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs index 32c86330f2..0668d09b6e 100644 --- a/mullvad-daemon/src/main.rs +++ b/mullvad-daemon/src/main.rs @@ -30,6 +30,7 @@ extern crate jsonrpc_ws_server; #[macro_use] extern crate lazy_static; extern crate rand; +extern crate tokio_core; extern crate tokio_timer; extern crate uuid; @@ -39,13 +40,14 @@ extern crate talpid_core; extern crate talpid_ipc; extern crate talpid_types; +mod account_history; mod cli; +mod geoip; mod management_interface; mod relays; mod rpc_info; mod settings; mod shutdown; -mod account_history; use app_dirs::AppInfo; @@ -57,14 +59,14 @@ use management_interface::{BoxFuture, ManagementInterfaceServer, TunnelCommand}; use mullvad_rpc::{AccountsProxy, HttpHandle}; use mullvad_types::account::{AccountData, AccountToken}; -use mullvad_types::location::Location; +use mullvad_types::location::GeoIpLocation; use mullvad_types::relay_constraints::{RelaySettings, RelaySettingsUpdate}; use mullvad_types::relay_list::{Relay, RelayList}; use mullvad_types::states::{DaemonState, SecurityState, TargetState}; use std::env; use std::io; -use std::net::{IpAddr, Ipv4Addr}; +use std::net::IpAddr; use std::path::{Path, PathBuf}; use std::sync::{mpsc, Arc, Mutex}; use std::thread; @@ -188,6 +190,8 @@ struct Daemon { management_interface_broadcaster: management_interface::EventBroadcaster, settings: settings::Settings, accounts_proxy: AccountsProxy<HttpHandle>, + http_handle: mullvad_rpc::rest::RequestSender, + tokio_remote: tokio_core::reactor::Remote, relay_selector: relays::RelaySelector, firewall: FirewallProxy, current_relay: Option<Relay>, @@ -201,11 +205,18 @@ impl Daemon { pub fn new(tunnel_log: Option<PathBuf>) -> Result<Self> { let resource_dir = get_resource_dir(); - let rpc_http_handle = mullvad_rpc::event_loop::create(|core| { - mullvad_rpc::shared(&core.handle()) - }).chain_err(|| "Unable to initialize network event loop")?; + let (rpc_handle, http_handle, tokio_remote) = + mullvad_rpc::event_loop::create(|core| { + let handle = core.handle(); + let rpc = mullvad_rpc::shared(&handle); + let http = mullvad_rpc::rest::create_http_client(&handle); + let remote = core.remote(); + (rpc, http, remote) + }).chain_err(|| "Unable to initialize network event loop")?; + let rpc_handle = rpc_handle.chain_err(|| "Unable to create RPC client")?; + let http_handle = http_handle.chain_err(|| "Unable to create HTTP client")?; - let relay_selector = Self::create_relay_selector(rpc_http_handle.clone(), &resource_dir)?; + let relay_selector = Self::create_relay_selector(rpc_handle.clone(), &resource_dir)?; let (tx, rx) = mpsc::channel(); let management_interface_broadcaster = Self::start_management_interface(tx.clone())?; @@ -224,7 +235,9 @@ impl Daemon { tx, management_interface_broadcaster, settings: settings::Settings::load().chain_err(|| "Unable to read settings")?, - accounts_proxy: AccountsProxy::new(rpc_http_handle), + accounts_proxy: AccountsProxy::new(rpc_handle), + http_handle, + tokio_remote, relay_selector, firewall: FirewallProxy::new().chain_err(|| ErrorKind::FirewallError)?, current_relay: None, @@ -236,10 +249,10 @@ impl Daemon { } fn create_relay_selector( - rpc_http_handle: mullvad_rpc::HttpHandle, + rpc_handle: mullvad_rpc::HttpHandle, resource_dir: &Path, ) -> Result<relays::RelaySelector> { - let mut relay_selector = relays::RelaySelector::new(rpc_http_handle, &resource_dir) + let mut relay_selector = relays::RelaySelector::new(rpc_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 { @@ -351,7 +364,6 @@ 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)), @@ -378,28 +390,28 @@ 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) + 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 { - 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"); + let http_handle = self.http_handle.clone(); + self.tokio_remote.spawn(move |_| { + geoip::send_location_request(http_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( diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 5bb79e8d14..3f3795183c 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -9,7 +9,7 @@ use jsonrpc_pubsub::{PubSubHandler, PubSubMetadata, Session, SubscriptionId}; use jsonrpc_ws_server; use mullvad_rpc; use mullvad_types::account::{AccountData, AccountToken}; -use mullvad_types::location::Location; +use mullvad_types::location::GeoIpLocation; use mullvad_types::relay_constraints::{RelaySettings, RelaySettingsUpdate}; use mullvad_types::relay_list::RelayList; @@ -19,7 +19,6 @@ use serde; use std::collections::HashMap; use std::collections::hash_map::Entry; -use std::net::IpAddr; use std::sync::{Arc, Mutex, RwLock}; use std::sync::atomic::{AtomicBool, Ordering}; use talpid_core::mpsc::IntoSender; @@ -99,14 +98,10 @@ build_rpc_trait! { #[rpc(meta, name = "get_state")] fn get_state(&self, Self::Metadata) -> BoxFuture<DaemonState, Error>; - /// Returns the current public IP of this computer. - #[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(meta, name = "get_current_location")] - fn get_current_location(&self, Self::Metadata) -> BoxFuture<Location, Error>; + fn get_current_location(&self, Self::Metadata) -> BoxFuture<GeoIpLocation, Error>; /// Makes the daemon exit its main loop and quit. #[rpc(meta, name = "shutdown")] @@ -149,10 +144,8 @@ 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>), + GetCurrentLocation(OneshotSender<GeoIpLocation>), /// Request the metadata for an account. GetAccountData( OneshotSender<BoxFuture<AccountData, mullvad_rpc::Error>>, @@ -505,16 +498,7 @@ impl<T: From<TunnelCommand> + 'static + Send> ManagementInterfaceApi for Managem Box::new(future) } - 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_current_location(&self, meta: Self::Metadata) -> BoxFuture<Location, Error> { + fn get_current_location(&self, meta: Self::Metadata) -> BoxFuture<GeoIpLocation, Error> { trace!("get_current_location"); try_future!(self.check_auth(&meta)); let (tx, rx) = sync::oneshot::channel(); diff --git a/mullvad-daemon/src/relays.rs b/mullvad-daemon/src/relays.rs index 299a0f940b..f19feb8edb 100644 --- a/mullvad-daemon/src/relays.rs +++ b/mullvad-daemon/src/relays.rs @@ -269,14 +269,16 @@ impl RelaySelector { city.has_active_relays = !city.relays.is_empty(); let city_name = city.name.clone(); let city_code = city.code.clone(); - let position = city.position; + let latitude = city.latitude; + let longitude = city.longitude; 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, + latitude, + longitude, }); relay })); diff --git a/mullvad-rpc/Cargo.toml b/mullvad-rpc/Cargo.toml index 69d6cd5bbf..39c17df17d 100644 --- a/mullvad-rpc/Cargo.toml +++ b/mullvad-rpc/Cargo.toml @@ -8,9 +8,14 @@ license = "GPL-3.0" [dependencies] chrono = { version = "0.4", features = ["serde"] } error-chain = "0.11" +futures = "0.1.15" jsonrpc-client-core = "0.2.1" jsonrpc-client-http = "0.2.1" serde_json = "1.0" tokio-core = "0.1" +hyper = "0.11" +hyper-tls = "0.1" +native-tls = "0.1" +log = "0.3" mullvad-types = { path = "../mullvad-types" } diff --git a/mullvad-rpc/src/event_loop.rs b/mullvad-rpc/src/event_loop.rs index aaae5900ec..09af5ed54c 100644 --- a/mullvad-rpc/src/event_loop.rs +++ b/mullvad-rpc/src/event_loop.rs @@ -4,18 +4,16 @@ use tokio_core::reactor::Core; error_chain! { errors { CoreError { description("Error when creating event loop") } - InitCallbackError { description("Error while executing supplied init closure") } } } /// Creates a new tokio event loop on a new thread, runs the provided `init` closure on the thread /// and sends back the result. /// Used to spawn futures on the core in the separate thread and be able to return sendable handles. -pub fn create<F, T, E>(init: F) -> Result<T> +pub fn create<F, T>(init: F) -> Result<T> where - F: FnOnce(&mut Core) -> ::std::result::Result<T, E> + Send + 'static, + F: FnOnce(&mut Core) -> T + Send + 'static, T: Send + 'static, - E: ::std::error::Error + Send + 'static, { let (tx, rx) = ::std::sync::mpsc::channel(); thread::spawn(move || match create_core(init) { @@ -30,12 +28,11 @@ where rx.recv().unwrap() } -fn create_core<F, T, E>(init: F) -> Result<(Core, T)> +fn create_core<F, T>(init: F) -> Result<(Core, T)> where - F: FnOnce(&mut Core) -> ::std::result::Result<T, E> + Send + 'static, - E: ::std::error::Error + Send + 'static, + F: FnOnce(&mut Core) -> T + Send + 'static, { let mut core = Core::new().chain_err(|| ErrorKind::CoreError)?; - let out = init(&mut core).chain_err(|| ErrorKind::InitCallbackError)?; + let out = init(&mut core); Ok((core, out)) } diff --git a/mullvad-rpc/src/lib.rs b/mullvad-rpc/src/lib.rs index 51da875891..ad20b1a0a3 100644 --- a/mullvad-rpc/src/lib.rs +++ b/mullvad-rpc/src/lib.rs @@ -9,9 +9,15 @@ extern crate chrono; #[macro_use] extern crate error_chain; +extern crate futures; +extern crate hyper; +extern crate hyper_tls; #[macro_use] extern crate jsonrpc_client_core; extern crate jsonrpc_client_http; +#[macro_use] +extern crate log; +extern crate native_tls; extern crate serde_json; extern crate tokio_core; @@ -31,6 +37,7 @@ use mullvad_types::relay_list::RelayList; use std::collections::HashMap; pub mod event_loop; +pub mod rest; static MASTER_API_URI: &str = "https://api.mullvad.net/rpc/"; diff --git a/mullvad-rpc/src/rest.rs b/mullvad-rpc/src/rest.rs new file mode 100644 index 0000000000..54123e6b28 --- /dev/null +++ b/mullvad-rpc/src/rest.rs @@ -0,0 +1,70 @@ +use futures::{future, Future, Stream}; +use futures::sync::{mpsc, oneshot}; + +use hyper; +use hyper::{Request, StatusCode, Uri}; +use hyper::client::Client; +use hyper_tls::HttpsConnector; +use native_tls; + +use tokio_core::reactor::Handle; + + +error_chain! { + errors { + /// When the http status code of the response is not 200 OK + HttpError(http_code: StatusCode) { + description("Http error. Server did not return 200 OK") + display("Http error. Status code {}", http_code) + } + } + foreign_links { + Tls(native_tls::Error); + Hyper(hyper::Error) #[doc = "An error occured in Hyper."]; + Uri(hyper::error::UriError) #[doc = "The string given was not a valid URI."]; + } +} + + +pub type RequestSender = mpsc::UnboundedSender<(Request, oneshot::Sender<Result<Vec<u8>>>)>; +type RequestReceiver = mpsc::UnboundedReceiver<(Request, oneshot::Sender<Result<Vec<u8>>>)>; + +pub fn create_http_client(handle: &Handle) -> Result<RequestSender> { + let connector = HttpsConnector::new(1, handle)?; + let client = Client::configure().connector(connector).build(handle); + let (request_tx, request_rx) = mpsc::unbounded(); + handle.spawn(create_request_processing_future(request_rx, client)); + Ok(request_tx) +} + +fn create_request_processing_future<CC: hyper::client::Connect>( + request_rx: RequestReceiver, + client: Client<CC, hyper::Body>, +) -> Box<Future<Item = (), Error = ()>> { + let f = request_rx.for_each(move |(request, response_tx)| { + trace!("Sending request to {}", request.uri()); + client + .request(request) + .from_err() + .and_then(|response: hyper::Response| { + if response.status() == hyper::StatusCode::Ok { + future::ok(response) + } else { + future::err(ErrorKind::HttpError(response.status()).into()) + } + }) + .and_then(|response: hyper::Response| response.body().concat2().from_err()) + .map(|response_chunk| response_chunk.to_vec()) + .then(move |response_result| { + if let Err(_) = response_tx.send(response_result) { + warn!("Unable to send response back to caller"); + } + Ok(()) + }) + }); + Box::new(f) as Box<Future<Item = (), Error = ()>> +} + +pub fn create_get_request(uri: Uri) -> Request { + Request::new(hyper::Method::Get, uri) +} diff --git a/mullvad-types/src/location.rs b/mullvad-types/src/location.rs index 6789e77ead..6c4ed94b00 100644 --- a/mullvad-types/src/location.rs +++ b/mullvad-types/src/location.rs @@ -1,3 +1,5 @@ +use std::net::IpAddr; + pub type CountryCode = String; pub type CityCode = String; @@ -7,5 +9,16 @@ pub struct Location { pub country_code: CountryCode, pub city: String, pub city_code: CityCode, - pub position: [f64; 2], + pub latitude: f64, + pub longitude: f64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GeoIpLocation { + pub ip: IpAddr, + pub country: String, + pub city: Option<String>, + pub latitude: f64, + pub longitude: f64, + pub mullvad_exit_ip: bool, } diff --git a/mullvad-types/src/relay_list.rs b/mullvad-types/src/relay_list.rs index 4eed3985d9..86d88ce615 100644 --- a/mullvad-types/src/relay_list.rs +++ b/mullvad-types/src/relay_list.rs @@ -21,7 +21,8 @@ pub struct RelayListCountry { pub struct RelayListCity { pub name: String, pub code: CityCode, - pub position: [f64; 2], + pub latitude: f64, + pub longitude: f64, #[serde(skip_deserializing)] pub has_active_relays: bool, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub relays: Vec<Relay>, } |
