534136339f
--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
109 lines
6.1 KiB
C++
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);
|