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"
|
"github.com/Control-D-Inc/ctrld/internal/controld"
|
||||||
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
|
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"
|
||||||
|
"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 (
|
var (
|
||||||
@@ -165,15 +168,20 @@ func initCLI() {
|
|||||||
mainLog.Fatal().Msg("network is not up yet")
|
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
|
// Processing --cd flag require connecting to ControlD API, which needs valid
|
||||||
// time for validating server certificate. Some routers need NTP synchronization
|
// time for validating server certificate. Some routers need NTP synchronization
|
||||||
// to set the current time, so this check must happen before processCDFlags.
|
// 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")
|
mainLog.Fatal().Err(err).Msg("failed to perform router pre-run check")
|
||||||
}
|
}
|
||||||
|
|
||||||
oldLogPath := cfg.Service.LogPath
|
oldLogPath := cfg.Service.LogPath
|
||||||
processCDFlags()
|
processCDFlags(p)
|
||||||
if newLogPath := cfg.Service.LogPath; newLogPath != "" && oldLogPath != newLogPath {
|
if newLogPath := cfg.Service.LogPath; newLogPath != "" && oldLogPath != newLogPath {
|
||||||
// After processCDFlags, log config may change, so reset mainLog and re-init logging.
|
// After processCDFlags, log config may change, so reset mainLog and re-init logging.
|
||||||
mainLog = zerolog.New(io.Discard)
|
mainLog = zerolog.New(io.Discard)
|
||||||
@@ -216,7 +224,7 @@ func initCLI() {
|
|||||||
|
|
||||||
if setupRouter {
|
if setupRouter {
|
||||||
switch platform := router.Name(); {
|
switch platform := router.Name(); {
|
||||||
case platform == router.DDWrt:
|
case platform == ddwrt.Name:
|
||||||
rootCertPool = certs.CACertPool()
|
rootCertPool = certs.CACertPool()
|
||||||
fallthrough
|
fallthrough
|
||||||
case platform != "":
|
case platform != "":
|
||||||
@@ -226,13 +234,13 @@ func initCLI() {
|
|||||||
}
|
}
|
||||||
p.onStarted = append(p.onStarted, func() {
|
p.onStarted = append(p.onStarted, func() {
|
||||||
mainLog.Debug().Msg("Router setup")
|
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")
|
mainLog.Error().Err(err).Msg("could not configure router")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
p.onStopped = append(p.onStopped, func() {
|
p.onStopped = append(p.onStopped, func() {
|
||||||
mainLog.Debug().Msg("Router cleanup")
|
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")
|
mainLog.Error().Err(err).Msg("could not cleanup router")
|
||||||
}
|
}
|
||||||
p.resetDNS()
|
p.resetDNS()
|
||||||
@@ -285,7 +293,13 @@ func initCLI() {
|
|||||||
}
|
}
|
||||||
setDependencies(sc)
|
setDependencies(sc)
|
||||||
sc.Arguments = append([]string{"run"}, osArgs...)
|
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")
|
mainLog.Fatal().Err(err).Msg("failed to configure service on router")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +325,7 @@ func initCLI() {
|
|||||||
|
|
||||||
initLogging()
|
initLogging()
|
||||||
|
|
||||||
processCDFlags()
|
processCDFlags(p)
|
||||||
|
|
||||||
if err := ctrld.ValidateConfig(validator.New(), &cfg); err != nil {
|
if err := ctrld.ValidateConfig(validator.New(), &cfg); err != nil {
|
||||||
mainLog.Fatal().Msgf("invalid config: %v", err)
|
mainLog.Fatal().Msgf("invalid config: %v", err)
|
||||||
@@ -324,7 +338,6 @@ func initCLI() {
|
|||||||
sc.Arguments = append(sc.Arguments, "--config="+defaultConfigFile)
|
sc.Arguments = append(sc.Arguments, "--config="+defaultConfigFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := &prog{}
|
|
||||||
s, err := newService(p, sc)
|
s, err := newService(p, sc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mainLog.Error().Msg(err.Error())
|
mainLog.Error().Msg(err.Error())
|
||||||
@@ -332,7 +345,7 @@ func initCLI() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mainLog.Debug().Msg("cleaning up router before installing")
|
mainLog.Debug().Msg("cleaning up router before installing")
|
||||||
_ = router.Cleanup(svcConfig)
|
_ = p.router.Cleanup()
|
||||||
|
|
||||||
tasks := []task{
|
tasks := []task{
|
||||||
{s.Stop, false},
|
{s.Stop, false},
|
||||||
@@ -341,7 +354,7 @@ func initCLI() {
|
|||||||
{s.Start, true},
|
{s.Start, true},
|
||||||
}
|
}
|
||||||
if doTasks(tasks) {
|
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")
|
mainLog.Warn().Err(err).Msg("post installation failed, please check system/service log for details error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -720,7 +733,7 @@ func processNoConfigFlags(noConfigStart bool) {
|
|||||||
v.Set("upstream", upstream)
|
v.Set("upstream", upstream)
|
||||||
}
|
}
|
||||||
|
|
||||||
func processCDFlags() {
|
func processCDFlags(p *prog) {
|
||||||
if cdUID == "" {
|
if cdUID == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -778,8 +791,9 @@ func processCDFlags() {
|
|||||||
switch {
|
switch {
|
||||||
case setupRouter:
|
case setupRouter:
|
||||||
if lc := cfg.Listener["0"]; lc != nil && lc.IP == "" {
|
if lc := cfg.Listener["0"]; lc != nil && lc.IP == "" {
|
||||||
lc.IP = router.ListenIP()
|
if err := p.router.Configure(); err != nil {
|
||||||
lc.Port = router.ListenPort()
|
mainLog.Fatal().Err(err).Msg("failed to change ctrld config for router")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case useSystemdResolved:
|
case useSystemdResolved:
|
||||||
if lc := cfg.Listener["0"]; lc != nil {
|
if lc := cfg.Listener["0"]; lc != nil {
|
||||||
@@ -824,11 +838,12 @@ func processCDFlags() {
|
|||||||
Rules: rules,
|
Rules: rules,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if setupRouter {
|
|
||||||
lc.IP = router.ListenIP()
|
|
||||||
lc.Port = router.ListenPort()
|
|
||||||
}
|
|
||||||
cfg.Listener["0"] = lc
|
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()
|
processLogAndCacheFlags()
|
||||||
@@ -940,7 +955,7 @@ func unsupportedPlatformHelp(cmd *cobra.Command) {
|
|||||||
|
|
||||||
func userHomeDir() (string, error) {
|
func userHomeDir() (string, error) {
|
||||||
switch router.Name() {
|
switch router.Name() {
|
||||||
case router.DDWrt, router.Merlin, router.Tomato:
|
case ddwrt.Name, merlin.Name, tomato.Name:
|
||||||
exe, err := os.Executable()
|
exe, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -988,7 +1003,8 @@ func uninstall(p *prog, s service.Service) {
|
|||||||
}
|
}
|
||||||
initLogging()
|
initLogging()
|
||||||
if doTasks(tasks) {
|
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")
|
mainLog.Warn().Err(err).Msg("post uninstallation failed, please check system/service log for details error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -999,7 +1015,7 @@ func uninstall(p *prog, s service.Service) {
|
|||||||
mainLog.Debug().Msg("Router cleanup")
|
mainLog.Debug().Msg("Router cleanup")
|
||||||
// Stop already did router.Cleanup and report any error if happens,
|
// Stop already did router.Cleanup and report any error if happens,
|
||||||
// ignoring error here to prevent false positive.
|
// ignoring error here to prevent false positive.
|
||||||
_ = router.Cleanup(svcConfig)
|
_ = r.Cleanup()
|
||||||
mainLog.Notice().Msg("Service uninstalled")
|
mainLog.Notice().Msg("Service uninstalled")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ import (
|
|||||||
"github.com/Control-D-Inc/ctrld/internal/clientinfo"
|
"github.com/Control-D-Inc/ctrld/internal/clientinfo"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/dnscache"
|
"github.com/Control-D-Inc/ctrld/internal/dnscache"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/router"
|
"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
|
const defaultSemaphoreCap = 256
|
||||||
@@ -39,10 +43,11 @@ type prog struct {
|
|||||||
waitCh chan struct{}
|
waitCh chan struct{}
|
||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
|
|
||||||
cfg *ctrld.Config
|
cfg *ctrld.Config
|
||||||
cache dnscache.Cacher
|
cache dnscache.Cacher
|
||||||
sema semaphore
|
sema semaphore
|
||||||
mt *clientinfo.MacTable
|
mt *clientinfo.MacTable
|
||||||
|
router router.Router
|
||||||
|
|
||||||
started chan struct{}
|
started chan struct{}
|
||||||
onStarted []func()
|
onStarted []func()
|
||||||
@@ -207,7 +212,7 @@ func (p *prog) deAllocateIP() error {
|
|||||||
|
|
||||||
func (p *prog) setDNS() {
|
func (p *prog) setDNS() {
|
||||||
switch router.Name() {
|
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.
|
// On router, ctrld run as a DNS forwarder, it does not have to change system DNS.
|
||||||
// Except for:
|
// Except for:
|
||||||
// + EdgeOS, which /etc/resolv.conf could be managed by vyatta_update_resolv.pl script.
|
// + 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")
|
logger.Debug().Msg("setting DNS for interface")
|
||||||
ns := cfg.Listener["0"].IP
|
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.
|
// 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.
|
// Thus, we use "br0" as the nameserver in /etc/resolv.conf file.
|
||||||
if ns == "127.0.0.1" {
|
if ns == "127.0.0.1" {
|
||||||
@@ -264,7 +269,7 @@ func (p *prog) setDNS() {
|
|||||||
|
|
||||||
func (p *prog) resetDNS() {
|
func (p *prog) resetDNS() {
|
||||||
switch router.Name() {
|
switch router.Name() {
|
||||||
case router.DDWrt, router.OpenWrt, router.Ubios:
|
case ddwrt.Name, openwrt.Name, ubios.Name:
|
||||||
// See comment in p.setDNS method.
|
// See comment in p.setDNS method.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Control-D-Inc/ctrld/internal/dns"
|
"github.com/Control-D-Inc/ctrld/internal/dns"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/router"
|
"github.com/Control-D-Inc/ctrld/internal/router"
|
||||||
|
"github.com/Control-D-Inc/ctrld/internal/router/edgeos"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -29,7 +30,7 @@ func setDependencies(svc *service.Config) {
|
|||||||
"After=systemd-networkd-wait-online.service",
|
"After=systemd-networkd-wait-online.service",
|
||||||
}
|
}
|
||||||
// On EdeOS, ctrld needs to start after vyatta-dhcpd, so it can read leases file.
|
// 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, "Wants=vyatta-dhcpd.service")
|
||||||
svc.Dependencies = append(svc.Dependencies, "After=vyatta-dhcpd.service")
|
svc.Dependencies = append(svc.Dependencies, "After=vyatta-dhcpd.service")
|
||||||
svc.Dependencies = append(svc.Dependencies, "Wants=dnsmasq.service")
|
svc.Dependencies = append(svc.Dependencies, "Wants=dnsmasq.service")
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/Control-D-Inc/ctrld/internal/certs"
|
"github.com/Control-D-Inc/ctrld/internal/certs"
|
||||||
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
|
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"
|
||||||
|
"github.com/Control-D-Inc/ctrld/internal/router/ddwrt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -92,7 +93,7 @@ func FetchResolverConfig(uid, version string, cdDev bool) (*ResolverConfig, erro
|
|||||||
return d.DialContext(ctx, network, addrs)
|
return d.DialContext(ctx, network, addrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if router.Name() == router.DDWrt {
|
if router.Name() == ddwrt.Name {
|
||||||
transport.TLSClientConfig = &tls.Config{RootCAs: certs.CACertPool()}
|
transport.TLSClientConfig = &tls.Config{RootCAs: certs.CACertPool()}
|
||||||
}
|
}
|
||||||
client := http.Client{
|
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 (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -7,52 +7,91 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||||
|
|
||||||
|
"github.com/Control-D-Inc/ctrld"
|
||||||
|
"github.com/kardianos/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
Name = "edgeos"
|
||||||
edgeOSDNSMasqConfigPath = "/etc/dnsmasq.d/dnsmasq-zzz-ctrld.conf"
|
edgeOSDNSMasqConfigPath = "/etc/dnsmasq.d/dnsmasq-zzz-ctrld.conf"
|
||||||
UsgDNSMasqConfigPath = "/etc/dnsmasq.conf"
|
usgDNSMasqConfigPath = "/etc/dnsmasq.conf"
|
||||||
UsgDNSMasqBackupConfigPath = "/etc/dnsmasq.conf.bak"
|
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
|
isUSG bool
|
||||||
)
|
|
||||||
|
|
||||||
func setupEdgeOS() error {
|
|
||||||
if isUSG {
|
|
||||||
return setupUSG()
|
|
||||||
}
|
|
||||||
return setupUDM()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupUDM() error {
|
// New returns a router.Router for configuring/setup/run ctrld on EdgeOS routers.
|
||||||
// Disable dnsmasq as DNS server.
|
func New(cfg *ctrld.Config) *EdgeOS {
|
||||||
dnsMasqConfigContent, err := dnsMasqConf()
|
e := &EdgeOS{cfg: cfg}
|
||||||
if err != nil {
|
e.isUSG = checkUSG()
|
||||||
return fmt.Errorf("setupUDM: generating dnsmasq config: %w", err)
|
return e
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(edgeOSDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil {
|
|
||||||
return fmt.Errorf("setupUDM: generating dnsmasq config: %w", err)
|
func (e *EdgeOS) ConfigureService(config *service.Config) error {
|
||||||
}
|
return nil
|
||||||
// Restart dnsmasq service.
|
}
|
||||||
if err := restartDNSMasq(); err != nil {
|
|
||||||
return fmt.Errorf("setupUDM: restartDNSMasq: %w", err)
|
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
|
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.
|
// 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
|
// 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.
|
// the config, then modify it to forward queries to ctrld listener.
|
||||||
|
|
||||||
// Creating a backup.
|
// Creating a backup.
|
||||||
buf, err := os.ReadFile(UsgDNSMasqConfigPath)
|
buf, err := os.ReadFile(usgDNSMasqConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("setupUSG: reading current config: %w", err)
|
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)
|
return fmt.Errorf("setupUSG: backup current config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,14 +109,13 @@ func setupUSG() error {
|
|||||||
sb.WriteString(line)
|
sb.WriteString(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding ctrld listener as the only upstream.
|
data, err := dnsmasq.ConfTmpl(dnsmasq.ConfigContentTmpl, e.cfg)
|
||||||
dnsMasqConfigContent, err := dnsMasqConf()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("setupUSG: generating dnsmasq config: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
sb.WriteString(dnsMasqConfigContent)
|
sb.WriteString(data)
|
||||||
if err := os.WriteFile(UsgDNSMasqConfigPath, []byte(sb.String()), 0644); err != nil {
|
if err := os.WriteFile(usgDNSMasqConfigPath, []byte(sb.String()), 0644); err != nil {
|
||||||
return fmt.Errorf("setupUSG: writing dnsmasq config: %w", err)
|
return fmt.Errorf("setupUSG: writing dnsmasq config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,14 +126,33 @@ func setupUSG() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanupEdgeOS() error {
|
func (e *EdgeOS) setupUDM() error {
|
||||||
if isUSG {
|
data, err := dnsmasq.ConfTmpl(dnsmasq.ConfigContentTmpl, e.cfg)
|
||||||
return cleanupUSG()
|
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
|
// Remove the custom dnsmasq config
|
||||||
if err := os.Remove(edgeOSDNSMasqConfigPath); err != nil {
|
if err := os.Remove(edgeOSDNSMasqConfigPath); err != nil {
|
||||||
return fmt.Errorf("cleanupUDM: os.Remove: %w", err)
|
return fmt.Errorf("cleanupUDM: os.Remove: %w", err)
|
||||||
@@ -107,30 +164,17 @@ func cleanupUDM() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanupUSG() error {
|
func ContentFilteringEnabled() bool {
|
||||||
if err := os.Rename(UsgDNSMasqBackupConfigPath, UsgDNSMasqConfigPath); err != nil {
|
st, err := os.Stat("/run/dnsfilter/dnsfilter")
|
||||||
return fmt.Errorf("cleanupUSG: os.Rename: %w", err)
|
return err == nil && !st.IsDir()
|
||||||
}
|
|
||||||
// Restart dnsmasq service.
|
|
||||||
if err := restartDNSMasq(); err != nil {
|
|
||||||
return fmt.Errorf("cleanupUSG: restartDNSMasq: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func postInstallEdgeOS() error {
|
func checkUSG() bool {
|
||||||
// If "Content Filtering" is enabled, UniFi OS will create firewall rules to intercept all DNS queries
|
out, _ := exec.Command("mca-cli-op", "info").Output()
|
||||||
// from outside, and route those queries to separated interfaces (e.g: dnsfilter-2@if79) created by UniFi OS.
|
return bytes.Contains(out, []byte("UniFi-Gateway-"))
|
||||||
// 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 edgeOSRestartDNSMasq() error {
|
func restartDNSMasq() error {
|
||||||
if out, err := exec.Command("/etc/init.d/dnsmasq", "restart").CombinedOutput(); err != nil {
|
if out, err := exec.Command("/etc/init.d/dnsmasq", "restart").CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf("edgeosRestartDNSMasq: %s, %w", string(out), err)
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_merlinParsePostConf(t *testing.T) {
|
func Test_merlinParsePostConf(t *testing.T) {
|
||||||
origContent := "# foo"
|
origContent := "# foo"
|
||||||
data := strings.Join([]string{
|
data := strings.Join([]string{
|
||||||
merlinDNSMasqPostConfTmpl,
|
dnsmasq.MerlinPostConfTmpl,
|
||||||
"\n",
|
"\n",
|
||||||
merlinDNSMasqPostConfMarker,
|
dnsmasq.MerlinPostConfMarker,
|
||||||
"\n",
|
"\n",
|
||||||
}, "\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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -7,42 +7,64 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"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")
|
var errUCIEntryNotFound = errors.New("uci: Entry not found")
|
||||||
|
|
||||||
const openwrtDNSMasqConfigPath = "/tmp/dnsmasq.d/ctrld.conf"
|
type Openwrt struct {
|
||||||
|
cfg *ctrld.Config
|
||||||
// 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"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsOldOpenwrt reports whether the router is an "old" version of Openwrt,
|
// New returns a router.Router for configuring/setup/run ctrld on Openwrt routers.
|
||||||
// aka versions which don't have "service" command.
|
func New(cfg *ctrld.Config) *Openwrt {
|
||||||
func IsOldOpenwrt() bool {
|
return &Openwrt{cfg: cfg}
|
||||||
if Name() != OpenWrt {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
cmd, _ := exec.LookPath("service")
|
|
||||||
return cmd == ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// Delete dnsmasq port if set.
|
||||||
if _, err := uci("delete", "dhcp.@dnsmasq[0].port"); err != nil && !errors.Is(err, errUCIEntryNotFound) {
|
if _, err := uci("delete", "dhcp.@dnsmasq[0].port"); err != nil && !errors.Is(err, errUCIEntryNotFound) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dnsMasqConfigContent, err := dnsMasqConf()
|
|
||||||
|
data, err := dnsmasq.ConfTmpl(dnsmasq.ConfigContentTmpl, o.cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(openwrtDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil {
|
if err := os.WriteFile(openwrtDNSMasqConfigPath, []byte(data), 0600); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Commit.
|
// Commit.
|
||||||
@@ -56,7 +78,7 @@ func setupOpenWrt() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanupOpenWrt() error {
|
func (o *Openwrt) Cleanup() error {
|
||||||
// Remove the custom dnsmasq config
|
// Remove the custom dnsmasq config
|
||||||
if err := os.Remove(openwrtDNSMasqConfigPath); err != nil {
|
if err := os.Remove(openwrtDNSMasqConfigPath); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -68,8 +90,11 @@ func cleanupOpenWrt() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func postInstallOpenWrt() error {
|
func restartDNSMasq() error {
|
||||||
return exec.Command("/etc/init.d/ctrld", "enable").Run()
|
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) {
|
func uci(args ...string) (string, error) {
|
||||||
@@ -85,10 +110,3 @@ func uci(args ...string) (string, error) {
|
|||||||
}
|
}
|
||||||
return strings.TrimSpace(stdout.String()), nil
|
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
|
const openWrtScript = `#!/bin/sh /etc/rc.common
|
||||||
USE_PROCD=1
|
USE_PROCD=1
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package router
|
package pfsense
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -6,46 +6,18 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/Control-D-Inc/ctrld"
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
Name = "pfsens"
|
||||||
|
|
||||||
rcPath = "/usr/local/etc/rc.d"
|
rcPath = "/usr/local/etc/rc.d"
|
||||||
unboundRcPath = rcPath + "/unbound"
|
unboundRcPath = rcPath + "/unbound"
|
||||||
dnsmasqRcPath = rcPath + "/dnsmasq"
|
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
|
const pfsenseInitScript = `#!/bin/sh
|
||||||
|
|
||||||
# PROVIDE: {{.Name}}
|
# PROVIDE: {{.Name}}
|
||||||
@@ -64,3 +36,56 @@ command_args="${daemon_args} {{.Path}}{{range .Arguments}} {{.}}{{end}}"
|
|||||||
|
|
||||||
run_rc_command "$1"
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
"tailscale.com/logtail/backoff"
|
|
||||||
|
|
||||||
"github.com/Control-D-Inc/ctrld"
|
"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 (
|
// Service is the interface to manage ctrld service on router.
|
||||||
DDWrt = "ddwrt"
|
type Service interface {
|
||||||
EdgeOS = "edgeos"
|
ConfigureService(*service.Config) error
|
||||||
Firewalla = "firewalla"
|
Install(*service.Config) error
|
||||||
Merlin = "merlin"
|
Uninstall(*service.Config) error
|
||||||
OpenWrt = "openwrt"
|
}
|
||||||
Pfsense = "pfsense"
|
|
||||||
Synology = "synology"
|
|
||||||
Tomato = "tomato"
|
|
||||||
Ubios = "ubios"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrNotSupported reports the current router is not supported error.
|
// Config is the interface to manage ctrld config on router.
|
||||||
var ErrNotSupported = errors.New("unsupported platform")
|
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]
|
var routerPlatform atomic.Pointer[router]
|
||||||
|
|
||||||
@@ -41,15 +97,15 @@ type router struct {
|
|||||||
// IsSupported reports whether the given platform is supported by ctrld.
|
// IsSupported reports whether the given platform is supported by ctrld.
|
||||||
func IsSupported(platform string) bool {
|
func IsSupported(platform string) bool {
|
||||||
switch platform {
|
switch platform {
|
||||||
case DDWrt,
|
case ddwrt.Name,
|
||||||
EdgeOS,
|
edgeos.Name,
|
||||||
Firewalla,
|
firewalla.Name,
|
||||||
Merlin,
|
merlin.Name,
|
||||||
OpenWrt,
|
openwrt.Name,
|
||||||
Pfsense,
|
pfsense.Name,
|
||||||
Synology,
|
synology.Name,
|
||||||
Tomato,
|
tomato.Name,
|
||||||
Ubios:
|
ubios.Name:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -58,193 +114,18 @@ func IsSupported(platform string) bool {
|
|||||||
// SupportedPlatforms return all platforms that can be configured to run with ctrld.
|
// SupportedPlatforms return all platforms that can be configured to run with ctrld.
|
||||||
func SupportedPlatforms() []string {
|
func SupportedPlatforms() []string {
|
||||||
return []string{
|
return []string{
|
||||||
DDWrt,
|
ddwrt.Name,
|
||||||
EdgeOS,
|
edgeos.Name,
|
||||||
Firewalla,
|
firewalla.Name,
|
||||||
Merlin,
|
merlin.Name,
|
||||||
OpenWrt,
|
openwrt.Name,
|
||||||
Pfsense,
|
pfsense.Name,
|
||||||
Synology,
|
synology.Name,
|
||||||
Tomato,
|
tomato.Name,
|
||||||
Ubios,
|
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.
|
// Name returns name of the router platform.
|
||||||
func Name() string {
|
func Name() string {
|
||||||
if r := routerPlatform.Load(); r != nil {
|
if r := routerPlatform.Load(); r != nil {
|
||||||
@@ -259,27 +140,25 @@ func Name() string {
|
|||||||
func distroName() string {
|
func distroName() string {
|
||||||
switch {
|
switch {
|
||||||
case bytes.HasPrefix(unameO(), []byte("DD-WRT")):
|
case bytes.HasPrefix(unameO(), []byte("DD-WRT")):
|
||||||
return DDWrt
|
return ddwrt.Name
|
||||||
case bytes.HasPrefix(unameO(), []byte("ASUSWRT-Merlin")):
|
case bytes.HasPrefix(unameO(), []byte("ASUSWRT-Merlin")):
|
||||||
return Merlin
|
return merlin.Name
|
||||||
case haveFile("/etc/openwrt_version"):
|
case haveFile("/etc/openwrt_version"):
|
||||||
return OpenWrt
|
return openwrt.Name
|
||||||
case haveDir("/data/unifi"):
|
case haveDir("/data/unifi"):
|
||||||
return Ubios
|
return ubios.Name
|
||||||
case bytes.HasPrefix(unameU(), []byte("synology")):
|
case bytes.HasPrefix(unameU(), []byte("synology")):
|
||||||
return Synology
|
return synology.Name
|
||||||
case bytes.HasPrefix(unameO(), []byte("Tomato")):
|
case bytes.HasPrefix(unameO(), []byte("Tomato")):
|
||||||
return Tomato
|
return tomato.Name
|
||||||
case haveDir("/config/scripts/post-config.d"):
|
case haveDir("/config/scripts/post-config.d"):
|
||||||
checkUSG()
|
return edgeos.Name
|
||||||
return EdgeOS
|
|
||||||
case haveFile("/etc/ubnt/init/vyatta-router"):
|
case haveFile("/etc/ubnt/init/vyatta-router"):
|
||||||
checkUSG()
|
return edgeos.Name // For 2.x
|
||||||
return EdgeOS // For 2.x
|
|
||||||
case isPfsense():
|
case isPfsense():
|
||||||
return Pfsense
|
return pfsense.Name
|
||||||
case haveFile("/etc/firewalla_release"):
|
case haveFile("/etc/firewalla_release"):
|
||||||
return Firewalla
|
return firewalla.Name
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -308,8 +187,3 @@ func isPfsense() bool {
|
|||||||
b, err := os.ReadFile("/etc/platform")
|
b, err := os.ReadFile("/etc/platform")
|
||||||
return err == nil && bytes.HasPrefix(b, []byte("pfSense"))
|
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"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"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() {
|
func init() {
|
||||||
systems := []service.System{
|
systems := []service.System{
|
||||||
&linuxSystemService{
|
&linuxSystemService{
|
||||||
name: "ddwrt",
|
name: "ddwrt",
|
||||||
detect: func() bool { return Name() == DDWrt },
|
detect: func() bool { return Name() == ddwrt.Name },
|
||||||
interactive: func() bool {
|
interactive: func() bool {
|
||||||
is, _ := isInteractive()
|
is, _ := isInteractive()
|
||||||
return is
|
return is
|
||||||
@@ -21,7 +26,7 @@ func init() {
|
|||||||
},
|
},
|
||||||
&linuxSystemService{
|
&linuxSystemService{
|
||||||
name: "merlin",
|
name: "merlin",
|
||||||
detect: func() bool { return Name() == Merlin },
|
detect: func() bool { return Name() == merlin.Name },
|
||||||
interactive: func() bool {
|
interactive: func() bool {
|
||||||
is, _ := isInteractive()
|
is, _ := isInteractive()
|
||||||
return is
|
return is
|
||||||
@@ -31,7 +36,7 @@ func init() {
|
|||||||
&linuxSystemService{
|
&linuxSystemService{
|
||||||
name: "ubios",
|
name: "ubios",
|
||||||
detect: func() bool {
|
detect: func() bool {
|
||||||
if Name() != Ubios {
|
if Name() != ubios.Name {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
out, err := exec.Command("ubnt-device-info", "firmware").CombinedOutput()
|
out, err := exec.Command("ubnt-device-info", "firmware").CombinedOutput()
|
||||||
@@ -50,7 +55,7 @@ func init() {
|
|||||||
},
|
},
|
||||||
&linuxSystemService{
|
&linuxSystemService{
|
||||||
name: "tomato",
|
name: "tomato",
|
||||||
detect: func() bool { return Name() == Tomato },
|
detect: func() bool { return Name() == tomato.Name },
|
||||||
interactive: func() bool {
|
interactive: func() bool {
|
||||||
is, _ := isInteractive()
|
is, _ := isInteractive()
|
||||||
return is
|
return is
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
|
|
||||||
|
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ddwrtSvc struct {
|
type ddwrtSvc struct {
|
||||||
@@ -94,19 +96,19 @@ func (s *ddwrtSvc) Install() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.rcStartup = sb.String()
|
s.rcStartup = sb.String()
|
||||||
curVal, err := nvram("get", nvramRCStartupKey)
|
curVal, err := nvram.Run("get", nvram.RCStartupKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
val := strings.Join([]string{curVal, s.rcStartup + " &", fmt.Sprintf(`echo $! > "/tmp/%s.pid"`, s.Config.Name)}, "\n")
|
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
|
return err
|
||||||
}
|
}
|
||||||
if out, err := nvram("commit"); err != nil {
|
if out, err := nvram.Run("commit"); err != nil {
|
||||||
return fmt.Errorf("%s: %w", out, err)
|
return fmt.Errorf("%s: %w", out, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,16 +120,16 @@ func (s *ddwrtSvc) Uninstall() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctrldStartupKey := nvramCtrldKeyPrefix + nvramRCStartupKey
|
ctrldStartupKey := nvram.CtrldKeyPrefix + nvram.RCStartupKey
|
||||||
rcStartup, err := nvram("get", ctrldStartupKey)
|
rcStartup, err := nvram.Run("get", ctrldStartupKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, _ = nvram("unset", ctrldStartupKey)
|
_, _ = nvram.Run("unset", ctrldStartupKey)
|
||||||
if _, err := nvram("set", nvramRCStartupKey+"="+rcStartup); err != nil {
|
if _, err := nvram.Run("set", nvram.RCStartupKey+"="+rcStartup); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if out, err := nvram("commit"); err != nil {
|
if out, err := nvram.Run("commit"); err != nil {
|
||||||
return fmt.Errorf("%s: %w", out, err)
|
return fmt.Errorf("%s: %w", out, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
|
|
||||||
|
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -67,10 +69,10 @@ func (s *merlinSvc) Install() error {
|
|||||||
if !strings.HasPrefix(exePath, "/jffs/") {
|
if !strings.HasPrefix(exePath, "/jffs/") {
|
||||||
return errors.New("could not install service outside /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
|
return err
|
||||||
}
|
}
|
||||||
if _, err := nvram("commit"); err != nil {
|
if _, err := nvram.Run("commit"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
|
|
||||||
|
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
|
||||||
)
|
)
|
||||||
|
|
||||||
const tomatoNvramScriptWanupKey = "script_wanup"
|
const tomatoNvramScriptWanupKey = "script_wanup"
|
||||||
@@ -63,10 +65,10 @@ func (s *tomatoSvc) Install() error {
|
|||||||
if !strings.HasPrefix(exePath, "/jffs/") {
|
if !strings.HasPrefix(exePath, "/jffs/") {
|
||||||
return errors.New("could not install service outside /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
|
return err
|
||||||
}
|
}
|
||||||
if _, err := nvram("commit"); err != nil {
|
if _, err := nvram.Run("commit"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,13 +99,15 @@ func (s *tomatoSvc) Install() error {
|
|||||||
return fmt.Errorf("os.Chmod: startup script: %w", err)
|
return fmt.Errorf("os.Chmod: startup script: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nvramKvMap := nvramInstallKV()
|
nvramKvMap := map[string]string{
|
||||||
old, err := nvram("get", tomatoNvramScriptWanupKey)
|
tomatoNvramScriptWanupKey: "", // script to start ctrld, filled by tomatoSvc.Install method.
|
||||||
|
}
|
||||||
|
old, err := nvram.Run("get", tomatoNvramScriptWanupKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("nvram: %w", err)
|
return fmt.Errorf("nvram: %w", err)
|
||||||
}
|
}
|
||||||
nvramKvMap[tomatoNvramScriptWanupKey] = strings.Join([]string{old, s.configPath() + " start"}, "\n")
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -113,8 +117,11 @@ func (s *tomatoSvc) Uninstall() error {
|
|||||||
if err := os.Remove(s.configPath()); err != nil {
|
if err := os.Remove(s.configPath()); err != nil {
|
||||||
return fmt.Errorf("os.Remove: %w", err)
|
return fmt.Errorf("os.Remove: %w", err)
|
||||||
}
|
}
|
||||||
|
nvramKvMap := map[string]string{
|
||||||
|
tomatoNvramScriptWanupKey: "", // script to start ctrld, filled by tomatoSvc.Install method.
|
||||||
|
}
|
||||||
// Restore old configs.
|
// Restore old configs.
|
||||||
if err := nvramRestore(nvramInstallKV(), nvramCtrldInstallKey); err != nil {
|
if err := nvram.Restore(nvramKvMap, nvram.CtrldInstallKey); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
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,
|
// 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.
|
// 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 {
|
type ubiosSvc struct {
|
||||||
i service.Interface
|
i service.Interface
|
||||||
platform string
|
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