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()}).
|
(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();
|
||||||
|
@ -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 {
|
||||||
|
@ -105,7 +105,8 @@ public:
|
|||||||
TypeOfServiceOption,
|
TypeOfServiceOption,
|
||||||
ReceivePacketInformation,
|
ReceivePacketInformation,
|
||||||
ReceiveHopLimit,
|
ReceiveHopLimit,
|
||||||
MaxStreamsSocketOption
|
MaxStreamsSocketOption,
|
||||||
|
PathMtuInformation
|
||||||
};
|
};
|
||||||
|
|
||||||
enum PacketHeaderOption {
|
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
|
#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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
[localAddress]
|
|
||||||
linux
|
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user