A mesh on both address families renders both

nftables matches ip and ip6 separately and one set holding both is a syntax
error, so the file would not load: the service reports a configuration fault
and the machine filters nothing. Also a make target for the builder image,
which the lab now stocks.
This commit is contained in:
2026-08-31 00:34:20 +02:00
parent 58c8ab7747
commit d9bee18d44
2 changed files with 44 additions and 2 deletions
+26 -2
View File
@@ -168,8 +168,15 @@ func AsNftables(rules []Rule, mesh []string) string {
rule.Protocol, rule.Port)) rule.Protocol, rule.Port))
break break
} }
b.WriteString(fmt.Sprintf("\t\tip saddr { %s } %s dport %d accept\n", four, six := byFamily(mesh)
strings.Join(mesh, ", "), rule.Protocol, rule.Port)) if len(four) > 0 {
b.WriteString(fmt.Sprintf("\t\tip saddr { %s } %s dport %d accept\n",
strings.Join(four, ", "), rule.Protocol, rule.Port))
}
if len(six) > 0 {
b.WriteString(fmt.Sprintf("\t\tip6 saddr { %s } %s dport %d accept\n",
strings.Join(six, ", "), rule.Protocol, rule.Port))
}
case FromEverywhere: case FromEverywhere:
b.WriteString(fmt.Sprintf("\t\t%s dport %d accept\n", rule.Protocol, rule.Port)) b.WriteString(fmt.Sprintf("\t\t%s dport %d accept\n", rule.Protocol, rule.Port))
} }
@@ -190,3 +197,20 @@ func AsNftables(rules []Rule, mesh []string) string {
b.WriteString("}\n") b.WriteString("}\n")
return b.String() return b.String()
} }
// byFamily splits addresses into the two nftables understands separately.
//
// `ip saddr` and `ip6 saddr` are different matches, and one set holding both families is a syntax
// error — which means the whole file fails to load and the machine filters nothing while the
// service reports a configuration fault. A mesh that is entirely one family renders one line, and
// a mixed one renders both rather than dropping half its nodes.
func byFamily(addresses []string) (four []string, six []string) {
for _, a := range addresses {
if strings.Contains(a, ":") {
six = append(six, a)
continue
}
four = append(four, a)
}
return four, six
}
+18
View File
@@ -219,3 +219,21 @@ func TestAskingForTheRuleSetWithNowhereToPutItIsRefused(t *testing.T) {
t.Fatal("a module asked for the rule set and named no path; it would receive nothing") t.Fatal("a module asked for the rule set and named no path; it would receive nothing")
} }
} }
// nftables matches the two address families separately, and one set holding both is a syntax
// error — so the file fails to load, the service reports a fault, and the machine filters nothing.
func TestAMeshOnBothAddressFamiliesRendersBoth(t *testing.T) {
nft := AsNftables((Resolution{Modules: []Manifest{
{Module: "store", Listens: []Listening{{Port: 5432, From: FromMesh}}},
}}).Filtering(), []string{"198.51.100.2", "2001:db8::2"})
if !strings.Contains(nft, "ip saddr { 198.51.100.2 } tcp dport 5432 accept") {
t.Fatalf("the machines with v4 addresses were dropped:\n%s", nft)
}
if !strings.Contains(nft, "ip6 saddr { 2001:db8::2 } tcp dport 5432 accept") {
t.Fatalf("the machines with v6 addresses were dropped:\n%s", nft)
}
// And never in one set, which is the syntax error this exists to avoid.
if strings.Contains(nft, "198.51.100.2, 2001:db8::2") {
t.Fatalf("both families are in one set, so the file will not load:\n%s", nft)
}
}