1 #include "internal/quic_cc.h"
2 #include "internal/quic_types.h"
3 #include "internal/safe_math.h"
4
5 OSSL_SAFE_MATH_UNSIGNED(u64, uint64_t)
6
7 typedef struct ossl_cc_newreno_st {
8 /* Dependencies. */
9 OSSL_TIME (*now_cb)(void *arg);
10 void *now_cb_arg;
11
12 /* 'Constants' (which we allow to be configurable). */
13 uint64_t k_init_wnd, k_min_wnd;
14 uint32_t k_loss_reduction_factor_num, k_loss_reduction_factor_den;
15 uint32_t persistent_cong_thresh;
16
17 /* State. */
18 size_t max_dgram_size;
19 uint64_t bytes_in_flight, cong_wnd, slow_start_thresh, bytes_acked;
20 OSSL_TIME cong_recovery_start_time;
21
22 /* Unflushed state during multiple on-loss calls. */
23 int processing_loss; /* 1 if not flushed */
24 OSSL_TIME tx_time_of_last_loss;
25
26 /* Diagnostic state. */
27 int in_congestion_recovery;
28
29 /* Diagnostic output locations. */
30 size_t *p_diag_max_dgram_payload_len;
31 uint64_t *p_diag_cur_cwnd_size;
32 uint64_t *p_diag_min_cwnd_size;
33 uint64_t *p_diag_cur_bytes_in_flight;
34 uint32_t *p_diag_cur_state;
35 } OSSL_CC_NEWRENO;
36
37 #define MIN_MAX_INIT_WND_SIZE 14720 /* RFC 9002 s. 7.2 */
38
39 /* TODO(QUIC FUTURE): Pacing support. */
40
41 static void newreno_set_max_dgram_size(OSSL_CC_NEWRENO *nr,
42 size_t max_dgram_size);
43 static void newreno_update_diag(OSSL_CC_NEWRENO *nr);
44
45 static void newreno_reset(OSSL_CC_DATA *cc);
46
newreno_new(OSSL_TIME (* now_cb)(void * arg),void * now_cb_arg)47 static OSSL_CC_DATA *newreno_new(OSSL_TIME (*now_cb)(void *arg),
48 void *now_cb_arg)
49 {
50 OSSL_CC_NEWRENO *nr;
51
52 if ((nr = OPENSSL_zalloc(sizeof(*nr))) == NULL)
53 return NULL;
54
55 nr->now_cb = now_cb;
56 nr->now_cb_arg = now_cb_arg;
57
58 newreno_set_max_dgram_size(nr, QUIC_MIN_INITIAL_DGRAM_LEN);
59 newreno_reset((OSSL_CC_DATA *)nr);
60
61 return (OSSL_CC_DATA *)nr;
62 }
63
newreno_free(OSSL_CC_DATA * cc)64 static void newreno_free(OSSL_CC_DATA *cc)
65 {
66 OPENSSL_free(cc);
67 }
68
newreno_set_max_dgram_size(OSSL_CC_NEWRENO * nr,size_t max_dgram_size)69 static void newreno_set_max_dgram_size(OSSL_CC_NEWRENO *nr,
70 size_t max_dgram_size)
71 {
72 size_t max_init_wnd;
73 int is_reduced = (max_dgram_size < nr->max_dgram_size);
74
75 nr->max_dgram_size = max_dgram_size;
76
77 max_init_wnd = 2 * max_dgram_size;
78 if (max_init_wnd < MIN_MAX_INIT_WND_SIZE)
79 max_init_wnd = MIN_MAX_INIT_WND_SIZE;
80
81 nr->k_init_wnd = 10 * max_dgram_size;
82 if (nr->k_init_wnd > max_init_wnd)
83 nr->k_init_wnd = max_init_wnd;
84
85 nr->k_min_wnd = 2 * max_dgram_size;
86
87 if (is_reduced)
88 nr->cong_wnd = nr->k_init_wnd;
89
90 newreno_update_diag(nr);
91 }
92
newreno_reset(OSSL_CC_DATA * cc)93 static void newreno_reset(OSSL_CC_DATA *cc)
94 {
95 OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
96
97 nr->k_loss_reduction_factor_num = 1;
98 nr->k_loss_reduction_factor_den = 2;
99 nr->persistent_cong_thresh = 3;
100
101 nr->cong_wnd = nr->k_init_wnd;
102 nr->bytes_in_flight = 0;
103 nr->bytes_acked = 0;
104 nr->slow_start_thresh = UINT64_MAX;
105 nr->cong_recovery_start_time = ossl_time_zero();
106
107 nr->processing_loss = 0;
108 nr->tx_time_of_last_loss = ossl_time_zero();
109 nr->in_congestion_recovery = 0;
110 }
111
newreno_set_input_params(OSSL_CC_DATA * cc,const OSSL_PARAM * params)112 static int newreno_set_input_params(OSSL_CC_DATA *cc, const OSSL_PARAM *params)
113 {
114 OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
115 const OSSL_PARAM *p;
116 size_t value;
117
118 p = OSSL_PARAM_locate_const(params, OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN);
119 if (p != NULL) {
120 if (!OSSL_PARAM_get_size_t(p, &value))
121 return 0;
122 if (value < QUIC_MIN_INITIAL_DGRAM_LEN)
123 return 0;
124
125 newreno_set_max_dgram_size(nr, value);
126 }
127
128 return 1;
129 }
130
bind_diag(OSSL_PARAM * params,const char * param_name,size_t len,void ** pp)131 static int bind_diag(OSSL_PARAM *params, const char *param_name, size_t len,
132 void **pp)
133 {
134 const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, param_name);
135
136 *pp = NULL;
137
138 if (p == NULL)
139 return 1;
140
141 if (p->data_type != OSSL_PARAM_UNSIGNED_INTEGER
142 || p->data_size != len)
143 return 0;
144
145 *pp = p->data;
146 return 1;
147 }
148
newreno_bind_diagnostic(OSSL_CC_DATA * cc,OSSL_PARAM * params)149 static int newreno_bind_diagnostic(OSSL_CC_DATA *cc, OSSL_PARAM *params)
150 {
151 OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
152 size_t *new_p_max_dgram_payload_len;
153 uint64_t *new_p_cur_cwnd_size;
154 uint64_t *new_p_min_cwnd_size;
155 uint64_t *new_p_cur_bytes_in_flight;
156 uint32_t *new_p_cur_state;
157
158 if (!bind_diag(params, OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN,
159 sizeof(size_t), (void **)&new_p_max_dgram_payload_len)
160 || !bind_diag(params, OSSL_CC_OPTION_CUR_CWND_SIZE,
161 sizeof(uint64_t), (void **)&new_p_cur_cwnd_size)
162 || !bind_diag(params, OSSL_CC_OPTION_MIN_CWND_SIZE,
163 sizeof(uint64_t), (void **)&new_p_min_cwnd_size)
164 || !bind_diag(params, OSSL_CC_OPTION_CUR_BYTES_IN_FLIGHT,
165 sizeof(uint64_t), (void **)&new_p_cur_bytes_in_flight)
166 || !bind_diag(params, OSSL_CC_OPTION_CUR_STATE,
167 sizeof(uint32_t), (void **)&new_p_cur_state))
168 return 0;
169
170 if (new_p_max_dgram_payload_len != NULL)
171 nr->p_diag_max_dgram_payload_len = new_p_max_dgram_payload_len;
172
173 if (new_p_cur_cwnd_size != NULL)
174 nr->p_diag_cur_cwnd_size = new_p_cur_cwnd_size;
175
176 if (new_p_min_cwnd_size != NULL)
177 nr->p_diag_min_cwnd_size = new_p_min_cwnd_size;
178
179 if (new_p_cur_bytes_in_flight != NULL)
180 nr->p_diag_cur_bytes_in_flight = new_p_cur_bytes_in_flight;
181
182 if (new_p_cur_state != NULL)
183 nr->p_diag_cur_state = new_p_cur_state;
184
185 newreno_update_diag(nr);
186 return 1;
187 }
188
unbind_diag(OSSL_PARAM * params,const char * param_name,void ** pp)189 static void unbind_diag(OSSL_PARAM *params, const char *param_name,
190 void **pp)
191 {
192 const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, param_name);
193
194 if (p != NULL)
195 *pp = NULL;
196 }
197
newreno_unbind_diagnostic(OSSL_CC_DATA * cc,OSSL_PARAM * params)198 static int newreno_unbind_diagnostic(OSSL_CC_DATA *cc, OSSL_PARAM *params)
199 {
200 OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
201
202 unbind_diag(params, OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN,
203 (void **)&nr->p_diag_max_dgram_payload_len);
204 unbind_diag(params, OSSL_CC_OPTION_CUR_CWND_SIZE,
205 (void **)&nr->p_diag_cur_cwnd_size);
206 unbind_diag(params, OSSL_CC_OPTION_MIN_CWND_SIZE,
207 (void **)&nr->p_diag_min_cwnd_size);
208 unbind_diag(params, OSSL_CC_OPTION_CUR_BYTES_IN_FLIGHT,
209 (void **)&nr->p_diag_cur_bytes_in_flight);
210 unbind_diag(params, OSSL_CC_OPTION_CUR_STATE,
211 (void **)&nr->p_diag_cur_state);
212 return 1;
213 }
214
newreno_update_diag(OSSL_CC_NEWRENO * nr)215 static void newreno_update_diag(OSSL_CC_NEWRENO *nr)
216 {
217 if (nr->p_diag_max_dgram_payload_len != NULL)
218 *nr->p_diag_max_dgram_payload_len = nr->max_dgram_size;
219
220 if (nr->p_diag_cur_cwnd_size != NULL)
221 *nr->p_diag_cur_cwnd_size = nr->cong_wnd;
222
223 if (nr->p_diag_min_cwnd_size != NULL)
224 *nr->p_diag_min_cwnd_size = nr->k_min_wnd;
225
226 if (nr->p_diag_cur_bytes_in_flight != NULL)
227 *nr->p_diag_cur_bytes_in_flight = nr->bytes_in_flight;
228
229 if (nr->p_diag_cur_state != NULL) {
230 if (nr->in_congestion_recovery)
231 *nr->p_diag_cur_state = 'R';
232 else if (nr->cong_wnd < nr->slow_start_thresh)
233 *nr->p_diag_cur_state = 'S';
234 else
235 *nr->p_diag_cur_state = 'A';
236 }
237 }
238
newreno_in_cong_recovery(OSSL_CC_NEWRENO * nr,OSSL_TIME tx_time)239 static int newreno_in_cong_recovery(OSSL_CC_NEWRENO *nr, OSSL_TIME tx_time)
240 {
241 return ossl_time_compare(tx_time, nr->cong_recovery_start_time) <= 0;
242 }
243
newreno_cong(OSSL_CC_NEWRENO * nr,OSSL_TIME tx_time)244 static void newreno_cong(OSSL_CC_NEWRENO *nr, OSSL_TIME tx_time)
245 {
246 int err = 0;
247
248 /* No reaction if already in a recovery period. */
249 if (newreno_in_cong_recovery(nr, tx_time))
250 return;
251
252 /* Start a new recovery period. */
253 nr->in_congestion_recovery = 1;
254 nr->cong_recovery_start_time = nr->now_cb(nr->now_cb_arg);
255
256 /* slow_start_thresh = cong_wnd * loss_reduction_factor */
257 nr->slow_start_thresh
258 = safe_muldiv_u64(nr->cong_wnd,
259 nr->k_loss_reduction_factor_num,
260 nr->k_loss_reduction_factor_den,
261 &err);
262
263 if (err)
264 nr->slow_start_thresh = UINT64_MAX;
265
266 nr->cong_wnd = nr->slow_start_thresh;
267 if (nr->cong_wnd < nr->k_min_wnd)
268 nr->cong_wnd = nr->k_min_wnd;
269 }
270
newreno_flush(OSSL_CC_NEWRENO * nr,uint32_t flags)271 static void newreno_flush(OSSL_CC_NEWRENO *nr, uint32_t flags)
272 {
273 if (!nr->processing_loss)
274 return;
275
276 newreno_cong(nr, nr->tx_time_of_last_loss);
277
278 if ((flags & OSSL_CC_LOST_FLAG_PERSISTENT_CONGESTION) != 0) {
279 nr->cong_wnd = nr->k_min_wnd;
280 nr->cong_recovery_start_time = ossl_time_zero();
281 }
282
283 nr->processing_loss = 0;
284 newreno_update_diag(nr);
285 }
286
newreno_get_tx_allowance(OSSL_CC_DATA * cc)287 static uint64_t newreno_get_tx_allowance(OSSL_CC_DATA *cc)
288 {
289 OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
290
291 if (nr->bytes_in_flight >= nr->cong_wnd)
292 return 0;
293
294 return nr->cong_wnd - nr->bytes_in_flight;
295 }
296
newreno_get_wakeup_deadline(OSSL_CC_DATA * cc)297 static OSSL_TIME newreno_get_wakeup_deadline(OSSL_CC_DATA *cc)
298 {
299 if (newreno_get_tx_allowance(cc) > 0) {
300 /* We have TX allowance now so wakeup immediately */
301 return ossl_time_zero();
302 } else {
303 /*
304 * The NewReno congestion controller does not vary its state in time,
305 * only in response to stimulus.
306 */
307 return ossl_time_infinite();
308 }
309 }
310
newreno_on_data_sent(OSSL_CC_DATA * cc,uint64_t num_bytes)311 static int newreno_on_data_sent(OSSL_CC_DATA *cc, uint64_t num_bytes)
312 {
313 OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
314
315 nr->bytes_in_flight += num_bytes;
316 newreno_update_diag(nr);
317 return 1;
318 }
319
newreno_is_cong_limited(OSSL_CC_NEWRENO * nr)320 static int newreno_is_cong_limited(OSSL_CC_NEWRENO *nr)
321 {
322 uint64_t wnd_rem;
323
324 /* We are congestion-limited if we are already at the congestion window. */
325 if (nr->bytes_in_flight >= nr->cong_wnd)
326 return 1;
327
328 wnd_rem = nr->cong_wnd - nr->bytes_in_flight;
329
330 /*
331 * Consider ourselves congestion-limited if less than three datagrams' worth
332 * of congestion window remains to be spent, or if we are in slow start and
333 * have consumed half of our window.
334 */
335 return (nr->cong_wnd < nr->slow_start_thresh && wnd_rem <= nr->cong_wnd / 2)
336 || wnd_rem <= 3 * nr->max_dgram_size;
337 }
338
newreno_on_data_acked(OSSL_CC_DATA * cc,const OSSL_CC_ACK_INFO * info)339 static int newreno_on_data_acked(OSSL_CC_DATA *cc,
340 const OSSL_CC_ACK_INFO *info)
341 {
342 OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
343
344 /*
345 * Packet has been acked. Firstly, remove it from the aggregate count of
346 * bytes in flight.
347 */
348 nr->bytes_in_flight -= info->tx_size;
349
350 /*
351 * We use acknowledgement of data as a signal that we are not at channel
352 * capacity and that it may be reasonable to increase the congestion window.
353 * However, acknowledgement is not a useful signal that there is further
354 * capacity if we are not actually saturating the congestion window that we
355 * already have (for example, if the application is not generating much data
356 * or we are limited by flow control). Therefore, we only expand the
357 * congestion window if we are consuming a significant fraction of the
358 * congestion window.
359 */
360 if (!newreno_is_cong_limited(nr))
361 goto out;
362
363 /*
364 * We can handle acknowledgement of a packet in one of three ways
365 * depending on our current state:
366 *
367 * - Congestion Recovery: Do nothing. We don't start increasing
368 * the congestion window in response to acknowledgements until
369 * we are no longer in the Congestion Recovery state.
370 *
371 * - Slow Start: Increase the congestion window using the slow
372 * start scale.
373 *
374 * - Congestion Avoidance: Increase the congestion window using
375 * the congestion avoidance scale.
376 */
377 if (newreno_in_cong_recovery(nr, info->tx_time)) {
378 /* Congestion recovery, do nothing. */
379 } else if (nr->cong_wnd < nr->slow_start_thresh) {
380 /* When this condition is true we are in the Slow Start state. */
381 nr->cong_wnd += info->tx_size;
382 nr->in_congestion_recovery = 0;
383 } else {
384 /* Otherwise, we are in the Congestion Avoidance state. */
385 nr->bytes_acked += info->tx_size;
386
387 /*
388 * Avoid integer division as per RFC 9002 s. B.5. / RFC3465 s. 2.1.
389 */
390 if (nr->bytes_acked >= nr->cong_wnd) {
391 nr->bytes_acked -= nr->cong_wnd;
392 nr->cong_wnd += nr->max_dgram_size;
393 }
394
395 nr->in_congestion_recovery = 0;
396 }
397
398 out:
399 newreno_update_diag(nr);
400 return 1;
401 }
402
newreno_on_data_lost(OSSL_CC_DATA * cc,const OSSL_CC_LOSS_INFO * info)403 static int newreno_on_data_lost(OSSL_CC_DATA *cc,
404 const OSSL_CC_LOSS_INFO *info)
405 {
406 OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
407
408 if (info->tx_size > nr->bytes_in_flight)
409 return 0;
410
411 nr->bytes_in_flight -= info->tx_size;
412
413 if (!nr->processing_loss) {
414
415 if (ossl_time_compare(info->tx_time, nr->tx_time_of_last_loss) <= 0)
416 /*
417 * After triggering congestion due to a lost packet at time t, don't
418 * trigger congestion again due to any subsequently detected lost
419 * packet at a time s < t, as we've effectively already signalled
420 * congestion on loss of that and subsequent packets.
421 */
422 goto out;
423
424 nr->processing_loss = 1;
425
426 /*
427 * Cancel any pending window increase in the Congestion Avoidance state.
428 */
429 nr->bytes_acked = 0;
430 }
431
432 nr->tx_time_of_last_loss
433 = ossl_time_max(nr->tx_time_of_last_loss, info->tx_time);
434
435 out:
436 newreno_update_diag(nr);
437 return 1;
438 }
439
newreno_on_data_lost_finished(OSSL_CC_DATA * cc,uint32_t flags)440 static int newreno_on_data_lost_finished(OSSL_CC_DATA *cc, uint32_t flags)
441 {
442 OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
443
444 newreno_flush(nr, flags);
445 return 1;
446 }
447
newreno_on_data_invalidated(OSSL_CC_DATA * cc,uint64_t num_bytes)448 static int newreno_on_data_invalidated(OSSL_CC_DATA *cc,
449 uint64_t num_bytes)
450 {
451 OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
452
453 nr->bytes_in_flight -= num_bytes;
454 newreno_update_diag(nr);
455 return 1;
456 }
457
newreno_on_ecn(OSSL_CC_DATA * cc,const OSSL_CC_ECN_INFO * info)458 static int newreno_on_ecn(OSSL_CC_DATA *cc,
459 const OSSL_CC_ECN_INFO *info)
460 {
461 OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
462
463 nr->processing_loss = 1;
464 nr->bytes_acked = 0;
465 nr->tx_time_of_last_loss = info->largest_acked_time;
466 newreno_flush(nr, 0);
467 return 1;
468 }
469
470 const OSSL_CC_METHOD ossl_cc_newreno_method = {
471 newreno_new,
472 newreno_free,
473 newreno_reset,
474 newreno_set_input_params,
475 newreno_bind_diagnostic,
476 newreno_unbind_diagnostic,
477 newreno_get_tx_allowance,
478 newreno_get_wakeup_deadline,
479 newreno_on_data_sent,
480 newreno_on_data_acked,
481 newreno_on_data_lost,
482 newreno_on_data_lost_finished,
483 newreno_on_data_invalidated,
484 newreno_on_ecn,
485 };
486