Files
slocker-lite/src/network_bridge.h
T
ceamac 534136339f Add --delete-network-full to fully tear down a network's live state
--delete-network only ever removed the config entry, leaving the
bridge/iptables/persistent-namespace state behind. Since
ensure_network_provisioned() treats "bridge exists" as "already fully
provisioned" and skips re-adding anything, a network recreated with
the same name after a plain --delete-network silently never got a
fresh MASQUERADE rule if the old one had been removed separately by
hand -- the bridge itself was still there the whole time.

teardown_network_state() (network_bridge.{h,cpp}) is the reverse of
ensure_network_provisioned(): for extern, removes the MASQUERADE
rule(s) then deletes the bridge; for intern, removes the whole
persistent namespace in one step (destroys the bridge inside it too,
no separate ip link del needed). Deliberately leaves the IPv4/IPv6
forwarding sysctls alone -- those are global host state shared across
every extern network, not per-network. Each step is best-effort
(teardown_step(), logging a warning not an error on failure) since a
step "failing" because that piece was already gone by hand is the
expected case this exists to handle, not a reason to abort --
delete_network_command() doesn't gate the config removal on any of
this succeeding, unlike delete_volume_command()'s own -full variant.

--delete-network-full wired into cli_args.{h,cpp} the same way
--delete-volume-full is.

Verified as root via the doas rule: an extern network's bridge and
MASQUERADE rule were both confirmed gone after --delete-network-full,
and recreating a network with the same name went through
provision_bridge() fresh instead of short-circuiting on a stale
bridge_exists() check -- fixing exactly the gap reported (a manually
removed MASQUERADE rule never came back on delete+recreate). An intern
network's persistent namespace was likewise confirmed fully removed
and recreatable without conflict.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
2026-08-30 17:59:52 +00:00

109 lines
6.1 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.
#pragma once
#include <string>
#include <vector>
#include "config_file.h"
// A stable, deterministic, <=15-character (Linux's IFNAMSIZ - 1 limit)
// interface name for `network_name`'s bridge: "slk" + 8 hex chars of a
// hand-rolled FNV-1a hash (not std::hash<std::string>(), whose value is
// implementation-defined and not guaranteed stable across a rebuild with a
// different standard library, which would silently orphan an
// already-provisioned bridge). Exported (not just this file's own internal
// helper) so network_join.h can attach a container's veth to the exact same
// bridge this file provisioned.
std::string bridge_name(const std::string& network_name);
// Wraps `argv` so it runs wherever `network`'s bridge actually lives: as-is
// for `extern` (the host's own root namespace -- this whole feature is
// root-only for now, so slocker-lite's own current namespace already is the
// right one); through `nsenter --net=<persistent path>` for `intern` (its
// own dedicated namespace, persistent_netns.h). Exported so network_join.h
// can run its own veth-setup commands (creating the pair, attaching the
// bridge-side end) in that same place, not just this file's own
// provisioning commands.
std::vector<std::string> wrap_for_network(const NetworkEntry& network, std::vector<std::string> argv);
// 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);
// Probes whether the running kernel supports veth (CONFIG_VETH) at all,
// independent of any one network's own `veth` policy flag
// (config_file.h's NetworkEntry::veth) -- the two are separate gates, same
// split as bwrap.cpp's own kernel-capability-vs-policy design for
// --unshare-xxx. Forks a child that unshare(CLONE_NEWNET)s into a throwaway
// namespace and attempts `ip link add ... type veth peer name ...` there
// (via the existing run_process()) -- the whole namespace, and anything
// created in it, vanishes with the child, so no cleanup is needed either
// way. The result is cached (function-local static) since a container
// joining several networks in one run would otherwise probe once per join
// for no reason -- this is a fixed fact about the running kernel, not
// something that varies per network.
bool probe_veth_support();
// A network should use veth to join a container (rather than the
// network_tap_relay.h fallback) exactly when its own `veth` policy flag
// allows it *and* the kernel actually supports veth -- both must hold, the
// same "policy and capability are independent gates" pattern
// namespace_policy_enabled() (bwrap.cpp) already uses for --unshare-xxx.
bool should_use_veth(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);
// Tears down `network`'s live host-side state -- the reverse of
// ensure_network_provisioned(). For `extern`: removes the MASQUERADE
// rule(s) (`ip6tables` first, if `ipv6`, then `iptables` -- purely for
// tidiness, since iptables doesn't require the interface a rule references
// to still exist), then deletes the bridge itself. For `intern`: removes
// the whole persistent namespace (persistent_netns.h) in one step, which
// destroys everything inside it -- the bridge included -- with no separate
// `ip link del` needed. Deliberately does **not** touch the IPv4/IPv6
// forwarding sysctls `provision_bridge()` enables for `extern` -- those are
// global host state shared across every `extern` network, not per-network,
// so disabling them here could break others still relying on them.
// Best-effort, like every other host-global teardown in this project
// (`remove_session_cgroup()`, `remove_port_forward()`): logs a warning and
// keeps going past any individual step that fails, rather than stopping --
// a step failing because that piece was already removed by hand (see
// ensure_network_provisioned()'s own existence-check caveat above) is the
// common case this exists to handle gracefully, not an error to abort on.
void teardown_network_state(const NetworkEntry& network);