mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-08-10 13:20:33 +02:00
all: add pull API config based on special DNS query
For query domain that matches "uid.verify.controld.com" in cd mode, and the uid has the same value with "--cd" flag, ctrld will fetch uid config from ControlD API, using this config if valid. This is useful for force syncing API without waiting until the API reload ticker fire.
This commit is contained in:
@@ -1141,6 +1141,7 @@ func run(appCallback *AppCallback, stopCh chan struct{}) {
|
|||||||
reloadDoneCh: make(chan struct{}),
|
reloadDoneCh: make(chan struct{}),
|
||||||
dnsWatcherStopCh: make(chan struct{}),
|
dnsWatcherStopCh: make(chan struct{}),
|
||||||
apiReloadCh: make(chan *ctrld.Config),
|
apiReloadCh: make(chan *ctrld.Config),
|
||||||
|
apiForceReloadCh: make(chan struct{}),
|
||||||
cfg: &cfg,
|
cfg: &cfg,
|
||||||
appCallback: appCallback,
|
appCallback: appCallback,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ func (p *prog) serveDNS(listenerNum string) error {
|
|||||||
ufr: ur,
|
ufr: ur,
|
||||||
})
|
})
|
||||||
go p.doSelfUninstall(pr.answer)
|
go p.doSelfUninstall(pr.answer)
|
||||||
|
|
||||||
answer = pr.answer
|
answer = pr.answer
|
||||||
rtt := time.Since(t)
|
rtt := time.Since(t)
|
||||||
ctrld.Log(ctx, mainLog.Load().Debug(), "received response of %d bytes in %s", answer.Len(), rtt)
|
ctrld.Log(ctx, mainLog.Load().Debug(), "received response of %d bytes in %s", answer.Len(), rtt)
|
||||||
@@ -168,6 +169,7 @@ func (p *prog) serveDNS(listenerNum string) error {
|
|||||||
go func() {
|
go func() {
|
||||||
p.WithLabelValuesInc(statsQueriesCount, labelValues...)
|
p.WithLabelValuesInc(statsQueriesCount, labelValues...)
|
||||||
p.WithLabelValuesInc(statsClientQueriesCount, []string{ci.IP, ci.Mac, ci.Hostname}...)
|
p.WithLabelValuesInc(statsClientQueriesCount, []string{ci.IP, ci.Mac, ci.Hostname}...)
|
||||||
|
p.forceFetchingAPI(domain)
|
||||||
}()
|
}()
|
||||||
if err := w.WriteMsg(answer); err != nil {
|
if err := w.WriteMsg(answer); err != nil {
|
||||||
ctrld.Log(ctx, mainLog.Load().Error().Err(err), "serveDNS: failed to send DNS response to client")
|
ctrld.Log(ctx, mainLog.Load().Error().Err(err), "serveDNS: failed to send DNS response to client")
|
||||||
@@ -926,6 +928,41 @@ func (p *prog) performCaptivePortalDetection() {
|
|||||||
mainLog.Load().Warn().Msg("captive portal login finished, stop leaking query")
|
mainLog.Load().Warn().Msg("captive portal login finished, stop leaking query")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// forceFetchingAPI sends signal to force syncing API config if run in cd mode,
|
||||||
|
// and the domain == "cdUID.verify.controld.com"
|
||||||
|
func (p *prog) forceFetchingAPI(domain string) {
|
||||||
|
if cdUID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolverID, parent, _ := strings.Cut(domain, ".")
|
||||||
|
if resolverID != cdUID {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case cdDev && parent == "verify.controld.dev":
|
||||||
|
// match ControlD dev
|
||||||
|
case parent == "verify.controld.com":
|
||||||
|
// match ControlD
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = p.apiForceReloadGroup.DoChan("force_sync_api", func() (interface{}, error) {
|
||||||
|
p.apiForceReloadCh <- struct{}{}
|
||||||
|
// Wait here to prevent abusing API if we are flooded.
|
||||||
|
time.Sleep(timeDurationOrDefault(p.cfg.Service.ForceRefetchWaitTime, 30) * time.Second)
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeDurationOrDefault returns time duration value from n if not nil.
|
||||||
|
// Otherwise, it returns time duration value defaultN.
|
||||||
|
func timeDurationOrDefault(n *int, defaultN int) time.Duration {
|
||||||
|
if n != nil && *n > 0 {
|
||||||
|
return time.Duration(*n)
|
||||||
|
}
|
||||||
|
return time.Duration(defaultN)
|
||||||
|
}
|
||||||
|
|
||||||
// queryFromSelf reports whether the input IP is from device running ctrld.
|
// queryFromSelf reports whether the input IP is from device running ctrld.
|
||||||
func queryFromSelf(ip string) bool {
|
func queryFromSelf(ip string) bool {
|
||||||
netIP := netip.MustParseAddr(ip)
|
netIP := netip.MustParseAddr(ip)
|
||||||
|
|||||||
+51
-48
@@ -21,11 +21,11 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tailscale.com/net/netmon"
|
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"golang.org/x/sync/singleflight"
|
||||||
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
|
|
||||||
"github.com/Control-D-Inc/ctrld"
|
"github.com/Control-D-Inc/ctrld"
|
||||||
@@ -69,19 +69,21 @@ var svcConfig = &service.Config{
|
|||||||
var useSystemdResolved = false
|
var useSystemdResolved = false
|
||||||
|
|
||||||
type prog struct {
|
type prog struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
waitCh chan struct{}
|
waitCh chan struct{}
|
||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
reloadCh chan struct{} // For Windows.
|
reloadCh chan struct{} // For Windows.
|
||||||
reloadDoneCh chan struct{}
|
reloadDoneCh chan struct{}
|
||||||
apiReloadCh chan *ctrld.Config
|
apiReloadCh chan *ctrld.Config
|
||||||
logConn net.Conn
|
apiForceReloadCh chan struct{}
|
||||||
cs *controlServer
|
apiForceReloadGroup singleflight.Group
|
||||||
csSetDnsDone chan struct{}
|
logConn net.Conn
|
||||||
csSetDnsOk bool
|
cs *controlServer
|
||||||
dnsWatchDogOnce sync.Once
|
csSetDnsDone chan struct{}
|
||||||
dnsWg sync.WaitGroup
|
csSetDnsOk bool
|
||||||
dnsWatcherStopCh chan struct{}
|
dnsWatchDogOnce sync.Once
|
||||||
|
dnsWg sync.WaitGroup
|
||||||
|
dnsWatcherStopCh chan struct{}
|
||||||
|
|
||||||
cfg *ctrld.Config
|
cfg *ctrld.Config
|
||||||
localUpstreams []string
|
localUpstreams []string
|
||||||
@@ -255,47 +257,48 @@ func (p *prog) apiConfigReload() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
secs := 3600
|
ticker := time.NewTicker(timeDurationOrDefault(p.cfg.Service.RefetchTime, 3600) * time.Second)
|
||||||
if p.cfg.Service.RefetchTime != nil && *p.cfg.Service.RefetchTime > 0 {
|
|
||||||
secs = *p.cfg.Service.RefetchTime
|
|
||||||
}
|
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Duration(secs) * time.Second)
|
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
logger := mainLog.Load().With().Str("mode", "api-reload").Logger()
|
logger := mainLog.Load().With().Str("mode", "api-reload").Logger()
|
||||||
logger.Debug().Msg("starting custom config reload timer")
|
logger.Debug().Msg("starting custom config reload timer")
|
||||||
lastUpdated := time.Now().Unix()
|
lastUpdated := time.Now().Unix()
|
||||||
|
|
||||||
|
doReloadApiConfig := func(forced bool, logger zerolog.Logger) {
|
||||||
|
resolverConfig, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev)
|
||||||
|
selfUninstallCheck(err, p, logger)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn().Err(err).Msg("could not fetch resolver config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resolverConfig.Ctrld.CustomConfig == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resolverConfig.Ctrld.CustomLastUpdate > lastUpdated || forced {
|
||||||
|
lastUpdated = time.Now().Unix()
|
||||||
|
cfg := &ctrld.Config{}
|
||||||
|
if err := validateCdRemoteConfig(resolverConfig, cfg); err != nil {
|
||||||
|
logger.Warn().Err(err).Msg("skipping invalid custom config")
|
||||||
|
if _, err := controld.UpdateCustomLastFailed(cdUID, rootCmd.Version, cdDev, true); err != nil {
|
||||||
|
logger.Error().Err(err).Msg("could not mark custom last update failed")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setListenerDefaultValue(cfg)
|
||||||
|
logger.Debug().Msg("custom config changes detected, reloading...")
|
||||||
|
p.apiReloadCh <- cfg
|
||||||
|
} else {
|
||||||
|
logger.Debug().Msg("custom config does not change")
|
||||||
|
}
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
case <-p.apiForceReloadCh:
|
||||||
|
doReloadApiConfig(true, logger.With().Bool("forced", true).Logger())
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
resolverConfig, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev)
|
doReloadApiConfig(false, logger)
|
||||||
selfUninstallCheck(err, p, logger)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn().Err(err).Msg("could not fetch resolver config")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if resolverConfig.Ctrld.CustomConfig == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if resolverConfig.Ctrld.CustomLastUpdate > lastUpdated {
|
|
||||||
lastUpdated = time.Now().Unix()
|
|
||||||
cfg := &ctrld.Config{}
|
|
||||||
if err := validateCdRemoteConfig(resolverConfig, cfg); err != nil {
|
|
||||||
logger.Warn().Err(err).Msg("skipping invalid custom config")
|
|
||||||
if _, err := controld.UpdateCustomLastFailed(cdUID, rootCmd.Version, cdDev, true); err != nil {
|
|
||||||
logger.Error().Err(err).Msg("could not mark custom last update failed")
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
setListenerDefaultValue(cfg)
|
|
||||||
logger.Debug().Msg("custom config changes detected, reloading...")
|
|
||||||
p.apiReloadCh <- cfg
|
|
||||||
} else {
|
|
||||||
logger.Debug().Msg("custom config does not change")
|
|
||||||
}
|
|
||||||
case <-p.stopCh:
|
case <-p.stopCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ type ServiceConfig struct {
|
|||||||
DnsWatchdogEnabled *bool `mapstructure:"dns_watchdog_enabled" toml:"dns_watchdog_enabled,omitempty"`
|
DnsWatchdogEnabled *bool `mapstructure:"dns_watchdog_enabled" toml:"dns_watchdog_enabled,omitempty"`
|
||||||
DnsWatchdogInvterval *time.Duration `mapstructure:"dns_watchdog_interval" toml:"dns_watchdog_interval,omitempty"`
|
DnsWatchdogInvterval *time.Duration `mapstructure:"dns_watchdog_interval" toml:"dns_watchdog_interval,omitempty"`
|
||||||
RefetchTime *int `mapstructure:"refetch_time" toml:"refetch_time,omitempty"`
|
RefetchTime *int `mapstructure:"refetch_time" toml:"refetch_time,omitempty"`
|
||||||
|
ForceRefetchWaitTime *int `mapstructure:"force_refetch_wait_time" toml:"force_refetch_wait_time,omitempty"`
|
||||||
Daemon bool `mapstructure:"-" toml:"-"`
|
Daemon bool `mapstructure:"-" toml:"-"`
|
||||||
AllocateIP bool `mapstructure:"-" toml:"-"`
|
AllocateIP bool `mapstructure:"-" toml:"-"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user