internal/router: generalize freebsd-like router support

This commit is contained in:
Cuong Manh Le
2023-07-21 16:31:02 +00:00
committed by Cuong Manh Le
parent 437fb1b16d
commit 6be80e4827
6 changed files with 203 additions and 143 deletions

View File

@@ -1,33 +0,0 @@
package router
import "github.com/kardianos/service"
type dummy struct{}
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
}

View File

@@ -0,0 +1,142 @@
package router
import (
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"text/template"
"github.com/kardianos/service"
"github.com/Control-D-Inc/ctrld"
)
const (
osName = "freebsd"
rcPath = "/usr/local/etc/rc.d"
rcConfPath = "/etc/rc.conf.d/"
unboundRcPath = rcPath + "/unbound"
dnsmasqRcPath = rcPath + "/dnsmasq"
)
func newOsRouter(cfg *ctrld.Config, cdMode bool) Router {
return &osRouter{cfg: cfg, cdMode: cdMode}
}
type osRouter struct {
cfg *ctrld.Config
svcName string
// cdMode indicates whether the router will configure ctrld in cd mode (aka --cd=<uid>).
// When ctrld is running on freebsd-like routers, and there's process running on port 53
// in cd mode, ctrld will attempt to kill the process and become direct listener.
// See details implemenation in osRouter.PreRun method.
cdMode bool
}
func (or *osRouter) ConfigureService(svc *service.Config) error {
svc.Option["SysvScript"] = bsdInitScript
or.svcName = svc.Name
rcFile := filepath.Join(rcConfPath, or.svcName)
var to = &struct {
Name string
}{
or.svcName,
}
f, err := os.Create(rcFile)
if err != nil {
return fmt.Errorf("os.Create: %w", err)
}
defer f.Close()
if err := template.Must(template.New("").Parse(rcConfTmpl)).Execute(f, to); err != nil {
return err
}
return f.Close()
}
func (or *osRouter) Install(_ *service.Config) error {
if isPfsense() {
// 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, or.svcName)
newname := filepath.Join(rcPath, or.svcName+".sh")
_ = os.Remove(newname)
if err := os.Symlink(oldname, newname); err != nil {
return fmt.Errorf("os.Symlink: %w", err)
}
}
return nil
}
func (or *osRouter) Uninstall(_ *service.Config) error {
rcFiles := []string{filepath.Join(rcConfPath, or.svcName)}
if isPfsense() {
rcFiles = append(rcFiles, filepath.Join(rcPath, or.svcName+".sh"))
}
for _, filename := range rcFiles {
if err := os.Remove(filename); err != nil {
return fmt.Errorf("os.Remove: %w", err)
}
}
return nil
}
func (or *osRouter) PreRun() error {
if or.cdMode {
addr := "0.0.0.0:53"
udpLn, udpErr := net.ListenPacket("udp", addr)
if udpLn != nil {
udpLn.Close()
}
tcpLn, tcpErr := net.Listen("tcp", addr)
if tcpLn != nil {
tcpLn.Close()
}
// If we could not listen on :53 for any reason, try killing unbound/dnsmasq, become direct listener
if udpErr != nil || tcpErr != nil {
_ = exec.Command("killall", "unbound").Run()
_ = exec.Command("killall", "dnsmasq").Run()
}
}
return nil
}
func (or *osRouter) Setup() error {
return nil
}
func (or *osRouter) Cleanup() error {
if or.cfg.FirstListener().IsDirectDnsListener() {
_ = exec.Command(unboundRcPath, "onerestart").Run()
_ = exec.Command(dnsmasqRcPath, "onerestart").Run()
}
return nil
}
const bsdInitScript = `#!/bin/sh
# PROVIDE: {{.Name}}
# REQUIRE: SERVERS
# REQUIRE: unbound dnsmasq securelevel
# KEYWORD: shutdown
. /etc/rc.subr
name="{{.Name}}"
rcvar="${name}_enable"
{{.Name}}_env="IS_DAEMON=1"
pidfile="/var/run/${name}.pid"
command="/usr/sbin/daemon"
daemon_args="-P ${pidfile} -r -t \"${name}: daemon\"{{if .WorkingDirectory}} -c {{.WorkingDirectory}}{{end}}"
command_args="${daemon_args} {{.Path}}{{range .Arguments}} {{.}}{{end}}"
load_rc_config "${name}"
run_rc_command "$1"
`
var rcConfTmpl = `# {{.Name}}
{{.Name}}_enable="YES"
`

View File

@@ -0,0 +1,41 @@
//go:build !freebsd
package router
import (
"github.com/kardianos/service"
"github.com/Control-D-Inc/ctrld"
)
const osName = ""
func newOsRouter(cfg *ctrld.Config, cdMode bool) Router {
return &osRouter{}
}
type osRouter struct{}
func (d *osRouter) ConfigureService(_ *service.Config) error {
return nil
}
func (d *osRouter) Install(_ *service.Config) error {
return nil
}
func (d *osRouter) Uninstall(_ *service.Config) error {
return nil
}
func (d *osRouter) PreRun() error {
return nil
}
func (d *osRouter) Setup() error {
return nil
}
func (d *osRouter) Cleanup() error {
return nil
}

View File

@@ -1,96 +0,0 @@
package pfsense
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/Control-D-Inc/ctrld"
"github.com/kardianos/service"
)
const (
Name = "pfsens"
rcPath = "/usr/local/etc/rc.d"
unboundRcPath = rcPath + "/unbound"
dnsmasqRcPath = rcPath + "/dnsmasq"
)
const pfsenseInitScript = `#!/bin/sh
# PROVIDE: {{.Name}}
# REQUIRE: SERVERS
# REQUIRE: unbound dnsmasq securelevel
# KEYWORD: shutdown
. /etc/rc.subr
name="{{.Name}}"
{{.Name}}_env="IS_DAEMON=1"
pidfile="/var/run/${name}.pid"
command="/usr/sbin/daemon"
daemon_args="-P ${pidfile} -r -t \"${name}: daemon\"{{if .WorkingDirectory}} -c {{.WorkingDirectory}}{{end}}"
command_args="${daemon_args} {{.Path}}{{range .Arguments}} {{.}}{{end}}"
run_rc_command "$1"
`
type Pfsense struct {
cfg *ctrld.Config
svcName string
}
// New returns a router.Router for configuring/setup/run ctrld on Pfsense routers.
func New(cfg *ctrld.Config) *Pfsense {
return &Pfsense{cfg: cfg}
}
func (p *Pfsense) ConfigureService(svc *service.Config) error {
svc.Option["SysvScript"] = pfsenseInitScript
p.svcName = svc.Name
return nil
}
func (p *Pfsense) Install(config *service.Config) error {
// 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, p.svcName)
newname := filepath.Join(rcPath, p.svcName+".sh")
_ = os.Remove(newname)
if err := os.Symlink(oldname, newname); err != nil {
return fmt.Errorf("os.Symlink: %w", err)
}
return nil
}
func (p *Pfsense) Uninstall(config *service.Config) error {
if err := os.Remove(filepath.Join(rcPath, p.svcName+".sh")); err != nil {
return fmt.Errorf("os.Remove: %w", err)
}
return nil
}
func (p *Pfsense) PreRun() error {
// TODO: remove this hacky solution.
// 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) Setup() error {
return nil
}
func (p *Pfsense) Cleanup() error {
if p.cfg.FirstListener().IsDirectDnsListener() {
_ = exec.Command(unboundRcPath, "onerestart").Run()
_ = exec.Command(dnsmasqRcPath, "onerestart").Run()
}
return nil
}

View File

@@ -19,7 +19,6 @@ import (
"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"
@@ -27,8 +26,11 @@ import (
// Service is the interface to manage ctrld service on router.
type Service interface {
// ConfigureService performs works for installing ctrla as a service on router.
ConfigureService(*service.Config) error
// Install performs necessary works after service.Install done.
Install(*service.Config) error
// Uninstall performs necessary works after service.Uninstallation done.
Uninstall(*service.Config) error
}
@@ -36,13 +38,17 @@ type Service interface {
type Router interface {
Service
// PreRun performs works need to be done before ctrld being run on router.
// Implementation should only return if the pre-condition was met (e.g: ntp synced).
PreRun() error
// Setup configures ctrld to be run on the router.
Setup() error
// Cleanup cleans up works setup on router by ctrld.
Cleanup() error
}
// New returns new Router interface.
func New(cfg *ctrld.Config) Router {
func New(cfg *ctrld.Config, cdMode bool) Router {
switch Name() {
case ddwrt.Name:
return ddwrt.New(cfg)
@@ -58,12 +64,10 @@ func New(cfg *ctrld.Config) Router {
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 &dummy{}
return newOsRouter(cfg, cdMode)
}
// IsGLiNet reports whether the router is an GL.iNet router.
@@ -202,12 +206,10 @@ func distroName() string {
return edgeos.Name
case haveFile("/etc/ubnt/init/vyatta-router"):
return edgeos.Name // For 2.x
case isPfsense():
return pfsense.Name
case haveFile("/etc/firewalla_release"):
return firewalla.Name
}
return ""
return osName
}
func haveFile(file string) bool {