From 76c09ef8de396e7a269578f430b1c9b29b5c8d04 Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Thu, 21 Sep 2017 11:03:52 +0200 Subject: [PATCH] MEDIUM: spoe/rules: Add "send-spoe-group" action for tcp/http rules This action is used to trigger sending of a group of SPOE messages. To do so, the SPOE engine used to send messages must be defined, as well as the SPOE group to send. Of course, the SPOE engine must refer to an existing SPOE filter. If not engine name is provided on the SPOE filter line, the SPOE agent name must be used. For example: http-request send-spoe-group my-engine some-group This action is available for "tcp-request content", "tcp-response content", "http-request" and "http-response" rulesets. It cannot be used for tcp connection/session rulesets because actions for these rulesets cannot yield. For now, the action keyword is parsed and checked. But it does nothing. Its processing will be added in another patch. --- doc/configuration.txt | 51 +++++++++++ include/types/spoe.h | 1 + src/flt_spoe.c | 199 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 245 insertions(+), 6 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 8d0624839..f5cf60305 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -3771,6 +3771,7 @@ http-request { allow | auth [realm ] | redirect | sc-inc-gpc0() | sc-set-gpt0() | silent-drop | + send-spoe-group } [ { if | unless } ] Access control for Layer 7 requests @@ -4167,10 +4168,23 @@ http-request { allow | auth [realm ] | redirect | pass the first router, though it's still delivered to local networks. Do not use it unless you fully understand how it works. + - "wait-for-handshake" : this will delay the processing of the request until the SSL handshake happened. This is mostly useful to delay processing early data until we're sure they are valid. + - send-spoe-group : + This action is used to trigger sending of a group of SPOE messages. To do + so, the SPOE engine used to send messages must be defined, as well as the + SPOE group to send. Of course, the SPOE engine must refer to an existing + SPOE filter. If not engine name is provided on the SPOE filter line, the + SPOE agent name must be used. + + The SPOE engine name. + + The SPOE group name as specified in the engine + configuration. + 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 @@ -4248,6 +4262,7 @@ http-response { allow | deny | add-header | set-nice | sc-inc-gpc0() | sc-set-gpt0() | silent-drop | + send-spoe-group } [ { if | unless } ] Access control for Layer 7 responses @@ -4499,6 +4514,18 @@ http-response { allow | deny | add-header | set-nice | pass the first router, though it's still delivered to local networks. Do not use it unless you fully understand how it works. + - send-spoe-group : + This action is used to trigger sending of a group of SPOE messages. To do + so, the SPOE engine used to send messages must be defined, as well as the + SPOE group to send. Of course, the SPOE engine must refer to an existing + SPOE filter. If not engine name is provided on the SPOE filter line, the + SPOE agent name must be used. + + The SPOE engine name. + + The SPOE group name as specified in the engine + configuration. + There is no limit to the number of http-response statements per instance. It is important to know that http-response rules are processed very early in @@ -9240,6 +9267,7 @@ tcp-request content [{if | unless} ] - set-var() - unset-var() - silent-drop + - send-spoe-group They have the same meaning as their counter-parts in "tcp-request connection" so please refer to that section for a complete description. @@ -9293,6 +9321,16 @@ tcp-request content [{if | unless} ] The "unset-var" is used to unset a variable. See above for details about . + The "send-spoe-group" is used to trigger sending of a group of SPOE + messages. To do so, the SPOE engine used to send messages must be defined, as + well as the SPOE group to send. Of course, the SPOE engine must refer to an + existing SPOE filter. If not engine name is provided on the SPOE filter line, + the SPOE agent name must be used. + + The SPOE engine name. + + The SPOE group name as specified in the engine configuration. + Example: tcp-request content set-var(sess.my_var) src @@ -9483,6 +9521,9 @@ tcp-response content [{if | unless} ] TCP reset doesn't pass the first router, though it's still delivered to local networks. Do not use it unless you fully understand how it works. + - send-spoe-group + Send a group of SPOE messages. + Note that the "if/unless" condition is optional. If no condition is set on the action, it is simply performed unconditionally. That can be useful for for changing the default action to a reject. @@ -9524,6 +9565,16 @@ tcp-response content [{if | unless} ] tcp-request content unset-var(sess.my_var) + The "send-spoe-group" is used to trigger sending of a group of SPOE + messages. To do so, the SPOE engine used to send messages must be defined, as + well as the SPOE group to send. Of course, the SPOE engine must refer to an + existing SPOE filter. If not engine name is provided on the SPOE filter line, + the SPOE agent name must be used. + + The SPOE engine name. + + The SPOE group name as specified in the engine configuration. + See section 7 about ACL usage. See also : "tcp-request content", "tcp-response inspect-delay" diff --git a/include/types/spoe.h b/include/types/spoe.h index d360cbac3..ff649fafc 100644 --- a/include/types/spoe.h +++ b/include/types/spoe.h @@ -276,6 +276,7 @@ struct spoe_context { struct stream *strm; /* The stream that should be offloaded */ struct list *events; /* List of messages that will be sent during the stream processing */ + struct list *groups; /* List of available SPOE group */ struct buffer *buffer; /* Buffer used to store a encoded messages */ struct buffer_wait buffer_wait; /* position in the list of ressources waiting for a buffer */ diff --git a/src/flt_spoe.c b/src/flt_spoe.c index 8494cafe3..9c50bf23d 100644 --- a/src/flt_spoe.c +++ b/src/flt_spoe.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -39,6 +40,7 @@ #include #include #include +#include #include #if defined(DEBUG_SPOE) || defined(DEBUG_FULL) @@ -2655,6 +2657,7 @@ spoe_create_context(struct filter *filter) ctx->status_code = SPOE_CTX_ERR_NONE; ctx->flags = 0; ctx->events = conf->agent->events; + ctx->groups = &conf->agent->groups; ctx->buffer = &buf_empty; LIST_INIT(&ctx->buffer_wait.list); ctx->buffer_wait.target = ctx; @@ -3786,8 +3789,8 @@ parse_spoe_flt(char **args, int *cur_arg, struct proxy *px, goto next_mph; } if (msg->event == SPOE_EV_NONE) { - Warning("Proxy '%s': Ignore SPOE message without event at %s:%d.\n", - px->id, msg->conf.file, msg->conf.line); + Warning("Proxy '%s': Ignore SPOE message '%s' without event at %s:%d.\n", + px->id, msg->id, msg->conf.file, msg->conf.line); goto next_mph; } @@ -3843,13 +3846,13 @@ parse_spoe_flt(char **args, int *cur_arg, struct proxy *px, list_for_each_entry(arg, &msg->args, list) { if (!(arg->expr->fetch->val & where)) { - Warning("Proxy '%s': Ignore SPOE message at %s:%d: " + memprintf(err, "Ignore SPOE message '%s' at %s:%d: " "some args extract information from '%s', " - "none of which is available here ('%s').\n", - px->id, msg->conf.file, msg->conf.line, + "none of which is available here ('%s')", + msg->id, msg->conf.file, msg->conf.line, sample_ckp_names(arg->expr->fetch->use), sample_ckp_names(where)); - goto next_mph; + goto error; } } @@ -3964,6 +3967,164 @@ parse_spoe_flt(char **args, int *cur_arg, struct proxy *px, return -1; } +/* Send a SPOE group. TODO */ +static enum act_return +spoe_send_group(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + struct filter *filter; + struct spoe_agent *agent = NULL; + struct spoe_group *group = NULL; + struct spoe_context *ctx = NULL; + int ret, dir; + + list_for_each_entry(filter, &s->strm_flt.filters, list) { + if (filter->config == rule->arg.act.p[0]) { + agent = rule->arg.act.p[2]; + group = rule->arg.act.p[3]; + ctx = filter->ctx; + break; + } + } + if (agent == NULL || group == NULL || ctx == NULL) + return ACT_RET_ERR; + + /* TODO */ + return ACT_RET_CONT; +} + +/* Check an "send-spoe-group" action. Here, we'll try to find the real SPOE + * group associated to . The format of an rule using 'send-spoe-group' + * action should be: + * + * (http|tcp)-(request|response) send-spoe-group + * + * So, we'll loop on each configured SPOE filter for the proxy to find the + * SPOE engine matching . And then, we'll try to find the good group + * matching . Finally, we'll check all messages referenced by the SPOE + * group. + * + * The function returns 1 in success case, otherwise, it returns 0 and err is + * filled. + */ +static int +check_send_spoe_group(struct act_rule *rule, struct proxy *px, char **err) +{ + struct flt_conf *fconf; + struct spoe_config *conf; + struct spoe_agent *agent = NULL; + struct spoe_group *group; + struct spoe_message *msg; + char *engine_id = rule->arg.act.p[0]; + char *group_id = rule->arg.act.p[1]; + unsigned int where = 0; + + switch (rule->from) { + case ACT_F_TCP_REQ_SES: where = SMP_VAL_FE_SES_ACC; break; + case ACT_F_TCP_REQ_CNT: where = SMP_VAL_FE_REQ_CNT; break; + case ACT_F_TCP_RES_CNT: where = SMP_VAL_BE_RES_CNT; break; + case ACT_F_HTTP_REQ: where = SMP_VAL_FE_HRQ_HDR; break; + case ACT_F_HTTP_RES: where = SMP_VAL_BE_HRS_HDR; break; + default: + memprintf(err, + "internal error, unexpected rule->from=%d, please report this bug!", + rule->from); + goto error; + } + + /* Try to find the SPOE engine by checking all SPOE filters for proxy + * */ + list_for_each_entry(fconf, &px->filter_configs, list) { + conf = fconf->conf; + + /* This is not an SPOE filter */ + if (fconf->id != spoe_filter_id) + continue; + + /* This is the good engine */ + if (!strcmp(conf->id, engine_id)) { + agent = conf->agent; + break; + } + } + if (agent == NULL) { + memprintf(err, "unable to find SPOE engine '%s' used by the send-spoe-group '%s'", + engine_id, group_id); + goto error; + } + + /* Try to find the right group */ + list_for_each_entry(group, &agent->groups, list) { + /* This is the good group */ + if (!strcmp(group->id, group_id)) + break; + } + if (&group->list == &agent->groups) { + memprintf(err, "unable to find SPOE group '%s' into SPOE engine '%s' configuration", + group_id, engine_id); + goto error; + } + + /* Ok, we found the group, we need to check messages and their + * arguments */ + list_for_each_entry(msg, &group->messages, by_grp) { + struct spoe_arg *arg; + + list_for_each_entry(arg, &msg->args, list) { + if (!(arg->expr->fetch->val & where)) { + memprintf(err, "Invalid SPOE message '%s' used by SPOE group '%s' at %s:%d: " + "some args extract information from '%s'," + "none of which is available here ('%s')", + msg->id, group->id, msg->conf.file, msg->conf.line, + sample_ckp_names(arg->expr->fetch->use), + sample_ckp_names(where)); + goto error; + } + } + } + + free(engine_id); + free(group_id); + rule->arg.act.p[0] = fconf; /* Associate filter config with the rule */ + rule->arg.act.p[1] = conf; /* Associate SPOE config with the rule */ + rule->arg.act.p[2] = agent; /* Associate SPOE agent with the rule */ + rule->arg.act.p[3] = group; /* Associate SPOE group with the rule */ + return 1; + + error: + free(engine_id); + free(group_id); + return 0; +} + +/* Parse 'send-spoe-group' action following the format: + * + * ... send-spoe-group + * + * It returns ACT_RET_PRS_ERR if fails and is filled with an error + * message. Otherwise, it returns ACT_RET_PRS_OK and parsing engine and group + * ids are saved and used later, when the rule will be checked. + */ +static enum act_parse_ret +parse_send_spoe_group(const char **args, int *orig_arg, struct proxy *px, + struct act_rule *rule, char **err) +{ + if (!*args[*orig_arg] || !*args[*orig_arg+1] || + (*args[*orig_arg+2] && strcmp(args[*orig_arg+2], "if") != 0 && strcmp(args[*orig_arg+2], "unless") != 0)) { + memprintf(err, "expects 2 arguments: "); + return ACT_RET_PRS_ERR; + } + rule->arg.act.p[0] = strdup(args[*orig_arg]); /* Copy the SPOE engine id */ + rule->arg.act.p[1] = strdup(args[*orig_arg+1]); /* Cope the SPOE group id */ + + (*orig_arg) += 2; + + rule->action = ACT_CUSTOM; + rule->action_ptr = spoe_send_group; + rule->check_ptr = check_send_spoe_group; + return ACT_RET_PRS_OK; +} + /* Declare the filter parser for "spoe" keyword */ static struct flt_kw_list flt_kws = { "SPOE", { }, { @@ -3972,10 +4133,36 @@ static struct flt_kw_list flt_kws = { "SPOE", { }, { } }; +/* Delcate the action parser for "spoe-action" keyword */ +static struct action_kw_list tcp_req_action_kws = { { }, { + { "send-spoe-group", parse_send_spoe_group }, + { /* END */ }, + } +}; +static struct action_kw_list tcp_res_action_kws = { { }, { + { "send-spoe-group", parse_send_spoe_group }, + { /* END */ }, + } +}; +static struct action_kw_list http_req_action_kws = { { }, { + { "send-spoe-group", parse_send_spoe_group }, + { /* END */ }, + } +}; +static struct action_kw_list http_res_action_kws = { { }, { + { "send-spoe-group", parse_send_spoe_group }, + { /* END */ }, + } +}; + __attribute__((constructor)) static void __spoe_init(void) { flt_register_keywords(&flt_kws); + tcp_req_cont_keywords_register(&tcp_req_action_kws); + tcp_res_cont_keywords_register(&tcp_res_action_kws); + http_req_keywords_register(&http_req_action_kws); + http_res_keywords_register(&http_res_action_kws); pool2_spoe_ctx = create_pool("spoe_ctx", sizeof(struct spoe_context), MEM_F_SHARED); pool2_spoe_appctx = create_pool("spoe_appctx", sizeof(struct spoe_appctx), MEM_F_SHARED);