From 97ecc7a8ea5339a753507c3d4e4cd83028c6d038 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Fri, 23 Sep 2022 17:15:58 +0200 Subject: [PATCH] MEDIUM: quic: retrieve frontend destination address Retrieve the frontend destination address for a QUIC connection. This address is retrieve from the first received datagram and then stored in the associated quic-conn. This feature relies on IP_PKTINFO or affiliated flags support on the socket. This flag is set for each QUIC listeners in sock_inet_bind_receiver(). To retrieve the destination address, recvfrom() has been replaced by recvmsg() syscall. This operation and parsing of msghdr structure has been extracted in a wrapper quic_recv(). This change is useful to finalize the implementation of 'dst' sample fetch. As such, quic_sock_get_dst() has been edited to return local address from the quic-conn. As a best effort, if local address is not available due to kernel non-support of IP_PKTINFO, address of the listener is returned instead. This should be backported up to 2.6. --- include/haproxy/quic_conn-t.h | 2 + src/quic_conn.c | 11 ++- src/quic_sock.c | 144 +++++++++++++++++++++++++++++----- src/sock_inet.c | 19 +++++ 4 files changed, 154 insertions(+), 22 deletions(-) diff --git a/include/haproxy/quic_conn-t.h b/include/haproxy/quic_conn-t.h index 8384c01ca..0e3e2734a 100644 --- a/include/haproxy/quic_conn-t.h +++ b/include/haproxy/quic_conn-t.h @@ -374,6 +374,7 @@ struct quic_dgram { unsigned char *dcid; size_t dcid_len; struct sockaddr_storage saddr; + struct sockaddr_storage daddr; struct quic_conn *qc; struct list list; struct mt_list mt_list; @@ -636,6 +637,7 @@ struct quic_conn { struct ssl_sock_ctx *xprt_ctx; + struct sockaddr_storage local_addr; struct sockaddr_storage peer_addr; /* Used only to reach the tasklet for the I/O handler from this quic_conn object. */ diff --git a/src/quic_conn.c b/src/quic_conn.c index 34809c35e..65c584df5 100644 --- a/src/quic_conn.c +++ b/src/quic_conn.c @@ -4590,13 +4590,14 @@ static int parse_retry_token(struct quic_conn *qc, * for QUIC servers (or haproxy listeners). * is the destination connection ID, is the source connection ID, * the token found to be used for this connection with as - * length. is the source address. + * length. Endpoints addresses are specified via and . * Returns the connection if succeeded, NULL if not. */ static struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, struct quic_cid *dcid, struct quic_cid *scid, const struct quic_cid *token_odcid, - struct sockaddr_storage *saddr, + struct sockaddr_storage *local_addr, + struct sockaddr_storage *peer_addr, int server, int token, void *owner) { int i; @@ -4715,7 +4716,8 @@ static struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, qc->streams_by_id = EB_ROOT_UNIQUE; qc->stream_buf_count = 0; - memcpy(&qc->peer_addr, saddr, sizeof qc->peer_addr); + memcpy(&qc->local_addr, local_addr, sizeof(qc->local_addr)); + memcpy(&qc->peer_addr, peer_addr, sizeof qc->peer_addr); if (server && !qc_lstnr_params_init(qc, &l->bind_conf->quic_params, icid->stateless_reset_token, @@ -6028,7 +6030,8 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, ipv4 = dgram->saddr.ss_family == AF_INET; qc = qc_new_conn(qv, ipv4, &pkt->dcid, &pkt->scid, token_odcid, - &pkt->saddr, 1, !!pkt->token_len, l); + &dgram->daddr, &pkt->saddr, 1, + !!pkt->token_len, l); if (qc == NULL) goto drop; diff --git a/src/quic_sock.c b/src/quic_sock.c index 6845c0c38..cbb88e84f 100644 --- a/src/quic_sock.c +++ b/src/quic_sock.c @@ -10,10 +10,12 @@ * */ +#define _GNU_SOURCE /* required for struct in6_pktinfo */ #include #include #include +#include #include #include @@ -142,14 +144,17 @@ int quic_sock_get_dst(struct connection *conn, struct sockaddr *addr, socklen_t len = sizeof(qc->peer_addr); memcpy(addr, &qc->peer_addr, len); } else { - /* FIXME: front connection, no local address for now, we'll - * return the listener's address instead. + struct sockaddr_storage *from; + + /* Return listener address if IP_PKTINFO or friends are not + * supported by the socket. */ BUG_ON(!qc->li); - - if (len > sizeof(qc->li->rx.addr)) - len = sizeof(qc->li->rx.addr); - memcpy(addr, &qc->li->rx.addr, len); + from = is_addr(&qc->local_addr) ? &qc->local_addr : + &qc->li->rx.addr; + if (len > sizeof(*from)) + len = sizeof(*from); + memcpy(addr, from, len); } return 0; } @@ -237,6 +242,7 @@ struct connection *quic_sock_accept_conn(struct listener *l, int *status) */ static int quic_lstnr_dgram_dispatch(unsigned char *buf, size_t len, void *owner, struct sockaddr_storage *saddr, + struct sockaddr_storage *daddr, struct quic_dgram *new_dgram, struct list *dgrams) { struct quic_dgram *dgram; @@ -260,6 +266,7 @@ static int quic_lstnr_dgram_dispatch(unsigned char *buf, size_t len, void *owner dgram->dcid = dcid; dgram->dcid_len = dcid_len; dgram->saddr = *saddr; + dgram->daddr = *daddr; dgram->qc = NULL; LIST_APPEND(dgrams, &dgram->list); MT_LIST_APPEND(&quic_dghdlrs[cid_tid].dgrams, &dgram->mt_list); @@ -274,6 +281,111 @@ static int quic_lstnr_dgram_dispatch(unsigned char *buf, size_t len, void *owner return 0; } +/* Receive data from datagram socket . Data are placed in buffer of + * length . + * + * Datagram addresses will be returned via the next arguments. will be + * the peer address and the reception one. Note that can only be + * retrieved if the socket supports IP_PKTINFO or affiliated options. If not, + * will be set as AF_UNSPEC. The caller must specify to ensure + * that address is completely filled. + * + * Returns value from recvmsg syscall. + */ +static ssize_t quic_recv(int fd, void *out, size_t len, + struct sockaddr *from, socklen_t from_len, + struct sockaddr *to, socklen_t to_len, + uint16_t dst_port) +{ + union pktinfo { +#ifdef IP_PKTINFO + struct in_pktinfo in; +#else /* !IP_PKTINFO */ + struct in_addr addr; +#endif +#ifdef IPV6_RECVPKTINFO + struct in6_pktinfo in6; +#endif + }; + char cdata[CMSG_SPACE(sizeof(union pktinfo))]; + struct msghdr msg; + struct iovec vec; + struct cmsghdr *cmsg; + ssize_t ret; + + vec.iov_base = out; + vec.iov_len = len; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = from; + msg.msg_namelen = from_len; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_control = &cdata; + msg.msg_controllen = sizeof(cdata); + + clear_addr((struct sockaddr_storage *)to); + + do { + ret = recvmsg(fd, &msg, 0); + } while (ret < 0 && errno == EINTR); + + /* TODO handle errno. On EAGAIN/EWOULDBLOCK use fd_cant_recv() if + * using dedicated connection socket. + */ + + if (ret < 0) + goto end; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + switch (cmsg->cmsg_level) { + case IPPROTO_IP: +#if defined(IP_PKTINFO) + if (cmsg->cmsg_type == IP_PKTINFO) { + struct sockaddr_in *in = (struct sockaddr_in *)to; + struct in_pktinfo *info = (struct in_pktinfo *)CMSG_DATA(cmsg); + + if (to_len >= sizeof(struct sockaddr_in)) { + in->sin_family = AF_INET; + in->sin_addr = info->ipi_addr; + in->sin_port = dst_port; + } + } +#elif defined(IP_RECVDSTADDR) + if (cmsg->cmsg_type == IP_RECVDSTADDR) { + struct sockaddr_in *in = (struct sockaddr_in *)to; + struct in_addr *info = (struct in_addr *)CMSG_DATA(cmsg); + + if (to_len >= sizeof(struct sockaddr_in)) { + in->sin_family = AF_INET; + in->sin_addr.s_addr = info->s_addr; + in->sin_port = dst_port; + } + } +#endif /* IP_PKTINFO || IP_RECVDSTADDR */ + break; + + case IPPROTO_IPV6: +#ifdef IPV6_RECVPKTINFO + if (cmsg->cmsg_type == IPV6_PKTINFO) { + struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)to; + struct in6_pktinfo *info6 = (struct in6_pktinfo *)CMSG_DATA(cmsg); + + if (to_len >= sizeof(struct sockaddr_in6)) { + in6->sin6_family = AF_INET6; + memcpy(&in6->sin6_addr, &info6->ipi6_addr, sizeof(in6->sin6_addr)); + in6->sin6_port = dst_port; + } + } +#endif + break; + } + } + + end: + return ret; +} + /* Function called on a read event from a listening socket. It tries * to handle as many connections as possible. */ @@ -285,9 +397,8 @@ void quic_sock_fd_iocb(int fd) struct listener *l = objt_listener(fdtab[fd].owner); struct quic_transport_params *params; /* Source address */ - struct sockaddr_storage saddr = {0}; + struct sockaddr_storage saddr = {0}, daddr = {0}; size_t max_sz, cspace; - socklen_t saddrlen; struct quic_dgram *new_dgram; unsigned char *dgram_buf; int max_dgrams; @@ -358,18 +469,15 @@ void quic_sock_fd_iocb(int fd) } dgram_buf = (unsigned char *)b_tail(buf); - saddrlen = sizeof saddr; - do { - ret = recvfrom(fd, dgram_buf, max_sz, 0, - (struct sockaddr *)&saddr, &saddrlen); - if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { - fd_cant_recv(fd); - goto out; - } - } while (ret < 0 && errno == EINTR); + ret = quic_recv(fd, dgram_buf, max_sz, + (struct sockaddr *)&saddr, sizeof(saddr), + (struct sockaddr *)&daddr, sizeof(daddr), + get_net_port(&l->rx.addr)); + if (ret <= 0) + goto out; b_add(buf, ret); - if (!quic_lstnr_dgram_dispatch(dgram_buf, ret, l, &saddr, + if (!quic_lstnr_dgram_dispatch(dgram_buf, ret, l, &saddr, &daddr, new_dgram, &rxbuf->dgrams)) { /* If wrong, consume this datagram */ b_del(buf, ret); diff --git a/src/sock_inet.c b/src/sock_inet.c index d517e4c42..f8e872cbf 100644 --- a/src/sock_inet.c +++ b/src/sock_inet.c @@ -381,6 +381,25 @@ int sock_inet_bind_receiver(struct receiver *rx, char **errmsg) } #endif +#ifdef USE_QUIC + if (rx->proto->proto_type == PROTO_TYPE_DGRAM) { + switch (addr_inet.ss_family) { + case AF_INET: +#if defined(IP_PKTINFO) + setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); +#elif defined(IP_RECVDSTADDR) + setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &one, sizeof(one)); +#endif /* IP_PKTINFO || IP_RECVDSTADDR */ + break; + case AF_INET6: +#ifdef IPV6_RECVPKTINFO + setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); +#endif + break; + } + } +#endif /* USE_QUIC */ + if (!ext && bind(fd, (struct sockaddr *)&addr_inet, rx->proto->fam->sock_addrlen) == -1) { err |= ERR_RETRYABLE | ERR_ALERT; memprintf(errmsg, "cannot bind socket (%s)", strerror(errno));