diff --git a/doc/configuration.txt b/doc/configuration.txt index ea3d342c2..3cd48d366 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 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 @@ -2060,6 +2062,40 @@ timeout contimeout (deprecated) See also : "timeout queue", "timeout server", "contimeout". +timeout http-request + 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 : + 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 --------------- diff --git a/include/types/proto_http.h b/include/types/proto_http.h index c5e051e22..645306165 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -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. diff --git a/include/types/proxy.h b/include/types/proxy.h index 8a72d82d5..5d397d403 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -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 */ diff --git a/src/cfgparse.c b/src/cfgparse.c index 655a40e63..b9cf62f07 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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; diff --git a/src/client.c b/src/client.c index cfcfb0c3f..d1d763db5 100644 --- a/src/client.c +++ b/src/client.c @@ -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) diff --git a/src/proto_http.c b/src/proto_http.c index f387d0b44..16028ceda 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -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)); diff --git a/src/proxy.c b/src/proxy.c index 82bb2ae14..e2e6b15fc 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -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; } diff --git a/tests/test-timeout.cfg b/tests/test-timeout.cfg index 7053f5c28..1c0842559 100644 --- a/tests/test-timeout.cfg +++ b/tests/test-timeout.cfg @@ -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