Compare commits
2 Commits
a8a06f3379
...
40e022df81
Author | SHA1 | Date |
---|---|---|
|
40e022df81 | |
|
c308cb2197 |
|
@ -33,3 +33,5 @@ jobs:
|
||||||
go test ./...
|
go test ./...
|
||||||
go test -c github.com/google/nftables
|
go test -c github.com/google/nftables
|
||||||
sudo ./nftables.test -test.v -run_system_tests
|
sudo ./nftables.test -test.v -run_system_tests
|
||||||
|
go test -c github.com/google/nftables/tests
|
||||||
|
sudo ./tests.test -test.v -run_system_tests
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -3,13 +3,14 @@ module github.com/google/nftables
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/mdlayher/netlink v1.7.2
|
github.com/mdlayher/netlink v1.7.2
|
||||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc
|
github.com/vishvananda/netlink v1.3.0
|
||||||
|
github.com/vishvananda/netns v0.0.4
|
||||||
golang.org/x/sys v0.18.0
|
golang.org/x/sys v0.18.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/mdlayher/socket v0.5.0 // indirect
|
github.com/mdlayher/socket v0.5.0 // indirect
|
||||||
golang.org/x/net v0.23.0 // indirect
|
golang.org/x/net v0.23.0 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -6,11 +6,15 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
|
||||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||||
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
||||||
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
|
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||||
|
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||||
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
|
|
@ -1442,7 +1442,7 @@ func TestSecMarkMarshaling(t *testing.T) {
|
||||||
conn.AddObj(sec)
|
conn.AddObj(sec)
|
||||||
|
|
||||||
if err := conn.Flush(); err != nil {
|
if err := conn.Flush(); err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1492,7 +1492,7 @@ func TestSynProxyObject(t *testing.T) {
|
||||||
conn.AddObj(syn2)
|
conn.AddObj(syn2)
|
||||||
conn.AddObj(syn3)
|
conn.AddObj(syn3)
|
||||||
if err := conn.Flush(); err != nil {
|
if err := conn.Flush(); err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objs, err := conn.GetNamedObjects(table)
|
objs, err := conn.GetNamedObjects(table)
|
||||||
|
@ -1637,7 +1637,7 @@ func TestCtTimeout(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := conn.Flush(); err != nil {
|
if err := conn.Flush(); err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
obj, err := conn.GetObject(ctt1)
|
obj, err := conn.GetObject(ctt1)
|
||||||
|
@ -1693,7 +1693,7 @@ func TestCtExpect(t *testing.T) {
|
||||||
|
|
||||||
conn.AddObj(cte)
|
conn.AddObj(cte)
|
||||||
if err := conn.Flush(); err != nil {
|
if err := conn.Flush(); err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objs, err := conn.GetNamedObjects(table)
|
objs, err := conn.GetNamedObjects(table)
|
||||||
|
@ -1758,7 +1758,7 @@ func TestCtHelper(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := conn.Flush(); err != nil {
|
if err := conn.Flush(); err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
obj1, err := conn.GetObject(cthelp1)
|
obj1, err := conn.GetObject(cthelp1)
|
||||||
|
@ -2590,7 +2590,7 @@ func TestGetResetNamedObj(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objsNamed, err := c.GetNamedObjects(table)
|
objsNamed, err := c.GetNamedObjects(table)
|
||||||
|
@ -2698,7 +2698,7 @@ func TestObjAPI(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objs, err := c.GetObjects(table)
|
objs, err := c.GetObjects(table)
|
||||||
|
@ -3049,7 +3049,7 @@ func TestObjAPICounterLegacyType(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objs, err := c.GetObjects(table)
|
objs, err := c.GetObjects(table)
|
||||||
|
|
|
@ -0,0 +1,265 @@
|
||||||
|
// Copyright 2025 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// 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 tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/nftables"
|
||||||
|
"github.com/google/nftables/binaryutil"
|
||||||
|
"github.com/google/nftables/expr"
|
||||||
|
"github.com/google/nftables/internal/nftest"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
var enableSysTests = flag.Bool("run_system_tests", false, "Run tests that operate against the live kernel")
|
||||||
|
|
||||||
|
func ifname(n string) []byte {
|
||||||
|
b := make([]byte, 16)
|
||||||
|
copy(b, []byte(n+"\x00"))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNFTables(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
scriptPath string
|
||||||
|
goCommands func(t *testing.T, c *nftables.Conn)
|
||||||
|
expectFailure bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "AddTable",
|
||||||
|
scriptPath: "testdata/add_table.nft",
|
||||||
|
goCommands: func(t *testing.T, c *nftables.Conn) {
|
||||||
|
c.FlushRuleset()
|
||||||
|
|
||||||
|
c.AddTable(&nftables.Table{
|
||||||
|
Name: "test-table",
|
||||||
|
Family: nftables.TableFamilyINet,
|
||||||
|
})
|
||||||
|
|
||||||
|
err := c.Flush()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating table: %v", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AddChain",
|
||||||
|
scriptPath: "testdata/add_chain.nft",
|
||||||
|
goCommands: func(t *testing.T, c *nftables.Conn) {
|
||||||
|
c.FlushRuleset()
|
||||||
|
|
||||||
|
table := c.AddTable(&nftables.Table{
|
||||||
|
Name: "test-table",
|
||||||
|
Family: nftables.TableFamilyINet,
|
||||||
|
})
|
||||||
|
|
||||||
|
c.AddChain(&nftables.Chain{
|
||||||
|
Name: "test-chain",
|
||||||
|
Table: table,
|
||||||
|
Hooknum: nftables.ChainHookOutput,
|
||||||
|
Priority: nftables.ChainPriorityNATDest,
|
||||||
|
Type: nftables.ChainTypeNAT,
|
||||||
|
})
|
||||||
|
|
||||||
|
err := c.Flush()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating table: %v", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AddFlowtables",
|
||||||
|
scriptPath: "testdata/add_flowtables.nft",
|
||||||
|
goCommands: func(t *testing.T, c *nftables.Conn) {
|
||||||
|
devices := []string{"dummy0"}
|
||||||
|
c.FlushRuleset()
|
||||||
|
// add + delete + add for flushing all the table
|
||||||
|
table := c.AddTable(&nftables.Table{
|
||||||
|
Family: nftables.TableFamilyINet,
|
||||||
|
Name: "test-table",
|
||||||
|
})
|
||||||
|
|
||||||
|
devicesSet := &nftables.Set{
|
||||||
|
Table: table,
|
||||||
|
Name: "test-set",
|
||||||
|
KeyType: nftables.TypeIFName,
|
||||||
|
KeyByteOrder: binaryutil.NativeEndian,
|
||||||
|
}
|
||||||
|
|
||||||
|
elements := []nftables.SetElement{}
|
||||||
|
for _, dev := range devices {
|
||||||
|
elements = append(elements, nftables.SetElement{
|
||||||
|
Key: ifname(dev),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.AddSet(devicesSet, elements); err != nil {
|
||||||
|
t.Errorf("failed to add Set %s : %v", devicesSet.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
flowtable := &nftables.Flowtable{
|
||||||
|
Table: table,
|
||||||
|
Name: "test-flowtable",
|
||||||
|
Devices: devices,
|
||||||
|
Hooknum: nftables.FlowtableHookIngress,
|
||||||
|
Priority: nftables.FlowtablePriorityRef(5),
|
||||||
|
}
|
||||||
|
c.AddFlowtable(flowtable)
|
||||||
|
|
||||||
|
chain := c.AddChain(&nftables.Chain{
|
||||||
|
Name: "test-chain",
|
||||||
|
Table: table,
|
||||||
|
Type: nftables.ChainTypeFilter,
|
||||||
|
Hooknum: nftables.ChainHookForward,
|
||||||
|
Priority: nftables.ChainPriorityMangle,
|
||||||
|
})
|
||||||
|
|
||||||
|
c.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Meta{Key: expr.MetaKeyIIFNAME, SourceRegister: false, Register: 0x1},
|
||||||
|
&expr.Lookup{SourceRegister: 0x1, DestRegister: 0x0, IsDestRegSet: false, SetName: "test-set", Invert: true},
|
||||||
|
&expr.Verdict{Kind: expr.VerdictReturn},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
c.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Meta{Key: expr.MetaKeyOIFNAME, SourceRegister: false, Register: 0x1},
|
||||||
|
&expr.Lookup{SourceRegister: 0x1, DestRegister: 0x0, IsDestRegSet: false, SetName: "test-set", Invert: true},
|
||||||
|
&expr.Verdict{Kind: expr.VerdictReturn},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
c.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Ct{Register: 0x1, SourceRegister: false, Key: expr.CtKeySTATE, Direction: 0x0},
|
||||||
|
&expr.Bitwise{SourceRegister: 0x1, DestRegister: 0x1, Len: 0x4, Mask: binaryutil.NativeEndian.PutUint32(expr.CtStateBitESTABLISHED), Xor: binaryutil.NativeEndian.PutUint32(0)},
|
||||||
|
&expr.Cmp{Op: 0x1, Register: 0x1, Data: []uint8{0x0, 0x0, 0x0, 0x0}},
|
||||||
|
&expr.Ct{Register: 0x1, SourceRegister: false, Key: expr.CtKeyPKTS, Direction: 0x0},
|
||||||
|
&expr.Cmp{Op: expr.CmpOpGt, Register: 0x1, Data: binaryutil.NativeEndian.PutUint64(20)},
|
||||||
|
&expr.FlowOffload{Name: "test-flowtable"},
|
||||||
|
&expr.Counter{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := c.Flush(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Create a new network namespace to test these operations,
|
||||||
|
// and tear down the namespace at test completion.
|
||||||
|
c, newNS := nftest.OpenSystemConn(t, *enableSysTests)
|
||||||
|
defer nftest.CleanupSystemConn(t, newNS)
|
||||||
|
|
||||||
|
// Real interface must exist otherwise some nftables will fail
|
||||||
|
la := netlink.NewLinkAttrs()
|
||||||
|
la.Name = "dummy0"
|
||||||
|
dummy := &netlink.Dummy{LinkAttrs: la}
|
||||||
|
if err := netlink.LinkAdd(dummy); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, filename, _, _ := runtime.Caller(0) // Get the current file's path
|
||||||
|
filePath := filepath.Join(filepath.Dir(filename), tt.scriptPath)
|
||||||
|
t.Logf("testing script from %s", filePath)
|
||||||
|
|
||||||
|
scriptOutput, err := applyNFTScript(filePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to apply nftables script: %v\noutput:%s", err, scriptOutput)
|
||||||
|
}
|
||||||
|
if len(scriptOutput) > 0 {
|
||||||
|
t.Logf("nft output:\n%s", scriptOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve nftables state using nft
|
||||||
|
expectedOutput, err := getNFTablesRuleset()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to list nftables ruleset: %v\noutput:%s", err, expectedOutput)
|
||||||
|
}
|
||||||
|
t.Logf("Expected output:\n%s", expectedOutput)
|
||||||
|
|
||||||
|
// Program nftables using your Go code
|
||||||
|
err = flushNFTRuleset()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to flush nftables ruleset: %v", err)
|
||||||
|
}
|
||||||
|
tt.goCommands(t, c)
|
||||||
|
|
||||||
|
// Retrieve nftables state using nft
|
||||||
|
actualOutput, err := getNFTablesRuleset()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to list nftables ruleset: %v\noutput:%s", err, actualOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Actual output:\n%s", actualOutput)
|
||||||
|
|
||||||
|
if expectedOutput != actualOutput {
|
||||||
|
t.Errorf("nftables ruleset mismatch:\n%s", cmp.Diff(expectedOutput, actualOutput))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = flushNFTRuleset()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to flush nftables ruleset: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyNFTScript(scriptPath string) (string, error) {
|
||||||
|
cmd := exec.Command("nft", "-f", scriptPath)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return string(out), err
|
||||||
|
}
|
||||||
|
return normalizeOutput(string(out)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNFTablesRuleset() (string, error) {
|
||||||
|
cmd := exec.Command("nft", "list", "ruleset")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return string(out), err
|
||||||
|
}
|
||||||
|
return normalizeOutput(string(out)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func flushNFTRuleset() error {
|
||||||
|
cmd := exec.Command("nft", "flush", "ruleset")
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeOutput(output string) string {
|
||||||
|
output = strings.TrimSpace(output)
|
||||||
|
return output
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
table inet test-table {
|
||||||
|
chain test-chain {
|
||||||
|
type nat hook output priority dstnat; policy accept;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
table inet test-table {
|
||||||
|
set test-set {
|
||||||
|
type ifname
|
||||||
|
elements = { "dummy0" }
|
||||||
|
}
|
||||||
|
|
||||||
|
flowtable test-flowtable {
|
||||||
|
hook ingress priority filter + 5
|
||||||
|
devices = { dummy0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
chain test-chain {
|
||||||
|
type filter hook forward priority mangle; policy accept;
|
||||||
|
iifname != @test-set return
|
||||||
|
oifname != @test-set return
|
||||||
|
ct state established ct packets > 20 flow add @test-flowtable counter packets 0 bytes 0
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
table inet test-table {
|
||||||
|
}
|
Loading…
Reference in New Issue