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.