cmd: allow import/running ctrld as library

This commit is contained in:
Ginder Singh
2023-09-18 11:40:03 +00:00
committed by Cuong Manh Le
parent 2765487f10
commit 507c1afd59
8 changed files with 379 additions and 219 deletions

View File

@@ -80,7 +80,7 @@ var rootCmd = &cobra.Command{
Short: strings.TrimLeft(rootShortDesc, "\n"),
Version: curVersion(),
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
},
}
@@ -121,188 +121,10 @@ func initCLI() {
Short: "Run the DNS proxy server",
Args: cobra.NoArgs,
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
},
Run: func(cmd *cobra.Command, args []string) {
waitCh := make(chan struct{})
stopCh := make(chan struct{})
p := &prog{
waitCh: waitCh,
stopCh: stopCh,
cfg: &cfg,
}
if homedir == "" {
if dir, err := userHomeDir(); err == nil {
homedir = dir
}
}
sockPath := filepath.Join(homedir, ctrldLogUnixSock)
if addr, err := net.ResolveUnixAddr("unix", sockPath); err == nil {
if conn, err := net.Dial(addr.Network(), addr.String()); err == nil {
lc := &logConn{conn: conn}
consoleWriter.Out = io.MultiWriter(os.Stdout, lc)
p.logConn = lc
}
}
if daemon && runtime.GOOS == "windows" {
mainLog.Load().Fatal().Msg("Cannot run in daemon mode. Please install a Windows service.")
}
if !daemon {
// We need to call s.Run() as soon as possible to response to the OS manager, so it
// can see ctrld is running and don't mark ctrld as failed service.
go func() {
s, err := newService(p, svcConfig)
if err != nil {
mainLog.Load().Fatal().Err(err).Msg("failed create new service")
}
if err := s.Run(); err != nil {
mainLog.Load().Error().Err(err).Msg("failed to start service")
}
}()
}
noConfigStart := isNoConfigStart(cmd)
writeDefaultConfig := !noConfigStart && configBase64 == ""
tryReadingConfig(writeDefaultConfig)
readBase64Config(configBase64)
processNoConfigFlags(noConfigStart)
if err := v.Unmarshal(&cfg); err != nil {
mainLog.Load().Fatal().Msgf("failed to unmarshal config: %v", err)
}
processLogAndCacheFlags()
// Log config do not have thing to validate, so it's safe to init log here,
// so it's able to log information in processCDFlags.
initLogging()
mainLog.Load().Info().Msgf("starting ctrld %s", curVersion())
mainLog.Load().Info().Msgf("os: %s", osVersion())
// Wait for network up.
if !ctrldnet.Up() {
mainLog.Load().Fatal().Msg("network is not up yet")
}
p.router = router.New(&cfg, cdUID != "")
cs, err := newControlServer(filepath.Join(homedir, ctrldControlUnixSock))
if err != nil {
mainLog.Load().Warn().Err(err).Msg("could not create control server")
}
p.cs = cs
// Processing --cd flag require connecting to ControlD API, which needs valid
// time for validating server certificate. Some routers need NTP synchronization
// to set the current time, so this check must happen before processCDFlags.
if err := p.router.PreRun(); err != nil {
mainLog.Load().Fatal().Err(err).Msg("failed to perform router pre-run check")
}
oldLogPath := cfg.Service.LogPath
if uid := cdUIDFromProvToken(); uid != "" {
cdUID = uid
}
if cdUID != "" {
processCDFlags()
}
updated := updateListenerConfig()
if cdUID != "" {
processLogAndCacheFlags()
}
if updated {
if err := writeConfigFile(); err != nil {
mainLog.Load().Fatal().Err(err).Msg("failed to write config file")
} else {
mainLog.Load().Info().Msg("writing config file to: " + defaultConfigFile)
}
}
if newLogPath := cfg.Service.LogPath; newLogPath != "" && oldLogPath != newLogPath {
// After processCDFlags, log config may change, so reset mainLog and re-init logging.
l := zerolog.New(io.Discard)
mainLog.Store(&l)
// Copy logs written so far to new log file if possible.
if buf, err := os.ReadFile(oldLogPath); err == nil {
if err := os.WriteFile(newLogPath, buf, os.FileMode(0o600)); err != nil {
mainLog.Load().Warn().Err(err).Msg("could not copy old log file")
}
}
initLoggingWithBackup(false)
}
validateConfig(&cfg)
initCache()
if daemon {
exe, err := os.Executable()
if err != nil {
mainLog.Load().Error().Err(err).Msg("failed to find the binary")
os.Exit(1)
}
curDir, err := os.Getwd()
if err != nil {
mainLog.Load().Error().Err(err).Msg("failed to get current working directory")
os.Exit(1)
}
// If running as daemon, re-run the command in background, with daemon off.
cmd := exec.Command(exe, append(os.Args[1:], "-d=false")...)
cmd.Dir = curDir
if err := cmd.Start(); err != nil {
mainLog.Load().Error().Err(err).Msg("failed to start process as daemon")
os.Exit(1)
}
mainLog.Load().Info().Int("pid", cmd.Process.Pid).Msg("DNS proxy started")
os.Exit(0)
}
p.onStarted = append(p.onStarted, func() {
for _, lc := range p.cfg.Listener {
if shouldAllocateLoopbackIP(lc.IP) {
if err := allocateIP(lc.IP); err != nil {
mainLog.Load().Error().Err(err).Msgf("could not allocate IP: %s", lc.IP)
}
}
}
})
p.onStopped = append(p.onStopped, func() {
for _, lc := range p.cfg.Listener {
if shouldAllocateLoopbackIP(lc.IP) {
if err := deAllocateIP(lc.IP); err != nil {
mainLog.Load().Error().Err(err).Msgf("could not de-allocate IP: %s", lc.IP)
}
}
}
})
if platform := router.Name(); platform != "" {
if cp := router.CertPool(); cp != nil {
rootCertPool = cp
}
p.onStarted = append(p.onStarted, func() {
mainLog.Load().Debug().Msg("router setup on start")
if err := p.router.Setup(); err != nil {
mainLog.Load().Error().Err(err).Msg("could not configure router")
}
})
p.onStopped = append(p.onStopped, func() {
mainLog.Load().Debug().Msg("router cleanup on stop")
if err := p.router.Cleanup(); err != nil {
mainLog.Load().Error().Err(err).Msg("could not cleanup router")
}
p.resetDNS()
})
}
close(waitCh)
<-stopCh
for _, f := range p.onStopped {
f()
}
Run(cmd, nil, nil, nil)
},
}
runCmd.Flags().BoolVarP(&daemon, "daemon", "d", false, "Run as daemon")
@@ -327,7 +149,7 @@ func initCLI() {
startCmd := &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
checkHasElevatedPrivilege()
},
Use: "start",
@@ -405,7 +227,7 @@ func initCLI() {
mainLog.Load().Fatal().Msgf("failed to unmarshal config: %v", err)
}
initLogging()
InitLogging()
// Explicitly passing config, so on system where home directory could not be obtained,
// or sub-process env is different with the parent, we still behave correctly and use
@@ -475,7 +297,7 @@ func initCLI() {
routerCmd := &cobra.Command{
Use: "setup",
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
},
Run: func(cmd *cobra.Command, _ []string) {
exe, err := os.Executable()
@@ -504,7 +326,7 @@ func initCLI() {
stopCmd := &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
checkHasElevatedPrivilege()
},
Use: "stop",
@@ -519,7 +341,7 @@ func initCLI() {
mainLog.Load().Error().Msg(err.Error())
return
}
initLogging()
InitLogging()
if doTasks([]task{{s.Stop, true}}) {
p.router.Cleanup()
p.resetDNS()
@@ -531,7 +353,7 @@ func initCLI() {
restartCmd := &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
checkHasElevatedPrivilege()
},
Use: "restart",
@@ -543,7 +365,7 @@ func initCLI() {
mainLog.Load().Error().Msg(err.Error())
return
}
initLogging()
InitLogging()
tasks := []task{
{s.Stop, false},
@@ -569,7 +391,7 @@ func initCLI() {
Short: "Show status of the ctrld service",
Args: cobra.NoArgs,
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
},
Run: func(cmd *cobra.Command, args []string) {
s, err := newService(&prog{}, svcConfig)
@@ -598,14 +420,14 @@ func initCLI() {
if runtime.GOOS == "darwin" {
// On darwin, running status command without privileges may return wrong information.
statusCmd.PreRun = func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
checkHasElevatedPrivilege()
}
}
uninstallCmd := &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
checkHasElevatedPrivilege()
},
Use: "uninstall",
@@ -636,7 +458,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
Short: "List network interfaces of the host",
Args: cobra.NoArgs,
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
},
Run: func(cmd *cobra.Command, args []string) {
err := interfaces.ForeachInterface(func(i interfaces.Interface, prefixes []netip.Prefix) {
@@ -696,7 +518,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
rootCmd.AddCommand(serviceCmd)
startCmdAlias := &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
checkHasElevatedPrivilege()
},
Use: "start",
@@ -714,7 +536,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
rootCmd.AddCommand(startCmdAlias)
stopCmdAlias := &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
checkHasElevatedPrivilege()
},
Use: "stop",
@@ -733,7 +555,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
restartCmdAlias := &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
checkHasElevatedPrivilege()
},
Use: "restart",
@@ -749,7 +571,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
Short: "Show status of the ctrld service",
Args: cobra.NoArgs,
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
},
Run: statusCmd.Run,
}
@@ -757,7 +579,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
uninstallCmdAlias := &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
checkHasElevatedPrivilege()
},
Use: "uninstall",
@@ -782,7 +604,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
Short: "List clients that ctrld discovered",
Args: cobra.NoArgs,
PreRun: func(cmd *cobra.Command, args []string) {
initConsoleLogging()
InitConsoleLogging()
checkHasElevatedPrivilege()
},
Run: func(cmd *cobra.Command, args []string) {
@@ -838,6 +660,206 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
rootCmd.AddCommand(clientsCmd)
}
// isMobile reports whether the current OS is a mobile platform.
func isMobile() bool {
return runtime.GOOS == "android" || runtime.GOOS == "ios"
}
func Run(cmd *cobra.Command, appConfig *AppConfig, appCallback *AppCallback, stopCh chan struct{}) {
if appConfig != nil {
homedir = appConfig.HomeDir
verbose = appConfig.Verbose
cdUID = appConfig.CdUID
logPath = appConfig.LogPath
}
if stopCh == nil {
stopCh = make(chan struct{})
}
waitCh := make(chan struct{})
p := &prog{
waitCh: waitCh,
stopCh: stopCh,
cfg: &cfg,
appCallback: appCallback,
}
if homedir == "" {
if dir, err := userHomeDir(); err == nil {
homedir = dir
}
}
sockPath := filepath.Join(homedir, ctrldLogUnixSock)
if addr, err := net.ResolveUnixAddr("unix", sockPath); err == nil {
if conn, err := net.Dial(addr.Network(), addr.String()); err == nil {
lc := &logConn{conn: conn}
consoleWriter.Out = io.MultiWriter(os.Stdout, lc)
p.logConn = lc
}
}
if daemon && runtime.GOOS == "windows" {
mainLog.Load().Fatal().Msg("Cannot run in daemon mode. Please install a Windows service.")
}
if !daemon {
// We need to call s.Run() as soon as possible to response to the OS manager, so it
// can see ctrld is running and don't mark ctrld as failed service.
go func() {
s, err := newService(p, svcConfig)
if err != nil {
mainLog.Load().Fatal().Err(err).Msg("failed create new service")
}
if err := s.Run(); err != nil {
mainLog.Load().Error().Err(err).Msg("failed to start service")
}
}()
}
noConfigStart := cmd != nil && isNoConfigStart(cmd)
writeDefaultConfig := !noConfigStart && configBase64 == ""
tryReadingConfig(writeDefaultConfig)
readBase64Config(configBase64)
processNoConfigFlags(noConfigStart)
if err := v.Unmarshal(&cfg); err != nil {
mainLog.Load().Fatal().Msgf("failed to unmarshal config: %v", err)
}
processLogAndCacheFlags()
// Log config do not have thing to validate, so it's safe to init log here,
// so it's able to log information in processCDFlags.
InitLogging()
mainLog.Load().Info().Msgf("starting ctrld %s", curVersion())
mainLog.Load().Info().Msgf("os: %s", osVersion())
// Wait for network up.
if !ctrldnet.Up() {
mainLog.Load().Fatal().Msg("network is not up yet")
}
p.router = router.New(&cfg, cdUID != "")
cs, err := newControlServer(filepath.Join(homedir, ctrldControlUnixSock))
if err != nil {
mainLog.Load().Warn().Err(err).Msg("could not create control server")
}
p.cs = cs
// Processing --cd flag require connecting to ControlD API, which needs valid
// time for validating server certificate. Some routers need NTP synchronization
// to set the current time, so this check must happen before processCDFlags.
if err := p.router.PreRun(); err != nil {
mainLog.Load().Fatal().Err(err).Msg("failed to perform router pre-run check")
}
oldLogPath := cfg.Service.LogPath
if uid := cdUIDFromProvToken(); uid != "" {
cdUID = uid
}
if cdUID != "" {
err := processCDFlags()
if err != nil {
appCallback.Exit(err.Error())
return
}
}
updated := updateListenerConfig()
if cdUID != "" {
processLogAndCacheFlags()
}
if updated {
if err := writeConfigFile(); err != nil {
mainLog.Load().Fatal().Err(err).Msg("failed to write config file")
} else {
mainLog.Load().Info().Msg("writing config file to: " + defaultConfigFile)
}
}
if newLogPath := cfg.Service.LogPath; newLogPath != "" && oldLogPath != newLogPath {
// After processCDFlags, log config may change, so reset mainLog and re-init logging.
l := zerolog.New(io.Discard)
mainLog.Store(&l)
// Copy logs written so far to new log file if possible.
if buf, err := os.ReadFile(oldLogPath); err == nil {
if err := os.WriteFile(newLogPath, buf, os.FileMode(0o600)); err != nil {
mainLog.Load().Warn().Err(err).Msg("could not copy old log file")
}
}
initLoggingWithBackup(false)
}
validateConfig(&cfg)
initCache()
if daemon {
exe, err := os.Executable()
if err != nil {
mainLog.Load().Error().Err(err).Msg("failed to find the binary")
os.Exit(1)
}
curDir, err := os.Getwd()
if err != nil {
mainLog.Load().Error().Err(err).Msg("failed to get current working directory")
os.Exit(1)
}
// If running as daemon, re-run the command in background, with daemon off.
cmd := exec.Command(exe, append(os.Args[1:], "-d=false")...)
cmd.Dir = curDir
if err := cmd.Start(); err != nil {
mainLog.Load().Error().Err(err).Msg("failed to start process as daemon")
os.Exit(1)
}
mainLog.Load().Info().Int("pid", cmd.Process.Pid).Msg("DNS proxy started")
os.Exit(0)
}
p.onStarted = append(p.onStarted, func() {
for _, lc := range p.cfg.Listener {
if shouldAllocateLoopbackIP(lc.IP) {
if err := allocateIP(lc.IP); err != nil {
mainLog.Load().Error().Err(err).Msgf("could not allocate IP: %s", lc.IP)
}
}
}
})
p.onStopped = append(p.onStopped, func() {
for _, lc := range p.cfg.Listener {
if shouldAllocateLoopbackIP(lc.IP) {
if err := deAllocateIP(lc.IP); err != nil {
mainLog.Load().Error().Err(err).Msgf("could not de-allocate IP: %s", lc.IP)
}
}
}
})
if platform := router.Name(); platform != "" {
if cp := router.CertPool(); cp != nil {
rootCertPool = cp
}
p.onStarted = append(p.onStarted, func() {
mainLog.Load().Debug().Msg("router setup on start")
if err := p.router.Setup(); err != nil {
mainLog.Load().Error().Err(err).Msg("could not configure router")
}
})
p.onStopped = append(p.onStopped, func() {
mainLog.Load().Debug().Msg("router cleanup on stop")
if err := p.router.Cleanup(); err != nil {
mainLog.Load().Error().Err(err).Msg("could not cleanup router")
}
p.resetDNS()
})
}
close(waitCh)
<-stopCh
for _, f := range p.onStopped {
f()
}
}
func writeConfigFile() error {
if cfu := v.ConfigFileUsed(); cfu != "" {
defaultConfigFile = cfu
@@ -972,7 +994,7 @@ func processNoConfigFlags(noConfigStart bool) {
v.Set("upstream", upstream)
}
func processCDFlags() {
func processCDFlags() error {
logger := mainLog.Load().With().Str("mode", "cd").Logger()
logger.Info().Msgf("fetching Controld D configuration from API: %s", cdUID)
bo := backoff.NewBackoff("processCDFlags", logf, 30*time.Second)
@@ -992,12 +1014,12 @@ func processCDFlags() {
s, err := newService(&prog{}, svcConfig)
if err != nil {
logger.Warn().Err(err).Msg("failed to create new service")
return
return nil
}
if netIface, _ := netInterface(iface); netIface != nil {
if err := restoreNetworkManager(); err != nil {
logger.Error().Err(err).Msg("could not restore NetworkManager")
return
return nil
}
logger.Debug().Str("iface", netIface.Name).Msg("Restoring DNS for interface")
if err := resetDNS(netIface); err != nil {
@@ -1011,11 +1033,16 @@ func processCDFlags() {
if doTasks(tasks) {
logger.Info().Msg("uninstalled service")
}
logger.Fatal().Err(uer).Msg("failed to fetch resolver config")
event := logger.Fatal()
if isMobile() {
event = logger.Warn()
}
event.Err(uer).Msg("failed to fetch resolver config")
return uer
}
if err != nil {
logger.Warn().Err(err).Msg("could not fetch resolver config")
return
return nil
}
logger.Info().Msg("generating ctrld config from Control-D configuration")
@@ -1059,6 +1086,7 @@ func processCDFlags() {
"0": {IP: "", Port: 0},
}
}
return nil
}
func processListenFlag() {
@@ -1269,6 +1297,10 @@ func userHomeDir() (string, error) {
if runtime.GOOS == "windows" {
return os.UserHomeDir()
}
// Mobile platform should provide a rw dir path for this.
if isMobile() {
return homedir, nil
}
dir = "/etc/controld"
if err := os.MkdirAll(dir, 0750); err != nil {
return os.UserHomeDir() // fallback to user home directory
@@ -1321,7 +1353,7 @@ func uninstall(p *prog, s service.Service) {
{s.Stop, false},
{s.Uninstall, true},
}
initLogging()
InitLogging()
if doTasks(tasks) {
if err := p.router.ConfigureService(svcConfig); err != nil {
mainLog.Load().Fatal().Err(err).Msg("could not configure service")
@@ -1413,6 +1445,14 @@ type listenerConfigCheck struct {
Port bool
}
// mobileListenerPort returns hardcoded port for mobile platforms.
func mobileListenerPort() int {
if runtime.GOOS == "ios" {
return 53
}
return 5354
}
// updateListenerConfig updates the config for listeners if not defined,
// or defined but invalid to be used, e.g: using loopback address other
// than 127.0.0.1 with systemd-resolved.
@@ -1436,7 +1476,25 @@ func updateListenerConfig() (updated bool) {
}
updated = updated || lcc[n].IP || lcc[n].Port
}
if isMobile() {
// On Mobile, only use first listener, ignore others.
firstLn := cfg.FirstListener()
for k := range cfg.Listener {
if cfg.Listener[k] != firstLn {
delete(cfg.Listener, k)
}
}
// In cd mode, always use 127.0.0.1:5354.
if cdMode {
firstLn.IP = "127.0.0.1" // Mobile platforms allows running listener only on loop back address.
firstLn.Port = mobileListenerPort()
// TODO: use clear(lcc) once upgrading to go 1.21
for k := range lcc {
delete(lcc, k)
}
updated = true
}
}
var closers []io.Closer
defer func() {
for _, closer := range closers {

View File

@@ -491,6 +491,12 @@ func runDNSServer(addr, network string, handler dns.Handler) (*dns.Server, <-cha
func (p *prog) getClientInfo(remoteIP string, msg *dns.Msg) *ctrld.ClientInfo {
ci := &ctrld.ClientInfo{}
if p.appCallback != nil {
ci.IP = p.appCallback.LanIp()
ci.Mac = p.appCallback.MacAddress()
ci.Hostname = p.appCallback.HostName()
return ci
}
ci.IP, ci.Mac = ipAndMacFromMsg(msg)
switch {
case ci.IP != "" && ci.Mac != "":

15
cmd/cli/library.go Normal file
View File

@@ -0,0 +1,15 @@
package cli
type AppCallback struct {
HostName func() string
LanIp func() string
MacAddress func() string
Exit func(error string)
}
type AppConfig struct {
CdUID string
HomeDir string
Verbose int
LogPath string
}

View File

@@ -65,7 +65,7 @@ func normalizeLogFilePath(logFilePath string) string {
return filepath.Join(dir, logFilePath)
}
func initConsoleLogging() {
func InitConsoleLogging() {
consoleWriter = zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
w.TimeFormat = time.StampMilli
})
@@ -84,8 +84,8 @@ func initConsoleLogging() {
}
}
// initLogging initializes global logging setup.
func initLogging() {
// InitLogging initializes global logging setup.
func InitLogging() {
initLoggingWithBackup(true)
}

View File

@@ -49,11 +49,12 @@ type prog struct {
logConn net.Conn
cs *controlServer
cfg *ctrld.Config
cache dnscache.Cacher
sema semaphore
ciTable *clientinfo.Table
router router.Router
cfg *ctrld.Config
appCallback *AppCallback
cache dnscache.Cacher
sema semaphore
ciTable *clientinfo.Table
router router.Router
started chan struct{}
onStartedDone chan struct{}
@@ -136,12 +137,14 @@ func (p *prog) run() {
format := ctrld.LeaseFileFormat(p.cfg.Service.DHCPLeaseFileFormat)
p.ciTable.AddLeaseFile(leaseFile, format)
}
go func() {
p.ciTable.Init()
p.ciTable.RefreshLoop(p.stopCh)
}()
go p.watchLinkState()
// Newer versions of android and iOS denies permission which breaks connectivity.
if !isMobile() {
go func() {
p.ciTable.Init()
p.ciTable.RefreshLoop(p.stopCh)
}()
go p.watchLinkState()
}
for listenerNum := range p.cfg.Listener {
p.cfg.Listener[listenerNum].Init()

75
cmd/ctrld_library/main.go Normal file
View File

@@ -0,0 +1,75 @@
package ctrld_library
import (
"github.com/Control-D-Inc/ctrld/cmd/cli"
)
// Controller holds global state
type Controller struct {
stopCh chan struct{}
AppCallback AppCallback
Config cli.AppConfig
}
// NewController provides reference to global state to be managed by android vpn service and iOS network extension.
// reference is not safe for concurrent use.
func NewController(appCallback AppCallback) *Controller {
return &Controller{AppCallback: appCallback}
}
// AppCallback provides access to app instance.
type AppCallback interface {
Hostname() string
LanIp() string
MacAddress() string
Exit(error string)
}
// Start configures utility with config.toml from provided directory.
// This function will block until Stop is called
// Check port availability prior to calling it.
func (c *Controller) Start(CdUID string, HomeDir string, logLevel int, logPath string) {
if c.stopCh == nil {
c.stopCh = make(chan struct{})
cli.InitConsoleLogging()
c.Config = cli.AppConfig{
CdUID: CdUID,
HomeDir: HomeDir,
Verbose: logLevel,
LogPath: logPath,
}
appCallback := mapCallback(c.AppCallback)
cli.Run(nil, &c.Config, &appCallback, c.stopCh)
}
}
// As workaround to avoid circular dependency between cli and ctrld_library module
func mapCallback(callback AppCallback) cli.AppCallback {
return cli.AppCallback{
HostName: func() string {
return callback.Hostname()
},
LanIp: func() string {
return callback.LanIp()
},
MacAddress: func() string {
return callback.MacAddress()
},
Exit: func(err string) {
callback.Exit(err)
},
}
}
func (c *Controller) Stop() bool {
if c.stopCh != nil {
close(c.stopCh)
c.stopCh = nil
return true
}
return false
}
func (c *Controller) IsRunning() bool {
return c.stopCh != nil
}

1
go.mod
View File

@@ -72,6 +72,7 @@ require (
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.9.1 // indirect

2
go.sum
View File

@@ -329,6 +329,8 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda h1:O+EUvnBNPwI4eLthn8W5K+cS8zQZfgTABPLNm6Bna34=
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=