Skip to content

Commit 8da11b2

Browse files
fix(set): round-trip KeyByteOrder and add DataByteOrder for maps
Add DataByteOrder to Set for tracking map data byte order. Emit NFTNL_UDATA_SET_DATABYTEORDER in AddSet for maps and parse both KEYBYTEORDER and DATABYTEORDER from userdata in setsFromMsg. Also fix KEYBYTEORDER emission: check NativeEndian before the anonymous/constant/interval/BigEndian catch-all so that sets with an explicit NativeEndian key order emit the correct value. Without these fixes, host-endian map data (e.g. marks) appeared byte-swapped when read back on little-endian systems, and neither KeyByteOrder nor DataByteOrder was populated when deserializing sets.
1 parent 1db35da commit 8da11b2

File tree

2 files changed

+177
-6
lines changed

2 files changed

+177
-6
lines changed

nftables_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3742,6 +3742,144 @@ func TestCreateAutoMergeSet(t *testing.T) {
37423742
}
37433743
}
37443744

3745+
func TestSetByteOrderRoundTrip(t *testing.T) {
3746+
tests := []struct {
3747+
name string
3748+
set nftables.Set
3749+
elems []nftables.SetElement
3750+
wantKeyOrder binaryutil.ByteOrder
3751+
wantDataOrder binaryutil.ByteOrder
3752+
}{
3753+
{
3754+
name: "constant set defaults key byteorder to big-endian",
3755+
set: nftables.Set{
3756+
Constant: true,
3757+
KeyType: nftables.TypeInetService,
3758+
},
3759+
elems: []nftables.SetElement{
3760+
{Key: binaryutil.BigEndian.PutUint16(80)},
3761+
},
3762+
wantKeyOrder: binaryutil.BigEndian,
3763+
},
3764+
{
3765+
name: "explicit host-endian key byteorder",
3766+
set: nftables.Set{
3767+
KeyType: nftables.TypeMark,
3768+
KeyByteOrder: binaryutil.NativeEndian,
3769+
},
3770+
elems: []nftables.SetElement{
3771+
{Key: binaryutil.NativeEndian.PutUint32(1)},
3772+
},
3773+
wantKeyOrder: binaryutil.NativeEndian,
3774+
},
3775+
{
3776+
name: "interval set defaults key byteorder to big-endian",
3777+
set: nftables.Set{
3778+
Interval: true,
3779+
KeyType: nftables.TypeInetService,
3780+
},
3781+
wantKeyOrder: binaryutil.BigEndian,
3782+
},
3783+
{
3784+
name: "interval set with explicit host-endian key byteorder",
3785+
set: nftables.Set{
3786+
Interval: true,
3787+
KeyType: nftables.TypeMark,
3788+
KeyByteOrder: binaryutil.NativeEndian,
3789+
},
3790+
elems: []nftables.SetElement{
3791+
{Key: binaryutil.NativeEndian.PutUint32(7)},
3792+
},
3793+
wantKeyOrder: binaryutil.NativeEndian,
3794+
},
3795+
{
3796+
name: "map with explicit host-endian key and data byteorder",
3797+
set: nftables.Set{
3798+
KeyType: nftables.TypeMark,
3799+
DataType: nftables.TypeMark,
3800+
KeyByteOrder: binaryutil.NativeEndian,
3801+
DataByteOrder: binaryutil.NativeEndian,
3802+
IsMap: true,
3803+
},
3804+
elems: []nftables.SetElement{
3805+
{
3806+
Key: binaryutil.NativeEndian.PutUint32(1),
3807+
Val: binaryutil.NativeEndian.PutUint32(2),
3808+
},
3809+
},
3810+
wantKeyOrder: binaryutil.NativeEndian,
3811+
wantDataOrder: binaryutil.NativeEndian,
3812+
},
3813+
{
3814+
name: "map with explicit data byteorder only",
3815+
set: nftables.Set{
3816+
KeyType: nftables.TypeInetService,
3817+
DataType: nftables.TypeMark,
3818+
DataByteOrder: binaryutil.NativeEndian,
3819+
IsMap: true,
3820+
},
3821+
elems: []nftables.SetElement{
3822+
{
3823+
Key: binaryutil.BigEndian.PutUint16(22),
3824+
Val: binaryutil.NativeEndian.PutUint32(1),
3825+
},
3826+
},
3827+
wantDataOrder: binaryutil.NativeEndian,
3828+
},
3829+
}
3830+
3831+
for i, tt := range tests {
3832+
t.Run(tt.name, func(t *testing.T) {
3833+
conn, newNS := nftest.OpenSystemConn(t, *enableSysTests)
3834+
defer nftest.CleanupSystemConn(t, newNS)
3835+
defer conn.FlushRuleset()
3836+
3837+
table := conn.AddTable(&nftables.Table{
3838+
Name: fmt.Sprintf("byteorder-table-%d", i),
3839+
Family: nftables.TableFamilyIPv4,
3840+
})
3841+
3842+
set := tt.set
3843+
set.Table = table
3844+
set.Name = fmt.Sprintf("byteorder-set-%d", i)
3845+
3846+
if err := conn.AddSet(&set, tt.elems); err != nil {
3847+
t.Fatalf("failed to add set: %v", err)
3848+
}
3849+
if err := conn.Flush(); err != nil {
3850+
t.Fatalf("failed to flush: %v", err)
3851+
}
3852+
3853+
gotSet, err := conn.GetSetByName(table, set.Name)
3854+
if err != nil {
3855+
t.Fatalf("failed to find set %q: %v", set.Name, err)
3856+
}
3857+
if gotSet.KeyByteOrder != tt.wantKeyOrder {
3858+
t.Fatalf("set.KeyByteOrder = %v, want %v", gotSet.KeyByteOrder, tt.wantKeyOrder)
3859+
}
3860+
if gotSet.DataByteOrder != tt.wantDataOrder {
3861+
t.Fatalf("set.DataByteOrder = %v, want %v", gotSet.DataByteOrder, tt.wantDataOrder)
3862+
}
3863+
3864+
gotElems, err := conn.GetSetElements(gotSet)
3865+
if err != nil {
3866+
t.Fatalf("failed to get set elements: %v", err)
3867+
}
3868+
if got, want := len(gotElems), len(tt.elems); got != want {
3869+
t.Fatalf("got %d elements, want %d", got, want)
3870+
}
3871+
for i := range tt.elems {
3872+
if !bytes.Equal(gotElems[i].Key, tt.elems[i].Key) {
3873+
t.Fatalf("element[%d].Key = %x, want %x", i, gotElems[i].Key, tt.elems[i].Key)
3874+
}
3875+
if !bytes.Equal(gotElems[i].Val, tt.elems[i].Val) {
3876+
t.Fatalf("element[%d].Val = %x, want %x", i, gotElems[i].Val, tt.elems[i].Val)
3877+
}
3878+
}
3879+
})
3880+
}
3881+
}
3882+
37453883
func TestIP6SetAddElements(t *testing.T) {
37463884
// Create a new network namespace to test these operations,
37473885
// and tear down the namespace at test completion.

set.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,9 @@ type Set struct {
267267
DataType SetDatatype
268268
// Either host (binaryutil.NativeEndian) or big (binaryutil.BigEndian) endian as per
269269
// https://git.netfilter.org/nftables/tree/include/datatype.h?id=d486c9e626405e829221b82d7355558005b26d8a#n109
270-
KeyByteOrder binaryutil.ByteOrder
271-
Comment string
270+
KeyByteOrder binaryutil.ByteOrder
271+
DataByteOrder binaryutil.ByteOrder
272+
Comment string
272273
// Indicates that the set has "size" specifier
273274
Size uint32
274275
}
@@ -716,12 +717,27 @@ func (cc *Conn) AddSet(s *Set, vals []SetElement) error {
716717
// https://git.netfilter.org/libnftnl/tree/include/udata.h#n17
717718
var userData []byte
718719

719-
if s.Anonymous || s.Constant || s.Interval || s.KeyByteOrder == binaryutil.BigEndian {
720-
// Semantically useless - kept for binary compatability with nft
721-
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_KEYBYTEORDER, 2)
722-
} else if s.KeyByteOrder == binaryutil.NativeEndian {
720+
// Emit KEYBYTEORDER metadata matching nft C tool behavior (mnl.c:mnl_nft_set_add).
721+
// Anonymous, constant, and interval sets always need byte order metadata.
722+
// When KeyByteOrder is explicitly set, use it; otherwise default to big-endian
723+
// for backward compatibility with prior library behavior.
724+
if s.KeyByteOrder == binaryutil.NativeEndian {
723725
// Per https://git.netfilter.org/nftables/tree/src/mnl.c?id=187c6d01d35722618c2711bbc49262c286472c8f#n1165
724726
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_KEYBYTEORDER, 1)
727+
} else if s.Anonymous || s.Constant || s.Interval || s.KeyByteOrder == binaryutil.BigEndian {
728+
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_KEYBYTEORDER, 2)
729+
}
730+
731+
// Emit DATABYTEORDER for maps, matching nft C tool behavior (mnl.c:mnl_nft_set_add).
732+
// Without this, nft list ruleset cannot determine the data byte order and displays
733+
// host-endian values (like marks) as byte-swapped on LE systems.
734+
if s.IsMap {
735+
switch s.DataByteOrder {
736+
case binaryutil.NativeEndian:
737+
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_DATABYTEORDER, 1)
738+
case binaryutil.BigEndian:
739+
userData = userdata.AppendUint32(userData, userdata.NFTNL_UDATA_SET_DATABYTEORDER, 2)
740+
}
725741
}
726742

727743
if s.Interval && s.AutoMerge {
@@ -862,6 +878,12 @@ func setsFromMsg(msg netlink.Message) (*Set, error) {
862878
set.DataType.Bytes = binary.BigEndian.Uint32(ad.Bytes())
863879
case unix.NFTA_SET_USERDATA:
864880
data := ad.Bytes()
881+
if val, ok := userdata.GetUint32(data, userdata.NFTNL_UDATA_SET_KEYBYTEORDER); ok {
882+
set.KeyByteOrder = parseSetByteOrder(val)
883+
}
884+
if val, ok := userdata.GetUint32(data, userdata.NFTNL_UDATA_SET_DATABYTEORDER); ok {
885+
set.DataByteOrder = parseSetByteOrder(val)
886+
}
865887
if val, ok := userdata.GetString(data, userdata.NFTNL_UDATA_SET_COMMENT); ok {
866888
set.Comment = val
867889
}
@@ -891,6 +913,17 @@ func setsFromMsg(msg netlink.Message) (*Set, error) {
891913
return &set, nil
892914
}
893915

916+
func parseSetByteOrder(v uint32) binaryutil.ByteOrder {
917+
switch v {
918+
case 1:
919+
return binaryutil.NativeEndian
920+
case 2:
921+
return binaryutil.BigEndian
922+
default:
923+
return nil
924+
}
925+
}
926+
894927
func parseSetDatatype(magic uint32) (SetDatatype, error) {
895928
types := make([]SetDatatype, 0, 32/SetConcatTypeBits)
896929
for magic != 0 {

0 commit comments

Comments
 (0)