mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-09-29 13:51:51 +02:00
cmd: allow import/running ctrld as library
This commit is contained in:
1 parent
2765487f10
commit
507c1afd59
8 files changed
+379
-219
No files matched your search
+263
-205
@@ -80,7 +80,7 @@ var rootCmd = &cobra.Command{
|
|||||||
Short: strings.TrimLeft(rootShortDesc, "\n"),
|
Short: strings.TrimLeft(rootShortDesc, "\n"),
|
||||||
Version: curVersion(),
|
Version: curVersion(),
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,188 +121,10 @@ func initCLI() {
|
|||||||
Short: "Run the DNS proxy server",
|
Short: "Run the DNS proxy server",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
waitCh := make(chan struct{})
|
Run(cmd, nil, nil, nil)
|
||||||
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()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
runCmd.Flags().BoolVarP(&daemon, "daemon", "d", false, "Run as daemon")
|
runCmd.Flags().BoolVarP(&daemon, "daemon", "d", false, "Run as daemon")
|
||||||
@@ -327,7 +149,7 @@ func initCLI() {
|
|||||||
|
|
||||||
startCmd := &cobra.Command{
|
startCmd := &cobra.Command{
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
checkHasElevatedPrivilege()
|
checkHasElevatedPrivilege()
|
||||||
},
|
},
|
||||||
Use: "start",
|
Use: "start",
|
||||||
@@ -405,7 +227,7 @@ func initCLI() {
|
|||||||
mainLog.Load().Fatal().Msgf("failed to unmarshal config: %v", err)
|
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,
|
// 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
|
// or sub-process env is different with the parent, we still behave correctly and use
|
||||||
@@ -475,7 +297,7 @@ func initCLI() {
|
|||||||
routerCmd := &cobra.Command{
|
routerCmd := &cobra.Command{
|
||||||
Use: "setup",
|
Use: "setup",
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, _ []string) {
|
Run: func(cmd *cobra.Command, _ []string) {
|
||||||
exe, err := os.Executable()
|
exe, err := os.Executable()
|
||||||
@@ -504,7 +326,7 @@ func initCLI() {
|
|||||||
|
|
||||||
stopCmd := &cobra.Command{
|
stopCmd := &cobra.Command{
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
checkHasElevatedPrivilege()
|
checkHasElevatedPrivilege()
|
||||||
},
|
},
|
||||||
Use: "stop",
|
Use: "stop",
|
||||||
@@ -519,7 +341,7 @@ func initCLI() {
|
|||||||
mainLog.Load().Error().Msg(err.Error())
|
mainLog.Load().Error().Msg(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
initLogging()
|
InitLogging()
|
||||||
if doTasks([]task{{s.Stop, true}}) {
|
if doTasks([]task{{s.Stop, true}}) {
|
||||||
p.router.Cleanup()
|
p.router.Cleanup()
|
||||||
p.resetDNS()
|
p.resetDNS()
|
||||||
@@ -531,7 +353,7 @@ func initCLI() {
|
|||||||
|
|
||||||
restartCmd := &cobra.Command{
|
restartCmd := &cobra.Command{
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
checkHasElevatedPrivilege()
|
checkHasElevatedPrivilege()
|
||||||
},
|
},
|
||||||
Use: "restart",
|
Use: "restart",
|
||||||
@@ -543,7 +365,7 @@ func initCLI() {
|
|||||||
mainLog.Load().Error().Msg(err.Error())
|
mainLog.Load().Error().Msg(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
initLogging()
|
InitLogging()
|
||||||
|
|
||||||
tasks := []task{
|
tasks := []task{
|
||||||
{s.Stop, false},
|
{s.Stop, false},
|
||||||
@@ -569,7 +391,7 @@ func initCLI() {
|
|||||||
Short: "Show status of the ctrld service",
|
Short: "Show status of the ctrld service",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
s, err := newService(&prog{}, svcConfig)
|
s, err := newService(&prog{}, svcConfig)
|
||||||
@@ -598,14 +420,14 @@ func initCLI() {
|
|||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
// On darwin, running status command without privileges may return wrong information.
|
// On darwin, running status command without privileges may return wrong information.
|
||||||
statusCmd.PreRun = func(cmd *cobra.Command, args []string) {
|
statusCmd.PreRun = func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
checkHasElevatedPrivilege()
|
checkHasElevatedPrivilege()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uninstallCmd := &cobra.Command{
|
uninstallCmd := &cobra.Command{
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
checkHasElevatedPrivilege()
|
checkHasElevatedPrivilege()
|
||||||
},
|
},
|
||||||
Use: "uninstall",
|
Use: "uninstall",
|
||||||
@@ -636,7 +458,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
|
|||||||
Short: "List network interfaces of the host",
|
Short: "List network interfaces of the host",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := interfaces.ForeachInterface(func(i interfaces.Interface, prefixes []netip.Prefix) {
|
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)
|
rootCmd.AddCommand(serviceCmd)
|
||||||
startCmdAlias := &cobra.Command{
|
startCmdAlias := &cobra.Command{
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
checkHasElevatedPrivilege()
|
checkHasElevatedPrivilege()
|
||||||
},
|
},
|
||||||
Use: "start",
|
Use: "start",
|
||||||
@@ -714,7 +536,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
|
|||||||
rootCmd.AddCommand(startCmdAlias)
|
rootCmd.AddCommand(startCmdAlias)
|
||||||
stopCmdAlias := &cobra.Command{
|
stopCmdAlias := &cobra.Command{
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
checkHasElevatedPrivilege()
|
checkHasElevatedPrivilege()
|
||||||
},
|
},
|
||||||
Use: "stop",
|
Use: "stop",
|
||||||
@@ -733,7 +555,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
|
|||||||
|
|
||||||
restartCmdAlias := &cobra.Command{
|
restartCmdAlias := &cobra.Command{
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
checkHasElevatedPrivilege()
|
checkHasElevatedPrivilege()
|
||||||
},
|
},
|
||||||
Use: "restart",
|
Use: "restart",
|
||||||
@@ -749,7 +571,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
|
|||||||
Short: "Show status of the ctrld service",
|
Short: "Show status of the ctrld service",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
},
|
},
|
||||||
Run: statusCmd.Run,
|
Run: statusCmd.Run,
|
||||||
}
|
}
|
||||||
@@ -757,7 +579,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
|
|||||||
|
|
||||||
uninstallCmdAlias := &cobra.Command{
|
uninstallCmdAlias := &cobra.Command{
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
checkHasElevatedPrivilege()
|
checkHasElevatedPrivilege()
|
||||||
},
|
},
|
||||||
Use: "uninstall",
|
Use: "uninstall",
|
||||||
@@ -782,7 +604,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
|
|||||||
Short: "List clients that ctrld discovered",
|
Short: "List clients that ctrld discovered",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
initConsoleLogging()
|
InitConsoleLogging()
|
||||||
checkHasElevatedPrivilege()
|
checkHasElevatedPrivilege()
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
@@ -838,6 +660,206 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
|
|||||||
rootCmd.AddCommand(clientsCmd)
|
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 {
|
func writeConfigFile() error {
|
||||||
if cfu := v.ConfigFileUsed(); cfu != "" {
|
if cfu := v.ConfigFileUsed(); cfu != "" {
|
||||||
defaultConfigFile = cfu
|
defaultConfigFile = cfu
|
||||||
@@ -972,7 +994,7 @@ func processNoConfigFlags(noConfigStart bool) {
|
|||||||
v.Set("upstream", upstream)
|
v.Set("upstream", upstream)
|
||||||
}
|
}
|
||||||
|
|
||||||
func processCDFlags() {
|
func processCDFlags() error {
|
||||||
logger := mainLog.Load().With().Str("mode", "cd").Logger()
|
logger := mainLog.Load().With().Str("mode", "cd").Logger()
|
||||||
logger.Info().Msgf("fetching Controld D configuration from API: %s", cdUID)
|
logger.Info().Msgf("fetching Controld D configuration from API: %s", cdUID)
|
||||||
bo := backoff.NewBackoff("processCDFlags", logf, 30*time.Second)
|
bo := backoff.NewBackoff("processCDFlags", logf, 30*time.Second)
|
||||||
@@ -992,12 +1014,12 @@ func processCDFlags() {
|
|||||||
s, err := newService(&prog{}, svcConfig)
|
s, err := newService(&prog{}, svcConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn().Err(err).Msg("failed to create new service")
|
logger.Warn().Err(err).Msg("failed to create new service")
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
if netIface, _ := netInterface(iface); netIface != nil {
|
if netIface, _ := netInterface(iface); netIface != nil {
|
||||||
if err := restoreNetworkManager(); err != nil {
|
if err := restoreNetworkManager(); err != nil {
|
||||||
logger.Error().Err(err).Msg("could not restore NetworkManager")
|
logger.Error().Err(err).Msg("could not restore NetworkManager")
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
logger.Debug().Str("iface", netIface.Name).Msg("Restoring DNS for interface")
|
logger.Debug().Str("iface", netIface.Name).Msg("Restoring DNS for interface")
|
||||||
if err := resetDNS(netIface); err != nil {
|
if err := resetDNS(netIface); err != nil {
|
||||||
@@ -1011,11 +1033,16 @@ func processCDFlags() {
|
|||||||
if doTasks(tasks) {
|
if doTasks(tasks) {
|
||||||
logger.Info().Msg("uninstalled service")
|
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 {
|
if err != nil {
|
||||||
logger.Warn().Err(err).Msg("could not fetch resolver config")
|
logger.Warn().Err(err).Msg("could not fetch resolver config")
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info().Msg("generating ctrld config from Control-D configuration")
|
logger.Info().Msg("generating ctrld config from Control-D configuration")
|
||||||
@@ -1059,6 +1086,7 @@ func processCDFlags() {
|
|||||||
"0": {IP: "", Port: 0},
|
"0": {IP: "", Port: 0},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func processListenFlag() {
|
func processListenFlag() {
|
||||||
@@ -1269,6 +1297,10 @@ func userHomeDir() (string, error) {
|
|||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return os.UserHomeDir()
|
return os.UserHomeDir()
|
||||||
}
|
}
|
||||||
|
// Mobile platform should provide a rw dir path for this.
|
||||||
|
if isMobile() {
|
||||||
|
return homedir, nil
|
||||||
|
}
|
||||||
dir = "/etc/controld"
|
dir = "/etc/controld"
|
||||||
if err := os.MkdirAll(dir, 0750); err != nil {
|
if err := os.MkdirAll(dir, 0750); err != nil {
|
||||||
return os.UserHomeDir() // fallback to user home directory
|
return os.UserHomeDir() // fallback to user home directory
|
||||||
@@ -1321,7 +1353,7 @@ func uninstall(p *prog, s service.Service) {
|
|||||||
{s.Stop, false},
|
{s.Stop, false},
|
||||||
{s.Uninstall, true},
|
{s.Uninstall, true},
|
||||||
}
|
}
|
||||||
initLogging()
|
InitLogging()
|
||||||
if doTasks(tasks) {
|
if doTasks(tasks) {
|
||||||
if err := p.router.ConfigureService(svcConfig); err != nil {
|
if err := p.router.ConfigureService(svcConfig); err != nil {
|
||||||
mainLog.Load().Fatal().Err(err).Msg("could not configure service")
|
mainLog.Load().Fatal().Err(err).Msg("could not configure service")
|
||||||
@@ -1413,6 +1445,14 @@ type listenerConfigCheck struct {
|
|||||||
Port bool
|
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,
|
// updateListenerConfig updates the config for listeners if not defined,
|
||||||
// or defined but invalid to be used, e.g: using loopback address other
|
// or defined but invalid to be used, e.g: using loopback address other
|
||||||
// than 127.0.0.1 with systemd-resolved.
|
// than 127.0.0.1 with systemd-resolved.
|
||||||
@@ -1436,7 +1476,25 @@ func updateListenerConfig() (updated bool) {
|
|||||||
}
|
}
|
||||||
updated = updated || lcc[n].IP || lcc[n].Port
|
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
|
var closers []io.Closer
|
||||||
defer func() {
|
defer func() {
|
||||||
for _, closer := range closers {
|
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 {
|
func (p *prog) getClientInfo(remoteIP string, msg *dns.Msg) *ctrld.ClientInfo {
|
||||||
ci := &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)
|
ci.IP, ci.Mac = ipAndMacFromMsg(msg)
|
||||||
switch {
|
switch {
|
||||||
case ci.IP != "" && ci.Mac != "":
|
case ci.IP != "" && ci.Mac != "":
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
+3
-3
@@ -65,7 +65,7 @@ func normalizeLogFilePath(logFilePath string) string {
|
|||||||
return filepath.Join(dir, logFilePath)
|
return filepath.Join(dir, logFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initConsoleLogging() {
|
func InitConsoleLogging() {
|
||||||
consoleWriter = zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
|
consoleWriter = zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
|
||||||
w.TimeFormat = time.StampMilli
|
w.TimeFormat = time.StampMilli
|
||||||
})
|
})
|
||||||
@@ -84,8 +84,8 @@ func initConsoleLogging() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initLogging initializes global logging setup.
|
// InitLogging initializes global logging setup.
|
||||||
func initLogging() {
|
func InitLogging() {
|
||||||
initLoggingWithBackup(true)
|
initLoggingWithBackup(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-11
@@ -49,11 +49,12 @@ type prog struct {
|
|||||||
logConn net.Conn
|
logConn net.Conn
|
||||||
cs *controlServer
|
cs *controlServer
|
||||||
|
|
||||||
cfg *ctrld.Config
|
cfg *ctrld.Config
|
||||||
cache dnscache.Cacher
|
appCallback *AppCallback
|
||||||
sema semaphore
|
cache dnscache.Cacher
|
||||||
ciTable *clientinfo.Table
|
sema semaphore
|
||||||
router router.Router
|
ciTable *clientinfo.Table
|
||||||
|
router router.Router
|
||||||
|
|
||||||
started chan struct{}
|
started chan struct{}
|
||||||
onStartedDone chan struct{}
|
onStartedDone chan struct{}
|
||||||
@@ -136,12 +137,14 @@ func (p *prog) run() {
|
|||||||
format := ctrld.LeaseFileFormat(p.cfg.Service.DHCPLeaseFileFormat)
|
format := ctrld.LeaseFileFormat(p.cfg.Service.DHCPLeaseFileFormat)
|
||||||
p.ciTable.AddLeaseFile(leaseFile, format)
|
p.ciTable.AddLeaseFile(leaseFile, format)
|
||||||
}
|
}
|
||||||
|
// Newer versions of android and iOS denies permission which breaks connectivity.
|
||||||
go func() {
|
if !isMobile() {
|
||||||
p.ciTable.Init()
|
go func() {
|
||||||
p.ciTable.RefreshLoop(p.stopCh)
|
p.ciTable.Init()
|
||||||
}()
|
p.ciTable.RefreshLoop(p.stopCh)
|
||||||
go p.watchLinkState()
|
}()
|
||||||
|
go p.watchLinkState()
|
||||||
|
}
|
||||||
|
|
||||||
for listenerNum := range p.cfg.Listener {
|
for listenerNum := range p.cfg.Listener {
|
||||||
p.cfg.Listener[listenerNum].Init()
|
p.cfg.Listener[listenerNum].Init()
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -72,6 +72,7 @@ require (
|
|||||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
|
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
|
||||||
golang.org/x/crypto v0.9.0 // indirect
|
golang.org/x/crypto v0.9.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // 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/mod v0.10.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
golang.org/x/tools v0.9.1 // indirect
|
golang.org/x/tools v0.9.1 // indirect
|
||||||
|
|||||||
@@ -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/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-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-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.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.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
|||||||
Reference in new issue
Block a user