diff --git a/doc/configuration.txt b/doc/configuration.txt index 9b833af5f..90052e384 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -3087,6 +3087,45 @@ http-request { allow | deny | tarpit | auth [realm ] | redirect | with large lists! It is the equivalent of the "set map" command from the stats socket, but can be triggered by an HTTP request. + - { track-sc0 | track-sc1 | track-sc2 } [table ] : + enables tracking of sticky counters from current request. These rules + do not stop evaluation and do not change default action. Three sets of + counters may be simultaneously tracked by the same connection. The first + "track-sc0" rule executed enables tracking of the counters of the + specified table as the first set. The first "track-sc1" rule executed + enables tracking of the counters of the specified table as the second + set. The first "track-sc2" rule executed enables tracking of the + counters of the specified table as the third set. It is a recommended + practice to use the first set of counters for the per-frontend counters + and the second set for the per-backend ones. But this is just a + guideline, all may be used everywhere. + + These actions take one or two arguments : + is mandatory, and is a sample expression rule as described + in section 7.3. It describes what elements of the incoming + request or connection will be analysed, extracted, combined, + and used to select which table entry to update the counters. + +
is an optional table to be used instead of the default one, + which is the stick-table declared in the current proxy. All + the counters for the matches and updates for the key will + then be performed in that table until the session ends. + + Once a "track-sc*" rule is executed, the key is looked up in the table + and if it is not found, an entry is allocated for it. Then a pointer to + that entry is kept during all the session's life, and this entry's + counters are updated as often as possible, every time the session's + counters are updated, and also systematically when the session ends. + Counters are only updated for events that happen after the tracking has + been started. As an exception, connection counters and request counters + are systematically updated so that they reflect useful information. + + If the entry tracks concurrent connection counters, one connection is + counted for as long as the entry is tracked, and the entry will not + expire during that time. Tracking counters also provides a performance + advantage over just checking the keys, because only one table lookup is + performed for all ACL checks that make use of it. + There is no limit to the number of http-request statements per instance. It is important to know that http-request rules are processed very early in @@ -7406,7 +7445,7 @@ tcp-request connection [{if | unless} ] - { track-sc0 | track-sc1 | track-sc2 } [table
] : enables tracking of sticky counters from current connection. These - rules do not stop evaluation and do not change default action. Two sets + rules do not stop evaluation and do not change default action. 3 sets of counters may be simultaneously tracked by the same connection. The first "track-sc0" rule executed enables tracking of the counters of the specified table as the first set. The first "track-sc1" rule executed diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index 8c222df48..3db8e10f6 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -208,6 +208,14 @@ static inline int http_body_bytes(const struct http_msg *msg) return len; } +/* for an http-request action HTTP_REQ_ACT_TRK_*, return a tracking index + * starting at zero for SC0. Unknown actions also return zero. + */ +static inline int http_req_trk_idx(int trk_action) +{ + return trk_action - HTTP_REQ_ACT_TRK_SC0; +} + /* for debugging, reports the HTTP message state name */ static inline const char *http_msg_state_str(int msg_state) { diff --git a/include/types/proto_http.h b/include/types/proto_http.h index c53c7fdd4..95bf59d6d 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -28,6 +28,7 @@ #include #include +#include /* These are the flags that are found in txn->flags */ @@ -261,6 +262,9 @@ enum { HTTP_REQ_ACT_SET_MAP, HTTP_REQ_ACT_CUSTOM_STOP, HTTP_REQ_ACT_CUSTOM_CONT, + HTTP_REQ_ACT_TRK_SC0, + /* SC1, SC2, ... SCn */ + HTTP_REQ_ACT_TRK_SCMAX = HTTP_REQ_ACT_TRK_SC0 + MAX_SESS_STKCTR - 1, HTTP_REQ_ACT_MAX /* must always be last */ }; @@ -435,6 +439,10 @@ struct http_req_rule { struct list value; /* pattern to retrieve MAP value */ } map; } arg; /* arguments used by some actions */ + + union { + struct track_ctr_prm trk_ctr; + } act_prm; }; struct http_res_rule { diff --git a/src/cfgparse.c b/src/cfgparse.c index 16cf717b2..42c1790ce 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -6024,6 +6024,7 @@ int check_config_validity() struct server_rule *srule; struct sticking_rule *mrule; struct tcp_rule *trule; + struct http_req_rule *hrqrule; struct listener *listener; unsigned int next_id; int nbproc; @@ -6500,6 +6501,45 @@ int check_config_validity() } } + /* find the target table for 'http-request' layer 7 rules */ + list_for_each_entry(hrqrule, &curproxy->http_req_rules, list) { + struct proxy *target; + + if (hrqrule->action < HTTP_REQ_ACT_TRK_SC0 || hrqrule->action > HTTP_REQ_ACT_TRK_SCMAX) + continue; + + if (hrqrule->act_prm.trk_ctr.table.n) + target = findproxy(hrqrule->act_prm.trk_ctr.table.n, 0); + else + target = curproxy; + + if (!target) { + Alert("Proxy '%s': unable to find table '%s' referenced by track-sc%d.\n", + curproxy->id, hrqrule->act_prm.trk_ctr.table.n, + http_req_trk_idx(hrqrule->action)); + cfgerr++; + } + else if (target->table.size == 0) { + Alert("Proxy '%s': table '%s' used but not configured.\n", + curproxy->id, hrqrule->act_prm.trk_ctr.table.n ? hrqrule->act_prm.trk_ctr.table.n : curproxy->id); + cfgerr++; + } + else if (!stktable_compatible_sample(hrqrule->act_prm.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->act_prm.trk_ctr.table.n ? hrqrule->act_prm.trk_ctr.table.n : curproxy->id, + http_req_trk_idx(hrqrule->action)); + cfgerr++; + } + else { + free(hrqrule->act_prm.trk_ctr.table.n); + hrqrule->act_prm.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(). + */ + } + } + /* move any "block" rules at the beginning of the http-request rules */ if (!LIST_ISEMPTY(&curproxy->block_rules)) { /* insert block_rules into http_req_rules at the beginning */ diff --git a/src/proto_http.c b/src/proto_http.c index 9e50796dc..e67970117 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -3498,6 +3498,39 @@ http_req_get_intercept_rule(struct proxy *px, struct list *rules, struct session case HTTP_REQ_ACT_CUSTOM_STOP: rule->action_ptr(rule, px, s, txn); return HTTP_RULE_RES_DONE; + + case HTTP_REQ_ACT_TRK_SC0 ... HTTP_REQ_ACT_TRK_SCMAX: + /* Note: only the first valid tracking parameter of each + * applies. + */ + + if (stkctr_entry(&s->stkctr[http_req_trk_idx(rule->action)]) == NULL) { + struct stktable *t; + struct stksess *ts; + struct stktable_key *key; + void *ptr; + + t = rule->act_prm.trk_ctr.table.t; + key = stktable_fetch_key(t, s->be, s, &s->txn, SMP_OPT_DIR_REQ | SMP_OPT_FINAL, rule->act_prm.trk_ctr.expr, NULL); + + if (key && (ts = stktable_get_entry(t, key))) { + session_track_stkctr(&s->stkctr[http_req_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_req_trk_idx(rule->action)], STKCTR_TRACK_CONTENT); + if (s->fe != s->be) + stkctr_set_flags(&s->stkctr[http_req_trk_idx(rule->action)], STKCTR_TRACK_BACKEND); + } + } } } @@ -8975,6 +9008,53 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i proxy->conf.lfs_file = strdup(proxy->conf.args.file); proxy->conf.lfs_line = proxy->conf.args.line; cur_arg += 1; + } 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-request %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_HRQ_HDR; + if (proxy->cap & PR_CAP_BE) + where |= SMP_VAL_BE_HRQ_HDR; + + if (!(expr->fetch->val & where)) { + Alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-request %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-request %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->act_prm.trk_ctr.table.n = strdup(args[cur_arg]); + cur_arg++; + } + rule->act_prm.trk_ctr.expr = expr; + rule->action = HTTP_REQ_ACT_TRK_SC0 + args[0][8] - '0'; } else if (strcmp(args[0], "redirect") == 0) { struct redirect_rule *redir; char *errmsg = NULL;