MEDIUM: quic: move receive out of FD handler to quic-conn io-cb
This change is the second part for reception on QUIC connection socket.
All operations inside the FD handler has been delayed to quic-conn
tasklet via the new function qc_rcv_buf().
With this change, buffer management on reception has been simplified. It
is now possible to use a local buffer inside qc_rcv_buf() instead of
quic_receiver_buf().
This change is part of quic-conn owned socket implementation.
It may be backported to 2.7 after a period of observation.
diff --git a/include/haproxy/quic_conn.h b/include/haproxy/quic_conn.h
index 47e256e..6e4774d 100644
--- a/include/haproxy/quic_conn.h
+++ b/include/haproxy/quic_conn.h
@@ -743,6 +743,7 @@
void quic_set_connection_close(struct quic_conn *qc, const struct quic_err err);
void quic_set_tls_alert(struct quic_conn *qc, int alert);
int quic_set_app_ops(struct quic_conn *qc, const unsigned char *alpn, size_t alpn_len);
+int qc_check_dcid(struct quic_conn *qc, unsigned char *dcid, size_t dcid_len);
int quic_get_dgram_dcid(unsigned char *buf, const unsigned char *end,
unsigned char **dcid, size_t *dcid_len);
int qc_send_mux(struct quic_conn *qc, struct list *frms);
diff --git a/include/haproxy/quic_sock.h b/include/haproxy/quic_sock.h
index 3bb87a2..f7b0eb5 100644
--- a/include/haproxy/quic_sock.h
+++ b/include/haproxy/quic_sock.h
@@ -45,6 +45,7 @@
void quic_lstnr_sock_fd_iocb(int fd);
int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t count,
int flags);
+int qc_rcv_buf(struct quic_conn *qc);
/* Set default value for <qc> socket as uninitialized. */
static inline void qc_init_fd(struct quic_conn *qc)
diff --git a/src/quic_conn.c b/src/quic_conn.c
index 65f4629..f86ad4b 100644
--- a/src/quic_conn.c
+++ b/src/quic_conn.c
@@ -4310,6 +4310,9 @@
TRACE_ENTER(QUIC_EV_CONN_IO_CB, qc);
TRACE_STATE("connection handshake state", QUIC_EV_CONN_IO_CB, qc, &qc->state);
+ if (qc_test_fd(qc))
+ qc_rcv_buf(qc);
+
/* Retranmissions */
if (qc->flags & QUIC_FL_CONN_RETRANS_NEEDED) {
TRACE_STATE("retransmission needed", QUIC_EV_CONN_IO_CB, qc);
@@ -4393,7 +4396,10 @@
zero_rtt = st < QUIC_HS_ST_COMPLETE &&
quic_tls_has_rx_sec(eqel) &&
(!LIST_ISEMPTY(&eqel->rx.pqpkts) || qc_el_rx_pkts(eqel));
- start:
+
+ if (qc_test_fd(qc))
+ qc_rcv_buf(qc);
+
if (st >= QUIC_HS_ST_COMPLETE &&
qc_el_rx_pkts(&qc->els[QUIC_TLS_ENC_LEVEL_HANDSHAKE])) {
TRACE_DEVEL("remaining Handshake packets", QUIC_EV_CONN_PHPKTS, qc);
@@ -7422,6 +7428,37 @@
return -1;
}
+/* Check if connection ID <dcid> of length <dcid_len> belongs to <qc> local
+ * CIDs. This can be used to determine if a datagram is addressed to the right
+ * connection instance.
+ *
+ * Returns a boolean value.
+ */
+int qc_check_dcid(struct quic_conn *qc, unsigned char *dcid, size_t dcid_len)
+{
+ struct ebmb_node *node;
+ struct quic_connection_id *id;
+
+ /* For ODCID, address is concatenated to it after qc.odcid.len so this
+ * comparison is safe.
+ */
+ if ((qc->scid.len == dcid_len &&
+ memcmp(qc->scid.data, dcid, dcid_len) == 0) ||
+ (qc->odcid.len == dcid_len &&
+ memcmp(qc->odcid.data, dcid, dcid_len)) == 0) {
+ return 1;
+ }
+
+ node = ebmb_lookup(&quic_dghdlrs[tid].cids, dcid, dcid_len);
+ if (node) {
+ id = ebmb_entry(node, struct quic_connection_id, node);
+ if (qc == id->qc)
+ return 1;
+ }
+
+ return 0;
+}
+
/* Retrieve the DCID from a QUIC datagram or packet with <buf> as first octet.
* Returns 1 if succeeded, 0 if not.
*/
diff --git a/src/quic_sock.c b/src/quic_sock.c
index f1207b7..5b10d8a 100644
--- a/src/quic_sock.c
+++ b/src/quic_sock.c
@@ -22,6 +22,7 @@
#include <haproxy/api.h>
#include <haproxy/buf.h>
#include <haproxy/connection.h>
+#include <haproxy/dynbuf.h>
#include <haproxy/fd.h>
#include <haproxy/freq_ctr.h>
#include <haproxy/global-t.h>
@@ -487,84 +488,14 @@
/* FD-owned quic-conn socket callback. */
static void quic_conn_sock_fd_iocb(int fd)
{
- struct quic_conn *qc;
+ struct quic_conn *qc = fdtab[fd].owner;
- struct sockaddr_storage saddr = {0}, daddr = {0};
- struct quic_receiver_buf *rxbuf;
- struct quic_transport_params *params;
- struct quic_dgram *new_dgram;
- struct buffer *buf;
- size_t max_sz;
- size_t cspace;
- unsigned char *dgram_buf;
- struct listener *l;
- ssize_t ret = 0;
-
- qc = fdtab[fd].owner;
- l = qc->li;
TRACE_ENTER(QUIC_EV_CONN_RCV, qc);
- new_dgram = NULL;
- rxbuf = MT_LIST_POP(&l->rx.rxbuf_list, typeof(rxbuf), rxbuf_el);
- if (!rxbuf)
- return;
-
- buf = &rxbuf->buf;
- new_dgram = quic_rxbuf_purge_dgrams(rxbuf);
-
- params = &l->bind_conf->quic_params;
- max_sz = params->max_udp_payload_size;
- cspace = b_contig_space(buf);
- if (cspace < max_sz) {
- struct quic_dgram *dgram;
-
- /* Do no mark <buf> as full, and do not try to consume it
- * if the contiguous remaining space is not at the end
- */
- if (b_tail(buf) + cspace < b_wrap(buf))
- goto end;
-
- /* Allocate a fake datagram, without data to locate
- * the end of the RX buffer (required during purging).
- */
- dgram = pool_alloc(pool_head_quic_dgram);
- if (!dgram)
- goto end;
-
- /* Initialize only the useful members of this fake datagram. */
- dgram->buf = NULL;
- dgram->len = cspace;
- /* Append this datagram only to the RX buffer list. It will
- * not be treated by any datagram handler.
- */
- LIST_APPEND(&rxbuf->dgram_list, &dgram->recv_list);
-
- /* Consume the remaining space */
- b_add(buf, cspace);
- if (b_contig_space(buf) < max_sz)
- goto end;
- }
-
- dgram_buf = (unsigned char *)b_tail(buf);
- ret = quic_recv(qc->fd, dgram_buf, max_sz,
- (struct sockaddr *)&saddr, sizeof(saddr),
- (struct sockaddr *)&daddr, sizeof(daddr),
- get_net_port(&qc->local_addr));
- if (ret <= 0)
- goto end;
-
- b_add(buf, ret);
- if (!quic_lstnr_dgram_dispatch(dgram_buf, ret, l, &qc->peer_addr, &qc->local_addr,
- new_dgram, &rxbuf->dgram_list)) {
- b_del(buf, ret);
- }
- new_dgram = NULL;
+ tasklet_wakeup_after(NULL, qc->wait_event.tasklet);
+ fd_stop_recv(fd);
- end:
- pool_free(pool_head_quic_dgram, new_dgram);
- MT_LIST_APPEND(&l->rx.rxbuf_list, &rxbuf->rxbuf_el);
TRACE_LEAVE(QUIC_EV_CONN_RCV, qc);
- return;
}
/* Send a datagram stored into <buf> buffer with <sz> as size.
@@ -627,6 +558,96 @@
return 0;
}
+/* Receive datagram on <qc> FD-owned socket.
+ *
+ * Returns the total number of bytes read or a negative value on error.
+ */
+int qc_rcv_buf(struct quic_conn *qc)
+{
+ struct sockaddr_storage saddr = {0}, daddr = {0};
+ struct quic_transport_params *params;
+ struct quic_dgram *new_dgram = NULL;
+ struct buffer buf = BUF_NULL;
+ size_t max_sz;
+ unsigned char *dgram_buf;
+ struct listener *l;
+ ssize_t ret = 0;
+
+ /* Do not call this if quic-conn FD is uninitialized. */
+ BUG_ON(qc->fd < 0);
+
+ TRACE_ENTER(QUIC_EV_CONN_RCV, qc);
+ l = qc->li;
+
+ params = &l->bind_conf->quic_params;
+ max_sz = params->max_udp_payload_size;
+
+ do {
+ if (!b_alloc(&buf))
+ break; /* TODO subscribe for memory again available. */
+
+ b_reset(&buf);
+ BUG_ON(b_contig_space(&buf) < max_sz);
+
+ /* Allocate datagram on first loop or after requeuing. */
+ if (!new_dgram && !(new_dgram = pool_alloc(pool_head_quic_dgram)))
+ break; /* TODO subscribe for memory again available. */
+
+ dgram_buf = (unsigned char *)b_tail(&buf);
+ ret = quic_recv(qc->fd, dgram_buf, max_sz,
+ (struct sockaddr *)&saddr, sizeof(saddr),
+ (struct sockaddr *)&daddr, sizeof(daddr),
+ get_net_port(&qc->local_addr));
+ if (ret <= 0) {
+ /* Subscribe FD for future reception. */
+ fd_want_recv(qc->fd);
+ break;
+ }
+
+ b_add(&buf, ret);
+
+ new_dgram->buf = dgram_buf;
+ new_dgram->len = ret;
+ new_dgram->dcid_len = 0;
+ new_dgram->dcid = NULL;
+ new_dgram->saddr = saddr;
+ new_dgram->daddr = daddr;
+ new_dgram->qc = NULL; /* set later via quic_dgram_parse() */
+
+ TRACE_DEVEL("read datagram", QUIC_EV_CONN_RCV, qc, new_dgram);
+
+ if (!quic_get_dgram_dcid(new_dgram->buf,
+ new_dgram->buf + new_dgram->len,
+ &new_dgram->dcid, &new_dgram->dcid_len)) {
+ continue;
+ }
+
+ if (!qc_check_dcid(qc, new_dgram->dcid, new_dgram->dcid_len)) {
+ /* Datagram received by error on the connection FD, dispatch it
+ * to its associated quic-conn.
+ *
+ * TODO count redispatch datagrams.
+ */
+ TRACE_STATE("wrong datagram on quic-conn socket, prepare to requeue it", QUIC_EV_CONN_RCV, qc);
+ ABORT_NOW();
+ }
+
+ quic_dgram_parse(new_dgram, qc, qc->li);
+ /* A datagram must always be consumed after quic_parse_dgram(). */
+ BUG_ON(new_dgram->buf);
+ } while (ret > 0);
+
+ pool_free(pool_head_quic_dgram, new_dgram);
+
+ if (b_size(&buf)) {
+ b_free(&buf);
+ offer_buffers(NULL, 1);
+ }
+
+ TRACE_LEAVE(QUIC_EV_CONN_RCV, qc);
+ return ret;
+}
+
/* Allocate a socket file-descriptor specific for QUIC connection <qc>.
* Endpoint addresses are specified by the two following arguments : <src> is
* the local address and <dst> is the remote one.