diff --git a/include/haproxy/quic_conn-t.h b/include/haproxy/quic_conn-t.h index a0281c94d..a8472add5 100644 --- a/include/haproxy/quic_conn-t.h +++ b/include/haproxy/quic_conn-t.h @@ -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 */ diff --git a/include/haproxy/quic_conn.h b/include/haproxy/quic_conn.h index c9ee29209..5353a5969 100644 --- a/include/haproxy/quic_conn.h +++ b/include/haproxy/quic_conn.h @@ -225,6 +225,23 @@ static inline void free_quic_conn_cids(struct quic_conn *conn) } } +/* Move all the connection IDs from QUIC connection to */ +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 new connection ID information to NEW_CONNECTION_ID frame. * Always succeeds. */ diff --git a/src/quic_conn.c b/src/quic_conn.c index 97cb1eb55..3ed409f7a 100644 --- a/src/quic_conn.c +++ b/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 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;