From 012e5beb51daff6eee9a10c897880505da4a5e92 Mon Sep 17 00:00:00 2001 From: Stanislav Chzhen Date: Wed, 1 Mar 2023 16:16:16 +0300 Subject: [PATCH] Pull request 1744: 1472-edns-custom-ip Merge in DNS/adguard-home from 1472-edns-custom-ip to master Updates #1472. Squashed commit of the following: commit 07460c3adf7747fd9ec1b4a3d04fb459dec44280 Merge: 65455430 ae653f16 Author: Stanislav Chzhen Date: Wed Mar 1 15:38:46 2023 +0300 Merge branch 'master' into 1472-edns-custom-ip commit 65455430993e4a62c49e1f45def909b0a135af3b Author: Stanislav Chzhen Date: Wed Mar 1 15:37:17 2023 +0300 dnsforward: add todo commit e1978ad4b6051f29185ef32973d20bc70f2a6634 Merge: 6cd98f42 bb226434 Author: Stanislav Chzhen Date: Wed Mar 1 11:32:23 2023 +0300 Merge branch 'master' into 1472-edns-custom-ip commit 6cd98f4235b1b52d443c1950f2516af3cc4fb258 Author: Stanislav Chzhen Date: Wed Mar 1 11:31:16 2023 +0300 all: fix chlog; fix field alignment commit defdec623919c23ab446324828d08839469669e1 Merge: 1130ebd5 a772212d Author: Stanislav Chzhen Date: Tue Feb 28 12:17:23 2023 +0300 Merge branch 'master' into 1472-edns-custom-ip commit 1130ebd509bf4f7ec25fbb53717576e273dbfff2 Author: Stanislav Chzhen Date: Tue Feb 28 12:13:30 2023 +0300 all: add use_custom field commit ec0cdc7af0f96f761ed85516bbcae2567fd6f7d2 Author: Stanislav Chzhen Date: Mon Feb 27 13:59:13 2023 +0300 all: fix chlog; imp code commit f8450cfcd6054f32d6ea0a5e26c551fe153a0b21 Author: Stanislav Chzhen Date: Mon Feb 27 11:28:16 2023 +0300 dnsforward: fix fmt commit 54a344e5bb17aae7ca213ed66b85f06ef6585316 Author: Stanislav Chzhen Date: Mon Feb 27 11:11:52 2023 +0300 all: fix chlog; add test case commit 47b5476f6621c6ea31aa496d4113445a8e8bceb4 Merge: 8724f374 304f2ba2 Author: Stanislav Chzhen Date: Wed Feb 22 16:33:07 2023 +0300 Merge branch 'master' into 1472-edns-custom-ip commit 8724f3745ccc29849a4001f79b055c7ebeb19106 Author: Stanislav Chzhen Date: Wed Feb 22 16:31:40 2023 +0300 all: fix comments commit d2b1528ba333e7669795a3fb80355ff7d90cf4f5 Merge: 7898c23a 76a513cd Author: Stanislav Chzhen Date: Wed Feb 22 11:53:25 2023 +0300 Merge branch 'master' into 1472-edns-custom-ip commit 7898c23ab991bc516bcc2f41e47bed15582fd962 Author: Stanislav Chzhen Date: Wed Feb 22 11:52:37 2023 +0300 all: upd chlog commit 8763261dcb4187a93104955e7cb440965e2b6739 Merge: d28394b3 ff9b24ad Author: Stanislav Chzhen Date: Tue Feb 21 17:12:03 2023 +0300 Merge branch 'master' into 1472-edns-custom-ip commit d28394b3c980b10f28c6c38ce35f368edb11d314 Author: Stanislav Chzhen Date: Tue Feb 21 17:11:29 2023 +0300 home: fix default value commit 1a5da3f267706baa83eebe1923ea1b0b4e79fd6c Author: Stanislav Chzhen Date: Tue Feb 21 13:37:04 2023 +0300 all: add custom ip for edns --- CHANGELOG.md | 36 ++++++ internal/dnsforward/config.go | 151 ++++++++++++++++++------- internal/dnsforward/dns64_test.go | 3 + internal/dnsforward/dns_test.go | 8 ++ internal/dnsforward/dnsforward_test.go | 57 +++++++++- internal/dnsforward/filter_test.go | 3 + internal/dnsforward/http.go | 4 +- internal/dnsforward/http_test.go | 10 +- internal/home/upgrade.go | 46 +++++++- internal/home/upgrade_test.go | 61 ++++++++++ 10 files changed, 333 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8447e073..7107748f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,9 +25,43 @@ NOTE: Add new changes BELOW THIS COMMENT. ### Added +- The ability to set custom IP for EDNS Client Subnet by using the new + `dns.edns_client_subnet.use_custom` and `dns.edns_client_subnet.custom_ip` + fields ([#1472]). The UI changes are coming in the upcoming releases. - The ability to use `dnstype` rules in the disallowed domains list ([#5468]). This allows dropping requests based on their question types. +### Changed + +#### Configuration Changes + +In this release, the schema version has changed from 16 to 17. + +- Property `edns_client_subnet`, which in schema versions 16 and earlier used + to be a part of the `dns` object, is now part of the `dns.edns_client_subnet` + object: + + ```yaml + # BEFORE: + 'dns': + # … + 'edns_client_subnet': false + + # AFTER: + 'dns': + # … + 'edns_client_subnet': + 'enabled': false + 'use_custom': false + 'custom_ip': '' + ``` + + To rollback this change, move the value of `dns.edns_client_subnet.enabled` + into the `dns.edns_client_subnet`, remove the fields + `dns.edns_client_subnet.enabled`, `dns.edns_client_subnet.use_custom`, + `dns.edns_client_subnet.custom_ip`, and change the `schema_version` back to + `16`. + ### Fixed - Various dark theme bugs ([#5439], [#5441], [#5442], [#5515]). @@ -37,6 +71,7 @@ NOTE: Add new changes BELOW THIS COMMENT. been relaxed to meet those from [RFC 3696][rfc3696] ([#4884]). - Failing service installation via script on FreeBSD ([#5431]). +[#1472]: https://github.com/AdguardTeam/AdGuardHome/issues/1472 [#4884]: https://github.com/AdguardTeam/AdGuardHome/issues/4884 [#5270]: https://github.com/AdguardTeam/AdGuardHome/issues/5270 [#5373]: https://github.com/AdguardTeam/AdGuardHome/issues/5373 @@ -129,6 +164,7 @@ In this release, the schema version has changed from 14 to 16. 'file_enabled': true 'interval': '2160h' 'size_memory': 1000 + 'ignored': [] ``` To rollback this change, rename and move properties back into the `dns` diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index eeeb4c40..f18e7513 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -53,7 +53,6 @@ const ( // The zero FilteringConfig is empty and ready for use. type FilteringConfig struct { // Callbacks for other modules - // -- // FilterHandler is an optional additional filtering callback. FilterHandler func(clientAddr net.IP, clientID string, settings *filtering.Settings) `yaml:"-"` @@ -64,50 +63,82 @@ type FilteringConfig struct { GetCustomUpstreamByClient func(id string) (conf *proxy.UpstreamConfig, err error) `yaml:"-"` // Protection configuration - // -- - ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of filtering features - BlockingMode BlockingMode `yaml:"blocking_mode"` // mode how to answer filtered requests - BlockingIPv4 net.IP `yaml:"blocking_ipv4"` // IP address to be returned for a blocked A request - BlockingIPv6 net.IP `yaml:"blocking_ipv6"` // IP address to be returned for a blocked AAAA request - BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` // if 0, then default is used (3600) + // ProtectionEnabled defines whether or not use any of filtering features. + ProtectionEnabled bool `yaml:"protection_enabled"` - // IP (or domain name) which is used to respond to DNS requests blocked by parental control or safe-browsing - ParentalBlockHost string `yaml:"parental_block_host"` + // BlockingMode defines the way how blocked responses are constructed. + BlockingMode BlockingMode `yaml:"blocking_mode"` + + // BlockingIPv4 is the IP address to be returned for a blocked A request. + BlockingIPv4 net.IP `yaml:"blocking_ipv4"` + + // BlockingIPv6 is the IP address to be returned for a blocked AAAA + // request. + BlockingIPv6 net.IP `yaml:"blocking_ipv6"` + + // BlockedResponseTTL is the time-to-live value for blocked responses. If + // 0, then default value is used (3600). + BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` + + // ParentalBlockHost is the IP (or domain name) which is used to respond to + // DNS requests blocked by parental control. + ParentalBlockHost string `yaml:"parental_block_host"` + + // SafeBrowsingBlockHost is the IP (or domain name) which is used to + // respond to DNS requests blocked by safe-browsing. SafeBrowsingBlockHost string `yaml:"safebrowsing_block_host"` // Anti-DNS amplification - // -- - Ratelimit uint32 `yaml:"ratelimit"` // max number of requests per second from a given IP (0 to disable) - RatelimitWhitelist []string `yaml:"ratelimit_whitelist"` // a list of whitelisted client IP addresses - RefuseAny bool `yaml:"refuse_any"` // if true, refuse ANY requests + // Ratelimit is the maximum number of requests per second from a given IP + // (0 to disable). + Ratelimit uint32 `yaml:"ratelimit"` + + // RatelimitWhitelist is the list of whitelisted client IP addresses. + RatelimitWhitelist []string `yaml:"ratelimit_whitelist"` + + // RefuseAny, if true, refuse ANY requests. + RefuseAny bool `yaml:"refuse_any"` // Upstream DNS servers configuration - // -- - UpstreamDNS []string `yaml:"upstream_dns"` - UpstreamDNSFileName string `yaml:"upstream_dns_file"` - BootstrapDNS []string `yaml:"bootstrap_dns"` // a list of bootstrap DNS for DoH and DoT (plain DNS only) - AllServers bool `yaml:"all_servers"` // if true, parallel queries to all configured upstream servers are enabled - FastestAddr bool `yaml:"fastest_addr"` // use Fastest Address algorithm + // UpstreamDNS is the list of upstream DNS servers. + UpstreamDNS []string `yaml:"upstream_dns"` + + // UpstreamDNSFileName, if set, points to the file which contains upstream + // DNS servers. + UpstreamDNSFileName string `yaml:"upstream_dns_file"` + + // BootstrapDNS is the list of bootstrap DNS servers for DoH and DoT + // resolvers (plain DNS only). + BootstrapDNS []string `yaml:"bootstrap_dns"` + + // AllServers, if true, parallel queries to all configured upstream servers + // are enabled. + AllServers bool `yaml:"all_servers"` + + // FastestAddr, if true, use Fastest Address algorithm. + FastestAddr bool `yaml:"fastest_addr"` + // FastestTimeout replaces the default timeout for dialing IP addresses // when FastestAddr is true. FastestTimeout timeutil.Duration `yaml:"fastest_timeout"` // Access settings - // -- - // AllowedClients is the slice of IP addresses, CIDR networks, and ClientIDs - // of allowed clients. If not empty, only these clients are allowed, and - // [FilteringConfig.DisallowedClients] are ignored. + // AllowedClients is the slice of IP addresses, CIDR networks, and + // ClientIDs of allowed clients. If not empty, only these clients are + // allowed, and [FilteringConfig.DisallowedClients] are ignored. AllowedClients []string `yaml:"allowed_clients"` // DisallowedClients is the slice of IP addresses, CIDR networks, and // ClientIDs of disallowed clients. DisallowedClients []string `yaml:"disallowed_clients"` - BlockedHosts []string `yaml:"blocked_hosts"` // hosts that should be blocked + // BlockedHosts is the list of hosts that should be blocked. + BlockedHosts []string `yaml:"blocked_hosts"` + // TrustedProxies is the list of IP addresses and CIDR networks to detect // proxy servers addresses the DoH requests from which should be handled. // The value of nil or an empty slice for this field makes Proxy not trust @@ -115,26 +146,46 @@ type FilteringConfig struct { TrustedProxies []string `yaml:"trusted_proxies"` // DNS cache settings - // -- - CacheSize uint32 `yaml:"cache_size"` // DNS cache size (in bytes) - CacheMinTTL uint32 `yaml:"cache_ttl_min"` // override TTL value (minimum) received from upstream server - CacheMaxTTL uint32 `yaml:"cache_ttl_max"` // override TTL value (maximum) received from upstream server + // CacheSize is the DNS cache size (in bytes). + CacheSize uint32 `yaml:"cache_size"` + + // CacheMinTTL is the override TTL value (minimum) received from upstream + // server. + CacheMinTTL uint32 `yaml:"cache_ttl_min"` + + // CacheMaxTTL is the override TTL value (maximum) received from upstream + // server. + CacheMaxTTL uint32 `yaml:"cache_ttl_max"` + // CacheOptimistic defines if optimistic cache mechanism should be used. CacheOptimistic bool `yaml:"cache_optimistic"` // Other settings - // -- - BogusNXDomain []string `yaml:"bogus_nxdomain"` // transform responses with these IP addresses to NXDOMAIN - AAAADisabled bool `yaml:"aaaa_disabled"` // Respond with an empty answer to all AAAA requests - 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 + // BogusNXDomain is the list of IP addresses, responses with them will be + // transformed to NXDOMAIN. + BogusNXDomain []string `yaml:"bogus_nxdomain"` - // IpsetList is the ipset configuration that allows AdGuard Home to add - // IP addresses of the specified domain names to an ipset list. Syntax: + // AAAADisabled, if true, respond with an empty answer to all AAAA + // requests. + AAAADisabled bool `yaml:"aaaa_disabled"` + + // EnableDNSSEC, if true, set AD flag in outcoming DNS request. + EnableDNSSEC bool `yaml:"enable_dnssec"` + + // EDNSClientSubnet is the settings list for EDNS Client Subnet. + EDNSClientSubnet *EDNSClientSubnet `yaml:"edns_client_subnet"` + + // MaxGoroutines is the max number of parallel goroutines for processing + // incoming requests. + MaxGoroutines uint32 `yaml:"max_goroutines"` + + // HandleDDR, if true, handle DDR requests + HandleDDR bool `yaml:"handle_ddr"` + + // IpsetList is the ipset configuration that allows AdGuard Home to add IP + // addresses of the specified domain names to an ipset list. Syntax: // // DOMAIN[,DOMAIN].../IPSET_NAME // @@ -146,6 +197,18 @@ type FilteringConfig struct { IpsetListFileName string `yaml:"ipset_file"` } +// EDNSClientSubnet is the settings list for EDNS Client Subnet. +type EDNSClientSubnet struct { + // CustomIP for EDNS Client Subnet. + CustomIP string `yaml:"custom_ip"` + + // Enabled defines if EDNS Client Subnet is enabled. + Enabled bool `yaml:"enabled"` + + // UseCustom defines if CustomIP should be used. + UseCustom bool `yaml:"use_custom"` +} + // TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS type TLSConfig struct { cert tls.Certificate @@ -270,12 +333,24 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) { UpstreamConfig: srvConf.UpstreamConfig, BeforeRequestHandler: s.beforeRequestHandler, RequestHandler: s.handleDNSRequest, - EnableEDNSClientSubnet: srvConf.EnableEDNSClientSubnet, + EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled, MaxGoroutines: int(srvConf.MaxGoroutines), UseDNS64: srvConf.UseDNS64, DNS64Prefs: srvConf.DNS64Prefixes, } + if srvConf.EDNSClientSubnet.UseCustom { + // TODO(s.chzhen): Add wrapper around netip.Addr. + var ip net.IP + ip, err = netutil.ParseIP(srvConf.EDNSClientSubnet.CustomIP) + if err != nil { + return conf, fmt.Errorf("edns: %w", err) + } + + // TODO(s.chzhen): Use netip.Addr instead of net.IP inside dnsproxy. + conf.EDNSAddr = ip + } + if srvConf.CacheSize != 0 { conf.CacheEnabled = true conf.CacheSizeBytes = int(srvConf.CacheSize) diff --git a/internal/dnsforward/dns64_test.go b/internal/dnsforward/dns64_test.go index f1df9500..85a07fc1 100644 --- a/internal/dnsforward/dns64_test.go +++ b/internal/dnsforward/dns64_test.go @@ -287,6 +287,9 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, UseDNS64: true, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, localUps) t.Run(tc.name, func(t *testing.T) { diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go index d07c30dc..02f1eb61 100644 --- a/internal/dnsforward/dns_test.go +++ b/internal/dnsforward/dns_test.go @@ -467,6 +467,11 @@ func TestServer_ProcessRestrictLocal(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + // TODO(s.chzhen): Add tests where EDNSClientSubnet.Enabled is true. + // Improve FilteringConfig declaration for tests. + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, ups) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ups} startDeferStop(t, s) @@ -539,6 +544,9 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) { ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) { return aghalg.Coalesce( diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index 6941c2d2..d0a0ada7 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -156,6 +156,9 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte) s = createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, nil) tlsConf.CertificateChainData, tlsConf.PrivateKeyData = certPem, keyPem @@ -267,6 +270,9 @@ func TestServer(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, nil) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()} startDeferStop(t, s) @@ -305,7 +311,8 @@ func TestServer_timeout(t *testing.T) { srvConf := &ServerConfig{ UpstreamTimeout: timeout, FilteringConfig: FilteringConfig{ - BlockingMode: BlockingModeDefault, + BlockingMode: BlockingModeDefault, + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, } @@ -323,6 +330,9 @@ func TestServer_timeout(t *testing.T) { require.NoError(t, err) s.conf.FilteringConfig.BlockingMode = BlockingModeDefault + s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{ + Enabled: false, + } err = s.Prepare(&s.conf) require.NoError(t, err) @@ -334,6 +344,9 @@ func TestServerWithProtectionDisabled(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, nil) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()} startDeferStop(t, s) @@ -438,6 +451,9 @@ func TestSafeSearch(t *testing.T) { TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, filterConf, forwardConf, nil) @@ -493,6 +509,11 @@ func TestInvalidRequest(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, + }, }, nil) startDeferStop(t, s) @@ -519,6 +540,9 @@ func TestBlockedRequest(t *testing.T) { FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: BlockingModeDefault, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, &filtering.Config{}, forwardConf, nil) @@ -544,6 +568,9 @@ func TestServerCustomClientUpstream(t *testing.T) { TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, &filtering.Config{}, forwardConf, nil) @@ -592,6 +619,11 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, + }, }, nil) testUpstm := &aghtest.Upstream{ CName: testCNAMEs, @@ -622,6 +654,9 @@ func TestBlockCNAME(t *testing.T) { FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: BlockingModeDefault, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, &filtering.Config{}, forwardConf, nil) @@ -691,6 +726,9 @@ func TestClientRulesForCNAMEMatching(t *testing.T) { FilterHandler: func(_ net.IP, _ string, settings *filtering.Settings) { settings.FilteringEnabled = false }, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, &filtering.Config{}, forwardConf, nil) @@ -732,6 +770,9 @@ func TestNullBlockedRequest(t *testing.T) { FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: BlockingModeNullIP, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, &filtering.Config{}, forwardConf, nil) @@ -784,6 +825,9 @@ func TestBlockedCustomIP(t *testing.T) { BlockingMode: BlockingModeCustomIP, BlockingIPv4: nil, UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } @@ -832,6 +876,9 @@ func TestBlockedByHosts(t *testing.T) { FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: BlockingModeDefault, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } @@ -865,6 +912,9 @@ func TestBlockedBySafeBrowsing(t *testing.T) { FilteringConfig: FilteringConfig{ SafeBrowsingBlockHost: ans4.String(), ProtectionEnabled: true, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } s := createTestServer(t, filterConf, forwardConf, nil) @@ -919,6 +969,9 @@ func TestRewrite(t *testing.T) { ProtectionEnabled: true, BlockingMode: BlockingModeDefault, UpstreamDNS: []string{"8.8.8.8:53"}, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, })) @@ -1033,6 +1086,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) { s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.FilteringConfig.ProtectionEnabled = true s.conf.FilteringConfig.BlockingMode = BlockingModeDefault + s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false} err = s.Prepare(&s.conf) require.NoError(t, err) @@ -1108,6 +1162,7 @@ func TestPTRResponseFromHosts(t *testing.T) { s.conf.TCPListenAddrs = []*net.TCPAddr{{}} s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.FilteringConfig.BlockingMode = BlockingModeDefault + s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false} err = s.Prepare(&s.conf) require.NoError(t, err) diff --git a/internal/dnsforward/filter_test.go b/internal/dnsforward/filter_test.go index 7fa0985a..3fbe58cc 100644 --- a/internal/dnsforward/filter_test.go +++ b/internal/dnsforward/filter_test.go @@ -29,6 +29,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) { FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: BlockingModeDefault, + EDNSClientSubnet: &EDNSClientSubnet{ + Enabled: false, + }, }, } filters := []filtering.Filter{{ diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index 18c4e82e..a876a411 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -57,7 +57,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) { blockingIPv4 := s.conf.BlockingIPv4 blockingIPv6 := s.conf.BlockingIPv6 ratelimit := s.conf.Ratelimit - enableEDNSClientSubnet := s.conf.EnableEDNSClientSubnet + enableEDNSClientSubnet := s.conf.EDNSClientSubnet.Enabled enableDNSSEC := s.conf.EnableDNSSEC aaaaDisabled := s.conf.AAAADisabled cacheSize := s.conf.CacheSize @@ -280,7 +280,7 @@ func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) { setIfNotNil(&s.conf.LocalPTRResolvers, dc.LocalPTRUpstreams), setIfNotNil(&s.conf.UpstreamDNSFileName, dc.UpstreamsFile), setIfNotNil(&s.conf.BootstrapDNS, dc.Bootstraps), - setIfNotNil(&s.conf.EnableEDNSClientSubnet, dc.EDNSCSEnabled), + setIfNotNil(&s.conf.EDNSClientSubnet.Enabled, dc.EDNSCSEnabled), setIfNotNil(&s.conf.CacheSize, dc.CacheSize), setIfNotNil(&s.conf.CacheMinTTL, dc.CacheMinTTL), setIfNotNil(&s.conf.CacheMaxTTL, dc.CacheMaxTTL), diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go index db3356dc..9d48151d 100644 --- a/internal/dnsforward/http_test.go +++ b/internal/dnsforward/http_test.go @@ -69,6 +69,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) { ProtectionEnabled: true, BlockingMode: BlockingModeDefault, UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ConfigModified: func() {}, } @@ -144,6 +145,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { ProtectionEnabled: true, BlockingMode: BlockingModeDefault, UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ConfigModified: func() {}, } @@ -227,7 +229,10 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { require.True(t, ok) t.Run(tc.name, func(t *testing.T) { - t.Cleanup(func() { s.conf = defaultConf }) + t.Cleanup(func() { + s.conf = defaultConf + s.conf.FilteringConfig.EDNSClientSubnet.Enabled = false + }) rBody := io.NopCloser(bytes.NewReader(caseData.Req)) var r *http.Request @@ -443,6 +448,9 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, UpstreamTimeout: upsTimeout, + FilteringConfig: FilteringConfig{ + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + }, }, nil) startDeferStop(t, srv) diff --git a/internal/home/upgrade.go b/internal/home/upgrade.go index 169633fe..b8f0b407 100644 --- a/internal/home/upgrade.go +++ b/internal/home/upgrade.go @@ -22,7 +22,7 @@ import ( ) // currentSchemaVersion is the current schema version. -const currentSchemaVersion = 16 +const currentSchemaVersion = 17 // These aliases are provided for convenience. type ( @@ -89,6 +89,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) { upgradeSchema13to14, upgradeSchema14to15, upgradeSchema15to16, + upgradeSchema16to17, } n := 0 @@ -892,19 +893,56 @@ func upgradeSchema15to16(diskConf yobj) (err error) { "ignored": []any{}, } - k := "statistics_interval" - v, has := dns[k] + const field = "statistics_interval" + v, has := dns[field] if has { stats["enabled"] = v != 0 stats["interval"] = v } - delete(dns, k) + delete(dns, field) diskConf["statistics"] = stats return nil } +// upgradeSchema16to17 performs the following changes: +// +// # BEFORE: +// 'dns': +// 'edns_client_subnet': false +// +// # AFTER: +// 'dns': +// 'edns_client_subnet': +// 'enabled': false +// 'use_custom': false +// 'custom_ip': "" +func upgradeSchema16to17(diskConf yobj) (err error) { + log.Printf("Upgrade yaml: 16 to 17") + diskConf["schema_version"] = 17 + + dnsVal, ok := diskConf["dns"] + if !ok { + return nil + } + + dns, ok := dnsVal.(yobj) + if !ok { + return fmt.Errorf("unexpected type of dns: %T", dnsVal) + } + + const field = "edns_client_subnet" + + dns[field] = map[string]any{ + "enabled": dns[field] == true, + "use_custom": false, + "custom_ip": "", + } + + return nil +} + // TODO(a.garipov): Replace with log.Output when we port it to our logging // package. func funcName() string { diff --git a/internal/home/upgrade_test.go b/internal/home/upgrade_test.go index 4e442fa0..23fd6393 100644 --- a/internal/home/upgrade_test.go +++ b/internal/home/upgrade_test.go @@ -747,3 +747,64 @@ func TestUpgradeSchema15to16(t *testing.T) { }) } } + +func TestUpgradeSchema16to17(t *testing.T) { + const newSchemaVer = 17 + + defaultWantObj := yobj{ + "dns": map[string]any{ + "edns_client_subnet": map[string]any{ + "enabled": false, + "use_custom": false, + "custom_ip": "", + }, + }, + "schema_version": newSchemaVer, + } + + testCases := []struct { + in yobj + want yobj + name string + }{{ + in: yobj{ + "dns": map[string]any{ + "edns_client_subnet": false, + }, + }, + want: defaultWantObj, + name: "basic", + }, { + in: yobj{ + "dns": map[string]any{}, + }, + want: defaultWantObj, + name: "default_values", + }, { + in: yobj{ + "dns": map[string]any{ + "edns_client_subnet": true, + }, + }, + want: yobj{ + "dns": map[string]any{ + "edns_client_subnet": map[string]any{ + "enabled": true, + "use_custom": false, + "custom_ip": "", + }, + }, + "schema_version": newSchemaVer, + }, + name: "is_true", + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := upgradeSchema16to17(tc.in) + require.NoError(t, err) + + assert.Equal(t, tc.want, tc.in) + }) + } +}