Add bridge provisioning for networks (root-only)

Commit 3/6 of the network isolation feature (docs/networking-design.md).
network_bridge.{h,cpp}: ensure_network_provisioned() stands up a
network's real bridge -- idempotent (checks `ip link show` first), so
this doubles as the reboot-reconciliation mechanism, no separate code
path. extern's bridge lives in the host's own root namespace with
net.ipv4.ip_forward + an iptables MASQUERADE rule for the subnet (+
IPv6 equivalents if enabled); intern's bridge lives inside its own
dedicated persistent namespace (persistent_netns.h) with no forwarding
or NAT at all -- a structural isolation boundary, not just a missing
rule. Bridge names are a deterministic FNV-1a hash of the network name
(not std::hash, whose value isn't guaranteed stable across a rebuild),
kept under Linux's 15-char interface name limit.

network_subnet.{h,cpp} gains ipv4_gateway_address()/
ipv6_gateway_address() (mask a CIDR to its network address, +1 for the
bridge's own ".1"). create_network_command() now calls
ensure_network_provisioned() before persisting the config entry -- a
network that fails to provision isn't saved.

Verified end-to-end as root (via a scoped doas rule): a real extern
network's bridge/gateway IPs/forwarding/NAT rule, and a real intern
network's isolated bridge with neither, both came up correctly; test
networks removed via --delete-network afterward.
This commit is contained in:
2026-08-30 12:50:55 +00:00
parent db3a9d82c7
commit 24b8ddcce7
8 changed files with 380 additions and 26 deletions
+45
View File
@@ -0,0 +1,45 @@
// 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.
#pragma once
#include "config_file.h"
// Checks that the external tools provisioning `network` needs are found in
// PATH, logging which are missing: always `ip`; `iptables`/`sysctl` (+
// `ip6tables` if `network.ipv6`) for `extern`; `nsenter` for `intern` (to
// reach its dedicated persistent namespace). Same shape/spirit as
// commands.cpp's own check_required_dependencies(), kept separate since this
// file's tool set depends on the network's own kind/ipv6 setting.
bool check_network_dependencies(const NetworkEntry& network);
// Ensures `network`'s bridge (and, for `intern`, its dedicated persistent
// namespace -- persistent_netns.h) exists and is configured, creating
// whatever's missing:
// - extern: bridge in the host's own root namespace (this whole feature is
// root-only for now, see docs/networking-design.md, so no nsenter
// wrapping is needed to reach it); net.ipv4.ip_forward=1 (+ the IPv6
// forwarding sysctl if network.ipv6) and one iptables MASQUERADE rule
// (+ ip6tables if network.ipv6) for the subnet.
// - intern: bridge inside network's own dedicated persistent namespace
// (created here if it doesn't exist yet); no forwarding sysctl, no NAT
// rule -- no route out at all.
// Idempotent: a no-op (true) if the bridge already exists. This doubles as
// the mechanism that transparently recreates a network's host-side state
// after a reboot (nothing about it survives one except the config.yaml
// entry) -- there's no separate "reconcile" path; calling this again just
// recreates whatever's missing.
bool ensure_network_provisioned(const NetworkEntry& network);