all: use local resolver for ADDC

For normal OS resolver, ctrld does not use local addresses as nameserver
to avoid possible looping. However, on AD environment with local DNS
running, AD queries must be sent to the local DNS server for proper
resolving.
This commit is contained in:
Cuong Manh Le
2025-01-24 00:38:53 +07:00
committed by Cuong Manh Le
parent 69e0aab73e
commit 20759017e6
5 changed files with 47 additions and 3 deletions

View File

@@ -8,3 +8,8 @@ import (
// addExtraSplitDnsRule adds split DNS rule if present. // addExtraSplitDnsRule adds split DNS rule if present.
func addExtraSplitDnsRule(_ *ctrld.Config) bool { return false } func addExtraSplitDnsRule(_ *ctrld.Config) bool { return false }
// getActiveDirectoryDomain returns AD domain name of this computer.
func getActiveDirectoryDomain() (string, error) {
return "", nil
}

View File

@@ -51,6 +51,12 @@ var privateUpstreamConfig = &ctrld.UpstreamConfig{
Timeout: 2000, Timeout: 2000,
} }
var localUpstreamConfig = &ctrld.UpstreamConfig{
Name: "Local resolver",
Type: ctrld.ResolverTypeLocal,
Timeout: 2000,
}
// proxyRequest contains data for proxying a DNS query to upstream. // proxyRequest contains data for proxying a DNS query to upstream.
type proxyRequest struct { type proxyRequest struct {
msg *dns.Msg msg *dns.Msg
@@ -443,6 +449,11 @@ func (p *prog) proxy(ctx context.Context, req *proxyRequest) *proxyResponse {
upstreams = []string{upstreamOS} upstreams = []string{upstreamOS}
} }
if p.isAdDomainQuery(req.msg) {
upstreamConfigs = []*ctrld.UpstreamConfig{localUpstreamConfig}
upstreams = []string{upstreamOS}
}
res := &proxyResponse{} res := &proxyResponse{}
// LAN/PTR lookup flow: // LAN/PTR lookup flow:
@@ -651,6 +662,14 @@ func (p *prog) upstreamConfigsFromUpstreamNumbers(upstreams []string) []*ctrld.U
return upstreamConfigs return upstreamConfigs
} }
func (p *prog) isAdDomainQuery(msg *dns.Msg) bool {
if p.adDomain == "" {
return false
}
cDomainName := canonicalName(msg.Question[0].Name)
return dns.IsSubDomain(p.adDomain, cDomainName)
}
// canonicalName returns canonical name from FQDN with "." trimmed. // canonicalName returns canonical name from FQDN with "." trimmed.
func canonicalName(fqdn string) string { func canonicalName(fqdn string) string {
q := strings.TrimSpace(fqdn) q := strings.TrimSpace(fqdn)

View File

@@ -106,6 +106,7 @@ type prog struct {
internalLogSent time.Time internalLogSent time.Time
runningIface string runningIface string
requiredMultiNICsConfig bool requiredMultiNICsConfig bool
adDomain string
selfUninstallMu sync.Mutex selfUninstallMu sync.Mutex
refusedQueryCount int refusedQueryCount int
@@ -441,6 +442,10 @@ func (p *prog) run(reload bool, reloadCh chan struct{}) {
} }
} }
} }
if domain, err := getActiveDirectoryDomain(); err == nil && domain != "" && hasLocalDnsServerRunning() {
mainLog.Load().Debug().Msgf("active directory domain: %s", domain)
p.adDomain = domain
}
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(len(p.cfg.Listener)) wg.Add(len(p.cfg.Listener))

View File

@@ -384,7 +384,7 @@ func (uc *UpstreamConfig) IsDiscoverable() bool {
return *uc.Discoverable return *uc.Discoverable
} }
switch uc.Type { switch uc.Type {
case ResolverTypeOS, ResolverTypeLegacy, ResolverTypePrivate: case ResolverTypeOS, ResolverTypeLegacy, ResolverTypePrivate, ResolverTypeLocal:
if ip, err := netip.ParseAddr(uc.Domain); err == nil { if ip, err := netip.ParseAddr(uc.Domain); err == nil {
return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || tsaddr.CGNATRange().Contains(ip) return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || tsaddr.CGNATRange().Contains(ip)
} }

View File

@@ -30,8 +30,10 @@ const (
ResolverTypeOS = "os" ResolverTypeOS = "os"
// ResolverTypeLegacy specifies legacy resolver. // ResolverTypeLegacy specifies legacy resolver.
ResolverTypeLegacy = "legacy" ResolverTypeLegacy = "legacy"
// ResolverTypePrivate is like ResolverTypeOS, but use for local resolver only. // ResolverTypePrivate is like ResolverTypeOS, but use for private resolver only.
ResolverTypePrivate = "private" ResolverTypePrivate = "private"
// ResolverTypeLocal is like ResolverTypeOS, but use for local resolver only.
ResolverTypeLocal = "local"
// ResolverTypeSDNS specifies resolver with information encoded using DNS Stamps. // ResolverTypeSDNS specifies resolver with information encoded using DNS Stamps.
// See: https://dnscrypt.info/stamps-specifications/ // See: https://dnscrypt.info/stamps-specifications/
ResolverTypeSDNS = "sdns" ResolverTypeSDNS = "sdns"
@@ -47,6 +49,16 @@ var controldPublicDnsWithPort = net.JoinHostPort(controldPublicDns, "53")
// or is the Resolver used for ResolverTypeOS. // or is the Resolver used for ResolverTypeOS.
var or = newResolverWithNameserver(defaultNameservers()) var or = newResolverWithNameserver(defaultNameservers())
var localResolver = newLocalResolver()
func newLocalResolver() Resolver {
var nss []string
for _, addr := range Rfc1918Addresses() {
nss = append(nss, net.JoinHostPort(addr, "53"))
}
return NewResolverWithNameserver(nss)
}
// LanQueryCtxKey is the context.Context key to indicate that the request is for LAN network. // LanQueryCtxKey is the context.Context key to indicate that the request is for LAN network.
type LanQueryCtxKey struct{} type LanQueryCtxKey struct{}
@@ -89,7 +101,8 @@ func availableNameservers() []string {
// It's the caller's responsibility to ensure the system DNS is in a clean state before // It's the caller's responsibility to ensure the system DNS is in a clean state before
// calling this function. // calling this function.
func InitializeOsResolver() []string { func InitializeOsResolver() []string {
return initializeOsResolver(availableNameservers()) ns := initializeOsResolver(availableNameservers())
return ns
} }
// initializeOsResolver performs logic for choosing OS resolver nameserver. // initializeOsResolver performs logic for choosing OS resolver nameserver.
@@ -301,6 +314,8 @@ func NewResolver(uc *UpstreamConfig) (Resolver, error) {
return &legacyResolver{uc: uc}, nil return &legacyResolver{uc: uc}, nil
case ResolverTypePrivate: case ResolverTypePrivate:
return NewPrivateResolver(), nil return NewPrivateResolver(), nil
case ResolverTypeLocal:
return localResolver, nil
} }
return nil, fmt.Errorf("%w: %s", errUnknownResolver, typ) return nil, fmt.Errorf("%w: %s", errUnknownResolver, typ)
} }