cmd/cli: handle stop signal from service manager

So using "ctrld stop" or service manager to stop ctrld will end up with
the same result, stopped ctrld with a working DNS, and deactivation pin
code will always have effects if set.
This commit is contained in:
Cuong Manh Le
2025-03-20 22:26:35 +07:00
committed by Cuong Manh Le
parent dacc67e50f
commit c6365e6b74
7 changed files with 60 additions and 42 deletions

View File

@@ -199,6 +199,7 @@ func run(appCallback *AppCallback, stopCh chan struct{}) {
p := &prog{
waitCh: waitCh,
stopCh: stopCh,
pinCodeValidCh: make(chan struct{}, 1),
reloadCh: make(chan struct{}),
reloadDoneCh: make(chan struct{}),
dnsWatcherStopCh: make(chan struct{}),
@@ -421,19 +422,28 @@ func run(appCallback *AppCallback, stopCh chan struct{}) {
if err := p.router.Cleanup(); err != nil {
mainLog.Load().Error().Err(err).Msg("could not cleanup router")
}
// restore static DNS settings or DHCP
p.resetDNS(false, true)
})
}
}
p.onStopped = append(p.onStopped, func() {
// restore static DNS settings or DHCP
p.resetDNS(false, true)
// Iterate over all physical interfaces and restore static DNS if a saved static config exists.
withEachPhysicalInterfaces("", "restore static DNS", func(i *net.Interface) error {
file := savedStaticDnsSettingsFilePath(i)
if _, err := os.Stat(file); err == nil {
if err := restoreDNS(i); err != nil {
mainLog.Load().Error().Err(err).Msgf("Could not restore static DNS on interface %s", i.Name)
} else {
mainLog.Load().Debug().Msgf("Restored static DNS on interface %s successfully", i.Name)
}
}
return nil
})
})
close(waitCh)
<-stopCh
p.stopDnsWatchers()
for _, f := range p.onStopped {
f()
}
}
func writeConfigFile(cfg *ctrld.Config) error {
@@ -609,9 +619,9 @@ func init() {
cdDeactivationPin.Store(defaultDeactivationPin)
}
// deactivationPinNotSet reports whether cdDeactivationPin was not set by processCDFlags.
func deactivationPinNotSet() bool {
return cdDeactivationPin.Load() == defaultDeactivationPin
// deactivationPinSet indicates if cdDeactivationPin is non-default..
func deactivationPinSet() bool {
return cdDeactivationPin.Load() != defaultDeactivationPin
}
func processCDFlags(cfg *ctrld.Config) (*controld.ResolverConfig, error) {

View File

@@ -629,23 +629,6 @@ func initStopCmd() *cobra.Command {
os.Exit(deactivationPinInvalidExitCode)
}
if doTasks([]task{{s.Stop, true, "Stop"}}) {
p.router.Cleanup()
// restore static DNS settings or DHCP
p.resetDNS(false, true)
// Iterate over all physical interfaces and restore static DNS if a saved static config exists.
withEachPhysicalInterfaces("", "restore static DNS", func(i *net.Interface) error {
file := savedStaticDnsSettingsFilePath(i)
if _, err := os.Stat(file); err == nil {
if err := restoreDNS(i); err != nil {
mainLog.Load().Error().Err(err).Msgf("Could not restore static DNS on interface %s", i.Name)
} else {
mainLog.Load().Debug().Msgf("Restored static DNS on interface %s successfully", i.Name)
}
}
return nil
})
if router.WaitProcessExited() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()

View File

@@ -228,7 +228,7 @@ func (p *prog) registerControlServerHandler() {
}
// If pin code not set, allowing deactivation.
if deactivationPinNotSet() {
if !deactivationPinSet() {
w.WriteHeader(http.StatusOK)
return
}
@@ -244,6 +244,10 @@ func (p *prog) registerControlServerHandler() {
switch req.Pin {
case cdDeactivationPin.Load():
code = http.StatusOK
select {
case p.pinCodeValidCh <- struct{}{}:
default:
}
case defaultDeactivationPin:
// If the pin code was set, but users do not provide --pin, return proper code to client.
code = http.StatusBadRequest

View File

@@ -87,6 +87,7 @@ type prog struct {
mu sync.Mutex
waitCh chan struct{}
stopCh chan struct{}
pinCodeValidCh chan struct{}
reloadCh chan struct{} // For Windows.
reloadDoneCh chan struct{}
apiReloadCh chan *ctrld.Config
@@ -268,13 +269,6 @@ func (p *prog) preRun() {
p.requiredMultiNICsConfig = requiredMultiNICsConfig()
}
p.runningIface = iface
if runtime.GOOS == "darwin" {
p.onStopped = append(p.onStopped, func() {
if !service.Interactive() {
p.resetDNS(false, true)
}
})
}
}
func (p *prog) postRun() {
@@ -622,14 +616,41 @@ func (p *prog) metricsEnabled() bool {
func (p *prog) Stop(s service.Service) error {
p.stopDnsWatchers()
mainLog.Load().Debug().Msg("dns watchers stopped")
for _, f := range p.onStopped {
f()
}
mainLog.Load().Debug().Msg("finish running onStopped functions")
defer func() {
mainLog.Load().Info().Msg("Service stopped")
}()
close(p.stopCh)
if err := p.deAllocateIP(); err != nil {
mainLog.Load().Error().Err(err).Msg("de-allocate ip failed")
return err
}
if deactivationPinSet() {
select {
case <-p.pinCodeValidCh:
// Allow stopping the service, pinCodeValidCh is only filled
// after control server did validate the pin code.
case <-time.After(time.Millisecond * 100):
// No valid pin code was checked, that mean we are stopping
// because of OS signal sent directly from someone else.
// In this case, restarting ctrld service by ourselves.
mainLog.Load().Debug().Msgf("receiving stopping signal without valid pin code")
mainLog.Load().Debug().Msgf("self restarting ctrld service")
if exe, err := os.Executable(); err == nil {
cmd := exec.Command(exe, "restart")
cmd.SysProcAttr = sysProcAttrForDetachedChildProcess()
if err := cmd.Start(); err != nil {
mainLog.Load().Error().Err(err).Msg("failed to run self restart command")
}
} else {
mainLog.Load().Error().Err(err).Msg("failed to self restart ctrld service")
}
os.Exit(deactivationPinInvalidExitCode)
}
}
close(p.stopCh)
return nil
}
@@ -1471,7 +1492,7 @@ func selfUpgradeCheck(vt string, cv *semver.Version, logger *zerolog.Logger) {
return
}
cmd := exec.Command(exe, "upgrade", "prod", "-vv")
cmd.SysProcAttr = sysProcAttrForSelfUpgrade()
cmd.SysProcAttr = sysProcAttrForDetachedChildProcess()
if err := cmd.Start(); err != nil {
mainLog.Load().Error().Err(err).Msg("failed to start self-upgrade")
return

View File

@@ -22,7 +22,7 @@ func selfUninstall(p *prog, logger zerolog.Logger) {
logger.Fatal().Err(err).Msg("could not determine executable")
}
args := []string{"uninstall"}
if !deactivationPinNotSet() {
if deactivationPinSet() {
args = append(args, fmt.Sprintf("--pin=%d", cdDeactivationPin.Load()))
}
cmd := exec.Command(bin, args...)

View File

@@ -6,7 +6,7 @@ import (
"syscall"
)
// sysProcAttrForSelfUpgrade returns *syscall.SysProcAttr instance for running self-upgrade command.
func sysProcAttrForSelfUpgrade() *syscall.SysProcAttr {
// sysProcAttrForDetachedChildProcess returns *syscall.SysProcAttr instance for running a detached child command.
func sysProcAttrForDetachedChildProcess() *syscall.SysProcAttr {
return &syscall.SysProcAttr{Setsid: true}
}

View File

@@ -9,8 +9,8 @@ import (
// SYSCALL_CREATE_NO_WINDOW set flag to run process without a console window.
const SYSCALL_CREATE_NO_WINDOW = 0x08000000
// sysProcAttrForSelfUpgrade returns *syscall.SysProcAttr instance for running self-upgrade command.
func sysProcAttrForSelfUpgrade() *syscall.SysProcAttr {
// sysProcAttrForDetachedChildProcess returns *syscall.SysProcAttr instance for running self-upgrade command.
func sysProcAttrForDetachedChildProcess() *syscall.SysProcAttr {
return &syscall.SysProcAttr{
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP | SYSCALL_CREATE_NO_WINDOW,
HideWindow: true,