summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShane Kearns <shane.kearns@accenture.com>2012-05-09 17:13:59 +0100
committerQt by Nokia <qt-info@nokia.com>2012-05-24 05:39:37 +0200
commit1573663182afd3d3371c131c564f4aeaf99d63d7 (patch)
treef21438fd53297289bc9b6ba4ae0b49dc4bc072dc
parent5e905619285b538cd94882f44a39fe748206f1fb (diff)
Fix sending UDP packets to link local addresses
When the scope ID is not set, Mac and Windows will not transmit packets to link local addresses. This patch implements setting the scope in the native socket engines and adds a test case. (it was partially implemented already, though UDP specific code paths were missed in the unix engine) Task-number: QTBUG-25634 Change-Id: I23300bdc9856e38458078e913daaa59cd05a74b5 Reviewed-by: Martin Petersson <Martin.Petersson@nokia.com> (back ported from commit 6abc66e076826b24cc3700c3a0702f552ab50429) Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r--src/network/socket/qnativesocketengine_unix.cpp35
-rw-r--r--src/network/socket/qnativesocketengine_win.cpp10
-rw-r--r--tests/auto/qudpsocket/tst_qudpsocket.cpp136
3 files changed, 163 insertions, 18 deletions
diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp
index 0a087d5c15..b7b4f55309 100644
--- a/src/network/socket/qnativesocketengine_unix.cpp
+++ b/src/network/socket/qnativesocketengine_unix.cpp
@@ -873,23 +873,30 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l
#if !defined(QT_NO_IPV6)
struct sockaddr_in6 sockAddrIPv6;
if (host.protocol() == QAbstractSocket::IPv6Protocol) {
- memset(&sockAddrIPv6, 0, sizeof(sockAddrIPv6));
- sockAddrIPv6.sin6_family = AF_INET6;
- sockAddrIPv6.sin6_port = htons(port);
-
- Q_IPV6ADDR tmp = host.toIPv6Address();
- memcpy(&sockAddrIPv6.sin6_addr.s6_addr, &tmp, sizeof(tmp));
- sockAddrSize = sizeof(sockAddrIPv6);
- sockAddrPtr = (struct sockaddr *)&sockAddrIPv6;
+ memset(&sockAddrIPv6, 0, sizeof(sockAddrIPv6));
+ sockAddrIPv6.sin6_family = AF_INET6;
+ sockAddrIPv6.sin6_port = htons(port);
+
+ Q_IPV6ADDR tmp = host.toIPv6Address();
+ memcpy(&sockAddrIPv6.sin6_addr.s6_addr, &tmp, sizeof(tmp));
+ QString scopeid = host.scopeId();
+ bool ok;
+ sockAddrIPv6.sin6_scope_id = scopeid.toInt(&ok);
+#ifndef QT_NO_IPV6IFNAME
+ if (!ok)
+ sockAddrIPv6.sin6_scope_id = ::if_nametoindex(scopeid.toLatin1());
+#endif
+ sockAddrSize = sizeof(sockAddrIPv6);
+ sockAddrPtr = (struct sockaddr *)&sockAddrIPv6;
} else
#endif
if (host.protocol() == QAbstractSocket::IPv4Protocol) {
- memset(&sockAddrIPv4, 0, sizeof(sockAddrIPv4));
- sockAddrIPv4.sin_family = AF_INET;
- sockAddrIPv4.sin_port = htons(port);
- sockAddrIPv4.sin_addr.s_addr = htonl(host.toIPv4Address());
- sockAddrSize = sizeof(sockAddrIPv4);
- sockAddrPtr = (struct sockaddr *)&sockAddrIPv4;
+ memset(&sockAddrIPv4, 0, sizeof(sockAddrIPv4));
+ sockAddrIPv4.sin_family = AF_INET;
+ sockAddrIPv4.sin_port = htons(port);
+ sockAddrIPv4.sin_addr.s_addr = htonl(host.toIPv4Address());
+ sockAddrSize = sizeof(sockAddrIPv4);
+ sockAddrPtr = (struct sockaddr *)&sockAddrIPv4;
}
// ignore the SIGPIPE signal
diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp
index d4b77a63a2..75cc0525c3 100644
--- a/src/network/socket/qnativesocketengine_win.cpp
+++ b/src/network/socket/qnativesocketengine_win.cpp
@@ -176,10 +176,12 @@ static inline void qt_socket_getPortAndAddress(SOCKET socketDescriptor, const qt
Q_IPV6ADDR tmp;
for (int i = 0; i < 16; ++i)
tmp.c[i] = sa6->sin6_addr.qt_s6_addr[i];
- QHostAddress a;
- a.setAddress(tmp);
- if (address)
- *address = a;
+ if (address) {
+ QHostAddress a;
+ a.setAddress(tmp);
+ a.setScopeId(QString::number(sa6->sin6_scope_id));
+ *address = a;
+ }
if (port)
WSANtohs(socketDescriptor, sa6->sin6_port, port);
} else
diff --git a/tests/auto/qudpsocket/tst_qudpsocket.cpp b/tests/auto/qudpsocket/tst_qudpsocket.cpp
index 4bf233edeb..2ccab7e833 100644
--- a/tests/auto/qudpsocket/tst_qudpsocket.cpp
+++ b/tests/auto/qudpsocket/tst_qudpsocket.cpp
@@ -54,6 +54,7 @@
#include <qstringlist.h>
#include "../network-settings.h"
+#include "../qbearertestcommon.h" // for QTRY_VERIFY
#ifndef QT_NO_BEARERMANAGEMENT
#include <QtNetwork/qnetworkconfigmanager.h>
@@ -116,6 +117,8 @@ private slots:
void multicast();
void echo_data();
void echo();
+ void linkLocalIPv6();
+ void linkLocalIPv4();
protected slots:
void empty_readyReadSlot();
@@ -1299,5 +1302,138 @@ void tst_QUdpSocket::echo()
QVERIFY(successes >= 9);
}
+void tst_QUdpSocket::linkLocalIPv6()
+{
+ QFETCH_GLOBAL(bool, setProxy);
+ if (setProxy)
+ return;
+
+ QList <QHostAddress> addresses;
+ QSet <QString> scopes;
+ QHostAddress localMask("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
+ if (iface.flags() & QNetworkInterface::IsUp) {
+ foreach (QNetworkAddressEntry addressEntry, iface.addressEntries()) {
+ QHostAddress addr(addressEntry.ip());
+ if (!addr.scopeId().isEmpty() && addr.isInSubnet(localMask, 64)) {
+ scopes << addr.scopeId();
+ addresses << addr;
+ qDebug() << addr;
+ }
+ }
+ }
+ }
+ if (addresses.isEmpty())
+ QSKIP("No IPv6 link local addresses", SkipSingle);
+
+ QList <QUdpSocket*> sockets;
+ quint16 port = 0;
+ foreach (const QHostAddress& addr, addresses) {
+ QUdpSocket *s = new QUdpSocket;
+ QVERIFY2(s->bind(addr, port), qPrintable(s->errorString()));
+ port = s->localPort(); //bind same port, different networks
+ sockets << s;
+ }
+
+ QUdpSocket neutral;
+ QVERIFY(neutral.bind(QHostAddress(QHostAddress::AnyIPv6), 0));
+ QSignalSpy neutralReadSpy(&neutral, SIGNAL(readyRead()));
+
+ QByteArray testData("hello");
+ QByteArray receiveBuffer("xxxxx");
+ foreach (QUdpSocket *s, sockets) {
+ QSignalSpy spy(s, SIGNAL(readyRead()));
+
+ neutralReadSpy.clear();
+ QVERIFY(s->writeDatagram(testData, s->localAddress(), neutral.localPort()));
+ QTRY_VERIFY(neutralReadSpy.count() > 0); //note may need to accept a firewall prompt
+ QHostAddress from;
+ quint16 fromPort;
+ QCOMPARE((int)neutral.readDatagram(receiveBuffer.data(), receiveBuffer.length(), &from, &fromPort), testData.length());
+ QCOMPARE(from, s->localAddress());
+ QCOMPARE(fromPort, s->localPort());
+ QCOMPARE(receiveBuffer, testData);
+
+ QVERIFY(neutral.writeDatagram(testData, s->localAddress(), s->localPort()));
+ QTRY_VERIFY(spy.count() > 0); //note may need to accept a firewall prompt
+ QCOMPARE((int)s->readDatagram(receiveBuffer.data(), receiveBuffer.length(), &from, &fromPort), testData.length());
+ QCOMPARE(receiveBuffer, testData);
+
+ //sockets bound to other interfaces shouldn't have received anything
+ foreach (QUdpSocket *s2, sockets) {
+ QCOMPARE((int)s2->bytesAvailable(), 0);
+ }
+
+ //Sending to the same address with different scope should normally fail
+ //However it will pass if there is a route between two interfaces,
+ //e.g. connected to a home/office network via wired and wireless interfaces
+ //which is a reasonably common case.
+ //So this is not auto tested.
+ }
+ qDeleteAll(sockets);
+}
+
+void tst_QUdpSocket::linkLocalIPv4()
+{
+ QFETCH_GLOBAL(bool, setProxy);
+ if (setProxy)
+ return;
+
+ QList <QHostAddress> addresses;
+ QHostAddress localMask("169.254.0.0");
+ 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) {
+ foreach (QNetworkAddressEntry addr, iface.addressEntries()) {
+ if (addr.ip().isInSubnet(localMask, 16)) {
+ addresses << addr.ip();
+ qDebug() << addr.ip();
+ }
+ }
+ }
+ }
+ if (addresses.isEmpty())
+ QSKIP("No IPv4 link local addresses", SkipSingle);
+
+ QList <QUdpSocket*> sockets;
+ quint16 port = 0;
+ foreach (const QHostAddress& addr, addresses) {
+ QUdpSocket *s = new QUdpSocket;
+ QVERIFY2(s->bind(addr, port), qPrintable(s->errorString()));
+ port = s->localPort(); //bind same port, different networks
+ sockets << s;
+ }
+
+ QUdpSocket neutral;
+ QVERIFY(neutral.bind(QHostAddress(QHostAddress::Any), 0));
+
+ QByteArray testData("hello");
+ QByteArray receiveBuffer("xxxxx");
+ foreach (QUdpSocket *s, sockets) {
+ QVERIFY(s->writeDatagram(testData, s->localAddress(), neutral.localPort()));
+ QVERIFY(neutral.waitForReadyRead(10000));
+ QHostAddress from;
+ quint16 fromPort;
+ QCOMPARE((int)neutral.readDatagram(receiveBuffer.data(), receiveBuffer.length(), &from, &fromPort), testData.length());
+ QCOMPARE(from, s->localAddress());
+ QCOMPARE(fromPort, s->localPort());
+ QCOMPARE(receiveBuffer, testData);
+
+ QVERIFY(neutral.writeDatagram(testData, s->localAddress(), s->localPort()));
+ QVERIFY(s->waitForReadyRead(10000));
+ QCOMPARE((int)s->readDatagram(receiveBuffer.data(), receiveBuffer.length(), &from, &fromPort), testData.length());
+ QCOMPARE(receiveBuffer, testData);
+
+ //sockets bound to other interfaces shouldn't have received anything
+ foreach (QUdpSocket *s2, sockets) {
+ QCOMPARE((int)s2->bytesAvailable(), 0);
+ }
+ }
+ qDeleteAll(sockets);
+}
+
QTEST_MAIN(tst_QUdpSocket)
#include "tst_qudpsocket.moc"