Skip to content

Commit a12a474

Browse files
committed
filter: support filtering dump/flush by L3 protocol family (v4/v6)
Signed-off-by: Timo Beckers <[email protected]>
1 parent 0d97d06 commit a12a474

File tree

3 files changed

+69
-6
lines changed

3 files changed

+69
-6
lines changed

conn.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,8 @@ func (c *Conn) Dump(opts *DumpOptions) ([]Flow, error) {
193193
return unmarshalFlows(nlm)
194194
}
195195

196-
// DumpFilter gets all Conntrack connections from the kernel in the form of a list
197-
// of Flow objects, but only returns Flows matching the connmark specified in the Filter parameter.
196+
// DumpFilter gets all Conntrack connections from the kernel in the form of a
197+
// list of Flow objects. Only Flows matching the provided [Filter] are returned.
198198
func (c *Conn) DumpFilter(filter Filter, opts *DumpOptions) ([]Flow, error) {
199199
if filter == nil {
200200
return nil, fmt.Errorf("filter is nil")
@@ -209,7 +209,7 @@ func (c *Conn) DumpFilter(filter Filter, opts *DumpOptions) ([]Flow, error) {
209209
netfilter.Header{
210210
SubsystemID: netfilter.NFSubsysCTNetlink,
211211
MessageType: netfilter.MessageType(msgType),
212-
Family: netfilter.ProtoUnspec, // ProtoUnspec dumps both IPv4 and IPv6
212+
Family: filter.family(),
213213
Flags: netlink.Request | netlink.Dump,
214214
},
215215
filter.marshal())
@@ -274,8 +274,8 @@ func (c *Conn) Flush() error {
274274
return nil
275275
}
276276

277-
// FlushFilter deletes all entries from the Conntrack table matching a given Filter.
278-
// Both IPv4 and IPv6 entries are considered for deletion.
277+
// FlushFilter deletes all entries from the Conntrack table matching a given
278+
// [Filter].
279279
func (c *Conn) FlushFilter(filter Filter) error {
280280
if filter == nil {
281281
return fmt.Errorf("filter is nil")
@@ -285,8 +285,16 @@ func (c *Conn) FlushFilter(filter Filter) error {
285285
netfilter.Header{
286286
SubsystemID: netfilter.NFSubsysCTNetlink,
287287
MessageType: netfilter.MessageType(ctDelete),
288-
Family: netfilter.ProtoUnspec, // Family is ignored for flush
288+
Family: filter.family(),
289289
Flags: netlink.Request | netlink.Acknowledge,
290+
291+
// Request 'new' filtered flush behaviour as described in e7600865db32
292+
// ("netfilter: ctnetlink: Fix regression in conntrack entry deletion").
293+
// Before this patch, the kernel would ignore the nfgenmsg family and
294+
// flush the entire table. As of 1ef7f50ccc6e ("netfilter: ctnetlink:
295+
// support CTA_FILTER for flush"), the same can be accomplished by setting
296+
// an empty CTA_FILTER.
297+
Version: 1,
290298
},
291299
filter.marshal())
292300

filter.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ import (
1111
//
1212
// Pass a filter to [Conn.DumpFilter] or [Conn.FlushFilter].
1313
type Filter interface {
14+
// Family sets the address (L3) family to filter on, similar to conntrack's
15+
// -f/--family.
16+
//
17+
// Common values are [netfilter.ProtoIPv4] and [netfilter.ProtoIPv6].
18+
//
19+
// Requires Linux 4.20 or later for [Conn.DumpFilter] and Linux 5.3 for
20+
// [Conn.FlushFilter].
21+
Family(l3 netfilter.ProtoFamily) Filter
22+
1423
// Mark sets the connmark to filter on, similar to conntrack's --mark option.
1524
//
1625
// When not specifying a mark mask, the kernel defaults to 0xFFFFFFFF, meaning
@@ -48,6 +57,8 @@ type Filter interface {
4857
// Requires Linux 6.8 or later.
4958
Zone(zone uint16) Filter
5059

60+
family() netfilter.ProtoFamily
61+
5162
marshal() []netfilter.Attribute
5263
}
5364

@@ -58,6 +69,17 @@ func NewFilter() Filter {
5869

5970
type filter struct {
6071
f map[attributeType][]byte
72+
73+
l3 netfilter.ProtoFamily
74+
}
75+
76+
func (f *filter) Family(l3 netfilter.ProtoFamily) Filter {
77+
f.l3 = l3
78+
return f
79+
}
80+
81+
func (f *filter) family() netfilter.ProtoFamily {
82+
return f.l3
6183
}
6284

6385
func (f *filter) Mark(mark uint32) Filter {

flow_integration_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/stretchr/testify/assert"
1212
"github.com/stretchr/testify/require"
13+
"github.com/ti-mo/netfilter"
1314
)
1415

1516
// Create a given number of flows with a randomized component and check the amount
@@ -490,3 +491,35 @@ func TestStatusFilter(t *testing.T) {
490491
require.NoError(t, err)
491492
assert.Len(t, flows, 0)
492493
}
494+
495+
func TestFamilyFilter(t *testing.T) {
496+
c, _, err := makeNSConn()
497+
require.NoError(t, err)
498+
499+
require.NoError(t, c.Create(NewFlow(unix.IPPROTO_TCP, 0, netip.MustParseAddr("1.2.3.4"), netip.MustParseAddr("5.6.7.8"), 1234, 80, 120, 0)))
500+
require.NoError(t, c.Create(NewFlow(unix.IPPROTO_UDP, 0, netip.MustParseAddr("2a00:1450:400e:804::200e"), netip.MustParseAddr("2a00:1450:400e:804::200f"), 1234, 80, 120, 0)))
501+
502+
flows, err := c.Dump(nil)
503+
require.NoError(t, err)
504+
assert.Len(t, flows, 2, "expected 2 flows in total")
505+
506+
flows, err = c.DumpFilter(NewFilter().Family(netfilter.ProtoIPv4), nil)
507+
require.NoError(t, err)
508+
assert.Len(t, flows, 1)
509+
assert.Equal(t, flows[0].TupleOrig.IP.SourceAddress, netip.MustParseAddr("1.2.3.4"))
510+
511+
flows, err = c.DumpFilter(NewFilter().Family(netfilter.ProtoIPv6), nil)
512+
require.NoError(t, err)
513+
assert.Len(t, flows, 1)
514+
assert.Equal(t, flows[0].TupleOrig.IP.SourceAddress, netip.MustParseAddr("2a00:1450:400e:804::200e"))
515+
516+
assert.NoError(t, c.FlushFilter(NewFilter().Family(netfilter.ProtoIPv4)))
517+
flows, err = c.Dump(nil)
518+
require.NoError(t, err)
519+
assert.Len(t, flows, 1, "expected 1 flow in total")
520+
521+
flows, err = c.DumpFilter(NewFilter().Family(netfilter.ProtoIPv6), nil)
522+
require.NoError(t, err)
523+
assert.Len(t, flows, 1)
524+
assert.Equal(t, flows[0].TupleOrig.IP.SourceAddress, netip.MustParseAddr("2a00:1450:400e:804::200e"))
525+
}

0 commit comments

Comments
 (0)