From fb6badcaddc57f2da1b4aafa408b9f2bd3bca087 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 19 Jun 2023 11:40:03 +0200 Subject: [PATCH 1/6] Remove `mullvad relay set hostname` command --- mullvad-cli/src/cmds/relay.rs | 43 ++++------------------------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index 9ddc86e5baa0..f2562dd69453 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -3,9 +3,8 @@ use clap::Subcommand; use itertools::Itertools; use mullvad_management_interface::MullvadProxyClient; use mullvad_types::{ - location::Hostname, relay_constraints::{ - Constraint, LocationConstraint, Match, OpenVpnConstraints, Ownership, Provider, Providers, + Constraint, Match, OpenVpnConstraints, Ownership, Provider, Providers, RelayConstraintsUpdate, RelaySettings, RelaySettingsUpdate, TransportPort, WireguardConstraints, }, @@ -42,14 +41,11 @@ pub enum Relay { pub enum SetCommands { /// Set country or city to select relays from. Use the 'list' /// command to show available alternatives. + /// + /// A relay can be selected by only supplying the hostname, such as + /// "se3-wireguard". Location(LocationArgs), - /// Set the location using only a hostname - Hostname { - /// A hostname, such as "se3-wireguard". - hostname: Hostname, - }, - /// Set hosting provider(s) to select relays from. The 'list' /// command shows the available relays and their providers. Provider { @@ -268,7 +264,6 @@ impl Relay { match subcmd { SetCommands::Custom(subcmd) => Self::set_custom(subcmd).await, SetCommands::Location(location) => Self::set_location(location).await, - SetCommands::Hostname { hostname } => Self::set_hostname(hostname).await, SetCommands::Provider { providers } => Self::set_providers(providers).await, SetCommands::Ownership { ownership } => Self::set_ownership(ownership).await, SetCommands::Tunnel(subcmd) => Self::set_tunnel(subcmd).await, @@ -395,36 +390,6 @@ impl Relay { }) } - async fn set_hostname(hostname: String) -> Result<()> { - let countries = Self::get_filtered_relays().await?; - - let find_relay = || { - for country in countries { - for city in country.cities { - for relay in city.relays { - if relay.hostname.to_lowercase() == hostname.to_lowercase() { - return Some(LocationConstraint::Hostname( - country.code, - city.code, - relay.hostname, - )); - } - } - } - } - None - }; - - let location = find_relay().ok_or(anyhow!("Hostname not found"))?; - - println!("Setting location constraint to {location}"); - Self::update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { - location: Some(Constraint::Only(location)), - ..Default::default() - })) - .await - } - async fn set_location(location_constraint: LocationArgs) -> Result<()> { let location_constraint = Constraint::from(location_constraint); match &location_constraint { From 83e38eba4036b65cd28be7e74768d19896d834ec Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 19 Jun 2023 13:31:53 +0200 Subject: [PATCH 2/6] Make `mullvad relay set location` smarter The `set location` command now takes a hostname and figures the country and city out. This is identical to how the (now deprecated) `mullvad relay set hostname` used to work. The `set location` command will try to resolve country code first, but if that fails we now fall back to trying to resolve a relay by hostname first. Update the help message (long & short) to cover this new use case. --- mullvad-cli/src/cmds/relay.rs | 99 ++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index f2562dd69453..9c5560d35e07 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -3,8 +3,9 @@ use clap::Subcommand; use itertools::Itertools; use mullvad_management_interface::MullvadProxyClient; use mullvad_types::{ + location::Location, relay_constraints::{ - Constraint, Match, OpenVpnConstraints, Ownership, Provider, Providers, + Constraint, LocationConstraint, Match, OpenVpnConstraints, Ownership, Provider, Providers, RelayConstraintsUpdate, RelaySettings, RelaySettingsUpdate, TransportPort, WireguardConstraints, }, @@ -39,11 +40,28 @@ pub enum Relay { #[derive(Subcommand, Debug, Clone)] pub enum SetCommands { - /// Set country or city to select relays from. Use the 'list' - /// command to show available alternatives. - /// - /// A relay can be selected by only supplying the hostname, such as - /// "se3-wireguard". + /// Select a relay using country, city or hostname. + /// The 'mullvad relay list' command shows the available relays and their + /// geographical location. + #[command( + override_usage = "mullvad relay set location [CITY] [HOSTNAME] | + + Select relay using a country: + +\tmullvad relay set location se + + Select relay using a country and city: + +\tmullvad relay set location se got + + Select relay using a country, city and hostname: + +\tmullvad relay set location se got se-got-wg-004 + + Select relay using only its hostname: + +\tmullvad relay set location se-got-wg-004" + )] Location(LocationArgs), /// Set hosting provider(s) to select relays from. The 'list' @@ -390,26 +408,35 @@ impl Relay { }) } - async fn set_location(location_constraint: LocationArgs) -> Result<()> { - let location_constraint = Constraint::from(location_constraint); - match &location_constraint { - Constraint::Any => (), - Constraint::Only(constraint) => { - let countries = Self::get_filtered_relays().await?; - - let found = countries - .into_iter() - .flat_map(|country| country.cities) - .flat_map(|city| city.relays) - .any(|relay| constraint.matches(&relay)); - - if !found { - eprintln!("Warning: No matching relay was found."); + async fn set_location(location_constraint_args: LocationArgs) -> Result<()> { + let countries = Self::get_filtered_relays().await?; + let constraint = + if let Some(relay) = + // The country field is assumed to be hostname due to CLI argument parsing + find_relay_by_hostname(&countries, &location_constraint_args.country) + { + Constraint::Only(relay) + } else { + let location_constraint = Constraint::from(location_constraint_args); + match &location_constraint { + Constraint::Any => (), + Constraint::Only(constraint) => { + let found = countries + .into_iter() + .flat_map(|country| country.cities) + .flat_map(|city| city.relays) + .any(|relay| constraint.matches(&relay)); + + if !found { + eprintln!("Warning: No matching relay was found."); + } + } } - } - } + location_constraint + }; + Self::update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { - location: Some(location_constraint), + location: Some(constraint), ..Default::default() })) .await @@ -553,3 +580,27 @@ fn parse_transport_port( (port, Constraint::Only(protocol)) => Constraint::Only(TransportPort { protocol, port }), } } + +/// Lookup a relay among a list of [`RelayListCountry`]s by hostname. +/// The matching is exact, bar capitalization. +fn find_relay_by_hostname( + countries: &[RelayListCountry], + hostname: &str, +) -> Option { + countries + .iter() + .flat_map(|country| country.cities.clone()) + .flat_map(|city| city.relays) + .find(|relay| relay.hostname.to_lowercase() == hostname.to_lowercase()) + .and_then(|relay| { + relay.location.map( + |Location { + country_code, + city_code, + .. + }| { + LocationConstraint::Hostname(country_code, city_code, relay.hostname) + }, + ) + }) +} From c6afb3defb5051223cae59488b7194dbace70b36 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 20 Jun 2023 09:37:04 +0200 Subject: [PATCH 3/6] Add smarter entry location constraint selection to multihop Add the same location constraint logic as `relay set location` to the entry location constraint for multihop. This implies that the relay selection for both work in the same way. --- mullvad-cli/src/cmds/relay.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index 9c5560d35e07..528c0addfbf0 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -129,6 +129,25 @@ pub enum SetTunnelCommands { pub enum EntryLocation { /// Entry endpoint to use. This can be 'any' or any location that is valid with 'set location', /// such as 'se got'. + #[command( + override_usage = "mullvad relay set tunnel wireguard entry-location [CITY] [HOSTNAME] | + + Select entry location using a country: + +\tmullvad relay set tunnel wireguard entry-location se + + Select entry location using a country and city: + +\tmullvad relay set tunnel wireguard entry-location se got + + Select entry location using a country, city and hostname: + +\tmullvad relay set tunnel wireguard entry-location se got se-got-wg-004 + + Select entry location using only its hostname: + +\tmullvad relay set tunnel wireguard entry-location se-got-wg-004" + )] EntryLocation(LocationArgs), } @@ -524,7 +543,14 @@ impl Relay { wireguard_constraints.use_multihop = *use_multihop; } if let Some(EntryLocation::EntryLocation(entry)) = entry_location { - wireguard_constraints.entry_location = Constraint::from(entry); + let countries = Self::get_filtered_relays().await?; + // The country field is assumed to be hostname due to CLI argument parsing + wireguard_constraints.entry_location = + if let Some(relay) = find_relay_by_hostname(&countries, &entry.country) { + Constraint::Only(relay) + } else { + Constraint::from(entry) + }; } Self::update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { From 4f7a239fd557111d5465c8e945411e3aa2a61986 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 20 Jun 2023 16:14:03 +0200 Subject: [PATCH 4/6] Add doc-comment to `get_filtered_relays` --- mullvad-cli/src/cmds/relay.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index 528c0addfbf0..d2aa62aa8eb9 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -261,6 +261,7 @@ impl Relay { Ok(()) } + /// Get active relays which are not bridges. async fn get_filtered_relays() -> Result> { let mut rpc = MullvadProxyClient::new().await?; let relay_list = rpc.get_relay_locations().await?; From 130ecedb86cf2a903d1d4ff3f3bce82b2a03743c Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 20 Jun 2023 16:18:59 +0200 Subject: [PATCH 5/6] Add smarter bridge location constraint selection Add the same location constraint logic as `relay set location` to the bridge location constraint for the `bridge set location` command. This implies that the relay selection for both work in the same way. --- mullvad-cli/src/cmds/bridge.rs | 35 +++++++++++++++++++++++++++++++--- mullvad-cli/src/cmds/relay.rs | 2 +- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs index 1652a0cfb2de..3a4061d9cab6 100644 --- a/mullvad-cli/src/cmds/bridge.rs +++ b/mullvad-cli/src/cmds/bridge.rs @@ -11,6 +11,7 @@ use mullvad_types::{ use std::net::{IpAddr, SocketAddr}; use talpid_types::net::openvpn::{self, SHADOWSOCKS_CIPHERS}; +use super::relay::find_relay_by_hostname; use super::relay_constraints::LocationArgs; #[derive(Subcommand, Debug)] @@ -29,8 +30,27 @@ pub enum SetCommands { /// Specify whether to use a bridge State { policy: BridgeState }, - /// Set country or city to select relays from. Use the 'list' - /// command to show available alternatives. + /// Set country or city to select relays from. + /// Use the 'mullvad bridge list' command to show available alternatives. + #[command( + override_usage = "mullvad bridge set location [CITY] [HOSTNAME] | + + Select bridge using a country: + +\tmullvad bridge set location se + + Select bridge using a country and city: + +\tmullvad bridge set location se got + + Select bridge using a country, city and hostname: + +\tmullvad bridge set location se got se-got-br-001 + + Select bridge using only its hostname: + +\tmullvad bridge set location se-got-br-001" + )] Location(LocationArgs), /// Set hosting provider(s) to select relays from. The 'list' @@ -138,7 +158,16 @@ impl Bridge { Ok(()) } SetCommands::Location(location) => { - Self::update_bridge_settings(Some(Constraint::from(location)), None, None).await + let mut rpc = MullvadProxyClient::new().await?; + let countries = rpc.get_relay_locations().await?.countries; + let location_constraint = + if let Some(relay) = find_relay_by_hostname(&countries, &location.country) { + Constraint::Only(relay) + } else { + Constraint::from(location) + }; + + Self::update_bridge_settings(Some(location_constraint), None, None).await } SetCommands::Ownership { ownership } => { Self::update_bridge_settings(None, None, Some(ownership)).await diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index d2aa62aa8eb9..6e213726d96f 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -610,7 +610,7 @@ fn parse_transport_port( /// Lookup a relay among a list of [`RelayListCountry`]s by hostname. /// The matching is exact, bar capitalization. -fn find_relay_by_hostname( +pub fn find_relay_by_hostname( countries: &[RelayListCountry], hostname: &str, ) -> Option { From 5c9d08d43c12f4c1b625d2734637cb35cf54df7f Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 20 Jun 2023 16:36:08 +0200 Subject: [PATCH 6/6] Update `CHANGELOG.md` --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46019032db37..910f28d73e95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,10 @@ Line wrap the file at 100 chars. Th multihop on or off. - In the CLI, the `mullvad account get` command will now print the account number (if there is one) after the device has been revoked. +- Update the CLI relay, multihop & bridge selection interface to accept a + hostname as sole argument, inheriting the behavior of `mullvad relay set + hostname`. This is in addition to accepting a geographical location as basis + for filtering relays. #### Windows - In the CLI, add a unified `mullvad split-tunnel get` command to replace the old commands @@ -62,6 +66,8 @@ Line wrap the file at 100 chars. Th #### macOS - Fix inability to sync iCloud and Safari bookmarks while connected to the VPN. +### Removed +- Remove the CLI subcommand `mullvad relay set hostname`. ## [android/2023.2] - 2023-05-22 ### Changed