1
//! Tools for determining what circuits to preemptively build.
2

            
3
use crate::{PreemptiveCircuitConfig, TargetCircUsage, TargetPort};
4
use std::collections::HashMap;
5
use std::sync::Arc;
6
use std::time::Instant;
7
use tracing::warn;
8

            
9
/// Predicts what circuits might be used in future based on past activity, and suggests
10
/// circuits to preemptively build as a result.
11
pub(crate) struct PreemptiveCircuitPredictor {
12
    /// A map of every exit port we've observed being used (or `None` if we observed an exit being
13
    /// used to resolve DNS names instead of building a stream), to the last time we encountered
14
    /// such usage.
15
    // TODO(nickm): Let's have a mechanism for cleaning this out from time to time.
16
    usages: HashMap<Option<TargetPort>, Instant>,
17

            
18
    /// Configuration for this predictor.
19
    config: tor_config::MutCfg<PreemptiveCircuitConfig>,
20
}
21

            
22
impl PreemptiveCircuitPredictor {
23
    /// Create a new predictor, starting out with a set of ports we think are likely to be used.
24
32
    pub(crate) fn new(config: PreemptiveCircuitConfig) -> Self {
25
32
        let mut usages = HashMap::new();
26
89
        for port in &config.initial_predicted_ports {
27
57
            // TODO(nickm) should this be IPv6? Should we have a way to configure IPv6 initial ports?
28
57
            usages.insert(Some(TargetPort::ipv4(*port)), Instant::now());
29
57
        }
30

            
31
        // We want to build circuits for resolving DNS, too.
32
32
        usages.insert(None, Instant::now());
33
32

            
34
32
        Self {
35
32
            usages,
36
32
            config: config.into(),
37
32
        }
38
32
    }
39

            
40
    /// Return the configuration for this PreemptiveCircuitPredictor.
41
6
    pub(crate) fn config(&self) -> Arc<PreemptiveCircuitConfig> {
42
6
        self.config.get()
43
6
    }
44

            
45
    /// Replace the current configuration for this PreemptiveCircuitPredictor
46
    /// with `new_config`.
47
    pub(crate) fn set_config(&self, mut new_config: PreemptiveCircuitConfig) {
48
        self.config.map_and_replace(|cfg| {
49
            // Force this to stay the same, since it can't meaningfully be changed.
50
            new_config.initial_predicted_ports = cfg.initial_predicted_ports.clone();
51
            new_config
52
        });
53
    }
54

            
55
    /// Make some predictions for what circuits should be built.
56
6
    pub(crate) fn predict(&self) -> Vec<TargetCircUsage> {
57
6
        let config = self.config();
58
6
        let now = Instant::now();
59
6
        let circs = config.min_exit_circs_for_port;
60
6
        self.usages
61
6
            .iter()
62
9
            .filter(|(_, &time)| {
63
9
                time.checked_add(config.prediction_lifetime)
64
9
                    .map(|t| t > now)
65
9
                    .unwrap_or_else(|| {
66
                        // FIXME(eta): this is going to be a bit noisy if it triggers, but that's better
67
                        //             than panicking or silently doing the wrong thing?
68
                        warn!("failed to represent preemptive circuit prediction lifetime as an Instant");
69
                        false
70
9
                    })
71
9
            })
72
8
            .map(|(&port, _)| TargetCircUsage::Preemptive { port, circs })
73
6
            .collect()
74
6
    }
75

            
76
    /// Note the use of a new port at the provided `time`.
77
    ///
78
    /// # Limitations
79
    ///
80
    /// This function assumes that the `time` values it receives are
81
    /// monotonically increasing.
82
2
    pub(crate) fn note_usage(&mut self, port: Option<TargetPort>, time: Instant) {
83
2
        self.usages.insert(port, time);
84
2
    }
85
}
86

            
87
#[cfg(test)]
88
mod test {
89
    #![allow(clippy::unwrap_used)]
90
    use crate::{PreemptiveCircuitConfig, PreemptiveCircuitPredictor, TargetCircUsage, TargetPort};
91
    use std::time::{Duration, Instant};
92

            
93
    #[test]
94
    fn predicts_starting_ports() {
95
        let cfg = PreemptiveCircuitConfig::builder()
96
            .initial_predicted_ports(vec![])
97
            .prediction_lifetime(Duration::from_secs(2))
98
            .build()
99
            .unwrap();
100
        let predictor = PreemptiveCircuitPredictor::new(cfg);
101

            
102
        let mut results = predictor.predict();
103
        results.sort();
104
        assert_eq!(
105
            predictor.predict(),
106
            vec![TargetCircUsage::Preemptive {
107
                port: None,
108
                circs: 2
109
            }]
110
        );
111

            
112
        let cfg = PreemptiveCircuitConfig::builder()
113
            .initial_predicted_ports(vec![80])
114
            .prediction_lifetime(Duration::from_secs(2))
115
            .build()
116
            .unwrap();
117

            
118
        let predictor = PreemptiveCircuitPredictor::new(cfg);
119

            
120
        let mut results = predictor.predict();
121
        results.sort();
122
        assert_eq!(
123
            results,
124
            vec![
125
                TargetCircUsage::Preemptive {
126
                    port: None,
127
                    circs: 2
128
                },
129
                TargetCircUsage::Preemptive {
130
                    port: Some(TargetPort::ipv4(80)),
131
                    circs: 2
132
                },
133
            ]
134
        );
135
    }
136

            
137
    #[test]
138
    fn predicts_used_ports() {
139
        let cfg = PreemptiveCircuitConfig::builder()
140
            .initial_predicted_ports(vec![])
141
            .prediction_lifetime(Duration::from_secs(2))
142
            .build()
143
            .unwrap();
144
        let mut predictor = PreemptiveCircuitPredictor::new(cfg);
145

            
146
        assert_eq!(
147
            predictor.predict(),
148
            vec![TargetCircUsage::Preemptive {
149
                port: None,
150
                circs: 2
151
            }]
152
        );
153

            
154
        predictor.note_usage(Some(TargetPort::ipv4(1234)), Instant::now());
155

            
156
        let mut results = predictor.predict();
157
        results.sort();
158
        assert_eq!(
159
            results,
160
            vec![
161
                TargetCircUsage::Preemptive {
162
                    port: None,
163
                    circs: 2
164
                },
165
                TargetCircUsage::Preemptive {
166
                    port: Some(TargetPort::ipv4(1234)),
167
                    circs: 2
168
                }
169
            ]
170
        );
171
    }
172

            
173
    #[test]
174
    fn does_not_predict_old_ports() {
175
        let cfg = PreemptiveCircuitConfig::builder()
176
            .initial_predicted_ports(vec![])
177
            .prediction_lifetime(Duration::from_secs(2))
178
            .build()
179
            .unwrap();
180
        let mut predictor = PreemptiveCircuitPredictor::new(cfg);
181
        let more_than_an_hour_ago = Instant::now() - Duration::from_secs(60 * 60 + 1);
182

            
183
        predictor.note_usage(Some(TargetPort::ipv4(2345)), more_than_an_hour_ago);
184

            
185
        assert_eq!(
186
            predictor.predict(),
187
            vec![TargetCircUsage::Preemptive {
188
                port: None,
189
                circs: 2
190
            }]
191
        );
192
    }
193
}