]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: spoe/rules: Add "send-spoe-group" action for tcp/http rules
authorChristopher Faulet <cfaulet@haproxy.com>
Thu, 21 Sep 2017 09:03:52 +0000 (11:03 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 31 Oct 2017 10:36:12 +0000 (11:36 +0100)
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
include/types/spoe.h
src/flt_spoe.c

index 8d0624839481f7c5e0a90974c7b2a403b7155128..f5cf60305a575a5164d6cedf0d2791ded7fa353d 100644 (file)
@@ -3771,6 +3771,7 @@ http-request { allow | auth [realm <realm>] | redirect <rule> |
               sc-inc-gpc0(<sc-id>) |
               sc-set-gpt0(<sc-id>) <int> |
               silent-drop |
+             send-spoe-group
              }
              [ { if | unless } <condition> ]
   Access control for Layer 7 requests
@@ -4167,10 +4168,23 @@ http-request { allow | auth [realm <realm>] | redirect <rule> |
       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 <engine-name> <group-name> :
+      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.
+
+        <engine-name> The SPOE engine name.
+
+        <group-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 <name> <fmt> | set-nice <nice> |
                 sc-inc-gpc0(<sc-id>) |
                 sc-set-gpt0(<sc-id>) <int> |
                 silent-drop |
+               send-spoe-group
               }
               [ { if | unless } <condition> ]
   Access control for Layer 7 responses
@@ -4499,6 +4514,18 @@ http-response { allow | deny | add-header <name> <fmt> | set-nice <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 <engine-name> <group-name> :
+      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.
+
+        <engine-name> The SPOE engine name.
+
+        <group-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 <action> [{if | unless} <condition>]
     - set-var(<var-name>) <expr>
     - unset-var(<var-name>)
     - silent-drop
+    - send-spoe-group <engin-name> <group-name>
 
   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 <action> [{if | unless} <condition>]
   The "unset-var" is used to unset a variable. See above for details about
   <var-name>.
 
+  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.
+
+    <engine-name> The SPOE engine name.
+
+    <group-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 <action> [{if | unless} <condition>]
         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 <engine-name> <group-name>
+        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 <action> [{if | unless} <condition>]
 
         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.
+
+    <engine-name> The SPOE engine name.
+
+    <group-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"
index d360cbac3d6d9632438655bf56a517bcf98a0e2b..ff649fafc9f8b121f18f9dc39440c829e3113f10 100644 (file)
@@ -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 */
index 8494cafe38ced2369e4dbc91b66be875eb032932..9c50bf23d690cd6393c88559e2cfef92ba19ab4c 100644 (file)
@@ -24,6 +24,7 @@
 #include <types/spoe.h>
 
 #include <proto/acl.h>
+#include <proto/action.h>
 #include <proto/arg.h>
 #include <proto/backend.h>
 #include <proto/filters.h>
@@ -39,6 +40,7 @@
 #include <proto/stream.h>
 #include <proto/stream_interface.h>
 #include <proto/task.h>
+#include <proto/tcp_rules.h>
 #include <proto/vars.h>
 
 #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 <rule>. The format of an rule using 'send-spoe-group'
+ * action should be:
+ *
+ *   (http|tcp)-(request|response) send-spoe-group <engine-id> <group-id>
+ *
+ * So, we'll loop on each configured SPOE filter for the proxy <px> to find the
+ * SPOE engine matching <engine-id>. And then, we'll try to find the good group
+ * matching <group-id>. 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
+        * <px> */
+       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 <engine-id> <group-id>
+ *
+ * It returns ACT_RET_PRS_ERR if fails and <err> 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: <engine-id> <group-id>");
+               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);