From 7c2e05b47d8b5a39c55994eb16c86a118522bb3a Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Thu, 21 Nov 2024 15:49:56 -0500 Subject: [PATCH] Add flags to Table and policy to Chain --- fake_test.go | 7 +++++++ objects.go | 49 ++++++++++++++++++++++++++++++++++++++++++------- objects_test.go | 29 ++++++++++++++++++++++++++++- types.go | 29 +++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 8 deletions(-) diff --git a/fake_test.go b/fake_test.go index a51c103..a25a4ea 100644 --- a/fake_test.go +++ b/fake_test.go @@ -614,6 +614,13 @@ func TestFakeParseDump(t *testing.T) { add element ip kube-proxy map1 { 192.168.0.2 . tcp . 443 comment "with a comment" : goto anotherchain } `, }, + { + ipFamily: IPv4Family, + dump: ` + add table ip kube-proxy { flags dormant ; } + add chain ip kube-proxy filter-prerouting { type filter hook prerouting priority -100 ; policy drop ; } + `, + }, { ipFamily: IPv4Family, dump: ` diff --git a/objects.go b/objects.go index ed11db2..b790957 100644 --- a/objects.go +++ b/objects.go @@ -76,15 +76,38 @@ func (table *Table) writeOperation(verb verb, ctx *nftContext, writer io.Writer) // All other cases refer to the table by name fmt.Fprintf(writer, "%s table %s %s", verb, ctx.family, ctx.table) if verb == addVerb || verb == createVerb { - if table.Comment != nil && !ctx.noObjectComments { - fmt.Fprintf(writer, " { comment %q ; }", *table.Comment) + hasComment := table.Comment != nil && !ctx.noObjectComments + if hasComment || len(table.Flags) != 0 { + fmt.Fprintf(writer, " {") + if hasComment { + fmt.Fprintf(writer, " comment %q ;", *table.Comment) + } + if len(table.Flags) != 0 { + fmt.Fprintf(writer, " flags ") + for i := range table.Flags { + if i > 0 { + fmt.Fprintf(writer, ",") + } + fmt.Fprintf(writer, "%s", table.Flags[i]) + } + fmt.Fprintf(writer, " ;") + } + fmt.Fprintf(writer, " }") } } fmt.Fprintf(writer, "\n") } var tableRegexp = regexp.MustCompile(fmt.Sprintf( - `(?:{ comment %s ; })?`, commentGroup)) + `(?:{ (?:comment %s ; )?(?:flags %s ; )?})?`, commentGroup, noSpaceGroup)) + +func parseTableFlags(s string) []TableFlag { + var res []TableFlag + for _, flag := range strings.Split(s, ",") { + res = append(res, TableFlag(flag)) + } + return res +} func (table *Table) parse(line string) error { match := tableRegexp.FindStringSubmatch(line) @@ -92,6 +115,9 @@ func (table *Table) parse(line string) error { return fmt.Errorf("failed parsing table add command") } table.Comment = getComment(match[1]) + if match[2] != "" { + table.Flags = parseTableFlags(match[2]) + } return nil } @@ -101,6 +127,9 @@ func (chain *Chain) validate(verb verb) error { if chain.Type != nil || chain.Priority != nil { return fmt.Errorf("regular chain %q must not specify Type or Priority", chain.Name) } + if chain.Policy != nil { + return fmt.Errorf("regular chain %q must not specify Policy", chain.Name) + } if chain.Device != nil { return fmt.Errorf("regular chain %q must not specify Device", chain.Name) } @@ -156,6 +185,9 @@ func (chain *Chain) writeOperation(verb verb, ctx *nftContext, writer io.Writer) } else { fmt.Fprintf(writer, " priority %s ;", *chain.Priority) } + if chain.Policy != nil { + fmt.Fprintf(writer, " policy %s ;", *chain.Policy) + } } if chain.Comment != nil && !ctx.noObjectComments { fmt.Fprintf(writer, " comment %q ;", *chain.Comment) @@ -168,10 +200,10 @@ func (chain *Chain) writeOperation(verb verb, ctx *nftContext, writer io.Writer) fmt.Fprintf(writer, "\n") } -// groups in []: [1]%s(?: {(?: type [2]%s hook [3]%s(?: device "[4]%s")(?: priority [5]%s ;))(?: comment [6]%s ;) }) +// groups in []: [1]%s(?: {(?: type [2]%s hook [3]%s(?: device "[4]%s")(?: priority [5]%s ;)(?: policy [6]%s ;)?)(?: comment [7]%s ;) }) var chainRegexp = regexp.MustCompile(fmt.Sprintf( - `%s(?: {(?: type %s hook %s(?: device "%s")?(?: priority %s ;))?(?: comment %s ;)? })?`, - noSpaceGroup, noSpaceGroup, noSpaceGroup, noSpaceGroup, noSpaceGroup, commentGroup)) + `%s(?: {(?: type %s hook %s(?: device "%s")?(?: priority %s ;)(?: policy %s ;)?)?(?: comment %s ;)? })?`, + noSpaceGroup, noSpaceGroup, noSpaceGroup, noSpaceGroup, noSpaceGroup, noSpaceGroup, commentGroup)) func (chain *Chain) parse(line string) error { match := chainRegexp.FindStringSubmatch(line) @@ -179,7 +211,7 @@ func (chain *Chain) parse(line string) error { return fmt.Errorf("failed parsing chain add command") } chain.Name = match[1] - chain.Comment = getComment(match[6]) + chain.Comment = getComment(match[7]) if match[2] != "" { chain.Type = (*BaseChainType)(&match[2]) } @@ -192,6 +224,9 @@ func (chain *Chain) parse(line string) error { if match[5] != "" { chain.Priority = (*BaseChainPriority)(&match[5]) } + if match[6] != "" { + chain.Policy = (*BaseChainPolicy)(&match[6]) + } return nil } diff --git a/objects_test.go b/objects_test.go index 6a8fc93..37dd8b4 100644 --- a/objects_test.go +++ b/objects_test.go @@ -52,6 +52,21 @@ func TestObjects(t *testing.T) { object: &Table{Comment: PtrTo("foo")}, out: `add table ip mytable { comment "foo" ; }`, }, + { + name: "add table with flags", + verb: addVerb, + object: &Table{Flags: []TableFlag{DormantFlag}}, + out: `add table ip mytable { flags dormant ; }`, + }, + { + name: "add table with comment and flags", + verb: addVerb, + object: &Table{ + Comment: PtrTo("foo"), + Flags: []TableFlag{DormantFlag}, + }, + out: `add table ip mytable { comment "foo" ; flags dormant ; }`, + }, { name: "create table", verb: createVerb, @@ -227,6 +242,12 @@ func TestObjects(t *testing.T) { object: &Chain{Name: "mychain", Type: PtrTo(NATType), Hook: PtrTo(PostroutingHook), Priority: PtrTo(SNATPriority), Comment: PtrTo("foo")}, out: `add chain ip mytable mychain { type nat hook postrouting priority 100 ; comment "foo" ; }`, }, + { + name: "add base chain with policy", + verb: addVerb, + object: &Chain{Name: "mychain", Type: PtrTo(NATType), Hook: PtrTo(PostroutingHook), Priority: PtrTo(SNATPriority), Policy: PtrTo(DropPolicy)}, + out: `add chain ip mytable mychain { type nat hook postrouting priority 100 ; policy drop ; }`, + }, { name: "add base chain with device", verb: addVerb, @@ -324,7 +345,13 @@ func TestObjects(t *testing.T) { err: "must not specify Type or Priority", }, { - name: "invalid add non-base chain with device", + name: "invalid add non-base chain with Policy", + verb: addVerb, + object: &Chain{Name: "mychain", Policy: PtrTo(AcceptPolicy)}, + err: "must not specify Policy", + }, + { + name: "invalid add non-base chain with Device", verb: addVerb, object: &Chain{Name: "mychain", Device: PtrTo("eth0")}, err: "must not specify Device", diff --git a/types.go b/types.go index 1d0da2c..c0c42ca 100644 --- a/types.go +++ b/types.go @@ -71,6 +71,15 @@ const ( NetDevFamily Family = "netdev" ) +// TableFlag represents a table flag +type TableFlag string + +const ( + // DormantFlag indicates that a table is not currently evaluated. (Its base chains + // are unregistered.) + DormantFlag TableFlag = "dormant" +) + // Table represents an nftables table. type Table struct { // Comment is an optional comment for the table. (Requires kernel >= 5.10 and @@ -78,6 +87,9 @@ type Table struct { // nft >= 1.0.8 to include comments in List() results.) Comment *string + // Flags are the table flags + Flags []TableFlag + // Handle is an identifier that can be used to uniquely identify an object when // deleting it. When adding a new object, this must be nil. Handle *int @@ -185,6 +197,19 @@ const ( SNATPriority BaseChainPriority = "srcnat" ) +// BaseChainPolicy sets what happens to packets not explicitly accepted or refused by a +// base chain. +type BaseChainPolicy string + +const ( + // AcceptPolicy, which is the default, accepts any unmatched packets (though, + // as with any other nftables chain, a later chain can drop or reject it). + AcceptPolicy BaseChainPolicy = "accept" + + // DropPolicy drops any unmatched packets. + DropPolicy BaseChainPolicy = "drop" +) + // Chain represents an nftables chain; either a "base chain" (if Type, Hook, and Priority // are specified), or a "regular chain" (if they are not). type Chain struct { @@ -201,6 +226,10 @@ type Chain struct { // a regular chain. You can call ParsePriority() to convert this to a number. Priority *BaseChainPriority + // Policy is the policy for packets not explicitly accepted or refused by a base + // chain. + Policy *BaseChainPolicy + // Device is the network interface that the chain is attached to; this must be set // for a base chain connected to the "ingress" or "egress" hooks, and unset for // all other chains.