From 06d82add96a6108e335e49f4ae2f8be30fc6458c Mon Sep 17 00:00:00 2001 From: Alex Tomic Date: Fri, 9 Aug 2024 15:08:12 -0400 Subject: [PATCH] ms8.1 - further improvements for single-binary operation Embed web-based and BPF C file templates so that nethadone can run entirely from a pre-built binary and not require a checkout of the source code External JS libraries will still be pulled from CDNs when accessing the web interface, tbd whether it's worth the trouble to embed those Github action to release AMD64, ARM64 and ARM single binary builds --- .github/workflows/build-nethadone-binary.yml | 32 +++++++++++++++ Makefile | 6 ++- cmd/nethadone.go | 6 ++- handlers/bpf.go | 41 +++++++++++++------- handlers/embed.go | 10 +++++ handlers/handlers.go | 2 - {ebpf => handlers}/throttle.bpf.c.tpl | 0 scripts/customize-image.sh | 19 +++++++++ views/embed.go | 7 ++++ 9 files changed, 104 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/build-nethadone-binary.yml create mode 100644 handlers/embed.go rename {ebpf => handlers}/throttle.bpf.c.tpl (100%) create mode 100644 views/embed.go diff --git a/.github/workflows/build-nethadone-binary.yml b/.github/workflows/build-nethadone-binary.yml new file mode 100644 index 0000000..ed871a7 --- /dev/null +++ b/.github/workflows/build-nethadone-binary.yml @@ -0,0 +1,32 @@ +name: Build nethadone binary + +on: + workflow_dispatch: + +jobs: + + build-nethadone-binary: + runs-on: ubuntu-22.04 + steps: + - id: generate_release_name + run: | + echo "release_name=nethadone-$(date -u -I)" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.22 + + - name: Build binaries + run: make all + + - name: Upload binaries to release + uses: svenstaro/upload-release-action@v2 + with: + repo_name: atomic77/nethadone + file: build/nethadone-* + tag: ${{ steps.generate_release_name.outputs.release_name }} + overwrite: true + file_glob: true \ No newline at end of file diff --git a/Makefile b/Makefile index 985dd23..3064f2c 100644 --- a/Makefile +++ b/Makefile @@ -27,8 +27,12 @@ up: ## Run the project in a local container run-root: go build -o build/ ./cmd/nethadone.go sudo build/nethadone -lan-interface $(NTH_LAN) -wan-interface $(NTH_WAN) -config-file config/nethadone.yml +all: + GOARCH=arm GOOS=linux go build -o build/nethadone-arm-linux ./cmd/nethadone.go + GOARCH=arm64 GOOS=linux go build -o build/nethadone-arm64-linux ./cmd/nethadone.go + GOARCH=amd64 GOOS=linux go build -o build/nethadone-arm-linux ./cmd/nethadone.go -build: ## Generate docker image +build: go build ./cmd/nethadone.go build-docker: ## Generate docker image diff --git a/cmd/nethadone.go b/cmd/nethadone.go index a7c332e..31102a3 100644 --- a/cmd/nethadone.go +++ b/cmd/nethadone.go @@ -3,12 +3,14 @@ package main import ( "flag" "log" + "net/http" "github.com/alecthomas/repr" "github.com/atomic77/nethadone/config" "github.com/atomic77/nethadone/database" "github.com/atomic77/nethadone/handlers" "github.com/atomic77/nethadone/policy" + "github.com/atomic77/nethadone/views" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/adaptor" "github.com/gofiber/fiber/v2/middleware/logger" @@ -32,8 +34,8 @@ func main() { database.Connect() handlers.Initialize() - // Pass the engine to the Views - engine := html.New("./views", ".tpl") + // Use embedded templates + engine := html.NewFileSystem(http.FS(views.EmbedTemplates), ".tpl") app := fiber.New(fiber.Config{ Views: engine, }) diff --git a/handlers/bpf.go b/handlers/bpf.go index 06a8b10..1bf44ca 100644 --- a/handlers/bpf.go +++ b/handlers/bpf.go @@ -88,28 +88,41 @@ func Initialize() { } func ApplyPolicies(ipPolicies *[]models.IpPolicy) { - rebuildBpf("ebpf/throttle.bpf.c.tpl", "ebpf/throttle.bpf.c", ipPolicies) - reattachThrottler(config.Cfg.LanInterface, tc.HandleMinEgress) + targFile, err := os.CreateTemp("", "throttle-*.bpf.c") + if err != nil { + log.Fatal("could not create throttler file: ", err) + } + objFile, err := os.CreateTemp("", "throttle-*.o") + if err != nil { + log.Fatal("could not create throttler object file: ", err) + } + + rebuildBpf("throttle.bpf.c.tpl", targFile, objFile, ipPolicies) + reattachThrottler(objFile, config.Cfg.LanInterface, tc.HandleMinEgress) + os.Remove(targFile.Name()) + os.Remove(objFile.Name()) } -func rebuildBpf(tplfile string, target string, ipPolicies *[]models.IpPolicy) { +func rebuildBpf(tplfile string, target *os.File, objFile *os.File, ipPolicies *[]models.IpPolicy) { log.Println("Rebuilding with ", len(*ipPolicies), " throttle targets from policy database") - - f, err := os.Create(target) - if err != nil { - log.Fatal("failed to create rendered file ", err) - } - tpl := template.Must(template.ParseFiles(tplfile)) + tpl := template.Must(template.ParseFS(EmbedThrottlerCode, tplfile)) type fdata struct { IpPolicies *[]models.IpPolicy } - err = tpl.Execute(f, fdata{IpPolicies: ipPolicies}) + + log.Println("Temporary file ", target.Name()) + err := tpl.Execute(target, fdata{IpPolicies: ipPolicies}) if err != nil { log.Fatal("failed to render file ", err) } // FIXME There surely must be a better way of doing this dynamically - cmd := exec.Command("make", "build-throttler") - cmd.Dir = "ebpf/" + cmd := exec.Command( + "clang", "-g", "-O2", + // Include both armv7 and aarch64 include folders + "-I/usr/include/aarch64-linux-gnu", "-I/usr/arm-linux-gnueabi/include", + "-Wall", "-target", "bpf", "-c", target.Name(), "-o", objFile.Name(), + ) + cmd.Dir = os.TempDir() out, err := cmd.CombinedOutput() if err != nil { log.Fatal("failed to rebuild throttler eBPF, out: ", string(out), " err: ", err) @@ -144,7 +157,7 @@ func cleanupThrottler(iface *net.Interface) { } -func reattachThrottler(ifname string, direction uint32) { +func reattachThrottler(objFile *os.File, ifname string, direction uint32) { log.Println("(Re)attaching throttler BPF to if ", ifname, " direction ", direction) @@ -157,7 +170,7 @@ func reattachThrottler(ifname string, direction uint32) { cleanupThrottler(iface) BpfCtx.ThrottleObjs = &throttleObjects{} - spec, err := ebpf.LoadCollectionSpec("ebpf/throttle.o") + spec, err := ebpf.LoadCollectionSpec(objFile.Name()) if err != nil { log.Fatal("failed to load spec ", err) } diff --git a/handlers/embed.go b/handlers/embed.go new file mode 100644 index 0000000..7943771 --- /dev/null +++ b/handlers/embed.go @@ -0,0 +1,10 @@ +package handlers + +import "embed" + +// Currently we are handling the management of the throttler +// ebpf outside of bpf2go due to challenges figuring out how to allow +// live recompiling + +//go:embed throttle.bpf.c.tpl +var EmbedThrottlerCode embed.FS diff --git a/handlers/handlers.go b/handlers/handlers.go index f31c51e..22abcc4 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -17,8 +17,6 @@ import ( "net" ) -// var config Config - func Index(c *fiber.Ctx) error { // Render index return c.Render("index", fiber.Map{ diff --git a/ebpf/throttle.bpf.c.tpl b/handlers/throttle.bpf.c.tpl similarity index 100% rename from ebpf/throttle.bpf.c.tpl rename to handlers/throttle.bpf.c.tpl diff --git a/scripts/customize-image.sh b/scripts/customize-image.sh index 0ff80da..ae9550d 100644 --- a/scripts/customize-image.sh +++ b/scripts/customize-image.sh @@ -67,6 +67,25 @@ WantedBy=multi-user.target EOF +## FIXME The interfaces are not being read from config file due to a bug in nethadone.go +cat < /etc/systemd/system/nethadone.service +[Unit] +Description=Nethadone +Wants=network-online.target +After=network-online.target + +[Service] +User=root +Restart=on-failure +RuntimeMaxSec=86400 + +ExecStart=/usr/local/bin/nethadone --config-file=/etc/nethadone.yml + +[Install] +WantedBy=multi-user.target + +EOF + systemctl daemon-reload systemctl enable prometheus diff --git a/views/embed.go b/views/embed.go new file mode 100644 index 0000000..d75a4a2 --- /dev/null +++ b/views/embed.go @@ -0,0 +1,7 @@ +package views + +import "embed" + +//go:embed *.tpl +//go:embed **/*.tpl +var EmbedTemplates embed.FS