Skip to content

Netlink_Netfilter process message implementation for adding and getting tables. #11818

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkg/abi/linux/netlink_netfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const (
)

// NetFilterGenMsg describes the netlink netfilter genmsg message, from uapi/linux/netfilter/nfnetlink.h.
//
// +marshal
type NetFilterGenMsg struct {
Family uint8
Version uint8
Expand Down
23 changes: 23 additions & 0 deletions pkg/abi/linux/nf_tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,29 @@ const (
NFT_MSG_MAX
)

// NfTableFlags represents table flags that can be set for a table, namely dormant.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
const (
NFT_TABLE_F_DORMANT = 0x1
)

// NfTableAttributes represents the netfilter table attributes.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
const (
NFTA_TABLE_UNSPEC uint16 = iota
NFTA_TABLE_NAME
NFTA_TABLE_FLAGS
NFTA_TABLE_USE
NFTA_TABLE_HANDLE
NFTA_TABLE_PAD
NFTA_TABLE_USERDATA
NFTA_TABLE_OWNER
__NFTA_TABLE_MAX
)

// NFTA_TABLE_MAX is the maximum netfilter table attribute.
const NFTA_TABLE_MAX = __NFTA_TABLE_MAX - 1

// Nf table relational operators.
// Used by the nft comparison operation to compare values in registers.
// These correspond to enum values in include/uapi/linux/netfilter/nf_tables.h.
Expand Down
4 changes: 4 additions & 0 deletions pkg/sentry/socket/netlink/netfilter/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ go_library(
deps = [
"//pkg/abi/linux",
"//pkg/context",
"//pkg/log",
"//pkg/sentry/inet",
"//pkg/sentry/kernel",
"//pkg/sentry/socket/netlink",
"//pkg/sentry/socket/netlink/nlmsg",
"//pkg/sentry/socket/netstack",
"//pkg/syserr",
"//pkg/tcpip/nftables",
"//pkg/tcpip/stack",
],
)
110 changes: 107 additions & 3 deletions pkg/sentry/socket/netlink/netfilter/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ package netfilter
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
"gvisor.dev/gvisor/pkg/sentry/socket/netstack"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/tcpip/nftables"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)

// Protocol implements netlink.Protocol.
Expand Down Expand Up @@ -60,22 +64,122 @@ func (p *Protocol) ProcessMessage(ctx context.Context, s *netlink.Socket, msg *n
// Netlink message payloads must be of at least the size of the genmsg. Return early if it is not,
// from linux/net/netfilter/nfnetlink.c.
if netLinkMessagePayloadSize(&hdr) < linux.SizeOfNetfilterGenMsg {
log.Debugf("Netlink message payload is too small: %d < %d", netLinkMessagePayloadSize(&hdr), linux.SizeOfNetfilterGenMsg)
return nil
}

msgType := hdr.NetFilterMsgType()
st := inet.StackFromContext(ctx).(*netstack.Stack).Stack
nft := (st.NFTables()).(*nftables.NFTables)
var nfGenMsg linux.NetFilterGenMsg

// The payload of a message is its attributes.
atr, ok := msg.GetData(&nfGenMsg)
if !ok {
log.Debugf("Failed to get message data")
return syserr.ErrInvalidArgument
}

attrs, ok := atr.Parse()
if !ok {
log.Debugf("Failed to parse message attributes")
return syserr.ErrInvalidArgument
}

// Nftables functions error check the address family value.
family := stack.AddressFamily(nfGenMsg.Family)
// TODO: b/421437663 - Match the message type and call the appropriate Nftables function.
switch msgType {
case linux.NFT_MSG_NEWTABLE:
return p.newTable(nft, attrs, family, hdr.Flags)
case linux.NFT_MSG_GETTABLE:
return p.getTable(nft, attrs, family, hdr.Flags, ms)
default:
log.Debugf("Unsupported message type: %d", msgType)
return syserr.ErrInvalidArgument
}
}

// init registers the NETLINK_NETFILTER provider.
func init() {
netlink.RegisterProvider(linux.NETLINK_NETFILTER, NewProtocol)
// newTable creates a new table for the given family.
func (p *Protocol) newTable(nft *nftables.NFTables, attrs map[uint16]nlmsg.BytesView, family stack.AddressFamily, flags uint16) *syserr.Error {
// TODO: b/421437663 - Handle the case where the table name is set to empty string.
// The table name is required.
tabNameBytes, ok := attrs[linux.NFTA_TABLE_NAME]
if !ok {
log.Debugf("Nftables: Table name attribute is malformed or not found")
return syserr.ErrInvalidArgument
}

var dormant bool
if dbytes, ok := attrs[linux.NFTA_TABLE_FLAGS]; ok {
dflag, _ := dbytes.Uint32()
dormant = (dflag & linux.NFT_TABLE_F_DORMANT) == linux.NFT_TABLE_F_DORMANT
}

tab, err := nft.GetTable(family, tabNameBytes.String())

// If a table already exists, only update its dormant flags if NLM_F_EXCL and NLM_F_REPLACE
// are not set. From net/netfilter/nf_tables_api.c:nf_tables_newtable:nf_tables_updtable
if tab != nil && err == nil {
if flags&linux.NLM_F_EXCL == linux.NLM_F_EXCL {
log.Debugf("Nftables: Table with name: %s already exists", tabNameBytes.String())
return syserr.ErrExists
}

if flags&linux.NLM_F_REPLACE == linux.NLM_F_REPLACE {
log.Debugf("Nftables: Table with name: %s already exists and NLM_F_REPLACE is not supported", tabNameBytes.String())
return syserr.ErrNotSupported
}
} else {
// There does not seem to be a way to add comments to a table using the nft binary.
tab, err = nft.CreateTable(family, tabNameBytes.String())
if err != nil {
log.Debugf("Nftables: Failed to create table with name: %s. Error: %s", tabNameBytes.String(), err.Error())
// If there is an error, it is not a duplicate error (checked above).
return syserr.ErrInvalidArgument
}
}

tab.SetDormant(dormant)
return nil
}

// getTable returns a table for the given family. Returns nil on success and
// a sys.error on failure.
func (p *Protocol) getTable(nft *nftables.NFTables, attrs map[uint16]nlmsg.BytesView, family stack.AddressFamily, flags uint16, ms *nlmsg.MessageSet) *syserr.Error {
// The table name is required.
tabNameBytes, ok := attrs[linux.NFTA_TABLE_NAME]
if !ok {
log.Debugf("Nftables: Table name attribute is malformed or not found")
return syserr.ErrInvalidArgument
}

tab, err := nft.GetTable(family, tabNameBytes.String())
if err != nil {
log.Debugf("Nftables: ENOENT for table with name: %s", tabNameBytes.String())
return syserr.ErrNoFileOrDir
}

tabName := tab.GetName()
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: uint16(linux.NFNL_SUBSYS_NFTABLES)<<8 | uint16(linux.NFT_MSG_GETTABLE),
})

m.Put(&linux.NetFilterGenMsg{
Family: uint8(family),
Version: uint8(linux.NFNETLINK_V0),
// Unused, set to 0.
ResourceID: uint16(0),
})
m.PutAttrString(linux.NFTA_TABLE_NAME, tabName)
return nil
}

func netLinkMessagePayloadSize(h *linux.NetlinkMessageHeader) int {
return int(h.Length) - linux.NetlinkMessageHeaderSize
}

// init registers the NETLINK_NETFILTER provider.
func init() {
netlink.RegisterProvider(linux.NETLINK_NETFILTER, NewProtocol)
}
19 changes: 5 additions & 14 deletions pkg/tcpip/nftables/nftables.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/stack"
)

// TODO: b/421437663 - Refactor functions to return a POSIX syserr

//
// Interface-Related Methods
//
Expand Down Expand Up @@ -291,7 +293,7 @@ func (nf *NFTables) GetTable(family stack.AddressFamily, tableName string) (*Tab
// Note: if the table already exists, the existing table is returned without any
// modifications.
// Note: Table initialized as not dormant.
func (nf *NFTables) AddTable(family stack.AddressFamily, name string, comment string,
func (nf *NFTables) AddTable(family stack.AddressFamily, name string,
errorOnDuplicate bool) (*Table, error) {
// Ensures address family is valid.
if err := validateAddressFamily(family); err != nil {
Expand Down Expand Up @@ -325,7 +327,6 @@ func (nf *NFTables) AddTable(family stack.AddressFamily, name string, comment st
name: name,
afFilter: nf.filters[family],
chains: make(map[string]*Chain),
comment: comment,
flagSet: make(map[TableFlag]struct{}),
}
tableMap[name] = t
Expand All @@ -337,8 +338,8 @@ func (nf *NFTables) AddTable(family stack.AddressFamily, name string, comment st
// but also returns an error if a table by the same name already exists.
// Note: this interface mirrors the difference between the create and add
// commands within the nft binary.
func (nf *NFTables) CreateTable(family stack.AddressFamily, name string, comment string) (*Table, error) {
return nf.AddTable(family, name, comment, true)
func (nf *NFTables) CreateTable(family stack.AddressFamily, name string) (*Table, error) {
return nf.AddTable(family, name, true)
}

// DeleteTable deletes the specified table from the NFTables object returning
Expand Down Expand Up @@ -436,16 +437,6 @@ func (t *Table) GetAddressFamily() stack.AddressFamily {
return t.afFilter.family
}

// GetComment returns the comment of the table.
func (t *Table) GetComment() string {
return t.comment
}

// SetComment sets the comment of the table.
func (t *Table) SetComment(comment string) {
t.comment = comment
}

// IsDormant returns whether the table is dormant.
func (t *Table) IsDormant() bool {
_, dormant := t.flagSet[TableFlagDormant]
Expand Down
30 changes: 15 additions & 15 deletions pkg/tcpip/nftables/nftables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ func TestEvaluateImmediateVerdict(t *testing.T) {
// Sets up an NFTables object with a base chain (for 2 rules) and another
// target chain (for 1 rule).
nf := newNFTablesStd()
tab, err := nf.AddTable(arbitraryFamily, "test", "test table", false)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -592,7 +592,7 @@ func TestEvaluateImmediateBytesData(t *testing.T) {
t.Run(tname, func(t *testing.T) {
// Sets up an NFTables object with a base chain with policy accept.
nf := newNFTablesStd()
tab, err := nf.AddTable(arbitraryFamily, "test", "test table", false)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -1078,7 +1078,7 @@ func TestEvaluateComparison(t *testing.T) {
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)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -1315,7 +1315,7 @@ func TestEvaluateRanged(t *testing.T) {
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)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -1549,7 +1549,7 @@ func TestEvaluatePayloadLoad(t *testing.T) {
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)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -2053,7 +2053,7 @@ func TestEvaluatePayloadSet(t *testing.T) {
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)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -2238,7 +2238,7 @@ func TestEvaluateBitwise(t *testing.T) {
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)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -2304,7 +2304,7 @@ func TestEvaluateCounter(t *testing.T) {
t.Run("counter increment tests", func(t *testing.T) {
// Sets up an NFTables object with a base chain with policy accept.
nf := newNFTablesStd()
tab, err := nf.AddTable(arbitraryFamily, "test", "test table", false)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -2372,7 +2372,7 @@ func TestEvaluateLast(t *testing.T) {
fakeClock := faketime.NewManualClock()
fixedRNG := rand.RNGFrom(&fixedReader{})
nf := NewNFTables(fakeClock, fixedRNG)
tab, err := nf.AddTable(arbitraryFamily, "test", "test table", false)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -2499,7 +2499,7 @@ func TestEvaluateRoute(t *testing.T) {
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)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -2740,7 +2740,7 @@ func TestEvaluateByteorder(t *testing.T) {
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)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -2922,7 +2922,7 @@ func TestEvaluateMetaLoad(t *testing.T) {
// Using Manual Clock sets time.Now to Unix Epoch which fixes rng seed!
nf := NewNFTables(fakeClock, rand.RNGFrom(&fixedReader{}))

tab, err := nf.AddTable(arbitraryFamily, "test", "test table", false)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -3012,7 +3012,7 @@ func TestEvaluateMetaSet(t *testing.T) {
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)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -3470,7 +3470,7 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) {
t.Run(test.tname, func(t *testing.T) {
// Sets up an NFTables object based on test struct.
nf := newNFTablesStd()
tab, err := nf.AddTable(arbitraryFamily, "test", "test table", false)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down Expand Up @@ -3581,7 +3581,7 @@ func TestMaxNestedJumps(t *testing.T) {
t.Run(test.tname, func(t *testing.T) {
// Sets up chains of nested jumps or gotos.
nf := newNFTablesStd()
tab, err := nf.AddTable(arbitraryFamily, "test", "test table", false)
tab, err := nf.AddTable(arbitraryFamily, "test", false)
if err != nil {
t.Fatalf("unexpected error for AddTable: %v", err)
}
Expand Down
3 changes: 0 additions & 3 deletions pkg/tcpip/nftables/nftables_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,6 @@ type Table struct {
// flags is the set of optional flags for the table.
// Note: currently nftables only has the single Dormant flag.
flagSet map[TableFlag]struct{}

// comment is the optional comment for the table.
comment string
}

// hookFunctionStack represents the list of base chains for a specific hook.
Expand Down
Loading
Loading