From ade6b2bfb6f559df3eaa82386a27930ee2c28ec3 Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 22 Sep 2026 17:59:32 +0200 Subject: [PATCH] Declare nftables before the guard's table, so a node joining adopted without nft can load it (hq ADR 0103) --- internal/catalogue/adoption.go | 21 ++++++++++++++------- internal/catalogue/adoption_test.go | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/internal/catalogue/adoption.go b/internal/catalogue/adoption.go index 04dc85d..f38c7ae 100644 --- a/internal/catalogue/adoption.go +++ b/internal/catalogue/adoption.go @@ -45,6 +45,13 @@ func GuardID() string { return AdoptionPrefix + "guard" } func GuardUnitID() string { return AdoptionPrefix + "guard-unit" } func GuardRunningID() string { return AdoptionPrefix + "guard-running" } +// GuardPackageID is the tool that loads the guard, declared first: a node joining adopted has +// no filter module and may have no nft at all, and a table nothing can load guards nothing. +func GuardPackageID() string { return AdoptionPrefix + "guard-package" } + +// GuardPackage is the package that carries nft. +const GuardPackage = "nftables" + // OpeningID is an opening's resource identity: its protocol, port and path say what it is. func OpeningID(protocol string, port int, path string) string { return fmt.Sprintf("%sopening-%s-%d-%s", AdoptionPrefix, protocol, port, path) @@ -148,10 +155,9 @@ func Published(resources []map[string]any) map[string]map[int]int { // the machine serves; and it is the mesh's own table, so the found firewall reloading does not // touch it. It refuses only packets addressed to this machine, and the ports except from the // machine itself — its loopback and the container runtime's own networks — and from the private -// network, known by the interface a packet arrives -// on and never by its source address. At prerouting, ahead of the runtime's destination -// translation, so it matches the port the packet was sent to; in the inet family, so both address -// families. +// network, known by the interface a packet arrives on and never by its source address. At +// prerouting, ahead of the runtime's destination translation, so it matches the port the packet +// was sent to; in the inet family, so both address families. // // The same text the installer raises on an adopted genesis; a test holds both to it. func AsGuard(ports []int) string { @@ -200,14 +206,15 @@ func GuardUnitText() string { "WantedBy=multi-user.target\n" } -// GuardResources are the guard as three resources of the existing kinds: the table, the unit, and -// the unit running, restarted when the table changes. Nothing when there is nothing to guard: an -// empty set is not a table nft loads. +// GuardResources are the guard as four resources of the existing kinds: the tool that loads it, +// the table, the unit, and the unit running, restarted when the table changes. Nothing when there +// is nothing to guard: an empty set is not a table nft loads. func GuardResources(ports []int) []map[string]any { if len(ports) == 0 { return nil } return []map[string]any{ + {"id": GuardPackageID(), "type": "package", "package": GuardPackage}, {"id": GuardID(), "type": "file", "path": GuardPath, "content": AsGuard(ports), "mode": "0644"}, {"id": GuardUnitID(), "type": "file", "path": GuardUnitPath, "content": GuardUnitText(), diff --git a/internal/catalogue/adoption_test.go b/internal/catalogue/adoption_test.go index 89ccc37..0d839e1 100644 --- a/internal/catalogue/adoption_test.go +++ b/internal/catalogue/adoption_test.go @@ -157,6 +157,23 @@ func TestAnAdoptedNodeLoadsNoFilterOfTheMeshs(t *testing.T) { t.Fatalf("the guard does not guard the store, the broker and its management port:\n%s", guard["content"]) } + // The tool that loads it comes first, and the table after it: a node joining adopted has no + // filter module and may have no nft. + pkg, table := -1, -1 + for i, r := range composed.Resources { + switch r["id"] { + case GuardPackageID(): + pkg = i + if r["type"] != "package" || r["package"] != "nftables" { + t.Fatalf("the guard's package is %v", r) + } + case GuardID(): + table = i + } + } + if pkg < 0 || pkg > table { + t.Fatalf("nftables is not declared before the guard's table (%d, %d)", pkg, table) + } if !reflect.DeepEqual(got[GuardRunningID()]["restart-on"], []any{GuardID(), GuardUnitID()}) { t.Fatalf("the guard is not reloaded when its table changes: %v", got[GuardRunningID()]) }