diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 27e009c..1d07863 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -312,7 +312,7 @@ func initCLI() { {s.Start, true}, } if doTasks(tasks) { - if err := router.PostInstall(); err != nil { + if err := router.PostInstall(svcConfig); err != nil { mainLog.Warn().Err(err).Msg("post installation failed, please check system/service log for details error") return } @@ -468,7 +468,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, } prog.resetDNS() mainLog.Debug().Msg("Router cleanup") - if err := router.Cleanup(); err != nil { + if err := router.Cleanup(svcConfig); err != nil { mainLog.Warn().Err(err).Msg("could not cleanup router") } mainLog.Notice().Msg("Service uninstalled") diff --git a/cmd/ctrld/cli_router_linux.go b/cmd/ctrld/cli_router.go similarity index 99% rename from cmd/ctrld/cli_router_linux.go rename to cmd/ctrld/cli_router.go index b6a4ffc..3d54c1c 100644 --- a/cmd/ctrld/cli_router_linux.go +++ b/cmd/ctrld/cli_router.go @@ -1,3 +1,5 @@ +//go:build linux || freebsd + package main import ( diff --git a/cmd/ctrld/cli_router_others.go b/cmd/ctrld/cli_router_others.go index 4a7b8c7..4934b5c 100644 --- a/cmd/ctrld/cli_router_others.go +++ b/cmd/ctrld/cli_router_others.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !linux && !freebsd package main diff --git a/internal/router/client_info.go b/internal/router/client_info.go index db6294f..1548c67 100644 --- a/internal/router/client_info.go +++ b/internal/router/client_info.go @@ -28,6 +28,7 @@ var clientInfoFiles = map[string]readClientInfoFunc{ "/tmp/var/lib/misc/dnsmasq.leases": dnsmasqReadClientInfoFile, // Tomato "/run/dnsmasq-dhcp.leases": dnsmasqReadClientInfoFile, // EdgeOS "/run/dhcpd.leases": iscDHCPReadClientInfoFile, // EdgeOS + "/var/dhcpd/var/db/dhcpd.leases": iscDHCPReadClientInfoFile, // Pfsense } func (r *router) watchClientInfoTable() { diff --git a/internal/router/pfsense.go b/internal/router/pfsense.go new file mode 100644 index 0000000..bae3dc2 --- /dev/null +++ b/internal/router/pfsense.go @@ -0,0 +1,56 @@ +package router + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/kardianos/service" +) + +const ( + rcPath = "/usr/local/etc/rc.d" + unboundRcPath = rcPath + "/unbound" +) + +func setupPfsense() error { + // If Pfsense is in DNS Resolver mode, ensure no unbound processes running. + if _, err := exec.Command("service", "unbound", "onestatus").CombinedOutput(); err == nil { + if out, err := exec.Command("killall", "unbound").CombinedOutput(); err != nil { + return fmt.Errorf("could not killall unbound: %s: %w", string(out), err) + } + } + // If Pfsense is in DNS Forwarder mode, ensure no dnsmasq processes running. + if _, err := exec.Command("service", "dnsmasq", "onestatus").CombinedOutput(); err == nil { + if out, err := exec.Command("killall", "dnsmasq").CombinedOutput(); err != nil { + return fmt.Errorf("could not killall unbound: %s: %w", string(out), err) + } + } + return nil +} + +func cleanupPfsense(svc *service.Config) error { + if err := os.Remove(filepath.Join(rcPath, svc.Name+".sh")); err != nil { + return fmt.Errorf("os.Remove: %w", err) + } + if out, err := exec.Command(unboundRcPath, "onerestart").CombinedOutput(); err != nil { + return fmt.Errorf("could not restart unbound: %s: %w", string(out), err) + } + if out, err := exec.Command(unboundRcPath, "onerestart").CombinedOutput(); err != nil { + return fmt.Errorf("could not restart unbound: %s: %w", string(out), err) + } + return nil +} + +func postInstallPfsense(svc *service.Config) error { + // pfsense need ".sh" extension for script to be run at boot. + // See: https://docs.netgate.com/pfsense/en/latest/development/boot-commands.html#shell-script-option + oldname := filepath.Join(rcPath, svc.Name) + newname := filepath.Join(rcPath, svc.Name+".sh") + _ = os.Remove(newname) + if err := os.Symlink(oldname, newname); err != nil { + return fmt.Errorf("os.Symlink: %w", err) + } + return nil +} diff --git a/internal/router/router.go b/internal/router/router.go index b5269f1..81246e4 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -22,6 +22,7 @@ const ( Synology = "synology" Tomato = "tomato" EdgeOS = "edgeos" + Pfsense = "pfsense" ) // ErrNotSupported reports the current router is not supported error. @@ -39,7 +40,7 @@ type router struct { // IsSupported reports whether the given platform is supported by ctrld. func IsSupported(platform string) bool { switch platform { - case EdgeOS, DDWrt, Merlin, OpenWrt, Synology, Tomato, Ubios: + case EdgeOS, DDWrt, Merlin, OpenWrt, Pfsense, Synology, Tomato, Ubios: return true } return false @@ -47,7 +48,7 @@ func IsSupported(platform string) bool { // SupportedPlatforms return all platforms that can be configured to run with ctrld. func SupportedPlatforms() []string { - return []string{EdgeOS, DDWrt, Merlin, OpenWrt, Synology, Tomato, Ubios} + return []string{EdgeOS, DDWrt, Merlin, OpenWrt, Pfsense, Synology, Tomato, Ubios} } var configureFunc = map[string]func() error{ @@ -55,6 +56,7 @@ var configureFunc = map[string]func() error{ DDWrt: setupDDWrt, Merlin: setupMerlin, OpenWrt: setupOpenWrt, + Pfsense: setupPfsense, Synology: setupSynology, Tomato: setupTomato, Ubios: setupUbiOS, @@ -64,7 +66,7 @@ var configureFunc = map[string]func() error{ func Configure(c *ctrld.Config) error { name := Name() switch name { - case EdgeOS, DDWrt, Merlin, OpenWrt, Synology, Tomato, Ubios: + case EdgeOS, DDWrt, Merlin, OpenWrt, Pfsense, Synology, Tomato, Ubios: if c.HasUpstreamSendClientInfo() { r := routerPlatform.Load() r.sendClientInfo = true @@ -99,7 +101,7 @@ func ConfigureService(sc *service.Config) error { } case OpenWrt: sc.Option["SysvScript"] = openWrtScript - case EdgeOS, Merlin, Synology, Tomato, Ubios: + case EdgeOS, Merlin, Pfsense, Synology, Tomato, Ubios: } return nil } @@ -118,7 +120,7 @@ func PreStart() (err error) { } // PostInstall performs task after installing ctrld on router. -func PostInstall() error { +func PostInstall(svc *service.Config) error { name := Name() switch name { case EdgeOS: @@ -129,6 +131,8 @@ func PostInstall() error { return postInstallMerlin() case OpenWrt: return postInstallOpenWrt() + case Pfsense: + return postInstallPfsense(svc) case Synology: return postInstallSynology() case Tomato: @@ -140,7 +144,7 @@ func PostInstall() error { } // Cleanup cleans ctrld setup on the router. -func Cleanup() error { +func Cleanup(svc *service.Config) error { name := Name() switch name { case EdgeOS: @@ -151,6 +155,8 @@ func Cleanup() error { return cleanupMerlin() case OpenWrt: return cleanupOpenWrt() + case Pfsense: + return cleanupPfsense(svc) case Synology: return cleanupSynology() case Tomato: @@ -167,6 +173,8 @@ func ListenAddress() string { switch name { case EdgeOS, DDWrt, Merlin, OpenWrt, Synology, Tomato, Ubios: return "127.0.0.1:5354" + case Pfsense: + // On pfsense, we run ctrld as DNS resolver. } return "" } @@ -200,6 +208,8 @@ func distroName() string { return EdgeOS case haveFile("/etc/ubnt/init/vyatta-router"): return EdgeOS // For 2.x + case isPfsense(): + return Pfsense } return "" } @@ -223,3 +233,8 @@ func unameU() []byte { out, _ := exec.Command("uname", "-u").Output() return out } + +func isPfsense() bool { + b, err := os.ReadFile("/etc/platform") + return err == nil && bytes.HasPrefix(b, []byte("pfSense")) +}