[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.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index d1fa1fa..02e765f 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -5171,10 +5171,12 @@
tcp-request content accept [{if | unless} <condition>]
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 reject [{if | unless} <condition>]
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 inspect-delay <timeout>
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 :
<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,
@@ -5246,6 +5250,11 @@
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 e08669d..7ad3e95 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 cf9a000..f313b71 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -5304,7 +5304,7 @@
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 @@
}
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 2ad9941..788c981 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -615,23 +615,9 @@
/* 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 @@
* - 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 @@
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 @@
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 84ffcbe..96933d3 100644
--- a/src/session.c
+++ b/src/session.c
@@ -855,9 +855,11 @@
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 @@
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 @@
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;