]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] Implement tcp inspect response rules
authorEmeric Brun <ebrun@exceliance.fr>
Thu, 23 Sep 2010 15:56:44 +0000 (17:56 +0200)
committerWilly Tarreau <w@1wt.eu>
Thu, 11 Nov 2010 08:28:18 +0000 (09:28 +0100)
include/proto/proto_tcp.h
include/types/proxy.h
src/cfgparse.c
src/proto_tcp.c
src/session.c

index c81be9bdcc6530042305d1b7a9eaefa4fd3bbd68..492efe4ea8374b6e102396cb342595533d45b0cf 100644 (file)
@@ -34,6 +34,7 @@ int tcpv4_connect_server(struct stream_interface *si,
                         struct proxy *be, struct server *srv,
                         struct sockaddr *srv_addr, struct sockaddr *from_addr);
 int tcp_inspect_request(struct session *s, struct buffer *req, int an_bit);
+int tcp_inspect_response(struct session *s, struct buffer *rep, int an_bit);
 int tcp_persist_rdp_cookie(struct session *s, struct buffer *req, int an_bit);
 int tcp_exec_req_rules(struct session *s);
 
index f4fd4a921e1ab4093bffef9a43631bfc68d50b82..9704783e220c3cc604569834c18430aa190ce650 100644 (file)
@@ -194,6 +194,10 @@ struct proxy {
                struct list inspect_rules;      /* inspection rules */
                struct list l4_rules;           /* layer4 rules */
        } tcp_req;
+       struct {                                /* TCP request processing */
+               unsigned int inspect_delay;     /* inspection delay */
+               struct list inspect_rules;      /* inspection rules */
+       } tcp_rep;
        int acl_requires;                       /* Elements required to satisfy all ACLs (ACL_USE_*) */
        struct server *srv, defsrv;             /* known servers; default server configuration */
        int srv_act, srv_bck;                   /* # of servers eligible for LB (UP|!checked) AND (enabled+weight!=0) */
index 7c27d8366f0c59de94956c39330c2342c4251a47..1cd7d398ff34214f1b9e9bc5b9bd9ec0b201ad8d 100644 (file)
@@ -1034,12 +1034,14 @@ static void init_new_proxy(struct proxy *p)
        LIST_INIT(&p->sticking_rules);
        LIST_INIT(&p->storersp_rules);
        LIST_INIT(&p->tcp_req.inspect_rules);
+       LIST_INIT(&p->tcp_rep.inspect_rules);
        LIST_INIT(&p->tcp_req.l4_rules);
        LIST_INIT(&p->req_add);
        LIST_INIT(&p->rsp_add);
 
        /* Timeouts are defined as -1 */
        proxy_reset_timeouts(p);
+       p->tcp_rep.inspect_delay = TICK_ETERNITY;
 }
 
 void init_default_instance()
@@ -2768,8 +2770,10 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                        err_code |= ERR_ALERT | ERR_FATAL;
                        goto out;
                }
-
-               err_code |= warnif_cond_requires_resp(cond, file, linenum);
+               if (flags & STK_ON_RSP)
+                       err_code |= warnif_cond_requires_req(cond, file, linenum);
+               else
+                       err_code |= warnif_cond_requires_resp(cond, file, linenum);
 
                rule = (struct sticking_rule *)calloc(1, sizeof(*rule));
                rule->cond = cond;
@@ -5970,6 +5974,9 @@ out_uri_auth_compat:
                            !LIST_ISEMPTY(&curproxy->tcp_req.inspect_rules))
                                curproxy->be_req_ana |= AN_REQ_INSPECT_BE;
 
+                       if (!LIST_ISEMPTY(&curproxy->tcp_rep.inspect_rules))
+                                curproxy->be_rsp_ana |= AN_RES_INSPECT;
+
                        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;
index 3757d5631f1ae0a107039fe2674917b37e97b6d4..f58d2641fa530c4a39bbe54265080462fa9061db 100644 (file)
@@ -50,6 +50,7 @@
 #include <proto/stick_table.h>
 #include <proto/stream_sock.h>
 #include <proto/task.h>
+#include <proto/buffers.h>
 
 #ifdef CONFIG_HAP_CTTPROXY
 #include <import/ip_tproxy.h>
@@ -750,6 +751,91 @@ int tcp_inspect_request(struct session *s, struct buffer *req, int an_bit)
        return 1;
 }
 
+/* This function performs the TCP response analysis on the current response. 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
+ * response. It relies on buffers flags, and updates s->rep->analysers. The
+ * function may be called for backend rules.
+ */
+int tcp_inspect_response(struct session *s, struct buffer *rep, int an_bit)
+{
+       struct tcp_rule *rule;
+       int partial;
+
+       DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n",
+               now_ms, __FUNCTION__,
+               s,
+               rep,
+               rep->rex, rep->wex,
+               rep->flags,
+               rep->l,
+               rep->analysers);
+
+       /* We don't know whether we have enough data, so must proceed
+        * this way :
+        * - iterate through all rules in their declaration order
+        * - if one rule returns MISS, it means the inspect delay is
+        *   not over yet, then return immediately, otherwise consider
+        *   it as a non-match.
+        * - if one rule returns OK, then return OK
+        * - if one rule returns KO, then return KO
+        */
+
+       if (rep->flags & BF_SHUTR || tick_is_expired(rep->analyse_exp, now_ms))
+               partial = 0;
+       else
+               partial = ACL_PARTIAL;
+
+       list_for_each_entry(rule, &s->be->tcp_rep.inspect_rules, list) {
+               int ret = ACL_PAT_PASS;
+
+               if (rule->cond) {
+                       ret = acl_exec_cond(rule->cond, s->be, s, &s->txn, ACL_DIR_RTR | partial);
+                       if (ret == ACL_PAT_MISS) {
+                               /* just set the analyser timeout once at the beginning of the response */
+                               if (!tick_isset(rep->analyse_exp) && s->be->tcp_rep.inspect_delay)
+                                       rep->analyse_exp = tick_add_ifset(now_ms, s->be->tcp_rep.inspect_delay);
+                               return 0;
+                       }
+
+                       ret = acl_pass(ret);
+                       if (rule->cond->pol == ACL_COND_UNLESS)
+                               ret = !ret;
+               }
+
+               if (ret) {
+                       /* we have a matching rule. */
+                       if (rule->action == TCP_ACT_REJECT) {
+                               buffer_abort(rep);
+                               buffer_abort(s->req);
+                               rep->analysers = 0;
+
+                               s->be->counters.denied_resp++;
+                               if (s->listener->counters)
+                                       s->listener->counters->denied_resp++;
+
+                               if (!(s->flags & SN_ERR_MASK))
+                                       s->flags |= SN_ERR_PRXCOND;
+                               if (!(s->flags & SN_FINST_MASK))
+                                       s->flags |= SN_FINST_D;
+                               return 0;
+                       }
+                       else {
+                               /* otherwise accept */
+                               break;
+                       }
+               }
+       }
+
+       /* if we get there, it means we have no rule which matches, or
+        * we have an explicit accept, so we apply the default accept.
+        */
+       rep->analysers &= ~an_bit;
+       rep->analyse_exp = TICK_ETERNITY;
+       return 1;
+}
+
+
 /* This function performs the TCP layer4 analysis on the current request. It
  * returns 0 if a reject rule matches, otherwise 1 if either an accept rule
  * matches or if no more rule matches. It can only use rules which don't need
@@ -822,6 +908,51 @@ int tcp_exec_req_rules(struct session *s)
        return result;
 }
 
+/* Parse a tcp-response rule. Return a negative value in case of failure */
+static int tcp_parse_response_rule(char **args, int arg, int section_type,
+                                 struct proxy *curpx, struct proxy *defpx,
+                                 struct tcp_rule *rule, char *err, int errlen)
+{
+       if (curpx == defpx || !(curpx->cap & PR_CAP_BE)) {
+               snprintf(err, errlen, "%s %s is only allowed in 'backend' sections",
+                        args[0], args[1]);
+               return -1;
+       }
+
+       if (strcmp(args[arg], "accept") == 0) {
+               arg++;
+               rule->action = TCP_ACT_ACCEPT;
+       }
+       else if (strcmp(args[arg], "reject") == 0) {
+               arg++;
+               rule->action = TCP_ACT_REJECT;
+       }
+       else {
+               snprintf(err, errlen,
+                        "'%s %s' expects 'accept' or 'reject' in %s '%s' (was '%s')",
+                        args[0], args[1], proxy_type_str(curpx), curpx->id, args[arg]);
+               return -1;
+       }
+
+       if (strcmp(args[arg], "if") == 0 || strcmp(args[arg], "unless") == 0) {
+               if ((rule->cond = build_acl_cond(NULL, 0, curpx, (const char **)args+arg)) == NULL) {
+                       snprintf(err, errlen,
+                                "error detected in %s '%s' while parsing '%s' condition",
+                                proxy_type_str(curpx), curpx->id, args[arg]);
+                       return -1;
+               }
+       }
+       else if (*args[arg]) {
+               snprintf(err, errlen,
+                        "'%s %s %s' only accepts 'if' or 'unless', in %s '%s' (was '%s')",
+                        args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg]);
+               return -1;
+       }
+       return 0;
+}
+
+
+
 /* Parse a tcp-request rule. Return a negative value in case of failure */
 static int tcp_parse_request_rule(char **args, int arg, int section_type,
                                  struct proxy *curpx, struct proxy *defpx,
@@ -890,6 +1021,89 @@ static int tcp_parse_request_rule(char **args, int arg, int section_type,
        return 0;
 }
 
+/* This function should be called to parse a line starting with the "tcp-response"
+ * keyword.
+ */
+static int tcp_parse_tcp_rep(char **args, int section_type, struct proxy *curpx,
+                            struct proxy *defpx, char *err, int errlen)
+{
+       const char *ptr = NULL;
+       unsigned int val;
+       int retlen;
+       int warn = 0;
+       int arg;
+       struct tcp_rule *rule;
+
+       if (!*args[1]) {
+               snprintf(err, errlen, "missing argument for '%s' in %s '%s'",
+                        args[0], proxy_type_str(curpx), curpx->id);
+               return -1;
+       }
+
+       if (strcmp(args[1], "inspect-delay") == 0) {
+               if (curpx == defpx || !(curpx->cap & PR_CAP_BE)) {
+                       snprintf(err, errlen, "%s %s is only allowed in 'backend' sections",
+                                args[0], args[1]);
+                       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'",
+                                         args[0], args[1], proxy_type_str(curpx), curpx->id);
+                       if (ptr && retlen < errlen)
+                               retlen += snprintf(err + retlen, errlen - retlen,
+                                                  " (unexpected character '%c')", *ptr);
+                       return -1;
+               }
+
+               if (curpx->tcp_rep.inspect_delay) {
+                       snprintf(err, errlen, "ignoring %s %s (was already defined) in %s '%s'",
+                                args[0], args[1], proxy_type_str(curpx), curpx->id);
+                       return 1;
+               }
+               curpx->tcp_rep.inspect_delay = val;
+               return 0;
+       }
+
+       rule = (struct tcp_rule *)calloc(1, sizeof(*rule));
+       LIST_INIT(&rule->list);
+       arg = 1;
+
+       if (strcmp(args[1], "content") == 0) {
+               arg++;
+               if (tcp_parse_response_rule(args, arg, section_type, curpx, defpx, rule, err, errlen) < 0)
+                       goto error;
+
+               if (rule->cond && (rule->cond->requires & ACL_USE_L6REQ_VOLATILE)) {
+                       struct acl *acl;
+                       const char *name;
+
+                       acl = cond_find_require(rule->cond, ACL_USE_L6REQ_VOLATILE);
+                       name = acl ? acl->name : "(unknown)";
+
+                       retlen = snprintf(err, errlen,
+                                         "acl '%s' involves some request-only criteria which will be ignored.",
+                                         name);
+                       warn++;
+               }
+
+               LIST_ADDQ(&curpx->tcp_rep.inspect_rules, &rule->list);
+       }
+       else {
+               retlen = snprintf(err, errlen,
+                                 "'%s' expects 'inspect-delay' or 'content' in %s '%s' (was '%s')",
+                                 args[0], proxy_type_str(curpx), curpx->id, args[1]);
+               goto error;
+       }
+
+       return warn;
+ error:
+       free(rule);
+       return -1;
+}
+
+
 /* This function should be called to parse a line starting with the "tcp-request"
  * keyword.
  */
@@ -1129,6 +1343,7 @@ pattern_fetch_dport(struct proxy *px, struct session *l4, void *l7, int dir,
 
 static struct cfg_kw_list cfg_kws = {{ },{
        { CFG_LISTEN, "tcp-request", tcp_parse_tcp_req },
+       { CFG_LISTEN, "tcp-response", tcp_parse_tcp_rep },
        { 0, NULL, NULL },
 }};
 
index ae73c8ff4d10c3bc123222e927b588fdc717234d..53ee397390c9b857fa1c5d8161a6b7527dca0a5e 100644 (file)
@@ -1585,6 +1585,12 @@ 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_RES_INSPECT) {
+                                       if (!tcp_inspect_response(s, s->rep, AN_RES_INSPECT))
+                                               break;
+                                       UPDATE_ANALYSERS(s->rep->analysers, ana_list, ana_back, AN_RES_INSPECT);
+                               }
+
                                if (ana_list & AN_RES_WAIT_HTTP) {
                                        if (!http_wait_for_response(s, s->rep, AN_RES_WAIT_HTTP))
                                                break;