summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Hartmann <phartmann@rim.com>2013-02-21 16:10:29 +0100
committerPeter Hartmann <phartmann@blackberry.com>2013-05-07 16:42:41 +0200
commitc67554fc3148b9849f7010896dcb753a7fc8dfb8 (patch)
treec4ee2387e54fdc8cb304f03d2eddb8e0a9764cb4
parent6b2413cb386c718ad8974e55d5ab460b07c41a56 (diff)
[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.cpp154
-rw-r--r--src/network/socket/qabstractsocket_p.h8
-rw-r--r--src/network/socket/qnativesocketengine.cpp12
-rw-r--r--src/network/ssl/qsslsocket.cpp41
-rw-r--r--src/network/ssl/qsslsocket_p.h5
-rw-r--r--tests/auto/qtcpsocket/tst_qtcpsocket.cpp21
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;