From 03f852cb47d508d98aa90f501e9b7f4214e8ad8b Mon Sep 17 00:00:00 2001 From: Robin Burchell Date: Sun, 23 Oct 2011 20:04:52 +0200 Subject: [PATCH] Move support for socket binding from QUdpSocket upstream to QAbstractSocket. This should be API-compatible with Qt 4, but is not ABI-compatible, due to removing the enum from QUdpSocket. Task-number: QTBUG-121 Change-Id: I967968c6cb6f96d3ab1d6300eadd5bde6154b300 Reviewed-by: Thiago Macieira --- src/network/socket/qabstractsocket.cpp | 143 ++++++++++++++++- src/network/socket/qabstractsocket.h | 13 ++ src/network/socket/qudpsocket.cpp | 150 ------------------ src/network/socket/qudpsocket.h | 16 -- .../socket/qtcpsocket/tst_qtcpsocket.cpp | 61 +++++++ 5 files changed, 212 insertions(+), 171 deletions(-) diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp index e5bb72f36a..4d80ba259a 100644 --- a/src/network/socket/qabstractsocket.cpp +++ b/src/network/socket/qabstractsocket.cpp @@ -291,10 +291,10 @@ bytes). \value NetworkError An error occurred with the network (e.g., the network cable was accidentally plugged out). - \value AddressInUseError The address specified to QUdpSocket::bind() is + \value AddressInUseError The address specified to QAbstractSocket::bind() is already in use and was set to be exclusive. \value SocketAddressNotAvailableError The address specified to - QUdpSocket::bind() does not belong to the host. + QAbstractSocket::bind() does not belong to the host. \value UnsupportedSocketOperationError The requested socket operation is not supported by the local operating system (e.g., lack of IPv6 support). @@ -329,7 +329,7 @@ \value HostLookupState The socket is performing a host name lookup. \value ConnectingState The socket has started establishing a connection. \value ConnectedState A connection is established. - \value BoundState The socket is bound to an address and port (for servers). + \value BoundState The socket is bound to an address and port. \value ClosingState The socket is about to close (data may still be waiting to be written). \value ListeningState For internal use only. @@ -363,6 +363,51 @@ \sa QAbstractSocket::setSocketOption(), QAbstractSocket::socketOption() */ +/*! \enum QAbstractSocket::BindFlag + \since 5.0 + + This enum describes the different flags you can pass to modify the + behavior of QAbstractSocket::bind(). + + \note On Symbian OS bind flags behaviour depends on process capabilties. + If process has NetworkControl capability, the bind attempt with + ReuseAddressHint will always succeed even if the address and port is already + bound by another socket with any flags. If process does not have + NetworkControl capability, the bind attempt to address and port already + bound by another socket will always fail. + + \value ShareAddress Allow other services to bind to the same address + and port. This is useful when multiple processes share + the load of a single service by listening to the same address and port + (e.g., a web server with several pre-forked listeners can greatly + improve response time). However, because any service is allowed to + rebind, this option is subject to certain security considerations. + Note that by combining this option with ReuseAddressHint, you will + also allow your service to rebind an existing shared address. On + Unix, this is equivalent to the SO_REUSEADDR socket option. On Windows, + this option is ignored. + + \value DontShareAddress Bind the address and port exclusively, so that + no other services are allowed to rebind. By passing this option to + QAbstractSocket::bind(), you are guaranteed that on successs, your service + is the only one that listens to the address and port. No services are + allowed to rebind, even if they pass ReuseAddressHint. This option + provides more security than ShareAddress, but on certain operating + systems, it requires you to run the server with administrator privileges. + On Unix and Mac OS X, not sharing is the default behavior for binding + an address and port, so this option is ignored. On Windows, this + option uses the SO_EXCLUSIVEADDRUSE socket option. + + \value ReuseAddressHint Provides a hint to QAbstractSocket that it should try + to rebind the service even if the address and port are already bound by + another socket. On Windows, this is equivalent to the SO_REUSEADDR + socket option. On Unix, this option is ignored. + + \value DefaultForPlatform The default option for the current platform. + On Unix and Mac OS X, this is equivalent to (DontShareAddress + + ReuseAddressHint), and on Windows, its equivalent to ShareAddress. +*/ + #include "qabstractsocket.h" #include "qabstractsocket_p.h" @@ -1293,6 +1338,94 @@ QAbstractSocket::~QAbstractSocket() abort(); } +/*! + \since 5.0 + + Binds to \a address on port \a port, using the BindMode \a mode. + + Binds this socket to the address \a address and the port \a port. + + For UDP sockets, after binding, the signal QUdpSocket::readyRead() is emitted + whenever a UDP datagram arrives on the specified address and port. + Thus, This function is useful to write UDP servers. + + For TCP sockets, this function may be used to specify which interface to use + for an outgoing connection, which is useful in case of multiple network + interfaces. + + By default, the socket is bound using the DefaultForPlatform BindMode. + If a port is not specified, a random port is chosen. + + On success, the functions returns true and the socket enters + BoundState; otherwise it returns false. + +*/ +bool QAbstractSocket::bind(const QHostAddress &address, quint16 port, BindMode mode) +{ + Q_D(QAbstractSocket); + + // 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); + + 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); +#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 (!result) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + emit error(d->socketError); + return false; + } + + d->state = BoundState; + d->localAddress = d->socketEngine->localAddress(); + d->localPort = d->socketEngine->localPort(); + + emit stateChanged(d->state); + d->socketEngine->setReadNotificationEnabled(true); + return true; +} + +/*! + \since 5.0 + \overload + + Binds to QHostAddress:Any on port \a port, using the BindMode \a mode. + + By default, the socket is bound using the DefaultForPlatform BindMode. + If a port is not specified, a random port is chosen. +*/ +bool QAbstractSocket::bind(quint16 port, BindMode mode) +{ + return bind(QHostAddress::Any, port, mode); +} + /*! Returns true if the socket is valid and ready for use; otherwise returns false. @@ -2345,7 +2478,7 @@ qint64 QAbstractSocket::writeData(const char *data, qint64 size) proxy connections for virtual connection settings. Note that this function does not bind the local port of the socket - prior to a connection (e.g., QUdpSocket::bind()). + prior to a connection (e.g., QAbstractSocket::bind()). \sa localAddress(), setLocalAddress(), setPeerPort() */ @@ -2367,7 +2500,7 @@ void QAbstractSocket::setLocalPort(quint16 port) proxy connections for virtual connection settings. Note that this function does not bind the local address of the socket - prior to a connection (e.g., QUdpSocket::bind()). + prior to a connection (e.g., QAbstractSocket::bind()). \sa localAddress(), setLocalPort(), setPeerAddress() */ diff --git a/src/network/socket/qabstractsocket.h b/src/network/socket/qabstractsocket.h index b757092915..ee910e3b33 100644 --- a/src/network/socket/qabstractsocket.h +++ b/src/network/socket/qabstractsocket.h @@ -115,10 +115,20 @@ public: MulticastTtlOption, // IP_MULTICAST_TTL MulticastLoopbackOption // IP_MULTICAST_LOOPBACK }; + enum BindFlag { + DefaultForPlatform = 0x0, + ShareAddress = 0x1, + DontShareAddress = 0x2, + ReuseAddressHint = 0x4 + }; + Q_DECLARE_FLAGS(BindMode, BindFlag) QAbstractSocket(SocketType socketType, QObject *parent); virtual ~QAbstractSocket(); + bool bind(const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform); + bool bind(quint16 port = 0, BindMode mode = DefaultForPlatform); + // ### Qt 5: Make connectToHost() and disconnectFromHost() virtual. void connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol); void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite); @@ -214,6 +224,9 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_forceDisconnect()) }; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractSocket::BindMode) + #ifndef QT_NO_DEBUG_STREAM Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketError); Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketState); diff --git a/src/network/socket/qudpsocket.cpp b/src/network/socket/qudpsocket.cpp index 009ce92d8e..79083e489a 100644 --- a/src/network/socket/qudpsocket.cpp +++ b/src/network/socket/qudpsocket.cpp @@ -108,51 +108,6 @@ \sa QTcpSocket */ -/*! \enum QUdpSocket::BindFlag - \since 4.1 - - This enum describes the different flags you can pass to modify the - behavior of QUdpSocket::bind(). - - \note On Symbian OS bind flags behaviour depends on process capabilties. - If process has NetworkControl capability, the bind attempt with - ReuseAddressHint will always succeed even if the address and port is already - bound by another socket with any flags. If process does not have - NetworkControl capability, the bind attempt to address and port already - bound by another socket will always fail. - - \value ShareAddress Allow other services to bind to the same address - and port. This is useful when multiple processes share - the load of a single service by listening to the same address and port - (e.g., a web server with several pre-forked listeners can greatly - improve response time). However, because any service is allowed to - rebind, this option is subject to certain security considerations. - Note that by combining this option with ReuseAddressHint, you will - also allow your service to rebind an existing shared address. On - Unix, this is equivalent to the SO_REUSEADDR socket option. On Windows, - this option is ignored. - - \value DontShareAddress Bind the address and port exclusively, so that - no other services are allowed to rebind. By passing this option to - QUdpSocket::bind(), you are guaranteed that on successs, your service - is the only one that listens to the address and port. No services are - allowed to rebind, even if they pass ReuseAddressHint. This option - provides more security than ShareAddress, but on certain operating - systems, it requires you to run the server with administrator privileges. - On Unix and Mac OS X, not sharing is the default behavior for binding - an address and port, so this option is ignored. On Windows, this - option uses the SO_EXCLUSIVEADDRUSE socket option. - - \value ReuseAddressHint Provides a hint to QUdpSocket that it should try - to rebind the service even if the address and port are already bound by - another socket. On Windows, this is equivalent to the SO_REUSEADDR - socket option. On Unix, this option is ignored. - - \value DefaultForPlatform The default option for the current platform. - On Unix and Mac OS X, this is equivalent to (DontShareAddress - + ReuseAddressHint), and on Windows, its equivalent to ShareAddress. -*/ - #include "qhostaddress.h" #include "qnetworkinterface.h" #include "qabstractsocket_p.h" @@ -224,111 +179,6 @@ QUdpSocket::~QUdpSocket() { } -/*! - Binds this socket to the address \a address and the port \a port. - When bound, the signal readyRead() is emitted whenever a UDP - datagram arrives on the specified address and port. This function - is useful to write UDP servers. - - On success, the functions returns true and the socket enters - BoundState; otherwise it returns false. - - The socket is bound using the DefaultForPlatform BindMode. - - \sa readDatagram() -*/ -bool QUdpSocket::bind(const QHostAddress &address, quint16 port) -{ - Q_D(QUdpSocket); - if (!d->ensureInitialized(address, port)) - return false; - - bool result = d_func()->socketEngine->bind(address, port); - d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); - - if (!result) { - d->socketError = d_func()->socketEngine->error(); - setErrorString(d_func()->socketEngine->errorString()); - emit error(d_func()->socketError); - return false; - } - - d->state = BoundState; - d->localAddress = d->socketEngine->localAddress(); - d->localPort = d->socketEngine->localPort(); - - emit stateChanged(d_func()->state); - d_func()->socketEngine->setReadNotificationEnabled(true); - return true; -} - -/*! - \since 4.1 - \overload - - Binds to \a address on port \a port, using the BindMode \a mode. -*/ -bool QUdpSocket::bind(const QHostAddress &address, quint16 port, BindMode mode) -{ - Q_D(QUdpSocket); - if (!d->ensureInitialized(address, port)) - return false; - -#ifdef Q_OS_UNIX - 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_func()->socketEngine->bind(address, port); - d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); - - if (!result) { - d->socketError = d_func()->socketEngine->error(); - setErrorString(d_func()->socketEngine->errorString()); - emit error(d_func()->socketError); - return false; - } - - d->state = BoundState; - d->localAddress = d->socketEngine->localAddress(); - d->localPort = d->socketEngine->localPort(); - - emit stateChanged(d_func()->state); - d_func()->socketEngine->setReadNotificationEnabled(true); - return true; -} - -/*! \overload - - Binds to QHostAddress:Any on port \a port. -*/ -bool QUdpSocket::bind(quint16 port) -{ - return bind(QHostAddress::Any, port); -} - -/*! - \since 4.1 - \overload - - Binds to QHostAddress:Any on port \a port, using the BindMode \a mode. -*/ -bool QUdpSocket::bind(quint16 port, BindMode mode) -{ - return bind(QHostAddress::Any, port, mode); -} - #ifndef QT_NO_NETWORKINTERFACE /*! diff --git a/src/network/socket/qudpsocket.h b/src/network/socket/qudpsocket.h index c477abd07e..068c20b9c4 100644 --- a/src/network/socket/qudpsocket.h +++ b/src/network/socket/qudpsocket.h @@ -60,23 +60,9 @@ class Q_NETWORK_EXPORT QUdpSocket : public QAbstractSocket { Q_OBJECT public: - enum BindFlag { - DefaultForPlatform = 0x0, - ShareAddress = 0x1, - DontShareAddress = 0x2, - ReuseAddressHint = 0x4 - }; - Q_DECLARE_FLAGS(BindMode, BindFlag) - explicit QUdpSocket(QObject *parent = 0); virtual ~QUdpSocket(); - bool bind(const QHostAddress &address, quint16 port); - bool bind(quint16 port = 0); - bool bind(const QHostAddress &address, quint16 port, BindMode mode); - bool bind(quint16 port, BindMode mode); - // ### Qt 5: Merge the bind functions - #ifndef QT_NO_NETWORKINTERFACE bool joinMulticastGroup(const QHostAddress &groupAddress); bool joinMulticastGroup(const QHostAddress &groupAddress, @@ -101,8 +87,6 @@ private: Q_DECLARE_PRIVATE(QUdpSocket) }; -Q_DECLARE_OPERATORS_FOR_FLAGS(QUdpSocket::BindMode) - #endif // QT_NO_UDPSOCKET QT_END_NAMESPACE diff --git a/tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp b/tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp index 46f8111945..e881d56271 100644 --- a/tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp +++ b/tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp @@ -138,6 +138,8 @@ public slots: private slots: void socketsConstructedBeforeEventLoop(); void constructing(); + void bind_data(); + void bind(); void setInvalidSocketDescriptor(); void setSocketDescriptor(); void socketDescriptor(); @@ -479,6 +481,65 @@ void tst_QTcpSocket::constructing() //---------------------------------------------------------------------------------- +void tst_QTcpSocket::bind_data() +{ + QTest::addColumn("stringAddr"); + QTest::addColumn("successExpected"); + QTest::addColumn("stringExpectedLocalAddress"); + + // iterate all interfaces, add all addresses on them as test data + QList interfaces = QNetworkInterface::allInterfaces(); + foreach (const QNetworkInterface &interface, interfaces) { + if (!interface.isValid()) + continue; + + foreach (const QNetworkAddressEntry &entry, interface.addressEntries()) { + if (entry.ip().isInSubnet(QHostAddress::parseSubnet("fe80::/10"))) + 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; + } + } + + // additionally, try bind to known-bad addresses, and make sure this doesn't work + // these ranges are guarenteed to be reserved for 'documentation purposes', + // and thus, should be unused in the real world. Not that I'm assuming the + // world is full of competent administrators, or anything. + QStringList knownBad; + knownBad << "198.51.100.1"; + knownBad << "2001:0DB8::1"; + foreach (const QString &badAddress, knownBad) { + QTest::newRow(badAddress.toLatin1().constData()) << badAddress << false << QString(); + } +} + +void tst_QTcpSocket::bind() +{ + QFETCH(QString, stringAddr); + QFETCH(bool, successExpected); + QFETCH(QString, stringExpectedLocalAddress); + + QHostAddress addr(stringAddr); + QHostAddress expectedLocalAddress(stringExpectedLocalAddress); + + QTcpSocket *socket = newSocket(); + qDebug() << "Binding " << addr; + + if (successExpected) { + QVERIFY2(socket->bind(addr), qPrintable(socket->errorString())); + } else { + QVERIFY(!socket->bind(addr)); + } + + QCOMPARE(socket->localAddress(), expectedLocalAddress); + + delete socket; +} + +//---------------------------------------------------------------------------------- + + void tst_QTcpSocket::setInvalidSocketDescriptor() { QTcpSocket *socket = newSocket();