From cedc554321f896d6dc09f0c7c35a6278f2b35339 Mon Sep 17 00:00:00 2001 From: Felix Braun Date: Mon, 11 Apr 2016 22:16:09 +0200 Subject: [PATCH] QHostAddress: comparator with flexible conversion rules Add a named comparison method that allows the caller to specify in how far a translation between IPv4 and IPv6 should be performed before comparing. Change-Id: I9aeffcd4539a6c0537c083c4553357b22167df3f Reviewed-by: Thiago Macieira --- src/network/kernel/qhostaddress.cpp | 120 +++++++++++++++--- src/network/kernel/qhostaddress.h | 12 ++ .../kernel/qhostaddress/tst_qhostaddress.cpp | 50 ++++++++ 3 files changed, 166 insertions(+), 16 deletions(-) diff --git a/src/network/kernel/qhostaddress.cpp b/src/network/kernel/qhostaddress.cpp index dfade24edd..757a6ad0b0 100644 --- a/src/network/kernel/qhostaddress.cpp +++ b/src/network/kernel/qhostaddress.cpp @@ -152,20 +152,37 @@ void QHostAddressPrivate::setAddress(quint32 a_) /// parses v4-mapped addresses or the AnyIPv6 address and stores in \a a; /// returns true if the address was one of those -static bool convertToIpv4(quint32& a, const Q_IPV6ADDR &a6) +static bool convertToIpv4(quint32& a, const Q_IPV6ADDR &a6, const QHostAddress::ConversionMode mode) { + if (mode == QHostAddress::StrictConversion) + return false; + const uchar *ptr = a6.c; if (qFromUnaligned(ptr) != 0) return false; - if (qFromBigEndian(ptr + 8) == 0) { - // is it AnyIPv6? - a = 0; - return qFromBigEndian(ptr + 12) == 0; + + const quint32 mid = qFromBigEndian(ptr + 8); + if ((mid == 0xffff) && (mode & QHostAddress::ConvertV4MappedToIPv4)) { + a = qFromBigEndian(ptr + 12); + return true; } - if (qFromBigEndian(ptr + 8) != 0xFFFF) + if (mid != 0) return false; - a = qFromBigEndian(ptr + 12); - return true; + + const quint32 low = qFromBigEndian(ptr + 12); + if ((low == 0) && (mode & QHostAddress::ConvertUnspecifiedAddress)) { + a = 0; + return true; + } + if ((low == 1) && (mode & QHostAddress::ConvertLocalHost)) { + a = INADDR_LOOPBACK; + return true; + } + if ((low != 1) && (mode & QHostAddress::ConvertV4CompatToIPv4)) { + a = low; + return true; + } + return false; } void QHostAddressPrivate::setAddress(const quint8 *a_) @@ -174,7 +191,8 @@ void QHostAddressPrivate::setAddress(const quint8 *a_) isParsed = true; memcpy(a6.c, a_, sizeof(a6)); a = 0; - convertToIpv4(a, a6); + convertToIpv4(a, a6, (QHostAddress::ConvertV4MappedToIPv4 + | QHostAddress::ConvertUnspecifiedAddress)); } void QHostAddressPrivate::setAddress(const Q_IPV6ADDR &a_) @@ -386,6 +404,20 @@ void QNetmaskAddress::setPrefixLength(QAbstractSocket::NetworkLayerProtocol prot \value Any The dual stack any-address. A socket bound with this address will listen on both IPv4 and IPv6 interfaces. */ +/*! \enum QHostAddress::ConversionModeFlag + + \since 5.8 + + \value StrictConversion Don't convert IPv6 addresses to IPv4 when comparing two QHostAddress objects of different protocols, so they will always be considered different. + \value ConvertV4MappedToIPv4 Convert IPv4-mapped IPv6 addresses (RFC 4291 sect. 2.5.5.2) when comparing. Therefore QHostAddress("::ffff:192.168.1.1") will compare equal to QHostAddress("192.168.1.1"). + \value ConvertV4CompatToIPv4 Convert IPv4-compatible IPv6 addresses (RFC 4291 sect. 2.5.5.1) when comparing. Therefore QHostAddress("::192.168.1.1") will compare equal to QHostAddress("192.168.1.1"). + \value ConvertLocalHost Convert the IPv6 loopback addresses to its IPv4 equivalent when comparing. Therefore e.g. QHostAddress("::1") will compare equal to QHostAddress("127.0.0.1"). + \value ConvertUnspecifiedAddress All unspecified addresses will compare equal, namely AnyIPv4, AnyIPv6 and Any. + \value TolerantConversion Sets all three preceding flags. + + \sa isEqual() + */ + /*! Constructs a null host address object, i.e. an address which is not valid for any host or interface. \sa clear() @@ -691,7 +723,9 @@ quint32 QHostAddress::toIPv4Address(bool *ok) const quint32 dummy; if (ok) *ok = d->protocol == QAbstractSocket::IPv4Protocol || d->protocol == QAbstractSocket::AnyIPProtocol - || (d->protocol == QAbstractSocket::IPv6Protocol && convertToIpv4(dummy, d->a6)); + || (d->protocol == QAbstractSocket::IPv6Protocol + && convertToIpv4(dummy, d->a6, ConversionMode(QHostAddress::ConvertV4MappedToIPv4 + | QHostAddress::ConvertUnspecifiedAddress))); return d->a; } @@ -813,19 +847,73 @@ void QHostAddress::setScopeId(const QString &id) /*! Returns \c true if this host address is the same as the \a other address - given; otherwise returns \c false. + given; otherwise returns \c false. This operator just calls isEqual(other, StrictConversion). + + \sa isEqual() */ bool QHostAddress::operator==(const QHostAddress &other) const +{ + return isEqual(other, StrictConversion); +} + +/*! + \since 5.8 + + Returns \c true if this host address is the same as the \a other address + given; otherwise returns \c false. + + The parameter \a mode controls which conversions are preformed between addresses + of differing protocols. If no \a mode is given, \c TolerantConversion is performed + by default. + + \sa ConversionMode, operator==() + */ +bool QHostAddress::isEqual(const QHostAddress &other, ConversionMode mode) const { QT_ENSURE_PARSED(this); QT_ENSURE_PARSED(&other); - if (d->protocol == QAbstractSocket::IPv4Protocol) - return other.d->protocol == QAbstractSocket::IPv4Protocol && d->a == other.d->a; - if (d->protocol == QAbstractSocket::IPv6Protocol) { - return other.d->protocol == QAbstractSocket::IPv6Protocol - && memcmp(&d->a6, &other.d->a6, sizeof(Q_IPV6ADDR)) == 0; + if (d->protocol == QAbstractSocket::IPv4Protocol) { + switch (other.d->protocol) { + case QAbstractSocket::IPv4Protocol: + return d->a == other.d->a; + case QAbstractSocket::IPv6Protocol: + quint32 a4; + return convertToIpv4(a4, other.d->a6, mode) && (a4 == d->a); + case QAbstractSocket::AnyIPProtocol: + return (mode & QHostAddress::ConvertUnspecifiedAddress) && d->a == 0; + case QAbstractSocket::UnknownNetworkLayerProtocol: + return false; + } } + + if (d->protocol == QAbstractSocket::IPv6Protocol) { + switch (other.d->protocol) { + case QAbstractSocket::IPv4Protocol: + quint32 a4; + return convertToIpv4(a4, d->a6, mode) && (a4 == other.d->a); + case QAbstractSocket::IPv6Protocol: + return memcmp(&d->a6, &other.d->a6, sizeof(Q_IPV6ADDR)) == 0; + case QAbstractSocket::AnyIPProtocol: + return (mode & QHostAddress::ConvertUnspecifiedAddress) + && (other.d->a6_64.c[0] == 0) && (other.d->a6_64.c[1] == 0); + case QAbstractSocket::UnknownNetworkLayerProtocol: + return false; + } + } + + if ((d->protocol == QAbstractSocket::AnyIPProtocol) + && (mode & QHostAddress::ConvertUnspecifiedAddress)) { + switch (other.d->protocol) { + case QAbstractSocket::IPv4Protocol: + return other.d->a == 0; + case QAbstractSocket::IPv6Protocol: + return (other.d->a6_64.c[0] == 0) && (other.d->a6_64.c[1] == 0); + default: + break; + } + } + return d->protocol == other.d->protocol; } diff --git a/src/network/kernel/qhostaddress.h b/src/network/kernel/qhostaddress.h index 8236a71986..8cf6876511 100644 --- a/src/network/kernel/qhostaddress.h +++ b/src/network/kernel/qhostaddress.h @@ -79,6 +79,16 @@ public: AnyIPv6, AnyIPv4 }; + enum ConversionModeFlag { + ConvertV4MappedToIPv4 = 1, + ConvertV4CompatToIPv4 = 2, + ConvertUnspecifiedAddress = 4, + ConvertLocalHost = 8, + TolerantConversion = 0xff, + + StrictConversion = 0 + }; + Q_DECLARE_FLAGS(ConversionMode, ConversionModeFlag) QHostAddress(); explicit QHostAddress(quint32 ip4Addr); @@ -118,6 +128,7 @@ public: QString scopeId() const; void setScopeId(const QString &id); + bool isEqual(const QHostAddress &address, ConversionMode mode = TolerantConversion) const; bool operator ==(const QHostAddress &address) const; bool operator ==(SpecialAddress address) const; inline bool operator !=(const QHostAddress &address) const @@ -139,6 +150,7 @@ public: protected: QScopedPointer d; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(QHostAddress::ConversionMode) Q_DECLARE_SHARED_NOT_MOVABLE_UNTIL_QT6(QHostAddress) inline bool operator ==(QHostAddress::SpecialAddress address1, const QHostAddress &address2) diff --git a/tests/auto/network/kernel/qhostaddress/tst_qhostaddress.cpp b/tests/auto/network/kernel/qhostaddress/tst_qhostaddress.cpp index a418e59ba4..79703dc649 100644 --- a/tests/auto/network/kernel/qhostaddress/tst_qhostaddress.cpp +++ b/tests/auto/network/kernel/qhostaddress/tst_qhostaddress.cpp @@ -62,6 +62,8 @@ private slots: void specialAddresses(); void compare_data(); void compare(); + void isEqual_data(); + void isEqual(); void assignment(); void scopeId(); void hashKey(); @@ -282,6 +284,7 @@ void tst_QHostAddress::compare_data() QTest::newRow("5") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::Broadcast) << false; QTest::newRow("6") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << false; QTest::newRow("7") << QHostAddress() << QHostAddress(QHostAddress::LocalHostIPv6) << false; + QTest::newRow("any4-any6") << QHostAddress(QHostAddress::AnyIPv4) << QHostAddress(QHostAddress::AnyIPv6) << false; Q_IPV6ADDR localhostv4mapped = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 127, 0, 0, 1 } }; QTest::newRow("v4-v4mapped") << QHostAddress(QHostAddress::LocalHost) << QHostAddress("::ffff:127.0.0.1") << false; @@ -300,6 +303,53 @@ void tst_QHostAddress::compare() QCOMPARE(qHash(first), qHash(second)); } +void tst_QHostAddress::isEqual_data() +{ + QTest::addColumn("first"); + QTest::addColumn("second"); + QTest::addColumn("flags"); + QTest::addColumn("result"); + + // QHostAddress::StrictConversion is already tested in compare() + QTest::newRow("localhost4to6-local") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertLocalHost << true; + QTest::newRow("localhost4to6-compat") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4CompatToIPv4 << false; + QTest::newRow("localhost4to6-mapped") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4MappedToIPv4 << false; + QTest::newRow("localhost4to6-unspec") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertUnspecifiedAddress << false; + QTest::newRow("0.0.0.1-::1-local") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertLocalHost << false; + QTest::newRow("v4-v4compat-local") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertLocalHost << false; + QTest::newRow("v4-v4mapped-local") << QHostAddress("192.168.1.1") << QHostAddress("::ffff:192.168.1.1") << (int)QHostAddress::ConvertLocalHost << false; + QTest::newRow("0.0.0.1-::1-unspec") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertUnspecifiedAddress << false; + QTest::newRow("v4-v4compat-unspec") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertUnspecifiedAddress << false; + QTest::newRow("v4-v4mapped-unspec") << QHostAddress("192.168.1.1") << QHostAddress("::ffff:192.168.1.1") << (int)QHostAddress::ConvertUnspecifiedAddress << false; + QTest::newRow("0.0.0.1-::1-compat") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4CompatToIPv4 << false; + QTest::newRow("v4-v4compat-compat") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertV4CompatToIPv4 << true; + QTest::newRow("v4-v4mapped-compat") << QHostAddress("192.168.1.1") << QHostAddress("::ffff:192.168.1.1") << (int)QHostAddress::ConvertV4CompatToIPv4 << false; + QTest::newRow("0.0.0.1-::1-mapped") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4MappedToIPv4 << false; + QTest::newRow("v4-v4compat-mapped") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertV4MappedToIPv4 << false; + QTest::newRow("v4-v4mapped-mapped") << QHostAddress("192.168.1.1") << QHostAddress("::FFFF:192.168.1.1") << (int)QHostAddress::ConvertV4MappedToIPv4 << true; + QTest::newRow("undef-any-local") << QHostAddress() << QHostAddress(QHostAddress::Any) << (int)QHostAddress::ConvertLocalHost << false; + QTest::newRow("undef-any-unspec") << QHostAddress() << QHostAddress(QHostAddress::Any) << (int)QHostAddress::ConvertUnspecifiedAddress << false; + QTest::newRow("anyv6-anyv4-compat") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertV4CompatToIPv4 << true; + QTest::newRow("anyv6-anyv4-mapped") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertV4MappedToIPv4 << false; + QTest::newRow("anyv6-anyv4-unspec") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertUnspecifiedAddress << true; + QTest::newRow("any-anyv4-unspec") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertUnspecifiedAddress << true; + QTest::newRow("any-anyv6-unspec") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv6) << (int)QHostAddress::ConvertUnspecifiedAddress << true; + QTest::newRow("anyv6-anyv4-local") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertLocalHost << false; + QTest::newRow("any-anyv4-local") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertLocalHost << false; + QTest::newRow("any-anyv6-local") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv6) << (int)QHostAddress::ConvertLocalHost << false; +} + +void tst_QHostAddress::isEqual() +{ + QFETCH(QHostAddress, first); + QFETCH(QHostAddress, second); + QFETCH(int, flags); + QFETCH(bool, result); + + QCOMPARE(first.isEqual(second, QHostAddress::ConversionModeFlag(flags)), result); + QCOMPARE(second.isEqual(first, QHostAddress::ConversionModeFlag(flags)), result); +} + void tst_QHostAddress::assignment() { QHostAddress address;