all: initial support for setup linux router

Wiring code to configure router when running ctrld. Future commits will
add implementation for each supported platforms.
This commit is contained in:
Cuong Manh Le
2023-03-15 23:25:44 +07:00
committed by Cuong Manh Le
parent 0043fdf859
commit 4b6a976747
5 changed files with 193 additions and 13 deletions

View File

@@ -18,9 +18,8 @@ import (
"sync"
"time"
"github.com/fsnotify/fsnotify"
"github.com/cuonglm/osinfo"
"github.com/fsnotify/fsnotify"
"github.com/go-playground/validator/v10"
"github.com/kardianos/service"
"github.com/miekg/dns"
@@ -33,6 +32,7 @@ import (
"github.com/Control-D-Inc/ctrld"
"github.com/Control-D-Inc/ctrld/internal/controld"
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
"github.com/Control-D-Inc/ctrld/internal/router"
)
const selfCheckFQDN = "verify.controld.com"
@@ -200,6 +200,18 @@ func initCLI() {
os.Exit(0)
}
if runtime.GOOS == "linux" && onRouter {
mainLog.Debug().Msg("Router setup")
err := router.Configure(&cfg)
if errors.Is(err, router.ErrNotSupported) {
unsupportedPlatformHelp(cmd)
os.Exit(1)
}
if err != nil {
mainLog.Fatal().Err(err).Msg("failed to configure router")
}
}
close(waitCh)
<-stopCh
},
@@ -218,6 +230,8 @@ func initCLI() {
_ = runCmd.Flags().MarkHidden("homedir")
runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`)
_ = runCmd.Flags().MarkHidden("iface")
runCmd.Flags().BoolVarP(&onRouter, "router", "", false, `Configure onRouter for running ctrld`)
_ = runCmd.Flags().MarkHidden("router")
rootCmd.AddCommand(runCmd)
@@ -315,6 +329,8 @@ func initCLI() {
startCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items")
startCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid")
startCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`)
startCmd.Flags().BoolVarP(&onRouter, "router", "", false, `Configure onRouter for running ctrld`)
_ = startCmd.Flags().MarkHidden("router")
stopCmd := &cobra.Command{
PreRun: checkHasElevatedPrivilege,
@@ -510,11 +526,6 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
stopCmdAlias.Flags().StringVarP(&ifaceStartStop, "iface", "", "auto", `Reset DNS setting for iface, "auto" means the default interface gateway`)
stopCmdAlias.Flags().AddFlagSet(stopCmd.Flags())
rootCmd.AddCommand(stopCmdAlias)
if err := rootCmd.Execute(); err != nil {
stderrMsg(err.Error())
os.Exit(1)
}
}
func writeConfigFile() error {
@@ -802,3 +813,7 @@ func selfCheckStatus(status service.Status) service.Status {
mainLog.Debug().Msgf("self-check against %q failed", selfCheckFQDN)
return service.StatusUnknown
}
func unsupportedPlatformHelp(cmd *cobra.Command) {
cmd.PrintErrln("Unsupported or incorrectly chosen onRouter platform. Please open an issue and provide all relevant information: https://github.com/Control-D-Inc/ctrld/issues/new")
}

View File

@@ -0,0 +1,70 @@
package main
import (
"log"
"os"
"os/exec"
"strings"
"github.com/spf13/cobra"
"github.com/Control-D-Inc/ctrld/internal/router"
)
func initRouterCLI() {
validArgs := append(router.SupportedPlatforms(), "auto")
var b strings.Builder
b.WriteString("Auto-setup Control D on a onRouter.\n\nSupported platforms:\n\n")
for _, arg := range validArgs {
b.WriteString(" ₒ ")
b.WriteString(arg)
if arg == "auto" {
b.WriteString(" - detect the platform you are running on")
}
b.WriteString("\n")
}
routerCmd := &cobra.Command{
Use: "setup",
Short: b.String(),
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
}
if len(args) != 1 {
_ = cmd.Help()
return
}
platform := args[0]
if platform == "auto" {
platform = router.Name()
}
switch platform {
case router.DDWrt, router.Merlin, router.OpenWrt, router.Ubios:
default:
unsupportedPlatformHelp(cmd)
}
exe, err := os.Executable()
if err != nil {
log.Fatal(err)
os.Exit(1)
}
cmdArgs := []string{"start"}
cmdArgs = append(cmdArgs, os.Args[3:]...)
cmdArgs = append(cmdArgs, "--router=true")
command := exec.Command(exe, cmdArgs...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
command.Stdin = os.Stdin
if err := command.Run(); err != nil {
log.Fatal(err)
}
},
}
tmpl := routerCmd.UsageTemplate()
tmpl = strings.Replace(tmpl, "{{.UseLine}}", "{{.UseLine}} [platform]", 1)
routerCmd.SetUsageTemplate(tmpl)
rootCmd.AddCommand(routerCmd)
}

View File

@@ -0,0 +1,5 @@
//go:build !linux
package main
func initRouterCLI() {}

View File

@@ -26,18 +26,22 @@ var (
cacheSize int
cfg ctrld.Config
verbose int
cdUID string
iface string
ifaceStartStop string
onRouter bool
rootLogger = zerolog.New(io.Discard)
mainLog = rootLogger
cdUID string
iface string
ifaceStartStop string
mainLog = zerolog.New(io.Discard)
)
func main() {
ctrld.InitConfig(v, "ctrld")
initCLI()
initRouterCLI()
if err := rootCmd.Execute(); err != nil {
stderrMsg(err.Error())
os.Exit(1)
}
}
func normalizeLogFilePath(logFilePath string) string {

86
internal/router/router.go Normal file
View File

@@ -0,0 +1,86 @@
package router
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"sync/atomic"
"github.com/Control-D-Inc/ctrld"
)
const (
OpenWrt = "openwrt"
DDWrt = "ddwrt"
Merlin = "merlin"
Ubios = "ubios"
)
// ErrNotSupported reports the current router is not supported error.
var ErrNotSupported = errors.New("unsupported platform")
var routerAtomic atomic.Pointer[router]
type router struct {
name string
}
// SupportedPlatforms return all platforms that can be configured to run with ctrld.
func SupportedPlatforms() []string {
return []string{DDWrt, Merlin, OpenWrt, Ubios}
}
// Configure change the given *ctrld.Config for running on the router.
func Configure(c *ctrld.Config) error {
name := Name()
switch name {
case DDWrt, Merlin, OpenWrt, Ubios:
default:
return ErrNotSupported
}
// TODO: implement all supported platforms.
fmt.Printf("Configuring router for: %s\n", name)
return nil
}
// Name returns name of the router platform.
func Name() string {
if r := routerAtomic.Load(); r != nil {
return r.name
}
r := &router{}
r.name = distroName()
routerAtomic.Store(r)
return r.name
}
func distroName() string {
switch {
case bytes.HasPrefix(uname(), []byte("DD-WRT")):
return DDWrt
case bytes.HasPrefix(uname(), []byte("ASUSWRT-Merlin")):
return Merlin
case haveFile("/etc/openwrt_version"):
return OpenWrt
case haveDir("/data/unifi"):
return Ubios
}
return ""
}
func haveFile(file string) bool {
_, err := os.Stat(file)
return err == nil
}
func haveDir(dir string) bool {
fi, _ := os.Stat(dir)
return fi != nil && fi.IsDir()
}
func uname() []byte {
out, _ := exec.Command("uname", "-o").Output()
return out
}