summaryrefslogtreecommitdiffhomepage
path: root/mullvad-daemon/src/custom_list.rs
blob: ce079c2fff38eccaf43ab8f89770825c5d60df3c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use crate::{Daemon, Error};
use mullvad_relay_selector::SelectorConfig;
use mullvad_types::relay_constraints::GeographicLocationConstraint;
use mullvad_types::{
    constraints::Constraint,
    custom_list::{CustomList, Id},
    relay_constraints::{BridgeState, LocationConstraint, RelaySettings, ResolvedBridgeSettings},
};
use std::collections::BTreeSet;
use talpid_types::net::TunnelType;

impl Daemon {
    /// Create a new custom list.
    ///
    /// Returns an error if the name is not unique.
    pub async fn create_custom_list(
        &mut self,
        name: String,
        locations: BTreeSet<GeographicLocationConstraint>,
    ) -> Result<Id, crate::Error> {
        let mut new_list = CustomList::new(name).map_err(crate::Error::CustomListError)?;
        new_list.append(locations);

        let id = new_list.id();

        self.settings
            .try_update(|settings| settings.custom_lists.add(new_list))
            .await
            .map_err(Error::SettingsError)?;

        Ok(id)
    }

    /// Update a custom list.
    ///
    /// Returns an error if the list doesn't exist.
    pub async fn delete_custom_list(&mut self, id: Id) -> Result<(), Error> {
        let settings_changed = self
            .settings
            .try_update(|settings| {
                // NOTE: Not using swap remove because it would make user output slightly
                // more confusing and the cost is so small.
                settings.custom_lists.remove(&id)
            })
            .await
            .map_err(Error::SettingsError);

        if let Ok(true) = settings_changed {
            self.relay_selector
                .set_config(SelectorConfig::from_settings(&self.settings));

            if self.change_should_cause_reconnect(Some(id)) {
                log::info!("Initiating tunnel restart because a selected custom list was deleted");
                self.reconnect_tunnel();
            }
        }

        settings_changed?;
        Ok(())
    }

    /// Update a custom list.
    ///
    /// Returns an error if...
    /// - there is no existing list with the same ID,
    /// - or the existing list has a different name.
    pub async fn update_custom_list(&mut self, new_list: CustomList) -> Result<(), Error> {
        let list_id = new_list.id();
        let settings_changed = self
            .settings
            .try_update(|settings| settings.custom_lists.update(new_list))
            .await
            .map_err(Error::SettingsError);

        if let Ok(true) = settings_changed {
            self.relay_selector
                .set_config(SelectorConfig::from_settings(&self.settings));

            if self.change_should_cause_reconnect(Some(list_id)) {
                log::info!("Initiating tunnel restart because a selected custom list changed");
                self.reconnect_tunnel();
            }
        }

        settings_changed?;
        Ok(())
    }

    /// Remove all custom lists.
    pub async fn clear_custom_lists(&mut self) -> Result<(), Error> {
        let settings_changed = self
            .settings
            .update(|settings| {
                settings.custom_lists.clear();
            })
            .await
            .map_err(Error::SettingsError);

        if let Ok(true) = settings_changed {
            self.relay_selector
                .set_config(SelectorConfig::from_settings(&self.settings));

            if self.change_should_cause_reconnect(None) {
                log::info!("Initiating tunnel restart because a selected custom list was deleted");
                self.reconnect_tunnel();
            }
        }

        settings_changed?;
        Ok(())
    }

    /// Check whether we need to reconnect after changing custom lists.
    ///
    /// 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 {
        let mut need_to_reconnect = false;

        let RelaySettings::Normal(relay_settings) = &self.settings.relay_settings else {
            return false;
        };

        if let Constraint::Only(LocationConstraint::CustomList { list_id }) =
            &relay_settings.location
        {
            need_to_reconnect |= custom_list_id.map(|id| &id == list_id).unwrap_or(true);
        }

        if let Some(endpoint) = self.tunnel_state.endpoint() {
            match endpoint.tunnel_type {
                TunnelType::Wireguard => {
                    if relay_settings.wireguard_constraints.multihop()
                        && let Constraint::Only(LocationConstraint::CustomList { list_id }) =
                            &relay_settings.wireguard_constraints.entry_location
                    {
                        need_to_reconnect |=
                            custom_list_id.map(|id| &id == list_id).unwrap_or(true);
                    }
                }

                TunnelType::OpenVpn => {
                    if !matches!(self.settings.bridge_state, BridgeState::Off)
                        && let Ok(ResolvedBridgeSettings::Normal(bridge_settings)) =
                            self.settings.bridge_settings.resolve()
                        && let Constraint::Only(LocationConstraint::CustomList { list_id }) =
                            &bridge_settings.location
                    {
                        need_to_reconnect |=
                            custom_list_id.map(|id| &id == list_id).unwrap_or(true);
                    }
                }
            }
        }

        need_to_reconnect
    }
}