From: Ruoshan Huang Date: Thu, 14 Jul 2016 07:07:45 +0000 (+0800) Subject: MEDIUM: http: implement http-response track-sc* directive X-Git-Tag: v1.7-dev4~36 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e4edc6b6285fe91cd2db8b599073b7192f2837c3;p=thirdparty%2Fhaproxy.git MEDIUM: http: implement http-response track-sc* directive This enables tracking of sticky counters from current response. The only difference from "http-request track-sc" is the sample expression can only make use of samples in response (eg. res.*, status etc.) and samples below Layer 6. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 1a96edb657..2a1782ad45 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -3980,6 +3980,7 @@ http-response { allow | deny | add-header | set-nice | del-map() | set-map() | set-var() | + { track-sc0 | track-sc1 | track-sc2 } [table ] | sc-inc-gpc0() | sc-set-gpt0() | silent-drop | @@ -4187,6 +4188,14 @@ http-response { allow | deny | add-header | set-nice | http-response set-var(sess.last_redir) res.hdr(location) + - { track-sc0 | track-sc1 | track-sc2 } [table
] : + enables tracking of sticky counters from current response. Please refer to + "http-request track-sc" for a complete description. The only difference + from "http-request track-sc" is the sample expression can only make + use of samples in response (eg. res.*, status etc.) and samples below + Layer 6 (eg. ssl related samples, see section 7.3.4). If the sample is + not supported, haproxy will fail and warn while parsing the config. + - sc-set-gpt0() : This action sets the GPT0 tag according to the sticky counter designated by and the value of . The expected result is a boolean. If diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index 0aa6643b98..9cdd589da5 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -242,10 +242,10 @@ static inline int http_body_bytes(const struct http_msg *msg) return len; } -/* for an http-request action ACT_HTTP_REQ_TRK_*, return a tracking index +/* for an http-request/response action ACT_ACTION_TRK_SC*, return a tracking index * starting at zero for SC0. Unknown actions also return zero. */ -static inline int http_req_trk_idx(int trk_action) +static inline int http_trk_idx(int trk_action) { return trk_action - ACT_ACTION_TRK_SC0; } diff --git a/include/types/stick_table.h b/include/types/stick_table.h index cab11c1d30..77eeccdc2d 100644 --- a/include/types/stick_table.h +++ b/include/types/stick_table.h @@ -181,6 +181,16 @@ struct stktable_key { /* WARNING: if new fields are added, they must be initialized in stream_accept() * and freed in stream_free() ! + * + * What's the purpose of there two macro: + * - STKCTR_TRACK_BACKEND indicates that a tracking pointer was set from the backend + * and thus that when a keep-alive request goes to another backend, the track + * must cease. + * + * - STKCTR_TRACK_CONTENT indicates that the tracking pointer was set in a + * content-aware rule (tcp-request content or http-request) and that the + * tracking has to be performed in the stream and not in the session, and + * will cease for a new keep-alive request over the same connection. */ #define STKCTR_TRACK_BACKEND 1 #define STKCTR_TRACK_CONTENT 2 diff --git a/src/cfgparse.c b/src/cfgparse.c index 4e4775a6e5..a65c701ed4 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -7880,7 +7880,7 @@ int check_config_validity() if (!target) { Alert("Proxy '%s': unable to find table '%s' referenced by track-sc%d.\n", curproxy->id, hrqrule->arg.trk_ctr.table.n, - http_req_trk_idx(hrqrule->action)); + http_trk_idx(hrqrule->action)); cfgerr++; } else if (target->table.size == 0) { @@ -7891,7 +7891,46 @@ int check_config_validity() else if (!stktable_compatible_sample(hrqrule->arg.trk_ctr.expr, target->table.type)) { Alert("Proxy '%s': stick-table '%s' uses a type incompatible with the 'track-sc%d' rule.\n", curproxy->id, hrqrule->arg.trk_ctr.table.n ? hrqrule->arg.trk_ctr.table.n : curproxy->id, - http_req_trk_idx(hrqrule->action)); + http_trk_idx(hrqrule->action)); + cfgerr++; + } + else { + free(hrqrule->arg.trk_ctr.table.n); + hrqrule->arg.trk_ctr.table.t = &target->table; + /* Note: if we decide to enhance the track-sc syntax, we may be able + * to pass a list of counters to track and allocate them right here using + * stktable_alloc_data_type(). + */ + } + } + + /* find the target table for 'http-response' layer 7 rules */ + list_for_each_entry(hrqrule, &curproxy->http_res_rules, list) { + struct proxy *target; + + if (hrqrule->action < ACT_ACTION_TRK_SC0 || hrqrule->action > ACT_ACTION_TRK_SCMAX) + continue; + + if (hrqrule->arg.trk_ctr.table.n) + target = proxy_tbl_by_name(hrqrule->arg.trk_ctr.table.n); + else + target = curproxy; + + if (!target) { + Alert("Proxy '%s': unable to find table '%s' referenced by track-sc%d.\n", + curproxy->id, hrqrule->arg.trk_ctr.table.n, + http_trk_idx(hrqrule->action)); + cfgerr++; + } + else if (target->table.size == 0) { + Alert("Proxy '%s': table '%s' used but not configured.\n", + curproxy->id, hrqrule->arg.trk_ctr.table.n ? hrqrule->arg.trk_ctr.table.n : curproxy->id); + cfgerr++; + } + else if (!stktable_compatible_sample(hrqrule->arg.trk_ctr.expr, target->table.type)) { + Alert("Proxy '%s': stick-table '%s' uses a type incompatible with the 'track-sc%d' rule.\n", + curproxy->id, hrqrule->arg.trk_ctr.table.n ? hrqrule->arg.trk_ctr.table.n : curproxy->id, + http_trk_idx(hrqrule->action)); cfgerr++; } else { diff --git a/src/proto_http.c b/src/proto_http.c index 7d1b678727..d2090bbbcf 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -3532,7 +3532,7 @@ resume_execution: * applies. */ - if (stkctr_entry(&s->stkctr[http_req_trk_idx(rule->action)]) == NULL) { + if (stkctr_entry(&s->stkctr[http_trk_idx(rule->action)]) == NULL) { struct stktable *t; struct stksess *ts; struct stktable_key *key; @@ -3542,7 +3542,7 @@ resume_execution: key = stktable_fetch_key(t, s->be, sess, s, SMP_OPT_DIR_REQ | SMP_OPT_FINAL, rule->arg.trk_ctr.expr, NULL); if (key && (ts = stktable_get_entry(t, key))) { - stream_track_stkctr(&s->stkctr[http_req_trk_idx(rule->action)], t, ts); + stream_track_stkctr(&s->stkctr[http_trk_idx(rule->action)], t, ts); /* let's count a new HTTP request as it's the first time we do it */ ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT); @@ -3554,9 +3554,9 @@ resume_execution: update_freq_ctr_period(&stktable_data_cast(ptr, http_req_rate), t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1); - stkctr_set_flags(&s->stkctr[http_req_trk_idx(rule->action)], STKCTR_TRACK_CONTENT); + stkctr_set_flags(&s->stkctr[http_trk_idx(rule->action)], STKCTR_TRACK_CONTENT); if (sess->fe != s->be) - stkctr_set_flags(&s->stkctr[http_req_trk_idx(rule->action)], STKCTR_TRACK_BACKEND); + stkctr_set_flags(&s->stkctr[http_trk_idx(rule->action)], STKCTR_TRACK_BACKEND); } } break; @@ -3778,6 +3778,50 @@ resume_execution: return HTTP_RULE_RES_BADREQ; return HTTP_RULE_RES_DONE; + case ACT_ACTION_TRK_SC0 ... ACT_ACTION_TRK_SCMAX: + /* Note: only the first valid tracking parameter of each + * applies. + */ + + if (stkctr_entry(&s->stkctr[http_trk_idx(rule->action)]) == NULL) { + struct stktable *t; + struct stksess *ts; + struct stktable_key *key; + void *ptr; + + t = rule->arg.trk_ctr.table.t; + key = stktable_fetch_key(t, s->be, sess, s, SMP_OPT_DIR_RES | SMP_OPT_FINAL, rule->arg.trk_ctr.expr, NULL); + + if (key && (ts = stktable_get_entry(t, key))) { + stream_track_stkctr(&s->stkctr[http_trk_idx(rule->action)], t, ts); + + /* let's count a new HTTP request as it's the first time we do it */ + ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT); + if (ptr) + stktable_data_cast(ptr, http_req_cnt)++; + + ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE); + if (ptr) + update_freq_ctr_period(&stktable_data_cast(ptr, http_req_rate), + t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1); + + stkctr_set_flags(&s->stkctr[http_trk_idx(rule->action)], STKCTR_TRACK_CONTENT); + if (sess->fe != s->be) + stkctr_set_flags(&s->stkctr[http_trk_idx(rule->action)], STKCTR_TRACK_BACKEND); + + /* When the client triggers a 4xx from the server, it's most often due + * to a missing object or permission. These events should be tracked + * because if they happen often, it may indicate a brute force or a + * vulnerability scan. Normally this is done when receiving the response + * but here we're tracking after this ought to have been done so we have + * to do it on purpose. + */ + if ((unsigned)(txn->status - 400) < 100) + stream_inc_http_err_ctr(s); + } + } + break; + case ACT_CUSTOM: if ((px->options & PR_O_ABRT_CLOSE) && (s->req.flags & (CF_SHUTR|CF_READ_NULL|CF_READ_ERROR))) act_flags |= ACT_FLAG_FINAL; @@ -9211,7 +9255,7 @@ struct act_rule *parse_http_req_cond(const char **args, const char *file, int li action_build_list(&http_req_keywords.list, &trash); Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', 'redirect', " "'tarpit', 'add-header', 'set-header', 'replace-header', 'replace-value', 'set-nice', " - "'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map'" + "'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map', 'track-sc*'" "%s%s, but got '%s'%s.\n", file, linenum, *trash.str ? ", " : "", trash.str, args[0], *args[0] ? "" : " (missing argument)"); goto out_err; @@ -9556,6 +9600,53 @@ struct act_rule *parse_http_res_cond(const char **args, const char *file, int li redir->cond = NULL; cur_arg = 2; return rule; + } else if (strncmp(args[0], "track-sc", 8) == 0 && + args[0][9] == '\0' && args[0][8] >= '0' && + args[0][8] < '0' + MAX_SESS_STKCTR) { /* track-sc 0..9 */ + struct sample_expr *expr; + unsigned int where; + char *err = NULL; + + cur_arg = 1; + proxy->conf.args.ctx = ARGC_TRK; + + expr = sample_parse_expr((char **)args, &cur_arg, file, linenum, &err, &proxy->conf.args); + if (!expr) { + Alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-response %s' rule : %s.\n", + file, linenum, proxy_type_str(proxy), proxy->id, args[0], err); + free(err); + goto out_err; + } + + where = 0; + if (proxy->cap & PR_CAP_FE) + where |= SMP_VAL_FE_HRS_HDR; + if (proxy->cap & PR_CAP_BE) + where |= SMP_VAL_BE_HRS_HDR; + + if (!(expr->fetch->val & where)) { + Alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-response %s' rule :" + " fetch method '%s' extracts information from '%s', none of which is available here.\n", + file, linenum, proxy_type_str(proxy), proxy->id, args[0], + args[cur_arg-1], sample_src_names(expr->fetch->use)); + free(expr); + goto out_err; + } + + if (strcmp(args[cur_arg], "table") == 0) { + cur_arg++; + if (!args[cur_arg]) { + Alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-response %s' rule : missing table name.\n", + file, linenum, proxy_type_str(proxy), proxy->id, args[0]); + free(expr); + goto out_err; + } + /* we copy the table name for now, it will be resolved later */ + rule->arg.trk_ctr.table.n = strdup(args[cur_arg]); + cur_arg++; + } + rule->arg.trk_ctr.expr = expr; + rule->action = ACT_ACTION_TRK_SC0 + args[0][8] - '0'; } else if (((custom = action_http_res_custom(args[0])) != NULL)) { char *errmsg = NULL; cur_arg = 1; @@ -9572,7 +9663,7 @@ struct act_rule *parse_http_res_cond(const char **args, const char *file, int li action_build_list(&http_res_keywords.list, &trash); Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', " "'add-header', 'del-header', 'set-header', 'replace-header', 'replace-value', 'set-nice', " - "'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map'" + "'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map', 'track-sc*'" "%s%s, but got '%s'%s.\n", file, linenum, *trash.str ? ", " : "", trash.str, args[0], *args[0] ? "" : " (missing argument)"); goto out_err;