From 20759017e6aa9d8c3cf1ed8c2115c712787fbfcd Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 24 Jan 2025 00:38:53 +0700 Subject: [PATCH] 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. --- cmd/cli/ad_others.go | 5 +++++ cmd/cli/dns_proxy.go | 19 +++++++++++++++++++ cmd/cli/prog.go | 5 +++++ config.go | 2 +- resolver.go | 19 +++++++++++++++++-- 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/cmd/cli/ad_others.go b/cmd/cli/ad_others.go index 6a7417f..b23476f 100644 --- a/cmd/cli/ad_others.go +++ b/cmd/cli/ad_others.go @@ -8,3 +8,8 @@ import ( // addExtraSplitDnsRule adds split DNS rule if present. func addExtraSplitDnsRule(_ *ctrld.Config) bool { return false } + +// getActiveDirectoryDomain returns AD domain name of this computer. +func getActiveDirectoryDomain() (string, error) { + return "", nil +} diff --git a/cmd/cli/dns_proxy.go b/cmd/cli/dns_proxy.go index 341a830..5396642 100644 --- a/cmd/cli/dns_proxy.go +++ b/cmd/cli/dns_proxy.go @@ -51,6 +51,12 @@ var privateUpstreamConfig = &ctrld.UpstreamConfig{ Timeout: 2000, } +var localUpstreamConfig = &ctrld.UpstreamConfig{ + Name: "Local resolver", + Type: ctrld.ResolverTypeLocal, + Timeout: 2000, +} + // proxyRequest contains data for proxying a DNS query to upstream. type proxyRequest struct { msg *dns.Msg @@ -443,6 +449,11 @@ func (p *prog) proxy(ctx context.Context, req *proxyRequest) *proxyResponse { upstreams = []string{upstreamOS} } + if p.isAdDomainQuery(req.msg) { + upstreamConfigs = []*ctrld.UpstreamConfig{localUpstreamConfig} + upstreams = []string{upstreamOS} + } + res := &proxyResponse{} // LAN/PTR lookup flow: @@ -651,6 +662,14 @@ func (p *prog) upstreamConfigsFromUpstreamNumbers(upstreams []string) []*ctrld.U 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. func canonicalName(fqdn string) string { q := strings.TrimSpace(fqdn) diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index a68dad2..46d4d18 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -106,6 +106,7 @@ type prog struct { internalLogSent time.Time runningIface string requiredMultiNICsConfig bool + adDomain string selfUninstallMu sync.Mutex 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 wg.Add(len(p.cfg.Listener)) diff --git a/config.go b/config.go index c88404c..099f75b 100644 --- a/config.go +++ b/config.go @@ -384,7 +384,7 @@ func (uc *UpstreamConfig) IsDiscoverable() bool { return *uc.Discoverable } switch uc.Type { - case ResolverTypeOS, ResolverTypeLegacy, ResolverTypePrivate: + case ResolverTypeOS, ResolverTypeLegacy, ResolverTypePrivate, ResolverTypeLocal: if ip, err := netip.ParseAddr(uc.Domain); err == nil { return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || tsaddr.CGNATRange().Contains(ip) } diff --git a/resolver.go b/resolver.go index 0097fe0..7dc76b0 100644 --- a/resolver.go +++ b/resolver.go @@ -30,8 +30,10 @@ const ( ResolverTypeOS = "os" // ResolverTypeLegacy specifies legacy resolver. ResolverTypeLegacy = "legacy" - // ResolverTypePrivate is like ResolverTypeOS, but use for local resolver only. + // ResolverTypePrivate is like ResolverTypeOS, but use for private resolver only. ResolverTypePrivate = "private" + // ResolverTypeLocal is like ResolverTypeOS, but use for local resolver only. + ResolverTypeLocal = "local" // ResolverTypeSDNS specifies resolver with information encoded using DNS Stamps. // See: https://dnscrypt.info/stamps-specifications/ ResolverTypeSDNS = "sdns" @@ -47,6 +49,16 @@ var controldPublicDnsWithPort = net.JoinHostPort(controldPublicDns, "53") // or is the Resolver used for ResolverTypeOS. 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. 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 // calling this function. func InitializeOsResolver() []string { - return initializeOsResolver(availableNameservers()) + ns := initializeOsResolver(availableNameservers()) + return ns } // initializeOsResolver performs logic for choosing OS resolver nameserver. @@ -301,6 +314,8 @@ func NewResolver(uc *UpstreamConfig) (Resolver, error) { return &legacyResolver{uc: uc}, nil case ResolverTypePrivate: return NewPrivateResolver(), nil + case ResolverTypeLocal: + return localResolver, nil } return nil, fmt.Errorf("%w: %s", errUnknownResolver, typ) }