Skip to content
Open
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
138 changes: 138 additions & 0 deletions nftables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3742,6 +3742,144 @@ func TestCreateAutoMergeSet(t *testing.T) {
}
}

func TestSetByteOrderRoundTrip(t *testing.T) {
tests := []struct {
name string
set nftables.Set
elems []nftables.SetElement
wantKeyOrder binaryutil.ByteOrder
wantDataOrder binaryutil.ByteOrder
}{
{
name: "constant set defaults key byteorder to big-endian",
set: nftables.Set{
Constant: true,
KeyType: nftables.TypeInetService,
},
elems: []nftables.SetElement{
{Key: binaryutil.BigEndian.PutUint16(80)},
},
wantKeyOrder: binaryutil.BigEndian,
},
{
name: "explicit host-endian key byteorder",
set: nftables.Set{
KeyType: nftables.TypeMark,
KeyByteOrder: binaryutil.NativeEndian,
},
elems: []nftables.SetElement{
{Key: binaryutil.NativeEndian.PutUint32(1)},
},
wantKeyOrder: binaryutil.NativeEndian,
},
{
name: "interval set defaults key byteorder to big-endian",
set: nftables.Set{
Interval: true,
KeyType: nftables.TypeInetService,
},
wantKeyOrder: binaryutil.BigEndian,
},
{
name: "interval set with explicit host-endian key byteorder",
set: nftables.Set{
Interval: true,
KeyType: nftables.TypeMark,
KeyByteOrder: binaryutil.NativeEndian,
},
elems: []nftables.SetElement{
{Key: binaryutil.NativeEndian.PutUint32(7)},
},
wantKeyOrder: binaryutil.NativeEndian,
},
{
name: "map with explicit host-endian key and data byteorder",
set: nftables.Set{
KeyType: nftables.TypeMark,
DataType: nftables.TypeMark,
KeyByteOrder: binaryutil.NativeEndian,
DataByteOrder: binaryutil.NativeEndian,
IsMap: true,
},
elems: []nftables.SetElement{
{
Key: binaryutil.NativeEndian.PutUint32(1),
Val: binaryutil.NativeEndian.PutUint32(2),
},
},
wantKeyOrder: binaryutil.NativeEndian,
wantDataOrder: binaryutil.NativeEndian,
},
{
name: "map with explicit data byteorder only",
set: nftables.Set{
KeyType: nftables.TypeInetService,
DataType: nftables.TypeMark,
DataByteOrder: binaryutil.NativeEndian,
IsMap: true,
},
elems: []nftables.SetElement{
{
Key: binaryutil.BigEndian.PutUint16(22),
Val: binaryutil.NativeEndian.PutUint32(1),
},
},
wantDataOrder: binaryutil.NativeEndian,
},
}

for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
conn, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
defer conn.FlushRuleset()

table := conn.AddTable(&nftables.Table{
Name: fmt.Sprintf("byteorder-table-%d", i),
Family: nftables.TableFamilyIPv4,
})

set := tt.set
set.Table = table
set.Name = fmt.Sprintf("byteorder-set-%d", i)

if err := conn.AddSet(&set, tt.elems); err != nil {
t.Fatalf("failed to add set: %v", err)
}
if err := conn.Flush(); err != nil {
t.Fatalf("failed to flush: %v", err)
}

gotSet, err := conn.GetSetByName(table, set.Name)
if err != nil {
t.Fatalf("failed to find set %q: %v", set.Name, err)
}
if gotSet.KeyByteOrder != tt.wantKeyOrder {
t.Fatalf("set.KeyByteOrder = %v, want %v", gotSet.KeyByteOrder, tt.wantKeyOrder)
}
if gotSet.DataByteOrder != tt.wantDataOrder {
t.Fatalf("set.DataByteOrder = %v, want %v", gotSet.DataByteOrder, tt.wantDataOrder)
}

gotElems, err := conn.GetSetElements(gotSet)
if err != nil {
t.Fatalf("failed to get set elements: %v", err)
}
if got, want := len(gotElems), len(tt.elems); got != want {
t.Fatalf("got %d elements, want %d", got, want)
}
for i := range tt.elems {
if !bytes.Equal(gotElems[i].Key, tt.elems[i].Key) {
t.Fatalf("element[%d].Key = %x, want %x", i, gotElems[i].Key, tt.elems[i].Key)
}
if !bytes.Equal(gotElems[i].Val, tt.elems[i].Val) {
t.Fatalf("element[%d].Val = %x, want %x", i, gotElems[i].Val, tt.elems[i].Val)
}
}
})
}
}

func TestIP6SetAddElements(t *testing.T) {
// Create a new network namespace to test these operations,
// and tear down the namespace at test completion.
Expand Down
45 changes: 39 additions & 6 deletions set.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,9 @@ type Set struct {
DataType SetDatatype
// Either host (binaryutil.NativeEndian) or big (binaryutil.BigEndian) endian as per
// https://git.netfilter.org/nftables/tree/include/datatype.h?id=d486c9e626405e829221b82d7355558005b26d8a#n109
KeyByteOrder binaryutil.ByteOrder
Comment string
KeyByteOrder binaryutil.ByteOrder
DataByteOrder binaryutil.ByteOrder
Comment string
// Indicates that the set has "size" specifier
Size uint32
}
Expand Down Expand Up @@ -716,12 +717,27 @@ func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
// https://git.netfilter.org/libnftnl/tree/include/udata.h#n17
var userData []byte

if s.Anonymous || s.Constant || s.Interval || s.KeyByteOrder == binaryutil.BigEndian {
// Semantically useless - kept for binary compatability with nft
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_KEYBYTEORDER, 2)
} else if s.KeyByteOrder == binaryutil.NativeEndian {
// Emit KEYBYTEORDER metadata matching nft C tool behavior (mnl.c:mnl_nft_set_add).
// Anonymous, constant, and interval sets always need byte order metadata.
// When KeyByteOrder is explicitly set, use it; otherwise default to big-endian
// for backward compatibility with prior library behavior.
if s.KeyByteOrder == binaryutil.NativeEndian {
// Per https://git.netfilter.org/nftables/tree/src/mnl.c?id=187c6d01d35722618c2711bbc49262c286472c8f#n1165
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_KEYBYTEORDER, 1)
} else if s.Anonymous || s.Constant || s.Interval || s.KeyByteOrder == binaryutil.BigEndian {
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_KEYBYTEORDER, 2)
}

// Emit DATABYTEORDER for maps, matching nft C tool behavior (mnl.c:mnl_nft_set_add).
// Without this, nft list ruleset cannot determine the data byte order and displays
// host-endian values (like marks) as byte-swapped on LE systems.
if s.IsMap {
switch s.DataByteOrder {
case binaryutil.NativeEndian:
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_DATABYTEORDER, 1)
case binaryutil.BigEndian:
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_DATABYTEORDER, 2)
}
}

if s.Interval && s.AutoMerge {
Expand Down Expand Up @@ -862,6 +878,12 @@ func setsFromMsg(msg netlink.Message) (*Set, error) {
set.DataType.Bytes = binary.BigEndian.Uint32(ad.Bytes())
case unix.NFTA_SET_USERDATA:
data := ad.Bytes()
if val, ok := userdata.GetUint32(data, userdata.NFTNL_UDATA_SET_KEYBYTEORDER); ok {
set.KeyByteOrder = parseSetByteOrder(val)
}
if val, ok := userdata.GetUint32(data, userdata.NFTNL_UDATA_SET_DATABYTEORDER); ok {
set.DataByteOrder = parseSetByteOrder(val)
}
if val, ok := userdata.GetString(data, userdata.NFTNL_UDATA_SET_COMMENT); ok {
set.Comment = val
}
Expand Down Expand Up @@ -891,6 +913,17 @@ func setsFromMsg(msg netlink.Message) (*Set, error) {
return &set, nil
}

func parseSetByteOrder(v uint32) binaryutil.ByteOrder {
switch v {
case 1:
return binaryutil.NativeEndian
case 2:
return binaryutil.BigEndian
default:
return nil
}
}

func parseSetDatatype(magic uint32) (SetDatatype, error) {
types := make([]SetDatatype, 0, 32/SetConcatTypeBits)
for magic != 0 {
Expand Down