summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--mullvad-cli/src/cmds/mod.rs26
-rw-r--r--mullvad-cli/src/cmds/relay.rs224
-rw-r--r--mullvad-cli/src/cmds/reset.rs32
-rw-r--r--mullvad-daemon/src/lib.rs63
-rw-r--r--mullvad-daemon/src/management_interface.rs29
-rw-r--r--mullvad-management-interface/proto/management_interface.proto9
-rw-r--r--mullvad-management-interface/src/client.rs21
-rw-r--r--mullvad-management-interface/src/types/conversions/relay_constraints.rs36
-rw-r--r--mullvad-management-interface/src/types/conversions/settings.rs11
-rw-r--r--mullvad-relay-selector/src/lib.rs266
-rw-r--r--mullvad-relay-selector/src/matcher.rs11
-rw-r--r--mullvad-relay-selector/src/updater.rs14
-rw-r--r--mullvad-types/src/relay_constraints.rs50
-rw-r--r--mullvad-types/src/relay_list.rs8
-rw-r--r--mullvad-types/src/settings/mod.rs28
16 files changed, 663 insertions, 166 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5b208bd3e1..0a4af7319c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,6 +31,7 @@ Line wrap the file at 100 chars. Th
`mullvad api-access`, and the initially supported network protocols are `Shadowsocks` and
`SOCKS5`.
- Add social media content blocker.
+- Add ability to override server IPs to the CLI.
### Changed
- Update Electron from 25.2.0 to 26.3.0.
diff --git a/mullvad-cli/src/cmds/mod.rs b/mullvad-cli/src/cmds/mod.rs
index 88e4184f07..43d224233e 100644
--- a/mullvad-cli/src/cmds/mod.rs
+++ b/mullvad-cli/src/cmds/mod.rs
@@ -1,4 +1,5 @@
use clap::builder::{PossibleValuesParser, TypedValueParser, ValueParser};
+use std::io::stdin;
use std::ops::Deref;
pub mod account;
@@ -80,3 +81,28 @@ impl std::fmt::Display for BooleanOption {
}
}
}
+
+async fn receive_confirmation(msg: &'static str, default: bool) -> bool {
+ let helper_str = match default {
+ true => "[Y/n]",
+ false => "[y/N]",
+ };
+
+ println!("{msg} {helper_str}");
+
+ tokio::task::spawn_blocking(move || loop {
+ let mut buf = String::new();
+ if let Err(e) = stdin().read_line(&mut buf) {
+ eprintln!("Couldn't read from STDIN: {e}");
+ return false;
+ }
+ match buf.trim().to_ascii_lowercase().as_str() {
+ "" => return default,
+ "y" | "ye" | "yes" => return true,
+ "n" | "no" => return false,
+ _ => eprintln!("Unexpected response. Please enter \"y\" or \"n\""),
+ }
+ })
+ .await
+ .unwrap()
+}
diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs
index 6d1b487126..273b6f3395 100644
--- a/mullvad-cli/src/cmds/relay.rs
+++ b/mullvad-cli/src/cmds/relay.rs
@@ -3,16 +3,17 @@ use clap::Subcommand;
use itertools::Itertools;
use mullvad_management_interface::MullvadProxyClient;
use mullvad_types::{
- location::Location,
+ location::{CountryCode, Location},
relay_constraints::{
Constraint, GeographicLocationConstraint, LocationConstraint, LocationConstraintFormatter,
- Match, OpenVpnConstraints, Ownership, Provider, Providers, RelayConstraints, RelaySettings,
- TransportPort, WireguardConstraints,
+ Match, OpenVpnConstraints, Ownership, Provider, Providers, RelayConstraints, RelayOverride,
+ RelaySettings, TransportPort, WireguardConstraints,
},
relay_list::{RelayEndpointData, RelayListCountry},
ConnectionConfig, CustomTunnelEndpoint,
};
use std::{
+ collections::HashMap,
io::BufRead,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
};
@@ -21,7 +22,7 @@ use talpid_types::net::{
};
use super::{relay_constraints::LocationArgs, BooleanOption};
-use crate::print_option;
+use crate::{cmds::receive_confirmation, print_option};
#[derive(Subcommand, Debug)]
pub enum Relay {
@@ -37,6 +38,10 @@ pub enum Relay {
/// Update the relay list
Update,
+
+ /// Override options for individual relays/servers
+ #[clap(subcommand)]
+ Override(OverrideCommands),
}
#[derive(Subcommand, Debug, Clone)]
@@ -201,6 +206,50 @@ pub enum SetCustomCommands {
},
}
+#[derive(Subcommand, Debug, Clone)]
+pub enum OverrideCommands {
+ /// Show current custom fields for servers
+ Get,
+ /// Set a custom field for a server
+ #[clap(subcommand)]
+ Set(OverrideSetCommands),
+ /// Unset a custom field for a server
+ #[clap(subcommand)]
+ Unset(OverrideUnsetCommands),
+ /// Unset custom IPs for all servers
+ ClearAll {
+ /// Clear overrides without asking for confirmation
+ #[arg(long, short = 'y', default_value_t = false)]
+ confirm: bool,
+ },
+}
+
+#[derive(Subcommand, Debug, Clone)]
+pub enum OverrideSetCommands {
+ /// Override entry IPv4 address for a given relay
+ Ipv4 {
+ /// The unique hostname for the server to set the override on
+ hostname: String,
+ /// The IPv4 address to use to connect to this server
+ address: Ipv4Addr,
+ },
+ /// Override entry IPv6 address for a given relay
+ Ipv6 {
+ /// The unique hostname for the server to set the override on
+ hostname: String,
+ /// The IPv6 address to use to connect to this server
+ address: Ipv6Addr,
+ },
+}
+
+#[derive(Subcommand, Debug, Clone)]
+pub enum OverrideUnsetCommands {
+ /// Remove overridden entry IPv4 address for the given server
+ Ipv4 { hostname: String },
+ /// Remove overridden entry IPv6 address for the given server
+ Ipv6 { hostname: String },
+}
+
impl Relay {
pub async fn handle(self) -> Result<()> {
match self {
@@ -208,6 +257,7 @@ impl Relay {
Relay::List => Self::list().await,
Relay::Update => Self::update().await,
Relay::Set(subcmd) => Self::set(subcmd).await,
+ Relay::Override(subcmd) => Self::r#override(subcmd).await,
}
}
@@ -662,6 +712,164 @@ impl Relay {
})
.await
}
+
+ async fn update_override(
+ hostname: &str,
+ update_fn: impl FnOnce(&mut RelayOverride),
+ warn_non_existent_hostname: bool,
+ ) -> Result<()> {
+ let mut rpc = MullvadProxyClient::new().await?;
+ let settings = rpc.get_settings().await?;
+
+ if warn_non_existent_hostname {
+ let countries = get_filtered_relays_with_client(&mut rpc).await?;
+ if find_relay_by_hostname(&countries, hostname).is_none() {
+ eprintln!("Warning: Setting overrides for an unrecognized server");
+ }
+ }
+
+ let mut relay_overrides = settings.relay_overrides;
+ let mut element = relay_overrides
+ .iter()
+ .position(|elem| elem.hostname == hostname)
+ .map(|index| relay_overrides.swap_remove(index))
+ .unwrap_or_else(|| RelayOverride::empty(hostname.to_owned()));
+ update_fn(&mut element);
+
+ rpc.set_relay_override(element).await?;
+ println!("Updated override options for {hostname}");
+ Ok(())
+ }
+
+ async fn r#override(subcmd: OverrideCommands) -> Result<()> {
+ match subcmd {
+ OverrideCommands::Get => {
+ let mut rpc = MullvadProxyClient::new().await?;
+ let settings = rpc.get_settings().await?;
+
+ let mut overrides = HashMap::new();
+ for relay_override in settings.relay_overrides {
+ overrides.insert(relay_override.hostname.clone(), relay_override);
+ }
+
+ struct Country {
+ name: String,
+ code: CountryCode,
+ cities: Vec<City>,
+ }
+ struct City {
+ name: String,
+ code: CountryCode,
+ overrides: Vec<RelayOverride>,
+ }
+
+ let mut countries_with_overrides = vec![];
+ for country in get_filtered_relays().await? {
+ let mut country_with_overrides = Country {
+ name: country.name,
+ code: country.code,
+ cities: vec![],
+ };
+
+ for city in country.cities {
+ let mut city_with_overrides = City {
+ name: city.name,
+ code: city.code,
+ overrides: vec![],
+ };
+
+ for relay in city.relays {
+ if let Some(relay_override) = overrides.remove(&relay.hostname) {
+ city_with_overrides.overrides.push(relay_override);
+ }
+ }
+
+ if !city_with_overrides.overrides.is_empty() {
+ country_with_overrides.cities.push(city_with_overrides);
+ }
+ }
+
+ if !country_with_overrides.cities.is_empty() {
+ countries_with_overrides.push(country_with_overrides);
+ }
+ }
+
+ let print_relay_override = |relay_override: RelayOverride| {
+ println!("{:<8}{}:", " ", relay_override.hostname);
+ if let Some(ipv4) = relay_override.ipv4_addr_in {
+ println!("{:<12}ipv4: {ipv4}", " ");
+ }
+ if let Some(ipv6) = relay_override.ipv6_addr_in {
+ println!("{:<12}ipv6: {ipv6}", " ");
+ }
+ };
+
+ for country in countries_with_overrides {
+ println!("{} ({})", country.name, country.code);
+ for city in country.cities {
+ println!("{:<4}{} ({})", " ", city.name, city.code);
+ for relay_override in city.overrides {
+ print_relay_override(relay_override);
+ }
+ }
+ }
+
+ if !overrides.is_empty() {
+ println!("Overrides for unrecognized servers. Consider removing these!");
+ for relay_override in overrides.into_values() {
+ print_relay_override(relay_override);
+ }
+ }
+ }
+ OverrideCommands::Set(set_cmds) => match set_cmds {
+ OverrideSetCommands::Ipv4 { hostname, address } => {
+ Self::update_override(
+ &hostname,
+ |relay_override| relay_override.ipv4_addr_in = Some(address),
+ true,
+ )
+ .await?;
+ }
+ OverrideSetCommands::Ipv6 { hostname, address } => {
+ Self::update_override(
+ &hostname,
+ |relay_override| relay_override.ipv6_addr_in = Some(address),
+ true,
+ )
+ .await?;
+ }
+ },
+ OverrideCommands::Unset(cmds) => match cmds {
+ OverrideUnsetCommands::Ipv4 { hostname } => {
+ Self::update_override(
+ &hostname,
+ |relay_override| relay_override.ipv4_addr_in = None,
+ false,
+ )
+ .await?;
+ }
+ OverrideUnsetCommands::Ipv6 { hostname } => {
+ Self::update_override(
+ &hostname,
+ |relay_override| relay_override.ipv6_addr_in = None,
+ false,
+ )
+ .await?;
+ }
+ },
+ OverrideCommands::ClearAll { confirm } => {
+ if confirm
+ || receive_confirmation("Are you sure you want to clear all overrides?", true)
+ .await
+ {
+ let mut rpc = MullvadProxyClient::new().await?;
+ rpc.clear_all_relay_overrides().await?;
+ println!("All overrides unset");
+ }
+ }
+ }
+ Ok(())
+ }
}
fn parse_transport_port(
@@ -714,8 +922,16 @@ pub fn find_relay_by_hostname(
})
}
+/// Return a list of all active non-bridge relays
pub async fn get_filtered_relays() -> Result<Vec<RelayListCountry>> {
let mut rpc = MullvadProxyClient::new().await?;
+ get_filtered_relays_with_client(&mut rpc).await
+}
+
+/// Return a list of all active non-bridge relays
+async fn get_filtered_relays_with_client(
+ rpc: &mut MullvadProxyClient,
+) -> Result<Vec<RelayListCountry>> {
let relay_list = rpc.get_relay_locations().await?;
let mut countries = vec![];
diff --git a/mullvad-cli/src/cmds/reset.rs b/mullvad-cli/src/cmds/reset.rs
index a8c275a042..88005c4631 100644
--- a/mullvad-cli/src/cmds/reset.rs
+++ b/mullvad-cli/src/cmds/reset.rs
@@ -1,32 +1,14 @@
+use super::receive_confirmation;
use anyhow::Result;
use mullvad_management_interface::MullvadProxyClient;
-use std::io::stdin;
pub async fn handle() -> Result<()> {
- if receive_confirmation().await {
- let mut rpc = MullvadProxyClient::new().await?;
- rpc.factory_reset().await?;
- #[cfg(target_os = "linux")]
- println!("If you're running systemd, to remove all logs, you must use journalctl");
+ if !receive_confirmation("Are you sure you want to disconnect, log out, delete all settings, logs and cache files for the Mullvad VPN system service?", false).await {
+ return Ok(());
}
+ let mut rpc = MullvadProxyClient::new().await?;
+ rpc.factory_reset().await?;
+ #[cfg(target_os = "linux")]
+ println!("If you're running systemd, to remove all logs, you must use journalctl");
Ok(())
}
-
-async fn receive_confirmation() -> bool {
- println!("Are you sure you want to disconnect, log out, delete all settings, logs and cache files for the Mullvad VPN system service? [Yes/No (default)]");
-
- tokio::task::spawn_blocking(|| loop {
- let mut buf = String::new();
- if let Err(e) = stdin().read_line(&mut buf) {
- eprintln!("Couldn't read from STDIN: {e}");
- return false;
- }
- match buf.trim() {
- "Yes" => return true,
- "No" | "no" | "" => return false,
- _ => eprintln!("Unexpected response. Please enter \"Yes\" or \"No\""),
- }
- })
- .await
- .unwrap()
-}
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 77304ae75f..98a8febe2a 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -47,7 +47,9 @@ use mullvad_types::{
custom_list::CustomList,
device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent},
location::GeoIpLocation,
- relay_constraints::{BridgeSettings, BridgeState, ObfuscationSettings, RelaySettings},
+ relay_constraints::{
+ BridgeSettings, BridgeState, ObfuscationSettings, RelayOverride, RelaySettings,
+ },
relay_list::RelayList,
settings::{DnsOptions, Settings},
states::{TargetState, TunnelState},
@@ -253,6 +255,10 @@ pub enum DaemonCommand {
SetQuantumResistantTunnel(ResponseTx<(), settings::Error>, QuantumResistantState),
/// Set DNS options or servers to use
SetDnsOptions(ResponseTx<(), settings::Error>, DnsOptions),
+ /// Set override options to use for a given relay
+ SetRelayOverride(ResponseTx<(), settings::Error>, RelayOverride),
+ /// Remove all relay override options
+ ClearAllRelayOverrides(ResponseTx<(), settings::Error>),
/// Toggle macOS network check leak
/// Set MTU for wireguard tunnels
SetWireguardMtu(ResponseTx<(), settings::Error>, Option<u16>),
@@ -1077,6 +1083,10 @@ where
.await
}
SetDnsOptions(tx, dns_servers) => self.on_set_dns_options(tx, dns_servers).await,
+ SetRelayOverride(tx, relay_override) => {
+ self.on_set_relay_override(tx, relay_override).await
+ }
+ ClearAllRelayOverrides(tx) => self.on_clear_all_relay_overrides(tx).await,
SetWireguardMtu(tx, mtu) => self.on_set_wireguard_mtu(tx, mtu).await,
SetWireguardRotationInterval(tx, interval) => {
self.on_set_wireguard_rotation_interval(tx, interval).await
@@ -2163,6 +2173,56 @@ where
}
}
+ async fn on_set_relay_override(
+ &mut self,
+ tx: ResponseTx<(), settings::Error>,
+ relay_override: RelayOverride,
+ ) {
+ match self
+ .settings
+ .update(move |settings| settings.set_relay_override(relay_override))
+ .await
+ {
+ Ok(settings_changed) => {
+ Self::oneshot_send(tx, Ok(()), "set_relay_override response");
+ if settings_changed {
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
+ self.relay_selector
+ .set_config(new_selector_config(&self.settings));
+ self.reconnect_tunnel();
+ }
+ }
+ Err(e) => {
+ log::error!("{}", e.display_chain_with_msg("Unable to save settings"));
+ Self::oneshot_send(tx, Err(e), "set_relay_override response");
+ }
+ }
+ }
+
+ async fn on_clear_all_relay_overrides(&mut self, tx: ResponseTx<(), settings::Error>) {
+ match self
+ .settings
+ .update(move |settings| settings.relay_overrides.clear())
+ .await
+ {
+ Ok(settings_changed) => {
+ Self::oneshot_send(tx, Ok(()), "clear_all_relay_overrides response");
+ if settings_changed {
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
+ self.relay_selector
+ .set_config(new_selector_config(&self.settings));
+ self.reconnect_tunnel();
+ }
+ }
+ Err(e) => {
+ log::error!("{}", e.display_chain_with_msg("Unable to save settings"));
+ Self::oneshot_send(tx, Err(e), "clear_all_relay_overrides response");
+ }
+ }
+ }
+
async fn on_set_wireguard_mtu(
&mut self,
tx: ResponseTx<(), settings::Error>,
@@ -2522,5 +2582,6 @@ fn new_selector_config(settings: &Settings) -> SelectorConfig {
obfuscation_settings: settings.obfuscation_settings.clone(),
default_tunnel_type,
custom_lists: settings.custom_lists.clone(),
+ relay_overrides: settings.relay_overrides.clone(),
}
}
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index 79466d8878..e67a02117c 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -13,7 +13,9 @@ use mullvad_paths;
use mullvad_types::settings::DnsOptions;
use mullvad_types::{
account::AccountToken,
- relay_constraints::{BridgeSettings, BridgeState, ObfuscationSettings, RelaySettings},
+ relay_constraints::{
+ BridgeSettings, BridgeState, ObfuscationSettings, RelayOverride, RelaySettings,
+ },
relay_list::RelayList,
settings::Settings,
states::{TargetState, TunnelState},
@@ -379,6 +381,31 @@ impl ManagementService for ManagementServiceImpl {
Ok(Response::new(()))
}
+ async fn set_relay_override(
+ &self,
+ request: Request<types::RelayOverride>,
+ ) -> ServiceResult<()> {
+ let relay_override =
+ RelayOverride::try_from(request.into_inner()).map_err(map_protobuf_type_err)?;
+ log::debug!("set_relay_override");
+ let (tx, rx) = oneshot::channel();
+ self.send_command_to_daemon(DaemonCommand::SetRelayOverride(tx, relay_override))?;
+ self.wait_for_result(rx)
+ .await?
+ .map(Response::new)
+ .map_err(map_settings_error)
+ }
+
+ async fn clear_all_relay_overrides(&self, _: Request<()>) -> ServiceResult<()> {
+ log::debug!("clear_all_relay_overrides");
+ let (tx, rx) = oneshot::channel();
+ self.send_command_to_daemon(DaemonCommand::ClearAllRelayOverrides(tx))?;
+ self.wait_for_result(rx)
+ .await?
+ .map(Response::new)
+ .map_err(map_settings_error)
+ }
+
// Account management
//
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index 0eefd9a1db..64d87d37ef 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -46,6 +46,8 @@ service ManagementService {
rpc SetEnableIpv6(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
rpc SetQuantumResistantTunnel(QuantumResistantState) returns (google.protobuf.Empty) {}
rpc SetDnsOptions(DnsOptions) returns (google.protobuf.Empty) {}
+ rpc SetRelayOverride(RelayOverride) returns (google.protobuf.Empty) {}
+ rpc ClearAllRelayOverrides(google.protobuf.Empty) returns (google.protobuf.Empty) {}
// Account management
rpc CreateNewAccount(google.protobuf.Empty) returns (google.protobuf.StringValue) {}
@@ -390,6 +392,13 @@ message Settings {
ObfuscationSettings obfuscation_settings = 10;
CustomListSettings custom_lists = 11;
ApiAccessMethodSettings api_access_methods = 12;
+ repeated RelayOverride relay_overrides = 13;
+}
+
+message RelayOverride {
+ string hostname = 1;
+ optional string ipv4_addr_in = 2;
+ optional string ipv6_addr_in = 3;
}
message SplitTunnelSettings {
diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs
index 1c15a169cd..140eddc08a 100644
--- a/mullvad-management-interface/src/client.rs
+++ b/mullvad-management-interface/src/client.rs
@@ -8,7 +8,9 @@ use mullvad_types::{
custom_list::{CustomList, Id},
device::{Device, DeviceEvent, DeviceId, DeviceState, RemoveDeviceEvent},
location::GeoIpLocation,
- relay_constraints::{BridgeSettings, BridgeState, ObfuscationSettings, RelaySettings},
+ relay_constraints::{
+ BridgeSettings, BridgeState, ObfuscationSettings, RelayOverride, RelaySettings,
+ },
relay_list::RelayList,
settings::{DnsOptions, Settings},
states::TunnelState,
@@ -339,6 +341,23 @@ impl MullvadProxyClient {
Ok(())
}
+ pub async fn set_relay_override(&mut self, relay_override: RelayOverride) -> Result<()> {
+ let r#override = types::RelayOverride::from(relay_override);
+ self.0
+ .set_relay_override(r#override)
+ .await
+ .map_err(Error::Rpc)?;
+ Ok(())
+ }
+
+ pub async fn clear_all_relay_overrides(&mut self) -> Result<()> {
+ self.0
+ .clear_all_relay_overrides(())
+ .await
+ .map_err(Error::Rpc)?;
+ Ok(())
+ }
+
pub async fn create_new_account(&mut self) -> Result<AccountToken> {
Ok(self
.0
diff --git a/mullvad-management-interface/src/types/conversions/relay_constraints.rs b/mullvad-management-interface/src/types/conversions/relay_constraints.rs
index cff2bb5847..a1d468c976 100644
--- a/mullvad-management-interface/src/types/conversions/relay_constraints.rs
+++ b/mullvad-management-interface/src/types/conversions/relay_constraints.rs
@@ -545,6 +545,42 @@ impl TryFrom<proto::TransportPort> for mullvad_types::relay_constraints::Transpo
}
}
+impl From<mullvad_types::relay_constraints::RelayOverride> for proto::RelayOverride {
+ fn from(r#override: mullvad_types::relay_constraints::RelayOverride) -> proto::RelayOverride {
+ proto::RelayOverride {
+ hostname: r#override.hostname,
+ ipv4_addr_in: r#override.ipv4_addr_in.map(|addr| addr.to_string()),
+ ipv6_addr_in: r#override.ipv6_addr_in.map(|addr| addr.to_string()),
+ }
+ }
+}
+
+impl TryFrom<proto::RelayOverride> for mullvad_types::relay_constraints::RelayOverride {
+ type Error = FromProtobufTypeError;
+
+ fn try_from(
+ r#override: proto::RelayOverride,
+ ) -> Result<mullvad_types::relay_constraints::RelayOverride, Self::Error> {
+ Ok(mullvad_types::relay_constraints::RelayOverride {
+ hostname: r#override.hostname,
+ ipv4_addr_in: r#override
+ .ipv4_addr_in
+ .map(|addr| {
+ addr.parse()
+ .map_err(|_| FromProtobufTypeError::InvalidArgument("invalid IPv4 address"))
+ })
+ .transpose()?,
+ ipv6_addr_in: r#override
+ .ipv6_addr_in
+ .map(|addr| {
+ addr.parse()
+ .map_err(|_| FromProtobufTypeError::InvalidArgument("invalid IPv6 address"))
+ })
+ .transpose()?,
+ })
+ }
+}
+
pub fn try_providers_constraint_from_proto(
providers: &[String],
) -> Result<Constraint<mullvad_types::relay_constraints::Providers>, FromProtobufTypeError> {
diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs
index c18e7aab57..a7c4bcd78c 100644
--- a/mullvad-management-interface/src/types/conversions/settings.rs
+++ b/mullvad-management-interface/src/types/conversions/settings.rs
@@ -45,6 +45,12 @@ impl From<&mullvad_types::settings::Settings> for proto::Settings {
api_access_methods: Some(proto::ApiAccessMethodSettings::from(
&settings.api_access_methods,
)),
+ relay_overrides: settings
+ .relay_overrides
+ .iter()
+ .cloned()
+ .map(proto::RelayOverride::from)
+ .collect(),
}
}
}
@@ -168,6 +174,11 @@ impl TryFrom<proto::Settings> for mullvad_types::settings::Settings {
block_when_disconnected: settings.block_when_disconnected,
auto_connect: settings.auto_connect,
tunnel_options: mullvad_types::settings::TunnelOptions::try_from(tunnel_options)?,
+ relay_overrides: settings
+ .relay_overrides
+ .into_iter()
+ .map(mullvad_types::relay_constraints::RelayOverride::try_from)
+ .collect::<Result<Vec<_>, _>>()?,
show_beta_releases: settings.show_beta_releases,
#[cfg(windows)]
split_tunnel: mullvad_types::settings::SplitTunnelSettings::from(split_tunnel),
diff --git a/mullvad-relay-selector/src/lib.rs b/mullvad-relay-selector/src/lib.rs
index 1046624af7..7c8583493c 100644
--- a/mullvad-relay-selector/src/lib.rs
+++ b/mullvad-relay-selector/src/lib.rs
@@ -10,8 +10,8 @@ use mullvad_types::{
relay_constraints::{
BridgeSettings, BridgeState, Constraint, InternalBridgeConstraints, LocationConstraint,
Match, ObfuscationSettings, OpenVpnConstraints, Ownership, Providers, RelayConstraints,
- RelayConstraintsFormatter, RelaySettings, ResolvedLocationConstraint, SelectedObfuscation,
- Set, TransportPort, Udp2TcpObfuscationSettings,
+ RelayConstraintsFormatter, RelayOverride, RelaySettings, ResolvedLocationConstraint,
+ SelectedObfuscation, Set, TransportPort, Udp2TcpObfuscationSettings,
},
relay_list::{BridgeEndpointData, Relay, RelayEndpointData, RelayList},
CustomTunnelEndpoint,
@@ -19,6 +19,7 @@ use mullvad_types::{
use parking_lot::{Mutex, MutexGuard};
use rand::{seq::SliceRandom, Rng};
use std::{
+ collections::HashMap,
io,
net::{IpAddr, SocketAddr},
path::Path,
@@ -79,64 +80,80 @@ pub enum Error {
struct ParsedRelays {
last_updated: SystemTime,
- locations: RelayList,
- relays: Vec<Relay>,
+ parsed_list: RelayList,
+ original_list: RelayList,
+ overrides: Vec<RelayOverride>,
}
impl ParsedRelays {
- pub fn empty() -> Self {
+ /// Return a flat iterator with all relays
+ pub fn relays(&self) -> impl Iterator<Item = &Relay> + Clone + '_ {
+ self.parsed_list.relays()
+ }
+
+ pub fn update(&mut self, new_relays: RelayList) {
+ *self = Self::from_relay_list(new_relays, SystemTime::now(), &self.overrides);
+
+ log::info!(
+ "Updated relay inventory has {} relays",
+ self.relays().count()
+ );
+ }
+
+ pub fn last_updated(&self) -> SystemTime {
+ self.last_updated
+ }
+
+ fn set_overrides(&mut self, new_overrides: &[RelayOverride]) {
+ self.parsed_list = Self::parse_relay_list(&self.original_list, new_overrides);
+ self.overrides = new_overrides.to_vec();
+ }
+
+ fn empty() -> Self {
ParsedRelays {
last_updated: time::UNIX_EPOCH,
- locations: RelayList::empty(),
- relays: Vec::new(),
+ parsed_list: RelayList::empty(),
+ original_list: RelayList::empty(),
+ overrides: vec![],
}
}
- pub fn from_relay_list(mut relay_list: RelayList, last_updated: SystemTime) -> Self {
- // Append data for obfuscation protocols ourselves, since the API does not provide it.
- if relay_list.wireguard.udp2tcp_ports.is_empty() {
- relay_list.wireguard.udp2tcp_ports.extend(UDP2TCP_PORTS);
- }
-
- let mut relays = Vec::new();
- for country in &relay_list.countries {
- let country_name = country.name.clone();
- let country_code = country.code.clone();
- for city in &country.cities {
- let city_name = city.name.clone();
- let city_code = city.code.clone();
- let latitude = city.latitude;
- let longitude = city.longitude;
- for relay in &city.relays {
- let mut relay_with_location = relay.clone();
- relay_with_location.location = Some(Location {
- country: country_name.clone(),
- country_code: country_code.clone(),
- city: city_name.clone(),
- city_code: city_code.clone(),
- latitude,
- longitude,
- });
- relays.push(relay_with_location);
- }
+ /// Try to read the relays from disk, preferring the newer ones.
+ fn from_dir(
+ cache_path: &Path,
+ resource_path: &Path,
+ overrides: &[RelayOverride],
+ ) -> Result<Self, Error> {
+ // prefer the resource path's relay list if the cached one doesn't exist or was modified
+ // before the resource one was created.
+ let cached_relays = Self::from_file(cache_path, overrides);
+ let bundled_relays = match Self::from_file(resource_path, overrides) {
+ Ok(bundled_relays) => bundled_relays,
+ Err(e) => {
+ log::error!("Failed to load bundled relays: {}", e);
+ return cached_relays;
}
- }
+ };
- ParsedRelays {
- last_updated,
- locations: relay_list,
- relays,
+ if cached_relays
+ .as_ref()
+ .map(|cached| cached.last_updated > bundled_relays.last_updated)
+ .unwrap_or(false)
+ {
+ cached_relays
+ } else {
+ Ok(bundled_relays)
}
}
- pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> {
+ fn from_file(path: impl AsRef<Path>, overrides: &[RelayOverride]) -> Result<Self, Error> {
log::debug!("Reading relays from {}", path.as_ref().display());
let (last_modified, file) =
Self::open_file(path.as_ref()).map_err(Error::OpenRelayCache)?;
let relay_list =
serde_json::from_reader(io::BufReader::new(file)).map_err(Error::Serialize)?;
- Ok(Self::from_relay_list(relay_list, last_modified))
+ Ok(Self::from_relay_list(relay_list, last_modified, overrides))
}
fn open_file(path: &Path) -> io::Result<(SystemTime, std::fs::File)> {
@@ -145,20 +162,58 @@ impl ParsedRelays {
Ok((last_modified, file))
}
- pub fn last_updated(&self) -> SystemTime {
- self.last_updated
+ fn from_relay_list(
+ relay_list: RelayList,
+ last_updated: SystemTime,
+ overrides: &[RelayOverride],
+ ) -> Self {
+ ParsedRelays {
+ last_updated,
+ parsed_list: Self::parse_relay_list(&relay_list, overrides),
+ original_list: relay_list,
+ overrides: overrides.to_vec(),
+ }
}
- pub fn locations(&self) -> &RelayList {
- &self.locations
- }
+ fn parse_relay_list(relay_list: &RelayList, overrides: &[RelayOverride]) -> RelayList {
+ let mut remaining_overrides = HashMap::new();
+ for relay_override in overrides {
+ remaining_overrides.insert(
+ relay_override.hostname.to_owned(),
+ relay_override.to_owned(),
+ );
+ }
- pub fn relays(&self) -> &Vec<Relay> {
- &self.relays
- }
+ let mut parsed_list = relay_list.clone();
+
+ // Append data for obfuscation protocols ourselves, since the API does not provide it.
+ if parsed_list.wireguard.udp2tcp_ports.is_empty() {
+ parsed_list.wireguard.udp2tcp_ports.extend(UDP2TCP_PORTS);
+ }
- pub fn tag(&self) -> Option<&str> {
- self.locations.etag.as_deref()
+ // Add location and override relay data
+ for country in &mut parsed_list.countries {
+ for city in &mut country.cities {
+ for relay in &mut city.relays {
+ // Append location data
+ relay.location = Some(Location {
+ country: country.name.clone(),
+ country_code: country.code.clone(),
+ city: city.name.clone(),
+ city_code: city.code.clone(),
+ latitude: city.latitude,
+ longitude: city.longitude,
+ });
+
+ // Append overrides
+ if let Some(overrides) = remaining_overrides.remove(&relay.hostname) {
+ overrides.apply_to_relay(relay);
+ }
+ }
+ }
+ }
+
+ parsed_list
}
}
@@ -170,6 +225,7 @@ pub struct SelectorConfig {
pub obfuscation_settings: ObfuscationSettings,
pub default_tunnel_type: TunnelType,
pub custom_lists: CustomListsSettings,
+ pub relay_overrides: Vec<RelayOverride>,
}
#[derive(Clone)]
@@ -183,17 +239,18 @@ impl RelaySelector {
pub fn new(config: SelectorConfig, resource_dir: &Path, cache_dir: &Path) -> Self {
let cache_path = cache_dir.join(RELAYS_FILENAME);
let resource_path = resource_dir.join(RELAYS_FILENAME);
- let unsynchronized_parsed_relays = Self::read_relays_from_disk(&cache_path, &resource_path)
- .unwrap_or_else(|error| {
- log::error!(
- "{}",
- error.display_chain_with_msg("Unable to load cached relays")
- );
- ParsedRelays::empty()
- });
+ let unsynchronized_parsed_relays =
+ ParsedRelays::from_dir(&cache_path, &resource_path, &config.relay_overrides)
+ .unwrap_or_else(|error| {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg("Unable to load cached and bundled relays")
+ );
+ ParsedRelays::empty()
+ });
log::info!(
"Initialized with {} cached relays from {}",
- unsynchronized_parsed_relays.relays().len(),
+ unsynchronized_parsed_relays.relays().count(),
DateTime::<Local>::from(unsynchronized_parsed_relays.last_updated())
.format(DATE_TIME_FORMAT_STR)
);
@@ -205,13 +262,15 @@ impl RelaySelector {
}
pub fn set_config(&mut self, config: SelectorConfig) {
+ let mut parsed_relays = self.parsed_relays.lock();
+ parsed_relays.set_overrides(&config.relay_overrides);
*self.config.lock() = config;
}
/// Returns all countries and cities. The cities in the object returned does not have any
/// relays in them.
pub fn get_locations(&mut self) -> RelayList {
- self.parsed_relays.lock().locations().clone()
+ self.parsed_relays.lock().original_list.clone()
}
/// Returns a random relay and relay endpoint matching the current constraints.
@@ -315,8 +374,8 @@ impl RelaySelector {
let (openvpn_data, wireguard_data) = {
let relays = self.parsed_relays.lock();
(
- relays.locations.openvpn.clone(),
- relays.locations.wireguard.clone(),
+ relays.parsed_list.openvpn.clone(),
+ relays.parsed_list.wireguard.clone(),
)
};
@@ -359,7 +418,7 @@ impl RelaySelector {
ownership: relay_constraints.ownership,
endpoint_matcher: OpenVpnMatcher::new(
relay_constraints.openvpn_constraints,
- self.parsed_relays.lock().locations.openvpn.clone(),
+ self.parsed_relays.lock().parsed_list.openvpn.clone(),
),
};
@@ -467,7 +526,7 @@ impl RelaySelector {
retry_attempt: u32,
custom_lists: &CustomListsSettings,
) -> Result<NormalSelectedRelay, Error> {
- let wg_endpoint_data = self.parsed_relays.lock().locations.wireguard.clone();
+ let wg_endpoint_data = self.parsed_relays.lock().parsed_list.wireguard.clone();
// NOTE: If not using multihop then `location` is set as the only location constraint.
// If using multihop then location is the exit constraint and
@@ -534,8 +593,8 @@ impl RelaySelector {
let (openvpn_data, wireguard_data) = {
let relays = self.parsed_relays.lock();
(
- relays.locations.openvpn.clone(),
- relays.locations.wireguard.clone(),
+ relays.parsed_list.openvpn.clone(),
+ relays.parsed_list.wireguard.clone(),
)
};
let mut matcher = RelayMatcher::new(
@@ -924,7 +983,7 @@ impl RelaySelector {
self.pick_random_relay(&matching_relays).cloned()
};
relay.and_then(|relay| {
- self.pick_random_bridge(&self.parsed_relays.lock().locations.bridge, &relay)
+ self.pick_random_bridge(&self.parsed_relays.lock().parsed_list.bridge, &relay)
.map(|bridge| (bridge, relay.clone()))
})
}
@@ -997,7 +1056,12 @@ impl RelaySelector {
endpoint: &MullvadWireguardEndpoint,
retry_attempt: u32,
) -> Option<SelectedObfuscator> {
- let udp2tcp_ports = &self.parsed_relays.lock().locations.wireguard.udp2tcp_ports;
+ let udp2tcp_ports = &self
+ .parsed_relays
+ .lock()
+ .parsed_list
+ .wireguard
+ .udp2tcp_ports;
let udp2tcp_endpoint = if obfuscation_settings.port.is_only() {
udp2tcp_ports
.iter()
@@ -1027,14 +1091,13 @@ impl RelaySelector {
) -> (Constraint<u16>, TransportProtocol, TunnelType) {
match default_tunnel_type {
TunnelType::OpenVpn => {
- let location_supports_openvpn =
- self.parsed_relays.lock().relays().iter().any(|relay| {
- relay.active
- && relay.endpoint_data == RelayEndpointData::Openvpn
- && location_constraint.matches_with_opts(relay, true)
- && providers_constraint.matches(relay)
- && ownership_constraint.matches(relay)
- });
+ let location_supports_openvpn = self.parsed_relays.lock().relays().any(|relay| {
+ relay.active
+ && relay.endpoint_data == RelayEndpointData::Openvpn
+ && location_constraint.matches_with_opts(relay, true)
+ && providers_constraint.matches(relay)
+ && ownership_constraint.matches(relay)
+ });
if location_supports_openvpn {
let (preferred_port, preferred_protocol) =
@@ -1043,14 +1106,13 @@ impl RelaySelector {
}
}
TunnelType::Wireguard => {
- let location_supports_wireguard =
- self.parsed_relays.lock().relays().iter().any(|relay| {
- relay.active
- && matches!(relay.endpoint_data, RelayEndpointData::Wireguard(_))
- && location_constraint.matches_with_opts(relay, true)
- && providers_constraint.matches(relay)
- && ownership_constraint.matches(relay)
- });
+ let location_supports_wireguard = self.parsed_relays.lock().relays().any(|relay| {
+ relay.active
+ && matches!(relay.endpoint_data, RelayEndpointData::Wireguard(_))
+ && location_constraint.matches_with_opts(relay, true)
+ && providers_constraint.matches(relay)
+ && ownership_constraint.matches(relay)
+ });
// If location does not support WireGuard, defer to preferred OpenVPN tunnel
// constraints
@@ -1197,36 +1259,10 @@ impl RelaySelector {
})
}
- /// Try to read the relays from disk, preferring the newer ones.
- fn read_relays_from_disk(
- cache_path: &Path,
- resource_path: &Path,
- ) -> Result<ParsedRelays, Error> {
- // prefer the resource path's relay list if the cached one doesn't exist or was modified
- // before the resource one was created.
- let cached_relays = ParsedRelays::from_file(cache_path);
- let bundled_relays = match ParsedRelays::from_file(resource_path) {
- Ok(bundled_relays) => bundled_relays,
- Err(e) => {
- log::error!("Failed to load bundled relays: {}", e);
- return cached_relays;
- }
- };
-
- if cached_relays
- .as_ref()
- .map(|cached| cached.last_updated > bundled_relays.last_updated)
- .unwrap_or(false)
- {
- cached_relays
- } else {
- Ok(bundled_relays)
- }
- }
-
fn wireguard_exit_matcher(&self) -> WireguardMatcher {
- let mut tunnel =
- WireguardMatcher::from_endpoint(self.parsed_relays.lock().locations.wireguard.clone());
+ let mut tunnel = WireguardMatcher::from_endpoint(
+ self.parsed_relays.lock().parsed_list.wireguard.clone(),
+ );
tunnel.ip_version = WIREGUARD_EXIT_IP_VERSION;
tunnel.port = WIREGUARD_EXIT_PORT;
tunnel
@@ -1458,6 +1494,7 @@ mod test {
parsed_relays: Arc::new(Mutex::new(ParsedRelays::from_relay_list(
relay_list,
SystemTime::now(),
+ &[],
))),
config: Arc::new(Mutex::new(SelectorConfig {
relay_settings: RelaySettings::Normal(RelayConstraints {
@@ -1474,6 +1511,7 @@ mod test {
bridge_state: BridgeState::Auto,
default_tunnel_type: default_tunnel_type(),
custom_lists: CustomListsSettings::default(),
+ relay_overrides: vec![],
})),
}
}
diff --git a/mullvad-relay-selector/src/matcher.rs b/mullvad-relay-selector/src/matcher.rs
index 05b3799dd8..e02d8abc45 100644
--- a/mullvad-relay-selector/src/matcher.rs
+++ b/mullvad-relay-selector/src/matcher.rs
@@ -67,13 +67,12 @@ impl RelayMatcher<WireguardMatcher> {
impl<T: EndpointMatcher> RelayMatcher<T> {
/// Filter a list of relays and their endpoints based on constraints.
/// Only relays with (and including) matching endpoints are returned.
- pub fn filter_matching_relay_list(&self, relays: &[Relay]) -> Vec<Relay> {
- let matches = relays
- .iter()
- .filter(|relay| self.pre_filter_matching_relay(relay));
-
+ pub fn filter_matching_relay_list<'a, R: Iterator<Item = &'a Relay> + Clone>(
+ &self,
+ relays: R,
+ ) -> Vec<Relay> {
+ let matches = relays.filter(|relay| self.pre_filter_matching_relay(relay));
let ignore_include_in_country = !matches.clone().any(|relay| relay.include_in_country);
-
matches
.filter(|relay| self.post_filter_matching_relay(relay, ignore_include_in_country))
.cloned()
diff --git a/mullvad-relay-selector/src/updater.rs b/mullvad-relay-selector/src/updater.rs
index 6600efb02f..e9d65d1dd8 100644
--- a/mullvad-relay-selector/src/updater.rs
+++ b/mullvad-relay-selector/src/updater.rs
@@ -91,7 +91,7 @@ impl RelayListUpdater {
futures::select! {
_check_update = next_check => {
if download_future.is_terminated() && self.should_update() {
- let tag = self.parsed_relays.lock().tag().map(|tag| tag.to_string());
+ let tag = self.parsed_relays.lock().parsed_list.etag.clone();
download_future = Box::pin(Self::download_relay_list(self.api_availability.clone(), self.api_client.clone(), tag).fuse());
self.last_check = SystemTime::now();
}
@@ -104,7 +104,7 @@ impl RelayListUpdater {
cmd = cmd_rx.next() => {
match cmd {
Some(()) => {
- let tag = self.parsed_relays.lock().tag().map(|tag| tag.to_string());
+ let tag = self.parsed_relays.lock().parsed_list.etag.clone();
download_future = Box::pin(Self::download_relay_list(self.api_availability.clone(), self.api_client.clone(), tag).fuse());
self.last_check = SystemTime::now();
},
@@ -178,15 +178,9 @@ impl RelayListUpdater {
);
}
- let new_parsed_relays = ParsedRelays::from_relay_list(new_relay_list, SystemTime::now());
- log::info!(
- "Downloaded relay inventory has {} relays",
- new_parsed_relays.relays().len()
- );
-
let mut parsed_relays = self.parsed_relays.lock();
- *parsed_relays = new_parsed_relays;
- (self.on_update)(parsed_relays.locations());
+ parsed_relays.update(new_relay_list);
+ (self.on_update)(&parsed_relays.original_list);
Ok(())
}
diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs
index fffc6e03de..6645fb493d 100644
--- a/mullvad-types/src/relay_constraints.rs
+++ b/mullvad-types/src/relay_constraints.rs
@@ -10,7 +10,12 @@ use crate::{
#[cfg(target_os = "android")]
use jnix::{jni::objects::JObject, FromJava, IntoJava, JnixEnv};
use serde::{Deserialize, Serialize};
-use std::{collections::HashSet, fmt, str::FromStr};
+use std::{
+ collections::HashSet,
+ fmt,
+ net::{Ipv4Addr, Ipv6Addr},
+ str::FromStr,
+};
use talpid_types::net::{openvpn::ProxySettings, IpVersion, TransportProtocol, TunnelType};
pub trait Match<T> {
@@ -991,3 +996,46 @@ pub struct InternalBridgeConstraints {
pub ownership: Constraint<Ownership>,
pub transport_protocol: Constraint<TransportProtocol>,
}
+
+/// Options to override for a particular relay to use instead of the ones specified in the relay
+/// list
+#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
+pub struct RelayOverride {
+ /// Hostname for which to override the given options
+ pub hostname: Hostname,
+ /// IPv4 address to use instead of the default
+ pub ipv4_addr_in: Option<Ipv4Addr>,
+ /// IPv6 address to use instead of the default
+ pub ipv6_addr_in: Option<Ipv6Addr>,
+}
+
+impl RelayOverride {
+ pub fn empty(hostname: Hostname) -> RelayOverride {
+ RelayOverride {
+ hostname,
+ ipv4_addr_in: None,
+ ipv6_addr_in: None,
+ }
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self == &Self::empty(self.hostname.clone())
+ }
+
+ pub fn apply_to_relay(&self, relay: &mut Relay) {
+ if let Some(ipv4_addr_in) = self.ipv4_addr_in {
+ log::debug!(
+ "Overriding ipv4_addr_in for {}: {ipv4_addr_in}",
+ relay.hostname
+ );
+ relay.ipv4_addr_in = ipv4_addr_in;
+ }
+ if let Some(ipv6_addr_in) = self.ipv6_addr_in {
+ log::debug!(
+ "Overriding ipv6_addr_in for {}: {ipv6_addr_in}",
+ relay.hostname
+ );
+ relay.ipv6_addr_in = Some(ipv6_addr_in);
+ }
+ }
+}
diff --git a/mullvad-types/src/relay_list.rs b/mullvad-types/src/relay_list.rs
index 4b1c8df346..94f4a9d59d 100644
--- a/mullvad-types/src/relay_list.rs
+++ b/mullvad-types/src/relay_list.rs
@@ -35,6 +35,14 @@ impl RelayList {
.iter()
.find(|country| country.code == country_code)
}
+
+ /// Return a flat iterator with all relays
+ pub fn relays(&self) -> impl Iterator<Item = &Relay> + Clone + '_ {
+ self.countries
+ .iter()
+ .flat_map(|country| country.cities.iter())
+ .flat_map(|city| city.relays.iter())
+ }
}
/// A list of [`RelayListCity`]s within a country. Used by [`RelayList`].
diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs
index 5978c6a736..612e353487 100644
--- a/mullvad-types/src/settings/mod.rs
+++ b/mullvad-types/src/settings/mod.rs
@@ -3,7 +3,7 @@ use crate::{
custom_list::CustomListsSettings,
relay_constraints::{
BridgeConstraints, BridgeSettings, BridgeState, Constraint, GeographicLocationConstraint,
- LocationConstraint, ObfuscationSettings, RelayConstraints, RelaySettings,
+ LocationConstraint, ObfuscationSettings, RelayConstraints, RelayOverride, RelaySettings,
RelaySettingsFormatter, SelectedObfuscation, WireguardConstraints,
},
wireguard,
@@ -91,6 +91,9 @@ pub struct Settings {
/// Options that should be applied to tunnels of a specific type regardless of where the relays
/// might be located.
pub tunnel_options: TunnelOptions,
+ /// Overrides for relays
+ #[cfg_attr(target_os = "android", jnix(skip))]
+ pub relay_overrides: Vec<RelayOverride>,
/// Whether to notify users of beta updates.
pub show_beta_releases: bool,
/// Split tunneling settings
@@ -131,16 +134,17 @@ impl Default for Settings {
..Default::default()
},
bridge_state: BridgeState::Auto,
+ custom_lists: CustomListsSettings::default(),
+ api_access_methods: access_method::Settings::default(),
allow_lan: false,
block_when_disconnected: false,
auto_connect: false,
tunnel_options: TunnelOptions::default(),
+ relay_overrides: vec![],
show_beta_releases: false,
#[cfg(windows)]
split_tunnel: SplitTunnelSettings::default(),
settings_version: CURRENT_SETTINGS_VERSION,
- custom_lists: CustomListsSettings::default(),
- api_access_methods: access_method::Settings::default(),
}
}
}
@@ -171,6 +175,24 @@ impl Settings {
self.relay_settings = new_settings;
}
}
+
+ pub fn set_relay_override(&mut self, relay_override: RelayOverride) {
+ let existing_override = self
+ .relay_overrides
+ .iter_mut()
+ .enumerate()
+ .find(|(_, elem)| elem.hostname == relay_override.hostname);
+ match existing_override {
+ None => self.relay_overrides.push(relay_override),
+ Some((index, elem)) => {
+ if relay_override.is_empty() {
+ self.relay_overrides.swap_remove(index);
+ } else {
+ *elem = relay_override;
+ }
+ }
+ }
+ }
}
/// TunnelOptions holds configuration data that applies to all kinds of tunnels.