Skip to content

Commit

Permalink
tst_QUdpSocket: always use an interface when binding to IPv6
Browse files Browse the repository at this point in the history
Binding without an interface and expecting the OS to select something is
not supported in all OSes. On FreeBSD, I keep getting EADDRNOTAVAIL. So
modify our test to only join, leave and send to multicast groups with an
interface selection.

With this, all tests either pass or are skipped for me on Linux,
FreeBSD, and macOS. On Windows, this revealed an inconsistency in
behavior, which this commit adds a workaround for.

Change-Id: Ifb5969bf206e4cd7b14efffd14fb6815456494d2
Reviewed-by: Mårten Nordheim <[email protected]>
Reviewed-by: Timur Pocheptsov <[email protected]>
  • Loading branch information
thiagomacieira committed Dec 29, 2017
1 parent 656804b commit 198c59d
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 7 deletions.
26 changes: 25 additions & 1 deletion src/network/socket/qnativesocketengine_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,9 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l

setPortAndAddress(header.destinationPort, header.destinationAddress, &aa, &msg.namelen);

uint oldIfIndex = 0;
bool mustSetIpv6MulticastIf = false;

if (msg.namelen == sizeof(aa.a6)) {
// sending IPv6
if (header.hopLimit != -1) {
Expand All @@ -1330,7 +1333,7 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l
cmsgptr = reinterpret_cast<WSACMSGHDR *>(reinterpret_cast<char *>(cmsgptr)
+ WSA_CMSG_SPACE(sizeof(int)));
}
if (header.ifindex != 0 || !header.senderAddress.isNull()) {
if (!header.senderAddress.isNull()) {
struct in6_pktinfo *data = reinterpret_cast<in6_pktinfo *>(WSA_CMSG_DATA(cmsgptr));
memset(data, 0, sizeof(*data));
msg.Control.len += WSA_CMSG_SPACE(sizeof(*data));
Expand All @@ -1343,6 +1346,21 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l
memcpy(&data->ipi6_addr, &tmp, sizeof(tmp));
cmsgptr = reinterpret_cast<WSACMSGHDR *>(reinterpret_cast<char *>(cmsgptr)
+ WSA_CMSG_SPACE(sizeof(*data)));
} else if (header.ifindex != 0) {
// Unlike other operating systems, setting the interface index in the in6_pktinfo
// structure above and leaving the ipi6_addr set to :: will cause the packets to be
// sent with source address ::. So we have to use IPV6_MULTICAST_IF, which MSDN is
// quite clear that "This option does not change the default interface for receiving
// IPv6 multicast traffic."
QT_SOCKOPTLEN_T len = sizeof(oldIfIndex);
if (::getsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF,
reinterpret_cast<char *>(&oldIfIndex), &len) == -1
|| ::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF,
reinterpret_cast<const char *>(&header.ifindex), sizeof(header.ifindex)) == -1) {
setError(QAbstractSocket::NetworkError, SendDatagramErrorString);
return -1;
}
mustSetIpv6MulticastIf = true;
}
} else {
// sending IPv4
Expand Down Expand Up @@ -1396,6 +1414,12 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l
ret = qint64(bytesSent);
}

if (mustSetIpv6MulticastIf) {
// undo what we did above
::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF,
reinterpret_cast<char *>(&oldIfIndex), sizeof(oldIfIndex));
}

#if defined (QNATIVESOCKETENGINE_DEBUG)
qDebug("QNativeSocketEnginePrivate::nativeSendDatagram(%p \"%s\", %lli, \"%s\", %i) == %lli", data,
qt_prettyDebug(data, qMin<qint64>(len, 16), len).data(), len,
Expand Down
10 changes: 10 additions & 0 deletions src/network/socket/qudpsocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ QUdpSocket::~QUdpSocket()
This function returns \c true if successful; otherwise it returns \c false
and sets the socket error accordingly.
\note Joining IPv6 multicast groups without an interface selection is not
supported in all operating systems. Consider using the overload where the
interface is specified.
\sa leaveMulticastGroup()
*/
bool QUdpSocket::joinMulticastGroup(const QHostAddress &groupAddress)
Expand Down Expand Up @@ -219,6 +223,9 @@ bool QUdpSocket::joinMulticastGroup(const QHostAddress &groupAddress,
This function returns \c true if successful; otherwise it returns \c false and
sets the socket error accordingly.
\note This function should be called with the same arguments as were passed
to joinMulticastGroup().
\sa joinMulticastGroup()
*/
bool QUdpSocket::leaveMulticastGroup(const QHostAddress &groupAddress)
Expand All @@ -233,6 +240,9 @@ bool QUdpSocket::leaveMulticastGroup(const QHostAddress &groupAddress)
Leaves the multicast group specified by \a groupAddress on the interface \a
iface.
\note This function should be called with the same arguments as were passed
to joinMulticastGroup().
\sa joinMulticastGroup()
*/
bool QUdpSocket::leaveMulticastGroup(const QHostAddress &groupAddress,
Expand Down
2 changes: 0 additions & 2 deletions tests/auto/network/socket/qudpsocket/BLACKLIST
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
[multicast]
osx
[writeDatagramToNonExistingPeer]
windows
osx
Expand Down
39 changes: 35 additions & 4 deletions tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ protected slots:
#ifdef SHOULD_CHECK_SYSCALL_SUPPORT
bool ipv6SetsockoptionMissing(int level, int optname);
#endif
QNetworkInterface interfaceForGroup(const QHostAddress &multicastGroup);

bool m_skipUnsupportedIPv6Tests;
QList<QHostAddress> allAddresses;
Expand Down Expand Up @@ -167,6 +168,33 @@ bool tst_QUdpSocket::shouldSkipIpv6TestsForBrokenSetsockopt()
return false;
}

QNetworkInterface tst_QUdpSocket::interfaceForGroup(const QHostAddress &multicastGroup)
{
if (multicastGroup.protocol() == QAbstractSocket::IPv4Protocol)
return QNetworkInterface();

static QNetworkInterface ipv6if = [=]() {
// find any link local address in the allAddress list
for (const QHostAddress &addr: qAsConst(allAddresses)) {
if (addr.isLoopback())
continue;

QString scope = addr.scopeId();
if (!scope.isEmpty()) {
QNetworkInterface iface = QNetworkInterface::interfaceFromName(scope);
qDebug() << "Will bind IPv6 sockets to" << iface;
return iface;
}
}

qWarning("interfaceForGroup(%s) could not find any link-local IPv6 address! "
"Make sure this test is behind a check of QtNetworkSettings::hasIPv6().",
qUtf8Printable(multicastGroup.toString()));
return QNetworkInterface();
}();
return ipv6if;
}

static QHostAddress makeNonAny(const QHostAddress &address, QHostAddress::SpecialAddress preferForAny = QHostAddress::LocalHost)
{
if (address == QHostAddress::Any)
Expand Down Expand Up @@ -1299,7 +1327,7 @@ void tst_QUdpSocket::multicastLeaveAfterClose()
bindAddress = QHostAddress::AnyIPv6;
QVERIFY2(udpSocket.bind(bindAddress, 0),
qPrintable(udpSocket.errorString()));
QVERIFY2(udpSocket.joinMulticastGroup(groupAddress),
QVERIFY2(udpSocket.joinMulticastGroup(groupAddress, interfaceForGroup(groupAddress)),
qPrintable(udpSocket.errorString()));
udpSocket.close();
QTest::ignoreMessage(QtWarningMsg, "QUdpSocket::leaveMulticastGroup() called on a QUdpSocket when not in QUdpSocket::BoundState");
Expand Down Expand Up @@ -1413,7 +1441,7 @@ void tst_QUdpSocket::multicast()
"QAbstractSocket: cannot bind to QHostAddress::Any (or an IPv6 address) and join an IPv4 multicast group;"
" bind to QHostAddress::AnyIPv4 instead if you want to do this");
}
QVERIFY2(receiver.joinMulticastGroup(groupAddress) == joinResult,
QVERIFY2(receiver.joinMulticastGroup(groupAddress, interfaceForGroup(groupAddress)) == joinResult,
qPrintable(receiver.errorString()));
if (!joinResult)
return;
Expand All @@ -1427,7 +1455,9 @@ void tst_QUdpSocket::multicast()
QUdpSocket sender;
sender.bind();
foreach (const QByteArray &datagram, datagrams) {
QCOMPARE(int(sender.writeDatagram(datagram, groupAddress, receiver.localPort())),
QNetworkDatagram dgram(datagram, groupAddress, receiver.localPort());
dgram.setInterfaceIndex(interfaceForGroup(groupAddress).index());
QCOMPARE(int(sender.writeDatagram(dgram)),
int(datagram.size()));
}

Expand All @@ -1452,7 +1482,8 @@ void tst_QUdpSocket::multicast()
}
QCOMPARE(receivedDatagrams, datagrams);

QVERIFY2(receiver.leaveMulticastGroup(groupAddress), qPrintable(receiver.errorString()));
QVERIFY2(receiver.leaveMulticastGroup(groupAddress, interfaceForGroup(groupAddress)),
qPrintable(receiver.errorString()));
}

void tst_QUdpSocket::echo_data()
Expand Down

0 comments on commit 198c59d

Please sign in to comment.