mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-10-24 14:08:27 +02:00
After a network change, the OS resolver keeps the previous network's nameservers until a read finds the new ones. In Standard mode that usually waits for the recovery to remove ctrld's DNS settings: while 127.0.0.1 is configured, the system reports nothing else, and on macOS an RDNSS-only IPv6 network has no other source. macOS resolves ipv4only.arpa through ctrld right after the change to discover the NAT64 prefix; a fast SERVFAIL from the stale set ends that discovery, macOS never starts its CLAT, and an IPv4-only upstream never recovers. On every accepted network change, on all platforms and in both Standard and intercept mode, mark the OS resolver stale and read the nameservers once before the re-bootstrap, without the scutil retry loop. While the set is stale, a failed ipv4only.arpa AAAA lookup gets no reply, so the host retries it. A read that finds nameservers clears the mark, and the mark expires after 30s. Network-change recovery also re-reads the nameservers right after the reset and then at 1s, 2s, 5s and every 30s while it waits, for networks that publish their resolvers late.
55 lines
1.9 KiB
Go
55 lines
1.9 KiB
Go
package cli
|
|
|
|
import "github.com/Control-D-Inc/ctrld"
|
|
|
|
// initializeOsResolver re-reads the system nameservers into the OS resolver.
|
|
// Tests replace it, so they never read the host's resolver configuration.
|
|
var initializeOsResolver = ctrld.InitializeOsResolverWithReason
|
|
|
|
// initializeOsResolverQuick is initializeOsResolver without waiting for the
|
|
// system to publish its nameservers.
|
|
var initializeOsResolverQuick = ctrld.InitializeOsResolverQuickWithReason
|
|
|
|
// Reasons that the journal reports when the resolver set changes. Each one
|
|
// names the code path that read the resolvers of the host.
|
|
const (
|
|
osResolverReasonStart = "start"
|
|
osResolverReasonWakeProbe = "wake_probe"
|
|
osResolverReasonVPNSettle = "vpn_settle"
|
|
osResolverReasonDelayedRecheck = "delayed_recheck"
|
|
osResolverReasonNetworkChange = "network_change"
|
|
)
|
|
|
|
func (p *prog) refreshDNSAfterVPNSettle(reason string) (routes, domainlessServers, exemptions int) {
|
|
mainLog.Load().Info().Msgf("DNS intercept: refreshing OS/VPN DNS route state after VPN settle (%s)", reason)
|
|
ns := initializeOsResolver(true, osResolverReasonVPNSettle)
|
|
mainLog.Load().Debug().Msgf("DNS intercept: post-settle OS resolver nameservers: %v", ns)
|
|
|
|
if p.vpnDNS == nil {
|
|
mainLog.Load().Debug().Msg("DNS intercept: post-settle VPN DNS route refresh skipped — manager unavailable")
|
|
return 0, 0, 0
|
|
}
|
|
|
|
routes, domainlessServers, exemptions = p.vpnDNS.RefreshRoutesOnly()
|
|
mainLog.Load().Info().Msgf("DNS intercept: post-settle VPN DNS route refresh completed — %d routes, %d domainless servers, %d exemptions",
|
|
routes, domainlessServers, exemptions)
|
|
return routes, domainlessServers, exemptions
|
|
}
|
|
|
|
func vpnDNSExemptionsEqual(a, b []vpnDNSExemption) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
seen := make(map[vpnDNSExemption]int, len(a))
|
|
for _, ex := range a {
|
|
seen[ex]++
|
|
}
|
|
for _, ex := range b {
|
|
if seen[ex] == 0 {
|
|
return false
|
|
}
|
|
seen[ex]--
|
|
}
|
|
return true
|
|
}
|