Nftables: Added support for set creation.

PiperOrigin-RevId: 936156719
This commit is contained in:
Parth Sarthi
2026-06-22 11:23:05 -07:00
committed by gVisor bot
parent af745f8b6c
commit 5a0321fac1
6 changed files with 668 additions and 1 deletions
+123
View File
@@ -28,6 +28,7 @@ const (
NFT_OBJ_MAXNAMELEN = NFT_NAME_MAXLEN
NFT_USERDATA_MAXLEN = 256
NFT_OSF_MAXGENRELEN = 16
NFT_SET_EXPR_MAX = 2
)
// 16-byte Registers that can be used to maintain state for rules.
@@ -537,3 +538,125 @@ const (
__NFTA_NAT_MAX
NFTA_NAT_MAX = __NFTA_NAT_MAX - 1
)
// NfTableSetFlags represents the netfilter set flags.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
const (
NFT_SET_ANONYMOUS = uint16(0x1)
NFT_SET_CONSTANT = uint16(0x2)
NFT_SET_INTERVAL = uint16(0x4)
NFT_SET_MAP = uint16(0x8)
NFT_SET_TIMEOUT = uint16(0x10)
NFT_SET_EVAL = uint16(0x20)
NFT_SET_OBJECT = uint16(0x40)
NFT_SET_CONCAT = uint16(0x80)
NFT_SET_EXPR = uint16(0x100)
)
// NfTableSetAttributes represents the netfilter set attributes.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
const (
NFTA_SET_UNSPEC uint16 = iota
NFTA_SET_TABLE
NFTA_SET_NAME
NFTA_SET_FLAGS
NFTA_SET_KEY_TYPE
NFTA_SET_KEY_LEN
NFTA_SET_DATA_TYPE
NFTA_SET_DATA_LEN
NFTA_SET_POLICY
NFTA_SET_DESC
NFTA_SET_ID
NFTA_SET_TIMEOUT
NFTA_SET_GC_INTERVAL
NFTA_SET_USERDATA
NFTA_SET_PAD
NFTA_SET_OBJ_TYPE
NFTA_SET_HANDLE
NFTA_SET_EXPR
NFTA_SET_EXPRESSIONS
__NFTA_SET_MAX
NFTA_SET_MAX = __NFTA_SET_MAX - 1
)
// NfTableSetPolicies represents the netfilter set policies.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
const (
NFT_SET_POL_PERFORMANCE uint32 = iota // prefer high performance over low memory use
NFT_SET_POL_MEMORY // prefer low memory use over high performance
)
// NfTableSetDescAttributes represents the netfilter set description attributes.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
const (
NFTA_SET_DESC_UNSPEC uint16 = iota
NFTA_SET_DESC_SIZE
NFTA_SET_DESC_CONCAT
__NFTA_SET_DESC_MAX
NFTA_SET_DESC_MAX = __NFTA_SET_DESC_MAX - 1
)
// NfTableSetFieldAttributes represents the netfilter set field attributes.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
const (
NFTA_SET_FIELD_UNSPEC uint16 = iota
NFTA_SET_FIELD_LEN
__NFTA_SET_FIELD_MAX
NFTA_SET_FIELD_MAX = __NFTA_SET_FIELD_MAX - 1
)
// NfTableObjectAttributes represents the netfilter object attributes.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
const (
NFT_OBJECT_UNSPEC uint32 = iota
NFT_OBJECT_COUNTER
NFT_OBJECT_QUOTA
NFT_OBJECT_CT_HELPER
NFT_OBJECT_LIMIT
NFT_OBJECT_CONNLIMIT
NFT_OBJECT_TUNNEL
NFT_OBJECT_CT_TIMEOUT
NFT_OBJECT_SECMARK
NFT_OBJECT_CT_EXPECT
NFT_OBJECT_SYNPROXY
__NFT_OBJECT_MAX
NFT_OBJECT_MAX = __NFT_OBJECT_MAX - 1
)
// NfTableSetElemListAttributes represents the netfilter set element list attributes.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
const (
NFTA_SET_ELEM_LIST_UNSPEC uint16 = iota
NFTA_SET_ELEM_LIST_TABLE
NFTA_SET_ELEM_LIST_SET
NFTA_SET_ELEM_LIST_ELEMENTS
NFTA_SET_ELEM_LIST_SET_ID
__NFTA_SET_ELEM_LIST_MAX
NFTA_SET_ELEM_LIST_MAX = __NFTA_SET_ELEM_LIST_MAX - 1
)
// NfTableSetElemAttributes represents the netfilter set element attributes.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
const (
NFTA_SET_ELEM_UNSPEC uint16 = iota
NFTA_SET_ELEM_KEY
NFTA_SET_ELEM_DATA
NFTA_SET_ELEM_FLAGS
NFTA_SET_ELEM_TIMEOUT
NFTA_SET_ELEM_EXPIRATION
NFTA_SET_ELEM_USERDATA
NFTA_SET_ELEM_EXPR
NFTA_SET_ELEM_PAD
NFTA_SET_ELEM_OBJREF
NFTA_SET_ELEM_KEY_END
NFTA_SET_ELEM_EXPRESSIONS
__NFTA_SET_ELEM_MAX
NFTA_SET_ELEM_MAX = __NFTA_SET_ELEM_MAX - 1
)
// NfTableSetElemFlags represents the netfilter set element flags.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
const (
NFT_SET_ELEM_INTERVAL_END = uint16(0x1)
NFT_SET_ELEM_CATCHALL = uint16(0x2)
)
@@ -1224,7 +1224,9 @@ func (p *Protocol) processBatchMessage(ctx context.Context, buf []byte, ms *nlms
// **********************************************************************
// TODO: b/436922484 - Add a transaction system to avoid deep copying the
// entire NFTables structure.
// Change logic to just replace atomic ptrs.
// **********************************************************************
nft.Mu.Lock()
defer nft.Mu.Unlock()
@@ -1327,7 +1329,9 @@ func (p *Protocol) processBatchMessage(ctx context.Context, buf []byte, ms *nlms
subErr = p.deleteChain(nftCopy, attrs, family, hdr.Flags, hdr.NetFilterMsgType(), ms)
case linux.NFT_MSG_NEWRULE:
subErr = p.newRule(nftCopy, st, attrs, family, hdr.Flags, ms)
case linux.NFT_MSG_DELRULE, linux.NFT_MSG_DESTROYRULE, linux.NFT_MSG_NEWSET,
case linux.NFT_MSG_NEWSET:
subErr = nftCopy.NewSet(attrs, family, hdr.Flags, ms)
case linux.NFT_MSG_DELRULE, linux.NFT_MSG_DESTROYRULE,
linux.NFT_MSG_DELSET, linux.NFT_MSG_DESTROYSET, linux.NFT_MSG_NEWSETELEM,
linux.NFT_MSG_DELSETELEM, linux.NFT_MSG_DESTROYSETELEM,
linux.NFT_MSG_NEWOBJ, linux.NFT_MSG_DELOBJ, linux.NFT_MSG_DESTROYOBJ,
+1
View File
@@ -34,6 +34,7 @@ go_library(
"nftables.go",
"nftables_attrs.go",
"nftables_mutex.go",
"nftables_set.go",
"nftables_types.go",
"nftinterp.go",
],
+2
View File
@@ -597,6 +597,8 @@ func (nf *NFTables) AddTable(family stack.AddressFamily, name string,
flagSet: make(map[TableFlag]struct{}),
handle: nf.getNewTableHandle(),
handleCounter: atomicbitops.Uint64{},
sets: make(map[string]*nftSet),
setHandles: make(map[uint64]*nftSet),
}
tableMap[name] = t
tableHandleMap[t.handle] = t
+444
View File
@@ -0,0 +1,444 @@
// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nftables
import (
"math"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// isSame checks if the set is the same as the given set description.
// Ref: net/netfilter/nf_tables_api.c:nft_set_is_same
func (s *nftSet) isSame(other *nftSet) bool {
if s.keyType != other.keyType ||
s.dataType != other.dataType ||
s.flags != other.flags ||
s.keyLen != other.keyLen ||
s.dataLen != other.dataLen || s.fieldCount != other.fieldCount ||
s.timeout != other.timeout ||
s.gcInterval != other.gcInterval ||
s.policy != other.policy ||
len(s.exprInfos) != len(other.exprInfos) {
return false
}
for i := uint8(0); i < s.fieldCount; i++ {
if s.fieldLen[i] != other.fieldLen[i] {
return false
}
}
// TODO: b/505409691 - Compare exprs.
return true
}
// AddSetToTable adds a new set to the corresponding table, returning an error if
// the table doesn't exist or a set by the same name already exists.
func (nf *NFTables) addSetToTable(tab *Table, set *nftSet) *syserr.AnnotatedError {
handle := tab.getNewHandle()
set.handle = handle
tab.sets[set.name] = set
tab.setHandles[handle] = set
return nil
}
// validateSetFlags validates the set flags.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_newset
func validateSetFlags(flags uint16) *syserr.AnnotatedError {
if flags&^(linux.NFT_SET_ANONYMOUS|linux.NFT_SET_CONSTANT|
linux.NFT_SET_INTERVAL|linux.NFT_SET_TIMEOUT|
linux.NFT_SET_MAP|linux.NFT_SET_EVAL|
linux.NFT_SET_OBJECT|linux.NFT_SET_CONCAT|linux.NFT_SET_EXPR) != 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "Unsupported set flags")
}
if (flags & (linux.NFT_SET_MAP | linux.NFT_SET_OBJECT)) ==
(linux.NFT_SET_MAP | linux.NFT_SET_OBJECT) {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "Only one of NFT_SET_MAP or NFT_SET_OBJECT is supported")
}
if (flags & (linux.NFT_SET_EVAL | linux.NFT_SET_OBJECT)) ==
(linux.NFT_SET_EVAL | linux.NFT_SET_OBJECT) {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "Only one of NFT_SET_EVAL or NFT_SET_OBJECT is supported")
}
if (flags & (linux.NFT_SET_ANONYMOUS | linux.NFT_SET_TIMEOUT | linux.NFT_SET_EVAL)) ==
(linux.NFT_SET_ANONYMOUS | linux.NFT_SET_TIMEOUT) {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "Only one of NFT_SET_ANONYMOUS or NFT_SET_TIMEOUT is supported")
}
if (flags & (linux.NFT_SET_CONSTANT | linux.NFT_SET_TIMEOUT)) ==
(linux.NFT_SET_CONSTANT | linux.NFT_SET_TIMEOUT) {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "Only one of NFT_SET_CONSTANT or NFT_SET_TIMEOUT is supported")
}
return nil
}
// parseSetDataAttr parses the set data type and length attributes.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_newset
func parseSetDataAttr(attrs map[uint16]nlmsg.BytesView, setFlags uint16) (dataType uint32, dataLen uint32, err *syserr.AnnotatedError) {
dataType, ok := AttrNetToHost[uint32](linux.NFTA_SET_DATA_TYPE, attrs)
if !ok {
if setFlags&linux.NFT_SET_MAP != 0 {
return 0, 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set data type attribute is missing")
}
// If the set is not a map, then the data type attribute is not required.
return 0, 0, nil
}
if setFlags&linux.NFT_SET_MAP == 0 {
return 0, 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set data type attribute should not be present for non-map set")
}
if (dataType&linux.NFT_DATA_RESERVED_MASK) == linux.NFT_DATA_RESERVED_MASK &&
dataType != linux.NFT_DATA_VERDICT {
return 0, 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Unsupported set data type")
}
if dataType == linux.NFT_DATA_VERDICT {
return dataType, 16, nil
}
dataLen, ok = AttrNetToHost[uint32](linux.NFTA_SET_DATA_LEN, attrs)
if !ok {
return 0, 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set data length attribute is missing")
}
if dataLen == 0 || dataLen > linux.NFT_DATA_VALUE_MAXLEN {
return 0, 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Invalid set data length")
}
return dataType, dataLen, nil
}
// parseSetObjectAttr parses the set object type attribute.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_newset
func parseSetObjectAttr(attrs map[uint16]nlmsg.BytesView, setFlags uint16) (objType uint32, err *syserr.AnnotatedError) {
objType, ok := AttrNetToHost[uint32](linux.NFTA_SET_OBJ_TYPE, attrs)
if ok && (setFlags&linux.NFT_SET_OBJECT == 0) {
return 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set object flag is not set but object type attribute is present")
}
if !ok && (setFlags&linux.NFT_SET_OBJECT != 0) {
return 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set object flag is set but object type attribute is missing")
}
if !ok {
// The object type attribute is not required if the set is not an object set.
return linux.NFT_OBJECT_UNSPEC, nil
}
if objType == linux.NFT_OBJECT_UNSPEC || objType > linux.NFT_OBJECT_MAX {
return 0, syserr.NewAnnotatedError(syserr.ErrNotSupported, "Invalid set object type")
}
return objType, nil
}
// parseSetTimeoutAttr parses the set timeout attribute.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_newset
func parseSetTimeoutAttr(attrs map[uint16]nlmsg.BytesView, setFlags uint16) (timeMiliSec uint64, err *syserr.AnnotatedError) {
timeMS, ok := AttrNetToHost[uint64](linux.NFTA_SET_TIMEOUT, attrs)
if ok && (setFlags&linux.NFT_SET_TIMEOUT == 0) {
return 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set timeout flag is not set but timeout attribute is present")
}
if !ok {
// The timeout attribute is not a required attribute.
return 0, nil
}
if setFlags&linux.NFT_SET_ANONYMOUS != 0 {
return 0, syserr.NewAnnotatedError(syserr.ErrNotSupported, "Set timeout flag is not supported for anonymous sets")
}
return timeMS, nil
}
// parseSetGCIntervalAttr parses the set gc interval attribute.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_newset
// TODO: b/505409691 - Add support for timeout and gc.
func parseSetGCIntervalAttr(attrs map[uint16]nlmsg.BytesView, setFlags uint16) (gcIntMiliSec uint32, err *syserr.AnnotatedError) {
gc, ok := AttrNetToHost[uint32](linux.NFTA_SET_GC_INTERVAL, attrs)
if !ok {
// The gc interval attribute is not a required attribute.
return 0, nil
}
if setFlags&linux.NFT_SET_TIMEOUT == 0 {
return 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set gc flag is not set but gc attribute is present")
}
if setFlags&linux.NFT_SET_ANONYMOUS != 0 {
return 0, syserr.NewAnnotatedError(syserr.ErrNotSupported, "Set gc flag is not supported for anonymous sets")
}
return gc, nil
}
// parseSetPolicyAttr parses the set policy attribute.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_newset
// TODO: b/505409691 - Add support for policy.
func parseSetPolicyAttr(attrs map[uint16]nlmsg.BytesView, setFlags uint16) (policy uint32, err *syserr.AnnotatedError) {
policy, ok := AttrNetToHost[uint32](linux.NFTA_SET_POLICY, attrs)
if !ok {
// The policy attribute is not a required attribute;
// default to performance optimized.
return linux.NFT_SET_POL_PERFORMANCE, nil
}
if policy != linux.NFT_SET_POL_PERFORMANCE && policy != linux.NFT_SET_POL_MEMORY {
return 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Invalid set policy")
}
return policy, nil
}
// setConcatPolicy is the policy for parsing the set description concat attributes.
// Ref: net/netfilter/nf_tables_api.c:nft_concat_policy
var setConcatPolicy = []NlaPolicy{
linux.NFTA_SET_FIELD_LEN: {nlaType: linux.NLA_U32},
}
// parseSetConcatAttr parses the set description concat attributes.
// Ref: net/netfilter/nf_tables_api.c:nft_set_desc_concat_parse
func parseSetConcatAttr(descConcatAttrs nlmsg.BytesView, keyLen uint32) (concat []uint8, err *syserr.AnnotatedError) {
roundUpToNearestMultipleOfFour := func(num uint32) uint32 {
return (num + (3)) & ^uint32(3)
}
concatAttrsList := nlmsg.AttrsView(descConcatAttrs)
var concatList [linux.NFT_REG32_COUNT]uint8
i := 0
totalLen := uint32(0)
for !concatAttrsList.Empty() {
if i >= len(concatList) {
return nil, syserr.NewAnnotatedError(syserr.ErrFileTooBig, "Too many set description concat elements")
}
hdr, value, rest, ok := concatAttrsList.ParseFirst()
if !ok {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Failed to parse set description concat element")
}
concatAttrsList = rest
if hdr.Type != linux.NFTA_LIST_ELEM {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set description concat element is not of type NFTA_LIST_ELEM")
}
concatElem, ok := NfParseWithOpts(nlmsg.AttrsView(value), &NfParseOpts{
Policy: setConcatPolicy,
})
if !ok {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Failed to parse set description concat element attributes")
}
fieldLen, ok := AttrNetToHost[uint32](linux.NFTA_SET_FIELD_LEN, concatElem)
if !ok {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Failed to parse set description field length")
}
if fieldLen == 0 || fieldLen > math.MaxUint8 {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Invalid set field length")
}
concatList[i] = uint8(fieldLen)
i++
totalLen += roundUpToNearestMultipleOfFour(fieldLen)
}
if totalLen != keyLen {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Total set description field length does not match key length")
}
if keyLen > linux.NFT_REG32_COUNT {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Key length is larger than maximum supported register size")
}
return concatList[:i], nil
}
// setDescPolicy is the policy for parsing the set description attributes.
// Ref: net/netfilter/nf_tables_api.c:nft_set_desc_policy
var setDescPolicy = []NlaPolicy{
linux.NFTA_SET_DESC_SIZE: {nlaType: linux.NLA_U32},
linux.NFTA_SET_DESC_CONCAT: {nlaType: linux.NLA_NESTED, validator: AttrArrayValidator(setConcatPolicy)},
}
type setDesc struct {
size uint32
concat []uint8
}
// parseSetDescAttr parses the set description attributes.
// Ref: net/netfilter/nf_tables_api.c:nft_set_desc_concat_parse
func parseSetDescAttr(attrs map[uint16]nlmsg.BytesView, keyLen uint32, setFlags uint16) (concat []uint8, descSize uint32, err *syserr.AnnotatedError) {
descAttrs, ok := attrs[linux.NFTA_SET_DESC]
if !ok {
if setFlags&linux.NFT_SET_CONCAT != 0 {
return nil, 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set concat flag present but set description attribute is missing")
}
// The set description attribute is not a required attribute.
// If the descSize is 0, then the set has no bounds on the number of elements.
return nil, 0, nil
}
subAttrs, ok := NfParseWithOpts(nlmsg.AttrsView(descAttrs), &NfParseOpts{
Policy: setDescPolicy,
})
if !ok {
return nil, 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Failed to parse set description attributes")
}
descSize, ok = AttrNetToHost[uint32](linux.NFTA_SET_DESC_SIZE, subAttrs)
if !ok {
descSize = 0
}
if concatAttrs, ok := subAttrs[linux.NFTA_SET_DESC_CONCAT]; ok {
concat, err = parseSetConcatAttr(concatAttrs, keyLen)
if err != nil {
return nil, 0, err
}
}
if len(concat) > 1 {
if setFlags&linux.NFT_SET_CONCAT == 0 {
return nil, 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set concat flag missing but multiple concat elements found")
}
} else if setFlags&linux.NFT_SET_CONCAT != 0 {
return nil, 0, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set concat flag present but single/no concat elements found")
}
return concat, descSize, nil
}
// NewSet creates a set in the given table.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_newset
func (nf *NFTables) NewSet(attrs map[uint16]nlmsg.BytesView, family stack.AddressFamily, msgFlags uint16, ms *nlmsg.MessageSet) *syserr.AnnotatedError {
tabNameBytes, tabNameOk := attrs[linux.NFTA_SET_TABLE]
if !tabNameOk {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set table name attribute is malformed or not found")
}
setNameBytes, nameOk := attrs[linux.NFTA_SET_NAME]
if !nameOk {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set name attribute is malformed or not found")
}
if _, idOk := attrs[linux.NFTA_SET_ID]; !idOk {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set id attribute is missing")
}
keyType, keyTypeOk := AttrNetToHost[uint32](linux.NFTA_SET_KEY_TYPE, attrs)
if !keyTypeOk {
keyType = uint32(0)
}
keyLen, keyLenOk := AttrNetToHost[uint32](linux.NFTA_SET_KEY_LEN, attrs)
if !keyLenOk {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set key length attribute is missing")
}
if keyLen == 0 || keyLen > linux.NFT_DATA_VALUE_MAXLEN {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Set key length attribute is invalid")
}
setFlagsU32, ok := AttrNetToHost[uint32](linux.NFTA_SET_FLAGS, attrs)
setFlags := uint16(setFlagsU32)
if ok {
if err := validateSetFlags(setFlags); err != nil {
return err
}
}
dataType, dataLen, err := parseSetDataAttr(attrs, setFlags)
if err != nil {
return err
}
objType, err := parseSetObjectAttr(attrs, setFlags)
if err != nil {
return err
}
timeout, err := parseSetTimeoutAttr(attrs, setFlags)
if err != nil {
return err
}
gcInterval, err := parseSetGCIntervalAttr(attrs, setFlags)
if err != nil {
return err
}
policy, err := parseSetPolicyAttr(attrs, setFlags)
if err != nil {
return err
}
descConcat, descSize, err := parseSetDescAttr(attrs, keyLen, setFlags)
if err != nil {
return err
}
var tab *Table
if tabNameOk {
tabName := tabNameBytes.String()
tab, err = nf.GetTable(family, tabName, uint32(ms.PortID))
if err != nil {
return err
}
}
udataAttr, udataExists := attrs[linux.NFTA_SET_USERDATA]
var udata []byte
if udataExists {
udata = []byte(udataAttr)
}
if uint32(msgFlags)&linux.NLM_F_CREATE == 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "NLM_F_CREATE is required for set creation")
}
var exprInfos []ExprInfo
if singleExpr, ok := attrs[linux.NFTA_SET_EXPR]; ok {
exprInfo, err := nf.ParseExpr(nlmsg.AttrsView(singleExpr))
if err != nil {
return err
}
exprInfos = []ExprInfo{*exprInfo}
} else if exprBytes, ok := attrs[linux.NFTA_SET_EXPRESSIONS]; ok {
exprInfos, err = nf.ParseNestedExprs(nlmsg.AttrsView(exprBytes), linux.NFT_SET_EXPR_MAX)
if err != nil {
return err
}
}
setName := setNameBytes.String()
newSet := &nftSet{
name: setName,
keyType: keyType,
dataType: dataType,
objType: objType,
descSize: descSize,
fieldLen: descConcat,
fieldCount: uint8(len(descConcat)),
timeout: timeout,
gcInterval: gcInterval,
policy: policy,
udata: udata,
exprInfos: exprInfos,
flags: setFlags,
dead: 0,
keyLen: uint8(keyLen),
dataLen: uint8(dataLen),
}
if set, exists := tab.sets[setName]; exists {
if msgFlags&linux.NLM_F_EXCL != 0 {
return syserr.NewAnnotatedError(syserr.ErrExists, "Set already exists")
}
if msgFlags&linux.NLM_F_REPLACE != 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "Replace flag is not supported")
}
if set.flags&linux.NFT_SET_ANONYMOUS != 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "Anonymous set already exists")
}
if set.isSame(newSet) {
return nil
}
return syserr.NewAnnotatedError(syserr.ErrExists, "Set already exists with differing attributes")
}
if msgFlags&linux.NLM_F_CREATE == 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "NLM_F_CREATE is required for set creation")
}
// TODO: b/505409691 - Support non-map sets.
if setFlags&linux.NFT_SET_MAP == 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "only map sets are supported yet")
}
return nf.addSetToTable(tab, newSet)
}
+93
View File
@@ -307,6 +307,10 @@ type Table struct {
// Note: currently nftables only has the single Dormant flag.
flagSet map[TableFlag]struct{}
// sets is a map of sets for each table.
sets map[string]*nftSet
setHandles map[uint64]*nftSet
// handleCounter is the counter for chain and rule handles.
handleCounter atomicbitops.Uint64
@@ -908,6 +912,61 @@ var netlinkAFToStackAF = map[uint8]stack.AddressFamily{
linux.NFPROTO_IPV6: stack.IP6,
}
// Set represents an nftables set.
// Ref: include/net/netfilter/nf_tables.h:nft_set
type nftSet struct {
// name represents the unique name for this set.
name string
// keyType represents the type of key; i.e. IPv4, IPv6, port, etc.
keyType uint32
// dataType represents the type of data; i.e. VERDICT or DATA.
dataType uint32
// objType represents the type of object that the set is storing.
objType uint32
// descSize represents the max number of elements.
// If 0, then the set has no bounds on the number of elements.
descSize uint32
// fieldLen represents the length of each sub-key.
fieldLen []uint8
// fieldCount represents the number of sub-keys.
fieldCount uint8
// timeout represents the timeout for the elements in the set.
// TODO: b/505409691 - Add support for set timeout.
timeout uint64
// gcInterval represents the interval for garbage collection.
// GC gets rid of expired elements in the set.
// TODO: b/505409691 - Add support for gc interval.
gcInterval uint32
// policy passed by the user hints at
// whether the set should be memory or performance optimized.
// TODO: b/505409691 - Add support for set policy.
policy uint32
// udata represents the user data of the set.
udata []byte
// exprInfos represents the stateful nft expressions/ops;
// counter, limit, etc.
// that are directly associated with the individual set elements.
// TODO: b/505409691 - Add support for expressions other than the counters.
exprInfos []ExprInfo
// flags determines the set's configuration.
flags uint16
// dead marks the set as deleted.
dead uint8
// genmask links to nftables generation.
// We don't need to support this as we make a full copy of
// the set on each transaction; so each NFTables instance
// is it's own "generation".
genmask uint16
// keyLen is the length of the key,
// or the combined length of all the sub-keys.
keyLen uint8
// dataLen is the length of the data;
// incase of a verdict set, this is not required.
dataLen uint8
// handle is the NFTables unique identifier for this set.
handle uint64
}
// AFtoNetlinkAF converts a generic address family to a netfilter address family.
// On error, we simply cast it to be a stack.AddressFamily and return an error to allow netfilter
// sockets to handle it accordingly if needed.
@@ -1122,6 +1181,32 @@ func deepCopyChain(chain *Chain, tableCopy *Table) *Chain {
return chainCopy
}
// deepCopySet returns a deep copy of the Set struct.
func deepCopySet(set *nftSet) *nftSet {
setCopy := &nftSet{
name: set.name,
keyType: set.keyType,
dataType: set.dataType,
objType: set.objType,
descSize: set.descSize,
fieldLen: slices.Clone(set.fieldLen),
fieldCount: set.fieldCount,
timeout: set.timeout,
gcInterval: set.gcInterval,
policy: set.policy,
udata: slices.Clone(set.udata),
exprInfos: slices.Clone(set.exprInfos),
flags: set.flags,
dead: set.dead,
genmask: set.genmask,
keyLen: set.keyLen,
dataLen: set.dataLen,
handle: set.handle,
}
return setCopy
}
// deepCopyTable returns a deep copy of the Table struct.
func deepCopyTable(table *Table, afFilter *addressFamilyFilter) *Table {
tableCopy := &Table{
@@ -1131,6 +1216,8 @@ func deepCopyTable(table *Table, afFilter *addressFamilyFilter) *Table {
chainHandles: make(map[uint64]*Chain),
flagSet: make(map[TableFlag]struct{}),
handle: table.handle,
sets: make(map[string]*nftSet),
setHandles: make(map[uint64]*nftSet),
owner: table.owner,
userData: slices.Clone(table.userData),
}
@@ -1140,6 +1227,12 @@ func deepCopyTable(table *Table, afFilter *addressFamilyFilter) *Table {
tableCopy.flagSet[flag] = struct{}{}
}
for setName, set := range table.sets {
setCopy := deepCopySet(set)
tableCopy.sets[setName] = setCopy
tableCopy.setHandles[setCopy.handle] = setCopy
}
for chainName, chain := range table.chains {
chainCopy := deepCopyChain(chain, tableCopy)
tableCopy.chains[chainName] = chainCopy