Files
hq/02-DECISIONS/0139-a-network-is-forwarded-because-a-module-declared-it.md
T
jschoubben 14ff89fa40 ADRs 0138 and 0139: an assignment binds an endpoint and says how far it reaches, and a network is forwarded because a module declared it
Both follow from the same rule the mesh is built on — a node's configuration is
composed from the modules assigned to it. Reach was settled separately by the
filter, the proxy's names and the certificate authority, so "this must not be
public" could not be written; it becomes one value on the assignment that all
three read. And the forward chain consulted two constants plus a typed list
although modules already declare their networks; it now forwards what they
declared, with the host rendering the addresses it allocated.
2026-09-28 23:03:32 +02:00

137 lines
8.3 KiB
Markdown

---
topic: what runs on it
status: accepted
date: 2026-09-28
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0137-a-machine-says-which-networks-it-routes.md
---
# 139. A network is forwarded because a module declared it
## Context
[ADR 0137](0137-a-machine-says-which-networks-it-routes.md), decided the same week, gave a machine a
way to say which networks it routes for its guests. It was written because the derived filter's
forward chain allowed two ranges named as constants in the control plane's source — the container
runtime's bridge pool, and part of the pool its compose files are given — with a comment admitting
the gap: *a machine whose runtime is configured with something else needs this to say so, which is a
thing the mesh cannot derive.*
**It can be derived, and from the right place.** Measured on the last machine still to be converged
([issue 141](../04-ISSUES/141-the-forward-chain-does-not-follow-the-modules/00-report.md)): twenty-one
container networks, nine inside the runtime's bridge pool, twelve in the other private range, and six
of those outside the constant's lower bound — so the flip would have cut their guests off exactly as
it did on the workstation that produced 0137.
Naming a range to cover the six is what 0137 provides for, and it is the wrong instrument. Of those
six networks, **four are networks the mesh's own modules declare**, present as network resources in
the node's plan and created by the host because a module asked for them. **Two are the predecessor's
leftovers** — compose networks of services the mesh does not run. Any range wide enough to keep the
four forwards the two as well: a firewall widened by hand to protect networks that should not exist.
The mesh already knows which of the twenty-one are its own, because it made them.
**And the node's configuration is meant to follow the modules assigned to it.** That is the mesh's
founding shape — the machine runs modules, and its files, its filter and its accounts are composed
from what runs there ([ADR 0005](0005-the-node-host.md),
[ADR 0010](0010-delivery.md),
[ADR 0045](0045-a-machine-firewall-is-the-sum-of-what-it-listens-on.md)). The forward chain is the
one derived thing that consults a constant and a list a person types.
## Considered Options
1. **Keep 0137 as it stands** — two constants plus a named list. Rejected: the list is written in
addresses, and addresses are what the runtime allocates, so the only entry safe enough to keep a
machine working is wider than the truth. It cannot distinguish a network the mesh made from one
left behind, which is the distinction that decides whether forwarding it is correct.
2. **Derive it from what the machine reports.** Still rejected, on 0137's own grounds: a test bed
creates its bridge between one declaration and the next, and a filter that follows whatever
appeared on a machine is a firewall that widens itself. **This decision is not that** — see below.
3. **Have the control plane allocate each module network's range from a pool it owns,** so it can
render the address itself. Rejected: more machinery for no gain. The runtime already allocates and
the host already knows, and taking allocation over means the mesh owning an address space it has no
other reason to own.
4. **Have each module declare its network's range.** Rejected by
[ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md): a definition names no address,
and the same definition runs on machines whose runtimes have allocated differently.
## Decision
**A network is forwarded because a module declared it.** Per node, the forward chain forwards the
networks of the modules assigned there, and by default nothing else. A module unassigned stops being
forwarded at the next reconcile.
**The host resolves a declared network to its addresses.** A network resource carries a name; the
runtime allocates the subnet when the network is created. So the control plane declares *forward the
networks these modules asked for* and the host — which made them, and already resolves a container by
its name — renders the addresses. [ADR 0005](0005-the-node-host.md) holds: the host applies, it does
not decide.
**Deriving from the declaration is not deriving from the machine.** Both of 0137's objections fall
away. The set is known before the network exists, because a module declared it, so a network created
between two declarations is already in the one that asked for it. And it cannot widen itself: a
network nobody declared is never forwarded, however it appeared on the machine.
**The runtime's own default bridge is forwarded, from what the runtime reports.** Containers that name
no module network attach to it, and it belongs to the runtime rather than to any module — so the host
renders it from what the runtime says, not from a range named in the control plane. The constants go.
**What a machine says is for guests no module declares.** A test bed is not a module and its range is
not a module's; that is the case 0137's mechanism is for, and it keeps it — added to the derived set,
never replacing it, as 0137 decided. Narrowed to that, it is named for it.
**Their guests keep address and name service**, per declared network, unchanged from
[ADR 0137](0137-a-machine-says-which-networks-it-routes.md): the input chain admits that network's own
DHCP and DNS and nothing else.
## Consequences
- **The two constants are removed**, and with them the class of fault that a machine's guests depend
on a range that describes some other machine.
- **This is a behaviour change, not a refactor.** On the machine measured, the derived set and the
constant do not cover the same ground — that is the whole reason for the record. A machine whose
module networks happen to fall inside the old ranges renders the same rules.
- **A range that exists only to keep a leftover alive becomes visible as such**, because it will not
be in the derived set and has to be said out loud to survive.
- **`node networks` narrows** to guests no module declares, and the preview says which of a machine's
networks are the mesh's and which are not, so the difference is readable before a flip rather than
after.
- **A module's declaration gains nothing.** It already declares its network; what changes is that the
filter reads it.
- **What got harder:** the host renders part of the forward chain from what it created, so the
control plane no longer holds the whole rule set as text. The rule the mesh states is the set of
networks; the addresses are the machine's.
## How it is checked
- **Only declared networks are forwarded.** A node with two modules that declare networks renders
forward rules for exactly those two, and none for a third network present on the machine that no
module declared. This fails against the previous behaviour, which forwards by range and cannot tell
them apart, and that is how it is written.
- **Unassigning a module removes its network's rule** at the next reconcile, asserted on the rendered
chain rather than on the intent.
- **Guests of a declared network keep address and name service**, asserted per chain body so a line in
the wrong chain cannot pass — carried from 0137.
- **The runtime's own default bridge comes from the runtime**, asserted by rendering for a runtime
whose default bridge is somewhere other than the range the constant named.
- **A machine that names a range for guests no module declares still gets it**, added to the derived
set and not replacing it.
- **Each family is matched in its own syntax**, carried from 0137: one set holding both is a syntax
error, and a ruleset that does not load is a machine filtering nothing while its unit reports a
fault.
## References
- [ADR 0137](0137-a-machine-says-which-networks-it-routes.md) — narrowed here; its mechanism keeps the
case it is right for
- [ADR 0005](0005-the-node-host.md) — the host applies; the addresses are the machine's
- [ADR 0045](0045-a-machine-firewall-is-the-sum-of-what-it-listens-on.md) — the filter is derived from
what runs there
- [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md) — why a module does not name its
range
- [issue 141](../04-ISSUES/141-the-forward-chain-does-not-follow-the-modules/00-report.md) — the
measurement
- [issue 137](../04-ISSUES/137-converging-a-machine-cut-off-its-own-guests/00-report.md) — the
breakage that produced 0137