Files
Jing Chen a093ad0450 Simplify and format gVisor codebase.
The changes are just output of `gofmt -s -w .`.
2024-10-13 00:50:32 -07:00

3832 lines
168 KiB
Go

// 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 (
"encoding/binary"
"fmt"
"reflect"
"slices"
"testing"
"time"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/rand"
"gvisor.dev/gvisor/pkg/sync"
"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 Hook = Prerouting
arbitraryFamily AddressFamily = 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
}
// 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
}
// 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
}
// 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 []AddressFamily{AddressFamily(NumAFs), AddressFamily(-1)} {
// Note: the Prerouting hook is arbitrary (any hook would work).
pkt := makeArbitraryPacket(arbitraryReservedHeaderBytes)
v, err := nf.EvaluateHook(unsupportedFamily, arbitraryHook, pkt)
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).
cmpPkt := makeArbitraryPacket(arbitraryReservedHeaderBytes)
for _, family := range []AddressFamily{IP, IP6, Inet, Arp, Bridge, Netdev} {
t.Run(family.String()+" address family", func(t *testing.T) {
nf := newNFTablesStd()
for _, hook := range []Hook{Prerouting, Input, Forward, Output, Postrouting, Ingress, Egress} {
pkt := makeArbitraryPacket(arbitraryReservedHeaderBytes)
v, err := nf.EvaluateHook(family, hook, pkt)
supported := false
for _, h := range supportedHooks[family] {
if h == hook {
supported = true
break
}
}
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, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})),
verdict: Verdict{Code: VC(linux.NF_ACCEPT)},
},
{
tname: "immediately drop",
baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newVerdictData(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, newVerdictData(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, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: arbitraryTargetChain})),
targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: arbitraryTargetChain})),
targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: arbitraryTargetChain})),
targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})),
baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: arbitraryTargetChain})),
targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})),
baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: arbitraryTargetChain})),
targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: arbitraryTargetChain})),
targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: arbitraryTargetChain})),
targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})),
baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: arbitraryTargetChain})),
targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})),
baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newBytesData([]byte{0, 1, 2, 3})),
baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newBytesData([]byte{0, 1, 2, 3})),
baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newBytesData([]byte{0, 1, 2, 3})),
baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})),
baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})),
verdict: Verdict{Code: VC(linux.NF_ACCEPT)},
},
{
tname: "multiple drops",
baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})),
baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})),
baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})),
baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})),
verdict: Verdict{Code: VC(linux.NF_DROP)},
},
{
tname: "immediate load register",
baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})),
baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
tc, err := tab.AddChain(arbitraryTargetChain, nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
// Adds testing rules and operations.
if test.baseOp1 != nil {
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 {
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 := makeArbitraryPacket(arbitraryReservedHeaderBytes)
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, pkt)
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", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
// 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), newBytesData(bytes[:blen])))
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), newBytesData(bytes[:blen])))
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 := makeArbitraryPacket(arbitraryReservedHeaderBytes)
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, pkt)
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, newBytesData([]byte{0, 0, 0, 0})),
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, newBytesData([]byte{1, 0, 0, 0})),
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, newBytesData([]byte{1, 7, 0, 0})),
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, newBytesData([]byte{1, 98, 0, 56})),
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, newBytesData([]byte{29, 0, 0, 0})),
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, newBytesData([]byte{100, 0, 0, 0})),
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, newBytesData([]byte{200, 0, 0, 0})),
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, newBytesData([]byte{29, 76, 230, 0})),
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, newBytesData([]byte{29, 76, 230, 0})),
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, newBytesData([]byte{28, 76, 230, 0})),
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, newBytesData([]byte{29, 0, 0, 0})),
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, newBytesData([]byte{100, 0, 0, 0})),
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, newBytesData([]byte{200, 0, 0, 0})),
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, newBytesData([]byte{29, 76, 230, 0})),
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, newBytesData([]byte{29, 76, 230, 0})),
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, newBytesData([]byte{28, 76, 230, 0})),
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, newBytesData([]byte{0, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{1, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{1, 7, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{1, 98, 0, 56, 0, 0, 0, 0})),
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, newBytesData([]byte{29, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{100, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{200, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{29, 76, 230, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{29, 76, 230, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{28, 76, 230, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{29, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{100, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{200, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{30, 0, 0, 1, 0, 0, 0, 0})),
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, newBytesData([]byte{29, 76, 230, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{28, 76, 230, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})),
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, newBytesData([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12})),
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, newBytesData([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x1f, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x21, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x21, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x1f, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00})),
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, newBytesData([]byte{0xaa, 0xaa, 0xaa, 0x20, 0xaa, 0xaa, 0xaa, 0x13, 0xc0, 0x09, 0x00, 0x00})),
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, newBytesData([]byte{0xaa, 0xaa, 0xaa, 0x20, 0xaa, 0xaa, 0xaa, 0x13, 0xc0, 0x09, 0x00, 0x00})),
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, newBytesData([]byte{0xab, 0xbc, 0xcd, 0xde, 0xef, 0x00, 0x01, 0x12, 0x23, 0x34, 0x45, 0x56})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x19, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00})),
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, newBytesData([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})),
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, newBytesData([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x1f, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0xaa})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x21, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0xaa})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x21, 0xaa, 0xaa, 0xaa, 0xaa, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x1f, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x90})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x86})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0xaa, 0x00, 0x0b, 0x13, 0x6a, 0x88})),
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, newBytesData([]byte{0xaa, 0xaa, 0xaa, 0x20, 0xaa, 0xaa, 0xaa, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87})),
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, newBytesData([]byte{0xab, 0xbc, 0xcd, 0xde, 0xef, 0x00, 0x01, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x90})),
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, newBytesData([]byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0a, 0x13, 0x6a, 0x85})),
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", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
rule := &Rule{}
// Adds testing operations.
if test.op1 != nil {
rule.addOperation(test.op1)
}
if test.op2 != nil {
rule.addOperation(test.op2)
}
// 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, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})))
// Registers the rule to the base chain.
if err := bc.RegisterRule(rule, -1); err != nil {
t.Fatalf("unexpected error for RegisterRule: %v", err)
}
// Runs evaluation and checks verdict.
pkt := makeArbitraryPacket(arbitraryReservedHeaderBytes)
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, pkt)
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, newBytesData(numToBE(1, 4))),
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, newBytesData(numToBE(4, 4))),
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, newBytesData(numToBE(1, 4))),
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, newBytesData(numToBE(1, 4))),
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, newBytesData(numToBE(954, 4))),
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, newBytesData(numToBE(954, 4))),
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, newBytesData(numToBE(1, 4))),
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, newBytesData(numToBE(1, 4))),
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, newBytesData(numToBE(100, 4))),
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, newBytesData(numToBE(100, 4))),
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, newBytesData(numToBE(123, 4))),
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, newBytesData(numToBE(123, 4))),
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, newBytesData(numToBE(1, 8))),
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, newBytesData(numToBE(4, 8))),
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, newBytesData(numToBE(1, 8))),
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, newBytesData(numToBE(1, 8))),
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, newBytesData(numToBE(954, 8))),
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, newBytesData(numToBE(954, 8))),
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, newBytesData(numToBE(1, 8))),
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, newBytesData(numToBE(1, 8))),
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, newBytesData(numToBE(100, 8))),
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, newBytesData(numToBE(100, 8))),
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, newBytesData(numToBE(123, 8))),
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, newBytesData(numToBE(123, 8))),
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, newBytesData([]byte{1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{0x45, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})),
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, newBytesData([]byte{0x45, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})),
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", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
rule := &Rule{}
// Adds testing operations.
if test.op1 != nil {
rule.addOperation(test.op1)
}
if test.op2 != nil {
rule.addOperation(test.op2)
}
// Adds drop operation. Will be final verdict if comparison is true.
rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})))
// Registers the rule to the base chain.
if err := bc.RegisterRule(rule, -1); err != nil {
t.Fatalf("unexpected error for RegisterRule: %v", err)
}
// Runs evaluation and checks verdict.
pkt := makeArbitraryPacket(arbitraryReservedHeaderBytes)
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, pkt)
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", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
rule := &Rule{}
// Adds testing operations.
if test.op1 != nil {
rule.addOperation(test.op1)
}
if test.op2 != nil {
rule.addOperation(test.op2)
}
// Adds drop operation. Will be final verdict if all comparisons are true.
rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})))
// Registers the rule to the base chain.
if err := bc.RegisterRule(rule, -1); err != nil {
t.Fatalf("unexpected error for RegisterRule: %v", err)
}
// Runs evaluation.
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, test.pkt)
if err != nil {
t.Fatalf("unexpected error for EvaluateHook: %v", err)
}
// Checks for final verdict.
if test.op2 == nil {
// If no comparison operation is set, then payload load should break,
// resulting in Accept as the default policy verdict.
if v.Code != VC(linux.NF_ACCEPT) {
t.Fatalf("expected verdict Accept for break during evaluation, got %v", v)
}
} else {
// If a comparison operation is set, both payload load and comparison
// should succeed, resulting in Drop as the final verdict.
if v.Code != VC(linux.NF_DROP) {
t.Fatalf("expected verdict Drop for true comparison, got %v", v)
}
}
})
}
}
// 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
return makeEthernetPacket(0, fields)
}(),
op1: mustCreateImmediate(t, linux.NFT_REG_1, newBytesData(arbitraryLinkAddrB2[:])),
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, newBytesData(arbitraryLinkAddrB[:])),
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, newBytesData(numToBE(int(header.IPv6ProtocolNumber), ethTypeLen))),
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, newBytesData(numToBE(30, ipv4LengthLen))),
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, newBytesData(numToBE(12345, ipv4IDLen))),
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, newBytesData(numToBE(arbitraryNonZeroFragmentOffset/8, ipv4FragOffLen))),
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, newBytesData(numToBE(0, ipv4FragOffLen))),
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, newBytesData(numToBE(10, ipv4FragOffLen))),
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, newBytesData(numToBE(128, ipv4TTLLen))),
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, newBytesData(arbitraryIPv4AddrB2[:])),
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, newBytesData(arbitraryIPv4AddrB[:])),
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, newBytesData(numToBE(6060, ipv4ChecksumLen))),
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, newBytesData(numToBE(232, ipv6LengthLen))),
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, newBytesData(numToBE(54, ipv6HopLimitLen))),
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, newBytesData(arbitraryIPv6AddrB2[:])),
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, newBytesData(arbitraryIPv6AddrB[:])),
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, newBytesData(numToBE(arbitraryPort, tcpSrcPortLen))),
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, newBytesData(numToBE(arbitraryPort2, tcpSrcPortLen))),
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, newBytesData(numToBE(arbitraryPort, tcpDstPortLen))),
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, newBytesData(numToBE(33, tcpSeqNumLen))),
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, newBytesData(numToBE(245, tcpAckNumLen))),
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, newBytesData(numToBE(91, tcpWindowLen))),
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, newBytesData(numToBE(7654, tcpChecksumLen))),
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, newBytesData(numToBE(40, tcpUrgPtrLen))),
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, newBytesData(numToBE(12345, ipv4IDLen))),
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, newBytesData(numToBE(128, ipv4TTLLen))),
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, newBytesData(arbitraryIPv4AddrB2[:])),
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, newBytesData(arbitraryIPv4AddrB[:])),
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, newBytesData(numToBE(arbitraryPort2, tcpSrcPortLen))),
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, newBytesData(numToBE(arbitraryPort, tcpDstPortLen))),
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, newBytesData(numToBE(33, tcpSeqNumLen))),
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, newBytesData(numToBE(245, tcpAckNumLen))),
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, newBytesData(numToBE(91, tcpWindowLen))),
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, newBytesData(numToBE(7654, tcpChecksumLen))),
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, newBytesData(numToBE(40, tcpUrgPtrLen))),
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, newBytesData(numToBE(232, ipv6LengthLen))),
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, newBytesData(numToBE(54, ipv6HopLimitLen))),
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, newBytesData(arbitraryIPv6AddrB2[:])),
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, newBytesData(arbitraryIPv6AddrB[:])),
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", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
rule := &Rule{}
// Adds testing operations.
if test.op1 != nil {
rule.addOperation(test.op1)
}
if test.op2 != nil {
rule.addOperation(test.op2)
}
// Adds drop operation. Will be final verdict if payload set evaluation is
// successful (operation breaks if anything goes wrong).
rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})))
// Registers the rule to the base chain.
if err := bc.RegisterRule(rule, -1); err != nil {
t.Fatalf("unexpected error for RegisterRule: %v", err)
}
// Runs evaluation.
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, test.pkt)
if err != nil {
t.Fatalf("unexpected error for EvaluateHook: %v", err)
}
// Checks for final verdict.
if test.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, newBytesData(numToBE(4783, 4))),
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, newBytesData(numToBE(4783, 4))),
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, newBytesData(numToBE(400700800, 4))),
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, newBytesData(numToBE(400700800, 4))),
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, newBytesData(numToBE(0, 4))),
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, newBytesData(numToBE(0, 4))),
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, newBytesData(numToBE(0x12345678, 8))),
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, newBytesData([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})),
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, newBytesData(numToBE(4783, 4))),
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, numToBE(4783, 4)),
},
{
tname: "0 shift right for bitwise rshift",
op1: mustCreateImmediate(t, linux.NFT_REG_1, newBytesData(numToBE(4783, 4))),
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, numToBE(4783, 4)),
},
{
tname: "1-bit shift left for bitwise lshift",
op1: mustCreateImmediate(t, linux.NFT_REG_4, newBytesData(numToBE(4782, 4))),
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, numToBE(4782<<1, 4)),
},
{
tname: "1-bit shift right for bitwise rshift",
op1: mustCreateImmediate(t, linux.NFT_REG32_06, newBytesData(numToBE(4782, 4))),
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, numToBE(4782>>1, 4)),
},
{
tname: "8-bit shift left for bitwise lshift",
op1: mustCreateImmediate(t, linux.NFT_REG_4, newBytesData(numToBE(4782, 4))),
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, numToBE(4782<<8, 4)),
},
{
tname: "8-bit shift right for bitwise rshift",
op1: mustCreateImmediate(t, linux.NFT_REG32_06, newBytesData(numToBE(4782, 4))),
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, numToBE(4782>>8, 4)),
},
{
tname: "16-bit shift left for bitwise lshift",
op1: mustCreateImmediate(t, linux.NFT_REG_4, newBytesData(numToBE(0x45678910, 8))),
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, numToBE(0x45678910<<16, 8)),
},
{
tname: "16-bit shift right for bitwise rshift",
op1: mustCreateImmediate(t, linux.NFT_REG32_06, newBytesData(numToBE(0x45678910, 4))),
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, numToBE(0x45678910>>16, 4)),
},
{
tname: "max-bit shift left for bitwise lshift",
op1: mustCreateImmediate(t, linux.NFT_REG32_03, newBytesData(numToBE(0x45678910, 4))),
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, numToBE(0x45678910<<(bitshiftLimit-1), 4)),
},
{
tname: "max-bit shift right for bitwise rshift",
op1: mustCreateImmediate(t, linux.NFT_REG_3, newBytesData(numToBE(0x45678910, 8))),
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, numToBE(0x45678910>>(bitshiftLimit-1), 8)),
},
} {
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", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
rule := &Rule{}
// Adds testing operations.
if test.op1 != nil {
rule.addOperation(test.op1)
}
if test.op2 != nil {
rule.addOperation(test.op2)
}
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, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})))
// Registers the rule to the base chain.
if err := bc.RegisterRule(rule, -1); err != nil {
t.Fatalf("unexpected error for RegisterRule: %v", err)
}
// Runs evaluation and checks verdict.
pkt := makeArbitraryPacket(arbitraryReservedHeaderBytes)
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, pkt)
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", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
// 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(arbitraryFamily, arbitraryHook, pkt)
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 := int64(0), int64(0)
if pkt.Network().SourceAddress() == desiredIpv4Address {
expectedDBytes, expectedDPackets = int64(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 := makeArbitraryPacket(arbitraryReservedHeaderBytes)
// Sets up an NFTables object with a base chain and fake manual clock.
fakeClock := faketime.NewManualClock()
fixedRNG := rand.RNGFrom(&fixedReader{})
nf := NewNFTables(fakeClock, fixedRNG)
tab, err := nf.AddTable(arbitraryFamily, "test", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
// 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(arbitraryFamily, arbitraryHook, pkt)
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", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
rule := &Rule{}
// Adds testing operations.
if test.op1 != nil {
rule.addOperation(test.op1)
}
if test.op2 != nil {
rule.addOperation(test.op2)
}
// Adds drop operation. Will be final verdict if all comparisons are true.
rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})))
// Registers the rule to the base chain.
if err := bc.RegisterRule(rule, -1); err != nil {
t.Fatalf("unexpected error for RegisterRule: %v", err)
}
// Runs evaluation.
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, test.pkt)
if err != nil {
t.Fatalf("unexpected error for EvaluateHook: %v", err)
}
// Checks for final verdict (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, newBytesData(numToBE(0x0102, 2))),
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, newBytesData(numToBE(0x0102, 2))),
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, newBytesData(numToBE(0x010203, 3))),
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, newBytesData(numToBE(0x010203, 3))),
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, newBytesData(numToBE(0x01020304, 4))),
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, newBytesData(numToBE(0x01020304, 4))),
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, newBytesData(numToBE(0x010203040506, 6))),
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, newBytesData(numToBE(0x010203040506, 6))),
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, newBytesData(numToBE(0x0102030405060708, 8))),
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, newBytesData(numToBE(0x0102030405060708, 8))),
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, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})),
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, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})),
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, newBytesData(numToBE(0x01020304, 4))),
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, newBytesData(numToBE(0x01020304, 4))),
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, newBytesData(numToBE(0x010203040506, 6))),
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, newBytesData(numToBE(0x010203040506, 6))),
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, newBytesData(numToBE(0x0102030405060708, 8))),
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, newBytesData(numToBE(0x0102030405060708, 8))),
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, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})),
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, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})),
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, newBytesData(numToBE(0x0102030405060708, 8))),
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, newBytesData(numToBE(0x0102030405060708, 8))),
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, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c})),
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, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c})),
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, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})),
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, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})),
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", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
rule := &Rule{}
// Adds testing operations.
if test.op1 != nil {
rule.addOperation(test.op1)
}
if test.op2 != nil {
rule.addOperation(test.op2)
}
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, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})))
// Registers the rule to the base chain.
if err := bc.RegisterRule(rule, -1); err != nil {
t.Fatalf("unexpected error for RegisterRule: %v", err)
}
// Runs evaluation and checks verdict.
pkt := makeArbitraryPacket(arbitraryReservedHeaderBytes)
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, pkt)
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)),
},
{ // cmd: add rule ip6 tab ch meta nfproto 0x0a
tname: "meta load nfproto test",
pkt: pkt,
op1: mustCreateMetaLoad(t, linux.NFT_META_NFPROTO, linux.NFT_REG_4),
op2: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ,
[]byte{IP6.Protocol(), 0, 0, 0}),
},
{ // cmd: add rule ip6 tab ch meta l4proto 0x6
tname: "meta load l4proto test",
pkt: pkt,
op1: mustCreateMetaLoad(t, linux.NFT_META_L4PROTO, linux.NFT_REG32_00),
op2: mustCreateComparison(t, linux.NFT_REG32_00, linux.NFT_CMP_EQ,
[]byte{uint8(tcpTransportProtocol), 0, 0, 0}),
},
{ // cmd: add rule ip6 tab ch skuid 0x020304
tname: "meta load skuid test",
pkt: pkt,
op1: mustCreateMetaLoad(t, linux.NFT_META_SKUID, linux.NFT_REG32_02),
op2: mustCreateComparison(t, linux.NFT_REG32_02, linux.NFT_CMP_EQ,
binary.NativeEndian.AppendUint32(nil, arbitrarySKUID)),
},
{ // cmd: add rule ip6 tab ch skgid 45668
tname: "meta load skgid test",
pkt: pkt,
op1: mustCreateMetaLoad(t, linux.NFT_META_SKGID, linux.NFT_REG32_03),
op2: mustCreateComparison(t, linux.NFT_REG32_03, linux.NFT_CMP_EQ,
binary.NativeEndian.AppendUint32(nil, arbitrarySKGID)),
},
{ // cmd: add rule ip6 tab ch rtclassid 0x05
tname: "meta load rtclassid test",
pkt: pkt,
op1: mustCreateMetaLoad(t, linux.NFT_META_RTCLASSID, linux.NFT_REG32_04),
op2: mustCreateComparison(t, linux.NFT_REG32_04, linux.NFT_CMP_EQ,
binary.NativeEndian.AppendUint32(nil, uint32(tcid))),
},
{ // cmd: add rule ip6 tab ch pkttype 2
tname: "meta load pkttype test",
pkt: pkt,
op1: mustCreateMetaLoad(t, linux.NFT_META_PKTTYPE, linux.NFT_REG32_05),
op2: mustCreateComparison(t, linux.NFT_REG32_05, linux.NFT_CMP_EQ,
[]byte{uint8(arbitraryPktType), 0, 0, 0}),
},
{ // cmd: add rule ip6 tab ch meta random 4059586549
tname: "meta load prandom test",
pkt: pkt,
op1: mustCreateMetaLoad(t, linux.NFT_META_PRANDOM, linux.NFT_REG32_01),
op2: mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ,
numToBE(int(seededRandUint32), 4)),
},
{ // cmd: add rule ip6 tab ch time "1970-01-01 00:00:00"
tname: "meta load time at unix epoch test",
pkt: pkt,
op1: mustCreateMetaLoad(t, linux.NFT_META_TIME_NS, linux.NFT_REG_3),
op2: mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_EQ,
binary.NativeEndian.AppendUint64(nil, uint64(timeNS))),
},
{ // cmd: add rule ip6 tab ch day Thursday
tname: "meta load day test",
pkt: pkt,
op1: mustCreateMetaLoad(t, linux.NFT_META_TIME_DAY, linux.NFT_REG32_15),
op2: mustCreateComparison(t, linux.NFT_REG32_15, linux.NFT_CMP_EQ,
[]byte{uint8(timeDay), 0, 0, 0}),
},
{ // cmd: add rule inet tab ch hour 0x01020304
tname: "meta load hour test",
pkt: pkt,
op1: mustCreateMetaLoad(t, linux.NFT_META_TIME_HOUR, linux.NFT_REG32_14),
op2: mustCreateComparison(t, linux.NFT_REG32_14, linux.NFT_CMP_EQ,
binary.NativeEndian.AppendUint32(nil, uint32(timeHour))),
},
} {
t.Run(test.tname, func(t *testing.T) {
// Sets up an NFTables object with a base chain and fake manual clock.
// Using Manual Clock sets time.Now to Unix Epoch which fixes rng seed!
nf := NewNFTables(fakeClock, rand.RNGFrom(&fixedReader{}))
tab, err := nf.AddTable(arbitraryFamily, "test", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
rule := &Rule{}
// Adds testing operations.
if test.op1 != nil {
rule.addOperation(test.op1)
}
if test.op2 != nil {
rule.addOperation(test.op2)
}
// Adds drop operation. Will be final verdict if all comparisons are true.
rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})))
// Registers the rule to the base chain.
if err := bc.RegisterRule(rule, -1); err != nil {
t.Fatalf("unexpected error for RegisterRule: %v", err)
}
// Runs evaluation.
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, test.pkt)
if err != nil {
t.Fatalf("unexpected error for EvaluateHook: %v", err)
}
// Checks for final verdict.
if test.op2 == nil {
// If no comparison operation is set, then payload load should break,
// resulting in Accept as the default policy verdict.
if v.Code != VC(linux.NF_ACCEPT) {
t.Fatalf("expected verdict Accept for break during evaluation, got %v", v)
}
} else {
// If a comparison operation is set, both payload load and comparison
// should succeed, resulting in Drop as the final verdict.
if v.Code != VC(linux.NF_DROP) {
t.Fatalf("expected verdict Drop for true comparison, got %v", v)
}
}
})
}
}
// 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, newBytesData([]byte{uint8(testPktType)})),
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, newBytesData([]byte{uint8(testPktType)})),
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", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
rule := &Rule{}
// Adds testing operations.
if test.op1 != nil {
rule.addOperation(test.op1)
}
if test.op2 != nil {
rule.addOperation(test.op2)
}
// Adds drop operation, to be final verdict if evaluation is successful.
rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})))
// Registers the rule to the base chain.
if err := bc.RegisterRule(rule, -1); err != nil {
t.Fatalf("unexpected error for RegisterRule: %v", err)
}
// Runs evaluation.
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, test.pkt)
if err != nil {
t.Fatalf("unexpected error for EvaluateHook: %v", err)
}
// 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{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "non_existent_chain"}))},
}},
},
},
shouldErr: true,
},
{
tname: "goto to non-existent chain",
chains: map[string]*Chain{
"base_chain": {
baseChainInfo: arbitraryInfoPolicyAccept,
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "non_existent_chain"}))},
}},
},
},
shouldErr: true,
},
{
tname: "jump to itself",
chains: map[string]*Chain{
"base_chain": {
baseChainInfo: arbitraryInfoPolicyAccept,
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "base_chain"}))},
}},
},
},
shouldErr: true,
},
{
tname: "goto to itself",
chains: map[string]*Chain{
"base_chain": {
baseChainInfo: arbitraryInfoPolicyAccept,
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "base_chain"}))},
}},
},
},
shouldErr: true,
},
{
tname: "simple 2-chain loop",
chains: map[string]*Chain{
"base_chain": {
baseChainInfo: arbitraryInfoPolicyAccept,
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))},
}},
},
"aux_chain": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "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{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))},
}},
},
"aux_chain": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))},
}},
},
"aux_chain2": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain"}))},
}},
},
},
shouldErr: true,
},
{
tname: "simple 3-chain loop",
chains: map[string]*Chain{
"base_chain": {
baseChainInfo: arbitraryInfoPolicyAccept,
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))},
}},
},
"aux_chain": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"}))},
}},
},
"aux_chain2": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "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{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))},
}},
},
"aux_chain": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))},
}},
},
"aux_chain2": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))},
}},
},
"aux_chain3": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain4"}))},
}},
},
"aux_chain4": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"}))},
}},
},
},
shouldErr: true,
},
{
tname: "simple 4-chain loop",
chains: map[string]*Chain{
"base_chain": {
baseChainInfo: arbitraryInfoPolicyAccept,
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))},
}},
},
"aux_chain": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))},
}},
},
"aux_chain2": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))},
}},
},
"aux_chain3": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "base_chain"}))},
}},
},
},
shouldErr: true,
},
{
tname: "simple 5-chain loop",
chains: map[string]*Chain{
"base_chain": {
baseChainInfo: arbitraryInfoPolicyAccept,
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))},
}},
},
"aux_chain": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))},
}},
},
"aux_chain2": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))},
}},
},
"aux_chain3": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "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{
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})),
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"})),
},
}},
},
"aux_chain": {
comment: "strictly target",
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))},
}},
},
"aux_chain2": {
rules: []*Rule{{
ops: []operation{
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})),
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"})),
},
}},
},
"aux_chain3": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "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, newBytesData([]byte{0, 1, 2, 3}))}},
{ops: []operation{mustCreateImmediate(t, linux.NFT_REG32_14, newBytesData([]byte{0, 1, 2, 3}))}},
{ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}},
},
},
"aux_chain": {
rules: []*Rule{{
ops: []operation{
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})),
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"})),
},
}},
},
"aux_chain2": {
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))},
}},
},
"aux_chain3": {
rules: []*Rule{
{ops: []operation{mustCreateImmediate(t, linux.NFT_REG_1, newBytesData([]byte{0, 1, 2, 3}))}},
{ops: []operation{mustCreateImmediate(t, linux.NFT_REG32_14, newBytesData([]byte{0, 1, 2, 3}))}},
{ops: []operation{
mustCreateImmediate(t, linux.NFT_REG_4, newBytesData([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})),
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain"})),
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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{
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})),
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"})),
},
},
{ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}},
},
},
"aux_chain": {
comment: "strictly target",
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_2, newBytesData([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}))},
}},
},
"aux_chain2": {
comment: "strictly target",
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_3, newBytesData([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}))},
}},
},
"aux_chain3": {
comment: "strictly target",
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_4, newBytesData([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}))},
}},
},
},
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{
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})),
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"})),
},
},
{ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}},
},
},
"aux_chain": {
comment: "strictly target",
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_2, newBytesData([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}))},
}},
},
"aux_chain2": {
comment: "strictly target",
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_3, newBytesData([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}))},
}},
},
"aux_chain3": {
comment: "strictly target",
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(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{
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})),
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"})),
},
},
{ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}},
{ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))}},
},
},
"aux_chain": {
comment: "strictly target",
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_2, newBytesData([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}))},
}},
},
"aux_chain2": {
comment: "strictly target",
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_3, newBytesData([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}))},
}},
},
"aux_chain3": {
comment: "strictly target",
rules: []*Rule{{
ops: []operation{mustCreateImmediate(t, linux.NFT_REG_4, newBytesData([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}))},
}},
},
},
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{
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})),
mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "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", "test table", 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 {
tab.AddChain(chainName, chainInit.GetBaseChainInfo(), chainInit.GetComment(), false)
}
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 {
// 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 := makeArbitraryPacket(arbitraryReservedHeaderBytes)
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, pkt)
if err != nil {
if test.verdict.ChainName != "error" {
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.UnregisterRule(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 // ChainName is set to "error" if an error is expected
}{
{
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,
verdict: Verdict{ChainName: "error"},
},
{
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", "test table", 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)
c, err := tab.AddChain(name, nil, "test chain", false)
if i == 0 {
c.SetBaseChainInfo(arbitraryInfoPolicyAccept)
}
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, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})))
} else {
targetName := fmt.Sprintf("chain %d", i+1)
code := VC(linux.NFT_JUMP)
if !test.useJumpOp {
code = VC(linux.NFT_GOTO)
}
err = r.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: code, ChainName: targetName})))
}
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 := makeArbitraryPacket(arbitraryReservedHeaderBytes)
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, pkt)
if err != nil {
if test.verdict.ChainName != "error" {
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(stdClock, fixedRNG)
}
// mustCreateImmediate wraps the newImmediate function for brevity.
func mustCreateImmediate(t *testing.T, dreg uint8, data registerData) *immediate {
imm, err := newImmediate(dreg, data)
if err != nil {
t.Fatalf("failed to create immediate: %v", err)
}
return imm
}
// 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, 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)
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, 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
}