Implement Bitwise operation (evaluation, tests, partial interpretation).

PiperOrigin-RevId: 670691161
This commit is contained in:
Jayden Nyamiaka
2024-09-03 13:50:05 -07:00
committed by gVisor bot
parent 974e6dac72
commit 3fbdd4a142
4 changed files with 594 additions and 4 deletions
+187 -4
View File
@@ -54,11 +54,19 @@ import (
// TODO(b/345684870): Remove unused functions once initial implementation is
// complete.
// Defines the default capacity for the slices of hook functions and rules.
// Defines general constants for the nftables interpreter.
const (
registersByteSize = 64 // 4 16-byte registers or 16 4-byte registers.
nestedJumpLimit = 16 // Maximum number of nested jumps allowed,
// corresponding to NFT_JUMP_STACK_SIZE in include/net/netfilter/nf_tables.h.
// Number of bytes for 4 16-byte registers or 16 4-byte registers.
registersByteSize = 64
// Maximum number of nested jumps allowed, corresponding to
// NFT_JUMP_STACK_SIZE in include/net/netfilter/nf_tables.h.
nestedJumpLimit = 16
// Limit (exclusive) for number of buts that can be shifted for non-boolean
// bitwise operations.
bitshiftLimit = 32
)
// AddressFamily describes the 6 address families supported by nftables.
@@ -570,6 +578,7 @@ var (
_ operation = (*comparison)(nil)
_ operation = (*payloadLoad)(nil)
_ operation = (*payloadSet)(nil)
_ operation = (*bitwise)(nil)
)
// immediate is an operation that sets the data in a register.
@@ -958,6 +967,180 @@ func (op payloadSet) evaluate(regs *registerSet, pkt *stack.PacketBuffer) {
}
}
// bitwiseOp is the bitwise operator for a bitwise operation.
// Note: corresponds to enum nft_bitwise_ops from
// include/uapi/linux/netfilter/nf_tables.h and uses the same constants.
type bitwiseOp int
// String for bitwiseOp returns the string representation of the bitwise
// operator.
func (bop bitwiseOp) String() string {
switch bop {
case linux.NFT_BITWISE_BOOL:
return "bitwise boolean"
case linux.NFT_BITWISE_LSHIFT:
return "bitwise <<"
case linux.NFT_BITWISE_RSHIFT:
return "bitwise >>"
default:
panic(fmt.Sprintf("invalid bitwise operator: %d", int(bop)))
}
}
// bitwise is an operation that performs bitwise math operations over data in
// a given register, storing the result in a destination register.
// Note: bitwise operations are not supported for the verdict register.
type bitwise struct {
sreg uint8 // Number of the source register.
dreg uint8 // Number of the destination register.
bop bitwiseOp // Bitwise operator to use.
blen uint8 // Number of bytes to apply bitwise operation to.
mask registerData // Mask to apply bitwise & for boolean operations (before ^).
xor registerData // Xor to apply bitwise ^ for boolean operations (after &).
shift uint32 // Shift to apply bitwise <</>> for non-boolean operations.
// Note: Technically, the linux kernel has defined bool, lshift, and rshift
// as the 3 types of bitwise operations. However, we have not been able to
// observe the lshift or rshift operations used by the nft binary. Thus, we
// have no way to test the interpretation of these operations. Maintaining
// consistency with the linux kernel, we have fully implemented lshift and
// rshift, and We will leave the code here in case we are able to observe
// their use in the future (perhaps outside the nft binary debug output).
}
// newBitwiseBool creates a new bitwise boolean operation.
func newBitwiseBool(sreg, dreg uint8, mask, xor []byte) (*bitwise, error) {
if sreg == linux.NFT_REG_VERDICT || dreg == linux.NFT_REG_VERDICT {
return nil, fmt.Errorf("bitwise operation cannot use verdict register as source or destination")
}
blen := len(mask)
if blen != len(xor) {
return nil, fmt.Errorf("bitwise boolean operation mask and xor must be the same length")
}
if blen > 16 || (blen > 4 && (is4ByteRegister(sreg) || is4ByteRegister(dreg))) {
return nil, fmt.Errorf("bitwise operation length %d is too long for source register %d, destination register %d", blen, sreg, dreg)
}
return &bitwise{sreg: sreg, dreg: dreg, bop: linux.NFT_BITWISE_BOOL, blen: uint8(blen), mask: newBytesData(mask), xor: newBytesData(xor)}, nil
}
// newBitwiseShift creates a new bitwise shift operation.
func newBitwiseShift(sreg, dreg, blen uint8, shift uint32, right bool) (*bitwise, error) {
if sreg == linux.NFT_REG_VERDICT || dreg == linux.NFT_REG_VERDICT {
return nil, fmt.Errorf("bitwise operation cannot use verdict register as source or destination")
}
if blen > 16 || (blen > 4 && (is4ByteRegister(sreg) || is4ByteRegister(dreg))) {
return nil, fmt.Errorf("bitwise operation length %d is too long for source register %d, destination register %d", blen, sreg, dreg)
}
if shift >= bitshiftLimit {
return nil, fmt.Errorf("bitwise operation shift %d must be less than %d", shift, bitshiftLimit)
}
bop := bitwiseOp(linux.NFT_BITWISE_LSHIFT)
if right {
bop = linux.NFT_BITWISE_RSHIFT
}
return &bitwise{sreg: sreg, dreg: dreg, blen: blen, bop: bop, shift: shift}, nil
}
// evaluateBitwiseBool performs the bitwise boolean operation on the source register
// data and stores the result in the destination register.
func evaluateBitwiseBool(sregBuf, dregBuf, mask, xor []byte) {
for i := 0; i < len(mask); i++ {
dregBuf[i] = (sregBuf[i] & mask[i]) ^ xor[i]
}
}
// evaluateBitwiseLshift performs the bitwise left shift operation on source
// register in 4 byte chunks and stores the result in the destination register.
func evaluateBitwiseLshift(sregBuf, dregBuf []byte, shift uint32) {
carry := uint32(0)
// Rounds down to nearest 4-byte multiple.
for start := (len(sregBuf) - 1) & ^3; start >= 0; start -= 4 {
// Extracts the 4-byte chunk from the source register, padding if necessary.
var chunk uint32
if start+4 <= len(sregBuf) {
chunk = binary.BigEndian.Uint32(sregBuf[start:])
} else {
var padded [4]byte
copy(padded[:], sregBuf[start:])
chunk = binary.BigEndian.Uint32(padded[:])
}
// Does left shift, adds the carry, and calculates the new carry.
res := (chunk << shift) | carry
carry = chunk >> (bitshiftLimit - shift)
// Stores the result in the destination register, using temporary buffer
// if necessary.
if start+4 <= len(dregBuf) {
binary.BigEndian.PutUint32(dregBuf[start:], res)
} else {
var padded [4]byte
binary.BigEndian.PutUint32(padded[:], res)
copy(dregBuf[start:], padded[:])
}
}
}
// evaluateBitwiseRshift performs the bitwise right shift operation on source
// register in 4 byte chunks and stores the result in the destination register.
func evaluateBitwiseRshift(sregBuf, dregBuf []byte, shift uint32) {
carry := uint32(0)
for start := 0; start < len(sregBuf); start += 4 {
// Extracts the 4-byte chunk from the source register, padding if necessary.
var chunk uint32
if start+4 <= len(sregBuf) {
chunk = binary.BigEndian.Uint32(sregBuf[start:])
} else {
var padded [4]byte
copy(padded[:], sregBuf[start:])
chunk = binary.BigEndian.Uint32(padded[:])
}
// Does right shift, adds the carry, and calculates the new carry.
res := carry | (chunk >> shift)
carry = chunk << (bitshiftLimit - shift)
// Stores the result in the destination register, using temporary buffer
// if necessary.
if start+4 <= len(dregBuf) {
binary.BigEndian.PutUint32(dregBuf[start:], res)
} else {
var padded [4]byte
binary.BigEndian.PutUint32(padded[:], res)
copy(dregBuf[start:], padded[:])
}
}
}
// evaluate for bitwise performs the bitwise operation on the source register
// data and stores the result in the destination register.
func (op bitwise) evaluate(regs *registerSet, pkt *stack.PacketBuffer) {
// Gets the specified buffers of the source and destination registers.
sregBuf := getRegisterBuffer(regs, op.sreg)[:op.blen]
dregBuf := getRegisterBuffer(regs, op.dreg)[:op.blen]
if op.bop == linux.NFT_BITWISE_BOOL {
mask, ok := op.mask.(bytesData)
if !ok {
panic("bitwise bool mask data is not BytesData")
}
xor, ok := op.xor.(bytesData)
if !ok {
panic("bitwise bool xor data is not BytesData")
}
evaluateBitwiseBool(sregBuf, dregBuf, mask.data, xor.data)
return
} else {
if op.bop == linux.NFT_BITWISE_LSHIFT {
evaluateBitwiseLshift(sregBuf, dregBuf, op.shift)
} else {
evaluateBitwiseRshift(sregBuf, dregBuf, op.shift)
}
}
}
//
// Register and Register-Related Implementations.
// Note: Registers are represented by type uint8 for the register number.
+190
View File
@@ -1906,6 +1906,178 @@ func TestEvaluatePayloadSet(t *testing.T) {
}
}
// 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 := 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)
}
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)
}
})
}
}
// TestLoopCheckOnRegisterAndUnregister tests the loop checking and accompanying
// logic on registering and unregistering rules.
func TestLoopCheckOnRegisterAndUnregister(t *testing.T) {
@@ -2537,3 +2709,21 @@ func mustCreatePayloadSet(t *testing.T, base payloadBase, offset uint8, len uint
}
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
}
+110
View File
@@ -147,6 +147,10 @@ func InterpretOperation(line string, lnIdx int) (operation, error) {
return InterpretPayloadSet(line, lnIdx)
}
return nil, &SyntaxError{lnIdx, 2, fmt.Sprintf("unrecognized operation type: payload %s", tokens[2])}
case "bitwise":
// Assumes the bitwise operation is a boolean because interpretation of
// non-boolean operations is not supported from the nft binary debug output.
return InterpretBitwiseBool(line, lnIdx)
default:
return nil, &SyntaxError{lnIdx, 1, fmt.Sprintf("unrecognized operation type: %s", tokens[1])}
}
@@ -502,6 +506,112 @@ func InterpretPayloadSet(line string, lnIdx int) (operation, error) {
return pdset, nil
}
// InterpretBitwiseBool creates a new Comparison operation from the given string.
func InterpretBitwiseBool(line string, lnIdx int) (operation, error) {
tokens := strings.Fields(line)
// Requires at least 14 tokens:
// "[", "bitwise", "reg", dreg index, "=", "(", "reg", sreg index, "&", mask value, ")", "^", xor value, "]".
if len(tokens) < 14 {
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for bitwise boolean operation, should be at least 14, got %d", len(tokens))}
}
if err := checkOperationBrackets(tokens, lnIdx); err != nil {
return nil, err
}
tkIdx := 1
// First token should be "bitwise".
if err := consumeToken("bitwise", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Second token should be "reg".
if err := consumeToken("reg", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Third token should be the uint8 representing destination register index.
dreg, err := parseRegister(tokens[tkIdx], lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Fourth token should be "=".
if err := consumeToken("=", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Fifth token should be "(".
if err := consumeToken("(", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Sixth token should be "reg".
if err := consumeToken("reg", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Seventh token should be the uint8 representing source register index.
sreg, err := parseRegister(tokens[tkIdx], lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Eighth token should be "&".
if err := consumeToken("&", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Ninth token should be the bytesData representing the mask value.
nextIdx, mask, err := parseHexData(tokens, lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx = nextIdx
// Tenth token should be ")".
if err := consumeToken(")", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Eleventh token should be "^".
if err := consumeToken("^", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Twelfth token should be the bytesData representing the xor value.
nextIdx, xor, err := parseHexData(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 bitwise boolean operation"}
}
// Create the operation with the specified arguments.
bitwiseBool, err := newBitwiseBool(sreg, dreg, mask, xor)
if err != nil {
return nil, &LogicError{lnIdx, tkIdx, err}
}
return bitwiseBool, nil
}
//
// Interpreter Helper Functions.
//
+107
View File
@@ -664,6 +664,113 @@ func checkPayloadSetOp(tname string, expected operation, actual operation) error
return nil
}
// TestInterpretBitwiseOps tests interpretation of bitwise operations.
// Note: Only tests bitwise bool operations for now because interpretation of
// non-boolean operations is not supported from the nft binary debug output.
func TestInterpretBitwiseOps(t *testing.T) {
for _, test := range []interpretOperationTestAction{
// Invalid interpretations.
{
tname: "verdict register with bitwise bool",
opStr: "[ bitwise reg 0 = ( reg 1 & 0x000003ff ) ^ 0x0000b000 ]",
expected: nil,
},
{
tname: "4-byte register with > 4-byte data for bitwise bool",
opStr: "[ bitwise reg 1 = ( reg 9 & 0x000003ff 0x09040302 ) ^ 0x0000b000 0x11ff11ff ]",
expected: nil,
},
{
tname: "mismatch mask and xor lengths for bitwise bool",
opStr: "[ bitwise reg 1 = ( reg 1 & 0x000003ff ) ^ 0x0000b000 0x11ff11ff ]",
expected: nil,
},
// cmd: add rule ip filter input ip dscp set 0x2c
{
tname: "same 4-byte register with 4-byte data for bitwise bool",
opStr: "[ bitwise reg 10 = ( reg 10 & 0x000003ff ) ^ 0x0000b000 ]",
expected: mustCreateBitwiseBool(t, linux.NFT_REG32_02, linux.NFT_REG32_02, []byte{0xff, 0x03, 0x00, 0x00}, []byte{0x00, 0xb0, 0x00, 0x00}),
},
{
tname: "dif 4-byte registers with 4-byte data for bitwise bool",
opStr: "[ bitwise reg 8 = ( reg 9 & 0x000003ff ) ^ 0x0000b000 ]",
expected: mustCreateBitwiseBool(t, linux.NFT_REG32_01, linux.NFT_REG32_00, []byte{0xff, 0x03, 0x00, 0x00}, []byte{0x00, 0xb0, 0x00, 0x00}),
},
// cmd: add rule ip filter input ip saddr and 55 or 0xffff0000 == 34
{
tname: "same 16-byte register with 4-byte data for bitwise bool",
opStr: "[ bitwise reg 1 = ( reg 1 & 0x37000000 ) ^ 0x0000ffff ]",
expected: mustCreateBitwiseBool(t, linux.NFT_REG_1, linux.NFT_REG_1, []byte{0x00, 0x00, 0x00, 0x37}, []byte{0xff, 0xff, 0x00, 0x00}),
},
{
tname: "dif 16-byte registers with 4-byte data for bitwise bool",
opStr: "[ bitwise reg 4 = ( reg 3 & 0x37000000 ) ^ 0x0000ffff ]",
expected: mustCreateBitwiseBool(t, linux.NFT_REG_3, linux.NFT_REG_4, []byte{0x00, 0x00, 0x00, 0x37}, []byte{0xff, 0xff, 0x00, 0x00}),
},
// cmd: add rule ip filter input ip saddr and 0xff0230ff == 5
{
tname: "4- and 16-byte registers with 4-byte data for bitwise bool",
opStr: "[ bitwise reg 4 = ( reg 14 & 0xff3002ff ) ^ 0x00000000 ]",
expected: mustCreateBitwiseBool(t, linux.NFT_REG32_06, linux.NFT_REG_4, []byte{0xff, 0x02, 0x30, 0xff}, []byte{0x00, 0x00, 0x00, 0x00}),
},
{
tname: "16- and 4-byte registers with 4-byte data for bitwise bool",
opStr: "[ bitwise reg 14 = ( reg 1 & 0xff3002ff ) ^ 0x00000000 ]",
expected: mustCreateBitwiseBool(t, linux.NFT_REG_1, linux.NFT_REG32_06, []byte{0xff, 0x02, 0x30, 0xff}, []byte{0x00, 0x00, 0x00, 0x00}),
},
// More than 4 bytes of data.
{
tname: "8-byte data for bitwise bool",
opStr: "[ bitwise reg 1 = ( reg 1 & 0x00000000 0x00000000 ) ^ 0x00000164 0x00000164 ]",
expected: mustCreateBitwiseBool(t, linux.NFT_REG_1, linux.NFT_REG_1, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, []byte{0x64, 0x01, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00}),
},
{
tname: "12-byte data for bitwise bool",
opStr: "[ bitwise reg 4 = ( reg 2 & 0x0302010a 0x00000000 0x12345678 ) ^ 0x0a000120 0x00000f13 0xc0090000 ]",
expected: mustCreateBitwiseBool(t, linux.NFT_REG_2, linux.NFT_REG_4, []byte{0x0a, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x34, 0x12}, []byte{0x20, 0x01, 0x00, 0x0a, 0x13, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x09, 0xc0}),
},
{
tname: "16-byte data for bitwise bool",
opStr: "[ bitwise reg 1 = ( reg 3 & 0xe8030000 0x00000f13 0xc0090000 0x0b136a87 ) ^ 0x0a000120 0x00000f13 0xc0090000 0x00000000 ]",
expected: mustCreateBitwiseBool(t, linux.NFT_REG_3, linux.NFT_REG_1, []byte{0x00, 0x00, 0x03, 0xe8, 0x13, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x09, 0xc0, 0x87, 0x6a, 0x13, 0x0b}, []byte{0x20, 0x01, 0x00, 0x0a, 0x13, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x09, 0xc0, 0x00, 0x00, 0x00, 0x00}),
},
} {
t.Run(test.tname, func(t *testing.T) { checkOp(t, test, checkBitwiseOp) })
}
}
// checkBitwiseOp checks that the given operation is a bitwise operation and
// that it matches the expected bitwise operation.
func checkBitwiseOp(tname string, expected operation, actual operation) error {
expectedBit := expected.(*bitwise)
bit, ok := actual.(*bitwise)
if !ok {
return fmt.Errorf("expected operation type to be BitwiseBool for %s, got %T", tname, actual)
}
if bit.sreg != expectedBit.sreg {
return fmt.Errorf("expected source register to be %d for %s, got %d", expectedBit.sreg, tname, bit.sreg)
}
if bit.dreg != expectedBit.dreg {
return fmt.Errorf("expected destination register to be %d for %s, got %d", expectedBit.dreg, tname, bit.dreg)
}
if bit.bop != expectedBit.bop {
return fmt.Errorf("expected bitwise operation to be %d for %s, got %d", expectedBit.bop, tname, bit.bop)
}
if bit.blen != expectedBit.blen {
return fmt.Errorf("expected bitwise length to be %d for %s, got %d", expectedBit.blen, tname, bit.blen)
}
if !bit.mask.equal(expectedBit.mask) {
return fmt.Errorf("expected bitwise mask to be %v for %s, got %v", expectedBit.mask, tname, bit.mask)
}
if !bit.xor.equal(expectedBit.xor) {
return fmt.Errorf("expected bitwise xor to be %v for %s, got %v", expectedBit.xor, tname, bit.xor)
}
if bit.shift != expectedBit.shift {
return fmt.Errorf("expected bitwise shift to be %d for %s, got %d", expectedBit.shift, tname, bit.shift)
}
return nil
}
// TestInterpretRule tests the interpretation of basic and general rules as a
// list of operations.
func TestInterpretRule(t *testing.T) {