diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 4a10928..8913b0f 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -429,6 +429,7 @@ func initCLI() { {s.Install, false}, {s.Start, true}, } + mainLog.Load().Notice().Msg("Starting service") if doTasks(tasks) { if err := p.router.Install(sc); err != nil { mainLog.Load().Warn().Err(err).Msg("post installation failed, please check system/service log for details error") @@ -450,12 +451,7 @@ func initCLI() { uninstall(p, s) os.Exit(1) } - // On Linux, Darwin, Freebsd, ctrld set DNS on startup, because the DNS setting could be - // reset after rebooting. On windows, we only need to set once here. See prog.preRun in - // prog_*.go file for dedicated code on each platform. (1) - if runtime.GOOS == "windows" { - p.setDNS() - } + p.setDNS() } }, } @@ -524,10 +520,7 @@ func initCLI() { initLogging() if doTasks([]task{{s.Stop, true}}) { p.router.Cleanup() - // See comment (1) in startCmd. - if runtime.GOOS != "windows" { - p.resetDNS() - } + p.resetDNS() mainLog.Load().Notice().Msg("Service stopped") } }, diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index e955c92..fab10cc 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -7,6 +7,7 @@ import ( "net" "net/url" "os" + "runtime" "strconv" "sync" "syscall" @@ -63,6 +64,19 @@ func (p *prog) Start(s service.Service) error { return nil } +func (p *prog) preRun() { + if !service.Interactive() { + p.setDNS() + } + if runtime.GOOS == "darwin" { + p.onStopped = append(p.onStopped, func() { + if !service.Interactive() { + p.resetDNS() + } + }) + } +} + func (p *prog) run() { // Wait the caller to signal that we can do our logic. <-p.waitCh diff --git a/cmd/ctrld/prog_darwin.go b/cmd/ctrld/prog_darwin.go index 4d9ad0a..1a6656d 100644 --- a/cmd/ctrld/prog_darwin.go +++ b/cmd/ctrld/prog_darwin.go @@ -4,17 +4,6 @@ import ( "github.com/kardianos/service" ) -func (p *prog) preRun() { - if !service.Interactive() { - p.setDNS() - } - p.onStopped = append(p.onStopped, func() { - if !service.Interactive() { - p.resetDNS() - } - }) -} - func setDependencies(svc *service.Config) {} func setWorkingDirectory(svc *service.Config, dir string) { diff --git a/cmd/ctrld/prog_freebsd.go b/cmd/ctrld/prog_freebsd.go index 63d8179..283e03c 100644 --- a/cmd/ctrld/prog_freebsd.go +++ b/cmd/ctrld/prog_freebsd.go @@ -6,12 +6,6 @@ import ( "github.com/kardianos/service" ) -func (p *prog) preRun() { - if !service.Interactive() { - p.setDNS() - } -} - func setDependencies(svc *service.Config) { // TODO(cuonglm): remove once https://github.com/kardianos/service/issues/359 fixed. _ = os.MkdirAll("/usr/local/etc/rc.d", 0755) diff --git a/cmd/ctrld/prog_linux.go b/cmd/ctrld/prog_linux.go index 58332bb..f14a054 100644 --- a/cmd/ctrld/prog_linux.go +++ b/cmd/ctrld/prog_linux.go @@ -13,12 +13,6 @@ func init() { } } -func (p *prog) preRun() { - if !service.Interactive() { - p.setDNS() - } -} - func setDependencies(svc *service.Config) { svc.Dependencies = []string{ "Wants=network-online.target", diff --git a/cmd/ctrld/prog_others.go b/cmd/ctrld/prog_others.go index 50fcf0d..7d70825 100644 --- a/cmd/ctrld/prog_others.go +++ b/cmd/ctrld/prog_others.go @@ -4,8 +4,6 @@ package main import "github.com/kardianos/service" -func (p *prog) preRun() {} - func setDependencies(svc *service.Config) {} func setWorkingDirectory(svc *service.Config, dir string) { diff --git a/cmd/ctrld/service.go b/cmd/ctrld/service.go index 28a70c6..263dfd8 100644 --- a/cmd/ctrld/service.go +++ b/cmd/ctrld/service.go @@ -26,6 +26,8 @@ func newService(i service.Interface, c *service.Config) (service.Service, error) return &sysV{s}, nil case s.Platform() == "unix-systemv": return &sysV{s}, nil + case s.Platform() == "linux-systemd": + return &systemd{s}, nil } return s, nil } @@ -105,6 +107,20 @@ func (s *procd) Status() (service.Status, error) { return service.StatusRunning, nil } +// procd wraps a service.Service, and provide status command to +// report the status correctly. +type systemd struct { + service.Service +} + +func (s *systemd) Status() (service.Status, error) { + out, _ := exec.Command("systemctl", "status", "ctrld").CombinedOutput() + if bytes.Contains(out, []byte("/FAILURE)")) { + return service.StatusStopped, nil + } + return s.Service.Status() +} + type task struct { f func() error abortOnError bool diff --git a/internal/clientinfo/dhcp.go b/internal/clientinfo/dhcp.go index a91bdb9..27e2bf4 100644 --- a/internal/clientinfo/dhcp.go +++ b/internal/clientinfo/dhcp.go @@ -53,7 +53,7 @@ func (d *dhcp) watchChanges() { if !ok { return } - if event.Has(fsnotify.Write) { + if event.Has(fsnotify.Write) || event.Has(fsnotify.Rename) || event.Has(fsnotify.Chmod) || event.Has(fsnotify.Remove) { format := clientInfoFiles[event.Name] if err := d.readLeaseFile(event.Name, format); err != nil && !os.IsNotExist(err) { ctrld.ProxyLogger.Load().Err(err).Str("file", event.Name).Msg("leases file changed but failed to update client info") diff --git a/internal/net/net.go b/internal/net/net.go index 9b47f2d..5f2c509 100644 --- a/internal/net/net.go +++ b/internal/net/net.go @@ -4,8 +4,11 @@ import ( "context" "errors" "net" + "os" + "os/signal" "sync" "sync/atomic" + "syscall" "time" "tailscale.com/logtail/backoff" @@ -13,17 +16,17 @@ import ( const ( controldIPv6Test = "ipv6.controld.io" - bootstrapDNS = "76.76.2.0:53" + v4BootstrapDNS = "76.76.2.0:53" + v6BootstrapDNS = "[2606:1a40::]:53" ) var Dialer = &net.Dialer{ Resolver: &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - d := net.Dialer{ - Timeout: 10 * time.Second, - } - return d.DialContext(ctx, "udp", bootstrapDNS) + d := ParallelDialer{} + d.Timeout = 10 * time.Second + return d.DialContext(ctx, "udp", []string{v4BootstrapDNS, v6BootstrapDNS}) }, }, } @@ -59,14 +62,32 @@ func supportListenIPv6Local() bool { } func probeStack() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go func() { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + <-sigs + cancel() + }() + b := backoff.NewBackoff("probeStack", func(format string, args ...any) {}, 5*time.Second) for { - if _, err := probeStackDialer.Dial("udp", bootstrapDNS); err == nil { + if _, err := probeStackDialer.DialContext(ctx, "udp", v4BootstrapDNS); err == nil { hasNetworkUp = true break - } else { - b.BackOff(context.Background(), err) } + if _, err := probeStackDialer.DialContext(ctx, "udp", v6BootstrapDNS); err == nil { + hasNetworkUp = true + break + } + select { + case <-ctx.Done(): + return + default: + } + b.BackOff(context.Background(), errors.New("network is down")) } canListenIPv6Local = supportListenIPv6Local() }