mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-06 07:07:04 +02:00
MEDIUM: quic: Allow the quic_conn memory to be asap released.
When the connection enters the "connection closing" state after having sent a datagram with CONNECTION_CLOSE frames inside its packets, a lot of memory may be freed from quic_conn objects (QUIC connection). This is done allocating a reduced sized object which keeps enough information to handle the remaining incoming packets for the connection in "connection closing" state, and to continue to send again the previous datagram with CONNECTION_CLOSE frames inside which has already been sent. Define a new quic_cc_conn struct which represents the connection object after entering the "connection close" state and after having release the quic_conn connection object. Define <pool_head_quic_cc_conn> new pool for these quic_cc_conn struct objects. Define QUIC_CONN_COMMON structure which is shared between quic_conn struct object (the connection before entering "connection close" state), and new quic_cc_conn struct object (the connection after entering "connection close"). So, all the members inside QUIC_CONN_COMMON may be indifferently dereferenced from a quic_conn struct or a quic_cc_conn struct pointer. Implement qc_new_cc_conn() function to allocate such connections in "connection close" state. This function is responsible of copying the required information from the original connection (quic_conn) to the remaining connection (quic_cc_conn). Among others initialization, it redefined the QUIC packet handler task to quic_cc_conn_io_cb() and the idle timer task to qc_cc_idle_timer_task(). quic_cc_conn_io_cb() drains the received and resend the datagram which CONNECTION_CLOSE frame which has already been sent when entering "connection close" state. qc_cc_idle_timer_task() only releases the remaining quic_cc_conn struct object. Modify quic_conn_release() to allocate quic_cc_conn struct objects from the original connection passed as argument. It does nothing if this original connection is not in closing state, or if the idle timer has already expired. Implement quic_release_cc_conn() to release a "connection close" connection. It is called when its timer expires or if an error occured when sending a packet from this connection when the peer is no more reachable.
This commit is contained in:
parent
276697438d
commit
9f7cfb0a56
@ -449,6 +449,7 @@ struct quic_conn_cntrs {
|
||||
#define QUIC_FL_CONN_IO_TO_REQUEUE (1U << 14) /* IO handler must be requeued on new thread after connection migration */
|
||||
#define QUIC_FL_CONN_IPKTNS_DCD (1U << 15) /* Initial packet number space discarded */
|
||||
#define QUIC_FL_CONN_HPKTNS_DCD (1U << 16) /* Handshake packet number space discarded */
|
||||
#define QUIC_FL_CONN_PEER_VALIDATED_ADDR (1U << 17) /* Connection with peer validated address */
|
||||
#define QUIC_FL_CONN_TO_KILL (1U << 24) /* Unusable connection, to be killed */
|
||||
#define QUIC_FL_CONN_TX_TP_RECEIVED (1U << 25) /* Peer transport parameters have been received (used for the transmitting part) */
|
||||
#define QUIC_FL_CONN_FINALIZED (1U << 26) /* QUIC connection finalized (functional, ready to send/receive) */
|
||||
@ -458,27 +459,59 @@ struct quic_conn_cntrs {
|
||||
#define QUIC_FL_CONN_DRAINING (1U << 30) /* draining state, entered on CONNECTION_CLOSE reception */
|
||||
#define QUIC_FL_CONN_IMMEDIATE_CLOSE (1U << 31) /* A CONNECTION_CLOSE must be sent */
|
||||
|
||||
#define QUIC_CONN_COMMON \
|
||||
struct { \
|
||||
/* Connection owned socket FD. */ \
|
||||
int fd; \
|
||||
unsigned int flags; \
|
||||
struct quic_err err; \
|
||||
/* When in closing state, number of packet before sending CC */ \
|
||||
unsigned int nb_pkt_for_cc; \
|
||||
/* When in closing state, number of packet since receiving CC */ \
|
||||
unsigned int nb_pkt_since_cc; \
|
||||
struct wait_event wait_event; \
|
||||
struct wait_event *subs; \
|
||||
struct sockaddr_storage local_addr; \
|
||||
struct sockaddr_storage peer_addr; \
|
||||
struct { \
|
||||
/* Number of bytes for prepared packets */ \
|
||||
uint64_t prep; \
|
||||
/* Number of sent bytes. */ \
|
||||
uint64_t tx; \
|
||||
/* Number of received bytes. */ \
|
||||
uint64_t rx; \
|
||||
} bytes; \
|
||||
/* First DCID used by client on its Initial packet. */ \
|
||||
struct quic_cid odcid; \
|
||||
/* DCID of our endpoint - not updated when a new DCID is used */ \
|
||||
struct quic_cid dcid; \
|
||||
/* first SCID of our endpoint - not updated when a new SCID is used */ \
|
||||
struct quic_cid scid; \
|
||||
/* tree of quic_connection_id - used to match a received packet DCID \
|
||||
* with a connection \
|
||||
*/ \
|
||||
struct eb_root *cids; \
|
||||
struct listener *li; /* only valid for frontend connections */ \
|
||||
/* Idle timer task */ \
|
||||
struct task *idle_timer_task; \
|
||||
unsigned int idle_expire; \
|
||||
}
|
||||
|
||||
struct quic_conn {
|
||||
QUIC_CONN_COMMON;
|
||||
const struct quic_version *original_version;
|
||||
const struct quic_version *negotiated_version;
|
||||
/* Negotiated version Initial TLS context */
|
||||
struct quic_tls_ctx *nictx;
|
||||
/* Connection owned socket FD. */
|
||||
int fd;
|
||||
/* QUIC transport parameters TLS extension */
|
||||
int tps_tls_ext;
|
||||
int state;
|
||||
enum qc_mux_state mux_state; /* status of the connection/mux layer */
|
||||
struct quic_err err;
|
||||
#ifdef USE_QUIC_OPENSSL_COMPAT
|
||||
unsigned char enc_params[QUIC_TP_MAX_ENCLEN]; /* encoded QUIC transport parameters */
|
||||
size_t enc_params_len;
|
||||
#endif
|
||||
|
||||
struct quic_cid odcid; /* First DCID used by client on its Initial packet. */
|
||||
struct quic_cid dcid; /* DCID of our endpoint - not updated when a new DCID is used */
|
||||
struct quic_cid scid; /* first SCID of our endpoint - not updated when a new SCID is used */
|
||||
struct eb_root *cids; /* tree of quic_connection_id - used to match a received packet DCID with a connection */
|
||||
uint64_t next_cid_seq_num;
|
||||
|
||||
/* Initial encryption level */
|
||||
@ -503,20 +536,9 @@ struct quic_conn {
|
||||
struct quic_openssl_compat openssl_compat;
|
||||
#endif
|
||||
|
||||
struct sockaddr_storage local_addr;
|
||||
struct sockaddr_storage peer_addr;
|
||||
|
||||
/* Used only to reach the tasklet for the I/O handler from this quic_conn object. */
|
||||
struct connection *conn;
|
||||
|
||||
struct {
|
||||
/* Number of bytes for prepared packets */
|
||||
uint64_t prep;
|
||||
/* Number of sent bytes. */
|
||||
uint64_t tx;
|
||||
/* Number of received bytes. */
|
||||
uint64_t rx;
|
||||
} bytes;
|
||||
struct {
|
||||
/* Transport parameters sent by the peer */
|
||||
struct quic_transport_params params;
|
||||
@ -549,29 +571,16 @@ struct quic_conn {
|
||||
struct quic_path paths[1];
|
||||
struct quic_path *path;
|
||||
|
||||
struct listener *li; /* only valid for frontend connections */
|
||||
struct mt_list accept_list; /* chaining element used for accept, only valid for frontend connections */
|
||||
|
||||
struct eb_root streams_by_id; /* qc_stream_desc tree */
|
||||
int stream_buf_count; /* total count of allocated stream buffers for this connection */
|
||||
|
||||
struct wait_event wait_event;
|
||||
struct wait_event *subs;
|
||||
|
||||
/* MUX */
|
||||
struct qcc *qcc;
|
||||
struct task *timer_task;
|
||||
unsigned int timer;
|
||||
/* Idle timer task */
|
||||
struct task *idle_timer_task;
|
||||
unsigned int idle_expire;
|
||||
unsigned int ack_expire;
|
||||
unsigned int flags;
|
||||
|
||||
/* When in closing state, number of packet before sending CC */
|
||||
unsigned int nb_pkt_for_cc;
|
||||
/* When in closing state, number of packet since receiving CC */
|
||||
unsigned int nb_pkt_since_cc;
|
||||
|
||||
const struct qcc_app_ops *app_ops;
|
||||
/* QUIC connection level counters */
|
||||
@ -584,5 +593,13 @@ struct quic_conn {
|
||||
unsigned int qc_epoch; /* delimiter for newer instances started after "show quic". */
|
||||
};
|
||||
|
||||
/* QUIC connection in "connection close" state. */
|
||||
struct quic_cc_conn {
|
||||
QUIC_CONN_COMMON;
|
||||
char *cc_buf_area;
|
||||
/* Length of the "connection close" datagram. */
|
||||
size_t cc_dgram_len;
|
||||
};
|
||||
|
||||
#endif /* USE_QUIC */
|
||||
#endif /* _HAPROXY_QUIC_CONN_T_H */
|
||||
|
@ -225,6 +225,23 @@ static inline void free_quic_conn_cids(struct quic_conn *conn)
|
||||
}
|
||||
}
|
||||
|
||||
/* Move all the connection IDs from <conn> QUIC connection to <cc_conn> */
|
||||
static inline void quic_conn_mv_cids_to_cc_conn(struct quic_cc_conn *cc_conn,
|
||||
struct quic_conn *conn)
|
||||
{
|
||||
struct eb64_node *node;
|
||||
|
||||
node = eb64_first(conn->cids);
|
||||
while (node) {
|
||||
struct quic_connection_id *conn_id;
|
||||
|
||||
conn_id = eb64_entry(node, struct quic_connection_id, seq_num);
|
||||
conn_id->qc = (struct quic_conn *)cc_conn;
|
||||
node = eb64_next(node);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Copy <src> new connection ID information to <dst> NEW_CONNECTION_ID frame.
|
||||
* Always succeeds.
|
||||
*/
|
||||
|
165
src/quic_conn.c
165
src/quic_conn.c
@ -129,6 +129,7 @@ const struct quic_version quic_version_VN_reserved = { .num = 0, };
|
||||
static BIO_METHOD *ha_quic_meth;
|
||||
|
||||
DECLARE_STATIC_POOL(pool_head_quic_conn, "quic_conn", sizeof(struct quic_conn));
|
||||
DECLARE_STATIC_POOL(pool_head_quic_cc_conn, "quic_cc_conn", sizeof(struct quic_cc_conn));
|
||||
DECLARE_STATIC_POOL(pool_head_quic_cids, "quic_cids", sizeof(struct eb_root));
|
||||
DECLARE_POOL(pool_head_quic_connection_id,
|
||||
"quic_connection_id", sizeof(struct quic_connection_id));
|
||||
@ -746,6 +747,130 @@ struct task *quic_conn_app_io_cb(struct task *t, void *context, unsigned int sta
|
||||
return t;
|
||||
}
|
||||
|
||||
static void quic_release_cc_conn(struct quic_cc_conn *cc_qc)
|
||||
{
|
||||
struct quic_conn *qc = (struct quic_conn *)cc_qc;
|
||||
|
||||
if (qc_test_fd(qc))
|
||||
_HA_ATOMIC_DEC(&jobs);
|
||||
|
||||
/* Close quic-conn socket fd. */
|
||||
qc_release_fd(qc, 0);
|
||||
|
||||
task_destroy(cc_qc->idle_timer_task);
|
||||
cc_qc->idle_timer_task = NULL;
|
||||
free_quic_conn_cids(qc);
|
||||
pool_free(pool_head_quic_cids, cc_qc->cids);
|
||||
cc_qc->cids = NULL;
|
||||
pool_free(pool_head_quic_cc_buf, cc_qc->cc_buf_area);
|
||||
cc_qc->cc_buf_area = NULL;
|
||||
pool_free(pool_head_quic_cc_conn, cc_qc);
|
||||
}
|
||||
|
||||
/* QUIC connection packet handler task used when in "closing connection" state. */
|
||||
static struct task *quic_cc_conn_io_cb(struct task *t, void *context, unsigned int state)
|
||||
{
|
||||
struct quic_cc_conn *cc_qc = context;
|
||||
struct quic_conn *qc = (struct quic_conn *)cc_qc;
|
||||
struct buffer buf;
|
||||
uint16_t dglen;
|
||||
struct quic_tx_packet *first_pkt;
|
||||
size_t headlen = sizeof dglen + sizeof first_pkt;
|
||||
|
||||
TRACE_ENTER(QUIC_EV_CONN_IO_CB, qc);
|
||||
|
||||
if (qc_test_fd(qc))
|
||||
qc_rcv_buf(qc);
|
||||
|
||||
/* Do not send too much data if the peer address was not validated. */
|
||||
if ((qc->flags & QUIC_FL_CONN_IMMEDIATE_CLOSE) &&
|
||||
!(qc->flags & QUIC_FL_CONN_PEER_VALIDATED_ADDR) &&
|
||||
quic_may_send_bytes(qc) < cc_qc->cc_dgram_len)
|
||||
goto leave;
|
||||
|
||||
buf = b_make(cc_qc->cc_buf_area + headlen,
|
||||
QUIC_MAX_CC_BUFSIZE - headlen, 0, cc_qc->cc_dgram_len);
|
||||
if (qc_snd_buf(qc, &buf, buf.data, 0) < 0) {
|
||||
TRACE_ERROR("sendto fatal error", QUIC_EV_CONN_IO_CB, qc);
|
||||
quic_release_cc_conn(cc_qc);
|
||||
goto leave;
|
||||
}
|
||||
|
||||
qc->flags &= ~QUIC_FL_CONN_IMMEDIATE_CLOSE;
|
||||
|
||||
leave:
|
||||
TRACE_LEAVE(QUIC_EV_CONN_IO_CB, qc);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
/* The task handling the idle timeout of a connection in "connection close" state */
|
||||
static struct task *qc_cc_idle_timer_task(struct task *t, void *ctx, unsigned int state)
|
||||
{
|
||||
struct quic_cc_conn *cc_qc = ctx;
|
||||
|
||||
quic_release_cc_conn(cc_qc);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Allocate a new connection in "connection close" state and return it
|
||||
* if succeeded, NULL if not. This function is also responsible of
|
||||
* copying enough and the least possible information from <qc> original
|
||||
* connection to the newly allocated connection so that to keep it
|
||||
* functionnal until its idle timer expires.
|
||||
*/
|
||||
static struct quic_cc_conn *qc_new_cc_conn(struct quic_conn *qc)
|
||||
{
|
||||
struct quic_cc_conn *cc_qc;
|
||||
|
||||
cc_qc = pool_alloc(pool_head_quic_cc_conn);
|
||||
if (!cc_qc)
|
||||
return NULL;
|
||||
|
||||
quic_conn_mv_cids_to_cc_conn(cc_qc, qc);
|
||||
|
||||
cc_qc->fd = qc->fd;
|
||||
fdtab[cc_qc->fd].owner = cc_qc;
|
||||
cc_qc->flags = qc->flags;
|
||||
if (quic_peer_validated_addr(qc))
|
||||
cc_qc->flags |= QUIC_FL_CONN_PEER_VALIDATED_ADDR;
|
||||
cc_qc->err = qc->err;
|
||||
|
||||
cc_qc->nb_pkt_for_cc = qc->nb_pkt_for_cc;
|
||||
cc_qc->nb_pkt_since_cc = qc->nb_pkt_since_cc;
|
||||
|
||||
cc_qc->local_addr = qc->local_addr;
|
||||
cc_qc->peer_addr = qc->peer_addr;
|
||||
|
||||
cc_qc->wait_event.tasklet = qc->wait_event.tasklet;
|
||||
cc_qc->wait_event.tasklet->process = quic_cc_conn_io_cb;
|
||||
cc_qc->wait_event.tasklet->context = cc_qc;
|
||||
cc_qc->wait_event.events = 0;
|
||||
cc_qc->subs = NULL;
|
||||
|
||||
cc_qc->bytes.prep = qc->bytes.prep;
|
||||
cc_qc->bytes.tx = qc->bytes.tx;
|
||||
cc_qc->bytes.rx = qc->bytes.rx;
|
||||
|
||||
cc_qc->odcid = qc->odcid;
|
||||
cc_qc->dcid = qc->dcid;
|
||||
cc_qc->scid = qc->scid;
|
||||
|
||||
cc_qc->li = qc->li;
|
||||
cc_qc->cids = qc->cids;
|
||||
|
||||
cc_qc->idle_timer_task = qc->idle_timer_task;
|
||||
cc_qc->idle_timer_task->process = qc_cc_idle_timer_task;
|
||||
cc_qc->idle_timer_task->context = cc_qc;
|
||||
cc_qc->idle_expire = qc->idle_expire;
|
||||
|
||||
cc_qc->cc_buf_area = qc->tx.cc_buf_area;
|
||||
cc_qc->cc_dgram_len = qc->tx.cc_dgram_len;
|
||||
|
||||
return cc_qc;
|
||||
}
|
||||
|
||||
/* QUIC connection packet handler task. */
|
||||
struct task *quic_conn_io_cb(struct task *t, void *context, unsigned int state)
|
||||
{
|
||||
@ -840,6 +965,11 @@ struct task *quic_conn_io_cb(struct task *t, void *context, unsigned int state)
|
||||
quic_nictx_free(qc);
|
||||
}
|
||||
|
||||
if (qc->flags & QUIC_FL_CONN_CLOSING) {
|
||||
quic_conn_release(qc);
|
||||
qc = NULL;
|
||||
}
|
||||
|
||||
TRACE_PROTO("ssl error", QUIC_EV_CONN_IO_CB, qc, &st);
|
||||
TRACE_LEAVE(QUIC_EV_CONN_IO_CB, qc);
|
||||
return t;
|
||||
@ -1251,6 +1381,7 @@ void quic_conn_release(struct quic_conn *qc)
|
||||
{
|
||||
struct eb64_node *node;
|
||||
struct quic_rx_packet *pkt, *pktback;
|
||||
struct quic_cc_conn *cc_qc;
|
||||
|
||||
TRACE_ENTER(QUIC_EV_CONN_CLOSE, qc);
|
||||
|
||||
@ -1260,11 +1391,27 @@ void quic_conn_release(struct quic_conn *qc)
|
||||
/* We must not free the quic-conn if the MUX is still allocated. */
|
||||
BUG_ON(qc->mux_state == QC_MUX_READY);
|
||||
|
||||
if (qc_test_fd(qc))
|
||||
_HA_ATOMIC_DEC(&jobs);
|
||||
cc_qc = NULL;
|
||||
if ((qc->flags & QUIC_FL_CONN_CLOSING) && !(qc->flags & QUIC_FL_CONN_EXP_TIMER) &&
|
||||
qc->tx.cc_buf_area)
|
||||
cc_qc = qc_new_cc_conn(qc);
|
||||
|
||||
/* Close quic-conn socket fd. */
|
||||
qc_release_fd(qc, 0);
|
||||
if (!cc_qc) {
|
||||
if (qc_test_fd(qc))
|
||||
_HA_ATOMIC_DEC(&jobs);
|
||||
|
||||
/* Close quic-conn socket fd. */
|
||||
qc_release_fd(qc, 0);
|
||||
task_destroy(qc->idle_timer_task);
|
||||
qc->idle_timer_task = NULL;
|
||||
tasklet_free(qc->wait_event.tasklet);
|
||||
/* remove the connection from receiver cids trees */
|
||||
free_quic_conn_cids(qc);
|
||||
pool_free(pool_head_quic_cids, qc->cids);
|
||||
qc->cids = NULL;
|
||||
pool_free(pool_head_quic_cc_buf, qc->tx.cc_buf_area);
|
||||
qc->tx.cc_buf_area = NULL;
|
||||
}
|
||||
|
||||
/* in the unlikely (but possible) case the connection was just added to
|
||||
* the accept_list we must delete it from there.
|
||||
@ -1292,18 +1439,9 @@ void quic_conn_release(struct quic_conn *qc)
|
||||
pool_free(pool_head_quic_rx_packet, pkt);
|
||||
}
|
||||
|
||||
task_destroy(qc->idle_timer_task);
|
||||
qc->idle_timer_task = NULL;
|
||||
|
||||
task_destroy(qc->timer_task);
|
||||
qc->timer_task = NULL;
|
||||
|
||||
tasklet_free(qc->wait_event.tasklet);
|
||||
|
||||
/* remove the connection from receiver cids trees */
|
||||
free_quic_conn_cids(qc);
|
||||
pool_free(pool_head_quic_cids, qc->cids);
|
||||
|
||||
/* free the SSL sock context */
|
||||
qc_free_ssl_sock_ctx(&qc->xprt_ctx);
|
||||
|
||||
@ -1332,7 +1470,6 @@ void quic_conn_release(struct quic_conn *qc)
|
||||
quic_conn_prx_cntrs_update(qc);
|
||||
pool_free(pool_head_quic_conn_rxbuf, qc->rx.buf.area);
|
||||
qc->rx.buf.area = NULL;
|
||||
pool_free(pool_head_quic_cc_buf, qc->tx.cc_buf_area);
|
||||
pool_free(pool_head_quic_conn, qc);
|
||||
qc = NULL;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user