Merge branch 'master' into AGDNS-3224-aghhttp-slog

This commit is contained in:
Stanislav Chzhen 2025-09-29 20:07:22 +03:00
commit e2ed1fef7f
45 changed files with 797 additions and 480 deletions

View File

@ -9,17 +9,29 @@ The format is based on [*Keep a Changelog*](https://keepachangelog.com/en/1.0.0/
<!--
## [v0.108.0] TBA
## [v0.107.67] - 2025-09-25 (APPROX.)
## [v0.107.68] - 2025-10-14 (APPROX.)
See also the [v0.107.67 GitHub milestone][ms-v0.107.67].
See also the [v0.107.68 GitHub milestone][ms-v0.107.68].
[ms-v0.107.67]: https://github.com/AdguardTeam/AdGuardHome/milestone/102?closed=1
[ms-v0.107.68]: https://github.com/AdguardTeam/AdGuardHome/milestone/103?closed=1
NOTE: Add new changes BELOW THIS COMMENT.
-->
### Added
- New fields `"groups"` and `"group_id"` added to the HTTP API (`GET /control/blocked_services/all`). See `openapi/openapi.yaml` for the full description.
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
## [v0.107.67] - 2025-09-29
See also the [v0.107.67 GitHub milestone][ms-v0.107.67].
### Added
- The *HaGeZi's DNS Rebind Protection* filter for protecting against DNS rebinding attacks ([#102]).
- Support for configuring the suggested default HTTP port for the installation wizard via the `ADGUARD_HOME_DEFAULT_WEB_PORT` environment variable (useful for vendors).
### Changed
@ -31,11 +43,10 @@ NOTE: Add new changes BELOW THIS COMMENT.
- Excessive configuration file overwrites when visiting the Web UI and a non-empty `language` is set.
- Lowered the severity of log messages for failed deletion of old filter files ([#7964]).
[#102]: https://github.com/AdguardTeam/AdGuardHome/issues/102
[#7964]: https://github.com/AdguardTeam/AdGuardHome/issues/7964
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
[ms-v0.107.67]: https://github.com/AdguardTeam/AdGuardHome/milestone/102?closed=1
## [v0.107.66] - 2025-09-15
@ -60,7 +71,7 @@ See also the [v0.107.66 GitHub milestone][ms-v0.107.66].
[#7985]: https://github.com/AdguardTeam/AdGuardHome/issues/7985
[#7987]: https://github.com/AdguardTeam/AdGuardHome/issues/7987
[go-1.25.1]: https://groups.google.com/g/golang-announce/c/PtW9VW21NPs
[go-1.25.1]: https://groups.google.com/g/golang-announce/c/PtW9VW21NPs
[ms-v0.107.66]: https://github.com/AdguardTeam/AdGuardHome/milestone/101?closed=1
## [v0.107.65] - 2025-08-20
@ -106,7 +117,7 @@ In this release, the schema version has changed from 29 to 30.
[#7923]: https://github.com/AdguardTeam/AdGuardHome/issues/7923
[go-1.24.6]: https://groups.google.com/g/golang-announce/c/x5MKroML2yM
[go-1.24.6]: https://groups.google.com/g/golang-announce/c/x5MKroML2yM
[ms-v0.107.65]: https://github.com/AdguardTeam/AdGuardHome/milestone/100?closed=1
## [v0.107.64] - 2025-07-28
@ -125,7 +136,7 @@ See also the [v0.107.64 GitHub milestone][ms-v0.107.64].
[#7856]: https://github.com/AdguardTeam/AdGuardHome/issues/7856
[#7903]: https://github.com/AdguardTeam/AdGuardHome/issues/7903
[go-1.24.5]: https://groups.google.com/g/golang-announce/c/gTNJnDXmn34
[go-1.24.5]: https://groups.google.com/g/golang-announce/c/gTNJnDXmn34
[ms-v0.107.64]: https://github.com/AdguardTeam/AdGuardHome/milestone/99?closed=1
## [v0.107.63] - 2025-06-26
@ -142,7 +153,7 @@ See also the [v0.107.63 GitHub milestone][ms-v0.107.63].
- Status reported by the systemd service implementation in cases of auto-restart after a failed start.
[go-1.24.4]: https://groups.google.com/g/golang-announce/c/ufZ8WpEsA3A
[go-1.24.4]: https://groups.google.com/g/golang-announce/c/ufZ8WpEsA3A
[ms-v0.107.63]: https://github.com/AdguardTeam/AdGuardHome/milestone/98?closed=1
## [v0.107.62] - 2025-05-27
@ -168,7 +179,7 @@ See also the [v0.107.62 GitHub milestone][ms-v0.107.62].
[#2945]: https://github.com/AdguardTeam/AdGuardHome/issues/2945
[#7801]: https://github.com/AdguardTeam/AdGuardHome/issues/7801
[go-1.24.3]: https://groups.google.com/g/golang-announce/c/UZoIkUT367A
[go-1.24.3]: https://groups.google.com/g/golang-announce/c/UZoIkUT367A
[ms-v0.107.62]: https://github.com/AdguardTeam/AdGuardHome/milestone/97?closed=1
## [v0.107.61] - 2025-04-22
@ -3265,11 +3276,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
[ms-v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/milestone/28?closed=1
<!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.67...HEAD
[v0.107.67]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.66...v0.107.67
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.68...HEAD
[v0.107.68]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.67...v0.107.68
-->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.66...HEAD
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.67...HEAD
[v0.107.67]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.66...v0.107.67
[v0.107.66]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.65...v0.107.66
[v0.107.65]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.64...v0.107.65
[v0.107.64]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.63...v0.107.64

View File

@ -156,7 +156,7 @@
"enforce_safe_search": "Узмацніць бяспечны пошук",
"enforce_save_search_hint": "AdGuard Home можа забяспечыць бяспечны пошук у наступных пошукавых сістэмах: Google, Youtube, Bing, DuckDuckGo, Yandex і Pixabay.",
"no_servers_specified": "Не паказаных сервераў",
"general_settings": "Асноўныя налады",
"general_settings": "Агульныя налады",
"dns_settings": "Налады DNS",
"dns_blocklists": "Чорныя спісы DNS",
"dns_allowlists": "Белыя спісы DNS",

View File

@ -643,7 +643,7 @@
"dnssec_enable_desc": "Установите флаг DNSSEC в исходящих DNS-запросах и проверьте результат (требуется резолвер с поддержкой DNSSEC).",
"validated_with_dnssec": "Подтверждено с помощью DNSSEC",
"all_queries": "Все запросы",
"show_blocked_responses": "Заблокировано",
"show_blocked_responses": "Заблокирован",
"show_whitelisted_responses": "Разрешённые",
"show_processed_responses": "Обработан",
"blocked_safebrowsing": "Заблокировано согласно базе данных Safe Browsing",
@ -651,7 +651,7 @@
"blocked_threats": "Заблокировано угроз",
"allowed": "Разрешённые",
"filtered": "Отфильтрованные",
"rewritten": "Переписанные",
"rewritten": "Перезаписан",
"safe_search": "Безопасный поиск",
"blocklist": "Чёрный список",
"milliseconds_abbreviation": "мс",

View File

@ -655,7 +655,10 @@
"safe_search": "Безпечний пошук",
"blocklist": "Список блокування",
"milliseconds_abbreviation": "мс",
"cache_enabled": "Увімкнути кеш",
"cache_enabled_desc": "Зберігати відповіді DNS локально.",
"cache_size": "Розмір кешу",
"cache_size_validation": "Розмір кешу має бути більшим за нуль, коли цю функцію увімкнуто.",
"cache_size_desc": "Розмір кешу DNS (в байтах). Щоб вимкнути кешування, встановіть 0.",
"cache_ttl_min_override": "Змінити мінімальний TTL",
"cache_ttl_max_override": "Змінити максимальний TTL",

View File

@ -214,6 +214,12 @@ export default {
"homepage": "https://github.com/hagezi/dns-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_55.txt"
},
"hagezi_dns_rebind_protection": {
"name": "HaGeZi's DNS Rebind Protection",
"categoryId": "security",
"homepage": "https://github.com/hagezi/dns-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_71.txt"
},
"hagezi_dyndns_blocklist": {
"name": "HaGeZi's DynDNS Blocklist",
"categoryId": "security",

62
go.mod
View File

@ -4,9 +4,8 @@ go 1.25.1
require (
github.com/AdguardTeam/dnsproxy v0.76.1
// TODO(s.chzhen): Use osutil/executil and fakeos/fakeexec.
github.com/AdguardTeam/golibs v0.34.0
github.com/AdguardTeam/urlfilter v0.21.0
github.com/AdguardTeam/golibs v0.34.1
github.com/AdguardTeam/urlfilter v0.22.0
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.4.0
github.com/bluele/gcache v0.0.2
@ -20,34 +19,34 @@ require (
github.com/google/gopacket v1.1.19
github.com/google/renameio/v2 v2.0.0
github.com/google/uuid v1.6.0
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f
github.com/kardianos/service v1.2.2
github.com/insomniacslk/dhcp v0.0.0-20250828142853-d3abe7ccb0ad
github.com/kardianos/service v1.2.4
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
github.com/mdlayher/netlink v1.7.2
github.com/mdlayher/netlink v1.8.0
github.com/mdlayher/packet v1.1.2
// TODO(a.garipov): This package is deprecated; find a new one or use our
// own code for that. Perhaps, use gopacket.
github.com/mdlayher/raw v0.1.0
github.com/miekg/dns v1.1.68
github.com/quic-go/quic-go v0.53.0
github.com/quic-go/quic-go v0.54.0
github.com/stretchr/testify v1.11.1
github.com/ti-mo/netfilter v0.5.3
go.etcd.io/bbolt v1.4.1
go.etcd.io/bbolt v1.4.3
go.yaml.in/yaml/v4 v4.0.0-rc.2
golang.org/x/crypto v0.41.0
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6
golang.org/x/net v0.43.0
golang.org/x/sys v0.35.0
golang.org/x/crypto v0.42.0
golang.org/x/exp v0.0.0-20250911091902-df9299821621
golang.org/x/net v0.44.0
golang.org/x/sys v0.36.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
howett.net/plist v1.0.1
)
require (
cloud.google.com/go v0.121.6 // indirect
cloud.google.com/go v0.122.0 // indirect
cloud.google.com/go/ai v0.12.1 // indirect
cloud.google.com/go/auth v0.16.5 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.8.0 // indirect
cloud.google.com/go/compute/metadata v0.8.3 // indirect
cloud.google.com/go/longrunning v0.6.7 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/ameshkov/dnsstamps v1.0.3 // indirect
@ -80,33 +79,34 @@ require (
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/uudashr/gocognit v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.uber.org/mock v0.5.2 // indirect
golang.org/x/exp/typeparams v0.0.0-20250819193227-8b4c13bb791b // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/telemetry v0.0.0-20250829165349-50b750f55de1 // indirect
golang.org/x/term v0.34.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.36.0 // indirect
go.uber.org/mock v0.6.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/oauth2 v0.31.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.13.0 // indirect
golang.org/x/tools v0.37.0 // indirect
golang.org/x/vuln v1.1.4 // indirect
gonum.org/v1/gonum v0.16.0 // indirect
google.golang.org/api v0.248.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
google.golang.org/grpc v1.75.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/api v0.249.0 // indirect
google.golang.org/genproto v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/tools v0.6.1 // indirect
mvdan.cc/editorconfig v0.3.0 // indirect
mvdan.cc/gofumpt v0.9.0 // indirect
mvdan.cc/gofumpt v0.9.1 // indirect
mvdan.cc/sh/v3 v3.12.0 // indirect
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect
)

141
go.sum
View File

@ -1,21 +1,21 @@
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
cloud.google.com/go v0.122.0 h1:0JTLGrcSIs3HIGsgVPvTx3cfyFSP/k9CI8vLPHTd6Wc=
cloud.google.com/go v0.122.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/ai v0.12.1 h1:m1n/VjUuHS+pEO/2R4/VbuuEIkgk0w67fDQvFaMngM0=
cloud.google.com/go/ai v0.12.1/go.mod h1:5vIPNe1ZQsVZqCliXIPL4QnhObQQY4d9hAGHdVc4iw4=
cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=
cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
cloud.google.com/go/compute/metadata v0.8.3 h1:1AzcHmzbrX8t3m0CVosfxCAwGvaAShtrnlNxDriLgIk=
cloud.google.com/go/compute/metadata v0.8.3/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
github.com/AdguardTeam/dnsproxy v0.76.1 h1:ms5vgdbYYXrKGPEpMFqUeql2j3aSfK1tGbCKju9rUgM=
github.com/AdguardTeam/dnsproxy v0.76.1/go.mod h1:9Mw3wQMTYwM/HR9FdtatQAd+m0S8mbwq2J+UZiy/gXc=
github.com/AdguardTeam/golibs v0.34.0 h1:JQK024DkTYxE7vsPVsYsoyDHW/53Nun7OYb9qscniK8=
github.com/AdguardTeam/golibs v0.34.0/go.mod h1:K4C2EbfSEM1zY5YXoti9SfbTAHN/kIX97LpDtCwORrM=
github.com/AdguardTeam/urlfilter v0.21.0 h1:ThIxiP7yoaXt8JTEroGQeU5ftQSoFpUq+t1L+TIx2pA=
github.com/AdguardTeam/urlfilter v0.21.0/go.mod h1:xoZ3AF5qpE9ngbbeSShY9hgVeyHtm9MdH5xH1u714Wg=
github.com/AdguardTeam/golibs v0.34.1 h1:RyBpZiXnJqlO3T+xjWldlxsEZDelmaFfKvXiJHDZZFQ=
github.com/AdguardTeam/golibs v0.34.1/go.mod h1:K4C2EbfSEM1zY5YXoti9SfbTAHN/kIX97LpDtCwORrM=
github.com/AdguardTeam/urlfilter v0.22.0 h1:ybOz3FywbpGDGC+8gFFkM1LMUOSosY7CWSBXIYXnG1U=
github.com/AdguardTeam/urlfilter v0.22.0/go.mod h1:q0lWKapXlYTA4TUWUM1YDwU6Q0PKvQEokztcvRV2OW0=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
@ -48,8 +48,6 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ping/ping v1.2.0 h1:vsJ8slZBZAXNCK4dPcI2PEE9eM9n9RbXbGouVQ/Y4yQ=
github.com/go-ping/ping v1.2.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
@ -95,29 +93,27 @@ github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk
github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU=
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBeJssZ1YBCMZ5Lzu1pX4vhftDvU10WUVb1uXKtM=
github.com/insomniacslk/dhcp v0.0.0-20250828142853-d3abe7ccb0ad h1:F2k0SD55QlicoEZ1IK+a6QEt/eUqT+okQAoQG7AwSk0=
github.com/insomniacslk/dhcp v0.0.0-20250828142853-d3abe7ccb0ad/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jstemmer/go-junit-report/v2 v2.1.0 h1:X3+hPYlSczH9IMIpSC9CQSZA0L+BipYafciZUWHEmsc=
github.com/jstemmer/go-junit-report/v2 v2.1.0/go.mod h1:mgHVr7VUo5Tn8OLVr1cKnLuEy0M92wdRntM99h7RkgQ=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/kardianos/service v1.2.4 h1:XNlGtZOYNx2u91urOdg/Kfmc+gfmuIo1Dd3rEi2OgBk=
github.com/kardianos/service v1.2.4/go.mod h1:E4V9ufUuY82F7Ztlu1eN9VXWIQxg8NoLQlmFe0MtrXc=
github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M=
github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og=
github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/netlink v1.8.0 h1:e7XNIYJKD7hUct3Px04RuIGJbBxy1/c4nX7D5YyvvlM=
github.com/mdlayher/netlink v1.8.0/go.mod h1:UhgKXUlDQhzb09DrCl2GuRNEglHmhYoWAHid9HK3594=
github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
@ -141,22 +137,16 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.53.0 h1:QHX46sISpG2S03dPeZBgVIZp8dGagIaiu2FiVYvpCZI=
github.com/quic-go/quic-go v0.53.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/securego/gosec/v2 v2.22.8 h1:3NMpmfXO8wAVFZPNsd3EscOTa32Jyo6FLLlW53bexMI=
github.com/securego/gosec/v2 v2.22.8/go.mod h1:ZAw8K2ikuH9qDlfdV87JmNghnVfKB1XC7+TVzk6Utto=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
@ -166,22 +156,16 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD
github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU=
github.com/ti-mo/netfilter v0.5.3 h1:ikzduvnaUMwre5bhbNwWOd6bjqLMVb33vv0XXbK0xGQ=
github.com/ti-mo/netfilter v0.5.3/go.mod h1:08SyBCg6hu1qyQk4s3DjjJKNrm3RTb32nm6AzyT972E=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=
github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.4.1 h1:5mOV+HWjIPLEAlUGMsveaUvK2+byZMFOzojoi7bh7uI=
go.etcd.io/bbolt v1.4.1/go.mod h1:c8zu2BnXWTu2XM4XcICtbGSl9cFwsXtcf9zLt2OncM8=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
@ -198,62 +182,61 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/exp/typeparams v0.0.0-20250819193227-8b4c13bb791b h1:GU1ttDuJS89SePnuEsEuLj7dMMFP2JkGsDV1Z51iDXo=
golang.org/x/exp/typeparams v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621 h1:Yl4H5w2RV7L/dvSHp2GerziT5K2CORgFINPaMFxWGWw=
golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
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.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
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.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20250829165349-50b750f55de1 h1:zRLyLxwtVA3d2qsYvSJVSNco/qz2wzR8oJNhpLTN6x8=
golang.org/x/telemetry v0.0.0-20250829165349-50b750f55de1/go.mod h1:JIJwPkb04vX0KeIBbQ7epGtgIjA8ihHbsAtW4A/lIQ4=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 h1:dHQOQddU4YHS5gY33/6klKjq7Gp3WwMyOXGNp5nzRj8=
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
@ -264,18 +247,18 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.248.0 h1:hUotakSkcwGdYUqzCRc5yGYsg4wXxpkKlW5ryVqvC1Y=
google.golang.org/api v0.248.0/go.mod h1:yAFUAF56Li7IuIQbTFoLwXTCI6XCFKueOlS7S9e4F9k=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 h1:APHvLLYBhtZvsbnpkfknDZ7NyH4z5+ub/I0u8L3Oz6g=
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1/go.mod h1:xUjFWUnWDpZ/C0Gu0qloASKFb6f8/QXiiXhSPFsD668=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/api v0.249.0 h1:0VrsWAKzIZi058aeq+I86uIXbNhm9GxSHpbmZ92a38w=
google.golang.org/api v0.249.0/go.mod h1:dGk9qyI0UYPwO/cjt2q06LG/EhUpwZGdAbYF14wHHrQ=
google.golang.org/genproto v0.0.0-20250908214217-97024824d090 h1:ywCL7vA2n3vVHyf+bx1ZV/knaTPRI8GIeKY0MEhEeOc=
google.golang.org/genproto v0.0.0-20250908214217-97024824d090/go.mod h1:zwJI9HzbJJlw2KXy0wX+lmT2JuZoaKK9JC4ppqmxxjk=
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:/OQuEa4YWtDt7uQWHd3q3sUMb+QOLQUg1xa8CEsRv5w=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@ -290,8 +273,8 @@ howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
mvdan.cc/editorconfig v0.3.0 h1:D1D2wLYEYGpawWT5SpM5pRivgEgXjtEXwC9MWhEY0gQ=
mvdan.cc/editorconfig v0.3.0/go.mod h1:NcJHuDtNOTEJ6251indKiWuzK6+VcrMuLzGMLKBFupQ=
mvdan.cc/gofumpt v0.9.0 h1:W0wNHMSvDBDIyZsm3nnGbVfgp5AknzBrGJnfLCy501w=
mvdan.cc/gofumpt v0.9.0/go.mod h1:3xYtNemnKiXaTh6R4VtlqDATFwBbdXI8lJvH/4qk7mw=
mvdan.cc/gofumpt v0.9.1 h1:p5YT2NfFWsYyTieYgwcQ8aKV3xRvFH4uuN/zB2gBbMQ=
mvdan.cc/gofumpt v0.9.1/go.mod h1:3xYtNemnKiXaTh6R4VtlqDATFwBbdXI8lJvH/4qk7mw=
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8=

View File

@ -1,25 +1,29 @@
package aghalg
import (
"cmp"
"slices"
)
// SortedMap is a map that keeps elements in order with internal sorting
// function. Must be initialized by the [NewSortedMap].
// function. It must be initialized with [NewSortedMap] or [NewSortedMapFunc].
type SortedMap[K comparable, V any] struct {
vals map[K]V
cmp func(a, b K) (res int)
keys []K
}
// NewSortedMap initializes the new instance of sorted map. cmp is a sort
// function to keep elements in order.
//
// TODO(s.chzhen): Use cmp.Compare in Go 1.21.
func NewSortedMap[K comparable, V any](cmp func(a, b K) (res int)) SortedMap[K, V] {
return SortedMap[K, V]{
// NewSortedMap initializes a new instance of sorted map.
func NewSortedMap[K cmp.Ordered, V any]() (m *SortedMap[K, V]) {
return NewSortedMapFunc[K, V](cmp.Compare[K])
}
// NewSortedMapFunc initializes a new instance of sorted map. cmpFunc is a
// comparison function to keep elements in order. cmpFunc must not be nil.
func NewSortedMapFunc[K comparable, V any](cmpFunc func(a, b K) (res int)) (m *SortedMap[K, V]) {
return &SortedMap[K, V]{
vals: map[K]V{},
cmp: cmp,
cmp: cmpFunc,
}
}
@ -69,7 +73,7 @@ func (m *SortedMap[K, V]) Clear() {
return
}
m.keys = nil
m.keys = m.keys[:0]
clear(m.vals)
}

View File

@ -1,7 +1,6 @@
package aghalg_test
import (
"strings"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
@ -9,7 +8,7 @@ import (
)
func TestNewSortedMap(t *testing.T) {
var m aghalg.SortedMap[string, int]
var m *aghalg.SortedMap[string, int]
letters := []string{}
for i := range 10 {
@ -18,7 +17,7 @@ func TestNewSortedMap(t *testing.T) {
}
t.Run("create_and_fill", func(t *testing.T) {
m = aghalg.NewSortedMap[string, int](strings.Compare)
m = aghalg.NewSortedMap[string, int]()
nums := []int{}
for i, r := range letters {

View File

@ -39,6 +39,7 @@ func checkOtherDHCP(ifaceName string) (ok4, ok6 bool, err4, err6 error) {
}
// ifaceIPv4Subnet returns the first suitable IPv4 subnetwork iface has.
// iface must not be nil.
func ifaceIPv4Subnet(iface *net.Interface) (subnet netip.Prefix, err error) {
var addrs []net.Addr
if addrs, err = iface.Addrs(); err != nil {
@ -90,6 +91,8 @@ func checkOtherDHCPv4(iface *net.Interface) (ok bool, err error) {
return discover4(iface, dstAddr, hostname)
}
// discover4 sends a DHCPv4 discovery to the specified network interface and
// waits for response. iface and dstAddr must not be nil.
func discover4(iface *net.Interface, dstAddr *net.UDPAddr, hostname string) (ok bool, err error) {
var req *dhcpv4.DHCPv4
if req, err = dhcpv4.NewDiscovery(iface.HardwareAddr); err != nil {
@ -120,17 +123,9 @@ func discover4(iface *net.Interface, dstAddr *net.UDPAddr, hostname string) (ok
}
for {
if err = c.SetDeadline(time.Now().Add(defaultDiscoverTime)); err != nil {
return false, fmt.Errorf("setting deadline: %w", err)
}
var next bool
ok, next, err = tryConn4(req, c, iface)
if next {
if err != nil {
log.Debug("dhcpv4: trying a connection: %s", err)
}
continue
}
@ -142,9 +137,20 @@ func discover4(iface *net.Interface, dstAddr *net.UDPAddr, hostname string) (ok
}
}
// tryConn4 reads and validates DHCPv4 response packet if it matches
// the original request. req and c must not be nil.
//
// TODO(a.garipov): Refactor further. Inspect error handling, remove parameter
// next, address the TODO, merge with tryConn6, etc.
func tryConn4(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, next bool, err error) {
func tryConn4(
req *dhcpv4.DHCPv4,
c net.PacketConn,
iface *net.Interface,
) (ok, next bool, err error) {
if err = c.SetDeadline(time.Now().Add(defaultDiscoverTime)); err != nil {
return false, false, fmt.Errorf("dhcpv4: setting deadline: %w", err)
}
// TODO: replicate dhclient's behavior of retrying several times with
// progressively longer timeouts.
log.Tracef("dhcpv4: waiting %v for an answer", defaultDiscoverTime)
@ -221,6 +227,8 @@ func checkOtherDHCPv6(iface *net.Interface) (ok bool, err error) {
return discover6(iface, udpAddr, dstAddr)
}
// discover6 sends a DHCPv6 discovery to the specified network interface and
// waits for response. iface, updAddr and dstAddr must not be nil.
func discover6(iface *net.Interface, udpAddr, dstAddr *net.UDPAddr) (ok bool, err error) {
req, err := dhcpv6.NewSolicit(iface.HardwareAddr)
if err != nil {
@ -243,10 +251,6 @@ func discover6(iface *net.Interface, udpAddr, dstAddr *net.UDPAddr) (ok bool, er
var next bool
ok, next, err = tryConn6(req, c)
if next {
if err != nil {
log.Debug("dhcpv6: trying a connection: %s", err)
}
continue
}
@ -258,6 +262,9 @@ func discover6(iface *net.Interface, udpAddr, dstAddr *net.UDPAddr) (ok bool, er
}
}
// tryConn6 reads and validates DHCPv6 response packet if it matches
// the original request. req and c must not be nil.
//
// TODO(a.garipov): See the comment on tryConn4. Sigh…
func tryConn6(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error) {
// TODO: replicate dhclient's behavior of retrying several times with

View File

@ -23,7 +23,7 @@ type NetIface interface {
Addrs() ([]net.Addr, error)
}
// IfaceIPAddrs returns the interface's IP addresses.
// IfaceIPAddrs returns the interface's IP addresses. iface must not be nil.
func IfaceIPAddrs(iface NetIface, ipv IPVersion) (ips []net.IP, err error) {
switch ipv {
case IPVersion4, IPVersion6:
@ -38,25 +38,7 @@ func IfaceIPAddrs(iface NetIface, ipv IPVersion) (ips []net.IP, err error) {
}
for _, a := range addrs {
var ip net.IP
switch a := a.(type) {
case *net.IPAddr:
ip = a.IP
case *net.IPNet:
ip = a.IP
default:
continue
}
// Assume that net.(*Interface).Addrs can only return valid IPv4 and
// IPv6 addresses. Thus, if it isn't an IPv4 address, it must be an
// IPv6 one.
ip4 := ip.To4()
if ipv == IPVersion4 {
if ip4 != nil {
ips = append(ips, ip4)
}
} else if ip4 == nil {
if ip := ipFromAddr(a, ipv); ip != nil {
ips = append(ips, ip)
}
}
@ -64,6 +46,29 @@ func IfaceIPAddrs(iface NetIface, ipv IPVersion) (ips []net.IP, err error) {
return ips, nil
}
// ipFromAddr converts addr to IP. addr must not be nil.
func ipFromAddr(addr net.Addr, ipv IPVersion) (ip net.IP) {
switch addr := addr.(type) {
case *net.IPAddr:
ip = addr.IP
case *net.IPNet:
ip = addr.IP
default:
return nil
}
// Assume that net.Addr can only be valid IPv4 or IPv6. Thus,
// if it isn't an IPv4 address, it must be an IPv6 one.
ip4 := ip.To4()
if ipv == IPVersion4 {
return ip4
} else if ip4 == nil {
return ip
}
return nil
}
// IfaceDNSIPAddrs returns IP addresses of the interface suitable to send to
// clients as DNS addresses. If err is nil, addrs contains either no addresses
// or at least two.

View File

@ -148,44 +148,72 @@ func NetInterfaceFrom(iface *net.Interface) (niface *NetInterface, err error) {
addrs, err := iface.Addrs()
if err != nil {
return nil, fmt.Errorf("failed to get addresses for interface %s: %w", iface.Name, err)
return nil, fmt.Errorf("getting addresses for interface %q: %w", iface.Name, err)
}
// Collect network interface addresses.
for _, addr := range addrs {
n, ok := addr.(*net.IPNet)
if !ok {
// Should be *net.IPNet, this is weird.
return nil, fmt.Errorf("expected %[2]s to be %[1]T, got %[2]T", n, addr)
} else if ip4 := n.IP.To4(); ip4 != nil {
n.IP = ip4
for i, addr := range addrs {
if err = populateAddrs(addr, niface); err != nil {
return nil, fmt.Errorf("populating at index %d: %w", i, err)
}
ip, ok := netip.AddrFromSlice(n.IP)
if !ok {
return nil, fmt.Errorf("bad address %s", n.IP)
}
ip = ip.Unmap()
if ip.IsLinkLocalUnicast() {
// Ignore link-local IPv4.
if ip.Is4() {
continue
}
ip = ip.WithZone(iface.Name)
}
ones, _ := n.Mask.Size()
p := netip.PrefixFrom(ip, ones)
niface.Addresses = append(niface.Addresses, ip)
niface.Subnets = append(niface.Subnets, p)
}
return niface, nil
}
// populateAddrs fills *NetInterface IP addresses and subnets. addr and niface
// must not be nil.
func populateAddrs(addr net.Addr, niface *NetInterface) (err error) {
n, err := ipNetFromAddr(addr)
if err != nil {
return err
}
ip, ok := netip.AddrFromSlice(n.IP)
if !ok {
return fmt.Errorf("bad address %s", n.IP)
}
ip = ip.Unmap()
// Skip link-local IPv4 addresses
if isLinkLocalV4(ip) {
return nil
}
if ip.IsLinkLocalUnicast() {
ip = ip.WithZone(niface.Name)
}
ones, _ := n.Mask.Size()
p := netip.PrefixFrom(ip, ones)
niface.Addresses = append(niface.Addresses, ip)
niface.Subnets = append(niface.Subnets, p)
return nil
}
// ipNetFromAddr converts net.Addr to *net.IPNet and its IP to v4 if necessary.
func ipNetFromAddr(addr net.Addr) (ip *net.IPNet, err error) {
ipNet, ok := addr.(*net.IPNet)
if !ok {
// Should be *net.IPNet, this is weird.
return nil, fmt.Errorf("bad type for interface net.Addr %T(%[1]v)", ipNet)
}
// TODO(f.setrakov): Explore whether this logic can be safely removed.
if ip4 := ipNet.IP.To4(); ip4 != nil {
ipNet.IP = ip4
}
return ipNet, nil
}
// isLinkLocalV4 checks if ip is link-local unicast IPv4 address.
func isLinkLocalV4(ip netip.Addr) (ok bool) {
return ip.Is4() && ip.IsLinkLocalUnicast()
}
// GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and
// WEB only we do not return link-local addresses here.
//

View File

@ -31,6 +31,9 @@ func macToKey(mac net.HardwareAddr) (key macKey) {
// index stores all information about persistent clients.
type index struct {
// subnetToUID maps subnet to UID.
subnetToUID *aghalg.SortedMap[netip.Prefix, UID]
// nameToUID maps client name to UID.
nameToUID map[string]UID
@ -45,18 +48,15 @@ type index struct {
// uidToClient maps UID to the persistent client.
uidToClient map[UID]*Persistent
// subnetToUID maps subnet to UID.
subnetToUID aghalg.SortedMap[netip.Prefix, UID]
}
// newIndex initializes the new instance of client index.
func newIndex() (ci *index) {
return &index{
subnetToUID: aghalg.NewSortedMapFunc[netip.Prefix, UID](subnetCompare),
nameToUID: map[string]UID{},
clientIDToUID: map[ClientID]UID{},
ipToUID: map[netip.Addr]UID{},
subnetToUID: aghalg.NewSortedMap[netip.Prefix, UID](subnetCompare),
macToUID: map[macKey]UID{},
uidToClient: map[UID]*Persistent{},
}

View File

@ -191,6 +191,8 @@ func (c *Persistent) SetIDs(ids []string) (err error) {
// subnetCompare is a comparison function for the two subnets. It returns -1 if
// x sorts before y, 1 if x sorts after y, and 0 if their relative sorting
// position is the same.
//
// TODO(s.chzhen): Use netip.Prefix.Compare in Go 1.26.
func subnetCompare(x, y netip.Prefix) (cmp int) {
if x == y {
return 0

View File

@ -32,7 +32,7 @@ func initBlockedServices(ctx context.Context, l *slog.Logger) {
for i, s := range blockedServices {
netRules := make([]*rules.NetworkRule, 0, len(s.Rules))
for _, text := range s.Rules {
rule, err := rules.NewNetworkRule(text, rulelist.URLFilterIDBlockedService)
rule, err := rules.NewNetworkRule(text, rulelist.IDBlockedService)
if err == nil {
netRules = append(netRules, rule)
@ -133,8 +133,10 @@ func (d *DNSFilter) handleBlockedServicesIDs(w http.ResponseWriter, r *http.Requ
func (d *DNSFilter) handleBlockedServicesAll(w http.ResponseWriter, r *http.Request) {
aghhttp.WriteJSONResponseOK(w, r, struct {
BlockedServices []blockedService `json:"blocked_services"`
ServiceGroups []serviceGroup `json:"groups"`
}{
BlockedServices: blockedServices,
ServiceGroups: serviceGroups,
})
}

View File

@ -29,10 +29,9 @@ func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) {
dr := nr.DNSRewrite
if dr.NewCNAME != "" {
// NewCNAME rules have a higher priority than other rules.
rules = []*ResultRule{{
FilterListID: nr.GetFilterListID(),
Text: nr.RuleText,
}}
rules = []*ResultRule{
NewResultRule(nr),
}
return Result{
Rules: rules,
@ -45,17 +44,13 @@ func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) {
case dns.RcodeSuccess:
dnsrr.RCode = dr.RCode
dnsrr.Response[dr.RRType] = append(dnsrr.Response[dr.RRType], dr.Value)
rules = append(rules, &ResultRule{
FilterListID: nr.GetFilterListID(),
Text: nr.RuleText,
})
rules = append(rules, NewResultRule(nr))
default:
// RcodeRefused and other such codes have higher priority. Return
// immediately.
rules = []*ResultRule{{
FilterListID: nr.GetFilterListID(),
Text: nr.RuleText,
}}
rules = []*ResultRule{
NewResultRule(nr),
}
dnsrr = &DNSRewriteResult{
RCode: dr.RCode,
}

View File

@ -52,7 +52,8 @@ func (filter *FilterYAML) Path(dataDir string) string {
return filepath.Join(
dataDir,
filterDir,
strconv.FormatInt(int64(filter.ID), 10)+".txt")
strconv.FormatUint(uint64(filter.ID), 10)+".txt",
)
}
// ensureName sets provided title or default name for the filter if it doesn't
@ -612,6 +613,7 @@ func (d *DNSFilter) load(ctx context.Context, flt *FilterYAML) (err error) {
d.logger.DebugContext(ctx, "loading filter", "id", flt.ID, "path", fileName)
// #nosec G304 -- Assume that fileName is always within DataDir.
file, err := os.Open(fileName)
if errors.Is(err, os.ErrNotExist) {
// Do nothing, file doesn't exist.
@ -655,7 +657,7 @@ func (d *DNSFilter) EnableFilters(async bool) {
func (d *DNSFilter) enableFiltersLocked(ctx context.Context, async bool) {
filters := make([]Filter, 1, len(d.conf.Filters)+len(d.conf.WhitelistFilters)+1)
filters[0] = Filter{
ID: rulelist.URLFilterIDCustom,
ID: rulelist.IDCustom,
Data: []byte(strings.Join(d.conf.UserRules, "\n")),
}

View File

@ -302,86 +302,10 @@ type Filter struct {
// Data is the content of the file.
Data []byte `yaml:"-"`
// ID is automatically assigned when filter is added using nextFilterID.
ID rulelist.URLFilterID `yaml:"id"`
// ID is automatically assigned when filter is added.
ID rules.ListID `yaml:"id"`
}
// Reason holds an enum detailing why it was filtered or not filtered
type Reason int
const (
// reasons for not filtering
// NotFilteredNotFound - host was not find in any checks, default value for result
NotFilteredNotFound Reason = iota
// NotFilteredAllowList - the host is explicitly allowed
NotFilteredAllowList
// NotFilteredError is returned when there was an error during
// checking. Reserved, currently unused.
NotFilteredError
// reasons for filtering
// FilteredBlockList - the host was matched to be advertising host
FilteredBlockList
// FilteredSafeBrowsing - the host was matched to be malicious/phishing
FilteredSafeBrowsing
// FilteredParental - the host was matched to be outside of parental control settings
FilteredParental
// FilteredInvalid - the request was invalid and was not processed
FilteredInvalid
// FilteredSafeSearch - the host was replaced with safesearch variant
FilteredSafeSearch
// FilteredBlockedService - the host is blocked by "blocked services" settings
FilteredBlockedService
// Rewritten is returned when there was a rewrite by a legacy DNS rewrite
// rule.
Rewritten
// RewrittenAutoHosts is returned when there was a rewrite by autohosts
// rules (/etc/hosts and so on).
RewrittenAutoHosts
// RewrittenRule is returned when a $dnsrewrite filter rule was applied.
//
// TODO(a.garipov): Remove Rewritten and RewrittenAutoHosts by merging their
// functionality into RewrittenRule.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/2499.
RewrittenRule
)
// TODO(a.garipov): Resync with actual code names or replace completely
// in HTTP API v1.
var reasonNames = []string{
NotFilteredNotFound: "NotFilteredNotFound",
NotFilteredAllowList: "NotFilteredWhiteList",
NotFilteredError: "NotFilteredError",
FilteredBlockList: "FilteredBlackList",
FilteredSafeBrowsing: "FilteredSafeBrowsing",
FilteredParental: "FilteredParental",
FilteredInvalid: "FilteredInvalid",
FilteredSafeSearch: "FilteredSafeSearch",
FilteredBlockedService: "FilteredBlockedService",
Rewritten: "Rewrite",
RewrittenAutoHosts: "RewriteEtcHosts",
RewrittenRule: "RewriteRule",
}
func (r Reason) String() string {
if r < 0 || int(r) >= len(reasonNames) {
return ""
}
return reasonNames[r]
}
// In returns true if reasons include r.
func (r Reason) In(reasons ...Reason) (ok bool) { return slices.Contains(reasons, r) }
// SetEnabled sets the status of the *DNSFilter.
func (d *DNSFilter) SetEnabled(enabled bool) {
atomic.StoreUint32(&d.conf.enabled, mathutil.BoolToNumber[uint32](enabled))
@ -556,54 +480,6 @@ func (d *DNSFilter) ParentalBlockHost() (host string) {
return d.conf.ParentalBlockHost
}
// ResultRule contains information about applied rules.
type ResultRule struct {
// Text is the text of the rule.
Text string `json:",omitempty"`
// IP is the host IP. It is nil unless the rule uses the
// /etc/hosts syntax or the reason is FilteredSafeSearch.
IP netip.Addr `json:",omitempty"`
// FilterListID is the ID of the rule's filter list.
FilterListID rulelist.URLFilterID `json:",omitempty"`
}
// Result contains the result of a request check.
//
// All fields transitively have omitempty tags so that the query log doesn't
// become too large.
//
// TODO(a.garipov): Clarify relationships between fields. Perhaps replace with
// a sum type or an interface?
type Result struct {
// DNSRewriteResult is the $dnsrewrite filter rule result.
DNSRewriteResult *DNSRewriteResult `json:",omitempty"`
// CanonName is the CNAME value from the lookup rewrite result. It is empty
// unless Reason is set to Rewritten or RewrittenRule.
CanonName string `json:",omitempty"`
// ServiceName is the name of the blocked service. It is empty unless
// Reason is set to FilteredBlockedService.
ServiceName string `json:",omitempty"`
// IPList is the lookup rewrite result. It is empty unless Reason is set to
// Rewritten.
IPList []netip.Addr `json:",omitempty"`
// Rules are applied rules. If Rules are not empty, each rule is not nil.
Rules []*ResultRule `json:",omitempty"`
// Reason is the reason for blocking or unblocking the request.
Reason Reason `json:",omitempty"`
// IsFiltered is true if the request is filtered.
//
// TODO(d.kolyshev): Get rid of this flag.
IsFiltered bool `json:",omitempty"`
}
// Matched returns true if any match at all was found regardless of
// whether it was filtered or not.
func (r Reason) Matched() bool {
@ -741,7 +617,9 @@ func (d *DNSFilter) matchBlockedServicesRules(
ruleText := rule.Text()
res.Rules = []*ResultRule{{
FilterListID: rule.GetFilterListID(),
// #nosec G115 -- The overflow is required for backwards
// compatibility.
FilterListID: rulelist.APIID(rule.GetFilterListID()),
Text: ruleText,
}}
@ -793,11 +671,9 @@ func newRuleStorage(filters []Filter) (rs *filterlist.RuleStorage, err error) {
// ruleListFromFilter returns a rule list from a Filter.
func ruleListFromFilter(f Filter) (rl filterlist.Interface, skip bool, err error) {
id := int(f.ID)
if len(f.Data) != 0 {
return filterlist.NewBytes(&filterlist.BytesConfig{
ID: id,
ID: f.ID,
RulesText: f.Data,
IgnoreCosmetic: true,
}), false, nil
@ -819,14 +695,14 @@ func ruleListFromFilter(f Filter) (rl filterlist.Interface, skip bool, err error
}
return filterlist.NewBytes(&filterlist.BytesConfig{
ID: id,
ID: f.ID,
RulesText: data,
IgnoreCosmetic: true,
}), false, nil
}
rl, err = filterlist.NewFile(&filterlist.FileConfig{
ID: id,
ID: f.ID,
Path: f.FilePath,
IgnoreCosmetic: true,
})
@ -1048,10 +924,7 @@ func (d *DNSFilter) matchHost(
func makeResult(matchedRules []rules.Rule, reason Reason) (res Result) {
resRules := make([]*ResultRule, len(matchedRules))
for i, mr := range matchedRules {
resRules[i] = &ResultRule{
FilterListID: mr.GetFilterListID(),
Text: mr.Text(),
}
resRules[i] = NewResultRule(mr)
}
return Result{
@ -1072,8 +945,9 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
ctx := context.TODO()
d = &DNSFilter{
logger: c.Logger,
idGen: newIDGenerator(int32(time.Now().Unix()), c.Logger),
logger: c.Logger,
// #nosec G115 -- The Unix epoch time is highly unlikely to be negative.
idGen: newIDGenerator(uint64(time.Now().Unix()), c.Logger),
bufPool: syncutil.NewSlicePool[byte](rulelist.DefaultRuleBufSize),
safeSearch: c.SafeSearch,
refreshLock: &sync.Mutex{},
@ -1253,7 +1127,7 @@ func (d *DNSFilter) checkSafeBrowsing(
res = Result{
Rules: []*ResultRule{{
Text: "adguard-malware-shavar",
FilterListID: rulelist.URLFilterIDSafeBrowsing,
FilterListID: rulelist.APIIDSafeBrowsing,
}},
Reason: FilteredSafeBrowsing,
IsFiltered: true,
@ -1289,7 +1163,7 @@ func (d *DNSFilter) checkParental(
res = Result{
Rules: []*ResultRule{{
Text: "parental CATEGORY_BLACKLISTED",
FilterListID: rulelist.URLFilterIDParentalControl,
FilterListID: rulelist.APIIDParentalControl,
}},
Reason: FilteredParental,
IsFiltered: true,

View File

@ -21,6 +21,8 @@ type cacheItem struct {
// toCacheItem decodes cacheItem from data. data must be at least equal to
// expiry size.
func toCacheItem(data []byte) *cacheItem {
// #nosec G115 -- Assume that the values are as the ones that have been
// encoded.
t := time.Unix(int64(binary.BigEndian.Uint64(data)), 0)
data = data[expirySize:]
@ -43,6 +45,7 @@ func fromCacheItem(item *cacheItem) (data []byte) {
data = make([]byte, 0, len(item.hashes)*hashSize+expirySize)
expiry := item.expiry.Unix()
// #nosec G115 -- The Unix epoch time is highly unlikely to be negative.
data = binary.BigEndian.AppendUint64(data, uint64(expiry))
for _, v := range item.hashes {

View File

@ -75,7 +75,7 @@ func (d *DNSFilter) hostsRewrites(
vals = append(vals, name)
rls = append(rls, &ResultRule{
Text: fmt.Sprintf("%s %s", addr, name),
FilterListID: rulelist.URLFilterIDEtcHosts,
FilterListID: rulelist.APIIDEtcHosts,
})
}
@ -97,7 +97,7 @@ func (d *DNSFilter) hostsRewrites(
}
rls = append(rls, &ResultRule{
Text: fmt.Sprintf("%s %s", addr, host),
FilterListID: rulelist.URLFilterIDEtcHosts,
FilterListID: rulelist.APIIDEtcHosts,
})
}

View File

@ -76,7 +76,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
dtyp: dns.TypeA,
wantRules: []*filtering.ResultRule{{
Text: "1.2.3.4 v4.host.example",
FilterListID: rulelist.URLFilterIDEtcHosts,
FilterListID: rulelist.APIIDEtcHosts,
}},
wantResps: []rules.RRValue{addrv4},
}, {
@ -85,7 +85,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
dtyp: dns.TypeAAAA,
wantRules: []*filtering.ResultRule{{
Text: "::1 v6.host.example",
FilterListID: rulelist.URLFilterIDEtcHosts,
FilterListID: rulelist.APIIDEtcHosts,
}},
wantResps: []rules.RRValue{addrv6},
}, {
@ -94,7 +94,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
dtyp: dns.TypeAAAA,
wantRules: []*filtering.ResultRule{{
Text: "::ffff:1.2.3.4 mapped.host.example",
FilterListID: rulelist.URLFilterIDEtcHosts,
FilterListID: rulelist.APIIDEtcHosts,
}},
wantResps: []rules.RRValue{addrMapped},
}, {
@ -103,7 +103,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
dtyp: dns.TypePTR,
wantRules: []*filtering.ResultRule{{
Text: "1.2.3.4 v4.host.example",
FilterListID: rulelist.URLFilterIDEtcHosts,
FilterListID: rulelist.APIIDEtcHosts,
}},
wantResps: []rules.RRValue{"v4.host.example"},
}, {
@ -112,7 +112,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
dtyp: dns.TypePTR,
wantRules: []*filtering.ResultRule{{
Text: "::ffff:1.2.3.4 mapped.host.example",
FilterListID: rulelist.URLFilterIDEtcHosts,
FilterListID: rulelist.APIIDEtcHosts,
}},
wantResps: []rules.RRValue{"mapped.host.example"},
}, {
@ -139,7 +139,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
dtyp: dns.TypeAAAA,
wantRules: []*filtering.ResultRule{{
Text: fmt.Sprintf("%s v4.host.example", addrv4),
FilterListID: rulelist.URLFilterIDEtcHosts,
FilterListID: rulelist.APIIDEtcHosts,
}},
wantResps: nil,
}, {
@ -148,7 +148,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
dtyp: dns.TypeA,
wantRules: []*filtering.ResultRule{{
Text: fmt.Sprintf("%s v6.host.example", addrv6),
FilterListID: rulelist.URLFilterIDEtcHosts,
FilterListID: rulelist.APIIDEtcHosts,
}},
wantResps: nil,
}, {
@ -169,7 +169,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
dtyp: dns.TypeA,
wantRules: []*filtering.ResultRule{{
Text: "4.3.2.1 v4.host.with-dup",
FilterListID: rulelist.URLFilterIDEtcHosts,
FilterListID: rulelist.APIIDEtcHosts,
}},
wantResps: []rules.RRValue{addrv4Dup},
}}

View File

@ -401,12 +401,14 @@ func (d *DNSFilter) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques
}
type filterJSON struct {
URL string `json:"url"`
Name string `json:"name"`
LastUpdated string `json:"last_updated,omitempty"`
ID rulelist.URLFilterID `json:"id"`
RulesCount uint32 `json:"rules_count"`
Enabled bool `json:"enabled"`
URL string `json:"url"`
Name string `json:"name"`
LastUpdated string `json:"last_updated,omitempty"`
ID rulelist.APIID `json:"id"`
RulesCount uint64 `json:"rules_count"`
Enabled bool `json:"enabled"`
}
type filteringConfig struct {
@ -419,11 +421,13 @@ type filteringConfig struct {
func filterToJSON(f FilterYAML) filterJSON {
fj := filterJSON{
ID: f.ID,
Enabled: f.Enabled,
URL: f.URL,
Name: f.Name,
RulesCount: uint32(f.RulesCount),
// #nosec G115 -- The overflow is required for backwards compatibility.
ID: rulelist.APIID(f.ID),
Enabled: f.Enabled,
URL: f.URL,
Name: f.Name,
// #nosec G115 -- The number of rules must not be negative.
RulesCount: uint64(f.RulesCount),
}
if !f.LastUpdated.IsZero() {
@ -485,8 +489,9 @@ func (d *DNSFilter) handleFilteringConfig(w http.ResponseWriter, r *http.Request
}
type checkHostRespRule struct {
Text string `json:"text"`
FilterListID rulelist.URLFilterID `json:"filter_list_id"`
Text string `json:"text"`
FilterListID rulelist.APIID `json:"filter_list_id"`
}
type checkHostResp struct {
@ -509,7 +514,7 @@ type checkHostResp struct {
// FilterID is the ID of the rule's filter list.
//
// Deprecated: Use Rules[*].FilterListID.
FilterID rulelist.URLFilterID `json:"filter_id"`
FilterID rulelist.APIID `json:"filter_id"`
}
// handleCheckHost is the handler for the GET /control/filtering/check_host HTTP

View File

@ -6,8 +6,8 @@ import (
"log/slog"
"sync/atomic"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/urlfilter/rules"
)
// idGenerator generates filtering-list IDs in a way broadly compatible with the
@ -16,15 +16,15 @@ import (
// TODO(a.garipov): Get rid of this once we switch completely to the new
// rule-list architecture.
type idGenerator struct {
current *atomic.Int32
current *atomic.Uint64
logger *slog.Logger
}
// newIDGenerator returns a new ID generator initialized with the given seed
// value.
func newIDGenerator(seed int32, l *slog.Logger) (g *idGenerator) {
// value. l must not be nil.
func newIDGenerator(seed uint64, l *slog.Logger) (g *idGenerator) {
g = &idGenerator{
current: &atomic.Int32{},
current: &atomic.Uint64{},
logger: l,
}
@ -34,18 +34,18 @@ func newIDGenerator(seed int32, l *slog.Logger) (g *idGenerator) {
}
// next returns the next ID from the generator. It is safe for concurrent use.
func (g *idGenerator) next() (id rulelist.URLFilterID) {
id32 := g.current.Add(1)
if id32 < 0 {
panic(fmt.Errorf("invalid current id value %d", id32))
func (g *idGenerator) next() (id rules.ListID) {
id64 := g.current.Add(1)
if id64 == 0 {
panic(fmt.Errorf("invalid current id value %d", id64))
}
return rulelist.URLFilterID(id32)
return rules.ListID(id64)
}
// fix ensures that flts all have unique IDs.
func (g *idGenerator) fix(flts []FilterYAML) {
set := container.NewMapSet[rulelist.URLFilterID]()
set := container.NewMapSet[rules.ListID]()
for i, f := range flts {
id := f.ID
if id == 0 {

View File

@ -4,8 +4,8 @@ import (
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/stretchr/testify/assert"
)
@ -78,7 +78,7 @@ func TestIDGenerator_Fix(t *testing.T) {
func assertUniqueIDs(tb testing.TB, flts []FilterYAML) {
tb.Helper()
uc := aghalg.UniqChecker[rulelist.URLFilterID]{}
uc := aghalg.UniqChecker[rules.ListID]{}
for _, f := range flts {
uc.Add(f.ID)
}

View File

@ -0,0 +1,152 @@
package filtering
import (
"fmt"
"net/netip"
"slices"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/urlfilter/rules"
)
// Result contains the result of a request check. All fields transitively have
// omitempty tags so that the query log doesn't become too large.
//
// TODO(a.garipov): Clarify relationships between fields. Perhaps replace with
// a sum type or an interface?
type Result struct {
// DNSRewriteResult is the $dnsrewrite filter rule result.
DNSRewriteResult *DNSRewriteResult `json:",omitempty"`
// CanonName is the CNAME value from the lookup rewrite result. It is empty
// unless Reason is set to Rewritten or RewrittenRule.
CanonName string `json:",omitempty"`
// ServiceName is the name of the blocked service. It is empty unless
// Reason is set to FilteredBlockedService.
ServiceName string `json:",omitempty"`
// IPList is the lookup rewrite result. It is empty unless Reason is set to
// Rewritten.
IPList []netip.Addr `json:",omitempty"`
// Rules are applied rules. If Rules are not empty, each rule is not nil.
Rules []*ResultRule `json:",omitempty"`
// Reason is the reason for blocking or unblocking the request.
Reason Reason `json:",omitempty"`
// IsFiltered is true if the request is filtered.
//
// TODO(d.kolyshev): Get rid of this flag.
IsFiltered bool `json:",omitempty"`
}
// ResultRule contains information about applied rules.
type ResultRule struct {
// Text is the text of the rule.
Text string `json:",omitempty"`
// IP is the host IP. It is nil unless the rule uses the /etc/hosts syntax
// or the reason is [FilteredSafeSearch].
IP netip.Addr `json:",omitzero"`
// FilterListID is the ID of the rule's filter list.
FilterListID rulelist.APIID `json:",omitempty"`
}
// NewResultRule converts an URLFilter rule into a *ResultRule. nr must not be
// nil.
func NewResultRule(r rules.Rule) (rr *ResultRule) {
return &ResultRule{
// #nosec G115 -- The overflow is required for backwards
// compatibility.
FilterListID: rulelist.APIID(r.GetFilterListID()),
Text: r.Text(),
}
}
// Reason holds an enum detailing why it was filtered or not filtered
type Reason int
const (
// NotFilteredNotFound: the host was not find in any checks, default value
// for results.
NotFilteredNotFound Reason = iota
// NotFilteredAllowList: the host is explicitly allowed.
NotFilteredAllowList
// NotFilteredError is returned when there was an error during checking.
// Reserved, currently unused.
NotFilteredError
// FilteredBlockList: the host was matched to be advertising host.
FilteredBlockList
// FilteredSafeBrowsing: the host was matched to be malicious/phishing.
FilteredSafeBrowsing
// FilteredParental: the host was matched to be outside of parental control
// settings.
FilteredParental
// FilteredInvalid: the request was invalid and was not processed.
FilteredInvalid
// FilteredSafeSearch: the host was replaced with safesearch variant.
FilteredSafeSearch
// FilteredBlockedService: the host is blocked by the blocked services
// feature.
FilteredBlockedService
// Rewritten is returned when there was a rewrite by a legacy DNS rewrite
// rule.
Rewritten
// RewrittenAutoHosts is returned when there was a rewrite by /etc/hosts.
RewrittenAutoHosts
// RewrittenRule is returned when a $dnsrewrite filter rule was applied.
//
// TODO(a.garipov): Remove [Rewritten] and [RewrittenAutoHosts] by merging
// their functionality into RewrittenRule.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/2499.
RewrittenRule
)
// TODO(a.garipov): Resync with actual code names or replace completely in HTTP
// API v1.
var reasonNames = []string{
NotFilteredNotFound: "NotFilteredNotFound",
NotFilteredAllowList: "NotFilteredWhiteList",
NotFilteredError: "NotFilteredError",
FilteredBlockList: "FilteredBlackList",
FilteredSafeBrowsing: "FilteredSafeBrowsing",
FilteredParental: "FilteredParental",
FilteredInvalid: "FilteredInvalid",
FilteredSafeSearch: "FilteredSafeSearch",
FilteredBlockedService: "FilteredBlockedService",
Rewritten: "Rewrite",
RewrittenAutoHosts: "RewriteEtcHosts",
RewrittenRule: "RewriteRule",
}
// type check
var _ fmt.Stringer = NotFilteredNotFound
// String implements the [fmt.Stringer] interface for Reason.
func (r Reason) String() (s string) {
if r < 0 || int(r) >= len(reasonNames) {
return ""
}
return reasonNames[r]
}
// In returns true if reasons include r.
func (r Reason) In(reasons ...Reason) (ok bool) { return slices.Contains(reasons, r) }

View File

@ -40,7 +40,7 @@ type Config struct {
Rewrites []*Item
// ListID is used as an identifier of the underlying rules list.
ListID int
ListID rules.ListID
}
// DefaultStorage is the default storage for rewrite rules.
@ -61,10 +61,7 @@ type DefaultStorage struct {
rewrites []*Item
// urlFilterID is the synthetic integer identifier for the urlfilter engine.
//
// TODO(a.garipov): Change the type to a string in module urlfilter and
// remove this crutch.
urlFilterID int
urlFilterID rules.ListID
}
// NewDefaultStorage returns new rewrites storage. conf must not be nil.
@ -140,7 +137,7 @@ func (s *DefaultStorage) resolveCNAMEChain(
return nil, nil
}
if isSelfMatchingWildcard(host, rwAns, rule.RuleText) {
if isSelfMatchingWildcard(host, rwAns, rule.Text()) {
return nil, rule.DNSRewrite
}

View File

@ -13,6 +13,9 @@ import (
"github.com/stretchr/testify/require"
)
// testListID is the common rule-list ID for tests.
const testListID rules.ListID = 1
func TestNewDefaultStorage(t *testing.T) {
items := []*Item{{
Domain: "example.com",
@ -22,7 +25,7 @@ func TestNewDefaultStorage(t *testing.T) {
s, err := NewDefaultStorage(&Config{
Logger: slogutil.NewDiscardLogger(),
Rewrites: items,
ListID: -1,
ListID: testListID,
})
require.NoError(t, err)
@ -35,7 +38,7 @@ func TestDefaultStorage_CRUD(t *testing.T) {
s, err := NewDefaultStorage(&Config{
Logger: slogutil.NewDiscardLogger(),
Rewrites: items,
ListID: -1,
ListID: testListID,
})
require.NoError(t, err)
require.Len(t, s.List(), 0)
@ -124,7 +127,7 @@ func TestDefaultStorage_MatchRequest(t *testing.T) {
s, err := NewDefaultStorage(&Config{
Logger: slogutil.NewDiscardLogger(),
Rewrites: items,
ListID: -1,
ListID: testListID,
})
require.NoError(t, err)
@ -300,7 +303,7 @@ func TestDefaultStorage_MatchRequest_Levels(t *testing.T) {
s, err := NewDefaultStorage(&Config{
Logger: slogutil.NewDiscardLogger(),
Rewrites: items,
ListID: -1,
ListID: testListID,
})
require.NoError(t, err)
@ -372,7 +375,7 @@ func TestDefaultStorage_MatchRequest_ExceptionCNAME(t *testing.T) {
s, err := NewDefaultStorage(&Config{
Logger: slogutil.NewDiscardLogger(),
Rewrites: items,
ListID: -1,
ListID: testListID,
})
require.NoError(t, err)
@ -440,7 +443,7 @@ func TestDefaultStorage_MatchRequest_ExceptionIP(t *testing.T) {
s, err := NewDefaultStorage(&Config{
Logger: slogutil.NewDiscardLogger(),
Rewrites: items,
ListID: -1,
ListID: testListID,
})
require.NoError(t, err)

View File

@ -16,6 +16,7 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/ioutil"
"github.com/AdguardTeam/urlfilter/filterlist"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/c2h5oh/datasize"
)
@ -42,7 +43,7 @@ type Filter struct {
uid UID
// urlFilterID is used for working with package urlfilter.
urlFilterID URLFilterID
urlFilterID rules.ListID
// rulesCount contains the number of rules in this rule-list filter.
rulesCount int
@ -72,7 +73,7 @@ type FilterConfig struct {
UID UID
// URLFilterID is used for working with package urlfilter.
URLFilterID URLFilterID
URLFilterID rules.ListID
// Enabled, if true, means that this rule-list filter is used for filtering.
Enabled bool
@ -154,16 +155,15 @@ func (f *Filter) setFromHTTP(
) (parseRes *ParseResult, err error) {
defer func() { err = errors.Annotate(err, "setting from http: %w") }()
text, parseRes, err := f.readFromHTTP(ctx, parseBuf, cli, cachePath, maxSize)
data, parseRes, err := f.readFromHTTP(ctx, parseBuf, cli, cachePath, maxSize)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return nil, err
}
// TODO(a.garipov): Add filterlist.BytesRuleList.
f.ruleList = filterlist.NewString(&filterlist.StringConfig{
f.ruleList = filterlist.NewBytes(&filterlist.BytesConfig{
ID: f.urlFilterID,
RulesText: text,
RulesText: data,
IgnoreCosmetic: true,
})
@ -179,27 +179,27 @@ func (f *Filter) readFromHTTP(
cli *http.Client,
cachePath string,
maxSize uint64,
) (text string, parseRes *ParseResult, err error) {
) (data []byte, parseRes *ParseResult, err error) {
urlStr := f.url.String()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
if err != nil {
return "", nil, fmt.Errorf("making request for http url %q: %w", urlStr, err)
return nil, nil, fmt.Errorf("making request for http url %q: %w", urlStr, err)
}
resp, err := cli.Do(req)
if err != nil {
return "", nil, fmt.Errorf("requesting from http url: %w", err)
return nil, nil, fmt.Errorf("requesting from http url: %w", err)
}
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
// TODO(a.garipov): Use [agdhttp.CheckStatus] when it's moved to golibs.
if resp.StatusCode != http.StatusOK {
return "", nil, fmt.Errorf("got status code %d, want %d", resp.StatusCode, http.StatusOK)
return nil, nil, fmt.Errorf("got status code %d, want %d", resp.StatusCode, http.StatusOK)
}
fltFile, err := aghrenameio.NewPendingFile(cachePath, aghos.DefaultPermFile)
if err != nil {
return "", nil, fmt.Errorf("creating temp file: %w", err)
return nil, nil, fmt.Errorf("creating temp file: %w", err)
}
defer func() { err = aghrenameio.WithDeferredCleanup(err, fltFile) }()
@ -210,10 +210,10 @@ func (f *Filter) readFromHTTP(
httpBody := ioutil.LimitReader(resp.Body, maxSize)
parseRes, err = parser.Parse(mw, httpBody, parseBuf)
if err != nil {
return "", nil, fmt.Errorf("parsing response from http url %q: %w", urlStr, err)
return nil, nil, fmt.Errorf("parsing response from http url %q: %w", urlStr, err)
}
return buf.String(), parseRes, nil
return buf.Bytes(), parseRes, nil
}
// setName sets the title using either the already-present name, the given title

View File

@ -6,7 +6,9 @@ package rulelist
import (
"fmt"
"math"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/c2h5oh/datasize"
"github.com/google/uuid"
)
@ -21,26 +23,33 @@ const DefaultRuleBufSize = 1024
// DefaultMaxRuleListSize is the default maximum filtering-rule list size.
const DefaultMaxRuleListSize = 64 * datasize.MB
// URLFilterID is a semantic type-alias for IDs used for working with package
// urlfilter.
type URLFilterID = int
// APIID is the type for the rule-list IDs used in the HTTP API.
type APIID int64
// The IDs of built-in filter lists.
// The IDs of built-in filter lists for the HTTP API.
//
// NOTE: Do not change without the need for it and keep in sync with
// NOTE: Do not change without the need for it and keep in sync with
// client/src/helpers/constants.ts.
const (
APIIDCustom APIID = 0
APIIDEtcHosts APIID = -1
APIIDBlockedService APIID = -2
APIIDParentalControl APIID = -3
APIIDSafeBrowsing APIID = -4
APIIDSafeSearch APIID = -5
)
// The IDs of built-in filter lists. The IDs for the blocked-service and the
// safe-search filters are chosen so that they equal to their [APIID]
// counterparts when converted to it.
//
// TODO(a.garipov): Add type [URLFilterID] once it is used consistently in
// package filtering.
// NOTE: Keep in sync with [APIIDCustom] etc.
//
// TODO(d.kolyshev): Add URLFilterIDLegacyRewrite here and to the UI.
const (
URLFilterIDCustom URLFilterID = 0
URLFilterIDEtcHosts URLFilterID = -1
URLFilterIDBlockedService URLFilterID = -2
URLFilterIDParentalControl URLFilterID = -3
URLFilterIDSafeBrowsing URLFilterID = -4
URLFilterIDSafeSearch URLFilterID = -5
IDCustom rules.ListID = rules.ListID(APIIDCustom)
IDBlockedService rules.ListID = math.MaxUint64 - rules.ListID(-APIIDBlockedService) + 1
IDSafeSearch rules.ListID = math.MaxUint64 - rules.ListID(-APIIDSafeSearch) + 1
)
// UID is the type for the unique IDs of filtering-rule lists.

View File

@ -13,14 +13,16 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// testTimeout is the common timeout for tests.
const testTimeout = 1 * time.Second
// testURLFilterID is the common [rulelist.URLFilterID] for tests.
const testURLFilterID rulelist.URLFilterID = 1
// testURLFilterID is the common rules.ListID for tests.
const testURLFilterID rules.ListID = 1
// testTitle is the common title for tests.
const testTitle = "Test Title"
@ -42,11 +44,11 @@ const (
)
// urlFilterIDCounter is the atomic integer used to create unique filter IDs.
var urlFilterIDCounter = &atomic.Int32{}
var urlFilterIDCounter = &atomic.Uint64{}
// newURLFilterID returns a new unique URLFilterID.
func newURLFilterID() (id rulelist.URLFilterID) {
return rulelist.URLFilterID(urlFilterIDCounter.Add(1))
func newURLFilterID() (id rules.ListID) {
return rules.ListID(urlFilterIDCounter.Add(1))
}
// newFilter is a helper for creating new filters in tests. It does not
@ -115,3 +117,15 @@ func newStringHTTPServer(s string) (srv *httptest.Server) {
require.NoError(pt, err)
}))
}
func TestIDs(t *testing.T) {
// Use a variable to prevent compilation errors.
id := rulelist.IDCustom
assert.Equal(t, rulelist.APIIDCustom, rulelist.APIID(id))
id = rulelist.IDBlockedService
assert.Equal(t, rulelist.APIIDBlockedService, rulelist.APIID(id))
id = rulelist.IDSafeSearch
assert.Equal(t, rulelist.APIIDSafeSearch, rulelist.APIID(id))
}

View File

@ -40,11 +40,13 @@ type StorageConfig struct {
CacheDir string
// AllowFilters are the filtering-rule lists used to exclude domain names
// from the filtering. Each item must not be nil.
// from the filtering. Each item must not be nil and must have unique IDs
// between AllowFilters and BlockFilters.
AllowFilters []*Filter
// BlockFilters are the filtering-rule lists used to block domain names.
// Each item must not be nil.
// Each item must not be nil and must have unique IDs between AllowFilters
// and BlockFilters.
BlockFilters []*Filter
// CustomRules contains custom rules of the user. They have priority over
@ -62,7 +64,7 @@ func NewStorage(c *StorageConfig) (s *Storage, err error) {
custom, err := NewTextEngine(&TextEngineConfig{
Name: EngineNameCustom,
Rules: c.CustomRules,
ID: URLFilterIDCustom,
ID: IDCustom,
})
if err != nil {
return nil, fmt.Errorf("creating custom engine: %w", err)

View File

@ -7,6 +7,7 @@ import (
"github.com/AdguardTeam/urlfilter"
"github.com/AdguardTeam/urlfilter/filterlist"
"github.com/AdguardTeam/urlfilter/rules"
)
// TextEngine is a single DNS filter based on a list of rules in text form.
@ -35,7 +36,7 @@ type TextEngineConfig struct {
Rules []string
// ID is the ID to use inside a URL-filter engine.
ID URLFilterID
ID rules.ListID
}
// NewTextEngine returns a new rule-list filtering engine that uses rules

View File

@ -38,5 +38,5 @@ func TestNewTextEngine(t *testing.T) {
require.NotNil(t, fltRes)
require.NotNil(t, fltRes.NetworkRule)
assert.Equal(t, fltRes.NetworkRule.FilterListID, testURLFilterID)
assert.Equal(t, fltRes.NetworkRule.GetFilterListID(), testURLFilterID)
}

View File

@ -120,7 +120,7 @@ func NewDefault(ctx context.Context, conf *DefaultConfig) (ss *Default, err erro
}
// TODO(s.chzhen): Move to [Default.InitialRefresh].
err = ss.resetEngine(ctx, rulelist.URLFilterIDSafeSearch, conf.ServicesConfig)
err = ss.resetEngine(ctx, rulelist.IDSafeSearch, conf.ServicesConfig)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return nil, err
@ -133,7 +133,7 @@ func NewDefault(ctx context.Context, conf *DefaultConfig) (ss *Default, err erro
// sets it in ss.
func (ss *Default) resetEngine(
ctx context.Context,
listID int,
id rules.ListID,
conf filtering.SafeSearchConfig,
) (err error) {
if !conf.Enabled {
@ -151,7 +151,7 @@ func (ss *Default) resetEngine(
strList := []filterlist.Interface{
filterlist.NewString(&filterlist.StringConfig{
ID: listID,
ID: id,
RulesText: sb.String(),
IgnoreCosmetic: true,
}),
@ -260,7 +260,7 @@ func (ss *Default) newResult(
}
res.Rules = []*filtering.ResultRule{{
FilterListID: rulelist.URLFilterIDSafeSearch,
FilterListID: rulelist.APIIDSafeSearch,
IP: ip,
}}
@ -274,12 +274,16 @@ func (ss *Default) newResult(
// setCacheResult stores data in cache for host. qtype is expected to be either
// [dns.TypeA] or [dns.TypeAAAA].
//
// TODO(a.garipov): Remove gob and use uint64.
func (ss *Default) setCacheResult(
ctx context.Context,
host string,
qtype rules.RRType,
res filtering.Result,
) {
// #nosec G115 -- The Unix epoch time is highly unlikely to be negative, and
// also see the TODO.
expire := uint32(time.Now().Add(ss.cacheTTL).Unix())
exp := make([]byte, 4)
binary.BigEndian.PutUint32(exp, expire)
@ -305,6 +309,8 @@ func (ss *Default) setCacheResult(
// getCachedResult returns stored data from cache for host. qtype is expected
// to be either [dns.TypeA] or [dns.TypeAAAA].
//
// TODO(a.garipov): Remove gob and use uint64.
func (ss *Default) getCachedResult(
ctx context.Context,
host string,
@ -318,6 +324,8 @@ func (ss *Default) getCachedResult(
}
exp := binary.BigEndian.Uint32(data[:4])
// #nosec G115 -- The Unix epoch time is highly unlikely to be negative, and
// also see the TODO.
if exp <= uint32(time.Now().Unix()) {
ss.cache.Del([]byte(host))
@ -342,7 +350,7 @@ func (ss *Default) Update(ctx context.Context, conf filtering.SafeSearchConfig)
ss.mu.Lock()
defer ss.mu.Unlock()
err = ss.resetEngine(ctx, rulelist.URLFilterIDSafeSearch, conf)
err = ss.resetEngine(ctx, rulelist.IDSafeSearch, conf)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err

View File

@ -101,7 +101,7 @@ func TestDefault_CheckHost_yandex(t *testing.T) {
rule := res.Rules[0]
assert.Equal(t, tc.want, rule.IP)
assert.Equal(t, rulelist.URLFilterIDSafeSearch, rule.FilterListID)
assert.Equal(t, rulelist.APIIDSafeSearch, rule.FilterListID)
}
}
})

View File

@ -8,6 +8,12 @@ type blockedService struct {
Name string `json:"name"`
IconSVG []byte `json:"icon_svg"`
Rules []string `json:"rules"`
GroupID string `json:"group_id"`
}
// serviceGroup represents single group of services.
type serviceGroup struct {
ID string `json:"id"`
}
// blockedServices contains raw blocked service data.
@ -20,6 +26,7 @@ var blockedServices = []blockedService{{
"||4chan.org^",
"||4channel.org^",
},
GroupID: "social_network",
}, {
ID: "500px",
Name: "500px",
@ -28,6 +35,7 @@ var blockedServices = []blockedService{{
"||500px.com^",
"||500px.org^",
},
GroupID: "social_network",
}, {
ID: "9gag",
Name: "9GAG",
@ -36,6 +44,7 @@ var blockedServices = []blockedService{{
"||9cache.com^",
"||9gag.com^",
},
GroupID: "social_network",
}, {
ID: "activision_blizzard",
Name: "Activision Blizzard",
@ -48,6 +57,7 @@ var blockedServices = []blockedService{{
"||codmwest.com^",
"||demonware.net^",
},
GroupID: "gaming",
}, {
ID: "aliexpress",
Name: "AliExpress",
@ -58,6 +68,7 @@ var blockedServices = []blockedService{{
"||aliexpress.com^",
"||aliexpress.ru^",
},
GroupID: "shopping",
}, {
ID: "amazon",
Name: "Amazon",
@ -265,6 +276,7 @@ var blockedServices = []blockedService{{
"||z.cn^",
"||zappos^",
},
GroupID: "shopping",
}, {
ID: "amazon_streaming",
Name: "Amazon Streaming",
@ -300,6 +312,7 @@ var blockedServices = []blockedService{{
"||primevideo.tv^",
"||video.a2z.com^",
},
GroupID: "streaming",
}, {
ID: "amino",
Name: "Amino",
@ -307,6 +320,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||aminoapps.com^",
},
GroupID: "social_network",
}, {
ID: "apple_streaming",
Name: "Apple Streaming",
@ -331,6 +345,7 @@ var blockedServices = []blockedService{{
"||tv.g.apple.com^",
"||tv.v.aaplimg.com^",
},
GroupID: "streaming",
}, {
ID: "battle_net",
Name: "Battle.net",
@ -341,6 +356,7 @@ var blockedServices = []blockedService{{
"||bnet.163.com^",
"||bnet.cn^",
},
GroupID: "gaming",
}, {
ID: "betano",
Name: "Betano",
@ -354,6 +370,7 @@ var blockedServices = []blockedService{{
"||betano.ng^",
"||betano.pt^",
},
GroupID: "gambling",
}, {
ID: "betfair",
Name: "Betfair",
@ -366,6 +383,7 @@ var blockedServices = []blockedService{{
"||betfair.ro^",
"||betfair.se^",
},
GroupID: "gambling",
}, {
ID: "betway",
Name: "Betway",
@ -388,6 +406,7 @@ var blockedServices = []blockedService{{
"||betwaysatta.com^",
"||vietnambetway88.com^",
},
GroupID: "gambling",
}, {
ID: "bigo_live",
Name: "Bigo Live",
@ -398,6 +417,7 @@ var blockedServices = []blockedService{{
"||bigolive.tv^",
"||bigovideo.tv^",
},
GroupID: "streaming",
}, {
ID: "bilibili",
Name: "Bilibili",
@ -450,6 +470,7 @@ var blockedServices = []blockedService{{
"||mincdn.com^",
"||yo9.com^",
},
GroupID: "streaming",
}, {
ID: "blaze",
Name: "Blaze",
@ -460,6 +481,7 @@ var blockedServices = []blockedService{{
"||blaze.com^",
"||blazecareers.com^",
},
GroupID: "gambling",
}, {
ID: "blizzard_entertainment",
Name: "Blizzard Entertainment",
@ -475,6 +497,7 @@ var blockedServices = []blockedService{{
"||bnet.163.com^",
"||bnet.cn^",
},
GroupID: "gaming",
}, {
ID: "bluesky",
Name: "Bluesky",
@ -483,6 +506,7 @@ var blockedServices = []blockedService{{
"||bsky.app^",
"||bsky.social^",
},
GroupID: "social_network",
}, {
ID: "box",
Name: "Box",
@ -493,6 +517,7 @@ var blockedServices = []blockedService{{
"||boxcdn.net^",
"||boxcloud.com^",
},
GroupID: "hosting",
}, {
ID: "canais_globo",
Name: "Canais Globo",
@ -502,6 +527,7 @@ var blockedServices = []blockedService{{
"||globosat.globo.com^",
"||gsatmulti.globo.com^",
},
GroupID: "streaming",
}, {
ID: "chatgpt",
Name: "ChatGPT",
@ -512,6 +538,7 @@ var blockedServices = []blockedService{{
"||oaiusercontent.com^",
"||openai.com^",
},
GroupID: "ai",
}, {
ID: "claro",
Name: "Claro",
@ -540,6 +567,7 @@ var blockedServices = []blockedService{{
"||clarovideo.com^",
"||usclaro.com^",
},
GroupID: "streaming",
}, {
ID: "claude",
Name: "Claude",
@ -548,6 +576,7 @@ var blockedServices = []blockedService{{
"||anthropic.com^",
"||claude.ai^",
},
GroupID: "ai",
}, {
ID: "cloudflare",
Name: "Cloudflare",
@ -584,6 +613,7 @@ var blockedServices = []blockedService{{
"||warp.plus^",
"||workers.dev^",
},
GroupID: "cdn",
}, {
ID: "clubhouse",
Name: "Clubhouse",
@ -592,6 +622,7 @@ var blockedServices = []blockedService{{
"||clubhouse.com^",
"||clubhouseapi.com^",
},
GroupID: "social_network",
}, {
ID: "coolapk",
Name: "CoolApk",
@ -601,6 +632,7 @@ var blockedServices = []blockedService{{
"||coolapkmarket.com^",
"||coolapkmarket.net^",
},
GroupID: "shopping",
}, {
ID: "crunchyroll",
Name: "Crunchyroll",
@ -609,6 +641,7 @@ var blockedServices = []blockedService{{
"||crunchyroll.com^",
"||gccrunchyroll.com^",
},
GroupID: "streaming",
}, {
ID: "dailymotion",
Name: "Dailymotion",
@ -618,6 +651,7 @@ var blockedServices = []blockedService{{
"||dm-event.net^",
"||dmcdn.net^",
},
GroupID: "streaming",
}, {
ID: "deepseek",
Name: "DeepSeek",
@ -625,6 +659,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||deepseek.com^",
},
GroupID: "ai",
}, {
ID: "deezer",
Name: "Deezer",
@ -633,6 +668,7 @@ var blockedServices = []blockedService{{
"||deezer.com^",
"||dzcdn.net^",
},
GroupID: "streaming",
}, {
ID: "directvgo",
Name: "DirecTV Go",
@ -640,6 +676,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||directvgo.com^",
},
GroupID: "streaming",
}, {
ID: "discord",
Name: "Discord",
@ -673,6 +710,7 @@ var blockedServices = []blockedService{{
"||discordstatus.com^",
"||watchanimeattheoffice.com^",
},
GroupID: "social_network",
}, {
ID: "discoveryplus",
Name: "Discovery+",
@ -681,6 +719,7 @@ var blockedServices = []blockedService{{
"||disco-api.com^",
"||discoveryplus.com^",
},
GroupID: "streaming",
}, {
ID: "disneyplus",
Name: "Disney+",
@ -695,6 +734,7 @@ var blockedServices = []blockedService{{
"||star.playback.edge.bamgrid.com^",
"||starplus.com^",
},
GroupID: "streaming",
}, {
ID: "douban",
Name: "Douban",
@ -704,6 +744,7 @@ var blockedServices = []blockedService{{
"||douban.fm^",
"||doubanio.com^",
},
GroupID: "social_network",
}, {
ID: "dropbox",
Name: "Dropbox",
@ -732,6 +773,7 @@ var blockedServices = []blockedService{{
"||dropboxusercontent.com^",
"||getdropbox.com^",
},
GroupID: "hosting",
}, {
ID: "ebay",
Name: "eBay",
@ -1058,6 +1100,7 @@ var blockedServices = []blockedService{{
"||xxbay.com^",
"||yibei.org^",
},
GroupID: "shopping",
}, {
ID: "electronic_arts",
Name: "Electronic Arts",
@ -1070,6 +1113,7 @@ var blockedServices = []blockedService{{
"||swtor.com^",
"||tnt-ea.com^",
},
GroupID: "gaming",
}, {
ID: "epic_games",
Name: "Epic Games",
@ -1083,6 +1127,7 @@ var blockedServices = []blockedService{{
"||easyanticheat.net^",
"||epicgames.com^",
},
GroupID: "gaming",
}, {
ID: "espn",
Name: "ESPN",
@ -1106,6 +1151,7 @@ var blockedServices = []blockedService{{
"||espncdn.com^",
"||espncricinfo.com^",
},
GroupID: "streaming",
}, {
ID: "facebook",
Name: "Facebook",
@ -1555,6 +1601,7 @@ var blockedServices = []blockedService{{
"||zuckerberg.com^",
"||zuckerberg.net^",
},
GroupID: "social_network",
}, {
ID: "fifa",
Name: "FIFA",
@ -1563,6 +1610,7 @@ var blockedServices = []blockedService{{
"||fifa.com^",
"||fifaplus.com^",
},
GroupID: "streaming",
}, {
ID: "flickr",
Name: "Flickr",
@ -1575,6 +1623,7 @@ var blockedServices = []blockedService{{
"||flickrpro.com^",
"||staticflickr.com^",
},
GroupID: "hosting",
}, {
ID: "globoplay",
Name: "Globoplay",
@ -1585,6 +1634,7 @@ var blockedServices = []blockedService{{
"||globoplay.com^",
"||globoplay.globo.com^",
},
GroupID: "streaming",
}, {
ID: "gog",
Name: "GOG",
@ -1595,6 +1645,7 @@ var blockedServices = []blockedService{{
"||gog.com^",
"||gogalaxy.com^",
},
GroupID: "gaming",
}, {
ID: "hbomax",
Name: "HBO Max",
@ -1614,6 +1665,7 @@ var blockedServices = []blockedService{{
"||max.com^",
"||maxgo.com^",
},
GroupID: "streaming",
}, {
ID: "hulu",
Name: "Hulu",
@ -1621,6 +1673,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||hulu.com^",
},
GroupID: "streaming",
}, {
ID: "icloud_private_relay",
Name: "iCloud Private Relay",
@ -1630,6 +1683,7 @@ var blockedServices = []blockedService{{
"||mask-h2.icloud.com^$dnsrewrite=NXDOMAIN;;",
"||mask.icloud.com^$dnsrewrite=NXDOMAIN;;",
},
GroupID: "privacy",
}, {
ID: "iheartradio",
Name: "iHeartRadio",
@ -1647,6 +1701,7 @@ var blockedServices = []blockedService{{
"||ihrint.com^",
"||ihrstage.com^",
},
GroupID: "streaming",
}, {
ID: "imgur",
Name: "Imgur",
@ -1654,6 +1709,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||imgur.com^",
},
GroupID: "hosting",
}, {
ID: "instagram",
Name: "Instagram",
@ -1732,6 +1788,7 @@ var blockedServices = []blockedService{{
"||web-instagram.net^",
"||wwwinstagram.com^",
},
GroupID: "social_network",
}, {
ID: "iqiyi",
Name: "iQIYI",
@ -1746,6 +1803,7 @@ var blockedServices = []blockedService{{
"||qiyipic.com^",
"||qy.net^",
},
GroupID: "streaming",
}, {
ID: "kakaotalk",
Name: "KakaoTalk",
@ -1754,6 +1812,7 @@ var blockedServices = []blockedService{{
"||kakao.com^",
"||kgslb.com^",
},
GroupID: "messenger",
}, {
ID: "kik",
Name: "Kik",
@ -1761,6 +1820,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||kik.com^",
},
GroupID: "messenger",
}, {
ID: "kook",
Name: "KOOK",
@ -1769,6 +1829,7 @@ var blockedServices = []blockedService{{
"||kaiheila.cn^",
"||kookapp.cn^",
},
GroupID: "social_network",
}, {
ID: "lazada",
Name: "Lazada",
@ -1784,6 +1845,7 @@ var blockedServices = []blockedService{{
"||lazada.vn^",
"||slatic.net^",
},
GroupID: "shopping",
}, {
ID: "leagueoflegends",
Name: "League of Legends",
@ -1795,6 +1857,7 @@ var blockedServices = []blockedService{{
"||lolstatic.com^",
"||lolusercontent.com^",
},
GroupID: "gaming",
}, {
ID: "line",
Name: "LINE",
@ -1819,6 +1882,7 @@ var blockedServices = []blockedService{{
"||lineshoppingseller.com^",
"||linetv.tw^",
},
GroupID: "social_network",
}, {
ID: "linkedin",
Name: "LinkedIn",
@ -1841,6 +1905,7 @@ var blockedServices = []blockedService{{
"||linkedin.qtlcdn.com^",
"||lnkd.in^",
},
GroupID: "social_network",
}, {
ID: "lionsgateplus",
Name: "Lionsgate+",
@ -1849,6 +1914,7 @@ var blockedServices = []blockedService{{
"||lionsgateplus.com^",
"||starz.com^",
},
GroupID: "streaming",
}, {
ID: "looke",
Name: "Looke",
@ -1857,6 +1923,7 @@ var blockedServices = []blockedService{{
"||looke.com.br^",
"||ottvs.com.br^",
},
GroupID: "streaming",
}, {
ID: "mail_ru",
Name: "Mail.ru",
@ -1866,6 +1933,7 @@ var blockedServices = []blockedService{{
"||mail.ru^",
"||mycdn.me^",
},
GroupID: "social_network",
}, {
ID: "mastodon",
Name: "Mastodon",
@ -1971,6 +2039,7 @@ var blockedServices = []blockedService{{
"||wien.rocks^",
"||wxw.moe^",
},
GroupID: "social_network",
}, {
ID: "mercado_libre",
Name: "Mercado Libre",
@ -1997,6 +2066,7 @@ var blockedServices = []blockedService{{
"||mercadolivre.com.br^",
"||mlstatic.com^",
},
GroupID: "shopping",
}, {
ID: "minecraft",
Name: "Minecraft",
@ -2006,6 +2076,7 @@ var blockedServices = []blockedService{{
"||minecraftservices.com^",
"||mojang.com^",
},
GroupID: "gaming",
}, {
ID: "nebula",
Name: "Nebula",
@ -2014,6 +2085,7 @@ var blockedServices = []blockedService{{
"||nebula.app^",
"||nebula.tv^",
},
GroupID: "streaming",
}, {
ID: "netflix",
Name: "Netflix",
@ -2045,6 +2117,7 @@ var blockedServices = []blockedService{{
"||nflxso.net^",
"||nflxvideo.net^",
},
GroupID: "streaming",
}, {
ID: "nintendo",
Name: "Nintendo",
@ -2068,6 +2141,7 @@ var blockedServices = []blockedService{{
"||nintendoswitch.cn^",
"||nintendowifi.net^",
},
GroupID: "gaming",
}, {
ID: "nvidia",
Name: "Nvidia",
@ -2084,6 +2158,7 @@ var blockedServices = []blockedService{{
"||nvidianews.com^",
"||tegrazone.com^",
},
GroupID: "software",
}, {
ID: "odysee",
Name: "Odysee",
@ -2094,6 +2169,7 @@ var blockedServices = []blockedService{{
"||odysee.live^",
"||odysee.tv^",
},
GroupID: "social_network",
}, {
ID: "ok",
Name: "OK.ru",
@ -2106,6 +2182,7 @@ var blockedServices = []blockedService{{
"||oktech.ru^",
"||st.mycdn.me^",
},
GroupID: "social_network",
}, {
ID: "olvid",
Name: "Olvid",
@ -2114,6 +2191,7 @@ var blockedServices = []blockedService{{
"||olvid-attachment-chunks.s3.eu-west-3.amazonaws.com^",
"||olvid.io^",
},
GroupID: "messenger",
}, {
ID: "onlyfans",
Name: "OnlyFans",
@ -2121,6 +2199,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||onlyfans.com^",
},
GroupID: "social_network",
}, {
ID: "origin",
Name: "Origin",
@ -2138,6 +2217,7 @@ var blockedServices = []blockedService{{
"||origin.tv^",
"||signin.ea.com^",
},
GroupID: "gaming",
}, {
ID: "paramountplus",
Name: "Paramount Plus",
@ -2146,6 +2226,7 @@ var blockedServices = []blockedService{{
"||paramountplus.com^",
"||pplusstatic.com^",
},
GroupID: "streaming",
}, {
ID: "peacock_tv",
Name: "Peacock TV",
@ -2154,6 +2235,7 @@ var blockedServices = []blockedService{{
"||peacock.com^",
"||peacocktv.com^",
},
GroupID: "streaming",
}, {
ID: "pinterest",
Name: "Pinterest",
@ -2209,6 +2291,7 @@ var blockedServices = []blockedService{{
"||pinterest.vn^",
"||pinterestmail.com^",
},
GroupID: "social_network",
}, {
ID: "playstation",
Name: "PlayStation",
@ -2223,6 +2306,7 @@ var blockedServices = []blockedService{{
"||sonyentertainmentnetwork.com",
"||station.sony.com",
},
GroupID: "gaming",
}, {
ID: "plenty_of_fish",
Name: "Plenty of Fish",
@ -2230,6 +2314,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||pof.com^",
},
GroupID: "dating",
}, {
ID: "plex",
Name: "Plex",
@ -2240,6 +2325,7 @@ var blockedServices = []blockedService{{
"||plex.tv^",
"||plexapp.com^",
},
GroupID: "streaming",
}, {
ID: "pluto_tv",
Name: "Pluto TV",
@ -2247,6 +2333,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||pluto.tv^",
},
GroupID: "streaming",
}, {
ID: "privacy",
Name: "Privacy",
@ -2254,6 +2341,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||privacy.com.br^",
},
GroupID: "privacy",
}, {
ID: "qq",
Name: "QQ",
@ -2263,6 +2351,7 @@ var blockedServices = []blockedService{{
"||qq.com^$denyallow=wx.qq.com|weixin.qq.com",
"||url.cn^",
},
GroupID: "streaming",
}, {
ID: "rakuten_viki",
Name: "Rakuten Viki",
@ -2272,6 +2361,7 @@ var blockedServices = []blockedService{{
"||viki.com^",
"||viki.io^",
},
GroupID: "streaming",
}, {
ID: "reddit",
Name: "Reddit",
@ -2283,6 +2373,7 @@ var blockedServices = []blockedService{{
"||redditmedia.com^",
"||redditstatic.com^",
},
GroupID: "social_network",
}, {
ID: "riot_games",
Name: "Riot Games",
@ -2295,6 +2386,7 @@ var blockedServices = []blockedService{{
"||riotcdn.net^",
"||riotgames.com^",
},
GroupID: "gaming",
}, {
ID: "roblox",
Name: "Roblox",
@ -2314,6 +2406,7 @@ var blockedServices = []blockedService{{
"||robloxcdn.com^",
"||robloxdev.cn^",
},
GroupID: "gaming",
}, {
ID: "rockstar_games",
Name: "Rockstar Games",
@ -2322,6 +2415,7 @@ var blockedServices = []blockedService{{
"||rockstargames.com^",
"||rsg.sc^",
},
GroupID: "gaming",
}, {
ID: "samsung_tv_plus",
Name: "Samsung TV Plus",
@ -2332,6 +2426,7 @@ var blockedServices = []blockedService{{
"||samsungcloud.tv^",
"||samsungtvplus.com^",
},
GroupID: "streaming",
}, {
ID: "shein",
Name: "Shein",
@ -2342,6 +2437,7 @@ var blockedServices = []blockedService{{
"||shein.se^",
"||sheinsz.ltwebstatic.com^",
},
GroupID: "shopping",
}, {
ID: "shopee",
Name: "Shopee",
@ -2368,6 +2464,7 @@ var blockedServices = []blockedService{{
"||shopeemobile.com^",
"||shp.ee^",
},
GroupID: "shopping",
}, {
ID: "signal",
Name: "Signal",
@ -2376,6 +2473,7 @@ var blockedServices = []blockedService{{
"||signal.org^",
"||whispersystems.org^",
},
GroupID: "messenger",
}, {
ID: "skype",
Name: "Skype",
@ -2390,6 +2488,7 @@ var blockedServices = []blockedService{{
"||skypeassets.net^",
"||skypedata.akadns.net^",
},
GroupID: "messenger",
}, {
ID: "slack",
Name: "Slack",
@ -2401,6 +2500,7 @@ var blockedServices = []blockedService{{
"||slack.com^",
"||slackb.com^",
},
GroupID: "messenger",
}, {
ID: "snapchat",
Name: "Snapchat",
@ -2413,6 +2513,7 @@ var blockedServices = []blockedService{{
"||snapchat.com^",
"||snapkit.co",
},
GroupID: "social_network",
}, {
ID: "soundcloud",
Name: "SoundCloud",
@ -2421,6 +2522,7 @@ var blockedServices = []blockedService{{
"||sndcdn.com^",
"||soundcloud.com^",
},
GroupID: "streaming",
}, {
ID: "spotify",
Name: "Spotify",
@ -2448,6 +2550,7 @@ var blockedServices = []blockedService{{
"||spotifyforbrands.com^",
"||spotifyjobs.com^",
},
GroupID: "streaming",
}, {
ID: "spotify_video",
Name: "Spotify Video",
@ -2460,6 +2563,7 @@ var blockedServices = []blockedService{{
"||video-akpcw.spotifycdn.com^",
"||video-fa.scdn.co^",
},
GroupID: "streaming",
}, {
ID: "steam",
Name: "Steam",
@ -2500,6 +2604,7 @@ var blockedServices = []blockedService{{
"||valvesoftware.com^",
"||wmsjsteam.com^",
},
GroupID: "gaming",
}, {
ID: "telegram",
Name: "Telegram (Web)",
@ -2523,6 +2628,7 @@ var blockedServices = []blockedService{{
"||tx.me^",
"||usercontent.dev^",
},
GroupID: "messenger",
}, {
ID: "temu",
Name: "Temu",
@ -2531,6 +2637,7 @@ var blockedServices = []blockedService{{
"||kwcdn.com^",
"||temu.com^",
},
GroupID: "shopping",
}, {
ID: "tidal",
Name: "Tidal",
@ -2538,6 +2645,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||tidal.com^",
},
GroupID: "streaming",
}, {
ID: "tiktok",
Name: "TikTok",
@ -2577,6 +2685,7 @@ var blockedServices = []blockedService{{
"||v*.tiktokcdn-eu.com^",
"||zijieapi.com^",
},
GroupID: "social_network",
}, {
ID: "tinder",
Name: "Tinder",
@ -2586,6 +2695,7 @@ var blockedServices = []blockedService{{
"||tinder.com^",
"||tindersparks.com^",
},
GroupID: "dating",
}, {
ID: "tumblr",
Name: "Tumblr",
@ -2593,6 +2703,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||tumblr.com^",
},
GroupID: "social_network",
}, {
ID: "twitch",
Name: "Twitch",
@ -2605,6 +2716,7 @@ var blockedServices = []blockedService{{
"||twitchcdn.net^",
"||twitchsvc.net^",
},
GroupID: "streaming",
}, {
ID: "twitter",
Name: "X (formerly Twitter)",
@ -2634,6 +2746,7 @@ var blockedServices = []blockedService{{
"||vine.co^",
"||x.com^",
},
GroupID: "social_network",
}, {
ID: "ubisoft",
Name: "Ubisoft",
@ -2644,6 +2757,7 @@ var blockedServices = []blockedService{{
"||ubisoft.org^",
"||ubisoftconnect.com^",
},
GroupID: "gaming",
}, {
ID: "valorant",
Name: "Valorant",
@ -2653,6 +2767,7 @@ var blockedServices = []blockedService{{
"||valorant.scd.riotcdn.net",
"||valorant.secure.dyn.riotcdn.net",
},
GroupID: "gaming",
}, {
ID: "viber",
Name: "Viber",
@ -2660,6 +2775,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||viber.com^",
},
GroupID: "messenger",
}, {
ID: "vimeo",
Name: "Vimeo",
@ -2683,6 +2799,7 @@ var blockedServices = []blockedService{{
"||vimeoondemand.com^",
"||vimeostatus.com^",
},
GroupID: "streaming",
}, {
ID: "vk",
Name: "VK.com",
@ -2709,6 +2826,7 @@ var blockedServices = []blockedService{{
"||vkuservideo.com^",
"||vkuservideo.net^",
},
GroupID: "social_network",
}, {
ID: "voot",
Name: "Voot",
@ -2716,6 +2834,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||voot.com^",
},
GroupID: "streaming",
}, {
ID: "wargaming",
Name: "Wargaming",
@ -2730,6 +2849,7 @@ var blockedServices = []blockedService{{
"||worldofwarships.eu^",
"||wotblitz.com^",
},
GroupID: "gaming",
}, {
ID: "wechat",
Name: "WeChat",
@ -2741,6 +2861,7 @@ var blockedServices = []blockedService{{
"||weixinbridge.com^",
"||wx.qq.com^",
},
GroupID: "messenger",
}, {
ID: "weibo",
Name: "Weibo",
@ -2754,6 +2875,7 @@ var blockedServices = []blockedService{{
"||weibo.com^",
"||weibocdn.com^",
},
GroupID: "streaming",
}, {
ID: "whatsapp",
Name: "WhatsApp",
@ -2771,6 +2893,7 @@ var blockedServices = []blockedService{{
"||whatsapp.tv^",
"||whatsappbrand.com^",
},
GroupID: "messenger",
}, {
ID: "wizz",
Name: "Wizz",
@ -2780,6 +2903,7 @@ var blockedServices = []blockedService{{
"||wizz.chat^",
"||wizzapp.com^",
},
GroupID: "dating",
}, {
ID: "xboxlive",
Name: "Xbox Live",
@ -2794,6 +2918,7 @@ var blockedServices = []blockedService{{
"||xboxlive.com^",
"||xboxservices.com^",
},
GroupID: "gaming",
}, {
ID: "xiaohongshu",
Name: "Xiaohongshu",
@ -2805,6 +2930,7 @@ var blockedServices = []blockedService{{
"||xiaohongshu.com^",
"||xiaohongshu.net^",
},
GroupID: "shopping",
}, {
ID: "youtube",
Name: "YouTube",
@ -2987,6 +3113,7 @@ var blockedServices = []blockedService{{
"||yt.be^",
"||ytimg.com^",
},
GroupID: "streaming",
}, {
ID: "yy",
Name: "YY",
@ -2994,6 +3121,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||yy.com^",
},
GroupID: "streaming",
}, {
ID: "zhihu",
Name: "Zhihu",
@ -3002,4 +3130,32 @@ var blockedServices = []blockedService{{
"||zhihu.com^",
"||zhimg.com^",
},
GroupID: "social_network",
}}
// serviceGroups contains raw service group data.
var serviceGroups = []serviceGroup{{
ID: "ai",
}, {
ID: "cdn",
}, {
ID: "dating",
}, {
ID: "gambling",
}, {
ID: "gaming",
}, {
ID: "hosting",
}, {
ID: "messenger",
}, {
ID: "privacy",
}, {
ID: "shopping",
}, {
ID: "social_network",
}, {
ID: "software",
}, {
ID: "streaming",
}}

View File

@ -525,8 +525,11 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
registerControlHandlers(web)
aghhttp.OK(ctx, web.logger, w)
if f, ok := w.(http.Flusher); ok {
f.Flush()
rc := http.NewResponseController(w)
err = rc.Flush()
if err != nil {
web.logger.WarnContext(ctx, "flushing response", slogutil.KeyError, err)
}
if !restartHTTP {

View File

@ -64,7 +64,7 @@ func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) {
}
}
err = web.requestVersionInfo(r.Context(), resp, req.Recheck)
err = web.requestVersionInfo(ctx, resp, req.Recheck)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
aghhttp.ErrorAndLog(ctx, web.logger, r, w, http.StatusBadGateway, "%s", err)
@ -161,8 +161,11 @@ func (web *webAPI) handleUpdate(w http.ResponseWriter, r *http.Request) {
}
aghhttp.OK(ctx, web.logger, w)
if f, ok := w.(http.Flusher); ok {
f.Flush()
rc := http.NewResponseController(w)
err = rc.Flush()
if err != nil {
web.logger.WarnContext(ctx, "flushing response", slogutil.KeyError, err)
}
// The background context is used because the underlying functions wrap it

View File

@ -188,7 +188,7 @@ func (l *queryLog) decodeResultRuleKey(
ent.Result.Rules, vToken = l.decodeVTokenAndAddRule(ctx, key, i, dec, ent.Result.Rules)
if n, ok := vToken.(json.Number); ok {
id, _ := n.Int64()
ent.Result.Rules[i].FilterListID = rulelist.URLFilterID(id)
ent.Result.Rules[i].FilterListID = rulelist.APIID(id)
}
case "IP":
ent.Result.Rules, vToken = l.decodeVTokenAndAddRule(ctx, key, i, dec, ent.Result.Rules)
@ -643,7 +643,7 @@ var resultHandlers = map[string]logEntryHandler{
l++
}
ent.Result.Rules[l-1].FilterListID = rulelist.URLFilterID(id)
ent.Result.Rules[l-1].FilterListID = rulelist.APIID(id)
return nil
},

View File

@ -2,7 +2,11 @@
<!-- TODO(a.garipov): Reformat in accordance with the KeepAChangelog spec. -->
## v0.108.0: API changes
## v0.107.67: API changes
- The new field `"groups"` in `GET /control/blocked_services/all` is a list of service group. Groups make it possible to block multiple services with equal `"group_id"` at once.
- The new field `"group_id"` for each `BlockedService` object in `GET /control/blocked_services/all` indicates which group the service belongs to.
## v0.107.64: API changes

View File

@ -3012,8 +3012,12 @@
'items':
'$ref': '#/components/schemas/BlockedService'
'type': 'array'
'groups':
'items':
'$ref': '#/components/schemas/ServiceGroup'
'required':
- 'blocked_services'
- 'groups'
'type': 'object'
'BlockedService':
'properties':
@ -3036,12 +3040,25 @@
'items':
'type': 'string'
'type': 'array'
'group_id':
'description': >
The ID of the group, that the service belongs to.
'type': 'string'
'required':
- 'icon_svg'
- 'id'
- 'name'
- 'rules'
'type': 'object'
'ServiceGroup':
'properties':
'id':
'description': >
The ID of this group.
'type': 'string'
'required':
- 'id'
'type': 'object'
'BlockedServicesSchedule':
'type': 'object'
'properties':

View File

@ -86,6 +86,12 @@ type blockedService struct {
Name string ` + "`" + `json:"name"` + "`" + `
IconSVG []byte ` + "`" + `json:"icon_svg"` + "`" + `
Rules []string ` + "`" + `json:"rules"` + "`" + `
GroupID string ` + "`" + `json:"group_id"` + "`" + `
}
// serviceGroup represents single group of services.
type serviceGroup struct {
ID string ` + "`" + `json:"id"` + "`" + `
}
// blockedServices contains raw blocked service data.
@ -97,6 +103,13 @@ var blockedServices = []blockedService{<% $l := len .BlockedServices %>
Rules: []string{<% range $s.Rules %>
<% printf "%q" . %>,<% end %>
},
GroupID: <% printf "%q" $s.Group %>,
}<% if isnotlast $i $l %>, <% end %><% end %>}
// serviceGroups contains raw service group data.
var serviceGroups = []serviceGroup{<% $l := len .ServiceGroups %>
<%- range $i, $s := .ServiceGroups %>{
ID: <% printf "%q" $s.ID %>,
}<% if isnotlast $i $l %>, <% end %><% end %>}
`
@ -104,6 +117,7 @@ var blockedServices = []blockedService{<% $l := len .BlockedServices %>
// index.
type hlServices struct {
BlockedServices []*hlServicesService `json:"blocked_services"`
ServiceGroups []*hlServicesGroup `json:"groups"`
}
// hlServicesService is the JSON structure for a service in the Hostlists
@ -113,4 +127,11 @@ type hlServicesService struct {
Name string `json:"name"`
IconSVG string `json:"icon_svg"`
Rules []string `json:"rules"`
Group string `json:"group"`
}
// hlServicesGroup is the JSON structure for a service group in the Hostlists
// Registry.
type hlServicesGroup struct {
ID string `json:"id"`
}

View File

@ -301,7 +301,7 @@ fix_darwin() {
# Function fix_freebsd performs some fixes to make it work on FreeBSD.
fix_freebsd() {
if ! [ "$os" = 'freebsd' ]; then
if [ "$os" != 'freebsd' ]; then
return 0
fi

View File

@ -180,13 +180,10 @@ run_linter gocognit --over='14' \
./internal/dhcpd \
;
run_linter gocognit --over='13' \
./internal/aghnet/ \
;
run_linter gocognit --over='10' \
./internal/aghalg/ \
./internal/aghhttp/ \
./internal/aghnet/ \
./internal/aghos/ \
./internal/aghrenameio/ \
./internal/aghtest/ \

View File

@ -17,6 +17,7 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/ioutil"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/syncutil"
)
// download and save all translations.
@ -47,7 +48,7 @@ func (c *twoskyClient) download(ctx context.Context, l *slog.Logger) (err error)
dw := &downloadWorker{
ctx: ctx,
l: l,
failed: &sync.Map{},
failed: syncutil.NewMap[string, struct{}](),
client: &http.Client{
Timeout: 10 * time.Second,
},
@ -72,25 +73,24 @@ func (c *twoskyClient) download(ctx context.Context, l *slog.Logger) (err error)
return nil
}
// printFailedLocales prints sorted list of failed downloads, if any.
func printFailedLocales(ctx context.Context, l *slog.Logger, failed *sync.Map) {
keys := []string{}
failed.Range(func(k, _ any) bool {
s, ok := k.(string)
if !ok {
panic("unexpected type")
}
keys = append(keys, s)
return true
})
// printFailedLocales prints sorted list of failed downloads, if any. l and
// failed must not be nil.
func printFailedLocales(
ctx context.Context,
l *slog.Logger,
failed *syncutil.Map[string, struct{}],
) {
var keys []string
for k := range failed.Range {
keys = append(keys, k)
}
if len(keys) == 0 {
return
}
slices.Sort(keys)
l.InfoContext(ctx, "failed", "locales", keys)
}
@ -100,7 +100,7 @@ func printFailedLocales(ctx context.Context, l *slog.Logger, failed *sync.Map) {
type downloadWorker struct {
ctx context.Context
l *slog.Logger
failed *sync.Map
failed *syncutil.Map[string, struct{}]
client *http.Client
uriCh <-chan *url.URL
}