Skip to content

Commit fcafe27

Browse files
ti-moantoninbas
andcommitted
filter: support filtering dump/flush by conntrack zone
Support for the CTA_ZONE attribute for dump and flush requests was added to the kernel in 6.8: eff3c558bb7e ("netfilter: ctnetlink: support filtering by zone"). This commit adds a Zone() method to allow filtering by zone ID. On older kernels, the attribute will be ignored and flows from all zones will be returned. Fixes: #23 Signed-off-by: Timo Beckers <[email protected]> Co-authored-by: Antonin Bas <[email protected]>
1 parent 1ac590d commit fcafe27

File tree

5 files changed

+116
-5
lines changed

5 files changed

+116
-5
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ With this library, the user can:
2323
- Interact with conntrack connections and expectations through Flow and Expect types respectively
2424
- Create, get, update and delete Flows in an idiomatic way (and Expects, to an extent)
2525
- Listen for create/update/destroy events
26-
- Flush (empty) and dump (display) the whole conntrack table, optionally filtering on specific connection marks
26+
- Flush (empty) and dump (display) the whole conntrack table, optionally filtering on specific flow fields
2727

2828
There are many usage examples in the [godoc](https://godoc.org/github.com/ti-mo/conntrack).
2929

conn_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,39 @@ func ExampleConn_dumpFilter() {
9494
log.Print(df)
9595
}
9696

97+
func ExampleConn_dumpFilterZone() {
98+
// Open a Conntrack connection.
99+
c, err := conntrack.Dial(nil)
100+
if err != nil {
101+
log.Fatal(err)
102+
}
103+
104+
// Create flows in different zones
105+
f1 := conntrack.NewFlow(
106+
6, 0, netip.MustParseAddr("1.2.3.4"), netip.MustParseAddr("5.6.7.8"),
107+
1234, 80, 120, 0,
108+
)
109+
f1.Zone = 10
110+
111+
f2 := conntrack.NewFlow(
112+
17, 0, netip.MustParseAddr("2a00:1450:400e:804::200e"), netip.MustParseAddr("2a00:1450:400e:804::200f"),
113+
1234, 80, 120, 0,
114+
)
115+
f2.Zone = 20
116+
117+
_ = c.Create(f1)
118+
_ = c.Create(f2)
119+
120+
// Dump all flows in table 20.
121+
df, err := c.DumpFilter(conntrack.NewFilter().Zone(20), nil)
122+
if err != nil {
123+
log.Fatal(err)
124+
}
125+
126+
// Print the result. Only f2 is displayed.
127+
log.Print(df)
128+
}
129+
97130
func ExampleConn_flush() {
98131
// Open a Conntrack connection.
99132
c, err := conntrack.Dial(nil)

filter.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ type Filter interface {
2323
// match exactly.
2424
MarkMask(mask uint32) Filter
2525

26+
// Zone sets the conntrack zone to filter on, similar to conntrack's -w/--zone
27+
// option.
28+
//
29+
// If not specified, flows from all zones are returned.
30+
//
31+
// Requires Linux 6.8 or later.
32+
Zone(zone uint16) Filter
33+
2634
marshal() []netfilter.Attribute
2735
}
2836

@@ -45,6 +53,11 @@ func (f *filter) MarkMask(mask uint32) Filter {
4553
return f
4654
}
4755

56+
func (f *filter) Zone(zone uint16) Filter {
57+
f.f[ctaZone] = netfilter.Uint16Bytes(zone)
58+
return f
59+
}
60+
4861
func (f *filter) marshal() []netfilter.Attribute {
4962
attrs := make([]netfilter.Attribute, 0, len(f.f))
5063

filter_test.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
11
package conntrack
22

33
import (
4+
"slices"
45
"testing"
56

67
"github.com/stretchr/testify/assert"
78

89
"github.com/ti-mo/netfilter"
910
)
1011

11-
func TestFilterMarkMask(t *testing.T) {
12-
f := NewFilter().Mark(0xf0000000).MarkMask(0x0000000f)
13-
fm := []netfilter.Attribute{
12+
func TestFilterMarshal(t *testing.T) {
13+
f := NewFilter().Mark(0xf0000000).MarkMask(0x0000000f).Zone(42)
14+
want := []netfilter.Attribute{
1415
{
1516
Type: uint16(ctaMark),
1617
Data: []byte{0xf0, 0, 0, 0},
1718
},
19+
{
20+
Type: uint16(ctaZone),
21+
Data: []byte{0, 42},
22+
},
1823
{
1924
Type: uint16(ctaMarkMask),
2025
Data: []byte{0, 0, 0, 0x0f},
2126
},
2227
}
2328

24-
assert.Equal(t, fm, f.marshal(), "unexpected Filter marshal")
29+
got := f.marshal()
30+
slices.SortStableFunc(got, func(a, b netfilter.Attribute) int {
31+
return int(a.Type) - int(b.Type)
32+
})
33+
34+
assert.Equal(t, want, got)
2535
}

flow_integration_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,58 @@ func BenchmarkCreateDeleteFlow(b *testing.B) {
412412
}
413413
}
414414
}
415+
416+
// Creates flows in a specific zone, dumps them using zone filter, flushes them using zone filter,
417+
// and verifies they are removed. Requires Linux kernel 6.8 or greater for zone filtering support.
418+
func TestZoneFilter(t *testing.T) {
419+
c, _, err := makeNSConn()
420+
require.NoError(t, err)
421+
422+
// One flow in the default zone (0).
423+
require.NoError(t, c.Create(NewFlow(6, 0, netip.MustParseAddr("1.2.3.4"), netip.MustParseAddr("5.6.7.8"), 1234, 80, 120, 0)))
424+
425+
// Two flows in zone 100.
426+
f1 := NewFlow(6, 0, netip.MustParseAddr("1.2.3.4"), netip.MustParseAddr("5.6.7.8"), 1234, 80, 120, 0)
427+
f1.Zone = 100
428+
require.NoError(t, c.Create(f1))
429+
430+
f2 := NewFlow(17, 0, netip.MustParseAddr("2a00:1450:400e:804::200e"), netip.MustParseAddr("2a00:1450:400e:804::200f"), 1234, 80, 120, 0)
431+
f2.Zone = 100
432+
require.NoError(t, c.Create(f2))
433+
434+
z0 := NewFilter().Zone(0)
435+
z100 := NewFilter().Zone(100)
436+
437+
// 3 flows in total.
438+
flows, err := c.Dump(nil)
439+
require.NoError(t, err)
440+
assert.Len(t, flows, 3, "expected 3 flows in total")
441+
442+
// Zone 0 should contain 1 flow.
443+
flows, err = c.DumpFilter(z0, nil)
444+
require.NoError(t, err)
445+
assert.Len(t, flows, 1, "expected 1 flow in zone 0")
446+
447+
// Zone 100 should contain 2 flows.
448+
flows, err = c.DumpFilter(z100, nil)
449+
require.NoError(t, err)
450+
assert.Len(t, flows, 2, "expected 2 flows in zone 100")
451+
452+
// Flush zone 100.
453+
require.NoError(t, c.FlushFilter(z100))
454+
455+
// 1 flow should remain in total.
456+
flows, err = c.Dump(nil)
457+
require.NoError(t, err)
458+
assert.Len(t, flows, 1, "expected 1 flow in total after flush")
459+
460+
// Zone 0 should still contain 1 flow.
461+
flows, err = c.DumpFilter(z0, nil)
462+
require.NoError(t, err)
463+
assert.Len(t, flows, 1, "expected 1 flow in zone 0 after flush")
464+
465+
// Zone 100 should be empty.
466+
flows, err = c.DumpFilter(z100, nil)
467+
require.NoError(t, err)
468+
assert.Empty(t, flows, "expected no flows in zone 100 after flush")
469+
}

0 commit comments

Comments
 (0)