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 <edward.welbourne@qt.io>
This commit is contained in:
Thiago Macieira 2017-08-20 15:50:41 -07:00
parent aa494d826a
commit aaa187cd99
8 changed files with 118 additions and 12 deletions

View File

@ -387,6 +387,11 @@
(see \l{QAbstractSocket::}{setReadBufferSize()}). (see \l{QAbstractSocket::}{setReadBufferSize()}).
This enum value has been introduced in Qt 5.3. 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: Possible values for \e{TypeOfServiceOption} are:
\table \table
@ -2023,6 +2028,10 @@ void QAbstractSocket::setSocketOption(QAbstractSocket::SocketOption option, cons
case ReceiveBufferSizeSocketOption: case ReceiveBufferSizeSocketOption:
d_func()->socketEngine->setOption(QAbstractSocketEngine::ReceiveBufferSocketOption, value.toInt()); d_func()->socketEngine->setOption(QAbstractSocketEngine::ReceiveBufferSocketOption, value.toInt());
break; break;
case PathMtuSocketOption:
d_func()->socketEngine->setOption(QAbstractSocketEngine::PathMtuInformation, value.toInt());
break;
} }
} }
@ -2065,6 +2074,10 @@ QVariant QAbstractSocket::socketOption(QAbstractSocket::SocketOption option)
case ReceiveBufferSizeSocketOption: case ReceiveBufferSizeSocketOption:
ret = d_func()->socketEngine->option(QAbstractSocketEngine::ReceiveBufferSocketOption); ret = d_func()->socketEngine->option(QAbstractSocketEngine::ReceiveBufferSocketOption);
break; break;
case PathMtuSocketOption:
ret = d_func()->socketEngine->option(QAbstractSocketEngine::PathMtuInformation);
break;
} }
if (ret == -1) if (ret == -1)
return QVariant(); return QVariant();

View File

@ -120,7 +120,8 @@ public:
MulticastLoopbackOption, // IP_MULTICAST_LOOPBACK MulticastLoopbackOption, // IP_MULTICAST_LOOPBACK
TypeOfServiceOption, //IP_TOS TypeOfServiceOption, //IP_TOS
SendBufferSizeSocketOption, //SO_SNDBUF SendBufferSizeSocketOption, //SO_SNDBUF
ReceiveBufferSizeSocketOption //SO_RCVBUF ReceiveBufferSizeSocketOption, //SO_RCVBUF
PathMtuSocketOption // IP_MTU
}; };
Q_ENUM(SocketOption) Q_ENUM(SocketOption)
enum BindFlag { enum BindFlag {

View File

@ -105,7 +105,8 @@ public:
TypeOfServiceOption, TypeOfServiceOption,
ReceivePacketInformation, ReceivePacketInformation,
ReceiveHopLimit, ReceiveHopLimit,
MaxStreamsSocketOption MaxStreamsSocketOption,
PathMtuInformation
}; };
enum PacketHeaderOption { enum PacketHeaderOption {

View File

@ -223,6 +223,20 @@ static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt,
#ifdef IP_RECVTTL // IP_RECVTTL is a non-standard extension supported on some OS #ifdef IP_RECVTTL // IP_RECVTTL is a non-standard extension supported on some OS
level = IPPROTO_IP; level = IPPROTO_IP;
n = IP_RECVTTL; 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 #endif
} }
break; break;
@ -331,6 +345,20 @@ int QNativeSocketEnginePrivate::option(QNativeSocketEngine::SocketOption opt) co
return -1; 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: default:
break; break;
} }
@ -420,6 +448,8 @@ bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt
} }
#endif #endif
if (n == -1)
return false;
return ::setsockopt(socketDescriptor, level, n, (char *) &v, sizeof(v)) == 0; return ::setsockopt(socketDescriptor, level, n, (char *) &v, sizeof(v)) == 0;
} }

View File

@ -209,7 +209,7 @@ static inline void qt_socket_getPortAndAddress(SOCKET socketDescriptor, const qt
static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt, static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt,
QAbstractSocket::NetworkLayerProtocol socketProtocol, int &level, int &n) QAbstractSocket::NetworkLayerProtocol socketProtocol, int &level, int &n)
{ {
n = 0; n = -1;
level = SOL_SOCKET; // default level = SOL_SOCKET; // default
switch (opt) { switch (opt) {
@ -281,6 +281,9 @@ static void convertToLevelAndOption(QNativeSocketEngine::SocketOption opt,
n = IP_HOPLIMIT; n = IP_HOPLIMIT;
} }
break; 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); QT_SOCKOPTLEN_T len = sizeof(v);
convertToLevelAndOption(opt, socketProtocol, level, n); convertToLevelAndOption(opt, socketProtocol, level, n);
if (getsockopt(socketDescriptor, level, n, (char *) &v, &len) == 0) if (n != -1) {
return v; if (getsockopt(socketDescriptor, level, n, (char *) &v, &len) == 0)
WS_ERROR_DEBUG(WSAGetLastError()); return v;
WS_ERROR_DEBUG(WSAGetLastError());
}
return -1; return -1;
} }
@ -514,6 +519,8 @@ bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt
int n, level; int n, level;
convertToLevelAndOption(opt, socketProtocol, level, n); convertToLevelAndOption(opt, socketProtocol, level, n);
if (n == -1)
return false;
if (::setsockopt(socketDescriptor, level, n, (char*)&v, sizeof(v)) != 0) { if (::setsockopt(socketDescriptor, level, n, (char*)&v, sizeof(v)) != 0) {
WS_ERROR_DEBUG(WSAGetLastError()); WS_ERROR_DEBUG(WSAGetLastError());
return false; return false;

View File

@ -1514,6 +1514,7 @@ int QNativeSocketEnginePrivate::option(QAbstractSocketEngine::SocketOption opt)
case QAbstractSocketEngine::MulticastLoopbackOption: case QAbstractSocketEngine::MulticastLoopbackOption:
case QAbstractSocketEngine::TypeOfServiceOption: case QAbstractSocketEngine::TypeOfServiceOption:
case QAbstractSocketEngine::MaxStreamsSocketOption: case QAbstractSocketEngine::MaxStreamsSocketOption:
case QAbstractSocketEngine::PathMtuInformation:
default: default:
return -1; return -1;
} }
@ -1573,6 +1574,7 @@ bool QNativeSocketEnginePrivate::setOption(QAbstractSocketEngine::SocketOption o
case QAbstractSocketEngine::MulticastLoopbackOption: case QAbstractSocketEngine::MulticastLoopbackOption:
case QAbstractSocketEngine::TypeOfServiceOption: case QAbstractSocketEngine::TypeOfServiceOption:
case QAbstractSocketEngine::MaxStreamsSocketOption: case QAbstractSocketEngine::MaxStreamsSocketOption:
case QAbstractSocketEngine::PathMtuInformation:
default: default:
return false; return false;
} }

View File

@ -1,2 +0,0 @@
[localAddress]
linux

View File

@ -40,6 +40,8 @@
#include "../../../network-settings.h" #include "../../../network-settings.h"
#include "emulationdetector.h" #include "emulationdetector.h"
Q_DECLARE_METATYPE(QHostAddress)
class tst_QNetworkInterface : public QObject class tst_QNetworkInterface : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -57,6 +59,7 @@ private slots:
void consistencyCheck(); void consistencyCheck();
void loopbackIPv4(); void loopbackIPv4();
void loopbackIPv6(); void loopbackIPv6();
void localAddress_data();
void localAddress(); void localAddress();
void interfaceFromXXX_data(); void interfaceFromXXX_data();
void interfaceFromXXX(); void interfaceFromXXX();
@ -210,18 +213,69 @@ void tst_QNetworkInterface::loopbackIPv6()
QList<QHostAddress> all = QNetworkInterface::allAddresses(); QList<QHostAddress> all = QNetworkInterface::allAddresses();
QVERIFY(all.contains(QHostAddress(QHostAddress::LocalHostIPv6))); QVERIFY(all.contains(QHostAddress(QHostAddress::LocalHostIPv6)));
} }
void tst_QNetworkInterface::localAddress_data()
{
QTest::addColumn<QHostAddress>("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<QHostAddress> 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() void tst_QNetworkInterface::localAddress()
{ {
QFETCH(QHostAddress, target);
QUdpSocket socket; QUdpSocket socket;
socket.connectToHost(QtNetworkSettings::serverName(), 80); socket.connectToHost(target, 80);
QVERIFY(socket.waitForConnected(5000)); QVERIFY(socket.waitForConnected(5000));
QHostAddress local = socket.localAddress(); QHostAddress local = socket.localAddress();
// test that we can find the address that QUdpSocket reported // find the interface that contains the address QUdpSocket reported
QList<QHostAddress> all = QNetworkInterface::allAddresses(); QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces();
QVERIFY(all.contains(local)); const QNetworkInterface *outgoingIface = nullptr;
for (const QNetworkInterface &iface : ifaces) {
QList<QNetworkAddressEntry> 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() void tst_QNetworkInterface::interfaceFromXXX_data()