mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
internal/router: generalize freebsd-like router support
This commit is contained in:
committed by
Cuong Manh Le
parent
437fb1b16d
commit
6be80e4827
@@ -186,7 +186,7 @@ func initCLI() {
|
||||
mainLog.Fatal().Msg("network is not up yet")
|
||||
}
|
||||
|
||||
p.router = router.New(&cfg)
|
||||
p.router = router.New(&cfg, cdUID != "")
|
||||
cs, err := newControlServer(filepath.Join(homedir, ctrldControlUnixSock))
|
||||
if err != nil {
|
||||
mainLog.Warn().Err(err).Msg("could not create control server")
|
||||
@@ -337,7 +337,7 @@ func initCLI() {
|
||||
sc.Arguments = append([]string{"run"}, osArgs...)
|
||||
|
||||
p := &prog{
|
||||
router: router.New(&cfg),
|
||||
router: router.New(&cfg, cdUID != ""),
|
||||
cfg: &cfg,
|
||||
}
|
||||
if err := p.router.ConfigureService(sc); err != nil {
|
||||
@@ -503,9 +503,9 @@ func initCLI() {
|
||||
Short: "Stop the ctrld service",
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
tryReadingConfig(false)
|
||||
readConfig(false)
|
||||
v.Unmarshal(&cfg)
|
||||
p := &prog{router: router.New(&cfg)}
|
||||
p := &prog{router: router.New(&cfg, cdUID != "")}
|
||||
s, err := newService(p, svcConfig)
|
||||
if err != nil {
|
||||
mainLog.Error().Msg(err.Error())
|
||||
@@ -593,9 +593,9 @@ func initCLI() {
|
||||
NOTE: Uninstalling will set DNS to values provided by DHCP.`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
tryReadingConfig(false)
|
||||
readConfig(false)
|
||||
v.Unmarshal(&cfg)
|
||||
p := &prog{router: router.New(&cfg)}
|
||||
p := &prog{router: router.New(&cfg, cdUID != "")}
|
||||
s, err := newService(p, svcConfig)
|
||||
if err != nil {
|
||||
mainLog.Error().Msg(err.Error())
|
||||
@@ -1212,6 +1212,10 @@ func tryReadingConfig(writeDefaultConfig bool) {
|
||||
if !writeDefaultConfig {
|
||||
return
|
||||
}
|
||||
readConfig(writeDefaultConfig)
|
||||
}
|
||||
|
||||
func readConfig(writeDefaultConfig bool) {
|
||||
configs := []struct {
|
||||
name string
|
||||
written bool
|
||||
|
||||
@@ -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
|
||||
}
|
||||
142
internal/router/os_freebsd.go
Normal file
142
internal/router/os_freebsd.go
Normal 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"
|
||||
`
|
||||
41
internal/router/os_others.go
Normal file
41
internal/router/os_others.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user