Skip to content

Commit 12267ac

Browse files
committed
filter: clone Filters after each method call
This was slightly surprising behaviour and would prevent callers from building template filters and extending them right before calling dump/flush. Signed-off-by: Timo Beckers <timo@incline.eu>
1 parent dd68e7e commit 12267ac

File tree

2 files changed

+49
-15
lines changed

2 files changed

+49
-15
lines changed

filter.go

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package conntrack
22

33
import (
4+
"maps"
5+
46
"github.com/ti-mo/netfilter"
57
)
68

79
// Filter is an object used to limit dump and flush operations to flows matching
810
// certain fields. Use [NewFilter] to create a new filter, then chain methods to
9-
// set filter fields. Methods mutate the Filter in place and return it for
10-
// chaining purposes.
11+
// set filter fields.
12+
//
13+
// Methods return a new Filter with the specified field set.
1114
//
1215
// Pass a filter to [Conn.DumpFilter] or [Conn.FlushFilter].
1316
type Filter interface {
14-
// Family sets the address (L3) family to filter on, similar to conntrack's
17+
// Family sets the address (L3) family to filter on, similar to conntrack's.
1518
// -f/--family.
1619
//
1720
// Common values are [netfilter.ProtoIPv4] and [netfilter.ProtoIPv6].
@@ -74,37 +77,50 @@ type filter struct {
7477
}
7578

7679
func (f *filter) Family(l3 netfilter.ProtoFamily) Filter {
77-
f.l3 = l3
78-
return f
80+
return f.withClone(func(cpy *filter) {
81+
cpy.l3 = l3
82+
})
7983
}
8084

8185
func (f *filter) family() netfilter.ProtoFamily {
8286
return f.l3
8387
}
8488

8589
func (f *filter) Mark(mark uint32) Filter {
86-
f.f[ctaMark] = netfilter.Uint32Bytes(mark)
87-
return f
90+
return f.withClone(func(cpy *filter) {
91+
cpy.f[ctaMark] = netfilter.Uint32Bytes(mark)
92+
})
8893
}
8994

9095
func (f *filter) MarkMask(mask uint32) Filter {
91-
f.f[ctaMarkMask] = netfilter.Uint32Bytes(mask)
92-
return f
96+
return f.withClone(func(cpy *filter) {
97+
cpy.f[ctaMarkMask] = netfilter.Uint32Bytes(mask)
98+
})
9399
}
94100

95101
func (f *filter) Status(status Status) Filter {
96-
f.f[ctaStatus] = netfilter.Uint32Bytes(uint32(status))
97-
return f
102+
return f.withClone(func(cpy *filter) {
103+
cpy.f[ctaStatus] = netfilter.Uint32Bytes(uint32(status))
104+
})
98105
}
99106

100107
func (f *filter) StatusMask(mask uint32) Filter {
101-
f.f[ctaStatusMask] = netfilter.Uint32Bytes(mask)
102-
return f
108+
return f.withClone(func(cpy *filter) {
109+
cpy.f[ctaStatusMask] = netfilter.Uint32Bytes(mask)
110+
})
103111
}
104112

105113
func (f *filter) Zone(zone uint16) Filter {
106-
f.f[ctaZone] = netfilter.Uint16Bytes(zone)
107-
return f
114+
return f.withClone(func(cpy *filter) {
115+
cpy.f[ctaZone] = netfilter.Uint16Bytes(zone)
116+
})
117+
}
118+
119+
func (f *filter) withClone(fn func(cpy *filter)) *filter {
120+
clone := *f
121+
clone.f = maps.Clone(f.f)
122+
fn(&clone)
123+
return &clone
108124
}
109125

110126
func (f *filter) marshal() []netfilter.Attribute {

filter_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,21 @@ func TestFilterMarshal(t *testing.T) {
4545

4646
assert.Equal(t, want, got)
4747
}
48+
49+
func TestFilterMutate(t *testing.T) {
50+
f := NewFilter().
51+
Mark(1).
52+
Family(1)
53+
54+
mod := f.
55+
Mark(2).
56+
Family(2)
57+
58+
// Ensure original filter is unchanged.
59+
assert.NotEqual(t, f, mod)
60+
assert.Equal(t, []byte{0, 0, 0, 1}, f.(*filter).f[ctaMark])
61+
assert.Equal(t, netfilter.ProtoFamily(1), f.(*filter).l3)
62+
63+
assert.Equal(t, []byte{0, 0, 0, 2}, mod.(*filter).f[ctaMark])
64+
assert.Equal(t, netfilter.ProtoFamily(2), mod.(*filter).l3)
65+
}

0 commit comments

Comments
 (0)