Pull request: 1163 safesearch vol.2
Merge in DNS/adguard-home from 1163-safesearch-1-2-1 to master Squashed commit of the following: commit d3a5ebef35210019842145074e898129b42f1f2c Merge: b85264aec6706445
Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Wed Mar 15 09:17:53 2023 +0700 Merge remote-tracking branch 'origin/master' into 1163-safesearch-1-2-1 # Conflicts: # CHANGELOG.md commit b85264aefc5f191ac6cb194b519f03ba15829a4e Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue Mar 14 00:16:07 2023 +0700 home: imp code commit ac2ed7a5ce8db40628e7d4d1c8634641e5f38b0b Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Mar 13 23:02:06 2023 +0700 all: changelog commit f0fccafcb01f50c7051df53bbe9b02cab75aa71e Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Mar 13 22:42:36 2023 +0700 all: changelog commit 37df29bf6372939644fb28e3d70365496e0cb4f6 Merge: b227b277595484e0
Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Mar 13 22:38:57 2023 +0700 Merge remote-tracking branch 'origin/master' into 1163-safesearch-1-2-1 commit b227b2775b4866d69241ad87acf99700715552cb Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Mar 13 16:56:01 2023 +0700 all: imp docs commit 6fd39fc3565c3f4bc7a7113d17733c20dfe24d8d Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Mar 13 16:55:03 2023 +0700 home: imp code commit 3bb3bb7c7dcf97b2a5602a7d2b6770c08b4d863d Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Mar 13 12:16:53 2023 +0700 home: imp docs commit 5f573a56a9fd9942ad677fa0fae6b24228dab653 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Mar 13 11:56:47 2023 +0700 home: imp code commit 23eeb5552cf2510596b2311cc3eda53ac678ffcc Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri Mar 10 10:57:33 2023 +0700 home: imp code commit 643de2fca1b5917c61fe83e1e472222404f3cd21 Merge: dada6e63a2053526
Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu Mar 9 21:03:08 2023 +0700 Merge remote-tracking branch 'origin/master' into 1163-safesearch-1-2-1 commit dada6e63ca5324d30775e2da1727da891743f654 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu Mar 9 17:09:03 2023 +0700 all: imp docs commit 81a180d99dd9a995440d5f4e2ebca34678e7d0c7 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu Mar 9 15:12:43 2023 +0700 all: imp code commit fa84877bc777004d246d71d0a9ae0bd9ee568a91 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu Mar 9 10:53:05 2023 +0700 all: imp code commit 6d7e02e745d72921a693d4f09eec7ce21c2aefd4 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu Mar 9 10:40:02 2023 +0700 all: imp docs commit 0a4332997070fb8d2fb3a34d32b92f57a325ff06 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue Mar 7 22:00:52 2023 +0700 safesearch: fix merge commit 145c2222ba4cf7f8909b816d83829d2217c94243 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu Mar 2 11:41:48 2023 +0700 safesearch: fix merge commit 14c6a8005fe15b5d5a39f91b17c96d8670975811 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Wed Mar 1 12:50:09 2023 +0700 all: docs commit 2a85c8831866bf1c34c423a289461fc1e32667b5 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Wed Mar 1 12:47:00 2023 +0700 all: use safesearch package
This commit is contained in:
parent
c6706445c9
commit
2b5e4850d0
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -23,11 +23,48 @@ See also the [v0.107.27 GitHub milestone][ms-v0.107.27].
|
||||||
NOTE: Add new changes BELOW THIS COMMENT.
|
NOTE: Add new changes BELOW THIS COMMENT.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The ability to manage safesearch for each service by using the new
|
||||||
|
`safe_search` field ([#1163]).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
#### Configuration Changes
|
||||||
|
|
||||||
|
In this release, the schema version has changed from 17 to 19.
|
||||||
|
|
||||||
|
- The `dns.safesearch_enabled` field has been replaced with `safe_search`
|
||||||
|
object containing per-service settings.
|
||||||
|
- The `clients.persistent.safesearch_enabled` field has been replaced with
|
||||||
|
`safe_search` object containing per-service settings.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# BEFORE:
|
||||||
|
'safesearch_enabled': true
|
||||||
|
|
||||||
|
# AFTER:
|
||||||
|
'safe_search':
|
||||||
|
'enabled': true
|
||||||
|
'bing': true
|
||||||
|
'duckduckgo': true
|
||||||
|
'google': true
|
||||||
|
'pixabay': true
|
||||||
|
'yandex': true
|
||||||
|
'youtube': true
|
||||||
|
```
|
||||||
|
|
||||||
|
To rollback this change, move the value of `dns.safe_search.enabled` into the
|
||||||
|
`dns.safesearch_enabled`, then remove `dns.safe_search` field. Do the same
|
||||||
|
client's specific `clients.persistent.safesearch` and then change the
|
||||||
|
`schema_version` back to `17`.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Panic caused by empty top-level domain name label in `/etc/hosts` files
|
- Panic caused by empty top-level domain name label in `/etc/hosts` files
|
||||||
([#5584]).
|
([#5584]).
|
||||||
|
|
||||||
|
[#1163]: https://github.com/AdguardTeam/AdGuardHome/issues/1163
|
||||||
[#5584]: https://github.com/AdguardTeam/AdGuardHome/issues/5584
|
[#5584]: https://github.com/AdguardTeam/AdGuardHome/issues/5584
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
|
@ -412,7 +413,7 @@ func TestServerRace(t *testing.T) {
|
||||||
filterConf := &filtering.Config{
|
filterConf := &filtering.Config{
|
||||||
SafeBrowsingEnabled: true,
|
SafeBrowsingEnabled: true,
|
||||||
SafeBrowsingCacheSize: 1000,
|
SafeBrowsingCacheSize: 1000,
|
||||||
SafeSearchEnabled: true,
|
SafeSearchConf: filtering.SafeSearchConfig{Enabled: true},
|
||||||
SafeSearchCacheSize: 1000,
|
SafeSearchCacheSize: 1000,
|
||||||
ParentalCacheSize: 1000,
|
ParentalCacheSize: 1000,
|
||||||
CacheTime: 30,
|
CacheTime: 30,
|
||||||
|
@ -440,12 +441,26 @@ func TestServerRace(t *testing.T) {
|
||||||
|
|
||||||
func TestSafeSearch(t *testing.T) {
|
func TestSafeSearch(t *testing.T) {
|
||||||
resolver := &aghtest.TestResolver{}
|
resolver := &aghtest.TestResolver{}
|
||||||
|
safeSearchConf := filtering.SafeSearchConfig{
|
||||||
|
Enabled: true,
|
||||||
|
Google: true,
|
||||||
|
Yandex: true,
|
||||||
|
CustomResolver: resolver,
|
||||||
|
}
|
||||||
|
|
||||||
filterConf := &filtering.Config{
|
filterConf := &filtering.Config{
|
||||||
SafeSearchEnabled: true,
|
SafeSearchConf: safeSearchConf,
|
||||||
SafeSearchCacheSize: 1000,
|
SafeSearchCacheSize: 1000,
|
||||||
CacheTime: 30,
|
CacheTime: 30,
|
||||||
CustomResolver: resolver,
|
|
||||||
}
|
}
|
||||||
|
safeSearch, err := safesearch.NewDefaultSafeSearch(
|
||||||
|
safeSearchConf,
|
||||||
|
filterConf.SafeSearchCacheSize,
|
||||||
|
time.Minute*time.Duration(filterConf.CacheTime),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
filterConf.SafeSearch = safeSearch
|
||||||
forwardConf := ServerConfig{
|
forwardConf := ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
@ -498,7 +513,8 @@ func TestSafeSearch(t *testing.T) {
|
||||||
t.Run(tc.host, func(t *testing.T) {
|
t.Run(tc.host, func(t *testing.T) {
|
||||||
req := createTestMessage(tc.host)
|
req := createTestMessage(tc.host)
|
||||||
|
|
||||||
reply, _, err := client.Exchange(req, addr)
|
var reply *dns.Msg
|
||||||
|
reply, _, err = client.Exchange(req, addr)
|
||||||
require.NoErrorf(t, err, "couldn't talk to server %s: %s", addr, err)
|
require.NoErrorf(t, err, "couldn't talk to server %s: %s", addr, err)
|
||||||
assertResponse(t, reply, tc.want)
|
assertResponse(t, reply, tc.want)
|
||||||
})
|
})
|
||||||
|
|
|
@ -57,7 +57,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
|
||||||
filterConf := &filtering.Config{
|
filterConf := &filtering.Config{
|
||||||
SafeBrowsingEnabled: true,
|
SafeBrowsingEnabled: true,
|
||||||
SafeBrowsingCacheSize: 1000,
|
SafeBrowsingCacheSize: 1000,
|
||||||
SafeSearchEnabled: true,
|
SafeSearchConf: filtering.SafeSearchConfig{Enabled: true},
|
||||||
SafeSearchCacheSize: 1000,
|
SafeSearchCacheSize: 1000,
|
||||||
ParentalCacheSize: 1000,
|
ParentalCacheSize: 1000,
|
||||||
CacheTime: 30,
|
CacheTime: 30,
|
||||||
|
@ -133,7 +133,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
||||||
filterConf := &filtering.Config{
|
filterConf := &filtering.Config{
|
||||||
SafeBrowsingEnabled: true,
|
SafeBrowsingEnabled: true,
|
||||||
SafeBrowsingCacheSize: 1000,
|
SafeBrowsingCacheSize: 1000,
|
||||||
SafeSearchEnabled: true,
|
SafeSearchConf: filtering.SafeSearchConfig{Enabled: true},
|
||||||
SafeSearchCacheSize: 1000,
|
SafeSearchCacheSize: 1000,
|
||||||
ParentalCacheSize: 1000,
|
ParentalCacheSize: 1000,
|
||||||
CacheTime: 30,
|
CacheTime: 30,
|
||||||
|
|
|
@ -63,6 +63,9 @@ type Settings struct {
|
||||||
SafeSearchEnabled bool
|
SafeSearchEnabled bool
|
||||||
SafeBrowsingEnabled bool
|
SafeBrowsingEnabled bool
|
||||||
ParentalEnabled bool
|
ParentalEnabled bool
|
||||||
|
|
||||||
|
// ClientSafeSearch is a client configured safe search.
|
||||||
|
ClientSafeSearch SafeSearch
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolver is the interface for net.Resolver to simplify testing.
|
// Resolver is the interface for net.Resolver to simplify testing.
|
||||||
|
@ -83,13 +86,16 @@ type Config struct {
|
||||||
FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` // time period to update filters (in hours)
|
FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` // time period to update filters (in hours)
|
||||||
|
|
||||||
ParentalEnabled bool `yaml:"parental_enabled"`
|
ParentalEnabled bool `yaml:"parental_enabled"`
|
||||||
SafeSearchEnabled bool `yaml:"safesearch_enabled"`
|
|
||||||
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
|
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
|
||||||
|
|
||||||
SafeBrowsingCacheSize uint `yaml:"safebrowsing_cache_size"` // (in bytes)
|
SafeBrowsingCacheSize uint `yaml:"safebrowsing_cache_size"` // (in bytes)
|
||||||
SafeSearchCacheSize uint `yaml:"safesearch_cache_size"` // (in bytes)
|
SafeSearchCacheSize uint `yaml:"safesearch_cache_size"` // (in bytes)
|
||||||
ParentalCacheSize uint `yaml:"parental_cache_size"` // (in bytes)
|
ParentalCacheSize uint `yaml:"parental_cache_size"` // (in bytes)
|
||||||
CacheTime uint `yaml:"cache_time"` // Element's TTL (in minutes)
|
// 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"`
|
Rewrites []*LegacyRewrite `yaml:"rewrites"`
|
||||||
|
|
||||||
|
@ -107,9 +113,6 @@ type Config struct {
|
||||||
// Register an HTTP handler
|
// Register an HTTP handler
|
||||||
HTTPRegister aghhttp.RegisterFunc `yaml:"-"`
|
HTTPRegister aghhttp.RegisterFunc `yaml:"-"`
|
||||||
|
|
||||||
// CustomResolver is the resolver used by DNSFilter.
|
|
||||||
CustomResolver Resolver `yaml:"-"`
|
|
||||||
|
|
||||||
// HTTPClient is the client to use for updating the remote filters.
|
// HTTPClient is the client to use for updating the remote filters.
|
||||||
HTTPClient *http.Client `yaml:"-"`
|
HTTPClient *http.Client `yaml:"-"`
|
||||||
|
|
||||||
|
@ -172,7 +175,6 @@ type DNSFilter struct {
|
||||||
|
|
||||||
safebrowsingCache cache.Cache
|
safebrowsingCache cache.Cache
|
||||||
parentalCache cache.Cache
|
parentalCache cache.Cache
|
||||||
safeSearchCache cache.Cache
|
|
||||||
|
|
||||||
Config // for direct access by library users, even a = assignment
|
Config // for direct access by library users, even a = assignment
|
||||||
// confLock protects Config.
|
// confLock protects Config.
|
||||||
|
@ -182,11 +184,6 @@ type DNSFilter struct {
|
||||||
filtersInitializerChan chan filtersInitializerParams
|
filtersInitializerChan chan filtersInitializerParams
|
||||||
filtersInitializerLock sync.Mutex
|
filtersInitializerLock sync.Mutex
|
||||||
|
|
||||||
// resolver only looks up the IP address of the host while safe search.
|
|
||||||
//
|
|
||||||
// TODO(e.burkov): Use upstream that configured in dnsforward instead.
|
|
||||||
resolver Resolver
|
|
||||||
|
|
||||||
refreshLock *sync.Mutex
|
refreshLock *sync.Mutex
|
||||||
|
|
||||||
// filterTitleRegexp is the regular expression to retrieve a name of a
|
// filterTitleRegexp is the regular expression to retrieve a name of a
|
||||||
|
@ -195,6 +192,7 @@ type DNSFilter struct {
|
||||||
// TODO(e.burkov): Don't use regexp for such a simple text processing task.
|
// TODO(e.burkov): Don't use regexp for such a simple text processing task.
|
||||||
filterTitleRegexp *regexp.Regexp
|
filterTitleRegexp *regexp.Regexp
|
||||||
|
|
||||||
|
safeSearch SafeSearch
|
||||||
hostCheckers []hostChecker
|
hostCheckers []hostChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,7 +296,7 @@ func (d *DNSFilter) GetConfig() (s Settings) {
|
||||||
|
|
||||||
return Settings{
|
return Settings{
|
||||||
FilteringEnabled: atomic.LoadUint32(&d.Config.enabled) != 0,
|
FilteringEnabled: atomic.LoadUint32(&d.Config.enabled) != 0,
|
||||||
SafeSearchEnabled: d.Config.SafeSearchEnabled,
|
SafeSearchEnabled: d.Config.SafeSearchConf.Enabled,
|
||||||
SafeBrowsingEnabled: d.Config.SafeBrowsingEnabled,
|
SafeBrowsingEnabled: d.Config.SafeBrowsingEnabled,
|
||||||
ParentalEnabled: d.Config.ParentalEnabled,
|
ParentalEnabled: d.Config.ParentalEnabled,
|
||||||
}
|
}
|
||||||
|
@ -942,7 +940,6 @@ func InitModule() {
|
||||||
// be non-nil.
|
// be non-nil.
|
||||||
func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
|
func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
|
||||||
d = &DNSFilter{
|
d = &DNSFilter{
|
||||||
resolver: net.DefaultResolver,
|
|
||||||
refreshLock: &sync.Mutex{},
|
refreshLock: &sync.Mutex{},
|
||||||
filterTitleRegexp: regexp.MustCompile(`^! Title: +(.*)$`),
|
filterTitleRegexp: regexp.MustCompile(`^! Title: +(.*)$`),
|
||||||
}
|
}
|
||||||
|
@ -951,18 +948,12 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
|
||||||
EnableLRU: true,
|
EnableLRU: true,
|
||||||
MaxSize: c.SafeBrowsingCacheSize,
|
MaxSize: c.SafeBrowsingCacheSize,
|
||||||
})
|
})
|
||||||
d.safeSearchCache = cache.New(cache.Config{
|
|
||||||
EnableLRU: true,
|
|
||||||
MaxSize: c.SafeSearchCacheSize,
|
|
||||||
})
|
|
||||||
d.parentalCache = cache.New(cache.Config{
|
d.parentalCache = cache.New(cache.Config{
|
||||||
EnableLRU: true,
|
EnableLRU: true,
|
||||||
MaxSize: c.ParentalCacheSize,
|
MaxSize: c.ParentalCacheSize,
|
||||||
})
|
})
|
||||||
|
|
||||||
if r := c.CustomResolver; r != nil {
|
d.safeSearch = c.SafeSearch
|
||||||
d.resolver = r
|
|
||||||
}
|
|
||||||
|
|
||||||
d.hostCheckers = []hostChecker{{
|
d.hostCheckers = []hostChecker{{
|
||||||
check: d.matchSysHosts,
|
check: d.matchSysHosts,
|
||||||
|
|
|
@ -2,10 +2,8 @@ package filtering
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
|
@ -33,7 +31,6 @@ func purgeCaches(d *DNSFilter) {
|
||||||
for _, c := range []cache.Cache{
|
for _, c := range []cache.Cache{
|
||||||
d.safebrowsingCache,
|
d.safebrowsingCache,
|
||||||
d.parentalCache,
|
d.parentalCache,
|
||||||
d.safeSearchCache,
|
|
||||||
} {
|
} {
|
||||||
if c != nil {
|
if c != nil {
|
||||||
c.Clear()
|
c.Clear()
|
||||||
|
@ -51,7 +48,7 @@ func newForTest(t testing.TB, c *Config, filters []Filter) (f *DNSFilter, setts
|
||||||
c.ParentalCacheSize = 10000
|
c.ParentalCacheSize = 10000
|
||||||
c.SafeSearchCacheSize = 1000
|
c.SafeSearchCacheSize = 1000
|
||||||
c.CacheTime = 30
|
c.CacheTime = 30
|
||||||
setts.SafeSearchEnabled = c.SafeSearchEnabled
|
setts.SafeSearchEnabled = c.SafeSearchConf.Enabled
|
||||||
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
|
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
|
||||||
setts.ParentalEnabled = c.ParentalEnabled
|
setts.ParentalEnabled = c.ParentalEnabled
|
||||||
} else {
|
} else {
|
||||||
|
@ -216,164 +213,6 @@ func TestParallelSB(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safe Search.
|
|
||||||
|
|
||||||
func TestSafeSearch(t *testing.T) {
|
|
||||||
d, _ := newForTest(t, &Config{SafeSearchEnabled: true}, nil)
|
|
||||||
t.Cleanup(d.Close)
|
|
||||||
val, ok := d.SafeSearchDomain("www.google.com")
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
assert.Equal(t, "forcesafesearch.google.com", val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckHostSafeSearchYandex(t *testing.T) {
|
|
||||||
d, setts := newForTest(t, &Config{
|
|
||||||
SafeSearchEnabled: true,
|
|
||||||
}, nil)
|
|
||||||
t.Cleanup(d.Close)
|
|
||||||
|
|
||||||
yandexIP := net.IPv4(213, 180, 193, 56)
|
|
||||||
|
|
||||||
// Check host for each domain.
|
|
||||||
for _, host := range []string{
|
|
||||||
"yAndeX.ru",
|
|
||||||
"YANdex.COM",
|
|
||||||
"yandex.ua",
|
|
||||||
"yandex.by",
|
|
||||||
"yandex.kz",
|
|
||||||
"www.yandex.com",
|
|
||||||
} {
|
|
||||||
t.Run(strings.ToLower(host), func(t *testing.T) {
|
|
||||||
res, err := d.CheckHost(host, dns.TypeA, setts)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.True(t, res.IsFiltered)
|
|
||||||
|
|
||||||
require.Len(t, res.Rules, 1)
|
|
||||||
|
|
||||||
assert.Equal(t, yandexIP, res.Rules[0].IP)
|
|
||||||
assert.EqualValues(t, SafeSearchListID, res.Rules[0].FilterListID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckHostSafeSearchGoogle(t *testing.T) {
|
|
||||||
resolver := &aghtest.TestResolver{}
|
|
||||||
d, setts := newForTest(t, &Config{
|
|
||||||
SafeSearchEnabled: true,
|
|
||||||
CustomResolver: resolver,
|
|
||||||
}, nil)
|
|
||||||
t.Cleanup(d.Close)
|
|
||||||
|
|
||||||
ip, _ := resolver.HostToIPs("forcesafesearch.google.com")
|
|
||||||
|
|
||||||
// Check host for each domain.
|
|
||||||
for _, host := range []string{
|
|
||||||
"www.google.com",
|
|
||||||
"www.google.im",
|
|
||||||
"www.google.co.in",
|
|
||||||
"www.google.iq",
|
|
||||||
"www.google.is",
|
|
||||||
"www.google.it",
|
|
||||||
"www.google.je",
|
|
||||||
} {
|
|
||||||
t.Run(host, func(t *testing.T) {
|
|
||||||
res, err := d.CheckHost(host, dns.TypeA, setts)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.True(t, res.IsFiltered)
|
|
||||||
|
|
||||||
require.Len(t, res.Rules, 1)
|
|
||||||
|
|
||||||
assert.Equal(t, ip, res.Rules[0].IP)
|
|
||||||
assert.EqualValues(t, SafeSearchListID, res.Rules[0].FilterListID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSafeSearchCacheYandex(t *testing.T) {
|
|
||||||
d, setts := newForTest(t, nil, nil)
|
|
||||||
t.Cleanup(d.Close)
|
|
||||||
const domain = "yandex.ru"
|
|
||||||
|
|
||||||
// Check host with disabled safesearch.
|
|
||||||
res, err := d.CheckHost(domain, dns.TypeA, setts)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.False(t, res.IsFiltered)
|
|
||||||
|
|
||||||
require.Empty(t, res.Rules)
|
|
||||||
|
|
||||||
yandexIP := net.IPv4(213, 180, 193, 56)
|
|
||||||
|
|
||||||
d, setts = newForTest(t, &Config{SafeSearchEnabled: true}, nil)
|
|
||||||
t.Cleanup(d.Close)
|
|
||||||
|
|
||||||
res, err = d.CheckHost(domain, dns.TypeA, setts)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// For yandex we already know valid IP.
|
|
||||||
require.Len(t, res.Rules, 1)
|
|
||||||
assert.Equal(t, res.Rules[0].IP, yandexIP)
|
|
||||||
|
|
||||||
// Check cache.
|
|
||||||
cachedValue, isFound := getCachedResult(d.safeSearchCache, domain)
|
|
||||||
require.True(t, isFound)
|
|
||||||
require.Len(t, cachedValue.Rules, 1)
|
|
||||||
|
|
||||||
assert.Equal(t, cachedValue.Rules[0].IP, yandexIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSafeSearchCacheGoogle(t *testing.T) {
|
|
||||||
resolver := &aghtest.TestResolver{}
|
|
||||||
d, setts := newForTest(t, &Config{
|
|
||||||
CustomResolver: resolver,
|
|
||||||
}, nil)
|
|
||||||
t.Cleanup(d.Close)
|
|
||||||
|
|
||||||
const domain = "www.google.ru"
|
|
||||||
res, err := d.CheckHost(domain, dns.TypeA, setts)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.False(t, res.IsFiltered)
|
|
||||||
|
|
||||||
require.Empty(t, res.Rules)
|
|
||||||
|
|
||||||
d, setts = newForTest(t, &Config{SafeSearchEnabled: true}, nil)
|
|
||||||
t.Cleanup(d.Close)
|
|
||||||
d.resolver = resolver
|
|
||||||
|
|
||||||
// Lookup for safesearch domain.
|
|
||||||
safeDomain, ok := d.SafeSearchDomain(domain)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
ips, err := resolver.LookupIP(context.Background(), "ip", safeDomain)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var ip net.IP
|
|
||||||
for _, foundIP := range ips {
|
|
||||||
if foundIP.To4() != nil {
|
|
||||||
ip = foundIP
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err = d.CheckHost(domain, dns.TypeA, setts)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, res.Rules, 1)
|
|
||||||
|
|
||||||
assert.True(t, res.Rules[0].IP.Equal(ip))
|
|
||||||
|
|
||||||
// Check cache.
|
|
||||||
cachedValue, isFound := getCachedResult(d.safeSearchCache, domain)
|
|
||||||
require.True(t, isFound)
|
|
||||||
require.Len(t, cachedValue.Rules, 1)
|
|
||||||
|
|
||||||
assert.True(t, cachedValue.Rules[0].IP.Equal(ip))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parental.
|
// Parental.
|
||||||
|
|
||||||
func TestParentalControl(t *testing.T) {
|
func TestParentalControl(t *testing.T) {
|
||||||
|
@ -854,27 +693,3 @@ func BenchmarkSafeBrowsingParallel(b *testing.B) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkSafeSearch(b *testing.B) {
|
|
||||||
d, _ := newForTest(b, &Config{SafeSearchEnabled: true}, nil)
|
|
||||||
b.Cleanup(d.Close)
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
val, ok := d.SafeSearchDomain("www.google.com")
|
|
||||||
require.True(b, ok)
|
|
||||||
|
|
||||||
assert.Equal(b, "forcesafesearch.google.com", val, "Expected safesearch for google.com to be forcesafesearch.google.com")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSafeSearchParallel(b *testing.B) {
|
|
||||||
d, _ := newForTest(b, &Config{SafeSearchEnabled: true}, nil)
|
|
||||||
b.Cleanup(d.Close)
|
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
|
||||||
for pb.Next() {
|
|
||||||
val, ok := d.SafeSearchDomain("www.google.com")
|
|
||||||
require.True(b, ok)
|
|
||||||
|
|
||||||
assert.Equal(b, "forcesafesearch.google.com", val, "Expected safesearch for google.com to be forcesafesearch.google.com")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,19 +1,8 @@
|
||||||
package filtering
|
package filtering
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/gob"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
|
||||||
"github.com/AdguardTeam/golibs/cache"
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
|
||||||
"github.com/AdguardTeam/urlfilter/rules"
|
"github.com/AdguardTeam/urlfilter/rules"
|
||||||
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SafeSearch interface describes a service for search engines hosts rewrites.
|
// SafeSearch interface describes a service for search engines hosts rewrites.
|
||||||
|
@ -44,60 +33,8 @@ type SafeSearchConfig struct {
|
||||||
YouTube bool `yaml:"youtube" json:"youtube"`
|
YouTube bool `yaml:"youtube" json:"youtube"`
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// checkSafeSearch checks host with safe search engine. Matches
|
||||||
expire byte[4]
|
// [hostChecker.check].
|
||||||
res Result
|
|
||||||
*/
|
|
||||||
func (d *DNSFilter) setCacheResult(cache cache.Cache, host string, res Result) int {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
|
|
||||||
exp := make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint32(exp, uint32(expire))
|
|
||||||
_, _ = buf.Write(exp)
|
|
||||||
|
|
||||||
enc := gob.NewEncoder(&buf)
|
|
||||||
err := enc.Encode(res)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("gob.Encode(): %s", err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
val := buf.Bytes()
|
|
||||||
_ = cache.Set([]byte(host), val)
|
|
||||||
return len(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCachedResult(cache cache.Cache, host string) (Result, bool) {
|
|
||||||
data := cache.Get([]byte(host))
|
|
||||||
if data == nil {
|
|
||||||
return Result{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
exp := int(binary.BigEndian.Uint32(data[:4]))
|
|
||||||
if exp <= int(time.Now().Unix()) {
|
|
||||||
cache.Del([]byte(host))
|
|
||||||
return Result{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
buf.Write(data[4:])
|
|
||||||
dec := gob.NewDecoder(&buf)
|
|
||||||
r := Result{}
|
|
||||||
err := dec.Decode(&r)
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("gob.Decode(): %s", err)
|
|
||||||
return Result{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SafeSearchDomain returns replacement address for search engine
|
|
||||||
func (d *DNSFilter) SafeSearchDomain(host string) (string, bool) {
|
|
||||||
val, ok := safeSearchDomains[host]
|
|
||||||
return val, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DNSFilter) checkSafeSearch(
|
func (d *DNSFilter) checkSafeSearch(
|
||||||
host string,
|
host string,
|
||||||
_ uint16,
|
_ uint16,
|
||||||
|
@ -107,295 +44,14 @@ func (d *DNSFilter) checkSafeSearch(
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if log.GetLevel() >= log.DEBUG {
|
if d.safeSearch == nil {
|
||||||
timer := log.StartTimer()
|
|
||||||
defer timer.LogElapsed("SafeSearch: lookup for %s", host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check cache. Return cached result if it was found
|
|
||||||
cachedValue, isFound := getCachedResult(d.safeSearchCache, host)
|
|
||||||
if isFound {
|
|
||||||
// atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1)
|
|
||||||
log.Tracef("SafeSearch: found in cache: %s", host)
|
|
||||||
return cachedValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
safeHost, ok := d.SafeSearchDomain(host)
|
|
||||||
if !ok {
|
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
res = Result{
|
clientSafeSearch := setts.ClientSafeSearch
|
||||||
Rules: []*ResultRule{{
|
if clientSafeSearch != nil {
|
||||||
FilterListID: SafeSearchListID,
|
return clientSafeSearch.CheckHost(host, dns.TypeA)
|
||||||
}},
|
|
||||||
Reason: FilteredSafeSearch,
|
|
||||||
IsFiltered: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip := net.ParseIP(safeHost); ip != nil {
|
return d.safeSearch.CheckHost(host, dns.TypeA)
|
||||||
res.Rules[0].IP = ip
|
|
||||||
valLen := d.setCacheResult(d.safeSearchCache, host, res)
|
|
||||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ips, err := d.resolver.LookupIP(context.Background(), "ip", safeHost)
|
|
||||||
if err != nil {
|
|
||||||
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
|
|
||||||
return Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ip := range ips {
|
|
||||||
if ip = ip.To4(); ip == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Rules[0].IP = ip
|
|
||||||
|
|
||||||
l := d.setCacheResult(d.safeSearchCache, host, res)
|
|
||||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, l)
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DNSFilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
|
|
||||||
setProtectedBool(&d.confLock, &d.Config.SafeSearchEnabled, true)
|
|
||||||
d.Config.ConfigModified()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
|
|
||||||
setProtectedBool(&d.confLock, &d.Config.SafeSearchEnabled, false)
|
|
||||||
d.Config.ConfigModified()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
|
|
||||||
resp := &struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
}{
|
|
||||||
Enabled: protectedBool(&d.confLock, &d.Config.SafeSearchEnabled),
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
var safeSearchDomains = map[string]string{
|
|
||||||
"yandex.com": "213.180.193.56",
|
|
||||||
"yandex.ru": "213.180.193.56",
|
|
||||||
"yandex.ua": "213.180.193.56",
|
|
||||||
"yandex.by": "213.180.193.56",
|
|
||||||
"yandex.kz": "213.180.193.56",
|
|
||||||
"www.yandex.com": "213.180.193.56",
|
|
||||||
"www.yandex.ru": "213.180.193.56",
|
|
||||||
"www.yandex.ua": "213.180.193.56",
|
|
||||||
"www.yandex.by": "213.180.193.56",
|
|
||||||
"www.yandex.kz": "213.180.193.56",
|
|
||||||
|
|
||||||
"www.bing.com": "strict.bing.com",
|
|
||||||
|
|
||||||
"duckduckgo.com": "safe.duckduckgo.com",
|
|
||||||
"www.duckduckgo.com": "safe.duckduckgo.com",
|
|
||||||
"start.duckduckgo.com": "safe.duckduckgo.com",
|
|
||||||
|
|
||||||
"www.google.com": "forcesafesearch.google.com",
|
|
||||||
"www.google.ad": "forcesafesearch.google.com",
|
|
||||||
"www.google.ae": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.af": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.ag": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.ai": "forcesafesearch.google.com",
|
|
||||||
"www.google.al": "forcesafesearch.google.com",
|
|
||||||
"www.google.am": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.ao": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.ar": "forcesafesearch.google.com",
|
|
||||||
"www.google.as": "forcesafesearch.google.com",
|
|
||||||
"www.google.at": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.au": "forcesafesearch.google.com",
|
|
||||||
"www.google.az": "forcesafesearch.google.com",
|
|
||||||
"www.google.ba": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.bd": "forcesafesearch.google.com",
|
|
||||||
"www.google.be": "forcesafesearch.google.com",
|
|
||||||
"www.google.bf": "forcesafesearch.google.com",
|
|
||||||
"www.google.bg": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.bh": "forcesafesearch.google.com",
|
|
||||||
"www.google.bi": "forcesafesearch.google.com",
|
|
||||||
"www.google.bj": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.bn": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.bo": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.br": "forcesafesearch.google.com",
|
|
||||||
"www.google.bs": "forcesafesearch.google.com",
|
|
||||||
"www.google.bt": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.bw": "forcesafesearch.google.com",
|
|
||||||
"www.google.by": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.bz": "forcesafesearch.google.com",
|
|
||||||
"www.google.ca": "forcesafesearch.google.com",
|
|
||||||
"www.google.cd": "forcesafesearch.google.com",
|
|
||||||
"www.google.cf": "forcesafesearch.google.com",
|
|
||||||
"www.google.cg": "forcesafesearch.google.com",
|
|
||||||
"www.google.ch": "forcesafesearch.google.com",
|
|
||||||
"www.google.ci": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.ck": "forcesafesearch.google.com",
|
|
||||||
"www.google.cl": "forcesafesearch.google.com",
|
|
||||||
"www.google.cm": "forcesafesearch.google.com",
|
|
||||||
"www.google.cn": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.co": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.cr": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.cu": "forcesafesearch.google.com",
|
|
||||||
"www.google.cv": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.cy": "forcesafesearch.google.com",
|
|
||||||
"www.google.cz": "forcesafesearch.google.com",
|
|
||||||
"www.google.de": "forcesafesearch.google.com",
|
|
||||||
"www.google.dj": "forcesafesearch.google.com",
|
|
||||||
"www.google.dk": "forcesafesearch.google.com",
|
|
||||||
"www.google.dm": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.do": "forcesafesearch.google.com",
|
|
||||||
"www.google.dz": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.ec": "forcesafesearch.google.com",
|
|
||||||
"www.google.ee": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.eg": "forcesafesearch.google.com",
|
|
||||||
"www.google.es": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.et": "forcesafesearch.google.com",
|
|
||||||
"www.google.fi": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.fj": "forcesafesearch.google.com",
|
|
||||||
"www.google.fm": "forcesafesearch.google.com",
|
|
||||||
"www.google.fr": "forcesafesearch.google.com",
|
|
||||||
"www.google.ga": "forcesafesearch.google.com",
|
|
||||||
"www.google.ge": "forcesafesearch.google.com",
|
|
||||||
"www.google.gg": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.gh": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.gi": "forcesafesearch.google.com",
|
|
||||||
"www.google.gl": "forcesafesearch.google.com",
|
|
||||||
"www.google.gm": "forcesafesearch.google.com",
|
|
||||||
"www.google.gp": "forcesafesearch.google.com",
|
|
||||||
"www.google.gr": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.gt": "forcesafesearch.google.com",
|
|
||||||
"www.google.gy": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.hk": "forcesafesearch.google.com",
|
|
||||||
"www.google.hn": "forcesafesearch.google.com",
|
|
||||||
"www.google.hr": "forcesafesearch.google.com",
|
|
||||||
"www.google.ht": "forcesafesearch.google.com",
|
|
||||||
"www.google.hu": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.id": "forcesafesearch.google.com",
|
|
||||||
"www.google.ie": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.il": "forcesafesearch.google.com",
|
|
||||||
"www.google.im": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.in": "forcesafesearch.google.com",
|
|
||||||
"www.google.iq": "forcesafesearch.google.com",
|
|
||||||
"www.google.is": "forcesafesearch.google.com",
|
|
||||||
"www.google.it": "forcesafesearch.google.com",
|
|
||||||
"www.google.je": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.jm": "forcesafesearch.google.com",
|
|
||||||
"www.google.jo": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.jp": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.ke": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.kh": "forcesafesearch.google.com",
|
|
||||||
"www.google.ki": "forcesafesearch.google.com",
|
|
||||||
"www.google.kg": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.kr": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.kw": "forcesafesearch.google.com",
|
|
||||||
"www.google.kz": "forcesafesearch.google.com",
|
|
||||||
"www.google.la": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.lb": "forcesafesearch.google.com",
|
|
||||||
"www.google.li": "forcesafesearch.google.com",
|
|
||||||
"www.google.lk": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.ls": "forcesafesearch.google.com",
|
|
||||||
"www.google.lt": "forcesafesearch.google.com",
|
|
||||||
"www.google.lu": "forcesafesearch.google.com",
|
|
||||||
"www.google.lv": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.ly": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.ma": "forcesafesearch.google.com",
|
|
||||||
"www.google.md": "forcesafesearch.google.com",
|
|
||||||
"www.google.me": "forcesafesearch.google.com",
|
|
||||||
"www.google.mg": "forcesafesearch.google.com",
|
|
||||||
"www.google.mk": "forcesafesearch.google.com",
|
|
||||||
"www.google.ml": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.mm": "forcesafesearch.google.com",
|
|
||||||
"www.google.mn": "forcesafesearch.google.com",
|
|
||||||
"www.google.ms": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.mt": "forcesafesearch.google.com",
|
|
||||||
"www.google.mu": "forcesafesearch.google.com",
|
|
||||||
"www.google.mv": "forcesafesearch.google.com",
|
|
||||||
"www.google.mw": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.mx": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.my": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.mz": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.na": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.nf": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.ng": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.ni": "forcesafesearch.google.com",
|
|
||||||
"www.google.ne": "forcesafesearch.google.com",
|
|
||||||
"www.google.nl": "forcesafesearch.google.com",
|
|
||||||
"www.google.no": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.np": "forcesafesearch.google.com",
|
|
||||||
"www.google.nr": "forcesafesearch.google.com",
|
|
||||||
"www.google.nu": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.nz": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.om": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.pa": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.pe": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.pg": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.ph": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.pk": "forcesafesearch.google.com",
|
|
||||||
"www.google.pl": "forcesafesearch.google.com",
|
|
||||||
"www.google.pn": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.pr": "forcesafesearch.google.com",
|
|
||||||
"www.google.ps": "forcesafesearch.google.com",
|
|
||||||
"www.google.pt": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.py": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.qa": "forcesafesearch.google.com",
|
|
||||||
"www.google.ro": "forcesafesearch.google.com",
|
|
||||||
"www.google.ru": "forcesafesearch.google.com",
|
|
||||||
"www.google.rw": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.sa": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.sb": "forcesafesearch.google.com",
|
|
||||||
"www.google.sc": "forcesafesearch.google.com",
|
|
||||||
"www.google.se": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.sg": "forcesafesearch.google.com",
|
|
||||||
"www.google.sh": "forcesafesearch.google.com",
|
|
||||||
"www.google.si": "forcesafesearch.google.com",
|
|
||||||
"www.google.sk": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.sl": "forcesafesearch.google.com",
|
|
||||||
"www.google.sn": "forcesafesearch.google.com",
|
|
||||||
"www.google.so": "forcesafesearch.google.com",
|
|
||||||
"www.google.sm": "forcesafesearch.google.com",
|
|
||||||
"www.google.sr": "forcesafesearch.google.com",
|
|
||||||
"www.google.st": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.sv": "forcesafesearch.google.com",
|
|
||||||
"www.google.td": "forcesafesearch.google.com",
|
|
||||||
"www.google.tg": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.th": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.tj": "forcesafesearch.google.com",
|
|
||||||
"www.google.tk": "forcesafesearch.google.com",
|
|
||||||
"www.google.tl": "forcesafesearch.google.com",
|
|
||||||
"www.google.tm": "forcesafesearch.google.com",
|
|
||||||
"www.google.tn": "forcesafesearch.google.com",
|
|
||||||
"www.google.to": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.tr": "forcesafesearch.google.com",
|
|
||||||
"www.google.tt": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.tw": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.tz": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.ua": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.ug": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.uk": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.uy": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.uz": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.vc": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.ve": "forcesafesearch.google.com",
|
|
||||||
"www.google.vg": "forcesafesearch.google.com",
|
|
||||||
"www.google.co.vi": "forcesafesearch.google.com",
|
|
||||||
"www.google.com.vn": "forcesafesearch.google.com",
|
|
||||||
"www.google.vu": "forcesafesearch.google.com",
|
|
||||||
"www.google.ws": "forcesafesearch.google.com",
|
|
||||||
"www.google.rs": "forcesafesearch.google.com",
|
|
||||||
|
|
||||||
"www.youtube.com": "restrictmoderate.youtube.com",
|
|
||||||
"m.youtube.com": "restrictmoderate.youtube.com",
|
|
||||||
"youtubei.googleapis.com": "restrictmoderate.youtube.com",
|
|
||||||
"youtube.googleapis.com": "restrictmoderate.youtube.com",
|
|
||||||
"www.youtube-nocookie.com": "restrictmoderate.youtube.com",
|
|
||||||
|
|
||||||
"pixabay.com": "safesearch.pixabay.com",
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package filtering
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(d.kolyshev): Replace handlers below with the new API.
|
||||||
|
|
||||||
|
func (d *DNSFilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
|
setProtectedBool(&d.confLock, &d.Config.SafeSearchConf.Enabled, true)
|
||||||
|
d.Config.ConfigModified()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
|
||||||
|
setProtectedBool(&d.confLock, &d.Config.SafeSearchConf.Enabled, false)
|
||||||
|
d.Config.ConfigModified()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
resp := &struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}{
|
||||||
|
Enabled: protectedBool(&d.confLock, &d.Config.SafeSearchConf.Enabled),
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding"
|
"encoding"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,6 +16,9 @@ type Client struct {
|
||||||
// these upstream must be used.
|
// these upstream must be used.
|
||||||
upstreamConfig *proxy.UpstreamConfig
|
upstreamConfig *proxy.UpstreamConfig
|
||||||
|
|
||||||
|
safeSearchConf filtering.SafeSearchConfig
|
||||||
|
SafeSearch filtering.SafeSearch
|
||||||
|
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
IDs []string
|
IDs []string
|
||||||
|
@ -24,7 +28,6 @@ type Client struct {
|
||||||
|
|
||||||
UseOwnSettings bool
|
UseOwnSettings bool
|
||||||
FilteringEnabled bool
|
FilteringEnabled bool
|
||||||
SafeSearchEnabled bool
|
|
||||||
SafeBrowsingEnabled bool
|
SafeBrowsingEnabled bool
|
||||||
ParentalEnabled bool
|
ParentalEnabled bool
|
||||||
UseOwnBlockedServices bool
|
UseOwnBlockedServices bool
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
|
@ -69,6 +70,7 @@ func (clients *clientsContainer) Init(
|
||||||
dhcpServer dhcpd.Interface,
|
dhcpServer dhcpd.Interface,
|
||||||
etcHosts *aghnet.HostsContainer,
|
etcHosts *aghnet.HostsContainer,
|
||||||
arpdb aghnet.ARPDB,
|
arpdb aghnet.ARPDB,
|
||||||
|
filteringConf *filtering.Config,
|
||||||
) {
|
) {
|
||||||
if clients.list != nil {
|
if clients.list != nil {
|
||||||
log.Fatal("clients.list != nil")
|
log.Fatal("clients.list != nil")
|
||||||
|
@ -82,7 +84,7 @@ func (clients *clientsContainer) Init(
|
||||||
clients.dhcpServer = dhcpServer
|
clients.dhcpServer = dhcpServer
|
||||||
clients.etcHosts = etcHosts
|
clients.etcHosts = etcHosts
|
||||||
clients.arpdb = arpdb
|
clients.arpdb = arpdb
|
||||||
clients.addFromConfig(objects)
|
clients.addFromConfig(objects, filteringConf)
|
||||||
|
|
||||||
if clients.testing {
|
if clients.testing {
|
||||||
return
|
return
|
||||||
|
@ -133,6 +135,8 @@ func (clients *clientsContainer) reloadARP() {
|
||||||
|
|
||||||
// clientObject is the YAML representation of a persistent client.
|
// clientObject is the YAML representation of a persistent client.
|
||||||
type clientObject struct {
|
type clientObject struct {
|
||||||
|
SafeSearchConf filtering.SafeSearchConfig `yaml:"safe_search"`
|
||||||
|
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
|
|
||||||
Tags []string `yaml:"tags"`
|
Tags []string `yaml:"tags"`
|
||||||
|
@ -143,14 +147,13 @@ type clientObject struct {
|
||||||
UseGlobalSettings bool `yaml:"use_global_settings"`
|
UseGlobalSettings bool `yaml:"use_global_settings"`
|
||||||
FilteringEnabled bool `yaml:"filtering_enabled"`
|
FilteringEnabled bool `yaml:"filtering_enabled"`
|
||||||
ParentalEnabled bool `yaml:"parental_enabled"`
|
ParentalEnabled bool `yaml:"parental_enabled"`
|
||||||
SafeSearchEnabled bool `yaml:"safesearch_enabled"`
|
|
||||||
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
|
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
|
||||||
UseGlobalBlockedServices bool `yaml:"use_global_blocked_services"`
|
UseGlobalBlockedServices bool `yaml:"use_global_blocked_services"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// addFromConfig initializes the clients container with objects from the
|
// addFromConfig initializes the clients container with objects from the
|
||||||
// configuration file.
|
// configuration file.
|
||||||
func (clients *clientsContainer) addFromConfig(objects []*clientObject) {
|
func (clients *clientsContainer) addFromConfig(objects []*clientObject, filteringConf *filtering.Config) {
|
||||||
for _, o := range objects {
|
for _, o := range objects {
|
||||||
cli := &Client{
|
cli := &Client{
|
||||||
Name: o.Name,
|
Name: o.Name,
|
||||||
|
@ -161,11 +164,28 @@ func (clients *clientsContainer) addFromConfig(objects []*clientObject) {
|
||||||
UseOwnSettings: !o.UseGlobalSettings,
|
UseOwnSettings: !o.UseGlobalSettings,
|
||||||
FilteringEnabled: o.FilteringEnabled,
|
FilteringEnabled: o.FilteringEnabled,
|
||||||
ParentalEnabled: o.ParentalEnabled,
|
ParentalEnabled: o.ParentalEnabled,
|
||||||
SafeSearchEnabled: o.SafeSearchEnabled,
|
safeSearchConf: o.SafeSearchConf,
|
||||||
SafeBrowsingEnabled: o.SafeBrowsingEnabled,
|
SafeBrowsingEnabled: o.SafeBrowsingEnabled,
|
||||||
UseOwnBlockedServices: !o.UseGlobalBlockedServices,
|
UseOwnBlockedServices: !o.UseGlobalBlockedServices,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.SafeSearchConf.Enabled {
|
||||||
|
o.SafeSearchConf.CustomResolver = safeSearchResolver{}
|
||||||
|
|
||||||
|
ss, err := safesearch.NewDefaultSafeSearch(
|
||||||
|
o.SafeSearchConf,
|
||||||
|
filteringConf.SafeSearchCacheSize,
|
||||||
|
time.Minute*time.Duration(filteringConf.CacheTime),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("clients: init client safesearch %s: %s", cli.Name, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.SafeSearch = ss
|
||||||
|
}
|
||||||
|
|
||||||
for _, s := range o.BlockedServices {
|
for _, s := range o.BlockedServices {
|
||||||
if filtering.BlockedSvcKnown(s) {
|
if filtering.BlockedSvcKnown(s) {
|
||||||
cli.BlockedServices = append(cli.BlockedServices, s)
|
cli.BlockedServices = append(cli.BlockedServices, s)
|
||||||
|
@ -210,7 +230,7 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
|
||||||
UseGlobalSettings: !cli.UseOwnSettings,
|
UseGlobalSettings: !cli.UseOwnSettings,
|
||||||
FilteringEnabled: cli.FilteringEnabled,
|
FilteringEnabled: cli.FilteringEnabled,
|
||||||
ParentalEnabled: cli.ParentalEnabled,
|
ParentalEnabled: cli.ParentalEnabled,
|
||||||
SafeSearchEnabled: cli.SafeSearchEnabled,
|
SafeSearchConf: cli.safeSearchConf,
|
||||||
SafeBrowsingEnabled: cli.SafeBrowsingEnabled,
|
SafeBrowsingEnabled: cli.SafeBrowsingEnabled,
|
||||||
UseGlobalBlockedServices: !cli.UseOwnBlockedServices,
|
UseGlobalBlockedServices: !cli.UseOwnBlockedServices,
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ func TestClients(t *testing.T) {
|
||||||
clients := clientsContainer{}
|
clients := clientsContainer{}
|
||||||
clients.testing = true
|
clients.testing = true
|
||||||
|
|
||||||
clients.Init(nil, nil, nil, nil)
|
clients.Init(nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
t.Run("add_success", func(t *testing.T) {
|
t.Run("add_success", func(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
|
@ -201,7 +201,7 @@ func TestClientsWHOIS(t *testing.T) {
|
||||||
clients := clientsContainer{
|
clients := clientsContainer{
|
||||||
testing: true,
|
testing: true,
|
||||||
}
|
}
|
||||||
clients.Init(nil, nil, nil, nil)
|
clients.Init(nil, nil, nil, nil, nil)
|
||||||
whois := &RuntimeClientWHOISInfo{
|
whois := &RuntimeClientWHOISInfo{
|
||||||
Country: "AU",
|
Country: "AU",
|
||||||
Orgname: "Example Org",
|
Orgname: "Example Org",
|
||||||
|
@ -250,7 +250,7 @@ func TestClientsAddExisting(t *testing.T) {
|
||||||
clients := clientsContainer{
|
clients := clientsContainer{
|
||||||
testing: true,
|
testing: true,
|
||||||
}
|
}
|
||||||
clients.Init(nil, nil, nil, nil)
|
clients.Init(nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
t.Run("simple", func(t *testing.T) {
|
t.Run("simple", func(t *testing.T) {
|
||||||
ip := netip.MustParseAddr("1.1.1.1")
|
ip := netip.MustParseAddr("1.1.1.1")
|
||||||
|
@ -328,7 +328,7 @@ func TestClientsCustomUpstream(t *testing.T) {
|
||||||
clients := clientsContainer{
|
clients := clientsContainer{
|
||||||
testing: true,
|
testing: true,
|
||||||
}
|
}
|
||||||
clients.Init(nil, nil, nil, nil)
|
clients.Init(nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
// Add client with upstreams.
|
// Add client with upstreams.
|
||||||
ok, err := clients.Add(&Client{
|
ok, err := clients.Add(&Client{
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
)
|
)
|
||||||
|
|
||||||
// clientJSON is a common structure used by several handlers to deal with
|
// clientJSON is a common structure used by several handlers to deal with
|
||||||
|
@ -35,9 +36,10 @@ type clientJSON struct {
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
Upstreams []string `json:"upstreams"`
|
Upstreams []string `json:"upstreams"`
|
||||||
|
|
||||||
FilteringEnabled bool `json:"filtering_enabled"`
|
FilteringEnabled bool `json:"filtering_enabled"`
|
||||||
ParentalEnabled bool `json:"parental_enabled"`
|
ParentalEnabled bool `json:"parental_enabled"`
|
||||||
SafeBrowsingEnabled bool `json:"safebrowsing_enabled"`
|
SafeBrowsingEnabled bool `json:"safebrowsing_enabled"`
|
||||||
|
// Deprecated: use safeSearchConf.
|
||||||
SafeSearchEnabled bool `json:"safesearch_enabled"`
|
SafeSearchEnabled bool `json:"safesearch_enabled"`
|
||||||
UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
|
UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
|
||||||
UseGlobalSettings bool `json:"use_global_settings"`
|
UseGlobalSettings bool `json:"use_global_settings"`
|
||||||
|
@ -88,6 +90,20 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
|
||||||
|
|
||||||
// Convert JSON object to Client object
|
// Convert JSON object to Client object
|
||||||
func jsonToClient(cj clientJSON) (c *Client) {
|
func jsonToClient(cj clientJSON) (c *Client) {
|
||||||
|
// TODO(d.kolyshev): Remove after cleaning the deprecated
|
||||||
|
// [clientJSON.SafeSearchEnabled] field.
|
||||||
|
safeSearchConf := filtering.SafeSearchConfig{Enabled: cj.SafeSearchEnabled}
|
||||||
|
|
||||||
|
// Set default service flags for enabled safesearch.
|
||||||
|
if safeSearchConf.Enabled {
|
||||||
|
safeSearchConf.Bing = true
|
||||||
|
safeSearchConf.DuckDuckGo = true
|
||||||
|
safeSearchConf.Google = true
|
||||||
|
safeSearchConf.Pixabay = true
|
||||||
|
safeSearchConf.Yandex = true
|
||||||
|
safeSearchConf.YouTube = true
|
||||||
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
Name: cj.Name,
|
Name: cj.Name,
|
||||||
IDs: cj.IDs,
|
IDs: cj.IDs,
|
||||||
|
@ -95,7 +111,7 @@ func jsonToClient(cj clientJSON) (c *Client) {
|
||||||
UseOwnSettings: !cj.UseGlobalSettings,
|
UseOwnSettings: !cj.UseGlobalSettings,
|
||||||
FilteringEnabled: cj.FilteringEnabled,
|
FilteringEnabled: cj.FilteringEnabled,
|
||||||
ParentalEnabled: cj.ParentalEnabled,
|
ParentalEnabled: cj.ParentalEnabled,
|
||||||
SafeSearchEnabled: cj.SafeSearchEnabled,
|
safeSearchConf: safeSearchConf,
|
||||||
SafeBrowsingEnabled: cj.SafeBrowsingEnabled,
|
SafeBrowsingEnabled: cj.SafeBrowsingEnabled,
|
||||||
|
|
||||||
UseOwnBlockedServices: !cj.UseGlobalBlockedServices,
|
UseOwnBlockedServices: !cj.UseGlobalBlockedServices,
|
||||||
|
@ -107,6 +123,11 @@ func jsonToClient(cj clientJSON) (c *Client) {
|
||||||
|
|
||||||
// Convert Client object to JSON
|
// Convert Client object to JSON
|
||||||
func clientToJSON(c *Client) (cj *clientJSON) {
|
func clientToJSON(c *Client) (cj *clientJSON) {
|
||||||
|
// TODO(d.kolyshev): Remove after cleaning the deprecated
|
||||||
|
// [clientJSON.SafeSearchEnabled] field.
|
||||||
|
cloneVal := c.safeSearchConf
|
||||||
|
safeSearchConf := &cloneVal
|
||||||
|
|
||||||
return &clientJSON{
|
return &clientJSON{
|
||||||
Name: c.Name,
|
Name: c.Name,
|
||||||
IDs: c.IDs,
|
IDs: c.IDs,
|
||||||
|
@ -114,7 +135,7 @@ func clientToJSON(c *Client) (cj *clientJSON) {
|
||||||
UseGlobalSettings: !c.UseOwnSettings,
|
UseGlobalSettings: !c.UseOwnSettings,
|
||||||
FilteringEnabled: c.FilteringEnabled,
|
FilteringEnabled: c.FilteringEnabled,
|
||||||
ParentalEnabled: c.ParentalEnabled,
|
ParentalEnabled: c.ParentalEnabled,
|
||||||
SafeSearchEnabled: c.SafeSearchEnabled,
|
SafeSearchEnabled: safeSearchConf.Enabled,
|
||||||
SafeBrowsingEnabled: c.SafeBrowsingEnabled,
|
SafeBrowsingEnabled: c.SafeBrowsingEnabled,
|
||||||
|
|
||||||
UseGlobalBlockedServices: !c.UseOwnBlockedServices,
|
UseGlobalBlockedServices: !c.UseOwnBlockedServices,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
@ -426,7 +427,8 @@ func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering
|
||||||
}
|
}
|
||||||
|
|
||||||
setts.FilteringEnabled = c.FilteringEnabled
|
setts.FilteringEnabled = c.FilteringEnabled
|
||||||
setts.SafeSearchEnabled = c.SafeSearchEnabled
|
setts.SafeSearchEnabled = c.safeSearchConf.Enabled
|
||||||
|
setts.ClientSafeSearch = c.SafeSearch
|
||||||
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
|
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
|
||||||
setts.ParentalEnabled = c.ParentalEnabled
|
setts.ParentalEnabled = c.ParentalEnabled
|
||||||
}
|
}
|
||||||
|
@ -556,3 +558,29 @@ func nonDupEmptyHostNames(list []string) (set *stringutil.Set, err error) {
|
||||||
|
|
||||||
return set, nil
|
return set, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// safeSearchResolver is a [filtering.Resolver] implementation used for safe
|
||||||
|
// search.
|
||||||
|
type safeSearchResolver struct{}
|
||||||
|
|
||||||
|
// type check
|
||||||
|
var _ filtering.Resolver = safeSearchResolver{}
|
||||||
|
|
||||||
|
// LookupIP implements [filtering.Resolver] interface for safeSearchResolver.
|
||||||
|
// It returns the slice of net.IP with IPv4 and IPv6 instances.
|
||||||
|
func (r safeSearchResolver) LookupIP(_ context.Context, _, host string) (ips []net.IP, err error) {
|
||||||
|
addrs, err := Context.dnsServer.Resolve(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
return nil, fmt.Errorf("couldn't lookup host: %s", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range addrs {
|
||||||
|
ips = append(ips, a.IP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
||||||
|
@ -298,6 +299,16 @@ func setupConfig(opts options) (err error) {
|
||||||
config.DNS.DnsfilterConf.UserRules = slices.Clone(config.UserRules)
|
config.DNS.DnsfilterConf.UserRules = slices.Clone(config.UserRules)
|
||||||
config.DNS.DnsfilterConf.HTTPClient = Context.client
|
config.DNS.DnsfilterConf.HTTPClient = Context.client
|
||||||
|
|
||||||
|
config.DNS.DnsfilterConf.SafeSearchConf.CustomResolver = safeSearchResolver{}
|
||||||
|
config.DNS.DnsfilterConf.SafeSearch, err = safesearch.NewDefaultSafeSearch(
|
||||||
|
config.DNS.DnsfilterConf.SafeSearchConf,
|
||||||
|
config.DNS.DnsfilterConf.SafeSearchCacheSize,
|
||||||
|
time.Minute*time.Duration(config.DNS.DnsfilterConf.CacheTime),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("initializing safesearch: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
config.DHCP.WorkDir = Context.workDir
|
config.DHCP.WorkDir = Context.workDir
|
||||||
config.DHCP.HTTPRegister = httpRegister
|
config.DHCP.HTTPRegister = httpRegister
|
||||||
config.DHCP.ConfigModified = onConfigModified
|
config.DHCP.ConfigModified = onConfigModified
|
||||||
|
@ -328,33 +339,16 @@ func setupConfig(opts options) (err error) {
|
||||||
arpdb = aghnet.NewARPDB()
|
arpdb = aghnet.NewARPDB()
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.clients.Init(config.Clients.Persistent, Context.dhcpServer, Context.etcHosts, arpdb)
|
Context.clients.Init(config.Clients.Persistent, Context.dhcpServer, Context.etcHosts, arpdb, config.DNS.DnsfilterConf)
|
||||||
|
|
||||||
if opts.bindPort != 0 {
|
if opts.bindPort != 0 {
|
||||||
tcpPorts := aghalg.UniqChecker[tcpPort]{}
|
|
||||||
addPorts(tcpPorts, tcpPort(opts.bindPort))
|
|
||||||
|
|
||||||
udpPorts := aghalg.UniqChecker[udpPort]{}
|
|
||||||
addPorts(udpPorts, udpPort(config.DNS.Port))
|
|
||||||
|
|
||||||
if config.TLS.Enabled {
|
|
||||||
addPorts(
|
|
||||||
tcpPorts,
|
|
||||||
tcpPort(config.TLS.PortHTTPS),
|
|
||||||
tcpPort(config.TLS.PortDNSOverTLS),
|
|
||||||
tcpPort(config.TLS.PortDNSCrypt),
|
|
||||||
)
|
|
||||||
|
|
||||||
addPorts(udpPorts, udpPort(config.TLS.PortDNSOverQUIC))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tcpPorts.Validate(); err != nil {
|
|
||||||
return fmt.Errorf("validating tcp ports: %w", err)
|
|
||||||
} else if err = udpPorts.Validate(); err != nil {
|
|
||||||
return fmt.Errorf("validating udp ports: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.BindPort = opts.bindPort
|
config.BindPort = opts.bindPort
|
||||||
|
|
||||||
|
err = checkPorts()
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error, because it's informative enough as is.
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// override bind host/port from the console
|
// override bind host/port from the console
|
||||||
|
@ -368,6 +362,34 @@ func setupConfig(opts options) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkPorts is a helper for ports validation in config.
|
||||||
|
func checkPorts() (err error) {
|
||||||
|
tcpPorts := aghalg.UniqChecker[tcpPort]{}
|
||||||
|
addPorts(tcpPorts, tcpPort(config.BindPort))
|
||||||
|
|
||||||
|
udpPorts := aghalg.UniqChecker[udpPort]{}
|
||||||
|
addPorts(udpPorts, udpPort(config.DNS.Port))
|
||||||
|
|
||||||
|
if config.TLS.Enabled {
|
||||||
|
addPorts(
|
||||||
|
tcpPorts,
|
||||||
|
tcpPort(config.TLS.PortHTTPS),
|
||||||
|
tcpPort(config.TLS.PortDNSOverTLS),
|
||||||
|
tcpPort(config.TLS.PortDNSCrypt),
|
||||||
|
)
|
||||||
|
|
||||||
|
addPorts(udpPorts, udpPort(config.TLS.PortDNSOverQUIC))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tcpPorts.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("validating tcp ports: %w", err)
|
||||||
|
} else if err = udpPorts.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("validating udp ports: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func initWeb(opts options, clientBuildFS fs.FS) (web *Web, err error) {
|
func initWeb(opts options, clientBuildFS fs.FS) (web *Web, err error) {
|
||||||
var clientFS fs.FS
|
var clientFS fs.FS
|
||||||
if opts.localFrontend {
|
if opts.localFrontend {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// currentSchemaVersion is the current schema version.
|
// currentSchemaVersion is the current schema version.
|
||||||
const currentSchemaVersion = 17
|
const currentSchemaVersion = 19
|
||||||
|
|
||||||
// These aliases are provided for convenience.
|
// These aliases are provided for convenience.
|
||||||
type (
|
type (
|
||||||
|
@ -90,6 +90,8 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
|
||||||
upgradeSchema14to15,
|
upgradeSchema14to15,
|
||||||
upgradeSchema15to16,
|
upgradeSchema15to16,
|
||||||
upgradeSchema16to17,
|
upgradeSchema16to17,
|
||||||
|
upgradeSchema17to18,
|
||||||
|
upgradeSchema18to19,
|
||||||
}
|
}
|
||||||
|
|
||||||
n := 0
|
n := 0
|
||||||
|
@ -943,6 +945,125 @@ func upgradeSchema16to17(diskConf yobj) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// upgradeSchema17to18 performs the following changes:
|
||||||
|
//
|
||||||
|
// # BEFORE:
|
||||||
|
// 'dns':
|
||||||
|
// 'safesearch_enabled': true
|
||||||
|
//
|
||||||
|
// # AFTER:
|
||||||
|
// 'dns':
|
||||||
|
// 'safe_search':
|
||||||
|
// 'enabled': true
|
||||||
|
// 'bing': true
|
||||||
|
// 'duckduckgo': true
|
||||||
|
// 'google': true
|
||||||
|
// 'pixabay': true
|
||||||
|
// 'yandex': true
|
||||||
|
// 'youtube': true
|
||||||
|
func upgradeSchema17to18(diskConf yobj) (err error) {
|
||||||
|
log.Printf("Upgrade yaml: 17 to 18")
|
||||||
|
diskConf["schema_version"] = 18
|
||||||
|
|
||||||
|
dnsVal, ok := diskConf["dns"]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dns, ok := dnsVal.(yobj)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
safeSearch := yobj{
|
||||||
|
"enabled": true,
|
||||||
|
"bing": true,
|
||||||
|
"duckduckgo": true,
|
||||||
|
"google": true,
|
||||||
|
"pixabay": true,
|
||||||
|
"yandex": true,
|
||||||
|
"youtube": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeSearchKey = "safesearch_enabled"
|
||||||
|
|
||||||
|
v, has := dns[safeSearchKey]
|
||||||
|
if has {
|
||||||
|
safeSearch["enabled"] = v
|
||||||
|
}
|
||||||
|
delete(dns, safeSearchKey)
|
||||||
|
|
||||||
|
dns["safe_search"] = safeSearch
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// upgradeSchema18to19 performs the following changes:
|
||||||
|
//
|
||||||
|
// # BEFORE:
|
||||||
|
// 'clients':
|
||||||
|
// 'persistent':
|
||||||
|
// - 'name': 'client-name'
|
||||||
|
// 'safesearch_enabled': true
|
||||||
|
//
|
||||||
|
// # AFTER:
|
||||||
|
// 'clients':
|
||||||
|
// 'persistent':
|
||||||
|
// - 'name': 'client-name'
|
||||||
|
// 'safe_search':
|
||||||
|
// 'enabled': true
|
||||||
|
// 'bing': true
|
||||||
|
// 'duckduckgo': true
|
||||||
|
// 'google': true
|
||||||
|
// 'pixabay': true
|
||||||
|
// 'yandex': true
|
||||||
|
// 'youtube': true
|
||||||
|
func upgradeSchema18to19(diskConf yobj) (err error) {
|
||||||
|
log.Printf("Upgrade yaml: 18 to 19")
|
||||||
|
diskConf["schema_version"] = 19
|
||||||
|
|
||||||
|
clientsVal, ok := diskConf["clients"]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, ok := clientsVal.(yobj)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected type of clients: %T", clientsVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
persistent, ok := clients["persistent"].([]yobj)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeSearchKey = "safesearch_enabled"
|
||||||
|
|
||||||
|
for i := range persistent {
|
||||||
|
c := persistent[i]
|
||||||
|
|
||||||
|
safeSearch := yobj{
|
||||||
|
"enabled": true,
|
||||||
|
"bing": true,
|
||||||
|
"duckduckgo": true,
|
||||||
|
"google": true,
|
||||||
|
"pixabay": true,
|
||||||
|
"yandex": true,
|
||||||
|
"youtube": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
v, has := c[safeSearchKey]
|
||||||
|
if has {
|
||||||
|
safeSearch["enabled"] = v
|
||||||
|
}
|
||||||
|
delete(c, safeSearchKey)
|
||||||
|
|
||||||
|
c["safe_search"] = safeSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(a.garipov): Replace with log.Output when we port it to our logging
|
// TODO(a.garipov): Replace with log.Output when we port it to our logging
|
||||||
// package.
|
// package.
|
||||||
func funcName() string {
|
func funcName() string {
|
||||||
|
|
|
@ -808,3 +808,146 @@ func TestUpgradeSchema16to17(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpgradeSchema17to18(t *testing.T) {
|
||||||
|
const newSchemaVer = 18
|
||||||
|
|
||||||
|
defaultWantObj := yobj{
|
||||||
|
"dns": yobj{
|
||||||
|
"safe_search": yobj{
|
||||||
|
"enabled": true,
|
||||||
|
"bing": true,
|
||||||
|
"duckduckgo": true,
|
||||||
|
"google": true,
|
||||||
|
"pixabay": true,
|
||||||
|
"yandex": true,
|
||||||
|
"youtube": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"schema_version": newSchemaVer,
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
in yobj
|
||||||
|
want yobj
|
||||||
|
name string
|
||||||
|
}{{
|
||||||
|
in: yobj{"dns": yobj{}},
|
||||||
|
want: defaultWantObj,
|
||||||
|
name: "default_values",
|
||||||
|
}, {
|
||||||
|
in: yobj{"dns": yobj{"safesearch_enabled": true}},
|
||||||
|
want: defaultWantObj,
|
||||||
|
name: "enabled",
|
||||||
|
}, {
|
||||||
|
in: yobj{"dns": yobj{"safesearch_enabled": false}},
|
||||||
|
want: yobj{
|
||||||
|
"dns": yobj{
|
||||||
|
"safe_search": map[string]any{
|
||||||
|
"enabled": false,
|
||||||
|
"bing": true,
|
||||||
|
"duckduckgo": true,
|
||||||
|
"google": true,
|
||||||
|
"pixabay": true,
|
||||||
|
"yandex": true,
|
||||||
|
"youtube": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"schema_version": newSchemaVer,
|
||||||
|
},
|
||||||
|
name: "disabled",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := upgradeSchema17to18(tc.in)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.want, tc.in)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpgradeSchema18to19(t *testing.T) {
|
||||||
|
const newSchemaVer = 19
|
||||||
|
|
||||||
|
defaultWantObj := yobj{
|
||||||
|
"clients": yobj{
|
||||||
|
"persistent": []yobj{{
|
||||||
|
"name": "localhost",
|
||||||
|
"safe_search": yobj{
|
||||||
|
"enabled": true,
|
||||||
|
"bing": true,
|
||||||
|
"duckduckgo": true,
|
||||||
|
"google": true,
|
||||||
|
"pixabay": true,
|
||||||
|
"yandex": true,
|
||||||
|
"youtube": true,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
"schema_version": newSchemaVer,
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
in yobj
|
||||||
|
want yobj
|
||||||
|
name string
|
||||||
|
}{{
|
||||||
|
in: yobj{
|
||||||
|
"clients": yobj{},
|
||||||
|
},
|
||||||
|
want: yobj{
|
||||||
|
"clients": yobj{},
|
||||||
|
"schema_version": newSchemaVer,
|
||||||
|
},
|
||||||
|
name: "no_clients",
|
||||||
|
}, {
|
||||||
|
in: yobj{
|
||||||
|
"clients": yobj{
|
||||||
|
"persistent": []yobj{{"name": "localhost"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: defaultWantObj,
|
||||||
|
name: "default_values",
|
||||||
|
}, {
|
||||||
|
in: yobj{
|
||||||
|
"clients": yobj{
|
||||||
|
"persistent": []yobj{{"name": "localhost", "safesearch_enabled": true}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: defaultWantObj,
|
||||||
|
name: "enabled",
|
||||||
|
}, {
|
||||||
|
in: yobj{
|
||||||
|
"clients": yobj{
|
||||||
|
"persistent": []yobj{{"name": "localhost", "safesearch_enabled": false}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: yobj{
|
||||||
|
"clients": yobj{"persistent": []yobj{{
|
||||||
|
"name": "localhost",
|
||||||
|
"safe_search": yobj{
|
||||||
|
"enabled": false,
|
||||||
|
"bing": true,
|
||||||
|
"duckduckgo": true,
|
||||||
|
"google": true,
|
||||||
|
"pixabay": true,
|
||||||
|
"yandex": true,
|
||||||
|
"youtube": true,
|
||||||
|
},
|
||||||
|
}}},
|
||||||
|
"schema_version": newSchemaVer,
|
||||||
|
},
|
||||||
|
name: "disabled",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := upgradeSchema18to19(tc.in)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.want, tc.in)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue