// Copyright 2024 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"
	"encoding/binary"
	"fmt"
	"math"
	"reflect"
	"slices"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/atomicbitops"
	"gvisor.dev/gvisor/pkg/buffer"
	"gvisor.dev/gvisor/pkg/marshal/primitive"
	"gvisor.dev/gvisor/pkg/rand"
	"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
	"gvisor.dev/gvisor/pkg/sync"
	"gvisor.dev/gvisor/pkg/syserr"
	"gvisor.dev/gvisor/pkg/tcpip"
	"gvisor.dev/gvisor/pkg/tcpip/faketime"
	"gvisor.dev/gvisor/pkg/tcpip/header"
	"gvisor.dev/gvisor/pkg/tcpip/stack"
)

// Table Constants.
const (
	arbitraryTargetChain         string              = "target_chain"
	arbitraryHook                stack.NFHook        = stack.NFPrerouting
	arbitraryFamily              stack.AddressFamily = stack.Inet
	arbitraryReservedHeaderBytes int                 = 16
)

var (
	arbitraryPriority Priority = func() Priority {
		priority, err := NewStandardPriority("filter", arbitraryFamily, arbitraryHook)
		if err != nil {
			panic(fmt.Sprintf("unexpected error for NewStandardPriority: %v", err))
		}
		return priority
	}()

	arbitraryInfoPolicyAccept *BaseChainInfo = &BaseChainInfo{
		BcType:   BaseChainTypeFilter,
		Hook:     arbitraryHook,
		Priority: arbitraryPriority,
	}
)

// Packet Constants.
const (
	tcpTransportProtocol = header.TCPProtocolNumber

	arbitraryHeaderID   = 3
	arbitraryTimeToLive = 64

	arbitraryPort    = 12345
	arbitraryPort2   = 80
	tcpSeqNum        = 32
	tcpAckNum        = 165
	tcpWinSize       = 65535
	tcpUrgentPointer = 0

	arbitraryNonZeroFragmentOffset = 16

	// TODO(b/345684870): Use constants defined in the pkg/tcpip/header package.
	// Ethernet Offsets and Lengths.
	ethDstAddrOffset = 0
	ethDstAddrLen    = 6
	ethSrcAddrOffset = 6
	ethSrcAddrLen    = 6
	ethTypeOffset    = 12
	ethTypeLen       = 2

	// IPv4 Offsets and Lengths.
	ipv4LengthOffset   = 2
	ipv4LengthLen      = 2
	ipv4IDOffset       = 4
	ipv4IDLen          = 2
	ipv4FragOffOffset  = 6
	ipv4FragOffLen     = 2
	ipv4TTLOffset      = 8
	ipv4TTLLen         = 1
	ipv4ProtocolOffset = 9
	ipv4ProtocolLen    = 1
	ipv4ChecksumOffset = 10
	ipv4ChecksumLen    = 2
	ipv4SrcAddrOffset  = 12
	ipv4SrcAddrLen     = 4
	ipv4DstAddrOffset  = 16
	ipv4DstAddrLen     = 4

	// IPv6 Offsets and Lengths.
	ipv6LengthOffset   = 4
	ipv6LengthLen      = 2
	ipv6NextHdrOffset  = 6
	ipv6NextHdrLen     = 1
	ipv6HopLimitOffset = 7
	ipv6HopLimitLen    = 1
	ipv6SrcAddrOffset  = 8
	ipv6SrcAddrLen     = 16
	ipv6DstAddrOffset  = 24
	ipv6DstAddrLen     = 16

	// TCP Offsets and Lengths.
	tcpSrcPortOffset  = 0
	tcpSrcPortLen     = 2
	tcpDstPortOffset  = 2
	tcpDstPortLen     = 2
	tcpSeqNumOffset   = 4
	tcpSeqNumLen      = 4
	tcpAckNumOffset   = 8
	tcpAckNumLen      = 4
	tcpWindowOffset   = 14
	tcpWindowLen      = 2
	tcpChecksumOffset = 16
	tcpChecksumLen    = 2
	tcpUrgPtrOffset   = 18
	tcpUrgPtrLen      = 2

	// Arbitrary Socket IDs
	arbitrarySKUID = 0x020304
	arbitrarySKGID = 45668

	// Arbitrary Packet Type
	arbitraryPktType = tcpip.PacketOutgoing
)

var (
	arbitraryLinkAddr     = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
	arbitraryLinkAddr2    = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x07")
	arbitraryLinkAddrB    = [6]byte{0x02, 0x02, 0x03, 0x04, 0x05, 0x06}
	arbitraryLinkAddrB2   = [6]byte{0x02, 0x02, 0x03, 0x04, 0x05, 0x07}
	arbitraryEthernetType = header.IPv4ProtocolNumber

	arbitraryIPv4AddrB  = [4]byte{192, 168, 1, 1}
	arbitraryIPv4AddrB2 = [4]byte{192, 168, 1, 9}
	ipv4MinTotalLength  = header.IPv4MinimumSize

	arbitraryIPv6AddrB   = [16]byte{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa}
	arbitraryIPv6AddrB2  = [16]byte{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb}
	ipv6MinPayloadLength = 0

	// Note: these are functions to make sure they are not modified by tests and
	// so each tests gets a new value.
	arbitraryEthernetFields = func() *header.EthernetFields {
		return &header.EthernetFields{
			SrcAddr: arbitraryLinkAddr,
			DstAddr: arbitraryLinkAddr2,
			Type:    arbitraryEthernetType,
		}
	}

	arbitraryIPv4Fields = func() *header.IPv4Fields {
		return &header.IPv4Fields{
			TOS:            0,
			TotalLength:    uint16(ipv4MinTotalLength),
			ID:             arbitraryHeaderID,
			FragmentOffset: 0,
			TTL:            arbitraryTimeToLive,
			Protocol:       uint8(tcpTransportProtocol),
			Checksum:       0,
			SrcAddr:        tcpip.AddrFrom4(arbitraryIPv4AddrB),
			DstAddr:        tcpip.AddrFrom4(arbitraryIPv4AddrB2),
			Options:        nil,
		}
	}

	fragmentedIPv4Fields = func() *header.IPv4Fields {
		fields := arbitraryIPv4Fields()
		fields.FragmentOffset = arbitraryNonZeroFragmentOffset
		return fields
	}

	arbitraryIPv6Fields = func() *header.IPv6Fields {
		return &header.IPv6Fields{
			TrafficClass:      0,
			FlowLabel:         0,
			PayloadLength:     uint16(ipv6MinPayloadLength),
			TransportProtocol: tcpTransportProtocol,
			HopLimit:          arbitraryTimeToLive,
			SrcAddr:           tcpip.AddrFrom16(arbitraryIPv6AddrB),
			DstAddr:           tcpip.AddrFrom16(arbitraryIPv6AddrB2),
		}
	}

	arbitraryTCPFields = func() *header.TCPFields {
		return &header.TCPFields{
			SrcPort:       uint16(arbitraryPort),
			DstPort:       uint16(arbitraryPort2),
			SeqNum:        uint32(tcpSeqNum),
			AckNum:        uint32(tcpAckNum),
			DataOffset:    header.TCPMinimumSize,
			WindowSize:    uint16(tcpWinSize),
			Checksum:      0,
			UrgentPointer: uint16(tcpUrgentPointer),
		}
	}
)

// makeArbitraryPacket creates an arbitrary packet for testing.
func makeArbitraryPacket(reserved int) *stack.PacketBuffer {
	return stack.NewPacketBuffer(stack.PacketBufferOptions{
		ReserveHeaderBytes: reserved,
		Payload:            buffer.MakeWithData([]byte{0, 2, 4, 8, 16, 32, 64, 128}),
	})
}

// makeEthernetPacket creates a packet with an Ethernet header.
func makeEthernetPacket(reserved int, ethFields *header.EthernetFields) *stack.PacketBuffer {
	eth := make([]byte, header.EthernetMinimumSize)
	header.Ethernet(eth).Encode(ethFields)
	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
		ReserveHeaderBytes: reserved,
		Payload:            buffer.MakeWithData(eth),
	})
	pkt.LinkHeader().Consume(header.EthernetMinimumSize)
	return pkt
}

// makeIPv4Packet creates a packet with an IPv4 header.
func makeIPv4Packet(reserved int, ipv4Fields *header.IPv4Fields) *stack.PacketBuffer {
	// Creates a new PacketBuffer with enough space for the IPv4 header.
	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
		ReserveHeaderBytes: reserved,
	})

	// 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)

	// Calculates and sets the checksum.
	ipv4Hdr.SetChecksum(^ipv4Hdr.CalculateChecksum())

	// Sets the network protocol number.
	pkt.NetworkProtocolNumber = header.IPv4ProtocolNumber

	return pkt
}

func makeArbitraryIPv4Packet() *stack.PacketBuffer {
	pkt := makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields())
	return pkt
}

// makeIPv6Packet creates a packet with an IPv6 header.
func makeIPv6Packet(reserved int, ipv6Fields *header.IPv6Fields) *stack.PacketBuffer {
	// Creates a new PacketBuffer with enough space for the IPv4 header.
	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
		ReserveHeaderBytes: reserved,
	})

	// 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)

	// No checksum for IPv6 (relies on L4 checksum if extra security is needed).

	// Sets the network protocol number.
	pkt.NetworkProtocolNumber = header.IPv6ProtocolNumber

	return pkt
}

func makeArbitraryIPv6Packet() *stack.PacketBuffer {
	pkt := makeIPv6Packet(header.IPv6MinimumSize, arbitraryIPv6Fields())
	return pkt
}

// addTCPHeader adds a TCP header to a packet and returns the header.
// Note: this does not compute the checksum.
func addTCPHeader(pkt *stack.PacketBuffer, tcpFields *header.TCPFields) header.TCP {
	// Prepends the TCP header to the packet buffer.
	tcpHdr := header.TCP(pkt.TransportHeader().Push(int(tcpFields.DataOffset)))

	// Initializes the TCP header with fields.
	tcpHdr.Encode(tcpFields)

	// Sets the transport protocol number.
	pkt.TransportProtocolNumber = header.TCPProtocolNumber

	return tcpHdr
}

// makeIPv4TCPPacket creates a packet with an IPv4 and TCP header.
func makeIPv4TCPPacket(reserved int, ipv4Fields *header.IPv4Fields, tcpFields *header.TCPFields) *stack.PacketBuffer {
	// Makes a packet with the L3 IPv4 header (this sets the checksum).
	pkt := makeIPv4Packet(reserved, ipv4Fields)

	// Adds the L4 TCP header.
	tcpHdr := addTCPHeader(pkt, tcpFields)

	// Calculates the TCP checksum using the pseudo-header and sets it in the TCP header.
	tcpHdr.SetChecksum(^tcpHdr.CalculateChecksum(header.PseudoHeaderChecksum(
		tcpip.TransportProtocolNumber(ipv4Fields.Protocol),
		ipv4Fields.SrcAddr,
		ipv4Fields.DstAddr,
		uint16(tcpFields.DataOffset),
	)))

	return pkt
}

// makeIPv6TCPPacket creates a packet with an IPv6 and TCP header.
func makeIPv6TCPPacket(reserved int, ipv6Fields *header.IPv6Fields, tcpFields *header.TCPFields) *stack.PacketBuffer {
	// Makes a packet with the L3 IPv6 header (this sets the checksum).
	pkt := makeIPv6Packet(reserved, ipv6Fields)

	// Adds the L4 TCP header.
	tcpHdr := addTCPHeader(pkt, tcpFields)

	// Calculates the TCP checksum using the pseudo-header and sets it in the TCP header.
	tcpHdr.SetChecksum(^tcpHdr.CalculateChecksum(header.PseudoHeaderChecksum(
		// Next header is supposed to be in pseudo-header calculation for IPv6
		// transport protocol checksum, not the transport protocol number according
		// to RFC 2460 (https://www.rfc-editor.org/rfc/rfc2460.html#section-8.1).
		header.IPv6(pkt.NetworkHeader().Slice()).TransportProtocol(),
		ipv6Fields.SrcAddr,
		ipv6Fields.DstAddr,
		uint16(ipv6Fields.PayloadLength),
	)))

	return pkt
}

func getAddrFamilyOrDefault(pkt *stack.PacketBuffer, family stack.AddressFamily) stack.AddressFamily {
	if len(pkt.NetworkHeader().Slice()) > 0 {
		switch pkt.NetworkProtocolNumber {
		case header.IPv4ProtocolNumber:
			return stack.IP
		case header.IPv6ProtocolNumber:
			return stack.IP6
		}
		return family
	}
	if len(pkt.LinkHeader().Slice()) >= header.EthernetMinimumSize {
		ethHdr := header.Ethernet(pkt.LinkHeader().Slice())
		switch ethHdr.Type() {
		case header.IPv4ProtocolNumber:
			return stack.IP
		case header.IPv6ProtocolNumber:
			return stack.IP6
		}
	}
	return family
}

// TestUnsupportedAddressFamily tests that an empty NFTables object returns an
// error when evaluating a packet for an unsupported address family.
func TestUnsupportedAddressFamily(t *testing.T) {
	// Makes arbitrary packet for comparison (to check for no changes).
	cmpPkt := makeArbitraryPacket(arbitraryReservedHeaderBytes)
	nf := newNFTablesStd()
	for _, unsupportedFamily := range []stack.AddressFamily{stack.AddressFamily(stack.NumAFs), stack.AddressFamily(-1)} {
		// Note: the Prerouting hook is arbitrary (any hook would work).
		pkt := makeArbitraryPacket(arbitraryReservedHeaderBytes)
		v, err := nf.EvaluateHook(unsupportedFamily, arbitraryHook, pkt, nil /* route */)
		if err == nil {
			t.Fatalf("expecting error for EvaluateHook with unsupported address family %d; got %v verdict, %s packet, and error %v",
				int(unsupportedFamily),
				v, packetResultString(cmpPkt, pkt), err)
		}
	}
}

// TestAcceptAll tests that an empty NFTables object accepts all packets for
// supported hooks and errors for unsupported hooks for all address families
// when evaluating packets at the hook-level.
func TestAcceptAllForSupportedHooks(t *testing.T) {
	// Makes arbitrary packet for comparison (to check for no changes).
	for _, family := range []stack.AddressFamily{stack.IP, stack.IP6, stack.Inet, stack.Arp, stack.Bridge, stack.Netdev} {
		t.Run(family.String()+" address family", func(t *testing.T) {
			nf := newNFTablesStd()
			for _, hook := range []stack.NFHook{stack.NFPrerouting, stack.NFInput, stack.NFForward, stack.NFOutput, stack.NFPostrouting, stack.NFIngress, stack.NFEgress} {
				var pkt *stack.PacketBuffer
				pktFamily := family

				switch family {
				case stack.IP, stack.Inet:
					// Inet address family is not supported for hook evaluation.
					pkt = makeArbitraryIPv4Packet()
					pktFamily = stack.IP
				case stack.IP6:
					pkt = makeArbitraryIPv6Packet()
				default:
					pkt = makeArbitraryPacket(arbitraryReservedHeaderBytes)
				}
				cmpPkt := pkt.Clone()
				v, err := nf.EvaluateHook(pktFamily, hook, pkt, nil /* route */)

				supported := supportedHooks[family][hook]
				if supported {
					if err != nil || v.Code != VC(linux.NF_ACCEPT) {
						t.Fatalf("expecting accept verdict for EvaluateHook with supported hook %v for family %v; got %v verdict, %s packet, and error %v",
							hook, family,
							v, packetResultString(cmpPkt, pkt), err)
					}
				} else {
					if err == nil {
						t.Fatalf("expecting error for EvaluateHook with unsupported hook %v for family %v; got %v verdict, %s packet, and error %v",
							hook, family,
							v, packetResultString(cmpPkt, pkt), err)
					}
				}
			}
		})
	}
}

// TestEvaluateImmediateVerdict tests that the Immediate operation correctly sets the
// register value and behaves as expected during evaluation.
func TestEvaluateImmediateVerdict(t *testing.T) {
	for _, test := range []struct {
		tname    string
		baseOp1  operation // will be nil if unused
		baseOp2  operation // will be nil if unused
		targetOp operation // will be nil if unused
		verdict  Verdict
	}{
		{
			tname:   "no operations",
			verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy
		},
		{
			tname:   "immediately accept",
			baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)}),
			verdict: Verdict{Code: VC(linux.NF_ACCEPT)},
		},
		{
			tname:   "immediately drop",
			baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
			verdict: Verdict{Code: VC(linux.NF_DROP)},
		},
		{
			tname:   "immediately continue with base chain policy accept",
			baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_CONTINUE)}),
			verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy
		},
		{
			tname:   "immediately return with base chain policy accept",
			baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_RETURN)}),
			verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy
		},
		{
			tname:    "immediately jump to target chain that accepts",
			baseOp1:  mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_JUMP)}),
			targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)}),
			verdict:  Verdict{Code: VC(linux.NF_ACCEPT)},
		},
		{
			tname:    "immediately jump to target chain that drops",
			baseOp1:  mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_JUMP)}),
			targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
			verdict:  Verdict{Code: VC(linux.NF_DROP)},
		},
		{
			tname:    "immediately jump to target chain that continues with second rule that accepts",
			baseOp1:  mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_JUMP)}),
			targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_CONTINUE)}),
			baseOp2:  mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)}),
			verdict:  Verdict{Code: VC(linux.NF_ACCEPT)},
		},
		{
			tname:    "immediately jump to target chain that continues with second rule that drops",
			baseOp1:  mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_JUMP)}),
			targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_CONTINUE)}),
			baseOp2:  mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
			verdict:  Verdict{Code: VC(linux.NF_DROP)},
		},
		{
			tname:    "immediately goto to target chain that accepts",
			baseOp1:  mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_GOTO)}),
			targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)}),
			verdict:  Verdict{Code: VC(linux.NF_ACCEPT)},
		},
		{
			tname:    "immediately goto to target chain that drops",
			baseOp1:  mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_GOTO)}),
			targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
			verdict:  Verdict{Code: VC(linux.NF_DROP)},
		},
		{
			tname:    "immediately goto to target chain that continues with second rule that accepts",
			baseOp1:  mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_GOTO)}),
			targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_CONTINUE)}),
			baseOp2:  mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)}),
			verdict:  Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy
		},
		{
			tname:    "immediately goto to target chain that continues with second rule that drops",
			baseOp1:  mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_GOTO)}),
			targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_CONTINUE)}),
			baseOp2:  mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
			verdict:  Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy
		},
		{
			tname:   "add data to register then accept",
			baseOp1: mustCreateImmediate(t, linux.NFT_REG32_13, []byte{0, 1, 2, 3}, Verdict{}),
			baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)}),
			verdict: Verdict{Code: VC(linux.NF_ACCEPT)},
		},
		{
			tname:   "add data to register then drop",
			baseOp1: mustCreateImmediate(t, linux.NFT_REG32_15, []byte{0, 1, 2, 3}, Verdict{}),
			baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
			verdict: Verdict{Code: VC(linux.NF_DROP)},
		},
		{
			tname:   "add data to register then continue",
			baseOp1: mustCreateImmediate(t, linux.NFT_REG_4, []byte{0, 1, 2, 3}, Verdict{}),
			baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_CONTINUE)}),
			verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy
		},
		{
			tname:   "multiple accepts",
			baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)}),
			baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)}),
			verdict: Verdict{Code: VC(linux.NF_ACCEPT)},
		},
		{
			tname:   "multiple drops",
			baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
			baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
			verdict: Verdict{Code: VC(linux.NF_DROP)},
		},
		{
			tname:   "immediately accept then drop",
			baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)}),
			baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
			verdict: Verdict{Code: VC(linux.NF_ACCEPT)},
		},
		{
			tname:   "immediately drop then accept",
			baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
			baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)}),
			verdict: Verdict{Code: VC(linux.NF_DROP)},
		},
		{
			tname:   "immediate load register",
			baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
			baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)}),
			verdict: Verdict{Code: VC(linux.NF_DROP)},
		},
	} {
		t.Run(test.tname, func(t *testing.T) {
			// Sets up an NFTables object with a base chain (for 2 rules) and another
			// target chain (for 1 rule).
			nf := newNFTablesStd()
			tab, err := nf.AddTable(arbitraryFamily, "test", false)
			if err != nil {
				t.Fatalf("unexpected error for AddTable: %v", err)
			}
			bc, err := nf.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept /* bcInfo */, "test chain", false /* errorOnDuplicate */, 0 /* chainFlags */, nil /* udata */, linux.NF_ACCEPT /* policy */)
			if err != nil {
				t.Fatalf("unexpected error for AddChainToTable: %v", err)
			}
			tc, err := nf.AddChainToTable(tab, arbitraryTargetChain, nil /* bcInfo */, "test chain", false /* errorOnDuplicate */, 0 /* chainFlags */, nil /* udata */, 0 /* policy */)
			if err != nil {
				t.Fatalf("unexpected error for AddChainToTable: %v", err)
			}

			// Adds testing rules and operations.
			if test.baseOp1 != nil {
				setVerdictChainIfJumpGoto(test.baseOp1, tc)
				rule1 := &Rule{}
				rule1.addOperation(test.baseOp1)
				if err := bc.RegisterRule(rule1, -1); err != nil {
					t.Fatalf("unexpected error for RegisterRule for the first operation: %v", err)
				}
			}
			if test.baseOp2 != nil {
				setVerdictChainIfJumpGoto(test.baseOp2, tc)
				rule2 := &Rule{}
				rule2.addOperation(test.baseOp2)
				if err := bc.RegisterRule(rule2, -1); err != nil {
					t.Fatalf("unexpected error for RegisterRule for the second operation: %v", err)
				}
			}
			if test.targetOp != nil {
				ruleTarget := &Rule{}
				ruleTarget.addOperation(test.targetOp)
				if err := tc.RegisterRule(ruleTarget, -1); err != nil {
					t.Fatalf("unexpected error for RegisterRule for the target operation: %v", err)
				}
			}

			// Runs evaluation and checks verdict.
			pkt := makeArbitraryIPv4Packet()
			v, err := nf.EvaluateHook(stack.IP, arbitraryHook, pkt, nil /* route */)

			if err != nil {
				t.Fatalf("unexpected error for EvaluateHook: %v", err)
			}
			if v.Code != test.verdict.Code {
				t.Fatalf("expected verdict %v, got %v", test.verdict, v)
			}
		})
	}
}

// TestEvaluateImmediateVerdict tests that the Immediate operation correctly
// loads bytes data of all lengths into all supported registers.
func TestEvaluateImmediateBytesData(t *testing.T) {
	bytes := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}
	for blen := 1; blen <= len(bytes); blen++ {
		for _, registerSize := range []int{linux.NFT_REG32_SIZE, linux.NFT_REG_SIZE} {
			if blen > registerSize {
				continue
			}
			tname := fmt.Sprintf("immediately load %d bytes into %d-byte registers", blen, registerSize)
			t.Run(tname, func(t *testing.T) {
				// Sets up an NFTables object with a base chain with policy accept.
				nf := newNFTablesStd()
				tab, err := nf.AddTable(arbitraryFamily, "test", false)
				if err != nil {
					t.Fatalf("unexpected error for AddTable: %v", err)
				}
				bc, err := nf.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept /* bcInfo */, "test chain", false /* errorOnDuplicate */, 0 /* chainFlags */, nil /* udata */, linux.NF_ACCEPT /* policy */)
				if err != nil {
					t.Fatalf("unexpected error for AddChain: %v", err)
				}

				// Adds a rule and immediate operation per register of registerSize.
				switch registerSize {
				case linux.NFT_REG32_SIZE:
					for reg := linux.NFT_REG32_00; reg <= linux.NFT_REG32_15; reg++ {
						rule := &Rule{}
						rule.addOperation(mustCreateImmediate(t, uint8(reg), bytes[:blen], Verdict{}))
						if err := bc.RegisterRule(rule, -1); err != nil {
							t.Fatalf("unexpected error for RegisterRule for rule %d: %v", reg-linux.NFT_REG32_00, err)
						}
					}
				case linux.NFT_REG_SIZE:
					for reg := linux.NFT_REG_1; reg <= linux.NFT_REG_4; reg++ {
						rule := &Rule{}
						rule.addOperation(mustCreateImmediate(t, uint8(reg), bytes[:blen], Verdict{}))
						if err := bc.RegisterRule(rule, -1); err != nil {
							t.Fatalf("unexpected error for RegisterRule for rule %d: %v", reg-linux.NFT_REG_1, err)
						}
					}
				}
				// Runs evaluation and checks for default policy verdict accept
				pkt := makeArbitraryIPv4Packet()
				v, err := nf.EvaluateHook(stack.IP, arbitraryHook, pkt, nil /* route */)
				if err != nil {
					t.Fatalf("unexpected error for EvaluateHook: %v", err)
				}
				if v.Code != linux.NF_ACCEPT {
					t.Fatalf("expected default policy verdict accept, got %v", v)
				}
			})
		}
	}
}

// TestEvaluateComparison tests that the Comparison operation correctly compares
// the data in the source register to the given data.
// Note: Relies on expected behavior of the Immediate operation.
func TestEvaluateComparison(t *testing.T) {
	for _, test := range []struct {
		tname string
		op1   operation // will be nil if unused
		op2   operation // will be nil if unused
		res   bool      // should be true if we reach end of the rule (no breaks)
	}{
		// 4-byte data comparisons, alternates between 4-byte and 16-byte registers.
		{
			tname: "compare register == 4-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, []byte{0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register == 4-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_11, []byte{1, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG32_11, linux.NFT_CMP_EQ, []byte{0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare register != 4-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_03, []byte{1, 7, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG32_03, linux.NFT_CMP_NEQ, []byte{1, 98, 0, 56}),
			res:   true,
		},
		{
			tname: "compare register != 4-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{1, 98, 0, 56}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_NEQ, []byte{1, 98, 0, 56}),
			res:   false,
		},
		{
			tname: "compare register < 4-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{29, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_LT, []byte{100, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register < 4-byte data, false eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_04, []byte{100, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG32_04, linux.NFT_CMP_LT, []byte{100, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare register < 4-byte data, false gt",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_14, []byte{200, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG32_14, linux.NFT_CMP_LT, []byte{100, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare register > 4-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_15, []byte{29, 76, 230, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG32_15, linux.NFT_CMP_GT, []byte{0, 0, 0, 1}),
			res:   true,
		},
		{
			tname: "compare register > 4-byte data, false eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_07, []byte{29, 76, 230, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG32_07, linux.NFT_CMP_GT, []byte{29, 76, 230, 0}),
			res:   false,
		},
		{
			tname: "compare register > 4-byte data, false lt",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_05, []byte{28, 76, 230, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG32_05, linux.NFT_CMP_GT, []byte{29, 76, 230, 0}),
			res:   false,
		},
		{
			tname: "compare register <= 4-byte data, true lt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{29, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_LTE, []byte{100, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register <= 4-byte data, true eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_09, []byte{100, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG32_09, linux.NFT_CMP_LTE, []byte{100, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register <= 4-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_06, []byte{200, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG32_06, linux.NFT_CMP_LTE, []byte{100, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare register >= 4-byte data, true gt",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_12, []byte{29, 76, 230, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG32_12, linux.NFT_CMP_GTE, []byte{0, 0, 0, 1}),
			res:   true,
		},
		{
			tname: "compare register >= 4-byte data, true eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{29, 76, 230, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_GTE, []byte{29, 76, 230, 0}),
			res:   true,
		},
		{
			tname: "compare register >= 4-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{28, 76, 230, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_GTE, []byte{29, 76, 230, 0}),
			res:   false,
		},
		// 8-byte data comparisons.
		{
			tname: "compare register == 8-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{0, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, []byte{0, 0, 0, 0, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register == 8-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{1, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, []byte{0, 0, 0, 0, 0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare register != 8-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{1, 7, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_NEQ, []byte{1, 98, 0, 56, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register != 8-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{1, 98, 0, 56, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_NEQ, []byte{1, 98, 0, 56, 0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare register < 8-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{29, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_LT, []byte{100, 0, 0, 0, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register < 8-byte data, false eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{100, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_LT, []byte{100, 0, 0, 0, 0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare register < 8-byte data, false gt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{200, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_LT, []byte{100, 0, 0, 0, 0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare register > 8-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{29, 76, 230, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_GT, []byte{0, 0, 0, 1, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register > 8-byte data, false eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{29, 76, 230, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_GT, []byte{29, 76, 230, 0, 0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare register > 8-byte data, false lt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{28, 76, 230, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_GT, []byte{29, 76, 230, 0, 0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare register <= 8-byte data, true lt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{29, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_LTE, []byte{100, 0, 0, 0, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register <= 8-byte data, true eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{100, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_LTE, []byte{100, 0, 0, 0, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register <= 8-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{200, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_LTE, []byte{100, 0, 0, 0, 0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare register >= 8-byte data, true gt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{30, 0, 0, 1, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_GTE, []byte{29, 76, 230, 0, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register >= 8-byte data, true eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{29, 76, 230, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_GTE, []byte{29, 76, 230, 0, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register >= 8-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{28, 76, 230, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_GTE, []byte{29, 76, 230, 0, 0, 0, 0, 0}),
			res:   false,
		},
		// 12-byte data comparisons.
		{
			tname: "compare register == 12-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register == 12-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare register != 12-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_NEQ, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}),
			res:   true,
		},
		{
			tname: "compare register != 12-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_NEQ, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}),
			res:   false,
		},
		{
			tname: "compare register < 12-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{0x0a, 0x00, 0x01, 0x1f, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_LT, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}),
			res:   true,
		},
		{
			tname: "compare register < 12-byte data, false eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_LT, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}),
			res:   false,
		},
		{
			tname: "compare register < 12-byte data, false gt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0x0a, 0x00, 0x01, 0x21, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_LT, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}),
			res:   false,
		},
		{
			tname: "compare register > 12-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{0x0a, 0x00, 0x01, 0x21, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_GT, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}),
			res:   true,
		},
		{
			tname: "compare register > 12-byte data, false eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_GT, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}),
			res:   false,
		},
		{
			tname: "compare register > 12-byte data, false lt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{0x0a, 0x00, 0x01, 0x1f, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_GT, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}),
			res:   false,
		},
		{
			tname: "compare register <= 12-byte data, true lt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_LTE, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}),
			res:   true,
		},
		{
			tname: "compare register <= 12-byte data, true eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_LTE, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}),
			res:   true,
		},
		{
			tname: "compare register <= 12-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{0xaa, 0xaa, 0xaa, 0x20, 0xaa, 0xaa, 0xaa, 0x13, 0xc0, 0x09, 0x00, 0x00}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_LTE, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}),
			res:   false,
		},
		{
			tname: "compare register >= 12-byte data, true gt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{0xaa, 0xaa, 0xaa, 0x20, 0xaa, 0xaa, 0xaa, 0x13, 0xc0, 0x09, 0x00, 0x00}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_GTE, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}),
			res:   true,
		},
		{
			tname: "compare register >= 12-byte data, true eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0xab, 0xbc, 0xcd, 0xde, 0xef, 0x00, 0x01, 0x12, 0x23, 0x34, 0x45, 0x56}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_GTE, []byte{0xab, 0xbc, 0xcd, 0xde, 0xef, 0x00, 0x01, 0x12, 0x23, 0x34, 0x45, 0x56}),
			res:   true,
		},
		{
			tname: "compare register >= 12-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{0x0a, 0x00, 0x01, 0x19, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_GTE, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00}),
			res:   false,
		},
		// 16-byte data comparisons.
		{
			tname: "compare register == 16-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare register == 16-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}),
			res:   false,
		},
		{
			tname: "compare register != 16-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_NEQ, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}),
			res:   true,
		},
		{
			tname: "compare register != 16-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_NEQ, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}),
			res:   false,
		},
		{
			tname: "compare register < 16-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{0x0a, 0x00, 0x01, 0x1f, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0xaa}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_LT, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}),
			res:   true,
		},
		{
			tname: "compare register < 16-byte data, false eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_LT, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}),
			res:   false,
		},
		{
			tname: "compare register < 16-byte data, false gt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0x0a, 0x00, 0x01, 0x21, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0xaa}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_LT, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}),
			res:   false,
		},
		{
			tname: "compare register > 16-byte data, true",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{0x0a, 0x00, 0x01, 0x21, 0xaa, 0xaa, 0xaa, 0xaa, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_GT, []byte{0x0a, 0x00, 0x01, 0x20, 0xcc, 0xcc, 0xcc, 0xcc, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}),
			res:   true,
		},
		{
			tname: "compare register > 16-byte data, false eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_GT, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}),
			res:   false,
		},
		{
			tname: "compare register > 16-byte data, false lt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{0x0a, 0x00, 0x01, 0x1f, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x90}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_GT, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}),
			res:   false,
		},
		{
			tname: "compare register <= 16-byte data, true lt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x86}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_LTE, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}),
			res:   true,
		},
		{
			tname: "compare register <= 16-byte data, true eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_LTE, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}),
			res:   true,
		},
		{
			tname: "compare register <= 16-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0xaa, 0x00, 0x0b, 0x13, 0x6a, 0x88}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_LTE, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}),
			res:   false,
		},
		{
			tname: "compare register >= 16-byte data, true gt",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{0xaa, 0xaa, 0xaa, 0x20, 0xaa, 0xaa, 0xaa, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_GTE, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}),
			res:   true,
		},
		{
			tname: "compare register >= 16-byte data, true eq",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0xab, 0xbc, 0xcd, 0xde, 0xef, 0x00, 0x01, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x90}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_GTE, []byte{0xab, 0xbc, 0xcd, 0xde, 0xef, 0x00, 0x01, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x90}),
			res:   true,
		},
		{
			tname: "compare register >= 16-byte data, false",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0a, 0x13, 0x6a, 0x85}, Verdict{}),
			op2:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_GTE, []byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87}),
			res:   false,
		},
		// Empty register comparisons.
		{
			tname: "compare empty 4-byte register, true",
			op1:   mustCreateComparison(t, linux.NFT_REG32_10, linux.NFT_CMP_EQ, []byte{0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare empty 4-byte register, false",
			op1:   mustCreateComparison(t, linux.NFT_REG32_11, linux.NFT_CMP_EQ, []byte{1, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare empty 8-byte register, true",
			op1:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_NEQ, []byte{1, 1, 1, 1, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare empty 8-byte register, false",
			op1:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_GT, []byte{1, 1, 1, 1, 0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare empty 12-byte register, true",
			op1:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_LTE, []byte{1, 1, 1, 1, 0, 0, 0, 0, 8, 9, 10, 11}),
			res:   true,
		},
		{
			tname: "compare empty 12-byte register, false",
			op1:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_NEQ, []byte{0, 0, 0, 0, 0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "compare empty 16-byte register, true",
			op1:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_LT, []byte{1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "compare empty 16-byte register, false",
			op1:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_GTE, []byte{1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
			res:   false,
		},
	} {
		t.Run(test.tname, func(t *testing.T) {
			// Sets up an NFTables object with a single table, chain, and rule.
			nf := newNFTablesStd()
			tab, err := nf.AddTable(arbitraryFamily, "test", false)
			if err != nil {
				t.Fatalf("unexpected error for AddTable: %v", err)
			}
			bc, err := nf.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept, "test chain", false, 0, nil, linux.NF_ACCEPT)
			if err != nil {
				t.Fatalf("unexpected error for AddChain: %v", err)
			}
			rule := &Rule{}

			// Adds testing operations.
			if test.op1 != nil {
				rule.addOperation(test.op1)
			}
			if test.op2 != nil {
				rule.addOperation(test.op2)
			}

			// Add an operation that drops. This is what the final verdict should be
			// if all the comparisons are true (res = true).
			rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, 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 and checks verdict.
			pkt := makeArbitraryIPv4Packet()
			v, err := nf.EvaluateHook(stack.IP, arbitraryHook, pkt, nil /*route*/)
			if err != nil {
				t.Fatalf("unexpected error for EvaluateHook: %v", err)
			}
			// If all comparisons are true, the packet will get to the end of the rule
			// and the last operation above will set the final verdict to oppose the
			// base chain policy. If any comparison is false, the comparison operation
			// will break from the rule and the final verdict will default to the base
			// chain policy.
			if test.res {
				if v.Code != VC(linux.NF_DROP) {
					t.Fatalf("expected verdict Drop for %t result, got %v", test.res, v)
				}
			} else {
				if v.Code != VC(linux.NF_ACCEPT) {
					t.Fatalf("expected base chain policy verdict Accept for %t result, got %v", test.res, v)
				}
			}
		})
	}
}

// TestEvaluateRanged tests that the Ranged operation correctly checks that the
// the data in the source register is within the specified inclusive range.
// Note: Relies on expected behavior of the Immediate operation.
func TestEvaluateRanged(t *testing.T) {
	for _, test := range []struct {
		tname string
		op1   operation // Immediate operation that sets the source register.
		op2   operation // Ranged operation to test.
		res   bool      // should be true if we reach end of the rule (no breaks)
	}{
		// 4-byte ranges, alternates between 4-byte and 16-byte registers.
		{
			tname: "4-byte data eq within range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(1, 4), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_1, linux.NFT_RANGE_EQ, numToBE(0, 4), numToBE(5, 4)),
			res:   true,
		},
		{
			tname: "4-byte data neq within range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(4, 4), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_1, linux.NFT_RANGE_NEQ, numToBE(0, 4), numToBE(5, 4)),
			res:   false,
		},
		{
			tname: "4-byte data eq below range",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_00, numToBE(1, 4), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG32_00, linux.NFT_RANGE_EQ, numToBE(3, 4), numToBE(5, 4)),
			res:   false,
		},
		{
			tname: "4-byte data neq below range",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_00, numToBE(1, 4), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG32_00, linux.NFT_RANGE_NEQ, numToBE(3, 4), numToBE(5, 4)),
			res:   true,
		},
		{
			tname: "4-byte data eq above range",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_00, numToBE(954, 4), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG32_00, linux.NFT_RANGE_EQ, numToBE(3, 4), numToBE(5, 4)),
			res:   false,
		},
		{
			tname: "4-byte data neq above range",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_00, numToBE(954, 4), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG32_00, linux.NFT_RANGE_NEQ, numToBE(3, 4), numToBE(5, 4)),
			res:   true,
		},
		{
			tname: "4-byte data eq on lower bound",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_00, numToBE(1, 4), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG32_00, linux.NFT_RANGE_EQ, numToBE(1, 4), numToBE(5, 4)),
			res:   true,
		},
		{
			tname: "4-byte data neq on lower bound",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_00, numToBE(1, 4), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG32_00, linux.NFT_RANGE_NEQ, numToBE(1, 4), numToBE(5, 4)),
			res:   false,
		},
		{
			tname: "4-byte data eq on upper bound",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, numToBE(100, 4), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_4, linux.NFT_RANGE_EQ, numToBE(4, 4), numToBE(100, 4)),
			res:   true,
		},
		{
			tname: "4-byte data neq on upper bound",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, numToBE(100, 4), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_4, linux.NFT_RANGE_NEQ, numToBE(4, 4), numToBE(100, 4)),
			res:   false,
		},
		{
			tname: "4-byte data eq on point range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, numToBE(123, 4), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_4, linux.NFT_RANGE_EQ, numToBE(123, 4), numToBE(123, 4)),
			res:   true,
		},
		{
			tname: "4-byte data neq on point range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, numToBE(123, 4), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_4, linux.NFT_RANGE_NEQ, numToBE(123, 4), numToBE(123, 4)),
			res:   false,
		},
		// 8-byte ranges.
		{
			tname: "8-byte data eq within range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(1, 8), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_1, linux.NFT_RANGE_EQ, numToBE(0, 8), numToBE(5, 8)),
			res:   true,
		},
		{
			tname: "8-byte data neq within range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, numToBE(4, 8), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_2, linux.NFT_RANGE_NEQ, numToBE(0, 8), numToBE(5, 8)),
			res:   false,
		},
		{
			tname: "8-byte data eq below range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, numToBE(1, 8), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_3, linux.NFT_RANGE_EQ, numToBE(3, 8), numToBE(5, 8)),
			res:   false,
		},
		{
			tname: "8-byte data neq below range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, numToBE(1, 8), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_4, linux.NFT_RANGE_NEQ, numToBE(3, 8), numToBE(5, 8)),
			res:   true,
		},
		{
			tname: "8-byte data eq above range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(954, 8), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_1, linux.NFT_RANGE_EQ, numToBE(3, 8), numToBE(5, 8)),
			res:   false,
		},
		{
			tname: "8-byte data neq above range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, numToBE(954, 8), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_2, linux.NFT_RANGE_NEQ, numToBE(3, 8), numToBE(5, 8)),
			res:   true,
		},
		{
			tname: "8-byte data eq on lower bound",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, numToBE(1, 8), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_3, linux.NFT_RANGE_EQ, numToBE(1, 8), numToBE(5, 8)),
			res:   true,
		},
		{
			tname: "8-byte data neq on lower bound",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, numToBE(1, 8), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_4, linux.NFT_RANGE_NEQ, numToBE(1, 8), numToBE(5, 8)),
			res:   false,
		},
		{
			tname: "8-byte data eq on upper bound",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, numToBE(100, 8), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_4, linux.NFT_RANGE_EQ, numToBE(4, 8), numToBE(100, 8)),
			res:   true,
		},
		{
			tname: "8-byte data neq on upper bound",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, numToBE(100, 8), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_4, linux.NFT_RANGE_NEQ, numToBE(4, 8), numToBE(100, 8)),
			res:   false,
		},
		{
			tname: "8-byte data eq on point range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(123, 8), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_1, linux.NFT_RANGE_EQ, numToBE(123, 8), numToBE(123, 8)),
			res:   true,
		},
		{
			tname: "8-byte data neq on point range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, numToBE(123, 8), Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_3, linux.NFT_RANGE_NEQ, numToBE(123, 8), numToBE(123, 8)),
			res:   false,
		},
		// simpler 16-byte ranges.
		{
			tname: "16-byte data eq within range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, []byte{1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_1, linux.NFT_RANGE_EQ, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, []byte{5, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}),
			res:   true,
		},
		{
			tname: "16-byte data neq within range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_2, []byte{1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_2, linux.NFT_RANGE_NEQ, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, []byte{5, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "16-byte data eq outside range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0x45, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_3, linux.NFT_RANGE_EQ, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, []byte{5, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}),
			res:   false,
		},
		{
			tname: "16-byte data neq outside range",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{0x45, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Verdict{}),
			op2:   mustCreateRanged(t, linux.NFT_REG_4, linux.NFT_RANGE_NEQ, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, []byte{5, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}),
			res:   true,
		},
	} {
		t.Run(test.tname, func(t *testing.T) {
			// Sets up an NFTables object with a single table, chain, and rule.
			nf := newNFTablesStd()
			tab, err := nf.AddTable(arbitraryFamily, "test", false)
			if err != nil {
				t.Fatalf("unexpected error for AddTable: %v", err)
			}
			bc, err := nf.AddChainToTable(tab, "test", arbitraryInfoPolicyAccept, "" /* comment */, true /* errorOnDuplicate */, 0 /* chainFlags */, nil /* udata */, linux.NF_ACCEPT)
			if err != nil {
				t.Fatalf("unexpected error for AddChain: %v", err)
			}
			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 comparison is true.
			rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, 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 and checks verdict.
			pkt := makeArbitraryIPv4Packet()
			v, err := nf.EvaluateHook(stack.IP, arbitraryHook, pkt, nil /* route */)
			if err != nil {
				t.Fatalf("unexpected error for EvaluateHook: %v", err)
			}
			if test.res {
				if v.Code != VC(linux.NF_DROP) {
					t.Fatalf("expected verdict Drop for %t result, got %v", test.res, v)
				}
			} else {
				if v.Code != VC(linux.NF_ACCEPT) {
					t.Fatalf("expected base chain policy verdict Accept for %t result, got %v", test.res, v)
				}
			}
		})
	}
}

// TestEvaluatePayloadLoad tests that the Payload Load operation correctly loads
// the specified payload 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.
// TODO(b/339691111): Add tests for VLAN, ARP, ICMP, ICMPv6, IGMP, UDP headers.
func TestEvaluatePayloadLoad(t *testing.T) {
	// Sets testing packets.
	ethernetPacket := makeEthernetPacket(0, arbitraryEthernetFields())
	ipv4Packet := makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields())
	ipv6Packet := makeIPv6Packet(header.IPv6MinimumSize, arbitraryIPv6Fields())
	tcpPacket := makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields())

	for _, test := range []struct {
		tname string
		pkt   *stack.PacketBuffer
		op1   operation // Payload Load operation to test.
		op2   operation // Comparison operation to check resulting data in register,
		// nil if expecting a break during evaluation.
	}{
		// Ethernet header expression commands.
		{ // cmd: add rule ip tab ch ether saddr 02:02:03:04:05:06
			tname: "load ethernet header source address",
			pkt:   ethernetPacket,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_LL_HEADER, ethSrcAddrOffset, ethSrcAddrLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, arbitraryLinkAddrB[:]),
		},
		{ // cmd: add rule ip tab ch ether daddr 02:02:03:04:05:07
			tname: "load ethernet header destination address",
			pkt:   ethernetPacket,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_LL_HEADER, ethDstAddrOffset, ethDstAddrLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, arbitraryLinkAddrB2[:]),
		},
		{ // cmd: add rule ip tab ch ether type ip
			tname: "load ethernet header type",
			pkt:   ethernetPacket,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_LL_HEADER, ethTypeOffset, ethTypeLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE(int(arbitraryEthernetType), ethTypeLen)),
		},

		// IPv4 header expression commands.
		{ // cmd: add rule ip tab ch ip length 20
			tname: "load ipv4 header length",
			pkt:   ipv4Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4LengthOffset, ipv4LengthLen, linux.NFT_REG32_01),
			op2:   mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ, numToBE(header.IPv4MinimumSize, ipv4LengthLen)),
		},
		{ // cmd: add rule ip tab ch ip id 3
			tname: "load ipv4 header ip id",
			pkt:   ipv4Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4IDOffset, ipv4IDLen, linux.NFT_REG32_01),
			op2:   mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ, numToBE(arbitraryHeaderID, ipv4IDLen)),
		},
		{ // cmd: add rule ip tab ch ip frag-off 0
			tname: "load ipv4 header fragment offset",
			pkt:   ipv4Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4FragOffOffset, ipv4FragOffLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE(0, ipv4FragOffLen)),
		},
		// Though the packet is fragmented, there should be no issue because we are
		// changing data within the network header.
		{ // cmd: add rule ip tab ch ip frag-off 2 (16 bytes)
			tname: "load ipv4 header fragment offset non zero for fragmented packet",
			pkt:   makeIPv4Packet(header.IPv4MinimumSize, fragmentedIPv4Fields()),
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, 6, 2, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE(arbitraryNonZeroFragmentOffset/8, ipv4FragOffLen)),
			// we divide by 8 because the fragment offset is in units of 8 bytes,
			// which is encoded into the packet in IPv4.Encode().
		},
		{ // cmd: add rule ip tab ch ip ttl 64
			tname: "load ipv4 header time to live",
			pkt:   ipv4Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4TTLOffset, ipv4TTLLen, linux.NFT_REG32_01),
			op2:   mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ, numToBE(arbitraryTimeToLive, ipv4TTLLen)),
		},
		{ // cmd: add rule ip tab ch ip protocol tcp
			tname: "load ipv4 header protocol",
			pkt:   ipv4Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4ProtocolOffset, ipv4ProtocolLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE(int(tcpTransportProtocol), ipv4ProtocolLen)),
		},
		{ // cmd: add rule ip tab ch ip saddr 192.168.1.1
			tname: "load ipv4 header source address",
			pkt:   ipv4Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4SrcAddrOffset, ipv4SrcAddrLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, arbitraryIPv4AddrB[:]),
		},
		{ // cmd: add rule ip tab ch ip daddr 192.168.1.9
			tname: "load ipv4 header destination address",
			pkt:   ipv4Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4DstAddrOffset, ipv4DstAddrLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, arbitraryIPv4AddrB2[:]),
		},
		{ // cmd: add rule ip tab ch ip checksum __
			tname: "load ipv4 header checksum",
			pkt:   ipv4Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4ChecksumOffset, ipv4ChecksumLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE(int(header.IPv4(ipv4Packet.NetworkHeader().Slice()).Checksum()), ipv4ChecksumLen)),
		},

		// IPv6 header expression commands.
		{ // cmd: add rule ip6 tab ch ip6 length 0
			tname: "load ipv6 header length",
			pkt:   ipv6Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6LengthOffset, ipv6LengthLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE(0, ipv6LengthLen)),
		},
		{ // cmd: add rule ip6 tab ch ip6 nexthdr tcp
			tname: "load ipv6 header next header",
			pkt:   ipv6Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6NextHdrOffset, ipv6NextHdrLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE(int(tcpTransportProtocol), ipv6NextHdrLen)),
		},
		{ // cmd: add rule ip6 tab ch ip6 hoplimit 64
			tname: "load ipv6 header hop limit",
			pkt:   ipv6Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6HopLimitOffset, ipv6HopLimitLen, linux.NFT_REG32_01),
			op2:   mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ, numToBE(arbitraryTimeToLive, ipv6HopLimitLen)),
		},
		{ // cmd: add rule ip6 tab ch ip6 saddr 2001:db8:85a3::aa
			tname: "load ipv6 header source address",
			pkt:   ipv6Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6SrcAddrOffset, ipv6SrcAddrLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, arbitraryIPv6AddrB[:]),
		},
		{ // cmd: add rule ip6 tab ch ip6 daddr 2001:db8:85a3::bb
			tname: "load ipv6 header destination address",
			pkt:   ipv6Packet,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6DstAddrOffset, ipv6DstAddrLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, arbitraryIPv6AddrB2[:]),
		},

		// TCP header expression commands.
		// Since we are changing data within the transport header with a fragmented
		// IPv4 packet, this can be problematic, so the evaluation should break.
		{
			tname: "load for transport header with a fragmented ipv4 packet",
			pkt:   makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, fragmentedIPv4Fields(), arbitraryTCPFields()),
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, 0, 2, linux.NFT_REG_1),
			op2:   nil,
		},
		{ // cmd: add rule ip tab ch tcp sport 12345
			tname: "load tcp header source port",
			pkt:   tcpPacket,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpSrcPortOffset, tcpSrcPortLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE(arbitraryPort, tcpSrcPortLen)),
		},
		{ // cmd: add rule ip tab ch tcp dport 80
			tname: "load tcp header destination port",
			pkt:   tcpPacket,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpDstPortOffset, tcpDstPortLen, linux.NFT_REG32_01),
			op2:   mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ, numToBE(arbitraryPort2, tcpDstPortLen)),
		},
		{
			// cmd: add rule ip tab ch tcp sequence 32
			tname: "load tcp header sequence number",
			pkt:   tcpPacket,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpSeqNumOffset, tcpSeqNumLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE(tcpSeqNum, tcpSeqNumLen)),
		},
		{ // cmd: add rule ip tab ch tcp ackseq 165
			tname: "load tcp header acknowledgement sequence number",
			pkt:   tcpPacket,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpAckNumOffset, tcpAckNumLen, linux.NFT_REG32_01),
			op2:   mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ, numToBE(tcpAckNum, tcpAckNumLen)),
		},
		{ // cmd: add rule ip tab ch tcp window 65535
			tname: "load tcp header window",
			pkt:   tcpPacket,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpWindowOffset, tcpWindowLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE(tcpWinSize, tcpWindowLen)),
		},
		{ // cmd: add rule ip tab ch tcp checksum __
			tname: "load tcp header checksum",
			pkt:   tcpPacket,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpChecksumOffset, tcpChecksumLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE(int(header.TCP(tcpPacket.TransportHeader().Slice()).Checksum()), tcpChecksumLen)),
		},
		{ // cmd: add rule ip tab ch tcp urgptr 0
			tname: "load tcp header urgent pointer",
			pkt:   tcpPacket,
			op1:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpUrgPtrOffset, tcpUrgPtrLen, linux.NFT_REG_1),
			op2:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE(tcpUrgentPointer, tcpUrgPtrLen)),
		},
	} {
		t.Run(test.tname, func(t *testing.T) {
			// Sets up an NFTables object with a single table, chain, and rule.
			nf := newNFTablesStd()
			tab, err := nf.AddTable(arbitraryFamily, "test", false)
			if err != nil {
				t.Fatalf("unexpected error for AddTable: %v", err)
			}
			bc, err := nf.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept, "" /* comment */, false /* errorOnDuplicate */, 0 /* chainFlags */, nil /* udata */, linux.NF_ACCEPT)
			if err != nil {
				t.Fatalf("unexpected error for AddChain: %v", err)
			}
			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, nil, 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(getAddrFamilyOrDefault(test.pkt, arbitraryFamily), arbitraryHook, test.pkt, nil /* route */)
			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)
				}
			}
		})
	}
}

// TestEvaluatePayloadSet tests that the Payload Set operation correctly sets
// the payload from the source register and updates the packet checksums.
// The nft binary commands used to generate these are stated above each test.
// All commands should be preceded by nft --debug=netlink.
// TODO(b/339691111): Add tests for VLAN, ARP, ICMP, ICMPv6, IGMP, UDP headers.
func TestEvaluatePayloadSet(t *testing.T) {
	for _, test := range []struct {
		tname  string
		pkt    *stack.PacketBuffer
		outPkt *stack.PacketBuffer // nil if expecting a break during evaluation.
		op1    operation           // Immediate operation to load source register.
		op2    operation           // Payload Set operation to test.
	}{
		// Ethernet header statement commands.
		{ // cmd: add rule ip tab ch ether saddr set 02:02:03:04:05:07
			tname: "set ethernet header source address",
			pkt:   makeEthernetPacket(0, arbitraryEthernetFields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryEthernetFields()
				fields.SrcAddr = arbitraryLinkAddr2
				fields.Type = header.IPv4ProtocolNumber
				return makeEthernetPacket(0, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, arbitraryLinkAddrB2[:], Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_LL_HEADER, ethSrcAddrOffset, ethSrcAddrLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 0, 0x0),
		},
		{ // cmd: add rule ip tab ch ether daddr set 02:02:03:04:05:06
			tname: "set ethernet header destination address",
			pkt:   makeEthernetPacket(0, arbitraryEthernetFields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryEthernetFields()
				fields.DstAddr = arbitraryLinkAddr
				return makeEthernetPacket(0, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_2, arbitraryLinkAddrB[:], Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_LL_HEADER, ethDstAddrOffset, ethDstAddrLen, linux.NFT_REG_2, linux.NFT_PAYLOAD_CSUM_NONE, 0, 0x0),
		},
		{ // cmd: add rule ip tab ch ether type set ip6
			tname: "set ethernet header type",
			pkt:   makeEthernetPacket(0, arbitraryEthernetFields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryEthernetFields()
				fields.Type = header.IPv6ProtocolNumber
				return makeEthernetPacket(0, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(int(header.IPv6ProtocolNumber), ethTypeLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_LL_HEADER, ethTypeOffset, ethTypeLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 0, 0x0),
		},

		// IPv4 header statement commands.
		{ // cmd: add rule ip tab ch ip length set 30
			tname: "set ipv4 header length",
			pkt:   makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv4Fields()
				fields.TotalLength = uint16(30)
				return makeIPv4Packet(header.IPv4MinimumSize, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(30, ipv4LengthLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4LengthOffset, ipv4LengthLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 10, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
		},
		{ // cmd: add rule ip tab ch ip id set 12345
			tname: "set ipv4 header ip id",
			pkt:   makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv4Fields()
				fields.ID = uint16(12345)
				return makeIPv4Packet(header.IPv4MinimumSize, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(12345, ipv4IDLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4IDOffset, ipv4IDLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 10, 0x0),
		},
		// Note: Fragment offsets are divided by 8 because they are in units of 8
		// bytes, which is encoded into the packet in IPv4.Encode().
		{ // cmd: add rule ip tab ch ip frag-off set 2 (16 bytes)
			tname:  "set ipv4 header fragment offset, set fragment on",
			pkt:    makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields()),
			outPkt: makeIPv4Packet(header.IPv4MinimumSize, fragmentedIPv4Fields()),
			op1:    mustCreateImmediate(t, linux.NFT_REG_1, numToBE(arbitraryNonZeroFragmentOffset/8, ipv4FragOffLen), Verdict{}),
			op2:    mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4FragOffOffset, ipv4FragOffLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 10, 0x0),
		},
		{ // cmd: add rule ip tab ch ip frag-off set 0
			tname:  "set ipv4 header fragment offset, set fragment off for fragmented packet",
			pkt:    makeIPv4Packet(header.IPv4MinimumSize, fragmentedIPv4Fields()),
			outPkt: makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields()),
			op1:    mustCreateImmediate(t, linux.NFT_REG_1, numToBE(0, ipv4FragOffLen), Verdict{}),
			op2:    mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4FragOffOffset, ipv4FragOffLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 10, 0x0),
		},
		{ // cmd: add rule ip tab ch ip frag-off set 10 (80 bytes)
			tname: "set ipv4 header fragment offset, change fragment offset for fragmented packet",
			pkt:   makeIPv4Packet(header.IPv4MinimumSize, fragmentedIPv4Fields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv4Fields()
				fields.FragmentOffset = uint16(10 * 8)
				return makeIPv4Packet(header.IPv4MinimumSize, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(10, ipv4FragOffLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4FragOffOffset, ipv4FragOffLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 10, 0x0),
		},
		{ // cmd: add rule ip tab ch ip ttl set 128
			tname: "set ipv4 time to live",
			pkt:   makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv4Fields()
				fields.TTL = uint8(128)
				return makeIPv4Packet(header.IPv4MinimumSize, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG32_01, numToBE(128, ipv4TTLLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4TTLOffset, ipv4TTLLen, linux.NFT_REG32_01, linux.NFT_PAYLOAD_CSUM_INET, 10, 0x0),
		},
		{ // cmd: add rule ip tab ch ip saddr set 192.168.1.9
			tname: "set ipv4 header source address",
			pkt:   makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv4Fields()
				fields.SrcAddr = tcpip.AddrFrom4(arbitraryIPv4AddrB2)
				return makeIPv4Packet(header.IPv4MinimumSize, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, arbitraryIPv4AddrB2[:], Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4SrcAddrOffset, ipv4SrcAddrLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 10, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
		},
		{ // cmd: add rule ip tab ch ip daddr set 192.168.1.1
			tname: "set ipv4 header destination address",
			pkt:   makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv4Fields()
				fields.DstAddr = tcpip.AddrFrom4(arbitraryIPv4AddrB)
				return makeIPv4Packet(header.IPv4MinimumSize, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_4, arbitraryIPv4AddrB[:], Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4DstAddrOffset, ipv4DstAddrLen, linux.NFT_REG_4, linux.NFT_PAYLOAD_CSUM_INET, 10, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
		},
		{ // cmd: add rule ip tab ch ip checksum set 6060
			tname: "set ipv4 header checksum",
			pkt:   makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields()),
			outPkt: func() *stack.PacketBuffer {
				pkt := makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields())
				pkt.Network().SetChecksum(6060)
				return pkt
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(6060, ipv4ChecksumLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4ChecksumOffset, ipv4ChecksumLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 10, 0x0),
		},

		// IPv6 header statement commands.
		{ // cmd: add rule ip6 tab ch ip6 length set 232
			tname: "set ipv6 header length",
			pkt:   makeIPv6Packet(header.IPv6MinimumSize, arbitraryIPv6Fields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv6Fields()
				fields.PayloadLength = uint16(232)
				return makeIPv6Packet(header.IPv6MinimumSize, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(232, ipv6LengthLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6LengthOffset, ipv6LengthLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 0, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
		},
		{ // cmd: add rule ip6 tab ch ip6 hoplimit set 54
			tname: "set ipv6 header hop limit",
			pkt:   makeIPv6Packet(header.IPv6MinimumSize, arbitraryIPv6Fields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv6Fields()
				fields.HopLimit = uint8(54)
				return makeIPv6Packet(header.IPv6MinimumSize, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(54, ipv6HopLimitLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6HopLimitOffset, ipv6HopLimitLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 0, 0x0),
		},
		{ // cmd: add rule ip6 tab ch ip6 saddr set 2001:db8:85a3::bb
			tname: "set ipv6 header source address",
			pkt:   makeIPv6Packet(header.IPv6MinimumSize, arbitraryIPv6Fields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv6Fields()
				fields.SrcAddr = tcpip.AddrFrom16(arbitraryIPv6AddrB2)
				return makeIPv6Packet(header.IPv6MinimumSize, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, arbitraryIPv6AddrB2[:], Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6SrcAddrOffset, ipv6SrcAddrLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 0, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
		},
		{ // cmd: add rule ip6 tab ch ip6 daddr set 2001:db8:85a3::aa
			tname: "set ipv6 header destination address",
			pkt:   makeIPv6Packet(header.IPv6MinimumSize, arbitraryIPv6Fields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv6Fields()
				fields.DstAddr = tcpip.AddrFrom16(arbitraryIPv6AddrB)
				return makeIPv6Packet(header.IPv6MinimumSize, fields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_3, arbitraryIPv6AddrB[:], Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6DstAddrOffset, ipv6DstAddrLen, linux.NFT_REG_3, linux.NFT_PAYLOAD_CSUM_NONE, 0, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
		},

		// TCP with IPv4 header statement commands.
		// TCP set commands.
		{
			// Since we change data within the transport header with a fragmented
			// IPv4 packet, this can be problematic, so the evaluation should break.
			tname:  "set for transport header with a fragmented ipv4 packet",
			pkt:    makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, fragmentedIPv4Fields(), arbitraryTCPFields()),
			outPkt: nil,
			op1:    mustCreateImmediate(t, linux.NFT_REG_1, numToBE(arbitraryPort, tcpSrcPortLen), Verdict{}),
			op2:    mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpSrcPortOffset, tcpSrcPortLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp sport set 80
			tname: "set tcp header with ipv4 source port",
			pkt:   makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				tcpFields := arbitraryTCPFields()
				tcpFields.SrcPort = arbitraryPort2
				return makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), tcpFields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(arbitraryPort2, tcpSrcPortLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpSrcPortOffset, tcpSrcPortLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp dport set 12345
			tname: "set tcp header with ipv4 destination port",
			pkt:   makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				tcpFields := arbitraryTCPFields()
				tcpFields.DstPort = arbitraryPort
				return makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), tcpFields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(arbitraryPort, tcpDstPortLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpDstPortOffset, tcpDstPortLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp sequence set 33
			tname: "set tcp header with ipv4 sequence number",
			pkt:   makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				tcpFields := arbitraryTCPFields()
				tcpFields.SeqNum = uint32(33)
				return makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), tcpFields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(33, tcpSeqNumLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpSeqNumOffset, tcpSeqNumLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp ackseq set 245
			tname: "set tcp header with ipv4 acknowledgement sequence number",
			pkt:   makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				tcpFields := arbitraryTCPFields()
				tcpFields.AckNum = uint32(245)
				return makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), tcpFields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(245, tcpAckNumLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpAckNumOffset, tcpAckNumLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp window set 91
			tname: "set tcp header with ipv4 window",
			pkt:   makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				tcpFields := arbitraryTCPFields()
				tcpFields.WindowSize = 91
				return makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), tcpFields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(91, tcpWindowLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpWindowOffset, tcpWindowLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp checksum set 7654
			tname: "set tcp header with ipv4 checksum",
			pkt:   makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				pkt := makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields())
				tcpHdr := header.TCP(pkt.TransportHeader().Slice())
				tcpHdr.SetChecksum(7654)
				return pkt
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(7654, tcpChecksumLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpChecksumOffset, tcpChecksumLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp urgptr set 40
			tname: "set tcp header with ipv4 urgent pointer",
			pkt:   makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				tcpFields := arbitraryTCPFields()
				tcpFields.UrgentPointer = 40
				return makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), tcpFields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(40, tcpUrgPtrLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpUrgPtrOffset, tcpUrgPtrLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		// IPv4 set commands.
		{ // cmd: add rule ip tab ch ip id set 12345
			tname: "set ipv4 header with tcp ip id",
			pkt:   makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				ipFields := arbitraryIPv4Fields()
				ipFields.ID = uint16(12345)
				return makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, ipFields, arbitraryTCPFields())
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(12345, ipv4IDLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4IDOffset, ipv4IDLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 10, 0x0),
		},
		{ // cmd: add rule ip tab ch ip ttl set 128
			tname: "set ipv4 time to live",
			pkt:   makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				ipFields := arbitraryIPv4Fields()
				ipFields.TTL = uint8(128)
				return makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, ipFields, arbitraryTCPFields())
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG32_01, numToBE(128, ipv4TTLLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4TTLOffset, ipv4TTLLen, linux.NFT_REG32_01, linux.NFT_PAYLOAD_CSUM_INET, 10, 0x0),
		},
		{ // cmd: add rule ip tab ch ip saddr set 192.168.1.9
			tname: "set ipv4 header with tcp source address",
			pkt:   makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				ipFields := arbitraryIPv4Fields()
				ipFields.SrcAddr = tcpip.AddrFrom4(arbitraryIPv4AddrB2)
				return makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, ipFields, arbitraryTCPFields())
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, arbitraryIPv4AddrB2[:], Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4SrcAddrOffset, ipv4SrcAddrLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 10, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
		},
		{ // cmd: add rule ip tab ch ip daddr set 192.168.1.1
			tname: "set ipv4 header with tcp destination address",
			pkt:   makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, arbitraryIPv4Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				ipFields := arbitraryIPv4Fields()
				ipFields.DstAddr = tcpip.AddrFrom4(arbitraryIPv4AddrB)
				return makeIPv4TCPPacket(header.IPv4MinimumSize+header.TCPMinimumSize, ipFields, arbitraryTCPFields())
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_4, arbitraryIPv4AddrB[:], Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4DstAddrOffset, ipv4DstAddrLen, linux.NFT_REG_4, linux.NFT_PAYLOAD_CSUM_INET, 10, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
		},

		// TCP on IPv6 header statement commands.
		// TCP set commands.
		{ // cmd: add rule ip tab ch tcp sport set 80
			tname: "set tcp header with ipv6 source port",
			pkt:   makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				tcpFields := arbitraryTCPFields()
				tcpFields.SrcPort = arbitraryPort2
				return makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), tcpFields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(arbitraryPort2, tcpSrcPortLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpSrcPortOffset, tcpSrcPortLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp dport set 12345
			tname: "set tcp header with ipv6 destination port",
			pkt:   makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				tcpFields := arbitraryTCPFields()
				tcpFields.DstPort = arbitraryPort
				return makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), tcpFields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(arbitraryPort, tcpDstPortLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpDstPortOffset, tcpDstPortLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp sequence set 33
			tname: "set tcp header with ipv6 sequence number",
			pkt:   makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				tcpFields := arbitraryTCPFields()
				tcpFields.SeqNum = uint32(33)
				return makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), tcpFields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(33, tcpSeqNumLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpSeqNumOffset, tcpSeqNumLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp ackseq set 245
			tname: "set tcp header with ipv6 acknowledgement sequence number",
			pkt:   makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				tcpFields := arbitraryTCPFields()
				tcpFields.AckNum = uint32(245)
				return makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), tcpFields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(245, tcpAckNumLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpAckNumOffset, tcpAckNumLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp window set 91
			tname: "set tcp header with ipv6 window",
			pkt:   makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				tcpFields := arbitraryTCPFields()
				tcpFields.WindowSize = uint16(91)
				return makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), tcpFields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(91, tcpWindowLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpWindowOffset, tcpWindowLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp checksum set 7654
			tname: "set tcp header with ipv6 checksum",
			pkt:   makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				pkt := makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), arbitraryTCPFields())
				tcpHdr := header.TCP(pkt.TransportHeader().Slice())
				tcpHdr.SetChecksum(7654)
				return pkt
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(7654, tcpChecksumLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpChecksumOffset, tcpChecksumLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		{ // cmd: add rule ip tab ch tcp urgptr set 40
			tname: "set tcp header with ipv6 urgent pointer",
			pkt:   makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				tcpFields := arbitraryTCPFields()
				tcpFields.UrgentPointer = uint16(40)
				return makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), tcpFields)
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(40, tcpUrgPtrLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, tcpUrgPtrOffset, tcpUrgPtrLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
		},
		// IPv6 set commands.
		{ // cmd: add rule ip6 tab ch ip6 length set 232
			tname: "set ipv6 header length",
			pkt:   makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv6Fields()
				fields.PayloadLength = uint16(232)
				return makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, fields, arbitraryTCPFields())
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(232, ipv6LengthLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6LengthOffset, ipv6LengthLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 0, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
		},
		{ // cmd: add rule ip6 tab ch ip6 hoplimit set 54
			tname: "set ipv6 header hop limit",
			pkt:   makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv6Fields()
				fields.HopLimit = uint8(54)
				return makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, fields, arbitraryTCPFields())
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, numToBE(54, ipv6HopLimitLen), Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6HopLimitOffset, ipv6HopLimitLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 0, 0x0),
		},
		{ // cmd: add rule ip6 tab ch ip6 saddr set 2001:db8:85a3::bb
			tname: "set ipv6 header source address",
			pkt:   makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv6Fields()
				fields.SrcAddr = tcpip.AddrFrom16(arbitraryIPv6AddrB2)
				return makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, fields, arbitraryTCPFields())
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_1, arbitraryIPv6AddrB2[:], Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6SrcAddrOffset, ipv6SrcAddrLen, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 0, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
		},
		{ // cmd: add rule ip6 tab ch ip6 daddr set 2001:db8:85a3::aa
			tname: "set ipv6 header destination address",
			pkt:   makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, arbitraryIPv6Fields(), arbitraryTCPFields()),
			outPkt: func() *stack.PacketBuffer {
				fields := arbitraryIPv6Fields()
				fields.DstAddr = tcpip.AddrFrom16(arbitraryIPv6AddrB)
				return makeIPv6TCPPacket(header.IPv6MinimumSize+header.TCPMinimumSize, fields, arbitraryTCPFields())
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_3, arbitraryIPv6AddrB[:], Verdict{}),
			op2: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv6DstAddrOffset, ipv6DstAddrLen, linux.NFT_REG_3, linux.NFT_PAYLOAD_CSUM_NONE, 0, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
		},
	} {
		t.Run(test.tname, func(t *testing.T) {
			// Sets up an NFTables object with a single table, chain, and rule.
			nf := newNFTablesStd()
			tab, err := nf.AddTable(arbitraryFamily, "test", false)
			if err != nil {
				t.Fatalf("unexpected error for AddTable: %v", err)
			}
			bc, err := nf.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept /* bcInfo */, "test chain", false, 0 /* chainFlags */, nil /* udata */, linux.NF_ACCEPT)
			if err != nil {
				t.Fatalf("unexpected error for AddChain: %v", err)
			}
			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 payload set evaluation is
			// successful (operation breaks if anything goes wrong).
			rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, 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(getAddrFamilyOrDefault(test.pkt, arbitraryFamily), arbitraryHook, test.pkt, nil /* route */)
			if err != nil {
				t.Fatalf("unexpected error for EvaluateHook: %v", err)
			}

			// Checks for final verdict.
			if test.outPkt == nil {
				// If no output packet is expected, then evaluation 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)
				}
				return
			} else {
				// If an output packet is expected, the evaluation should go until end
				// of rule (no errors/breaks), resulting in Drop as the final verdict.
				if v.Code != VC(linux.NF_DROP) {
					t.Fatalf("expected verdict Drop for successful evaluation, got %v", v)
				}
			}

			// Checks if the packet are equal.
			checkPacketEquality(t, test.outPkt, test.pkt)
		})
	}
}

// TestEvaluateBitwise tests that the Bitwise operation correctly performs the
// appropriate bitwise operation on the source register data and stores the
// result in the destination register.
// Note: Relies on expected behavior of the Immediate and Comparison operation.
func TestEvaluateBitwise(t *testing.T) {
	for _, test := range []struct {
		tname string
		op1   operation // Immediate operation to set source register.
		op2   operation // Bitwise operation to test.
		op3   operation // Comparison operation to validate result.
	}{
		// Bitwise bool operations.
		// cmd: add rule ip filter input ip saddr and _ or _ == 105
		{
			tname: "same 4-byte register with 4-byte data for bitwise bool",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_01, numToBE(4783, 4), Verdict{}),
			op2:   mustCreateBitwiseBool(t, linux.NFT_REG32_01, linux.NFT_REG32_01, numToBE(55, 4), numToBE(78, 4)),
			op3:   mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ, numToBE((4783&55)^78, 4)),
		},
		{
			tname: "same 16-byte register with 4-byte data for bitwise bool",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(4783, 4), Verdict{}),
			op2:   mustCreateBitwiseBool(t, linux.NFT_REG_1, linux.NFT_REG_1, numToBE(55, 4), numToBE(78, 4)),
			op3:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE((4783&55)^78, 4)),
		},
		// cmd: add rule ip filter input ip saddr and 0x11111111 == 285217024
		{
			tname: "dif 4-byte registers with 4-byte data for bitwise bool",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_01, numToBE(400700800, 4), Verdict{}),
			op2:   mustCreateBitwiseBool(t, linux.NFT_REG32_01, linux.NFT_REG32_02, numToBE(0x11111111, 4), numToBE(0, 4)),
			op3:   mustCreateComparison(t, linux.NFT_REG32_02, linux.NFT_CMP_EQ, numToBE(400700800&0x11111111, 4)),
		},
		{
			tname: "dif 16-byte registers with 4-byte data for bitwise bool",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(400700800, 4), Verdict{}),
			op2:   mustCreateBitwiseBool(t, linux.NFT_REG_1, linux.NFT_REG_2, numToBE(0x11111111, 4), numToBE(0, 4)),
			op3:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, numToBE(400700800&0x11111111, 4)),
		},
		// add rule ip filter input ip saddr or 0xff0230ff == 267583535
		{
			tname: "4- and 16-byte registers with 4-byte data for bitwise bool",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_10, numToBE(0, 4), Verdict{}),
			op2:   mustCreateBitwiseBool(t, linux.NFT_REG32_10, linux.NFT_REG_2, numToBE(0x00cffd00, 4), numToBE(0xff3002ff, 4)),
			op3:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, numToBE((0&0x00cffd00)^0xff3002ff, 4)),
		},
		{
			tname: "16- and 4-byte registers with 4-byte data for bitwise bool",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, numToBE(0, 4), Verdict{}),
			op2:   mustCreateBitwiseBool(t, linux.NFT_REG_3, linux.NFT_REG32_05, numToBE(0x00cffd00, 4), numToBE(0xff3002ff, 4)),
			op3:   mustCreateComparison(t, linux.NFT_REG32_05, linux.NFT_CMP_EQ, numToBE((0&0x00cffd00)^0xff3002ff, 4)),
		},
		{
			tname: "8-byte data for bitwise bool",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(0x12345678, 8), Verdict{}),
			op2:   mustCreateBitwiseBool(t, linux.NFT_REG_1, linux.NFT_REG_1, numToBE(0x00cffd00, 8), numToBE(0xff3002ff, 8)),
			op3:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, numToBE((0x12345678&0x00cffd00)^0xff3002ff, 8)),
		},
		{
			tname: "16-byte data for bitwise bool",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, Verdict{}),
			op2:   mustCreateBitwiseBool(t, linux.NFT_REG_4, linux.NFT_REG_2, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, []byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}),
			op3:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, []byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}),
		},
		// Bitwise shift operations.
		// No nft binary commands were observed that directly used shift operations.
		{
			tname: "0 shift left for bitwise lshift",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_01, binary.NativeEndian.AppendUint32(nil, 4783), Verdict{}),
			op2:   mustCreateBitwiseShift(t, linux.NFT_REG32_01, linux.NFT_REG32_01, 4, 0, false),
			op3:   mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ, binary.NativeEndian.AppendUint32(nil, 4783)),
		},
		{
			tname: "0 shift right for bitwise rshift",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, binary.NativeEndian.AppendUint32(nil, 4783), Verdict{}),
			op2:   mustCreateBitwiseShift(t, linux.NFT_REG_1, linux.NFT_REG_1, 4, 0, true),
			op3:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, binary.NativeEndian.AppendUint32(nil, 4783)),
		},
		{
			tname: "1-bit shift left for bitwise lshift",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, binary.NativeEndian.AppendUint32(nil, 4782), Verdict{}),
			op2:   mustCreateBitwiseShift(t, linux.NFT_REG_4, linux.NFT_REG_4, 4, 1, false),
			op3:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, binary.NativeEndian.AppendUint32(nil, 4782<<1)),
		},
		{
			tname: "1-bit shift right for bitwise rshift",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_06, binary.NativeEndian.AppendUint32(nil, 4782), Verdict{}),
			op2:   mustCreateBitwiseShift(t, linux.NFT_REG32_06, linux.NFT_REG32_06, 4, 1, true),
			op3:   mustCreateComparison(t, linux.NFT_REG32_06, linux.NFT_CMP_EQ, binary.NativeEndian.AppendUint32(nil, 4782>>1)),
		},
		{
			tname: "8-bit shift left for bitwise lshift",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, binary.NativeEndian.AppendUint32(nil, 4782), Verdict{}),
			op2:   mustCreateBitwiseShift(t, linux.NFT_REG_4, linux.NFT_REG_4, 4, 8, false),
			op3:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, binary.NativeEndian.AppendUint32(nil, 4782<<8)),
		},
		{
			tname: "8-bit shift right for bitwise rshift",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_06, binary.NativeEndian.AppendUint32(nil, 4782), Verdict{}),
			op2:   mustCreateBitwiseShift(t, linux.NFT_REG32_06, linux.NFT_REG32_06, 4, 8, true),
			op3:   mustCreateComparison(t, linux.NFT_REG32_06, linux.NFT_CMP_EQ, binary.NativeEndian.AppendUint32(nil, 4782>>8)),
		},
		{
			tname: "16-bit shift left for bitwise lshift",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, append(binary.NativeEndian.AppendUint32(nil, 0), binary.NativeEndian.AppendUint32(nil, 0x45678910)...), Verdict{}),
			op2:   mustCreateBitwiseShift(t, linux.NFT_REG_4, linux.NFT_REG_4, 8, 16, false),
			op3:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, append(binary.NativeEndian.AppendUint32(nil, 0x00004567), binary.NativeEndian.AppendUint32(nil, 0x89100000)...)),
		},
		{
			tname: "16-bit shift right for bitwise rshift",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_06, binary.NativeEndian.AppendUint32(nil, 0x45678910), Verdict{}),
			op2:   mustCreateBitwiseShift(t, linux.NFT_REG32_06, linux.NFT_REG32_06, 4, 16, true),
			op3:   mustCreateComparison(t, linux.NFT_REG32_06, linux.NFT_CMP_EQ, binary.NativeEndian.AppendUint32(nil, 0x45678910>>16)),
		},
		{
			tname: "max-bit shift left for bitwise lshift",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_03, binary.NativeEndian.AppendUint32(nil, 0x45678910), Verdict{}),
			op2:   mustCreateBitwiseShift(t, linux.NFT_REG32_03, linux.NFT_REG_2, 4, bitshiftLimit-1, false),
			op3:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, binary.NativeEndian.AppendUint32(nil, 0)), // 0x45678910 << 31 in 32-bit is 0
		},
		{
			tname: "max-bit shift right for bitwise rshift",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, append(binary.NativeEndian.AppendUint32(nil, 0x45678910), binary.NativeEndian.AppendUint32(nil, 0)...), Verdict{}),
			op2:   mustCreateBitwiseShift(t, linux.NFT_REG_3, linux.NFT_REG_2, 8, bitshiftLimit-1, true),
			op3:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, append(binary.NativeEndian.AppendUint32(nil, 0), binary.NativeEndian.AppendUint32(nil, 0x8ACF1220)...)),
		},
	} {
		t.Run(test.tname, func(t *testing.T) {
			// Sets up an NFTables object with a single table, chain, and rule.
			nf := newNFTablesStd()
			tab, err := nf.AddTable(arbitraryFamily, "test", false)
			if err != nil {
				t.Fatalf("unexpected error for AddTable: %v", err)
			}
			bc, err := nf.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept /* bcInfo */, "test chain", false, 0, nil, linux.NF_ACCEPT)
			if err != nil {
				t.Fatalf("unexpected error for AddChain: %v", err)
			}
			rule := &Rule{}

			// Adds testing operations.
			if test.op1 != nil {
				rule.addOperation(test.op1)
			}
			if test.op2 != nil {
				rule.addOperation(test.op2)
			}
			if test.op3 != nil {
				rule.addOperation(test.op3)
			}

			// Adds drop operation. Will be final verdict if comparison is true.
			rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, 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 and checks verdict.
			pkt := makeArbitraryIPv4Packet()
			v, err := nf.EvaluateHook(stack.IP, arbitraryHook, pkt, nil /* route */)
			if err != nil {
				t.Fatalf("unexpected error for EvaluateHook: %v", err)
			}
			if v.Code != VC(linux.NF_DROP) {
				t.Fatalf("expected verdict Drop for true comparison, got %v", v)
			}
		})
	}
}

// TestEvaluateCounter tests that the Counter operation correctly increments the
// counter for the number of bytes and packets as it encounters packets.
// Note: relies on expected behavior of Comparison and Payload Load operations.
func TestEvaluateCounter(t *testing.T) {
	// Creates a counter operation.
	counter := newCounter(0, 0)
	// Defines the packets to be used in the test.
	desiredIpv4Address := tcpip.AddrFrom4(arbitraryIPv4AddrB)
	countedIPv4Pkt := func() *stack.PacketBuffer {
		fields := arbitraryIPv4Fields()
		fields.SrcAddr = desiredIpv4Address
		return makeIPv4Packet(header.IPv4MinimumSize, fields)
	}
	uncountedIPv4Pkt := func() *stack.PacketBuffer {
		fields := arbitraryIPv4Fields()
		fields.SrcAddr = tcpip.AddrFrom4(arbitraryIPv4AddrB2)
		return makeIPv4Packet(header.IPv4MinimumSize, fields)
	}
	pkts := []*stack.PacketBuffer{countedIPv4Pkt(), uncountedIPv4Pkt(), countedIPv4Pkt(), countedIPv4Pkt(),
		uncountedIPv4Pkt(), countedIPv4Pkt(), uncountedIPv4Pkt(), uncountedIPv4Pkt(), uncountedIPv4Pkt(), countedIPv4Pkt()}
	t.Run("counter increment tests", func(t *testing.T) {
		// Sets up an NFTables object with a base chain with policy accept.
		nf := newNFTablesStd()
		tab, err := nf.AddTable(arbitraryFamily, "test", false)
		if err != nil {
			t.Fatalf("unexpected error for AddTable: %v", err)
		}
		bc, err := nf.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept /* bcInfo */, "test chain", false, 0, nil, linux.NF_ACCEPT)
		if err != nil {
			t.Fatalf("unexpected error for AddChain: %v", err)
		}

		// Creates a rule that filters for the desired IPv4 address and adds the
		// counter to the end of the rule. So, the counter should only increment for
		// packets that satisfy the comparison.
		rule := &Rule{}
		rule.addOperation(mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER, ipv4SrcAddrOffset, ipv4SrcAddrLen, linux.NFT_REG_1))
		rule.addOperation(mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, desiredIpv4Address.AsSlice()))
		rule.addOperation(counter)
		if err := bc.RegisterRule(rule, -1); err != nil {
			t.Fatalf("unexpected error for RegisterRule: %v", err)
		}

		// Runs evaluation for each packet and checks whether the counter has
		// incremented correctly.
		prevBytes := counter.bytes.Load()
		prevPackets := counter.packets.Load()
		for i, pkt := range pkts {
			_, err := nf.EvaluateHook(getAddrFamilyOrDefault(pkt, arbitraryFamily), arbitraryHook, pkt, nil /* route */)
			if err != nil {
				t.Fatalf("unexpected error for EvaluateHook for packet %d: %v", i, err)
			}
			// Checks whether the counter should have incremented for the packet.
			expectedDBytes, expectedDPackets := uint64(0), uint64(0)
			if pkt.Network().SourceAddress() == desiredIpv4Address {
				expectedDBytes, expectedDPackets = uint64(pkt.Size()), 1
			}
			// Checks that the counter incremented correctly.
			newBytes := counter.bytes.Load()
			newPackets := counter.packets.Load()
			if dBytes := newBytes - prevBytes; dBytes != expectedDBytes {
				t.Fatalf("counter bytes incremented by %d for packet %d, expected %d", dBytes, i, expectedDBytes)
			}
			if dPackets := newPackets - prevPackets; dPackets != expectedDPackets {
				t.Fatalf("counter packets incremented by %d for packet %d, expected %d", dPackets, i, expectedDPackets)
			}
			// Updates the previous values for the next packet.
			prevBytes = newBytes
			prevPackets = newPackets
		}
	})
}

// TestEvaluateLast tests that the Last operation correctly records the last
// time the operation was evaluated.
func TestEvaluateLast(t *testing.T) {
	// Creates last operation and number of elapses (in milliseconds) for testing.
	last := &last{}
	elapses := []int64{0, 1000, 500, 1000, 100, 200, 300, 20000, 50, 700}
	totalElapsed := make([]int64, len(elapses))
	copy(totalElapsed, elapses)

	t.Run("last timing tests", func(t *testing.T) {
		// Makes an arbitrary packet to be used in the test.
		pkt := makeArbitraryIPv4Packet()
		pktFamily := stack.IP

		// Sets up an NFTables object with a base chain and fake manual clock.
		fakeClock := faketime.NewManualClock()
		fixedRNG := rand.RNGFrom(&fixedReader{})
		nf := NewNFTables(nil /* stack */, fakeClock, fixedRNG)
		tab, err := nf.AddTable(arbitraryFamily, "test", false)
		if err != nil {
			t.Fatalf("unexpected error for AddTable: %v", err)
		}
		bc, err := nf.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept /* bcInfo */, "test chain", false, 0, nil, linux.NF_ACCEPT)
		if err != nil {
			t.Fatalf("unexpected error for AddChain: %v", err)
		}

		// Registers a single rule with the last operation.
		rule := &Rule{}
		rule.addOperation(last)
		if err := bc.RegisterRule(rule, -1); err != nil {
			t.Fatalf("unexpected error for RegisterRule: %v", err)
		}

		// Sets up a wait group to wait for all AfterFunc goroutines to complete.
		var wg sync.WaitGroup
		wg.Add(len(elapses))
		defer wg.Wait()

		// Calls EvaluateHook for each elapse and checks that the last operation
		// recorded the correct timestamp and has the set flag set.
		startStamp := nf.startTime.UnixMilli()
		clock := nf.clock
		for i := range elapses {
			// Uses totalElapsed slice to avoid race conditions.
			if i != 0 {
				totalElapsed[i] += totalElapsed[i-1]
			}
			clock.AfterFunc(time.Duration(totalElapsed[i])*time.Millisecond, func() {
				// Decrements wait group counter at end to signal func has completed.
				defer wg.Done()

				// Evaluates the packet (which should update last's timestamp).
				_, err := nf.EvaluateHook(pktFamily, arbitraryHook, pkt, nil /* route */)
				if err != nil {
					t.Fatalf("unexpected error for EvaluateHook for packet %d: %v", i, err)
				}

				// Checks the set flag and the timestamp via total elapsed time.
				if !last.set.Load() {
					t.Fatalf("last operation not set for packet %d", i)
				}
				if dTotal := last.timestampMS.Load() - startStamp; dTotal != totalElapsed[i] {
					t.Fatalf("last operation recorded %d milliseconds since start for packet %d, expected %d", dTotal, i, totalElapsed[i])
				}
			})
		}
		// Manually advances the clock to trigger the AfterFunc goroutines.
		for _, elapse := range elapses {
			fakeClock.Advance(time.Duration(elapse) * time.Millisecond)
		}
	})
}

// TestEvaluateRoute tests that the Route operation correctly loads the specific
// route data into into the destination register.
// The nft binary commands used to generate these are stated above each test.
// Also note that all these commands mirror the ones in TestInterpretRouteOps.
// All commands should be preceded by nft --debug=netlink.
// Note: Relies on expected behavior of the Comparison operation.
func TestEvaluateRoute(t *testing.T) {
	for _, test := range []struct {
		tname string
		pkt   *stack.PacketBuffer
		op1   operation // Route operation to test.
		op2   operation // Comparison operation to check resulting data in register,
	}{
		// IPv4 Next Hop Commands
		{ // cmd: add rule ip filter output rt nexthop 192.168.1.1
			tname: "load nexthop4 key to 4-byte register",
			pkt: func() *stack.PacketBuffer {
				pkt := makeIPv4Packet(header.IPv6MinimumSize, arbitraryIPv4Fields())
				pkt.EgressRoute.NextHop = tcpip.AddrFrom4(arbitraryIPv4AddrB)
				return pkt
			}(),
			op1: mustCreateRoute(t, linux.NFT_RT_NEXTHOP4, linux.NFT_REG32_06),
			op2: mustCreateComparison(t, linux.NFT_REG32_06, linux.NFT_CMP_EQ, arbitraryIPv4AddrB[:]),
		},
		{ // cmd: add rule ip filter output rt nexthop 192.168.1.9
			tname: "load nexthop4 key to 16-byte register",
			pkt: func() *stack.PacketBuffer {
				pkt := makeIPv4Packet(header.IPv6MinimumSize, arbitraryIPv4Fields())
				pkt.EgressRoute.NextHop = tcpip.AddrFrom4(arbitraryIPv4AddrB2)
				return pkt
			}(),
			op1: mustCreateRoute(t, linux.NFT_RT_NEXTHOP4, linux.NFT_REG_3),
			op2: mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_EQ, arbitraryIPv4AddrB2[:]),
		},
		// IPv6 Next Hop Commands
		{ // cmd: add rule ip filter output rt nexthop 2001:db8:85a3::aa
			tname: "load nexthop6 key to 16-byte register",
			pkt: func() *stack.PacketBuffer {
				pkt := makeIPv6Packet(header.IPv6MinimumSize, arbitraryIPv6Fields())
				pkt.EgressRoute.NextHop = tcpip.AddrFrom16(arbitraryIPv6AddrB)
				return pkt
			}(),
			op1: mustCreateRoute(t, linux.NFT_RT_NEXTHOP6, linux.NFT_REG_1),
			op2: mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, arbitraryIPv6AddrB[:]),
		},
		// TCP Maximum Segment Size Commands
		{ // cmd: add rule ip filter output rt mtu 1500
			tname: "load tcpmss key to 4-byte register",
			pkt: func() *stack.PacketBuffer {
				pkt := makeIPv4Packet(header.IPv6MinimumSize, arbitraryIPv4Fields())
				pkt.GSOOptions.MSS = 1500
				return pkt
			}(),
			op1: mustCreateRoute(t, linux.NFT_RT_TCPMSS, linux.NFT_REG32_00),
			op2: mustCreateComparison(t, linux.NFT_REG32_00, linux.NFT_CMP_EQ, binary.NativeEndian.AppendUint16(nil, 1500)),
		},
		{ // cmd: add rule ip filter output rt mtu 0x0102
			tname: "load tcpmss key to 16-byte register",
			pkt: func() *stack.PacketBuffer {
				pkt := makeIPv6Packet(header.IPv6MinimumSize, arbitraryIPv6Fields())
				pkt.GSOOptions.MSS = 0x0102
				return pkt
			}(),
			op1: mustCreateRoute(t, linux.NFT_RT_TCPMSS, linux.NFT_REG_4),
			op2: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, binary.NativeEndian.AppendUint16(nil, 0x0102)),
		},
	} {
		t.Run(test.tname, func(t *testing.T) {
			// Sets up an NFTables object with a single table, chain, and rule.
			nf := newNFTablesStd()
			tab, err := nf.AddTable(arbitraryFamily, "test", false)
			if err != nil {
				t.Fatalf("unexpected error for AddTable: %v", err)
			}
			bc, err := nf.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept, "test chain", false, 0, nil, linux.NF_ACCEPT)
			if err != nil {
				t.Fatalf("unexpected error for AddChain: %v", err)
			}
			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, nil, 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(getAddrFamilyOrDefault(test.pkt, arbitraryFamily), arbitraryHook, test.pkt, nil /* route */)
			if err != nil {
				t.Fatalf("unexpected error for EvaluateHook: %v", err)
			}

			// Checks for final verdict (should be Drop if comparisons are true).
			if v.Code != VC(linux.NF_DROP) {
				t.Fatalf("expected verdict Drop for true comparison, got %v", v)
			}
		})
	}
}

// TestEvaluateByteorder tests that the Byteorder operation correctly performs
// the appropriate byteorder operation on the source register data and stores
// the result in the destination register.
// Note: Relies on expected behavior of the Immediate and Comparison operation.
func TestEvaluateByteorder(t *testing.T) {
	// Given a big endian and little endian byte slice of the same number, returns
	// the correct byte slice based on the host endianness.
	// Note: Uses enclosure so endianness doesn't need to be passed as an arg or
	// rechecked for every call.
	chooseOrder := func() func([]byte, []byte) []byte {
		hostBytes := binary.NativeEndian.AppendUint16(nil, 0x0102)
		isBigEndian := hostBytes[0] == 0x01
		return func(big, little []byte) []byte {
			if isBigEndian {
				return big
			}
			return little
		}
	}()
	// Like createChooseOrder but takes ints instead of byte slices.
	chooseOrderN := func(big, little, size int) []byte {
		return chooseOrder(numToBE(big, size), numToBE(little, size))
	}
	for _, test := range []struct {
		tname string
		op1   operation // Immediate operation to set source register.
		op2   operation // Byteorder operation to test.
		op3   operation // Comparison operation to validate result.
	}{
		// Size 2 tests (Lengths 2, 3, 4, 6, 8, 16)
		{
			tname: "ntoh size 2 len 2",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_01, numToBE(0x0102, 2), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG32_01, linux.NFT_REG32_01, linux.NFT_BYTEORDER_NTOH, 2, 2),
			op3:   mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ, chooseOrderN(0x0102, 0x0201, 2)),
		},
		{
			tname: "hton size 2 len 2",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_01, numToBE(0x0102, 2), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG32_01, linux.NFT_REG_1, linux.NFT_BYTEORDER_HTON, 2, 2),
			op3:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, chooseOrderN(0x0102, 0x0201, 2)),
		},
		{
			tname: "ntoh size 2 len 3",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_01, numToBE(0x010203, 3), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG32_01, linux.NFT_REG_1, linux.NFT_BYTEORDER_NTOH, 3, 2),
			op3:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, chooseOrderN(0x010203, 0x020100, 3)),
		},
		{
			tname: "hton size 2 len 3",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(0x010203, 3), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG32_01, linux.NFT_BYTEORDER_HTON, 3, 2),
			op3:   mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ, chooseOrderN(0x010203, 0x020100, 3)),
		},
		{
			tname: "ntoh size 2 len 4",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_10, numToBE(0x01020304, 4), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG32_10, linux.NFT_REG32_05, linux.NFT_BYTEORDER_NTOH, 4, 2),
			op3:   mustCreateComparison(t, linux.NFT_REG32_05, linux.NFT_CMP_EQ, chooseOrderN(0x01020304, 0x02010403, 4)),
		},
		{
			tname: "hton size 2 len 4",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, numToBE(0x01020304, 4), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG32_09, linux.NFT_BYTEORDER_HTON, 4, 2),
			op3:   mustCreateComparison(t, linux.NFT_REG32_09, linux.NFT_CMP_EQ, chooseOrderN(0x01020304, 0x02010403, 4)),
		},
		{
			tname: "ntoh size 2 len 6",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(0x010203040506, 6), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_1, linux.NFT_BYTEORDER_NTOH, 6, 2),
			op3:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, chooseOrderN(0x010203040506, 0x020104030605, 6)),
		},
		{
			tname: "hton size 2 len 6",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(0x010203040506, 6), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_1, linux.NFT_BYTEORDER_HTON, 6, 2),
			op3:   mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, chooseOrderN(0x010203040506, 0x020104030605, 6)),
		},
		{
			tname: "ntoh size 2 len 8",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(0x0102030405060708, 8), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_4, linux.NFT_BYTEORDER_NTOH, 8, 2),
			op3:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrderN(0x0102030405060708, 0x0201040306050807, 8)),
		},
		{
			tname: "hton size 2 len 8",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(0x0102030405060708, 8), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_4, linux.NFT_BYTEORDER_HTON, 8, 2),
			op3:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrderN(0x0102030405060708, 0x0201040306050807, 8)),
		},
		{
			tname: "ntoh size 2 len 16",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_3, linux.NFT_REG_2, linux.NFT_BYTEORDER_NTOH, 16, 2),
			op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
				[]byte{0x02, 0x01, 0x04, 0x03, 0x06, 0x05, 0x08, 0x07, 0x0a, 0x09, 0x0c, 0x0b, 0x0e, 0x0d, 0x10, 0x0f})),
		},
		{
			tname: "hton size 2 len 16",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_3, linux.NFT_REG_2, linux.NFT_BYTEORDER_HTON, 16, 2),
			op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
				[]byte{0x02, 0x01, 0x04, 0x03, 0x06, 0x05, 0x08, 0x07, 0x0a, 0x09, 0x0c, 0x0b, 0x0e, 0x0d, 0x10, 0x0f})),
		},
		// Size 4 tests (Lengths 4, 6, 8, 16)
		{
			tname: "ntoh size 4 len 4",
			op1:   mustCreateImmediate(t, linux.NFT_REG32_05, numToBE(0x01020304, 4), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG32_05, linux.NFT_REG_2, linux.NFT_BYTEORDER_NTOH, 4, 4),
			op3:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrderN(0x01020304, 0x04030201, 4)),
		},
		{
			tname: "hton size 4 len 4",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, numToBE(0x01020304, 4), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG32_09, linux.NFT_BYTEORDER_HTON, 4, 4),
			op3:   mustCreateComparison(t, linux.NFT_REG32_09, linux.NFT_CMP_EQ, chooseOrderN(0x01020304, 0x04030201, 4)),
		},
		{
			tname: "ntoh size 4 len 6",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, numToBE(0x010203040506, 6), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG_2, linux.NFT_BYTEORDER_NTOH, 6, 4),
			op3:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrderN(0x010203040506, 0x040302010000, 6)),
		},
		{
			tname: "hton size 4 len 6",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, numToBE(0x010203040506, 6), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG_2, linux.NFT_BYTEORDER_HTON, 6, 4),
			op3:   mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrderN(0x010203040506, 0x040302010000, 6)),
		},
		{
			tname: "ntoh size 4 len 8",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(0x0102030405060708, 8), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_4, linux.NFT_BYTEORDER_NTOH, 8, 4),
			op3:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrderN(0x0102030405060708, 0x0403020108070605, 8)),
		},
		{
			tname: "hton size 4 len 8",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(0x0102030405060708, 8), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_4, linux.NFT_BYTEORDER_HTON, 8, 4),
			op3:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrderN(0x0102030405060708, 0x0403020108070605, 8)),
		},
		{
			tname: "ntoh size 4 len 16",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_3, linux.NFT_REG_2, linux.NFT_BYTEORDER_NTOH, 16, 4),
			op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
				[]byte{0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05, 0x0c, 0x0b, 0x0a, 0x09, 0x10, 0x0f, 0x0e, 0x0d})),
		},
		{
			tname: "hton size 4 len 16",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_3, linux.NFT_REG_2, linux.NFT_BYTEORDER_HTON, 16, 4),
			op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
				[]byte{0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05, 0x0c, 0x0b, 0x0a, 0x09, 0x10, 0x0f, 0x0e, 0x0d})),
		},
		// Size 8 tests (Lengths 8, 12, 16)
		{
			tname: "ntoh size 8 len 8",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(0x0102030405060708, 8), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_4, linux.NFT_BYTEORDER_NTOH, 8, 8),
			op3:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrderN(0x0102030405060708, 0x0807060504030201, 8)),
		},
		{
			tname: "hton size 8 len 8",
			op1:   mustCreateImmediate(t, linux.NFT_REG_1, numToBE(0x0102030405060708, 8), Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_4, linux.NFT_BYTEORDER_HTON, 8, 8),
			op3:   mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrderN(0x0102030405060708, 0x0807060504030201, 8)),
		},
		{
			tname: "ntoh size 8 len 12",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}, Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_3, linux.NFT_REG_2, linux.NFT_BYTEORDER_NTOH, 12, 8),
			op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c},
				[]byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00})),
		},
		{
			tname: "hton size 8 len 12",
			op1:   mustCreateImmediate(t, linux.NFT_REG_3, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}, Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_3, linux.NFT_REG_2, linux.NFT_BYTEORDER_HTON, 12, 8),
			op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c},
				[]byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00})),
		},
		{
			tname: "ntoh size 8 len 16",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG_4, linux.NFT_BYTEORDER_NTOH, 16, 8),
			op3: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
				[]byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09})),
		},
		{
			tname: "hton size 8 len 16",
			op1:   mustCreateImmediate(t, linux.NFT_REG_4, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, Verdict{}),
			op2:   mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG_4, linux.NFT_BYTEORDER_HTON, 16, 8),
			op3: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
				[]byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09})),
		},
	} {
		t.Run(test.tname, func(t *testing.T) {
			// Sets up an NFTables object with a single table, chain, and rule.
			nf := newNFTablesStd()
			tab, err := nf.AddTable(arbitraryFamily, "test", false)
			if err != nil {
				t.Fatalf("unexpected error for AddTable: %v", err)
			}
			bc, err := nf.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept /* bcInfo */, "test chain", false, 0, nil, linux.NF_ACCEPT)
			if err != nil {
				t.Fatalf("unexpected error for AddChain: %v", err)
			}
			rule := &Rule{}

			// Adds testing operations.
			if test.op1 != nil {
				rule.addOperation(test.op1)
			}
			if test.op2 != nil {
				rule.addOperation(test.op2)
			}
			if test.op3 != nil {
				rule.addOperation(test.op3)
			}

			// Adds drop operation. Will be final verdict if comparison is true.
			rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, 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 and checks verdict.
			pkt := makeArbitraryIPv4Packet()
			v, err := nf.EvaluateHook(stack.IP, arbitraryHook, pkt, nil /* route */)
			if err != nil {
				t.Fatalf("unexpected error for EvaluateHook: %v", err)
			}
			if v.Code != VC(linux.NF_DROP) {
				t.Fatalf("expected verdict Drop for true comparison, got %v", v)
			}
		})
	}
}

// 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.RNGFrom(&fixedReader{})
	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)),
		},
		{ // meta nfproto is only useful in the inet family
			// cmd: add rule inet 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{linux.NFPROTO_IPV6, 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(nil /* stack */, fakeClock, rand.RNGFrom(&fixedReader{}))

			tab, err := nf.AddTable(arbitraryFamily, "test", false)
			if err != nil {
				t.Fatalf("unexpected error for AddTable: %v", err)
			}
			bc, err := nf.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept /* bcInfo */, "test chain", false, 0, nil, linux.NF_ACCEPT)
			if err != nil {
				t.Fatalf("unexpected error for AddChain: %v", err)
			}
			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, nil, 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(getAddrFamilyOrDefault(test.pkt, arbitraryFamily), arbitraryHook, test.pkt, nil /* route */)
			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)
				}
			}
		})
	}
}

// TestEvaluateMetaSet tests that the Meta Set operation correctly sets specific
// packet meta data to the value in the source register.
func TestEvaluateMetaSet(t *testing.T) {
	// Packet type to set anc test for.
	testPktType := tcpip.PacketMulticast
	for _, test := range []struct {
		tname  string
		pkt    *stack.PacketBuffer
		outPkt *stack.PacketBuffer
		op1    operation // Immediate operation to load source register.
		op2    operation // Meta set operation to test.
	}{
		// cmd: nft --debug=netlink add rule ip tab ch meta pkttype set 34
		{
			tname: "meta set pkttype 4-byte reg test",
			pkt:   makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields()),
			outPkt: func() *stack.PacketBuffer {
				pkt := makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields())
				pkt.PktType = testPktType
				return pkt
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG32_06, []byte{uint8(testPktType)}, Verdict{}),
			op2: mustCreateMetaSet(t, linux.NFT_META_PKTTYPE, linux.NFT_REG32_06),
		},
		{
			tname: "meta set pkttype 16-byte reg test",
			pkt:   makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields()),
			outPkt: func() *stack.PacketBuffer {
				pkt := makeIPv4Packet(header.IPv4MinimumSize, arbitraryIPv4Fields())
				pkt.PktType = testPktType
				return pkt
			}(),
			op1: mustCreateImmediate(t, linux.NFT_REG_3, []byte{uint8(testPktType)}, Verdict{}),
			op2: mustCreateMetaSet(t, linux.NFT_META_PKTTYPE, linux.NFT_REG_3),
		},
	} {
		t.Run(test.tname, func(t *testing.T) {
			// Sets up an NFTables object with a single table, chain, and rule.
			nf := newNFTablesStd()
			tab, err := nf.AddTable(arbitraryFamily, "test", false)
			if err != nil {
				t.Fatalf("unexpected error for AddTable: %v", err)
			}
			bc, err := nf.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept /* bcInfo */, "test chain", false, 0, nil, linux.NF_ACCEPT)
			if err != nil {
				t.Fatalf("unexpected error for AddChain: %v", err)
			}
			rule := &Rule{}

			// Adds testing operations.
			if test.op1 != nil {
				rule.addOperation(test.op1)
			}
			if test.op2 != nil {
				rule.addOperation(test.op2)
			}

			// Adds drop operation, to be final verdict if evaluation is successful.
			rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, 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(getAddrFamilyOrDefault(test.pkt, arbitraryFamily), arbitraryHook, test.pkt, nil /* route */)
			if err != nil {
				t.Fatalf("unexpected error for EvaluateHook: %v", err)
			}

			// Evaluation should be successful and result in Drop verdict.
			if v.Code != VC(linux.NF_DROP) {
				t.Fatalf("expected verdict Drop for successful evaluation, got %v", v)
			}

			// Checks if the packet are equal.
			checkPacketEquality(t, test.outPkt, test.pkt)
		})
	}
}

// TestLoopCheckOnRegisterAndUnregister tests the loop checking and accompanying
// logic on registering and unregistering rules.
func TestLoopCheckOnRegisterAndUnregister(t *testing.T) {
	for _, test := range []struct {
		tname     string
		chains    map[string]*Chain
		verdict   Verdict
		shouldErr bool
	}{
		{
			tname: "jump to non-existent chain",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "non_existent_chain")},
					}},
				},
			},
			shouldErr: true,
		},
		{
			tname: "goto to non-existent chain",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "non_existent_chain")},
					}},
				},
			},
			shouldErr: true,
		},
		{
			tname: "jump to itself",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "base_chain")},
					}},
				},
			},
			shouldErr: true,
		},
		{
			tname: "goto to itself",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "base_chain")},
					}},
				},
			},
			shouldErr: true,
		},
		{
			tname: "simple 2-chain loop",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "aux_chain")},
					}},
				},
				"aux_chain": {
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "base_chain")},
					}},
				},
			},
			shouldErr: true,
		},
		{
			tname: "2-chain loop with entry point outside loop",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "aux_chain")},
					}},
				},
				"aux_chain": {
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "aux_chain2")},
					}},
				},
				"aux_chain2": {
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "aux_chain")},
					}},
				},
			},
			shouldErr: true,
		},
		{
			tname: "simple 3-chain loop",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "aux_chain")},
					}},
				},
				"aux_chain": {
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "aux_chain2")},
					}},
				},
				"aux_chain2": {
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "base_chain")},
					}},
				},
			},
			shouldErr: true,
		},
		{
			tname: "3-chain loop with entry point 2 points outside loop",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "aux_chain")},
					}},
				},
				"aux_chain": {
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "aux_chain2")},
					}},
				},
				"aux_chain2": {
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "aux_chain3")},
					}},
				},
				"aux_chain3": {
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "aux_chain4")},
					}},
				},
				"aux_chain4": {
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "aux_chain2")},
					}},
				},
			},
			shouldErr: true,
		},
		{
			tname: "simple 4-chain loop",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "aux_chain")},
					}},
				},
				"aux_chain": {
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "aux_chain2")},
					}},
				},
				"aux_chain2": {
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "aux_chain3")},
					}},
				},
				"aux_chain3": {
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "base_chain")},
					}},
				},
			},
			shouldErr: true,
		},
		{
			tname: "simple 5-chain loop",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "aux_chain")},
					}},
				},
				"aux_chain": {
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "aux_chain2")},
					}},
				},
				"aux_chain2": {
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "aux_chain3")},
					}},
				},
				"aux_chain3": {
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "base_chain")},
					}},
				},
			},
			shouldErr: true,
		},
		{
			//     0
			//  	/ \
			//   v   v
			//   1 <- 2 <-> 3
			tname: "complex 2-3 loop",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{{
						ops: []operation{
							mustCreateJump(t, "aux_chain"),
							mustCreateJump(t, "aux_chain2"),
						},
					}},
				},
				"aux_chain": {
					comment: "strictly target",
					rules: []*Rule{{
						ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)})},
					}},
				},
				"aux_chain2": {
					rules: []*Rule{{
						ops: []operation{
							mustCreateJump(t, "aux_chain"),
							mustCreateJump(t, "aux_chain3"),
						},
					}},
				},
				"aux_chain3": {
					rules: []*Rule{{
						ops: []operation{mustCreateGoto(t, "aux_chain2")},
					}},
				},
			},
			shouldErr: true,
		},
		{
			tname: "simple loop amongst other rules and operations",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{
						{ops: []operation{mustCreateImmediate(t, linux.NFT_REG_1, []byte{0, 1, 2, 3}, Verdict{})}},
						{ops: []operation{mustCreateImmediate(t, linux.NFT_REG32_14, []byte{0, 1, 2, 3}, Verdict{})}},
						{ops: []operation{mustCreateJump(t, "aux_chain")}},
					},
				},
				"aux_chain": {
					rules: []*Rule{{
						ops: []operation{
							mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
							mustCreateGoto(t, "aux_chain2"),
						},
					}},
				},
				"aux_chain2": {
					rules: []*Rule{{
						ops: []operation{mustCreateJump(t, "aux_chain3")},
					}},
				},
				"aux_chain3": {
					rules: []*Rule{
						{ops: []operation{mustCreateImmediate(t, linux.NFT_REG_1, []byte{0, 1, 2, 3}, Verdict{})}},
						{ops: []operation{mustCreateImmediate(t, linux.NFT_REG32_14, []byte{0, 1, 2, 3}, Verdict{})}},
						{ops: []operation{
							mustCreateImmediate(t, linux.NFT_REG_4, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Verdict{}),
							mustCreateGoto(t, "aux_chain"),
							mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}),
						}},
					},
				},
			},
			shouldErr: true,
		},
		{
			tname: "base chain jump to 3 other chains",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{
						{
							ops: []operation{
								mustCreateJump(t, "aux_chain"),
								mustCreateJump(t, "aux_chain2"),
							},
						},
						{ops: []operation{mustCreateJump(t, "aux_chain3")}},
					},
				},
				"aux_chain": {
					comment: "strictly target",
					rules: []*Rule{{
						ops: []operation{mustCreateImmediate(t, linux.NFT_REG_2, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Verdict{})},
					}},
				},
				"aux_chain2": {
					comment: "strictly target",
					rules: []*Rule{{
						ops: []operation{mustCreateImmediate(t, linux.NFT_REG_3, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Verdict{})},
					}},
				},
				"aux_chain3": {
					comment: "strictly target",
					rules: []*Rule{{
						ops: []operation{mustCreateImmediate(t, linux.NFT_REG_4, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Verdict{})},
					}},
				},
			},
			verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy
		},
		{
			tname: "base chain jump to 3 other chains with last chain dropping",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{
						{
							ops: []operation{
								mustCreateJump(t, "aux_chain"),
								mustCreateJump(t, "aux_chain2"),
							},
						},
						{ops: []operation{mustCreateJump(t, "aux_chain3")}},
					},
				},
				"aux_chain": {
					comment: "strictly target",
					rules: []*Rule{{
						ops: []operation{mustCreateImmediate(t, linux.NFT_REG_2, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Verdict{})},
					}}},
				"aux_chain2": {
					comment: "strictly target",
					rules: []*Rule{{
						ops: []operation{mustCreateImmediate(t, linux.NFT_REG_3, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Verdict{})},
					}},
				},
				"aux_chain3": {
					comment: "strictly target",
					rules: []*Rule{{
						ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)})},
					}},
				},
			},
			verdict: Verdict{Code: VC(linux.NF_DROP)}, // from last chain
		},
		{
			tname: "base chain jump to 3 other chains with last rule in base chain dropping",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{
						{
							ops: []operation{
								mustCreateJump(t, "aux_chain"),
								mustCreateJump(t, "aux_chain2"),
							},
						},
						{ops: []operation{mustCreateJump(t, "aux_chain3")}},
						{ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)})}},
					},
				},
				"aux_chain": {
					comment: "strictly target",
					rules: []*Rule{{
						ops: []operation{mustCreateImmediate(t, linux.NFT_REG_2, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Verdict{})},
					}}},
				"aux_chain2": {
					comment: "strictly target",
					rules: []*Rule{{
						ops: []operation{mustCreateImmediate(t, linux.NFT_REG_3, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Verdict{})},
					}},
				},
				"aux_chain3": {
					comment: "strictly target",
					rules: []*Rule{{
						ops: []operation{mustCreateImmediate(t, linux.NFT_REG_4, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Verdict{})},
					}},
				},
			},
			verdict: Verdict{Code: VC(linux.NF_DROP)}, // from last rule in base chain
		},
		{
			tname: "jump to the same chain",
			chains: map[string]*Chain{
				"base_chain": {
					baseChainInfo: arbitraryInfoPolicyAccept,
					rules: []*Rule{
						{
							ops: []operation{
								mustCreateJump(t, "aux_chain"),
								mustCreateJump(t, "aux_chain"),
							},
						},
					},
				},
				"aux_chain": {
					comment: "strictly target",
					rules:   []*Rule{{}},
				},
			},
			verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy
		},
	} {
		t.Run(test.tname, func(t *testing.T) {
			// Sets up an NFTables object based on test struct.
			nf := newNFTablesStd()
			tab, err := nf.AddTable(arbitraryFamily, "test", false)
			if err != nil {
				t.Fatalf("unexpected error for AddTable: %v", err)
			}
			// Creates all chains in the test struct first. This is necessary so the
			// loop checking sees the target chains exist (otherwise it would error).
			for chainName, chainInit := range test.chains {
				policy := linux.NF_ACCEPT
				if chainInit.baseChainInfo != nil && chainInit.baseChainInfo.PolicyDrop {
					policy = linux.NF_DROP
				}

				nf.AddChainToTable(tab, chainName, chainInit.GetBaseChainInfo(), chainInit.GetComment(), false, 0, nil, uint8(policy))
			}
			if len(test.chains) != tab.ChainCount() {
				t.Fatalf("not all chains added to table")
			}
			// Registers all rules to all chains in the test struct.
			for chainName, chainInit := range test.chains {
				chain, err := nf.GetChain(tab.GetAddressFamily(), tab.GetName(), chainName)
				if err != nil {
					t.Fatalf("unexpected error for GetChain: %v", err)
				}
				for _, rule := range chainInit.rules {
					for _, op := range rule.ops {
						if imm, ok := op.(*immediate); ok && (imm.verdict.Code == VC(linux.NFT_JUMP) || imm.verdict.Code == VC(linux.NFT_GOTO)) && imm.verdict.Chain != nil {
							targetChain, err := nf.GetChain(tab.GetAddressFamily(), tab.GetName(), imm.verdict.Chain.name)
							if err == nil {
								imm.verdict.Chain = targetChain
							} else {
								imm.verdict.Chain = nil
							}
						}
					}
					// Note: this is where the loop checking is triggered.
					if err := chain.RegisterRule(rule, -1); err != nil {
						if !test.shouldErr {
							t.Fatalf("unexpected error for RegisterRule: %v", err)
						}
						return
					}
					// Checks that the chain was assigned to the rule.
					if rule.chain == nil {
						t.Fatalf("chain is not assigned to rule after RegisterRule")
					}
				}
				if chainInit.RuleCount() != chain.RuleCount() {
					t.Fatalf("not all rules added to chain")
				}
			}

			// Runs evaluation and checks verdict.
			pkt := makeArbitraryIPv4Packet()
			v, err := nf.EvaluateHook(stack.IP, arbitraryHook, pkt, nil /* route */)
			if err != nil {
				t.Fatalf("unexpected error for EvaluateHook: %v", err)
			}
			if v.Code != test.verdict.Code {
				t.Fatalf("expected verdict %v, got %v", test.verdict, v)
			}

			// Unregisters all rules from all chains and checks that the chain is
			// unassigned from the rule.
			for chainName, chainInit := range test.chains {
				chain, err := nf.GetChain(tab.GetAddressFamily(), tab.GetName(), chainName)
				if err != nil {
					t.Fatalf("unexpected error for GetChain: %v", err)
				}
				for rIdx := chainInit.RuleCount() - 1; rIdx >= 0; rIdx-- {
					rule, err := chain.UnregisterRuleByIndex(rIdx)
					if err != nil {
						t.Fatalf("unexpected error for UnregisterRule: %v", err)
					}
					if rule != chainInit.rules[rIdx] {
						t.Fatalf("rule returned by UnregisterRule does not match previously registered rule")
					}
					if rule.chain != nil {
						t.Fatalf("chain is not unassigned from rule after UnregisterRule")
					}
				}
				if chain.RuleCount() != 0 {
					t.Fatalf("not all rules removed from chain")
				}
			}
		})
	}
}

// TestMaxNestedJumps tests the limit on nested jumps (no limit for gotos).
func TestMaxNestedJumps(t *testing.T) {
	for _, test := range []struct {
		tname         string
		useJumpOp     bool
		numberOfJumps int
		verdict       Verdict
		expectErr     bool
	}{
		{
			tname:         "nested jump limit reached with jumps",
			useJumpOp:     true,
			numberOfJumps: nestedJumpLimit,
			verdict:       Verdict{Code: VC(linux.NF_DROP)},
		},
		{
			tname:         "nested jump limit reached with gotos",
			useJumpOp:     false,
			numberOfJumps: nestedJumpLimit,
			verdict:       Verdict{Code: VC(linux.NF_DROP)},
		},
		{
			tname:         "nested jump limit exceeded with jumps",
			useJumpOp:     true,
			numberOfJumps: nestedJumpLimit + 1,
			expectErr:     true,
		},
		{
			tname:         "nested jump limit exceeded with gotos",
			useJumpOp:     false,
			numberOfJumps: nestedJumpLimit + 1,
			verdict:       Verdict{Code: VC(linux.NF_DROP)}, // limit only for jumps
		},
	} {
		t.Run(test.tname, func(t *testing.T) {
			// Sets up chains of nested jumps or gotos.
			nf := newNFTablesStd()
			tab, err := nf.AddTable(arbitraryFamily, "test", false)
			if err != nil {
				t.Fatalf("unexpected error for AddTable: %v", err)
			}
			for i := test.numberOfJumps - 1; i >= 0; i-- {
				name := fmt.Sprintf("chain %d", i)
				var c *Chain
				var err *syserr.AnnotatedError

				if i == 0 {
					c, err = nf.AddChainToTable(tab, name, arbitraryInfoPolicyAccept, "test chain", false, 0, nil, linux.NF_ACCEPT)
				} else {
					c, err = nf.AddChainToTable(tab, name, nil /* bcInfo */, "test chain", false, 0, nil, linux.NF_ACCEPT)
				}
				if err != nil {
					t.Fatalf("unexpected error for AddChain: %v", err)
				}
				r := &Rule{}
				if i == test.numberOfJumps-1 {
					err = r.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_DROP)}))
				} else {
					targetName := fmt.Sprintf("chain %d", i+1)
					var targetChain *Chain
					if targetChain, err = nf.GetChain(tab.GetAddressFamily(), tab.GetName(), targetName); err != nil {
						t.Fatalf("unexpected error getting chain: %v", err)
					}
					var op *immediate
					if test.useJumpOp {
						op = mustCreateJump(t, targetName)
					} else {
						op = mustCreateGoto(t, targetName)
					}
					op.verdict.Chain = targetChain
					err = r.addOperation(op)
				}
				if err != nil {
					t.Fatalf("unexpected error for AddOperation: %v", err)
				}
				if err := c.RegisterRule(r, -1); err != nil {
					t.Fatalf("unexpected error for RegisterRule: %v", err)
				}
			}

			// Runs evaluation and checks verdict.
			pkt := makeArbitraryIPv4Packet()
			v, err := nf.EvaluateHook(getAddrFamilyOrDefault(pkt, arbitraryFamily), arbitraryHook, pkt, nil /* route */)
			if err != nil {
				if !test.expectErr {
					t.Fatalf("unexpected error for EvaluateHook: %v", err)
				}
			}
			if v.Code != test.verdict.Code {
				t.Fatalf("expected verdict %v, got %v", test.verdict, v)
			}
		})
	}
}

// checkPacketEquality checks that the given packets are equal for all fields
// and data relevant to our testing. This is not an exhaustive check.
func checkPacketEquality(t *testing.T, expected, actual *stack.PacketBuffer) {
	if expected.PktType != actual.PktType {
		t.Fatalf("expected packet type %d for resulting packet, got %d", int(expected.PktType), int(actual.PktType))
	}

	// Compares checksums first for the expected and actual packet.
	if expected.NetworkProtocolNumber != actual.NetworkProtocolNumber {
		t.Fatalf("expected network protocol number %d for resulting packet, got %d", expected.NetworkProtocolNumber, actual.NetworkProtocolNumber)
	}
	if actualHasNetwork, expectedHasNetwork := actual.NetworkHeader().View() != nil, expected.NetworkHeader().View() != nil; actualHasNetwork != expectedHasNetwork {
		t.Fatalf("expected network header is present to be %t for resulting packet, got %v", actualHasNetwork, expectedHasNetwork)
	}
	if actual.NetworkHeader().View() != nil && expected.Network().Checksum() != actual.Network().Checksum() {
		t.Fatalf("expected network checksum %d for resulting packet, got %d", expected.Network().Checksum(), actual.Network().Checksum())
	}
	if actual.TransportProtocolNumber != expected.TransportProtocolNumber {
		t.Fatalf("expected transport protocol number %d for resulting packet, got %d", expected.TransportProtocolNumber, actual.TransportProtocolNumber)
	}
	if actual.TransportProtocolNumber != 0 {
		var transport header.Transport
		var transportExpected header.Transport
		switch tBytes, tOutBytes := actual.TransportHeader().Slice(), expected.TransportHeader().Slice(); actual.TransportProtocolNumber {
		case header.TCPProtocolNumber:
			transport = header.TCP(tBytes)
			transportExpected = header.TCP(tOutBytes)
		case header.UDPProtocolNumber:
			transport = header.UDP(tBytes)
			transportExpected = header.UDP(tOutBytes)
		case header.ICMPv4ProtocolNumber:
			transport = header.ICMPv4(tBytes)
			transportExpected = header.ICMPv4(tOutBytes)
		case header.ICMPv6ProtocolNumber:
			transport = header.ICMPv6(tBytes)
			transportExpected = header.ICMPv6(tOutBytes)
		case header.IGMPProtocolNumber:
			transport = header.IGMP(tBytes)
			transportExpected = header.IGMP(tOutBytes)
		}
		if transport != nil && transport.Checksum() != transportExpected.Checksum() {
			t.Fatalf("expected transport checksum %d for resulting packet, got %d", transport.Checksum(), transportExpected.Checksum())
		}
	}

	// Compares raw packet data in bytes for resulting and expected packet.
	actualSlices := actual.AsSlices()
	expectedSlices := expected.AsSlices()
	if len(actualSlices) != len(expectedSlices) {
		t.Fatalf("expected %d slices of data for the resulting packet, got %d", len(expectedSlices), len(actualSlices))
	}
	for i := range actualSlices {
		if !slices.Equal(actualSlices[i], expectedSlices[i]) {
			t.Fatalf("packet data does not match expected packet data (for slice %d)", i)
		}
	}
}

// numToBE converts an n-byte int to Big Endian where n is in [1, 8].
// Assumes the given number can be represented in n bytes.
func numToBE(v int, n int) []byte {
	if n > 8 {
		panic("cannot support more than 8 bytes")
	}
	// Gets 8-byte slice Big Endian representation of the number.
	be64 := binary.BigEndian.AppendUint64(nil, uint64(v))
	// Returns last n bytes as the n-byte Big Endian representation.
	return be64[8-n:]
}

// packetResultString compares 2 packets by equality and returns a string
// representation.
func packetResultString(initial, final *stack.PacketBuffer) string {
	if final == nil {
		return "nil"
	}
	if reflect.DeepEqual(final, initial) {
		return "unmodified"
	}
	return "modified"
}

// newNFTablesStd creates a new NFTables object w/ a standard clock for testing.
func newNFTablesStd() *NFTables {
	stdClock := tcpip.NewStdClock()
	fixedRNG := rand.RNGFrom(&fixedReader{})
	return NewNFTables(nil /* stack */, stdClock, fixedRNG)
}

func setVerdictChainIfJumpGoto(op operation, chain *Chain) {
	imm, ok := op.(*immediate)
	if !ok {
		return
	}
	ok, vc := isJumpOrGotoOperation(op)
	if !ok || vc != nil {
		return
	}
	imm.verdict.Chain = chain
}

// mustCreateImmediate wraps the newImmediate function for brevity.
func mustCreateImmediate(t *testing.T, dreg uint8, data []byte, verdict Verdict) *immediate {
	dataType := immRegToType(uint32(dreg))
	imm, err := newImmediate(dreg, dataType, data, verdict)
	if err != nil {
		t.Fatalf("failed to create immediate: %v", err)
	}
	return imm
}

// mustCreateJump wraps the newImmediate function for jump operations in tests.
func mustCreateJump(t *testing.T, targetChainName string) *immediate {
	return mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{
		Code:  VC(linux.NFT_JUMP),
		Chain: &Chain{name: targetChainName},
	})
}

// mustCreateGoto wraps the newImmediate function for goto operations in tests.
func mustCreateGoto(t *testing.T, targetChainName string) *immediate {
	return mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{
		Code:  VC(linux.NFT_GOTO),
		Chain: &Chain{name: targetChainName},
	})
}

// mustCreateComparison wraps the newComparison function for brevity.
func mustCreateComparison(t *testing.T, sreg uint8, cop int, data []byte) *comparison {
	cmp, err := newComparison(sreg, cop, data)
	if err != nil {
		t.Fatalf("failed to create comparison: %v", err)
	}
	return cmp
}

// mustCreateRanged wraps the newRanged function for brevity.
func mustCreateRanged(t *testing.T, sreg uint8, rop int, low, high []byte) *ranged {
	rng, err := newRanged(sreg, rop, low, high)
	if err != nil {
		t.Fatalf("failed to create ranged: %v", err)
	}
	return rng
}

// mustCreatePayloadLoad wraps the newPayloadLoad function for brevity.
func mustCreatePayloadLoad(t *testing.T, base payloadBase, offset, len, dreg uint8) *payloadLoad {
	pdload, err := newPayloadLoad(base, offset, int(len), dreg)
	if err != nil {
		t.Fatalf("failed to create payload load: %v", err)
	}
	return pdload
}

// mustCreatePayloadSet wraps the newPayloadSet function for brevity.
func mustCreatePayloadSet(t *testing.T, base payloadBase, offset uint8, len uint8, sreg uint8, csumType uint8, csumOff uint8, csumFlags uint8) *payloadSet {
	pdset, err := newPayloadSet(base, offset, len, sreg, csumType, csumOff, csumFlags)
	if err != nil {
		t.Fatalf("failed to create payload set: %v", err)
	}
	return pdset
}

// mustCreateBitwiseBool wraps the newBitwiseBool function for brevity.
func mustCreateBitwiseBool(t *testing.T, sreg, dreg uint8, mask, xor []byte) *bitwise {
	bit, err := newBitwiseBool(sreg, dreg, mask, xor, len(mask))
	if err != nil {
		t.Fatalf("failed to create bitwise bool: %v", err)
	}
	return bit
}

// mustCreateBitwiseShift wraps the newBitwiseShift function for brevity.
func mustCreateBitwiseShift(t *testing.T, sreg, dreg, blen uint8, shift uint32, right bool) *bitwise {
	bit, err := newBitwiseShift(sreg, dreg, int(blen), shift, right)
	if err != nil {
		t.Fatalf("failed to create bitwise shift: %v", err)
	}
	return bit
}

// mustCreateRoute wraps the newRoute function for brevity.
func mustCreateRoute(t *testing.T, key routeKey, dreg uint8) *route {
	rt, err := newRoute(key, dreg)
	if err != nil {
		t.Fatalf("failed to create route: %v", err)
	}
	return rt
}

// mustCreateByteorder wraps the newByteorder function for brevity.
func mustCreateByteorder(t *testing.T, sreg, dreg uint8, bop byteorderOp, blen, size uint8) *byteorder {
	order, err := newByteorder(sreg, dreg, bop, blen, size)
	if err != nil {
		t.Fatalf("failed to create byteorder: %v", err)
	}
	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
}

// mustCreateMetaSet wraps the newMetaSet function for brevity.
func mustCreateMetaSet(t *testing.T, key metaKey, sreg uint8) *metaSet {
	mtset, err := newMetaSet(key, sreg)
	if err != nil {
		t.Fatalf("failed to create meta set: %v", err)
	}
	return mtset
}

// A fixedReader sets all bytes to the same value (1) when Read is called.
//
// It is used to make the RNG deterministic for testing, i.e. it's really
// really bad at being an RNG.
type fixedReader struct{}

// Read implements io.Reader.Read.
func (*fixedReader) Read(buf []byte) (int, error) {
	for i := range len(buf) {
		buf[i] = 1
	}
	return len(buf), nil
}

func createEmptyNlMsg() *nlmsg.Message {
	m := nlmsg.NewMessage(linux.NetlinkMessageHeader{})
	m.Put(&linux.NetFilterGenMsg{})
	return m
}

func TestNfAttrParser(t *testing.T) {
	tests := []struct {
		name   string
		msg    *nlmsg.Message
		policy []NlaPolicy
		want   map[uint16]nlmsg.BytesView
	}{
		{
			name: "validAttrs",
			msg: func() *nlmsg.Message {
				m := createEmptyNlMsg()
				m.PutAttr(linux.NFTA_TABLE_HANDLE, nlmsg.PutU32(123))
				m.PutAttr(linux.NFTA_TABLE_USE, nlmsg.PutU64(1))
				m.PutAttrString(linux.NFTA_TABLE_NAME, "test_table")
				return m
			}(),
			policy: []NlaPolicy{
				linux.NFTA_TABLE_HANDLE: {nlaType: linux.NLA_BE32, validator: AttrMaxValidator[uint32](256)},
				linux.NFTA_TABLE_USE:    {nlaType: linux.NLA_U64, validator: AttrMaxValidator[uint64](nlmsg.HostToNetU64(1))},
				linux.NFTA_TABLE_NAME:   {nlaType: linux.NLA_STRING},
			},
			want: map[uint16]nlmsg.BytesView{
				linux.NFTA_TABLE_HANDLE: []byte{0, 0, 0, 123},           // Network byte order.
				linux.NFTA_TABLE_USE:    []byte{0, 0, 0, 0, 0, 0, 0, 1}, // Network byte order.
				// The string is expected to be null terminated.
				linux.NFTA_TABLE_NAME: []byte{'t', 'e', 's', 't', '_', 't', 'a', 'b', 'l', 'e', '\x00'},
			},
		},
		{
			name: "attrsWithoutPolicy",
			msg: func() *nlmsg.Message {
				m := createEmptyNlMsg()
				m.PutAttr(linux.NFTA_TABLE_HANDLE, nlmsg.PutU32(123))
				m.PutAttr(linux.NFTA_TABLE_USE, nlmsg.PutU64(1))
				return m
			}(),
			want: map[uint16]nlmsg.BytesView{
				linux.NFTA_TABLE_HANDLE: []byte{0, 0, 0, 123},
				linux.NFTA_TABLE_USE:    []byte{0, 0, 0, 0, 0, 0, 0, 1},
			},
		},
		{
			name: "attrGtThanMax",
			msg: func() *nlmsg.Message {
				m := createEmptyNlMsg()
				m.PutAttr(linux.NFTA_TABLE_HANDLE, nlmsg.PutU32(123))
				return m
			}(),
			policy: []NlaPolicy{
				linux.NFTA_TABLE_HANDLE: {nlaType: linux.NLA_BE32, validator: AttrMaxValidator[uint32](1)},
			},
		},
		{
			name: "attrWithInvalidType",
			msg: func() *nlmsg.Message {
				m := createEmptyNlMsg()
				m.PutAttr(linux.NFTA_TABLE_HANDLE, nlmsg.PutU16(123))
				return m
			}(),
			policy: []NlaPolicy{
				linux.NFTA_TABLE_HANDLE: {nlaType: linux.NLA_U32},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			var nfGenMsg linux.NetFilterGenMsg
			attr, ok := test.msg.GetData(&nfGenMsg)
			if !ok {
				t.Fatalf("GetData() failed for msg %v", test.msg)
			}
			got, gotErr := NfParseWithOpts(attr, &NfParseOpts{Policy: test.policy})
			wantErr := test.want == nil
			if wantErr && gotErr == nil {
				t.Fatalf("NfParseWithOpts() failed, want error, got nil error")
			}
			if gotErr != nil {
				return
			}
			if diff := cmp.Diff(test.want, got); diff != "" {
				t.Fatalf("NfParseWithOpts() returned diff (-want +got): %v", diff)
			}
		})
	}
}

func TestNfAttrParserNestedArray(t *testing.T) {
	putMsg := func(b []byte) *nlmsg.Message {
		m := createEmptyNlMsg()
		m.PutNestedAttr(linux.NFTA_RULE_EXPRESSIONS, b)
		return m
	}

	newArray := func(elems ...nlmsg.NestedAttr) nlmsg.NestedAttr {
		arr := nlmsg.NestedAttr{}
		for _, elem := range elems {
			arr.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(elem))
		}
		return arr
	}

	tests := []struct {
		name  string
		setup func() (msg *nlmsg.Message, policy []NlaPolicy, want map[uint16]nlmsg.BytesView)
	}{
		{
			name: "validArray",
			setup: func() (*nlmsg.Message, []NlaPolicy, map[uint16]nlmsg.BytesView) {
				//
				// [ NFTA_RULE_EXPRESSIONS (Outer Array Container) ]
				//   └── [ NFTA_LIST_ELEM (Element #1) ]
				//         ├── [ NFTA_PAYLOAD_SREG ] (Value: 100)
				//         └── [ NFTA_PAYLOAD_DREG ] (Value: 123)
				elementPolicy := []NlaPolicy{
					linux.NFTA_PAYLOAD_SREG: {nlaType: linux.NLA_U32},
					linux.NFTA_PAYLOAD_DREG: {nlaType: linux.NLA_U32},
				}

				elems := [2]nlmsg.NestedAttr{}
				elems[0].PutAttr(linux.NFTA_PAYLOAD_SREG, nlmsg.PutU32(100))
				elems[0].PutAttr(linux.NFTA_PAYLOAD_DREG, nlmsg.PutU32(123))
				elems[1].PutAttr(linux.NFTA_PAYLOAD_SREG, nlmsg.
					PutU32(200))
				arrayAttr := newArray(elems[:]...)

				policy := []NlaPolicy{
					linux.NFTA_RULE_EXPRESSIONS: {
						nlaType:   linux.NLA_NESTED_ARRAY,
						validator: AttrArrayValidator(elementPolicy),
					},
				}

				return putMsg(arrayAttr), policy, map[uint16]nlmsg.BytesView{
					linux.NFTA_RULE_EXPRESSIONS: nlmsg.BytesView(arrayAttr),
				}
			},
		},
		{
			name: "validArrayOfArrays",
			setup: func() (*nlmsg.Message, []NlaPolicy, map[uint16]nlmsg.BytesView) {
				//
				// [ NFTA_RULE_EXPRESSIONS (Outer Array Container) ]
				//   ├── [ NFTA_LIST_ELEM (Outer Element Wrapper #1) ]
				//   │     └── [ NFTA_EXPR_DATA (Inner Array Container #1) ]
				//   │           └── [ NFTA_LIST_ELEM (Inner Element Wrapper #1) ]
				//   │                 └── [ NFTA_IMMEDIATE_DREG ] (Value: 100)
				//   │
				//   └── [ NFTA_LIST_ELEM (Outer Element Wrapper #2) ]
				//         └── [ NFTA_EXPR_DATA (Inner Array Container #2) ]
				//               └── [ NFTA_LIST_ELEM (Inner Element Wrapper #2) ]
				//                     └── [ NFTA_IMMEDIATE_DREG ] (Value: 200)
				elementPolicy := []NlaPolicy{
					linux.NFTA_IMMEDIATE_DREG: {nlaType: linux.NLA_U32},
				}
				innerArrayPolicy := []NlaPolicy{
					linux.NFTA_EXPR_DATA: {
						nlaType:   linux.NLA_NESTED_ARRAY,
						validator: AttrArrayValidator(elementPolicy),
					},
				}

				innerElems := [2]nlmsg.NestedAttr{}
				innerElems[0].PutAttr(linux.NFTA_IMMEDIATE_DREG, nlmsg.PutU32(100))
				innerElems[1].PutAttr(linux.NFTA_IMMEDIATE_DREG, nlmsg.PutU32(200))

				outerElems := [2]nlmsg.NestedAttr{}
				outerElems[0].PutAttr(linux.NFTA_EXPR_DATA, primitive.AsByteSlice(newArray(innerElems[0])))
				outerElems[1].PutAttr(linux.NFTA_EXPR_DATA, primitive.AsByteSlice(newArray(innerElems[1])))

				outerArray := newArray(outerElems[:]...)

				policy := []NlaPolicy{
					linux.NFTA_RULE_EXPRESSIONS: {
						nlaType:   linux.NLA_NESTED_ARRAY,
						validator: AttrArrayValidator(innerArrayPolicy),
					},
				}

				return putMsg(outerArray), policy, map[uint16]nlmsg.BytesView{
					linux.NFTA_RULE_EXPRESSIONS: nlmsg.BytesView(outerArray),
				}
			},
		},
		{
			name: "invalidElement",
			setup: func() (*nlmsg.Message, []NlaPolicy, map[uint16]nlmsg.BytesView) {
				//
				// [ NFTA_RULE_EXPRESSIONS (Outer Array Container) ]
				//   ├── [ NFTA_LIST_ELEM (Element Wrapper #1) ]
				//   │     └── [ NFTA_PAYLOAD_SREG ] (Value: 2)
				//   │
				//   └── [ NFTA_LIST_ELEM (Element Wrapper #2) ]
				//         └── [ NFTA_PAYLOAD_SREG ] (Value: 10)
				elementPolicy := []NlaPolicy{
					linux.NFTA_PAYLOAD_SREG: {
						nlaType:   linux.NLA_U32,
						validator: AttrMaxValidator[uint32](8),
					},
				}

				elems := [2]nlmsg.NestedAttr{}
				elems[0].PutAttr(linux.NFTA_PAYLOAD_SREG, nlmsg.PutU32(2))
				// Invalid value exceeds max value of validator.
				elems[1].PutAttr(linux.NFTA_PAYLOAD_SREG, nlmsg.PutU32(10))

				arrayAttr := newArray(elems[:]...)

				policy := []NlaPolicy{
					linux.NFTA_RULE_EXPRESSIONS: {
						nlaType:   linux.NLA_NESTED_ARRAY,
						validator: AttrArrayValidator(elementPolicy),
					},
				}

				return putMsg(arrayAttr), policy, nil
			},
		},
		{
			name: "recursionDepthExceeded",
			setup: func() (*nlmsg.Message, []NlaPolicy, map[uint16]nlmsg.BytesView) {
				//
				// [ NFTA_RULE_EXPRESSIONS (Outer Array Container) ]
				//   └── [ NFTA_EXPR_DATA ] (Nesting Layer #1)
				//         └── [ NFTA_EXPR_DATA ] (Nesting Layer #2)
				//               └── ... (Nesting Layer #10)
				recursePolicy := []NlaPolicy{
					linux.NFTA_EXPR_DATA: {
						nlaType:   linux.NLA_NESTED_ARRAY,
						validator: AttrArrayValidator(nil),
					},
				}
				recursePolicy[linux.NFTA_EXPR_DATA].validator = AttrArrayValidator(recursePolicy)

				policy := []NlaPolicy{
					linux.NFTA_RULE_EXPRESSIONS: {
						nlaType:   linux.NLA_NESTED_ARRAY,
						validator: AttrArrayValidator(recursePolicy),
					},
				}

				curr := nlmsg.NestedAttr{}
				// Build a nested attribute with maxPolicyRecursionDepth + 1 nesting.
				for i := 0; i < maxPolicyRecursionDepth; i++ {
					upper := nlmsg.NestedAttr{}
					upper.PutAttr(linux.NFTA_EXPR_DATA, primitive.AsByteSlice(curr))
					curr = upper
				}

				return putMsg(curr), policy, nil
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			msg, policy, want := test.setup()
			var nfGenMsg linux.NetFilterGenMsg
			attr, ok := msg.GetData(&nfGenMsg)
			if !ok {
				t.Fatalf("GetData() failed for msg %v", msg)
			}
			got, gotErr := NfParseWithOpts(attr, &NfParseOpts{Policy: policy})
			wantErr := want == nil
			if wantErr && gotErr == nil {
				t.Fatalf("NfParseWithOpts() failed, want error, got nil error")
			}
			if gotErr != nil {
				return
			}
			if diff := cmp.Diff(want, got); diff != "" {
				t.Fatalf("NfParseWithOpts() returned diff (-want +got): %v", diff)
			}
		})
	}
}

func TestAttrNetToHost(t *testing.T) {
	const attr uint16 = 1
	tests := []struct {
		name   string
		attrs  map[uint16]nlmsg.BytesView
		wantOK bool
		want   any
	}{
		{
			name:   "uint8",
			attrs:  map[uint16]nlmsg.BytesView{attr: nlmsg.BytesView([]byte{0x01})},
			wantOK: true,
			want:   any(uint8(0x01)),
		},
		{
			name:   "uint16",
			attrs:  map[uint16]nlmsg.BytesView{attr: nlmsg.BytesView([]byte{0x01, 0x02})},
			wantOK: true,
			want:   any(uint16(0x0102)),
		},
		{
			name:   "uint32",
			attrs:  map[uint16]nlmsg.BytesView{attr: nlmsg.BytesView([]byte{0x01, 0x02, 0x03, 0x04})},
			wantOK: true,
			want:   any(uint32(0x01020304)),
		},
		{
			name:   "uint64",
			attrs:  map[uint16]nlmsg.BytesView{attr: nlmsg.BytesView([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08})},
			wantOK: true,
			want:   any(uint64(0x0102030405060708)),
		},
		{
			name:   "empty",
			attrs:  map[uint16]nlmsg.BytesView{},
			wantOK: false,
		},
		{
			name:   "invalidUint8",
			attrs:  map[uint16]nlmsg.BytesView{attr: nlmsg.BytesView([]byte{})},
			wantOK: false,
			want:   any(uint8(0)),
		},
		{
			name:   "invalidUint16",
			attrs:  map[uint16]nlmsg.BytesView{attr: nlmsg.BytesView([]byte{0x01})},
			wantOK: false,
			want:   any(uint16(0)),
		},
		{
			name:   "invalidUint32",
			attrs:  map[uint16]nlmsg.BytesView{attr: nlmsg.BytesView([]byte{0x01})},
			wantOK: false,
			want:   any(uint32(0)),
		},
		{
			name:   "invalidUint64",
			attrs:  map[uint16]nlmsg.BytesView{attr: nlmsg.BytesView([]byte{0x01})},
			wantOK: false,
			want:   any(uint64(0)),
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			var got any
			var gotOk bool
			switch test.want.(type) {
			case uint8:
				got, gotOk = AttrNetToHost[uint8](attr, test.attrs)
			case uint16:
				got, gotOk = AttrNetToHost[uint16](attr, test.attrs)
			case uint32:
				got, gotOk = AttrNetToHost[uint32](attr, test.attrs)
			case uint64:
				got, gotOk = AttrNetToHost[uint64](attr, test.attrs)
			}
			if gotOk != test.wantOK {
				t.Errorf("AttrNetToHost: got ok %v, want ok %v", gotOk, test.wantOK)
			}
			if !reflect.DeepEqual(got, test.want) {
				t.Errorf("AttrNetToHost: got 0x%x, want 0x%x", got, test.want)
			}
		})
	}
}

func TestDumpOperations(t *testing.T) {
	tests := []struct {
		name     string
		op       operation
		validate func([]byte) error
	}{
		{
			name: "immediate",
			op:   mustCreateImmediate(t, linux.NFT_REG_1 /*dreg*/, []byte{1, 2, 3, 4} /*data*/, Verdict{}),
			validate: func(dump []byte) error {
				attrs, ok := NfParse(dump)
				if !ok {
					return fmt.Errorf("failed to parse dumped attributes")
				}
				reg, ok := AttrNetToHost[uint32](linux.NFTA_IMMEDIATE_DREG, attrs)
				if !ok {
					return fmt.Errorf("failed to get dreg value")
				}
				if reg != linux.NFT_REG_1 {
					return fmt.Errorf("unexpected dreg value: %d, want %d", reg, linux.NFT_REG_1)
				}
				dataAttrs, ok := attrs[linux.NFTA_IMMEDIATE_DATA]
				if !ok {
					return fmt.Errorf("failed to find data attributes")
				}
				data, ok := NfParse(nlmsg.AttrsView(dataAttrs))
				if !ok {
					return fmt.Errorf("failed to parse data attributes")
				}
				dataVal, ok := data[linux.NFTA_DATA_VALUE]
				if !ok {
					return fmt.Errorf("failed to find data value")
				}
				if !slices.Equal(dataVal, []byte{1, 2, 3, 4}) {
					return fmt.Errorf("unexpected data value: %v, want %v", dataVal, []byte{1, 2, 3, 4})
				}
				return nil
			},
		},
		{
			name: "comparison",
			op:   mustCreateComparison(t, linux.NFT_REG_2 /*sreg*/, linux.NFT_CMP_LT /*cop*/, []byte{1, 2, 3, 4} /*data*/),
			validate: func(dump []byte) error {
				attrs, ok := NfParse(dump)
				if !ok {
					return fmt.Errorf("failed to parse dumped attributes")
				}
				reg, ok := AttrNetToHost[uint32](linux.NFTA_CMP_SREG, attrs)
				if !ok {
					return fmt.Errorf("failed to get dreg value")
				}
				if reg != linux.NFT_REG_2 {
					return fmt.Errorf("unexpected dreg value: %d, want %d", reg, linux.NFT_REG_2)
				}
				cop, ok := AttrNetToHost[uint32](linux.NFTA_CMP_OP, attrs)
				if !ok {
					return fmt.Errorf("failed to get cop value")
				}
				if cop != linux.NFT_CMP_LT {
					return fmt.Errorf("unexpected cop value: %d, want %d", cop, linux.NFT_CMP_EQ)
				}
				dataAttrs, ok := attrs[linux.NFTA_CMP_DATA]
				if !ok {
					return fmt.Errorf("failed to find data attributes")
				}
				data, ok := NfParse(nlmsg.AttrsView(dataAttrs))
				if !ok {
					return fmt.Errorf("failed to parse data attributes")
				}
				dataVal, ok := data[linux.NFTA_DATA_VALUE]
				if !ok {
					return fmt.Errorf("failed to find data value")
				}
				if !slices.Equal(dataVal, []byte{1, 2, 3, 4}) {
					return fmt.Errorf("unexpected data value: %v, want %v", dataVal, []byte{1, 2, 3, 4})
				}
				return nil
			},
		},
		{
			name: "range",
			op:   mustCreateRanged(t, linux.NFT_REG_1, linux.NFT_RANGE_EQ, []byte{1}, []byte{2}),
			validate: func(dump []byte) error {
				// TODO: b/452648112 - Implement validation for ranged operation when dump is implemented.
				if dump != nil {
					return fmt.Errorf("unexpected dump: %v, want nil", dump)
				}
				return nil
			},
		},
		{
			name: "payloadLoad",
			op:   mustCreatePayloadLoad(t, linux.NFT_PAYLOAD_NETWORK_HEADER /*base*/, 4 /*offset*/, 4 /*len*/, linux.NFT_REG_1 /*dreg*/),
			validate: func(dump []byte) error {
				attrs, ok := NfParse(dump)
				if !ok {
					return fmt.Errorf("failed to parse dumped attributes")
				}
				reg, ok := AttrNetToHost[uint32](linux.NFTA_PAYLOAD_DREG, attrs)
				if !ok {
					return fmt.Errorf("failed to get dreg value")
				}
				if reg != linux.NFT_REG_1 {
					return fmt.Errorf("unexpected dreg value: %d, want %d", reg, linux.NFT_REG_1)
				}
				base, ok := AttrNetToHost[uint32](linux.NFTA_PAYLOAD_BASE, attrs)
				if !ok {
					return fmt.Errorf("failed to get base value")
				}
				if base != linux.NFT_PAYLOAD_NETWORK_HEADER {
					return fmt.Errorf("unexpected base value: %d, want %d", base, linux.NFT_PAYLOAD_NETWORK_HEADER)
				}
				offset, ok := AttrNetToHost[uint32](linux.NFTA_PAYLOAD_OFFSET, attrs)
				if !ok {
					return fmt.Errorf("failed to get offset value")
				}
				if offset != 4 {
					return fmt.Errorf("unexpected offset value: %d, want %d", offset, 4)
				}
				len, ok := AttrNetToHost[uint32](linux.NFTA_PAYLOAD_LEN, attrs)
				if !ok {
					return fmt.Errorf("failed to get len value")
				}
				if len != 4 {
					return fmt.Errorf("unexpected len value: %d, want %d", len, 4)
				}
				return nil
			},
		},
		{
			name: "payloadSet",
			op:   mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER /*base*/, 0 /*offset*/, 4 /*len*/, linux.NFT_REG_4 /*sreg*/, linux.NFT_PAYLOAD_CSUM_NONE /*csumType*/, 0 /*csumOff*/, 0 /*csumFlags*/),
			validate: func(dump []byte) error {
				attrs, ok := NfParse(dump)
				if !ok {
					return fmt.Errorf("failed to parse dumped attributes")
				}
				reg, ok := AttrNetToHost[uint32](linux.NFTA_PAYLOAD_SREG, attrs)
				if !ok {
					return fmt.Errorf("failed to get sreg value")
				}
				if reg != linux.NFT_REG_4 {
					return fmt.Errorf("unexpected sreg value: %d, want %d", reg, linux.NFT_REG_4)
				}
				base, ok := AttrNetToHost[uint32](linux.NFTA_PAYLOAD_BASE, attrs)
				if !ok {
					return fmt.Errorf("failed to get base value")
				}
				if base != linux.NFT_PAYLOAD_NETWORK_HEADER {
					return fmt.Errorf("unexpected base value: %d, want %d", base, linux.NFT_PAYLOAD_NETWORK_HEADER)
				}
				offset, ok := AttrNetToHost[uint32](linux.NFTA_PAYLOAD_OFFSET, attrs)
				if !ok {
					return fmt.Errorf("failed to get offset value")
				}
				if offset != 0 {
					return fmt.Errorf("unexpected offset value: %d, want %d", offset, 0)
				}
				len, ok := AttrNetToHost[uint32](linux.NFTA_PAYLOAD_LEN, attrs)
				if !ok {
					return fmt.Errorf("failed to get len value")
				}
				if len != 4 {
					return fmt.Errorf("unexpected len value: %d, want %d", len, 4)
				}
				csumType, ok := AttrNetToHost[uint32](linux.NFTA_PAYLOAD_CSUM_TYPE, attrs)
				if !ok {
					return fmt.Errorf("failed to get csum type value")
				}
				if csumType != linux.NFT_PAYLOAD_CSUM_NONE {
					return fmt.Errorf("unexpected csum type value: %d, want %d", csumType, linux.NFT_PAYLOAD_CSUM_NONE)
				}
				csumOffset, ok := AttrNetToHost[uint32](linux.NFTA_PAYLOAD_CSUM_OFFSET, attrs)
				if !ok {
					return fmt.Errorf("failed to get csum offset value")
				}
				if csumOffset != 0 {
					return fmt.Errorf("unexpected csum offset value: %d, want %d", csumOffset, 0)
				}
				csumFlags, ok := AttrNetToHost[uint32](linux.NFTA_PAYLOAD_CSUM_FLAGS, attrs)
				if !ok {
					return fmt.Errorf("failed to get csum flags value")
				}
				if csumFlags != 0 {
					return fmt.Errorf("unexpected csum flags value: %d, want %d", csumFlags, 0)
				}
				return nil
			},
		},
		{
			name: "bitwise",
			op:   mustCreateBitwiseBool(t, linux.NFT_REG_1, linux.NFT_REG_2, []byte{0xff}, []byte{0x00}),
			validate: func(dump []byte) error {
				attrs, ok := NfParse(dump)
				if !ok {
					return fmt.Errorf("failed to parse dumped attributes")
				}
				sreg, ok := AttrNetToHost[uint32](linux.NFTA_BITWISE_SREG, attrs)
				if !ok {
					return fmt.Errorf("failed to get sreg")
				}
				if sreg != linux.NFT_REG_1 {
					return fmt.Errorf("unexpected sreg: %d, want %d", sreg, linux.NFT_REG_1)
				}
				dreg, ok := AttrNetToHost[uint32](linux.NFTA_BITWISE_DREG, attrs)
				if !ok {
					return fmt.Errorf("failed to get dreg")
				}
				if dreg != linux.NFT_REG_2 {
					return fmt.Errorf("unexpected dreg: %d, want %d", dreg, linux.NFT_REG_2)
				}
				lenAttr, ok := AttrNetToHost[uint32](linux.NFTA_BITWISE_LEN, attrs)
				if !ok {
					return fmt.Errorf("failed to get len")
				}
				if lenAttr != 1 {
					return fmt.Errorf("unexpected len: %d, want %d", lenAttr, 1)
				}
				bop, ok := AttrNetToHost[uint32](linux.NFTA_BITWISE_OP, attrs)
				if !ok {
					return fmt.Errorf("failed to get op")
				}
				if bop != linux.NFT_BITWISE_BOOL {
					return fmt.Errorf("unexpected op: %d, want %d", bop, linux.NFT_BITWISE_BOOL)
				}

				maskAttr, ok := attrs[linux.NFTA_BITWISE_MASK]
				if !ok {
					return fmt.Errorf("failed to get mask")
				}
				maskAttrs, ok := NfParse(nlmsg.AttrsView(maskAttr))
				if !ok {
					return fmt.Errorf("failed to parse mask")
				}
				maskValue, err := parseDataAttrs(maskAttrs)
				if err != nil {
					return fmt.Errorf("failed to parse mask data: %v", err)
				}
				if !bytes.Equal(maskValue, []byte{0xff}) {
					return fmt.Errorf("unexpected mask value: %v, want %v", maskValue, []byte{0xff})
				}

				xorAttr, ok := attrs[linux.NFTA_BITWISE_XOR]
				if !ok {
					return fmt.Errorf("failed to get xor")
				}
				xorAttrs, ok := NfParse(nlmsg.AttrsView(xorAttr))
				if !ok {
					return fmt.Errorf("failed to parse xor")
				}
				xorValue, err := parseDataAttrs(xorAttrs)
				if err != nil {
					return fmt.Errorf("failed to parse xor data: %v", err)
				}
				if !bytes.Equal(xorValue, []byte{0x00}) {
					return fmt.Errorf("unexpected xor value: %v, want %v", xorValue, []byte{0x00})
				}

				return nil
			},
		},
		{
			name: "byteorder",
			op:   mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_2, linux.NFT_BYTEORDER_NTOH, 4, 4),
			validate: func(dump []byte) error {
				// TODO: b/452648112 - Implement validation for byteorder operation when dump is implemented.
				if dump != nil {
					return fmt.Errorf("unexpected dump: %v, want nil", dump)
				}
				return nil
			},
		},
		{
			name: "metaLoad",
			op:   mustCreateMetaLoad(t, linux.NFT_META_LEN, linux.NFT_REG_1),
			validate: func(dump []byte) error {
				attrs, ok := NfParse(dump)
				if !ok {
					return fmt.Errorf("failed to parse dumped attributes")
				}
				key, ok := AttrNetToHost[uint32](linux.NFTA_META_KEY, attrs)
				if !ok {
					return fmt.Errorf("failed to get key value")
				}
				if key != linux.NFT_META_LEN {
					return fmt.Errorf("unexpected key value: %d, want %d", key, linux.NFT_META_LEN)
				}
				reg, ok := AttrNetToHost[uint32](linux.NFTA_META_DREG, attrs)
				if !ok {
					return fmt.Errorf("failed to get dreg value")
				}
				if reg != linux.NFT_REG_1 {
					return fmt.Errorf("unexpected dreg value: %d, want %d", reg, linux.NFT_REG_1)
				}
				return nil
			},
		},
		{
			name: "metaSet",
			op:   mustCreateMetaSet(t, linux.NFT_META_PKTTYPE, linux.NFT_REG_1),
			validate: func(dump []byte) error {
				attrs, ok := NfParse(dump)
				if !ok {
					return fmt.Errorf("failed to parse dumped attributes")
				}
				key, ok := AttrNetToHost[uint32](linux.NFTA_META_KEY, attrs)
				if !ok {
					return fmt.Errorf("failed to get key value")
				}
				if key != linux.NFT_META_PKTTYPE {
					return fmt.Errorf("unexpected key value: %d, want %d", key, linux.NFT_META_PKTTYPE)
				}
				reg, ok := AttrNetToHost[uint32](linux.NFTA_META_SREG, attrs)
				if !ok {
					return fmt.Errorf("failed to get sreg value")
				}
				if reg != linux.NFT_REG_1 {
					return fmt.Errorf("unexpected sreg value: %d, want %d", reg, linux.NFT_REG_1)
				}
				return nil
			},
		},
		{
			name: "route",
			op:   mustCreateRoute(t, linux.NFT_RT_NEXTHOP4, linux.NFT_REG_1),
			validate: func(dump []byte) error {
				// TODO: b/452648112 - Implement validation for route operation when dump is implemented.
				if dump != nil {
					return fmt.Errorf("unexpected dump: %v, want nil", dump)
				}
				return nil
			},
		},
		{
			name: "counter",
			op:   newCounter(10 /*bytes*/, 20 /*packets*/),
			validate: func(dump []byte) error {
				attrs, ok := NfParse(dump)
				if !ok {
					return fmt.Errorf("failed to parse dumped attributes")
				}
				bytes, ok := AttrNetToHost[uint64](linux.NFTA_COUNTER_BYTES, attrs)
				if !ok {
					return fmt.Errorf("failed to get bytes value")
				}
				if bytes != 10 {
					return fmt.Errorf("unexpected bytes value: %d, want %d", bytes, 10)
				}
				packets, ok := AttrNetToHost[uint64](linux.NFTA_COUNTER_PACKETS, attrs)
				if !ok {
					return fmt.Errorf("failed to get packets value")
				}
				if packets != 20 {
					return fmt.Errorf("unexpected packets value: %d, want %d", packets, 20)
				}
				return nil
			},
		},
		{
			name: "lookup",
			op: &lookupOp{
				set:      &nftSet{name: "map_set"},
				sregIdx:  0,
				invert:   true,
				fillData: true,
				dregIdx:  16,
			},
			validate: func(dump []byte) error {
				attrs, ok := NfParse(dump)
				if !ok {
					return fmt.Errorf("failed to parse dumped attributes")
				}
				setName, ok := attrs[linux.NFTA_LOOKUP_SET]
				if !ok {
					return fmt.Errorf("failed to get set name")
				}
				if setName.String() != "map_set" {
					return fmt.Errorf("unexpected set name: %q, want %q", setName.String(), "map_set")
				}
				sreg, ok := AttrNetToHost[uint32](linux.NFTA_LOOKUP_SREG, attrs)
				if !ok {
					return fmt.Errorf("failed to get sreg")
				}
				if sreg != linux.NFT_REG_1 {
					return fmt.Errorf("unexpected sreg: %d, want %d", sreg, linux.NFT_REG_1)
				}
				dreg, ok := AttrNetToHost[uint32](linux.NFTA_LOOKUP_DREG, attrs)
				if !ok {
					return fmt.Errorf("failed to get dreg")
				}
				if dreg != linux.NFT_REG_2 {
					return fmt.Errorf("unexpected dreg: %d, want %d", dreg, linux.NFT_REG_2)
				}
				flags, ok := AttrNetToHost[uint32](linux.NFTA_LOOKUP_FLAGS, attrs)
				if !ok {
					return fmt.Errorf("failed to get flags")
				}
				if flags != linux.NFT_LOOKUP_F_INV {
					return fmt.Errorf("unexpected flags: %d, want %d", flags, linux.NFT_LOOKUP_F_INV)
				}
				return nil
			},
		},
		{
			name: "last",
			op:   &last{},
			validate: func(dump []byte) error {
				// TODO: b/452648112 - Implement validation for last operation when dump is implemented.
				if dump != nil {
					return fmt.Errorf("unexpected dump: %v, want nil", dump)
				}
				return nil
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			dump, err := test.op.Dump()
			if err != nil {
				t.Fatalf("unexpected error for Dump(): %v", err)
			}
			if err := test.validate(dump); err != nil {
				t.Errorf("validation failed: %v", err)
			}
		})
	}
}

// TestBaseChainEvalOrder tests that base chains are evaluated in priority order.
func TestBaseChainEvalOrder(t *testing.T) {
	nf := newNFTablesStd()
	hook := stack.NFOutput
	chainNum := 0
	createBaseChains := func(t *testing.T, ip stack.AddressFamily, bcType BaseChainType, priority int) *NFTables {
		tab, err := nf.AddTable(ip, "test_table_"+stack.AddressFamilyStrings[ip], false)
		if err != nil {
			t.Fatalf("AddTable failed: %v", err)
		}
		bcInfo := &BaseChainInfo{
			BcType:   bcType,
			Hook:     hook,
			Priority: NewIntPriority(priority),
		}
		_, err = nf.AddChainToTable(tab, fmt.Sprintf("chain_%d", chainNum), bcInfo, "test", false, 0, nil, linux.NF_ACCEPT)
		if err != nil {
			t.Fatalf("AddChain failed: %v", err)
		}
		chainNum++
		return nf
	}
	// Create chains with filter base chains for IP, Inet, and IP6.
	nf = createBaseChains(t, stack.IP, BaseChainTypeFilter, 1)
	nf = createBaseChains(t, stack.Inet, BaseChainTypeFilter, -100)
	nf = createBaseChains(t, stack.IP6, BaseChainTypeFilter, 1)

	// Create chains with nat base chains for IP, Inet, and IP6.
	nf = createBaseChains(t, stack.IP, BaseChainTypeNat, 10)
	nf = createBaseChains(t, stack.Inet, BaseChainTypeNat, -50)
	nf = createBaseChains(t, stack.IP6, BaseChainTypeNat, 10)

	validateOrder := func(t *testing.T, family stack.AddressFamily, wantNATChains bool, wantLen int) {
		baseChains, err := nf.getBaseChainsForEvaluation(family, hook, wantNATChains)
		if err != nil {
			t.Fatalf("getBaseChainsForEvaluation failed: %v", err)
		}
		if len(baseChains) != wantLen {
			t.Errorf("unexpected number of base chains: %d, want %d", len(baseChains), wantLen)
		}
		prevPriority := math.MinInt32
		for _, bc := range baseChains {
			if wantNATChains && bc.baseChainInfo.BcType != BaseChainTypeNat {
				t.Fatalf("base chain type: got %v, want nat", bc.baseChainInfo.BcType.String())
			} else if !wantNATChains && bc.baseChainInfo.BcType == BaseChainTypeNat {
				t.Fatalf("base chain type got nat, want %v", bc.baseChainInfo.BcType.String())
			}
			p := bc.baseChainInfo.Priority.GetValue()
			if p < prevPriority {
				t.Fatalf("base chain priority %d is less than previous priority %d", p, prevPriority)
			}
			prevPriority = p
		}
	}

	wantLen := 2 // ip[4|6] + inet
	validateOrder(t, stack.IP, false /*wantNATChains*/, wantLen)
	validateOrder(t, stack.IP6, false /*wantNATChains*/, wantLen)
	validateOrder(t, stack.IP, true /*wantNATChains*/, wantLen)
	validateOrder(t, stack.IP, true /*wantNATChains*/, wantLen)
}

func TestDeepCopyIsolatesOperations(t *testing.T) {
	tests := []struct {
		name        string
		op          operation
		valuesEqual func(op1, op2 operation) bool
	}{
		{
			name: "immediate",
			op: &immediate{
				data: []byte{1, 2, 3},
			},
			valuesEqual: func(op1, op2 operation) bool {
				// Check that the pointers to the data are different.
				if &op1.(*immediate).data[0] == &op2.(*immediate).data[0] {
					return false
				}
				// Check that the data values are equal.
				return slices.Equal(op1.(*immediate).data, op2.(*immediate).data)
			},
		},
		{
			name: "comparison",
			op: &comparison{
				data: []byte{1, 2, 3},
			},
			valuesEqual: func(op1, op2 operation) bool {
				// Check that the pointers to the data are different.
				if &op1.(*comparison).data[0] == &op2.(*comparison).data[0] {
					return false
				}
				// Check that the data values are equal.
				return slices.Equal(op1.(*comparison).data, op2.(*comparison).data)
			},
		},
		{
			name: "ranged",
			op: &ranged{
				low:  []byte{1, 2},
				high: []byte{3, 4},
			},

			valuesEqual: func(op1, op2 operation) bool {
				// Check that the pointers are different.
				if &op1.(*ranged).low[0] == &op2.(*ranged).low[0] ||
					&op1.(*ranged).high[0] == &op2.(*ranged).high[0] {
					return false
				}
				// Check that the data values are equal.
				return slices.Equal(op1.(*ranged).low, op2.(*ranged).low) &&
					slices.Equal(op1.(*ranged).high, op2.(*ranged).high)
			},
		},
		{
			name: "bitwise",
			op: &bitwise{
				mask: []byte{1, 2},
				xor:  []byte{3, 4},
			},
			valuesEqual: func(op1, op2 operation) bool {
				if &op1.(*bitwise).mask[0] == &op2.(*bitwise).mask[0] ||
					&op1.(*bitwise).xor[0] == &op2.(*bitwise).xor[0] {
					return false
				}
				return slices.Equal(op1.(*bitwise).mask, op2.(*bitwise).mask) &&
					slices.Equal(op1.(*bitwise).xor, op2.(*bitwise).xor)
			},
		},
		{
			name: "counter",
			op:   newCounter(0, 0),
			valuesEqual: func(op1, op2 operation) bool {
				return op1.(*counter).packets.Load() == op2.(*counter).packets.Load()
			},
		},
		{
			name: "last",
			op:   &last{},
			valuesEqual: func(op1, op2 operation) bool {
				return op1.(*last).timestampMS.Load() == op2.(*last).timestampMS.Load()
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			nf := newNFTablesStd()
			tab, err := nf.AddTable(stack.IP, "test_table", false)
			if err != nil {
				t.Fatalf("AddTable failed, err: %v", err)
			}

			bc, err := nf.AddChainToTable(tab, "test_chain", arbitraryInfoPolicyAccept, "test chain", false, 0, nil, linux.NF_ACCEPT)
			if err != nil {
				t.Fatalf("AddChainToTable failed, err: %v", err)
			}

			rule := &Rule{}
			if err := rule.addOperation(test.op); err != nil {
				t.Fatalf("addOperation failed, err: %v", err)
			}
			if err := bc.RegisterRule(rule, -1 /*=index*/); err != nil {
				t.Fatalf("RegisterRule failed, err: %v", err)
			}

			nftCopy := nf.DeepCopy()
			origTable := nf.filters[stack.IP].tables["test_table"]
			origChain := origTable.chains["test_chain"]
			origOp := origChain.rules[0].ops[0]

			copyTable := nftCopy.filters[stack.IP].tables["test_table"]
			copyChain := copyTable.chains["test_chain"]
			copyOp := copyChain.rules[0].ops[0]

			if origOp == copyOp {
				t.Fatalf("wanted orig != copy, got %p == %p", origOp, copyOp)
			}
			if !test.valuesEqual(origOp, copyOp) {
				t.Fatalf("wanted valuesEqual(orig, copy) == true, got false")
			}
		})
	}
}

func TestGetSet(t *testing.T) {
	tabName := "test_table"
	setName := "test_set"

	tests := []struct {
		name         string
		setAttrs     map[uint16]nlmsg.BytesView
		getAttrs     map[uint16]nlmsg.BytesView
		getFlags     uint16
		expectGetErr *syserr.Error
		verifyMsg    func(*testing.T, *nlmsg.MessageSet)
	}{
		{
			name: "GetSet",
			setAttrs: map[uint16]nlmsg.BytesView{
				linux.NFTA_SET_TABLE:     nlmsg.BytesView(tabName),
				linux.NFTA_SET_NAME:      nlmsg.BytesView(setName),
				linux.NFTA_SET_ID:        nlmsg.BytesView([]byte{0, 0, 0, 1}),
				linux.NFTA_SET_KEY_LEN:   nlmsg.BytesView([]byte{0, 0, 0, 4}),
				linux.NFTA_SET_FLAGS:     nlmsg.BytesView([]byte{0, 0, 0, 8}),
				linux.NFTA_SET_DATA_TYPE: nlmsg.BytesView([]byte{0xff, 0xff, 0xff, 0x00}),
			},
			getAttrs: map[uint16]nlmsg.BytesView{
				linux.NFTA_SET_TABLE: nlmsg.BytesView(tabName),
				linux.NFTA_SET_NAME:  nlmsg.BytesView(setName),
			},
			verifyMsg: func(t *testing.T, ms *nlmsg.MessageSet) {
				if ms.Multi {
					t.Errorf("Expected Multi to be false for non-DUMP GetSet")
				}
				if len(ms.Messages) != 1 {
					t.Fatalf("Expected 1 message, got %d", len(ms.Messages))
				}
				attrs, ok := ms.Messages[0].GetData(&linux.NetFilterGenMsg{})
				if !ok {
					t.Fatalf("Failed to extract NetFilterGenMsg")
				}
				attrMap, ok := NfParse(attrs)
				if !ok {
					t.Fatalf("Failed to parse netlink attributes")
				}
				if string(attrMap[linux.NFTA_SET_TABLE][:len(tabName)]) != tabName {
					t.Errorf("Unexpected table name: got %q, want %q", attrMap[linux.NFTA_SET_TABLE], tabName)
				}
				if string(attrMap[linux.NFTA_SET_NAME][:len(setName)]) != setName {
					t.Errorf("Unexpected set name: got %q, want %q", attrMap[linux.NFTA_SET_NAME], setName)
				}
			},
		},
		{
			name: "GetAllSets",
			setAttrs: map[uint16]nlmsg.BytesView{
				linux.NFTA_SET_TABLE:     nlmsg.BytesView(tabName),
				linux.NFTA_SET_NAME:      nlmsg.BytesView(setName),
				linux.NFTA_SET_ID:        nlmsg.BytesView([]byte{0, 0, 0, 1}),
				linux.NFTA_SET_KEY_LEN:   nlmsg.BytesView([]byte{0, 0, 0, 4}),
				linux.NFTA_SET_FLAGS:     nlmsg.BytesView([]byte{0, 0, 0, 8}),
				linux.NFTA_SET_DATA_TYPE: nlmsg.BytesView([]byte{0xff, 0xff, 0xff, 0x00}),
			},
			getAttrs: map[uint16]nlmsg.BytesView{},
			getFlags: linux.NLM_F_DUMP,
			verifyMsg: func(t *testing.T, ms *nlmsg.MessageSet) {
				if !ms.Multi {
					t.Errorf("Expected Multi to be true for DUMP GetSet")
				}
				if len(ms.Messages) != 1 {
					t.Fatalf("Expected 1 message, got %d", len(ms.Messages))
				}
			},
		},
		{
			name: "GetNonExistentSet",
			setAttrs: map[uint16]nlmsg.BytesView{
				linux.NFTA_SET_TABLE:     nlmsg.BytesView(tabName),
				linux.NFTA_SET_NAME:      nlmsg.BytesView(setName),
				linux.NFTA_SET_ID:        nlmsg.BytesView([]byte{0, 0, 0, 1}),
				linux.NFTA_SET_KEY_LEN:   nlmsg.BytesView([]byte{0, 0, 0, 4}),
				linux.NFTA_SET_FLAGS:     nlmsg.BytesView([]byte{0, 0, 0, 8}),
				linux.NFTA_SET_DATA_TYPE: nlmsg.BytesView([]byte{0xff, 0xff, 0xff, 0x00}),
			},
			getAttrs: map[uint16]nlmsg.BytesView{
				linux.NFTA_SET_TABLE: nlmsg.BytesView(tabName),
				linux.NFTA_SET_NAME:  nlmsg.BytesView("BAD_SET"),
			},
			expectGetErr: syserr.ErrInvalidArgument,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			nf := &NFTables{}
			nf.filters[stack.Inet] = &addressFamilyFilter{
				family: stack.Inet,
				tables: make(map[string]*Table),
			}

			tab := &Table{
				name:       tabName,
				sets:       make(map[string]*nftSet),
				setHandles: make(map[uint64]*nftSet),
				afFilter:   nf.filters[stack.Inet],
			}
			nf.filters[stack.Inet].tables[tabName] = tab

			if tc.setAttrs != nil {
				ms := &nlmsg.MessageSet{Multi: false, PortID: 100}
				if err := nf.NewSet(tc.setAttrs, stack.Inet, linux.NLM_F_CREATE, ms); err != nil {
					t.Fatalf("NewSet failed: %v", err)
				}
			}

			ms := &nlmsg.MessageSet{Multi: false, PortID: 100}
			err := nf.GetSet(tc.getAttrs, stack.Inet, tc.getFlags, ms)
			if tc.expectGetErr != nil {
				if err == nil || err.GetError() != tc.expectGetErr {
					t.Fatalf("Expected GetSet error %v, got %v", tc.expectGetErr, err)
				}
				return
			}
			if err != nil {
				t.Fatalf("GetSet failed: %v", err)
			}
			if tc.verifyMsg != nil {
				tc.verifyMsg(t, ms)
			}
		})
	}
}

type wantSetElem struct {
	key        []byte
	userData   []byte
	verdict    *Verdict
	isCatchAll bool
}

func parseDumpedElements(t *testing.T, ms *nlmsg.MessageSet) []wantSetElem {
	var parsed []wantSetElem
	for _, msg := range ms.Messages {
		msgData, ok := msg.GetData(&linux.NetFilterGenMsg{})
		if !ok {
			continue
		}
		attrMap, ok := NfParse(msgData)
		if !ok {
			continue
		}
		elementsAttr, ok := attrMap[linux.NFTA_SET_ELEM_LIST_ELEMENTS]
		if !ok {
			continue
		}

		elementsView := nlmsg.AttrsView(elementsAttr)
		for len(elementsView) > 0 {
			hdrType, elem, rest, ok := elementsView.ParseFirst()
			if !ok {
				t.Fatalf("Failed to parse nested element in list")
			}
			elementsView = rest
			if hdrType.Type != linux.NFTA_LIST_ELEM {
				continue
			}

			elemMap, ok := NfParse(nlmsg.AttrsView(elem))
			if !ok {
				continue
			}

			var item wantSetElem
			flagsAttr, flagsExists := elemMap[linux.NFTA_SET_ELEM_FLAGS]
			if flagsExists {
				flagVal := binary.BigEndian.Uint32([]byte(flagsAttr))
				if uint16(flagVal)&linux.NFT_SET_ELEM_CATCHALL != 0 {
					item.isCatchAll = true
				}
			}

			if !item.isCatchAll {
				keyAttr, ok := elemMap[linux.NFTA_SET_ELEM_KEY]
				if ok {
					keyMap, _ := NfParse(nlmsg.AttrsView(keyAttr))
					keyData := keyMap[linux.NFTA_DATA_VALUE]
					item.key = slices.Clone(keyData)
				}
			}

			userDataAttr, userDataExists := elemMap[linux.NFTA_SET_ELEM_USERDATA]
			if userDataExists {
				item.userData = slices.Clone(userDataAttr)
			}

			dataAttr, dataExists := elemMap[linux.NFTA_SET_ELEM_DATA]
			if dataExists {
				dataMap, _ := NfParse(nlmsg.AttrsView(dataAttr))
				verdictData, ok := dataMap[linux.NFTA_DATA_VERDICT]
				if ok {
					verdictMap, _ := NfParse(nlmsg.AttrsView(verdictData))
					verdictCode, ok := AttrNetToHost[uint32](linux.NFTA_VERDICT_CODE, verdictMap)
					if ok {
						item.verdict = &Verdict{Code: verdictCode}
					}
				}
			}
			parsed = append(parsed, item)
		}
	}
	return parsed
}

func TestGetSetElements(t *testing.T) {
	tabName := "test_table"
	setName := "test_set"

	key1 := []byte{192, 168, 1, 1}
	keyData1, _ := dumpDataAttr(key1)
	verdict1, _ := dumpVerdictDataAttr(Verdict{Code: VC(linux.NF_ACCEPT)})

	key2 := []byte{10, 0, 0, 1}
	keyData2, _ := dumpDataAttr(key2)
	verdict2, _ := dumpVerdictDataAttr(Verdict{Code: VC(linux.NF_DROP)})
	userData2 := []byte("my-element-userdata")

	// Standard element 1
	var qElem1 nlmsg.NestedAttr
	qElem1.PutAttr(linux.NFTA_SET_ELEM_KEY, primitive.AsByteSlice(keyData1))
	qElem1.PutAttr(linux.NFTA_SET_ELEM_DATA, primitive.AsByteSlice(verdict1))

	// Userdata element 2
	var qElem2 nlmsg.NestedAttr
	qElem2.PutAttr(linux.NFTA_SET_ELEM_KEY, primitive.AsByteSlice(keyData2))
	qElem2.PutAttr(linux.NFTA_SET_ELEM_DATA, primitive.AsByteSlice(verdict2))
	qElem2.PutAttr(linux.NFTA_SET_ELEM_USERDATA, primitive.AsByteSlice(userData2))

	// Catchall element 3
	var qElem3 nlmsg.NestedAttr
	qElem3.PutAttr(linux.NFTA_SET_ELEM_FLAGS, nlmsg.PutU32(uint32(linux.NFT_SET_ELEM_CATCHALL)))
	qElem3.PutAttr(linux.NFTA_SET_ELEM_DATA, primitive.AsByteSlice(verdict1))

	tests := []struct {
		name             string
		elemAttrs        nlmsg.AttrsView            // elements to create via NewSetElements
		getAttrs         map[uint16]nlmsg.BytesView // elements to get via GetSetElements
		getFlags         uint16
		expectNewElemErr *syserr.Error
		expectGetErr     *syserr.Error
		wanted           []wantSetElem
	}{
		{
			name:      "GetEmptyElements",
			elemAttrs: nil,
			getAttrs: map[uint16]nlmsg.BytesView{
				linux.NFTA_SET_ELEM_LIST_TABLE: nlmsg.BytesView(tabName),
				linux.NFTA_SET_ELEM_LIST_SET:   nlmsg.BytesView(setName),
			},
			getFlags: linux.NLM_F_DUMP,
			wanted:   nil,
		},
		{
			name: "GetAllElements",
			elemAttrs: func() nlmsg.AttrsView {
				var list nlmsg.NestedAttr
				list.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(qElem1))
				list.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(qElem2))
				list.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(qElem3))

				var attrs nlmsg.NestedAttr
				attrs.PutAttrString(linux.NFTA_SET_ELEM_LIST_TABLE, tabName)
				attrs.PutAttrString(linux.NFTA_SET_ELEM_LIST_SET, setName)
				attrs.PutAttr(linux.NFTA_SET_ELEM_LIST_ELEMENTS, primitive.AsByteSlice(list))
				return nlmsg.AttrsView(attrs)
			}(),
			getAttrs: map[uint16]nlmsg.BytesView{
				linux.NFTA_SET_ELEM_LIST_TABLE: nlmsg.BytesView(tabName),
				linux.NFTA_SET_ELEM_LIST_SET:   nlmsg.BytesView(setName),
			},
			getFlags: linux.NLM_F_DUMP,
			wanted: []wantSetElem{
				{
					key:     key1,
					verdict: &Verdict{Code: VC(linux.NF_ACCEPT)},
				},
				{
					key:      key2,
					userData: userData2,
					verdict:  &Verdict{Code: VC(linux.NF_DROP)},
				},
				{
					isCatchAll: true,
					verdict:    &Verdict{Code: VC(linux.NF_ACCEPT)},
				},
			},
		},
		{
			name: "GetElement",
			elemAttrs: func() nlmsg.AttrsView {
				var list nlmsg.NestedAttr
				list.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(qElem2))

				var attrs nlmsg.NestedAttr
				attrs.PutAttrString(linux.NFTA_SET_ELEM_LIST_TABLE, tabName)
				attrs.PutAttrString(linux.NFTA_SET_ELEM_LIST_SET, setName)
				attrs.PutAttr(linux.NFTA_SET_ELEM_LIST_ELEMENTS, primitive.AsByteSlice(list))
				return nlmsg.AttrsView(attrs)
			}(),
			getAttrs: func() map[uint16]nlmsg.BytesView {
				var queryElemAttr nlmsg.NestedAttr
				queryKeyData, _ := dumpDataAttr(key2)
				queryElemAttr.PutAttr(linux.NFTA_SET_ELEM_KEY, primitive.AsByteSlice(queryKeyData))

				var queryListAttr nlmsg.NestedAttr
				queryListAttr.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(queryElemAttr))
				return map[uint16]nlmsg.BytesView{
					linux.NFTA_SET_ELEM_LIST_TABLE:    nlmsg.BytesView(tabName),
					linux.NFTA_SET_ELEM_LIST_SET:      nlmsg.BytesView(setName),
					linux.NFTA_SET_ELEM_LIST_ELEMENTS: nlmsg.BytesView(queryListAttr),
				}
			}(),
			wanted: []wantSetElem{
				{
					key:      key2,
					userData: userData2,
					verdict:  &Verdict{Code: VC(linux.NF_DROP)},
				},
			},
		},
		{
			name: "GetElementsFromNonExistentSet",
			getAttrs: map[uint16]nlmsg.BytesView{
				linux.NFTA_SET_ELEM_LIST_TABLE: nlmsg.BytesView(tabName),
				linux.NFTA_SET_ELEM_LIST_SET:   nlmsg.BytesView("BAD_SET"),
			},
			getFlags:     linux.NLM_F_DUMP,
			expectGetErr: syserr.ErrNoFileOrDir,
		},
		{
			name: "GetNonExistentElement",
			getAttrs: func() map[uint16]nlmsg.BytesView {
				var queryElemAttr nlmsg.NestedAttr
				queryKeyData, _ := dumpDataAttr([]byte{255, 255, 255, 255})
				queryElemAttr.PutAttr(linux.NFTA_SET_ELEM_KEY, primitive.AsByteSlice(queryKeyData))

				var queryListAttr nlmsg.NestedAttr
				queryListAttr.PutAttr(linux.NFTA_LIST_ELEM, primitive.AsByteSlice(queryElemAttr))
				return map[uint16]nlmsg.BytesView{
					linux.NFTA_SET_ELEM_LIST_TABLE:    nlmsg.BytesView(tabName),
					linux.NFTA_SET_ELEM_LIST_SET:      nlmsg.BytesView(setName),
					linux.NFTA_SET_ELEM_LIST_ELEMENTS: nlmsg.BytesView(queryListAttr),
				}
			}(),
			expectGetErr: syserr.ErrNoFileOrDir,
		},
		{
			name: "GetElementsMissingElementsAttr",
			getAttrs: map[uint16]nlmsg.BytesView{
				linux.NFTA_SET_ELEM_LIST_TABLE: nlmsg.BytesView(tabName),
				linux.NFTA_SET_ELEM_LIST_SET:   nlmsg.BytesView(setName),
			},
			expectGetErr: syserr.ErrInvalidArgument,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			nf := &NFTables{}
			nf.filters[stack.Inet] = &addressFamilyFilter{
				family: stack.Inet,
				tables: make(map[string]*Table),
			}

			tab := &Table{
				name:       tabName,
				sets:       make(map[string]*nftSet),
				setHandles: make(map[uint64]*nftSet),
				afFilter:   nf.filters[stack.Inet],
			}
			nf.filters[stack.Inet].tables[tabName] = tab

			// Pre-create the set where elements will be added/queried
			newSetAttrs := map[uint16]nlmsg.BytesView{
				linux.NFTA_SET_TABLE:     nlmsg.BytesView(tabName),
				linux.NFTA_SET_NAME:      nlmsg.BytesView(setName),
				linux.NFTA_SET_ID:        nlmsg.BytesView([]byte{0, 0, 0, 1}),
				linux.NFTA_SET_KEY_LEN:   nlmsg.BytesView([]byte{0, 0, 0, 4}),
				linux.NFTA_SET_FLAGS:     nlmsg.BytesView([]byte{0, 0, 0, 8}),
				linux.NFTA_SET_DATA_TYPE: nlmsg.BytesView([]byte{0xff, 0xff, 0xff, 0x00}),
			}
			if err := nf.NewSet(newSetAttrs, stack.Inet, linux.NLM_F_CREATE, &nlmsg.MessageSet{}); err != nil {
				t.Fatalf("Setup NewSet failed: %v", err)
			}

			if tc.elemAttrs != nil {
				ms := &nlmsg.MessageSet{Multi: false, PortID: 100}
				err := nf.NewSetElements(tc.elemAttrs, stack.Inet, 0, ms)
				if tc.expectNewElemErr != nil {
					if err == nil || err.GetError() != tc.expectNewElemErr {
						t.Fatalf("Expected NewSetElements error %v, got %v", tc.expectNewElemErr, err)
					}
					return
				}
				if err != nil {
					t.Fatalf("NewSetElements failed: %v", err)
				}
			}

			ms := &nlmsg.MessageSet{Multi: false, PortID: 100}
			err := nf.GetSetElements(tc.getAttrs, stack.Inet, tc.getFlags, ms)
			if tc.expectGetErr != nil {
				if err == nil || err.GetError() != tc.expectGetErr {
					t.Fatalf("Expected GetSetElements error %v, got %v", tc.expectGetErr, err)
				}
				return
			}
			if err != nil {
				t.Fatalf("GetSetElements failed: %v", err)
			}

			// Validate returned elements against expected elements list
			parsed := parseDumpedElements(t, ms)
			if diff := cmp.Diff(tc.wanted, parsed, cmp.AllowUnexported(wantSetElem{})); diff != "" {
				t.Errorf("GetSetElements returned unexpected elements diff (-want +got):\n%s", diff)
			}
		})
	}
}

func TestSetMapRemoveElement(t *testing.T) {
	key1 := []byte{192, 168, 1, 1}
	key2 := []byte{10, 0, 0, 1}
	key3 := []byte{172, 16, 0, 1}
	keyNone := []byte{1, 2, 3, 4}

	testCases := []struct {
		name           string
		initialElems   [][]byte
		keyToRemove    []byte
		wantErr        *syserr.Error
		wantElems      [][]byte
		wantBackendMap map[string]int
	}{
		{
			name:         "delete first element",
			initialElems: [][]byte{key1, key2, key3},
			keyToRemove:  key1,
			wantElems:    [][]byte{key3, key2},
			wantBackendMap: map[string]int{
				string(key3): 0,
				string(key2): 1,
			},
		},
		{
			name:         "delete middle element",
			initialElems: [][]byte{key1, key2, key3},
			keyToRemove:  key2,
			wantElems:    [][]byte{key1, key3},
			wantBackendMap: map[string]int{
				string(key1): 0,
				string(key3): 1,
			},
		},
		{
			name:         "delete last element",
			initialElems: [][]byte{key1, key2, key3},
			keyToRemove:  key3,
			wantElems:    [][]byte{key1, key2},
			wantBackendMap: map[string]int{
				string(key1): 0,
				string(key2): 1,
			},
		},
		{
			name:         "delete non-existent element",
			initialElems: [][]byte{key1, key2, key3},
			keyToRemove:  keyNone,
			wantErr:      syserr.ErrNoFileOrDir,
			wantElems:    [][]byte{key1, key2, key3},
			wantBackendMap: map[string]int{
				string(key1): 0,
				string(key2): 1,
				string(key3): 2,
			},
		},
		{
			name:           "delete from single element set",
			initialElems:   [][]byte{key1},
			keyToRemove:    key1,
			wantBackendMap: map[string]int{},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			set := &nftSet{
				name:   "test_set",
				keyLen: 4,
				backend: &setMapBackend{
					m: make(map[string]int),
				},
			}

			for i, k := range tc.initialElems {
				set.elements = append(set.elements, nftSetElem{startKey: k})
				set.backend.(*setMapBackend).m[string(k)] = i
			}

			err := set.removeElement(tc.keyToRemove, nil)
			if tc.wantErr != nil {
				if err == nil || err.GetError() != tc.wantErr {
					t.Errorf("Expected error %v, got %v", tc.wantErr, err)
				}
				return
			}
			if err != nil {
				t.Fatalf("removeElement failed: %v", err)
			}

			var gotElems [][]byte
			for _, e := range set.elements {
				gotElems = append(gotElems, e.startKey)
			}
			if diff := cmp.Diff(tc.wantElems, gotElems); diff != "" {
				t.Errorf("Elements mismatch (-want +got):\n%s", diff)
			}

			backend := set.backend.(*setMapBackend)
			if diff := cmp.Diff(tc.wantBackendMap, backend.m); diff != "" {
				t.Errorf("Backend mapping mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

func TestPruneUnused(t *testing.T) {
	nft := newNFTablesStd()
	tab, err := nft.AddTable(arbitraryFamily, "test_table", false)
	if err != nil {
		t.Fatalf("unexpected error for AddTable: %v", err)
	}

	bc, err := nft.AddChainToTable(tab, "base_chain", arbitraryInfoPolicyAccept, "base", false, 0, nil, linux.NF_ACCEPT)
	if err != nil {
		t.Fatalf("unexpected error for AddChain: %v", err)
	}

	// Create an anonymous binding chain.
	bindingChain, err := nft.AddChainToTable(tab, "anon_chain", nil, "anon", false, linux.NFT_CHAIN_BINDING, nil, 0)
	if err != nil {
		t.Fatalf("unexpected error for AddChain binding: %v", err)
	}

	// Add rule jumping to bindingChain.
	rule := &Rule{}
	jumpOp := mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_JUMP), Chain: bindingChain})
	if err := rule.addOperation(jumpOp); err != nil {
		t.Fatalf("unexpected error for addOperation: %v", err)
	}
	bindingChain.IncrementChainUse()

	if err := bc.RegisterRule(rule, -1); err != nil {
		t.Fatalf("unexpected error for RegisterRule: %v", err)
	}

	if tab.ChainCount() != 2 {
		t.Fatalf("expected 2 chains in table, got %d", tab.ChainCount())
	}

	// Delete rule jumping to bindingChain.
	if err := bc.DeleteRule(rule); err != nil {
		t.Fatalf("unexpected error for DeleteRule: %v", err)
	}

	// Sweep unbound binding chains at top level.
	nft.PruneUnused()

	// Verify that bindingChain was swept from tab.
	if tab.ChainCount() != 1 {
		t.Fatalf("expected 1 chain after sweep, got %d", tab.ChainCount())
	}
	if _, err := tab.GetChain("anon_chain"); err == nil {
		t.Fatalf("expected anon_chain to be swept and removed from table")
	}
}

func TestDestroyTable(t *testing.T) {
	nf := newNFTablesStd()
	tab, err := nf.AddTable(arbitraryFamily, "test_table", false)
	if err != nil {
		t.Fatalf("unexpected error for AddTable: %v", err)
	}

	bc, err := nf.AddChainToTable(tab, "base", arbitraryInfoPolicyAccept, "base", false, 0, nil, linux.NF_ACCEPT)
	if err != nil {
		t.Fatalf("unexpected error for AddChain: %v", err)
	}

	// Add a target chain and rule jumping to it.
	targetChain, err := nf.AddChainToTable(tab, "target", nil, "target", false, 0, nil, 0)
	if err != nil {
		t.Fatalf("unexpected error for AddChain target: %v", err)
	}
	rule := &Rule{}
	op := mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NFT_JUMP), Chain: targetChain})
	if err := rule.addOperation(op); err != nil {
		t.Fatalf("unexpected error for addOperation: %v", err)
	}

	// Add a set with an element jumping to targetChain, and bind a lookupOp to it.
	set := &nftSet{
		name:   "test_set",
		keyLen: 4,
		backend: &setMapBackend{
			m: make(map[string]int),
		},
	}
	if err := nf.addSetToTable(tab, set); err != nil {
		t.Fatalf("unexpected error for addSetToTable: %v", err)
	}
	setElem := nftSetElem{
		startKey: []byte{1, 2, 3, 4},
		data: dataOrVerdict{
			isVerdict: true,
			verdict:   Verdict{Code: VC(linux.NFT_JUMP), Chain: targetChain},
		},
	}
	set.elements = append(set.elements, setElem)
	targetChain.IncrementChainUse()

	lookup := &lookupOp{
		set:     set,
		sregIdx: 0,
	}
	set.bindings = append(set.bindings, lookup)
	if err := rule.addOperation(lookup); err != nil {
		t.Fatalf("unexpected error for addOperation lookup: %v", err)
	}

	targetChain.IncrementChainUse()
	if err := bc.RegisterRule(rule, -1); err != nil {
		t.Fatalf("unexpected error for RegisterRule: %v", err)
	}

	afFilter := tab.afFilter

	tab.destroy()

	// 1. Verify the table was cleanly reset back to an empty shell.
	if diff := cmp.Diff(&Table{}, tab, cmp.AllowUnexported(Table{}, atomicbitops.Uint64{})); diff != "" {
		t.Errorf("Table differs from expected after destroy (-want +got):\n%s", diff)
	}

	if len(afFilter.hfStacks) != 0 {
		t.Errorf("expected afFilter.hfStacks to be empty after destroy")
	}

	// 2. Verify the base chain was destroyed.
	if diff := cmp.Diff(&Chain{}, bc, cmp.AllowUnexported(Chain{})); diff != "" {
		t.Errorf("Chain differs from expected after destroy (-want +got):\n%s", diff)
	}

	// 3. Verify the target chain was destroyed and its chainUse decremented to 0.
	if diff := cmp.Diff(&Chain{}, targetChain, cmp.AllowUnexported(Chain{})); diff != "" {
		t.Errorf("Target chain differs from expected after destroy (-want +got):\n%s", diff)
	}

	// 4. Verify the rule was destroyed.
	if diff := cmp.Diff(&Rule{}, rule, cmp.AllowUnexported(Rule{})); diff != "" {
		t.Errorf("Rule differs from expected after destroy (-want +got):\n%s", diff)
	}

	// 5. Verify the set was destroyed and its element-level chainUse decremented to 0.
	if diff := cmp.Diff(&nftSet{dead: 1}, set, cmp.AllowUnexported(nftSet{}, setMapBackend{})); diff != "" {
		t.Errorf("Set differs from expected after destroy (-want +got):\n%s", diff)
	}
}

// runWriter keeps updating the NFTables ruleset.
func runWriter(t *testing.T, stk *stack.Stack, writerID, iterations int, wg *sync.WaitGroup) {
	defer wg.Done()
	for j := 0; j < iterations; j++ {
		func() {
			stk.LockNFTablesUpdate()
			defer stk.UnlockNFTablesUpdate()

			current := stk.NFTables().(*NFTables)
			copyNFT := current.DeepCopy()

			copyTab, err := copyNFT.GetTable(stack.IP, "test_table", 0)
			if err != nil {
				t.Errorf("writer %d failed to get table: %v", writerID, err)
				return
			}
			copyChain, err := copyTab.GetChain("test_chain")
			if err != nil {
				t.Errorf("writer %d failed to get chain: %v", writerID, err)
				return
			}

			newRule := &Rule{}
			newOp := mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)})
			if err := newRule.addOperation(newOp); err != nil {
				t.Errorf("writer %d addOperation failed: %v", writerID, err)
				return
			}
			if err := copyChain.RegisterRule(newRule, -1); err != nil {
				t.Errorf("writer %d RegisterRule failed: %v", writerID, err)
				return
			}
			stk.SetNFTables(copyNFT)
		}()
	}
}

// runReader repeatedly performs packet evaluations.
func runReader(t *testing.T, stk *stack.Stack, readIterations int, wg *sync.WaitGroup) {
	defer wg.Done()
	pkt := makeArbitraryIPv4Packet()
	for j := 0; j < readIterations; j++ {
		activeNFT := stk.NFTables()
		if activeNFT == nil {
			t.Errorf("expected non-nil NFTables from stack")
			return
		}
		for jj := 0; jj < 1000; jj++ {
			// Evaluate NFTables ruleset.
			if !activeNFT.CheckInput(pkt, nil, stack.IP) {
				t.Errorf("expected CheckInput to return true (accept)")
				return
			}
		}
	}
}

// TestNFTablesConcurrentUpdateAndEvaluate tests the concurrent update and
// packet processing path.
func TestNFTablesConcurrentUpdateAndEvaluate(t *testing.T) {
	// Create a new stack and NFTables.
	stk := stack.New(stack.Options{})
	fakeClock := faketime.NewManualClock()
	fixedRNG := rand.RNGFrom(&fixedReader{})
	nf := NewNFTables(stk, fakeClock, fixedRNG)
	tab, err := nf.AddTable(stack.IP, "test_table", false)
	if err != nil {
		t.Fatalf("unexpected error for AddTable: %v", err)
	}
	bc, err := nf.AddChainToTable(tab, "test_chain", arbitraryInfoPolicyAccept, "test", false, 0, nil, linux.NF_ACCEPT)
	if err != nil {
		t.Fatalf("unexpected error for AddChain: %v", err)
	}

	// Add an initial immediate rule to the chain.
	rule := &Rule{}
	op := mustCreateImmediate(t, linux.NFT_REG_VERDICT, nil, Verdict{Code: VC(linux.NF_ACCEPT)})
	if err := rule.addOperation(op); err != nil {
		t.Fatalf("unexpected error for addOperation: %v", err)
	}
	if err := bc.RegisterRule(rule, -1); err != nil {
		t.Fatalf("unexpected error for RegisterRule: %v", err)
	}

	stk.SetNFTables(nf)

	const numWriters = 2
	const numReaders = 4
	const iterations = 50
	const readIterations = iterations * 4

	var wg sync.WaitGroup

	// Writers: serialize updates per-stack using LockNFTablesUpdate().
	for i := 0; i < numWriters; i++ {
		wg.Add(1)
		go runWriter(t, stk, i, iterations, &wg)
	}

	// Readers: concurrently perform packet evaluations.
	for i := 0; i < numReaders; i++ {
		wg.Add(1)
		go runReader(t, stk, readIterations, &wg)
	}

	wg.Wait()

	// Verify that all serialized updates were persisted in the active NFTables object.
	finalNFT := stk.NFTables().(*NFTables)
	finalTab, err := finalNFT.GetTable(stack.IP, "test_table", 0)
	if err != nil {
		t.Fatalf("failed to get table from final NFTables: %v", err)
	}
	finalChain, err := finalTab.GetChain("test_chain")
	if err != nil {
		t.Fatalf("failed to get chain from final NFTables: %v", err)
	}
	// Initial rule (1) + numWriters * iterations rules added.
	expectedRules := 1 + numWriters*iterations
	if len(finalChain.GetRules()) != expectedRules {
		t.Fatalf("expected %d rules in final chain, got %d", expectedRules, len(finalChain.GetRules()))
	}
}
