diff --git a/doc/configuration.txt b/doc/configuration.txt index 6d1c2d8aa..5ad1fe9e6 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -7202,6 +7202,30 @@ http-request return [status ] [content-type ] lf-string "Access denied. IP %[src] is blacklisted." \ if { src -f /etc/haproxy/blacklist.lst } +http-request sc-add-gpc(,) { | } + [ { if | unless } ] + + This action increments the General Purpose Counter at the index of the + array associated to the sticky counter designated by by the value of + either integer or the integer evaluation of expression . Integers + and expressions are limited to unsigned 32-bit values. If an error occurs, + this action silently fails and the actions evaluation continues. is an + integer between 0 and 99 and is an integer between 0 and 2. It also + silently fails if the there is no GPC stored at this index. The entry in the + table is refreshed even if the value is zero. The 'gpc_rate' is automatically + adjusted to reflect the average growth rate of the gpc value. + + This action applies only to the 'gpc' and 'gpc_rate' array data_types (and + not to the legacy 'gpc0', 'gpc1', 'gpc0_rate' nor 'gpc1_rate' data_types). + There is no equivalent function for legacy data types, but if the value is + always 1, please see 'sc-inc-gpc()', 'sc-inc-gpc0()' and 'sc-inc-gpc1()'. + There is no way to decrement the value either, but it is possible to store + exact values in a General Purpose Tag using 'sc-set-gpt()' instead. + + The main use of this action is to count scores or total volumes (e.g. + estimated danger per source IP reported by the server or a WAF, total + uploaded bytes, etc). + http-request sc-inc-gpc(,) [ { if | unless } ] This actions increments the General Purpose Counter at the index diff --git a/include/haproxy/action-t.h b/include/haproxy/action-t.h index 4c919e841..2745ba7b4 100644 --- a/include/haproxy/action-t.h +++ b/include/haproxy/action-t.h @@ -175,6 +175,8 @@ struct act_rule { struct { int sc; unsigned int idx; + long long int value; + struct sample_expr *expr; } gpc; struct { int sc; diff --git a/src/stick_table.c b/src/stick_table.c index 6a861110a..b6f3ef1e5 100644 --- a/src/stick_table.c +++ b/src/stick_table.c @@ -2848,6 +2848,166 @@ static enum act_parse_ret parse_set_gpt(const char **args, int *arg, struct prox return ACT_RET_PRS_OK; } +/* This function updates the gpc at index 'rule->arg.gpc.idx' of the array on + * the tracksc counter of index 'rule->arg.gpc.sc' stored into the or + * directly in the session if is set to NULL. This gpc is + * set to the value computed by the expression 'rule->arg.gpc.expr' or if + * 'rule->arg.gpc.expr' is null directly to the value of 'rule->arg.gpc.value'. + * + * This function always returns ACT_RET_CONT and parameter flags is unused. + */ +static enum act_return action_add_gpc(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + void *ptr1, *ptr2; + struct stksess *ts; + struct stkctr *stkctr; + unsigned int value = 0; + struct sample *smp; + int smp_opt_dir; + + /* Extract the stksess, return OK if no stksess available. */ + if (s) + stkctr = &s->stkctr[rule->arg.gpc.sc]; + else + stkctr = &sess->stkctr[rule->arg.gpc.sc]; + + ts = stkctr_entry(stkctr); + if (!ts) + return ACT_RET_CONT; + + /* First, update gpc_rate if it's tracked. Second, update its gpc if tracked. */ + ptr1 = stktable_data_ptr_idx(stkctr->table, ts, STKTABLE_DT_GPC_RATE, rule->arg.gpc.idx); + ptr2 = stktable_data_ptr_idx(stkctr->table, ts, STKTABLE_DT_GPC, rule->arg.gpc.idx); + + if (ptr1 || ptr2) { + if (!rule->arg.gpc.expr) + value = (unsigned int)(rule->arg.gpc.value); + else { + switch (rule->from) { + case ACT_F_TCP_REQ_SES: smp_opt_dir = SMP_OPT_DIR_REQ; break; + case ACT_F_TCP_REQ_CNT: smp_opt_dir = SMP_OPT_DIR_REQ; break; + case ACT_F_TCP_RES_CNT: smp_opt_dir = SMP_OPT_DIR_RES; break; + case ACT_F_HTTP_REQ: smp_opt_dir = SMP_OPT_DIR_REQ; break; + case ACT_F_HTTP_RES: smp_opt_dir = SMP_OPT_DIR_RES; break; + default: + send_log(px, LOG_ERR, "stick table: internal error while setting gpc%u.", rule->arg.gpc.idx); + if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) + ha_alert("stick table: internal error while executing setting gpc%u.\n", rule->arg.gpc.idx); + return ACT_RET_CONT; + } + + /* Fetch and cast the expression. */ + smp = sample_fetch_as_type(px, sess, s, smp_opt_dir|SMP_OPT_FINAL, rule->arg.gpc.expr, SMP_T_SINT); + if (!smp) { + send_log(px, LOG_WARNING, "stick table: invalid expression or data type while setting gpc%u.", rule->arg.gpc.idx); + if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) + ha_alert("stick table: invalid expression or data type while setting gpc%u.\n", rule->arg.gpc.idx); + return ACT_RET_CONT; + } + value = (unsigned int)(smp->data.u.sint); + } + + if (value) { + /* only update the value if non-null increment */ + HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock); + + if (ptr1) + update_freq_ctr_period(&stktable_data_cast(ptr1, std_t_frqp), + stkctr->table->data_arg[STKTABLE_DT_GPC_RATE].u, value); + + if (ptr2) + stktable_data_cast(ptr2, std_t_uint) += value; + + HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock); + } + /* always touch the table so that it doesn't expire */ + stktable_touch_local(stkctr->table, ts, 0); + } + + return ACT_RET_CONT; +} + +/* This function is a parser for the "sc-add-gpc" action. It understands the + * format: + * + * sc-add-gpc(,) + * + * It returns ACT_RET_PRS_ERR if fails and is filled with an error message. + * Otherwise, it returns ACT_RET_PRS_OK and the variable 'rule->arg.gpc.expr' + * is filled with the pointer to the expression to execute or NULL if the arg + * is directly an integer stored into 'rule->arg.gpt.value'. + */ +static enum act_parse_ret parse_add_gpc(const char **args, int *arg, struct proxy *px, + struct act_rule *rule, char **err) +{ + const char *cmd_name = args[*arg-1]; + char *error; + int smp_val; + + cmd_name += strlen("sc-add-gpc"); + if (*cmd_name != '(') { + memprintf(err, "Missing or invalid arguments for '%s'. Expects sc-add-gpc(,)", args[*arg-1]); + return ACT_RET_PRS_ERR; + } + cmd_name++; /* skip the '(' */ + rule->arg.gpc.idx = strtoul(cmd_name, &error, 10); /* Convert stick table id. */ + if (*error != ',') { + memprintf(err, "Missing gpc ID. Expects %s(,)", args[*arg-1]); + return ACT_RET_PRS_ERR; + } + else { + cmd_name = error + 1; /* skip the ',' */ + rule->arg.gpc.sc = strtol(cmd_name, &error, 10); /* Convert stick table id. */ + if (*error != ')') { + memprintf(err, "invalid stick table track ID '%s'. Expects %s(,)", cmd_name, args[*arg-1]); + return ACT_RET_PRS_ERR; + } + + if (rule->arg.gpc.sc >= MAX_SESS_STKCTR) { + memprintf(err, "invalid stick table track ID '%s' for '%s'. The max allowed ID is %d", + cmd_name, args[*arg-1], MAX_SESS_STKCTR-1); + return ACT_RET_PRS_ERR; + } + } + rule->action_ptr = action_add_gpc; + + /* value may be either an integer or an expression */ + rule->arg.gpc.expr = NULL; + rule->arg.gpc.value = strtol(args[*arg], &error, 10); + if (*error == '\0') { + /* valid integer, skip it */ + (*arg)++; + } else { + rule->arg.gpc.expr = sample_parse_expr((char **)args, arg, px->conf.args.file, + px->conf.args.line, err, &px->conf.args, NULL); + if (!rule->arg.gpc.expr) + return ACT_RET_PRS_ERR; + + switch (rule->from) { + case ACT_F_TCP_REQ_SES: smp_val = SMP_VAL_FE_SES_ACC; break; + case ACT_F_TCP_REQ_CNT: smp_val = SMP_VAL_FE_REQ_CNT; break; + case ACT_F_TCP_RES_CNT: smp_val = SMP_VAL_BE_RES_CNT; break; + case ACT_F_HTTP_REQ: smp_val = SMP_VAL_FE_HRQ_HDR; break; + case ACT_F_HTTP_RES: smp_val = SMP_VAL_BE_HRS_HDR; break; + default: + memprintf(err, "internal error, unexpected rule->from=%d, please report this bug!", rule->from); + return ACT_RET_PRS_ERR; + } + + if (!(rule->arg.gpc.expr->fetch->val & smp_val)) { + memprintf(err, "fetch method '%s' extracts information from '%s', none of which is available here", args[*arg-1], + sample_src_names(rule->arg.gpc.expr->fetch->use)); + free(rule->arg.gpc.expr); + return ACT_RET_PRS_ERR; + } + } + + rule->action = ACT_CUSTOM; + + return ACT_RET_PRS_OK; +} + /* set temp integer to the number of used entries in the table pointed to by expr. * Accepts exactly 1 argument of type table. */ @@ -5072,6 +5232,7 @@ static struct cli_kw_list cli_kws = {{ },{ INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); static struct action_kw_list tcp_conn_kws = { { }, { + { "sc-add-gpc", parse_add_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc", parse_inc_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc0", parse_inc_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc1", parse_inc_gpc, KWF_MATCH_PREFIX }, @@ -5083,6 +5244,7 @@ static struct action_kw_list tcp_conn_kws = { { }, { INITCALL1(STG_REGISTER, tcp_req_conn_keywords_register, &tcp_conn_kws); static struct action_kw_list tcp_sess_kws = { { }, { + { "sc-add-gpc", parse_add_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc", parse_inc_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc0", parse_inc_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc1", parse_inc_gpc, KWF_MATCH_PREFIX }, @@ -5094,6 +5256,7 @@ static struct action_kw_list tcp_sess_kws = { { }, { INITCALL1(STG_REGISTER, tcp_req_sess_keywords_register, &tcp_sess_kws); static struct action_kw_list tcp_req_kws = { { }, { + { "sc-add-gpc", parse_add_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc", parse_inc_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc0", parse_inc_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc1", parse_inc_gpc, KWF_MATCH_PREFIX }, @@ -5105,6 +5268,7 @@ static struct action_kw_list tcp_req_kws = { { }, { INITCALL1(STG_REGISTER, tcp_req_cont_keywords_register, &tcp_req_kws); static struct action_kw_list tcp_res_kws = { { }, { + { "sc-add-gpc", parse_add_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc", parse_inc_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc0", parse_inc_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc1", parse_inc_gpc, KWF_MATCH_PREFIX }, @@ -5116,6 +5280,7 @@ static struct action_kw_list tcp_res_kws = { { }, { INITCALL1(STG_REGISTER, tcp_res_cont_keywords_register, &tcp_res_kws); static struct action_kw_list http_req_kws = { { }, { + { "sc-add-gpc", parse_add_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc", parse_inc_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc0", parse_inc_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc1", parse_inc_gpc, KWF_MATCH_PREFIX }, @@ -5127,6 +5292,7 @@ static struct action_kw_list http_req_kws = { { }, { INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_kws); static struct action_kw_list http_res_kws = { { }, { + { "sc-add-gpc", parse_add_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc", parse_inc_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc0", parse_inc_gpc, KWF_MATCH_PREFIX }, { "sc-inc-gpc1", parse_inc_gpc, KWF_MATCH_PREFIX },