Compare commits

..

3 Commits

Author SHA1 Message Date
Antonio Ojea 76be8b8092
Merge 1e48c1007e into 6f574e7fd1 2025-02-03 16:23:47 +01:00
Alexander 6f574e7fd1
added numgen case in exprFromName (#297) 2025-02-03 16:23:31 +01:00
Antonio Ojea 1e48c1007e nf2go: convert nftables rules to golang code
One of the biggest barriers to adopt the netlink format for nftables is
the complexity of writing bytecode.

This commits adds a tool that allows to take an nftables dump and
generate the corresponding golang code and validating that the generated
code produces the exact same output.

Change-Id: I491b35e0d8062de33c67091dd4126d843b231838
Signed-off-by: Antonio Ojea <aojea@google.com>
2025-02-03 15:16:57 +00:00
2 changed files with 67 additions and 56 deletions

View File

@ -209,6 +209,8 @@ func exprFromName(name string) Any {
e = &CtTimeout{}
case "fib":
e = &Fib{}
case "numgen":
e = &Numgen{}
}
return e
}

View File

@ -2,6 +2,7 @@ package main
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
@ -12,6 +13,7 @@ import (
"regexp"
"runtime"
"strings"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/nftables"
@ -69,6 +71,7 @@ func main() {
pf("\n")
pf("\tvar expressions []expr.Any\n")
pf("\tvar chain *nftables.Chain\n")
pf("\tvar table *nftables.Table\n")
tables, err := n.ListTables()
if err != nil {
@ -81,7 +84,9 @@ func main() {
}
for _, table := range tables {
pf("\ttable:= n.AddTable(&nftables.Table{Family: %s,Name: \"%s\"})\n", TableFamilyString(table.Family), table.Name)
log.Printf("processing table: %s", table.Name)
pf("\ttable = n.AddTable(&nftables.Table{Family: %s,Name: \"%s\"})\n", TableFamilyString(table.Family), table.Name)
for _, chain := range chains {
if chain.Table.Name != table.Name {
continue
@ -120,74 +125,78 @@ func main() {
pf("\t})\n")
}
}
}
pf("\n\tif err:= n.Flush(); err!= nil {\n")
pf("\t\tlog.Fatalf(\"fail to flush rules: %v\", err)\n")
pf("\t}\n\n")
pf("\tfmt.Println(\"nft ruleset applied.\")\n")
pf("}\n")
pf("\n\tif err:= n.Flush(); err!= nil {\n")
pf("\t\tlog.Fatal(err)\n")
pf("\t}\n\n")
pf("\tfmt.Println(\"nft ruleset applied.\")\n")
pf("}\n")
// Program nftables using your Go code
if err := flushNFTRuleset(); err != nil {
log.Fatalf("Failed to flush nftables ruleset: %v", err)
}
// Program nftables using your Go code
if err := flushNFTRuleset(); err != nil {
log.Fatalf("Failed to flush nftables ruleset: %v", err)
}
// Create the output file
// Create a temporary directory
tempDir, err := ioutil.TempDir("", "nftables_gen")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(tempDir) // Clean up the temporary directory
// Create the output file
// Create a temporary directory
tempDir, err := ioutil.TempDir("", "nftables_gen")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(tempDir) // Clean up the temporary directory
// Create the temporary Go file
tempGoFile := filepath.Join(tempDir, "nftables_recreate.go")
f, err := os.Create(tempGoFile)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// Create the temporary Go file
tempGoFile := filepath.Join(tempDir, "nftables_recreate.go")
f, err := os.Create(tempGoFile)
if err != nil {
log.Fatal(err)
}
defer f.Close()
fmt.Println("Generated code:")
mw := io.MultiWriter(f, os.Stdout)
buf.WriteTo(mw)
mw := io.MultiWriter(f, os.Stdout)
buf.WriteTo(mw)
// Format the generated code
log.Printf("formating file: %s", tempGoFile)
cmd := exec.Command("gofmt", "-w", "-s", tempGoFile)
output, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("gofmt error: %v\nOutput: %s", err, output)
}
// Format the generated code
cmd := exec.Command("gofmt", "-w", "-s", tempGoFile)
output, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("gofmt error: %v\nOutput: %s", err, output)
}
// Run the generated code
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Run the generated code
cmd = exec.Command("go", "run", tempGoFile)
output, err = cmd.CombinedOutput()
if err != nil {
log.Fatalf("Execution error: %v\nOutput: %s", err, output)
}
log.Printf("executing file: %s", tempGoFile)
cmd = exec.CommandContext(ctx, "go", "run", tempGoFile)
output, err = cmd.CombinedOutput()
if err != nil {
log.Fatalf("Execution error: %v\nOutput: %s", err, output)
}
// Retrieve nftables state using nft
actualOutput, err := listNFTRuleset()
if err != nil {
log.Fatalf("Failed to list nftables ruleset: %v\noutput:%s", err, actualOutput)
}
// Retrieve nftables state using nft
log.Printf("obtain current ruleset: %s", tempGoFile)
actualOutput, err := listNFTRuleset()
if err != nil {
log.Fatalf("Failed to list nftables ruleset: %v\noutput:%s", err, actualOutput)
}
expectedOutput, err := os.ReadFile(filename)
if err != nil {
log.Fatalf("Failed to list nftables ruleset: %v\noutput:%s", err, actualOutput)
}
expectedOutput, err := os.ReadFile(filename)
if err != nil {
log.Fatalf("Failed to list nftables ruleset: %v\noutput:%s", err, actualOutput)
}
if !compareMultilineStringsIgnoreIndentation(string(expectedOutput), actualOutput) {
log.Printf("Expected output:\n%s", string(expectedOutput))
log.Printf("Actual output:\n%s", actualOutput)
if !compareMultilineStringsIgnoreIndentation(string(expectedOutput), actualOutput) {
log.Printf("Expected output:\n%s", string(expectedOutput))
log.Printf("Actual output:\n%s", actualOutput)
log.Fatalf("nftables ruleset mismatch:\n%s", cmp.Diff(string(expectedOutput), actualOutput))
}
log.Fatalf("nftables ruleset mismatch:\n%s", cmp.Diff(string(expectedOutput), actualOutput))
}
if err := flushNFTRuleset(); err != nil {
log.Fatalf("Failed to flush nftables ruleset: %v", err)
}
if err := flushNFTRuleset(); err != nil {
log.Fatalf("Failed to flush nftables ruleset: %v", err)
}
}