From 708c511cbfe5d0861cb5ca174e97e7d54bc49c36 Mon Sep 17 00:00:00 2001 From: Parth Sarthi Date: Mon, 8 Dec 2025 02:26:33 -0800 Subject: [PATCH] nftables: Added support for cmp op init. PiperOrigin-RevId: 841668653 --- pkg/abi/linux/nf_tables.go | 22 +++ pkg/tcpip/nftables/BUILD | 1 + pkg/tcpip/nftables/nft_comparison.go | 35 ++++ pkg/tcpip/nftables/nft_meta.go | 129 +++++++++++++++ pkg/tcpip/nftables/nft_metaload.go | 101 ++---------- pkg/tcpip/nftables/nft_metaset.go | 46 ++++-- pkg/tcpip/nftables/nftables.go | 9 + .../linux/socket_netlink_netfilter.cc | 156 ++++++++++++++---- 8 files changed, 365 insertions(+), 134 deletions(-) create mode 100644 pkg/tcpip/nftables/nft_meta.go diff --git a/pkg/abi/linux/nf_tables.go b/pkg/abi/linux/nf_tables.go index 4df091fd80..18a3c585bb 100644 --- a/pkg/abi/linux/nf_tables.go +++ b/pkg/abi/linux/nf_tables.go @@ -364,6 +364,17 @@ const ( NFT_CMP_GTE // greater than or equal to ) +// Nf table cmp expression netlink attributes. +// These correspond to enum values in include/uapi/linux/netfilter/nf_tables.h. +const ( + NFTA_CMP_UNSPEC uint16 = iota + NFTA_CMP_SREG + NFTA_CMP_OP + NFTA_CMP_DATA + __NFTA_CMP_MAX + NFTA_CMP_MAX = __NFTA_CMP_MAX - 1 +) + // Nf table range operators. // Used by the nft range operation to compare values in registers. // These correspond to enum values in include/uapi/linux/netfilter/nf_tables.h. @@ -479,6 +490,17 @@ const ( NFT_META_BRI_BROUTE // Packet br_netfilter_broute bit ) +// Nf table meta expression netlink attributes +// These correspond to enum values in include/uapi/linux/netfilter/nf_tables.h. +const ( + NFTA_META_UNSPEC = iota + NFTA_META_DREG + NFTA_META_KEY + NFTA_META_SREG + __NFTA_META_MAX + NFTA_META_MAX = __NFTA_META_MAX - 1 +) + // Nftables Generation Attributes // These correspond to values in include/uapi/linux/netfilter/nf_tables.h. const ( diff --git a/pkg/tcpip/nftables/BUILD b/pkg/tcpip/nftables/BUILD index a7c4ea88a3..3d702a88c3 100644 --- a/pkg/tcpip/nftables/BUILD +++ b/pkg/tcpip/nftables/BUILD @@ -22,6 +22,7 @@ go_library( "nft_counter.go", "nft_immediate.go", "nft_last.go", + "nft_meta.go", "nft_metaload.go", "nft_metaset.go", "nft_payload.go", diff --git a/pkg/tcpip/nftables/nft_comparison.go b/pkg/tcpip/nftables/nft_comparison.go index f61765b77f..22f8e38c8c 100644 --- a/pkg/tcpip/nftables/nft_comparison.go +++ b/pkg/tcpip/nftables/nft_comparison.go @@ -19,6 +19,7 @@ import ( "fmt" "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" ) @@ -115,3 +116,37 @@ func (op comparison) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule * regs.verdict = stack.NFVerdict{Code: VC(linux.NFT_BREAK)} } } + +var cmpAttrPolicy = []NlaPolicy{ + linux.NFTA_CMP_SREG: NlaPolicy{nlaType: linux.NLA_U32}, + linux.NFTA_CMP_OP: NlaPolicy{nlaType: linux.NLA_U32}, + linux.NFTA_CMP_DATA: NlaPolicy{nlaType: linux.NLA_NESTED}, +} + +func initComparison(tab *Table, exprInfo ExprInfo) (*comparison, *syserr.AnnotatedError) { + attrs, ok := NfParseWithPolicy(exprInfo.ExprData, cmpAttrPolicy) + if !ok { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse comparison expression data") + } + sreg, ok := AttrNetToHostU32(linux.NFTA_CMP_SREG, attrs) + if !ok { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse NFTA_CMP_SREG attribute") + } + op, ok := AttrNetToHostU32(linux.NFTA_CMP_OP, attrs) + if !ok { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse NFTA_CMP_OP attribute") + } + dataAttrBytes, ok := attrs[linux.NFTA_CMP_DATA] + if !ok { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: NFTA_CMP_DATA attribute is not found") + } + dataAttrs, ok := NfParse(nlmsg.AttrsView(dataAttrBytes)) + if !ok { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse NFTA_CMP_DATA attribute") + } + valueBytes, ok := dataAttrs[linux.NFTA_DATA_VALUE] + if !ok { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: NFTA_DATA_VALUE attribute is not found") + } + return newComparison(uint8(sreg), int(op), valueBytes) +} diff --git a/pkg/tcpip/nftables/nft_meta.go b/pkg/tcpip/nftables/nft_meta.go new file mode 100644 index 0000000000..19d17feea4 --- /dev/null +++ b/pkg/tcpip/nftables/nft_meta.go @@ -0,0 +1,129 @@ +// Copyright 2025 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 ( + "fmt" + + "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/syserr" +) + +// metaKey is the key that determines the specific meta data to retrieve. +// Note: corresponds to enum nft_meta_keys from +// include/uapi/linux/netfilter/nf_tables.h and uses the same constants. +type metaKey int + +// metaKeyStrings is a map of meta key to its string representation. +var metaKeyStrings = map[metaKey]string{ + linux.NFT_META_LEN: "NFT_META_LEN", + linux.NFT_META_PROTOCOL: "NFT_META_PROTOCOL", + linux.NFT_META_PRIORITY: "NFT_META_PRIORITY", + linux.NFT_META_MARK: "NFT_META_MARK", + linux.NFT_META_IIF: "NFT_META_IIF", + linux.NFT_META_OIF: "NFT_META_OIF", + linux.NFT_META_IIFNAME: "NFT_META_IIFNAME", + linux.NFT_META_OIFNAME: "NFT_META_OIFNAME", + linux.NFT_META_IIFTYPE: "NFT_META_IIFTYPE", + linux.NFT_META_OIFTYPE: "NFT_META_OIFTYPE", + linux.NFT_META_SKUID: "NFT_META_SKUID", + linux.NFT_META_SKGID: "NFT_META_SKGID", + linux.NFT_META_NFTRACE: "NFT_META_NFTRACE", + linux.NFT_META_RTCLASSID: "NFT_META_RTCLASSID", + linux.NFT_META_SECMARK: "NFT_META_SECMARK", + linux.NFT_META_NFPROTO: "NFT_META_NFPROTO", + linux.NFT_META_L4PROTO: "NFT_META_L4PROTO", + linux.NFT_META_BRI_IIFNAME: "NFT_META_BRI_IIFNAME", + linux.NFT_META_BRI_OIFNAME: "NFT_META_BRI_OIFNAME", + linux.NFT_META_PKTTYPE: "NFT_META_PKTTYPE", + linux.NFT_META_CPU: "NFT_META_CPU", + linux.NFT_META_IIFGROUP: "NFT_META_IIFGROUP", + linux.NFT_META_OIFGROUP: "NFT_META_OIFGROUP", + linux.NFT_META_CGROUP: "NFT_META_CGROUP", + linux.NFT_META_PRANDOM: "NFT_META_PRANDOM", + linux.NFT_META_SECPATH: "NFT_META_SECPATH", + linux.NFT_META_IIFKIND: "NFT_META_IIFKIND", + linux.NFT_META_OIFKIND: "NFT_META_OIFKIND", + linux.NFT_META_BRI_IIFPVID: "NFT_META_BRI_IIFPVID", + linux.NFT_META_BRI_IIFVPROTO: "NFT_META_BRI_IIFVPROTO", + linux.NFT_META_TIME_NS: "NFT_META_TIME_NS", + linux.NFT_META_TIME_DAY: "NFT_META_TIME_DAY", + linux.NFT_META_TIME_HOUR: "NFT_META_TIME_HOUR", + linux.NFT_META_SDIF: "NFT_META_SDIF", + linux.NFT_META_SDIFNAME: "NFT_META_SDIFNAME", + linux.NFT_META_BRI_BROUTE: "NFT_META_BRI_BROUTE", +} + +// String for metaKey returns the string representation of the meta key. This +// supports strings for supported and unsupported meta keys. +func (key metaKey) String() string { + if keyStr, ok := metaKeyStrings[key]; ok { + return keyStr + } + panic(fmt.Sprintf("invalid meta key: %d", int(key))) +} + +// metaDataLengths holds the length in bytes for each supported meta key. +var metaDataLengths = map[metaKey]int{ + linux.NFT_META_LEN: 4, + linux.NFT_META_PROTOCOL: 2, + linux.NFT_META_NFPROTO: 1, + linux.NFT_META_L4PROTO: 1, + linux.NFT_META_SKUID: 4, + linux.NFT_META_SKGID: 4, + linux.NFT_META_RTCLASSID: 4, + linux.NFT_META_PKTTYPE: 1, + linux.NFT_META_PRANDOM: 4, + linux.NFT_META_TIME_NS: 8, + linux.NFT_META_TIME_DAY: 1, + linux.NFT_META_TIME_HOUR: 4, +} + +// validateMetaKey ensures the meta key is valid. +func validateMetaKey(key metaKey) *syserr.AnnotatedError { + switch key { + case linux.NFT_META_LEN, linux.NFT_META_PROTOCOL, linux.NFT_META_NFPROTO, + linux.NFT_META_L4PROTO, linux.NFT_META_SKUID, linux.NFT_META_SKGID, + linux.NFT_META_RTCLASSID, linux.NFT_META_PKTTYPE, linux.NFT_META_PRANDOM, + linux.NFT_META_TIME_NS, linux.NFT_META_TIME_DAY, linux.NFT_META_TIME_HOUR: + + return nil + default: + return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("meta key %v is not supported", key)) + } +} + +var metaAttrPolicy = []NlaPolicy{ + linux.NFTA_META_DREG: NlaPolicy{nlaType: linux.NLA_U32}, + linux.NFTA_META_KEY: NlaPolicy{nlaType: linux.NLA_BE32, validator: AttrMaxValidator[uint32](255)}, + linux.NFTA_META_SREG: NlaPolicy{nlaType: linux.NLA_U32}, +} + +func initMeta(tab *Table, exprInfo ExprInfo) (operation, *syserr.AnnotatedError) { + attrs, ok := NfParseWithPolicy(exprInfo.ExprData, metaAttrPolicy) + if !ok { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse meta expression data") + } + if _, ok := attrs[linux.NFTA_META_SREG]; ok { + if _, ok := attrs[linux.NFTA_META_DREG]; ok { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Only one of NFTA_PAYLOAD_SREG and NFTA_PAYLOAD_DREG should be set") + } + return initMetaSet(attrs) + } + if _, ok := attrs[linux.NFTA_META_DREG]; ok { + return initMetaLoad(attrs) + } + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: NFTA_PAYLOAD_SREG or NFTA_PAYLOAD_DREG attribute is not found") +} diff --git a/pkg/tcpip/nftables/nft_metaload.go b/pkg/tcpip/nftables/nft_metaload.go index d847a9579c..8617903bbe 100644 --- a/pkg/tcpip/nftables/nft_metaload.go +++ b/pkg/tcpip/nftables/nft_metaload.go @@ -19,6 +19,7 @@ import ( "fmt" "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg" "gvisor.dev/gvisor/pkg/syserr" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/stack" @@ -41,90 +42,6 @@ type metaLoad struct { // endian for the latter. } -// metaKey is the key that determines the specific meta data to retrieve. -// Note: corresponds to enum nft_meta_keys from -// include/uapi/linux/netfilter/nf_tables.h and uses the same constants. -type metaKey int - -// metaKeyStrings is a map of meta key to its string representation. -var metaKeyStrings = map[metaKey]string{ - linux.NFT_META_LEN: "NFT_META_LEN", - linux.NFT_META_PROTOCOL: "NFT_META_PROTOCOL", - linux.NFT_META_PRIORITY: "NFT_META_PRIORITY", - linux.NFT_META_MARK: "NFT_META_MARK", - linux.NFT_META_IIF: "NFT_META_IIF", - linux.NFT_META_OIF: "NFT_META_OIF", - linux.NFT_META_IIFNAME: "NFT_META_IIFNAME", - linux.NFT_META_OIFNAME: "NFT_META_OIFNAME", - linux.NFT_META_IIFTYPE: "NFT_META_IIFTYPE", - linux.NFT_META_OIFTYPE: "NFT_META_OIFTYPE", - linux.NFT_META_SKUID: "NFT_META_SKUID", - linux.NFT_META_SKGID: "NFT_META_SKGID", - linux.NFT_META_NFTRACE: "NFT_META_NFTRACE", - linux.NFT_META_RTCLASSID: "NFT_META_RTCLASSID", - linux.NFT_META_SECMARK: "NFT_META_SECMARK", - linux.NFT_META_NFPROTO: "NFT_META_NFPROTO", - linux.NFT_META_L4PROTO: "NFT_META_L4PROTO", - linux.NFT_META_BRI_IIFNAME: "NFT_META_BRI_IIFNAME", - linux.NFT_META_BRI_OIFNAME: "NFT_META_BRI_OIFNAME", - linux.NFT_META_PKTTYPE: "NFT_META_PKTTYPE", - linux.NFT_META_CPU: "NFT_META_CPU", - linux.NFT_META_IIFGROUP: "NFT_META_IIFGROUP", - linux.NFT_META_OIFGROUP: "NFT_META_OIFGROUP", - linux.NFT_META_CGROUP: "NFT_META_CGROUP", - linux.NFT_META_PRANDOM: "NFT_META_PRANDOM", - linux.NFT_META_SECPATH: "NFT_META_SECPATH", - linux.NFT_META_IIFKIND: "NFT_META_IIFKIND", - linux.NFT_META_OIFKIND: "NFT_META_OIFKIND", - linux.NFT_META_BRI_IIFPVID: "NFT_META_BRI_IIFPVID", - linux.NFT_META_BRI_IIFVPROTO: "NFT_META_BRI_IIFVPROTO", - linux.NFT_META_TIME_NS: "NFT_META_TIME_NS", - linux.NFT_META_TIME_DAY: "NFT_META_TIME_DAY", - linux.NFT_META_TIME_HOUR: "NFT_META_TIME_HOUR", - linux.NFT_META_SDIF: "NFT_META_SDIF", - linux.NFT_META_SDIFNAME: "NFT_META_SDIFNAME", - linux.NFT_META_BRI_BROUTE: "NFT_META_BRI_BROUTE", -} - -// String for metaKey returns the string representation of the meta key. This -// supports strings for supported and unsupported meta keys. -func (key metaKey) String() string { - if keyStr, ok := metaKeyStrings[key]; ok { - return keyStr - } - panic(fmt.Sprintf("invalid meta key: %d", int(key))) -} - -// metaDataLengths holds the length in bytes for each supported meta key. -var metaDataLengths = map[metaKey]int{ - linux.NFT_META_LEN: 4, - linux.NFT_META_PROTOCOL: 2, - linux.NFT_META_NFPROTO: 1, - linux.NFT_META_L4PROTO: 1, - linux.NFT_META_SKUID: 4, - linux.NFT_META_SKGID: 4, - linux.NFT_META_RTCLASSID: 4, - linux.NFT_META_PKTTYPE: 1, - linux.NFT_META_PRANDOM: 4, - linux.NFT_META_TIME_NS: 8, - linux.NFT_META_TIME_DAY: 1, - linux.NFT_META_TIME_HOUR: 4, -} - -// validateMetaKey ensures the meta key is valid. -func validateMetaKey(key metaKey) *syserr.AnnotatedError { - switch key { - case linux.NFT_META_LEN, linux.NFT_META_PROTOCOL, linux.NFT_META_NFPROTO, - linux.NFT_META_L4PROTO, linux.NFT_META_SKUID, linux.NFT_META_SKGID, - linux.NFT_META_RTCLASSID, linux.NFT_META_PKTTYPE, linux.NFT_META_PRANDOM, - linux.NFT_META_TIME_NS, linux.NFT_META_TIME_DAY, linux.NFT_META_TIME_HOUR: - - return nil - default: - return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("meta key %v is not supported", key)) - } -} - // newMetaLoad creates a new metaLoad operation. func newMetaLoad(key metaKey, dreg uint8) (*metaLoad, *syserr.AnnotatedError) { if isVerdictRegister(dreg) { @@ -241,3 +158,19 @@ func (op metaLoad) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Ru // Copies target data into the destination register. copy(dst, target) } + +func initMetaLoad(attrs map[uint16]nlmsg.BytesView) (*metaLoad, *syserr.AnnotatedError) { + key, ok := AttrNetToHostU32(linux.NFTA_META_KEY, attrs) + if !ok { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse NFTA_META_KEY attribute") + } + reg, ok := AttrNetToHostU32(linux.NFTA_META_DREG, attrs) + if !ok { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse NFTA_META_DREG attribute") + } + dreg, err := nftMatchReg(reg) + if err != nil { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("Nftables: Invalid source register: %d", reg)) + } + return newMetaLoad(metaKey(key), uint8(dreg)) +} diff --git a/pkg/tcpip/nftables/nft_metaset.go b/pkg/tcpip/nftables/nft_metaset.go index 33ae55f960..d152ab7de2 100644 --- a/pkg/tcpip/nftables/nft_metaset.go +++ b/pkg/tcpip/nftables/nft_metaset.go @@ -18,6 +18,7 @@ import ( "fmt" "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg" "gvisor.dev/gvisor/pkg/syserr" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/stack" @@ -49,6 +50,21 @@ func checkMetaKeySetCompatible(key metaKey) *syserr.AnnotatedError { } } +// evaluate for metaSet sets specific meta data to the value in the source +// register. +func (op metaSet) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule) { + // Gets the data from the source register. + src := getRegisterBuffer(regs, op.sreg)[:metaDataLengths[op.key]] + + // Sets the meta data of the appropriate field. + switch op.key { + // Only Packet Type is supported for now. + case linux.NFT_META_PKTTYPE: + pkt.PktType = tcpip.PacketType(src[0]) + return + } +} + // newMetaSet creates a new metaSet operation. func newMetaSet(key metaKey, sreg uint8) (*metaSet, *syserr.AnnotatedError) { if isVerdictRegister(sreg) { @@ -63,25 +79,21 @@ func newMetaSet(key metaKey, sreg uint8) (*metaSet, *syserr.AnnotatedError) { if metaDataLengths[key] > 4 && !is16ByteRegister(sreg) { return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("meta load operation cannot use 4-byte register as destination for key %s", key)) } - return &metaSet{key: key, sreg: sreg}, nil } -// evaluate for metaSet sets specific meta data to the value in the source -// register. -func (op metaSet) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule) { - // Gets the data from the source register. - src := getRegisterBuffer(regs, op.sreg)[:metaDataLengths[op.key]] - - // Sets the meta data of the appropriate field. - switch op.key { - // Only Packet Type is supported for now. - case linux.NFT_META_PKTTYPE: - pkt.PktType = tcpip.PacketType(src[0]) - return +func initMetaSet(attrs map[uint16]nlmsg.BytesView) (*metaSet, *syserr.AnnotatedError) { + key, ok := AttrNetToHostU32(linux.NFTA_META_KEY, attrs) + if !ok { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse NFTA_META_KEY attribute") } - - // Breaks if could not set the meta data. - regs.verdict = stack.NFVerdict{Code: VC(linux.NFT_BREAK)} - return + reg, ok := AttrNetToHostU32(linux.NFTA_META_SREG, attrs) + if !ok { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse NFTA_META_SREG attribute") + } + sreg, err := nftMatchReg(reg) + if err != nil { + return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("Nftables: Invalid source register: %d", reg)) + } + return newMetaSet(metaKey(key), uint8(sreg)) } diff --git a/pkg/tcpip/nftables/nftables.go b/pkg/tcpip/nftables/nftables.go index 102504c124..94ab052a5f 100644 --- a/pkg/tcpip/nftables/nftables.go +++ b/pkg/tcpip/nftables/nftables.go @@ -1110,6 +1110,15 @@ func (r *Rule) AddOpFromExprInfo(tab *Table, exprInfo ExprInfo) *syserr.Annotate if op, err = initPayload(tab, exprInfo); err != nil { return err } + case "meta": + if op, err = initMeta(tab, exprInfo); err != nil { + return err + } + case "cmp": + if op, err = initComparison(tab, exprInfo); err != nil { + return err + } + default: return syserr.NewAnnotatedError(syserr.ErrNoFileOrDir, fmt.Sprintf("Nftables: Unknown expression type not found: %s", exprInfo.ExprName)) } diff --git a/test/syscalls/linux/socket_netlink_netfilter.cc b/test/syscalls/linux/socket_netlink_netfilter.cc index 12856342de..25ad5833b1 100644 --- a/test/syscalls/linux/socket_netlink_netfilter.cc +++ b/test/syscalls/linux/socket_netlink_netfilter.cc @@ -3342,29 +3342,31 @@ TEST(NetlinkNetfilterTest, GetGenerationID) { false)); } -struct PayloadRuleTestParams { +struct RuleWithExprTestParams { std::string test_name; - NlNestedAttr payload_attrs; + std::string expr_name; + NlNestedAttr expr_attrs; int expected_error_no; }; -class AddRuleWithPayloadTest - : public ::testing::TestWithParam {}; +class AddRuleWithExprTest + : public ::testing::TestWithParam {}; -TEST_P(AddRuleWithPayloadTest, AddRuleWithPayload) { +TEST_P(AddRuleWithExprTest, AddRuleWithExpr) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); SKIP_IF(!IsRunningOnGvisor()); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_NETFILTER)); - std::vector payload_data = - NlNestedAttr(GetParam().payload_attrs).Build(); + std::vector expr_data = NlNestedAttr(GetParam().expr_attrs).Build(); + ASSERT_FALSE(GetParam().expr_name.empty()); std::vector rule_expr_data = NlNestedAttr() - .StrAttr(NFTA_EXPR_NAME, "payload") - .RawAttr(NFTA_EXPR_DATA, payload_data.data(), payload_data.size()) + .StrAttr(NFTA_EXPR_NAME, GetParam().expr_name) + .RawAttr(NFTA_EXPR_DATA, expr_data.data(), expr_data.size()) .Build(); std::vector list_expr_data = NlListAttr().Add(rule_expr_data).Build(); - const std::string table_name = GetUniqueTestTableName(); + const std::string table_name = + absl::StrCat("table_", GetParam().expr_name, "_", GetParam().test_name); const std::string chain_name = "test_chain"; std::vector add_rule_request_buffer = NlBatchReq() @@ -3397,27 +3399,30 @@ TEST_P(AddRuleWithPayloadTest, AddRuleWithPayload) { } } -std::vector GetPayloadRuleTestParams() { +std::vector GetPayloadRuleTestParams() { return { - PayloadRuleTestParams{ + RuleWithExprTestParams{ .test_name = "LoadValid", - .payload_attrs = + .expr_name = "payload", + .expr_attrs = NlNestedAttr() .U32Attr(NFTA_PAYLOAD_BASE, NFT_PAYLOAD_NETWORK_HEADER) .U32Attr(NFTA_PAYLOAD_OFFSET, 1) .U32Attr(NFTA_PAYLOAD_LEN, 4) .U32Attr(NFTA_PAYLOAD_DREG, NFT_REG_1)}, - PayloadRuleTestParams{ + RuleWithExprTestParams{ .test_name = "SetValid", - .payload_attrs = + .expr_name = "payload", + .expr_attrs = NlNestedAttr() .U32Attr(NFTA_PAYLOAD_BASE, NFT_PAYLOAD_NETWORK_HEADER) .U32Attr(NFTA_PAYLOAD_OFFSET, 1) .U32Attr(NFTA_PAYLOAD_LEN, 4) .U32Attr(NFTA_PAYLOAD_SREG, NFT_REG32_00)}, - PayloadRuleTestParams{ + RuleWithExprTestParams{ .test_name = "SetWithCsumValid", - .payload_attrs = + .expr_name = "payload", + .expr_attrs = NlNestedAttr() .U32Attr(NFTA_PAYLOAD_BASE, NFT_PAYLOAD_NETWORK_HEADER) .U32Attr(NFTA_PAYLOAD_OFFSET, 1) @@ -3425,9 +3430,10 @@ std::vector GetPayloadRuleTestParams() { .U32Attr(NFTA_PAYLOAD_SREG, NFT_REG_1) .U32Attr(NFTA_PAYLOAD_CSUM_TYPE, NFT_PAYLOAD_CSUM_INET) .U32Attr(NFTA_PAYLOAD_CSUM_OFFSET, 1)}, - PayloadRuleTestParams{ + RuleWithExprTestParams{ .test_name = "LoadWithInvalidRegister", - .payload_attrs = + .expr_name = "payload", + .expr_attrs = NlNestedAttr() .U32Attr(NFTA_PAYLOAD_BASE, NFT_PAYLOAD_NETWORK_HEADER) .U32Attr(NFTA_PAYLOAD_OFFSET, 1) @@ -3435,54 +3441,60 @@ std::vector GetPayloadRuleTestParams() { // Verdict register is not supported for payload load. .U32Attr(NFTA_PAYLOAD_SREG, NFT_REG_VERDICT), .expected_error_no = EINVAL}, - PayloadRuleTestParams{ + RuleWithExprTestParams{ .test_name = "LoadWithInvalidOffset", - .payload_attrs = + .expr_name = "payload", + .expr_attrs = NlNestedAttr() .U32Attr(NFTA_PAYLOAD_BASE, NFT_PAYLOAD_NETWORK_HEADER) .U32Attr(NFTA_PAYLOAD_OFFSET, UINT32_MAX) .U32Attr(NFTA_PAYLOAD_LEN, 4) .U32Attr(NFTA_PAYLOAD_SREG, NFT_REG_VERDICT), .expected_error_no = EINVAL}, - PayloadRuleTestParams{ + RuleWithExprTestParams{ .test_name = "LoadWithInvalidLen", - .payload_attrs = + .expr_name = "payload", + .expr_attrs = NlNestedAttr() .U32Attr(NFTA_PAYLOAD_BASE, NFT_PAYLOAD_NETWORK_HEADER) .U32Attr(NFTA_PAYLOAD_OFFSET, 1) .U32Attr(NFTA_PAYLOAD_LEN, NFT_REG_SIZE + 1) .U32Attr(NFTA_PAYLOAD_SREG, NFT_REG_VERDICT), .expected_error_no = EINVAL}, - PayloadRuleTestParams{ + RuleWithExprTestParams{ .test_name = "SetWithInvalidBase", - .payload_attrs = + .expr_name = "payload", + .expr_attrs = NlNestedAttr() .U32Attr(NFTA_PAYLOAD_BASE, /* NFT_PAYLOAD_INNER_HEADER */ 3) .U32Attr(NFTA_PAYLOAD_OFFSET, 1) .U32Attr(NFTA_PAYLOAD_LEN, 4) .U32Attr(NFTA_PAYLOAD_DREG, NFT_REG_1), .expected_error_no = EINVAL}, - PayloadRuleTestParams{ + RuleWithExprTestParams{ .test_name = "SetWithInvalidRegister", - .payload_attrs = + .expr_name = "payload", + .expr_attrs = NlNestedAttr() .U32Attr(NFTA_PAYLOAD_BASE, NFT_PAYLOAD_NETWORK_HEADER) .U32Attr(NFTA_PAYLOAD_OFFSET, 1) .U32Attr(NFTA_PAYLOAD_LEN, 4) .U32Attr(NFTA_PAYLOAD_DREG, NFT_REG_VERDICT), .expected_error_no = EINVAL}, - PayloadRuleTestParams{ + RuleWithExprTestParams{ .test_name = "SetWithInvalidLen", - .payload_attrs = + .expr_name = "payload", + .expr_attrs = NlNestedAttr() .U32Attr(NFTA_PAYLOAD_BASE, NFT_PAYLOAD_NETWORK_HEADER) .U32Attr(NFTA_PAYLOAD_OFFSET, 1) .U32Attr(NFTA_PAYLOAD_LEN, NFT_REG_SIZE + 1) .U32Attr(NFTA_PAYLOAD_SREG, NFT_REG_VERDICT), .expected_error_no = EINVAL}, - PayloadRuleTestParams{ + RuleWithExprTestParams{ .test_name = "WithSregAndDregSet", - .payload_attrs = + .expr_name = "payload", + .expr_attrs = NlNestedAttr() .U32Attr(NFTA_PAYLOAD_BASE, NFT_PAYLOAD_NETWORK_HEADER) .U32Attr(NFTA_PAYLOAD_OFFSET, 1) @@ -3494,10 +3506,88 @@ std::vector GetPayloadRuleTestParams() { } INSTANTIATE_TEST_SUITE_P( - PayloadRuleTest, AddRuleWithPayloadTest, + PayloadRuleTest, AddRuleWithExprTest, /*param_generator=*/::testing::ValuesIn(GetPayloadRuleTestParams()), /*param_name_generator=*/ - [](const ::testing::TestParamInfo& info) { + [](const ::testing::TestParamInfo& info) { + return info.param.test_name; + }); + +std::vector GetMetaRuleTestParams() { + return { + RuleWithExprTestParams{ + .test_name = "GetValid", + .expr_name = "meta", + .expr_attrs = NlNestedAttr() + .U32Attr(NFTA_META_DREG, NFT_REG_1) + .U32Attr(NFTA_META_KEY, NFT_META_PKTTYPE)}, + RuleWithExprTestParams{ + .test_name = "SetValid", + .expr_name = "meta", + .expr_attrs = NlNestedAttr() + .U32Attr(NFTA_META_DREG, NFT_REG_1) + .U32Attr(NFTA_META_KEY, NFT_META_PKTTYPE)}, + RuleWithExprTestParams{ + .test_name = "WithSregAndDregSet", + .expr_name = "meta", + .expr_attrs = NlNestedAttr() + .U32Attr(NFTA_META_DREG, NFT_REG_1) + .U32Attr(NFTA_META_KEY, NFT_META_PKTTYPE) + .U32Attr(NFTA_META_SREG, NFT_REG_2), + .expected_error_no = EINVAL}, + RuleWithExprTestParams{ + .test_name = "WithInvalidKey", + .expr_name = "meta", + .expr_attrs = NlNestedAttr() + .U32Attr(NFTA_META_DREG, NFT_REG_1) + .U32Attr(NFTA_META_KEY, 256), + .expected_error_no = EINVAL}, + }; +} + +INSTANTIATE_TEST_SUITE_P( + MetaRuleTest, AddRuleWithExprTest, + /*param_generator=*/::testing::ValuesIn(GetMetaRuleTestParams()), + /*param_name_generator=*/ + [](const ::testing::TestParamInfo& info) { + return info.param.test_name; + }); + +std::vector GetCmpRuleTestParams() { + return { + RuleWithExprTestParams{ + .test_name = "Valid", + .expr_name = "cmp", + .expr_attrs = + []() { + std::vector data_value = + NlNestedAttr().U32Attr(NFTA_DATA_VALUE, 1).Build(); + return NlNestedAttr() + .U32Attr(NFTA_CMP_SREG, NFT_REG_1) + .U32Attr(NFTA_CMP_OP, NFT_CMP_EQ) + .RawAttr(NFTA_CMP_DATA, data_value.data(), + data_value.size()); + }()}, + RuleWithExprTestParams{ + .test_name = "InvalidCmpOp", + .expr_name = "cmp", + .expr_attrs = []() -> NlNestedAttr { + std::vector data_value = + NlNestedAttr().U32Attr(NFTA_DATA_VALUE, 1).Build(); + return NlNestedAttr() + .U32Attr(NFTA_CMP_SREG, NFT_REG_1) + .U32Attr(NFTA_CMP_OP, 100) + .RawAttr(NFTA_CMP_DATA, data_value.data(), data_value.size()); + }(), + .expected_error_no = EINVAL}, + }; +} + +INSTANTIATE_TEST_SUITE_P( + CmpRuleTest, AddRuleWithExprTest, + /*param_generator=*/::testing::ValuesIn(GetCmpRuleTestParams()), + /*param_name_generator=*/ + [](const ::testing::TestParamInfo& info) { return info.param.test_name; });