From 8d58df58e6c3765a5f0038c6b0c7000ac46ca4a0 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 28 Sep 2026 12:21:56 -0400 Subject: [PATCH] Also exclude the dns servers from the tunnel --- olm/connect.go | 1 + olm/dns_config.go | 1 + olm/gateway.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++ olm/olm.go | 20 ++++++++++++++++ 4 files changed, 82 insertions(+) diff --git a/olm/connect.go b/olm/connect.go index 5218b09..879d79e 100644 --- a/olm/connect.go +++ b/olm/connect.go @@ -266,6 +266,7 @@ func (o *Olm) handleConnect(msg websocket.WSMessage) { // "olm/wg/connect" message - so push in whatever hole-punch bypass // endpoints it already recorded now that there's somewhere to put them. o.flushPendingHolepunchBypassEndpoints() + o.flushPendingDNSBypassEndpoints() if o.dnsProxy != nil { if err := o.dnsProxy.Start(); err != nil { // start DNS proxy first so there is no downtime diff --git a/olm/dns_config.go b/olm/dns_config.go index de29246..d6d63f2 100644 --- a/olm/dns_config.go +++ b/olm/dns_config.go @@ -26,6 +26,7 @@ func (o *Olm) applyDNSConfigUpdate(cfg DNSConfigUpdate) { if o.dnsProxy != nil { o.dnsProxy.SetUpstreamDNS(cfg.UpstreamDNS) } + o.updateDNSBypassEndpoints(cfg.UpstreamDNS) } if len(cfg.MatchDomains) > 0 { diff --git a/olm/gateway.go b/olm/gateway.go index 56b18a8..962e3c0 100644 --- a/olm/gateway.go +++ b/olm/gateway.go @@ -214,6 +214,66 @@ func (o *Olm) flushPendingHolepunchBypassEndpoints() { } } +// updateDNSBypassEndpoints diffs servers (the DNS proxy's upstream/primary +// and secondary DNS servers - see TunnelConfig.UpstreamDNS and +// dns.DNSProxy.SetUpstreamDNS) against the currently-registered set and +// adds/removes gateway bypass routes for the difference, via the same +// AddGatewayBypassEndpoint/RemoveGatewayBypassEndpoint machinery used for +// hole-punch endpoints above. This keeps the DNS proxy's own outbound queries +// to its real upstream resolvers off the gateway default-route-equivalent, so +// they reach the real servers directly instead of looping back through the +// tunnel. Called from StartTunnel (initial value and dynamic system-DNS +// updates, including SetSystemDNS pushes) and applyDNSConfigUpdate (live +// server-pushed overrides). Safe to call before the peer manager exists (see +// flushPendingDNSBypassEndpoints) and safe to call repeatedly with the same +// servers (no-op). +func (o *Olm) updateDNSBypassEndpoints(servers []string) { + pm := o.getPeerManager() + + newBypassEndpoints := make(map[string]bool, len(servers)) + for _, server := range servers { + newBypassEndpoints[server] = true + } + + o.dnsBypassMu.Lock() + defer o.dnsBypassMu.Unlock() + if pm != nil { + for server := range newBypassEndpoints { + if !o.dnsBypassEndpoints[server] { + pm.AddGatewayBypassEndpoint(server) + } + } + for server := range o.dnsBypassEndpoints { + if !newBypassEndpoints[server] { + pm.RemoveGatewayBypassEndpoint(server) + } + } + } + o.dnsBypassEndpoints = newBypassEndpoints +} + +// flushPendingDNSBypassEndpoints re-registers every currently-known upstream +// DNS bypass endpoint with the peer manager. Mirrors +// flushPendingHolepunchBypassEndpoints: updateDNSBypassEndpoints typically +// runs before the peer manager exists (the initial UpstreamDNS value is +// applied in StartTunnel, and a DNS config override may arrive at the very +// start of handleConnect - see olm/dns_config.go - both well before +// handleConnect constructs the peer manager further down), so whatever was +// recorded needs to be pushed in once it becomes available. +// AddGatewayBypassEndpoint is idempotent. +func (o *Olm) flushPendingDNSBypassEndpoints() { + pm := o.getPeerManager() + if pm == nil { + return + } + + o.dnsBypassMu.Lock() + defer o.dnsBypassMu.Unlock() + for server := range o.dnsBypassEndpoints { + pm.AddGatewayBypassEndpoint(server) + } +} + // extractControlEndpointHost returns the bare host (no scheme/port) of the // Pangolin server olm is registered against, for gateway bypass-route // purposes. Falls back to the raw endpoint string on parse failure - diff --git a/olm/olm.go b/olm/olm.go index f1d5158..78f69de 100644 --- a/olm/olm.go +++ b/olm/olm.go @@ -81,6 +81,18 @@ type Olm struct { hpBypassEndpoints map[string]bool hpBypassMu sync.Mutex + // dnsBypassEndpoints tracks the "host:port" upstream DNS servers (see + // TunnelConfig.UpstreamDNS / dns.DNSProxy's upstreamDNS - the DNS proxy's + // primary/secondary real resolvers) currently registered as gateway bypass + // targets, so the proxy's own outbound DNS queries aren't captured by the + // gateway default-route-equivalent and end up looping back through the + // tunnel. Diffed against every update - the initial value in StartTunnel, a + // live server-pushed DNS config override, or system DNS detection/ + // SetSystemDNS - so stale entries are unregistered and new ones protected + // while gateway mode is active. Mirrors hpBypassEndpoints. + dnsBypassEndpoints map[string]bool + dnsBypassMu sync.Mutex + // primaryTunnelIP is the site tunnel's own address (wgData.TunnelIP), set once // per connect in handleConnect. It's the interface's first/primary address - // on macOS/iOS NetworkExtension, an unbound outbound socket's source gets @@ -526,6 +538,7 @@ func (o *Olm) StartTunnel(config TunnelConfig) { if o.dnsProxy != nil { o.dnsProxy.SetUpstreamDNS(servers) } + o.updateDNSBypassEndpoints(servers) } else { logger.Debug("Not updating UpstreamDNS: statically configured to %v", config.UpstreamDNS) } @@ -549,6 +562,13 @@ func (o *Olm) StartTunnel(config TunnelConfig) { if len(o.tunnelConfig.UpstreamDNS) == 0 { o.tunnelConfig.UpstreamDNS = []string{"8.8.8.8:53"} } + // Register the startup upstream DNS servers as gateway bypass targets + // regardless of how they were determined (statically configured, defaulted + // above, or already applied from a dynamic detection callback a moment + // ago) - the peer manager doesn't exist yet at this point, so this only + // records intent; flushPendingDNSBypassEndpoints (called from + // handleConnect) pushes it in once the peer manager is created. + o.updateDNSBypassEndpoints(o.tunnelConfig.UpstreamDNS) // Reset terminated status when tunnel starts o.apiServer.SetTerminated(false)