summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md4
-rw-r--r--Cargo.lock9
-rw-r--r--app/components/Connect.js40
-rw-r--r--app/lib/backend.js26
-rw-r--r--app/lib/ipc-facade.js43
-rw-r--r--app/redux/connection/actions.js42
-rw-r--r--app/redux/connection/reducers.js15
-rw-r--r--app/redux/settings/reducers.js3
-rw-r--r--dist-assets/relays.json426
-rwxr-xr-xformat.sh2
-rw-r--r--mullvad-cli/src/cmds/relay.rs8
-rw-r--r--mullvad-cli/src/cmds/status.rs19
-rw-r--r--mullvad-daemon/Cargo.toml4
-rw-r--r--mullvad-daemon/src/bin/list-relays.rs2
-rw-r--r--mullvad-daemon/src/geoip.rs32
-rw-r--r--mullvad-daemon/src/main.rs78
-rw-r--r--mullvad-daemon/src/management_interface.rs25
-rw-r--r--mullvad-daemon/src/relays.rs6
-rw-r--r--mullvad-rpc/Cargo.toml7
-rw-r--r--mullvad-rpc/src/event_loop.rs38
-rw-r--r--mullvad-rpc/src/lib.rs21
-rw-r--r--mullvad-rpc/src/rest.rs70
-rw-r--r--mullvad-types/src/location.rs15
-rw-r--r--mullvad-types/src/relay_list.rs12
-rw-r--r--test/components/Connect.spec.js16
-rw-r--r--test/components/SelectLocation.spec.js6
-rw-r--r--test/connection-info.spec.js46
-rw-r--r--test/mocks/ipc.js9
28 files changed, 591 insertions, 433 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 661731428f..fa60a301b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
## [Unreleased]
+### Added
+- Uses the https://am.i.mullvad.net/ service to figure out location and public IP of the device.
+ The app then shows this information in the unsecured state.
+
### Fixed
- Fixed bug where problem report tool would redact some things in the logs which were not IPv6
addresses, but looked like ones.
diff --git a/Cargo.lock b/Cargo.lock
index 75f7e38624..bedbdb76f0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -701,12 +701,12 @@ dependencies = [
"rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"simple-signal 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"talpid-core 0.1.0",
"talpid-ipc 0.1.0",
"talpid-types 0.1.0",
+ "tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -716,10 +716,17 @@ name = "mullvad-rpc"
version = "0.1.0"
dependencies = [
"chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hyper 0.11.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-client-core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-client-http 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"mullvad-types 0.1.0",
+ "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
diff --git a/app/components/Connect.js b/app/components/Connect.js
index fdbe927b77..abbf7785a9 100644
--- a/app/components/Connect.js
+++ b/app/components/Connect.js
@@ -153,19 +153,6 @@ export default class Connect extends Component {
: './assets/images/location-marker-unsecure.svg' } />
*/
- let ipComponent = undefined;
- if (isConnected || isDisconnected) {
- if (this.state.showCopyIPMessage) {
- ipComponent = (<span>{ 'IP copied to clipboard!' }</span>);
- } else {
- // TODO: remove empty IP placeholder when implemented in backend.
- if(isDisconnected) {
- ipComponent = (<span>{ '\u2003' }</span>);
- } else {
- ipComponent = (<span>{ this.props.connection.clientIp }</span>);
- }
- }
- }
return (
<div className="connect">
<div className="connect__map">
@@ -188,18 +175,8 @@ export default class Connect extends Component {
**********************************
*/ }
- { /* location when disconnected.
- TODO: merge with the isConnecting block below when implemented in backend.
- */ }
- { isDisconnected ?
- <div className="connect__status-location">
- <span>{ '\u2002' }</span>
- </div>
- : null
- }
-
- { /* location when connecting */ }
- { isConnecting ?
+ { /* location when connecting or disconnected */ }
+ { isConnecting || isDisconnected ?
<div className="connect__status-location">
<span>{ this.props.connection.country }</span>
</div>
@@ -209,9 +186,11 @@ export default class Connect extends Component {
{ /* location when connected */ }
{ isConnected ?
<div className="connect__status-location">
- { this.props.connection.city }<br/>{ this.props.connection.country }
+ { this.props.connection.city }
+ { this.props.connection.city && <br/> }
+ { this.props.connection.country }
</div>
- : null
+ :null
}
{ /*
@@ -221,7 +200,12 @@ export default class Connect extends Component {
*/ }
<div className={ this.ipAddressClass() } onClick={ this.onIPAddressClick.bind(this) }>
- { ipComponent }
+ { (isConnected || isDisconnected) ? (
+ <span>{
+ this.state.showCopyIPMessage ?
+ 'IP copied to clipboard!' :
+ this.props.connection.ip
+ }</span>) : null }
</div>
</div>
diff --git a/app/lib/backend.js b/app/lib/backend.js
index cd14adb045..9439e09256 100644
--- a/app/lib/backend.js
+++ b/app/lib/backend.js
@@ -128,12 +128,6 @@ export class Backend {
}
try {
- await this._fetchPublicIP();
- } catch(e) {
- log.error('Failed to fetch the public IP: ', e.message);
- }
-
- try {
await this._fetchLocation();
} catch(e) {
log.error('Failed to fetch the location: ', e.message);
@@ -359,7 +353,8 @@ export class Backend {
cities: country.cities.map((city) => ({
name: city.name,
code: city.code,
- position: city.position,
+ latitude: city.latitude,
+ longitude: city.longitude,
hasActiveRelays: city.has_active_relays,
}))
}));
@@ -369,18 +364,6 @@ export class Backend {
);
}
- async _fetchPublicIP() {
- await this._ensureAuthenticated();
-
- const publicIp = await this._ipc.getPublicIp();
-
- log.info('Got public IP: ', publicIp);
-
- this._store.dispatch(
- connectionActions.newPublicIp(publicIp)
- );
- }
-
async _fetchLocation() {
await this._ensureAuthenticated();
@@ -389,9 +372,12 @@ export class Backend {
log.info('Got location: ', location);
const locationUpdate = {
+ ip: location.ip,
country: location.country,
city: location.city,
- location: location.position
+ latitude: location.latitude,
+ longitude: location.longitude,
+ mullvadExitIp: location.mullvad_exit_ip,
};
this._store.dispatch(
diff --git a/app/lib/ipc-facade.js b/app/lib/ipc-facade.js
index b2676193ee..244e6bcc4b 100644
--- a/app/lib/ipc-facade.js
+++ b/app/lib/ipc-facade.js
@@ -1,25 +1,27 @@
// @flow
import JsonRpcWs, { InvalidReply } from './jsonrpc-ws-ipc';
-import { object, string, number, boolean, enumeration, arrayOf, oneOf } from 'validated/schema';
+import { object, maybe, string, number, boolean, enumeration, arrayOf, oneOf } from 'validated/schema';
import { validate } from 'validated/object';
-import type { Coordinate2d } from '../types';
-
export type AccountData = { expiry: string };
export type AccountToken = string;
export type Ip = string;
export type Location = {
+ ip: Ip,
country: string,
- city: string,
- position: Coordinate2d,
+ city: ?string,
+ latitude: number,
+ longitude: number,
+ mullvad_exit_ip: boolean,
};
const LocationSchema = object({
+ ip: string,
country: string,
- country_code: string,
- city: string,
- city_code: string,
- position: arrayOf(number),
+ city: maybe(string),
+ latitude: number,
+ longitude: number,
+ mullvad_exit_ip: boolean,
});
export type SecurityState = 'secured' | 'unsecured';
@@ -121,7 +123,8 @@ export type RelayListCountry = {
export type RelayListCity = {
name: string,
code: string,
- position: [number, number],
+ latitude: number,
+ longitude: number,
has_active_relays: boolean,
};
@@ -132,7 +135,8 @@ const RelayListSchema = object({
cities: arrayOf(object({
name: string,
code: string,
- position: arrayOf(number),
+ latitude: number,
+ longitude: number,
has_active_relays: boolean,
})),
})),
@@ -152,7 +156,6 @@ export interface IpcFacade {
connect(): Promise<void>,
disconnect(): Promise<void>,
shutdown(): Promise<void>,
- getPublicIp(): Promise<Ip>,
getLocation(): Promise<Location>,
getState(): Promise<BackendState>,
registerStateListener((BackendState) => void): void,
@@ -264,19 +267,11 @@ export class RealIpc implements IpcFacade {
.then(this._ignoreResponse);
}
- getPublicIp(): Promise<Ip> {
- return this._ipc.send('get_public_ip')
- .then(raw => {
- if (typeof raw === 'string' && raw) {
- return raw;
- } else {
- throw new InvalidReply(raw, 'Expected a string');
- }
- });
- }
-
getLocation(): Promise<Location> {
- return this._ipc.send('get_current_location')
+ // send the IPC with 30s timeout since the backend will wait
+ // for a HTTP request before replying
+
+ return this._ipc.send('get_current_location', [], 30000)
.then(raw => {
try {
const validated: any = validate(LocationSchema, raw);
diff --git a/app/redux/connection/actions.js b/app/redux/connection/actions.js
index aef4b85213..6f0da8cecf 100644
--- a/app/redux/connection/actions.js
+++ b/app/redux/connection/actions.js
@@ -4,15 +4,15 @@ import { Clipboard } from 'reactxp';
import type { Backend } from '../../lib/backend';
import type { ReduxThunk } from '../store';
-import type { Coordinate2d } from '../../types';
+import type { Ip } from '../../lib/ipc-facade';
const connect = (backend: Backend): ReduxThunk => () => backend.connect();
const disconnect = (backend: Backend) => () => backend.disconnect();
const copyIPAddress = (): ReduxThunk => {
return (_, getState) => {
- const { connection: { clientIp } } = getState();
- if(clientIp) {
- Clipboard.setText(clientIp);
+ const ip = getState().connection.ip;
+ if(ip) {
+ Clipboard.setText(ip);
}
};
};
@@ -28,20 +28,16 @@ type DisconnectedAction = {
type: 'DISCONNECTED',
};
-type NewPublicIpAction = {
- type: 'NEW_PUBLIC_IP',
- ip: string,
-};
-
-type Location = {
- location: Coordinate2d,
- country: string,
- city: string,
-};
-
type NewLocationAction = {
type: 'NEW_LOCATION',
- newLocation: Location,
+ newLocation: {
+ ip: Ip,
+ country: string,
+ city: ?string,
+ latitude: number,
+ longitude: number,
+ mullvadExitIp: boolean,
+ },
};
type OnlineAction = {
@@ -52,8 +48,7 @@ type OfflineAction = {
type: 'OFFLINE',
};
-export type ConnectionAction = NewPublicIpAction
- | NewLocationAction
+export type ConnectionAction = NewLocationAction
| ConnectingAction
| ConnectedAction
| DisconnectedAction
@@ -78,14 +73,7 @@ function disconnected(): DisconnectedAction {
};
}
-function newPublicIp(ip: string): NewPublicIpAction {
- return {
- type: 'NEW_PUBLIC_IP',
- ip: ip,
- };
-}
-
-function newLocation(newLoc: Location): NewLocationAction {
+function newLocation(newLoc: $PropertyType<NewLocationAction, 'newLocation'>): NewLocationAction {
return {
type: 'NEW_LOCATION',
newLocation: newLoc,
@@ -105,5 +93,5 @@ function offline(): OfflineAction {
}
-export default { connect, disconnect, copyIPAddress, newPublicIp, newLocation, connecting, connected, disconnected, online, offline };
+export default { connect, disconnect, copyIPAddress, newLocation, connecting, connected, disconnected, online, offline };
diff --git a/app/redux/connection/reducers.js b/app/redux/connection/reducers.js
index 52c1d648c1..7eb4c2aaf4 100644
--- a/app/redux/connection/reducers.js
+++ b/app/redux/connection/reducers.js
@@ -1,14 +1,15 @@
// @flow
import type { ReduxAction } from '../store';
-import type { Coordinate2d } from '../../types';
+import type { Ip } from '../../lib/ipc-facade';
export type ConnectionState = 'disconnected' | 'connecting' | 'connected';
export type ConnectionReduxState = {
status: ConnectionState,
isOnline: boolean,
- clientIp: ?string,
- location: ?Coordinate2d,
+ ip: ?Ip,
+ latitude: ?number,
+ longitude: ?number,
country: ?string,
city: ?string,
};
@@ -16,8 +17,9 @@ export type ConnectionReduxState = {
const initialState: ConnectionReduxState = {
status: 'disconnected',
isOnline: true,
- clientIp: null,
- location: null,
+ ip: null,
+ latitude: null,
+ longitude: null,
country: null,
city: null,
};
@@ -29,9 +31,6 @@ export default function(state: ConnectionReduxState = initialState, action: Redu
case 'CONNECTION_CHANGE':
return { ...state, ...action.newData };
- case 'NEW_PUBLIC_IP':
- return { ...state, ...{ clientIp: action.ip }};
-
case 'NEW_LOCATION':
return { ...state, ...action.newLocation };
diff --git a/app/redux/settings/reducers.js b/app/redux/settings/reducers.js
index 59f51c2a66..39005fd486 100644
--- a/app/redux/settings/reducers.js
+++ b/app/redux/settings/reducers.js
@@ -20,7 +20,8 @@ export type RelaySettingsRedux = {|
export type RelayLocationCityRedux = {
name: string,
code: string,
- position: [number, number],
+ latitude: number,
+ longitude: number,
hasActiveRelays: boolean,
};
diff --git a/dist-assets/relays.json b/dist-assets/relays.json
index 37cf8098c1..d34fcb7d26 100644
--- a/dist-assets/relays.json
+++ b/dist-assets/relays.json
@@ -7,10 +7,8 @@
{
"name": "Melbourne",
"code": "mel",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -66,10 +64,8 @@
{
"name": "Sydney",
"code": "syd",
- "position": [
- -33.861481,
- 151.205475
- ],
+ "latitude": -33.861481,
+ "longitude": 151.205475,
"has_active_relays": false,
"relays": [
{
@@ -179,10 +175,8 @@
{
"name": "Wien",
"code": "vie",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -292,10 +286,8 @@
{
"name": "Brussels",
"code": "bru",
- "position": [
- 50.833333,
- 4.333333
- ],
+ "latitude": 50.833333,
+ "longitude": 4.333333,
"has_active_relays": false,
"relays": [
{
@@ -405,10 +397,8 @@
{
"name": "Sofia",
"code": "sof",
- "position": [
- 42.6833333,
- 23.3166667
- ],
+ "latitude": 42.6833333,
+ "longitude": 23.3166667,
"has_active_relays": false,
"relays": [
{
@@ -518,10 +508,8 @@
{
"name": "Toronto",
"code": "tor",
- "position": [
- 43.666667,
- -79.416667
- ],
+ "latitude": 43.666667,
+ "longitude": -79.416667,
"has_active_relays": false,
"relays": [
{
@@ -673,10 +661,8 @@
{
"name": "Vancouver",
"code": "van",
- "position": [
- 49.25,
- -123.133333
- ],
+ "latitude": 49.25,
+ "longitude": -123.133333,
"has_active_relays": false,
"relays": [
{
@@ -786,10 +772,8 @@
{
"name": "Prague",
"code": "prg",
- "position": [
- 50.083333,
- 14.466667
- ],
+ "latitude": 50.083333,
+ "longitude": 14.466667,
"has_active_relays": false,
"relays": [
{
@@ -899,10 +883,8 @@
{
"name": "Copenhagen",
"code": "cph",
- "position": [
- 55.666667,
- 12.583333
- ],
+ "latitude": 55.666667,
+ "longitude": 12.583333,
"has_active_relays": false,
"relays": [
{
@@ -1108,10 +1090,8 @@
{
"name": "Helsinki",
"code": "hel",
- "position": [
- 60.6,
- 21.433333
- ],
+ "latitude": 60.6,
+ "longitude": 21.433333,
"has_active_relays": false,
"relays": [
{
@@ -1221,10 +1201,8 @@
{
"name": "Paris",
"code": "par",
- "position": [
- 48.866667,
- 2.333333
- ],
+ "latitude": 48.866667,
+ "longitude": 2.333333,
"has_active_relays": false,
"relays": [
{
@@ -1334,10 +1312,8 @@
{
"name": "Berlin",
"code": "ber",
- "position": [
- 54.033333,
- 10.45
- ],
+ "latitude": 54.033333,
+ "longitude": 10.45,
"has_active_relays": false,
"relays": [
{
@@ -1537,10 +1513,8 @@
{
"name": "Frankfurt",
"code": "fra",
- "position": [
- 52.35,
- 14.55
- ],
+ "latitude": 52.35,
+ "longitude": 14.55,
"has_active_relays": false,
"relays": [
{
@@ -1746,10 +1720,8 @@
{
"name": "Hong Kong",
"code": "hkg",
- "position": [
- 22.2833333,
- 114.15
- ],
+ "latitude": 22.2833333,
+ "longitude": 114.15,
"has_active_relays": false,
"relays": [
{
@@ -1811,10 +1783,8 @@
{
"name": "Budapest",
"code": "bud",
- "position": [
- 47.5,
- 19.083333
- ],
+ "latitude": 47.5,
+ "longitude": 19.083333,
"has_active_relays": false,
"relays": [
{
@@ -1876,10 +1846,8 @@
{
"name": "Petach-Tikva",
"code": "pet",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -1941,10 +1909,8 @@
{
"name": "Milan",
"code": "mil",
- "position": [
- 45.466667,
- 9.2
- ],
+ "latitude": 45.466667,
+ "longitude": 9.2,
"has_active_relays": false,
"relays": [
{
@@ -2054,10 +2020,8 @@
{
"name": "Tokyo",
"code": "tyo",
- "position": [
- 35.685,
- 139.751389
- ],
+ "latitude": 35.685,
+ "longitude": 139.751389,
"has_active_relays": false,
"relays": [
{
@@ -2119,10 +2083,8 @@
{
"name": "Chisinau",
"code": "kiv",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -2184,10 +2146,8 @@
{
"name": "Amsterdam",
"code": "ams",
- "position": [
- 52.35,
- 4.916667
- ],
+ "latitude": 52.35,
+ "longitude": 4.916667,
"has_active_relays": false,
"relays": [
{
@@ -3065,10 +3025,8 @@
{
"name": "Oslo",
"code": "osl",
- "position": [
- 59.916667,
- 10.75
- ],
+ "latitude": 59.916667,
+ "longitude": 10.75,
"has_active_relays": false,
"relays": [
{
@@ -3418,10 +3376,8 @@
{
"name": "Warsaw",
"code": "waw",
- "position": [
- 52.25,
- 21.0
- ],
+ "latitude": 52.25,
+ "longitude": 21.0,
"has_active_relays": false,
"relays": [
{
@@ -3483,10 +3439,8 @@
{
"name": "Lisbon",
"code": "lis",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -3548,10 +3502,8 @@
{
"name": "Bucharest",
"code": "buh",
- "position": [
- 44.433333,
- 26.1
- ],
+ "latitude": 44.433333,
+ "longitude": 26.1,
"has_active_relays": false,
"relays": [
{
@@ -3661,10 +3613,8 @@
{
"name": "Singapore",
"code": "sin",
- "position": [
- 1.2930556,
- 103.8558333
- ],
+ "latitude": 1.2930556,
+ "longitude": 103.8558333,
"has_active_relays": false,
"relays": [
{
@@ -3774,10 +3724,8 @@
{
"name": "Madrid",
"code": "mad",
- "position": [
- 40.408566,
- -3.69222
- ],
+ "latitude": 40.408566,
+ "longitude": -3.69222,
"has_active_relays": false,
"relays": [
{
@@ -3887,10 +3835,8 @@
{
"name": "Helsingborg",
"code": "hel",
- "position": [
- 56.05,
- 12.7
- ],
+ "latitude": 56.05,
+ "longitude": 12.7,
"has_active_relays": false,
"relays": [
{
@@ -4282,10 +4228,8 @@
{
"name": "Malmö",
"code": "mma",
- "position": [
- 58.35,
- 11.333333
- ],
+ "latitude": 58.35,
+ "longitude": 11.333333,
"has_active_relays": false,
"relays": [
{
@@ -4767,16 +4711,158 @@
],
"wireguard": []
}
+ },
+ {
+ "hostname": "se-mma-007",
+ "ipv4_addr_in": "193.138.218.137",
+ "ipv4_addr_exit": "193.138.218.167",
+ "include_in_country": true,
+ "weight": 400,
+ "tunnels": {
+ "openvpn": [
+ {
+ "port": 1194,
+ "protocol": "udp"
+ },
+ {
+ "port": 1195,
+ "protocol": "udp"
+ },
+ {
+ "port": 1196,
+ "protocol": "udp"
+ },
+ {
+ "port": 1197,
+ "protocol": "udp"
+ },
+ {
+ "port": 1300,
+ "protocol": "udp"
+ },
+ {
+ "port": 1301,
+ "protocol": "udp"
+ },
+ {
+ "port": 1302,
+ "protocol": "udp"
+ },
+ {
+ "port": 443,
+ "protocol": "tcp"
+ },
+ {
+ "port": 80,
+ "protocol": "tcp"
+ }
+ ],
+ "wireguard": []
+ }
+ },
+ {
+ "hostname": "se-mma-008",
+ "ipv4_addr_in": "193.138.218.138",
+ "ipv4_addr_exit": "193.138.218.168",
+ "include_in_country": true,
+ "weight": 400,
+ "tunnels": {
+ "openvpn": [
+ {
+ "port": 1194,
+ "protocol": "udp"
+ },
+ {
+ "port": 1195,
+ "protocol": "udp"
+ },
+ {
+ "port": 1196,
+ "protocol": "udp"
+ },
+ {
+ "port": 1197,
+ "protocol": "udp"
+ },
+ {
+ "port": 1300,
+ "protocol": "udp"
+ },
+ {
+ "port": 1301,
+ "protocol": "udp"
+ },
+ {
+ "port": 1302,
+ "protocol": "udp"
+ },
+ {
+ "port": 443,
+ "protocol": "tcp"
+ },
+ {
+ "port": 80,
+ "protocol": "tcp"
+ }
+ ],
+ "wireguard": []
+ }
+ },
+ {
+ "hostname": "se-mma-009",
+ "ipv4_addr_in": "193.138.218.139",
+ "ipv4_addr_exit": "193.138.218.169",
+ "include_in_country": true,
+ "weight": 400,
+ "tunnels": {
+ "openvpn": [
+ {
+ "port": 1194,
+ "protocol": "udp"
+ },
+ {
+ "port": 1195,
+ "protocol": "udp"
+ },
+ {
+ "port": 1196,
+ "protocol": "udp"
+ },
+ {
+ "port": 1197,
+ "protocol": "udp"
+ },
+ {
+ "port": 1300,
+ "protocol": "udp"
+ },
+ {
+ "port": 1301,
+ "protocol": "udp"
+ },
+ {
+ "port": 1302,
+ "protocol": "udp"
+ },
+ {
+ "port": 443,
+ "protocol": "tcp"
+ },
+ {
+ "port": 80,
+ "protocol": "tcp"
+ }
+ ],
+ "wireguard": []
+ }
}
]
},
{
"name": "Stockholm",
"code": "sto",
- "position": [
- 61.883056,
- 13.75
- ],
+ "latitude": 61.883056,
+ "longitude": 13.75,
"has_active_relays": false,
"relays": [
{
@@ -5510,10 +5596,8 @@
{
"name": "Zurich",
"code": "zrh",
- "position": [
- 47.366667,
- 8.55
- ],
+ "latitude": 47.366667,
+ "longitude": 8.55,
"has_active_relays": false,
"relays": [
{
@@ -5815,10 +5899,8 @@
{
"name": "London",
"code": "lon",
- "position": [
- 51.514125,
- -0.093689
- ],
+ "latitude": 51.514125,
+ "longitude": -0.093689,
"has_active_relays": false,
"relays": [
{
@@ -6018,10 +6100,8 @@
{
"name": "Manchester",
"code": "mnc",
- "position": [
- 53.5,
- -2.216667
- ],
+ "latitude": 53.5,
+ "longitude": -2.216667,
"has_active_relays": false,
"relays": [
{
@@ -6179,10 +6259,8 @@
{
"name": "Atlanta, Georgia",
"code": "atl",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -6286,10 +6364,8 @@
{
"name": "Chicago, Illinois",
"code": "chi",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -6489,10 +6565,8 @@
{
"name": "Dallas, Texas",
"code": "dal",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -6644,10 +6718,8 @@
{
"name": "Las Vegas, Nevada",
"code": "las",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -6799,10 +6871,8 @@
{
"name": "Los Angeles, California",
"code": "lax",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -7098,10 +7168,8 @@
{
"name": "Miami, Florida",
"code": "mia",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -7301,10 +7369,8 @@
{
"name": "Newark, New Jersey",
"code": "ewr",
- "position": [
- 4042.0,
- 7400.0
- ],
+ "latitude": 4042.0,
+ "longitude": 7400.0,
"has_active_relays": false,
"relays": [
{
@@ -7456,10 +7522,8 @@
{
"name": "New York, New York",
"code": "nyc",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -7707,10 +7771,8 @@
{
"name": "Phoenix, Arizona",
"code": "PHX",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -7862,10 +7924,8 @@
{
"name": "Piscataway, New Jersey",
"code": "pil",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -7969,10 +8029,8 @@
{
"name": "Richmond, Virginia",
"code": "ric",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -8028,10 +8086,8 @@
{
"name": "Salt Lake City, Utah",
"code": "slc",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -8135,10 +8191,8 @@
{
"name": "San Jose, California",
"code": "sjc",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -8194,10 +8248,8 @@
{
"name": "Seattle, Washington",
"code": "sea",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
@@ -8301,10 +8353,8 @@
{
"name": "Washington DC",
"code": "was",
- "position": [
- 0.0,
- 0.0
- ],
+ "latitude": 0.0,
+ "longitude": 0.0,
"has_active_relays": false,
"relays": [
{
diff --git a/format.sh b/format.sh
index 2ee4194fa3..cc82f8e25a 100755
--- a/format.sh
+++ b/format.sh
@@ -5,7 +5,7 @@
set -u
-VERSION="0.3.4"
+VERSION="0.3.5"
CMD="rustfmt"
INSTALL_CMD="cargo install --vers $VERSION --force rustfmt-nightly"
diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs
index cbd18f1f03..c48a990f62 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!("");
}
@@ -208,8 +211,7 @@ impl Relay {
fn parse_port_constraint(raw_port: &str) -> Result<Constraint<u16>> {
match raw_port.to_lowercase().as_str() {
"any" => Ok(Constraint::Any),
- port => Ok(Constraint::Only(u16::from_str(port)
- .chain_err(|| "Invalid port")?)),
+ port => Ok(Constraint::Only(u16::from_str(port).chain_err(|| "Invalid port")?)),
}
}
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 f6ff2d648e..48bd3c59af 100644
--- a/mullvad-daemon/Cargo.toml
+++ b/mullvad-daemon/Cargo.toml
@@ -13,8 +13,7 @@ clap = "2.25"
error-chain = "0.11"
fern = "0.4"
futures = "0.1"
-serde = "1.0"
-serde_derive = "1.0"
+serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
log = "0.3"
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc", tag = "v7.1.1" }
@@ -24,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/bin/list-relays.rs b/mullvad-daemon/src/bin/list-relays.rs
index 2718c05396..7a1fcb092a 100644
--- a/mullvad-daemon/src/bin/list-relays.rs
+++ b/mullvad-daemon/src/bin/list-relays.rs
@@ -17,7 +17,7 @@ error_chain!{}
quick_main!(run);
fn run() -> Result<()> {
- let rpc_http_handle = mullvad_rpc::connect().chain_err(|| "Unable to connect RPC")?;
+ let rpc_http_handle = mullvad_rpc::standalone().chain_err(|| "Unable to connect RPC")?;
let mut client = mullvad_rpc::RelayListProxy::new(rpc_http_handle);
let relays = client
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 696e85d783..0668d09b6e 100644
--- a/mullvad-daemon/src/main.rs
+++ b/mullvad-daemon/src/main.rs
@@ -18,9 +18,8 @@ extern crate futures;
#[macro_use]
extern crate log;
-extern crate serde;
#[macro_use]
-extern crate serde_derive;
+extern crate serde;
extern crate serde_json;
extern crate jsonrpc_core;
@@ -31,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;
@@ -40,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;
@@ -58,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;
@@ -189,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,9 +204,19 @@ struct Daemon {
impl Daemon {
pub fn new(tunnel_log: Option<PathBuf>) -> Result<Self> {
let resource_dir = get_resource_dir();
- let rpc_http_handle = mullvad_rpc::connect().chain_err(|| "Unable to connect to RPC API")?;
- let relay_selector = Self::create_relay_selector(rpc_http_handle.clone(), &resource_dir)?;
+ let (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_handle.clone(), &resource_dir)?;
let (tx, rx) = mpsc::channel();
let management_interface_broadcaster = Self::start_management_interface(tx.clone())?;
@@ -222,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,
@@ -234,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 {
@@ -349,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)),
@@ -376,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)
- } 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()
+ 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 {
- 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..ccc4fdeebb 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>>,
@@ -233,6 +226,7 @@ pub struct EventBroadcaster {
impl EventBroadcaster {
/// Sends a new state update to all `new_state` subscribers of the management interface.
pub fn notify_new_state(&self, new_state: DaemonState) {
+ debug!("Broadcasting new state to listeners: {:?}", new_state);
self.notify(&self.subscriptions.new_state_subscriptions, new_state);
}
@@ -505,16 +499,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 d49557cb96..39c17df17d 100644
--- a/mullvad-rpc/Cargo.toml
+++ b/mullvad-rpc/Cargo.toml
@@ -7,8 +7,15 @@ 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
new file mode 100644
index 0000000000..09af5ed54c
--- /dev/null
+++ b/mullvad-rpc/src/event_loop.rs
@@ -0,0 +1,38 @@
+use std::thread;
+use tokio_core::reactor::Core;
+
+error_chain! {
+ errors {
+ CoreError { description("Error when creating event loop") }
+ }
+}
+
+/// 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>(init: F) -> Result<T>
+where
+ F: FnOnce(&mut Core) -> T + Send + 'static,
+ T: Send + 'static,
+{
+ let (tx, rx) = ::std::sync::mpsc::channel();
+ thread::spawn(move || match create_core(init) {
+ Err(e) => tx.send(Err(e)).unwrap(),
+ Ok((mut core, out)) => {
+ tx.send(Ok(out)).unwrap();
+ loop {
+ core.turn(None);
+ }
+ }
+ });
+ rx.recv().unwrap()
+}
+
+fn create_core<F, T>(init: F) -> Result<(Core, T)>
+where
+ F: FnOnce(&mut Core) -> T + Send + 'static,
+{
+ let mut core = Core::new().chain_err(|| ErrorKind::CoreError)?;
+ let out = init(&mut core);
+ Ok((core, out))
+}
diff --git a/mullvad-rpc/src/lib.rs b/mullvad-rpc/src/lib.rs
index 19d9a29287..ad20b1a0a3 100644
--- a/mullvad-rpc/src/lib.rs
+++ b/mullvad-rpc/src/lib.rs
@@ -8,15 +8,25 @@
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;
extern crate mullvad_types;
use chrono::DateTime;
use chrono::offset::Utc;
use jsonrpc_client_http::HttpTransport;
+use tokio_core::reactor::Handle;
pub use jsonrpc_client_core::{Error, ErrorKind};
pub use jsonrpc_client_http::{Error as HttpError, HttpHandle};
@@ -26,11 +36,20 @@ 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/";
-pub fn connect() -> Result<HttpHandle, HttpError> {
+/// Create and returns a `HttpHandle` running on the given core handle.
+pub fn shared(handle: &Handle) -> Result<HttpHandle, HttpError> {
+ HttpTransport::shared(handle)?.handle(MASTER_API_URI)
+}
+
+/// Spawns a tokio core on a new thread and returns a `HttpHandle` running on that core.
+pub fn standalone() -> Result<HttpHandle, HttpError> {
HttpTransport::new()?.handle(MASTER_API_URI)
}
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..762cd41075 100644
--- a/mullvad-types/src/relay_list.rs
+++ b/mullvad-types/src/relay_list.rs
@@ -21,9 +21,12 @@ pub struct RelayListCountry {
pub struct RelayListCity {
pub name: String,
pub code: CityCode,
- pub position: [f64; 2],
- #[serde(skip_deserializing)] pub has_active_relays: bool,
- #[serde(skip_serializing_if = "Vec::is_empty", default)] pub relays: Vec<Relay>,
+ 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>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
@@ -34,7 +37,8 @@ pub struct Relay {
pub include_in_country: bool,
pub weight: u64,
pub tunnels: RelayTunnels,
- #[serde(skip)] pub location: Option<Location>,
+ #[serde(skip)]
+ pub location: Option<Location>,
}
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
diff --git a/test/components/Connect.spec.js b/test/components/Connect.spec.js
index ed8c17982a..c92baf51d4 100644
--- a/test/components/Connect.spec.js
+++ b/test/components/Connect.spec.js
@@ -69,7 +69,7 @@ describe('components/Connect', () => {
status: 'connected',
country: 'Norway',
city: 'Oslo',
- clientIp: '4.3.2.1',
+ ip: '4.3.2.1',
}
});
const countryAndCity = component.find('.connect__status-location');
@@ -87,15 +87,15 @@ describe('components/Connect', () => {
status: 'disconnected',
country: 'Norway',
city: 'Oslo',
- clientIp: '4.3.2.1',
+ ip: '4.3.2.1',
}
});
const countryAndCity = component.find('.connect__status-location');
const ipAddr = component.find('.connect__status-ipaddress');
- expect(countryAndCity.text()).to.contain('\u2002');
+ expect(countryAndCity.text()).to.contain('Norway');
expect(countryAndCity.text()).to.not.contain('Oslo');
- expect(ipAddr.text()).to.contain('\u2003');
+ expect(ipAddr.text()).to.contain('4.3.2.1');
});
it('shows the country name in the location switcher', () => {
@@ -157,8 +157,9 @@ const defaultProps: ConnectProps = {
cities: [{
name: 'Malmö',
code: 'mma',
+ latitude: 0,
+ longitude: 0,
hasActiveRelays: true,
- position: [0, 0],
}]
}],
allowLan: false,
@@ -166,8 +167,9 @@ const defaultProps: ConnectProps = {
connection: {
status: 'disconnected',
isOnline: true,
- clientIp: null,
- location: null,
+ ip: null,
+ latitude: null,
+ longitude: null,
country: null,
city: null,
},
diff --git a/test/components/SelectLocation.spec.js b/test/components/SelectLocation.spec.js
index 7ec17ff798..eb72b03c57 100644
--- a/test/components/SelectLocation.spec.js
+++ b/test/components/SelectLocation.spec.js
@@ -24,12 +24,14 @@ describe('components/SelectLocation', () => {
cities: [{
name: 'Malmö',
code: 'mma',
- position: [0, 0],
+ latitude: 0,
+ longitude: 0,
hasActiveRelays: true,
}, {
name: 'Stockholm',
code: 'sto',
- position: [0, 0],
+ latitude: 0,
+ longitude: 0,
hasActiveRelays: true,
}],
}],
diff --git a/test/connection-info.spec.js b/test/connection-info.spec.js
deleted file mode 100644
index 76a97a35f7..0000000000
--- a/test/connection-info.spec.js
+++ /dev/null
@@ -1,46 +0,0 @@
-// @flow
-
-import { expect } from 'chai';
-import { createMemoryHistory } from 'history';
-import configureStore from '../app/redux/store';
-import connectionActions from '../app/redux/connection/actions';
-
-describe('The connection state', () => {
-
- it('should contain the latest IP', () => {
- const store = createStore();
-
- store.dispatch(connectionActions.newPublicIp('1.2.3.4'));
- store.dispatch(connectionActions.newPublicIp('5.6.7.8'));
-
- expect(store.getState().connection.clientIp).to.equal('5.6.7.8');
- });
-
- it('should contain the latest location', () => {
- const store = createStore();
-
- const firstLoc = {
- location: [1, 2],
- city: 'a',
- country: 'b',
- };
- const secondLoc = {
- location: [3, 4],
- city: 'c',
- country: 'd',
- };
-
- store.dispatch(connectionActions.newLocation(firstLoc));
- store.dispatch(connectionActions.newLocation(secondLoc));
-
- const { location, city, country } = store.getState().connection;
- expect(location).to.equal(secondLoc.location);
- expect(city).to.equal(secondLoc.city);
- expect(country).to.equal(secondLoc.country);
- });
-});
-
-function createStore() {
- const memoryHistory = createMemoryHistory();
- return configureStore(null, memoryHistory);
-}
diff --git a/test/mocks/ipc.js b/test/mocks/ipc.js
index fc035f55ed..2ba32ecf2f 100644
--- a/test/mocks/ipc.js
+++ b/test/mocks/ipc.js
@@ -56,14 +56,13 @@ export function newMockIpc() {
shutdown: () => Promise.resolve(),
- getPublicIp: () => Promise.resolve('1.2.3.4'),
-
getLocation: () => Promise.resolve({
+ ip: '',
country: '',
- country_code: '',
city: '',
- city_code: '',
- position: [0, 0],
+ latitude: 0.0,
+ longitude: 0.0,
+ mullvad_exit_ip: false,
}),
getState: () => Promise.resolve({