Compare commits

..

1 Commits

Author SHA1 Message Date
Paul Greenberg d477ae97f0
Merge dae73eaa9c into e99829fb4f 2024-12-19 10:28:30 +01:00
10 changed files with 24 additions and 397 deletions

View File

@ -33,5 +33,3 @@ 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/integration
(cd integration && sudo ../integration.test -test.v -run_system_tests)

12
go.mod
View File

@ -3,15 +3,15 @@ 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.3-0.20250113171957-fbb4dce95f42 github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc
github.com/vishvananda/netlink v1.3.0 golang.org/x/sys v0.18.0
github.com/vishvananda/netns v0.0.4
golang.org/x/sys v0.28.0
) )
require ( require (
github.com/google/go-cmp v0.6.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.33.0 // indirect golang.org/x/net v0.23.0 // indirect
golang.org/x/sync v0.6.0 // indirect golang.org/x/sync v0.6.0 // indirect
) )

22
go.sum
View File

@ -1,18 +1,16 @@
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
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/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

View File

@ -1,252 +0,0 @@
// 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 integration
import (
"flag"
"os/exec"
"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)
}
scriptOutput, err := applyNFTRuleset(tt.scriptPath)
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 := listNFTRuleset()
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
if err := flushNFTRuleset(); err != nil {
t.Fatalf("Failed to flush nftables ruleset: %v", err)
}
tt.goCommands(t, c)
// Retrieve nftables state using nft
actualOutput, err := listNFTRuleset()
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))
}
if err := flushNFTRuleset(); err != nil {
t.Fatalf("Failed to flush nftables ruleset: %v", err)
}
})
}
}
func applyNFTRuleset(scriptPath string) (string, error) {
cmd := exec.Command("nft", "--debug=all", "-f", scriptPath)
out, err := cmd.CombinedOutput()
if err != nil {
return string(out), err
}
return strings.TrimSpace(string(out)), nil
}
func listNFTRuleset() (string, error) {
cmd := exec.Command("nft", "list", "ruleset")
out, err := cmd.CombinedOutput()
if err != nil {
return string(out), err
}
return strings.TrimSpace(string(out)), nil
}
func flushNFTRuleset() error {
cmd := exec.Command("nft", "flush", "ruleset")
return cmd.Run()
}

View File

@ -1,5 +0,0 @@
table inet test-table {
chain test-chain {
type nat hook output priority dstnat; policy accept;
}
}

View File

@ -1,18 +0,0 @@
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
}
}

View File

@ -1,2 +0,0 @@
table inet test-table {
}

View File

@ -1457,7 +1457,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.Fatal(err.Error()) t.Fatalf(err.Error())
} }
} }
@ -1507,7 +1507,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.Fatal(err) t.Fatalf(err.Error())
} }
objs, err := conn.GetNamedObjects(table) objs, err := conn.GetNamedObjects(table)
@ -1652,7 +1652,7 @@ func TestCtTimeout(t *testing.T) {
}) })
if err := conn.Flush(); err != nil { if err := conn.Flush(); err != nil {
t.Fatal(err) t.Fatalf(err.Error())
} }
obj, err := conn.GetObject(ctt1) obj, err := conn.GetObject(ctt1)
@ -1708,7 +1708,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.Fatal(err) t.Fatalf(err.Error())
} }
objs, err := conn.GetNamedObjects(table) objs, err := conn.GetNamedObjects(table)
@ -1773,7 +1773,7 @@ func TestCtHelper(t *testing.T) {
}) })
if err := conn.Flush(); err != nil { if err := conn.Flush(); err != nil {
t.Fatal(err) t.Fatalf(err.Error())
} }
obj1, err := conn.GetObject(cthelp1) obj1, err := conn.GetObject(cthelp1)
@ -2605,7 +2605,7 @@ func TestGetResetNamedObj(t *testing.T) {
}) })
if err := c.Flush(); err != nil { if err := c.Flush(); err != nil {
t.Fatal(err) t.Fatalf(err.Error())
} }
objsNamed, err := c.GetNamedObjects(table) objsNamed, err := c.GetNamedObjects(table)
@ -2713,7 +2713,7 @@ func TestObjAPI(t *testing.T) {
}) })
if err := c.Flush(); err != nil { if err := c.Flush(); err != nil {
t.Fatal(err) t.Fatalf(err.Error())
} }
objs, err := c.GetObjects(table) objs, err := c.GetObjects(table)
@ -3064,7 +3064,7 @@ func TestObjAPICounterLegacyType(t *testing.T) {
}) })
if err := c.Flush(); err != nil { if err := c.Flush(); err != nil {
t.Fatal(err) t.Fatalf(err.Error())
} }
objs, err := c.GetObjects(table) objs, err := c.GetObjects(table)
@ -7831,74 +7831,3 @@ func TestNftablesDeadlock(t *testing.T) {
}) })
} }
} }
func TestSetElementComment(t *testing.T) {
// Create a new network namespace to test these operations
conn, newNS := nftest.OpenSystemConn(t, *enableSysTests)
defer nftest.CleanupSystemConn(t, newNS)
conn.FlushRuleset()
defer conn.FlushRuleset()
// Add a new table
table := &nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
}
conn.AddTable(table)
// Create a new set
set := &nftables.Set{
Name: "test-set",
Table: table,
KeyType: nftables.TypeIPAddr,
}
// Create set elements with comments
elements := []nftables.SetElement{
{
Key: net.ParseIP("192.0.2.1").To4(),
Comment: "First IP address",
},
{
Key: net.ParseIP("192.0.2.2").To4(),
Comment: "Second IP address",
},
}
// Add the set with elements
if err := conn.AddSet(set, elements); err != nil {
t.Fatalf("failed to add set: %v", err)
}
if err := conn.Flush(); err != nil {
t.Fatalf("failed to flush: %v", err)
}
// Get the set elements back and verify comments
gotElements, err := conn.GetSetElements(set)
if err != nil {
t.Fatalf("failed to get set elements: %v", err)
}
if got, want := len(gotElements), len(elements); got != want {
t.Fatalf("got %d elements, want %d", got, want)
}
// Create maps to compare elements by their IP addresses
wantMap := make(map[string]string)
for _, elem := range elements {
wantMap[string(elem.Key)] = elem.Comment
}
gotMap := make(map[string]string)
for _, elem := range gotElements {
gotMap[string(elem.Key)] = elem.Comment
}
// Compare the comments for each IP
for ip, wantComment := range wantMap {
if gotComment, ok := gotMap[ip]; !ok {
t.Errorf("IP %s not found in retrieved elements", ip)
} else if gotComment != wantComment {
t.Errorf("for IP %s: got comment %q, want comment %q", ip, gotComment, wantComment)
}
}
}

15
set.go
View File

@ -288,7 +288,6 @@ type SetElement struct {
Expires time.Duration Expires time.Duration
Counter *expr.Counter Counter *expr.Counter
Comment string
} }
func (s *SetElement) decode(fam byte) func(b []byte) error { func (s *SetElement) decode(fam byte) func(b []byte) error {
@ -323,12 +322,6 @@ func (s *SetElement) decode(fam byte) func(b []byte) error {
s.Timeout = time.Millisecond * time.Duration(ad.Uint64()) s.Timeout = time.Millisecond * time.Duration(ad.Uint64())
case unix.NFTA_SET_ELEM_EXPIRATION: case unix.NFTA_SET_ELEM_EXPIRATION:
s.Expires = time.Millisecond * time.Duration(ad.Uint64()) s.Expires = time.Millisecond * time.Duration(ad.Uint64())
case unix.NFTA_SET_ELEM_USERDATA:
userData := ad.Bytes()
// Try to extract comment from userdata if present
if comment, ok := userdata.GetString(userData, userdata.NFTNL_UDATA_SET_ELEM_COMMENT); ok {
s.Comment = comment
}
case unix.NFTA_SET_ELEM_EXPR: case unix.NFTA_SET_ELEM_EXPR:
elems, err := parseexprfunc.ParseExprBytesFunc(fam, ad) elems, err := parseexprfunc.ParseExprBytesFunc(fam, ad)
if err != nil { if err != nil {
@ -461,12 +454,6 @@ func (s *Set) makeElemList(vals []SetElement, id uint32) ([]netlink.Attribute, e
// If niether of previous cases matche, it means 'e' is an element of a regular Set, no need to add to the attributes // If niether of previous cases matche, it means 'e' is an element of a regular Set, no need to add to the attributes
} }
// Add comment to userdata if present
if len(v.Comment) > 0 {
userData := userdata.AppendString(nil, userdata.NFTNL_UDATA_SET_ELEM_COMMENT, v.Comment)
item = append(item, netlink.Attribute{Type: unix.NFTA_SET_ELEM_USERDATA, Data: userData})
}
encodedItem, err := netlink.MarshalAttributes(item) encodedItem, err := netlink.MarshalAttributes(item)
if err != nil { if err != nil {
return nil, fmt.Errorf("marshal item %d: %v", i, err) return nil, fmt.Errorf("marshal item %d: %v", i, err)
@ -820,7 +807,6 @@ func elementsFromMsg(fam byte, msg netlink.Message) ([]SetElement, error) {
b := ad.Bytes() b := ad.Bytes()
if ad.Type() == unix.NFTA_SET_ELEM_LIST_ELEMENTS { if ad.Type() == unix.NFTA_SET_ELEM_LIST_ELEMENTS {
ad, err := netlink.NewAttributeDecoder(b) ad, err := netlink.NewAttributeDecoder(b)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -832,7 +818,6 @@ func elementsFromMsg(fam byte, msg netlink.Message) ([]SetElement, error) {
case unix.NFTA_LIST_ELEM: case unix.NFTA_LIST_ELEM:
ad.Do(elem.decode(fam)) ad.Do(elem.decode(fam))
} }
elements = append(elements, elem) elements = append(elements, elem)
} }
} }

View File

@ -46,12 +46,6 @@ const (
NFTNL_UDATA_SET_MAX NFTNL_UDATA_SET_MAX
) )
// Set element userdata types
const (
NFTNL_UDATA_SET_ELEM_COMMENT Type = iota
NFTNL_UDATA_SET_ELEM_FLAGS
)
func Append(udata []byte, typ Type, data []byte) []byte { func Append(udata []byte, typ Type, data []byte) []byte {
udata = append(udata, byte(typ), byte(len(data))) udata = append(udata, byte(typ), byte(len(data)))
udata = append(udata, data...) udata = append(udata, data...)