Skip to content

Added fields to Nftable struct for better NFT_TABLE_NEW and GET functionality. #11833

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
Jul 15, 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
17 changes: 16 additions & 1 deletion pkg/abi/linux/nf_tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ package linux

// This file contains constants required to support nf_tables.

// Name length constants for nf_table structures. These correspond to values in
// include/uapi/linux/netfilter/nf_tables.h.
const (
NFT_NAME_MAXLEN = 256
NFT_TABLE_MAXNAMELEN = NFT_NAME_MAXLEN
NFT_CHAIN_MAXNAMELEN = NFT_NAME_MAXLEN
NFT_SET_MAXNAMELEN = NFT_NAME_MAXLEN
NFT_OBJ_MAXNAMELEN = NFT_NAME_MAXLEN
NFT_USERDATA_MAXLEN = 256
NFT_OSF_MAXGENRELEN = 16
)

// 16-byte Registers that can be used to maintain state for rules.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
const (
Expand Down Expand Up @@ -127,7 +139,10 @@ const (
// 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
NFT_TABLE_F_DORMANT uint32 = 0x1
NFT_TABLE_F_OWNER = 0x2
NFT_TABLE_F_PERSIST = 0x4
NFT_TABLE_F_MASK = NFT_TABLE_F_DORMANT | NFT_TABLE_F_OWNER | NFT_TABLE_F_PERSIST
)

// NfTableAttributes represents the netfilter table attributes.
Expand Down
1 change: 1 addition & 0 deletions pkg/sentry/socket/netlink/netfilter/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ go_library(
"//pkg/abi/linux",
"//pkg/context",
"//pkg/log",
"//pkg/marshal/primitive",
"//pkg/sentry/inet",
"//pkg/sentry/kernel",
"//pkg/sentry/socket/netlink",
Expand Down
102 changes: 85 additions & 17 deletions pkg/sentry/socket/netlink/netfilter/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/marshal/primitive"
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink"
Expand Down Expand Up @@ -93,7 +94,7 @@ func (p *Protocol) ProcessMessage(ctx context.Context, s *netlink.Socket, msg *n
// TODO: b/421437663 - Match the message type and call the appropriate Nftables function.
switch msgType {
case linux.NFT_MSG_NEWTABLE:
if err := p.newTable(nft, attrs, family, hdr.Flags); err != nil {
if err := p.newTable(nft, attrs, family, hdr.Flags, ms); err != nil {
log.Debugf("Nftables new table error: %s", err)
return err.GetError()
}
Expand All @@ -111,42 +112,93 @@ func (p *Protocol) ProcessMessage(ctx context.Context, s *netlink.Socket, msg *n
}

// 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.AnnotatedError {
func (p *Protocol) newTable(nft *nftables.NFTables, attrs map[uint16]nlmsg.BytesView, family stack.AddressFamily, flags uint16, ms *nlmsg.MessageSet) *syserr.AnnotatedError {
// 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 {
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("Nftables: Table name attribute is malformed or not found"))
}

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())
tab, err := nft.GetTable(family, tabNameBytes.String(), uint32(ms.PortID))
if err != nil && err.GetError() != syserr.ErrNoFileOrDir {
return err
}

// 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 {
if flags&linux.NLM_F_EXCL == linux.NLM_F_EXCL {
return syserr.NewAnnotatedError(syserr.ErrExists, fmt.Sprintf("Nftables: Table with name: %s already exists", tabNameBytes.String()))
if flags&linux.NLM_F_EXCL != 0 {
return syserr.NewAnnotatedError(syserr.ErrExists, fmt.Sprintf("Nftables: Table with name: %s already exists", tab.GetName()))
}

if flags&linux.NLM_F_REPLACE != 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("Nftables: Table with name: %s already exists and NLM_F_REPLACE is not supported", tab.GetName()))
}

return p.updateTable(nft, tab, attrs, family, ms)
}

// TODO: b/421437663 - Support additional user-specified table flags.
var attrFlags uint32 = 0
if uflags, ok := attrs[linux.NFTA_TABLE_FLAGS]; ok {
attrFlags, _ = uflags.Uint32()
// Flags sent through the NFTA_TABLE_FLAGS attribute are of type uint32
// but should only have user flags set. This check needs to be done before table creation.
if attrFlags & ^uint32(linux.NFT_TABLE_F_MASK) != 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("Nftables: Table flags set are not supported"))
}
}

tab, err = nft.CreateTable(family, tabNameBytes.String())
if err != nil {
return err
}

if udata, ok := attrs[linux.NFTA_TABLE_USERDATA]; ok {
tab.SetUserData(udata)
}

// Flags should only be assigned after we have successfully created the table.
dormant := (attrFlags & uint32(linux.NFT_TABLE_F_DORMANT)) != 0
tab.SetDormant(dormant)

owner := (attrFlags & uint32(linux.NFT_TABLE_F_OWNER)) != 0
if owner {
if err := tab.SetOwner(uint32(ms.PortID)); err != nil {
return err
}
}

if flags&linux.NLM_F_REPLACE == linux.NLM_F_REPLACE {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("Nftables: Table with name: %s already exists and NLM_F_REPLACE is not supported", tabNameBytes.String()))
return nil
}

// updateTable updates an existing table.
func (p *Protocol) updateTable(nft *nftables.NFTables, tab *nftables.Table, attrs map[uint16]nlmsg.BytesView, family stack.AddressFamily, ms *nlmsg.MessageSet) *syserr.AnnotatedError {
var attrFlags uint32
if uflags, ok := attrs[linux.NFTA_TABLE_FLAGS]; ok {
attrFlags, _ = uflags.Uint32()
// This check needs to be done before table update.
if attrFlags & ^uint32(linux.NFT_TABLE_F_MASK) > 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("Nftables: Table flags set are not supported"))
}
} else {
tab, err = nft.CreateTable(family, tabNameBytes.String())
if err != nil {
}

// When updating the table, if the table has an owner but the owner flag isn't set,
// the table should not be updated.
// From net/netfilter/nf_tables_api.c:nf_tables_updtable.
if tab.HasOwner() && (attrFlags&uint32(linux.NFT_TABLE_F_OWNER)) == 0 {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("Nftables: Table with name: %s already has an owner but NFT_TABLE_F_OWNER was not set when updating the table", tab.GetName()))
}

// The owner is only updated if the table has no previous owner.
if !tab.HasOwner() && attrFlags&uint32(linux.NFT_TABLE_F_OWNER) != 0 {
if err := tab.SetOwner(uint32(ms.PortID)); err != nil {
return err
}
}

dormant := (attrFlags & uint32(linux.NFT_TABLE_F_DORMANT)) != 0
tab.SetDormant(dormant)
return nil
}
Expand All @@ -159,12 +211,16 @@ func (p *Protocol) getTable(nft *nftables.NFTables, attrs map[uint16]nlmsg.Bytes
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("Nftables: Table name attribute is malformed or not found"))
}

tab, err := nft.GetTable(family, tabNameBytes.String())
tab, err := nft.GetTable(family, tabNameBytes.String(), uint32(ms.PortID))
if err != nil {
return err
}

tabName := tab.GetName()
userFlags, err := tab.GetLinuxUserFlagSet()
if err != nil {
return err
}
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: uint16(linux.NFNL_SUBSYS_NFTABLES)<<8 | uint16(linux.NFT_MSG_GETTABLE),
})
Expand All @@ -176,6 +232,18 @@ func (p *Protocol) getTable(nft *nftables.NFTables, attrs map[uint16]nlmsg.Bytes
ResourceID: uint16(0),
})
m.PutAttrString(linux.NFTA_TABLE_NAME, tabName)
m.PutAttr(linux.NFTA_TABLE_USE, primitive.AllocateUint32(uint32(tab.ChainCount())))
m.PutAttr(linux.NFTA_TABLE_HANDLE, primitive.AllocateUint64(tab.GetHandle()))
m.PutAttr(linux.NFTA_TABLE_FLAGS, primitive.AllocateUint8(userFlags))

if tab.HasOwner() {
m.PutAttr(linux.NFTA_TABLE_OWNER, primitive.AllocateUint32(tab.GetOwner()))
}

if tab.HasUserData() {
m.PutAttr(linux.NFTA_TABLE_USERDATA, primitive.AsByteSlice(tab.GetUserData()))
}

return nil
}

Expand Down
104 changes: 98 additions & 6 deletions pkg/tcpip/nftables/nftables.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"slices"

"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/rand"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/tcpip"
Expand Down Expand Up @@ -241,7 +242,7 @@ func NewNFTables(clock tcpip.Clock, rng rand.RNG) *NFTables {
if rng.Reader == nil {
panic("nftables state must be initialized with a non-nil random number generator")
}
return &NFTables{clock: clock, startTime: clock.Now(), rng: rng}
return &NFTables{clock: clock, startTime: clock.Now(), rng: rng, tableHandleCounter: atomicbitops.Uint64{}}
}

// Flush clears entire ruleset and all data for all address families.
Expand All @@ -264,7 +265,7 @@ func (nf *NFTables) FlushAddressFamily(family stack.AddressFamily) *syserr.Annot
}

// GetTable validates the inputs and gets a table if it exists, error otherwise.
func (nf *NFTables) GetTable(family stack.AddressFamily, tableName string) (*Table, *syserr.AnnotatedError) {
func (nf *NFTables) GetTable(family stack.AddressFamily, tableName string, portID uint32) (*Table, *syserr.AnnotatedError) {
// Ensures address family is valid.
if err := validateAddressFamily(family); err != nil {
return nil, err
Expand All @@ -284,6 +285,13 @@ func (nf *NFTables) GetTable(family stack.AddressFamily, tableName string) (*Tab
return nil, syserr.NewAnnotatedError(syserr.ErrNoFileOrDir, fmt.Sprintf("table %s not found for address family %v", tableName, family))
}

// If the table has an owner, it must match the Netlink portID of the calling process.
// User space processes only have non-zero port ids.
// Only the kernel can have a zero port id.
if t.HasOwner() && portID != 0 && portID != t.GetOwner() {
return nil, syserr.NewAnnotatedError(syserr.ErrNotPermitted, fmt.Sprintf("table %s has owner %d, which does not match the Netlink portID of the calling process %d", tableName, t.GetOwner(), portID))
}

return t, nil
}

Expand Down Expand Up @@ -329,12 +337,18 @@ func (nf *NFTables) AddTable(family stack.AddressFamily, name string,
afFilter: nf.filters[family],
chains: make(map[string]*Chain),
flagSet: make(map[TableFlag]struct{}),
handle: nf.getNewTableHandle(),
}
tableMap[name] = t

return t, nil
}

// getNewTableHandle returns a new table handle for the NFTables object.
func (nf *NFTables) getNewTableHandle() uint64 {
return nf.tableHandleCounter.Add(1)
}

// CreateTable makes a new table for the specified address family like AddTable
// 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
Expand All @@ -353,7 +367,7 @@ func (nf *NFTables) DeleteTable(family stack.AddressFamily, tableName string) (b
}

// Gets and checks the table.
t, err := nf.GetTable(family, tableName)
t, err := nf.GetTable(family, tableName, 0)
if err != nil {
return false, err
}
Expand All @@ -371,7 +385,7 @@ func (nf *NFTables) DeleteTable(family stack.AddressFamily, tableName string) (b
// GetChain validates the inputs and gets a chain if it exists, error otherwise.
func (nf *NFTables) GetChain(family stack.AddressFamily, tableName string, chainName string) (*Chain, *syserr.AnnotatedError) {
// Gets and checks the table.
t, err := nf.GetTable(family, tableName)
t, err := nf.GetTable(family, tableName, 0)
if err != nil {
return nil, err
}
Expand All @@ -389,7 +403,7 @@ func (nf *NFTables) GetChain(family stack.AddressFamily, tableName string, chain
// Note: if the chain is not a base chain, info should be nil.
func (nf *NFTables) AddChain(family stack.AddressFamily, tableName string, chainName string, info *BaseChainInfo, comment string, errorOnDuplicate bool) (*Chain, *syserr.AnnotatedError) {
// Gets and checks the table.
t, err := nf.GetTable(family, tableName)
t, err := nf.GetTable(family, tableName, 0)
if err != nil {
return nil, err
}
Expand All @@ -411,7 +425,7 @@ func (nf *NFTables) CreateChain(family stack.AddressFamily, tableName string, ch
// an error if the address family is invalid or the table doesn't exist.
func (nf *NFTables) DeleteChain(family stack.AddressFamily, tableName string, chainName string) (bool, *syserr.AnnotatedError) {
// Gets and checks the table.
t, err := nf.GetTable(family, tableName)
t, err := nf.GetTable(family, tableName, 0)
if err != nil {
return false, err
}
Expand All @@ -438,6 +452,56 @@ func (t *Table) GetAddressFamily() stack.AddressFamily {
return t.afFilter.family
}

// GetHandle returns the handle of the table.
func (t *Table) GetHandle() uint64 {
return t.handle
}

// GetOwner returns the owner of the table.
func (t *Table) GetOwner() uint32 {
return t.owner
}

// SetOwner sets the owner of the table. If the table already has an owner, it
// is not updated.
func (t *Table) SetOwner(nlpid uint32) *syserr.AnnotatedError {
// This should only be called once, when setting the owner of a table for the first time.
if t.HasOwner() {
return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("table %s already has an owner", t.name))
}

t.flagSet[TableFlagOwner] = struct{}{}
t.owner = nlpid
return nil
}

// HasOwner returns whether the table has an owner.
func (t *Table) HasOwner() bool {
_, ok := t.flagSet[TableFlagOwner]
return ok
}

// GetUserData returns the user data of the table.
func (t *Table) GetUserData() []byte {
return t.userData
}

// HasUserData returns whether the table has user data.
func (t *Table) HasUserData() bool {
return t.userData != nil
}

// SetUserData sets the user data of the table.
func (t *Table) SetUserData(data []byte) {
// User data should only be set once.
if t.userData != nil {
return
}

t.userData = make([]byte, len(data))
copy(t.userData, data)
}

// IsDormant returns whether the table is dormant.
func (t *Table) IsDormant() bool {
_, dormant := t.flagSet[TableFlagDormant]
Expand All @@ -453,6 +517,34 @@ func (t *Table) SetDormant(dormant bool) {
}
}

// GetLinuxFlagSet returns the flag set of the table.
// Although user flags map to uint8 space, internal flags could eventually be
// supported, which together map to a uint32 space.
func (t *Table) GetLinuxFlagSet() (uint32, *syserr.AnnotatedError) {
var flags uint32 = 0
for flag := range t.flagSet {
switch flag {
case TableFlagDormant:
flags |= linux.NFT_TABLE_F_DORMANT
case TableFlagOwner:
flags |= linux.NFT_TABLE_F_OWNER
default:
return 0, syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("unsupported flag %v", flag))
}
}

return flags, nil
}

// GetLinuxUserFlagSet returns the user flag set of the table.
func (t *Table) GetLinuxUserFlagSet() (uint8, *syserr.AnnotatedError) {
flags, err := t.GetLinuxFlagSet()
if err != nil {
return 0, err
}
return uint8(flags & linux.NFT_TABLE_F_MASK), nil
}

// GetChain returns the chain with the specified name if it exists, error
// otherwise.
func (t *Table) GetChain(chainName string) (*Chain, *syserr.AnnotatedError) {
Expand Down
Loading