mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
cmd: allow import/running ctrld as library
This commit is contained in:
committed by
Cuong Manh Le
parent
2765487f10
commit
507c1afd59
468
cmd/cli/cli.go
468
cmd/cli/cli.go
@@ -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 {
|
||||
|
||||
@@ -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
15
cmd/cli/library.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
75
cmd/ctrld_library/main.go
Normal 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
1
go.mod
@@ -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
2
go.sum
@@ -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=
|
||||
|
||||
Reference in New Issue
Block a user