diff --git a/cmd/ctrld/prog_linux.go b/cmd/ctrld/prog_linux.go index 36bc316..7dc14d9 100644 --- a/cmd/ctrld/prog_linux.go +++ b/cmd/ctrld/prog_linux.go @@ -26,6 +26,13 @@ func setDependencies(svc *service.Config) { svc.Dependencies = append(svc.Dependencies, "Wants=dnsmasq.service") svc.Dependencies = append(svc.Dependencies, "After=dnsmasq.service") } + // On Firewalla, ctrld needs to start after firerouter_{dhcp,dns}, so it can read leases file. + if router.Name() == router.Firewalla { + svc.Dependencies = append(svc.Dependencies, "Wants=firerouter_dhcp.service") + svc.Dependencies = append(svc.Dependencies, "After=firerouter_dhcp.service") + svc.Dependencies = append(svc.Dependencies, "Wants=firerouter_dns.service") + svc.Dependencies = append(svc.Dependencies, "After=firerouter_dns.service") + } } func setWorkingDirectory(svc *service.Config, dir string) { diff --git a/internal/router/client_info.go b/internal/router/client_info.go index 8fc5709..c708b75 100644 --- a/internal/router/client_info.go +++ b/internal/router/client_info.go @@ -21,16 +21,17 @@ type readClientInfoFunc func(name string) error // clientInfoFiles specifies client info files and how to read them on supported platforms. var clientInfoFiles = map[string]readClientInfoFunc{ - "/tmp/dnsmasq.leases": dnsmasqReadClientInfoFile, // ddwrt - "/tmp/dhcp.leases": dnsmasqReadClientInfoFile, // openwrt - "/var/lib/misc/dnsmasq.leases": dnsmasqReadClientInfoFile, // merlin - "/mnt/data/udapi-config/dnsmasq.lease": dnsmasqReadClientInfoFile, // UDM Pro - "/data/udapi-config/dnsmasq.lease": dnsmasqReadClientInfoFile, // UDR - "/etc/dhcpd/dhcpd-leases.log": dnsmasqReadClientInfoFile, // Synology - "/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 + "/tmp/dnsmasq.leases": dnsmasqReadClientInfoFile, // ddwrt + "/tmp/dhcp.leases": dnsmasqReadClientInfoFile, // openwrt + "/var/lib/misc/dnsmasq.leases": dnsmasqReadClientInfoFile, // merlin + "/mnt/data/udapi-config/dnsmasq.lease": dnsmasqReadClientInfoFile, // UDM Pro + "/data/udapi-config/dnsmasq.lease": dnsmasqReadClientInfoFile, // UDR + "/etc/dhcpd/dhcpd-leases.log": dnsmasqReadClientInfoFile, // Synology + "/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 + "/home/pi/.router/run/dhcp/dnsmasq.leases": dnsmasqReadClientInfoFile, // Firewalla } // watchClientInfoTable watches changes happens in dnsmasq/dhcpd diff --git a/internal/router/dnsmasq.go b/internal/router/dnsmasq.go index 4d43d20..2ca3249 100644 --- a/internal/router/dnsmasq.go +++ b/internal/router/dnsmasq.go @@ -1,6 +1,7 @@ package router import ( + "fmt" "strings" "text/template" ) @@ -49,8 +50,10 @@ func dnsMasqConf() (string, error) { var sb strings.Builder var tmplText string switch Name() { - case EdgeOS, DDWrt, OpenWrt, Ubios, Synology, Tomato: + case DDWrt, EdgeOS, OpenWrt, Ubios, Synology, Tomato: tmplText = dnsMasqConfigContentTmpl + case Firewalla: + tmplText = dnsMasqConfigContentTmpl + fmt.Sprintf("listen-address=127.0.0.1\n") case Merlin: tmplText = merlinDNSMasqPostConfTmpl } @@ -68,10 +71,12 @@ func dnsMasqConf() (string, error) { func restartDNSMasq() error { switch Name() { - case EdgeOS: - return edgeOSRestartDNSMasq() case DDWrt: return ddwrtRestartDNSMasq() + case EdgeOS: + return edgeOSRestartDNSMasq() + case Firewalla: + return firewallaRestartDNSMasq() case Merlin: return merlinRestartDNSMasq() case OpenWrt: diff --git a/internal/router/firewalla.go b/internal/router/firewalla.go new file mode 100644 index 0000000..7b7e6ea --- /dev/null +++ b/internal/router/firewalla.go @@ -0,0 +1,103 @@ +package router + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" +) + +const ( + firewallaDNSMasqConfigPath = "/home/pi/.firewalla/config/dnsmasq_local" + firewallaDNSMasqBackupConfigPath = "/home/pi/.firewalla/config/dnsmasq_local.bak" + firewallaConfigPostMainDir = "/home/pi/.firewalla/config/post_main.d" + firewallaCtrldInitScriptPath = "/home/pi/.firewalla/config/post_main.d/start_ctrld.sh" +) + +func setupFirewalla() error { + fi, err := os.Stat(firewallaDNSMasqConfigPath) + if err != nil { + return fmt.Errorf("setupFirewalla: get current config directory: %w", err) + } + + _ = os.RemoveAll(firewallaDNSMasqBackupConfigPath) + + // Creating a backup. + if err := os.Rename(firewallaDNSMasqConfigPath, firewallaDNSMasqBackupConfigPath); err != nil { + return fmt.Errorf("setupFirewalla: backup current config: %w", err) + } + + // Creating our own config. + if err := os.MkdirAll(firewallaDNSMasqConfigPath, fi.Mode()); err != nil { + return fmt.Errorf("setupFirewalla: creating config dir: %w", err) + } + + // Adding ctrld listener as the only upstream. + dnsMasqConfigContent, err := dnsMasqConf() + if err != nil { + return fmt.Errorf("setupFirewalla: generating dnsmasq config: %w", err) + } + ctrldConfPath := filepath.Join(firewallaDNSMasqConfigPath, "ctrld") + if err := os.WriteFile(ctrldConfPath, []byte(dnsMasqConfigContent), 0600); err != nil { + return fmt.Errorf("setupFirewalla: writing ctrld config: %w", err) + } + + // Restart dnsmasq service. + if err := restartDNSMasq(); err != nil { + return fmt.Errorf("setupFirewalla: restartDNSMasq: %w", err) + } + + return nil +} + +func cleanupFirewalla() error { + // Do nothing if there's no backup config. + if _, err := os.Stat(firewallaDNSMasqBackupConfigPath); err != nil && os.IsNotExist(err) { + return nil + } + + // Removing current config. + if err := os.RemoveAll(firewallaDNSMasqConfigPath); err != nil { + return fmt.Errorf("cleanupFirewalla: removing ctrld config: %w", err) + } + + // Restoring backup. + if err := os.Rename(firewallaDNSMasqBackupConfigPath, firewallaDNSMasqConfigPath); err != nil { + return fmt.Errorf("cleanupFirewalla: restoring backup config: %w", err) + } + + // Restart dnsmasq service. + if err := restartDNSMasq(); err != nil { + return fmt.Errorf("cleaupFirewalla: restartDNSMasq: %w", err) + } + + return nil +} + +func postInstallFirewalla() error { + // Writing startup script. + if err := writeFirewallStartupScript(); err != nil { + return fmt.Errorf("postInstallFirewalla: writing startup script: %w", err) + } + return nil +} + +func firewallaRestartDNSMasq() error { + return exec.Command("systemctl", "restart", "firerouter_dns").Run() +} + +func writeFirewallStartupScript() error { + if err := os.MkdirAll(firewallaConfigPostMainDir, 0775); err != nil { + return err + } + exe, err := os.Executable() + if err != nil { + return err + } + // This is called when "ctrld start ..." runs, so recording + // the same command line arguments to use in startup script. + argStr := strings.Join(os.Args[1:], " ") + script := fmt.Sprintf("#!/bin/bash\n\nsudo %q %s\n", exe, argStr) + return os.WriteFile(firewallaCtrldInitScriptPath, []byte(script), 0755) +} diff --git a/internal/router/router.go b/internal/router/router.go index 29a95d5..d3beda7 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -19,14 +19,15 @@ import ( ) const ( - OpenWrt = "openwrt" - DDWrt = "ddwrt" - Merlin = "merlin" - Ubios = "ubios" - Synology = "synology" - Tomato = "tomato" - EdgeOS = "edgeos" - Pfsense = "pfsense" + DDWrt = "ddwrt" + EdgeOS = "edgeos" + Firewalla = "firewalla" + Merlin = "merlin" + OpenWrt = "openwrt" + Pfsense = "pfsense" + Synology = "synology" + Tomato = "tomato" + Ubios = "ubios" ) // ErrNotSupported reports the current router is not supported error. @@ -44,7 +45,15 @@ 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, Pfsense, Synology, Tomato, Ubios: + case DDWrt, + EdgeOS, + Firewalla, + Merlin, + OpenWrt, + Pfsense, + Synology, + Tomato, + Ubios: return true } return false @@ -52,25 +61,44 @@ 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, Pfsense, Synology, Tomato, Ubios} + return []string{ + DDWrt, + EdgeOS, + Firewalla, + Merlin, + OpenWrt, + Pfsense, + Synology, + Tomato, + Ubios, + } } var configureFunc = map[string]func() error{ - EdgeOS: setupEdgeOS, - DDWrt: setupDDWrt, - Merlin: setupMerlin, - OpenWrt: setupOpenWrt, - Pfsense: setupPfsense, - Synology: setupSynology, - Tomato: setupTomato, - Ubios: setupUbiOS, + DDWrt: setupDDWrt, + EdgeOS: setupEdgeOS, + Firewalla: setupFirewalla, + Merlin: setupMerlin, + OpenWrt: setupOpenWrt, + Pfsense: setupPfsense, + Synology: setupSynology, + Tomato: setupTomato, + Ubios: setupUbiOS, } // Configure configures things for running ctrld on the router. func Configure(c *ctrld.Config) error { name := Name() switch name { - case EdgeOS, DDWrt, Merlin, OpenWrt, Pfsense, Synology, Tomato, Ubios: + case DDWrt, + EdgeOS, + Firewalla, + Merlin, + OpenWrt, + Pfsense, + Synology, + Tomato, + Ubios: if c.HasUpstreamSendClientInfo() { r := routerPlatform.Load() r.sendClientInfo = true @@ -107,7 +135,7 @@ func ConfigureService(sc *service.Config) error { sc.Option["SysvScript"] = openWrtScript case Pfsense: sc.Option["SysvScript"] = pfsenseInitScript - case EdgeOS, Merlin, Synology, Tomato, Ubios: + case EdgeOS, Firewalla, Merlin, Synology, Tomato, Ubios: } return nil } @@ -138,10 +166,12 @@ func PreRun() (err error) { func PostInstall(svc *service.Config) error { name := Name() switch name { - case EdgeOS: - return postInstallEdgeOS() case DDWrt: return postInstallDDWrt() + case EdgeOS: + return postInstallEdgeOS() + case Firewalla: + return postInstallFirewalla() case Merlin: return postInstallMerlin() case OpenWrt: @@ -162,10 +192,12 @@ func PostInstall(svc *service.Config) error { func Cleanup(svc *service.Config) error { name := Name() switch name { - case EdgeOS: - return cleanupEdgeOS() case DDWrt: return cleanupDDWrt() + case EdgeOS: + return cleanupEdgeOS() + case Firewalla: + return cleanupFirewalla() case Merlin: return cleanupMerlin() case OpenWrt: @@ -186,7 +218,7 @@ func Cleanup(svc *service.Config) error { func ListenAddress() string { name := Name() switch name { - case EdgeOS, DDWrt, Merlin, OpenWrt, Synology, Tomato, Ubios: + case DDWrt, Firewalla, Merlin, OpenWrt, Synology, Tomato, Ubios: return "127.0.0.1:5354" case Pfsense: // On pfsense, we run ctrld as DNS resolver. @@ -227,6 +259,8 @@ func distroName() string { return EdgeOS // For 2.x case isPfsense(): return Pfsense + case haveFile("/etc/firewalla_release"): + return Firewalla } return "" }