mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-07 23:56:57 +02:00
MEDIUM: quic: define an accept queue limit
QUIC connections are pushed manually into a dedicated listener queue when they are ready to be accepted. This happens after handshake finalization or on 0-RTT packet reception. Listener is then woken up to dequeue them with listener_accept(). This patch comptabilizes the number of connections currently stored in the accept queue. If reaching a certain limit, INITIAL packets are dropped on reception to prevent further QUIC connections allocation. This should help to preserve system resources. This limit is automatically derived from the listener backlog. Half of its value is reserved for handshakes and the other half for accept queues. By default, backlog is equal to maxconn which guarantee that there can't be no more than maxconn connections in handshake or waiting to be accepted.
This commit is contained in:
parent
3df6a60113
commit
bb28215d9b
@ -4659,10 +4659,11 @@ backlog <conns>
|
|||||||
system, it may represent the number of already acknowledged
|
system, it may represent the number of already acknowledged
|
||||||
connections, of non-acknowledged ones, or both.
|
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
|
For all listeners but QUIC, in order to protect against SYN flood attacks,
|
||||||
solution is to increase the system's SYN backlog size. Depending on the
|
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
|
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
|
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
|
application at the time of the listen() syscall. By default, HAProxy passes
|
||||||
@ -4674,10 +4675,14 @@ backlog <conns>
|
|||||||
used as a hint and the system accepts up to the smallest greater power of
|
used as a hint and the system accepts up to the smallest greater power of
|
||||||
two, and never more than some limits (usually 32768).
|
two, and never more than some limits (usually 32768).
|
||||||
|
|
||||||
When using a QUIC listener, this option has a similar albeit not quite
|
For QUIC listeners, backlog sets a shared limits for both the maximum count
|
||||||
equivalent meaning. It will set the maximum number of connections waiting for
|
of active handshakes and connections waiting to be accepted. The handshake
|
||||||
handshake completion. When this limit is reached, INITIAL packets are dropped
|
phase relies primarily of the network latency with the remote peer, whereas
|
||||||
to prevent creation of a new QUIC connection.
|
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.
|
See also : "maxconn" and the target operating system's tuning guide.
|
||||||
|
|
||||||
|
@ -70,6 +70,7 @@ void qc_want_recv(struct quic_conn *qc);
|
|||||||
void quic_accept_push_qc(struct quic_conn *qc);
|
void quic_accept_push_qc(struct quic_conn *qc);
|
||||||
|
|
||||||
int quic_listener_max_handshake(const struct listener *l);
|
int quic_listener_max_handshake(const struct listener *l);
|
||||||
|
int quic_listener_max_accept(const struct listener *l);
|
||||||
|
|
||||||
#endif /* USE_QUIC */
|
#endif /* USE_QUIC */
|
||||||
#endif /* _HAPROXY_QUIC_SOCK_H */
|
#endif /* _HAPROXY_QUIC_SOCK_H */
|
||||||
|
@ -82,6 +82,7 @@ struct receiver {
|
|||||||
struct mt_list rxbuf_list; /* list of buffers to receive and dispatch QUIC datagrams. */
|
struct mt_list rxbuf_list; /* list of buffers to receive and dispatch QUIC datagrams. */
|
||||||
enum quic_sock_mode quic_mode; /* QUIC socket allocation strategy */
|
enum quic_sock_mode quic_mode; /* QUIC socket allocation strategy */
|
||||||
unsigned int quic_curr_handshake; /* count of active QUIC handshakes */
|
unsigned int quic_curr_handshake; /* count of active QUIC handshakes */
|
||||||
|
unsigned int quic_curr_accept; /* count of QUIC conns waiting for accept */
|
||||||
#endif
|
#endif
|
||||||
struct {
|
struct {
|
||||||
struct task *task; /* Task used to open connection for reverse. */
|
struct task *task; /* Task used to open connection for reverse. */
|
||||||
|
@ -4188,6 +4188,7 @@ int check_config_validity()
|
|||||||
/* quic_conn are counted against maxconn. */
|
/* quic_conn are counted against maxconn. */
|
||||||
listener->bind_conf->options |= BC_O_XPRT_MAXCONN;
|
listener->bind_conf->options |= BC_O_XPRT_MAXCONN;
|
||||||
listener->rx.quic_curr_handshake = 0;
|
listener->rx.quic_curr_handshake = 0;
|
||||||
|
listener->rx.quic_curr_accept = 0;
|
||||||
|
|
||||||
# ifdef USE_QUIC_OPENSSL_COMPAT
|
# ifdef USE_QUIC_OPENSSL_COMPAT
|
||||||
/* store the last checked bind_conf in bind_conf */
|
/* store the last checked bind_conf in bind_conf */
|
||||||
|
@ -1511,7 +1511,11 @@ void quic_conn_release(struct quic_conn *qc)
|
|||||||
/* in the unlikely (but possible) case the connection was just added to
|
/* in the unlikely (but possible) case the connection was just added to
|
||||||
* the accept_list we must delete it from there.
|
* the accept_list we must delete it from there.
|
||||||
*/
|
*/
|
||||||
|
if (MT_LIST_INLIST(&qc->accept_list)) {
|
||||||
MT_LIST_DELETE(&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 */
|
/* free remaining stream descriptors */
|
||||||
node = eb64_first(&qc->streams_by_id);
|
node = eb64_first(&qc->streams_by_id);
|
||||||
|
@ -1966,6 +1966,13 @@ static struct quic_conn *quic_rx_pkt_retrieve_conn(struct quic_rx_packet *pkt,
|
|||||||
goto out;
|
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) &&
|
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) {
|
HA_ATOMIC_LOAD(&prx_counters->half_open_conn) >= global.tune.quic_retry_threshold) {
|
||||||
TRACE_PROTO("Initial without token, sending retry",
|
TRACE_PROTO("Initial without token, sending retry",
|
||||||
|
@ -158,7 +158,15 @@ struct connection *quic_sock_accept_conn(struct listener *l, int *status)
|
|||||||
|
|
||||||
done:
|
done:
|
||||||
*status = CO_AC_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:
|
err:
|
||||||
/* in case of error reinsert the element to process it later. */
|
/* in case of error reinsert the element to process it later. */
|
||||||
@ -928,6 +936,7 @@ void quic_accept_push_qc(struct quic_conn *qc)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
BUG_ON(MT_LIST_INLIST(&qc->accept_list));
|
BUG_ON(MT_LIST_INLIST(&qc->accept_list));
|
||||||
|
HA_ATOMIC_INC(&qc->li->rx.quic_curr_accept);
|
||||||
|
|
||||||
qc->flags |= QUIC_FL_CONN_ACCEPT_REGISTERED;
|
qc->flags |= QUIC_FL_CONN_ACCEPT_REGISTERED;
|
||||||
/* 1. insert the listener in the accept queue
|
/* 1. insert the listener in the accept queue
|
||||||
@ -967,12 +976,21 @@ struct task *quic_accept_run(struct task *t, void *ctx, unsigned int i)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Returns the maximum number of QUIC connections waiting for handshake to
|
/* Returns the maximum number of QUIC connections waiting for handshake to
|
||||||
* complete in parallel on listener <l> instance. This reuses the listener
|
* complete in parallel on listener <l> instance. This is directly based on
|
||||||
* backlog value.
|
* listener backlog value.
|
||||||
*/
|
*/
|
||||||
int quic_listener_max_handshake(const struct listener *l)
|
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)
|
static int quic_alloc_accept_queues(void)
|
||||||
|
Loading…
Reference in New Issue
Block a user