Extend IP normalization to handle various IPv4-embedding formats. This change introduces NormalizeWithCheckForEmbeddedIPv4Address() which handles IPv4-mapped, IPv4-compatible (obsolete), NAT64 (RFC 6052), 6to4 (RFC 3056), Teredo (RFC 4380), and ISATAP (RFC 5214) formats. The IP classification functions (IPIsLoopback, IPIsLinkLocal, etc.) are updated to use this more thorough normalization to ensure correct identification of local and private addresses regardless of the IPv6 embedding format used. Original Normalized() is kept for backward compatibility and used by VirtualSocketServer to maintain separation between native and mapped address families. In order to make the tests readable, win32.cc has been updated to support several formats that inet_pton on linux did support, but the special function in win32.cc did not support. Bug: webrtc:497635018 Change-Id: I20fe73d77b9f7e534a76ba6f2f271ac3034f7046 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/462843 Reviewed-by: Danil Chapovalov <danilchap@webrtc.org> Commit-Queue: Harald Alvestrand <hta@webrtc.org> Auto-Submit: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Danil Chapovalov <danilchap@webrtc.org> Cr-Commit-Position: refs/heads/main@{#47424}
diff --git a/rtc_base/ip_address.cc b/rtc_base/ip_address.cc index 007f13e..f8f1588 100644 --- a/rtc_base/ip_address.cc +++ b/rtc_base/ip_address.cc
@@ -31,13 +31,22 @@ namespace webrtc { // Prefixes used for categorizing IPv6 addresses. +// V4 mapped addresses are defined in RFC 4291 section 2.5.5. static const in6_addr kV4MappedPrefix = { {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0}}}; +// 6to4 is defined in RFC 3056 static const in6_addr k6To4Prefix = {{{0x20, 0x02, 0}}}; +// TEREDO is defined in RFC 8190. static const in6_addr kTeredoPrefix = {{{0x20, 0x01, 0x00, 0x00}}}; +// V4 compatible addresses are defined in RFC 4291 section 2.5.5. static const in6_addr kV4CompatibilityPrefix = {{{0}}}; +// 6bone is no longer in use, see RFC 5156 static const in6_addr k6BonePrefix = {{{0x3f, 0xfe, 0}}}; +// Unique Local Address with the L bit set, defined in RFC 4193 section 3.1 static const in6_addr kPrivateNetworkPrefix = {{{0xFD}}}; +// The NAT64 prefix is defined in RFC 6052 section 2.1 +static const in6_addr kNat64Prefix = { + {{0x00, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0}}}; static bool IPIsHelper(const IPAddress& ip, const in6_addr& tomatch, @@ -183,6 +192,32 @@ return IPAddress(addr); } +IPAddress IPAddress::NormalizeWithCheckForEmbeddedIPv4Address() const { + if (family_ != AF_INET6) { + return *this; + } + if (IPIsV4Mapped(*this) || IPIsV4Compatibility(*this) || IPIsNat64(*this)) { + in_addr addr = ExtractMappedAddress(u_.ip6); + return IPAddress(addr); + } + if (IPIs6To4(*this)) { + in_addr addr; + ::memcpy(&addr.s_addr, &u_.ip6.s6_addr[2], sizeof(addr.s_addr)); + return IPAddress(addr); + } + if (IPIsTeredo(*this)) { + in_addr addr; + ::memcpy(&addr.s_addr, &u_.ip6.s6_addr[12], sizeof(addr.s_addr)); + addr.s_addr = ~addr.s_addr; + return IPAddress(addr); + } + if (IPIsIsatap(*this)) { + in_addr addr = ExtractMappedAddress(u_.ip6); + return IPAddress(addr); + } + return *this; +} + IPAddress IPAddress::AsIPv6Address() const { if (family_ != AF_INET) { return *this; @@ -229,7 +264,7 @@ } bool IPIsPrivateNetwork(const IPAddress& ip) { - IPAddress normalized = ip.Normalized(); + IPAddress normalized = ip.NormalizeWithCheckForEmbeddedIPv4Address(); switch (normalized.family()) { case AF_INET: { return IPIsPrivateNetworkV4(normalized); @@ -247,7 +282,7 @@ } bool IPIsSharedNetwork(const IPAddress& ip) { - IPAddress normalized = ip.Normalized(); + IPAddress normalized = ip.NormalizeWithCheckForEmbeddedIPv4Address(); if (normalized.family() == AF_INET) { return IPIsSharedNetworkV4(normalized); } @@ -326,7 +361,7 @@ } bool IPIsLoopback(const IPAddress& ip) { - IPAddress normalized = ip.Normalized(); + IPAddress normalized = ip.NormalizeWithCheckForEmbeddedIPv4Address(); switch (normalized.family()) { case AF_INET: { return IPIsLoopbackV4(normalized); @@ -348,7 +383,7 @@ } size_t HashIP(const IPAddress& ip) { - IPAddress normalized = ip.Normalized(); + IPAddress normalized = ip.NormalizeWithCheckForEmbeddedIPv4Address(); switch (normalized.family()) { case AF_INET: { return normalized.ipv4_address().s_addr; @@ -468,11 +503,21 @@ } bool IPIs6Bone(const IPAddress& ip) { - return IPIsHelper(ip, k6BonePrefix, 16); + return ip.family() == AF_INET6 && IPIsHelper(ip, k6BonePrefix, 16); } bool IPIs6To4(const IPAddress& ip) { - return IPIsHelper(ip, k6To4Prefix, 16); + return ip.family() == AF_INET6 && IPIsHelper(ip, k6To4Prefix, 16); +} + +bool IPIsIsatap(const IPAddress& ip) { + if (ip.family() != AF_INET6) { + return false; + } + in6_addr addr = ip.ipv6_address(); + return (addr.s6_addr[8] == 0x00 || addr.s6_addr[8] == 0x02) && + addr.s6_addr[9] == 0x00 && addr.s6_addr[10] == 0x5E && + addr.s6_addr[11] == 0xFE; } static bool IPIsLinkLocalV4(const IPAddress& ip) { @@ -487,7 +532,7 @@ } bool IPIsLinkLocal(const IPAddress& ip) { - IPAddress normalized = ip.Normalized(); + IPAddress normalized = ip.NormalizeWithCheckForEmbeddedIPv4Address(); switch (normalized.family()) { case AF_INET: { return IPIsLinkLocalV4(normalized); @@ -508,28 +553,43 @@ addr.s6_addr[12] == 0xFE); } +bool IPIsNat64(const IPAddress& ip) { + return ip.family() == AF_INET6 && IPIsHelper(ip, kNat64Prefix, 96); +} + bool IPIsSiteLocal(const IPAddress& ip) { // Can't use the helper because the prefix is 10 bits. + if (ip.family() != AF_INET6) { + return false; + } in6_addr addr = ip.ipv6_address(); return addr.s6_addr[0] == 0xFE && (addr.s6_addr[1] & 0xC0) == 0xC0; } bool IPIsULA(const IPAddress& ip) { // Can't use the helper because the prefix is 7 bits. + if (ip.family() != AF_INET6) { + return false; + } in6_addr addr = ip.ipv6_address(); return (addr.s6_addr[0] & 0xFE) == 0xFC; } bool IPIsTeredo(const IPAddress& ip) { - return IPIsHelper(ip, kTeredoPrefix, 32); + return ip.family() == AF_INET6 && IPIsHelper(ip, kTeredoPrefix, 32); } bool IPIsV4Compatibility(const IPAddress& ip) { - return IPIsHelper(ip, kV4CompatibilityPrefix, 96); + if (ip.family() != AF_INET6 || !IPIsHelper(ip, kV4CompatibilityPrefix, 96)) { + return false; + } + in_addr v4 = ExtractMappedAddress(ip.ipv6_address()); + uint32_t v4_h = NetworkToHost32(v4.s_addr); + return v4_h != 0 && v4_h != 1; } bool IPIsV4Mapped(const IPAddress& ip) { - return IPIsHelper(ip, kV4MappedPrefix, 96); + return ip.family() == AF_INET6 && IPIsHelper(ip, kV4MappedPrefix, 96); } int IPAddressPrecedence(const IPAddress& ip) {
diff --git a/rtc_base/ip_address.h b/rtc_base/ip_address.h index 7ebad47..f7f3822 100644 --- a/rtc_base/ip_address.h +++ b/rtc_base/ip_address.h
@@ -116,6 +116,9 @@ // Returns the same address if this isn't a mapped address. IPAddress Normalized() const; + // Returns an unmapped address from multiple IPv4-embedding formats. + IPAddress NormalizeWithCheckForEmbeddedIPv4Address() const; + // Returns this address as an IPv6 address. // Maps v4 addresses (as ::ffff:a.b.c.d), returns v6 addresses unchanged. IPAddress AsIPv6Address() const; @@ -213,7 +216,9 @@ // These are only really applicable for IPv6 addresses. bool IPIs6Bone(const IPAddress& ip); bool IPIs6To4(const IPAddress& ip); +bool IPIsIsatap(const IPAddress& ip); RTC_EXPORT bool IPIsMacBased(const IPAddress& ip); +bool IPIsNat64(const IPAddress& ip); bool IPIsSiteLocal(const IPAddress& ip); bool IPIsTeredo(const IPAddress& ip); bool IPIsULA(const IPAddress& ip);
diff --git a/rtc_base/ip_address_unittest.cc b/rtc_base/ip_address_unittest.cc index 38b625b..6a9ab9f 100644 --- a/rtc_base/ip_address_unittest.cc +++ b/rtc_base/ip_address_unittest.cc
@@ -674,6 +674,57 @@ EXPECT_FALSE(IPIsMacBased(addr)); } +TEST(IPAddressTest, TestFromStringRFC4291) { + IPAddress addr, addr2; + + // RFC 4291 Section 2.2, item 1 + EXPECT_TRUE(IPFromString("ABCD:EF01:2345:6789:ABCD:EF01:2345:6789", &addr)); + EXPECT_TRUE(IPFromString("2001:DB8:0:0:8:800:200C:417A", &addr)); + + // RFC 4291 Section 2.2, item 2 (Compressed) + EXPECT_TRUE(IPFromString("2001:DB8::8:800:200C:417A", &addr2)); + EXPECT_EQ(addr, addr2); + + EXPECT_TRUE(IPFromString("FF01:0:0:0:0:0:0:101", &addr)); + EXPECT_TRUE(IPFromString("FF01::101", &addr2)); + EXPECT_EQ(addr, addr2); + + EXPECT_TRUE(IPFromString("0:0:0:0:0:0:0:1", &addr)); + EXPECT_TRUE(IPFromString("::1", &addr2)); + EXPECT_EQ(addr, addr2); + + EXPECT_TRUE(IPFromString("0:0:0:0:0:0:0:0", &addr)); + EXPECT_TRUE(IPFromString("::", &addr2)); + EXPECT_EQ(addr, addr2); + + // RFC 4291 Section 2.2, item 3 (Hybrid) + // 13.1.68.3 is 0D 01 44 03 + EXPECT_TRUE(IPFromString("0:0:0:0:0:0:13.1.68.3", &addr)); + EXPECT_TRUE(IPFromString("::13.1.68.3", &addr2)); + EXPECT_EQ(addr, addr2); + EXPECT_TRUE(IPFromString("::0:13.1.68.3", &addr2)); + EXPECT_EQ(addr, addr2); + + // 129.144.52.38 is 81 90 34 26 + EXPECT_TRUE(IPFromString("0:0:0:0:0:FFFF:129.144.52.38", &addr)); + EXPECT_TRUE(IPFromString("::FFFF:129.144.52.38", &addr2)); + EXPECT_EQ(addr, addr2); + + // NAT64 (RFC 6052) Well-Known Prefix (not in RFC 4291 but uses hybrid) + EXPECT_TRUE(IPFromString("64:ff9b::192.168.7.1", &addr)); + EXPECT_TRUE(IPFromString("64:ff9b::c0a8:0701", &addr2)); + EXPECT_EQ(addr, addr2); + + // ISATAP (RFC 5214) (not in RFC 4291 but uses hybrid) + EXPECT_TRUE(IPFromString("fe80::5efe:192.168.7.1", &addr)); + EXPECT_TRUE(IPFromString("fe80::5efe:c0a8:0701", &addr2)); + EXPECT_EQ(addr, addr2); + + EXPECT_TRUE(IPFromString("fe80::200:5efe:192.168.7.1", &addr)); + EXPECT_TRUE(IPFromString("fe80::200:5efe:c0a8:0701", &addr2)); + EXPECT_EQ(addr, addr2); +} + TEST(IPAddressTest, TestNormalized) { // Check normalizing a ::ffff:a.b.c.d address. IPAddress addr; @@ -709,12 +760,61 @@ addr2 = addr; addr = addr.Normalized(); EXPECT_EQ(addr, addr2); +} +TEST(IPAddressTest, TestNormalizeWithCheckForEmbeddedIPv4Address) { + IPAddress addr, addr2; + + // IPv4-mapped + EXPECT_TRUE(IPFromString(kIPv4MappedV4StyleAddrString, &addr)); + addr = addr.NormalizeWithCheckForEmbeddedIPv4Address(); + EXPECT_EQ(addr, IPAddress(kIPv4RFC1918Addr)); + + // IPv4-compatible + EXPECT_TRUE(IPFromString("::0102:0304", &addr)); + addr = addr.NormalizeWithCheckForEmbeddedIPv4Address(); + EXPECT_EQ(addr, IPAddress(kIPv4PublicAddr)); + + EXPECT_TRUE(IPFromString("::192.168.7.1", &addr)); + addr = addr.NormalizeWithCheckForEmbeddedIPv4Address(); + EXPECT_EQ(addr, IPAddress(kIPv4RFC1918Addr)); + + // NAT64 (RFC 6052) Well-Known Prefix normalization + EXPECT_TRUE(IPFromString("64:ff9b::192.168.7.1", &addr)); + addr = addr.NormalizeWithCheckForEmbeddedIPv4Address(); + EXPECT_EQ(addr, IPAddress(kIPv4RFC1918Addr)); + + // 6to4 (RFC 3056) normalization: 2002:c0a8:0701:: + EXPECT_TRUE(IPFromString("2002:c0a8:0701::", &addr)); + addr = addr.NormalizeWithCheckForEmbeddedIPv4Address(); + EXPECT_EQ(addr, IPAddress(kIPv4RFC1918Addr)); + + // Teredo (RFC 4380) normalization: 2001:0000:....:3f57:f8fe + // Embedded IPv4 is at the end, bit-flipped. + // 192.168.7.1 is c0 a8 07 01. Bit-flipped: 3f 57 f8 fe. + EXPECT_TRUE(IPFromString("2001:0000:4112:1:2831:3421:3f57:f8fe", &addr)); + addr = addr.NormalizeWithCheckForEmbeddedIPv4Address(); + EXPECT_EQ(addr, IPAddress(kIPv4RFC1918Addr)); + + // ISATAP (RFC 5214) normalization: fe80::5efe:c0a8:0701 + EXPECT_TRUE(IPFromString("fe80::5efe:192.168.7.1", &addr)); + addr = addr.NormalizeWithCheckForEmbeddedIPv4Address(); + EXPECT_EQ(addr, IPAddress(kIPv4RFC1918Addr)); + + EXPECT_TRUE(IPFromString("fe80::200:5efe:192.168.7.1", &addr)); + addr = addr.NormalizeWithCheckForEmbeddedIPv4Address(); + EXPECT_EQ(addr, IPAddress(kIPv4RFC1918Addr)); +} + +TEST(IPAddressTest, TestV4NotAltered) { + IPAddress addr, addr2; // Check that v4 addresses aren't altered. addr = IPAddress(htonl(kIPv4PublicAddr)); addr2 = IPAddress(htonl(kIPv4PublicAddr)); addr = addr.Normalized(); EXPECT_EQ(addr, addr2); + addr = addr.NormalizeWithCheckForEmbeddedIPv4Address(); + EXPECT_EQ(addr, addr2); } TEST(IPAddressTest, TestAsIPv6Address) {
diff --git a/rtc_base/win32.cc b/rtc_base/win32.cc index a505017..53e3c02 100644 --- a/rtc_base/win32.cc +++ b/rtc_base/win32.cc
@@ -217,40 +217,6 @@ uint16_t* addr_end = reinterpret_cast<uint16_t*>(&an_addr.s6_addr[16]); bool seencompressed = false; - // Addresses that start with "::" (i.e., a run of initial zeros) or - // "::ffff:" can potentially be IPv4 mapped or compatibility addresses. - // These have dotted-style IPv4 addresses on the end (e.g. "::192.168.7.1"). - if (*readcursor == ':' && *(readcursor + 1) == ':' && - *(readcursor + 2) != 0) { - // Check for periods, which we'll take as a sign of v4 addresses. - const char* addrstart = readcursor + 2; - if (strchr(addrstart, '.')) { - const char* colon = strchr(addrstart, ':'); - if (colon) { - uint16_t a_short; - int bytesread = 0; - if (sscanf(addrstart, "%hx%n", &a_short, &bytesread) != 1 || - a_short != 0xFFFF || bytesread != 4) { - // Colons + periods means has to be ::ffff:a.b.c.d. But it wasn't. - return 0; - } else { - an_addr.s6_addr[10] = 0xFF; - an_addr.s6_addr[11] = 0xFF; - addrstart = colon + 1; - } - } - struct in_addr v4; - if (inet_pton_v4(addrstart, &v4.s_addr)) { - memcpy(&an_addr.s6_addr[12], &v4, sizeof(v4)); - memcpy(dst, &an_addr, sizeof(an_addr)); - return 1; - } else { - // Invalid v4 address. - return 0; - } - } - } - // For addresses without a trailing IPv4 component ('normal' IPv6 addresses). while (*readcursor != 0 && addr_cursor < addr_end) { if (*readcursor == ':') { @@ -268,25 +234,43 @@ // Special case - trailing ::. addr_cursor = addr_end; } else { + bool has_dot = false; while (*coloncounter) { if (*coloncounter == ':') { ++coloncount; + } else if (*coloncounter == '.') { + has_dot = true; } ++coloncounter; } // (coloncount + 1) is the number of shorts left in the address. + // If there's a dot, the last part is an IPv4 address, which is 2 + // shorts but has no internal colon. + int expected_shorts = coloncount + 1; + if (has_dot) { + expected_shorts++; + } // If this number is greater than the number of available shorts, the // address is malformed. - if (coloncount + 1 > addr_end - addr_cursor) { + if (expected_shorts > addr_end - addr_cursor) { return 0; } - addr_cursor = addr_end - (coloncount + 1); + addr_cursor = addr_end - expected_shorts; seencompressed = true; } } else { ++readcursor; } } else { + if (strchr(readcursor, '.') && addr_cursor + 2 <= addr_end) { + struct in_addr v4; + if (inet_pton_v4(readcursor, &v4.s_addr)) { + memcpy(addr_cursor, &v4, sizeof(v4)); + addr_cursor += 2; + readcursor += strlen(readcursor); + break; + } + } uint16_t word; int bytesread = 0; if (sscanf(readcursor, "%4hx%n", &word, &bytesread) != 1) {