MEDIUM: http: add the track-sc* actions to http-request rules

Add support for http-request track-sc, similar to what is done in
tcp-request for backends. A new act_prm field was added to HTTP
request rules to store the track params (table, counter). Just
like for TCP rules, the table is resolved while checking for
config validity. The code was mostly copied from the TCP code
with the exception that here we also count the HTTP request count
and rate by hand. Probably that something could be factored out in
the future.

It seems like tracking flags should be improved to mark each hook
which tracks a key so that we can have some check points where to
increase counters of the past if not done yet, a bit like is done
for TRACK_BACKEND.
This commit is contained in:
Willy Tarreau 2014-06-25 18:12:15 +02:00
parent 5ed1bbfc75
commit 09448f7d7c
5 changed files with 176 additions and 1 deletions

View File

@ -3087,6 +3087,45 @@ http-request { allow | deny | tarpit | auth [realm <realm>] | redirect <rule> |
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 } <key> [table <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 :
<key> 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.
<table> 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 <action> [{if | unless} <condition>]
- { track-sc0 | track-sc1 | track-sc2 } <key> [table <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

View File

@ -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)
{

View File

@ -28,6 +28,7 @@
#include <common/regex.h>
#include <types/hdr_idx.h>
#include <types/stick_table.h>
/* 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 {

View File

@ -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 */

View File

@ -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;