mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-08-10 13:20:33 +02:00
internal/router: generalize freebsd-like router support
This commit is contained in:
+10
-6
@@ -186,7 +186,7 @@ func initCLI() {
|
|||||||
mainLog.Fatal().Msg("network is not up yet")
|
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))
|
cs, err := newControlServer(filepath.Join(homedir, ctrldControlUnixSock))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mainLog.Warn().Err(err).Msg("could not create control server")
|
mainLog.Warn().Err(err).Msg("could not create control server")
|
||||||
@@ -337,7 +337,7 @@ func initCLI() {
|
|||||||
sc.Arguments = append([]string{"run"}, osArgs...)
|
sc.Arguments = append([]string{"run"}, osArgs...)
|
||||||
|
|
||||||
p := &prog{
|
p := &prog{
|
||||||
router: router.New(&cfg),
|
router: router.New(&cfg, cdUID != ""),
|
||||||
cfg: &cfg,
|
cfg: &cfg,
|
||||||
}
|
}
|
||||||
if err := p.router.ConfigureService(sc); err != nil {
|
if err := p.router.ConfigureService(sc); err != nil {
|
||||||
@@ -503,9 +503,9 @@ func initCLI() {
|
|||||||
Short: "Stop the ctrld service",
|
Short: "Stop the ctrld service",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
tryReadingConfig(false)
|
readConfig(false)
|
||||||
v.Unmarshal(&cfg)
|
v.Unmarshal(&cfg)
|
||||||
p := &prog{router: router.New(&cfg)}
|
p := &prog{router: router.New(&cfg, cdUID != "")}
|
||||||
s, err := newService(p, svcConfig)
|
s, err := newService(p, svcConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mainLog.Error().Msg(err.Error())
|
mainLog.Error().Msg(err.Error())
|
||||||
@@ -593,9 +593,9 @@ func initCLI() {
|
|||||||
NOTE: Uninstalling will set DNS to values provided by DHCP.`,
|
NOTE: Uninstalling will set DNS to values provided by DHCP.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
tryReadingConfig(false)
|
readConfig(false)
|
||||||
v.Unmarshal(&cfg)
|
v.Unmarshal(&cfg)
|
||||||
p := &prog{router: router.New(&cfg)}
|
p := &prog{router: router.New(&cfg, cdUID != "")}
|
||||||
s, err := newService(p, svcConfig)
|
s, err := newService(p, svcConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mainLog.Error().Msg(err.Error())
|
mainLog.Error().Msg(err.Error())
|
||||||
@@ -1212,6 +1212,10 @@ func tryReadingConfig(writeDefaultConfig bool) {
|
|||||||
if !writeDefaultConfig {
|
if !writeDefaultConfig {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
readConfig(writeDefaultConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConfig(writeDefaultConfig bool) {
|
||||||
configs := []struct {
|
configs := []struct {
|
||||||
name string
|
name string
|
||||||
written bool
|
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
|
|
||||||
}
|
|
||||||
@@ -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"
|
||||||
|
`
|
||||||
@@ -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/firewalla"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/router/merlin"
|
"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/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/synology"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/router/tomato"
|
"github.com/Control-D-Inc/ctrld/internal/router/tomato"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/router/ubios"
|
"github.com/Control-D-Inc/ctrld/internal/router/ubios"
|
||||||
@@ -27,8 +26,11 @@ import (
|
|||||||
|
|
||||||
// Service is the interface to manage ctrld service on router.
|
// Service is the interface to manage ctrld service on router.
|
||||||
type Service interface {
|
type Service interface {
|
||||||
|
// ConfigureService performs works for installing ctrla as a service on router.
|
||||||
ConfigureService(*service.Config) error
|
ConfigureService(*service.Config) error
|
||||||
|
// Install performs necessary works after service.Install done.
|
||||||
Install(*service.Config) error
|
Install(*service.Config) error
|
||||||
|
// Uninstall performs necessary works after service.Uninstallation done.
|
||||||
Uninstall(*service.Config) error
|
Uninstall(*service.Config) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,13 +38,17 @@ type Service interface {
|
|||||||
type Router interface {
|
type Router interface {
|
||||||
Service
|
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
|
PreRun() error
|
||||||
|
// Setup configures ctrld to be run on the router.
|
||||||
Setup() error
|
Setup() error
|
||||||
|
// Cleanup cleans up works setup on router by ctrld.
|
||||||
Cleanup() error
|
Cleanup() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns new Router interface.
|
// New returns new Router interface.
|
||||||
func New(cfg *ctrld.Config) Router {
|
func New(cfg *ctrld.Config, cdMode bool) Router {
|
||||||
switch Name() {
|
switch Name() {
|
||||||
case ddwrt.Name:
|
case ddwrt.Name:
|
||||||
return ddwrt.New(cfg)
|
return ddwrt.New(cfg)
|
||||||
@@ -58,12 +64,10 @@ func New(cfg *ctrld.Config) Router {
|
|||||||
return synology.New(cfg)
|
return synology.New(cfg)
|
||||||
case tomato.Name:
|
case tomato.Name:
|
||||||
return tomato.New(cfg)
|
return tomato.New(cfg)
|
||||||
case pfsense.Name:
|
|
||||||
return pfsense.New(cfg)
|
|
||||||
case firewalla.Name:
|
case firewalla.Name:
|
||||||
return firewalla.New(cfg)
|
return firewalla.New(cfg)
|
||||||
}
|
}
|
||||||
return &dummy{}
|
return newOsRouter(cfg, cdMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsGLiNet reports whether the router is an GL.iNet router.
|
// IsGLiNet reports whether the router is an GL.iNet router.
|
||||||
@@ -202,12 +206,10 @@ func distroName() string {
|
|||||||
return edgeos.Name
|
return edgeos.Name
|
||||||
case haveFile("/etc/ubnt/init/vyatta-router"):
|
case haveFile("/etc/ubnt/init/vyatta-router"):
|
||||||
return edgeos.Name // For 2.x
|
return edgeos.Name // For 2.x
|
||||||
case isPfsense():
|
|
||||||
return pfsense.Name
|
|
||||||
case haveFile("/etc/firewalla_release"):
|
case haveFile("/etc/firewalla_release"):
|
||||||
return firewalla.Name
|
return firewalla.Name
|
||||||
}
|
}
|
||||||
return ""
|
return osName
|
||||||
}
|
}
|
||||||
|
|
||||||
func haveFile(file string) bool {
|
func haveFile(file string) bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user