#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) */
#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 */
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;
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 */
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 */
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));
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)
{
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;
{
struct eb64_node *node;
struct quic_rx_packet *pkt, *pktback;
+ struct quic_cc_conn *cc_qc;
TRACE_ENTER(QUIC_EV_CONN_CLOSE, 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.
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);
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;