Server::sendMessage: encode the source IP of outgoing UDP packets to match the UDP socket's address family.

Murmur uses the saiTcpLocalAddress field of ServerUser to determine
the correct source address of outgoing UDP packets. On some systems,
such as multi-homed setups, this is important for correct datagram
delivery.

The saiTcpLocalAddress field is initialized when the client first
connects. Its value is extracted from QHostAddress, and then
converted to a sockaddr struct via our own HostAddress class's
toSockAddr method.

HostAddress::toSockAddr is implemented such that any IPv4 address
(including IPv4-mapped IPv6 addresses) will always cause a sockaddr
struct belonging to the AF_INET (IPv4) family to be output. Pure IPv6
addresses will be output as an AF_INET6 sockaddr.

The code that this change touches assumed that it could use the
value of saiTcpLocalAddress directly as the source address in UDP
packets.

This is not always the case. On most systems, Murmur will listen on
[::]:64738 by default, and as such, the address of the server's UDP
socket will be in the AF_INET6 family. Attempting to send packets
with a source address in the AF_INET family using that socket will
cause sendmsg() to return EINVAL on some systems, if not all.

The new code for the sendmsg() code path converts the ServerUser's
saiTcpLocalAddress back into a HostAddress, which, internally, is
fully IPv6. If the input address is IPv4, HostAddress will convert
it to an IPv4-mapped IPv6 address.

When the client's UDP socket is of the AF_INET6 family, we can now
trust that the HostAddress, 'tcpha', is either a real IPv6 address,
or an IPv4-mapped IPv6 address. This allows us to use the 'tcpha'
address regardless of the address family of the saiTcpLocalAddress.
We can simply memcpy it in place.

When the UDP socket is of the AF_INET family, we can only set the
source address if the saiTcpLocalAddress is also AF_INET, or if
it is of the AF_INET6 family and its address is an IPv4-mapped IPv6
address. When a pure IPv6 address is encountered in that path, it is
simply dropped.
This commit is contained in:
Mikkel Krautz 2013-05-31 20:48:38 +02:00
parent c00b44eefa
commit aab9cdcce4

View File

@ -853,20 +853,23 @@ void Server::sendMessage(ServerUser *u, const char *data, int len, QByteArray &c
msg.msg_controllen = CMSG_SPACE((u->saiUdpAddress.ss_family == AF_INET6) ? sizeof(struct in6_pktinfo) : sizeof(struct in_pktinfo));
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
if (u->saiTcpLocalAddress.ss_family == AF_INET6) {
HostAddress tcpha(u->saiTcpLocalAddress);
if (u->saiUdpAddress.ss_family == AF_INET6) {
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
struct in6_pktinfo *pktinfo = reinterpret_cast<struct in6_pktinfo *>(CMSG_DATA(cmsg));
memset(pktinfo, 0, sizeof(*pktinfo));
pktinfo->ipi6_addr = reinterpret_cast<struct sockaddr_in6 *>(& u->saiTcpLocalAddress)->sin6_addr;
memcpy(&pktinfo->ipi6_addr.s6_addr[0], &tcpha.qip6.c[0], sizeof(pktinfo->ipi6_addr.s6_addr));
} else {
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
struct in_pktinfo *pktinfo = reinterpret_cast<struct in_pktinfo *>(CMSG_DATA(cmsg));
memset(pktinfo, 0, sizeof(*pktinfo));
pktinfo->ipi_spec_dst = reinterpret_cast<struct sockaddr_in *>(& u->saiTcpLocalAddress)->sin_addr;
if (tcpha.isV6())
return;
pktinfo->ipi_spec_dst.s_addr = tcpha.hash[3];
}