diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 84d80f99..be43d704 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ 'name': 'build' 'env': - 'GO_VERSION': '1.19.6' + 'GO_VERSION': '1.19.7' 'NODE_VERSION': '14' 'on': diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cf94cb26..8313cfa8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,7 +1,7 @@ 'name': 'lint' 'env': - 'GO_VERSION': '1.19.6' + 'GO_VERSION': '1.19.7' 'on': 'push': diff --git a/CHANGELOG.md b/CHANGELOG.md index d4654787..d7c99e04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,11 +14,11 @@ and this project adheres to @@ -31,19 +31,19 @@ NOTE: Add new changes BELOW THIS COMMENT. - Two new HTTP APIs, `PUT /control/querylog/config/update` and `GET control/querylog/config`, which can be used to set and receive the statistics configuration. See openapi/openapi.yaml for the full description. -- The ability to set custom IP for EDNS Client Subnet by using the new - `dns.edns_client_subnet.use_custom` and `dns.edns_client_subnet.custom_ip` - fields ([#1472]). The UI changes are coming in the upcoming releases. -- The ability to use `dnstype` rules in the disallowed domains list ([#5468]). - This allows dropping requests based on their question types. +- The ability to manage safesearch for each service by using the new + `safe_search` field ([#1163]). -### Changed +### Changed + +- ARPA domain names containing a subnet within private networks now also + considered private, behaving closer to [RFC 6761][rfc6761] ([#5567]). #### Configuration Changes -In this release, the schema version has changed from 16 to 18. +In this release, the schema version has changed from 17 to 20. -- Property `statistics.interval`, which in schema versions 17 and earlier used +- Property `statistics.interval`, which in schema versions 19 and earlier used to be an integer number of days, is now a string with a human-readable duration: @@ -60,7 +60,87 @@ In this release, the schema version has changed from 16 to 18. ``` To rollback this change, convert the property back into days and change the + `schema_version` back 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`. + +### Deprecated + +- The `GET /control/stats_info` HTTP API; use the new `GET + /control/stats/config` API instead. + + **NOTE:** If interval is custom then it will be equal to `90` days for + compatibility reasons. See openapi/openapi.yaml and `openapi/CHANGELOG.md`. +- The `POST /control/stats_config` HTTP API; use the new `PUT + /control/stats/config/update` API instead. +- The `GET /control/querylog_info` HTTP API; use the new `GET + /control/querylog/config` API instead. + + **NOTE:** If interval is custom then it will be equal to `90` days for + compatibility reasons. See openapi/openapi.yaml and `openapi/CHANGELOG.md`. +- The `POST /control/querylog_config` HTTP API; use the new `PUT + /control/querylog/config/update` API instead. + +### Fixed + +- Panic caused by empty top-level domain name label in `/etc/hosts` files + ([#5584]). + +[#1163]: https://github.com/AdguardTeam/AdGuardHome/issues/1163 +[#5567]: https://github.com/AdguardTeam/AdGuardHome/issues/5567 +[#5584]: https://github.com/AdguardTeam/AdGuardHome/issues/5584 + +[rfc6761]: https://www.rfc-editor.org/rfc/rfc6761 + + + + + +## [v0.107.26] - 2023-03-09 + +See also the [v0.107.26 GitHub milestone][ms-v0.107.26]. + +### Security + +- Go version has been updated to prevent the possibility of exploiting the + CVE-2023-24532 Go vulnerability fixed in [Go 1.19.7][go-1.19.7]. + +### Added + +- The ability to set custom IP for EDNS Client Subnet by using the new + `dns.edns_client_subnet.use_custom` and `dns.edns_client_subnet.custom_ip` + fields ([#1472]). The UI changes are coming in the upcoming releases. +- The ability to use `dnstype` rules in the disallowed domains list ([#5468]). + This allows dropping requests based on their question types. + +### Changed + +#### Configuration Changes + - Property `edns_client_subnet`, which in schema versions 16 and earlier used to be a part of the `dns` object, is now part of the `dns.edns_client_subnet` object: @@ -86,25 +166,9 @@ In this release, the schema version has changed from 16 to 18. `dns.edns_client_subnet.custom_ip`, and change the `schema_version` back to `16`. -### Deprecated - -- The `GET /control/stats_info` HTTP API; use the new `GET - /control/stats/config` API instead. - - **NOTE:** If interval is custom then it will be equal to `90` days for - compatibility reasons. See openapi/openapi.yaml and `openapi/CHANGELOG.md`. -- The `POST /control/stats_config` HTTP API; use the new `PUT - /control/stats/config/update` API instead. -- The `GET /control/querylog_info` HTTP API; use the new `GET - /control/querylog/config` API instead. - - **NOTE:** If interval is custom then it will be equal to `90` days for - compatibility reasons. See openapi/openapi.yaml and `openapi/CHANGELOG.md`. -- The `POST /control/querylog_config` HTTP API; use the new `PUT - /control/querylog/config/update` API instead. - ### Fixed +- Obsolete value of the Interface MTU DHCP option is now omitted ([#5281]). - Various dark theme bugs ([#5439], [#5441], [#5442], [#5515]). - Automatic update on MIPS64 and little-endian 32-bit MIPS architectures ([#5270], [#5373]). @@ -115,6 +179,7 @@ In this release, the schema version has changed from 16 to 18. [#1472]: https://github.com/AdguardTeam/AdGuardHome/issues/1472 [#4884]: https://github.com/AdguardTeam/AdGuardHome/issues/4884 [#5270]: https://github.com/AdguardTeam/AdGuardHome/issues/5270 +[#5281]: https://github.com/AdguardTeam/AdGuardHome/issues/5281 [#5373]: https://github.com/AdguardTeam/AdGuardHome/issues/5373 [#5431]: https://github.com/AdguardTeam/AdGuardHome/issues/5431 [#5439]: https://github.com/AdguardTeam/AdGuardHome/issues/5439 @@ -123,11 +188,9 @@ In this release, the schema version has changed from 16 to 18. [#5468]: https://github.com/AdguardTeam/AdGuardHome/issues/5468 [#5515]: https://github.com/AdguardTeam/AdGuardHome/issues/5515 -[rfc3696]: https://datatracker.ietf.org/doc/html/rfc3696 - - +[go-1.19.7]: https://groups.google.com/g/golang-announce/c/3-TpUx48iQY +[ms-v0.107.26]: https://github.com/AdguardTeam/AdGuardHome/milestone/62?closed=1 +[rfc3696]: https://datatracker.ietf.org/doc/html/rfc3696 @@ -1786,11 +1849,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2]. -[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.25...HEAD +[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.26...HEAD +[v0.107.26]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.25...v0.107.26 [v0.107.25]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.24...v0.107.25 [v0.107.24]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.23...v0.107.24 [v0.107.23]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.22...v0.107.23 diff --git a/bamboo-specs/release.yaml b/bamboo-specs/release.yaml index f02431e0..0dce84b7 100644 --- a/bamboo-specs/release.yaml +++ b/bamboo-specs/release.yaml @@ -7,7 +7,7 @@ # Make sure to sync any changes with the branch overrides below. 'variables': 'channel': 'edge' - 'dockerGo': 'adguard/golang-ubuntu:6.1' + 'dockerGo': 'adguard/golang-ubuntu:6.2' 'stages': - 'Build frontend': @@ -331,7 +331,7 @@ # need to build a few of these. 'variables': 'channel': 'beta' - 'dockerGo': 'adguard/golang-ubuntu:6.1' + 'dockerGo': 'adguard/golang-ubuntu:6.2' # release-vX.Y.Z branches are the branches from which the actual final release # is built. - '^release-v[0-9]+\.[0-9]+\.[0-9]+': @@ -346,4 +346,4 @@ # are the ones that actually get released. 'variables': 'channel': 'release' - 'dockerGo': 'adguard/golang-ubuntu:6.1' + 'dockerGo': 'adguard/golang-ubuntu:6.2' diff --git a/bamboo-specs/test.yaml b/bamboo-specs/test.yaml index 4708bcc7..38579455 100644 --- a/bamboo-specs/test.yaml +++ b/bamboo-specs/test.yaml @@ -5,7 +5,7 @@ 'key': 'AHBRTSPECS' 'name': 'AdGuard Home - Build and run tests' 'variables': - 'dockerGo': 'adguard/golang-ubuntu:6.1' + 'dockerGo': 'adguard/golang-ubuntu:6.2' 'stages': - 'Tests': diff --git a/client/src/__locales/fi.json b/client/src/__locales/fi.json index c60508b9..b04ad021 100644 --- a/client/src/__locales/fi.json +++ b/client/src/__locales/fi.json @@ -297,7 +297,7 @@ "blocking_mode_refused": "REFUSED: Vastaa REFUSED-koodilla", "blocking_mode_nxdomain": "NXDOMAIN: Vastaa NXDOMAIN-koodilla", "blocking_mode_null_ip": "Tyhjä IP: Vastaa IP-nollaosoitteella (0.0.0.0 korvaa A; :: korvaa AAAA)", - "blocking_mode_custom_ip": "Mukautettu IP: Vastaa itse määritetyllä IP-osoitteella", + "blocking_mode_custom_ip": "Mukautettu IP: Vastaa manuaalisesti määritetyllä IP-osoitteella", "theme_auto": "Automaattinen", "theme_light": "Vaalea", "theme_dark": "Tumma", diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json index 9756f88a..ca218d07 100644 --- a/client/src/__locales/no.json +++ b/client/src/__locales/no.json @@ -281,6 +281,7 @@ "blocking_mode_nxdomain": "NXDOMAIN: Svar med NXDOMAIN-koden", "blocking_mode_null_ip": "Null IP: Svar med en 0-IP-adresse (0.0.0.0 for A; :: for AAAA)", "blocking_mode_custom_ip": "Tilpasset IP: Svar med en manuelt valgt IP-adresse", + "theme_auto": "Auto", "upstream_dns_client_desc": "Hvis dette feltet holdes tomt, vil AdGuard Home bruke tjenerne som er satt opp i <0>DNS-innstillingene.", "tracker_source": "Sporerkilde", "source_label": "Kilde", diff --git a/client/src/components/Dashboard/index.js b/client/src/components/Dashboard/index.js index 9bde0ea7..ff66820e 100644 --- a/client/src/components/Dashboard/index.js +++ b/client/src/components/Dashboard/index.js @@ -60,7 +60,7 @@ const Dashboard = ({ title={t('refresh_btn')} onClick={() => getAllStats()} > - + ; diff --git a/client/src/components/Filters/Table.js b/client/src/components/Filters/Table.js index 045d4be4..7a3c0e0b 100644 --- a/client/src/components/Filters/Table.js +++ b/client/src/components/Filters/Table.js @@ -100,7 +100,7 @@ class Table extends Component { }) } > - + @@ -110,7 +110,7 @@ class Table extends Component { onClick={() => handleDelete(url)} title={t('delete_table_action')} > - + diff --git a/client/src/components/Logs/Cells/ClientCell.js b/client/src/components/Logs/Cells/ClientCell.js index 9467f14e..3590df48 100644 --- a/client/src/components/Logs/Cells/ClientCell.js +++ b/client/src/components/Logs/Cells/ClientCell.js @@ -162,7 +162,7 @@ const ClientCell = ({ {content && ( @@ -301,7 +301,7 @@ const ClientsTable = ({ disabled={processingDeleting} title={t('delete_table_action')} > - + diff --git a/client/src/components/Settings/Clients/Service.css b/client/src/components/Settings/Clients/Service.css index 6cfcf40a..c4ca9d8c 100644 --- a/client/src/components/Settings/Clients/Service.css +++ b/client/src/components/Settings/Clients/Service.css @@ -54,6 +54,12 @@ color: #495057; } +.service__icon svg { + width: 20px; + height: 20px; + fill: #495057; +} + .service--global .service__icon { display: none; } diff --git a/client/src/components/ui/Icons.js b/client/src/components/ui/Icons.js index 38eff1be..bc725419 100644 --- a/client/src/components/ui/Icons.js +++ b/client/src/components/ui/Icons.js @@ -86,10 +86,10 @@ const Icons = () => ( d="m19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1 -2.83 0l-.06-.06a1.65 1.65 0 0 0 -1.82-.33 1.65 1.65 0 0 0 -1 1.51v.17a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2v-.09a1.65 1.65 0 0 0 -1.08-1.51 1.65 1.65 0 0 0 -1.82.33l-.06.06a2 2 0 0 1 -2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0 -1.51-1h-.17a2 2 0 0 1 -2-2 2 2 0 0 1 2-2h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0 -.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33h.08a1.65 1.65 0 0 0 1-1.51v-.17a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 -.33 1.82v.08a1.65 1.65 0 0 0 1.51 1h.17a2 2 0 0 1 2 2 2 2 0 0 1 -2 2h-.09a1.65 1.65 0 0 0 -1.51 1z" /> - - - + + + + ( - + { disabled={processingVersion} title={t('check_updates_now')} > - + } diff --git a/client/src/helpers/filters/filters.js b/client/src/helpers/filters/filters.js index 5ace4d7c..68b3dd9c 100644 --- a/client/src/helpers/filters/filters.js +++ b/client/src/helpers/filters/filters.js @@ -235,7 +235,7 @@ export default { "urlhaus_filter_online": { "name": "Malicious URL Blocklist (URLHaus)", "categoryId": "security", - "homepage": "https://gitlab.com/malware-filter/urlhaus-filter", + "homepage": "https://urlhaus.abuse.ch/", "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt" }, "windowsspyblocker_hosts_spy_rules": { diff --git a/client/src/helpers/trackers/trackers.json b/client/src/helpers/trackers/trackers.json index 5e9cbd9c..8afb5aeb 100644 --- a/client/src/helpers/trackers/trackers.json +++ b/client/src/helpers/trackers/trackers.json @@ -1,5 +1,5 @@ { - "timeUpdated": "2023-03-01T10:05:51.445Z", + "timeUpdated": "2023-03-08T00:09:48.692Z", "categories": { "0": "audio_video_player", "1": "comments", @@ -19316,6 +19316,13 @@ "companyId": null, "source": "AdGuard" }, + "edgio": { + "name": "Edgio", + "categoryId": 9, + "url": "https://edg.io/", + "companyId": "edgio", + "source": "AdGuard" + }, "element": { "name": "Element", "categoryId": 7, @@ -19372,6 +19379,13 @@ "companyId": "lets_encrypt", "source": "AdGuard" }, + "lgtv": { + "name": "LG TV", + "categoryId": 8, + "url": "https://www.lg.com/", + "companyId": "lgcorp", + "source": "AdGuard" + }, "matrix": { "name": "Matrix", "categoryId": 5, @@ -19407,6 +19421,13 @@ "companyId": "mozilla", "source": "AdGuard" }, + "nab": { + "name": "National Australia Bank", + "categoryId": 8, + "url": "https://www.nab.com.au/", + "companyId": "nab", + "source": "AdGuard" + }, "notion": { "name": "Notion", "categoryId": 8, @@ -19456,6 +19477,13 @@ "companyId": "qualcomm", "source": "AdGuard" }, + "recaptcha": { + "name": "reCAPTCHA", + "categoryId": 8, + "url": "https://www.google.com/recaptcha/about/", + "companyId": "google", + "source": "AdGuard" + }, "sectigo": { "name": "Sectigo Limited", "categoryId": 5, @@ -23893,6 +23921,11 @@ "adguardvpn.com": "adguard_vpn", "adguard-vpn.com": "adguard_vpn", "adguard-vpn.online": "adguard_vpn", + "adjust.net.in": "adjust", + "adj.st": "adjust", + "adjust.io": "adjust", + "adjust.world": "adjust", + "apptrace.com": "adjust", "akadns.net": "akamai_technologies", "akamaiedge.net": "akamai_technologies", "akaquill.net": "akamai_technologies", @@ -23907,6 +23940,21 @@ "aliyun.com": "alibaba_cloud", "ucweb.com": "alibaba_ucbrowser", "alipayobjects.com": "alipay.com", + "amazoncrl.com": "amazon", + "aamazoncognito.com": "amazon", + "amazonbrowserapp.es": "amazon", + "amazonbrowserapp.co.uk": "amazon", + "amazon.sa": "amazon", + "amazon.nl": "amazon", + "amazon.in": "amazon", + "amazon.com.mx": "amazon", + "amazon.com.au": "amazon", + "amazon-corp.com": "amazon", + "a2z.com": "amazon", + "amazontrust.com": "amazon_cdn", + "associates-amazon.com": "amazon_cdn", + "amazonpay.in": "amazon_payments", + "amazonvideo.com": "amazon_video", "taobao.com": "taobao", "appcenter.ms": "appcenter", "iadsdk.apple.com": "apple_ads", @@ -23926,6 +23974,8 @@ "apple-livephotoskit.com": "apple", "safebrowsing.apple": "apple", "safebrowsing.g.applimg.com": "apple", + "applvn.com": "applovin", + "applovin.com": "applovin", "blob.core.windows.net": "azure_blob_storage", "azure.com": "azure", "trafficmanager.net": "azure", @@ -23935,12 +23985,21 @@ "cloudflare-dns.com": "cloudflare", "crashlytics.com": "crashlytics", "phicdn.net": "digicert_trust_seal", + "alphacdn.net": "edgio", + "edg.io": "edgio", + "edgecast.com": "edgio", + "edgecastcdn.net": "edgio", + "edgecastdns.net": "edgio", + "sigmacdn.net": "edgio", "element.io": "element", "riot.im": "element", "app-measurement.com": "firebase", "flipboard.com": "flipboard", "flurry.com": "flurry", + "ghcr.io": "github", + "github.dev": "github", "gmail.com": "gmail", + "googlehosted.com": "google_appspot", "gvt1.com": "google_servers", "gvt2.com": "google_servers", "gvt3.com": "google_servers", @@ -23949,10 +24008,15 @@ "kik.com": "kik", "apikik.com": "kik", "kik-live.com": "kik", - "letsencrypt.org": "lets_encrypt", "slatic.net": "lazada", "lencr.org": "lets_encrypt", - "edgecastcdn.net": "markmonitor", + "letsencrypt.org": "lets_encrypt", + "lgsmartad.com": "lgtv", + "lgtvcommon.com": "lgtv", + "lgtvsdp.com": "lgtv", + "lge.com": "lgtv", + "lg.com": "lgtv", + "markmonitor.com": "markmonitor", "matrix.org": "matrix", "medialab.la": "medialab", "media-lab.ai": "medialab", @@ -23968,6 +24032,13 @@ "mozilla.net": "mozilla", "mozilla.org": "mozilla", "nflximg.com": "netflix", + "nab.com": "nab", + "nab.com.au": "nab", + "nab.net": "nab", + "nabgroup.com": "nab", + "national.com.au": "nab", + "nationalaustraliabank.com.au": "nab", + "nationalbank.com.au": "nab", "notion.so": "notion", "ntp.org": "ntppool", "ntppool.org": "ntppool", @@ -23984,6 +24055,7 @@ "plex.direct": "plex", "xtracloud.net": "qualcomm", "qualcomm.com": "qualcomm", + "recaptcha.net": "recaptcha", "sectigo.com": "sectigo", "showrss.info": "showrss", "similarweb.io": "similarweb", diff --git a/go.mod b/go.mod index 39e40a4c..05729ad5 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( // TODO(a.garipov): Use v0.48.0 when it's released. github.com/AdguardTeam/dnsproxy v0.48.0 - github.com/AdguardTeam/golibs v0.12.0 + github.com/AdguardTeam/golibs v0.13.0 github.com/AdguardTeam/urlfilter v0.16.1 github.com/NYTimes/gziphandler v1.1.1 github.com/ameshkov/dnscrypt/v2 v2.2.5 @@ -26,13 +26,13 @@ require ( github.com/mdlayher/raw v0.1.0 github.com/miekg/dns v1.1.50 github.com/quic-go/quic-go v0.32.0 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.2 github.com/ti-mo/netfilter v0.5.0 go.etcd.io/bbolt v1.3.7 golang.org/x/crypto v0.6.0 - golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb - golang.org/x/net v0.7.0 - golang.org/x/sys v0.5.0 + golang.org/x/exp v0.0.0-20230306221820-f0f767cdffd6 + golang.org/x/net v0.8.0 + golang.org/x/sys v0.6.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 howett.net/plist v1.0.0 @@ -63,6 +63,10 @@ require ( github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/text v0.8.0 // indirect golang.org/x/tools v0.6.0 // indirect ) + +// TODO(a.garipov): Remove this and update github.com/ameshkov/dnscrypt when +// it's released. +replace github.com/ameshkov/dnscrypt/v2 => github.com/ainar-g/dnscrypt/v2 v2.0.1-0.20230315131826-cdb2bf61bda8 diff --git a/go.sum b/go.sum index 8060927f..51ccf463 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/AdguardTeam/dnsproxy v0.48.0 h1:sGViYy2pV0cEp2zCsxPjFd9rlgD0+yELpIeLk github.com/AdguardTeam/dnsproxy v0.48.0/go.mod h1:9OHoeaVod+moWwrLjHF95RQnFWGi/6B1tfKsxWc/yGE= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw= -github.com/AdguardTeam/golibs v0.12.0 h1:z4Q3Mz0pHJ2Zag4B0RBaIXEUue1TPOKkbRiYkwC4r7I= -github.com/AdguardTeam/golibs v0.12.0/go.mod h1:87bN2x4VsTritptE3XZg9l8T6gznWsIxHBcQ1DeRIXA= +github.com/AdguardTeam/golibs v0.13.0 h1:hVBeNQXT/BgcjKz/4FMpFGvEYqXiXDJG+b5XpGCUOLk= +github.com/AdguardTeam/golibs v0.13.0/go.mod h1:rIglKDHdLvFT1UbhumBLHO9S4cvWS9MEyT1njommI/Y= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= github.com/AdguardTeam/urlfilter v0.16.1 h1:ZPi0rjqo8cQf2FVdzo6cqumNoHZx2KPXj2yZa1A5BBw= github.com/AdguardTeam/urlfilter v0.16.1/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI= @@ -15,8 +15,8 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= -github.com/ameshkov/dnscrypt/v2 v2.2.5 h1:Ju1gQeez+6XLtk/b/k3RoJ2t+Ls+BSItLTZjZeedneY= -github.com/ameshkov/dnscrypt/v2 v2.2.5/go.mod h1:Cu5GgMvCR10BeXgACiGDwXyOpfMktsSIidml1XBp6uM= +github.com/ainar-g/dnscrypt/v2 v2.0.1-0.20230315131826-cdb2bf61bda8 h1:jc3/aOQ01Yy3vxB/uqcuUi4F+6E/FKWaTCHq5J1ef2o= +github.com/ainar-g/dnscrypt/v2 v2.0.1-0.20230315131826-cdb2bf61bda8/go.mod h1:qPWhwz6FdSmuK7W4sMyvogrez4MWdtzosdqlr0Rg3ow= github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM= @@ -145,8 +145,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU= github.com/ti-mo/netfilter v0.5.0 h1:MZmsUw5bFRecOb0AeyjOPxTHg4UxYzyEs0Ek/6Lxoy8= github.com/ti-mo/netfilter v0.5.0/go.mod h1:nt+8B9hx/QpqHr7Hazq+2qMCCA8u2OTkyc/7+U9ARz8= @@ -165,8 +165,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w= -golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230306221820-f0f767cdffd6 h1:3p+wVC0x0TCIPgd3LCQlpgVlEtjziEC5v42w7+B8t8M= +golang.org/x/exp v0.0.0-20230306221820-f0f767cdffd6/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -188,8 +188,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= @@ -222,16 +222,16 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/internal/aghnet/hostscontainer_test.go b/internal/aghnet/hostscontainer_test.go index a5822530..81da4e2e 100644 --- a/internal/aghnet/hostscontainer_test.go +++ b/internal/aghnet/hostscontainer_test.go @@ -608,6 +608,11 @@ func TestUniqueRules_ParseLine(t *testing.T) { line: ``, wantIP: netip.Addr{}, wantHosts: nil, + }, { + name: "bad_hosts", + line: ipStr + ` bad..host bad._tld empty.tld. ok.host`, + wantIP: ip, + wantHosts: []string{"ok.host"}, }} for _, tc := range testCases { diff --git a/internal/dhcpd/options_unix.go b/internal/dhcpd/options_unix.go index 082826d5..6881d942 100644 --- a/internal/dhcpd/options_unix.go +++ b/internal/dhcpd/options_unix.go @@ -263,15 +263,12 @@ func (s *v4Server) prepareOptions() { // IP-Layer Per Interface - // Since nearly all networks in the Internet currently support an MTU of - // 576 or greater, we strongly recommend the use of 576 for datagrams - // sent to non-local networks. + // Don't set the Interface MTU because client may choose the value on + // their own since it's listed in the [Host Requirements RFC]. It also + // seems the values listed there sometimes appear obsolete, see + // https://github.com/AdguardTeam/AdGuardHome/issues/5281. // - // See https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.3. - dhcpv4.Option{ - Code: dhcpv4.OptionInterfaceMTU, - Value: dhcpv4.Uint16(576), - }, + // [Host Requirements RFC]: https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.3. // Set the All Subnets Are Local Option to false since commonly the // connected hosts aren't expected to be multihomed. diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index 9289eb45..cc02bb9c 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "net" "net/netip" + "strconv" "strings" "time" @@ -37,6 +38,8 @@ type dnsContext struct { // was parsed successfully and belongs to one of the locally served IP // ranges. It is also filled with unmapped version of the address if it's // within DNS64 prefixes. + // + // TODO(e.burkov): Use netip.Addr when we switch to netip more fully. unreversedReqIP net.IP // err is the error returned from a processing function. @@ -442,6 +445,88 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) { return resultCodeSuccess } +// indexFirstV4Label returns the index at which the reversed IPv4 address +// starts, assuiming the domain is pre-validated ARPA domain having in-addr and +// arpa labels removed. +func indexFirstV4Label(domain string) (idx int) { + idx = len(domain) + for labelsNum := 0; labelsNum < net.IPv4len && idx > 0; labelsNum++ { + curIdx := strings.LastIndexByte(domain[:idx-1], '.') + 1 + _, parseErr := strconv.ParseUint(domain[curIdx:idx-1], 10, 8) + if parseErr != nil { + return idx + } + + idx = curIdx + } + + return idx +} + +// indexFirstV6Label returns the index at which the reversed IPv6 address +// starts, assuiming the domain is pre-validated ARPA domain having ip6 and arpa +// labels removed. +func indexFirstV6Label(domain string) (idx int) { + idx = len(domain) + for labelsNum := 0; labelsNum < net.IPv6len*2 && idx > 0; labelsNum++ { + curIdx := idx - len("a.") + if curIdx > 1 && domain[curIdx-1] != '.' { + return idx + } + + nibble := domain[curIdx] + if (nibble < '0' || nibble > '9') && (nibble < 'a' || nibble > 'f') { + return idx + } + + idx = curIdx + } + + return idx +} + +// extractARPASubnet tries to convert a reversed ARPA address being a part of +// domain to an IP network. domain must be an FQDN. +// +// TODO(e.burkov): Move to golibs. +func extractARPASubnet(domain string) (pref netip.Prefix, err error) { + err = netutil.ValidateDomainName(strings.TrimSuffix(domain, ".")) + if err != nil { + // Don't wrap the error since it's informative enough as is. + return netip.Prefix{}, err + } + + const ( + v4Suffix = "in-addr.arpa." + v6Suffix = "ip6.arpa." + ) + + domain = strings.ToLower(domain) + + var idx int + switch { + case strings.HasSuffix(domain, v4Suffix): + idx = indexFirstV4Label(domain[:len(domain)-len(v4Suffix)]) + case strings.HasSuffix(domain, v6Suffix): + idx = indexFirstV6Label(domain[:len(domain)-len(v6Suffix)]) + default: + return netip.Prefix{}, &netutil.AddrError{ + Err: netutil.ErrNotAReversedSubnet, + Kind: netutil.AddrKindARPA, + Addr: domain, + } + } + + var subnet *net.IPNet + subnet, err = netutil.SubnetFromReversedAddr(domain[idx:]) + if err != nil { + // Don't wrap the error since it's informative enough as is. + return netip.Prefix{}, err + } + + return netutil.IPNetToPrefixNoMapped(subnet) +} + // processRestrictLocal responds with NXDOMAIN to PTR requests for IP addresses // in locally served network from external clients. func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) { @@ -453,34 +538,29 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) { return resultCodeSuccess } - ip, err := netutil.IPFromReversedAddr(q.Name) + subnet, err := extractARPASubnet(q.Name) if err != nil { - log.Debug("dnsforward: parsing reversed addr: %s", err) + if errors.Is(err, netutil.ErrNotAReversedSubnet) { + log.Debug("dnsforward: request is not for arpa domain") - // DNS-Based Service Discovery uses PTR records having not an ARPA - // format of the domain name in question. Those shouldn't be - // invalidated. See http://www.dns-sd.org/ServerStaticSetup.html and - // RFC 2782. - name := strings.TrimSuffix(q.Name, ".") - if err = netutil.ValidateSRVDomainName(name); err != nil { - log.Debug("dnsforward: validating service domain: %s", err) - - return resultCodeError + return resultCodeSuccess } - log.Debug("dnsforward: request is not for arpa domain") + log.Debug("dnsforward: parsing reversed addr: %s", err) - return resultCodeSuccess + return resultCodeError } // Restrict an access to local addresses for external clients. We also // assume that all the DHCP leases we give are locally served or at least // shouldn't be accessible externally. - if !s.privateNets.Contains(ip) { + subnetAddr := subnet.Addr() + addrData := subnetAddr.AsSlice() + if !s.privateNets.Contains(addrData) { return resultCodeSuccess } - log.Debug("dnsforward: addr %s is from locally served network", ip) + log.Debug("dnsforward: addr %s is from locally served network", subnetAddr) if !dctx.isLocalClient { log.Debug("dnsforward: %q requests an internal ip", pctx.Addr) @@ -491,7 +571,7 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) { } // Do not perform unreversing ever again. - dctx.unreversedReqIP = ip + dctx.unreversedReqIP = addrData // There is no need to filter request from external addresses since this // code is only executed when the request is for locally served ARPA diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/dns_test.go index 02f1eb61..1bcca756 100644 --- a/internal/dnsforward/dns_test.go +++ b/internal/dnsforward/dns_test.go @@ -605,3 +605,129 @@ func TestIPStringFromAddr(t *testing.T) { assert.Empty(t, ipStringFromAddr(nil)) }) } + +// TODO(e.burkov): Add fuzzing when moving to golibs. +func TestExtractARPASubnet(t *testing.T) { + const ( + v4Suf = `in-addr.arpa.` + v4Part = `2.1.` + v4Suf + v4Whole = `4.3.` + v4Part + + v6Suf = `ip6.arpa.` + v6Part = `4.3.2.1.0.0.0.0.0.0.0.0.0.0.0.0.` + v6Suf + v6Whole = `f.e.d.c.0.0.0.0.0.0.0.0.0.0.0.0.` + v6Part + ) + + v4Pref := netip.MustParsePrefix("1.2.3.4/32") + v4PrefPart := netip.MustParsePrefix("1.2.0.0/16") + v6Pref := netip.MustParsePrefix("::1234:0:0:0:cdef/128") + v6PrefPart := netip.MustParsePrefix("0:0:0:1234::/64") + + testCases := []struct { + want netip.Prefix + name string + domain string + wantErr string + }{{ + want: netip.Prefix{}, + name: "not_an_arpa", + domain: "some.domain.name.", + wantErr: `bad arpa domain name "some.domain.name.": ` + + `not a reversed ip network`, + }, { + want: netip.Prefix{}, + name: "bad_domain_name", + domain: "abc.123.", + wantErr: `bad domain name "abc.123": ` + + `bad top-level domain name label "123": all octets are numeric`, + }, { + want: v4Pref, + name: "whole_v4", + domain: v4Whole, + wantErr: "", + }, { + want: v4PrefPart, + name: "partial_v4", + domain: v4Part, + wantErr: "", + }, { + want: v4Pref, + name: "whole_v4_within_domain", + domain: "a." + v4Whole, + wantErr: "", + }, { + want: v4Pref, + name: "whole_v4_additional_label", + domain: "5." + v4Whole, + wantErr: "", + }, { + want: v4PrefPart, + name: "partial_v4_within_domain", + domain: "a." + v4Part, + wantErr: "", + }, { + want: v4PrefPart, + name: "overflow_v4", + domain: "256." + v4Part, + wantErr: "", + }, { + want: v4PrefPart, + name: "overflow_v4_within_domain", + domain: "a.256." + v4Part, + wantErr: "", + }, { + want: netip.Prefix{}, + name: "empty_v4", + domain: v4Suf, + wantErr: `bad arpa domain name "in-addr.arpa": ` + + `not a reversed ip network`, + }, { + want: netip.Prefix{}, + name: "empty_v4_within_domain", + domain: "a." + v4Suf, + wantErr: `bad arpa domain name "in-addr.arpa": ` + + `not a reversed ip network`, + }, { + want: v6Pref, + name: "whole_v6", + domain: v6Whole, + wantErr: "", + }, { + want: v6PrefPart, + name: "partial_v6", + domain: v6Part, + }, { + want: v6Pref, + name: "whole_v6_within_domain", + domain: "g." + v6Whole, + wantErr: "", + }, { + want: v6Pref, + name: "whole_v6_additional_label", + domain: "1." + v6Whole, + wantErr: "", + }, { + want: v6PrefPart, + name: "partial_v6_within_domain", + domain: "label." + v6Part, + wantErr: "", + }, { + want: netip.Prefix{}, + name: "empty_v6", + domain: v6Suf, + wantErr: `bad arpa domain name "ip6.arpa": not a reversed ip network`, + }, { + want: netip.Prefix{}, + name: "empty_v6_within_domain", + domain: "g." + v6Suf, + wantErr: `bad arpa domain name "ip6.arpa": not a reversed ip network`, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + subnet, err := extractARPASubnet(tc.domain) + testutil.AssertErrorMsg(t, tc.wantErr, err) + assert.Equal(t, tc.want, subnet) + }) + } +} diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index d0a0ada7..d0ef9fa2 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -23,6 +23,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/filtering" + "github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/netutil" @@ -412,7 +413,7 @@ func TestServerRace(t *testing.T) { filterConf := &filtering.Config{ SafeBrowsingEnabled: true, SafeBrowsingCacheSize: 1000, - SafeSearchEnabled: true, + SafeSearchConf: filtering.SafeSearchConfig{Enabled: true}, SafeSearchCacheSize: 1000, ParentalCacheSize: 1000, CacheTime: 30, @@ -440,12 +441,26 @@ func TestServerRace(t *testing.T) { func TestSafeSearch(t *testing.T) { resolver := &aghtest.TestResolver{} + safeSearchConf := filtering.SafeSearchConfig{ + Enabled: true, + Google: true, + Yandex: true, + CustomResolver: resolver, + } + filterConf := &filtering.Config{ - SafeSearchEnabled: true, + SafeSearchConf: safeSearchConf, SafeSearchCacheSize: 1000, 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{ UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, @@ -498,7 +513,8 @@ func TestSafeSearch(t *testing.T) { t.Run(tc.host, func(t *testing.T) { 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) assertResponse(t, reply, tc.want) }) diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index a876a411..0c8b6726 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -388,15 +388,15 @@ func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet) var errs []error for _, domain := range keys { - var subnet *net.IPNet - subnet, err = netutil.SubnetFromReversedAddr(domain) + var subnet netip.Prefix + subnet, err = extractARPASubnet(domain) if err != nil { errs = append(errs, err) continue } - if !privateNets.Contains(subnet.IP) { + if !privateNets.Contains(subnet.Addr().AsSlice()) { errs = append( errs, fmt.Errorf("arpa domain %q should point to a locally-served network", domain), diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go index 9d48151d..ef2228c1 100644 --- a/internal/dnsforward/http_test.go +++ b/internal/dnsforward/http_test.go @@ -57,7 +57,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) { filterConf := &filtering.Config{ SafeBrowsingEnabled: true, SafeBrowsingCacheSize: 1000, - SafeSearchEnabled: true, + SafeSearchConf: filtering.SafeSearchConfig{Enabled: true}, SafeSearchCacheSize: 1000, ParentalCacheSize: 1000, CacheTime: 30, @@ -133,7 +133,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { filterConf := &filtering.Config{ SafeBrowsingEnabled: true, SafeBrowsingCacheSize: 1000, - SafeSearchEnabled: true, + SafeSearchConf: filtering.SafeSearchConfig{Enabled: true}, SafeSearchCacheSize: 1000, ParentalCacheSize: 1000, CacheTime: 30, @@ -212,7 +212,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { }, { name: "local_ptr_upstreams_bad", wantSet: `validating private upstream servers: checking domain-specific upstreams: ` + - `bad arpa domain name "non.arpa": not a reversed ip network`, + `bad arpa domain name "non.arpa.": not a reversed ip network`, }, { name: "local_ptr_upstreams_null", wantSet: "", @@ -373,7 +373,7 @@ func TestValidateUpstreamsPrivate(t *testing.T) { }, { name: "not_arpa_subnet", wantErr: `checking domain-specific upstreams: ` + - `bad arpa domain name "hello.world": not a reversed ip network`, + `bad arpa domain name "hello.world.": not a reversed ip network`, u: "[/hello.world/]#", }, { name: "non-private_arpa_address", @@ -389,8 +389,12 @@ func TestValidateUpstreamsPrivate(t *testing.T) { name: "several_bad", wantErr: `checking domain-specific upstreams: 2 errors: ` + `"arpa domain \"1.2.3.4.in-addr.arpa.\" should point to a locally-served network", ` + - `"bad arpa domain name \"non.arpa\": not a reversed ip network"`, + `"bad arpa domain name \"non.arpa.\": not a reversed ip network"`, u: "[/non.arpa/1.2.3.4.in-addr.arpa/127.in-addr.arpa/]#", + }, { + name: "partial_good", + wantErr: "", + u: "[/a.1.2.3.10.in-addr.arpa/a.10.in-addr.arpa/]#", }} for _, tc := range testCases { diff --git a/internal/filtering/filtering.go b/internal/filtering/filtering.go index c7c1d8ff..4da26616 100644 --- a/internal/filtering/filtering.go +++ b/internal/filtering/filtering.go @@ -63,6 +63,9 @@ type Settings struct { SafeSearchEnabled bool SafeBrowsingEnabled bool ParentalEnabled bool + + // ClientSafeSearch is a client configured safe search. + ClientSafeSearch SafeSearch } // 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) ParentalEnabled bool `yaml:"parental_enabled"` - SafeSearchEnabled bool `yaml:"safesearch_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) - 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"` @@ -107,9 +113,6 @@ type Config struct { // Register an HTTP handler 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 *http.Client `yaml:"-"` @@ -172,7 +175,6 @@ type DNSFilter struct { safebrowsingCache cache.Cache parentalCache cache.Cache - safeSearchCache cache.Cache Config // for direct access by library users, even a = assignment // confLock protects Config. @@ -182,11 +184,6 @@ type DNSFilter struct { filtersInitializerChan chan filtersInitializerParams 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 // 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. filterTitleRegexp *regexp.Regexp + safeSearch SafeSearch hostCheckers []hostChecker } @@ -298,7 +296,7 @@ func (d *DNSFilter) GetConfig() (s Settings) { return Settings{ FilteringEnabled: atomic.LoadUint32(&d.Config.enabled) != 0, - SafeSearchEnabled: d.Config.SafeSearchEnabled, + SafeSearchEnabled: d.Config.SafeSearchConf.Enabled, SafeBrowsingEnabled: d.Config.SafeBrowsingEnabled, ParentalEnabled: d.Config.ParentalEnabled, } @@ -942,7 +940,6 @@ func InitModule() { // be non-nil. func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) { d = &DNSFilter{ - resolver: net.DefaultResolver, refreshLock: &sync.Mutex{}, filterTitleRegexp: regexp.MustCompile(`^! Title: +(.*)$`), } @@ -951,18 +948,12 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) { EnableLRU: true, MaxSize: c.SafeBrowsingCacheSize, }) - d.safeSearchCache = cache.New(cache.Config{ - EnableLRU: true, - MaxSize: c.SafeSearchCacheSize, - }) d.parentalCache = cache.New(cache.Config{ EnableLRU: true, MaxSize: c.ParentalCacheSize, }) - if r := c.CustomResolver; r != nil { - d.resolver = r - } + d.safeSearch = c.SafeSearch d.hostCheckers = []hostChecker{{ check: d.matchSysHosts, diff --git a/internal/filtering/filtering_test.go b/internal/filtering/filtering_test.go index 05869765..17cbebfb 100644 --- a/internal/filtering/filtering_test.go +++ b/internal/filtering/filtering_test.go @@ -2,10 +2,8 @@ package filtering import ( "bytes" - "context" "fmt" "net" - "strings" "testing" "github.com/AdguardTeam/AdGuardHome/internal/aghtest" @@ -33,7 +31,6 @@ func purgeCaches(d *DNSFilter) { for _, c := range []cache.Cache{ d.safebrowsingCache, d.parentalCache, - d.safeSearchCache, } { if c != nil { c.Clear() @@ -51,7 +48,7 @@ func newForTest(t testing.TB, c *Config, filters []Filter) (f *DNSFilter, setts c.ParentalCacheSize = 10000 c.SafeSearchCacheSize = 1000 c.CacheTime = 30 - setts.SafeSearchEnabled = c.SafeSearchEnabled + setts.SafeSearchEnabled = c.SafeSearchConf.Enabled setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled setts.ParentalEnabled = c.ParentalEnabled } 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. 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") - } - }) -} diff --git a/internal/filtering/safesearch.go b/internal/filtering/safesearch.go index f7661dd6..4b331ddb 100644 --- a/internal/filtering/safesearch.go +++ b/internal/filtering/safesearch.go @@ -1,74 +1,40 @@ package filtering 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/miekg/dns" ) -/* -expire byte[4] -res Result -*/ -func (d *DNSFilter) setCacheResult(cache cache.Cache, host string, res Result) int { - var buf bytes.Buffer +// SafeSearch interface describes a service for search engines hosts rewrites. +type SafeSearch interface { + // SearchHost returns a replacement address for the search engine host. + SearchHost(host string, qtype uint16) (res *rules.DNSRewrite) - 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) + // CheckHost checks host with safe search engine. + CheckHost(host string, qtype uint16) (res Result, err error) } -func getCachedResult(cache cache.Cache, host string) (Result, bool) { - data := cache.Get([]byte(host)) - if data == nil { - return Result{}, false - } +// SafeSearchConfig is a struct with safe search related settings. +type SafeSearchConfig struct { + // CustomResolver is the resolver used by safe search. + CustomResolver Resolver `yaml:"-"` - exp := int(binary.BigEndian.Uint32(data[:4])) - if exp <= int(time.Now().Unix()) { - cache.Del([]byte(host)) - return Result{}, false - } + // Enabled indicates if safe search is enabled entirely. + Enabled bool `yaml:"enabled" json:"enabled"` - 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 - } + // Services flags. Each flag indicates if the corresponding service is + // enabled or disabled. - 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 + Bing bool `yaml:"bing" json:"bing"` + DuckDuckGo bool `yaml:"duckduckgo" json:"duckduckgo"` + Google bool `yaml:"google" json:"google"` + Pixabay bool `yaml:"pixabay" json:"pixabay"` + Yandex bool `yaml:"yandex" json:"yandex"` + YouTube bool `yaml:"youtube" json:"youtube"` } +// checkSafeSearch checks host with safe search engine. Matches +// [hostChecker.check]. func (d *DNSFilter) checkSafeSearch( host string, _ uint16, @@ -78,295 +44,14 @@ func (d *DNSFilter) checkSafeSearch( return Result{}, nil } - if log.GetLevel() >= log.DEBUG { - 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 { + if d.safeSearch == nil { return Result{}, nil } - res = Result{ - Rules: []*ResultRule{{ - FilterListID: SafeSearchListID, - }}, - Reason: FilteredSafeSearch, - IsFiltered: true, + clientSafeSearch := setts.ClientSafeSearch + if clientSafeSearch != nil { + return clientSafeSearch.CheckHost(host, dns.TypeA) } - if ip := net.ParseIP(safeHost); ip != nil { - 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", + return d.safeSearch.CheckHost(host, dns.TypeA) } diff --git a/internal/filtering/safesearch/rules.go b/internal/filtering/safesearch/rules.go new file mode 100644 index 00000000..75e512e8 --- /dev/null +++ b/internal/filtering/safesearch/rules.go @@ -0,0 +1,34 @@ +package safesearch + +import _ "embed" + +//go:embed rules/bing.txt +var bing string + +//go:embed rules/google.txt +var google string + +//go:embed rules/pixabay.txt +var pixabay string + +//go:embed rules/duckduckgo.txt +var duckduckgo string + +//go:embed rules/yandex.txt +var yandex string + +//go:embed rules/youtube.txt +var youtube string + +// safeSearchRules is a map with rules texts grouped by search providers. +// Source rules downloaded from: +// https://adguardteam.github.io/HostlistsRegistry/assets/engines_safe_search.txt, +// https://adguardteam.github.io/HostlistsRegistry/assets/youtube_safe_search.txt. +var safeSearchRules = map[Service]string{ + Bing: bing, + DuckDuckGo: duckduckgo, + Google: google, + Pixabay: pixabay, + Yandex: yandex, + YouTube: youtube, +} diff --git a/internal/filtering/safesearch/rules/bing.txt b/internal/filtering/safesearch/rules/bing.txt new file mode 100644 index 00000000..8c61b63c --- /dev/null +++ b/internal/filtering/safesearch/rules/bing.txt @@ -0,0 +1 @@ +|www.bing.com^$dnsrewrite=NOERROR;CNAME;strict.bing.com \ No newline at end of file diff --git a/internal/filtering/safesearch/rules/duckduckgo.txt b/internal/filtering/safesearch/rules/duckduckgo.txt new file mode 100644 index 00000000..084f1be0 --- /dev/null +++ b/internal/filtering/safesearch/rules/duckduckgo.txt @@ -0,0 +1,3 @@ +|duckduckgo.com^$dnsrewrite=NOERROR;CNAME;safe.duckduckgo.com +|start.duckduckgo.com^$dnsrewrite=NOERROR;CNAME;safe.duckduckgo.com +|www.duckduckgo.com^$dnsrewrite=NOERROR;CNAME;safe.duckduckgo.com \ No newline at end of file diff --git a/internal/filtering/safesearch/rules/google.txt b/internal/filtering/safesearch/rules/google.txt new file mode 100644 index 00000000..62f13067 --- /dev/null +++ b/internal/filtering/safesearch/rules/google.txt @@ -0,0 +1,191 @@ +|www.google.ad^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ae^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.al^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.am^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.as^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.at^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.az^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ba^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.be^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.bf^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.bg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.bi^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.bj^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.bs^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.bt^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.by^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ca^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.cat^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.cd^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.cf^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.cg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ch^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ci^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.cl^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.cm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.cn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.ao^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.bw^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.ck^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.cr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.id^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.il^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.in^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.jp^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.ke^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.kr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.ls^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.ma^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.mz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.nz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.th^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.tz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.ug^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.uk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.uz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.ve^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.co.vi^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.af^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.ag^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.ai^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.ar^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.au^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.bd^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.bh^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.bn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.bo^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.br^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.bz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.co^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.cu^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.cy^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.do^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.ec^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.eg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.et^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.fj^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.gh^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.gi^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.gt^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.hk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.jm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.kh^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.kw^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.lb^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.ly^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.mm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.mt^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.mx^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.my^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.na^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.nf^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.ng^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.ni^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.np^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.om^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.pa^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.pe^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.pg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.ph^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.pk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.pr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.py^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.qa^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.sa^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.sb^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.sg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.sl^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.sv^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.tj^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.tr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.tw^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.ua^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.uy^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.vc^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com.vn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.com^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.cv^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.cz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.de^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.dj^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.dk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.dm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.dz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ee^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.es^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.fi^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.fm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.fr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ga^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ge^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.gg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.gl^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.gm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.gp^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.gr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.gy^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.hn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.hr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ht^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.hu^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ie^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.im^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.iq^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.is^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.it^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.je^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.jo^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.kg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ki^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.kz^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.la^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.li^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.lk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.lt^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.lu^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.lv^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.md^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.me^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.mg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.mk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ml^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.mn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ms^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.mu^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.mv^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.mw^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ne^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.nl^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.no^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.nr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.nu^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.pl^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.pn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ps^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.pt^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ro^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.rs^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ru^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.rw^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.sc^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.se^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.sh^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.si^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.sk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.sm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.sn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.so^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.sr^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.st^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.td^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.tg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.tk^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.tl^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.tm^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.tn^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.to^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.tt^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.vg^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.vu^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com +|www.google.ws^$dnsrewrite=NOERROR;CNAME;forcesafesearch.google.com \ No newline at end of file diff --git a/internal/filtering/safesearch/rules/pixabay.txt b/internal/filtering/safesearch/rules/pixabay.txt new file mode 100644 index 00000000..0ab07746 --- /dev/null +++ b/internal/filtering/safesearch/rules/pixabay.txt @@ -0,0 +1 @@ +|pixabay.com^$dnsrewrite=NOERROR;CNAME;safesearch.pixabay.com \ No newline at end of file diff --git a/internal/filtering/safesearch/rules/yandex.txt b/internal/filtering/safesearch/rules/yandex.txt new file mode 100644 index 00000000..b6f4afb7 --- /dev/null +++ b/internal/filtering/safesearch/rules/yandex.txt @@ -0,0 +1,52 @@ +|www.xn--d1acpjx3f.xn--p1ai^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.ya.ru^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.az^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.by^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.co.il^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.com.am^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.com.ge^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.com.ru^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.com.tr^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.com^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.de^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.ee^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.eu^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.fi^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.fr^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.kz^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.lt^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.lv^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.md^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.net^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.org^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.pl^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.ru^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.tj^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.tm^$dnsrewrite=NOERROR;A;213.180.193.56 +|www.yandex.uz^$dnsrewrite=NOERROR;A;213.180.193.56 +|xn--d1acpjx3f.xn--p1ai^$dnsrewrite=NOERROR;A;213.180.193.56 +|ya.ru^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.az^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.by^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.co.il^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.com.am^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.com.ge^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.com.ru^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.com.tr^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.com^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.de^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.ee^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.eu^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.fi^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.fr^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.kz^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.lt^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.lv^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.md^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.net^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.org^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.pl^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.ru^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.tj^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.tm^$dnsrewrite=NOERROR;A;213.180.193.56 +|yandex.uz^$dnsrewrite=NOERROR;A;213.180.193.56 \ No newline at end of file diff --git a/internal/filtering/safesearch/rules/youtube.txt b/internal/filtering/safesearch/rules/youtube.txt new file mode 100644 index 00000000..70e3ae46 --- /dev/null +++ b/internal/filtering/safesearch/rules/youtube.txt @@ -0,0 +1,5 @@ +|www.youtube.com^$dnsrewrite=NOERROR;CNAME;restrictmoderate.youtube.com +|m.youtube.com^$dnsrewrite=NOERROR;CNAME;restrictmoderate.youtube.com +|youtubei.googleapis.com^$dnsrewrite=NOERROR;CNAME;restrictmoderate.youtube.com +|youtube.googleapis.com^$dnsrewrite=NOERROR;CNAME;restrictmoderate.youtube.com +|www.youtube-nocookie.com^$dnsrewrite=NOERROR;CNAME;restrictmoderate.youtube.com \ No newline at end of file diff --git a/internal/filtering/safesearch/safesearch.go b/internal/filtering/safesearch/safesearch.go new file mode 100644 index 00000000..e944e217 --- /dev/null +++ b/internal/filtering/safesearch/safesearch.go @@ -0,0 +1,269 @@ +// Package safesearch implements safesearch host matching. +package safesearch + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/gob" + "fmt" + "net" + "strings" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/filtering" + "github.com/AdguardTeam/golibs/cache" + "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/urlfilter" + "github.com/AdguardTeam/urlfilter/filterlist" + "github.com/AdguardTeam/urlfilter/rules" + "github.com/miekg/dns" +) + +// Service is a enum with service names used as search providers. +type Service string + +// Service enum members. +const ( + Bing Service = "bing" + DuckDuckGo Service = "duckduckgo" + Google Service = "google" + Pixabay Service = "pixabay" + Yandex Service = "yandex" + YouTube Service = "youtube" +) + +// isServiceProtected returns true if the service safe search is active. +func isServiceProtected(s filtering.SafeSearchConfig, service Service) (ok bool) { + switch service { + case Bing: + return s.Bing + case DuckDuckGo: + return s.DuckDuckGo + case Google: + return s.Google + case Pixabay: + return s.Pixabay + case Yandex: + return s.Yandex + case YouTube: + return s.YouTube + default: + panic(fmt.Errorf("safesearch: invalid sources: not found service %q", service)) + } +} + +// DefaultSafeSearch is the default safesearch struct. +type DefaultSafeSearch struct { + engine *urlfilter.DNSEngine + safeSearchCache cache.Cache + resolver filtering.Resolver + cacheTime time.Duration +} + +// NewDefaultSafeSearch returns new safesearch struct. CacheTime is an element +// TTL (in minutes). +func NewDefaultSafeSearch( + conf filtering.SafeSearchConfig, + cacheSize uint, + cacheTime time.Duration, +) (ss *DefaultSafeSearch, err error) { + engine, err := newEngine(filtering.SafeSearchListID, conf) + if err != nil { + return nil, err + } + + var resolver filtering.Resolver = net.DefaultResolver + if conf.CustomResolver != nil { + resolver = conf.CustomResolver + } + + return &DefaultSafeSearch{ + engine: engine, + safeSearchCache: cache.New(cache.Config{ + EnableLRU: true, + MaxSize: cacheSize, + }), + cacheTime: cacheTime, + resolver: resolver, + }, nil +} + +// newEngine creates new engine for provided safe search configuration. +func newEngine(listID int, conf filtering.SafeSearchConfig) (engine *urlfilter.DNSEngine, err error) { + var sb strings.Builder + for service, serviceRules := range safeSearchRules { + if isServiceProtected(conf, service) { + sb.WriteString(serviceRules) + } + } + + strList := &filterlist.StringRuleList{ + ID: listID, + RulesText: sb.String(), + IgnoreCosmetic: true, + } + + rs, err := filterlist.NewRuleStorage([]filterlist.RuleList{strList}) + if err != nil { + return nil, fmt.Errorf("creating rule storage: %w", err) + } + + engine = urlfilter.NewDNSEngine(rs) + log.Info("safesearch: filter %d: reset %d rules", listID, engine.RulesCount) + + return engine, nil +} + +// type check +var _ filtering.SafeSearch = (*DefaultSafeSearch)(nil) + +// SearchHost implements the [filtering.SafeSearch] interface for *DefaultSafeSearch. +func (ss *DefaultSafeSearch) SearchHost(host string, qtype uint16) (res *rules.DNSRewrite) { + r, _ := ss.engine.MatchRequest(&urlfilter.DNSRequest{ + Hostname: strings.ToLower(host), + DNSType: qtype, + }) + + rewritesRules := r.DNSRewrites() + if len(rewritesRules) > 0 { + return rewritesRules[0].DNSRewrite + } + + return nil +} + +// CheckHost implements the [filtering.SafeSearch] interface for +// *DefaultSafeSearch. +func (ss *DefaultSafeSearch) CheckHost( + host string, + qtype uint16, +) (res filtering.Result, err error) { + if log.GetLevel() >= log.DEBUG { + timer := log.StartTimer() + defer timer.LogElapsed("safesearch: lookup for %s", host) + } + + // Check cache. Return cached result if it was found + cachedValue, isFound := ss.getCachedResult(host) + if isFound { + log.Debug("safesearch: found in cache: %s", host) + + return cachedValue, nil + } + + rewrite := ss.SearchHost(host, qtype) + if rewrite == nil { + return filtering.Result{}, nil + } + + dRes, err := ss.newResult(rewrite, qtype) + if err != nil { + log.Debug("safesearch: failed to lookup addresses for %s: %s", host, err) + + return filtering.Result{}, err + } + + if dRes != nil { + res = *dRes + ss.setCacheResult(host, res) + + return res, nil + } + + return filtering.Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", host) +} + +// newResult creates Result object from rewrite rule. +func (ss *DefaultSafeSearch) newResult( + rewrite *rules.DNSRewrite, + qtype uint16, +) (res *filtering.Result, err error) { + res = &filtering.Result{ + Rules: []*filtering.ResultRule{{ + FilterListID: filtering.SafeSearchListID, + }}, + Reason: filtering.FilteredSafeSearch, + IsFiltered: true, + } + + if rewrite.RRType == qtype && (qtype == dns.TypeA || qtype == dns.TypeAAAA) { + ip, ok := rewrite.Value.(net.IP) + if !ok || ip == nil { + return nil, nil + } + + res.Rules[0].IP = ip + + return res, nil + } + + if rewrite.NewCNAME == "" { + return nil, nil + } + + ips, err := ss.resolver.LookupIP(context.Background(), "ip", rewrite.NewCNAME) + if err != nil { + return nil, err + } + + for _, ip := range ips { + if ip = ip.To4(); ip == nil { + continue + } + + res.Rules[0].IP = ip + + return res, nil + } + + return nil, nil +} + +// setCacheResult stores data in cache for host. +func (ss *DefaultSafeSearch) setCacheResult(host string, res filtering.Result) { + expire := uint32(time.Now().Add(ss.cacheTime).Unix()) + exp := make([]byte, 4) + binary.BigEndian.PutUint32(exp, expire) + buf := bytes.NewBuffer(exp) + + err := gob.NewEncoder(buf).Encode(res) + if err != nil { + log.Error("safesearch: cache encoding: %s", err) + + return + } + + val := buf.Bytes() + _ = ss.safeSearchCache.Set([]byte(host), val) + + log.Debug("safesearch: stored in cache: %s (%d bytes)", host, len(val)) +} + +// getCachedResult returns stored data from cache for host. +func (ss *DefaultSafeSearch) getCachedResult(host string) (res filtering.Result, ok bool) { + res = filtering.Result{} + + data := ss.safeSearchCache.Get([]byte(host)) + if data == nil { + return res, false + } + + exp := binary.BigEndian.Uint32(data[:4]) + if exp <= uint32(time.Now().Unix()) { + ss.safeSearchCache.Del([]byte(host)) + + return res, false + } + + buf := bytes.NewBuffer(data[4:]) + + err := gob.NewDecoder(buf).Decode(&res) + if err != nil { + log.Debug("safesearch: cache decoding: %s", err) + + return filtering.Result{}, false + } + + return res, true +} diff --git a/internal/filtering/safesearch/safesearch_test.go b/internal/filtering/safesearch/safesearch_test.go new file mode 100644 index 00000000..97d18f95 --- /dev/null +++ b/internal/filtering/safesearch/safesearch_test.go @@ -0,0 +1,202 @@ +package safesearch + +import ( + "context" + "net" + "testing" + "time" + + "github.com/AdguardTeam/AdGuardHome/internal/aghtest" + "github.com/AdguardTeam/AdGuardHome/internal/filtering" + "github.com/AdguardTeam/urlfilter/rules" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + safeSearchCacheSize = 5000 + cacheTime = 30 * time.Minute +) + +var defaultSafeSearchConf = filtering.SafeSearchConfig{ + Enabled: true, + Bing: true, + DuckDuckGo: true, + Google: true, + Pixabay: true, + Yandex: true, + YouTube: true, +} + +var yandexIP = net.IPv4(213, 180, 193, 56) + +func newForTest(t testing.TB, ssConf filtering.SafeSearchConfig) (ss *DefaultSafeSearch) { + ss, err := NewDefaultSafeSearch(ssConf, safeSearchCacheSize, cacheTime) + require.NoError(t, err) + + return ss +} + +func TestSafeSearch(t *testing.T) { + ss := newForTest(t, defaultSafeSearchConf) + val := ss.SearchHost("www.google.com", dns.TypeA) + + assert.Equal(t, &rules.DNSRewrite{NewCNAME: "forcesafesearch.google.com"}, val) +} + +func TestCheckHostSafeSearchYandex(t *testing.T) { + ss := newForTest(t, defaultSafeSearchConf) + + // Check host for each domain. + for _, host := range []string{ + "yandex.ru", + "yAndeX.ru", + "YANdex.COM", + "yandex.by", + "yandex.kz", + "www.yandex.com", + } { + res, err := ss.CheckHost(host, dns.TypeA) + 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, filtering.SafeSearchListID, res.Rules[0].FilterListID) + } +} + +func TestCheckHostSafeSearchGoogle(t *testing.T) { + resolver := &aghtest.TestResolver{} + ip, _ := resolver.HostToIPs("forcesafesearch.google.com") + + ss := newForTest(t, defaultSafeSearchConf) + ss.resolver = resolver + + // 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 := ss.CheckHost(host, dns.TypeA) + 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, filtering.SafeSearchListID, res.Rules[0].FilterListID) + }) + } +} + +func TestSafeSearchCacheYandex(t *testing.T) { + const domain = "yandex.ru" + + ss := newForTest(t, filtering.SafeSearchConfig{Enabled: false}) + + // Check host with disabled safesearch. + res, err := ss.CheckHost(domain, dns.TypeA) + require.NoError(t, err) + + assert.False(t, res.IsFiltered) + assert.Empty(t, res.Rules) + + ss = newForTest(t, defaultSafeSearchConf) + res, err = ss.CheckHost(domain, dns.TypeA) + 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 := ss.getCachedResult(domain) + require.True(t, isFound) + require.Len(t, cachedValue.Rules, 1) + + assert.Equal(t, cachedValue.Rules[0].IP, yandexIP) +} + +func TestSafeSearchCacheGoogle(t *testing.T) { + const domain = "www.google.ru" + + ss := newForTest(t, filtering.SafeSearchConfig{Enabled: false}) + + res, err := ss.CheckHost(domain, dns.TypeA) + require.NoError(t, err) + + assert.False(t, res.IsFiltered) + assert.Empty(t, res.Rules) + + resolver := &aghtest.TestResolver{} + ss = newForTest(t, defaultSafeSearchConf) + ss.resolver = resolver + + // Lookup for safesearch domain. + rewrite := ss.SearchHost(domain, dns.TypeA) + + ips, err := resolver.LookupIP(context.Background(), "ip", rewrite.NewCNAME) + require.NoError(t, err) + + var foundIP net.IP + for _, ip := range ips { + if ip.To4() != nil { + foundIP = ip + + break + } + } + + res, err = ss.CheckHost(domain, dns.TypeA) + require.NoError(t, err) + require.Len(t, res.Rules, 1) + + assert.True(t, res.Rules[0].IP.Equal(foundIP)) + + // Check cache. + cachedValue, isFound := ss.getCachedResult(domain) + require.True(t, isFound) + require.Len(t, cachedValue.Rules, 1) + + assert.True(t, cachedValue.Rules[0].IP.Equal(foundIP)) +} + +const googleHost = "www.google.com" + +var dnsRewriteSink *rules.DNSRewrite + +func BenchmarkSafeSearch(b *testing.B) { + ss := newForTest(b, defaultSafeSearchConf) + + for n := 0; n < b.N; n++ { + dnsRewriteSink = ss.SearchHost(googleHost, dns.TypeA) + } + + assert.Equal(b, "forcesafesearch.google.com", dnsRewriteSink.NewCNAME) +} + +var dnsRewriteParallelSink *rules.DNSRewrite + +func BenchmarkSafeSearch_parallel(b *testing.B) { + ss := newForTest(b, defaultSafeSearchConf) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + dnsRewriteParallelSink = ss.SearchHost(googleHost, dns.TypeA) + } + }) + + assert.Equal(b, "forcesafesearch.google.com", dnsRewriteParallelSink.NewCNAME) +} diff --git a/internal/filtering/safesearchhttp.go b/internal/filtering/safesearchhttp.go new file mode 100644 index 00000000..0ec94c89 --- /dev/null +++ b/internal/filtering/safesearchhttp.go @@ -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) +} diff --git a/internal/filtering/servicelist.go b/internal/filtering/servicelist.go index 6e02536a..112db944 100644 --- a/internal/filtering/servicelist.go +++ b/internal/filtering/servicelist.go @@ -1370,6 +1370,7 @@ var blockedServices = []blockedService{{ "||mastodon.au^", "||mastodon.bida.im^", "||mastodon.com.tr^", + "||mastodon.eus^", "||mastodon.green^", "||mastodon.ie^", "||mastodon.iriseden.eu^", @@ -1397,7 +1398,6 @@ var blockedServices = []blockedService{{ "||mindly.social^", "||mstdn.ca^", "||mstdn.jp^", - "||mstdn.party^", "||mstdn.social^", "||muenchen.social^", "||newsie.social^", @@ -1435,11 +1435,11 @@ var blockedServices = []blockedService{{ "||toot.wales^", "||troet.cafe^", "||twingyeo.kr^", - "||uiuxdev.social^", "||union.place^", "||universeodon.com^", "||urbanists.social^", "||vocalodon.net^", + "||wien.rocks^", "||wxw.moe^", }, }, { @@ -1636,6 +1636,14 @@ var blockedServices = []blockedService{{ "||snapchat.com^", "||snapkit.co", }, +}, { + ID: "soundcloud", + Name: "SoundCloud", + IconSVG: []byte(""), + Rules: []string{ + "||sndcdn.com^", + "||soundcloud.com^", + }, }, { ID: "spotify", Name: "Spotify", diff --git a/internal/home/client.go b/internal/home/client.go index d84b7c26..31e20743 100644 --- a/internal/home/client.go +++ b/internal/home/client.go @@ -4,6 +4,7 @@ import ( "encoding" "fmt" + "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/dnsproxy/proxy" ) @@ -15,6 +16,9 @@ type Client struct { // these upstream must be used. upstreamConfig *proxy.UpstreamConfig + safeSearchConf filtering.SafeSearchConfig + SafeSearch filtering.SafeSearch + Name string IDs []string @@ -24,7 +28,6 @@ type Client struct { UseOwnSettings bool FilteringEnabled bool - SafeSearchEnabled bool SafeBrowsingEnabled bool ParentalEnabled bool UseOwnBlockedServices bool diff --git a/internal/home/clients.go b/internal/home/clients.go index d5e0d02d..923f71ad 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -13,6 +13,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/filtering" + "github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch" "github.com/AdguardTeam/AdGuardHome/internal/querylog" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" @@ -69,6 +70,7 @@ func (clients *clientsContainer) Init( dhcpServer dhcpd.Interface, etcHosts *aghnet.HostsContainer, arpdb aghnet.ARPDB, + filteringConf *filtering.Config, ) { if clients.list != nil { log.Fatal("clients.list != nil") @@ -82,7 +84,7 @@ func (clients *clientsContainer) Init( clients.dhcpServer = dhcpServer clients.etcHosts = etcHosts clients.arpdb = arpdb - clients.addFromConfig(objects) + clients.addFromConfig(objects, filteringConf) if clients.testing { return @@ -133,6 +135,8 @@ func (clients *clientsContainer) reloadARP() { // clientObject is the YAML representation of a persistent client. type clientObject struct { + SafeSearchConf filtering.SafeSearchConfig `yaml:"safe_search"` + Name string `yaml:"name"` Tags []string `yaml:"tags"` @@ -143,14 +147,13 @@ type clientObject struct { UseGlobalSettings bool `yaml:"use_global_settings"` FilteringEnabled bool `yaml:"filtering_enabled"` ParentalEnabled bool `yaml:"parental_enabled"` - SafeSearchEnabled bool `yaml:"safesearch_enabled"` SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"` UseGlobalBlockedServices bool `yaml:"use_global_blocked_services"` } // addFromConfig initializes the clients container with objects from the // configuration file. -func (clients *clientsContainer) addFromConfig(objects []*clientObject) { +func (clients *clientsContainer) addFromConfig(objects []*clientObject, filteringConf *filtering.Config) { for _, o := range objects { cli := &Client{ Name: o.Name, @@ -161,11 +164,28 @@ func (clients *clientsContainer) addFromConfig(objects []*clientObject) { UseOwnSettings: !o.UseGlobalSettings, FilteringEnabled: o.FilteringEnabled, ParentalEnabled: o.ParentalEnabled, - SafeSearchEnabled: o.SafeSearchEnabled, + safeSearchConf: o.SafeSearchConf, SafeBrowsingEnabled: o.SafeBrowsingEnabled, 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 { if filtering.BlockedSvcKnown(s) { cli.BlockedServices = append(cli.BlockedServices, s) @@ -210,7 +230,7 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) { UseGlobalSettings: !cli.UseOwnSettings, FilteringEnabled: cli.FilteringEnabled, ParentalEnabled: cli.ParentalEnabled, - SafeSearchEnabled: cli.SafeSearchEnabled, + SafeSearchConf: cli.safeSearchConf, SafeBrowsingEnabled: cli.SafeBrowsingEnabled, UseGlobalBlockedServices: !cli.UseOwnBlockedServices, } diff --git a/internal/home/clients_test.go b/internal/home/clients_test.go index 612fe7db..ae58fa35 100644 --- a/internal/home/clients_test.go +++ b/internal/home/clients_test.go @@ -19,7 +19,7 @@ func TestClients(t *testing.T) { clients := clientsContainer{} clients.testing = true - clients.Init(nil, nil, nil, nil) + clients.Init(nil, nil, nil, nil, nil) t.Run("add_success", func(t *testing.T) { var ( @@ -201,7 +201,7 @@ func TestClientsWHOIS(t *testing.T) { clients := clientsContainer{ testing: true, } - clients.Init(nil, nil, nil, nil) + clients.Init(nil, nil, nil, nil, nil) whois := &RuntimeClientWHOISInfo{ Country: "AU", Orgname: "Example Org", @@ -250,7 +250,7 @@ func TestClientsAddExisting(t *testing.T) { clients := clientsContainer{ testing: true, } - clients.Init(nil, nil, nil, nil) + clients.Init(nil, nil, nil, nil, nil) t.Run("simple", func(t *testing.T) { ip := netip.MustParseAddr("1.1.1.1") @@ -328,7 +328,7 @@ func TestClientsCustomUpstream(t *testing.T) { clients := clientsContainer{ testing: true, } - clients.Init(nil, nil, nil, nil) + clients.Init(nil, nil, nil, nil, nil) // Add client with upstreams. ok, err := clients.Add(&Client{ diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go index d9bb0ea9..689f530e 100644 --- a/internal/home/clientshttp.go +++ b/internal/home/clientshttp.go @@ -7,6 +7,7 @@ import ( "net/netip" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" + "github.com/AdguardTeam/AdGuardHome/internal/filtering" ) // clientJSON is a common structure used by several handlers to deal with @@ -35,9 +36,10 @@ type clientJSON struct { Tags []string `json:"tags"` Upstreams []string `json:"upstreams"` - FilteringEnabled bool `json:"filtering_enabled"` - ParentalEnabled bool `json:"parental_enabled"` - SafeBrowsingEnabled bool `json:"safebrowsing_enabled"` + FilteringEnabled bool `json:"filtering_enabled"` + ParentalEnabled bool `json:"parental_enabled"` + SafeBrowsingEnabled bool `json:"safebrowsing_enabled"` + // Deprecated: use safeSearchConf. SafeSearchEnabled bool `json:"safesearch_enabled"` UseGlobalBlockedServices bool `json:"use_global_blocked_services"` 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 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{ Name: cj.Name, IDs: cj.IDs, @@ -95,7 +111,7 @@ func jsonToClient(cj clientJSON) (c *Client) { UseOwnSettings: !cj.UseGlobalSettings, FilteringEnabled: cj.FilteringEnabled, ParentalEnabled: cj.ParentalEnabled, - SafeSearchEnabled: cj.SafeSearchEnabled, + safeSearchConf: safeSearchConf, SafeBrowsingEnabled: cj.SafeBrowsingEnabled, UseOwnBlockedServices: !cj.UseGlobalBlockedServices, @@ -107,6 +123,11 @@ func jsonToClient(cj clientJSON) (c *Client) { // Convert Client object to JSON func clientToJSON(c *Client) (cj *clientJSON) { + // TODO(d.kolyshev): Remove after cleaning the deprecated + // [clientJSON.SafeSearchEnabled] field. + cloneVal := c.safeSearchConf + safeSearchConf := &cloneVal + return &clientJSON{ Name: c.Name, IDs: c.IDs, @@ -114,7 +135,7 @@ func clientToJSON(c *Client) (cj *clientJSON) { UseGlobalSettings: !c.UseOwnSettings, FilteringEnabled: c.FilteringEnabled, ParentalEnabled: c.ParentalEnabled, - SafeSearchEnabled: c.SafeSearchEnabled, + SafeSearchEnabled: safeSearchConf.Enabled, SafeBrowsingEnabled: c.SafeBrowsingEnabled, UseGlobalBlockedServices: !c.UseOwnBlockedServices, diff --git a/internal/home/config.go b/internal/home/config.go index 60575d2d..625dc86d 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -283,6 +283,12 @@ var config = &configuration{ TrustedProxies: []string{"127.0.0.0/8", "::1/128"}, CacheSize: 4 * 1024 * 1024, + EDNSClientSubnet: &dnsforward.EDNSClientSubnet{ + CustomIP: "", + Enabled: false, + UseCustom: false, + }, + // set default maximum concurrent queries to 300 // we introduced a default limit due to this: // https://github.com/AdguardTeam/AdGuardHome/issues/2015#issuecomment-674041912 diff --git a/internal/home/dns.go b/internal/home/dns.go index 14ad4b09..47d6f177 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -1,6 +1,7 @@ package home import ( + "context" "fmt" "net" "net/netip" @@ -427,7 +428,8 @@ func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering } setts.FilteringEnabled = c.FilteringEnabled - setts.SafeSearchEnabled = c.SafeSearchEnabled + setts.SafeSearchEnabled = c.safeSearchConf.Enabled + setts.ClientSafeSearch = c.SafeSearch setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled setts.ParentalEnabled = c.ParentalEnabled } @@ -533,3 +535,29 @@ func closeDNSServer() { log.Debug("all dns modules are closed") } + +// 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 +} diff --git a/internal/home/home.go b/internal/home/home.go index 9c0351eb..055b0ea2 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -28,6 +28,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/filtering" + "github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch" "github.com/AdguardTeam/AdGuardHome/internal/querylog" "github.com/AdguardTeam/AdGuardHome/internal/stats" "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.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.HTTPRegister = httpRegister config.DHCP.ConfigModified = onConfigModified @@ -328,33 +339,16 @@ func setupConfig(opts options) (err error) { 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 { - 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 + + 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 @@ -368,6 +362,34 @@ func setupConfig(opts options) (err error) { 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) { var clientFS fs.FS if opts.localFrontend { diff --git a/internal/home/upgrade.go b/internal/home/upgrade.go index af3715ab..7a85c524 100644 --- a/internal/home/upgrade.go +++ b/internal/home/upgrade.go @@ -22,7 +22,7 @@ import ( ) // currentSchemaVersion is the current schema version. -const currentSchemaVersion = 17 +const currentSchemaVersion = 19 // These aliases are provided for convenience. type ( @@ -91,6 +91,8 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) { upgradeSchema15to16, upgradeSchema16to17, upgradeSchema17to18, + upgradeSchema18to19, + upgradeSchema19to20, } n := 0 @@ -947,15 +949,134 @@ func upgradeSchema16to17(diskConf yobj) (err error) { // 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 +} + +// upgradeSchema19to20 performs the following changes: +// +// # BEFORE: // 'statistics': // 'interval': 1 // // # AFTER: // 'statistics': // 'interval': 24h -func upgradeSchema17to18(diskConf yobj) (err error) { - log.Printf("Upgrade yaml: 17 to 18") - diskConf["schema_version"] = 18 +func upgradeSchema19to20(diskConf yobj) (err error) { + log.Printf("Upgrade yaml: 19 to 20") + diskConf["schema_version"] = 20 statsVal, ok := diskConf["statistics"] if !ok { diff --git a/internal/home/upgrade_test.go b/internal/home/upgrade_test.go index 9e0830c3..44a33f32 100644 --- a/internal/home/upgrade_test.go +++ b/internal/home/upgrade_test.go @@ -810,6 +810,149 @@ 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) + }) + } +} + +func TestUpgradeSchema19to20(t *testing.T) { testCases := []struct { ivl any want any @@ -835,7 +978,7 @@ func TestUpgradeSchema17to18(t *testing.T) { "schema_version": 17, } t.Run(tc.name, func(t *testing.T) { - err := upgradeSchema17to18(conf) + err := upgradeSchema19to20(conf) if tc.wantErr != "" { require.Error(t, err) @@ -846,7 +989,7 @@ func TestUpgradeSchema17to18(t *testing.T) { } require.NoError(t, err) - require.Equal(t, conf["schema_version"], 18) + require.Equal(t, conf["schema_version"], 20) statsVal, ok := conf["statistics"] require.True(t, ok) @@ -864,13 +1007,13 @@ func TestUpgradeSchema17to18(t *testing.T) { } t.Run("no_stats", func(t *testing.T) { - err := upgradeSchema17to18(yobj{}) + err := upgradeSchema19to20(yobj{}) assert.NoError(t, err) }) t.Run("bad_stats", func(t *testing.T) { - err := upgradeSchema17to18(yobj{ + err := upgradeSchema19to20(yobj{ "statistics": 0, }) @@ -882,7 +1025,7 @@ func TestUpgradeSchema17to18(t *testing.T) { "statistics": yobj{}, } - err := upgradeSchema17to18(conf) + err := upgradeSchema19to20(conf) require.NoError(t, err) statsVal, ok := conf["statistics"] diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index b367153f..3216b40a 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -35,7 +35,7 @@ set -f -u go_version="$( "${GO:-go}" version )" readonly go_version -go_min_version='go1.19.6' +go_min_version='go1.19.7' go_version_msg=" warning: your go version (${go_version}) is different from the recommended minimal one (${go_min_version}). if you have the version installed, please set the GO environment variable.