Compare commits

...

25 Commits

Author SHA1 Message Date
Dimitry Kolyshev
734578fd04 docs: check port install 2023-08-14 16:25:09 +03:00
Dimitry Kolyshev
38b2d56fb9 home: check port install 2023-08-14 16:23:56 +03:00
Ainar Garipov
418c830e53 Pull request 1957: 1453-fix-stats-table
Updates #1453.

Squashed commit of the following:

commit 81105a53a588e6c5d3e16e8ded955b6462a94b7c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 9 17:51:42 2023 +0300

    client: fix total for upstream table
2023-08-09 18:44:42 +03:00
Dimitry Kolyshev
1e939703e5 Pull request: 6053-https-filtering
Updates #6053.

Squashed commit of the following:

commit b71957f87eca93e9827d027c246d2ca9d7a7f45a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 9 16:12:10 2023 +0300

    all: docs

commit 3e394fb2d723c4e305ea91f10fffc866f0b9948a
Merge: f406a5ff4 c47509fab
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 9 15:15:37 2023 +0300

    all: imp code

commit f406a5ff4977acdcd19557969bd405747b84ebbc
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 9 15:05:43 2023 +0300

    all: imp code

commit 0de1e0e8a9f0dfd3a0ff0c9e787d6e50cf2a1ee8
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 9 14:45:21 2023 +0300

    all: docs

commit d98cbafe62edd77afcf6c760e28cb5e7632a993e
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 9 11:54:39 2023 +0300

    dnsforward: https blocked rcode

commit c13ffda6182920f97fe8293a9c0b518bbf77956e
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 9 10:45:27 2023 +0300

    dnsforward: imp tests

commit 9c5bc29b33d53ba82ca11f508391e5b5d534a834
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 9 10:08:06 2023 +0300

    dnsforward: imp code

commit d6ff28b9c277c24b4f273cd4b292543ead13d859
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 8 16:00:15 2023 +0300

    all: imp code

commit 832b59965d1515badd0a0650f9753fc2985dff1c
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 8 13:32:15 2023 +0300

    dnsforward: https filtering

commit 6a2bdd11331ffddb13bac4e05de85b6661360783
Merge: 257a1b6b8 54aee2272
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 8 11:44:12 2023 +0300

    Merge remote-tracking branch 'origin/master' into 6053-https-filtering

    # Conflicts:
    #	CHANGELOG.md

commit 257a1b6b868826cb4112c1c88b177290242d3fdd
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 8 11:26:13 2023 +0300

    dnsforward: imp tests

commit edba217a72101b8b5a79e7b82614b3ea0e4c1f09
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri Aug 4 15:03:02 2023 +0300

    dnsforward: https filtering

commit 4c93be3e0c7b98c1242b60ba5a3c45cea2775be4
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri Aug 4 14:36:33 2023 +0300

    docs: https filtering

commit 1d2d1aa3b4ce7a994395fade2f87b2d88d68ac63
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri Aug 4 12:54:05 2023 +0300

    all: https filtering hints
2023-08-09 16:27:21 +03:00
Stanislav Chzhen
c47509fabc Pull request 1928: 1453-stats-tests
Updates #1453.

Squashed commit of the following:

commit f08f68ef5493dad03d3eb120d886f2df1af28be6
Merge: b70b088af 54aee2272
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Aug 8 19:04:06 2023 +0300

    Merge branch 'master' into 1453-stats-tests

commit b70b088af0fdc7d6d048d688160048bad1fceb12
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Aug 3 19:32:04 2023 +0300

    stats: imp code

commit c341012ba61894c255c1868624be1cac0d26a6fa
Merge: a2ac8c34e 5eb3cd0f9
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Aug 3 13:36:24 2023 +0300

    Merge branch 'master' into 1453-stats-tests

commit a2ac8c34ee32606ca5e259c3e2a47db0dd5858de
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Aug 3 13:25:12 2023 +0300

    client: add top upstreams and average processing time tables

commit 11118947f9bf945be0b056f8475cf3b848c6e66e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Aug 1 17:24:57 2023 +0300

    stats: imp docs

commit 904cf81d02a1f327b9647fa7ad9e181cfabb68a4
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jul 31 17:34:06 2023 +0300

    stats: imp code

commit 34f0c96dd5865d1470385322a88842dd0b3d996d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jul 31 15:43:46 2023 +0300

    all: imp docs

commit 2cb2d0d8bef3580f64bc25c414fe9b5ea6b9f997
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jul 28 17:24:31 2023 +0300

    all: imp code

commit 5251a899fecc21e50a0ba06042f96f5b404e196a
Merge: b6c2b12d4 300821a7f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 27 20:34:39 2023 +0300

    Merge branch 'master' into 1453-stats-tests

commit b6c2b12d4425012efd73549c3a426735f3a677cd
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 27 20:32:18 2023 +0300

    stats: imp code

commit 5546b82a78326f9cc6d8c87df5083f8fc66a0178
Merge: 8a3d6b1b4 5f8fa006c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 27 14:24:01 2023 +0300

    Merge branch 'master' into 1453-stats-tests

commit 8a3d6b1b49ce189f95adfa7406a34108e885e676
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 27 14:17:47 2023 +0300

    all: imp code

commit 2a48001e275e3cdcf70e13e1c9cebd4e502f3259
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jul 25 18:27:20 2023 +0300

    all: imp docs

commit 3dd21890175af32a3368378f7e013383f6d040ec
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jul 25 16:00:39 2023 +0300

    all: imp naming

commit 6124456fc3149b71f6bd58d35ecf24eb6cf40d5d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 20 16:15:56 2023 +0300

    all: add upstreams avg processing time

commit 187ad0c77a81c9fd95c24e23141355db2e83e50d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jul 18 16:42:19 2023 +0300

    all: add top upstreams
2023-08-09 14:33:52 +03:00
Eugene Burkov
54aee22720 Pull request 1954: upd urlfilter
Merge in DNS/adguard-home from upd-urlfilter to master

Squashed commit of the following:

commit e3f1e9c818e8627ee827e9d7383b4a7985ee24aa
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 7 17:05:19 2023 +0300

    all: log changes

commit 541faedad84f45da58303772675b0da32d170c39
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 7 16:15:38 2023 +0300

    all: upd urlfilter
2023-08-07 17:14:20 +03:00
Stanislav Chzhen
93a0601f41 Pull request 1952: 5948-fix-dns-filter
Updates #5948.

Squashed commit of the following:

commit 9dbc197f004a19211e5fedeb9bdd7075e2915fce
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Aug 7 15:06:38 2023 +0300

    all: imp chlog

commit fbcccc2ff3663fc8ae0cd75ef6ac4cdcc0fa7d36
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Aug 3 16:58:35 2023 +0300

    all: upd chlog

commit 4f9e8fcbfb4d43fd98a99529f20e9d40946ee5c1
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Aug 2 19:24:42 2023 +0300

    dnsforward: fix dns filter
2023-08-07 16:07:21 +03:00
Dimitry Kolyshev
5eb3cd0f92 Pull request 1947: AG-24320 home: pprof conf
Squashed commit of the following:

commit bc0facffe41e140fab00edeeeca3b69306cf2ceb
Merge: 71e0806ba c0691cab6
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 2 17:34:15 2023 +0300

    Merge branch 'master' into pprof-conf

commit 71e0806bac52412cae7cad2748216ece7fbed36f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 2 08:37:51 2023 +0300

    all: docs

commit 6ebb6f9a5f4dbeb753dd470879f2e5ff556ee5f1
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 1 15:56:45 2023 +0300

    home: imp code

commit ca084011cddc20f5c0b770ee38f9ac55d62bff24
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 1 13:57:53 2023 +0300

    all: docs

commit 1b498a84d6cb8207d350fceb4db64d45dc2aa46d
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 1 13:46:13 2023 +0300

    all: docs

commit 0cd76c057e0f3e9e62e5bf38f95080afa830f4ff
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 1 13:00:43 2023 +0300

    home: pprof conf
2023-08-02 17:39:33 +03:00
Ainar Garipov
c0691cab6a Pull request 1951: upd-chlog
Squashed commit of the following:

commit 6b727dbc2b8f09765f63a983abf5b83c1340149c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 2 17:11:38 2023 +0300

    all: upd chlog
2023-08-02 17:17:25 +03:00
Ainar Garipov
c0c152885d Pull request 1950: upd-all
Squashed commit of the following:

commit 6e17dd9bf96fd684bd2ff28285ff9ef6534641e9
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 2 14:59:19 2023 +0300

    all: upd i18n, svcs, trackers
2023-08-02 15:58:05 +03:00
Ainar Garipov
a6c5cab218 Pull request 1949: upd-go
Squashed commit of the following:

commit d850dc74a5f36797bd2270c011fe0525adbf9d14
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 2 13:15:01 2023 +0300

    all: upd go, tools
2023-08-02 13:25:04 +03:00
Ainar Garipov
fe0edc0065 Pull request 1948: imp-test
Squashed commit of the following:

commit d2e61b0a2406a503d9d7bcd12612ed7e04c1fac6
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Aug 1 18:02:29 2023 +0300

    client: imp addrproc test

commit f7cf0fb1549299b00fdbe400bb4a96c73530bfe0
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Aug 1 17:23:12 2023 +0300

    dnsforward: rm mutex
2023-08-01 19:10:41 +03:00
Andrey Meshkov
2cbc5e5f9d Pull request 1946: Unix --> Linux/Unix/MacOS
Merge in DNS/adguard-home from fix-readme-unix to master

Squashed commit of the following:

commit c51fbed552876d3298480aad2f7382585c036091
Author: Andrey Meshkov <am@adguard.com>
Date:   Mon Jul 31 16:16:52 2023 +0300

    Added bsd

commit 30bee72bcf6f629b3f1871ddb6181fd2f4d1f7fa
Author: Andrey Meshkov <am@adguard.com>
Date:   Sun Jul 30 17:47:53 2023 +0300

    Unix --> Linux/Unix/MacOS
2023-07-31 16:31:03 +03:00
Stanislav Chzhen
5d900bdaa4 Pull request 1942: AG-24087-opts-root-cas
Squashed commit of the following:

commit 60db425504fce9743d46cfc0d155364fa5a1e77e
Merge: c589343e7 79306cb48
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jul 28 19:43:01 2023 +0300

    Merge branch 'master' into AG-24087-opts-root-cas

commit c589343e7b1db6f66c3890fd2caff755fcf92d08
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 27 14:59:23 2023 +0300

    all: upd dnsproxy
2023-07-28 19:50:53 +03:00
Ainar Garipov
79306cb48a Pull request 1944: 6049-block-ns-root
Updates #6049.

Squashed commit of the following:

commit 288a486b741b4dc57769bd5a0bdd67b4d75cc8c0
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 27 20:59:14 2023 +0300

    dnsforward: fix blocking of ns root
2023-07-27 21:06:51 +03:00
Eugene Burkov
300821a7fb Pull request 1943: 6046 Local PTR
Merge in DNS/adguard-home from 6046-local-ptr to master

Updates #6046.

Squashed commit of the following:

commit 3e90815f29173d2f68970278bd7b1b29cc0a4465
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 27 18:17:41 2023 +0300

    all: log changes

commit 7639f6f785670c15911fb3ca20abeb4e2b8f8582
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 27 17:40:49 2023 +0300

    all: fix 0 ttl ptr
2023-07-27 18:23:23 +03:00
Ainar Garipov
5f8fa006cf Pull request 1941: upd-chlog-deps
Squashed commit of the following:

commit 1ede57bd8778a18a61823e046f78464fca2ecd3c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 26 16:06:13 2023 +0300

    all: upd chlog, deps
2023-07-26 16:24:46 +03:00
Ainar Garipov
9f3af37eb3 Pull request 1940: upd-all
Squashed commit of the following:

commit 1119a81fdbc0c2bad7845931e25109fa47a8b07b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 26 13:02:24 2023 +0300

    all: upd i18n, svcs, tools
2023-07-26 13:10:23 +03:00
Ainar Garipov
698b963e11 Pull request 1937: imp-filter-upd
Squashed commit of the following:

commit 6ce649c06398cf8a6f8e1a90f560fa8205f6500e
Merge: 1c6327e5d 996c6b3ee
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 25 17:42:01 2023 +0300

    Merge branch 'master' into imp-filter-upd

commit 1c6327e5d4c04393abc5d4d3e4b8568d4c6eca23
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jul 21 17:32:47 2023 +0300

    all: imp code; use renameio/v2 consistently

commit 1669288c9b662d1310f83a4e0d3f1f60731188cd
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jul 21 16:26:17 2023 +0300

    all: add renameioutil; imp flt upd
2023-07-25 17:47:24 +03:00
Stanislav Chzhen
996c6b3ee3 Pull request 1938: AG-24132-rdns-ttl
Squashed commit of the following:

commit ba1e7b12cf7c0dc3ffab508d59c149f6c0930548
Merge: 8a94433ec ed86af582
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jul 25 13:43:25 2023 +0300

    Merge branch 'master' into AG-24132-rdns-ttl

commit 8a94433ec119d2158c166dd0222f57917908f3ad
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jul 24 19:30:21 2023 +0300

    all: imp docs

commit 4c1a3676b7be7ac4295c4e28550ddb6eb79a35d4
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jul 24 13:13:34 2023 +0300

    all: add rdns ttl
2023-07-25 14:16:26 +03:00
Ildar Kamalov
ed86af582a Pull request: fix invalid client tags after submit
Updates #6002

Squashed commit of the following:

commit 1129596eb460c0726f53c10ce1e4758833786984
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Jul 24 16:09:52 2023 +0300

    client: fix invalid client tags after submit
2023-07-25 09:59:45 +03:00
Eugene Burkov
ac2ecaf4f5 Pull request 1936: fix-addr-proc
Merge in DNS/adguard-home from fix-addr-proc to master

Squashed commit of the following:

commit eb48be2aa4ceb27aa95c55034b35486d8f8d3c9e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 21 14:43:04 2023 +0300

    dnsforward: rm redundant precaution

commit c58f1464e2c72b79724217f6ec1445da4a4ee5f5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 21 14:40:09 2023 +0300

    all: revise the addr proc crutch

commit 8a01be9e1abe70268eff996460d2e56132462887
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 20 20:05:54 2023 +0300

    dnsforward: fix tests panic
2023-07-24 15:11:11 +03:00
Stanislav Chzhen
f9daf72c7e Pull request 1934: AG-24191-blocker-languages
Squashed commit of the following:

commit 00294be24c45724a9b2c7a14226dec9f0bf6d24e
Merge: ef96a6759 84a2991ac
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 20 19:45:31 2023 +0300

    Merge branch 'master' into AG-24191-blocker-languages

commit ef96a6759b29c8d30c58dfc787aff573b5c7d5e6
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 20 19:32:29 2023 +0300

    scripts: imp docs

commit d89b4a4e6a49e6fa3f010e7b8dfedf55cea149f0
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 20 19:05:38 2023 +0300

    scripts: imp code

commit bd4d3a68187099691d91c2736bf816333b843f00
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 20 17:54:39 2023 +0300

    scripts: add blocker languages
2023-07-20 19:52:14 +03:00
Ainar Garipov
84a2991ac2 Pull request 1935: upd-pprof
Squashed commit of the following:

commit 71d8936bddcf2d2b293015d3091df72aa1333270
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 20 18:48:08 2023 +0300

    next/websvc: fix pprof disabling

commit 30cc75d1eb89f7422555c18ad474324ab55eb13b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 20 18:30:29 2023 +0300

    all: upd golibs; add pprof to next
2023-07-20 18:57:06 +03:00
Ainar Garipov
5be0e84719 Pull request 1933: upd-golibs
Squashed commit of the following:

commit 081d10e6909def3a075707e75dbd0c5f63f91903
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 20 14:17:01 2023 +0300

    aghnet: fix docs

commit 7433b72c0653cb33fe5ff810ae8a1346a6994f95
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 20 14:03:16 2023 +0300

    all: imp tests; upd golibs
2023-07-20 14:26:35 +03:00
107 changed files with 2975 additions and 1288 deletions

View File

@@ -1,7 +1,7 @@
'name': 'build'
'env':
'GO_VERSION': '1.19.11'
'GO_VERSION': '1.20.7'
'NODE_VERSION': '14'
'on':

View File

@@ -1,7 +1,7 @@
'name': 'lint'
'env':
'GO_VERSION': '1.19.11'
'GO_VERSION': '1.20.7'
'on':
'push':

View File

@@ -14,15 +14,108 @@ and this project adheres to
<!--
## [v0.108.0] - TBA
## [v0.107.35] - 2023-08-02 (APPROX.)
## [v0.107.37] - 2023-08-16 (APPROX.)
See also the [v0.107.35 GitHub milestone][ms-v0.107.35].
See also the [v0.107.37 GitHub milestone][ms-v0.107.37].
[ms-v0.107.35]: https://github.com/AdguardTeam/AdGuardHome/milestone/70?closed=1
[ms-v0.107.37]: https://github.com/AdguardTeam/AdGuardHome/milestone/72?closed=1
NOTE: Add new changes BELOW THIS COMMENT.
-->
### Added
- The ability to filter DNS HTTPS records including IPv4/v6 hints. ([#6053]).
- Two new metrics showing total number of responses from each upstream DNS
server and their average processing time in the Web UI ([#1453]).
- The ability to set the port for the `pprof` debug API, see configuration
changes below.
### Changed
- For non-A and non-AAAA requests, which has been filtered, the NODATA response
is returned if the blocking mode isn't set to `Null IP`. In previous versions
it returned NXDOMAIN response in such cases.
#### Configuration Changes
In this release, the schema version has changed from 24 to 25.
- Property `debug_pprof` which used to setup profiling HTTP handler, is now
moved to the new `pprof` object under `http` section. The new object contains
properties `enabled` and `port`:
```yaml
# BEFORE:
'debug_pprof': true
# AFTER:
'http':
'pprof':
'enabled': true
'port': 6060
```
Note that the new default `6060` is used as default. To rollback this change,
remove the new object `pprof`, set back `debug_pprof`, and change the
`schema_version` back to `24`.
### Fixed
- Address already in use when trying to install on port 3000 ([#6099]).
- Panic on using a single-slash filtering rule.
- Panic on shutting down while DNS requests are in process of filtering
([#5948]).
[#1453]: https://github.com/AdguardTeam/AdGuardHome/issues/1453
[#5948]: https://github.com/AdguardTeam/AdGuardHome/issues/5948
[#6053]: https://github.com/AdguardTeam/AdGuardHome/issues/6053
[#6099]: https://github.com/AdguardTeam/AdGuardHome/issues/6099
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
## [v0.107.36] - 2023-08-02
See also the [v0.107.36 GitHub milestone][ms-v0.107.36].
### Security
- Go version has been updated to prevent the possibility of exploiting the
CVE-2023-29409 Go vulnerability fixed in [Go 1.20.7][go-1.20.7].
### Deprecated
- Go 1.20 support. Future versions will require at least Go 1.21 to build.
### Fixed
- Inability to block queries for the root domain, such as `NS .` queries, using
the *Disallowed domains* feature on the *DNS settings* page ([#6049]). Users
who want to block `.` queries should use the `|.^` AdBlock rule or a similar
regular expression.
- Client hostnames not resolving when upstream server responds with zero-TTL
records ([#6046]).
[#6046]: https://github.com/AdguardTeam/AdGuardHome/issues/6046
[#6049]: https://github.com/AdguardTeam/AdGuardHome/issues/6049
[go-1.20.7]: https://groups.google.com/g/golang-announce/c/X0b6CsSAaYI/m/Efv5DbZ9AwAJ
[ms-v0.107.36]: https://github.com/AdguardTeam/AdGuardHome/milestone/71?closed=1
## [v0.107.35] - 2023-07-26
See also the [v0.107.35 GitHub milestone][ms-v0.107.35].
### Changed
- Improved reliability filtering-rule list updates on Unix systems.
### Fixed
- Occasional client information lookup failures that could lead to the DNS
@@ -39,9 +132,7 @@ NOTE: Add new changes BELOW THIS COMMENT.
[#6003]: https://github.com/AdguardTeam/AdGuardHome/issues/6003
[#6006]: https://github.com/AdguardTeam/AdGuardHome/issues/6006
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
[ms-v0.107.35]: https://github.com/AdguardTeam/AdGuardHome/milestone/70?closed=1
@@ -2258,11 +2349,13 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.35...HEAD
[v0.107.35]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.34...v0.107.35
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.37...HEAD
[v0.107.37]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.36...v0.107.37
-->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.34...HEAD
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.36...HEAD
[v0.107.36]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.35...v0.107.36
[v0.107.35]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.34...v0.107.35
[v0.107.34]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...v0.107.34
[v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...v0.107.33
[v0.107.32]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.31...v0.107.32

View File

@@ -54,7 +54,7 @@ code.
* [Getting Started](#getting-started)
* [Automated install (Unix)](#automated-install-linux-and-mac)
* [Automated install (Linux/Unix/MacOS/FreeBSD/OpenBSD)](#automated-install-linux-and-mac)
* [Alternative methods](#alternative-methods)
* [Guides](#guides)
* [API](#api)
@@ -79,7 +79,7 @@ code.
## <a href="#getting-started" id="getting-started" name="getting-started">Getting Started</a>
### <a href="#automated-install-linux-and-mac" id="automated-install-linux-and-mac" name="automated-install-linux-and-mac">Automated install (Unix)</a>
### <a href="#automated-install-linux-and-mac" id="automated-install-linux-and-mac" name="automated-install-linux-and-mac">Automated install (Linux/Unix/MacOS/FreeBSD/OpenBSD)</a>
To install with `curl` run the following command:
@@ -261,7 +261,7 @@ Run `make init` to prepare the development environment.
You will need this to build AdGuard Home:
* [Go](https://golang.org/dl/) v1.19 or later;
* [Go](https://golang.org/dl/) v1.20 or later;
* [Node.js](https://nodejs.org/en/download/) v10.16.2 or later;
* [npm](https://www.npmjs.com/) v6.14 or later;
* [yarn](https://yarnpkg.com/) v1.22.5 or later.

View File

@@ -7,7 +7,7 @@
# Make sure to sync any changes with the branch overrides below.
'variables':
'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:6.8'
'dockerGo': 'adguard/golang-ubuntu:7.0'
'stages':
- 'Build frontend':
@@ -272,7 +272,7 @@
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:6.8'
'dockerGo': 'adguard/golang-ubuntu:7.0'
# release-vX.Y.Z branches are the branches from which the actual final
# release is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@@ -287,4 +287,4 @@
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:6.8'
'dockerGo': 'adguard/golang-ubuntu:7.0'

View File

@@ -10,7 +10,7 @@
# Make sure to sync any changes with the branch overrides below.
'variables':
'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:6.8'
'dockerGo': 'adguard/golang-ubuntu:7.0'
'snapcraftChannel': 'edge'
'stages':
@@ -191,7 +191,7 @@
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:6.8'
'dockerGo': 'adguard/golang-ubuntu:7.0'
'snapcraftChannel': 'beta'
# release-vX.Y.Z branches are the branches from which the actual final
# release is built.
@@ -207,5 +207,5 @@
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:6.8'
'dockerGo': 'adguard/golang-ubuntu:7.0'
'snapcraftChannel': 'candidate'

View File

@@ -5,7 +5,7 @@
'key': 'AHBRTSPECS'
'name': 'AdGuard Home - Build and run tests'
'variables':
'dockerGo': 'adguard/golang-ubuntu:6.8'
'dockerGo': 'adguard/golang-ubuntu:7.0'
'stages':
- 'Tests':

View File

@@ -125,6 +125,8 @@
"top_clients": "Top clients",
"no_clients_found": "No clients found",
"general_statistics": "General statistics",
"top_upstreams": "Top upstreams",
"no_upstreams_data_found": "No upstreams data found",
"number_of_dns_query_days": "The number of DNS queries processed for the last {{count}} day",
"number_of_dns_query_days_plural": "The number of DNS queries processed for the last {{count}} days",
"number_of_dns_query_24_hours": "The number of DNS queries processed for the last 24 hours",
@@ -134,6 +136,7 @@
"enforced_save_search": "Enforced safe search",
"number_of_dns_query_to_safe_search": "The number of DNS requests to search engines for which Safe Search was enforced",
"average_processing_time": "Average processing time",
"processing_time": "Processing time",
"average_processing_time_hint": "Average time in milliseconds on processing a DNS request",
"block_domain_use_filters_and_hosts": "Block domains using filters and hosts files",
"filters_block_toggle_hint": "You can setup blocking rules in the <a>Filters</a> settings.",
@@ -158,6 +161,7 @@
"upstream_dns_configured_in_file": "Configured in {{path}}",
"test_upstream_btn": "Test upstreams",
"upstreams": "Upstreams",
"upstream": "Upstream",
"apply_btn": "Apply",
"disabled_filtering_toast": "Disabled filtering",
"enabled_filtering_toast": "Enabled filtering",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Jeste li sigurni da želite ukloniti \"{{key}}\" klijenta?",
"list_confirm_delete": "Jeste li sigurni da želite ukloniti ovaj popis?",
"auto_clients_title": "Runtime klijenti",
"auto_clients_desc": "Podaci na klijentu koji koriste AdGuard Home, ali se ne mijenjaju u postavkama",
"auto_clients_desc": "Informacije o IP adresama uređaja koji koriste ili bi mogli koristiti AdGuard Home. Ove informacije prikupljaju se iz nekoliko izvora, uključujući datoteke hostova, obrnuti DNS itd.",
"access_title": "Postavke pristupa",
"access_desc": "Ovdje možete konfigurirati pravila pristupa za AdGuard Home DNS poslužitelj",
"access_allowed_title": "Dopušteni klijenti",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Biztosan törölni szeretné a(z) \"{{key}}\" klienst?",
"list_confirm_delete": "Biztosan törölni kívánja ezt a listát?",
"auto_clients_title": "Futási idejű kliensek",
"auto_clients_desc": "Ezek az eszközök nem szerepelnek a fenntartott kliensek listáján, de használják az AdGuard Home-ot",
"auto_clients_desc": "Az AdGuard Home-ot használó vagy esetleg használó eszközök IP-címeire vonatkozó információk. Ezeket az információkat több forrásból gyűjtik, beleértve a hosts fájlokat, a fordított DNS-t stb.",
"access_title": "Hozzáférési beállítások",
"access_desc": "Itt konfigurálhatja az AdGuard Home DNS-kiszolgáló hozzáférési szabályait",
"access_allowed_title": "Engedélyezett kliensek",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "정말 클라이언트 '{{key}}'을(를) 삭제하시겠습니까?",
"list_confirm_delete": "정말로 이 목록을 제거하시겠습니까?",
"auto_clients_title": "런타임 클라이언트",
"auto_clients_desc": "AdGuard Home을 계속 사용할 수 있는 영구 클라이언트 목록에 없는 디바이스입니다",
"auto_clients_desc": "AdGuard Home을 사용 중이거나 사용할 수 있는 기기의 IP 주소에 대한 정보가 표시됩니다. 이 정보는 호스트 파일, 역방향 DNS 등 여러 소스에서 수집됩니다.",
"access_title": "접근 설정",
"access_desc": "여기에서 AdGuard Home DNS 서버에 대한 액세스 규칙을 설정할 수 있습니다",
"access_allowed_title": "허용된 클라이언트",

View File

@@ -186,7 +186,7 @@
"cancel_btn": "Annuleren",
"enter_name_hint": "Voeg naam toe",
"enter_url_or_path_hint": "Voer een URL in of het pad van de lijst",
"check_updates_btn": "Controleer op updates",
"check_updates_btn": "Controleren op updates",
"new_blocklist": "Nieuwe blokkeerlijst",
"new_allowlist": "Nieuwe toelatingslijst",
"edit_blocklist": "Blokkeerlijst beheren",
@@ -456,7 +456,7 @@
"access_settings_saved": "Toegangsinstellingen succesvol opgeslagen",
"updates_checked": "Een nieuwe versie van AdGuard Home is beschikbaar\n",
"updates_version_equal": "AdGuard Home is actueel",
"check_updates_now": "Controleer op updates",
"check_updates_now": "Nu controleren op updates",
"version_request_error": "Updatecontrole mislukt. Controleer je internetverbinding.",
"dns_privacy": "DNS Privacy",
"setup_dns_privacy_1": "<0>DNS-via-TLS:</0> Gebruik <1>{{address}}</1> string.",
@@ -573,7 +573,7 @@
"tags_title": "Labels",
"tags_desc": "Je kunt labels selecteren die overeenkomen met de client. Labels kunnen worden opgenomen in de filterregels om ze \n nauwkeuriger toe te passen. <0>Meer informatie</0>.",
"form_select_tags": "Client tags selecteren",
"check_title": "Controleer de filtering",
"check_title": "De filtering controleren",
"check_desc": "Controleren of een hostnaam wordt gefilterd.",
"check": "Controleren",
"form_enter_host": "Voer een hostnaam in",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Sunteți sigur că doriți să ștergeți clientul \"{{key}}\"?",
"list_confirm_delete": "Sigur doriți să ștergeți această listă?",
"auto_clients_title": "Clienți runtime",
"auto_clients_desc": "Dispozitivele care nu se află pe lista de clienți Persistent care pot utiliza în continuare AdGuard Home",
"auto_clients_desc": "Informații despre adresele IP ale dispozitivelor care utilizează sau pot utiliza AdGuard Home. Aceste informații sunt colectate din mai multe surse, inclusiv din fișiere hosts, DNS inversat etc.",
"access_title": "Setări de acces",
"access_desc": "Aici puteți configura regulile de acces pentru serverul DNS AdGuard Home",
"access_allowed_title": "Clienți autorizați",

View File

@@ -435,6 +435,7 @@
"updates_checked": "ඇඩ්ගාර්ඩ් හෝම් හි නව අනුවාදයක් තිබේ",
"updates_version_equal": "ඇඩ්ගාර්ඩ් හෝම් යාවත්කාලීනයි",
"check_updates_now": "දැන් යාවත්කාල පරීක්‍ෂා කරන්න",
"version_request_error": "යාවත්කාලීන පරීක්‍ෂාවට අසමත් විය. ඔබගේ අන්තර්ජාල සම්බන්ධතාවය පරීක්‍ෂා කරන්න.",
"dns_privacy": "ව.නා.ප. රහස්‍යතා",
"setup_dns_privacy_1": "<0>TLS-මගින්-ව.නා.ප.</0> සඳහා <1>{{address}}</1>.",
"setup_dns_privacy_2": "<0>HTTPS-මගින්-ව.නා.ප.</0> සඳහා <1>{{address}}</1>.",
@@ -453,7 +454,9 @@
"setup_dns_notice": "ඔබට <1>HTTPS-මගින්-ව.නා.ප.</1> හෝ <1>DNS-මගින්-ව.නා.ප.</1> භාවිතයට ඇඩ්ගාර්ඩ් හෝම් සැකසුම් තුළ <0>සංකේතනය වින්‍යාසගත</0> කළ යුතුය.",
"rewrite_added": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම සාර්ථකව එකතු කෙරිණි",
"rewrite_deleted": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම ඉවත් කෙරිණි",
"rewrite_add": "ව.නා.ප. නැවත ලිවීමක් එකතු කරන්න",
"rewrite_updated": "ව.නා.ප. නැවත ලිවීම සාර්ථකව යාවත්කාලීන කෙරිණි",
"rewrite_add": "ව.නා.ප. නැවත ලිවීමක් යොදන්න",
"rewrite_edit": "ව.නා.ප. නැවත ලිවීම සංස්කරණය",
"rewrite_not_found": "ව.නා.ප. නැවත ලිවීම් හමු නොවිණි",
"rewrite_confirm_delete": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?",
"rewrite_desc": "නිශ්චිත වසම් නාමයක් සඳහා අභිරුචි ව.නා.ප. ප්‍රතිචාර පහසුවෙන් වින්‍යාසගත කිරීමට ඉඩ දෙයි.",
@@ -611,9 +614,12 @@
"safe_browsing": "ආරක්‍ෂිත පිරික්සුම",
"served_from_cache": "{{value}} <i>(නිහිතයෙන් ගැනිණි)</i>",
"form_error_password_length": "මුරපදය අවම වශයෙන් අකුරු {{value}} ක් දිගු විය යුතුමයි",
"anonymizer_notification": "<0>සටහන:</0> අ.ජා.කෙ. නිර්නාමිකකරණය සබලයි. ඔබට එය <1>පොදු සැකසුම්</1> හරහා අබල කිරීමට හැකිය .",
"confirm_dns_cache_clear": "ඔබට ව.නා.ප. නිහිතය හිස් කිරීමට වුවමනාද?",
"cache_cleared": "ව.නා.ප. නිහිතය හිස් කෙරිණි",
"clear_cache": "නිහිතය මකන්න",
"make_static": "ස්ථිතික කරන්න",
"theme_auto_desc": "ස්වයං (උපාංගයේ වර්ණ පරිපාටිය මත පදනම්ව)",
"theme_dark_desc": "අඳුරු තේමාව",
"theme_light_desc": "දීප්ත තේමාව",
"disable_for_seconds": "තත්පර {{count}} ක්",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Naozaj chcete vymazať \"{{key}}\" klienta?",
"list_confirm_delete": "Naozaj chcete vymazať tento zoznam?",
"auto_clients_title": "Runtime klienti",
"auto_clients_desc": "Zariadenia, ktoré nie sú na zozname trvalých klientov, ktorí môžu stále používať AdGuard Home",
"auto_clients_desc": "Informácie o IP adresách zariadení, ktoré používajú alebo môžu používať AdGuard Home. Tieto informácie sa získavajú z viacerých zdrojov vrátane súborov hosts, reverzného DNS atď.",
"access_title": "Nastavenia prístupu",
"access_desc": "Tu môžete konfigurovať pravidlá prístupu pre server DNS AdGuard Home.",
"access_allowed_title": "Povolení klienti",

View File

@@ -167,6 +167,7 @@
"enabled_parental_toast": "Uključena roditeljska kontrola",
"disabled_safe_search_toast": "Isključena sigurna pretraga",
"enabled_save_search_toast": "Uključeno sigurno pretraživanje",
"updated_save_search_toast": "Ažurirane postavke bezbedne pretrage",
"enabled_table_header": "Uključeno",
"name_table_header": "Ime",
"list_url_table_header": "URL do liste",
@@ -256,12 +257,12 @@
"query_log_cleared": "Dnevnik unosa je uspešno očišćen",
"query_log_updated": "Dnevnik zapisa je uspešno ažuriran",
"query_log_clear": "Očisti dnevnike unosa",
"query_log_retention": "Zadržavanje dnevnika unosa",
"query_log_retention": "Rotacija evidencija upita",
"query_log_enable": "Uključi dnevnik",
"query_log_configuration": "Konfiguracija dnevnika",
"query_log_disabled": "Dnevnik unosa je isključen ali se može konfigurisati u <0>postavkama</0>",
"query_log_strict_search": "Koristi duple navodnike za striktnu pretragu",
"query_log_retention_confirm": "Jeste li sigurni da želite da promenite zadržavanje dnevnika unosa? Ako smanjite vrednost intervala, neki podaci će biti izgubljeni",
"query_log_retention_confirm": "Želite li zaista da promenite rotaciju evidencije upita? Ako smanjite vrednost intervala, neki podaci će biti izgubljeni",
"anonymize_client_ip": "Anonimizuj IP klijenta",
"anonymize_client_ip_desc": "Ne čuvaj punu IP adresu klijenta u dnevnicima i statistikama",
"dns_config": "Konfiguracija DNS servera",
@@ -290,6 +291,8 @@
"rate_limit": "Ograničenje brzine",
"edns_enable": "Uključi EDNS Client Subnet",
"edns_cs_desc": "Dodajte opciju podmreži EDNS klijenta (ECS) uzvodnim zahtevima i evidentirajte vrednosti koje klijenti šalju u evidenciji upita.",
"edns_use_custom_ip": "Koristi prilagođeni IP za EDNS",
"edns_use_custom_ip_desc": "Dozvoli korišćenje prilagođenog IP-a za EDNS",
"rate_limit_desc": "Broj zahteva u sekundi dozvoljen po klijentu. Postavljanje na 0 znači da nema ograničenja.",
"blocking_ipv4_desc": "IP adresa koja će biti vraćena za blokirane zahteve",
"blocking_ipv6_desc": "IP adresa koja će biti vraćena za blokirane AAAA zahteve",
@@ -441,7 +444,7 @@
"client_confirm_delete": "Jeste li sigurni da želite da izbrišete klijenta \"{{key}}\"?",
"list_confirm_delete": "Jeste li sigurni da želite da izbrišete ovu listu?",
"auto_clients_title": "Klijenti (runtime)",
"auto_clients_desc": "Uređaji koji nisu na listi upornih klijenata koji i dalje mogu da koriste AdGuard Home",
"auto_clients_desc": "Podaci o klijentima koji koriste AdGuard Home, ali nisu sačuvani u konfiguraciji",
"access_title": "Postavke pristupa",
"access_desc": "Ovde možete konfigurisati pravila pristupa za AdGuard Home DNS server",
"access_allowed_title": "Dozvoljeni klijenti",
@@ -525,6 +528,10 @@
"statistics_retention_confirm": "Jeste li sigurni da želite da promenite zadržavanje statistike? Ako smanjite vrednost intervala, neki podaci će biti izgubljeni",
"statistics_cleared": "Statistika je uspešno očišćena",
"statistics_enable": "Uključi statistiku",
"ignore_domains": "Zanemari domene (razdvojene novom linijom)",
"ignore_domains_title": "Zanemareni domeni",
"ignore_domains_desc_stats": "Upiti za ove domene nisu upisani u statistiku",
"ignore_domains_desc_query": "Upiti za ove domene nisu upisani u evidenciju upita",
"interval_hours": "{{count}} čas",
"interval_hours_plural": "{{count}} časova",
"filters_configuration": "Konfiguracija filtera",
@@ -645,5 +652,29 @@
"confirm_dns_cache_clear": "Želite li zaista da obrišite DNS keš?",
"cache_cleared": "DNS keš je uspešno očišćen",
"clear_cache": "Obriši keš memoriju",
"protection_section_label": "Zaštita"
"make_static": "Učini statičnim",
"theme_auto_desc": "Automatski (na osnovu šeme boja uređaja)",
"theme_dark_desc": "Tamna tema",
"theme_light_desc": "Svetla tema",
"disable_for_seconds": "Za {{count}} sekund",
"disable_for_seconds_plural": "Za {{count}} sekundi",
"disable_for_minutes": "Za {{count}} minut",
"disable_for_minutes_plural": "Za {{count}} minuta",
"disable_for_hours": "Za {{count}} sat",
"disable_for_hours_plural": "Za {{count}} sati",
"disable_until_tomorrow": "Do sutra",
"disable_notify_for_seconds": "Isključi zaštitu na {{count}} sekund",
"disable_notify_for_seconds_plural": "Isključi zaštitu na {{count}} sekundi",
"disable_notify_for_minutes": "Isključi zaštitu na {{count}} minut",
"disable_notify_for_minutes_plural": "Isključi zaštitu na {{count}} minuta",
"disable_notify_for_hours": "Isključi zaštitu na {{count}} sat",
"disable_notify_for_hours_plural": "Isključi zaštitu na {{count}} sati",
"disable_notify_until_tomorrow": "Isključi zaštitu do sutra",
"enable_protection_timer": "Zaštita će biti uključena u {{time}}",
"custom_retention_input": "Unesite zadržavanje u časovima",
"custom_rotation_input": "Unesite rotaciju u časovima",
"protection_section_label": "Zaštita",
"log_and_stats_section_label": "Evidencija upita i statistika",
"ignore_query_log": "Zanemari ovog klijenta u evidenciji upita",
"ignore_statistics": "Zanemari ovog klijenta u statističkim podacima"
}

View File

@@ -172,6 +172,7 @@
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
"dns_over_quic": "DNS-over-QUIC",
"form_enter_rate_limit": "ป้อนขีดจำกัดอัตรา",
"rate_limit": "จำกัดอัตรา",
"edns_enable": "เปิดใช้งานซับเน็ตไคลเอ็นต์ EDNS",
@@ -392,6 +393,7 @@
"show_processed_responses": "การประมวลผล",
"blocked_adult_websites": "ถูกปิดกั้นโดยการควบคุมของผู้ปกครอง",
"safe_search": "ค้นหาอย่างปลอดภัย",
"blocklist": "บัญชีดำ",
"filter_category_other": "อื่น ๆ",
"parental_control": "ควบคุมโดยผู้ปกครอง"
}

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Ви впевнені, що хочете видалити клієнта «{{key}}»?",
"list_confirm_delete": "Ви впевнені, що хочете видалити цей список?",
"auto_clients_title": "Runtime-клієнти",
"auto_clients_desc": "Клієнти, які використовують AdGuard Home, незалежно від того, чи збережені вони в списку постійних",
"auto_clients_desc": "Інформація про IP-адреси пристроїв, які використовують або можуть використовувати AdGuard Home. Ця інформація збирається з кількох джерел, зокрема з файлів hosts, зворотного DNS тощо.",
"access_title": "Налаштування доступу",
"access_desc": "Тут ви можете налаштувати правила доступу для DNS-сервера AdGuard Home",
"access_allowed_title": "Дозволені клієнти",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Bạn có chắc chắn muốn xóa máy khách \"{{key}}\" không?",
"list_confirm_delete": "Bạn có muốn xóa bộ lọc này?",
"auto_clients_title": "Máy khách (thời gian chạy)",
"auto_clients_desc": "Các thiết bị không có trong danh sách khách hàng ổn định vẫn có thể sử dụng AdGuard Home",
"auto_clients_desc": "Thông tin về địa chỉ IP của thiết bị đang sử dụng hoặc có thể sử dụng AdGuard Home. Thông tin này được thu thập từ nhiều nguồn, bao gồm tệp máy chủ, DNS ngược, v.v.",
"access_title": "Cài đặt truy cập",
"access_desc": "Tại đây bạn có thể định cấu hình quy tắc truy cập cho máy chủ AdGuard Home DNS",
"access_allowed_title": "Máy chủ được phép",

View File

@@ -211,6 +211,10 @@
"example_upstream_doq": "加密 <0>DNS-over-QUIC</0>",
"example_upstream_sdns": "您可以使透過 <0>DNS Stamps</0> 來解析 <1>DNSCrypt</1> 或 <2>DNS-over-HTTPS</2>",
"example_upstream_tcp": "一般 DNS透過 TCP",
"example_upstream_regular_port": "一般 DNS透過 UDP連接埠",
"example_upstream_udp": "一般 DNS透過 UDP主機名稱",
"example_upstream_tcp_port": "一般 DNS透過 TCP連接埠",
"example_upstream_tcp_hostname": "一般 DNS透過 TCP主機名稱",
"all_lists_up_to_date_toast": "所有清單已更新至最新",
"dns_test_ok_toast": "設定中的 DNS 上游運作正常",
"dns_test_not_ok_toast": "DNS 設定中的 \"{{key}}\" 出現錯誤,請確認是否正確輸入",

View File

@@ -138,9 +138,9 @@
"block_domain_use_filters_and_hosts": "透過過濾器和主機檔案封鎖網域",
"filters_block_toggle_hint": "您可在<a>過濾器</a>設定中設置封鎖規則。",
"use_adguard_browsing_sec": "使用 AdGuard 瀏覽安全網路服務",
"use_adguard_browsing_sec_hint": "AdGuard Home 將檢查該網域是否被瀏覽安全網路服務封鎖。它將使用友好的隱私查找應用程式介面API以執行檢查僅域名 SHA256 雜湊的短前綴被傳送到該伺服器。",
"use_adguard_browsing_sec_hint": "AdGuard Home 將檢查該網域是否被瀏覽安全網路服務封鎖。它將使用對隱私友好的查找應用程式介面API以執行檢查僅域名 SHA256 雜湊的短前綴被傳送到該伺服器。",
"use_adguard_parental": "使用 AdGuard 家長控制之網路服務",
"use_adguard_parental_hint": "AdGuard Home 將檢查網域是否包含成人資料。它使用如同瀏覽安全網路服務一樣之友好的隱私應用程式介面API。",
"use_adguard_parental_hint": "AdGuard Home 將檢查網域是否包含成人資料。它使用如同瀏覽安全網路服務一樣之對隱私友好的應用程式介面API。",
"enforce_safe_search": "使用安全搜尋",
"enforce_save_search_hint": "AdGuard Home 將在下列的搜尋引擎Google、YouTube、Bing、DuckDuckGo、Yandex 和 Pixabay 中強制執行安全搜尋。",
"no_servers_specified": "無已明確指定的伺服器",

View File

@@ -56,6 +56,8 @@ export const getStats = () => async (dispatch) => {
top_clients: topClientsWithInfo,
top_queried_domains: normalizeTopStats(stats.top_queried_domains),
avg_processing_time: secondsToMilliseconds(stats.avg_processing_time),
top_upstreams_responses: normalizeTopStats(stats.top_upstreams_responses),
top_upstrems_avg_time: normalizeTopStats(stats.top_upstreams_avg_time),
};
dispatch(getStatsSuccess(normalizedStats));

View File

@@ -0,0 +1,79 @@
import React from 'react';
import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import round from 'lodash/round';
import { withTranslation, Trans } from 'react-i18next';
import Card from '../ui/Card';
import DomainCell from './DomainCell';
const TimeCell = ({ value }) => {
if (!value) {
return '';
}
const valueInMilliseconds = round(value * 1000);
return (
<div className="logs__row o-hidden">
<span className="logs__text logs__text--full" title={valueInMilliseconds}>
{valueInMilliseconds}&nbsp;ms
</span>
</div>
);
};
TimeCell.propTypes = {
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
};
const UpstreamAvgTime = ({
t,
refreshButton,
topUpstreamsAvgTime,
subtitle,
}) => (
<Card
title={t('average_processing_time')}
subtitle={subtitle}
bodyType="card-table"
refresh={refreshButton}
>
<ReactTable
data={topUpstreamsAvgTime.map(({ name: domain, count }) => ({
domain,
count,
}))}
columns={[
{
Header: <Trans>upstream</Trans>,
accessor: 'domain',
Cell: DomainCell,
},
{
Header: <Trans>processing_time</Trans>,
accessor: 'count',
maxWidth: 190,
Cell: TimeCell,
},
]}
showPagination={false}
noDataText={t('no_upstreams_data_found')}
minRows={6}
defaultPageSize={100}
className="-highlight card-table-overflow--limited stats__table"
/>
</Card>
);
UpstreamAvgTime.propTypes = {
topUpstreamsAvgTime: PropTypes.array.isRequired,
refreshButton: PropTypes.node.isRequired,
subtitle: PropTypes.string.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(UpstreamAvgTime);

View File

@@ -0,0 +1,81 @@
import React from 'react';
import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import { withTranslation, Trans } from 'react-i18next';
import Card from '../ui/Card';
import Cell from '../ui/Cell';
import DomainCell from './DomainCell';
import { getPercent } from '../../helpers/helpers';
import { STATUS_COLORS } from '../../helpers/constants';
const CountCell = (totalBlocked) => (
function cell(row) {
const { value } = row;
const percent = getPercent(totalBlocked, value);
return (
<Cell
value={value}
percent={percent}
color={STATUS_COLORS.green}
/>
);
}
);
const getTotalUpstreamRequests = (stats) => {
let total = 0;
stats.forEach(({ count }) => { total += count; });
return total;
};
const UpstreamResponses = ({
t,
refreshButton,
topUpstreamsResponses,
subtitle,
}) => (
<Card
title={t('top_upstreams')}
subtitle={subtitle}
bodyType="card-table"
refresh={refreshButton}
>
<ReactTable
data={topUpstreamsResponses.map(({ name: domain, count }) => ({
domain,
count,
}))}
columns={[
{
Header: <Trans>upstream</Trans>,
accessor: 'domain',
Cell: DomainCell,
},
{
Header: <Trans>requests_count</Trans>,
accessor: 'count',
maxWidth: 190,
Cell: CountCell(getTotalUpstreamRequests(topUpstreamsResponses)),
},
]}
showPagination={false}
noDataText={t('no_upstreams_data_found')}
minRows={6}
defaultPageSize={100}
className="-highlight card-table-overflow--limited stats__table"
/>
</Card>
);
UpstreamResponses.propTypes = {
topUpstreamsResponses: PropTypes.array.isRequired,
refreshButton: PropTypes.node.isRequired,
subtitle: PropTypes.string.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(UpstreamResponses);

View File

@@ -21,6 +21,8 @@ import PageTitle from '../ui/PageTitle';
import Loading from '../ui/Loading';
import './Dashboard.css';
import Dropdown from '../ui/Dropdown';
import UpstreamResponses from './UpstreamResponses';
import UpstreamAvgTime from './UpstreamAvgTime';
const Dashboard = ({
getAccessList,
@@ -136,12 +138,12 @@ const Dashboard = ({
<PageTitle title={t('dashboard')} containerClass="page-title--dashboard">
<div className="page-title__protection">
<button
type="button"
className={buttonClass}
onClick={() => {
toggleProtection(protectionEnabled);
}}
disabled={processingProtection}
type="button"
className={buttonClass}
onClick={() => {
toggleProtection(protectionEnabled);
}}
disabled={processingProtection}
>
{protectionDisabledDuration
? `${t('enable_protection_timer')} ${getRemaningTimeText(protectionDisabledDuration)}`
@@ -160,9 +162,9 @@ const Dashboard = ({
</Dropdown>}
</div>
<button
type="button"
className="btn btn-outline-primary btn-sm"
onClick={getAllStats}
type="button"
className="btn btn-outline-primary btn-sm"
onClick={getAllStats}
>
<Trans>refresh_statics</Trans>
</button>
@@ -185,53 +187,67 @@ const Dashboard = ({
</div>
)}
<Statistics
interval={msToDays(stats.interval)}
dnsQueries={stats.dnsQueries}
blockedFiltering={stats.blockedFiltering}
replacedSafebrowsing={stats.replacedSafebrowsing}
replacedParental={stats.replacedParental}
numDnsQueries={stats.numDnsQueries}
numBlockedFiltering={stats.numBlockedFiltering}
numReplacedSafebrowsing={stats.numReplacedSafebrowsing}
numReplacedParental={stats.numReplacedParental}
refreshButton={refreshButton}
interval={msToDays(stats.interval)}
dnsQueries={stats.dnsQueries}
blockedFiltering={stats.blockedFiltering}
replacedSafebrowsing={stats.replacedSafebrowsing}
replacedParental={stats.replacedParental}
numDnsQueries={stats.numDnsQueries}
numBlockedFiltering={stats.numBlockedFiltering}
numReplacedSafebrowsing={stats.numReplacedSafebrowsing}
numReplacedParental={stats.numReplacedParental}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<Counters
subtitle={subtitle}
refreshButton={refreshButton}
subtitle={subtitle}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<Clients
subtitle={subtitle}
dnsQueries={stats.numDnsQueries}
topClients={stats.topClients}
clients={dashboard.clients}
autoClients={dashboard.autoClients}
refreshButton={refreshButton}
processingAccessSet={access.processingSet}
disallowedClients={access.disallowed_clients}
subtitle={subtitle}
dnsQueries={stats.numDnsQueries}
topClients={stats.topClients}
clients={dashboard.clients}
autoClients={dashboard.autoClients}
refreshButton={refreshButton}
processingAccessSet={access.processingSet}
disallowedClients={access.disallowed_clients}
/>
</div>
<div className="col-lg-6">
<QueriedDomains
subtitle={subtitle}
dnsQueries={stats.numDnsQueries}
topQueriedDomains={stats.topQueriedDomains}
refreshButton={refreshButton}
subtitle={subtitle}
dnsQueries={stats.numDnsQueries}
topQueriedDomains={stats.topQueriedDomains}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<BlockedDomains
subtitle={subtitle}
topBlockedDomains={stats.topBlockedDomains}
blockedFiltering={stats.numBlockedFiltering}
replacedSafebrowsing={stats.numReplacedSafebrowsing}
replacedSafesearch={stats.numReplacedSafesearch}
replacedParental={stats.numReplacedParental}
refreshButton={refreshButton}
subtitle={subtitle}
topBlockedDomains={stats.topBlockedDomains}
blockedFiltering={stats.numBlockedFiltering}
replacedSafebrowsing={stats.numReplacedSafebrowsing}
replacedSafesearch={stats.numReplacedSafesearch}
replacedParental={stats.numReplacedParental}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<UpstreamResponses
subtitle={subtitle}
topUpstreamsResponses={stats.topUpstreamsResponses}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<UpstreamAvgTime
subtitle={subtitle}
topUpstreamsAvgTime={stats.topUpstreamsAvgTime}
refreshButton={refreshButton}
/>
</div>
</div>}

View File

@@ -57,7 +57,7 @@ const ClientsTable = ({
};
const handleSubmit = (values) => {
const config = values;
const config = { ...values };
if (values) {
if (values.blocked_services) {

View File

@@ -1,25 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import LogsSearchLink from './LogsSearchLink';
import { formatNumber } from '../../helpers/helpers';
const Cell = ({
value, percent, color, search,
}) => <div className="stats__row">
<div className="stats__row-value mb-1">
<strong><LogsSearchLink search={search}>{formatNumber(value)}</LogsSearchLink></strong>
<small className="ml-3 text-muted">{percent}%</small>
value,
percent,
color,
search,
}) => (
<div className="stats__row">
<div className="stats__row-value mb-1">
<strong>
{search ? (
<LogsSearchLink search={search}>
{formatNumber(value)}
</LogsSearchLink>
) : (
formatNumber(value)
)}
</strong>
<small className="ml-3 text-muted">{percent}%</small>
</div>
<div className="progress progress-xs">
<div
className="progress-bar"
style={{
width: `${percent}%`,
backgroundColor: color,
}}
/>
</div>
</div>
<div className="progress progress-xs">
<div
className="progress-bar"
style={{
width: `${percent}%`,
backgroundColor: color,
}}
/>
</div>
</div>;
);
Cell.propTypes = {
value: PropTypes.number.isRequired,

View File

@@ -1,5 +1,5 @@
{
"timeUpdated": "2023-07-15T00:10:47.501Z",
"timeUpdated": "2023-08-01T00:10:42.759Z",
"categories": {
"0": "audio_video_player",
"1": "comments",
@@ -42,7 +42,8 @@
"name": "1822direkt.de",
"categoryId": 8,
"url": "https://www.1822direkt.de/",
"companyId": null
"companyId": "1822direkt",
"source": "AdGuard"
},
"1dmp.io": {
"name": "1DMP",
@@ -69,16 +70,18 @@
"companyId": "dentsu_aegis_network"
},
"1und1": {
"name": "1&1 Internet",
"name": "1&1 IONOS",
"categoryId": 8,
"url": null,
"companyId": null
"url": "http://www.ionos.com/",
"companyId": "1und1",
"source": "AdGuard"
},
"24-ads.com": {
"name": "24-ADS GmbH",
"name": "24-ADS",
"categoryId": 4,
"url": "http://www.24-ads.com/",
"companyId": null
"companyId": "24-ads.com",
"source": "AdGuard"
},
"24_7": {
"name": "[24]7",
@@ -93,10 +96,11 @@
"companyId": "24log"
},
"24smi": {
"name": "24СМИ",
"name": "24SMI",
"categoryId": 8,
"url": "https://24smi.org/",
"companyId": null
"companyId": "24smi",
"source": "AdGuard"
},
"2leep": {
"name": "2leep",
@@ -127,13 +131,15 @@
"name": "4Chan",
"categoryId": 8,
"url": "https://www.4chan.org/",
"companyId": null
"companyId": "4chan",
"source": "AdGuard"
},
"4finance_com": {
"name": "4finance.com",
"name": "4finance",
"categoryId": 2,
"url": "http://4finance.com/",
"companyId": null
"url": "https://4finance.com/",
"companyId": "4finance",
"source": "AdGuard"
},
"4w_marketplace": {
"name": "4w Marketplace",
@@ -179,10 +185,11 @@
"source": "AdGuard"
},
"7tv.de": {
"name": "7tv.de",
"name": "7tv.app",
"categoryId": 0,
"url": "https://www.7tv.de/",
"companyId": null
"url": "https://www.7tv.app/",
"companyId": "7tv",
"source": "AdGuard"
},
"888media": {
"name": "888media",
@@ -2554,7 +2561,7 @@
"name": "Microsoft App Center",
"categoryId": 5,
"url": "https://appcenter.ms/",
"companyId": null,
"companyId": "microsoft",
"source": "AdGuard"
},
"appcues": {
@@ -3925,7 +3932,7 @@
"name": "Button",
"categoryId": 4,
"url": "https://www.usebutton.com/",
"companyId": null,
"companyId": "button",
"source": "AdGuard"
},
"buysellads": {
@@ -5276,7 +5283,7 @@
"name": "Crashlytics",
"categoryId": 101,
"url": "https://crashlytics.com/",
"companyId": null,
"companyId": "google",
"source": "AdGuard"
},
"crazy_egg": {
@@ -6427,6 +6434,13 @@
"url": "http://www.amazon.com/",
"companyId": "amazon_associates"
},
"electronic_arts": {
"name": "Electronic Arts",
"categoryId": 2,
"url": "https://www.ea.com/",
"companyId": "electronic_arts",
"source": "AdGuard"
},
"element": {
"name": "Element",
"categoryId": 7,
@@ -7014,6 +7028,13 @@
"url": null,
"companyId": null
},
"farlight_pte_ltd": {
"name": "Farlight Pte Ltd.",
"categoryId": 8,
"url": "https://farlightgames.com/",
"companyId": "farlight",
"source": "AdGuard"
},
"fastly_insights": {
"name": "Fastly Insights",
"categoryId": 6,
@@ -8655,7 +8676,7 @@
"name": "HockeyApp",
"categoryId": 101,
"url": "https://hockeyapp.net/",
"companyId": null,
"companyId": "microsoft",
"source": "AdGuard"
},
"hoholikik.club": {
@@ -16729,6 +16750,13 @@
"url": "http://www.sundaysky.com/",
"companyId": "sundaysky"
},
"supercell": {
"name": "Supercell",
"categoryId": 2,
"url": "https://supercell.com/",
"companyId": "supercell",
"source": "AdGuard"
},
"supercounters": {
"name": "SuperCounters",
"categoryId": 6,
@@ -19318,10 +19346,11 @@
"companyId": "xapads"
},
"xen-media.com": {
"name": "xen-media.com",
"name": "Xen Media",
"categoryId": 11,
"url": null,
"companyId": null
"url": "https://www.xenmedia.net/",
"companyId": "xenmedia",
"source": "AdGuard"
},
"xfreeservice.com": {
"name": "xfreeservice.com",
@@ -19332,8 +19361,9 @@
"xhamster": {
"name": "xHamster",
"categoryId": 3,
"url": null,
"companyId": null
"url": "https://xhamster.com/",
"companyId": "xhamster",
"source": "AdGuard"
},
"xing": {
"name": "Xing",
@@ -19348,10 +19378,11 @@
"companyId": "exoclick"
},
"xnxx_cdn": {
"name": "xnxx CDN",
"name": "XNXX",
"categoryId": 9,
"url": "https://www.xnxx.com",
"companyId": null
"companyId": "xnxx",
"source": "AdGuard"
},
"xplosion": {
"name": "xplosion",
@@ -19366,16 +19397,18 @@
"companyId": "matomy_media"
},
"xvideos_com": {
"name": "xvideos.com",
"name": "Xvideos",
"categoryId": 8,
"url": null,
"companyId": null
"url": "https://www.xvideos.com",
"companyId": "xvideos",
"source": "AdGuard"
},
"xxxlshop.de": {
"name": "xxxlshop.de",
"name": "XXXLutz",
"categoryId": 8,
"url": "https://www.xxxlshop.de/",
"companyId": null
"url": "https://www.xxxlutz.de/",
"companyId": "xxxlutz",
"source": "AdGuard"
},
"xxxlutz": {
"name": "XXXLutz",
@@ -19387,7 +19420,8 @@
"name": "Yabbi",
"categoryId": 4,
"url": "https://yabbi.me/",
"companyId": null
"companyId": "yabbi",
"source": "AdGuard"
},
"yabuka": {
"name": "Yabuka",
@@ -19649,10 +19683,11 @@
"companyId": "yomedia"
},
"yoochoose.net": {
"name": "YOOCHOOSE",
"name": "Ibexa Personalizaton Software",
"categoryId": 4,
"url": "https://yoochoose.com/",
"companyId": null
"url": "https://yoochoose.net/",
"companyId": "ibexa",
"source": "AdGuard"
},
"yotpo": {
"name": "Yotpo",
@@ -19687,8 +19722,9 @@
"youporn": {
"name": "YouPorn",
"categoryId": 3,
"url": null,
"companyId": null
"url": "https://www.youporn.com/",
"companyId": "youporn",
"source": "AdGuard"
},
"youtube": {
"name": "YouTube",
@@ -19826,7 +19862,8 @@
"name": "ZeusClicks",
"categoryId": 4,
"url": "http://zeusclicks.com/",
"companyId": null
"companyId": "zeusclicks",
"source": "AdGuard"
},
"ziff_davis": {
"name": "Ziff Davis",
@@ -19844,7 +19881,8 @@
"name": "Zimbio",
"categoryId": 8,
"url": "http://www.zimbio.com/",
"companyId": null
"companyId": "livinglymedia",
"source": "AdGuard"
},
"zippyshare_widget": {
"name": "Zippyshare Widget",
@@ -21450,6 +21488,9 @@
"ekomi.de": "ekomi",
"elasticad.net": "elastic_ad",
"elasticbeanstalk.com": "elastic_beanstalk",
"cloudcell.com": "electronic_arts",
"ea.com": "electronic_arts",
"eamobile.com": "electronic_arts",
"element.io": "element",
"riot.im": "element",
"elicitapp.com": "elicit",
@@ -21570,6 +21611,7 @@
"thefancy.com": "fancy_widget",
"d1q7pknmpq2wkm.cloudfront.net": "fanplayr",
"fap.to": "fap.to",
"farlightgames.com": "farlight_pte_ltd",
"fastly-insights.com": "fastly_insights",
"fastly.net": "fastlylb.net",
"fastlylb.net": "fastlylb.net",
@@ -24146,6 +24188,8 @@
"sumo.com": "sumome",
"sumome.com": "sumome",
"sundaysky.com": "sundaysky",
"supercell.com": "supercell",
"supercellsupport.com": "supercell",
"supercounters.com": "supercounters",
"superfastcdn.com": "superfastcdn.com",
"socdm.com": "supership",

View File

@@ -58,6 +58,8 @@ const stats = handleActions(
num_replaced_safebrowsing: numReplacedSafebrowsing,
num_replaced_safesearch: numReplacedSafesearch,
avg_processing_time: avgProcessingTime,
top_upstreams_responses: topUpstreamsResponses,
top_upstrems_avg_time: topUpstreamsAvgTime,
} = payload;
const newState = {
@@ -77,6 +79,8 @@ const stats = handleActions(
numReplacedSafebrowsing,
numReplacedSafesearch,
avgProcessingTime,
topUpstreamsResponses,
topUpstreamsAvgTime,
};
return newState;

33
go.mod
View File

@@ -1,11 +1,11 @@
module github.com/AdguardTeam/AdGuardHome
go 1.19
go 1.20
require (
github.com/AdguardTeam/dnsproxy v0.52.0
github.com/AdguardTeam/golibs v0.13.4
github.com/AdguardTeam/urlfilter v0.16.1
github.com/AdguardTeam/dnsproxy v0.52.1-0.20230726165924-30c459b0cdef
github.com/AdguardTeam/golibs v0.13.6
github.com/AdguardTeam/urlfilter v0.16.2
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.2.7
github.com/bluele/gcache v0.0.2
@@ -15,9 +15,9 @@ require (
github.com/go-ping/ping v1.1.0
github.com/google/go-cmp v0.5.9
github.com/google/gopacket v1.1.19
github.com/google/renameio v1.0.1
github.com/google/renameio/v2 v2.0.0
github.com/google/uuid v1.3.0
github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df
github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
github.com/kardianos/service v1.2.2
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
@@ -27,14 +27,17 @@ require (
// own code for that. Perhaps, use gopacket.
github.com/mdlayher/raw v0.1.0
github.com/miekg/dns v1.1.55
github.com/quic-go/quic-go v0.36.1
// TODO(a.garipov): Update to v0.37.0 once we update to Go 1.20.
github.com/quic-go/quic-go v0.36.2
github.com/stretchr/testify v1.8.4
github.com/ti-mo/netfilter v0.5.0
go.etcd.io/bbolt v1.3.7
golang.org/x/crypto v0.10.0
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df
golang.org/x/net v0.11.0
golang.org/x/sys v0.9.0
golang.org/x/crypto v0.11.0
// TODO(a.garipov): Update after updating slices.Sort and friends to
// stdlib versions in dnsproxy and golibs in Go 1.20.
golang.org/x/exp v0.0.0-20230724220655-d98519c11495
golang.org/x/net v0.12.0
golang.org/x/sys v0.10.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.0
@@ -48,7 +51,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
@@ -59,8 +62,8 @@ require (
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/tools v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/tools v0.11.0 // indirect
)

56
go.sum
View File

@@ -1,12 +1,12 @@
github.com/AdguardTeam/dnsproxy v0.52.0 h1:uZxCXflHSAwtJ7uTYXP6qgWcxaBsH0pJvldpwTqIDJk=
github.com/AdguardTeam/dnsproxy v0.52.0/go.mod h1:Jo2zeRe97Rxt3yikXc+fn0LdLtqCj0Xlyh1PNBj6bpM=
github.com/AdguardTeam/dnsproxy v0.52.1-0.20230726165924-30c459b0cdef h1:3ZJieG+PV+wJEXLgUndW4yL9/7iubyipbDmA0w3sa7Y=
github.com/AdguardTeam/dnsproxy v0.52.1-0.20230726165924-30c459b0cdef/go.mod h1:Jo2zeRe97Rxt3yikXc+fn0LdLtqCj0Xlyh1PNBj6bpM=
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
github.com/AdguardTeam/golibs v0.13.4 h1:ACTwIR1pEENBijHcEWtiMbSh4wWQOlIHRxmUB8oBHf8=
github.com/AdguardTeam/golibs v0.13.4/go.mod h1:wkJ6EUsN4np/9Gp7+9QeooY9E2U2WCLJYAioLCzkHsI=
github.com/AdguardTeam/golibs v0.13.6 h1:z/0Q25pRLdaQxtoxvfSaooz5mdv8wj0R8KREj54q8yQ=
github.com/AdguardTeam/golibs v0.13.6/go.mod h1:hOtcb8dPfKcFjWTPA904hTA4dl1aWvzeebdJpE72IPk=
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
github.com/AdguardTeam/urlfilter v0.16.1 h1:ZPi0rjqo8cQf2FVdzo6cqumNoHZx2KPXj2yZa1A5BBw=
github.com/AdguardTeam/urlfilter v0.16.1/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
github.com/AdguardTeam/urlfilter v0.16.2 h1:k9m9dUYVJ3sTswYa2/ukVNjicfGcz0oqFDO13hPmfHE=
github.com/AdguardTeam/urlfilter v0.16.2/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
@@ -50,16 +50,16 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU=
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA=
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df h1:pF1MMIzEJzJ/MyI4bXYXVYyN8CJgoQ2PPKT2z3O/Cl4=
github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd h1:D772X7igTag7yKErVWAR7boXpOml3fqqBzH1wNaD/jk=
github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
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.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -108,8 +108,8 @@ github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc8
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.36.1 h1:WsG73nVtnDy1TiACxFxhQ3TqaW+DipmqzLEtNlAwZyY=
github.com/quic-go/quic-go v0.36.1/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
github.com/quic-go/quic-go v0.36.2 h1:ZX/UNQ4gvpCv2RmwdbA6lrRjF6EBm5yZ7TMoT4NQVrA=
github.com/quic-go/quic-go v0.36.2/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA=
github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -134,15 +134,15 @@ go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
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.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20230724220655-d98519c11495 h1:zKGKw2WlGb8oPoRGqQ2PT8g2YoCN1w/YbbQjHXCdUWE=
golang.org/x/exp v0.0.0-20230724220655-d98519c11495/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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=
@@ -152,8 +152,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
@@ -177,22 +177,22 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -1,10 +1,11 @@
package aghio
package aghio_test
import (
"io"
"strings"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -31,7 +32,7 @@ func TestLimitReader(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := LimitReader(nil, tc.n)
_, err := aghio.LimitReader(nil, tc.n)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
})
}
@@ -57,7 +58,7 @@ func TestLimitedReader_Read(t *testing.T) {
limit: 3,
want: 0,
}, {
err: &LimitReachedError{
err: &aghio.LimitReachedError{
Limit: 0,
},
name: "limit_reached",
@@ -74,7 +75,7 @@ func TestLimitedReader_Read(t *testing.T) {
for _, tc := range testCases {
readCloser := io.NopCloser(strings.NewReader(tc.rStr))
lreader, err := LimitReader(readCloser, tc.limit)
lreader, err := aghio.LimitReader(readCloser, tc.limit)
require.NoError(t, err)
require.NotNil(t, lreader)
@@ -89,7 +90,7 @@ func TestLimitedReader_Read(t *testing.T) {
}
func TestLimitedReader_LimitReachedError(t *testing.T) {
testutil.AssertErrorMsg(t, "attempted to read more than 0 bytes", &LimitReachedError{
testutil.AssertErrorMsg(t, "attempted to read more than 0 bytes", &aghio.LimitReachedError{
Limit: 0,
})
}

View File

@@ -141,9 +141,9 @@ type HostsRecord struct {
Canonical string
}
// equal returns true if all fields of rec are equal to field in other or they
// Equal returns true if all fields of rec are equal to field in other or they
// both are nil.
func (rec *HostsRecord) equal(other *HostsRecord) (ok bool) {
func (rec *HostsRecord) Equal(other *HostsRecord) (ok bool) {
if rec == nil {
return other == nil
} else if other == nil {
@@ -495,7 +495,7 @@ func (hc *HostsContainer) refresh() (err error) {
}
// hc.last is nil on the first refresh, so let that one through.
if hc.last != nil && maps.EqualFunc(hp.table, hc.last, (*HostsRecord).equal) {
if hc.last != nil && maps.EqualFunc(hp.table, hc.last, (*HostsRecord).Equal) {
log.Debug("%s: no changes detected", hostsContainerPrefix)
return nil

View File

@@ -0,0 +1,144 @@
package aghnet
import (
"io/fs"
"net/netip"
"path"
"testing"
"testing/fstest"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil/fakefs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const nl = "\n"
func TestHostsContainer_PathsToPatterns(t *testing.T) {
gsfs := fstest.MapFS{
"dir_0/file_1": &fstest.MapFile{Data: []byte{1}},
"dir_0/file_2": &fstest.MapFile{Data: []byte{2}},
"dir_0/dir_1/file_3": &fstest.MapFile{Data: []byte{3}},
}
testCases := []struct {
name string
paths []string
want []string
}{{
name: "no_paths",
paths: nil,
want: nil,
}, {
name: "single_file",
paths: []string{"dir_0/file_1"},
want: []string{"dir_0/file_1"},
}, {
name: "several_files",
paths: []string{"dir_0/file_1", "dir_0/file_2"},
want: []string{"dir_0/file_1", "dir_0/file_2"},
}, {
name: "whole_dir",
paths: []string{"dir_0"},
want: []string{"dir_0/*"},
}, {
name: "file_and_dir",
paths: []string{"dir_0/file_1", "dir_0/dir_1"},
want: []string{"dir_0/file_1", "dir_0/dir_1/*"},
}, {
name: "non-existing",
paths: []string{path.Join("dir_0", "file_3")},
want: nil,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
patterns, err := pathsToPatterns(gsfs, tc.paths)
require.NoError(t, err)
assert.Equal(t, tc.want, patterns)
})
}
t.Run("bad_file", func(t *testing.T) {
const errStat errors.Error = "bad file"
badFS := &fakefs.StatFS{
OnOpen: func(_ string) (f fs.File, err error) { panic("not implemented") },
OnStat: func(name string) (fi fs.FileInfo, err error) {
return nil, errStat
},
}
_, err := pathsToPatterns(badFS, []string{""})
assert.ErrorIs(t, err, errStat)
})
}
func TestUniqueRules_ParseLine(t *testing.T) {
ip := netutil.IPv4Localhost()
ipStr := ip.String()
testCases := []struct {
name string
line string
wantIP netip.Addr
wantHosts []string
}{{
name: "simple",
line: ipStr + ` hostname`,
wantIP: ip,
wantHosts: []string{"hostname"},
}, {
name: "aliases",
line: ipStr + ` hostname alias`,
wantIP: ip,
wantHosts: []string{"hostname", "alias"},
}, {
name: "invalid_line",
line: ipStr,
wantIP: netip.Addr{},
wantHosts: nil,
}, {
name: "invalid_line_hostname",
line: ipStr + ` # hostname`,
wantIP: ip,
wantHosts: nil,
}, {
name: "commented_aliases",
line: ipStr + ` hostname # alias`,
wantIP: ip,
wantHosts: []string{"hostname"},
}, {
name: "whole_comment",
line: `# ` + ipStr + ` hostname`,
wantIP: netip.Addr{},
wantHosts: nil,
}, {
name: "partial_comment",
line: ipStr + ` host#name`,
wantIP: ip,
wantHosts: []string{"host"},
}, {
name: "empty",
line: ``,
wantIP: netip.Addr{},
wantHosts: nil,
}, {
name: "bad_hosts",
line: ipStr + ` bad..host bad._tld empty.tld. ok.host`,
wantIP: ip,
wantHosts: []string{"ok.host"},
}}
for _, tc := range testCases {
hp := hostsParser{}
t.Run(tc.name, func(t *testing.T) {
got, hosts := hp.parseLine(tc.line)
assert.Equal(t, tc.wantIP, got)
assert.Equal(t, tc.wantHosts, hosts)
})
}
}

View File

@@ -1,9 +1,7 @@
package aghnet
package aghnet_test
import (
"io/fs"
"net"
"net/netip"
"path"
"strings"
"sync/atomic"
@@ -12,6 +10,7 @@ import (
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghchan"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
@@ -24,10 +23,7 @@ import (
"github.com/stretchr/testify/require"
)
const (
nl = "\n"
sp = " "
)
const nl = "\n"
func TestNewHostsContainer(t *testing.T) {
const dirname = "dir"
@@ -48,11 +44,11 @@ func TestNewHostsContainer(t *testing.T) {
name: "one_file",
paths: []string{p},
}, {
wantErr: ErrNoHostsPaths,
wantErr: aghnet.ErrNoHostsPaths,
name: "no_files",
paths: []string{},
}, {
wantErr: ErrNoHostsPaths,
wantErr: aghnet.ErrNoHostsPaths,
name: "non-existent_file",
paths: []string{path.Join(dirname, filename+"2")},
}, {
@@ -77,7 +73,7 @@ func TestNewHostsContainer(t *testing.T) {
return eventsCh
}
hc, err := NewHostsContainer(0, testFS, &aghtest.FSWatcher{
hc, err := aghnet.NewHostsContainer(0, testFS, &aghtest.FSWatcher{
OnEvents: onEvents,
OnAdd: onAdd,
OnClose: func() (err error) { return nil },
@@ -103,7 +99,7 @@ func TestNewHostsContainer(t *testing.T) {
t.Run("nil_fs", func(t *testing.T) {
require.Panics(t, func() {
_, _ = NewHostsContainer(0, nil, &aghtest.FSWatcher{
_, _ = aghnet.NewHostsContainer(0, nil, &aghtest.FSWatcher{
// Those shouldn't panic.
OnEvents: func() (e <-chan struct{}) { return nil },
OnAdd: func(name string) (err error) { return nil },
@@ -114,7 +110,7 @@ func TestNewHostsContainer(t *testing.T) {
t.Run("nil_watcher", func(t *testing.T) {
require.Panics(t, func() {
_, _ = NewHostsContainer(0, testFS, nil, p)
_, _ = aghnet.NewHostsContainer(0, testFS, nil, p)
})
})
@@ -127,7 +123,7 @@ func TestNewHostsContainer(t *testing.T) {
OnClose: func() (err error) { return nil },
}
hc, err := NewHostsContainer(0, testFS, errWatcher, p)
hc, err := aghnet.NewHostsContainer(0, testFS, errWatcher, p)
require.ErrorIs(t, err, errOnAdd)
assert.Nil(t, hc)
@@ -158,11 +154,11 @@ func TestHostsContainer_refresh(t *testing.T) {
OnClose: func() (err error) { return nil },
}
hc, err := NewHostsContainer(0, testFS, w, "dir")
hc, err := aghnet.NewHostsContainer(0, testFS, w, "dir")
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, hc.Close)
checkRefresh := func(t *testing.T, want *HostsRecord) {
checkRefresh := func(t *testing.T, want *aghnet.HostsRecord) {
t.Helper()
upd, ok := aghchan.MustReceive(hc.Upd(), 1*time.Second)
@@ -175,11 +171,11 @@ func TestHostsContainer_refresh(t *testing.T) {
require.True(t, ok)
require.NotNil(t, rec)
assert.Truef(t, rec.equal(want), "%+v != %+v", rec, want)
assert.Truef(t, rec.Equal(want), "%+v != %+v", rec, want)
}
t.Run("initial_refresh", func(t *testing.T) {
checkRefresh(t, &HostsRecord{
checkRefresh(t, &aghnet.HostsRecord{
Aliases: stringutil.NewSet(),
Canonical: "hostname",
})
@@ -189,7 +185,7 @@ func TestHostsContainer_refresh(t *testing.T) {
testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + ` alias` + nl)}
eventsCh <- event{}
checkRefresh(t, &HostsRecord{
checkRefresh(t, &aghnet.HostsRecord{
Aliases: stringutil.NewSet("alias"),
Canonical: "hostname",
})
@@ -228,66 +224,6 @@ func TestHostsContainer_refresh(t *testing.T) {
})
}
func TestHostsContainer_PathsToPatterns(t *testing.T) {
gsfs := fstest.MapFS{
"dir_0/file_1": &fstest.MapFile{Data: []byte{1}},
"dir_0/file_2": &fstest.MapFile{Data: []byte{2}},
"dir_0/dir_1/file_3": &fstest.MapFile{Data: []byte{3}},
}
testCases := []struct {
name string
paths []string
want []string
}{{
name: "no_paths",
paths: nil,
want: nil,
}, {
name: "single_file",
paths: []string{"dir_0/file_1"},
want: []string{"dir_0/file_1"},
}, {
name: "several_files",
paths: []string{"dir_0/file_1", "dir_0/file_2"},
want: []string{"dir_0/file_1", "dir_0/file_2"},
}, {
name: "whole_dir",
paths: []string{"dir_0"},
want: []string{"dir_0/*"},
}, {
name: "file_and_dir",
paths: []string{"dir_0/file_1", "dir_0/dir_1"},
want: []string{"dir_0/file_1", "dir_0/dir_1/*"},
}, {
name: "non-existing",
paths: []string{path.Join("dir_0", "file_3")},
want: nil,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
patterns, err := pathsToPatterns(gsfs, tc.paths)
require.NoError(t, err)
assert.Equal(t, tc.want, patterns)
})
}
t.Run("bad_file", func(t *testing.T) {
const errStat errors.Error = "bad file"
badFS := &aghtest.StatFS{
OnStat: func(name string) (fs.FileInfo, error) {
return nil, errStat
},
}
_, err := pathsToPatterns(badFS, []string{""})
assert.ErrorIs(t, err, errStat)
})
}
func TestHostsContainer_Translate(t *testing.T) {
stubWatcher := aghtest.FSWatcher{
OnEvents: func() (e <-chan struct{}) { return nil },
@@ -297,7 +233,7 @@ func TestHostsContainer_Translate(t *testing.T) {
require.NoError(t, fstest.TestFS(testdata, "etc_hosts"))
hc, err := NewHostsContainer(0, testdata, &stubWatcher, "etc_hosts")
hc, err := aghnet.NewHostsContainer(0, testdata, &stubWatcher, "etc_hosts")
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, hc.Close)
@@ -527,7 +463,7 @@ func TestHostsContainer(t *testing.T) {
OnClose: func() (err error) { return nil },
}
hc, err := NewHostsContainer(listID, testdata, &stubWatcher, "etc_hosts")
hc, err := aghnet.NewHostsContainer(listID, testdata, &stubWatcher, "etc_hosts")
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, hc.Close)
@@ -558,69 +494,3 @@ func TestHostsContainer(t *testing.T) {
})
}
}
func TestUniqueRules_ParseLine(t *testing.T) {
ip := netutil.IPv4Localhost()
ipStr := ip.String()
testCases := []struct {
name string
line string
wantIP netip.Addr
wantHosts []string
}{{
name: "simple",
line: ipStr + ` hostname`,
wantIP: ip,
wantHosts: []string{"hostname"},
}, {
name: "aliases",
line: ipStr + ` hostname alias`,
wantIP: ip,
wantHosts: []string{"hostname", "alias"},
}, {
name: "invalid_line",
line: ipStr,
wantIP: netip.Addr{},
wantHosts: nil,
}, {
name: "invalid_line_hostname",
line: ipStr + ` # hostname`,
wantIP: ip,
wantHosts: nil,
}, {
name: "commented_aliases",
line: ipStr + ` hostname # alias`,
wantIP: ip,
wantHosts: []string{"hostname"},
}, {
name: "whole_comment",
line: `# ` + ipStr + ` hostname`,
wantIP: netip.Addr{},
wantHosts: nil,
}, {
name: "partial_comment",
line: ipStr + ` host#name`,
wantIP: ip,
wantHosts: []string{"host"},
}, {
name: "empty",
line: ``,
wantIP: netip.Addr{},
wantHosts: nil,
}, {
name: "bad_hosts",
line: ipStr + ` bad..host bad._tld empty.tld. ok.host`,
wantIP: ip,
wantHosts: []string{"ok.host"},
}}
for _, tc := range testCases {
hp := hostsParser{}
t.Run(tc.name, func(t *testing.T) {
got, hosts := hp.parseLine(tc.line)
assert.Equal(t, tc.wantIP, got)
assert.Equal(t, tc.wantHosts, hosts)
})
}
}

View File

@@ -3,6 +3,7 @@ package aghnet
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
@@ -15,6 +16,10 @@ import (
"github.com/AdguardTeam/golibs/log"
)
// DialContextFunc is the semantic alias for dialing functions, such as
// [http.Transport.DialContext].
type DialContextFunc = func(ctx context.Context, network, addr string) (conn net.Conn, err error)
// Variables and functions to substitute in tests.
var (
// aghosRunCommand is the function to run shell commands.

View File

@@ -5,9 +5,9 @@ import (
"testing"
"testing/fstest"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/fakefs"
"github.com/stretchr/testify/assert"
)
@@ -118,7 +118,7 @@ func TestIfaceSetStaticIP(t *testing.T) {
Data: []byte(`nameserver 1.1.1.1`),
},
}
panicFsys := &aghtest.FS{
panicFsys := &fakefs.FS{
OnOpen: func(name string) (fs.File, error) { panic("not implemented") },
}

View File

@@ -0,0 +1,334 @@
package aghnet
import (
"bytes"
"encoding/json"
"fmt"
"io/fs"
"net"
"net/netip"
"os"
"strings"
"testing"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// testdata is the filesystem containing data for testing the package.
var testdata fs.FS = os.DirFS("./testdata")
// substRootDirFS replaces the aghos.RootDirFS function used throughout the
// package with fsys for tests ran under t.
func substRootDirFS(t testing.TB, fsys fs.FS) {
t.Helper()
prev := rootDirFS
t.Cleanup(func() { rootDirFS = prev })
rootDirFS = fsys
}
// RunCmdFunc is the signature of aghos.RunCommand function.
type RunCmdFunc func(cmd string, args ...string) (code int, out []byte, err error)
// substShell replaces the the aghos.RunCommand function used throughout the
// package with rc for tests ran under t.
func substShell(t testing.TB, rc RunCmdFunc) {
t.Helper()
prev := aghosRunCommand
t.Cleanup(func() { aghosRunCommand = prev })
aghosRunCommand = rc
}
// mapShell is a substitution of aghos.RunCommand that maps the command to it's
// execution result. It's only needed to simplify testing.
//
// TODO(e.burkov): Perhaps put all the shell interactions behind an interface.
type mapShell map[string]struct {
err error
out string
code int
}
// theOnlyCmd returns mapShell that only handles a single command and arguments
// combination from cmd.
func theOnlyCmd(cmd string, code int, out string, err error) (s mapShell) {
return mapShell{cmd: {code: code, out: out, err: err}}
}
// RunCmd is a RunCmdFunc handled by s.
func (s mapShell) RunCmd(cmd string, args ...string) (code int, out []byte, err error) {
key := strings.Join(append([]string{cmd}, args...), " ")
ret, ok := s[key]
if !ok {
return 0, nil, fmt.Errorf("unexpected shell command %q", key)
}
return ret.code, []byte(ret.out), ret.err
}
// ifaceAddrsFunc is the signature of net.InterfaceAddrs function.
type ifaceAddrsFunc func() (ifaces []net.Addr, err error)
// substNetInterfaceAddrs replaces the the net.InterfaceAddrs function used
// throughout the package with f for tests ran under t.
func substNetInterfaceAddrs(t *testing.T, f ifaceAddrsFunc) {
t.Helper()
prev := netInterfaceAddrs
t.Cleanup(func() { netInterfaceAddrs = prev })
netInterfaceAddrs = f
}
func TestGatewayIP(t *testing.T) {
const ifaceName = "ifaceName"
const cmd = "ip route show dev " + ifaceName
testCases := []struct {
shell mapShell
want netip.Addr
name string
}{{
shell: theOnlyCmd(cmd, 0, `default via 1.2.3.4 onlink`, nil),
want: netip.MustParseAddr("1.2.3.4"),
name: "success_v4",
}, {
shell: theOnlyCmd(cmd, 0, `default via ::ffff onlink`, nil),
want: netip.MustParseAddr("::ffff"),
name: "success_v6",
}, {
shell: theOnlyCmd(cmd, 0, `non-default via 1.2.3.4 onlink`, nil),
want: netip.Addr{},
name: "bad_output",
}, {
shell: theOnlyCmd(cmd, 0, "", errors.Error("can't run command")),
want: netip.Addr{},
name: "err_runcmd",
}, {
shell: theOnlyCmd(cmd, 1, "", nil),
want: netip.Addr{},
name: "bad_code",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
substShell(t, tc.shell.RunCmd)
assert.Equal(t, tc.want, GatewayIP(ifaceName))
})
}
}
func TestInterfaceByIP(t *testing.T) {
ifaces, err := GetValidNetInterfacesForWeb()
require.NoError(t, err)
require.NotEmpty(t, ifaces)
for _, iface := range ifaces {
t.Run(iface.Name, func(t *testing.T) {
require.NotEmpty(t, iface.Addresses)
for _, ip := range iface.Addresses {
ifaceName := InterfaceByIP(ip)
require.Equal(t, iface.Name, ifaceName)
}
})
}
}
func TestBroadcastFromIPNet(t *testing.T) {
known4 := netip.MustParseAddr("192.168.0.1")
fullBroadcast4 := netip.MustParseAddr("255.255.255.255")
known6 := netip.MustParseAddr("102:304:506:708:90a:b0c:d0e:f10")
testCases := []struct {
pref netip.Prefix
want netip.Addr
name string
}{{
pref: netip.PrefixFrom(known4, 0),
want: fullBroadcast4,
name: "full",
}, {
pref: netip.PrefixFrom(known4, 20),
want: netip.MustParseAddr("192.168.15.255"),
name: "full",
}, {
pref: netip.PrefixFrom(known6, netutil.IPv6BitLen),
want: known6,
name: "ipv6_no_mask",
}, {
pref: netip.PrefixFrom(known4, netutil.IPv4BitLen),
want: known4,
name: "ipv4_no_mask",
}, {
pref: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
want: fullBroadcast4,
name: "unspecified",
}, {
pref: netip.Prefix{},
want: netip.Addr{},
name: "invalid",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.want, BroadcastFromPref(tc.pref))
})
}
}
func TestCheckPort(t *testing.T) {
laddr := netip.AddrPortFrom(netutil.IPv4Localhost(), 0)
t.Run("tcp_bound", func(t *testing.T) {
l, err := net.Listen("tcp", laddr.String())
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, l.Close)
ipp := testutil.RequireTypeAssert[*net.TCPAddr](t, l.Addr()).AddrPort()
require.Equal(t, laddr.Addr(), ipp.Addr())
require.NotZero(t, ipp.Port())
err = CheckPort("tcp", ipp)
target := &net.OpError{}
require.ErrorAs(t, err, &target)
assert.Equal(t, "listen", target.Op)
})
t.Run("udp_bound", func(t *testing.T) {
conn, err := net.ListenPacket("udp", laddr.String())
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, conn.Close)
ipp := testutil.RequireTypeAssert[*net.UDPAddr](t, conn.LocalAddr()).AddrPort()
require.Equal(t, laddr.Addr(), ipp.Addr())
require.NotZero(t, ipp.Port())
err = CheckPort("udp", ipp)
target := &net.OpError{}
require.ErrorAs(t, err, &target)
assert.Equal(t, "listen", target.Op)
})
t.Run("bad_network", func(t *testing.T) {
err := CheckPort("bad_network", netip.AddrPortFrom(netip.Addr{}, 0))
assert.NoError(t, err)
})
t.Run("can_bind", func(t *testing.T) {
err := CheckPort("udp", netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
assert.NoError(t, err)
})
}
func TestCollectAllIfacesAddrs(t *testing.T) {
testCases := []struct {
name string
wantErrMsg string
addrs []net.Addr
wantAddrs []string
}{{
name: "success",
wantErrMsg: ``,
addrs: []net.Addr{&net.IPNet{
IP: net.IP{1, 2, 3, 4},
Mask: net.CIDRMask(24, netutil.IPv4BitLen),
}, &net.IPNet{
IP: net.IP{4, 3, 2, 1},
Mask: net.CIDRMask(16, netutil.IPv4BitLen),
}},
wantAddrs: []string{"1.2.3.4", "4.3.2.1"},
}, {
name: "not_cidr",
wantErrMsg: `parsing cidr: invalid CIDR address: 1.2.3.4`,
addrs: []net.Addr{&net.IPAddr{
IP: net.IP{1, 2, 3, 4},
}},
wantAddrs: nil,
}, {
name: "empty",
wantErrMsg: ``,
addrs: []net.Addr{},
wantAddrs: nil,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
substNetInterfaceAddrs(t, func() ([]net.Addr, error) { return tc.addrs, nil })
addrs, err := CollectAllIfacesAddrs()
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
assert.Equal(t, tc.wantAddrs, addrs)
})
}
t.Run("internal_error", func(t *testing.T) {
const errAddrs errors.Error = "can't get addresses"
const wantErrMsg string = `getting interfaces addresses: ` + string(errAddrs)
substNetInterfaceAddrs(t, func() ([]net.Addr, error) { return nil, errAddrs })
_, err := CollectAllIfacesAddrs()
testutil.AssertErrorMsg(t, wantErrMsg, err)
})
}
func TestIsAddrInUse(t *testing.T) {
t.Run("addr_in_use", func(t *testing.T) {
l, err := net.Listen("tcp", "0.0.0.0:0")
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, l.Close)
_, err = net.Listen(l.Addr().Network(), l.Addr().String())
assert.True(t, IsAddrInUse(err))
})
t.Run("another", func(t *testing.T) {
const anotherErr errors.Error = "not addr in use"
assert.False(t, IsAddrInUse(anotherErr))
})
}
func TestNetInterface_MarshalJSON(t *testing.T) {
const want = `{` +
`"hardware_address":"aa:bb:cc:dd:ee:ff",` +
`"flags":"up|multicast",` +
`"ip_addresses":["1.2.3.4","aaaa::1"],` +
`"name":"iface0",` +
`"mtu":1500` +
`}` + "\n"
ip4, ok := netip.AddrFromSlice([]byte{1, 2, 3, 4})
require.True(t, ok)
ip6, ok := netip.AddrFromSlice([]byte{0xAA, 0xAA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
require.True(t, ok)
net4 := netip.PrefixFrom(ip4, 24)
net6 := netip.PrefixFrom(ip6, 8)
iface := &NetInterface{
Addresses: []netip.Addr{ip4, ip6},
Subnets: []netip.Prefix{net4, net6},
Name: "iface0",
HardwareAddr: net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
Flags: net.FlagUp | net.FlagMulticast,
MTU: 1500,
}
b := &bytes.Buffer{}
err := json.NewEncoder(b).Encode(iface)
require.NoError(t, err)
assert.Equal(t, want, b.String())
}

View File

@@ -14,7 +14,7 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/google/renameio/maybe"
"github.com/google/renameio/v2/maybe"
"golang.org/x/sys/unix"
)

View File

@@ -1,21 +1,11 @@
package aghnet
package aghnet_test
import (
"bytes"
"encoding/json"
"fmt"
"io/fs"
"net"
"net/netip"
"os"
"strings"
"testing"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
@@ -24,315 +14,3 @@ func TestMain(m *testing.M) {
// testdata is the filesystem containing data for testing the package.
var testdata fs.FS = os.DirFS("./testdata")
// substRootDirFS replaces the aghos.RootDirFS function used throughout the
// package with fsys for tests ran under t.
func substRootDirFS(t testing.TB, fsys fs.FS) {
t.Helper()
prev := rootDirFS
t.Cleanup(func() { rootDirFS = prev })
rootDirFS = fsys
}
// RunCmdFunc is the signature of aghos.RunCommand function.
type RunCmdFunc func(cmd string, args ...string) (code int, out []byte, err error)
// substShell replaces the the aghos.RunCommand function used throughout the
// package with rc for tests ran under t.
func substShell(t testing.TB, rc RunCmdFunc) {
t.Helper()
prev := aghosRunCommand
t.Cleanup(func() { aghosRunCommand = prev })
aghosRunCommand = rc
}
// mapShell is a substitution of aghos.RunCommand that maps the command to it's
// execution result. It's only needed to simplify testing.
//
// TODO(e.burkov): Perhaps put all the shell interactions behind an interface.
type mapShell map[string]struct {
err error
out string
code int
}
// theOnlyCmd returns mapShell that only handles a single command and arguments
// combination from cmd.
func theOnlyCmd(cmd string, code int, out string, err error) (s mapShell) {
return mapShell{cmd: {code: code, out: out, err: err}}
}
// RunCmd is a RunCmdFunc handled by s.
func (s mapShell) RunCmd(cmd string, args ...string) (code int, out []byte, err error) {
key := strings.Join(append([]string{cmd}, args...), " ")
ret, ok := s[key]
if !ok {
return 0, nil, fmt.Errorf("unexpected shell command %q", key)
}
return ret.code, []byte(ret.out), ret.err
}
// ifaceAddrsFunc is the signature of net.InterfaceAddrs function.
type ifaceAddrsFunc func() (ifaces []net.Addr, err error)
// substNetInterfaceAddrs replaces the the net.InterfaceAddrs function used
// throughout the package with f for tests ran under t.
func substNetInterfaceAddrs(t *testing.T, f ifaceAddrsFunc) {
t.Helper()
prev := netInterfaceAddrs
t.Cleanup(func() { netInterfaceAddrs = prev })
netInterfaceAddrs = f
}
func TestGatewayIP(t *testing.T) {
const ifaceName = "ifaceName"
const cmd = "ip route show dev " + ifaceName
testCases := []struct {
shell mapShell
want netip.Addr
name string
}{{
shell: theOnlyCmd(cmd, 0, `default via 1.2.3.4 onlink`, nil),
want: netip.MustParseAddr("1.2.3.4"),
name: "success_v4",
}, {
shell: theOnlyCmd(cmd, 0, `default via ::ffff onlink`, nil),
want: netip.MustParseAddr("::ffff"),
name: "success_v6",
}, {
shell: theOnlyCmd(cmd, 0, `non-default via 1.2.3.4 onlink`, nil),
want: netip.Addr{},
name: "bad_output",
}, {
shell: theOnlyCmd(cmd, 0, "", errors.Error("can't run command")),
want: netip.Addr{},
name: "err_runcmd",
}, {
shell: theOnlyCmd(cmd, 1, "", nil),
want: netip.Addr{},
name: "bad_code",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
substShell(t, tc.shell.RunCmd)
assert.Equal(t, tc.want, GatewayIP(ifaceName))
})
}
}
func TestInterfaceByIP(t *testing.T) {
ifaces, err := GetValidNetInterfacesForWeb()
require.NoError(t, err)
require.NotEmpty(t, ifaces)
for _, iface := range ifaces {
t.Run(iface.Name, func(t *testing.T) {
require.NotEmpty(t, iface.Addresses)
for _, ip := range iface.Addresses {
ifaceName := InterfaceByIP(ip)
require.Equal(t, iface.Name, ifaceName)
}
})
}
}
func TestBroadcastFromIPNet(t *testing.T) {
known4 := netip.MustParseAddr("192.168.0.1")
fullBroadcast4 := netip.MustParseAddr("255.255.255.255")
known6 := netip.MustParseAddr("102:304:506:708:90a:b0c:d0e:f10")
testCases := []struct {
pref netip.Prefix
want netip.Addr
name string
}{{
pref: netip.PrefixFrom(known4, 0),
want: fullBroadcast4,
name: "full",
}, {
pref: netip.PrefixFrom(known4, 20),
want: netip.MustParseAddr("192.168.15.255"),
name: "full",
}, {
pref: netip.PrefixFrom(known6, netutil.IPv6BitLen),
want: known6,
name: "ipv6_no_mask",
}, {
pref: netip.PrefixFrom(known4, netutil.IPv4BitLen),
want: known4,
name: "ipv4_no_mask",
}, {
pref: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
want: fullBroadcast4,
name: "unspecified",
}, {
pref: netip.Prefix{},
want: netip.Addr{},
name: "invalid",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.want, BroadcastFromPref(tc.pref))
})
}
}
func TestCheckPort(t *testing.T) {
laddr := netip.AddrPortFrom(netutil.IPv4Localhost(), 0)
t.Run("tcp_bound", func(t *testing.T) {
l, err := net.Listen("tcp", laddr.String())
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, l.Close)
ipp := testutil.RequireTypeAssert[*net.TCPAddr](t, l.Addr()).AddrPort()
require.Equal(t, laddr.Addr(), ipp.Addr())
require.NotZero(t, ipp.Port())
err = CheckPort("tcp", ipp)
target := &net.OpError{}
require.ErrorAs(t, err, &target)
assert.Equal(t, "listen", target.Op)
})
t.Run("udp_bound", func(t *testing.T) {
conn, err := net.ListenPacket("udp", laddr.String())
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, conn.Close)
ipp := testutil.RequireTypeAssert[*net.UDPAddr](t, conn.LocalAddr()).AddrPort()
require.Equal(t, laddr.Addr(), ipp.Addr())
require.NotZero(t, ipp.Port())
err = CheckPort("udp", ipp)
target := &net.OpError{}
require.ErrorAs(t, err, &target)
assert.Equal(t, "listen", target.Op)
})
t.Run("bad_network", func(t *testing.T) {
err := CheckPort("bad_network", netip.AddrPortFrom(netip.Addr{}, 0))
assert.NoError(t, err)
})
t.Run("can_bind", func(t *testing.T) {
err := CheckPort("udp", netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
assert.NoError(t, err)
})
}
func TestCollectAllIfacesAddrs(t *testing.T) {
testCases := []struct {
name string
wantErrMsg string
addrs []net.Addr
wantAddrs []string
}{{
name: "success",
wantErrMsg: ``,
addrs: []net.Addr{&net.IPNet{
IP: net.IP{1, 2, 3, 4},
Mask: net.CIDRMask(24, netutil.IPv4BitLen),
}, &net.IPNet{
IP: net.IP{4, 3, 2, 1},
Mask: net.CIDRMask(16, netutil.IPv4BitLen),
}},
wantAddrs: []string{"1.2.3.4", "4.3.2.1"},
}, {
name: "not_cidr",
wantErrMsg: `parsing cidr: invalid CIDR address: 1.2.3.4`,
addrs: []net.Addr{&net.IPAddr{
IP: net.IP{1, 2, 3, 4},
}},
wantAddrs: nil,
}, {
name: "empty",
wantErrMsg: ``,
addrs: []net.Addr{},
wantAddrs: nil,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
substNetInterfaceAddrs(t, func() ([]net.Addr, error) { return tc.addrs, nil })
addrs, err := CollectAllIfacesAddrs()
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
assert.Equal(t, tc.wantAddrs, addrs)
})
}
t.Run("internal_error", func(t *testing.T) {
const errAddrs errors.Error = "can't get addresses"
const wantErrMsg string = `getting interfaces addresses: ` + string(errAddrs)
substNetInterfaceAddrs(t, func() ([]net.Addr, error) { return nil, errAddrs })
_, err := CollectAllIfacesAddrs()
testutil.AssertErrorMsg(t, wantErrMsg, err)
})
}
func TestIsAddrInUse(t *testing.T) {
t.Run("addr_in_use", func(t *testing.T) {
l, err := net.Listen("tcp", "0.0.0.0:0")
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, l.Close)
_, err = net.Listen(l.Addr().Network(), l.Addr().String())
assert.True(t, IsAddrInUse(err))
})
t.Run("another", func(t *testing.T) {
const anotherErr errors.Error = "not addr in use"
assert.False(t, IsAddrInUse(anotherErr))
})
}
func TestNetInterface_MarshalJSON(t *testing.T) {
const want = `{` +
`"hardware_address":"aa:bb:cc:dd:ee:ff",` +
`"flags":"up|multicast",` +
`"ip_addresses":["1.2.3.4","aaaa::1"],` +
`"name":"iface0",` +
`"mtu":1500` +
`}` + "\n"
ip4, ok := netip.AddrFromSlice([]byte{1, 2, 3, 4})
require.True(t, ok)
ip6, ok := netip.AddrFromSlice([]byte{0xAA, 0xAA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
require.True(t, ok)
net4 := netip.PrefixFrom(ip4, 24)
net6 := netip.PrefixFrom(ip6, 8)
iface := &NetInterface{
Addresses: []netip.Addr{ip4, ip6},
Subnets: []netip.Prefix{net4, net6},
Name: "iface0",
HardwareAddr: net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
Flags: net.FlagUp | net.FlagMulticast,
MTU: 1500,
}
b := &bytes.Buffer{}
err := json.NewEncoder(b).Encode(iface)
require.NoError(t, err)
assert.Equal(t, want, b.String())
}

View File

@@ -0,0 +1,52 @@
// Package aghrenameio is a wrapper around package github.com/google/renameio/v2
// that provides a similar stream-based API for both Unix and Windows systems.
// While the Windows API is not technically atomic, it still provides a
// consistent stream-based interface, and atomic renames of files do not seem to
// be possible in all cases anyway.
//
// See https://github.com/google/renameio/issues/1.
//
// TODO(a.garipov): Consider moving to golibs/renameioutil once tried and
// tested.
package aghrenameio
import (
"io/fs"
"github.com/AdguardTeam/golibs/errors"
)
// PendingFile is the interface for pending temporary files.
type PendingFile interface {
// Cleanup closes the file, and removes it without performing the renaming.
// To close and rename the file, use CloseReplace.
Cleanup() (err error)
// CloseReplace closes the temporary file and replaces the destination file
// with it, possibly atomically.
//
// This method is not safe for concurrent use by multiple goroutines.
CloseReplace() (err error)
// Write writes len(b) bytes from b to the File. It returns the number of
// bytes written and an error, if any. Write returns a non-nil error when n
// != len(b).
Write(b []byte) (n int, err error)
}
// NewPendingFile is a wrapper around [renameio.NewPendingFile] on Unix systems
// and [os.CreateTemp] on Windows.
func NewPendingFile(filePath string, mode fs.FileMode) (f PendingFile, err error) {
return newPendingFile(filePath, mode)
}
// WithDeferredCleanup is a helper that performs the necessary cleanups and
// finalizations of the temporary files based on the returned error.
func WithDeferredCleanup(returned error, file PendingFile) (err error) {
// Make sure that any error returned from here is marked as a deferred one.
if returned != nil {
return errors.WithDeferred(returned, file.Cleanup())
}
return errors.WithDeferred(nil, file.CloseReplace())
}

View File

@@ -0,0 +1,101 @@
package aghrenameio_test
import (
"io/fs"
"os"
"path/filepath"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghrenameio"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// testPerm is the common permission mode for tests.
const testPerm fs.FileMode = 0o644
// Common file data for tests.
var (
initialData = []byte("initial data\n")
newData = []byte("new data\n")
)
func TestPendingFile(t *testing.T) {
t.Parallel()
targetPath := newInitialFile(t)
f, err := aghrenameio.NewPendingFile(targetPath, testPerm)
require.NoError(t, err)
_, err = f.Write(newData)
require.NoError(t, err)
err = f.CloseReplace()
require.NoError(t, err)
gotData, err := os.ReadFile(targetPath)
require.NoError(t, err)
assert.Equal(t, newData, gotData)
}
// newInitialFile is a test helper that returns the path to the file containing
// [initialData].
func newInitialFile(t *testing.T) (targetPath string) {
t.Helper()
dir := t.TempDir()
targetPath = filepath.Join(dir, "target")
err := os.WriteFile(targetPath, initialData, 0o644)
require.NoError(t, err)
return targetPath
}
func TestWithDeferredCleanup(t *testing.T) {
t.Parallel()
const testError errors.Error = "test error"
testCases := []struct {
error error
name string
wantErrMsg string
wantData []byte
}{{
name: "success",
error: nil,
wantErrMsg: "",
wantData: newData,
}, {
name: "error",
error: testError,
wantErrMsg: testError.Error(),
wantData: initialData,
}}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
targetPath := newInitialFile(t)
f, err := aghrenameio.NewPendingFile(targetPath, testPerm)
require.NoError(t, err)
_, err = f.Write(newData)
require.NoError(t, err)
err = aghrenameio.WithDeferredCleanup(tc.error, f)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
gotData, err := os.ReadFile(targetPath)
require.NoError(t, err)
assert.Equal(t, tc.wantData, gotData)
})
}
}

View File

@@ -0,0 +1,48 @@
//go:build unix
package aghrenameio
import (
"io/fs"
"github.com/google/renameio/v2"
)
// pendingFile is a wrapper around [*renameio.PendingFile] making it an
// [io.WriteCloser].
type pendingFile struct {
file *renameio.PendingFile
}
// type check
var _ PendingFile = pendingFile{}
// Cleanup implements the [PendingFile] interface for pendingFile.
func (f pendingFile) Cleanup() (err error) {
return f.file.Cleanup()
}
// CloseReplace implements the [PendingFile] interface for pendingFile.
func (f pendingFile) CloseReplace() (err error) {
return f.file.CloseAtomicallyReplace()
}
// Write implements the [PendingFile] interface for pendingFile.
func (f pendingFile) Write(b []byte) (n int, err error) {
return f.file.Write(b)
}
// NewPendingFile is a wrapper around [renameio.NewPendingFile].
//
// f.Close must be called to finish the renaming.
func newPendingFile(filePath string, mode fs.FileMode) (f PendingFile, err error) {
file, err := renameio.NewPendingFile(filePath, renameio.WithPermissions(mode))
if err != nil {
// Don't wrap the error since it's informative enough as is.
return nil, err
}
return pendingFile{
file: file,
}, nil
}

View File

@@ -0,0 +1,74 @@
//go:build windows
package aghrenameio
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"github.com/AdguardTeam/golibs/errors"
)
// pendingFile is a wrapper around [*os.File] calling [os.Rename] in its Close
// method.
type pendingFile struct {
file *os.File
targetPath string
}
// type check
var _ PendingFile = (*pendingFile)(nil)
// Cleanup implements the [PendingFile] interface for *pendingFile.
func (f *pendingFile) Cleanup() (err error) {
closeErr := f.file.Close()
err = os.Remove(f.file.Name())
// Put closeErr into the deferred error because that's where it is usually
// expected.
return errors.WithDeferred(err, closeErr)
}
// CloseReplace implements the [PendingFile] interface for *pendingFile.
func (f *pendingFile) CloseReplace() (err error) {
err = f.file.Close()
if err != nil {
return fmt.Errorf("closing: %w", err)
}
err = os.Rename(f.file.Name(), f.targetPath)
if err != nil {
return fmt.Errorf("renaming: %w", err)
}
return nil
}
// Write implements the [PendingFile] interface for *pendingFile.
func (f *pendingFile) Write(b []byte) (n int, err error) {
return f.file.Write(b)
}
// NewPendingFile is a wrapper around [os.CreateTemp].
//
// f.Close must be called to finish the renaming.
func newPendingFile(filePath string, mode fs.FileMode) (f PendingFile, err error) {
// Use the same directory as the file itself, because moves across
// filesystems can be especially problematic.
file, err := os.CreateTemp(filepath.Dir(filePath), "")
if err != nil {
return nil, fmt.Errorf("opening pending file: %w", err)
}
err = file.Chmod(mode)
if err != nil {
return nil, fmt.Errorf("preparing pending file: %w", err)
}
return &pendingFile{
file: file,
targetPath: filePath,
}, nil
}

View File

@@ -2,12 +2,22 @@
package aghtest
import (
"crypto/sha256"
"io"
"net"
"testing"
"github.com/AdguardTeam/golibs/log"
)
const (
// ReqHost is the common request host for filtering tests.
ReqHost = "www.host.example"
// ReqFQDN is the common request FQDN for filtering tests.
ReqFQDN = ReqHost + "."
)
// ReplaceLogWriter moves logger output to w and uses Cleanup method of t to
// revert changes.
func ReplaceLogWriter(t testing.TB, w io.Writer) {
@@ -34,3 +44,10 @@ func ReplaceLogLevel(t testing.TB, l log.Level) {
t.Cleanup(func() { log.SetLevel(prev) })
log.SetLevel(l)
}
// HostToIPs is a helper that generates one IPv4 and one IPv6 address from host.
func HostToIPs(host string) (ipv4, ipv6 net.IP) {
hash := sha256.Sum256([]byte(host))
return net.IP(hash[:4]), net.IP(hash[4:20])
}

View File

@@ -2,9 +2,9 @@ package aghtest
import (
"context"
"io"
"io/fs"
"net"
"net/netip"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/AdGuardHome/internal/client"
@@ -19,67 +19,6 @@ import (
//
// Keep entities in this file in alphabetic order.
// Standard Library
// Package fs
// FS is a fake [fs.FS] implementation for tests.
type FS struct {
OnOpen func(name string) (fs.File, error)
}
// type check
var _ fs.FS = (*FS)(nil)
// Open implements the [fs.FS] interface for *FS.
func (fsys *FS) Open(name string) (fs.File, error) {
return fsys.OnOpen(name)
}
// type check
var _ fs.GlobFS = (*GlobFS)(nil)
// GlobFS is a fake [fs.GlobFS] implementation for tests.
type GlobFS struct {
// FS is embedded here to avoid implementing all it's methods.
FS
OnGlob func(pattern string) ([]string, error)
}
// Glob implements the [fs.GlobFS] interface for *GlobFS.
func (fsys *GlobFS) Glob(pattern string) ([]string, error) {
return fsys.OnGlob(pattern)
}
// type check
var _ fs.StatFS = (*StatFS)(nil)
// StatFS is a fake [fs.StatFS] implementation for tests.
type StatFS struct {
// FS is embedded here to avoid implementing all it's methods.
FS
OnStat func(name string) (fs.FileInfo, error)
}
// Stat implements the [fs.StatFS] interface for *StatFS.
func (fsys *StatFS) Stat(name string) (fs.FileInfo, error) {
return fsys.OnStat(name)
}
// Package io
// Writer is a fake [io.Writer] implementation for tests.
type Writer struct {
OnWrite func(b []byte) (n int, err error)
}
var _ io.Writer = (*Writer)(nil)
// Write implements the [io.Writer] interface for *Writer.
func (w *Writer) Write(b []byte) (n int, err error) {
return w.OnWrite(b)
}
// Module adguard-home
// Package aghos
@@ -177,18 +116,30 @@ func (p *AddressUpdater) UpdateAddress(ip netip.Addr, host string, info *whois.I
p.OnUpdateAddress(ip, host, info)
}
// Package filtering
// Resolver is a fake [filtering.Resolver] implementation for tests.
type Resolver struct {
OnLookupIP func(ctx context.Context, network, host string) (ips []net.IP, err error)
}
// LookupIP implements the [filtering.Resolver] interface for *Resolver.
func (r *Resolver) LookupIP(ctx context.Context, network, host string) (ips []net.IP, err error) {
return r.OnLookupIP(ctx, network, host)
}
// Package rdns
// Exchanger is a fake [rdns.Exchanger] implementation for tests.
type Exchanger struct {
OnExchange func(ip netip.Addr) (host string, err error)
OnExchange func(ip netip.Addr) (host string, ttl time.Duration, err error)
}
// type check
var _ rdns.Exchanger = (*Exchanger)(nil)
// Exchange implements [rdns.Exchanger] interface for *Exchanger.
func (e *Exchanger) Exchange(ip netip.Addr) (host string, err error) {
func (e *Exchanger) Exchange(ip netip.Addr) (host string, ttl time.Duration, err error) {
return e.OnExchange(ip)
}

View File

@@ -1,3 +1,11 @@
package aghtest_test
import (
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
)
// Put interface checks that cause import cycles here.
// type check
var _ filtering.Resolver = (*aghtest.Resolver)(nil)

View File

@@ -1,57 +0,0 @@
package aghtest
import (
"context"
"crypto/sha256"
"net"
"sync"
)
// TestResolver is a Resolver for tests.
type TestResolver struct {
counter int
counterLock sync.Mutex
}
// HostToIPs generates IPv4 and IPv6 from host.
func (r *TestResolver) HostToIPs(host string) (ipv4, ipv6 net.IP) {
hash := sha256.Sum256([]byte(host))
return net.IP(hash[:4]), net.IP(hash[4:20])
}
// LookupIP implements Resolver interface for *testResolver. It returns the
// slice of net.IP with IPv4 and IPv6 instances.
func (r *TestResolver) LookupIP(_ context.Context, _, host string) (ips []net.IP, err error) {
ipv4, ipv6 := r.HostToIPs(host)
addrs := []net.IP{ipv4, ipv6}
r.counterLock.Lock()
defer r.counterLock.Unlock()
r.counter++
return addrs, nil
}
// LookupHost implements Resolver interface for *testResolver. It returns the
// slice of IPv4 and IPv6 instances converted to strings.
func (r *TestResolver) LookupHost(host string) (addrs []string, err error) {
ipv4, ipv6 := r.HostToIPs(host)
r.counterLock.Lock()
defer r.counterLock.Unlock()
r.counter++
return []string{
ipv4.String(),
ipv6.String(),
}, nil
}
// Counter returns the number of requests handled.
func (r *TestResolver) Counter() int {
r.counterLock.Lock()
defer r.counterLock.Unlock()
return r.counter
}

View File

@@ -6,6 +6,7 @@ import (
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/golibs/errors"
@@ -39,7 +40,7 @@ func (EmptyAddrProc) Close() (_ error) { return nil }
type DefaultAddrProcConfig struct {
// DialContext is used to create TCP connections to WHOIS servers.
// DialContext must not be nil if [DefaultAddrProcConfig.UseWHOIS] is true.
DialContext whois.DialContextFunc
DialContext aghnet.DialContextFunc
// Exchanger is used to perform rDNS queries. Exchanger must not be nil if
// [DefaultAddrProcConfig.UseRDNS] is true.
@@ -57,6 +58,12 @@ type DefaultAddrProcConfig struct {
// immediately by [NewDefaultAddrProc].
InitialAddresses []netip.Addr
// CatchPanics, if true, makes the address processor catch and log panics.
//
// TODO(a.garipov): Consider better ways to do this or apply this method to
// other parts of the codebase.
CatchPanics bool
// UseRDNS, if true, enables resolving of client IP addresses using reverse
// DNS.
UseRDNS bool
@@ -150,7 +157,7 @@ func NewDefaultAddrProc(c *DefaultAddrProcConfig) (p *DefaultAddrProc) {
p.whois = newWHOIS(c.DialContext)
}
go p.process()
go p.process(c.CatchPanics)
for _, ip := range c.InitialAddresses {
p.Process(ip)
@@ -161,7 +168,7 @@ func NewDefaultAddrProc(c *DefaultAddrProcConfig) (p *DefaultAddrProc) {
// newWHOIS returns a whois.Interface instance using the given function for
// dialing.
func newWHOIS(dialFunc whois.DialContextFunc) (w whois.Interface) {
func newWHOIS(dialFunc aghnet.DialContextFunc) (w whois.Interface) {
// TODO(s.chzhen): Consider making configurable.
const (
// defaultTimeout is the timeout for WHOIS requests.
@@ -213,8 +220,10 @@ func (p *DefaultAddrProc) Process(ip netip.Addr) {
// process processes the incoming client IP-address information. It is intended
// to be used as a goroutine. Once clientIPs is closed, process exits.
func (p *DefaultAddrProc) process() {
defer log.OnPanic("addrProcessor.process")
func (p *DefaultAddrProc) process(catchPanics bool) {
if catchPanics {
defer log.OnPanic("addrProcessor.process")
}
log.Info("clients: processing addresses")

View File

@@ -2,6 +2,7 @@ package client_test
import (
"context"
"fmt"
"io"
"net"
"net/netip"
@@ -104,14 +105,15 @@ func TestDefaultAddrProc_Process_rDNS(t *testing.T) {
panic("not implemented")
},
Exchanger: &aghtest.Exchanger{
OnExchange: func(ip netip.Addr) (host string, err error) {
return tc.host, tc.rdnsErr
OnExchange: func(ip netip.Addr) (host string, ttl time.Duration, err error) {
return tc.host, 0, tc.rdnsErr
},
},
PrivateSubnets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
AddressUpdater: &aghtest.AddressUpdater{
OnUpdateAddress: newOnUpdateAddress(tc.wantUpd, updIPCh, updHostCh, updInfoCh),
},
CatchPanics: false,
UseRDNS: true,
UsePrivateRDNS: tc.usePrivate,
UseWHOIS: false,
@@ -146,8 +148,8 @@ func newOnUpdateAddress(
infos chan<- *whois.Info,
) (f func(ip netip.Addr, host string, info *whois.Info)) {
return func(ip netip.Addr, host string, info *whois.Info) {
if !want {
panic("got unexpected update")
if !want && (host != "" || info != nil) {
panic(fmt.Errorf("got unexpected update for %v with %q and %v", ip, host, info))
}
ips <- ip
@@ -214,7 +216,7 @@ func TestDefaultAddrProc_Process_WHOIS(t *testing.T) {
return whoisConn, nil
},
Exchanger: &aghtest.Exchanger{
OnExchange: func(_ netip.Addr) (host string, err error) {
OnExchange: func(_ netip.Addr) (_ string, _ time.Duration, _ error) {
panic("not implemented")
},
},
@@ -222,6 +224,7 @@ func TestDefaultAddrProc_Process_WHOIS(t *testing.T) {
AddressUpdater: &aghtest.AddressUpdater{
OnUpdateAddress: newOnUpdateAddress(tc.wantUpd, updIPCh, updHostCh, updInfoCh),
},
CatchPanics: false,
UseRDNS: false,
UsePrivateRDNS: false,
UseWHOIS: true,

View File

@@ -9,7 +9,7 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/google/renameio/maybe"
"github.com/google/renameio/v2/maybe"
"golang.org/x/exp/slices"
)

View File

@@ -90,7 +90,7 @@ func newAccessCtx(allowed, blocked, blockedHosts []string) (a *accessManager, er
lists := []filterlist.RuleList{
&filterlist.StringRuleList{
ID: int(0),
ID: 0,
RulesText: b.String(),
IgnoreCosmetic: true,
},

View File

@@ -31,6 +31,7 @@ func TestIsBlockedHost(t *testing.T) {
"*.host.com",
"||host3.com^",
"||*^$dnstype=HTTPS",
"|.^",
})
require.NoError(t, err)
@@ -94,6 +95,11 @@ func TestIsBlockedHost(t *testing.T) {
name: "by_qtype_other",
host: "site-with-https-record.example",
qt: dns.TypeA,
}, {
want: assert.True,
name: "ns_root",
host: ".",
qt: dns.TypeNS,
}}
for _, tc := range testCases {

View File

@@ -10,7 +10,7 @@ import (
"github.com/AdguardTeam/golibs/log"
)
// DialContext is a [whois.DialContextFunc] that uses s to resolve hostnames.
// DialContext is an [aghnet.DialContextFunc] that uses s to resolve hostnames.
func (s *Server) DialContext(ctx context.Context, network, addr string) (conn net.Conn, err error) {
log.Debug("dnsforward: dialing %q for network %q", addr, network)

View File

@@ -244,7 +244,7 @@ func (s *Server) Close() {
s.serverLock.Lock()
defer s.serverLock.Unlock()
s.dnsFilter = nil
// TODO(s.chzhen): Remove it.
s.stats = nil
s.queryLog = nil
s.dnsProxy = nil
@@ -316,13 +316,13 @@ const (
var _ rdns.Exchanger = (*Server)(nil)
// Exchange implements the [rdns.Exchanger] interface for *Server.
func (s *Server) Exchange(ip netip.Addr) (host string, err error) {
func (s *Server) Exchange(ip netip.Addr) (host string, ttl time.Duration, err error) {
s.serverLock.RLock()
defer s.serverLock.RUnlock()
arpa, err := netutil.IPToReversedAddr(ip.AsSlice())
if err != nil {
return "", fmt.Errorf("reversing ip: %w", err)
return "", 0, fmt.Errorf("reversing ip: %w", err)
}
arpa = dns.Fqdn(arpa)
@@ -346,43 +346,66 @@ func (s *Server) Exchange(ip netip.Addr) (host string, err error) {
}
var resolver *proxy.Proxy
var errMsg string
if s.privateNets.Contains(ip.AsSlice()) {
if !s.conf.UsePrivateRDNS {
return "", nil
return "", 0, nil
}
resolver = s.localResolvers
errMsg = "resolving a private address: %w"
s.recDetector.add(*req)
} else {
resolver = s.internalProxy
errMsg = "resolving an address: %w"
}
if err = resolver.Resolve(dctx); err != nil {
return "", err
return "", 0, fmt.Errorf(errMsg, err)
}
return hostFromPTR(dctx.Res)
}
// hostFromPTR returns domain name from the PTR response or error.
func hostFromPTR(resp *dns.Msg) (host string, err error) {
func hostFromPTR(resp *dns.Msg) (host string, ttl time.Duration, err error) {
// Distinguish between NODATA response and a failed request.
if resp.Rcode != dns.RcodeSuccess && resp.Rcode != dns.RcodeNameError {
return "", fmt.Errorf(
return "", 0, fmt.Errorf(
"received %s response: %w",
dns.RcodeToString[resp.Rcode],
ErrRDNSFailed,
)
}
var ttlSec uint32
log.Debug("dnsforward: resolving ptr, received %d answers", len(resp.Answer))
for _, ans := range resp.Answer {
ptr, ok := ans.(*dns.PTR)
if ok {
return strings.TrimSuffix(ptr.Ptr, "."), nil
if !ok {
continue
}
// Respect zero TTL records since some DNS servers use it to
// locally-resolved addresses.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/6046.
if ptr.Hdr.Ttl >= ttlSec {
host = ptr.Ptr
ttlSec = ptr.Hdr.Ttl
}
}
return "", ErrRDNSNoData
if host != "" {
// NOTE: Don't use [aghnet.NormalizeDomain] to retain original letter
// case.
host = strings.TrimSuffix(host, ".")
ttl = time.Duration(ttlSec) * time.Second
return host, ttl, nil
}
return "", 0, ErrRDNSNoData
}
// Start starts the DNS server.
@@ -449,6 +472,7 @@ func (s *Server) filterOurDNSAddrs(addrs []string) (filtered []string, err error
}
ourAddrsSet := stringutil.NewSet(ourAddrs...)
log.Debug("dnsforward: filtering out %s", ourAddrsSet.String())
// TODO(e.burkov): The approach of subtracting sets of strings is not
// really applicable here since in case of listening on all network
@@ -485,7 +509,7 @@ func (s *Server) setupLocalResolvers() (err error) {
PreferIPv6: s.conf.BootstrapPreferIPv6,
})
if err != nil {
return fmt.Errorf("parsing private upstreams: %w", err)
return fmt.Errorf("preparing private upstreams: %w", err)
}
s.localResolvers = &proxy.Proxy{
@@ -562,9 +586,20 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
s.recDetector.clear()
s.setupAddrProc()
s.registerHandlers()
return nil
}
// setupAddrProc initializes the address processor. For internal use only.
func (s *Server) setupAddrProc() {
// TODO(a.garipov): This is a crutch for tests; remove.
if s.conf.AddrProcConf == nil {
// TODO(a.garipov): This is a crutch for tests; remove.
s.conf.AddrProcConf = &client.DefaultAddrProcConfig{}
}
if s.conf.AddrProcConf.AddressUpdater == nil {
s.addrProc = client.EmptyAddrProc{}
} else {
c := s.conf.AddrProcConf
@@ -579,10 +614,6 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
// logic is moved to package client.
c.InitialAddresses = nil
}
s.registerHandlers()
return nil
}
// validateBlockingMode returns an error if the blocking mode data aren't valid.

View File

@@ -1,6 +1,7 @@
package dnsforward
import (
"context"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
@@ -71,13 +72,6 @@ func startDeferStop(t *testing.T, s *Server) {
testutil.CleanupAndRequireSuccess(t, s.Stop)
}
// packageUpstreamVariableMu is used to serialize access to the package-level
// variables of package upstream.
//
// TODO(s.chzhen): Move these parameters to upstream options and remove this
// crutch.
var packageUpstreamVariableMu = &sync.Mutex{}
func createTestServer(
t *testing.T,
filterConf *filtering.Config,
@@ -86,9 +80,6 @@ func createTestServer(
) (s *Server) {
t.Helper()
packageUpstreamVariableMu.Lock()
defer packageUpstreamVariableMu.Unlock()
rules := `||nxdomain.example.org
||NULL.example.org^
127.0.0.1 host.example.org
@@ -240,6 +231,17 @@ func createTestMessageWithType(host string, qtype uint16) *dns.Msg {
return req
}
// newResp returns the new DNS response with response code set to rcode, req
// used as request, and rrs added.
func newResp(rcode int, req *dns.Msg, ans []dns.RR) (resp *dns.Msg) {
resp = (&dns.Msg{}).SetRcode(req, rcode)
resp.RecursionAvailable = true
resp.Compress = true
resp.Answer = ans
return resp
}
func assertGoogleAResponse(t *testing.T, reply *dns.Msg) {
assertResponse(t, reply, net.IP{8, 8, 8, 8})
}
@@ -344,7 +346,7 @@ func TestServer_timeout(t *testing.T) {
},
}
s, err := NewServer(DNSCreateParams{})
s, err := NewServer(DNSCreateParams{DNSFilter: &filtering.DNSFilter{}})
require.NoError(t, err)
err = s.Prepare(srvConf)
@@ -354,7 +356,7 @@ func TestServer_timeout(t *testing.T) {
})
t.Run("default", func(t *testing.T) {
s, err := NewServer(DNSCreateParams{})
s, err := NewServer(DNSCreateParams{DNSFilter: &filtering.DNSFilter{}})
require.NoError(t, err)
s.conf.FilteringConfig.BlockingMode = BlockingModeDefault
@@ -467,7 +469,14 @@ func TestServerRace(t *testing.T) {
}
func TestSafeSearch(t *testing.T) {
resolver := &aghtest.TestResolver{}
resolver := &aghtest.Resolver{
OnLookupIP: func(_ context.Context, _, host string) (ips []net.IP, err error) {
ip4, ip6 := aghtest.HostToIPs(host)
return []net.IP{ip4, ip6}, nil
},
}
safeSearchConf := filtering.SafeSearchConfig{
Enabled: true,
Google: true,
@@ -506,7 +515,7 @@ func TestSafeSearch(t *testing.T) {
client := &dns.Client{}
yandexIP := net.IP{213, 180, 193, 56}
googleIP, _ := resolver.HostToIPs("forcesafesearch.google.com")
googleIP, _ := aghtest.HostToIPs("forcesafesearch.google.com")
testCases := []struct {
host string
@@ -954,7 +963,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
Upstream: aghtest.NewBlockUpstream(hostname, true),
})
ans4, _ := (&aghtest.TestResolver{}).HostToIPs(hostname)
ans4, _ := aghtest.HostToIPs(hostname)
filterConf := &filtering.Config{
SafeBrowsingEnabled: true,
@@ -1292,25 +1301,57 @@ func TestNewServer(t *testing.T) {
}
}
// doubleTTL is a helper function that returns a clone of DNS PTR with appended
// copy of first answer record with doubled TTL.
func doubleTTL(msg *dns.Msg) (resp *dns.Msg) {
if msg == nil {
return nil
}
if len(msg.Answer) == 0 {
return msg
}
rec := msg.Answer[0]
ptr, ok := rec.(*dns.PTR)
if !ok {
return msg
}
clone := *ptr
clone.Hdr.Ttl *= 2
msg.Answer = append(msg.Answer, &clone)
return msg
}
func TestServer_Exchange(t *testing.T) {
const (
onesHost = "one.one.one.one"
twosHost = "two.two.two.two"
localDomainHost = "local.domain"
defaultTTL = time.Second * 60
)
var (
onesIP = netip.MustParseAddr("1.1.1.1")
twosIP = netip.MustParseAddr("2.2.2.2")
localIP = netip.MustParseAddr("192.168.1.1")
)
revExtIPv4, err := netutil.IPToReversedAddr(onesIP.AsSlice())
onesRevExtIPv4, err := netutil.IPToReversedAddr(onesIP.AsSlice())
require.NoError(t, err)
twosRevExtIPv4, err := netutil.IPToReversedAddr(twosIP.AsSlice())
require.NoError(t, err)
extUpstream := &aghtest.UpstreamMock{
OnAddress: func() (addr string) { return "external.upstream.example" },
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
return aghalg.Coalesce(
aghtest.MatchedResponse(req, dns.TypePTR, revExtIPv4, onesHost),
aghtest.MatchedResponse(req, dns.TypePTR, onesRevExtIPv4, onesHost),
doubleTTL(aghtest.MatchedResponse(req, dns.TypePTR, twosRevExtIPv4, twosHost)),
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
), nil
},
@@ -1334,6 +1375,24 @@ func TestServer_Exchange(t *testing.T) {
refusingUpstream := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
return new(dns.Msg).SetRcode(req, dns.RcodeRefused), nil
})
zeroTTLUps := &aghtest.UpstreamMock{
OnAddress: func() (addr string) { return "zero.ttl.example" },
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
resp = new(dns.Msg).SetReply(req)
hdr := dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: dns.TypePTR,
Class: dns.ClassINET,
Ttl: 0,
}
resp.Answer = []dns.RR{&dns.PTR{
Hdr: hdr,
Ptr: localDomainHost,
}}
return resp, nil
},
}
srv := &Server{
recDetector: newRecursionDetector(0, 1),
@@ -1350,47 +1409,68 @@ func TestServer_Exchange(t *testing.T) {
srv.privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed)
testCases := []struct {
name string
want string
req netip.Addr
wantErr error
locUpstream upstream.Upstream
req netip.Addr
name string
want string
wantTTL time.Duration
}{{
name: "external_good",
want: onesHost,
wantErr: nil,
locUpstream: nil,
req: onesIP,
wantTTL: defaultTTL,
}, {
name: "local_good",
want: localDomainHost,
wantErr: nil,
locUpstream: locUpstream,
req: localIP,
wantTTL: defaultTTL,
}, {
name: "upstream_error",
want: "",
wantErr: aghtest.ErrUpstream,
locUpstream: errUpstream,
req: localIP,
wantTTL: 0,
}, {
name: "empty_answer_error",
want: "",
wantErr: ErrRDNSNoData,
locUpstream: locUpstream,
req: netip.MustParseAddr("192.168.1.2"),
wantTTL: 0,
}, {
name: "invalid_answer",
want: "",
wantErr: ErrRDNSNoData,
locUpstream: nonPtrUpstream,
req: localIP,
wantTTL: 0,
}, {
name: "refused",
want: "",
wantErr: ErrRDNSFailed,
locUpstream: refusingUpstream,
req: localIP,
wantTTL: 0,
}, {
name: "longest_ttl",
want: twosHost,
wantErr: nil,
locUpstream: nil,
req: twosIP,
wantTTL: defaultTTL * 2,
}, {
name: "zero_ttl",
want: localDomainHost,
wantErr: nil,
locUpstream: zeroTTLUps,
req: localIP,
wantTTL: 0,
}}
for _, tc := range testCases {
@@ -1404,17 +1484,19 @@ func TestServer_Exchange(t *testing.T) {
}
t.Run(tc.name, func(t *testing.T) {
host, eerr := srv.Exchange(tc.req)
host, ttl, eerr := srv.Exchange(tc.req)
require.ErrorIs(t, eerr, tc.wantErr)
assert.Equal(t, tc.want, host)
assert.Equal(t, tc.wantTTL, ttl)
})
}
t.Run("resolving_disabled", func(t *testing.T) {
srv.conf.UsePrivateRDNS = false
t.Cleanup(func() { srv.conf.UsePrivateRDNS = true })
host, eerr := srv.Exchange(localIP)
host, _, eerr := srv.Exchange(localIP)
require.NoError(t, eerr)
assert.Empty(t, host)

View File

@@ -3,8 +3,10 @@ package dnsforward
import (
"encoding/binary"
"fmt"
"net"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/log"
@@ -33,9 +35,9 @@ func (s *Server) beforeRequestHandler(
if len(pctx.Req.Question) == 1 {
q := pctx.Req.Question[0]
qt := q.Qtype
host := strings.TrimSuffix(q.Name, ".")
host := aghnet.NormalizeDomain(q.Name)
if s.access.isBlockedHost(host, qt) {
log.Debug("request %s %s is in access blocklist", dns.Type(qt), host)
log.Debug("access: request %s %s is in access blocklist", dns.Type(qt), host)
return s.preBlockedResponse(pctx)
}
@@ -79,7 +81,12 @@ func (s *Server) filterDNSRequest(dctx *dnsContext) (res *filtering.Result, err
res = &resVal
switch {
case res.IsFiltered:
log.Tracef("host %q is filtered, reason %q, rule: %q", host, res.Reason, res.Rules[0].Text)
log.Debug(
"dnsforward: host %q is filtered, reason: %q; rule: %q",
host,
res.Reason,
res.Rules[0].Text,
)
pctx.Res = s.genDNSFilterMessage(pctx, res)
case res.Reason.In(filtering.Rewritten, filtering.RewrittenRule) &&
res.CanonName != "" &&
@@ -139,10 +146,6 @@ func (s *Server) checkHostRules(host string, rrtype uint16, setts *filtering.Set
s.serverLock.RLock()
defer s.serverLock.RUnlock()
if s.dnsFilter == nil {
return nil, nil
}
var res filtering.Result
res, err = s.dnsFilter.CheckHostRules(host, rrtype, setts)
if err != nil {
@@ -170,26 +173,33 @@ func (s *Server) filterDNSResponse(
case *dns.CNAME:
host = strings.TrimSuffix(a.Target, ".")
rrtype = dns.TypeCNAME
res, err = s.checkHostRules(host, rrtype, setts)
case *dns.A:
host = a.A.String()
rrtype = dns.TypeA
res, err = s.checkHostRules(host, rrtype, setts)
case *dns.AAAA:
host = a.AAAA.String()
rrtype = dns.TypeAAAA
res, err = s.checkHostRules(host, rrtype, setts)
case *dns.HTTPS:
res, err = s.filterHTTPSRecords(a, setts)
default:
continue
}
log.Debug("dnsforward: checking %s %s for %s", dns.Type(rrtype), host, a.Header().Name)
log.Debug("dnsforward: checked %s %s for %s", dns.Type(rrtype), host, a.Header().Name)
res, err = s.checkHostRules(host, rrtype, setts)
if err != nil {
return nil, err
} else if res == nil {
continue
} else if res.IsFiltered {
pctx.Res = s.genDNSFilterMessage(pctx, res)
log.Debug("DNSFwd: Matched %s by response: %s", pctx.Req.Question[0].Name, host)
log.Debug("dnsforward: matched %q by response: %q", pctx.Req.Question[0].Name, host)
return res, nil
}
@@ -197,3 +207,56 @@ func (s *Server) filterDNSResponse(
return nil, nil
}
// filterHTTPSRecords filters HTTPS answers information through all rule list
// filters of the server filters.
func (s *Server) filterHTTPSRecords(
rr *dns.HTTPS,
setts *filtering.Settings,
) (r *filtering.Result, err error) {
for _, kv := range rr.Value {
var ips []net.IP
switch hint := kv.(type) {
case *dns.SVCBIPv4Hint:
ips = hint.Hint
case *dns.SVCBIPv6Hint:
ips = hint.Hint
default:
// Go on.
}
if len(ips) == 0 {
continue
}
r, err = s.filterSVCBHint(ips, setts)
if err != nil {
return nil, fmt.Errorf("filtering svcb hints: %w", err)
}
if r != nil {
return r, nil
}
}
return nil, nil
}
// filterSVCBHint filters SVCB hint information.
func (s *Server) filterSVCBHint(
hint []net.IP,
setts *filtering.Settings,
) (res *filtering.Result, err error) {
for _, h := range hint {
res, err = s.checkHostRules(h.String(), dns.TypeHTTPS, setts)
if err != nil {
return nil, fmt.Errorf("checking rules for %s: %w", h, err)
}
if res != nil && res.IsFiltered {
return res, nil
}
}
return nil, nil
}

View File

@@ -2,6 +2,7 @@ package dnsforward
import (
"net"
"net/netip"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
@@ -14,7 +15,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
func TestHandleDNSRequest_handleDNSRequest(t *testing.T) {
rules := `
||blocked.domain^
@@||allowed.domain^
@@ -23,6 +24,7 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
||::1^$dnstype=~AAAA
0.0.0.0 duplicate.domain
0.0.0.0 duplicate.domain
0.0.0.0 blocked.by.hostrule
`
forwardConf := ServerConfig{
@@ -73,12 +75,19 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
startDeferStop(t, s)
testCases := []struct {
req *dns.Msg
name string
wantAns []dns.RR
req *dns.Msg
name string
wantRCode int
wantAns []dns.RR
}{{
req: createTestMessage("cname.exception."),
name: "cname_exception",
req: createTestMessage(aghtest.ReqFQDN),
name: "pass",
wantRCode: dns.RcodeNameError,
wantAns: nil,
}, {
req: createTestMessage("cname.exception."),
name: "cname_exception",
wantRCode: dns.RcodeSuccess,
wantAns: []dns.RR{&dns.CNAME{
Hdr: dns.RR_Header{
Name: "cname.exception.",
@@ -87,8 +96,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
Target: "cname.specific.",
}},
}, {
req: createTestMessage("should.block."),
name: "blocked_by_cname",
req: createTestMessage("should.block."),
name: "blocked_by_cname",
wantRCode: dns.RcodeSuccess,
wantAns: []dns.RR{&dns.A{
Hdr: dns.RR_Header{
Name: "should.block.",
@@ -98,8 +108,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
A: netutil.IPv4Zero(),
}},
}, {
req: createTestMessage("a.exception."),
name: "a_exception",
req: createTestMessage("a.exception."),
name: "a_exception",
wantRCode: dns.RcodeSuccess,
wantAns: []dns.RR{&dns.A{
Hdr: dns.RR_Header{
Name: "a.exception.",
@@ -108,8 +119,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
A: net.IP{0, 0, 0, 1},
}},
}, {
req: createTestMessageWithType("aaaa.exception.", dns.TypeAAAA),
name: "aaaa_exception",
req: createTestMessageWithType("aaaa.exception.", dns.TypeAAAA),
name: "aaaa_exception",
wantRCode: dns.RcodeSuccess,
wantAns: []dns.RR{&dns.AAAA{
Hdr: dns.RR_Header{
Name: "aaaa.exception.",
@@ -118,8 +130,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
AAAA: net.ParseIP("::1"),
}},
}, {
req: createTestMessage("allowed.first."),
name: "allowed_first",
req: createTestMessage("allowed.first."),
name: "allowed_first",
wantRCode: dns.RcodeSuccess,
wantAns: []dns.RR{&dns.A{
Hdr: dns.RR_Header{
Name: "allowed.first.",
@@ -129,8 +142,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
A: netutil.IPv4Zero(),
}},
}, {
req: createTestMessage("blocked.first."),
name: "blocked_first",
req: createTestMessage("blocked.first."),
name: "blocked_first",
wantRCode: dns.RcodeSuccess,
wantAns: []dns.RR{&dns.A{
Hdr: dns.RR_Header{
Name: "blocked.first.",
@@ -140,8 +154,9 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
A: netutil.IPv4Zero(),
}},
}, {
req: createTestMessage("duplicate.domain."),
name: "duplicate_domain",
req: createTestMessage("duplicate.domain."),
name: "duplicate_domain",
wantRCode: dns.RcodeSuccess,
wantAns: []dns.RR{&dns.A{
Hdr: dns.RR_Header{
Name: "duplicate.domain.",
@@ -150,6 +165,16 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
},
A: netutil.IPv4Zero(),
}},
}, {
req: createTestMessageWithType("blocked.domain.", dns.TypeHTTPS),
name: "blocked_https_req",
wantRCode: dns.RcodeSuccess,
wantAns: nil,
}, {
req: createTestMessageWithType("blocked.by.hostrule.", dns.TypeHTTPS),
name: "blocked_host_rule_https_req",
wantRCode: dns.RcodeSuccess,
wantAns: nil,
}}
for _, tc := range testCases {
@@ -164,7 +189,175 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, dctx.Res)
assert.Equal(t, tc.wantRCode, dctx.Res.Rcode)
assert.Equal(t, tc.wantAns, dctx.Res.Answer)
})
}
}
func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
const (
passedIPv4Str = "1.1.1.1"
blockedIPv4Str = "1.2.3.4"
blockedIPv6Str = "1234::cdef"
blockRules = blockedIPv4Str + "\n" + blockedIPv6Str + "\n"
)
var (
passedIPv4 net.IP = netip.MustParseAddr(passedIPv4Str).AsSlice()
blockedIPv4 net.IP = netip.MustParseAddr(blockedIPv4Str).AsSlice()
blockedIPv6 net.IP = netip.MustParseAddr(blockedIPv6Str).AsSlice()
)
filters := []filtering.Filter{{
ID: 0, Data: []byte(blockRules),
}}
f, err := filtering.New(&filtering.Config{}, filters)
require.NoError(t, err)
f.SetEnabled(true)
s, err := NewServer(DNSCreateParams{
DHCPServer: testDHCP,
DNSFilter: f,
PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
})
require.NoError(t, err)
testCases := []struct {
req *dns.Msg
name string
wantRule string
respAns []dns.RR
}{{
name: "pass",
req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeA),
wantRule: "",
respAns: []dns.RR{&dns.A{
Hdr: dns.RR_Header{
Name: aghtest.ReqFQDN,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
},
A: passedIPv4,
}},
}, {
name: "ipv4",
req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeA),
wantRule: blockedIPv4Str,
respAns: []dns.RR{&dns.A{
Hdr: dns.RR_Header{
Name: aghtest.ReqFQDN,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
},
A: blockedIPv4,
}},
}, {
name: "ipv6",
req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeAAAA),
wantRule: blockedIPv6Str,
respAns: []dns.RR{&dns.AAAA{
Hdr: dns.RR_Header{
Name: aghtest.ReqFQDN,
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
},
AAAA: blockedIPv6,
}},
}, {
name: "ipv4hint",
req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeHTTPS),
wantRule: blockedIPv4Str,
respAns: newSVCBHintsAnswer(
aghtest.ReqFQDN,
[]dns.SVCBKeyValue{
&dns.SVCBIPv4Hint{Hint: []net.IP{blockedIPv4}},
&dns.SVCBIPv6Hint{Hint: []net.IP{}},
},
),
}, {
name: "ipv6hint",
req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeHTTPS),
wantRule: blockedIPv6Str,
respAns: newSVCBHintsAnswer(
aghtest.ReqFQDN,
[]dns.SVCBKeyValue{
&dns.SVCBIPv4Hint{Hint: []net.IP{}},
&dns.SVCBIPv6Hint{Hint: []net.IP{blockedIPv6}},
},
),
}, {
name: "ipv4_ipv6_hints",
req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeHTTPS),
wantRule: blockedIPv4Str,
respAns: newSVCBHintsAnswer(
aghtest.ReqFQDN,
[]dns.SVCBKeyValue{
&dns.SVCBIPv4Hint{Hint: []net.IP{blockedIPv4}},
&dns.SVCBIPv6Hint{Hint: []net.IP{blockedIPv6}},
},
),
}, {
name: "pass_hints",
req: createTestMessageWithType(aghtest.ReqFQDN, dns.TypeHTTPS),
wantRule: "",
respAns: newSVCBHintsAnswer(
aghtest.ReqFQDN,
[]dns.SVCBKeyValue{
&dns.SVCBIPv4Hint{Hint: []net.IP{passedIPv4}},
&dns.SVCBIPv6Hint{Hint: []net.IP{}},
},
),
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resp := newResp(dns.RcodeSuccess, tc.req, tc.respAns)
pctx := &proxy.DNSContext{
Proto: proxy.ProtoUDP,
Req: tc.req,
Res: resp,
Addr: &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 1},
}
res, rErr := s.filterDNSResponse(pctx, &filtering.Settings{
ProtectionEnabled: true,
FilteringEnabled: true,
})
require.NoError(t, rErr)
if tc.wantRule == "" {
assert.Nil(t, res)
return
}
want := &filtering.Result{
IsFiltered: true,
Reason: filtering.FilteredBlockList,
Rules: []*filtering.ResultRule{{
Text: tc.wantRule,
}},
}
assert.Equal(t, want, res)
})
}
}
// newSVCBHintsAnswer returns a test HTTPS answer RRs with SVCB hints.
func newSVCBHintsAnswer(target string, hints []dns.SVCBKeyValue) (rrs []dns.RR) {
return []dns.RR{&dns.HTTPS{
SVCB: dns.SVCB{
Hdr: dns.RR_Header{
Name: target,
Rrtype: dns.TypeHTTPS,
Class: dns.ClassINET,
},
Target: target,
Value: hints,
},
}}
}

View File

@@ -667,7 +667,7 @@ func (s *Server) parseUpstreamLine(
PreferIPv6: opts.PreferIPv6,
}
if s.dnsFilter != nil && s.dnsFilter.EtcHosts != nil {
if s.dnsFilter.EtcHosts != nil {
resolved := s.resolveUpstreamHost(extractUpstreamHost(upstreamAddr))
sortNetIPAddrs(resolved, opts.PreferIPv6)
opts.ServerIPAddrs = resolved

View File

@@ -58,12 +58,13 @@ func (s *Server) genDNSFilterMessage(
res *filtering.Result,
) (resp *dns.Msg) {
req := dctx.Req
if qt := req.Question[0].Qtype; qt != dns.TypeA && qt != dns.TypeAAAA {
qt := req.Question[0].Qtype
if qt != dns.TypeA && qt != dns.TypeAAAA {
if s.conf.BlockingMode == BlockingModeNullIP {
return s.makeResponse(req)
}
return s.genNXDomain(req)
return s.newMsgNODATA(req)
}
switch res.Reason {
@@ -314,6 +315,17 @@ func (s *Server) makeResponseREFUSED(request *dns.Msg) *dns.Msg {
return &resp
}
// newMsgNODATA returns a properly initialized NODATA response.
//
// See https://www.rfc-editor.org/rfc/rfc2308#section-2.2.
func (s *Server) newMsgNODATA(req *dns.Msg) (resp *dns.Msg) {
resp = (&dns.Msg{}).SetRcode(req, dns.RcodeSuccess)
resp.RecursionAvailable = true
resp.Ns = s.genSOA(req)
return resp
}
func (s *Server) genNXDomain(request *dns.Msg) *dns.Msg {
resp := dns.Msg{}
resp.SetRcode(request, dns.RcodeNameError)

View File

@@ -719,6 +719,8 @@ func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
if s.conf.UsePrivateRDNS {
s.recDetector.add(*pctx.Req)
if err := s.localResolvers.Resolve(pctx); err != nil {
log.Debug("dnsforward: resolving private address: %s", err)
// Generate the server failure if the private upstream configuration
// is empty.
//
@@ -760,10 +762,6 @@ func (s *Server) processFilteringBeforeRequest(ctx *dnsContext) (rc resultCode)
s.serverLock.RLock()
defer s.serverLock.RUnlock()
if s.dnsFilter == nil {
return resultCodeSuccess
}
var err error
if ctx.result, err = s.filterDNSRequest(ctx); err != nil {
ctx.err = err
@@ -970,7 +968,7 @@ func (s *Server) filterAfterResponse(dctx *dnsContext, pctx *proxy.DNSContext) (
// Check the response only if it's from an upstream. Don't check the
// response if the protection is disabled since dnsrewrite rules aren't
// applied to it anyway.
if !dctx.protectionEnabled || !dctx.responseFromUpstream || s.dnsFilter == nil {
if !dctx.protectionEnabled || !dctx.responseFromUpstream {
return resultCodeSuccess
}

View File

@@ -139,10 +139,14 @@ func (s *Server) updateStats(
clientIP string,
) {
pctx := ctx.proxyCtx
e := stats.Entry{
e := &stats.Entry{
Domain: aghnet.NormalizeDomain(pctx.Req.Question[0].Name),
Result: stats.RNotFiltered,
Time: uint32(elapsed / 1000),
Time: elapsed,
}
if pctx.Upstream != nil {
e.Upstream = pctx.Upstream.Address()
}
if clientID := ctx.clientID; clientID != "" {

View File

@@ -41,11 +41,11 @@ type testStats struct {
// without actually implementing all methods.
stats.Interface
lastEntry stats.Entry
lastEntry *stats.Entry
}
// Update implements the [stats.Interface] interface for *testStats.
func (l *testStats) Update(e stats.Entry) {
func (l *testStats) Update(e *stats.Entry) {
if e.Domain == "" {
return
}

View File

@@ -42,16 +42,6 @@ func (s *Server) loadUpstreams() (upstreams []string, err error) {
// prepareUpstreamSettings sets upstream DNS server settings.
func (s *Server) prepareUpstreamSettings() (err error) {
// Use a customized set of RootCAs, because Go's default mechanism of
// loading TLS roots does not always work properly on some routers so we're
// loading roots manually and pass it here.
//
// See [aghtls.SystemRootCAs].
//
// TODO(a.garipov): Investigate if that's true.
upstream.RootCAs = s.conf.TLSv12Roots
upstream.CipherSuites = s.conf.TLSCiphers
// Load upstreams either from the file, or from the settings
var upstreams []string
upstreams, err = s.loadUpstreams()
@@ -64,6 +54,15 @@ func (s *Server) prepareUpstreamSettings() (err error) {
Timeout: s.conf.UpstreamTimeout,
HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams),
PreferIPv6: s.conf.BootstrapPreferIPv6,
// Use a customized set of RootCAs, because Go's default mechanism of
// loading TLS roots does not always work properly on some routers so we're
// loading roots manually and pass it here.
//
// See [aghtls.SystemRootCAs].
//
// TODO(a.garipov): Investigate if that's true.
RootCAs: s.conf.TLSv12Roots,
CipherSuites: s.conf.TLSCiphers,
})
if err != nil {
return fmt.Errorf("preparing upstream config: %w", err)
@@ -95,7 +94,7 @@ func (s *Server) prepareUpstreamConfig(
uc.Upstreams = defaultUpstreamConfig.Upstreams
}
if s.dnsFilter != nil && s.dnsFilter.EtcHosts != nil {
if s.dnsFilter.EtcHosts != nil {
err = s.replaceUpstreamsWithHosts(uc, opts)
if err != nil {
return nil, fmt.Errorf("resolving upstreams with hosts: %w", err)

View File

@@ -11,6 +11,7 @@ import (
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghrenameio"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
@@ -83,53 +84,53 @@ func (d *DNSFilter) filterSetProperties(
filters = d.WhitelistFilters
}
i := slices.IndexFunc(filters, func(filt FilterYAML) bool { return filt.URL == listURL })
i := slices.IndexFunc(filters, func(flt FilterYAML) bool { return flt.URL == listURL })
if i == -1 {
return false, errFilterNotExist
}
filt := &filters[i]
flt := &filters[i]
log.Debug(
"filtering: set name to %q, url to %s, enabled to %t for filter %s",
newList.Name,
newList.URL,
newList.Enabled,
filt.URL,
flt.URL,
)
defer func(oldURL, oldName string, oldEnabled bool, oldUpdated time.Time, oldRulesCount int) {
if err != nil {
filt.URL = oldURL
filt.Name = oldName
filt.Enabled = oldEnabled
filt.LastUpdated = oldUpdated
filt.RulesCount = oldRulesCount
flt.URL = oldURL
flt.Name = oldName
flt.Enabled = oldEnabled
flt.LastUpdated = oldUpdated
flt.RulesCount = oldRulesCount
}
}(filt.URL, filt.Name, filt.Enabled, filt.LastUpdated, filt.RulesCount)
}(flt.URL, flt.Name, flt.Enabled, flt.LastUpdated, flt.RulesCount)
filt.Name = newList.Name
flt.Name = newList.Name
if filt.URL != newList.URL {
if flt.URL != newList.URL {
if d.filterExistsLocked(newList.URL) {
return false, errFilterExists
}
shouldRestart = true
filt.URL = newList.URL
filt.LastUpdated = time.Time{}
filt.unload()
flt.URL = newList.URL
flt.LastUpdated = time.Time{}
flt.unload()
}
if filt.Enabled != newList.Enabled {
filt.Enabled = newList.Enabled
if flt.Enabled != newList.Enabled {
flt.Enabled = newList.Enabled
shouldRestart = true
}
if filt.Enabled {
if flt.Enabled {
if shouldRestart {
// Download the filter contents.
shouldRestart, err = d.update(filt)
shouldRestart, err = d.update(flt)
}
} else {
// TODO(e.burkov): The validation of the contents of the new URL is
@@ -137,7 +138,7 @@ func (d *DNSFilter) filterSetProperties(
// possible to set a bad rules source, but the validation should still
// kick in when the filter is enabled. Consider changing this behavior
// to be stricter.
filt.unload()
flt.unload()
}
return shouldRestart, err
@@ -250,24 +251,24 @@ func assignUniqueFilterID() int64 {
// Sets up a timer that will be checking for filters updates periodically
func (d *DNSFilter) periodicallyRefreshFilters() {
const maxInterval = 1 * 60 * 60
intval := 5 // use a dynamically increasing time interval
ivl := 5 // use a dynamically increasing time interval
for {
isNetErr, ok := false, false
if d.FiltersUpdateIntervalHours != 0 {
_, isNetErr, ok = d.tryRefreshFilters(true, true, false)
if ok && !isNetErr {
intval = maxInterval
ivl = maxInterval
}
}
if isNetErr {
intval *= 2
if intval > maxInterval {
intval = maxInterval
ivl *= 2
if ivl > maxInterval {
ivl = maxInterval
}
}
time.Sleep(time.Duration(intval) * time.Second)
time.Sleep(time.Duration(ivl) * time.Second)
}
}
@@ -329,20 +330,20 @@ func (d *DNSFilter) refreshFiltersArray(filters *[]FilterYAML, force bool) (int,
return 0, nil, nil, false
}
nfail := 0
failNum := 0
for i := range updateFilters {
uf := &updateFilters[i]
updated, err := d.update(uf)
updateFlags = append(updateFlags, updated)
if err != nil {
nfail++
log.Info("filtering: updating filter from url %q: %s\n", uf.URL, err)
failNum++
log.Error("filtering: updating filter from url %q: %s\n", uf.URL, err)
continue
}
}
if nfail == len(updateFilters) {
if failNum == len(updateFilters) {
return 0, nil, nil, true
}
@@ -464,48 +465,6 @@ func (d *DNSFilter) update(filter *FilterYAML) (b bool, err error) {
return b, err
}
// finalizeUpdate closes and gets rid of temporary file f with filter's content
// according to updated. It also saves new values of flt's name, rules number
// and checksum if succeeded.
func (d *DNSFilter) finalizeUpdate(
file *os.File,
flt *FilterYAML,
updated bool,
res *rulelist.ParseResult,
) (err error) {
tmpFileName := file.Name()
// Close the file before renaming it because it's required on Windows.
//
// See https://github.com/adguardTeam/adGuardHome/issues/1553.
err = file.Close()
if err != nil {
return fmt.Errorf("closing temporary file: %w", err)
}
if !updated {
log.Debug("filtering: filter %d from url %q has no changes, skipping", flt.ID, flt.URL)
return os.Remove(tmpFileName)
}
fltPath := flt.Path(d.DataDir)
log.Info("filtering: saving contents of filter %d into %q", flt.ID, fltPath)
// Don't use renameio or maybe packages, since those will require loading
// the whole filter content to the memory on Windows.
err = os.Rename(tmpFileName, fltPath)
if err != nil {
return errors.WithDeferred(err, os.Remove(tmpFileName))
}
flt.Name = aghalg.Coalesce(flt.Name, res.Title)
flt.checksum, flt.RulesCount = res.Checksum, res.RulesCount
return nil
}
// updateIntl updates the flt rewriting it's actual file. It returns true if
// the actual update has been performed.
func (d *DNSFilter) updateIntl(flt *FilterYAML) (ok bool, err error) {
@@ -513,63 +472,22 @@ func (d *DNSFilter) updateIntl(flt *FilterYAML) (ok bool, err error) {
var res *rulelist.ParseResult
var tmpFile *os.File
tmpFile, err = os.CreateTemp(filepath.Join(d.DataDir, filterDir), "")
if err != nil {
return false, err
}
defer func() {
finErr := d.finalizeUpdate(tmpFile, flt, ok, res)
if ok && finErr == nil {
log.Info(
"filtering: updated filter %d: %d bytes, %d rules",
flt.ID,
res.BytesWritten,
res.RulesCount,
)
return
}
err = errors.WithDeferred(err, finErr)
}()
// Change the default 0o600 permission to something more acceptable by end
// users.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/3198.
if err = tmpFile.Chmod(0o644); err != nil {
return false, fmt.Errorf("changing file mode: %w", err)
tmpFile, err := aghrenameio.NewPendingFile(flt.Path(d.DataDir), 0o644)
if err != nil {
return false, err
}
defer func() { err = d.finalizeUpdate(tmpFile, flt, res, err, ok) }()
var r io.Reader
if !filepath.IsAbs(flt.URL) {
var resp *http.Response
resp, err = d.HTTPClient.Get(flt.URL)
if err != nil {
log.Info("filtering: requesting filter from %q: %s, skipping", flt.URL, err)
return false, err
}
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
if resp.StatusCode != http.StatusOK {
log.Info("filtering got status code %d from %q, skipping", resp.StatusCode, flt.URL)
return false, fmt.Errorf("got status code %d, want %d", resp.StatusCode, http.StatusOK)
}
r = resp.Body
} else {
var f *os.File
f, err = os.Open(flt.URL)
if err != nil {
return false, fmt.Errorf("open file: %w", err)
}
defer func() { err = errors.WithDeferred(err, f.Close()) }()
r = f
r, err := d.reader(flt.URL)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return false, err
}
defer func() { err = errors.WithDeferred(err, r.Close()) }()
bufPtr := d.bufPool.Get().(*[]byte)
defer d.bufPool.Put(bufPtr)
@@ -580,6 +498,78 @@ func (d *DNSFilter) updateIntl(flt *FilterYAML) (ok bool, err error) {
return res.Checksum != flt.checksum && err == nil, err
}
// finalizeUpdate closes and gets rid of temporary file f with filter's content
// according to updated. It also saves new values of flt's name, rules number
// and checksum if succeeded.
func (d *DNSFilter) finalizeUpdate(
file aghrenameio.PendingFile,
flt *FilterYAML,
res *rulelist.ParseResult,
returned error,
updated bool,
) (err error) {
id := flt.ID
if !updated {
if returned == nil {
log.Debug("filtering: filter %d from url %q has no changes, skipping", id, flt.URL)
}
return errors.WithDeferred(returned, file.Cleanup())
}
log.Info("filtering: saving contents of filter %d into %q", id, flt.Path(d.DataDir))
err = file.CloseReplace()
if err != nil {
return fmt.Errorf("finalizing update: %w", err)
}
rulesCount := res.RulesCount
log.Info("filtering: updated filter %d: %d bytes, %d rules", id, res.BytesWritten, rulesCount)
flt.Name = aghalg.Coalesce(flt.Name, res.Title)
flt.checksum = res.Checksum
flt.RulesCount = rulesCount
return nil
}
// reader returns an io.ReadCloser reading filtering-rule list data form either
// a file on the filesystem or the filter's HTTP URL.
func (d *DNSFilter) reader(fltURL string) (r io.ReadCloser, err error) {
if !filepath.IsAbs(fltURL) {
r, err = d.readerFromURL(fltURL)
if err != nil {
return nil, fmt.Errorf("reading from url: %w", err)
}
return r, nil
}
r, err = os.Open(fltURL)
if err != nil {
return nil, fmt.Errorf("opening file: %w", err)
}
return r, nil
}
// readerFromURL returns an io.ReadCloser reading filtering-rule list data form
// the filter's URL.
func (d *DNSFilter) readerFromURL(fltURL string) (r io.ReadCloser, err error) {
resp, err := d.HTTPClient.Get(fltURL)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("got status code %d, want %d", resp.StatusCode, http.StatusOK)
}
return resp.Body, nil
}
// loads filter contents from the file in dataDir
func (d *DNSFilter) load(flt *FilterYAML) (err error) {
fileName := flt.Path(d.DataDir)

View File

@@ -6,10 +6,10 @@ import (
"strings"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/fakeio"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -159,7 +159,7 @@ func TestParser_Parse(t *testing.T) {
func TestParser_Parse_writeError(t *testing.T) {
t.Parallel()
dst := &aghtest.Writer{
dst := &fakeio.Writer{
OnWrite: func(b []byte) (n int, err error) {
return 1, errors.Error("test error")
},

View File

@@ -89,37 +89,34 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
assert.False(t, res.IsFiltered)
assert.Empty(t, res.Rules)
resolver := &aghtest.TestResolver{}
resolver := &aghtest.Resolver{
OnLookupIP: func(_ context.Context, _, host string) (ips []net.IP, err error) {
ip4, ip6 := aghtest.HostToIPs(host)
return []net.IP{ip4, ip6}, nil
},
}
ss = newForTest(t, defaultSafeSearchConf)
ss.resolver = resolver
// Lookup for safesearch domain.
rewrite := ss.searchHost(domain, testQType)
ips, err := resolver.LookupIP(context.Background(), "ip", rewrite.NewCNAME)
require.NoError(t, err)
var foundIP net.IP
for _, ip := range ips {
if ip.To4() != nil {
foundIP = ip
break
}
}
wantIP, _ := aghtest.HostToIPs(rewrite.NewCNAME)
res, err = ss.CheckHost(domain, testQType)
require.NoError(t, err)
require.Len(t, res.Rules, 1)
assert.True(t, res.Rules[0].IP.Equal(foundIP))
assert.True(t, res.Rules[0].IP.Equal(wantIP))
// Check cache.
cachedValue, isFound := ss.getCachedResult(domain, testQType)
require.True(t, isFound)
require.Len(t, cachedValue.Rules, 1)
assert.True(t, cachedValue.Rules[0].IP.Equal(foundIP))
assert.True(t, cachedValue.Rules[0].IP.Equal(wantIP))
}
const googleHost = "www.google.com"

View File

@@ -92,8 +92,15 @@ func TestDefault_CheckHost_yandexAAAA(t *testing.T) {
}
func TestDefault_CheckHost_google(t *testing.T) {
resolver := &aghtest.TestResolver{}
ip, _ := resolver.HostToIPs("forcesafesearch.google.com")
resolver := &aghtest.Resolver{
OnLookupIP: func(_ context.Context, _, host string) (ips []net.IP, err error) {
ip4, ip6 := aghtest.HostToIPs(host)
return []net.IP{ip4, ip6}, nil
},
}
wantIP, _ := aghtest.HostToIPs("forcesafesearch.google.com")
conf := testConf
conf.CustomResolver = resolver
@@ -119,7 +126,7 @@ func TestDefault_CheckHost_google(t *testing.T) {
require.Len(t, res.Rules, 1)
assert.Equal(t, ip, res.Rules[0].IP)
assert.Equal(t, wantIP, res.Rules[0].IP)
assert.EqualValues(t, filtering.SafeSearchListID, res.Rules[0].FilterListID)
})
}

View File

@@ -253,6 +253,30 @@ var blockedServices = []blockedService{{
"||z.cn^",
"||zappos^",
},
}, {
ID: "apple_streaming",
Name: "Apple Streaming",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M33.375 0c-2.836.191-5.871 1.879-7.75 4.156-1.645 2.004-3.023 4.946-2.5 8-.469-.144-.895-.16-1.406-.344-1.395-.496-2.989-1.03-4.969-1.03-3.934 0-7.96 2.34-10.5 6.25C2.555 22.71 3.297 32.706 8.906 41.25c.989 1.5 2.14 3.137 3.563 4.438 1.422 1.3 3.14 2.292 5.156 2.312 1.723.02 2.922-.555 4-1.031 1.078-.477 2.082-.899 3.969-.907h.031c1.879-.015 2.852.399 3.906.876 1.055.476 2.242 1.078 3.969 1.062 2.055-.016 3.8-1.14 5.25-2.531 1.45-1.39 2.64-3.098 3.625-4.594 1.41-2.148 1.977-3.32 3.063-5.719a1.001 1.001 0 0 0-.563-1.344C41.32 32.47 39.293 29.325 39 26c-.293-3.324 1.113-6.746 4.656-8.688a1 1 0 0 0 .508-.675 1.007 1.007 0 0 0-.195-.825c-2.543-3.16-6.121-5.03-9.625-5.03-2.235 0-3.875.527-5.219 1.03-.223.086-.387.079-.594.157 1.364-.719 2.567-1.715 3.469-2.875 1.64-2.106 2.906-5.102 2.438-8.25A.999.999 0 0 0 33.374 0Zm-1.063 2.375c-.066 2.02-.757 3.996-1.906 5.469-1.203 1.547-3.226 2.617-5.187 2.937.035-1.941.8-3.953 1.968-5.375 1.227-1.484 3.258-2.554 5.125-3.031ZM16.75 12.781c1.613 0 2.906.418 4.281.906 1.375.489 2.824 1.063 4.532 1.063 1.667 0 2.988-.578 4.28-1.063 1.294-.484 2.583-.906 4.5-.906 2.505 0 5.212 1.301 7.344 3.563-3.414 2.41-5.011 6.168-4.687 9.812.324 3.684 2.543 7.18 6.188 9-.79 1.719-1.31 2.856-2.47 4.625-.956 1.457-2.093 3.051-3.343 4.25-1.25 1.2-2.574 1.957-3.906 1.969-1.285.012-2.016-.371-3.125-.875-1.11-.504-2.543-1.082-4.75-1.063-2.203.012-3.657.567-4.782 1.063s-1.863.887-3.156.875c-1.367-.012-2.636-.676-3.843-1.781-1.208-1.106-2.297-2.614-3.25-4.063-5.25-8-5.672-17.398-2.657-22.031 2.211-3.402 5.723-5.344 8.844-5.344Z\"/></svg>"),
Rules: []string{
"||applemusic.apple^",
"||hls-svod-aoc-ve.itunes.g.aaplimg.com^",
"||itun.es^",
"||itunes.apple.com^",
"||itunes.ca^",
"||itunes.co.th^",
"||itunes.co^",
"||itunes.com^",
"||itunes.es^",
"||itunes.g.aaplimg.com^",
"||itunes.hk^",
"||itunes.mx^",
"||itunes.org^",
"||itunes.us^",
"||music.apple.com^",
"||tv.apple.com^",
"||tv.g.apple.com^",
"||tv.v.aaplimg.com^",
},
}, {
ID: "battle_net",
Name: "Battle.net",
@@ -327,6 +351,34 @@ var blockedServices = []blockedService{{
"||bnet.cn^",
"||lizzard.com^",
},
}, {
ID: "claro",
Name: "Claro",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 -21 67 67\"><path d=\"M49.004 0c.933.01 1.866.002 2.8.003.003 2.842.001 5.684 0 8.525-.934.001-1.867.002-2.8.001 0-2.842-.002-5.686 0-8.529ZM55.2 9.622c2.564-2.63 5.1-5.292 7.662-7.926.657.69 1.334 1.36 1.978 2.064-2.535 2.654-5.096 5.282-7.632 7.933-.68-.679-1.339-1.38-2.008-2.07ZM6.091 8.06a7.942 7.942 0 0 1 2.155-.233c2.405-.058 4.742 1.202 6.232 3.131a8.516 8.516 0 0 1 1.514 3.12c-1.102.004-2.204 0-3.306 0-.486-1.001-1.23-1.893-2.2-2.413a4.756 4.756 0 0 0-1.728-.58c-.565-.012-1.142-.062-1.695.086a4.798 4.798 0 0 0-2.452 1.427c-.859.836-1.434 2.013-1.485 3.243-.11 1.171.105 2.399.749 3.384.619.944 1.494 1.73 2.53 2.135 1.739.666 3.843.265 5.174-1.095a6.18 6.18 0 0 0 1.118-1.604c1.098-.006 2.195-.006 3.292 0-.271 1.202-.863 2.316-1.611 3.27-.513.556-1.016 1.138-1.648 1.552-2.835 2.024-6.953 1.91-9.618-.379-.829-.73-1.586-1.572-2.107-2.57-.96-1.765-1.199-3.886-.859-5.863.286-1.676 1.135-3.22 2.305-4.4.987-1.065 2.25-1.868 3.64-2.21Zm11.58-.234h3.142c0 5.723.003 11.446-.002 17.169-1.047.002-2.093-.002-3.14-.001V7.826Zm9.493 3.417c.596-.125 1.205-.054 1.807-.07.698.062 1.398.166 2.062.41.665.24 1.35.54 1.817 1.111.548.676.742 1.574.785 2.435-.002 3.288.002 6.577-.002 9.866-1.062-.006-2.126.023-3.187-.015-.01-.447.009-.895-.007-1.341-.924.826-2.147 1.207-3.346 1.303-.756.135-1.54.013-2.261-.238a3.151 3.151 0 0 1-1.968-2.1c-.297-1.042-.235-2.183.112-3.204.377-1.04 1.285-1.78 2.284-2.117 1.28-.469 2.647-.541 3.97-.812.458 0 .91-.294 1.08-.74.123-.486.017-1.096-.397-1.405-.455-.311-1.011-.376-1.543-.392-.473.015-.973.02-1.392.28-.544.32-.788.956-.895 1.564-1.052-.023-2.105.001-3.157-.018.13-1.072.347-2.217 1.09-3.031.777-.943 1.982-1.392 3.148-1.486Zm2.316 7.423c-.622.149-1.234.34-1.866.44-.502.103-1.031.271-1.389.674-.497.608-.533 1.547-.148 2.224.168.31.5.463.809.574.997.2 2.091-.122 2.819-.864.746-.967.614-2.278.612-3.437-.294.097-.53.33-.837.389Zm11.644-7.032c.648-.164 1.284-.44 1.965-.375.007 1.111.065 2.224.043 3.337-.58-.083-1.184-.15-1.752.03-1.225.351-2.25 1.471-2.394 2.801-.008.293-.084.58-.087.873-.002 2.233.003 4.464 0 6.696-1.044-.002-2.09.008-3.134-.006-.012-4.395-.003-8.791.006-13.187 1.012-.01 2.023.03 3.035.068.001.543-.013 1.086.006 1.63.592-.819 1.369-1.527 2.312-1.867Zm7.824-.34c.466-.049.94-.095 1.409-.055 2.817.037 5.389 2.29 6.06 5.1.58 2.296.017 4.946-1.66 6.612-.97 1.086-2.302 1.823-3.714 2.044-.681.006-1.362.002-2.042.001-2.033-.296-3.8-1.735-4.802-3.557-1.042-2-1.046-4.535-.024-6.545.97-1.849 2.736-3.299 4.773-3.6Zm.253 3.255c-.938.22-1.737.902-2.215 1.757-.714 1.244-.6 2.94.29 4.06.907 1.354 2.77 1.811 4.194 1.117.828-.386 1.46-1.144 1.811-2.002.39-.985.32-2.141-.148-3.084-.492-.954-1.395-1.703-2.436-1.875-.497-.042-1.003-.057-1.496.027Zm9.407.496c2.796 0 5.594-.002 8.392.002-.002.963.002 1.927-.001 2.892-2.797-.004-5.593.006-8.39.001-.004-.965.003-1.93-.002-2.895Z\"/></svg>"),
Rules: []string{
"||claro.com.ar^",
"||claro.com.br^",
"||claro.com.co^",
"||claro.com.do^",
"||claro.com.ec^",
"||claro.com.gt^",
"||claro.com.hn^",
"||claro.com.ni^",
"||claro.com.pa^",
"||claro.com.pe^",
"||claro.com.py^",
"||claro.com.sv^",
"||claro.com.uy^",
"||claro.com^",
"||claro.cr^",
"||claro.net.br^",
"||claro.net.co^",
"||clarochile.cl^",
"||claromusica.com^",
"||claropr.com^",
"||clarovideo.com^",
"||usclaro.com^",
},
}, {
ID: "cloudflare",
Name: "CloudFlare",
@@ -1505,6 +1557,7 @@ var blockedServices = []blockedService{{
"||aus.social^",
"||awscommunity.social^",
"||climatejustice.social^",
"||cupoftea.social^",
"||cyberplace.social^",
"||defcon.social^",
"||det.social^",
@@ -1546,12 +1599,12 @@ var blockedServices = []blockedService{{
"||mastodon.social^",
"||mastodon.uno^",
"||mastodon.world^",
"||mastodon.zaclys.com^",
"||mastodonapp.uk^",
"||mastodonners.nl^",
"||mastodont.cat^",
"||mastodontech.de^",
"||mastodontti.fi^",
"||mastouille.fr^",
"||mathstodon.xyz^",
"||metalhead.club^",
"||mindly.social^",
@@ -1595,7 +1648,6 @@ var blockedServices = []blockedService{{
"||toot.io^",
"||toot.wales^",
"||troet.cafe^",
"||twingyeo.kr^",
"||union.place^",
"||universeodon.com^",
"||urbanists.social^",

View File

@@ -20,7 +20,7 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/google/renameio/maybe"
"github.com/google/renameio/v2/maybe"
"golang.org/x/exp/slices"
yaml "gopkg.in/yaml.v3"
)
@@ -114,8 +114,6 @@ type configuration struct {
Language string `yaml:"language"`
// Theme is a UI theme for current user.
Theme Theme `yaml:"theme"`
// DebugPProf defines if the profiling HTTP handler will listen on :6060.
DebugPProf bool `yaml:"debug_pprof"`
DNS dnsConfig `yaml:"dns"`
TLS tlsConfigSettings `yaml:"tls"`
@@ -155,6 +153,9 @@ type configuration struct {
// Field ordering is important, YAML fields better not to be reordered, if it's
// not absolutely necessary.
type httpConfig struct {
// Pprof defines the profiling HTTP handler.
Pprof *httpPprofConfig `yaml:"pprof"`
// Address is the address to serve the web UI on.
Address netip.AddrPort
@@ -163,6 +164,15 @@ type httpConfig struct {
SessionTTL timeutil.Duration `yaml:"session_ttl"`
}
// httpPprofConfig is the block with pprof HTTP configuration.
type httpPprofConfig struct {
// Port for the profiling handler.
Port uint16 `yaml:"port"`
// Enabled defines if the profiling handler is enabled.
Enabled bool `yaml:"enabled"`
}
// dnsConfig is a block with DNS configuration params.
//
// Field ordering is important, YAML fields better not to be reordered, if it's
@@ -277,6 +287,10 @@ var config = &configuration{
HTTPConfig: httpConfig{
Address: netip.AddrPortFrom(netip.IPv4Unspecified(), 3000),
SessionTTL: timeutil.Duration{Duration: 30 * timeutil.Day},
Pprof: &httpPprofConfig{
Enabled: false,
Port: 6060,
},
},
DNS: dnsConfig{
BindHosts: []netip.Addr{netip.IPv4Unspecified()},

View File

@@ -402,13 +402,6 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
return
}
err = aghnet.CheckPort("tcp", netip.AddrPortFrom(req.DNS.IP, uint16(req.DNS.Port)))
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
curConfig := &configuration{}
copyInstallSettings(curConfig, config)

View File

@@ -254,6 +254,7 @@ func newServerConfig(
Exchanger: Context.dnsServer,
AddressUpdater: &Context.clients,
InitialAddresses: initialAddresses,
CatchPanics: true,
UseRDNS: config.Clients.Sources.RDNS,
UseWHOIS: config.Clients.Sources.WHOIS,
}

View File

@@ -567,9 +567,8 @@ func run(opts options, clientBuildFS fs.FS) {
err = config.write()
fatalOnError(err)
if config.DebugPProf {
// TODO(a.garipov): Make the address configurable.
startPprof("localhost:6060")
if config.HTTPConfig.Pprof.Enabled {
startPprof(config.HTTPConfig.Pprof.Port)
}
}

View File

@@ -1,39 +0,0 @@
package home
import (
"net/http"
"net/http/pprof"
"runtime"
"github.com/AdguardTeam/golibs/log"
)
// startPprof launches the debug and profiling server on addr.
func startPprof(addr string) {
runtime.SetBlockProfileRate(1)
runtime.SetMutexProfileFraction(1)
mux := http.NewServeMux()
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
// See profileSupportsDelta in src/net/http/pprof/pprof.go.
mux.Handle("/debug/pprof/allocs", pprof.Handler("allocs"))
mux.Handle("/debug/pprof/block", pprof.Handler("block"))
mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
mux.Handle("/debug/pprof/mutex", pprof.Handler("mutex"))
mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
go func() {
defer log.OnPanic("pprof server")
log.Info("pprof: listening on %q", addr)
err := http.ListenAndServe(addr, mux)
log.Info("pprof server errors: %v", err)
}()
}

View File

@@ -17,13 +17,13 @@ import (
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/google/renameio/maybe"
"github.com/google/renameio/v2/maybe"
"golang.org/x/crypto/bcrypt"
yaml "gopkg.in/yaml.v3"
)
// currentSchemaVersion is the current schema version.
const currentSchemaVersion = 24
const currentSchemaVersion = 25
// These aliases are provided for convenience.
type (
@@ -99,6 +99,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
upgradeSchema21to22,
upgradeSchema22to23,
upgradeSchema23to24,
upgradeSchema24to25,
}
n := 0
@@ -1380,6 +1381,50 @@ func upgradeSchema23to24(diskConf yobj) (err error) {
return nil
}
// upgradeSchema24to25 performs the following changes:
//
// # BEFORE:
// 'debug_pprof': true
//
// # AFTER:
// 'http':
// 'pprof':
// 'enabled': true
// 'port': 6060
func upgradeSchema24to25(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 24 to 25")
diskConf["schema_version"] = 25
debugPprofVal, ok := diskConf["debug_pprof"]
if !ok {
return nil
}
debugPprofEnabled, ok := debugPprofVal.(bool)
if !ok {
return fmt.Errorf("unexpected type of debug_pprof: %T", debugPprofVal)
}
httpVal, ok := diskConf["http"]
if !ok {
return nil
}
httpObj, ok := httpVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", httpVal)
}
httpObj["pprof"] = yobj{
"enabled": debugPprofEnabled,
"port": 6060,
}
delete(diskConf, "debug_pprof")
return nil
}
// moveField gets field value for key from diskConf, and then set this value
// in newConf for newKey.
func moveField[T any](diskConf, newConf yobj, key, newKey string) (err error) {

View File

@@ -1379,3 +1379,90 @@ func TestUpgradeSchema23to24(t *testing.T) {
})
}
}
func TestUpgradeSchema24to25(t *testing.T) {
const newSchemaVer = 25
testCases := []struct {
in yobj
want yobj
name string
wantErrMsg string
}{{
name: "empty",
in: yobj{},
want: yobj{
"schema_version": newSchemaVer,
},
wantErrMsg: "",
}, {
name: "ok",
in: yobj{
"http": yobj{
"address": "0.0.0.0:3000",
"session_ttl": "720h",
},
"debug_pprof": true,
},
want: yobj{
"http": yobj{
"address": "0.0.0.0:3000",
"session_ttl": "720h",
"pprof": yobj{
"enabled": true,
"port": 6060,
},
},
"schema_version": newSchemaVer,
},
wantErrMsg: "",
}, {
name: "ok_disabled",
in: yobj{
"http": yobj{
"address": "0.0.0.0:3000",
"session_ttl": "720h",
},
"debug_pprof": false,
},
want: yobj{
"http": yobj{
"address": "0.0.0.0:3000",
"session_ttl": "720h",
"pprof": yobj{
"enabled": false,
"port": 6060,
},
},
"schema_version": newSchemaVer,
},
wantErrMsg: "",
}, {
name: "invalid",
in: yobj{
"http": yobj{
"address": "0.0.0.0:3000",
"session_ttl": "720h",
},
"debug_pprof": 1,
},
want: yobj{
"http": yobj{
"address": "0.0.0.0:3000",
"session_ttl": "720h",
},
"debug_pprof": 1,
"schema_version": newSchemaVer,
},
wantErrMsg: "unexpected type of debug_pprof: int",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := upgradeSchema24to25(tc.in)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
assert.Equal(t, tc.want, tc.in)
})
}
}

View File

@@ -6,6 +6,7 @@ import (
"io/fs"
"net/http"
"net/netip"
"runtime"
"sync"
"time"
@@ -15,6 +16,7 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/pprofutil"
"github.com/NYTimes/gziphandler"
"github.com/quic-go/quic-go/http3"
"golang.org/x/net/http2"
@@ -309,3 +311,24 @@ func (web *webAPI) mustStartHTTP3(address string) {
log.Fatalf("web: http3: %s", err)
}
}
// startPprof launches the debug and profiling server on the provided port.
func startPprof(port uint16) {
addr := netip.AddrPortFrom(netutil.IPv4Localhost(), port)
runtime.SetBlockProfileRate(1)
runtime.SetMutexProfileFraction(1)
mux := http.NewServeMux()
pprofutil.RoutePprof(mux)
go func() {
defer log.OnPanic("pprof server")
log.Info("pprof: listening on %q", addr)
err := http.ListenAndServe(addr.String(), mux)
if !errors.Is(err, http.ErrServerClosed) {
log.Error("pprof: shutting down: %s", err)
}
}()
}

View File

@@ -18,6 +18,9 @@ dns:
bootstrap_prefer_ipv6: true
use_dns64: true
http:
pprof:
enabled: true
port: 6060
addresses:
- '0.0.0.0:3000'
secure_addresses: []

View File

@@ -7,6 +7,7 @@ enough.
### Added
- The ability to change the port of the pprof debug API.
- The ability to log to stderr using `--logFile=stderr`.
- The new `--web-addr` flag to set the Web UI address in a `host:port` form.
- `SIGHUP` now reloads all configuration from the configuration file ([#5676]).

View File

@@ -8,7 +8,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
"github.com/AdguardTeam/golibs/log"
"github.com/google/renameio/maybe"
"github.com/google/renameio/v2/maybe"
)
// signalHandler processes incoming signals and shuts services down.

View File

@@ -17,8 +17,6 @@ type config struct {
Log *logConfig `yaml:"log"`
// TODO(a.garipov): Use.
SchemaVersion int `yaml:"schema_version"`
// TODO(a.garipov): Use.
DebugPprof bool `yaml:"debug_pprof"`
}
const errNoConf errors.Error = "configuration not found"
@@ -84,6 +82,8 @@ func (c *dnsConfig) validate() (err error) {
// httpConfig is the on-disk web API configuration.
type httpConfig struct {
Pprof *httpPprofConfig `yaml:"pprof"`
// TODO(a.garipov): Document the configuration change.
Addresses []netip.AddrPort `yaml:"addresses"`
SecureAddresses []netip.AddrPort `yaml:"secure_addresses"`
@@ -101,10 +101,25 @@ func (c *httpConfig) validate() (err error) {
case c.Timeout.Duration <= 0:
return newMustBePositiveError("timeout", c.Timeout)
default:
return nil
return c.Pprof.validate()
}
}
// httpPprofConfig is the on-disk pprof configuration.
type httpPprofConfig struct {
Port uint16 `yaml:"port"`
Enabled bool `yaml:"enabled"`
}
// validate returns an error if the pprof configuration structure is invalid.
func (c *httpPprofConfig) validate() (err error) {
if c == nil {
return errNoConf
}
return nil
}
// logConfig is the on-disk web API configuration.
type logConfig struct {
// TODO(a.garipov): Use.

View File

@@ -19,7 +19,7 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/google/renameio/maybe"
"github.com/google/renameio/v2/maybe"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3"
)
@@ -151,6 +151,10 @@ func (m *Manager) assemble(
}
webSvcConf := &websvc.Config{
Pprof: &websvc.PprofConfig{
Port: conf.HTTP.Pprof.Port,
Enabled: conf.HTTP.Pprof.Enabled,
},
ConfigManager: m,
Frontend: frontend,
// TODO(a.garipov): Fill from config file.
@@ -259,9 +263,6 @@ func (m *Manager) UpdateWeb(ctx context.Context, c *websvc.Config) (err error) {
m.updMu.Lock()
defer m.updMu.Unlock()
// TODO(a.garipov): Update and write the configuration file. Return an
// error if something went wrong.
err = m.updateWeb(ctx, c)
if err != nil {
return fmt.Errorf("reassembling websvc: %w", err)
@@ -291,6 +292,8 @@ func (m *Manager) updateWeb(ctx context.Context, c *websvc.Config) (err error) {
// updateCurrentWeb updates the web configuration in the current config.
func (m *Manager) updateCurrentWeb(c *websvc.Config) {
// TODO(a.garipov): Update pprof from API?
m.current.HTTP.Addresses = slices.Clone(c.Addresses)
m.current.HTTP.SecureAddresses = slices.Clone(c.SecureAddresses)
m.current.HTTP.Timeout = timeutil.Duration{Duration: c.Timeout}

View File

@@ -0,0 +1,79 @@
package websvc
import (
"crypto/tls"
"io/fs"
"net/netip"
"time"
)
// Config is the AdGuard Home web service configuration structure.
type Config struct {
// Pprof is the configuration for the pprof debug API. It must not be nil.
Pprof *PprofConfig
// ConfigManager is used to show information about services as well as
// dynamically reconfigure them.
ConfigManager ConfigManager
// Frontend is the filesystem with the frontend and other statically
// compiled files.
Frontend fs.FS
// TLS is the optional TLS configuration. If TLS is not nil,
// SecureAddresses must not be empty.
TLS *tls.Config
// Start is the time of start of AdGuard Home.
Start time.Time
// OverrideAddress is the initial or override address for the HTTP API. If
// set, it is used instead of [Addresses] and [SecureAddresses].
OverrideAddress netip.AddrPort
// Addresses are the addresses on which to serve the plain HTTP API.
Addresses []netip.AddrPort
// SecureAddresses are the addresses on which to serve the HTTPS API. If
// SecureAddresses is not empty, TLS must not be nil.
SecureAddresses []netip.AddrPort
// Timeout is the timeout for all server operations.
Timeout time.Duration
// ForceHTTPS tells if all requests to Addresses should be redirected to a
// secure address instead.
//
// TODO(a.garipov): Use; define rules, which address to redirect to.
ForceHTTPS bool
}
// PprofConfig is the configuration for the pprof debug API.
type PprofConfig struct {
Port uint16 `yaml:"port"`
Enabled bool `yaml:"enabled"`
}
// Config returns the current configuration of the web service. Config must not
// be called simultaneously with Start. If svc was initialized with ":0"
// addresses, addrs will not return the actual bound ports until Start is
// finished.
func (svc *Service) Config() (c *Config) {
c = &Config{
Pprof: &PprofConfig{
Port: svc.pprofPort,
Enabled: svc.pprof != nil,
},
ConfigManager: svc.confMgr,
TLS: svc.tls,
// Leave Addresses and SecureAddresses empty and get the actual
// addresses that include the :0 ones later.
Start: svc.start,
Timeout: svc.timeout,
ForceHTTPS: svc.forceHTTPS,
}
c.Addresses, c.SecureAddresses = svc.addrs()
return c
}

View File

@@ -52,6 +52,10 @@ func (svc *Service) handlePatchSettingsHTTP(w http.ResponseWriter, r *http.Reque
}
newConf := &Config{
Pprof: &PprofConfig{
Port: svc.pprofPort,
Enabled: svc.pprof != nil,
},
ConfigManager: svc.confMgr,
Frontend: svc.frontend,
TLS: svc.tls,

View File

@@ -25,6 +25,9 @@ func TestService_HandlePatchSettingsHTTP(t *testing.T) {
}
svc, err := websvc.New(&websvc.Config{
Pprof: &websvc.PprofConfig{
Enabled: false,
},
TLS: &tls.Config{
Certificates: []tls.Certificate{{}},
},

View File

@@ -49,6 +49,9 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
}
svc, err := websvc.New(&websvc.Config{
Pprof: &websvc.PprofConfig{
Enabled: false,
},
TLS: &tls.Config{
Certificates: []tls.Certificate{{}},
},

View File

@@ -15,6 +15,7 @@ import (
"net"
"net/http"
"net/netip"
"runtime"
"sync"
"time"
@@ -22,6 +23,8 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
"github.com/AdguardTeam/golibs/pprofutil"
httptreemux "github.com/dimfeld/httptreemux/v5"
)
@@ -34,54 +37,18 @@ type ConfigManager interface {
UpdateWeb(ctx context.Context, c *Config) (err error)
}
// Config is the AdGuard Home web service configuration structure.
type Config struct {
// ConfigManager is used to show information about services as well as
// dynamically reconfigure them.
ConfigManager ConfigManager
// Frontend is the filesystem with the frontend and other statically
// compiled files.
Frontend fs.FS
// TLS is the optional TLS configuration. If TLS is not nil,
// SecureAddresses must not be empty.
TLS *tls.Config
// Start is the time of start of AdGuard Home.
Start time.Time
// OverrideAddress is the initial or override address for the HTTP API. If
// set, it is used instead of [Addresses] and [SecureAddresses].
OverrideAddress netip.AddrPort
// Addresses are the addresses on which to serve the plain HTTP API.
Addresses []netip.AddrPort
// SecureAddresses are the addresses on which to serve the HTTPS API. If
// SecureAddresses is not empty, TLS must not be nil.
SecureAddresses []netip.AddrPort
// Timeout is the timeout for all server operations.
Timeout time.Duration
// ForceHTTPS tells if all requests to Addresses should be redirected to a
// secure address instead.
//
// TODO(a.garipov): Use; define rules, which address to redirect to.
ForceHTTPS bool
}
// Service is the AdGuard Home web service. A nil *Service is a valid
// [agh.Service] that does nothing.
type Service struct {
confMgr ConfigManager
frontend fs.FS
tls *tls.Config
pprof *http.Server
start time.Time
overrideAddr netip.AddrPort
servers []*http.Server
timeout time.Duration
pprofPort uint16
forceHTTPS bool
}
@@ -120,9 +87,35 @@ func New(c *Config) (svc *Service, err error) {
}
}
svc.setupPprof(c.Pprof)
return svc, nil
}
// setupPprof sets the pprof properties of svc.
func (svc *Service) setupPprof(c *PprofConfig) {
if !c.Enabled {
// Set to zero explicitly in case pprof used to be enabled before a
// reconfiguration took place.
runtime.SetBlockProfileRate(0)
runtime.SetMutexProfileFraction(0)
return
}
runtime.SetBlockProfileRate(1)
runtime.SetMutexProfileFraction(1)
pprofMux := http.NewServeMux()
pprofutil.RoutePprof(pprofMux)
svc.pprofPort = c.Port
addr := netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), c.Port)
// TODO(a.garipov): Consider making pprof timeout configurable.
svc.pprof = newSrv(addr, nil, pprofMux, 10*time.Minute)
}
// newSrv returns a new *http.Server with the given parameters.
func newSrv(
addr netip.AddrPort,
@@ -254,12 +247,19 @@ func (svc *Service) Start() (err error) {
return nil
}
pprofEnabled := svc.pprof != nil
srvNum := len(svc.servers) + mathutil.BoolToNumber[int](pprofEnabled)
wg := &sync.WaitGroup{}
wg.Add(len(svc.servers))
wg.Add(srvNum)
for _, srv := range svc.servers {
go serve(srv, wg)
}
if pprofEnabled {
go serve(svc.pprof, wg)
}
wg.Wait()
return nil
@@ -310,9 +310,20 @@ func (svc *Service) Shutdown(ctx context.Context) (err error) {
var errs []error
for _, srv := range svc.servers {
serr := srv.Shutdown(ctx)
if serr != nil {
errs = append(errs, fmt.Errorf("shutting down srv %s: %w", srv.Addr, serr))
shutdownErr := srv.Shutdown(ctx)
if shutdownErr != nil {
errs = append(errs, fmt.Errorf("shutting down srv %s: %w", srv.Addr, shutdownErr))
}
}
if svc.pprof != nil {
shutdownErr := svc.pprof.Shutdown(ctx)
if shutdownErr != nil {
errs = append(errs, fmt.Errorf(
"shutting down pprof srv %s: %w",
svc.pprof.Addr,
shutdownErr,
))
}
}
@@ -322,23 +333,3 @@ func (svc *Service) Shutdown(ctx context.Context) (err error) {
return nil
}
// Config returns the current configuration of the web service. Config must not
// be called simultaneously with Start. If svc was initialized with ":0"
// addresses, addrs will not return the actual bound ports until Start is
// finished.
func (svc *Service) Config() (c *Config) {
c = &Config{
ConfigManager: svc.confMgr,
TLS: svc.tls,
// Leave Addresses and SecureAddresses empty and get the actual
// addresses that include the :0 ones later.
Start: svc.start,
Timeout: svc.timeout,
ForceHTTPS: svc.forceHTTPS,
}
c.Addresses, c.SecureAddresses = svc.addrs()
return c
}

View File

@@ -12,11 +12,11 @@ import (
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/fakefs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -89,8 +89,11 @@ func newTestServer(
t.Helper()
c := &websvc.Config{
Pprof: &websvc.PprofConfig{
Enabled: false,
},
ConfigManager: confMgr,
Frontend: &aghtest.FS{
Frontend: &fakefs.FS{
OnOpen: func(_ string) (_ fs.File, _ error) { return nil, fs.ErrNotExist },
},
TLS: nil,

View File

@@ -127,7 +127,7 @@ func TestDecodeLogEntry(t *testing.T) {
}, {
name: "bad_time",
log: `{"IP":"127.0.0.1","T":"12/09/1998T15:00:00.000000+05:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`,
want: "decodeLogEntry handler err: parsing time \"12/09/1998T15:00:00.000000+05:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"9/1998T15:00:00.000000+05:00\" as \"2006\"\n",
want: "decodeLogEntry handler err: parsing time \"12/09/1998T15:00:00.000000+05:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"12/09/1998T15:00:00.000000+05:00\" as \"2006\"\n",
}, {
name: "bad_host",
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":6,"QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`,

View File

@@ -7,6 +7,7 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
"github.com/bluele/gcache"
)
@@ -32,7 +33,7 @@ func (Empty) Process(_ netip.Addr) (host string, changed bool) {
type Exchanger interface {
// Exchange tries to resolve the ip in a suitable way, i.e. either as local
// or as external.
Exchange(ip netip.Addr) (host string, err error)
Exchange(ip netip.Addr) (host string, ttl time.Duration, err error)
}
// Config is the configuration structure for Default.
@@ -82,13 +83,16 @@ func (r *Default) Process(ip netip.Addr) (host string, changed bool) {
return fromCache, false
}
host, err := r.exchanger.Exchange(ip)
host, ttl, err := r.exchanger.Exchange(ip)
if err != nil {
log.Debug("rdns: resolving %q: %s", ip, err)
}
// TODO(s.chzhen): Use built-in function max in Go 1.21.
ttl = mathutil.Max(ttl, r.cacheTTL)
item := &cacheItem{
expiry: time.Now().Add(r.cacheTTL),
expiry: time.Now().Add(ttl),
host: host,
}
@@ -97,6 +101,8 @@ func (r *Default) Process(ip netip.Addr) (host string, changed bool) {
log.Debug("rdns: cache: adding item %q: %s", ip, err)
}
// TODO(e.burkov): The name doesn't change if it's neither stored in cache
// nor resolved successfully. Is it correct?
return host, fromCache == "" || host != fromCache
}

View File

@@ -25,11 +25,6 @@ func TestDefault_Process(t *testing.T) {
localRevAddr1, err := netutil.IPToReversedAddr(localIP.AsSlice())
require.NoError(t, err)
config := &rdns.Config{
CacheSize: 100,
CacheTTL: time.Hour,
}
testCases := []struct {
name string
addr netip.Addr
@@ -55,26 +50,26 @@ func TestDefault_Process(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
hit := 0
onExchange := func(ip netip.Addr) (host string, err error) {
onExchange := func(ip netip.Addr) (host string, ttl time.Duration, err error) {
hit++
switch ip {
case ip1:
return revAddr1, nil
return revAddr1, time.Hour, nil
case ip2:
return revAddr2, nil
return revAddr2, time.Hour, nil
case localIP:
return localRevAddr1, nil
return localRevAddr1, time.Hour, nil
default:
return "", nil
return "", time.Hour, nil
}
}
exchanger := &aghtest.Exchanger{
OnExchange: onExchange,
}
config.Exchanger = exchanger
r := rdns.New(config)
r := rdns.New(&rdns.Config{
CacheSize: 100,
CacheTTL: time.Hour,
Exchanger: &aghtest.Exchanger{OnExchange: onExchange},
})
got, changed := r.Process(tc.addr)
require.True(t, changed)
@@ -90,4 +85,40 @@ func TestDefault_Process(t *testing.T) {
assert.Equal(t, 1, hit)
})
}
t.Run("zero_ttl", func(t *testing.T) {
const cacheTTL = time.Second / 2
zeroTTLExchanger := &aghtest.Exchanger{
OnExchange: func(ip netip.Addr) (host string, ttl time.Duration, err error) {
return revAddr1, 0, nil
},
}
r := rdns.New(&rdns.Config{
CacheSize: 1,
CacheTTL: cacheTTL,
Exchanger: zeroTTLExchanger,
})
got, changed := r.Process(ip1)
require.True(t, changed)
assert.Equal(t, revAddr1, got)
zeroTTLExchanger.OnExchange = func(ip netip.Addr) (host string, ttl time.Duration, err error) {
return revAddr2, time.Hour, nil
}
require.EventuallyWithT(t, func(t *assert.CollectT) {
got, changed = r.Process(ip1)
assert.True(t, changed)
assert.Equal(t, revAddr2, got)
}, 2*cacheTTL, time.Millisecond*100)
assert.Never(t, func() (changed bool) {
_, changed = r.Process(ip1)
return changed
}, 2*cacheTTL, time.Millisecond*100)
})
}

View File

@@ -19,6 +19,9 @@ import (
// The key is either a client's address or a requested address.
type topAddrs = map[string]uint64
// topAddrsFloat is like [topAddrs] but the value is float64 number.
type topAddrsFloat = map[string]float64
// StatsResp is a response to the GET /control/stats.
type StatsResp struct {
TimeUnits string `json:"time_units"`
@@ -27,6 +30,9 @@ type StatsResp struct {
TopClients []topAddrs `json:"top_clients"`
TopBlocked []topAddrs `json:"top_blocked_domains"`
TopUpstreamsResponses []topAddrs `json:"top_upstreams_responses"`
TopUpstreamsAvgTime []topAddrsFloat `json:"top_upstreams_avg_time"`
DNSQueries []uint64 `json:"dns_queries"`
BlockedFiltering []uint64 `json:"blocked_filtering"`

View File

@@ -5,7 +5,6 @@ package stats
import (
"fmt"
"io"
"net"
"net/netip"
"os"
"sync"
@@ -80,7 +79,7 @@ type Interface interface {
io.Closer
// Update collects the incoming statistics data.
Update(e Entry)
Update(e *Entry)
// GetTopClientIP returns at most limit IP addresses corresponding to the
// clients with the most number of requests.
@@ -225,7 +224,7 @@ func (s *StatsCtx) Start() {
go s.periodicFlush()
}
// Close implements the io.Closer interface for *StatsCtx.
// Close implements the [io.Closer] interface for *StatsCtx.
func (s *StatsCtx) Close() (err error) {
defer func() { err = errors.Annotate(err, "stats: closing: %w") }()
@@ -256,8 +255,9 @@ func (s *StatsCtx) Close() (err error) {
return udb.flushUnitToDB(tx, s.curr.id)
}
// Update implements the Interface interface for *StatsCtx.
func (s *StatsCtx) Update(e Entry) {
// Update implements the [Interface] interface for *StatsCtx. e must not be
// nil.
func (s *StatsCtx) Update(e *Entry) {
s.confMu.Lock()
defer s.confMu.Unlock()
@@ -265,8 +265,9 @@ func (s *StatsCtx) Update(e Entry) {
return
}
if e.Result == 0 || e.Result >= resultLast || e.Domain == "" || e.Client == "" {
log.Debug("stats: malformed entry")
err := e.validate()
if err != nil {
log.Debug("stats: updating: validating entry: %s", err)
return
}
@@ -280,15 +281,10 @@ func (s *StatsCtx) Update(e Entry) {
return
}
clientID := e.Client
if ip := net.ParseIP(clientID); ip != nil {
clientID = ip.String()
}
s.curr.add(e.Result, e.Domain, clientID, uint64(e.Time))
s.curr.add(e)
}
// WriteDiskConfig implements the Interface interface for *StatsCtx.
// WriteDiskConfig implements the [Interface] interface for *StatsCtx.
func (s *StatsCtx) WriteDiskConfig(dc *Config) {
s.confMu.RLock()
defer s.confMu.RUnlock()
@@ -412,6 +408,12 @@ func (s *StatsCtx) flush() (cont bool, sleepFor time.Duration) {
return true, time.Second
}
return s.flushDB(id, limit, ptr)
}
// flushDB flushes the unit to the database. confMu and currMu are expected to
// be locked.
func (s *StatsCtx) flushDB(id, limit uint32, ptr *unit) (cont bool, sleepFor time.Duration) {
db := s.db.Load()
if db == nil {
return true, 0

View File

@@ -50,11 +50,11 @@ func TestStats_races(t *testing.T) {
testutil.CleanupAndRequireSuccess(t, s.Close)
writeFunc := func(start, fin *sync.WaitGroup, waitCh <-chan unit, i int) {
e := Entry{
e := &Entry{
Domain: fmt.Sprintf("example-%d.org", i),
Client: fmt.Sprintf("client_%d", i),
Result: Result(i)%(resultLast-1) + 1,
Time: uint32(time.Since(startTime).Milliseconds()),
Time: time.Since(startTime),
}
start.Done()

View File

@@ -9,6 +9,7 @@ import (
"path/filepath"
"sync/atomic"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/stats"
"github.com/AdguardTeam/golibs/netutil"
@@ -72,24 +73,29 @@ func TestStats(t *testing.T) {
t.Run("data", func(t *testing.T) {
const reqDomain = "domain"
const respUpstream = "upstream"
entries := []stats.Entry{{
Domain: reqDomain,
Client: cliIPStr,
Result: stats.RFiltered,
Time: 123456,
entries := []*stats.Entry{{
Domain: reqDomain,
Client: cliIPStr,
Result: stats.RFiltered,
Time: time.Microsecond * 123456,
Upstream: respUpstream,
}, {
Domain: reqDomain,
Client: cliIPStr,
Result: stats.RNotFiltered,
Time: 123456,
Domain: reqDomain,
Client: cliIPStr,
Result: stats.RNotFiltered,
Time: time.Microsecond * 123456,
Upstream: respUpstream,
}}
wantData := &stats.StatsResp{
TimeUnits: "hours",
TopQueried: []map[string]uint64{0: {reqDomain: 1}},
TopClients: []map[string]uint64{0: {cliIPStr: 2}},
TopBlocked: []map[string]uint64{0: {reqDomain: 1}},
TimeUnits: "hours",
TopQueried: []map[string]uint64{0: {reqDomain: 1}},
TopClients: []map[string]uint64{0: {cliIPStr: 2}},
TopBlocked: []map[string]uint64{0: {reqDomain: 1}},
TopUpstreamsResponses: []map[string]uint64{0: {respUpstream: 2}},
TopUpstreamsAvgTime: []map[string]float64{0: {respUpstream: 0.123456}},
DNSQueries: []uint64{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
@@ -138,14 +144,16 @@ func TestStats(t *testing.T) {
_24zeroes := [24]uint64{}
emptyData := &stats.StatsResp{
TimeUnits: "hours",
TopQueried: []map[string]uint64{},
TopClients: []map[string]uint64{},
TopBlocked: []map[string]uint64{},
DNSQueries: _24zeroes[:],
BlockedFiltering: _24zeroes[:],
ReplacedSafebrowsing: _24zeroes[:],
ReplacedParental: _24zeroes[:],
TimeUnits: "hours",
TopQueried: []map[string]uint64{},
TopClients: []map[string]uint64{},
TopBlocked: []map[string]uint64{},
TopUpstreamsResponses: []map[string]uint64{},
TopUpstreamsAvgTime: []map[string]float64{},
DNSQueries: _24zeroes[:],
BlockedFiltering: _24zeroes[:],
ReplacedSafebrowsing: _24zeroes[:],
ReplacedParental: _24zeroes[:],
}
req = httptest.NewRequest(http.MethodGet, "/control/stats", nil)
@@ -187,7 +195,7 @@ func TestLargeNumbers(t *testing.T) {
for i := 0; i < cliNumPerHour; i++ {
ip := net.IP{127, 0, byte((i & 0xff00) >> 8), byte(i & 0xff)}
e := stats.Entry{
e := &stats.Entry{
Domain: fmt.Sprintf("domain%d.hour%d", i, h),
Client: ip.String(),
Result: stats.RNotFiltered,

View File

@@ -11,17 +11,19 @@ import (
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
"go.etcd.io/bbolt"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
// TODO(a.garipov): Rewrite all of this. Add proper error handling and
// inspection. Improve logging. Decrease complexity.
const (
// maxDomains is the max number of top domains to return.
maxDomains = 100
// maxClients is the max number of top clients to return.
maxClients = 100
// maxUpstreams is the max number of top upstreams to return.
maxUpstreams = 100
)
// UnitIDGenFunc is the signature of a function that generates a unique ID for
@@ -63,11 +65,30 @@ type Entry struct {
// Domain is the domain name requested.
Domain string
// Upstream is the upstream DNS server.
Upstream string
// Result is the result of processing the request.
Result Result
// Time is the duration of the request processing in milliseconds.
Time uint32
// Time is the duration of the request processing.
Time time.Duration
}
// validate returs an error if entry is not valid.
func (e *Entry) validate() (err error) {
switch {
case e.Result == 0:
return errors.Error("result code is not set")
case e.Result >= resultLast:
return fmt.Errorf("unknown result code %d", e.Result)
case e.Domain == "":
return errors.Error("domain is empty")
case e.Client == "":
return errors.Error("client is empty")
default:
return nil
}
}
// unit collects the statistics data for a specific period of time.
@@ -82,6 +103,13 @@ type unit struct {
// clients stores the number of requests from each client.
clients map[string]uint64
// upstreamsResponses stores the number of responses from each upstream.
upstreamsResponses map[string]uint64
// upstreamsTimeSum stores the sum of processing time in microseconds of
// responses from each upstream.
upstreamsTimeSum map[string]uint64
// nResult stores the number of requests grouped by it's result.
nResult []uint64
@@ -95,7 +123,7 @@ type unit struct {
// nTotal stores the total number of requests.
nTotal uint64
// timeSum stores the sum of processing time in milliseconds of each request
// timeSum stores the sum of processing time in microseconds of each request
// written by the unit.
timeSum uint64
}
@@ -103,11 +131,13 @@ type unit struct {
// newUnit allocates the new *unit.
func newUnit(id uint32) (u *unit) {
return &unit{
domains: map[string]uint64{},
blockedDomains: map[string]uint64{},
clients: map[string]uint64{},
nResult: make([]uint64, resultLast),
id: id,
domains: map[string]uint64{},
blockedDomains: map[string]uint64{},
clients: map[string]uint64{},
upstreamsResponses: map[string]uint64{},
upstreamsTimeSum: map[string]uint64{},
nResult: make([]uint64, resultLast),
id: id,
}
}
@@ -135,10 +165,17 @@ type unitDB struct {
// Clients is the number of requests from each client.
Clients []countPair
// UpstreamsResponses is the number of responses from each upstream.
UpstreamsResponses []countPair
// UpstreamsTimeSum is the sum of processing time in microseconds of
// responses from each upstream.
UpstreamsTimeSum []countPair
// NTotal is the total number of requests.
NTotal uint64
// TimeAvg is the average of processing times in milliseconds of all the
// TimeAvg is the average of processing times in microseconds of all the
// requests in the unit.
TimeAvg uint32
}
@@ -218,12 +255,14 @@ func (u *unit) serialize() (udb *unitDB) {
}
return &unitDB{
NTotal: u.nTotal,
NResult: append([]uint64{}, u.nResult...),
Domains: convertMapToSlice(u.domains, maxDomains),
BlockedDomains: convertMapToSlice(u.blockedDomains, maxDomains),
Clients: convertMapToSlice(u.clients, maxClients),
TimeAvg: timeAvg,
NTotal: u.nTotal,
NResult: append([]uint64{}, u.nResult...),
Domains: convertMapToSlice(u.domains, maxDomains),
BlockedDomains: convertMapToSlice(u.blockedDomains, maxDomains),
Clients: convertMapToSlice(u.clients, maxClients),
UpstreamsResponses: convertMapToSlice(u.upstreamsResponses, maxUpstreams),
UpstreamsTimeSum: convertMapToSlice(u.upstreamsTimeSum, maxUpstreams),
TimeAvg: timeAvg,
}
}
@@ -262,21 +301,29 @@ func (u *unit) deserialize(udb *unitDB) {
u.domains = convertSliceToMap(udb.Domains)
u.blockedDomains = convertSliceToMap(udb.BlockedDomains)
u.clients = convertSliceToMap(udb.Clients)
u.upstreamsResponses = convertSliceToMap(udb.UpstreamsResponses)
u.upstreamsTimeSum = convertSliceToMap(udb.UpstreamsTimeSum)
u.timeSum = uint64(udb.TimeAvg) * udb.NTotal
}
// add adds new data to u. It's safe for concurrent use.
func (u *unit) add(res Result, domain, cli string, dur uint64) {
u.nResult[res]++
if res == RNotFiltered {
u.domains[domain]++
func (u *unit) add(e *Entry) {
u.nResult[e.Result]++
if e.Result == RNotFiltered {
u.domains[e.Domain]++
} else {
u.blockedDomains[domain]++
u.blockedDomains[e.Domain]++
}
u.clients[cli]++
u.timeSum += dur
u.clients[e.Client]++
t := uint64(e.Time.Microseconds())
u.timeSum += t
u.nTotal++
if e.Upstream != "" {
u.upstreamsResponses[e.Upstream]++
u.upstreamsTimeSum[e.Upstream] += t
}
}
// flushUnitToDB puts udb to the database at id.
@@ -390,9 +437,11 @@ func (s *StatsCtx) getData(limit uint32) (StatsResp, bool) {
return StatsResp{
TimeUnits: "days",
TopBlocked: []topAddrs{},
TopClients: []topAddrs{},
TopQueried: []topAddrs{},
TopBlocked: []topAddrs{},
TopClients: []topAddrs{},
TopQueried: []topAddrs{},
TopUpstreamsResponses: []topAddrs{},
TopUpstreamsAvgTime: []topAddrsFloat{},
BlockedFiltering: []uint64{},
DNSQueries: []uint64{},
@@ -416,21 +465,35 @@ func (s *StatsCtx) getData(limit uint32) (StatsResp, bool) {
log.Fatalf("len(dnsQueries) != limit: %d %d", len(dnsQueries), limit)
}
return s.dataFromUnits(units, dnsQueries, firstID, timeUnit), true
}
// dataFromUnits collects and returns the statistics data.
func (s *StatsCtx) dataFromUnits(
units []*unitDB,
dnsQueries []uint64,
firstID uint32,
timeUnit TimeUnit,
) (resp StatsResp) {
topUpstreamsResponses, topUpstreamsAvgTime := topUpstreamsPairs(units)
data := StatsResp{
DNSQueries: dnsQueries,
BlockedFiltering: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RFiltered] }),
ReplacedSafebrowsing: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RSafeBrowsing] }),
ReplacedParental: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RParental] }),
TopQueried: topsCollector(units, maxDomains, s.ignored, func(u *unitDB) (pairs []countPair) { return u.Domains }),
TopBlocked: topsCollector(units, maxDomains, s.ignored, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }),
TopClients: topsCollector(units, maxClients, nil, topClientPairs(s)),
DNSQueries: dnsQueries,
BlockedFiltering: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RFiltered] }),
ReplacedSafebrowsing: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RSafeBrowsing] }),
ReplacedParental: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RParental] }),
TopQueried: topsCollector(units, maxDomains, s.ignored, func(u *unitDB) (pairs []countPair) { return u.Domains }),
TopBlocked: topsCollector(units, maxDomains, s.ignored, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }),
TopUpstreamsResponses: topUpstreamsResponses,
TopUpstreamsAvgTime: topUpstreamsAvgTime,
TopClients: topsCollector(units, maxClients, nil, topClientPairs(s)),
}
// Total counters:
sum := unitDB{
NResult: make([]uint64, resultLast),
}
timeN := 0
var timeN uint32
for _, u := range units {
sum.NTotal += u.NTotal
sum.TimeAvg += u.TimeAvg
@@ -450,7 +513,7 @@ func (s *StatsCtx) getData(limit uint32) (StatsResp, bool) {
data.NumReplacedParental = sum.NResult[RParental]
if timeN != 0 {
data.AvgProcessingTime = float64(sum.TimeAvg/uint32(timeN)) / 1000000
data.AvgProcessingTime = microsecondsToSeconds(float64(sum.TimeAvg / timeN))
}
data.TimeUnits = "hours"
@@ -458,7 +521,7 @@ func (s *StatsCtx) getData(limit uint32) (StatsResp, bool) {
data.TimeUnits = "days"
}
return data, true
return data
}
func topClientPairs(s *StatsCtx) (pg pairsGetter) {
@@ -474,3 +537,66 @@ func topClientPairs(s *StatsCtx) (pg pairsGetter) {
return clients
}
}
// topUpstreamsPairs returns sorted lists of number of total responses and the
// average of processing time for each upstream.
func topUpstreamsPairs(
units []*unitDB,
) (topUpstreamsResponses []topAddrs, topUpstreamsAvgTime []topAddrsFloat) {
upstreamsResponses := topAddrs{}
upstreamsTimeSum := topAddrsFloat{}
for _, u := range units {
for _, cp := range u.UpstreamsResponses {
upstreamsResponses[cp.Name] += cp.Count
}
for _, cp := range u.UpstreamsTimeSum {
upstreamsTimeSum[cp.Name] += float64(cp.Count)
}
}
upstreamsAvgTime := topAddrsFloat{}
for u, n := range upstreamsResponses {
total := upstreamsTimeSum[u]
if total != 0 {
upstreamsAvgTime[u] = microsecondsToSeconds(total / float64(n))
}
}
upstreamsPairs := convertMapToSlice(upstreamsResponses, maxUpstreams)
topUpstreamsResponses = convertTopSlice(upstreamsPairs)
return topUpstreamsResponses, prepareTopUpstreamsAvgTime(upstreamsAvgTime)
}
// microsecondsToSeconds converts microseconds to seconds.
//
// NOTE: Frontend expects time duration in seconds as floating-point number
// with double precision.
func microsecondsToSeconds(n float64) (r float64) {
const micro = 1e-6
return n * micro
}
// prepareTopUpstreamsAvgTime returns sorted list of average processing times
// of the DNS requests from each upstream.
func prepareTopUpstreamsAvgTime(
upstreamsAvgTime topAddrsFloat,
) (topUpstreamsAvgTime []topAddrsFloat) {
keys := maps.Keys(upstreamsAvgTime)
slices.SortFunc(keys, func(a, b string) (sortsBefore bool) {
return upstreamsAvgTime[a] > upstreamsAvgTime[b]
})
topUpstreamsAvgTime = make([]topAddrsFloat, 0, len(upstreamsAvgTime))
for _, k := range keys {
topUpstreamsAvgTime = append(topUpstreamsAvgTime, topAddrsFloat{k: upstreamsAvgTime[k]})
}
return topUpstreamsAvgTime
}

View File

@@ -0,0 +1,177 @@
package stats
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUnit_Deserialize(t *testing.T) {
testCases := []struct {
db *unitDB
name string
want unit
}{{
name: "empty",
want: unit{
domains: map[string]uint64{},
blockedDomains: map[string]uint64{},
clients: map[string]uint64{},
nResult: []uint64{0, 0, 0, 0, 0, 0},
id: 0,
nTotal: 0,
timeSum: 0,
upstreamsResponses: map[string]uint64{},
upstreamsTimeSum: map[string]uint64{},
},
db: &unitDB{
NResult: []uint64{0, 0, 0, 0, 0, 0},
Domains: []countPair{},
BlockedDomains: []countPair{},
Clients: []countPair{},
NTotal: 0,
TimeAvg: 0,
UpstreamsResponses: []countPair{},
UpstreamsTimeSum: []countPair{},
},
}, {
name: "basic",
want: unit{
domains: map[string]uint64{
"example.com": 1,
},
blockedDomains: map[string]uint64{
"example.net": 1,
},
clients: map[string]uint64{
"127.0.0.1": 2,
},
nResult: []uint64{0, 1, 1, 0, 0, 0},
id: 0,
nTotal: 2,
timeSum: 246912,
upstreamsResponses: map[string]uint64{
"1.2.3.4": 2,
},
upstreamsTimeSum: map[string]uint64{
"1.2.3.4": 246912,
},
},
db: &unitDB{
NResult: []uint64{0, 1, 1, 0, 0, 0},
Domains: []countPair{{
"example.com", 1,
}},
BlockedDomains: []countPair{{
"example.net", 1,
}},
Clients: []countPair{{
"127.0.0.1", 2,
}},
NTotal: 2,
TimeAvg: 123456,
UpstreamsResponses: []countPair{{
"1.2.3.4", 2,
}},
UpstreamsTimeSum: []countPair{{
"1.2.3.4", 246912,
}},
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := unit{}
got.deserialize(tc.db)
require.Equal(t, tc.want, got)
})
}
}
func TestTopUpstreamsPairs(t *testing.T) {
testCases := []struct {
db *unitDB
name string
wantResponses []topAddrs
wantAvgTime []topAddrsFloat
}{{
name: "empty",
db: &unitDB{
NResult: []uint64{0, 0, 0, 0, 0, 0},
Domains: []countPair{},
BlockedDomains: []countPair{},
Clients: []countPair{},
NTotal: 0,
TimeAvg: 0,
UpstreamsResponses: []countPair{},
UpstreamsTimeSum: []countPair{},
},
wantResponses: []topAddrs{},
wantAvgTime: []topAddrsFloat{},
}, {
name: "basic",
db: &unitDB{
NResult: []uint64{0, 0, 0, 0, 0, 0},
Domains: []countPair{},
BlockedDomains: []countPair{},
Clients: []countPair{},
NTotal: 0,
TimeAvg: 0,
UpstreamsResponses: []countPair{{
"1.2.3.4", 2,
}},
UpstreamsTimeSum: []countPair{{
"1.2.3.4", 246912,
}},
},
wantResponses: []topAddrs{{
"1.2.3.4": 2,
}},
wantAvgTime: []topAddrsFloat{{
"1.2.3.4": 0.123456,
}},
}, {
name: "sorted",
db: &unitDB{
NResult: []uint64{0, 0, 0, 0, 0, 0},
Domains: []countPair{},
BlockedDomains: []countPair{},
Clients: []countPair{},
NTotal: 0,
TimeAvg: 0,
UpstreamsResponses: []countPair{
{"3.3.3.3", 8},
{"2.2.2.2", 4},
{"4.4.4.4", 16},
{"1.1.1.1", 2},
},
UpstreamsTimeSum: []countPair{
{"3.3.3.3", 800_000_000},
{"2.2.2.2", 40_000_000},
{"4.4.4.4", 16_000_000_000},
{"1.1.1.1", 2_000_000},
},
},
wantResponses: []topAddrs{
{"4.4.4.4": 16},
{"3.3.3.3": 8},
{"2.2.2.2": 4},
{"1.1.1.1": 2},
},
wantAvgTime: []topAddrsFloat{
{"4.4.4.4": 1000},
{"3.3.3.3": 100},
{"2.2.2.2": 10},
{"1.1.1.1": 1},
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
gotResponses, gotAvgTime := topUpstreamsPairs([]*unitDB{tc.db})
assert.Equal(t, tc.wantResponses, gotResponses)
assert.Equal(t, tc.wantAvgTime, gotAvgTime)
})
}
}

View File

@@ -1,6 +1,6 @@
module github.com/AdguardTeam/AdGuardHome/internal/tools
go 1.19
go 1.20
require (
github.com/fzipp/gocyclo v0.6.0
@@ -10,7 +10,7 @@ require (
github.com/kyoh86/looppointer v0.2.1
github.com/securego/gosec/v2 v2.16.0
github.com/uudashr/gocognit v1.0.7
golang.org/x/tools v0.11.0
golang.org/x/tools v0.11.1
golang.org/x/vuln v1.0.0
// TODO(a.garipov): Return to tagged releases once a new one appears.
honnef.co/go/tools v0.5.0-0.dev.0.20230709092525-bc759185c5ee
@@ -22,12 +22,12 @@ require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.3 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/kyoh86/nolint v0.0.1 // indirect
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/exp/typeparams v0.0.0-20230713183714-613f0c0eb8a1 // indirect
golang.org/x/exp/typeparams v0.0.0-20230801115018-d63ba01acd4b // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.10.0 // indirect

View File

@@ -16,8 +16,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE=
github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8=
github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8=
@@ -38,7 +38,7 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR
github.com/securego/gosec/v2 v2.16.0 h1:Pi0JKoasQQ3NnoRao/ww/N/XdynIB9NRYYZT5CyOs5U=
github.com/securego/gosec/v2 v2.16.0/go.mod h1:xvLcVZqUfo4aAQu56TNv7/Ltz6emAOQAEsrZrt7uGlI=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/uudashr/gocognit v1.0.7 h1:e9aFXgKgUJrQ5+bs61zBigmj7bFJ/5cC6HmMahVzuDo=
github.com/uudashr/gocognit v1.0.7/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
@@ -52,8 +52,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp/typeparams v0.0.0-20230713183714-613f0c0eb8a1 h1:VXDua8UTGWl3e7L5kCk5Vyt0LA3QpsyRu6XXL7K3v1w=
golang.org/x/exp/typeparams v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230801115018-d63ba01acd4b h1:3dfup1Bt5y1sKG6rbyAX4qNymwAtJcqx+Aqm1DPP/Qg=
golang.org/x/exp/typeparams v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
@@ -96,8 +96,8 @@ golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4X
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc=
golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/vuln v1.0.0 h1:tYLAU3jD9LQr98Y+3el06lWyGMCnvzw06PIWP3LIy7g=
golang.org/x/vuln v1.0.0/go.mod h1:V0eyhHwaAaHrt42J9bgrN6rd12f6GU4T0Lu0ex2wDg4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -13,6 +13,7 @@ import (
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
@@ -49,7 +50,7 @@ func (Empty) Process(_ context.Context, _ netip.Addr) (info *Info, changed bool)
// Config is the configuration structure for Default.
type Config struct {
// DialContext is used to create TCP connections to WHOIS servers.
DialContext DialContextFunc
DialContext aghnet.DialContextFunc
// ServerAddr is the address of the WHOIS server.
ServerAddr string
@@ -77,13 +78,6 @@ type Config struct {
Port uint16
}
// DialContextFunc is the semantic alias for dialing functions, such as
// [http.Transport.DialContext].
//
// TODO(a.garipov): Move to aghnet once it stops importing aghtest, because
// otherwise there is an import cycle.
type DialContextFunc = func(ctx context.Context, network, addr string) (conn net.Conn, err error)
// Default is the default WHOIS information processor.
type Default struct {
// cache is the cache containing IP addresses of clients. An active IP
@@ -93,7 +87,7 @@ type Default struct {
cache gcache.Cache
// dialContext is used to create TCP connections to WHOIS servers.
dialContext DialContextFunc
dialContext aghnet.DialContextFunc
// serverAddr is the address of the WHOIS server.
serverAddr string

Some files were not shown because too many files have changed in this diff Show More