diff options
| author | Linus Färnstrand <linus@mullvad.net> | 2019-09-13 13:24:07 +0200 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2019-09-13 13:24:07 +0200 |
| commit | f664e5e245804754b15a086d810c81a2f376da0d (patch) | |
| tree | 814428191d3776033c87f449030359e8cb1666eb | |
| parent | 40de3386b2c98369d76cbd12e194c931bc17bd87 (diff) | |
| parent | e5511a373c937b28009e7cebada3249af2e820ed (diff) | |
| download | mullvadvpn-f664e5e245804754b15a086d810c81a2f376da0d.tar.xz mullvadvpn-f664e5e245804754b15a086d810c81a2f376da0d.zip | |
Merge branch 'relay-list-v3'
23 files changed, 165 insertions, 49 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/Relay.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/Relay.kt index 4173af782b..b5d582e18d 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/Relay.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/Relay.kt @@ -1,4 +1,4 @@ package net.mullvad.mullvadvpn.model -data class Relay(val hostname: String, val hasWireguardTunnels: Boolean) { +data class Relay(val hostname: String, val hasWireguardTunnels: Boolean, val active: Boolean) { } diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/Relay.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/Relay.kt index 8620a64963..db8afade98 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/Relay.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/Relay.kt @@ -2,7 +2,11 @@ package net.mullvad.mullvadvpn.relaylist import net.mullvad.mullvadvpn.model.LocationConstraint -data class Relay(val city: RelayCity, override val name: String) : RelayItem { +data class Relay( + val city: RelayCity, + override val name: String, + override val active: Boolean +) : RelayItem { override val code = name override val type = RelayItemType.Relay override val location = LocationConstraint.Hostname(city.country.code, city.code, name) diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCity.kt index 9d4ba65ccf..70b2ca2d0a 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCity.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCity.kt @@ -12,6 +12,9 @@ class RelayCity( override val type = RelayItemType.City override val location = LocationConstraint.City(country.code, code) + override val active + get() = relays.any { relay -> relay.active } + override val hasChildren get() = relays.size > 1 diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCountry.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCountry.kt index fb2aacab33..f5d5a867c3 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCountry.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayCountry.kt @@ -11,6 +11,9 @@ class RelayCountry( override val type = RelayItemType.Country override val location = LocationConstraint.Country(code) + override val active + get() = cities.any { city -> city.active } + override val hasChildren get() = getRelayCount() > 1 diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt index d55378fbd4..e5f28acee6 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt @@ -7,6 +7,7 @@ interface RelayItem { val name: String val code: String val location: LocationConstraint + val active: Boolean val hasChildren: Boolean val visibleChildCount: Int diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemHolder.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemHolder.kt index e753d3af5a..275e6f5dfa 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemHolder.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemHolder.kt @@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.relaylist import android.support.v7.widget.RecyclerView.ViewHolder import android.view.View import android.widget.ImageButton +import android.widget.ImageView import android.widget.TextView import net.mullvad.mullvadvpn.R @@ -14,7 +15,7 @@ class RelayItemHolder( ) : ViewHolder(view) { private val name: TextView = view.findViewById(R.id.name) private val chevron: ImageButton = view.findViewById(R.id.chevron) - private val relayActive: View = view.findViewById(R.id.relay_active) + private val relayActive: ImageView = view.findViewById(R.id.relay_active) private val selectedIcon: View = view.findViewById(R.id.selected) private val countryColor = view.context.getColor(R.color.blue) @@ -49,14 +50,28 @@ class RelayItemHolder( if (item != null) { name.text = item.name + if (item.active) { + name.alpha = 1.0F + } else { + name.alpha = 0.5F + } + if (selected) { relayActive.visibility = View.INVISIBLE selectedIcon.visibility = View.VISIBLE } else { relayActive.visibility = View.VISIBLE selectedIcon.visibility = View.INVISIBLE + + if (item.active) { + relayActive.setImageResource(R.drawable.icon_relay_active) + } else { + relayActive.setImageResource(R.drawable.icon_relay_inactive) + } } + view.setEnabled(item.active) + when (item.type) { RelayItemType.Country -> setViewStyle(countryColor, countryPadding) RelayItemType.City -> setViewStyle(cityColor, cityPadding) diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayList.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayList.kt index 26cd7ffa58..7d09e421db 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayList.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayList.kt @@ -19,7 +19,7 @@ class RelayList { val validCityRelays = city.relays.filter { relay -> relay.hasWireguardTunnels } for (relay in validCityRelays) { - relays.add(Relay(relayCity, relay.hostname)) + relays.add(Relay(relayCity, relay.hostname, relay.active)) } if (relays.isNotEmpty()) { diff --git a/android/src/main/res/drawable/icon_relay_inactive.xml b/android/src/main/res/drawable/icon_relay_inactive.xml new file mode 100644 index 0000000000..79818e106f --- /dev/null +++ b/android/src/main/res/drawable/icon_relay_inactive.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval" + > + <solid android:color="@color/red"/> + <size android:width="16dp" android:height="16dp"/> +</shape> @@ -148,7 +148,7 @@ set -e JSONRPC_RESPONSE="$(curl -X POST \ --fail \ -H "Content-Type: application/json" \ - -d '{"jsonrpc": "2.0", "id": "0", "method": "relay_list_v2"}' \ + -d '{"jsonrpc": "2.0", "id": "0", "method": "relay_list_v3"}' \ https://api.mullvad.net/rpc/)" echo $JSONRPC_RESPONSE | node -e "$JSONRPC_CODE" > dist-assets/relays.json diff --git a/gui/scripts/extract-geo-data.py b/gui/scripts/extract-geo-data.py index 0597fb0c49..82f98bf951 100644 --- a/gui/scripts/extract-geo-data.py +++ b/gui/scripts/extract-geo-data.py @@ -519,7 +519,7 @@ def map_locale(locale_ident): def request_relays(): - data = json.dumps({"jsonrpc": "2.0", "id": "0", "method": "relay_list_v2"}) + data = json.dumps({"jsonrpc": "2.0", "id": "0", "method": "relay_list_v3"}) headers = {"Content-Type": "application/json"} request = urllib2.Request("https://api.mullvad.net/rpc/", data, headers) return json.load(urllib2.urlopen(request)) diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts index 25f0d32748..1ce4450af3 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -137,6 +137,7 @@ const relayListSchema = partialObject({ hostname: string, ipv4_addr_in: string, include_in_country: boolean, + active: boolean, weight: number, bridges: maybe( partialObject({ diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts index ac583f4300..05edd6af6c 100644 --- a/gui/src/main/index.ts +++ b/gui/src/main/index.ts @@ -13,10 +13,10 @@ import { IAppVersionInfo, ILocation, IRelayList, - IRelayListHostname, ISettings, IWireguardPublicKey, KeygenEvent, + liftConstraint, RelayLocation, RelaySettings, RelaySettingsUpdate, @@ -663,42 +663,44 @@ class ApplicationMain { relayList: IRelayList, relaySettings: RelaySettings, ): IRelayList { - // TODO: once wireguard is stable, by default we should only filter by - // hasToHaveOpenvpn || hasToHaveWg, until then, only filter wireguard - // relays if tunnel constraints specify wireguard tunnels. - const hasOpenVpnTunnels = (relay: IRelayListHostname): boolean => { - if (relay.tunnels) { - return relay.tunnels.openvpn.length > 0; - } else { - return false; - } - }; - const hasWireguardTunnels = (relay: IRelayListHostname): boolean => { - if (relay.tunnels) { - return relay.tunnels.wireguard.length > 0; - } else { - return false; - } - }; - let fnHasWantedTunnels = hasOpenVpnTunnels; + const tunnelProtocol = + 'normal' in relaySettings ? liftConstraint(relaySettings.normal.tunnelProtocol) : undefined; - if ('normal' in relaySettings) { - const tunnelConstraints = relaySettings.normal.tunnelProtocol; - if (tunnelConstraints !== 'any' && 'wireguard' === tunnelConstraints.only) { - fnHasWantedTunnels = hasWireguardTunnels; - } - } - - return { - countries: relayList.countries.map((country) => ({ + const filteredCountries = relayList.countries + .map((country) => ({ ...country, cities: country.cities .map((city) => ({ ...city, - relays: city.relays.filter(fnHasWantedTunnels), + relays: city.relays.filter((relay) => { + if (relay.tunnels) { + switch (tunnelProtocol) { + case 'openvpn': + return relay.tunnels.openvpn.length > 0; + + case 'wireguard': + return relay.tunnels.wireguard.length > 0; + + case 'any': + // TODO: once wireguard is stable, by default we should only filter by + // hasToHaveOpenvpn || hasToHaveWg, until then, only filter wireguard + // relays if tunnel constraints specify wireguard tunnels. + return relay.tunnels.openvpn.length > 0; + + default: + return false; + } + } else { + return false; + } + }), })) .filter((city) => city.relays.length > 0), - })), + })) + .filter((country) => country.cities.length > 0); + + return { + countries: filteredCountries, }; } diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index 6f54281f3c..c8027223a0 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -555,14 +555,14 @@ export default class AppRenderer { .map((country) => ({ name: country.name, code: country.code, - hasActiveRelays: country.cities.some((city) => city.relays.length > 0), + hasActiveRelays: country.cities.some((city) => city.relays.some((relay) => relay.active)), cities: country.cities .map((city) => ({ name: city.name, code: city.code, latitude: city.latitude, longitude: city.longitude, - hasActiveRelays: city.relays.length > 0, + hasActiveRelays: city.relays.some((relay) => relay.active), relays: city.relays, })) .sort((cityA, cityB) => cityA.name.localeCompare(cityB.name)), diff --git a/gui/src/renderer/components/LocationList.tsx b/gui/src/renderer/components/LocationList.tsx index 418da66e94..72a3b64f75 100644 --- a/gui/src/renderer/components/LocationList.tsx +++ b/gui/src/renderer/components/LocationList.tsx @@ -84,6 +84,7 @@ export default class LocationList extends Component<IProps, IState> { return ( <RelayRow key={getLocationKey(relayLocation)} + active={relay.active} hostname={relay.hostname} onSelect={this.handleSelection} {...this.getCommonCellProps(relayLocation)} diff --git a/gui/src/renderer/components/RelayRow.tsx b/gui/src/renderer/components/RelayRow.tsx index 69f632e04e..5dff0a33f1 100644 --- a/gui/src/renderer/components/RelayRow.tsx +++ b/gui/src/renderer/components/RelayRow.tsx @@ -7,6 +7,7 @@ import RelayStatusIndicator from './RelayStatusIndicator'; interface IProps { location: RelayLocation; + active: boolean; hostname: string; selected: boolean; onSelect?: (location: RelayLocation) => void; @@ -30,6 +31,7 @@ export default class RelayRow extends Component<IProps> { return ( oldProps.hostname === nextProps.hostname && oldProps.selected === nextProps.selected && + oldProps.active === nextProps.active && compareRelayLocation(oldProps.location, nextProps.location) ); } @@ -43,8 +45,9 @@ export default class RelayRow extends Component<IProps> { <Cell.CellButton onPress={this.handlePress} cellHoverStyle={this.props.selected ? styles.selected : undefined} + disabled={!this.props.active} style={[styles.base, this.props.selected ? styles.selected : undefined]}> - <RelayStatusIndicator isActive={true} isSelected={this.props.selected} /> + <RelayStatusIndicator isActive={this.props.active} isSelected={this.props.selected} /> <Cell.Label>{this.props.hostname}</Cell.Label> </Cell.CellButton> diff --git a/gui/src/renderer/redux/settings/reducers.ts b/gui/src/renderer/redux/settings/reducers.ts index 80cc2ee5c9..fed04b38e4 100644 --- a/gui/src/renderer/redux/settings/reducers.ts +++ b/gui/src/renderer/redux/settings/reducers.ts @@ -47,6 +47,7 @@ export interface IRelayLocationRelayRedux { hostname: string; ipv4AddrIn: string; includeInCountry: boolean; + active: boolean; weight: number; } diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts index 86dd5ab9b2..346b766e1b 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -198,6 +198,7 @@ export interface IRelayListHostname { hostname: string; ipv4AddrIn: string; includeInCountry: boolean; + active: boolean; weight: number; tunnels?: IRelayTunnels; bridges?: IRelayBridges; diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs index caee182123..a2b8a0abc1 100644 --- a/mullvad-cli/src/cmds/bridge.rs +++ b/mullvad-cli/src/cmds/bridge.rs @@ -323,7 +323,8 @@ impl Bridge { .cities .into_iter() .filter_map(|mut city| { - city.relays.retain(|relay| !relay.bridges.is_empty()); + city.relays + .retain(|relay| relay.active && !relay.bridges.is_empty()); if !city.relays.is_empty() { Some(city) } else { @@ -339,14 +340,24 @@ impl Bridge { }) .collect(); + locations + .countries + .sort_by(|c1, c2| c1.name.to_lowercase().cmp(&c2.name.to_lowercase())); for mut country in locations.countries { - country.cities.sort_by(|c1, c2| c1.name.cmp(&c2.name)); + country + .cities + .sort_by(|c1, c2| c1.name.to_lowercase().cmp(&c2.name.to_lowercase())); println!("{} ({})", country.name, country.code); - for city in &country.cities { + for mut city in country.cities { + city.relays + .sort_by(|r1, r2| r1.hostname.to_lowercase().cmp(&r2.hostname.to_lowercase())); println!( "\t{} ({}) @ {:.5}°N, {:.5}°W", city.name, city.code, city.latitude, city.longitude ); + for relay in &city.relays { + println!("\t\t{} ({})", relay.hostname, relay.ipv4_addr_in); + } } println!(); } diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index 13b26b79c6..efa319ef4a 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -315,15 +315,61 @@ impl Relay { fn list(&self) -> Result<()> { let mut rpc = new_rpc_client()?; let mut locations = rpc.get_relay_locations()?; - locations.countries.sort_by(|c1, c2| c1.name.cmp(&c2.name)); + + locations.countries = locations + .countries + .into_iter() + .filter_map(|mut country| { + country.cities = country + .cities + .into_iter() + .filter_map(|mut city| { + city.relays + .retain(|relay| relay.active && !relay.tunnels.is_empty()); + if !city.relays.is_empty() { + Some(city) + } else { + None + } + }) + .collect(); + if !country.cities.is_empty() { + Some(country) + } else { + None + } + }) + .collect(); + + locations + .countries + .sort_by(|c1, c2| c1.name.to_lowercase().cmp(&c2.name.to_lowercase())); for mut country in locations.countries { - country.cities.sort_by(|c1, c2| c1.name.cmp(&c2.name)); + country + .cities + .sort_by(|c1, c2| c1.name.to_lowercase().cmp(&c2.name.to_lowercase())); println!("{} ({})", country.name, country.code); - for city in &country.cities { + for mut city in country.cities { + city.relays + .sort_by(|r1, r2| r1.hostname.to_lowercase().cmp(&r2.hostname.to_lowercase())); println!( "\t{} ({}) @ {:.5}°N, {:.5}°W", city.name, city.code, city.latitude, city.longitude ); + for relay in &city.relays { + let supports_openvpn = !relay.tunnels.openvpn.is_empty(); + let supports_wireguard = !relay.tunnels.wireguard.is_empty(); + let support_msg = match (supports_openvpn, supports_wireguard) { + (true, true) => "OpenVPN and WireGuard", + (true, false) => "OpenVPN", + (false, true) => "WireGuard", + _ => unreachable!("Bug in relay filtering earlier on"), + }; + println!( + "\t\t{} ({}) - {}", + relay.hostname, relay.ipv4_addr_in, support_msg + ); + } } println!(); } diff --git a/mullvad-daemon/src/relays.rs b/mullvad-daemon/src/relays.rs index ea1bcc3114..cf6f2f4542 100644 --- a/mullvad-daemon/src/relays.rs +++ b/mullvad-daemon/src/relays.rs @@ -321,6 +321,7 @@ impl RelaySelector { .lock() .relays() .iter() + .filter(|relay| relay.active) .filter_map(|relay| Self::matching_bridge_relay(relay, constraints)) .collect(); @@ -349,6 +350,7 @@ impl RelaySelector { .lock() .relays() .iter() + .filter(|relay| relay.active) .filter_map(|relay| Self::matching_relay(relay, constraints)) .collect(); @@ -495,6 +497,7 @@ impl RelaySelector { .cloned() .collect() } + /// Pick a random relay from the given slice. Will return `None` if the given slice is empty /// or all relays in it has zero weight. fn pick_random_relay<'a>(&mut self, relays: &'a [Relay]) -> Option<&'a Relay> { @@ -527,7 +530,18 @@ impl RelaySelector { .bridges .shadowsocks .choose(&mut self.rng) - .map(|data| data.clone().to_proxy_settings(relay.ipv4_addr_in.into())) + .map(|shadowsocks_endpoint| { + info!( + "Selected Shadowsocks bridge {} at {}:{}/{}", + relay.hostname, + relay.ipv4_addr_in, + shadowsocks_endpoint.port, + shadowsocks_endpoint.protocol + ); + shadowsocks_endpoint + .clone() + .to_proxy_settings(relay.ipv4_addr_in.into()) + }) } fn get_random_tunnel( @@ -762,7 +776,7 @@ impl RelayListUpdater { } fn download_relay_list(&mut self) -> Result<RelayList, Error> { - let download_future = self.rpc_client.relay_list_v2().map_err(Error::Download); + let download_future = self.rpc_client.relay_list_v3().map_err(Error::Download); let relay_list = Timer::default() .timeout(download_future, DOWNLOAD_TIMEOUT) .wait()?; diff --git a/mullvad-jni/src/into_java.rs b/mullvad-jni/src/into_java.rs index 1e46973c32..2354ce0656 100644 --- a/mullvad-jni/src/into_java.rs +++ b/mullvad-jni/src/into_java.rs @@ -436,9 +436,10 @@ impl<'env> IntoJava<'env> for Relay { let parameters = [ JValue::Object(hostname.as_obj()), JValue::Bool(has_wireguard_tunnels), + JValue::Bool(self.active as jboolean), ]; - env.new_object(&class, "(Ljava/lang/String;Z)V", ¶meters) + env.new_object(&class, "(Ljava/lang/String;ZZ)V", ¶meters) .expect("Failed to create Relay Java object") } } diff --git a/mullvad-rpc/src/lib.rs b/mullvad-rpc/src/lib.rs index a68dfea0fa..1d2fdcf758 100644 --- a/mullvad-rpc/src/lib.rs +++ b/mullvad-rpc/src/lib.rs @@ -118,7 +118,7 @@ jsonrpc_client!(pub struct ProblemReportProxy { }); jsonrpc_client!(pub struct RelayListProxy { - pub fn relay_list_v2(&mut self) -> RpcRequest<RelayList>; + pub fn relay_list_v3(&mut self) -> RpcRequest<RelayList>; }); jsonrpc_client!(pub struct AppVersionProxy { diff --git a/mullvad-types/src/relay_list.rs b/mullvad-types/src/relay_list.rs index 370b8e134f..ee87751c43 100644 --- a/mullvad-types/src/relay_list.rs +++ b/mullvad-types/src/relay_list.rs @@ -48,6 +48,7 @@ pub struct Relay { pub hostname: String, pub ipv4_addr_in: Ipv4Addr, pub include_in_country: bool, + pub active: bool, pub weight: u64, #[serde(skip_serializing_if = "RelayTunnels::is_empty", default)] pub tunnels: RelayTunnels, |
