summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarkus Pettersson <markus.pettersson@mullvad.net>2024-08-01 15:39:21 +0200
committerMarkus Pettersson <markus.pettersson@mullvad.net>2024-08-01 15:39:21 +0200
commitd567fe11456fff8b1521c974d2ac6defda72ab7f (patch)
tree7b5c505570d2f64221ddc5a89f53593f4569492f
parent2b59f823d60de3063fa6b1c3da206d198f6218d0 (diff)
parent7992055557e32fdbed2f8267ae6fb1b57ed9c1d8 (diff)
downloadmullvadvpn-d567fe11456fff8b1521c974d2ac6defda72ab7f.tar.xz
mullvadvpn-d567fe11456fff8b1521c974d2ac6defda72ab7f.zip
Merge branch 'implement-feature-list-in-rpc-des-1082'
-rw-r--r--mullvad-cli/src/cmds/status.rs12
-rw-r--r--mullvad-cli/src/format.rs54
-rw-r--r--mullvad-daemon/src/custom_list.rs11
-rw-r--r--mullvad-daemon/src/lib.rs163
-rw-r--r--mullvad-daemon/src/management_interface.rs17
-rw-r--r--mullvad-management-interface/proto/management_interface.proto31
-rw-r--r--mullvad-management-interface/src/client.rs10
-rw-r--r--mullvad-management-interface/src/types/conversions/features.rs64
-rw-r--r--mullvad-management-interface/src/types/conversions/mod.rs1
-rw-r--r--mullvad-management-interface/src/types/conversions/states.rs50
-rw-r--r--mullvad-types/src/features.rs62
-rw-r--r--mullvad-types/src/lib.rs1
-rw-r--r--mullvad-types/src/settings/dns.rs21
-rw-r--r--mullvad-types/src/states.rs60
14 files changed, 468 insertions, 89 deletions
diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs
index 939beeca4a..96631d02d0 100644
--- a/mullvad-cli/src/cmds/status.rs
+++ b/mullvad-cli/src/cmds/status.rs
@@ -54,9 +54,15 @@ impl Status {
// Do print an updated state if the lockdown setting was changed
) if was_locked_down == locked_down => continue,
(
- Some(TunnelState::Connected { .. }),
- TunnelState::Connected { .. },
- ) => continue,
+ Some(TunnelState::Connected {
+ feature_indicators: old_feature_indicators,
+ ..
+ }),
+ TunnelState::Connected {
+ feature_indicators, ..
+ },
+ // Do print an updated state if the feature indicators changed
+ ) if old_feature_indicators == feature_indicators => continue,
_ => {}
}
format::print_state(&new_state, args.verbose);
diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs
index 8660683919..63395ea87a 100644
--- a/mullvad-cli/src/format.rs
+++ b/mullvad-cli/src/format.rs
@@ -1,4 +1,8 @@
-use mullvad_types::{auth_failed::AuthFailed, location::GeoIpLocation, states::TunnelState};
+use itertools::Itertools;
+use mullvad_types::{
+ auth_failed::AuthFailed, features::FeatureIndicators, location::GeoIpLocation,
+ states::TunnelState,
+};
use talpid_types::{
net::{Endpoint, TunnelEndpoint},
tunnel::ErrorState,
@@ -19,18 +23,30 @@ pub fn print_state(state: &TunnelState, verbose: bool) {
match state {
Error(error) => print_error_state(error),
- Connected { endpoint, location } => {
+ Connected {
+ endpoint,
+ location,
+ feature_indicators,
+ } => {
println!(
"Connected to {}",
format_relay_connection(endpoint, location.as_ref(), verbose)
);
if verbose {
+ println!(
+ "Active features: {}",
+ format_feature_indicators(feature_indicators)
+ );
if let Some(tunnel_interface) = &endpoint.tunnel_interface {
println!("Tunnel interface: {tunnel_interface}")
}
}
}
- Connecting { endpoint, location } => {
+ Connecting {
+ endpoint,
+ location,
+ feature_indicators: _,
+ } => {
let ellipsis = if !verbose { "..." } else { "" };
println!(
"Connecting to {}{ellipsis}",
@@ -166,44 +182,30 @@ fn format_relay_connection(
} else {
String::new()
};
- let quantum_resistant = if !verbose {
- ""
- } else if endpoint.quantum_resistant {
- "\nQuantum resistant tunnel: yes"
- } else {
- "\nQuantum resistant tunnel: no"
- };
-
- #[cfg(daita)]
- let daita = if !verbose {
- ""
- } else if endpoint.daita {
- "\nDAITA: yes"
- } else {
- "\nDAITA: no"
- };
- #[cfg(not(daita))]
- let daita = "";
let mut bridge_type = String::new();
- let mut obfuscator_type = String::new();
if verbose {
if let Some(bridge) = &endpoint.proxy {
bridge_type = format!("\nBridge type: {}", bridge.proxy_type);
}
- if let Some(obfuscator) = &endpoint.obfuscation {
- obfuscator_type = format!("\nObfuscator: {}", obfuscator.obfuscation_type);
- }
}
format!(
- "{exit_endpoint}{first_hop}{bridge}{obfuscator}{tunnel_type}{quantum_resistant}{daita}{bridge_type}{obfuscator_type}",
+ "{exit_endpoint}{first_hop}{bridge}{obfuscator}{tunnel_type}{bridge_type}",
first_hop = first_hop.unwrap_or_default(),
bridge = bridge.unwrap_or_default(),
obfuscator = obfuscator.unwrap_or_default(),
)
}
+fn format_feature_indicators(feature_indicators: &FeatureIndicators) -> String {
+ feature_indicators
+ .active_features()
+ // Sort the features alphabetically (Just to have some order, arbitrarily chosen)
+ .sorted_by_key(|feature| feature.to_string())
+ .join(", ")
+}
+
fn format_endpoint(hostname: Option<&str>, endpoint: &Endpoint, verbose: bool) -> String {
match (hostname, verbose) {
(Some(hostname), true) => format!("{hostname} ({endpoint})"),
diff --git a/mullvad-daemon/src/custom_list.rs b/mullvad-daemon/src/custom_list.rs
index f3e92f3c97..459ef9b932 100644
--- a/mullvad-daemon/src/custom_list.rs
+++ b/mullvad-daemon/src/custom_list.rs
@@ -108,7 +108,6 @@ where
///
/// If `custom_list_id` is `Some`, only changes to that custom list will trigger a reconnect.
fn change_should_cause_reconnect(&self, custom_list_id: Option<Id>) -> bool {
- use mullvad_types::states::TunnelState;
let mut need_to_reconnect = false;
let RelaySettings::Normal(relay_settings) = &self.settings.relay_settings else {
@@ -121,15 +120,7 @@ where
need_to_reconnect |= custom_list_id.map(|id| &id == list_id).unwrap_or(true);
}
- if let TunnelState::Connecting {
- endpoint,
- location: _,
- }
- | TunnelState::Connected {
- endpoint,
- location: _,
- } = &self.tunnel_state
- {
+ if let Some(endpoint) = self.tunnel_state.endpoint() {
match endpoint.tunnel_type {
TunnelType::Wireguard => {
if relay_settings.wireguard_constraints.multihop() {
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 2e685d778f..ba83da27f5 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -52,12 +52,13 @@ use mullvad_types::{
auth_failed::AuthFailed,
custom_list::CustomList,
device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent},
+ features::{FeatureIndicator, FeatureIndicators},
location::{GeoIpLocation, LocationEventData},
relay_constraints::{
BridgeSettings, BridgeState, BridgeType, ObfuscationSettings, RelayOverride, RelaySettings,
},
relay_list::RelayList,
- settings::{DnsOptions, Settings},
+ settings::{DnsOptions, DnsState, Settings},
states::{TargetState, TunnelState},
version::{AppVersion, AppVersionInfo},
wireguard::{PublicKey, QuantumResistantState, RotationInterval},
@@ -85,7 +86,7 @@ use talpid_types::android::AndroidContext;
#[cfg(target_os = "windows")]
use talpid_types::split_tunnel::ExcludedProcess;
use talpid_types::{
- net::{IpVersion, TunnelEndpoint, TunnelType},
+ net::{IpVersion, ObfuscationType, TunnelType},
tunnel::{ErrorStateCause, TunnelStateTransition},
ErrorExt,
};
@@ -369,6 +370,8 @@ pub enum DaemonCommand {
ApplyJsonSettings(ResponseTx<(), settings::patch::Error>, String),
/// Return a JSON blob containing all overridable settings, if there are any
ExportJsonSettings(ResponseTx<String, settings::patch::Error>),
+ /// Request the current feature indicators.
+ GetFeatureIndicators(oneshot::Sender<FeatureIndicators>),
}
/// All events that can happen in the daemon. Sent from various threads and exposed interfaces.
@@ -393,6 +396,8 @@ pub(crate) enum InternalDaemonEvent {
DeviceMigrationEvent(Result<PrivateAccountAndDevice, device::Error>),
/// A geographical location has has been received from am.i.mullvad.net
LocationEvent(LocationEventData),
+ /// A generic event for when any settings change.
+ SettingsChanged,
/// The split tunnel paths or state were updated.
#[cfg(any(windows, target_os = "android", target_os = "macos"))]
ExcludedPathsEvent(ExcludedPathsUpdate, oneshot::Sender<Result<(), Error>>),
@@ -755,6 +760,13 @@ where
let _ = param_gen_tx.unbounded_send(settings.tunnel_options.to_owned());
});
+ // Register a listener for generic settings changes.
+ // This is useful for example for updating feature indicators when the settings change.
+ let settings_changed_event_sender = internal_event_tx.clone();
+ settings.register_change_listener(move |_settings| {
+ let _ = settings_changed_event_sender.send(InternalDaemonEvent::SettingsChanged);
+ });
+
let (offline_state_tx, offline_state_rx) = mpsc::unbounded();
#[cfg(target_os = "windows")]
let (volume_update_tx, volume_update_rx) = mpsc::unbounded();
@@ -947,6 +959,9 @@ where
} => self.handle_access_method_event(event, endpoint_active_tx),
DeviceMigrationEvent(event) => self.handle_device_migration_event(event),
LocationEvent(location_data) => self.handle_location_event(location_data),
+ SettingsChanged => {
+ self.handle_feature_indicator_event();
+ }
#[cfg(any(windows, target_os = "android", target_os = "macos"))]
ExcludedPathsEvent(update, tx) => self.handle_new_excluded_paths(update, tx).await,
}
@@ -969,11 +984,13 @@ where
TunnelStateTransition::Connecting(endpoint) => TunnelState::Connecting {
endpoint,
location: self.parameters_generator.get_last_location().await,
+ feature_indicators: self.get_feature_indicators(),
+ },
+ TunnelStateTransition::Connected(endpoint) => TunnelState::Connected {
+ endpoint,
+ location: self.parameters_generator.get_last_location().await,
+ feature_indicators: self.get_feature_indicators(),
},
- TunnelStateTransition::Connected(endpoint) => {
- let location = self.parameters_generator.get_last_location().await;
- TunnelState::Connected { endpoint, location }
- }
TunnelStateTransition::Disconnecting(after_disconnect) => {
TunnelState::Disconnecting(after_disconnect)
}
@@ -1097,6 +1114,23 @@ where
.notify_new_state(self.tunnel_state.clone());
}
+ /// Update the set of feature indicators.
+ fn handle_feature_indicator_event(&mut self) {
+ // Note: If the current tunnel state carries information about active feature indicators,
+ // we should care to update the known set of feature indicators (i.e. in the connecting /
+ // connected state). Otherwise, we can just skip broadcasting a new tunnel state.
+ if let Some(current_feature_indicators) = self.tunnel_state.get_feature_indicators() {
+ let new_feature_indicators = self.get_feature_indicators();
+ if *current_feature_indicators != new_feature_indicators {
+ // Make sure to update the daemon's actual tunnel state. Otherwise feature indicator changes won't be persisted.
+ self.tunnel_state
+ .set_feature_indicators(new_feature_indicators);
+ self.event_listener
+ .notify_new_state(self.tunnel_state.clone());
+ }
+ }
+ }
+
fn reset_rpc_sockets_on_tunnel_state_transition(
&mut self,
tunnel_state_transition: &TunnelStateTransition,
@@ -1248,6 +1282,7 @@ where
}
ApplyJsonSettings(tx, blob) => self.on_apply_json_settings(tx, blob).await,
ExportJsonSettings(tx) => self.on_export_json_settings(tx),
+ GetFeatureIndicators(tx) => self.on_get_feature_indicators(tx),
}
}
@@ -2718,6 +2753,14 @@ where
Self::oneshot_send(tx, result, "export_json_settings response");
}
+ fn on_get_feature_indicators(&self, tx: oneshot::Sender<FeatureIndicators>) {
+ Self::oneshot_send(
+ tx,
+ self.get_feature_indicators(),
+ "get_feature_indicators response",
+ );
+ }
+
/// Set the target state of the client. If it changed trigger the operations needed to
/// progress towards that state.
/// Returns a bool representing whether or not a state change was initiated.
@@ -2752,30 +2795,15 @@ where
}
}
- fn get_connected_tunnel_type(&self) -> Option<TunnelType> {
- if let TunnelState::Connected {
- endpoint: TunnelEndpoint { tunnel_type, .. },
- ..
- } = self.tunnel_state
- {
- Some(tunnel_type)
- } else {
- None
+ const fn get_connected_tunnel_type(&self) -> Option<TunnelType> {
+ match self.tunnel_state.get_tunnel_type() {
+ Some(tunnel_type) if self.tunnel_state.is_connected() => Some(tunnel_type),
+ Some(_) | None => None,
}
}
- fn get_target_tunnel_type(&self) -> Option<TunnelType> {
- match self.tunnel_state {
- TunnelState::Connected {
- endpoint: TunnelEndpoint { tunnel_type, .. },
- ..
- }
- | TunnelState::Connecting {
- endpoint: TunnelEndpoint { tunnel_type, .. },
- ..
- } => Some(tunnel_type),
- _ => None,
- }
+ const fn get_target_tunnel_type(&self) -> Option<TunnelType> {
+ self.tunnel_state.get_tunnel_type()
}
fn send_tunnel_command(&self, command: TunnelCommand) {
@@ -2790,6 +2818,87 @@ where
tx: self.tx.clone(),
}
}
+
+ /// Source all active [`FeatureIndicators`].
+ ///
+ /// Note that [`FeatureIndicators`] only affect an active connection, which means that when the
+ /// daemon is disconnected while calling this function the caller will see an empty set of
+ /// [`FeatureIndicators`].
+ fn get_feature_indicators(&self) -> FeatureIndicators {
+ // Check if there is an active tunnel.
+ let Some(endpoint) = self.tunnel_state.endpoint() else {
+ // If there is not, no features are actually active and thus should not be displayed.
+ return Default::default();
+ };
+ let settings = self.settings.to_settings();
+
+ #[cfg(any(windows, target_os = "android", target_os = "macos"))]
+ let split_tunneling = self.settings.split_tunnel.enable_exclusions;
+ #[cfg(not(any(windows, target_os = "android", target_os = "macos")))]
+ let split_tunneling = false;
+
+ let lockdown_mode = settings.block_when_disconnected;
+ let lan_sharing = settings.allow_lan;
+ let dns_content_blockers = settings
+ .tunnel_options
+ .dns_options
+ .default_options
+ .any_blockers_enabled();
+ let custom_dns = settings.tunnel_options.dns_options.state == DnsState::Custom;
+ let server_ip_override = !settings.relay_overrides.is_empty();
+
+ let generic_features = [
+ (split_tunneling, FeatureIndicator::SplitTunneling),
+ (lockdown_mode, FeatureIndicator::LockdownMode),
+ (lan_sharing, FeatureIndicator::LanSharing),
+ (dns_content_blockers, FeatureIndicator::DnsContentBlockers),
+ (custom_dns, FeatureIndicator::CustomDns),
+ (server_ip_override, FeatureIndicator::ServerIpOverride),
+ ];
+
+ // Pick protocol-specific features and whether they are currently enabled.
+ let protocol_features = match endpoint.tunnel_type {
+ TunnelType::OpenVpn => {
+ let bridge_mode = endpoint.proxy.is_some();
+ let mss_fix = settings.tunnel_options.openvpn.mssfix.is_some();
+
+ vec![
+ (bridge_mode, FeatureIndicator::BridgeMode),
+ (mss_fix, FeatureIndicator::CustomMssFix),
+ ]
+ }
+ TunnelType::Wireguard => {
+ let quantum_resistant = endpoint.quantum_resistant;
+ let multihop = endpoint.entry_endpoint.is_some();
+ let udp_tcp = endpoint
+ .obfuscation
+ .as_ref()
+ .filter(|obfuscation| obfuscation.obfuscation_type == ObfuscationType::Udp2Tcp)
+ .is_some();
+
+ let mtu = settings.tunnel_options.wireguard.mtu.is_some();
+
+ #[cfg(daita)]
+ let daita = endpoint.daita;
+
+ vec![
+ (quantum_resistant, FeatureIndicator::QuantumResistance),
+ (multihop, FeatureIndicator::Multihop),
+ (udp_tcp, FeatureIndicator::Udp2Tcp),
+ (mtu, FeatureIndicator::CustomMtu),
+ #[cfg(daita)]
+ (daita, FeatureIndicator::Daita),
+ ]
+ }
+ };
+
+ // use the booleans to filter into a list of only the active features
+ generic_features
+ .into_iter()
+ .chain(protocol_features)
+ .filter_map(|(active, feature)| active.then_some(feature))
+ .collect()
+ }
}
#[derive(Clone)]
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index bc6ebe8287..a7e2b23f4f 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -1022,6 +1022,23 @@ impl ManagementService for ManagementServiceImpl {
log::error!("Called `verify_play_purchase` on non-Android platform");
Ok(Response::new(()))
}
+
+ async fn get_feature_indicators(
+ &self,
+ _: Request<()>,
+ ) -> ServiceResult<types::FeatureIndicators> {
+ log::debug!("get_feature_indicators");
+
+ let (tx, rx) = oneshot::channel();
+ self.send_command_to_daemon(DaemonCommand::GetFeatureIndicators(tx))?;
+
+ let feature_indicators = self
+ .wait_for_result(rx)
+ .await
+ .map(types::FeatureIndicators::from)?;
+
+ Ok(Response::new(feature_indicators))
+ }
}
impl ManagementServiceImpl {
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index 798e3a3bee..7e78cffde8 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -119,6 +119,9 @@ service ManagementService {
rpc ApplyJsonSettings(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
// Return a JSON blob containing all overridable settings, if there are any
rpc ExportJsonSettings(google.protobuf.Empty) returns (google.protobuf.StringValue) {}
+
+ // Get current feature indicators
+ rpc GetFeatureIndicators(google.protobuf.Empty) returns (FeatureIndicators) {}
}
message UUID { string value = 1; }
@@ -200,8 +203,14 @@ message TunnelState {
GeoIpLocation disconnected_location = 1;
bool locked_down = 2;
}
- message Connecting { TunnelStateRelayInfo relay_info = 1; }
- message Connected { TunnelStateRelayInfo relay_info = 1; }
+ message Connecting {
+ TunnelStateRelayInfo relay_info = 1;
+ FeatureIndicators feature_indicators = 2;
+ }
+ message Connected {
+ TunnelStateRelayInfo relay_info = 1;
+ FeatureIndicators feature_indicators = 2;
+ }
message Disconnecting { AfterDisconnect after_disconnect = 1; }
message Error { ErrorState error_state = 1; }
@@ -236,6 +245,24 @@ message TunnelEndpoint {
bool daita = 9;
}
+message FeatureIndicators { repeated FeatureIndicator active_features = 1; }
+
+enum FeatureIndicator {
+ QUANTUM_RESISTANCE = 0;
+ MULTIHOP = 1;
+ BRIDGE_MODE = 2;
+ SPLIT_TUNNELING = 3;
+ LOCKDOWN_MODE = 4;
+ UDP_2_TCP = 5;
+ LAN_SHARING = 6;
+ DNS_CONTENT_BLOCKERS = 7;
+ CUSTOM_DNS = 8;
+ SERVER_IP_OVERRIDE = 9;
+ CUSTOM_MTU = 10;
+ CUSTOM_MSS_FIX = 11;
+ DAITA = 12;
+}
+
enum ObfuscationType {
UDP2TCP = 0;
}
diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs
index 4db40cac52..b0876093fa 100644
--- a/mullvad-management-interface/src/client.rs
+++ b/mullvad-management-interface/src/client.rs
@@ -20,6 +20,7 @@ use mullvad_types::{
account::{AccountData, AccountToken, VoucherSubmission},
custom_list::{CustomList, Id},
device::{Device, DeviceId, DeviceState},
+ features::FeatureIndicators,
relay_constraints::{
BridgeSettings, BridgeState, ObfuscationSettings, RelayOverride, RelaySettings,
},
@@ -741,6 +742,15 @@ impl MullvadProxyClient {
let blob = self.0.export_json_settings(()).await.map_err(Error::Rpc)?;
Ok(blob.into_inner())
}
+
+ pub async fn get_feature_indicators(&mut self) -> Result<FeatureIndicators> {
+ self.0
+ .get_feature_indicators(())
+ .await
+ .map_err(Error::Rpc)
+ .map(|response| response.into_inner())
+ .map(FeatureIndicators::from)
+ }
}
#[cfg(not(target_os = "android"))]
diff --git a/mullvad-management-interface/src/types/conversions/features.rs b/mullvad-management-interface/src/types/conversions/features.rs
new file mode 100644
index 0000000000..ae04fc9099
--- /dev/null
+++ b/mullvad-management-interface/src/types/conversions/features.rs
@@ -0,0 +1,64 @@
+use crate::types::proto;
+
+impl From<mullvad_types::features::FeatureIndicator> for proto::FeatureIndicator {
+ fn from(feature: mullvad_types::features::FeatureIndicator) -> Self {
+ use proto::FeatureIndicator::*;
+ match feature {
+ mullvad_types::features::FeatureIndicator::QuantumResistance => QuantumResistance,
+ mullvad_types::features::FeatureIndicator::Multihop => Multihop,
+ mullvad_types::features::FeatureIndicator::BridgeMode => BridgeMode,
+ mullvad_types::features::FeatureIndicator::SplitTunneling => SplitTunneling,
+ mullvad_types::features::FeatureIndicator::LockdownMode => LockdownMode,
+ mullvad_types::features::FeatureIndicator::Udp2Tcp => Udp2Tcp,
+ mullvad_types::features::FeatureIndicator::LanSharing => LanSharing,
+ mullvad_types::features::FeatureIndicator::DnsContentBlockers => DnsContentBlockers,
+ mullvad_types::features::FeatureIndicator::CustomDns => CustomDns,
+ mullvad_types::features::FeatureIndicator::ServerIpOverride => ServerIpOverride,
+ mullvad_types::features::FeatureIndicator::CustomMtu => CustomMtu,
+ mullvad_types::features::FeatureIndicator::CustomMssFix => CustomMssFix,
+ mullvad_types::features::FeatureIndicator::Daita => Daita,
+ }
+ }
+}
+
+impl From<proto::FeatureIndicator> for mullvad_types::features::FeatureIndicator {
+ fn from(feature: proto::FeatureIndicator) -> Self {
+ match feature {
+ proto::FeatureIndicator::QuantumResistance => Self::QuantumResistance,
+ proto::FeatureIndicator::Multihop => Self::Multihop,
+ proto::FeatureIndicator::BridgeMode => Self::BridgeMode,
+ proto::FeatureIndicator::SplitTunneling => Self::SplitTunneling,
+ proto::FeatureIndicator::LockdownMode => Self::LockdownMode,
+ proto::FeatureIndicator::Udp2Tcp => Self::Udp2Tcp,
+ proto::FeatureIndicator::LanSharing => Self::LanSharing,
+ proto::FeatureIndicator::DnsContentBlockers => Self::DnsContentBlockers,
+ proto::FeatureIndicator::CustomDns => Self::CustomDns,
+ proto::FeatureIndicator::ServerIpOverride => Self::ServerIpOverride,
+ proto::FeatureIndicator::CustomMtu => Self::CustomMtu,
+ proto::FeatureIndicator::CustomMssFix => Self::CustomMssFix,
+ proto::FeatureIndicator::Daita => Self::Daita,
+ }
+ }
+}
+
+impl From<proto::FeatureIndicators> for mullvad_types::features::FeatureIndicators {
+ fn from(features: proto::FeatureIndicators) -> Self {
+ features
+ .active_features()
+ .map(mullvad_types::features::FeatureIndicator::from)
+ .collect()
+ }
+}
+
+impl From<mullvad_types::features::FeatureIndicators> for proto::FeatureIndicators {
+ fn from(features: mullvad_types::features::FeatureIndicators) -> Self {
+ let mut proto_features = Self::default();
+
+ features
+ .active_features()
+ .map(proto::FeatureIndicator::from)
+ .for_each(|feature| proto_features.push_active_features(feature));
+
+ proto_features
+ }
+}
diff --git a/mullvad-management-interface/src/types/conversions/mod.rs b/mullvad-management-interface/src/types/conversions/mod.rs
index 02281846c3..0654cbb641 100644
--- a/mullvad-management-interface/src/types/conversions/mod.rs
+++ b/mullvad-management-interface/src/types/conversions/mod.rs
@@ -5,6 +5,7 @@ mod account;
mod custom_list;
mod custom_tunnel;
mod device;
+mod features;
mod location;
mod net;
pub mod relay_constraints;
diff --git a/mullvad-management-interface/src/types/conversions/states.rs b/mullvad-management-interface/src/types/conversions/states.rs
index 80dbb8bf2f..777c661c6d 100644
--- a/mullvad-management-interface/src/types/conversions/states.rs
+++ b/mullvad-management-interface/src/types/conversions/states.rs
@@ -39,22 +39,28 @@ impl From<mullvad_types::states::TunnelState> for proto::TunnelState {
disconnected_location: disconnected_location.map(proto::GeoIpLocation::from),
locked_down,
}),
- MullvadTunnelState::Connecting { endpoint, location } => {
- proto::tunnel_state::State::Connecting(proto::tunnel_state::Connecting {
- relay_info: Some(proto::TunnelStateRelayInfo {
- tunnel_endpoint: Some(proto::TunnelEndpoint::from(endpoint)),
- location: location.map(proto::GeoIpLocation::from),
- }),
- })
- }
- MullvadTunnelState::Connected { endpoint, location } => {
- proto::tunnel_state::State::Connected(proto::tunnel_state::Connected {
- relay_info: Some(proto::TunnelStateRelayInfo {
- tunnel_endpoint: Some(proto::TunnelEndpoint::from(endpoint)),
- location: location.map(proto::GeoIpLocation::from),
- }),
- })
- }
+ MullvadTunnelState::Connecting {
+ endpoint,
+ location,
+ feature_indicators,
+ } => proto::tunnel_state::State::Connecting(proto::tunnel_state::Connecting {
+ relay_info: Some(proto::TunnelStateRelayInfo {
+ tunnel_endpoint: Some(proto::TunnelEndpoint::from(endpoint)),
+ location: location.map(proto::GeoIpLocation::from),
+ }),
+ feature_indicators: Some(proto::FeatureIndicators::from(feature_indicators)),
+ }),
+ MullvadTunnelState::Connected {
+ endpoint,
+ location,
+ feature_indicators,
+ } => proto::tunnel_state::State::Connected(proto::tunnel_state::Connected {
+ relay_info: Some(proto::TunnelStateRelayInfo {
+ tunnel_endpoint: Some(proto::TunnelEndpoint::from(endpoint)),
+ location: location.map(proto::GeoIpLocation::from),
+ }),
+ feature_indicators: Some(proto::FeatureIndicators::from(feature_indicators)),
+ }),
MullvadTunnelState::Disconnecting(after_disconnect) => {
proto::tunnel_state::State::Disconnecting(proto::tunnel_state::Disconnecting {
after_disconnect: match after_disconnect {
@@ -233,11 +239,17 @@ impl TryFrom<proto::TunnelState> for mullvad_types::states::TunnelState {
tunnel_endpoint: Some(tunnel_endpoint),
location,
}),
+ feature_indicators,
})) => MullvadState::Connecting {
endpoint: talpid_net::TunnelEndpoint::try_from(tunnel_endpoint)?,
location: location
.map(mullvad_types::location::GeoIpLocation::try_from)
.transpose()?,
+ feature_indicators: feature_indicators
+ .map(mullvad_types::features::FeatureIndicators::from)
+ .ok_or(FromProtobufTypeError::InvalidArgument(
+ "Missing feature indicators",
+ ))?,
},
Some(proto::tunnel_state::State::Connected(proto::tunnel_state::Connected {
relay_info:
@@ -245,11 +257,17 @@ impl TryFrom<proto::TunnelState> for mullvad_types::states::TunnelState {
tunnel_endpoint: Some(tunnel_endpoint),
location,
}),
+ feature_indicators,
})) => MullvadState::Connected {
endpoint: talpid_net::TunnelEndpoint::try_from(tunnel_endpoint)?,
location: location
.map(mullvad_types::location::GeoIpLocation::try_from)
.transpose()?,
+ feature_indicators: feature_indicators
+ .map(mullvad_types::features::FeatureIndicators::from)
+ .ok_or(FromProtobufTypeError::InvalidArgument(
+ "Missing feature indicators",
+ ))?,
},
Some(proto::tunnel_state::State::Disconnecting(
proto::tunnel_state::Disconnecting { after_disconnect },
diff --git a/mullvad-types/src/features.rs b/mullvad-types/src/features.rs
new file mode 100644
index 0000000000..9a6b7c7e64
--- /dev/null
+++ b/mullvad-types/src/features.rs
@@ -0,0 +1,62 @@
+use std::collections::HashSet;
+
+use serde::{Deserialize, Serialize};
+
+/// Feature indicators are active settings that should be shown to the user to make them aware of
+/// what is affecting their connection at any given time.
+///
+/// Note that the feature indicators are not ordered.
+#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub struct FeatureIndicators(HashSet<FeatureIndicator>);
+
+impl FeatureIndicators {
+ pub fn active_features(&self) -> impl Iterator<Item = FeatureIndicator> {
+ self.0.clone().into_iter()
+ }
+}
+
+impl FromIterator<FeatureIndicator> for FeatureIndicators {
+ fn from_iter<T: IntoIterator<Item = FeatureIndicator>>(iter: T) -> Self {
+ Self(iter.into_iter().collect())
+ }
+}
+
+/// All possible feature indicators. These represent a subset of all VPN settings in a
+/// non-technical fashion.
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub enum FeatureIndicator {
+ QuantumResistance,
+ Multihop,
+ BridgeMode,
+ SplitTunneling,
+ LockdownMode,
+ Udp2Tcp,
+ LanSharing,
+ DnsContentBlockers,
+ CustomDns,
+ ServerIpOverride,
+ CustomMtu,
+ CustomMssFix,
+ Daita,
+}
+
+impl std::fmt::Display for FeatureIndicator {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let feature = match self {
+ FeatureIndicator::QuantumResistance => "Quantum Resistance",
+ FeatureIndicator::Multihop => "Multihop",
+ FeatureIndicator::BridgeMode => "Bridge Mode",
+ FeatureIndicator::SplitTunneling => "Split Tunneling",
+ FeatureIndicator::LockdownMode => "Lockdown Mode",
+ FeatureIndicator::Udp2Tcp => "Udp2Tcp",
+ FeatureIndicator::LanSharing => "LAN Sharing",
+ FeatureIndicator::DnsContentBlockers => "Dns Content Blocker",
+ FeatureIndicator::CustomDns => "Custom Dns",
+ FeatureIndicator::ServerIpOverride => "Server Ip Override",
+ FeatureIndicator::CustomMtu => "Custom MTU",
+ FeatureIndicator::CustomMssFix => "Custom MSS",
+ FeatureIndicator::Daita => "DAITA",
+ };
+ write!(f, "{feature}")
+ }
+}
diff --git a/mullvad-types/src/lib.rs b/mullvad-types/src/lib.rs
index d1a50fa0ea..e9fde63653 100644
--- a/mullvad-types/src/lib.rs
+++ b/mullvad-types/src/lib.rs
@@ -5,6 +5,7 @@ pub mod constraints;
pub mod custom_list;
pub mod device;
pub mod endpoint;
+pub mod features;
pub mod location;
pub mod relay_constraints;
pub mod relay_list;
diff --git a/mullvad-types/src/settings/dns.rs b/mullvad-types/src/settings/dns.rs
index 82d6fe35af..3136efd79f 100644
--- a/mullvad-types/src/settings/dns.rs
+++ b/mullvad-types/src/settings/dns.rs
@@ -35,3 +35,24 @@ pub struct DefaultDnsOptions {
pub struct CustomDnsOptions {
pub addresses: Vec<IpAddr>,
}
+
+impl DefaultDnsOptions {
+ /// Return whether any content blockers are enabled.
+ pub fn any_blockers_enabled(&self) -> bool {
+ let DefaultDnsOptions {
+ block_ads,
+ block_trackers,
+ block_malware,
+ block_adult_content,
+ block_gambling,
+ block_social_media,
+ } = *self;
+
+ block_ads
+ || block_trackers
+ || block_malware
+ || block_adult_content
+ || block_gambling
+ || block_social_media
+ }
+}
diff --git a/mullvad-types/src/states.rs b/mullvad-types/src/states.rs
index f31b48aa2f..cae80ae9b6 100644
--- a/mullvad-types/src/states.rs
+++ b/mullvad-types/src/states.rs
@@ -1,8 +1,8 @@
-use crate::location::GeoIpLocation;
+use crate::{features::FeatureIndicators, location::GeoIpLocation};
use serde::{Deserialize, Serialize};
use std::fmt;
use talpid_types::{
- net::TunnelEndpoint,
+ net::{TunnelEndpoint, TunnelType},
tunnel::{ActionAfterDisconnect, ErrorState},
};
@@ -38,10 +38,12 @@ pub enum TunnelState {
Connecting {
endpoint: TunnelEndpoint,
location: Option<GeoIpLocation>,
+ feature_indicators: FeatureIndicators,
},
Connected {
endpoint: TunnelEndpoint,
location: Option<GeoIpLocation>,
+ feature_indicators: FeatureIndicators,
},
Disconnecting(ActionAfterDisconnect),
Error(ErrorState),
@@ -49,17 +51,65 @@ pub enum TunnelState {
impl TunnelState {
/// Returns true if the tunnel state is in the error state.
- pub fn is_in_error_state(&self) -> bool {
+ pub const fn is_in_error_state(&self) -> bool {
matches!(self, TunnelState::Error(_))
}
/// Returns true if the tunnel state is in the connected state.
- pub fn is_connected(&self) -> bool {
+ pub const fn is_connected(&self) -> bool {
matches!(self, TunnelState::Connected { .. })
}
/// Returns true if the tunnel state is in the disconnected state.
- pub fn is_disconnected(&self) -> bool {
+ pub const fn is_disconnected(&self) -> bool {
matches!(self, TunnelState::Disconnected { .. })
}
+
+ /// Returns the tunnel endpoint for an active connection.
+ /// This value exists in the connecting and connected states.
+ pub const fn endpoint(&self) -> Option<&TunnelEndpoint> {
+ match self {
+ TunnelState::Connecting { endpoint, .. } | TunnelState::Connected { endpoint, .. } => {
+ Some(endpoint)
+ }
+ _ => None,
+ }
+ }
+
+ /// Returns the tunnel type for an active connection.
+ /// This value exists in the connecting and connected states.
+ pub const fn get_tunnel_type(&self) -> Option<TunnelType> {
+ match self.endpoint() {
+ Some(endpoint) => Some(endpoint.tunnel_type),
+ None => None,
+ }
+ }
+
+ /// Returns the current feature indicators for an active connection.
+ /// This value exists in the connecting and connected states.
+ pub const fn get_feature_indicators(&self) -> Option<&FeatureIndicators> {
+ match self {
+ TunnelState::Connecting {
+ feature_indicators, ..
+ }
+ | TunnelState::Connected {
+ feature_indicators, ..
+ } => Some(feature_indicators),
+ _ => None,
+ }
+ }
+
+ /// Update the set of feature indicators for this [`TunnelState`]. This is only applicable in
+ /// the connecting and connected states.
+ pub fn set_feature_indicators(&mut self, new_feature_indicators: FeatureIndicators) {
+ if let TunnelState::Connecting {
+ feature_indicators, ..
+ }
+ | TunnelState::Connected {
+ feature_indicators, ..
+ } = self
+ {
+ *feature_indicators = new_feature_indicators;
+ }
+ }
}