system, it may represent the number of already acknowledged
connections, of non-acknowledged ones, or both.
- This option is both used for stream and datagram listeners.
+ This option is only meaningful for stream listeners, including QUIC ones. Its
+ behavior however is not identical with QUIC instances.
- 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
+ For all listeners but QUIC, 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
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.
+ For QUIC listeners, backlog sets a shared limits for both the maximum count
+ of active handshakes and connections waiting to be accepted. The handshake
+ phase relies primarily of the network latency with the remote peer, whereas
+ the second phase depends solely on haproxy load. When either one of this
+ limit is reached, haproxy starts to drop reception of INITIAL packets,
+ preventing any new connection allocation, until the connection excess starts
+ to decrease. This situation may cause browsers to silently downgrade the HTTP
+ versions and switching to TCP.
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);
+int quic_listener_max_accept(const struct listener *l);
#endif /* USE_QUIC */
#endif /* _HAPROXY_QUIC_SOCK_H */
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 */
+ unsigned int quic_curr_accept; /* count of QUIC conns waiting for accept */
#endif
struct {
struct task *task; /* Task used to open connection for reverse. */
/* quic_conn are counted against maxconn. */
listener->bind_conf->options |= BC_O_XPRT_MAXCONN;
listener->rx.quic_curr_handshake = 0;
+ listener->rx.quic_curr_accept = 0;
# ifdef USE_QUIC_OPENSSL_COMPAT
/* store the last checked bind_conf in bind_conf */
/* in the unlikely (but possible) case the connection was just added to
* the accept_list we must delete it from there.
*/
- MT_LIST_DELETE(&qc->accept_list);
+ if (MT_LIST_INLIST(&qc->accept_list)) {
+ MT_LIST_DELETE(&qc->accept_list);
+ BUG_ON(qc->li->rx.quic_curr_accept == 0);
+ HA_ATOMIC_DEC(&qc->li->rx.quic_curr_accept);
+ }
/* free remaining stream descriptors */
node = eb64_first(&qc->streams_by_id);
goto out;
}
+ if (unlikely(HA_ATOMIC_LOAD(&l->rx.quic_curr_accept) >=
+ quic_listener_max_accept(l))) {
+ TRACE_DATA("Drop INITIAL on max accept",
+ 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",
done:
*status = CO_AC_DONE;
- return qc ? qc->conn : NULL;
+
+ if (qc) {
+ BUG_ON(l->rx.quic_curr_accept <= 0);
+ HA_ATOMIC_DEC(&l->rx.quic_curr_accept);
+ return qc->conn;
+ }
+ else {
+ return NULL;
+ }
err:
/* in case of error reinsert the element to process it later. */
return;
BUG_ON(MT_LIST_INLIST(&qc->accept_list));
+ HA_ATOMIC_INC(&qc->li->rx.quic_curr_accept);
qc->flags |= QUIC_FL_CONN_ACCEPT_REGISTERED;
/* 1. insert the listener in the accept queue
}
/* Returns the maximum number of QUIC connections waiting for handshake to
- * complete in parallel on listener <l> instance. This reuses the listener
- * backlog value.
+ * complete in parallel on listener <l> instance. This is directly based on
+ * listener backlog value.
*/
int quic_listener_max_handshake(const struct listener *l)
{
- return listener_backlog(l);
+ return listener_backlog(l) / 2;
+}
+
+/* Returns the value which is considered as the maximum number of QUIC
+ * connections waiting to be accepted for listener <l> instance. This is
+ * directly based on listener backlog value.
+ */
+int quic_listener_max_accept(const struct listener *l)
+{
+ return listener_backlog(l) / 2;
}
static int quic_alloc_accept_queues(void)