Compare commits
4 Commits
96866fc962
...
e6e7681f1a
Author | SHA1 | Date |
---|---|---|
|
e6e7681f1a | |
|
4d2aea87f8 | |
|
9a398ccf70 | |
|
8c0e43d650 |
|
@ -7816,3 +7816,74 @@ func TestNftablesDeadlock(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func TestSetElementComment(t *testing.T) {
|
||||||
|
// Create a new network namespace to test these operations
|
||||||
|
conn, newNS := nftest.OpenSystemConn(t, *enableSysTests)
|
||||||
|
defer nftest.CleanupSystemConn(t, newNS)
|
||||||
|
conn.FlushRuleset()
|
||||||
|
defer conn.FlushRuleset()
|
||||||
|
|
||||||
|
// Add a new table
|
||||||
|
table := &nftables.Table{
|
||||||
|
Family: nftables.TableFamilyIPv4,
|
||||||
|
Name: "filter",
|
||||||
|
}
|
||||||
|
conn.AddTable(table)
|
||||||
|
|
||||||
|
// Create a new set
|
||||||
|
set := &nftables.Set{
|
||||||
|
Name: "test-set",
|
||||||
|
Table: table,
|
||||||
|
KeyType: nftables.TypeIPAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create set elements with comments
|
||||||
|
elements := []nftables.SetElement{
|
||||||
|
{
|
||||||
|
Key: net.ParseIP("192.0.2.1").To4(),
|
||||||
|
Comment: "First IP address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: net.ParseIP("192.0.2.2").To4(),
|
||||||
|
Comment: "Second IP address",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the set with elements
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the set elements back and verify comments
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create maps to compare elements by their IP addresses
|
||||||
|
wantMap := make(map[string]string)
|
||||||
|
for _, elem := range elements {
|
||||||
|
wantMap[string(elem.Key)] = elem.Comment
|
||||||
|
}
|
||||||
|
|
||||||
|
gotMap := make(map[string]string)
|
||||||
|
for _, elem := range gotElements {
|
||||||
|
gotMap[string(elem.Key)] = elem.Comment
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare the comments for each IP
|
||||||
|
for ip, wantComment := range wantMap {
|
||||||
|
if gotComment, ok := gotMap[ip]; !ok {
|
||||||
|
t.Errorf("IP %s not found in retrieved elements", ip)
|
||||||
|
} else if gotComment != wantComment {
|
||||||
|
t.Errorf("for IP %s: got comment %q, want comment %q", ip, gotComment, wantComment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
56
set.go
56
set.go
|
@ -267,6 +267,8 @@ type Set struct {
|
||||||
// https://git.netfilter.org/nftables/tree/include/datatype.h?id=d486c9e626405e829221b82d7355558005b26d8a#n109
|
// https://git.netfilter.org/nftables/tree/include/datatype.h?id=d486c9e626405e829221b82d7355558005b26d8a#n109
|
||||||
KeyByteOrder binaryutil.ByteOrder
|
KeyByteOrder binaryutil.ByteOrder
|
||||||
Comment string
|
Comment string
|
||||||
|
// Indicates that the set has "size" specifier
|
||||||
|
Size uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetElement represents a data point within a set.
|
// SetElement represents a data point within a set.
|
||||||
|
@ -288,6 +290,7 @@ type SetElement struct {
|
||||||
Expires time.Duration
|
Expires time.Duration
|
||||||
|
|
||||||
Counter *expr.Counter
|
Counter *expr.Counter
|
||||||
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SetElement) decode(fam byte) func(b []byte) error {
|
func (s *SetElement) decode(fam byte) func(b []byte) error {
|
||||||
|
@ -322,6 +325,12 @@ func (s *SetElement) decode(fam byte) func(b []byte) error {
|
||||||
s.Timeout = time.Millisecond * time.Duration(ad.Uint64())
|
s.Timeout = time.Millisecond * time.Duration(ad.Uint64())
|
||||||
case unix.NFTA_SET_ELEM_EXPIRATION:
|
case unix.NFTA_SET_ELEM_EXPIRATION:
|
||||||
s.Expires = time.Millisecond * time.Duration(ad.Uint64())
|
s.Expires = time.Millisecond * time.Duration(ad.Uint64())
|
||||||
|
case unix.NFTA_SET_ELEM_USERDATA:
|
||||||
|
userData := ad.Bytes()
|
||||||
|
// Try to extract comment from userdata if present
|
||||||
|
if comment, ok := userdata.GetString(userData, userdata.NFTNL_UDATA_SET_ELEM_COMMENT); ok {
|
||||||
|
s.Comment = comment
|
||||||
|
}
|
||||||
case unix.NFTA_SET_ELEM_EXPR:
|
case unix.NFTA_SET_ELEM_EXPR:
|
||||||
elems, err := parseexprfunc.ParseExprBytesFunc(fam, ad)
|
elems, err := parseexprfunc.ParseExprBytesFunc(fam, ad)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -454,6 +463,12 @@ func (s *Set) makeElemList(vals []SetElement, id uint32) ([]netlink.Attribute, e
|
||||||
// If niether of previous cases matche, it means 'e' is an element of a regular Set, no need to add to the attributes
|
// If niether of previous cases matche, it means 'e' is an element of a regular Set, no need to add to the attributes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add comment to userdata if present
|
||||||
|
if len(v.Comment) > 0 {
|
||||||
|
userData := userdata.AppendString(nil, userdata.NFTNL_UDATA_SET_ELEM_COMMENT, v.Comment)
|
||||||
|
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_USERDATA, Data: userData})
|
||||||
|
}
|
||||||
|
|
||||||
encodedItem, err := netlink.MarshalAttributes(item)
|
encodedItem, err := netlink.MarshalAttributes(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("marshal item %d: %v", i, err)
|
return nil, fmt.Errorf("marshal item %d: %v", i, err)
|
||||||
|
@ -553,6 +568,21 @@ func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
|
||||||
}
|
}
|
||||||
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_SET_DESC, Data: numberOfElements})
|
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_SET_DESC, Data: numberOfElements})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var descBytes []byte
|
||||||
|
|
||||||
|
if s.Size > 0 {
|
||||||
|
// Marshal set size description
|
||||||
|
descSizeBytes, err := netlink.MarshalAttributes([]netlink.Attribute{
|
||||||
|
{Type: unix.NFTA_SET_DESC_SIZE, Data: binaryutil.BigEndian.PutUint32(s.Size)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fail to marshal set size description: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
descBytes = append(descBytes, descSizeBytes...)
|
||||||
|
}
|
||||||
|
|
||||||
if s.Concatenation {
|
if s.Concatenation {
|
||||||
// Length of concatenated types is a must, otherwise segfaults when executing nft list ruleset
|
// Length of concatenated types is a must, otherwise segfaults when executing nft list ruleset
|
||||||
var concatDefinition []byte
|
var concatDefinition []byte
|
||||||
|
@ -579,8 +609,13 @@ func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("fail to marshal concat definition %v", err)
|
return fmt.Errorf("fail to marshal concat definition %v", err)
|
||||||
}
|
}
|
||||||
// Marshal concat size description as set description
|
|
||||||
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_SET_DESC, Data: concatBytes})
|
descBytes = append(descBytes, concatBytes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(descBytes) > 0 {
|
||||||
|
// Marshal set description
|
||||||
|
tableInfo = append(tableInfo, netlink.Attribute{Type: unix.NLA_F_NESTED | unix.NFTA_SET_DESC, Data: descBytes})
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://git.netfilter.org/libnftnl/tree/include/udata.h#n17
|
// https://git.netfilter.org/libnftnl/tree/include/udata.h#n17
|
||||||
|
@ -734,6 +769,7 @@ func setsFromMsg(msg netlink.Message) (*Set, error) {
|
||||||
set.Interval = (flags & unix.NFT_SET_INTERVAL) != 0
|
set.Interval = (flags & unix.NFT_SET_INTERVAL) != 0
|
||||||
set.IsMap = (flags & unix.NFT_SET_MAP) != 0
|
set.IsMap = (flags & unix.NFT_SET_MAP) != 0
|
||||||
set.HasTimeout = (flags & unix.NFT_SET_TIMEOUT) != 0
|
set.HasTimeout = (flags & unix.NFT_SET_TIMEOUT) != 0
|
||||||
|
set.Dynamic = (flags & unix.NFT_SET_EVAL) != 0
|
||||||
set.Concatenation = (flags & NFT_SET_CONCAT) != 0
|
set.Concatenation = (flags & NFT_SET_CONCAT) != 0
|
||||||
case unix.NFTA_SET_KEY_TYPE:
|
case unix.NFTA_SET_KEY_TYPE:
|
||||||
nftMagic := ad.Uint32()
|
nftMagic := ad.Uint32()
|
||||||
|
@ -762,6 +798,20 @@ func setsFromMsg(msg netlink.Message) (*Set, error) {
|
||||||
data := ad.Bytes()
|
data := ad.Bytes()
|
||||||
value, ok := userdata.GetUint32(data, userdata.NFTNL_UDATA_SET_MERGE_ELEMENTS)
|
value, ok := userdata.GetUint32(data, userdata.NFTNL_UDATA_SET_MERGE_ELEMENTS)
|
||||||
set.AutoMerge = ok && value == 1
|
set.AutoMerge = ok && value == 1
|
||||||
|
case unix.NFTA_SET_DESC:
|
||||||
|
nestedAD, err := netlink.NewAttributeDecoder(ad.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("nested NewAttributeDecoder() failed: %w", err)
|
||||||
|
}
|
||||||
|
for nestedAD.Next() {
|
||||||
|
switch nestedAD.Type() {
|
||||||
|
case unix.NFTA_SET_DESC_SIZE:
|
||||||
|
set.Size = binary.BigEndian.Uint32(nestedAD.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nestedAD.Err() != nil {
|
||||||
|
return nil, fmt.Errorf("decoding set description: %w", nestedAD.Err())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &set, nil
|
return &set, nil
|
||||||
|
@ -807,6 +857,7 @@ func elementsFromMsg(fam byte, msg netlink.Message) ([]SetElement, error) {
|
||||||
b := ad.Bytes()
|
b := ad.Bytes()
|
||||||
if ad.Type() == unix.NFTA_SET_ELEM_LIST_ELEMENTS {
|
if ad.Type() == unix.NFTA_SET_ELEM_LIST_ELEMENTS {
|
||||||
ad, err := netlink.NewAttributeDecoder(b)
|
ad, err := netlink.NewAttributeDecoder(b)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -818,6 +869,7 @@ func elementsFromMsg(fam byte, msg netlink.Message) ([]SetElement, error) {
|
||||||
case unix.NFTA_LIST_ELEM:
|
case unix.NFTA_LIST_ELEM:
|
||||||
ad.Do(elem.decode(fam))
|
ad.Do(elem.decode(fam))
|
||||||
}
|
}
|
||||||
|
|
||||||
elements = append(elements, elem)
|
elements = append(elements, elem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
83
set_test.go
83
set_test.go
|
@ -1,7 +1,11 @@
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mdlayher/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
// unknownNFTMagic is an nftMagic value that's unhandled by this
|
// unknownNFTMagic is an nftMagic value that's unhandled by this
|
||||||
|
@ -185,3 +189,82 @@ func TestConcatSetTypeElements(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalSet(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tbl := &Table{
|
||||||
|
Name: "ipv4table",
|
||||||
|
Family: TableFamilyIPv4,
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := New(WithTestDial(
|
||||||
|
func(req []netlink.Message) ([]netlink.Message, error) {
|
||||||
|
return req, nil
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AddTable(tbl)
|
||||||
|
|
||||||
|
// Ensure the table is added.
|
||||||
|
const connMsgStart = 1
|
||||||
|
if len(c.messages) != connMsgStart {
|
||||||
|
t.Fatalf("AddSet() wrong start message count: %d, expected: %d", len(c.messages), connMsgStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
set Set
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Set without flags",
|
||||||
|
set: Set{
|
||||||
|
Name: "test-set",
|
||||||
|
ID: uint32(1),
|
||||||
|
Table: tbl,
|
||||||
|
KeyType: TypeIPAddr,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set with size, timeout, dynamic flag specified",
|
||||||
|
set: Set{
|
||||||
|
Name: "test-set",
|
||||||
|
ID: uint32(2),
|
||||||
|
HasTimeout: true,
|
||||||
|
Dynamic: true,
|
||||||
|
Size: 10,
|
||||||
|
Table: tbl,
|
||||||
|
KeyType: TypeIPAddr,
|
||||||
|
Timeout: time.Duration(30) * time.Second,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := c.AddSet(&tt.set, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
connMsgSetIdx := connMsgStart + i
|
||||||
|
if len(c.messages) != connMsgSetIdx+1 {
|
||||||
|
t.Fatalf("AddSet() wrong message count: %d, expected: %d", len(c.messages), connMsgSetIdx+1)
|
||||||
|
}
|
||||||
|
msg := c.messages[connMsgSetIdx]
|
||||||
|
|
||||||
|
nset, err := setsFromMsg(msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("setsFromMsg() error: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table pointer is set after flush, which is not implemented in the test.
|
||||||
|
tt.set.Table = nil
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(&tt.set, nset) {
|
||||||
|
t.Fatalf("original %+v and recovered %+v Set structs are different", tt.set, nset)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,12 @@ const (
|
||||||
NFTNL_UDATA_SET_MAX
|
NFTNL_UDATA_SET_MAX
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Set element userdata types
|
||||||
|
const (
|
||||||
|
NFTNL_UDATA_SET_ELEM_COMMENT Type = iota
|
||||||
|
NFTNL_UDATA_SET_ELEM_FLAGS
|
||||||
|
)
|
||||||
|
|
||||||
func Append(udata []byte, typ Type, data []byte) []byte {
|
func Append(udata []byte, typ Type, data []byte) []byte {
|
||||||
udata = append(udata, byte(typ), byte(len(data)))
|
udata = append(udata, byte(typ), byte(len(data)))
|
||||||
udata = append(udata, data...)
|
udata = append(udata, data...)
|
||||||
|
|
Loading…
Reference in New Issue