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{} e = &CtTimeout{}
case "fib": case "fib":
e = &Fib{} e = &Fib{}
case "numgen":
e = &Numgen{}
} }
return e return e
} }

View File

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