Files
ctrld/internal/router/router.go
T
Cuong Manh Le 0645a738ad all: add router client info detection
This commit add the ability for ctrld to gather client information,
including mac/ip/hostname, and send to Control-D server through a
config per upstream.

 - Add send_client_info upstream config.
 - Read/Watch dnsmasq leases files on supported platforms.
 - Add corresponding client info to DoH query header

All of these only apply for Control-D upstream, though.
2023-05-13 12:03:24 +07:00

173 lines
3.4 KiB
Go

package router
import (
"bytes"
"errors"
"os"
"os/exec"
"sync"
"sync/atomic"
"github.com/fsnotify/fsnotify"
"github.com/kardianos/service"
"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 ddwrtJffs2NotEnabledErr
}
case OpenWrt:
sc.Option["SysvScript"] = openWrtScript
case Merlin, Ubios:
}
return nil
}
// 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
}