1
//! Implement GuardFilter and related types.
2

            
3
use tor_linkspec::ChanTarget;
4
// TODO(nickm): Conceivably, this type should be exposed from a lower-level crate than
5
// tor-netdoc.
6
use tor_netdoc::types::policy::AddrPortPattern;
7
use tor_relay_selection::{LowLevelRelayPredicate, RelayRestriction, RelaySelector, RelayUsage};
8

            
9
/// An object specifying which relays are eligible to be guards.
10
///
11
/// We _always_ restrict the set of possible guards to be the set of
12
/// relays currently listed in the consensus directory document, and
13
/// tagged with the `Guard` flag.  But clients may narrow the eligible set
14
/// even further—for example, to those supporting only a given set of ports,
15
/// or to those in a given country.
16
#[derive(Debug, Clone, Default, Eq, PartialEq)]
17
pub struct GuardFilter {
18
    /// A list of filters to apply to guard or fallback selection.  Each filter
19
    /// restricts which guards may be used, and possibly how those guards may be
20
    /// contacted.
21
    ///
22
    /// This list of filters has "and" semantics: a relay is permitted by this
23
    /// filter if ALL patterns in this list permit that first hop.
24
    filters: Vec<SingleFilter>,
25
}
26

            
27
/// A single restriction places upon usable guards.
28
#[derive(Debug, Clone, Eq, PartialEq)]
29
enum SingleFilter {
30
    /// A set of allowable addresses that we are willing to try to connect to.
31
    ///
32
    /// This list of patterns has "or" semantics: a guard is permitted by this filter
33
    /// if ANY pattern in this list permits one of the guard's addresses.
34
    ReachableAddrs(Vec<AddrPortPattern>),
35
}
36

            
37
impl GuardFilter {
38
    /// Create a new [`GuardFilter`] that doesn't restrict the set of
39
    /// permissible guards at all.
40
37814
    pub fn unfiltered() -> Self {
41
37814
        GuardFilter::default()
42
37814
    }
43

            
44
    /// Restrict this filter to only permit connections to an address permitted
45
    /// by one of the patterns in `addrs`.
46
662
    pub fn push_reachable_addresses(&mut self, addrs: impl IntoIterator<Item = AddrPortPattern>) {
47
662
        self.filters
48
662
            .push(SingleFilter::ReachableAddrs(addrs.into_iter().collect()));
49
662
    }
50

            
51
    /// Return true if this filter permits the provided `target`.
52
1962138
    pub(crate) fn permits<C: ChanTarget>(&self, target: &C) -> bool {
53
1962138
        self.filters.iter().all(|filt| filt.permits(target))
54
1962138
    }
55

            
56
    /// Modify `first_hop` so that it contains no elements not permitted by this
57
    /// filter.
58
    ///
59
    /// (For example, if we are restricted only to use certain addresses, then
60
    /// `permits` will return true for a guard that has multiple addresses even
61
    /// if _some_ of those addresses are not permitted.  In that scenario, this
62
    /// method will remove disallowed addresses from `first_hop`.)
63
555940
    pub(crate) fn modify_hop(
64
555940
        &self,
65
555940
        mut first_hop: crate::FirstHop,
66
555940
    ) -> Result<crate::FirstHop, crate::PickGuardError> {
67
555952
        for filt in &self.filters {
68
12
            first_hop = filt.modify_hop(first_hop)?;
69
        }
70
555940
        Ok(first_hop)
71
555940
    }
72

            
73
    /// Return true if this filter excludes no guards at all.
74
23604
    pub(crate) fn is_unfiltered(&self) -> bool {
75
23604
        self.filters.is_empty()
76
23604
    }
77

            
78
    /// Return a fraction between 0.0 and 1.0 describing what fraction of the
79
    /// guard bandwidth this filter permits.
80
1914
    pub(crate) fn frac_bw_permitted(&self, netdir: &tor_netdir::NetDir) -> f64 {
81
        use tor_netdir::{RelayWeight, WeightRole};
82
1914
        let mut guard_bw: RelayWeight = 0.into();
83
1914
        let mut permitted_bw: RelayWeight = 0.into();
84
        // TODO #504: This is an unaccompanied RelayUsage, and is therefore a
85
        // bit suspect.  We should consider whether we like this behavior,
86
        // or whether we should convert it into a RelaySelector.
87
        //
88
        // It is not _too_ bad, however, since we're only looking at the
89
        // fraction of the relays that might be guards; we won't use these
90
        // relays without later choosing them via a RelaySelector.  Nonetheless,
91
        // it would be better to construct a RelaySelector.
92
1914
        let usage = RelayUsage::new_guard();
93

            
94
        // TODO: There is a case to be made for converting "permitted by this
95
        // address-port filter?" into a RelayRestriction.
96
76560
        for relay in netdir.relays() {
97
76560
            if usage.low_level_predicate_permits_relay(&relay) {
98
19140
                let w = netdir.relay_weight(&relay, WeightRole::Guard);
99
19140
                guard_bw += w;
100
19140
                if self.permits(&relay) {
101
19008
                    permitted_bw += w;
102
19008
                }
103
57420
            }
104
        }
105

            
106
1914
        permitted_bw.checked_div(guard_bw).unwrap_or(1.0)
107
1914
    }
108

            
109
    /// Update `selector` with all the restrictions from this filter.
110
23628
    pub(crate) fn add_to_selector(&self, selector: &mut RelaySelector) {
111
        // TODO #504: There is a case to be made that we should refactor
112
        // `tor-guardmgr` crate so that GuardFilter no longer exists
113
        // independently, but instead is just a part of RelaySelector.
114
        //
115
        // But before we do that, we should let the rest of #504 settle.
116
23652
        for filt in &self.filters {
117
24
            selector.push_restriction(match filt {
118
24
                SingleFilter::ReachableAddrs(addrs) => {
119
24
                    RelayRestriction::require_address(addrs.clone())
120
24
                }
121
24
            });
122
24
        }
123
23628
    }
124
}
125

            
126
impl SingleFilter {
127
    /// Return true if this filter permits the provided target.
128
252
    fn permits<C: ChanTarget>(&self, target: &C) -> bool {
129
252
        match self {
130
            // TODO: This is partially duplicated with tor-relay-selection,
131
            // but (for now) that only covers Relays, not general ChanTargets.
132
252
            SingleFilter::ReachableAddrs(patterns) => {
133
252
                patterns.iter().any(|pat| {
134
252
                    match target.chan_method().socket_addrs() {
135
                        // Check whether _any_ address actually used by this
136
                        // method is permitted by _any_ pattern.
137
252
                        Some(addrs) => addrs.iter().any(|addr| pat.matches_sockaddr(addr)),
138
                        // This target doesn't use addresses: only hostnames or "None"
139
                        None => true,
140
                    }
141
252
                })
142
            }
143
        }
144
252
    }
145

            
146
    /// Modify `first_hop` so that it contains no elements not permitted by this
147
    /// filter.
148
    ///
149
    /// It is an internal error to call this function on a guard not already
150
    /// passed by `self.permits()`.
151
12
    fn modify_hop(
152
12
        &self,
153
12
        mut first_hop: crate::FirstHop,
154
12
    ) -> Result<crate::FirstHop, crate::PickGuardError> {
155
12
        match self {
156
12
            SingleFilter::ReachableAddrs(patterns) => {
157
12
                let r = first_hop
158
12
                    .chan_target_mut()
159
12
                    .chan_method_mut()
160
18
                    .retain_addrs(|addr| patterns.iter().any(|pat| pat.matches_sockaddr(addr)));
161

            
162
12
                if r.is_err() {
163
                    // TODO(nickm): The fact that this check needs to be checked
164
                    // happen indicates a likely problem in our code design.
165
                    // Right now, we have `modify_hop` and `permits` as separate
166
                    // methods because our GuardSet logic needs a way to check
167
                    // whether a guard will be permitted by a filter without
168
                    // actually altering that guard (since another filter might
169
                    // be used in the future that would allow the same guard).
170
                    //
171
                    // To mitigate the risk of hitting this error, we try to
172
                    // make sure that modify_hop is always called right after
173
                    // (or at least soon after) the filter is checked, with the
174
                    // same filter object.
175
                    return Err(tor_error::internal!(
176
                        "Tried to apply an address filter to an unsupported guard"
177
                    )
178
                    .into());
179
12
                }
180
            }
181
        }
182
12
        Ok(first_hop)
183
12
    }
184
}
185

            
186
#[cfg(test)]
187
mod test {
188
    // @@ begin test lint list maintained by maint/add_warning @@
189
    #![allow(clippy::bool_assert_comparison)]
190
    #![allow(clippy::clone_on_copy)]
191
    #![allow(clippy::dbg_macro)]
192
    #![allow(clippy::mixed_attributes_style)]
193
    #![allow(clippy::print_stderr)]
194
    #![allow(clippy::print_stdout)]
195
    #![allow(clippy::single_char_pattern)]
196
    #![allow(clippy::unwrap_used)]
197
    #![allow(clippy::unchecked_time_subtraction)]
198
    #![allow(clippy::useless_vec)]
199
    #![allow(clippy::needless_pass_by_value)]
200
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
201
    use super::*;
202
    use float_eq::assert_float_eq;
203
    use tor_netdir::testnet;
204

            
205
    #[test]
206
    fn permissiveness() {
207
        let nd = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
208
        const TOL: f64 = 0.01;
209

            
210
        let non_filter = GuardFilter::default();
211
        assert_float_eq!(non_filter.frac_bw_permitted(&nd), 1.0, abs <= TOL);
212

            
213
        let forbid_all = {
214
            let mut f = GuardFilter::default();
215
            f.push_reachable_addresses(vec!["*:1".parse().unwrap()]);
216
            f
217
        };
218
        assert_float_eq!(forbid_all.frac_bw_permitted(&nd), 0.0, abs <= TOL);
219
        let net_1_only = {
220
            let mut f = GuardFilter::default();
221
            f.push_reachable_addresses(vec!["1.0.0.0/8:*".parse().unwrap()]);
222
            f
223
        };
224
        assert_float_eq!(net_1_only.frac_bw_permitted(&nd), 0.28, abs <= TOL);
225
    }
226
}