mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-10-12 01:58:46 +02:00
cli: add network fallback and explicit-only Internal Domain modes
Internal Domains now support three modes. "os" is unchanged. "resolvers" becomes explicit resolver with network fallback: the configured resolvers are tried first, and a timeout, unreachable resolver, SERVFAIL or NXDOMAIN hands the query to matching VPN DNS servers, then domain-less VPN DNS servers, then the OS resolver's LAN nameservers through the new LanOnlyQueryCtx. "resolvers_only" keeps the previous strict behavior. Down fallback-mode resolvers are skipped and re-checked in the background at most every 30s. Generated upstreams are identified by the UpstreamConfig.InternalDomain marker instead of the internal_ key prefix. Master adaptations: the fallback state lives on proxyRequest and is applied in tryUpstreams, shouldContinueWithNextUpstream and handleAllUpstreamsFailure instead of v1.0's monolithic proxy loop. Resolver construction uses master's context-aware NewResolver, and the background re-check marks its context private so transport errors that name the resolver stay at debug. Tests use master's zap JSON capture and VPN manager logger.
This commit is contained in:
1 parent
bfc46ef265
commit
b0408e4d1c
17 files changed
+1334
-138
No files matched your search
+33
@@ -126,6 +126,29 @@ func PrivateResolverCtx(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, privateResolverCtxKey{}, true)
|
||||
}
|
||||
|
||||
// LanOnlyQueryCtxKey is the context.Context key that limits an OS resolver
|
||||
// query to its LAN nameservers.
|
||||
type LanOnlyQueryCtxKey struct{}
|
||||
|
||||
// LanOnlyQueryCtx returns a context.Context with LanOnlyQueryCtxKey set.
|
||||
//
|
||||
// An OS resolver query made with it goes only to LAN-classified nameservers:
|
||||
// private, loopback, link-local and CGNAT addresses. Every public nameserver
|
||||
// is left out, the ones DHCP or a VPN supplied as well as ctrld's own public
|
||||
// fallback, and with no LAN nameserver the query fails without being sent. It
|
||||
// is for names that must not reach public DNS, such as an organization's
|
||||
// Internal Domains. LanQueryCtx, by contrast, only drops ctrld's own public
|
||||
// fallback.
|
||||
func LanOnlyQueryCtx(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, LanOnlyQueryCtxKey{}, true)
|
||||
}
|
||||
|
||||
// isLanOnlyQuery reports whether ctx was made by LanOnlyQueryCtx.
|
||||
func isLanOnlyQuery(ctx context.Context) bool {
|
||||
lanOnly, _ := ctx.Value(LanOnlyQueryCtxKey{}).(bool)
|
||||
return lanOnly
|
||||
}
|
||||
|
||||
// defaultNameservers is like nameservers with each element formed "ip:53".
|
||||
func defaultNameservers(ctx context.Context) []string {
|
||||
ns := nameservers(ctx)
|
||||
@@ -580,6 +603,12 @@ func (o *osResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error
|
||||
// the key so subnet-specific answers are neither coalesced nor hot-cached
|
||||
// across different subnets (RFC 7871 §7.3).
|
||||
key := fmt.Sprintf("%s:%d:%s", domain, qtype, dnscache.CanonicalECS(msg))
|
||||
// A LAN-only query must not join, or be answered from the hot cache of, an
|
||||
// ordinary query for the same name: that answer may have come from a public
|
||||
// nameserver.
|
||||
if isLanOnlyQuery(ctx) {
|
||||
key += ":lan-only"
|
||||
}
|
||||
|
||||
logger := LoggerFromCtx(ctx)
|
||||
Log(ctx, logger.Debug(), "OS resolver query started: %s - %s", domain, dns.TypeToString[qtype])
|
||||
@@ -641,6 +670,10 @@ func (o *osResolver) resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error
|
||||
if p := o.lanServers.Load(); p != nil {
|
||||
nss = append(nss, (*p)...)
|
||||
}
|
||||
// A LAN-only query is never sent to a public nameserver.
|
||||
if isLanOnlyQuery(ctx) {
|
||||
publicServers = nil
|
||||
}
|
||||
numServers := len(nss) + len(publicServers)
|
||||
|
||||
// If this is a LAN query, skip public DNS.
|
||||
|
||||
Reference in new issue
Block a user