Use a genuinely random ULA /48 for auto-allocated IPv6 subnets

The old fd00:168:0::/48 base was never actually generated via RFC
4193's randomization procedure -- just a memorable placeholder chosen
to visibly pair with the IPv4 10.168.x.x scheme. Replaced with
fdf0:f243:f06f::/48, a real randomly-generated ULA prefix.

That /48 consumes all three "identity" hextets a ULA prefix has room
for, leaving only the subnet-id (4th) hextet -- the same one the
per-network auto-allocation index already lived in -- with nowhere
left to also place a fixed "168" marker without colliding with either
the random prefix or the index itself. Per the user's own choice
(offered two options): the per-network index is now offset by a
constant 168 instead of matching IPv4's index number-for-number, so
the first auto-allocated network's IPv6 block is
fdf0:f243:f06f:168::/64 (paired with 10.168.0.0/24), second is
...:169::/64 (paired with 10.168.1.0/24), and so on -- deterministic
and still visibly project-stamped, just via a constant offset instead
of an identical digit.

network_subnet.cpp's new ipv6_ula_prefix48/ipv6_subnet_id_base
constants hold the new prefix and offset.

Verified as root via the doas rule: two freshly created extern
networks got fdf0:f243:f06f:168::/64 and fdf0:f243:f06f:169::/64
exactly as expected, correctly paired with their IPv4 subnets.

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 18:49:15 +00:00
parent 8a0d800478
commit cf166ed3b4
5 changed files with 100 additions and 33 deletions
+43 -8
View File
@@ -103,13 +103,18 @@ than one extern network and didn't disambiguate.
network (`10.168.1.0/24`, `10.168.2.0/24`, ...), with an optional
`--subnet <cidr>` override at `-n/--network` creation time.
- **IPv6 is a per-network on/off option, defaulting to enabled.** When on,
also auto-allocates a ULA `/64` alongside the IPv4 block from a matching
incrementing base (proposed: `fd00:168:0:1::/64`, `fd00:168:0:2::/64`, ...
— mirroring the `168` from the v4 base so the two are visibly paired), with
a `--subnet6 <cidr>` override. `--no-ipv6` at creation time skips both the
v6 allocation and the IPv6-forwarding sysctl for that network entirely
(there's no `ip6tables` setup to skip — see "Mechanism" above for why one
is never added at all).
also auto-allocates a ULA `/64` alongside the IPv4 block: a randomly
generated `/48` (RFC 4193 — `fdf0:f243:f06f::/48`, replacing an earlier
`fd00:168:0::/48` placeholder that was never actually randomly generated;
see the "Resolved" IPv6 randomization section further below) with the
subnet-id hextet offset by a fixed `168``fdf0:f243:f06f:168::/64`,
`fdf0:f243:f06f:169::/64`, ... — keeping the same project-recognizable
`168` stamp the old scheme's fixed 2nd hextet had, now as a constant
offset rather than a numerically-identical index. `--subnet6 <cidr>`
overrides it per network as before. `--no-ipv6` at creation time skips
both the v6 allocation and the IPv6-forwarding sysctl for that network
entirely (there's no `ip6tables` setup to skip — see "Mechanism" above for
why one is never added at all).
## CLI surface
@@ -195,7 +200,9 @@ pass.
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
paired IPv6 ULA `/64` auto-allocation (`fd00:168:0:N::/64` at the time —
**later replaced with a genuinely randomly generated `/48`, see the
"Resolved" IPv6 randomization section further below**) 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.
@@ -489,6 +496,34 @@ way later) — only the `ip6tables` MASQUERADE call itself, and the
still means "skip IPv6 addressing entirely," an orthogonal decision from
whether NAT is ever attempted for the addresses that are assigned.
## Resolved: IPv6 base prefix is now genuinely randomly generated
**Trigger**: a user asked directly, once IPv6 NAT was already dropped (see
above) — the original `fd00:168:0::/48` base was never actually generated
via RFC 4193's randomization procedure, just a memorable placeholder picked
to visibly pair with the IPv4 `10.168.x.x` scheme. Since ULA's whole
collision-avoidance property depends on the /48 actually being randomly
chosen (not on it being memorable), a real generator was worth using.
**Replaced with `fdf0:f243:f06f::/48`** (user-provided, from an external ULA
generator). Confirmed with the user (`AskUserQuestion`, two options offered)
how to keep the old scheme's recognizable `168` stamp: the /48 alone
consumes all three "identity" hextets a ULA prefix has room for, leaving
only the subnet-id (4th) hextet — the same one the per-network auto-
allocation index already lived in — with nowhere left to also place a fixed
marker without colliding with either the random prefix or the index. Chosen
answer: **offset the per-network index by a constant `168`** rather than
matching IPv4's index number-for-number as before. First auto-allocated
network's IPv6 block is now `fdf0:f243:f06f:168::/64` (paired with IPv4's
`10.168.0.0/24`), second is `fdf0:f243:f06f:169::/64` (paired with
`10.168.1.0/24`), and so on — deterministic and still visibly
project-stamped, just via a constant offset instead of an identical digit.
`network_subnet.cpp`'s `ipv6_ula_prefix48`/`ipv6_subnet_id_base` constants
hold the new prefix and the `168` offset respectively. Verified as root via
the `doas` rule: two freshly created `extern` networks got
`fdf0:f243:f06f:168::/64` and `fdf0:f243:f06f:169::/64` exactly as
expected, correctly paired with `10.168.0.0/24`/`10.168.1.0/24`.
## Explicitly out of scope for now
- **Rootless networking.** An earlier draft of this design considered a