mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-16 10:22:45 +00:00
The assignment is changed wrongly in process of refactoring parallel dialer for resolving bootstrap IP. While at it, also satisfy staticheck for jffs not enabled error.
223 lines
4.6 KiB
Go
223 lines
4.6 KiB
Go
package router
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
"github.com/kardianos/service"
|
|
"tailscale.com/logtail/backoff"
|
|
|
|
"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 routerPlatform atomic.Pointer[router]
|
|
|
|
type router struct {
|
|
name string
|
|
sendClientInfo bool
|
|
mac sync.Map
|
|
watcher *fsnotify.Watcher
|
|
}
|
|
|
|
// SupportedPlatforms return all platforms that can be configured to run with ctrld.
|
|
func SupportedPlatforms() []string {
|
|
return []string{DDWrt, Merlin, OpenWrt, Ubios}
|
|
}
|
|
|
|
var configureFunc = map[string]func() error{
|
|
DDWrt: setupDDWrt,
|
|
Merlin: setupMerlin,
|
|
OpenWrt: setupOpenWrt,
|
|
Ubios: setupUbiOS,
|
|
}
|
|
|
|
// Configure configures things for running ctrld on the router.
|
|
func Configure(c *ctrld.Config) error {
|
|
name := Name()
|
|
switch name {
|
|
case DDWrt, Merlin, OpenWrt, Ubios:
|
|
if c.HasUpstreamSendClientInfo() {
|
|
r := routerPlatform.Load()
|
|
r.sendClientInfo = true
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.watcher = watcher
|
|
go r.watchClientInfoTable()
|
|
for _, file := range clientInfoFiles {
|
|
_ = readClientInfoFile(file)
|
|
_ = r.watcher.Add(file)
|
|
}
|
|
}
|
|
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 Merlin, Ubios:
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PreStart blocks until the router is ready for running ctrld.
|
|
func PreStart() (err error) {
|
|
if Name() != DDWrt {
|
|
return nil
|
|
}
|
|
|
|
pidFile := "/tmp/ctrld.pid"
|
|
// On Merlin, NTP may out of sync, so waiting for it to be ready.
|
|
//
|
|
// Remove pid file and trigger dnsmasq restart, so NTP can resolve
|
|
// server name and perform time synchronization.
|
|
pid, err := os.ReadFile(pidFile)
|
|
if err != nil {
|
|
return fmt.Errorf("PreStart: os.Readfile: %w", err)
|
|
}
|
|
if err := os.Remove(pidFile); err != nil {
|
|
return fmt.Errorf("PreStart: os.Remove: %w", err)
|
|
}
|
|
defer func() {
|
|
if werr := os.WriteFile(pidFile, pid, 0600); werr != nil {
|
|
err = errors.Join(err, werr)
|
|
return
|
|
}
|
|
if rerr := merlinRestartDNSMasq(); rerr != nil {
|
|
err = errors.Join(err, rerr)
|
|
return
|
|
}
|
|
}()
|
|
if err := merlinRestartDNSMasq(); err != nil {
|
|
return fmt.Errorf("PreStart: merlinRestartDNSMasq: %w", err)
|
|
}
|
|
|
|
// Wait until `ntp_read=1` set.
|
|
b := backoff.NewBackoff("PreStart", 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"))
|
|
}
|
|
}
|
|
|
|
// PostInstall performs task after installing ctrld on router.
|
|
func PostInstall() error {
|
|
name := Name()
|
|
switch name {
|
|
case DDWrt:
|
|
return postInstallDDWrt()
|
|
case Merlin:
|
|
return postInstallMerlin()
|
|
case OpenWrt:
|
|
return postInstallOpenWrt()
|
|
case Ubios:
|
|
return postInstallUbiOS()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Cleanup cleans ctrld setup on the router.
|
|
func Cleanup() error {
|
|
name := Name()
|
|
switch name {
|
|
case DDWrt:
|
|
return cleanupDDWrt()
|
|
case Merlin:
|
|
return cleanupMerlin()
|
|
case OpenWrt:
|
|
return cleanupOpenWrt()
|
|
case Ubios:
|
|
return cleanupUbiOS()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListenAddress returns the listener address of ctrld on router.
|
|
func ListenAddress() string {
|
|
name := Name()
|
|
switch name {
|
|
case DDWrt, Merlin, OpenWrt, Ubios:
|
|
return "127.0.0.1:5354"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Name returns name of the router platform.
|
|
func Name() string {
|
|
if r := routerPlatform.Load(); r != nil {
|
|
return r.name
|
|
}
|
|
r := &router{}
|
|
r.name = distroName()
|
|
routerPlatform.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
|
|
}
|