From: Yeonggi Kim <[email protected]>
Most QUIC datagrams are emitted via qc_snd_buf() which relies on a
quic_conn instance. When the connection uses the listener socket and the
listener is bound on a wildcard address, the local address retrieved on
reception is set as datagram source via ancillary data. This guarantees
that replies are emitted from the address targeted by the client, which
may otherwise drop them if its socket is connected.
However, some packets are emitted before any connection instance
exists: Retry, Version Negotiation and Stateless Reset. These are sent
via a bare sendto() on the listener socket, without source address.
Define a new function quic_lstnr_sock_send() for these packets. It
emits a datagram through a listener socket, with an extra parameter for
the local address targeted by the peer. This address is set as source
via cmsg_set_saddr() as in qc_snd_buf(), only if the listener is bound
on a wildcard address. This rule is now defined in a new function
quic_lstnr_may_set_src(), on which qc_may_use_saddr() also relies.
Apart from the source address, the emission is identical to the
existing sendto() calls: no flag is set and EINTR is not retried. As the
listener socket is non-blocking, a comment highlights that a datagram
is lost if the socket buffer is full, as there is no connection to retry
its emission later.
This patch does not introduce any functional change. It is a
prerequisite for the following fixes on Retry, Stateless Reset and
Version Negotiation emission. As such, it should be backported with
them up to 2.8.
---
include/haproxy/quic_sock.h | 3 ++
src/quic_sock.c | 75 +++++++++++++++++++++++++++++++++----
2 files changed, 71 insertions(+), 7 deletions(-)
diff --git a/include/haproxy/quic_sock.h b/include/haproxy/quic_sock.h
index 7940e240a..dad9115ec 100644
--- a/include/haproxy/quic_sock.h
+++ b/include/haproxy/quic_sock.h
@@ -48,6 +48,9 @@ void quic_lstnr_sock_fd_iocb(int fd);
int quic_dgram_requeue(struct quic_dgram *dgram, int cid_tid);
int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t count,
int flags, uint16_t gso_size);
+ssize_t quic_lstnr_sock_send(struct listener *l, const void *buf, size_t len,
+ struct sockaddr_storage *src,
+ struct sockaddr_storage *dst);
int qc_rcv_buf(struct quic_conn *qc);
void quic_conn_sock_fd_iocb(int fd);
void quic_conn_closed_sock_fd_iocb(int fd);
diff --git a/src/quic_sock.c b/src/quic_sock.c
index d8ebf4cd6..b4ea07eed 100644
--- a/src/quic_sock.c
+++ b/src/quic_sock.c
@@ -673,6 +673,18 @@ static void cmsg_set_gso(struct msghdr *msg, struct
cmsghdr **cmsg,
#endif
}
+/* Returns true if <addr> may be explicitly set as source address for a
+ * datagram emitted through <l> listener socket. This is only useful if the
+ * socket is bound on a wildcard address, as the kernel is then free to select
+ * any local address, possibly not the one targeted by the peer. It is even
+ * prohibited on FreeBSD if the socket is bound on a specific address.
+ */
+static inline int quic_lstnr_may_set_src(const struct listener *l,
+ const struct sockaddr_storage *addr)
+{
+ return !is_addr(&l->rx.addr) && is_addr(addr);
+}
+
/* Return 1 if the source address may be used, 0 if not. */
static int qc_may_use_saddr(struct quic_conn *qc)
{
@@ -681,13 +693,8 @@ static int qc_may_use_saddr(struct quic_conn *qc)
if (qc_test_fd(qc))
return 0;
- /* Connection to a listener from here.
- * The source address may be used when using listener socket (fd=-1) if
- * possible. This is not useful if the listening socket is bound to
- * a specific address. It is even prohibited on FreeBSD.
- */
- return (!is_addr(&qc->li->rx.addr) &&
- is_addr(&qc->local_addr));
+ /* Connection to a listener from here, using the listener socket
(fd=-1). */
+ return quic_lstnr_may_set_src(qc->li, &qc->local_addr);
}
/* Send a datagram stored into <buf> buffer with <sz> as size. The caller must
@@ -813,6 +820,60 @@ int qc_snd_buf(struct quic_conn *qc, const struct buffer
*buf, size_t sz,
return ret;
}
+/* Send <len> bytes from <buf> as a single datagram through <l> listener socket
+ * to <dst> address. This is reserved to datagrams emitted without connection.
+ * <src> is the local address targeted by the peer, as retrieved on reception.
+ * It is used as datagram source address if possible (see
+ * quic_lstnr_may_set_src()), else the kernel selects it.
+ *
+ * Note that the listener socket is non-blocking. Contrary to qc_snd_buf(),
+ * there is no connection to retry the emission later if the socket buffer is
+ * full: the datagram is then lost, and a new one will only be emitted in
+ * response to a further packet from the peer.
+ *
+ * Returns the sendmsg() result.
+ */
+ssize_t quic_lstnr_sock_send(struct listener *l, const void *buf, size_t len,
+ struct sockaddr_storage *src,
+ struct sockaddr_storage *dst)
+{
+ struct msghdr msg;
+ struct iovec vec;
+ struct cmsghdr *cmsg = NULL;
+
+ union {
+#ifdef IP_PKTINFO
+ char buf[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#endif /* IP_PKTINFO */
+#ifdef IPV6_RECVPKTINFO
+ char buf6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+#endif /* IPV6_RECVPKTINFO */
+ char bufaddr[CMSG_SPACE(sizeof(struct in_addr))];
+ struct cmsghdr align;
+ } ancillary_data;
+
+ /* Must be zero-initialized for CMSG_NXTHDR(), see qc_snd_buf(). */
+ memset(&ancillary_data, 0, sizeof(ancillary_data));
+
+ vec.iov_base = (void *)buf;
+ vec.iov_len = len;
+
+ msg.msg_name = dst;
+ msg.msg_namelen = get_addr_len(dst);
+ msg.msg_iov = &vec;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+
+ if (quic_lstnr_may_set_src(l, src)) {
+ msg.msg_control = ancillary_data.bufaddr;
+ cmsg_set_saddr(&msg, &cmsg, src);
+ }
+
+ return sendmsg(l->rx.fd, &msg, 0);
+}
+
/* Receive datagram on <qc> FD-owned socket.
*
* Returns the total number of bytes read or a negative value on error.
--
2.50.1 (Apple Git-155)