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
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) */
* 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;
path->prep_in_flight = 0;
path->in_flight = 0;
path->ifae_pkts = 0;
+ path->pacing_burst = burst;
quic_cc_init(&path->cc, algo, qc);
}
#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>
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;
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 */
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;
/* 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);
/* 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);