Skip to main content

ts_control/
dial_plan.rs

1use core::{net::IpAddr, time::Duration};
2
3use ts_control_serde::{ControlDialPlan, ControlIpCandidate};
4
5/// A plan to connect to the control plane, supplied as part of a netmap update.
6#[derive(Debug, Clone, PartialEq, Eq, Default)]
7pub enum DialPlan {
8    /// Use system DNS to resolve the control plane's IP address.
9    #[default]
10    UseDns,
11
12    /// Use the contained plan to connect to the control plane.
13    Plan(Vec<DialCandidate>),
14}
15
16/// A candidate endpoint for a control plane connection.
17#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
18pub struct DialCandidate {
19    // NOTE(npry): field order in this struct is load-bearing for the PartialOrd derive.
20    // The relevant initial tuple is (priority, start_delay_sec).
21    /// The priority of this candidate.
22    ///
23    /// Higher priorities are preferred over lower ones.
24    pub priority: usize,
25
26    /// How long to delay before attempting to use this candidate.
27    pub start_delay_sec: Duration,
28    /// Timeout before giving up on this candidate.
29    pub timeout: Duration,
30
31    /// The mode with which to connect.
32    pub mode: DialMode,
33}
34
35/// The mode with which to connect to the control plane.
36#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
37pub enum DialMode {
38    /// Connect directly via the indicated IP address (do not resolve using DNS).
39    Ip(IpAddr),
40
41    /// Connect to control over HTTPS using the proxy CONNECT method via the selected host.
42    Ace {
43        /// The hostname to connect to.
44        ///
45        /// This is always set as the HTTP request's `Host`.
46        host: String,
47
48        /// If present, the resolved address of the host to connect to.
49        ip: Option<IpAddr>,
50    },
51}
52
53impl From<ControlDialPlan<'_>> for DialPlan {
54    fn from(value: ControlDialPlan<'_>) -> Self {
55        (&value).into()
56    }
57}
58
59impl TryFrom<ControlIpCandidate<'_>> for DialCandidate {
60    type Error = ();
61
62    fn try_from(value: ControlIpCandidate<'_>) -> Result<Self, Self::Error> {
63        (&value).try_into()
64    }
65}
66
67impl From<&ControlDialPlan<'_>> for DialPlan {
68    fn from(value: &ControlDialPlan<'_>) -> Self {
69        let mut plan_candidates = value
70            .candidates
71            .iter()
72            .filter_map(|x| x.try_into().ok())
73            .collect::<Vec<DialCandidate>>();
74
75        // sort in decreasing priority order
76        plan_candidates.sort_by(|a, b| a.cmp(b).reverse());
77
78        if plan_candidates.is_empty() {
79            DialPlan::UseDns
80        } else {
81            DialPlan::Plan(plan_candidates)
82        }
83    }
84}
85
86impl TryFrom<&ControlIpCandidate<'_>> for DialCandidate {
87    type Error = ();
88
89    fn try_from(value: &ControlIpCandidate<'_>) -> Result<Self, Self::Error> {
90        let mode = if let Some(ace_host) = value.ace_host {
91            DialMode::Ace {
92                host: ace_host.to_string(),
93                ip: value.ip,
94            }
95        } else if let Some(ip) = value.ip {
96            DialMode::Ip(ip)
97        } else {
98            return Err(());
99        };
100
101        Ok(Self {
102            mode,
103            timeout: value.dial_timeout_sec,
104            start_delay_sec: value.dial_start_delay_sec,
105            priority: value.priority.clamp(0, 256) as _,
106        })
107    }
108}