Implement Byteorder operation (parsing, interpretation, evaluation, tests).

Uses binary.NativeEndian & binary.BigEndian for all byteorder operations.

Also removes all magic number instances of 4 and 16 as register sizes.

PiperOrigin-RevId: 674065001
This commit is contained in:
Jayden Nyamiaka
2024-09-12 17:08:39 -07:00
committed by gVisor bot
parent 2da6847b5d
commit 548733574e
4 changed files with 560 additions and 27 deletions
+133 -8
View File
@@ -55,6 +55,8 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// TODO(b/345684870): Break this file up into multiple files by operation type.
// Each operation should get its own file.
// TODO(b/345684870): Make the nftables package thread-safe! Must be done before
// the package is used in production.
@@ -593,6 +595,7 @@ var (
_ operation = (*counter)(nil)
_ operation = (*last)(nil)
_ operation = (*route)(nil)
_ operation = (*byteorder)(nil)
)
// immediate is an operation that sets the data in a register.
@@ -877,7 +880,7 @@ func newPayloadLoad(base payloadBase, offset, blen, dreg uint8) (*payloadLoad, e
if isVerdictRegister(dreg) {
return nil, fmt.Errorf("payload load operation cannot use verdict register as destination")
}
if blen > 16 || (blen > 4 && is4ByteRegister(dreg)) {
if blen > linux.NFT_REG_SIZE || (blen > linux.NFT_REG32_SIZE && is4ByteRegister(dreg)) {
return nil, fmt.Errorf("payload length %d is too long for destination register %d", blen, dreg)
}
if err := validatePayloadBase(base); err != nil {
@@ -950,7 +953,7 @@ func newPayloadSet(base payloadBase, offset, blen, sreg, csumType, csumOffset, c
if isVerdictRegister(sreg) {
return nil, fmt.Errorf("payload set operation cannot use verdict register as destination")
}
if blen > 16 || (blen > 4 && is4ByteRegister(sreg)) {
if blen > linux.NFT_REG_SIZE || (blen > linux.NFT_REG32_SIZE && is4ByteRegister(sreg)) {
return nil, fmt.Errorf("payload length %d is too long for destination register %d", blen, sreg)
}
if err := validatePayloadBase(base); err != nil {
@@ -1110,7 +1113,7 @@ func newBitwiseBool(sreg, dreg uint8, mask, xor []byte) (*bitwise, error) {
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))) {
if blen > linux.NFT_REG_SIZE || (blen > linux.NFT_REG32_SIZE && (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
@@ -1121,7 +1124,7 @@ func newBitwiseShift(sreg, dreg, blen uint8, shift uint32, right bool) (*bitwise
if isVerdictRegister(sreg) || isVerdictRegister(dreg) {
return nil, fmt.Errorf("bitwise operation cannot use verdict register as source or destination")
}
if blen > 16 || (blen > 4 && (is4ByteRegister(sreg) || is4ByteRegister(dreg))) {
if blen > linux.NFT_REG_SIZE || (blen > linux.NFT_REG32_SIZE && (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 {
@@ -1389,6 +1392,128 @@ func (op route) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule)
data.storeData(regs, op.dreg)
}
// byteorder is an operation that performs byte order operations on a register.
// Note: byteorder operations are not supported for the verdict register.
type byteorder struct {
sreg uint8 // Number of the source register.
dreg uint8 // Number of the destination register.
bop byteorderOp // Byte order operation to perform.
blen uint8 // Number of total bytes to operate on.
size uint8 // Granular size in bytes to operate on.
}
// byteorderOp is the byte order operator for a byteorder operation.
// Note: corresponds to enum nft_byteorder_ops from
// include/uapi/linux/netfilter/nf_tables.h and uses the same constants.
type byteorderOp int
// String for byteorderOp returns the string representation of the byteorder
// operator.
func (bop byteorderOp) String() string {
switch bop {
case linux.NFT_BYTEORDER_NTOH:
return "network to host"
case linux.NFT_BYTEORDER_HTON:
return "host to network"
default:
panic(fmt.Sprintf("unknown supported byteorder operator: %d", int(bop)))
}
}
// validateByteorderOp ensures the byteorder operator is valid.
func validateByteorderOp(bop byteorderOp) error {
switch bop {
// Supported operators.
case linux.NFT_BYTEORDER_NTOH, linux.NFT_BYTEORDER_HTON:
return nil
default:
return fmt.Errorf("invalid byteorder operator: %d", int(bop))
}
}
// newByteorder creates a new byteorder operation.
func newByteorder(sreg, dreg uint8, bop byteorderOp, blen, size uint8) (*byteorder, error) {
if isVerdictRegister(sreg) || isVerdictRegister(dreg) {
return nil, fmt.Errorf("byteorder operation cannot use verdict register")
}
if err := validateByteorderOp(bop); err != nil {
return nil, err
}
if blen > linux.NFT_REG_SIZE {
return nil, fmt.Errorf("byteorder operation cannot have length greater than the max register size of %d bytes", linux.NFT_REG_SIZE)
}
if (is4ByteRegister(sreg) || is4ByteRegister(dreg)) && blen > linux.NFT_REG32_SIZE {
return nil, fmt.Errorf("byteorder operation cannot have length greater than the max register size of %d bytes", linux.NFT_REG32_SIZE)
}
if size > blen {
return nil, fmt.Errorf("byteorder operation cannot have size greater than length")
}
if size != 2 && size != 4 && size != 8 {
return nil, fmt.Errorf("byteorder operation size must be 2, 4, or 8 bytes")
}
return &byteorder{sreg: sreg, dreg: dreg, bop: bop, blen: blen, size: size}, nil
}
// evaluate for byteorder performs the byte order operation on the source
// register and stores the result in the destination register.
func (op byteorder) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule) {
// Gets the source and destination registers.
src := getRegisterBuffer(regs, op.sreg)
dst := getRegisterBuffer(regs, op.dreg)
// Performs the byte order operations on the source register and stores the
// result in as many bytes as are available in the destination register.
switch op.size {
case 8:
switch op.bop {
case linux.NFT_BYTEORDER_NTOH:
for i := uint8(0); i < op.blen; i += 8 {
networkNum := binary.BigEndian.Uint64(src[i : i+8])
binary.NativeEndian.PutUint64(dst[i:], networkNum)
}
case linux.NFT_BYTEORDER_HTON:
for i := uint8(0); i < op.blen; i += 8 {
hostNum := binary.NativeEndian.Uint64(src[i : i+8])
binary.BigEndian.PutUint64(dst[i:], hostNum)
}
}
case 4:
switch op.bop {
case linux.NFT_BYTEORDER_NTOH:
for i := uint8(0); i < op.blen; i += 4 {
networkNum := binary.BigEndian.Uint32(src[i : i+4])
binary.NativeEndian.PutUint32(dst[i:], networkNum)
}
case linux.NFT_BYTEORDER_HTON:
for i := uint8(0); i < op.blen; i += 4 {
hostNum := binary.NativeEndian.Uint32(src[i : i+4])
binary.BigEndian.PutUint32(dst[i:], hostNum)
}
}
case 2:
switch op.bop {
case linux.NFT_BYTEORDER_NTOH:
for i := uint8(0); i < op.blen; i += 2 {
networkNum := binary.BigEndian.Uint16(src[i : i+2])
binary.NativeEndian.PutUint16(dst[i:], networkNum)
}
case linux.NFT_BYTEORDER_HTON:
for i := uint8(0); i < op.blen; i += 2 {
hostNum := binary.NativeEndian.Uint16(src[i : i+2])
binary.BigEndian.PutUint16(dst[i:], hostNum)
}
}
}
// Zeroes out excess bytes of the destination register.
// This is done since comparison can be done in multiples of 4 bytes.
if rem := op.blen % 4; rem != 0 {
clear(dst[op.blen : op.blen+4-rem])
}
}
//
// Register and Register-Related Implementations.
// Note: Registers are represented by type uint8 for the register number.
@@ -1481,8 +1606,8 @@ func newBytesData(bytes []byte) bytesData {
if len(bytes) == 0 {
panic("bytes data cannot be empty")
}
if len(bytes) > 16 {
panic(fmt.Errorf("bytes data cannot be more than 16 bytes: %d", len(bytes)))
if len(bytes) > linux.NFT_REG_SIZE {
panic(fmt.Errorf("bytes data cannot be more than %d bytes: %d", linux.NFT_REG_SIZE, len(bytes)))
}
return bytesData{data: bytes}
}
@@ -1509,8 +1634,8 @@ func (rd bytesData) validateRegister(reg uint8) error {
if isVerdictRegister(reg) {
return fmt.Errorf("data cannot be stored in verdict register")
}
if is4ByteRegister(reg) && len(rd.data) > 4 {
return fmt.Errorf("%d-byte data cannot be stored in 4-byte register", len(rd.data))
if is4ByteRegister(reg) && len(rd.data) > linux.NFT_REG32_SIZE {
return fmt.Errorf("%d-byte data cannot be stored in %d-byte register", len(rd.data), linux.NFT_REG32_SIZE)
}
// 16-byte register can be used for any data (guaranteed to be <= 16 bytes)
return nil
+252
View File
@@ -2570,6 +2570,249 @@ func TestEvaluateRoute(t *testing.T) {
}
}
// TestEvaluateByteorder tests that the Byteorder operation correctly performs
// the appropriate byteorder operation on the source register data and stores
// the result in the destination register.
// Note: Relies on expected behavior of the Immediate and Comparison operation.
func TestEvaluateByteorder(t *testing.T) {
// Given a big endian and little endian byte slice of the same number, returns
// the correct byte slice based on the host endianness.
// Note: Uses enclosure so endianness doesn't need to be passed as an arg or
// rechecked for every call.
chooseOrder := func() func([]byte, []byte) []byte {
hostBytes := binary.NativeEndian.AppendUint16(nil, 0x0102)
isBigEndian := hostBytes[0] == 0x01
return func(big, little []byte) []byte {
if isBigEndian {
return big
}
return little
}
}()
// Like createChooseOrder but takes ints instead of byte slices.
chooseOrderN := func(big, little, size int) []byte {
return chooseOrder(numToBE(big, size), numToBE(little, size))
}
for _, test := range []struct {
tname string
op1 operation // Immediate operation to set source register.
op2 operation // Byteorder operation to test.
op3 operation // Comparison operation to validate result.
}{
// Size 2 tests (Lengths 2, 3, 4, 6, 8, 16)
{
tname: "ntoh size 2 len 2",
op1: mustCreateImmediate(t, linux.NFT_REG32_01, newBytesData(numToBE(0x0102, 2))),
op2: mustCreateByteorder(t, linux.NFT_REG32_01, linux.NFT_REG32_01, linux.NFT_BYTEORDER_NTOH, 2, 2),
op3: mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ, chooseOrderN(0x0102, 0x0201, 2)),
},
{
tname: "hton size 2 len 2",
op1: mustCreateImmediate(t, linux.NFT_REG32_01, newBytesData(numToBE(0x0102, 2))),
op2: mustCreateByteorder(t, linux.NFT_REG32_01, linux.NFT_REG_1, linux.NFT_BYTEORDER_HTON, 2, 2),
op3: mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, chooseOrderN(0x0102, 0x0201, 2)),
},
{
tname: "ntoh size 2 len 3",
op1: mustCreateImmediate(t, linux.NFT_REG32_01, newBytesData(numToBE(0x010203, 3))),
op2: mustCreateByteorder(t, linux.NFT_REG32_01, linux.NFT_REG_1, linux.NFT_BYTEORDER_NTOH, 3, 2),
op3: mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, chooseOrderN(0x010203, 0x020100, 3)),
},
{
tname: "hton size 2 len 3",
op1: mustCreateImmediate(t, linux.NFT_REG_1, newBytesData(numToBE(0x010203, 3))),
op2: mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG32_01, linux.NFT_BYTEORDER_HTON, 3, 2),
op3: mustCreateComparison(t, linux.NFT_REG32_01, linux.NFT_CMP_EQ, chooseOrderN(0x010203, 0x020100, 3)),
},
{
tname: "ntoh size 2 len 4",
op1: mustCreateImmediate(t, linux.NFT_REG32_10, newBytesData(numToBE(0x01020304, 4))),
op2: mustCreateByteorder(t, linux.NFT_REG32_10, linux.NFT_REG32_05, linux.NFT_BYTEORDER_NTOH, 4, 2),
op3: mustCreateComparison(t, linux.NFT_REG32_05, linux.NFT_CMP_EQ, chooseOrderN(0x01020304, 0x02010403, 4)),
},
{
tname: "hton size 2 len 4",
op1: mustCreateImmediate(t, linux.NFT_REG_4, newBytesData(numToBE(0x01020304, 4))),
op2: mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG32_09, linux.NFT_BYTEORDER_HTON, 4, 2),
op3: mustCreateComparison(t, linux.NFT_REG32_09, linux.NFT_CMP_EQ, chooseOrderN(0x01020304, 0x02010403, 4)),
},
{
tname: "ntoh size 2 len 6",
op1: mustCreateImmediate(t, linux.NFT_REG_1, newBytesData(numToBE(0x010203040506, 6))),
op2: mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_1, linux.NFT_BYTEORDER_NTOH, 6, 2),
op3: mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, chooseOrderN(0x010203040506, 0x020104030605, 6)),
},
{
tname: "hton size 2 len 6",
op1: mustCreateImmediate(t, linux.NFT_REG_1, newBytesData(numToBE(0x010203040506, 6))),
op2: mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_1, linux.NFT_BYTEORDER_HTON, 6, 2),
op3: mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, chooseOrderN(0x010203040506, 0x020104030605, 6)),
},
{
tname: "ntoh size 2 len 8",
op1: mustCreateImmediate(t, linux.NFT_REG_1, newBytesData(numToBE(0x0102030405060708, 8))),
op2: mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_4, linux.NFT_BYTEORDER_NTOH, 8, 2),
op3: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrderN(0x0102030405060708, 0x0201040306050807, 8)),
},
{
tname: "hton size 2 len 8",
op1: mustCreateImmediate(t, linux.NFT_REG_1, newBytesData(numToBE(0x0102030405060708, 8))),
op2: mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_4, linux.NFT_BYTEORDER_HTON, 8, 2),
op3: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrderN(0x0102030405060708, 0x0201040306050807, 8)),
},
{
tname: "ntoh size 2 len 16",
op1: mustCreateImmediate(t, linux.NFT_REG_3, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})),
op2: mustCreateByteorder(t, linux.NFT_REG_3, linux.NFT_REG_2, linux.NFT_BYTEORDER_NTOH, 16, 2),
op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
[]byte{0x02, 0x01, 0x04, 0x03, 0x06, 0x05, 0x08, 0x07, 0x0a, 0x09, 0x0c, 0x0b, 0x0e, 0x0d, 0x10, 0x0f})),
},
{
tname: "hton size 2 len 16",
op1: mustCreateImmediate(t, linux.NFT_REG_3, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})),
op2: mustCreateByteorder(t, linux.NFT_REG_3, linux.NFT_REG_2, linux.NFT_BYTEORDER_HTON, 16, 2),
op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
[]byte{0x02, 0x01, 0x04, 0x03, 0x06, 0x05, 0x08, 0x07, 0x0a, 0x09, 0x0c, 0x0b, 0x0e, 0x0d, 0x10, 0x0f})),
},
// Size 4 tests (Lengths 4, 6, 8, 16)
{
tname: "ntoh size 4 len 4",
op1: mustCreateImmediate(t, linux.NFT_REG32_05, newBytesData(numToBE(0x01020304, 4))),
op2: mustCreateByteorder(t, linux.NFT_REG32_05, linux.NFT_REG_2, linux.NFT_BYTEORDER_NTOH, 4, 4),
op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrderN(0x01020304, 0x04030201, 4)),
},
{
tname: "hton size 4 len 4",
op1: mustCreateImmediate(t, linux.NFT_REG_4, newBytesData(numToBE(0x01020304, 4))),
op2: mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG32_09, linux.NFT_BYTEORDER_HTON, 4, 4),
op3: mustCreateComparison(t, linux.NFT_REG32_09, linux.NFT_CMP_EQ, chooseOrderN(0x01020304, 0x04030201, 4)),
},
{
tname: "ntoh size 4 len 6",
op1: mustCreateImmediate(t, linux.NFT_REG_4, newBytesData(numToBE(0x010203040506, 6))),
op2: mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG_2, linux.NFT_BYTEORDER_NTOH, 6, 4),
op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrderN(0x010203040506, 0x040302010000, 6)),
},
{
tname: "hton size 4 len 6",
op1: mustCreateImmediate(t, linux.NFT_REG_4, newBytesData(numToBE(0x010203040506, 6))),
op2: mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG_2, linux.NFT_BYTEORDER_HTON, 6, 4),
op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrderN(0x010203040506, 0x040302010000, 6)),
},
{
tname: "ntoh size 4 len 8",
op1: mustCreateImmediate(t, linux.NFT_REG_1, newBytesData(numToBE(0x0102030405060708, 8))),
op2: mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_4, linux.NFT_BYTEORDER_NTOH, 8, 4),
op3: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrderN(0x0102030405060708, 0x0403020108070605, 8)),
},
{
tname: "hton size 4 len 8",
op1: mustCreateImmediate(t, linux.NFT_REG_1, newBytesData(numToBE(0x0102030405060708, 8))),
op2: mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_4, linux.NFT_BYTEORDER_HTON, 8, 4),
op3: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrderN(0x0102030405060708, 0x0403020108070605, 8)),
},
{
tname: "ntoh size 4 len 16",
op1: mustCreateImmediate(t, linux.NFT_REG_3, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})),
op2: mustCreateByteorder(t, linux.NFT_REG_3, linux.NFT_REG_2, linux.NFT_BYTEORDER_NTOH, 16, 4),
op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
[]byte{0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05, 0x0c, 0x0b, 0x0a, 0x09, 0x10, 0x0f, 0x0e, 0x0d})),
},
{
tname: "hton size 4 len 16",
op1: mustCreateImmediate(t, linux.NFT_REG_3, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})),
op2: mustCreateByteorder(t, linux.NFT_REG_3, linux.NFT_REG_2, linux.NFT_BYTEORDER_HTON, 16, 4),
op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
[]byte{0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05, 0x0c, 0x0b, 0x0a, 0x09, 0x10, 0x0f, 0x0e, 0x0d})),
},
// Size 8 tests (Lengths 8, 12, 16)
{
tname: "ntoh size 8 len 8",
op1: mustCreateImmediate(t, linux.NFT_REG_1, newBytesData(numToBE(0x0102030405060708, 8))),
op2: mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_4, linux.NFT_BYTEORDER_NTOH, 8, 8),
op3: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrderN(0x0102030405060708, 0x0807060504030201, 8)),
},
{
tname: "hton size 8 len 8",
op1: mustCreateImmediate(t, linux.NFT_REG_1, newBytesData(numToBE(0x0102030405060708, 8))),
op2: mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_4, linux.NFT_BYTEORDER_HTON, 8, 8),
op3: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrderN(0x0102030405060708, 0x0807060504030201, 8)),
},
{
tname: "ntoh size 8 len 12",
op1: mustCreateImmediate(t, linux.NFT_REG_3, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c})),
op2: mustCreateByteorder(t, linux.NFT_REG_3, linux.NFT_REG_2, linux.NFT_BYTEORDER_NTOH, 12, 8),
op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c},
[]byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00})),
},
{
tname: "hton size 8 len 12",
op1: mustCreateImmediate(t, linux.NFT_REG_3, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c})),
op2: mustCreateByteorder(t, linux.NFT_REG_3, linux.NFT_REG_2, linux.NFT_BYTEORDER_HTON, 12, 8),
op3: mustCreateComparison(t, linux.NFT_REG_2, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c},
[]byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00})),
},
{
tname: "ntoh size 8 len 16",
op1: mustCreateImmediate(t, linux.NFT_REG_4, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})),
op2: mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG_4, linux.NFT_BYTEORDER_NTOH, 16, 8),
op3: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
[]byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09})),
},
{
tname: "hton size 8 len 16",
op1: mustCreateImmediate(t, linux.NFT_REG_4, newBytesData([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10})),
op2: mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG_4, linux.NFT_BYTEORDER_HTON, 16, 8),
op3: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, chooseOrder([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10},
[]byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09})),
},
} {
t.Run(test.tname, func(t *testing.T) {
// Sets up an NFTables object with a single table, chain, and rule.
nf := newNFTablesStd()
tab, err := nf.AddTable(arbitraryFamily, "test", "test table", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
bc, err := tab.AddChain("base_chain", nil, "test chain", false)
if err != nil {
t.Fatalf("unexpected error for AddChain: %v", err)
}
bc.SetBaseChainInfo(arbitraryInfoPolicyAccept)
rule := &Rule{}
// Adds testing operations.
if test.op1 != nil {
rule.addOperation(test.op1)
}
if test.op2 != nil {
rule.addOperation(test.op2)
}
if test.op3 != nil {
rule.addOperation(test.op3)
}
// Adds drop operation. Will be final verdict if comparison is true.
rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})))
// Registers the rule to the base chain.
if err := bc.RegisterRule(rule, -1); err != nil {
t.Fatalf("unexpected error for RegisterRule: %v", err)
}
// Runs evaluation and checks verdict.
pkt := makeArbitraryPacket(arbitraryReservedHeaderBytes)
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, pkt)
if err != nil {
t.Fatalf("unexpected error for EvaluateHook: %v", err)
}
if v.Code != VC(linux.NF_DROP) {
t.Fatalf("expected verdict Drop for true comparison, got %v", v)
}
})
}
}
// TestLoopCheckOnRegisterAndUnregister tests the loop checking and accompanying
// logic on registering and unregistering rules.
func TestLoopCheckOnRegisterAndUnregister(t *testing.T) {
@@ -3242,3 +3485,12 @@ func mustCreateRoute(t *testing.T, key routeKey, dreg uint8) *route {
}
return rt
}
// mustCreateByteorder wraps the newByteorder function for brevity.
func mustCreateByteorder(t *testing.T, sreg, dreg uint8, bop byteorderOp, blen, size uint8) *byteorder {
order, err := newByteorder(sreg, dreg, bop, blen, size)
if err != nil {
t.Fatalf("failed to create byteorder: %v", err)
}
return order
}
+102 -19
View File
@@ -155,6 +155,8 @@ func InterpretOperation(line string, lnIdx int) (operation, error) {
return InterpretCounter(line, lnIdx)
case "rt":
return InterpretRoute(line, lnIdx)
case "byteorder":
return InterpretByteorder(line, lnIdx)
default:
return nil, &SyntaxError{lnIdx, 1, fmt.Sprintf("unrecognized operation type: %s", tokens[1])}
}
@@ -309,7 +311,7 @@ func InterpretPayloadLoad(line string, lnIdx int) (operation, error) {
tkIdx++
// Third token should be the length (in bytes) of the payload followed by 'b'.
len, err := parsePayloadLength(tokens[tkIdx], lnIdx, tkIdx)
blen, err := parseUint8PlusChar(tokens[tkIdx], 'b', lnIdx, tkIdx)
if err != nil {
return nil, err
}
@@ -367,7 +369,7 @@ func InterpretPayloadLoad(line string, lnIdx int) (operation, error) {
tkIdx++
// Create the operation with the specified arguments.
pdload, err := newPayloadLoad(base, offset, len, reg)
pdload, err := newPayloadLoad(base, offset, blen, reg)
if err != nil {
return nil, &LogicError{lnIdx, tkIdx, err}
}
@@ -424,7 +426,7 @@ func InterpretPayloadSet(line string, lnIdx int) (operation, error) {
tkIdx++
// Sixth token should be the length (in bytes) of the payload followed by 'b'.
len, err := parsePayloadLength(tokens[tkIdx], lnIdx, tkIdx)
blen, err := parseUint8PlusChar(tokens[tkIdx], 'b', lnIdx, tkIdx)
if err != nil {
return nil, err
}
@@ -502,7 +504,7 @@ func InterpretPayloadSet(line string, lnIdx int) (operation, error) {
tkIdx++
// Create the operation with the specified arguments.
pdset, err := newPayloadSet(base, offset, len, reg, csumType, csumOff, csumFlags)
pdset, err := newPayloadSet(base, offset, blen, reg, csumType, csumOff, csumFlags)
if err != nil {
return nil, &LogicError{lnIdx, tkIdx, err}
}
@@ -733,6 +735,89 @@ func InterpretRoute(line string, lnIdx int) (operation, error) {
return rt, nil
}
// InterpretByteorder creates a new Byteorder operation from the given string.
func InterpretByteorder(line string, lnIdx int) (operation, error) {
tokens := strings.Fields(line)
// Requires exactly 10 tokens:
// "[", "byteorder", "reg", dreg index, "=", byteorder op+"(reg", sreg index+",", size+",", blen+")", "]".
if len(tokens) != 10 {
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for route operation, should be exactly 10, got %d", len(tokens))}
}
if err := checkOperationBrackets(tokens, lnIdx); err != nil {
return nil, err
}
tkIdx := 1
// First token should be "byteorder".
if err := consumeToken("byteorder", 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 "ntoh(reg".
var bop byteorderOp
switch tokens[tkIdx] {
case "ntoh(reg":
bop = linux.NFT_BYTEORDER_NTOH
case "hton(reg":
bop = linux.NFT_BYTEORDER_HTON
default:
return nil, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("expected 'ntoh' or 'hton' keyword followed by '(reg' at token %d, got '%s'", tkIdx, tokens[tkIdx])}
}
tkIdx++
// Sixth token should be the source register index followed by ','.
sreg, err := parseUint8PlusChar(tokens[tkIdx], ',', lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Seventh token should be the size in bytes followed by ','.
size, err := parseUint8PlusChar(tokens[tkIdx], ',', lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Eighth token should be the length in bytes followed by ')'.
blen, err := parseUint8PlusChar(tokens[tkIdx], ')', lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Create the operation with the specified arguments.
order, err := newByteorder(dreg, sreg, bop, blen, size)
if err != nil {
return nil, &LogicError{lnIdx, tkIdx, err}
}
return order, nil
}
//
// Interpreter Helper Functions.
//
@@ -870,8 +955,9 @@ func parseHexData(tokens []string, lnIdx int, tkIdx int) (int, []byte, error) {
slices.Reverse(bytes4)
bytes = append(bytes, bytes4...)
}
if len(bytes) > 16 {
return 0, nil, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("cannot have more than 16 bytes of hexadecimal data, got %d", len(bytes))}
if len(bytes) > linux.NFT_REG_SIZE {
return 0, nil, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("cannot have more than %d bytes of hexadecimal data, got %d",
linux.NFT_REG_SIZE, len(bytes))}
}
return tkIdx, bytes, nil
}
@@ -896,22 +982,19 @@ func parseCmpOp(copString string, lnIdx int, tkIdx int) (int, error) {
}
}
// parsePayloadLength parses the payload length from the given string
// expecting a unsigned 8-bit integer followed by 'b'.
func parsePayloadLength(lenString string, lnIdx int, tkIdx int) (uint8, error) {
lastChar := lenString[len(lenString)-1]
if lastChar != 'b' {
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("expected 'b' at the end of payload length, got '%c'", lastChar)}
// parseUint8PlusChar parses the a uint8 followed by the given character from
// the given string.
func parseUint8PlusChar(numString string, char byte, lnIdx int, tkIdx int) (uint8, error) {
lastChar := numString[len(numString)-1]
if lastChar != char {
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("expected '%c' at the end of the uint8, got '%c'", char, lastChar)}
}
numStr := lenString[:len(lenString)-1]
len, err := strconv.ParseUint(numStr, 10, 8)
numStr := numString[:len(numString)-1]
num, err := strconv.ParseUint(numStr, 10, 8)
if err != nil {
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("could not parse uint8 payload length: '%s'", numStr)}
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("could not parse uint8: '%s'", numStr)}
}
if len > 16 {
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("payload length must be <= 16 bytes, got %d", len)}
}
return uint8(len), nil
return uint8(num), nil
}
// parsePayloadBase parses the payload base header from the given string.
+73
View File
@@ -884,6 +884,79 @@ func checkRouteOp(tname string, expected operation, actual operation) error {
return nil
}
// TestInterpretByteorderOps tests interpretation of byteorder operations.
// Note: Most byteorder operations have been revealed in the nft binary
// debug output through bitshifts (which oddly do not use the native bitwise
// operation lshift and rshift operators). Thus, many of following commands are
// simply variations of lshift and rshift commands.
func TestInterpretByteorderOps(t *testing.T) {
for _, test := range []interpretOperationTestAction{
{ // cmd: add rule ip tab ch tcp dport rshift 4 == 0x5678
tname: "ntoh size 2 len 2",
opStr: "[ byteorder reg 1 = ntoh(reg 1, 2, 2) ]",
expected: mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_1, linux.NFT_BYTEORDER_NTOH, 2, 2),
},
{ // cmd: add rule ip tab ch tcp dport rshift 7 == 0x345
tname: "ntoh size 2 len 2 again",
opStr: "[ byteorder reg 2 = ntoh(reg 11, 2, 2) ]",
expected: mustCreateByteorder(t, linux.NFT_REG_2, linux.NFT_REG32_03, linux.NFT_BYTEORDER_NTOH, 2, 2),
},
{ // cmd: add rule ip filter input @th,24,24 rshift 1 0xabcdef
tname: "ntoh size 2 len 3 again",
opStr: "[ byteorder reg 15 = ntoh(reg 15, 2, 3) ]",
expected: mustCreateByteorder(t, linux.NFT_REG32_07, linux.NFT_REG32_07, linux.NFT_BYTEORDER_NTOH, 3, 2),
},
{ // cmd: add rule ip filter input ether saddr lshift 1 == 01223456
tname: "ntoh size 2 len 6",
opStr: "[ byteorder reg 4 = ntoh(reg 3, 2, 6) ]",
expected: mustCreateByteorder(t, linux.NFT_REG_4, linux.NFT_REG_3, linux.NFT_BYTEORDER_NTOH, 6, 2),
},
{ // cmd: add rule ip tab ch ip daddr rshift 20 99900
tname: "ntoh size 4 len 4",
opStr: "[ byteorder reg 9 = ntoh(reg 1, 4, 4) ]",
expected: mustCreateByteorder(t, linux.NFT_REG32_01, linux.NFT_REG_1, linux.NFT_BYTEORDER_NTOH, 4, 4),
},
{ // cmd: add rule ip6 tab ch ip6 daddr rshift 90 603
tname: "ntoh size 8 len 16",
opStr: "[ byteorder reg 1 = ntoh(reg 1, 8, 16) ]",
expected: mustCreateByteorder(t, linux.NFT_REG_1, linux.NFT_REG_1, linux.NFT_BYTEORDER_NTOH, 16, 8),
},
{ // cmd: add rule ip filter input meta length gt 1000 accept
tname: "hton size 4 len 4",
opStr: "[ byteorder reg 8 = hton(reg 1, 4, 4) ]",
expected: mustCreateByteorder(t, linux.NFT_REG32_00, linux.NFT_REG_1, linux.NFT_BYTEORDER_HTON, 4, 4),
},
} {
t.Run(test.tname, func(t *testing.T) { checkOp(t, test, checkByteorderOp) })
}
}
// checkByteorderOp checks that the given operation is a byteorder operation
// and that it matches the expected byteorder operation.
func checkByteorderOp(tname string, expected operation, actual operation) error {
expectedOrder := expected.(*byteorder)
order, ok := actual.(*byteorder)
if !ok {
return fmt.Errorf("expected operation type to be Byteorder for %s, got %T", tname, actual)
}
if order.sreg != expectedOrder.sreg {
return fmt.Errorf("expected source register to be %d for %s, got %d", expectedOrder.sreg, tname, order.sreg)
}
if order.dreg != expectedOrder.dreg {
return fmt.Errorf("expected destination register to be %d for %s, got %d", expectedOrder.dreg, tname, order.dreg)
}
if order.bop != expectedOrder.bop {
return fmt.Errorf("expected byteorder operator to be %v for %s, got %v", expectedOrder.bop, tname, order.bop)
}
if order.blen != expectedOrder.blen {
return fmt.Errorf("expected byteorder length to be %d for %s, got %d", expectedOrder.blen, tname, order.blen)
}
if order.size != expectedOrder.size {
return fmt.Errorf("expected byteorder size to be %d for %s, got %d", expectedOrder.size, tname, order.size)
}
return nil
}
// TestInterpretRule tests the interpretation of basic and general rules as a
// list of operations.
func TestInterpretRule(t *testing.T) {