1
//! This module provides the [`PathBuilder`] helper for building vanguard [`TorPath`]s.
2

            
3
use std::result::Result as StdResult;
4

            
5
use rand::Rng;
6

            
7
use tor_error::{internal, Bug};
8
use tor_guardmgr::vanguards::{Layer, VanguardMgr};
9
use tor_linkspec::HasRelayIds;
10
use tor_netdir::{NetDir, Relay};
11
use tor_relay_selection::{RelayExclusion, RelaySelector, RelayUsage};
12
use tor_rtcompat::Runtime;
13

            
14
use crate::path::{MaybeOwnedRelay, TorPath};
15
use crate::{Error, Result};
16

            
17
/// A vanguard path builder.
18
///
19
/// A `PathBuilder` is a state machine whose current state is the [`HopKind`] of its last hop.
20
/// Not all state transitions are valid. For the permissible state transitions, see
21
/// [update_last_hop_kind](PathBuilder::update_last_hop_kind).
22
///
23
/// This type is an implementation detail that should remain private.
24
/// Used by [`VanguardHsPathBuilder`](super::VanguardHsPathBuilder).
25
pub(super) struct PathBuilder<'n, 'a, RT: Runtime, R: Rng> {
26
    /// The relays in the path.
27
    hops: Vec<MaybeOwnedRelay<'n>>,
28
    /// The network directory.
29
    netdir: &'n NetDir,
30
    /// The vanguard manager.
31
    vanguards: &'a VanguardMgr<RT>,
32
    /// An RNG for selecting vanguards and middle relays.
33
    rng: &'a mut R,
34
    /// The `HopKind` of the last hop in the path.
35
    last_hop_kind: HopKind,
36
}
37

            
38
/// The type of a `PathBuilder` hop.
39
#[derive(Copy, Clone, Debug, PartialEq, derive_more::Display)]
40
enum HopKind {
41
    /// The L1 guard.
42
    Guard,
43
    /// A vanguard from the specified [`Layer`].
44
    Vanguard(Layer),
45
    /// A middle relay.
46
    Middle,
47
}
48

            
49
impl<'n, 'a, RT: Runtime, R: Rng> PathBuilder<'n, 'a, RT, R> {
50
    /// Create a new `PathBuilder`.
51
56
    pub(super) fn new(
52
56
        rng: &'a mut R,
53
56
        netdir: &'n NetDir,
54
56
        vanguards: &'a VanguardMgr<RT>,
55
56
        l1_guard: MaybeOwnedRelay<'n>,
56
56
    ) -> Self {
57
56
        Self {
58
56
            hops: vec![l1_guard],
59
56
            netdir,
60
56
            vanguards,
61
56
            rng,
62
56
            last_hop_kind: HopKind::Guard,
63
56
        }
64
56
    }
65

            
66
    /// Extend the path with a vanguard.
67
88
    pub(super) fn add_vanguard(
68
88
        mut self,
69
88
        target_exclusion: &RelayExclusion<'n>,
70
88
        layer: Layer,
71
88
    ) -> Result<Self> {
72
88
        let mut neighbor_exclusion = exclude_neighbors(&self.hops);
73
88
        neighbor_exclusion.extend(target_exclusion);
74
88
        let vanguard: MaybeOwnedRelay = self
75
88
            .vanguards
76
88
            .select_vanguard(&mut self.rng, self.netdir, layer, &neighbor_exclusion)?
77
80
            .into();
78
80
        let () = self.add_hop(vanguard, HopKind::Vanguard(layer))?;
79
80
        Ok(self)
80
88
    }
81

            
82
    /// Extend the path with a middle relay.
83
36
    pub(super) fn add_middle(mut self, target_exclusion: &RelayExclusion<'n>) -> Result<Self> {
84
28
        let middle =
85
36
            select_middle_for_vanguard_circ(&self.hops, self.netdir, target_exclusion, self.rng)?
86
28
                .into();
87
28
        let () = self.add_hop(middle, HopKind::Middle)?;
88
28
        Ok(self)
89
36
    }
90

            
91
    /// Return a [`TorPath`] built using the hops from this `PathBuilder`.
92
40
    pub(super) fn build(self) -> Result<TorPath<'n>> {
93
        use HopKind::*;
94
        use Layer::*;
95

            
96
12
        match self.last_hop_kind {
97
40
            Vanguard(Layer3) | Middle => Ok(TorPath::new_multihop_from_maybe_owned(self.hops)),
98
            _ => Err(internal!(
99
                "tried to build TorPath from incomplete PathBuilder (last_hop_kind={})",
100
                self.last_hop_kind
101
            )
102
            .into()),
103
        }
104
40
    }
105

            
106
    /// Try to append `hop` to the end of the path.
107
    ///
108
    /// This also causes the `PathBuilder` to transition to the state represented by `hop_kind`,
109
    /// if the transition is valid.
110
    ///
111
    /// Returns an error if the `hop_kind` is incompatible with the `HopKind` of the last hop.
112
108
    fn add_hop(&mut self, hop: MaybeOwnedRelay<'n>, hop_kind: HopKind) -> StdResult<(), Bug> {
113
108
        self.update_last_hop_kind(hop_kind)?;
114
108
        self.hops.push(hop);
115
108
        Ok(())
116
108
    }
117

            
118
    /// Transition to the state specified by `kind`.
119
    ///
120
    /// The state of the `PathBuilder` is represented by the [`HopKind`] of its last hop.
121
    /// This function should be called whenever a new hop is added
122
    /// (e.g. in [`add_hop`](PathBuilder::add_hop)), to set the current state to the
123
    /// [`HopKind`] of the new hop.
124
    ///
125
    /// Not all transitions are valid. The permissible state transitions are:
126
    ///   * `G  -> L2`
127
    ///   * `L2 -> L3`
128
    ///   * `L2 -> M`
129
    ///   * `L3 -> M`
130
108
    fn update_last_hop_kind(&mut self, kind: HopKind) -> StdResult<(), Bug> {
131
        use HopKind::*;
132
        use Layer::*;
133

            
134
108
        match (self.last_hop_kind, kind) {
135
            (Guard, Vanguard(Layer2))
136
            | (Vanguard(Layer2), Vanguard(Layer3))
137
            | (Vanguard(Layer2), Middle)
138
96
            | (Vanguard(Layer3), Middle) => {
139
96
                self.last_hop_kind = kind;
140
96
            }
141
            (_, _) => {
142
                return Err(internal!(
143
                    "tried to build an invalid vanguard path: cannot add a {kind} hop after {}",
144
                    self.last_hop_kind
145
                ))
146
            }
147
        }
148

            
149
96
        Ok(())
150
96
    }
151
}
152

            
153
/// Build a [`RelayExclusion`] that excludes the specified relays.
154
124
fn exclude_identities<'a, T: HasRelayIds + 'a>(exclude_ids: &[&T]) -> RelayExclusion<'a> {
155
124
    RelayExclusion::exclude_identities(
156
124
        exclude_ids
157
124
            .iter()
158
192
            .flat_map(|relay| relay.identities())
159
384
            .map(|id| id.to_owned())
160
124
            .collect(),
161
124
    )
162
124
}
163

            
164
/// Create a `RelayExclusion` suitable for selecting the next hop to add to `hops`.
165
124
fn exclude_neighbors<'n, T: HasRelayIds + 'n>(hops: &[T]) -> RelayExclusion<'n> {
166
124
    // We must exclude the last 2 hops in the path,
167
124
    // because a relay can't extend to itself or to its predecessor.
168
124
    let skip_n = 2;
169
124
    let neighbors = hops.iter().rev().take(skip_n).collect::<Vec<&T>>();
170
124
    exclude_identities(&neighbors[..])
171
124
}
172

            
173
/// Select a middle relay that can be appended to a vanguard circuit.
174
///
175
/// Used by [`PathBuilder`] to build [`TorPath`]s of the form
176
///
177
///   G - L2 - M
178
///   G - L2 - L3 - M
179
///
180
/// If full vanguards are enabled, this is also used by [`HsCircPool`](crate::hspool::HsCircPool),
181
/// for extending NAIVE circuits to become GUARDED circuits.
182
36
pub(crate) fn select_middle_for_vanguard_circ<'n, R: Rng, T: HasRelayIds + 'n>(
183
36
    hops: &[T],
184
36
    netdir: &'n NetDir,
185
36
    target_exclusion: &RelayExclusion<'n>,
186
36
    rng: &mut R,
187
36
) -> Result<Relay<'n>> {
188
36
    let mut neighbor_exclusion = exclude_neighbors(hops);
189
36
    neighbor_exclusion.extend(target_exclusion);
190
36

            
191
36
    // TODO: this usage has need_stable = true, but we probably
192
36
    // don't necessarily need a stable relay here.
193
36
    let usage = RelayUsage::middle_relay(None);
194
36
    let selector = RelaySelector::new(usage, neighbor_exclusion);
195
36

            
196
36
    let (extra_hop, info) = selector.select_relay(rng, netdir);
197
36
    extra_hop.ok_or_else(|| Error::NoRelay {
198
8
        path_kind: "onion-service vanguard circuit",
199
8
        role: "extra hop",
200
8
        problem: info.to_string(),
201
36
    })
202
36
}