mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement Comparison operation (parsing, interpretation, evaluation, tests).
Also improves the conciseness and consistency of the nftinterp_test file. PiperOrigin-RevId: 663200389
This commit is contained in:
committed by
gVisor bot
parent
be54c87aaf
commit
b508258e39
+137
-28
@@ -42,6 +42,8 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
@@ -554,9 +556,6 @@ type Rule struct {
|
||||
// Operation represents a single operation in a rule.
|
||||
type Operation interface {
|
||||
|
||||
// TypeString returns the string representation of the type of the operation.
|
||||
TypeString() string
|
||||
|
||||
// evaluate evaluates the operation on the given packet and register set,
|
||||
// changing the register set and possibly the packet in place.
|
||||
evaluate(regs *RegisterSet, pkt *stack.PacketBuffer)
|
||||
@@ -565,6 +564,7 @@ type Operation interface {
|
||||
// Ensures all operations implement the Operation interface at compile time.
|
||||
var (
|
||||
_ Operation = (*Immediate)(nil)
|
||||
_ Operation = (*Comparison)(nil)
|
||||
)
|
||||
|
||||
// Immediate is an operation that sets the data in a register.
|
||||
@@ -581,14 +581,119 @@ func NewImmediate(dreg uint8, data RegisterData) (*Immediate, error) {
|
||||
return &Immediate{dreg: dreg, data: data}, nil
|
||||
}
|
||||
|
||||
// TypeString for Immediate returns "Immediate" as the string operation type.
|
||||
func (op *Immediate) TypeString() string { return "Immediate" }
|
||||
|
||||
// evaluate for Immediate sets the data in the destination register.
|
||||
func (op Immediate) evaluate(regs *RegisterSet, pkt *stack.PacketBuffer) {
|
||||
op.data.StoreData(regs, op.dreg)
|
||||
}
|
||||
|
||||
// Comparison is an operation that compares the data in a register to a given
|
||||
// value and breaks (by setting the verdict register to NFT_BREAK) from the rule
|
||||
// if the comparison is false.
|
||||
// Note: comparison operations are not supported for the verdict register.
|
||||
type Comparison struct {
|
||||
data RegisterData // Data to compare the source register to.
|
||||
sreg uint8 // Number of the source register.
|
||||
cop cmpOp // Comparison operator.
|
||||
}
|
||||
|
||||
// cmpOp is the comparison operator for a Comparison operation.
|
||||
// Note: corresponds to enum nft_cmp_op from
|
||||
// include/uapi/linux/netfilter/nf_tables.h and uses the same constants.
|
||||
type cmpOp int
|
||||
|
||||
// String for NftCmpOp returns the string representation of the comparison
|
||||
// operator.
|
||||
func (cop cmpOp) String() string {
|
||||
switch cop {
|
||||
case linux.NFT_CMP_EQ:
|
||||
return "=="
|
||||
case linux.NFT_CMP_NEQ:
|
||||
return "!="
|
||||
case linux.NFT_CMP_LT:
|
||||
return "<"
|
||||
case linux.NFT_CMP_LTE:
|
||||
return "<="
|
||||
case linux.NFT_CMP_GT:
|
||||
return ">"
|
||||
case linux.NFT_CMP_GTE:
|
||||
return ">="
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid comparison operator: %d", int(cop)))
|
||||
}
|
||||
}
|
||||
|
||||
// validateComparisonOp ensures the comparison operator is valid.
|
||||
func validateComparisonOp(cop cmpOp) error {
|
||||
switch cop {
|
||||
case linux.NFT_CMP_EQ, linux.NFT_CMP_NEQ, linux.NFT_CMP_LT, linux.NFT_CMP_LTE, linux.NFT_CMP_GT, linux.NFT_CMP_GTE:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("invalid comparison operator: %d", int(cop))
|
||||
}
|
||||
}
|
||||
|
||||
// NewComparison creates a new Comparison operation.
|
||||
func NewComparison(sreg uint8, op int, data RegisterData) (*Comparison, error) {
|
||||
if sreg == linux.NFT_REG_VERDICT {
|
||||
return nil, fmt.Errorf("comparison operation cannot use verdict register as source")
|
||||
}
|
||||
if err := data.ValidateRegister(sreg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cop := cmpOp(op)
|
||||
if err := validateComparisonOp(cop); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Comparison{sreg: sreg, cop: cop, data: data}, nil
|
||||
}
|
||||
|
||||
// evaluate for Comparison compares the data in the source register to the given
|
||||
// data and breaks from the rule if the comparison is false.
|
||||
func (op Comparison) evaluate(regs *RegisterSet, pkt *stack.PacketBuffer) {
|
||||
// Gets the data from the source register.
|
||||
regBuf := getRegisterData(regs, op.sreg, op.data.Type())
|
||||
// Gets the data to compare to.
|
||||
bytesData, ok := op.data.(BytesData)
|
||||
if !ok {
|
||||
panic("comparison operation data is not BytesData")
|
||||
}
|
||||
// Compares from left to right in 4-byte chunks starting with the rightmost
|
||||
// byte of every 4-byte chunk since the data is little endian.
|
||||
// For example, 16-byte IPv6 address 2001:000a:130f:0000:0000:09c0:876a:130b
|
||||
// is represented as 0x0a000120 0x00000f13 0xc0090000 0x0b136a87 in operations
|
||||
// and as [0a|00|01|20|00|00|0f|13|c0|09|00|00|0b|13|6a|87] in the byte slice,
|
||||
// so we compare right to left in the first 4 bytes and then go to the next 4.
|
||||
dif := 0
|
||||
for i := 0; i < len(bytesData.data) && dif == 0; i += 4 {
|
||||
regVal := binary.LittleEndian.Uint32(regBuf[i : i+4])
|
||||
opVal := binary.LittleEndian.Uint32(bytesData.data[i : i+4])
|
||||
if regVal < opVal {
|
||||
dif = -1
|
||||
} else if regVal > opVal {
|
||||
dif = 1
|
||||
}
|
||||
}
|
||||
var result bool
|
||||
switch op.cop {
|
||||
case linux.NFT_CMP_EQ:
|
||||
result = dif == 0
|
||||
case linux.NFT_CMP_NEQ:
|
||||
result = dif != 0
|
||||
case linux.NFT_CMP_LT:
|
||||
result = dif < 0
|
||||
case linux.NFT_CMP_LTE:
|
||||
result = dif <= 0
|
||||
case linux.NFT_CMP_GT:
|
||||
result = dif > 0
|
||||
case linux.NFT_CMP_GTE:
|
||||
result = dif >= 0
|
||||
}
|
||||
if !result {
|
||||
// Comparison is false, so break from the rule.
|
||||
regs.verdict = Verdict{Code: VC(linux.NFT_BREAK)}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Register and Register-Related Implementations.
|
||||
// Note: Registers are represented by type uint8 for the register number.
|
||||
@@ -735,29 +840,33 @@ func (rd BytesData) ValidateRegister(reg uint8) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getRegisterData is a helper function that gets the appropriate slice of
|
||||
// register data from the register set.
|
||||
// Note: does not support verdict data and assumes the register is valid for the
|
||||
// given data type.
|
||||
func getRegisterData(regs *RegisterSet, reg uint8, dataType RegisterDataType) []byte {
|
||||
// 4-byte data in a 4-byte register.
|
||||
if is4ByteRegister(reg) {
|
||||
start := (reg - linux.NFT_REG32_00) * linux.NFT_REG32_SIZE
|
||||
return regs.data[start : start+linux.NFT_REG32_SIZE]
|
||||
}
|
||||
// 16-byte data in a 16-byte register.
|
||||
if dataType == Data16Bytes {
|
||||
start := (reg - linux.NFT_REG_1) * linux.NFT_REG_SIZE
|
||||
return regs.data[start : start+linux.NFT_REG_SIZE]
|
||||
}
|
||||
// 4-byte data in a 16-byte register
|
||||
// Leaves excess space on the left (bc the data is little endian).
|
||||
start := (reg-linux.NFT_REG_1)*linux.NFT_REG_SIZE + linux.NFT_REG_SIZE - linux.NFT_REG32_SIZE
|
||||
return regs.data[start : start+linux.NFT_REG32_SIZE]
|
||||
}
|
||||
|
||||
// StoreData sets the data in the destination register to the uint32.
|
||||
func (rd BytesData) StoreData(regs *RegisterSet, reg uint8) {
|
||||
if err := rd.ValidateRegister(reg); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var start uint8
|
||||
var regBuf []byte
|
||||
// Stores 4-byte data in a 4-byte register.
|
||||
if is4ByteRegister(reg) {
|
||||
start = (reg - linux.NFT_REG32_00) * linux.NFT_REG32_SIZE
|
||||
regBuf = regs.data[start : start+linux.NFT_REG32_SIZE]
|
||||
} else {
|
||||
// Stores 16-byte data in a 16-byte register.
|
||||
if rd.Type() == Data16Bytes {
|
||||
start = (reg - linux.NFT_REG_1) * linux.NFT_REG_SIZE
|
||||
regBuf = regs.data[start : start+linux.NFT_REG_SIZE]
|
||||
} else {
|
||||
// Stores 4-byte data in a 16-byte register, leaving excess space on the
|
||||
// left (bc the data is little endian).
|
||||
start = (reg-linux.NFT_REG_1)*linux.NFT_REG_SIZE + linux.NFT_REG_SIZE - linux.NFT_REG32_SIZE
|
||||
regBuf = regs.data[start : start+linux.NFT_REG32_SIZE]
|
||||
}
|
||||
}
|
||||
regBuf := getRegisterData(regs, reg, rd.Type())
|
||||
copy(regBuf, rd.data)
|
||||
}
|
||||
|
||||
@@ -806,7 +915,7 @@ type Verdict struct {
|
||||
|
||||
// String returns a string representation of the verdict.
|
||||
func (v Verdict) String() string {
|
||||
out := VerdictToString(v.Code)
|
||||
out := VerdictCodeToString(v.Code)
|
||||
if v.ChainName != "" {
|
||||
out += fmt.Sprintf(" -> %s", v.ChainName)
|
||||
}
|
||||
@@ -818,8 +927,8 @@ func VC(v int32) uint32 {
|
||||
return uint32(v)
|
||||
}
|
||||
|
||||
// VerdictToString prints names for the supported verdicts.
|
||||
func VerdictToString(v uint32) string {
|
||||
// VerdictCodeToString prints names for the supported verdicts.
|
||||
func VerdictCodeToString(v uint32) string {
|
||||
switch v {
|
||||
// Netfilter (External) Verdicts:
|
||||
case VC(linux.NF_DROP):
|
||||
@@ -916,7 +1025,7 @@ func (nf *NFTables) EvaluateHook(family AddressFamily, hook Hook, pkt *stack.Pac
|
||||
return Verdict{Code: VC(linux.NF_ACCEPT)}, nil
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unexpected verdict from hook evaluation: %s", VerdictToString(regs.Verdict().Code)))
|
||||
panic(fmt.Sprintf("unexpected verdict from hook evaluation: %s", VerdictCodeToString(regs.Verdict().Code)))
|
||||
}
|
||||
|
||||
// evaluateFromRule is a helper function for Chain.evaluate that evaluates the
|
||||
|
||||
@@ -290,6 +290,284 @@ func TestEvaluateImmediate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}{
|
||||
{
|
||||
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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]byte{100, 0, 0, 0})),
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
tname: "compare register > 4-byte data, true",
|
||||
op1: mustCreateImmediate(t, linux.NFT_REG32_15, NewBytesData([]byte{0, 0, 0, 1})),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG32_15, linux.NFT_CMP_GT, NewBytesData([]byte{29, 76, 230, 0})),
|
||||
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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]byte{100, 0, 0, 0})),
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
tname: "compare register >= 4-byte data, true gt",
|
||||
op1: mustCreateImmediate(t, linux.NFT_REG32_12, NewBytesData([]byte{0, 0, 0, 1})),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG32_12, linux.NFT_CMP_GTE, NewBytesData([]byte{29, 76, 230, 0})),
|
||||
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, NewBytesData([]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, NewBytesData([]byte{29, 76, 230, 0})),
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]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, NewBytesData([]byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87})),
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
tname: "compare empty 4-byte register, true",
|
||||
op1: mustCreateComparison(t, linux.NFT_REG32_10, linux.NFT_CMP_EQ, NewBytesData([]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, NewBytesData([]byte{1, 0, 0, 0})),
|
||||
res: false,
|
||||
},
|
||||
{
|
||||
tname: "compare empty 16-byte register, true",
|
||||
op1: mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_LT, NewBytesData([]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, NewBytesData([]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 := NewNFTables()
|
||||
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 := makeTestingPacket()
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoopCheckOnRegisterAndUnregister tests the loop checking and accompanying
|
||||
// logic on registering and unregistering rules.
|
||||
func TestLoopCheckOnRegisterAndUnregister(t *testing.T) {
|
||||
@@ -882,3 +1160,12 @@ func mustCreateImmediate(t *testing.T, dreg uint8, data RegisterData) *Immediate
|
||||
}
|
||||
return imm
|
||||
}
|
||||
|
||||
// mustCreateComparison wraps the NewComparison function for brevity.
|
||||
func mustCreateComparison(t *testing.T, sreg uint8, cop int, data RegisterData) *Comparison {
|
||||
cmp, err := NewComparison(sreg, cop, data)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create comparison: %v", err)
|
||||
}
|
||||
return cmp
|
||||
}
|
||||
|
||||
@@ -138,6 +138,8 @@ func InterpretOperation(line string, lnIdx int) (Operation, error) {
|
||||
switch tokens[1] {
|
||||
case "immediate":
|
||||
return InterpretImmediate(line, lnIdx)
|
||||
case "cmp":
|
||||
return InterpretComparison(line, lnIdx)
|
||||
default:
|
||||
return nil, &SyntaxError{lnIdx, 1, fmt.Sprintf("unrecognized operation type: %s", tokens[1])}
|
||||
}
|
||||
@@ -199,6 +201,69 @@ func InterpretImmediate(line string, lnIdx int) (Operation, error) {
|
||||
return imm, nil
|
||||
}
|
||||
|
||||
// InterpretComparison creates a new Comparison operation from the given string.
|
||||
func InterpretComparison(line string, lnIdx int) (Operation, error) {
|
||||
tokens := strings.Fields(line)
|
||||
|
||||
// Requires at least 7 tokens:
|
||||
// "[", "cmp", op, "reg", register index, register value, "]".
|
||||
if len(tokens) < 7 {
|
||||
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for cmp operation, should be at least 7, got %d", len(tokens))}
|
||||
}
|
||||
|
||||
if err := checkOperationBrackets(tokens, lnIdx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tkIdx := 1
|
||||
|
||||
// First token should be "cmp".
|
||||
if err := consumeToken("cmp", tokens, lnIdx, tkIdx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkIdx++
|
||||
|
||||
// Second token should be the comparison operator.
|
||||
cop, err := parseCmpOp(tokens[tkIdx], lnIdx, tkIdx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkIdx++
|
||||
|
||||
// Third token should be "reg".
|
||||
if err := consumeToken("reg", tokens, lnIdx, tkIdx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkIdx++
|
||||
|
||||
// Fourth token should be the uint8 representing the register index.
|
||||
reg, err := parseRegister(tokens[tkIdx], lnIdx, tkIdx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkIdx++
|
||||
|
||||
// Fifth token should be the value.
|
||||
nextIdx, data, err := parseRegisterData(reg, tokens, lnIdx, tkIdx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tkIdx = nextIdx
|
||||
|
||||
// Done parsing tokens.
|
||||
if tkIdx != len(tokens)-1 {
|
||||
return nil, &SyntaxError{lnIdx, tkIdx, "unexpected token after comparison operation"}
|
||||
}
|
||||
|
||||
// Create the operation with the specified arguments.
|
||||
cmp, err := NewComparison(reg, cop, data)
|
||||
if err != nil {
|
||||
return nil, &LogicError{lnIdx, tkIdx, err}
|
||||
}
|
||||
|
||||
return cmp, nil
|
||||
}
|
||||
|
||||
//
|
||||
// Interpreter Helper Functions.
|
||||
//
|
||||
@@ -326,6 +391,26 @@ func parseHexData(tokens []string, lnIdx int, tkIdx int) (int, RegisterData, err
|
||||
return 0, nil, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("incorrect number of bytes for hexadecimal data, should be 4 or 16, got %d", len(bytes))}
|
||||
}
|
||||
|
||||
// parseCmpOp parses the int representing the cmpOp from the given string.
|
||||
func parseCmpOp(copString string, lnIdx int, tkIdx int) (int, error) {
|
||||
switch copString {
|
||||
case "eq":
|
||||
return linux.NFT_CMP_EQ, nil
|
||||
case "neq":
|
||||
return linux.NFT_CMP_NEQ, nil
|
||||
case "lt":
|
||||
return linux.NFT_CMP_LT, nil
|
||||
case "lte":
|
||||
return linux.NFT_CMP_LTE, nil
|
||||
case "gt":
|
||||
return linux.NFT_CMP_GT, nil
|
||||
case "gte":
|
||||
return linux.NFT_CMP_GTE, nil
|
||||
default:
|
||||
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid comparison operator: '%s'", copString)}
|
||||
}
|
||||
}
|
||||
|
||||
// consumeToken is a helper function that checks if the token at the given index
|
||||
// matches the expected string, returning a SyntaxError if not.
|
||||
func consumeToken(expected string, tokens []string, lnIdx int, tkIdx int) error {
|
||||
|
||||
@@ -21,145 +21,264 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
)
|
||||
|
||||
func TestInterpretImmediateOps(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
tname string
|
||||
opStr string
|
||||
op *Immediate // will be nil if an error is expected
|
||||
}{
|
||||
{
|
||||
tname: "verdict register with accept verdict",
|
||||
opStr: "[ immediate reg 0 accept ]",
|
||||
op: mustCreateImmediate(t, linux.NFT_REG_VERDICT, NewVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with drop verdict",
|
||||
opStr: "[ immediate reg 0 drop ]",
|
||||
op: mustCreateImmediate(t, linux.NFT_REG_VERDICT, NewVerdictData(Verdict{Code: VC(linux.NF_DROP)})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with continue verdict",
|
||||
opStr: "[ immediate reg 0 continue ]",
|
||||
op: mustCreateImmediate(t, linux.NFT_REG_VERDICT, NewVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with return verdict",
|
||||
opStr: "[ immediate reg 0 return ]",
|
||||
op: mustCreateImmediate(t, linux.NFT_REG_VERDICT, NewVerdictData(Verdict{Code: VC(linux.NFT_RETURN)})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with jump verdict",
|
||||
opStr: "[ immediate reg 0 jump -> next_chain ]",
|
||||
op: mustCreateImmediate(t, linux.NFT_REG_VERDICT, NewVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "next_chain"})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with goto verdict",
|
||||
opStr: "[ immediate reg 0 goto -> next_chain ]",
|
||||
op: mustCreateImmediate(t, linux.NFT_REG_VERDICT, NewVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "next_chain"})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with 4-byte data",
|
||||
opStr: "[ immediate reg 0 0x0201a8c0 ]",
|
||||
op: nil,
|
||||
},
|
||||
{
|
||||
tname: "verdict register with 16-byte data",
|
||||
opStr: "[ immediate reg 0 0xb80d0120 0x00000000 0x00000000 0x02000000 ]",
|
||||
op: nil,
|
||||
},
|
||||
{
|
||||
tname: "16-byte register with verdict data",
|
||||
opStr: "[ immediate reg 1 accept ]",
|
||||
op: nil,
|
||||
},
|
||||
{
|
||||
tname: "16-byte register with verdict data with target",
|
||||
opStr: "[ immediate reg 2 jump -> next_chain ]",
|
||||
op: nil,
|
||||
},
|
||||
{
|
||||
tname: "16-byte register with 4-byte data",
|
||||
opStr: "[ immediate reg 3 0x0201a8c0 ]",
|
||||
op: mustCreateImmediate(t, linux.NFT_REG_3, NewBytesData([]byte{0x02, 0x01, 0xa8, 0xc0})),
|
||||
},
|
||||
{
|
||||
tname: "16-byte register with 16-byte data",
|
||||
opStr: "[ immediate reg 4 0xb80d0120 0x00000000 0x00000000 0x02000000 ]",
|
||||
op: mustCreateImmediate(t, linux.NFT_REG_4, NewBytesData([]byte{0xb8, 0x0d, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00})),
|
||||
},
|
||||
{
|
||||
tname: "16-byte register with 8-byte data",
|
||||
opStr: "[ immediate reg 4 0xb80d0120 0x00000050 ]",
|
||||
op: nil,
|
||||
},
|
||||
{
|
||||
tname: "4-byte register with verdict data",
|
||||
opStr: "[ immediate reg 8 return ]",
|
||||
op: nil,
|
||||
},
|
||||
{
|
||||
tname: "4-byte register with verdict data with target",
|
||||
opStr: "[ immediate reg 9 goto -> next_chain ]",
|
||||
op: nil,
|
||||
},
|
||||
{
|
||||
tname: "4-byte register with 4-byte data",
|
||||
opStr: "[ immediate reg 10 0x0201a8c0 ]",
|
||||
op: mustCreateImmediate(t, linux.NFT_REG32_02, NewBytesData([]byte{0x02, 0x01, 0xa8, 0xc0})),
|
||||
},
|
||||
{
|
||||
tname: "4-byte register with 16-byte data",
|
||||
opStr: "[ immediate reg 9 0xb80d0120 0x00000000 0x00000000 0x02000000 ]",
|
||||
op: nil,
|
||||
},
|
||||
} {
|
||||
t.Run(test.tname, func(t *testing.T) {
|
||||
rule, err := InterpretRule(test.opStr)
|
||||
if err != nil {
|
||||
if test.op == nil {
|
||||
return
|
||||
}
|
||||
t.Fatalf("unexpected interpretation error for %s: %v", test.tname, err)
|
||||
}
|
||||
// interpretOperationTestAction is a generic action for testing the
|
||||
// interpretation of an operation.
|
||||
type interpretOperationTestAction struct {
|
||||
tname string
|
||||
opStr string
|
||||
expected Operation // will be nil if an error is expected
|
||||
}
|
||||
|
||||
if len(rule.ops) != 1 {
|
||||
t.Fatalf("expected single operation for %s, got %d", test.tname, len(rule.ops))
|
||||
}
|
||||
op := rule.ops[0]
|
||||
if err := checkImmediateOp(test.tname, test.op, op); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
})
|
||||
// checkOp is a generic operation validation function used for testing that
|
||||
// the interpretation of an operation matches the expected operation.
|
||||
func checkOp(t *testing.T, test interpretOperationTestAction, checkFunc func(string, Operation, Operation) error) {
|
||||
rule, err := InterpretRule(test.opStr)
|
||||
if test.expected == nil {
|
||||
if err == nil {
|
||||
t.Fatalf("unexpected interpretation success for %s", test.tname)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected interpretation error for %s: %v", test.tname, err)
|
||||
}
|
||||
if len(rule.ops) != 1 {
|
||||
t.Fatalf("expected single operation for %s, got %d", test.tname, len(rule.ops))
|
||||
}
|
||||
actual := rule.ops[0]
|
||||
if actual == nil {
|
||||
t.Fatalf("expected non-nil operation for %s, got nil", test.tname)
|
||||
}
|
||||
if err := checkFunc(test.tname, test.expected, actual); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func checkImmediateOp(tname string, expected *Immediate, actual Operation) error {
|
||||
if actual == nil {
|
||||
return fmt.Errorf("expected non-nil operation for %s, got nil", tname)
|
||||
// TestInterpretImmediateOps tests the interpretation of immediate operations.
|
||||
func TestInterpretImmediateOps(t *testing.T) {
|
||||
for _, test := range []interpretOperationTestAction{
|
||||
{
|
||||
tname: "verdict register with accept verdict",
|
||||
opStr: "[ immediate reg 0 accept ]",
|
||||
expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, NewVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with drop verdict",
|
||||
opStr: "[ immediate reg 0 drop ]",
|
||||
expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, NewVerdictData(Verdict{Code: VC(linux.NF_DROP)})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with continue verdict",
|
||||
opStr: "[ immediate reg 0 continue ]",
|
||||
expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, NewVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with return verdict",
|
||||
opStr: "[ immediate reg 0 return ]",
|
||||
expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, NewVerdictData(Verdict{Code: VC(linux.NFT_RETURN)})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with jump verdict",
|
||||
opStr: "[ immediate reg 0 jump -> next_chain ]",
|
||||
expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, NewVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "next_chain"})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with goto verdict",
|
||||
opStr: "[ immediate reg 0 goto -> next_chain ]",
|
||||
expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, NewVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "next_chain"})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with 4-byte data",
|
||||
opStr: "[ immediate reg 0 0x0201a8c0 ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "verdict register with 16-byte data",
|
||||
opStr: "[ immediate reg 0 0xb80d0120 0x00000000 0x00000000 0x02000000 ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "16-byte register with verdict data",
|
||||
opStr: "[ immediate reg 1 accept ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "16-byte register with verdict data with target",
|
||||
opStr: "[ immediate reg 2 jump -> next_chain ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "16-byte register with 4-byte data",
|
||||
opStr: "[ immediate reg 3 0x0201a8c0 ]",
|
||||
expected: mustCreateImmediate(t, linux.NFT_REG_3, NewBytesData([]byte{0x02, 0x01, 0xa8, 0xc0})),
|
||||
},
|
||||
{
|
||||
tname: "16-byte register with 16-byte data",
|
||||
opStr: "[ immediate reg 4 0xb80d0120 0x00000000 0x00000000 0x02000000 ]",
|
||||
expected: mustCreateImmediate(t, linux.NFT_REG_4, NewBytesData([]byte{0xb8, 0x0d, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00})),
|
||||
},
|
||||
{
|
||||
tname: "16-byte register with 8-byte data",
|
||||
opStr: "[ immediate reg 4 0xb80d0120 0x00000050 ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "4-byte register with verdict data",
|
||||
opStr: "[ immediate reg 8 return ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "4-byte register with verdict data with target",
|
||||
opStr: "[ immediate reg 9 goto -> next_chain ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "4-byte register with 4-byte data",
|
||||
opStr: "[ immediate reg 10 0x0201a8c0 ]",
|
||||
expected: mustCreateImmediate(t, linux.NFT_REG32_02, NewBytesData([]byte{0x02, 0x01, 0xa8, 0xc0})),
|
||||
},
|
||||
{
|
||||
tname: "4-byte register with 16-byte data",
|
||||
opStr: "[ immediate reg 9 0xb80d0120 0x00000000 0x00000000 0x02000000 ]",
|
||||
expected: nil,
|
||||
},
|
||||
} {
|
||||
t.Run(test.tname, func(t *testing.T) { checkOp(t, test, checkImmediateOp) })
|
||||
}
|
||||
}
|
||||
|
||||
// checkImmediateOp checks that the given operation is an immediate operation
|
||||
// and that it matches the expected immediate operation.
|
||||
func checkImmediateOp(tname string, expected Operation, actual Operation) error {
|
||||
expectedImm := expected.(*Immediate)
|
||||
imm, ok := actual.(*Immediate)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected operation type to be Immediate for %s, got %s", tname, actual.TypeString())
|
||||
return fmt.Errorf("expected operation type to be Immediate for %s, got %T", tname, actual)
|
||||
}
|
||||
if imm.dreg != expected.dreg {
|
||||
return fmt.Errorf("expected register to be %d for %s, got %d", expected.dreg, tname, imm.dreg)
|
||||
if imm.dreg != expectedImm.dreg {
|
||||
return fmt.Errorf("expected register to be %d for %s, got %d", expectedImm.dreg, tname, imm.dreg)
|
||||
}
|
||||
if !imm.data.Equal(expected.data) {
|
||||
return fmt.Errorf("expected data to be %s for %s, got %s", expected.data.String(), tname, imm.data.String())
|
||||
if !imm.data.Equal(expectedImm.data) {
|
||||
return fmt.Errorf("expected data to be %v for %s, got %v", expectedImm.data, tname, imm.data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestInterpretComparisonOps tests the interpretation of comparison operations.
|
||||
func TestInterpretComparisonOps(t *testing.T) {
|
||||
for _, test := range []interpretOperationTestAction{
|
||||
{
|
||||
tname: "verdict register with 4-byte data comparison",
|
||||
opStr: "[ cmp eq reg 0 0x00000002 ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "register == 4-byte data",
|
||||
opStr: "[ cmp eq reg 1 0x0302010a ]",
|
||||
expected: mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, NewBytesData([]byte{0x03, 0x02, 0x01, 0x0a})),
|
||||
},
|
||||
{
|
||||
tname: "register != 4-byte data",
|
||||
opStr: "[ cmp neq reg 2 0x00000064 ]",
|
||||
expected: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_NEQ, NewBytesData([]byte{0x00, 0x00, 0x00, 0x64})),
|
||||
},
|
||||
{
|
||||
tname: "register < 4-byte data",
|
||||
opStr: "[ cmp lt reg 3 0x00000000 ]",
|
||||
expected: mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_LT, NewBytesData([]byte{0x00, 0x00, 0x00, 0x00})),
|
||||
},
|
||||
{
|
||||
tname: "register <= 4-byte data",
|
||||
opStr: "[ cmp lte reg 4 0x00000164 ]",
|
||||
expected: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_LTE, NewBytesData([]byte{0x00, 0x00, 0x01, 0x64})),
|
||||
},
|
||||
{
|
||||
tname: "register > 4-byte data",
|
||||
opStr: "[ cmp gt reg 8 0xe8030000 ]",
|
||||
expected: mustCreateComparison(t, linux.NFT_REG32_00, linux.NFT_CMP_GT, NewBytesData([]byte{0xe8, 0x03, 0x00, 0x00})),
|
||||
},
|
||||
{
|
||||
tname: "register >= 4-byte data",
|
||||
opStr: "[ cmp gte reg 9 0xc02b0000 ]",
|
||||
expected: mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_GTE, NewBytesData([]byte{0xc0, 0x2b, 0x00, 0x00})),
|
||||
},
|
||||
{
|
||||
tname: "verdict register with 16-byte data comparison",
|
||||
opStr: "[ cmp gt reg 0 0xb80d0120 0x00000000 0x00000000 0x02000000 ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "4-byte register with 16-byte data comparison",
|
||||
opStr: "[ cmp lte reg 8 0x0302010a 0x00000000 0x00000000 0x02000001 ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "register == 16-byte data",
|
||||
opStr: "[ cmp eq reg 1 0x0302010a 0x00000000 0x00000000 0x02000002 ]",
|
||||
expected: mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, NewBytesData([]byte{0x03, 0x02, 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02})),
|
||||
},
|
||||
{
|
||||
tname: "register != 16-byte data",
|
||||
opStr: "[ cmp neq reg 2 0x00000064 0x00000000 0x00000000 0x02000000 ]",
|
||||
expected: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_NEQ, NewBytesData([]byte{0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00})),
|
||||
},
|
||||
{
|
||||
tname: "register < 16-byte data",
|
||||
opStr: "[ cmp lt reg 3 0x00000000 0x00000000 0x00000000 0x00000000 ]",
|
||||
expected: mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_LT, NewBytesData([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})),
|
||||
},
|
||||
{
|
||||
tname: "register <= 16-byte data",
|
||||
opStr: "[ cmp lte reg 4 0x00000164 0x00000164 0x00000164 0x00000164 ]",
|
||||
expected: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_LTE, NewBytesData([]byte{0x00, 0x00, 0x01, 0x64, 0x00, 0x00, 0x01, 0x64, 0x00, 0x00, 0x01, 0x64, 0x00, 0x00, 0x01, 0x64})),
|
||||
},
|
||||
{
|
||||
tname: "register > 16-byte data",
|
||||
opStr: "[ cmp gt reg 2 0xe8030000 0x00000f13 0xc0090000 0x0b136a87 ]",
|
||||
expected: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_GT, NewBytesData([]byte{0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87})),
|
||||
},
|
||||
{
|
||||
tname: "register >= 16-byte data",
|
||||
opStr: "[ cmp gte reg 3 0x0a000120 0x00000f13 0xc0090000 0x0b136a87 ]",
|
||||
expected: mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_GTE, NewBytesData([]byte{0x0a, 0x00, 0x01, 0x20, 0x00, 0x00, 0x0f, 0x13, 0xc0, 0x09, 0x00, 0x00, 0x0b, 0x13, 0x6a, 0x87})),
|
||||
},
|
||||
} {
|
||||
t.Run(test.tname, func(t *testing.T) { checkOp(t, test, checkComparisonOp) })
|
||||
}
|
||||
}
|
||||
|
||||
// checkComparisonOp checks that the given operation is an comparison operation
|
||||
// and that it matches the expected comparison operation.
|
||||
func checkComparisonOp(tname string, expected Operation, actual Operation) error {
|
||||
expectedCmp := expected.(*Comparison)
|
||||
cmp, ok := actual.(*Comparison)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected operation type to be Comparison for %s, got %T", tname, actual)
|
||||
}
|
||||
if cmp.sreg != expectedCmp.sreg {
|
||||
return fmt.Errorf("expected register to be %d for %s, got %d", expectedCmp.sreg, tname, cmp.sreg)
|
||||
}
|
||||
if cmp.cop != expectedCmp.cop {
|
||||
return fmt.Errorf("expected comparison operator to be %v for %s, got %v", expectedCmp.cop, tname, cmp.cop)
|
||||
}
|
||||
if !cmp.data.Equal(expectedCmp.data) {
|
||||
return fmt.Errorf("expected data to be %v for %s, got %v", expectedCmp.data, tname, cmp.data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestInterpretRule tests the interpretation of basic and general rules as a
|
||||
// list of operations.
|
||||
func TestInterpretRule(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
tname string
|
||||
ruleStr string
|
||||
rule *Rule // will be nil if an error is expected
|
||||
tname string
|
||||
ruleStr string
|
||||
expected *Rule // will be nil if an error is expected
|
||||
}{
|
||||
{
|
||||
tname: "empty ruleset",
|
||||
ruleStr: ``,
|
||||
rule: &Rule{},
|
||||
tname: "empty ruleset",
|
||||
ruleStr: ``,
|
||||
expected: &Rule{},
|
||||
},
|
||||
{
|
||||
tname: "empty ruleset with excess whitespace",
|
||||
@@ -167,33 +286,40 @@ func TestInterpretRule(t *testing.T) {
|
||||
|
||||
|
||||
`,
|
||||
rule: &Rule{},
|
||||
expected: &Rule{},
|
||||
},
|
||||
} {
|
||||
t.Run(test.tname, func(t *testing.T) {
|
||||
rule, err := InterpretRule(test.ruleStr)
|
||||
if err != nil {
|
||||
if test.rule == nil {
|
||||
return
|
||||
if test.expected == nil {
|
||||
if err == nil {
|
||||
t.Fatalf("unexpected interpretation success for %s", test.tname)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected interpretation error for %s: %v", test.tname, err)
|
||||
}
|
||||
|
||||
if len(rule.ops) != len(test.rule.ops) {
|
||||
t.Fatalf("expected %d operations for %s, got %d", len(test.rule.ops), test.tname, len(rule.ops))
|
||||
if len(rule.ops) != len(test.expected.ops) {
|
||||
t.Fatalf("expected %d operations for %s, got %d", len(test.expected.ops), test.tname, len(rule.ops))
|
||||
}
|
||||
|
||||
// Checks each operation in the rule with the appropriate check function.
|
||||
for i, op := range rule.ops {
|
||||
testOp := test.rule.ops[i]
|
||||
testOp := test.expected.ops[i]
|
||||
switch testOp.(type) {
|
||||
case *Immediate:
|
||||
if err := checkImmediateOp(test.tname, testOp.(*Immediate), op); err != nil {
|
||||
if err := checkImmediateOp(test.tname, testOp, op); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
case *Comparison:
|
||||
if err := checkComparisonOp(test.tname, testOp, op); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
// TODO(b/345684870): cases will be added here as more types are supported.
|
||||
default:
|
||||
t.Fatalf("unexpected operation type for %s: %s", test.tname, testOp.TypeString())
|
||||
t.Fatalf("unexpected operation type for %s: %T", test.tname, testOp)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user