Skip to content

Commit

Permalink
Merge pull request #18 from aojea/flowtable
Browse files Browse the repository at this point in the history
add flowtable object
  • Loading branch information
k8s-ci-robot authored Oct 29, 2024
2 parents 52e5579 + 7f3100d commit d048b18
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 16 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ The `Transaction` methods take arguments of type `knftables.Object`.
The currently-supported objects are:

- `Table`
- `Flowtable`
- `Chain`
- `Rule`
- `Set`
Expand Down
71 changes: 62 additions & 9 deletions fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ type Fake struct {
type FakeTable struct {
Table

// Flowtables contains the table's flowtables, keyed by name
Flowtables map[string]*FakeFlowtable

// Chains contains the table's chains, keyed by name
Chains map[string]*FakeChain

Expand All @@ -61,6 +64,11 @@ type FakeTable struct {
Maps map[string]*FakeMap
}

// FakeFlowtable wraps Flowtable for the Fake implementation
type FakeFlowtable struct {
Flowtable
}

// FakeChain wraps Chain for the Fake implementation
type FakeChain struct {
Chain
Expand Down Expand Up @@ -110,6 +118,10 @@ func (fake *Fake) List(_ context.Context, objectType string) ([]string, error) {
var result []string

switch objectType {
case "flowtable", "flowtables":
for name := range fake.Table.Flowtables {
result = append(result, name)
}
case "chain", "chains":
for name := range fake.Table.Chains {
result = append(result, name)
Expand Down Expand Up @@ -236,17 +248,41 @@ func (fake *Fake) run(tx *Transaction) (*FakeTable, error) {
table := *obj
table.Handle = PtrTo(fake.nextHandle)
updatedTable = &FakeTable{
Table: table,
Chains: make(map[string]*FakeChain),
Sets: make(map[string]*FakeSet),
Maps: make(map[string]*FakeMap),
Table: table,
Flowtables: make(map[string]*FakeFlowtable),
Chains: make(map[string]*FakeChain),
Sets: make(map[string]*FakeSet),
Maps: make(map[string]*FakeMap),
}
case deleteVerb:
updatedTable = nil
default:
return nil, fmt.Errorf("unhandled operation %q", op.verb)
}

case *Flowtable:
existingFlowtable := updatedTable.Flowtables[obj.Name]
err := checkExists(op.verb, "flowtable", obj.Name, existingFlowtable != nil)
if err != nil {
return nil, err
}
switch op.verb {
case addVerb, createVerb:
if existingFlowtable != nil {
continue
}
flowtable := *obj
flowtable.Handle = PtrTo(fake.nextHandle)
updatedTable.Flowtables[obj.Name] = &FakeFlowtable{
Flowtable: flowtable,
}
case deleteVerb:
// FIXME delete-by-handle
delete(updatedTable.Flowtables, obj.Name)
default:
return nil, fmt.Errorf("unhandled operation %q", op.verb)
}

case *Chain:
existingChain := updatedTable.Chains[obj.Name]
err := checkExists(op.verb, "chain", obj.Name, existingChain != nil)
Expand Down Expand Up @@ -461,10 +497,14 @@ func checkRuleRefs(rule *Rule, table *FakeTable) error {
for i, word := range words {
if strings.HasPrefix(word, "@") {
name := word[1:]
if i > 0 && (words[i] == "map" || words[i] == "vmap") {
if i > 0 && (words[i-1] == "map" || words[i-1] == "vmap") {
if table.Maps[name] == nil {
return notFoundError("no such map %q", name)
}
} else if i > 0 && words[i-1] == "offload" {
if table.Flowtables[name] == nil {
return notFoundError("no such flowtable %q", name)
}
} else {
// recent nft lets you use a map in a set lookup
if table.Sets[name] == nil && table.Maps[name] == nil {
Expand Down Expand Up @@ -507,13 +547,18 @@ func (fake *Fake) Dump() string {
buf := &strings.Builder{}

table := fake.Table
flowtables := sortKeys(table.Flowtables)
chains := sortKeys(table.Chains)
sets := sortKeys(table.Sets)
maps := sortKeys(table.Maps)

// Write out all of the object adds first.

table.writeOperation(addVerb, &fake.nftContext, buf)
for _, fname := range flowtables {
ft := table.Flowtables[fname]
ft.writeOperation(addVerb, &fake.nftContext, buf)
}
for _, cname := range chains {
ch := table.Chains[cname]
ch.writeOperation(addVerb, &fake.nftContext, buf)
Expand Down Expand Up @@ -585,6 +630,8 @@ func (fake *Fake) ParseDump(data string) (err error) {
switch match[1] {
case "table":
obj = &Table{}
case "flowtable":
obj = &Flowtable{}
case "chain":
obj = &Chain{}
case "rule":
Expand Down Expand Up @@ -643,10 +690,16 @@ func (table *FakeTable) copy() *FakeTable {
}

tcopy := &FakeTable{
Table: table.Table,
Chains: make(map[string]*FakeChain),
Sets: make(map[string]*FakeSet),
Maps: make(map[string]*FakeMap),
Table: table.Table,
Flowtables: make(map[string]*FakeFlowtable),
Chains: make(map[string]*FakeChain),
Sets: make(map[string]*FakeSet),
Maps: make(map[string]*FakeMap),
}
for name, flowtable := range table.Flowtables {
tcopy.Flowtables[name] = &FakeFlowtable{
Flowtable: flowtable.Flowtable,
}
}
for name, chain := range table.Chains {
tcopy.Chains[name] = &FakeChain{
Expand Down
9 changes: 8 additions & 1 deletion fake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ func TestFakeRun(t *testing.T) {
Key: []string{"192.168.0.1", "tcp", "80"},
Value: []string{"drop"},
})
tx.Add(&Flowtable{
Name: "myflowtable",
Devices: []string{"eth0", "eth1"},
})

// The transaction should contain exactly those commands, in order
expected := strings.TrimPrefix(dedent.Dedent(`
Expand All @@ -102,6 +106,7 @@ func TestFakeRun(t *testing.T) {
add element ip kube-proxy map1 { 192.168.0.1 . tcp . 80 : goto chain }
add element ip kube-proxy map1 { 192.168.0.2 . tcp . 443 comment "with a comment" : goto anotherchain }
add element ip kube-proxy map1 { 192.168.0.1 . tcp . 80 : drop }
add flowtable ip kube-proxy myflowtable { devices = { eth0, eth1 } ; }
`), "\n")
diff := cmp.Diff(expected, tx.String())
if diff != "" {
Expand Down Expand Up @@ -168,6 +173,7 @@ func TestFakeRun(t *testing.T) {
// be seen.
expected = strings.TrimPrefix(dedent.Dedent(`
add table ip kube-proxy
add flowtable ip kube-proxy myflowtable { devices = { eth0, eth1 } ; }
add chain ip kube-proxy anotherchain
add chain ip kube-proxy chain { comment "foo" ; }
add map ip kube-proxy map1 { type ipv4_addr . inet_proto . inet_service : verdict ; }
Expand Down Expand Up @@ -208,6 +214,7 @@ func TestFakeRun(t *testing.T) {
}
expected = strings.TrimPrefix(dedent.Dedent(`
add table ip kube-proxy
add flowtable ip kube-proxy myflowtable { devices = { eth0, eth1 } ; }
add chain ip kube-proxy anotherchain
add chain ip kube-proxy chain { comment "foo" ; }
add map ip kube-proxy map1 { type ipv4_addr . inet_proto . inet_service : verdict ; }
Expand Down Expand Up @@ -594,6 +601,7 @@ func TestFakeParseDump(t *testing.T) {
ipFamily: IPv4Family,
dump: `
add table ip kube-proxy
add flowtable ip kube-proxy myflowtable { hook ingress priority filter ; devices = { eth0, eth1 } ; }
add chain ip kube-proxy anotherchain
add chain ip kube-proxy chain { comment "foo" ; }
add map ip kube-proxy map1 { type ipv4_addr . inet_proto . inet_service ; }
Expand All @@ -610,7 +618,6 @@ func TestFakeParseDump(t *testing.T) {
ipFamily: IPv4Family,
dump: `
add table ip kube-proxy { comment "rules for kube-proxy" ; }
add chain ip kube-proxy mark-for-masquerade
add chain ip kube-proxy masquerading
add chain ip kube-proxy services
Expand Down
13 changes: 7 additions & 6 deletions nftables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,14 @@ import (

func newTestInterface(t *testing.T, family Family, tableName string) (Interface, *fakeExec, error) {
fexec := newFakeExec(t)
ip := "ip"
if family == IPv6Family {
ip = "ip6"
}
fexec.expected = append(fexec.expected,
expectedCmd{
args: []string{"/nft", "--version"},
stdout: "nftables v1.0.7 (Old Doc Yak)\n",
},
expectedCmd{
args: []string{"/nft", "--check", "-f", "-"},
stdin: fmt.Sprintf("add table %s %s { comment \"test\" ; }\n", ip, tableName),
stdin: fmt.Sprintf("add table %s %s { comment \"test\" ; }\n", family, tableName),
},
)
nft, err := newInternal(family, tableName, fexec)
Expand Down Expand Up @@ -187,11 +183,16 @@ func TestRun(t *testing.T) {
Chain: "chain",
Rule: "ip daddr 10.0.0.0/8 drop",
})

tx.Add(&Flowtable{
Name: "flowtable",
Priority: PtrTo(FilterIngressPriority),
Devices: []string{"eth0", "eth1"},
})
expected := strings.TrimPrefix(dedent.Dedent(`
add table ip kube-proxy
add chain ip kube-proxy chain { comment "foo" ; }
add rule ip kube-proxy chain ip daddr 10.0.0.0/8 drop
add flowtable ip kube-proxy flowtable { hook ingress priority filter ; devices = { eth0, eth1 } ; }
`), "\n")
fexec.expected = append(fexec.expected,
expectedCmd{
Expand Down
77 changes: 77 additions & 0 deletions objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,3 +579,80 @@ func (element *Element) parse(line string) error {
}
return nil
}

// Object implementation for Flowtable
func (flowtable *Flowtable) validate(verb verb) error {
switch verb {
case addVerb, createVerb:
if flowtable.Name == "" {
return fmt.Errorf("no name specified for flowtable")
}
if flowtable.Handle != nil {
return fmt.Errorf("cannot specify Handle in %s operation", verb)
}
case deleteVerb:
if flowtable.Name == "" && flowtable.Handle == nil {
return fmt.Errorf("must specify either name or handle")
}
default:
return fmt.Errorf("%s is not implemented for flowtables", verb)
}

return nil
}

func (flowtable *Flowtable) writeOperation(verb verb, ctx *nftContext, writer io.Writer) {
// Special case for delete-by-handle
if verb == deleteVerb && flowtable.Handle != nil {
fmt.Fprintf(writer, "delete flowtable %s %s handle %d", ctx.family, ctx.table, *flowtable.Handle)
return
}

fmt.Fprintf(writer, "%s flowtable %s %s %s", verb, ctx.family, ctx.table, flowtable.Name)
if verb == addVerb || verb == createVerb {
fmt.Fprintf(writer, " {")

if flowtable.Priority != nil {
// since there is only one priority value allowed "filter" just use the value
// provided and not try to parse it.
fmt.Fprintf(writer, " hook ingress priority %s ;", *flowtable.Priority)
}

if len(flowtable.Devices) > 0 {
fmt.Fprintf(writer, " devices = { %s } ;", strings.Join(flowtable.Devices, ", "))
}

fmt.Fprintf(writer, " }")
}

fmt.Fprintf(writer, "\n")
}

// nft add flowtable inet example_table example_flowtable { hook ingress priority filter ; devices = { eth0 }; }
var flowtableRegexp = regexp.MustCompile(fmt.Sprintf(
`%s(?: {(?: hook ingress priority %s ;)(?: devices = {(.*)} ;) })?`,
noSpaceGroup, noSpaceGroup))

func (flowtable *Flowtable) parse(line string) error {
match := flowtableRegexp.FindStringSubmatch(line)
if match == nil {
return fmt.Errorf("failed parsing flowtableRegexp add command")
}
flowtable.Name = match[1]
if match[2] != "" {
flowtable.Priority = (*FlowtableIngressPriority)(&match[2])
}
// to avoid complex regular expressions the regex match everything between the brackets
// to match a single interface or a comma separated list of interfaces, and it is postprocessed
// here to remove the whitespaces.
if match[3] != "" {
devices := strings.Split(strings.TrimSpace(match[3]), ",")
for i := range devices {
devices[i] = strings.TrimSpace(devices[i])
}
if len(devices) > 0 {
flowtable.Devices = devices
}
}
return nil
}
Loading

0 comments on commit d048b18

Please sign in to comment.