MEDIUM: quic-sock: fix udp source address for send on listener socket

When receiving a QUIC datagram, destination address is retrieved via
recvmsg() and stored in quic-conn as qc.local_addr. This address is then
reused when using the quic-conn owned socket.

When listener socket mode is preferred, send operation did not specify
the source address of the emitted datagram. If listener socket is bound
on a wildcard address, the kernel is free to choose any address assigned
to the local machine. This may be different from the address selected by
the client on its first datagram which will prevent the client to emit
next replies.

To address this, this patch fixes the UDP source address via sendmsg().
This process is similar to the reception and relies on ancillary
message, so the socket is left untouched after the operation. This is
heavily platform specific and may not be supported by some kernels.

This change has only an impact if listener socket only is used for QUIC
communications. This is the default behavior for 2.7 branch but not
anymore on 2.8. Use tune.quic.socket-owner set to listener to ensure set
it.

This should be backported up to 2.7.
This commit is contained in:
Amaury Denoyelle 2023-01-19 18:05:54 +01:00
parent d18025eeef
commit 2d380926ba

View File

@ -516,6 +516,91 @@ int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t sz,
ret = send(qc->fd, b_peek(buf, b_head_ofs(buf)), sz,
MSG_DONTWAIT | MSG_NOSIGNAL);
}
#if defined(IP_PKTINFO) || defined(IP_RECVDSTADDR) || defined(IPV6_RECVPKTINFO)
else if (is_addr(&qc->local_addr)) {
struct msghdr msg = { 0 };
struct iovec vec;
struct cmsghdr *cmsg;
#ifdef IP_PKTINFO
struct in_pktinfo in;
#endif /* IP_PKTINFO */
#ifdef IPV6_RECVPKTINFO
struct in6_pktinfo in6;
#endif /* IPV6_RECVPKTINFO */
union {
#ifdef IP_PKTINFO
char buf[CMSG_SPACE(sizeof(in))];
#endif /* IP_PKTINFO */
#ifdef IPV6_RECVPKTINFO
char buf6[CMSG_SPACE(sizeof(in6))];
#endif /* IPV6_RECVPKTINFO */
char bufaddr[CMSG_SPACE(sizeof(struct in_addr))];
struct cmsghdr align;
} u;
vec.iov_base = b_peek(buf, b_head_ofs(buf));
vec.iov_len = sz;
msg.msg_name = &qc->peer_addr;
msg.msg_namelen = get_addr_len(&qc->peer_addr);
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
switch (qc->local_addr.ss_family) {
case AF_INET:
#if defined(IP_PKTINFO)
memset(&in, 0, sizeof(in));
memcpy(&in.ipi_spec_dst,
&((struct sockaddr_in *)&qc->local_addr)->sin_addr,
sizeof(struct in_addr));
msg.msg_control = u.buf;
msg.msg_controllen = sizeof(u.buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
memcpy(CMSG_DATA(cmsg), &in, sizeof(in));
#elif defined(IP_RECVDSTADDR)
msg.msg_control = u.bufaddr;
msg.msg_controllen = sizeof(u.bufaddr);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_SENDSRCADDR;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
memcpy(CMSG_DATA(cmsg),
&((struct sockaddr_in *)&qc->local_addr)->sin_addr,
sizeof(struct in_addr));
#endif /* IP_PKTINFO || IP_RECVDSTADDR */
break;
case AF_INET6:
#ifdef IPV6_RECVPKTINFO
memset(&in6, 0, sizeof(in6));
memcpy(&in6.ipi6_addr,
&((struct sockaddr_in6 *)&qc->local_addr)->sin6_addr,
sizeof(struct in6_addr));
msg.msg_control = u.buf6;
msg.msg_controllen = sizeof(u.buf6);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
memcpy(CMSG_DATA(cmsg), &in6, sizeof(in6));
#endif /* IPV6_RECVPKTINFO */
break;
default:
break;
}
ret = sendmsg(qc->li->rx.fd, &msg,
MSG_DONTWAIT|MSG_NOSIGNAL);
}
#endif /* IP_PKTINFO || IP_RECVDSTADDR || IPV6_RECVPKTINFO */
else {
ret = sendto(qc->li->rx.fd, b_peek(buf, b_head_ofs(buf)), sz,
MSG_DONTWAIT|MSG_NOSIGNAL,