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