diff --git a/CHANGELOG.md b/CHANGELOG.md index 4959f325..a0974e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,77 @@ NOTE: Add new changes BELOW THIS COMMENT. #### Configuration Changes -In this release, the schema version has changed from 24 to 25. +In this release, the schema version has changed from 24 to 26. + +- Filtering-related settings have been moved from `dns` section of the YAML + configuration file to the new section `filtering`: + + ```yaml + # BEFORE: + 'dns': + 'filtering_enabled': true + 'filters_update_interval': 24 + 'parental_enabled': false + 'safebrowsing_enabled': false + 'safebrowsing_cache_size': 1048576 + 'safesearch_cache_size': 1048576 + 'parental_cache_size': 1048576 + 'safe_search': + 'enabled': false + 'bing': true + 'duckduckgo': true + 'google': true + 'pixabay': true + 'yandex': true + 'youtube': true + 'rewrites': [] + 'blocked_services': + 'schedule': + 'time_zone': 'Local' + 'ids': [] + 'protection_enabled': true, + 'blocking_mode': 'custom_ip', + 'blocking_ipv4': '1.2.3.4', + 'blocking_ipv6': '1:2:3::4', + 'blocked_response_ttl': 10, + 'protection_disabled_until': 'null', + 'parental_block_host': 'p.dns.adguard.com', + 'safebrowsing_block_host': 's.dns.adguard.com' + + # AFTER: + 'filtering': + 'filtering_enabled': true + 'filters_update_interval': 24 + 'parental_enabled': false + 'safebrowsing_enabled': false + 'safebrowsing_cache_size': 1048576 + 'safesearch_cache_size': 1048576 + 'parental_cache_size': 1048576 + 'safe_search': + 'enabled': false + 'bing': true + 'duckduckgo': true + 'google': true + 'pixabay': true + 'yandex': true + 'youtube': true + 'rewrites': [] + 'blocked_services': + 'schedule': + 'time_zone': 'Local' + 'ids': [] + 'protection_enabled': true, + 'blocking_mode': 'custom_ip', + 'blocking_ipv4': '1.2.3.4', + 'blocking_ipv6': '1:2:3::4', + 'blocked_response_ttl': 10, + 'protection_disabled_until': 'null', + 'parental_block_host': 'p.dns.adguard.com', + 'safebrowsing_block_host': 's.dns.adguard.com', + ``` + + To rollback this change, remove the new object `filtering`, set back filtering + properties in `dns` section, and change the `schema_version` back to `25`. - Property `debug_pprof` which used to setup profiling HTTP handler, is now moved to the new `pprof` object under `http` section. The new object contains diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index 331310dc..10ca71bb 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -25,33 +25,9 @@ import ( "golang.org/x/exp/slices" ) -// BlockingMode is an enum of all allowed blocking modes. -type BlockingMode string - -// Allowed blocking modes. -const ( - // BlockingModeCustomIP means respond with a custom IP address. - BlockingModeCustomIP BlockingMode = "custom_ip" - - // BlockingModeDefault is the same as BlockingModeNullIP for - // Adblock-style rules, but responds with the IP address specified in - // the rule when blocked by an `/etc/hosts`-style rule. - BlockingModeDefault BlockingMode = "default" - - // BlockingModeNullIP means respond with a zero IP address: "0.0.0.0" - // for A requests and "::" for AAAA ones. - BlockingModeNullIP BlockingMode = "null_ip" - - // BlockingModeNXDOMAIN means respond with the NXDOMAIN code. - BlockingModeNXDOMAIN BlockingMode = "nxdomain" - - // BlockingModeREFUSED means respond with the REFUSED code. - BlockingModeREFUSED BlockingMode = "refused" -) - -// FilteringConfig represents the DNS filtering configuration of AdGuard Home -// The zero FilteringConfig is empty and ready for use. -type FilteringConfig struct { +// Config represents the DNS filtering configuration of AdGuard Home. The zero +// Config is empty and ready for use. +type Config struct { // Callbacks for other modules // FilterHandler is an optional additional filtering callback. @@ -62,37 +38,6 @@ type FilteringConfig struct { // nil if there are no custom upstreams for the client. GetCustomUpstreamByClient func(id string) (conf *proxy.UpstreamConfig, err error) `yaml:"-"` - // Protection configuration - - // ProtectionEnabled defines whether or not use any of filtering features. - ProtectionEnabled bool `yaml:"protection_enabled"` - - // 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 netip.Addr `yaml:"blocking_ipv4"` - - // BlockingIPv6 is the IP address to be returned for a blocked AAAA - // request. - BlockingIPv6 netip.Addr `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"` - - // ProtectionDisabledUntil is the timestamp until when the protection is - // disabled. - ProtectionDisabledUntil *time.Time `yaml:"protection_disabled_until"` - - // 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 is the maximum number of requests per second from a given IP @@ -137,7 +82,7 @@ type FilteringConfig struct { // 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. + // allowed, and [Config.DisallowedClients] are ignored. AllowedClients []string `yaml:"allowed_clients"` // DisallowedClients is the slice of IP addresses, CIDR networks, and @@ -283,7 +228,7 @@ type ServerConfig struct { // Remove that. AddrProcConf *client.DefaultAddrProcConfig - FilteringConfig + Config TLSConfig DNSCryptConfig TLSAllowUnencryptedDoH bool @@ -324,13 +269,6 @@ type ServerConfig struct { UseHTTP3Upstreams bool } -// if any of ServerConfig values are zero, then default values from below are used -var defaultValues = ServerConfig{ - UDPListenAddrs: []*net.UDPAddr{{Port: 53}}, - TCPListenAddrs: []*net.TCPAddr{{Port: 53}}, - FilteringConfig: FilteringConfig{BlockedResponseTTL: 3600}, -} - // createProxyConfig creates and validates configuration for the main proxy. func (s *Server) createProxyConfig() (conf proxy.Config, err error) { srvConf := s.conf @@ -403,10 +341,7 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) { return conf, nil } -const ( - defaultSafeBrowsingBlockHost = "standard-block.dns.adguard.com" - defaultParentalBlockHost = "family-block.dns.adguard.com" -) +const defaultBlockedResponseTTL = 3600 // initDefaultSettings initializes default settings if nothing // is configured @@ -419,20 +354,12 @@ func (s *Server) initDefaultSettings() { s.conf.BootstrapDNS = defaultBootstrap } - if s.conf.ParentalBlockHost == "" { - s.conf.ParentalBlockHost = defaultParentalBlockHost - } - - if s.conf.SafeBrowsingBlockHost == "" { - s.conf.SafeBrowsingBlockHost = defaultSafeBrowsingBlockHost - } - if s.conf.UDPListenAddrs == nil { - s.conf.UDPListenAddrs = defaultValues.UDPListenAddrs + s.conf.UDPListenAddrs = defaultUDPListenAddrs } if s.conf.TCPListenAddrs == nil { - s.conf.TCPListenAddrs = defaultValues.TCPListenAddrs + s.conf.TCPListenAddrs = defaultTCPListenAddrs } if len(s.conf.BlockedHosts) == 0 { @@ -565,9 +492,9 @@ func (s *Server) UpdatedProtectionStatus() (enabled bool, disabledUntil *time.Ti s.serverLock.RLock() defer s.serverLock.RUnlock() - disabledUntil = s.conf.ProtectionDisabledUntil + disabledUntil = s.dnsFilter.ProtectionDisabledUntil if disabledUntil == nil { - return s.conf.ProtectionEnabled, nil + return s.dnsFilter.ProtectionEnabled, nil } if time.Now().Before(*disabledUntil) { @@ -599,8 +526,8 @@ func (s *Server) enableProtectionAfterPause() { s.serverLock.Lock() defer s.serverLock.Unlock() - s.conf.ProtectionEnabled = true - s.conf.ProtectionDisabledUntil = nil + s.dnsFilter.ProtectionEnabled = true + s.dnsFilter.ProtectionDisabledUntil = nil log.Info("dns: protection is restarted after pause") } diff --git a/internal/dnsforward/dns64_test.go b/internal/dnsforward/dns64_test.go index 85a07fc1..53a18c4e 100644 --- a/internal/dnsforward/dns64_test.go +++ b/internal/dnsforward/dns64_test.go @@ -283,11 +283,13 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) { // right after stop, due to a data race in [proxy.Proxy.Init] method // when setting an OOB size. As a temporary workaround, recreate the // whole server for each test case. - s := createTestServer(t, &filtering.Config{}, ServerConfig{ + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, UseDNS64: true, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, localUps) diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index f6f1ef1d..941721bd 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -45,6 +45,14 @@ var defaultBootstrap = []string{"9.9.9.10", "149.112.112.10", "2620:fe::10", "26 // Often requested by all kinds of DNS probes var defaultBlockedHosts = []string{"version.bind", "id.server", "hostname.bind"} +var ( + // defaultUDPListenAddrs are the default UDP addresses for the server. + defaultUDPListenAddrs = []*net.UDPAddr{{Port: 53}} + + // defaultTCPListenAddrs are the default TCP addresses for the server. + defaultTCPListenAddrs = []*net.TCPAddr{{Port: 53}} +) + var webRegistered bool // DHCP is an interface for accesing DHCP lease data needed in this package. @@ -255,11 +263,11 @@ func (s *Server) Close() { } // WriteDiskConfig - write configuration -func (s *Server) WriteDiskConfig(c *FilteringConfig) { +func (s *Server) WriteDiskConfig(c *Config) { s.serverLock.RLock() defer s.serverLock.RUnlock() - sc := s.conf.FilteringConfig + sc := s.conf.Config *c = sc c.RatelimitWhitelist = stringutil.CloneSlice(sc.RatelimitWhitelist) c.BootstrapDNS = stringutil.CloneSlice(sc.BootstrapDNS) @@ -534,7 +542,11 @@ func (s *Server) setupLocalResolvers() (err error) { func (s *Server) Prepare(conf *ServerConfig) (err error) { s.conf = *conf - err = validateBlockingMode(s.conf.BlockingMode, s.conf.BlockingIPv4, s.conf.BlockingIPv6) + err = validateBlockingMode( + s.dnsFilter.BlockingMode, + s.dnsFilter.BlockingIPv4, + s.dnsFilter.BlockingIPv6, + ) if err != nil { return fmt.Errorf("checking blocking mode: %w", err) } @@ -645,15 +657,18 @@ func (s *Server) setupAddrProc() { } // validateBlockingMode returns an error if the blocking mode data aren't valid. -func validateBlockingMode(mode BlockingMode, blockingIPv4, blockingIPv6 netip.Addr) (err error) { +func validateBlockingMode( + mode filtering.BlockingMode, + blockingIPv4, blockingIPv6 netip.Addr, +) (err error) { switch mode { case - BlockingModeDefault, - BlockingModeNXDOMAIN, - BlockingModeREFUSED, - BlockingModeNullIP: + filtering.BlockingModeDefault, + filtering.BlockingModeNXDOMAIN, + filtering.BlockingModeREFUSED, + filtering.BlockingModeNullIP: return nil - case BlockingModeCustomIP: + case filtering.BlockingModeCustomIP: if !blockingIPv4.Is4() { return fmt.Errorf("blocking_ipv4 must be valid ipv4 on custom_ip blocking_mode") } else if !blockingIPv6.Is6() { diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index 46d475b2..a438aa91 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -105,8 +105,8 @@ func createTestServer( }) require.NoError(t, err) - if forwardConf.BlockingMode == "" { - forwardConf.BlockingMode = BlockingModeDefault + if s.dnsFilter.BlockingMode == "" { + s.dnsFilter.BlockingMode = filtering.BlockingModeDefault } err = s.Prepare(&forwardConf) @@ -181,7 +181,7 @@ 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{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, nil) @@ -303,10 +303,12 @@ func sendTestMessages(t *testing.T, conn *dns.Conn) { } func TestServer(t *testing.T) { - s := createTestServer(t, &filtering.Config{}, ServerConfig{ + s := createTestServer(t, &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, nil) @@ -344,14 +346,14 @@ func TestServer_timeout(t *testing.T) { t.Run("custom", func(t *testing.T) { srvConf := &ServerConfig{ UpstreamTimeout: testTimeout, - FilteringConfig: FilteringConfig{ - BlockingMode: BlockingModeDefault, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, } s, err := NewServer(DNSCreateParams{DNSFilter: &filtering.DNSFilter{}}) require.NoError(t, err) + s.dnsFilter.BlockingMode = filtering.BlockingModeDefault err = s.Prepare(srvConf) require.NoError(t, err) @@ -363,8 +365,8 @@ func TestServer_timeout(t *testing.T) { s, err := NewServer(DNSCreateParams{DNSFilter: &filtering.DNSFilter{}}) require.NoError(t, err) - s.conf.FilteringConfig.BlockingMode = BlockingModeDefault - s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{ + s.dnsFilter.BlockingMode = filtering.BlockingModeDefault + s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{ Enabled: false, } err = s.Prepare(&s.conf) @@ -378,7 +380,7 @@ func TestServerWithProtectionDisabled(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, nil) @@ -454,9 +456,8 @@ func TestServerRace(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + Config: Config{ + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, }, ConfigModified: func() {}, } @@ -489,6 +490,7 @@ func TestSafeSearch(t *testing.T) { } filterConf := &filtering.Config{ + ProtectionEnabled: true, SafeSearchConf: safeSearchConf, SafeSearchCacheSize: 1000, CacheTime: 30, @@ -505,8 +507,7 @@ func TestSafeSearch(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -566,7 +567,7 @@ func TestInvalidRequest(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -594,15 +595,16 @@ func TestBlockedRequest(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, }, } - s := createTestServer(t, &filtering.Config{}, forwardConf, nil) + s := createTestServer(t, &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, + }, forwardConf, nil) startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) @@ -623,8 +625,7 @@ func TestServerCustomClientUpstream(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -676,7 +677,7 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) { s := createTestServer(t, &filtering.Config{}, ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -686,7 +687,7 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) { CName: testCNAMEs, IPv4: testIPv4, } - s.conf.ProtectionEnabled = false + s.dnsProxy.UpstreamConfig = &proxy.UpstreamConfig{ Upstreams: []upstream.Upstream{testUpstm}, } @@ -708,15 +709,16 @@ func TestBlockCNAME(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, }, } - s := createTestServer(t, &filtering.Config{}, forwardConf, nil) + s := createTestServer(t, &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, + }, forwardConf, nil) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ &aghtest.Upstream{ CName: testCNAMEs, @@ -778,8 +780,7 @@ func TestClientRulesForCNAMEMatching(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, + Config: Config{ FilterHandler: func(_ netip.Addr, _ string, settings *filtering.Settings) { settings.FilteringEnabled = false }, @@ -824,15 +825,16 @@ func TestNullBlockedRequest(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeNullIP, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, }, } - s := createTestServer(t, &filtering.Config{}, forwardConf, nil) + s := createTestServer(t, &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeNullIP, + }, forwardConf, nil) startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) @@ -864,7 +866,12 @@ func TestBlockedCustomIP(t *testing.T) { Data: []byte(rules), }} - f, err := filtering.New(&filtering.Config{}, filters) + f, err := filtering.New(&filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeCustomIP, + BlockingIPv4: netip.Addr{}, + BlockingIPv6: netip.Addr{}, + }, filters) require.NoError(t, err) dhcp := &testDHCP{ @@ -882,12 +889,8 @@ func TestBlockedCustomIP(t *testing.T) { conf := &ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeCustomIP, - BlockingIPv4: netip.Addr{}, - BlockingIPv6: netip.Addr{}, - UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + Config: Config{ + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -898,8 +901,8 @@ func TestBlockedCustomIP(t *testing.T) { err = s.Prepare(conf) assert.Error(t, err) - conf.BlockingIPv4 = netip.AddrFrom4([4]byte{0, 0, 0, 1}) - conf.BlockingIPv6 = netip.MustParseAddr("::1") + s.dnsFilter.BlockingIPv4 = netip.AddrFrom4([4]byte{0, 0, 0, 1}) + s.dnsFilter.BlockingIPv6 = netip.MustParseAddr("::1") err = s.Prepare(conf) require.NoError(t, err) @@ -936,16 +939,17 @@ func TestBlockedByHosts(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, }, } - s := createTestServer(t, &filtering.Config{}, forwardConf, nil) + s := createTestServer(t, &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, + }, forwardConf, nil) startDeferStop(t, s) addr := s.dnsProxy.Addr(proxy.ProtoUDP) @@ -976,15 +980,15 @@ func TestBlockedBySafeBrowsing(t *testing.T) { ans4, _ := aghtest.HostToIPs(hostname) filterConf := &filtering.Config{ - SafeBrowsingEnabled: true, - SafeBrowsingChecker: sbChecker, + ProtectionEnabled: true, + SafeBrowsingEnabled: true, + SafeBrowsingChecker: sbChecker, + SafeBrowsingBlockHost: ans4.String(), } forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - SafeBrowsingBlockHost: ans4.String(), - ProtectionEnabled: true, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -1006,6 +1010,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) { func TestRewrite(t *testing.T) { c := &filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, Rewrites: []*filtering.LegacyRewrite{{ Domain: "test.com", Answer: "1.2.3.4", @@ -1040,10 +1045,8 @@ func TestRewrite(t *testing.T) { assert.NoError(t, s.Prepare(&ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, - UpstreamDNS: []string{"8.8.8.8:53"}, + Config: Config{ + UpstreamDNS: []string{"8.8.8.8:53"}, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -1148,7 +1151,9 @@ func (d *testDHCP) Enabled() (ok bool) { return d.OnEnabled() } func TestPTRResponseFromDHCPLeases(t *testing.T) { const localDomain = "lan" - flt, err := filtering.New(&filtering.Config{}, nil) + flt, err := filtering.New(&filtering.Config{ + BlockingMode: filtering.BlockingModeDefault, + }, nil) require.NoError(t, err) s, err := NewServer(DNSCreateParams{ @@ -1168,9 +1173,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) { s.conf.UDPListenAddrs = []*net.UDPAddr{{}} s.conf.TCPListenAddrs = []*net.TCPAddr{{}} 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} + s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false} err = s.Prepare(&s.conf) require.NoError(t, err) @@ -1234,7 +1237,8 @@ func TestPTRResponseFromHosts(t *testing.T) { }) flt, err := filtering.New(&filtering.Config{ - EtcHosts: hc, + BlockingMode: filtering.BlockingModeDefault, + EtcHosts: hc, }, nil) require.NoError(t, err) @@ -1251,8 +1255,7 @@ func TestPTRResponseFromHosts(t *testing.T) { s.conf.UDPListenAddrs = []*net.UDPAddr{{}} 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} + s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false} err = s.Prepare(&s.conf) require.NoError(t, err) diff --git a/internal/dnsforward/dnsrewrite_test.go b/internal/dnsforward/dnsrewrite_test.go index 5d591c2c..1566e2ff 100644 --- a/internal/dnsforward/dnsrewrite_test.go +++ b/internal/dnsforward/dnsrewrite_test.go @@ -34,7 +34,9 @@ func TestServer_FilterDNSRewrite(t *testing.T) { } // Helper functions and entities. - srv := &Server{} + srv := &Server{ + dnsFilter: &filtering.DNSFilter{}, + } makeQ := func(qtype rules.RRType) (req *dns.Msg) { return &dns.Msg{ Question: []dns.Question{{ diff --git a/internal/dnsforward/filter_test.go b/internal/dnsforward/filter_test.go index 842401f6..88a316e8 100644 --- a/internal/dnsforward/filter_test.go +++ b/internal/dnsforward/filter_test.go @@ -30,9 +30,7 @@ func TestHandleDNSRequest_handleDNSRequest(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -42,7 +40,10 @@ func TestHandleDNSRequest_handleDNSRequest(t *testing.T) { ID: 0, Data: []byte(rules), }} - f, err := filtering.New(&filtering.Config{}, filters) + f, err := filtering.New(&filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, + }, filters) require.NoError(t, err) f.SetEnabled(true) diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index 431e979a..05838877 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -10,6 +10,7 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" + "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/errors" @@ -47,7 +48,7 @@ type jsonDNSConfig struct { RateLimit *uint32 `json:"ratelimit"` // BlockingMode defines the way blocked responses are constructed. - BlockingMode *BlockingMode `json:"blocking_mode"` + BlockingMode *filtering.BlockingMode `json:"blocking_mode"` // EDNSCSEnabled defines if EDNS Client Subnet is enabled. EDNSCSEnabled *bool `json:"edns_cs_enabled"` @@ -113,9 +114,9 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) { upstreamFile := s.conf.UpstreamDNSFileName bootstraps := stringutil.CloneSliceOrEmpty(s.conf.BootstrapDNS) fallbacks := stringutil.CloneSliceOrEmpty(s.conf.FallbackDNS) - blockingMode := s.conf.BlockingMode - blockingIPv4 := s.conf.BlockingIPv4 - blockingIPv6 := s.conf.BlockingIPv6 + blockingMode := s.dnsFilter.BlockingMode + blockingIPv4 := s.dnsFilter.BlockingIPv4 + blockingIPv6 := s.dnsFilter.BlockingIPv6 ratelimit := s.conf.Ratelimit customIP := s.conf.EDNSClientSubnet.CustomIP @@ -319,10 +320,10 @@ func (s *Server) setConfig(dc *jsonDNSConfig) (shouldRestart bool) { defer s.serverLock.Unlock() if dc.BlockingMode != nil { - s.conf.BlockingMode = *dc.BlockingMode - if *dc.BlockingMode == BlockingModeCustomIP { - s.conf.BlockingIPv4 = dc.BlockingIPv4 - s.conf.BlockingIPv6 = dc.BlockingIPv6 + s.dnsFilter.BlockingMode = *dc.BlockingMode + if *dc.BlockingMode == filtering.BlockingModeCustomIP { + s.dnsFilter.BlockingIPv4 = dc.BlockingIPv4 + s.dnsFilter.BlockingIPv6 = dc.BlockingIPv6 } } @@ -335,7 +336,7 @@ func (s *Server) setConfig(dc *jsonDNSConfig) (shouldRestart bool) { s.conf.EDNSClientSubnet.CustomIP = dc.EDNSCSCustomIP } - setIfNotNil(&s.conf.ProtectionEnabled, dc.ProtectionEnabled) + setIfNotNil(&s.dnsFilter.ProtectionEnabled, dc.ProtectionEnabled) setIfNotNil(&s.conf.EnableDNSSEC, dc.DNSSECEnabled) setIfNotNil(&s.conf.AAAADisabled, dc.DisableIPv6) @@ -831,8 +832,8 @@ func (s *Server) handleSetProtection(w http.ResponseWriter, r *http.Request) { s.serverLock.Lock() defer s.serverLock.Unlock() - s.conf.ProtectionEnabled = protectionReq.Enabled - s.conf.ProtectionDisabledUntil = disabledUntil + s.dnsFilter.ProtectionEnabled = protectionReq.Enabled + s.dnsFilter.ProtectionDisabledUntil = disabledUntil }() s.conf.ConfigModified() diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go index 11a1a53f..2721a9b4 100644 --- a/internal/dnsforward/http_test.go +++ b/internal/dnsforward/http_test.go @@ -58,6 +58,8 @@ const jsonExt = ".json" func TestDNSForwardHTTP_handleGetConfig(t *testing.T) { filterConf := &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, SafeBrowsingEnabled: true, SafeBrowsingCacheSize: 1000, SafeSearchConf: filtering.SafeSearchConfig{Enabled: true}, @@ -68,12 +70,10 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{}, TCPListenAddrs: []*net.TCPAddr{}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, - UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, - FallbackDNS: []string{"9.9.9.10"}, - EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + Config: Config{ + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + FallbackDNS: []string{"9.9.9.10"}, + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ConfigModified: func() {}, } @@ -135,6 +135,8 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) { func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { filterConf := &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, SafeBrowsingEnabled: true, SafeBrowsingCacheSize: 1000, SafeSearchConf: filtering.SafeSearchConfig{Enabled: true}, @@ -145,11 +147,9 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { forwardConf := ServerConfig{ UDPListenAddrs: []*net.UDPAddr{}, TCPListenAddrs: []*net.TCPAddr{}, - FilteringConfig: FilteringConfig{ - ProtectionEnabled: true, - BlockingMode: BlockingModeDefault, - UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, - EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, + Config: Config{ + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ConfigModified: func() {}, } @@ -157,6 +157,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { s.sysResolvers = &fakeSystemResolvers{} defaultConf := s.conf + defaultFilterConf := filterConf err := s.Start() assert.NoError(t, err) @@ -247,8 +248,9 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Cleanup(func() { + s.dnsFilter.Config = *defaultFilterConf s.conf = defaultConf - s.conf.FilteringConfig.EDNSClientSubnet = &EDNSClientSubnet{} + s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{} }) rBody := io.NopCloser(bytes.NewReader(caseData.Req)) @@ -503,7 +505,7 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, UpstreamTimeout: upsTimeout, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, nil) diff --git a/internal/dnsforward/msg.go b/internal/dnsforward/msg.go index 6d917fbe..b8ed6a10 100644 --- a/internal/dnsforward/msg.go +++ b/internal/dnsforward/msg.go @@ -50,7 +50,7 @@ func (s *Server) genDNSFilterMessage( req := dctx.Req qt := req.Question[0].Qtype if qt != dns.TypeA && qt != dns.TypeAAAA { - if s.conf.BlockingMode == BlockingModeNullIP { + if s.dnsFilter.BlockingMode == filtering.BlockingModeNullIP { return s.makeResponse(req) } @@ -59,9 +59,9 @@ func (s *Server) genDNSFilterMessage( switch res.Reason { case filtering.FilteredSafeBrowsing: - return s.genBlockedHost(req, s.conf.SafeBrowsingBlockHost, dctx) + return s.genBlockedHost(req, s.dnsFilter.SafeBrowsingBlockHost, dctx) case filtering.FilteredParental: - return s.genBlockedHost(req, s.conf.ParentalBlockHost, dctx) + return s.genBlockedHost(req, s.dnsFilter.ParentalBlockHost, dctx) case filtering.FilteredSafeSearch: // If Safe Search generated the necessary IP addresses, use them. // Otherwise, if there were no errors, there are no addresses for the @@ -76,13 +76,13 @@ func (s *Server) genDNSFilterMessage( // blocking mode. func (s *Server) genForBlockingMode(req *dns.Msg, ips []netip.Addr) (resp *dns.Msg) { qt := req.Question[0].Qtype - switch m := s.conf.BlockingMode; m { - case BlockingModeCustomIP: + switch m := s.dnsFilter.BlockingMode; m { + case filtering.BlockingModeCustomIP: switch qt { case dns.TypeA: - return s.genARecord(req, s.conf.BlockingIPv4) + return s.genARecord(req, s.dnsFilter.BlockingIPv4) case dns.TypeAAAA: - return s.genAAAARecord(req, s.conf.BlockingIPv6) + return s.genAAAARecord(req, s.dnsFilter.BlockingIPv6) default: // Generally shouldn't happen, since the types are checked in // genDNSFilterMessage. @@ -90,20 +90,20 @@ func (s *Server) genForBlockingMode(req *dns.Msg, ips []netip.Addr) (resp *dns.M return s.makeResponse(req) } - case BlockingModeDefault: + case filtering.BlockingModeDefault: if len(ips) > 0 { return s.genResponseWithIPs(req, ips) } return s.makeResponseNullIP(req) - case BlockingModeNullIP: + case filtering.BlockingModeNullIP: return s.makeResponseNullIP(req) - case BlockingModeNXDOMAIN: + case filtering.BlockingModeNXDOMAIN: return s.genNXDomain(req) - case BlockingModeREFUSED: + case filtering.BlockingModeREFUSED: return s.makeResponseREFUSED(req) default: - log.Error("dns: invalid blocking mode %q", s.conf.BlockingMode) + log.Error("dns: invalid blocking mode %q", s.dnsFilter.BlockingMode) return s.makeResponse(req) } @@ -132,7 +132,7 @@ func (s *Server) hdr(req *dns.Msg, rrType rules.RRType) (h dns.RR_Header) { return dns.RR_Header{ Name: req.Question[0].Name, Rrtype: rrType, - Ttl: s.conf.BlockedResponseTTL, + Ttl: s.dnsFilter.BlockedResponseTTL, Class: dns.ClassINET, } } @@ -243,6 +243,12 @@ func (s *Server) makeResponseNullIP(req *dns.Msg) (resp *dns.Msg) { } func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSContext) *dns.Msg { + if newAddr == "" { + log.Printf("block host is not specified.") + + return s.genServerFailure(request) + } + ip, err := netip.ParseAddr(newAddr) if err == nil { return s.genResponseWithIPs(request, []netip.Addr{ip}) @@ -346,13 +352,13 @@ func (s *Server) genSOA(request *dns.Msg) []dns.RR { Hdr: dns.RR_Header{ Name: zone, Rrtype: dns.TypeSOA, - Ttl: s.conf.BlockedResponseTTL, + Ttl: s.dnsFilter.BlockedResponseTTL, Class: dns.ClassINET, }, Mbox: "hostmaster.", // zone will be appended later if it's not empty or "." } if soa.Hdr.Ttl == 0 { - soa.Hdr.Ttl = defaultValues.BlockedResponseTTL + soa.Hdr.Ttl = defaultBlockedResponseTTL } if len(zone) > 0 && zone[0] != '.' { soa.Mbox += zone diff --git a/internal/dnsforward/process.go b/internal/dnsforward/process.go index 3741a635..09cef976 100644 --- a/internal/dnsforward/process.go +++ b/internal/dnsforward/process.go @@ -607,7 +607,7 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) { Rrtype: dns.TypePTR, // TODO(e.burkov): Use [dhcpsvc.Lease.Expiry]. See // https://github.com/AdguardTeam/AdGuardHome/issues/3932. - Ttl: s.conf.BlockedResponseTTL, + Ttl: s.dnsFilter.BlockedResponseTTL, Class: dns.ClassINET, }, Ptr: dns.Fqdn(strings.Join([]string{host, s.localDomainSuffix}, ".")), diff --git a/internal/dnsforward/process_internal_test.go b/internal/dnsforward/process_internal_test.go index 014035c3..8c100de9 100644 --- a/internal/dnsforward/process_internal_test.go +++ b/internal/dnsforward/process_internal_test.go @@ -77,7 +77,7 @@ func TestServer_ProcessInitial(t *testing.T) { t.Parallel() c := ServerConfig{ - FilteringConfig: FilteringConfig{ + Config: Config{ AAAADisabled: tc.aaaaDisabled, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, @@ -174,7 +174,7 @@ func TestServer_ProcessFilteringAfterResponse(t *testing.T) { t.Parallel() c := ServerConfig{ - FilteringConfig: FilteringConfig{ + Config: Config{ AAAADisabled: tc.aaaaDisabled, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, @@ -342,11 +342,12 @@ func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled b t.Helper() s = &Server{ + dnsFilter: &filtering.DNSFilter{}, dnsProxy: &proxy.Proxy{ Config: proxy.Config{}, }, conf: ServerConfig{ - FilteringConfig: FilteringConfig{ + Config: Config{ HandleDDR: ddrEnabled, }, TLSConfig: TLSConfig{ @@ -466,6 +467,7 @@ func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { s := &Server{ + dnsFilter: &filtering.DNSFilter{}, dhcpServer: dhcp, localDomainSuffix: localDomainSuffix, } @@ -600,6 +602,7 @@ func TestServer_ProcessDHCPHosts(t *testing.T) { } s := &Server{ + dnsFilter: &filtering.DNSFilter{}, dhcpServer: testDHCP, localDomainSuffix: tc.suffix, } @@ -674,8 +677,8 @@ func TestServer_ProcessRestrictLocal(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, // TODO(s.chzhen): Add tests where EDNSClientSubnet.Enabled is true. - // Improve FilteringConfig declaration for tests. - FilteringConfig: FilteringConfig{ + // Improve Config declaration for tests. + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, ups) @@ -750,7 +753,7 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) { ServerConfig{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, - FilteringConfig: FilteringConfig{ + Config: Config{ EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, }, diff --git a/internal/dnsforward/svcbmsg_test.go b/internal/dnsforward/svcbmsg_test.go index 8de53988..2804c52a 100644 --- a/internal/dnsforward/svcbmsg_test.go +++ b/internal/dnsforward/svcbmsg_test.go @@ -4,6 +4,7 @@ import ( "net" "testing" + "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" "github.com/stretchr/testify/assert" @@ -13,11 +14,7 @@ func TestGenAnswerHTTPS_andSVCB(t *testing.T) { // Preconditions. s := &Server{ - conf: ServerConfig{ - FilteringConfig: FilteringConfig{ - BlockedResponseTTL: 3600, - }, - }, + dnsFilter: &filtering.DNSFilter{}, } req := &dns.Msg{ diff --git a/internal/filtering/filtering.go b/internal/filtering/filtering.go index 0c49eefd..506db370 100644 --- a/internal/filtering/filtering.go +++ b/internal/filtering/filtering.go @@ -15,6 +15,7 @@ import ( "strings" "sync" "sync/atomic" + "time" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" @@ -76,35 +77,19 @@ type Resolver interface { // Config allows you to configure DNS filtering with New() or just change variables directly. type Config struct { + // BlockingIPv4 is the IP address to be returned for a blocked A request. + BlockingIPv4 netip.Addr `yaml:"blocking_ipv4"` + + // BlockingIPv6 is the IP address to be returned for a blocked AAAA request. + BlockingIPv6 netip.Addr `yaml:"blocking_ipv6"` + // SafeBrowsingChecker is the safe browsing hash-prefix checker. SafeBrowsingChecker Checker `yaml:"-"` // ParentControl is the parental control hash-prefix checker. ParentalControlChecker Checker `yaml:"-"` - // enabled is used to be returned within Settings. - // - // It is of type uint32 to be accessed by atomic. - // - // TODO(e.burkov): Use atomic.Bool in Go 1.19. - enabled uint32 - - FilteringEnabled bool `yaml:"filtering_enabled"` // whether or not use filter lists - FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` // time period to update filters (in hours) - - ParentalEnabled bool `yaml:"parental_enabled"` - SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"` - - SafeBrowsingCacheSize uint `yaml:"safebrowsing_cache_size"` // (in bytes) - SafeSearchCacheSize uint `yaml:"safesearch_cache_size"` // (in bytes) - ParentalCacheSize uint `yaml:"parental_cache_size"` // (in bytes) - // TODO(a.garipov): Use timeutil.Duration - CacheTime uint `yaml:"cache_time"` // Element's TTL (in minutes) - - SafeSearchConf SafeSearchConfig `yaml:"safe_search"` - SafeSearch SafeSearch `yaml:"-"` - - Rewrites []*LegacyRewrite `yaml:"rewrites"` + SafeSearch SafeSearch `yaml:"-"` // BlockedServices is the configuration of blocked services. // Per-client settings can override this configuration. @@ -123,11 +108,30 @@ type Config struct { // HTTPClient is the client to use for updating the remote filters. HTTPClient *http.Client `yaml:"-"` + // filtersMu protects filter lists. + filtersMu *sync.RWMutex + + // ProtectionDisabledUntil is the timestamp until when the protection is + // disabled. + ProtectionDisabledUntil *time.Time `yaml:"protection_disabled_until"` + + SafeSearchConf SafeSearchConfig `yaml:"safe_search"` + // DataDir is used to store filters' contents. DataDir string `yaml:"-"` - // filtersMu protects filter lists. - filtersMu *sync.RWMutex + // BlockingMode defines the way how blocked responses are constructed. + BlockingMode BlockingMode `yaml:"blocking_mode"` + + // 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"` + + Rewrites []*LegacyRewrite `yaml:"rewrites"` // Filters are the blocking filter lists. Filters []FilterYAML `yaml:"-"` @@ -137,8 +141,62 @@ type Config struct { // UserRules is the global list of custom rules. UserRules []string `yaml:"-"` + + SafeBrowsingCacheSize uint `yaml:"safebrowsing_cache_size"` // (in bytes) + SafeSearchCacheSize uint `yaml:"safesearch_cache_size"` // (in bytes) + ParentalCacheSize uint `yaml:"parental_cache_size"` // (in bytes) + // TODO(a.garipov): Use timeutil.Duration + CacheTime uint `yaml:"cache_time"` // Element's TTL (in minutes) + + // enabled is used to be returned within Settings. + // + // It is of type uint32 to be accessed by atomic. + // + // TODO(e.burkov): Use atomic.Bool in Go 1.19. + enabled uint32 + + // FiltersUpdateIntervalHours is the time period to update filters + // (in hours). + FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` + + // BlockedResponseTTL is the time-to-live value for blocked responses. If + // 0, then default value is used (3600). + BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` + + // FilteringEnabled indicates whether or not use filter lists. + FilteringEnabled bool `yaml:"filtering_enabled"` + + ParentalEnabled bool `yaml:"parental_enabled"` + SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"` + + // ProtectionEnabled defines whether or not use any of filtering features. + ProtectionEnabled bool `yaml:"protection_enabled"` } +// BlockingMode is an enum of all allowed blocking modes. +type BlockingMode string + +// Allowed blocking modes. +const ( + // BlockingModeCustomIP means respond with a custom IP address. + BlockingModeCustomIP BlockingMode = "custom_ip" + + // BlockingModeDefault is the same as BlockingModeNullIP for + // Adblock-style rules, but responds with the IP address specified in + // the rule when blocked by an `/etc/hosts`-style rule. + BlockingModeDefault BlockingMode = "default" + + // BlockingModeNullIP means respond with a zero IP address: "0.0.0.0" + // for A requests and "::" for AAAA ones. + BlockingModeNullIP BlockingMode = "null_ip" + + // BlockingModeNXDOMAIN means respond with the NXDOMAIN code. + BlockingModeNXDOMAIN BlockingMode = "nxdomain" + + // BlockingModeREFUSED means respond with the REFUSED code. + BlockingModeREFUSED BlockingMode = "refused" +) + // LookupStats store stats collected during safebrowsing or parental checks type LookupStats struct { Requests uint64 // number of HTTP requests that were sent @@ -182,6 +240,15 @@ type DNSFilter struct { rulesStorageAllow *filterlist.RuleStorage filteringEngineAllow *urlfilter.DNSEngine + // Config contains filtering parameters. For direct access by library + // users, even a = assignment. + // + // TODO(d.kolyshev): Remove this embed. + Config + + // confLock protects Config. + confLock sync.RWMutex + safeSearch SafeSearch // safeBrowsingChecker is the safe browsing hash-prefix checker. @@ -192,10 +259,6 @@ type DNSFilter struct { engineLock sync.RWMutex - Config // for direct access by library users, even a = assignment - // confLock protects Config. - confLock sync.RWMutex - // Channel for passing data to filters-initializer goroutine filtersInitializerChan chan filtersInitializerParams filtersInitializerLock sync.Mutex diff --git a/internal/home/config.go b/internal/home/config.go index 2183c52c..5e399b00 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -131,7 +131,8 @@ type configuration struct { WhitelistFilters []filtering.FilterYAML `yaml:"whitelist_filters"` UserRules []string `yaml:"user_rules"` - DHCP *dhcpd.ServerConfig `yaml:"dhcp"` + DHCP *dhcpd.ServerConfig `yaml:"dhcp"` + Filtering *filtering.Config `yaml:"filtering"` // Clients contains the YAML representations of the persistent clients. // This field is only used for reading and writing persistent client data. @@ -185,9 +186,10 @@ type dnsConfig struct { // in query log and statistics. AnonymizeClientIP bool `yaml:"anonymize_client_ip"` - dnsforward.FilteringConfig `yaml:",inline"` - - DnsfilterConf *filtering.Config `yaml:",inline"` + // Config is the embed configuration with DNS params. + // + // TODO(a.garipov): Remove embed. + dnsforward.Config `yaml:",inline"` // UpstreamTimeout is the timeout for querying upstream servers. UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"` @@ -295,14 +297,11 @@ var config = &configuration{ DNS: dnsConfig{ BindHosts: []netip.Addr{netip.IPv4Unspecified()}, Port: defaultPortDNS, - FilteringConfig: dnsforward.FilteringConfig{ - ProtectionEnabled: true, // whether or not use any of filtering features - BlockingMode: dnsforward.BlockingModeDefault, - BlockedResponseTTL: 10, // in seconds - Ratelimit: 20, - RefuseAny: true, - AllServers: false, - HandleDDR: true, + Config: dnsforward.Config{ + Ratelimit: 20, + RefuseAny: true, + AllServers: false, + HandleDDR: true, FastestTimeout: timeutil.Duration{ Duration: fastip.DefaultPingWaitTimeout, }, @@ -322,33 +321,6 @@ var config = &configuration{ // was later increased to 300 due to https://github.com/AdguardTeam/AdGuardHome/issues/2257 MaxGoroutines: 300, }, - DnsfilterConf: &filtering.Config{ - FilteringEnabled: true, - FiltersUpdateIntervalHours: 24, - - ParentalEnabled: false, - SafeBrowsingEnabled: false, - - SafeBrowsingCacheSize: 1 * 1024 * 1024, - SafeSearchCacheSize: 1 * 1024 * 1024, - ParentalCacheSize: 1 * 1024 * 1024, - CacheTime: 30, - - SafeSearchConf: filtering.SafeSearchConfig{ - Enabled: false, - Bing: true, - DuckDuckGo: true, - Google: true, - Pixabay: true, - Yandex: true, - YouTube: true, - }, - - BlockedServices: &filtering.BlockedServices{ - Schedule: schedule.EmptyWeekly(), - IDs: []string{}, - }, - }, UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout}, UsePrivateRDNS: true, }, @@ -385,6 +357,37 @@ var config = &configuration{ URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt", Name: "AdAway Default Blocklist", }}, + Filtering: &filtering.Config{ + ProtectionEnabled: true, + BlockingMode: filtering.BlockingModeDefault, + BlockedResponseTTL: 10, // in seconds + + FilteringEnabled: true, + FiltersUpdateIntervalHours: 24, + + ParentalEnabled: false, + SafeBrowsingEnabled: false, + + SafeBrowsingCacheSize: 1 * 1024 * 1024, + SafeSearchCacheSize: 1 * 1024 * 1024, + ParentalCacheSize: 1 * 1024 * 1024, + CacheTime: 30, + + SafeSearchConf: filtering.SafeSearchConfig{ + Enabled: false, + Bing: true, + DuckDuckGo: true, + Google: true, + Pixabay: true, + Yandex: true, + YouTube: true, + }, + + BlockedServices: &filtering.BlockedServices{ + Schedule: schedule.EmptyWeekly(), + IDs: []string{}, + }, + }, DHCP: &dhcpd.ServerConfig{ LocalDomainName: "lan", Conf4: dhcpd.V4ServerConf{ @@ -493,8 +496,8 @@ func parseConfig() (err error) { return fmt.Errorf("validating udp ports: %w", err) } - if !filtering.ValidateUpdateIvl(config.DNS.DnsfilterConf.FiltersUpdateIntervalHours) { - config.DNS.DnsfilterConf.FiltersUpdateIntervalHours = 24 + if !filtering.ValidateUpdateIvl(config.Filtering.FiltersUpdateIntervalHours) { + config.Filtering.FiltersUpdateIntervalHours = 24 } if config.DNS.UpstreamTimeout.Duration == 0 { @@ -574,17 +577,17 @@ func (c *configuration) write() (err error) { } if Context.filters != nil { - Context.filters.WriteDiskConfig(config.DNS.DnsfilterConf) - config.Filters = config.DNS.DnsfilterConf.Filters - config.WhitelistFilters = config.DNS.DnsfilterConf.WhitelistFilters - config.UserRules = config.DNS.DnsfilterConf.UserRules + Context.filters.WriteDiskConfig(config.Filtering) + config.Filters = config.Filtering.Filters + config.WhitelistFilters = config.Filtering.WhitelistFilters + config.UserRules = config.Filtering.UserRules } if s := Context.dnsServer; s != nil { - c := dnsforward.FilteringConfig{} + c := dnsforward.Config{} s.WriteDiskConfig(&c) dns := &config.DNS - dns.FilteringConfig = c + dns.Config = c dns.LocalPTRResolvers = s.LocalPTRResolvers() diff --git a/internal/home/control.go b/internal/home/control.go index f7286aed..51d6efe4 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -127,12 +127,12 @@ func handleStatus(w http.ResponseWriter, r *http.Request) { } var ( - fltConf *dnsforward.FilteringConfig + fltConf *dnsforward.Config protectionDisabledUntil *time.Time protectionEnabled bool ) if Context.dnsServer != nil { - fltConf = &dnsforward.FilteringConfig{} + fltConf = &dnsforward.Config{} Context.dnsServer.WriteDiskConfig(fltConf) protectionEnabled, protectionDisabledUntil = Context.dnsServer.UpdatedProtectionStatus() } diff --git a/internal/home/dns.go b/internal/home/dns.go index cf1a1198..309c0111 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -94,7 +94,7 @@ func initDNS() (err error) { return fmt.Errorf("init querylog: %w", err) } - Context.filters, err = filtering.New(config.DNS.DnsfilterConf, nil) + Context.filters, err = filtering.New(config.Filtering, nil) if err != nil { // Don't wrap the error, since it's informative enough as is. return err @@ -230,13 +230,13 @@ func newServerConfig( hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()}) newConf = &dnsforward.ServerConfig{ - UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port), - TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port), - FilteringConfig: dnsConf.FilteringConfig, - ConfigModified: onConfigModified, - HTTPRegister: httpReg, - UseDNS64: config.DNS.UseDNS64, - DNS64Prefixes: config.DNS.DNS64Prefixes, + UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port), + TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port), + Config: dnsConf.Config, + ConfigModified: onConfigModified, + HTTPRegister: httpReg, + UseDNS64: config.DNS.UseDNS64, + DNS64Prefixes: config.DNS.DNS64Prefixes, } var initialAddresses []netip.Addr diff --git a/internal/home/home.go b/internal/home/home.go index f6c1024e..1ad41a6a 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -272,7 +272,7 @@ func setupOpts(opts options) (err error) { // initContextClients initializes Context clients and related fields. func initContextClients() (err error) { - err = setupDNSFilteringConf(config.DNS.DnsfilterConf) + err = setupDNSFilteringConf(config.Filtering) if err != nil { // Don't wrap the error, because it's informative enough as is. return err @@ -303,7 +303,7 @@ func initContextClients() (err error) { Context.dhcpServer, Context.etcHosts, arpDB, - config.DNS.DnsfilterConf, + config.Filtering, ) if err != nil { // Don't wrap the error, because it's informative enough as is. @@ -365,6 +365,9 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) { pcService = "parental control" defaultParentalServer = `https://family.adguard-dns.com/dns-query` pcTXTSuffix = `pc.dns.adguard.com.` + + defaultSafeBrowsingBlockHost = "standard-block.dns.adguard.com" + defaultParentalBlockHost = "family-block.dns.adguard.com" ) conf.EtcHosts = Context.etcHosts @@ -401,6 +404,10 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) { CacheSize: conf.SafeBrowsingCacheSize, }) + if conf.SafeBrowsingBlockHost != "" { + conf.SafeBrowsingBlockHost = defaultSafeBrowsingBlockHost + } + parUps, err := upstream.AddressToUpstream(defaultParentalServer, upsOpts) if err != nil { return fmt.Errorf("converting parental server: %w", err) @@ -414,6 +421,10 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) { CacheSize: conf.ParentalCacheSize, }) + if conf.ParentalBlockHost != "" { + conf.ParentalBlockHost = defaultParentalBlockHost + } + conf.SafeSearchConf.CustomResolver = safeSearchResolver{} conf.SafeSearch, err = safesearch.NewDefault( conf.SafeSearchConf, @@ -544,7 +555,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) { fatalOnError(err) upd := updater.NewUpdater(&updater.Config{ - Client: config.DNS.DnsfilterConf.HTTPClient, + Client: config.Filtering.HTTPClient, Version: version.Version(), Channel: version.Channel(), GOARCH: runtime.GOARCH, diff --git a/internal/home/upgrade.go b/internal/home/upgrade.go index 4f3fb565..5b2cf3cd 100644 --- a/internal/home/upgrade.go +++ b/internal/home/upgrade.go @@ -23,7 +23,7 @@ import ( ) // currentSchemaVersion is the current schema version. -const currentSchemaVersion = 25 +const currentSchemaVersion = 26 // These aliases are provided for convenience. type ( @@ -100,6 +100,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) { upgradeSchema22to23, upgradeSchema23to24, upgradeSchema24to25, + upgradeSchema25to26, } n := 0 @@ -1425,34 +1426,172 @@ func upgradeSchema24to25(diskConf yobj) (err error) { return nil } -// moveField gets field value for key from diskConf, and then set this value -// in newConf for newKey. -func moveField[T any](diskConf, newConf yobj, key, newKey string) (err error) { - ok, newVal, err := fieldValue[T](diskConf, key) +// upgradeSchema25to26 performs the following changes: +// +// # BEFORE: +// 'dns': +// 'filtering_enabled': true +// 'filters_update_interval': 24 +// 'parental_enabled': false +// 'safebrowsing_enabled': false +// 'safebrowsing_cache_size': 1048576 +// 'safesearch_cache_size': 1048576 +// 'parental_cache_size': 1048576 +// 'safe_search': +// 'enabled': false +// 'bing': true +// 'duckduckgo': true +// 'google': true +// 'pixabay': true +// 'yandex': true +// 'youtube': true +// 'rewrites': [] +// 'blocked_services': +// 'schedule': +// 'time_zone': 'Local' +// 'ids': [] +// 'protection_enabled': true, +// 'blocking_mode': 'custom_ip', +// 'blocking_ipv4': '1.2.3.4', +// 'blocking_ipv6': '1:2:3::4', +// 'blocked_response_ttl': 10, +// 'protection_disabled_until': 'null', +// 'parental_block_host': 'p.dns.adguard.com', +// 'safebrowsing_block_host': 's.dns.adguard.com', +// ... +// +// # AFTER: +// 'filtering': +// 'filtering_enabled': true +// 'filters_update_interval': 24 +// 'parental_enabled': false +// 'safebrowsing_enabled': false +// 'safebrowsing_cache_size': 1048576 +// 'safesearch_cache_size': 1048576 +// 'parental_cache_size': 1048576 +// 'safe_search': +// 'enabled': false +// 'bing': true +// 'duckduckgo': true +// 'google': true +// 'pixabay': true +// 'yandex': true +// 'youtube': true +// 'rewrites': [] +// 'blocked_services': +// 'schedule': +// 'time_zone': 'Local' +// 'ids': [] +// 'protection_enabled': true, +// 'blocking_mode': 'custom_ip', +// 'blocking_ipv4': '1.2.3.4', +// 'blocking_ipv6': '1:2:3::4', +// 'blocked_response_ttl': 10, +// 'protection_disabled_until': 'null', +// 'parental_block_host': 'p.dns.adguard.com', +// 'safebrowsing_block_host': 's.dns.adguard.com', +// 'dns' +// ... +func upgradeSchema25to26(diskConf yobj) (err error) { + log.Printf("Upgrade yaml: 25 to 26") + diskConf["schema_version"] = 26 + + dnsVal, ok := diskConf["dns"] if !ok { + return nil + } + + dnsObj, ok := dnsVal.(yobj) + if !ok { + return fmt.Errorf("unexpected type of dns: %T", dnsVal) + } + + filteringObj := yobj{} + err = coalesceError( + moveFieldValue[bool](dnsObj, filteringObj, "filtering_enabled"), + moveFieldValue[int](dnsObj, filteringObj, "filters_update_interval"), + moveFieldValue[bool](dnsObj, filteringObj, "parental_enabled"), + moveFieldValue[bool](dnsObj, filteringObj, "safebrowsing_enabled"), + moveFieldValue[int](dnsObj, filteringObj, "safebrowsing_cache_size"), + moveFieldValue[int](dnsObj, filteringObj, "safesearch_cache_size"), + moveFieldValue[int](dnsObj, filteringObj, "parental_cache_size"), + moveFieldValue[yobj](dnsObj, filteringObj, "safe_search"), + moveFieldValue[yarr](dnsObj, filteringObj, "rewrites"), + moveFieldValue[yobj](dnsObj, filteringObj, "blocked_services"), + moveFieldValue[bool](dnsObj, filteringObj, "protection_enabled"), + moveFieldValue[string](dnsObj, filteringObj, "blocking_mode"), + moveFieldValue[string](dnsObj, filteringObj, "blocking_ipv4"), + moveFieldValue[string](dnsObj, filteringObj, "blocking_ipv6"), + moveFieldValue[int](dnsObj, filteringObj, "blocked_response_ttl"), + moveFieldValue[any](dnsObj, filteringObj, "protection_disabled_until"), + moveFieldValue[string](dnsObj, filteringObj, "parental_block_host"), + moveFieldValue[string](dnsObj, filteringObj, "safebrowsing_block_host"), + ) + if err != nil { + // Don't wrap the error, because it's informative enough as is. return err } - switch v := newVal.(type) { - case int, bool, string: - newConf[newKey] = v - default: - return fmt.Errorf("invalid type of %s: %T", key, newVal) + if len(filteringObj) != 0 { + diskConf["filtering"] = filteringObj } + delete(dnsObj, "filtering_enabled") + delete(dnsObj, "filters_update_interval") + delete(dnsObj, "parental_enabled") + delete(dnsObj, "safebrowsing_enabled") + delete(dnsObj, "safebrowsing_cache_size") + delete(dnsObj, "safesearch_cache_size") + delete(dnsObj, "parental_cache_size") + delete(dnsObj, "safe_search") + delete(dnsObj, "rewrites") + delete(dnsObj, "blocked_services") + delete(dnsObj, "protection_enabled") + delete(dnsObj, "blocking_mode") + delete(dnsObj, "blocking_ipv4") + delete(dnsObj, "blocking_ipv6") + delete(dnsObj, "blocked_response_ttl") + delete(dnsObj, "protection_disabled_until") + delete(dnsObj, "parental_block_host") + delete(dnsObj, "safebrowsing_block_host") + return nil } -// fieldValue returns the value of type T for key in diskConf object. -func fieldValue[T any](diskConf yobj, key string) (ok bool, field any, err error) { - fieldVal, ok := diskConf[key] +// moveField gets field value for key from fromObj, and then sets this value in +// newConf for newKey. +func moveField[T any](fromObj, newConf yobj, key, newKey string) (err error) { + ok, newVal, err := fieldValue[T](fromObj, key) if !ok { - return false, new(T), nil + return err + } + + newConf[newKey] = newVal + + return nil +} + +// moveFieldValue gets field value for key from fromObj, and then sets this +// value in newConf with the same key. +func moveFieldValue[T any](fromObj, newConf yobj, key string) (err error) { + return moveField[T](fromObj, newConf, key, key) +} + +// fieldValue returns the value of type T for key in confObj object. Returns +// nil for fields with nil values. +func fieldValue[T any](confObj yobj, key string) (ok bool, field T, err error) { + fieldVal, ok := confObj[key] + if !ok { + return false, field, nil + } + + if fieldVal == nil { + return true, field, nil } f, ok := fieldVal.(T) if !ok { - return false, nil, fmt.Errorf("unexpected type of %s: %T", key, fieldVal) + return false, field, fmt.Errorf("unexpected type of %s: %T", key, fieldVal) } return true, f, nil diff --git a/internal/home/upgrade_test.go b/internal/home/upgrade_test.go index c6dd56c0..3d626bad 100644 --- a/internal/home/upgrade_test.go +++ b/internal/home/upgrade_test.go @@ -1466,3 +1466,102 @@ func TestUpgradeSchema24to25(t *testing.T) { }) } } + +func TestUpgradeSchema25to26(t *testing.T) { + const newSchemaVer = 26 + + testCases := []struct { + in yobj + want yobj + name string + }{{ + name: "empty", + in: yobj{}, + want: yobj{ + "schema_version": newSchemaVer, + }, + }, { + name: "ok", + in: yobj{ + "dns": yobj{ + "filtering_enabled": true, + "filters_update_interval": 24, + "parental_enabled": false, + "safebrowsing_enabled": false, + "safebrowsing_cache_size": 1048576, + "safesearch_cache_size": 1048576, + "parental_cache_size": 1048576, + "safe_search": yobj{ + "enabled": false, + "bing": true, + "duckduckgo": true, + "google": true, + "pixabay": true, + "yandex": true, + "youtube": true, + }, + "rewrites": yarr{}, + "blocked_services": yobj{ + "schedule": yobj{ + "time_zone": "Local", + }, + "ids": yarr{}, + }, + "protection_enabled": true, + "blocking_mode": "custom_ip", + "blocking_ipv4": "1.2.3.4", + "blocking_ipv6": "1:2:3::4", + "blocked_response_ttl": 10, + "protection_disabled_until": nil, + "parental_block_host": "p.dns.adguard.com", + "safebrowsing_block_host": "s.dns.adguard.com", + }, + }, + want: yobj{ + "dns": yobj{}, + "filtering": yobj{ + "filtering_enabled": true, + "filters_update_interval": 24, + "parental_enabled": false, + "safebrowsing_enabled": false, + "safebrowsing_cache_size": 1048576, + "safesearch_cache_size": 1048576, + "parental_cache_size": 1048576, + "safe_search": yobj{ + "enabled": false, + "bing": true, + "duckduckgo": true, + "google": true, + "pixabay": true, + "yandex": true, + "youtube": true, + }, + "rewrites": yarr{}, + "blocked_services": yobj{ + "schedule": yobj{ + "time_zone": "Local", + }, + "ids": yarr{}, + }, + "protection_enabled": true, + "blocking_mode": "custom_ip", + "blocking_ipv4": "1.2.3.4", + "blocking_ipv6": "1:2:3::4", + "blocked_response_ttl": 10, + "protection_disabled_until": nil, + "parental_block_host": "p.dns.adguard.com", + "safebrowsing_block_host": "s.dns.adguard.com", + }, + "schema_version": newSchemaVer, + }, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := upgradeSchema25to26(tc.in) + require.NoError(t, err) + + assert.Equal(t, tc.want, tc.in) + }) + } +}