blob: fd7fef5a231f3e912190d5209fd8f108cd0d095c [file] [log] [blame]
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +02001#include <haproxy/quic_cc.h>
Frédéric Lécaille8f991942023-03-24 15:14:45 +01002#include <haproxy/ticks.h>
3#include <haproxy/trace.h>
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +02004
5/* This source file is highly inspired from Linux kernel source file
6 * implementation for TCP Cubic. In fact, we have no choice if we do
7 * not want to use any floating point operations to be fast!
8 * (See net/ipv4/tcp_cubic.c)
9 */
10#define TRACE_SOURCE &trace_quic
11
12#define CUBIC_BETA_SCALE 1024
13#define CUBIC_BETA_SCALE_SHIFT 10
14/* beta = 0.7 ; C = 0.4 */
15#define CUBIC_BETA 717 /* CUBIC_BETA / CUBIC_BETA_SCALE = 0.7 */
16#define CUBIC_C 410 /* CUBIC_C / CUBIC_BETA_SCALE = 0.4 */
17
18#define CUBIC_BETA_SCALE_FACTOR_SHIFT (3 * CUBIC_BETA_SCALE_SHIFT)
19#define TIME_SCALE_FACTOR_SHIFT 10
20
21/* The maximum value which may be cubed an multiplied by CUBIC_BETA */
22#define CUBIC_DIFF_TIME_LIMIT 355535ULL /* ms */
23
24/* K cube factor: (1 - beta) / c */
25struct cubic {
Frédéric Lécaille7d6270a2023-04-02 12:43:22 +020026 uint32_t state;
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +020027 uint32_t ssthresh;
28 uint32_t remaining_inc;
29 uint32_t remaining_tcp_inc;
30 uint32_t epoch_start;
31 uint32_t origin_point;
32 uint32_t K;
33 uint32_t last_w_max;
34 uint32_t tcp_wnd;
35 uint32_t recovery_start_time;
36};
37
38static void quic_cc_cubic_reset(struct quic_cc *cc)
39{
40 struct cubic *c = quic_cc_priv(cc);
41
Frédéric Lécaillede2ba862023-04-02 10:49:35 +020042 TRACE_ENTER(QUIC_EV_CONN_CC, cc->qc);
Frédéric Lécaille7d6270a2023-04-02 12:43:22 +020043 c->state = QUIC_CC_ST_SS;
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +020044 c->ssthresh = QUIC_CC_INFINITE_SSTHESH;
45 c->remaining_inc = 0;
46 c->remaining_tcp_inc = 0;
47 c->epoch_start = 0;
48 c->origin_point = 0;
49 c->K = 0;
50 c->last_w_max = 0;
51 c->tcp_wnd = 0;
52 c->recovery_start_time = 0;
Frédéric Lécaillede2ba862023-04-02 10:49:35 +020053 TRACE_LEAVE(QUIC_EV_CONN_CC, cc->qc);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +020054}
55
56static int quic_cc_cubic_init(struct quic_cc *cc)
57{
58 quic_cc_cubic_reset(cc);
59 return 1;
60}
61
62/* Cubic root.
63 * Highly inspired from Linux kernel sources.
64 * See net/ipv4/tcp_cubic.c
65 */
66static uint32_t cubic_root(uint64_t val)
67{
68 uint32_t x, b, shift;
69
70 static const uint8_t v[] = {
71 0, 54, 54, 54, 118, 118, 118, 118,
72 123, 129, 134, 138, 143, 147, 151, 156,
73 157, 161, 164, 168, 170, 173, 176, 179,
74 181, 185, 187, 190, 192, 194, 197, 199,
75 200, 202, 204, 206, 209, 211, 213, 215,
76 217, 219, 221, 222, 224, 225, 227, 229,
77 231, 232, 234, 236, 237, 239, 240, 242,
78 244, 245, 246, 248, 250, 251, 252, 254,
79 };
80
Frédéric Lécaille2c77a5e2022-08-03 12:49:30 +020081 if (!val || (b = my_flsl(val)) < 7) {
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +020082 /* val in [0..63] */
83 return ((uint32_t)v[(uint32_t)val] + 35) >> 6;
84 }
85
86 b = ((b * 84) >> 8) - 1;
87 shift = (val >> (b * 3));
88
89 x = ((uint32_t)(((uint32_t)v[shift] + 10) << b)) >> 6;
90
91 x = 2 * x + (uint32_t)(val / ((uint64_t)x * (uint64_t)(x - 1)));
92 x = ((x * 341) >> 10);
93
94 return x;
95}
96
97static inline void quic_cubic_update(struct quic_cc *cc, uint32_t acked)
98{
99 struct cubic *c = quic_cc_priv(cc);
100 struct quic_path *path = container_of(cc, struct quic_path, cc);
101 /* Current cwnd as number of packets */
102 uint32_t t, target, inc, inc_diff;
103 uint64_t delta, diff;
104
Frédéric Lécaillede2ba862023-04-02 10:49:35 +0200105 TRACE_ENTER(QUIC_EV_CONN_CC, cc->qc);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200106 if (!c->epoch_start) {
107 c->epoch_start = now_ms;
108 if (c->last_w_max <= path->cwnd) {
109 c->K = 0;
110 c->origin_point = path->cwnd;
111 }
112 else {
113 /* K = cubic_root((1 - beta) * W_max / C) */
114 c->K = cubic_root((c->last_w_max - path->cwnd) *
115 (CUBIC_BETA_SCALE - CUBIC_BETA) / CUBIC_C / path->mtu) << TIME_SCALE_FACTOR_SHIFT;
116 c->origin_point = c->last_w_max;
117 }
118
119 c->tcp_wnd = path->cwnd;
120 c->remaining_inc = 0;
121 c->remaining_tcp_inc = 0;
122 }
123
124 t = now_ms + path->loss.rtt_min - c->epoch_start;
125 if (t < c->K) {
126 diff = c->K - t;
127 }
128 else {
129 diff = t - c->K;
130 }
131
132 if (diff > CUBIC_DIFF_TIME_LIMIT) {
133 /* TODO : should not happen if we handle the case
134 * of very late acks receipt. This must be handled as a congestion
135 * control event: a very late ack should trigger a congestion
136 * control algorithm reset.
137 */
138 quic_cc_cubic_reset(cc);
Frédéric Lécaillede2ba862023-04-02 10:49:35 +0200139 goto leave;
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200140 }
141
142 delta = path->mtu * ((CUBIC_C * diff * diff * diff) >> (10 + 3 * TIME_SCALE_FACTOR_SHIFT));
143 if (t < c->K)
144 target = c->origin_point - delta;
145 else
146 target = c->origin_point + delta;
147
148 if (target > path->cwnd) {
149 inc_diff = c->remaining_inc + path->mtu * (target - path->cwnd);
150 c->remaining_inc = inc_diff % path->cwnd;
151 inc = inc_diff / path->cwnd;
152 }
153 else {
154 /* small increment */
155 inc_diff = c->remaining_inc + path->mtu;
156 c->remaining_inc = inc_diff % (100 * path->cwnd);
157 inc = inc_diff / (100 * path->cwnd);
158 }
159
160 inc_diff = c->remaining_tcp_inc + path->mtu * acked;
161 c->tcp_wnd += inc_diff / path->cwnd;
162 c->remaining_tcp_inc = inc_diff % path->cwnd;
163 /* TCP friendliness */
164 if (c->tcp_wnd > path->cwnd) {
165 uint32_t tcp_inc = path->mtu * (c->tcp_wnd - path->cwnd) / path->cwnd;
166 if (tcp_inc > inc)
167 inc = tcp_inc;
168 }
169
170 path->cwnd += inc;
Frédéric Lécaillefad0e6c2023-04-06 10:19:17 +0200171 path->mcwnd = QUIC_MAX(path->cwnd, path->mcwnd);
Frédéric Lécaillede2ba862023-04-02 10:49:35 +0200172 leave:
173 TRACE_LEAVE(QUIC_EV_CONN_CC, cc->qc);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200174}
175
176static void quic_cc_cubic_slow_start(struct quic_cc *cc)
177{
Frédéric Lécaillede2ba862023-04-02 10:49:35 +0200178 TRACE_ENTER(QUIC_EV_CONN_CC, cc->qc);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200179 quic_cc_cubic_reset(cc);
Frédéric Lécaillede2ba862023-04-02 10:49:35 +0200180 TRACE_LEAVE(QUIC_EV_CONN_CC, cc->qc);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200181}
182
183static void quic_enter_recovery(struct quic_cc *cc)
184{
185 struct quic_path *path = container_of(cc, struct quic_path, cc);
186 struct cubic *c = quic_cc_priv(cc);
187 /* Current cwnd as number of packets */
188
Frédéric Lécaillede2ba862023-04-02 10:49:35 +0200189 TRACE_ENTER(QUIC_EV_CONN_CC, cc->qc);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200190 c->epoch_start = 0;
191 c->recovery_start_time = now_ms;
192 /* Fast convergence */
193 if (path->cwnd < c->last_w_max) {
194 /* (1 + beta) * path->cwnd / 2 */
195 c->last_w_max = (path->cwnd * (CUBIC_BETA_SCALE + CUBIC_BETA) / 2) >> CUBIC_BETA_SCALE_SHIFT;
196 }
197 else {
198 c->last_w_max = path->cwnd;
199 }
Frédéric Lécaille595251f2023-04-13 10:46:42 +0200200 c->ssthresh = (CUBIC_BETA * path->cwnd) >> CUBIC_BETA_SCALE_SHIFT;
201 path->cwnd = QUIC_MAX(c->ssthresh, (uint32_t)path->min_cwnd);
Frédéric Lécaille7d6270a2023-04-02 12:43:22 +0200202 c->state = QUIC_CC_ST_RP;
Frédéric Lécaillede2ba862023-04-02 10:49:35 +0200203 TRACE_LEAVE(QUIC_EV_CONN_CC, cc->qc, NULL, cc);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200204}
205
206/* Congestion slow-start callback. */
207static void quic_cc_cubic_ss_cb(struct quic_cc *cc, struct quic_cc_event *ev)
208{
209 struct quic_path *path = container_of(cc, struct quic_path, cc);
210 struct cubic *c = quic_cc_priv(cc);
211
Frédéric Lécaille8f991942023-03-24 15:14:45 +0100212 TRACE_ENTER(QUIC_EV_CONN_CC, cc->qc);
213 TRACE_PROTO("CC cubic", QUIC_EV_CONN_CC, cc->qc, ev);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200214 switch (ev->type) {
215 case QUIC_CC_EVT_ACK:
Frédéric Lécailledb548472023-04-02 10:07:48 +0200216 if (path->cwnd < QUIC_CC_INFINITE_SSTHESH - ev->ack.acked)
217 path->cwnd += ev->ack.acked;
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200218 /* Exit to congestion avoidance if slow start threshold is reached. */
219 if (path->cwnd >= c->ssthresh)
Frédéric Lécaille7d6270a2023-04-02 12:43:22 +0200220 c->state = QUIC_CC_ST_CA;
Frédéric Lécaillefad0e6c2023-04-06 10:19:17 +0200221 path->mcwnd = QUIC_MAX(path->cwnd, path->mcwnd);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200222 break;
223
224 case QUIC_CC_EVT_LOSS:
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200225 quic_enter_recovery(cc);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200226 break;
227
228 case QUIC_CC_EVT_ECN_CE:
229 /* TODO */
230 break;
231 }
232
233 out:
Frédéric Lécaille8f991942023-03-24 15:14:45 +0100234 TRACE_PROTO("CC cubic", QUIC_EV_CONN_CC, cc->qc, NULL, cc);
235 TRACE_LEAVE(QUIC_EV_CONN_CC, cc->qc);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200236}
237
238/* Congestion avoidance callback. */
239static void quic_cc_cubic_ca_cb(struct quic_cc *cc, struct quic_cc_event *ev)
240{
Frédéric Lécaille8f991942023-03-24 15:14:45 +0100241 TRACE_ENTER(QUIC_EV_CONN_CC, cc->qc);
242 TRACE_PROTO("CC cubic", QUIC_EV_CONN_CC, cc->qc, ev);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200243 switch (ev->type) {
244 case QUIC_CC_EVT_ACK:
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200245 quic_cubic_update(cc, ev->ack.acked);
246 break;
247 case QUIC_CC_EVT_LOSS:
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200248 quic_enter_recovery(cc);
249 break;
250 case QUIC_CC_EVT_ECN_CE:
251 /* TODO */
252 break;
253 }
254
255 out:
Frédéric Lécaille8f991942023-03-24 15:14:45 +0100256 TRACE_PROTO("CC cubic", QUIC_EV_CONN_CC, cc->qc, NULL, cc);
Frédéric Lécaille23b8eef2023-03-31 20:18:44 +0200257 TRACE_LEAVE(QUIC_EV_CONN_CC, cc->qc);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200258}
259
Frédéric Lécailled7243312023-03-21 11:54:02 +0100260/* Recovery period callback */
261static void quic_cc_cubic_rp_cb(struct quic_cc *cc, struct quic_cc_event *ev)
262{
263 struct cubic *c = quic_cc_priv(cc);
264
265 TRACE_ENTER(QUIC_EV_CONN_CC, cc->qc, ev);
Frédéric Lécaillede2ba862023-04-02 10:49:35 +0200266 TRACE_PROTO("CC cubic", QUIC_EV_CONN_CC, cc->qc, ev, cc);
Frédéric Lécailled7243312023-03-21 11:54:02 +0100267
Frédéric Lécailled7243312023-03-21 11:54:02 +0100268 switch (ev->type) {
269 case QUIC_CC_EVT_ACK:
270 /* RFC 9022 7.3.2. Recovery
271 * A recovery period ends and the sender enters congestion avoidance when a
272 * packet sent during the recovery period is acknowledged.
273 */
Frédéric Lécaille8f991942023-03-24 15:14:45 +0100274 if (tick_is_le(ev->ack.time_sent, c->recovery_start_time)) {
275 TRACE_PROTO("CC cubic (still in recov. period)", QUIC_EV_CONN_CC, cc->qc);
Frédéric Lécailled7243312023-03-21 11:54:02 +0100276 goto leave;
Frédéric Lécaille8f991942023-03-24 15:14:45 +0100277 }
Frédéric Lécailled7243312023-03-21 11:54:02 +0100278
Frédéric Lécaille7d6270a2023-04-02 12:43:22 +0200279 c->state = QUIC_CC_ST_CA;
Frédéric Lécailled7243312023-03-21 11:54:02 +0100280 c->recovery_start_time = TICK_ETERNITY;
281 break;
282 case QUIC_CC_EVT_LOSS:
283 break;
284 case QUIC_CC_EVT_ECN_CE:
285 /* TODO */
286 break;
287 }
288
289 leave:
Frédéric Lécaille8f991942023-03-24 15:14:45 +0100290 TRACE_PROTO("CC cubic", QUIC_EV_CONN_CC, cc->qc, NULL, cc);
Frédéric Lécailled7243312023-03-21 11:54:02 +0100291 TRACE_LEAVE(QUIC_EV_CONN_CC, cc->qc, NULL, cc);
292}
293
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200294static void (*quic_cc_cubic_state_cbs[])(struct quic_cc *cc,
295 struct quic_cc_event *ev) = {
296 [QUIC_CC_ST_SS] = quic_cc_cubic_ss_cb,
297 [QUIC_CC_ST_CA] = quic_cc_cubic_ca_cb,
Frédéric Lécailled7243312023-03-21 11:54:02 +0100298 [QUIC_CC_ST_RP] = quic_cc_cubic_rp_cb,
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200299};
300
301static void quic_cc_cubic_event(struct quic_cc *cc, struct quic_cc_event *ev)
302{
Frédéric Lécaille7d6270a2023-04-02 12:43:22 +0200303 struct cubic *c = quic_cc_priv(cc);
304
305 return quic_cc_cubic_state_cbs[c->state](cc, ev);
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200306}
307
308static void quic_cc_cubic_state_trace(struct buffer *buf, const struct quic_cc *cc)
309{
Frédéric Lécaille01314b82023-03-24 14:09:55 +0100310 struct quic_path *path;
311 struct cubic *c = quic_cc_priv(cc);
312
313 path = container_of(cc, struct quic_path, cc);
Frédéric Lécaillefad0e6c2023-04-06 10:19:17 +0200314 chunk_appendf(buf, " state=%s cwnd=%llu mcwnd=%llu ssthresh=%d rpst=%dms",
Frédéric Lécaille7d6270a2023-04-02 12:43:22 +0200315 quic_cc_state_str(c->state),
Frédéric Lécaille01314b82023-03-24 14:09:55 +0100316 (unsigned long long)path->cwnd,
Frédéric Lécaillefad0e6c2023-04-06 10:19:17 +0200317 (unsigned long long)path->mcwnd,
Frédéric Lécaille01314b82023-03-24 14:09:55 +0100318 (int)c->ssthresh,
319 !tick_isset(c->recovery_start_time) ? -1 :
320 TICKS_TO_MS(tick_remain(c->recovery_start_time, now_ms)));
Frédéric Lécaille1c9c2f62022-06-01 15:06:58 +0200321}
322
323struct quic_cc_algo quic_cc_algo_cubic = {
324 .type = QUIC_CC_ALGO_TP_CUBIC,
325 .init = quic_cc_cubic_init,
326 .event = quic_cc_cubic_event,
327 .slow_start = quic_cc_cubic_slow_start,
328 .state_trace = quic_cc_cubic_state_trace,
329};