mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
all: refactor router code to use interface
So the code is more modular, easier to read/maintain.
This commit is contained in:
committed by
Cuong Manh Le
parent
78a7c87ecc
commit
aec2596262
@@ -34,6 +34,9 @@ import (
|
||||
"github.com/Control-D-Inc/ctrld/internal/controld"
|
||||
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/ddwrt"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/merlin"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/tomato"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -165,15 +168,20 @@ func initCLI() {
|
||||
mainLog.Fatal().Msg("network is not up yet")
|
||||
}
|
||||
|
||||
p.router = router.NewDummyRouter()
|
||||
if setupRouter {
|
||||
p.router = router.New(&cfg)
|
||||
}
|
||||
|
||||
// Processing --cd flag require connecting to ControlD API, which needs valid
|
||||
// time for validating server certificate. Some routers need NTP synchronization
|
||||
// to set the current time, so this check must happen before processCDFlags.
|
||||
if err := router.PreRun(svcConfig); err != nil {
|
||||
if err := p.router.PreRun(); err != nil {
|
||||
mainLog.Fatal().Err(err).Msg("failed to perform router pre-run check")
|
||||
}
|
||||
|
||||
oldLogPath := cfg.Service.LogPath
|
||||
processCDFlags()
|
||||
processCDFlags(p)
|
||||
if newLogPath := cfg.Service.LogPath; newLogPath != "" && oldLogPath != newLogPath {
|
||||
// After processCDFlags, log config may change, so reset mainLog and re-init logging.
|
||||
mainLog = zerolog.New(io.Discard)
|
||||
@@ -216,7 +224,7 @@ func initCLI() {
|
||||
|
||||
if setupRouter {
|
||||
switch platform := router.Name(); {
|
||||
case platform == router.DDWrt:
|
||||
case platform == ddwrt.Name:
|
||||
rootCertPool = certs.CACertPool()
|
||||
fallthrough
|
||||
case platform != "":
|
||||
@@ -226,13 +234,13 @@ func initCLI() {
|
||||
}
|
||||
p.onStarted = append(p.onStarted, func() {
|
||||
mainLog.Debug().Msg("Router setup")
|
||||
if err := router.Configure(&cfg); err != nil {
|
||||
if err := p.router.Setup(); err != nil {
|
||||
mainLog.Error().Err(err).Msg("could not configure router")
|
||||
}
|
||||
})
|
||||
p.onStopped = append(p.onStopped, func() {
|
||||
mainLog.Debug().Msg("Router cleanup")
|
||||
if err := router.Cleanup(svcConfig); err != nil {
|
||||
if err := p.router.Cleanup(); err != nil {
|
||||
mainLog.Error().Err(err).Msg("could not cleanup router")
|
||||
}
|
||||
p.resetDNS()
|
||||
@@ -285,7 +293,13 @@ func initCLI() {
|
||||
}
|
||||
setDependencies(sc)
|
||||
sc.Arguments = append([]string{"run"}, osArgs...)
|
||||
if err := router.ConfigureService(sc); err != nil {
|
||||
|
||||
p := &prog{router: router.NewDummyRouter()}
|
||||
if setupRouter {
|
||||
p.router = router.New(&cfg)
|
||||
}
|
||||
|
||||
if err := p.router.ConfigureService(sc); err != nil {
|
||||
mainLog.Fatal().Err(err).Msg("failed to configure service on router")
|
||||
}
|
||||
|
||||
@@ -311,7 +325,7 @@ func initCLI() {
|
||||
|
||||
initLogging()
|
||||
|
||||
processCDFlags()
|
||||
processCDFlags(p)
|
||||
|
||||
if err := ctrld.ValidateConfig(validator.New(), &cfg); err != nil {
|
||||
mainLog.Fatal().Msgf("invalid config: %v", err)
|
||||
@@ -324,7 +338,6 @@ func initCLI() {
|
||||
sc.Arguments = append(sc.Arguments, "--config="+defaultConfigFile)
|
||||
}
|
||||
|
||||
p := &prog{}
|
||||
s, err := newService(p, sc)
|
||||
if err != nil {
|
||||
mainLog.Error().Msg(err.Error())
|
||||
@@ -332,7 +345,7 @@ func initCLI() {
|
||||
}
|
||||
|
||||
mainLog.Debug().Msg("cleaning up router before installing")
|
||||
_ = router.Cleanup(svcConfig)
|
||||
_ = p.router.Cleanup()
|
||||
|
||||
tasks := []task{
|
||||
{s.Stop, false},
|
||||
@@ -341,7 +354,7 @@ func initCLI() {
|
||||
{s.Start, true},
|
||||
}
|
||||
if doTasks(tasks) {
|
||||
if err := router.PostInstall(svcConfig); err != nil {
|
||||
if err := p.router.Install(sc); err != nil {
|
||||
mainLog.Warn().Err(err).Msg("post installation failed, please check system/service log for details error")
|
||||
return
|
||||
}
|
||||
@@ -720,7 +733,7 @@ func processNoConfigFlags(noConfigStart bool) {
|
||||
v.Set("upstream", upstream)
|
||||
}
|
||||
|
||||
func processCDFlags() {
|
||||
func processCDFlags(p *prog) {
|
||||
if cdUID == "" {
|
||||
return
|
||||
}
|
||||
@@ -778,8 +791,9 @@ func processCDFlags() {
|
||||
switch {
|
||||
case setupRouter:
|
||||
if lc := cfg.Listener["0"]; lc != nil && lc.IP == "" {
|
||||
lc.IP = router.ListenIP()
|
||||
lc.Port = router.ListenPort()
|
||||
if err := p.router.Configure(); err != nil {
|
||||
mainLog.Fatal().Err(err).Msg("failed to change ctrld config for router")
|
||||
}
|
||||
}
|
||||
case useSystemdResolved:
|
||||
if lc := cfg.Listener["0"]; lc != nil {
|
||||
@@ -824,11 +838,12 @@ func processCDFlags() {
|
||||
Rules: rules,
|
||||
},
|
||||
}
|
||||
if setupRouter {
|
||||
lc.IP = router.ListenIP()
|
||||
lc.Port = router.ListenPort()
|
||||
}
|
||||
cfg.Listener["0"] = lc
|
||||
if setupRouter {
|
||||
if err := p.router.Configure(); err != nil {
|
||||
mainLog.Fatal().Err(err).Msg("failed to change ctrld config for router")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processLogAndCacheFlags()
|
||||
@@ -940,7 +955,7 @@ func unsupportedPlatformHelp(cmd *cobra.Command) {
|
||||
|
||||
func userHomeDir() (string, error) {
|
||||
switch router.Name() {
|
||||
case router.DDWrt, router.Merlin, router.Tomato:
|
||||
case ddwrt.Name, merlin.Name, tomato.Name:
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -988,7 +1003,8 @@ func uninstall(p *prog, s service.Service) {
|
||||
}
|
||||
initLogging()
|
||||
if doTasks(tasks) {
|
||||
if err := router.PostUninstall(svcConfig); err != nil {
|
||||
r := router.New(&cfg)
|
||||
if err := r.Uninstall(svcConfig); err != nil {
|
||||
mainLog.Warn().Err(err).Msg("post uninstallation failed, please check system/service log for details error")
|
||||
return
|
||||
}
|
||||
@@ -999,7 +1015,7 @@ func uninstall(p *prog, s service.Service) {
|
||||
mainLog.Debug().Msg("Router cleanup")
|
||||
// Stop already did router.Cleanup and report any error if happens,
|
||||
// ignoring error here to prevent false positive.
|
||||
_ = router.Cleanup(svcConfig)
|
||||
_ = r.Cleanup()
|
||||
mainLog.Notice().Msg("Service uninstalled")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ import (
|
||||
"github.com/Control-D-Inc/ctrld/internal/clientinfo"
|
||||
"github.com/Control-D-Inc/ctrld/internal/dnscache"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/ddwrt"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/firewalla"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/openwrt"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/ubios"
|
||||
)
|
||||
|
||||
const defaultSemaphoreCap = 256
|
||||
@@ -39,10 +43,11 @@ type prog struct {
|
||||
waitCh chan struct{}
|
||||
stopCh chan struct{}
|
||||
|
||||
cfg *ctrld.Config
|
||||
cache dnscache.Cacher
|
||||
sema semaphore
|
||||
mt *clientinfo.MacTable
|
||||
cfg *ctrld.Config
|
||||
cache dnscache.Cacher
|
||||
sema semaphore
|
||||
mt *clientinfo.MacTable
|
||||
router router.Router
|
||||
|
||||
started chan struct{}
|
||||
onStarted []func()
|
||||
@@ -207,7 +212,7 @@ func (p *prog) deAllocateIP() error {
|
||||
|
||||
func (p *prog) setDNS() {
|
||||
switch router.Name() {
|
||||
case router.DDWrt, router.OpenWrt, router.Ubios:
|
||||
case ddwrt.Name, openwrt.Name, ubios.Name:
|
||||
// On router, ctrld run as a DNS forwarder, it does not have to change system DNS.
|
||||
// Except for:
|
||||
// + EdgeOS, which /etc/resolv.conf could be managed by vyatta_update_resolv.pl script.
|
||||
@@ -236,7 +241,7 @@ func (p *prog) setDNS() {
|
||||
}
|
||||
logger.Debug().Msg("setting DNS for interface")
|
||||
ns := cfg.Listener["0"].IP
|
||||
if router.Name() == router.Firewalla && (ns == "127.0.0.1" || ns == "0.0.0.0" || ns == "") {
|
||||
if router.Name() == firewalla.Name && (ns == "127.0.0.1" || ns == "0.0.0.0" || ns == "") {
|
||||
// On Firewalla, the lo interface is excluded in all dnsmasq settings of all interfaces.
|
||||
// Thus, we use "br0" as the nameserver in /etc/resolv.conf file.
|
||||
if ns == "127.0.0.1" {
|
||||
@@ -264,7 +269,7 @@ func (p *prog) setDNS() {
|
||||
|
||||
func (p *prog) resetDNS() {
|
||||
switch router.Name() {
|
||||
case router.DDWrt, router.OpenWrt, router.Ubios:
|
||||
case ddwrt.Name, openwrt.Name, ubios.Name:
|
||||
// See comment in p.setDNS method.
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/dns"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/edgeos"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -29,7 +30,7 @@ func setDependencies(svc *service.Config) {
|
||||
"After=systemd-networkd-wait-online.service",
|
||||
}
|
||||
// On EdeOS, ctrld needs to start after vyatta-dhcpd, so it can read leases file.
|
||||
if router.Name() == router.EdgeOS {
|
||||
if router.Name() == edgeos.Name {
|
||||
svc.Dependencies = append(svc.Dependencies, "Wants=vyatta-dhcpd.service")
|
||||
svc.Dependencies = append(svc.Dependencies, "After=vyatta-dhcpd.service")
|
||||
svc.Dependencies = append(svc.Dependencies, "Wants=dnsmasq.service")
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/Control-D-Inc/ctrld/internal/certs"
|
||||
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/ddwrt"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -92,7 +93,7 @@ func FetchResolverConfig(uid, version string, cdDev bool) (*ResolverConfig, erro
|
||||
return d.DialContext(ctx, network, addrs)
|
||||
}
|
||||
|
||||
if router.Name() == router.DDWrt {
|
||||
if router.Name() == ddwrt.Name {
|
||||
transport.TLSClientConfig = &tls.Config{RootCAs: certs.CACertPool()}
|
||||
}
|
||||
client := http.Client{
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
const (
|
||||
nvramCtrldKeyPrefix = "ctrld_"
|
||||
nvramCtrldSetupKey = "ctrld_setup"
|
||||
nvramCtrldInstallKey = "ctrld_install"
|
||||
nvramRCStartupKey = "rc_startup"
|
||||
)
|
||||
|
||||
//lint:ignore ST1005 This error is for human.
|
||||
var errDdwrtJffs2NotEnabled = errors.New(`could not install service without jffs, follow this guide to enable:
|
||||
|
||||
https://wiki.dd-wrt.com/wiki/index.php/Journalling_Flash_File_System
|
||||
`)
|
||||
|
||||
func setupDDWrt() error {
|
||||
// Already setup.
|
||||
if val, _ := nvram("get", nvramCtrldSetupKey); val == "1" {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nvramKvMap := nvramSetupKV()
|
||||
nvramKvMap["dnsmasq_options"] = data
|
||||
if err := nvramSetKV(nvramKvMap, nvramCtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupDDWrt() error {
|
||||
// Restore old configs.
|
||||
if err := nvramRestore(nvramSetupKV(), nvramCtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postInstallDDWrt() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ddwrtRestartDNSMasq() error {
|
||||
if out, err := exec.Command("restart_dns").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("restart_dns: %s, %w", string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ddwrtJff2Enabled() bool {
|
||||
out, _ := nvram("get", "enable_jffs2")
|
||||
return out == "1"
|
||||
}
|
||||
115
internal/router/ddwrt/ddwrt.go
Normal file
115
internal/router/ddwrt/ddwrt.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package ddwrt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/ntp"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
|
||||
)
|
||||
|
||||
const Name = "ddwrt"
|
||||
|
||||
//lint:ignore ST1005 This error is for human.
|
||||
var errDdwrtJffs2NotEnabled = errors.New(`could not install service without jffs, follow this guide to enable:
|
||||
|
||||
https://wiki.dd-wrt.com/wiki/index.php/Journalling_Flash_File_System
|
||||
`)
|
||||
|
||||
var nvramKvMap = map[string]string{
|
||||
"dns_dnsmasq": "1", // Make dnsmasq running but disable DNS ability, ctrld will replace it.
|
||||
"dnsmasq_options": "", // Configuration of dnsmasq set by ctrld, filled by setupDDWrt.
|
||||
"dns_crypt": "0", // Disable DNSCrypt.
|
||||
"dnssec": "0", // Disable DNSSEC.
|
||||
}
|
||||
|
||||
type Ddwrt struct {
|
||||
cfg *ctrld.Config
|
||||
}
|
||||
|
||||
// New returns a router.Router for configuring/setup/run ctrld on ddwrt routers.
|
||||
func New(cfg *ctrld.Config) *Ddwrt {
|
||||
return &Ddwrt{cfg: cfg}
|
||||
}
|
||||
|
||||
func (d *Ddwrt) ConfigureService(config *service.Config) error {
|
||||
if !ddwrtJff2Enabled() {
|
||||
return errDdwrtJffs2NotEnabled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Ddwrt) Install(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Ddwrt) Uninstall(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Ddwrt) PreRun() error {
|
||||
_ = d.Cleanup()
|
||||
return ntp.Wait()
|
||||
}
|
||||
|
||||
func (d *Ddwrt) Configure() error {
|
||||
d.cfg.Listener["0"].IP = "127.0.0.1"
|
||||
d.cfg.Listener["0"].Port = 5354
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Ddwrt) Setup() error {
|
||||
// Already setup.
|
||||
if val, _ := nvram.Run("get", nvram.CtrldSetupKey); val == "1" {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := dnsmasq.ConfTmpl(dnsmasq.ConfigContentTmpl, d.cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nvramKvMap["dnsmasq_options"] = data
|
||||
if err := nvram.SetKV(nvramKvMap, nvram.CtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Ddwrt) Cleanup() error {
|
||||
if val, _ := nvram.Run("get", nvram.CtrldSetupKey); val == "1" {
|
||||
nvramKvMap["dnsmasq_options"] = ""
|
||||
// Restore old configs.
|
||||
if err := nvram.Restore(nvramKvMap, nvram.CtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func restartDNSMasq() error {
|
||||
if out, err := exec.Command("restart_dns").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("restart_dns: %s, %w", string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ddwrtJff2Enabled() bool {
|
||||
out, _ := nvram.Run("get", "enable_jffs2")
|
||||
return out == "1"
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const dnsMasqConfigContentTmpl = `# GENERATED BY ctrld - DO NOT MODIFY
|
||||
no-resolv
|
||||
{{- range .Upstreams}}
|
||||
server={{ .Ip }}#{{ .Port }}
|
||||
{{- end}}
|
||||
{{- if .SendClientInfo}}
|
||||
add-mac
|
||||
{{- end}}
|
||||
`
|
||||
|
||||
const merlinDNSMasqPostConfPath = "/jffs/scripts/dnsmasq.postconf"
|
||||
const merlinDNSMasqPostConfMarker = `# GENERATED BY ctrld - EOF`
|
||||
|
||||
const merlinDNSMasqPostConfTmpl = `# GENERATED BY ctrld - DO NOT MODIFY
|
||||
|
||||
#!/bin/sh
|
||||
|
||||
config_file="$1"
|
||||
. /usr/sbin/helper.sh
|
||||
|
||||
pid=$(cat /tmp/ctrld.pid 2>/dev/null)
|
||||
if [ -n "$pid" ] && [ -f "/proc/${pid}/cmdline" ]; then
|
||||
pc_delete "servers-file" "$config_file" # no WAN DNS settings
|
||||
pc_append "no-resolv" "$config_file" # do not read /etc/resolv.conf
|
||||
pc_append "server=127.0.0.1#5354" "$config_file" # use ctrld as upstream
|
||||
{{- if .SendClientInfo}}
|
||||
pc_append "add-mac" "$config_file" # add client mac
|
||||
{{- end}}
|
||||
pc_delete "dnssec" "$config_file" # disable DNSSEC
|
||||
pc_delete "trust-anchor=" "$config_file" # disable DNSSEC
|
||||
|
||||
# For John fork
|
||||
pc_delete "resolv-file" "$config_file" # no WAN DNS settings
|
||||
|
||||
# Change /etc/resolv.conf, which may be changed by WAN DNS setup
|
||||
pc_delete "nameserver" /etc/resolv.conf
|
||||
pc_append "nameserver 127.0.0.1" /etc/resolv.conf
|
||||
|
||||
exit 0
|
||||
fi
|
||||
`
|
||||
|
||||
type dnsmasqUpstream struct {
|
||||
Ip string
|
||||
Port int
|
||||
}
|
||||
|
||||
func dnsMasqConf() (string, error) {
|
||||
var sb strings.Builder
|
||||
var tmplText string
|
||||
switch Name() {
|
||||
case DDWrt, EdgeOS, Firewalla, OpenWrt, Ubios, Synology, Tomato:
|
||||
tmplText = dnsMasqConfigContentTmpl
|
||||
case Merlin:
|
||||
tmplText = merlinDNSMasqPostConfTmpl
|
||||
}
|
||||
tmpl := template.Must(template.New("").Parse(tmplText))
|
||||
upstreams := []dnsmasqUpstream{{ListenIP(), ListenPort()}}
|
||||
if Name() == Firewalla {
|
||||
if fu := firewallaDnsmasqUpstreams(); len(fu) > 0 {
|
||||
upstreams = fu
|
||||
}
|
||||
}
|
||||
var to = &struct {
|
||||
SendClientInfo bool
|
||||
Upstreams []dnsmasqUpstream
|
||||
}{
|
||||
SendClientInfo: routerPlatform.Load().sendClientInfo,
|
||||
Upstreams: upstreams,
|
||||
}
|
||||
if err := tmpl.Execute(&sb, to); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func restartDNSMasq() error {
|
||||
switch Name() {
|
||||
case DDWrt:
|
||||
return ddwrtRestartDNSMasq()
|
||||
case EdgeOS:
|
||||
return edgeOSRestartDNSMasq()
|
||||
case Firewalla:
|
||||
return firewallaRestartDNSMasq()
|
||||
case Merlin:
|
||||
return merlinRestartDNSMasq()
|
||||
case OpenWrt:
|
||||
return openwrtRestartDNSMasq()
|
||||
case Ubios:
|
||||
return ubiosRestartDNSMasq()
|
||||
case Synology:
|
||||
return synologyRestartDNSMasq()
|
||||
case Tomato:
|
||||
return tomatoRestartService(tomatoDNSMasqSvcName)
|
||||
}
|
||||
panic("not supported platform")
|
||||
}
|
||||
115
internal/router/dnsmasq/dnsmasq.go
Normal file
115
internal/router/dnsmasq/dnsmasq.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package dnsmasq
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
)
|
||||
|
||||
const ConfigContentTmpl = `# GENERATED BY ctrld - DO NOT MODIFY
|
||||
no-resolv
|
||||
{{- range .Upstreams}}
|
||||
server={{ .Ip }}#{{ .Port }}
|
||||
{{- end}}
|
||||
{{- if .SendClientInfo}}
|
||||
add-mac
|
||||
{{- end}}
|
||||
`
|
||||
|
||||
const MerlinPostConfPath = "/jffs/scripts/dnsmasq.postconf"
|
||||
const MerlinPostConfMarker = `# GENERATED BY ctrld - EOF`
|
||||
const MerlinPostConfTmpl = `# GENERATED BY ctrld - DO NOT MODIFY
|
||||
|
||||
#!/bin/sh
|
||||
|
||||
config_file="$1"
|
||||
. /usr/sbin/helper.sh
|
||||
|
||||
pid=$(cat /tmp/ctrld.pid 2>/dev/null)
|
||||
if [ -n "$pid" ] && [ -f "/proc/${pid}/cmdline" ]; then
|
||||
pc_delete "servers-file" "$config_file" # no WAN DNS settings
|
||||
pc_append "no-resolv" "$config_file" # do not read /etc/resolv.conf
|
||||
# use ctrld as upstream
|
||||
pc_delete "server=" "$config_file"
|
||||
{{- range .Upstreams}}
|
||||
pc_append "server={{ .Ip }}#{{ .Port }}" "$config_file"
|
||||
{{- end}}
|
||||
{{- if .SendClientInfo}}
|
||||
pc_append "add-mac" "$config_file" # add client mac
|
||||
{{- end}}
|
||||
pc_delete "dnssec" "$config_file" # disable DNSSEC
|
||||
pc_delete "trust-anchor=" "$config_file" # disable DNSSEC
|
||||
|
||||
# For John fork
|
||||
pc_delete "resolv-file" "$config_file" # no WAN DNS settings
|
||||
|
||||
# Change /etc/resolv.conf, which may be changed by WAN DNS setup
|
||||
pc_delete "nameserver" /etc/resolv.conf
|
||||
pc_append "nameserver 127.0.0.1" /etc/resolv.conf
|
||||
|
||||
exit 0
|
||||
fi
|
||||
`
|
||||
|
||||
type Upstream struct {
|
||||
Ip string
|
||||
Port int
|
||||
}
|
||||
|
||||
func ConfTmpl(tmplText string, cfg *ctrld.Config) (string, error) {
|
||||
upstreams := make([]Upstream, 0, len(cfg.Listener))
|
||||
for _, listener := range cfg.Listener {
|
||||
upstreams = append(upstreams, Upstream{Ip: listener.IP, Port: listener.Port})
|
||||
}
|
||||
return confTmpl(tmplText, upstreams, cfg.HasUpstreamSendClientInfo())
|
||||
}
|
||||
|
||||
func FirewallaConfTmpl(tmplText string, cfg *ctrld.Config) (string, error) {
|
||||
if lc := cfg.Listener["0"]; lc != nil && lc.IP == "0.0.0.0" {
|
||||
return confTmpl(tmplText, firewallaUpstreams(lc.Port), cfg.HasUpstreamSendClientInfo())
|
||||
}
|
||||
return ConfTmpl(tmplText, cfg)
|
||||
}
|
||||
|
||||
func confTmpl(tmplText string, upstreams []Upstream, sendClientInfo bool) (string, error) {
|
||||
tmpl := template.Must(template.New("").Parse(tmplText))
|
||||
var to = &struct {
|
||||
SendClientInfo bool
|
||||
Upstreams []Upstream
|
||||
}{
|
||||
SendClientInfo: sendClientInfo,
|
||||
Upstreams: upstreams,
|
||||
}
|
||||
var sb strings.Builder
|
||||
if err := tmpl.Execute(&sb, to); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func firewallaUpstreams(port int) []Upstream {
|
||||
matches, err := filepath.Glob("/home/pi/firerouter/etc/dnsmasq.dns.*.conf")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
upstreams := make([]Upstream, 0, len(matches))
|
||||
for _, match := range matches {
|
||||
// Trim prefix and suffix to get the iface name only.
|
||||
ifaceName := strings.TrimSuffix(strings.TrimPrefix(match, "/home/pi/firerouter/etc/dnsmasq.dns."), ".conf")
|
||||
if netIface, _ := net.InterfaceByName(ifaceName); netIface != nil {
|
||||
addrs, _ := netIface.Addrs()
|
||||
for _, addr := range addrs {
|
||||
if netIP, ok := addr.(*net.IPNet); ok && netIP.IP.To4() != nil {
|
||||
upstreams = append(upstreams, Upstream{
|
||||
Ip: netIP.IP.To4().String(),
|
||||
Port: port,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return upstreams
|
||||
}
|
||||
37
internal/router/dummy.go
Normal file
37
internal/router/dummy.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package router
|
||||
|
||||
import "github.com/kardianos/service"
|
||||
|
||||
type dummy struct{}
|
||||
|
||||
func NewDummyRouter() Router {
|
||||
return &dummy{}
|
||||
}
|
||||
|
||||
func (d *dummy) ConfigureService(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dummy) Install(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dummy) Uninstall(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dummy) PreRun() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dummy) Configure() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dummy) Setup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dummy) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package edgeos
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -7,52 +7,91 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
"github.com/kardianos/service"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "edgeos"
|
||||
edgeOSDNSMasqConfigPath = "/etc/dnsmasq.d/dnsmasq-zzz-ctrld.conf"
|
||||
UsgDNSMasqConfigPath = "/etc/dnsmasq.conf"
|
||||
UsgDNSMasqBackupConfigPath = "/etc/dnsmasq.conf.bak"
|
||||
usgDNSMasqConfigPath = "/etc/dnsmasq.conf"
|
||||
usgDNSMasqBackupConfigPath = "/etc/dnsmasq.conf.bak"
|
||||
toggleContentFilteringLink = "https://community.ui.com/questions/UDM-Pro-disable-enable-DNS-filtering/e2cc4060-e56a-4139-b200-62d7f773ff8f"
|
||||
)
|
||||
|
||||
var (
|
||||
var ErrContentFilteringEnabled = fmt.Errorf(`the "Content Filtering" feature" is enabled, which is conflicted with ctrld.\n
|
||||
To disable it, folowing instruction here: %s`, toggleContentFilteringLink)
|
||||
|
||||
type EdgeOS struct {
|
||||
cfg *ctrld.Config
|
||||
isUSG bool
|
||||
)
|
||||
|
||||
func setupEdgeOS() error {
|
||||
if isUSG {
|
||||
return setupUSG()
|
||||
}
|
||||
return setupUDM()
|
||||
}
|
||||
|
||||
func setupUDM() error {
|
||||
// Disable dnsmasq as DNS server.
|
||||
dnsMasqConfigContent, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return fmt.Errorf("setupUDM: generating dnsmasq config: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(edgeOSDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil {
|
||||
return fmt.Errorf("setupUDM: generating dnsmasq config: %w", err)
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return fmt.Errorf("setupUDM: restartDNSMasq: %w", err)
|
||||
// New returns a router.Router for configuring/setup/run ctrld on EdgeOS routers.
|
||||
func New(cfg *ctrld.Config) *EdgeOS {
|
||||
e := &EdgeOS{cfg: cfg}
|
||||
e.isUSG = checkUSG()
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *EdgeOS) ConfigureService(config *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EdgeOS) Install(_ *service.Config) error {
|
||||
// If "Content Filtering" is enabled, UniFi OS will create firewall rules to intercept all DNS queries
|
||||
// from outside, and route those queries to separated interfaces (e.g: dnsfilter-2@if79) created by UniFi OS.
|
||||
// Thus, those queries will never reach ctrld listener. UniFi OS does not provide any mechanism to toggle this
|
||||
// feature via command line, so there's nothing ctrld can do to disable this feature. For now, reporting an
|
||||
// error and guiding users to disable the feature using UniFi OS web UI.
|
||||
if ContentFilteringEnabled() {
|
||||
return ErrContentFilteringEnabled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupUSG() error {
|
||||
func (e *EdgeOS) Uninstall(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EdgeOS) PreRun() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EdgeOS) Configure() error {
|
||||
e.cfg.Listener["0"].IP = "127.0.0.1"
|
||||
e.cfg.Listener["0"].Port = 5354
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EdgeOS) Setup() error {
|
||||
if e.isUSG {
|
||||
return e.setupUSG()
|
||||
}
|
||||
return e.setupUDM()
|
||||
}
|
||||
|
||||
func (e *EdgeOS) Cleanup() error {
|
||||
if e.isUSG {
|
||||
return e.cleanupUSG()
|
||||
}
|
||||
return e.cleanupUDM()
|
||||
}
|
||||
|
||||
func (e *EdgeOS) setupUSG() error {
|
||||
// On USG, dnsmasq is configured to forward queries to external provider by default.
|
||||
// So instead of generating config in /etc/dnsmasq.d, we need to create a backup of
|
||||
// the config, then modify it to forward queries to ctrld listener.
|
||||
|
||||
// Creating a backup.
|
||||
buf, err := os.ReadFile(UsgDNSMasqConfigPath)
|
||||
buf, err := os.ReadFile(usgDNSMasqConfigPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setupUSG: reading current config: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(UsgDNSMasqBackupConfigPath, buf, 0600); err != nil {
|
||||
if err := os.WriteFile(usgDNSMasqBackupConfigPath, buf, 0600); err != nil {
|
||||
return fmt.Errorf("setupUSG: backup current config: %w", err)
|
||||
}
|
||||
|
||||
@@ -70,14 +109,13 @@ func setupUSG() error {
|
||||
sb.WriteString(line)
|
||||
}
|
||||
|
||||
// Adding ctrld listener as the only upstream.
|
||||
dnsMasqConfigContent, err := dnsMasqConf()
|
||||
data, err := dnsmasq.ConfTmpl(dnsmasq.ConfigContentTmpl, e.cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setupUSG: generating dnsmasq config: %w", err)
|
||||
return err
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
sb.WriteString(dnsMasqConfigContent)
|
||||
if err := os.WriteFile(UsgDNSMasqConfigPath, []byte(sb.String()), 0644); err != nil {
|
||||
sb.WriteString(data)
|
||||
if err := os.WriteFile(usgDNSMasqConfigPath, []byte(sb.String()), 0644); err != nil {
|
||||
return fmt.Errorf("setupUSG: writing dnsmasq config: %w", err)
|
||||
}
|
||||
|
||||
@@ -88,14 +126,33 @@ func setupUSG() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupEdgeOS() error {
|
||||
if isUSG {
|
||||
return cleanupUSG()
|
||||
func (e *EdgeOS) setupUDM() error {
|
||||
data, err := dnsmasq.ConfTmpl(dnsmasq.ConfigContentTmpl, e.cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cleanupUDM()
|
||||
if err := os.WriteFile(edgeOSDNSMasqConfigPath, []byte(data), 0600); err != nil {
|
||||
return fmt.Errorf("setupUDM: generating dnsmasq config: %w", err)
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return fmt.Errorf("setupUDM: restartDNSMasq: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupUDM() error {
|
||||
func (e *EdgeOS) cleanupUSG() error {
|
||||
if err := os.Rename(usgDNSMasqBackupConfigPath, usgDNSMasqConfigPath); err != nil {
|
||||
return fmt.Errorf("cleanupUSG: os.Rename: %w", err)
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return fmt.Errorf("cleanupUSG: restartDNSMasq: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EdgeOS) cleanupUDM() error {
|
||||
// Remove the custom dnsmasq config
|
||||
if err := os.Remove(edgeOSDNSMasqConfigPath); err != nil {
|
||||
return fmt.Errorf("cleanupUDM: os.Remove: %w", err)
|
||||
@@ -107,30 +164,17 @@ func cleanupUDM() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupUSG() error {
|
||||
if err := os.Rename(UsgDNSMasqBackupConfigPath, UsgDNSMasqConfigPath); err != nil {
|
||||
return fmt.Errorf("cleanupUSG: os.Rename: %w", err)
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return fmt.Errorf("cleanupUSG: restartDNSMasq: %w", err)
|
||||
}
|
||||
return nil
|
||||
func ContentFilteringEnabled() bool {
|
||||
st, err := os.Stat("/run/dnsfilter/dnsfilter")
|
||||
return err == nil && !st.IsDir()
|
||||
}
|
||||
|
||||
func postInstallEdgeOS() error {
|
||||
// If "Content Filtering" is enabled, UniFi OS will create firewall rules to intercept all DNS queries
|
||||
// from outside, and route those queries to separated interfaces (e.g: dnsfilter-2@if79) created by UniFi OS.
|
||||
// Thus, those queries will never reach ctrld listener. UniFi OS does not provide any mechanism to toggle this
|
||||
// feature via command line, so there's nothing ctrld can do to disable this feature. For now, reporting an
|
||||
// error and guiding users to disable the feature using UniFi OS web UI.
|
||||
if contentFilteringEnabled() {
|
||||
return errContentFilteringEnabled
|
||||
}
|
||||
return nil
|
||||
func checkUSG() bool {
|
||||
out, _ := exec.Command("mca-cli-op", "info").Output()
|
||||
return bytes.Contains(out, []byte("UniFi-Gateway-"))
|
||||
}
|
||||
|
||||
func edgeOSRestartDNSMasq() error {
|
||||
func restartDNSMasq() error {
|
||||
if out, err := exec.Command("/etc/init.d/dnsmasq", "restart").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("edgeosRestartDNSMasq: %s, %w", string(out), err)
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
firewallaDNSMasqConfigPath = "/home/pi/.firewalla/config/dnsmasq_local/ctrld"
|
||||
firewallaConfigPostMainDir = "/home/pi/.firewalla/config/post_main.d"
|
||||
firewallaCtrldInitScriptPath = "/home/pi/.firewalla/config/post_main.d/start_ctrld.sh"
|
||||
)
|
||||
|
||||
func setupFirewalla() error {
|
||||
dnsMasqConfigContent, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return fmt.Errorf("setupFirewalla: generating dnsmasq config: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(firewallaDNSMasqConfigPath, []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 {
|
||||
// Removing current config.
|
||||
if err := os.Remove(firewallaDNSMasqConfigPath); err != nil {
|
||||
return fmt.Errorf("cleanupFirewalla: removing ctrld config: %w", err)
|
||||
}
|
||||
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return fmt.Errorf("cleanupFirewalla: 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 postUninstallFirewalla() error {
|
||||
// Removing startup script.
|
||||
if err := os.Remove(firewallaCtrldInitScriptPath); err != nil {
|
||||
return fmt.Errorf("postUninstallFirewalla: removing 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)
|
||||
}
|
||||
|
||||
func firewallaDnsmasqUpstreams() []dnsmasqUpstream {
|
||||
matches, err := filepath.Glob("/home/pi/firerouter/etc/dnsmasq.dns.*.conf")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
upstreams := make([]dnsmasqUpstream, 0, len(matches))
|
||||
for _, match := range matches {
|
||||
// Trim prefix and suffix to get the iface name only.
|
||||
ifaceName := strings.TrimSuffix(strings.TrimPrefix(match, "/home/pi/firerouter/etc/dnsmasq.dns."), ".conf")
|
||||
if netIface, _ := net.InterfaceByName(ifaceName); netIface != nil {
|
||||
addrs, _ := netIface.Addrs()
|
||||
for _, addr := range addrs {
|
||||
if netIP, ok := addr.(*net.IPNet); ok && netIP.IP.To4() != nil {
|
||||
upstreams = append(upstreams, dnsmasqUpstream{
|
||||
Ip: netIP.IP.To4().String(),
|
||||
Port: ListenPort(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return upstreams
|
||||
}
|
||||
110
internal/router/firewalla/firewalla.go
Normal file
110
internal/router/firewalla/firewalla.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package firewalla
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
"github.com/kardianos/service"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "firewalla"
|
||||
|
||||
firewallaDNSMasqConfigPath = "/home/pi/.firewalla/config/dnsmasq_local/ctrld"
|
||||
firewallaConfigPostMainDir = "/home/pi/.firewalla/config/post_main.d"
|
||||
firewallaCtrldInitScriptPath = "/home/pi/.firewalla/config/post_main.d/start_ctrld.sh"
|
||||
)
|
||||
|
||||
type Firewalla struct {
|
||||
cfg *ctrld.Config
|
||||
}
|
||||
|
||||
// New returns a router.Router for configuring/setup/run ctrld on Firewalla routers.
|
||||
func New(cfg *ctrld.Config) *Firewalla {
|
||||
return &Firewalla{cfg: cfg}
|
||||
}
|
||||
|
||||
func (f *Firewalla) ConfigureService(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewalla) Install(_ *service.Config) error {
|
||||
// Writing startup script.
|
||||
if err := writeFirewallStartupScript(); err != nil {
|
||||
return fmt.Errorf("writing startup script: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewalla) Uninstall(_ *service.Config) error {
|
||||
// Removing startup script.
|
||||
if err := os.Remove(firewallaCtrldInitScriptPath); err != nil {
|
||||
return fmt.Errorf("removing startup script: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewalla) PreRun() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewalla) Configure() error {
|
||||
f.cfg.Listener["0"].IP = "0.0.0.0"
|
||||
f.cfg.Listener["0"].Port = 5354
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewalla) Setup() error {
|
||||
data, err := dnsmasq.FirewallaConfTmpl(dnsmasq.ConfigContentTmpl, f.cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating dnsmasq config: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(firewallaDNSMasqConfigPath, []byte(data), 0600); err != nil {
|
||||
return fmt.Errorf("writing ctrld config: %w", err)
|
||||
}
|
||||
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return fmt.Errorf("restartDNSMasq: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewalla) Cleanup() error {
|
||||
// Removing current config.
|
||||
if err := os.Remove(firewallaDNSMasqConfigPath); err != nil {
|
||||
return fmt.Errorf("removing ctrld config: %w", err)
|
||||
}
|
||||
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return fmt.Errorf("restartDNSMasq: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func restartDNSMasq() error {
|
||||
return exec.Command("systemctl", "restart", "firerouter_dns").Run()
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func setupMerlin() error {
|
||||
buf, err := os.ReadFile(merlinDNSMasqPostConfPath)
|
||||
// Already setup.
|
||||
if bytes.Contains(buf, []byte(merlinDNSMasqPostConfMarker)) {
|
||||
return nil
|
||||
}
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
merlinDNSMasqPostConf, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := strings.Join([]string{
|
||||
merlinDNSMasqPostConf,
|
||||
"\n",
|
||||
merlinDNSMasqPostConfMarker,
|
||||
"\n",
|
||||
string(buf),
|
||||
}, "\n")
|
||||
// Write dnsmasq post conf file.
|
||||
if err := os.WriteFile(merlinDNSMasqPostConfPath, []byte(data), 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := nvramSetKV(nvramSetupKV(), nvramCtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupMerlin() error {
|
||||
// Restore old configs.
|
||||
if err := nvramRestore(nvramSetupKV(), nvramCtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
buf, err := os.ReadFile(merlinDNSMasqPostConfPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
// Restore dnsmasq post conf file.
|
||||
if err := os.WriteFile(merlinDNSMasqPostConfPath, merlinParsePostConf(buf), 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postInstallMerlin() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func merlinRestartDNSMasq() error {
|
||||
if out, err := exec.Command("service", "restart_dnsmasq").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("restart_dnsmasq: %s, %w", string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func merlinParsePostConf(buf []byte) []byte {
|
||||
if len(buf) == 0 {
|
||||
return nil
|
||||
}
|
||||
parts := bytes.Split(buf, []byte(merlinDNSMasqPostConfMarker))
|
||||
if len(parts) != 1 {
|
||||
return bytes.TrimLeftFunc(parts[1], unicode.IsSpace)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
133
internal/router/merlin/merlin.go
Normal file
133
internal/router/merlin/merlin.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package merlin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/ntp"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
|
||||
)
|
||||
|
||||
const Name = "merlin"
|
||||
|
||||
var nvramKvMap = map[string]string{
|
||||
"dnspriv_enable": "0", // Ensure Merlin native DoT disabled.
|
||||
}
|
||||
|
||||
type Merlin struct {
|
||||
cfg *ctrld.Config
|
||||
}
|
||||
|
||||
// New returns a router.Router for configuring/setup/run ctrld on Merlin routers.
|
||||
func New(cfg *ctrld.Config) *Merlin {
|
||||
return &Merlin{cfg: cfg}
|
||||
}
|
||||
|
||||
func (m *Merlin) ConfigureService(config *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Merlin) Install(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Merlin) Uninstall(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Merlin) PreRun() error {
|
||||
_ = m.Cleanup()
|
||||
return ntp.Wait()
|
||||
}
|
||||
|
||||
func (m *Merlin) Configure() error {
|
||||
m.cfg.Listener["0"].IP = "127.0.0.1"
|
||||
m.cfg.Listener["0"].Port = 5354
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Merlin) Setup() error {
|
||||
buf, err := os.ReadFile(dnsmasq.MerlinPostConfPath)
|
||||
// Already setup.
|
||||
if bytes.Contains(buf, []byte(dnsmasq.MerlinPostConfMarker)) {
|
||||
return nil
|
||||
}
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := dnsmasq.ConfTmpl(dnsmasq.MerlinPostConfTmpl, m.cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = strings.Join([]string{
|
||||
data,
|
||||
"\n",
|
||||
dnsmasq.MerlinPostConfMarker,
|
||||
"\n",
|
||||
string(buf),
|
||||
}, "\n")
|
||||
// Write dnsmasq post conf file.
|
||||
if err := os.WriteFile(dnsmasq.MerlinPostConfPath, []byte(data), 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := nvram.SetKV(nvramKvMap, nvram.CtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Merlin) Cleanup() error {
|
||||
if val, _ := nvram.Run("get", nvram.CtrldSetupKey); val == "1" {
|
||||
// Restore old configs.
|
||||
if err := nvram.Restore(nvramKvMap, nvram.CtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
buf, err := os.ReadFile(dnsmasq.MerlinPostConfPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
// Restore dnsmasq post conf file.
|
||||
if err := os.WriteFile(dnsmasq.MerlinPostConfPath, merlinParsePostConf(buf), 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func restartDNSMasq() error {
|
||||
if out, err := exec.Command("service", "restart_dnsmasq").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("restart_dnsmasq: %s, %w", string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func merlinParsePostConf(buf []byte) []byte {
|
||||
if len(buf) == 0 {
|
||||
return nil
|
||||
}
|
||||
parts := bytes.Split(buf, []byte(dnsmasq.MerlinPostConfMarker))
|
||||
if len(parts) != 1 {
|
||||
return bytes.TrimLeftFunc(parts[1], unicode.IsSpace)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
package router
|
||||
package merlin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||
)
|
||||
|
||||
func Test_merlinParsePostConf(t *testing.T) {
|
||||
origContent := "# foo"
|
||||
data := strings.Join([]string{
|
||||
merlinDNSMasqPostConfTmpl,
|
||||
dnsmasq.MerlinPostConfTmpl,
|
||||
"\n",
|
||||
merlinDNSMasqPostConfMarker,
|
||||
dnsmasq.MerlinPostConfMarker,
|
||||
"\n",
|
||||
}, "\n")
|
||||
|
||||
26
internal/router/ntp/ntp.go
Normal file
26
internal/router/ntp/ntp.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package ntp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
|
||||
"tailscale.com/logtail/backoff"
|
||||
)
|
||||
|
||||
func Wait() error {
|
||||
// Wait until `ntp_ready=1` set.
|
||||
b := backoff.NewBackoff("ntp.Wait", func(format string, args ...any) {}, 10*time.Second)
|
||||
for {
|
||||
out, err := nvram.Run("get", "ntp_ready")
|
||||
if err != nil {
|
||||
return fmt.Errorf("PreStart: nvram: %w", err)
|
||||
}
|
||||
if out == "1" {
|
||||
return nil
|
||||
}
|
||||
b.BackOff(context.Background(), errors.New("ntp not ready"))
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func nvram(args ...string) (string, error) {
|
||||
cmd := exec.Command("nvram", args...)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", fmt.Errorf("%s:%w", stderr.String(), err)
|
||||
}
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
}
|
||||
|
||||
/*
|
||||
NOTE:
|
||||
- For Openwrt, DNSSEC is not included in default dnsmasq (require dnsmasq-full).
|
||||
- For Merlin, DNSSEC is configured during postconf script (see merlinDNSMasqPostConfTmpl).
|
||||
- For Ubios UDM Pro/Dream Machine, DNSSEC is not included in their dnsmasq package:
|
||||
+https://community.ui.com/questions/Implement-DNSSEC-into-UniFi/951c72b0-4d88-4c86-9174-45417bd2f9ca
|
||||
+https://community.ui.com/questions/Enable-DNSSEC-for-Unifi-Dream-Machine-FW-updates/e68e367c-d09b-4459-9444-18908f7c1ea1
|
||||
*/
|
||||
func nvramSetupKV() map[string]string {
|
||||
switch Name() {
|
||||
case DDWrt:
|
||||
return map[string]string{
|
||||
"dns_dnsmasq": "1", // Make dnsmasq running but disable DNS ability, ctrld will replace it.
|
||||
"dnsmasq_options": "", // Configuration of dnsmasq set by ctrld, filled by setupDDWrt.
|
||||
"dns_crypt": "0", // Disable DNSCrypt.
|
||||
"dnssec": "0", // Disable DNSSEC.
|
||||
}
|
||||
case Merlin:
|
||||
return map[string]string{
|
||||
"dnspriv_enable": "0", // Ensure Merlin native DoT disabled.
|
||||
}
|
||||
case Tomato:
|
||||
return map[string]string{
|
||||
"dnsmasq_custom": "", // Configuration of dnsmasq set by ctrld, filled by setupTomato.
|
||||
"dnscrypt_proxy": "0", // Disable DNSCrypt.
|
||||
"dnssec_enable": "0", // Disable DNSSEC.
|
||||
"stubby_proxy": "0", // Disable Stubby
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nvramInstallKV() map[string]string {
|
||||
switch Name() {
|
||||
case Tomato:
|
||||
return map[string]string{
|
||||
tomatoNvramScriptWanupKey: "", // script to start ctrld, filled by tomatoSvc.Install method.
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nvramSetKV(m map[string]string, setupKey string) error {
|
||||
// Backup current value, store ctrld's configs.
|
||||
for key, value := range m {
|
||||
old, err := nvram("get", key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", old, err)
|
||||
}
|
||||
if out, err := nvram("set", nvramCtrldKeyPrefix+key+"="+old); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
if out, err := nvram("set", key+"="+value); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
}
|
||||
|
||||
if out, err := nvram("set", setupKey+"=1"); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
// Commit.
|
||||
if out, err := nvram("commit"); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nvramRestore(m map[string]string, setupKey string) error {
|
||||
// Restore old configs.
|
||||
for key := range m {
|
||||
ctrldKey := nvramCtrldKeyPrefix + key
|
||||
old, err := nvram("get", ctrldKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", old, err)
|
||||
}
|
||||
_, _ = nvram("unset", ctrldKey)
|
||||
if out, err := nvram("set", key+"="+old); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
}
|
||||
|
||||
if out, err := nvram("unset", setupKey); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
// Commit.
|
||||
if out, err := nvram("commit"); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
89
internal/router/nvram/nvram.go
Normal file
89
internal/router/nvram/nvram.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package nvram
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
CtrldKeyPrefix = "ctrld_"
|
||||
CtrldSetupKey = "ctrld_setup"
|
||||
CtrldInstallKey = "ctrld_install"
|
||||
RCStartupKey = "rc_startup"
|
||||
)
|
||||
|
||||
// Run runs the given nvram command.
|
||||
func Run(args ...string) (string, error) {
|
||||
cmd := exec.Command("nvram", args...)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", fmt.Errorf("%s:%w", stderr.String(), err)
|
||||
}
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
}
|
||||
|
||||
/*
|
||||
NOTE:
|
||||
- For Openwrt, DNSSEC is not included in default dnsmasq (require dnsmasq-full).
|
||||
- For Merlin, DNSSEC is configured during postconf script (see merlinDNSMasqPostConfTmpl).
|
||||
- For Ubios UDM Pro/Dream Machine, DNSSEC is not included in their dnsmasq package:
|
||||
+https://community.ui.com/questions/Implement-DNSSEC-into-UniFi/951c72b0-4d88-4c86-9174-45417bd2f9ca
|
||||
+https://community.ui.com/questions/Enable-DNSSEC-for-Unifi-Dream-Machine-FW-updates/e68e367c-d09b-4459-9444-18908f7c1ea1
|
||||
*/
|
||||
|
||||
// SetKV writes the given key/value from map to nvram.
|
||||
// The given setupKey is set to 1 to indicates key/value set.
|
||||
func SetKV(m map[string]string, setupKey string) error {
|
||||
// Backup current value, store ctrld's configs.
|
||||
for key, value := range m {
|
||||
old, err := Run("get", key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", old, err)
|
||||
}
|
||||
if out, err := Run("set", CtrldKeyPrefix+key+"="+old); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
if out, err := Run("set", key+"="+value); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
}
|
||||
|
||||
if out, err := Run("set", setupKey+"=1"); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
// Commit.
|
||||
if out, err := Run("commit"); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore restores the old value of given key from map m.
|
||||
// The given setupKey is set to 0 to indicates key/value restored.
|
||||
func Restore(m map[string]string, setupKey string) error {
|
||||
// Restore old configs.
|
||||
for key := range m {
|
||||
ctrldKey := CtrldKeyPrefix + key
|
||||
old, err := Run("get", ctrldKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", old, err)
|
||||
}
|
||||
_, _ = Run("unset", ctrldKey)
|
||||
if out, err := Run("set", key+"="+old); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
}
|
||||
|
||||
if out, err := Run("unset", setupKey); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
// Commit.
|
||||
if out, err := Run("commit"); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package openwrt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -7,42 +7,64 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "openwrt"
|
||||
openwrtDNSMasqConfigPath = "/tmp/dnsmasq.d/ctrld.conf"
|
||||
)
|
||||
|
||||
var errUCIEntryNotFound = errors.New("uci: Entry not found")
|
||||
|
||||
const openwrtDNSMasqConfigPath = "/tmp/dnsmasq.d/ctrld.conf"
|
||||
|
||||
// IsGLiNet reports whether the router is an GL.iNet router.
|
||||
func IsGLiNet() bool {
|
||||
if Name() != OpenWrt {
|
||||
return false
|
||||
}
|
||||
buf, _ := os.ReadFile("/proc/version")
|
||||
// The output of /proc/version contains "(glinet@glinet)".
|
||||
return bytes.Contains(buf, []byte(" (glinet"))
|
||||
type Openwrt struct {
|
||||
cfg *ctrld.Config
|
||||
}
|
||||
|
||||
// IsOldOpenwrt reports whether the router is an "old" version of Openwrt,
|
||||
// aka versions which don't have "service" command.
|
||||
func IsOldOpenwrt() bool {
|
||||
if Name() != OpenWrt {
|
||||
return false
|
||||
}
|
||||
cmd, _ := exec.LookPath("service")
|
||||
return cmd == ""
|
||||
// New returns a router.Router for configuring/setup/run ctrld on Openwrt routers.
|
||||
func New(cfg *ctrld.Config) *Openwrt {
|
||||
return &Openwrt{cfg: cfg}
|
||||
}
|
||||
|
||||
func setupOpenWrt() error {
|
||||
func (o *Openwrt) ConfigureService(svc *service.Config) error {
|
||||
svc.Option["SysvScript"] = openWrtScript
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Openwrt) Install(config *service.Config) error {
|
||||
return exec.Command("/etc/init.d/ctrld", "enable").Run()
|
||||
}
|
||||
|
||||
func (o *Openwrt) Uninstall(config *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Openwrt) PreRun() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Openwrt) Configure() error {
|
||||
o.cfg.Listener["0"].IP = "127.0.0.1"
|
||||
o.cfg.Listener["0"].Port = 5354
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Openwrt) Setup() error {
|
||||
// Delete dnsmasq port if set.
|
||||
if _, err := uci("delete", "dhcp.@dnsmasq[0].port"); err != nil && !errors.Is(err, errUCIEntryNotFound) {
|
||||
return err
|
||||
}
|
||||
dnsMasqConfigContent, err := dnsMasqConf()
|
||||
|
||||
data, err := dnsmasq.ConfTmpl(dnsmasq.ConfigContentTmpl, o.cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(openwrtDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil {
|
||||
if err := os.WriteFile(openwrtDNSMasqConfigPath, []byte(data), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
// Commit.
|
||||
@@ -56,7 +78,7 @@ func setupOpenWrt() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupOpenWrt() error {
|
||||
func (o *Openwrt) Cleanup() error {
|
||||
// Remove the custom dnsmasq config
|
||||
if err := os.Remove(openwrtDNSMasqConfigPath); err != nil {
|
||||
return err
|
||||
@@ -68,8 +90,11 @@ func cleanupOpenWrt() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func postInstallOpenWrt() error {
|
||||
return exec.Command("/etc/init.d/ctrld", "enable").Run()
|
||||
func restartDNSMasq() error {
|
||||
if out, err := exec.Command("/etc/init.d/dnsmasq", "restart").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("%s: %w", string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func uci(args ...string) (string, error) {
|
||||
@@ -85,10 +110,3 @@ func uci(args ...string) (string, error) {
|
||||
}
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
}
|
||||
|
||||
func openwrtRestartDNSMasq() error {
|
||||
if out, err := exec.Command("/etc/init.d/dnsmasq", "restart").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("%s: %w", string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package openwrt
|
||||
|
||||
const openWrtScript = `#!/bin/sh /etc/rc.common
|
||||
USE_PROCD=1
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package pfsense
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -6,46 +6,18 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
"github.com/kardianos/service"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "pfsens"
|
||||
|
||||
rcPath = "/usr/local/etc/rc.d"
|
||||
unboundRcPath = rcPath + "/unbound"
|
||||
dnsmasqRcPath = rcPath + "/dnsmasq"
|
||||
)
|
||||
|
||||
func setupPfsense() error {
|
||||
// If Pfsense is in DNS Resolver mode, ensure no unbound processes running.
|
||||
_ = exec.Command("killall", "unbound").Run()
|
||||
|
||||
// If Pfsense is in DNS Forwarder mode, ensure no dnsmasq processes running.
|
||||
_ = exec.Command("killall", "dnsmasq").Run()
|
||||
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)
|
||||
}
|
||||
_ = exec.Command(unboundRcPath, "onerestart").Run()
|
||||
_ = exec.Command(dnsmasqRcPath, "onerestart").Run()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const pfsenseInitScript = `#!/bin/sh
|
||||
|
||||
# PROVIDE: {{.Name}}
|
||||
@@ -64,3 +36,56 @@ command_args="${daemon_args} {{.Path}}{{range .Arguments}} {{.}}{{end}}"
|
||||
|
||||
run_rc_command "$1"
|
||||
`
|
||||
|
||||
type Pfsense struct {
|
||||
cfg *ctrld.Config
|
||||
svcName string
|
||||
}
|
||||
|
||||
// New returns a router.Router for configuring/setup/run ctrld on Pfsense routers.
|
||||
func New(cfg *ctrld.Config) *Pfsense {
|
||||
return &Pfsense{cfg: cfg}
|
||||
}
|
||||
|
||||
func (p *Pfsense) ConfigureService(svc *service.Config) error {
|
||||
svc.Option["SysvScript"] = pfsenseInitScript
|
||||
p.svcName = svc.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pfsense) Install(config *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pfsense) Uninstall(config *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pfsense) PreRun() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pfsense) Configure() error {
|
||||
p.cfg.Listener["0"].IP = "127.0.0.1"
|
||||
p.cfg.Listener["0"].Port = 53
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pfsense) Setup() error {
|
||||
// If Pfsense is in DNS Resolver mode, ensure no unbound processes running.
|
||||
_ = exec.Command("killall", "unbound").Run()
|
||||
|
||||
// If Pfsense is in DNS Forwarder mode, ensure no dnsmasq processes running.
|
||||
_ = exec.Command("killall", "dnsmasq").Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pfsense) Cleanup() error {
|
||||
if err := os.Remove(filepath.Join(rcPath, p.svcName+".sh")); err != nil {
|
||||
return fmt.Errorf("os.Remove: %w", err)
|
||||
}
|
||||
_ = exec.Command(unboundRcPath, "onerestart").Run()
|
||||
_ = exec.Command(dnsmasqRcPath, "onerestart").Run()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2,34 +2,90 @@ package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
"tailscale.com/logtail/backoff"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/ddwrt"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/edgeos"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/firewalla"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/merlin"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/openwrt"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/pfsense"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/synology"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/tomato"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/ubios"
|
||||
)
|
||||
|
||||
const (
|
||||
DDWrt = "ddwrt"
|
||||
EdgeOS = "edgeos"
|
||||
Firewalla = "firewalla"
|
||||
Merlin = "merlin"
|
||||
OpenWrt = "openwrt"
|
||||
Pfsense = "pfsense"
|
||||
Synology = "synology"
|
||||
Tomato = "tomato"
|
||||
Ubios = "ubios"
|
||||
)
|
||||
// Service is the interface to manage ctrld service on router.
|
||||
type Service interface {
|
||||
ConfigureService(*service.Config) error
|
||||
Install(*service.Config) error
|
||||
Uninstall(*service.Config) error
|
||||
}
|
||||
|
||||
// ErrNotSupported reports the current router is not supported error.
|
||||
var ErrNotSupported = errors.New("unsupported platform")
|
||||
// Config is the interface to manage ctrld config on router.
|
||||
type Config interface {
|
||||
Configure() error
|
||||
}
|
||||
|
||||
// Router is the interface for managing ctrld running on router.
|
||||
type Router interface {
|
||||
Service
|
||||
Config
|
||||
|
||||
PreRun() error
|
||||
Setup() error
|
||||
Cleanup() error
|
||||
}
|
||||
|
||||
// New returns new Router interface.
|
||||
func New(cfg *ctrld.Config) Router {
|
||||
switch Name() {
|
||||
case ddwrt.Name:
|
||||
return ddwrt.New(cfg)
|
||||
case merlin.Name:
|
||||
return merlin.New(cfg)
|
||||
case openwrt.Name:
|
||||
return openwrt.New(cfg)
|
||||
case edgeos.Name:
|
||||
return edgeos.New(cfg)
|
||||
case ubios.Name:
|
||||
return ubios.New(cfg)
|
||||
case synology.Name:
|
||||
return synology.New(cfg)
|
||||
case tomato.Name:
|
||||
return tomato.New(cfg)
|
||||
case pfsense.Name:
|
||||
return pfsense.New(cfg)
|
||||
case firewalla.Name:
|
||||
return firewalla.New(cfg)
|
||||
}
|
||||
return NewDummyRouter()
|
||||
}
|
||||
|
||||
// IsGLiNet reports whether the router is an GL.iNet router.
|
||||
func IsGLiNet() bool {
|
||||
if Name() != openwrt.Name {
|
||||
return false
|
||||
}
|
||||
buf, _ := os.ReadFile("/proc/version")
|
||||
// The output of /proc/version contains "(glinet@glinet)".
|
||||
return bytes.Contains(buf, []byte(" (glinet"))
|
||||
}
|
||||
|
||||
// IsOldOpenwrt reports whether the router is an "old" version of Openwrt,
|
||||
// aka versions which don't have "service" command.
|
||||
func IsOldOpenwrt() bool {
|
||||
if Name() != openwrt.Name {
|
||||
return false
|
||||
}
|
||||
cmd, _ := exec.LookPath("service")
|
||||
return cmd == ""
|
||||
}
|
||||
|
||||
var routerPlatform atomic.Pointer[router]
|
||||
|
||||
@@ -41,15 +97,15 @@ type router struct {
|
||||
// IsSupported reports whether the given platform is supported by ctrld.
|
||||
func IsSupported(platform string) bool {
|
||||
switch platform {
|
||||
case DDWrt,
|
||||
EdgeOS,
|
||||
Firewalla,
|
||||
Merlin,
|
||||
OpenWrt,
|
||||
Pfsense,
|
||||
Synology,
|
||||
Tomato,
|
||||
Ubios:
|
||||
case ddwrt.Name,
|
||||
edgeos.Name,
|
||||
firewalla.Name,
|
||||
merlin.Name,
|
||||
openwrt.Name,
|
||||
pfsense.Name,
|
||||
synology.Name,
|
||||
tomato.Name,
|
||||
ubios.Name:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -58,193 +114,18 @@ func IsSupported(platform string) bool {
|
||||
// SupportedPlatforms return all platforms that can be configured to run with ctrld.
|
||||
func SupportedPlatforms() []string {
|
||||
return []string{
|
||||
DDWrt,
|
||||
EdgeOS,
|
||||
Firewalla,
|
||||
Merlin,
|
||||
OpenWrt,
|
||||
Pfsense,
|
||||
Synology,
|
||||
Tomato,
|
||||
Ubios,
|
||||
ddwrt.Name,
|
||||
edgeos.Name,
|
||||
firewalla.Name,
|
||||
merlin.Name,
|
||||
openwrt.Name,
|
||||
pfsense.Name,
|
||||
synology.Name,
|
||||
tomato.Name,
|
||||
ubios.Name,
|
||||
}
|
||||
}
|
||||
|
||||
var configureFunc = map[string]func() error{
|
||||
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 DDWrt,
|
||||
EdgeOS,
|
||||
Firewalla,
|
||||
Merlin,
|
||||
OpenWrt,
|
||||
Pfsense,
|
||||
Synology,
|
||||
Tomato,
|
||||
Ubios:
|
||||
if c.HasUpstreamSendClientInfo() {
|
||||
r := routerPlatform.Load()
|
||||
r.sendClientInfo = true
|
||||
}
|
||||
configure := configureFunc[name]
|
||||
if err := configure(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return ErrNotSupported
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigureService performs necessary setup for running ctrld as a service on router.
|
||||
func ConfigureService(sc *service.Config) error {
|
||||
name := Name()
|
||||
switch name {
|
||||
case DDWrt:
|
||||
if !ddwrtJff2Enabled() {
|
||||
return errDdwrtJffs2NotEnabled
|
||||
}
|
||||
case OpenWrt:
|
||||
sc.Option["SysvScript"] = openWrtScript
|
||||
case Pfsense:
|
||||
sc.Option["SysvScript"] = pfsenseInitScript
|
||||
case EdgeOS, Firewalla, Merlin, Synology, Tomato, Ubios:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PreRun blocks until the router is ready for running ctrld.
|
||||
func PreRun(svc *service.Config) (err error) {
|
||||
// On some routers, NTP may out of sync, so waiting for it to be ready.
|
||||
switch Name() {
|
||||
case DDWrt, Merlin, Tomato:
|
||||
// Cleanup router to ensure valid DNS for NTP synchronization.
|
||||
_ = Cleanup(svc)
|
||||
|
||||
// Wait until `ntp_ready=1` set.
|
||||
b := backoff.NewBackoff("PreRun", func(format string, args ...any) {}, 10*time.Second)
|
||||
for {
|
||||
out, err := nvram("get", "ntp_ready")
|
||||
if err != nil {
|
||||
return fmt.Errorf("PreStart: nvram: %w", err)
|
||||
}
|
||||
if out == "1" {
|
||||
return nil
|
||||
}
|
||||
b.BackOff(context.Background(), errors.New("ntp not ready"))
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// PostInstall performs task after installing ctrld on router.
|
||||
func PostInstall(svc *service.Config) error {
|
||||
name := Name()
|
||||
switch name {
|
||||
case DDWrt:
|
||||
return postInstallDDWrt()
|
||||
case EdgeOS:
|
||||
return postInstallEdgeOS()
|
||||
case Firewalla:
|
||||
return postInstallFirewalla()
|
||||
case Merlin:
|
||||
return postInstallMerlin()
|
||||
case OpenWrt:
|
||||
return postInstallOpenWrt()
|
||||
case Pfsense:
|
||||
return postInstallPfsense(svc)
|
||||
case Synology:
|
||||
return postInstallSynology()
|
||||
case Tomato:
|
||||
return postInstallTomato()
|
||||
case Ubios:
|
||||
return postInstallUbiOS()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostUninstall performs task after uninstalling ctrld on router.
|
||||
func PostUninstall(svc *service.Config) error {
|
||||
name := Name()
|
||||
switch name {
|
||||
case DDWrt:
|
||||
case EdgeOS:
|
||||
case Firewalla:
|
||||
return postUninstallFirewalla()
|
||||
case Merlin:
|
||||
case OpenWrt:
|
||||
case Pfsense:
|
||||
case Synology:
|
||||
case Tomato:
|
||||
case Ubios:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cleanup cleans ctrld setup on the router.
|
||||
func Cleanup(svc *service.Config) error {
|
||||
name := Name()
|
||||
switch name {
|
||||
case DDWrt:
|
||||
return cleanupDDWrt()
|
||||
case EdgeOS:
|
||||
return cleanupEdgeOS()
|
||||
case Firewalla:
|
||||
return cleanupFirewalla()
|
||||
case Merlin:
|
||||
return cleanupMerlin()
|
||||
case OpenWrt:
|
||||
return cleanupOpenWrt()
|
||||
case Pfsense:
|
||||
return cleanupPfsense(svc)
|
||||
case Synology:
|
||||
return cleanupSynology()
|
||||
case Tomato:
|
||||
return cleanupTomato()
|
||||
case Ubios:
|
||||
return cleanupUbiOS()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListenIP returns the listener IP of ctrld on router.
|
||||
func ListenIP() string {
|
||||
name := Name()
|
||||
switch name {
|
||||
case Firewalla:
|
||||
// Firewalla excepts 127.0.0.1 in all interfaces config. So we need to listen on all interfaces,
|
||||
// making dnsmasq to be able to forward DNS query to specific interface based on VLAN config.
|
||||
return "0.0.0.0"
|
||||
}
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
// ListenPort returns the listener port of ctrld on router.
|
||||
func ListenPort() int {
|
||||
name := Name()
|
||||
switch name {
|
||||
case EdgeOS, DDWrt, Firewalla, Merlin, OpenWrt, Synology, Tomato, Ubios:
|
||||
return 5354
|
||||
case Pfsense:
|
||||
// On pfsense, we run ctrld as DNS resolver.
|
||||
}
|
||||
return 53
|
||||
}
|
||||
|
||||
// Name returns name of the router platform.
|
||||
func Name() string {
|
||||
if r := routerPlatform.Load(); r != nil {
|
||||
@@ -259,27 +140,25 @@ func Name() string {
|
||||
func distroName() string {
|
||||
switch {
|
||||
case bytes.HasPrefix(unameO(), []byte("DD-WRT")):
|
||||
return DDWrt
|
||||
return ddwrt.Name
|
||||
case bytes.HasPrefix(unameO(), []byte("ASUSWRT-Merlin")):
|
||||
return Merlin
|
||||
return merlin.Name
|
||||
case haveFile("/etc/openwrt_version"):
|
||||
return OpenWrt
|
||||
return openwrt.Name
|
||||
case haveDir("/data/unifi"):
|
||||
return Ubios
|
||||
return ubios.Name
|
||||
case bytes.HasPrefix(unameU(), []byte("synology")):
|
||||
return Synology
|
||||
return synology.Name
|
||||
case bytes.HasPrefix(unameO(), []byte("Tomato")):
|
||||
return Tomato
|
||||
return tomato.Name
|
||||
case haveDir("/config/scripts/post-config.d"):
|
||||
checkUSG()
|
||||
return EdgeOS
|
||||
return edgeos.Name
|
||||
case haveFile("/etc/ubnt/init/vyatta-router"):
|
||||
checkUSG()
|
||||
return EdgeOS // For 2.x
|
||||
return edgeos.Name // For 2.x
|
||||
case isPfsense():
|
||||
return Pfsense
|
||||
return pfsense.Name
|
||||
case haveFile("/etc/firewalla_release"):
|
||||
return Firewalla
|
||||
return firewalla.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -308,8 +187,3 @@ func isPfsense() bool {
|
||||
b, err := os.ReadFile("/etc/platform")
|
||||
return err == nil && bytes.HasPrefix(b, []byte("pfSense"))
|
||||
}
|
||||
|
||||
func checkUSG() {
|
||||
out, _ := exec.Command("mca-cli-op", "info").Output()
|
||||
isUSG = bytes.Contains(out, []byte("UniFi-Gateway-"))
|
||||
}
|
||||
|
||||
@@ -6,13 +6,18 @@ import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/ddwrt"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/merlin"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/tomato"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/ubios"
|
||||
)
|
||||
|
||||
func init() {
|
||||
systems := []service.System{
|
||||
&linuxSystemService{
|
||||
name: "ddwrt",
|
||||
detect: func() bool { return Name() == DDWrt },
|
||||
detect: func() bool { return Name() == ddwrt.Name },
|
||||
interactive: func() bool {
|
||||
is, _ := isInteractive()
|
||||
return is
|
||||
@@ -21,7 +26,7 @@ func init() {
|
||||
},
|
||||
&linuxSystemService{
|
||||
name: "merlin",
|
||||
detect: func() bool { return Name() == Merlin },
|
||||
detect: func() bool { return Name() == merlin.Name },
|
||||
interactive: func() bool {
|
||||
is, _ := isInteractive()
|
||||
return is
|
||||
@@ -31,7 +36,7 @@ func init() {
|
||||
&linuxSystemService{
|
||||
name: "ubios",
|
||||
detect: func() bool {
|
||||
if Name() != Ubios {
|
||||
if Name() != ubios.Name {
|
||||
return false
|
||||
}
|
||||
out, err := exec.Command("ubnt-device-info", "firmware").CombinedOutput()
|
||||
@@ -50,7 +55,7 @@ func init() {
|
||||
},
|
||||
&linuxSystemService{
|
||||
name: "tomato",
|
||||
detect: func() bool { return Name() == Tomato },
|
||||
detect: func() bool { return Name() == tomato.Name },
|
||||
interactive: func() bool {
|
||||
is, _ := isInteractive()
|
||||
return is
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
|
||||
)
|
||||
|
||||
type ddwrtSvc struct {
|
||||
@@ -94,19 +96,19 @@ func (s *ddwrtSvc) Install() error {
|
||||
return err
|
||||
}
|
||||
s.rcStartup = sb.String()
|
||||
curVal, err := nvram("get", nvramRCStartupKey)
|
||||
curVal, err := nvram.Run("get", nvram.RCStartupKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := nvram("set", nvramCtrldKeyPrefix+nvramRCStartupKey+"="+curVal); err != nil {
|
||||
if _, err := nvram.Run("set", nvram.CtrldKeyPrefix+nvram.RCStartupKey+"="+curVal); err != nil {
|
||||
return err
|
||||
}
|
||||
val := strings.Join([]string{curVal, s.rcStartup + " &", fmt.Sprintf(`echo $! > "/tmp/%s.pid"`, s.Config.Name)}, "\n")
|
||||
|
||||
if _, err := nvram("set", nvramRCStartupKey+"="+val); err != nil {
|
||||
if _, err := nvram.Run("set", nvram.RCStartupKey+"="+val); err != nil {
|
||||
return err
|
||||
}
|
||||
if out, err := nvram("commit"); err != nil {
|
||||
if out, err := nvram.Run("commit"); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
|
||||
@@ -118,16 +120,16 @@ func (s *ddwrtSvc) Uninstall() error {
|
||||
return err
|
||||
}
|
||||
|
||||
ctrldStartupKey := nvramCtrldKeyPrefix + nvramRCStartupKey
|
||||
rcStartup, err := nvram("get", ctrldStartupKey)
|
||||
ctrldStartupKey := nvram.CtrldKeyPrefix + nvram.RCStartupKey
|
||||
rcStartup, err := nvram.Run("get", ctrldStartupKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = nvram("unset", ctrldStartupKey)
|
||||
if _, err := nvram("set", nvramRCStartupKey+"="+rcStartup); err != nil {
|
||||
_, _ = nvram.Run("unset", ctrldStartupKey)
|
||||
if _, err := nvram.Run("set", nvram.RCStartupKey+"="+rcStartup); err != nil {
|
||||
return err
|
||||
}
|
||||
if out, err := nvram("commit"); err != nil {
|
||||
if out, err := nvram.Run("commit"); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -67,10 +69,10 @@ func (s *merlinSvc) Install() error {
|
||||
if !strings.HasPrefix(exePath, "/jffs/") {
|
||||
return errors.New("could not install service outside /jffs")
|
||||
}
|
||||
if _, err := nvram("set", "jffs2_scripts=1"); err != nil {
|
||||
if _, err := nvram.Run("set", "jffs2_scripts=1"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := nvram("commit"); err != nil {
|
||||
if _, err := nvram.Run("commit"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
|
||||
)
|
||||
|
||||
const tomatoNvramScriptWanupKey = "script_wanup"
|
||||
@@ -63,10 +65,10 @@ func (s *tomatoSvc) Install() error {
|
||||
if !strings.HasPrefix(exePath, "/jffs/") {
|
||||
return errors.New("could not install service outside /jffs")
|
||||
}
|
||||
if _, err := nvram("set", "jffs2_on=1"); err != nil {
|
||||
if _, err := nvram.Run("set", "jffs2_on=1"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := nvram("commit"); err != nil {
|
||||
if _, err := nvram.Run("commit"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -97,13 +99,15 @@ func (s *tomatoSvc) Install() error {
|
||||
return fmt.Errorf("os.Chmod: startup script: %w", err)
|
||||
}
|
||||
|
||||
nvramKvMap := nvramInstallKV()
|
||||
old, err := nvram("get", tomatoNvramScriptWanupKey)
|
||||
nvramKvMap := map[string]string{
|
||||
tomatoNvramScriptWanupKey: "", // script to start ctrld, filled by tomatoSvc.Install method.
|
||||
}
|
||||
old, err := nvram.Run("get", tomatoNvramScriptWanupKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nvram: %w", err)
|
||||
}
|
||||
nvramKvMap[tomatoNvramScriptWanupKey] = strings.Join([]string{old, s.configPath() + " start"}, "\n")
|
||||
if err := nvramSetKV(nvramKvMap, nvramCtrldInstallKey); err != nil {
|
||||
if err := nvram.SetKV(nvramKvMap, nvram.CtrldInstallKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -113,8 +117,11 @@ func (s *tomatoSvc) Uninstall() error {
|
||||
if err := os.Remove(s.configPath()); err != nil {
|
||||
return fmt.Errorf("os.Remove: %w", err)
|
||||
}
|
||||
nvramKvMap := map[string]string{
|
||||
tomatoNvramScriptWanupKey: "", // script to start ctrld, filled by tomatoSvc.Install method.
|
||||
}
|
||||
// Restore old configs.
|
||||
if err := nvramRestore(nvramInstallKV(), nvramCtrldInstallKey); err != nil {
|
||||
if err := nvram.Restore(nvramKvMap, nvram.CtrldInstallKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -18,6 +18,9 @@ import (
|
||||
// This is a copy of https://github.com/kardianos/service/blob/v1.2.1/service_sysv_linux.go,
|
||||
// with modification for supporting ubios v1 init system.
|
||||
|
||||
// Keep in sync with ubios.ubiosDNSMasqConfigPath
|
||||
const ubiosDNSMasqConfigPath = "/run/dnsmasq.conf.d/zzzctrld.conf"
|
||||
|
||||
type ubiosSvc struct {
|
||||
i service.Interface
|
||||
platform string
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
const (
|
||||
synologyDNSMasqConfigPath = "/etc/dhcpd/dhcpd-zzz-ctrld.conf"
|
||||
synologyDhcpdInfoPath = "/etc/dhcpd/dhcpd-zzz-ctrld.info"
|
||||
)
|
||||
|
||||
func setupSynology() error {
|
||||
dnsMasqConfigContent, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(synologyDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(synologyDhcpdInfoPath, []byte(`enable="yes"`), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupSynology() error {
|
||||
// Remove the custom config files.
|
||||
for _, f := range []string{synologyDNSMasqConfigPath, synologyDhcpdInfoPath} {
|
||||
if err := os.Remove(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postInstallSynology() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func synologyRestartDNSMasq() error {
|
||||
if out, err := exec.Command("/etc/rc.network", "nat-restart-dhcp").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("synologyRestartDNSMasq: %s - %w", string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
88
internal/router/synology/synology.go
Normal file
88
internal/router/synology/synology.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package synology
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
"github.com/kardianos/service"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "synology"
|
||||
|
||||
synologyDNSMasqConfigPath = "/etc/dhcpd/dhcpd-zzz-ctrld.conf"
|
||||
synologyDhcpdInfoPath = "/etc/dhcpd/dhcpd-zzz-ctrld.info"
|
||||
)
|
||||
|
||||
type Synology struct {
|
||||
cfg *ctrld.Config
|
||||
}
|
||||
|
||||
// New returns a router.Router for configuring/setup/run ctrld on Ubios routers.
|
||||
func New(cfg *ctrld.Config) *Synology {
|
||||
return &Synology{cfg: cfg}
|
||||
}
|
||||
|
||||
func (s *Synology) ConfigureService(config *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Synology) Install(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Synology) Uninstall(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Synology) PreRun() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Synology) Configure() error {
|
||||
s.cfg.Listener["0"].IP = "127.0.0.1"
|
||||
s.cfg.Listener["0"].Port = 5354
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Synology) Setup() error {
|
||||
data, err := dnsmasq.ConfTmpl(dnsmasq.ConfigContentTmpl, s.cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(synologyDNSMasqConfigPath, []byte(data), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(synologyDhcpdInfoPath, []byte(`enable="yes"`), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Synology) Cleanup() error {
|
||||
// Remove the custom config files.
|
||||
for _, f := range []string{synologyDNSMasqConfigPath, synologyDhcpdInfoPath} {
|
||||
if err := os.Remove(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func restartDNSMasq() error {
|
||||
if out, err := exec.Command("/etc/rc.network", "nat-restart-dhcp").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("synologyRestartDNSMasq: %s - %w", string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
const (
|
||||
tomatoDnsCryptProxySvcName = "dnscrypt-proxy"
|
||||
tomatoStubbySvcName = "stubby"
|
||||
tomatoDNSMasqSvcName = "dnsmasq"
|
||||
)
|
||||
|
||||
func setupTomato() error {
|
||||
// Already setup.
|
||||
if val, _ := nvram("get", nvramCtrldSetupKey); val == "1" {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nvramKvMap := nvramSetupKV()
|
||||
nvramKvMap["dnsmasq_custom"] = data
|
||||
if err := nvramSetKV(nvramKvMap, nvramCtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Restart dnscrypt-proxy service.
|
||||
if err := tomatoRestartServiceWithKill(tomatoDnsCryptProxySvcName, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart stubby service.
|
||||
if err := tomatoRestartService(tomatoStubbySvcName); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postInstallTomato() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupTomato() error {
|
||||
// Restore old configs.
|
||||
if err := nvramRestore(nvramSetupKV(), nvramCtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnscrypt-proxy service.
|
||||
if err := tomatoRestartServiceWithKill(tomatoDnsCryptProxySvcName, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart stubby service.
|
||||
if err := tomatoRestartService(tomatoStubbySvcName); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tomatoRestartService(name string) error {
|
||||
return tomatoRestartServiceWithKill(name, false)
|
||||
}
|
||||
|
||||
func tomatoRestartServiceWithKill(name string, killBeforeRestart bool) error {
|
||||
if killBeforeRestart {
|
||||
_, _ = exec.Command("killall", name).CombinedOutput()
|
||||
}
|
||||
if out, err := exec.Command("service", name, "restart").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("service restart %s: %s, %w", name, string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
131
internal/router/tomato/tomato.go
Normal file
131
internal/router/tomato/tomato.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package tomato
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/ntp"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
|
||||
"github.com/kardianos/service"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "freshtomato"
|
||||
|
||||
tomatoDnsCryptProxySvcName = "dnscrypt-proxy"
|
||||
tomatoStubbySvcName = "stubby"
|
||||
tomatoDNSMasqSvcName = "dnsmasq"
|
||||
)
|
||||
|
||||
var nvramKvMap = map[string]string{
|
||||
"dnsmasq_custom": "", // Configuration of dnsmasq set by ctrld, filled by setupTomato.
|
||||
"dnscrypt_proxy": "0", // Disable DNSCrypt.
|
||||
"dnssec_enable": "0", // Disable DNSSEC.
|
||||
"stubby_proxy": "0", // Disable Stubby
|
||||
}
|
||||
|
||||
type FreshTomato struct {
|
||||
cfg *ctrld.Config
|
||||
}
|
||||
|
||||
// New returns a router.Router for configuring/setup/run ctrld on Ubios routers.
|
||||
func New(cfg *ctrld.Config) *FreshTomato {
|
||||
return &FreshTomato{cfg: cfg}
|
||||
}
|
||||
|
||||
func (f *FreshTomato) ConfigureService(config *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FreshTomato) Install(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FreshTomato) Uninstall(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FreshTomato) PreRun() error {
|
||||
_ = f.Cleanup()
|
||||
return ntp.Wait()
|
||||
}
|
||||
|
||||
func (f *FreshTomato) Configure() error {
|
||||
f.cfg.Listener["0"].IP = "127.0.0.1"
|
||||
f.cfg.Listener["0"].Port = 5354
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FreshTomato) Setup() error {
|
||||
// Already setup.
|
||||
if val, _ := nvram.Run("get", nvram.CtrldSetupKey); val == "1" {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := dnsmasq.ConfTmpl(dnsmasq.ConfigContentTmpl, f.cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nvramKvMap["dnsmasq_custom"] = data
|
||||
if err := nvram.SetKV(nvramKvMap, nvram.CtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Restart dnscrypt-proxy service.
|
||||
if err := tomatoRestartServiceWithKill(tomatoDnsCryptProxySvcName, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart stubby service.
|
||||
if err := tomatoRestartService(tomatoStubbySvcName); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FreshTomato) Cleanup() error {
|
||||
if val, _ := nvram.Run("get", nvram.CtrldSetupKey); val == "1" {
|
||||
nvramKvMap["dnsmasq_custom"] = ""
|
||||
// Restore old configs.
|
||||
if err := nvram.Restore(nvramKvMap, nvram.CtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Restart dnscrypt-proxy service.
|
||||
if err := tomatoRestartServiceWithKill(tomatoDnsCryptProxySvcName, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart stubby service.
|
||||
if err := tomatoRestartService(tomatoStubbySvcName); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tomatoRestartService(name string) error {
|
||||
return tomatoRestartServiceWithKill(name, false)
|
||||
}
|
||||
|
||||
func tomatoRestartServiceWithKill(name string, killBeforeRestart bool) error {
|
||||
if killBeforeRestart {
|
||||
_, _ = exec.Command("killall", name).CombinedOutput()
|
||||
}
|
||||
if out, err := exec.Command("service", name, "restart").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("service restart %s: %s, %w", name, string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func restartDNSMasq() error {
|
||||
return tomatoRestartService(tomatoDNSMasqSvcName)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var errContentFilteringEnabled = fmt.Errorf(`the "Content Filtering" feature" is enabled, which is conflicted with ctrld.\n
|
||||
To disable it, folowing instruction here: %s`, toggleContentFilteringLink)
|
||||
|
||||
const (
|
||||
ubiosDNSMasqConfigPath = "/run/dnsmasq.conf.d/zzzctrld.conf"
|
||||
toggleContentFilteringLink = "https://community.ui.com/questions/UDM-Pro-disable-enable-DNS-filtering/e2cc4060-e56a-4139-b200-62d7f773ff8f"
|
||||
)
|
||||
|
||||
func setupUbiOS() error {
|
||||
// Disable dnsmasq as DNS server.
|
||||
dnsMasqConfigContent, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(ubiosDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupUbiOS() error {
|
||||
// Remove the custom dnsmasq config
|
||||
if err := os.Remove(ubiosDNSMasqConfigPath); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postInstallUbiOS() error {
|
||||
// See comment in postInstallEdgeOS.
|
||||
if contentFilteringEnabled() {
|
||||
return errContentFilteringEnabled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ubiosRestartDNSMasq() error {
|
||||
buf, err := os.ReadFile("/run/dnsmasq.pid")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pid, err := strconv.ParseUint(string(bytes.TrimSpace(buf)), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proc, err := os.FindProcess(int(pid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return proc.Kill()
|
||||
}
|
||||
|
||||
func contentFilteringEnabled() bool {
|
||||
st, err := os.Stat("/run/dnsfilter/dnsfilter")
|
||||
return err == nil && !st.IsDir()
|
||||
}
|
||||
96
internal/router/ubios/ubios.go
Normal file
96
internal/router/ubios/ubios.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package ubios
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
"github.com/Control-D-Inc/ctrld/internal/router/edgeos"
|
||||
"github.com/kardianos/service"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "ubios"
|
||||
ubiosDNSMasqConfigPath = "/run/dnsmasq.conf.d/zzzctrld.conf"
|
||||
)
|
||||
|
||||
type Ubios struct {
|
||||
cfg *ctrld.Config
|
||||
}
|
||||
|
||||
// New returns a router.Router for configuring/setup/run ctrld on Ubios routers.
|
||||
func New(cfg *ctrld.Config) *Ubios {
|
||||
return &Ubios{cfg: cfg}
|
||||
}
|
||||
|
||||
func (u *Ubios) ConfigureService(config *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Ubios) Install(config *service.Config) error {
|
||||
// See comment in (*edgeos.EdgeOS).Install method.
|
||||
if edgeos.ContentFilteringEnabled() {
|
||||
return edgeos.ErrContentFilteringEnabled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Ubios) Uninstall(_ *service.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Ubios) PreRun() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Ubios) Configure() error {
|
||||
u.cfg.Listener["0"].IP = "127.0.0.1"
|
||||
u.cfg.Listener["0"].Port = 5354
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Ubios) Setup() error {
|
||||
data, err := dnsmasq.ConfTmpl(dnsmasq.ConfigContentTmpl, u.cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(ubiosDNSMasqConfigPath, []byte(data), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Ubios) Cleanup() error {
|
||||
// Remove the custom dnsmasq config
|
||||
if err := os.Remove(ubiosDNSMasqConfigPath); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func restartDNSMasq() error {
|
||||
buf, err := os.ReadFile("/run/dnsmasq.pid")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pid, err := strconv.ParseUint(string(bytes.TrimSpace(buf)), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proc, err := os.FindProcess(int(pid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return proc.Kill()
|
||||
}
|
||||
Reference in New Issue
Block a user