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

Tests payload set evaluation for all basic fields of IP, IPv6, & TCP headers.
Similar to PayloadLoad, these headers were prioritized. Setting other headers
should still work but wasn't explicitly tested. Tests for other packets should
be added later.

PiperOrigin-RevId: 670636213
This commit is contained in:
Jayden Nyamiaka
2024-09-03 11:28:54 -07:00
committed by gVisor bot
parent 7ef3dda2a1
commit bd89a24410
5 changed files with 1223 additions and 162 deletions
+1
View File
@@ -13,6 +13,7 @@ go_library(
],
deps = [
"//pkg/abi/linux",
"//pkg/tcpip/checksum",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
],
+189 -22
View File
@@ -41,10 +41,12 @@
package nftables
import (
"encoding/binary"
"fmt"
"slices"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
@@ -567,6 +569,7 @@ var (
_ operation = (*immediate)(nil)
_ operation = (*comparison)(nil)
_ operation = (*payloadLoad)(nil)
_ operation = (*payloadSet)(nil)
)
// immediate is an operation that sets the data in a register.
@@ -740,6 +743,32 @@ func validatePayloadBase(base payloadBase) error {
}
}
// getPayloadBuffer gets the data from the packet payload starting from the
// the beginning of the specified base header.
// Returns nil if the payload is not present or invalid.
func getPayloadBuffer(pkt *stack.PacketBuffer, base payloadBase) []byte {
switch base {
case linux.NFT_PAYLOAD_LL_HEADER:
// Note: Assumes Mac Header is present and valid for necessary use cases.
// Also, doesn't check VLAN tag because VLAN isn't supported by gVisor.
return pkt.LinkHeader().Slice()
case linux.NFT_PAYLOAD_NETWORK_HEADER:
// No checks done in linux kernel.
return pkt.NetworkHeader().Slice()
case linux.NFT_PAYLOAD_TRANSPORT_HEADER:
// Note: Assumes L4 protocol is present and valid for necessary use cases.
// Errors if the packet is fragmented for IPv4 only.
if net := pkt.NetworkHeader().Slice(); len(net) > 0 && pkt.NetworkProtocolNumber == header.IPv4ProtocolNumber {
if h := header.IPv4(net); h.More() || h.FragmentOffset() != 0 {
break // packet is fragmented
}
}
return pkt.TransportHeader().Slice()
}
return nil
}
// newPayloadLoad creates a new PayloadLoad operation.
func newPayloadLoad(base payloadBase, offset, blen, dreg uint8) (*payloadLoad, error) {
if dreg == linux.NFT_REG_VERDICT {
@@ -757,27 +786,8 @@ func newPayloadLoad(base payloadBase, offset, blen, dreg uint8) (*payloadLoad, e
// evaluate for PayloadLoad loads data from the packet payload into the
// destination register.
func (op payloadLoad) evaluate(regs *registerSet, pkt *stack.PacketBuffer) {
// Gets the data from the packet payload.
var payload []byte = nil
switch op.base {
case linux.NFT_PAYLOAD_LL_HEADER:
// Note: Assumes Mac Header is present and valid for necessary use cases.
// Also, doesn't check VLAN tag because VLAN isn't supported by gVisor.
payload = pkt.LinkHeader().Slice()
case linux.NFT_PAYLOAD_NETWORK_HEADER:
// No checks done in linux kernel.
payload = pkt.NetworkHeader().Slice()
case linux.NFT_PAYLOAD_TRANSPORT_HEADER:
// Note: Assumes L4 protocol is present and valid for necessary use cases.
// Errors if the packet is fragmented for IPv4 only.
if net := pkt.NetworkHeader().Slice(); len(net) > 0 && pkt.NetworkProtocolNumber == header.IPv4ProtocolNumber {
if h := header.IPv4(net); h.More() || h.FragmentOffset() != 0 {
break // packet is fragmented
}
}
payload = pkt.TransportHeader().Slice()
}
// Gets the packet payload.
payload := getPayloadBuffer(pkt, op.base)
// Breaks if could not retrieve packet data.
if payload == nil || len(payload) < int(op.offset+op.blen) {
@@ -790,6 +800,163 @@ func (op payloadLoad) evaluate(regs *registerSet, pkt *stack.PacketBuffer) {
data.storeData(regs, op.dreg)
}
// payloadSet is an operation that sets data in the packet payload to the value
// in a register.
// Note: payload operations are not supported for the verdict register.
type payloadSet struct {
base payloadBase // Payload base to access data from.
offset uint8 // Number of bytes to skip after the base for data.
blen uint8 // Number of bytes to load.
sreg uint8 // Number of the source register.
csumType uint8 // Type of checksum to use.
csumOffset uint8 // Number of bytes to skip after the base for checksum.
csumFlags uint8 // Flags for checksum.
// Note: the only flag defined for csumFlags is NFT_PAYLOAD_L4CSUM_PSEUDOHDR.
// This flag is used to update L4 checksums whenever there has been a change
// to a field that is part of the pseudo-header for the L4 checksum, not when
// data within the L4 header is changed (instead setting csumType to
// NFT_PAYLOAD_CSUM_INET suffices for that case).
// For example, if any part of the L4 header is changed, csumType is set to
// NFT_PAYLOAD_CSUM_INET and no flag is set for csumFlags since we only need
// to update the checksum of the header specified by the payload base.
// On the other hand, if data in the L3 header is changed that is part of
// the pseudo-header for the L4 checksum (like saddr/daddr), csumType is set
// to NFT_PAYLOAD_CSUM_INET and csumFlags to NFT_PAYLOAD_L4CSUM_PSEUDOHDR
// because in addition to updating the checksum for the header specified by
// the payload base, we need to separately locate and update the L4 checksum.
}
// validateChecksumType ensures the checksum type is valid.
func validateChecksumType(csumType uint8) error {
switch csumType {
case linux.NFT_PAYLOAD_CSUM_NONE:
return nil
case linux.NFT_PAYLOAD_CSUM_INET:
return nil
case linux.NFT_PAYLOAD_CSUM_SCTP:
return fmt.Errorf("SCTP checksum not supported")
default:
return fmt.Errorf("invalid checksum type: %d", csumType)
}
}
// newPayloadSet creates a new PayloadSet operation.
func newPayloadSet(base payloadBase, offset, blen, sreg, csumType, csumOffset, csumFlags uint8) (*payloadSet, error) {
if sreg == linux.NFT_REG_VERDICT {
return nil, fmt.Errorf("payload set operation cannot use verdict register as destination")
}
if blen > 16 || (blen > 4 && is4ByteRegister(sreg)) {
return nil, fmt.Errorf("payload length %d is too long for destination register %d", blen, sreg)
}
if err := validatePayloadBase(base); err != nil {
return nil, err
}
if err := validateChecksumType(csumType); err != nil {
return nil, err
}
if csumFlags&^linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR != 0 {
return nil, fmt.Errorf("invalid checksum flags: %d", csumFlags)
}
return &payloadSet{base: base, offset: offset, blen: blen, sreg: sreg,
csumType: csumType, csumOffset: csumOffset, csumFlags: csumFlags}, nil
}
// evaluate for PayloadSet sets data in the packet payload to the value in the
// source register.
func (op payloadSet) evaluate(regs *registerSet, pkt *stack.PacketBuffer) {
// Gets the packet payload.
payload := getPayloadBuffer(pkt, op.base)
// Breaks if could not retrieve packet data.
if payload == nil || len(payload) < int(op.offset+op.blen) {
regs.verdict = Verdict{Code: VC(linux.NFT_BREAK)}
return
}
// Gets the register data assumed to be in Big Endian.
regData := getRegisterBuffer(regs, op.sreg)[:op.blen]
// Returns early if the source data is the same as the existing payload data.
if slices.Equal(regData, payload[op.offset:op.offset+op.blen]) {
return
}
// Sets payload data to source register data after checksum updates.
defer copy(payload[op.offset:op.offset+op.blen], regData)
// Specifies no checksum updates.
if op.csumType != linux.NFT_PAYLOAD_CSUM_INET && op.csumFlags == 0 {
return
}
// Calculates partial checksums of old and new data.
// Note: Checksums are done on 2-byte boundaries, so we must append the
// surrounding bytes in our checksum calculations if the beginning or end
// of the checksum is not aligned to a 2-byte boundary.
begin := op.offset
end := op.offset + op.blen
if begin%2 != 0 {
begin--
}
if end%2 != 0 && end != uint8(len(payload)) {
end++
}
tempOld := make([]byte, end-begin)
copy(tempOld, payload[begin:end])
tempNew := make([]byte, end-begin)
if begin != op.offset {
tempNew[0] = payload[begin]
}
copy(tempNew[op.offset-begin:], regData)
if end != op.offset+op.blen {
tempNew[len(tempNew)-1] = payload[end-1]
}
oldDataCsum := checksum.Checksum(tempOld, 0)
newDataCsum := checksum.Checksum(tempNew, 0)
// Updates the checksum of the header specified by the payload base.
if op.csumType == linux.NFT_PAYLOAD_CSUM_INET {
// Reads the old checksum from the packet payload.
oldTotalCsum := binary.BigEndian.Uint16(payload[op.csumOffset:])
// New Total = Old Total - Old Data + New Data
// Logic is very similar to checksum.checksumUpdate2ByteAlignedUint16
// in gvisor/pkg/tcpip/header/checksum.go
newTotalCsum := checksum.Combine(^oldTotalCsum, checksum.Combine(newDataCsum, ^oldDataCsum))
checksum.Put(payload[op.csumOffset:], ^newTotalCsum)
}
// Separately updates the L4 checksum if the pseudo-header flag is set.
// Note: it is possible to update the L4 checksum without updating the
// checksum of the header specified by the payload base (ie type is NONE,
// flag is pseudo-header). Specifically, IPv6 headers don't have their
// own checksum calculations, but the L4 checksum is still updated for any
// TCP/UDP headers following the IPv6 header.
if op.csumFlags&linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR != 0 {
if tBytes := pkt.TransportHeader().Slice(); pkt.TransportProtocolNumber != 0 && len(tBytes) > 0 {
var transport header.Transport
switch pkt.TransportProtocolNumber {
case header.TCPProtocolNumber:
transport = header.TCP(tBytes)
case header.UDPProtocolNumber:
transport = header.UDP(tBytes)
case header.ICMPv4ProtocolNumber:
transport = header.ICMPv4(tBytes)
case header.ICMPv6ProtocolNumber:
transport = header.ICMPv6(tBytes)
case header.IGMPProtocolNumber:
transport = header.IGMP(tBytes)
}
if transport != nil { // only updates if the transport header is present.
// New Total = Old Total - Old Data + New Data (same as above)
transport.SetChecksum(^checksum.Combine(^transport.Checksum(), checksum.Combine(newDataCsum, ^oldDataCsum)))
}
}
}
}
//
// Register and Register-Related Implementations.
// Note: Registers are represented by type uint8 for the register number.
@@ -942,7 +1109,7 @@ func (rd bytesData) storeData(regs *registerSet, reg uint8) {
}
// registerSet represents the set of registers supported by the kernel.
// Use RegisterData.StoreData to set data in the registers.
// Use RegisterData.storeData to set data in the registers.
// Note: Corresponds to nft_regs from include/net/netfilter/nf_tables.h.
type registerSet struct {
verdict Verdict // 16-byte verdict register
File diff suppressed because it is too large Load Diff
+150 -2
View File
@@ -140,7 +140,13 @@ func InterpretOperation(line string, lnIdx int) (operation, error) {
case "cmp":
return InterpretComparison(line, lnIdx)
case "payload":
return InterpretPayloadLoad(line, lnIdx)
switch tokens[2] {
case "load":
return InterpretPayloadLoad(line, lnIdx)
case "write":
return InterpretPayloadSet(line, lnIdx)
}
return nil, &SyntaxError{lnIdx, 2, fmt.Sprintf("unrecognized operation type: payload %s", tokens[2])}
default:
return nil, &SyntaxError{lnIdx, 1, fmt.Sprintf("unrecognized operation type: %s", tokens[1])}
}
@@ -361,6 +367,141 @@ func InterpretPayloadLoad(line string, lnIdx int) (operation, error) {
return pdload, nil
}
// InterpretPayloadSet creates a new PayloadSet operation from the given string.
func InterpretPayloadSet(line string, lnIdx int) (operation, error) {
tokens := strings.Fields(line)
// Requires at least 19 tokens:
// "[", "payload", "write", "reg", register index, "=>", len+"b", "@", payload base, "header", "+", offset,
// "csum_type", checksum type, "csum_off", checksum offset, "csum_flags", checksum flags as hexadecimal, "]".
if len(tokens) != 19 {
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for payload set operation, should be exactly 19, got %d", len(tokens))}
}
if err := checkOperationBrackets(tokens, lnIdx); err != nil {
return nil, err
}
tkIdx := 1
// First token should be "payload".
if err := consumeToken("payload", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Second token should be "write".
if err := consumeToken("write", tokens, lnIdx, tkIdx); 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 "=>".
if err := consumeToken("=>", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Sixth token should be the length (in bytes) of the payload followed by 'b'.
len, err := parsePayloadLength(tokens[tkIdx], lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Seventh token should be "@".
if err := consumeToken("@", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Eighth token should be the payload base header.
base, err := parsePayloadBase(tokens[tkIdx], lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Ninth token should be "header".
if err := consumeToken("header", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Tenth token should be "+".
if err := consumeToken("+", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Eleventh token should be the uint8 representing the offset.
offset, err := parseUint8(tokens[tkIdx], "offset", lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Twelfth token should be "csum_type".
if err := consumeToken("csum_type", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Thirteenth token should be the uint8 representing the checksum type.
csumType, err := parseUint8(tokens[tkIdx], "checksum type", lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Fourteenth token should be "csum_off".
if err := consumeToken("csum_off", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Fifteenth token should be the uint8 representing the checksum offset.
csumOff, err := parseUint8(tokens[tkIdx], "checksum offset", lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Sixteenth token should be "csum_flags".
if err := consumeToken("csum_flags", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Seventeenth token should be the uint8 representing checksum flags (in hex).
csumFlags, err := parseUint8(tokens[tkIdx], "checksum flags", lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Create the operation with the specified arguments.
pdset, err := newPayloadSet(base, offset, len, reg, csumType, csumOff, csumFlags)
if err != nil {
return nil, &LogicError{lnIdx, tkIdx, err}
}
return pdset, nil
}
//
// Interpreter Helper Functions.
//
@@ -378,8 +519,15 @@ func checkOperationBrackets(tokens []string, lnIdx int) error {
}
// parseUint8 parses the uint8 which should be supposed from the given string.
// Input starting with "0x" are parsed as base 16, otherwise assumes base 10.
func parseUint8(regString string, supposed string, lnIdx int, tkIdx int) (uint8, error) {
v64, err := strconv.ParseUint(regString, 10, 8)
var v64 uint64
var err error
if len(regString) > 2 && regString[:2] == "0x" {
v64, err = strconv.ParseUint(regString[2:], 16, 8)
} else {
v64, err = strconv.ParseUint(regString, 10, 8)
}
if err != nil {
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("could not parse uint8 %s: '%s'", supposed, regString)}
}
+170 -1
View File
@@ -385,7 +385,7 @@ func TestInterpretPayloadLoadOps(t *testing.T) {
opStr: "[ payload load 2b @ transport header + 0 => reg 0 ]",
expected: nil,
},
// cmd: add rule ip6 ip tab ch tcp flags syn counter accept
// cmd: add rule ip tab ch tcp flags syn counter accept
{
tname: "load 1 byte into 4-byte register",
opStr: "[ payload load 1b @ transport header + 13 => reg 9 ]",
@@ -495,6 +495,175 @@ func checkPayloadLoadOp(tname string, expected operation, actual operation) erro
return nil
}
// TestInterpretPayloadSetOps tests interpretation of payload set operations.
// Most operations are direct output of nft binary commands. All stated commands
// should be preceded by nft --debug=netlink to generate matching operations.
func TestInterpretPayloadSetOps(t *testing.T) {
for _, test := range []interpretOperationTestAction{
// Simple checksum type tests.
{
tname: "set checksum type, none",
opStr: "[ payload write reg 1 => 6b @ link header + 0 csum_type 0 csum_off 0 csum_flags 0x0 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_LL_HEADER, 0, 6, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 0, 0x0),
},
{
tname: "set checksum type, inet",
opStr: "[ payload write reg 1 => 6b @ link header + 0 csum_type 1 csum_off 0 csum_flags 0x0 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_LL_HEADER, 0, 6, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_INET, 0, 0x0),
},
{
tname: "set checksum type, sctp", // not supported
opStr: "[ payload write reg 1 => 6b @ link header + 0 csum_type 2 csum_off 0 csum_flags 0x0 ]",
expected: nil,
},
{
tname: "set out of range checksum type",
opStr: "[ payload write reg 1 => 6b @ link header + 0 csum_type 3 csum_off 0 csum_flags 0x0 ]",
expected: nil,
},
// Simple checksum offset tests.
{
tname: "set valid offset",
opStr: "[ payload write reg 1 => 6b @ link header + 0 csum_type 0 csum_off 100 csum_flags 0x0 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_LL_HEADER, 0, 6, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 100, 0x0),
},
{
tname: "set negative checksum offset",
opStr: "[ payload write reg 1 => 6b @ link header + 100 csum_type 1 csum_off -1 csum_flags 0x0 ]",
expected: nil,
},
// Simple checksum flags tests.
{
tname: "set checksum flags, L4 with psuedoheader flag",
opStr: "[ payload write reg 1 => 6b @ link header + 0 csum_type 0 csum_off 0 csum_flags 0x1 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_LL_HEADER, 0, 6, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 0, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
},
{
tname: "set invalid checksum flags",
opStr: "[ payload write reg 1 => 6b @ link header + 0 csum_type 0 csum_off 0 csum_flags 0x2 ]",
expected: nil,
},
// Invalid register tests.
{
tname: "set from verdict register",
opStr: "[ payload write reg 0 => 4b @ link header + 0 csum_type 0 csum_off 0 csum_flags 0x0 ]",
expected: nil,
},
{
tname: "set >4 bytes from 4-byte register",
opStr: "[ payload write reg 9 => 6b @ link header + 0 csum_type 0 csum_off 0 csum_flags 0x0 ]",
expected: nil,
},
{
tname: "set >16 bytes from 16-byte register",
opStr: "[ payload write reg 2 => 20b @ link header + 0 csum_type 0 csum_off 0 csum_flags 0x0 ]",
expected: nil,
},
// Valid tests.
// Note: It doesn't seem like the nft binary ever outputs payload set ops
// that have an odd offset or length and checksumming on. This makes sense
// because the offset and length are specified in bytes, but the checksum is
// calculated in half-words (2-bytes), which means the checksum calculation
// is only valid if the offset and length are even. However, the linux
// kernel does not specifically enforce this, so on linux it's technically
// possible to declare payload set operations that undoubtedly result in
// invalid checksums. Since the nft binary is what generates our input, we
// do not test these edge cases either.
// cmd: add rule ip tab ch @nh,24,8 set 0xab
{
tname: "set 1 byte from 4-byte register with csum NONE and no flags",
opStr: "[ payload write reg 8 => 1b @ network header + 3 csum_type 0 csum_off 0 csum_flags 0x0 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, 3, 1, linux.NFT_REG32_00, linux.NFT_PAYLOAD_CSUM_NONE, 0, 0x0),
},
{
tname: "set 1 byte from 16-byte register with csum NONE and no flags",
opStr: "[ payload write reg 1 => 1b @ network header + 4 csum_type 0 csum_off 0 csum_flags 0x0 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, 4, 1, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 0, 0x0),
},
// cmd: add rule ip tab ch tcp sport set 80
{
tname: "set 2 bytes from 4-byte register with csum INET and no flags",
opStr: "[ payload write reg 9 => 2b @ transport header + 0 csum_type 1 csum_off 16 csum_flags 0x0 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, 0, 2, linux.NFT_REG32_01, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
},
{
tname: "set 2 bytes from 16-byte register with csum INET and no flags",
opStr: "[ payload write reg 2 => 2b @ transport header + 0 csum_type 1 csum_off 16 csum_flags 0x0 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_TRANSPORT_HEADER, 0, 2, linux.NFT_REG_2, linux.NFT_PAYLOAD_CSUM_INET, 16, 0x0),
},
// cmd: add rule ip tab ch @ll,24,24 set 0xabcdef
{
tname: "set 3 bytes from 4-byte register with csum NONE and no flags",
opStr: "[ payload write reg 10 => 3b @ link header + 3 csum_type 0 csum_off 0 csum_flags 0x0 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_LL_HEADER, 3, 3, linux.NFT_REG32_02, linux.NFT_PAYLOAD_CSUM_NONE, 0, 0x0),
},
{
tname: "set 3 bytes from 16-byte register with csum NONE and no flags",
opStr: "[ payload write reg 2 => 3b @ link header + 3 csum_type 0 csum_off 0 csum_flags 0x0 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_LL_HEADER, 3, 3, linux.NFT_REG_2, linux.NFT_PAYLOAD_CSUM_NONE, 0, 0x0),
},
// cmd: add rule ip tab ch ip daddr set 192.168.1.1
{
tname: "set 4 bytes from 4-byte register with csum INET and pseudoheader flag",
opStr: "[ payload write reg 11 => 4b @ network header + 16 csum_type 1 csum_off 10 csum_flags 0x1 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, 16, 4, linux.NFT_REG32_03, linux.NFT_PAYLOAD_CSUM_INET, 10, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
},
{
tname: "set 4 bytes from 4-byte register with csum INET and pseudoheader flag",
opStr: "[ payload write reg 3 => 4b @ network header + 16 csum_type 1 csum_off 10 csum_flags 0x1 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, 16, 4, linux.NFT_REG_3, linux.NFT_PAYLOAD_CSUM_INET, 10, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
},
// cmd: add rule ip tab ch ether saddr set 01:23:45:67:89:ab
{
tname: "set 6 bytes from 16-byte register with csum NONE and no flags",
opStr: "[ payload write reg 4 => 6b @ link header + 6 csum_type 0 csum_off 0 csum_flags 0x0 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_LL_HEADER, 6, 6, linux.NFT_REG_4, linux.NFT_PAYLOAD_CSUM_NONE, 0, 0x0),
},
// cmd: add rule ip6 tab ch ip6 saddr set 2001:db8::2
{
tname: "set 16 bytes from 16-byte register with csum NONE and psuedoheader flag",
opStr: "[ payload write reg 1 => 16b @ network header + 8 csum_type 0 csum_off 0 csum_flags 0x1 ]",
expected: mustCreatePayloadSet(t, linux.NFT_PAYLOAD_NETWORK_HEADER, 8, 16, linux.NFT_REG_1, linux.NFT_PAYLOAD_CSUM_NONE, 0, linux.NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
},
} {
t.Run(test.tname, func(t *testing.T) { checkOp(t, test, checkPayloadSetOp) })
}
}
// checkPayloadSetOp checks that the given operation is a payload set
// operation and that it matches the expected payload set operation.
func checkPayloadSetOp(tname string, expected operation, actual operation) error {
expectedPdSet := expected.(*payloadSet)
pdset, ok := actual.(*payloadSet)
if !ok {
return fmt.Errorf("expected operation type to be PayloadLoad for %s, got %T", tname, actual)
}
if pdset.base != expectedPdSet.base {
return fmt.Errorf("expected payload base to be %v for %s, got %v", expectedPdSet.base, tname, pdset.base)
}
if pdset.offset != expectedPdSet.offset {
return fmt.Errorf("expected offset to be %d for %s, got %d", expectedPdSet.offset, tname, pdset.offset)
}
if pdset.blen != expectedPdSet.blen {
return fmt.Errorf("expected length to be %d for %s, got %d", expectedPdSet.blen, tname, pdset.blen)
}
if pdset.sreg != expectedPdSet.sreg {
return fmt.Errorf("expected destination register to be %d for %s, got %d", expectedPdSet.sreg, tname, pdset.sreg)
}
if pdset.csumType != expectedPdSet.csumType {
return fmt.Errorf("expected checksum type to be %d for %s, got %d", expectedPdSet.csumType, tname, pdset.csumType)
}
if pdset.csumOffset != expectedPdSet.csumOffset {
return fmt.Errorf("expected checksum offset to be %d for %s, got %d", expectedPdSet.csumOffset, tname, pdset.csumOffset)
}
if pdset.csumFlags != expectedPdSet.csumFlags {
return fmt.Errorf("expected checksum flags to be %b for %s, got %b", expectedPdSet.csumFlags, tname, pdset.csumFlags)
}
return nil
}
// TestInterpretRule tests the interpretation of basic and general rules as a
// list of operations.
func TestInterpretRule(t *testing.T) {