Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ With this library, the user can:
- Interact with conntrack connections and expectations through Flow and Expect types respectively
- Create, get, update and delete Flows in an idiomatic way (and Expects, to an extent)
- Listen for create/update/destroy events
- Flush (empty) and dump (display) the whole conntrack table, optionally filtering on specific connection marks
- Flush (empty) and dump (display) the whole conntrack table, optionally filtering on specific connection marks and on the zone

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

Expand Down
4 changes: 3 additions & 1 deletion conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ func (c *Conn) Dump(opts *DumpOptions) ([]Flow, error) {
}

// DumpFilter gets all Conntrack connections from the kernel in the form of a list
// of Flow objects, but only returns Flows matching the connmark specified in the Filter parameter.
// of Flow objects, but only returns Flows matching the connmark and/or zone specified in the Filter parameter.
// Zone filtering requires Linux kernel 6.8 or greater.
func (c *Conn) DumpFilter(f Filter, opts *DumpOptions) ([]Flow, error) {
msgType := ctGet
if opts != nil && opts.ZeroCounters {
Expand Down Expand Up @@ -272,6 +273,7 @@ func (c *Conn) Flush() error {

// FlushFilter deletes all entries from the Conntrack table matching a given Filter.
// Both IPv4 and IPv6 entries are considered for deletion.
// Zone filtering requires Linux kernel 6.8 or greater.
func (c *Conn) FlushFilter(f Filter) error {

req, err := netfilter.MarshalNetlink(
Expand Down
34 changes: 34 additions & 0 deletions conn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,40 @@ func ExampleConn_dumpFilter() {
log.Print(df)
}

func ExampleConn_dumpFilterZone() {
// Open a Conntrack connection.
c, err := conntrack.Dial(nil)
if err != nil {
log.Fatal(err)
}

// Create flows in different zones
f1 := conntrack.NewFlow(
6, 0, netip.MustParseAddr("1.2.3.4"), netip.MustParseAddr("5.6.7.8"),
1234, 80, 120, 0,
)
f1.Zone = 10

f2 := conntrack.NewFlow(
17, 0, netip.MustParseAddr("2a00:1450:400e:804::200e"), netip.MustParseAddr("2a00:1450:400e:804::200f"),
1234, 80, 120, 0,
)
f2.Zone = 20

_ = c.Create(f1)
_ = c.Create(f2)

// Dump all records in the Conntrack table that match zone 20.
zone := uint16(20)
df, err := c.DumpFilter(conntrack.Filter{Zone: &zone}, nil)
if err != nil {
log.Fatal(err)
}

// Print the result. Only f2 is displayed.
log.Print(df)
}

func ExampleConn_flush() {
// Open a Conntrack connection.
c, err := conntrack.Dial(nil)
Expand Down
17 changes: 15 additions & 2 deletions filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import (
// based on a given connmark and mask. The mask is applied to the Mark field of
// all flows in the conntrack table, the result is compared to the filter's Mark.
// Each flow that matches will be returned by the kernel.
// Zone can be used to filter connections by conntrack zone.
type Filter struct {
Mark, Mask uint32
// Requires at least Linux 6.8.
// If omitted, the default behavior is to consider ALL zones.
Zone *uint16
}

// marshal marshals a Filter into a list of netfilter.Attributes.
func (f Filter) marshal() []netfilter.Attribute {

return []netfilter.Attribute{
attrs := []netfilter.Attribute{
{
Type: uint16(ctaMark),
Data: netfilter.Uint32Bytes(f.Mark),
Expand All @@ -25,4 +28,14 @@ func (f Filter) marshal() []netfilter.Attribute {
Data: netfilter.Uint32Bytes(f.Mask),
},
}

// Add CTA_ZONE attribute if Zone is specified
if f.Zone != nil {
attrs = append(attrs, netfilter.Attribute{
Type: uint16(ctaZone),
Data: netfilter.Uint16Bytes(*f.Zone),
})
}

return attrs
}
42 changes: 42 additions & 0 deletions filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,45 @@ func TestFilterMarshal(t *testing.T) {

assert.Equal(t, fm, f.marshal(), "unexpected Filter marshal")
}

func TestFilterMarshalZoneOnly(t *testing.T) {
zone := uint16(123)
f := Filter{Zone: &zone}
fm := []netfilter.Attribute{
{
Type: uint16(ctaMark),
Data: []byte{0, 0, 0, 0},
},
{
Type: uint16(ctaMarkMask),
Data: []byte{0, 0, 0, 0},
},
{
Type: uint16(ctaZone),
Data: []byte{0, 123},
},
}

assert.Equal(t, fm, f.marshal(), "unexpected Filter marshal")
}

func TestFilterMarshalMarkAndZone(t *testing.T) {
zone := uint16(42)
f := Filter{Mark: 0xf0000000, Mask: 0x0000000f, Zone: &zone}
fm := []netfilter.Attribute{
{
Type: uint16(ctaMark),
Data: []byte{0xf0, 0, 0, 0},
},
{
Type: uint16(ctaMarkMask),
Data: []byte{0, 0, 0, 0x0f},
},
{
Type: uint16(ctaZone),
Data: []byte{0, 42},
},
}

assert.Equal(t, fm, f.marshal(), "unexpected Filter marshal")
}
50 changes: 50 additions & 0 deletions flow_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,3 +401,53 @@ func BenchmarkCreateDeleteFlow(b *testing.B) {
}
}
}

// Creates flows in a specific zone, dumps them using zone filter, flushes them using zone filter,
// and verifies they are removed. Requires Linux kernel 6.8 or greater for zone filtering support.
func TestZoneFilter(t *testing.T) {
if !findKsym("ctnetlink_alloc_filter") {
t.Skip("DumpFilter not supported in this kernel")
}

if !findKsym("ctnetlink_flush_iterate") {
t.Skip("FlushFilter not supported in this kernel")
}

c, _, err := makeNSConn()
require.NoError(t, err)

zone := uint16(100)

// Create two flows in zone 100
f1 := NewFlow(
6, 0,
netip.MustParseAddr("1.2.3.4"),
netip.MustParseAddr("5.6.7.8"),
1234, 80, 120, 0,
)
f1.Zone = zone

f2 := NewFlow(
17, 0,
netip.MustParseAddr("2a00:1450:400e:804::200e"),
netip.MustParseAddr("2a00:1450:400e:804::200f"),
1234, 80, 120, 0,
)
f2.Zone = zone

require.NoError(t, c.Create(f1), "creating IPv4 flow in zone 100")
require.NoError(t, c.Create(f2), "creating IPv6 flow in zone 100")

// Dump flows using zone filter - should return 2 flows
flows, err := c.DumpFilter(Filter{Zone: &zone}, nil)
require.NoError(t, err, "dumping flows with zone filter")
assert.Len(t, flows, 2, "expected 2 flows in zone 100")

// Flush flows using zone filter
require.NoError(t, c.FlushFilter(Filter{Zone: &zone}), "flushing flows with zone filter")

// Dump flows using zone filter again - should return empty list
flows, err = c.DumpFilter(Filter{Zone: &zone}, nil)
require.NoError(t, err, "dumping flows with zone filter after flush")
assert.Empty(t, flows, "expected no flows in zone 100 after flush")
}
Loading