mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-07-16 13:17:19 +02:00
dns: fix recovery race condition during rapid network transitions
When multiple network changes fire in quick succession (e.g., VPN disconnect + interface swap), the second handleRecovery() call cancels the first but inherits stale DoH transports, causing DNS blackouts of up to 30 seconds. Three changes to reduce worst-case recovery from ~30s to <3s: 1. ForceReBootstrap() on recovery entry — closes dead connections and creates fresh transports synchronously before probing, replacing the lazy ReBootstrap() flag that left stale connections for probes to hit. 2. Debounce handleRecovery() for network changes (500ms window) — only the recovery flow is debounced; all other state updates (IP, pf anchor, VPN DNS, tunnel checks) still run immediately on every event. This eliminates the cancel-and-restart race without missing state. 3. Combined effect: ForceReBootstrap closes old in-flight connections (closeTransports) and builds new ones (SetupTransport) atomically, so recovery probes never inherit dead connections from a prior recovery attempt.
This commit is contained in:
committed by
Cuong Manh Le
parent
70b45710e7
commit
b3c670b17e
+8
-2
@@ -133,6 +133,12 @@ type prog struct {
|
||||
recoveryCancelMu sync.Mutex
|
||||
recoveryCancel context.CancelFunc
|
||||
recoveryRunning atomic.Bool
|
||||
|
||||
// recoveryDebounceTimer coalesces rapid NetworkChange recovery triggers
|
||||
// into a single handleRecovery call. Only handleRecovery is debounced —
|
||||
// all other state updates (IP, pf anchor, VPN DNS) run immediately.
|
||||
recoveryDebounceMu sync.Mutex
|
||||
recoveryDebounceTimer *time.Timer
|
||||
// recoveryBypass is set when dns-intercept mode enters recovery.
|
||||
// While true, proxy() forwards all queries to the OS/DHCP resolver
|
||||
// instead of the configured upstreams. This allows captive portal
|
||||
@@ -1017,10 +1023,10 @@ func (p *prog) resetDNS(isStart bool, restoreStatic bool) {
|
||||
if err := p.stopDNSIntercept(); err != nil {
|
||||
p.Error().Err(err).Msg("Failed to stop DNS intercept mode during reset")
|
||||
}
|
||||
|
||||
|
||||
// Clean up VPN DNS manager
|
||||
p.vpnDNS = nil
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
netIfaceName := ""
|
||||
|
||||
Reference in New Issue
Block a user