From 394b8c529411c81dbfe5445f04b12fc7c88c526f Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Thu, 25 Sep 2025 15:06:34 +0300 Subject: [PATCH 1/5] Pull request 2486: upd-all Squashed commit of the following: commit bc0cdb528086f874456e6855df69164ccb14282b Author: Eugene Burkov Date: Thu Sep 25 14:37:14 2025 +0300 all: log changes commit 1973ba9a860ab82beda1c0275e62f6f1d78d869e Author: Eugene Burkov Date: Thu Sep 25 14:00:35 2025 +0300 client: upd i18n commit 4e7626b8fd405be557624e1c685b0472cfe29789 Author: Eugene Burkov Date: Thu Sep 25 13:51:23 2025 +0300 client: upd filters --- CHANGELOG.md | 12 +++++++----- client/src/__locales/be.json | 2 +- client/src/__locales/ru.json | 4 ++-- client/src/__locales/uk.json | 3 +++ client/src/helpers/filters/filters.ts | 6 ++++++ 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d81e05e..8456b83f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ NOTE: Add new changes BELOW THIS COMMENT. ### 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,6 +32,7 @@ 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 -## 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 diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index ba7b1b3b..aa252a5c 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -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': diff --git a/scripts/blocked-services/main.go b/scripts/blocked-services/main.go index 27c52d9f..3e0f5d8c 100644 --- a/scripts/blocked-services/main.go +++ b/scripts/blocked-services/main.go @@ -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"` } From fbc4a2700773aa30e780f1b518f47c639ea7a4c4 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 26 Sep 2025 15:32:35 +0300 Subject: [PATCH 4/5] Pull request 2484: AGDNS-3118-upd-urlfilter Squashed commit of the following: commit fd449c5931b8d0bff325f1f6edabe16b965e77d8 Merge: c4a416ee5 d0b89e4d3 Author: Ainar Garipov Date: Fri Sep 26 15:06:54 2025 +0300 Merge branch 'master' into AGDNS-3118-upd-urlfilter commit c4a416ee591600058b357532b7a7269806a1edfa Author: Ainar Garipov Date: Wed Sep 24 17:26:29 2025 +0300 filtering: imp code, docs commit a7d2af91f40239e6541b4ccf20da8c81a24a4fbf Author: Ainar Garipov Date: Wed Sep 24 13:52:56 2025 +0300 all: upd urlfilter, other deps, tools, etc. --- go.mod | 62 +++---- go.sum | 141 +++++++--------- internal/aghalg/sortedmap.go | 22 ++- internal/aghalg/sortedmap_test.go | 5 +- internal/client/index.go | 8 +- internal/client/persistent.go | 2 + internal/filtering/blocked.go | 2 +- internal/filtering/dnsrewrite.go | 19 +-- internal/filtering/filter.go | 6 +- internal/filtering/filtering.go | 154 ++---------------- internal/filtering/hashprefix/cache.go | 3 + internal/filtering/hosts.go | 4 +- internal/filtering/hosts_test.go | 16 +- internal/filtering/http.go | 33 ++-- internal/filtering/idgenerator.go | 22 +-- .../filtering/idgenerator_internal_test.go | 4 +- internal/filtering/result.go | 152 +++++++++++++++++ internal/filtering/rewrite/storage.go | 9 +- .../rewrite/storage_internal_test.go | 15 +- internal/filtering/rulelist/filter.go | 26 +-- internal/filtering/rulelist/rulelist.go | 35 ++-- internal/filtering/rulelist/rulelist_test.go | 24 ++- internal/filtering/rulelist/storage.go | 8 +- internal/filtering/rulelist/textengine.go | 3 +- .../filtering/rulelist/textengine_test.go | 2 +- internal/filtering/safesearch/safesearch.go | 18 +- .../filtering/safesearch/safesearch_test.go | 2 +- internal/querylog/decode.go | 4 +- scripts/translations/download.go | 30 ++-- 29 files changed, 442 insertions(+), 389 deletions(-) create mode 100644 internal/filtering/result.go diff --git a/go.mod b/go.mod index 09ba6566..9d26813e 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index e227a1ec..37597216 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/aghalg/sortedmap.go b/internal/aghalg/sortedmap.go index c89fcafd..ff4178fc 100644 --- a/internal/aghalg/sortedmap.go +++ b/internal/aghalg/sortedmap.go @@ -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) } diff --git a/internal/aghalg/sortedmap_test.go b/internal/aghalg/sortedmap_test.go index a3806639..ce769dfb 100644 --- a/internal/aghalg/sortedmap_test.go +++ b/internal/aghalg/sortedmap_test.go @@ -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 { diff --git a/internal/client/index.go b/internal/client/index.go index a900ab14..6c5127f1 100644 --- a/internal/client/index.go +++ b/internal/client/index.go @@ -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{}, } diff --git a/internal/client/persistent.go b/internal/client/persistent.go index 1d1967a2..0656a31e 100644 --- a/internal/client/persistent.go +++ b/internal/client/persistent.go @@ -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 diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go index 2a7cd1c2..ba9368a2 100644 --- a/internal/filtering/blocked.go +++ b/internal/filtering/blocked.go @@ -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) diff --git a/internal/filtering/dnsrewrite.go b/internal/filtering/dnsrewrite.go index bba2f701..b5ca2a03 100644 --- a/internal/filtering/dnsrewrite.go +++ b/internal/filtering/dnsrewrite.go @@ -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, } diff --git a/internal/filtering/filter.go b/internal/filtering/filter.go index cfc0baae..167b8d1f 100644 --- a/internal/filtering/filter.go +++ b/internal/filtering/filter.go @@ -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")), } diff --git a/internal/filtering/filtering.go b/internal/filtering/filtering.go index 906f8b07..d7ebe055 100644 --- a/internal/filtering/filtering.go +++ b/internal/filtering/filtering.go @@ -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, diff --git a/internal/filtering/hashprefix/cache.go b/internal/filtering/hashprefix/cache.go index 99d6cc73..25ac8ffc 100644 --- a/internal/filtering/hashprefix/cache.go +++ b/internal/filtering/hashprefix/cache.go @@ -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 { diff --git a/internal/filtering/hosts.go b/internal/filtering/hosts.go index ba5b3899..635b5d39 100644 --- a/internal/filtering/hosts.go +++ b/internal/filtering/hosts.go @@ -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, }) } diff --git a/internal/filtering/hosts_test.go b/internal/filtering/hosts_test.go index 9385314a..cb075418 100644 --- a/internal/filtering/hosts_test.go +++ b/internal/filtering/hosts_test.go @@ -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}, }} diff --git a/internal/filtering/http.go b/internal/filtering/http.go index 55b8d4ac..c8548759 100644 --- a/internal/filtering/http.go +++ b/internal/filtering/http.go @@ -327,12 +327,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 { @@ -345,11 +347,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() { @@ -408,8 +412,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 { @@ -432,7 +437,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 diff --git a/internal/filtering/idgenerator.go b/internal/filtering/idgenerator.go index 7d07cf23..bcc6bae3 100644 --- a/internal/filtering/idgenerator.go +++ b/internal/filtering/idgenerator.go @@ -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 { diff --git a/internal/filtering/idgenerator_internal_test.go b/internal/filtering/idgenerator_internal_test.go index 195e9976..c345476f 100644 --- a/internal/filtering/idgenerator_internal_test.go +++ b/internal/filtering/idgenerator_internal_test.go @@ -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) } diff --git a/internal/filtering/result.go b/internal/filtering/result.go new file mode 100644 index 00000000..285c42df --- /dev/null +++ b/internal/filtering/result.go @@ -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) } diff --git a/internal/filtering/rewrite/storage.go b/internal/filtering/rewrite/storage.go index 2ca1d0d4..badd46d7 100644 --- a/internal/filtering/rewrite/storage.go +++ b/internal/filtering/rewrite/storage.go @@ -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 } diff --git a/internal/filtering/rewrite/storage_internal_test.go b/internal/filtering/rewrite/storage_internal_test.go index 10df670c..7fb4875f 100644 --- a/internal/filtering/rewrite/storage_internal_test.go +++ b/internal/filtering/rewrite/storage_internal_test.go @@ -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) diff --git a/internal/filtering/rulelist/filter.go b/internal/filtering/rulelist/filter.go index 6d455e3d..044a136b 100644 --- a/internal/filtering/rulelist/filter.go +++ b/internal/filtering/rulelist/filter.go @@ -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 diff --git a/internal/filtering/rulelist/rulelist.go b/internal/filtering/rulelist/rulelist.go index d8a82375..17e8c3cd 100644 --- a/internal/filtering/rulelist/rulelist.go +++ b/internal/filtering/rulelist/rulelist.go @@ -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. diff --git a/internal/filtering/rulelist/rulelist_test.go b/internal/filtering/rulelist/rulelist_test.go index 0e966da2..8089aee6 100644 --- a/internal/filtering/rulelist/rulelist_test.go +++ b/internal/filtering/rulelist/rulelist_test.go @@ -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)) +} diff --git a/internal/filtering/rulelist/storage.go b/internal/filtering/rulelist/storage.go index 3281032d..4a28cbc6 100644 --- a/internal/filtering/rulelist/storage.go +++ b/internal/filtering/rulelist/storage.go @@ -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) diff --git a/internal/filtering/rulelist/textengine.go b/internal/filtering/rulelist/textengine.go index df37aca2..0a98f826 100644 --- a/internal/filtering/rulelist/textengine.go +++ b/internal/filtering/rulelist/textengine.go @@ -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 diff --git a/internal/filtering/rulelist/textengine_test.go b/internal/filtering/rulelist/textengine_test.go index 6b80074b..bcc54c15 100644 --- a/internal/filtering/rulelist/textengine_test.go +++ b/internal/filtering/rulelist/textengine_test.go @@ -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) } diff --git a/internal/filtering/safesearch/safesearch.go b/internal/filtering/safesearch/safesearch.go index a29e3a61..6e2488a9 100644 --- a/internal/filtering/safesearch/safesearch.go +++ b/internal/filtering/safesearch/safesearch.go @@ -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 diff --git a/internal/filtering/safesearch/safesearch_test.go b/internal/filtering/safesearch/safesearch_test.go index 77eca2f1..c77dcae2 100644 --- a/internal/filtering/safesearch/safesearch_test.go +++ b/internal/filtering/safesearch/safesearch_test.go @@ -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) } } }) diff --git a/internal/querylog/decode.go b/internal/querylog/decode.go index 47d87efc..fbe5bc40 100644 --- a/internal/querylog/decode.go +++ b/internal/querylog/decode.go @@ -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 }, diff --git a/scripts/translations/download.go b/scripts/translations/download.go index 3e27c642..df05d5e4 100644 --- a/scripts/translations/download.go +++ b/scripts/translations/download.go @@ -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 } From 94d61abcc74f6e9f46ccc4a7a75d87c315985d2e Mon Sep 17 00:00:00 2001 From: Stanislav Chzhen Date: Mon, 29 Sep 2025 16:39:30 +0300 Subject: [PATCH 5/5] Pull request 2489: AGDNS-3234-http-response-controller Squashed commit of the following: commit cbe1bf70e33022889b5bbae8c0ad4d90ddad43e0 Merge: 266f74d3d fbc4a2700 Author: Stanislav Chzhen Date: Mon Sep 29 16:25:19 2025 +0300 Merge branch 'master' into AGDNS-3234-http-response-controller commit 266f74d3d6ba9af9db86f0235634141b35acc26d Author: Stanislav Chzhen Date: Thu Sep 25 17:27:38 2025 +0300 home: http response controller --- internal/home/controlinstall.go | 7 +++++-- internal/home/controlupdate.go | 11 ++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index 77fb3ef4..0243f4cd 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -497,8 +497,11 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request registerControlHandlers(web) aghhttp.OK(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 { diff --git a/internal/home/controlupdate.go b/internal/home/controlupdate.go index e791633d..5bc638ad 100644 --- a/internal/home/controlupdate.go +++ b/internal/home/controlupdate.go @@ -115,6 +115,8 @@ func (web *webAPI) requestVersionInfo( // handleUpdate performs an update to the latest available version procedure. func (web *webAPI) handleUpdate(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + updater := web.conf.updater if updater.NewVersion() == "" { aghhttp.Error(r, w, http.StatusBadRequest, "/update request isn't allowed now") @@ -133,7 +135,7 @@ func (web *webAPI) handleUpdate(w http.ResponseWriter, r *http.Request) { return } - err = updater.Update(r.Context(), false) + err = updater.Update(ctx, false) if err != nil { aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err) @@ -141,8 +143,11 @@ func (web *webAPI) handleUpdate(w http.ResponseWriter, r *http.Request) { } aghhttp.OK(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