Skip to main content

tor_proto/congestion/
params.rs

1//! Define the congestion control parameters needed for the algorithms.
2//!
3//! All of these values are taken from the consensus. And so the details of these values can be
4//! found in section 6.5.1. of proposal 324.
5
6use caret::caret_int;
7use derive_builder::Builder;
8
9use tor_config::{ConfigBuildError, impl_standard_builder};
10use tor_units::Percentage;
11
12/// Fixed window parameters that are for the SENDME v0 world of fixed congestion window.
13#[non_exhaustive]
14#[derive(Builder, Copy, Clone, Debug, amplify::Getters)]
15#[builder(build_fn(error = "ConfigBuildError"))]
16pub struct FixedWindowParams {
17    /// Circuit window starting point. From the "circwindow" param.
18    #[getter(as_copy)]
19    circ_window_start: u16,
20    /// Circuit window minimum value.
21    #[getter(as_copy)]
22    circ_window_min: u16,
23    /// Circuit window maximum value.
24    #[getter(as_copy)]
25    circ_window_max: u16,
26}
27impl_standard_builder! { FixedWindowParams: !Deserialize + !Default }
28
29impl FixedWindowParams {
30    #[cfg(test)]
31    // These have been copied from C-tor.
32    pub(crate) fn defaults_for_tests() -> Self {
33        Self {
34            circ_window_start: 1000,
35            circ_window_min: 100,
36            circ_window_max: 1000,
37        }
38    }
39}
40
41/// Vegas queuing parameters taken from the consensus only which are different depending if the
42/// circuit is an onion service one, an exit or used for SBWS.
43#[non_exhaustive]
44#[derive(Copy, Clone, Debug, amplify::Getters)]
45pub struct VegasQueueParams {
46    /// Alpha parameter is used to know when to increase the window.
47    #[getter(as_copy)]
48    alpha: u32,
49    /// Beta parameter is used to know when to decrease the window
50    #[getter(as_copy)]
51    beta: u32,
52    /// Delta parameter is used as an indicator to drop the window to this considering the current
53    /// BDP value and increment.
54    #[getter(as_copy)]
55    delta: u32,
56    /// Gamma parameter is only used in slow start and used to know when to increase or adjust the
57    /// window with the BDP.
58    #[getter(as_copy)]
59    gamma: u32,
60    /// Parameter describe the RFC3742 'cap', after which congestion window increments are reduced.
61    /// INT32_MAX disables
62    #[getter(as_copy)]
63    ss_cwnd_cap: u32,
64}
65
66/// Used when we parse at once all the specific circuit type vegas queue parameters. They are
67/// bundled in a 5-tuple and transformed with this.
68impl From<(u32, u32, u32, u32, u32)> for VegasQueueParams {
69    fn from(v: (u32, u32, u32, u32, u32)) -> Self {
70        Self {
71            alpha: v.0,
72            beta: v.1,
73            delta: v.2,
74            gamma: v.3,
75            ss_cwnd_cap: v.4,
76        }
77    }
78}
79
80/// Vegas algorithm parameters taken from the consensus.
81#[non_exhaustive]
82#[derive(Builder, Copy, Clone, Debug, amplify::Getters)]
83#[builder(build_fn(error = "ConfigBuildError"))]
84pub struct VegasParams {
85    /// The amount of queued cells that Vegas can tolerate before reacting.
86    cell_in_queue_params: VegasQueueParams,
87    /// A hard-max on the congestion window in Slow Start.
88    #[getter(as_copy)]
89    ss_cwnd_max: u32,
90    /// This parameter defines the integer number of 'cc_sendme_inc' multiples
91    /// of gap allowed between inflight and cwnd, to still declare the cwnd full.
92    #[getter(as_copy)]
93    cwnd_full_gap: u32,
94    /// This parameter defines a low watermark in percent.
95    cwnd_full_min_pct: Percentage<u32>,
96    /// This parameter governs how often a cwnd must be full.
97    #[getter(as_copy)]
98    cwnd_full_per_cwnd: u32,
99}
100impl_standard_builder! { VegasParams: !Deserialize + !Default }
101
102impl VegasParams {
103    #[cfg(test)]
104    // These have been copied from spec (prop324).
105    pub(crate) fn defaults_for_tests() -> Self {
106        // The OUTBUF_CELLS size from prop324.
107        const OC: u32 = 62;
108        Self {
109            cell_in_queue_params: (3 * OC, 4 * OC, 5 * OC, 3 * OC, 600).into(),
110            ss_cwnd_max: 5000,
111            cwnd_full_gap: 4,
112            cwnd_full_min_pct: Percentage::new(25),
113            cwnd_full_per_cwnd: 1,
114        }
115    }
116}
117
118/// The different congestion control algorithms. Each contain their parameters taken from the
119/// consensus.
120#[non_exhaustive]
121#[derive(Clone, Debug, strum::EnumDiscriminants)]
122// No need for the discriminants to be public at the moment,
123// and we might want to rename it if it does become public.
124#[strum_discriminants(vis(pub(crate)))]
125pub enum Algorithm {
126    /// Fixed window algorithm.
127    FixedWindow(FixedWindowParams),
128    /// Vegas algorithm.
129    Vegas(VegasParams),
130}
131
132impl Algorithm {
133    /// Return true if this algorithm can be used along with CGO.
134    ///
135    /// CGO requires the V1 relay cell format, where every relay command
136    /// implies either the presence or absence of a StreamID.
137    /// But that format is not compatible with (legacy) stream-level SENDME messages
138    /// for flow control.
139    pub(crate) fn compatible_with_cgo(&self) -> bool {
140        match self {
141            Algorithm::FixedWindow(_) => false,
142            Algorithm::Vegas(_) => true,
143        }
144    }
145}
146
147caret_int! {
148    /// Congestion control algorithm types defined by numerical values. See "cc_alg" in proposal
149    /// 324 section 6.5.1 for the supported values.
150    ///
151    /// This is a i32 so it is the same type as the consensus supported value type.
152    pub struct AlgorithmType(i32) {
153        /// Fixed window algorithm.
154        FIXED_WINDOW = 0,
155        /// Vegas algorithm.
156        VEGAS = 2,
157    }
158}
159
160/// The round trip estimator parameters taken from consensus and used to estimate the round trip
161/// time on a circuit.
162#[non_exhaustive]
163#[derive(Builder, Clone, Debug, amplify::Getters)]
164#[builder(build_fn(error = "ConfigBuildError"))]
165pub struct RoundTripEstimatorParams {
166    /// The "N" parameter in N-EWMA smoothing of RTT and/or bandwidth estimation, specified as a
167    /// percentage of the number of SENDME acks in a congestion window.
168    ///
169    /// A percentage over 100% indicates smoothing with more than one congestion window's worth
170    /// of SENDMEs.
171    ewma_cwnd_pct: Percentage<u32>,
172    /// The maximum value of the "N" parameter in N-EWMA smoothing of RTT and/or bandwidth
173    /// estimation.
174    #[getter(as_copy)]
175    ewma_max: u32,
176    /// The maximum value of the "N" parameter in N-EWMA smoothing of RTT and/or bandwidth
177    /// estimation but in Slow Start.
178    #[getter(as_copy)]
179    ewma_ss_max: u32,
180    /// Describes a percentile average between min and current ewma, for use to reset RTT_min, when
181    /// the congestion window hits cwnd_min.
182    rtt_reset_pct: Percentage<u32>,
183}
184impl_standard_builder! { RoundTripEstimatorParams: !Deserialize + !Default }
185
186impl RoundTripEstimatorParams {
187    #[cfg(test)]
188    // These have been copied from spec (prop324).
189    pub(crate) fn defaults_for_tests() -> Self {
190        Self {
191            ewma_cwnd_pct: Percentage::new(50),
192            ewma_max: 10,
193            ewma_ss_max: 2,
194            rtt_reset_pct: Percentage::new(100),
195        }
196    }
197}
198
199/// The parameters of what constitute a congestion window. This is used by all congestion control
200/// algorithms as in it is not specific to an algorithm.
201#[non_exhaustive]
202#[derive(Builder, Clone, Copy, Debug, amplify::Getters)]
203#[builder(build_fn(error = "ConfigBuildError"))]
204pub struct CongestionWindowParams {
205    /// Initial size of the congestion window.
206    #[getter(as_copy)]
207    cwnd_init: u32,
208    /// Percent of cwnd to increment by during slow start.
209    cwnd_inc_pct_ss: Percentage<u32>,
210    /// Number of cells to increment cwnd by during steady state.
211    #[getter(as_copy)]
212    cwnd_inc: u32,
213    /// Number of times per congestion window to update based on congestion signals.
214    #[getter(as_copy)]
215    cwnd_inc_rate: u32,
216    /// Minimum congestion window (must be at least sendme_inc)
217    #[getter(as_copy)]
218    cwnd_min: u32,
219    /// Maximum congestion window
220    #[getter(as_copy)]
221    cwnd_max: u32,
222    /// The SENDME increment as in the number of cells to ACK with every SENDME. This is coming
223    /// from the consensus and negotiated during circuit setup.
224    #[getter(as_copy)]
225    sendme_inc: u32,
226}
227impl_standard_builder! { CongestionWindowParams: !Deserialize + !Default}
228
229impl CongestionWindowParams {
230    /// Set the `sendme_inc` value.
231    ///
232    /// This is used to override the default increment value from when this was constructed with a
233    /// [`CongestionWindowParamsBuilder`].
234    /// Typically the default when built should be from the network parameters from the consensus.
235    pub(crate) fn set_sendme_inc(&mut self, inc: u8) {
236        self.sendme_inc = u32::from(inc);
237    }
238
239    #[cfg(test)]
240    // These have been copied from spec (prop324).
241    pub(crate) fn defaults_for_tests() -> Self {
242        Self {
243            cwnd_init: 4 * 31,
244            cwnd_inc_pct_ss: Percentage::new(50),
245            cwnd_inc: 31,
246            cwnd_inc_rate: 1,
247            cwnd_min: 31,
248            cwnd_max: u32::MAX,
249            sendme_inc: 31,
250        }
251    }
252}
253
254/// Global congestion control parameters taken from consensus. These are per-circuit.
255#[non_exhaustive]
256#[derive(Builder, Clone, Debug, amplify::Getters)]
257#[builder(build_fn(error = "ConfigBuildError"))]
258pub struct CongestionControlParams {
259    /// The congestion control algorithm to use.
260    alg: Algorithm,
261    /// Parameters to the fallback fixed-window algorithm, which we use
262    /// when the one in `alg` is not supported by a given relay.
263    ///
264    /// It is put in here because by the time we do path selection, we don't have access to the
265    /// consensus and so we have to keep our fallback ready.
266    fixed_window_params: FixedWindowParams,
267    /// Congestion window parameters.
268    #[getter(as_mut)]
269    #[getter(as_copy)]
270    cwnd_params: CongestionWindowParams,
271    /// RTT calculation parameters.
272    rtt_params: RoundTripEstimatorParams,
273}
274impl_standard_builder! { CongestionControlParams: !Deserialize + !Default }
275
276impl CongestionControlParams {
277    /// Return true iff congestion control is enabled that is the algorithm is anything other than
278    /// the fixed window SENDMEs.
279    ///
280    /// C-tor ref: congestion_control_enabled()
281    pub(crate) fn is_enabled(&self) -> bool {
282        !matches!(self.alg(), Algorithm::FixedWindow(_))
283    }
284
285    /// Make these parameters to use the fallback algorithm. This can't be reversed.
286    pub(crate) fn use_fallback_alg(&mut self) {
287        self.alg = Algorithm::FixedWindow(self.fixed_window_params);
288    }
289}
290
291/// Return true iff the given sendme increment is valid with regards to the value in the circuit
292/// parameters that is taken from the consensus.
293pub(crate) fn is_sendme_inc_valid(inc: u8, params: &CongestionControlParams) -> bool {
294    // Ease our lives a bit because the consensus value is u32.
295    let inc_u32 = u32::from(inc);
296    // A consensus value of 1 would allow this sendme increment to be 0 and thus
297    // we have to special case it before evaluating.
298    if inc == 0 {
299        return false;
300    }
301    let inc_consensus = params.cwnd_params().sendme_inc();
302    // See prop324 section 10.3
303    if inc_u32 > (inc_consensus.saturating_add(1)) || inc_u32 < (inc_consensus.saturating_sub(1)) {
304        return false;
305    }
306    true
307}
308
309#[cfg(test)]
310mod test {
311    use crate::{
312        ccparams::is_sendme_inc_valid, congestion::test_utils::params::build_cc_vegas_params,
313    };
314
315    #[test]
316    fn test_sendme_inc_valid() {
317        let params = build_cc_vegas_params();
318        let ref_inc = params.cwnd_params().sendme_inc() as u8;
319
320        // In range.
321        assert!(is_sendme_inc_valid(ref_inc, &params));
322        assert!(is_sendme_inc_valid(ref_inc + 1, &params));
323        assert!(is_sendme_inc_valid(ref_inc - 1, &params));
324        // Out of range.
325        assert!(!is_sendme_inc_valid(0, &params));
326        assert!(!is_sendme_inc_valid(ref_inc + 2, &params));
327        assert!(!is_sendme_inc_valid(ref_inc - 2, &params));
328    }
329}