From 97679e79014a75e07ca8c7151a1da03b98badc30 Mon Sep 17 00:00:00 2001 From: Emeric Brun Date: Thu, 23 Sep 2010 17:56:44 +0200 Subject: [PATCH] [MEDIUM] Implement tcp inspect response rules --- include/proto/proto_tcp.h | 1 + include/types/proxy.h | 4 + src/cfgparse.c | 11 +- src/proto_tcp.c | 215 ++++++++++++++++++++++++++++++++++++++ src/session.c | 6 ++ 5 files changed, 235 insertions(+), 2 deletions(-) diff --git a/include/proto/proto_tcp.h b/include/proto/proto_tcp.h index c81be9bdcc..492efe4ea8 100644 --- a/include/proto/proto_tcp.h +++ b/include/proto/proto_tcp.h @@ -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); diff --git a/include/types/proxy.h b/include/types/proxy.h index f4fd4a921e..9704783e22 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -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) */ diff --git a/src/cfgparse.c b/src/cfgparse.c index 7c27d8366f..1cd7d398ff 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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; diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 3757d5631f..f58d2641fa 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -50,6 +50,7 @@ #include #include #include +#include #ifdef CONFIG_HAP_CTTPROXY #include @@ -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 }, }}; diff --git a/src/session.c b/src/session.c index ae73c8ff4d..53ee397390 100644 --- a/src/session.c +++ b/src/session.c @@ -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; -- 2.39.5