Fix broadcasts with dual-socket IPv6 in setSocketDescriptor

Amends and complements 28619f1ddd4a4b21d8489476d18927564ee0839d, which
had identified this could be a problem, but since there was no test, we
couldn't confirm it. Now that there is, we can see that it fails with
VxWorks, where I guess the two stacks are wholly separate.

Note fetchConnectionParameters() only sets AnyIPProtocol if the socket
is bound to :: or ::ffff:0.0.0.0. Binding to other addresses is
technically possible, but broadcasting would only work if bound to
another v4-mapped address. Changing fetchConnectionParameters() to, for
example, use toIPv4Address() and check for success is left as an
exercise for the reader.

Task-number: QTBUG-139586
Task-number: QTBUG-130070
Pick-to: 6.10 6.9 6.8 6.5
Change-Id: I44daa5ec27877abc2e09fffd875d4be6dfc86446
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Thiago Macieira 2025-08-26 11:21:09 -07:00
parent 1261e71b90
commit 1472386c95
2 changed files with 111 additions and 3 deletions

View File

@ -511,6 +511,7 @@ bool QNativeSocketEngine::initialize(qintptr socketDescriptor, QAbstractSocket::
// Set the broadcasting flag if it's a UDP socket. // Set the broadcasting flag if it's a UDP socket.
if (d->socketType == QAbstractSocket::UdpSocket if (d->socketType == QAbstractSocket::UdpSocket
&& d->socketProtocol != QAbstractSocket::IPv6Protocol
&& !setOption(BroadcastSocketOption, 1)) { && !setOption(BroadcastSocketOption, 1)) {
d->setError(QAbstractSocket::UnsupportedSocketOperationError, d->setError(QAbstractSocket::UnsupportedSocketOperationError,
QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString); QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString);

View File

@ -34,18 +34,24 @@
#if defined(Q_OS_LINUX) #if defined(Q_OS_LINUX)
#define SHOULD_CHECK_SYSCALL_SUPPORT #define SHOULD_CHECK_SYSCALL_SUPPORT
#endif
#ifdef Q_OS_UNIX
#include <netinet/in.h> #include <netinet/in.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <errno.h> #include <errno.h>
#else
#include <winsock2.h>
#include <ws2tcpip.h>
#endif #endif
#ifdef Q_OS_UNIX
# include <sys/socket.h>
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(SO_NREAD) #if defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(SO_NREAD)
# define RELIABLE_BYTES_AVAILABLE # define RELIABLE_BYTES_AVAILABLE
#endif #endif
#ifndef INVALID_SOCKET
# define INVALID_SOCKET -1
#endif
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
Q_DECLARE_METATYPE(QHostAddress) Q_DECLARE_METATYPE(QHostAddress)
@ -62,6 +68,8 @@ private slots:
void init(); void init();
void cleanup(); void cleanup();
void constructing(); void constructing();
void setSocketDescriptor_data();
void setSocketDescriptor();
void unconnectedServerAndClientTest(); void unconnectedServerAndClientTest();
void broadcasting(); void broadcasting();
void broadcastingDualSocket_data(); void broadcastingDualSocket_data();
@ -321,6 +329,105 @@ void tst_QUdpSocket::constructing()
// Check the state of the socket api // Check the state of the socket api
} }
//----------------------------------------------------------------------------------
void tst_QUdpSocket::setSocketDescriptor_data()
{
QTest::addColumn<QAbstractSocket::NetworkLayerProtocol>("protocol");
QTest::newRow("ipv4") << QAbstractSocket::IPv4Protocol;
bool hasIPv6 = true;
#if defined(PF_INET6) && defined(Q_OS_UNIX)
if (int fd = socket(PF_INET6, SOCK_DGRAM, 0); fd >= 0)
close(fd);
else
hasIPv6 = false;
#endif
if (hasIPv6) {
QTest::newRow("ipv6") << QAbstractSocket::IPv6Protocol;
# if defined(IPV6_V6ONLY) && !defined(Q_OS_VXWORKS)
QTest::newRow("dualstack") << QAbstractSocket::AnyIPProtocol;
# endif
}
}
void tst_QUdpSocket::setSocketDescriptor()
{
QFETCH(QAbstractSocket::NetworkLayerProtocol, protocol);
qintptr descriptor = -1;
int port = -1;
int domain = (protocol == QAbstractSocket::IPv4Protocol) ? AF_INET : AF_INET6;
descriptor = socket(domain, SOCK_DGRAM, 0);
if (descriptor == INVALID_SOCKET)
QSKIP("Could not create native socket");
#ifdef Q_OS_WIN
auto closeSocket = qScopeGuard([&descriptor] { closesocket(descriptor); });
using socklen_t = int;
#else
auto closeSocket = qScopeGuard([&descriptor] { close(descriptor); });
#endif
if (domain == AF_INET) {
sockaddr_in addr = {};
addr.sin_family = AF_INET;
if (bind(descriptor, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) < 0)
QSKIP("Could not bind IPv4 socket");
socklen_t len = sizeof(addr);
if (getsockname(descriptor, reinterpret_cast<sockaddr *>(&addr), &len) < 0)
QSKIP("Could not get local IPv4 port number");
port = qFromBigEndian(addr.sin_port);
#ifdef PF_INET6
} else {
# ifdef IPV6_V6ONLY
int v6only = protocol == QAbstractSocket::IPv6Protocol;
if (setsockopt(descriptor, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&v6only, sizeof(v6only)) < 0)
QSKIP("Could not set IPV6_V6ONLY");
# endif
sockaddr_in6 addr = {};
addr.sin6_family = AF_INET6;
if (bind(descriptor, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) < 0)
QSKIP("Could not bind IPv6 socket");
socklen_t len = sizeof(addr);
if (getsockname(descriptor, reinterpret_cast<sockaddr *>(&addr), &len) < 0)
QSKIP("Could not get local IPv6 port number");
port = qFromBigEndian(addr.sin6_port);
# endif // IPv6
}
QUdpSocket socket;
QSignalSpy spy(&socket, &QUdpSocket::stateChanged);
QVERIFY2(socket.setSocketDescriptor(descriptor, QAbstractSocket::BoundState),
qPrintable(socket.errorString()));
QCOMPARE(socket.socketDescriptor(), descriptor);
closeSocket.dismiss();
QVERIFY(socket.isValid());
QCOMPARE(socket.socketType(), QAbstractSocket::UdpSocket);
QCOMPARE(socket.state(), QAbstractSocket::BoundState);
QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0), QVariantList{QVariant::fromValue(QAbstractSocket::BoundState)});
QCOMPARE(socket.localPort(), port);
if (protocol == QAbstractSocket::IPv4Protocol)
QCOMPARE(socket.localAddress(), QHostAddress::AnyIPv4);
else if (protocol == QAbstractSocket::IPv6Protocol)
QCOMPARE(socket.localAddress(), QHostAddress::AnyIPv6);
else
QCOMPARE(socket.localAddress(), QHostAddress::Any);
QCOMPARE(socket.bytesToWrite(), 0);
QCOMPARE(socket.bytesAvailable(), 0);
}
//----------------------------------------------------------------------------------
void tst_QUdpSocket::unconnectedServerAndClientTest() void tst_QUdpSocket::unconnectedServerAndClientTest()
{ {
QUdpSocket serverSocket; QUdpSocket serverSocket;