Pull request 284: 6156 domain-specific ds

Merge in GO/dnsproxy from 6156-domain-specific-ds to master

Squashed commit of the following:

commit 7714a5eb34da8cbf0874731da2fa27db382c1312
Merge: e244189 c30fcc5
Author: Eugene Burkov <[email protected]>
Date:   Thu Oct 5 15:27:17 2023 +0300

    Merge branch 'master' into 6156-domain-specific-ds

commit e244189f9a70eed01f5180eaf2e84cd666f19826
Author: Eugene Burkov <[email protected]>
Date:   Thu Sep 28 19:04:04 2023 +0300

    proxy: imp doc, code

commit 0d402a905db0947b1e69aa00f7908564aa219238
Author: Eugene Burkov <[email protected]>
Date:   Tue Sep 26 16:23:01 2023 +0300

    proxy: add special ds handling
This commit is contained in:
Eugene Burkov
2023-10-05 15:59:02 +03:00
parent c30fcc54fa
commit 60d2174079
3 changed files with 151 additions and 37 deletions
+13 -3
View File
@@ -458,18 +458,28 @@ func (p *Proxy) needsLocalUpstream(req *dns.Msg) (ok bool) {
// firstly considers custom upstreams if those aren't empty and then the
// configured ones. The returned slice may be empty or nil.
func (p *Proxy) selectUpstreams(d *DNSContext) (upstreams []upstream.Upstream) {
host := d.Req.Question[0].Name
q := d.Req.Question[0]
host := q.Name
qtype := q.Qtype
if !p.needsLocalUpstream(d.Req) {
// TODO(e.burkov): Use the same logic for private upstreams as well,
// when those start supporting non-PTR requests.
getUpstreams := (*UpstreamConfig).getUpstreamsForDomain
if qtype == dns.TypeDS {
getUpstreams = (*UpstreamConfig).getUpstreamsForDS
}
if custom := d.CustomUpstreamConfig; custom != nil {
// Try to use custom.
upstreams = custom.getUpstreamsForDomain(host)
upstreams = getUpstreams(custom, host)
if len(upstreams) > 0 {
return upstreams
}
}
// Use configured.
return p.UpstreamConfig.getUpstreamsForDomain(host)
return getUpstreams(p.UpstreamConfig, host)
}
// Use private upstreams.
+24 -11
View File
@@ -211,8 +211,6 @@ func (uc *UpstreamConfig) getUpstreamsForDomain(host string) (ups []upstream.Ups
return uc.Upstreams
}
var ok bool
dotsCount := strings.Count(host, ".")
if dotsCount < 2 {
host = UnqualifiedNames
@@ -223,21 +221,36 @@ func (uc *UpstreamConfig) getUpstreamsForDomain(host string) (ups []upstream.Ups
}
}
for i := 1; i <= dotsCount; i++ {
h := strings.SplitAfterN(host, ".", i)
name := h[i-1]
ups, ok = uc.lookupUpstreams(name)
if !ok {
continue
for host != "" {
var ok bool
if ups, ok = uc.lookupUpstreams(host); ok {
return ups
}
return ups
_, host, _ = strings.Cut(host, ".")
}
return uc.Upstreams
}
// getUpstreamsForDS is like [getUpstreamsForDomain], but intended for DS
// queries only, so that it matches the host without the first label.
//
// A DS RRset SHOULD be present at a delegation point when the child zone is
// signed. The DS RRset MAY contain multiple records, each referencing a public
// key in the child zone used to verify the RRSIGs in that zone. All DS RRsets
// in a zone MUST be signed, and DS RRsets MUST NOT appear at a zone's apex.
//
// See https://datatracker.ietf.org/doc/html/rfc4035#section-2.4
func (uc *UpstreamConfig) getUpstreamsForDS(host string) (ups []upstream.Upstream) {
_, host, found := strings.Cut(host, ".")
if !found {
return uc.Upstreams
}
return uc.getUpstreamsForDomain(host)
}
// lookupSubdomainExclusion returns upstreams for the host from subdomain
// exclusions list.
func (uc *UpstreamConfig) lookupSubdomainExclusion(host string) (u []upstream.Upstream) {
@@ -266,7 +279,7 @@ func (uc *UpstreamConfig) lookupUpstreams(name string) (ups []upstream.Upstream,
if len(ups) == 0 {
// The domain has been excluded from reserved upstreams querying.
return uc.Upstreams, true
ups = uc.Upstreams
}
return ups, true
+114 -23
View File
@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestGetUpstreamsForDomain(t *testing.T) {
func TestUpstreamConfig_GetUpstreamsForDomain(t *testing.T) {
upstreams := []string{
"[/google.com/local/]4.3.2.1",
"[/www.google.com//]1.2.3.4",
@@ -28,12 +28,95 @@ func TestGetUpstreamsForDomain(t *testing.T) {
)
require.NoError(t, err)
assertUpstreamsForDomain(t, config, "www.google.com.", []string{"1.2.3.4:53", "tls://1.1.1.1:853"})
assertUpstreamsForDomain(t, config, "www2.google.com.", []string{"4.3.2.1:53"})
assertUpstreamsForDomain(t, config, "internal.local.", []string{"4.3.2.1:53"})
assertUpstreamsForDomain(t, config, "google.", []string{"1.2.3.4:53"})
assertUpstreamsForDomain(t, config, "_acme-challenge.example.org.", []string{})
assertUpstreamsForDomain(t, config, "maps.google.com.", []string{})
testCases := []struct {
name string
in string
want []string
}{{
name: "direct_match",
in: "www.google.com.",
want: []string{"1.2.3.4:53", "tls://1.1.1.1:853"},
}, {
name: "subdomain_match",
in: "www2.google.com.",
want: []string{"4.3.2.1:53"},
}, {
name: "second_subdomain_match",
in: "internal.local.",
want: []string{"4.3.2.1:53"},
}, {
name: "unqualified_match",
in: "google.",
want: []string{"1.2.3.4:53"},
}, {
name: "default",
in: "_acme-challenge.example.org.",
want: []string{},
}, {
name: "another_default",
in: "maps.google.com.",
want: []string{},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ups := config.getUpstreamsForDomain(tc.in)
assertUpstreamsForDomain(t, ups, tc.want)
})
}
}
func TestUpstreamConfig_GetUpstreamsForDS(t *testing.T) {
upstreams := []string{
"[/google.com/local/]4.3.2.1",
"[/www.google.com//]1.2.3.4",
"[/maps.google.com/]#",
"[/www.google.com/]tls://1.1.1.1",
"[/_acme-challenge.example.org/]#",
}
config, err := ParseUpstreamsConfig(
upstreams,
&upstream.Options{
InsecureSkipVerify: false,
Bootstrap: []string{},
Timeout: 1 * time.Second,
},
)
require.NoError(t, err)
testCases := []struct {
name string
in string
want []string
}{{
name: "direct_match",
in: "www.google.com.",
want: []string{"4.3.2.1:53"},
}, {
name: "subdomain_match",
in: "abc.www.google.com.",
want: []string{"1.2.3.4:53", "tls://1.1.1.1:853"},
}, {
name: "unqualified_match",
in: "internal.local.",
want: []string{"1.2.3.4:53"},
}, {
name: "more_unqualified_match",
in: "google.",
want: []string{"1.2.3.4:53"},
}, {
name: "default",
in: "_acme-challenge.example.org.",
want: []string{},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ups := config.getUpstreamsForDS(tc.in)
assertUpstreamsForDomain(t, ups, tc.want)
})
}
}
func TestUpstreamConfig_Validate(t *testing.T) {
@@ -157,7 +240,8 @@ func TestGetUpstreamsForDomain_wildcards(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assertUpstreamsForDomain(t, uconf, tc.in, tc.want)
ups := uconf.getUpstreamsForDomain(tc.in)
assertUpstreamsForDomain(t, ups, tc.want)
})
}
}
@@ -197,7 +281,8 @@ func TestGetUpstreamsForDomain_sub_wildcards(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assertUpstreamsForDomain(t, uconf, tc.in, tc.want)
ups := uconf.getUpstreamsForDomain(tc.in)
assertUpstreamsForDomain(t, ups, tc.want)
})
}
}
@@ -238,11 +323,15 @@ func TestGetUpstreamsForDomain_default_wildcards(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assertUpstreamsForDomain(t, uconf, tc.in, tc.want)
ups := uconf.getUpstreamsForDomain(tc.in)
assertUpstreamsForDomain(t, ups, tc.want)
})
}
}
// upsSink is the typed sink variable for the result of benchmarked function.
var upsSink []upstream.Upstream
func BenchmarkGetUpstreamsForDomain(b *testing.B) {
upstreams := []string{
"[/google.com/local/]4.3.2.1",
@@ -260,24 +349,26 @@ func BenchmarkGetUpstreamsForDomain(b *testing.B) {
},
)
for i := 0; i < b.N; i++ {
assertUpstreamsForDomain(b, config, "www.google.com.", []string{"1.2.3.4:53", "tls://1.1.1.1:853"})
assertUpstreamsForDomain(b, config, "www2.google.com.", []string{"4.3.2.1:53"})
assertUpstreamsForDomain(b, config, "internal.local.", []string{"4.3.2.1:53"})
assertUpstreamsForDomain(b, config, "google.", []string{"1.2.3.4:53"})
assertUpstreamsForDomain(b, config, "maps.google.com.", []string{})
domains := []string{
"www.google.com.",
"www2.google.com.",
"internal.local.",
"google.",
"maps.google.com.",
}
for i, l := 0, len(domains); i < b.N; i++ {
upsSink = config.getUpstreamsForDomain(domains[i%l])
}
}
// assertUpstreamsForDomain checks the addresses of the specified domain
// upstreams and their number.
func assertUpstreamsForDomain(t testing.TB, config *UpstreamConfig, domain string, address []string) {
t.Helper()
func assertUpstreamsForDomain(tb testing.TB, ups []upstream.Upstream, want []string) {
tb.Helper()
u := config.getUpstreamsForDomain(domain)
require.Len(t, u, len(address))
for i, up := range u {
assert.Equalf(t, address[i], up.Address(), "bad upstream at index %d", i)
require.Len(tb, ups, len(want))
for i, up := range ups {
assert.Equalf(tb, want[i], up.Address(), "bad upstream at index %d", i)
}
}