// Copyright (C) 2026 Viorel Munteanu // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // [unit] tests for network_subnet.h's pure CIDR arithmetic -- no kernel/ip // calls of its own, so entirely self-contained. #include #include "network_subnet.h" TEST_CASE("is_valid_network_name", "[unit]") { CHECK(is_valid_network_name("mynet")); CHECK_FALSE(is_valid_network_name("")); CHECK_FALSE(is_valid_network_name("has:colon")); } TEST_CASE("is_valid_ipv4_cidr", "[unit]") { CHECK(is_valid_ipv4_cidr("10.168.0.0/24")); CHECK(is_valid_ipv4_cidr("0.0.0.0/0")); CHECK(is_valid_ipv4_cidr("255.255.255.255/32")); CHECK_FALSE(is_valid_ipv4_cidr("10.168.0.0/33")); CHECK_FALSE(is_valid_ipv4_cidr("not-an-ip/24")); CHECK_FALSE(is_valid_ipv4_cidr("10.168.0.0")); // no prefix length CHECK_FALSE(is_valid_ipv4_cidr("fdf0::1/64")); // IPv6, not IPv4 } TEST_CASE("is_valid_ipv6_cidr", "[unit]") { CHECK(is_valid_ipv6_cidr("fdf0:f243:f06f:168::/64")); CHECK(is_valid_ipv6_cidr("::/0")); CHECK_FALSE(is_valid_ipv6_cidr("fdf0::/129")); CHECK_FALSE(is_valid_ipv6_cidr("10.168.0.0/24")); // IPv4, not IPv6 } TEST_CASE("ipv4_cidrs_overlap: identical subnets overlap", "[unit]") { CHECK(ipv4_cidrs_overlap("10.168.0.0/24", "10.168.0.0/24")); } TEST_CASE("ipv4_cidrs_overlap: disjoint subnets don't overlap", "[unit]") { CHECK_FALSE(ipv4_cidrs_overlap("10.168.0.0/24", "10.168.1.0/24")); } TEST_CASE("ipv4_cidrs_overlap: a subnet containing another overlaps, either direction", "[unit]") { CHECK(ipv4_cidrs_overlap("10.168.0.0/16", "10.168.5.0/24")); CHECK(ipv4_cidrs_overlap("10.168.5.0/24", "10.168.0.0/16")); } TEST_CASE("ipv4_cidrs_overlap: an unparseable CIDR is treated as no overlap", "[unit]") { CHECK_FALSE(ipv4_cidrs_overlap("garbage", "10.168.0.0/24")); } TEST_CASE("ipv6_cidrs_overlap: same shape as the IPv4 case", "[unit]") { CHECK(ipv6_cidrs_overlap("fdf0:f243:f06f:168::/64", "fdf0:f243:f06f:168::/64")); CHECK_FALSE(ipv6_cidrs_overlap("fdf0:f243:f06f:168::/64", "fdf0:f243:f06f:169::/64")); CHECK(ipv6_cidrs_overlap("fdf0:f243:f06f::/48", "fdf0:f243:f06f:168::/64")); } TEST_CASE("allocate_ipv4_subnet: first block when nothing exists yet", "[unit]") { auto subnet = allocate_ipv4_subnet({}); REQUIRE(subnet.has_value()); CHECK(*subnet == "10.168.0.0/24"); } TEST_CASE("allocate_ipv4_subnet: skips a subnet already in use", "[unit]") { std::vector existing = {{"taken", NetworkKind::extern_, "10.168.0.0/24", true, "", true}}; auto subnet = allocate_ipv4_subnet(existing); REQUIRE(subnet.has_value()); CHECK(*subnet == "10.168.1.0/24"); } TEST_CASE("allocate_ipv4_subnet: also respects a manually --subnet-overridden entry", "[unit]") { // Not itself an exact 10.168..0/24 block (a real --subnet override // need not be), but still overlaps n=0's candidate and must be // skipped, via ipv4_cidrs_overlap() -- not just an exact-match check. std::vector existing = {{"manual", NetworkKind::intern, "10.168.0.128/25", true, "", true}}; auto subnet = allocate_ipv4_subnet(existing); REQUIRE(subnet.has_value()); CHECK(*subnet == "10.168.1.0/24"); } TEST_CASE("allocate_ipv6_subnet: first block when nothing exists yet", "[unit]") { auto subnet = allocate_ipv6_subnet({}); REQUIRE(subnet.has_value()); CHECK(*subnet == "fdf0:f243:f06f:168::/64"); } TEST_CASE("allocate_ipv6_subnet: skips a subnet already in use", "[unit]") { std::vector existing = { {"taken", NetworkKind::extern_, "10.168.0.0/24", true, "fdf0:f243:f06f:168::/64", true}}; auto subnet = allocate_ipv6_subnet(existing); REQUIRE(subnet.has_value()); CHECK(*subnet == "fdf0:f243:f06f:169::/64"); } TEST_CASE("allocate_ipv6_subnet: an ipv6-disabled existing entry doesn't block reuse of its subnet6", "[unit]") { // A network created with --with-ipv6=false has ipv6=false and an empty subnet6 // -- nothing to collide with, so this is really just confirming // allocate_ipv6_subnet() doesn't crash/misbehave on such an entry. std::vector existing = {{"v4only", NetworkKind::extern_, "10.168.0.0/24", false, "", true}}; auto subnet = allocate_ipv6_subnet(existing); REQUIRE(subnet.has_value()); CHECK(*subnet == "fdf0:f243:f06f:168::/64"); } TEST_CASE("ipv4_gateway_address: masks down to the network address and sets the host bits to .1", "[unit]") { CHECK(ipv4_gateway_address("10.168.0.0/24") == "10.168.0.1/24"); // Not already a canonical network address -- still masks down first. CHECK(ipv4_gateway_address("10.168.0.5/24") == "10.168.0.1/24"); } TEST_CASE("ipv4_gateway_address: nullopt on an unparseable CIDR", "[unit]") { CHECK_FALSE(ipv4_gateway_address("garbage").has_value()); } TEST_CASE("ipv6_gateway_address: masks down and sets the host bits to ::1", "[unit]") { CHECK(ipv6_gateway_address("fdf0:f243:f06f:168::/64") == "fdf0:f243:f06f:168::1/64"); } TEST_CASE("ipv4_host_address: n=1 matches the gateway address", "[unit]") { CHECK(ipv4_host_address("10.168.0.0/24", 1) == ipv4_gateway_address("10.168.0.0/24")); } TEST_CASE("ipv4_host_address: n=2, 3 are distinct successive addresses", "[unit]") { CHECK(ipv4_host_address("10.168.0.0/24", 2) == "10.168.0.2/24"); CHECK(ipv4_host_address("10.168.0.0/24", 3) == "10.168.0.3/24"); } TEST_CASE("ipv4_host_address: carries across an octet boundary", "[unit]") { CHECK(ipv4_host_address("10.168.0.0/16", 256) == "10.168.1.0/16"); } TEST_CASE("ipv4_host_address: nullopt when n doesn't fit the host-bit width", "[unit]") { // A /24 has 8 host bits -- 256 hosts (0..255), so n=300 doesn't fit. CHECK_FALSE(ipv4_host_address("10.168.0.0/24", 300).has_value()); } TEST_CASE("ipv6_host_address: n=2 is a distinct address from the gateway", "[unit]") { CHECK(ipv6_host_address("fdf0:f243:f06f:168::/64", 2) == "fdf0:f243:f06f:168::2/64"); }