From f5af7c2a9b053fafc417301bffb0d4ac7e3020b2 Mon Sep 17 00:00:00 2001 From: Alexander Cueva Date: Fri, 13 Jun 2025 10:20:02 -0700 Subject: [PATCH] Isolate NETLINK_NETFILTER socket behavior behind the nftables flag in runsc. PiperOrigin-RevId: 771139318 --- pkg/sentry/inet/inet.go | 6 + pkg/sentry/inet/test_stack.go | 12 ++ pkg/sentry/socket/hostinet/stack.go | 10 + pkg/sentry/socket/netlink/BUILD | 1 + pkg/sentry/socket/netlink/provider.go | 5 +- pkg/sentry/socket/netstack/stack.go | 14 ++ pkg/tcpip/nftables/nft_comparison.go | 2 +- pkg/tcpip/nftables/nft_metaload.go | 4 +- pkg/tcpip/nftables/nft_metaset.go | 2 +- pkg/tcpip/nftables/nft_payload_load.go | 2 +- pkg/tcpip/nftables/nft_payload_set.go | 2 +- pkg/tcpip/nftables/nft_ranged.go | 2 +- pkg/tcpip/nftables/nft_route.go | 2 +- pkg/tcpip/nftables/nftables.go | 95 +++++++-- pkg/tcpip/nftables/nftables_test.go | 276 ++++++++++++------------- pkg/tcpip/nftables/nftables_types.go | 268 +++++++----------------- pkg/tcpip/nftables/nftinterp.go | 5 +- pkg/tcpip/nftables/nftinterp_test.go | 13 +- pkg/tcpip/stack/BUILD | 1 + pkg/tcpip/stack/nftables_types.go | 165 +++++++++++++++ pkg/tcpip/stack/stack.go | 19 ++ runsc/boot/loader.go | 12 +- 22 files changed, 549 insertions(+), 369 deletions(-) create mode 100644 pkg/tcpip/stack/nftables_types.go diff --git a/pkg/sentry/inet/inet.go b/pkg/sentry/inet/inet.go index 8b4ef8848e..f259567443 100644 --- a/pkg/sentry/inet/inet.go +++ b/pkg/sentry/inet/inet.go @@ -138,6 +138,12 @@ type Stack interface { // IsSaveRestoreEnabled returns true when netstack s/r is enabled. IsSaveRestoreEnabled() bool + // EnableNFTables enables nftables support for the stack. + EnableNFTables() error + + // IsNFTablesEnabled returns true when nftables support is enabled. + IsNFTablesEnabled() bool + // Stats returns the network stats. Stats() tcpip.Stats } diff --git a/pkg/sentry/inet/test_stack.go b/pkg/sentry/inet/test_stack.go index 26d3f61651..cd4d44dd8b 100644 --- a/pkg/sentry/inet/test_stack.go +++ b/pkg/sentry/inet/test_stack.go @@ -236,6 +236,18 @@ func (*TestStack) IsSaveRestoreEnabled() bool { return false } +// EnableNFTables implements Stack. +func (*TestStack) EnableNFTables() error { + // No-op. + return nil +} + +// IsNFTablesEnabled implements Stack. +func (*TestStack) IsNFTablesEnabled() bool { + // No-op. + return false +} + // Stats implements Stack. func (*TestStack) Stats() tcpip.Stats { // No-op. diff --git a/pkg/sentry/socket/hostinet/stack.go b/pkg/sentry/socket/hostinet/stack.go index 4d1facb0af..98da60ccca 100644 --- a/pkg/sentry/socket/hostinet/stack.go +++ b/pkg/sentry/socket/hostinet/stack.go @@ -439,6 +439,16 @@ func (s *Stack) IsSaveRestoreEnabled() bool { return false } +// EnableNFTables implements inet.Stack.EnableNFTables. +func (s *Stack) EnableNFTables() error { + return fmt.Errorf("nftables is not supported for hostinet") +} + +// IsNFTablesEnabled implements inet.Stack.IsNFTablesEnabled. +func (s *Stack) IsNFTablesEnabled() bool { + return false +} + // Stats implements inet.Stack.Stats. func (s *Stack) Stats() tcpip.Stats { return tcpip.Stats{} diff --git a/pkg/sentry/socket/netlink/BUILD b/pkg/sentry/socket/netlink/BUILD index 997e43728e..1e1ec0d722 100644 --- a/pkg/sentry/socket/netlink/BUILD +++ b/pkg/sentry/socket/netlink/BUILD @@ -29,6 +29,7 @@ go_library( "//pkg/sentry/socket", "//pkg/sentry/socket/netlink/nlmsg", "//pkg/sentry/socket/netlink/port", + "//pkg/sentry/socket/netstack", "//pkg/sentry/socket/unix", "//pkg/sentry/socket/unix/transport", "//pkg/sentry/vfs", diff --git a/pkg/sentry/socket/netlink/provider.go b/pkg/sentry/socket/netlink/provider.go index 071dda67cf..2f1a793ade 100644 --- a/pkg/sentry/socket/netlink/provider.go +++ b/pkg/sentry/socket/netlink/provider.go @@ -20,9 +20,11 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs" + "gvisor.dev/gvisor/pkg/sentry/inet" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/socket" "gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg" + "gvisor.dev/gvisor/pkg/sentry/socket/netstack" "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/syserr" ) @@ -81,8 +83,9 @@ func (*socketProvider) Socket(t *kernel.Task, stype linux.SockType, protocol int return nil, syserr.ErrSocketNotSupported } + nftEnabled := inet.StackFromContext(t.Kernel().SupervisorContext()).(*netstack.Stack).Stack.IsNFTablesEnabled() provider, ok := protocols[protocol] - if !ok { + if !ok || (!nftEnabled && protocol == linux.NETLINK_NETFILTER) { return nil, syserr.ErrProtocolNotSupported } diff --git a/pkg/sentry/socket/netstack/stack.go b/pkg/sentry/socket/netstack/stack.go index 1d69aa3dab..a266e7f1b9 100644 --- a/pkg/sentry/socket/netstack/stack.go +++ b/pkg/sentry/socket/netstack/stack.go @@ -57,6 +57,20 @@ func (s *Stack) IsSaveRestoreEnabled() bool { return s.Stack.IsSaveRestoreEnabled() } +// EnableNFTables enables nftables support for the stack. +func (s *Stack) EnableNFTables() error { + s.Stack.EnableNFTables() + return nil +} + +// IsNFTablesEnabled implements inet.Stack.IsNFTablesEnabled. +func (s *Stack) IsNFTablesEnabled() bool { + if s.Stack == nil { + return false + } + return s.Stack.IsNFTablesEnabled() +} + // Destroy implements inet.Stack.Destroy. func (s *Stack) Destroy() { s.Stack.Close() diff --git a/pkg/tcpip/nftables/nft_comparison.go b/pkg/tcpip/nftables/nft_comparison.go index 154f175809..5a33912e61 100644 --- a/pkg/tcpip/nftables/nft_comparison.go +++ b/pkg/tcpip/nftables/nft_comparison.go @@ -111,6 +111,6 @@ func (op comparison) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule * } if !result { // Comparison is false, so break from the rule. - regs.verdict = Verdict{Code: VC(linux.NFT_BREAK)} + regs.verdict = stack.NFVerdict{Code: VC(linux.NFT_BREAK)} } } diff --git a/pkg/tcpip/nftables/nft_metaload.go b/pkg/tcpip/nftables/nft_metaload.go index d280018820..e5cce0f6ab 100644 --- a/pkg/tcpip/nftables/nft_metaload.go +++ b/pkg/tcpip/nftables/nft_metaload.go @@ -159,7 +159,7 @@ func (op metaLoad) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Ru // Netfilter (Family) Protocol (8-bit, single byte). case linux.NFT_META_NFPROTO: family := rule.chain.GetAddressFamily() - target = []byte{family.Protocol()} + target = []byte{AfProtocol(family)} // L4 Transport Layer Protocol (8-bit, single byte). case linux.NFT_META_L4PROTO: @@ -225,7 +225,7 @@ func (op metaLoad) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Ru // Breaks if could not retrieve meta data. if target == nil { - regs.verdict = Verdict{Code: VC(linux.NFT_BREAK)} + regs.verdict = stack.NFVerdict{Code: VC(linux.NFT_BREAK)} return } diff --git a/pkg/tcpip/nftables/nft_metaset.go b/pkg/tcpip/nftables/nft_metaset.go index 4a5a3b12e2..ab84c6558b 100644 --- a/pkg/tcpip/nftables/nft_metaset.go +++ b/pkg/tcpip/nftables/nft_metaset.go @@ -81,6 +81,6 @@ func (op metaSet) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rul } // Breaks if could not set the meta data. - regs.verdict = Verdict{Code: VC(linux.NFT_BREAK)} + regs.verdict = stack.NFVerdict{Code: VC(linux.NFT_BREAK)} return } diff --git a/pkg/tcpip/nftables/nft_payload_load.go b/pkg/tcpip/nftables/nft_payload_load.go index 78218a03d1..17c2701f1f 100644 --- a/pkg/tcpip/nftables/nft_payload_load.go +++ b/pkg/tcpip/nftables/nft_payload_load.go @@ -114,7 +114,7 @@ func (op payloadLoad) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule // Breaks if could not retrieve packet data. if payload == nil || len(payload) < int(op.offset+op.blen) { - regs.verdict = Verdict{Code: VC(linux.NFT_BREAK)} + regs.verdict = stack.NFVerdict{Code: VC(linux.NFT_BREAK)} return } diff --git a/pkg/tcpip/nftables/nft_payload_set.go b/pkg/tcpip/nftables/nft_payload_set.go index d08a895e62..4ce7351ddd 100644 --- a/pkg/tcpip/nftables/nft_payload_set.go +++ b/pkg/tcpip/nftables/nft_payload_set.go @@ -96,7 +96,7 @@ func (op payloadSet) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule * // Breaks if could not retrieve packet data. if payload == nil || len(payload) < int(op.offset+op.blen) { - regs.verdict = Verdict{Code: VC(linux.NFT_BREAK)} + regs.verdict = stack.NFVerdict{Code: VC(linux.NFT_BREAK)} return } diff --git a/pkg/tcpip/nftables/nft_ranged.go b/pkg/tcpip/nftables/nft_ranged.go index 8e3aba75f0..dfca514706 100644 --- a/pkg/tcpip/nftables/nft_ranged.go +++ b/pkg/tcpip/nftables/nft_ranged.go @@ -107,6 +107,6 @@ func (op ranged) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule // Determines the comparison result depending on the operator. if (d1 >= 0 && d2 <= 0) != (op.rop == linux.NFT_RANGE_EQ) { // Comparison is false, so break from the rule. - regs.verdict = Verdict{Code: VC(linux.NFT_BREAK)} + regs.verdict = stack.NFVerdict{Code: VC(linux.NFT_BREAK)} } } diff --git a/pkg/tcpip/nftables/nft_route.go b/pkg/tcpip/nftables/nft_route.go index f7aba5309d..7861afec3f 100644 --- a/pkg/tcpip/nftables/nft_route.go +++ b/pkg/tcpip/nftables/nft_route.go @@ -125,7 +125,7 @@ func (op route) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule) // Breaks if could not retrieve target data. if target == nil { - regs.verdict = Verdict{Code: VC(linux.NFT_BREAK)} + regs.verdict = stack.NFVerdict{Code: VC(linux.NFT_BREAK)} return } diff --git a/pkg/tcpip/nftables/nftables.go b/pkg/tcpip/nftables/nftables.go index 1d69d8f85e..d6f8c68b84 100644 --- a/pkg/tcpip/nftables/nftables.go +++ b/pkg/tcpip/nftables/nftables.go @@ -24,12 +24,67 @@ import ( "gvisor.dev/gvisor/pkg/tcpip/stack" ) +// +// Interface-Related Methods +// + +// CheckPrerouting checks at the Prerouting hook if the packet should continue traversing the stack. +func (nf *NFTables) CheckPrerouting(pkt *stack.PacketBuffer, af stack.AddressFamily) bool { + return nf.checkHook(pkt, af, stack.NFPrerouting) +} + +// CheckInput checks at the Input hook if the packet should continue traversing the stack. +func (nf *NFTables) CheckInput(pkt *stack.PacketBuffer, af stack.AddressFamily) bool { + return nf.checkHook(pkt, af, stack.NFInput) +} + +// CheckForward checks at the Forward hook if the packet should continue traversing the stack. +func (nf *NFTables) CheckForward(pkt *stack.PacketBuffer, af stack.AddressFamily) bool { + return nf.checkHook(pkt, af, stack.NFForward) +} + +// CheckOutput checks at the Output hook if the packet should continue traversing the stack. +func (nf *NFTables) CheckOutput(pkt *stack.PacketBuffer, af stack.AddressFamily) bool { + return nf.checkHook(pkt, af, stack.NFOutput) +} + +// CheckPostrouting checks at the Postrouting hook if the packet should continue traversing the stack. +func (nf *NFTables) CheckPostrouting(pkt *stack.PacketBuffer, af stack.AddressFamily) bool { + return nf.checkHook(pkt, af, stack.NFPostrouting) +} + +// CheckIngress checks at the Ingress hook if the packet should continue traversing the stack. +func (nf *NFTables) CheckIngress(pkt *stack.PacketBuffer, af stack.AddressFamily) bool { + return nf.checkHook(pkt, af, stack.NFIngress) +} + +// CheckEgress checks at the Egress hook if the packet should continue traversing the stack. +func (nf *NFTables) CheckEgress(pkt *stack.PacketBuffer, af stack.AddressFamily) bool { + return nf.checkHook(pkt, af, stack.NFEgress) +} + +// checkHook returns true if the packet should continue traversing the stack or false +// if the packet should be dropped. +func (nf *NFTables) checkHook(pkt *stack.PacketBuffer, af stack.AddressFamily, hook stack.NFHook) bool { + v, err := nf.EvaluateHook(af, hook, pkt) + + if err != nil { + return false + } + + return v.Code == VC(linux.NF_ACCEPT) +} + +// +// Core Evaluation Functions +// + // EvaluateHook evaluates a packet using the rules of the given hook for the // given address family, returning a netfilter verdict and modifying the packet // in place. // Returns an error if address family or hook is invalid or they don't match. // TODO(b/345684870): Consider removing error case if we never return an error. -func (nf *NFTables) EvaluateHook(family AddressFamily, hook Hook, pkt *stack.PacketBuffer) (Verdict, error) { +func (nf *NFTables) EvaluateHook(family stack.AddressFamily, hook stack.NFHook, pkt *stack.PacketBuffer) (stack.NFVerdict, error) { // Note: none of the other evaluate functions are public because they require // jumping to different chains in the same table, so all chains, rules, and // operations must be tied to a table. Thus, calling evaluate for standalone @@ -37,18 +92,18 @@ func (nf *NFTables) EvaluateHook(family AddressFamily, hook Hook, pkt *stack.Pac // Ensures address family is valid. if err := validateAddressFamily(family); err != nil { - return Verdict{}, err + return stack.NFVerdict{}, err } // Ensures hook is valid. if err := validateHook(hook, family); err != nil { - return Verdict{}, err + return stack.NFVerdict{}, err } // Immediately accept if there are no base chains for the specified hook. if nf.filters[family] == nil || nf.filters[family].hfStacks[hook] == nil || len(nf.filters[family].hfStacks[hook].baseChains) == 0 { - return Verdict{Code: VC(linux.NF_ACCEPT)}, nil + return stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, nil } regs := newRegisterSet() @@ -63,7 +118,7 @@ func (nf *NFTables) EvaluateHook(family AddressFamily, hook Hook, pkt *stack.Pac err := bc.evaluate(®s, pkt) if err != nil { - return Verdict{}, err + return stack.NFVerdict{}, err } // Terminates immediately on netfilter terminal verdicts. @@ -78,9 +133,9 @@ func (nf *NFTables) EvaluateHook(family AddressFamily, hook Hook, pkt *stack.Pac switch regs.Verdict().Code { case VC(linux.NFT_CONTINUE), VC(linux.NFT_RETURN): if bc.GetBaseChainInfo().PolicyDrop { - return Verdict{Code: VC(linux.NF_DROP)}, nil + return stack.NFVerdict{Code: VC(linux.NF_DROP)}, nil } - return Verdict{Code: VC(linux.NF_ACCEPT)}, nil + return stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, nil } panic(fmt.Sprintf("unexpected verdict from hook evaluation: %s", VerdictCodeToString(regs.Verdict().Code))) @@ -184,14 +239,14 @@ func NewNFTables(clock tcpip.Clock, rng rand.RNG) *NFTables { // Flush clears entire ruleset and all data for all address families. func (nf *NFTables) Flush() { - for family := range NumAFs { + for family := range stack.NumAFs { nf.filters[family] = nil } } // FlushAddressFamily clears ruleset and all data for the given address family, // returning an error if the address family is invalid. -func (nf *NFTables) FlushAddressFamily(family AddressFamily) error { +func (nf *NFTables) FlushAddressFamily(family stack.AddressFamily) error { // Ensures address family is valid. if err := validateAddressFamily(family); err != nil { return err @@ -202,7 +257,7 @@ func (nf *NFTables) FlushAddressFamily(family AddressFamily) error { } // GetTable validates the inputs and gets a table if it exists, error otherwise. -func (nf *NFTables) GetTable(family AddressFamily, tableName string) (*Table, error) { +func (nf *NFTables) GetTable(family stack.AddressFamily, tableName string) (*Table, error) { // Ensures address family is valid. if err := validateAddressFamily(family); err != nil { return nil, err @@ -232,7 +287,7 @@ func (nf *NFTables) GetTable(family AddressFamily, tableName string) (*Table, er // 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 AddressFamily, name string, comment string, +func (nf *NFTables) AddTable(family stack.AddressFamily, name string, comment string, errorOnDuplicate bool) (*Table, error) { // Ensures address family is valid. if err := validateAddressFamily(family); err != nil { @@ -245,7 +300,7 @@ func (nf *NFTables) AddTable(family AddressFamily, name string, comment string, family: family, nftState: nf, tables: make(map[string]*Table), - hfStacks: make(map[Hook]*hookFunctionStack), + hfStacks: make(map[stack.NFHook]*hookFunctionStack), } } @@ -278,14 +333,14 @@ func (nf *NFTables) AddTable(family AddressFamily, name string, comment string, // 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 AddressFamily, name string, comment string) (*Table, error) { +func (nf *NFTables) CreateTable(family stack.AddressFamily, name string, comment string) (*Table, error) { return nf.AddTable(family, name, comment, true) } // DeleteTable deletes the specified table from the NFTables object returning // true if the table was deleted and false if the table doesn't exist. Returns // an error if the address family is invalid. -func (nf *NFTables) DeleteTable(family AddressFamily, tableName string) (bool, error) { +func (nf *NFTables) DeleteTable(family stack.AddressFamily, tableName string) (bool, error) { // Ensures address family is valid. if err := validateAddressFamily(family); err != nil { return false, err @@ -308,7 +363,7 @@ func (nf *NFTables) DeleteTable(family AddressFamily, tableName string) (bool, e } // GetChain validates the inputs and gets a chain if it exists, error otherwise. -func (nf *NFTables) GetChain(family AddressFamily, tableName string, chainName string) (*Chain, error) { +func (nf *NFTables) GetChain(family stack.AddressFamily, tableName string, chainName string) (*Chain, error) { // Gets and checks the table. t, err := nf.GetTable(family, tableName) if err != nil { @@ -326,7 +381,7 @@ func (nf *NFTables) GetChain(family AddressFamily, tableName string, chainName s // Note: if the chain already exists, the existing chain is returned without any // modifications. // Note: if the chain is not a base chain, info should be nil. -func (nf *NFTables) AddChain(family AddressFamily, tableName string, chainName string, info *BaseChainInfo, comment string, errorOnDuplicate bool) (*Chain, error) { +func (nf *NFTables) AddChain(family stack.AddressFamily, tableName string, chainName string, info *BaseChainInfo, comment string, errorOnDuplicate bool) (*Chain, error) { // Gets and checks the table. t, err := nf.GetTable(family, tableName) if err != nil { @@ -341,14 +396,14 @@ func (nf *NFTables) AddChain(family AddressFamily, tableName string, chainName s // chain 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) CreateChain(family AddressFamily, tableName string, chainName string, info *BaseChainInfo, comment string) (*Chain, error) { +func (nf *NFTables) CreateChain(family stack.AddressFamily, tableName string, chainName string, info *BaseChainInfo, comment string) (*Chain, error) { return nf.AddChain(family, tableName, chainName, info, comment, true) } // DeleteChain deletes the specified chain from the NFTables object returning // true if the chain was deleted and false if the chain doesn't exist. Returns // an error if the address family is invalid or the table doesn't exist. -func (nf *NFTables) DeleteChain(family AddressFamily, tableName string, chainName string) (bool, error) { +func (nf *NFTables) DeleteChain(family stack.AddressFamily, tableName string, chainName string) (bool, error) { // Gets and checks the table. t, err := nf.GetTable(family, tableName) if err != nil { @@ -373,7 +428,7 @@ func (t *Table) GetName() string { } // GetAddressFamily returns the address family of the table. -func (t *Table) GetAddressFamily() AddressFamily { +func (t *Table) GetAddressFamily() stack.AddressFamily { return t.afFilter.family } @@ -486,7 +541,7 @@ func (c *Chain) GetName() string { } // GetAddressFamily returns the address family of the chain. -func (c *Chain) GetAddressFamily() AddressFamily { +func (c *Chain) GetAddressFamily() stack.AddressFamily { return c.table.GetAddressFamily() } diff --git a/pkg/tcpip/nftables/nftables_test.go b/pkg/tcpip/nftables/nftables_test.go index e96ca7d542..1749927657 100644 --- a/pkg/tcpip/nftables/nftables_test.go +++ b/pkg/tcpip/nftables/nftables_test.go @@ -34,10 +34,10 @@ import ( // Table Constants. const ( - arbitraryTargetChain string = "target_chain" - arbitraryHook Hook = Prerouting - arbitraryFamily AddressFamily = Inet - arbitraryReservedHeaderBytes int = 16 + arbitraryTargetChain string = "target_chain" + arbitraryHook stack.NFHook = stack.NFPrerouting + arbitraryFamily stack.AddressFamily = stack.Inet + arbitraryReservedHeaderBytes int = 16 ) var ( @@ -338,7 +338,7 @@ func TestUnsupportedAddressFamily(t *testing.T) { // Makes arbitrary packet for comparison (to check for no changes). cmpPkt := makeArbitraryPacket(arbitraryReservedHeaderBytes) nf := newNFTablesStd() - for _, unsupportedFamily := range []AddressFamily{AddressFamily(NumAFs), AddressFamily(-1)} { + for _, unsupportedFamily := range []stack.AddressFamily{stack.AddressFamily(stack.NumAFs), stack.AddressFamily(-1)} { // Note: the Prerouting hook is arbitrary (any hook would work). pkt := makeArbitraryPacket(arbitraryReservedHeaderBytes) v, err := nf.EvaluateHook(unsupportedFamily, arbitraryHook, pkt) @@ -356,10 +356,10 @@ func TestUnsupportedAddressFamily(t *testing.T) { func TestAcceptAllForSupportedHooks(t *testing.T) { // Makes arbitrary packet for comparison (to check for no changes). cmpPkt := makeArbitraryPacket(arbitraryReservedHeaderBytes) - for _, family := range []AddressFamily{IP, IP6, Inet, Arp, Bridge, Netdev} { + for _, family := range []stack.AddressFamily{stack.IP, stack.IP6, stack.Inet, stack.Arp, stack.Bridge, stack.Netdev} { t.Run(family.String()+" address family", func(t *testing.T) { nf := newNFTablesStd() - for _, hook := range []Hook{Prerouting, Input, Forward, Output, Postrouting, Ingress, Egress} { + for _, hook := range []stack.NFHook{stack.NFPrerouting, stack.NFInput, stack.NFForward, stack.NFOutput, stack.NFPostrouting, stack.NFIngress, stack.NFEgress} { pkt := makeArbitraryPacket(arbitraryReservedHeaderBytes) v, err := nf.EvaluateHook(family, hook, pkt) @@ -397,131 +397,131 @@ func TestEvaluateImmediateVerdict(t *testing.T) { baseOp1 operation // will be nil if unused baseOp2 operation // will be nil if unused targetOp operation // will be nil if unused - verdict Verdict + verdict stack.NFVerdict }{ { tname: "no operations", - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy }, { tname: "immediately accept", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})), - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_ACCEPT)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, }, { tname: "immediately drop", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), - verdict: Verdict{Code: VC(linux.NF_DROP)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, }, { tname: "immediately continue with base chain policy accept", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})), - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_CONTINUE)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy }, { tname: "immediately return with base chain policy accept", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_RETURN)})), - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_RETURN)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy }, { tname: "immediately jump to target chain that accepts", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: arbitraryTargetChain})), - targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})), - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: arbitraryTargetChain})), + targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_ACCEPT)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, }, { tname: "immediately jump to target chain that drops", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: arbitraryTargetChain})), - targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), - verdict: Verdict{Code: VC(linux.NF_DROP)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: arbitraryTargetChain})), + targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, }, { tname: "immediately jump to target chain that continues with second rule that accepts", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: arbitraryTargetChain})), - targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})), - baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})), - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: arbitraryTargetChain})), + targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_CONTINUE)})), + baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_ACCEPT)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, }, { tname: "immediately jump to target chain that continues with second rule that drops", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: arbitraryTargetChain})), - targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})), - baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), - verdict: Verdict{Code: VC(linux.NF_DROP)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: arbitraryTargetChain})), + targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_CONTINUE)})), + baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, }, { tname: "immediately goto to target chain that accepts", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: arbitraryTargetChain})), - targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})), - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: arbitraryTargetChain})), + targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_ACCEPT)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, }, { tname: "immediately goto to target chain that drops", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: arbitraryTargetChain})), - targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), - verdict: Verdict{Code: VC(linux.NF_DROP)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: arbitraryTargetChain})), + targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, }, { tname: "immediately goto to target chain that continues with second rule that accepts", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: arbitraryTargetChain})), - targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})), - baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})), - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: arbitraryTargetChain})), + targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_CONTINUE)})), + baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_ACCEPT)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy }, { tname: "immediately goto to target chain that continues with second rule that drops", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: arbitraryTargetChain})), - targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})), - baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: arbitraryTargetChain})), + targetOp: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_CONTINUE)})), + baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy }, { tname: "add data to register then accept", baseOp1: mustCreateImmediate(t, linux.NFT_REG32_13, newBytesData([]byte{0, 1, 2, 3})), - baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})), - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, + baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_ACCEPT)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, }, { tname: "add data to register then drop", baseOp1: mustCreateImmediate(t, linux.NFT_REG32_15, newBytesData([]byte{0, 1, 2, 3})), - baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), - verdict: Verdict{Code: VC(linux.NF_DROP)}, + baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, }, { tname: "add data to register then continue", baseOp1: mustCreateImmediate(t, linux.NFT_REG_4, newBytesData([]byte{0, 1, 2, 3})), - baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})), - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy + baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_CONTINUE)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy }, { tname: "multiple accepts", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})), - baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})), - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_ACCEPT)})), + baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_ACCEPT)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, }, { tname: "multiple drops", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), - baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), - verdict: Verdict{Code: VC(linux.NF_DROP)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), + baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, }, { tname: "immediately accept then drop", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})), - baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_ACCEPT)})), + baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, }, { tname: "immediately drop then accept", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), - baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})), - verdict: Verdict{Code: VC(linux.NF_DROP)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), + baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_ACCEPT)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, }, { tname: "immediate load register", - baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), - baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})), - verdict: Verdict{Code: VC(linux.NF_DROP)}, + baseOp1: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), + baseOp2: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_ACCEPT)})), + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, }, } { t.Run(test.tname, func(t *testing.T) { @@ -1099,7 +1099,7 @@ func TestEvaluateComparison(t *testing.T) { // Add an operation that drops. This is what the final verdict should be // if all the comparisons are true (res = true). - rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))) + rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))) // Registers the rule to the base chain. if err := bc.RegisterRule(rule, -1); err != nil { @@ -1335,7 +1335,7 @@ func TestEvaluateRanged(t *testing.T) { } // Adds drop operation. Will be final verdict if comparison is true. - rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))) + rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))) // Registers the rule to the base chain. if err := bc.RegisterRule(rule, -1); err != nil { @@ -1569,7 +1569,7 @@ func TestEvaluatePayloadLoad(t *testing.T) { } // Adds drop operation. Will be final verdict if all comparisons are true. - rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))) + rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))) // Registers the rule to the base chain. if err := bc.RegisterRule(rule, -1); err != nil { @@ -2074,7 +2074,7 @@ func TestEvaluatePayloadSet(t *testing.T) { // Adds drop operation. Will be final verdict if payload set evaluation is // successful (operation breaks if anything goes wrong). - rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))) + rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))) // Registers the rule to the base chain. if err := bc.RegisterRule(rule, -1); err != nil { @@ -2261,7 +2261,7 @@ func TestEvaluateBitwise(t *testing.T) { } // Adds drop operation. Will be final verdict if comparison is true. - rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))) + rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))) // Registers the rule to the base chain. if err := bc.RegisterRule(rule, -1); err != nil { @@ -2519,7 +2519,7 @@ func TestEvaluateRoute(t *testing.T) { } // Adds drop operation. Will be final verdict if all comparisons are true. - rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))) + rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))) // Registers the rule to the base chain. if err := bc.RegisterRule(rule, -1); err != nil { @@ -2763,7 +2763,7 @@ func TestEvaluateByteorder(t *testing.T) { } // Adds drop operation. Will be final verdict if comparison is true. - rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))) + rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))) // Registers the rule to the base chain. if err := bc.RegisterRule(rule, -1); err != nil { @@ -2851,7 +2851,7 @@ func TestEvaluateMetaLoad(t *testing.T) { pkt: pkt, op1: mustCreateMetaLoad(t, linux.NFT_META_NFPROTO, linux.NFT_REG_4), op2: mustCreateComparison(t, linux.NFT_REG_4, linux.NFT_CMP_EQ, - []byte{IP6.Protocol(), 0, 0, 0}), + []byte{AfProtocol(stack.IP6), 0, 0, 0}), }, { // cmd: add rule ip6 tab ch meta l4proto 0x6 tname: "meta load l4proto test", @@ -2942,7 +2942,7 @@ func TestEvaluateMetaLoad(t *testing.T) { } // Adds drop operation. Will be final verdict if all comparisons are true. - rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))) + rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))) // Registers the rule to the base chain. if err := bc.RegisterRule(rule, -1); err != nil { @@ -3032,7 +3032,7 @@ func TestEvaluateMetaSet(t *testing.T) { } // Adds drop operation, to be final verdict if evaluation is successful. - rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))) + rule.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))) // Registers the rule to the base chain. if err := bc.RegisterRule(rule, -1); err != nil { @@ -3062,7 +3062,7 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { for _, test := range []struct { tname string chains map[string]*Chain - verdict Verdict + verdict stack.NFVerdict shouldErr bool }{ { @@ -3071,7 +3071,7 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { "base_chain": { baseChainInfo: arbitraryInfoPolicyAccept, rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "non_existent_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "non_existent_chain"}))}, }}, }, }, @@ -3083,7 +3083,7 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { "base_chain": { baseChainInfo: arbitraryInfoPolicyAccept, rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "non_existent_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "non_existent_chain"}))}, }}, }, }, @@ -3095,7 +3095,7 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { "base_chain": { baseChainInfo: arbitraryInfoPolicyAccept, rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "base_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "base_chain"}))}, }}, }, }, @@ -3107,7 +3107,7 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { "base_chain": { baseChainInfo: arbitraryInfoPolicyAccept, rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "base_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "base_chain"}))}, }}, }, }, @@ -3119,12 +3119,12 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { "base_chain": { baseChainInfo: arbitraryInfoPolicyAccept, rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}, }}, }, "aux_chain": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "base_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "base_chain"}))}, }}, }, }, @@ -3136,17 +3136,17 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { "base_chain": { baseChainInfo: arbitraryInfoPolicyAccept, rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}, }}, }, "aux_chain": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))}, }}, }, "aux_chain2": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain"}))}, }}, }, }, @@ -3158,17 +3158,17 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { "base_chain": { baseChainInfo: arbitraryInfoPolicyAccept, rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}, }}, }, "aux_chain": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"}))}, }}, }, "aux_chain2": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "base_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "base_chain"}))}, }}, }, }, @@ -3180,27 +3180,27 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { "base_chain": { baseChainInfo: arbitraryInfoPolicyAccept, rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}, }}, }, "aux_chain": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))}, }}, }, "aux_chain2": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}, }}, }, "aux_chain3": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain4"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain4"}))}, }}, }, "aux_chain4": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"}))}, }}, }, }, @@ -3212,22 +3212,22 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { "base_chain": { baseChainInfo: arbitraryInfoPolicyAccept, rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}, }}, }, "aux_chain": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))}, }}, }, "aux_chain2": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}, }}, }, "aux_chain3": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "base_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "base_chain"}))}, }}, }, }, @@ -3239,22 +3239,22 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { "base_chain": { baseChainInfo: arbitraryInfoPolicyAccept, rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}, }}, }, "aux_chain": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))}, }}, }, "aux_chain2": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}, }}, }, "aux_chain3": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "base_chain"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "base_chain"}))}, }}, }, }, @@ -3271,28 +3271,28 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { baseChainInfo: arbitraryInfoPolicyAccept, rules: []*Rule{{ ops: []operation{ - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"})), }, }}, }, "aux_chain": { comment: "strictly target", rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))}, }}, }, "aux_chain2": { rules: []*Rule{{ ops: []operation{ - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"})), }, }}, }, "aux_chain3": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"}))}, }}, }, }, @@ -3306,20 +3306,20 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { rules: []*Rule{ {ops: []operation{mustCreateImmediate(t, linux.NFT_REG_1, newBytesData([]byte{0, 1, 2, 3}))}}, {ops: []operation{mustCreateImmediate(t, linux.NFT_REG32_14, newBytesData([]byte{0, 1, 2, 3}))}}, - {ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}}, + {ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"}))}}, }, }, "aux_chain": { rules: []*Rule{{ ops: []operation{ - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain2"})), }, }}, }, "aux_chain2": { rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}, }}, }, "aux_chain3": { @@ -3328,8 +3328,8 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { {ops: []operation{mustCreateImmediate(t, linux.NFT_REG32_14, newBytesData([]byte{0, 1, 2, 3}))}}, {ops: []operation{ mustCreateImmediate(t, linux.NFT_REG_4, newBytesData([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})), - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain"})), - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "aux_chain"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), }}, }, }, @@ -3344,11 +3344,11 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { rules: []*Rule{ { ops: []operation{ - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"})), }, }, - {ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}}, + {ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}}, }, }, "aux_chain": { @@ -3370,7 +3370,7 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { }}, }, }, - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy }, { tname: "base chain jump to 3 other chains with last chain dropping", @@ -3380,11 +3380,11 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { rules: []*Rule{ { ops: []operation{ - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"})), }, }, - {ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}}, + {ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}}, }, }, "aux_chain": { @@ -3402,11 +3402,11 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { "aux_chain3": { comment: "strictly target", rules: []*Rule{{ - ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))}, + ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))}, }}, }, }, - verdict: Verdict{Code: VC(linux.NF_DROP)}, // from last chain + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, // from last chain }, { tname: "base chain jump to 3 other chains with last rule in base chain dropping", @@ -3416,12 +3416,12 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { rules: []*Rule{ { ops: []operation{ - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain2"})), }, }, - {ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}}, - {ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))}}, + {ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain3"}))}}, + {ops: []operation{mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))}}, }, }, "aux_chain": { @@ -3443,7 +3443,7 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { }}, }, }, - verdict: Verdict{Code: VC(linux.NF_DROP)}, // from last rule in base chain + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, // from last rule in base chain }, { tname: "jump to the same chain", @@ -3453,8 +3453,8 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { rules: []*Rule{ { ops: []operation{ - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), - mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), + mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "aux_chain"})), }, }, }, @@ -3464,7 +3464,7 @@ func TestLoopCheckOnRegisterAndUnregister(t *testing.T) { rules: []*Rule{{}}, }, }, - verdict: Verdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy + verdict: stack.NFVerdict{Code: VC(linux.NF_ACCEPT)}, // from base chain policy }, } { t.Run(test.tname, func(t *testing.T) { @@ -3551,31 +3551,31 @@ func TestMaxNestedJumps(t *testing.T) { tname string useJumpOp bool numberOfJumps int - verdict Verdict // ChainName is set to "error" if an error is expected + verdict stack.NFVerdict // ChainName is set to "error" if an error is expected }{ { tname: "nested jump limit reached with jumps", useJumpOp: true, numberOfJumps: nestedJumpLimit, - verdict: Verdict{Code: VC(linux.NF_DROP)}, + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, }, { tname: "nested jump limit reached with gotos", useJumpOp: false, numberOfJumps: nestedJumpLimit, - verdict: Verdict{Code: VC(linux.NF_DROP)}, + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, }, { tname: "nested jump limit exceeded with jumps", useJumpOp: true, numberOfJumps: nestedJumpLimit + 1, - verdict: Verdict{ChainName: "error"}, + verdict: stack.NFVerdict{ChainName: "error"}, }, { tname: "nested jump limit exceeded with gotos", useJumpOp: false, numberOfJumps: nestedJumpLimit + 1, - verdict: Verdict{Code: VC(linux.NF_DROP)}, // limit only for jumps + verdict: stack.NFVerdict{Code: VC(linux.NF_DROP)}, // limit only for jumps }, } { t.Run(test.tname, func(t *testing.T) { @@ -3596,14 +3596,14 @@ func TestMaxNestedJumps(t *testing.T) { } r := &Rule{} if i == test.numberOfJumps-1 { - err = r.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)}))) + err = r.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)}))) } else { targetName := fmt.Sprintf("chain %d", i+1) code := VC(linux.NFT_JUMP) if !test.useJumpOp { code = VC(linux.NFT_GOTO) } - err = r.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: code, ChainName: targetName}))) + err = r.addOperation(mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: code, ChainName: targetName}))) } if err != nil { t.Fatalf("unexpected error for AddOperation: %v", err) diff --git a/pkg/tcpip/nftables/nftables_types.go b/pkg/tcpip/nftables/nftables_types.go index 2562a27052..be850b8765 100644 --- a/pkg/tcpip/nftables/nftables_types.go +++ b/pkg/tcpip/nftables/nftables_types.go @@ -71,64 +71,18 @@ const ( bitshiftLimit = 32 ) -// AddressFamily describes the 6 address families supported by nftables. -// The address family determines the type of packets processed, and each family -// contains hooks at specific stages of the packet processing pipeline. -type AddressFamily int - -const ( - // IP represents IPv4 Family. - IP AddressFamily = iota - - // IP6 represents IPv6 Family. - IP6 - - // Inet represents Internet Family for hybrid IPv4/IPv6 rules. - Inet - - // Arp represents ARP Family for IPv4 ARP packets. - Arp - - // Bridge represents Bridge Family for Ethernet packets across bridge devices. - Bridge - - // Netdev represents Netdev Family for packets on ingress and egress. - Netdev - - // NumAFs is the number of address families supported by nftables. - NumAFs -) - -// addressFamilyStrings maps address families to their string representation. -var addressFamilyStrings = map[AddressFamily]string{ - IP: "IPv4", - IP6: "IPv6", - Inet: "Internet (Both IPv4/IPv6)", - Arp: "ARP", - Bridge: "Bridge", - Netdev: "Netdev", -} - -// String for AddressFamily returns the name of the address family. -func (f AddressFamily) String() string { - if af, ok := addressFamilyStrings[f]; ok { - return af - } - panic(fmt.Sprintf("invalid address family: %d", int(f))) -} - // addressFamilyProtocols maps address families to their protocol number. -var addressFamilyProtocols = map[AddressFamily]uint8{ - IP: linux.NFPROTO_INET, - IP6: linux.NFPROTO_IPV6, - Inet: linux.NFPROTO_IPV6, - Arp: linux.NFPROTO_ARP, - Bridge: linux.NFPROTO_BRIDGE, - Netdev: linux.NFPROTO_NETDEV, +var addressFamilyProtocols = map[stack.AddressFamily]uint8{ + stack.IP: linux.NFPROTO_INET, + stack.IP6: linux.NFPROTO_IPV6, + stack.Inet: linux.NFPROTO_IPV6, + stack.Arp: linux.NFPROTO_ARP, + stack.Bridge: linux.NFPROTO_BRIDGE, + stack.Netdev: linux.NFPROTO_NETDEV, } -// Protocol returns the protocol number for the address family. -func (f AddressFamily) Protocol() uint8 { +// AfProtocol returns the protocol number for the address family. +func AfProtocol(f stack.AddressFamily) uint8 { if protocol, ok := addressFamilyProtocols[f]; ok { return protocol } @@ -136,79 +90,27 @@ func (f AddressFamily) Protocol() uint8 { } // validateAddressFamily ensures the family address is valid (within bounds). -func validateAddressFamily(family AddressFamily) error { - if family < 0 || family >= NumAFs { +func validateAddressFamily(family stack.AddressFamily) error { + if family < 0 || family >= stack.NumAFs { return fmt.Errorf("invalid address family: %d", int(family)) } return nil } -// Hook describes specific points in the pipeline where chains can be attached. -// Each address family has its own set of hooks (defined in supportedHooks). -// For IPv4/IPv6/Inet and Bridge, there are two possible pipelines: -// 1. Prerouting -> Input -> ~Local Process~ -> Output -> Postrouting -// 2. Prerouting -> Forward -> Postrouting -type Hook int - -const ( - // Prerouting Hook is supported by IPv4/IPv6/Inet, Bridge Families. - Prerouting Hook = iota - - // Input Hook is supported by IPv4/IPv6/Inet, Bridge, ARP Families. - Input - - // Forward Hook is supported by IPv4/IPv6/Inet, Bridge Families. - Forward - - // Output Hook is supported by IPv4/IPv6/Inet, Bridge, ARP Families. - Output - - // Postrouting Hook is supported by IPv4/IPv6/Inet, Bridge Families. - Postrouting - - // Ingress Hook is supported by IPv4/IPv6/Inet, Bridge, Netdev Families. - Ingress - - // Egress Hook is supported by Netdev Family only. - Egress - - // NumHooks is the number of hooks supported by nftables. - NumHooks -) - -// hookStrings maps hooks to their string representation. -var hookStrings = map[Hook]string{ - Prerouting: "Prerouting", - Input: "Input", - Forward: "Forward", - Output: "Output", - Postrouting: "Postrouting", - Ingress: "Ingress", - Egress: "Egress", -} - -// String for Hook returns the name of the hook. -func (h Hook) String() string { - if hook, ok := hookStrings[h]; ok { - return hook - } - panic(fmt.Sprintf("invalid hook: %d", int(h))) -} - // supportedHooks maps each address family to its supported hooks. -var supportedHooks [NumAFs][]Hook = [NumAFs][]Hook{ - IP: {Prerouting, Input, Forward, Output, Postrouting, Ingress}, - IP6: {Prerouting, Input, Forward, Output, Postrouting, Ingress}, - Inet: {Prerouting, Input, Forward, Output, Postrouting, Ingress}, - Arp: {Input, Output}, - Bridge: {Prerouting, Input, Forward, Output, Postrouting, Ingress}, - Netdev: {Ingress, Egress}, +var supportedHooks [stack.NumAFs][]stack.NFHook = [stack.NumAFs][]stack.NFHook{ + stack.IP: {stack.NFPrerouting, stack.NFInput, stack.NFForward, stack.NFOutput, stack.NFPostrouting, stack.NFIngress}, + stack.IP6: {stack.NFPrerouting, stack.NFInput, stack.NFForward, stack.NFOutput, stack.NFPostrouting, stack.NFIngress}, + stack.Inet: {stack.NFPrerouting, stack.NFInput, stack.NFForward, stack.NFOutput, stack.NFPostrouting, stack.NFIngress}, + stack.Arp: {stack.NFInput, stack.NFOutput}, + stack.Bridge: {stack.NFPrerouting, stack.NFInput, stack.NFForward, stack.NFOutput, stack.NFPostrouting, stack.NFIngress}, + stack.Netdev: {stack.NFIngress, stack.NFEgress}, } // validateHook ensures the hook is within bounds and supported for the given // address family. -func validateHook(hook Hook, family AddressFamily) error { - if hook < 0 || hook >= NumHooks { +func validateHook(hook stack.NFHook, family stack.AddressFamily) error { + if hook >= stack.NFNumHooks { return fmt.Errorf("invalid hook: %d", int(hook)) } if slices.Contains(supportedHooks[family], hook) { @@ -221,17 +123,20 @@ func validateHook(hook Hook, family AddressFamily) error { // NFTables represents the nftables state for all address families. // Note: unlike iptables, nftables doesn't start with any initialized tables. type NFTables struct { - filters [NumAFs]*addressFamilyFilter // Filters for each address family. - clock tcpip.Clock // Clock for timing evaluations. - startTime time.Time // Time NFTables object was created. - rng rand.RNG // Random number generator. + filters [stack.NumAFs]*addressFamilyFilter // Filters for each address family. + clock tcpip.Clock // Clock for timing evaluations. + startTime time.Time // Time NFTables object was created. + rng rand.RNG // Random number generator. } +// Ensures NFTables implements the NFTablesInterface. +var _ stack.NFTablesInterface = (*NFTables)(nil) + // addressFamilyFilter represents the nftables state for a specific address // family. type addressFamilyFilter struct { // family is the address family of the filter. - family AddressFamily + family stack.AddressFamily // nftState is the NFTables object the filter belongs to. nftState *NFTables @@ -241,7 +146,7 @@ type addressFamilyFilter struct { // hfStacks is a map of hook function stacks (slice of base chains for a // given hook ordered by priority). - hfStacks map[Hook]*hookFunctionStack + hfStacks map[stack.NFHook]*hookFunctionStack } // Table represents a single table as a collection of named chains. @@ -269,7 +174,7 @@ type Table struct { // hookFunctionStack represents the list of base chains for a specific hook. // The stack is ordered by priority and built as chains are added to tables. type hookFunctionStack struct { - hook Hook + hook stack.NFHook baseChains []*Chain } @@ -317,7 +222,7 @@ type BaseChainInfo struct { BcType BaseChainType // Hook is the hook to attach the chain to in the netfilter pipeline - Hook Hook + Hook stack.NFHook // Priority determines the order in which base chains with the same hook are // traversed. Each priority is associated with a signed integer priority value @@ -339,7 +244,7 @@ type BaseChainInfo struct { // NewBaseChainInfo creates a new BaseChainInfo object with the given values. // The device and policyDrop parameters are optional in the nft binary and // should be set to empty string and false if not needed. -func NewBaseChainInfo(bcType BaseChainType, hook Hook, priority Priority, device string, policyDrop bool) *BaseChainInfo { +func NewBaseChainInfo(bcType BaseChainType, hook stack.NFHook, priority Priority, device string, policyDrop bool) *BaseChainInfo { return &BaseChainInfo{ BcType: bcType, Hook: hook, @@ -384,18 +289,18 @@ func (bcType BaseChainType) String() string { // supportedAFsForBaseChainTypes maps each base chain type to its supported // address families. -var supportedAFsForBaseChainTypes [NumBaseChainTypes][]AddressFamily = [NumBaseChainTypes][]AddressFamily{ - BaseChainTypeFilter: {IP, IP6, Inet, Bridge, Arp, Netdev}, - BaseChainTypeNat: {IP, IP6, Inet}, - BaseChainTypeRoute: {IP, IP6}, +var supportedAFsForBaseChainTypes [NumBaseChainTypes][]stack.AddressFamily = [NumBaseChainTypes][]stack.AddressFamily{ + BaseChainTypeFilter: {stack.IP, stack.IP6, stack.Inet, stack.Bridge, stack.Arp, stack.Netdev}, + BaseChainTypeNat: {stack.IP, stack.IP6, stack.Inet}, + BaseChainTypeRoute: {stack.IP, stack.IP6}, } // supportedHooksForBaseChainTypes maps each base chain type to its supported // hooks. -var supportedHooksForBaseChainTypes [NumBaseChainTypes][]Hook = [NumBaseChainTypes][]Hook{ - BaseChainTypeFilter: {Prerouting, Input, Forward, Output, Postrouting, Ingress, Egress}, - BaseChainTypeNat: {Prerouting, Input, Output, Postrouting}, - BaseChainTypeRoute: {Output}, +var supportedHooksForBaseChainTypes [NumBaseChainTypes][]stack.NFHook = [NumBaseChainTypes][]stack.NFHook{ + BaseChainTypeFilter: {stack.NFPrerouting, stack.NFInput, stack.NFForward, stack.NFOutput, stack.NFPostrouting, stack.NFIngress, stack.NFEgress}, + BaseChainTypeNat: {stack.NFPrerouting, stack.NFInput, stack.NFOutput, stack.NFPostrouting}, + BaseChainTypeRoute: {stack.NFOutput}, } // @@ -433,7 +338,7 @@ func NewIntPriority(value int) Priority { // NewStandardPriority creates a new Priority object given a standard priority // name, returning an error if the standard priority name is not compatible with // the given address family and hook. -func NewStandardPriority(name string, family AddressFamily, hook Hook) (Priority, error) { +func NewStandardPriority(name string, family stack.AddressFamily, hook stack.NFHook) (Priority, error) { // Validates address family and hook first. if err := validateAddressFamily(family); err != nil { return Priority{}, err @@ -503,54 +408,54 @@ type standardPriority struct { // value is the priority value of the standard priority name. value int // hooks are the hooks that are compatible with the standard priority name. - hooks []Hook + hooks []stack.NFHook } // standardPriorityMatrix is used to look up information for the predefined // standard priority names. -var standardPriorityMatrix = map[AddressFamily](map[string]standardPriority){ - IP: spmIP, +var standardPriorityMatrix = map[stack.AddressFamily](map[string]standardPriority){ + stack.IP: spmIP, // Note: IPv6 standard priorities constants currently have the same values as // IPv4's, but the definitions (in the linux kernel) may change in the future. - IP6: map[string]standardPriority{ // from uapi/linux/netfilter_ipv6.h - "raw": {name: "raw", value: linux.NF_IP6_PRI_RAW, hooks: supportedHooks[IP6]}, - "mangle": {name: "mangle", value: linux.NF_IP6_PRI_MANGLE, hooks: supportedHooks[IP6]}, - "dstnat": {name: "dstnat", value: linux.NF_IP6_PRI_NAT_DST, hooks: []Hook{Prerouting}}, - "filter": {name: "filter", value: linux.NF_IP6_PRI_FILTER, hooks: supportedHooks[IP6]}, - "security": {name: "security", value: linux.NF_IP6_PRI_SECURITY, hooks: supportedHooks[IP6]}, - "srcnat": {name: "srcnat", value: linux.NF_IP6_PRI_NAT_SRC, hooks: []Hook{Postrouting}}, + stack.IP6: map[string]standardPriority{ // from uapi/linux/netfilter_ipv6.h + "raw": {name: "raw", value: linux.NF_IP6_PRI_RAW, hooks: supportedHooks[stack.IP6]}, + "mangle": {name: "mangle", value: linux.NF_IP6_PRI_MANGLE, hooks: supportedHooks[stack.IP6]}, + "dstnat": {name: "dstnat", value: linux.NF_IP6_PRI_NAT_DST, hooks: []stack.NFHook{stack.NFPrerouting}}, + "filter": {name: "filter", value: linux.NF_IP6_PRI_FILTER, hooks: supportedHooks[stack.IP6]}, + "security": {name: "security", value: linux.NF_IP6_PRI_SECURITY, hooks: supportedHooks[stack.IP6]}, + "srcnat": {name: "srcnat", value: linux.NF_IP6_PRI_NAT_SRC, hooks: []stack.NFHook{stack.NFPostrouting}}, }, - Inet: spmIP, - Arp: map[string]standardPriority{ // defined as same as IP filter priority - "filter": {name: "filter", value: spmIP["filter"].value, hooks: supportedHooks[Arp]}, + stack.Inet: spmIP, + stack.Arp: map[string]standardPriority{ // defined as same as IP filter priority + "filter": {name: "filter", value: spmIP["filter"].value, hooks: supportedHooks[stack.Arp]}, }, - Bridge: map[string]standardPriority{ // from uapi/linux/netfilter_bridge.h - "dstnat": {name: "dstnat", value: linux.NF_BR_PRI_NAT_DST_BRIDGED, hooks: []Hook{Prerouting}}, - "filter": {name: "filter", value: linux.NF_BR_PRI_FILTER_BRIDGED, hooks: supportedHooks[Bridge]}, - "out": {name: "out", value: linux.NF_BR_PRI_NAT_DST_OTHER, hooks: []Hook{Output}}, - "srcnat": {name: "srcnat", value: linux.NF_BR_PRI_NAT_SRC, hooks: []Hook{Postrouting}}, + stack.Bridge: map[string]standardPriority{ // from uapi/linux/netfilter_bridge.h + "dstnat": {name: "dstnat", value: linux.NF_BR_PRI_NAT_DST_BRIDGED, hooks: []stack.NFHook{stack.NFPrerouting}}, + "filter": {name: "filter", value: linux.NF_BR_PRI_FILTER_BRIDGED, hooks: supportedHooks[stack.Bridge]}, + "out": {name: "out", value: linux.NF_BR_PRI_NAT_DST_OTHER, hooks: []stack.NFHook{stack.NFOutput}}, + "srcnat": {name: "srcnat", value: linux.NF_BR_PRI_NAT_SRC, hooks: []stack.NFHook{stack.NFPostrouting}}, }, - Netdev: map[string]standardPriority{ // defined as same as IP filter priority - "filter": {name: "filter", value: spmIP["filter"].value, hooks: supportedHooks[Netdev]}, + stack.Netdev: map[string]standardPriority{ // defined as same as IP filter priority + "filter": {name: "filter", value: spmIP["filter"].value, hooks: supportedHooks[stack.Netdev]}, }, } // Used in the standardPriorityMatrix above. // Note: IPv4 and Inet address families use the same standard priority names. var spmIP = map[string]standardPriority{ // from uapi/linux/netfilter_ipv4.h - "raw": {name: "raw", value: linux.NF_IP_PRI_RAW, hooks: supportedHooks[IP]}, - "mangle": {name: "mangle", value: linux.NF_IP_PRI_MANGLE, hooks: supportedHooks[IP]}, - "dstnat": {name: "dstnat", value: linux.NF_IP_PRI_NAT_DST, hooks: []Hook{Prerouting}}, - "filter": {name: "filter", value: linux.NF_IP_PRI_FILTER, hooks: supportedHooks[IP]}, - "security": {name: "security", value: linux.NF_IP_PRI_SECURITY, hooks: supportedHooks[IP]}, - "srcnat": {name: "srcnat", value: linux.NF_IP_PRI_NAT_SRC, hooks: []Hook{Postrouting}}, + "raw": {name: "raw", value: linux.NF_IP_PRI_RAW, hooks: supportedHooks[stack.IP]}, + "mangle": {name: "mangle", value: linux.NF_IP_PRI_MANGLE, hooks: supportedHooks[stack.IP]}, + "dstnat": {name: "dstnat", value: linux.NF_IP_PRI_NAT_DST, hooks: []stack.NFHook{stack.NFPrerouting}}, + "filter": {name: "filter", value: linux.NF_IP_PRI_FILTER, hooks: supportedHooks[stack.IP]}, + "security": {name: "security", value: linux.NF_IP_PRI_SECURITY, hooks: supportedHooks[stack.IP]}, + "srcnat": {name: "srcnat", value: linux.NF_IP_PRI_NAT_SRC, hooks: []stack.NFHook{stack.NFPostrouting}}, } // validateBaseChainInfo ensures the base chain info is valid by checking the // compatibility of the set base chain type, hook, and priority, and the given // address family. // Note: errors if the provided base chain info is nil. -func validateBaseChainInfo(info *BaseChainInfo, family AddressFamily) error { +func validateBaseChainInfo(info *BaseChainInfo, family stack.AddressFamily) error { if info == nil { return fmt.Errorf("base chain info is nil") } @@ -653,15 +558,15 @@ type registerData interface { // verdictData represents a verdict as data to be stored in a register. type verdictData struct { - data Verdict + data stack.NFVerdict } // newVerdictData creates a registerData for a verdict. -func newVerdictData(verdict Verdict) verdictData { return verdictData{data: verdict} } +func newVerdictData(verdict stack.NFVerdict) verdictData { return verdictData{data: verdict} } // String returns a string representation of the verdict data. func (rd verdictData) String() string { - return rd.data.String() + return VerdictString(rd.data) } // equal compares the verdict data to another RegisterData object. @@ -765,7 +670,7 @@ func (rd bytesData) storeData(regs *registerSet, reg uint8) { // Use RegisterData.storeData to set data in the registers. // Note: Corresponds to nft_regs from include/net/netfilter/nf_tables.h. type registerSet struct { - verdict Verdict // 16-byte verdict register + verdict stack.NFVerdict // 16-byte verdict register data [registersByteSize]byte // 4 16-byte registers or 16 4-byte registers } @@ -773,13 +678,13 @@ type registerSet struct { // registers set to 0. func newRegisterSet() registerSet { return registerSet{ - verdict: Verdict{Code: VC(linux.NFT_CONTINUE)}, + verdict: stack.NFVerdict{Code: VC(linux.NFT_CONTINUE)}, data: [registersByteSize]byte{0}, } } // Verdict returns the verdict data. -func (regs *registerSet) Verdict() Verdict { +func (regs *registerSet) Verdict() stack.NFVerdict { return regs.verdict } @@ -787,29 +692,10 @@ func (regs *registerSet) String() string { return fmt.Sprintf("verdict: %v, data: %x", regs.verdict, regs.data) } -// -// Verdict Implementation. -// There are two types of verdicts: -// 1. Netfilter (External) Verdicts: Drop, Accept, Stolen, Queue, Repeat, Stop -// These are terminal verdicts that are returned to the kernel. -// 2. Nftable (Internal) Verdicts:, Continue, Break, Jump, Goto, Return -// These are internal verdicts that only exist within the nftables library. -// Both share the same numeric space (uint32 Verdict Code). -// - -// Verdict represents the result of evaluating a packet against a rule or chain. -type Verdict struct { - // Code is the numeric code that represents the verdict issued. - Code uint32 - - // ChainName is the name of the chain to continue evaluation if the verdict is - // Jump or Goto. - // Note: the chain must be in the same table as the current chain. - ChainName string -} +// NF Verdict Helper Functions -// String returns a string representation of the verdict. -func (v Verdict) String() string { +// VerdictString returns a string representation of the verdict. +func VerdictString(v stack.NFVerdict) string { out := VerdictCodeToString(v.Code) if v.ChainName != "" { out += fmt.Sprintf(" -> %s", v.ChainName) diff --git a/pkg/tcpip/nftables/nftinterp.go b/pkg/tcpip/nftables/nftinterp.go index dbcd023479..69527d0878 100644 --- a/pkg/tcpip/nftables/nftinterp.go +++ b/pkg/tcpip/nftables/nftinterp.go @@ -23,6 +23,7 @@ import ( "strings" "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/tcpip/stack" ) // SyntaxError is an interpretation error due to incorrect syntax. @@ -1036,8 +1037,8 @@ var verdictCodeFromKeyword = map[string]int32{ // parseVerdict parses the verdict from the given token and returns // the index of the next token to process (can consume multiple tokens). -func parseVerdict(tokens []string, lnIdx int, tkIdx int) (int, Verdict, error) { - v := Verdict{} +func parseVerdict(tokens []string, lnIdx int, tkIdx int) (int, stack.NFVerdict, error) { + v := stack.NFVerdict{} vcString := tokens[tkIdx] vc, ok := verdictCodeFromKeyword[vcString] diff --git a/pkg/tcpip/nftables/nftinterp_test.go b/pkg/tcpip/nftables/nftinterp_test.go index eee556f080..8112997e3c 100644 --- a/pkg/tcpip/nftables/nftinterp_test.go +++ b/pkg/tcpip/nftables/nftinterp_test.go @@ -19,6 +19,7 @@ import ( "testing" "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/tcpip/stack" ) type interpretOperationTestAction struct { @@ -58,32 +59,32 @@ func TestInterpretImmediateOps(t *testing.T) { { tname: "verdict register with accept verdict", opStr: "[ immediate reg 0 accept ]", - expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_ACCEPT)})), + expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_ACCEPT)})), }, { tname: "verdict register with drop verdict", opStr: "[ immediate reg 0 drop ]", - expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NF_DROP)})), + expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NF_DROP)})), }, { tname: "verdict register with continue verdict", opStr: "[ immediate reg 0 continue ]", - expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_CONTINUE)})), + expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_CONTINUE)})), }, { tname: "verdict register with return verdict", opStr: "[ immediate reg 0 return ]", - expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_RETURN)})), + expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_RETURN)})), }, { tname: "verdict register with jump verdict", opStr: "[ immediate reg 0 jump -> next_chain ]", - expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_JUMP), ChainName: "next_chain"})), + expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_JUMP), ChainName: "next_chain"})), }, { tname: "verdict register with goto verdict", opStr: "[ immediate reg 0 goto -> next_chain ]", - expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(Verdict{Code: VC(linux.NFT_GOTO), ChainName: "next_chain"})), + expected: mustCreateImmediate(t, linux.NFT_REG_VERDICT, newVerdictData(stack.NFVerdict{Code: VC(linux.NFT_GOTO), ChainName: "next_chain"})), }, { tname: "verdict register with 4-byte data", diff --git a/pkg/tcpip/stack/BUILD b/pkg/tcpip/stack/BUILD index 07e4b80c66..2c5e7b2fe8 100644 --- a/pkg/tcpip/stack/BUILD +++ b/pkg/tcpip/stack/BUILD @@ -230,6 +230,7 @@ go_library( "neighbor_entry_list.go", "neighbor_entry_mutex.go", "neighborstate_string.go", + "nftables_types.go", "nic.go", "nic_mutex.go", "nic_stats.go", diff --git a/pkg/tcpip/stack/nftables_types.go b/pkg/tcpip/stack/nftables_types.go new file mode 100644 index 0000000000..9926b1788a --- /dev/null +++ b/pkg/tcpip/stack/nftables_types.go @@ -0,0 +1,165 @@ +// Copyright 2025 The gVisor Authors. +// +// 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 stack + +import ( + "fmt" +) + +// NFTablesInterface is an interface for evaluating chains. +type NFTablesInterface interface { + CheckPrerouting(pkt *PacketBuffer, af AddressFamily) bool + CheckInput(pkt *PacketBuffer, af AddressFamily) bool + CheckForward(pkt *PacketBuffer, af AddressFamily) bool + CheckOutput(pkt *PacketBuffer, af AddressFamily) bool + CheckPostrouting(pkt *PacketBuffer, af AddressFamily) bool + CheckIngress(pkt *PacketBuffer, af AddressFamily) bool + CheckEgress(pkt *PacketBuffer, af AddressFamily) bool +} + +// NFHook describes specific points in the pipeline where chains can be attached. +// Each address family has its own set of hooks (defined in supportedHooks). +// For IPv4/IPv6/Inet and Bridge, there are two possible pipelines: +// 1. Prerouting -> Input -> ~Local Process~ -> Output -> Postrouting +// 2. Prerouting -> Forward -> Postrouting +type NFHook uint16 + +const ( + // NFPrerouting Hook is supported by IPv4/IPv6/Inet, Bridge Families. + // Prerouting is evaluated before a packet is routed to applications or forwarded. + NFPrerouting NFHook = iota + + // NFInput Hook is supported by IPv4/IPv6/Inet, Bridge, ARP Families. + // Input is evaluated before a packet reaches an application. + NFInput + + // NFForward Hook is supported by IPv4/IPv6/Inet, Bridge Families. + // Forward is evaluated once it's decided that a packet should be forwarded to another host. + NFForward + + // NFOutput Hook is supported by IPv4/IPv6/Inet, Bridge, ARP Families. + // Output is evaluated after a packet is written by an application to be sent out. + NFOutput + + // NFPostrouting Hook is supported by IPv4/IPv6/Inet, Bridge Families. + // Postrouting is evaluated just before a packet goes out on the wire. + NFPostrouting + + // NFIngress Hook is supported by IPv4/IPv6/Inet, Bridge, Netdev Families. + // Ingress is the first hook evaluated, even before prerouting. + NFIngress + + // NFEgress Hook is supported by Netdev Family only. + // Egress is the last hook evaluated, after the packet has been processed by the + // application and is being prepared for transmission out of the network interface. + NFEgress + + // NFNumHooks is the number of hooks supported by nftables. + NFNumHooks +) + +// hookStrings maps hooks to their string representation. +var hookStrings = map[NFHook]string{ + NFPrerouting: "Prerouting", + NFInput: "Input", + NFForward: "Forward", + NFOutput: "Output", + NFPostrouting: "Postrouting", + NFIngress: "Ingress", + NFEgress: "Egress", +} + +// String for Hook returns the name of the hook. +func (h NFHook) String() string { + if hook, ok := hookStrings[h]; ok { + return hook + } + panic(fmt.Sprintf("invalid NFHook: %d", int(h))) +} + +// AddressFamily describes the 6 address families supported by nftables. +// The address family determines the type of packets processed, and each family +// contains hooks at specific stages of the packet processing pipeline. +type AddressFamily int + +const ( + // IP represents IPv4 Family. + IP AddressFamily = iota + + // IP6 represents IPv6 Family. + IP6 + + // Inet represents Internet Family for hybrid IPv4/IPv6 rules. + Inet + + // Arp represents ARP Family for IPv4 ARP packets. + Arp + + // Bridge represents Bridge Family for Ethernet packets across bridge devices. + Bridge + + // Netdev represents Netdev Family for packets on ingress and egress. + Netdev + + // NumAFs is the number of address families supported by nftables. + NumAFs +) + +// addressFamilyStrings maps address families to their string representation. +var addressFamilyStrings = map[AddressFamily]string{ + IP: "IPv4", + IP6: "IPv6", + Inet: "Internet (Both IPv4/IPv6)", + Arp: "ARP", + Bridge: "Bridge", + Netdev: "Netdev", +} + +// ValidateAddressFamily ensures the family address is valid (within bounds). +func ValidateAddressFamily(family AddressFamily) error { + if family < 0 || family >= NumAFs { + return fmt.Errorf("invalid address family: %d", int(family)) + } + return nil +} + +// String for AddressFamily returns the name of the address family. +func (f AddressFamily) String() string { + if af, ok := addressFamilyStrings[f]; ok { + return af + } + panic(fmt.Sprintf("invalid address family: %d", int(f))) +} + +// +// Verdict Implementation. +// There are two types of verdicts: +// 1. Netfilter (External) Verdicts: Drop, Accept, Stolen, Queue, Repeat, Stop +// These are terminal verdicts that are returned to the kernel. +// 2. Nftable (Internal) Verdicts:, Continue, Break, Jump, Goto, Return +// These are internal verdicts that only exist within the nftables library. +// Both share the same numeric space (uint32 Verdict Code). +// + +// NFVerdict represents the result of evaluating a packet against a rule or chain. +type NFVerdict struct { + // Code is the numeric code that represents the verdict issued. + Code uint32 + + // ChainName is the name of the chain to continue evaluation if the verdict is + // Jump or Goto. + // Note: the chain must be in the same table as the current chain. + ChainName string +} diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index 2d24becc98..2694856b32 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -176,6 +176,9 @@ type Stack struct { // saveRestoreEnabled indicates whether the stack is saved and restored. saveRestoreEnabled bool + + // nftablesEnabled indicates whether nftables support is enabled. + nftablesEnabled bool } // NetworkProtocolFactory instantiates a network protocol. @@ -2428,6 +2431,22 @@ func (s *Stack) IsSaveRestoreEnabled() bool { return s.saveRestoreEnabled } +// EnableNFTables enables nftables support for the stack. +func (s *Stack) EnableNFTables() { + s.mu.Lock() + defer s.mu.Unlock() + + s.nftablesEnabled = true +} + +// IsNFTablesEnabled returns true if nftables is enabled for the stack. +func (s *Stack) IsNFTablesEnabled() bool { + s.mu.Lock() + defer s.mu.Unlock() + + return s.nftablesEnabled +} + // contextID is this package's type for context.Context.Value keys. type contextID int diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index bc8df90bd9..590f254331 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -1556,7 +1556,7 @@ func newRootNetworkNamespace(conf *config.Config, clock tcpip.Clock, userns *aut return inet.NewRootNamespace(hostinet.NewStack(), nil, userns), nil case config.NetworkNone, config.NetworkSandbox: - s, err := newEmptySandboxNetworkStack(clock, conf.AllowPacketEndpointWrite) + s, err := newEmptySandboxNetworkStack(clock, conf.AllowPacketEndpointWrite, conf.Nftables) if err != nil { return nil, err } @@ -1574,7 +1574,7 @@ func newRootNetworkNamespace(conf *config.Config, clock tcpip.Clock, userns *aut } -func newEmptySandboxNetworkStack(clock tcpip.Clock, allowPacketEndpointWrite bool) (*netstack.Stack, error) { +func newEmptySandboxNetworkStack(clock tcpip.Clock, allowPacketEndpointWrite bool, enableNFTables bool) (*netstack.Stack, error) { netProtos := []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol, arp.NewProtocol} transProtos := []stack.TransportProtocolFactory{ tcp.NewProtocol, @@ -1595,6 +1595,11 @@ func newEmptySandboxNetworkStack(clock tcpip.Clock, allowPacketEndpointWrite boo DefaultIPTables: netfilter.DefaultLinuxTables, })} + // Enable nftables support only if the flag is enabled. + if enableNFTables { + s.Stack.EnableNFTables() + } + // Enable SACK Recovery. { opt := tcpip.TCPSACKEnabled(true) @@ -1631,11 +1636,12 @@ func newEmptySandboxNetworkStack(clock tcpip.Clock, allowPacketEndpointWrite boo type sandboxNetstackCreator struct { clock tcpip.Clock allowPacketEndpointWrite bool + nftables bool } // CreateStack implements kernel.NetworkStackCreator.CreateStack. func (f *sandboxNetstackCreator) CreateStack() (inet.Stack, error) { - s, err := newEmptySandboxNetworkStack(f.clock, f.allowPacketEndpointWrite) + s, err := newEmptySandboxNetworkStack(f.clock, f.allowPacketEndpointWrite, f.nftables) if err != nil { return nil, err }