From: Willy Tarreau Date: Mon, 7 Oct 2013 16:51:07 +0000 (+0200) Subject: MEDIUM: listener: add support for limiting the session rate in addition to the connec... X-Git-Tag: v1.5-dev22~23 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=93e7c006c1bc2e6a376af47a8587ea90580ac97a;p=thirdparty%2Fhaproxy.git MEDIUM: listener: add support for limiting the session rate in addition to the connection rate It's sometimes useful to be able to limit the connection rate on a machine running many haproxy instances (eg: per customer) but it removes the ability for that machine to defend itself against a DoS. Thus, better also provide a limit on the session rate, which does not include the connections rejected by "tcp-request connection" rules. This permits to have much higher limits on the connection rate without having to raise the session rate limit to insane values. The limit can be changed on the CLI using "set rate-limit sessions global", or in the global section using "maxsessrate". --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 030a9a6604..9e129de098 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -465,6 +465,7 @@ The following keywords are supported in the "global" section : - maxcomprate - maxcompcpuusage - maxpipes + - maxsessrate - maxsslconn - noepoll - nokqueue @@ -733,6 +734,16 @@ maxpipes The splice code dynamically allocates and releases pipes, and can fall back to standard copy, so setting this value too low may only impact performance. +maxsessrate + Sets the maximum per-process number of sessions per second to . + Proxies will stop accepting connections when this limit is reached. It can be + used to limit the global capacity regardless of each frontend capacity. It is + important to note that this can only be used as a service protection measure, + as there will not necessarily be a fair share between frontends when the + limit is reached, so it's a good idea to also limit each frontend to some + value close to its expected share. Also, lowering tune.maxaccept can improve + fairness. + maxsslconn Sets the maximum per-process number of concurrent SSL connections to . By default there is no SSL-specific limit, which means that the @@ -12598,6 +12609,12 @@ set rate-limit http-compression global passed in number of kilobytes per second. The value is available in the "show info" on the line "CompressBpsRateLim" in bytes. +set rate-limit sessions global + Change the process-wide session rate limit, which is set by the global + 'maxsessrate' setting. A value of zero disables the limitation. This limit + applies to all frontends and the change has an immediate effect. The value + is passed in number of sessions per second. + set table key [data. ]* Create or update a stick-table entry in the table. If the key is not present, an entry is inserted. See stick-table in section 4.2 to find all possible diff --git a/include/types/global.h b/include/types/global.h index 7d78d207d5..393ddc457f 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -80,9 +80,11 @@ struct global { char *connect_default_ciphers; #endif struct freq_ctr conn_per_sec; + struct freq_ctr sess_per_sec; struct freq_ctr comp_bps_in; /* bytes per second, before http compression */ struct freq_ctr comp_bps_out; /* bytes per second, after http compression */ int cps_lim, cps_max; + int sps_lim, sps_max; int comp_rate_lim; /* HTTP compression rate limit */ int maxpipes; /* max # of pipes */ int maxsock; /* max # of sockets */ diff --git a/src/cfgparse.c b/src/cfgparse.c index e11730e5ab..cab9d6ebca 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -875,6 +875,19 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm) } global.cps_lim = atol(args[1]); } + else if (!strcmp(args[0], "maxsessrate")) { + if (global.sps_lim != 0) { + Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.sps_lim = atol(args[1]); + } else if (!strcmp(args[0], "maxcomprate")) { if (*(args[1]) == 0) { Alert("parsing [%s:%d] : '%s' expects an integer argument in kb/s.\n", file, linenum, args[0]); diff --git a/src/dumpstats.c b/src/dumpstats.c index 227abc5461..b6a4dc4db6 100644 --- a/src/dumpstats.c +++ b/src/dumpstats.c @@ -1146,6 +1146,7 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) } global.cps_max = 0; + global.sps_max = 0; return 1; } else if (strcmp(args[1], "table") == 0) { @@ -1424,6 +1425,43 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) return 1; } } + else if (strcmp(args[2], "sessions") == 0) { + if (strcmp(args[3], "global") == 0) { + int v; + + if (s->listener->bind_conf->level < ACCESS_LVL_ADMIN) { + appctx->ctx.cli.msg = stats_permission_denied_msg; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + if (!*args[4]) { + appctx->ctx.cli.msg = "Expects an integer value.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + v = atoi(args[4]); + if (v < 0) { + appctx->ctx.cli.msg = "Value out of range.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + global.sps_lim = v; + + /* Dequeues all of the listeners waiting for a resource */ + if (!LIST_ISEMPTY(&global_listener_queue)) + dequeue_all_listeners(&global_listener_queue); + + return 1; + } + else { + appctx->ctx.cli.msg = "'set rate-limit sessions' only supports 'global'.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + } else if (strcmp(args[2], "http-compression") == 0) { if (strcmp(args[3], "global") == 0) { int v; @@ -1444,7 +1482,7 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) } } else { - appctx->ctx.cli.msg = "'set rate-limit' supports 'connections' and 'http-compression'.\n"; + appctx->ctx.cli.msg = "'set rate-limit' supports 'connections', 'sessions', and 'http-compression'.\n"; appctx->st0 = STAT_CLI_PRINT; return 1; } @@ -2182,6 +2220,9 @@ static int stats_dump_info_to_buffer(struct stream_interface *si) "ConnRate: %d\n" "ConnRateLimit: %d\n" "MaxConnRate: %d\n" + "SessRate: %d\n" + "SessRateLimit: %d\n" + "MaxSessRate: %d\n" "CompressBpsIn: %u\n" "CompressBpsOut: %u\n" "CompressBpsRateLim: %u\n" @@ -2209,6 +2250,7 @@ static int stats_dump_info_to_buffer(struct stream_interface *si) #endif global.maxpipes, pipes_used, pipes_free, read_freq_ctr(&global.conn_per_sec), global.cps_lim, global.cps_max, + read_freq_ctr(&global.sess_per_sec), global.sps_lim, global.sps_max, read_freq_ctr(&global.comp_bps_in), read_freq_ctr(&global.comp_bps_out), global.comp_rate_lim, #ifdef USE_ZLIB diff --git a/src/listener.c b/src/listener.c index 836ca70a41..95a11998f2 100644 --- a/src/listener.c +++ b/src/listener.c @@ -263,13 +263,31 @@ void listener_accept(int fd) return; } - if (global.cps_lim && !(l->options & LI_O_UNLIMITED)) { + if (!(l->options & LI_O_UNLIMITED) && global.sps_lim) { + int max = freq_ctr_remain(&global.sess_per_sec, global.sps_lim, 0); + int expire; + + if (unlikely(!max)) { + /* frontend accept rate limit was reached */ + limit_listener(l, &global_listener_queue); + expire = tick_add(now_ms, next_event_delay(&global.sess_per_sec, global.sps_lim, 0)); + task_schedule(global_listener_queue_task, tick_first(expire, global_listener_queue_task->expire)); + return; + } + + if (max_accept > max) + max_accept = max; + } + + if (!(l->options & LI_O_UNLIMITED) && global.cps_lim) { int max = freq_ctr_remain(&global.conn_per_sec, global.cps_lim, 0); + int expire; if (unlikely(!max)) { /* frontend accept rate limit was reached */ limit_listener(l, &global_listener_queue); - task_schedule(global_listener_queue_task, tick_add(now_ms, next_event_delay(&global.conn_per_sec, global.cps_lim, 0))); + expire = tick_add(now_ms, next_event_delay(&global.conn_per_sec, global.cps_lim, 0)); + task_schedule(global_listener_queue_task, tick_first(expire, global_listener_queue_task->expire)); return; } @@ -411,6 +429,13 @@ void listener_accept(int fd) return; } + /* increase the per-process number of cumulated connections */ + if (!(l->options & LI_O_UNLIMITED)) { + update_freq_ctr(&global.sess_per_sec, 1); + if (global.sess_per_sec.curr_ctr > global.sps_max) + global.sps_max = global.sess_per_sec.curr_ctr; + } + } /* end of while (max_accept--) */ /* we've exhausted max_accept, so there is no need to poll again */