From aaa187cd9951b71127c11c375e6a3954a187e1d3 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Sun, 20 Aug 2017 15:50:41 -0700 Subject: QAbstractSocket: Add socketOption for the Path MTU This allow retrieving the value of the known PMTU for the current socket. This works on Linux (IPv6 and IPv4) and FreeBSD (IPv6 only) -- the other OSes don't have the necessary API. Note: do we need add IP_MTU_DISCOVER? Change-Id: I6e9274c1e7444ad48c81fffd14dcaf97a18ce335 Reviewed-by: Edward Welbourne --- src/network/socket/qabstractsocket.cpp | 13 +++++ src/network/socket/qabstractsocket.h | 3 +- src/network/socket/qabstractsocketengine_p.h | 3 +- src/network/socket/qnativesocketengine_unix.cpp | 30 +++++++++++ src/network/socket/qnativesocketengine_win.cpp | 15 ++++-- src/network/socket/qnativesocketengine_winrt.cpp | 2 + .../network/kernel/qnetworkinterface/BLACKLIST | 2 - .../qnetworkinterface/tst_qnetworkinterface.cpp | 62 ++++++++++++++++++++-- 8 files changed, 118 insertions(+), 12 deletions(-) delete mode 100644 tests/auto/network/kernel/qnetworkinterface/BLACKLIST diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp index 6d47540b75..98baa0c047 100644 --- a/src/network/socket/qabstractsocket.cpp +++ b/src/network/socket/qabstractsocket.cpp @@ -387,6 +387,11 @@ (see \l{QAbstractSocket::}{setReadBufferSize()}). This enum value has been introduced in Qt 5.3. + \value PathMtuSocketOption Retrieves the Path Maximum Transmission Unit + (PMTU) value currently known by the IP stack, if any. Some IP stacks also + allow setting the MTU for transmission. + This enum value was introduced in Qt 5.11. + Possible values for \e{TypeOfServiceOption} are: \table @@ -2023,6 +2028,10 @@ void QAbstractSocket::setSocketOption(QAbstractSocket::SocketOption option, cons case ReceiveBufferSizeSocketOption: d_func()->socketEngine->setOption(QAbstractSocketEngine::ReceiveBufferSocketOption, value.toInt()); break; + + case PathMtuSocketOption: + d_func()->socketEngine->setOption(QAbstractSocketEngine::PathMtuInformation, value.toInt()); + break; } } @@ -2065,6 +2074,10 @@ QVariant QAbstractSocket::socketOption(QAbstractSocket::SocketOption option) case ReceiveBufferSizeSocketOption: ret = d_func()->socketEngine->option(QAbstractSocketEngine::ReceiveBufferSocketOption); break; + + case PathMtuSocketOption: + ret = d_func()->socketEngine->option(QAbstractSocketEngine::PathMtuInformation); + break; } if (ret == -1) return QVariant(); diff --git a/src/network/socket/qabstractsocket.h b/src/network/socket/qabstractsocket.h index ba499ddf7d..6d5e57ac52 100644 --- a/src/network/socket/qabstractsocket.h +++ b/src/network/socket/qabstractsocket.h @@ -120,7 +120,8 @@ public: MulticastLoopbackOption, // IP_MULTICAST_LOOPBACK TypeOfServiceOption, //IP_TOS SendBufferSizeSocketOption, //SO_SNDBUF - ReceiveBufferSizeSocketOption //SO_RCVBUF + ReceiveBufferSizeSocketOption, //SO_RCVBUF + PathMtuSocketOption // IP_MTU }; Q_ENUM(SocketOption) enum BindFlag { diff --git a/src/network/socket/qabstractsocketengine_p.h b/src/network/socket/qabstractsocketengine_p.h index 0cb519ce90..b15dd73c96 100644 --- a/src/network/socket/qabstractsocketengine_p.h +++ b/src/network/socket/qabstractsocketengine_p.h @@ -105,7 +105,8 @@ public: TypeOfServiceOption, ReceivePacketInformation, ReceiveHopLimit, - MaxStreamsSocketOption + MaxStreamsSocketOption, + PathMtuInformation }; enum PacketHeaderOption { diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp index cb0a521360..b380b0f7d6 100644 --- a/src/network/socket/qnativesocketengine_unix.cpp +++ b/src/network/socket/qnativesocketengine_unix.cpp @@ -223,6 +223,20 @@ static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt, #ifdef IP_RECVTTL // IP_RECVTTL is a non-standard extension supported on some OS level = IPPROTO_IP; n = IP_RECVTTL; +#endif + } + break; + + case QNativeSocketEngine::PathMtuInformation: + if (socketProtocol == QAbstractSocket::IPv6Protocol || socketProtocol == QAbstractSocket::AnyIPProtocol) { +#ifdef IPV6_MTU + level = IPPROTO_IPV6; + n = IPV6_MTU; +#endif + } else { +#ifdef IP_MTU + level = IPPROTO_IP; + n = IP_MTU; #endif } break; @@ -331,6 +345,20 @@ int QNativeSocketEnginePrivate::option(QNativeSocketEngine::SocketOption opt) co return -1; } + case QNativeSocketEngine::PathMtuInformation: +#if defined(IPV6_PATHMTU) && !defined(IPV6_MTU) + // Prefer IPV6_MTU (handled by convertToLevelAndOption), if available + // (Linux); fall back to IPV6_PATHMTU otherwise (FreeBSD): + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + ip6_mtuinfo mtuinfo; + QT_SOCKOPTLEN_T len = sizeof(mtuinfo); + if (::getsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &len) == 0) + return int(mtuinfo.ip6m_mtu); + return -1; + } +#endif + break; + default: break; } @@ -420,6 +448,8 @@ bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt } #endif + if (n == -1) + return false; return ::setsockopt(socketDescriptor, level, n, (char *) &v, sizeof(v)) == 0; } diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp index 1ec3df842f..fb31607e2c 100644 --- a/src/network/socket/qnativesocketengine_win.cpp +++ b/src/network/socket/qnativesocketengine_win.cpp @@ -209,7 +209,7 @@ static inline void qt_socket_getPortAndAddress(SOCKET socketDescriptor, const qt static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt, QAbstractSocket::NetworkLayerProtocol socketProtocol, int &level, int &n) { - n = 0; + n = -1; level = SOL_SOCKET; // default switch (opt) { @@ -281,6 +281,9 @@ static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt, n = IP_HOPLIMIT; } break; + + case QAbstractSocketEngine::PathMtuInformation: + break; // not supported on Windows } } @@ -471,9 +474,11 @@ int QNativeSocketEnginePrivate::option(QNativeSocketEngine::SocketOption opt) co QT_SOCKOPTLEN_T len = sizeof(v); convertToLevelAndOption(opt, socketProtocol, level, n); - if (getsockopt(socketDescriptor, level, n, (char *) &v, &len) == 0) - return v; - WS_ERROR_DEBUG(WSAGetLastError()); + if (n != -1) { + if (getsockopt(socketDescriptor, level, n, (char *) &v, &len) == 0) + return v; + WS_ERROR_DEBUG(WSAGetLastError()); + } return -1; } @@ -514,6 +519,8 @@ bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt int n, level; convertToLevelAndOption(opt, socketProtocol, level, n); + if (n == -1) + return false; if (::setsockopt(socketDescriptor, level, n, (char*)&v, sizeof(v)) != 0) { WS_ERROR_DEBUG(WSAGetLastError()); return false; diff --git a/src/network/socket/qnativesocketengine_winrt.cpp b/src/network/socket/qnativesocketengine_winrt.cpp index 0baf5c9e21..9df5f0c500 100644 --- a/src/network/socket/qnativesocketengine_winrt.cpp +++ b/src/network/socket/qnativesocketengine_winrt.cpp @@ -1514,6 +1514,7 @@ int QNativeSocketEnginePrivate::option(QAbstractSocketEngine::SocketOption opt) case QAbstractSocketEngine::MulticastLoopbackOption: case QAbstractSocketEngine::TypeOfServiceOption: case QAbstractSocketEngine::MaxStreamsSocketOption: + case QAbstractSocketEngine::PathMtuInformation: default: return -1; } @@ -1573,6 +1574,7 @@ bool QNativeSocketEnginePrivate::setOption(QAbstractSocketEngine::SocketOption o case QAbstractSocketEngine::MulticastLoopbackOption: case QAbstractSocketEngine::TypeOfServiceOption: case QAbstractSocketEngine::MaxStreamsSocketOption: + case QAbstractSocketEngine::PathMtuInformation: default: return false; } diff --git a/tests/auto/network/kernel/qnetworkinterface/BLACKLIST b/tests/auto/network/kernel/qnetworkinterface/BLACKLIST deleted file mode 100644 index 23bb688d9a..0000000000 --- a/tests/auto/network/kernel/qnetworkinterface/BLACKLIST +++ /dev/null @@ -1,2 +0,0 @@ -[localAddress] -linux diff --git a/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp b/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp index 93dd73d64a..0b4ed4870d 100644 --- a/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp +++ b/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp @@ -40,6 +40,8 @@ #include "../../../network-settings.h" #include "emulationdetector.h" +Q_DECLARE_METATYPE(QHostAddress) + class tst_QNetworkInterface : public QObject { Q_OBJECT @@ -57,6 +59,7 @@ private slots: void consistencyCheck(); void loopbackIPv4(); void loopbackIPv6(); + void localAddress_data(); void localAddress(); void interfaceFromXXX_data(); void interfaceFromXXX(); @@ -210,18 +213,69 @@ void tst_QNetworkInterface::loopbackIPv6() QList all = QNetworkInterface::allAddresses(); QVERIFY(all.contains(QHostAddress(QHostAddress::LocalHostIPv6))); } +void tst_QNetworkInterface::localAddress_data() +{ + QTest::addColumn("target"); + + QTest::newRow("localhost-ipv4") << QHostAddress(QHostAddress::LocalHost); + if (isIPv6Working()) + QTest::newRow("localhost-ipv6") << QHostAddress(QHostAddress::LocalHostIPv6); + + QTest::newRow("test-server") << QtNetworkSettings::serverIP(); + + // Since we don't actually transmit anything, we can list any IPv4 address + // and it should work. But we're using a linklocal address so that this + // test can pass even machines that failed to reach a DHCP server. + QTest::newRow("linklocal-ipv4") << QHostAddress("169.254.0.1"); + + if (isIPv6Working()) { + // On the other hand, we can't list just any IPv6 here. It's very + // likely that this machine has not received a route via ICMPv6-RA or + // DHCPv6, so it won't have a global route. On some OSes, IPv6 may be + // enabled per interface, so we need to know which ones work. + const QList addrs = QNetworkInterface::allAddresses(); + for (const QHostAddress &addr : addrs) { + QString scope = addr.scopeId(); + if (scope.isEmpty()) + continue; + QTest::addRow("linklocal-ipv6-%s", qPrintable(scope)) + << QHostAddress("fe80::1234%" + scope); + } + } +} void tst_QNetworkInterface::localAddress() { + QFETCH(QHostAddress, target); QUdpSocket socket; - socket.connectToHost(QtNetworkSettings::serverName(), 80); + socket.connectToHost(target, 80); QVERIFY(socket.waitForConnected(5000)); QHostAddress local = socket.localAddress(); - // test that we can find the address that QUdpSocket reported - QList all = QNetworkInterface::allAddresses(); - QVERIFY(all.contains(local)); + // find the interface that contains the address QUdpSocket reported + QList ifaces = QNetworkInterface::allInterfaces(); + const QNetworkInterface *outgoingIface = nullptr; + for (const QNetworkInterface &iface : ifaces) { + QList addrs = iface.addressEntries(); + for (const QNetworkAddressEntry &entry : addrs) { + if (entry.ip() == local) { + outgoingIface = &iface; + break; + } + } + if (outgoingIface) + break; + } + QVERIFY(outgoingIface); + + // we get QVariant() if the QNativeSocketEngine doesn't know how to get the PMTU + int pmtu = socket.socketOption(QAbstractSocket::PathMtuSocketOption).toInt(); + qDebug() << "Connected to" << target.toString() << "via interface" << outgoingIface->name() + << "pmtu" << pmtu; + + // check that the Path MTU is less than or equal the interface's MTU + QVERIFY(pmtu <= outgoingIface->maxTransmissionUnit()); } void tst_QNetworkInterface::interfaceFromXXX_data() -- cgit v1.2.3