]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: quic: support ACL for quic-initial rules
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 19 Jul 2024 14:05:15 +0000 (16:05 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Thu, 25 Jul 2024 13:39:39 +0000 (15:39 +0200)
Add ACL condition support for quic-initial rules. This requires the
extension of quic_parse_quic_initial() to parse an extra if/unless
block.

Only layer4 client samples are allowed to be used with quic-initial
rules. However, due to the early execution of quic-initial rules prior
to any connection instantiation, some samples are non supported.

To be able to use the 4 described samples, a dummy session is
instantiated before quic-initial rules execution. Its src and dst fields
are set from the received datagram values.

doc/configuration.txt
include/haproxy/quic_rules.h
src/cfgparse-quic.c
src/quic_rules.c
src/quic_rx.c

index a64320a698986e3a17c598cd0301877934ad6248..0af6bb04fdf6ce2531dfbdf5e0472fc8f638645e 100644 (file)
@@ -10981,7 +10981,7 @@ persist rdp-cookie(<name>)
   See also : "balance rdp-cookie", "tcp-request" and the "req.rdp_cookie" ACL.
 
 
-quic-initial <action>
+quic-initial <action> [ { if | unless } <condition> ]
   Perform an action on an incoming QUIC Initial packet. Contrary to
   "tcp-request connection", this is executed prior to any connection element
   instantiation and starting and completion of the SSL handshake, which is more
@@ -10996,6 +10996,15 @@ quic-initial <action>
     <action>    defines the action to perform if the condition applies. See
                 below.
 
+    <condition> is a standard layer4-only ACL-based condition (see section 7).
+                However, QUIC initial rules are executed too early even for
+                some layer4 sample fetch methods despite no configuration
+                warning and may result in unspecified runtime behavior,
+                although they will not crash. Consider that only internal
+                samples and layer4 "src*" and "dst*" are considered as
+                supported for now.
+
+
   This action is executed early during QUIC packet parsing. As such, only a
   minimal list of actions is supported :
     - accept
index a38ff696a80d57ec1356ff795ff6f4d14bd101eb..2cfcf99644b54c30e96e2b85cda478df2dcde7f5 100644 (file)
@@ -1,13 +1,17 @@
 #ifndef _HAPROXY_QUIC_RULES_H
 #define _HAPROXY_QUIC_RULES_H
 
+#include <sys/socket.h>
+
 #include <haproxy/action-t.h>
 
 struct listener;
 
 extern struct action_kw_list quic_init_actions_list;
 
-int quic_init_exec_rules(struct listener *li);
+int quic_init_exec_rules(struct listener *li,
+                         struct sockaddr_storage *saddr,
+                         struct sockaddr_storage *daddr);
 
 struct action_kw *action_quic_init_custom(const char *kw);
 
index 6b287e291061c8bf29ec7a11c2be9f8fc99e844b..929360de19ff371d1314839cf846cc3865243851 100644 (file)
@@ -2,6 +2,7 @@
 #include <string.h>
 #include <netinet/udp.h>
 
+#include <haproxy/acl.h>
 #include <haproxy/action.h>
 #include <haproxy/api.h>
 #include <haproxy/cfgparse.h>
@@ -345,10 +346,16 @@ static int quic_parse_quic_initial(char **args, int section_type, struct proxy *
                                    const struct proxy *defpx, const char *file, int line,
                                    char **err)
 {
+       const struct acl *acl;
        struct act_rule *rule;
        struct action_kw *kw;
+       const char *acl_kw;
+       unsigned int where;
+       int warn = 0;
        int arg = 1;
 
+       where = SMP_VAL_FE_CON_ACC;
+
        if (curpx == defpx && strlen(defpx->id) == 0) {
                memprintf(err, "%s is not allowed in anonymous 'defaults' sections",
                          args[0]);
@@ -397,12 +404,54 @@ static int quic_parse_quic_initial(char **args, int section_type, struct proxy *
                goto err;
        }
 
+       if (strcmp(args[arg], "if") == 0 || strcmp(args[arg], "unless") == 0) {
+               if ((rule->cond = build_acl_cond(file, line, &curpx->acl, curpx, (const char **)args+arg, err)) == NULL) {
+                       memprintf(err,
+                                 "'%s %s %s' : error detected in %s '%s' while parsing '%s' condition : %s",
+                                 args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg], *err);
+                       goto err;
+               }
+       }
+       else if (*args[arg]) {
+               memprintf(err,
+                        "'%s %s %s' only accepts 'if' or 'unless', in %s '%s' (got '%s')",
+                        args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg]);
+               goto err;
+       }
+
+       acl = rule->cond ? acl_cond_conflicts(rule->cond, where) : NULL;
+       if (acl) {
+               if (acl->name && *acl->name)
+                       memprintf(err,
+                                 "acl '%s' will never match in '%s' because it only involves keywords that are incompatible with '%s'",
+                                 acl->name, args[0], sample_ckp_names(where));
+               else
+                       memprintf(err,
+                                 "anonymous acl will never match in '%s' because it uses keyword '%s' which is incompatible with '%s'",
+                                 args[0],
+                                 LIST_ELEM(acl->expr.n, struct acl_expr *, list)->kw,
+                                 sample_ckp_names(where));
+
+               warn++;
+       }
+       else if (rule->cond && acl_cond_kw_conflicts(rule->cond, where, &acl, &acl_kw)) {
+               if (acl->name && *acl->name)
+                       memprintf(err,
+                                 "acl '%s' involves keyword '%s' which is incompatible with '%s'",
+                                 acl->name, acl_kw, sample_ckp_names(where));
+               else
+                       memprintf(err,
+                                 "anonymous acl involves keyword '%s' which is incompatible with '%s'",
+                                 acl_kw, sample_ckp_names(where));
+               warn++;
+       }
+
        /* the following function directly emits the warning */
        warnif_misplaced_quic_init(curpx, file, line, args[0]);
 
        LIST_APPEND(&curpx->quic_init_rules, &rule->list);
 
-       return 0;
+       return warn;
 
  err:
        free_act_rule(rule);
index 909c0a08b8808abb7265ede683bfaa823ea363f4..cd7202b61ea276c8d529c5c2fe25423840682fda 100644 (file)
@@ -6,10 +6,14 @@
 #include <haproxy/listener.h>
 #include <haproxy/proxy-t.h>
 #include <haproxy/sample-t.h>
+#include <haproxy/session-t.h>
 
 /* Execute registered quic-initial rules on proxy owning <li> listener. */
-int quic_init_exec_rules(struct listener *li)
+int quic_init_exec_rules(struct listener *li,
+                         struct sockaddr_storage *saddr,
+                         struct sockaddr_storage *daddr)
 {
+       static THREAD_LOCAL struct session rule_sess;
        struct act_rule *rule;
        enum acl_test_res ret;
        struct proxy *px;
@@ -17,11 +21,19 @@ int quic_init_exec_rules(struct listener *li)
 
        px = li->bind_conf->frontend;
 
+       /* Initialize session elements specific to the current datagram. All
+        * others members are set to 0 thanks to static storage class.
+        */
+       rule_sess.fe = px;
+       rule_sess.listener = li;
+       rule_sess.src = saddr;
+       rule_sess.dst = daddr;
+
        list_for_each_entry(rule, &px->quic_init_rules, list) {
                ret = ACL_TEST_PASS;
 
                if (rule->cond) {
-                       ret = acl_exec_cond(rule->cond, px, NULL, NULL, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
+                       ret = acl_exec_cond(rule->cond, px, &rule_sess, NULL, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
                        ret = acl_pass(ret);
                        if (rule->cond->pol == ACL_COND_UNLESS)
                                ret = !ret;
@@ -29,7 +41,7 @@ int quic_init_exec_rules(struct listener *li)
 
                if (ret) {
                        if (rule->action_ptr) {
-                               switch (rule->action_ptr(rule, px, NULL, NULL, 0)) {
+                               switch (rule->action_ptr(rule, px, &rule_sess, NULL, 0)) {
                                case ACT_RET_CONT:
                                        break;
                                case ACT_RET_DONE:
index 6bec16dc6563664374c4fcbfb0f9004c1246e0a7..d9c4e3290ae6b5ae6a6cc52b5f1cb1ed82ba431e 100644 (file)
@@ -1607,7 +1607,7 @@ static struct quic_conn *quic_rx_pkt_retrieve_conn(struct quic_rx_packet *pkt,
                                        goto err;
                        }
 
-                       if (!quic_init_exec_rules(l)) {
+                       if (!quic_init_exec_rules(l, &dgram->saddr, &dgram->daddr)) {
                                TRACE_USER("drop datagram on quic-initial rules", QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
                                goto err;
                        }