Compare commits

..

31 Commits

Author SHA1 Message Date
Ainar Garipov
0c7d56dca3 Merge branch 'master' into 4927-refactor-tls 2022-11-22 17:10:40 +03:00
Ainar Garipov
08282dc4d9 Pull request: 4927-imp-ui
Updates #4927.

Squashed commit of the following:

commit 510143325805133e379ebc207cdc6bff59c94ade
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Nov 22 15:00:13 2022 +0300

    home: imp err

commit fd65a9914494b6dccdee7c0f0aa08bce80ce0945
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Nov 21 18:53:39 2022 +0300

    client: imp validation ui
2022-11-22 17:07:49 +03:00
Ainar Garipov
f36efa26a4 home: refactor more 2022-11-21 19:45:18 +03:00
Ainar Garipov
a8850059db home: refactor tls 2022-11-21 19:05:49 +03:00
Dimitry Kolyshev
93882d6860 Pull request: 4223 home: cmd update
Merge in DNS/adguard-home from 4223-cmd-update to master

Squashed commit of the following:

commit ffda71246f37eaba0cb190840f1370ba65099d7c
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Nov 15 16:32:10 2022 +0200

    home: cmd update

commit 9c4e1c33da78952a2b1477ac380a0cf042a8990f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Nov 15 13:51:33 2022 +0200

    home: cmd update

commit 6a564dc30771b3675e8861ca3befaaee15d83026
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Nov 14 11:05:06 2022 +0200

    all: docs

commit a546bdbdb6f3f78c40908bc1864f2a1ae1c9071f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Nov 14 10:55:16 2022 +0200

    home: cmd update

commit cbbb594980d3d163fe0489494b0ddca5f679d6e6
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Nov 14 10:16:09 2022 +0200

    home: imp code

commit 677f8a7ca0f47da0ac636e5bab9db24506cf5041
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Sun Nov 13 14:12:48 2022 +0200

    home: cmd update
2022-11-15 17:44:50 +03:00
Ainar Garipov
167b112511 Pull request: 5035-more-clients-netip-addr
Updates #5035.

Squashed commit of the following:

commit 1934ea14299921760e9fcf6dd9053bd3155cb40e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Nov 9 14:19:54 2022 +0300

    all: move more client code to netip.Addr
2022-11-09 14:37:07 +03:00
Ainar Garipov
98af0e000e Pull request: upd-chlog
Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit efe2d95b870e413af4f91be5de7c7b97970e1dd5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Nov 8 18:08:03 2022 +0300

    all: upd chlog
2022-11-08 18:15:01 +03:00
Ainar Garipov
2bfdcbbc10 Pull request: upd-before-release
Merge in DNS/adguard-home from upd-before-release to master

Squashed commit of the following:

commit 71f36273a55f63d389188fd7df2950a6207549a9
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Nov 8 14:35:18 2022 +0300

    all: upd deps, tools, filters
2022-11-08 16:24:44 +03:00
Ainar Garipov
8fdbcc005c Pull request: imp-pprof
Merge in DNS/adguard-home from imp-pprof to master

Squashed commit of the following:

commit cd6c2ec15342ef55957ab65e1599733c2ee57b7b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Nov 7 19:45:12 2022 +0300

    home: imp pprof handling, field alignment
2022-11-08 11:36:42 +03:00
Ainar Garipov
464fbf0b54 Pull request: 5089-windows-hosts-crash
Updates #5089.

Squashed commit of the following:

commit dd3ce763326ad3207de111bb911e0a2665bcebba
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Nov 7 16:21:43 2022 +0300

    aghnet: fix comparison

commit 0d736fb7fc5cb2e77fd533cd95fdf3fbc6dc86d1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Nov 7 14:13:05 2022 +0300

    aghnet: fix crash
2022-11-07 16:51:07 +03:00
Ainar Garipov
a7d02fa935 Pull request: upd-chlog
Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit d148e3db4f432cfb447eeda2f56f1df2fb512a50
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Nov 2 18:33:21 2022 +0300

    all: upd chlog
2022-11-02 18:39:46 +03:00
Ainar Garipov
af8f64ac00 Pull request: upd-i18n
Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit 1460df0f7c07f49f056068a6b01ab6e01566a087
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Nov 2 14:43:23 2022 +0300

    client: upd i18n
2022-11-02 14:53:45 +03:00
Eugene Burkov
c139287787 Pull request: 4927-ddr-ip-san
Merge in DNS/adguard-home from 4927-ddr-ip-san to master

Updates #4927.

Squashed commit of the following:

commit 92e7498a7a9101648c4cfdf719adf4eb135fc903
Merge: f4770abf fa0fd90d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Nov 2 14:29:08 2022 +0300

    Merge branch 'master' into 4927-ddr-ip-san

commit f4770abf98ea2c0db2f0c2ddb9509a29a06c9509
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Nov 2 13:50:40 2022 +0300

    dnsforward: imp logs

commit 8d71371365070e221e104ae20acc8312e840eff9
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Nov 1 20:57:43 2022 +0300

    all: imp code, docs

commit 9793820f2c581e0ffcb28a59677be5c8df0c43f3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Nov 1 19:37:39 2022 +0300

    all: remember the cert props
2022-11-02 14:37:41 +03:00
Ainar Garipov
fa0fd90ddd Pull request: upd-go
Merge in DNS/adguard-home from upd-go to master

Squashed commit of the following:

commit 9548937ef70f195bbd9b6d19093bb88e9d5f0330
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Nov 2 14:09:46 2022 +0300

    all: upd go, tools
2022-11-02 14:22:10 +03:00
Eugene Burkov
c5565a9e4e Pull request: 4898-redirect-https
Merge in DNS/adguard-home from 4898-redirect-https to master

Updates #4898.
Updates #4927.

Squashed commit of the following:

commit bc41b6cae7ede0f1235e3956ab49204af1c9f38d
Merge: 815e2991 ac7634da
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Nov 1 13:02:23 2022 +0300

    Merge branch 'master' into 4898-redirect-https

commit 815e299137224fc3c7fd46924d7b936515b95d67
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Nov 1 12:58:28 2022 +0300

    home: imp ip addr detection

commit 9d4ecd9ab0e13ef6c19c3b923363bff43394ea4c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Oct 31 17:23:41 2022 +0300

    home: imp cyclo

commit 86c47b68fe6e3916cec97eee5d34e3e6c18e4892
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Oct 31 15:06:05 2022 +0300

    all: imp text

commit bcc25697b551668d1dab53a874e716fcadd83f09
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Oct 31 11:47:57 2022 +0300

    home: fix test

commit bb51a74cb82eeaa977821fa7314810c7b8be55cb
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Sun Oct 30 23:23:40 2022 +0300

    home: imp code

commit 38522330691baf8475a59ed4f40b1d45363df1e3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Oct 28 17:00:50 2022 +0300

    home: imp code

commit 7284f7288feb7491560f0f5d2754044c7a9f603a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Oct 27 19:42:57 2022 +0300

    all: log changes

commit 540efcb013e15294b98efe581323f75ceefc8f5a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Oct 27 19:24:21 2022 +0300

    home: imp tls
2022-11-01 13:09:52 +03:00
Ainar Garipov
ac7634da37 Pull request: imp-ups-tests
Merge in DNS/adguard-home from imp-ups-tests to master

Squashed commit of the following:

commit 60c22e8dfb81c3613b00994ccf01ba96137946f9
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Oct 28 19:26:34 2022 +0300

    all: imp names, fix aaaa

commit c3bd8a9fa95a86f5af86ea2956565337ee620c99
Merge: 23f82237 746e9df7
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Oct 28 19:16:56 2022 +0300

    Merge branch 'master' into imp-ups-tests

commit 23f82237a81b0ef22cf418afdc2ec95dc50639a3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Oct 28 18:14:27 2022 +0300

    all: imp upstream tests
2022-10-28 19:35:59 +03:00
Ainar Garipov
746e9df727 Pull request: rm-jsonutil
Merge in DNS/adguard-home from rm-jsonutil to master

Squashed commit of the following:

commit dec746d321adbeb41bfd0c44e71d198809c4731e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Oct 28 18:41:39 2022 +0300

    querylog: rm jsonutil
2022-10-28 19:15:27 +03:00
Ainar Garipov
3dd7393b3f Pull request: upd-pkgs-tools
Merge in DNS/adguard-home from upd-pkgs-tools to master

Squashed commit of the following:

commit 114c1ee94ff20339709e3ed66941d46692ab062c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Oct 28 17:17:03 2022 +0300

    all: upd go pkgs, tools
2022-10-28 17:42:45 +03:00
Ainar Garipov
9c9d6b48e3 Pull request: imp-tests
Merge in DNS/adguard-home from imp-tests to master

Squashed commit of the following:

commit ea5b4e7a93359ae4800f75e77d02944a4f5d2df8
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Oct 27 15:12:45 2022 +0300

    all: imp tests, use testutil
2022-10-27 16:11:36 +03:00
Ainar Garipov
9951d861d1 Pull request: 3972-hostlists-services
Updates #3972.

Squashed commit of the following:

commit 9dc0efe2453cb6c738d97d39b02c86eccb18a42c
Merge: 239550f8 8a935d4f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Oct 27 14:42:38 2022 +0300

    Merge branch 'master' into 3972-hostlists-services

commit 239550f84228e7c7a6f4ae6b1cadcc47e01f54d5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Oct 27 14:41:42 2022 +0300

    filtering: upd service list

commit b8bf3a6a4b1333059b886be95a1419612aebac39
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Oct 27 13:41:09 2022 +0300

    client: remove todo

commit caa504b482befb804db2a1ca0b6d4834aa4da49a
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Oct 27 12:54:45 2022 +0300

    fix build

commit 511797c305d9eef84a20553dab795414e00da51a
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Oct 27 12:40:33 2022 +0300

    client: add titles with service names to the clients table

commit 79ed3157a85b489a0b13381cff867a8c73ba60e9
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Oct 27 12:36:59 2022 +0300

    client: fix empty icons

commit ab69b95784de87665d5a1a3683f28e3b3df1c210
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Oct 27 11:55:48 2022 +0300

    client: use all blocked services

commit 9a4a87665c8463224d8e93f1e162988107f6c7ca
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Oct 25 19:25:20 2022 +0300

    all: fix json response

commit 86eb4493ce305cd5991176bd4cd8f7f5afdea330
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Oct 25 19:09:44 2022 +0300

    all: use hostslists registry for blocked svcs
2022-10-27 15:46:25 +03:00
Ainar Garipov
8a935d4ffb Pull request: 5035-dhcp-hosts-netip-addr
Updates #5035.

Squashed commit of the following:

commit 3a272842f738da322abb2bc5306aed94da79304b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Oct 26 20:34:49 2022 +0300

    dnsforward: imp docs, tests

commit b442ca9b57d730be3af14c68759c706f1742e4c4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Oct 26 19:51:21 2022 +0300

    dnsforward: imp code, tests

commit 8fca6de93edb8cfdb0ff5a940d08f8700e12a423
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Oct 26 16:38:27 2022 +0300

    dnsforward: mv dhcp hosts to netip.Addr
2022-10-26 21:12:51 +03:00
Ainar Garipov
bf10f157ab Pull request: http3-tls-fix
Merge in DNS/adguard-home from http3-tls-fix to master

Squashed commit of the following:

commit 4b4ac9f91d6a36654674c5e7037d2bf35a6b211a
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Oct 26 15:35:07 2022 +0300

    dnsforward: add crutch for quic-go bug
2022-10-26 15:54:04 +03:00
Eugene Burkov
15f5876e33 Pull request: upd-proxy
Merge in DNS/adguard-home from upd-proxy to master

Squashed commit of the following:

commit ba7c3595d7a0747b18f16e418473cb633b09200c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Oct 25 18:15:15 2022 +0300

    all: upd dnsproxy
2022-10-25 18:34:55 +03:00
Ainar Garipov
04c8e3b288 Pull request: 5035-netip-arp-hosts
Updates #5035.

Squashed commit of the following:

commit d1c4493ee4e28d05670c20532ebae1aa809d18da
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Oct 25 14:26:52 2022 +0300

    aghnet: imp hosts rec equal

commit 0a7f40a64a819245fba20d3b481b0fc34e0c60e6
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Oct 24 18:10:09 2022 +0300

    aghnet: move arp and hosts to netip.Addr
2022-10-25 15:08:12 +03:00
Ainar Garipov
cebbb69a4c Pull request: 5035-netip-maps-clients
Updates #5035.

Squashed commit of the following:

commit c2d38fe75b8aa2f00b19892724984ed3bb843db5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Oct 24 16:50:14 2022 +0300

    home: move clients to netip.Addr
2022-10-24 17:49:52 +03:00
Ainar Garipov
a272b61ed6 Pull request: 5035-netip-maps-access
Updates #5035.

Squashed commit of the following:

commit 0c9f80761419dc50d89e0e82f68cdb462569417d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Oct 24 16:11:03 2022 +0300

    dnsforward: fix access check

commit df981acb4816cfba11bf6bbe4ef7796a6e365ea9
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Oct 24 15:27:45 2022 +0300

    dnsforward: mv access to netip.Addr
2022-10-24 16:29:44 +03:00
Ainar Garipov
b86250737e Pull request: 5035-slices-clone
Updates #5035.

Squashed commit of the following:

commit 9a5be90a347d4da08649927731b20fd5ecb90be2
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Oct 21 20:11:54 2022 +0300

    all: use slices.Clone, netip.AddrPort
2022-10-21 20:42:00 +03:00
Eugene Burkov
a149d816d9 Pull request: 4916 Editing filter
Merge in DNS/adguard-home from 4916-fix-filter-edit to master

Closes #4916.

Squashed commit of the following:

commit c31be58abf73ed6047edc04ee606bceeb698f1bb
Merge: c9f3e337 67d89660
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Oct 21 19:58:16 2022 +0300

    Merge branch 'master' into 4916-fix-filter-edit

commit c9f3e337be8f005cc667d1cfd505f8cbca97cf20
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Oct 21 14:49:53 2022 +0300

    filtering: imp docs

commit ef8228fd51772fb4c1876864a1d8e41caec45a70
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Oct 21 12:40:00 2022 +0300

    filtering: imp code

commit 57fdbfca882537e50685b514f89bd9cf8a4cf5da
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Oct 20 11:54:39 2022 +0300

    filtering: imp docs

commit 670ac9aa009f4d6b021c37992182492f943a5005
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Oct 19 21:03:26 2022 +0300

    home: unexport close of clients container

commit f5b29166ede4c89966740bee8d09b443fde9e475
Merge: 2e57624e 2de42284
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Oct 19 21:02:33 2022 +0300

    Merge branch 'master' into 4916-fix-filter-edit

commit 2e57624e00ff702ef469ec0aa129eae9b627e41f
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Oct 19 21:01:19 2022 +0300

    filtering: imp code, tests

commit be56df7cef9b0548de3ac6bb9ced7705d7f31783
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Oct 18 15:31:30 2022 +0300

    filtering: fix url edit
2022-10-21 20:14:43 +03:00
Ainar Garipov
67d89660ca Pull request: upd-twosky
Merge in DNS/adguard-home from upd-twosky to master

Squashed commit of the following:

commit aba02252dc754aa25a7ea117c0310f990d45eeed
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Oct 21 19:13:24 2022 +0300

    all: upd twosky
2022-10-21 19:21:21 +03:00
Ainar Garipov
2a85d7dd7e Pull request: 3972-hostlists-registry
Updates #3972.

Squashed commit of the following:

commit 8341c13c39f38048796bc42c5f5337e1c8d3bcd4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Oct 21 13:06:55 2022 +0300

    all: imp docs

commit b7f961c879abf288c347e5bf55182e7dfa8e84e4
Merge: 607077ca 68d13fcc
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Oct 20 18:23:57 2022 +0300

    Merge branch 'master' into 3972-hostlists-registry

commit 607077ca8141f732d4678955c62cc2345958766b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Oct 20 18:07:09 2022 +0300

    all: use hostlists registry
2022-10-21 14:05:10 +03:00
Eugene Burkov
68d13fcc2b Pull request: 5052 Close errors
Merge in DNS/adguard-home from 5052-nonfatal-close to master

Updates #5052.

Squashed commit of the following:

commit 8411de465a29517640556e7d2390059482e01e8a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Oct 20 16:54:52 2022 +0300

    dnsforward: imp code

commit d6dff964397d6d878267f24b844bc6040edec316
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Oct 20 16:48:55 2022 +0300

    dnsforward: revert tmp stuff

commit 519aaf21cd1929141ea1902388d40599dced55ea
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Oct 20 16:28:32 2022 +0300

    dnsforward: fix ups closing errors
2022-10-20 18:22:37 +03:00
119 changed files with 3485 additions and 2716 deletions

View File

@@ -6,6 +6,11 @@
website
'name': 'AdGuard filters issues'
'url': 'https://link.adtidy.org/forward.html?action=report&app=home&from=github'
- 'about': >
Please send requests for addition to the vetted filtering lists to the
Hostlists Registry repository.
'name': 'AdGuard Hostlists Registry'
'url': 'https://github.com/AdguardTeam/HostlistsRegistry'
- 'about': >
Please use GitHub Discussions for questions
'name': 'Q&A Discussions'

View File

@@ -1,7 +1,7 @@
'name': 'build'
'env':
'GO_VERSION': '1.18.7'
'GO_VERSION': '1.18.8'
'NODE_VERSION': '14'
'on':

View File

@@ -1,7 +1,7 @@
'name': 'lint'
'env':
'GO_VERSION': '1.18.7'
'GO_VERSION': '1.18.8'
'on':
'push':

View File

@@ -15,8 +15,57 @@ and this project adheres to
## [v0.108.0] - TBA (APPROX.)
-->
## Added
<!--
## [v0.107.19] - 2022-11-23 (APPROX.)
See also the [v0.107.19 GitHub milestone][ms-v0.107.19].
[ms-v0.107.19]: https://github.com/AdguardTeam/AdGuardHome/milestone/55?closed=1
-->
### Added
- The new `--update` command-line option, which allows updating AdGuard Home
silently ([#4223]).
[#4223]: https://github.com/AdguardTeam/AdGuardHome/issues/4223
## [v0.107.18] - 2022-11-08
See also the [v0.107.18 GitHub milestone][ms-v0.107.18].
### Fixed
- Crash on some systems when domains from system hosts files are processed
([#5089]).
[#5089]: https://github.com/AdguardTeam/AdGuardHome/issues/5089
[ms-v0.107.18]: https://github.com/AdguardTeam/AdGuardHome/milestone/54?closed=1
## [v0.107.17] - 2022-11-02
See also the [v0.107.17 GitHub milestone][ms-v0.107.17].
### Security
- Go version has been updated to prevent the possibility of exploiting the
CVE-2022-41716 Go vulnerability fixed in [Go 1.18.8][go-1.18.8].
### Added
- The warning message when adding a certificate having no IP addresses
([#4898]).
- Several new blockable services ([#3972]). Those will now be more in sync with
the services that are already blockable in AdGuard DNS.
- A new HTTP API, `GET /control/blocked_services/all`, that lists all available
blocked services and their data, such as SVG icons ([#3972]).
- The new optional `tls.override_tls_ciphers` property, which allows
overriding TLS ciphers used by AdGuard Home ([#4925], [#4990]).
- The ability to serve DNS on link-local IPv6 addresses ([#2926]).
@@ -26,10 +75,20 @@ and this project adheres to
### Changed
- DNS-over-TLS resolvers aren't returned anymore when the configured TLS
certificate contains no IP addresses ([#4927]).
- Responses with `SERVFAIL` code are now cached for at least 30 seconds.
### Deprecated
- The `GET /control/blocked_services/services` HTTP API; use the new
`GET /control/blocked_services/all` API instead ([#3972]).
### Fixed
- ClientIDs not working when using DNS-over-HTTPS with HTTP/3.
- Editing the URL of an enabled rule list also includes validation of the filter
contents preventing from saving a bad one ([#4916]).
- The default value of `dns.cache_size` accidentally set to 0 has now been
reverted to 4 MiB ([#5010]).
- Responses for which the DNSSEC validation had explicitly been omitted aren't
@@ -38,6 +97,9 @@ and this project adheres to
[#2926]: https://github.com/AdguardTeam/AdGuardHome/issues/2926
[#3418]: https://github.com/AdguardTeam/AdGuardHome/issues/3418
[#3972]: https://github.com/AdguardTeam/AdGuardHome/issues/3972
[#4898]: https://github.com/AdguardTeam/AdGuardHome/issues/4898
[#4916]: https://github.com/AdguardTeam/AdGuardHome/issues/4916
[#4925]: https://github.com/AdguardTeam/AdGuardHome/issues/4925
[#4942]: https://github.com/AdguardTeam/AdGuardHome/issues/4942
[#4986]: https://github.com/AdguardTeam/AdGuardHome/issues/4986
@@ -45,17 +107,9 @@ and this project adheres to
[#4993]: https://github.com/AdguardTeam/AdGuardHome/issues/4993
[#5010]: https://github.com/AdguardTeam/AdGuardHome/issues/5010
[clientid]: https://github.com/AdguardTeam/AdGuardHome/wiki/Clients#clientid
<!--
## [v0.107.17] - 2022-11-02 (APPROX.)
See also the [v0.107.17 GitHub milestone][ms-v0.107.17].
[ms-v0.107.17]: https://github.com/AdguardTeam/AdGuardHome/milestone/52?closed=1
-->
[clientid]: https://github.com/AdguardTeam/AdGuardHome/wiki/Clients#clientid
[go-1.18.8]: https://groups.google.com/g/golang-announce/c/mbHY1UY3BaM
[ms-v0.107.17]: https://github.com/AdguardTeam/AdGuardHome/milestone/53?closed=1
@@ -64,7 +118,7 @@ See also the [v0.107.17 GitHub milestone][ms-v0.107.17].
This is a security update. There is no GitHub milestone, since no GitHub issues
were resolved.
## Security
### Security
- Go version has been updated to prevent the possibility of exploiting the
CVE-2022-2879, CVE-2022-2880, and CVE-2022-41715 Go vulnerabilities fixed in
@@ -798,7 +852,7 @@ See also the [v0.107.0 GitHub milestone][ms-v0.107.0].
- Query log search now supports internationalized domains ([#3012]).
- Internationalized domains are now shown decoded in the query log with the
original encoded version shown in request details ([#3013]).
- When /etc/hosts-type rules have several IPs for one host, all IPs are now
- When `/etc/hosts`-type rules have several IPs for one host, all IPs are now
returned instead of only the first one ([#1381]).
- Property `rlimit_nofile` is now in the `os` object of the configuration
file, together with the new `group` and `user` properties ([#2763]).
@@ -1056,7 +1110,7 @@ See also the [v0.106.0 GitHub milestone][ms-v0.106.0].
- Hostname uniqueness validation in the DHCP server ([#2952]).
- Hostname generating for DHCP clients which don't provide their own ([#2723]).
- New flag `--no-etc-hosts` to disable client domain name lookups in the
operating system's /etc/hosts files ([#1947]).
operating system's `/etc/hosts` files ([#1947]).
- The ability to set up custom upstreams to resolve PTR queries for local
addresses and to disable the automatic resolving of clients' addresses
([#2704]).
@@ -1373,11 +1427,13 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.17...HEAD
[v0.107.17]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.16...v0.107.17
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.19...HEAD
[v0.107.19]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.18...v0.107.19
-->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.16...HEAD
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.18...HEAD
[v0.107.18]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.17...v0.107.18
[v0.107.17]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.16...v0.107.17
[v0.107.16]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.15...v0.107.16
[v0.107.15]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.14...v0.107.15
[v0.107.14]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.13...v0.107.14

View File

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

View File

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

View File

@@ -393,6 +393,7 @@
"encryption_issuer": "Issuer",
"encryption_hostnames": "Hostnames",
"encryption_reset": "Are you sure you want to reset encryption settings?",
"encryption_warning": "Warning",
"topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings</0>.",
"topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings</0>.",
"form_error_port_range": "Enter port number in the range of 80-65535",

View File

@@ -88,7 +88,7 @@
"response_details": "Vastauksen tiedot",
"request_details": "Pyynnön tiedot",
"client_details": "Päätelaitteen tiedot",
"details": "Tiedot",
"details": "Yksityiskohdat",
"back": "Takaisin",
"dashboard": "Tila",
"settings": "Asetukset",
@@ -275,7 +275,7 @@
"nxdomain": "NXDOMAIN",
"refused": "REFUSED",
"null_ip": "Tyhjä IP",
"custom_ip": "Oma IP",
"custom_ip": "Oma IP-osoite",
"blocking_ipv4": "IPv4-esto",
"blocking_ipv6": "IPv6-esto",
"dnscrypt": "DNSCrypt",
@@ -415,8 +415,8 @@
"clients_title": "Pysyvät päätelaitteet",
"clients_desc": "Määritä pysyvät AdGuard Homeen yhdistetyt päätelaitetiedot.",
"settings_global": "Yleinen",
"settings_custom": "Oma",
"table_client": "Päätelaite",
"settings_custom": "Muut aiheet",
"table_client": "Asiakas",
"table_name": "Nimi",
"save_btn": "Tallenna",
"client_add": "Lisää päätelaite",
@@ -597,7 +597,7 @@
"show_whitelisted_responses": "Sallitut",
"show_processed_responses": "Käsitelty",
"blocked_safebrowsing": "Turvallisen selauksen estämät",
"blocked_adult_websites": "Lapsilukon estämät",
"blocked_adult_websites": "Estetty lapsilukolla",
"blocked_threats": "Estetyt uhat",
"allowed": "Sallitut",
"filtered": "Suodatetut",

View File

@@ -126,7 +126,7 @@
"number_of_dns_query_to_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්‍ර සඳහා ව.නා.ප. ඉල්ලීම් ගණන",
"average_processing_time": "සාමාන්‍ය සැකසුම් කාලය",
"average_processing_time_hint": "ව.නා.ප. ඉල්ලීමක් සැකසීමේ සාමාන්‍ය කාලය මිලි තත්පර වලින්",
"block_domain_use_filters_and_hosts": "පෙරහන් සහ ධාරක ගොනු භාවිතා කරමින් වසම් අවහිර කරන්න",
"block_domain_use_filters_and_hosts": "පෙරහන් හා සත්කාරක ගොනු භාවිතයෙන් වසම් අවහිර කරන්න",
"filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති <a>පෙරහන්</a> තුළ පිහිටුවිය හැකිය.",
"use_adguard_browsing_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව භාවිතා කරන්න",
"use_adguard_browsing_sec_hint": "ඇඩ්ගාර්ඩ් හෝම් විසින් පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව මගින් වසම අවහිර කර ඇත්දැයි පරීක්‍ෂා කරයි. එය සිදු කිරීමට රහස්‍යතා-හිතකාමී බැලීමේ යෙ.ක්‍ර.මු. භාවිතා කෙරේ: වසමේ කෙටි උපසර්ගයක SHA256 පූරකයක් පමණක් සේවාදායකය වෙත යවනු ලැබේ.",
@@ -575,7 +575,7 @@
"blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුව",
"milliseconds_abbreviation": "මිලි තත්.",
"cache_size": "නිහිතයෙහි ප්‍රමාණය",
"cache_size_desc": "ව.නා.ප. නිහිතයෙහි ප්‍රමාණය (බයිට)",
"cache_size_desc": "ව.නා.ප. නිහිතයෙහි ප්‍රමාණය (බයිට). නිහිතය අබල කිරීමට, හිස්ව තබන්න.",
"cache_ttl_min_override": "අවම පව. කා. අභිබවන්න",
"cache_ttl_max_override": "උපරිම පව. කා. අභිබවන්න",
"enter_cache_size": "ව.නා.ප. නිහිතයෙහි ප්‍රමාණය යොදන්න (බයිට)",

View File

@@ -215,7 +215,7 @@
"example_upstream_udp": "uobičajen DNS (preko UDP, imena domaćina);",
"example_upstream_dot": "šifrovano <0>DNS-over-TLS</0>;",
"example_upstream_doh": "šifrovano <0>DNS-over-HTTPS</0>;",
"example_upstream_doh3": "šifrovani DNS-over-HTTPS sa prinudnim <0>HTTP/3</0> i bez povratka na HTTP/2 ili ispod;",
"example_upstream_doh3": "šifrovani DNS-over-HTTPS sa prinudnim <0>HTTP/3</0> bez povratka na HTTP/2 ili ispod;",
"example_upstream_doq": "šifrovano <0>DNS-over-QUIC</0>;",
"example_upstream_sdns": "<0>DNS brojeve</0> za <1>DNSCrypt</1> ili <2>DNS-over-HTTPS</2> razrešivače;",
"example_upstream_tcp": "uobičajeni DNS (preko TCP);",

View File

@@ -32,6 +32,21 @@ export const getBlockedServices = () => async (dispatch) => {
}
};
export const getAllBlockedServicesRequest = createAction('GET_ALL_BLOCKED_SERVICES_REQUEST');
export const getAllBlockedServicesFailure = createAction('GET_ALL_BLOCKED_SERVICES_FAILURE');
export const getAllBlockedServicesSuccess = createAction('GET_ALL_BLOCKED_SERVICES_SUCCESS');
export const getAllBlockedServices = () => async (dispatch) => {
dispatch(getAllBlockedServicesRequest());
try {
const data = await apiClient.getAllBlockedServices();
dispatch(getAllBlockedServicesSuccess(data));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getAllBlockedServicesFailure());
}
};
export const setBlockedServicesRequest = createAction('SET_BLOCKED_SERVICES_REQUEST');
export const setBlockedServicesFailure = createAction('SET_BLOCKED_SERVICES_FAILURE');
export const setBlockedServicesSuccess = createAction('SET_BLOCKED_SERVICES_SUCCESS');

View File

@@ -465,11 +465,18 @@ class Api {
BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' };
BLOCKED_SERVICES_ALL = { path: 'blocked_services/all', method: 'GET' };
getBlockedServicesAvailableServices() {
const { path, method } = this.BLOCKED_SERVICES_SERVICES;
return this.makeRequest(path, method);
}
getAllBlockedServices() {
const { path, method } = this.BLOCKED_SERVICES_ALL;
return this.makeRequest(path, method);
}
getBlockedServices() {
const { path, method } = this.BLOCKED_SERVICES_LIST;
return this.makeRequest(path, method);

View File

@@ -15,7 +15,7 @@ import {
getObjDiff,
} from '../../helpers/helpers';
const filtersCatalog = require('../../helpers/filters/filters.json');
import filtersCatalog from '../../helpers/filters/filters';
class DnsBlocklist extends Component {
componentDidMount() {

View File

@@ -7,8 +7,7 @@ import classNames from 'classnames';
import { validatePath, validateRequiredValue } from '../../helpers/validators';
import { CheckboxField, renderInputField } from '../../helpers/form';
import { MODAL_OPEN_TIMEOUT, MODAL_TYPE, FORM_NAME } from '../../helpers/constants';
const filtersCatalog = require('../../helpers/filters/filters.json');
import filtersCatalog from '../../helpers/filters/filters';
const getIconsData = (homepage, source) => ([
{

View File

@@ -6,10 +6,11 @@ import flow from 'lodash/flow';
import { toggleAllServices } from '../../../helpers/helpers';
import { renderServiceField } from '../../../helpers/form';
import { FORM_NAME, SERVICES } from '../../../helpers/constants';
import { FORM_NAME } from '../../../helpers/constants';
const Form = (props) => {
const {
blockedServices,
handleSubmit,
change,
pristine,
@@ -27,7 +28,7 @@ const Form = (props) => {
type="button"
className="btn btn-secondary btn-block"
disabled={processing || processingSet}
onClick={() => toggleAllServices(SERVICES, change, true)}
onClick={() => toggleAllServices(blockedServices, change, true)}
>
<Trans>block_all</Trans>
</button>
@@ -37,17 +38,17 @@ const Form = (props) => {
type="button"
className="btn btn-secondary btn-block"
disabled={processing || processingSet}
onClick={() => toggleAllServices(SERVICES, change, false)}
onClick={() => toggleAllServices(blockedServices, change, false)}
>
<Trans>unblock_all</Trans>
</button>
</div>
</div>
<div className="services">
{SERVICES.map((service) => (
{blockedServices.map((service) => (
<Field
key={service.id}
icon={`service_${service.id}`}
icon={service.icon_svg}
name={`blocked_services.${service.id}`}
type="checkbox"
component={renderServiceField}
@@ -72,6 +73,7 @@ const Form = (props) => {
};
Form.propTypes = {
blockedServices: PropTypes.array.isRequired,
pristine: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
change: PropTypes.func.isRequired,

View File

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import Form from './Form';
import Card from '../../ui/Card';
import { getBlockedServices, setBlockedServices } from '../../../actions/services';
import { getBlockedServices, getAllBlockedServices, setBlockedServices } from '../../../actions/services';
import PageTitle from '../../ui/PageTitle';
const getInitialDataForServices = (initial) => (initial ? initial.reduce(
@@ -21,6 +21,7 @@ const Services = () => {
useEffect(() => {
dispatch(getBlockedServices());
dispatch(getAllBlockedServices());
}, []);
const handleSubmit = (values) => {
@@ -49,6 +50,7 @@ const Services = () => {
<div className="form">
<Form
initialValues={initialValues}
blockedServices={services.allServices}
processing={services.processing}
processingSet={services.processingSet}
onSubmit={handleSubmit}

View File

@@ -27,6 +27,7 @@ const ResponseCell = ({
const filters = useSelector((state) => state.filtering.filters, shallowEqual);
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
const services = useSelector((store) => store?.services);
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
@@ -60,8 +61,8 @@ const ResponseCell = ({
install_settings_dns: upstreamString,
elapsed: formattedElapsedMs,
response_code: status,
...(service_name
&& { service_name: getServiceName(service_name) }
...(service_name && services.allServices
&& { service_name: getServiceName(services.allServices, service_name) }
),
...(rules.length > 0
&& { rule_label: getRulesToFilterList(rules, filters, whitelistFilters) }
@@ -80,10 +81,10 @@ const ResponseCell = ({
const getDetailedInfo = (reason) => {
switch (reason) {
case FILTERED_STATUS.FILTERED_BLOCKED_SERVICE:
if (!service_name) {
if (!service_name || !services.allServices) {
return formattedElapsedMs;
}
return getServiceName(service_name);
return getServiceName(services.allServices, service_name);
case FILTERED_STATUS.FILTERED_BLACK_LIST:
case FILTERED_STATUS.NOT_FILTERED_WHITE_LIST:
return getFilterNames(rules, filters, whitelistFilters).join(', ');

View File

@@ -52,6 +52,7 @@ const Row = memo(({
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
const processingSet = useSelector((state) => state.access.processingSet);
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual);
const services = useSelector((store) => store?.services);
const clients = useSelector((state) => state.dashboard.clients);
@@ -175,8 +176,8 @@ const Row = memo(({
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
encryption_status: isBlocked
? <div className="bg--danger">{requestStatus}</div> : requestStatus,
...(FILTERED_STATUS.FILTERED_BLOCKED_SERVICE && service_name
&& { service_name: getServiceName(service_name) }),
...(FILTERED_STATUS.FILTERED_BLOCKED_SERVICE && service_name && services.allServices
&& { service_name: getServiceName(services.allServices, service_name) }),
domain,
type_table_header: type,
protocol,

View File

@@ -16,6 +16,7 @@ import { getFilteringStatus } from '../../actions/filtering';
import { getClients } from '../../actions';
import { getDnsConfig } from '../../actions/dnsConfig';
import { getAccessList } from '../../actions/access';
import { getAllBlockedServices } from '../../actions/services';
import {
getLogsConfig,
resetFilteredLogs,
@@ -130,6 +131,7 @@ const Logs = () => {
setIsLoading(true);
dispatch(getFilteringStatus());
dispatch(getClients());
dispatch(getAllBlockedServices());
try {
await Promise.all([
dispatch(getLogsConfig()),

View File

@@ -1,25 +1,57 @@
import React, { Component, Fragment } from 'react';
/* eslint-disable react/display-name */
/* eslint-disable react/prop-types */
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import ReactTable from 'react-table';
import { MODAL_TYPE } from '../../../helpers/constants';
import { splitByNewLine, countClientsStatistics, sortIp } from '../../../helpers/helpers';
import Card from '../../ui/Card';
import Modal from './Modal';
import CellWrap from '../../ui/CellWrap';
import LogsSearchLink from '../../ui/LogsSearchLink';
import { getAllBlockedServices } from '../../../../actions/services';
import {
splitByNewLine,
countClientsStatistics,
sortIp,
getService,
} from '../../../../helpers/helpers';
import { MODAL_TYPE } from '../../../../helpers/constants';
import Card from '../../../ui/Card';
import CellWrap from '../../../ui/CellWrap';
import LogsSearchLink from '../../../ui/LogsSearchLink';
import Modal from '../Modal';
class ClientsTable extends Component {
handleFormAdd = (values) => {
this.props.addClient(values);
const ClientsTable = ({
clients,
normalizedTopClients,
isModalOpen,
modalClientName,
modalType,
addClient,
updateClient,
deleteClient,
toggleClientModal,
processingAdding,
processingDeleting,
processingUpdating,
getStats,
supportedTags,
}) => {
const [t] = useTranslation();
const dispatch = useDispatch();
const services = useSelector((store) => store?.services);
useEffect(() => {
dispatch(getAllBlockedServices());
}, []);
const handleFormAdd = (values) => {
addClient(values);
};
handleFormUpdate = (values, name) => {
this.props.updateClient(values, name);
const handleFormUpdate = (values, name) => {
updateClient(values, name);
};
handleSubmit = (values) => {
const handleSubmit = (values) => {
const config = values;
if (values) {
@@ -42,21 +74,21 @@ class ClientsTable extends Component {
}
}
if (this.props.modalType === MODAL_TYPE.EDIT_FILTERS) {
this.handleFormUpdate(config, this.props.modalClientName);
if (modalType === MODAL_TYPE.EDIT_FILTERS) {
handleFormUpdate(config, modalClientName);
} else {
this.handleFormAdd(config);
handleFormAdd(config);
}
};
getOptionsWithLabels = (options) => (
const getOptionsWithLabels = (options) => (
options.map((option) => ({
value: option,
label: option,
}))
);
getClient = (name, clients) => {
const getClient = (name, clients) => {
const client = clients.find((item) => name === item.name);
if (client) {
@@ -65,7 +97,7 @@ class ClientsTable extends Component {
} = client;
return {
upstreams: (upstreams && upstreams.join('\n')) || '',
tags: (tags && this.getOptionsWithLabels(tags)) || [],
tags: (tags && getOptionsWithLabels(tags)) || [],
...values,
};
}
@@ -78,17 +110,17 @@ class ClientsTable extends Component {
};
};
handleDelete = (data) => {
const handleDelete = (data) => {
// eslint-disable-next-line no-alert
if (window.confirm(this.props.t('client_confirm_delete', { key: data.name }))) {
this.props.deleteClient(data);
this.props.getStats();
if (window.confirm(t('client_confirm_delete', { key: data.name }))) {
deleteClient(data);
getStats();
}
};
columns = [
const columns = [
{
Header: this.props.t('table_client'),
Header: t('table_client'),
accessor: 'ids',
minWidth: 150,
Cell: (row) => {
@@ -109,13 +141,13 @@ class ClientsTable extends Component {
sortMethod: sortIp,
},
{
Header: this.props.t('table_name'),
Header: t('table_name'),
accessor: 'name',
minWidth: 120,
Cell: CellWrap,
},
{
Header: this.props.t('settings'),
Header: t('settings'),
accessor: 'use_global_settings',
minWidth: 120,
Cell: ({ value }) => {
@@ -133,7 +165,7 @@ class ClientsTable extends Component {
},
},
{
Header: this.props.t('blocked_services'),
Header: t('blocked_services'),
accessor: 'blocked_services',
minWidth: 180,
Cell: (row) => {
@@ -143,25 +175,40 @@ class ClientsTable extends Component {
return <Trans>settings_global</Trans>;
}
if (value && services.allServices) {
return (
<div className="logs__row logs__row--icons">
{value.map((service) => {
const serviceInfo = getService(services.allServices, service);
if (serviceInfo?.icon_svg) {
return (
<div
key={serviceInfo.name}
dangerouslySetInnerHTML={{
__html: window.atob(serviceInfo.icon_svg),
}}
className="service__icon service__icon--table"
title={serviceInfo.name}
/>
);
}
return null;
})}
</div>
);
}
return (
<div className="logs__row logs__row--icons">
{value && value.length > 0
? value.map((service) => (
<svg
className="service__icon service__icon--table"
title={service}
key={service}
>
<use xlinkHref={`#service_${service}`} />
</svg>
))
: ''}
</div>
);
},
},
{
Header: this.props.t('upstreams'),
Header: t('upstreams'),
accessor: 'upstreams',
minWidth: 120,
Cell: ({ value }) => {
@@ -179,7 +226,7 @@ class ClientsTable extends Component {
},
},
{
Header: this.props.t('tags_title'),
Header: t('tags_title'),
accessor: 'tags',
minWidth: 140,
Cell: (row) => {
@@ -203,11 +250,11 @@ class ClientsTable extends Component {
},
},
{
Header: this.props.t('requests_count'),
Header: t('requests_count'),
id: 'statistics',
accessor: (row) => countClientsStatistics(
row.ids,
this.props.normalizedTopClients.auto,
normalizedTopClients.auto,
),
sortMethod: (a, b) => b - a,
minWidth: 120,
@@ -222,16 +269,13 @@ class ClientsTable extends Component {
},
},
{
Header: this.props.t('actions_table_header'),
Header: t('actions_table_header'),
accessor: 'actions',
maxWidth: 100,
sortable: false,
resizable: false,
Cell: (row) => {
const clientName = row.original.name;
const {
toggleClientModal, processingDeleting, processingUpdating, t,
} = this.props;
return (
<div className="logs__row logs__row--center">
@@ -253,7 +297,7 @@ class ClientsTable extends Component {
<button
type="button"
className="btn btn-icon btn-outline-secondary btn-sm"
onClick={() => this.handleDelete({ name: clientName })}
onClick={() => handleDelete({ name: clientName })}
disabled={processingDeleting}
title={t('delete_table_action')}
>
@@ -267,76 +311,61 @@ class ClientsTable extends Component {
},
];
render() {
const {
t,
clients,
isModalOpen,
modalType,
modalClientName,
toggleClientModal,
processingAdding,
processingUpdating,
supportedTags,
} = this.props;
const currentClientData = getClient(modalClientName, clients);
const tagsOptions = getOptionsWithLabels(supportedTags);
const currentClientData = this.getClient(modalClientName, clients);
const tagsOptions = this.getOptionsWithLabels(supportedTags);
return (
<Card
title={t('clients_title')}
subtitle={t('clients_desc')}
bodyType="card-body box-body--settings"
>
<Fragment>
<ReactTable
data={clients || []}
columns={this.columns}
defaultSorted={[
{
id: 'statistics',
asc: true,
},
]}
className="-striped -highlight card-table-overflow"
showPagination
defaultPageSize={10}
minRows={5}
ofText="/"
previousText={t('previous_btn')}
nextText={t('next_btn')}
pageText={t('page_table_footer_text')}
rowsText={t('rows_table_footer_text')}
loadingText={t('loading_table_status')}
noDataText={t('clients_not_found')}
/>
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => toggleClientModal(MODAL_TYPE.ADD_FILTERS)}
disabled={processingAdding}
>
<Trans>client_add</Trans>
</button>
<Modal
isModalOpen={isModalOpen}
modalType={modalType}
toggleClientModal={toggleClientModal}
currentClientData={currentClientData}
handleSubmit={this.handleSubmit}
processingAdding={processingAdding}
processingUpdating={processingUpdating}
tagsOptions={tagsOptions}
/>
</Fragment>
</Card>
);
}
}
return (
<Card
title={t('clients_title')}
subtitle={t('clients_desc')}
bodyType="card-body box-body--settings"
>
<>
<ReactTable
data={clients || []}
columns={columns}
defaultSorted={[
{
id: 'statistics',
asc: true,
},
]}
className="-striped -highlight card-table-overflow"
showPagination
defaultPageSize={10}
minRows={5}
ofText="/"
previousText={t('previous_btn')}
nextText={t('next_btn')}
pageText={t('page_table_footer_text')}
rowsText={t('rows_table_footer_text')}
loadingText={t('loading_table_status')}
noDataText={t('clients_not_found')}
/>
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => toggleClientModal(MODAL_TYPE.ADD_FILTERS)}
disabled={processingAdding}
>
<Trans>client_add</Trans>
</button>
<Modal
isModalOpen={isModalOpen}
modalType={modalType}
toggleClientModal={toggleClientModal}
currentClientData={currentClientData}
handleSubmit={handleSubmit}
processingAdding={processingAdding}
processingUpdating={processingUpdating}
tagsOptions={tagsOptions}
/>
</>
</Card>
);
};
ClientsTable.propTypes = {
t: PropTypes.func.isRequired,
clients: PropTypes.array.isRequired,
normalizedTopClients: PropTypes.object.isRequired,
toggleClientModal: PropTypes.func.isRequired,
@@ -353,4 +382,4 @@ ClientsTable.propTypes = {
supportedTags: PropTypes.array.isRequired,
};
export default withTranslation()(ClientsTable);
export default ClientsTable;

View File

@@ -0,0 +1 @@
export { default as ClientsTable } from './ClientsTable';

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { connect, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import {
Field, FieldArray, reduxForm, formValueSelector,
@@ -19,7 +19,7 @@ import {
renderServiceField,
} from '../../../helpers/form';
import { validateClientId, validateRequiredValue } from '../../../helpers/validators';
import { CLIENT_ID_LINK, FORM_NAME, SERVICES } from '../../../helpers/constants';
import { CLIENT_ID_LINK, FORM_NAME } from '../../../helpers/constants';
import './Service.css';
const settingsCheckboxes = [
@@ -139,6 +139,7 @@ let Form = (props) => {
invalid,
tagsOptions,
} = props;
const services = useSelector((store) => store?.services);
const [activeTabLabel, setActiveTabLabel] = useState('settings');
@@ -180,7 +181,9 @@ let Form = (props) => {
type="button"
className="btn btn-secondary btn-block"
disabled={useGlobalServices}
onClick={() => toggleAllServices(SERVICES, change, true)}
onClick={() => (
toggleAllServices(services.allServices, change, true)
)}
>
<Trans>block_all</Trans>
</button>
@@ -190,25 +193,29 @@ let Form = (props) => {
type="button"
className="btn btn-secondary btn-block"
disabled={useGlobalServices}
onClick={() => toggleAllServices(SERVICES, change, false)}
onClick={() => (
toggleAllServices(services.allServices, change, false)
)}
>
<Trans>unblock_all</Trans>
</button>
</div>
</div>
<div className="services">
{SERVICES.map((service) => (
<Field
key={service.id}
icon={`service_${service.id}`}
name={`blocked_services.${service.id}`}
type="checkbox"
component={renderServiceField}
placeholder={service.name}
disabled={useGlobalServices}
/>
))}
</div>
{services.allServices.length > 0 && (
<div className="services">
{services.allServices.map((service) => (
<Field
key={service.id}
icon={service.icon_svg}
name={`blocked_services.${service.id}`}
type="checkbox"
component={renderServiceField}
placeholder={service.name}
disabled={useGlobalServices}
/>
))}
</div>
)}
</div>
</div>,
},

View File

@@ -9,6 +9,12 @@
cursor: pointer;
}
.service__text {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
@media screen and (min-width: 768px) {
.services {
display: flex;
@@ -33,7 +39,7 @@
margin-right: 30px;
margin-left: 0;
}
.service:nth-child(3n) {
margin-right: 0;
margin-left: auto;

View File

@@ -2,7 +2,7 @@ import React, { Component, Fragment } from 'react';
import { withTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import ClientsTable from './ClientsTable';
import { ClientsTable } from './ClientsTable';
import AutoClients from './AutoClients';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';

View File

@@ -56,6 +56,26 @@ const clearFields = (change, setTlsConfig, t) => {
}
};
const validationMessage = (warningValidation, isWarning) => {
if (!warningValidation) {
return null;
}
if (isWarning) {
return (
<div className="col-12">
<p><Trans>encryption_warning</Trans>: {warningValidation}</p>
</div>
);
}
return (
<div className="col-12">
<p className="text-danger">{warningValidation}</p>
</div>
);
};
let Form = (props) => {
const {
t,
@@ -95,6 +115,8 @@ let Form = (props) => {
|| !valid_cert
|| !valid_pair;
const isWarning = valid_key && valid_cert && valid_pair;
return (
<form onSubmit={handleSubmit}>
<div className="row">
@@ -382,11 +404,7 @@ let Form = (props) => {
)}
</div>
</div>
{warning_validation && (
<div className="col-12">
<p className="text-danger">{warning_validation}</p>
</div>
)}
{validationMessage(warning_validation, isWarning)}
</div>
<div className="btn-list mt-2">

File diff suppressed because one or more lines are too long

View File

@@ -15452,6 +15452,7 @@ a.tag-addon:hover {
}
.custom-switch-indicator {
flex-shrink: 0;
display: inline-block;
height: 1.25rem;
width: 2.25rem;

View File

@@ -202,158 +202,6 @@ export const FILTERS_URLS = {
blocked_services: '/blocked_services',
};
export const SERVICES = [
{
id: '9gag',
name: '9GAG',
},
{
id: 'amazon',
name: 'Amazon',
},
{
id: 'bilibili',
name: 'Bilibili',
},
{
id: 'cloudflare',
name: 'CloudFlare',
},
{
id: 'dailymotion',
name: 'Dailymotion',
},
{
id: 'discord',
name: 'Discord',
},
{
id: 'disneyplus',
name: 'Disney+',
},
{
id: 'ebay',
name: 'EBay',
},
{
id: 'epic_games',
name: 'Epic Games',
},
{
id: 'facebook',
name: 'Facebook',
},
{
id: 'hulu',
name: 'Hulu',
},
{
id: 'imgur',
name: 'Imgur',
},
{
id: 'instagram',
name: 'Instagram',
},
{
id: 'mail_ru',
name: 'Mail.ru',
},
{
id: 'netflix',
name: 'Netflix',
},
{
id: 'ok',
name: 'OK.ru',
},
{
id: 'origin',
name: 'Origin',
},
{
id: 'pinterest',
name: 'Pinterest',
},
{
id: 'qq',
name: 'QQ',
},
{
id: 'reddit',
name: 'Reddit',
},
{
id: 'skype',
name: 'Skype',
},
{
id: 'snapchat',
name: 'Snapchat',
},
{
id: 'spotify',
name: 'Spotify',
},
{
id: 'steam',
name: 'Steam',
},
{
id: 'telegram',
name: 'Telegram',
},
{
id: 'tiktok',
name: 'TikTok',
},
{
id: 'tinder',
name: 'Tinder',
},
{
id: 'twitch',
name: 'Twitch',
},
{
id: 'twitter',
name: 'Twitter',
},
{
id: 'viber',
name: 'Viber',
},
{
id: 'vimeo',
name: 'Vimeo',
},
{
id: 'vk',
name: 'VK.com',
},
{
id: 'wechat',
name: 'WeChat',
},
{
id: 'weibo',
name: 'Weibo',
},
{
id: 'whatsapp',
name: 'WhatsApp',
},
{
id: 'youtube',
name: 'YouTube',
},
];
export const SERVICES_ID_NAME_MAP = SERVICES.reduce((acc, { id, name }) => {
acc[id] = name;
return acc;
}, {});
export const ENCRYPTION_SOURCE = {
PATH: 'path',
CONTENT: 'content',

View File

@@ -1,162 +1,220 @@
{
// Code generated by go run ./scripts/vetted-filters/main.go; DO NOT EDIT.
/* eslint quote-props: 'off', quotes: 'off', comma-dangle: 'off', semi: 'off' */
export default {
"categories": {
"general": {
"name": "filter_category_general",
"description": "filter_category_general_desc"
},
"security": {
"name": "filter_category_security",
"description": "filter_category_security_desc"
"other": {
"name": "filter_category_other",
"description": "filter_category_other_desc"
},
"regional": {
"name": "filter_category_regional",
"description": "filter_category_regional_desc"
},
"other": {
"name": "filter_category_other",
"description": "filter_category_other_desc"
"security": {
"name": "filter_category_security",
"description": "filter_category_security_desc"
}
},
"filters": {
"adguard-dns-filter": {
"name": "AdGuard DNS filter",
"1hosts_lite": {
"name": "1Hosts (Lite)",
"categoryId": "general",
"homepage": "https://github.com/AdguardTeam/AdGuardSDNSFilter",
"source": "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt"
"homepage": "https://badmojr.github.io/1Hosts/",
"source": "https://badmojr.gitlab.io/1hosts/Lite/adblock.txt"
},
"adaway-default-blocklist": {
"name": "AdAway Default Blocklist",
"categoryId": "general",
"homepage": "https://github.com/AdAway/adaway.github.io/",
"source": "https://adaway.org/hosts.txt"
},
"peter-lowe-list": {
"name": "Peter Lowe's List",
"categoryId": "general",
"homepage": "https://pgl.yoyo.org/adservers/",
"source": "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblockplus&showintro=1&mimetype=plaintext"
},
"dan-pollock-list": {
"name": "Dan Pollock's List",
"categoryId": "general",
"homepage": "https://someonewhocares.org/",
"source": "https://someonewhocares.org/hosts/zero/hosts"
},
"oisd": {
"name": "OISD Blocklist Basic",
"categoryId": "general",
"homepage": "https://oisd.nl/",
"source": "https://abp.oisd.nl/basic/"
},
"game-console-adblock-list": {
"name": "Game Console Adblock List",
"categoryId": "general",
"homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/GameConsoleAdblockList.txt"
},
"perflyst-dandelion-sprout-smart-tv-blocklist-for-adguard-home": {
"name": "Perflyst and Dandelion Sprout's Smart-TV Blocklist",
"categoryId": "general",
"homepage": "https://github.com/Perflyst/PiHoleBlocklist",
"source": "https://raw.githubusercontent.com/Perflyst/PiHoleBlocklist/master/SmartTV-AGH.txt"
},
"nocoin-filter-list": {
"name": "NoCoin Filter List",
"categoryId": "security",
"homepage": "https://github.com/hoshsadiq/adblock-nocoin-list/",
"source": "https://raw.githubusercontent.com/hoshsadiq/adblock-nocoin-list/master/hosts.txt"
},
"the-big-list-of-hacked-malware-web-sites": {
"name": "The Big List of Hacked Malware Web Sites",
"categoryId": "security",
"homepage": "https://github.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites",
"source": "https://raw.githubusercontent.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites/master/hosts"
},
"scam-blocklist-by-durable-napkin": {
"name": "Scam Blocklist by DurableNapkin",
"categoryId": "security",
"homepage": "https://github.com/durablenapkin/scamblocklist",
"source": "https://raw.githubusercontent.com/durablenapkin/scamblocklist/master/adguard.txt"
},
"urlhaus-filter-online": {
"name": "Online Malicious URL Blocklist",
"categoryId": "security",
"homepage": "https://gitlab.com/malware-filter/urlhaus-filter",
"source": "https://malware-filter.gitlab.io/malware-filter/urlhaus-filter-agh-online.txt"
},
"dandelion-sprouts-anti-malware-list": {
"name": "Dandelion Sprout's Anti-Malware List",
"categoryId": "security",
"homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareAdGuardHome.txt"
},
"NOR-dandelion-sprouts-nordiske-filtre": {
"name": "NOR: Dandelion Sprouts nordiske filtre",
"CHN_adrules": {
"name": "CHN: AdRules DNS List",
"categoryId": "regional",
"homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/NorwegianExperimentalList%20alternate%20versions/NordicFiltersAdGuardHome.txt"
"homepage": "https://github.com/Cats-Team/AdRules",
"source": "https://raw.githubusercontent.com/Cats-Team/AdRules/main/dns.txt"
},
"POL-polish-filters-for-pihole": {
"name": "POL: Polish filters for Pi hole",
"categoryId": "regional",
"homepage": "https://www.certyficate.it/",
"source": "https://raw.githubusercontent.com/MajkiIT/polish-ads-filter/master/polish-pihole-filters/hostfile.txt"
},
"KOR-youslist": {
"name": "KOR: YousList",
"categoryId": "regional",
"homepage": "https://github.com/yous/YousList",
"source": "https://raw.githubusercontent.com/yous/YousList/master/hosts.txt"
},
"VNM-abpvn-list": {
"name": "VNM: ABPVN List",
"categoryId": "regional",
"homepage": "http://abpvn.com/",
"source": "https://abpvn.com/android/abpvn.txt"
},
"SWE-frellwit-swedish-hosts-file": {
"name": "SWE: Frellwit's Swedish Hosts File",
"categoryId": "regional",
"homepage": "https://github.com/lassekongo83/Frellwits-filter-lists/",
"source": "https://raw.githubusercontent.com/lassekongo83/Frellwits-filter-lists/master/Frellwits-Swedish-Hosts-File.txt"
},
"ITA-filtri-dns": {
"name": "ITA: Filtri-DNS",
"categoryId": "regional",
"homepage": "https://filtri-dns.ga/",
"source": "https://filtri-dns.ga/filtri.txt"
},
"IRN-unwanted-iranian-domains": {
"name": "IRN: Unwanted Iranian domains",
"categoryId": "regional",
"homepage": "https://github.com/DRSDavidSoft/additional-hosts",
"source": "https://raw.githubusercontent.com/DRSDavidSoft/additional-hosts/master/domains/blacklist/unwanted-iranian.txt"
},
"MKD-macedonian-pi-hole-blocklist": {
"name": "MKD: Macedonian Pi-hole Blocklist",
"categoryId": "regional",
"homepage": "https://github.com/cchevy/macedonian-pi-hole-blocklist",
"source": "https://raw.githubusercontent.com/cchevy/macedonian-pi-hole-blocklist/master/hosts.txt"
},
"CHN-anti-ad" : {
"CHN_anti_ad": {
"name": "CHN: anti-AD",
"categoryId": "regional",
"homepage": "https://anti-ad.net/",
"source": "https://anti-ad.net/easylist.txt"
},
"IDN-abpindo": {
"IDN_abpindo": {
"name": "IDN: ABPindo",
"categoryId": "regional",
"homepage": "https://github.com/ABPindo/indonesianadblockrules/",
"source": "https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/abpindo.txt"
"homepage": "https://github.com/ABPindo/indonesianadblockrules",
"source": "https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/aghome.txt"
},
"NLD-Easylist": {
"name": "NLD: Easylist",
"IRN_unwanted_iranian_domains": {
"name": "IRN: PersianBlocker list",
"categoryId": "regional",
"homepage": "https://forums.lanik.us/viewforum.php?f=100",
"source": "https://easylist-downloads.adblockplus.org/easylistdutch.txt"
"homepage": "https://github.com/MasterKia/PersianBlocker",
"source": "https://raw.githubusercontent.com/MasterKia/PersianBlocker/main/PersianBlockerHosts.txt"
},
"windows-spy-blocker" : {
"ITA_filtri_dns": {
"name": "ITA: Filtri-DNS",
"categoryId": "regional",
"homepage": "https://filtri-dns.ga/",
"source": "https://filtri-dns.ga/filtri.txt"
},
"KOR_list_kr": {
"name": "KOR: List-KR DNS",
"categoryId": "regional",
"homepage": "https://github.com/List-KR/List-KR",
"source": "https://github.com/List-KR/List-KR"
},
"KOR_youslist": {
"name": "KOR: YousList",
"categoryId": "regional",
"homepage": "https://github.com/yous/YousList",
"source": "https://raw.githubusercontent.com/yous/YousList/master/hosts.txt"
},
"MKD_macedonian_pi_hole_blocklist": {
"name": "MKD: Macedonian Pi-hole Blocklist",
"categoryId": "regional",
"homepage": "https://github.com/cchevy/macedonian-pi-hole-blocklist",
"source": "https://raw.githubusercontent.com/cchevy/macedonian-pi-hole-blocklist/master/hosts.txt"
},
"NOR_dandelion_sprouts_anti_malware_list": {
"name": "NOR: Dandelion Sprouts nordiske filtre",
"categoryId": "regional",
"homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/NorwegianExperimentalList%20alternate%20versions/NordicFiltersAdGuardHome.txt"
},
"POL_polish_filters_for_pi_hole": {
"name": "POL: Polish filters for Pi hole",
"categoryId": "regional",
"homepage": "https://www.certyficate.it/",
"source": "https://raw.githubusercontent.com/MajkiIT/polish-ads-filter/master/polish-pihole-filters/hostfile.txt"
},
"SWE_frellwit_swedish_hosts_file": {
"name": "SWE: Frellwit's Swedish Hosts File",
"categoryId": "regional",
"homepage": "https://github.com/lassekongo83/Frellwits-filter-lists/",
"source": "https://raw.githubusercontent.com/lassekongo83/Frellwits-filter-lists/master/Frellwits-Swedish-Hosts-File.txt"
},
"TUR_turk_adlist": {
"name": "TUR: turk-adlist",
"categoryId": "regional",
"homepage": "https://github.com/bkrucarci/turk-adlist",
"source": "https://raw.githubusercontent.com/bkrucarci/turk-adlist/master/hosts"
},
"VNM_abpvn": {
"name": "VNM: ABPVN List",
"categoryId": "regional",
"homepage": "http://abpvn.com/",
"source": "https://abpvn.com/android/abpvn.txt"
},
"adguard_dns_filter": {
"name": "AdGuard DNS filter",
"categoryId": "general",
"homepage": "https://github.com/AdguardTeam/AdGuardSDNSFilter",
"source": "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt"
},
"adway_default_blocklist": {
"name": "AdAway Default Blocklist",
"categoryId": "general",
"homepage": "https://github.com/AdAway/adaway.github.io/",
"source": "https://adaway.org/hosts.txt"
},
"curben_phishing_filter": {
"name": "Phishing URL Blocklist (PhishTank and OpenPhish)",
"categoryId": "security",
"homepage": "https://gitlab.com/malware-filter/phishing-filter",
"source": "https://malware-filter.gitlab.io/malware-filter/phishing-filter-agh.txt"
},
"dan_pollocks_list": {
"name": "Dan Pollock's List",
"categoryId": "general",
"homepage": "https://someonewhocares.org/",
"source": "https://someonewhocares.org/hosts/zero/hosts"
},
"dandelion_sprouts_anti_malware_list": {
"name": "Dandelion Sprout's Anti-Malware List",
"categoryId": "security",
"homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareAdGuardHome.txt"
},
"dandelion_sprouts_game_console_adblock_list": {
"name": "Dandelion Sprout's Game Console Adblock List",
"categoryId": "other",
"homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/GameConsoleAdblockList.txt"
},
"energized_spark": {
"name": "Energized Spark",
"categoryId": "general",
"homepage": "https://energized.pro/",
"source": "https://block.energized.pro/spark/formats/filter"
},
"nocoin_filter_list": {
"name": "NoCoin Filter List",
"categoryId": "security",
"homepage": "https://github.com/hoshsadiq/adblock-nocoin-list/",
"source": "https://raw.githubusercontent.com/hoshsadiq/adblock-nocoin-list/master/hosts.txt"
},
"notracking_hosts_blocklists": {
"name": "The NoTracking blocklist",
"categoryId": "general",
"homepage": "https://github.com/notracking/hosts-blocklists",
"source": "https://raw.githubusercontent.com/notracking/hosts-blocklists/master/adblock/adblock.txt"
},
"oisd_basic": {
"name": "OISD Blocklist Basic",
"categoryId": "general",
"homepage": "https://oisd.nl/",
"source": "https://abp.oisd.nl/basic/"
},
"oisd_full": {
"name": "OISD Blocklist Full",
"categoryId": "general",
"homepage": "https://oisd.nl/",
"source": "https://abp.oisd.nl/"
},
"perflyst_dandelion_sprout_smart_tv_blocklist_for_adguard_home": {
"name": "Perflyst and Dandelion Sprout's Smart-TV Blocklist",
"categoryId": "other",
"homepage": "https://github.com/Perflyst/PiHoleBlocklist",
"source": "https://raw.githubusercontent.com/Perflyst/PiHoleBlocklist/master/SmartTV-AGH.txt"
},
"peter_lowe_list": {
"name": "Peter Lowe's Blocklist",
"categoryId": "general",
"homepage": "https://pgl.yoyo.org/adservers/",
"source": "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblockplus\u0026showintro=1\u0026mimetype=plaintext"
},
"scam_blocklist_by_durablenapkin": {
"name": "Scam Blocklist by DurableNapkin",
"categoryId": "security",
"homepage": "https://github.com/durablenapkin/scamblocklist",
"source": "https://raw.githubusercontent.com/durablenapkin/scamblocklist/master/adguard.txt"
},
"staklerware_indicators_list": {
"name": "Stalkerware Indicators List",
"categoryId": "security",
"homepage": "https://github.com/AssoEchap/stalkerware-indicators",
"source": "https://raw.githubusercontent.com/AssoEchap/stalkerware-indicators/master/generated/hosts"
},
"steven_blacks_list": {
"name": "Steven Black's List",
"categoryId": "general",
"homepage": "https://github.com/StevenBlack/hosts",
"source": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
},
"the_big_list_of_hacked_malware_web_sites": {
"name": "The Big List of Hacked Malware Web Sites",
"categoryId": "security",
"homepage": "https://github.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites",
"source": "https://raw.githubusercontent.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites/master/hosts"
},
"urlhaus_filter_online": {
"name": "Malicious URL Blocklist (URLHaus)",
"categoryId": "security",
"homepage": "https://gitlab.com/malware-filter/urlhaus-filter",
"source": "https://malware-filter.gitlab.io/malware-filter/urlhaus-filter-agh.txt"
},
"windowsspyblocker_hosts_spy_rules": {
"name": "WindowsSpyBlocker - Hosts spy rules",
"categoryId": "other",
"homepage": "https://github.com/crazy-max/WindowsSpyBlocker",

View File

@@ -1,6 +1,8 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Trans } from 'react-i18next';
import cn from 'classnames';
import { createOnBlurHandler } from './helpers';
import { R_MAC_WITHOUT_COLON, R_UNIX_ABSOLUTE_PATH, R_WIN_ABSOLUTE_PATH } from './constants';
@@ -229,24 +231,34 @@ export const renderServiceField = ({
modifier,
icon,
meta: { touched, error },
}) => <Fragment>
<label className={`service custom-switch ${modifier}`}>
<input
{...input}
type="checkbox"
className="custom-switch-input"
value={placeholder.toLowerCase()}
disabled={disabled}
/>
<span className="service__switch custom-switch-indicator"></span>
<span className="service__text">{placeholder}</span>
<svg className="service__icon">
<use xlinkHref={`#${icon}`} />
</svg>
</label>
{!disabled && touched && error
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
</Fragment>;
}) => (
<>
<label className={cn('service custom-switch', { [modifier]: modifier })}>
<input
{...input}
type="checkbox"
className="custom-switch-input"
value={placeholder.toLowerCase()}
disabled={disabled}
/>
<span className="service__switch custom-switch-indicator"></span>
<span className="service__text" title={placeholder}>
{placeholder}
</span>
{icon && (
<div
dangerouslySetInnerHTML={{ __html: window.atob(icon) }}
className="service__icon"
/>
)}
</label>
{!disabled && touched && error && (
<span className="form__message form__message--error">
<Trans>{error}</Trans>
</span>
)}
</>
);
renderServiceField.propTypes = {
input: PropTypes.object.isRequired,

View File

@@ -21,7 +21,6 @@ import {
FILTERED,
FILTERED_STATUS,
R_CLIENT_ID,
SERVICES_ID_NAME_MAP,
STANDARD_DNS_PORT,
STANDARD_HTTPS_PORT,
STANDARD_WEB_PORT,
@@ -991,7 +990,22 @@ export const filterOutComments = (lines) => lines
.filter((line) => !line.startsWith(COMMENT_LINE_DEFAULT_TOKEN));
/**
* @param {string} serviceId
* @param {array} services
* @param {string} id
* @returns {string}
*/
export const getServiceName = (serviceId) => SERVICES_ID_NAME_MAP[serviceId] || serviceId;
export const getService = (services, id) => services.find((s) => s.id === id);
/**
* @param {array} services
* @param {string} id
* @returns {string}
*/
export const getServiceName = (services, id) => getService(services, id)?.name;
/**
* @param {array} services
* @param {string} id
* @returns {string}
*/
export const getServiceIcon = (services, id) => getService(services, id)?.icon_svg;

View File

@@ -12,6 +12,14 @@ const services = handleActions(
processing: false,
}),
[actions.getAllBlockedServicesRequest]: (state) => ({ ...state, processingAll: true }),
[actions.getAllBlockedServicesFailure]: (state) => ({ ...state, processingAll: false }),
[actions.getAllBlockedServicesSuccess]: (state, { payload }) => ({
...state,
allServices: payload.blocked_services,
processingAll: false,
}),
[actions.setBlockedServicesRequest]: (state) => ({ ...state, processingSet: true }),
[actions.setBlockedServicesFailure]: (state) => ({ ...state, processingSet: false }),
[actions.setBlockedServicesSuccess]: (state) => ({
@@ -21,8 +29,10 @@ const services = handleActions(
},
{
processing: true,
processingAll: true,
processingSet: false,
list: [],
allServices: [],
},
);

36
go.mod
View File

@@ -3,24 +3,24 @@ module github.com/AdguardTeam/AdGuardHome
go 1.18
require (
github.com/AdguardTeam/dnsproxy v0.46.1
github.com/AdguardTeam/golibs v0.10.9
github.com/AdguardTeam/dnsproxy v0.46.2
github.com/AdguardTeam/golibs v0.11.3
github.com/AdguardTeam/urlfilter v0.16.0
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.2.5
github.com/digineo/go-ipset/v2 v2.2.1
github.com/dimfeld/httptreemux/v5 v5.4.0
github.com/fsnotify/fsnotify v1.5.4
github.com/dimfeld/httptreemux/v5 v5.5.0
github.com/fsnotify/fsnotify v1.6.0
github.com/go-ping/ping v1.1.0
github.com/google/go-cmp v0.5.8
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/uuid v1.3.0
github.com/insomniacslk/dhcp v0.0.0-20220822114210-de18a9d48e84
github.com/kardianos/service v1.2.1
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c
github.com/kardianos/service v1.2.2
github.com/lucas-clemente/quic-go v0.29.2
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
github.com/mdlayher/netlink v1.6.0
github.com/mdlayher/netlink v1.6.2
// TODO(a.garipov): This package is deprecated; find a new one or use
// our own code for that. Perhaps, use gopacket.
github.com/mdlayher/raw v0.1.0
@@ -28,10 +28,10 @@ require (
github.com/stretchr/testify v1.8.0
github.com/ti-mo/netfilter v0.4.0
go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9
golang.org/x/net v0.0.0-20220927171203-f486391704dc
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
golang.org/x/crypto v0.1.0
golang.org/x/exp v0.0.0-20221106115401-f9659909a136
golang.org/x/net v0.1.0
golang.org/x/sys v0.2.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.0
@@ -48,20 +48,22 @@ require (
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/marten-seemann/qpack v0.2.1 // indirect
github.com/marten-seemann/qpack v0.3.0 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
github.com/mdlayher/packet v1.0.0 // indirect
github.com/mdlayher/socket v0.2.3 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/ginkgo/v2 v2.5.0 // indirect
github.com/onsi/gomega v1.24.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
golang.org/x/mod v0.6.0-dev.0.20220922195421-2adab6b8c60e // indirect
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/tools v0.2.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
)

91
go.sum
View File

@@ -1,9 +1,9 @@
github.com/AdguardTeam/dnsproxy v0.46.1 h1:ej9iRorG+vekaXGYB854waAiS+q8OfswYZ1MQRZolHk=
github.com/AdguardTeam/dnsproxy v0.46.1/go.mod h1:PAmRzFqls0E92XTglyY2ESAqMAzZJhHKErG1ZpRnpjA=
github.com/AdguardTeam/dnsproxy v0.46.2 h1:ZUKM713Ts5meYQqk6cJkUBMCFSWqFPXTgjXkN4RI1Vo=
github.com/AdguardTeam/dnsproxy v0.46.2/go.mod h1:PAmRzFqls0E92XTglyY2ESAqMAzZJhHKErG1ZpRnpjA=
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.10.9 h1:F9oP2da0dQ9RQDM1lGR7LxUTfUWu8hEFOs4icwAkKM0=
github.com/AdguardTeam/golibs v0.10.9/go.mod h1:W+5rznZa1cSNSFt+gPS7f4Wytnr9fOrd5ZYqwadPw14=
github.com/AdguardTeam/golibs v0.11.3 h1:Oif+REq2WLycQ2Xm3ZPmJdfftptss0HbGWbxdFaC310=
github.com/AdguardTeam/golibs v0.11.3/go.mod h1:87bN2x4VsTritptE3XZg9l8T6gznWsIxHBcQ1DeRIXA=
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
github.com/AdguardTeam/urlfilter v0.16.0 h1:IO29m+ZyQuuOnPLTzHuXj35V1DZOp1Dcryl576P2syg=
github.com/AdguardTeam/urlfilter v0.16.0/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
@@ -31,13 +31,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1MX77g=
github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU=
github.com/dimfeld/httptreemux/v5 v5.4.0 h1:IiHYEjh+A7pYbhWyjmGnj5HZK6gpOOvyBXCJ+BE8/Gs=
github.com/dimfeld/httptreemux/v5 v5.4.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ=
github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
@@ -52,8 +53,8 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -61,8 +62,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU=
@@ -74,8 +75,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20220822114210-de18a9d48e84 h1:MJTy6H+EpXLeAn0P5WAWeLk6dJA3V0ik6S3VJfUyQuI=
github.com/insomniacslk/dhcp v0.0.0-20220822114210-de18a9d48e84/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c h1:OCFM4+DXTWfNlyeoddrTwdup/ztkGSyAMR2UGcPckNQ=
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -84,16 +85,16 @@ github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGu
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kardianos/service v1.2.1 h1:AYndMsehS+ywIS6RB9KOlcXzteWUzxgMgBymJD7+BYk=
github.com/kardianos/service v1.2.1/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucas-clemente/quic-go v0.29.2 h1:O8Mt0O6LpvEW+wfC40vZdcw0DngwYzoxq5xULZNzSI8=
github.com/lucas-clemente/quic-go v0.29.2/go.mod h1:g6/h9YMmLuU54tL1gW25uIi3VlBp3uv+sBihplIuskE=
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE=
github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g=
github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE=
@@ -107,15 +108,14 @@ github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqc
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.1.2-0.20201013204415-ded538f7f4be/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
github.com/mdlayher/netlink v1.6.2 h1:D2zGSkvYsJ6NreeED3JiVTu1lj2sIYATqSaZlhPzUgQ=
github.com/mdlayher/netlink v1.6.2/go.mod h1:O1HXX2sIWSMJ3Qn1BYZk1yZM+7iMki/uYGGiwGyq/iU=
github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8=
github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.1.0 h1:K4PFMVy+AFsp0Zdlrts7yNhxc/uXoPVHi9RzRvtZF2Y=
github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5s9Sg=
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
@@ -129,12 +129,14 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.5.0 h1:TRtrvv2vdQqzkwrQ1ke6vtXf7IK34RBUJafIy1wMwls=
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg=
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -173,16 +175,16 @@ go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
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.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9 h1:lNtcVz/3bOstm7Vebox+5m3nLh/BYWnhmc3AhXOW6oI=
golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw=
golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.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.20220922195421-2adab6b8c60e h1:WhB000cGjOfbJiedMGvJkMTclI18VD69w27k+sceql8=
golang.org/x/mod v0.6.0-dev.0.20220922195421-2adab6b8c60e/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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=
@@ -194,7 +196,6 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -203,17 +204,17 @@ 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-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -230,7 +231,6 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -250,20 +250,21 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -271,8 +272,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
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=
@@ -282,8 +283,8 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -5,10 +5,11 @@ import (
"bytes"
"fmt"
"net"
"net/netip"
"sync"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"golang.org/x/exp/slices"
)
// ARPDB: The Network Neighborhood Database
@@ -54,7 +55,7 @@ type Neighbor struct {
Name string
// IP contains either IPv4 or IPv6.
IP net.IP
IP netip.Addr
// MAC contains the hardware address.
MAC net.HardwareAddr
@@ -64,8 +65,8 @@ type Neighbor struct {
func (n Neighbor) Clone() (clone Neighbor) {
return Neighbor{
Name: n.Name,
IP: netutil.CloneIP(n.IP),
MAC: netutil.CloneMAC(n.MAC),
IP: n.IP,
MAC: slices.Clone(n.MAC),
}
}

View File

@@ -5,6 +5,7 @@ package aghnet
import (
"bufio"
"net"
"net/netip"
"strings"
"sync"
@@ -47,22 +48,28 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
if ipStr := fields[1]; len(ipStr) < 2 {
continue
} else if ip := net.ParseIP(ipStr[1 : len(ipStr)-1]); ip == nil {
} else if ip, err := netip.ParseAddr(ipStr[1 : len(ipStr)-1]); err != nil {
log.Debug("arpdb: parsing arp output: ip: %s", err)
continue
} else {
n.IP = ip
}
hwStr := fields[3]
if mac, err := net.ParseMAC(hwStr); err != nil {
mac, err := net.ParseMAC(hwStr)
if err != nil {
log.Debug("arpdb: parsing arp output: mac: %s", err)
continue
} else {
n.MAC = mac
}
host := fields[0]
if err := netutil.ValidateDomainName(host); err != nil {
log.Debug("parsing arp output: %s", err)
err = netutil.ValidateDomainName(host)
if err != nil {
log.Debug("arpdb: parsing arp output: host: %s", err)
} else {
n.Name = host
}

View File

@@ -4,6 +4,7 @@ package aghnet
import (
"net"
"net/netip"
)
const arpAOutput = `
@@ -17,14 +18,14 @@ hostname.two (::ffff:ffff) at ef:cd:ab:ef:cd:ab on em0 expires in 1198 seconds [
var wantNeighs = []Neighbor{{
Name: "hostname.one",
IP: net.IPv4(192, 168, 1, 2),
IP: netip.MustParseAddr("192.168.1.2"),
MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
}, {
Name: "hostname.two",
IP: net.ParseIP("::ffff:ffff"),
IP: netip.MustParseAddr("::ffff:ffff"),
MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
}, {
Name: "",
IP: net.ParseIP("::1234"),
IP: netip.MustParseAddr("::1234"),
MAC: net.HardwareAddr{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
}}

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"io/fs"
"net"
"net/netip"
"strings"
"sync"
@@ -94,7 +95,8 @@ func (arp *fsysARPDB) Refresh() (err error) {
}
n := Neighbor{}
if n.IP = net.ParseIP(fields[0]); n.IP == nil || n.IP.IsUnspecified() {
n.IP, err = netip.ParseAddr(fields[0])
if err != nil || n.IP.IsUnspecified() {
continue
} else if n.MAC, err = net.ParseMAC(fields[3]); err != nil {
continue
@@ -135,15 +137,19 @@ func parseArpAWrt(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
n := Neighbor{}
if ip := net.ParseIP(fields[0]); ip == nil || n.IP.IsUnspecified() {
ip, err := netip.ParseAddr(fields[0])
if err != nil || n.IP.IsUnspecified() {
log.Debug("arpdb: parsing arp output: ip: %s", err)
continue
} else {
n.IP = ip
}
hwStr := fields[3]
if mac, err := net.ParseMAC(hwStr); err != nil {
log.Debug("parsing arp output: %s", err)
mac, err := net.ParseMAC(hwStr)
if err != nil {
log.Debug("arpdb: parsing arp output: mac: %s", err)
continue
} else {
@@ -174,7 +180,9 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
if ipStr := fields[1]; len(ipStr) < 2 {
continue
} else if ip := net.ParseIP(ipStr[1 : len(ipStr)-1]); ip == nil {
} else if ip, err := netip.ParseAddr(ipStr[1 : len(ipStr)-1]); err != nil {
log.Debug("arpdb: parsing arp output: ip: %s", err)
continue
} else {
n.IP = ip
@@ -182,7 +190,7 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
hwStr := fields[3]
if mac, err := net.ParseMAC(hwStr); err != nil {
log.Debug("parsing arp output: %s", err)
log.Debug("arpdb: parsing arp output: mac: %s", err)
continue
} else {
@@ -191,7 +199,7 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
host := fields[0]
if verr := netutil.ValidateDomainName(host); verr != nil {
log.Debug("parsing arp output: %s", verr)
log.Debug("arpdb: parsing arp output: host: %s", verr)
} else {
n.Name = host
}
@@ -218,14 +226,18 @@ func parseIPNeigh(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
n := Neighbor{}
if ip := net.ParseIP(fields[0]); ip == nil {
ip, err := netip.ParseAddr(fields[0])
if err != nil {
log.Debug("arpdb: parsing arp output: ip: %s", err)
continue
} else {
n.IP = ip
}
if mac, err := net.ParseMAC(fields[4]); err != nil {
log.Debug("parsing arp output: %s", err)
mac, err := net.ParseMAC(fields[4])
if err != nil {
log.Debug("arpdb: parsing arp output: mac: %s", err)
continue
} else {

View File

@@ -4,6 +4,7 @@ package aghnet
import (
"net"
"net/netip"
"sync"
"testing"
"testing/fstest"
@@ -33,10 +34,10 @@ const ipNeighOutput = `
::ffff:ffff dev enp0s3 lladdr ef:cd:ab:ef:cd:ab router STALE`
var wantNeighs = []Neighbor{{
IP: net.IPv4(192, 168, 1, 2),
IP: netip.MustParseAddr("192.168.1.2"),
MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
}, {
IP: net.ParseIP("::ffff:ffff"),
IP: netip.MustParseAddr("::ffff:ffff"),
MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
}}

View File

@@ -5,6 +5,7 @@ package aghnet
import (
"bufio"
"net"
"net/netip"
"strings"
"sync"
@@ -50,14 +51,18 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
n := Neighbor{}
if ip := net.ParseIP(fields[0]); ip == nil {
ip, err := netip.ParseAddr(fields[0])
if err != nil {
log.Debug("arpdb: parsing arp output: ip: %s", err)
continue
} else {
n.IP = ip
}
if mac, err := net.ParseMAC(fields[1]); err != nil {
log.Debug("parsing arp output: %s", err)
mac, err := net.ParseMAC(fields[1])
if err != nil {
log.Debug("arpdb: parsing arp output: mac: %s", err)
continue
} else {

View File

@@ -4,6 +4,7 @@ package aghnet
import (
"net"
"net/netip"
)
const arpAOutput = `
@@ -15,9 +16,9 @@ Host Ethernet Address Netif Expire Flags
`
var wantNeighs = []Neighbor{{
IP: net.IPv4(192, 168, 1, 2),
IP: netip.MustParseAddr("192.168.1.2"),
MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
}, {
IP: net.ParseIP("::ffff:ffff"),
IP: netip.MustParseAddr("::ffff:ffff"),
MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
}}

View File

@@ -2,6 +2,7 @@ package aghnet
import (
"net"
"net/netip"
"sync"
"testing"
@@ -35,7 +36,7 @@ func (arp *TestARPDB) Neighbors() (ns []Neighbor) {
}
func TestARPDBS(t *testing.T) {
knownIP := net.IP{1, 2, 3, 4}
knownIP := netip.MustParseAddr("1.2.3.4")
knownMAC := net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF}
succRefrCount, failRefrCount := 0, 0

View File

@@ -5,6 +5,7 @@ package aghnet
import (
"bufio"
"net"
"net/netip"
"strings"
"sync"
)
@@ -43,13 +44,15 @@ func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
n := Neighbor{}
if ip := net.ParseIP(fields[0]); ip == nil {
ip, err := netip.ParseAddr(fields[0])
if err != nil {
continue
} else {
n.IP = ip
}
if mac, err := net.ParseMAC(fields[1]); err != nil {
mac, err := net.ParseMAC(fields[1])
if err != nil {
continue
} else {
n.MAC = mac

View File

@@ -4,6 +4,7 @@ package aghnet
import (
"net"
"net/netip"
)
const arpAOutput = `
@@ -14,9 +15,9 @@ Interface: 192.168.1.1 --- 0x7
::ffff:ffff ef-cd-ab-ef-cd-ab static`
var wantNeighs = []Neighbor{{
IP: net.IPv4(192, 168, 1, 2),
IP: netip.MustParseAddr("192.168.1.2"),
MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF},
}, {
IP: net.ParseIP("::ffff:ffff"),
IP: netip.MustParseAddr("::ffff:ffff"),
MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB},
}}

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"io"
"io/fs"
"net"
"net/netip"
"path"
"strings"
"sync"
@@ -19,6 +19,7 @@ import (
"github.com/AdguardTeam/urlfilter/filterlist"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"golang.org/x/exp/maps"
)
// DefaultHostsPaths returns the slice of paths default for the operating system
@@ -106,14 +107,10 @@ type HostsContainer struct {
done chan struct{}
// updates is the channel for receiving updated hosts.
//
// TODO(e.burkov): Use map[netip.Addr]struct{} instead.
updates chan *netutil.IPMap
updates chan HostsRecords
// last is the set of hosts that was cached within last detected change.
//
// TODO(e.burkov): Use map[netip.Addr]struct{} instead.
last *netutil.IPMap
last HostsRecords
// fsys is the working file system to read hosts files from.
fsys fs.FS
@@ -128,6 +125,27 @@ type HostsContainer struct {
listID int
}
// HostsRecords is a mapping of an IP address to its hosts data.
type HostsRecords map[netip.Addr]*HostsRecord
// HostsRecord represents a single hosts file record.
type HostsRecord struct {
Aliases *stringutil.Set
Canonical string
}
// 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) {
if rec == nil {
return other == nil
} else if other == nil {
return false
}
return rec.Canonical == other.Canonical && rec.Aliases.Equal(other.Aliases)
}
// ErrNoHostsPaths is returned when there are no valid paths to watch passed to
// the HostsContainer.
const ErrNoHostsPaths errors.Error = "no valid paths to hosts files provided"
@@ -162,7 +180,7 @@ func NewHostsContainer(
},
listID: listID,
done: make(chan struct{}, 1),
updates: make(chan *netutil.IPMap, 1),
updates: make(chan HostsRecords, 1),
fsys: fsys,
w: w,
patterns: patterns,
@@ -200,9 +218,8 @@ func (hc *HostsContainer) Close() (err error) {
return nil
}
// Upd returns the channel into which the updates are sent. The receivable
// map's values are guaranteed to be of type of *HostsRecord.
func (hc *HostsContainer) Upd() (updates <-chan *netutil.IPMap) {
// Upd returns the channel into which the updates are sent.
func (hc *HostsContainer) Upd() (updates <-chan HostsRecords) {
return hc.updates
}
@@ -268,7 +285,7 @@ type hostsParser struct {
// table stores only the unique IP-hostname pairs. It's also sent to the
// updates channel afterwards.
table *netutil.IPMap
table HostsRecords
}
// newHostsParser creates a new *hostsParser with buffers of size taken from the
@@ -277,7 +294,7 @@ func (hc *HostsContainer) newHostsParser() (hp *hostsParser) {
return &hostsParser{
rulesBuilder: &strings.Builder{},
translations: map[string]string{},
table: netutil.NewIPMap(hc.last.Len()),
table: make(HostsRecords, len(hc.last)),
}
}
@@ -289,7 +306,7 @@ func (hp *hostsParser) parseFile(r io.Reader) (patterns []string, cont bool, err
s := bufio.NewScanner(r)
for s.Scan() {
ip, hosts := hp.parseLine(s.Text())
if ip == nil || len(hosts) == 0 {
if ip == (netip.Addr{}) || len(hosts) == 0 {
continue
}
@@ -300,14 +317,15 @@ func (hp *hostsParser) parseFile(r io.Reader) (patterns []string, cont bool, err
}
// parseLine parses the line having the hosts syntax ignoring invalid ones.
func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
func (hp *hostsParser) parseLine(line string) (ip netip.Addr, hosts []string) {
fields := strings.Fields(line)
if len(fields) < 2 {
return nil, nil
return netip.Addr{}, nil
}
if ip = net.ParseIP(fields[0]); ip == nil {
return nil, nil
ip, err := netip.ParseAddr(fields[0])
if err != nil {
return netip.Addr{}, nil
}
for _, f := range fields[1:] {
@@ -325,7 +343,7 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
// See https://github.com/AdguardTeam/AdGuardHome/issues/3946.
//
// TODO(e.burkov): Investigate if hosts may contain DNS-SD domains.
err := netutil.ValidateDomainName(f)
err = netutil.ValidateDomainName(f)
if err != nil {
log.Error("%s: host %q is invalid, ignoring", hostsContainerPref, f)
@@ -338,30 +356,13 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
return ip, hosts
}
// HostsRecord represents a single hosts file record.
type HostsRecord struct {
Aliases *stringutil.Set
Canonical string
}
// 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) {
if rec == nil {
return other == nil
}
return rec.Canonical == other.Canonical && rec.Aliases.Equal(other.Aliases)
}
// addRecord puts the record for the IP address to the rules builder if needed.
// The first host is considered to be the canonical name for the IP address.
// hosts must have at least one name.
func (hp *hostsParser) addRecord(ip net.IP, hosts []string) {
func (hp *hostsParser) addRecord(ip netip.Addr, hosts []string) {
line := strings.Join(append([]string{ip.String()}, hosts...), " ")
var rec *HostsRecord
v, ok := hp.table.Get(ip)
rec, ok := hp.table[ip]
if !ok {
rec = &HostsRecord{
Aliases: stringutil.NewSet(),
@@ -369,14 +370,7 @@ func (hp *hostsParser) addRecord(ip net.IP, hosts []string) {
rec.Canonical, hosts = hosts[0], hosts[1:]
hp.addRules(ip, rec.Canonical, line)
hp.table.Set(ip, rec)
} else {
rec, ok = v.(*HostsRecord)
if !ok {
log.Error("%s: adding pairs: unexpected type %T", hostsContainerPref, v)
return
}
hp.table[ip] = rec
}
for _, host := range hosts {
@@ -391,7 +385,7 @@ func (hp *hostsParser) addRecord(ip net.IP, hosts []string) {
}
// addRules adds rules and rule translations for the line.
func (hp *hostsParser) addRules(ip net.IP, host, line string) {
func (hp *hostsParser) addRules(ip netip.Addr, host, line string) {
rule, rulePtr := hp.writeRules(host, ip)
hp.translations[rule], hp.translations[rulePtr] = line, line
@@ -400,8 +394,9 @@ func (hp *hostsParser) addRules(ip net.IP, host, line string) {
// writeRules writes the actual rule for the qtype and the PTR for the host-ip
// pair into internal builders.
func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string) {
arpa, err := netutil.IPToReversedAddr(ip)
func (hp *hostsParser) writeRules(host string, ip netip.Addr) (rule, rulePtr string) {
// TODO(a.garipov): Add a netip.Addr version to netutil.
arpa, err := netutil.IPToReversedAddr(ip.AsSlice())
if err != nil {
return "", ""
}
@@ -419,7 +414,7 @@ func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string)
var qtype string
// The validation of the IP address has been performed earlier so it is
// guaranteed to be either an IPv4 or an IPv6.
if ip.To4() != nil {
if ip.Is4() {
qtype = "A"
} else {
qtype = "AAAA"
@@ -446,51 +441,8 @@ func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string)
return rule, rulePtr
}
// equalSet returns true if the internal hosts table just parsed equals target.
// target's values must be of type *HostsRecord.
func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {
if target == nil {
// hp.table shouldn't appear nil since it's initialized on each refresh.
return target == hp.table
}
if hp.table.Len() != target.Len() {
return false
}
hp.table.Range(func(ip net.IP, recVal any) (cont bool) {
var targetVal any
targetVal, ok = target.Get(ip)
if !ok {
return false
}
var rec *HostsRecord
rec, ok = recVal.(*HostsRecord)
if !ok {
log.Error("%s: comparing: unexpected type %T", hostsContainerPref, recVal)
return false
}
var targetRec *HostsRecord
targetRec, ok = targetVal.(*HostsRecord)
if !ok {
log.Error("%s: comparing: target: unexpected type %T", hostsContainerPref, targetVal)
return false
}
ok = rec.Equal(targetRec)
return ok
})
return ok
}
// sendUpd tries to send the parsed data to the ch.
func (hp *hostsParser) sendUpd(ch chan *netutil.IPMap) {
func (hp *hostsParser) sendUpd(ch chan HostsRecords) {
log.Debug("%s: sending upd", hostsContainerPref)
upd := hp.table
@@ -528,14 +480,18 @@ func (hc *HostsContainer) refresh() (err error) {
return fmt.Errorf("refreshing : %w", err)
}
if hp.equalSet(hc.last) {
// hc.last is nil on the first refresh, so let that one through.
//
// TODO(a.garipov): Once https://github.com/golang/go/issues/56621 is
// resolved, remove the first condition.
if hc.last != nil && maps.EqualFunc(hp.table, hc.last, (*HostsRecord).equal) {
log.Debug("%s: no changes detected", hostsContainerPref)
return nil
}
defer hp.sendUpd(hc.updates)
hc.last = hp.table.ShallowClone()
hc.last = maps.Clone(hp.table)
var rulesStrg *filterlist.RuleStorage
if rulesStrg, err = hp.newStrg(hc.listID); err != nil {

View File

@@ -3,6 +3,7 @@ package aghnet
import (
"io/fs"
"net"
"net/netip"
"path"
"strings"
"sync/atomic"
@@ -13,6 +14,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghchan"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/urlfilter"
@@ -135,7 +137,7 @@ func TestNewHostsContainer(t *testing.T) {
func TestHostsContainer_refresh(t *testing.T) {
// TODO(e.burkov): Test the case with no actual updates.
ip := net.IP{127, 0, 0, 1}
ip := netutil.IPv4Localhost()
ipStr := ip.String()
testFS := fstest.MapFS{"dir/file1": &fstest.MapFile{Data: []byte(ipStr + ` hostname` + nl)}}
@@ -167,17 +169,13 @@ func TestHostsContainer_refresh(t *testing.T) {
require.True(t, ok)
require.NotNil(t, upd)
assert.Equal(t, 1, upd.Len())
assert.Len(t, upd, 1)
v, ok := upd.Get(ip)
rec, ok := upd[ip]
require.True(t, ok)
require.IsType(t, (*HostsRecord)(nil), v)
rec, _ := v.(*HostsRecord)
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) {
@@ -562,13 +560,13 @@ func TestHostsContainer(t *testing.T) {
}
func TestUniqueRules_ParseLine(t *testing.T) {
ip := net.IP{127, 0, 0, 1}
ip := netutil.IPv4Localhost()
ipStr := ip.String()
testCases := []struct {
name string
line string
wantIP net.IP
wantIP netip.Addr
wantHosts []string
}{{
name: "simple",
@@ -583,7 +581,7 @@ func TestUniqueRules_ParseLine(t *testing.T) {
}, {
name: "invalid_line",
line: ipStr,
wantIP: nil,
wantIP: netip.Addr{},
wantHosts: nil,
}, {
name: "invalid_line_hostname",
@@ -598,7 +596,7 @@ func TestUniqueRules_ParseLine(t *testing.T) {
}, {
name: "whole_comment",
line: `# ` + ipStr + ` hostname`,
wantIP: nil,
wantIP: netip.Addr{},
wantHosts: nil,
}, {
name: "partial_comment",
@@ -608,7 +606,7 @@ func TestUniqueRules_ParseLine(t *testing.T) {
}, {
name: "empty",
line: ``,
wantIP: nil,
wantIP: netip.Addr{},
wantHosts: nil,
}}
@@ -616,7 +614,7 @@ func TestUniqueRules_ParseLine(t *testing.T) {
hp := hostsParser{}
t.Run(tc.name, func(t *testing.T) {
got, hosts := hp.parseLine(tc.line)
assert.True(t, tc.wantIP.Equal(got))
assert.Equal(t, tc.wantIP, got)
assert.Equal(t, tc.wantHosts, hosts)
})
}

View File

@@ -31,12 +31,6 @@ var (
// the IP being static is available.
const ErrNoStaticIPInfo errors.Error = "no information about static ip"
// IPv4Localhost returns 127.0.0.1, which returns true for [netip.Addr.Is4].
func IPv4Localhost() (ip netip.Addr) { return netip.AddrFrom4([4]byte{127, 0, 0, 1}) }
// IPv6Localhost returns ::1, which returns true for [netip.Addr.Is6].
func IPv6Localhost() (ip netip.Addr) { return netip.AddrFrom16([16]byte{15: 1}) }
// IfaceHasStaticIP checks if interface is configured to have static IP address.
// If it can't give a definitive answer, it returns false and an error for which
// errors.Is(err, ErrNoStaticIPInfo) is true.

View File

@@ -11,7 +11,6 @@ import (
"strings"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
@@ -20,7 +19,7 @@ import (
)
func TestMain(m *testing.M) {
aghtest.DiscardLogOutput(m)
testutil.DiscardLogOutput(m)
}
// testdata is the filesystem containing data for testing the package.
@@ -189,17 +188,14 @@ func TestBroadcastFromIPNet(t *testing.T) {
}
func TestCheckPort(t *testing.T) {
laddr := netip.AddrPortFrom(IPv4Localhost(), 0)
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)
addr := l.Addr()
require.IsType(t, new(net.TCPAddr), addr)
ipp := addr.(*net.TCPAddr).AddrPort()
ipp := testutil.RequireTypeAssert[*net.TCPAddr](t, l.Addr()).AddrPort()
require.Equal(t, laddr.Addr(), ipp.Addr())
require.NotZero(t, ipp.Port())
@@ -215,10 +211,7 @@ func TestCheckPort(t *testing.T) {
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, conn.Close)
addr := conn.LocalAddr()
require.IsType(t, new(net.UDPAddr), addr)
ipp := addr.(*net.UDPAddr).AddrPort()
ipp := testutil.RequireTypeAssert[*net.UDPAddr](t, conn.LocalAddr()).AddrPort()
require.Equal(t, laddr.Addr(), ipp.Addr())
require.NotZero(t, ipp.Port())

View File

@@ -6,6 +6,7 @@ import (
"context"
"testing"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -17,9 +18,8 @@ func createTestSystemResolversImpl(
t.Helper()
sr := createTestSystemResolvers(t, hostGenFunc)
require.IsType(t, (*systemResolvers)(nil), sr)
return sr.(*systemResolvers)
return testutil.RequireTypeAssert[*systemResolvers](t, sr)
}
func TestSystemResolvers_Refresh(t *testing.T) {

View File

@@ -3,9 +3,9 @@ package aghos_test
import (
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/testutil"
)
func TestMain(m *testing.M) {
aghtest.DiscardLogOutput(m)
testutil.DiscardLogOutput(m)
}

View File

@@ -55,9 +55,7 @@ func isReconfigureSignal(sig os.Signal) (ok bool) {
func isShutdownSignal(sig os.Signal) (ok bool) {
switch sig {
case
os.Interrupt,
syscall.SIGTERM:
case os.Interrupt, syscall.SIGTERM:
return true
default:
return false

View File

@@ -3,21 +3,11 @@ package aghtest
import (
"io"
"os"
"testing"
"github.com/AdguardTeam/golibs/log"
)
// DiscardLogOutput runs tests with discarded logger output.
func DiscardLogOutput(m *testing.M) {
// TODO(e.burkov): Refactor code and tests to not use the global mutable
// logger.
log.SetOutput(io.Discard)
os.Exit(m.Run())
}
// ReplaceLogWriter moves logger output to w and uses Cleanup method of t to
// revert changes.
func ReplaceLogWriter(t testing.TB, w io.Writer) {

View File

@@ -5,13 +5,12 @@ import (
"encoding/hex"
"fmt"
"net"
"net/netip"
"strings"
"testing"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/miekg/dns"
"github.com/stretchr/testify/require"
)
// Additional Upstream Testing Utilities
@@ -26,51 +25,10 @@ type Upstream struct {
IPv4 map[string][]net.IP
// IPv6 is a map of hostname to IPv6.
IPv6 map[string][]net.IP
// Reverse is a map of address to domain name.
Reverse map[string][]string
// Addr is the address for Address method.
Addr string
}
var _ upstream.Upstream = (*Upstream)(nil)
// RespondTo returns a response with answer if req has class cl, question type
// qt, and target targ.
func RespondTo(t testing.TB, req *dns.Msg, cl, qt uint16, targ, answer string) (resp *dns.Msg) {
t.Helper()
require.NotNil(t, req)
require.Len(t, req.Question, 1)
q := req.Question[0]
targ = dns.Fqdn(targ)
if q.Qclass != cl || q.Qtype != qt || q.Name != targ {
return nil
}
respHdr := dns.RR_Header{
Name: targ,
Rrtype: qt,
Class: cl,
Ttl: 60,
}
resp = new(dns.Msg).SetReply(req)
switch qt {
case dns.TypePTR:
resp.Answer = []dns.RR{
&dns.PTR{
Hdr: respHdr,
Ptr: answer,
},
}
default:
t.Fatalf("unsupported question type: %s", dns.Type(qt))
}
return resp
}
// Exchange implements the [upstream.Upstream] interface for *Upstream.
//
// TODO(a.garipov): Split further into handlers.
@@ -105,10 +63,6 @@ func (u *Upstream) Exchange(m *dns.Msg) (resp *dns.Msg, err error) {
for _, ip := range u.IPv6[name] {
resp.Answer = append(resp.Answer, &dns.AAAA{Hdr: hdr, AAAA: ip})
}
case dns.TypePTR:
for _, name := range u.Reverse[name] {
resp.Answer = append(resp.Answer, &dns.PTR{Hdr: hdr, Ptr: name})
}
}
if len(resp.Answer) == 0 {
resp.SetRcode(m, dns.RcodeNameError)
@@ -119,7 +73,7 @@ func (u *Upstream) Exchange(m *dns.Msg) (resp *dns.Msg, err error) {
// Address implements [upstream.Upstream] interface for *Upstream.
func (u *Upstream) Address() string {
return u.Addr
return "todo.upstream.example"
}
// Close implements [upstream.Upstream] interface for *Upstream.
@@ -127,6 +81,98 @@ func (u *Upstream) Close() (err error) {
return nil
}
// MatchedResponse is a test helper that returns a response with answer if req
// has question type qt, and target targ. Otherwise, it returns nil.
//
// req must not be nil and req.Question must have a length of 1. Answer is
// interpreted in the following ways:
//
// - For A and AAAA queries, answer must be an IP address of the corresponding
// protocol version.
//
// - For PTR queries, answer should be a domain name in the response.
//
// If the answer does not correspond to the question type, MatchedResponse panics.
// Panics are used instead of [testing.TB], because the helper is intended to
// use in [UpstreamMock.OnExchange] callbacks, which are usually called in a
// separate goroutine.
//
// TODO(a.garipov): Consider adding version with DNS class as well.
func MatchedResponse(req *dns.Msg, qt uint16, targ, answer string) (resp *dns.Msg) {
if req == nil || len(req.Question) != 1 {
panic(fmt.Errorf("bad req: %+v", req))
}
q := req.Question[0]
targ = dns.Fqdn(targ)
if q.Qclass != dns.ClassINET || q.Qtype != qt || q.Name != targ {
return nil
}
respHdr := dns.RR_Header{
Name: targ,
Rrtype: qt,
Class: dns.ClassINET,
Ttl: 60,
}
resp = new(dns.Msg).SetReply(req)
switch qt {
case dns.TypeA:
resp.Answer = mustAnsA(respHdr, answer)
case dns.TypeAAAA:
resp.Answer = mustAnsAAAA(respHdr, answer)
case dns.TypePTR:
resp.Answer = []dns.RR{&dns.PTR{
Hdr: respHdr,
Ptr: answer,
}}
default:
panic(fmt.Errorf("aghtest: bad question type: %s", dns.Type(qt)))
}
return resp
}
// mustAnsA returns valid answer records if s is a valid IPv4 address.
// Otherwise, mustAnsA panics.
func mustAnsA(respHdr dns.RR_Header, s string) (ans []dns.RR) {
ip, err := netip.ParseAddr(s)
if err != nil || !ip.Is4() {
panic(fmt.Errorf("aghtest: bad A answer: %+v", s))
}
return []dns.RR{&dns.A{
Hdr: respHdr,
A: ip.AsSlice(),
}}
}
// mustAnsAAAA returns valid answer records if s is a valid IPv6 address.
// Otherwise, mustAnsAAAA panics.
func mustAnsAAAA(respHdr dns.RR_Header, s string) (ans []dns.RR) {
ip, err := netip.ParseAddr(s)
if err != nil || !ip.Is6() {
panic(fmt.Errorf("aghtest: bad AAAA answer: %+v", s))
}
return []dns.RR{&dns.AAAA{
Hdr: respHdr,
AAAA: ip.AsSlice(),
}}
}
// NewUpstreamMock returns an [*UpstreamMock], fields OnAddress and OnClose of
// which are set to stubs that return "upstream.example" and nil respectively.
// The field OnExchange is set to onExc.
func NewUpstreamMock(onExc func(req *dns.Msg) (resp *dns.Msg, err error)) (u *UpstreamMock) {
return &UpstreamMock{
OnAddress: func() (addr string) { return "upstream.example" },
OnExchange: onExc,
OnClose: func() (err error) { return nil },
}
}
// NewBlockUpstream returns an [*UpstreamMock] that works like an upstream that
// supports hash-based safe-browsing/adult-blocking feature. If shouldBlock is
// true, hostname's actual hash is returned, blocking it. Otherwise, it returns
@@ -152,9 +198,7 @@ func NewBlockUpstream(hostname string, shouldBlock bool) (u *UpstreamMock) {
}
return &UpstreamMock{
OnAddress: func() (addr string) {
return "sbpc.upstream.example"
},
OnAddress: func() (addr string) { return "sbpc.upstream.example" },
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
resp = respTmpl.Copy()
resp.SetReply(req)
@@ -162,6 +206,7 @@ func NewBlockUpstream(hostname string, shouldBlock bool) (u *UpstreamMock) {
return resp, nil
},
OnClose: func() (err error) { return nil },
}
}
@@ -173,11 +218,10 @@ const ErrUpstream errors.Error = "test upstream error"
// its Exchange method.
func NewErrorUpstream() (u *UpstreamMock) {
return &UpstreamMock{
OnAddress: func() (addr string) {
return "error.upstream.example"
},
OnAddress: func() (addr string) { return "error.upstream.example" },
OnExchange: func(_ *dns.Msg) (resp *dns.Msg, err error) {
return nil, errors.Error("test upstream error")
},
OnClose: func() (err error) { return nil },
}
}

View File

@@ -3,7 +3,9 @@ package aghtls
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net/netip"
"github.com/AdguardTeam/golibs/log"
)
@@ -69,3 +71,19 @@ func SaferCipherSuites() (safe []uint16) {
return safe
}
// CertificateHasIP returns true if cert has at least a single IP address among
// its subjectAltNames.
func CertificateHasIP(cert *x509.Certificate) (ok bool) {
if len(cert.IPAddresses) > 0 {
return true
}
for _, name := range cert.DNSNames {
if _, err := netip.ParseAddr(name); err == nil {
return true
}
}
return false
}

View File

@@ -4,14 +4,13 @@ import (
"crypto/tls"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
aghtest.DiscardLogOutput(m)
testutil.DiscardLogOutput(m)
}
func TestParseCiphers(t *testing.T) {

View File

@@ -9,8 +9,8 @@ import (
"time"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil"
"golang.org/x/exp/slices"
)
const (
@@ -54,8 +54,8 @@ func (l *Lease) Clone() (clone *Lease) {
return &Lease{
Expiry: l.Expiry,
Hostname: l.Hostname,
HWAddr: netutil.CloneMAC(l.HWAddr),
IP: netutil.CloneIP(l.IP),
HWAddr: slices.Clone(l.HWAddr),
IP: slices.Clone(l.IP),
}
}

View File

@@ -9,15 +9,14 @@ import (
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/exp/slices"
)
func TestMain(m *testing.M) {
aghtest.DiscardLogOutput(m)
testutil.DiscardLogOutput(m)
}
func testNotify(flags uint32) {
@@ -157,7 +156,7 @@ func TestV4Server_badRange(t *testing.T) {
// cloneUDPAddr returns a deep copy of a.
func cloneUDPAddr(a *net.UDPAddr) (clone *net.UDPAddr) {
return &net.UDPAddr{
IP: netutil.CloneIP(a.IP),
IP: slices.Clone(a.IP),
Port: a.Port,
Zone: a.Zone,
}

View File

@@ -1018,7 +1018,7 @@ func (s *v4Server) handle(req, resp *dhcpv4.DHCPv4) int {
}
if l != nil {
resp.YourIPAddr = netutil.CloneIP(l.IP)
resp.YourIPAddr = slices.Clone(l.IP)
}
s.updateOptions(req, resp)

View File

@@ -482,7 +482,6 @@ func TestV4Server_updateOptions(t *testing.T) {
s, err := v4Create(conf)
require.NoError(t, err)
require.IsType(t, (*v4Server)(nil), s)
t.Run(tc.name, func(t *testing.T) {

View File

@@ -3,25 +3,26 @@ package dnsforward
import (
"encoding/json"
"fmt"
"net"
"net/http"
"net/netip"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/AdguardTeam/urlfilter"
"github.com/AdguardTeam/urlfilter/filterlist"
)
// accessCtx controls IP and client blocking that takes place before all other
// processing. An accessCtx is safe for concurrent use.
type accessCtx struct {
// TODO(e.burkov): Use map[netip.Addr]struct{} instead.
allowedIPs *netutil.IPMap
blockedIPs *netutil.IPMap
// unit is a convenient alias for struct{}
type unit = struct{}
// accessManager controls IP and client blocking that takes place before all
// other processing. An accessManager is safe for concurrent use.
type accessManager struct {
allowedIPs map[netip.Addr]unit
blockedIPs map[netip.Addr]unit
allowedClientIDs *stringutil.Set
blockedClientIDs *stringutil.Set
@@ -29,36 +30,29 @@ type accessCtx struct {
blockedHostsEng *urlfilter.DNSEngine
// TODO(a.garipov): Create a type for a set of IP networks.
// netutil.IPNetSet?
allowedNets []*net.IPNet
blockedNets []*net.IPNet
allowedNets []netip.Prefix
blockedNets []netip.Prefix
}
// unit is a convenient alias for struct{}
type unit = struct{}
// processAccessClients is a helper for processing a list of client strings,
// which may be an IP address, a CIDR, or a ClientID.
func processAccessClients(
clientStrs []string,
ips *netutil.IPMap,
nets *[]*net.IPNet,
ips map[netip.Addr]unit,
nets *[]netip.Prefix,
clientIDs *stringutil.Set,
) (err error) {
for i, s := range clientStrs {
if ip := net.ParseIP(s); ip != nil {
ips.Set(ip, unit{})
} else if cidrIP, ipnet, cidrErr := net.ParseCIDR(s); cidrErr == nil {
ipnet.IP = cidrIP
var ip netip.Addr
var ipnet netip.Prefix
if ip, err = netip.ParseAddr(s); err == nil {
ips[ip] = unit{}
} else if ipnet, err = netip.ParsePrefix(s); err == nil {
*nets = append(*nets, ipnet)
} else {
idErr := ValidateClientID(s)
if idErr != nil {
return fmt.Errorf(
"value %q at index %d: bad ip, cidr, or clientid",
s,
i,
)
err = ValidateClientID(s)
if err != nil {
return fmt.Errorf("value %q at index %d: bad ip, cidr, or clientid", s, i)
}
clientIDs.Add(s)
@@ -69,10 +63,10 @@ func processAccessClients(
}
// newAccessCtx creates a new accessCtx.
func newAccessCtx(allowed, blocked, blockedHosts []string) (a *accessCtx, err error) {
a = &accessCtx{
allowedIPs: netutil.NewIPMap(0),
blockedIPs: netutil.NewIPMap(0),
func newAccessCtx(allowed, blocked, blockedHosts []string) (a *accessManager, err error) {
a = &accessManager{
allowedIPs: map[netip.Addr]unit{},
blockedIPs: map[netip.Addr]unit{},
allowedClientIDs: stringutil.NewSet(),
blockedClientIDs: stringutil.NewSet(),
@@ -112,12 +106,12 @@ func newAccessCtx(allowed, blocked, blockedHosts []string) (a *accessCtx, err er
}
// allowlistMode returns true if this *accessCtx is in the allowlist mode.
func (a *accessCtx) allowlistMode() (ok bool) {
return a.allowedIPs.Len() != 0 || a.allowedClientIDs.Len() != 0 || len(a.allowedNets) != 0
func (a *accessManager) allowlistMode() (ok bool) {
return len(a.allowedIPs) != 0 || a.allowedClientIDs.Len() != 0 || len(a.allowedNets) != 0
}
// isBlockedClientID returns true if the ClientID should be blocked.
func (a *accessCtx) isBlockedClientID(id string) (ok bool) {
func (a *accessManager) isBlockedClientID(id string) (ok bool) {
allowlistMode := a.allowlistMode()
if id == "" {
// In allowlist mode, consider requests without ClientIDs blocked by
@@ -133,7 +127,7 @@ func (a *accessCtx) isBlockedClientID(id string) (ok bool) {
}
// isBlockedHost returns true if host should be blocked.
func (a *accessCtx) isBlockedHost(host string) (ok bool) {
func (a *accessManager) isBlockedHost(host string) (ok bool) {
_, ok = a.blockedHostsEng.Match(strings.ToLower(host))
return ok
@@ -141,7 +135,7 @@ func (a *accessCtx) isBlockedHost(host string) (ok bool) {
// isBlockedIP returns the status of the IP address blocking as well as the rule
// that blocked it.
func (a *accessCtx) isBlockedIP(ip net.IP) (blocked bool, rule string) {
func (a *accessManager) isBlockedIP(ip netip.Addr) (blocked bool, rule string) {
blocked = true
ips := a.blockedIPs
ipnets := a.blockedNets
@@ -153,7 +147,7 @@ func (a *accessCtx) isBlockedIP(ip net.IP) (blocked bool, rule string) {
ipnets = a.allowedNets
}
if _, ok := ips.Get(ip); ok {
if _, ok := ips[ip]; ok {
return blocked, ip.String()
}
@@ -241,7 +235,7 @@ func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {
return
}
var a *accessCtx
var a *accessManager
a, err = newAccessCtx(list.AllowedClients, list.DisallowedClients, list.BlockedHosts)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "creating access ctx: %s", err)

View File

@@ -1,7 +1,7 @@
package dnsforward
import (
"net"
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
@@ -95,27 +95,27 @@ func TestIsBlockedIP(t *testing.T) {
testCases := []struct {
name string
wantRule string
ip net.IP
ip netip.Addr
wantBlocked bool
}{{
name: "match_ip",
wantRule: "1.2.3.4",
ip: net.IP{1, 2, 3, 4},
ip: netip.MustParseAddr("1.2.3.4"),
wantBlocked: true,
}, {
name: "match_cidr",
wantRule: "5.6.7.8/24",
ip: net.IP{5, 6, 7, 100},
ip: netip.MustParseAddr("5.6.7.100"),
wantBlocked: true,
}, {
name: "no_match_ip",
wantRule: "",
ip: net.IP{9, 2, 3, 4},
ip: netip.MustParseAddr("9.2.3.4"),
wantBlocked: false,
}, {
name: "no_match_cidr",
wantRule: "",
ip: net.IP{9, 6, 7, 100},
ip: netip.MustParseAddr("9.6.7.100"),
wantBlocked: false,
}}

View File

@@ -23,16 +23,6 @@ func ValidateClientID(id string) (err error) {
return nil
}
// hasLabelSuffix returns true if s ends with suffix preceded by a dot. It's
// a helper function to prevent unnecessary allocations in code like:
//
// if strings.HasSuffix(s, "." + suffix) { /* … */ }
//
// s must be longer than suffix.
func hasLabelSuffix(s, suffix string) (ok bool) {
return strings.HasSuffix(s, suffix) && s[len(s)-len(suffix)-1] == '.'
}
// clientIDFromClientServerName extracts and validates a ClientID. hostSrvName
// is the server name of the host. cliSrvName is the server name as sent by the
// client. When strict is true, and client and host server name don't match,
@@ -46,7 +36,7 @@ func clientIDFromClientServerName(
return "", nil
}
if !hasLabelSuffix(cliSrvName, hostSrvName) {
if !netutil.IsImmediateSubdomain(cliSrvName, hostSrvName) {
if !strict {
return "", nil
}
@@ -161,18 +151,30 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
func clientServerName(pctx *proxy.DNSContext, proto proxy.Proto) (srvName string, err error) {
switch proto {
case proxy.ProtoHTTPS:
if connState := pctx.HTTPRequest.TLS; connState != nil {
srvName = pctx.HTTPRequest.TLS.ServerName
// github.com/lucas-clemente/quic-go seems to not populate the TLS
// field. So, if the request comes over HTTP/3, use the Host header
// value as the server name.
//
// See https://github.com/lucas-clemente/quic-go/issues/2879.
//
// TODO(a.garipov): Remove this crutch once they fix it.
r := pctx.HTTPRequest
if r.ProtoAtLeast(3, 0) {
var host string
host, err = netutil.SplitHost(r.Host)
if err != nil {
return "", fmt.Errorf("parsing host: %w", err)
}
srvName = host
} else if connState := r.TLS; connState != nil {
srvName = r.TLS.ServerName
}
case proxy.ProtoQUIC:
qConn := pctx.QUICConnection
conn, ok := qConn.(quicConnection)
if !ok {
return "", fmt.Errorf(
"proxy ctx quic conn of proto %s is %T, want quic.Connection",
proto,
qConn,
)
return "", fmt.Errorf("pctx conn of proto %s is %T, want quic.Connection", proto, qConn)
}
srvName = conn.ConnectionState().TLS.ServerName
@@ -180,7 +182,7 @@ func clientServerName(pctx *proxy.DNSContext, proto proxy.Proto) (srvName string
conn := pctx.Conn
tc, ok := conn.(tlsConn)
if !ok {
return "", fmt.Errorf("proxy ctx conn of proto %s is %T, want *tls.Conn", proto, conn)
return "", fmt.Errorf("pctx conn of proto %s is %T, want *tls.Conn", proto, conn)
}
srvName = tc.ConnectionState().ServerName

View File

@@ -47,8 +47,6 @@ func (c testQUICConnection) ConnectionState() (cs quic.ConnectionState) {
}
func TestServer_clientIDFromDNSContext(t *testing.T) {
// TODO(a.garipov): Consider moving away from the text-based error
// checks and onto a more structured approach.
testCases := []struct {
name string
proto proxy.Proto
@@ -57,6 +55,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID string
wantErrMsg string
strictSNI bool
useHTTP3 bool
}{{
name: "udp",
proto: proxy.ProtoUDP,
@@ -65,6 +64,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "",
wantErrMsg: "",
strictSNI: false,
useHTTP3: false,
}, {
name: "tls_no_clientid",
proto: proxy.ProtoTLS,
@@ -73,6 +73,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "",
wantErrMsg: "",
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_no_client_server_name",
proto: proxy.ProtoTLS,
@@ -82,6 +83,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantErrMsg: `clientid check: client server name "" ` +
`doesn't match host server name "example.com"`,
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_no_client_server_name_no_strict",
proto: proxy.ProtoTLS,
@@ -90,6 +92,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "",
wantErrMsg: "",
strictSNI: false,
useHTTP3: false,
}, {
name: "tls_clientid",
proto: proxy.ProtoTLS,
@@ -98,6 +101,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "cli",
wantErrMsg: "",
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_clientid_hostname_error",
proto: proxy.ProtoTLS,
@@ -107,6 +111,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantErrMsg: `clientid check: client server name "cli.example.net" ` +
`doesn't match host server name "example.com"`,
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_invalid_clientid",
proto: proxy.ProtoTLS,
@@ -116,6 +121,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantErrMsg: `clientid check: invalid clientid "!!!": ` +
`bad domain name label rune '!'`,
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_clientid_too_long",
proto: proxy.ProtoTLS,
@@ -127,6 +133,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
`pqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789": ` +
`domain name label is too long: got 72, max 63`,
strictSNI: true,
useHTTP3: false,
}, {
name: "quic_clientid",
proto: proxy.ProtoQUIC,
@@ -135,6 +142,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "cli",
wantErrMsg: "",
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_clientid_issue3437",
proto: proxy.ProtoTLS,
@@ -144,6 +152,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantErrMsg: `clientid check: client server name "cli.myexample.com" ` +
`doesn't match host server name "example.com"`,
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_case",
proto: proxy.ProtoTLS,
@@ -152,6 +161,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "insensitive",
wantErrMsg: ``,
strictSNI: true,
useHTTP3: false,
}, {
name: "quic_case",
proto: proxy.ProtoQUIC,
@@ -160,6 +170,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "insensitive",
wantErrMsg: ``,
strictSNI: true,
useHTTP3: false,
}, {
name: "https_no_clientid",
proto: proxy.ProtoHTTPS,
@@ -168,6 +179,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "",
wantErrMsg: "",
strictSNI: true,
useHTTP3: false,
}, {
name: "https_clientid",
proto: proxy.ProtoHTTPS,
@@ -176,6 +188,16 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "cli",
wantErrMsg: "",
strictSNI: true,
useHTTP3: false,
}, {
name: "https_clientid_quic",
proto: proxy.ProtoHTTPS,
hostSrvName: "example.com",
cliSrvName: "cli.example.com",
wantClientID: "cli",
wantErrMsg: "",
strictSNI: true,
useHTTP3: true,
}}
for _, tc := range testCases {
@@ -197,18 +219,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
switch tc.proto {
case proxy.ProtoHTTPS:
u := &url.URL{
Path: "/dns-query",
}
connState := &tls.ConnectionState{
ServerName: tc.cliSrvName,
}
httpReq = &http.Request{
URL: u,
TLS: connState,
}
httpReq = newHTTPReq(tc.cliSrvName, tc.useHTTP3)
case proxy.ProtoQUIC:
qconn = testQUICConnection{
serverName: tc.cliSrvName,
@@ -234,6 +245,33 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
}
}
// newHTTPReq is a helper to create HTTP requests for tests.
func newHTTPReq(cliSrvName string, useHTTP3 bool) (r *http.Request) {
u := &url.URL{
Path: "/dns-query",
}
if useHTTP3 {
return &http.Request{
ProtoMajor: 3,
ProtoMinor: 0,
URL: u,
Host: cliSrvName,
TLS: &tls.ConnectionState{},
}
}
return &http.Request{
ProtoMajor: 1,
ProtoMinor: 1,
URL: u,
Host: cliSrvName,
TLS: &tls.ConnectionState{
ServerName: cliSrvName,
},
}
}
func TestClientIDFromDNSContextHTTPS(t *testing.T) {
testCases := []struct {
name string

View File

@@ -12,6 +12,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
@@ -96,9 +97,16 @@ type FilteringConfig struct {
// Access settings
// --
AllowedClients []string `yaml:"allowed_clients"` // IP addresses of whitelist clients
DisallowedClients []string `yaml:"disallowed_clients"` // IP addresses of clients that should be blocked
BlockedHosts []string `yaml:"blocked_hosts"` // hosts that should be blocked
// AllowedClients is the slice of IP addresses, CIDR networks, and ClientIDs
// of allowed clients. If not empty, only these clients are allowed, and
// [FilteringConfig.DisallowedClients] are ignored.
AllowedClients []string `yaml:"allowed_clients"`
// DisallowedClients is the slice of IP addresses, CIDR networks, and
// ClientIDs of disallowed clients.
DisallowedClients []string `yaml:"disallowed_clients"`
BlockedHosts []string `yaml:"blocked_hosts"` // hosts that should be blocked
// TrustedProxies is the list of IP addresses and CIDR networks to detect
// proxy servers addresses the DoH requests from which should be handled.
// The value of nil or an empty slice for this field makes Proxy not trust
@@ -137,15 +145,15 @@ type FilteringConfig struct {
IpsetListFileName string `yaml:"ipset_file"`
}
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, DNS-over-TLS,
// and DNS-over-QUIC.
type TLSConfig struct {
cert tls.Certificate
TLSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"`
QUICListenAddrs []*net.UDPAddr `yaml:"-" json:"-"`
HTTPSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"`
// Reject connection if the client uses server name (in SNI) that doesn't match the certificate
StrictSNICheck bool `yaml:"strict_sni_check" json:"-"`
// PEM-encoded certificates chain
CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"`
// PEM-encoded private key
@@ -161,13 +169,20 @@ type TLSConfig struct {
// used for ClientID checking and Discovery of Designated Resolvers (DDR).
ServerName string `yaml:"-" json:"-"`
cert tls.Certificate
// DNS names from certificate (SAN) or CN value from Subject
dnsNames []string
// OverrideTLSCiphers, when set, contains the names of the cipher suites to
// use. If the slice is empty, the default safe suites are used.
OverrideTLSCiphers []string `yaml:"override_tls_ciphers,omitempty" json:"-"`
// StrictSNICheck controls if the connections with SNI mismatching the
// certificate's ones should be rejected.
StrictSNICheck bool `yaml:"strict_sni_check" json:"-"`
// hasIPAddrs is set during the certificate parsing and is true if the
// configured certificate contains at least a single IP address.
hasIPAddrs bool
}
// DNSCryptConfig is the DNSCrypt server configuration struct.
@@ -452,7 +467,7 @@ func (s *Server) prepareIpsetListSettings() (err error) {
}
// prepareTLS - prepares TLS configuration for the DNS proxy
func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
func (s *Server) prepareTLS(proxyConfig *proxy.Config) (err error) {
if len(s.conf.CertificateChainData) == 0 || len(s.conf.PrivateKeyData) == 0 {
return nil
}
@@ -471,25 +486,26 @@ func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
proxyConfig.QUICListenAddr,
)
var err error
s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
if err != nil {
return fmt.Errorf("failed to parse TLS keypair: %w", err)
}
cert, err := x509.ParseCertificate(s.conf.cert.Certificate[0])
if err != nil {
return fmt.Errorf("x509.ParseCertificate(): %w", err)
}
s.conf.hasIPAddrs = aghtls.CertificateHasIP(cert)
if s.conf.StrictSNICheck {
var x *x509.Certificate
x, err = x509.ParseCertificate(s.conf.cert.Certificate[0])
if err != nil {
return fmt.Errorf("x509.ParseCertificate(): %w", err)
}
if len(x.DNSNames) != 0 {
s.conf.dnsNames = x.DNSNames
log.Debug("dns: using DNS names from certificate's SAN: %v", x.DNSNames)
if len(cert.DNSNames) != 0 {
s.conf.dnsNames = cert.DNSNames
log.Debug("dnsforward: using certificate's SAN as DNS names: %v", cert.DNSNames)
sort.Strings(s.conf.dnsNames)
} else {
s.conf.dnsNames = append(s.conf.dnsNames, x.Subject.CommonName)
log.Debug("dns: using DNS name from certificate's CN: %s", x.Subject.CommonName)
s.conf.dnsNames = append(s.conf.dnsNames, cert.Subject.CommonName)
log.Debug("dnsforward: using certificate's CN as DNS name: %s", cert.Subject.CommonName)
}
}

View File

@@ -3,6 +3,7 @@ package dnsforward
import (
"encoding/binary"
"net"
"net/netip"
"strings"
"time"
@@ -194,7 +195,7 @@ func (s *Server) setTableHostToIP(t hostToIPTable) {
s.tableHostToIP = t
}
func (s *Server) setTableIPToHost(t *netutil.IPMap) {
func (s *Server) setTableIPToHost(t ipToHostTable) {
s.tableIPToHostLock.Lock()
defer s.tableIPToHostLock.Unlock()
@@ -202,52 +203,54 @@ func (s *Server) setTableIPToHost(t *netutil.IPMap) {
}
func (s *Server) onDHCPLeaseChanged(flags int) {
var err error
add := true
switch flags {
case dhcpd.LeaseChangedAdded,
dhcpd.LeaseChangedAddedStatic,
dhcpd.LeaseChangedRemovedStatic:
// Go on.
case dhcpd.LeaseChangedRemovedAll:
add = false
s.setTableHostToIP(nil)
s.setTableIPToHost(nil)
return
default:
return
}
var hostToIP hostToIPTable
var ipToHost *netutil.IPMap
if add {
ll := s.dhcpServer.Leases(dhcpd.LeasesAll)
ll := s.dhcpServer.Leases(dhcpd.LeasesAll)
hostToIP := make(hostToIPTable, len(ll))
ipToHost := make(ipToHostTable, len(ll))
hostToIP = make(hostToIPTable, len(ll))
ipToHost = netutil.NewIPMap(len(ll))
for _, l := range ll {
// TODO(a.garipov): Remove this after we're finished with the client
// hostname validations in the DHCP server code.
err := netutil.ValidateDomainName(l.Hostname)
if err != nil {
log.Debug("dnsforward: skipping invalid hostname %q from dhcp: %s", l.Hostname, err)
for _, l := range ll {
// TODO(a.garipov): Remove this after we're finished with the client
// hostname validations in the DHCP server code.
err = netutil.ValidateDomainName(l.Hostname)
if err != nil {
log.Debug(
"dns: skipping invalid hostname %q from dhcp: %s",
l.Hostname,
err,
)
}
lowhost := strings.ToLower(l.Hostname + "." + s.localDomainSuffix)
ip := netutil.CloneIP(l.IP)
ipToHost.Set(ip, lowhost)
hostToIP[lowhost] = ip
continue
}
log.Debug("dns: added %d A/PTR entries from DHCP", ipToHost.Len())
lowhost := strings.ToLower(l.Hostname + "." + s.localDomainSuffix)
// Assume that we only process IPv4 now.
//
// TODO(a.garipov): Remove once we switch to netip.Addr more fully.
ip, err := netutil.IPToAddr(l.IP, netutil.AddrFamilyIPv4)
if err != nil {
log.Debug("dnsforward: skipping invalid ip %v from dhcp: %s", l.IP, err)
continue
}
ipToHost[ip] = lowhost
hostToIP[lowhost] = ip
}
s.setTableHostToIP(hostToIP)
s.setTableIPToHost(ipToHost)
log.Debug("dnsforward: added %d a and ptr entries from dhcp", len(ipToHost))
}
// processDDRQuery responds to Discovery of Designated Resolvers (DDR) SVCB
@@ -256,21 +259,13 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
//
// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-10.html.
func (s *Server) processDDRQuery(dctx *dnsContext) (rc resultCode) {
pctx := dctx.proxyCtx
q := pctx.Req.Question[0]
if !s.conf.HandleDDR {
return resultCodeSuccess
}
pctx := dctx.proxyCtx
q := pctx.Req.Question[0]
if q.Name == ddrHostFQDN {
if s.dnsProxy.TLSListenAddr == nil && s.conf.HTTPSListenAddrs == nil &&
s.dnsProxy.QUICListenAddr == nil || q.Qtype != dns.TypeSVCB {
pctx.Res = s.makeResponse(pctx.Req)
return resultCodeFinish
}
pctx.Res = s.makeDDRResponse(pctx.Req)
return resultCodeFinish
@@ -288,6 +283,10 @@ func (s *Server) processDDRQuery(dctx *dnsContext) (rc resultCode) {
// [draft standard]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-10.html.
func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
resp = s.makeResponse(req)
if req.Question[0].Qtype != dns.TypeSVCB {
return resp
}
// TODO(e.burkov): Think about storing the FQDN version of the server's
// name somewhere.
domainName := dns.Fqdn(s.conf.ServerName)
@@ -309,20 +308,26 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
resp.Answer = append(resp.Answer, ans)
}
for _, addr := range s.dnsProxy.TLSListenAddr {
values := []dns.SVCBKeyValue{
&dns.SVCBAlpn{Alpn: []string{"dot"}},
&dns.SVCBPort{Port: uint16(addr.Port)},
}
if s.conf.hasIPAddrs {
// Only add DNS-over-TLS resolvers in case the certificate contains IP
// addresses.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/4927.
for _, addr := range s.dnsProxy.TLSListenAddr {
values := []dns.SVCBKeyValue{
&dns.SVCBAlpn{Alpn: []string{"dot"}},
&dns.SVCBPort{Port: uint16(addr.Port)},
}
ans := &dns.SVCB{
Hdr: s.hdr(req, dns.TypeSVCB),
Priority: 1,
Target: domainName,
Value: values,
}
ans := &dns.SVCB{
Hdr: s.hdr(req, dns.TypeSVCB),
Priority: 1,
Target: domainName,
Value: values,
}
resp.Answer = append(resp.Answer, ans)
resp.Answer = append(resp.Answer, ans)
}
}
for _, addr := range s.dnsProxy.QUICListenAddr {
@@ -362,24 +367,13 @@ func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
// dhcpHostToIP tries to get an IP leased by DHCP and returns the copy of
// address since the data inside the internal table may be changed while request
// processing. It's safe for concurrent use.
func (s *Server) dhcpHostToIP(host string) (ip net.IP, ok bool) {
func (s *Server) dhcpHostToIP(host string) (ip netip.Addr, ok bool) {
s.tableHostToIPLock.Lock()
defer s.tableHostToIPLock.Unlock()
if s.tableHostToIP == nil {
return nil, false
}
ip, ok = s.tableHostToIP[host]
var ipFromTable net.IP
ipFromTable, ok = s.tableHostToIP[host]
if !ok {
return nil, false
}
ip = make(net.IP, len(ipFromTable))
copy(ip, ipFromTable)
return ip, true
return ip, ok
}
// processDHCPHosts respond to A requests if the target hostname is known to
@@ -396,7 +390,7 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
}
if !dctx.isLocalClient {
log.Debug("dns: %q requests for dhcp host %q", pctx.Addr, reqHost)
log.Debug("dnsforward: %q requests for dhcp host %q", pctx.Addr, reqHost)
pctx.Res = s.genNXDomain(req)
// Do not even put into query log.
@@ -407,18 +401,18 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
if !ok {
// Go on and process them with filters, including dnsrewrite ones, and
// possibly route them to a domain-specific upstream.
log.Debug("dns: no dhcp record for %q", reqHost)
log.Debug("dnsforward: no dhcp record for %q", reqHost)
return resultCodeSuccess
}
log.Debug("dns: dhcp record for %q is %s", reqHost, ip)
log.Debug("dnsforward: dhcp record for %q is %s", reqHost, ip)
resp := s.makeResponse(req)
if q.Qtype == dns.TypeA {
a := &dns.A{
Hdr: s.hdr(req, dns.TypeA),
A: ip,
A: ip.AsSlice(),
}
resp.Answer = append(resp.Answer, a)
}
@@ -440,7 +434,7 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
ip, err := netutil.IPFromReversedAddr(q.Name)
if err != nil {
log.Debug("dns: parsing reversed addr: %s", err)
log.Debug("dnsforward: parsing reversed addr: %s", err)
// DNS-Based Service Discovery uses PTR records having not an ARPA
// format of the domain name in question. Those shouldn't be
@@ -448,12 +442,12 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
// RFC 2782.
name := strings.TrimSuffix(q.Name, ".")
if err = netutil.ValidateSRVDomainName(name); err != nil {
log.Debug("dns: validating service domain: %s", err)
log.Debug("dnsforward: validating service domain: %s", err)
return resultCodeError
}
log.Debug("dns: request is for a service domain")
log.Debug("dnsforward: request is for a service domain")
return resultCodeSuccess
}
@@ -462,13 +456,13 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
// assume that all the DHCP leases we give are locally-served or at least
// don't need to be accessible externally.
if !s.privateNets.Contains(ip) {
log.Debug("dns: addr %s is not from locally-served network", ip)
log.Debug("dnsforward: addr %s is not from locally-served network", ip)
return resultCodeSuccess
}
if !dctx.isLocalClient {
log.Debug("dns: %q requests an internal ip", pctx.Addr)
log.Debug("dnsforward: %q requests an internal ip", pctx.Addr)
pctx.Res = s.genNXDomain(req)
// Do not even put into query log.
@@ -492,27 +486,13 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
// ipToDHCPHost tries to get a hostname leased by DHCP. It's safe for
// concurrent use.
func (s *Server) ipToDHCPHost(ip net.IP) (host string, ok bool) {
func (s *Server) ipToDHCPHost(ip netip.Addr) (host string, ok bool) {
s.tableIPToHostLock.Lock()
defer s.tableIPToHostLock.Unlock()
if s.tableIPToHost == nil {
return "", false
}
host, ok = s.tableIPToHost[ip]
var v any
v, ok = s.tableIPToHost.Get(ip)
if !ok {
return "", false
}
if host, ok = v.(string); !ok {
log.Error("dns: bad type %T in tableIPToHost for %s", v, ip)
return "", false
}
return host, true
return host, ok
}
// processDHCPAddrs responds to PTR requests if the target IP is leased by the
@@ -528,12 +508,20 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
host, ok := s.ipToDHCPHost(ip)
// TODO(a.garipov): Remove once we switch to netip.Addr more fully.
ipAddr, err := netutil.IPToAddrNoMapped(ip)
if err != nil {
log.Debug("dnsforward: bad reverse ip %v from dhcp: %s", ip, err)
return resultCodeSuccess
}
host, ok := s.ipToDHCPHost(ipAddr)
if !ok {
return resultCodeSuccess
}
log.Debug("dns: dhcp reverse record for %s is %q", ip, host)
log.Debug("dnsforward: dhcp reverse record for %s is %q", ip, host)
req := pctx.Req
resp := s.makeResponse(req)
@@ -638,7 +626,7 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
//
// TODO(a.garipov): Route such queries to a custom upstream for the
// local domain name if there is one.
log.Debug("dns: dhcp client hostname %q was not filtered", reqHost)
log.Debug("dnsforward: dhcp client hostname %q was not filtered", reqHost)
pctx.Res = s.genNXDomain(req)
return resultCodeFinish
@@ -711,13 +699,13 @@ func (s *Server) setCustomUpstream(pctx *proxy.DNSContext, clientID string) {
id := stringutil.Coalesce(clientID, ipStringFromAddr(pctx.Addr))
upsConf, err := customUpsByClient(id)
if err != nil {
log.Error("dns: getting custom upstreams for client %s: %s", id, err)
log.Error("dnsforward: getting custom upstreams for client %s: %s", id, err)
return
}
if upsConf != nil {
log.Debug("dns: using custom upstreams for client %s", id)
log.Debug("dnsforward: using custom upstreams for client %s", id)
}
pctx.CustomUpstreamConfig = upsConf

View File

@@ -2,13 +2,16 @@ package dnsforward
import (
"net"
"net/netip"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -154,19 +157,9 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled bool) (s *Server) {
t.Helper()
proxyConf := proxy.Config{}
if portDoT > 0 {
proxyConf.TLSListenAddr = []*net.TCPAddr{{Port: portDoT}}
}
if portDoQ > 0 {
proxyConf.QUICListenAddr = []*net.UDPAddr{{Port: portDoQ}}
}
s = &Server{
dnsProxy: &proxy.Proxy{
Config: proxyConf,
Config: proxy.Config{},
},
conf: ServerConfig{
FilteringConfig: FilteringConfig{
@@ -178,8 +171,17 @@ func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled b
},
}
if portDoT > 0 {
s.dnsProxy.TLSListenAddr = []*net.TCPAddr{{Port: portDoT}}
s.conf.hasIPAddrs = true
}
if portDoQ > 0 {
s.dnsProxy.QUICListenAddr = []*net.UDPAddr{{Port: portDoQ}}
}
if portDoH > 0 {
s.conf.TLSConfig.HTTPSListenAddrs = []*net.TCPAddr{{Port: portDoH}}
s.conf.HTTPSListenAddrs = []*net.TCPAddr{{Port: portDoH}}
}
return s
@@ -230,12 +232,11 @@ func TestServer_ProcessDetermineLocal(t *testing.T) {
}
func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
knownIP := net.IP{1, 2, 3, 4}
knownIP := netip.MustParseAddr("1.2.3.4")
testCases := []struct {
name string
host string
wantIP net.IP
wantIP netip.Addr
wantRes resultCode
isLocalCli bool
}{{
@@ -247,19 +248,19 @@ func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
}, {
name: "local_client_unknown_host",
host: "wronghost.lan",
wantIP: nil,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess,
isLocalCli: true,
}, {
name: "external_client_known_host",
host: "example.lan",
wantIP: nil,
wantIP: netip.Addr{},
wantRes: resultCodeFinish,
isLocalCli: false,
}, {
name: "external_client_unknown_host",
host: "wronghost.lan",
wantIP: nil,
wantIP: netip.Addr{},
wantRes: resultCodeFinish,
isLocalCli: false,
}}
@@ -304,7 +305,7 @@ func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
return
}
if tc.wantIP == nil {
if tc.wantIP == (netip.Addr{}) {
assert.Nil(t, pctx.Res)
} else {
require.NotNil(t, pctx.Res)
@@ -312,7 +313,12 @@ func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
ans := pctx.Res.Answer
require.Len(t, ans, 1)
assert.Equal(t, tc.wantIP, ans[0].(*dns.A).A)
a := testutil.RequireTypeAssert[*dns.A](t, ans[0])
ip, err := netutil.IPToAddr(a.A, netutil.AddrFamilyIPv4)
require.NoError(t, err)
assert.Equal(t, tc.wantIP, ip)
}
})
}
@@ -324,26 +330,26 @@ func TestServer_ProcessDHCPHosts(t *testing.T) {
examplelan = "example." + defaultLocalDomainSuffix
)
knownIP := net.IP{1, 2, 3, 4}
knownIP := netip.MustParseAddr("1.2.3.4")
testCases := []struct {
name string
host string
suffix string
wantIP net.IP
wantIP netip.Addr
wantRes resultCode
qtyp uint16
}{{
name: "success_external",
host: examplecom,
suffix: defaultLocalDomainSuffix,
wantIP: nil,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess,
qtyp: dns.TypeA,
}, {
name: "success_external_non_a",
host: examplecom,
suffix: defaultLocalDomainSuffix,
wantIP: nil,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess,
qtyp: dns.TypeCNAME,
}, {
@@ -357,14 +363,14 @@ func TestServer_ProcessDHCPHosts(t *testing.T) {
name: "success_internal_unknown",
host: "example-new.lan",
suffix: defaultLocalDomainSuffix,
wantIP: nil,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess,
qtyp: dns.TypeA,
}, {
name: "success_internal_aaaa",
host: examplelan,
suffix: defaultLocalDomainSuffix,
wantIP: nil,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess,
qtyp: dns.TypeAAAA,
}, {
@@ -423,7 +429,7 @@ func TestServer_ProcessDHCPHosts(t *testing.T) {
ans := pctx.Res.Answer
require.Len(t, ans, 0)
} else if tc.wantIP == nil {
} else if tc.wantIP == (netip.Addr{}) {
assert.Nil(t, pctx.Res)
} else {
require.NotNil(t, pctx.Res)
@@ -431,19 +437,33 @@ func TestServer_ProcessDHCPHosts(t *testing.T) {
ans := pctx.Res.Answer
require.Len(t, ans, 1)
assert.Equal(t, tc.wantIP, ans[0].(*dns.A).A)
a := testutil.RequireTypeAssert[*dns.A](t, ans[0])
ip, err := netutil.IPToAddr(a.A, netutil.AddrFamilyIPv4)
require.NoError(t, err)
assert.Equal(t, tc.wantIP, ip)
}
})
}
}
func TestServer_ProcessRestrictLocal(t *testing.T) {
ups := &aghtest.Upstream{
Reverse: map[string][]string{
"251.252.253.254.in-addr.arpa.": {"host1.example.net."},
"1.1.168.192.in-addr.arpa.": {"some.local-client."},
},
}
const (
extPTRQuestion = "251.252.253.254.in-addr.arpa."
extPTRAnswer = "host1.example.net."
intPTRQuestion = "1.1.168.192.in-addr.arpa."
intPTRAnswer = "some.local-client."
)
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
return aghalg.Coalesce(
aghtest.MatchedResponse(req, dns.TypePTR, extPTRQuestion, extPTRAnswer),
aghtest.MatchedResponse(req, dns.TypePTR, intPTRQuestion, intPTRAnswer),
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
), nil
})
s := createTestServer(t, &filtering.Config{}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
@@ -513,14 +533,20 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
const locDomain = "some.local."
const reqAddr = "1.1.168.192.in-addr.arpa."
s := createTestServer(t, &filtering.Config{}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
}, &aghtest.Upstream{
Reverse: map[string][]string{
reqAddr: {locDomain},
s := createTestServer(
t,
&filtering.Config{},
ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
},
})
aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
return aghalg.Coalesce(
aghtest.MatchedResponse(req, dns.TypePTR, reqAddr, locDomain),
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
), nil
}),
)
var proxyCtx *proxy.DNSContext
var dnsCtx *dnsContext

View File

@@ -5,11 +5,13 @@ import (
"fmt"
"net"
"net/http"
"net/netip"
"runtime"
"strings"
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
@@ -43,8 +45,13 @@ var defaultBlockedHosts = []string{"version.bind", "id.server", "hostname.bind"}
var webRegistered bool
// hostToIPTable is an alias for the type of Server.tableHostToIP.
type hostToIPTable = map[string]net.IP
// hostToIPTable is a convenient type alias for tables of host names to an IP
// address.
type hostToIPTable = map[string]netip.Addr
// ipToHostTable is a convenient type alias for tables of IP addresses to their
// host names. For example, for use with PTR queries.
type ipToHostTable = map[netip.Addr]string
// Server is the main way to start a DNS server.
//
@@ -63,7 +70,7 @@ type Server struct {
dhcpServer dhcpd.Interface // DHCP server instance (optional)
queryLog querylog.QueryLog // Query log instance
stats stats.Interface
access *accessCtx
access *accessManager
// localDomainSuffix is the suffix used to detect internal hosts. It
// must be a valid domain name plus dots on each side.
@@ -81,8 +88,7 @@ type Server struct {
tableHostToIP hostToIPTable
tableHostToIPLock sync.Mutex
// TODO(e.burkov): Use map[netip.Addr]struct{} instead.
tableIPToHost *netutil.IPMap
tableIPToHost ipToHostTable
tableIPToHostLock sync.Mutex
// clientIDCache is a temporary storage for ClientIDs that were extracted
@@ -240,6 +246,7 @@ type RDNSExchanger interface {
// Exchange tries to resolve the ip in a suitable way, e.g. either as
// local or as external.
Exchange(ip net.IP) (host string, err error)
// ResolvesPrivatePTR returns true if the RDNSExchanger is able to
// resolve PTR requests for locally-served addresses.
ResolvesPrivatePTR() (ok bool)
@@ -255,6 +262,9 @@ const (
rDNSNotPTRErr errors.Error = "the response is not a ptr"
)
// type check
var _ RDNSExchanger = (*Server)(nil)
// Exchange implements the RDNSExchanger interface for *Server.
func (s *Server) Exchange(ip net.IP) (host string, err error) {
s.serverLock.RLock()
@@ -560,31 +570,43 @@ func (s *Server) Stop() error {
// stopLocked stops the DNS server without locking. For internal use only.
func (s *Server) stopLocked() (err error) {
var errs []error
if s.dnsProxy != nil {
err = s.dnsProxy.Stop()
if err != nil {
errs = append(errs, fmt.Errorf("could not stop primary resolvers properly: %w", err))
return fmt.Errorf("closing primary resolvers: %w", err)
}
}
if s.internalProxy != nil && s.internalProxy.UpstreamConfig != nil {
err = s.internalProxy.UpstreamConfig.Close()
var errs []error
if upsConf := s.internalProxy.UpstreamConfig; upsConf != nil {
const action = "closing internal resolvers"
err = upsConf.Close()
if err != nil {
errs = append(errs, fmt.Errorf("could not stop internal resolvers properly: %w", err))
if errors.Is(err, net.ErrClosed) {
log.Debug("dnsforward: %s: %s", action, err)
} else {
errs = append(errs, fmt.Errorf("%s: %w", action, err))
}
}
}
if s.localResolvers != nil && s.localResolvers.UpstreamConfig != nil {
err = s.localResolvers.UpstreamConfig.Close()
if upsConf := s.localResolvers.UpstreamConfig; upsConf != nil {
const action = "closing local resolvers"
err = upsConf.Close()
if err != nil {
errs = append(errs, fmt.Errorf("could not stop local resolvers properly: %w", err))
if errors.Is(err, net.ErrClosed) {
log.Debug("dnsforward: %s: %s", action, err)
} else {
errs = append(errs, fmt.Errorf("%s: %w", action, err))
}
}
}
if len(errs) > 0 {
return errors.List("stopping DNS server", errs...)
return errors.List("stopping dns server", errs...)
} else {
s.isRunning = false
}
@@ -657,31 +679,31 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// IsBlockedClient returns true if the client is blocked by the current access
// settings.
func (s *Server) IsBlockedClient(ip net.IP, clientID string) (blocked bool, rule string) {
func (s *Server) IsBlockedClient(ip netip.Addr, clientID string) (blocked bool, rule string) {
s.serverLock.RLock()
defer s.serverLock.RUnlock()
blockedByIP := false
if ip != (netip.Addr{}) {
blockedByIP, rule = s.access.isBlockedIP(ip)
}
allowlistMode := s.access.allowlistMode()
blockedByIP, rule := s.access.isBlockedIP(ip)
blockedByClientID := s.access.isBlockedClientID(clientID)
// Allow if at least one of the checks allows in allowlist mode, but
// block if at least one of the checks blocks in blocklist mode.
// Allow if at least one of the checks allows in allowlist mode, but block
// if at least one of the checks blocks in blocklist mode.
if allowlistMode && blockedByIP && blockedByClientID {
log.Debug("client %s (id %q) is not in access allowlist", ip, clientID)
log.Debug("client %v (id %q) is not in access allowlist", ip, clientID)
// Return now without substituting the empty rule for the
// clientID because the rule can't be empty here.
return true, rule
} else if !allowlistMode && (blockedByIP || blockedByClientID) {
log.Debug("client %s (id %q) is in access blocklist", ip, clientID)
log.Debug("client %v (id %q) is in access blocklist", ip, clientID)
blocked = true
}
if rule == "" {
rule = clientID
}
return blocked, rule
return blocked, aghalg.Coalesce(rule, clientID)
}

View File

@@ -33,7 +33,7 @@ import (
)
func TestMain(m *testing.M) {
aghtest.DiscardLogOutput(m)
testutil.DiscardLogOutput(m)
}
const (
@@ -161,8 +161,23 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte)
return s, certPem
}
const googleDomainName = "google-public-dns-a.google.com."
func createGoogleATestMessage() *dns.Msg {
return createTestMessage("google-public-dns-a.google.com.")
return createTestMessage(googleDomainName)
}
func newGoogleUpstream() (u upstream.Upstream) {
return &aghtest.UpstreamMock{
OnAddress: func() (addr string) { return "google.upstream.example" },
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
return aghalg.Coalesce(
aghtest.MatchedResponse(req, dns.TypeA, googleDomainName, "8.8.8.8"),
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
), nil
},
OnClose: func() (err error) { return nil },
}
}
func createTestMessage(host string) *dns.Msg {
@@ -247,13 +262,7 @@ func TestServer(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
}, nil)
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
&aghtest.Upstream{
IPv4: map[string][]net.IP{
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
},
},
}
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
startDeferStop(t, s)
testCases := []struct {
@@ -320,13 +329,7 @@ func TestServerWithProtectionDisabled(t *testing.T) {
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
}, nil)
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
&aghtest.Upstream{
IPv4: map[string][]net.IP{
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
},
},
}
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
startDeferStop(t, s)
// Message over UDP.
@@ -343,13 +346,7 @@ func TestDoTServer(t *testing.T) {
s, certPem := createTestTLS(t, TLSConfig{
TLSListenAddrs: []*net.TCPAddr{{}},
})
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
&aghtest.Upstream{
IPv4: map[string][]net.IP{
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
},
},
}
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
startDeferStop(t, s)
// Add our self-signed generated config to roots.
@@ -373,13 +370,7 @@ func TestDoQServer(t *testing.T) {
s, _ := createTestTLS(t, TLSConfig{
QUICListenAddrs: []*net.UDPAddr{{IP: net.IP{127, 0, 0, 1}}},
})
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
&aghtest.Upstream{
IPv4: map[string][]net.IP{
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
},
},
}
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
startDeferStop(t, s)
// Create a DNS-over-QUIC upstream.
@@ -417,13 +408,7 @@ func TestServerRace(t *testing.T) {
ConfigModified: func() {},
}
s := createTestServer(t, filterConf, forwardConf, nil)
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
&aghtest.Upstream{
IPv4: map[string][]net.IP{
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
},
},
}
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
startDeferStop(t, s)
// Message over UDP.
@@ -557,11 +542,12 @@ func TestServerCustomClientUpstream(t *testing.T) {
}
s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
s.conf.GetCustomUpstreamByClient = func(_ string) (conf *proxy.UpstreamConfig, err error) {
ups := &aghtest.Upstream{
IPv4: map[string][]net.IP{
"host.": {{192, 168, 0, 1}},
},
}
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
return aghalg.Coalesce(
aghtest.MatchedResponse(req, dns.TypeA, "host", "192.168.0.1"),
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
), nil
})
return &proxy.UpstreamConfig{
Upstreams: []upstream.Upstream{ups},
@@ -604,7 +590,6 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) {
testUpstm := &aghtest.Upstream{
CName: testCNAMEs,
IPv4: testIPv4,
IPv6: nil,
}
s.conf.ProtectionEnabled = false
s.dnsProxy.UpstreamConfig = &proxy.UpstreamConfig{
@@ -931,16 +916,13 @@ func TestRewrite(t *testing.T) {
},
}))
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
&aghtest.Upstream{
CName: map[string][]string{
"example.org": {"somename"},
},
IPv4: map[string][]net.IP{
"example.org.": {{4, 3, 2, 1}},
},
},
}
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
return aghalg.Coalesce(
aghtest.MatchedResponse(req, dns.TypeA, "example.org", "4.3.2.1"),
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
), nil
})
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ups}
startDeferStop(t, s)
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
@@ -1061,11 +1043,12 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
require.Len(t, resp.Answer, 1)
assert.Equal(t, dns.TypePTR, resp.Answer[0].Header().Rrtype)
assert.Equal(t, "34.12.168.192.in-addr.arpa.", resp.Answer[0].Header().Name)
ans := resp.Answer[0]
assert.Equal(t, dns.TypePTR, ans.Header().Rrtype)
assert.Equal(t, "34.12.168.192.in-addr.arpa.", ans.Header().Name)
ptr := testutil.RequireTypeAssert[*dns.PTR](t, ans)
ptr, ok := resp.Answer[0].(*dns.PTR)
require.True(t, ok)
assert.Equal(t, dns.Fqdn("myhost."+localDomain), ptr.Ptr)
}
@@ -1211,12 +1194,10 @@ func TestServer_Exchange(t *testing.T) {
extUpstream := &aghtest.UpstreamMock{
OnAddress: func() (addr string) { return "external.upstream.example" },
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
resp = aghalg.Coalesce(
aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revExtIPv4, onesHost),
return aghalg.Coalesce(
aghtest.MatchedResponse(req, dns.TypePTR, revExtIPv4, onesHost),
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
)
return resp, nil
), nil
},
}
@@ -1226,12 +1207,10 @@ func TestServer_Exchange(t *testing.T) {
locUpstream := &aghtest.UpstreamMock{
OnAddress: func() (addr string) { return "local.upstream.example" },
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
resp = aghalg.Coalesce(
aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revLocIPv4, localDomainHost),
return aghalg.Coalesce(
aghtest.MatchedResponse(req, dns.TypePTR, revLocIPv4, localDomainHost),
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
)
return resp, nil
), nil
},
}

View File

@@ -20,19 +20,13 @@ func (s *Server) filterDNSRewriteResponse(
v rules.RRValue,
) (ans dns.RR, err error) {
switch rr {
case
dns.TypeA,
dns.TypeAAAA:
case dns.TypeA, dns.TypeAAAA:
return s.ansFromDNSRewriteIP(v, rr, req)
case
dns.TypePTR,
dns.TypeTXT:
case dns.TypePTR, dns.TypeTXT:
return s.ansFromDNSRewriteText(v, rr, req)
case dns.TypeMX:
return s.ansFromDNSRewriteMX(v, rr, req)
case
dns.TypeHTTPS,
dns.TypeSVCB:
case dns.TypeHTTPS, dns.TypeSVCB:
return s.ansFromDNSRewriteSVCB(v, rr, req)
case dns.TypeSRV:
return s.ansFromDNSRewriteSRV(v, rr, req)

View File

@@ -19,13 +19,13 @@ func (s *Server) beforeRequestHandler(
_ *proxy.Proxy,
pctx *proxy.DNSContext,
) (reply bool, err error) {
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
clientID, err := s.clientIDFromDNSContext(pctx)
if err != nil {
return false, fmt.Errorf("getting clientid: %w", err)
}
blocked, _ := s.IsBlockedClient(ip, clientID)
addrPort := netutil.NetAddrToAddrPort(pctx.Addr)
blocked, _ := s.IsBlockedClient(addrPort.Addr(), clientID)
if blocked {
return s.preBlockedResponse(pctx)
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"net"
"net/http"
"net/netip"
"strings"
"time"
@@ -435,22 +436,22 @@ func validateUpstream(u string, domains []string) (useDefault bool, err error) {
// TODO(e.burkov): Validate the domain name.
for _, proto := range protocols {
if strings.HasPrefix(u, proto) {
return useDefault, nil
return false, nil
}
}
if strings.Contains(u, "://") {
return useDefault, errors.Error("wrong protocol")
if proto, _, ok := strings.Cut(u, "://"); ok {
return false, fmt.Errorf("bad protocol %q", proto)
}
// Check if upstream is either an IP or IP with port.
if net.ParseIP(u) != nil {
return useDefault, nil
} else if _, err = netutil.ParseIPPort(u); err != nil {
return useDefault, err
if _, err = netip.ParseAddr(u); err == nil {
return false, nil
} else if _, err = netip.ParseAddrPort(u); err == nil {
return false, nil
}
return useDefault, nil
return false, err
}
// separateUpstream returns the upstream and the specified domains. domains is

View File

@@ -188,10 +188,8 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
name: "upstream_mode_fastest_addr",
wantSet: "",
}, {
name: "upstream_dns_bad",
wantSet: `validating upstream servers: ` +
`validating upstream "!!!": bad ipport address "!!!": ` +
`address !!!: missing port in address`,
name: "upstream_dns_bad",
wantSet: `validating upstream servers: validating upstream "!!!": not an ip:port`,
}, {
name: "bootstraps_bad",
wantSet: `checking bootstrap a: invalid address: ` +
@@ -297,15 +295,15 @@ func TestValidateUpstreams(t *testing.T) {
},
}, {
name: "invalid",
wantErr: `validating upstream "dhcp://fake.dns": wrong protocol`,
wantErr: `validating upstream "dhcp://fake.dns": bad protocol "dhcp"`,
set: []string{"dhcp://fake.dns"},
}, {
name: "invalid",
wantErr: `validating upstream "1.2.3.4.5": bad ipport address "1.2.3.4.5": address 1.2.3.4.5: missing port in address`,
wantErr: `validating upstream "1.2.3.4.5": not an ip:port`,
set: []string{"1.2.3.4.5"},
}, {
name: "invalid",
wantErr: `validating upstream "123.3.7m": bad ipport address "123.3.7m": address 123.3.7m: missing port in address`,
wantErr: `validating upstream "123.3.7m": not an ip:port`,
set: []string{"123.3.7m"},
}, {
name: "invalid",
@@ -313,7 +311,7 @@ func TestValidateUpstreams(t *testing.T) {
set: []string{"[/host.com]tls://dns.adguard.com"},
}, {
name: "invalid",
wantErr: `validating upstream "[host.ru]#": bad ipport address "[host.ru]#": address [host.ru]#: missing port in address`,
wantErr: `validating upstream "[host.ru]#": not an ip:port`,
set: []string{"[host.ru]#"},
}, {
name: "valid_default",

View File

@@ -12,6 +12,7 @@ import (
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// Write Stats data and logs
@@ -28,7 +29,7 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
}
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
ip = netutil.CloneIP(ip)
ip = slices.Clone(ip)
s.serverLock.RLock()
defer s.serverLock.RUnlock()

View File

@@ -10,382 +10,6 @@ import (
"golang.org/x/exp/slices"
)
// svc represents a single blocked service.
type svc struct {
name string
rules []string
}
// servicesData contains raw blocked service data.
//
// Keep in sync with:
// - client/src/helpers/constants.js
// - client/src/components/ui/Icons.js
var servicesData = []svc{{
name: "whatsapp",
rules: []string{
"||wa.me^",
"||whatsapp.com^",
"||whatsapp.net^",
},
}, {
name: "facebook",
rules: []string{
"||facebook.com^",
"||facebook.net^",
"||fbcdn.net^",
"||accountkit.com^",
"||fb.me^",
"||fb.com^",
"||fb.gg^",
"||fbsbx.com^",
"||fbwat.ch^",
"||messenger.com^",
"||facebookcorewwwi.onion^",
"||fbcdn.com^",
"||fb.watch^",
},
}, {
name: "twitter",
rules: []string{
"||t.co^",
"||twimg.com^",
"||twitter.com^",
"||twttr.com^",
},
}, {
name: "youtube",
rules: []string{
"||googlevideo.com^",
"||wide-youtube.l.google.com^",
"||youtu.be^",
"||youtube",
"||youtube-nocookie.com^",
"||youtube.com^",
"||youtubei.googleapis.com^",
"||youtubekids.com^",
"||ytimg.com^",
},
}, {
name: "twitch",
rules: []string{
"||jtvnw.net^",
"||ttvnw.net^",
"||twitch.tv^",
"||twitchcdn.net^",
},
}, {
name: "netflix",
rules: []string{
"||nflxext.com^",
"||netflix.com^",
"||nflximg.net^",
"||nflxvideo.net^",
"||nflxso.net^",
},
}, {
name: "instagram",
rules: []string{"||instagram.com^", "||cdninstagram.com^"},
}, {
name: "snapchat",
rules: []string{
"||snapchat.com^",
"||sc-cdn.net^",
"||snap-dev.net^",
"||snapkit.co",
"||snapads.com^",
"||impala-media-production.s3.amazonaws.com^",
},
}, {
name: "discord",
rules: []string{
"||discord.gg^",
"||discordapp.net^",
"||discordapp.com^",
"||discord.com^",
"||discord.gift",
"||discord.media^",
},
}, {
name: "ok",
rules: []string{"||ok.ru^"},
}, {
name: "skype",
rules: []string{
"||edge-skype-com.s-0001.s-msedge.net^",
"||skype-edf.akadns.net^",
"||skype.com^",
"||skypeassets.com^",
"||skypedata.akadns.net^",
},
}, {
name: "vk",
rules: []string{
"||userapi.com^",
"||vk-cdn.net^",
"||vk.com^",
"||vkuservideo.net^",
},
}, {
name: "origin",
rules: []string{
"||accounts.ea.com^",
"||origin.com^",
"||signin.ea.com^",
},
}, {
name: "steam",
rules: []string{
"||steam.com^",
"||steampowered.com^",
"||steamcommunity.com^",
"||steamstatic.com^",
"||steamstore-a.akamaihd.net^",
"||steamcdn-a.akamaihd.net^",
},
}, {
name: "epic_games",
rules: []string{"||epicgames.com^", "||easyanticheat.net^", "||easy.ac^", "||eac-cdn.com^"},
}, {
name: "reddit",
rules: []string{"||reddit.com^", "||redditstatic.com^", "||redditmedia.com^", "||redd.it^"},
}, {
name: "mail_ru",
rules: []string{"||mail.ru^"},
}, {
name: "cloudflare",
rules: []string{
"||1.1.1.1^",
"||argotunnel.com^",
"||cloudflare-dns.com^",
"||cloudflare-ipfs.com^",
"||cloudflare-quic.com^",
"||cloudflare.cn^",
"||cloudflare.com^",
"||cloudflare.net^",
"||cloudflareaccess.com^",
"||cloudflareapps.com^",
"||cloudflarebolt.com^",
"||cloudflareclient.com^",
"||cloudflareinsights.com^",
"||cloudflareresolve.com^",
"||cloudflarestatus.com^",
"||cloudflarestream.com^",
"||cloudflarewarp.com^",
"||dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion^",
"||one.one^",
"||pages.dev^",
"||trycloudflare.com^",
"||videodelivery.net^",
"||warp.plus^",
"||workers.dev^",
},
}, {
name: "amazon",
rules: []string{
"||amazon.com^",
"||media-amazon.com^",
"||primevideo.com^",
"||amazontrust.com^",
"||images-amazon.com^",
"||ssl-images-amazon.com^",
"||amazonpay.com^",
"||amazonpay.in^",
"||amazon-adsystem.com^",
"||a2z.com^",
"||amazon.ae^",
"||amazon.ca^",
"||amazon.cn^",
"||amazon.de^",
"||amazon.es^",
"||amazon.fr^",
"||amazon.in^",
"||amazon.it^",
"||amazon.nl^",
"||amazon.com.au^",
"||amazon.com.br^",
"||amazon.co.jp^",
"||amazon.com.mx^",
"||amazon.com.tr^",
"||amazon.co.uk^",
"||createspace.com^",
"||aws",
},
}, {
name: "ebay",
rules: []string{
"||ebay.com^",
"||ebayimg.com^",
"||ebaystatic.com^",
"||ebaycdn.net^",
"||ebayinc.com^",
"||ebay.at^",
"||ebay.be^",
"||ebay.ca^",
"||ebay.ch^",
"||ebay.cn^",
"||ebay.de^",
"||ebay.es^",
"||ebay.fr^",
"||ebay.ie^",
"||ebay.in^",
"||ebay.it^",
"||ebay.ph^",
"||ebay.pl^",
"||ebay.nl^",
"||ebay.com.au^",
"||ebay.com.cn^",
"||ebay.com.hk^",
"||ebay.com.my^",
"||ebay.com.sg^",
"||ebay.co.uk^",
},
}, {
name: "tiktok",
rules: []string{
"||amemv.com^",
"||bdurl.com^",
"||bytecdn.cn^",
"||bytedance.map.fastly.net^",
"||bytedapm.com^",
"||byteimg.com^",
"||byteoversea.com^",
"||douyin.com^",
"||douyincdn.com^",
"||douyinpic.com^",
"||douyinstatic.com^",
"||douyinvod.com^",
"||ixigua.com^",
"||ixiguavideo.com^",
"||muscdn.com^",
"||musical.ly^",
"||pstatp.com^",
"||snssdk.com^",
"||tiktok.com^",
"||tiktokcdn.com^",
"||tiktokv.com^",
"||toutiao.com^",
"||toutiaocloud.com^",
"||toutiaocloud.net^",
"||toutiaovod.com^",
},
}, {
name: "vimeo",
rules: []string{
"*vod-adaptive.akamaized.net^",
"||vimeo.com^",
"||vimeocdn.com^",
},
}, {
name: "pinterest",
rules: []string{
"||pinimg.com^",
"||pinterest.*^",
},
}, {
name: "imgur",
rules: []string{"||imgur.com^"},
}, {
name: "dailymotion",
rules: []string{
"||dailymotion.com^",
"||dm-event.net^",
"||dmcdn.net^",
},
}, {
name: "qq",
rules: []string{
// Block qq.com and subdomains excluding WeChat's domains.
"||qq.com^$denyallow=wx.qq.com|weixin.qq.com",
"||qqzaixian.com^",
"||qq-video.cdn-go.cn^",
"||url.cn^",
},
}, {
name: "wechat",
rules: []string{
"||wechat.com^",
"||weixin.qq.com.cn^",
"||weixin.qq.com^",
"||weixinbridge.com^",
"||wx.qq.com^",
},
}, {
name: "viber",
rules: []string{"||viber.com^"},
}, {
name: "weibo",
rules: []string{
"||weibo.cn^",
"||weibo.com^",
"||weibocdn.com^",
},
}, {
name: "9gag",
rules: []string{
"||9cache.com^",
"||9gag.com^",
},
}, {
name: "telegram",
rules: []string{
"||t.me^",
"||telegram.me^",
"||telegram.org^",
},
}, {
name: "disneyplus",
rules: []string{
"||disney-plus.net^",
"||disney.playback.edge.bamgrid.com^",
"||disneynow.com^",
"||disneyplus.com^",
"||hotstar.com^",
"||media.dssott.com^",
"||star.playback.edge.bamgrid.com^",
"||starplus.com^",
},
}, {
name: "hulu",
rules: []string{"||hulu.com^"},
}, {
name: "spotify",
rules: []string{
"/_spotify-connect._tcp.local/",
"||spotify.com^",
"||scdn.co^",
"||spotify.com.edgesuite.net^",
"||spotify.map.fastly.net^",
"||spotify.map.fastlylb.net^",
"||spotifycdn.net^",
"||audio-ak-spotify-com.akamaized.net^",
"||audio4-ak-spotify-com.akamaized.net^",
"||heads-ak-spotify-com.akamaized.net^",
"||heads4-ak-spotify-com.akamaized.net^",
},
}, {
name: "tinder",
rules: []string{
"||gotinder.com^",
"||tinder.com^",
"||tindersparks.com^",
},
}, {
name: "bilibili",
rules: []string{
"||b23.tv^",
"||biliapi.net^",
"||bilibili.com^",
"||bilicdn1.com^",
"||bilicdn2.com^",
"||biligame.com^",
"||bilivideo.cn^",
"||bilivideo.com^",
"||dreamcast.hk^",
"||hdslb.com^",
},
}}
// serviceRules maps a service ID to its filtering rules.
var serviceRules map[string][]*rules.NetworkRule
@@ -394,16 +18,16 @@ var serviceIDs []string
// initBlockedServices initializes package-level blocked service data.
func initBlockedServices() {
l := len(servicesData)
l := len(blockedServices)
serviceIDs = make([]string, l)
serviceRules = make(map[string][]*rules.NetworkRule, l)
for i, s := range servicesData {
netRules := make([]*rules.NetworkRule, 0, len(s.rules))
for _, text := range s.rules {
for i, s := range blockedServices {
netRules := make([]*rules.NetworkRule, 0, len(s.Rules))
for _, text := range s.Rules {
rule, err := rules.NewNetworkRule(text, BlockedSvcsListID)
if err != nil {
log.Error("parsing blocked service %q rule %q: %s", s.name, text, err)
log.Error("parsing blocked service %q rule %q: %s", s.ID, text, err)
continue
}
@@ -411,8 +35,8 @@ func initBlockedServices() {
netRules = append(netRules, rule)
}
serviceIDs[i] = s.name
serviceRules[s.name] = netRules
serviceIDs[i] = s.ID
serviceRules[s.ID] = netRules
}
slices.Sort(serviceIDs)
@@ -420,7 +44,7 @@ func initBlockedServices() {
log.Debug("filtering: initialized %d services", l)
}
// BlockedSvcKnown - return TRUE if a blocked service name is known
// BlockedSvcKnown returns true if a blocked service ID is known.
func BlockedSvcKnown(s string) (ok bool) {
_, ok = serviceRules[s]
@@ -452,10 +76,18 @@ func (d *DNSFilter) ApplyBlockedServices(setts *Settings, list []string) {
}
}
func (d *DNSFilter) handleBlockedServicesAvailableServices(w http.ResponseWriter, r *http.Request) {
func (d *DNSFilter) handleBlockedServicesIDs(w http.ResponseWriter, r *http.Request) {
_ = aghhttp.WriteJSONResponse(w, r, serviceIDs)
}
func (d *DNSFilter) handleBlockedServicesAll(w http.ResponseWriter, r *http.Request) {
_ = aghhttp.WriteJSONResponse(w, r, struct {
BlockedServices []blockedService `json:"blocked_services"`
}{
BlockedServices: blockedServices,
})
}
func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
d.confLock.RLock()
list := d.Config.BlockedServices
@@ -479,5 +111,5 @@ func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ
log.Debug("Updated blocked services list: %d", len(list))
d.ConfigModified()
d.Config.ConfigModified()
}

View File

@@ -54,97 +54,120 @@ func (filter *FilterYAML) Path(dataDir string) string {
}
const (
statusFound = 1 << iota
statusEnabledChanged
statusURLChanged
statusURLExists
statusUpdateRequired
// errFilterNotExist is returned from [filterSetProperties] when there are
// no lists with the desired URL to update.
//
// TODO(e.burkov): Use wherever the same error is needed.
errFilterNotExist errors.Error = "url doesn't exist"
// errFilterExists is returned from [filterSetProperties] when there is
// another filter having the same URL as the one updated.
//
// TODO(e.burkov): Use wherever the same error is needed.
errFilterExists errors.Error = "url already exists"
)
// Update properties for a filter specified by its URL
// Return status* flags.
func (d *DNSFilter) filterSetProperties(url string, newf FilterYAML, whitelist bool) int {
r := 0
// filterSetProperties searches for the particular filter list by url and sets
// the values of newList to it, updating afterwards if needed. It returns true
// if the update was performed and the filtering engine restart is required.
func (d *DNSFilter) filterSetProperties(
listURL string,
newList FilterYAML,
isAllowlist bool,
) (shouldRestart bool, err error) {
d.filtersMu.Lock()
defer d.filtersMu.Unlock()
filters := d.Filters
if whitelist {
if isAllowlist {
filters = d.WhitelistFilters
}
i := slices.IndexFunc(filters, func(filt FilterYAML) bool {
return filt.URL == url
})
i := slices.IndexFunc(filters, func(filt FilterYAML) bool { return filt.URL == listURL })
if i == -1 {
return 0
return false, errFilterNotExist
}
filt := &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,
)
log.Debug("filter: set properties: %s: {%s %s %v}", filt.URL, newf.Name, newf.URL, newf.Enabled)
filt.Name = newf.Name
defer func(oldURL, oldName string, oldEnabled bool, oldUpdated time.Time) {
if err != nil {
filt.URL = oldURL
filt.Name = oldName
filt.Enabled = oldEnabled
filt.LastUpdated = oldUpdated
}
}(filt.URL, filt.Name, filt.Enabled, filt.LastUpdated)
if filt.URL != newf.URL {
r |= statusURLChanged | statusUpdateRequired
if d.filterExistsNoLock(newf.URL) {
return statusURLExists
filt.Name = newList.Name
if filt.URL != newList.URL {
if d.filterExistsLocked(newList.URL) {
return false, errFilterExists
}
filt.URL = newf.URL
filt.unload()
shouldRestart = true
filt.URL = newList.URL
filt.LastUpdated = time.Time{}
filt.checksum = 0
filt.RulesCount = 0
filt.unload()
}
if filt.Enabled != newf.Enabled {
r |= statusEnabledChanged
filt.Enabled = newf.Enabled
if filt.Enabled {
if (r & statusURLChanged) == 0 {
err := d.load(filt)
if err != nil {
// TODO(e.burkov): It seems the error is only returned when
// the file exists and couldn't be open. Investigate and
// improve.
log.Error("loading filter %d: %s", filt.ID, err)
if filt.Enabled != newList.Enabled {
filt.Enabled = newList.Enabled
shouldRestart = true
}
filt.LastUpdated = time.Time{}
filt.checksum = 0
filt.RulesCount = 0
r |= statusUpdateRequired
}
}
} else {
filt.unload()
if filt.Enabled {
if shouldRestart {
// Download the filter contents.
shouldRestart, err = d.update(filt)
}
} else {
// TODO(e.burkov): The validation of the contents of the new URL is
// currently skipped if the rule list is disabled. This makes it
// possible to set a bad rules source, but the validation should still
// kick in when the filter is enabled. Consider making changing this
// behavior to be stricter.
filt.unload()
}
return r | statusFound
return shouldRestart, err
}
// Return TRUE if a filter with this URL exists
func (d *DNSFilter) filterExists(url string) bool {
// filterExists returns true if a filter with the same url exists in d. It's
// safe for concurrent use.
func (d *DNSFilter) filterExists(url string) (ok bool) {
d.filtersMu.RLock()
defer d.filtersMu.RUnlock()
r := d.filterExistsNoLock(url)
r := d.filterExistsLocked(url)
return r
}
func (d *DNSFilter) filterExistsNoLock(url string) bool {
// filterExistsLocked returns true if d contains the filter with the same url.
// d.filtersMu is expected to be locked.
func (d *DNSFilter) filterExistsLocked(url string) (ok bool) {
for _, f := range d.Filters {
if f.URL == url {
return true
}
}
for _, f := range d.WhitelistFilters {
if f.URL == url {
return true
}
}
return false
}
@@ -155,7 +178,7 @@ func (d *DNSFilter) filterAdd(flt FilterYAML) bool {
defer d.filtersMu.Unlock()
// Check for duplicates
if d.filterExistsNoLock(flt.URL) {
if d.filterExistsLocked(flt.URL) {
return false
}
@@ -258,18 +281,6 @@ func (d *DNSFilter) tryRefreshFilters(block, allow, force bool) (updated int, is
return updated, isNetworkErr, ok
}
// refreshFilters updates the lists and returns the number of updated ones.
// It's safe for concurrent use, but blocks at least until the previous
// refreshing is finished.
func (d *DNSFilter) refreshFilters(block, allow, force bool) (updated int) {
d.refreshLock.Lock()
defer d.refreshLock.Unlock()
updated, _ = d.refreshFiltersIntl(block, allow, force)
return updated
}
// listsToUpdate returns the slice of filter lists that could be updated.
func (d *DNSFilter) listsToUpdate(filters *[]FilterYAML, force bool) (toUpd []FilterYAML) {
now := time.Now()
@@ -279,7 +290,6 @@ func (d *DNSFilter) listsToUpdate(filters *[]FilterYAML, force bool) (toUpd []Fi
for i := range *filters {
flt := &(*filters)[i] // otherwise we will be operating on a copy
log.Debug("checking list at index %d: %v", i, flt)
if !flt.Enabled {
continue

View File

@@ -4,9 +4,9 @@ import (
"io/fs"
"net"
"net/http"
"net/netip"
"net/url"
"os"
"path"
"path/filepath"
"testing"
"time"
@@ -17,27 +17,30 @@ import (
"github.com/stretchr/testify/require"
)
const testFltsFileName = "1.txt"
func testStartFilterListener(t *testing.T, fltContent *[]byte) (l net.Listener) {
// serveFiltersLocally is a helper that concurrently listens on a free port to
// respond with fltContent. It also gracefully closes the listener when the
// test under t finishes.
func serveFiltersLocally(t *testing.T, fltContent []byte) (ipp netip.AddrPort) {
t.Helper()
h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
n, werr := w.Write(*fltContent)
require.NoError(t, werr)
require.Equal(t, len(*fltContent), n)
pt := testutil.PanicT{}
n, werr := w.Write(fltContent)
require.NoError(pt, werr)
require.Equal(pt, len(fltContent), n)
})
var err error
l, err = net.Listen("tcp", ":0")
l, err := net.Listen("tcp", ":0")
require.NoError(t, err)
go func() {
_ = http.Serve(l, h)
}()
go func() { _ = http.Serve(l, h) }()
testutil.CleanupAndRequireSuccess(t, l.Close)
return l
addr := l.Addr()
require.IsType(t, new(net.TCPAddr), addr)
return netip.AddrPortFrom(netutil.IPv4Localhost(), uint16(addr.(*net.TCPAddr).Port))
}
func TestFilters(t *testing.T) {
@@ -49,7 +52,7 @@ func TestFilters(t *testing.T) {
fltContent := []byte(content)
l := testStartFilterListener(t, &fltContent)
addr := serveFiltersLocally(t, fltContent)
tempDir := t.TempDir()
@@ -64,11 +67,7 @@ func TestFilters(t *testing.T) {
f := &FilterYAML{
URL: (&url.URL{
Scheme: "http",
Host: (&netutil.IPPort{
IP: net.IP{127, 0, 0, 1},
Port: l.Addr().(*net.TCPAddr).Port,
}).String(),
Path: path.Join(filterDir, testFltsFileName),
Host: addr.String(),
}).String(),
}
@@ -101,8 +100,15 @@ func TestFilters(t *testing.T) {
})
t.Run("refresh_actually", func(t *testing.T) {
fltContent = []byte(`||example.com^`)
t.Cleanup(func() { fltContent = []byte(content) })
anotherContent := []byte(`||example.com^`)
oldURL := f.URL
ipp := serveFiltersLocally(t, anotherContent)
f.URL = (&url.URL{
Scheme: "http",
Host: ipp.String(),
}).String()
t.Cleanup(func() { f.URL = oldURL })
updateAndAssert(t, require.True, 1)
})

View File

@@ -345,27 +345,29 @@ func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool)
blockFilters: blockFilters,
}
d.filtersInitializerLock.Lock() // prevent multiple writers from adding more than 1 task
d.filtersInitializerLock.Lock()
defer d.filtersInitializerLock.Unlock()
// remove all pending tasks
stop := false
for !stop {
// Remove all pending tasks.
removeLoop:
for {
select {
case <-d.filtersInitializerChan:
//
// Continue removing.
default:
stop = true
break removeLoop
}
}
d.filtersInitializerChan <- params
return nil
}
err := d.initFiltering(allowFilters, blockFilters)
if err != nil {
log.Error("Can't initialize filtering subsystem: %s", err)
log.Error("filtering: can't initialize filtering subsystem: %s", err)
return err
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/cache"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
@@ -18,7 +19,7 @@ import (
)
func TestMain(m *testing.M) {
aghtest.DiscardLogOutput(m)
testutil.DiscardLogOutput(m)
}
const (

View File

@@ -56,7 +56,6 @@ func (d *DNSFilter) handleFilteringAddURL(w http.ResponseWriter, r *http.Request
err = validateFilterURL(fj.URL)
if err != nil {
err = fmt.Errorf("invalid url: %s", err)
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
@@ -75,8 +74,10 @@ func (d *DNSFilter) handleFilteringAddURL(w http.ResponseWriter, r *http.Request
URL: fj.URL,
Name: fj.Name,
white: fj.Whitelist,
Filter: Filter{
ID: assignUniqueFilterID(),
},
}
filt.ID = assignUniqueFilterID()
// Download the filter contents
ok, err := d.update(&filt)
@@ -216,32 +217,15 @@ func (d *DNSFilter) handleFilteringSetURL(w http.ResponseWriter, r *http.Request
Name: fj.Data.Name,
URL: fj.Data.URL,
}
status := d.filterSetProperties(fj.URL, filt, fj.Whitelist)
if (status & statusFound) == 0 {
aghhttp.Error(r, w, http.StatusBadRequest, "URL doesn't exist")
return
}
if (status & statusURLExists) != 0 {
aghhttp.Error(r, w, http.StatusBadRequest, "URL already exists")
restart, err := d.filterSetProperties(fj.URL, filt, fj.Whitelist)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, err.Error())
return
}
d.ConfigModified()
restart := (status & statusEnabledChanged) != 0
if (status&statusUpdateRequired) != 0 && fj.Data.Enabled {
// download new filter and apply its rules.
nUpdated := d.refreshFilters(!fj.Whitelist, fj.Whitelist, false)
// if at least 1 filter has been updated, refreshFilters() restarts the filtering automatically
// if not - we restart the filtering ourselves
restart = false
if nUpdated == 0 {
restart = true
}
}
if restart {
d.EnableFilters(true)
}
@@ -482,7 +466,8 @@ func (d *DNSFilter) RegisterFilteringHandlers() {
registerHTTP(http.MethodPost, "/control/rewrite/add", d.handleRewriteAdd)
registerHTTP(http.MethodPost, "/control/rewrite/delete", d.handleRewriteDelete)
registerHTTP(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesAvailableServices)
registerHTTP(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesIDs)
registerHTTP(http.MethodGet, "/control/blocked_services/all", d.handleBlockedServicesAll)
registerHTTP(http.MethodGet, "/control/blocked_services/list", d.handleBlockedServicesList)
registerHTTP(http.MethodPost, "/control/blocked_services/set", d.handleBlockedServicesSet)

View File

@@ -0,0 +1,143 @@
package filtering
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDNSFilter_handleFilteringSetURL(t *testing.T) {
filtersDir := t.TempDir()
var goodRulesEndpoint, anotherGoodRulesEndpoint, badRulesEndpoint string
for _, rulesSource := range []struct {
endpoint *string
content []byte
}{{
endpoint: &goodRulesEndpoint,
content: []byte(`||example.org^`),
}, {
endpoint: &anotherGoodRulesEndpoint,
content: []byte(`||example.com^`),
}, {
endpoint: &badRulesEndpoint,
content: []byte(`<html></html>`),
}} {
ipp := serveFiltersLocally(t, rulesSource.content)
*rulesSource.endpoint = (&url.URL{
Scheme: "http",
Host: ipp.String(),
}).String()
}
testCases := []struct {
name string
wantBody string
oldURL string
newName string
newURL string
initial []FilterYAML
}{{
name: "success",
wantBody: "",
oldURL: goodRulesEndpoint,
newName: "default_one",
newURL: anotherGoodRulesEndpoint,
initial: []FilterYAML{{
Enabled: true,
URL: goodRulesEndpoint,
Name: "default_one",
white: false,
}},
}, {
name: "non-existing",
wantBody: "url doesn't exist\n",
oldURL: anotherGoodRulesEndpoint,
newName: "default_one",
newURL: goodRulesEndpoint,
initial: []FilterYAML{{
Enabled: true,
URL: goodRulesEndpoint,
Name: "default_one",
white: false,
}},
}, {
name: "existing",
wantBody: "url already exists\n",
oldURL: goodRulesEndpoint,
newName: "default_one",
newURL: anotherGoodRulesEndpoint,
initial: []FilterYAML{{
Enabled: true,
URL: goodRulesEndpoint,
Name: "default_one",
white: false,
}, {
Enabled: true,
URL: anotherGoodRulesEndpoint,
Name: "another_default_one",
white: false,
}},
}, {
name: "bad_rules",
wantBody: "data is HTML, not plain text\n",
oldURL: goodRulesEndpoint,
newName: "default_one",
newURL: badRulesEndpoint,
initial: []FilterYAML{{
Enabled: true,
URL: goodRulesEndpoint,
Name: "default_one",
white: false,
}},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
confModifiedCalled := false
d, err := New(&Config{
FilteringEnabled: true,
Filters: tc.initial,
HTTPClient: &http.Client{
Timeout: 5 * time.Second,
},
ConfigModified: func() { confModifiedCalled = true },
DataDir: filtersDir,
}, nil)
require.NoError(t, err)
t.Cleanup(d.Close)
d.Start()
reqData := &filterURLReq{
Data: &filterURLReqData{
// Leave the name of an existing list.
Name: tc.newName,
URL: tc.newURL,
Enabled: true,
},
URL: tc.oldURL,
Whitelist: false,
}
data, err := json.Marshal(reqData)
require.NoError(t, err)
r := httptest.NewRequest(http.MethodPost, "http://example.org", bytes.NewReader(data))
w := httptest.NewRecorder()
d.handleFilteringSetURL(w, r)
assert.Equal(t, tc.wantBody, w.Body.String())
// For the moment the non-empty response body only contains occurred
// error, so the configuration shouldn't be written.
assert.Equal(t, tc.wantBody == "", confModifiedCalled)
})
}
}

View File

@@ -13,8 +13,8 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// LegacyRewrite is a single legacy DNS rewrite record.
@@ -41,7 +41,7 @@ func (rw *LegacyRewrite) clone() (cloneRW *LegacyRewrite) {
return &LegacyRewrite{
Domain: rw.Domain,
Answer: rw.Answer,
IP: netutil.CloneIP(rw.IP),
IP: slices.Clone(rw.IP),
Type: rw.Type,
}
}

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,7 @@ import (
"encoding"
"fmt"
"net"
"net/netip"
"sort"
"strings"
"sync"
@@ -128,14 +129,12 @@ type RuntimeClientWHOISInfo struct {
type clientsContainer struct {
// TODO(a.garipov): Perhaps use a number of separate indices for
// different types (string, net.IP, and so on).
// different types (string, netip.Addr, and so on).
list map[string]*Client // name -> client
idIndex map[string]*Client // ID -> client
// ipToRC is the IP address to *RuntimeClient map.
//
// TODO(e.burkov): Use map[netip.Addr]struct{} instead.
ipToRC *netutil.IPMap
ipToRC map[netip.Addr]*RuntimeClient
lock sync.Mutex
@@ -171,7 +170,7 @@ func (clients *clientsContainer) Init(
}
clients.list = make(map[string]*Client)
clients.idIndex = make(map[string]*Client)
clients.ipToRC = netutil.NewIPMap(0)
clients.ipToRC = map[netip.Addr]*RuntimeClient{}
clients.allTags = stringutil.NewSet(clientTags...)
@@ -333,8 +332,8 @@ func (clients *clientsContainer) onDHCPLeaseChanged(flags int) {
}
}
// Exists checks if client with this IP address already exists.
func (clients *clientsContainer) Exists(ip net.IP, source clientSource) (ok bool) {
// exists checks if client with this IP address already exists.
func (clients *clientsContainer) exists(ip netip.Addr, source clientSource) (ok bool) {
clients.lock.Lock()
defer clients.lock.Unlock()
@@ -343,7 +342,7 @@ func (clients *clientsContainer) Exists(ip net.IP, source clientSource) (ok bool
return true
}
rc, ok := clients.findRuntimeClientLocked(ip)
rc, ok := clients.ipToRC[ip]
if !ok {
return false
}
@@ -372,7 +371,8 @@ func (clients *clientsContainer) findMultiple(ids []string) (c *querylog.Client,
var artClient *querylog.Client
var art bool
for _, id := range ids {
c, art = clients.clientOrArtificial(net.ParseIP(id), id)
ip, _ := netip.ParseAddr(id)
c, art = clients.clientOrArtificial(ip, id)
if art {
artClient = c
@@ -390,7 +390,7 @@ func (clients *clientsContainer) findMultiple(ids []string) (c *querylog.Client,
// records about this client besides maybe whether or not it is blocked. c is
// never nil.
func (clients *clientsContainer) clientOrArtificial(
ip net.IP,
ip netip.Addr,
id string,
) (c *querylog.Client, art bool) {
defer func() {
@@ -407,15 +407,8 @@ func (clients *clientsContainer) clientOrArtificial(
}, false
}
if ip == nil {
// Technically should never happen, but still.
return &querylog.Client{
Name: "",
}, true
}
var rc *RuntimeClient
rc, ok = clients.FindRuntimeClient(ip)
rc, ok = clients.findRuntimeClient(ip)
if ok {
return &querylog.Client{
Name: rc.Host,
@@ -493,19 +486,20 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
return c, true
}
ip := net.ParseIP(id)
if ip == nil {
ip, err := netip.ParseAddr(id)
if err != nil {
return nil, false
}
for _, c = range clients.list {
for _, id := range c.IDs {
_, ipnet, err := net.ParseCIDR(id)
var n netip.Prefix
n, err = netip.ParsePrefix(id)
if err != nil {
continue
}
if ipnet.Contains(ip) {
if n.Contains(ip) {
return c, true
}
}
@@ -515,19 +509,20 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
return nil, false
}
macFound := clients.dhcpServer.FindMACbyIP(ip)
macFound := clients.dhcpServer.FindMACbyIP(ip.AsSlice())
if macFound == nil {
return nil, false
}
for _, c = range clients.list {
for _, id := range c.IDs {
hwAddr, err := net.ParseMAC(id)
var mac net.HardwareAddr
mac, err = net.ParseMAC(id)
if err != nil {
continue
}
if bytes.Equal(hwAddr, macFound) {
if bytes.Equal(mac, macFound) {
return c, true
}
}
@@ -536,35 +531,18 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
return nil, false
}
// findRuntimeClientLocked finds a runtime client by their IP address. For
// internal use only.
func (clients *clientsContainer) findRuntimeClientLocked(ip net.IP) (rc *RuntimeClient, ok bool) {
var v any
v, ok = clients.ipToRC.Get(ip)
if !ok {
return nil, false
}
rc, ok = v.(*RuntimeClient)
if !ok {
log.Error("clients: bad type %T in ipToRC for %s", v, ip)
return nil, false
}
return rc, true
}
// FindRuntimeClient finds a runtime client by their IP.
func (clients *clientsContainer) FindRuntimeClient(ip net.IP) (rc *RuntimeClient, ok bool) {
if ip == nil {
// findRuntimeClient finds a runtime client by their IP.
func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *RuntimeClient, ok bool) {
if ip == (netip.Addr{}) {
return nil, false
}
clients.lock.Lock()
defer clients.lock.Unlock()
return clients.findRuntimeClientLocked(ip)
rc, ok = clients.ipToRC[ip]
return rc, ok
}
// check validates the client.
@@ -582,14 +560,16 @@ func (clients *clientsContainer) check(c *Client) (err error) {
for i, id := range c.IDs {
// Normalize structured data.
var ip net.IP
var ipnet *net.IPNet
var mac net.HardwareAddr
if ip = net.ParseIP(id); ip != nil {
var (
ip netip.Addr
n netip.Prefix
mac net.HardwareAddr
)
if ip, err = netip.ParseAddr(id); err == nil {
c.IDs[i] = ip.String()
} else if ip, ipnet, err = net.ParseCIDR(id); err == nil {
ipnet.IP = ip
c.IDs[i] = ipnet.String()
} else if n, err = netip.ParsePrefix(id); err == nil {
c.IDs[i] = n.String()
} else if mac, err = net.ParseMAC(id); err == nil {
c.IDs[i] = mac.String()
} else if err = dnsforward.ValidateClientID(id); err == nil {
@@ -753,8 +733,8 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) {
return nil
}
// SetWHOISInfo sets the WHOIS information for a client.
func (clients *clientsContainer) SetWHOISInfo(ip net.IP, wi *RuntimeClientWHOISInfo) {
// setWHOISInfo sets the WHOIS information for a client.
func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWHOISInfo) {
clients.lock.Lock()
defer clients.lock.Unlock()
@@ -764,7 +744,7 @@ func (clients *clientsContainer) SetWHOISInfo(ip net.IP, wi *RuntimeClientWHOISI
return
}
rc, ok := clients.findRuntimeClientLocked(ip)
rc, ok := clients.ipToRC[ip]
if ok {
rc.WHOISInfo = wi
log.Debug("clients: set whois info for runtime client %s: %+v", rc.Host, wi)
@@ -780,23 +760,32 @@ func (clients *clientsContainer) SetWHOISInfo(ip net.IP, wi *RuntimeClientWHOISI
rc.WHOISInfo = wi
clients.ipToRC.Set(ip, rc)
clients.ipToRC[ip] = rc
log.Debug("clients: set whois info for runtime client with ip %s: %+v", ip, wi)
}
// AddHost adds a new IP-hostname pairing. The priorities of the sources are
// taken into account. ok is true if the pairing was added.
func (clients *clientsContainer) AddHost(ip net.IP, host string, src clientSource) (ok bool, err error) {
func (clients *clientsContainer) AddHost(
ip netip.Addr,
host string,
src clientSource,
) (ok bool) {
clients.lock.Lock()
defer clients.lock.Unlock()
return clients.addHostLocked(ip, host, src), nil
return clients.addHostLocked(ip, host, src)
}
// addHostLocked adds a new IP-hostname pairing. For internal use only.
func (clients *clientsContainer) addHostLocked(ip net.IP, host string, src clientSource) (ok bool) {
rc, ok := clients.findRuntimeClientLocked(ip)
// addHostLocked adds a new IP-hostname pairing. clients.lock is expected to be
// locked.
func (clients *clientsContainer) addHostLocked(
ip netip.Addr,
host string,
src clientSource,
) (ok bool) {
rc, ok := clients.ipToRC[ip]
if ok {
if rc.Source > src {
return false
@@ -811,10 +800,10 @@ func (clients *clientsContainer) addHostLocked(ip net.IP, host string, src clien
WHOISInfo: &RuntimeClientWHOISInfo{},
}
clients.ipToRC.Set(ip, rc)
clients.ipToRC[ip] = rc
}
log.Debug("clients: added %s -> %q [%d]", ip, host, clients.ipToRC.Len())
log.Debug("clients: added %s -> %q [%d]", ip, host, len(clients.ipToRC))
return true
}
@@ -822,47 +811,29 @@ func (clients *clientsContainer) addHostLocked(ip net.IP, host string, src clien
// rmHostsBySrc removes all entries that match the specified source.
func (clients *clientsContainer) rmHostsBySrc(src clientSource) {
n := 0
clients.ipToRC.Range(func(ip net.IP, v any) (cont bool) {
rc, ok := v.(*RuntimeClient)
if !ok {
log.Error("clients: bad type %T in ipToRC for %s", v, ip)
return true
}
for ip, rc := range clients.ipToRC {
if rc.Source == src {
clients.ipToRC.Del(ip)
delete(clients.ipToRC, ip)
n++
}
return true
})
}
log.Debug("clients: removed %d client aliases", n)
}
// addFromHostsFile fills the client-hostname pairing index from the system's
// hosts files.
func (clients *clientsContainer) addFromHostsFile(hosts *netutil.IPMap) {
func (clients *clientsContainer) addFromHostsFile(hosts aghnet.HostsRecords) {
clients.lock.Lock()
defer clients.lock.Unlock()
clients.rmHostsBySrc(ClientSourceHostsFile)
n := 0
hosts.Range(func(ip net.IP, v any) (cont bool) {
rec, ok := v.(*aghnet.HostsRecord)
if !ok {
log.Error("dns: bad type %T in ipToRC for %s", v, ip)
return true
}
for ip, rec := range hosts {
clients.addHostLocked(ip, rec.Canonical, ClientSourceHostsFile)
n++
return true
})
}
log.Debug("clients: added %d client aliases from system hosts file", n)
}
@@ -923,7 +894,15 @@ func (clients *clientsContainer) updateFromDHCP(add bool) {
continue
}
ok := clients.addHostLocked(l.IP, l.Hostname, ClientSourceDHCP)
// TODO(a.garipov): Remove once we switch to netip.Addr more fully.
ipAddr, err := netutil.IPToAddrNoMapped(l.IP)
if err != nil {
log.Error("clients: bad client ip %v from dhcp: %s", l.IP, err)
continue
}
ok := clients.addHostLocked(ipAddr, l.Hostname, ClientSourceDHCP)
if ok {
n++
}
@@ -932,9 +911,9 @@ func (clients *clientsContainer) updateFromDHCP(add bool) {
log.Debug("clients: added %d client aliases from dhcp", n)
}
// Close gracefully closes all the client-specific upstream configurations of
// close gracefully closes all the client-specific upstream configurations of
// the persistent clients.
func (clients *clientsContainer) Close() (err error) {
func (clients *clientsContainer) close() (err error) {
persistent := maps.Values(clients.list)
slices.SortFunc(persistent, func(a, b *Client) (less bool) { return a.Name < b.Name })

View File

@@ -22,8 +22,18 @@ func TestClients(t *testing.T) {
clients.Init(nil, nil, nil, nil)
t.Run("add_success", func(t *testing.T) {
var (
cliNone = "1.2.3.4"
cli1 = "1.1.1.1"
cli2 = "2.2.2.2"
cliNoneIP = netip.MustParseAddr(cliNone)
cli1IP = netip.MustParseAddr(cli1)
cli2IP = netip.MustParseAddr(cli2)
)
c := &Client{
IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
IDs: []string{cli1, "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
Name: "client1",
}
@@ -33,7 +43,7 @@ func TestClients(t *testing.T) {
assert.True(t, ok)
c = &Client{
IDs: []string{"2.2.2.2"},
IDs: []string{cli2},
Name: "client2",
}
@@ -42,7 +52,7 @@ func TestClients(t *testing.T) {
assert.True(t, ok)
c, ok = clients.Find("1.1.1.1")
c, ok = clients.Find(cli1)
require.True(t, ok)
assert.Equal(t, "client1", c.Name)
@@ -52,14 +62,14 @@ func TestClients(t *testing.T) {
assert.Equal(t, "client1", c.Name)
c, ok = clients.Find("2.2.2.2")
c, ok = clients.Find(cli2)
require.True(t, ok)
assert.Equal(t, "client2", c.Name)
assert.False(t, clients.Exists(net.IP{1, 2, 3, 4}, ClientSourceHostsFile))
assert.True(t, clients.Exists(net.IP{1, 1, 1, 1}, ClientSourceHostsFile))
assert.True(t, clients.Exists(net.IP{2, 2, 2, 2}, ClientSourceHostsFile))
assert.False(t, clients.exists(cliNoneIP, ClientSourceHostsFile))
assert.True(t, clients.exists(cli1IP, ClientSourceHostsFile))
assert.True(t, clients.exists(cli2IP, ClientSourceHostsFile))
})
t.Run("add_fail_name", func(t *testing.T) {
@@ -103,23 +113,31 @@ func TestClients(t *testing.T) {
})
t.Run("update_success", func(t *testing.T) {
var (
cliOld = "1.1.1.1"
cliNew = "1.1.1.2"
cliOldIP = netip.MustParseAddr(cliOld)
cliNewIP = netip.MustParseAddr(cliNew)
)
err := clients.Update("client1", &Client{
IDs: []string{"1.1.1.2"},
IDs: []string{cliNew},
Name: "client1",
})
require.NoError(t, err)
assert.False(t, clients.Exists(net.IP{1, 1, 1, 1}, ClientSourceHostsFile))
assert.True(t, clients.Exists(net.IP{1, 1, 1, 2}, ClientSourceHostsFile))
assert.False(t, clients.exists(cliOldIP, ClientSourceHostsFile))
assert.True(t, clients.exists(cliNewIP, ClientSourceHostsFile))
err = clients.Update("client1", &Client{
IDs: []string{"1.1.1.2"},
IDs: []string{cliNew},
Name: "client1-renamed",
UseOwnSettings: true,
})
require.NoError(t, err)
c, ok := clients.Find("1.1.1.2")
c, ok := clients.Find(cliNew)
require.True(t, ok)
assert.Equal(t, "client1-renamed", c.Name)
@@ -132,14 +150,14 @@ func TestClients(t *testing.T) {
require.Len(t, c.IDs, 1)
assert.Equal(t, "1.1.1.2", c.IDs[0])
assert.Equal(t, cliNew, c.IDs[0])
})
t.Run("del_success", func(t *testing.T) {
ok := clients.Del("client1-renamed")
require.True(t, ok)
assert.False(t, clients.Exists(net.IP{1, 1, 1, 2}, ClientSourceHostsFile))
assert.False(t, clients.exists(netip.MustParseAddr("1.1.1.2"), ClientSourceHostsFile))
})
t.Run("del_fail", func(t *testing.T) {
@@ -148,45 +166,33 @@ func TestClients(t *testing.T) {
})
t.Run("addhost_success", func(t *testing.T) {
ip := net.IP{1, 1, 1, 1}
ok, err := clients.AddHost(ip, "host", ClientSourceARP)
require.NoError(t, err)
ip := netip.MustParseAddr("1.1.1.1")
ok := clients.AddHost(ip, "host", ClientSourceARP)
assert.True(t, ok)
ok, err = clients.AddHost(ip, "host2", ClientSourceARP)
require.NoError(t, err)
ok = clients.AddHost(ip, "host2", ClientSourceARP)
assert.True(t, ok)
ok, err = clients.AddHost(ip, "host3", ClientSourceHostsFile)
require.NoError(t, err)
ok = clients.AddHost(ip, "host3", ClientSourceHostsFile)
assert.True(t, ok)
assert.True(t, clients.Exists(ip, ClientSourceHostsFile))
assert.True(t, clients.exists(ip, ClientSourceHostsFile))
})
t.Run("dhcp_replaces_arp", func(t *testing.T) {
ip := net.IP{1, 2, 3, 4}
ok, err := clients.AddHost(ip, "from_arp", ClientSourceARP)
require.NoError(t, err)
ip := netip.MustParseAddr("1.2.3.4")
ok := clients.AddHost(ip, "from_arp", ClientSourceARP)
assert.True(t, ok)
assert.True(t, clients.Exists(ip, ClientSourceARP))
ok, err = clients.AddHost(ip, "from_dhcp", ClientSourceDHCP)
require.NoError(t, err)
assert.True(t, clients.exists(ip, ClientSourceARP))
ok = clients.AddHost(ip, "from_dhcp", ClientSourceDHCP)
assert.True(t, ok)
assert.True(t, clients.Exists(ip, ClientSourceDHCP))
assert.True(t, clients.exists(ip, ClientSourceDHCP))
})
t.Run("addhost_fail", func(t *testing.T) {
ok, err := clients.AddHost(net.IP{1, 1, 1, 1}, "host1", ClientSourceRDNS)
require.NoError(t, err)
ip := netip.MustParseAddr("1.1.1.1")
ok := clients.AddHost(ip, "host1", ClientSourceRDNS)
assert.False(t, ok)
})
}
@@ -202,38 +208,28 @@ func TestClientsWHOIS(t *testing.T) {
}
t.Run("new_client", func(t *testing.T) {
ip := net.IP{1, 1, 1, 255}
clients.SetWHOISInfo(ip, whois)
v, _ := clients.ipToRC.Get(ip)
require.NotNil(t, v)
rc, ok := v.(*RuntimeClient)
require.True(t, ok)
ip := netip.MustParseAddr("1.1.1.255")
clients.setWHOISInfo(ip, whois)
rc := clients.ipToRC[ip]
require.NotNil(t, rc)
assert.Equal(t, rc.WHOISInfo, whois)
})
t.Run("existing_auto-client", func(t *testing.T) {
ip := net.IP{1, 1, 1, 1}
ok, err := clients.AddHost(ip, "host", ClientSourceRDNS)
require.NoError(t, err)
ip := netip.MustParseAddr("1.1.1.1")
ok := clients.AddHost(ip, "host", ClientSourceRDNS)
assert.True(t, ok)
clients.SetWHOISInfo(ip, whois)
v, _ := clients.ipToRC.Get(ip)
require.NotNil(t, v)
rc, ok := v.(*RuntimeClient)
require.True(t, ok)
clients.setWHOISInfo(ip, whois)
rc := clients.ipToRC[ip]
require.NotNil(t, rc)
assert.Equal(t, rc.WHOISInfo, whois)
})
t.Run("can't_set_manually-added", func(t *testing.T) {
ip := net.IP{1, 1, 1, 2}
ip := netip.MustParseAddr("1.1.1.2")
ok, err := clients.Add(&Client{
IDs: []string{"1.1.1.2"},
@@ -242,9 +238,9 @@ func TestClientsWHOIS(t *testing.T) {
require.NoError(t, err)
assert.True(t, ok)
clients.SetWHOISInfo(ip, whois)
v, _ := clients.ipToRC.Get(ip)
require.Nil(t, v)
clients.setWHOISInfo(ip, whois)
rc := clients.ipToRC[ip]
require.Nil(t, rc)
assert.True(t, clients.Del("client1"))
})
@@ -257,7 +253,7 @@ func TestClientsAddExisting(t *testing.T) {
clients.Init(nil, nil, nil, nil)
t.Run("simple", func(t *testing.T) {
ip := net.IP{1, 1, 1, 1}
ip := netip.MustParseAddr("1.1.1.1")
// Add a client.
ok, err := clients.Add(&Client{
@@ -268,8 +264,7 @@ func TestClientsAddExisting(t *testing.T) {
assert.True(t, ok)
// Now add an auto-client with the same IP.
ok, err = clients.AddHost(ip, "test", ClientSourceRDNS)
require.NoError(t, err)
ok = clients.AddHost(ip, "test", ClientSourceRDNS)
assert.True(t, ok)
})

View File

@@ -3,11 +3,10 @@ package home
import (
"encoding/json"
"fmt"
"net"
"net/http"
"net/netip"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/log"
)
// clientJSON is a common structure used by several handlers to deal with
@@ -48,8 +47,8 @@ type runtimeClientJSON struct {
WHOISInfo *RuntimeClientWHOISInfo `json:"whois_info"`
Name string `json:"name"`
IP netip.Addr `json:"ip"`
Source clientSource `json:"source"`
IP net.IP `json:"ip"`
}
type clientListJSON struct {
@@ -70,14 +69,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
data.Clients = append(data.Clients, cj)
}
clients.ipToRC.Range(func(ip net.IP, v any) (cont bool) {
rc, ok := v.(*RuntimeClient)
if !ok {
log.Error("dns: bad type %T in ipToRC for %s", v, ip)
return true
}
for ip, rc := range clients.ipToRC {
cj := runtimeClientJSON{
WHOISInfo: rc.WHOISInfo,
@@ -87,9 +79,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
}
data.RuntimeClients = append(data.RuntimeClients, cj)
return true
})
}
data.Tags = clientTags
@@ -228,7 +218,7 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
break
}
ip := net.ParseIP(idStr)
ip, _ := netip.ParseAddr(idStr)
c, ok := clients.Find(idStr)
var cj *clientJSON
if !ok {
@@ -250,8 +240,8 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
// findRuntime looks up the IP in runtime and temporary storages, like
// /etc/hosts tables, DHCP leases, or blocklists. cj is guaranteed to be
// non-nil.
func (clients *clientsContainer) findRuntime(ip net.IP, idStr string) (cj *clientJSON) {
rc, ok := clients.FindRuntimeClient(ip)
func (clients *clientsContainer) findRuntime(ip netip.Addr, idStr string) (cj *clientJSON) {
rc, ok := clients.findRuntimeClient(ip)
if !ok {
// It is still possible that the IP used to be in the runtime clients
// list, but then the server was reloaded. So, check the DNS server's

View File

@@ -20,6 +20,7 @@ import (
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/google/renameio/maybe"
"golang.org/x/exp/slices"
yaml "gopkg.in/yaml.v3"
)
@@ -113,8 +114,8 @@ type configuration struct {
// An active session is automatically refreshed once a day.
WebSessionTTLHours uint32 `yaml:"web_session_ttl"`
DNS dnsConfig `yaml:"dns"`
TLS tlsConfigSettings `yaml:"tls"`
DNS dnsConfig `yaml:"dns"`
TLS tlsConfiguration `yaml:"tls"`
// Filters reflects the filters from [filtering.Config]. It's cloned to the
// config used in the filtering module at the startup. Afterwards it's
@@ -199,7 +200,8 @@ type dnsConfig struct {
UseHTTP3Upstreams bool `yaml:"use_http3_upstreams"`
}
type tlsConfigSettings struct {
// tlsConfiguration is the on-disk TLS configuration.
type tlsConfiguration struct {
Enabled bool `yaml:"enabled" json:"enabled"` // Enabled is the encryption (DoT/DoH/HTTPS) status
ServerName string `yaml:"server_name" json:"server_name,omitempty"` // ServerName is the hostname of your HTTPS/TLS server
ForceHTTPS bool `yaml:"force_https" json:"force_https"` // ForceHTTPS: if true, forces HTTP->HTTPS redirect
@@ -223,6 +225,29 @@ type tlsConfigSettings struct {
dnsforward.TLSConfig `yaml:",inline" json:",inline"`
}
// cloneForEncoding returns a clone of c with all top-level fields of c and all
// exported and YAML-encoded fields of c.TLSConfig cloned.
//
// TODO(a.garipov): This is better than races, but still not good enough.
func (c *tlsConfiguration) cloneForEncoding() (cloned *tlsConfiguration) {
if c == nil {
return nil
}
v := *c
cloned = &v
cloned.TLSConfig = dnsforward.TLSConfig{
CertificateChain: c.CertificateChain,
PrivateKey: c.PrivateKey,
CertificatePath: c.CertificatePath,
PrivateKeyPath: c.PrivateKeyPath,
OverrideTLSCiphers: slices.Clone(c.OverrideTLSCiphers),
StrictSNICheck: c.StrictSNICheck,
}
return cloned
}
// config is the global configuration structure.
//
// TODO(a.garipov, e.burkov): This global is awful and must be removed.
@@ -273,7 +298,7 @@ var config = &configuration{
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
UsePrivateRDNS: true,
},
TLS: tlsConfigSettings{
TLS: tlsConfiguration{
PortHTTPS: defaultPortHTTPS,
PortDNSOverTLS: defaultPortTLS, // needs to be passed through to dnsproxy
PortDNSOverQUIC: defaultPortQUIC,
@@ -442,7 +467,7 @@ func (c *configuration) write() (err error) {
}
if Context.tls != nil {
tlsConf := tlsConfigSettings{}
tlsConf := tlsConfiguration{}
Context.tls.WriteDiskConfig(&tlsConf)
config.TLS = tlsConf
}

View File

@@ -71,9 +71,7 @@ func appendDNSAddrsWithIfaces(dst []string, src []netip.Addr) (res []string, err
// on, including the addresses on all interfaces in cases of unspecified IPs.
func collectDNSAddresses() (addrs []string, err error) {
if hosts := config.DNS.BindHosts; len(hosts) == 0 {
addr := aghnet.IPv4Localhost()
addrs = appendDNSAddrs(addrs, addr)
addrs = appendDNSAddrs(addrs, netutil.IPv4Localhost())
} else {
addrs, err = appendDNSAddrsWithIfaces(addrs, hosts)
if err != nil {

View File

@@ -154,7 +154,7 @@ func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
return nil
}
tlsConf := &tlsConfigSettings{}
tlsConf := &tlsConfiguration{}
Context.tls.WriteDiskConfig(tlsConf)
canUpdate := true
@@ -172,7 +172,7 @@ func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
// tlsConfUsesPrivilegedPorts returns true if the provided TLS configuration
// indicates that privileged ports are used.
func tlsConfUsesPrivilegedPorts(c *tlsConfigSettings) (ok bool) {
func tlsConfUsesPrivilegedPorts(c *tlsConfiguration) (ok bool) {
return c.Enabled && (c.PortHTTPS < 1024 || c.PortDNSOverTLS < 1024 || c.PortDNSOverQUIC < 1024)
}

View File

@@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
@@ -150,8 +151,8 @@ func isRunning() bool {
}
func onDNSRequest(pctx *proxy.DNSContext) {
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
if ip == nil {
ip := netutil.NetAddrToAddrPort(pctx.Addr).Addr()
if ip == (netip.Addr{}) {
// This would be quite weird if we get here.
return
}
@@ -160,7 +161,8 @@ func onDNSRequest(pctx *proxy.DNSContext) {
if srcs.RDNS && !ip.IsLoopback() {
Context.rdns.Begin(ip)
}
if srcs.WHOIS && !netutil.IsSpecialPurpose(ip) {
if srcs.WHOIS && !netutil.IsSpecialPurposeAddr(ip) {
Context.whois.Begin(ip)
}
}
@@ -193,11 +195,7 @@ func ipsToUDPAddrs(ips []netip.Addr, port int) (udpAddrs []*net.UDPAddr) {
func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
dnsConf := config.DNS
hosts := dnsConf.BindHosts
if len(hosts) == 0 {
hosts = []netip.Addr{aghnet.IPv4Localhost()}
}
hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()})
newConf = dnsforward.ServerConfig{
UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port),
TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port),
@@ -207,7 +205,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
OnDNSRequest: onDNSRequest,
}
tlsConf := tlsConfigSettings{}
tlsConf := tlsConfiguration{}
Context.tls.WriteDiskConfig(&tlsConf)
if tlsConf.Enabled {
newConf.TLSConfig = tlsConf.TLSConfig
@@ -252,7 +250,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
return newConf, nil
}
func newDNSCrypt(hosts []netip.Addr, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) {
func newDNSCrypt(hosts []netip.Addr, tlsConf tlsConfiguration) (dnscc dnsforward.DNSCryptConfig, err error) {
if tlsConf.DNSCryptConfigFile == "" {
return dnscc, errors.Error("no dnscrypt_config_file")
}
@@ -290,7 +288,7 @@ type dnsEncryption struct {
}
func getDNSEncryption() (de dnsEncryption) {
tlsConf := tlsConfigSettings{}
tlsConf := tlsConfiguration{}
Context.tls.WriteDiskConfig(&tlsConf)
@@ -400,15 +398,12 @@ func startDNSServer() error {
const topClientsNumber = 100 // the number of clients to get
for _, ip := range Context.stats.TopClientsIP(topClientsNumber) {
if ip == nil {
continue
}
srcs := config.Clients.Sources
if srcs.RDNS && !ip.IsLoopback() {
Context.rdns.Begin(ip)
}
if srcs.WHOIS && !netutil.IsSpecialPurpose(ip) {
if srcs.WHOIS && !netutil.IsSpecialPurposeAddr(ip) {
Context.whois.Begin(ip)
}
}
@@ -441,7 +436,7 @@ func stopDNSServer() (err error) {
return fmt.Errorf("stopping forwarding dns server: %w", err)
}
err = Context.clients.Close()
err = Context.clients.close()
if err != nil {
return fmt.Errorf("closing clients container: %w", err)
}

View File

@@ -76,9 +76,7 @@ type homeContext struct {
configFilename string // Config filename (can be overridden via the command line arguments)
workDir string // Location of our directory, used to protect against CWD being somewhere else
firstRun bool // if set to true, don't run any services except HTTP web interface, and serve only first-run html
pidFileName string // PID file name. Empty if no PID file was created.
disableUpdate bool // If set, don't check for updates
controlLock sync.Mutex
tlsRoots *x509.CertPool // list of root CAs for TLSv1.2
transport *http.Transport
@@ -88,6 +86,13 @@ type homeContext struct {
// tlsCipherIDs are the ID of the cipher suites that AdGuard Home must use.
tlsCipherIDs []uint16
// disableUpdate, if true, tells AdGuard Home to not check for updates.
disableUpdate bool
// firstRun, if true, tells AdGuard Home to only start the web interface
// service, and only serve the first-run APIs.
firstRun bool
// runningAsService flag is set to true when options are passed from the service runner
runningAsService bool
}
@@ -462,6 +467,15 @@ func run(opts options, clientBuildFS fs.FS) {
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() {
log.Info("pprof: listening on localhost:6060")
lerr := http.ListenAndServe("localhost:6060", mux)
@@ -498,7 +512,7 @@ func run(opts options, clientBuildFS fs.FS) {
}
config.Users = nil
Context.tls, err = newTLSManager(config.TLS)
Context.tls, err = newTLSManager(&config.TLS)
if err != nil {
log.Fatalf("initializing tls: %s", err)
}
@@ -528,6 +542,11 @@ func run(opts options, clientBuildFS fs.FS) {
}
}
// TODO(a.garipov): This could be made much earlier and could be done on
// the first run as well, but to achieve this we need to bypass requests
// over dnsforward resolver.
cmdlineUpdate(opts)
Context.web.Start()
// wait indefinitely for other go-routines to complete their job
@@ -562,7 +581,7 @@ func checkPermissions() {
}
// We should check if AdGuard Home is able to bind to port 53
err := aghnet.CheckPort("tcp", netip.AddrPortFrom(aghnet.IPv4Localhost(), defaultPortDNS))
err := aghnet.CheckPort("tcp", netip.AddrPortFrom(netutil.IPv4Localhost(), defaultPortDNS))
if err != nil {
if errors.Is(err, os.ErrPermission) {
log.Fatal(`Permission check failed.
@@ -798,7 +817,7 @@ func printWebAddrs(proto, addr string, port, betaPort int) {
// printHTTPAddresses prints the IP addresses which user can use to access the
// admin interface. proto is either schemeHTTP or schemeHTTPS.
func printHTTPAddresses(proto string) {
tlsConf := tlsConfigSettings{}
tlsConf := tlsConfiguration{}
if Context.tls != nil {
Context.tls.WriteDiskConfig(&tlsConf)
}
@@ -913,3 +932,37 @@ type jsonError struct {
// Message is the error message, an opaque string.
Message string `json:"message"`
}
// cmdlineUpdate updates current application and exits.
func cmdlineUpdate(opts options) {
if !opts.performUpdate {
return
}
log.Info("starting update")
if Context.firstRun {
log.Info("update not allowed on first run")
os.Exit(0)
}
_, err := Context.updater.VersionInfo(true)
if err != nil {
vcu := Context.updater.VersionCheckURL()
log.Error("getting version info from %s: %s", vcu, err)
os.Exit(0)
}
if Context.updater.NewVersion() == "" {
log.Info("no updates available")
os.Exit(0)
}
err = Context.updater.Update()
fatalOnError(err)
os.Exit(0)
}

View File

@@ -3,10 +3,10 @@ package home
import (
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/testutil"
)
func TestMain(m *testing.M) {
aghtest.DiscardLogOutput(m)
testutil.DiscardLogOutput(m)
initCmdLineOpts()
}

View File

@@ -32,7 +32,11 @@ func setupDNSIPs(t testing.TB) {
},
}
Context.tls = &tlsManager{}
var err error
Context.tls, err = newTLSManager(&tlsConfiguration{
Enabled: true,
})
require.NoError(t, err)
}
func TestHandleMobileConfigDoH(t *testing.T) {
@@ -65,7 +69,11 @@ func TestHandleMobileConfigDoH(t *testing.T) {
oldTLSConf := Context.tls
t.Cleanup(func() { Context.tls = oldTLSConf })
Context.tls = &tlsManager{conf: tlsConfigSettings{}}
var err error
Context.tls, err = newTLSManager(&tlsConfiguration{
Enabled: true,
})
require.NoError(t, err)
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil)
require.NoError(t, err)
@@ -137,7 +145,11 @@ func TestHandleMobileConfigDoT(t *testing.T) {
oldTLSConf := Context.tls
t.Cleanup(func() { Context.tls = oldTLSConf })
Context.tls = &tlsManager{conf: tlsConfigSettings{}}
var err error
Context.tls, err = newTLSManager(&tlsConfiguration{
Enabled: true,
})
require.NoError(t, err)
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil)
require.NoError(t, err)

View File

@@ -47,6 +47,9 @@ type options struct {
// disableUpdate, if set, makes AdGuard Home not check for updates.
disableUpdate bool
// performUpdate, if set, updates AdGuard Home without GUI and exits.
performUpdate bool
// verbose shows if verbose logging is enabled.
verbose bool
@@ -221,6 +224,14 @@ var cmdLineOpts = []cmdLineOpt{{
description: "Don't check for updates.",
longName: "no-check-update",
shortName: "",
}, {
updateWithValue: nil,
updateNoValue: func(o options) (options, error) { o.performUpdate = true; return o, nil },
effect: nil,
serialize: func(o options) (val string, ok bool) { return "", o.performUpdate },
description: "Update application and exit.",
longName: "update",
shortName: "",
}, {
updateWithValue: nil,
updateNoValue: nil,

View File

@@ -103,6 +103,11 @@ func TestParseDisableUpdate(t *testing.T) {
assert.True(t, testParseOK(t, "--no-check-update").disableUpdate, "--no-check-update is disable update")
}
func TestParsePerformUpdate(t *testing.T) {
assert.False(t, testParseOK(t).performUpdate, "empty is not perform update")
assert.True(t, testParseOK(t, "--update").performUpdate, "--update is perform update")
}
// TODO(e.burkov): Remove after v0.108.0.
func TestParseDisableMemoryOptimization(t *testing.T) {
o, eff, err := parseCmdOpts("", []string{"--no-mem-optimization"})
@@ -169,6 +174,10 @@ func TestOptsToArgs(t *testing.T) {
name: "disable_update",
args: []string{"--no-check-update"},
opts: options{disableUpdate: true},
}, {
name: "perform_update",
args: []string{"--update"},
opts: options{performUpdate: true},
}, {
name: "control_action",
args: []string{"-s", "run"},

View File

@@ -2,7 +2,7 @@ package home
import (
"encoding/binary"
"net"
"net/netip"
"sync/atomic"
"time"
@@ -21,7 +21,7 @@ type RDNS struct {
usePrivate uint32
// ipCh used to pass client's IP to rDNS workerLoop.
ipCh chan net.IP
ipCh chan netip.Addr
// ipCache caches the IP addresses to be resolved by rDNS. The resolved
// address stays here while it's inside clients. After leaving clients the
@@ -50,7 +50,7 @@ func NewRDNS(
EnableLRU: true,
MaxCount: defaultRDNSCacheSize,
}),
ipCh: make(chan net.IP, defaultRDNSIPChSize),
ipCh: make(chan netip.Addr, defaultRDNSIPChSize),
}
if usePrivate {
rDNS.usePrivate = 1
@@ -80,9 +80,10 @@ func (r *RDNS) ensurePrivateCache() {
// isCached returns true if ip is already cached and not expired yet. It also
// caches it otherwise.
func (r *RDNS) isCached(ip net.IP) (ok bool) {
func (r *RDNS) isCached(ip netip.Addr) (ok bool) {
ipBytes := ip.AsSlice()
now := uint64(time.Now().Unix())
if expire := r.ipCache.Get(ip); len(expire) != 0 {
if expire := r.ipCache.Get(ipBytes); len(expire) != 0 {
if binary.BigEndian.Uint64(expire) > now {
return true
}
@@ -91,25 +92,25 @@ func (r *RDNS) isCached(ip net.IP) (ok bool) {
// The cache entry either expired or doesn't exist.
ttl := make([]byte, 8)
binary.BigEndian.PutUint64(ttl, now+defaultRDNSCacheTTL)
r.ipCache.Set(ip, ttl)
r.ipCache.Set(ipBytes, ttl)
return false
}
// Begin adds the ip to the resolving queue if it is not cached or already
// resolved.
func (r *RDNS) Begin(ip net.IP) {
func (r *RDNS) Begin(ip netip.Addr) {
r.ensurePrivateCache()
if r.isCached(ip) || r.clients.Exists(ip, ClientSourceRDNS) {
if r.isCached(ip) || r.clients.exists(ip, ClientSourceRDNS) {
return
}
select {
case r.ipCh <- ip:
log.Tracef("rdns: %q added to queue", ip)
log.Debug("rdns: %q added to queue", ip)
default:
log.Tracef("rdns: queue is full")
log.Debug("rdns: queue is full")
}
}
@@ -119,7 +120,7 @@ func (r *RDNS) workerLoop() {
defer log.OnPanic("rdns")
for ip := range r.ipCh {
host, err := r.exchanger.Exchange(ip)
host, err := r.exchanger.Exchange(ip.AsSlice())
if err != nil {
log.Debug("rdns: resolving %q: %s", ip, err)
@@ -128,8 +129,6 @@ func (r *RDNS) workerLoop() {
continue
}
// Don't handle any errors since AddHost doesn't return non-nil errors
// for now.
_, _ = r.clients.AddHost(ip, host, ClientSourceRDNS)
_ = r.clients.AddHost(ip, host, ClientSourceRDNS)
}
}

View File

@@ -5,6 +5,7 @@ import (
"encoding/binary"
"fmt"
"net"
"net/netip"
"sync"
"testing"
"time"
@@ -26,14 +27,14 @@ func TestRDNS_Begin(t *testing.T) {
w := &bytes.Buffer{}
aghtest.ReplaceLogWriter(t, w)
ip1234, ip1235 := net.IP{1, 2, 3, 4}, net.IP{1, 2, 3, 5}
ip1234, ip1235 := netip.MustParseAddr("1.2.3.4"), netip.MustParseAddr("1.2.3.5")
testCases := []struct {
cliIDIndex map[string]*Client
customChan chan net.IP
customChan chan netip.Addr
name string
wantLog string
req net.IP
ip netip.Addr
wantCacheHit int
wantCacheMiss int
}{{
@@ -41,7 +42,7 @@ func TestRDNS_Begin(t *testing.T) {
customChan: nil,
name: "cached",
wantLog: "",
req: ip1234,
ip: ip1234,
wantCacheHit: 1,
wantCacheMiss: 0,
}, {
@@ -49,7 +50,7 @@ func TestRDNS_Begin(t *testing.T) {
customChan: nil,
name: "not_cached",
wantLog: "rdns: queue is full",
req: ip1235,
ip: ip1235,
wantCacheHit: 0,
wantCacheMiss: 1,
}, {
@@ -57,15 +58,15 @@ func TestRDNS_Begin(t *testing.T) {
customChan: nil,
name: "already_in_clients",
wantLog: "",
req: ip1235,
ip: ip1235,
wantCacheHit: 0,
wantCacheMiss: 1,
}, {
cliIDIndex: map[string]*Client{},
customChan: make(chan net.IP, 1),
customChan: make(chan netip.Addr, 1),
name: "add_to_queue",
wantLog: `rdns: "1.2.3.5" added to queue`,
req: ip1235,
ip: ip1235,
wantCacheHit: 0,
wantCacheMiss: 1,
}}
@@ -88,7 +89,7 @@ func TestRDNS_Begin(t *testing.T) {
clients: &clientsContainer{
list: map[string]*Client{},
idIndex: tc.cliIDIndex,
ipToRC: netutil.NewIPMap(0),
ipToRC: map[netip.Addr]*RuntimeClient{},
allTags: stringutil.NewSet(),
},
}
@@ -101,7 +102,7 @@ func TestRDNS_Begin(t *testing.T) {
}
t.Run(tc.name, func(t *testing.T) {
rdns.Begin(tc.req)
rdns.Begin(tc.ip)
assert.Equal(t, tc.wantCacheHit, ipCache.Stats().Hit)
assert.Equal(t, tc.wantCacheMiss, ipCache.Stats().Miss)
assert.Contains(t, w.String(), tc.wantLog)
@@ -178,8 +179,8 @@ func TestRDNS_WorkerLoop(t *testing.T) {
w := &bytes.Buffer{}
aghtest.ReplaceLogWriter(t, w)
localIP := net.IP{192, 168, 1, 1}
revIPv4, err := netutil.IPToReversedAddr(localIP)
localIP := netip.MustParseAddr("192.168.1.1")
revIPv4, err := netutil.IPToReversedAddr(localIP.AsSlice())
require.NoError(t, err)
revIPv6, err := netutil.IPToReversedAddr(net.ParseIP("2a00:1450:400c:c06::93"))
@@ -188,13 +189,11 @@ func TestRDNS_WorkerLoop(t *testing.T) {
locUpstream := &aghtest.UpstreamMock{
OnAddress: func() (addr string) { return "local.upstream.example" },
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
resp = aghalg.Coalesce(
aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revIPv4, "local.domain"),
aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revIPv6, "ipv6.domain"),
return aghalg.Coalesce(
aghtest.MatchedResponse(req, dns.TypePTR, revIPv4, "local.domain"),
aghtest.MatchedResponse(req, dns.TypePTR, revIPv6, "ipv6.domain"),
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
)
return resp, nil
), nil
},
}
@@ -202,24 +201,24 @@ func TestRDNS_WorkerLoop(t *testing.T) {
testCases := []struct {
ups upstream.Upstream
cliIP netip.Addr
wantLog string
name string
cliIP net.IP
}{{
ups: locUpstream,
cliIP: localIP,
wantLog: "",
name: "all_good",
cliIP: localIP,
}, {
ups: errUpstream,
cliIP: netip.MustParseAddr("192.168.1.2"),
wantLog: `rdns: resolving "192.168.1.2": test upstream error`,
name: "resolve_error",
cliIP: net.IP{192, 168, 1, 2},
}, {
ups: locUpstream,
cliIP: netip.MustParseAddr("2a00:1450:400c:c06::93"),
wantLog: "",
name: "ipv6_good",
cliIP: net.ParseIP("2a00:1450:400c:c06::93"),
}}
for _, tc := range testCases {
@@ -228,10 +227,10 @@ func TestRDNS_WorkerLoop(t *testing.T) {
cc := &clientsContainer{
list: map[string]*Client{},
idIndex: map[string]*Client{},
ipToRC: netutil.NewIPMap(0),
ipToRC: map[netip.Addr]*RuntimeClient{},
allTags: stringutil.NewSet(),
}
ch := make(chan net.IP)
ch := make(chan netip.Addr)
rdns := &RDNS{
exchanger: &rDNSExchanger{
ex: tc.ups,
@@ -258,7 +257,7 @@ func TestRDNS_WorkerLoop(t *testing.T) {
return
}
assert.True(t, cc.Exists(tc.cliIP, ClientSourceRDNS))
assert.True(t, cc.exists(tc.cliIP, ClientSourceRDNS))
})
}
}

View File

@@ -8,41 +8,39 @@ import (
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/google/go-cmp/cmp"
)
// tlsManager contains the current configuration and state of AdGuard Home TLS
// encryption.
type tlsManager struct {
// status is the current status of the configuration. It is never nil.
status *tlsConfigStatus
// mu protects all fields.
mu *sync.RWMutex
// certLastMod is the last modification time of the certificate file.
certLastMod time.Time
confLock sync.Mutex
conf tlsConfigSettings
// status is the current status of the configuration. It is never nil.
status *tlsConfigStatus
// conf is the current TLS configuration.
conf *tlsConfiguration
}
// newTLSManager initializes the TLS configuration.
func newTLSManager(conf tlsConfigSettings) (m *tlsManager, err error) {
func newTLSManager(conf *tlsConfiguration) (m *tlsManager, err error) {
m = &tlsManager{
status: &tlsConfigStatus{},
mu: &sync.RWMutex{},
conf: conf,
}
@@ -58,9 +56,19 @@ func newTLSManager(conf tlsConfigSettings) (m *tlsManager, err error) {
return m, nil
}
// confForEncoding returns a partial clone of the current TLS configuration. It
// is safe for concurrent use.
func (m *tlsManager) confForEncoding() (conf *tlsConfiguration) {
m.mu.RLock()
defer m.mu.RUnlock()
return m.conf.cloneForEncoding()
}
// load reloads the TLS configuration from files or data from the config file.
// m.mu is expected to be locked for writing.
func (m *tlsManager) load() (err error) {
err = loadTLSConf(&m.conf, m.status)
err = loadTLSConf(m.conf, m.status)
if err != nil {
return fmt.Errorf("loading config: %w", err)
}
@@ -69,14 +77,12 @@ func (m *tlsManager) load() (err error) {
}
// WriteDiskConfig - write config
func (m *tlsManager) WriteDiskConfig(conf *tlsConfigSettings) {
m.confLock.Lock()
*conf = m.conf
m.confLock.Unlock()
func (m *tlsManager) WriteDiskConfig(conf *tlsConfiguration) {
*conf = *m.confForEncoding()
}
// setCertFileTime sets t.certLastMod from the certificate. If there are
// errors, setCertFileTime logs them.
// errors, setCertFileTime logs them. mu is expected to be locked for writing.
func (m *tlsManager) setCertFileTime() {
if len(m.conf.CertificatePath) == 0 {
return
@@ -96,27 +102,22 @@ func (m *tlsManager) setCertFileTime() {
func (m *tlsManager) start() {
m.registerWebHandlers()
m.confLock.Lock()
tlsConf := m.conf
m.confLock.Unlock()
// The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current
// request.
Context.web.TLSConfigChanged(context.Background(), tlsConf)
Context.web.TLSConfigChanged(context.Background(), m.confForEncoding())
}
// reload updates the configuration and restarts t.
// reload updates the configuration and restarts m.
func (m *tlsManager) reload() {
m.confLock.Lock()
tlsConf := m.conf
m.confLock.Unlock()
m.mu.Lock()
defer m.mu.Unlock()
if !tlsConf.Enabled || len(tlsConf.CertificatePath) == 0 {
if !m.conf.Enabled || len(m.conf.CertificatePath) == 0 {
return
}
fi, err := os.Stat(tlsConf.CertificatePath)
fi, err := os.Stat(m.conf.CertificatePath)
if err != nil {
log.Error("tls: %s", err)
@@ -131,9 +132,7 @@ func (m *tlsManager) reload() {
log.Debug("tls: certificate file is modified")
m.confLock.Lock()
err = m.load()
m.confLock.Unlock()
if err != nil {
log.Error("tls: reloading: %s", err)
@@ -144,22 +143,22 @@ func (m *tlsManager) reload() {
_ = reconfigureDNSServer()
m.confLock.Lock()
tlsConf = m.conf
m.confLock.Unlock()
// The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current
// request.
Context.web.TLSConfigChanged(context.Background(), tlsConf)
Context.web.TLSConfigChanged(context.Background(), m.conf)
}
// loadTLSConf loads and validates the TLS configuration. The returned error is
// also set in status.WarningValidation.
func loadTLSConf(tlsConf *tlsConfigSettings, status *tlsConfigStatus) (err error) {
func loadTLSConf(tlsConf *tlsConfiguration, status *tlsConfigStatus) (err error) {
defer func() {
if err != nil {
status.WarningValidation = err.Error()
if status.ValidCert && status.ValidKey && status.ValidPair {
// Do not return warnings since those aren't critical.
err = nil
}
}
}()
@@ -167,26 +166,22 @@ func loadTLSConf(tlsConf *tlsConfigSettings, status *tlsConfigStatus) (err error
tlsConf.PrivateKeyData = []byte(tlsConf.PrivateKey)
if tlsConf.CertificatePath != "" {
if tlsConf.CertificateChain != "" {
return errors.Error("certificate data and file can't be set together")
}
tlsConf.CertificateChainData, err = os.ReadFile(tlsConf.CertificatePath)
err = loadCert(tlsConf)
if err != nil {
return fmt.Errorf("reading cert file: %w", err)
// Don't wrap the error, since it's informative enough as is.
return err
}
// Set status.ValidCert to true to signal the frontend that the
// certificate opens successfully while the private key can't be opened.
status.ValidCert = true
}
if tlsConf.PrivateKeyPath != "" {
if tlsConf.PrivateKey != "" {
return errors.Error("private key data and file can't be set together")
}
tlsConf.PrivateKeyData, err = os.ReadFile(tlsConf.PrivateKeyPath)
err = loadPKey(tlsConf)
if err != nil {
return fmt.Errorf("reading key file: %w", err)
// Don't wrap the error, since it's informative enough as is.
return err
}
status.ValidKey = true
@@ -205,350 +200,91 @@ func loadTLSConf(tlsConf *tlsConfigSettings, status *tlsConfigStatus) (err error
return nil
}
// tlsConfigStatus contains the status of a certificate chain and key pair.
type tlsConfigStatus struct {
// Subject is the subject of the first certificate in the chain.
Subject string `json:"subject,omitempty"`
// Issuer is the issuer of the first certificate in the chain.
Issuer string `json:"issuer,omitempty"`
// KeyType is the type of the private key.
KeyType string `json:"key_type,omitempty"`
// NotBefore is the NotBefore field of the first certificate in the chain.
NotBefore time.Time `json:"not_before,omitempty"`
// NotAfter is the NotAfter field of the first certificate in the chain.
NotAfter time.Time `json:"not_after,omitempty"`
// WarningValidation is a validation warning message with the issue
// description.
WarningValidation string `json:"warning_validation,omitempty"`
// DNSNames is the value of SubjectAltNames field of the first certificate
// in the chain.
DNSNames []string `json:"dns_names"`
// ValidCert is true if the specified certificate chain is a valid chain of
// X509 certificates.
ValidCert bool `json:"valid_cert"`
// ValidChain is true if the specified certificate chain is verified and
// issued by a known CA.
ValidChain bool `json:"valid_chain"`
// ValidKey is true if the key is a valid private key.
ValidKey bool `json:"valid_key"`
// ValidPair is true if both certificate and private key are correct for
// each other.
ValidPair bool `json:"valid_pair"`
}
// tlsConfig is the TLS configuration and status response.
type tlsConfig struct {
*tlsConfigStatus `json:",inline"`
tlsConfigSettingsExt `json:",inline"`
}
// tlsConfigSettingsExt is used to (un)marshal the PrivateKeySaved field to
// ensure that clients don't send and receive previously saved private keys.
type tlsConfigSettingsExt struct {
tlsConfigSettings `json:",inline"`
// PrivateKeySaved is true if the private key is saved as a string and omit
// key from answer.
PrivateKeySaved bool `yaml:"-" json:"private_key_saved,inline"`
}
func (m *tlsManager) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
m.confLock.Lock()
data := tlsConfig{
tlsConfigSettingsExt: tlsConfigSettingsExt{
tlsConfigSettings: m.conf,
},
tlsConfigStatus: m.status,
// loadCert loads the certificate from file, if necessary.
func loadCert(tlsConf *tlsConfiguration) (err error) {
if tlsConf.CertificateChain != "" {
return errors.Error("certificate data and file can't be set together")
}
m.confLock.Unlock()
marshalTLS(w, r, data)
}
func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
setts, err := unmarshalTLS(r)
tlsConf.CertificateChainData, err = os.ReadFile(tlsConf.CertificatePath)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
}
if setts.PrivateKeySaved {
setts.PrivateKey = m.conf.PrivateKey
}
if setts.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(setts.PortHTTPS),
tcpPort(setts.PortDNSOverTLS),
tcpPort(setts.PortDNSCrypt),
udpPort(config.DNS.Port),
udpPort(setts.PortDNSOverQUIC),
)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
if !webCheckPortAvailable(setts.PortHTTPS) {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable HTTPS on it",
setts.PortHTTPS,
)
return
}
// Skip the error check, since we are only interested in the value of
// status.WarningValidation.
status := &tlsConfigStatus{}
_ = loadTLSConf(&setts.tlsConfigSettings, status)
resp := tlsConfig{
tlsConfigSettingsExt: setts,
tlsConfigStatus: status,
}
marshalTLS(w, r, resp)
}
func (m *tlsManager) setConfig(newConf tlsConfigSettings, status *tlsConfigStatus) (restartHTTPS bool) {
m.confLock.Lock()
defer m.confLock.Unlock()
// Reset the DNSCrypt data before comparing, since we currently do not
// accept these from the frontend.
//
// TODO(a.garipov): Define a custom comparer for dnsforward.TLSConfig.
newConf.DNSCryptConfigFile = m.conf.DNSCryptConfigFile
newConf.PortDNSCrypt = m.conf.PortDNSCrypt
if !cmp.Equal(m.conf, newConf, cmp.AllowUnexported(dnsforward.TLSConfig{})) {
log.Info("tls config has changed, restarting https server")
restartHTTPS = true
} else {
log.Info("tls: config has not changed")
}
// Note: don't do just `t.conf = data` because we must preserve all other members of t.conf
m.conf.Enabled = newConf.Enabled
m.conf.ServerName = newConf.ServerName
m.conf.ForceHTTPS = newConf.ForceHTTPS
m.conf.PortHTTPS = newConf.PortHTTPS
m.conf.PortDNSOverTLS = newConf.PortDNSOverTLS
m.conf.PortDNSOverQUIC = newConf.PortDNSOverQUIC
m.conf.CertificateChain = newConf.CertificateChain
m.conf.CertificatePath = newConf.CertificatePath
m.conf.CertificateChainData = newConf.CertificateChainData
m.conf.PrivateKey = newConf.PrivateKey
m.conf.PrivateKeyPath = newConf.PrivateKeyPath
m.conf.PrivateKeyData = newConf.PrivateKeyData
m.status = status
return restartHTTPS
}
func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
req, err := unmarshalTLS(r)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
}
if req.PrivateKeySaved {
req.PrivateKey = m.conf.PrivateKey
}
if req.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(req.PortHTTPS),
tcpPort(req.PortDNSOverTLS),
tcpPort(req.PortDNSCrypt),
udpPort(config.DNS.Port),
udpPort(req.PortDNSOverQUIC),
)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
// TODO(e.burkov): Investigate and perhaps check other ports.
if !webCheckPortAvailable(req.PortHTTPS) {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable https on it",
req.PortHTTPS,
)
return
}
status := &tlsConfigStatus{}
err = loadTLSConf(&req.tlsConfigSettings, status)
if err != nil {
resp := tlsConfig{
tlsConfigSettingsExt: req,
tlsConfigStatus: status,
}
marshalTLS(w, r, resp)
return
}
restartHTTPS := m.setConfig(req.tlsConfigSettings, status)
m.setCertFileTime()
onConfigModified()
err = reconfigureDNSServer()
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
return
}
resp := tlsConfig{
tlsConfigSettingsExt: req,
tlsConfigStatus: m.status,
}
marshalTLS(w, r, resp)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
// The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current
// request. It is also should be done in a separate goroutine due to the
// same reason.
if restartHTTPS {
go func() {
Context.web.TLSConfigChanged(context.Background(), req.tlsConfigSettings)
}()
}
}
// validatePorts validates the uniqueness of TCP and UDP ports for AdGuard Home
// DNS protocols.
func validatePorts(
bindPort, betaBindPort, dohPort, dotPort, dnscryptTCPPort tcpPort,
dnsPort, doqPort udpPort,
) (err error) {
tcpPorts := aghalg.UniqChecker[tcpPort]{}
addPorts(
tcpPorts,
tcpPort(bindPort),
tcpPort(betaBindPort),
tcpPort(dohPort),
tcpPort(dotPort),
tcpPort(dnscryptTCPPort),
)
err = tcpPorts.Validate()
if err != nil {
return fmt.Errorf("validating tcp ports: %w", err)
}
udpPorts := aghalg.UniqChecker[udpPort]{}
addPorts(udpPorts, udpPort(dnsPort), udpPort(doqPort))
err = udpPorts.Validate()
if err != nil {
return fmt.Errorf("validating udp ports: %w", err)
return fmt.Errorf("reading cert file: %w", err)
}
return nil
}
// validateCertChain validates the certificate chain and sets data in status.
// The returned error is also set in status.WarningValidation.
func validateCertChain(status *tlsConfigStatus, certChain []byte, serverName string) (err error) {
defer func() {
if err != nil {
status.WarningValidation = err.Error()
}
}()
log.Debug("tls: got certificate chain: %d bytes", len(certChain))
var certs []*pem.Block
pemblock := certChain
for {
var decoded *pem.Block
decoded, pemblock = pem.Decode(pemblock)
if decoded == nil {
break
}
if decoded.Type == "CERTIFICATE" {
certs = append(certs, decoded)
}
// loadPKey loads the private key from file, if necessary.
func loadPKey(tlsConf *tlsConfiguration) (err error) {
if tlsConf.PrivateKey != "" {
return errors.Error("private key data and file cannot be set together")
}
parsedCerts, err := parsePEMCerts(certs)
tlsConf.PrivateKeyData, err = os.ReadFile(tlsConf.PrivateKeyPath)
if err != nil {
return err
return fmt.Errorf("reading key file: %w", err)
}
status.ValidCert = true
return nil
}
opts := x509.VerifyOptions{
DNSName: serverName,
Roots: Context.tlsRoots,
}
log.Info("tls: number of certs: %d", len(parsedCerts))
// validateCertChain verifies certs using the first as the main one and others
// as intermediate. srvName stands for the expected DNS name.
func validateCertChain(certs []*x509.Certificate, srvName string) (err error) {
main, others := certs[0], certs[1:]
pool := x509.NewCertPool()
for _, cert := range parsedCerts[1:] {
for _, cert := range others {
log.Info("tls: got an intermediate cert")
pool.AddCert(cert)
}
opts.Intermediates = pool
mainCert := parsedCerts[0]
_, err = mainCert.Verify(opts)
if err != nil {
// Let self-signed certs through and don't return this error.
status.WarningValidation = fmt.Sprintf("certificate does not verify: %s", err)
} else {
status.ValidChain = true
opts := x509.VerifyOptions{
DNSName: srvName,
Roots: Context.tlsRoots,
Intermediates: pool,
}
if mainCert != nil {
status.Subject = mainCert.Subject.String()
status.Issuer = mainCert.Issuer.String()
status.NotAfter = mainCert.NotAfter
status.NotBefore = mainCert.NotBefore
status.DNSNames = mainCert.DNSNames
_, err = main.Verify(opts)
if err != nil {
return fmt.Errorf("certificate does not verify: %w", err)
}
return nil
}
// errNoIPInCert is the error that is returned from [parseCertChain] if the leaf
// certificate doesn't contain IPs.
const errNoIPInCert errors.Error = `certificates has no IP addresses; ` +
`DNS-over-TLS won't be advertised via DDR`
// parseCertChain parses the certificate chain from raw data, and returns it.
// If ok is true, the returned error, if any, is not critical.
func parseCertChain(chain []byte) (parsedCerts []*x509.Certificate, ok bool, err error) {
log.Debug("tls: got certificate chain: %d bytes", len(chain))
var certs []*pem.Block
for decoded, pemblock := pem.Decode(chain); decoded != nil; {
if decoded.Type == "CERTIFICATE" {
certs = append(certs, decoded)
}
decoded, pemblock = pem.Decode(pemblock)
}
parsedCerts, err = parsePEMCerts(certs)
if err != nil {
return nil, false, err
}
log.Info("tls: number of certs: %d", len(parsedCerts))
if !aghtls.CertificateHasIP(parsedCerts[0]) {
err = errNoIPInCert
}
return parsedCerts, true, err
}
// parsePEMCerts parses multiple PEM-encoded certificates.
func parsePEMCerts(certs []*pem.Block) (parsedCerts []*x509.Certificate, err error) {
for i, cert := range certs {
@@ -568,106 +304,99 @@ func parsePEMCerts(certs []*pem.Block) (parsedCerts []*x509.Certificate, err err
return parsedCerts, nil
}
// validatePKey validates the private key and sets data in status. The returned
// error is also set in status.WarningValidation.
func validatePKey(status *tlsConfigStatus, pkey []byte) (err error) {
defer func() {
if err != nil {
status.WarningValidation = err.Error()
}
}()
// validatePKey validates the private key, returning its type. It returns an
// empty string if error occurs.
func validatePKey(pkey []byte) (keyType string, err error) {
var key *pem.Block
// Go through all pem blocks, but take first valid pem block and drop the
// rest.
pemblock := []byte(pkey)
for {
var decoded *pem.Block
decoded, pemblock = pem.Decode(pemblock)
if decoded == nil {
break
}
for decoded, pemblock := pem.Decode([]byte(pkey)); decoded != nil; {
if decoded.Type == "PRIVATE KEY" || strings.HasSuffix(decoded.Type, " PRIVATE KEY") {
key = decoded
break
}
decoded, pemblock = pem.Decode(pemblock)
}
if key == nil {
return errors.Error("no valid keys were found")
return "", errors.Error("no valid keys were found")
}
_, keyType, err := parsePrivateKey(key.Bytes)
_, keyType, err = parsePrivateKey(key.Bytes)
if err != nil {
return fmt.Errorf("parsing private key: %w", err)
return "", fmt.Errorf("parsing private key: %w", err)
}
if keyType == keyTypeED25519 {
return errors.Error(
return "", errors.Error(
"ED25519 keys are not supported by browsers; " +
"did you mean to use X25519 for key exchange?",
)
}
status.ValidKey = true
status.KeyType = keyType
return nil
return keyType, nil
}
// validateCertificates processes certificate data and its private key. All
// parameters are optional. status must not be nil. The returned error is also
// set in status.WarningValidation.
// validateCertificates processes certificate data and its private key. status
// must not be nil, since it's used to accumulate the validation results. Other
// parameters are optional.
func validateCertificates(
status *tlsConfigStatus,
certChain []byte,
pkey []byte,
serverName string,
) (err error) {
defer func() {
// Capitalize the warning for the UI. Assume that warnings are all
// ASCII-only.
//
// TODO(a.garipov): Figure out a better way to do this. Perhaps a
// custom string or error type.
if w := status.WarningValidation; w != "" {
status.WarningValidation = strings.ToUpper(w[:1]) + w[1:]
}
}()
// Check only the public certificate separately from the key.
if len(certChain) > 0 {
err = validateCertChain(status, certChain, serverName)
if err != nil {
var certs []*x509.Certificate
certs, status.ValidCert, err = parseCertChain(certChain)
if !status.ValidCert {
// Don't wrap the error, since it's informative enough as is.
return err
}
mainCert := certs[0]
status.Subject = mainCert.Subject.String()
status.Issuer = mainCert.Issuer.String()
status.NotAfter = mainCert.NotAfter
status.NotBefore = mainCert.NotBefore
status.DNSNames = mainCert.DNSNames
if chainErr := validateCertChain(certs, serverName); chainErr != nil {
// Let self-signed certs through and don't return this error to set
// its message into the status.WarningValidation afterwards.
err = chainErr
} else {
status.ValidChain = true
}
}
// Validate the private key by parsing it.
if len(pkey) > 0 {
err = validatePKey(status, pkey)
if err != nil {
return err
var keyErr error
status.KeyType, keyErr = validatePKey(pkey)
if keyErr != nil {
// Don't wrap the error, since it's informative enough as is.
return keyErr
}
status.ValidKey = true
}
// If both are set, validate together.
if len(certChain) > 0 && len(pkey) > 0 {
_, err = tls.X509KeyPair(certChain, pkey)
if err != nil {
err = fmt.Errorf("certificate-key pair: %w", err)
status.WarningValidation = err.Error()
return err
_, pairErr := tls.X509KeyPair(certChain, pkey)
if pairErr != nil {
return fmt.Errorf("certificate-key pair: %w", pairErr)
}
status.ValidPair = true
}
return nil
return err
}
// Key types.
@@ -710,61 +439,3 @@ func parsePrivateKey(der []byte) (key crypto.PrivateKey, typ string, err error)
return nil, "", errors.Error("tls: failed to parse private key")
}
// unmarshalTLS handles base64-encoded certificates transparently
func unmarshalTLS(r *http.Request) (tlsConfigSettingsExt, error) {
data := tlsConfigSettingsExt{}
err := json.NewDecoder(r.Body).Decode(&data)
if err != nil {
return data, fmt.Errorf("failed to parse new TLS config json: %w", err)
}
if data.CertificateChain != "" {
var cert []byte
cert, err = base64.StdEncoding.DecodeString(data.CertificateChain)
if err != nil {
return data, fmt.Errorf("failed to base64-decode certificate chain: %w", err)
}
data.CertificateChain = string(cert)
if data.CertificatePath != "" {
return data, fmt.Errorf("certificate data and file can't be set together")
}
}
if data.PrivateKey != "" {
var key []byte
key, err = base64.StdEncoding.DecodeString(data.PrivateKey)
if err != nil {
return data, fmt.Errorf("failed to base64-decode private key: %w", err)
}
data.PrivateKey = string(key)
if data.PrivateKeyPath != "" {
return data, fmt.Errorf("private key data and file can't be set together")
}
}
return data, nil
}
func marshalTLS(w http.ResponseWriter, r *http.Request, data tlsConfig) {
if data.CertificateChain != "" {
encoded := base64.StdEncoding.EncodeToString([]byte(data.CertificateChain))
data.CertificateChain = encoded
}
if data.PrivateKey != "" {
data.PrivateKeySaved = true
data.PrivateKey = ""
}
_ = aghhttp.WriteJSONResponse(w, r, data)
}
// registerWebHandlers registers HTTP handlers for TLS configuration.
func (m *tlsManager) registerWebHandlers() {
httpRegister(http.MethodGet, "/control/tls/status", m.handleTLSStatus)
httpRegister(http.MethodPost, "/control/tls/configure", m.handleTLSConfigure)
httpRegister(http.MethodPost, "/control/tls/validate", m.handleTLSValidate)
}

View File

@@ -4,6 +4,7 @@ import (
"testing"
"time"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
)
@@ -43,8 +44,7 @@ func TestValidateCertificates(t *testing.T) {
t.Run("bad_certificate", func(t *testing.T) {
status := &tlsConfigStatus{}
err := validateCertificates(status, []byte("bad cert"), nil, "")
assert.Error(t, err)
assert.NotEmpty(t, status.WarningValidation)
testutil.AssertErrorMsg(t, "empty certificate", err)
assert.False(t, status.ValidCert)
assert.False(t, status.ValidChain)
})
@@ -52,20 +52,18 @@ func TestValidateCertificates(t *testing.T) {
t.Run("bad_private_key", func(t *testing.T) {
status := &tlsConfigStatus{}
err := validateCertificates(status, nil, []byte("bad priv key"), "")
assert.Error(t, err)
assert.NotEmpty(t, status.WarningValidation)
testutil.AssertErrorMsg(t, "no valid keys were found", err)
assert.False(t, status.ValidKey)
})
t.Run("valid", func(t *testing.T) {
status := &tlsConfigStatus{}
err := validateCertificates(status, testCertChainData, testPrivateKeyData, "")
assert.NoError(t, err)
assert.Error(t, err)
notBefore := time.Date(2019, 2, 27, 9, 24, 23, 0, time.UTC)
notAfter := time.Date(2046, 7, 14, 9, 24, 23, 0, time.UTC)
assert.NotEmpty(t, status.WarningValidation)
assert.True(t, status.ValidCert)
assert.False(t, status.ValidChain)
assert.True(t, status.ValidKey)

362
internal/home/tlshttp.go Normal file
View File

@@ -0,0 +1,362 @@
package home
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/golibs/log"
"github.com/google/go-cmp/cmp"
)
// Encryption Settings HTTP API
// tlsConfigStatus contains the status of a certificate chain and key pair.
type tlsConfigStatus struct {
// Subject is the subject of the first certificate in the chain.
Subject string `json:"subject,omitempty"`
// Issuer is the issuer of the first certificate in the chain.
Issuer string `json:"issuer,omitempty"`
// KeyType is the type of the private key.
KeyType string `json:"key_type,omitempty"`
// NotBefore is the NotBefore field of the first certificate in the chain.
NotBefore time.Time `json:"not_before,omitempty"`
// NotAfter is the NotAfter field of the first certificate in the chain.
NotAfter time.Time `json:"not_after,omitempty"`
// WarningValidation is a validation warning message with the issue
// description.
WarningValidation string `json:"warning_validation,omitempty"`
// DNSNames is the value of SubjectAltNames field of the first certificate
// in the chain.
DNSNames []string `json:"dns_names"`
// ValidCert is true if the specified certificate chain is a valid chain of
// X509 certificates.
ValidCert bool `json:"valid_cert"`
// ValidChain is true if the specified certificate chain is verified and
// issued by a known CA.
ValidChain bool `json:"valid_chain"`
// ValidKey is true if the key is a valid private key.
ValidKey bool `json:"valid_key"`
// ValidPair is true if both certificate and private key are correct for
// each other.
ValidPair bool `json:"valid_pair"`
}
// tlsConfigResp is the TLS configuration and status response.
type tlsConfigResp struct {
*tlsConfigStatus
*tlsConfiguration
// PrivateKeySaved is true if the private key is saved as a string and omit
// key from answer.
PrivateKeySaved bool `yaml:"-" json:"private_key_saved"`
}
// tlsConfigReq is the TLS configuration request.
type tlsConfigReq struct {
tlsConfiguration
// PrivateKeySaved is true if the private key is saved as a string and omit
// key from answer.
PrivateKeySaved bool `yaml:"-" json:"private_key_saved"`
}
// handleTLSStatus is the handler for the GET /control/tls/status HTTP API.
func (m *tlsManager) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
var resp *tlsConfigResp
func() {
m.mu.RLock()
defer m.mu.RUnlock()
resp = &tlsConfigResp{
tlsConfigStatus: m.status,
tlsConfiguration: m.conf.cloneForEncoding(),
}
}()
marshalTLS(w, r, resp)
}
// handleTLSValidate is the handler for the POST /control/tls/validate HTTP API.
func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
req, err := unmarshalTLS(r)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
}
if req.PrivateKeySaved {
req.PrivateKey = m.confForEncoding().PrivateKey
}
if req.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(req.PortHTTPS),
tcpPort(req.PortDNSOverTLS),
tcpPort(req.PortDNSCrypt),
udpPort(config.DNS.Port),
udpPort(req.PortDNSOverQUIC),
)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
if !webCheckPortAvailable(req.PortHTTPS) {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable HTTPS on it",
req.PortHTTPS,
)
return
}
resp := &tlsConfigResp{
tlsConfigStatus: &tlsConfigStatus{},
tlsConfiguration: &req.tlsConfiguration,
}
// Skip the error check, since we are only interested in the value of
// resl.tlsConfigStatus.WarningValidation.
_ = loadTLSConf(resp.tlsConfiguration, resp.tlsConfigStatus)
marshalTLS(w, r, resp)
}
// validatePorts validates the uniqueness of TCP and UDP ports for AdGuard Home
// DNS protocols.
func validatePorts(
bindPort, betaBindPort, dohPort, dotPort, dnscryptTCPPort tcpPort,
dnsPort, doqPort udpPort,
) (err error) {
tcpPorts := aghalg.UniqChecker[tcpPort]{}
addPorts(
tcpPorts,
tcpPort(bindPort),
tcpPort(betaBindPort),
tcpPort(dohPort),
tcpPort(dotPort),
tcpPort(dnscryptTCPPort),
)
err = tcpPorts.Validate()
if err != nil {
return fmt.Errorf("validating tcp ports: %w", err)
}
udpPorts := aghalg.UniqChecker[udpPort]{}
addPorts(udpPorts, udpPort(dnsPort), udpPort(doqPort))
err = udpPorts.Validate()
if err != nil {
return fmt.Errorf("validating udp ports: %w", err)
}
return nil
}
// handleTLSConfigure is the handler for the POST /control/tls/configure HTTP
// API.
func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
req, err := unmarshalTLS(r)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
}
if req.PrivateKeySaved {
req.PrivateKey = m.confForEncoding().PrivateKey
}
if req.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(req.PortHTTPS),
tcpPort(req.PortDNSOverTLS),
tcpPort(req.PortDNSCrypt),
udpPort(config.DNS.Port),
udpPort(req.PortDNSOverQUIC),
)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
}
// TODO(e.burkov): Investigate and perhaps check other ports.
if !webCheckPortAvailable(req.PortHTTPS) {
aghhttp.Error(
r,
w,
http.StatusBadRequest,
"port %d is not available, cannot enable https on it",
req.PortHTTPS,
)
return
}
resp := &tlsConfigResp{
tlsConfigStatus: &tlsConfigStatus{},
tlsConfiguration: &req.tlsConfiguration,
}
err = loadTLSConf(resp.tlsConfiguration, resp.tlsConfigStatus)
if err != nil {
marshalTLS(w, r, resp)
return
}
restartRequired := m.setConf(resp)
onConfigModified()
err = reconfigureDNSServer()
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
return
}
resp.tlsConfiguration = m.confForEncoding()
marshalTLS(w, r, resp)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
// The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current
// request. It is also should be done in a separate goroutine due to the
// same reason.
if restartRequired {
go func() {
Context.web.TLSConfigChanged(context.Background(), resp.tlsConfiguration)
}()
}
}
// setConf sets the necessary values from the new configuration.
func (m *tlsManager) setConf(newConf *tlsConfigResp) (restartRequired bool) {
m.mu.Lock()
defer m.mu.Unlock()
// Reset the DNSCrypt data before comparing, since we currently do not
// accept these from the frontend.
//
// TODO(a.garipov): Define a custom comparer for dnsforward.TLSConfig.
newConf.DNSCryptConfigFile = m.conf.DNSCryptConfigFile
newConf.PortDNSCrypt = m.conf.PortDNSCrypt
if !cmp.Equal(m.conf, newConf, cmp.AllowUnexported(dnsforward.TLSConfig{})) {
log.Info("tls: config has changed, restarting https server")
restartRequired = true
} else {
log.Info("tls: config has not changed")
}
// Do not just write "m.conf = *newConf.tlsConfiguration", because all other
// members of m.conf must be preserved.
m.conf.Enabled = newConf.Enabled
m.conf.ServerName = newConf.ServerName
m.conf.ForceHTTPS = newConf.ForceHTTPS
m.conf.PortHTTPS = newConf.PortHTTPS
m.conf.PortDNSOverTLS = newConf.PortDNSOverTLS
m.conf.PortDNSOverQUIC = newConf.PortDNSOverQUIC
m.conf.CertificateChain = newConf.CertificateChain
m.conf.CertificatePath = newConf.CertificatePath
m.conf.CertificateChainData = newConf.CertificateChainData
m.conf.PrivateKey = newConf.PrivateKey
m.conf.PrivateKeyPath = newConf.PrivateKeyPath
m.conf.PrivateKeyData = newConf.PrivateKeyData
m.setCertFileTime()
m.status = newConf.tlsConfigStatus
return restartRequired
}
// marshalTLS handles Base64-encoded certificates transparently.
func marshalTLS(w http.ResponseWriter, r *http.Request, conf *tlsConfigResp) {
if conf.CertificateChain != "" {
encoded := base64.StdEncoding.EncodeToString([]byte(conf.CertificateChain))
conf.CertificateChain = encoded
}
if conf.PrivateKey != "" {
conf.PrivateKeySaved = true
conf.PrivateKey = ""
}
_ = aghhttp.WriteJSONResponse(w, r, conf)
}
// unmarshalTLS handles Base64-encoded certificates transparently.
func unmarshalTLS(r *http.Request) (req *tlsConfigReq, err error) {
req = &tlsConfigReq{}
err = json.NewDecoder(r.Body).Decode(req)
if err != nil {
return nil, fmt.Errorf("parsing tls config: %w", err)
}
if req.CertificateChain != "" {
var cert []byte
cert, err = base64.StdEncoding.DecodeString(req.CertificateChain)
if err != nil {
return nil, fmt.Errorf("failed to base64-decode certificate chain: %w", err)
}
req.CertificateChain = string(cert)
if req.CertificatePath != "" {
return nil, fmt.Errorf("certificate data and file can't be set together")
}
}
if req.PrivateKey != "" {
var key []byte
key, err = base64.StdEncoding.DecodeString(req.PrivateKey)
if err != nil {
return nil, fmt.Errorf("failed to base64-decode private key: %w", err)
}
req.PrivateKey = string(key)
if req.PrivateKeyPath != "" {
return nil, fmt.Errorf("private key data and file can't be set together")
}
}
return req, nil
}
// registerWebHandlers registers HTTP handlers for TLS configuration.
func (m *tlsManager) registerWebHandlers() {
httpRegister(http.MethodGet, "/control/tls/status", m.handleTLSStatus)
httpRegister(http.MethodPost, "/control/tls/configure", m.handleTLSConfigure)
httpRegister(http.MethodPost, "/control/tls/validate", m.handleTLSValidate)
}

View File

@@ -143,7 +143,7 @@ func webCheckPortAvailable(port int) (ok bool) {
// TLSConfigChanged updates the TLS configuration and restarts the HTTPS server
// if necessary.
func (web *Web) TLSConfigChanged(ctx context.Context, tlsConf tlsConfigSettings) {
func (web *Web) TLSConfigChanged(ctx context.Context, tlsConf *tlsConfiguration) {
log.Debug("web: applying new tls configuration")
web.conf.PortHTTPS = tlsConf.PortHTTPS
web.forceHTTPS = (tlsConf.ForceHTTPS && tlsConf.Enabled && tlsConf.PortHTTPS != 0)

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