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
This commit is contained in:
Cuong Manh Le
2025-03-24 23:13:11 +07:00
committed by Cuong Manh Le
parent c6365e6b74
commit a9ed70200b
2 changed files with 62 additions and 18 deletions

View File

@@ -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

View File

@@ -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)