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:
parent
aa494d826a
commit
aaa187cd99
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -105,7 +105,8 @@ public:
|
||||
TypeOfServiceOption,
|
||||
ReceivePacketInformation,
|
||||
ReceiveHopLimit,
|
||||
MaxStreamsSocketOption
|
||||
MaxStreamsSocketOption,
|
||||
PathMtuInformation
|
||||
};
|
||||
|
||||
enum PacketHeaderOption {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
[localAddress]
|
||||
linux
|
@ -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<QHostAddress> all = QNetworkInterface::allAddresses();
|
||||
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()
|
||||
{
|
||||
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<QHostAddress> all = QNetworkInterface::allAddresses();
|
||||
QVERIFY(all.contains(local));
|
||||
// find the interface that contains the address QUdpSocket reported
|
||||
QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces();
|
||||
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()
|
||||
|
Loading…
Reference in New Issue
Block a user