Files
netbird/client/firewall/nftables/interface_allower_integration_linux_test.go
Viktor Liu 778b3b3264 [client] Unify peer and route ACL filtering with multi-source rules (#6322)
* Unify peer and route ACL filtering with multi-source peer rules

* Remove partial userspace firewall mode and open foreign chains via a table-less allower

* Snapshot iptables rule maps before persisting state

* Scope userspace firewall wildcard source rules per address family

* Install nftables peer filter and mangle rules in a single transaction

* Share the iptables jump rule spec between install and cleanup

* Fix legacy ACL source wildcard and keep rollback tracking on delete failure

* Fix CI: recognize multi-value port set lookups in tests and correct PeerIP lint suppression

* Fall back to per-prefix filter rules when ipset is unavailable

* Annotate legacy PeerIP usages in ACL tests and fix import formatting

* Keep firewall rule bookkeeping in step with the kernel on replace and teardown

* Release the routing reference when the route manager shuts down

* Keep set references and rule tracking consistent when a routing rule fails
2026-09-03 10:02:50 +02:00

91 lines
2.8 KiB
Go

//go:build privileged
package nftables
import (
"bytes"
"os"
"testing"
"github.com/google/nftables"
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/client/iface"
)
// TestInterfaceAllowerInputOnly verifies the userspace-mode allower opens the
// interface on the INPUT hook of foreign chains only (not FORWARD, since the
// userspace router never forwards in the kernel), creates no netbird work
// table, and removes its rules on Close.
func TestInterfaceAllowerInputOnly(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip("root required")
}
require.False(t, ipTableExists(t, getTableName()), "precondition: no stale netbird table")
conn := &nftables.Conn{}
extTable := conn.AddTable(&nftables.Table{Name: "nbtest_extchains", Family: nftables.TableFamilyINet})
inputChain := conn.AddChain(&nftables.Chain{
Name: "ext_input", Table: extTable,
Hooknum: nftables.ChainHookInput, Priority: nftables.ChainPriorityFilter, Type: nftables.ChainTypeFilter,
})
forwardChain := conn.AddChain(&nftables.Chain{
Name: "ext_forward", Table: extTable,
Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, Type: nftables.ChainTypeFilter,
})
require.NoError(t, conn.Flush(), "create external table and chains")
t.Cleanup(func() {
c := &nftables.Conn{}
c.DelTable(extTable)
_ = c.Flush()
})
allower, err := NewInterfaceAllower(ifaceMock, iface.DefaultMTU)
require.NoError(t, err, "create allower")
require.NoError(t, allower.Apply(), "apply")
require.True(t, chainHasUserData(t, extTable, inputChain, userDataAcceptInputRule),
"external INPUT chain should get the accept rule")
require.Len(t, listRules(t, extTable, forwardChain), 0,
"external FORWARD chain must not be opened in userspace mode")
require.False(t, ipTableExists(t, getTableName()),
"allower must not create a netbird work table")
require.NoError(t, allower.Close(), "close")
require.False(t, chainHasUserData(t, extTable, inputChain, userDataAcceptInputRule),
"accept rule should be removed on close")
}
func listRules(t *testing.T, table *nftables.Table, chain *nftables.Chain) []*nftables.Rule {
t.Helper()
c := &nftables.Conn{}
rules, err := c.GetRules(table, chain)
require.NoError(t, err)
return rules
}
func chainHasUserData(t *testing.T, table *nftables.Table, chain *nftables.Chain, ud string) bool {
for _, r := range listRules(t, table, chain) {
if bytes.Equal(r.UserData, []byte(ud)) {
return true
}
}
return false
}
func ipTableExists(t *testing.T, name string) bool {
t.Helper()
c := &nftables.Conn{}
for _, fam := range []nftables.TableFamily{nftables.TableFamilyIPv4, nftables.TableFamilyIPv6} {
tbls, err := c.ListTablesOfFamily(fam)
require.NoError(t, err)
for _, tb := range tbls {
if tb.Name == name {
return true
}
}
}
return false
}