mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
7ef3dda2a1
commit
bd89a24410
@@ -13,6 +13,7 @@ go_library(
|
||||
],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/tcpip/checksum",
|
||||
"//pkg/tcpip/header",
|
||||
"//pkg/tcpip/stack",
|
||||
],
|
||||
|
||||
+189
-22
@@ -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
|
||||
|
||||
+713
-137
File diff suppressed because it is too large
Load Diff
@@ -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)}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user