system, it may represent the number of already acknowledged
connections, of non-acknowledged ones, or both.
- In order to protect against SYN flood attacks, one solution is to increase
- the system's SYN backlog size. Depending on the system, sometimes it is just
- tunable via a system parameter, sometimes it is not adjustable at all, and
- sometimes the system relies on hints given by the application at the time of
- the listen() syscall. By default, HAProxy passes the frontend's maxconn value
- to the listen() syscall. On systems which can make use of this value, it can
- sometimes be useful to be able to specify a different value, hence this
- backlog parameter.
+ This option is both used for stream and datagram listeners.
+
+ In order to protect against SYN flood attacks on a stream-based listener, one
+ solution is to increase the system's SYN backlog size. Depending on the
+ system, sometimes it is just tunable via a system parameter, sometimes it is
+ not adjustable at all, and sometimes the system relies on hints given by the
+ application at the time of the listen() syscall. By default, HAProxy passes
+ the frontend's maxconn value to the listen() syscall. On systems which can
+ make use of this value, it can sometimes be useful to be able to specify a
+ different value, hence this backlog parameter.
On Linux 2.4, the parameter is ignored by the system. On Linux 2.6, it is
used as a hint and the system accepts up to the smallest greater power of
two, and never more than some limits (usually 32768).
+ When using a QUIC listener, this option has a similar albeit not quite
+ equivalent meaning. It will set the maximum number of connections waiting for
+ handshake completion. When this limit is reached, INITIAL packets are dropped
+ to prevent creation of a new QUIC connection.
+
See also : "maxconn" and the target operating system's tuning guide.
void quic_accept_push_qc(struct quic_conn *qc);
+int quic_listener_max_handshake(const struct listener *l);
+
#endif /* USE_QUIC */
#endif /* _HAPROXY_QUIC_SOCK_H */
#ifdef USE_QUIC
struct mt_list rxbuf_list; /* list of buffers to receive and dispatch QUIC datagrams. */
enum quic_sock_mode quic_mode; /* QUIC socket allocation strategy */
+ unsigned int quic_curr_handshake; /* count of active QUIC handshakes */
#endif
struct {
struct task *task; /* Task used to open connection for reverse. */
if (listener->bind_conf->xprt == xprt_get(XPRT_QUIC)) {
/* quic_conn are counted against maxconn. */
listener->bind_conf->options |= BC_O_XPRT_MAXCONN;
+ listener->rx.quic_curr_handshake = 0;
# ifdef USE_QUIC_OPENSSL_COMPAT
/* store the last checked bind_conf in bind_conf */
return task;
}
+/* Try to increment <l> handshake current counter. If listener limit is
+ * reached, incrementation is rejected and 0 is returned.
+ */
+static int quic_increment_curr_handshake(struct listener *l)
+{
+ unsigned int count, next;
+ const int max = quic_listener_max_handshake(l);
+
+ do {
+ count = l->rx.quic_curr_handshake;
+ if (count >= max) {
+ /* maxconn reached */
+ next = 0;
+ goto end;
+ }
+
+ /* try to increment quic_curr_handshake */
+ next = count + 1;
+ } while (!_HA_ATOMIC_CAS(&l->rx.quic_curr_handshake, &count, next) && __ha_cpu_relax());
+
+ end:
+ return next;
+}
+
/* Allocate a new QUIC connection with <version> as QUIC version. <ipv4>
* boolean is set to 1 for IPv4 connection, 0 for IPv6. <server> is set to 1
* for QUIC servers (or haproxy listeners).
struct quic_conn *qc = NULL;
struct listener *l = NULL;
struct quic_cc_algo *cc_algo = NULL;
- unsigned int next_actconn = 0, next_sslconn = 0;
+ unsigned int next_actconn = 0, next_sslconn = 0, next_handshake = 0;
TRACE_ENTER(QUIC_EV_CONN_INIT);
goto err;
}
+ if (server) {
+ next_handshake = quic_increment_curr_handshake(owner);
+ if (!next_handshake) {
+ TRACE_STATE("max handshake reached", QUIC_EV_CONN_INIT);
+ goto err;
+ }
+ }
+
qc = pool_alloc(pool_head_quic_conn);
if (!qc) {
TRACE_ERROR("Could not allocate a new connection", QUIC_EV_CONN_INIT);
/* Now that quic_conn instance is allocated, quic_conn_release() will
* ensure global accounting is decremented.
*/
- next_sslconn = next_actconn = 0;
+ next_handshake = next_sslconn = next_actconn = 0;
/* Initialize in priority qc members required for a safe dealloc. */
qc->nictx = NULL;
/* Required to safely call quic_conn_prx_cntrs_update() from quic_conn_release(). */
qc->prx_counters = NULL;
- /* Now proceeds to allocation of qc members. */
- qc->rx.buf.area = pool_alloc(pool_head_quic_conn_rxbuf);
- if (!qc->rx.buf.area) {
- TRACE_ERROR("Could not allocate a new RX buffer", QUIC_EV_CONN_INIT, qc);
- goto err;
- }
-
- qc->cids = pool_alloc(pool_head_quic_cids);
- if (!qc->cids) {
- TRACE_ERROR("Could not allocate a new CID tree", QUIC_EV_CONN_INIT, qc);
- goto err;
- }
-
- *qc->cids = EB_ROOT;
/* QUIC Server (or listener). */
if (server) {
struct proxy *prx;
qc->mux_state = QC_MUX_NULL;
qc->err = quic_err_transport(QC_ERR_NO_ERROR);
+ /* Now proceeds to allocation of qc members. */
+ qc->rx.buf.area = pool_alloc(pool_head_quic_conn_rxbuf);
+ if (!qc->rx.buf.area) {
+ TRACE_ERROR("Could not allocate a new RX buffer", QUIC_EV_CONN_INIT, qc);
+ goto err;
+ }
+
+ qc->cids = pool_alloc(pool_head_quic_cids);
+ if (!qc->cids) {
+ TRACE_ERROR("Could not allocate a new CID tree", QUIC_EV_CONN_INIT, qc);
+ goto err;
+ }
+ *qc->cids = EB_ROOT;
+
conn_id->qc = qc;
if (HA_ATOMIC_LOAD(&l->rx.quic_mode) == QUIC_SOCK_MODE_CONN &&
_HA_ATOMIC_DEC(&actconn);
if (next_sslconn)
_HA_ATOMIC_DEC(&global.sslconns);
+ if (next_handshake)
+ _HA_ATOMIC_DEC(&l->rx.quic_curr_handshake);
TRACE_LEAVE(QUIC_EV_CONN_INIT);
return NULL;
HA_ATOMIC_DEC(&qc->prx_counters->half_open_conn);
}
+ /* Connection released before handshake completion. */
+ if (unlikely(qc->state < QUIC_HS_ST_COMPLETE)) {
+ if (qc_is_listener(qc)) {
+ BUG_ON(qc->li->rx.quic_curr_handshake == 0);
+ HA_ATOMIC_DEC(&qc->li->rx.quic_curr_handshake);
+ }
+ }
+
pool_free(pool_head_quic_conn, qc);
qc = NULL;
struct quic_connection_id *conn_id;
int ipv4;
+ /* Reject INITIAL early if listener limits reached. */
+ if (unlikely(HA_ATOMIC_LOAD(&l->rx.quic_curr_handshake) >=
+ quic_listener_max_handshake(l))) {
+ TRACE_DATA("Drop INITIAL on max handshake",
+ QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
+ goto out;
+ }
+
if (!pkt->token_len && !(l->bind_conf->options & BC_O_QUIC_FORCE_RETRY) &&
HA_ATOMIC_LOAD(&prx_counters->half_open_conn) >= global.tune.quic_retry_threshold) {
TRACE_PROTO("Initial without token, sending retry",
return NULL;
}
+/* Returns the maximum number of QUIC connections waiting for handshake to
+ * complete in parallel on listener <l> instance. This reuses the listener
+ * backlog value.
+ */
+int quic_listener_max_handshake(const struct listener *l)
+{
+ return listener_backlog(l);
+}
+
static int quic_alloc_accept_queues(void)
{
int i;
qc->state = QUIC_HS_ST_CONFIRMED;
/* The connection is ready to be accepted. */
quic_accept_push_qc(qc);
+
+ BUG_ON(qc->li->rx.quic_curr_handshake == 0);
+ HA_ATOMIC_DEC(&qc->li->rx.quic_curr_handshake);
}
else {
qc->state = QUIC_HS_ST_COMPLETE;