diff --git a/internal/config/conntrack.go b/internal/config/conntrack.go index 63edb4a..9b5be24 100644 --- a/internal/config/conntrack.go +++ b/internal/config/conntrack.go @@ -1,6 +1,9 @@ package config -import "fmt" +import ( + "fmt" + "strings" +) type ConntrackAction string @@ -68,6 +71,9 @@ func (c *Config) validateConntrack() error { return fmt.Errorf("conntrack[%d]: helper name required for helper action", i) } + if HasZoneExclusion(ct.Source) || HasZoneExclusion(ct.Dest) { + return fmt.Errorf("conntrack[%d]: zone exclusions are not supported in conntrack entries", i) + } if err := c.validateZoneRef(ct.Source); err != nil { return fmt.Errorf("conntrack[%d]: source %w", i, err) } @@ -87,3 +93,9 @@ func (c *Config) validateConntrack() error { } return nil } + +// HasZoneExclusion reports an all/any zone ref with a "+" or "!" modifier (all+, all!x, any+!x, ...). +func HasZoneExclusion(spec string) bool { + zones, _, _ := strings.Cut(spec, ":") + return (strings.HasPrefix(zones, "all") || strings.HasPrefix(zones, "any")) && strings.ContainsAny(zones[3:], "+!") +} diff --git a/internal/config/extras_test.go b/internal/config/extras_test.go index 82ebd24..d312ed9 100644 --- a/internal/config/extras_test.go +++ b/internal/config/extras_test.go @@ -67,13 +67,38 @@ func TestValidateConntrack(t *testing.T) { wantErr: `dest zone "nte" not defined`, }, { - name: "unknown excluded zone", - rules: []ConntrackRule{{Action: ConntrackNoTrack, Source: "all!nte"}}, - wantErr: `excluded zone "nte" not defined`, + name: "all and plain zone forms are valid", + rules: []ConntrackRule{{Action: ConntrackNoTrack, Source: "net,fw", Dest: "all:192.0.2.1"}}, }, { - name: "exclusion and all forms are valid", - rules: []ConntrackRule{{Action: ConntrackNoTrack, Source: "all!net", Dest: "all:192.0.2.1"}}, + name: "Source all!net rejected", + rules: []ConntrackRule{{Action: ConntrackNoTrack, Source: "all!net"}}, + wantErr: "zone exclusions are not supported in conntrack entries", + }, + { + name: "Dest all!net:192.0.2.1 rejected", + rules: []ConntrackRule{{Action: ConntrackNoTrack, Dest: "all!net:192.0.2.1"}}, + wantErr: "zone exclusions are not supported in conntrack entries", + }, + { + name: "Source all+ rejected", + rules: []ConntrackRule{{Action: ConntrackNoTrack, Source: "all+"}}, + wantErr: "zone exclusions are not supported in conntrack entries", + }, + { + name: "Dest all+!net rejected", + rules: []ConntrackRule{{Action: ConntrackNoTrack, Dest: "all+!net"}}, + wantErr: "zone exclusions are not supported in conntrack entries", + }, + { + name: "Source any!net rejected", + rules: []ConntrackRule{{Action: ConntrackNoTrack, Source: "any!net"}}, + wantErr: "zone exclusions are not supported in conntrack entries", + }, + { + name: "Dest any+ rejected", + rules: []ConntrackRule{{Action: ConntrackNoTrack, Dest: "any+"}}, + wantErr: "zone exclusions are not supported in conntrack entries", }, { name: "helper without source/dest is valid", diff --git a/internal/nftables/compiler.go b/internal/nftables/compiler.go index 66b36c2..d57c16d 100644 --- a/internal/nftables/compiler.go +++ b/internal/nftables/compiler.go @@ -216,6 +216,9 @@ func (c *Compiler) compileConntrack(state *FirewallState) error { fwZone := c.cfg.FirewallZone() for i, ct := range c.cfg.Conntrack { tag := fmt.Sprintf("conntrack:%d", i) + if config.HasZoneExclusion(ct.Source) || config.HasZoneExclusion(ct.Dest) { + return fmt.Errorf("conntrack[%d]: zone exclusions are not supported in conntrack entries", i) + } srcs, dsts := c.zoneSpecs(ct.Source), c.zoneSpecs(ct.Dest) if len(srcs) == 0 { srcs = []config.ZoneSpec{{}} @@ -225,11 +228,6 @@ func (c *Compiler) compileConntrack(state *FirewallState) error { } for _, src := range srcs { - // raw_output only sees locally generated traffic, so it applies to an fw (or omitted) source only. - if isZoneExclusion(ct.Source) && (ct.Chain == config.ConntrackPrerouting && src.Zone == fwZone || - ct.Chain == config.ConntrackOutput && src.Zone != fwZone) { - continue - } chains := []string{"raw_prerouting"} switch { case ct.Chain == config.ConntrackOutput && src.Zone != fwZone && src.Zone != "": diff --git a/internal/nftables/compiler_test.go b/internal/nftables/compiler_test.go index 1485ef5..11408e9 100644 --- a/internal/nftables/compiler_test.go +++ b/internal/nftables/compiler_test.go @@ -2517,14 +2517,34 @@ func TestCompile_ConntrackZones(t *testing.T) { want: map[string][]string{}, }, { - name: "all!net expands to every other zone", - ct: config.ConntrackRule{Action: config.ConntrackNoTrack, Source: "all!net", Dest: "fw:192.0.2.53"}, - want: map[string][]string{"raw_output": {"daddr=192.0.2.53"}, "raw_prerouting": {"iif=eth1 daddr=192.0.2.53"}}, + name: "all!net rejected", + ct: config.ConntrackRule{Action: config.ConntrackNoTrack, Source: "all!net"}, + wantErr: "zone exclusions are not supported in conntrack entries", }, { - name: "all!net in prerouting skips fw", - ct: config.ConntrackRule{Action: config.ConntrackNoTrack, Source: "all!net", Dest: "fw", Chain: config.ConntrackPrerouting}, - want: map[string][]string{"raw_prerouting": {"iif=eth1"}}, + name: "all!net rejected", + ct: config.ConntrackRule{Action: config.ConntrackNoTrack, Dest: "all!net"}, + wantErr: "zone exclusions are not supported in conntrack entries", + }, + { + name: "all+ rejected", + ct: config.ConntrackRule{Action: config.ConntrackNoTrack, Source: "all+"}, + wantErr: "zone exclusions are not supported in conntrack entries", + }, + { + name: "all+!net rejected", + ct: config.ConntrackRule{Action: config.ConntrackNoTrack, Dest: "all+!net"}, + wantErr: "zone exclusions are not supported in conntrack entries", + }, + { + name: "any!net rejected", + ct: config.ConntrackRule{Action: config.ConntrackNoTrack, Source: "any!net"}, + wantErr: "zone exclusions are not supported in conntrack entries", + }, + { + name: "any+ rejected", + ct: config.ConntrackRule{Action: config.ConntrackNoTrack, Dest: "any+"}, + wantErr: "zone exclusions are not supported in conntrack entries", }, { name: "omitted source and dest is global",