1
//! Code for estimating good values for circuit timeouts.
2
//!
3
//! We need good circuit timeouts for two reasons: first, they help
4
//! user experience.  If user wait too long for their circuits, or if
5
//! they use exceptionally slow circuits, then Tor will feel really
6
//! slow.  Second, these timeouts are actually a security
7
//! property.
8
// TODO(nickm): explain why!
9

            
10
use std::time::Duration;
11

            
12
pub(crate) mod estimator;
13
pub(crate) mod pareto;
14
pub(crate) mod readonly;
15

            
16
pub(crate) use estimator::Estimator;
17

            
18
/// An object that calculates circuit timeout thresholds from the history
19
/// of circuit build times.
20
pub(crate) trait TimeoutEstimator {
21
    /// Record that a given circuit hop has completed.
22
    ///
23
    /// The `hop` number is a zero-indexed value for which hop just completed.
24
    ///
25
    /// The `delay` value is the amount of time after we first launched the
26
    /// circuit.
27
    ///
28
    /// If this is the last hop of the circuit, then `is_last` is true.
29
    fn note_hop_completed(&mut self, hop: u8, delay: Duration, is_last: bool);
30

            
31
    /// Record that a circuit failed to complete because it took too long.
32
    ///
33
    /// The `hop` number is a the number of hops that were successfully
34
    /// completed.
35
    ///
36
    /// The `delay` number is the amount of time after we first launched the
37
    /// circuit.
38
    fn note_circ_timeout(&mut self, hop: u8, delay: Duration);
39

            
40
    /// Return the current estimation for how long we should wait for a given
41
    /// [`Action`] to complete.
42
    ///
43
    /// This function should return a 2-tuple of `(timeout, abandon)`
44
    /// durations.  After `timeout` has elapsed since circuit launch,
45
    /// the circuit should no longer be used, but we should still keep
46
    /// building it in order see how long it takes.  After `abandon`
47
    /// has elapsed since circuit launch, the circuit should be
48
    /// abandoned completely.
49
    fn timeouts(&mut self, action: &Action) -> (Duration, Duration);
50

            
51
    /// Return true if we're currently trying to learn more timeouts
52
    /// by launching testing circuits.
53
    fn learning_timeouts(&self) -> bool;
54

            
55
    /// Replace the network parameters used by this estimator (if any)
56
    /// with ones derived from `params`.
57
    fn update_params(&mut self, params: &tor_netdir::params::NetParameters);
58

            
59
    /// Construct a new ParetoTimeoutState to represent the current state
60
    /// of this estimator, if it is possible to store the state to disk.
61
    ///
62
    /// TODO: change the type used for the state.
63
    fn build_state(&mut self) -> Option<pareto::ParetoTimeoutState>;
64
}
65

            
66
/// A possible action for which we can try to estimate a timeout.
67
#[non_exhaustive]
68
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
69
pub(crate) enum Action {
70
    /// Build a circuit of a given length.
71
    BuildCircuit {
72
        /// The length of the circuit to construct.
73
        ///
74
        /// (A 0-hop circuit takes no time.)
75
        length: usize,
76
    },
77
    /// Extend a given circuit from one length to another.
78
    #[allow(dead_code)]
79
    ExtendCircuit {
80
        /// The current length of the circuit.
81
        initial_length: usize,
82
        /// The new length of the circuit.
83
        ///
84
        /// (Must be greater than `initial_length`.)
85
        final_length: usize,
86
    },
87
    /// Send a message to the last hop of a circuit and receive a response
88
    #[allow(dead_code)]
89
    RoundTrip {
90
        /// The length of the circuit.
91
        length: usize,
92
    },
93
}
94

            
95
impl Action {
96
    /// Compute a scaling factor for a given `Action`
97
    ///
98
    /// These values are arbitrary numbers such that if the correct
99
    /// timeout for an Action `a1` is `t`, then the correct timeout
100
    /// for an action `a2` is `t * a2.timeout_scale() /
101
    /// a1.timeout_scale()`.
102
    ///
103
    /// This function can return garbage if the circuit length is larger
104
    /// than actually supported on the Tor network.
105
68
    fn timeout_scale(&self) -> usize {
106
68
        /// An arbitrary value to use to prevent overflow.
107
68
        const MAX_LEN: usize = 64;
108
68

            
109
68
        /// Return the scale value for building a `len`-hop circuit.
110
69
        fn build_scale(len: usize) -> usize {
111
69
            len * (len + 1) / 2
112
69
        }
113
68
        // This is based on an approximation from Tor's
114
68
        // `circuit_expire_building()` code.
115
68
        //
116
68
        // The general principle here is that when you're waiting for
117
68
        // a round-trip through a circuit through three relays
118
68
        // 'a--b--c', it takes three units of time.  Thus, building a
119
68
        // three hop circuit requires you to send a message through
120
68
        // "a", then through "a--b", then through "a--b--c", for a
121
68
        // total of 6.
122
68
        //
123
68
        // This is documented in path-spec.txt under "Calculating
124
68
        // timeouts thresholds for circuits of different lengths".
125
68
        match *self {
126
65
            Action::BuildCircuit { length } => {
127
65
                // We never down-scale our estimates for building a circuit
128
65
                // below a 3-hop length.
129
65
                //
130
65
                // TODO: This is undocumented.
131
65
                let length = length.clamp(3, MAX_LEN);
132
65
                build_scale(length)
133
            }
134
            Action::ExtendCircuit {
135
2
                initial_length,
136
2
                final_length,
137
2
            } => {
138
2
                let initial_length = initial_length.clamp(0, MAX_LEN);
139
2
                let final_length = final_length.clamp(initial_length, MAX_LEN);
140
2
                build_scale(final_length) - build_scale(initial_length)
141
            }
142
1
            Action::RoundTrip { length } => length.clamp(0, MAX_LEN),
143
        }
144
68
    }
145
}
146

            
147
/// A safe variant of [`Duration::mul_f64`] that never panics.
148
///
149
/// For infinite or NaN or negative multipliers, the results might be
150
/// nonsensical, but at least they won't be a panic.
151
45
fn mul_duration_f64_saturating(d: Duration, mul: f64) -> Duration {
152
45
    let secs = d.as_secs_f64() * mul;
153
45
    // At this point I'd like to use Duration::try_from_secs_f64, but
154
45
    // that isn't stable yet. :p
155
45
    if secs.is_finite() && secs >= 0.0 {
156
        // We rely on the property that `f64 as uNN` is saturating.
157
41
        let seconds = secs.trunc() as u64;
158
41
        let nanos = if seconds == u64::MAX {
159
1
            0 // prevent any possible overflow.
160
        } else {
161
40
            (secs.fract() * 1e9) as u32
162
        };
163
41
        Duration::new(seconds, nanos)
164
    } else {
165
4
        Duration::from_secs(1)
166
    }
167
45
}
168

            
169
#[cfg(test)]
170
mod test {
171
    use super::*;
172

            
173
    #[test]
174
    fn action_scale_values() {
175
        assert_eq!(Action::BuildCircuit { length: 1 }.timeout_scale(), 6);
176
        assert_eq!(Action::BuildCircuit { length: 2 }.timeout_scale(), 6);
177
        assert_eq!(Action::BuildCircuit { length: 3 }.timeout_scale(), 6);
178
        assert_eq!(Action::BuildCircuit { length: 4 }.timeout_scale(), 10);
179
        assert_eq!(Action::BuildCircuit { length: 5 }.timeout_scale(), 15);
180

            
181
        assert_eq!(
182
            Action::ExtendCircuit {
183
                initial_length: 3,
184
                final_length: 4
185
            }
186
            .timeout_scale(),
187
            4
188
        );
189
        assert_eq!(
190
            Action::ExtendCircuit {
191
                initial_length: 99,
192
                final_length: 4
193
            }
194
            .timeout_scale(),
195
            0
196
        );
197

            
198
        assert_eq!(Action::RoundTrip { length: 3 }.timeout_scale(), 3);
199
    }
200

            
201
    #[test]
202
    fn test_mul_duration() {
203
        // This is wrong because of leap years, but we'll fake it.
204
        let mega_year = Duration::from_secs(86400 * 365 * 1000 * 1000);
205

            
206
        // Multiply by zero.
207
        let v = mul_duration_f64_saturating(mega_year, 0.0);
208
        assert!(v.is_zero());
209

            
210
        // Multiply by one.
211
        assert_eq!(mul_duration_f64_saturating(mega_year, 1.0), mega_year);
212

            
213
        // Divide by 1000.
214
        let v = mul_duration_f64_saturating(mega_year, 1.0 / 1000.0);
215
        let s = v.as_secs_f64();
216
        assert!((s - (mega_year.as_secs_f64() / 1000.0)).abs() < 0.1);
217

            
218
        // This would overflow if we were using mul_f64.
219
        let v = mul_duration_f64_saturating(mega_year, 1e9);
220
        assert!(v > mega_year * 1000);
221

            
222
        // This would underflow.
223
        let v = mul_duration_f64_saturating(mega_year, -1.0);
224
        assert_eq!(v, Duration::from_secs(1));
225

            
226
        // These are just silly.
227
        let v = mul_duration_f64_saturating(mega_year, f64::INFINITY);
228
        assert_eq!(v, Duration::from_secs(1));
229
        let v = mul_duration_f64_saturating(mega_year, f64::NEG_INFINITY);
230
        assert_eq!(v, Duration::from_secs(1));
231
        let v = mul_duration_f64_saturating(mega_year, f64::NAN);
232
        assert_eq!(v, Duration::from_secs(1));
233
    }
234
}