Files
slocker-lite/tests/unit/test_network_subnet.cpp
ceamac cdf9dcd210 Add global.with-veth/with-ipv6 config defaults for -n/--network creation
Replaces --no-ipv6/--no-veth (plain flags) with --with-ipv6/--with-veth,
each taking an explicit true/false value (e.g. --with-veth=false), parsed
via the same parse_bool_flag() the config file itself already uses (now
exported from config_file.h so cli_args.cpp can reuse it).

create_network_command() now resolves ipv6/veth as CLI flag -> config's own
global.with-ipv6/global.with-veth -> true, so a host that always wants the
tap+relay fallback (or no IPv6) can set it once in the config instead of
passing the flag on every network creation. -w/--write-config fills in both
new keys like the existing six unshare-* bools.
2026-09-05 08:36:12 +00:00

152 lines
6.5 KiB
C++

// 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 <catch2/catch_test_macros.hpp>
#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<NetworkEntry> 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.<n>.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<NetworkEntry> 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<NetworkEntry> 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<NetworkEntry> 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");
}