summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--gui/packages/components/src/ConnectionInfo.tsx105
-rw-r--r--gui/packages/components/src/index.ts1
-rw-r--r--gui/packages/desktop/src/renderer/app.js14
-rw-r--r--gui/packages/desktop/src/renderer/components/Connect.js307
-rw-r--r--gui/packages/desktop/src/renderer/lib/daemon-rpc.js46
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/actions.js10
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/reducers.js4
-rw-r--r--mullvad-cli/src/cmds/status.rs9
-rw-r--r--mullvad-daemon/src/geoip.rs8
-rw-r--r--mullvad-daemon/src/lib.rs81
-rw-r--r--mullvad-types/src/location.rs3
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs3
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs8
-rw-r--r--talpid-types/src/net.rs2
-rw-r--r--talpid-types/src/tunnel.rs6
16 files changed, 413 insertions, 195 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4cc372980..638d9d935d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,7 @@ Line wrap the file at 100 chars. Th
with UDP fail in a row. If that also fails, alternate between UDP and TCP with random ports.
- Add new system and in-app notifications to inform the user when the app becomes outdated,
unsupported or may have security issues.
+- Allow the user to view the relay in/out IP address in the GUI.
### Fixed
- Pick new random relay for each reconnect attempt instead of just retrying with the same one.
diff --git a/gui/packages/components/src/ConnectionInfo.tsx b/gui/packages/components/src/ConnectionInfo.tsx
new file mode 100644
index 0000000000..b22c510c23
--- /dev/null
+++ b/gui/packages/components/src/ConnectionInfo.tsx
@@ -0,0 +1,105 @@
+import * as React from 'react';
+import { Component, Styles, Text, View } from 'reactxp';
+import { default as Accordion } from './Accordion';
+
+const styles = {
+ toggle: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 14,
+ fontWeight: '800',
+ color: 'rgb(255, 255, 255, 0.4)',
+ paddingBottom: 2,
+ }),
+ content: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 16,
+ fontWeight: '800',
+ color: 'rgb(255, 255, 255)',
+ paddingBottom: 2,
+ }),
+};
+
+interface IInAddress {
+ ip: string;
+ port: number;
+ protocol: string;
+}
+
+interface IOutAddress {
+ ipv4: string | null;
+ ipv6: string | null;
+}
+
+interface IProps {
+ inAddress: IInAddress;
+ outAddress: IOutAddress;
+ startExpanded: boolean;
+ onToggle: (expanded: boolean) => void;
+}
+
+interface IState {
+ expanded: boolean;
+}
+
+export default class ConnectionInfo extends Component<IProps, IState> {
+ public static defaultProps = {
+ inAddress: {
+ ip: null,
+ port: null,
+ protocol: null,
+ },
+ outAddress: null,
+ startExpanded: false,
+ onToggle: (_: boolean) => {},
+ };
+
+ constructor(props: IProps) {
+ super(props);
+
+ this.state = {
+ expanded: props.startExpanded,
+ };
+ }
+
+ public render() {
+ return (
+ <View>
+ <Accordion height={this.state.expanded ? 'auto' : 0}>
+ <Text style={styles.content}>{this.inAddress()}</Text>
+ <Text style={styles.content}>{this.outAddress()}</Text>
+ </Accordion>
+ <Text style={styles.toggle} onPress={() => this.toggle()}>
+ {this.state.expanded ? 'LESS' : 'MORE'}
+ </Text>
+ </View>
+ );
+ }
+
+ private toggle() {
+ this.setState((state, props) => {
+ const expanded = !state.expanded;
+
+ props.onToggle(expanded);
+
+ return { expanded };
+ });
+ }
+
+ private inAddress() {
+ const { ip, port, protocol } = this.props.inAddress;
+
+ return (
+ 'IN: ' + (ip || '<unknown>') + (port ? `:${port}` : '') + (protocol ? ` - ${protocol}` : '')
+ );
+ }
+
+ private outAddress() {
+ const { ipv4, ipv6 } = this.props.outAddress;
+
+ if (ipv4 || ipv6) {
+ return `OUT: ${ipv4}` + (ipv6 ? ` / ${ipv6}` : '');
+ } else {
+ return '';
+ }
+ }
+}
diff --git a/gui/packages/components/src/index.ts b/gui/packages/components/src/index.ts
index 7d5ad156f0..648ddabdf9 100644
--- a/gui/packages/components/src/index.ts
+++ b/gui/packages/components/src/index.ts
@@ -1,3 +1,4 @@
export { default as Accordion } from './Accordion';
export { default as ClipboardLabel } from './ClipboardLabel';
+export { default as ConnectionInfo } from './ConnectionInfo';
export { default as SecuredLabel, SecuredDisplayStyle } from './SecuredLabel';
diff --git a/gui/packages/desktop/src/renderer/app.js b/gui/packages/desktop/src/renderer/app.js
index 1ef54ca4e9..2ae7b66f9c 100644
--- a/gui/packages/desktop/src/renderer/app.js
+++ b/gui/packages/desktop/src/renderer/app.js
@@ -229,7 +229,7 @@ export default class AppRenderer {
if (tunnelState.state === 'disconnected' || tunnelState.state === 'blocked') {
// switch to connecting state immediately to prevent a lag that may be caused by RPC
// communication delay
- actions.connection.connecting();
+ actions.connection.connecting(null);
await this._daemonRpc.connectTunnel();
}
@@ -478,12 +478,6 @@ export default class AppRenderer {
await this._setStartView();
try {
- await this._fetchLocation();
- } catch (error) {
- log.error(`Cannot fetch the location: ${error.message}`);
- }
-
- try {
await this._fetchLatestVersionInfo();
} catch (error) {
log.error(`Cannot fetch the latest version information: ${error.message}`);
@@ -602,7 +596,7 @@ export default class AppRenderer {
}
async _updateUserLocation(tunnelState: TunnelState) {
- if (tunnelState === 'connecting' || tunnelState === 'disconnected') {
+ if (['connected', 'connecting', 'disconnected'].includes(tunnelState)) {
try {
await this._fetchLocation();
} catch (error) {
@@ -616,11 +610,11 @@ export default class AppRenderer {
switch (stateTransition.state) {
case 'connecting':
- actions.connection.connecting();
+ actions.connection.connecting(stateTransition.details);
break;
case 'connected':
- actions.connection.connected();
+ actions.connection.connected(stateTransition.details);
break;
case 'disconnecting':
diff --git a/gui/packages/desktop/src/renderer/components/Connect.js b/gui/packages/desktop/src/renderer/components/Connect.js
index a50227b289..3602410c86 100644
--- a/gui/packages/desktop/src/renderer/components/Connect.js
+++ b/gui/packages/desktop/src/renderer/components/Connect.js
@@ -3,7 +3,7 @@
import moment from 'moment';
import * as React from 'react';
import { Component, Text, View, Types } from 'reactxp';
-import { SecuredLabel, SecuredDisplayStyle } from '@mullvad/components';
+import { ConnectionInfo, SecuredLabel, SecuredDisplayStyle } from '@mullvad/components';
import { Layout, Container, Header } from './Layout';
import { SettingsBarButton, Brand } from './HeaderBar';
import NotificationArea from './NotificationArea';
@@ -136,6 +136,19 @@ export default class Connect extends Component<Props> {
}
renderMap() {
+ const tunnelState = this.props.connection.status.state;
+ const details = this.props.connection.status.details;
+
+ let relayIp = null;
+ let relayPort = null;
+ let relayProtocol = null;
+
+ if ((tunnelState === 'connecting' || tunnelState === 'connected') && details) {
+ relayIp = details.address;
+ relayPort = details.tunnel.openvpn.port;
+ relayProtocol = details.tunnel.openvpn.protocol;
+ }
+
return (
<View style={styles.connect}>
<View style={styles.map}>
@@ -143,19 +156,23 @@ export default class Connect extends Component<Props> {
</View>
<View style={styles.container}>
{/* show spinner when connecting */}
- {this.props.connection.status.state === 'connecting' ? (
+ {tunnelState === 'connecting' ? (
<View style={styles.status_icon}>
<Img source="icon-spinner" height={60} width={60} alt="" />
</View>
) : null}
<TunnelControl
- style={styles.tunnel_control}
tunnelState={this.props.connection.status.state}
selectedRelayName={this.props.selectedRelayName}
city={this.props.connection.city}
country={this.props.connection.country}
hostname={this.props.connection.hostname}
+ relayIp={relayIp}
+ relayPort={relayPort}
+ relayProtocol={relayProtocol}
+ outIpv4={this.props.connection.ip}
+ outIpv6={null}
onConnect={this.props.onConnect}
onDisconnect={this.props.onDisconnect}
onSelectLocation={this.props.onSelectLocation}
@@ -220,144 +237,190 @@ type TunnelControlProps = {
city: ?string,
country: ?string,
hostname: ?string,
+ relayIp: ?string,
+ relayPort: ?number,
+ relayProtocol: ?string,
+ outIpv4: ?string,
+ outIpv6: ?string,
onConnect: () => void,
onDisconnect: () => void,
onSelectLocation: () => void,
- style: Types.ViewStyleRuleSet,
};
-export function TunnelControl(props: TunnelControlProps) {
- const Location = ({ children }) => <View style={styles.status_location}>{children}</View>;
- const City = () => <Text style={styles.status_location_text}>{props.city}</Text>;
- const Country = () => <Text style={styles.status_location_text}>{props.country}</Text>;
- const Hostname = () => <Text style={styles.status_hostname}>{props.hostname || ''}</Text>;
+type TunnelControlState = {
+ showConnectionInfo: boolean,
+};
- const SwitchLocation = () => {
- return (
+class TunnelControl extends Component<TunnelControlProps, TunnelControlState> {
+ state = {
+ showConnectionInfo: false,
+ };
+
+ render() {
+ const Location = ({ children }) => <View style={styles.status_location}>{children}</View>;
+ const City = () => <Text style={styles.status_location_text}>{this.props.city}</Text>;
+ const Country = () => <Text style={styles.status_location_text}>{this.props.country}</Text>;
+ const Hostname = () => <Text style={styles.status_hostname}>{this.props.hostname || ''}</Text>;
+
+ const SwitchLocation = () => {
+ return (
+ <AppButton.TransparentButton
+ style={styles.switch_location_button}
+ onPress={this.props.onSelectLocation}>
+ <AppButton.Label>{'Switch location'}</AppButton.Label>
+ </AppButton.TransparentButton>
+ );
+ };
+
+ const SelectedLocation = () => (
<AppButton.TransparentButton
style={styles.switch_location_button}
- onPress={props.onSelectLocation}>
- <AppButton.Label>{'Switch location'}</AppButton.Label>
+ onPress={this.props.onSelectLocation}>
+ <AppButton.Label>{this.props.selectedRelayName}</AppButton.Label>
+ <Img height={12} width={7} source="icon-chevron" />
</AppButton.TransparentButton>
);
- };
- const SelectedLocation = () => (
- <AppButton.TransparentButton
- style={styles.switch_location_button}
- onPress={props.onSelectLocation}>
- <AppButton.Label>{props.selectedRelayName}</AppButton.Label>
- <Img height={12} width={7} source="icon-chevron" />
- </AppButton.TransparentButton>
- );
+ const Connect = () => (
+ <AppButton.GreenButton onPress={this.props.onConnect}>
+ {'Secure my connection'}
+ </AppButton.GreenButton>
+ );
- const Connect = () => (
- <AppButton.GreenButton onPress={props.onConnect}>
- {'Secure my connection'}
- </AppButton.GreenButton>
- );
+ const Disconnect = () => (
+ <AppButton.RedTransparentButton onPress={this.props.onDisconnect}>
+ {'Disconnect'}
+ </AppButton.RedTransparentButton>
+ );
- const Disconnect = () => (
- <AppButton.RedTransparentButton onPress={props.onDisconnect}>
- {'Disconnect'}
- </AppButton.RedTransparentButton>
- );
+ const Cancel = () => (
+ <AppButton.RedTransparentButton onPress={this.props.onDisconnect}>
+ {'Cancel'}
+ </AppButton.RedTransparentButton>
+ );
- const Cancel = () => (
- <AppButton.RedTransparentButton onPress={props.onDisconnect}>
- {'Cancel'}
- </AppButton.RedTransparentButton>
- );
+ const Secured = ({ displayStyle }) => (
+ <SecuredLabel style={styles.status_security} displayStyle={displayStyle} />
+ );
+ const Footer = ({ children }) => <View style={styles.footer}>{children}</View>;
- const Secured = ({ displayStyle }) => (
- <SecuredLabel style={styles.status_security} displayStyle={displayStyle} />
- );
- const Wrapper = ({ children }) => <View style={props.style}>{children}</View>;
- const Body = ({ children }) => <View style={styles.body}>{children}</View>;
- const Footer = ({ children }) => <View style={styles.footer}>{children}</View>;
+ const connectionInfoProps = {
+ inAddress: {
+ ip: this.props.relayIp,
+ port: this.props.relayPort,
+ protocol: this.props.relayProtocol,
+ },
+ outAddress: {
+ ipv4: this.props.outIpv4,
+ ipv6: this.props.outIpv6,
+ },
+ startExpanded: this.state.showConnectionInfo,
+ onToggle: (expanded) => {
+ this.setState({ showConnectionInfo: expanded });
+ },
+ };
- switch (props.tunnelState) {
- case 'connecting':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.securing} />
- <Location>
- <City />
- <Country />
- </Location>
- <Hostname />
- </Body>
- <Footer>
- <SwitchLocation />
- <Cancel />
- </Footer>
- </Wrapper>
- );
- case 'connected':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.secured} />
- <Location>
- <City />
- <Country />
- </Location>
- <Hostname />
- </Body>
- <Footer>
- <SwitchLocation />
- <Disconnect />
- </Footer>
- </Wrapper>
- );
+ switch (this.props.tunnelState) {
+ case 'connecting':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.securing} />
+ <Location>
+ <City />
+ <Country />
+ </Location>
+ <Hostname />
+ <ConnectionInfo {...connectionInfoProps} />
+ </Body>
+ <Footer>
+ <SwitchLocation />
+ <Cancel />
+ </Footer>
+ </Wrapper>
+ );
+ case 'connected':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.secured} />
+ <Location>
+ <City />
+ <Country />
+ </Location>
+ <Hostname />
+ <ConnectionInfo {...connectionInfoProps} />
+ </Body>
+ <Footer>
+ <SwitchLocation />
+ <Disconnect />
+ </Footer>
+ </Wrapper>
+ );
- case 'blocked':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.blocked} />
- </Body>
- <Footer>
- <SwitchLocation />
- <Cancel />
- </Footer>
- </Wrapper>
- );
+ case 'blocked':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.blocked} />
+ </Body>
+ <Footer>
+ <SwitchLocation />
+ <Cancel />
+ </Footer>
+ </Wrapper>
+ );
- case 'disconnecting':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.secured} />
- <Location>
- <Country />
- </Location>
- </Body>
- <Footer>
- <SelectedLocation />
- <Connect />
- </Footer>
- </Wrapper>
- );
+ case 'disconnecting':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.secured} />
+ <Location>
+ <Country />
+ </Location>
+ </Body>
+ <Footer>
+ <SelectedLocation />
+ <Connect />
+ </Footer>
+ </Wrapper>
+ );
- case 'disconnected':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.unsecured} />
- <Location>
- <Country />
- </Location>
- </Body>
- <Footer>
- <SelectedLocation />
- <Connect />
- </Footer>
- </Wrapper>
- );
+ case 'disconnected':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.unsecured} />
+ <Location>
+ <Country />
+ </Location>
+ </Body>
+ <Footer>
+ <SelectedLocation />
+ <Connect />
+ </Footer>
+ </Wrapper>
+ );
+
+ default:
+ throw new Error(`Unknown TunnelState: ${(this.props.tunnelState: empty)}`);
+ }
+ }
+}
+
+type ContainerProps = {
+ children?: Types.ReactNode,
+};
- default:
- throw new Error(`Unknown TunnelState: ${(props.tunnelState: empty)}`);
+class Wrapper extends Component<ContainerProps> {
+ render() {
+ return <View style={styles.tunnel_control}>{this.props.children}</View>;
+ }
+}
+
+class Body extends Component<ContainerProps> {
+ render() {
+ return <View style={styles.body}>{this.props.children}</View>;
}
}
diff --git a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js b/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
index 572954b26b..594a33fdf6 100644
--- a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
+++ b/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
@@ -25,6 +25,7 @@ export type AccountData = { expiry: string };
export type AccountToken = string;
export type Ip = string;
export type Location = {
+ ip: ?string,
country: string,
city: ?string,
latitude: number,
@@ -33,6 +34,7 @@ export type Location = {
hostname: ?string,
};
const LocationSchema = object({
+ ip: maybe(string),
country: string,
city: maybe(string),
latitude: number,
@@ -55,8 +57,22 @@ export type AfterDisconnect = 'nothing' | 'block' | 'reconnect';
export type TunnelState = 'connecting' | 'connected' | 'disconnecting' | 'disconnected' | 'blocked';
+export type TunnelEndpoint = {
+ address: string,
+ tunnel: TunnelEndpointData,
+};
+
+export type TunnelEndpointData = {
+ openvpn: {
+ port: number,
+ protocol: RelayProtocol,
+ },
+};
+
export type TunnelStateTransition =
- | { state: 'disconnected' | 'connecting' | 'connected' }
+ | { state: 'disconnected' }
+ | { state: 'connecting', details: ?TunnelEndpoint }
+ | { state: 'connected', details: TunnelEndpoint }
| { state: 'disconnecting', details: AfterDisconnect }
| { state: 'blocked', details: BlockReason };
@@ -91,12 +107,7 @@ type RelaySettingsNormal<TTunnelConstraints> = {
// types describing the structure of RelaySettings
export type RelaySettingsCustom = {
host: string,
- tunnel: {
- openvpn: {
- port: number,
- protocol: RelayProtocol,
- },
- },
+ tunnel: TunnelEndpointData,
};
export type RelaySettings =
| {|
@@ -127,6 +138,13 @@ const constraint = <T>(constraintValue: SchemaNode<T>) => {
);
};
+const TunnelEndpointDataSchema = object({
+ openvpn: object({
+ port: number,
+ protocol: enumeration('udp', 'tcp'),
+ }),
+});
+
const RelaySettingsSchema = oneOf(
object({
normal: object({
@@ -156,12 +174,7 @@ const RelaySettingsSchema = oneOf(
object({
custom_tunnel_endpoint: object({
host: string,
- tunnel: object({
- openvpn: object({
- port: number,
- protocol: enumeration('udp', 'tcp'),
- }),
- }),
+ tunnel: TunnelEndpointDataSchema,
}),
}),
);
@@ -240,6 +253,13 @@ const TunnelStateTransitionSchema = oneOf(
details: enumeration('nothing', 'block', 'reconnect'),
}),
object({
+ state: enumeration('connecting', 'connected'),
+ details: object({
+ address: string,
+ tunnel: TunnelEndpointDataSchema,
+ }),
+ }),
+ object({
state: enumeration('blocked'),
details: oneOf(
object({
diff --git a/gui/packages/desktop/src/renderer/redux/connection/actions.js b/gui/packages/desktop/src/renderer/redux/connection/actions.js
index 668eb2a67b..6f8a297678 100644
--- a/gui/packages/desktop/src/renderer/redux/connection/actions.js
+++ b/gui/packages/desktop/src/renderer/redux/connection/actions.js
@@ -1,13 +1,15 @@
// @flow
-import type { AfterDisconnect, BlockReason } from '../../lib/daemon-rpc';
+import type { AfterDisconnect, BlockReason, TunnelEndpoint } from '../../lib/daemon-rpc';
type ConnectingAction = {
type: 'CONNECTING',
+ tunnelEndpoint: ?TunnelEndpoint,
};
type ConnectedAction = {
type: 'CONNECTED',
+ tunnelEndpoint: TunnelEndpoint,
};
type DisconnectedAction = {
@@ -54,15 +56,17 @@ export type ConnectionAction =
| OnlineAction
| OfflineAction;
-function connecting(): ConnectingAction {
+function connecting(tunnelEndpoint: ?TunnelEndpoint): ConnectingAction {
return {
type: 'CONNECTING',
+ tunnelEndpoint,
};
}
-function connected(): ConnectedAction {
+function connected(tunnelEndpoint: TunnelEndpoint): ConnectedAction {
return {
type: 'CONNECTED',
+ tunnelEndpoint,
};
}
diff --git a/gui/packages/desktop/src/renderer/redux/connection/reducers.js b/gui/packages/desktop/src/renderer/redux/connection/reducers.js
index b90f285e4f..622663dc14 100644
--- a/gui/packages/desktop/src/renderer/redux/connection/reducers.js
+++ b/gui/packages/desktop/src/renderer/redux/connection/reducers.js
@@ -32,10 +32,10 @@ export default function(
return { ...state, ...action.newLocation };
case 'CONNECTING':
- return { ...state, status: { state: 'connecting' } };
+ return { ...state, status: { state: 'connecting', details: action.tunnelEndpoint } };
case 'CONNECTED':
- return { ...state, status: { state: 'connected' } };
+ return { ...state, status: { state: 'connected', details: action.tunnelEndpoint } };
case 'DISCONNECTED':
return { ...state, status: { state: 'disconnected' } };
diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs
index 11e436d34a..d25a591726 100644
--- a/mullvad-cli/src/cmds/status.rs
+++ b/mullvad-cli/src/cmds/status.rs
@@ -31,8 +31,9 @@ impl Command for Status {
for new_state in rpc.new_state_subscribe()? {
print_state(&new_state);
- if new_state == Connected || new_state == Disconnected {
- print_location(&mut rpc)?;
+ match new_state {
+ Connected(_) | Disconnected => print_location(&mut rpc)?,
+ _ => {}
}
}
}
@@ -44,8 +45,8 @@ fn print_state(state: &TunnelStateTransition) {
print!("Tunnel status: ");
match state {
Blocked(reason) => println!("Blocked ({})", reason),
- Connected => println!("Connected"),
- Connecting => println!("Connecting..."),
+ Connected(_) => println!("Connected"),
+ Connecting(_) => println!("Connecting..."),
Disconnected => println!("Disconnected"),
Disconnecting(_) => println!("Disconnecting..."),
}
diff --git a/mullvad-daemon/src/geoip.rs b/mullvad-daemon/src/geoip.rs
index 01f42ec6c9..2faed344a6 100644
--- a/mullvad-daemon/src/geoip.rs
+++ b/mullvad-daemon/src/geoip.rs
@@ -21,13 +21,13 @@ error_chain! {
pub fn send_location_request(
request_sender: mullvad_rpc::rest::RequestSender,
-) -> Box<Future<Item = GeoIpLocation, Error = Error>> {
+) -> impl 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.clone(), (request, response_tx))
+
+ 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)
+ .and_then(|response| serde_json::from_slice(&response).map_err(Error::from))
}
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index ad62336a38..e88c4cd0e1 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -45,6 +45,7 @@ mod rpc_uniqueness_check;
use error_chain::ChainedError;
use futures::{
+ future,
sync::{mpsc::UnboundedSender, oneshot},
Future, Sink,
};
@@ -444,36 +445,58 @@ impl Daemon {
fn on_get_current_location(&self, tx: oneshot::Sender<GeoIpLocation>) {
use self::TunnelStateTransition::*;
- match self.tunnel_state {
- Disconnected => {
- 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());
- })
- });
- }
- Connecting | Connected | Disconnecting(..) => {
- if let Some(ref relay) = self.last_generated_relay {
- let location = relay.location.as_ref().cloned().unwrap();
- let hostname = relay.hostname.clone();
- let geo_ip_location = GeoIpLocation {
- country: location.country,
- city: Some(location.city),
- latitude: location.latitude,
- longitude: location.longitude,
- mullvad_exit_ip: true,
- hostname: Some(hostname),
- };
- Self::oneshot_send(tx, geo_ip_location, "current location");
+ let get_location: Box<dyn Future<Item = GeoIpLocation, Error = ()> + Send> =
+ match self.tunnel_state {
+ Disconnected => Box::new(self.get_geo_location()),
+ Connecting(_) | Disconnecting(..) => {
+ Box::new(future::result(Ok(self.build_location_from_relay())))
}
- }
- Blocked(..) => {
- // We are not online at all at this stage. Return error.
- mem::drop(tx);
- }
+ Connected(_) => {
+ let location_from_relay = self.build_location_from_relay();
+ Box::new(
+ self.get_geo_location()
+ .map(|fetched_location| GeoIpLocation {
+ ip: fetched_location.ip,
+ ..location_from_relay
+ }),
+ )
+ }
+ Blocked(..) => {
+ // We are not online at all at this stage. Return error.
+ mem::drop(tx);
+ return;
+ }
+ };
+
+ self.tokio_remote.spawn(move |_| {
+ get_location.map(|location| Self::oneshot_send(tx, location, "current location"))
+ });
+ }
+
+ fn get_geo_location(&self) -> impl Future<Item = GeoIpLocation, Error = ()> {
+ let https_handle = self.https_handle.clone();
+
+ geoip::send_location_request(https_handle).map_err(|e| {
+ warn!("Unable to fetch GeoIP location: {}", e.display_chain());
+ })
+ }
+
+ fn build_location_from_relay(&self) -> GeoIpLocation {
+ let relay = self
+ .last_generated_relay
+ .as_ref()
+ .expect("Can't build location from relay in disconnected state");
+ let location = relay.location.as_ref().cloned().unwrap();
+ let hostname = relay.hostname.clone();
+
+ GeoIpLocation {
+ ip: None,
+ country: location.country,
+ city: Some(location.city),
+ latitude: location.latitude,
+ longitude: location.longitude,
+ mullvad_exit_ip: true,
+ hostname: Some(hostname),
}
}
diff --git a/mullvad-types/src/location.rs b/mullvad-types/src/location.rs
index 57d1e31269..2c420fafc5 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;
pub type Hostname = String;
@@ -14,6 +16,7 @@ pub struct Location {
#[derive(Debug, Serialize, Deserialize)]
pub struct GeoIpLocation {
+ pub ip: Option<IpAddr>,
pub country: String,
pub city: Option<String>,
pub latitude: f64,
diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs
index a969ece39f..d648b43ec4 100644
--- a/talpid-core/src/tunnel_state_machine/connected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connected_state.rs
@@ -149,12 +149,13 @@ impl TunnelState for ConnectedState {
shared_values: &mut SharedTunnelStateValues,
bootstrap: Self::Bootstrap,
) -> (TunnelStateWrapper, TunnelStateTransition) {
+ let tunnel_endpoint = bootstrap.tunnel_parameters.endpoint;
let connected_state = ConnectedState::from(bootstrap);
match connected_state.set_security_policy(shared_values) {
Ok(()) => (
TunnelStateWrapper::from(connected_state),
- TunnelStateTransition::Connected,
+ TunnelStateTransition::Connected(tunnel_endpoint),
),
Err(error) => {
error!("{}", error.display_chain());
diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs
index 1e0880141e..f2ce3e0b4e 100644
--- a/talpid-core/src/tunnel_state_machine/connecting_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs
@@ -283,9 +283,9 @@ impl TunnelState for ConnectingState {
{
None => BlockedState::enter(shared_values, BlockReason::NoMatchingRelay),
Some(tunnel_parameters) => {
- if let Err(error) =
- Self::set_security_policy(shared_values, tunnel_parameters.endpoint)
- {
+ let tunnel_endpoint = tunnel_parameters.endpoint;
+
+ if let Err(error) = Self::set_security_policy(shared_values, tunnel_endpoint) {
error!("{}", error.display_chain());
BlockedState::enter(shared_values, BlockReason::StartTunnelError)
} else {
@@ -297,7 +297,7 @@ impl TunnelState for ConnectingState {
) {
Ok(connecting_state) => (
TunnelStateWrapper::from(connecting_state),
- TunnelStateTransition::Connecting,
+ TunnelStateTransition::Connecting(tunnel_endpoint),
),
Err(error) => {
let block_reason = match *error.kind() {
diff --git a/talpid-types/src/net.rs b/talpid-types/src/net.rs
index e00d0d9fe4..e871e7a93e 100644
--- a/talpid-types/src/net.rs
+++ b/talpid-types/src/net.rs
@@ -4,7 +4,7 @@ use std::net::{IpAddr, SocketAddr};
use std::str::FromStr;
/// Represents one tunnel endpoint. Address, plus extra parameters specific to tunnel protocol.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct TunnelEndpoint {
pub address: IpAddr,
pub tunnel: TunnelEndpointData,
diff --git a/talpid-types/src/tunnel.rs b/talpid-types/src/tunnel.rs
index 3d911f640a..cfe1472cdf 100644
--- a/talpid-types/src/tunnel.rs
+++ b/talpid-types/src/tunnel.rs
@@ -1,5 +1,7 @@
use std::fmt;
+use super::net::TunnelEndpoint;
+
/// Event resulting from a transition to a new tunnel state.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@@ -8,9 +10,9 @@ pub enum TunnelStateTransition {
/// No connection is established and network is unsecured.
Disconnected,
/// Network is secured but tunnel is still connecting.
- Connecting,
+ Connecting(TunnelEndpoint),
/// Tunnel is connected.
- Connected,
+ Connected(TunnelEndpoint),
/// Disconnecting tunnel.
Disconnecting(ActionAfterDisconnect),
/// Tunnel is disconnected but secured by blocking all connections.