Files
Parth SarthiandgVisor bot 86be803f8a NFTables: Support NFT_MSG_DELSETELEM and NFT_MSG_DESTROYSETELEM.
- Allows flushing/deleting set elements.
- Added unit/integration tests.

PiperOrigin-RevId: 958448387
2026-08-03 10:28:25 -07:00

1568 lines
53 KiB
Go

// 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 (
"bytes"
"fmt"
"math"
"slices"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/marshal/primitive"
"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
}
// exprInfoToSetOp converts the given expression information to a set operation.
func exprInfoToSetOp(exprInfo *ExprInfo) (operation, *syserr.AnnotatedError) {
if exprInfo == nil {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "expression information is nil")
}
opType := ToOpType(exprInfo.ExprName)
if opType == OpTypeUnknown {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "invalid set expression name")
}
// TODO - b/505409691: Support quota, and limit ops in Set.
// Nftables only supports counter, quota, and limit ops in sets.
switch opType {
case OpTypeCounter:
op, err := initCounter(*exprInfo)
if err != nil {
return nil, err
}
return op, nil
default:
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "unsupported set expression type")
}
}
// 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, err := NfParseWithOpts(nlmsg.AttrsView(value), &NfParseOpts{
Policy: setConcatPolicy,
})
if err != nil {
return nil, err
}
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, err := NfParseWithOpts(nlmsg.AttrsView(descAttrs), &NfParseOpts{
Policy: setDescPolicy,
})
if err != nil {
return nil, 0, err
}
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 := ParseExpr(nlmsg.AttrsView(singleExpr))
if err != nil {
return err
}
exprInfos = []ExprInfo{*exprInfo}
} else if exprBytes, ok := attrs[linux.NFTA_SET_EXPRESSIONS]; ok {
exprInfos, err = 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")
}
newSet.backend = nf.newSetMapBackend(int(keyLen), int(dataLen))
return nf.addSetToTable(tab, newSet)
}
// nftSetExprPolicy is the policy for parsing the set expression attributes.
// Ref: net/netfilter/nf_tables_api.c:nft_set_expr_policy
var nftSetExprPolicy = []NlaPolicy{
linux.NFTA_EXPR_NAME: {nlaType: linux.NLA_STRING},
linux.NFTA_EXPR_DATA: {nlaType: linux.NLA_NESTED},
}
// setElemPolicy is the policy for parsing the set element attributes.
// Ref: net/netfilter/nf_tables_api.c:nft_set_elem_policy
var setElemPolicy = []NlaPolicy{
linux.NFTA_SET_ELEM_KEY: {nlaType: linux.NLA_NESTED},
linux.NFTA_SET_ELEM_DATA: {nlaType: linux.NLA_NESTED},
linux.NFTA_SET_ELEM_FLAGS: {nlaType: linux.NLA_U32},
linux.NFTA_SET_ELEM_TIMEOUT: {nlaType: linux.NLA_U64},
linux.NFTA_SET_ELEM_EXPIRATION: {nlaType: linux.NLA_U64},
linux.NFTA_SET_ELEM_USERDATA: {nlaType: linux.NLA_BINARY, validator: AttrMaxLenValidator(linux.NFT_USERDATA_MAXLEN - 1)},
linux.NFTA_SET_ELEM_EXPR: {nlaType: linux.NLA_NESTED},
linux.NFTA_SET_ELEM_OBJREF: {nlaType: linux.NLA_STRING, validator: AttrMaxLenValidator(linux.NFT_OBJ_MAXNAMELEN - 1)},
linux.NFTA_SET_ELEM_KEY_END: {nlaType: linux.NLA_NESTED},
linux.NFTA_SET_ELEM_EXPRESSIONS: {nlaType: linux.NLA_NESTED, validator: AttrArrayValidator(nftSetExprPolicy)},
}
// setElemListPolicy is the policy for parsing the set element list attributes.
// Ref: net/netfilter/nf_tables_api.c:nft_set_elem_list_policy
var setElemListPolicy = []NlaPolicy{
linux.NFTA_SET_ELEM_LIST_TABLE: {nlaType: linux.NLA_STRING, validator: AttrMaxLenValidator(linux.NFT_TABLE_MAXNAMELEN - 1)},
linux.NFTA_SET_ELEM_LIST_SET: {nlaType: linux.NLA_STRING, validator: AttrMaxLenValidator(linux.NFT_SET_MAXNAMELEN - 1)},
linux.NFTA_SET_ELEM_LIST_ELEMENTS: {nlaType: linux.NLA_NESTED, validator: AttrArrayValidator(setElemPolicy)},
linux.NFTA_SET_ELEM_LIST_SET_ID: {nlaType: linux.NLA_U32},
}
// validateSetElemFlags validates the set element flags.
// Ref: net/netfilter/nf_tables_api.c:nft_setelem_parse_flags
func validateSetElemFlags(setFlags uint16, elemFlags uint16) *syserr.AnnotatedError {
if elemFlags & ^(linux.NFT_SET_ELEM_INTERVAL_END|linux.NFT_SET_ELEM_CATCHALL) != 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "unsupported set element flags")
}
if (setFlags&linux.NFT_SET_INTERVAL) == 0 &&
(elemFlags&linux.NFT_SET_ELEM_INTERVAL_END != 0) {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "interval end flag is only supported for interval sets")
}
if (elemFlags & (linux.NFT_SET_ELEM_INTERVAL_END | linux.NFT_SET_ELEM_CATCHALL)) ==
(linux.NFT_SET_ELEM_INTERVAL_END | linux.NFT_SET_ELEM_CATCHALL) {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "interval end and catchall flags cannot be set at the same time")
}
return nil
}
// parseSetElemKeyAttr parses the set element key attribute.
// Ref: net/netfilter/nf_tables_api.c:nft_setelem_parse_key
func parseSetElemKeyAttr(attrs nlmsg.BytesView, setKeyLen int) ([]byte, *syserr.AnnotatedError) {
keyDataAttrs, ok := NfParse(nlmsg.AttrsView(attrs))
if !ok {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse key attributes")
}
keyBytes, err := parseDataAttrs(keyDataAttrs)
if err != nil {
return nil, err
}
if len(keyBytes) != int(setKeyLen) {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "key length does not match set key length")
}
return keyBytes, nil
}
// parseSetElemKeys parses the element key attributes.
func parseSetElemKeys(elemAttrs map[uint16]nlmsg.BytesView, setKeyLen int) (startKey []byte, endKey []byte, err *syserr.AnnotatedError) {
if keyAttr, ok := elemAttrs[linux.NFTA_SET_ELEM_KEY]; ok {
startKey, err = parseSetElemKeyAttr(keyAttr, setKeyLen)
if err != nil {
return nil, nil, err
}
}
if endKeyAttr, ok := elemAttrs[linux.NFTA_SET_ELEM_KEY_END]; ok {
endKey, err = parseSetElemKeyAttr(endKeyAttr, setKeyLen)
if err != nil {
return nil, nil, err
}
}
return startKey, endKey, nil
}
func (s *nftSet) parseElemDataAttr(tab *Table, dataAttrs nlmsg.BytesView) (*dataOrVerdict, *syserr.AnnotatedError) {
dv := &dataOrVerdict{}
dataValueAttr, ok := NfParse(nlmsg.AttrsView(dataAttrs))
if !ok {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse set element data attribute")
}
var dataValue []byte
var verdict Verdict
dataType := linux.NFT_DATA_VALUE
if b, err := parseDataAttrs(dataValueAttr); err == nil {
dataValue = b
} else if v, err := parseVerdictAttrs(tab, dataValueAttr); err == nil {
verdict = v
dataType = linux.NFT_DATA_VERDICT
} else {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse set element data attribute")
}
switch dataType {
case linux.NFT_DATA_VALUE:
lenData := len(dataValue)
if lenData != int(s.dataLen) {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "data length does not match set data length")
}
for _, ops := range s.bindings {
if validateDataRegister(ops.dregIdx, lenData) != nil {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Data register does not match set data length")
}
}
case linux.NFT_DATA_VERDICT:
// TODO: b/505409691 - Support validation for verdict lookups.
// For verdict data, it should check that the jump/gotos
// are not illegal or out of bounds.
log.Warningf("Unimplemented: verdict data verification is not supported yet; assuming it is valid.")
}
dv.data = dataValue
dv.verdict = verdict
dv.isVerdict = dataType == linux.NFT_DATA_VERDICT
return dv, nil
}
// conflicts checks if the set element data/verdict clashes with the other set element.
// Ref: net/netfilter/nf_tables_api.c duplicate clash detection logic.
func (e *nftSetElem) conflicts(other *nftSetElem) bool {
if e.data.isVerdict != other.data.isVerdict ||
e.data.verdict != other.data.verdict ||
!bytes.Equal(e.data.data, other.data.data) {
return true
}
// TODO: b/505409691 - Compare objref when supported.
return false
}
// destroy destroys the set element and its operations.
func (e *nftSetElem) destroy() {
if e.data.isVerdict && e.data.verdict.Chain != nil {
e.data.verdict.Chain.DecrementChainUse()
e.data.verdict.Chain = nil
}
for _, op := range e.ops {
op.destroy()
}
}
// addCatchallElement adds a catchall element to the set.
// Ref: net/netfilter/nf_tables_api.c:nft_add_set_elem
func (s *nftSet) addCatchAllElement(elem *nftSetElem, msgFlags uint16) *syserr.AnnotatedError {
catchAllElem := s.catchAllElem
if catchAllElem == nil {
s.catchAllElem = elem
return nil
}
if msgFlags&linux.NLM_F_EXCL != 0 {
return syserr.NewAnnotatedError(syserr.ErrExists, "catchall element already exists")
}
if !catchAllElem.conflicts(elem) {
return nil
}
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "catchall element already exists and is not the same")
}
// commitElement adds a set element to the set backend.
func (s *nftSet) commitElement(elem *nftSetElem, msgFlags uint16) *syserr.AnnotatedError {
sb := s.backend
newIdx := len(s.elements)
existingIdx, err := sb.Add(elem, newIdx)
if err != nil {
return err
}
// Element was added successfully and no duplicate was found.
if existingIdx == newIdx {
s.elements = append(s.elements, *elem)
return nil
}
// Handle existing element.
if msgFlags&linux.NLM_F_EXCL != 0 {
return err
}
// if NLM_F_EXCL is not set, element should not conflict.
existingElem := &s.elements[existingIdx]
if !existingElem.conflicts(elem) {
return nil
}
return syserr.NewAnnotatedError(syserr.ErrExists, "set element already exists")
}
// removeElement removes a regular element from the set and its backend.
func (s *nftSet) removeElement(startKey, endKey []byte) *syserr.AnnotatedError {
elem := &nftSetElem{
startKey: startKey,
endKey: endKey,
}
// Remove from backend.
idx, err := s.backend.Remove(elem)
if err != nil {
return err
}
// Remove from set.elements.
lastIdx := len(s.elements) - 1
de := s.elements[idx]
if idx != lastIdx {
// Swap with the last element and update its index in backend.
movedElem := s.elements[lastIdx]
s.elements[idx] = movedElem
if err := s.backend.Update(&s.elements[idx], idx); err != nil {
return err
}
}
// Slice off last element.
s.elements = s.elements[:lastIdx]
de.destroy()
return nil
}
// removeAllElements removes all elements from a set.
func (s *nftSet) removeAllElements() *syserr.AnnotatedError {
// Destroy all elements in the set.
for i := range s.elements {
s.elements[i].destroy()
}
// Destroy the catchall element.
if s.catchAllElem != nil {
s.catchAllElem.destroy()
}
s.backend.RemoveAll()
s.elements = nil
s.catchAllElem = nil
return nil
}
// destroy removes all elements and clears the set fields.
func (s *nftSet) destroy() {
if len(s.bindings) != 0 {
log.BugTraceback(fmt.Errorf("set: %s has bindings count %d != 0", s.name, len(s.bindings)))
}
s.removeAllElements()
*s = nftSet{dead: 1}
}
// addElemToSet adds a set element to a set.
// Ref: net/netfilter/nf_tables_api.c:nft_add_set_elem
func (s *nftSet) addElemToSet(tab *Table, elemAttrs map[uint16]nlmsg.BytesView, msgFlags uint16) *syserr.AnnotatedError {
// If the set has a desc size, check if the set is full.
// Otherwise, allow any number of elements to be added to the set.
// Ref: net/netfilter/nf_tables_api.c:nft_set_maxsize
if s.descSize > 0 && len(s.elements) >= int(s.descSize) {
return syserr.NewAnnotatedError(syserr.ErrTooManyOpenFiles, "set element limit reached")
}
flagU32, flagExists := AttrNetToHost[uint32](linux.NFTA_SET_ELEM_FLAGS, elemAttrs)
if !flagExists {
flagU32 = uint32(0)
}
flag := uint16(flagU32)
if err := validateSetElemFlags(s.flags, flag); err != nil {
return err
}
_, keyExists := elemAttrs[linux.NFTA_SET_ELEM_KEY]
if keyExists && (flag&linux.NFT_SET_ELEM_CATCHALL != 0) {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set element key attribute should not be present for catchall element")
}
if !keyExists && (flag&linux.NFT_SET_ELEM_CATCHALL == 0) {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set element key attribute is missing for non-catchall element")
}
dataAttr, dataExists := elemAttrs[linux.NFTA_SET_ELEM_DATA]
if (s.flags & linux.NFT_SET_MAP) != 0 {
if !dataExists && (flag&linux.NFT_SET_ELEM_INTERVAL_END == 0) {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set element data attribute is missing for non-interval element")
}
} else if dataExists {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set element data attribute should not be present for non-map set")
}
_, objRefExists := elemAttrs[linux.NFTA_SET_ELEM_OBJREF]
if (s.flags & linux.NFT_SET_OBJECT) != 0 {
if !objRefExists && (flag&linux.NFT_SET_ELEM_INTERVAL_END == 0) {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set element object reference attribute is missing for non-interval element")
}
} else if objRefExists {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set element object reference attribute should not be present for non-object set")
}
if (flag&linux.NFT_SET_ELEM_INTERVAL_END) != 0 &&
(dataExists ||
objRefExists ||
elemAttrs[linux.NFTA_SET_ELEM_TIMEOUT] != nil ||
elemAttrs[linux.NFTA_SET_ELEM_EXPIRATION] != nil ||
elemAttrs[linux.NFTA_SET_ELEM_USERDATA] != nil ||
elemAttrs[linux.NFTA_SET_ELEM_EXPR] != nil ||
elemAttrs[linux.NFTA_SET_ELEM_KEY_END] != nil ||
elemAttrs[linux.NFTA_SET_ELEM_EXPRESSIONS] != nil) {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "interval end flag is set but interval end attributes are missing")
}
// TODO: b/505409691 - Add support for timeout and gc.
timeout, ok := AttrNetToHost[uint64](linux.NFTA_SET_ELEM_TIMEOUT, elemAttrs)
if ok {
if (s.flags & linux.NFT_SET_TIMEOUT) == 0 {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "timeout flag is not set but timeout attribute is present")
}
} else if (s.flags&linux.NFT_SET_TIMEOUT) != 0 && (flag&linux.NFT_SET_ELEM_INTERVAL_END) == 0 {
timeout = s.timeout
}
// TODO: b/505409691 - Add support for expiration.
expiration, ok := AttrNetToHost[uint64](linux.NFTA_SET_ELEM_EXPIRATION, elemAttrs)
if ok {
if (s.flags & linux.NFT_SET_TIMEOUT) == 0 {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "expiration flag is not set but expiration attribute is present")
}
if timeout == 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "expiration attribute present but timeout is zero")
}
if expiration > timeout {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "expiration time is greater than timeout")
}
}
var exprInfos []ExprInfo
var err *syserr.AnnotatedError
expr, ok := elemAttrs[linux.NFTA_SET_ELEM_EXPR]
if ok {
if len(s.exprInfos) > 0 && len(s.exprInfos) != 1 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "only one expression is supported for set element expression")
}
exprInfo, err := ParseExpr(nlmsg.AttrsView(expr))
if err != nil {
return err
}
exprInfos = []ExprInfo{*exprInfo}
} else {
exprListAttr, ok := elemAttrs[linux.NFTA_SET_ELEM_EXPRESSIONS]
if ok {
exprInfos, err = ParseNestedExprs(nlmsg.AttrsView(exprListAttr), linux.NFT_SET_EXPR_MAX)
if err != nil {
return err
}
}
}
if len(exprInfos) > 0 && len(s.exprInfos) > 0 {
if len(exprInfos) != len(s.exprInfos) {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "number of set element expressions does not match number of set operations")
}
for i, exprInfo := range exprInfos {
if exprInfo.ExprName != s.exprInfos[i].ExprName {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "set element expression operation does not match set operation")
}
}
} else if len(s.exprInfos) != 0 && (flag&linux.NFT_SET_ELEM_INTERVAL_END) == 0 {
exprInfos = s.exprInfos
}
// Convert expressions to operations to execute at evaluation time.
// Each element has it's private set of operations to execute at evaluation time.
// Ref: net/netfilter/nf_tables_api.c:nft_set_elem_expr_clone
var ops []operation
if len(exprInfos) != 0 {
ops = make([]operation, len(exprInfos))
}
for i, ei := range exprInfos {
var err *syserr.AnnotatedError
ops[i], err = exprInfoToSetOp(&ei)
if err != nil {
return err
}
}
startKey, endKey, err := parseSetElemKeys(elemAttrs, int(s.keyLen))
if err != nil {
return err
}
if objRefExists {
// TODO: b/505409691 - Support object reference attribute.
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "object reference attribute is not supported")
}
var dv dataOrVerdict
if dataExists {
parsedDv, err := s.parseElemDataAttr(tab, dataAttr)
if err != nil {
return err
}
dv = *parsedDv
}
var userData []byte
userDataAttr, userDataExists := elemAttrs[linux.NFTA_SET_ELEM_USERDATA]
if userDataExists {
userData = slices.Clone([]byte(userDataAttr))
}
newSetElem := nftSetElem{
startKey: startKey,
endKey: endKey,
timeout: timeout,
expiration: expiration,
ops: ops,
userData: userData,
data: dv,
}
if flag&linux.NFT_SET_ELEM_CATCHALL != 0 {
return s.addCatchAllElement(&newSetElem, msgFlags)
}
return s.commitElement(&newSetElem, msgFlags)
}
// parseAttrAndAddElements adds a list of elements to a set.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_newsetelem
func (s *nftSet) parseAttrAndAddElements(tab *Table, attr nlmsg.AttrsView, msgFlags uint16) *syserr.AnnotatedError {
for len(attr) > 0 {
_, elem, rest, ok := attr.ParseFirst()
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse set elem list attributes")
}
attr = rest
elemAttrs, err := NfParseWithOpts(nlmsg.AttrsView(elem), &NfParseOpts{
Policy: setElemPolicy,
})
if err != nil {
return err
}
if err := s.addElemToSet(tab, elemAttrs, msgFlags); err != nil {
return err
}
}
return nil
}
// parseAttrAndRemoveElem removes a single element from a set.
// Ref: net/netfilter/nf_tables_api.c:nft_del_setelem()
func (s *nftSet) parseAttrAndRemoveElem(elemAttrs map[uint16]nlmsg.BytesView, msgFlags uint16) *syserr.AnnotatedError {
flagU32, flagExists := AttrNetToHost[uint32](linux.NFTA_SET_ELEM_FLAGS, elemAttrs)
if !flagExists {
flagU32 = uint32(0)
}
flag := uint16(flagU32)
if err := validateSetElemFlags(s.flags, flag); err != nil {
return err
}
_, keyExists := elemAttrs[linux.NFTA_SET_ELEM_KEY]
if flag&linux.NFT_SET_ELEM_CATCHALL != 0 {
if keyExists {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set element key attribute should not be present for catchall element")
}
if s.catchAllElem == nil {
return syserr.NewAnnotatedError(syserr.ErrNoFileOrDir, "catchall element does not exist")
}
s.catchAllElem.destroy()
s.catchAllElem = nil
return nil
}
if !keyExists {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set element key attribute is missing")
}
startKey, endKey, err := parseSetElemKeys(elemAttrs, int(s.keyLen))
if err != nil {
return err
}
return s.removeElement(startKey, endKey)
}
// parseAttrAndRemoveElements removes a list of elements from a set.
func (s *nftSet) parseAttrAndRemoveElements(attr nlmsg.AttrsView, tab *Table, msgFlags uint16, msgType linux.NfTableMsgType) *syserr.AnnotatedError {
for len(attr) > 0 {
_, elem, rest, ok := attr.ParseFirst()
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse set elem list attributes")
}
attr = rest
elemAttrs, err := NfParseWithOpts(nlmsg.AttrsView(elem), &NfParseOpts{
Policy: setElemPolicy,
})
if err != nil {
return err
}
if err := s.parseAttrAndRemoveElem(elemAttrs, msgFlags); err != nil {
if err.GetError() == syserr.ErrNoFileOrDir && msgType == linux.NFT_MSG_DESTROYSETELEM {
continue
}
return err
}
}
return nil
}
// NewSetElements handles NFT_MSG_NEWSETELEM.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_newsetelem()
func (nf *NFTables) NewSetElements(atr nlmsg.AttrsView, family stack.AddressFamily, flags uint16, ms *nlmsg.MessageSet) *syserr.AnnotatedError {
attrs, err := NfParseWithOpts(atr, &NfParseOpts{Policy: setElemListPolicy})
if err != nil {
return err
}
elemListAttr, ok := attrs[linux.NFTA_SET_ELEM_LIST_ELEMENTS]
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set elem list elements attribute is missing")
}
tabNameBytes, ok := attrs[linux.NFTA_SET_ELEM_LIST_TABLE]
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set elem list table attribute is missing")
}
tabName := tabNameBytes.String()
tab, err := nf.GetTable(family, tabName, uint32(ms.PortID))
if err != nil {
return err
}
setNameBytes, ok := attrs[linux.NFTA_SET_ELEM_LIST_SET]
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set elem list set attribute is missing")
}
setName := setNameBytes.String()
var set *nftSet
if set, ok = tab.sets[setName]; !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set does not exist")
}
if len(set.bindings) != 0 && (set.flags&linux.NFT_SET_CONSTANT != 0 || set.flags&linux.NFT_SET_ANONYMOUS != 0) {
return syserr.NewAnnotatedError(syserr.ErrBusy, "set is constant or anonymous and cannot be modified")
}
if err := set.parseAttrAndAddElements(tab, nlmsg.AttrsView(elemListAttr), flags); err != nil {
return err
}
return nil
}
// DeleteSetElements handles NFT_MSG_DELSETELEM and NFT_MSG_DESTROYSETELEM.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_delsetelem()
func (nf *NFTables) DeleteSetElements(atr nlmsg.AttrsView, family stack.AddressFamily, flags uint16, msgType linux.NfTableMsgType, ms *nlmsg.MessageSet) *syserr.AnnotatedError {
// Use NfParseWithOpts to parse and validate attributes in one go.
attrs, err := NfParseWithOpts(atr, &NfParseOpts{Policy: setElemListPolicy})
if err != nil {
return err
}
// Get the table name from the attributes.
tabNameBytes, ok := attrs[linux.NFTA_SET_ELEM_LIST_TABLE]
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "attribute NFTA_SET_ELEM_LIST_TABLE is missing")
}
tabName := tabNameBytes.String()
// Get the set name from the attributes.
setNameBytes, ok := attrs[linux.NFTA_SET_ELEM_LIST_SET]
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "attribute NFTA_SET_ELEM_LIST_SET is missing")
}
setName := setNameBytes.String()
// Find the table in the NFTables object.
tab, err := nf.GetTable(family, tabName, uint32(ms.PortID))
if err != nil {
return err
}
// Find the set in the table.
set, ok := tab.sets[setName]
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set does not exist")
}
// Anonymous set modification is not allowed.
if set.flags&linux.NFT_SET_ANONYMOUS != 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, "anonymous set modification is not supported")
}
// Constant set modification is not allowed if bound.
if len(set.bindings) != 0 && set.flags&linux.NFT_SET_CONSTANT != 0 {
return syserr.NewAnnotatedError(syserr.ErrBusy, "set is constant and bound, cannot be modified")
}
elemListAttr, ok := attrs[linux.NFTA_SET_ELEM_LIST_ELEMENTS]
// Remove all elements from the set if the element list attribute is not
// present.
if !ok {
return set.removeAllElements()
}
if err := set.parseAttrAndRemoveElements(nlmsg.AttrsView(elemListAttr), tab, flags, msgType); err != nil {
return err
}
return nil
}
func (nf *NFTables) dumpSetDescInfo(set *nftSet, tab *Table, family stack.AddressFamily, ms *nlmsg.MessageSet) ([]byte, *syserr.AnnotatedError) {
m := &nlmsg.Message{}
if set.descSize != 0 {
m.PutAttr(linux.NFTA_SET_DESC_SIZE, nlmsg.PutU32(set.descSize))
}
if set.fieldCount > 1 {
var concatList nlmsg.NestedAttr
for _, concat := range set.fieldLen {
var concatAttrs nlmsg.NestedAttr
concatAttrs.PutAttr(linux.NFTA_SET_FIELD_LEN, primitive.AsByteSlice([]byte{concat}))
concatList.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(concatAttrs))
}
m.PutNestedAttr(linux.NFTA_SET_DESC_CONCAT, concatList)
}
return m.Buffer(), nil
}
func (nf *NFTables) fillSetInfo(set *nftSet, tab *Table, family stack.AddressFamily, ms *nlmsg.MessageSet) *syserr.AnnotatedError {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: uint16(linux.NFNL_SUBSYS_NFTABLES)<<8 | uint16(linux.NFT_MSG_NEWSET),
})
m.Put(&linux.NetFilterGenMsg{
Family: uint8(AfProtocol(tab.GetAddressFamily())),
Version: uint8(linux.NFNETLINK_V0),
// Unused, set to 0.
ResourceID: uint16(0),
})
m.PutAttrString(linux.NFTA_SET_TABLE, tab.GetName())
m.PutAttrString(linux.NFTA_SET_NAME, set.name)
m.PutAttr(linux.NFTA_SET_HANDLE, nlmsg.PutU64(set.handle))
if set.flags != 0 {
m.PutAttr(linux.NFTA_SET_FLAGS, nlmsg.PutU32(uint32(set.flags)))
}
m.PutAttr(linux.NFTA_SET_KEY_TYPE, nlmsg.PutU32(set.keyType))
m.PutAttr(linux.NFTA_SET_KEY_LEN, nlmsg.PutU32(uint32(set.keyLen)))
if set.flags&linux.NFT_SET_MAP != 0 {
m.PutAttr(linux.NFTA_SET_DATA_TYPE, nlmsg.PutU32(set.dataType))
m.PutAttr(linux.NFTA_SET_DATA_LEN, nlmsg.PutU32(uint32(set.dataLen)))
}
if set.flags&linux.NFT_SET_OBJECT != 0 {
m.PutAttr(linux.NFTA_SET_OBJ_TYPE, nlmsg.PutU32(set.objType))
}
if set.timeout != 0 {
m.PutAttr(linux.NFTA_SET_TIMEOUT, nlmsg.PutU64(set.timeout))
}
if set.gcInterval != 0 {
m.PutAttr(linux.NFTA_SET_GC_INTERVAL, nlmsg.PutU32(set.gcInterval))
}
if set.policy != linux.NFT_SET_POL_PERFORMANCE {
m.PutAttr(linux.NFTA_SET_POLICY, nlmsg.PutU32(set.policy))
}
if set.udata != nil {
m.PutAttr(linux.NFTA_SET_USERDATA, primitive.AsByteSlice(set.udata))
}
descAttr, err := nf.dumpSetDescInfo(set, tab, family, ms)
if err != nil {
return err
}
if len(descAttr) > 0 {
m.PutNestedAttr(linux.NFTA_SET_DESC, descAttr)
}
if len(set.exprInfos) == 1 {
var exprAttr nlmsg.NestedAttr
exprAttr.PutAttrString(linux.NFTA_EXPR_NAME, set.exprInfos[0].ExprName)
if len(set.exprInfos[0].ExprData) > 0 {
exprAttr.PutAttr(linux.NFTA_EXPR_DATA, primitive.AsByteSlice(set.exprInfos[0].ExprData))
}
m.PutNestedAttr(linux.NFTA_SET_EXPR, exprAttr)
} else if len(set.exprInfos) > 1 {
var exprList nlmsg.NestedAttr
for _, info := range set.exprInfos {
var exprAttr nlmsg.NestedAttr
exprAttr.PutAttrString(linux.NFTA_EXPR_NAME, info.ExprName)
if len(info.ExprData) > 0 {
exprAttr.PutAttr(linux.NFTA_EXPR_DATA, primitive.AsByteSlice(info.ExprData))
}
exprList.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(exprAttr))
}
m.PutNestedAttr(linux.NFTA_SET_EXPRESSIONS, exprList)
}
return nil
}
func (nf *NFTables) dumpSets(family stack.AddressFamily, ms *nlmsg.MessageSet) *syserr.AnnotatedError {
for f := range stack.NumAFs {
if family != stack.Unspec && f != family {
continue
}
for _, tab := range nf.GetAddressFamilyTables(f) {
for _, set := range tab.sets {
if err := nf.fillSetInfo(set, tab, f, ms); err != nil {
return err
}
}
}
}
return nil
}
// GetSet handles dumping sets.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_getset()
func (nf *NFTables) GetSet(attrs map[uint16]nlmsg.BytesView, family stack.AddressFamily, flags uint16, ms *nlmsg.MessageSet) *syserr.AnnotatedError {
if (flags & linux.NLM_F_DUMP) != 0 {
ms.Multi = true
return nf.dumpSets(family, ms)
}
tableNameBytes, ok := attrs[linux.NFTA_SET_TABLE]
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set table attribute is missing")
}
tableName := tableNameBytes.String()
table, err := nf.GetTable(family, tableName, uint32(ms.PortID))
if err != nil {
return err
}
setNameBytes, ok := attrs[linux.NFTA_SET_NAME]
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set name attribute is missing")
}
setName := setNameBytes.String()
set, ok := table.sets[setName]
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set does not exist")
}
return nf.fillSetInfo(set, table, family, ms)
}
func dumpSetElem(set *nftSet, elem *nftSetElem, isCatchAll bool) (nlmsg.NestedAttr, *syserr.AnnotatedError) {
var elemAttrs nlmsg.NestedAttr
if !isCatchAll {
if len(elem.startKey) > 0 {
b, err := dumpDataAttr(elem.startKey)
if err != nil {
return nil, err
}
elemAttrs.PutAttr(linux.NFTA_SET_ELEM_KEY, primitive.AsByteSlice(b))
}
if len(elem.endKey) > 0 {
b, err := dumpDataAttr(elem.endKey)
if err != nil {
return nil, err
}
elemAttrs.PutAttr(linux.NFTA_SET_ELEM_KEY_END, primitive.AsByteSlice(b))
}
} else {
elemAttrs.PutAttr(linux.NFTA_SET_ELEM_FLAGS, nlmsg.PutU32(uint32(linux.NFT_SET_ELEM_CATCHALL)))
}
if (set.flags & linux.NFT_SET_MAP) != 0 {
var dataDump []byte
var err *syserr.AnnotatedError
if elem.data.isVerdict {
dataDump, err = dumpVerdictDataAttr(elem.data.verdict)
} else if len(elem.data.data) > 0 {
dataDump, err = dumpDataAttr(elem.data.data)
}
if err != nil {
return nil, err
}
if dataDump != nil {
elemAttrs.PutAttr(linux.NFTA_SET_ELEM_DATA, primitive.AsByteSlice(dataDump))
}
}
if elem.timeout != 0 {
elemAttrs.PutAttr(linux.NFTA_SET_ELEM_TIMEOUT, nlmsg.PutU64(elem.timeout))
}
if elem.expiration != 0 {
elemAttrs.PutAttr(linux.NFTA_SET_ELEM_EXPIRATION, nlmsg.PutU64(elem.expiration))
}
if len(elem.userData) > 0 {
elemAttrs.PutAttr(linux.NFTA_SET_ELEM_USERDATA, primitive.AsByteSlice(elem.userData))
}
if len(elem.ops) > 0 {
if len(elem.ops) == 1 {
opDump, err := elem.ops[0].Dump()
if err != nil {
return nil, err
}
if opDump != nil {
elemAttrs.PutAttr(linux.NFTA_SET_ELEM_EXPR, primitive.AsByteSlice(opDump))
}
} else {
var exprList nlmsg.NestedAttr
for _, op := range elem.ops {
opDump, err := op.Dump()
if err != nil {
return nil, err
}
if opDump != nil {
exprList.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(opDump))
}
}
elemAttrs.PutAttr(linux.NFTA_SET_ELEM_EXPRESSIONS, primitive.AsByteSlice(exprList))
}
}
return elemAttrs, nil
}
// fillSetElemListInfo populates the message set with information about all
// elements in a given set.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_fill_setelem
func (nf *NFTables) fillSetElemListInfo(set *nftSet, tab *Table, family stack.AddressFamily, ms *nlmsg.MessageSet) *syserr.AnnotatedError {
var elemAttrList nlmsg.NestedAttr
flushElems := func() {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: uint16(linux.NFNL_SUBSYS_NFTABLES)<<8 | uint16(linux.NFT_MSG_NEWSETELEM),
Flags: linux.NLM_F_MULTI,
})
m.Put(&linux.NetFilterGenMsg{
Family: uint8(AfProtocol(tab.GetAddressFamily())),
Version: uint8(linux.NFNETLINK_V0),
ResourceID: uint16(0),
})
m.PutAttrString(linux.NFTA_SET_ELEM_LIST_TABLE, tab.GetName())
m.PutAttrString(linux.NFTA_SET_ELEM_LIST_SET, set.name)
m.PutNestedAttr(linux.NFTA_SET_ELEM_LIST_ELEMENTS, elemAttrList)
elemAttrList = nil
}
for i := range set.elements {
elemAttrs, err := dumpSetElem(set, &set.elements[i], false)
if err != nil {
return err
}
// A Netlink attribute header is 4 bytes. The total nested attribute size
// cannot exceed math.MaxUint16.
elemSize := linux.NetlinkAttrHeaderSize + len(elemAttrs)
if len(elemAttrList) > 0 && len(elemAttrList)+elemSize > math.MaxUint16 {
flushElems()
}
elemAttrList.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(elemAttrs))
}
if set.catchAllElem != nil {
elemAttrs, err := dumpSetElem(set, set.catchAllElem, true)
if err != nil {
return err
}
elemSize := linux.NetlinkAttrHeaderSize + len(elemAttrs)
if len(elemAttrList) > 0 && len(elemAttrList)+elemSize > math.MaxUint16 {
flushElems()
}
elemAttrList.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(elemAttrs))
}
if len(elemAttrList) > 0 {
flushElems()
}
return nil
}
// getSetElem fills the message set with the requested set element.
// Ref: net/netfilter/nf_tables_api.c:nft_get_set_elem
func (nf *NFTables) getSetElem(set *nftSet, table *Table, family stack.AddressFamily, ms *nlmsg.MessageSet, elementsMap map[uint16]nlmsg.BytesView) *syserr.AnnotatedError {
var foundElem *nftSetElem
for _, elemViewBytes := range elementsMap {
elemMap, ok := NfParse(nlmsg.AttrsView(elemViewBytes))
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse inner element contents")
}
// Parse Catchall
elemFlagsVal, flagExists := AttrNetToHost[uint32](linux.NFTA_SET_ELEM_FLAGS, elemMap)
if flagExists && (uint16(elemFlagsVal)&linux.NFT_SET_ELEM_CATCHALL != 0) {
if set.catchAllElem == nil {
return syserr.NewAnnotatedError(syserr.ErrNoFileOrDir, "catchall element does not exist in set")
}
foundElem = set.catchAllElem
break
}
keyAttr, keyOk := elemMap[linux.NFTA_SET_ELEM_KEY]
if !keyOk {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "missing key to lookup in element")
}
keyMap, ok := NfParse(nlmsg.AttrsView(keyAttr))
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse key data attributes for lookup")
}
keyData, dataOk := keyMap[linux.NFTA_DATA_VALUE]
if !dataOk {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "missing data value in key for lookup")
}
// Execute lookup and locate matching element.
idx := set.backend.Find(keyData)
if idx == -1 {
return syserr.NewAnnotatedError(syserr.ErrNoFileOrDir, "element does not exist in set")
}
foundElem = &set.elements[idx]
break
}
if foundElem == nil {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "no valid element provided in lookup payload")
}
elemAttrs, err := dumpSetElem(set, foundElem, foundElem == set.catchAllElem)
if err != nil {
return err
}
var elemAttrList nlmsg.NestedAttr
elemAttrList.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(elemAttrs))
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: uint16(linux.NFNL_SUBSYS_NFTABLES)<<8 | uint16(linux.NFT_MSG_NEWSETELEM),
Flags: 0,
})
m.Put(&linux.NetFilterGenMsg{
Family: uint8(AfProtocol(table.GetAddressFamily())),
Version: uint8(linux.NFNETLINK_V0),
ResourceID: uint16(0),
})
m.PutAttrString(linux.NFTA_SET_ELEM_LIST_TABLE, table.GetName())
m.PutAttrString(linux.NFTA_SET_ELEM_LIST_SET, set.name)
m.PutNestedAttr(linux.NFTA_SET_ELEM_LIST_ELEMENTS, elemAttrList)
return nil
}
// GetSetElements handles dumping set elements.
// Ref: net/netfilter/nf_tables_api.c:nf_tables_getsetelem
func (nf *NFTables) GetSetElements(attrs map[uint16]nlmsg.BytesView, family stack.AddressFamily, flags uint16, ms *nlmsg.MessageSet) *syserr.AnnotatedError {
tableNameBytes, tabOk := attrs[linux.NFTA_SET_ELEM_LIST_TABLE]
if !tabOk {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set elem list table attribute is missing")
}
tableName := tableNameBytes.String()
table, err := nf.GetTable(family, tableName, uint32(ms.PortID))
if err != nil {
return err
}
setNameBytes, setOk := attrs[linux.NFTA_SET_ELEM_LIST_SET]
if !setOk {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set elem list set attribute is missing")
}
setName := setNameBytes.String()
set, ok := table.sets[setName]
if !ok {
return syserr.NewAnnotatedError(syserr.ErrNoFileOrDir, "set does not exist")
}
if (flags & linux.NLM_F_DUMP) != 0 {
ms.Multi = true
return nf.fillSetElemListInfo(set, table, family, ms)
}
// This is a direct lookup (`nf_tables_getsetelem`).
// We must parse the requested element block to find its key.
elementsAttr, elementsOk := attrs[linux.NFTA_SET_ELEM_LIST_ELEMENTS]
if !elementsOk {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "set elem list elements attribute is missing for lookup")
}
elementsMap, ok := NfParse(nlmsg.AttrsView(elementsAttr))
if !ok {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse elements wrapper for lookup")
}
return nf.getSetElem(set, table, family, ms, elementsMap)
}
// Add set backend implementations below.
// setMapBackend is a backend to handle map operations.
type setMapBackend struct {
keyLen int
dataLen int
m map[string]int
}
// Lookup implements NftSetBackend.Lookup.
// The key-register length access should be validated during the set creation.
func (s *setMapBackend) Evaluate(regs *registerSet, keyIdx int) int {
v, ok := s.m[string(regs.data[keyIdx:keyIdx+s.keyLen])]
if !ok {
return -1
}
return v
}
// Find implements NftSetBackend.Find.
func (s *setMapBackend) Find(keyData []byte) int {
if len(keyData) != s.keyLen {
return -1
}
v, ok := s.m[string(keyData)]
if !ok {
return -1
}
return v
}
// Add implements NftSetBackend.Add.
func (s *setMapBackend) Add(e *nftSetElem, idx int) (int, *syserr.AnnotatedError) {
if e == nil {
return -1, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "element is nil")
}
if len(e.startKey) != s.keyLen {
return -1, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "key length does not match set key length")
}
key := string(e.startKey)
if v, ok := s.m[key]; ok {
return v, nil
}
s.m[key] = idx
return idx, nil
}
// Remove implements NftSetBackend.Remove.
func (s *setMapBackend) Remove(e *nftSetElem) (int, *syserr.AnnotatedError) {
if e == nil {
return -1, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "element is nil")
}
key := string(e.startKey)
if v, ok := s.m[key]; ok {
delete(s.m, key)
return v, nil
}
return -1, syserr.NewAnnotatedError(syserr.ErrNoFileOrDir, "key not found")
}
// Update implements NftSetBackend.Update.
func (s *setMapBackend) Update(e *nftSetElem, idx int) *syserr.AnnotatedError {
if e == nil {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "element is nil")
}
key := string(e.startKey)
if _, ok := s.m[key]; !ok {
return syserr.NewAnnotatedError(syserr.ErrNoFileOrDir, "key not found")
}
s.m[key] = idx
return nil
}
// RemoveAll implements NftSetBackend.RemoveAll.
func (s *setMapBackend) RemoveAll() *syserr.AnnotatedError {
s.m = make(map[string]int)
return nil
}
// Clone implements NftSetBackend.Clone.
func (s *setMapBackend) Clone() NftSetBackend {
mCopy := make(map[string]int, len(s.m))
for k, v := range s.m {
mCopy[k] = v
}
return &setMapBackend{
keyLen: s.keyLen,
dataLen: s.dataLen,
m: mCopy,
}
}
// newSetMapBackend creates a new map backend.
func (nf *NFTables) newSetMapBackend(keyLen int, dataLen int) *setMapBackend {
return &setMapBackend{
keyLen: keyLen,
dataLen: dataLen,
m: make(map[string]int),
}
}