Files
Jayden NyamiakaandgVisor bot b2340af8a7 Implement Meta Set operation (parsing, interpretation, evaluation, tests).
The set of meta fields for meta set is much more limited than meta load.
Currently supports setting a limited set of meta fields: pkttype.
It should also be able to support: mark, priority, nftrace, and secmark,
but these have yet to be implemented.

PiperOrigin-RevId: 674426635
2024-09-13 14:07:41 -07:00

1228 lines
35 KiB
Go

// Copyright 2024 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nftables
import (
"encoding/hex"
"fmt"
"regexp"
"slices"
"strconv"
"strings"
"gvisor.dev/gvisor/pkg/abi/linux"
)
// SyntaxError is an interpretation error due to incorrect syntax.
type SyntaxError struct {
// lnIdx is the index of the line where the error occurred.
lnIdx int
// tkIdx is the index of the token where the error occurred.
tkIdx int
// msg is the error message, defined locally.
msg string
}
// Error implements error interface for SyntaxError to return an error message.
func (e *SyntaxError) Error() string {
// Adds 1 to line index and token index to account for 0-indexing.
return fmt.Sprintf("syntax error at line %d, token %d: %s", e.lnIdx+1, e.tkIdx+1, e.msg)
}
// LogicError is an interpretation error from modifying the NFTables state.
type LogicError struct {
// lnIdx is the index of the line where the error occurred.
lnIdx int
// tkIdx is the index of the token where the error occurred.
tkIdx int
// error is the error returned from modifying the NFTables state.
err error
}
// Error implements error interface for LogicError to return an error message.
func (e *LogicError) Error() string {
// Adds 1 to line index and token index to account for 0-indexing.
return fmt.Sprintf("logic error at line %d, token %d: %v", e.lnIdx+1, e.tkIdx+1, e.err)
}
// Note: this is a limited set of keywords.
var reservedKeywords []string = []string{
"include", // include keyword
"define", "undefine", "redefine", // symbolic variables keywords
"ip", "ip6", "inet", "arp", "bridge", "netdev", // address families
"list", "flush", "ruleset", // ruleset operations
"add", "create", "delete", "destroy", "table", "comment", "flags", "handle", // table operations
"rename", "chain", "type", "hook", "device", "priority", "policy", // chain operations
"insert", "reset", "replace", "rule", "index", // rule operations
}
// Set of reserved specifiers for quick lookup.
var reservedKeywordSet map[string]struct{} = initReservedKeywordSet()
func initReservedKeywordSet() map[string]struct{} {
set := make(map[string]struct{})
for _, k := range reservedKeywords {
set[k] = struct{}{}
}
return set
}
var identifierRegexp = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_/.]*$")
// validateIdentifier checks if the identifier is valid.
// An identifier is valid if it is not a reserved keyword and begins with an
// alphabetic character or underscore followed by zero or more alphanumeric
// characters, underscores, forward slashes, or periods.
func validateIdentifier(id string, lnIdx int, tkIdx int) error {
if _, ok := reservedKeywordSet[id]; ok {
return &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("cannot use reserved keyword %s as an identifier", id)}
}
if !identifierRegexp.MatchString(id) {
return &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid identifier %s", id)}
}
return nil
}
// InterpretRule creates a new Rule from the given rule string, assumed to be
// represented as a block of text with a single operation per line.
// Note: the rule string should be generated as output from the official nft
// binary (can be accomplished by using flag --debug=netlink).
func InterpretRule(ruleString string) (*Rule, error) {
ruleString = strings.TrimSpace(ruleString)
lines := slices.DeleteFunc(strings.Split(ruleString, "\n"), func(s string) bool {
return s == ""
})
r := &Rule{ops: make([]operation, 0, len(lines))}
// Interprets all operations in the rule.
for lnIdx, line := range lines {
op, err := InterpretOperation(line, lnIdx)
if err != nil {
return nil, err
}
if err := r.addOperation(op); err != nil {
return nil, err
}
}
return r, nil
}
// InterpretOperation creates a new operation from the given operation string,
// assumed to be a single line of text surrounded in square brackets.
// Note: the operation string should be generated as output from the official nft
// binary (can be accomplished by using flag --debug=netlink).
func InterpretOperation(line string, lnIdx int) (operation, error) {
tokens := strings.Fields(line)
if len(tokens) < 2 {
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for operation, should be at least 2, got %d", len(tokens))}
}
// Second token decides the operation type.
switch tokens[1] {
case "immediate":
return InterpretImmediate(line, lnIdx)
case "cmp":
return InterpretComparison(line, lnIdx)
case "payload":
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])}
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)
case "counter":
return InterpretCounter(line, lnIdx)
case "rt":
return InterpretRoute(line, lnIdx)
case "byteorder":
return InterpretByteorder(line, lnIdx)
case "meta":
switch tokens[2] {
case "load":
return InterpretMetaLoad(line, lnIdx)
case "set":
return InterpretMetaSet(line, lnIdx)
}
return nil, &SyntaxError{lnIdx, 2, fmt.Sprintf("unrecognized operation type: meta %s", tokens[2])}
default:
return nil, &SyntaxError{lnIdx, 1, fmt.Sprintf("unrecognized operation type: %s", tokens[1])}
}
}
// InterpretImmediate creates a new Immediate operation from the given string.
func InterpretImmediate(line string, lnIdx int) (operation, error) {
tokens := strings.Fields(line)
// Requires at least 6 tokens:
// "[", "immediate", "reg", register index, register value, "]".
if len(tokens) < 6 {
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for immediate operation, should be at least 6, got %d", len(tokens))}
}
if err := checkOperationBrackets(tokens, lnIdx); err != nil {
return nil, err
}
tkIdx := 1
// First token should be "immediate".
if err := consumeToken("immediate", 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 the register index.
reg, err := parseRegister(tokens[tkIdx], lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Fourth token should be the value.
nextIdx, data, err := parseRegisterData(reg, 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 immediate operation"}
}
// Create the operation with the specified arguments.
imm, err := newImmediate(reg, data)
if err != nil {
return nil, &LogicError{lnIdx, tkIdx, err}
}
return imm, nil
}
// InterpretComparison creates a new Comparison operation from the given string.
func InterpretComparison(line string, lnIdx int) (operation, error) {
tokens := strings.Fields(line)
// Requires at least 7 tokens:
// "[", "cmp", op, "reg", register index, register value, "]".
if len(tokens) < 7 {
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for cmp operation, should be at least 7, got %d", len(tokens))}
}
if err := checkOperationBrackets(tokens, lnIdx); err != nil {
return nil, err
}
tkIdx := 1
// First token should be "cmp".
if err := consumeToken("cmp", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Second token should be the comparison operator.
cop, err := parseCmpOp(tokens[tkIdx], lnIdx, tkIdx)
if 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 the bytesData representing the value.
nextIdx, data, 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 comparison operation"}
}
// Create the operation with the specified arguments.
cmp, err := newComparison(reg, cop, data)
if err != nil {
return nil, &LogicError{lnIdx, tkIdx, err}
}
return cmp, nil
}
// InterpretPayloadLoad creates a new PayloadLoad operation from the given
// string.
func InterpretPayloadLoad(line string, lnIdx int) (operation, error) {
tokens := strings.Fields(line)
// Requires exactly 13 tokens:
// "[", "payload", "load", len+"b", "@", payload base, "header", "+", offset, "=>", "reg", register index, "]".
if len(tokens) != 13 {
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for payload load operation, should be exactly 13, 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 "load".
if err := consumeToken("load", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Third token should be the length (in bytes) of the payload followed by 'b'.
blen, err := parseUint8PlusChar(tokens[tkIdx], 'b', 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 the payload base header.
base, err := parsePayloadBase(tokens[tkIdx], lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Sixth token should be "header".
if err := consumeToken("header", tokens, lnIdx, tkIdx); 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 uint8 representing the offset.
offset, err := parseUint8(tokens[tkIdx], "offset", lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Ninth token should be "=>".
if err := consumeToken("=>", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Tenth token should be "reg".
if err := consumeToken("reg", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Eleventh token should be the uint8 representing the register index.
reg, err := parseRegister(tokens[tkIdx], lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Create the operation with the specified arguments.
pdload, err := newPayloadLoad(base, offset, blen, reg)
if err != nil {
return nil, &LogicError{lnIdx, tkIdx, err}
}
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'.
blen, err := parseUint8PlusChar(tokens[tkIdx], 'b', 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, blen, reg, csumType, csumOff, csumFlags)
if err != nil {
return nil, &LogicError{lnIdx, tkIdx, err}
}
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
}
// InterpretCounter creates a new Counter operation from the given string.
func InterpretCounter(line string, lnIdx int) (operation, error) {
tokens := strings.Fields(line)
// Requires exactly 7 tokens:
// "[", "counter", "pkts", initial packets, "bytes", initial bytes, "]".
if len(tokens) != 7 {
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for counter operation, should be exactly 7, got %d", len(tokens))}
}
if err := checkOperationBrackets(tokens, lnIdx); err != nil {
return nil, err
}
tkIdx := 1
// First token should be "counter".
if err := consumeToken("counter", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Second token should be "pkts".
if err := consumeToken("pkts", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Third token should be int64 representing initial packets.
initialPkts, err := strconv.ParseInt(tokens[tkIdx], 10, 64)
if err != nil {
return nil, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("could not parse int64 initial packets: '%s'", tokens[tkIdx])}
}
tkIdx++
// Fourth token should be "bytes".
if err := consumeToken("bytes", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Fifth token should be int64 representing initial bytes.
initialBytes, err := strconv.ParseInt(tokens[tkIdx], 10, 64)
if err != nil {
return nil, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("could not parse int64 initial bytes: '%s'", tokens[tkIdx])}
}
tkIdx++
// Create the operation with the specified arguments.
cntr := newCounter(initialPkts, initialBytes)
return cntr, nil
}
// InterpretRoute creates a new Route operation from the given string.
func InterpretRoute(line string, lnIdx int) (operation, error) {
tokens := strings.Fields(line)
// Requires exactly 8 tokens:
// "[", "rt", "load", route key, "=>", "reg", register index, "]".
if len(tokens) != 8 {
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for route operation, should be exactly 8, got %d", len(tokens))}
}
if err := checkOperationBrackets(tokens, lnIdx); err != nil {
return nil, err
}
tkIdx := 1
// First token should be "rt".
if err := consumeToken("rt", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Second token should be "load".
if err := consumeToken("load", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Third token should be the route key.
key, err := parseRouteKey(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 "reg".
if err := consumeToken("reg", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Sixth token should be the uint8 representing the register index.
reg, err := parseRegister(tokens[tkIdx], lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Create the operation with the specified arguments.
rt, err := newRoute(key, reg)
if err != nil {
return nil, &LogicError{lnIdx, tkIdx, err}
}
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
}
// InterpretMetaLoad creates a new MetaLoad operation from the given string.
func InterpretMetaLoad(line string, lnIdx int) (operation, error) {
tokens := strings.Fields(line)
// Requires exactly 8 tokens:
// "[", "meta", "load", meta key, "=>", "reg", register index, "]".
if len(tokens) != 8 {
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for meta operation, should be exactly 8, got %d", len(tokens))}
}
if err := checkOperationBrackets(tokens, lnIdx); err != nil {
return nil, err
}
tkIdx := 1
// First token should be "meta".
if err := consumeToken("meta", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Second token should be "load".
if err := consumeToken("load", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Third token should be the meta key.
key, err := parseMetaKey(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 "reg".
if err := consumeToken("reg", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Sixth token should be the uint8 representing the register index.
reg, err := parseRegister(tokens[tkIdx], lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Create the operation with the specified arguments.
mtLoad, err := newMetaLoad(key, reg)
if err != nil {
return nil, &LogicError{lnIdx, tkIdx, err}
}
return mtLoad, nil
}
// InterpretMetaSet creates a new MetaSet operation from the given string.
func InterpretMetaSet(line string, lnIdx int) (operation, error) {
tokens := strings.Fields(line)
// Requires exactly 8 tokens:
// "[", "meta", "set", meta key, "with", "reg", register index, "]".
if len(tokens) != 8 {
return nil, &SyntaxError{lnIdx, 0, fmt.Sprintf("incorrect number of tokens for meta operation, should be exactly 8, got %d", len(tokens))}
}
if err := checkOperationBrackets(tokens, lnIdx); err != nil {
return nil, err
}
tkIdx := 1
// First token should be "meta".
if err := consumeToken("meta", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Second token should be "set".
if err := consumeToken("set", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Third token should be the meta key.
key, err := parseMetaKey(tokens[tkIdx], lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Fourth token should be "with".
if err := consumeToken("with", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Fifth token should be "reg".
if err := consumeToken("reg", tokens, lnIdx, tkIdx); err != nil {
return nil, err
}
tkIdx++
// Sixth token should be the uint8 representing the register index.
reg, err := parseRegister(tokens[tkIdx], lnIdx, tkIdx)
if err != nil {
return nil, err
}
tkIdx++
// Create the operation with the specified arguments.
mtSet, err := newMetaSet(key, reg)
if err != nil {
return nil, &LogicError{lnIdx, tkIdx, err}
}
return mtSet, nil
}
//
// Interpreter Helper Functions.
//
// checkOperationBrackets checks that the operation string is surrounded by
// square brackets.
func checkOperationBrackets(tokens []string, lnIdx int) error {
if tokens[0] != "[" {
return &SyntaxError{lnIdx, 0, "operation missing opening square bracket"}
}
if tokens[len(tokens)-1] != "]" {
return &SyntaxError{lnIdx, len(tokens) - 1, "operation missing closing square bracket"}
}
return nil
}
// 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) {
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)}
}
return uint8(v64), nil
}
// parseRegister parses the register index from the given string.
func parseRegister(regString string, lnIdx int, tkIdx int) (uint8, error) {
reg, err := parseUint8(regString, "register index", lnIdx, tkIdx)
if err != nil {
return 0, err
}
if !isRegister(reg) {
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid register index: %d", reg)}
}
return reg, nil
}
// parseRegisterData parses the register data from the given token and returns
// the index of the next token to process (can consume multiple tokens).
// Note: assumes the register index is valid (was checked in parseRegister).
func parseRegisterData(reg uint8, tokens []string, lnIdx int, tkIdx int) (int, registerData, error) {
// Handles verdict data.
if isVerdictRegister(reg) {
nextIdx, verdict, err := parseVerdict(tokens, lnIdx, tkIdx)
if err != nil {
return 0, nil, err
}
return nextIdx, newVerdictData(verdict), nil
}
// Handles hex data.
if len(tokens[tkIdx]) > 1 && tokens[tkIdx][:2] == "0x" {
nextIdx, data, err := parseHexData(tokens, lnIdx, tkIdx)
if err != nil {
return 0, nil, err
}
bytesData := newBytesData(data)
if err := bytesData.validateRegister(reg); err != nil {
return 0, nil, &LogicError{lnIdx, tkIdx, err}
}
return nextIdx, bytesData, nil
}
// TODO(b/345684870): cases will be added here as more types are supported.
return 0, nil, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid register data: '%s'", tokens[tkIdx])}
}
// verdictCodeFromKeyword is a map of verdict keyword to its corresponding enum value.
var verdictCodeFromKeyword = map[string]int32{
"accept": linux.NF_ACCEPT,
"drop": linux.NF_DROP,
"continue": linux.NFT_CONTINUE,
"return": linux.NFT_RETURN,
"jump": linux.NFT_JUMP,
"goto": linux.NFT_GOTO,
}
// parseVerdict parses the verdict from the given token and returns
// the index of the next token to process (can consume multiple tokens).
func parseVerdict(tokens []string, lnIdx int, tkIdx int) (int, Verdict, error) {
v := Verdict{}
vcString := tokens[tkIdx]
vc, ok := verdictCodeFromKeyword[vcString]
if !ok {
return 0, v, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid verdict: '%s'", tokens[tkIdx])}
}
v.Code = VC(vc)
tkIdx++
// jump and chain verdicts require 2 more tokens to specify the target chain.
// "jump"/"goto", "->", chain name.
switch v.Code {
case VC(linux.NFT_JUMP), VC(linux.NFT_GOTO):
if err := consumeToken("->", tokens, lnIdx, tkIdx); err != nil {
return 0, v, err
}
tkIdx++
if err := validateIdentifier(tokens[tkIdx], lnIdx, tkIdx); err != nil {
return 0, v, err
}
v.ChainName = tokens[tkIdx]
tkIdx++
}
return tkIdx, v, nil
}
// parseHexData parses little endian hexadecimal data from the given token,
// converts to big endian, and returns the index of the next token to process.
func parseHexData(tokens []string, lnIdx int, tkIdx int) (int, []byte, error) {
var bytes []byte
for ; tkIdx < len(tokens); tkIdx++ {
if len(tokens[tkIdx]) <= 2 || tokens[tkIdx][:2] != "0x" {
break
}
// Hexadecimal data must have 2 digits per byte (even number of characters).
if len(tokens[tkIdx])%2 != 0 {
return 0, nil, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid hexadecimal data: '%s'", tokens[tkIdx])}
}
// Decodes the little endian hex string into bytes
bytes4, err := hex.DecodeString(tokens[tkIdx][2:])
if err != nil {
return 0, nil, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("could not decode hexadecimal data: '%s'", tokens[tkIdx])}
}
// Converts the bytes to big endian and appends to the bytes slice.
slices.Reverse(bytes4)
bytes = append(bytes, bytes4...)
}
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
}
// cmpOpFromKeyword is a map of comparison operator keywords to their
// corresponding enum value.
var cmpOpFromKeyword = map[string]int{
"eq": linux.NFT_CMP_EQ,
"neq": linux.NFT_CMP_NEQ,
"lt": linux.NFT_CMP_LT,
"lte": linux.NFT_CMP_LTE,
"gt": linux.NFT_CMP_GT,
"gte": linux.NFT_CMP_GTE,
}
// parseCmpOp parses the int representing the cmpOp from the given string.
func parseCmpOp(copString string, lnIdx int, tkIdx int) (int, error) {
cop, ok := cmpOpFromKeyword[copString]
if !ok {
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid comparison operator keyword: '%s'", copString)}
}
return cop, nil
}
// 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 := 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: '%s'", numStr)}
}
return uint8(num), nil
}
// payloadBaseFromKeyword is a map of payload base keywords to their
// corresponding enum value.
var payloadBaseFromKeyword = map[string]payloadBase{
"link": linux.NFT_PAYLOAD_LL_HEADER,
"network": linux.NFT_PAYLOAD_NETWORK_HEADER,
"transport": linux.NFT_PAYLOAD_TRANSPORT_HEADER,
}
// parsePayloadBase parses the payload base header from the given string.
func parsePayloadBase(baseString string, lnIdx int, tkIdx int) (payloadBase, error) {
base, ok := payloadBaseFromKeyword[baseString]
if !ok {
// Inner and Tunnel Headers cannot be specified in payload load operation.
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid payload base keyword: '%s'", baseString)}
}
return base, nil
}
// routeKeys is a map of route key keywords to their corresponding enum value.
var routeKeyFromKeyword = map[string]routeKey{
// Fully supported route keys.
"nexthop4": linux.NFT_RT_NEXTHOP4,
"nexthop6": linux.NFT_RT_NEXTHOP6,
"tcpmss": linux.NFT_RT_TCPMSS,
// Keys supported for interpretation but not yet for logic/evaluation.
// Note: Will result in logic error during operation construction.
"classid": linux.NFT_RT_CLASSID,
"ipsec": linux.NFT_RT_XFRM,
}
// parseRouteKey parses the route key from the given string.
func parseRouteKey(keyString string, lnIdx int, tkIdx int) (routeKey, error) {
key, ok := routeKeyFromKeyword[keyString]
if !ok {
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid route key keyword: '%s'", keyString)}
}
return key, nil
}
// metaKeyFromKeyword is a map of meta key keywords to their corresponding enum value.
var metaKeyFromKeyword = map[string]metaKey{
// Supported meta keys.
"len": linux.NFT_META_LEN,
"protocol": linux.NFT_META_PROTOCOL,
"nfproto": linux.NFT_META_NFPROTO,
"l4proto": linux.NFT_META_L4PROTO,
"skuid": linux.NFT_META_SKUID,
"skgid": linux.NFT_META_SKGID,
"rtclassid": linux.NFT_META_RTCLASSID,
"pkttype": linux.NFT_META_PKTTYPE,
"prandom": linux.NFT_META_PRANDOM,
"time": linux.NFT_META_TIME_NS,
"day": linux.NFT_META_TIME_DAY,
"hour": linux.NFT_META_TIME_HOUR,
// Unsupported meta keys.
"priority": linux.NFT_META_PRIORITY,
"mark": linux.NFT_META_MARK,
"iif": linux.NFT_META_IIF,
"oif": linux.NFT_META_OIF,
"iifname": linux.NFT_META_IIFNAME,
"oifname": linux.NFT_META_OIFNAME,
"iiftype": linux.NFT_META_IIFTYPE,
"oiftype": linux.NFT_META_OIFTYPE,
"iifgroup": linux.NFT_META_IIFGROUP,
"oifgroup": linux.NFT_META_OIFGROUP,
"cgroup": linux.NFT_META_CGROUP,
"iifkind": linux.NFT_META_IIFKIND,
"oifkind": linux.NFT_META_OIFKIND,
"sdif": linux.NFT_META_SDIF,
"sdifname": linux.NFT_META_SDIFNAME,
"nftrace": linux.NFT_META_NFTRACE,
"cpu": linux.NFT_META_CPU,
"secmark": linux.NFT_META_SECMARK,
"secpath": linux.NFT_META_SECPATH,
"broute": linux.NFT_META_BRI_BROUTE,
}
// parseMetaKey parses the meta key from the given string.
func parseMetaKey(keyString string, lnIdx int, tkIdx int) (metaKey, error) {
key, ok := metaKeyFromKeyword[keyString]
if !ok {
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid meta key keyword: '%s'", keyString)}
}
return key, nil
}
// consumeToken is a helper function that checks if the token at the given index
// matches the expected string, returning a SyntaxError if not.
func consumeToken(expected string, tokens []string, lnIdx int, tkIdx int) error {
if tokens[tkIdx] != expected {
return &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("unexpected string: %s", tokens[tkIdx])}
}
return nil
}