mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-16 10:22:45 +00:00
The only reason that forces ctrld to depend on vyatta-dhcpd service on EdgeOS is allowing ctrld to watch lease files properly, because those files may not be created at the time client info table initialized. However, on some EdgeOS version, vyatta-dhcpd could not start with an empty config file, causing restart loop itself, flooding systemd log, making the router run out of memory. To fix this, instead of depending on vyatta-dhcpd, we should just watch for lease files creation, then adding them to watch list. While at it, also making ctrld starts after nss-lookup, ensuring we have a working DNS before starting ctrld.
237 lines
5.9 KiB
Go
237 lines
5.9 KiB
Go
package router
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/x509"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sync/atomic"
|
|
|
|
"github.com/kardianos/service"
|
|
|
|
"github.com/Control-D-Inc/ctrld"
|
|
"github.com/Control-D-Inc/ctrld/internal/certs"
|
|
"github.com/Control-D-Inc/ctrld/internal/router/ddwrt"
|
|
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
|
"github.com/Control-D-Inc/ctrld/internal/router/edgeos"
|
|
"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/synology"
|
|
"github.com/Control-D-Inc/ctrld/internal/router/tomato"
|
|
"github.com/Control-D-Inc/ctrld/internal/router/ubios"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// Router is the interface for managing ctrld running on router.
|
|
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, cdMode bool) Router {
|
|
switch Name() {
|
|
case ddwrt.Name:
|
|
return ddwrt.New(cfg)
|
|
case merlin.Name:
|
|
return merlin.New(cfg)
|
|
case openwrt.Name:
|
|
return openwrt.New(cfg)
|
|
case edgeos.Name:
|
|
return edgeos.New(cfg)
|
|
case ubios.Name:
|
|
return ubios.New(cfg)
|
|
case synology.Name:
|
|
return synology.New(cfg)
|
|
case tomato.Name:
|
|
return tomato.New(cfg)
|
|
case firewalla.Name:
|
|
return firewalla.New(cfg)
|
|
}
|
|
return newOsRouter(cfg, cdMode)
|
|
}
|
|
|
|
// IsGLiNet reports whether the router is an GL.iNet router.
|
|
func IsGLiNet() bool {
|
|
if Name() != openwrt.Name {
|
|
return false
|
|
}
|
|
buf, _ := os.ReadFile("/proc/version")
|
|
// The output of /proc/version contains "(glinet@glinet)".
|
|
return bytes.Contains(buf, []byte(" (glinet"))
|
|
}
|
|
|
|
// IsOldOpenwrt reports whether the router is an "old" version of Openwrt,
|
|
// aka versions which don't have "service" command.
|
|
func IsOldOpenwrt() bool {
|
|
if Name() != openwrt.Name {
|
|
return false
|
|
}
|
|
cmd, _ := exec.LookPath("service")
|
|
return cmd == ""
|
|
}
|
|
|
|
var routerPlatform atomic.Pointer[router]
|
|
|
|
type router struct {
|
|
name string
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// DefaultInterfaceName returns the default interface name of the current router.
|
|
func DefaultInterfaceName() string {
|
|
switch Name() {
|
|
case ubios.Name:
|
|
return "lo"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// LocalResolverIP returns the IP that could be used as nameserver in /etc/resolv.conf file.
|
|
func LocalResolverIP() string {
|
|
var iface string
|
|
switch Name() {
|
|
case edgeos.Name:
|
|
// On EdgeOS, dnsmasq is run with "--local-service", so we need to get
|
|
// the proper interface from dnsmasq config.
|
|
if name, _ := dnsmasq.InterfaceNameFromConfig("/etc/dnsmasq.conf"); name != "" {
|
|
iface = name
|
|
}
|
|
case firewalla.Name:
|
|
// On Firewalla, the lo interface is excluded in all dnsmasq settings of all interfaces.
|
|
// Thus, we use "br0" as the nameserver in /etc/resolv.conf file.
|
|
iface = "br0"
|
|
}
|
|
if netIface, _ := net.InterfaceByName(iface); netIface != nil {
|
|
addrs, _ := netIface.Addrs()
|
|
for _, addr := range addrs {
|
|
if netIP, ok := addr.(*net.IPNet); ok && netIP.IP.To4() != nil {
|
|
return netIP.IP.To4().String()
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// HomeDir returns the home directory of ctrld on current router.
|
|
func HomeDir() (string, error) {
|
|
switch Name() {
|
|
case ddwrt.Name, merlin.Name, tomato.Name:
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Dir(exe), nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// CertPool returns the system certificate pool of the current router.
|
|
func CertPool() *x509.CertPool {
|
|
if Name() == ddwrt.Name {
|
|
return certs.CACertPool()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CanListenLocalhost reports whether the ctrld can listen on localhost with current host.
|
|
func CanListenLocalhost() bool {
|
|
switch {
|
|
case Name() == firewalla.Name:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
// SelfInterfaces return list of *net.Interface that will be source of requests from router itself.
|
|
func SelfInterfaces() []*net.Interface {
|
|
switch Name() {
|
|
case firewalla.Name:
|
|
return dnsmasq.FirewallaSelfInterfaces()
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// LeaseFilesDir is the directory which contains lease files.
|
|
func LeaseFilesDir() string {
|
|
if Name() == edgeos.Name {
|
|
edgeos.LeaseFileDir()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func distroName() string {
|
|
switch {
|
|
case bytes.HasPrefix(unameO(), []byte("DD-WRT")):
|
|
return ddwrt.Name
|
|
case bytes.HasPrefix(unameO(), []byte("ASUSWRT-Merlin")):
|
|
return merlin.Name
|
|
case haveFile("/etc/openwrt_version"):
|
|
return openwrt.Name
|
|
case haveDir("/data/unifi"):
|
|
return ubios.Name
|
|
case bytes.HasPrefix(unameU(), []byte("synology")):
|
|
return synology.Name
|
|
case bytes.HasPrefix(unameO(), []byte("Tomato")):
|
|
return tomato.Name
|
|
case haveDir("/config/scripts/post-config.d"):
|
|
return edgeos.Name
|
|
case haveFile("/etc/ubnt/init/vyatta-router"):
|
|
return edgeos.Name // For 2.x
|
|
case haveFile("/etc/firewalla_release"):
|
|
return firewalla.Name
|
|
}
|
|
return osName
|
|
}
|
|
|
|
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 unameO() []byte {
|
|
out, _ := exec.Command("uname", "-o").Output()
|
|
return out
|
|
}
|
|
|
|
func unameU() []byte {
|
|
out, _ := exec.Command("uname", "-u").Output()
|
|
return out
|
|
}
|