blob: 0340e87b6f8f487ae74f727d1ce886285810a246 [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
38struct bwlim_config {
39 struct proxy *proxy;
40 char *name;
41 unsigned int flags;
42 struct sample_expr *expr;
43 union {
44 char *n;
45 struct stktable *t;
46 } table;
47 unsigned int period;
48 unsigned int limit;
49 unsigned int min_size;
50};
51
52struct bwlim_state {
53 struct freq_ctr bytes_rate;
54 struct stksess *ts;
55 struct act_rule *rule;
56 unsigned int limit;
57 unsigned int period;
58 unsigned int exp;
59};
60
61
62/* Pools used to allocate comp_state structs */
63DECLARE_STATIC_POOL(pool_head_bwlim_state, "bwlim_state", sizeof(struct bwlim_state));
64
65
66/* Apply the bandwidth limitation of the filter <filter>. <len> is the maximum
67 * amount of data that the filter can forward. This function applies the
68 * limitation and returns what the stream is authorized to forward. Several
69 * limitation can be stacked.
70 */
71static int bwlim_apply_limit(struct filter *filter, struct channel *chn, unsigned int len)
72{
73 struct bwlim_config *conf = FLT_CONF(filter);
74 struct bwlim_state *st = filter->ctx;
75 struct freq_ctr *bytes_rate;
76 unsigned int period, limit, remain, tokens, users;
77 unsigned int wait = 0;
78 int overshoot, ret = 0;
79
80 /* Don't forward anything if there is nothing to forward or the waiting
81 * time is not expired
82 */
83 if (!len || (tick_isset(st->exp) && !tick_is_expired(st->exp, now_ms)))
84 goto end;
85
86 st->exp = TICK_ETERNITY;
87 ret = len;
88 if (conf->flags & BWLIM_FL_SHARED) {
89 void *ptr;
90 unsigned int type = ((conf->flags & BWLIM_FL_IN) ? STKTABLE_DT_BYTES_IN_RATE : STKTABLE_DT_BYTES_OUT_RATE);
91
92 /* In shared mode, get a pointer on the stick table entry. it
93 * will be used to get the freq-counter. It is also used to get
94 * The number of users.
95 */
96 ptr = stktable_data_ptr(conf->table.t, st->ts, type);
97 if (!ptr)
98 goto end;
99
100 HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &st->ts->lock);
101 bytes_rate = &stktable_data_cast(ptr, std_t_frqp);
102 period = conf->table.t->data_arg[type].u;
103 limit = conf->limit;
104 users = st->ts->ref_cnt;
105 }
106 else {
107 /* On per-stream mode, the freq-counter is private to the
108 * stream. Get it from the filter state. Rely on the custom
109 * limit/period if defined or use the defaut ones. In this mode,
110 * there is only one user.
111 */
112 bytes_rate = &st->bytes_rate;
113 period = (st->period ? st->period : conf->period);
114 limit = (st->limit ? st->limit : conf->limit);
115 users = 1;
116 }
117
118 /* Be sure the current rate does not exceed the limit over the current
119 * period. In this case, nothing is forwarded and the waiting time is
120 * computed to be sure to not retry too early.
121 *
122 * The test is used to avoid the initial burst. Otherwise, streams will
123 * consume the limit as fast as possible and will then be paused for
124 * long time.
125 */
126 overshoot = freq_ctr_overshoot_period(bytes_rate, period, limit);
127 if (overshoot > 0) {
128 if (conf->flags & BWLIM_FL_SHARED)
129 HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &st->ts->lock);
130 wait = div64_32((uint64_t)(conf->min_size + overshoot) * period * users,
131 limit);
132 st->exp = tick_add(now_ms, (wait ? wait : 1));
133 ret = 0;
134 goto end;
135 }
136
137 /* Get the allowed quota per user. */
138 remain = freq_ctr_remain_period(bytes_rate, period, limit, 0);
139 tokens = div64_32((uint64_t)(remain + users - 1), users);
140
141 if (tokens < len) {
142 /* The stream cannot forward all its data. But we will check if
143 * it can perform a small burst if the global quota is large
144 * enought. But, in this case, its waiting time will be
145 * increased accordingly.
146 */
147 ret = tokens;
148 if (tokens < conf->min_size) {
149 ret = (chn->flags & (CF_EOI|CF_SHUTR|CF_READ_ERROR))
150 ? MIN(len, conf->min_size)
151 : conf->min_size;
152
153 if (ret <= remain)
154 wait = div64_32((uint64_t)(ret - tokens) * period * users + limit - 1, limit);
155 else
156 ret = (limit < ret) ? remain : 0;
157 }
158 }
159
160 /* At the end, update the freq-counter and compute the waiting time if
161 * the stream is limited
162 */
163 update_freq_ctr_period(bytes_rate, period, ret);
164 if (ret < len) {
165 wait += next_event_delay_period(bytes_rate, period, limit, MIN(len - ret, conf->min_size * users));
166 st->exp = tick_add(now_ms, (wait ? wait : 1));
167 }
168
169 if (conf->flags & BWLIM_FL_SHARED)
170 HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &st->ts->lock);
171
172 end:
173 chn->analyse_exp = tick_first((tick_is_expired(chn->analyse_exp, now_ms) ? TICK_ETERNITY : chn->analyse_exp),
174 st->exp);
175 return ret;
176}
177
178/***************************************************************************
179 * Hooks that manage the filter lifecycle (init/check/deinit)
180 **************************************************************************/
181/* Initialize the filter. Returns -1 on error, else 0. */
182static int bwlim_init(struct proxy *px, struct flt_conf *fconf)
183{
184 fconf->flags |= FLT_CFG_FL_HTX;
185 return 0;
186}
187
188/* Free resources allocated by the bwlim filter. */
189static void bwlim_deinit(struct proxy *px, struct flt_conf *fconf)
190{
191 struct bwlim_config *conf = fconf->conf;
192
193 if (conf) {
194 free(conf->name);
195 release_sample_expr(conf->expr);
196 free(conf);
197 }
198}
199
200/* Check configuration of a bwlim filter for a specified proxy.
201 * Return 1 on error, else 0. */
202static int bwlim_check(struct proxy *px, struct flt_conf *fconf)
203{
204 struct bwlim_config *conf = fconf->conf;
205 struct stktable *target;
206
207 if (!(conf->flags & BWLIM_FL_SHARED))
208 return 0;
209
210 if (conf->table.n)
211 target = stktable_find_by_name(conf->table.n);
212 else
213 target = px->table;
214
215 if (!target) {
216 ha_alert("Proxy %s : unable to find table '%s' referenced by bwlim filter '%s'",
217 px->id, conf->table.n ? conf->table.n : px->id, conf->name);
218 return 1;
219 }
220
221 if ((conf->flags & BWLIM_FL_IN) && !target->data_ofs[STKTABLE_DT_BYTES_IN_RATE]) {
222 ha_alert("Proxy %s : stick-table '%s' uses a data type incompatible with bwlim filter '%s'."
223 " It must be 'bytes_in_rate'",
224 px->id, conf->table.n ? conf->table.n : px->id, conf->name);
225 return 1;
226 }
227 else if ((conf->flags & BWLIM_FL_OUT) && !target->data_ofs[STKTABLE_DT_BYTES_OUT_RATE]) {
228 ha_alert("Proxy %s : stick-table '%s' uses a data type incompatible with bwlim filter '%s'."
229 " It must be 'bytes_out_rate'",
230 px->id, conf->table.n ? conf->table.n : px->id, conf->name);
231 return 1;
232 }
233
234 if (!stktable_compatible_sample(conf->expr, target->type)) {
235 ha_alert("Proxy %s : stick-table '%s' uses a key type incompatible with bwlim filter '%s'",
236 px->id, conf->table.n ? conf->table.n : px->id, conf->name);
237 return 1;
238 }
239 else {
240 if (!in_proxies_list(target->proxies_list, px)) {
241 px->next_stkt_ref = target->proxies_list;
242 target->proxies_list = px;
243 }
244 free(conf->table.n);
245 conf->table.t = target;
246 }
247
248 return 0;
249}
250
251/**************************************************************************
252 * Hooks to handle start/stop of streams
253 *************************************************************************/
254/* Called when a filter instance is created and attach to a stream */
255static int bwlim_attach(struct stream *s, struct filter *filter)
256{
257 struct bwlim_state *st;
258
259 st = pool_zalloc(pool_head_bwlim_state);
260 if (!st)
261 return -1;
262 filter->ctx = st;
263 return 1;
264}
265
266/* Called when a filter instance is detach from a stream, just before its
267 * destruction */
268static void bwlim_detach(struct stream *s, struct filter *filter)
269{
270 struct bwlim_config *conf = FLT_CONF(filter);
271 struct bwlim_state *st = filter->ctx;
272 struct stktable *t = conf->table.t;
273
274 if (!st)
275 return;
276
277 if (st->ts)
278 stktable_touch_local(t, st->ts, 1);
279
280 /* release any possible compression context */
281 pool_free(pool_head_bwlim_state, st);
282 filter->ctx = NULL;
283}
284
285/**************************************************************************
286 * Hooks to filter HTTP messages
287 *************************************************************************/
288static int bwlim_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg)
289{
290 msg->chn->analyse_exp = TICK_ETERNITY;
291 return 1;
292}
293
294static int bwlim_http_payload(struct stream *s, struct filter *filter, struct http_msg *msg,
295 unsigned int offset, unsigned int len)
296{
297 return bwlim_apply_limit(filter, msg->chn, len);
298}
299
300/**************************************************************************
301 * Hooks to filter TCP data
302 *************************************************************************/
303static int bwlim_tcp_payload(struct stream *s, struct filter *filter, struct channel *chn,
304 unsigned int offset, unsigned int len)
305{
306 return bwlim_apply_limit(filter, chn, len);
307}
308
309/********************************************************************
310 * Functions that manage the filter initialization
311 ********************************************************************/
312struct flt_ops bwlim_ops = {
313 /* Manage bwlim filter, called for each filter declaration */
314 .init = bwlim_init,
315 .deinit = bwlim_deinit,
316 .check = bwlim_check,
317
318 /* Handle start/stop of streams */
319 .attach = bwlim_attach,
320 .detach = bwlim_detach,
321
322
323 /* Filter HTTP requests and responses */
324 .http_headers = bwlim_http_headers,
325 .http_payload = bwlim_http_payload,
326
327 /* Filter TCP data */
328 .tcp_payload = bwlim_tcp_payload,
329};
330
331/* Set a bandwidth limitation. It always return ACT_RET_CONT. On error, the rule
332 * is ignored. First of all, it looks for the corresponding filter. Then, for a
333 * shared limitation, the stick-table entry is retrieved. For a per-stream
334 * limitation, the custom limit and period are computed, if necessary. At the
335 * end, the filter is registered on the data filtering for the right channel
336 * (bwlim-in = request, bwlim-out = response).
337 */
338static enum act_return bwlim_set_limit(struct act_rule *rule, struct proxy *px,
339 struct session *sess, struct stream *s, int flags)
340{
341 struct bwlim_config *conf = rule->arg.act.p[3];
342 struct filter *filter;
343 struct bwlim_state *st = NULL;
344 struct stktable *t;
345 struct stktable_key *key;
346 struct stksess *ts;
347 int opt;
348
349 list_for_each_entry(filter, &s->strm_flt.filters, list) {
350 if (FLT_ID(filter) == bwlim_flt_id && FLT_CONF(filter) == conf) {
351 st = filter->ctx;
352 break;
353 }
354 }
355
356 if (!st)
357 goto end;
358
359 switch (rule->from) {
360 case ACT_F_TCP_REQ_CNT: opt = SMP_OPT_DIR_REQ | SMP_OPT_FINAL; break;
361 case ACT_F_TCP_RES_CNT: opt = SMP_OPT_DIR_RES | SMP_OPT_FINAL; break;
362 case ACT_F_HTTP_REQ: opt = SMP_OPT_DIR_REQ | SMP_OPT_FINAL; break;
363 case ACT_F_HTTP_RES: opt = SMP_OPT_DIR_RES | SMP_OPT_FINAL; break;
364 default:
365 goto end;
366 }
367
368 if (conf->flags & BWLIM_FL_SHARED) {
369 t = conf->table.t;
370 key = stktable_fetch_key(t, px, sess, s, opt, conf->expr, NULL);
371 if (!key)
372 goto end;
373
374 ts = stktable_get_entry(t, key);
375 if (!ts)
376 goto end;
377
378 st->ts = ts;
379 st->rule = rule;
380 }
381 else {
382 struct sample *smp;
383
384 st->limit = 0;
385 st->period = 0;
386 if (rule->arg.act.p[1]) {
387 smp = sample_fetch_as_type(px, sess, s, opt, rule->arg.act.p[1], SMP_T_SINT);
388 if (smp && smp->data.u.sint > 0)
389 st->limit = smp->data.u.sint;
390 }
391 if (rule->arg.act.p[2]) {
392 smp = sample_fetch_as_type(px, sess, s, opt, rule->arg.act.p[2], SMP_T_SINT);
393 if (smp && smp->data.u.sint > 0)
394 st->period = smp->data.u.sint;
395 }
396 }
397
398 st->exp = TICK_ETERNITY;
399 if (conf->flags & BWLIM_FL_IN)
400 register_data_filter(s, &s->req, filter);
401 else
402 register_data_filter(s, &s->res, filter);
403
404 end:
405 return ACT_RET_CONT;
406}
407
408/* Check function for "set-bandwidth-limit" aciton. It returns 1 on
409 * success. Otherwise, it returns 0 and <err> is filled.
410 */
411int check_bwlim_action(struct act_rule *rule, struct proxy *px, char **err)
412{
413 struct flt_conf *fconf;
414 struct bwlim_config *conf = NULL;
415 unsigned int where;
416
417 list_for_each_entry(fconf, &px->filter_configs, list) {
418 conf = NULL;
419 if (fconf->id == bwlim_flt_id) {
420 conf = fconf->conf;
421 if (!strcmp(rule->arg.act.p[0], conf->name))
422 break;
423 }
424 }
425 if (!conf) {
426 memprintf(err, "unable to find bwlim filter '%s' referenced by set-bandwidth-limit rule",
427 (char *)rule->arg.act.p[0]);
428 return 0;
429 }
430
431 if ((conf->flags & BWLIM_FL_SHARED) && rule->arg.act.p[1]) {
432 memprintf(err, "set-bandwidth-limit rule cannot define a limit for a shared bwlim filter");
433 return 0;
434 }
435
436 where = 0;
437 if (px->cap & PR_CAP_FE)
438 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_FE_HRS_HDR);
439 if (px->cap & PR_CAP_BE)
440 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_BE_HRQ_HDR : SMP_VAL_BE_HRS_HDR);
441
442 if (rule->arg.act.p[1]) {
443 struct sample_expr *expr = rule->arg.act.p[1];
444
445 if (!(expr->fetch->val & where)) {
446 memprintf(err, "set-bandwidth-limit rule uses a limit extracting information from '%s', none of which is available here",
447 sample_src_names(expr->fetch->use));
448 return 0;
449 }
450
451 if (rule->from == ACT_F_TCP_REQ_CNT && (px->cap & PR_CAP_FE)) {
452 if (!px->tcp_req.inspect_delay && !(expr->fetch->val & SMP_VAL_FE_SES_ACC)) {
453 ha_warning("%s '%s' : a 'tcp-request content set-bandwidth-limit*' rule explicitly depending on request"
454 " contents without any 'tcp-request inspect-delay' setting."
455 " This means that this rule will randomly find its contents. This can be fixed by"
456 " setting the tcp-request inspect-delay.\n",
457 proxy_type_str(px), px->id);
458 }
459 }
460 }
461
462 if (conf->expr) {
463 if (!(conf->expr->fetch->val & where)) {
464 memprintf(err, "bwlim filter '%s uses a key extracting information from '%s', none of which is available here",
465 conf->name, sample_src_names(conf->expr->fetch->use));
466 return 0;
467 }
468
469 if (rule->from == ACT_F_TCP_REQ_CNT && (px->cap & PR_CAP_FE)) {
470 if (!px->tcp_req.inspect_delay && !(conf->expr->fetch->val & SMP_VAL_FE_SES_ACC)) {
471 ha_warning("%s '%s' : a 'tcp-request content set-bandwidth-limit*' rule explicitly depending on request"
472 " contents without any 'tcp-request inspect-delay' setting."
473 " This means that this rule will randomly find its contents. This can be fixed by"
474 " setting the tcp-request inspect-delay.\n",
475 proxy_type_str(px), px->id);
476 }
477 }
478 }
479
480 end:
481 rule->arg.act.p[3] = conf;
482 return 1;
483}
484
485/* Release memory allocated by "set-bandwidth-limit" action. */
486static void release_bwlim_action(struct act_rule *rule)
487{
488 free(rule->arg.act.p[0]);
489 if (rule->arg.act.p[1])
490 release_sample_expr(rule->arg.act.p[1]);
491 if (rule->arg.act.p[2])
492 release_sample_expr(rule->arg.act.p[2]);
493}
494
495/* Parse "set-bandwidth-limit" action. The filter name must be specified. For
496 * shared limitations, there is no other supported parameter. For per-stream
497 * limitations, a custom limit and period may be specified. In both case, it
498 * must be an expression. On success:
499 *
500 * arg.act.p[0] will be the filter name (mandatory)
501 * arg.act.p[1] will be an expression for the custom limit (optional, may be NULL)
502 * arg.act.p[2] will be an expression for the custom period (optiona, may be NULLl)
503 *
504 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
505 */
506static enum act_parse_ret parse_bandwidth_limit(const char **args, int *orig_arg, struct proxy *px,
507 struct act_rule *rule, char **err)
508{
509 struct sample_expr *expr;
510 int cur_arg;
511
512 cur_arg = *orig_arg;
513
514 if (!*args[cur_arg]) {
515 memprintf(err, "missing bwlim filter name");
516 return ACT_RET_PRS_ERR;
517 }
518
519 rule->arg.act.p[0] = strdup(args[cur_arg]);
520 if (!rule->arg.act.p[0]) {
521 memprintf(err, "out of memory");
522 return ACT_RET_PRS_ERR;
523 }
524 cur_arg++;
525
526 while (1) {
527 if (strcmp(args[cur_arg], "limit") == 0) {
528 cur_arg++;
529 if (!args[cur_arg]) {
530 memprintf(err, "missing limit expression");
531 goto error;
532 }
533
534 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
535 if (!expr)
536 goto error;
537 rule->arg.act.p[1] = expr;
538 }
539 else if (strcmp(args[cur_arg], "period") == 0) {
540 cur_arg++;
541 if (!args[cur_arg]) {
542 memprintf(err, "missing period expression");
543 goto error;
544 }
545
546 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
547 if (!expr)
548 goto error;
549 rule->arg.act.p[2] = expr;
550 }
551 else
552 break;
553 }
554
555 rule->action_ptr = bwlim_set_limit;
556 rule->check_ptr = check_bwlim_action;
557 rule->release_ptr = release_bwlim_action;
558
559 *orig_arg = cur_arg;
560 return ACT_RET_PRS_OK;
561
562error:
563 release_bwlim_action(rule);
564 return ACT_RET_PRS_ERR;
565}
566
567
568static struct action_kw_list tcp_req_cont_actions = {
569 .kw = {
570 { "set-bandwidth-limit", parse_bandwidth_limit, 0 },
571 { NULL, NULL }
572 }
573};
574
575static struct action_kw_list tcp_res_cont_actions = {
576 .kw = {
577 { "set-bandwidth-limit", parse_bandwidth_limit, 0 },
578 { NULL, NULL }
579 }
580};
581
582static struct action_kw_list http_req_actions = {
583 .kw = {
584 { "set-bandwidth-limit", parse_bandwidth_limit, 0 },
585 { NULL, NULL }
586 }
587};
588
589static struct action_kw_list http_res_actions = {
590 .kw = {
591 { "set-bandwidth-limit", parse_bandwidth_limit, 0 },
592 { NULL, NULL }
593 }
594};
595
596INITCALL1(STG_REGISTER, tcp_req_cont_keywords_register, &tcp_req_cont_actions);
597INITCALL1(STG_REGISTER, tcp_res_cont_keywords_register, &tcp_res_cont_actions);
598INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
599INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
600
601
602/* Generic function to parse bandwidth limitation filter configurartion. It
603 * Returns -1 on error and 0 on success. It handles configuration for per-stream
604 * and shared limitations.
605 */
606static int parse_bwlim_flt(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf,
607 char **err, void *private)
608{
609 struct flt_conf *fc;
610 struct bwlim_config *conf;
611 int shared, per_stream;
612 int pos = *cur_arg + 1;
613
614 conf = calloc(1, sizeof(*conf));
615 if (!conf) {
616 memprintf(err, "%s: out of memory", args[*cur_arg]);
617 return -1;
618 }
619 conf->proxy = px;
620
621 if (!*args[pos]) {
622 memprintf(err, "'%s' : a name is expected as first argument ", args[*cur_arg]);
623 goto error;
624 }
625 conf->flags = BWLIM_FL_NONE;
626 conf->name = strdup(args[pos]);
627 if (!conf->name) {
628 memprintf(err, "%s: out of memory", args[*cur_arg]);
629 goto error;
630 }
631
632 list_for_each_entry(fc, &px->filter_configs, list) {
633 if (fc->id == bwlim_flt_id) {
634 struct bwlim_config *c = fc->conf;
635
636 if (!strcmp(conf->name, c->name)) {
637 memprintf(err, "bwlim filter '%s' already declared for proxy '%s'\n",
638 conf->name, px->id);
639 goto error;
640 }
641 }
642 }
643 shared = per_stream = 0;
644 pos++;
645 while (*args[pos]) {
646 if (strcmp(args[pos], "key") == 0) {
647 if (per_stream) {
648 memprintf(err, "'%s' : cannot mix per-stream and shared parameter",
649 args[*cur_arg]);
650 goto error;
651 }
652 if (!*args[pos + 1]) {
653 memprintf(err, "'%s' : the sample expression is missing for '%s' option",
654 args[*cur_arg], args[pos]);
655 goto error;
656 }
657 shared = 1;
658 pos++;
659 conf->expr = sample_parse_expr((char **)args, &pos, px->conf.args.file, px->conf.args.line,
660 err, &px->conf.args, NULL);
661 if (!conf->expr)
662 goto error;
663 }
664 else if (strcmp(args[pos], "table") == 0) {
665 if (per_stream) {
666 memprintf(err, "'%s' : cannot mix per-stream and shared parameter",
667 args[*cur_arg]);
668 goto error;
669 }
670 if (!*args[pos + 1]) {
671 memprintf(err, "'%s' : the table name is missing for '%s' option",
672 args[*cur_arg], args[pos]);
673 goto error;
674 }
675 shared = 1;
676 conf->table.n = strdup(args[pos + 1]);
677 if (!conf->table.n) {
678 memprintf(err, "%s: out of memory", args[*cur_arg]);
679 goto error;
680 }
681 pos += 2;
682 }
683 else if (strcmp(args[pos], "default-period") == 0) {
684 const char *res;
685
686 if (shared) {
687 memprintf(err, "'%s' : cannot mix per-stream and shared parameter",
688 args[*cur_arg]);
689 goto error;
690 }
691 if (!*args[pos + 1]) {
692 memprintf(err, "'%s' : the value is missing for '%s' option",
693 args[*cur_arg], args[pos]);
694 goto error;
695 }
696 per_stream = 1;
697 res = parse_time_err(args[pos + 1], &conf->period, TIME_UNIT_MS);
698 if (res) {
699 memprintf(err, "'%s' : invalid value for option '%s' (unexpected character '%c')",
700 args[*cur_arg], args[pos], *res);
701 goto error;
702 }
703 pos += 2;
704 }
705 else if (strcmp(args[pos], "limit") == 0) {
706 const char *res;
707
708 if (per_stream) {
709 memprintf(err, "'%s' : cannot mix per-stream and shared parameter",
710 args[*cur_arg]);
711 goto error;
712 }
713 if (!*args[pos + 1]) {
714 memprintf(err, "'%s' : the value is missing for '%s' option",
715 args[*cur_arg], args[pos]);
716 goto error;
717 }
718 shared = 1;
719 res = parse_size_err(args[pos + 1], &conf->limit);
720 if (res) {
721 memprintf(err, "'%s' : invalid value for option '%s' (unexpected character '%c')",
722 args[*cur_arg], args[pos], *res);
723 goto error;
724 }
725 pos += 2;
726 }
727 else if (strcmp(args[pos], "default-limit") == 0) {
728 const char *res;
729
730 if (shared) {
731 memprintf(err, "'%s' : cannot mix per-stream and shared parameter",
732 args[*cur_arg]);
733 goto error;
734 }
735 if (!*args[pos + 1]) {
736 memprintf(err, "'%s' : the value is missing for '%s' option",
737 args[*cur_arg], args[pos]);
738 goto error;
739 }
740 per_stream = 1;
741 res = parse_size_err(args[pos + 1], &conf->limit);
742 if (res) {
743 memprintf(err, "'%s' : invalid value for option '%s' (unexpected character '%c')",
744 args[*cur_arg], args[pos], *res);
745 goto error;
746 }
747 pos += 2;
748 }
749 else if (strcmp(args[pos], "min-size") == 0) {
750 const char *res;
751
752 if (!*args[pos + 1]) {
753 memprintf(err, "'%s' : the value is missing for '%s' option",
754 args[*cur_arg], args[pos]);
755 goto error;
756 }
757 res = parse_size_err(args[pos + 1], &conf->min_size);
758 if (res) {
759 memprintf(err, "'%s' : invalid value for option '%s' (unexpected character '%c')",
760 args[*cur_arg], args[pos], *res);
761 goto error;
762 }
763 pos += 2;
764 }
765 else
766 break;
767 }
768
769 if (shared) {
770 conf->flags |= BWLIM_FL_SHARED;
771 if (!conf->expr) {
772 memprintf(err, "'%s' : <key> option is missing", args[*cur_arg]);
773 goto error;
774 }
775 if (!conf->limit) {
776 memprintf(err, "'%s' : <limit> option is missing", args[*cur_arg]);
777 goto error;
778 }
779 }
780 else {
781 /* Per-stream: limit downloads only for now */
782 conf->flags |= BWLIM_FL_OUT;
783 if (!conf->period) {
784 memprintf(err, "'%s' : <default-period> option is missing", args[*cur_arg]);
785 goto error;
786 }
787 if (!conf->limit) {
788 memprintf(err, "'%s' : <default-limit> option is missing", args[*cur_arg]);
789 goto error;
790 }
791 }
792
793 *cur_arg = pos;
794 fconf->id = bwlim_flt_id;
795 fconf->ops = &bwlim_ops;
796 fconf->conf = conf;
797 return 0;
798
799 error:
800 if (conf->name)
801 free(conf->name);
802 if (conf->expr)
803 release_sample_expr(conf->expr);
804 if (conf->table.n)
805 free(conf->table.n);
806 free(conf);
807 return -1;
808}
809
810
811static int parse_bwlim_in_flt(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf,
812 char **err, void *private)
813{
814 int ret;
815
816 ret = parse_bwlim_flt(args, cur_arg, px, fconf, err, private);
817 if (!ret) {
818 struct bwlim_config *conf = fconf->conf;
819
820 conf->flags |= BWLIM_FL_IN;
821 }
822
823 return ret;
824}
825
826static int parse_bwlim_out_flt(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf,
827 char **err, void *private)
828{
829 int ret;
830
831 ret = parse_bwlim_flt(args, cur_arg, px, fconf, err, private);
832 if (!ret) {
833 struct bwlim_config *conf = fconf->conf;
834
835 conf->flags |= BWLIM_FL_OUT;
836 }
837 return ret;
838}
839
840/* Declare the filter parser for "trace" keyword */
841static struct flt_kw_list flt_kws = { "BWLIM", { }, {
842 { "bwlim-in", parse_bwlim_in_flt, NULL },
843 { "bwlim-out", parse_bwlim_out_flt, NULL },
844 { NULL, NULL, NULL },
845 }
846};
847
848INITCALL1(STG_REGISTER, flt_register_keywords, &flt_kws);