diff options
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": [ { @@ -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({ |
