MEDIUM: quic: use quic-conn socket for reception

Try to use the quic-conn socket for reception if it is allocated. For
this, the socket is inserted in the fdtab. This will call the new
handler quic_conn_io_cb() which is responsible to process the recv()
system call. It will reuse datagram dispatch for simplicity. However,
this is guaranteed to be called on the quic-conn thread, so it will be
more efficient to use a dedicated buffer. This will be implemented in
another commit.

This patch should improve performance by reducing contention on the
receiver socket. However, more gain can be obtained when the datagram
dispatch operation will be skipped.

Older quic_sock_fd_iocb() is renamed to quic_lstnr_sock_fd_iocb() to
emphasize its usage for the receiver socket.

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-t.h b/include/haproxy/quic_conn-t.h
index 1a4571d..44ae6a4 100644
--- a/include/haproxy/quic_conn-t.h
+++ b/include/haproxy/quic_conn-t.h
@@ -221,6 +221,7 @@
 #define           QUIC_EV_CONN_IDLE_TIMER (1ULL << 45)
 #define           QUIC_EV_CONN_SUB       (1ULL << 46)
 #define           QUIC_EV_CONN_ELEVELSEL (1ULL << 47)
+#define           QUIC_EV_CONN_RCV       (1ULL << 48)
 
 /* Similar to kernel min()/max() definitions. */
 #define QUIC_MIN(a, b) ({ \
diff --git a/include/haproxy/quic_sock.h b/include/haproxy/quic_sock.h
index 2788a23..3bb87a2 100644
--- a/include/haproxy/quic_sock.h
+++ b/include/haproxy/quic_sock.h
@@ -42,7 +42,7 @@
 struct connection *quic_sock_accept_conn(struct listener *l, int *status);
 
 struct task *quic_lstnr_dghdlr(struct task *t, void *ctx, unsigned int state);
-void quic_sock_fd_iocb(int fd);
+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);
 
diff --git a/src/proto_quic.c b/src/proto_quic.c
index 9dd9633..57dafc3 100644
--- a/src/proto_quic.c
+++ b/src/proto_quic.c
@@ -96,7 +96,7 @@
 	.rx_disable     = sock_disable,
 	.rx_unbind      = sock_unbind,
 	.rx_listening   = quic_sock_accepting_conn,
-	.default_iocb   = quic_sock_fd_iocb,
+	.default_iocb   = quic_lstnr_sock_fd_iocb,
 	.receivers      = LIST_HEAD_INIT(proto_quic4.receivers),
 	.nb_receivers   = 0,
 };
@@ -136,7 +136,7 @@
 	.rx_disable     = sock_disable,
 	.rx_unbind      = sock_unbind,
 	.rx_listening   = quic_sock_accepting_conn,
-	.default_iocb   = quic_sock_fd_iocb,
+	.default_iocb   = quic_lstnr_sock_fd_iocb,
 	.receivers      = LIST_HEAD_INIT(proto_quic6.receivers),
 	.nb_receivers   = 0,
 };
diff --git a/src/quic_conn.c b/src/quic_conn.c
index 5c48b55..65f4629 100644
--- a/src/quic_conn.c
+++ b/src/quic_conn.c
@@ -165,6 +165,7 @@
 	{ .mask = QUIC_EV_TRANSP_PARAMS, .name = "transport_params", .desc = "transport parameters"},
 	{ .mask = QUIC_EV_CONN_IDLE_TIMER, .name = "idle_timer",     .desc = "idle timer task"},
 	{ .mask = QUIC_EV_CONN_SUB,      .name = "xprt_sub",         .desc = "RX/TX subcription or unsubscription to QUIC xprt"},
+	{ .mask = QUIC_EV_CONN_RCV,      .name = "conn_recv",        .desc = "RX on connection" },
 	{ /* end */ }
 };
 
@@ -672,6 +673,13 @@
 				chunk_appendf(&trace_buf, " next_level=%c", quic_enc_level_char(*next_level));
 
 		}
+
+		if (mask & QUIC_EV_CONN_RCV) {
+			const struct quic_dgram *dgram = a2;
+
+			if (dgram)
+				chunk_appendf(&trace_buf, " dgram.len=%zu", dgram->len);
+		}
 	}
 	if (mask & QUIC_EV_CONN_LPKT) {
 		const struct quic_rx_packet *pkt = a2;
diff --git a/src/quic_sock.c b/src/quic_sock.c
index 7de9613..f1207b7 100644
--- a/src/quic_sock.c
+++ b/src/quic_sock.c
@@ -39,6 +39,9 @@
 #include <haproxy/task.h>
 #include <haproxy/trace.h>
 #include <haproxy/tools.h>
+#include <haproxy/trace.h>
+
+#define TRACE_SOURCE &trace_quic
 
 #define TRACE_SOURCE    &trace_quic
 
@@ -383,7 +386,7 @@
 /* Function called on a read event from a listening socket. It tries
  * to handle as many connections as possible.
  */
-void quic_sock_fd_iocb(int fd)
+void quic_lstnr_sock_fd_iocb(int fd)
 {
 	ssize_t ret;
 	struct quic_receiver_buf *rxbuf;
@@ -479,6 +482,89 @@
  out:
 	pool_free(pool_head_quic_dgram, new_dgram);
 	MT_LIST_APPEND(&l->rx.rxbuf_list, &rxbuf->rxbuf_el);
+}
+
+/* FD-owned quic-conn socket callback. */
+static void quic_conn_sock_fd_iocb(int fd)
+{
+	struct quic_conn *qc;
+
+	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;
+
+ 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.
@@ -602,6 +688,8 @@
 
 	qc->fd = fd;
 	fd_set_nonblock(fd);
+	fd_insert(fd, qc, quic_conn_sock_fd_iocb, tgid, ti->ltid_bit);
+	fd_want_recv(fd);
 
 	return;
 
@@ -613,8 +701,10 @@
 /* Release socket file-descriptor specific for QUIC connection <qc>. */
 void qc_release_fd(struct quic_conn *qc)
 {
-	if (qc_test_fd(qc))
+	if (qc_test_fd(qc)) {
+		fd_delete(qc->fd);
 		qc->fd = DEAD_FD_MAGIC;
+	}
 }
 
 /*********************** QUIC accept queue management ***********************/