- tune.quic.fe.tx.udp-gso
- tune.quic.listen
- tune.quic.mem.tx-max
+ - tune.quic.sec.stateless-reset-rate
- tune.quic.zero-copy-fwd-send
- tune.renice.runtime
- tune.renice.startup
connection will always be able to use a window of at least 2 datagrams, so a
proper maxconn should be used in conjunction.
+tune.quic.sec.stateless-reset-rate <number>
+ Sets the maximum total number of QUIC Stateless Reset packets emitted per
+ second, across all threads. Each thread gets its own even share of this
+ total, so the actual number sent per second may be slightly under or over
+ <number> depending on how evenly it divides by the thread count, but the
+ total across all threads never drifts from it over time.
+
+ The default value is 1000.
+
+ See https://www.rfc-editor.org/rfc/rfc9000.html#section-10.3 for more
+ information about QUIC Stateless Reset.
+
tune.quic.zero-copy-fwd-send { on | off }
Enables ('on') of disabled ('off') the zero-copy sends of data for the QUIC
multiplexer. It is enabled by default.
#define QUIC_DFLT_BE_MAX_IDLE_TIMEOUT 30000 /* milliseconds */
/* Default Retry threshold */
#define QUIC_DFLT_SEC_RETRY_THRESHOLD 100 /* in connection openings */
+/* Default Stateless Reset rate limit */
+#define QUIC_DFLT_SEC_STATELESS_RESET_RATE 1000 /* packets/s, all threads */
/* Default settings related to flow-control */
#define QUIC_DFLT_FE_STREAM_DATA_RATIO 90
#define QUIC_DFLT_BE_STREAM_DATA_RATIO 90
} be;
uint64_t mem_tx_max;
+ uint sec_stateless_reset_rate; /* max total Stateless Reset pkts/s, all threads (0 = unlimited) */
};
#endif /* USE_QUIC */
.fb_opts = QUIC_TUNE_FB_TX_PACING|QUIC_TUNE_FB_TX_UDP_GSO,
},
.mem_tx_max = QUIC_MAX_TX_MEM,
+ .sec_stateless_reset_rate = QUIC_DFLT_SEC_STATELESS_RESET_RATE,
};
static int bind_parse_quic_force_retry(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
else if (strcmp(suffix, "fe.sec.retry-threshold") == 0) {
quic_tune.fe.sec_retry_threshold = arg;
}
+ else if (strcmp(suffix, "sec.stateless-reset-rate") == 0) {
+ quic_tune.sec_stateless_reset_rate = arg;
+ }
else if (strcmp(suffix, "be.stream.data-ratio") == 0 ||
strcmp(suffix, "fe.stream.data-ratio") == 0) {
uint *ptr = (suffix[0] == 'b') ? &quic_tune.be.stream_data_ratio :
static struct cfg_kw_list cfg_kws = {ILH, {
{ CFG_GLOBAL, "tune.quic.listen", cfg_parse_quic_tune_on_off },
{ CFG_GLOBAL, "tune.quic.mem.tx-max", cfg_parse_quic_tune_setting },
+ { CFG_GLOBAL, "tune.quic.sec.stateless-reset-rate", cfg_parse_quic_tune_setting },
{ CFG_GLOBAL, "tune.quic.zero-copy-fwd-send", cfg_parse_quic_tune_on_off },
{ CFG_GLOBAL, "tune.quic.fe.cc.cubic-min-losses", cfg_parse_quic_tune_setting },
#include <errno.h>
+#include <haproxy/freq_ctr.h>
+#include <haproxy/global.h>
+#include <haproxy/init.h>
#include <haproxy/pool.h>
#include <haproxy/trace.h>
#include <haproxy/quic_cc_drs.h>
return !ret;
}
+/* Stateless Reset rate limiting: each thread gets its own share of
+ * quic_tune.sec_stateless_reset_rate (the configured total, all
+ * threads), and its own freq_ctr to track how many it already sent.
+ */
+static THREAD_LOCAL struct freq_ctr quic_stateless_reset_freq_ctr;
+static THREAD_LOCAL uint quic_stateless_reset_rate;
+
/* Send a stateless reset packet depending on <pkt> RX packet information
* from <fd> UDP socket to <dst>
* Return 1 if succeeded, 0 if not.
TRACE_ENTER(QUIC_EV_STATELESS_RST);
+ if (quic_stateless_reset_rate &&
+ !freq_ctr_remain(&quic_stateless_reset_freq_ctr, quic_stateless_reset_rate, 0)) {
+ TRACE_DEVEL("stateless reset rate limited", QUIC_EV_STATELESS_RST);
+ goto leave;
+ }
+
/* RFC 9000 10.3. Stateless Reset
*
* Endpoints MUST discard packets that are too small to be valid QUIC
ret = 1;
HA_ATOMIC_INC(&prx_counters->stateless_reset_sent);
+ if (quic_stateless_reset_rate)
+ update_freq_ctr(&quic_stateless_reset_freq_ctr, 1);
TRACE_PROTO("stateless reset sent", QUIC_EV_STATELESS_RST, NULL, &rxpkt->dcid);
leave:
TRACE_LEAVE(QUIC_EV_STATELESS_RST);
}
REGISTER_UNITTEST("quic_tx", quic_tx_unittest);
+static int quic_stateless_reset_rate_init(void)
+{
+ /* split N across threads so the totals add up to exactly N: this
+ * thread gets (N + tid) / nbthread. No flat ceil() here, since that
+ * could push the total above N.
+ */
+ quic_stateless_reset_rate =
+ (quic_tune.sec_stateless_reset_rate + tid) / global.nbthread;
+ return 1;
+}
+REGISTER_PER_THREAD_INIT(quic_stateless_reset_rate_init);
+
/*
* Local variables:
* c-indent-level: 8