mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement Meta Load operation (parsing, interpretation, evaluation, tests).
Currently supports a limited set of meta fields. The supported fields are len, protocol, nfproto, l4proto, skiud, skgid, rtclassid (only for ipv6), pkttype, prandom, time, day, and hour. PiperOrigin-RevId: 674392201
This commit is contained in:
committed by
gVisor bot
parent
6e6960c539
commit
ab0c794f9b
@@ -44,6 +44,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"slices"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -123,6 +124,26 @@ func (f AddressFamily) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// Protocol returns the protocol number for the address family.
|
||||
func (f AddressFamily) Protocol() uint8 {
|
||||
switch f {
|
||||
case IP:
|
||||
return linux.NFPROTO_INET
|
||||
case IP6:
|
||||
return linux.NFPROTO_IPV6
|
||||
case Inet:
|
||||
return linux.NFPROTO_IPV6
|
||||
case Arp:
|
||||
return linux.NFPROTO_ARP
|
||||
case Bridge:
|
||||
return linux.NFPROTO_BRIDGE
|
||||
case Netdev:
|
||||
return linux.NFPROTO_NETDEV
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid address family: %d", int(f)))
|
||||
}
|
||||
}
|
||||
|
||||
// validateAddressFamily ensures the family address is valid (within bounds).
|
||||
func validateAddressFamily(family AddressFamily) error {
|
||||
if family < 0 || family >= NumAFs {
|
||||
@@ -215,6 +236,7 @@ type NFTables struct {
|
||||
filters [NumAFs]*addressFamilyFilter // Filters for each address family.
|
||||
clock tcpip.Clock // Clock for timing evaluations.
|
||||
startTime time.Time // Time NFTables object was created.
|
||||
rng *rand.Rand // Random number generator.
|
||||
}
|
||||
|
||||
// addressFamilyFilter represents the nftables state for a specific address
|
||||
@@ -596,6 +618,7 @@ var (
|
||||
_ operation = (*last)(nil)
|
||||
_ operation = (*route)(nil)
|
||||
_ operation = (*byteorder)(nil)
|
||||
_ operation = (*metaLoad)(nil)
|
||||
)
|
||||
|
||||
// immediate is an operation that sets the data in a register.
|
||||
@@ -604,7 +627,7 @@ type immediate struct {
|
||||
dreg uint8 // Number of the destination register.
|
||||
}
|
||||
|
||||
// newImmediate creates a new Immediate operation.
|
||||
// newImmediate creates a new immediate operation.
|
||||
func newImmediate(dreg uint8, data registerData) (*immediate, error) {
|
||||
if err := data.validateRegister(dreg); err != nil {
|
||||
return nil, err
|
||||
@@ -662,7 +685,7 @@ func validateComparisonOp(cop cmpOp) error {
|
||||
}
|
||||
}
|
||||
|
||||
// newComparison creates a new Comparison operation.
|
||||
// newComparison creates a new comparison operation.
|
||||
func newComparison(sreg uint8, op int, data []byte) (*comparison, error) {
|
||||
if isVerdictRegister(sreg) {
|
||||
return nil, fmt.Errorf("comparison operation cannot use verdict register as source")
|
||||
@@ -756,7 +779,7 @@ func validateRangeOp(rop rngOp) error {
|
||||
}
|
||||
}
|
||||
|
||||
// newRanged creates a new Ranged operation.
|
||||
// newRanged creates a new ranged operation.
|
||||
func newRanged(sreg uint8, op int, low, high []byte) (*ranged, error) {
|
||||
if isVerdictRegister(sreg) {
|
||||
return nil, fmt.Errorf("comparison operation cannot use verdict register as source")
|
||||
@@ -875,7 +898,7 @@ func getPayloadBuffer(pkt *stack.PacketBuffer, base payloadBase) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
// newPayloadLoad creates a new PayloadLoad operation.
|
||||
// newPayloadLoad creates a new payloadLoad operation.
|
||||
func newPayloadLoad(base payloadBase, offset, blen, dreg uint8) (*payloadLoad, error) {
|
||||
if isVerdictRegister(dreg) {
|
||||
return nil, fmt.Errorf("payload load operation cannot use verdict register as destination")
|
||||
@@ -948,7 +971,7 @@ func validateChecksumType(csumType uint8) error {
|
||||
}
|
||||
}
|
||||
|
||||
// newPayloadSet creates a new PayloadSet operation.
|
||||
// newPayloadSet creates a new payloadSet operation.
|
||||
func newPayloadSet(base payloadBase, offset, blen, sreg, csumType, csumOffset, csumFlags uint8) (*payloadSet, error) {
|
||||
if isVerdictRegister(sreg) {
|
||||
return nil, fmt.Errorf("payload set operation cannot use verdict register as destination")
|
||||
@@ -1514,6 +1537,224 @@ func (op byteorder) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *R
|
||||
}
|
||||
}
|
||||
|
||||
// metaLoad is an operation that loads specific meta data into a register.
|
||||
// Note: meta operations are not supported for the verdict register.
|
||||
// TODO(b/345684870): Support retrieving more meta fields for Meta Load.
|
||||
type metaLoad struct {
|
||||
key metaKey // Meta key specifying what data to retrieve.
|
||||
dreg uint8 // Number of the destination register.
|
||||
|
||||
// Note: Similar to route, meta fields are stored AS IS. If the meta data is
|
||||
// a field stored by the kernel (i.e. length), it is stored in host endian. On
|
||||
// the contrary, if the meta data is data from the packet (i.e. protocol), it
|
||||
// is stored in big endian (network order).
|
||||
// The nft binary handles the necessary endian conversions from user input.
|
||||
// For example, if the user wants to check if meta len == 123 vs payload
|
||||
// data == 123, the nft binary passes host endian for the former and big
|
||||
// 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 {
|
||||
keyStr, ok := metaKeyStrings[key]
|
||||
if !ok {
|
||||
return fmt.Sprintf("Unsupported Meta Key: %d", int(key))
|
||||
}
|
||||
return keyStr
|
||||
}
|
||||
|
||||
// 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) error {
|
||||
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 fmt.Errorf("invalid meta key: %d", int(key))
|
||||
}
|
||||
}
|
||||
|
||||
// newMetaLoad creates a new metaLoad operation.
|
||||
func newMetaLoad(key metaKey, dreg uint8) (*metaLoad, error) {
|
||||
if isVerdictRegister(dreg) {
|
||||
return nil, fmt.Errorf("meta load operation cannot use verdict register as destination")
|
||||
}
|
||||
if err := validateMetaKey(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if metaDataLengths[key] > 4 && !is16ByteRegister(dreg) {
|
||||
return nil, fmt.Errorf("meta load operation cannot use 4-byte register as destination for key %s", key)
|
||||
}
|
||||
|
||||
return &metaLoad{key: key, dreg: dreg}, nil
|
||||
}
|
||||
|
||||
// evaluate for MetaLoad loads specific meta data into the destination register.
|
||||
func (op metaLoad) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule) {
|
||||
var target []byte
|
||||
switch op.key {
|
||||
|
||||
// Packet Length, in bytes (32-bit, host order).
|
||||
case linux.NFT_META_LEN:
|
||||
target = binary.NativeEndian.AppendUint32(nil, uint32(pkt.Size()))
|
||||
|
||||
// Network EtherType Protocol (16-bit, network order).
|
||||
case linux.NFT_META_PROTOCOL:
|
||||
// Only valid if network header is present.
|
||||
if pkt.NetworkHeader().View() == nil {
|
||||
break
|
||||
}
|
||||
target = binary.BigEndian.AppendUint16(nil, uint16(pkt.NetworkProtocolNumber))
|
||||
|
||||
// Netfilter (Family) Protocol (8-bit, single byte).
|
||||
case linux.NFT_META_NFPROTO:
|
||||
family := rule.chain.GetAddressFamily()
|
||||
target = []byte{family.Protocol()}
|
||||
|
||||
// L4 Transport Layer Protocol (8-bit, single byte).
|
||||
case linux.NFT_META_L4PROTO:
|
||||
// Only valid if non-zero.
|
||||
if pkt.TransportProtocolNumber == 0 {
|
||||
break
|
||||
}
|
||||
target = []byte{uint8(pkt.TransportProtocolNumber)}
|
||||
|
||||
// Originating Socket UID (32-bit, host order).
|
||||
case linux.NFT_META_SKUID:
|
||||
// Only valid if Owner is set (only set for locally generated packets).
|
||||
if pkt.Owner == nil {
|
||||
break
|
||||
}
|
||||
target = binary.NativeEndian.AppendUint32(nil, pkt.Owner.KUID())
|
||||
|
||||
// Originating Socket GID (32-bit, host order).
|
||||
case linux.NFT_META_SKGID:
|
||||
// Only valid if Owner is set (only set for locally generated packets).
|
||||
if pkt.Owner == nil {
|
||||
break
|
||||
}
|
||||
target = binary.NativeEndian.AppendUint32(nil, pkt.Owner.KGID())
|
||||
|
||||
// Route Traffic Class ID, same as Route equivalent (32-bit, host order).
|
||||
// Currently only implemented for IPv6, but should be for IPv4 as well.
|
||||
case linux.NFT_META_RTCLASSID:
|
||||
if pkt.NetworkProtocolNumber != header.IPv6ProtocolNumber {
|
||||
break
|
||||
}
|
||||
if pkt.NetworkHeader().View() != nil {
|
||||
tcid, _ := pkt.Network().TOS()
|
||||
target = binary.NativeEndian.AppendUint32(nil, uint32(tcid))
|
||||
}
|
||||
|
||||
// Packet Type (8-bit, single byte).
|
||||
case linux.NFT_META_PKTTYPE:
|
||||
target = []byte{uint8(pkt.PktType)}
|
||||
|
||||
// Generated Pseudo-Random Number (32-bit, network order).
|
||||
case linux.NFT_META_PRANDOM:
|
||||
rng := rule.chain.table.afFilter.nftState.rng
|
||||
target = binary.BigEndian.AppendUint32(nil, uint32(rng.Uint32()))
|
||||
|
||||
// Unix Time in Nanoseconds (64-bit, host order).
|
||||
case linux.NFT_META_TIME_NS:
|
||||
clock := rule.chain.table.afFilter.nftState.clock
|
||||
target = binary.NativeEndian.AppendUint64(nil, uint64(clock.Now().UnixNano()))
|
||||
|
||||
// Day of Week (0 = Sunday, 6 = Saturday) (8-bit, single byte).
|
||||
case linux.NFT_META_TIME_DAY:
|
||||
clock := rule.chain.table.afFilter.nftState.clock
|
||||
target = []byte{uint8(clock.Now().Weekday())}
|
||||
|
||||
// Hour of Day, in seconds (seconds since start of day) (32-bit, host order).
|
||||
case linux.NFT_META_TIME_HOUR:
|
||||
clock := rule.chain.table.afFilter.nftState.clock
|
||||
now := clock.Now()
|
||||
secs := now.Hour()*3600 + now.Minute()*60 + now.Second()
|
||||
target = binary.NativeEndian.AppendUint32(nil, uint32(secs))
|
||||
}
|
||||
|
||||
// Breaks if could not retrieve meta data.
|
||||
if target == nil {
|
||||
regs.verdict = Verdict{Code: VC(linux.NFT_BREAK)}
|
||||
return
|
||||
}
|
||||
|
||||
// Gets the destination register.
|
||||
dst := getRegisterBuffer(regs, op.dreg)
|
||||
// Zeroes out excess bytes of the destination register.
|
||||
// This is done since comparison can be done in multiples of 4 bytes.
|
||||
blen := metaDataLengths[op.key]
|
||||
if rem := blen % 4; rem != 0 {
|
||||
clear(dst[blen : blen+4-rem])
|
||||
}
|
||||
// Copies target data into the destination register.
|
||||
copy(dst, target)
|
||||
}
|
||||
|
||||
//
|
||||
// Register and Register-Related Implementations.
|
||||
// Note: Registers are represented by type uint8 for the register number.
|
||||
@@ -1912,11 +2153,16 @@ func (r *Rule) evaluate(regs *registerSet, pkt *stack.PacketBuffer) error {
|
||||
|
||||
// NewNFTables creates a new NFTables state object using the given clock for
|
||||
// timing operations.
|
||||
func NewNFTables(clock tcpip.Clock) *NFTables {
|
||||
// Note: Expects random number generator to be initialized with a seed.
|
||||
// TODO(b/345684870): Use a secure RNG.
|
||||
func NewNFTables(clock tcpip.Clock, rng *rand.Rand) *NFTables {
|
||||
if clock == nil {
|
||||
panic("nftables state must be initialized with a non-nil clock")
|
||||
}
|
||||
return &NFTables{clock: clock, startTime: clock.Now()}
|
||||
if rng == nil {
|
||||
panic("nftables state must be initialized with a non-nil random number generator")
|
||||
}
|
||||
return &NFTables{clock: clock, startTime: clock.Now(), rng: rng}
|
||||
}
|
||||
|
||||
// Flush clears entire ruleset and all data for all address families.
|
||||
|
||||
@@ -17,6 +17,7 @@ package nftables
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
@@ -125,6 +126,13 @@ const (
|
||||
tcpChecksumLen = 2
|
||||
tcpUrgPtrOffset = 18
|
||||
tcpUrgPtrLen = 2
|
||||
|
||||
// Arbitrary Socket IDs
|
||||
arbitrarySKUID = 0x020304
|
||||
arbitrarySKGID = 45668
|
||||
|
||||
// Arbitrary Packet Type
|
||||
arbitraryPktType = tcpip.PacketOutgoing
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -229,6 +237,9 @@ func makeIPv4Packet(reserved int, ipv4Fields *header.IPv4Fields) *stack.PacketBu
|
||||
// Prepends the IPv4 header to the packet buffer.
|
||||
ipv4Hdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
|
||||
|
||||
// Sets the packet type.
|
||||
pkt.PktType = arbitraryPktType
|
||||
|
||||
// Initializes the IPv4 header with fields.
|
||||
ipv4Hdr.Encode(ipv4Fields)
|
||||
|
||||
@@ -251,6 +262,9 @@ func makeIPv6Packet(reserved int, ipv6Fields *header.IPv6Fields) *stack.PacketBu
|
||||
// Prepends the IPv6 header to the packet buffer.
|
||||
ipv6Hdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
|
||||
|
||||
// Sets the packet type.
|
||||
pkt.PktType = arbitraryPktType
|
||||
|
||||
// Initializes the IPv6 header with fields.
|
||||
ipv6Hdr.Encode(ipv6Fields)
|
||||
|
||||
@@ -2401,7 +2415,8 @@ func TestEvaluateLast(t *testing.T) {
|
||||
|
||||
// Sets up an NFTables object with a base chain and fake manual clock.
|
||||
fakeClock := faketime.NewManualClock()
|
||||
nf := NewNFTables(fakeClock)
|
||||
fixedRng := rand.New(rand.NewSource(0))
|
||||
nf := NewNFTables(fakeClock, fixedRng)
|
||||
tab, err := nf.AddTable(arbitraryFamily, "test", "test table", false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for AddTable: %v", err)
|
||||
@@ -2813,6 +2828,196 @@ func TestEvaluateByteorder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// mockPacketOwner implements PacketOwner for testing.
|
||||
type mockPacketOwner struct {
|
||||
uid uint32
|
||||
gid uint32
|
||||
}
|
||||
|
||||
// KUID returns the UID of the mock packet owner.
|
||||
func (m mockPacketOwner) KUID() uint32 {
|
||||
return m.uid
|
||||
}
|
||||
|
||||
// KGID returns the GID of the mock packet owner.
|
||||
func (m mockPacketOwner) KGID() uint32 {
|
||||
return m.gid
|
||||
}
|
||||
|
||||
// TestEvaluateMetaLoad tests that the Meta Load operation correctly loads
|
||||
// the specific meta data into the destination register.
|
||||
// The nft binary commands used to generate these are stated above each test.
|
||||
// All commands should be preceded by nft --debug=netlink.
|
||||
// Note: Relies on expected behavior of the Comparison operation.
|
||||
// Note: Does all comparisons in multiples of 4 bytes.
|
||||
// TODO(b/339691111): Add tests for VLAN, ARP, ICMP, ICMPv6, IGMP, UDP headers.
|
||||
func TestEvaluateMetaLoad(t *testing.T) {
|
||||
// Initializes testing packet.
|
||||
tcid := 0x05
|
||||
pktSize := header.IPv6MinimumSize + header.TCPMinimumSize
|
||||
ipv6Fields := arbitraryIPv6Fields()
|
||||
ipv6Fields.TrafficClass = uint8(tcid)
|
||||
tcpFields := arbitraryTCPFields()
|
||||
pkt := makeIPv6TCPPacket(pktSize, ipv6Fields, tcpFields)
|
||||
pkt.Owner = &mockPacketOwner{arbitrarySKUID, arbitrarySKGID}
|
||||
|
||||
// Sets up a fake clock (now = UnixEpoch) and dependent time/random fields.
|
||||
fakeClock := faketime.NewManualClock()
|
||||
now := fakeClock.Now()
|
||||
timeNS := now.UnixNano()
|
||||
timeDay := now.Weekday()
|
||||
timeHour := now.Hour()*3600 + now.Minute()*60 + now.Second()
|
||||
fixedRng := rand.New(rand.NewSource(0))
|
||||
seededRandUint32 := fixedRng.Uint32() // fixes rng
|
||||
|
||||
for _, test := range []struct {
|
||||
tname string
|
||||
pkt *stack.PacketBuffer
|
||||
op1 operation // Meta Load operation to test.
|
||||
op2 operation // Comparison operation to check result data in register.
|
||||
// Note: op2 should be nil if expecting a break during evaluation.
|
||||
}{
|
||||
{ // cmd: add rule ip6 tab ch meta length 60
|
||||
tname: "meta load len test",
|
||||
pkt: pkt,
|
||||
op1: mustCreateMetaLoad(t, linux.NFT_META_LEN, linux.NFT_REG_2),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ,
|
||||
binary.NativeEndian.AppendUint32(nil, uint32(pktSize))),
|
||||
},
|
||||
{ // cmd: add rule ip6 tab ch meta protocol 0x86dd
|
||||
tname: "meta load protocol test",
|
||||
pkt: pkt,
|
||||
op1: mustCreateMetaLoad(t, linux.NFT_META_PROTOCOL, linux.NFT_REG_3),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_EQ,
|
||||
append(numToBE(int(header.IPv6ProtocolNumber), 2), 0, 0)),
|
||||
},
|
||||
{ // cmd: add rule ip6 tab ch meta nfproto 0x0a
|
||||
tname: "meta load nfproto test",
|
||||
pkt: pkt,
|
||||
op1: mustCreateMetaLoad(t, linux.NFT_META_NFPROTO, linux.NFT_REG_4),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ,
|
||||
[]byte{IP6.Protocol(), 0, 0, 0}),
|
||||
},
|
||||
{ // cmd: add rule ip6 tab ch meta l4proto 0x6
|
||||
tname: "meta load l4proto test",
|
||||
pkt: pkt,
|
||||
op1: mustCreateMetaLoad(t, linux.NFT_META_L4PROTO, linux.NFT_REG32_00),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG32_00, linux.NFT_CMP_EQ,
|
||||
[]byte{uint8(tcpTransportProtocol), 0, 0, 0}),
|
||||
},
|
||||
{ // cmd: add rule ip6 tab ch skuid 0x020304
|
||||
tname: "meta load skuid test",
|
||||
pkt: pkt,
|
||||
op1: mustCreateMetaLoad(t, linux.NFT_META_SKUID, linux.NFT_REG32_02),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG32_02, linux.NFT_CMP_EQ,
|
||||
binary.NativeEndian.AppendUint32(nil, arbitrarySKUID)),
|
||||
},
|
||||
{ // cmd: add rule ip6 tab ch skgid 45668
|
||||
tname: "meta load skgid test",
|
||||
pkt: pkt,
|
||||
op1: mustCreateMetaLoad(t, linux.NFT_META_SKGID, linux.NFT_REG32_03),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG32_03, linux.NFT_CMP_EQ,
|
||||
binary.NativeEndian.AppendUint32(nil, arbitrarySKGID)),
|
||||
},
|
||||
{ // cmd: add rule ip6 tab ch rtclassid 0x05
|
||||
tname: "meta load rtclassid test",
|
||||
pkt: pkt,
|
||||
op1: mustCreateMetaLoad(t, linux.NFT_META_RTCLASSID, linux.NFT_REG32_04),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG32_04, linux.NFT_CMP_EQ,
|
||||
binary.NativeEndian.AppendUint32(nil, uint32(tcid))),
|
||||
},
|
||||
{ // cmd: add rule ip6 tab ch pkttype 2
|
||||
tname: "meta load pkttype test",
|
||||
pkt: pkt,
|
||||
op1: mustCreateMetaLoad(t, linux.NFT_META_PKTTYPE, linux.NFT_REG32_05),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG32_05, linux.NFT_CMP_EQ,
|
||||
[]byte{uint8(arbitraryPktType), 0, 0, 0}),
|
||||
},
|
||||
{ // cmd: add rule ip6 tab ch meta random 4059586549
|
||||
tname: "meta load prandom test",
|
||||
pkt: pkt,
|
||||
op1: mustCreateMetaLoad(t, linux.NFT_META_PRANDOM, linux.NFT_REG32_01),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ,
|
||||
numToBE(int(seededRandUint32), 4)),
|
||||
},
|
||||
{ // cmd: add rule ip6 tab ch time "1970-01-01 00:00:00"
|
||||
tname: "meta load time at unix epoch test",
|
||||
pkt: pkt,
|
||||
op1: mustCreateMetaLoad(t, linux.NFT_META_TIME_NS, linux.NFT_REG_3),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_EQ,
|
||||
binary.NativeEndian.AppendUint64(nil, uint64(timeNS))),
|
||||
},
|
||||
{ // cmd: add rule ip6 tab ch day Thursday
|
||||
tname: "meta load day test",
|
||||
pkt: pkt,
|
||||
op1: mustCreateMetaLoad(t, linux.NFT_META_TIME_DAY, linux.NFT_REG32_15),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG32_15, linux.NFT_CMP_EQ,
|
||||
[]byte{uint8(timeDay), 0, 0, 0}),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch hour 0x01020304
|
||||
tname: "meta load hour test",
|
||||
pkt: pkt,
|
||||
op1: mustCreateMetaLoad(t, linux.NFT_META_TIME_HOUR, linux.NFT_REG32_14),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG32_14, linux.NFT_CMP_EQ,
|
||||
binary.NativeEndian.AppendUint32(nil, uint32(timeHour))),
|
||||
},
|
||||
} {
|
||||
t.Run(test.tname, func(t *testing.T) {
|
||||
// Sets up an NFTables object with a base chain and fake manual clock.
|
||||
// Using Manual Clock sets time.Now to Unix Epoch which fixes rng seed!
|
||||
nf := NewNFTables(fakeClock, rand.New(rand.NewSource(0)))
|
||||
|
||||
tab, err := nf.AddTable(arbitraryFamily, "test", "test table", false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for AddTable: %v", err)
|
||||
}
|
||||
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for AddChain: %v", err)
|
||||
}
|
||||
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
|
||||
rule := &Rule{}
|
||||
|
||||
// Adds testing operations.
|
||||
if test.op1 != nil {
|
||||
rule.addOperation(test.op1)
|
||||
}
|
||||
if test.op2 != nil {
|
||||
rule.addOperation(test.op2)
|
||||
}
|
||||
|
||||
// Adds drop operation. Will be final verdict if all comparisons are true.
|
||||
rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})))
|
||||
|
||||
// Registers the rule to the base chain.
|
||||
if err := bc.RegisterRule(rule, -1); err != nil {
|
||||
t.Fatalf("unexpected error for RegisterRule: %v", err)
|
||||
}
|
||||
|
||||
// Runs evaluation.
|
||||
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, test.pkt)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for EvaluateHook: %v", err)
|
||||
}
|
||||
|
||||
// Checks for final verdict.
|
||||
if test.op2 == nil {
|
||||
// If no comparison operation is set, then payload load should break,
|
||||
// resulting in Accept as the default policy verdict.
|
||||
if v.Code != VC(linux.NF_ACCEPT) {
|
||||
t.Fatalf("expected verdict Accept for break during evaluation, got %v", v)
|
||||
}
|
||||
} else {
|
||||
// If a comparison operation is set, both payload load and comparison
|
||||
// should succeed, resulting in Drop as the final verdict.
|
||||
if v.Code != VC(linux.NF_DROP) {
|
||||
t.Fatalf("expected verdict Drop for true comparison, got %v", v)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoopCheckOnRegisterAndUnregister tests the loop checking and accompanying
|
||||
// logic on registering and unregistering rules.
|
||||
func TestLoopCheckOnRegisterAndUnregister(t *testing.T) {
|
||||
@@ -3411,7 +3616,9 @@ func packetResultString(initial, final *stack.PacketBuffer) string {
|
||||
|
||||
// newNFTablesStd creates a new NFTables object w/ a standard clock for testing.
|
||||
func newNFTablesStd() *NFTables {
|
||||
return NewNFTables(tcpip.NewStdClock())
|
||||
stdClock := tcpip.NewStdClock()
|
||||
fixedRng := rand.New(rand.NewSource(0))
|
||||
return NewNFTables(stdClock, fixedRng)
|
||||
}
|
||||
|
||||
// mustCreateImmediate wraps the newImmediate function for brevity.
|
||||
@@ -3494,3 +3701,12 @@ func mustCreateByteorder(t *testing.T, sreg, dreg uint8, bop byteorderOp, blen,
|
||||
}
|
||||
return order
|
||||
}
|
||||
|
||||
// mustCreateMetaLoad wraps the newMetaLoad function for brevity.
|
||||
func mustCreateMetaLoad(t *testing.T, key metaKey, dreg uint8) *metaLoad {
|
||||
mtload, err := newMetaLoad(key, dreg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create meta load: %v", err)
|
||||
}
|
||||
return mtload
|
||||
}
|
||||
|
||||
@@ -157,6 +157,8 @@ func InterpretOperation(line string, lnIdx int) (operation, error) {
|
||||
return InterpretRoute(line, lnIdx)
|
||||
case "byteorder":
|
||||
return InterpretByteorder(line, lnIdx)
|
||||
case "meta":
|
||||
return InterpretMetaLoad(line, lnIdx)
|
||||
default:
|
||||
return nil, &SyntaxError{lnIdx, 1, fmt.Sprintf("unrecognized operation type: %s", tokens[1])}
|
||||
}
|
||||
@@ -818,6 +820,69 @@ func InterpretByteorder(line string, lnIdx int) (operation, error) {
|
||||
return order, nil
|
||||
}
|
||||
|
||||
// InterpretMetaLoad creates a new MetaLoad operation from the given string.
|
||||
func InterpretMetaLoad(line string, lnIdx int) (operation, error) {
|
||||
tokens := strings.Fields(line)
|
||||
|
||||
// Requires exactly 8 tokens:
|
||||
// "[", "meta", "load", meta key, "=>", "reg", register index, "]".
|
||||
if len(tokens) != 8 {
|
||||
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for meta operation, should be exactly 8, got %d", len(tokens))}
|
||||
}
|
||||
|
||||
if err := checkOperationBrackets(tokens, lnIdx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tkIdx := 1
|
||||
|
||||
// First token should be "meta".
|
||||
if err := consumeToken("meta", tokens, lnIdx, tkIdx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkIdx++
|
||||
|
||||
// Second token should be "load".
|
||||
if err := consumeToken("load", tokens, lnIdx, tkIdx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkIdx++
|
||||
|
||||
// Third token should be the meta key.
|
||||
key, err := parseMetaKey(tokens[tkIdx], lnIdx, tkIdx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkIdx++
|
||||
|
||||
// Fourth token should be "=>".
|
||||
if err := consumeToken("=>", tokens, lnIdx, tkIdx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkIdx++
|
||||
|
||||
// Fifth token should be "reg".
|
||||
if err := consumeToken("reg", tokens, lnIdx, tkIdx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkIdx++
|
||||
|
||||
// Sixth token should be the uint8 representing the register index.
|
||||
reg, err := parseRegister(tokens[tkIdx], lnIdx, tkIdx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkIdx++
|
||||
|
||||
// Create the operation with the specified arguments.
|
||||
mtLoad, err := newMetaLoad(key, reg)
|
||||
if err != nil {
|
||||
return nil, &LogicError{lnIdx, tkIdx, err}
|
||||
}
|
||||
|
||||
return mtLoad, nil
|
||||
}
|
||||
|
||||
//
|
||||
// Interpreter Helper Functions.
|
||||
//
|
||||
@@ -978,7 +1043,7 @@ func parseCmpOp(copString string, lnIdx int, tkIdx int) (int, error) {
|
||||
case "gte":
|
||||
return linux.NFT_CMP_GTE, nil
|
||||
default:
|
||||
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid comparison operator: '%s'", copString)}
|
||||
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid comparison operator keyword: '%s'", copString)}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1008,7 +1073,7 @@ func parsePayloadBase(baseString string, lnIdx int, tkIdx int) (payloadBase, err
|
||||
return linux.NFT_PAYLOAD_TRANSPORT_HEADER, nil
|
||||
// Inner and Tunnel Headers cannot be specified in payload load operation.
|
||||
default:
|
||||
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid payload base: '%s'", baseString)}
|
||||
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid payload base keyword: '%s'", baseString)}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1033,6 +1098,53 @@ func parseRouteKey(keyString string, lnIdx int, tkIdx int) (routeKey, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// metaKeyFromKeyword is a map of meta key keywords to their corresponding enum value.
|
||||
var metaKeyFromKeyword = map[string]metaKey{
|
||||
// Supported meta keys.
|
||||
"len": linux.NFT_META_LEN,
|
||||
"protocol": linux.NFT_META_PROTOCOL,
|
||||
"nfproto": linux.NFT_META_NFPROTO,
|
||||
"l4proto": linux.NFT_META_L4PROTO,
|
||||
"skuid": linux.NFT_META_SKUID,
|
||||
"skgid": linux.NFT_META_SKGID,
|
||||
"rtclassid": linux.NFT_META_RTCLASSID,
|
||||
"pkttype": linux.NFT_META_PKTTYPE,
|
||||
"prandom": linux.NFT_META_PRANDOM,
|
||||
"time": linux.NFT_META_TIME_NS,
|
||||
"day": linux.NFT_META_TIME_DAY,
|
||||
"hour": linux.NFT_META_TIME_HOUR,
|
||||
// Unsupported meta keys.
|
||||
"priority": linux.NFT_META_PRIORITY,
|
||||
"mark": linux.NFT_META_MARK,
|
||||
"iif": linux.NFT_META_IIF,
|
||||
"oif": linux.NFT_META_OIF,
|
||||
"iifname": linux.NFT_META_IIFNAME,
|
||||
"oifname": linux.NFT_META_OIFNAME,
|
||||
"iiftype": linux.NFT_META_IIFTYPE,
|
||||
"oiftype": linux.NFT_META_OIFTYPE,
|
||||
"iifgroup": linux.NFT_META_IIFGROUP,
|
||||
"oifgroup": linux.NFT_META_OIFGROUP,
|
||||
"cgroup": linux.NFT_META_CGROUP,
|
||||
"iifkind": linux.NFT_META_IIFKIND,
|
||||
"oifkind": linux.NFT_META_OIFKIND,
|
||||
"sdif": linux.NFT_META_SDIF,
|
||||
"sdifname": linux.NFT_META_SDIFNAME,
|
||||
"nftrace": linux.NFT_META_NFTRACE,
|
||||
"cpu": linux.NFT_META_CPU,
|
||||
"secmark": linux.NFT_META_SECMARK,
|
||||
"secpath": linux.NFT_META_SECPATH,
|
||||
"broute": linux.NFT_META_BRI_BROUTE,
|
||||
}
|
||||
|
||||
// parseMetaKey parses the meta key from the given string.
|
||||
func parseMetaKey(keyString string, lnIdx int, tkIdx int) (metaKey, error) {
|
||||
key, ok := metaKeyFromKeyword[keyString]
|
||||
if !ok {
|
||||
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid meta key keyword: '%s'", keyString)}
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// consumeToken is a helper function that checks if the token at the given index
|
||||
// matches the expected string, returning a SyntaxError if not.
|
||||
func consumeToken(expected string, tokens []string, lnIdx int, tkIdx int) error {
|
||||
|
||||
@@ -957,6 +957,101 @@ func checkByteorderOp(tname string, expected operation, actual operation) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestInterpretMetaLoadOps tests interpretation of meta load operations.
|
||||
func TestInterpretMetaLoadOps(t *testing.T) {
|
||||
for _, test := range []interpretOperationTestAction{
|
||||
{ // cmd: add rule ip tab ch meta length 0x01020304
|
||||
tname: "meta load len test",
|
||||
opStr: "[ meta load len => reg 2 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_LEN, linux.NFT_REG_2),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch meta protocol 0x0102
|
||||
tname: "meta load protocol test",
|
||||
opStr: "[ meta load protocol => reg 3 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_PROTOCOL, linux.NFT_REG_3),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch meta nfproto 253
|
||||
tname: "meta load nfproto test",
|
||||
opStr: "[ meta load nfproto => reg 4 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_NFPROTO, linux.NFT_REG_4),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch meta l4proto 0x17
|
||||
tname: "meta load l4proto test",
|
||||
opStr: "[ meta load l4proto => reg 8 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_L4PROTO, linux.NFT_REG32_00),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch skuid 0x09080706
|
||||
tname: "meta load skuid test",
|
||||
opStr: "[ meta load skuid => reg 10 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_SKUID, linux.NFT_REG32_02),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch meta skgid 0x09080706
|
||||
tname: "meta load skgid test",
|
||||
opStr: "[ meta load skgid => reg 11 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_SKGID, linux.NFT_REG32_03),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch rtclassid 0x01020304
|
||||
tname: "meta load rtclassid test",
|
||||
opStr: "[ meta load rtclassid => reg 12 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_RTCLASSID, linux.NFT_REG32_04),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch pkttype 0x59
|
||||
tname: "meta load pkttype test",
|
||||
opStr: "[ meta load pkttype => reg 13 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_PKTTYPE, linux.NFT_REG32_05),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch meta random 0x02040608
|
||||
tname: "meta load prandom test",
|
||||
opStr: "[ meta load prandom => reg 9 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_PRANDOM, linux.NFT_REG32_01),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch time "2020-06-06 17:00"
|
||||
tname: "meta load arbitrary time test",
|
||||
opStr: "[ meta load time => reg 4 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_TIME_NS, linux.NFT_REG_4),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch time "1970-01-01 00:00:01"
|
||||
tname: "meta load time 1 sec after unix epoch test",
|
||||
opStr: "[ meta load time => reg 3 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_TIME_NS, linux.NFT_REG_3),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch time "1969-01-01 00:00:00"
|
||||
tname: "meta load time 1 year before unix epoch test",
|
||||
opStr: "[ meta load time => reg 2 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_TIME_NS, linux.NFT_REG_2),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch day Monday
|
||||
tname: "meta load day test",
|
||||
opStr: "[ meta load day => reg 23 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_TIME_DAY, linux.NFT_REG32_15),
|
||||
},
|
||||
{ // cmd: add rule inet tab ch hour 0x01020304
|
||||
tname: "meta load hour test",
|
||||
opStr: "[ meta load hour => reg 22 ]",
|
||||
expected: mustCreateMetaLoad(t, linux.NFT_META_TIME_HOUR, linux.NFT_REG32_14),
|
||||
},
|
||||
} {
|
||||
t.Run(test.tname, func(t *testing.T) { checkOp(t, test, checkMetaLoadOp) })
|
||||
}
|
||||
}
|
||||
|
||||
// checkMetaLoadOp checks that the given operation is a meta load operation and
|
||||
// that it matches the expected meta load operation.
|
||||
func checkMetaLoadOp(tname string, expected operation, actual operation) error {
|
||||
expectedMtLoad := expected.(*metaLoad)
|
||||
mtLoad, ok := actual.(*metaLoad)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected operation type to be MetaLoad for %s, got %T", tname, actual)
|
||||
}
|
||||
if mtLoad.key != expectedMtLoad.key {
|
||||
return fmt.Errorf("expected meta key to be %v for %s, got %v", expectedMtLoad.key, tname, mtLoad.key)
|
||||
}
|
||||
if mtLoad.dreg != expectedMtLoad.dreg {
|
||||
return fmt.Errorf("expected destination register to be %d for %s, got %d", expectedMtLoad.dreg, tname, mtLoad.dreg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestInterpretRule tests the interpretation of basic and general rules as a
|
||||
// list of operations.
|
||||
func TestInterpretRule(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user