diff --git a/olm/connect.go b/olm/connect.go index e828dc9..4a5d0dd 100644 --- a/olm/connect.go +++ b/olm/connect.go @@ -273,17 +273,24 @@ func (o *Olm) handleConnect(msg websocket.WSMessage) { }) if o.tunnelConfig.OverrideDNS { - // Set up DNS override to use our DNS proxy - if err := dnsOverride.SetupDNSOverride(o.tunnelConfig.InterfaceName, o.dnsProxy.GetProxyIP()); err != nil { - logger.Error("Failed to setup DNS override: %v", err) - return - } + // When the host platform already applies DNS natively (NEDNSSettings on + // macOS/iOS, scoped to the tunnel session and auto-cleaned by the OS no + // matter how the session ends), skip olm's own raw scutil-based override - + // there is nothing for it to add and, unlike NEDNSSettings, it has no way + // to guarantee cleanup if this process dies uncleanly. See NativeDNSManaged. + if !o.tunnelConfig.NativeDNSManaged { + // Set up DNS override to use our DNS proxy + if err := dnsOverride.SetupDNSOverride(o.tunnelConfig.InterfaceName, o.dnsProxy.GetProxyIP()); err != nil { + logger.Error("Failed to setup DNS override: %v", err) + return + } - // Start the external watchdog (if configured). The watchdog will - // reset DNS if this process dies before it can call - // RestoreDNSOverride. This is a no-op when no watchdog - // subcommand has been configured on the OlmConfig. - o.startDNSWatchdog(o.tunnelConfig.InterfaceName) + // Start the external watchdog (if configured). The watchdog will + // reset DNS if this process dies before it can call + // RestoreDNSOverride. This is a no-op when no watchdog + // subcommand has been configured on the OlmConfig. + o.startDNSWatchdog(o.tunnelConfig.InterfaceName) + } network.SetDNSServers([]string{o.dnsProxy.GetProxyIP().String()}) } diff --git a/olm/olm.go b/olm/olm.go index 8df0abe..041fb3a 100644 --- a/olm/olm.go +++ b/olm/olm.go @@ -816,16 +816,20 @@ func (o *Olm) Close() { o.websocket = nil } - // Restore original DNS configuration + // Restore original DNS configuration (skipped when the host platform + // manages DNS natively - see NativeDNSManaged - since olm never installed + // its own override in that case) // we do this first to avoid any DNS issues if something else gets stuck - if err := dnsOverride.RestoreDNSOverride(); err != nil { - logger.Error("Failed to restore DNS: %v", err) - } + if !o.tunnelConfig.NativeDNSManaged { + if err := dnsOverride.RestoreDNSOverride(); err != nil { + logger.Error("Failed to restore DNS: %v", err) + } - // Stop the watchdog *after* a successful DNS restore so that if we - // somehow crash mid-restore the watchdog still has a chance to clean - // up. The watchdog itself is a no-op if it was never spawned. - o.stopDNSWatchdog() + // Stop the watchdog *after* a successful DNS restore so that if we + // somehow crash mid-restore the watchdog still has a chance to clean + // up. The watchdog itself is a no-op if it was never spawned. + o.stopDNSWatchdog() + } if o.holePunchManager != nil { o.holePunchManager.Stop() diff --git a/olm/types.go b/olm/types.go index a51f9e9..afde6cb 100644 --- a/olm/types.go +++ b/olm/types.go @@ -138,6 +138,14 @@ type TunnelConfig struct { OverrideDNS bool TunnelDNS bool + // NativeDNSManaged indicates the DNS override is already applied natively by + // the host platform (e.g. NEDNSSettings on macOS/iOS), scoped to the tunnel + // session and auto-cleaned by the OS regardless of how the session ends. When + // true, olm skips installing its own raw scutil-based override (and the + // subprocess watchdog that guards it) since there is nothing for it to add + // and nothing that can leak. + NativeDNSManaged bool + InitialFingerprint map[string]any InitialPostures map[string]any