Compare commits

...

4 Commits

Author SHA1 Message Date
Aleksei Ilin 9e98914fa2
Merge 9a398ccf70 into 1c789726cf 2025-01-16 10:55:25 -03:00
turekt 1c789726cf
Fix Fib parsing (#296) 2025-01-16 09:15:33 +01:00
Aleksei Ilin 9a398ccf70
set: Add set support for size specifier
Handle attribute NFTNL_SET_DESC_SIZE, as done in libnftnl:
https://git.netfilter.org/libnftnl/tree/src/set.c#n424

Example:
nft add set ip filter myset { type ipv4_addr\; size 65535\; flags dynamic\; }
2025-01-11 21:55:03 +01:00
Aleksei Ilin 8c0e43d650
set: Fix missing 'Dynamic' field restore in set unmarshal 2025-01-11 21:53:06 +01:00
8 changed files with 196 additions and 14 deletions

View File

@ -207,6 +207,8 @@ func exprFromName(name string) Any {
e = &SecMark{} e = &SecMark{}
case "cttimeout": case "cttimeout":
e = &CtTimeout{} e = &CtTimeout{}
case "fib":
e = &Fib{}
} }
return e return e
} }

View File

@ -118,17 +118,22 @@ func (e *Fib) unmarshal(fam byte, data []byte) error {
e.Register = ad.Uint32() e.Register = ad.Uint32()
case unix.NFTA_FIB_RESULT: case unix.NFTA_FIB_RESULT:
result := ad.Uint32() result := ad.Uint32()
e.ResultOIF = (result & unix.NFT_FIB_RESULT_OIF) == 1 switch result {
e.ResultOIFNAME = (result & unix.NFT_FIB_RESULT_OIFNAME) == 1 case unix.NFT_FIB_RESULT_OIF:
e.ResultADDRTYPE = (result & unix.NFT_FIB_RESULT_ADDRTYPE) == 1 e.ResultOIF = true
case unix.NFT_FIB_RESULT_OIFNAME:
e.ResultOIFNAME = true
case unix.NFT_FIB_RESULT_ADDRTYPE:
e.ResultADDRTYPE = true
}
case unix.NFTA_FIB_FLAGS: case unix.NFTA_FIB_FLAGS:
flags := ad.Uint32() flags := ad.Uint32()
e.FlagSADDR = (flags & unix.NFTA_FIB_F_SADDR) == 1 e.FlagSADDR = (flags & unix.NFTA_FIB_F_SADDR) != 0
e.FlagDADDR = (flags & unix.NFTA_FIB_F_DADDR) == 1 e.FlagDADDR = (flags & unix.NFTA_FIB_F_DADDR) != 0
e.FlagMARK = (flags & unix.NFTA_FIB_F_MARK) == 1 e.FlagMARK = (flags & unix.NFTA_FIB_F_MARK) != 0
e.FlagIIF = (flags & unix.NFTA_FIB_F_IIF) == 1 e.FlagIIF = (flags & unix.NFTA_FIB_F_IIF) != 0
e.FlagOIF = (flags & unix.NFTA_FIB_F_OIF) == 1 e.FlagOIF = (flags & unix.NFTA_FIB_F_OIF) != 0
e.FlagPRESENT = (flags & unix.NFTA_FIB_F_PRESENT) == 1 e.FlagPRESENT = (flags & unix.NFTA_FIB_F_PRESENT) != 0
} }
} }
return ad.Err() return ad.Err()

View File

@ -123,7 +123,7 @@ func (l *Limit) unmarshal(fam byte, data []byte) error {
return fmt.Errorf("expr: invalid limit type %d", l.Type) return fmt.Errorf("expr: invalid limit type %d", l.Type)
} }
case unix.NFTA_LIMIT_FLAGS: case unix.NFTA_LIMIT_FLAGS:
l.Over = (ad.Uint32() & unix.NFT_LIMIT_F_INV) == 1 l.Over = (ad.Uint32() & unix.NFT_LIMIT_F_INV) != 0
default: default:
return errors.New("expr: unhandled limit netlink attribute") return errors.New("expr: unhandled limit netlink attribute")
} }

View File

@ -73,7 +73,7 @@ func (q *Quota) unmarshal(fam byte, data []byte) error {
case unix.NFTA_QUOTA_CONSUMED: case unix.NFTA_QUOTA_CONSUMED:
q.Consumed = ad.Uint64() q.Consumed = ad.Uint64()
case unix.NFTA_QUOTA_FLAGS: case unix.NFTA_QUOTA_FLAGS:
q.Over = (ad.Uint32() & unix.NFT_QUOTA_F_INV) == 1 q.Over = (ad.Uint32() & unix.NFT_QUOTA_F_INV) != 0
} }
} }
return ad.Err() return ad.Err()

View File

@ -6387,6 +6387,61 @@ func TestFib(t *testing.T) {
} }
} }
func TestFibSystem(t *testing.T) {
c, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
c.FlushRuleset()
defer c.FlushRuleset()
filter := c.AddTable(&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
})
chain := c.AddChain(&nftables.Chain{
Name: "test-chain",
Table: filter,
})
expect := &expr.Fib{
Register: 1,
FlagDADDR: true,
ResultADDRTYPE: true,
}
c.AddRule(&nftables.Rule{
Table: filter,
Chain: chain,
Exprs: []expr.Any{expect},
})
if err := c.Flush(); err != nil {
t.Fatalf("c.Flush() failed with error %+v", err)
}
rules, err := c.GetRules(filter, chain)
if err != nil {
t.Fatalf("GetRules failed: %v", err)
}
if got, want := len(rules), 1; got != want {
t.Fatalf("unexpected number of rules: got %d, want %d", got, want)
}
if got, want := len(rules[0].Exprs), 1; got != want {
t.Fatalf("unexpected number of exprs: got %d, want %d", got, want)
}
fib := rules[0].Exprs[0].(*expr.Fib)
if got, want := fib.FlagDADDR, expect.FlagDADDR; got != want {
t.Errorf("fib daddr not equal: got %+v, want %+v", got, want)
}
if got, want := fib.ResultADDRTYPE, expect.ResultADDRTYPE; got != want {
t.Errorf("fib addr type not equal: got %+v, want %+v", got, want)
}
}
func TestNumgen(t *testing.T) { func TestNumgen(t *testing.T) {
tests := []struct { tests := []struct {
name string name string

View File

@ -36,7 +36,7 @@ func (q *QuotaObj) unmarshal(ad *netlink.AttributeDecoder) error {
case unix.NFTA_QUOTA_CONSUMED: case unix.NFTA_QUOTA_CONSUMED:
q.Consumed = ad.Uint64() q.Consumed = ad.Uint64()
case unix.NFTA_QUOTA_FLAGS: case unix.NFTA_QUOTA_FLAGS:
q.Over = (ad.Uint32() & unix.NFT_QUOTA_F_INV) == 1 q.Over = (ad.Uint32() & unix.NFT_QUOTA_F_INV) != 0
} }
} }
return nil return nil

41
set.go
View File

@ -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.
@ -566,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
@ -592,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
@ -747,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()
@ -775,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

View File

@ -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)
}
})
}
}