diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp index 747fdeb9671..6a091209bea 100644 --- a/src/network/socket/qnativesocketengine_win.cpp +++ b/src/network/socket/qnativesocketengine_win.cpp @@ -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) { @@ -1330,7 +1333,7 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l cmsgptr = reinterpret_cast(reinterpret_cast(cmsgptr) + WSA_CMSG_SPACE(sizeof(int))); } - if (header.ifindex != 0 || !header.senderAddress.isNull()) { + if (!header.senderAddress.isNull()) { struct in6_pktinfo *data = reinterpret_cast(WSA_CMSG_DATA(cmsgptr)); memset(data, 0, sizeof(*data)); msg.Control.len += WSA_CMSG_SPACE(sizeof(*data)); @@ -1343,6 +1346,21 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l memcpy(&data->ipi6_addr, &tmp, sizeof(tmp)); cmsgptr = reinterpret_cast(reinterpret_cast(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(&oldIfIndex), &len) == -1 + || ::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF, + reinterpret_cast(&header.ifindex), sizeof(header.ifindex)) == -1) { + setError(QAbstractSocket::NetworkError, SendDatagramErrorString); + return -1; + } + mustSetIpv6MulticastIf = true; } } else { // sending IPv4 @@ -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(&oldIfIndex), sizeof(oldIfIndex)); + } + #if defined (QNATIVESOCKETENGINE_DEBUG) qDebug("QNativeSocketEnginePrivate::nativeSendDatagram(%p \"%s\", %lli, \"%s\", %i) == %lli", data, qt_prettyDebug(data, qMin(len, 16), len).data(), len, diff --git a/src/network/socket/qudpsocket.cpp b/src/network/socket/qudpsocket.cpp index 79629a07f2a..85c4f4cbfdb 100644 --- a/src/network/socket/qudpsocket.cpp +++ b/src/network/socket/qudpsocket.cpp @@ -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) @@ -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) @@ -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, diff --git a/tests/auto/network/socket/qudpsocket/BLACKLIST b/tests/auto/network/socket/qudpsocket/BLACKLIST index dcb5f876285..5b8d4f1e344 100644 --- a/tests/auto/network/socket/qudpsocket/BLACKLIST +++ b/tests/auto/network/socket/qudpsocket/BLACKLIST @@ -1,5 +1,3 @@ -[multicast] -osx [writeDatagramToNonExistingPeer] windows osx diff --git a/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp b/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp index 69d549d7381..5fdf014fc64 100644 --- a/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp +++ b/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp @@ -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 allAddresses; @@ -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) @@ -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"); @@ -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; @@ -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())); } @@ -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()