mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement Bitwise operation (evaluation, tests, partial interpretation).
PiperOrigin-RevId: 670691161
This commit is contained in:
committed by
gVisor bot
parent
974e6dac72
commit
3fbdd4a142
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user