Break the network isolation feature into a 6-commit sequence
Documentation only, no code: config schema + subnet/IPv6 allocation + -n/--network CRUD, persistent netns primitives, bridge provisioning (also doubles as reboot reconciliation), veth join at -r/--run time, -p port forwarding, then a crash-orphan cleanup sweep. Each step is independently buildable and manually verifiable, since the full feature is too large for one commit.
This commit is contained in:
+124
-3
@@ -1,7 +1,8 @@
|
||||
# Network isolation design
|
||||
|
||||
Status: design confirmed, not yet broken into a file-by-file implementation
|
||||
plan; no code written yet. Captured 2026-08-30 on the `networking` branch.
|
||||
Status: design confirmed, broken into a commit sequence below (see
|
||||
"Implementation plan: commit sequence"); no code written yet. Captured
|
||||
2026-08-30 on the `networking` branch.
|
||||
|
||||
## Goal
|
||||
|
||||
@@ -158,7 +159,127 @@ cgroups (`session_cgroup.{h,cpp}`), plus a `--clean-processes`-style sweep for
|
||||
anything orphaned by a crash, so a dead `slocker-lite` doesn't leave stray
|
||||
bridges/veths/iptables rules behind forever.
|
||||
|
||||
## Explicitly out of scope for now
|
||||
## Implementation plan: commit sequence
|
||||
|
||||
The whole feature is too large for one commit (unlike, say, `--kill`, which
|
||||
landed as a single commit despite touching several new files). Split into six
|
||||
commits, each a coherent, independently buildable and manually verifiable
|
||||
unit, in dependency order. Docs (`CLAUDE.md`/`README.md`) get updated *within*
|
||||
each commit, matching this branch's existing practice — not saved for a final
|
||||
pass.
|
||||
|
||||
1. **Config schema + subnet/IPv6 allocation + `-n/--network create/list/delete`
|
||||
(config-only, no host-side effects yet)**
|
||||
- `config_file.{h,cpp}`: new `NetworkEntry {name, kind, subnet, ipv6,
|
||||
subnet6}` (`kind` = `extern`/`intern`), a `networks` section — directly
|
||||
parallel to `VolumeEntry`/`volumes`.
|
||||
- New `network_subnet.{h,cpp}`: IPv4 `/24` auto-allocation starting at
|
||||
`10.168.0.0/24` incrementing per existing network, `--subnet` override;
|
||||
paired IPv6 ULA `/64` auto-allocation (`fd00:168:0:N::/64`) when enabled
|
||||
(default), `--subnet6` override, `--no-ipv6` to skip. Pure allocation
|
||||
logic against the already-loaded config's existing networks — no kernel/
|
||||
`ip`/`iptables` calls in this commit.
|
||||
- `cli_args.{h,cpp}`: `Mode::network`/`list_networks`/`delete_network`,
|
||||
`-n`/`--network` (`required_argument`, single token = name; separate
|
||||
`--extern`/`--intern`/`--subnet`/`--no-ipv6`/`--subnet6` flags for the
|
||||
create case), `--list-networks`, `--delete-network <name>` — directly
|
||||
mirroring `-v/--volume`'s existing three-mode shape in the same file.
|
||||
- `commands.cpp`: `create_network_command()`/`list_networks_command()`/
|
||||
`delete_network_command()` — mirroring `create_volume_command()`/
|
||||
`list_volumes_command()`/`delete_volume_command()`, including the shared
|
||||
tab-aligned listing helper.
|
||||
- Verify: `-n mynet --extern`, `-n other --intern --no-ipv6`,
|
||||
`--list-networks` shows both with correct kind/subnet, `--subnet`/
|
||||
`--subnet6` overrides land correctly in `config.yaml`, `--delete-network`
|
||||
removes an entry. No bridges/namespaces/iptables rules exist yet — purely
|
||||
config bookkeeping, same as a freshly-created volume before it's ever
|
||||
mounted.
|
||||
|
||||
2. **Persistent network namespace primitives (generic infra for `intern`
|
||||
networks)**
|
||||
- New `persistent_netns.{h,cpp}`: create/find/remove a persistent network
|
||||
namespace kept alive with no process in it, the way `ip netns add` does
|
||||
(bind-mount a fresh namespace's `ns/net` onto a path that outlives the
|
||||
creating process) — narrow, reusable infra, no `intern`/`extern`
|
||||
branching or bridge logic here (parallels how `session_cgroup.{h,cpp}`
|
||||
stayed narrowly scoped to cgroup mechanics only).
|
||||
- Not wired into `-n/--network` yet in this commit.
|
||||
- Verify: a small manual exercise (or a `-t/--test` addition) creating a
|
||||
persistent namespace, confirming it survives after the creating process
|
||||
exits, then removing it.
|
||||
|
||||
3. **Bridge provisioning for a network (idempotent — this is also the reboot-
|
||||
reconciliation mechanism, not a separate later step)**
|
||||
- New `network_bridge.{h,cpp}`: given a `NetworkEntry`, ensure its bridge
|
||||
exists and is configured — creating it if missing (idempotent, so this
|
||||
doubles as "reconcile after reboot" with no separate code path):
|
||||
- `extern`: bridge in the *host's own* root namespace; assign it the
|
||||
gateway IP from the network's subnet; `net.ipv4.ip_forward=1` (+ IPv6
|
||||
forwarding sysctl if `ipv6`); one iptables `MASQUERADE` rule for the
|
||||
subnet (`ip6tables` too, if `ipv6`).
|
||||
- `intern`: bridge inside its own dedicated `persistent_netns.h`
|
||||
namespace (commit 2); gateway IP assigned; no forwarding, no NAT rule
|
||||
— no route out at all.
|
||||
- Wire this "ensure provisioned" call into `create_network_command()` (so
|
||||
creating a network actually stands up its bridge immediately) — later
|
||||
commits also call it lazily before a join, covering the reboot case.
|
||||
- `check_required_dependencies()`-style availability check added for `ip`/
|
||||
`iptables` (and `ip6tables` when needed), alongside the existing
|
||||
`containers-storage`/`bwrap` check.
|
||||
- Verify: `-n mynet --extern` produces a real bridge with the expected
|
||||
gateway IP, `ip_forward` enabled, and a matching `MASQUERADE` rule
|
||||
(`ip link show`, `iptables -t nat -L`); an `intern` network's bridge
|
||||
exists in its own namespace with no such rule. Delete/recreate a
|
||||
network's config entry, delete its bridge by hand (`ip link del`), then
|
||||
trigger provisioning again (e.g. re-running `--network create` or the
|
||||
first join in commit 4) and confirm it comes back.
|
||||
|
||||
4. **Joining networks at `-r/--run` time: veth creation, IP assignment, route**
|
||||
- Repeatable `-n <name>` with `-r/--run` (`cli_args.cpp`, same
|
||||
repeatable-with-`-r` shape `-v/--volume` already has).
|
||||
- `commands.cpp`'s `run_container()`: once `bwrap`'s pid (and via
|
||||
`resolve_namespace_pid()`-style lookup, `sandbox_process.h`, its actual
|
||||
net namespace) is known — same timing hook `on_bwrap_pid_known`/
|
||||
`on_start` already provides for session locks/cgroups (`bwrap.cpp`) —
|
||||
for each joined network: ensure it's provisioned (commit 3, covers
|
||||
reboot recreation), create a veth pair, move the container-side end into
|
||||
the container's net namespace, attach the bridge-side end, assign the
|
||||
container's veth an IP from the subnet, and (for an `extern` join) set
|
||||
it as the default route.
|
||||
- This is the core connectivity commit — no veth pairs exist before it,
|
||||
regardless of how many networks are configured/joined.
|
||||
- Verify: two containers joined to the same `intern` network can ping each
|
||||
other and cannot reach the host or outside; a container joined to an
|
||||
`extern` network can reach the outside (and the host cannot reach *it*
|
||||
without commit 5's port forwarding); a container joined to both loses
|
||||
neither path (two interfaces, both functional).
|
||||
|
||||
5. **`-p` port forwarding**
|
||||
- `cli_args.cpp`: `-p [<network>:]<host-port>:<container-port>`,
|
||||
`<network>` optional (resolves to the container's sole `extern` network;
|
||||
error if ambiguous).
|
||||
- New `port_forward.{h,cpp}`: add/remove the iptables `DNAT`
|
||||
(`PREROUTING`) + `FORWARD ACCEPT` rule pair for one mapping, tied to the
|
||||
container's own session lifecycle the same create-on-start/
|
||||
remove-on-stop way `pid_file.{h,cpp}`/`session_cgroup.{h,cpp}` already
|
||||
are.
|
||||
- Verify: `-p 8080:80` against a container on an extern network answering
|
||||
on port 80 is reachable via `curl localhost:8080` from the host; the
|
||||
rule is gone after the container exits.
|
||||
|
||||
6. **Crash-orphan cleanup sweep**
|
||||
- Extend `--clean-processes` (or add a dedicated `--clean-networks`,
|
||||
whichever reads better once this is reached) to find and remove
|
||||
bridges/veths/iptables rules left behind by a `slocker-lite` that died
|
||||
before its own teardown ran — mirroring `clean_stale_sessions()`
|
||||
(`pid_file.cpp`)'s existing stale-pid-file sweep, but for host-global
|
||||
network state instead of pid files.
|
||||
- Verify: kill `-9` a running `-r/--run` session mid-flight (bypassing its
|
||||
normal cleanup), confirm the orphaned veth/iptables rule is detected and
|
||||
removed by the sweep, and that a *still-running* session's state is left
|
||||
untouched.
|
||||
|
||||
|
||||
|
||||
- **Rootless networking.** An earlier draft of this design considered a
|
||||
hybrid strategy (real bridge+veth when root, a simpler shared-network-
|
||||
|
||||
Reference in New Issue
Block a user