blob: 82aa44bade2e8398d80eb82132870830eaa1df32 [file] [log] [blame]
Christopher Faulet2b677702022-06-22 16:55:04 +02001/*
2 * Bandwidth limitation filter.
3 *
4 * Copyright 2022 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
5 *
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 */
13
14#include <ctype.h>
15
16#include <haproxy/api.h>
17#include <haproxy/channel-t.h>
18#include <haproxy/filters.h>
19#include <haproxy/global.h>
20#include <haproxy/http_ana-t.h>
21#include <haproxy/http_rules.h>
22#include <haproxy/proxy.h>
23#include <haproxy/sample.h>
24#include <haproxy/stream.h>
25#include <haproxy/tcp_rules.h>
26#include <haproxy/time.h>
27#include <haproxy/tools.h>
28
29const char *bwlim_flt_id = "bandwidth limitation filter";
30
31struct flt_ops bwlim_ops;
32
33#define BWLIM_FL_NONE 0x00000000 /* For init purposr */
34#define BWLIM_FL_IN 0x00000001 /* Limit clients uploads */
35#define BWLIM_FL_OUT 0x00000002 /* Limit clients downloads */
36#define BWLIM_FL_SHARED 0x00000004 /* Limit shared between clients (using stick-tables) */
37
Christopher Fauletda2e1172023-01-13 15:33:32 +010038#define BWLIM_ACT_LIMIT_EXPR 0x00000001
39#define BWLIM_ACT_LIMIT_CONST 0x00000002
40#define BWLIM_ACT_PERIOD_EXPR 0x00000004
41#define BWLIM_ACT_PERIOD_CONST 0x00000008
42
Christopher Faulet2b677702022-06-22 16:55:04 +020043struct bwlim_config {
44 struct proxy *proxy;
45 char *name;
46 unsigned int flags;
47 struct sample_expr *expr;
48 union {
49 char *n;
50 struct stktable *t;
51 } table;
52 unsigned int period;
53 unsigned int limit;
54 unsigned int min_size;
55};
56
57struct bwlim_state {
58 struct freq_ctr bytes_rate;
59 struct stksess *ts;
60 struct act_rule *rule;
61 unsigned int limit;
62 unsigned int period;
63 unsigned int exp;
64};
65
66
67/* Pools used to allocate comp_state structs */
68DECLARE_STATIC_POOL(pool_head_bwlim_state, "bwlim_state", sizeof(struct bwlim_state));
69
70
71/* Apply the bandwidth limitation of the filter <filter>. <len> is the maximum
72 * amount of data that the filter can forward. This function applies the
73 * limitation and returns what the stream is authorized to forward. Several
74 * limitation can be stacked.
75 */
76static int bwlim_apply_limit(struct filter *filter, struct channel *chn, unsigned int len)
77{
78 struct bwlim_config *conf = FLT_CONF(filter);
79 struct bwlim_state *st = filter->ctx;
80 struct freq_ctr *bytes_rate;
81 unsigned int period, limit, remain, tokens, users;
82 unsigned int wait = 0;
83 int overshoot, ret = 0;
84
85 /* Don't forward anything if there is nothing to forward or the waiting
86 * time is not expired
87 */
Christopher Faulet5d5fbaa2024-07-09 18:51:43 +020088 if (tick_isset(st->exp) && !tick_is_expired(st->exp, now_ms))
Christopher Faulet2b677702022-06-22 16:55:04 +020089 goto end;
90
91 st->exp = TICK_ETERNITY;
Christopher Faulet5d5fbaa2024-07-09 18:51:43 +020092 if (!len)
93 goto end;
94
Christopher Faulet2b677702022-06-22 16:55:04 +020095 ret = len;
96 if (conf->flags & BWLIM_FL_SHARED) {
97 void *ptr;
98 unsigned int type = ((conf->flags & BWLIM_FL_IN) ? STKTABLE_DT_BYTES_IN_RATE : STKTABLE_DT_BYTES_OUT_RATE);
99
100 /* In shared mode, get a pointer on the stick table entry. it
101 * will be used to get the freq-counter. It is also used to get
102 * The number of users.
103 */
104 ptr = stktable_data_ptr(conf->table.t, st->ts, type);
105 if (!ptr)
106 goto end;
107
108 HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &st->ts->lock);
109 bytes_rate = &stktable_data_cast(ptr, std_t_frqp);
110 period = conf->table.t->data_arg[type].u;
111 limit = conf->limit;
112 users = st->ts->ref_cnt;
113 }
114 else {
115 /* On per-stream mode, the freq-counter is private to the
116 * stream. Get it from the filter state. Rely on the custom
Ilya Shipitsin3b64a282022-07-29 22:26:53 +0500117 * limit/period if defined or use the default ones. In this mode,
Christopher Faulet2b677702022-06-22 16:55:04 +0200118 * there is only one user.
119 */
120 bytes_rate = &st->bytes_rate;
121 period = (st->period ? st->period : conf->period);
122 limit = (st->limit ? st->limit : conf->limit);
123 users = 1;
124 }
125
126 /* Be sure the current rate does not exceed the limit over the current
127 * period. In this case, nothing is forwarded and the waiting time is
128 * computed to be sure to not retry too early.
129 *
130 * The test is used to avoid the initial burst. Otherwise, streams will
131 * consume the limit as fast as possible and will then be paused for
132 * long time.
133 */
134 overshoot = freq_ctr_overshoot_period(bytes_rate, period, limit);
135 if (overshoot > 0) {
136 if (conf->flags & BWLIM_FL_SHARED)
137 HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &st->ts->lock);
138 wait = div64_32((uint64_t)(conf->min_size + overshoot) * period * users,
139 limit);
140 st->exp = tick_add(now_ms, (wait ? wait : 1));
141 ret = 0;
142 goto end;
143 }
144
145 /* Get the allowed quota per user. */
146 remain = freq_ctr_remain_period(bytes_rate, period, limit, 0);
147 tokens = div64_32((uint64_t)(remain + users - 1), users);
148
149 if (tokens < len) {
150 /* The stream cannot forward all its data. But we will check if
151 * it can perform a small burst if the global quota is large
Ilya Shipitsin3b64a282022-07-29 22:26:53 +0500152 * enough. But, in this case, its waiting time will be
Christopher Faulet2b677702022-06-22 16:55:04 +0200153 * increased accordingly.
154 */
155 ret = tokens;
156 if (tokens < conf->min_size) {
Christopher Fauletca5309a2023-04-17 16:17:32 +0200157 ret = (chn_prod(chn)->flags & (SC_FL_EOI|SC_FL_EOS|SC_FL_ABRT_DONE))
Christopher Faulet2b677702022-06-22 16:55:04 +0200158 ? MIN(len, conf->min_size)
159 : conf->min_size;
160
161 if (ret <= remain)
162 wait = div64_32((uint64_t)(ret - tokens) * period * users + limit - 1, limit);
163 else
164 ret = (limit < ret) ? remain : 0;
165 }
166 }
167
168 /* At the end, update the freq-counter and compute the waiting time if
169 * the stream is limited
170 */
171 update_freq_ctr_period(bytes_rate, period, ret);
172 if (ret < len) {
173 wait += next_event_delay_period(bytes_rate, period, limit, MIN(len - ret, conf->min_size * users));
174 st->exp = tick_add(now_ms, (wait ? wait : 1));
175 }
176
177 if (conf->flags & BWLIM_FL_SHARED)
178 HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &st->ts->lock);
179
180 end:
181 chn->analyse_exp = tick_first((tick_is_expired(chn->analyse_exp, now_ms) ? TICK_ETERNITY : chn->analyse_exp),
182 st->exp);
Christopher Faulet5d5fbaa2024-07-09 18:51:43 +0200183 BUG_ON(tick_is_expired(chn->analyse_exp, now_ms));
Christopher Faulet2b677702022-06-22 16:55:04 +0200184 return ret;
185}
186
187/***************************************************************************
188 * Hooks that manage the filter lifecycle (init/check/deinit)
189 **************************************************************************/
190/* Initialize the filter. Returns -1 on error, else 0. */
191static int bwlim_init(struct proxy *px, struct flt_conf *fconf)
192{
193 fconf->flags |= FLT_CFG_FL_HTX;
194 return 0;
195}
196
197/* Free resources allocated by the bwlim filter. */
198static void bwlim_deinit(struct proxy *px, struct flt_conf *fconf)
199{
200 struct bwlim_config *conf = fconf->conf;
201
202 if (conf) {
Christopher Fauletf0196f42022-06-24 14:52:18 +0200203 ha_free(&conf->name);
Christopher Faulet2b677702022-06-22 16:55:04 +0200204 release_sample_expr(conf->expr);
Christopher Fauletf0196f42022-06-24 14:52:18 +0200205 conf->expr = NULL;
206 ha_free(&fconf->conf);
Christopher Faulet2b677702022-06-22 16:55:04 +0200207 }
208}
209
210/* Check configuration of a bwlim filter for a specified proxy.
211 * Return 1 on error, else 0. */
212static int bwlim_check(struct proxy *px, struct flt_conf *fconf)
213{
214 struct bwlim_config *conf = fconf->conf;
215 struct stktable *target;
216
217 if (!(conf->flags & BWLIM_FL_SHARED))
218 return 0;
219
220 if (conf->table.n)
221 target = stktable_find_by_name(conf->table.n);
222 else
223 target = px->table;
224
225 if (!target) {
226 ha_alert("Proxy %s : unable to find table '%s' referenced by bwlim filter '%s'",
227 px->id, conf->table.n ? conf->table.n : px->id, conf->name);
228 return 1;
229 }
230
231 if ((conf->flags & BWLIM_FL_IN) && !target->data_ofs[STKTABLE_DT_BYTES_IN_RATE]) {
232 ha_alert("Proxy %s : stick-table '%s' uses a data type incompatible with bwlim filter '%s'."
233 " It must be 'bytes_in_rate'",
234 px->id, conf->table.n ? conf->table.n : px->id, conf->name);
235 return 1;
236 }
237 else if ((conf->flags & BWLIM_FL_OUT) && !target->data_ofs[STKTABLE_DT_BYTES_OUT_RATE]) {
238 ha_alert("Proxy %s : stick-table '%s' uses a data type incompatible with bwlim filter '%s'."
239 " It must be 'bytes_out_rate'",
240 px->id, conf->table.n ? conf->table.n : px->id, conf->name);
241 return 1;
242 }
243
244 if (!stktable_compatible_sample(conf->expr, target->type)) {
245 ha_alert("Proxy %s : stick-table '%s' uses a key type incompatible with bwlim filter '%s'",
246 px->id, conf->table.n ? conf->table.n : px->id, conf->name);
247 return 1;
248 }
249 else {
250 if (!in_proxies_list(target->proxies_list, px)) {
251 px->next_stkt_ref = target->proxies_list;
252 target->proxies_list = px;
253 }
Christopher Fauletf0196f42022-06-24 14:52:18 +0200254 ha_free(&conf->table.n);
Christopher Faulet2b677702022-06-22 16:55:04 +0200255 conf->table.t = target;
256 }
257
258 return 0;
259}
260
261/**************************************************************************
262 * Hooks to handle start/stop of streams
263 *************************************************************************/
264/* Called when a filter instance is created and attach to a stream */
265static int bwlim_attach(struct stream *s, struct filter *filter)
266{
267 struct bwlim_state *st;
268
269 st = pool_zalloc(pool_head_bwlim_state);
270 if (!st)
271 return -1;
272 filter->ctx = st;
273 return 1;
274}
275
276/* Called when a filter instance is detach from a stream, just before its
277 * destruction */
278static void bwlim_detach(struct stream *s, struct filter *filter)
279{
280 struct bwlim_config *conf = FLT_CONF(filter);
281 struct bwlim_state *st = filter->ctx;
282 struct stktable *t = conf->table.t;
283
284 if (!st)
285 return;
286
287 if (st->ts)
288 stktable_touch_local(t, st->ts, 1);
289
290 /* release any possible compression context */
291 pool_free(pool_head_bwlim_state, st);
292 filter->ctx = NULL;
293}
294
295/**************************************************************************
Christopher Faulet331a3b92023-08-01 08:16:42 +0200296 * Hooks to handle channels activity
297 *************************************************************************/
298
299/* Called when analyze ends for a given channel */
300static int bwlim_chn_end_analyze(struct stream *s, struct filter *filter, struct channel *chn)
301{
302 chn->analyse_exp = TICK_ETERNITY;
303 return 1;
304}
305
306
307/**************************************************************************
Christopher Faulet2b677702022-06-22 16:55:04 +0200308 * Hooks to filter HTTP messages
309 *************************************************************************/
310static int bwlim_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg)
311{
312 msg->chn->analyse_exp = TICK_ETERNITY;
313 return 1;
314}
315
316static int bwlim_http_payload(struct stream *s, struct filter *filter, struct http_msg *msg,
317 unsigned int offset, unsigned int len)
318{
319 return bwlim_apply_limit(filter, msg->chn, len);
320}
321
322/**************************************************************************
323 * Hooks to filter TCP data
324 *************************************************************************/
325static int bwlim_tcp_payload(struct stream *s, struct filter *filter, struct channel *chn,
326 unsigned int offset, unsigned int len)
327{
328 return bwlim_apply_limit(filter, chn, len);
329}
330
331/********************************************************************
332 * Functions that manage the filter initialization
333 ********************************************************************/
334struct flt_ops bwlim_ops = {
335 /* Manage bwlim filter, called for each filter declaration */
336 .init = bwlim_init,
337 .deinit = bwlim_deinit,
338 .check = bwlim_check,
339
340 /* Handle start/stop of streams */
341 .attach = bwlim_attach,
342 .detach = bwlim_detach,
343
Christopher Faulet331a3b92023-08-01 08:16:42 +0200344 /* Handle channels activity */
345 .channel_end_analyze = bwlim_chn_end_analyze,
Christopher Faulet2b677702022-06-22 16:55:04 +0200346
347 /* Filter HTTP requests and responses */
348 .http_headers = bwlim_http_headers,
349 .http_payload = bwlim_http_payload,
350
351 /* Filter TCP data */
352 .tcp_payload = bwlim_tcp_payload,
353};
354
355/* Set a bandwidth limitation. It always return ACT_RET_CONT. On error, the rule
356 * is ignored. First of all, it looks for the corresponding filter. Then, for a
357 * shared limitation, the stick-table entry is retrieved. For a per-stream
358 * limitation, the custom limit and period are computed, if necessary. At the
359 * end, the filter is registered on the data filtering for the right channel
360 * (bwlim-in = request, bwlim-out = response).
361 */
362static enum act_return bwlim_set_limit(struct act_rule *rule, struct proxy *px,
363 struct session *sess, struct stream *s, int flags)
364{
365 struct bwlim_config *conf = rule->arg.act.p[3];
366 struct filter *filter;
367 struct bwlim_state *st = NULL;
368 struct stktable *t;
369 struct stktable_key *key;
370 struct stksess *ts;
371 int opt;
372
373 list_for_each_entry(filter, &s->strm_flt.filters, list) {
374 if (FLT_ID(filter) == bwlim_flt_id && FLT_CONF(filter) == conf) {
375 st = filter->ctx;
376 break;
377 }
378 }
379
380 if (!st)
381 goto end;
382
383 switch (rule->from) {
384 case ACT_F_TCP_REQ_CNT: opt = SMP_OPT_DIR_REQ | SMP_OPT_FINAL; break;
385 case ACT_F_TCP_RES_CNT: opt = SMP_OPT_DIR_RES | SMP_OPT_FINAL; break;
386 case ACT_F_HTTP_REQ: opt = SMP_OPT_DIR_REQ | SMP_OPT_FINAL; break;
387 case ACT_F_HTTP_RES: opt = SMP_OPT_DIR_RES | SMP_OPT_FINAL; break;
388 default:
389 goto end;
390 }
391
392 if (conf->flags & BWLIM_FL_SHARED) {
393 t = conf->table.t;
394 key = stktable_fetch_key(t, px, sess, s, opt, conf->expr, NULL);
395 if (!key)
396 goto end;
397
398 ts = stktable_get_entry(t, key);
399 if (!ts)
400 goto end;
401
402 st->ts = ts;
403 st->rule = rule;
404 }
405 else {
406 struct sample *smp;
407
408 st->limit = 0;
409 st->period = 0;
Christopher Fauletda2e1172023-01-13 15:33:32 +0100410 if (rule->action & BWLIM_ACT_LIMIT_EXPR) {
Christopher Faulet2b677702022-06-22 16:55:04 +0200411 smp = sample_fetch_as_type(px, sess, s, opt, rule->arg.act.p[1], SMP_T_SINT);
412 if (smp && smp->data.u.sint > 0)
413 st->limit = smp->data.u.sint;
414 }
Christopher Fauletda2e1172023-01-13 15:33:32 +0100415 else if (rule->action & BWLIM_ACT_LIMIT_CONST)
416 st->limit = (uintptr_t)rule->arg.act.p[1];
417
418 if (rule->action & BWLIM_ACT_PERIOD_EXPR) {
Christopher Faulet2b677702022-06-22 16:55:04 +0200419 smp = sample_fetch_as_type(px, sess, s, opt, rule->arg.act.p[2], SMP_T_SINT);
420 if (smp && smp->data.u.sint > 0)
421 st->period = smp->data.u.sint;
422 }
Christopher Fauletda2e1172023-01-13 15:33:32 +0100423 else if (rule->action & BWLIM_ACT_PERIOD_CONST)
424 st->period = (uintptr_t)rule->arg.act.p[2];
Christopher Faulet2b677702022-06-22 16:55:04 +0200425 }
426
427 st->exp = TICK_ETERNITY;
428 if (conf->flags & BWLIM_FL_IN)
429 register_data_filter(s, &s->req, filter);
430 else
431 register_data_filter(s, &s->res, filter);
432
433 end:
434 return ACT_RET_CONT;
435}
436
437/* Check function for "set-bandwidth-limit" aciton. It returns 1 on
438 * success. Otherwise, it returns 0 and <err> is filled.
439 */
440int check_bwlim_action(struct act_rule *rule, struct proxy *px, char **err)
441{
442 struct flt_conf *fconf;
443 struct bwlim_config *conf = NULL;
444 unsigned int where;
445
446 list_for_each_entry(fconf, &px->filter_configs, list) {
447 conf = NULL;
448 if (fconf->id == bwlim_flt_id) {
449 conf = fconf->conf;
Tim Duesterhusa6fc6162022-10-08 12:33:19 +0200450 if (strcmp(rule->arg.act.p[0], conf->name) == 0)
Christopher Faulet2b677702022-06-22 16:55:04 +0200451 break;
452 }
453 }
454 if (!conf) {
455 memprintf(err, "unable to find bwlim filter '%s' referenced by set-bandwidth-limit rule",
456 (char *)rule->arg.act.p[0]);
457 return 0;
458 }
459
460 if ((conf->flags & BWLIM_FL_SHARED) && rule->arg.act.p[1]) {
461 memprintf(err, "set-bandwidth-limit rule cannot define a limit for a shared bwlim filter");
462 return 0;
463 }
464
Christopher Fauletab34ebe2023-01-13 15:21:53 +0100465 if ((conf->flags & BWLIM_FL_SHARED) && rule->arg.act.p[2]) {
466 memprintf(err, "set-bandwidth-limit rule cannot define a period for a shared bwlim filter");
467 return 0;
468 }
469
Christopher Faulet2b677702022-06-22 16:55:04 +0200470 where = 0;
Christopher Faulet6bf86c72023-01-13 15:39:54 +0100471 if (px->cap & PR_CAP_FE) {
472 if (rule->from == ACT_F_TCP_REQ_CNT)
473 where |= SMP_VAL_FE_REQ_CNT;
474 else if (rule->from == ACT_F_HTTP_REQ)
475 where |= SMP_VAL_FE_HRQ_HDR;
476 else if (rule->from == ACT_F_TCP_RES_CNT)
477 where |= SMP_VAL_FE_RES_CNT;
478 else if (rule->from == ACT_F_HTTP_RES)
479 where |= SMP_VAL_FE_HRS_HDR;
480 }
481 if (px->cap & PR_CAP_BE) {
482 if (rule->from == ACT_F_TCP_REQ_CNT)
483 where |= SMP_VAL_BE_REQ_CNT;
484 else if (rule->from == ACT_F_HTTP_REQ)
485 where |= SMP_VAL_BE_HRQ_HDR;
486 else if (rule->from == ACT_F_TCP_RES_CNT)
487 where |= SMP_VAL_BE_RES_CNT;
488 else if (rule->from == ACT_F_HTTP_RES)
489 where |= SMP_VAL_BE_HRS_HDR;
490 }
Christopher Faulet2b677702022-06-22 16:55:04 +0200491
Christopher Fauletda2e1172023-01-13 15:33:32 +0100492 if ((rule->action & BWLIM_ACT_LIMIT_EXPR) && rule->arg.act.p[1]) {
Christopher Faulet2b677702022-06-22 16:55:04 +0200493 struct sample_expr *expr = rule->arg.act.p[1];
494
495 if (!(expr->fetch->val & where)) {
496 memprintf(err, "set-bandwidth-limit rule uses a limit extracting information from '%s', none of which is available here",
497 sample_src_names(expr->fetch->use));
498 return 0;
499 }
500
501 if (rule->from == ACT_F_TCP_REQ_CNT && (px->cap & PR_CAP_FE)) {
502 if (!px->tcp_req.inspect_delay && !(expr->fetch->val & SMP_VAL_FE_SES_ACC)) {
503 ha_warning("%s '%s' : a 'tcp-request content set-bandwidth-limit*' rule explicitly depending on request"
504 " contents without any 'tcp-request inspect-delay' setting."
505 " This means that this rule will randomly find its contents. This can be fixed by"
506 " setting the tcp-request inspect-delay.\n",
507 proxy_type_str(px), px->id);
508 }
509 }
Christopher Faulet6bf86c72023-01-13 15:39:54 +0100510 if (rule->from == ACT_F_TCP_RES_CNT && (px->cap & PR_CAP_BE)) {
511 if (!px->tcp_rep.inspect_delay && !(expr->fetch->val & SMP_VAL_BE_SRV_CON)) {
512 ha_warning("%s '%s' : a 'tcp-response content set-bandwidth-limit*' rule explicitly depending on response"
513 " contents without any 'tcp-response inspect-delay' setting."
514 " This means that this rule will randomly find its contents. This can be fixed by"
515 " setting the tcp-response inspect-delay.\n",
516 proxy_type_str(px), px->id);
517 }
518 }
Christopher Faulet2b677702022-06-22 16:55:04 +0200519 }
520
Christopher Fauletda2e1172023-01-13 15:33:32 +0100521 if ((rule->action & BWLIM_ACT_PERIOD_EXPR) && rule->arg.act.p[2]) {
Christopher Fauletab34ebe2023-01-13 15:21:53 +0100522 struct sample_expr *expr = rule->arg.act.p[2];
523
524 if (!(expr->fetch->val & where)) {
525 memprintf(err, "set-bandwidth-limit rule uses a period extracting information from '%s', none of which is available here",
526 sample_src_names(expr->fetch->use));
527 return 0;
528 }
529
530 if (rule->from == ACT_F_TCP_REQ_CNT && (px->cap & PR_CAP_FE)) {
531 if (!px->tcp_req.inspect_delay && !(expr->fetch->val & SMP_VAL_FE_SES_ACC)) {
532 ha_warning("%s '%s' : a 'tcp-request content set-bandwidth-limit*' rule explicitly depending on request"
533 " contents without any 'tcp-request inspect-delay' setting."
534 " This means that this rule will randomly find its contents. This can be fixed by"
535 " setting the tcp-request inspect-delay.\n",
536 proxy_type_str(px), px->id);
Christopher Faulet6bf86c72023-01-13 15:39:54 +0100537 }
538 }
539 if (rule->from == ACT_F_TCP_RES_CNT && (px->cap & PR_CAP_BE)) {
540 if (!px->tcp_rep.inspect_delay && !(expr->fetch->val & SMP_VAL_BE_SRV_CON)) {
541 ha_warning("%s '%s' : a 'tcp-response content set-bandwidth-limit*' rule explicitly depending on response"
542 " contents without any 'tcp-response inspect-delay' setting."
543 " This means that this rule will randomly find its contents. This can be fixed by"
544 " setting the tcp-response inspect-delay.\n",
545 proxy_type_str(px), px->id);
Christopher Fauletab34ebe2023-01-13 15:21:53 +0100546 }
547 }
548 }
549
Christopher Faulet2b677702022-06-22 16:55:04 +0200550 if (conf->expr) {
551 if (!(conf->expr->fetch->val & where)) {
552 memprintf(err, "bwlim filter '%s uses a key extracting information from '%s', none of which is available here",
553 conf->name, sample_src_names(conf->expr->fetch->use));
554 return 0;
555 }
556
557 if (rule->from == ACT_F_TCP_REQ_CNT && (px->cap & PR_CAP_FE)) {
558 if (!px->tcp_req.inspect_delay && !(conf->expr->fetch->val & SMP_VAL_FE_SES_ACC)) {
559 ha_warning("%s '%s' : a 'tcp-request content set-bandwidth-limit*' rule explicitly depending on request"
560 " contents without any 'tcp-request inspect-delay' setting."
561 " This means that this rule will randomly find its contents. This can be fixed by"
562 " setting the tcp-request inspect-delay.\n",
563 proxy_type_str(px), px->id);
564 }
565 }
Christopher Faulet6bf86c72023-01-13 15:39:54 +0100566 if (rule->from == ACT_F_TCP_RES_CNT && (px->cap & PR_CAP_BE)) {
567 if (!px->tcp_rep.inspect_delay && !(conf->expr->fetch->val & SMP_VAL_BE_SRV_CON)) {
568 ha_warning("%s '%s' : a 'tcp-response content set-bandwidth-limit*' rule explicitly depending on response"
569 " contents without any 'tcp-response inspect-delay' setting."
570 " This means that this rule will randomly find its contents. This can be fixed by"
571 " setting the tcp-response inspect-delay.\n",
572 proxy_type_str(px), px->id);
573 }
574 }
Christopher Faulet2b677702022-06-22 16:55:04 +0200575 }
576
577 end:
578 rule->arg.act.p[3] = conf;
579 return 1;
580}
581
582/* Release memory allocated by "set-bandwidth-limit" action. */
583static void release_bwlim_action(struct act_rule *rule)
584{
Christopher Fauletf0196f42022-06-24 14:52:18 +0200585 ha_free(&rule->arg.act.p[0]);
Christopher Fauletda2e1172023-01-13 15:33:32 +0100586 if ((rule->action & BWLIM_ACT_LIMIT_EXPR) && rule->arg.act.p[1]) {
Christopher Fauletf0196f42022-06-24 14:52:18 +0200587 release_sample_expr(rule->arg.act.p[1]);
588 rule->arg.act.p[1] = NULL;
589 }
Christopher Fauletda2e1172023-01-13 15:33:32 +0100590 if ((rule->action & BWLIM_ACT_PERIOD_EXPR) && rule->arg.act.p[2]) {
Christopher Fauletf0196f42022-06-24 14:52:18 +0200591 release_sample_expr(rule->arg.act.p[2]);
592 rule->arg.act.p[2] = NULL;
593 }
594 rule->arg.act.p[3] = NULL; /* points on the filter's config */
Christopher Faulet2b677702022-06-22 16:55:04 +0200595}
596
597/* Parse "set-bandwidth-limit" action. The filter name must be specified. For
598 * shared limitations, there is no other supported parameter. For per-stream
599 * limitations, a custom limit and period may be specified. In both case, it
600 * must be an expression. On success:
601 *
602 * arg.act.p[0] will be the filter name (mandatory)
603 * arg.act.p[1] will be an expression for the custom limit (optional, may be NULL)
Ilya Shipitsin3b64a282022-07-29 22:26:53 +0500604 * arg.act.p[2] will be an expression for the custom period (optional, may be NULL)
Christopher Faulet2b677702022-06-22 16:55:04 +0200605 *
606 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
607 */
608static enum act_parse_ret parse_bandwidth_limit(const char **args, int *orig_arg, struct proxy *px,
609 struct act_rule *rule, char **err)
610{
611 struct sample_expr *expr;
612 int cur_arg;
613
614 cur_arg = *orig_arg;
615
616 if (!*args[cur_arg]) {
617 memprintf(err, "missing bwlim filter name");
618 return ACT_RET_PRS_ERR;
619 }
620
621 rule->arg.act.p[0] = strdup(args[cur_arg]);
622 if (!rule->arg.act.p[0]) {
623 memprintf(err, "out of memory");
624 return ACT_RET_PRS_ERR;
625 }
626 cur_arg++;
627
628 while (1) {
629 if (strcmp(args[cur_arg], "limit") == 0) {
Christopher Fauletda2e1172023-01-13 15:33:32 +0100630 const char *res;
631 unsigned int limit;
632
Christopher Faulet2b677702022-06-22 16:55:04 +0200633 cur_arg++;
634 if (!args[cur_arg]) {
Christopher Fauletda2e1172023-01-13 15:33:32 +0100635 memprintf(err, "missing limit value or expression");
Christopher Faulet2b677702022-06-22 16:55:04 +0200636 goto error;
637 }
638
Christopher Fauletda2e1172023-01-13 15:33:32 +0100639 res = parse_size_err(args[cur_arg], &limit);
640 if (!res) {
641 rule->action |= BWLIM_ACT_LIMIT_CONST;
642 rule->arg.act.p[1] = (void *)(uintptr_t)limit;
643 cur_arg++;
644 continue;
645 }
646
647 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, NULL, &px->conf.args, NULL);
648 if (!expr) {
649 memprintf(err, "'%s': invalid size value or unknown fetch method '%s'", args[cur_arg-1], args[cur_arg]);
Christopher Faulet2b677702022-06-22 16:55:04 +0200650 goto error;
Christopher Fauletda2e1172023-01-13 15:33:32 +0100651 }
652 rule->action |= BWLIM_ACT_LIMIT_EXPR;
Christopher Faulet2b677702022-06-22 16:55:04 +0200653 rule->arg.act.p[1] = expr;
654 }
655 else if (strcmp(args[cur_arg], "period") == 0) {
Christopher Fauletda2e1172023-01-13 15:33:32 +0100656 const char *res;
657 unsigned int period;
658
Christopher Faulet2b677702022-06-22 16:55:04 +0200659 cur_arg++;
660 if (!args[cur_arg]) {
Christopher Fauletda2e1172023-01-13 15:33:32 +0100661 memprintf(err, "missing period value or expression");
Christopher Faulet2b677702022-06-22 16:55:04 +0200662 goto error;
663 }
664
Christopher Fauletda2e1172023-01-13 15:33:32 +0100665 res = parse_time_err(args[cur_arg], &period, TIME_UNIT_MS);
666 if (!res) {
667 rule->action |= BWLIM_ACT_PERIOD_CONST;
668 rule->arg.act.p[2] = (void *)(uintptr_t)period;
669 cur_arg++;
670 continue;
671 }
672
673 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, NULL, &px->conf.args, NULL);
674 if (!expr) {
675 memprintf(err, "'%s': invalid time value or unknown fetch method '%s'", args[cur_arg-1], args[cur_arg]);
Christopher Faulet2b677702022-06-22 16:55:04 +0200676 goto error;
Christopher Fauletda2e1172023-01-13 15:33:32 +0100677 }
678 rule->action |= BWLIM_ACT_PERIOD_EXPR;
Christopher Faulet2b677702022-06-22 16:55:04 +0200679 rule->arg.act.p[2] = expr;
680 }
681 else
682 break;
683 }
684
685 rule->action_ptr = bwlim_set_limit;
686 rule->check_ptr = check_bwlim_action;
687 rule->release_ptr = release_bwlim_action;
688
689 *orig_arg = cur_arg;
690 return ACT_RET_PRS_OK;
691
692error:
693 release_bwlim_action(rule);
694 return ACT_RET_PRS_ERR;
695}
696
697
698static struct action_kw_list tcp_req_cont_actions = {
699 .kw = {
700 { "set-bandwidth-limit", parse_bandwidth_limit, 0 },
701 { NULL, NULL }
702 }
703};
704
705static struct action_kw_list tcp_res_cont_actions = {
706 .kw = {
707 { "set-bandwidth-limit", parse_bandwidth_limit, 0 },
708 { NULL, NULL }
709 }
710};
711
712static struct action_kw_list http_req_actions = {
713 .kw = {
714 { "set-bandwidth-limit", parse_bandwidth_limit, 0 },
715 { NULL, NULL }
716 }
717};
718
719static struct action_kw_list http_res_actions = {
720 .kw = {
721 { "set-bandwidth-limit", parse_bandwidth_limit, 0 },
722 { NULL, NULL }
723 }
724};
725
726INITCALL1(STG_REGISTER, tcp_req_cont_keywords_register, &tcp_req_cont_actions);
727INITCALL1(STG_REGISTER, tcp_res_cont_keywords_register, &tcp_res_cont_actions);
728INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
729INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
730
731
732/* Generic function to parse bandwidth limitation filter configurartion. It
733 * Returns -1 on error and 0 on success. It handles configuration for per-stream
734 * and shared limitations.
735 */
736static int parse_bwlim_flt(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf,
737 char **err, void *private)
738{
739 struct flt_conf *fc;
740 struct bwlim_config *conf;
741 int shared, per_stream;
742 int pos = *cur_arg + 1;
743
744 conf = calloc(1, sizeof(*conf));
745 if (!conf) {
746 memprintf(err, "%s: out of memory", args[*cur_arg]);
747 return -1;
748 }
749 conf->proxy = px;
750
751 if (!*args[pos]) {
752 memprintf(err, "'%s' : a name is expected as first argument ", args[*cur_arg]);
753 goto error;
754 }
755 conf->flags = BWLIM_FL_NONE;
756 conf->name = strdup(args[pos]);
757 if (!conf->name) {
758 memprintf(err, "%s: out of memory", args[*cur_arg]);
759 goto error;
760 }
761
762 list_for_each_entry(fc, &px->filter_configs, list) {
763 if (fc->id == bwlim_flt_id) {
764 struct bwlim_config *c = fc->conf;
765
Tim Duesterhusa6fc6162022-10-08 12:33:19 +0200766 if (strcmp(conf->name, c->name) == 0) {
Christopher Faulet2b677702022-06-22 16:55:04 +0200767 memprintf(err, "bwlim filter '%s' already declared for proxy '%s'\n",
768 conf->name, px->id);
769 goto error;
770 }
771 }
772 }
773 shared = per_stream = 0;
774 pos++;
775 while (*args[pos]) {
776 if (strcmp(args[pos], "key") == 0) {
777 if (per_stream) {
778 memprintf(err, "'%s' : cannot mix per-stream and shared parameter",
779 args[*cur_arg]);
780 goto error;
781 }
782 if (!*args[pos + 1]) {
783 memprintf(err, "'%s' : the sample expression is missing for '%s' option",
784 args[*cur_arg], args[pos]);
785 goto error;
786 }
787 shared = 1;
788 pos++;
789 conf->expr = sample_parse_expr((char **)args, &pos, px->conf.args.file, px->conf.args.line,
790 err, &px->conf.args, NULL);
791 if (!conf->expr)
792 goto error;
793 }
794 else if (strcmp(args[pos], "table") == 0) {
795 if (per_stream) {
796 memprintf(err, "'%s' : cannot mix per-stream and shared parameter",
797 args[*cur_arg]);
798 goto error;
799 }
800 if (!*args[pos + 1]) {
801 memprintf(err, "'%s' : the table name is missing for '%s' option",
802 args[*cur_arg], args[pos]);
803 goto error;
804 }
805 shared = 1;
806 conf->table.n = strdup(args[pos + 1]);
807 if (!conf->table.n) {
808 memprintf(err, "%s: out of memory", args[*cur_arg]);
809 goto error;
810 }
811 pos += 2;
812 }
813 else if (strcmp(args[pos], "default-period") == 0) {
814 const char *res;
815
816 if (shared) {
817 memprintf(err, "'%s' : cannot mix per-stream and shared parameter",
818 args[*cur_arg]);
819 goto error;
820 }
821 if (!*args[pos + 1]) {
822 memprintf(err, "'%s' : the value is missing for '%s' option",
823 args[*cur_arg], args[pos]);
824 goto error;
825 }
826 per_stream = 1;
827 res = parse_time_err(args[pos + 1], &conf->period, TIME_UNIT_MS);
828 if (res) {
829 memprintf(err, "'%s' : invalid value for option '%s' (unexpected character '%c')",
830 args[*cur_arg], args[pos], *res);
831 goto error;
832 }
833 pos += 2;
834 }
835 else if (strcmp(args[pos], "limit") == 0) {
836 const char *res;
837
838 if (per_stream) {
839 memprintf(err, "'%s' : cannot mix per-stream and shared parameter",
840 args[*cur_arg]);
841 goto error;
842 }
843 if (!*args[pos + 1]) {
844 memprintf(err, "'%s' : the value is missing for '%s' option",
845 args[*cur_arg], args[pos]);
846 goto error;
847 }
848 shared = 1;
849 res = parse_size_err(args[pos + 1], &conf->limit);
850 if (res) {
851 memprintf(err, "'%s' : invalid value for option '%s' (unexpected character '%c')",
852 args[*cur_arg], args[pos], *res);
853 goto error;
854 }
855 pos += 2;
856 }
857 else if (strcmp(args[pos], "default-limit") == 0) {
858 const char *res;
859
860 if (shared) {
861 memprintf(err, "'%s' : cannot mix per-stream and shared parameter",
862 args[*cur_arg]);
863 goto error;
864 }
865 if (!*args[pos + 1]) {
866 memprintf(err, "'%s' : the value is missing for '%s' option",
867 args[*cur_arg], args[pos]);
868 goto error;
869 }
870 per_stream = 1;
871 res = parse_size_err(args[pos + 1], &conf->limit);
872 if (res) {
873 memprintf(err, "'%s' : invalid value for option '%s' (unexpected character '%c')",
874 args[*cur_arg], args[pos], *res);
875 goto error;
876 }
877 pos += 2;
878 }
879 else if (strcmp(args[pos], "min-size") == 0) {
880 const char *res;
881
882 if (!*args[pos + 1]) {
883 memprintf(err, "'%s' : the value is missing for '%s' option",
884 args[*cur_arg], args[pos]);
885 goto error;
886 }
887 res = parse_size_err(args[pos + 1], &conf->min_size);
888 if (res) {
889 memprintf(err, "'%s' : invalid value for option '%s' (unexpected character '%c')",
890 args[*cur_arg], args[pos], *res);
891 goto error;
892 }
893 pos += 2;
894 }
895 else
896 break;
897 }
898
899 if (shared) {
900 conf->flags |= BWLIM_FL_SHARED;
901 if (!conf->expr) {
902 memprintf(err, "'%s' : <key> option is missing", args[*cur_arg]);
903 goto error;
904 }
905 if (!conf->limit) {
906 memprintf(err, "'%s' : <limit> option is missing", args[*cur_arg]);
907 goto error;
908 }
909 }
910 else {
911 /* Per-stream: limit downloads only for now */
912 conf->flags |= BWLIM_FL_OUT;
913 if (!conf->period) {
914 memprintf(err, "'%s' : <default-period> option is missing", args[*cur_arg]);
915 goto error;
916 }
917 if (!conf->limit) {
918 memprintf(err, "'%s' : <default-limit> option is missing", args[*cur_arg]);
919 goto error;
920 }
921 }
922
923 *cur_arg = pos;
924 fconf->id = bwlim_flt_id;
925 fconf->ops = &bwlim_ops;
926 fconf->conf = conf;
927 return 0;
928
929 error:
930 if (conf->name)
Christopher Fauletf0196f42022-06-24 14:52:18 +0200931 ha_free(&conf->name);
932 if (conf->expr) {
Christopher Faulet2b677702022-06-22 16:55:04 +0200933 release_sample_expr(conf->expr);
Christopher Fauletf0196f42022-06-24 14:52:18 +0200934 conf->expr = NULL;
935 }
Christopher Faulet2b677702022-06-22 16:55:04 +0200936 if (conf->table.n)
Christopher Fauletf0196f42022-06-24 14:52:18 +0200937 ha_free(&conf->table.n);
Christopher Faulet2b677702022-06-22 16:55:04 +0200938 free(conf);
939 return -1;
940}
941
942
943static int parse_bwlim_in_flt(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf,
944 char **err, void *private)
945{
946 int ret;
947
948 ret = parse_bwlim_flt(args, cur_arg, px, fconf, err, private);
949 if (!ret) {
950 struct bwlim_config *conf = fconf->conf;
951
952 conf->flags |= BWLIM_FL_IN;
953 }
954
955 return ret;
956}
957
958static int parse_bwlim_out_flt(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf,
959 char **err, void *private)
960{
961 int ret;
962
963 ret = parse_bwlim_flt(args, cur_arg, px, fconf, err, private);
964 if (!ret) {
965 struct bwlim_config *conf = fconf->conf;
966
967 conf->flags |= BWLIM_FL_OUT;
968 }
969 return ret;
970}
971
972/* Declare the filter parser for "trace" keyword */
973static struct flt_kw_list flt_kws = { "BWLIM", { }, {
974 { "bwlim-in", parse_bwlim_in_flt, NULL },
975 { "bwlim-out", parse_bwlim_out_flt, NULL },
976 { NULL, NULL, NULL },
977 }
978};
979
980INITCALL1(STG_REGISTER, flt_register_keywords, &flt_kws);