From a9ed70200bd3f568c9954d1282d8bc733fc10057 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 24 Mar 2025 23:13:11 +0700 Subject: [PATCH] internal/router: change dnsmasq config manipulation on Merlin Generally, using /jffs/scripts/dnsmasq.postconf is the right way to add custom configuration to dnsmasq on Merlin. However, we have seen many reports that the postconf does not work on their devices. This commit changes how dnsmasq config manipulation is done on Merlin, so it's expected to work on all Merlin devices: - Writing /jffs/scripts/dnsmasq.postconf script - Copy current dnsmasq.conf to /jffs/configs/dnsmasq.conf - Run postconf script directly on /jffs/configs/dnsmasq.conf - Restart dnsmasq This way, the /jffs/configs/dnsmasq.conf will contain both current dnsmasq config, and also custom config added by ctrld, without worrying about conflicting, because configuration was added by postconf. See (1) for more details about custom config files on Merlin. (1) https://github.com/RMerl/asuswrt-merlin.ng/wiki/Custom-config-files --- internal/router/dnsmasq/dnsmasq.go | 2 + internal/router/merlin/merlin.go | 78 +++++++++++++++++++++++------- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/internal/router/dnsmasq/dnsmasq.go b/internal/router/dnsmasq/dnsmasq.go index 55c62e8..819bd59 100644 --- a/internal/router/dnsmasq/dnsmasq.go +++ b/internal/router/dnsmasq/dnsmasq.go @@ -26,6 +26,8 @@ max-cache-ttl=0 {{- end}} ` +const MerlinConfPath = "/tmp/etc/dnsmasq.conf" +const MerlinJffsConfPath = "/jffs/configs/dnsmasq.conf" const MerlinPostConfPath = "/jffs/scripts/dnsmasq.postconf" const MerlinPostConfMarker = `# GENERATED BY ctrld - EOF` const MerlinPostConfTmpl = `# GENERATED BY ctrld - DO NOT MODIFY diff --git a/internal/router/merlin/merlin.go b/internal/router/merlin/merlin.go index 8b6a0fc..cacc508 100644 --- a/internal/router/merlin/merlin.go +++ b/internal/router/merlin/merlin.go @@ -3,6 +3,7 @@ package merlin import ( "bytes" "fmt" + "io" "os" "os/exec" "strings" @@ -73,30 +74,42 @@ func (m *Merlin) Setup() error { if val, _ := nvram.Run("get", nvram.CtrldSetupKey); val == "1" { return nil } - buf, err := os.ReadFile(dnsmasq.MerlinPostConfPath) - // Already setup. - if bytes.Contains(buf, []byte(dnsmasq.MerlinPostConfMarker)) { - return nil - } - if err != nil && !os.IsNotExist(err) { + + if err := m.writeDnsmasqPostconf(); err != nil { return err } - data, err := dnsmasq.ConfTmpl(dnsmasq.MerlinPostConfTmpl, m.cfg) + // Copy current dnsmasq config to /jffs/configs/dnsmasq.conf, + // Then we will run postconf script on this file. + // + // Normally, adding postconf script is enough. However, we see + // reports on some Merlin devices that postconf scripts does not + // work, but manipulating the config directly via /jffs/configs does. + src, err := os.Open(dnsmasq.MerlinConfPath) if err != nil { - return err + return fmt.Errorf("failed to open dnsmasq config: %w", 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 + defer src.Close() + + dst, err := os.Create(dnsmasq.MerlinJffsConfPath) + if err != nil { + return fmt.Errorf("failed to create %s: %w", dnsmasq.MerlinJffsConfPath, err) } + defer dst.Close() + + if _, err := io.Copy(dst, src); err != nil { + return fmt.Errorf("failed to copy current dnsmasq config: %w", err) + } + if err := dst.Close(); err != nil { + return fmt.Errorf("failed to save %s: %w", dnsmasq.MerlinJffsConfPath, err) + } + + // Run postconf script on /jffs/configs/dnsmasq.conf directly. + cmd := exec.Command("/bin/sh", dnsmasq.MerlinPostConfPath, dnsmasq.MerlinJffsConfPath) + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("failed to run post conf: %s: %w", string(out), err) + } + // Restart dnsmasq service. if err := restartDNSMasq(); err != nil { return err @@ -130,6 +143,10 @@ func (m *Merlin) Cleanup() error { if err := os.WriteFile(dnsmasq.MerlinPostConfPath, merlinParsePostConf(buf), 0750); err != nil { return err } + // Remove /jffs/configs/dnsmasq.conf file. + if err := os.Remove(dnsmasq.MerlinJffsConfPath); err != nil && !os.IsNotExist(err) { + return err + } // Restart dnsmasq service. if err := restartDNSMasq(); err != nil { return err @@ -137,6 +154,31 @@ func (m *Merlin) Cleanup() error { return nil } +func (m *Merlin) writeDnsmasqPostconf() 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. + return os.WriteFile(dnsmasq.MerlinPostConfPath, []byte(data), 0750) +} + func restartDNSMasq() error { if out, err := exec.Command("service", "restart_dnsmasq").CombinedOutput(); err != nil { return fmt.Errorf("restart_dnsmasq: %s, %w", string(out), err)