summaryrefslogtreecommitdiffhomepage
path: root/talpid-routing/src/unix/macos/default_routes.rs
blob: 43fee4ad99f2bb7ff2cabfae6db63a38457999d9 (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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
use std::{collections::HashMap, convert::Infallible, future::pending, mem, time::Duration};

use futures::{
    FutureExt, StreamExt,
    channel::mpsc::{self, UnboundedReceiver, UnboundedSender},
    select_biased,
};
use tokio::time::{Instant, sleep_until};

use crate::imp::imp::interface::NetworkServiceDetails;

use super::{
    DefaultRoute,
    interface::{Family, InterfaceEvent, PrimaryInterfaceDetails, PrimaryInterfaceMonitor},
    ip_map::IpMap,
};

/// Grace time during which we don't act if the best default route disappears.
//
// 5 seconds seemed to be a reasonable value when testing.
// Increasing this value will increase the time it takes the daemon to realize when there's no
// network connectivity. Decreasing it will increase the risk of unnecessary reconnects when the
// best default route simply goes away for a few seconds.
const NO_ROUTE_GRACE_TIME: Duration = Duration::from_secs(5);

/// Monitors changes to the primary interface and reports [BestRoute].
pub struct DefaultRouteMonitor {
    monitor: PrimaryInterfaceMonitor,
    event_rx: UnboundedReceiver<Vec<InterfaceEvent>>,

    route_tx: IpMap<UnboundedSender<Option<DefaultRoute>>>,

    /// The current best routes.
    current_route: IpMap<DefaultRoute>,

    /// The current primary interfaces.
    primary_interfaces: IpMap<PrimaryInterfaceDetails>,
}

impl DefaultRouteMonitor {
    /// Start monitoring interfaces for changes to the best route.
    ///
    /// Returns an IPv4 and an IPv6 channel of [BestRoute] updates.
    pub fn start(
        monitor: PrimaryInterfaceMonitor,
        event_rx: UnboundedReceiver<Vec<InterfaceEvent>>,
    ) -> (
        UnboundedReceiver<Option<DefaultRoute>>,
        UnboundedReceiver<Option<DefaultRoute>>,
    ) {
        let (route_v4_tx, route_v4_rx) = mpsc::unbounded();
        let (route_v6_tx, route_v6_rx) = mpsc::unbounded();

        let mut route_tx = IpMap::new();
        route_tx.insert(Family::V4, route_v4_tx);
        route_tx.insert(Family::V6, route_v6_tx);

        let monitor = DefaultRouteMonitor {
            monitor,
            event_rx,
            route_tx,
            current_route: IpMap::new(),
            primary_interfaces: IpMap::new(),
        };

        tokio::task::spawn(monitor.run());

        let route_v4_rx =
            filter_duplicates(delay_nones_except_first(NO_ROUTE_GRACE_TIME, route_v4_rx));
        let route_v6_rx =
            filter_duplicates(delay_nones_except_first(NO_ROUTE_GRACE_TIME, route_v6_rx));

        (route_v4_rx, route_v6_rx)
    }

    async fn run(mut self) {
        for family in [Family::V4, Family::V6] {
            let route = self.monitor.get_route(family);

            self.current_route.set(family, route.clone());
            if let Some(tx) = self.route_tx.get(family) {
                let _ = tx.unbounded_send(route);
            }
        }

        while let Some(events) = self.event_rx.next().await {
            if self.route_tx.is_empty() {
                break;
            }

            self.handle_events(events);
        }
    }

    fn handle_events(&mut self, events: Vec<InterfaceEvent>) {
        // Split events by address family and handle them separately.
        let mut ipv4_events = vec![];
        let mut ipv6_events = vec![];
        for event in events {
            match event.family() {
                Family::V4 => ipv4_events.push(event),
                Family::V6 => ipv6_events.push(event),
            }
        }

        self.handle_events_for_family(Family::V4, ipv4_events);
        self.handle_events_for_family(Family::V6, ipv6_events);
    }

    fn handle_events_for_family(&mut self, family: Family, events: Vec<InterfaceEvent>) {
        enum Change<T> {
            New(T),
            Removed,
        }

        // Go through the events and figure out if the primary interface changed.
        let mut primary_interface_change: Option<Change<PrimaryInterfaceDetails>> = None;
        for event in &events {
            let InterfaceEvent::PrimaryInterfaceUpdate { new_value, .. } = event else {
                continue;
            };

            primary_interface_change = Some(match new_value {
                Some(new_value) => Change::New(new_value.clone()),
                None => Change::Removed,
            });
        }

        // Collect all NetworkServiceUpdates into a HashMap.
        let changed_services: HashMap<String, Change<NetworkServiceDetails>> = events
            .into_iter()
            .filter_map(|service| {
                let InterfaceEvent::NetworkServiceUpdate {
                    service_id,
                    new_value,
                    ..
                } = service
                else {
                    return None;
                };

                let change = match new_value {
                    Some(service) => Change::New(service),
                    None => Change::Removed,
                };

                Some((service_id, change))
            })
            .collect();

        // Figure out if anything interesting happened.
        // Things we care about:
        //  - The primary interface changed.
        //  - The service of the primary interface changed.
        //  - If we're NOT using the primary interface, we care about whether ANY service changed.
        let an_important_service_changed =
            if let Some(primary_interface) = self.primary_interfaces.get(family) {
                changed_services.contains_key(&primary_interface.service_id)
            } else {
                !changed_services.is_empty()
            };

        // If nothing interesting has happened, just return.
        if primary_interface_change.is_none() && !an_important_service_changed {
            return;
        }

        // Figure out what the new default route should be.
        // Match on the new primary interface, and the previous primary interface
        let new_route = match (
            primary_interface_change.as_ref(),
            self.primary_interfaces.get(family),
        ) {
            // This match covers two cases:
            // - The primary interface changed.
            // - The primary interface didn't change, and we have one from before.
            (Some(Change::New(interface)), _) | (None, Some(interface)) => changed_services
                .get(&interface.service_id)
                .and_then(|change| match change {
                    Change::New(service) => Some(service),
                    Change::Removed => None,
                })
                .and_then(|service| self.monitor.route_from_service(service))
                .or_else(|| self.monitor.get_route_by_service_order(family)),

            // This match covers the case where the primary interface was removed, or it never
            // existed. In this case we iterate over all network services and pick the first good
            // one.
            _ => self.monitor.get_route_by_service_order(family),
        };

        self.current_route.set(family, new_route.clone());
        if let Some(tx) = self.route_tx.get(family) {
            if tx.unbounded_send(new_route).is_err() {
                self.route_tx.remove(family);
            }
        }
    }
}

/// Filter out duplicate messages from a channel.
///
/// This will always keep a clone of the last value that was sent on the channel.
fn filter_duplicates<T: PartialEq + Clone + Send + 'static>(
    unfiltered_rx: UnboundedReceiver<T>,
) -> UnboundedReceiver<T> {
    async fn do_filtering<T: PartialEq + Clone + Send + 'static>(
        mut unfiltered_rx: UnboundedReceiver<T>,
        filtered_tx: UnboundedSender<T>,
    ) -> Option<Infallible> {
        let mut last_value = unfiltered_rx.next().await?;
        filtered_tx.unbounded_send(last_value.clone()).ok()?;

        loop {
            let prev_value = mem::replace(&mut last_value, unfiltered_rx.next().await?);

            if last_value != prev_value {
                filtered_tx.unbounded_send(last_value.clone()).ok()?;
            }
        }
    }

    let (filtered_tx, filtered_rx) = mpsc::unbounded();
    tokio::task::spawn(do_filtering(unfiltered_rx, filtered_tx));
    filtered_rx
}

/// Delay `None`-events by `grace_time`, except for the first value received.
///
/// When receiving a `None` on the channel, a timer will start. If no `Some`s are received within
/// the deadline, a `None` will be sent.
///
/// Some `None`s may be dropped, but `Some`-values are passed along immediately.
fn delay_nones_except_first<T: Send + 'static>(
    grace_time: Duration,
    mut fast_rx: UnboundedReceiver<Option<T>>,
) -> UnboundedReceiver<Option<T>> {
    let (slow_tx, slow_rx) = mpsc::unbounded();

    tokio::task::spawn(async move {
        let mut no_route_grace_timeout = None;

        // We send the initial value without any delay
        let Some(route) = fast_rx.next().await else {
            return;
        };
        if slow_tx.unbounded_send(route).is_err() {
            return;
        }

        loop {
            let no_route_grace_timer = async {
                match no_route_grace_timeout {
                    None => pending().await,
                    Some(time) => sleep_until(time).await,
                };
            };

            select_biased! {
                route = fast_rx.next() => {
                    let Some(route) = route else { return };

                    if route.is_some() {
                        no_route_grace_timeout = None;
                        if slow_tx.unbounded_send(route).is_err() {
                            return;
                        };

                    } else if no_route_grace_timeout.is_none() {
                        no_route_grace_timeout = Some(Instant::now() + grace_time);
                    }
                }

                _ = no_route_grace_timer.fuse() => {
                    no_route_grace_timeout = None;
                    if slow_tx.unbounded_send(None).is_err() {
                        return;
                    };
                }
            }
        }
    });

    slow_rx
}