cdf9dcd210
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.
152 lines
6.5 KiB
C++
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");
|
|
}
|