mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-11-25 06:25:44 +03:00
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: 7882c56eed449c61
Author: Dimitry Kolyshev <dkolyshev@adguard.com> 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 <dkolyshev@adguard.com> Date: Tue Apr 26 13:29:16 2022 +0200 all: docs commit 59593cf47f8db2131fb8a4a44ec3721de8f73567 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue Apr 26 13:06:49 2022 +0200 all: docs commit 13bfe00d91b190a2538eeee642ce40abe031ecf2 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue Apr 26 12:58:48 2022 +0200 all: docs commit a663b53d211483a717a480e24e120a201dc3d9da Merge: 53122f6a235316e0
Author: Dimitry Kolyshev <dkolyshev@adguard.com> 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 <dkolyshev@adguard.com> Date: Tue Apr 26 12:30:56 2022 +0200 dnsforward: ddr support commit 87083ded02c120e1fb3e54b885a1992efd8f780d Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue Apr 26 11:51:06 2022 +0200 dnsforward: ddr support commit 3dc711e0a9ba1a024e7d24527b2a690aa36413ce Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue Apr 26 11:39:59 2022 +0200 dnsforward: imp code commit f63f6a9d65a96960ae2c06aeca2b32aef70d8f63 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue Apr 26 11:34:23 2022 +0200 dnsforward: ddr support commit e64ffcdac8f9428e4c93a6dc99cc3f1bb090af35 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue Apr 26 11:22:20 2022 +0200 dnsforward: ddr support commit 297460946bb1765137c7c3fe3e298cd574635287 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue Apr 26 11:08:59 2022 +0200 dnsforward: imp code commit 61b4e2e0e06e212c31b7a9d1b09fab392ae6dbc4 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Apr 25 14:39:34 2022 +0200 dnsforward: ddr support commit 7c2787e12eb67a02b41cbb4fe36a12671259f9c9 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Apr 25 11:41:42 2022 +0200 all: docs commit 29c2c872843f6d006e6a98144a52e23a4cbe7be9 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Apr 25 11:26:07 2022 +0200 dnsforward: ddr support commit 2d4ba0c4ce4fbbf3d99da8dd92349da2ec9cff13 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Apr 25 11:03:34 2022 +0200 dnsforward: ddr support commit 0efb5b5cd55bcba3dfae35e80209277f0643a87e Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Sun Apr 24 13:07:25 2022 +0200 dnsforward: imp code commit 884381ef04029d5d743834555cb6601d891c2d25 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Sun Apr 24 12:56:41 2022 +0200 dnsforward: imp code commit 41231f24e83a9690d36546e83fd61ddd709050ed Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri Apr 22 16:05:47 2022 +0200 dnsforward: ddr support commit 9d9da3f479efa5d5609f9b1e6b0d1a93fc253b9f Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri Apr 22 13:46:29 2022 +0200 all: ddr support commit b225363df143d599e9acbf1a6b0bf6d00044dd47 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri Apr 22 13:38:27 2022 +0200 dnsforward: imp code ... and 10 more commits
This commit is contained in:
parent
ed449c6186
commit
c4ff80fd3a
5 changed files with 229 additions and 2 deletions
|
@ -23,6 +23,8 @@ and this project adheres to
|
||||||
|
|
||||||
### Added
|
### 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
|
- The ability to control each source of runtime clients separately via
|
||||||
`clients.runtime_sources` configuration object ([#3020]).
|
`clients.runtime_sources` configuration object ([#3020]).
|
||||||
- The ability to customize the set of networks that are considered private
|
- 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
|
[#4276]: https://github.com/AdguardTeam/AdGuardHome/issues/4276
|
||||||
[#4499]: https://github.com/AdguardTeam/AdGuardHome/issues/4499
|
[#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
|
[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/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -122,6 +122,7 @@ type FilteringConfig struct {
|
||||||
EnableDNSSEC bool `yaml:"enable_dnssec"` // Set AD flag in outcoming DNS request
|
EnableDNSSEC bool `yaml:"enable_dnssec"` // Set AD flag in outcoming DNS request
|
||||||
EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option
|
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
|
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
|
// IpsetList is the ipset configuration that allows AdGuard Home to add
|
||||||
// IP addresses of the specified domain names to an ipset list. Syntax:
|
// IP addresses of the specified domain names to an ipset list. Syntax:
|
||||||
|
@ -151,7 +152,7 @@ type TLSConfig struct {
|
||||||
PrivateKeyData []byte `yaml:"-" json:"-"`
|
PrivateKeyData []byte `yaml:"-" json:"-"`
|
||||||
|
|
||||||
// ServerName is the hostname of the server. Currently, it is only being
|
// 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:"-"`
|
ServerName string `yaml:"-" json:"-"`
|
||||||
|
|
||||||
cert tls.Certificate
|
cert tls.Certificate
|
||||||
|
|
|
@ -76,6 +76,10 @@ const (
|
||||||
resultCodeError
|
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
|
// handleDNSRequest filters the incoming DNS requests and writes them to the query log
|
||||||
func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
||||||
ctx := &dnsContext{
|
ctx := &dnsContext{
|
||||||
|
@ -94,6 +98,7 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
||||||
mods := []modProcessFunc{
|
mods := []modProcessFunc{
|
||||||
s.processRecursion,
|
s.processRecursion,
|
||||||
s.processInitial,
|
s.processInitial,
|
||||||
|
s.processDDRQuery,
|
||||||
s.processDetermineLocal,
|
s.processDetermineLocal,
|
||||||
s.processInternalHosts,
|
s.processInternalHosts,
|
||||||
s.processRestrictLocal,
|
s.processRestrictLocal,
|
||||||
|
@ -241,6 +246,77 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
|
||||||
s.setTableIPToHost(ipToHost)
|
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
|
// processDetermineLocal determines if the client's IP address is from
|
||||||
// locally-served network and saves the result into the context.
|
// locally-served network and saves the result into the context.
|
||||||
func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
|
func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
|
||||||
|
|
|
@ -14,6 +14,152 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"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) {
|
func TestServer_ProcessDetermineLocal(t *testing.T) {
|
||||||
s := &Server{
|
s := &Server{
|
||||||
privateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
privateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
||||||
|
|
|
@ -187,6 +187,7 @@ var config = &configuration{
|
||||||
Ratelimit: 20,
|
Ratelimit: 20,
|
||||||
RefuseAny: true,
|
RefuseAny: true,
|
||||||
AllServers: false,
|
AllServers: false,
|
||||||
|
HandleDDR: true,
|
||||||
FastestTimeout: timeutil.Duration{
|
FastestTimeout: timeutil.Duration{
|
||||||
Duration: fastip.DefaultPingWaitTimeout,
|
Duration: fastip.DefaultPingWaitTimeout,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue