diff --git a/nftables_test.go b/nftables_test.go index b35304aa..eea03f06 100644 --- a/nftables_test.go +++ b/nftables_test.go @@ -7462,6 +7462,61 @@ func TestSetElementComment(t *testing.T) { } } +func TestSetElementIntervalOpen(t *testing.T) { + conn, newNS := nftest.OpenSystemConn(t, *enableSysTests) + defer nftest.CleanupSystemConn(t, newNS) + conn.FlushRuleset() + defer conn.FlushRuleset() + + table := &nftables.Table{ + Family: nftables.TableFamilyIPv4, + Name: "test-table", + } + conn.AddTable(table) + set := &nftables.Set{ + Name: "test-set", + Table: table, + KeyType: nftables.TypeIPAddr, + Interval: true, + } + elements := []nftables.SetElement{ + // 10.0.0.0/24 + { + Key: net.ParseIP("10.0.0.0").To4(), + }, + { + Key: net.ParseIP("10.0.1.0").To4(), + IntervalEnd: true, + }, + // 255.255.255.254/31 (open interval) + { + Key: net.ParseIP("255.255.255.254").To4(), + IntervalOpen: true, + }, + } + if err := conn.AddSet(set, elements); err != nil { + t.Fatalf("failed to add set: %v", err) + } + if err := conn.Flush(); err != nil { + t.Fatalf("failed to flush: %v", err) + } + + gotElements, err := conn.GetSetElements(set) + if err != nil { + t.Fatalf("failed to get set elements: %v", err) + } + if got, want := len(gotElements), len(elements); got != want { + t.Fatalf("got %d elements, want %d", got, want) + } + + // Reverse the gotElements slice to match the order of elements + slices.Reverse(gotElements) + + if !reflect.DeepEqual(gotElements, elements) { + t.Errorf("got elements %+v, want %+v", gotElements, elements) + } +} + func TestAutoBufferSize(t *testing.T) { conn, newNS := nftest.OpenSystemConn(t, *enableSysTests) defer nftest.CleanupSystemConn(t, newNS) diff --git a/set.go b/set.go index af86666d..59d2c070 100644 --- a/set.go +++ b/set.go @@ -293,6 +293,8 @@ type SetElement struct { Counter *expr.Counter Comment string + // Indicates an unbounded interval [start, ∞) with no end element. + IntervalOpen bool } func (s *SetElement) decode(fam byte) func(b []byte) error { @@ -333,6 +335,9 @@ func (s *SetElement) decode(fam byte) func(b []byte) error { if comment, ok := userdata.GetString(userData, userdata.NFTNL_UDATA_SET_ELEM_COMMENT); ok { s.Comment = comment } + if userDataFlags, ok := userdata.GetUint32(userData, userdata.NFTNL_UDATA_SET_ELEM_FLAGS); ok { + s.IntervalOpen = (userDataFlags & userdata.NFTNL_UDATA_SET_ELEM_F_INTERVAL_OPEN) != 0 + } case unix.NFTA_SET_ELEM_EXPR: elems, err := parseexprfunc.ParseExprBytesFunc(fam, ad) if err != nil { @@ -541,9 +546,15 @@ func (cc *Conn) marshalSetElement(s *Set, e *SetElement) ([]byte, error) { // If niether of previous cases matche, it means 'e' is an element of a regular Set, no need to add to the attributes } + userData := []byte{} // Add comment to userdata if present if len(e.Comment) > 0 { - userData := userdata.AppendString(nil, userdata.NFTNL_UDATA_SET_ELEM_COMMENT, e.Comment) + userData = userdata.AppendString(userData, userdata.NFTNL_UDATA_SET_ELEM_COMMENT, e.Comment) + } + if e.IntervalOpen { + userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_ELEM_FLAGS, userdata.NFTNL_UDATA_SET_ELEM_F_INTERVAL_OPEN) + } + if len(userData) > 0 { item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_USERDATA, Data: userData}) } diff --git a/userdata/userdata.go b/userdata/userdata.go index 984d84c8..71b2be6f 100644 --- a/userdata/userdata.go +++ b/userdata/userdata.go @@ -52,6 +52,12 @@ const ( NFTNL_UDATA_SET_ELEM_FLAGS ) +// Set element userdata flags +// https://git.netfilter.org/libnftnl/tree/include/libnftnl/udata.h?id=d36691a4ac486857fd437d3a9aa3701a073a9539#n71 +const ( + NFTNL_UDATA_SET_ELEM_F_INTERVAL_OPEN uint32 = 0x01 +) + func Append(udata []byte, typ Type, data []byte) []byte { udata = append(udata, byte(typ), byte(len(data))) udata = append(udata, data...)