/* 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<TestCase> 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<TestCase> 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);
}