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

8.3 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
what runs on it accepted 2026-09-28 jochen false 02-DECISIONS/0137-a-machine-says-which-networks-it-routes.md

139. A network is forwarded because a module declared it

Context

ADR 0137, 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): 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, ADR 0010, ADR 0045). 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: 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 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: 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 — narrowed here; its mechanism keeps the case it is right for
  • ADR 0005 — the host applies; the addresses are the machine's
  • ADR 0045 — the filter is derived from what runs there
  • ADR 0112 — why a module does not name its range
  • issue 141 — the measurement
  • issue 137 — the breakage that produced 0137