mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-12-07 18:51:21 +01:00
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".
This commit is contained in:
parent
71b734c307
commit
93e7c006c1
@ -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 <number>
|
||||
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 <number>
|
||||
Sets the maximum per-process number of sessions per second to <number>.
|
||||
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 <number>
|
||||
Sets the maximum per-process number of concurrent SSL connections to
|
||||
<number>. By default there is no SSL-specific limit, which means that the
|
||||
@ -12598,6 +12609,12 @@ set rate-limit http-compression global <value>
|
||||
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 <value>
|
||||
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 <table> key <key> [data.<data_type> <value>]*
|
||||
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
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -263,13 +263,31 @@ void listener_accept(int fd)
|
||||
return;
|
||||
}
|
||||
|
||||
if (global.cps_lim && !(l->options & LI_O_UNLIMITED)) {
|
||||
int max = freq_ctr_remain(&global.conn_per_sec, global.cps_lim, 0);
|
||||
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);
|
||||
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.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);
|
||||
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 */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user