Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: specify local-only domains #366

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,12 @@ func (p *Proxy) selectUpstreams(d *DNSContext) (upstreams []upstream.Upstream) {
func (p *Proxy) replyFromUpstream(d *DNSContext) (ok bool, err error) {
req := d.Req

if p.UpstreamConfig.checkLocalOnly(req.Question[0].Name) {
resp := p.genWithRCode(req, dns.RcodeNameError)
p.handleExchangeResult(d, req, resp, nil)
return true, nil
}

upstreams := p.selectUpstreams(d)
if len(upstreams) == 0 {
return false, fmt.Errorf("selecting general upstream: %w", upstream.ErrNoUpstreams)
Expand Down
48 changes: 46 additions & 2 deletions proxy/upstreams.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ type UpstreamConfig struct {
// corresponding upstreams.
SpecifiedDomainUpstreams map[string][]upstream.Upstream

// LocalOnlyDomains is a set of domains which should never be looked up on
// any upstream server. The value of the boolean doesn't matter, this is only
// a map to make lookups quicker than if it was a slice. If the key exists,
// it is treated as local only.
LocalOnlyDomains map[string]bool

// SubdomainExclusions is set of domains with subdomains exclusions.
SubdomainExclusions *stringutil.Set

Expand Down Expand Up @@ -58,16 +64,23 @@ var _ io.Closer = (*UpstreamConfig)(nil)
//
// [/domain1/../domainN/]#
//
// To ensure domains will never be looked up on any upstream servers, and will
// respond with NXDOMAIN if not resolved locally, use the following syntax:
//
// [/domain1/../domainN/]-
//
// So the following config:
//
// [/host.com/]1.2.3.4
// [/www.host.com/]2.3.4.5"
// [/domain.local/]-
// [/maps.host.com/news.host.com/]#
// 3.4.5.6
//
// will send queries for *.host.com to 1.2.3.4. Except for *.www.host.com,
// which will go to 2.3.4.5. And *.maps.host.com or *.news.host.com, which
// will go to default server 3.4.5.6 with all other domains.
// which will go to 2.3.4.5. Any requests to *.domain.local or domain.local
// will only be resolved locally. And *.maps.host.com or *.news.host.com,
// which will go to default server 3.4.5.6 with all other domains.
//
// To exclude top level domain from reserved upstreams querying you could use
// the following:
Expand Down Expand Up @@ -95,6 +108,7 @@ func ParseUpstreamsConfig(upstreamConfig []string, options *upstream.Options) (*
domainReservedUpstreams: map[string][]upstream.Upstream{},
specifiedDomainUpstreams: map[string][]upstream.Upstream{},
subdomainsOnlyUpstreams: map[string][]upstream.Upstream{},
localOnlyDomains: map[string]bool{},
subdomainsOnlyExclusions: stringutil.NewSet(),
}

Expand All @@ -121,6 +135,12 @@ type configParser struct {
// corresponding upstreams.
subdomainsOnlyUpstreams map[string][]upstream.Upstream

// localOnlyDomains is a set of domains which should never be looked up on
// any upstream server. The value of the boolean doesn't matter, this is only
// a map to make lookups quicker than if it was a slice. If the key exists,
// it is treated as local only.
localOnlyDomains map[string]bool

// subdomainsOnlyExclusions is set of domains with subdomains exclusions.
subdomainsOnlyExclusions *stringutil.Set

Expand All @@ -147,6 +167,7 @@ func (p *configParser) parse(conf []string) (c *UpstreamConfig, err error) {
DomainReservedUpstreams: p.domainReservedUpstreams,
SpecifiedDomainUpstreams: p.specifiedDomainUpstreams,
SubdomainExclusions: p.subdomainsOnlyExclusions,
LocalOnlyDomains: p.localOnlyDomains,
}, nil
}

Expand All @@ -158,6 +179,12 @@ func (p *configParser) parseLine(idx int, confLine string) (err error) {
return err
}

if upstreams[0] == "-" && len(domains) > 0 {
p.specifyLocalOnly(domains)

return nil
}

if upstreams[0] == "#" && len(domains) > 0 {
p.excludeFromReserved(domains)

Expand Down Expand Up @@ -208,6 +235,12 @@ func splitConfigLine(idx int, confLine string) (upstreams, domains []string, err
return strings.Fields(upstreamsLine), domains, nil
}

func (p *configParser) specifyLocalOnly(domains []string) {
for _, domain := range domains {
p.localOnlyDomains[domain] = true
}
}

// specifyUpstream specifies the upstream for domains.
func (p *configParser) specifyUpstream(
domains []string,
Expand Down Expand Up @@ -296,6 +329,17 @@ func (uc *UpstreamConfig) validate() (err error) {
}
}

func (uc *UpstreamConfig) checkLocalOnly(host string) bool {
for host != "" {
var ok bool
if _, ok = uc.LocalOnlyDomains[host]; ok {
return true
}
_, host, _ = strings.Cut(host, ".")
}
return false
}

// getUpstreamsForDomain looks for a domain in the reserved domains map and
// returns a list of corresponding upstreams. It returns default upstreams list
// if the domain was not found in the map. More specific domains take priority
Expand Down