Name the parent network and count the VLAN tags #3

Merged
gsadmin merged 2 commits from development into main 2026-08-18 02:19:12 +00:00
Owner

Replaces the seed-based network selection with a directly named parent network, and stops deriving VLAN tags from the addressing.

Why

-NetworkSeed was repeatable in the strict sense and unusable in the practical one:

  • The seed was not meaningful. Reaching a wanted /20 meant brute-forcing seeds until one landed on the right index.
  • The seed was not portable. The index was taken against the candidate list that remains after the host's own networks are excluded. Measured on a host holding 10.130.224.0/24, that removes one candidate, so N is 4095 rather than 4096 and every index above it shifts by one. Seed 4249 selects 10.130.240.0/20 there and 10.130.224.0/20 on a host with no network inside 10.0.0.0/8 — the adjacent, wrong block.

What changed

Was Now
-NetworkSeed 4249 -ParentNetwork 10.130.240.0/20
-NetworkCount 8 -ParentNetworkMaskLength 21
(fixed at /24, not exposed) -ParentNetworkSubnetMaskLength 24
VLAN tag = third octet -StartingVlanId 2, -VlanIdIncrement 1

Leaving -ParentNetwork unset still picks one at random, and the summary reports the argument that reproduces it:

Parent Network: 10.147.208.0/20 [16 network(s)] [Selected at random]
Reproduce This Plan: Specify -ParentNetwork 10.147.208.0/20 to produce this exact addressing again.

Counted VLAN tags

The derived tag read well while every network occupied a whole third octet, and broke the moment one did not — a /25 produced 32 networks carrying only 16 distinct tags. That had briefly forced a /24 cap on the subnet mask length. Counting removes the constraint at its source, so the cap is lifted back to /30 and /25 and /26 now yield 31 and 63 distinct tags.

The trade: a tag no longer tells you the address. In exchange the two are independent, and -VlanIdIncrement 10 can express 2, 12, 22 …, which a derived tag could not.

Notes

  • -BaseNetwork is now a ValidateSet of the three RFC 1918 ranges. It only bounds the random pick — an explicit -ParentNetwork is not required to sit inside it, or to avoid the host's networks. Both are warned about and then honoured.
  • ParentNetwork collided with the existing name for the first divided /24; that concept is now IsUntaggedNetwork. ParentInterfaceDevice is untouched.
  • A range of tags ending past 4094 throws before any are assigned.

Verification

  • Parse 0 failures, doc/parameter drift 0, 144 markdown links checked, 0 broken.
  • Both selection paths; parent mask /19/22; subnet mask /22/26; all three base networks; unaligned input; out-of-base and host-overlapping parents; both division guards; the 4094 ceiling.
  • Two consecutive -ParentNetwork runs produce identical addressing across all 48 interface, VLAN and scope entries; documents differ only in UUIDs, which regenerate by design.
  • End to end: defaults give tags 2–16 with matching descriptions and vlan0.N devices; /22 parent ÷ /26 at increment 10 gives 15 distinct tags from 2–142 — a combination the previous scheme could not express. Both exit 0.

One bug caught in testing: the two paths read the subnet mask from different cmdlets (Get-NetworkSummary spells it Mask, Get-Subnet spells it SubnetMask), so the host route command emitted an empty mask on the explicit path. Fixed.

Not restaged

docs/screenshots/ and samples/…-Sample.log are real captures from runs under the old scheme. The screenshot caption is annotated to say so and gives the names today's run would produce; the sample log is left as a historical transcript.

🤖 Generated with Claude Code

Replaces the seed-based network selection with a directly named parent network, and stops deriving VLAN tags from the addressing. ## Why `-NetworkSeed` was repeatable in the strict sense and unusable in the practical one: - **The seed was not meaningful.** Reaching a wanted `/20` meant brute-forcing seeds until one landed on the right index. - **The seed was not portable.** The index was taken against the candidate list that remains *after the host's own networks are excluded*. Measured on a host holding `10.130.224.0/24`, that removes one candidate, so `N` is `4095` rather than `4096` and every index above it shifts by one. Seed `4249` selects `10.130.240.0/20` there and `10.130.224.0/20` on a host with no network inside `10.0.0.0/8` — the adjacent, wrong block. ## What changed | Was | Now | | --- | --- | | `-NetworkSeed 4249` | `-ParentNetwork 10.130.240.0/20` | | `-NetworkCount 8` | `-ParentNetworkMaskLength 21` | | _(fixed at `/24`, not exposed)_ | `-ParentNetworkSubnetMaskLength 24` | | VLAN tag = third octet | `-StartingVlanId 2`, `-VlanIdIncrement 1` | Leaving `-ParentNetwork` unset still picks one at random, and the summary reports the argument that reproduces it: ``` Parent Network: 10.147.208.0/20 [16 network(s)] [Selected at random] Reproduce This Plan: Specify -ParentNetwork 10.147.208.0/20 to produce this exact addressing again. ``` ### Counted VLAN tags The derived tag read well while every network occupied a whole third octet, and broke the moment one did not — a `/25` produced 32 networks carrying only 16 distinct tags. That had briefly forced a `/24` cap on the subnet mask length. Counting removes the constraint at its source, so the cap is lifted back to `/30` and `/25` and `/26` now yield 31 and 63 distinct tags. The trade: a tag no longer tells you the address. In exchange the two are independent, and `-VlanIdIncrement 10` can express `2, 12, 22 …`, which a derived tag could not. ### Notes - `-BaseNetwork` is now a `ValidateSet` of the three RFC 1918 ranges. It only bounds the *random* pick — an explicit `-ParentNetwork` is not required to sit inside it, or to avoid the host's networks. Both are warned about and then honoured. - `ParentNetwork` collided with the existing name for the first divided `/24`; that concept is now `IsUntaggedNetwork`. `ParentInterfaceDevice` is untouched. - A range of tags ending past `4094` throws before any are assigned. ## Verification - Parse 0 failures, doc/parameter drift 0, 144 markdown links checked, 0 broken. - Both selection paths; parent mask `/19`–`/22`; subnet mask `/22`–`/26`; all three base networks; unaligned input; out-of-base and host-overlapping parents; both division guards; the `4094` ceiling. - Two consecutive `-ParentNetwork` runs produce **identical addressing across all 48 interface, VLAN and scope entries**; documents differ only in UUIDs, which regenerate by design. - End to end: defaults give tags 2–16 with matching descriptions and `vlan0.N` devices; `/22` parent ÷ `/26` at increment 10 gives 15 distinct tags from 2–142 — a combination the previous scheme could not express. Both exit 0. One bug caught in testing: the two paths read the subnet mask from different cmdlets (`Get-NetworkSummary` spells it `Mask`, `Get-Subnet` spells it `SubnetMask`), so the host route command emitted an empty mask on the explicit path. Fixed. ## Not restaged `docs/screenshots/` and `samples/…-Sample.log` are real captures from runs under the old scheme. The screenshot caption is annotated to say so and gives the names today's run would produce; the sample log is left as a historical transcript. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
gsadmin added 2 commits 2026-08-18 02:08:35 +00:00
The size of each individual network was fixed at a /24 by a parameter the plan
function held privately and the main script never surfaced, so the only way to
change how the parent network was divided was to edit the function. It is now
ParentNetworkSubnetMaskLength on both, defaulting to 24, which pairs it with
ParentNetworkMaskLength and makes the relationship between the two legible: one
says how large the whole range is, the other says how large each network inside
it is, and the count is what falls out.

It applies on both paths. It describes how the parent network is divided rather
than how the parent network is chosen, so the mask length that an explicitly
supplied parent network carries has no bearing on it. Only
ParentNetworkMaskLength is bypassed when a parent network is named, and the
distinction is now stated in the help of both parameters rather than left to be
inferred.

Testing it across a range of values surfaced a constraint that had not been
considered. The virtual local area network tag is the third octet of the network
address, so a network has to occupy a whole third octet to own a tag. A network
shorter than a /24 spans several and takes the first, which stays unique, but a
network longer than a /24 occupies only part of one, so several networks share a
third octet and are assigned the same tag. Measured against a /20 parent, a /25
produced thirty two networks carrying only sixteen distinct tags, and a /26
produced sixty four carrying sixteen.

Duplicate tags on one parent interface are not a valid virtual local area
network configuration, so the range is capped at /24 rather than allowed to emit
a document the appliance would refuse. Supporting smaller networks would mean
decoupling the tag from the address, which is the property that makes the
addressing readable, so the cap is the right side of that trade rather than a
limitation to be worked around later.

Verified with mask lengths from /22 through /26 against both a specified and a
randomly selected parent network, confirming that the tags stay distinct at every
accepted value, that both rejected values are refused by the range rather than by
producing a broken plan, and that the existing guard against a subnet mask no
longer than the parent mask still fires. An end to end run with a /20 parent
divided by /22 produced four networks with tags 244, 248 and 252 and exited 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tag was the third octet of the network address. That reads well while every
network happens to occupy a whole octet, and stops being possible the moment one
does not, because two networks that share an octet are assigned the same tag. It
had already forced a cap on the subnet mask length one commit earlier, since a
/25 produced thirty two networks carrying only sixteen distinct tags.

The tag is now counted. StartingVlanId gives the first tagged network its tag and
defaults to 2, and VlanIdIncrement advances it for each network after that and
defaults to 1, so a sixteen network deployment carries the tags 2 through 16. The
default leaves the tag of 1 unused, because a switch commonly treats it as its
own default network and colliding with that is awkward to diagnose.

Counting removes the constraint at its source rather than fencing it off. The tag
no longer has anything to do with the address, so it stays unique however the
parent network was divided, and the cap on the subnet mask length is lifted back
to a /30. The same /25 and /26 that collided now produce thirty one and sixty
three distinct tags.

The highest tag is computed before the loop runs, so a range that would pass 4094
throws and names the range it would have produced rather than emitting invalid
tags. A run starting at 4080 across fifteen networks is accepted and ends exactly
at 4094.

The increment also makes tag ranges expressible, which a derived tag could never
have offered. An increment of ten gives 2, 12, 22 and so on, which is useful when
a tag is meant to carry meaning of its own.

What is given up is that a tag no longer tells you the address. That was a real
convenience, but it was only ever available while the addressing cooperated, and
paying for it with a cap on how the parent network could be divided was the worse
side of the trade.

The screenshot walkthrough is annotated rather than restaged. Those frames are a
real run captured while the tag was still derived, so the caption now says so and
gives the names the same deployment would produce today.

Verified end to end with the defaults, which produced the tags 2 through 16 with
matching interface descriptions and vlan devices, and with a /22 parent divided
into /26 networks at an increment of ten, which is a combination the previous
scheme could not express at all and which produced fifteen distinct tags from 2
to 142. Both exited 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gsadmin merged commit 71c470f53b into main 2026-08-18 02:19:12 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Grace-Solutions/Invoke-OPNSenseVMDeployment#3