Segment policy, shared gateways, and what the model leaves out

Asked whether a real setup is coverable — router, modem, access points —
the answer splits, and one part was a genuine gap.

Most equipment is invisible and the omission is deliberate. The test: does
the device change what an IP packet can do? A switch moves frames within a
segment. An access point bridges wireless clients onto one — a machine on
wifi and a machine on cable are the same machine to IP. A controller
configures equipment and has no packets of its own. Modelling any of them
adds a fixture with no fault to catch.

Two entries in that list do matter. A modem in bridge mode is a media
converter and invisible; in router mode it is a second gateway, which is
double NAT — expressible as nested segments, but publishing through two
gateways still is not, and that is now named as the one real absence.

And VLANs are segments, which exposed the gap: inter-segment policy was
inexpressible. inbound: is a HOST firewall, per machine. A segmented
router enforcing rules between networks is a different thing and blocks
traffic regardless of what the destination thinks — a node behind such a
rule cannot be reached even by a peer that knows exactly where it is.

policy: states it as a fact about a pair rather than a property of either,
defaulting to allowed and asymmetric by design, because the useful
configuration is almost always one-directional.

Segments may also share a gateway: identical gateway declarations mean one
gateway machine, not two, because that is what a VLAN-capable router is —
and two routers sharing an address would not work anyway.
This commit is contained in:
2026-08-23 23:32:30 +02:00
parent 274bd3b304
commit b1874f1d0f
+93 -5
View File
@@ -308,6 +308,86 @@ not first.
- **Steps.** A scenario is a desired state. Anything expressed as an ordered list of actions
belongs in the lifecycle, not the declaration.
## What the model deliberately does not contain
A real network is full of equipment: a modem, a router, switches, access points, controllers.
Almost none of it appears here, and the omission is deliberate rather than an oversight.
**The test is whether a device changes what an IP packet can do.** If two machines can exchange
packets, at the same addresses, with the same reachability and the same MTU, whether or not the
device exists — then the device is invisible to the mesh, and modelling it would add a fixture
with no fault to catch.
| Equipment | Modelled? | Why |
|---|---|---|
| **Switch** | no | Moves frames within a segment. Two machines on a switch are two machines on a segment. |
| **Access point** | no | Bridges wireless clients onto a segment. A machine on wifi and a machine on cable are the same machine to IP. |
| **Network controller** | no | Configures equipment. Its effects appear as segments and policy; it has no packets of its own. |
| **Cabling, PoE, uplink speed** | no | Change performance and availability, not reachability. |
| **Router / security gateway** | **yes — it *is* the gateway** | Translation, forwarding and inter-segment policy all live here. |
| **ISP modem** | **only in router mode** | In bridge mode it is a media converter and invisible. Doing its own NAT, it is a second gateway — and that is double NAT. |
| **VLANs** | **yes — they are segments** | Machines on separate VLANs cannot reach each other except through the router, which is the definition of a separate segment. |
| **Inter-VLAN firewall rules** | **yes** — see below | A rule stopping one segment reaching another is a reachability fact, and the mesh will hit it. |
The two entries worth dwelling on are the ones where a common household setup produces
something the mesh has to survive.
**A modem in router mode gives you double NAT.** Your gateway holds a private address from the
modem, which holds the public one. Forwarding then requires a rule on *both*, and one of them
may not be configurable. This is expressible as nested segments — `to:` chains — but publishing
through two gateways is not, and remains open.
**Segmented networks are the common case, not the exotic one.** A router with separate networks
for trusted machines, guests and devices is ordinary, and the rules between them are ordinary
too. A mesh node on one segment and a mesh node on another are, as far as reachability goes, on
different networks that happen to share a gateway.
## Segments may share a gateway
Several segments can name the same parent and the same external address. That is one router
with several networks behind it, which is what a VLAN-capable gateway is:
```yaml
segments:
trusted:
kind: private
cidr: [192.168.1.0/24]
gateway: { to: internet, address: 203.0.113.50, nat: [v4], forwardable: true }
devices:
kind: private
cidr: [192.168.30.0/24]
gateway: { to: internet, address: 203.0.113.50, nat: [v4], forwardable: true }
```
Identical gateway declarations mean **one gateway machine**, not two. The lab materialises a
single router serving both, because that is what the topology being reproduced is — and two
routers sharing one address would not work anyway.
## Policy between segments
Sharing a gateway does not mean segments can reach each other. What they may do is stated
separately, because it is a fact about a pair rather than a property of either:
```yaml
policy:
- { from: devices, to: trusted, allow: false } # devices may not initiate to trusted
- { from: trusted, to: devices, allow: true } # the reverse is fine
```
Default is `true` between segments behind the same gateway, matching a router with no rules
configured. Asymmetry is the normal case and the reason this cannot be a single flag: the
useful configuration is almost always one-directional.
This is **not** the same as `machines[].inbound`, and conflating them loses a real distinction:
| | Enforced by | Blocks |
|---|---|---|
| `policy` | the gateway, between segments | everything crossing, regardless of what the destination thinks |
| `inbound` | the machine itself | unsolicited traffic that already reached it |
A mesh node behind a `policy` deny cannot be reached even by a peer that knows exactly where it
is — and that is a real topology, not a contrived one.
## Is this general? — the axes a setup can vary along
The question that matters is not *does this cover our mesh*, but **can it express any mesh**.
@@ -320,7 +400,9 @@ how the mesh behaves**. Bandwidth does not change correctness; MTU does.
| **Reachability** | attached · forwardable gateway · unforwardable gateway · isolated | yes | the core of the model, and **per family** |
| **Address family** | IPv4 · IPv6 · dual-stack · neither | yes | `cidr:` and `address:` take both; `nat:` names which families are translated |
| **Interfaces per machine** | one · several | yes | `at:` takes a list |
| **Reachability policy** | symmetric · asymmetric | yes | `inbound:` — a routable machine that refuses everything |
| **Reachability policy, host** | symmetric · asymmetric | yes | `inbound:` — a routable machine that refuses everything |
| **Reachability policy, network** | open · segmented · asymmetric between segments | yes | `policy:` — inter-segment rules, as a segmented router enforces |
| **Segments per gateway** | one · several behind one router | yes | identical gateway declarations mean one gateway machine |
| **Gateway state** | permanent · expiring mappings | yes | `mapping_ttl:` |
| **Path MTU** | standard · reduced | yes | `segments[].mtu` |
| **Overlapping ranges** | distinct · two sites both on `192.168.1.0/24` | yes | segments may carry the same range |
@@ -357,11 +439,17 @@ revisited — and it would be revisited by a real failure, which is the right tr
### The honest summary
The model now covers **where a machine sits** and **what the path between machines is like**,
across both address families, which together are what the mesh's reachability logic turns on.
The model now covers **where a machine sits**, **what the path between machines is like**, and
**what is permitted between them** — across both address families. Together those are what the
mesh's reachability logic turns on.
What it does not model is *change over time* beyond moving a machine, and *degradation* short of
failure. Both are absences chosen rather than overlooked.
It contains almost no equipment, by the test above: a device that does not change what an IP
packet can do has no fault for a scenario to catch. What it does contain is every device that
does — which turns out to be the router, and a modem only when the modem is also a router.
What it still does not model is *change over time* beyond moving a machine, *degradation* short
of failure, and *publishing through two gateways at once*. The first two are chosen; the third
is the one real absence, and it is exactly the double-NAT case.
## Open