|
|
|
@@ -0,0 +1,116 @@
|
|
|
|
|
---
|
|
|
|
|
topic: what runs on it
|
|
|
|
|
status: accepted
|
|
|
|
|
date: 2026-09-28
|
|
|
|
|
deciders: jochen
|
|
|
|
|
extends: 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
|
|
|
|
|
reconstructed: false
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# 137. A machine says which networks it routes
|
|
|
|
|
|
|
|
|
|
## Context
|
|
|
|
|
|
|
|
|
|
The filter the mesh derives denies forwarding by default, because without a forward chain it says
|
|
|
|
|
nothing about a container's published port
|
|
|
|
|
([ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md), [issue 047](../04-ISSUES/047-the-firewall-does-not-cover-published-container-ports/00-report.md)).
|
|
|
|
|
To keep a machine's own containers working it then allows two ranges: the container runtime's
|
|
|
|
|
default bridge pool, and the pool its compose files are given. Those two are named in the
|
|
|
|
|
controller's code, with a comment saying what the gap is:
|
|
|
|
|
|
|
|
|
|
> A machine whose runtime is configured with something else needs this to say so — which is a thing
|
|
|
|
|
> the mesh cannot derive and a reason this list is named here rather than computed.
|
|
|
|
|
|
|
|
|
|
**There was no way to say so.** The list was a constant. A machine whose guests live anywhere else
|
|
|
|
|
was filtered by a rule that looked deliberate and was a guess.
|
|
|
|
|
|
|
|
|
|
**Measured, on the day a workstation was converged.** Flipping it cut egress for five of its
|
|
|
|
|
container networks at once, and for every network its test beds create — the beds allocate a fresh
|
|
|
|
|
range per run, from a pool neither default covers. Nothing reported a fault. The containers could
|
|
|
|
|
not reach anything, the machine went on reporting that it had applied what it was told, and the
|
|
|
|
|
converge preview had said nothing about it either, because the preview lists what *listens* and
|
|
|
|
|
routing is not a listener.
|
|
|
|
|
|
|
|
|
|
**And two questions, not one.** A guest also asks its host for an address and for names. Both arrive
|
|
|
|
|
at the input chain, where nothing declared them, so denying by default left the guests of a routed
|
|
|
|
|
network with no address and no resolution — which is not a closed port but a network that does not
|
|
|
|
|
function, asked for by this machine's own guest.
|
|
|
|
|
|
|
|
|
|
**Why the machine cannot simply be read.** A test bed creates its bridge while it runs, between one
|
|
|
|
|
declaration and the next, so a filter derived from what the machine last reported would be correct
|
|
|
|
|
only for the networks that already existed when it was composed. A declared range covers the ones
|
|
|
|
|
that do not exist yet.
|
|
|
|
|
|
|
|
|
|
## Decision
|
|
|
|
|
|
|
|
|
|
**A machine says which networks it routes for what it hosts, and the filter forwards them.** A
|
|
|
|
|
node-level fact, beside the node's public domain
|
|
|
|
|
([ADR 0066](0066-public-routing-is-name-agnostic.md)) and for the same reason: the
|
|
|
|
|
machine routes them, and the module that loads the filter holds a seat and may be replaced.
|
|
|
|
|
|
|
|
|
|
**Added to the runtime's defaults, never replacing them.** A machine that names one range has not
|
|
|
|
|
stopped hosting whatever was already on the runtime's own pools, and replacing would trade one
|
|
|
|
|
silent breakage for another.
|
|
|
|
|
|
|
|
|
|
**Their guests keep address and name service.** For a network that was named, the input chain admits
|
|
|
|
|
that network's own DHCP and DNS, and nothing else: everything else a guest might want from its host
|
|
|
|
|
is a port somebody declares, like every other port on this machine.
|
|
|
|
|
|
|
|
|
|
**Said in CIDR form and checked when it is said.** An entry that does not parse is a line nftables
|
|
|
|
|
refuses, and a refused ruleset is a machine filtering nothing while its unit reports a fault — so
|
|
|
|
|
the refusal happens where a person can read it, not on the machine.
|
|
|
|
|
|
|
|
|
|
**A machine that says nothing is filtered exactly as before.** Every machine already converged is
|
|
|
|
|
untouched by this.
|
|
|
|
|
|
|
|
|
|
## Options considered
|
|
|
|
|
|
|
|
|
|
1. **Leave it constant and edit the code per installation.** Rejected: the value is a property of
|
|
|
|
|
one machine, the code is the whole mesh's, and the two ranges as they stand describe a machine
|
|
|
|
|
whose runtime was left at its defaults. It is also how this got here.
|
|
|
|
|
2. **Derive it from what the machine reports.** Rejected as insufficient, not as wrong: it cannot
|
|
|
|
|
cover a network created between two declarations, which is precisely the case that was broken. It
|
|
|
|
|
would also make the filter follow whatever appeared on the machine, which is a firewall that
|
|
|
|
|
widens itself.
|
|
|
|
|
3. **A per-node setting on the module that loads the filter.** Rejected: the machine routes the
|
|
|
|
|
networks. The filter module holds a node-scoped seat and is meant to be replaceable, and a
|
|
|
|
|
replacement must not lose the machine's own truth.
|
|
|
|
|
4. **Replace the defaults with what is said.** Rejected: see the decision. The first machine to name
|
|
|
|
|
its bed range would lose its containers.
|
|
|
|
|
5. **Admit all input from a routed network, not only address and name service.** Rejected: that is
|
|
|
|
|
every port on the machine open to anything it hosts, which is the derivation abandoned.
|
|
|
|
|
|
|
|
|
|
## Consequences
|
|
|
|
|
|
|
|
|
|
**The converge preview says what a machine routes**, including when it routes nothing but the
|
|
|
|
|
defaults, with the command that changes it. The preview's own sentence about traffic it cannot
|
|
|
|
|
preview stays, because a tunnel and the found firewall's NAT are still not previewable.
|
|
|
|
|
|
|
|
|
|
**A machine whose guests are already broken by an earlier flip is fixed by saying its networks and
|
|
|
|
|
pushing**, with no flip to undo.
|
|
|
|
|
|
|
|
|
|
**The list is one more thing that can be wrong and stale.** A range removed from the machine and
|
|
|
|
|
left here keeps forwarding for a network that no longer exists, which admits nothing, because there
|
|
|
|
|
is no guest on it to admit. That is the safe direction of being out of date.
|
|
|
|
|
|
|
|
|
|
## How this is checked
|
|
|
|
|
|
|
|
|
|
- **What a machine says it routes is forwarded, and its guests keep address and name service.** A
|
|
|
|
|
test renders a ruleset for a machine that names one range and asserts both chains, per chain body
|
|
|
|
|
so a line in the wrong chain cannot pass it. It fails against the previous behaviour, which is how
|
|
|
|
|
it was written.
|
|
|
|
|
- **The runtime's own defaults survive naming a range.** Asserted in the same test.
|
|
|
|
|
- **A machine that names nothing renders byte-identically to one that names nil**, so every machine
|
|
|
|
|
already behind this filter is untouched.
|
|
|
|
|
- **Each family is matched in its own syntax.** A test with one v4 and one v6 network asserts
|
|
|
|
|
`ip saddr` and `ip6 saddr`, because one set holding both is a syntax error and a ruleset that does
|
|
|
|
|
not load is a machine filtering nothing.
|
|
|
|
|
- **An entry that is not a network is refused where it is said**, by the parse in the setter.
|
|
|
|
|
|
|
|
|
|
## References
|
|
|
|
|
|
|
|
|
|
- [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md) — the derived filter this completes
|
|
|
|
|
- [ADR 0066](0066-public-routing-is-name-agnostic.md) — the precedent for a node-level fact
|
|
|
|
|
- [issue 047](../04-ISSUES/047-the-firewall-does-not-cover-published-container-ports/00-report.md) — why there is a forward chain at all
|
|
|
|
|
- [issue 137](../04-ISSUES/137-converging-a-machine-cut-off-its-own-guests/00-report.md) — the measurement that produced this
|
|
|
|
|
- mesh-controller `internal/catalogue/filtering.go` — the constant whose own comment named this gap
|