mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement Route operation (parsing, interpretation, evaluation, tests).
Out of the 5 route keys, we currently support all but IPsec/XRFM key. However, classid key is only supported for IPv6 (needs support for IPv4 too). Also removes repetition in validatePayloadBase & String for payloadBase. PiperOrigin-RevId: 674044789
This commit is contained in:
committed by
gVisor bot
parent
b050c045d1
commit
684a1c8665
+129
-13
@@ -592,6 +592,7 @@ var (
|
||||
_ operation = (*bitwise)(nil)
|
||||
_ operation = (*counter)(nil)
|
||||
_ operation = (*last)(nil)
|
||||
_ operation = (*route)(nil)
|
||||
)
|
||||
|
||||
// immediate is an operation that sets the data in a register.
|
||||
@@ -660,7 +661,7 @@ func validateComparisonOp(cop cmpOp) error {
|
||||
|
||||
// newComparison creates a new Comparison operation.
|
||||
func newComparison(sreg uint8, op int, data []byte) (*comparison, error) {
|
||||
if sreg == linux.NFT_REG_VERDICT {
|
||||
if isVerdictRegister(sreg) {
|
||||
return nil, fmt.Errorf("comparison operation cannot use verdict register as source")
|
||||
}
|
||||
bytesData := newBytesData(data)
|
||||
@@ -754,7 +755,7 @@ func validateRangeOp(rop rngOp) error {
|
||||
|
||||
// newRanged creates a new Ranged operation.
|
||||
func newRanged(sreg uint8, op int, low, high []byte) (*ranged, error) {
|
||||
if sreg == linux.NFT_REG_VERDICT {
|
||||
if isVerdictRegister(sreg) {
|
||||
return nil, fmt.Errorf("comparison operation cannot use verdict register as source")
|
||||
}
|
||||
if len(low) != len(high) {
|
||||
@@ -810,9 +811,13 @@ type payloadLoad struct {
|
||||
// include/uapi/linux/netfilter/nf_tables.h and uses the same constants.
|
||||
type payloadBase int
|
||||
|
||||
// String for NftPayloadBase returns the string representation of the payload
|
||||
// base.
|
||||
// String for payloadBase returns the string representation of the payload base.
|
||||
func (base payloadBase) String() string {
|
||||
// Uses errors from validation to handle unsupported payload bases.
|
||||
if err := validatePayloadBase(base); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Cases for supported payload bases.
|
||||
switch base {
|
||||
case linux.NFT_PAYLOAD_LL_HEADER:
|
||||
return "Link Layer Header"
|
||||
@@ -820,20 +825,18 @@ func (base payloadBase) String() string {
|
||||
return "Network Header"
|
||||
case linux.NFT_PAYLOAD_TRANSPORT_HEADER:
|
||||
return "Transport Header"
|
||||
case linux.NFT_PAYLOAD_INNER_HEADER:
|
||||
panic("inner header not supported")
|
||||
case linux.NFT_PAYLOAD_TUN_HEADER:
|
||||
panic("tunneling header not supported")
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid payload base: %d", int(base)))
|
||||
return fmt.Sprintf("Unknown Supported Payload Base: %d", int(base))
|
||||
}
|
||||
}
|
||||
|
||||
// validatePayloadBase ensures the payload base is valid.
|
||||
func validatePayloadBase(base payloadBase) error {
|
||||
switch base {
|
||||
// Supported payload bases.
|
||||
case linux.NFT_PAYLOAD_LL_HEADER, linux.NFT_PAYLOAD_NETWORK_HEADER, linux.NFT_PAYLOAD_TRANSPORT_HEADER:
|
||||
return nil
|
||||
// Unsupported payload bases.
|
||||
case linux.NFT_PAYLOAD_INNER_HEADER:
|
||||
return fmt.Errorf("inner header not supported")
|
||||
case linux.NFT_PAYLOAD_TUN_HEADER:
|
||||
@@ -871,7 +874,7 @@ func getPayloadBuffer(pkt *stack.PacketBuffer, base payloadBase) []byte {
|
||||
|
||||
// newPayloadLoad creates a new PayloadLoad operation.
|
||||
func newPayloadLoad(base payloadBase, offset, blen, dreg uint8) (*payloadLoad, error) {
|
||||
if dreg == linux.NFT_REG_VERDICT {
|
||||
if isVerdictRegister(dreg) {
|
||||
return nil, fmt.Errorf("payload load operation cannot use verdict register as destination")
|
||||
}
|
||||
if blen > 16 || (blen > 4 && is4ByteRegister(dreg)) {
|
||||
@@ -944,7 +947,7 @@ func validateChecksumType(csumType uint8) error {
|
||||
|
||||
// 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 {
|
||||
if isVerdictRegister(sreg) {
|
||||
return nil, fmt.Errorf("payload set operation cannot use verdict register as destination")
|
||||
}
|
||||
if blen > 16 || (blen > 4 && is4ByteRegister(sreg)) {
|
||||
@@ -1100,7 +1103,7 @@ type bitwise struct {
|
||||
|
||||
// newBitwiseBool creates a new bitwise boolean operation.
|
||||
func newBitwiseBool(sreg, dreg uint8, mask, xor []byte) (*bitwise, error) {
|
||||
if sreg == linux.NFT_REG_VERDICT || dreg == linux.NFT_REG_VERDICT {
|
||||
if isVerdictRegister(sreg) || isVerdictRegister(dreg) {
|
||||
return nil, fmt.Errorf("bitwise operation cannot use verdict register as source or destination")
|
||||
}
|
||||
blen := len(mask)
|
||||
@@ -1115,7 +1118,7 @@ func newBitwiseBool(sreg, dreg uint8, mask, xor []byte) (*bitwise, error) {
|
||||
|
||||
// newBitwiseShift creates a new bitwise shift operation.
|
||||
func newBitwiseShift(sreg, dreg, blen uint8, shift uint32, right bool) (*bitwise, error) {
|
||||
if sreg == linux.NFT_REG_VERDICT || dreg == linux.NFT_REG_VERDICT {
|
||||
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))) {
|
||||
@@ -1273,6 +1276,119 @@ func (op *last) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule)
|
||||
op.set.CompareAndSwap(false, true)
|
||||
}
|
||||
|
||||
// route is an operation that loads specific route data into a register.
|
||||
// Note: route operations are not supported for the verdict register.
|
||||
type route struct {
|
||||
key routeKey // Route key specifying what data to retrieve.
|
||||
dreg uint8 // Number of the destination register.
|
||||
|
||||
// Route information is stored AS IS. If the data is a field stored by the
|
||||
// kernel, it is stored in host endian. If the data is from the packet, it
|
||||
// is stored in big endian (network order).
|
||||
// The nft binary handles the necessary endian conversions from user input.
|
||||
// For example, if the user wants to check if some kernel data == 123 vs
|
||||
// payload data == 123, the nft binary passes host endian register data for
|
||||
// the former and big endian register data for the latter.
|
||||
}
|
||||
|
||||
// routeKey is the key that determines the specific route data to retrieve.
|
||||
// Note: corresponds to enum nft_rt_keys from
|
||||
// include/uapi/linux/netfilter/nf_tables.h and uses the same constants.
|
||||
type routeKey int
|
||||
|
||||
// String for routeKey returns the string representation of the route key.
|
||||
func (key routeKey) String() string {
|
||||
// Uses errors from validation to handle unsupported route keys.
|
||||
if err := validateRouteKey(key); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Cases for supported route keys.
|
||||
switch key {
|
||||
case linux.NFT_RT_NEXTHOP4:
|
||||
return "Next Hop IPv4"
|
||||
case linux.NFT_RT_NEXTHOP6:
|
||||
return "Next Hop IPv6"
|
||||
case linux.NFT_RT_TCPMSS:
|
||||
return "TCP Maximum Segment Size (TCPMSS)"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown Supported Route Key: %d", int(key))
|
||||
}
|
||||
}
|
||||
|
||||
// validateRouteKey ensures the route key is valid.
|
||||
func validateRouteKey(key routeKey) error {
|
||||
switch key {
|
||||
// Supported route keys.
|
||||
case linux.NFT_RT_NEXTHOP4, linux.NFT_RT_NEXTHOP6, linux.NFT_RT_TCPMSS:
|
||||
return nil
|
||||
// Unsupported route keys.
|
||||
case linux.NFT_RT_CLASSID:
|
||||
// Note: We can trivially support Traffic Class ID for IPv6, but we need to
|
||||
// do more work to support it for IPv4. For safety, we mark it as
|
||||
// unsupported since we don't know what packet type we're working with until
|
||||
// the time of evaluation. In the worst case, we don't want the user to
|
||||
// initialize a route with this key and then have it silently break and
|
||||
// yield a difficult-to-debug error.
|
||||
return fmt.Errorf("traffic class id not supported")
|
||||
case linux.NFT_RT_XFRM:
|
||||
return fmt.Errorf("xfrm transformation not supported")
|
||||
default:
|
||||
return fmt.Errorf("invalid route key: %d", int(key))
|
||||
}
|
||||
}
|
||||
|
||||
// newRoute creates a new route operation.
|
||||
func newRoute(key routeKey, dreg uint8) (*route, error) {
|
||||
if isVerdictRegister(dreg) {
|
||||
return nil, fmt.Errorf("route operation cannot use verdict register as destination")
|
||||
}
|
||||
if err := validateRouteKey(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &route{key: key, dreg: dreg}, nil
|
||||
}
|
||||
|
||||
// evaluate for Route loads specific routing data into the destination register.
|
||||
func (op route) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule) {
|
||||
// Gets the target data to be stored in the destination register.
|
||||
var target []byte
|
||||
switch op.key {
|
||||
|
||||
// Retrieves next hop IPv4 address (restricted to IPv4).
|
||||
// Stores data in big endian network order.
|
||||
case linux.NFT_RT_NEXTHOP4:
|
||||
if pkt.NetworkProtocolNumber != header.IPv4ProtocolNumber {
|
||||
break
|
||||
}
|
||||
target = pkt.EgressRoute.NextHop.AsSlice()
|
||||
|
||||
// Retrieves next hop IPv6 address (restricted to IPv6).
|
||||
// Stores data in big endian network order.
|
||||
case linux.NFT_RT_NEXTHOP6:
|
||||
if pkt.NetworkProtocolNumber != header.IPv6ProtocolNumber {
|
||||
break
|
||||
}
|
||||
target = pkt.EgressRoute.NextHop.AsSlice()
|
||||
|
||||
// Retrieves the TCP Maximum Segment Size (TCPMSS).
|
||||
// Stores data in host endian.
|
||||
case linux.NFT_RT_TCPMSS:
|
||||
tcpmss := pkt.GSOOptions.MSS
|
||||
target = binary.NativeEndian.AppendUint16(nil, tcpmss)
|
||||
}
|
||||
|
||||
// Breaks if could not retrieve target data.
|
||||
if target == nil {
|
||||
regs.verdict = Verdict{Code: VC(linux.NFT_BREAK)}
|
||||
return
|
||||
}
|
||||
|
||||
// Stores the target data in the destination register.
|
||||
data := newBytesData(target)
|
||||
data.storeData(regs, op.dreg)
|
||||
}
|
||||
|
||||
//
|
||||
// Register and Register-Related Implementations.
|
||||
// Note: Registers are represented by type uint8 for the register number.
|
||||
|
||||
@@ -2459,6 +2459,117 @@ func TestEvaluateLast(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestEvaluateRoute tests that the Route operation correctly loads the specific
|
||||
// route data into into the destination register.
|
||||
// The nft binary commands used to generate these are stated above each test.
|
||||
// Also note that all these commands mirror the ones in TestInterpretRouteOps.
|
||||
// All commands should be preceded by nft --debug=netlink.
|
||||
// Note: Relies on expected behavior of the Comparison operation.
|
||||
func TestEvaluateRoute(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
tname string
|
||||
pkt *stack.PacketBuffer
|
||||
op1 operation // Route operation to test.
|
||||
op2 operation // Comparison operation to check resulting data in register,
|
||||
}{
|
||||
// IPv4 Next Hop Commands
|
||||
{ // cmd: add rule ip filter output rt nexthop 192.168.1.1
|
||||
tname: "load nexthop4 key to 4-byte register",
|
||||
pkt: func() *stack.PacketBuffer {
|
||||
pkt := makeIPv4Packet(header.IPv6MinimumSize, arbitraryIPv4Fields())
|
||||
pkt.EgressRoute.NextHop = tcpip.AddrFrom4(arbitraryIPv4AddrB)
|
||||
return pkt
|
||||
}(),
|
||||
op1: mustCreateRoute(t, linux.NFT_RT_NEXTHOP4, linux.NFT_REG32_06),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG32_06, linux.NFT_CMP_EQ, arbitraryIPv4AddrB[:]),
|
||||
},
|
||||
{ // cmd: add rule ip filter output rt nexthop 192.168.1.9
|
||||
tname: "load nexthop4 key to 16-byte register",
|
||||
pkt: func() *stack.PacketBuffer {
|
||||
pkt := makeIPv4Packet(header.IPv6MinimumSize, arbitraryIPv4Fields())
|
||||
pkt.EgressRoute.NextHop = tcpip.AddrFrom4(arbitraryIPv4AddrB2)
|
||||
return pkt
|
||||
}(),
|
||||
op1: mustCreateRoute(t, linux.NFT_RT_NEXTHOP4, linux.NFT_REG_3),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG_3, linux.NFT_CMP_EQ, arbitraryIPv4AddrB2[:]),
|
||||
},
|
||||
// IPv6 Next Hop Commands
|
||||
{ // cmd: add rule ip filter output rt nexthop 2001:db8:85a3::aa
|
||||
tname: "load nexthop6 key to 16-byte register",
|
||||
pkt: func() *stack.PacketBuffer {
|
||||
pkt := makeIPv6Packet(header.IPv6MinimumSize, arbitraryIPv6Fields())
|
||||
pkt.EgressRoute.NextHop = tcpip.AddrFrom16(arbitraryIPv6AddrB)
|
||||
return pkt
|
||||
}(),
|
||||
op1: mustCreateRoute(t, linux.NFT_RT_NEXTHOP6, linux.NFT_REG_1),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG_1, linux.NFT_CMP_EQ, arbitraryIPv6AddrB[:]),
|
||||
},
|
||||
// TCP Maximum Segment Size Commands
|
||||
{ // cmd: add rule ip filter output rt mtu 1500
|
||||
tname: "load tcpmss key to 4-byte register",
|
||||
pkt: func() *stack.PacketBuffer {
|
||||
pkt := makeIPv4Packet(header.IPv6MinimumSize, arbitraryIPv4Fields())
|
||||
pkt.GSOOptions.MSS = 1500
|
||||
return pkt
|
||||
}(),
|
||||
op1: mustCreateRoute(t, linux.NFT_RT_TCPMSS, linux.NFT_REG32_00),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG32_00, linux.NFT_CMP_EQ, binary.NativeEndian.AppendUint16(nil, 1500)),
|
||||
},
|
||||
{ // cmd: add rule ip filter output rt mtu 0x0102
|
||||
tname: "load tcpmss key to 16-byte register",
|
||||
pkt: func() *stack.PacketBuffer {
|
||||
pkt := makeIPv6Packet(header.IPv6MinimumSize, arbitraryIPv6Fields())
|
||||
pkt.GSOOptions.MSS = 0x0102
|
||||
return pkt
|
||||
}(),
|
||||
op1: mustCreateRoute(t, linux.NFT_RT_TCPMSS, linux.NFT_REG_4),
|
||||
op2: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, binary.NativeEndian.AppendUint16(nil, 0x0102)),
|
||||
},
|
||||
} {
|
||||
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)
|
||||
}
|
||||
|
||||
// Adds drop operation. Will be final verdict if all comparisons are 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.
|
||||
v, err := nf.EvaluateHook(arbitraryFamily, arbitraryHook, test.pkt)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for EvaluateHook: %v", err)
|
||||
}
|
||||
|
||||
// Checks for final verdict (should be Drop if comparisons are true).
|
||||
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) {
|
||||
@@ -3122,3 +3233,12 @@ func mustCreateBitwiseShift(t *testing.T, sreg, dreg, blen uint8, shift uint32,
|
||||
}
|
||||
return bit
|
||||
}
|
||||
|
||||
// mustCreateRoute wraps the newRoute function for brevity.
|
||||
func mustCreateRoute(t *testing.T, key routeKey, dreg uint8) *route {
|
||||
rt, err := newRoute(key, dreg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create route: %v", err)
|
||||
}
|
||||
return rt
|
||||
}
|
||||
|
||||
@@ -153,6 +153,8 @@ func InterpretOperation(line string, lnIdx int) (operation, error) {
|
||||
return InterpretBitwiseBool(line, lnIdx)
|
||||
case "counter":
|
||||
return InterpretCounter(line, lnIdx)
|
||||
case "rt":
|
||||
return InterpretRoute(line, lnIdx)
|
||||
default:
|
||||
return nil, &SyntaxError{lnIdx, 1, fmt.Sprintf("unrecognized operation type: %s", tokens[1])}
|
||||
}
|
||||
@@ -668,6 +670,69 @@ func InterpretCounter(line string, lnIdx int) (operation, error) {
|
||||
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
|
||||
}
|
||||
|
||||
//
|
||||
// Interpreter Helper Functions.
|
||||
//
|
||||
@@ -864,6 +929,27 @@ func parsePayloadBase(baseString string, lnIdx int, tkIdx int) (payloadBase, err
|
||||
}
|
||||
}
|
||||
|
||||
// parseRouteKey parses the route key from the given string.
|
||||
func parseRouteKey(keyString string, lnIdx int, tkIdx int) (routeKey, error) {
|
||||
switch keyString {
|
||||
// Fully supported route keys.
|
||||
case "nexthop4":
|
||||
return linux.NFT_RT_NEXTHOP4, nil
|
||||
case "nexthop6":
|
||||
return linux.NFT_RT_NEXTHOP6, nil
|
||||
case "tcpmss":
|
||||
return linux.NFT_RT_TCPMSS, nil
|
||||
// Keys supported for interpretation but not yet for logic/evaluation.
|
||||
// Note: Will result in logic error during operation construction.
|
||||
case "classid":
|
||||
return linux.NFT_RT_CLASSID, nil
|
||||
case "ipsec":
|
||||
return linux.NFT_RT_XFRM, nil
|
||||
default:
|
||||
return 0, &SyntaxError{lnIdx, tkIdx, fmt.Sprintf("invalid route key keyword: '%s'", keyString)}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -808,6 +808,82 @@ func checkCounterOp(tname string, expected operation, actual operation) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestInterpretRouteOps tests interpretation of route operations.
|
||||
func TestInterpretRouteOps(t *testing.T) {
|
||||
for _, test := range []interpretOperationTestAction{
|
||||
{ // cmd: add rule ip filter output rt nexthop 192.168.1.1
|
||||
tname: "load nexthop4 key to 4-byte register",
|
||||
opStr: "[ rt load nexthop4 => reg 14 ]",
|
||||
expected: mustCreateRoute(t, linux.NFT_RT_NEXTHOP4, linux.NFT_REG32_06),
|
||||
},
|
||||
{ // cmd: add rule ip filter output rt nexthop 192.168.1.9
|
||||
tname: "load nexthop4 key to 4-byte register",
|
||||
opStr: "[ rt load nexthop4 => reg 3 ]",
|
||||
expected: mustCreateRoute(t, linux.NFT_RT_NEXTHOP4, linux.NFT_REG_3),
|
||||
},
|
||||
{ // cmd: add rule ip6 filter output rt nexthop 2001:db8:85a3::aa
|
||||
tname: "load nexthop6 key to 16-byte register",
|
||||
opStr: "[ rt load nexthop6 => reg 1 ]",
|
||||
expected: mustCreateRoute(t, linux.NFT_RT_NEXTHOP6, linux.NFT_REG_1),
|
||||
},
|
||||
{ // cmd: add rule ip filter output rt mtu 1500
|
||||
tname: "load tcpmss key to 4-byte register",
|
||||
opStr: "[ rt load tcpmss => reg 8 ]",
|
||||
expected: mustCreateRoute(t, linux.NFT_RT_TCPMSS, linux.NFT_REG32_00),
|
||||
},
|
||||
{ // cmd: add rule ip filter output rt mtu 0x0102
|
||||
tname: "load tcpmss key to 16-byte register",
|
||||
opStr: "[ rt load tcpmss => reg 4 ]",
|
||||
expected: mustCreateRoute(t, linux.NFT_RT_TCPMSS, linux.NFT_REG_4),
|
||||
},
|
||||
// Result in errors.
|
||||
{ // cmd: add rule ip filter output rt classid 0x05
|
||||
tname: "unsupported route key classid",
|
||||
opStr: "[ rt load classid => reg 10 ]",
|
||||
expected: nil,
|
||||
},
|
||||
{ // cmd: add rule ip filter output rt ipsec exists
|
||||
tname: "unsupported route key ipsec",
|
||||
opStr: "[ rt load ipsec => reg 1 ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "invalid route key keyword",
|
||||
opStr: "[ rt load xrfm => reg 1 ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "too few tokens for route operation",
|
||||
opStr: "[ rt nexthop6 => reg 1 ]",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
tname: "too many tokens for route operation",
|
||||
opStr: "[ rt load tcpmss => reg 4 -> reg 5 ]",
|
||||
expected: nil,
|
||||
},
|
||||
} {
|
||||
t.Run(test.tname, func(t *testing.T) { checkOp(t, test, checkRouteOp) })
|
||||
}
|
||||
}
|
||||
|
||||
// checkRouteOp checks that the given operation is a route operation and
|
||||
// that it matches the expected route operation.
|
||||
func checkRouteOp(tname string, expected operation, actual operation) error {
|
||||
expectedRt := expected.(*route)
|
||||
rt, ok := actual.(*route)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected operation type to be Route for %s, got %T", tname, actual)
|
||||
}
|
||||
if rt.key != expectedRt.key {
|
||||
return fmt.Errorf("expected route key to be %v for %s, got %v", expectedRt.key, tname, rt.key)
|
||||
}
|
||||
if rt.dreg != expectedRt.dreg {
|
||||
return fmt.Errorf("expected destination register to be %d for %s, got %d", expectedRt.dreg, tname, rt.dreg)
|
||||
}
|
||||
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