fix(windows): include domainless VPN DNS adapters in discovery

This commit is contained in:
Codescribe
2026-05-27 03:21:58 -04:00
committed by Cuong Manh Le
parent f281466118
commit 6c5489873b
+41 -12
View File
@@ -32,28 +32,45 @@ func DiscoverVPNDNS(ctx context.Context) []VPNDNSConfig {
var vpnConfigs []VPNDNSConfig var vpnConfigs []VPNDNSConfig
for _, aa := range aas { for _, aa := range aas {
// Skip adapters that are not up
if aa.OperStatus != winipcfg.IfOperStatusUp { if aa.OperStatus != winipcfg.IfOperStatusUp {
Log(ctx, logger.Debug(), "Skipping adapter %s - not up, status: %d", Log(ctx, logger.Debug(), "Skipping adapter %s - not up, status: %d",
aa.FriendlyName(), aa.OperStatus) aa.FriendlyName(), aa.OperStatus)
continue continue
} }
// Skip software loopback
if aa.IfType == winipcfg.IfTypeSoftwareLoopback { if aa.IfType == winipcfg.IfTypeSoftwareLoopback {
Log(ctx, logger.Debug(), "Skipping %s (software loopback)", aa.FriendlyName()) Log(ctx, logger.Debug(), "Skipping %s (software loopback)", aa.FriendlyName())
continue continue
} }
// INVERT the ValidInterfaces filter: we want non-physical/non-hardware adapters // INVERT the ValidInterfaces filter: we want non-physical/non-hardware adapters.
// that are UP and have DNS servers AND DNS suffixes
_, isValidPhysical := validInterfacesMap[aa.FriendlyName()] _, isValidPhysical := validInterfacesMap[aa.FriendlyName()]
if isValidPhysical { if isValidPhysical {
Log(ctx, logger.Debug(), "Skipping %s (physical/hardware adapter)", aa.FriendlyName()) Log(ctx, logger.Debug(), "Skipping %s (physical/hardware adapter)", aa.FriendlyName())
continue continue
} }
// Collect DNS servers // Skip adapters that have no routable unicast addresses. An adapter
// with only link-local (fe80::) or APIPA (169.254.x.x) addresses is
// not actually connected — its DNS servers are stale. This prevents
// picking up e.g. Tailscale's adapter when the app is installed but
// disconnected (OperStatus reports Up but only APIPA addresses exist).
hasRoutableAddr := false
for a := aa.FirstUnicastAddress; a != nil; a = a.Next {
ip := a.Address.IP()
if ip == nil {
continue
}
if !ip.IsLinkLocalUnicast() {
hasRoutableAddr = true
break
}
}
if !hasRoutableAddr {
Log(ctx, logger.Debug(), "Skipping %s - no routable addresses (likely disconnected)", aa.FriendlyName())
continue
}
var servers []string var servers []string
for dns := aa.FirstDNSServerAddress; dns != nil; dns = dns.Next { for dns := aa.FirstDNSServerAddress; dns != nil; dns = dns.Next {
ip := dns.Address.IP() ip := dns.Address.IP()
@@ -69,8 +86,18 @@ func DiscoverVPNDNS(ctx context.Context) []VPNDNSConfig {
servers = append(servers, ipStr) servers = append(servers, ipStr)
} }
// Collect DNS suffixes (search/match domains) // Check adapter-specific (connection-specific) DNS suffix first,
// since we want to map per-adapter DNS servers to per-adapter suffixes.
// This is what most traditional VPNs set (F5, Cisco AnyConnect, GlobalProtect).
var domains []string var domains []string
if connSuffix := strings.TrimSpace(aa.DNSSuffix()); connSuffix != "" {
domains = append(domains, connSuffix)
Log(ctx, logger.Debug(), "Using connection-specific DNS suffix for %s: %s",
aa.FriendlyName(), connSuffix)
}
// Then check supplemental DNS suffix list (used by Tailscale and
// VPN clients that register search domains via the DNS Client API).
for suffix := aa.FirstDNSSuffix; suffix != nil; suffix = suffix.Next { for suffix := aa.FirstDNSSuffix; suffix != nil; suffix = suffix.Next {
domain := strings.TrimSpace(suffix.String()) domain := strings.TrimSpace(suffix.String())
if domain != "" { if domain != "" {
@@ -78,8 +105,10 @@ func DiscoverVPNDNS(ctx context.Context) []VPNDNSConfig {
} }
} }
// Only include interfaces that have BOTH DNS servers AND search domains // Accept VPN adapters with DNS servers even without domains.
if len(servers) > 0 && len(domains) > 0 { // Domain-less configs still provide useful DNS server IPs that
// can serve existing split-rules and OS resolver queries.
if len(servers) > 0 {
config := VPNDNSConfig{ config := VPNDNSConfig{
InterfaceName: aa.FriendlyName(), InterfaceName: aa.FriendlyName(),
Servers: servers, Servers: servers,
@@ -91,11 +120,11 @@ func DiscoverVPNDNS(ctx context.Context) []VPNDNSConfig {
Log(ctx, logger.Debug(), "Found VPN DNS config - Interface: %s, Servers: %v, Domains: %v", Log(ctx, logger.Debug(), "Found VPN DNS config - Interface: %s, Servers: %v, Domains: %v",
config.InterfaceName, config.Servers, config.Domains) config.InterfaceName, config.Servers, config.Domains)
} else { } else {
Log(ctx, logger.Debug(), "Skipping %s - insufficient DNS config (servers: %d, domains: %d)", Log(ctx, logger.Debug(), "Skipping %s - no DNS servers found",
aa.FriendlyName(), len(servers), len(domains)) aa.FriendlyName())
} }
} }
Log(ctx, logger.Debug(), "VPN DNS discovery completed: found %d VPN interfaces", len(vpnConfigs)) Log(ctx, logger.Debug(), "VPN DNS discovery completed: found %d VPN interfaces", len(vpnConfigs))
return vpnConfigs return vpnConfigs
} }