]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: quic: define cubic-pacing congestion algorithm
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Mon, 18 Nov 2024 10:05:28 +0000 (11:05 +0100)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Tue, 19 Nov 2024 15:20:58 +0000 (16:20 +0100)
Define a new QUIC congestion algorithm token 'cubic-pacing' for
quic-cc-algo bind keyword. This is identical to default cubic
implementation, except that pacing is used for STREAM frames emission.

This algorithm supports an extra argument to specify a burst size. This
is stored into a new bind_conf member named quic_pacing_burst which can
be reuse to initialize quic path.

Pacing support is still considered experimental. As such, 'cubic-pacing'
can only be used with expose-experimental-directives set.

doc/configuration.txt
include/haproxy/listener-t.h
include/haproxy/quic_cc.h
src/cfgparse-quic.c
src/listener.c
src/quic_conn.c

index 8fbd480579f17ac6ab65ff6292f09357df8f0639..b35a2562b7ca301df1f204805ec2bf6114fd4dda 100644 (file)
@@ -17035,25 +17035,37 @@ proto <name>
   instance, it is possible to force the http/2 on clear TCP by specifying "proto
   h2" on the bind line.
 
-quic-cc-algo { cubic | newreno | nocc }[(<args,...>)]
+quic-cc-algo { cubic[-pacing] | newreno | nocc }[(<args,...>)]
   This is a QUIC specific setting to select the congestion control algorithm
   for any connection attempts to the configured QUIC listeners. They are similar
   to those used by TCP.
 
   Default value: cubic
 
+  It is possible to active pacing if the algorithm is compatible. This is done
+  by using the suffix "-pacing" after the algorithm name. Pacing purpose is to
+  smooth emission of data without burst to reduce network loss. In some
+  scenario, it can significantly improve network throughput. However, it can
+  also increase CPU usage if haproxy is forced to wait too long between each
+  emission. Pacing support is still experimental, as such it requires
+  "expose-experimental-directives".
+
   For further customization, a list of parameters can be specified after the
   algorithm token. It must be written between parenthesis, separated by a comma
   operator. Each argument is optional and can be empty if needed. Here is the
   mandatory order of each parameters :
   - maximum window size in bytes. It must be greater than 10k and smaller than
     4g. By default "tune.quic.frontend.default-max-window-size" value is used.
+  - count of datagrams to emit in a burst if pacing is activated. It must be
+    between the default value of 1 and 1024.
 
   Example:
       # newreno congestion control algorithm
       quic-cc-algo newreno
       # cubic congestion control algorithm with one megabytes as window
       quic-cc-algo cubic(1m)
+      # cubic with pacing on top of it, with burst limited to 12 datagrams
+      quic-cc-algo cubic-pacing(,12)
 
   A special value "nocc" may be used to force a fixed congestion window always
   set at the maximum size. It is reserved for debugging scenarios to remove any
index becc83b01d5df6a9891b78a62005c4f272d94a0e..ceb19b3821fa4bc6925ec4c5eb65e6d95986e3af 100644 (file)
@@ -185,6 +185,7 @@ struct bind_conf {
        struct quic_cc_algo *quic_cc_algo; /* QUIC control congestion algorithm */
        size_t max_cwnd;                   /* QUIC maximumu congestion control window size (kB) */
        enum quic_sock_mode quic_mode;     /* QUIC socket allocation strategy */
+       int quic_pacing_burst;             /* QUIC allowed pacing burst size */
 #endif
        struct proxy *frontend;    /* the frontend all these listeners belong to, or NULL */
        const struct mux_proto_list *mux_proto; /* the mux to use for all incoming connections (specified by the "proto" keyword) */
index 471bf57da36bc9dc8007df19fcb9dfd5dad92554..8003878fdb3f778911a9f3c5d0baf23f8196d73f 100644 (file)
@@ -82,7 +82,8 @@ static inline void *quic_cc_priv(const struct quic_cc *cc)
  * which is true for an IPv4 path, if not false for an IPv6 path.
  */
 static inline void quic_cc_path_init(struct quic_cc_path *path, int ipv4, unsigned long max_cwnd,
-                                     struct quic_cc_algo *algo, struct quic_conn *qc)
+                                     struct quic_cc_algo *algo, int burst,
+                                     struct quic_conn *qc)
 {
        unsigned int max_dgram_sz;
 
@@ -96,6 +97,7 @@ static inline void quic_cc_path_init(struct quic_cc_path *path, int ipv4, unsign
        path->prep_in_flight = 0;
        path->in_flight = 0;
        path->ifae_pkts = 0;
+       path->pacing_burst = burst;
        quic_cc_init(&path->cc, algo, qc);
 }
 
index c7ff2b7ca53c682dad90117f278c73820d1db5aa..0b1b4596d9b21becf0996e168218cbf790143296 100644 (file)
@@ -13,7 +13,7 @@
 #include <haproxy/global.h>
 #include <haproxy/listener.h>
 #include <haproxy/proxy.h>
-#include <haproxy/quic_cc-t.h>
+#include <haproxy/quic_cc.h>
 #include <haproxy/quic_rules.h>
 #include <haproxy/tools.h>
 
@@ -69,11 +69,38 @@ static unsigned long parse_window_size(const char *kw, char *value,
        return 0;
 }
 
+/* Parse <value> as a number of datagrams allowed for burst.
+ *
+ * Returns the parsed value or 0 on error.
+ */
+static uint parse_burst(const char *kw, char *value, char **end_opt, char **err)
+{
+       uint burst;
+
+       errno = 0;
+       burst = strtoul(value, end_opt, 0);
+       if (*end_opt == value || errno != 0) {
+               memprintf(err, "'%s' : could not parse burst value", kw);
+               goto fail;
+       }
+
+       if (!burst || burst > 1024) {
+               memprintf(err, "'%s' : pacing burst value must be between 1 and 1024", kw);
+               goto fail;
+       }
+
+       return burst;
+
+ fail:
+       return 0;
+}
+
 /* parse "quic-cc-algo" bind keyword */
 static int bind_parse_quic_cc_algo(char **args, int cur_arg, struct proxy *px,
                                    struct bind_conf *conf, char **err)
 {
        struct quic_cc_algo *cc_algo = NULL;
+       const char *str_pacing = "-pacing";
        const char *algo = NULL;
        char *arg;
 
@@ -100,6 +127,19 @@ static int bind_parse_quic_cc_algo(char **args, int cur_arg, struct proxy *px,
                algo = QUIC_CC_CUBIC_STR;
                *cc_algo = quic_cc_algo_cubic;
                arg += strlen(QUIC_CC_CUBIC_STR);
+
+               if (strncmp(arg, str_pacing, strlen(str_pacing)) == 0) {
+                       if (!experimental_directives_allowed) {
+                               memprintf(err, "'%s' : support for pacing is experimental, must be allowed via a global "
+                                         "'expose-experimental-directives'\n", args[cur_arg]);
+                               goto fail;
+                       }
+
+                       cc_algo->pacing_rate = quic_cc_default_pacing_rate;
+                       cc_algo->pacing_burst = quic_cc_default_pacing_burst;
+                       conf->quic_pacing_burst = 1;
+                       arg += strlen(str_pacing);
+               }
        }
        else if (strncmp(arg, QUIC_CC_NO_CC_STR, strlen(QUIC_CC_NO_CC_STR)) == 0) {
                /* nocc */
@@ -141,6 +181,26 @@ static int bind_parse_quic_cc_algo(char **args, int cur_arg, struct proxy *px,
                        arg = end_opt;
                }
 
+               if (*++arg == ')')
+                       goto out;
+
+               if (*arg != ',') {
+                       uint burst = parse_burst(args[cur_arg], arg, &end_opt, err);
+                       if (!burst)
+                               goto fail;
+
+                       conf->quic_pacing_burst = burst;
+
+                       if (*end_opt == ')') {
+                               goto out;
+                       }
+                       else if (*end_opt != ',') {
+                               memprintf(err, "'%s' : cannot parse burst argument for '%s' algorithm", args[cur_arg], algo);
+                               goto fail;
+                       }
+                       arg = end_opt;
+               }
+
                if (*++arg != ')') {
                        memprintf(err, "'%s' : too many argument for '%s' algorithm", args[cur_arg], algo);
                        goto fail;
index ec105c310872d8922b5fb0775d333050019b6a5a..7518f34ad280ab824b3277b3ae2fa918b09572d4 100644 (file)
@@ -2040,6 +2040,7 @@ struct bind_conf *bind_conf_alloc(struct proxy *fe, const char *file,
        /* Use connection socket for QUIC by default. */
        bind_conf->quic_mode = QUIC_SOCK_MODE_CONN;
        bind_conf->max_cwnd = global.tune.quic_frontend_max_window_size;
+       bind_conf->quic_pacing_burst = 0;
 #endif
        LIST_INIT(&bind_conf->listeners);
 
index dd2df5307d09222f598c311a84c9fa2859c093e3..c5c2a74ff6122412a803e73c6754282cf23e85b1 100644 (file)
@@ -1224,7 +1224,8 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
        /* Only one path at this time (multipath not supported) */
        qc->path = &qc->paths[0];
        quic_cc_path_init(qc->path, ipv4, server ? l->bind_conf->max_cwnd : 0,
-                         cc_algo ? cc_algo : default_quic_cc_algo, qc);
+                         cc_algo ? cc_algo : default_quic_cc_algo,
+                         l->bind_conf->quic_pacing_burst, qc);
 
        memcpy(&qc->local_addr, local_addr, sizeof(qc->local_addr));
        memcpy(&qc->peer_addr, peer_addr, sizeof qc->peer_addr);