internal/router: support openwrt 24.10

openwrt 24.10 changes the dnsmasq default config path, causing breaking
changes to softwares which depends on old behavior.

This commit adds a workaround for the issue, by querying the actual
config directory from ubus service list, instead of relying on the
default hardcode one.
This commit is contained in:
Cuong Manh Le
2025-02-13 20:07:52 +07:00
committed by Cuong Manh Le
parent 043a28eb33
commit 8ccaeeab60
2 changed files with 125 additions and 4 deletions

View File

@@ -2,10 +2,13 @@ package openwrt
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"strings" "strings"
"github.com/kardianos/service" "github.com/kardianos/service"
@@ -15,10 +18,13 @@ import (
) )
const ( const (
Name = "openwrt" Name = "openwrt"
openwrtDNSMasqConfigPath = "/tmp/dnsmasq.d/ctrld.conf" openwrtDNSMasqConfigName = "ctrld.conf"
openwrtDNSMasqDefaultConfigDir = "/tmp/dnsmasq.d"
) )
var openwrtDnsmasqDefaultConfigPath = filepath.Join(openwrtDNSMasqDefaultConfigDir, openwrtDNSMasqConfigName)
type Openwrt struct { type Openwrt struct {
cfg *ctrld.Config cfg *ctrld.Config
dnsmasqCacheSize string dnsmasqCacheSize string
@@ -67,7 +73,7 @@ func (o *Openwrt) Setup() error {
if err != nil { if err != nil {
return err return err
} }
if err := os.WriteFile(openwrtDNSMasqConfigPath, []byte(data), 0600); err != nil { if err := os.WriteFile(dnsmasqConfPathFromUbus(), []byte(data), 0600); err != nil {
return err return err
} }
// Restart dnsmasq service. // Restart dnsmasq service.
@@ -82,7 +88,7 @@ func (o *Openwrt) Cleanup() error {
return nil return nil
} }
// Remove the custom dnsmasq config // Remove the custom dnsmasq config
if err := os.Remove(openwrtDNSMasqConfigPath); err != nil { if err := os.Remove(dnsmasqConfPathFromUbus()); err != nil {
return err return err
} }
@@ -126,3 +132,60 @@ func uci(args ...string) (string, error) {
} }
return strings.TrimSpace(stdout.String()), nil return strings.TrimSpace(stdout.String()), nil
} }
// openwrtServiceList represents openwrt services config.
type openwrtServiceList struct {
Dnsmasq dnsmasqConf `json:"dnsmasq"`
}
// dnsmasqConf represents dnsmasq config.
type dnsmasqConf struct {
Instances map[string]confInstances `json:"instances"`
}
// confInstances represents an instance config of a service.
type confInstances struct {
Mount map[string]string `json:"mount"`
}
// dnsmasqConfPath returns the dnsmasq config path.
//
// Since version 24.10, openwrt makes some changes to dnsmasq to support
// multiple instances of dnsmasq. This change causes breaking changes to
// software which depends on the default dnsmasq path.
//
// There are some discussion/PRs in openwrt repo to address this:
//
// - https://github.com/openwrt/openwrt/pull/16806
// - https://github.com/openwrt/openwrt/pull/16890
//
// In the meantime, workaround this problem by querying the actual config path
// by querying ubus service list.
func dnsmasqConfPath(r io.Reader) string {
var svc openwrtServiceList
if err := json.NewDecoder(r).Decode(&svc); err != nil {
return openwrtDnsmasqDefaultConfigPath
}
for _, inst := range svc.Dnsmasq.Instances {
for mount := range inst.Mount {
dirName := filepath.Base(mount)
parts := strings.Split(dirName, ".")
if len(parts) < 2 {
continue
}
if parts[0] == "dnsmasq" && parts[len(parts)-1] == "d" {
return filepath.Join(mount, openwrtDNSMasqConfigName)
}
}
}
return openwrtDnsmasqDefaultConfigPath
}
// dnsmasqConfPathFromUbus get dnsmasq config path from ubus service list.
func dnsmasqConfPathFromUbus() string {
output, err := exec.Command("ubus", "call", "service", "list").Output()
if err != nil {
return openwrtDnsmasqDefaultConfigPath
}
return dnsmasqConfPath(bytes.NewReader(output))
}

View File

@@ -0,0 +1,58 @@
package openwrt
import (
"io"
"path/filepath"
"strings"
"testing"
)
// Sample output from https://github.com/openwrt/openwrt/pull/16806#issuecomment-2448255734
const ubusDnsmasqBefore2410 = `{
"dnsmasq": {
"instances": {
"guest_dns": {
"mount": {
"/tmp/dnsmasq.d": "0",
"/var/run/dnsmasq/": "1"
}
}
}
}
}`
const ubusDnsmasq2410 = `{
"dnsmasq": {
"instances": {
"guest_dns": {
"mount": {
"/tmp/dnsmasq.guest_dns.d": "0",
"/var/run/dnsmasq/": "1"
}
}
}
}
}`
func Test_dnsmasqConfPath(t *testing.T) {
var dnsmasq2410expected = filepath.Join("/tmp/dnsmasq.guest_dns.d", openwrtDNSMasqConfigName)
tests := []struct {
name string
in io.Reader
expected string
}{
{"empty", strings.NewReader(""), openwrtDnsmasqDefaultConfigPath},
{"invalid", strings.NewReader("}}"), openwrtDnsmasqDefaultConfigPath},
{"before 24.10", strings.NewReader(ubusDnsmasqBefore2410), openwrtDnsmasqDefaultConfigPath},
{"24.10", strings.NewReader(ubusDnsmasq2410), dnsmasq2410expected},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := dnsmasqConfPath(tc.in); got != tc.expected {
t.Errorf("dnsmasqConfPath() = %v, want %v", got, tc.expected)
}
})
}
}