/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "TestCommon.h" #include "gtest/gtest.h" #include "mozilla/gtest/MozAssertions.h" #include "mozilla/StaticPrefs_network.h" #include "mozilla/Preferences.h" #include "mozilla/net/DNS.h" #include "nsNetUtil.h" #include "nsIOService.h" TEST(TestNetAddrLNAUtil, IPAddressSpaceCategorization) { /*--------------------------------------------------------------------------* | Network | Description | RFC | Scope | |----------------------|------------------------|-----------|------------------| 127.0.0.0/8 | IPv4 Loopback | RFC1122 | local | 10.0.0.0/8 | Private Use | RFC1918 | private | 100.64.0.0/10 | Carrier-Grade NAT | RFC6598 | private | 172.16.0.0/12 | Private Use | RFC1918 | private | 192.168.0.0/16 | Private Use | RFC1918 | private | 198.18.0.0/15 | Benchmarking | RFC2544 | pref/local 169.254.0.0/16 | Link Local | RFC3927 | private | ::1/128 | IPv6 Loopback | RFC4291 | local | fc00::/7 | Unique Local | RFC4193 | private | fe80::/10 | Link-Local Unicast | RFC4291 | private | ::ffff:0:0/96 | IPv4-mapped | RFC4291 | IPv4-mapped address space | *--------------------------------------------------------------------------*/ using namespace mozilla::net; using namespace mozilla; struct TestCase { const char* mIp; nsILoadInfo::IPAddressSpace mExpectedSpace; }; std::vector testCases = { // Local IPv4 {"", nsILoadInfo::IPAddressSpace::Unknown}, {"127.0.0.1", nsILoadInfo::IPAddressSpace::Local}, {"198.18.0.0", StaticPrefs::network_lna_benchmarking_is_local() ? nsILoadInfo::IPAddressSpace::Local : nsILoadInfo::IPAddressSpace::Public}, {"198.19.255.255", StaticPrefs::network_lna_benchmarking_is_local() ? nsILoadInfo::IPAddressSpace::Local : nsILoadInfo::IPAddressSpace::Public}, // Private IPv4 {"10.0.0.1", nsILoadInfo::IPAddressSpace::Private}, {"100.64.0.1", nsILoadInfo::IPAddressSpace::Private}, {"100.127.255.254", nsILoadInfo::IPAddressSpace::Private}, {"172.16.0.1", nsILoadInfo::IPAddressSpace::Private}, {"172.31.255.255", nsILoadInfo::IPAddressSpace::Private}, {"192.168.1.1", nsILoadInfo::IPAddressSpace::Private}, {"169.254.0.1", nsILoadInfo::IPAddressSpace::Private}, {"169.254.255.254", nsILoadInfo::IPAddressSpace::Private}, // IPv6 Local and Private {"::1", nsILoadInfo::IPAddressSpace::Local}, // Loopback {"fc00::", nsILoadInfo::IPAddressSpace::Private}, // Unique Local {"fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", nsILoadInfo::IPAddressSpace::Private}, {"fe80::1", nsILoadInfo::IPAddressSpace::Private}, // Link-local // IPv4-mapped IPv6 (mapped IPv4, should fall back to IPv4 classification) {"::ffff:127.0.0.1", nsILoadInfo::IPAddressSpace::Local}, // Loopback {"::ffff:10.0.0.1", nsILoadInfo::IPAddressSpace::Private}, // Private {"::ffff:1.1.1.1", nsILoadInfo::IPAddressSpace::Public}, // Public // Public IPv4 {"8.8.8.8", nsILoadInfo::IPAddressSpace::Public}, {"1.1.1.1", nsILoadInfo::IPAddressSpace::Public}, // Public IPv6 {"2001:4860:4860::8888", nsILoadInfo::IPAddressSpace::Public}, {"2606:4700:4700::1111", nsILoadInfo::IPAddressSpace::Public}}; for (const auto& testCase : testCases) { NetAddr addr; addr.InitFromString(nsCString(testCase.mIp)); if (addr.raw.family == AF_INET) { EXPECT_EQ(addr.GetIpAddressSpace(), testCase.mExpectedSpace) << "Failed for IP: " << testCase.mIp; } else if (addr.raw.family == AF_INET6) { EXPECT_EQ(addr.GetIpAddressSpace(), testCase.mExpectedSpace) << "Failed for IP: " << testCase.mIp; } else { EXPECT_EQ(addr.GetIpAddressSpace(), nsILoadInfo::IPAddressSpace::Unknown) << "Failed for IP: " << testCase.mIp; } } } TEST(TestNetAddrLNAUtil, DefaultAndOverrideTransitions) { using mozilla::Preferences; using mozilla::net::NetAddr; using IPAddressSpace = nsILoadInfo::IPAddressSpace; struct TestCase { const char* ip; uint16_t port; IPAddressSpace defaultSpace; IPAddressSpace overrideSpace; const char* prefName; }; std::vector testCases = { // Public -> Private {"8.8.8.8", 80, IPAddressSpace::Public, IPAddressSpace::Private, "network.lna.address_space.private.override"}, // Public -> Local {"8.8.4.4", 53, IPAddressSpace::Public, IPAddressSpace::Local, "network.lna.address_space.local.override"}, // Private -> Public {"192.168.0.1", 8080, IPAddressSpace::Private, IPAddressSpace::Public, "network.lna.address_space.public.override"}, // Private -> Local {"10.0.0.1", 1234, IPAddressSpace::Private, IPAddressSpace::Local, "network.lna.address_space.local.override"}, // Local -> Public {"127.0.0.1", 4444, IPAddressSpace::Local, IPAddressSpace::Public, "network.lna.address_space.public.override"}, // Local -> Private {"127.0.0.1", 9999, IPAddressSpace::Local, IPAddressSpace::Private, "network.lna.address_space.private.override"}, }; for (const auto& tc : testCases) { NetAddr addr; addr.InitFromString(nsCString(tc.ip), tc.port); ASSERT_EQ(addr.GetIpAddressSpace(), tc.defaultSpace) << "Expected default space for " << tc.ip << ":" << tc.port; std::string overrideStr = std::string(tc.ip) + ":" + std::to_string(tc.port); Preferences::SetCString(tc.prefName, overrideStr.c_str()); NetAddr overriddenAddr; overriddenAddr.InitFromString(nsCString(tc.ip), tc.port); ASSERT_EQ(overriddenAddr.GetIpAddressSpace(), tc.overrideSpace) << "Expected override to " << tc.overrideSpace << " for " << overrideStr; // Reset preference and confirm classification returns to default Preferences::SetCString(tc.prefName, ""_ns); NetAddr resetAddr; resetAddr.InitFromString(nsCString(tc.ip), tc.port); ASSERT_EQ(resetAddr.GetIpAddressSpace(), tc.defaultSpace) << "Expected reset back to default space for " << tc.ip; } } TEST(TestNetAddrLNAUtil, ShouldSkipDomainForLNA) { using mozilla::Preferences; // Get nsIOService instance mozilla::net::nsIOService* ioService = mozilla::net::gIOService; ASSERT_NE(ioService, nullptr); // Test with empty preference (should not skip any domains) Preferences::SetCString("network.lna.skip-domains", ""_ns); EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("example.com"_ns)); EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("test.example.com"_ns)); EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("localhost"_ns)); // Test exact domain matching Preferences::SetCString("network.lna.skip-domains", "example.com,test.org"_ns); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("example.com"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("test.org"_ns)); EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("sub.example.com"_ns)); EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("example.org"_ns)); EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("notexample.com"_ns)); // Test wildcard domain matching Preferences::SetCString("network.lna.skip-domains", "*.example.com,*.test.org"_ns); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("sub.example.com"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("deep.sub.example.com"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("api.test.org"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA( "example.com"_ns)); // Should match exact domain too EXPECT_TRUE(ioService->ShouldSkipDomainForLNA( "test.org"_ns)); // Should match exact domain too EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("example.net"_ns)); EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("notexample.com"_ns)); // Test more suffix wildcard patterns Preferences::SetCString("network.lna.skip-domains", "*.local,*.internal,*.test"_ns); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("server.local"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("api.internal"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("service.test"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("deep.subdomain.local"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA( "local"_ns)); // Should match exact domain too EXPECT_TRUE(ioService->ShouldSkipDomainForLNA( "internal"_ns)); // Should match exact domain too EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("local.example.com"_ns)); EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("localhost"_ns)); EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("example.com"_ns)); // Test mixed patterns (exact and suffix wildcard) Preferences::SetCString( "network.lna.skip-domains", "localhost,*.dev.local,*.staging.com,production.example.com"_ns); // Exact matches EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("localhost"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("production.example.com"_ns)); // Suffix wildcard matches EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("api.dev.local"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("web.dev.local"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("dev.local"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("test.staging.com"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("api.staging.com"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("staging.com"_ns)); // Non-matches EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("example.com"_ns)); EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("dev.example.com"_ns)); EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("staging.example.com"_ns)); // Test with whitespace and empty entries Preferences::SetCString( "network.lna.skip-domains", " example.com , , *.test.local , admin.internal "_ns); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("example.com"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("api.test.local"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("admin.internal"_ns)); EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("test.com"_ns)); // Test invalid patterns (unknown patterns treated as exact match) Preferences::SetCString("network.lna.skip-domains", "example.com,invalid.pattern"_ns); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA( "example.com"_ns)); // Valid exact match EXPECT_TRUE(ioService->ShouldSkipDomainForLNA( "invalid.pattern"_ns)); // Treated as exact match EXPECT_FALSE(ioService->ShouldSkipDomainForLNA("test.com"_ns)); // No match // Test case sensitivity Preferences::SetCString("network.lna.skip-domains", "Example.COM,*.Test.ORG"_ns); EXPECT_TRUE( ioService->ShouldSkipDomainForLNA("Example.COM"_ns)); // Exact case match EXPECT_FALSE(ioService->ShouldSkipDomainForLNA( "example.com"_ns)); // Different case (no match) EXPECT_TRUE(ioService->ShouldSkipDomainForLNA( "api.Test.ORG"_ns)); // Wildcard case match EXPECT_FALSE(ioService->ShouldSkipDomainForLNA( "api.test.org"_ns)); // Different case (no match) // Test plain "*" matches all domains Preferences::SetCString("network.lna.skip-domains", "*"_ns); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("example.com"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("test.org"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("localhost"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("any.domain.here"_ns)); EXPECT_TRUE(ioService->ShouldSkipDomainForLNA("server.local"_ns)); // Reset preference for cleanup Preferences::SetCString("network.lna.skip-domains", ""_ns); }