Add bridge name column to --list-networks

list_networks_command() now includes each network's bridge name
(recomputed via bridge_name(), not stored -- it's already a pure
deterministic function of the network name), tab-aligned the same way
as the existing name/kind/subnet columns, positioned before the
trailing unaligned IPv6 column. Useful for correlating a network entry
with its live host-side state (`ip link show <bridge>`,
`iptables -t nat -L`) without recomputing the hash by hand.

Verified as root via the doas rule against a fresh extern and intern
network.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
2026-08-30 17:15:31 +00:00
parent 53b859b7bf
commit 69a18924b4
4 changed files with 18 additions and 6 deletions
+5 -2
View File
@@ -177,8 +177,11 @@ Source layout (all under `src/`):
the entry appended to `config.networks` and persisted; a network that the entry appended to `config.networks` and persisted; a network that
fails to provision isn't saved. `list_networks_command()` reuses the same fails to provision isn't saved. `list_networks_command()` reuses the same
independently-per-column tab-alignment scheme as `list_processes_command()` independently-per-column tab-alignment scheme as `list_processes_command()`
(name/kind/subnet each aligned, then the IPv6 subnet — or `"(no ipv6)"` (name/kind/subnet/bridge each aligned the bridge name recomputed via
appended unaligned as the trailing column, nothing follows it). `bridge_name(network.name)``network_bridge.h` — rather than stored, since
it's already a pure deterministic function of the name — then the IPv6
subnet — or `"(no ipv6)"` — appended unaligned as the trailing column,
nothing follows it).
`delete_network_command()` currently only removes the config entry, the `delete_network_command()` currently only removes the config entry, the
same as `delete_volume_command()`'s default (non-`-full`) behavior — it same as `delete_volume_command()`'s default (non-`-full`) behavior — it
does not tear down the network's live bridge/namespace/iptables state (no does not tear down the network's live bridge/namespace/iptables state (no
+1 -1
View File
@@ -93,7 +93,7 @@ slocker-lite -V|--version
| `--delete-volume <name>` | Remove a named volume from the config. The host directory is left untouched. | | `--delete-volume <name>` | Remove a named volume from the config. The host directory is left untouched. |
| `--delete-volume-full <name>` | Like `--delete-volume`, but also recursively deletes the volume's host directory. | | `--delete-volume-full <name>` | Like `--delete-volume`, but also recursively deletes the volume's host directory. |
| `-n, --network <name>` | Create/manage a persistent named network: requires exactly one of `--extern` (a real Linux bridge in the host's own namespace, with NAT/forwarding set up so containers on it reach the host's real network) or `--intern` (a bridge inside its own dedicated, routeless namespace, only reachable by other containers on the same network). `--subnet <cidr>` overrides the auto-allocated IPv4 range (`10.168.0.0/24`, incrementing per network); `--no-ipv6` disables (and `--subnet6 <cidr>` overrides) the auto-allocated IPv6 range, on by default. `--no-veth` forces the tap+relay join fallback even on a kernel that supports veth (useful for testing that path; it's otherwise chosen automatically whenever the running kernel lacks veth support). With `--run`, instead joins `<name>` to the container as its own `eth<N>` interface with an address from the network's subnet; repeatable, no membership limit. Root-only for now. **Known gap**: on a network joined via the tap+relay fallback, peer-to-peer connectivity works, but reaching the network's own gateway (and thus the outside, for `extern`) does not yet — root cause unconfirmed, see [`docs/networking-design.md`](docs/networking-design.md). | | `-n, --network <name>` | Create/manage a persistent named network: requires exactly one of `--extern` (a real Linux bridge in the host's own namespace, with NAT/forwarding set up so containers on it reach the host's real network) or `--intern` (a bridge inside its own dedicated, routeless namespace, only reachable by other containers on the same network). `--subnet <cidr>` overrides the auto-allocated IPv4 range (`10.168.0.0/24`, incrementing per network); `--no-ipv6` disables (and `--subnet6 <cidr>` overrides) the auto-allocated IPv6 range, on by default. `--no-veth` forces the tap+relay join fallback even on a kernel that supports veth (useful for testing that path; it's otherwise chosen automatically whenever the running kernel lacks veth support). With `--run`, instead joins `<name>` to the container as its own `eth<N>` interface with an address from the network's subnet; repeatable, no membership limit. Root-only for now. **Known gap**: on a network joined via the tap+relay fallback, peer-to-peer connectivity works, but reaching the network's own gateway (and thus the outside, for `extern`) does not yet — root cause unconfirmed, see [`docs/networking-design.md`](docs/networking-design.md). |
| `--list-networks` | List all named networks (see `-n/--network`) with their kind, IPv4 subnet, and IPv6 subnet (or `(no ipv6)`). | | `--list-networks` | List all named networks (see `-n/--network`) with their kind, IPv4 subnet, bridge name, and IPv6 subnet (or `(no ipv6)`). |
| `--delete-network <name>` | Remove a named network from the config. | | `--delete-network <name>` | Remove a named network from the config. |
| `-p, --port-forward [<network>:]<host-port>:<container-port>` | With `--run`, forward a TCP port from the host into the container. `<network>` is optional, defaulting to the container's sole `--extern` network (an error if it joined more than one without specifying). Repeatable. Reachable via the host's real, externally-facing IP; `localhost`/loopback access has a known NAT-hairpinning limitation (see [`docs/networking-design.md`](docs/networking-design.md)). | | `-p, --port-forward [<network>:]<host-port>:<container-port>` | With `--run`, forward a TCP port from the host into the container. `<network>` is optional, defaulting to the container's sole `--extern` network (an error if it joined more than one without specifying). Repeatable. Reachable via the host's real, externally-facing IP; `localhost`/loopback access has a known NAT-hairpinning limitation (see [`docs/networking-design.md`](docs/networking-design.md)). |
| `--list-processes` | List running `--run` sessions found by their pid files under `$XDG_STATE_HOME/slocker-lite/run/`, with their pid, container name, and status (`running` or `exited`). | | `--list-processes` | List running `--run` sessions found by their pid files under `$XDG_STATE_HOME/slocker-lite/run/`, with their pid, container name, and status (`running` or `exited`). |
+2 -2
View File
@@ -233,8 +233,8 @@ void print_usage(const char* prog) {
" --run, instead join <name> to the container;\n" " --run, instead join <name> to the container;\n"
" may be repeated, no membership limit\n" " may be repeated, no membership limit\n"
" --list-networks list all named networks (see -n/--network)\n" " --list-networks list all named networks (see -n/--network)\n"
" with their kind, IPv4 subnet, and IPv6 subnet\n" " with their kind, IPv4 subnet, bridge name,\n"
" (or \"(no ipv6)\")\n" " and IPv6 subnet (or \"(no ipv6)\")\n"
" --delete-network <name>\n" " --delete-network <name>\n"
" remove a named network from the config\n" " remove a named network from the config\n"
" -p, --port-forward [<network>:]<host-port>:<container-port>\n" " -p, --port-forward [<network>:]<host-port>:<container-port>\n"
+10 -1
View File
@@ -467,25 +467,31 @@ int list_networks_command(const AppConfig& config) {
std::vector<std::string> names; std::vector<std::string> names;
std::vector<std::string> kinds; std::vector<std::string> kinds;
std::vector<std::string> subnets; std::vector<std::string> subnets;
std::vector<std::string> bridges;
names.reserve(config.networks.size()); names.reserve(config.networks.size());
kinds.reserve(config.networks.size()); kinds.reserve(config.networks.size());
subnets.reserve(config.networks.size()); subnets.reserve(config.networks.size());
bridges.reserve(config.networks.size());
size_t max_name_len = 0; size_t max_name_len = 0;
size_t max_kind_len = 0; size_t max_kind_len = 0;
size_t max_subnet_len = 0; size_t max_subnet_len = 0;
size_t max_bridge_len = 0;
for (const auto& network : config.networks) { for (const auto& network : config.networks) {
names.push_back(network.name); names.push_back(network.name);
kinds.push_back(network.kind == NetworkKind::extern_ ? "extern" : "intern"); kinds.push_back(network.kind == NetworkKind::extern_ ? "extern" : "intern");
subnets.push_back(network.subnet); subnets.push_back(network.subnet);
bridges.push_back(bridge_name(network.name));
max_name_len = std::max(max_name_len, names.back().size()); max_name_len = std::max(max_name_len, names.back().size());
max_kind_len = std::max(max_kind_len, kinds.back().size()); max_kind_len = std::max(max_kind_len, kinds.back().size());
max_subnet_len = std::max(max_subnet_len, subnets.back().size()); max_subnet_len = std::max(max_subnet_len, subnets.back().size());
max_bridge_len = std::max(max_bridge_len, bridges.back().size());
} }
// Independently tracked per column, same scheme as list_processes_command(). // Independently tracked per column, same scheme as list_processes_command().
size_t name_target_tabs = max_name_len / tab_width + 1; size_t name_target_tabs = max_name_len / tab_width + 1;
size_t kind_target_tabs = max_kind_len / tab_width + 1; size_t kind_target_tabs = max_kind_len / tab_width + 1;
size_t subnet_target_tabs = max_subnet_len / tab_width + 1; size_t subnet_target_tabs = max_subnet_len / tab_width + 1;
size_t bridge_target_tabs = max_bridge_len / tab_width + 1;
for (size_t i = 0; i < config.networks.size(); ++i) { for (size_t i = 0; i < config.networks.size(); ++i) {
size_t name_tabs_used = names[i].size() / tab_width; size_t name_tabs_used = names[i].size() / tab_width;
@@ -494,8 +500,11 @@ int list_networks_command(const AppConfig& config) {
size_t kind_tabs_needed = kind_target_tabs > kind_tabs_used ? kind_target_tabs - kind_tabs_used : 1; size_t kind_tabs_needed = kind_target_tabs > kind_tabs_used ? kind_target_tabs - kind_tabs_used : 1;
size_t subnet_tabs_used = subnets[i].size() / tab_width; size_t subnet_tabs_used = subnets[i].size() / tab_width;
size_t subnet_tabs_needed = subnet_target_tabs > subnet_tabs_used ? subnet_target_tabs - subnet_tabs_used : 1; size_t subnet_tabs_needed = subnet_target_tabs > subnet_tabs_used ? subnet_target_tabs - subnet_tabs_used : 1;
fmt::print("{}{}{}{}{}{}{}\n", names[i], std::string(name_tabs_needed, '\t'), kinds[i], size_t bridge_tabs_used = bridges[i].size() / tab_width;
size_t bridge_tabs_needed = bridge_target_tabs > bridge_tabs_used ? bridge_target_tabs - bridge_tabs_used : 1;
fmt::print("{}{}{}{}{}{}{}{}{}\n", names[i], std::string(name_tabs_needed, '\t'), kinds[i],
std::string(kind_tabs_needed, '\t'), subnets[i], std::string(subnet_tabs_needed, '\t'), std::string(kind_tabs_needed, '\t'), subnets[i], std::string(subnet_tabs_needed, '\t'),
bridges[i], std::string(bridge_tabs_needed, '\t'),
config.networks[i].ipv6 ? config.networks[i].subnet6 : std::string("(no ipv6)")); config.networks[i].ipv6 ? config.networks[i].subnet6 : std::string("(no ipv6)"));
} }
return 0; return 0;