Reading a converged machine's rendered rules showed the cause: the chain blocks everything passing through and then allows the machine's own containers back by listing their address ranges. 0137 made that list typeable and 0139 tried to generate it; both refined a list that should not exist, because the mesh has no position on a container reaching outward. Constrain what arrives from outside, allow what did not, and let the machine report which links face outside — one fact instead of a list. Ports keep following the modules unchanged. The records check now allows one record to supersede several, and stops requiring a withdrawn record's own citations to be live.
89 lines
5.4 KiB
Markdown
89 lines
5.4 KiB
Markdown
---
|
|
status: located
|
|
opened: 2026-09-28
|
|
located-in:
|
|
- mesh-controller internal/catalogue/filtering.go
|
|
- mesh-host internal/apply
|
|
fixed-by:
|
|
amended-design: 03-DESIGN/01-to-be/08-connectivity.md
|
|
---
|
|
|
|
# 141 — The forward chain does not follow the modules, though the modules declare their networks
|
|
|
|
## What was observed
|
|
|
|
[ADR 0137](../../02-DECISIONS/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, because the derived
|
|
filter's forward chain had until then allowed two ranges named as constants in the control plane's
|
|
own source — the container runtime's default bridge pool, and half of the pool its compose files
|
|
are given.
|
|
|
|
Checking the last machine still to be converged, the same fault was found to be live there, and the
|
|
declaration needed to work around it turned out to be wrong in kind.
|
|
|
|
That machine hosts twenty-one container networks. Nine fall inside the runtime's bridge pool and
|
|
are forwarded. Twelve sit in the other private range, and **six of those fall below the lower bound
|
|
of the constant**, 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 was the obvious move, and is what 0137 provides for. But of those
|
|
six networks, **four are networks the mesh's own modules declare** — they appear as network
|
|
resources in the node's plan, created by the host because a module asked for them — and **two are
|
|
leftovers of the predecessor**, compose networks of services the mesh does not run. A range wide
|
|
enough to keep the four would have forwarded 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. It made them.
|
|
|
|
## Why it matters beyond this instance
|
|
|
|
**The node's configuration is supposed 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. The forward chain is the one derived thing that does not: it consults two
|
|
constants and, since 0137, a list a person types. A module added tomorrow brings a network the filter
|
|
will not forward; a module deprecated leaves a range in the list that outlives it.
|
|
|
|
**A typed range cannot distinguish the mesh's networks from what was left behind.** It is stated in
|
|
addresses, and addresses are what the runtime allocates, so the only honest declaration is one wide
|
|
enough to include whatever else the runtime has handed out. The derivation is narrower than anything
|
|
a person can safely write, because it names networks rather than ranges.
|
|
|
|
**0137 rejected deriving this, and was right about what it rejected.** It considered deriving the
|
|
list from *what the machine reports* and refused, on two grounds: a test bed creates its bridge
|
|
between one declaration and the next, and a filter that follows whatever appeared on the machine is a
|
|
firewall that widens itself. Deriving from the **declaration** is neither. The set is known before
|
|
the network exists, because a module declared it; and it cannot widen itself, because only a network
|
|
some module asked for is ever forwarded. What remains genuinely for a machine to say is guests no
|
|
module declares — a test bed's pool — which is a much smaller residue than the list as it stands.
|
|
|
|
**The gap is invisible in the one place that should show it.** The converge preview lists what
|
|
*listens*, and routing is not a listener. It says in one line what the machine routes, and a reader
|
|
has to know the runtime's allocations to tell whether that line is sufficient. On the machine
|
|
measured here it read as though nothing needed saying.
|
|
|
|
## What was decided
|
|
|
|
*2026-09-28, later the same day.* The answer is not a better list. The question in the first open
|
|
item below — should the chain be derived from the networks the modules declare — was answered *no*,
|
|
after a converged machine's rendered rules were read: the chain blocks everything passing through the
|
|
machine and then allows its own guests back by listing their addresses. Every route to a correct list
|
|
fails, because the mesh has no position on a container reaching outward in the first place. The filter
|
|
now constrains what arrives from **outside** the machine and says nothing about what did not, and a
|
|
machine says which of its links face outside — one reported fact instead of a list. See
|
|
[ADR 0140](../../02-DECISIONS/0140-the-filter-constrains-what-arrives-from-outside.md), which
|
|
supersedes both 0137 and the first attempt at answering this.
|
|
|
|
## Open questions
|
|
|
|
- Should the forward chain be derived from the network resources the node's modules declare, with the
|
|
host resolving each declared network to its address the way it already resolves a container by name?
|
|
The controller cannot render the address itself: a module's network resource carries a name, and the
|
|
runtime allocates the subnet at creation.
|
|
- What remains of `node networks` once that exists — only guests no module declares, such as a test
|
|
bed's pool? And should it then be named for that, rather than for all routing?
|
|
- The runtime's own default bridge, which containers attach to when no module network is named, is
|
|
not a module's network. Is it derived from the machine, declared by the module that owns the
|
|
runtime, or left as the one constant?
|
|
- Should the preview say which of a machine's networks are the mesh's and which are not, so a range
|
|
that exists to protect a leftover is visible as such?
|