1
//! Implement port-based policies
2
//!
3
//! These are also known as "short policies" or "policy summaries".
4

            
5
use std::fmt::Display;
6
use std::str::FromStr;
7
use std::sync::Arc;
8

            
9
use super::{PolicyError, PortRange};
10
use crate::util::intern::InternCache;
11

            
12
/// A policy to match zero or more TCP/UDP ports.
13
///
14
/// These are used in Tor to summarize all policies in
15
/// microdescriptors, and Ipv6 policies in router descriptors.
16
///
17
/// NOTE: If a port is listed as accepted, it doesn't mean that the
18
/// relay allows _every_ address on that port.  Instead, a port is
19
/// listed if a relay will exit to _most public addresses_ on that
20
/// port. Therefore, unlike [super::addrpolicy::AddrPolicy] objects,
21
/// these policies cannot tell you if a port is _definitely_ allowed
22
/// or rejected: only if it is _probably_ allowed or rejected.
23
///
24
/// # Examples
25
/// ```
26
/// use tor_netdoc::types::policy::PortPolicy;
27
/// let policy: PortPolicy = "accept 1-1023,8000-8999,60000-65535".parse().unwrap();
28
///
29
/// assert!(policy.allows_port(22));
30
/// assert!(policy.allows_port(8000));
31
/// assert!(! policy.allows_port(1024));
32
/// assert!(! policy.allows_port(9000));
33
/// ```
34
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
35
pub struct PortPolicy {
36
    /// A list of port ranges that this policy allows.
37
    ///
38
    /// These ranges sorted, disjoint, and compact.
39
    allowed: Vec<PortRange>,
40
}
41

            
42
impl Display for PortPolicy {
43
12
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44
12
        if self.allowed.is_empty() {
45
2
            write!(f, "reject 1-65535")?;
46
        } else {
47
10
            write!(f, "accept ")?;
48
10
            let mut comma = "";
49
30
            for range in &self.allowed {
50
20
                write!(f, "{}{}", comma, range)?;
51
20
                comma = ",";
52
            }
53
        }
54
12
        Ok(())
55
12
    }
56
}
57

            
58
impl PortPolicy {
59
    /// Return a new PortPolicy that rejects all ports.
60
620076
    pub fn new_reject_all() -> Self {
61
620076
        PortPolicy {
62
620076
            allowed: Vec::new(),
63
620076
        }
64
620076
    }
65

            
66
    /// Create a PortPolicy from a list of allowed ports. All other ports will be rejected. The
67
    /// ports in the list may be in any order.
68
17704
    pub fn from_allowed_port_list(ports: Vec<u16>) -> Self {
69
17704
        let mut ports = ports;
70
17704
        ports.sort();
71
17704
        let mut ports = ports.iter().peekable();
72
17704

            
73
17704
        let mut out = PortPolicy::new_reject_all();
74
17704

            
75
17704
        let mut current_min = None;
76
94290
        while let Some(port) = ports.next() {
77
76586
            if current_min.is_none() {
78
46202
                current_min = Some(port);
79
46202
            }
80
76586
            if let Some(next_port) = ports.peek() {
81
62664
                if **next_port != port + 1 {
82
32280
                    let _ = out.push_policy(PortRange::new_unchecked(
83
32280
                        *current_min.expect("Don't have min port number"),
84
32280
                        *port,
85
32280
                    ));
86
32280
                    current_min = None;
87
32400
                }
88
13922
            } else {
89
13922
                let _ = out.push_policy(PortRange::new_unchecked(
90
13922
                    *current_min.expect("Don't have min port number"),
91
13922
                    *port,
92
13922
                ));
93
13922
            }
94
        }
95

            
96
17704
        out
97
17704
    }
98

            
99
    /// Helper: replace this policy with its inverse.
100
165582
    fn invert(&mut self) {
101
165582
        let mut prev_hi = 0;
102
165582
        let mut new_allowed = Vec::new();
103
331166
        for entry in &self.allowed {
104
            // ports prev_hi+1 through entry.lo-1 were rejected.  We should
105
            // make them allowed.
106
165584
            if entry.lo > prev_hi + 1 {
107
6
                new_allowed.push(PortRange::new_unchecked(prev_hi + 1, entry.lo - 1));
108
165578
            }
109
165584
            prev_hi = entry.hi;
110
        }
111
165582
        if prev_hi < 65535 {
112
6
            new_allowed.push(PortRange::new_unchecked(prev_hi + 1, 65535));
113
165576
        }
114
165582
        self.allowed = new_allowed;
115
165582
    }
116
    /// Helper: add a new range to the end of this portpolicy.
117
    ///
118
    /// gives an error if this range cannot appear next in sequence.
119
440002
    fn push_policy(&mut self, item: PortRange) -> Result<(), PolicyError> {
120
440002
        if let Some(prev) = self.allowed.last() {
121
            // TODO SPEC: We don't enforce this in Tor, but we probably
122
            // should.  See torspec#60.
123
108168
            if prev.hi >= item.lo {
124
8
                return Err(PolicyError::InvalidPolicy);
125
108160
            } else if prev.hi == item.lo - 1 {
126
                // We compress a-b,(b+1)-c into a-c.
127
10
                let r = PortRange::new_unchecked(prev.lo, item.hi);
128
10
                self.allowed.pop();
129
10
                self.allowed.push(r);
130
10
                return Ok(());
131
108150
            }
132
331834
        }
133

            
134
439984
        self.allowed.push(item);
135
439984
        Ok(())
136
440002
    }
137
    /// Return true iff `port` is allowed by this policy.
138
20923886
    pub fn allows_port(&self, port: u16) -> bool {
139
20923886
        self.allowed
140
21358631
            .binary_search_by(|range| range.compare_to_port(port))
141
20923886
            .is_ok()
142
20923886
    }
143
    /// Replace this PortPolicy with an interned copy, to save memory.
144
628288
    pub fn intern(self) -> Arc<Self> {
145
628288
        POLICY_CACHE.intern(self)
146
628288
    }
147
    /// Return true if this policy allows any ports at all.
148
    ///
149
    /// # Example
150
    /// ```
151
    /// use tor_netdoc::types::policy::PortPolicy;
152
    ///
153
    /// let policy: PortPolicy = "accept 22".parse().unwrap();
154
    /// assert!(policy.allows_some_port());
155
    /// let policy2: PortPolicy = "reject 1-65535".parse().unwrap();
156
    /// assert!(! policy2.allows_some_port());
157
    /// ```
158
10304196
    pub fn allows_some_port(&self) -> bool {
159
10304196
        !self.allowed.is_empty()
160
10304196
    }
161
}
162

            
163
impl FromStr for PortPolicy {
164
    type Err = PolicyError;
165
317926
    fn from_str(mut s: &str) -> Result<Self, PolicyError> {
166
317926
        let invert = if s.starts_with("accept ") {
167
152328
            false
168
165598
        } else if s.starts_with("reject ") {
169
165588
            true
170
        } else {
171
10
            return Err(PolicyError::InvalidPolicy);
172
        };
173
317916
        let mut result = PortPolicy {
174
317916
            allowed: Vec::new(),
175
317916
        };
176
317916
        s = &s[7..];
177
393806
        for item in s.split(',') {
178
393806
            let r: PortRange = item.parse()?;
179
393800
            result.push_policy(r)?;
180
        }
181
317902
        if invert {
182
165582
            result.invert();
183
165592
        }
184
317902
        Ok(result)
185
317926
    }
186
}
187

            
188
/// Cache of PortPolicy objects, for saving memory.
189
//
190
/// This only holds weak references to the policy objects, so we don't
191
/// need to worry about running out of space because of stale entries.
192
static POLICY_CACHE: InternCache<PortPolicy> = InternCache::new();
193

            
194
#[cfg(test)]
195
mod test {
196
    // @@ begin test lint list maintained by maint/add_warning @@
197
    #![allow(clippy::bool_assert_comparison)]
198
    #![allow(clippy::clone_on_copy)]
199
    #![allow(clippy::dbg_macro)]
200
    #![allow(clippy::mixed_attributes_style)]
201
    #![allow(clippy::print_stderr)]
202
    #![allow(clippy::print_stdout)]
203
    #![allow(clippy::single_char_pattern)]
204
    #![allow(clippy::unwrap_used)]
205
    #![allow(clippy::unchecked_duration_subtraction)]
206
    #![allow(clippy::useless_vec)]
207
    #![allow(clippy::needless_pass_by_value)]
208
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
209
    use itertools::Itertools;
210

            
211
    use super::*;
212

            
213
    #[test]
214
    fn test_roundtrip() {
215
        fn check(inp: &str, outp: &str, allow: &[u16], deny: &[u16]) {
216
            let policy = inp.parse::<PortPolicy>().unwrap();
217
            assert_eq!(format!("{}", policy), outp);
218
            for p in allow {
219
                assert!(policy.allows_port(*p));
220
            }
221
            for p in deny {
222
                assert!(!policy.allows_port(*p));
223
            }
224
        }
225

            
226
        check(
227
            "accept 1-10,30-50,600",
228
            "accept 1-10,30-50,600",
229
            &[1, 10, 35, 600],
230
            &[0, 11, 55, 599, 601],
231
        );
232
        check("accept 1-10,11-20", "accept 1-20", &[], &[]);
233
        check(
234
            "reject 1-30",
235
            "accept 31-65535",
236
            &[31, 10001, 65535],
237
            &[0, 1, 30],
238
        );
239
        check(
240
            "reject 300-500",
241
            "accept 1-299,501-65535",
242
            &[31, 10001, 65535],
243
            &[300, 301, 500],
244
        );
245
        check("reject 10,11,12,13,15", "accept 1-9,14,16-65535", &[], &[]);
246
        check(
247
            "reject 1-65535",
248
            "reject 1-65535",
249
            &[],
250
            &[1, 300, 301, 500, 10001, 65535],
251
        );
252
    }
253

            
254
    #[test]
255
    fn test_bad() {
256
        for s in &[
257
            "ignore 1-10",
258
            "allow 1-100",
259
            "accept",
260
            "reject",
261
            "accept x-y",
262
            "accept 1-20,19-30",
263
            "accept 1-20,20-30",
264
            "reject 1,1,1,1",
265
            "reject 1,2,foo,4",
266
            "reject 5,4,3,2",
267
        ] {
268
            assert!(s.parse::<PortPolicy>().is_err());
269
        }
270
    }
271

            
272
    #[test]
273
    fn test_from_allowed_port_list() {
274
        let mut cases = vec![];
275
        cases.push((vec![1, 2, 3, 7, 8, 10, 42], "accept 1-3,7-8,10,42"));
276
        cases.push((vec![1, 3, 5], "accept 1,3,5"));
277
        cases.push((vec![1, 2, 3, 4], "accept 1-4"));
278
        cases.push((vec![65535], "accept 65535"));
279
        cases.push((vec![], "reject 1-65535"));
280

            
281
        for (port_list, port_range) in cases {
282
            let expected = port_range.parse::<PortPolicy>().unwrap();
283
            for port_list in port_list.iter().copied().permutations(port_list.len()) {
284
                assert_eq!(PortPolicy::from_allowed_port_list(port_list), expected,);
285
            }
286
        }
287
    }
288
}