QHostAddress: add more classification functions

[ChangeLog][QtNetwork][QHostAddress] Added isGlobal(), isLinkLocal(),
isSiteLocal(), isUniqueLocalUnicast(), and isBroadcast() classification
functions to complement isLoopback() and isMulticast().

Change-Id: I3868166e5efc45538544fffd14d8fca6e9042c04
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
Thiago Macieira 2017-08-08 14:37:49 -07:00
parent f93003e0c6
commit cf7a97a658
5 changed files with 273 additions and 100 deletions

View File

@ -64,7 +64,6 @@
QT_BEGIN_NAMESPACE
class QHostAddressPrivate : public QSharedData
{
public:
@ -87,6 +86,8 @@ public:
quint32 a; // IPv4 address
qint8 protocol;
AddressClassification classify() const;
friend class QHostAddress;
};
@ -205,6 +206,75 @@ void QHostAddressPrivate::clear()
memset(&a6, 0, sizeof(a6));
}
AddressClassification QHostAddressPrivate::classify() const
{
if (a) {
// This is an IPv4 address or an IPv6 v4-mapped address includes all
// IPv6 v4-compat addresses, except for ::ffff:0.0.0.0 (because `a' is
// zero). See setAddress(quint8*) below, which calls convertToIpv4(),
// for details.
// Source: RFC 5735
if ((a & 0xff000000U) == 0x7f000000U) // 127.0.0.0/8
return LoopbackAddress;
if ((a & 0xf0000000U) == 0xe0000000U) // 224.0.0.0/4
return MulticastAddress;
if ((a & 0xffff0000U) == 0xa9fe0000U) // 169.254.0.0/16
return LinkLocalAddress;
if ((a & 0xff000000U) == 0) // 0.0.0.0/8 except 0.0.0.0 (handled below)
return LocalNetAddress;
if ((a & 0xf0000000U) == 0xf0000000U) { // 240.0.0.0/4
if (a == 0xffffffffU) // 255.255.255.255
return BroadcastAddress;
return UnknownAddress;
}
// Not testing for PrivateNetworkAddress and TestNetworkAddress
// since we don't need them yet.
return GlobalAddress;
}
// As `a' is zero, this address is either ::ffff:0.0.0.0 or a non-v4-mapped IPv6 address.
// Source: https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml
if (a6_64.c[0]) {
quint32 high16 = qFromBigEndian(a6_32.c[0]) >> 16;
switch (high16 >> 8) {
case 0xff: // ff00::/8
return MulticastAddress;
case 0xfe:
switch (high16 & 0xffc0) {
case 0xfec0: // fec0::/10
return SiteLocalAddress;
case 0xfe80: // fe80::/10
return LinkLocalAddress;
default: // fe00::/9
return UnknownAddress;
}
case 0xfd: // fc00::/7
case 0xfc:
return UniqueLocalAddress;
default:
return GlobalAddress;
}
}
quint64 low64 = qFromBigEndian(a6_64.c[1]);
if (low64 == 1) // ::1
return LoopbackAddress;
if (low64 >> 32 == 0xffff) { // ::ffff:0.0.0.0/96
Q_ASSERT(quint32(low64) == 0);
return LocalNetAddress;
}
if (low64) // not ::
return GlobalAddress;
if (protocol == QAbstractSocket::UnknownNetworkLayerProtocol)
return UnknownAddress;
// only :: and 0.0.0.0 remain now
return LocalNetAddress;
}
bool QNetmask::setAddress(const QHostAddress &address)
{
@ -1154,21 +1224,91 @@ QPair<QHostAddress, int> QHostAddress::parseSubnet(const QString &subnet)
*/
bool QHostAddress::isLoopback() const
{
if ((d->a & 0xFF000000) == 0x7F000000)
return true; // v4 range (including IPv6 wrapped IPv4 addresses)
if (d->protocol == QAbstractSocket::IPv6Protocol) {
#ifdef __SSE2__
const __m128i loopback = _mm_setr_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
__m128i ipv6 = _mm_loadu_si128((const __m128i *)d->a6.c);
__m128i cmp = _mm_cmpeq_epi8(ipv6, loopback);
return _mm_movemask_epi8(cmp) == 0xffff;
#else
if (d->a6_64.c[0] != 0 || qFromBigEndian(d->a6_64.c[1]) != 1)
return false;
#endif
return true;
}
return false;
return d->classify() == LoopbackAddress;
}
/*!
\since 5.11
Returns \c true if the address is an IPv4 or IPv6 global address, \c false
otherwise. A global address is an address that is not reserved for
special purposes (like loopback or multicast) or future purposes.
Note that IPv6 unique local unicast addresses are considered global
addresses (see isUniqueLocalUnicast()), as are IPv4 addresses reserved for
local networks by \l {https://tools.ietf.org/html/rfc1918}{RFC 1918}.
Also note that IPv6 site-local addresses are deprecated and should be
considered as global in new applications. This function returns true for
site-local addresses too.
\sa isLoopback(), isSiteLocal(), isUniqueLocalUnicast()
*/
bool QHostAddress::isGlobal() const
{
return d->classify() & GlobalAddress; // GlobalAddress is a bit
}
/*!
\since 5.11
Returns \c true if the address is an IPv4 or IPv6 link-local address, \c
false otherwise.
An IPv4 link-local address is an address in the network 169.254.0.0/16. An
IPv6 link-local address is one in the network fe80::/10. See the
\l{https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml}{IANA
IPv6 Address Space} registry for more information.
\sa isLoopback(), isGlobal(). isMulticast(), isSiteLocal(), isUniqueLocalUnicast()
*/
bool QHostAddress::isLinkLocal() const
{
return d->classify() == LinkLocalAddress;
}
/*!
\since 5.11
Returns \c true if the address is an IPv6 site-local address, \c
false otherwise.
An IPv6 site-local address is one in the network fec0::/10. See the
\l{https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml}{IANA
IPv6 Address Space} registry for more information.
IPv6 site-local addresses are deprecated and should not be depended upon in
new applications. New applications should not depend on this function and
should consider site-local addresses the same as global (which is why
isGlobal() also returns true). Site-local addresses were replaced by Unique
Local Addresses (ULA).
\sa isLoopback(), isGlobal(), isMulticast(), isLinkLocal(), isUniqueLocalUnicast()
*/
bool QHostAddress::isSiteLocal() const
{
return d->classify() == SiteLocalAddress;
}
/*!
\since 5.11
Returns \c true if the address is an IPv6 unique local unicast address, \c
false otherwise.
An IPv6 unique local unicast address is one in the network fc00::/7. See the
\l{https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml}
{IANA IPv6 Address Space} registry for more information.
Note that Unique local unicast addresses count as global addresses too. RFC
4193 says that, in practice, "applications may treat these addresses like
global scoped addresses." Only routers need care about the distinction.
\sa isLoopback(), isGlobal(), isMulticast(), isLinkLocal(), isUniqueLocalUnicast()
*/
bool QHostAddress::isUniqueLocalUnicast() const
{
return d->classify() == UniqueLocalAddress;
}
/*!
@ -1176,14 +1316,29 @@ bool QHostAddress::isLoopback() const
Returns \c true if the address is an IPv4 or IPv6 multicast address, \c
false otherwise.
\sa isLoopback(), isGlobal(), isLinkLocal(), isSiteLocal(), isUniqueLocalUnicast()
*/
bool QHostAddress::isMulticast() const
{
if ((d->a & 0xF0000000) == 0xE0000000)
return true; // 224.0.0.0-239.255.255.255 (including v4-mapped IPv6 addresses)
if (d->protocol == QAbstractSocket::IPv6Protocol)
return d->a6.c[0] == 0xff;
return false;
return d->classify() == MulticastAddress;
}
/*!
\since 5.11
Returns \c true if the address is the IPv4 broadcast address, \c false
otherwise. The IPv4 broadcast address is 255.255.255.255.
Note that this function does not return true for an IPv4 network's local
broadcast address. For that, please use \l QNetworkInterface to obtain the
broadcast addresses of the local machine.
\sa isLoopback(), isGlobal(), isMulticast(), isLinkLocal(), isUniqueLocalUnicast()
*/
bool QHostAddress::isBroadcast() const
{
return d->classify() == BroadcastAddress;
}
#ifndef QT_NO_DEBUG_STREAM

View File

@ -148,7 +148,12 @@ public:
bool isInSubnet(const QPair<QHostAddress, int> &subnet) const;
bool isLoopback() const;
bool isGlobal() const;
bool isLinkLocal() const;
bool isSiteLocal() const;
bool isUniqueLocalUnicast() const;
bool isMulticast() const;
bool isBroadcast() const;
static QPair<QHostAddress, int> parseSubnet(const QString &subnet);

View File

@ -57,6 +57,22 @@
QT_BEGIN_NAMESPACE
enum AddressClassification {
LoopbackAddress = 1,
LocalNetAddress, // RFC 1122
LinkLocalAddress, // RFC 4291 (v6), RFC 3927 (v4)
MulticastAddress, // RFC 4291 (v6), RFC 3171 (v4)
BroadcastAddress, // RFC 919, 922
GlobalAddress = 16,
TestNetworkAddress, // RFC 3849 (v6), RFC 5737 (v4),
PrivateNetworkAddress, // RFC 1918
UniqueLocalAddress, // RFC 4193
SiteLocalAddress, // RFC 4291 (deprecated by RFC 3879, should be treated as global)
UnknownAddress = 0 // unclassified or reserved
};
class QNetmask
{
// stores 0-32 for IPv4, 0-128 for IPv6, or 255 for invalid

View File

@ -2,7 +2,6 @@ CONFIG += testcase
TARGET = tst_qhostaddress
SOURCES += tst_qhostaddress.cpp
QT = core network testlib
QT = core network-private testlib
win32:LIBS += -lws2_32

View File

@ -27,9 +27,10 @@
**
****************************************************************************/
#include <qhostaddress.h>
#include <private/qhostaddress_p.h>
#include <qcoreapplication.h>
#include <QtTest/QtTest>
#include <qhostaddress.h>
#include <qplatformdefs.h>
#include <qdebug.h>
#include <qhash.h>
@ -46,6 +47,7 @@
# include <netinet/in.h>
#endif
Q_DECLARE_METATYPE(AddressClassification)
Q_DECLARE_METATYPE(QHostAddress::SpecialAddress)
class tst_QHostAddress : public QObject
@ -75,10 +77,8 @@ private slots:
void parseSubnet();
void isInSubnet_data();
void isInSubnet();
void isLoopback_data();
void isLoopback();
void isMulticast_data();
void isMulticast();
void classification_data();
void classification();
void convertv4v6_data();
void convertv4v6();
};
@ -667,90 +667,88 @@ void tst_QHostAddress::isInSubnet()
QTEST(address.isInSubnet(prefix, prefixLength), "result");
}
void tst_QHostAddress::isLoopback_data()
void tst_QHostAddress::classification_data()
{
QTest::addColumn<QHostAddress>("address");
QTest::addColumn<bool>("result");
QTest::addColumn<AddressClassification>("result");
QTest::newRow("default") << QHostAddress() << false;
QTest::newRow("invalid") << QHostAddress("&&&") << false;
QTest::newRow("default") << QHostAddress() << UnknownAddress;
QTest::newRow("invalid") << QHostAddress("&&&") << UnknownAddress;
QTest::newRow("ipv6_loop") << QHostAddress(QHostAddress::LocalHostIPv6) << true;
QTest::newRow("::1") << QHostAddress("::1") << true;
QTest::newRow("Any") << QHostAddress(QHostAddress::Any) << LocalNetAddress;
QTest::newRow("Null") << QHostAddress(QHostAddress::Null) << UnknownAddress;
QTest::newRow("ipv4_loop") << QHostAddress(QHostAddress::LocalHost) << true;
QTest::newRow("127.0.0.1") << QHostAddress("127.0.0.1") << true;
QTest::newRow("127.0.0.2") << QHostAddress("127.0.0.2") << true;
QTest::newRow("127.3.2.1") << QHostAddress("127.3.2.1") << true;
QTest::newRow("1.2.3.4") << QHostAddress("1.2.3.4") << false;
QTest::newRow("10.0.0.4") << QHostAddress("10.0.0.4") << false;
QTest::newRow("192.168.3.4") << QHostAddress("192.168.3.4") << false;
QTest::newRow("::") << QHostAddress("::") << false;
QTest::newRow("Any") << QHostAddress(QHostAddress::Any) << false;
QTest::newRow("AnyIPv4") << QHostAddress(QHostAddress::AnyIPv4) << false;
QTest::newRow("AnyIPv6") << QHostAddress(QHostAddress::AnyIPv6) << false;
QTest::newRow("Broadcast") << QHostAddress(QHostAddress::Broadcast) << false;
QTest::newRow("Null") << QHostAddress(QHostAddress::Null) << false;
QTest::newRow("ipv6-all-ffff") << QHostAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") << false;
QTest::newRow("::ffff:127.0.0.1") << QHostAddress("::ffff:127.0.0.1") << true;
QTest::newRow("::ffff:127.0.0.2") << QHostAddress("::ffff:127.0.0.2") << true;
QTest::newRow("::ffff:127.3.2.1") << QHostAddress("::ffff:127.3.2.1") << true;
// IPv6 address space
auto addV6 = [](const char *str, AddressClassification cl) {
QTest::newRow(str) << QHostAddress(str) << cl;
};
QTest::newRow("AnyIPv6") << QHostAddress(QHostAddress::AnyIPv6) << LocalNetAddress;
QTest::newRow("ipv6_loop") << QHostAddress(QHostAddress::LocalHostIPv6) << LoopbackAddress;
addV6("::", LocalNetAddress);
addV6("::1", LoopbackAddress);
addV6("::2", GlobalAddress);
addV6("2000::", GlobalAddress);
addV6("3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", GlobalAddress);
addV6("fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", GlobalAddress);
addV6("fc00::", UniqueLocalAddress);
addV6("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", UniqueLocalAddress);
addV6("fe00::", UnknownAddress);
addV6("fe7f:ffff:ffff:ffff:ffff:ffff:ffff:ffff", UnknownAddress);
addV6("fe80::", LinkLocalAddress);
addV6("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff", LinkLocalAddress);
addV6("fec0::", SiteLocalAddress);
addV6("feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", SiteLocalAddress);
addV6("ff00::", MulticastAddress);
addV6("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", MulticastAddress);
// IPv4 address space
auto addV4 = [](const char *str, AddressClassification cl) {
QTest::newRow(str) << QHostAddress(str) << cl;
QByteArray v6 = "::ffff:";
v6 += str;
QTest::newRow(v6.constData()) << QHostAddress(QString::fromLatin1(v6)) << cl;
};
QTest::newRow("AnyIPv4") << QHostAddress(QHostAddress::AnyIPv4) << LocalNetAddress;
QTest::newRow("ipv4_loop") << QHostAddress(QHostAddress::LocalHost) << LoopbackAddress;
QTest::newRow("Broadcast") << QHostAddress(QHostAddress::Broadcast) << BroadcastAddress;
addV4("0.0.0.0", LocalNetAddress);
addV4("0.0.0.1", LocalNetAddress);
addV4("0.255.255.255", LocalNetAddress);
addV4("1.0.0.0", GlobalAddress);
addV4("1.2.3.4", GlobalAddress);
addV4("10.0.0.4", PrivateNetworkAddress);
addV4("127.0.0.1", LoopbackAddress);
addV4("127.0.0.2", LoopbackAddress);
addV4("127.255.255.255", LoopbackAddress);
addV4("192.168.3.4", PrivateNetworkAddress);
addV4("223.255.255.255", GlobalAddress);
addV4("224.0.0.0", MulticastAddress);
addV4("239.255.255.255", MulticastAddress);
addV4("240.0.0.0", UnknownAddress);
addV4("255.255.255.254", UnknownAddress);
addV4("255.255.255.255", BroadcastAddress);
}
void tst_QHostAddress::isLoopback()
void tst_QHostAddress::classification()
{
QFETCH(QHostAddress, address);
QFETCH(bool, result);
QFETCH(AddressClassification, result);
QCOMPARE(address.isLoopback(), result);
}
bool isLoopback = (result == LoopbackAddress);
bool isGlobal = (result & GlobalAddress); // GlobalAddress is a bit
bool isLinkLocal = (result == LinkLocalAddress);
bool isSiteLocal = (result == SiteLocalAddress);
bool isUniqueLocalAddress = (result == UniqueLocalAddress);
bool isMulticast = (result == MulticastAddress);
bool isBroadcast = (result == BroadcastAddress);
void tst_QHostAddress::isMulticast_data()
{
QTest::addColumn<QHostAddress>("address");
QTest::addColumn<bool>("result");
QTest::newRow("default") << QHostAddress() << false;
QTest::newRow("invalid") << QHostAddress("&&&") << false;
QTest::newRow("ipv6_loop") << QHostAddress(QHostAddress::LocalHostIPv6) << false;
QTest::newRow("::1") << QHostAddress("::1") << false;
QTest::newRow("ipv4_loop") << QHostAddress(QHostAddress::LocalHost) << false;
QTest::newRow("127.0.0.1") << QHostAddress("127.0.0.1") << false;
QTest::newRow("::") << QHostAddress("::") << false;
QTest::newRow("Any") << QHostAddress(QHostAddress::Any) << false;
QTest::newRow("AnyIPv4") << QHostAddress(QHostAddress::AnyIPv4) << false;
QTest::newRow("AnyIPv6") << QHostAddress(QHostAddress::AnyIPv6) << false;
QTest::newRow("Broadcast") << QHostAddress(QHostAddress::Broadcast) << false;
QTest::newRow("Null") << QHostAddress(QHostAddress::Null) << false;
QTest::newRow("223.255.255.255") << QHostAddress("223.255.255.255") << false;
QTest::newRow("224.0.0.0") << QHostAddress("224.0.0.0") << true;
QTest::newRow("239.255.255.255") << QHostAddress("239.255.255.255") << true;
QTest::newRow("240.0.0.0") << QHostAddress("240.0.0.0") << false;
QTest::newRow("::ffff:223.255.255.255") << QHostAddress("::ffff:223.255.255.255") << false;
QTest::newRow("::ffff:224.0.0.0") << QHostAddress("::ffff:224.0.0.0") << true;
QTest::newRow("::ffff:239.255.255.255") << QHostAddress("::ffff:239.255.255.255") << true;
QTest::newRow("::ffff:240.0.0.0") << QHostAddress("::ffff:240.0.0.0") << false;
QTest::newRow("fc00::") << QHostAddress("fc00::") << false;
QTest::newRow("fe80::") << QHostAddress("fe80::") << false;
QTest::newRow("fec0::") << QHostAddress("fec0::") << false;
QTest::newRow("ff00::") << QHostAddress("ff00::") << true;
QTest::newRow("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") << QHostAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") << true;
}
void tst_QHostAddress::isMulticast()
{
QFETCH(QHostAddress, address);
QFETCH(bool, result);
QCOMPARE(address.isMulticast(), result);
QCOMPARE(address.isLoopback(), isLoopback);
QCOMPARE(address.isGlobal(), isGlobal);
QCOMPARE(address.isLinkLocal(), isLinkLocal);
QCOMPARE(address.isSiteLocal(), isSiteLocal);
QCOMPARE(address.isUniqueLocalUnicast(), isUniqueLocalAddress);
QCOMPARE(address.isMulticast(), isMulticast);
QCOMPARE(address.isBroadcast(), isBroadcast);
}
void tst_QHostAddress::convertv4v6_data()