@@ -52,12 +52,13 @@ use mullvad_types::{
52
52
auth_failed:: AuthFailed ,
53
53
custom_list:: CustomList ,
54
54
device:: { Device , DeviceEvent , DeviceEventCause , DeviceId , DeviceState , RemoveDeviceEvent } ,
55
+ features:: { FeatureIndicator , FeatureIndicators } ,
55
56
location:: { GeoIpLocation , LocationEventData } ,
56
57
relay_constraints:: {
57
58
BridgeSettings , BridgeState , BridgeType , ObfuscationSettings , RelayOverride , RelaySettings ,
58
59
} ,
59
60
relay_list:: RelayList ,
60
- settings:: { DnsOptions , Settings } ,
61
+ settings:: { DnsOptions , DnsState , Settings } ,
61
62
states:: { TargetState , TunnelState } ,
62
63
version:: { AppVersion , AppVersionInfo } ,
63
64
wireguard:: { PublicKey , QuantumResistantState , RotationInterval } ,
@@ -85,7 +86,7 @@ use talpid_types::android::AndroidContext;
85
86
#[ cfg( target_os = "windows" ) ]
86
87
use talpid_types:: split_tunnel:: ExcludedProcess ;
87
88
use talpid_types:: {
88
- net:: { IpVersion , TunnelEndpoint , TunnelType } ,
89
+ net:: { IpVersion , ObfuscationType , TunnelType } ,
89
90
tunnel:: { ErrorStateCause , TunnelStateTransition } ,
90
91
ErrorExt ,
91
92
} ;
@@ -369,6 +370,8 @@ pub enum DaemonCommand {
369
370
ApplyJsonSettings ( ResponseTx < ( ) , settings:: patch:: Error > , String ) ,
370
371
/// Return a JSON blob containing all overridable settings, if there are any
371
372
ExportJsonSettings ( ResponseTx < String , settings:: patch:: Error > ) ,
373
+ /// Request the current feature indicators.
374
+ GetFeatureIndicators ( oneshot:: Sender < FeatureIndicators > ) ,
372
375
}
373
376
374
377
/// All events that can happen in the daemon. Sent from various threads and exposed interfaces.
@@ -393,6 +396,8 @@ pub(crate) enum InternalDaemonEvent {
393
396
DeviceMigrationEvent ( Result < PrivateAccountAndDevice , device:: Error > ) ,
394
397
/// A geographical location has has been received from am.i.mullvad.net
395
398
LocationEvent ( LocationEventData ) ,
399
+ /// A generic event for when any settings change.
400
+ SettingsChanged ,
396
401
/// The split tunnel paths or state were updated.
397
402
#[ cfg( any( windows, target_os = "android" , target_os = "macos" ) ) ]
398
403
ExcludedPathsEvent ( ExcludedPathsUpdate , oneshot:: Sender < Result < ( ) , Error > > ) ,
@@ -755,6 +760,13 @@ where
755
760
let _ = param_gen_tx. unbounded_send ( settings. tunnel_options . to_owned ( ) ) ;
756
761
} ) ;
757
762
763
+ // Register a listener for generic settings changes.
764
+ // This is useful for example for updating feature indicators when the settings change.
765
+ let settings_changed_event_sender = internal_event_tx. clone ( ) ;
766
+ settings. register_change_listener ( move |_settings| {
767
+ let _ = settings_changed_event_sender. send ( InternalDaemonEvent :: SettingsChanged ) ;
768
+ } ) ;
769
+
758
770
let ( offline_state_tx, offline_state_rx) = mpsc:: unbounded ( ) ;
759
771
#[ cfg( target_os = "windows" ) ]
760
772
let ( volume_update_tx, volume_update_rx) = mpsc:: unbounded ( ) ;
@@ -947,6 +959,9 @@ where
947
959
} => self . handle_access_method_event ( event, endpoint_active_tx) ,
948
960
DeviceMigrationEvent ( event) => self . handle_device_migration_event ( event) ,
949
961
LocationEvent ( location_data) => self . handle_location_event ( location_data) ,
962
+ SettingsChanged => {
963
+ self . handle_feature_indicator_event ( ) ;
964
+ }
950
965
#[ cfg( any( windows, target_os = "android" , target_os = "macos" ) ) ]
951
966
ExcludedPathsEvent ( update, tx) => self . handle_new_excluded_paths ( update, tx) . await ,
952
967
}
@@ -969,11 +984,13 @@ where
969
984
TunnelStateTransition :: Connecting ( endpoint) => TunnelState :: Connecting {
970
985
endpoint,
971
986
location : self . parameters_generator . get_last_location ( ) . await ,
987
+ feature_indicators : self . get_feature_indicators ( ) ,
988
+ } ,
989
+ TunnelStateTransition :: Connected ( endpoint) => TunnelState :: Connected {
990
+ endpoint,
991
+ location : self . parameters_generator . get_last_location ( ) . await ,
992
+ feature_indicators : self . get_feature_indicators ( ) ,
972
993
} ,
973
- TunnelStateTransition :: Connected ( endpoint) => {
974
- let location = self . parameters_generator . get_last_location ( ) . await ;
975
- TunnelState :: Connected { endpoint, location }
976
- }
977
994
TunnelStateTransition :: Disconnecting ( after_disconnect) => {
978
995
TunnelState :: Disconnecting ( after_disconnect)
979
996
}
@@ -1097,6 +1114,23 @@ where
1097
1114
. notify_new_state ( self . tunnel_state . clone ( ) ) ;
1098
1115
}
1099
1116
1117
+ /// Update the set of feature indicators.
1118
+ fn handle_feature_indicator_event ( & mut self ) {
1119
+ // Note: If the current tunnel state carries information about active feature indicators,
1120
+ // we should care to update the known set of feature indicators (i.e. in the connecting /
1121
+ // connected state). Otherwise, we can just skip broadcasting a new tunnel state.
1122
+ if let Some ( current_feature_indicators) = self . tunnel_state . get_feature_indicators ( ) {
1123
+ let new_feature_indicators = self . get_feature_indicators ( ) ;
1124
+ if * current_feature_indicators != new_feature_indicators {
1125
+ // Make sure to update the daemon's actual tunnel state. Otherwise feature indicator changes won't be persisted.
1126
+ self . tunnel_state
1127
+ . set_feature_indicators ( new_feature_indicators) ;
1128
+ self . event_listener
1129
+ . notify_new_state ( self . tunnel_state . clone ( ) ) ;
1130
+ }
1131
+ }
1132
+ }
1133
+
1100
1134
fn reset_rpc_sockets_on_tunnel_state_transition (
1101
1135
& mut self ,
1102
1136
tunnel_state_transition : & TunnelStateTransition ,
@@ -1248,6 +1282,7 @@ where
1248
1282
}
1249
1283
ApplyJsonSettings ( tx, blob) => self . on_apply_json_settings ( tx, blob) . await ,
1250
1284
ExportJsonSettings ( tx) => self . on_export_json_settings ( tx) ,
1285
+ GetFeatureIndicators ( tx) => self . on_get_feature_indicators ( tx) ,
1251
1286
}
1252
1287
}
1253
1288
@@ -2718,6 +2753,14 @@ where
2718
2753
Self :: oneshot_send ( tx, result, "export_json_settings response" ) ;
2719
2754
}
2720
2755
2756
+ fn on_get_feature_indicators ( & self , tx : oneshot:: Sender < FeatureIndicators > ) {
2757
+ Self :: oneshot_send (
2758
+ tx,
2759
+ self . get_feature_indicators ( ) ,
2760
+ "get_feature_indicators response" ,
2761
+ ) ;
2762
+ }
2763
+
2721
2764
/// Set the target state of the client. If it changed trigger the operations needed to
2722
2765
/// progress towards that state.
2723
2766
/// Returns a bool representing whether or not a state change was initiated.
@@ -2752,30 +2795,15 @@ where
2752
2795
}
2753
2796
}
2754
2797
2755
- fn get_connected_tunnel_type ( & self ) -> Option < TunnelType > {
2756
- if let TunnelState :: Connected {
2757
- endpoint : TunnelEndpoint { tunnel_type, .. } ,
2758
- ..
2759
- } = self . tunnel_state
2760
- {
2761
- Some ( tunnel_type)
2762
- } else {
2763
- None
2798
+ const fn get_connected_tunnel_type ( & self ) -> Option < TunnelType > {
2799
+ match self . tunnel_state . get_tunnel_type ( ) {
2800
+ Some ( tunnel_type) if self . tunnel_state . is_connected ( ) => Some ( tunnel_type) ,
2801
+ Some ( _) | None => None ,
2764
2802
}
2765
2803
}
2766
2804
2767
- fn get_target_tunnel_type ( & self ) -> Option < TunnelType > {
2768
- match self . tunnel_state {
2769
- TunnelState :: Connected {
2770
- endpoint : TunnelEndpoint { tunnel_type, .. } ,
2771
- ..
2772
- }
2773
- | TunnelState :: Connecting {
2774
- endpoint : TunnelEndpoint { tunnel_type, .. } ,
2775
- ..
2776
- } => Some ( tunnel_type) ,
2777
- _ => None ,
2778
- }
2805
+ const fn get_target_tunnel_type ( & self ) -> Option < TunnelType > {
2806
+ self . tunnel_state . get_tunnel_type ( )
2779
2807
}
2780
2808
2781
2809
fn send_tunnel_command ( & self , command : TunnelCommand ) {
@@ -2790,6 +2818,87 @@ where
2790
2818
tx : self . tx . clone ( ) ,
2791
2819
}
2792
2820
}
2821
+
2822
+ /// Source all active [`FeatureIndicators`].
2823
+ ///
2824
+ /// Note that [`FeatureIndicators`] only affect an active connection, which means that when the
2825
+ /// daemon is disconnected while calling this function the caller will see an empty set of
2826
+ /// [`FeatureIndicators`].
2827
+ fn get_feature_indicators ( & self ) -> FeatureIndicators {
2828
+ // Check if there is an active tunnel.
2829
+ let Some ( endpoint) = self . tunnel_state . endpoint ( ) else {
2830
+ // If there is not, no features are actually active and thus should not be displayed.
2831
+ return Default :: default ( ) ;
2832
+ } ;
2833
+ let settings = self . settings . to_settings ( ) ;
2834
+
2835
+ #[ cfg( any( windows, target_os = "android" , target_os = "macos" ) ) ]
2836
+ let split_tunneling = self . settings . split_tunnel . enable_exclusions ;
2837
+ #[ cfg( not( any( windows, target_os = "android" , target_os = "macos" ) ) ) ]
2838
+ let split_tunneling = false ;
2839
+
2840
+ let lockdown_mode = settings. block_when_disconnected ;
2841
+ let lan_sharing = settings. allow_lan ;
2842
+ let dns_content_blockers = settings
2843
+ . tunnel_options
2844
+ . dns_options
2845
+ . default_options
2846
+ . any_blockers_enabled ( ) ;
2847
+ let custom_dns = settings. tunnel_options . dns_options . state == DnsState :: Custom ;
2848
+ let server_ip_override = !settings. relay_overrides . is_empty ( ) ;
2849
+
2850
+ let generic_features = [
2851
+ ( split_tunneling, FeatureIndicator :: SplitTunneling ) ,
2852
+ ( lockdown_mode, FeatureIndicator :: LockdownMode ) ,
2853
+ ( lan_sharing, FeatureIndicator :: LanSharing ) ,
2854
+ ( dns_content_blockers, FeatureIndicator :: DnsContentBlockers ) ,
2855
+ ( custom_dns, FeatureIndicator :: CustomDns ) ,
2856
+ ( server_ip_override, FeatureIndicator :: ServerIpOverride ) ,
2857
+ ] ;
2858
+
2859
+ // Pick protocol-specific features and whether they are currently enabled.
2860
+ let protocol_features = match endpoint. tunnel_type {
2861
+ TunnelType :: OpenVpn => {
2862
+ let bridge_mode = endpoint. proxy . is_some ( ) ;
2863
+ let mss_fix = settings. tunnel_options . openvpn . mssfix . is_some ( ) ;
2864
+
2865
+ vec ! [
2866
+ ( bridge_mode, FeatureIndicator :: BridgeMode ) ,
2867
+ ( mss_fix, FeatureIndicator :: CustomMssFix ) ,
2868
+ ]
2869
+ }
2870
+ TunnelType :: Wireguard => {
2871
+ let quantum_resistant = endpoint. quantum_resistant ;
2872
+ let multihop = endpoint. entry_endpoint . is_some ( ) ;
2873
+ let udp_tcp = endpoint
2874
+ . obfuscation
2875
+ . as_ref ( )
2876
+ . filter ( |obfuscation| obfuscation. obfuscation_type == ObfuscationType :: Udp2Tcp )
2877
+ . is_some ( ) ;
2878
+
2879
+ let mtu = settings. tunnel_options . wireguard . mtu . is_some ( ) ;
2880
+
2881
+ #[ cfg( daita) ]
2882
+ let daita = endpoint. daita ;
2883
+
2884
+ vec ! [
2885
+ ( quantum_resistant, FeatureIndicator :: QuantumResistance ) ,
2886
+ ( multihop, FeatureIndicator :: Multihop ) ,
2887
+ ( udp_tcp, FeatureIndicator :: Udp2Tcp ) ,
2888
+ ( mtu, FeatureIndicator :: CustomMtu ) ,
2889
+ #[ cfg( daita) ]
2890
+ ( daita, FeatureIndicator :: Daita ) ,
2891
+ ]
2892
+ }
2893
+ } ;
2894
+
2895
+ // use the booleans to filter into a list of only the active features
2896
+ generic_features
2897
+ . into_iter ( )
2898
+ . chain ( protocol_features)
2899
+ . filter_map ( |( active, feature) | active. then_some ( feature) )
2900
+ . collect ( )
2901
+ }
2793
2902
}
2794
2903
2795
2904
#[ derive( Clone ) ]
0 commit comments