diff --git a/doc/configuration.txt b/doc/configuration.txt index d1fa1fa1f..02e765fd5 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -5171,10 +5171,12 @@ tcp-request accept [{if | unless} ] tcp-request content accept [{if | unless} ] Accept a connection if/unless a content inspection condition is matched May be used in sections : defaults | frontend | listen | backend - no | yes | yes | no + no | yes | yes | yes During TCP content inspection, the connection is immediately validated if the condition is true (when used with "if") or false (when used with "unless"). + TCP content inspection applies very early when a connection reaches a + frontend, then very early when the connection is forwarded to a backend. Most of the time during content inspection, a condition will be in an uncertain state which is neither true nor false. The evaluation immediately stops when such a condition is encountered. It is important to understand @@ -5197,10 +5199,12 @@ tcp-request content accept [{if | unless} ] tcp-request content reject [{if | unless} ] Reject a connection if/unless a content inspection condition is matched May be used in sections : defaults | frontend | listen | backend - no | yes | yes | no + no | yes | yes | yes During TCP content inspection, the connection is immediately rejected if the condition is true (when used with "if") or false (when used with "unless"). + TCP content inspection applies very early when a connection reaches a + frontend, then very early when the connection is forwarded to a backend. Most of the time during content inspection, a condition will be in an uncertain state which is neither true nor false. The evaluation immediately stops when such a condition is encountered. It is important to understand @@ -5234,7 +5238,7 @@ tcp-request content reject [{if | unless} ] tcp-request inspect-delay Set the maximum allowed time to wait for data during content inspection May be used in sections : defaults | frontend | listen | backend - no | yes | yes | no + no | yes | yes | yes Arguments : is the timeout value specified in milliseconds by default, but can be in any other unit if the number is suffixed by the unit, @@ -5246,6 +5250,11 @@ tcp-request inspect-delay the data then analyze them. This statement simply enables withholding of data for at most the specified amount of time. + TCP content inspection applies very early when a connection reaches a + frontend, then very early when the connection is forwarded to a backend. This + means that a connection may experience a first delay in the frontend and a + second delay in the backend if both have tcp-request rules. + Note that when performing content inspection, haproxy will evaluate the whole rules for every new chunk which gets in, taking into account the fact that those data are partial. If no rule matches before the aforementioned delay, diff --git a/include/types/buffers.h b/include/types/buffers.h index e08669dea..7ad3e9527 100644 --- a/include/types/buffers.h +++ b/include/types/buffers.h @@ -134,16 +134,16 @@ * The field is blanked by buffer_init() and only by analysers themselves * afterwards. */ -#define AN_REQ_INSPECT 0x00000001 /* inspect request contents */ +#define AN_REQ_INSPECT_FE 0x00000001 /* inspect request contents in the frontend */ #define AN_REQ_WAIT_HTTP 0x00000002 /* wait for an HTTP request */ #define AN_REQ_HTTP_PROCESS_FE 0x00000004 /* process the frontend's HTTP part */ #define AN_REQ_SWITCHING_RULES 0x00000008 /* apply the switching rules */ -#define AN_REQ_HTTP_PROCESS_BE 0x00000010 /* process the backend's HTTP part */ -#define AN_REQ_HTTP_INNER 0x00000020 /* inner processing of HTTP request */ -#define AN_REQ_HTTP_TARPIT 0x00000040 /* wait for end of HTTP tarpit */ -#define AN_REQ_HTTP_BODY 0x00000080 /* inspect HTTP request body */ -#define AN_REQ_STICKING_RULES 0x00000100 /* table persistence matching */ -/* unused: 0x200 */ +#define AN_REQ_INSPECT_BE 0x00000010 /* inspect request contents in the backend */ +#define AN_REQ_HTTP_PROCESS_BE 0x00000020 /* process the backend's HTTP part */ +#define AN_REQ_HTTP_INNER 0x00000040 /* inner processing of HTTP request */ +#define AN_REQ_HTTP_TARPIT 0x00000080 /* wait for end of HTTP tarpit */ +#define AN_REQ_HTTP_BODY 0x00000100 /* inspect HTTP request body */ +#define AN_REQ_STICKING_RULES 0x00000200 /* table persistence matching */ #define AN_REQ_PRST_RDP_COOKIE 0x00000400 /* persistence on rdp cookie */ #define AN_REQ_HTTP_XFER_BODY 0x00000800 /* forward request body */ diff --git a/src/cfgparse.c b/src/cfgparse.c index cf9a000b2..f313b71ec 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -5304,7 +5304,7 @@ out_uri_auth_compat: if (curproxy->tcp_req.inspect_delay || !LIST_ISEMPTY(&curproxy->tcp_req.inspect_rules)) - curproxy->fe_req_ana |= AN_REQ_INSPECT; + curproxy->fe_req_ana |= AN_REQ_INSPECT_FE; if (curproxy->mode == PR_MODE_HTTP) { curproxy->fe_req_ana |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_PROCESS_FE; @@ -5316,6 +5316,10 @@ out_uri_auth_compat: } if (curproxy->cap & PR_CAP_BE) { + if (curproxy->tcp_req.inspect_delay || + !LIST_ISEMPTY(&curproxy->tcp_req.inspect_rules)) + curproxy->be_req_ana |= AN_REQ_INSPECT_BE; + if (curproxy->mode == PR_MODE_HTTP) { curproxy->be_req_ana |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_INNER | AN_REQ_HTTP_PROCESS_BE; curproxy->be_rsp_ana |= AN_RES_WAIT_HTTP | AN_RES_HTTP_PROCESS_BE; diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 2ad99416a..788c98167 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -615,23 +615,9 @@ void tcpv6_add_listener(struct listener *listener) /* This function performs the TCP request analysis on the current request. It * returns 1 if the processing can continue on next analysers, or zero if it * needs more data, encounters an error, or wants to immediately abort the - * request. It relies on buffers flags, and updates s->req->analysers. Its - * behaviour is rather simple: - * - the analyser should check for errors and timeouts, and react as expected. - * It does not have to close anything upon error, the caller will. Note that - * the caller also knows how to report errors and timeouts. - * - if the analyser does not have enough data, it must return 0 without calling - * other ones. It should also probably do a buffer_write_dis() to ensure - * that unprocessed data will not be forwarded. But that probably depends on - * the protocol. - * - if an analyser has enough data, it just has to pass on to the next - * analyser without using buffer_write_dis() (enabled by default). - * - if an analyser thinks it has no added value anymore staying here, it must - * reset its bit from the analysers flags in order not to be called anymore. - * - * In the future, analysers should be able to indicate that they want to be - * called after XXX bytes have been received (or transfered), and the min of - * all's wishes will be used to ring back (unless a special condition occurs). + * request. It relies on buffers flags, and updates s->req->analysers. The + * function may be called for frontend rules and backend rules. It only relies + * on the backend pointer so this works for both cases. */ int tcp_inspect_request(struct session *s, struct buffer *req, int an_bit) { @@ -657,21 +643,21 @@ int tcp_inspect_request(struct session *s, struct buffer *req, int an_bit) * - if one rule returns KO, then return KO */ - if (req->flags & BF_SHUTR || !s->fe->tcp_req.inspect_delay || tick_is_expired(req->analyse_exp, now_ms)) + if (req->flags & BF_SHUTR || !s->be->tcp_req.inspect_delay || tick_is_expired(req->analyse_exp, now_ms)) partial = 0; else partial = ACL_PARTIAL; - list_for_each_entry(rule, &s->fe->tcp_req.inspect_rules, list) { + list_for_each_entry(rule, &s->be->tcp_req.inspect_rules, list) { int ret = ACL_PAT_PASS; if (rule->cond) { - ret = acl_exec_cond(rule->cond, s->fe, s, &s->txn, ACL_DIR_REQ | partial); + ret = acl_exec_cond(rule->cond, s->be, s, &s->txn, ACL_DIR_REQ | partial); if (ret == ACL_PAT_MISS) { buffer_dont_connect(req); /* just set the request timeout once at the beginning of the request */ - if (!tick_isset(req->analyse_exp) && s->fe->tcp_req.inspect_delay) - req->analyse_exp = tick_add_ifset(now_ms, s->fe->tcp_req.inspect_delay); + if (!tick_isset(req->analyse_exp) && s->be->tcp_req.inspect_delay) + req->analyse_exp = tick_add_ifset(now_ms, s->be->tcp_req.inspect_delay); return 0; } @@ -687,7 +673,7 @@ int tcp_inspect_request(struct session *s, struct buffer *req, int an_bit) buffer_abort(s->rep); req->analysers = 0; - s->fe->counters.denied_req++; + s->be->counters.denied_req++; if (s->listener->counters) s->listener->counters->denied_req++; @@ -777,13 +763,6 @@ static int tcp_parse_tcp_req(char **args, int section_type, struct proxy *curpx, return -1; } - if (!(curpx->cap & PR_CAP_FE)) { - snprintf(err, errlen, "%s %s will be ignored because %s '%s' has no %s capability", - args[0], args[1], proxy_type_str(curpx), curpx->id, - "frontend"); - return 1; - } - if (!*args[2] || (ptr = parse_time_err(args[2], &val, TIME_UNIT_MS))) { retlen = snprintf(err, errlen, "'%s %s' expects a positive delay in milliseconds, in %s '%s'", diff --git a/src/session.c b/src/session.c index 84ffcbe46..96933d3d0 100644 --- a/src/session.c +++ b/src/session.c @@ -855,9 +855,11 @@ int process_switching_rules(struct session *s, struct buffer *req, int an_bit) goto sw_failed; } - /* we don't want to run the HTTP filters again if the backend has not changed */ - if (s->fe == s->be) + /* we don't want to run the TCP or HTTP filters again if the backend has not changed */ + if (s->fe == s->be) { + s->req->analysers &= ~AN_REQ_INSPECT_BE; s->req->analysers &= ~AN_REQ_HTTP_PROCESS_BE; + } /* as soon as we know the backend, we must check if we have a matching forced or ignored * persistence rule, and report that in the session. @@ -1327,10 +1329,10 @@ struct task *process_session(struct task *t) while (ana_list && max_loops--) { /* Warning! ensure that analysers are always placed in ascending order! */ - if (ana_list & AN_REQ_INSPECT) { - if (!tcp_inspect_request(s, s->req, AN_REQ_INSPECT)) + if (ana_list & AN_REQ_INSPECT_FE) { + if (!tcp_inspect_request(s, s->req, AN_REQ_INSPECT_FE)) break; - UPDATE_ANALYSERS(s->req->analysers, ana_list, ana_back, AN_REQ_INSPECT); + UPDATE_ANALYSERS(s->req->analysers, ana_list, ana_back, AN_REQ_INSPECT_FE); } if (ana_list & AN_REQ_WAIT_HTTP) { @@ -1351,6 +1353,12 @@ struct task *process_session(struct task *t) UPDATE_ANALYSERS(s->req->analysers, ana_list, ana_back, AN_REQ_SWITCHING_RULES); } + if (ana_list & AN_REQ_INSPECT_BE) { + if (!tcp_inspect_request(s, s->req, AN_REQ_INSPECT_BE)) + break; + UPDATE_ANALYSERS(s->req->analysers, ana_list, ana_back, AN_REQ_INSPECT_BE); + } + if (ana_list & AN_REQ_HTTP_PROCESS_BE) { if (!http_process_req_common(s, s->req, AN_REQ_HTTP_PROCESS_BE, s->be)) break;