mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-09-04 13:36:35 +02:00
dns intercept: port DNS-less network recovery to master
Port !997 from v1.0 onto the context-aware master recovery lifecycle. Preserve master logging and resolver APIs while adding macOS default-route DHCP detection, temporary DNS-target cleanup, and atomic recovery ownership. Includes parser, lifecycle, failure, and concurrency regressions plus the corrected macOS QA helper. Relates to #533 and #597.
This commit is contained in:
+26
-7
@@ -145,6 +145,7 @@ type prog struct {
|
||||
recoveryCancelMu sync.Mutex
|
||||
recoveryCancel context.CancelFunc
|
||||
recoveryRunning atomic.Bool
|
||||
recoveryGen atomic.Uint64
|
||||
|
||||
// recoveryDebounceTimer coalesces rapid NetworkChange recovery triggers
|
||||
// into a single handleRecovery call. Only handleRecovery is debounced —
|
||||
@@ -157,6 +158,14 @@ type prog struct {
|
||||
// authentication without tearing down WFP/pf filters.
|
||||
recoveryBypass atomic.Bool
|
||||
|
||||
// interceptDNSTargetService names the macOS network service on which
|
||||
// ctrld set a temporary DNS target; interceptDNSTargetSetValue records the
|
||||
// exact value. Both are guarded by interceptDNSTargetMu.
|
||||
interceptDNSTargetMu sync.Mutex //lint:ignore U1000 used on darwin
|
||||
interceptDNSTargetService string //lint:ignore U1000 used on darwin
|
||||
interceptDNSTargetSetValue string //lint:ignore U1000 used on darwin
|
||||
interceptDNSTargetLoaded bool //lint:ignore U1000 used on darwin
|
||||
|
||||
// DNS intercept mode state (platform-specific).
|
||||
// On Windows: *wfpState, on macOS: *pfState, nil on other platforms.
|
||||
dnsInterceptState any
|
||||
@@ -481,9 +490,10 @@ func (p *prog) postRun() {
|
||||
if !p.skipInitialDNSReset() {
|
||||
p.resetDNS(false, false)
|
||||
}
|
||||
ns := ctrld.InitializeOsResolver(ctrld.LoggerCtx(context.Background(), p.logger.Load()), false)
|
||||
loggerCtx := ctrld.LoggerCtx(context.Background(), p.logger.Load())
|
||||
ns, systemNameservers := initializeOsResolverWithSystemNameserversFn(loggerCtx, false)
|
||||
p.Debug().Msgf("Initialized os resolver with nameservers: %v", ns)
|
||||
p.setDNS()
|
||||
p.setDNS(systemNameservers)
|
||||
if p.allowList != nil {
|
||||
p.initPlatformFirewall()
|
||||
}
|
||||
@@ -958,10 +968,13 @@ func (p *prog) deAllocateIP() error {
|
||||
// NRPT rule, so a test of what happens *after* it fails must not be the thing that
|
||||
// runs it.
|
||||
var (
|
||||
startDNSInterceptFn = (*prog).startDNSIntercept
|
||||
setDnsForRunningIfaceFn = (*prog).setDnsForRunningIface
|
||||
resetDNSFn = (*prog).resetDNS
|
||||
refuseFallbackFatal = func(format string, v ...any) {
|
||||
startDNSInterceptFn = (*prog).startDNSIntercept
|
||||
ensureInterceptDNSTargetFn = (*prog).ensureInterceptDNSTarget
|
||||
removeInterceptDNSTargetFn = (*prog).removeInterceptDNSTarget
|
||||
initializeOsResolverWithSystemNameserversFn = ctrld.InitializeOsResolverWithSystemNameservers
|
||||
setDnsForRunningIfaceFn = (*prog).setDnsForRunningIface
|
||||
resetDNSFn = (*prog).resetDNS
|
||||
refuseFallbackFatal = func(format string, v ...any) {
|
||||
mainLog.Load().Fatal().Msgf(format, v...)
|
||||
}
|
||||
)
|
||||
@@ -983,7 +996,7 @@ func interfaceDNSFallbackViable(lc *ctrld.ListenerConfig) bool {
|
||||
return lc == nil || lc.Port == 0 || lc.Port == 53
|
||||
}
|
||||
|
||||
func (p *prog) setDNS() {
|
||||
func (p *prog) setDNS(systemNameservers []string) {
|
||||
setDnsOK := false
|
||||
defer func() {
|
||||
p.csSetDnsOk = setDnsOK
|
||||
@@ -1017,6 +1030,7 @@ func (p *prog) setDNS() {
|
||||
// software that also manages DNS. See issue #489.
|
||||
if dnsIntercept {
|
||||
if err := startDNSInterceptFn(p); err != nil {
|
||||
removeInterceptDNSTargetFn(p, "DNS intercept unavailable")
|
||||
// An external GP catch-all still owns the namespace even when its probe
|
||||
// fails. In either external-owner state, rewriting adapter DNS would violate
|
||||
// the policy that startup deliberately preserved. Only the verified case has
|
||||
@@ -1056,6 +1070,7 @@ func (p *prog) setDNS() {
|
||||
p.Error().Err(err).Msg("DNS intercept mode failed — falling back to interface DNS settings")
|
||||
// Fall through to traditional setDNS behavior.
|
||||
} else {
|
||||
ensureInterceptDNSTargetFn(p, systemNameservers)
|
||||
if hardIntercept {
|
||||
p.Info().Msg("Hard intercept mode active — all DNS through ctrld, no VPN split routing")
|
||||
} else {
|
||||
@@ -1073,6 +1088,9 @@ func (p *prog) setDNS() {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !dnsIntercept {
|
||||
removeInterceptDNSTargetFn(p, "intercept mode inactive")
|
||||
}
|
||||
|
||||
if cfg.Listener == nil {
|
||||
return
|
||||
@@ -1305,6 +1323,7 @@ func (p *prog) dnsWatchdog(iface *net.Interface, nameservers []string) {
|
||||
// resetDNS performs a DNS reset for all interfaces.
|
||||
// In DNS intercept mode, this tears down the WFP/pf filters instead.
|
||||
func (p *prog) resetDNS(isStart bool, restoreStatic bool) {
|
||||
removeInterceptDNSTargetFn(p, "DNS reset")
|
||||
if dnsIntercept && p.dnsInterceptState != nil {
|
||||
if err := p.stopDNSIntercept(); err != nil {
|
||||
p.Error().Err(err).Msg("Failed to stop DNS intercept mode during reset")
|
||||
|
||||
Reference in New Issue
Block a user