diff options
author | Peter Hartmann <phartmann@rim.com> | 2013-02-21 16:10:29 +0100 |
---|---|---|
committer | Peter Hartmann <phartmann@blackberry.com> | 2013-05-07 16:42:41 +0200 |
commit | c67554fc3148b9849f7010896dcb753a7fc8dfb8 (patch) | |
tree | c4ee2387e54fdc8cb304f03d2eddb8e0a9764cb4 | |
parent | 6b2413cb386c718ad8974e55d5ab460b07c41a56 (diff) | |
download | qt4-tools-c67554fc3148b9849f7010896dcb753a7fc8dfb8.tar.gz |
[BB10-internal] QAbstractSocket: delay bind for TCP and SSL sockets
... because we delete the socket engine anyhow when connecting; so we
just store the local address, port and bind mode and bind after we
deleted the socket engine and before we connect to the server.
*caveat*:
DNS traffic is still flowing over the standard interface, because there
seems to be no way to tell getaddrinfo() which route to use.
This change also requires the following changes:
- the socket engine needs to allow connections when in BoundState
- in bind(), we need to always set the local address and port from
the socket engine, so that in case the bind() failed we clear the
address and port again.
Change-Id: I883199a7869effc3a66f0ded37a949ba7cd95b3d
Signed-off-by: Peter Hartmann <phartmann@rim.com>
-rw-r--r-- | src/network/socket/qabstractsocket.cpp | 154 | ||||
-rw-r--r-- | src/network/socket/qabstractsocket_p.h | 8 | ||||
-rw-r--r-- | src/network/socket/qnativesocketengine.cpp | 12 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket.cpp | 41 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_p.h | 5 | ||||
-rw-r--r-- | tests/auto/qtcpsocket/tst_qtcpsocket.cpp | 21 |
6 files changed, 194 insertions, 47 deletions
diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp index a11bf89636..5943452e9d 100644 --- a/src/network/socket/qabstractsocket.cpp +++ b/src/network/socket/qabstractsocket.cpp @@ -383,6 +383,7 @@ #ifndef QT_NO_OPENSSL #include <QtNetwork/qsslsocket.h> +#include <private/qsslsocket_p.h> #endif #include <private/qthread_p.h> @@ -486,7 +487,8 @@ QAbstractSocketPrivate::QAbstractSocketPrivate() hostLookupId(-1), socketType(QAbstractSocket::UnknownSocketType), state(QAbstractSocket::UnconnectedState), - socketError(QAbstractSocket::UnknownSocketError) + socketError(QAbstractSocket::UnknownSocketError), + localBindMode(DefaultForPlatform) { } @@ -855,6 +857,18 @@ void QAbstractSocketPrivate::startConnectingByName(const QString &host) connectTimeElapsed = 0; + if (!checkForBind()) { + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + state = QAbstractSocket::UnconnectedState; + emit q->stateChanged(state); + emit q->error(socketError); + return; + } else { + // move immediately from BoundState to ConnectingState + state = QAbstractSocket::ConnectingState; + } + if (initSocketLayer(QAbstractSocket::UnknownNetworkLayerProtocol)) { if (socketEngine->connectToHostByName(host, port) || socketEngine->state() == QAbstractSocket::ConnectingState) { @@ -939,6 +953,20 @@ void QAbstractSocketPrivate::_q_startConnecting(const QHostInfo &hostInfo) /*! \internal + Check whether we need to bind the socket before connecting. +*/ +bool QAbstractSocketPrivate::checkForBind() // ### how about UDP??? +{ + Q_Q(QAbstractSocket); + if (!localAddress.isNull()) { + return bind(q, QHostAddress(localAddress.toString()), localPort, localBindMode); + } else { // no bind() was called or scheduled via the properties + return true; + } +} + +/*! \internal + Called by a queued or direct connection from _q_startConnecting() or _q_testConnection(), this function takes the first address of the pending addresses list and tries to connect to it. If the @@ -1005,6 +1033,18 @@ void QAbstractSocketPrivate::_q_connectToNextAddress() continue; } + if (!checkForBind()) { + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + state = QAbstractSocket::UnconnectedState; + emit q->stateChanged(state); + emit q->error(socketError); + return; + } else { + // move immediately from BoundState to ConnectingState + state = QAbstractSocket::ConnectingState; + } + // Tries to connect to the address. If it succeeds immediately // (localhost address on BSD or any UDP connect), emit // connected() and return. @@ -1239,52 +1279,83 @@ bool QAbstractSocketPrivate::bind(QAbstractSocket *socket, const QHostAddress &a { QAbstractSocketPrivate *d = socket->d_func(); - // now check if the socket engine is initialized and to the right type - if (!d->socketEngine || !d->socketEngine->isValid()) { - QHostAddress nullAddress; - d->resolveProxy(nullAddress.toString(), port); + // If we are called before connecting, just save the state for + // the later bind, because upon connecting, the socket engine + // is reset anyhow. + if (d->socketType == QAbstractSocket::TcpSocket + && d->state == QAbstractSocket::UnconnectedState) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug() << "QAbstractSocketPrivate::bind() TCP socket called in unconnected state, delaying bind"; +#endif + socket->setLocalAddress(address); // ### Qt5: document + socket->setLocalPort(port); + d->localBindMode = mode; // we will need those 3 later when we bind again + return true; // actual errors will be reported when connecting + } else { + // If we are called before connecting, do the actual bind +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug() << "QAbstractSocketPrivate::bind() called in other state or as UDP socket, binding..."; +#endif - QAbstractSocket::NetworkLayerProtocol protocol = address.protocol(); - if (protocol == QAbstractSocket::UnknownNetworkLayerProtocol) - protocol = nullAddress.protocol(); + // now check if the socket engine is initialized and to the right type + if (!d->socketEngine || !d->socketEngine->isValid()) { + QHostAddress nullAddress; + d->resolveProxy(nullAddress.toString(), port); - if (!d->initSocketLayer(protocol)) - return false; - } + QAbstractSocket::NetworkLayerProtocol protocol = address.protocol(); + if (protocol == QAbstractSocket::UnknownNetworkLayerProtocol) + protocol = nullAddress.protocol(); + + if (!d->initSocketLayer(protocol)) + return false; + } #ifdef Q_OS_UNIX - if ((mode & ShareAddress) || (mode & ReuseAddressHint)) - d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); - else - d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0); + if ((mode & ShareAddress) || (mode & ReuseAddressHint)) + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); + else + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0); #endif #ifdef Q_OS_WIN - if (mode & ReuseAddressHint) - d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); - else - d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0); - if (mode & DontShareAddress) - d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 1); - else - d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 0); -#endif - bool result = d->socketEngine->bind(address, port); - d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); + if (mode & ReuseAddressHint) + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); + else + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0); + if (mode & DontShareAddress) + d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 1); + else + d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 0); +#endif + bool result = d->socketEngine->bind(address, port); + d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); + + d->localAddress = d->socketEngine->localAddress(); + d->localPort = d->socketEngine->localPort(); + + if (!result) { + d->socketError = d->socketEngine->error(); + socket->setErrorString(d->socketEngine->errorString()); + emit socket->error(d->socketError); + return false; + } - if (!result) { - d->socketError = d->socketEngine->error(); - socket->setErrorString(d->socketEngine->errorString()); - emit socket->error(d->socketError); - return false; + d->state = QAbstractSocket::BoundState; + + emit socket->stateChanged(d->state); + d->socketEngine->setReadNotificationEnabled(true); + return true; } +} - d->state = QAbstractSocket::BoundState; - d->localAddress = d->socketEngine->localAddress(); - d->localPort = d->socketEngine->localPort(); +void QAbstractSocketPrivate::setLocalAddress(QAbstractSocket *socket, + const QHostAddress &address) +{ + socket->setLocalAddress(address); +} - emit socket->stateChanged(d->state); - d->socketEngine->setReadNotificationEnabled(true); - return true; +void QAbstractSocketPrivate::setLocalPort(QAbstractSocket *socket, quint16 port) +{ + socket->setLocalPort(port); } QAbstractSocketEngine* QAbstractSocketPrivate::getSocketEngine(QAbstractSocket *socket) @@ -1416,9 +1487,9 @@ void QAbstractSocket::connectToHostImplementation(const QString &hostName, quint d->abortCalled = false; d->closeCalled = false; d->pendingClose = false; - d->localPort = 0; +// d->localPort = 0; // preserve; this might have been set through a bind() d->peerPort = 0; - d->localAddress.clear(); +// d->localAddress.clear(); // preserve; this might have been set through a bind() d->peerAddress.clear(); d->peerName = hostName; if (d->hostLookupId != -1) { @@ -2418,6 +2489,11 @@ void QAbstractSocket::setLocalAddress(const QHostAddress &address) { Q_D(QAbstractSocket); d->localAddress = address; +#ifndef QT_NO_OPENSSL + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) { + QSslSocketPrivate::setLocalAddress(socket, address); + } +#endif } /*! diff --git a/src/network/socket/qabstractsocket_p.h b/src/network/socket/qabstractsocket_p.h index 9c63c1276b..51e801d88f 100644 --- a/src/network/socket/qabstractsocket_p.h +++ b/src/network/socket/qabstractsocket_p.h @@ -170,10 +170,18 @@ public: }; Q_DECLARE_FLAGS(BindMode, BindFlag) + BindMode localBindMode; + + bool checkForBind(); Q_AUTOTEST_EXPORT static bool bind(QAbstractSocket *socket, const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform); // we don't need the other overload for now + // we cannot call the protected setLocalAddress from the static QSslSocket + // method, so we need these: + static void setLocalAddress(QAbstractSocket *socket, const QHostAddress &address); + static void setLocalPort(QAbstractSocket *socket, quint16 port); + static QAbstractSocketEngine* getSocketEngine(QAbstractSocket*); }; diff --git a/src/network/socket/qnativesocketengine.cpp b/src/network/socket/qnativesocketengine.cpp index 106932cb5e..0373cbe21f 100644 --- a/src/network/socket/qnativesocketengine.cpp +++ b/src/network/socket/qnativesocketengine.cpp @@ -143,6 +143,12 @@ QT_BEGIN_NAMESPACE " not in "#state1" or "#state2); \ return (returnValue); \ } } while (0) +#define Q_CHECK_STATES3(function, state1, state2, state3, returnValue) do { \ + if (d->socketState != (state1) && d->socketState != (state2) && d->socketState != (state3)) { \ + qWarning(""#function" was called" \ + " not in "#state1" or "#state2" or "#state3); \ + return (returnValue); \ + } } while (0) #define Q_CHECK_TYPE(function, type, returnValue) do { \ if (d->socketType != (type)) { \ qWarning(#function" was called by a" \ @@ -521,11 +527,13 @@ bool QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 por if (!d->checkProxy(address)) return false; - Q_CHECK_STATES(QNativeSocketEngine::connectToHost(), - QAbstractSocket::UnconnectedState, QAbstractSocket::ConnectingState, false); + Q_CHECK_STATES3(QNativeSocketEngine::connectToHost(), + QAbstractSocket::UnconnectedState, + QAbstractSocket::ConnectingState, QAbstractSocket::BoundState, false); d->peerAddress = address; d->peerPort = port; + bool connected = d->nativeConnect(address, port); if (connected) d->fetchConnectionParameters(); diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 757a49392b..be46ca5ffb 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -1415,6 +1415,8 @@ bool QSslSocket::waitForConnected(int msecs) if (!d->plainSocket) return false; bool retVal = d->plainSocket->waitForConnected(msecs); + setLocalAddress(d->plainSocket->localAddress()); + setLocalPort(d->plainSocket->localPort()); if (!retVal) { setSocketState(d->plainSocket->state()); setSocketError(d->plainSocket->error()); @@ -1750,7 +1752,15 @@ void QSslSocket::connectToHostImplementation(const QString &hostName, quint16 po d->plainSocket->setProperty("_q_user-agent", property("_q_user-agent")); #endif QIODevice::open(openMode); + + if (!d->cachedLocalHostAddress.isNull()) + QAbstractSocketPrivate::setLocalAddress(d->plainSocket, d->cachedLocalHostAddress); + + if (!d->cachedLocalPort != 0) + QAbstractSocketPrivate::setLocalPort(d->plainSocket, d->cachedLocalPort); + d->plainSocket->connectToHost(hostName, port, openMode); + d->cachedSocketDescriptor = d->plainSocket->socketDescriptor(); } @@ -1856,6 +1866,7 @@ QSslSocketPrivate::QSslSocketPrivate() , readyReadEmittedPointer(0) , allowRootCertOnDemandLoading(true) , plainSocket(0) + , cachedLocalPort(0) { QSslConfigurationPrivate::deepCopyDefaultConfiguration(&configuration); } @@ -2051,8 +2062,8 @@ void QSslSocketPrivate::createPlainSocket(QIODevice::OpenMode openMode) q->setOpenMode(openMode); // <- from QIODevice q->setSocketState(QAbstractSocket::UnconnectedState); q->setSocketError(QAbstractSocket::UnknownSocketError); - q->setLocalPort(0); - q->setLocalAddress(QHostAddress()); +// q->setLocalPort(0); // preserve; this might have been set through a bind() +// q->setLocalAddress(QHostAddress()); // preserve; this might have been set through a bind() q->setPeerPort(0); q->setPeerAddress(QHostAddress()); q->setPeerName(QString()); @@ -2308,6 +2319,32 @@ QByteArray QSslSocketPrivate::peek(qint64 maxSize) /*! \internal */ +void QSslSocketPrivate::setLocalAddress(QSslSocket *socket, const QHostAddress &address) +{ + QAbstractSocket *plainSocket = socket->d_func()->plainSocket; + if (plainSocket) { + QAbstractSocketPrivate::setLocalAddress(plainSocket, address); + } else { + socket->d_func()->cachedLocalHostAddress = address; + } +} + +/*! + \internal +*/ +void QSslSocketPrivate::setLocalPort(QSslSocket *socket, quint16 port) +{ + QAbstractSocket *plainSocket = socket->d_func()->plainSocket; + if (plainSocket) { + QAbstractSocketPrivate::setLocalPort(plainSocket, port); + } else { + socket->d_func()->cachedLocalPort = port; + } +} + +/*! + \internal +*/ bool QSslSocketPrivate::rootCertOnDemandLoadingSupported() { return s_loadRootCertsOnDemand; diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h index 40f6db9e8c..d827d0e35b 100644 --- a/src/network/ssl/qsslsocket_p.h +++ b/src/network/ssl/qsslsocket_p.h @@ -185,6 +185,11 @@ public: virtual void disconnected() = 0; virtual QSslCipher sessionCipher() const = 0; + quint16 cachedLocalPort; + QHostAddress cachedLocalHostAddress; + static void setLocalAddress(QSslSocket *socket, const QHostAddress &address); + static void setLocalPort(QSslSocket *socket, quint16 port); + Q_AUTOTEST_EXPORT static bool rootCertOnDemandLoadingSupported(); private: diff --git a/tests/auto/qtcpsocket/tst_qtcpsocket.cpp b/tests/auto/qtcpsocket/tst_qtcpsocket.cpp index 6de324ab83..654c5b572d 100644 --- a/tests/auto/qtcpsocket/tst_qtcpsocket.cpp +++ b/tests/auto/qtcpsocket/tst_qtcpsocket.cpp @@ -503,7 +503,13 @@ void tst_QTcpSocket::bind_data() continue; // link-local bind will fail, at least on Linux, so skip it. QString ip(entry.ip().toString()); - QTest::newRow(ip.toLatin1().constData()) << ip << true << ip; + QHostAddress hostAddress(ip); + // we will later connect to an IPv4 address, so we get success + // only with an IPv4 bind address. + bool successExpected = hostAddress.protocol() == QAbstractSocket::IPv4Protocol; + QString expectedBindAddress = (successExpected) ? ip : QString(); + QTest::newRow(ip.toLatin1().constData()) << ip << successExpected << + expectedBindAddress; } } @@ -534,12 +540,19 @@ void tst_QTcpSocket::bind() QTcpSocket *socket = newSocket(); qDebug() << "Binding " << addr; + // the first bind() will always succeed + QVERIFY(QAbstractSocketPrivate::bind(socket, addr)); + + socket->connectToHost(QtNetworkSettings::serverName(), 80); if (successExpected) { - QVERIFY2(QAbstractSocketPrivate::bind(socket, addr), qPrintable(socket->errorString())); + // there is no way to find out which interface can connect to + // the test server, so we cannot depend on whether connection + // succeeded or not. We just test that the bind address is + // what we expect after connecting + socket->waitForConnected(5000); } else { - QVERIFY(!QAbstractSocketPrivate::bind(socket, addr)); + QVERIFY(!socket->waitForConnected(5000)); } - QCOMPARE(socket->localAddress(), expectedLocalAddress); delete socket; |