Declare nftables before the guard's table, so a node joining adopted without nft can load it (hq ADR 0103)

This commit is contained in:
2026-09-22 17:59:32 +02:00
parent a2dfaaf4d1
commit ade6b2bfb6
2 changed files with 31 additions and 7 deletions
+14 -7
View File
@@ -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(),
+17
View File
@@ -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()])
}