[MEDIUM] session: support "tcp-request content" rules in backends

Sometimes it's necessary to be able to perform some "layer 6" analysis
in the backend. TCP request rules were not available till now, although
documented in the diagram. Enable them in backend now.
This commit is contained in:
Willy Tarreau 2010-08-03 14:02:05 +02:00
parent 6df7a0e7d3
commit fb35620e87
5 changed files with 46 additions and 46 deletions

View File

@ -5171,10 +5171,12 @@ tcp-request accept [{if | unless} <condition>]
tcp-request content accept [{if | unless} <condition>] tcp-request content accept [{if | unless} <condition>]
Accept a connection if/unless a content inspection condition is matched Accept a connection if/unless a content inspection condition is matched
May be used in sections : defaults | frontend | listen | backend 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 During TCP content inspection, the connection is immediately validated if the
condition is true (when used with "if") or false (when used with "unless"). 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 Most of the time during content inspection, a condition will be in an
uncertain state which is neither true nor false. The evaluation immediately uncertain state which is neither true nor false. The evaluation immediately
stops when such a condition is encountered. It is important to understand stops when such a condition is encountered. It is important to understand
@ -5197,10 +5199,12 @@ tcp-request content accept [{if | unless} <condition>]
tcp-request content reject [{if | unless} <condition>] tcp-request content reject [{if | unless} <condition>]
Reject a connection if/unless a content inspection condition is matched Reject a connection if/unless a content inspection condition is matched
May be used in sections : defaults | frontend | listen | backend 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 During TCP content inspection, the connection is immediately rejected if the
condition is true (when used with "if") or false (when used with "unless"). 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 Most of the time during content inspection, a condition will be in an
uncertain state which is neither true nor false. The evaluation immediately uncertain state which is neither true nor false. The evaluation immediately
stops when such a condition is encountered. It is important to understand stops when such a condition is encountered. It is important to understand
@ -5234,7 +5238,7 @@ tcp-request content reject [{if | unless} <condition>]
tcp-request inspect-delay <timeout> tcp-request inspect-delay <timeout>
Set the maximum allowed time to wait for data during content inspection Set the maximum allowed time to wait for data during content inspection
May be used in sections : defaults | frontend | listen | backend May be used in sections : defaults | frontend | listen | backend
no | yes | yes | no no | yes | yes | yes
Arguments : Arguments :
<timeout> is the timeout value specified in milliseconds by default, but <timeout> is the timeout value specified in milliseconds by default, but
can be in any other unit if the number is suffixed by the unit, can be in any other unit if the number is suffixed by the unit,
@ -5246,6 +5250,11 @@ tcp-request inspect-delay <timeout>
the data then analyze them. This statement simply enables withholding of the data then analyze them. This statement simply enables withholding of
data for at most the specified amount of time. 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 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 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, those data are partial. If no rule matches before the aforementioned delay,

View File

@ -134,16 +134,16 @@
* The field is blanked by buffer_init() and only by analysers themselves * The field is blanked by buffer_init() and only by analysers themselves
* afterwards. * 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_WAIT_HTTP 0x00000002 /* wait for an HTTP request */
#define AN_REQ_HTTP_PROCESS_FE 0x00000004 /* process the frontend's HTTP part */ #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_SWITCHING_RULES 0x00000008 /* apply the switching rules */
#define AN_REQ_HTTP_PROCESS_BE 0x00000010 /* process the backend's HTTP part */ #define AN_REQ_INSPECT_BE 0x00000010 /* inspect request contents in the backend */
#define AN_REQ_HTTP_INNER 0x00000020 /* inner processing of HTTP request */ #define AN_REQ_HTTP_PROCESS_BE 0x00000020 /* process the backend's HTTP part */
#define AN_REQ_HTTP_TARPIT 0x00000040 /* wait for end of HTTP tarpit */ #define AN_REQ_HTTP_INNER 0x00000040 /* inner processing of HTTP request */
#define AN_REQ_HTTP_BODY 0x00000080 /* inspect HTTP request body */ #define AN_REQ_HTTP_TARPIT 0x00000080 /* wait for end of HTTP tarpit */
#define AN_REQ_STICKING_RULES 0x00000100 /* table persistence matching */ #define AN_REQ_HTTP_BODY 0x00000100 /* inspect HTTP request body */
/* unused: 0x200 */ #define AN_REQ_STICKING_RULES 0x00000200 /* table persistence matching */
#define AN_REQ_PRST_RDP_COOKIE 0x00000400 /* persistence on rdp cookie */ #define AN_REQ_PRST_RDP_COOKIE 0x00000400 /* persistence on rdp cookie */
#define AN_REQ_HTTP_XFER_BODY 0x00000800 /* forward request body */ #define AN_REQ_HTTP_XFER_BODY 0x00000800 /* forward request body */

View File

@ -5304,7 +5304,7 @@ out_uri_auth_compat:
if (curproxy->tcp_req.inspect_delay || if (curproxy->tcp_req.inspect_delay ||
!LIST_ISEMPTY(&curproxy->tcp_req.inspect_rules)) !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) { if (curproxy->mode == PR_MODE_HTTP) {
curproxy->fe_req_ana |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_PROCESS_FE; 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->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) { 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_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; curproxy->be_rsp_ana |= AN_RES_WAIT_HTTP | AN_RES_HTTP_PROCESS_BE;

View File

@ -615,23 +615,9 @@ void tcpv6_add_listener(struct listener *listener)
/* This function performs the TCP request analysis on the current request. It /* 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 * 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 * needs more data, encounters an error, or wants to immediately abort the
* request. It relies on buffers flags, and updates s->req->analysers. Its * request. It relies on buffers flags, and updates s->req->analysers. The
* behaviour is rather simple: * function may be called for frontend rules and backend rules. It only relies
* - the analyser should check for errors and timeouts, and react as expected. * on the backend pointer so this works for both cases.
* 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).
*/ */
int tcp_inspect_request(struct session *s, struct buffer *req, int an_bit) 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 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; partial = 0;
else else
partial = ACL_PARTIAL; 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; int ret = ACL_PAT_PASS;
if (rule->cond) { 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) { if (ret == ACL_PAT_MISS) {
buffer_dont_connect(req); buffer_dont_connect(req);
/* just set the request timeout once at the beginning of the request */ /* just set the request timeout once at the beginning of the request */
if (!tick_isset(req->analyse_exp) && 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->fe->tcp_req.inspect_delay); req->analyse_exp = tick_add_ifset(now_ms, s->be->tcp_req.inspect_delay);
return 0; return 0;
} }
@ -687,7 +673,7 @@ int tcp_inspect_request(struct session *s, struct buffer *req, int an_bit)
buffer_abort(s->rep); buffer_abort(s->rep);
req->analysers = 0; req->analysers = 0;
s->fe->counters.denied_req++; s->be->counters.denied_req++;
if (s->listener->counters) if (s->listener->counters)
s->listener->counters->denied_req++; 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; 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))) { if (!*args[2] || (ptr = parse_time_err(args[2], &val, TIME_UNIT_MS))) {
retlen = snprintf(err, errlen, retlen = snprintf(err, errlen,
"'%s %s' expects a positive delay in milliseconds, in %s '%s'", "'%s %s' expects a positive delay in milliseconds, in %s '%s'",

View File

@ -855,9 +855,11 @@ int process_switching_rules(struct session *s, struct buffer *req, int an_bit)
goto sw_failed; goto sw_failed;
} }
/* we don't want to run the HTTP filters again if the backend has not changed */ /* we don't want to run the TCP or HTTP filters again if the backend has not changed */
if (s->fe == s->be) if (s->fe == s->be) {
s->req->analysers &= ~AN_REQ_INSPECT_BE;
s->req->analysers &= ~AN_REQ_HTTP_PROCESS_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 /* 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. * persistence rule, and report that in the session.
@ -1327,10 +1329,10 @@ struct task *process_session(struct task *t)
while (ana_list && max_loops--) { while (ana_list && max_loops--) {
/* Warning! ensure that analysers are always placed in ascending order! */ /* Warning! ensure that analysers are always placed in ascending order! */
if (ana_list & AN_REQ_INSPECT) { if (ana_list & AN_REQ_INSPECT_FE) {
if (!tcp_inspect_request(s, s->req, AN_REQ_INSPECT)) if (!tcp_inspect_request(s, s->req, AN_REQ_INSPECT_FE))
break; 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) { 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); 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 (ana_list & AN_REQ_HTTP_PROCESS_BE) {
if (!http_process_req_common(s, s->req, AN_REQ_HTTP_PROCESS_BE, s->be)) if (!http_process_req_common(s, s->req, AN_REQ_HTTP_PROCESS_BE, s->be))
break; break;