qt5base-lts/tests/auto/network/socket/qtcpserver/tst_qtcpserver.cpp
Øystein Heskestad 1c563035c7 Disable unit testing setting up QTcpServer on special Apple interfaces
The iBridge interface is used for the keyboard touch bar, and the Apple
Wireless Direct Link interfaces are used by Apple for various purposes.
Setting up a server on these interfaces does not work.
Only the tst_QTcpServer::linkLocal unit test on macOS is affected by
this change.

Fixes: QTBUG-103892
Change-Id: I29701ce51d5e40dff6c59547a8639c1fba330d36
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
2022-07-12 13:24:57 +02:00

1140 lines
36 KiB
C++

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <qglobal.h>
// To prevent windows system header files from re-defining min/max
#define NOMINMAX 1
#if defined(_WIN32)
#include <winsock2.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#define SOCKET int
#define INVALID_SOCKET -1
#endif
#include <QTest>
#include <QSignalSpy>
#include <QTimer>
#ifndef Q_OS_WIN
#include <unistd.h>
#include <sys/ioctl.h>
#endif
#include <qcoreapplication.h>
#include <qtcpsocket.h>
#include <qtcpserver.h>
#include <qhostaddress.h>
#if QT_CONFIG(process)
# include <qprocess.h>
#endif
#include <qstringlist.h>
#include <qplatformdefs.h>
#include <qhostinfo.h>
#include <qnetworkinterface.h>
#include <QNetworkProxy>
#include <QSet>
#include <QList>
#include "../../../network-settings.h"
#if defined(Q_OS_LINUX)
#define SHOULD_CHECK_SYSCALL_SUPPORT
#include <netinet/in.h>
#include <sys/socket.h>
#include <errno.h>
#endif
class tst_QTcpServer : public QObject
{
Q_OBJECT
private slots:
void initTestCase_data();
void initTestCase();
void init();
void cleanup();
void getSetCheck();
void constructing();
void clientServerLoop();
void ipv6Server();
void dualStack_data();
void dualStack();
void ipv6ServerMapped();
void crashTests();
void maxPendingConnections();
void listenError();
void waitForConnectionTest();
void setSocketDescriptor();
void listenWhileListening();
void addressReusable();
void setNewSocketDescriptorBlocking();
#ifndef QT_NO_NETWORKPROXY
void invalidProxy_data();
void invalidProxy();
void proxyFactory_data();
void proxyFactory();
#endif // !QT_NO_NETWORKPROXY
void qtbug14268_peek();
void serverAddress_data();
void serverAddress();
void qtbug6305_data() { serverAddress_data(); }
void qtbug6305();
void linkLocal();
void eagainBlockingAccept();
void canAccessPendingConnectionsWhileNotListening();
void pauseAccepting();
void pendingConnectionAvailable_data();
void pendingConnectionAvailable();
private:
bool shouldSkipIpv6TestsForBrokenGetsockopt();
#ifdef SHOULD_CHECK_SYSCALL_SUPPORT
bool ipv6GetsockoptionMissing(int level, int optname);
#endif
QString crashingServerDir;
};
// Testing get/set functions
void tst_QTcpServer::getSetCheck()
{
QTcpServer obj1;
// int QTcpServer::maxPendingConnections()
// void QTcpServer::setMaxPendingConnections(int)
obj1.setMaxPendingConnections(0);
QCOMPARE(0, obj1.maxPendingConnections());
obj1.setMaxPendingConnections(INT_MIN);
QCOMPARE(INT_MIN, obj1.maxPendingConnections());
obj1.setMaxPendingConnections(INT_MAX);
QCOMPARE(INT_MAX, obj1.maxPendingConnections());
}
void tst_QTcpServer::initTestCase_data()
{
QTest::addColumn<bool>("setProxy");
QTest::addColumn<int>("proxyType");
QTest::newRow("WithoutProxy") << false << 0;
#if QT_CONFIG(socks5)
QTest::newRow("WithSocks5Proxy") << true << int(QNetworkProxy::Socks5Proxy);
#endif
crashingServerDir = QFINDTESTDATA("crashingServer");
QVERIFY2(!crashingServerDir.isEmpty(), qPrintable(
QString::fromLatin1("Couldn't find crashingServer dir starting from %1.").arg(QDir::currentPath())));
}
void tst_QTcpServer::initTestCase()
{
#ifdef QT_TEST_SERVER
QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::socksProxyServerName(), 1080));
QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::httpProxyServerName(), 3128));
QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::ftpProxyServerName(), 2121));
QVERIFY(QtNetworkSettings::verifyConnection(QtNetworkSettings::imapServerName(), 143));
#else
if (!QtNetworkSettings::verifyTestNetworkSettings())
QSKIP("No network test server available");
#endif
}
void tst_QTcpServer::init()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy) {
#ifndef QT_NO_NETWORKPROXY
QFETCH_GLOBAL(int, proxyType);
if (proxyType == QNetworkProxy::Socks5Proxy) {
QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::socksProxyServerName(), 1080));
}
#else // !QT_NO_NETWORKPROXY
QSKIP("No proxy support");
#endif // QT_NO_NETWORKPROXY
}
}
void tst_QTcpServer::cleanup()
{
#ifndef QT_NO_NETWORKPROXY
QNetworkProxy::setApplicationProxy(QNetworkProxy::DefaultProxy);
#endif
}
#ifdef SHOULD_CHECK_SYSCALL_SUPPORT
bool tst_QTcpServer::ipv6GetsockoptionMissing(int level, int optname)
{
int testSocket;
testSocket = socket(PF_INET6, SOCK_STREAM, 0);
// If we can't test here, assume it's not missing
if (testSocket == -1)
return false;
bool result = false;
if (getsockopt(testSocket, level, optname, nullptr, 0) == -1) {
if (errno == EOPNOTSUPP) {
result = true;
}
}
close(testSocket);
return result;
}
#endif //SHOULD_CHECK_SYSCALL_SUPPORT
bool tst_QTcpServer::shouldSkipIpv6TestsForBrokenGetsockopt()
{
#ifdef SHOULD_CHECK_SYSCALL_SUPPORT
// Following parameters for setsockopt are not supported by all QEMU versions:
if (ipv6GetsockoptionMissing(SOL_IPV6, IPV6_V6ONLY)) {
return true;
}
#endif //SHOULD_CHECK_SYSCALL_SUPPORT
return false;
}
//----------------------------------------------------------------------------------
void tst_QTcpServer::constructing()
{
QTcpServer socket;
// Check the initial state of the QTcpSocket.
QCOMPARE(socket.isListening(), false);
QCOMPARE((int)socket.serverPort(), 0);
QCOMPARE(socket.serverAddress(), QHostAddress());
QCOMPARE(socket.maxPendingConnections(), 30);
QCOMPARE(socket.hasPendingConnections(), false);
QCOMPARE(socket.socketDescriptor(), (qintptr)-1);
QCOMPARE(socket.serverError(), QAbstractSocket::UnknownSocketError);
// Check the state of the socket layer?
}
//----------------------------------------------------------------------------------
void tst_QTcpServer::clientServerLoop()
{
QTcpServer server;
QSignalSpy spy(&server, SIGNAL(newConnection()));
QVERIFY(!server.isListening());
QVERIFY(!server.hasPendingConnections());
QVERIFY(server.listen(QHostAddress::Any, 11423));
QVERIFY(server.isListening());
QTcpSocket client;
QHostAddress serverAddress = QHostAddress::LocalHost;
if (!(server.serverAddress() == QHostAddress::Any) && !(server.serverAddress() == QHostAddress::AnyIPv6) && !(server.serverAddress() == QHostAddress::AnyIPv4))
serverAddress = server.serverAddress();
client.connectToHost(serverAddress, server.serverPort());
QVERIFY(client.waitForConnected(5000));
QVERIFY(server.waitForNewConnection(5000));
QVERIFY(server.hasPendingConnections());
QCOMPARE(spy.count(), 1);
QTcpSocket *serverSocket = server.nextPendingConnection();
QVERIFY(serverSocket != 0);
QVERIFY(serverSocket->write("Greetings, client!\n", 19) == 19);
serverSocket->flush();
QVERIFY(client.waitForReadyRead(5000));
QByteArray arr = client.readAll();
QCOMPARE(arr.constData(), "Greetings, client!\n");
QVERIFY(client.write("Well, hello to you!\n", 20) == 20);
client.flush();
QVERIFY(serverSocket->waitForReadyRead(5000));
arr = serverSocket->readAll();
QCOMPARE(arr.constData(), "Well, hello to you!\n");
}
//----------------------------------------------------------------------------------
void tst_QTcpServer::ipv6Server()
{
if (!QtNetworkSettings::hasIPv6())
QSKIP("system doesn't support ipv6!");
//### need to enter the event loop for the server to get the connection ?? ( windows)
QTcpServer server;
if (!server.listen(QHostAddress::LocalHostIPv6, 8944)) {
QCOMPARE(server.serverError(), QAbstractSocket::UnsupportedSocketOperationError);
return;
}
QCOMPARE(server.serverPort(), quint16(8944));
QVERIFY(server.serverAddress() == QHostAddress::LocalHostIPv6);
QTcpSocket client;
client.connectToHost("::1", 8944);
QVERIFY(client.waitForConnected(5000));
QVERIFY(server.waitForNewConnection());
QVERIFY(server.hasPendingConnections());
QTcpSocket *serverSocket = 0;
QVERIFY((serverSocket = server.nextPendingConnection()));
serverSocket->close();
delete serverSocket;
}
Q_DECLARE_METATYPE(QHostAddress);
void tst_QTcpServer::dualStack_data()
{
QTest::addColumn<QHostAddress>("bindAddress");
QTest::addColumn<bool>("v4ok");
QTest::addColumn<bool>("v6ok");
QTest::newRow("any") << QHostAddress(QHostAddress::Any) << true << true;
QTest::newRow("anyIPv4") << QHostAddress(QHostAddress::AnyIPv4) << true << false;
QTest::newRow("anyIPv6") << QHostAddress(QHostAddress::AnyIPv6) << false << true;
}
void tst_QTcpServer::dualStack()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy)
QSKIP("test server proxy doesn't support ipv6");
if (!QtNetworkSettings::hasIPv6())
QSKIP("system doesn't support ipv6!");
QFETCH(QHostAddress, bindAddress);
QFETCH(bool, v4ok);
QFETCH(bool, v6ok);
QTcpServer server;
QVERIFY(server.listen(bindAddress));
QTcpSocket v4client;
v4client.connectToHost(QHostAddress::LocalHost, server.serverPort());
QTcpSocket v6client;
v6client.connectToHost(QHostAddress::LocalHostIPv6, server.serverPort());
QCOMPARE(v4client.waitForConnected(5000), v4ok);
QCOMPARE(v6client.waitForConnected(5000), v6ok);
}
//----------------------------------------------------------------------------------
void tst_QTcpServer::ipv6ServerMapped()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy)
return;
QTcpServer server;
QVERIFY(server.listen(QHostAddress::LocalHost));
// let's try the normal case
QTcpSocket client1;
client1.connectToHost("127.0.0.1", server.serverPort());
QVERIFY(server.waitForNewConnection(5000));
delete server.nextPendingConnection();
if (!QtNetworkSettings::hasIPv6())
QSKIP("system doesn't support ipv6!");
// let's try the mapped one in the nice format
QTcpSocket client2;
client2.connectToHost("::ffff:127.0.0.1", server.serverPort());
QVERIFY(server.waitForNewConnection(5000));
delete server.nextPendingConnection();
// let's try the mapped in hex format
QTcpSocket client3;
client3.connectToHost("::ffff:7F00:0001", server.serverPort());
QVERIFY(server.waitForNewConnection(5000));
delete server.nextPendingConnection();
// However connecting to the v6 localhost should not work
QTcpSocket client4;
client4.connectToHost("::1", server.serverPort());
QVERIFY(!server.waitForNewConnection(5000));
}
//----------------------------------------------------------------------------------
void tst_QTcpServer::crashTests()
{
QTcpServer server;
server.close();
QVERIFY(server.listen());
}
//----------------------------------------------------------------------------------
void tst_QTcpServer::maxPendingConnections()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy) {
#ifndef QT_NO_NETWORKPROXY
QFETCH_GLOBAL(int, proxyType);
if (proxyType == QNetworkProxy::Socks5Proxy)
QSKIP("With socks5 only 1 connection is allowed ever");
#else // !QT_NO_NETWORKPROXY
QSKIP("No proxy support");
#endif // QT_NO_NETWORKPROXY
}
//### sees to fail sometimes ... a timing issue with the test on windows
QTcpServer server;
server.setMaxPendingConnections(2);
QTcpSocket socket1;
QTcpSocket socket2;
QTcpSocket socket3;
QSignalSpy spy(&server, SIGNAL(newConnection()));
QVERIFY(server.listen());
socket1.connectToHost(QHostAddress::LocalHost, server.serverPort());
socket2.connectToHost(QHostAddress::LocalHost, server.serverPort());
socket3.connectToHost(QHostAddress::LocalHost, server.serverPort());
// We must have two and only two connections. First compare waits until
// two connections have been made. The second compare makes sure no
// more are accepted. Creating connections happens multithreaded so
// qWait must be used for that.
QTRY_COMPARE(spy.count(), 2);
QTest::qWait(100);
QCOMPARE(spy.count(), 2);
QVERIFY(server.hasPendingConnections());
QVERIFY(server.nextPendingConnection());
QVERIFY(server.hasPendingConnections());
QVERIFY(server.nextPendingConnection());
QVERIFY(!server.hasPendingConnections());
QCOMPARE(server.nextPendingConnection(), (QTcpSocket*)0);
QVERIFY(server.waitForNewConnection(5000));
QVERIFY(server.hasPendingConnections());
QVERIFY(server.nextPendingConnection());
}
//----------------------------------------------------------------------------------
void tst_QTcpServer::listenError()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy) {
#ifndef QT_NO_NETWORKPROXY
QFETCH_GLOBAL(int, proxyType);
if (proxyType == QNetworkProxy::Socks5Proxy)
QSKIP("With socks5 we can not make hard requirements on the address or port");
#else // !QT_NO_NETWORKPROXY
QSKIP("No proxy support");
#endif //QT_NO_NETWORKPROXY
}
QTcpServer server;
QVERIFY(!server.listen(QHostAddress("1.2.3.4"), 0));
QCOMPARE(server.serverError(), QAbstractSocket::SocketAddressNotAvailableError);
QCOMPARE(server.errorString().toLatin1().constData(), "The address is not available");
}
class ThreadConnector : public QThread
{
public:
ThreadConnector(const QHostAddress &host, quint16 port)
: host(host), port(port)
{ }
~ThreadConnector()
{
wait();
}
protected:
void run() override
{
sleep(2);
QTcpSocket socket;
socket.connectToHost(host, port);
QEventLoop loop;
QTimer::singleShot(5000, &loop, SLOT(quit()));
loop.exec();
}
private:
QHostAddress host;
quint16 port;
};
//----------------------------------------------------------------------------------
void tst_QTcpServer::waitForConnectionTest()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy) {
#ifndef QT_NO_NETWORKPROXY
QFETCH_GLOBAL(int, proxyType);
if (proxyType == QNetworkProxy::Socks5Proxy)
QSKIP("Localhost servers don't work well with SOCKS5");
#else // !QT_NO_NETWORKPROXY
QSKIP("No proxy support");
#endif // QT_NO_NETWORKPROXY
}
QTcpSocket findLocalIpSocket;
findLocalIpSocket.connectToHost(QtNetworkSettings::imapServerName(), 143);
QVERIFY(findLocalIpSocket.waitForConnected(5000));
QTcpServer server;
bool timeout = false;
QVERIFY(server.listen(findLocalIpSocket.localAddress()));
QVERIFY(!server.waitForNewConnection(1000, &timeout));
QCOMPARE(server.serverError(), QAbstractSocket::SocketTimeoutError);
QVERIFY(timeout);
ThreadConnector connector(findLocalIpSocket.localAddress(), server.serverPort());
connector.start();
QVERIFY(server.waitForNewConnection(3000, &timeout));
QVERIFY(!timeout);
}
//----------------------------------------------------------------------------------
void tst_QTcpServer::setSocketDescriptor()
{
QTcpServer server;
QVERIFY(!server.setSocketDescriptor(42));
QCOMPARE(server.serverError(), QAbstractSocket::UnsupportedSocketOperationError);
//adopting Open C sockets is not supported, neither is adopting externally created RSocket
#ifdef Q_OS_WIN
// ensure winsock is started
WSADATA wsaData;
QVERIFY(WSAStartup(MAKEWORD(2,0), &wsaData) == NO_ERROR);
#endif
SOCKET sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
QVERIFY(sock != INVALID_SOCKET);
sockaddr_in sin;
memset(&sin, 0, sizeof(sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_port = 0;
sin.sin_addr.s_addr = 0x00000000;
QVERIFY(::bind(sock, (sockaddr*)&sin, sizeof(sockaddr_in)) == 0);
QVERIFY(::listen(sock, 10) == 0);
QVERIFY(server.setSocketDescriptor(sock));
#ifdef Q_OS_WIN
WSACleanup();
#endif
}
//----------------------------------------------------------------------------------
void tst_QTcpServer::listenWhileListening()
{
QTcpServer server;
QVERIFY(server.listen());
QTest::ignoreMessage(QtWarningMsg, "QTcpServer::listen() called when already listening");
QVERIFY(!server.listen());
}
//----------------------------------------------------------------------------------
class SeverWithBlockingSockets : public QTcpServer
{
public:
SeverWithBlockingSockets()
: ok(false) { }
bool ok;
protected:
void incomingConnection(qintptr socketDescriptor) override
{
// how a user woulddo it (qabstractsocketengine is not public)
unsigned long arg = 0;
#if defined(Q_OS_WIN)
ok = ::ioctlsocket(socketDescriptor, FIONBIO, &arg) == 0;
::closesocket(socketDescriptor);
#else
ok = ::ioctl(socketDescriptor, FIONBIO, &arg) == 0;
::close(socketDescriptor);
#endif
}
};
void tst_QTcpServer::addressReusable()
{
#if !QT_CONFIG(process)
QSKIP("No qprocess support", SkipAll);
#else
#ifdef Q_OS_LINUX
QSKIP("The addressReusable test is unstable on Linux. See QTBUG-39985.");
#endif
QFETCH_GLOBAL(bool, setProxy);
if (setProxy) {
#ifndef QT_NO_NETWORKPROXY
QFETCH_GLOBAL(int, proxyType);
if (proxyType == QNetworkProxy::Socks5Proxy)
QSKIP("With socks5 this test does not make senans at the momment");
#else // !QT_NO_NETWORKPROXY
QSKIP("No proxy support");
#endif // QT_NO_NETWORKPROXY
}
// The crashingServer process will crash once it gets a connection.
QProcess process;
QString processExe = crashingServerDir + "/crashingServer";
process.start(processExe);
QVERIFY2(process.waitForStarted(), qPrintable(
QString::fromLatin1("Could not start %1: %2").arg(processExe, process.errorString())));
QVERIFY(process.waitForReadyRead(5000));
QTcpSocket socket;
socket.connectToHost(QHostAddress::LocalHost, 49199);
QVERIFY(socket.waitForConnected(5000));
QVERIFY(process.waitForFinished(30000));
// Give the system some time.
QTest::qSleep(10);
QTcpServer server;
QVERIFY(server.listen(QHostAddress::LocalHost, 49199));
#endif
}
void tst_QTcpServer::setNewSocketDescriptorBlocking()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy) {
#ifndef QT_NO_NETWORKPROXY
QFETCH_GLOBAL(int, proxyType);
if (proxyType == QNetworkProxy::Socks5Proxy)
QSKIP("With socks5 we can not make the socket descripter blocking");
#else // !QT_NO_NETWORKPROXY
QSKIP("No proxy support");
#endif // QT_NO_NETWORKPROXY
}
SeverWithBlockingSockets server;
QVERIFY(server.listen());
QTcpSocket socket;
socket.connectToHost(QHostAddress::LocalHost, server.serverPort());
QVERIFY(server.waitForNewConnection(5000));
QVERIFY(server.ok);
}
#ifndef QT_NO_NETWORKPROXY
void tst_QTcpServer::invalidProxy_data()
{
QTest::addColumn<int>("type");
QTest::addColumn<QString>("host");
QTest::addColumn<int>("port");
QTest::addColumn<int>("expectedError");
const QString imapIp = QtNetworkSettings::imapServerIp().toString();
const QString httpProxyIp = QtNetworkSettings::httpProxyServerIp().toString();
const QString socksIp = QtNetworkSettings::socksProxyServerIp().toString();
QTest::newRow("ftp-proxy") << int(QNetworkProxy::FtpCachingProxy) << imapIp << 143
<< int(QAbstractSocket::UnsupportedSocketOperationError);
QTest::newRow("http-proxy") << int(QNetworkProxy::HttpProxy) << httpProxyIp << 3128
<< int(QAbstractSocket::UnsupportedSocketOperationError);
QTest::newRow("no-such-host") << int(QNetworkProxy::Socks5Proxy)
<< "invalid.test.qt-project.org" << 1080
<< int(QAbstractSocket::ProxyNotFoundError);
QTest::newRow("socks5-on-http") << int(QNetworkProxy::Socks5Proxy) << httpProxyIp << 3128
<< int(QAbstractSocket::SocketTimeoutError);
}
void tst_QTcpServer::invalidProxy()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy)
return;
QFETCH(int, type);
QFETCH(QString, host);
QFETCH(int, port);
QNetworkProxy::ProxyType proxyType = QNetworkProxy::ProxyType(type);
QNetworkProxy proxy(proxyType, host, port);
QTcpServer server;
server.setProxy(proxy);
bool listenResult = server.listen();
QVERIFY(!listenResult);
QVERIFY(!server.errorString().isEmpty());
// note: the following test is not a hard failure.
// Sometimes, error codes change for the better
QTEST(int(server.serverError()), "expectedError");
}
// copied from tst_qnetworkreply.cpp
class MyProxyFactory: public QNetworkProxyFactory
{
public:
int callCount;
QList<QNetworkProxy> toReturn;
QNetworkProxyQuery lastQuery;
inline MyProxyFactory() { clear(); }
inline void clear()
{
callCount = 0;
toReturn = QList<QNetworkProxy>() << QNetworkProxy::DefaultProxy;
lastQuery = QNetworkProxyQuery();
}
virtual QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery &query) override
{
lastQuery = query;
++callCount;
return toReturn;
}
};
void tst_QTcpServer::proxyFactory_data()
{
QTest::addColumn<QList<QNetworkProxy> >("proxyList");
QTest::addColumn<QNetworkProxy>("proxyUsed");
QTest::addColumn<bool>("fails");
QTest::addColumn<int>("expectedError");
QList<QNetworkProxy> proxyList;
// tests that do get to listen
proxyList << QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::socksProxyServerName(), 1080);
QTest::newRow("socks5")
<< proxyList << proxyList.at(0)
<< false << int(QAbstractSocket::UnknownSocketError);
proxyList.clear();
proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3128)
<< QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::socksProxyServerName(), 1080);
QTest::newRow("cachinghttp+socks5")
<< proxyList << proxyList.at(1)
<< false << int(QAbstractSocket::UnknownSocketError);
proxyList.clear();
proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121)
<< QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3128)
<< QNetworkProxy(QNetworkProxy::Socks5Proxy, QtNetworkSettings::socksProxyServerName(), 1080);
QTest::newRow("ftp+cachinghttp+socks5")
<< proxyList << proxyList.at(2)
<< false << int(QAbstractSocket::UnknownSocketError);
// tests that fail to listen
proxyList.clear();
proxyList << QNetworkProxy(QNetworkProxy::HttpProxy, QtNetworkSettings::httpProxyServerName(), 3128);
QTest::newRow("http")
<< proxyList << proxyList.at(0)
<< true << int(QAbstractSocket::UnsupportedSocketOperationError);
proxyList.clear();
proxyList << QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3128);
QTest::newRow("cachinghttp")
<< proxyList << QNetworkProxy()
<< true << int(QAbstractSocket::UnsupportedSocketOperationError);
proxyList.clear();
proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121);
QTest::newRow("ftp")
<< proxyList << QNetworkProxy()
<< true << int(QAbstractSocket::UnsupportedSocketOperationError);
proxyList.clear();
proxyList << QNetworkProxy(QNetworkProxy::FtpCachingProxy, QtNetworkSettings::ftpProxyServerName(), 2121)
<< QNetworkProxy(QNetworkProxy::HttpCachingProxy, QtNetworkSettings::httpProxyServerName(), 3128);
QTest::newRow("ftp+cachinghttp")
<< proxyList << QNetworkProxy()
<< true << int(QAbstractSocket::UnsupportedSocketOperationError);
}
void tst_QTcpServer::proxyFactory()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy)
return;
QFETCH(QList<QNetworkProxy>, proxyList);
QFETCH(QNetworkProxy, proxyUsed);
QFETCH(bool, fails);
MyProxyFactory *factory = new MyProxyFactory;
factory->toReturn = proxyList;
QNetworkProxyFactory::setApplicationProxyFactory(factory);
QTcpServer server;
bool listenResult = server.listen();
// Verify that the factory was called properly
QCOMPARE(factory->callCount, 1);
QCOMPARE(factory->lastQuery, QNetworkProxyQuery(0, QString(), QNetworkProxyQuery::TcpServer));
QCOMPARE(listenResult, !fails);
QCOMPARE(server.errorString().isEmpty(), !fails);
// note: the following test is not a hard failure.
// Sometimes, error codes change for the better
QTEST(int(server.serverError()), "expectedError");
}
#endif // !QT_NO_NETWORKPROXY
class Qtbug14268Helper : public QObject
{
Q_OBJECT
public:
QByteArray lastDataPeeked;
public slots:
void newConnection() {
QTcpServer* server=static_cast<QTcpServer*>(sender());
QTcpSocket* s=server->nextPendingConnection();
connect(s,SIGNAL(readyRead()),this,SLOT(onServerReadyRead()));
}
void onServerReadyRead() {
QTcpSocket* clientSocket=static_cast<QTcpSocket*>(sender());
lastDataPeeked = clientSocket->peek(128*1024).toHex();
QTestEventLoop::instance().exitLoop();
}
};
// there is a similar test inside tst_qtcpsocket that uses the waitFor* functions instead
void tst_QTcpServer::qtbug14268_peek()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy)
return;
QTcpServer server;
server.listen();
Qtbug14268Helper helper;
QObject::connect(&server, SIGNAL(newConnection()), &helper, SLOT(newConnection()));
QTcpSocket client;
client.connectToHost(QHostAddress::LocalHost, server.serverPort());
QVERIFY(client.waitForConnected(2000));
client.write("abc\n");
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(helper.lastDataPeeked, QByteArray("6162630a"));
client.write("def\n");
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(helper.lastDataPeeked, QByteArray("6162630a6465660a"));
client.write("ghi\n");
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(helper.lastDataPeeked, QByteArray("6162630a6465660a6768690a"));
}
void tst_QTcpServer::serverAddress_data()
{
QTest::addColumn<QHostAddress>("listenAddress");
QTest::addColumn<QHostAddress>("serverAddress");
if (QtNetworkSettings::hasIPv6())
QTest::newRow("Any") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::Any);
else
QTest::newRow("Any") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv4);
QTest::newRow("AnyIPv4") << QHostAddress(QHostAddress::AnyIPv4) << QHostAddress(QHostAddress::AnyIPv4);
if (QtNetworkSettings::hasIPv6())
QTest::newRow("AnyIPv6") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv6);
foreach (const QNetworkInterface &iface, QNetworkInterface::allInterfaces()) {
if ((iface.flags() & QNetworkInterface::IsUp) == 0)
continue;
foreach (const QNetworkAddressEntry &entry, iface.addressEntries()) {
QTest::newRow(qPrintable(entry.ip().toString())) << entry.ip() << entry.ip();
}
}
}
void tst_QTcpServer::serverAddress()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy)
return;
QFETCH(QHostAddress, listenAddress);
QFETCH(QHostAddress, serverAddress);
QTcpServer server;
if (shouldSkipIpv6TestsForBrokenGetsockopt()
&& listenAddress == QHostAddress(QHostAddress::Any)) {
QSKIP("Syscalls needed for ipv6 sockoptions missing functionality");
}
// TODO: why does this QSKIP?
if (!server.listen(listenAddress))
QSKIP(qPrintable(server.errorString()));
QCOMPARE(server.serverAddress(), serverAddress);
}
// on OS X, calling listen() multiple times would succeed each time, which is
// most definitely not wanted.
void tst_QTcpServer::qtbug6305()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy)
return;
QFETCH(QHostAddress, listenAddress);
QTcpServer server;
QVERIFY2(server.listen(listenAddress), qPrintable(server.errorString()));
QTcpServer server2;
QVERIFY(!server2.listen(listenAddress, server.serverPort())); // second listen should fail
}
void tst_QTcpServer::linkLocal()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy)
return;
QList <QHostAddress> addresses;
QSet <QString> scopes;
QHostAddress localMaskv4("169.254.0.0");
QHostAddress localMaskv6("fe80::");
foreach (const QNetworkInterface& iface, QNetworkInterface::allInterfaces()) {
//Windows preallocates link local addresses to interfaces that are down.
//These may or may not work depending on network driver (they do not work for the Bluetooth PAN driver)
if (iface.flags() & QNetworkInterface::IsUp) {
#if defined(Q_OS_WIN)
// Do not connect to the Teredo Tunneling interface on Windows Xp.
if (iface.humanReadableName() == QString("Teredo Tunneling Pseudo-Interface"))
continue;
#elif defined(Q_OS_DARWIN)
// Do not add "utun" interfaces on macOS: nothing ever gets received
// (we don't know why)
if (iface.name().startsWith("utun"))
continue;
// Do not use the iBridge interfae
if (iface.hardwareAddress() == "AC:DE:48:00:11:22")
continue;
// Do no use the Apple Wireless Direct Link interfaces
if (iface.name().startsWith("awdl"))
continue;
#endif
foreach (QNetworkAddressEntry addressEntry, iface.addressEntries()) {
QHostAddress addr = addressEntry.ip();
if (addr.isInSubnet(localMaskv4, 16)) {
addresses << addr;
qDebug() << addr;
}
else if (!addr.scopeId().isEmpty() && addr.isInSubnet(localMaskv6, 64)) {
scopes << addr.scopeId();
addresses << addr;
qDebug() << addr;
}
}
}
}
if (addresses.isEmpty())
QSKIP("no link local addresses");
QList<QTcpServer*> servers;
quint16 port = 0;
foreach (const QHostAddress& addr, addresses) {
QTcpServer *server = new QTcpServer;
QVERIFY(server->listen(addr, port));
port = server->serverPort(); //listen to same port on different interfaces
servers << server;
}
QList<QTcpSocket*> clients;
foreach (const QHostAddress& addr, addresses) {
//unbound socket
QTcpSocket *socket = new QTcpSocket;
socket->connectToHost(addr, port);
QVERIFY(socket->waitForConnected(5000));
clients << socket;
//bound socket
socket = new QTcpSocket;
QVERIFY(socket->bind(addr));
socket->connectToHost(addr, port);
QVERIFY(socket->waitForConnected(5000));
clients << socket;
}
//each server should have two connections
foreach (QTcpServer* server, servers) {
//qDebug() << "checking for connections" << server->serverAddress() << ":" << server->serverPort();
QVERIFY(server->waitForNewConnection(5000));
QTcpSocket* remote = server->nextPendingConnection();
QVERIFY(remote != nullptr);
remote->close();
delete remote;
if (!server->hasPendingConnections())
QVERIFY(server->waitForNewConnection(5000));
remote = server->nextPendingConnection();
QVERIFY(remote != nullptr);
remote->close();
delete remote;
QVERIFY(!server->hasPendingConnections());
}
//Connecting to the same address with different scope should normally fail
//However it will pass if there are two interfaces connected to the same physical network,
//e.g. connected via wired and wireless interfaces, or two wired NICs.
//which is a reasonably common case.
//So this is not auto tested.
qDeleteAll(clients);
qDeleteAll(servers);
}
void tst_QTcpServer::eagainBlockingAccept()
{
QTcpServer server;
server.listen(QHostAddress::LocalHost, 7896);
// Receiving a new connection causes TemporaryError, but shouldn't pause accepting.
QTcpSocket s;
s.connectToHost(QHostAddress::LocalHost, 7896);
QSignalSpy spy(&server, SIGNAL(newConnection()));
QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 500);
s.close();
// To test try again, should connect just fine.
s.connectToHost(QHostAddress::LocalHost, 7896);
QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 2, 500);
s.close();
server.close();
}
class NonListeningTcpServer : public QTcpServer
{
public:
void addSocketFromOutside(QTcpSocket* s)
{
addPendingConnection(s);
}
};
void tst_QTcpServer::canAccessPendingConnectionsWhileNotListening()
{
NonListeningTcpServer server;
QTcpSocket socket;
server.addSocketFromOutside(&socket);
QCOMPARE(&socket, server.nextPendingConnection());
}
void tst_QTcpServer::pauseAccepting()
{
QTcpServer server;
QSignalSpy spy(&server, &QTcpServer::newConnection);
QVERIFY(server.listen());
QFETCH_GLOBAL(bool, setProxy);
const auto address = QHostAddress(setProxy ? QtNetworkSettings::socksProxyServerIp()
: QHostAddress::LocalHost);
const int NumSockets = 6;
QTcpSocket sockets[NumSockets];
sockets[0].connectToHost(address, server.serverPort());
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
server.pauseAccepting();
for (int i = 1; i < NumSockets; ++i)
sockets[i].connectToHost(address, server.serverPort());
QVERIFY(!spy.wait(400));
QCOMPARE(spy.count(), 1);
server.resumeAccepting();
if (setProxy) {
QEXPECT_FAIL("", "The socks proxy does weird things after accepting the first connection",
Abort);
}
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 6);
}
// Only adds the socket to the pending connections list after emitNextSocket is
// called. It's very artificial, but it allows us to test the behavior of
// the pendingConnectionAvailable signal when a server doesn't add the socket
// during the incomingConnection virtual function.
class DerivedServer : public QTcpServer
{
public:
explicit DerivedServer(QObject *parent = nullptr)
: QTcpServer(parent)
{
}
void emitNextSocket()
{
if (m_socketDescriptors.isEmpty())
return;
auto *socket = new QTcpSocket(this);
socket->setSocketDescriptor(m_socketDescriptors.back());
m_socketDescriptors.pop_back();
addPendingConnection(socket);
}
protected:
void incomingConnection(qintptr socketDescriptor) override
{
m_socketDescriptors.push_back(socketDescriptor);
}
private:
QList<qintptr> m_socketDescriptors;
};
void tst_QTcpServer::pendingConnectionAvailable_data()
{
QTest::addColumn<bool>("useDerivedServer");
QTest::newRow("QTcpServer") << false;
QTest::newRow("DerivedServer") << true;
}
void tst_QTcpServer::pendingConnectionAvailable()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy)
QSKIP("This feature does not differentiate with or without proxy");
QFETCH(bool, useDerivedServer);
QTcpServer *server = useDerivedServer ? new DerivedServer : new QTcpServer;
if (!server->listen(QHostAddress::LocalHost, 0)) {
qWarning() << "Server failed to listen:" << server->errorString();
QSKIP("Server failed to listen");
}
QSignalSpy newConnectionSpy(server, &QTcpServer::newConnection);
QSignalSpy pendingConnectionSpy(server, &QTcpServer::pendingConnectionAvailable);
QTcpSocket socket;
socket.connectToHost(QHostAddress::LocalHost, server->serverPort());
QVERIFY(newConnectionSpy.wait());
QVERIFY(socket.waitForConnected());
QCOMPARE(socket.state(), QTcpSocket::ConnectedState);
int expectedPendingConnections = useDerivedServer ? 0 : 1;
QCOMPARE(pendingConnectionSpy.count(), expectedPendingConnections);
if (useDerivedServer)
static_cast<DerivedServer *>(server)->emitNextSocket();
QCOMPARE(pendingConnectionSpy.count(), 1);
}
QTEST_MAIN(tst_QTcpServer)
#include "tst_qtcpserver.moc"