fix(topology/allocator): widen default subnet base to /12 for mass-scale

A 30-LAN generate request already fits in 172.20.0.0/16, but trees
with depth/branching that multiply past 256 (e.g. depth=6,
branching=4 ≈ 5k LANs) hit AllocatorExhausted before the first
write.

SubnetAllocator now accepts a full CIDR base ("172.16.0.0/12" →
4096 /24s) in addition to the legacy two-octet shorthand ("172.20",
auto-lifted to /16). The parent must be ≤/24; a /24 base yields
exactly one slot. Iteration order is preserved for /16 bases so
existing topologies keep their third-octet sweep; /12 adds a
second-octet dimension underneath.

Defaults bumped to 172.16.0.0/12: TopologyConfig.subnet_base_prefix,
/next-subnet query param, and the mutator's add-LAN fallback. The
field pattern widens to accept CIDR. create-blank and manual LAN
CRUD still use "10.0" (lifts to /16) — one DMZ LAN per topology,
256 is plenty.
This commit is contained in:
2026-04-24 18:57:55 -04:00
parent 207f791684
commit f3408d5e62
5 changed files with 100 additions and 15 deletions

View File

@@ -30,8 +30,16 @@ class TopologyConfig(BaseModel):
# from its LAN to a non-parent, non-child LAN. 0.0 yields a tree.
cross_edge_probability: float = Field(default=0.0, ge=0.0, le=1.0)
# IP allocation base. LANs get sequential /24s starting here.
subnet_base_prefix: str = Field(default="172.20", pattern=r"^\d{1,3}\.\d{1,3}$")
# IP allocation base. LANs get sequential /24s carved out of this
# network. Accepts either a full CIDR (e.g. ``172.16.0.0/12`` for
# 4096 slots) or the legacy two-octet shorthand ``172.20`` which
# auto-lifts to ``172.20.0.0/16`` (256 slots). Default is a /12
# so mass-scale topologies (depth/branching trees with >256 LANs)
# don't exhaust the pool on first generation.
subnet_base_prefix: str = Field(
default="172.16.0.0/12",
pattern=r"^\d{1,3}\.\d{1,3}(\.\d{1,3}\.\d{1,3}/\d{1,2})?$",
)
# Service selection — reuses decnet.fleet.build_deckies' randomizer.
randomize_services: bool = Field(default=True)