MINOR: quic: allocate a socket per quic-conn

Allocate quic-conn owned socket if possible. This requires that this is
activated in haproxy configuration. Also, this is done only if local
address is known so it depends on the support of IP_PKTINFO.

For the moment this socket is not used. This causes QUIC support to be
broken as received datagram are not read. This commit will be completed
by a following patch to support recv operation on the newly allocated
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 119adf8..1a4571d 100644
--- a/include/haproxy/quic_conn-t.h
+++ b/include/haproxy/quic_conn-t.h
@@ -625,6 +625,8 @@
 	const struct quic_version *negotiated_version;
 	/* Negotiated version Initial TLS context */
 	struct quic_tls_ctx negotiated_ictx;
+	/* Connection owned socket FD. */
+	int fd;
 	/* QUIC transport parameters TLS extension */
 	int tps_tls_ext;
 	/* Thread ID this connection is attached to */
diff --git a/include/haproxy/quic_sock.h b/include/haproxy/quic_sock.h
index eeec932..2788a23 100644
--- a/include/haproxy/quic_sock.h
+++ b/include/haproxy/quic_sock.h
@@ -32,6 +32,7 @@
 #include <haproxy/api.h>
 #include <haproxy/connection-t.h>
 #include <haproxy/listener-t.h>
+#include <haproxy/quic_conn-t.h>
 #include <haproxy/quic_sock-t.h>
 
 int quic_session_accept(struct connection *cli_conn);
@@ -45,6 +46,24 @@
 int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t count,
                int flags);
 
+/* Set default value for <qc> socket as uninitialized. */
+static inline void qc_init_fd(struct quic_conn *qc)
+{
+	qc->fd = -1;
+}
+
+/* Returns true if <qc> socket is initialized else false. */
+static inline char qc_test_fd(struct quic_conn *qc)
+{
+	/* quic-conn socket should not be accessed once it has been released. */
+	BUG_ON(qc->fd == DEAD_FD_MAGIC);
+	return qc->fd >= 0;
+}
+
+void qc_alloc_fd(struct quic_conn *qc, const struct sockaddr_storage *src,
+                 const struct sockaddr_storage *dst);
+void qc_release_fd(struct quic_conn *qc);
+
 void quic_accept_push_qc(struct quic_conn *qc);
 
 #endif /* USE_QUIC */
diff --git a/src/quic_conn.c b/src/quic_conn.c
index 5a011eb..5c48b55 100644
--- a/src/quic_conn.c
+++ b/src/quic_conn.c
@@ -4817,6 +4817,15 @@
 		goto err;
 	}
 
+	if ((global.tune.options & GTUNE_QUIC_SOCK_PER_CONN) &&
+	    is_addr(local_addr)) {
+		TRACE_USER("Allocate a socket for QUIC connection", QUIC_EV_CONN_INIT, qc);
+		qc_alloc_fd(qc, local_addr, peer_addr);
+	}
+	else {
+		qc_init_fd(qc);
+	}
+
 	/* insert the allocated CID in the receiver datagram handler tree */
 	if (server)
 		ebmb_insert(&quic_dghdlrs[tid].cids, &icid->node, icid->cid.len);
@@ -4936,6 +4945,9 @@
 	/* We must not free the quic-conn if the MUX is still allocated. */
 	BUG_ON(qc->mux_state == QC_MUX_READY);
 
+	/* Close quic-conn socket fd. */
+	qc_release_fd(qc);
+
 	/* in the unlikely (but possible) case the connection was just added to
 	 * the accept_list we must delete it from there.
 	 */
diff --git a/src/quic_sock.c b/src/quic_sock.c
index 10e8b3f..bce8a1b 100644
--- a/src/quic_sock.c
+++ b/src/quic_sock.c
@@ -27,6 +27,7 @@
 #include <haproxy/global-t.h>
 #include <haproxy/list.h>
 #include <haproxy/listener.h>
+#include <haproxy/log.h>
 #include <haproxy/pool.h>
 #include <haproxy/proto_quic.h>
 #include <haproxy/proxy-t.h>
@@ -531,6 +532,81 @@
 	return 0;
 }
 
+/* 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.
+ *
+ * Return the socket FD or a negative error code. On error, socket is marked as
+ * uninitialized.
+ */
+void qc_alloc_fd(struct quic_conn *qc, const struct sockaddr_storage *src,
+                 const struct sockaddr_storage *dst)
+{
+	struct proxy *p = qc->li->bind_conf->frontend;
+	int fd = -1;
+	int ret;
+
+	/* Must not happen. */
+	BUG_ON(src->ss_family != dst->ss_family);
+
+	qc_init_fd(qc);
+
+	fd = socket(src->ss_family, SOCK_DGRAM, 0);
+	if (fd < 0)
+		goto err;
+
+	if (fd >= global.maxsock) {
+		send_log(p, LOG_EMERG,
+		         "Proxy %s reached the configured maximum connection limit. Please check the global 'maxconn' value.\n",
+		         p->id);
+		goto err;
+	}
+
+	ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+	if (ret < 0)
+		goto err;
+
+	switch (src->ss_family) {
+	case AF_INET:
+#if defined(IP_PKTINFO)
+		ret = setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
+#elif defined(IP_RECVDSTADDR)
+		ret = setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &one, sizeof(one));
+#endif /* IP_PKTINFO || IP_RECVDSTADDR */
+		break;
+	case AF_INET6:
+#ifdef IPV6_RECVPKTINFO
+		ret = setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one));
+#endif
+		break;
+	}
+	if (ret < 0)
+		goto err;
+
+	ret = bind(fd, (struct sockaddr *)src, get_addr_len(src));
+	if (ret < 0)
+		goto err;
+
+	ret = connect(fd, (struct sockaddr *)dst, get_addr_len(dst));
+	if (ret < 0)
+		goto err;
+
+	qc->fd = fd;
+	fd_set_nonblock(fd);
+
+	return;
+
+ err:
+	if (fd >= 0)
+		close(fd);
+}
+
+/* Release socket file-descriptor specific for QUIC connection <qc>. */
+void qc_release_fd(struct quic_conn *qc)
+{
+	if (qc_test_fd(qc))
+		qc->fd = DEAD_FD_MAGIC;
+}
 
 /*********************** QUIC accept queue management ***********************/
 /* per-thread accept queues */