package dnsforward import ( "fmt" "slices" "time" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/stringutil" ) // newBootstrap returns a bootstrap resolver based on the configuration of s. // boots are the upstream resolvers that should be closed after use. r is the // actual bootstrap resolver, which may include the system hosts. // // TODO(e.burkov): This function currently returns a resolver and a slice of // the upstream resolvers, which are essentially the same. boots are returned // for being able to close them afterwards, but it introduces an implicit // contract that r could only be used before that. Anyway, this code should // improve when the [proxy.UpstreamConfig] will become an [upstream.Resolver] // and be used here. func newBootstrap( addrs []string, etcHosts upstream.Resolver, opts *upstream.Options, ) (r upstream.Resolver, boots []*upstream.UpstreamResolver, err error) { if len(addrs) == 0 { addrs = defaultBootstrap } boots, err = aghnet.ParseBootstraps(addrs, opts) if err != nil { // Don't wrap the error, since it's informative enough as is. return nil, nil, err } var parallel upstream.ParallelResolver for _, b := range boots { parallel = append(parallel, upstream.NewCachingResolver(b)) } if etcHosts != nil { r = upstream.ConsequentResolver{etcHosts, parallel} } else { r = parallel } return r, boots, nil } // newUpstreamConfig returns the upstream configuration based on upstreams. If // upstreams slice specifies no default upstreams, defaultUpstreams are used to // create upstreams with no domain specifications. opts are used when creating // upstream configuration. func newUpstreamConfig( upstreams []string, defaultUpstreams []string, opts *upstream.Options, ) (uc *proxy.UpstreamConfig, err error) { uc, err = proxy.ParseUpstreamsConfig(upstreams, opts) if err != nil { return uc, fmt.Errorf("parsing upstreams: %w", err) } if len(uc.Upstreams) == 0 && len(defaultUpstreams) > 0 { log.Info("dnsforward: warning: no default upstreams specified, using %v", defaultUpstreams) var defaultUpstreamConfig *proxy.UpstreamConfig defaultUpstreamConfig, err = proxy.ParseUpstreamsConfig(defaultUpstreams, opts) if err != nil { return uc, fmt.Errorf("parsing default upstreams: %w", err) } uc.Upstreams = defaultUpstreamConfig.Upstreams } return uc, nil } // newPrivateConfig creates an upstream configuration for resolving PTR records // for local addresses. The configuration is built either from the provided // addresses or from the system resolvers. unwanted filters the resulting // upstream configuration. func newPrivateConfig( addrs []string, unwanted addrPortSet, sysResolvers SystemResolvers, privateNets netutil.SubnetSet, opts *upstream.Options, ) (uc *proxy.UpstreamConfig, err error) { confNeedsFiltering := len(addrs) > 0 if confNeedsFiltering { addrs = stringutil.FilterOut(addrs, IsCommentOrEmpty) } else { sysResolvers := slices.DeleteFunc(slices.Clone(sysResolvers.Addrs()), unwanted.Has) addrs = make([]string, 0, len(sysResolvers)) for _, r := range sysResolvers { addrs = append(addrs, r.String()) } } log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", addrs) uc, err = proxy.ParseUpstreamsConfig(addrs, opts) if err != nil { return uc, fmt.Errorf("preparing private upstreams: %w", err) } if !confNeedsFiltering { return uc, nil } err = filterOutAddrs(uc, unwanted) if err != nil { return uc, fmt.Errorf("filtering private upstreams: %w", err) } // Prevalidate the config to catch the exact error before creating proxy. // See TODO on [PrivateRDNSError]. err = proxy.ValidatePrivateConfig(uc, privateNets) if err != nil { return uc, &PrivateRDNSError{err: err} } return uc, nil } // UpstreamHTTPVersions returns the HTTP versions for upstream configuration // depending on configuration. func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) { if !http3 { return upstream.DefaultHTTPVersions } return []upstream.HTTPVersion{ upstream.HTTPVersion3, upstream.HTTPVersion2, upstream.HTTPVersion11, } } // setProxyUpstreamMode sets the upstream mode and related settings in conf // based on provided parameters. func setProxyUpstreamMode( conf *proxy.Config, upstreamMode UpstreamMode, fastestTimeout time.Duration, ) (err error) { switch upstreamMode { case UpstreamModeParallel: conf.UpstreamMode = proxy.UModeParallel case UpstreamModeFastestAddr: conf.UpstreamMode = proxy.UModeFastestAddr conf.FastestPingTimeout = fastestTimeout case UpstreamModeLoadBalance: conf.UpstreamMode = proxy.UModeLoadBalance default: return fmt.Errorf("unexpected value %q", upstreamMode) } return nil } // IsCommentOrEmpty returns true if s starts with a "#" character or is empty. // This function is useful for filtering out non-upstream lines from upstream // configs. func IsCommentOrEmpty(s string) (ok bool) { return len(s) == 0 || s[0] == '#' }