From c4ff80fd3aa070b75bcfc02b0aaf6b5670fddefe Mon Sep 17 00:00:00 2001 From: Dimitry Kolyshev Date: Wed, 27 Apr 2022 11:39:48 +0300 Subject: [PATCH] Pull request: dnsforward: ddr support Merge in DNS/adguard-home from 4463-ddr-support-1 to master Squashed commit of the following: commit 74d8337a9d78e00a0b01301bbf92054fc58aff0d Merge: 7882c56e ed449c61 Author: Dimitry Kolyshev Date: Wed Apr 27 10:32:48 2022 +0200 Merge remote-tracking branch 'origin/master' into 4463-ddr-support-1 commit 7882c56eced204b99a0189c839f5b5ef56fcbfd8 Author: Dimitry Kolyshev Date: Tue Apr 26 13:29:16 2022 +0200 all: docs commit 59593cf47f8db2131fb8a4a44ec3721de8f73567 Author: Dimitry Kolyshev Date: Tue Apr 26 13:06:49 2022 +0200 all: docs commit 13bfe00d91b190a2538eeee642ce40abe031ecf2 Author: Dimitry Kolyshev Date: Tue Apr 26 12:58:48 2022 +0200 all: docs commit a663b53d211483a717a480e24e120a201dc3d9da Merge: 53122f6a 235316e0 Author: Dimitry Kolyshev Date: Tue Apr 26 12:33:07 2022 +0200 Merge remote-tracking branch 'origin/master' into 4463-ddr-support-1 commit 53122f6aac8e9ede69de833e367e006f4c5c75c0 Author: Dimitry Kolyshev Date: Tue Apr 26 12:30:56 2022 +0200 dnsforward: ddr support commit 87083ded02c120e1fb3e54b885a1992efd8f780d Author: Dimitry Kolyshev Date: Tue Apr 26 11:51:06 2022 +0200 dnsforward: ddr support commit 3dc711e0a9ba1a024e7d24527b2a690aa36413ce Author: Dimitry Kolyshev Date: Tue Apr 26 11:39:59 2022 +0200 dnsforward: imp code commit f63f6a9d65a96960ae2c06aeca2b32aef70d8f63 Author: Dimitry Kolyshev Date: Tue Apr 26 11:34:23 2022 +0200 dnsforward: ddr support commit e64ffcdac8f9428e4c93a6dc99cc3f1bb090af35 Author: Dimitry Kolyshev Date: Tue Apr 26 11:22:20 2022 +0200 dnsforward: ddr support commit 297460946bb1765137c7c3fe3e298cd574635287 Author: Dimitry Kolyshev Date: Tue Apr 26 11:08:59 2022 +0200 dnsforward: imp code commit 61b4e2e0e06e212c31b7a9d1b09fab392ae6dbc4 Author: Dimitry Kolyshev Date: Mon Apr 25 14:39:34 2022 +0200 dnsforward: ddr support commit 7c2787e12eb67a02b41cbb4fe36a12671259f9c9 Author: Dimitry Kolyshev Date: Mon Apr 25 11:41:42 2022 +0200 all: docs commit 29c2c872843f6d006e6a98144a52e23a4cbe7be9 Author: Dimitry Kolyshev Date: Mon Apr 25 11:26:07 2022 +0200 dnsforward: ddr support commit 2d4ba0c4ce4fbbf3d99da8dd92349da2ec9cff13 Author: Dimitry Kolyshev Date: Mon Apr 25 11:03:34 2022 +0200 dnsforward: ddr support commit 0efb5b5cd55bcba3dfae35e80209277f0643a87e Author: Dimitry Kolyshev Date: Sun Apr 24 13:07:25 2022 +0200 dnsforward: imp code commit 884381ef04029d5d743834555cb6601d891c2d25 Author: Dimitry Kolyshev Date: Sun Apr 24 12:56:41 2022 +0200 dnsforward: imp code commit 41231f24e83a9690d36546e83fd61ddd709050ed Author: Dimitry Kolyshev Date: Fri Apr 22 16:05:47 2022 +0200 dnsforward: ddr support commit 9d9da3f479efa5d5609f9b1e6b0d1a93fc253b9f Author: Dimitry Kolyshev Date: Fri Apr 22 13:46:29 2022 +0200 all: ddr support commit b225363df143d599e9acbf1a6b0bf6d00044dd47 Author: Dimitry Kolyshev Date: Fri Apr 22 13:38:27 2022 +0200 dnsforward: imp code ... and 10 more commits --- CHANGELOG.md | 5 +- internal/dnsforward/config.go | 3 +- internal/dnsforward/dns.go | 76 +++++++++++++++++ internal/dnsforward/dns_test.go | 146 ++++++++++++++++++++++++++++++++ internal/home/config.go | 1 + 5 files changed, 229 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 047726ff..84f45312 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ and this project adheres to ### Added +- Support for Discovery of Designated Resolvers (DDR) according to the + [RFC draft][ddr-draft-06] ([#4463]). - The ability to control each source of runtime clients separately via `clients.runtime_sources` configuration object ([#3020]). - The ability to customize the set of networks that are considered private @@ -143,8 +145,9 @@ In this release, the schema version has changed from 12 to 14. [#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276 [#4499]: https://github.com/AdguardTeam/AdGuardHome/issues/4499 -[repr]: https://reproducible-builds.org/docs/source-date-epoch/ +[ddr-draft-06]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html [doq-draft-10]: https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-10#section-10.2 +[repr]: https://reproducible-builds.org/docs/source-date-epoch/ diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index 9a050f52..16a6325e 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -122,6 +122,7 @@ type FilteringConfig struct { EnableDNSSEC bool `yaml:"enable_dnssec"` // Set AD flag in outcoming DNS request EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option MaxGoroutines uint32 `yaml:"max_goroutines"` // Max. number of parallel goroutines for processing incoming requests + HandleDDR bool `yaml:"handle_ddr"` // Handle DDR requests // IpsetList is the ipset configuration that allows AdGuard Home to add // IP addresses of the specified domain names to an ipset list. Syntax: @@ -151,7 +152,7 @@ type TLSConfig struct { PrivateKeyData []byte `yaml:"-" json:"-"` // ServerName is the hostname of the server. Currently, it is only being - // used for ClientID checking. + // used for ClientID checking and Discovery of Designated Resolvers (DDR). ServerName string `yaml:"-" json:"-"` cert tls.Certificate diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index d423482a..19d54d91 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -76,6 +76,10 @@ const ( resultCodeError ) +// ddrHostFQDN is the FQDN used in Discovery of Designated Resolvers (DDR) requests. +// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html. +const ddrHostFQDN = "_dns.resolver.arpa." + // handleDNSRequest filters the incoming DNS requests and writes them to the query log func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error { ctx := &dnsContext{ @@ -94,6 +98,7 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error { mods := []modProcessFunc{ s.processRecursion, s.processInitial, + s.processDDRQuery, s.processDetermineLocal, s.processInternalHosts, s.processRestrictLocal, @@ -241,6 +246,77 @@ func (s *Server) onDHCPLeaseChanged(flags int) { s.setTableIPToHost(ipToHost) } +// processDDRQuery responds to SVCB query for a special use domain name +// ‘_dns.resolver.arpa’. The result contains different types of encryption +// supported by current user configuration. +// +// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html. +func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) { + d := ctx.proxyCtx + question := d.Req.Question[0] + + if !s.conf.HandleDDR { + return resultCodeSuccess + } + + if question.Name == ddrHostFQDN { + // TODO(a.garipov): Check DoQ support in next RFC drafts. + if s.dnsProxy.TLSListenAddr == nil && s.dnsProxy.HTTPSListenAddr == nil || + question.Qtype != dns.TypeSVCB { + d.Res = s.makeResponse(d.Req) + + return resultCodeFinish + } + + d.Res = s.makeDDRResponse(d.Req) + + return resultCodeFinish + } + + return resultCodeSuccess +} + +// makeDDRResponse creates DDR answer according to server configuration. +func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) { + resp = s.makeResponse(req) + domainName := s.conf.ServerName + + for _, addr := range s.dnsProxy.HTTPSListenAddr { + values := []dns.SVCBKeyValue{ + &dns.SVCBAlpn{Alpn: []string{"h2"}}, + &dns.SVCBPort{Port: uint16(addr.Port)}, + &dns.SVCBDoHPath{Template: "/dns-query?dns"}, + } + + ans := &dns.SVCB{ + Hdr: s.hdr(req, dns.TypeSVCB), + Priority: 1, + Target: domainName, + Value: values, + } + + resp.Answer = append(resp.Answer, ans) + } + + for _, addr := range s.dnsProxy.TLSListenAddr { + values := []dns.SVCBKeyValue{ + &dns.SVCBAlpn{Alpn: []string{"dot"}}, + &dns.SVCBPort{Port: uint16(addr.Port)}, + } + + ans := &dns.SVCB{ + Hdr: s.hdr(req, dns.TypeSVCB), + Priority: 2, + Target: domainName, + Value: values, + } + + resp.Answer = append(resp.Answer, ans) + } + + return resp +} + // processDetermineLocal determines if the client's IP address is from // locally-served network and saves the result into the context. func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) { diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go index 54104268..8ab7501c 100644 --- a/internal/dnsforward/dns_test.go +++ b/internal/dnsforward/dns_test.go @@ -14,6 +14,152 @@ import ( "github.com/stretchr/testify/require" ) +const ddrTestDomainName = "dns.example.net" + +func TestServer_ProcessDDRQuery(t *testing.T) { + dohSVCB := &dns.SVCB{ + Priority: 1, + Target: ddrTestDomainName, + Value: []dns.SVCBKeyValue{ + &dns.SVCBAlpn{Alpn: []string{"h2"}}, + &dns.SVCBPort{Port: 8044}, + &dns.SVCBDoHPath{Template: "/dns-query?dns"}, + }, + } + + dotSVCB := &dns.SVCB{ + Priority: 2, + Target: ddrTestDomainName, + Value: []dns.SVCBKeyValue{ + &dns.SVCBAlpn{Alpn: []string{"dot"}}, + &dns.SVCBPort{Port: 8043}, + }, + } + + testCases := []struct { + name string + host string + want []*dns.SVCB + wantRes resultCode + portDoH int + portDoT int + qtype uint16 + ddrEnabled bool + }{{ + name: "pass_host", + wantRes: resultCodeSuccess, + host: "example.net.", + qtype: dns.TypeSVCB, + ddrEnabled: true, + portDoH: 8043, + }, { + name: "pass_qtype", + wantRes: resultCodeFinish, + host: ddrHostFQDN, + qtype: dns.TypeA, + ddrEnabled: true, + portDoH: 8043, + }, { + name: "pass_disabled_tls", + wantRes: resultCodeFinish, + host: ddrHostFQDN, + qtype: dns.TypeSVCB, + ddrEnabled: true, + }, { + name: "pass_disabled_ddr", + wantRes: resultCodeSuccess, + host: ddrHostFQDN, + qtype: dns.TypeSVCB, + ddrEnabled: false, + portDoH: 8043, + }, { + name: "dot", + wantRes: resultCodeFinish, + want: []*dns.SVCB{dotSVCB}, + host: ddrHostFQDN, + qtype: dns.TypeSVCB, + ddrEnabled: true, + portDoT: 8043, + }, { + name: "doh", + wantRes: resultCodeFinish, + want: []*dns.SVCB{dohSVCB}, + host: ddrHostFQDN, + qtype: dns.TypeSVCB, + ddrEnabled: true, + portDoH: 8044, + }, { + name: "dot_doh", + wantRes: resultCodeFinish, + want: []*dns.SVCB{dotSVCB, dohSVCB}, + host: ddrHostFQDN, + qtype: dns.TypeSVCB, + ddrEnabled: true, + portDoT: 8043, + portDoH: 8044, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + s := prepareTestServer(t, tc.portDoH, tc.portDoT, tc.ddrEnabled) + + req := createTestMessageWithType(tc.host, tc.qtype) + + dctx := &dnsContext{ + proxyCtx: &proxy.DNSContext{ + Req: req, + }, + } + + res := s.processDDRQuery(dctx) + require.Equal(t, tc.wantRes, res) + + if tc.wantRes != resultCodeFinish { + return + } + + msg := dctx.proxyCtx.Res + require.NotNil(t, msg) + + for _, v := range tc.want { + v.Hdr = s.hdr(req, dns.TypeSVCB) + } + + assert.ElementsMatch(t, tc.want, msg.Answer) + }) + } +} + +func prepareTestServer(t *testing.T, portDoH, portDoT int, ddrEnabled bool) (s *Server) { + t.Helper() + + proxyConf := proxy.Config{} + + if portDoH > 0 { + proxyConf.HTTPSListenAddr = []*net.TCPAddr{{Port: portDoH}} + } + + if portDoT > 0 { + proxyConf.TLSListenAddr = []*net.TCPAddr{{Port: portDoT}} + } + + s = &Server{ + dnsProxy: &proxy.Proxy{ + Config: proxyConf, + }, + conf: ServerConfig{ + FilteringConfig: FilteringConfig{ + HandleDDR: ddrEnabled, + }, + TLSConfig: TLSConfig{ + ServerName: ddrTestDomainName, + }, + }, + } + + return s +} + func TestServer_ProcessDetermineLocal(t *testing.T) { s := &Server{ privateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed), diff --git a/internal/home/config.go b/internal/home/config.go index 720683a1..14f5781e 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -187,6 +187,7 @@ var config = &configuration{ Ratelimit: 20, RefuseAny: true, AllServers: false, + HandleDDR: true, FastestTimeout: timeutil.Duration{ Duration: fastip.DefaultPingWaitTimeout, },