[MEDIUM] introduce "timeout http-request" in frontends

In order to offer DoS protection, it may be required to lower the maximum
accepted time to receive a complete HTTP request without affecting the client
timeout. This helps protecting against established connections on which
nothing is sent. The client timeout cannot offer a good protection against
this abuse because it is an inactivity timeout, which means that if the
attacker sends one character every now and then, the timeout will not
trigger. With the HTTP request timeout, no matter what speed the client
types, the request will be aborted if it does not complete in time.
This commit is contained in:
Willy Tarreau 2008-01-06 13:24:40 +01:00
parent a0250ba38d
commit 036fae0ec9
8 changed files with 61 additions and 5 deletions

View File

@ -603,6 +603,7 @@ timeout client X X X -
timeout clitimeout X X X - (deprecated)
timeout connect X - X X
timeout contimeout X - X X (deprecated)
timeout httpreq X X X -
timeout queue X - X X
timeout server X - X X
timeout srvtimeout X - X X (deprecated)
@ -952,7 +953,8 @@ clitimeout <timeout>
This parameter is provided for compatibility but is currently deprecated.
Please use "timeout client" instead.
See also : "timeout client", "timeout server", "srvtimeout".
See also : "timeout client", "timeout http-request", "timeout server", and
"srvtimeout".
contimeout <timeout>
@ -2060,6 +2062,40 @@ timeout contimeout <timeout> (deprecated)
See also : "timeout queue", "timeout server", "contimeout".
timeout http-request <timeout>
Set the maximum allowed time to wait for a complete HTTP request
May be used in sections : defaults | frontend | listen | backend
yes | yes | yes | no
Arguments :
<timeout> is the timeout value is specified in milliseconds by default, but
can be in any other unit if the number is suffixed by the unit,
as explained at the top of this document.
In order to offer DoS protection, it may be required to lower the maximum
accepted time to receive a complete HTTP request without affecting the client
timeout. This helps protecting against established connections on which
nothing is sent. The client timeout cannot offer a good protection against
this abuse because it is an inactivity timeout, which means that if the
attacker sends one character every now and then, the timeout will not
trigger. With the HTTP request timeout, no matter what speed the client
types, the request will be aborted if it does not complete in time.
Note that this timeout only applies to the header part of the request, and
not to any data. As soon as the empty line is received, this timeout is not
used anymore.
Generally it is enough to set it to a few seconds, as most clients send the
full request immediately upon connection. Add 3 or more seconds to cover TCP
retransmits but that's all. Setting it to very low values (eg: 50 ms) will
generally work on local networks as long as there are no packet losses. This
will prevent people from sending bare HTTP requests using telnet.
If this parameter is not set, the client timeout still applies between each
chunk of the incoming request.
See also : "timeout client".
2.3) Using ACLs
---------------

View File

@ -241,6 +241,7 @@ struct http_txn {
char *srv_cookie; /* cookie presented by the server, in capture mode */
int status; /* HTTP status from the server, negative if from proxy */
unsigned int flags; /* transaction flags */
struct timeval exp; /* expiration date for the transaction (generally a request) */
};
/* This structure is used by http_find_header() to return values of headers.

View File

@ -176,7 +176,8 @@ struct proxy {
struct timeval queue; /* queue timeout, defaults to connect if unspecified */
struct timeval connect; /* connect timeout (in milliseconds) */
struct timeval server; /* server I/O timeout (in milliseconds) */
struct timeval appsession;
struct timeval appsession; /* appsession cookie expiration */
struct timeval httpreq; /* maximum time for complete HTTP request */
} timeout;
char *id; /* proxy id */
struct list pendconns; /* pending connections with no server assigned yet */

View File

@ -521,6 +521,7 @@ static void init_default_instance()
tv_eternity(&defproxy.timeout.appsession);
tv_eternity(&defproxy.timeout.queue);
tv_eternity(&defproxy.timeout.tarpit);
tv_eternity(&defproxy.timeout.httpreq);
}
/*
@ -603,6 +604,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
tv_eternity(&curproxy->timeout.appsession);
tv_eternity(&curproxy->timeout.queue);
tv_eternity(&curproxy->timeout.tarpit);
tv_eternity(&curproxy->timeout.httpreq);
curproxy->last_change = now.tv_sec;
curproxy->id = strdup(args[1]);
@ -663,6 +665,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
if (curproxy->cap & PR_CAP_FE) {
curproxy->timeout.client = defproxy.timeout.client;
curproxy->timeout.tarpit = defproxy.timeout.tarpit;
curproxy->timeout.httpreq = defproxy.timeout.httpreq;
curproxy->uri_auth = defproxy.uri_auth;
curproxy->mon_net = defproxy.mon_net;
curproxy->mon_mask = defproxy.mon_mask;

View File

@ -390,6 +390,7 @@ int event_accept(int fd) {
tv_eternity(&s->req->cex);
tv_eternity(&s->rep->rex);
tv_eternity(&s->rep->wex);
tv_eternity(&s->txn.exp);
tv_eternity(&t->expire);
if (tv_isset(&s->fe->timeout.client)) {
@ -403,6 +404,11 @@ int event_accept(int fd) {
}
}
if (s->cli_state == CL_STHEADERS && tv_isset(&s->fe->timeout.httpreq)) {
tv_add(&s->txn.exp, &now, &s->fe->timeout.httpreq);
tv_bound(&t->expire, &s->txn.exp);
}
task_queue(t);
if (p->mode != PR_MODE_HEALTH)

View File

@ -620,11 +620,12 @@ void process_session(struct task *t, struct timeval *next)
s->req->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE;
s->rep->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE;
t->expire = s->req->rex;
tv_min(&t->expire, &s->req->rex, &s->req->wex);
tv_bound(&t->expire, &s->req->cex);
tv_bound(&t->expire, &s->rep->rex);
tv_bound(&t->expire, &s->rep->wex);
if (s->cli_state == CL_STHEADERS)
tv_bound(&t->expire, &s->txn.exp);
/* restore t to its place in the task list */
task_queue(t);
@ -1585,7 +1586,8 @@ int process_cli(struct session *t)
}
/* 3: has the read timeout expired ? */
else if (unlikely(tv_isle(&req->rex, &now))) {
else if (unlikely(tv_isle(&req->rex, &now) ||
tv_isle(&txn->exp, &now))) {
/* read timeout : give up with an error message. */
txn->status = 408;
client_retnclose(t, error_message(t, HTTP_ERR_408));

View File

@ -104,6 +104,10 @@ int proxy_parse_timeout(const char **args, struct proxy *proxy,
tv = &proxy->timeout.tarpit;
td = &defpx->timeout.tarpit;
cap = PR_CAP_FE;
} else if (!strcmp(args[0], "http-request")) {
tv = &proxy->timeout.httpreq;
td = &defpx->timeout.httpreq;
cap = PR_CAP_FE;
} else if (!strcmp(args[0], "server") || !strcmp(args[0], "srvtimeout")) {
name = "server";
tv = &proxy->timeout.server;
@ -123,7 +127,9 @@ int proxy_parse_timeout(const char **args, struct proxy *proxy,
td = &defpx->timeout.queue;
cap = PR_CAP_BE;
} else {
snprintf(err, errlen, "timeout '%s': must be 'client', 'server', 'connect', 'appsession', 'queue', or 'tarpit'",
snprintf(err, errlen,
"timeout '%s': must be 'client', 'server', 'connect', "
"'appsession', 'queue', 'http-request' or 'tarpit'",
args[0]);
return -1;
}

View File

@ -10,6 +10,7 @@ listen sample1
retries 1
redispatch
timeout client 15m
timeout http-request 6s
timeout tarpit 20s
timeout queue 60s
timeout connect 5s