Pull request 2484: AGDNS-3118-upd-urlfilter

Squashed commit of the following:

commit fd449c5931b8d0bff325f1f6edabe16b965e77d8
Merge: c4a416ee5 d0b89e4d3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Sep 26 15:06:54 2025 +0300

    Merge branch 'master' into AGDNS-3118-upd-urlfilter

commit c4a416ee59
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Sep 24 17:26:29 2025 +0300

    filtering: imp code, docs

commit a7d2af91f4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Sep 24 13:52:56 2025 +0300

    all: upd urlfilter, other deps, tools, etc.
This commit is contained in:
Ainar Garipov 2025-09-26 15:32:35 +03:00
parent d0b89e4d36
commit fbc4a27007
29 changed files with 442 additions and 389 deletions

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

@ -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)

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

@ -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

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

@ -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

@ -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
}