mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-11-29 06:40:59 +01:00
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 <key> sample expression can only make use of samples in response (eg. res.*, status etc.) and samples below Layer 6.
This commit is contained in:
parent
9bd52d478b
commit
e4edc6b628
@ -3980,6 +3980,7 @@ http-response { allow | deny | add-header <name> <fmt> | set-nice <nice> |
|
|||||||
del-map(<file name>) <key fmt> |
|
del-map(<file name>) <key fmt> |
|
||||||
set-map(<file name>) <key fmt> <value fmt> |
|
set-map(<file name>) <key fmt> <value fmt> |
|
||||||
set-var(<var-name>) <expr> |
|
set-var(<var-name>) <expr> |
|
||||||
|
{ track-sc0 | track-sc1 | track-sc2 } <key> [table <table>] |
|
||||||
sc-inc-gpc0(<sc-id>) |
|
sc-inc-gpc0(<sc-id>) |
|
||||||
sc-set-gpt0(<sc-id>) <int> |
|
sc-set-gpt0(<sc-id>) <int> |
|
||||||
silent-drop |
|
silent-drop |
|
||||||
@ -4187,6 +4188,14 @@ http-response { allow | deny | add-header <name> <fmt> | set-nice <nice> |
|
|||||||
|
|
||||||
http-response set-var(sess.last_redir) res.hdr(location)
|
http-response set-var(sess.last_redir) res.hdr(location)
|
||||||
|
|
||||||
|
- { track-sc0 | track-sc1 | track-sc2 } <key> [table <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 <key> 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(<sc-id>) <int> :
|
- sc-set-gpt0(<sc-id>) <int> :
|
||||||
This action sets the GPT0 tag according to the sticky counter designated
|
This action sets the GPT0 tag according to the sticky counter designated
|
||||||
by <sc-id> and the value of <int>. The expected result is a boolean. If
|
by <sc-id> and the value of <int>. The expected result is a boolean. If
|
||||||
|
|||||||
@ -242,10 +242,10 @@ static inline int http_body_bytes(const struct http_msg *msg)
|
|||||||
return len;
|
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.
|
* 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;
|
return trk_action - ACT_ACTION_TRK_SC0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -181,6 +181,16 @@ struct stktable_key {
|
|||||||
|
|
||||||
/* WARNING: if new fields are added, they must be initialized in stream_accept()
|
/* WARNING: if new fields are added, they must be initialized in stream_accept()
|
||||||
* and freed in stream_free() !
|
* 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_BACKEND 1
|
||||||
#define STKCTR_TRACK_CONTENT 2
|
#define STKCTR_TRACK_CONTENT 2
|
||||||
|
|||||||
@ -7880,7 +7880,7 @@ int check_config_validity()
|
|||||||
if (!target) {
|
if (!target) {
|
||||||
Alert("Proxy '%s': unable to find table '%s' referenced by track-sc%d.\n",
|
Alert("Proxy '%s': unable to find table '%s' referenced by track-sc%d.\n",
|
||||||
curproxy->id, hrqrule->arg.trk_ctr.table.n,
|
curproxy->id, hrqrule->arg.trk_ctr.table.n,
|
||||||
http_req_trk_idx(hrqrule->action));
|
http_trk_idx(hrqrule->action));
|
||||||
cfgerr++;
|
cfgerr++;
|
||||||
}
|
}
|
||||||
else if (target->table.size == 0) {
|
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)) {
|
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",
|
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,
|
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++;
|
cfgerr++;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
103
src/proto_http.c
103
src/proto_http.c
@ -3532,7 +3532,7 @@ resume_execution:
|
|||||||
* applies.
|
* 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 stktable *t;
|
||||||
struct stksess *ts;
|
struct stksess *ts;
|
||||||
struct stktable_key *key;
|
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);
|
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))) {
|
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 */
|
/* 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);
|
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),
|
update_freq_ctr_period(&stktable_data_cast(ptr, http_req_rate),
|
||||||
t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
|
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)
|
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;
|
break;
|
||||||
@ -3778,6 +3778,50 @@ resume_execution:
|
|||||||
return HTTP_RULE_RES_BADREQ;
|
return HTTP_RULE_RES_BADREQ;
|
||||||
return HTTP_RULE_RES_DONE;
|
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:
|
case ACT_CUSTOM:
|
||||||
if ((px->options & PR_O_ABRT_CLOSE) && (s->req.flags & (CF_SHUTR|CF_READ_NULL|CF_READ_ERROR)))
|
if ((px->options & PR_O_ABRT_CLOSE) && (s->req.flags & (CF_SHUTR|CF_READ_NULL|CF_READ_ERROR)))
|
||||||
act_flags |= ACT_FLAG_FINAL;
|
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);
|
action_build_list(&http_req_keywords.list, &trash);
|
||||||
Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', 'redirect', "
|
Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', 'redirect', "
|
||||||
"'tarpit', 'add-header', 'set-header', 'replace-header', 'replace-value', 'set-nice', "
|
"'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",
|
"%s%s, but got '%s'%s.\n",
|
||||||
file, linenum, *trash.str ? ", " : "", trash.str, args[0], *args[0] ? "" : " (missing argument)");
|
file, linenum, *trash.str ? ", " : "", trash.str, args[0], *args[0] ? "" : " (missing argument)");
|
||||||
goto out_err;
|
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;
|
redir->cond = NULL;
|
||||||
cur_arg = 2;
|
cur_arg = 2;
|
||||||
return rule;
|
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)) {
|
} else if (((custom = action_http_res_custom(args[0])) != NULL)) {
|
||||||
char *errmsg = NULL;
|
char *errmsg = NULL;
|
||||||
cur_arg = 1;
|
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);
|
action_build_list(&http_res_keywords.list, &trash);
|
||||||
Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', "
|
Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', "
|
||||||
"'add-header', 'del-header', 'set-header', 'replace-header', 'replace-value', 'set-nice', "
|
"'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",
|
"%s%s, but got '%s'%s.\n",
|
||||||
file, linenum, *trash.str ? ", " : "", trash.str, args[0], *args[0] ? "" : " (missing argument)");
|
file, linenum, *trash.str ? ", " : "", trash.str, args[0], *args[0] ? "" : " (missing argument)");
|
||||||
goto out_err;
|
goto out_err;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user