[MEDIUM] implement "rate-limit sessions" for the frontend
The new "rate-limit sessions" statement sets a limit on the number of new connections per second on the frontend. As it is extremely accurate (about 0.1%), it is efficient at limiting resource abuse or DoS.
This commit is contained in:
parent
079ff0a207
commit
3a7d20781d
|
@ -617,6 +617,7 @@ option tcpka X X X X
|
|||
option tcplog X X X X
|
||||
[no] option tcpsplice X X X X
|
||||
[no] option transparent X - X X
|
||||
rate-limit sessions X X X -
|
||||
redirect - X X X
|
||||
redisp X - X X (deprecated)
|
||||
redispatch X - X X (deprecated)
|
||||
|
@ -2573,6 +2574,39 @@ no option transparent
|
|||
"transparent" option of the "bind" keyword.
|
||||
|
||||
|
||||
rate-limit sessions <rate>
|
||||
Set a limit on the number of new sessions accepted per second on a frontend
|
||||
May be used in sections : defaults | frontend | listen | backend
|
||||
yes | yes | yes | no
|
||||
Arguments :
|
||||
<rate> The <rate> parameter is an integer designating the maximum number
|
||||
of new sessions per second to accept on the frontend.
|
||||
|
||||
When the frontend reaches the specified number of new sessions per second, it
|
||||
stops accepting new connections until the rate drops below the limit again.
|
||||
During this time, the pending sessions will be kept in the socket's backlog
|
||||
(in system buffers) and haproxy will not even be aware that sessions are
|
||||
pending. When applying very low limit on a highly loaded service, it may make
|
||||
sense to increase the socket's backlog using the "backlog" keyword.
|
||||
|
||||
This feature is particularly efficient at blocking connection-based attacks
|
||||
or service abuse on fragile servers. Since the session rate is measured every
|
||||
millisecond, it is extremely accurate. Also, the limit applies immediately,
|
||||
no delay is needed at all to detect the threshold.
|
||||
|
||||
Example : limit the connection rate on SMTP to 10 per second max
|
||||
listen smtp
|
||||
mode tcp
|
||||
bind :25
|
||||
rate-limit sessions 10
|
||||
server 127.0.0.1:1025
|
||||
|
||||
Note : when the maximum rate is reached, the frontend's status appears as
|
||||
"FULL" in the statistics, exactly as when it is saturated.
|
||||
|
||||
See also : the "backlog" keyword and the "fe_sess_rate" ACL criterion.
|
||||
|
||||
|
||||
redirect location <to> [code <code>] <option> {if | unless} <condition>
|
||||
redirect prefix <to> [code <code>] <option> {if | unless} <condition>
|
||||
Return an HTTP redirection if/unless a condition is matched
|
||||
|
|
|
@ -226,6 +226,7 @@ struct proxy {
|
|||
unsigned int cum_feconn, cum_beconn; /* cumulated number of processed sessions */
|
||||
unsigned int cum_lbconn; /* cumulated number of sessions processed by load balancing */
|
||||
unsigned int maxconn; /* max # of active sessions on the frontend */
|
||||
unsigned int fe_maxsps; /* max # of new sessions per second on the frontend */
|
||||
unsigned int fullconn; /* #conns on backend above which servers are used at full load */
|
||||
struct in_addr except_net, except_mask; /* don't x-forward-for for this address. FIXME: should support IPv6 */
|
||||
char *fwdfor_hdr_name; /* header to use - default: "x-forwarded-for" */
|
||||
|
|
|
@ -70,7 +70,9 @@ int event_accept(int fd) {
|
|||
int cfd;
|
||||
int max_accept = global.tune.maxaccept;
|
||||
|
||||
while (p->feconn < p->maxconn && max_accept--) {
|
||||
while (p->feconn < p->maxconn &&
|
||||
(!p->fe_maxsps || read_freq_ctr(&p->fe_sess_per_sec) < p->fe_maxsps) &&
|
||||
max_accept--) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t laddr = sizeof(addr);
|
||||
|
||||
|
|
106
src/proxy.c
106
src/proxy.c
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Proxy variables and functions.
|
||||
*
|
||||
* Copyright 2000-2008 Willy Tarreau <w@1wt.eu>
|
||||
* Copyright 2000-2009 Willy Tarreau <w@1wt.eu>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
|
@ -169,6 +169,68 @@ static int proxy_parse_timeout(char **args, int section, struct proxy *proxy,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/* This function parses a "rate-limit" statement in a proxy section. It returns
|
||||
* -1 if there is any error, 1 for a warning, otherwise zero. If it does not
|
||||
* return zero, it may write an error message into the <err> buffer, for at
|
||||
* most <errlen> bytes, trailing zero included. The trailing '\n' must not
|
||||
* be written. The function must be called with <args> pointing to the first
|
||||
* command line word, with <proxy> pointing to the proxy being parsed, and
|
||||
* <defpx> to the default proxy or NULL.
|
||||
*/
|
||||
static int proxy_parse_rate_limit(char **args, int section, struct proxy *proxy,
|
||||
struct proxy *defpx, char *err, int errlen)
|
||||
{
|
||||
int retval, cap;
|
||||
char *res, *name;
|
||||
unsigned int *tv = NULL;
|
||||
unsigned int *td = NULL;
|
||||
unsigned int val;
|
||||
|
||||
retval = 0;
|
||||
|
||||
/* simply skip "rate-limit" */
|
||||
if (strcmp(args[0], "rate-limit") == 0)
|
||||
args++;
|
||||
|
||||
name = args[0];
|
||||
if (!strcmp(args[0], "sessions")) {
|
||||
name = "sessions";
|
||||
tv = &proxy->fe_maxsps;
|
||||
td = &defpx->fe_maxsps;
|
||||
cap = PR_CAP_FE;
|
||||
} else {
|
||||
snprintf(err, errlen,
|
||||
"%s '%s': must be 'sessions'",
|
||||
"rate-limit", args[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (*args[1] == 0) {
|
||||
snprintf(err, errlen, "%s %s expects expects an integer value (in sessions/second)", "rate-limit", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
val = strtoul(args[1], &res, 0);
|
||||
if (*res) {
|
||||
snprintf(err, errlen, "%s %s: unexpected character '%c' in integer value '%s'", "rate-limit", name, *res, args[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(proxy->cap & cap)) {
|
||||
snprintf(err, errlen, "%s %s will be ignored because %s '%s' has no %s capability",
|
||||
"rate-limit", name, proxy_type_str(proxy), proxy->id,
|
||||
(cap & PR_CAP_BE) ? "backend" : "frontend");
|
||||
retval = 1;
|
||||
}
|
||||
else if (defpx && *tv != *td) {
|
||||
snprintf(err, errlen, "overwriting %s %s which was already specified", "rate-limit", name);
|
||||
retval = 1;
|
||||
}
|
||||
|
||||
*tv = val;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function finds a proxy with matching name, mode and with satisfying
|
||||
* capabilities. It also checks if there are more matching proxies with
|
||||
|
@ -313,22 +375,35 @@ void maintain_proxies(int *next)
|
|||
|
||||
/* if there are enough free sessions, we'll activate proxies */
|
||||
if (actconn < global.maxconn) {
|
||||
while (p) {
|
||||
if (!p->maxconn || p->feconn < p->maxconn) {
|
||||
if (p->state == PR_STIDLE) {
|
||||
for (l = p->listen; l != NULL; l = l->next)
|
||||
enable_listener(l);
|
||||
p->state = PR_STRUN;
|
||||
}
|
||||
for (; p; p = p->next) {
|
||||
/* check the various reasons we may find to block the frontend */
|
||||
if (p->feconn >= p->maxconn)
|
||||
goto do_block;
|
||||
|
||||
if (p->fe_maxsps && read_freq_ctr(&p->fe_sess_per_sec) >= p->fe_maxsps) {
|
||||
/* we're blocking because a limit was reached on the number of
|
||||
* requests/s on the frontend. We want to re-check ASAP, which
|
||||
* means in 1 ms because the timer will have settled down. Note
|
||||
* that we may already be in IDLE state here.
|
||||
*/
|
||||
*next = tick_first(*next, tick_add(now_ms, 1));
|
||||
goto do_block;
|
||||
}
|
||||
else {
|
||||
if (p->state == PR_STRUN) {
|
||||
for (l = p->listen; l != NULL; l = l->next)
|
||||
disable_listener(l);
|
||||
p->state = PR_STIDLE;
|
||||
}
|
||||
|
||||
/* OK we have no reason to block, so let's unblock if we were blocking */
|
||||
if (p->state == PR_STIDLE) {
|
||||
for (l = p->listen; l != NULL; l = l->next)
|
||||
enable_listener(l);
|
||||
p->state = PR_STRUN;
|
||||
}
|
||||
continue;
|
||||
|
||||
do_block:
|
||||
if (p->state == PR_STRUN) {
|
||||
for (l = p->listen; l != NULL; l = l->next)
|
||||
disable_listener(l);
|
||||
p->state = PR_STIDLE;
|
||||
}
|
||||
p = p->next;
|
||||
}
|
||||
}
|
||||
else { /* block all proxies */
|
||||
|
@ -525,6 +600,7 @@ static struct cfg_kw_list cfg_kws = {{ },{
|
|||
{ CFG_LISTEN, "clitimeout", proxy_parse_timeout },
|
||||
{ CFG_LISTEN, "contimeout", proxy_parse_timeout },
|
||||
{ CFG_LISTEN, "srvtimeout", proxy_parse_timeout },
|
||||
{ CFG_LISTEN, "rate-limit", proxy_parse_rate_limit },
|
||||
{ 0, NULL, NULL },
|
||||
}};
|
||||
|
||||
|
|
Loading…
Reference in New Issue