Allow autobound UDP sockets to send to IPv4 and IPv6
authorShane Kearns <ext-shane.2.kearns@nokia.com>
Thu, 15 Mar 2012 17:58:22 +0000 (17:58 +0000)
committerQt by Nokia <qt-info@nokia.com>
Thu, 22 Mar 2012 17:59:45 +0000 (18:59 +0100)
When writeDatagram is called without first binding the UDP socket,
then bind it as QHostAddress::Any.
This allows the same socket to be used to sent to both IPv4 and
IPv6 destination addresses.

Allowing the OS to autobind the socket inside sendTo() may
result in a single protocol socket.

Task-number: QTBUG-5275
Change-Id: I2b76507e8a8a38369c6eafb61ce4191d1d6cc930
Reviewed-by: Richard J. Moore <rich@kde.org>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Martin Petersson <Martin.Petersson@nokia.com>
src/network/socket/qnativesocketengine_unix.cpp
src/network/socket/qnativesocketengine_win.cpp
src/network/socket/qudpsocket.cpp
tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp

index b7c149c..295d396 100644 (file)
@@ -854,7 +854,8 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l
 
     struct sockaddr_in6 sockAddrIPv6;
     if (host.protocol() == QAbstractSocket::IPv6Protocol
-        || socketProtocol == QAbstractSocket::IPv6Protocol) {
+        || socketProtocol == QAbstractSocket::IPv6Protocol
+        || socketProtocol == QAbstractSocket::AnyIPProtocol) {
     memset(&sockAddrIPv6, 0, sizeof(sockAddrIPv6));
     sockAddrIPv6.sin6_family = AF_INET6;
     sockAddrIPv6.sin6_port = htons(port);
index 93a470c..4bf2a6c 100644 (file)
@@ -202,7 +202,8 @@ void QNativeSocketEnginePrivate::setPortAndAddress(sockaddr_in * sockAddrIPv4, q
 
     if (address.protocol() == QAbstractSocket::IPv6Protocol
         || address.protocol() == QAbstractSocket::AnyIPProtocol
-        || socketProtocol == QAbstractSocket::IPv6Protocol) {
+        || socketProtocol == QAbstractSocket::IPv6Protocol
+        || socketProtocol == QAbstractSocket::AnyIPProtocol) {
         memset(sockAddrIPv6, 0, sizeof(qt_sockaddr_in6));
         sockAddrIPv6->sin6_family = AF_INET6;
         sockAddrIPv6->sin6_scope_id = address.scopeId().toInt();
index a31b16e..ec751c2 100644 (file)
@@ -332,8 +332,10 @@ qint64 QUdpSocket::writeDatagram(const char *data, qint64 size, const QHostAddre
     qDebug("QUdpSocket::writeDatagram(%p, %llu, \"%s\", %i)", data, size,
            address.toString().toLatin1().constData(), port);
 #endif
-    if (!d->ensureInitialized(address))
+    if (!d->doEnsureInitialized(QHostAddress::Any, 0, address))
         return -1;
+    if (state() == UnconnectedState)
+        bind();
 
     qint64 sent = d->socketEngine->writeDatagram(data, size, address, port);
     d->cachedSocketDescriptor = d->socketEngine->socketDescriptor();
index 3dabe67..c53450e 100644 (file)
@@ -92,6 +92,8 @@ private slots:
     void ipv6Loop_data();
     void ipv6Loop();
     void dualStack();
+    void dualStackAutoBinding();
+    void dualStackNoIPv4onV6only();
     void readLine();
     void pendingDatagramSize();
     void writeDatagram();
@@ -490,6 +492,83 @@ void tst_QUdpSocket::dualStack()
 
 }
 
+void tst_QUdpSocket::dualStackAutoBinding()
+{
+    QFETCH_GLOBAL(bool, setProxy);
+    if (setProxy)
+        QSKIP("test server SOCKS proxy doesn't support IPv6");
+    QUdpSocket v4Sock;
+    QVERIFY(v4Sock.bind(QHostAddress(QHostAddress::AnyIPv4), 0));
+
+    QUdpSocket v6Sock;
+    QVERIFY(v6Sock.bind(QHostAddress(QHostAddress::AnyIPv6), 0));
+
+    QByteArray dualData("dual");
+    QHostAddress from;
+    quint16 port;
+    QByteArray buffer;
+    int size;
+
+    {
+        //test an autobound socket can send to both v4 and v6 addresses (v4 first)
+        QUdpSocket dualSock;
+
+        QCOMPARE((int)dualSock.writeDatagram(dualData.constData(), dualData.length(), QHostAddress(QHostAddress::LocalHost), v4Sock.localPort()), dualData.length());
+        QVERIFY(v4Sock.waitForReadyRead(5000));
+        buffer.reserve(100);
+        size = v4Sock.readDatagram(buffer.data(), 100, &from, &port);
+        QCOMPARE((int)size, dualData.length());
+        buffer.resize(size);
+        QCOMPARE(buffer, dualData);
+
+        QCOMPARE((int)dualSock.writeDatagram(dualData.constData(), dualData.length(), QHostAddress(QHostAddress::LocalHostIPv6), v6Sock.localPort()), dualData.length());
+        QVERIFY(v6Sock.waitForReadyRead(5000));
+        buffer.reserve(100);
+        size = v6Sock.readDatagram(buffer.data(), 100, &from, &port);
+        QCOMPARE((int)size, dualData.length());
+        buffer.resize(size);
+        QCOMPARE(buffer, dualData);
+    }
+
+    {
+        //test an autobound socket can send to both v4 and v6 addresses (v6 first)
+        QUdpSocket dualSock;
+
+        QCOMPARE((int)dualSock.writeDatagram(dualData.constData(), dualData.length(), QHostAddress(QHostAddress::LocalHostIPv6), v6Sock.localPort()), dualData.length());
+        QVERIFY(v6Sock.waitForReadyRead(5000));
+        buffer.reserve(100);
+        size = v6Sock.readDatagram(buffer.data(), 100, &from, &port);
+        QCOMPARE((int)size, dualData.length());
+        buffer.resize(size);
+        QCOMPARE(buffer, dualData);
+
+        QCOMPARE((int)dualSock.writeDatagram(dualData.constData(), dualData.length(), QHostAddress(QHostAddress::LocalHost), v4Sock.localPort()), dualData.length());
+        QVERIFY(v4Sock.waitForReadyRead(5000));
+        buffer.reserve(100);
+        size = v4Sock.readDatagram(buffer.data(), 100, &from, &port);
+        QCOMPARE((int)size, dualData.length());
+        buffer.resize(size);
+        QCOMPARE(buffer, dualData);
+    }
+}
+
+void tst_QUdpSocket::dualStackNoIPv4onV6only()
+{
+    QFETCH_GLOBAL(bool, setProxy);
+    if (setProxy)
+        QSKIP("test server SOCKS proxy doesn't support IPv6");
+    QUdpSocket v4Sock;
+    QVERIFY(v4Sock.bind(QHostAddress(QHostAddress::AnyIPv4), 0));
+    QByteArray v4Data("v4");
+
+    QUdpSocket v6Sock;
+    QVERIFY(v6Sock.bind(QHostAddress(QHostAddress::AnyIPv6), 0));
+
+    //test v4 -> v6 (should not be received as this is a v6 only socket)
+    QCOMPARE((int)v4Sock.writeDatagram(v4Data.constData(), v4Data.length(), QHostAddress(QHostAddress::LocalHost), v6Sock.localPort()), v4Data.length());
+    QVERIFY(!v6Sock.waitForReadyRead(1000));
+}
+
 void tst_QUdpSocket::empty_readyReadSlot()
 {
     QTestEventLoop::instance().exitLoop();