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_sock_sendto() for these packets. It is
similar to sendto() with an extra optional parameter for the source
address, which is set via cmsg_set_saddr() as in qc_snd_buf(). Flags
and EINTR handling are also identical to qc_snd_buf().

Also define two inline functions. quic_lstnr_may_set_src() checks if a
source address may be explicitly set for a datagram emitted through a
listener socket, which is only useful when it is bound on a wildcard
address, and even prohibited on FreeBSD otherwise. qc_may_use_saddr()
now relies on it so that this rule is defined in a single place.
quic_dgram_reply_src() returns the destination address of a received
datagram if it can be used as source address for a reply.

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. Note that prior to 3.4, quic_dgram addresses are of
sockaddr_storage type, so in46un_to_addr() conversion is not necessary
and quic_dgram_reply_src() can simply return &dgram->daddr.
---
 include/haproxy/quic_sock.h | 30 +++++++++++++++++
 src/quic_sock.c             | 64 +++++++++++++++++++++++++++++++++----
 2 files changed, 87 insertions(+), 7 deletions(-)

diff --git a/include/haproxy/quic_sock.h b/include/haproxy/quic_sock.h
index 7940e240a..7e77c3b2b 100644
--- a/include/haproxy/quic_sock.h
+++ b/include/haproxy/quic_sock.h
@@ -36,6 +36,7 @@
 #include <haproxy/obj_type.h>
 #include <haproxy/quic_conn-t.h>
 #include <haproxy/quic_sock-t.h>
+#include <haproxy/tools.h>
 
 int quic_session_accept(struct connection *cli_conn);
 int quic_sock_get_src(struct connection *conn, struct sockaddr *addr, 
socklen_t len);
@@ -48,6 +49,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_sock_sendto(int fd, const void *buf, size_t len,
+                         struct sockaddr_storage *dst,
+                         struct sockaddr_storage *src);
 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);
@@ -145,6 +149,32 @@ static inline void in46un_to_addr(const union 
sockaddr_in46 *src,
        }
 }
 
+/* Returns true if <addr> can be explicitly set as source address for the
+ * datagrams emitted through <l> listener socket. This is only useful when 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 when 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);
+}
+
+/* Converts <dgram> destination address into <laddr> and returns it if it can
+ * be used as source address for a reply emitted through <l> listener socket,
+ * else NULL is returned. This is useful for datagrams emitted without any
+ * connection, so that the reply originates from the address targeted by the
+ * peer when the listener is bound on a wildcard address.
+ */
+static inline struct sockaddr_storage *
+quic_dgram_reply_src(const struct quic_dgram *dgram, const struct listener *l,
+                     struct sockaddr_storage *laddr)
+{
+       in46un_to_addr(&dgram->daddr, laddr);
+       return quic_lstnr_may_set_src(l, laddr) ? laddr : NULL;
+}
+
 #endif /* USE_QUIC */
 #endif /* _HAPROXY_QUIC_SOCK_H */
 
diff --git a/src/quic_sock.c b/src/quic_sock.c
index d8ebf4cd6..7ed5316a9 100644
--- a/src/quic_sock.c
+++ b/src/quic_sock.c
@@ -681,13 +681,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 +808,61 @@ 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 <fd> socket to 
<dst>
+ * address. This is reserved to datagrams emitted without any connection, via
+ * a listener socket. If <src> is not NULL, it is used as the datagram source
+ * address, which is only useful and permitted if the socket is bound on a
+ * wildcard address (see quic_lstnr_may_set_src()). Else the source address is
+ * selected by the kernel.
+ *
+ * Returns the sendmsg() result.
+ */
+ssize_t quic_sock_sendto(int fd, const void *buf, size_t len,
+                         struct sockaddr_storage *dst,
+                         struct sockaddr_storage *src)
+{
+       ssize_t ret;
+       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 (src) {
+               msg.msg_control = ancillary_data.bufaddr;
+               cmsg_set_saddr(&msg, &cmsg, src);
+       }
+
+       do {
+               ret = sendmsg(fd, &msg, MSG_DONTWAIT|MSG_NOSIGNAL);
+       } while (ret < 0 && errno == EINTR);
+
+       return ret;
+}
+
 /* 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)



Reply via email to