From a93437342b9bf60c24a0d339244e8a0b308d4a7f Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Tue, 8 Aug 2017 16:40:29 -0700 Subject: QNetworkInterface: add support for extracting address lifetime [ChangeLog][QtNetwork][QNetworkInterface] Added preferredLifetime() and validityLifetime() to QNetworkAddressEntry that report the remaining lifetime of the address in the network interface. Change-Id: I292b84e2193979446e43344b0727642812cba630 Reviewed-by: Edward Welbourne --- src/network/kernel/qnetworkinterface.cpp | 116 +++++++++++++++++++++ src/network/kernel/qnetworkinterface.h | 10 +- src/network/kernel/qnetworkinterface_linux.cpp | 12 +++ src/network/kernel/qnetworkinterface_p.h | 5 + src/network/kernel/qnetworkinterface_unix.cpp | 60 ++++++++++- src/network/kernel/qnetworkinterface_win.cpp | 8 ++ .../qnetworkinterface/tst_qnetworkinterface.cpp | 16 +++ 7 files changed, 225 insertions(+), 2 deletions(-) diff --git a/src/network/kernel/qnetworkinterface.cpp b/src/network/kernel/qnetworkinterface.cpp index 92525d2c86..e546c8d949 100644 --- a/src/network/kernel/qnetworkinterface.cpp +++ b/src/network/kernel/qnetworkinterface.cpp @@ -334,6 +334,122 @@ void QNetworkAddressEntry::setBroadcast(const QHostAddress &newBroadcast) d->broadcast = newBroadcast; } +/*! + \since 5.11 + + Returns \c true if the address lifetime is known, \c false if not. If the + lifetime is not known, both preferredLifetime() and validityLifetime() will + return QDeadlineTimer::Forever. + + \sa preferredLifetime(), validityLifetime(), setAddressLifetime(), clearAddressLifetime() +*/ +bool QNetworkAddressEntry::isLifetimeKnown() const +{ + return d->lifetimeKnown; +} + +/*! + \since 5.11 + + Returns the deadline when this address becomes deprecated (no longer + preferred), if known. If the address lifetime is not known (see + isLifetimeKnown()), this function always returns QDeadlineTimer::Forever. + + While an address is preferred, it may be used by the operating system as + the source address for new, outgoing packets. After it becomes deprecated, + it will remain valid for incoming packets for a while longer until finally + removed (see validityLifetime()). + + \sa validityLifetime(), isLifetimeKnown(), setAddressLifetime(), clearAddressLifetime() +*/ +QDeadlineTimer QNetworkAddressEntry::preferredLifetime() const +{ + return d->preferredLifetime; +} + +/*! + \since 5.11 + + Returns the deadline when this address becomes invalid and will be removed + from the networking stack, if known. If the address lifetime is not known + (see isLifetimeKnown()), this function always returns + QDeadlineTimer::Forever. + + While an address is valid, it will be accepted by the operating system as a + valid destination address for this machine. Whether it is used as a source + address for new, outgoing packets is controlled by, among other rules, the + preferred lifetime (see preferredLifetime()). + + \sa preferredLifetime(), isLifetimeKnown(), setAddressLifetime(), clearAddressLifetime() +*/ +QDeadlineTimer QNetworkAddressEntry::validityLifetime() const +{ + return d->validityLifetime; +} + +/*! + \since 5.11 + + Sets both the preferred and valid lifetimes for this address to the \a + preferred and \a validity deadlines, respectively. After this call, + isLifetimeKnown() will return \c true, even if both parameters are + QDeadlineTimer::Forever. + + \sa preferredLifetime(), validityLifetime(), isLifetimeKnown(), clearAddressLifetime() +*/ +void QNetworkAddressEntry::setAddressLifetime(QDeadlineTimer preferred, QDeadlineTimer validity) +{ + d->preferredLifetime = preferred; + d->validityLifetime = validity; + d->lifetimeKnown = true; +} + +/*! + \since 5.11 + + Resets both the preferred and valid lifetimes for this address. After this + call, isLifetimeKnown() will return \c false. + + \sa preferredLifetime(), validityLifetime(), isLifetimeKnown(), setAddressLifetime() +*/ +void QNetworkAddressEntry::clearAddressLifetime() +{ + d->preferredLifetime = QDeadlineTimer::Forever; + d->validityLifetime = QDeadlineTimer::Forever; + d->lifetimeKnown = false; +} + +/*! + \since 5.11 + + Returns \c true if this address is permanent on this interface, \c false if + it's temporary. A permenant address is one which has no expiration time and + is often static (manually configured). + + If this information could not be determined, this function returns \c true. + + \note Depending on the operating system and the networking configuration + tool, it is possible for a temporary address to be interpreted as + permanent, if the tool did not inform the details correctly to the + operating system. + + \sa isLifetimeKnown(), validityLifetime(), isTemporary() +*/ +bool QNetworkAddressEntry::isPermanent() const +{ + return d->validityLifetime.isForever(); +} + +/*! + \fn bool QNetworkAddressEntry::isTemporary() const + \since 5.11 + + Returns \c true if this address is temporary on this interface, \c false if + it's permanent. + + \sa isLifetimeKnown(), validityLifetime(), isPermanent() +*/ + /*! \class QNetworkInterface \brief The QNetworkInterface class provides a listing of the host's IP diff --git a/src/network/kernel/qnetworkinterface.h b/src/network/kernel/qnetworkinterface.h index 7036117e2d..d39615e430 100644 --- a/src/network/kernel/qnetworkinterface.h +++ b/src/network/kernel/qnetworkinterface.h @@ -49,7 +49,7 @@ QT_BEGIN_NAMESPACE - +class QDeadlineTimer; template class QList; class QNetworkAddressEntryPrivate; @@ -81,6 +81,14 @@ public: QHostAddress broadcast() const; void setBroadcast(const QHostAddress &newBroadcast); + bool isLifetimeKnown() const; + QDeadlineTimer preferredLifetime() const; + QDeadlineTimer validityLifetime() const; + void setAddressLifetime(QDeadlineTimer preferred, QDeadlineTimer validity); + void clearAddressLifetime(); + bool isPermanent() const; + bool isTemporary() const { return !isPermanent(); } + private: QScopedPointer d; }; diff --git a/src/network/kernel/qnetworkinterface_linux.cpp b/src/network/kernel/qnetworkinterface_linux.cpp index a985342286..cc66c310cb 100644 --- a/src/network/kernel/qnetworkinterface_linux.cpp +++ b/src/network/kernel/qnetworkinterface_linux.cpp @@ -386,6 +386,18 @@ static void getAddresses(int sock, char *buf, QList entry.setBroadcast(makeAddress(payloadPtr, payloadLen)); break; + case IFA_CACHEINFO: + if (size_t(payloadLen) >= sizeof(ifa_cacheinfo)) { + auto cacheinfo = reinterpret_cast(payloadPtr); + auto toDeadline = [](quint32 lifetime) -> QDeadlineTimer { + if (lifetime == quint32(-1)) + return QDeadlineTimer::Forever; + return QDeadlineTimer(lifetime * 1000); + }; + entry.setAddressLifetime(toDeadline(cacheinfo->ifa_prefered), toDeadline(cacheinfo->ifa_valid)); + } + break; + case IFA_FLAGS: Q_ASSERT(payloadLen == 4); flags = qFromUnaligned(payloadPtr); diff --git a/src/network/kernel/qnetworkinterface_p.h b/src/network/kernel/qnetworkinterface_p.h index 44b9b013bc..305a8fe020 100644 --- a/src/network/kernel/qnetworkinterface_p.h +++ b/src/network/kernel/qnetworkinterface_p.h @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -70,7 +71,11 @@ class QNetworkAddressEntryPrivate public: QHostAddress address; QHostAddress broadcast; + QDeadlineTimer preferredLifetime = QDeadlineTimer::Forever; + QDeadlineTimer validityLifetime = QDeadlineTimer::Forever; + QNetmask netmask; + bool lifetimeKnown = false; }; class QNetworkInterfacePrivate: public QSharedData diff --git a/src/network/kernel/qnetworkinterface_unix.cpp b/src/network/kernel/qnetworkinterface_unix.cpp index fb94a2185d..7733f073d0 100644 --- a/src/network/kernel/qnetworkinterface_unix.cpp +++ b/src/network/kernel/qnetworkinterface_unix.cpp @@ -46,6 +46,10 @@ #ifndef QT_NO_NETWORKINTERFACE +#if defined(QT_NO_CLOCK_MONOTONIC) +# include "qdatetime.h" +#endif + #if defined(QT_LINUXBASE) # define QT_NO_GETIFADDRS #endif @@ -381,11 +385,19 @@ static QList createInterfaces(ifaddrs *rawList) return interfaces; } +static void getAddressExtraInfo(QNetworkAddressEntry *entry, struct sockaddr *sa, const char *ifname) +{ + Q_UNUSED(entry); + Q_UNUSED(sa); + Q_UNUSED(ifname) +} + # elif defined(Q_OS_BSD4) QT_BEGIN_INCLUDE_NAMESPACE # include # include # include +# include QT_END_INCLUDE_NAMESPACE static int openSocket(int &socket) @@ -471,6 +483,46 @@ static QList createInterfaces(ifaddrs *rawList) return interfaces; } +static void getAddressExtraInfo(QNetworkAddressEntry *entry, struct sockaddr *sa, const char *ifname) +{ + // get IPv6 address lifetimes + if (sa->sa_family != AF_INET6) + return; + + struct in6_ifreq ifr; + + int s6 = qt_safe_socket(AF_INET6, SOCK_DGRAM, 0); + if (Q_UNLIKELY(s6 < 0)) { + qErrnoWarning("QNetworkInterface: could not create IPv6 socket"); + return; + } + + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + + // get lifetimes + ifr.ifr_addr = *reinterpret_cast(sa); + if (qt_safe_ioctl(s6, SIOCGIFALIFETIME_IN6, &ifr) < 0) { + qt_safe_close(s6); + return; + } + qt_safe_close(s6); + + auto toDeadline = [](time_t when) { + QDeadlineTimer deadline = QDeadlineTimer::Forever; + if (when) { +#if defined(QT_NO_CLOCK_MONOTONIC) + // no monotonic clock + deadline.setPreciseRemainingTime(when - QDateTime::currentSecsSinceEpoch()); +#else + deadline.setPreciseDeadline(when); +#endif + } + return deadline; + }; + entry->setAddressLifetime(toDeadline(ifr.ifr_ifru.ifru_lifetime.ia6t_preferred), + toDeadline(ifr.ifr_ifru.ifru_lifetime.ia6t_expire)); +} + # else // Generic version static QList createInterfaces(ifaddrs *rawList) @@ -502,9 +554,14 @@ static QList createInterfaces(ifaddrs *rawList) return interfaces; } +static void getAddressExtraInfo(QNetworkAddressEntry *entry, struct sockaddr *sa, const char *ifname) +{ + Q_UNUSED(entry); + Q_UNUSED(sa); + Q_UNUSED(ifname) +} # endif - static QList interfaceListing() { QList interfaces; @@ -553,6 +610,7 @@ static QList interfaceListing() entry.setNetmask(addressFromSockaddr(ptr->ifa_netmask, iface->index, iface->name)); if (iface->flags & QNetworkInterface::CanBroadcast) entry.setBroadcast(addressFromSockaddr(ptr->ifa_broadaddr, iface->index, iface->name)); + getAddressExtraInfo(&entry, ptr->ifa_addr, name.latin1()); iface->addressEntries << entry; } diff --git a/src/network/kernel/qnetworkinterface_win.cpp b/src/network/kernel/qnetworkinterface_win.cpp index f0c0ba2126..34d253a6d2 100644 --- a/src/network/kernel/qnetworkinterface_win.cpp +++ b/src/network/kernel/qnetworkinterface_win.cpp @@ -222,6 +222,14 @@ static QList interfaceListing() QNetworkAddressEntry entry; entry.setIp(addressFromSockaddr(addr->Address.lpSockaddr)); entry.setPrefixLength(addr->OnLinkPrefixLength); + + auto toDeadline = [](ULONG lifetime) -> QDeadlineTimer { + if (lifetime == 0xffffffffUL) + return QDeadlineTimer::Forever; + return QDeadlineTimer(lifetime * 1000); + }; + entry.setAddressLifetime(toDeadline(addr->ValidLifetime), toDeadline(addr->PreferredLifetime)); + iface->addressEntries << entry; } } diff --git a/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp b/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp index 0a15da71f8..95dbfc749a 100644 --- a/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp +++ b/tests/auto/network/kernel/qnetworkinterface/tst_qnetworkinterface.cpp @@ -152,6 +152,12 @@ void tst_QNetworkInterface::dump() << " (" << qPrintable(e.netmask().toString()) << ')'; if (!e.broadcast().isNull()) s.nospace() << " broadcast " << qPrintable(e.broadcast().toString()); + if (e.isLifetimeKnown()) { +#define printable(l) qPrintable(l.isForever() ? "forever" : QString::fromLatin1("%1ms").arg(l.remainingTime())) + s.nospace() << " preferred:" << printable(e.preferredLifetime()) + << " valid:" << printable(e.validityLifetime()); +#undef printable + } } } } @@ -163,6 +169,7 @@ void tst_QNetworkInterface::consistencyCheck() QVector interfaceIndexes; foreach (const QNetworkInterface &iface, ifaces) { + QVERIFY(iface.isValid()); QVERIFY2(!interfaceNames.contains(iface.name()), "duplicate name = " + iface.name().toLocal8Bit()); interfaceNames << iface.name(); @@ -171,6 +178,15 @@ void tst_QNetworkInterface::consistencyCheck() "duplicate index = " + QByteArray::number(iface.index())); if (iface.index()) interfaceIndexes << iface.index(); + + const QList addresses = iface.addressEntries(); + for (auto entry : addresses) { + QVERIFY(entry.ip().protocol() != QAbstractSocket::UnknownNetworkLayerProtocol); + if (!entry.preferredLifetime().isForever() || !entry.validityLifetime().isForever()) + QVERIFY(entry.isLifetimeKnown()); + if (!entry.validityLifetime().isForever()) + QVERIFY(entry.isTemporary()); + } } } -- cgit v1.2.3