Compare commits

..

41 Commits

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

Squashed commit of the following:

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

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

Squashed commit of the following:

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

    all: docs

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

    all: imp code

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

    all: imp code

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

    all: docs

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

    dnsforward: https blocked rcode

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

    dnsforward: imp tests

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

    dnsforward: imp code

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

    all: imp code

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

    dnsforward: https filtering

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

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

    # Conflicts:
    #	CHANGELOG.md

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

    dnsforward: imp tests

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

    dnsforward: https filtering

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

    docs: https filtering

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

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

Squashed commit of the following:

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

    Merge branch 'master' into 1453-stats-tests

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

    stats: imp code

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

    Merge branch 'master' into 1453-stats-tests

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

    client: add top upstreams and average processing time tables

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

    stats: imp docs

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

    stats: imp code

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

    all: imp docs

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

    all: imp code

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

    Merge branch 'master' into 1453-stats-tests

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

    stats: imp code

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

    Merge branch 'master' into 1453-stats-tests

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

    all: imp code

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

    all: imp docs

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

    all: imp naming

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

    all: add upstreams avg processing time

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

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

Squashed commit of the following:

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

    all: log changes

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

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

Squashed commit of the following:

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

    all: imp chlog

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

    all: upd chlog

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

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

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

    Merge branch 'master' into pprof-conf

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

    all: docs

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

    home: imp code

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

    all: docs

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

    all: docs

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

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

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

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

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

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

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

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

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

    client: imp addrproc test

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

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

Squashed commit of the following:

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

    Added bsd

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

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

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

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

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

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

Squashed commit of the following:

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

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

Updates #6046.

Squashed commit of the following:

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

    all: log changes

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

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

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

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

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

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

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

    Merge branch 'master' into imp-filter-upd

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

    all: imp code; use renameio/v2 consistently

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

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

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

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

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

    all: imp docs

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

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

Squashed commit of the following:

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

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

Squashed commit of the following:

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

    dnsforward: rm redundant precaution

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

    all: revise the addr proc crutch

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

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

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

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

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

    scripts: imp docs

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

    scripts: imp code

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

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

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

    next/websvc: fix pprof disabling

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

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

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

    aghnet: fix docs

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

    all: imp tests; upd golibs
2023-07-20 14:26:35 +03:00
Ainar Garipov
4e8d3d7628 Pull request 1932: upd-all
Squashed commit of the following:

commit cac6e9a9bc9a3ed631a3e3d2d2f36174e6c0c415
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 19 16:56:35 2023 +0300

    all: upd flts, i18n, svcs, tools, trackers
2023-07-19 17:17:03 +03:00
Ainar Garipov
685d982924 Pull request 1930: fewer-globals
Squashed commit of the following:

commit ce882cfff4c1f7afdf0cba13b39e6ee568eb812f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 19 15:56:52 2023 +0300

    all: imp code, lint

commit 96fc5c589e7474f4bba291b0a20a0834148bb9c1
Merge: 3e91eea6b b0185201c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 18 21:22:32 2023 +0300

    Merge branch 'master' into fewer-globals

commit 3e91eea6b68bac51251784e3069b1c9d241da439
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 18 19:01:45 2023 +0300

    home: rm globals
2023-07-19 16:57:57 +03:00
Ainar Garipov
b0185201c6 Pull request 1931: 6006-fix-cmdline-upd
Updates #6006.

Squashed commit of the following:

commit f974a08856b894fd586cfbba703d98dbcf8c6a97
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 18 20:51:58 2023 +0300

    home: fix cmdline update
2023-07-18 21:20:43 +03:00
Stanislav Chzhen
33ce24abe4 Pull request 1922: AG-23889-upd-dnsproxy
Merge in DNS/adguard-home from AG-23889-upd-dnsproxy to master

Squashed commit of the following:

commit ec61d4824946d28bf898d023d3321753273b7df3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jul 18 19:09:32 2023 +0300

    all: imp code

commit 271f1ca0e6e583c829519cb0b5b24ab070e08933
Merge: 684c5aedc dee7c0681
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jul 18 17:54:52 2023 +0300

    Merge branch 'master' into AG-23889-upd-dnsproxy

commit 684c5aedc7206578f89b80932999e714506d5ce0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jul 18 16:51:17 2023 +0300

    dnsforward: save prev proxy behavior

commit 9032c2179b941bec6d43b3e6bafdca5125a462b4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jul 17 17:45:10 2023 +0500

    dnsforward: use proxy ua

commit f658c031957fe45243e66a589ed32294e9aa4e27
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jul 17 17:37:03 2023 +0500

    dnsforward: fix private rdns ups conf for dns64

commit 70080e347dbc32cbdcb7d757514da13f865f8381
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 13 16:56:34 2023 +0300

    all: upd dnsproxy
2023-07-18 20:02:01 +03:00
Ainar Garipov
dee7c0681d Pull request 1929: fix-gh-tmpl
Squashed commit of the following:

commit 8ac1f14e422ad9a7cc0186e5ea18988613639240
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 18 17:04:36 2023 +0300

    .github: do not use md
2023-07-18 17:11:12 +03:00
Ainar Garipov
7bfad08dde Pull request 1927: 6006-use-address-processor
Updates #6006.

Squashed commit of the following:

commit ac27db95c12858b6ef182a0bd4acebab67a23993
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 18 15:47:17 2023 +0300

    all: imp code

commit 3936288512bfc2d44902ead6ab1bb5711f92b73c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 17 19:23:46 2023 +0300

    all: imp client resolving
2023-07-18 17:02:07 +03:00
Ainar Garipov
dead10e033 Pull request 1925: 6006-client-processor
Updates #6006.

Squashed commit of the following:

commit c72d6375e9c472c73b0bb9d025a8e197f404ba38
Merge: 02d64b10e 0cd441f04
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 18 13:56:26 2023 +0300

    Merge branch 'master' into 6006-client-processor

commit 02d64b10e19b2e937e45cab58d2310231a19bfbc
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 17 19:42:07 2023 +0300

    client: imp code, tests

commit b1613463089b4dde97484ff6a44b05888f0c2276
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 17 18:42:19 2023 +0300

    client: imp code, docs, tests

commit f71a17983b70d79839cf35dbe3279f0fdcac2ed7
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jul 14 21:53:47 2023 +0300

    all: add new client processor; imp code
2023-07-18 14:02:32 +03:00
Ainar Garipov
0cd441f04f Pull request 1926: imp-gh-tmpls
Squashed commit of the following:

commit 1a66a8af6aeb1b57507759b526d5adca2e8f7d1d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 17 17:09:45 2023 +0300

    .github: fix length

commit b9551cd5b09531cdb7887bd657a60459dd59259c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 17 16:58:48 2023 +0300

    all: imp gh tmpls, readme
2023-07-17 17:30:36 +03:00
Ainar Garipov
2adc8624c0 Pull request 1924: 6003-relax-rule-validation
Updates #6003.

Squashed commit of the following:

commit 1874860877662999d158631e3a25f8072c24f155
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 13 19:36:26 2023 +0300

    filtering/rulelist: imp test

commit 871a41af8039bf4d4fb139622d4296bcaff6729c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 13 19:10:35 2023 +0300

    filtering/rulelist: relax validation
2023-07-13 19:43:53 +03:00
Ainar Garipov
f22d893845 Pull request 1921: 6003-relax-scan-limit
Updates #6003.

Squashed commit of the following:

commit 1cc42303c29edc621802fc182ccb5701e412f099
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 13 13:47:41 2023 +0300

    all: fix chlog

commit e835084c7aac6384ea7b0886e6b3b1d614438baa
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 13 13:40:45 2023 +0300

    rulelist: imp longer line handling
2023-07-13 13:57:32 +03:00
Ainar Garipov
de63eeabfa Pull request 1920: 5985-client-text
Updates #5985.

Squashed commit of the following:

commit 925b55df066cb44eb37851491034f65727efcc79
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 12 20:39:04 2023 +0300

    client: imp text more

commit f4094eeeab01c168362366450169f7806faba198
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 12 20:24:33 2023 +0300

    client: fix text

commit 4e3a76da7c5f3d12716dbf5a9d5472e0c9b744c0
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 12 20:22:31 2023 +0300

    client: imp runtime client text
2023-07-12 21:05:29 +03:00
Ainar Garipov
1aaffd1b72 Pull request 1919: rm-ports
Squashed commit of the following:

commit 892cb403112f8b816d99e645d7419bfd49ad3c33
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 12 17:30:28 2023 +0300

    docker: rm 784, 8853 ports
2023-07-12 17:52:29 +03:00
Ainar Garipov
eb97e7dc01 Pull request 1918: upd-chlog
Squashed commit of the following:

commit d5d21a8dd3ca892b8c9ba3d6c2154a99933d6dc3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 12 16:07:53 2023 +0300

    all: upd chlog
2023-07-12 16:14:23 +03:00
Stanislav Chzhen
55335c4061 Pull request 1908: AG-23497-scripts-download-languages
Squashed commit of the following:

commit 874e847fc9bbfaeb8af1c02eb0ba1dbb98bd008f
Merge: 4becdd809 a79deda66
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jul 12 16:01:45 2023 +0300

    Merge branch 'master' into AG-23497-scripts-download-languages

commit 4becdd8092558b15d783674f5b9d1e9c151e3a8c
Merge: 1e5385c33 40884624c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jul 12 13:34:34 2023 +0300

    Merge branch 'master' into AG-23497-scripts-download-languages

commit 1e5385c33a298b0b8563fee6704f6bb3ded12d60
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jul 11 19:56:29 2023 +0300

    all: upd golibs, imp code

commit 0498960b00be21b1294f8b71108b234554e5847f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jul 7 19:05:58 2023 +0300

    scripts: imp naming

commit 6e36ed83c6bec2fe6159442a9e6805c0720e27f5
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 6 16:37:13 2023 +0300

    scripts: separate files

commit 55027cfa1c04b0a36e5267b024b53a45f26dd974
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jul 5 13:51:40 2023 +0300

    scripts: add download languages
2023-07-12 16:06:17 +03:00
Ainar Garipov
a79deda665 Pull request 1917: upd-go
Squashed commit of the following:

commit 72423458d6589027221d340a53af607622678b23
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 12 14:24:02 2023 +0300

    all: upd go
2023-07-12 14:37:05 +03:00
Ainar Garipov
40884624c2 Pull request 1916: 5990-root-ignore
Updates #5990.

Squashed commit of the following:

commit 1d5d3451c855681a631b85652417ee1bebadab01
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 11 20:11:45 2023 +0300

    all: allow ignoring root in querylog and stats
2023-07-11 20:42:40 +03:00
148 changed files with 5046 additions and 2308 deletions

View File

@@ -32,31 +32,33 @@
- 'attributes': - 'attributes':
'description': 'On which Platform does the issue occur?' 'description': 'On which Platform does the issue occur?'
'label': 'Platform (OS and CPU architecture)' 'label': 'Platform (OS and CPU architecture)'
# NOTE: Keep the 386 at the bottom for each OS, because a lot of people
# Seem to confuse them with AMD64, which is what they actually need.
'options': 'options':
- 'Darwin (aka macOS)/AMD64 (aka x86_64)' - 'Darwin (aka macOS), AMD64 (aka x86_64)'
- 'Darwin (aka macOS)/ARM64' - 'Darwin (aka macOS), ARM64'
- 'FreeBSD/386' - 'FreeBSD, AMD64 (aka x86_64)'
- 'FreeBSD/AMD64 (aka x86_64)' - 'FreeBSD, ARM64'
- 'FreeBSD/ARM64' - 'FreeBSD, ARMv5'
- 'FreeBSD/ARMv5' - 'FreeBSD, ARMv6'
- 'FreeBSD/ARMv6' - 'FreeBSD, ARMv7'
- 'FreeBSD/ARMv7' - 'FreeBSD, 32-bit Intel (aka 386)'
- 'Linux/386' - 'Linux, AMD64 (aka x86_64)'
- 'Linux/AMD64 (aka x86_64)' - 'Linux, ARM64'
- 'Linux/ARM64' - 'Linux, ARMv5'
- 'Linux/ARMv5' - 'Linux, ARMv6'
- 'Linux/ARMv6' - 'Linux, ARMv7'
- 'Linux/ARMv7' - 'Linux, MIPS LE'
- 'Linux/MIPS LE' - 'Linux, MIPS'
- 'Linux/MIPS' - 'Linux, MIPS64 LE'
- 'Linux/MIPS64 LE' - 'Linux, MIPS64'
- 'Linux/MIPS64' - 'Linux, PPC64 LE'
- 'Linux/PPC64 LE' - 'Linux, 32-bit Intel (aka 386)'
- 'OpenBSD/AMD64 (aka x86_64)' - 'OpenBSD, AMD64 (aka x86_64)'
- 'OpenBSD/ARM64' - 'OpenBSD, ARM64'
- 'Windows/386' - 'Windows, AMD64 (aka x86_64)'
- 'Windows/AMD64 (aka x86_64)' - 'Windows, ARM64'
- 'Windows/ARM64' - 'Windows, 32-bit Intel (aka 386)'
- 'Custom (please mention in the description)' - 'Custom (please mention in the description)'
'id': 'os' 'id': 'os'
'type': 'dropdown' 'type': 'dropdown'
@@ -142,8 +144,10 @@
'type': 'textarea' 'type': 'textarea'
'validations': 'validations':
'required': false 'required': false
'description': > # NOTE: GitHub limits the description length to 200 characters. Also, Markdown
Open a bug report. Please do not open bug reports for questions or help # doesn't work here.
with configuring clients. If you want to ask for help, use the Discussions 'description': |
section. For help, use the Discussions section instead. Write the title in English
to make it easier for other people to search for duplicates. (Any language
is fine in the body.)
'name': 'Bug' 'name': 'Bug'

View File

@@ -48,7 +48,11 @@
'type': 'textarea' 'type': 'textarea'
'validations': 'validations':
'required': false 'required': false
'description': 'Suggest a feature or an enhancement for AdGuard Home' # NOTE: GitHub limits the description length to 200 characters. Also, Markdown
# doesn't work here.
'description': |
Write the title in English to make it easier for other people to search for
duplicates. (Any language is fine in the body.)
'labels': 'labels':
- 'feature request' - 'feature request'
'name': 'Feature request or enhancement' 'name': 'Feature request or enhancement'

View File

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

View File

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

View File

@@ -14,15 +14,142 @@ and this project adheres to
<!-- <!--
## [v0.108.0] - TBA ## [v0.108.0] - TBA
## [v0.107.34] - 2023-07-26 (APPROX.) ## [v0.107.37] - 2023-08-16 (APPROX.)
See also the [v0.107.34 GitHub milestone][ms-v0.107.34]. See also the [v0.107.37 GitHub milestone][ms-v0.107.37].
[ms-v0.107.34]: https://github.com/AdguardTeam/AdGuardHome/milestone/69?closed=1 [ms-v0.107.37]: https://github.com/AdguardTeam/AdGuardHome/milestone/72?closed=1
NOTE: Add new changes BELOW THIS COMMENT. NOTE: Add new changes BELOW THIS COMMENT.
--> -->
### Added
- The ability to filter DNS HTTPS records including IPv4/v6 hints. ([#6053]).
- Two new metrics showing total number of responses from each upstream DNS
server and their average processing time in the Web UI ([#1453]).
- The ability to set the port for the `pprof` debug API, see configuration
changes below.
### Changed
- For non-A and non-AAAA requests, which has been filtered, the NODATA response
is returned if the blocking mode isn't set to `Null IP`. In previous versions
it returned NXDOMAIN response in such cases.
#### Configuration Changes
In this release, the schema version has changed from 24 to 25.
- Property `debug_pprof` which used to setup profiling HTTP handler, is now
moved to the new `pprof` object under `http` section. The new object contains
properties `enabled` and `port`:
```yaml
# BEFORE:
'debug_pprof': true
# AFTER:
'http':
'pprof':
'enabled': true
'port': 6060
```
Note that the new default `6060` is used as default. To rollback this change,
remove the new object `pprof`, set back `debug_pprof`, and change the
`schema_version` back to `24`.
### Fixed
- Address already in use when trying to install on port 3000 ([#6099]).
- Panic on using a single-slash filtering rule.
- Panic on shutting down while DNS requests are in process of filtering
([#5948]).
[#1453]: https://github.com/AdguardTeam/AdGuardHome/issues/1453
[#5948]: https://github.com/AdguardTeam/AdGuardHome/issues/5948
[#6053]: https://github.com/AdguardTeam/AdGuardHome/issues/6053
[#6099]: https://github.com/AdguardTeam/AdGuardHome/issues/6099
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
## [v0.107.36] - 2023-08-02
See also the [v0.107.36 GitHub milestone][ms-v0.107.36].
### Security
- Go version has been updated to prevent the possibility of exploiting the
CVE-2023-29409 Go vulnerability fixed in [Go 1.20.7][go-1.20.7].
### Deprecated
- Go 1.20 support. Future versions will require at least Go 1.21 to build.
### Fixed
- Inability to block queries for the root domain, such as `NS .` queries, using
the *Disallowed domains* feature on the *DNS settings* page ([#6049]). Users
who want to block `.` queries should use the `|.^` AdBlock rule or a similar
regular expression.
- Client hostnames not resolving when upstream server responds with zero-TTL
records ([#6046]).
[#6046]: https://github.com/AdguardTeam/AdGuardHome/issues/6046
[#6049]: https://github.com/AdguardTeam/AdGuardHome/issues/6049
[go-1.20.7]: https://groups.google.com/g/golang-announce/c/X0b6CsSAaYI/m/Efv5DbZ9AwAJ
[ms-v0.107.36]: https://github.com/AdguardTeam/AdGuardHome/milestone/71?closed=1
## [v0.107.35] - 2023-07-26
See also the [v0.107.35 GitHub milestone][ms-v0.107.35].
### Changed
- Improved reliability filtering-rule list updates on Unix systems.
### Fixed
- Occasional client information lookup failures that could lead to the DNS
server getting stuck ([#6006]).
- `bufio.Scanner: token too long` and other errors when trying to add
filtering-rule lists with lines over 1024 bytes long or containing cosmetic
rules ([#6003]).
### Removed
- Default exposure of the non-standard ports 784 and 8853 for DNS-over-QUIC in
the `Dockerfile`.
[#6003]: https://github.com/AdguardTeam/AdGuardHome/issues/6003
[#6006]: https://github.com/AdguardTeam/AdGuardHome/issues/6006
[ms-v0.107.35]: https://github.com/AdguardTeam/AdGuardHome/milestone/70?closed=1
## [v0.107.34] - 2023-07-12
See also the [v0.107.34 GitHub milestone][ms-v0.107.34].
### Security
- Go version has been updated to prevent the possibility of exploiting the
CVE-2023-29406 Go vulnerability fixed in [Go 1.19.11][go-1.19.11].
### Added
- Ability to ignore queries for the root domain, such as `NS .` queries
([#5990]).
### Changed ### Changed
- Improved CPU and RAM consumption during updates of filtering-rule lists. - Improved CPU and RAM consumption during updates of filtering-rule lists.
@@ -32,8 +159,9 @@ NOTE: Add new changes BELOW THIS COMMENT.
In this release, the schema version has changed from 23 to 24. In this release, the schema version has changed from 23 to 24.
- Properties starting with `log_`, and `verbose` property, which used to set up - Properties starting with `log_`, and `verbose` property, which used to set up
logging are now moved to the new object `log` containing new properties `file`, logging are now moved to the new object `log` containing new properties
`max_backups`, `max_size`, `max_age`, `compress`, `local_time`, and `verbose`: `file`, `max_backups`, `max_size`, `max_age`, `compress`, `local_time`, and
`verbose`:
```yaml ```yaml
# BEFORE: # BEFORE:
@@ -47,13 +175,13 @@ In this release, the schema version has changed from 23 to 24.
# AFTER: # AFTER:
'log': 'log':
'file': "" 'file': ""
'max_backups': 0 'max_backups': 0
'max_size': 100 'max_size': 100
'max_age': 3 'max_age': 3
'compress': false 'compress': false
'local_time': false 'local_time': false
'verbose': false 'verbose': false
``` ```
To rollback this change, remove the new object `log`, set back `log_` and To rollback this change, remove the new object `log`, set back `log_` and
@@ -83,10 +211,10 @@ In this release, the schema version has changed from 23 to 24.
[#5896]: https://github.com/AdguardTeam/AdGuardHome/issues/5896 [#5896]: https://github.com/AdguardTeam/AdGuardHome/issues/5896
[#5972]: https://github.com/AdguardTeam/AdGuardHome/issues/5972 [#5972]: https://github.com/AdguardTeam/AdGuardHome/issues/5972
[#5990]: https://github.com/AdguardTeam/AdGuardHome/issues/5990
<!-- [go-1.19.11]: https://groups.google.com/g/golang-announce/c/2q13H6LEEx0/m/sduSepLLBwAJ
NOTE: Add new changes ABOVE THIS COMMENT. [ms-v0.107.34]: https://github.com/AdguardTeam/AdGuardHome/milestone/69?closed=1
-->
@@ -2221,11 +2349,14 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!-- <!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.34...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.37...HEAD
[v0.107.34]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...v0.107.34 [v0.107.37]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.36...v0.107.37
--> -->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.36...HEAD
[v0.107.36]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.35...v0.107.36
[v0.107.35]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.34...v0.107.35
[v0.107.34]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...v0.107.34
[v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...v0.107.33 [v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...v0.107.33
[v0.107.32]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.31...v0.107.32 [v0.107.32]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.31...v0.107.32
[v0.107.31]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.30...v0.107.31 [v0.107.31]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.30...v0.107.31

View File

@@ -130,3 +130,10 @@ openapi-lint: ; cd ./openapi/ && $(YARN) test
openapi-show: ; cd ./openapi/ && $(YARN) start openapi-show: ; cd ./openapi/ && $(YARN) start
txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh
# TODO(a.garipov): Consider adding to scripts/ and the common project
# structure.
go-upd-tools:
cd ./internal/tools/ &&\
"$(GO.MACRO)" get -u &&\
"$(GO.MACRO)" mod tidy

View File

@@ -54,7 +54,7 @@ code.
* [Getting Started](#getting-started) * [Getting Started](#getting-started)
* [Automated install (Unix)](#automated-install-linux-and-mac) * [Automated install (Linux/Unix/MacOS/FreeBSD/OpenBSD)](#automated-install-linux-and-mac)
* [Alternative methods](#alternative-methods) * [Alternative methods](#alternative-methods)
* [Guides](#guides) * [Guides](#guides)
* [API](#api) * [API](#api)
@@ -79,7 +79,7 @@ code.
## <a href="#getting-started" id="getting-started" name="getting-started">Getting Started</a> ## <a href="#getting-started" id="getting-started" name="getting-started">Getting Started</a>
### <a href="#automated-install-linux-and-mac" id="automated-install-linux-and-mac" name="automated-install-linux-and-mac">Automated install (Unix)</a> ### <a href="#automated-install-linux-and-mac" id="automated-install-linux-and-mac" name="automated-install-linux-and-mac">Automated install (Linux/Unix/MacOS/FreeBSD/OpenBSD)</a>
To install with `curl` run the following command: To install with `curl` run the following command:
@@ -261,7 +261,7 @@ Run `make init` to prepare the development environment.
You will need this to build AdGuard Home: You will need this to build AdGuard Home:
* [Go](https://golang.org/dl/) v1.19 or later; * [Go](https://golang.org/dl/) v1.20 or later;
* [Node.js](https://nodejs.org/en/download/) v10.16.2 or later; * [Node.js](https://nodejs.org/en/download/) v10.16.2 or later;
* [npm](https://www.npmjs.com/) v6.14 or later; * [npm](https://www.npmjs.com/) v6.14 or later;
* [yarn](https://yarnpkg.com/) v1.22.5 or later. * [yarn](https://yarnpkg.com/) v1.22.5 or later.
@@ -416,7 +416,8 @@ There are three options how you can install an unstable version:
### <a href="#reporting-issues" id="reporting-issues" name="reporting-issues">Report issues</a> ### <a href="#reporting-issues" id="reporting-issues" name="reporting-issues">Report issues</a>
If you run into any problem or have a suggestion, head to [this page][iss] and If you run into any problem or have a suggestion, head to [this page][iss] and
click on the “New issue” button. click on the “New issue” button. Please follow the instructions in the issue
form carefully and don't forget to start by searching for duplicates.
[iss]: https://github.com/AdguardTeam/AdGuardHome/issues [iss]: https://github.com/AdguardTeam/AdGuardHome/issues

View File

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

View File

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

View File

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

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Opravdu chcete odstranit klienta \"{{key}}\"?", "client_confirm_delete": "Opravdu chcete odstranit klienta \"{{key}}\"?",
"list_confirm_delete": "Opravdu chcete smazat tento seznam?", "list_confirm_delete": "Opravdu chcete smazat tento seznam?",
"auto_clients_title": "Spuštění klienti", "auto_clients_title": "Spuštění klienti",
"auto_clients_desc": "Zařízení, která nejsou na seznamu stálých klientů, a mohou nadále používat AdGuard Home", "auto_clients_desc": "Informace o IP adresách zařízení, která používají nebo mohou používat AdGuard Home. Tyto informace se získávají z několika zdrojů, včetně souborů hosts, reverzního DNS atd.",
"access_title": "Nastavení přístupu", "access_title": "Nastavení přístupu",
"access_desc": "Zde můžete konfigurovat pravidla přístupu pro server DNS AdGuard Home", "access_desc": "Zde můžete konfigurovat pravidla přístupu pro server DNS AdGuard Home",
"access_allowed_title": "Povolení klienti", "access_allowed_title": "Povolení klienti",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Sikker på, at du vil slette klient \"{{key}}\"?", "client_confirm_delete": "Sikker på, at du vil slette klient \"{{key}}\"?",
"list_confirm_delete": "Sikker på, at du vil slette denne liste?", "list_confirm_delete": "Sikker på, at du vil slette denne liste?",
"auto_clients_title": "Klienter (runtime)", "auto_clients_title": "Klienter (runtime)",
"auto_clients_desc": "Enheder, som ikke er på listen over Permanente klienter, kan stadig bruge AdGuard Home", "auto_clients_desc": "Oplysninger om IP-adresser på enheder, som (måske) bruger AdGuard Home. Disse oplysninger indsamles fra flere kilder, herunder hosts-filer, reverse DNS mv.",
"access_title": "Adgangsindstillinger", "access_title": "Adgangsindstillinger",
"access_desc": "Her kan adgangsregler for AdGuard Home DNS-serveren opsættes", "access_desc": "Her kan adgangsregler for AdGuard Home DNS-serveren opsættes",
"access_allowed_title": "Tilladte klienter", "access_allowed_title": "Tilladte klienter",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Möchten Sie den Client „{{key}}“ wirklich löschen?", "client_confirm_delete": "Möchten Sie den Client „{{key}}“ wirklich löschen?",
"list_confirm_delete": "Möchten Sie diese Liste wirklich löschen?", "list_confirm_delete": "Möchten Sie diese Liste wirklich löschen?",
"auto_clients_title": "Laufzeit-Clients", "auto_clients_title": "Laufzeit-Clients",
"auto_clients_desc": "Geräte, die nicht auf der Liste der persistenten Clients stehen und trotzdem AdGuard Home verwenden dürfen", "auto_clients_desc": "Informationen über IP-Adressen der Geräten, die AdGuard Home nutzen oder nutzen könnten. Diese Informationen werden aus verschiedenen Quellen gesammelt, darunter Hosts-Dateien, Reverse-DNS usw.",
"access_title": "Zugriffsrechte", "access_title": "Zugriffsrechte",
"access_desc": "Hier können Sie die Zugriffsregeln für den DNS-Server von AdGuard Home konfigurieren", "access_desc": "Hier können Sie die Zugriffsregeln für den DNS-Server von AdGuard Home konfigurieren",
"access_allowed_title": "Zugelassene Clients", "access_allowed_title": "Zugelassene Clients",

View File

@@ -125,6 +125,8 @@
"top_clients": "Top clients", "top_clients": "Top clients",
"no_clients_found": "No clients found", "no_clients_found": "No clients found",
"general_statistics": "General statistics", "general_statistics": "General statistics",
"top_upstreams": "Top upstreams",
"no_upstreams_data_found": "No upstreams data found",
"number_of_dns_query_days": "The number of DNS queries processed for the last {{count}} day", "number_of_dns_query_days": "The number of DNS queries processed for the last {{count}} day",
"number_of_dns_query_days_plural": "The number of DNS queries processed for the last {{count}} days", "number_of_dns_query_days_plural": "The number of DNS queries processed for the last {{count}} days",
"number_of_dns_query_24_hours": "The number of DNS queries processed for the last 24 hours", "number_of_dns_query_24_hours": "The number of DNS queries processed for the last 24 hours",
@@ -134,6 +136,7 @@
"enforced_save_search": "Enforced safe search", "enforced_save_search": "Enforced safe search",
"number_of_dns_query_to_safe_search": "The number of DNS requests to search engines for which Safe Search was enforced", "number_of_dns_query_to_safe_search": "The number of DNS requests to search engines for which Safe Search was enforced",
"average_processing_time": "Average processing time", "average_processing_time": "Average processing time",
"processing_time": "Processing time",
"average_processing_time_hint": "Average time in milliseconds on processing a DNS request", "average_processing_time_hint": "Average time in milliseconds on processing a DNS request",
"block_domain_use_filters_and_hosts": "Block domains using filters and hosts files", "block_domain_use_filters_and_hosts": "Block domains using filters and hosts files",
"filters_block_toggle_hint": "You can setup blocking rules in the <a>Filters</a> settings.", "filters_block_toggle_hint": "You can setup blocking rules in the <a>Filters</a> settings.",
@@ -158,6 +161,7 @@
"upstream_dns_configured_in_file": "Configured in {{path}}", "upstream_dns_configured_in_file": "Configured in {{path}}",
"test_upstream_btn": "Test upstreams", "test_upstream_btn": "Test upstreams",
"upstreams": "Upstreams", "upstreams": "Upstreams",
"upstream": "Upstream",
"apply_btn": "Apply", "apply_btn": "Apply",
"disabled_filtering_toast": "Disabled filtering", "disabled_filtering_toast": "Disabled filtering",
"enabled_filtering_toast": "Enabled filtering", "enabled_filtering_toast": "Enabled filtering",
@@ -444,7 +448,7 @@
"client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?", "client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?",
"list_confirm_delete": "Are you sure you want to delete this list?", "list_confirm_delete": "Are you sure you want to delete this list?",
"auto_clients_title": "Runtime clients", "auto_clients_title": "Runtime clients",
"auto_clients_desc": "Devices not on the list of Persistent clients that may still use AdGuard Home", "auto_clients_desc": "Information about IP addresses of devices that are using or may use AdGuard Home. This information is gathered from several sources, including hosts files, reverse DNS, etc.",
"access_title": "Access settings", "access_title": "Access settings",
"access_desc": "Here you can configure access rules for the AdGuard Home DNS server", "access_desc": "Here you can configure access rules for the AdGuard Home DNS server",
"access_allowed_title": "Allowed clients", "access_allowed_title": "Allowed clients",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "¿Estás seguro de que deseas eliminar el cliente \"{{key}}\"?", "client_confirm_delete": "¿Estás seguro de que deseas eliminar el cliente \"{{key}}\"?",
"list_confirm_delete": "¿Estás seguro de que deseas eliminar esta lista?", "list_confirm_delete": "¿Estás seguro de que deseas eliminar esta lista?",
"auto_clients_title": "Clientes activos", "auto_clients_title": "Clientes activos",
"auto_clients_desc": "Dispositivos que no están en la lista de clientes persistentes que aún pueden utilizar AdGuard Home", "auto_clients_desc": "Información sobre las direcciones IP de los dispositivos que usan o pueden usar AdGuard Home. Esta información se recopila de varias fuentes, incluidos ficheros de host, DNS inverso, etc.",
"access_title": "Configuración de acceso", "access_title": "Configuración de acceso",
"access_desc": "Aquí puedes configurar las reglas de acceso para el servidor DNS de AdGuard Home", "access_desc": "Aquí puedes configurar las reglas de acceso para el servidor DNS de AdGuard Home",
"access_allowed_title": "Clientes permitidos", "access_allowed_title": "Clientes permitidos",

View File

@@ -2,21 +2,21 @@
"client_settings": "Päätelaiteasetukset", "client_settings": "Päätelaiteasetukset",
"example_upstream_reserved": "ylävirta <0>tietyille verkkotunnuksille</0>;", "example_upstream_reserved": "ylävirta <0>tietyille verkkotunnuksille</0>;",
"example_upstream_comment": "kommentti.", "example_upstream_comment": "kommentti.",
"upstream_parallel": "Käytä rinnakkaisia pyyntöjä ja nopeuta selvitystä käyttämällä kaikkia ylävirran palvelimia samanaikaisesti.", "upstream_parallel": "Käytä rinnakkaisia pyyntöjä ja nopeuta selvitystä käyttämällä kaikkia ylävirtapalvelimia samanaikaisesti.",
"parallel_requests": "Rinnakkaiset pyynnöt", "parallel_requests": "Rinnakkaiset pyynnöt",
"load_balancing": "Kuormantasaus", "load_balancing": "Kuormantasaus",
"load_balancing_desc": "Lähetä pyyntö yhdelle ylävirran palvelimelle kerrallaan. AdGuard Home pyrkii valitsemaan nopeimman palvelimen painotetun satunnaisalgoritminsa avulla.", "load_balancing_desc": "Lähetä pyyntö yhdelle ylävirtapalvelimelle kerrallaan. AdGuard Home pyrkii valitsemaan nopeimman palvelimen painotetun satunnaisalgoritminsa avulla.",
"bootstrap_dns": "Bootstrap DNS-palvelimet", "bootstrap_dns": "Bootstrap DNS-palvelimet",
"bootstrap_dns_desc": "Bootstrap DNS-palvelimia käytetään ylävirroiksi määritettyjen DoH/DoT-resolvereiden IP-osoitteiden selvitykseen.", "bootstrap_dns_desc": "Bootstrap DNS-palvelimia käytetään ylävirroiksi määritettyjen DoH/DoT-resolvereiden IP-osoitteiden selvitykseen.",
"local_ptr_title": "Yksityiset käänteiset DNS-palvelimet", "local_ptr_title": "Yksityiset käänteis-DNS-palvelimet",
"local_ptr_desc": "DNS-palvelimet, joita AdGuard Home käyttää paikallisille PTR-pyynnöille. Näitä palvelimia käytetään yksityistä IP-osoitetta käyttävien PTR-pyyntöjen osoitteiden, kuten \"192.168.12.34\", selvitykseen käänteisen DNS:n avulla. Jos ei käytössä, AdGuard Home käyttää käyttöjärjestelmän oletusarvoisia DNS-resolvereita, poislukien AdGuard Homen omat osoitteet.", "local_ptr_desc": "DNS-palvelimet, joita AdGuard Home käyttää paikallisille PTR-pyynnöille. Näitä palvelimia käytetään yksityistä IP-osoitetta käyttävien PTR-pyyntöjen osoitteiden, kuten \"192.168.12.34\", selvitykseen käänteis-DNS:n avulla. Jos ei käytössä, AdGuard Home käyttää käyttöjärjestelmän oletusarvoisia DNS-resolvereita, poislukien AdGuard Homen omat osoitteet.",
"local_ptr_default_resolver": "Oletusarvoisesti AdGuard Home käyttää seuraavia käänteisDNS-resolvereita: {{ip}}.", "local_ptr_default_resolver": "Oletusarvoisesti AdGuard Home käyttää seuraavia käänteis-DNS-resolvereita: {{ip}}.",
"local_ptr_no_default_resolver": "AdGuard Home ei voinut määrittää tälle järjestelmälle sopivaa yksityistä käänteisDNS-resolveria.", "local_ptr_no_default_resolver": "AdGuard Home ei voinut määrittää tälle järjestelmälle sopivaa yksityistä käänteis-DNS-resolveria.",
"local_ptr_placeholder": "Syötä yksi palvelimen osoite per rivi", "local_ptr_placeholder": "Syötä yksi palvelimen osoite per rivi",
"resolve_clients_title": "Käytä päätelaitteiden IP-osoitteille käänteistä selvitystä", "resolve_clients_title": "Käytä päätelaitteiden IP-osoitteille käänteistä selvitystä",
"resolve_clients_desc": "Selvitä päätelaitteiden IP-osoitteiden isäntänimet käänteisesti lähettämällä PTR-pyynnöt sopiville resolvereille (yksityiset DNS-palvelimet paikallisille päätelaitteille, lähtevät palvelimet päätelaitteille, joilla on julkiset IP-osoitteet).", "resolve_clients_desc": "Selvitä päätelaitteiden IP-osoitteiden isäntänimet käänteisesti lähettämällä PTR-pyynnöt sopiville resolvereille (yksityiset DNS-palvelimet paikallisille päätelaitteille, yvirtapalvelimet päätelaitteille, joilla on julkiset IP-osoitteet).",
"use_private_ptr_resolvers_title": "Käytä yksityisiä käänteisDNS-resolvereita", "use_private_ptr_resolvers_title": "Käytä yksityisiä käänteis-DNS-resolvereita",
"use_private_ptr_resolvers_desc": "Suorita käänteiset DNS-selvitykset paikallisesti tarjotuille osoitteille käyttäen näitä ylävirran palvelimia. Jos ei käytössä, vastaa AdGuard Home kaikkiin sen tyyppisiin PTR-pyyntöihin NXDOMAIN-arvolla, pois lukien DHCP, /etc/hosts, yms. -tiedoista tunnistettut päätelaitteet.", "use_private_ptr_resolvers_desc": "Suorita käänteis-DNS-selvitykset paikallisesti tarjotuille osoitteille käyttäen näitä ylävirtapalvelimia. Jos ei käytössä, vastaa AdGuard Home kaikkiin sen tyyppisiin PTR-pyyntöihin NXDOMAIN-arvolla, pois lukien DHCP, /etc/hosts, yms. -tiedoista tunnistettut päätelaitteet.",
"check_dhcp_servers": "Etsi DHCP-palvelimia", "check_dhcp_servers": "Etsi DHCP-palvelimia",
"save_config": "Tallenna asetukset", "save_config": "Tallenna asetukset",
"enabled_dhcp": "DHCP-palvelin otettiin käyttöön", "enabled_dhcp": "DHCP-palvelin otettiin käyttöön",
@@ -220,7 +220,7 @@
"example_upstream_tcp_port": "tavallinen DNS (TCP, portti);", "example_upstream_tcp_port": "tavallinen DNS (TCP, portti);",
"example_upstream_tcp_hostname": "tavallinen DNS (TCP, isäntänimi);", "example_upstream_tcp_hostname": "tavallinen DNS (TCP, isäntänimi);",
"all_lists_up_to_date_toast": "Kaikki listat ovat ajan tasalla", "all_lists_up_to_date_toast": "Kaikki listat ovat ajan tasalla",
"updated_upstream_dns_toast": "Ylävirtojen palvelimet tallennettiin", "updated_upstream_dns_toast": "Ylävirtapalvelimet tallennettiin",
"dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein", "dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein",
"dns_test_not_ok_toast": "Palvelin \"{{key}}\": Ei voitu käyttää, tarkista oikeinkirjoitus", "dns_test_not_ok_toast": "Palvelin \"{{key}}\": Ei voitu käyttää, tarkista oikeinkirjoitus",
"dns_test_warning_toast": "Datavuon \"{{key}}\" ei vastaa testipyyntöihin eikä välttämättä toimi kunnolla", "dns_test_warning_toast": "Datavuon \"{{key}}\" ei vastaa testipyyntöihin eikä välttämättä toimi kunnolla",
@@ -444,7 +444,7 @@
"client_confirm_delete": "Haluatko varmasti poistaa päätelaitteen \"{{key}}\"?", "client_confirm_delete": "Haluatko varmasti poistaa päätelaitteen \"{{key}}\"?",
"list_confirm_delete": "Haluatko varmasti poistaa tämän listan?", "list_confirm_delete": "Haluatko varmasti poistaa tämän listan?",
"auto_clients_title": "Määrittämättömät päätelaitteet", "auto_clients_title": "Määrittämättömät päätelaitteet",
"auto_clients_desc": "Päätelaitteet, joita ei ole määritetty pysyviksi ja jotka voivat silti käyttää AdGuard Homea.", "auto_clients_desc": "Päätelaitteet, joita ei ole määritetty pysyviksi ja jotka voivat silti käyttää AdGuard Homea. Näitä tietoja kertään useista lähteistä, mm. hosts-tiedostoista ja kääteis-DNS:llä.",
"access_title": "Käytön asetukset", "access_title": "Käytön asetukset",
"access_desc": "Tässä voidaan määrittää AdGuard Homen DNS-palvelimen käyttöoikeussääntöjä.", "access_desc": "Tässä voidaan määrittää AdGuard Homen DNS-palvelimen käyttöoikeussääntöjä.",
"access_allowed_title": "Sallitut päätelaitteet", "access_allowed_title": "Sallitut päätelaitteet",
@@ -623,7 +623,7 @@
"enter_cache_size": "Syötä välimuistin koko (tavuina)", "enter_cache_size": "Syötä välimuistin koko (tavuina)",
"enter_cache_ttl_min_override": "Syötä vähimmäis-TTL (sekunteina)", "enter_cache_ttl_min_override": "Syötä vähimmäis-TTL (sekunteina)",
"enter_cache_ttl_max_override": "Syötä enimmäis-TTL (sekunteina)", "enter_cache_ttl_max_override": "Syötä enimmäis-TTL (sekunteina)",
"cache_ttl_min_override_desc": "Pidennä ylävirran palvelimelta vastaanotettuja, lyhyitä elinaika-arvoja (sekunteina) tallennettaessa DNS-vastauksia välimuistiin.", "cache_ttl_min_override_desc": "Pidennä ylävirtapalvelimelta vastaanotettuja, lyhyitä elinaika-arvoja (sekunteina) tallennettaessa DNS-vastauksia välimuistiin.",
"cache_ttl_max_override_desc": "Määritä DNS-välimuistin kohteiden enimmäiselinaika (sekunteina).", "cache_ttl_max_override_desc": "Määritä DNS-välimuistin kohteiden enimmäiselinaika (sekunteina).",
"ttl_cache_validation": "Välimuistin vähimmäiselinajan on oltava pienempi tai sama kuin enimmäiselinajan", "ttl_cache_validation": "Välimuistin vähimmäiselinajan on oltava pienempi tai sama kuin enimmäiselinajan",
"cache_optimistic": "Optimistinen välimuisti", "cache_optimistic": "Optimistinen välimuisti",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Voulez-vous vraiment supprimer le client « {{key}} » ?", "client_confirm_delete": "Voulez-vous vraiment supprimer le client « {{key}} » ?",
"list_confirm_delete": "Voulez-vous vraiment supprimer cette liste ?", "list_confirm_delete": "Voulez-vous vraiment supprimer cette liste ?",
"auto_clients_title": "Clients d'exécution", "auto_clients_title": "Clients d'exécution",
"auto_clients_desc": "Appareils ne figurant pas sur la liste des clients persistants qui peuvent encore utiliser AdGuard Home.", "auto_clients_desc": "Informations sur les adresses IP des appareils qui utilisent ou pourraient utiliser AdGuard Home. Ces informations sont recueillies à partir de plusieurs sources, notamment les fichiers hosts, le DNS inverse, etc.",
"access_title": "Paramètres d'accès", "access_title": "Paramètres d'accès",
"access_desc": "Ici vous pouvez configurer les règles d'accès au serveur DNS AdGuard Home", "access_desc": "Ici vous pouvez configurer les règles d'accès au serveur DNS AdGuard Home",
"access_allowed_title": "Clients autorisés", "access_allowed_title": "Clients autorisés",

View File

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

View File

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

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Sei sicuro di voler eliminare il client \"{{key}}\"?", "client_confirm_delete": "Sei sicuro di voler eliminare il client \"{{key}}\"?",
"list_confirm_delete": "Sei sicuro di voler eliminare questo elenco?", "list_confirm_delete": "Sei sicuro di voler eliminare questo elenco?",
"auto_clients_title": "Client in tempo reale", "auto_clients_title": "Client in tempo reale",
"auto_clients_desc": "Dispositivi non presenti nell'elenco dei client Persistenti che possono ancora utilizzare AdGuard Home", "auto_clients_desc": "Informazioni sugli indirizzi IP dei dispositivi che utilizzano o potrebbero utilizzare AdGuard Home. Queste informazioni vengono raccolte da diverse fonti, inclusi file host, DNS inverso, ecc.",
"access_title": "Impostazioni di accesso", "access_title": "Impostazioni di accesso",
"access_desc": "Qui puoi configurare le regole d'accesso per il server DNS di AdGuard Home", "access_desc": "Qui puoi configurare le regole d'accesso per il server DNS di AdGuard Home",
"access_allowed_title": "Client permessi", "access_allowed_title": "Client permessi",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "クライアント \"{{key}}\" を削除してもよろしいですか?", "client_confirm_delete": "クライアント \"{{key}}\" を削除してもよろしいですか?",
"list_confirm_delete": "このリストを削除してもよろしいですか?", "list_confirm_delete": "このリストを削除してもよろしいですか?",
"auto_clients_title": "ランタイムクライアント", "auto_clients_title": "ランタイムクライアント",
"auto_clients_desc": "永続的クライアントのリストに未登録で、AdGuard Homeを使用する場合があるデバイスのリスト。", "auto_clients_desc": "AdGuard Home を使用している、または使用する可能性のあるデバイスの IP アドレスに関する情報です。この情報は、hosts ファイル、リバース DNS など、複数の情報源から収集されます。",
"access_title": "アクセス設定", "access_title": "アクセス設定",
"access_desc": "こちらでは、AdGuard Home DNSサーバーのアクセスルールを設定できます。", "access_desc": "こちらでは、AdGuard Home DNSサーバーのアクセスルールを設定できます。",
"access_allowed_title": "許可されたクライアント", "access_allowed_title": "許可されたクライアント",

View File

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

View File

@@ -186,7 +186,7 @@
"cancel_btn": "Annuleren", "cancel_btn": "Annuleren",
"enter_name_hint": "Voeg naam toe", "enter_name_hint": "Voeg naam toe",
"enter_url_or_path_hint": "Voer een URL in of het pad van de lijst", "enter_url_or_path_hint": "Voer een URL in of het pad van de lijst",
"check_updates_btn": "Controleer op updates", "check_updates_btn": "Controleren op updates",
"new_blocklist": "Nieuwe blokkeerlijst", "new_blocklist": "Nieuwe blokkeerlijst",
"new_allowlist": "Nieuwe toelatingslijst", "new_allowlist": "Nieuwe toelatingslijst",
"edit_blocklist": "Blokkeerlijst beheren", "edit_blocklist": "Blokkeerlijst beheren",
@@ -444,7 +444,7 @@
"client_confirm_delete": "Ben je zeker dat je deze gebruiker \"{{key}}\" wilt verwijderen?", "client_confirm_delete": "Ben je zeker dat je deze gebruiker \"{{key}}\" wilt verwijderen?",
"list_confirm_delete": "Ben je zeker om deze lijst te verwijderen?", "list_confirm_delete": "Ben je zeker om deze lijst te verwijderen?",
"auto_clients_title": "Runtime-clients", "auto_clients_title": "Runtime-clients",
"auto_clients_desc": "Apparaten die niet op de lijst van permanente clients staan die mogelijk nog steeds AdGuard Home gebruiken", "auto_clients_desc": "Informatie over IP-adressen van apparaten die AdGuard Home gebruiken of kunnen gebruiken. Deze informatie wordt verzameld uit verschillende bronnen, waaronder hosts-bestanden, reverse DNS, enz.",
"access_title": "Toegangs instellingen", "access_title": "Toegangs instellingen",
"access_desc": "Hier kan je toegangsregels voor de AdGuard Home DNS-server instellen", "access_desc": "Hier kan je toegangsregels voor de AdGuard Home DNS-server instellen",
"access_allowed_title": "Toegestane gebruikers", "access_allowed_title": "Toegestane gebruikers",
@@ -456,7 +456,7 @@
"access_settings_saved": "Toegangsinstellingen succesvol opgeslagen", "access_settings_saved": "Toegangsinstellingen succesvol opgeslagen",
"updates_checked": "Een nieuwe versie van AdGuard Home is beschikbaar\n", "updates_checked": "Een nieuwe versie van AdGuard Home is beschikbaar\n",
"updates_version_equal": "AdGuard Home is actueel", "updates_version_equal": "AdGuard Home is actueel",
"check_updates_now": "Controleer op updates", "check_updates_now": "Nu controleren op updates",
"version_request_error": "Updatecontrole mislukt. Controleer je internetverbinding.", "version_request_error": "Updatecontrole mislukt. Controleer je internetverbinding.",
"dns_privacy": "DNS Privacy", "dns_privacy": "DNS Privacy",
"setup_dns_privacy_1": "<0>DNS-via-TLS:</0> Gebruik <1>{{address}}</1> string.", "setup_dns_privacy_1": "<0>DNS-via-TLS:</0> Gebruik <1>{{address}}</1> string.",
@@ -573,7 +573,7 @@
"tags_title": "Labels", "tags_title": "Labels",
"tags_desc": "Je kunt labels selecteren die overeenkomen met de client. Labels kunnen worden opgenomen in de filterregels om ze \n nauwkeuriger toe te passen. <0>Meer informatie</0>.", "tags_desc": "Je kunt labels selecteren die overeenkomen met de client. Labels kunnen worden opgenomen in de filterregels om ze \n nauwkeuriger toe te passen. <0>Meer informatie</0>.",
"form_select_tags": "Client tags selecteren", "form_select_tags": "Client tags selecteren",
"check_title": "Controleer de filtering", "check_title": "De filtering controleren",
"check_desc": "Controleren of een hostnaam wordt gefilterd.", "check_desc": "Controleren of een hostnaam wordt gefilterd.",
"check": "Controleren", "check": "Controleren",
"form_enter_host": "Voer een hostnaam in", "form_enter_host": "Voer een hostnaam in",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Czy na pewno chcesz usunąć klienta \"{{key}}\"?", "client_confirm_delete": "Czy na pewno chcesz usunąć klienta \"{{key}}\"?",
"list_confirm_delete": "Czy na pewno chcesz usunąć tę listę?", "list_confirm_delete": "Czy na pewno chcesz usunąć tę listę?",
"auto_clients_title": "Uruchomieni klienci", "auto_clients_title": "Uruchomieni klienci",
"auto_clients_desc": "Urządzenia, których nie ma na liście stałych klientów, które mogą nadal korzystać z AdGuard Home", "auto_clients_desc": "Informacje o adresach IP urządzeń korzystających lub mogących korzystać z AdGuard Home. Te informacje są gromadzone z wielu źródeł takich jak pliki hosta, odwrotna translacja DNS, itp.",
"access_title": "Ustawienia dostępu", "access_title": "Ustawienia dostępu",
"access_desc": "Tutaj możesz skonfigurować reguły dostępu dla serwera DNS AdGuard Home", "access_desc": "Tutaj możesz skonfigurować reguły dostępu dla serwera DNS AdGuard Home",
"access_allowed_title": "Dozwoleni klienci", "access_allowed_title": "Dozwoleni klienci",
@@ -470,7 +470,7 @@
"setup_dns_privacy_ios_2": "Aplikacja <0>AdGuard dla iOS</0> obsługuje <1>DNS-over-HTTPS</1> i <1>DNS-over-TLS</1>.", "setup_dns_privacy_ios_2": "Aplikacja <0>AdGuard dla iOS</0> obsługuje <1>DNS-over-HTTPS</1> i <1>DNS-over-TLS</1>.",
"setup_dns_privacy_other_title": "Inne implementacje", "setup_dns_privacy_other_title": "Inne implementacje",
"setup_dns_privacy_other_1": "Sam AdGuard Home może być bezpiecznym klientem DNS na dowolnej platformie.", "setup_dns_privacy_other_1": "Sam AdGuard Home może być bezpiecznym klientem DNS na dowolnej platformie.",
"setup_dns_privacy_other_2": "<0>dnsproxy</0> obsługuje wszystkie znane bezpieczne protokoły DNS.\n\n", "setup_dns_privacy_other_2": "<0>dnsproxy</0> obsługuje wszystkie znane bezpieczne protokoły DNS.",
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> obsługuje <1>DNS-over-HTTPS</1>.", "setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> obsługuje <1>DNS-over-HTTPS</1>.",
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> obsługuje <1>DNS-over-HTTPS</1>.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> obsługuje <1>DNS-over-HTTPS</1>.",
"setup_dns_privacy_other_5": "Znajdziesz więcej implementacji <0>tutaj</0> i <1>tutaj</1>.", "setup_dns_privacy_other_5": "Znajdziesz więcej implementacji <0>tutaj</0> i <1>tutaj</1>.",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Você tem certeza de que deseja excluir o cliente \"{{key}}\"?", "client_confirm_delete": "Você tem certeza de que deseja excluir o cliente \"{{key}}\"?",
"list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?", "list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?",
"auto_clients_title": "Clientes ativos", "auto_clients_title": "Clientes ativos",
"auto_clients_desc": "Dispositivo não está na lista de dispositivos persistentes que podem ser utilizados no AdGuard Home", "auto_clients_desc": "Informações sobre endereços IP de dispositivos que usam ou podem usar o AdGuard Home. Essas informações são coletadas de várias fontes, incluindo arquivos de hosts, DNS reverso, etc.",
"access_title": "Configurações de acessos", "access_title": "Configurações de acessos",
"access_desc": "Aqui você pode configurar as regras de acesso para o servidores de DNS do AdGuard Home", "access_desc": "Aqui você pode configurar as regras de acesso para o servidores de DNS do AdGuard Home",
"access_allowed_title": "Clientes permitidos", "access_allowed_title": "Clientes permitidos",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Tem a certeza de que deseja excluir o cliente \"{{key}}\"?", "client_confirm_delete": "Tem a certeza de que deseja excluir o cliente \"{{key}}\"?",
"list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?", "list_confirm_delete": "Você tem certeza de que deseja excluir essa lista?",
"auto_clients_title": "Clientes ativos", "auto_clients_title": "Clientes ativos",
"auto_clients_desc": "Dispositivo não está na lista de dispositivos persistentes que podem ser utilizados no AdGuard Home", "auto_clients_desc": "Informações sobre endereços IP de dispositivos que estão a utilizar ou podem utilizar o AdGuard Home. Estas informações são recolhidas a partir de várias fontes, incluindo ficheiros hosts, DNS reverso etc.",
"access_title": "Definições de acesso", "access_title": "Definições de acesso",
"access_desc": "Aqui pode configurar as regras de acesso para o servidores de DNS do AdGuard Home", "access_desc": "Aqui pode configurar as regras de acesso para o servidores de DNS do AdGuard Home",
"access_allowed_title": "Clientes permitidos", "access_allowed_title": "Clientes permitidos",

View File

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

View File

@@ -135,7 +135,7 @@
"number_of_dns_query_to_safe_search": "Количество запросов DNS для поисковых систем, для которых был применён Безопасный поиск", "number_of_dns_query_to_safe_search": "Количество запросов DNS для поисковых систем, для которых был применён Безопасный поиск",
"average_processing_time": "Среднее время обработки запроса", "average_processing_time": "Среднее время обработки запроса",
"average_processing_time_hint": "Среднее время для обработки запроса DNS в миллисекундах", "average_processing_time_hint": "Среднее время для обработки запроса DNS в миллисекундах",
"block_domain_use_filters_and_hosts": "Блокировать домены с использованием фильтров и файлов хостов", "block_domain_use_filters_and_hosts": "Блокировать домены с использованием фильтров и файлов hosts",
"filters_block_toggle_hint": "Вы можете настроить правила блокировки в <a>«Фильтрах»</a>.", "filters_block_toggle_hint": "Вы можете настроить правила блокировки в <a>«Фильтрах»</a>.",
"use_adguard_browsing_sec": "Включить Безопасную навигацию AdGuard", "use_adguard_browsing_sec": "Включить Безопасную навигацию AdGuard",
"use_adguard_browsing_sec_hint": "AdGuard Home проверит, включён ли домен в веб-службу безопасности браузера. Он будет использовать API, чтобы выполнить проверку: на сервер отправляется только короткий префикс имени домена SHA256.", "use_adguard_browsing_sec_hint": "AdGuard Home проверит, включён ли домен в веб-службу безопасности браузера. Он будет использовать API, чтобы выполнить проверку: на сервер отправляется только короткий префикс имени домена SHA256.",
@@ -296,7 +296,7 @@
"rate_limit_desc": "Ограничение на количество запросов в секунду для каждого клиента (0 — неограниченно).", "rate_limit_desc": "Ограничение на количество запросов в секунду для каждого клиента (0 — неограниченно).",
"blocking_ipv4_desc": "IP-адрес, возвращаемый при блокировке A-запроса", "blocking_ipv4_desc": "IP-адрес, возвращаемый при блокировке A-запроса",
"blocking_ipv6_desc": "IP-адрес, возвращаемый при блокировке AAAA-запроса", "blocking_ipv6_desc": "IP-адрес, возвращаемый при блокировке AAAA-запроса",
"blocking_mode_default": "Стандартный: Отвечает с нулевым IP-адресом, (0.0.0.0 для A; :: для AAAA) когда заблокировано правилом в стиле Adblock; отвечает с IP-адресом, указанным в правиле, когда заблокировано правилом в стиле /etc/hosts-style", "blocking_mode_default": "Стандартный: Отвечает с нулевым IP-адресом, (0.0.0.0 для A; :: для AAAA) когда заблокировано правилом в стиле Adblock; отвечает с IP-адресом, указанным в правиле, когда заблокировано правилом в стиле файлов hosts",
"blocking_mode_refused": "REFUSED: Отвечает с кодом REFUSED", "blocking_mode_refused": "REFUSED: Отвечает с кодом REFUSED",
"blocking_mode_nxdomain": "NXDOMAIN: Отвечает с кодом NXDOMAIN\n", "blocking_mode_nxdomain": "NXDOMAIN: Отвечает с кодом NXDOMAIN\n",
"blocking_mode_null_ip": "Нулевой IP: Отвечает с нулевым IP-адресом (0.0.0.0 для A; :: для AAAA)", "blocking_mode_null_ip": "Нулевой IP: Отвечает с нулевым IP-адресом (0.0.0.0 для A; :: для AAAA)",
@@ -444,7 +444,7 @@
"client_confirm_delete": "Вы уверены, что хотите удалить клиента «{{key}}»?", "client_confirm_delete": "Вы уверены, что хотите удалить клиента «{{key}}»?",
"list_confirm_delete": "Вы уверены, что хотите удалить этот список?", "list_confirm_delete": "Вы уверены, что хотите удалить этот список?",
"auto_clients_title": "Клиенты (runtime)", "auto_clients_title": "Клиенты (runtime)",
"auto_clients_desc": "Несохранённые клиенты, которые могут пользоваться AdGuard Home", "auto_clients_desc": "Информация об IP-адресах устройств, которые используют или могут использовать AdGuard Home. Эта информация собирается из нескольких источников, включая файлы hosts, обратный DNS и так далее.",
"access_title": "Настройки доступа", "access_title": "Настройки доступа",
"access_desc": "Здесь вы можете настроить правила доступа к DNS-серверу AdGuard Home", "access_desc": "Здесь вы можете настроить правила доступа к DNS-серверу AdGuard Home",
"access_allowed_title": "Разрешённые клиенты", "access_allowed_title": "Разрешённые клиенты",

View File

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

View File

@@ -387,7 +387,7 @@
"encryption_key": "Súkromný kľúč", "encryption_key": "Súkromný kľúč",
"encryption_key_input": "Skopírujte a prilepte sem svoj súkromný kľúč vo formáte PEM pre Váš certifikát.", "encryption_key_input": "Skopírujte a prilepte sem svoj súkromný kľúč vo formáte PEM pre Váš certifikát.",
"encryption_enable": "Zapnite šifrovanie (HTTPS, DNS-cez-HTTPS a DNS-cez-TLS)", "encryption_enable": "Zapnite šifrovanie (HTTPS, DNS-cez-HTTPS a DNS-cez-TLS)",
"encryption_enable_desc": "Ak je šifrovanie zapnuté, AdGuard Home administrátorské rozhranie bude pracovať cez HTTPS a DNS server bude počúvať požiadavky cez DNS-cez-HTTPS a DNS-cez-TLS.", "encryption_enable_desc": "Ak je šifrovanie zapnuté, AdGuard Home administrátorské rozhranie bude pracovať cez HTTPS a DNS server bude počúvať dopyty cez DNS-cez-HTTPS a DNS-cez-TLS.",
"encryption_chain_valid": "Certifikačný reťazec je platný", "encryption_chain_valid": "Certifikačný reťazec je platný",
"encryption_chain_invalid": "Certifikačný reťazec je neplatný", "encryption_chain_invalid": "Certifikačný reťazec je neplatný",
"encryption_key_valid": "Toto je platný {{type}} súkromný kľúč", "encryption_key_valid": "Toto je platný {{type}} súkromný kľúč",
@@ -444,7 +444,7 @@
"client_confirm_delete": "Naozaj chcete vymazať \"{{key}}\" klienta?", "client_confirm_delete": "Naozaj chcete vymazať \"{{key}}\" klienta?",
"list_confirm_delete": "Naozaj chcete vymazať tento zoznam?", "list_confirm_delete": "Naozaj chcete vymazať tento zoznam?",
"auto_clients_title": "Runtime klienti", "auto_clients_title": "Runtime klienti",
"auto_clients_desc": "Zariadenia, ktoré nie sú na zozname trvalých klientov, ktorí môžu stále používať AdGuard Home", "auto_clients_desc": "Informácie o IP adresách zariadení, ktoré používajú alebo môžu používať AdGuard Home. Tieto informácie sa získavajú z viacerých zdrojov vrátane súborov hosts, reverzného DNS atď.",
"access_title": "Nastavenia prístupu", "access_title": "Nastavenia prístupu",
"access_desc": "Tu môžete konfigurovať pravidlá prístupu pre server DNS AdGuard Home.", "access_desc": "Tu môžete konfigurovať pravidlá prístupu pre server DNS AdGuard Home.",
"access_allowed_title": "Povolení klienti", "access_allowed_title": "Povolení klienti",
@@ -497,7 +497,7 @@
"blocked_services": "Blokované služby", "blocked_services": "Blokované služby",
"blocked_services_desc": "Umožňuje rýchlo blokovať populárne stránky a služby.", "blocked_services_desc": "Umožňuje rýchlo blokovať populárne stránky a služby.",
"blocked_services_saved": "Blokované služby boli úspešne uložené", "blocked_services_saved": "Blokované služby boli úspešne uložené",
"blocked_services_global": "Použite globálne blokované služby", "blocked_services_global": "Použiť globálne blokované služby",
"blocked_service": "Blokované služby", "blocked_service": "Blokované služby",
"block_all": "Blokovať všetko", "block_all": "Blokovať všetko",
"unblock_all": "Odblokovať všetko", "unblock_all": "Odblokovať všetko",
@@ -554,7 +554,7 @@
"whois": "WHOIS", "whois": "WHOIS",
"filtering_rules_learn_more": "<0>Dozvedieť sa viac</0> o tvorbe vlastných zoznamov hostiteľov.", "filtering_rules_learn_more": "<0>Dozvedieť sa viac</0> o tvorbe vlastných zoznamov hostiteľov.",
"blocked_by_response": "Blokované pomocou CNAME alebo IP v odpovedi", "blocked_by_response": "Blokované pomocou CNAME alebo IP v odpovedi",
"blocked_by_cname_or_ip": "Zablokované na základe CNAME alebo IP", "blocked_by_cname_or_ip": "Blokované pomocou CNAME alebo IP",
"try_again": "Skúste znova", "try_again": "Skúste znova",
"domain_desc": "Zadajte meno domény alebo zástupný znak, ktorý chcete prepísať.", "domain_desc": "Zadajte meno domény alebo zástupný znak, ktorý chcete prepísať.",
"example_rewrite_domain": "prepísať odpovede iba pre toto meno domény.", "example_rewrite_domain": "prepísať odpovede iba pre toto meno domény.",
@@ -571,7 +571,7 @@
"autofix_warning_list": "Bude vykonávať tieto úlohy: <0>Deaktivovať systém DNSStubListener</0> <0>Nastaviť adresu servera DNS na 127.0.0.1</0> <0>Nahradiť cieľový symbolický odkaz /etc/resolv.conf na /run/systemd/resolve/resolv.conf</0> <0>Zastaviť službu DNSStubListener (znova načítať službu systemd-resolved)</0>", "autofix_warning_list": "Bude vykonávať tieto úlohy: <0>Deaktivovať systém DNSStubListener</0> <0>Nastaviť adresu servera DNS na 127.0.0.1</0> <0>Nahradiť cieľový symbolický odkaz /etc/resolv.conf na /run/systemd/resolve/resolv.conf</0> <0>Zastaviť službu DNSStubListener (znova načítať službu systemd-resolved)</0>",
"autofix_warning_result": "Výsledkom bude, že všetky DNS dopyty z Vášho systému budú štandardne spracované službou AdGuard Home.", "autofix_warning_result": "Výsledkom bude, že všetky DNS dopyty z Vášho systému budú štandardne spracované službou AdGuard Home.",
"tags_title": "Tagy", "tags_title": "Tagy",
"tags_desc": "Môžete vybrať značky, ktoré zodpovedajú klientovi. Zahrňte značky do pravidiel filtrovania, aby ste ich použili presnejšie. <0>Viac informácií</0>.", "tags_desc": "Môžete vybrať značky, ktoré zodpovedajú klientovi. Zahrňte značky do pravidiel filtrácie, aby ste ich použili presnejšie. <0>Viac informácií</0>.",
"form_select_tags": "Zvoľte tagy klienta", "form_select_tags": "Zvoľte tagy klienta",
"check_title": "Skontrolujte filtráciu", "check_title": "Skontrolujte filtráciu",
"check_desc": "Skontrolujte, či je názov hostiteľa filtrovaný.", "check_desc": "Skontrolujte, či je názov hostiteľa filtrovaný.",
@@ -608,7 +608,7 @@
"show_whitelisted_responses": "Obsiahnuté v bielej listine", "show_whitelisted_responses": "Obsiahnuté v bielej listine",
"show_processed_responses": "Spracované", "show_processed_responses": "Spracované",
"blocked_safebrowsing": "Zablokované modulom Bezpečné prehliadanie", "blocked_safebrowsing": "Zablokované modulom Bezpečné prehliadanie",
"blocked_adult_websites": "Zablokovaná stránka pre dospelých", "blocked_adult_websites": "Zablokované Rodičovskou kontrolou",
"blocked_threats": "Zablokované hrozby", "blocked_threats": "Zablokované hrozby",
"allowed": "Povolené", "allowed": "Povolené",
"filtered": "Filtrované", "filtered": "Filtrované",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Ali ste prepričani, da želite izbrisati odjemalca \"{{key}}\"?", "client_confirm_delete": "Ali ste prepričani, da želite izbrisati odjemalca \"{{key}}\"?",
"list_confirm_delete": "Ali ste prepričani, da želite izbrisati ta seznam?", "list_confirm_delete": "Ali ste prepričani, da želite izbrisati ta seznam?",
"auto_clients_title": "Odjemalci izvajanja", "auto_clients_title": "Odjemalci izvajanja",
"auto_clients_desc": "Naprave, ki niso na seznamu trajnih odjemalcev, ki morda še vedno uporabljajo AdGuard Home", "auto_clients_desc": "Informacije o naslovih IP naprav, ki uporabljajo ali bi lahko uporabljale AdGuard Home. Te informacije so zbrane iz več virov, vključno z datotekami gostiteljev, povratnim DNS-jem itd.",
"access_title": "Nastavitve dostopa", "access_title": "Nastavitve dostopa",
"access_desc": "Tukaj lahko nastavite pravila dostopa strežnika DNS AdGuard Home", "access_desc": "Tukaj lahko nastavite pravila dostopa strežnika DNS AdGuard Home",
"access_allowed_title": "Dovoljeni odjemalci", "access_allowed_title": "Dovoljeni odjemalci",

View File

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

View File

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

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "\"{{key}}\" istemcisini silmek istediğinizden emin misiniz?", "client_confirm_delete": "\"{{key}}\" istemcisini silmek istediğinizden emin misiniz?",
"list_confirm_delete": "Bu listeyi silmek istediğinizden emin misiniz?", "list_confirm_delete": "Bu listeyi silmek istediğinizden emin misiniz?",
"auto_clients_title": "Çalışma zamanı istemcileri", "auto_clients_title": "Çalışma zamanı istemcileri",
"auto_clients_desc": "Henüz AdGuard Home'u kullanabilecek Kalıcı istemciler listesinde olmayan cihazlar", "auto_clients_desc": "AdGuard Home'u kullanan veya kullanabilecek cihazların IP adresleri hakkında bilgiler. Bu bilgiler, hosts dosyaları, ters DNS, vb. dahil olmak üzere çeşitli kaynaklardan toplanır.",
"access_title": "Erişim ayarları", "access_title": "Erişim ayarları",
"access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz", "access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz",
"access_allowed_title": "İzin verilen istemciler", "access_allowed_title": "İzin verilen istemciler",

View File

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

View File

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

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "您确定要删除客户端 \"{{key}}\"", "client_confirm_delete": "您确定要删除客户端 \"{{key}}\"",
"list_confirm_delete": "您确定要删除此列表吗?", "list_confirm_delete": "您确定要删除此列表吗?",
"auto_clients_title": "客户端(运行时间)", "auto_clients_title": "客户端(运行时间)",
"auto_clients_desc": "不在可继续使用 AdGuard Home 的持久客户端列表中的设备。", "auto_clients_desc": "有关正在使用或可能使用 AdGuard Home 的设备的 IP 地址的信息。此信息是从多个来源收集的,包括 hosts 文件、反向 DNS 等。",
"access_title": "访问设置", "access_title": "访问设置",
"access_desc": "您可以在此处配置 AdGuard Home 的 DNS 服务器的访问规则", "access_desc": "您可以在此处配置 AdGuard Home 的 DNS 服务器的访问规则",
"access_allowed_title": "允许的客户端", "access_allowed_title": "允许的客户端",

View File

@@ -164,7 +164,7 @@
"disabled_parental_toast": "已停用家長監護", "disabled_parental_toast": "已停用家長監護",
"enabled_parental_toast": "已啟用家長監護", "enabled_parental_toast": "已啟用家長監護",
"disabled_safe_search_toast": "已停用安全搜尋", "disabled_safe_search_toast": "已停用安全搜尋",
"enabled_save_search_toast": "已啟用安全搜尋", "updated_save_search_toast": "已更新安全搜尋設定",
"enabled_table_header": "啟用", "enabled_table_header": "啟用",
"name_table_header": "名稱", "name_table_header": "名稱",
"list_url_table_header": "清單 URL 網址", "list_url_table_header": "清單 URL 網址",
@@ -211,6 +211,10 @@
"example_upstream_doq": "加密 <0>DNS-over-QUIC</0>", "example_upstream_doq": "加密 <0>DNS-over-QUIC</0>",
"example_upstream_sdns": "您可以使透過 <0>DNS Stamps</0> 來解析 <1>DNSCrypt</1> 或 <2>DNS-over-HTTPS</2>", "example_upstream_sdns": "您可以使透過 <0>DNS Stamps</0> 來解析 <1>DNSCrypt</1> 或 <2>DNS-over-HTTPS</2>",
"example_upstream_tcp": "一般 DNS透過 TCP", "example_upstream_tcp": "一般 DNS透過 TCP",
"example_upstream_regular_port": "一般 DNS透過 UDP連接埠",
"example_upstream_udp": "一般 DNS透過 UDP主機名稱",
"example_upstream_tcp_port": "一般 DNS透過 TCP連接埠",
"example_upstream_tcp_hostname": "一般 DNS透過 TCP主機名稱",
"all_lists_up_to_date_toast": "所有清單已更新至最新", "all_lists_up_to_date_toast": "所有清單已更新至最新",
"dns_test_ok_toast": "設定中的 DNS 上游運作正常", "dns_test_ok_toast": "設定中的 DNS 上游運作正常",
"dns_test_not_ok_toast": "DNS 設定中的 \"{{key}}\" 出現錯誤,請確認是否正確輸入", "dns_test_not_ok_toast": "DNS 設定中的 \"{{key}}\" 出現錯誤,請確認是否正確輸入",
@@ -468,6 +472,7 @@
"rewrite_added": "「{{key}}」的 DNS 覆寫新增成功", "rewrite_added": "「{{key}}」的 DNS 覆寫新增成功",
"rewrite_deleted": "「{{key}}」的 DNS 覆寫刪除成功", "rewrite_deleted": "「{{key}}」的 DNS 覆寫刪除成功",
"rewrite_add": "新增 DNS 覆寫", "rewrite_add": "新增 DNS 覆寫",
"rewrite_edit": "編輯 DNS 覆寫",
"rewrite_not_found": "找不到 DNS 覆寫", "rewrite_not_found": "找不到 DNS 覆寫",
"rewrite_confirm_delete": "您確定要刪除 \"{{key}}\" 的 DNS 覆寫?", "rewrite_confirm_delete": "您確定要刪除 \"{{key}}\" 的 DNS 覆寫?",
"rewrite_desc": "提供簡單的方式對特定網域自訂 DNS 回應。", "rewrite_desc": "提供簡單的方式對特定網域自訂 DNS 回應。",
@@ -501,6 +506,7 @@
"interval_days": "{{count}} 天", "interval_days": "{{count}} 天",
"interval_days_plural": "{{count}} 天", "interval_days_plural": "{{count}} 天",
"domain": "網域", "domain": "網域",
"ecs": "EDNS 子網",
"punycode": "Punycode", "punycode": "Punycode",
"answer": "回應", "answer": "回應",
"filter_added_successfully": "已成功新增清單", "filter_added_successfully": "已成功新增清單",
@@ -514,6 +520,8 @@
"statistics_retention_confirm": "您確定要更改統計資料保存時間嗎?如果您縮短期限部分資料可能將會遺失", "statistics_retention_confirm": "您確定要更改統計資料保存時間嗎?如果您縮短期限部分資料可能將會遺失",
"statistics_cleared": "已清除統計資料", "statistics_cleared": "已清除統計資料",
"statistics_enable": "啟用統計數據", "statistics_enable": "啟用統計數據",
"ignore_domains": "已忽略網域(每行一個)",
"ignore_domains_title": "已忽略網域",
"interval_hours": "{{count}} 小時", "interval_hours": "{{count}} 小時",
"interval_hours_plural": "{{count}} 小時", "interval_hours_plural": "{{count}} 小時",
"filters_configuration": "過濾器設定", "filters_configuration": "過濾器設定",
@@ -626,6 +634,7 @@
"safe_browsing": "安全瀏覽", "safe_browsing": "安全瀏覽",
"served_from_cache": "{{value}} <i>(由快取回應)</i>", "served_from_cache": "{{value}} <i>(由快取回應)</i>",
"form_error_password_length": "密碼必須至少 {{value}} 個字元長度", "form_error_password_length": "密碼必須至少 {{value}} 個字元長度",
"make_static": "新增為靜態",
"theme_dark_desc": "深色主題", "theme_dark_desc": "深色主題",
"theme_light_desc": "淺色主題", "theme_light_desc": "淺色主題",
"disable_for_seconds": "{{count}} 秒", "disable_for_seconds": "{{count}} 秒",

View File

@@ -138,9 +138,9 @@
"block_domain_use_filters_and_hosts": "透過過濾器和主機檔案封鎖網域", "block_domain_use_filters_and_hosts": "透過過濾器和主機檔案封鎖網域",
"filters_block_toggle_hint": "您可在<a>過濾器</a>設定中設置封鎖規則。", "filters_block_toggle_hint": "您可在<a>過濾器</a>設定中設置封鎖規則。",
"use_adguard_browsing_sec": "使用 AdGuard 瀏覽安全網路服務", "use_adguard_browsing_sec": "使用 AdGuard 瀏覽安全網路服務",
"use_adguard_browsing_sec_hint": "AdGuard Home 將檢查該網域是否被瀏覽安全網路服務封鎖。它將使用友好的隱私查找應用程式介面API以執行檢查僅域名 SHA256 雜湊的短前綴被傳送到該伺服器。", "use_adguard_browsing_sec_hint": "AdGuard Home 將檢查該網域是否被瀏覽安全網路服務封鎖。它將使用對隱私友好的查找應用程式介面API以執行檢查僅域名 SHA256 雜湊的短前綴被傳送到該伺服器。",
"use_adguard_parental": "使用 AdGuard 家長控制之網路服務", "use_adguard_parental": "使用 AdGuard 家長控制之網路服務",
"use_adguard_parental_hint": "AdGuard Home 將檢查網域是否包含成人資料。它使用如同瀏覽安全網路服務一樣之友好的隱私應用程式介面API。", "use_adguard_parental_hint": "AdGuard Home 將檢查網域是否包含成人資料。它使用如同瀏覽安全網路服務一樣之對隱私友好的應用程式介面API。",
"enforce_safe_search": "使用安全搜尋", "enforce_safe_search": "使用安全搜尋",
"enforce_save_search_hint": "AdGuard Home 將在下列的搜尋引擎Google、YouTube、Bing、DuckDuckGo、Yandex 和 Pixabay 中強制執行安全搜尋。", "enforce_save_search_hint": "AdGuard Home 將在下列的搜尋引擎Google、YouTube、Bing、DuckDuckGo、Yandex 和 Pixabay 中強制執行安全搜尋。",
"no_servers_specified": "無已明確指定的伺服器", "no_servers_specified": "無已明確指定的伺服器",
@@ -444,7 +444,7 @@
"client_confirm_delete": "您確定您想要刪除用戶端 \"{{key}}\" 嗎?", "client_confirm_delete": "您確定您想要刪除用戶端 \"{{key}}\" 嗎?",
"list_confirm_delete": "您確定您想要刪除該清單嗎?", "list_confirm_delete": "您確定您想要刪除該清單嗎?",
"auto_clients_title": "執行時期用戶端", "auto_clients_title": "執行時期用戶端",
"auto_clients_desc": "未於可能仍然使用 AdGuard Home 的持續性用戶端之清單上的裝置", "auto_clients_desc": "AdGuard Home 使用或可能使用的裝置的 IP 地址資訊。這些資訊來自多個來源,包括主機檔案、反向 DNS 等。",
"access_title": "存取設定", "access_title": "存取設定",
"access_desc": "於此您可配置用於 AdGuard Home DNS 伺服器之存取規則", "access_desc": "於此您可配置用於 AdGuard Home DNS 伺服器之存取規則",
"access_allowed_title": "已允許的用戶端", "access_allowed_title": "已允許的用戶端",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -64,12 +64,6 @@ export default {
"homepage": "https://github.com/MasterKia/PersianBlocker", "homepage": "https://github.com/MasterKia/PersianBlocker",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_19.txt" "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_19.txt"
}, },
"ITA_filtri_dns": {
"name": "ITA: Filtri-DNS",
"categoryId": "regional",
"homepage": "https://filtri-dns.ga/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt"
},
"KOR_list_kr": { "KOR_list_kr": {
"name": "KOR: List-KR DNS", "name": "KOR: List-KR DNS",
"categoryId": "regional", "categoryId": "regional",
@@ -166,14 +160,20 @@ export default {
"homepage": "https://github.com/DandelionSprout/adfilt", "homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_12.txt" "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_12.txt"
}, },
"dandelion_sprouts_anti_push_notifications": {
"name": "Dandelion Sprout's Anti Push Notifications",
"categoryId": "other",
"homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_39.txt"
},
"dandelion_sprouts_game_console_adblock_list": { "dandelion_sprouts_game_console_adblock_list": {
"name": "Dandelion Sprout's Game Console Adblock List", "name": "Dandelion Sprout's Game Console Adblock List",
"categoryId": "other", "categoryId": "other",
"homepage": "https://github.com/DandelionSprout/adfilt", "homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_6.txt" "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_6.txt"
}, },
"hagezi_personal": { "hagezi_multinormal": {
"name": "HaGeZi Personal Black \u0026 White", "name": "HaGeZi Multi NORMAL",
"categoryId": "general", "categoryId": "general",
"homepage": "https://github.com/hagezi/dns-blocklists", "homepage": "https://github.com/hagezi/dns-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_34.txt" "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_34.txt"

View File

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

View File

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

View File

@@ -41,18 +41,12 @@ RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
# 68 : UDP : DHCP (client) # 68 : UDP : DHCP (client)
# 80 : TCP : HTTP (main) # 80 : TCP : HTTP (main)
# 443 : TCP, UDP : HTTPS, DNS-over-HTTPS (incl. HTTP/3), DNSCrypt (main) # 443 : TCP, UDP : HTTPS, DNS-over-HTTPS (incl. HTTP/3), DNSCrypt (main)
# 784 : UDP : DNS-over-QUIC (deprecated; use 853)
# 853 : TCP, UDP : DNS-over-TLS, DNS-over-QUIC # 853 : TCP, UDP : DNS-over-TLS, DNS-over-QUIC
# 3000 : TCP, UDP : HTTP(S) (alt, incl. HTTP/3) # 3000 : TCP, UDP : HTTP(S) (alt, incl. HTTP/3)
# 5443 : TCP, UDP : DNSCrypt (alt) # 5443 : TCP, UDP : DNSCrypt (alt)
# 6060 : TCP : HTTP (pprof) # 6060 : TCP : HTTP (pprof)
# 8853 : UDP : DNS-over-QUIC (deprecated; use 853) EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 443/udp 853/tcp\
# 853/udp 3000/tcp 3000/udp 5443/tcp 5443/udp 6060/tcp
# TODO(a.garipov): Remove the old, non-standard 784 and 8853 ports for
# DNS-over-QUIC in a future release.
EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 443/udp 784/udp\
853/tcp 853/udp 3000/tcp 3000/udp 5443/tcp 5443/udp 6060/tcp\
8853/udp
WORKDIR /opt/adguardhome/work WORKDIR /opt/adguardhome/work

34
go.mod
View File

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

56
go.sum
View File

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

View File

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

43
internal/aghnet/addr.go Normal file
View File

@@ -0,0 +1,43 @@
package aghnet
import (
"fmt"
"strings"
"github.com/AdguardTeam/golibs/stringutil"
)
// NormalizeDomain returns a lowercased version of host without the final dot,
// unless host is ".", in which case it returns it unchanged. That is a special
// case that to allow matching queries like:
//
// dig IN NS '.'
func NormalizeDomain(host string) (norm string) {
if host == "." {
return host
}
return strings.ToLower(strings.TrimSuffix(host, "."))
}
// NewDomainNameSet returns nil and error, if list has duplicate or empty domain
// name. Otherwise returns a set, which contains domain names normalized using
// [NormalizeDomain].
func NewDomainNameSet(list []string) (set *stringutil.Set, err error) {
set = stringutil.NewSet()
for i, host := range list {
if host == "" {
return nil, fmt.Errorf("at index %d: hostname is empty", i)
}
host = NormalizeDomain(host)
if set.Has(host) {
return nil, fmt.Errorf("duplicate hostname %q at index %d", host, i)
}
set.Add(host)
}
return set, nil
}

View File

@@ -0,0 +1,59 @@
package aghnet_test
import (
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
)
func TestNewDomainNameSet(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
wantErrMsg string
in []string
}{{
name: "nil",
wantErrMsg: "",
in: nil,
}, {
name: "success",
wantErrMsg: "",
in: []string{
"Domain.Example",
".",
},
}, {
name: "dups",
wantErrMsg: `duplicate hostname "domain.example" at index 1`,
in: []string{
"Domain.Example",
"domain.example",
},
}, {
name: "bad_domain",
wantErrMsg: "at index 0: hostname is empty",
in: []string{
"",
},
}}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
set, err := aghnet.NewDomainNameSet(tc.in)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
if err != nil {
return
}
for _, host := range tc.in {
assert.Truef(t, set.Has(aghnet.NormalizeDomain(host)), "%q not matched", host)
}
})
}
}

View File

@@ -1,12 +1,8 @@
package aghnet package aghnet
import ( import (
"fmt"
"net/netip" "net/netip"
"strings" "strings"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/stringutil"
) )
// GenerateHostname generates the hostname from ip. In case of using IPv4 the // GenerateHostname generates the hostname from ip. In case of using IPv4 the
@@ -29,32 +25,8 @@ func GenerateHostname(ip netip.Addr) (hostname string) {
hostname = ip.StringExpanded() hostname = ip.StringExpanded()
if ip.Is4() { if ip.Is4() {
return strings.Replace(hostname, ".", "-", -1) return strings.ReplaceAll(hostname, ".", "-")
} }
return strings.Replace(hostname, ":", "-", -1) return strings.ReplaceAll(hostname, ":", "-")
}
// NewDomainNameSet returns nil and error, if list has duplicate or empty
// domain name. Otherwise returns a set, which contains non-FQDN domain names,
// and nil error.
func NewDomainNameSet(list []string) (set *stringutil.Set, err error) {
set = stringutil.NewSet()
for i, v := range list {
host := strings.ToLower(strings.TrimSuffix(v, "."))
// TODO(a.garipov): Think about ignoring empty (".") names in the
// future.
if host == "" {
return nil, errors.Error("host name is empty")
}
if set.Has(host) {
return nil, fmt.Errorf("duplicate host name %q at index %d", host, i)
}
set.Add(host)
}
return set, nil
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

302
internal/client/addrproc.go Normal file
View File

@@ -0,0 +1,302 @@
package client
import (
"context"
"net/netip"
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
)
// ErrClosed is returned from [AddressProcessor.Close] if it's closed more than
// once.
const ErrClosed errors.Error = "use of closed address processor"
// AddressProcessor is the interface for types that can process clients.
type AddressProcessor interface {
Process(ip netip.Addr)
Close() (err error)
}
// EmptyAddrProc is an [AddressProcessor] that does nothing.
type EmptyAddrProc struct{}
// type check
var _ AddressProcessor = EmptyAddrProc{}
// Process implements the [AddressProcessor] interface for EmptyAddrProc.
func (EmptyAddrProc) Process(_ netip.Addr) {}
// Close implements the [AddressProcessor] interface for EmptyAddrProc.
func (EmptyAddrProc) Close() (_ error) { return nil }
// DefaultAddrProcConfig is the configuration structure for address processors.
type DefaultAddrProcConfig struct {
// DialContext is used to create TCP connections to WHOIS servers.
// DialContext must not be nil if [DefaultAddrProcConfig.UseWHOIS] is true.
DialContext aghnet.DialContextFunc
// Exchanger is used to perform rDNS queries. Exchanger must not be nil if
// [DefaultAddrProcConfig.UseRDNS] is true.
Exchanger rdns.Exchanger
// PrivateSubnets are used to determine if an incoming IP address is
// private. It must not be nil.
PrivateSubnets netutil.SubnetSet
// AddressUpdater is used to update the information about a client's IP
// address. It must not be nil.
AddressUpdater AddressUpdater
// InitialAddresses are the addresses that are queued for processing
// immediately by [NewDefaultAddrProc].
InitialAddresses []netip.Addr
// CatchPanics, if true, makes the address processor catch and log panics.
//
// TODO(a.garipov): Consider better ways to do this or apply this method to
// other parts of the codebase.
CatchPanics bool
// UseRDNS, if true, enables resolving of client IP addresses using reverse
// DNS.
UseRDNS bool
// UsePrivateRDNS, if true, enables resolving of private client IP addresses
// using reverse DNS. See [DefaultAddrProcConfig.PrivateSubnets].
UsePrivateRDNS bool
// UseWHOIS, if true, enables resolving of client IP addresses using WHOIS.
UseWHOIS bool
}
// AddressUpdater is the interface for storages of DNS clients that can update
// information about them.
//
// TODO(a.garipov): Consider using the actual client storage once it is moved
// into this package.
type AddressUpdater interface {
// UpdateAddress updates information about an IP address, setting host (if
// not empty) and WHOIS information (if not nil).
UpdateAddress(ip netip.Addr, host string, info *whois.Info)
}
// DefaultAddrProc processes incoming client addresses with rDNS and WHOIS, if
// configured, and updates that information in a client storage.
type DefaultAddrProc struct {
// clientIPsMu serializes closure of clientIPs and access to isClosed.
clientIPsMu *sync.Mutex
// clientIPs is the channel queueing client processing tasks.
clientIPs chan netip.Addr
// rdns is used to perform rDNS lookups of clients' IP addresses.
rdns rdns.Interface
// whois is used to perform WHOIS lookups of clients' IP addresses.
whois whois.Interface
// addrUpdater is used to update the information about a client's IP
// address.
addrUpdater AddressUpdater
// privateSubnets are used to determine if an incoming IP address is
// private.
privateSubnets netutil.SubnetSet
// isClosed is set to true once the address processor is closed.
isClosed bool
// usePrivateRDNS, if true, enables resolving of private client IP addresses
// using reverse DNS.
usePrivateRDNS bool
}
const (
// defaultQueueSize is the size of queue of IPs for rDNS and WHOIS
// processing.
defaultQueueSize = 255
// defaultCacheSize is the maximum size of the cache for rDNS and WHOIS
// processing. It must be greater than zero.
defaultCacheSize = 10_000
// defaultIPTTL is the Time to Live duration for IP addresses cached by
// rDNS and WHOIS.
defaultIPTTL = 1 * time.Hour
)
// NewDefaultAddrProc returns a new running client address processor. c must
// not be nil.
func NewDefaultAddrProc(c *DefaultAddrProcConfig) (p *DefaultAddrProc) {
p = &DefaultAddrProc{
clientIPsMu: &sync.Mutex{},
clientIPs: make(chan netip.Addr, defaultQueueSize),
rdns: &rdns.Empty{},
addrUpdater: c.AddressUpdater,
whois: &whois.Empty{},
privateSubnets: c.PrivateSubnets,
usePrivateRDNS: c.UsePrivateRDNS,
}
if c.UseRDNS {
p.rdns = rdns.New(&rdns.Config{
Exchanger: c.Exchanger,
CacheSize: defaultCacheSize,
CacheTTL: defaultIPTTL,
})
}
if c.UseWHOIS {
p.whois = newWHOIS(c.DialContext)
}
go p.process(c.CatchPanics)
for _, ip := range c.InitialAddresses {
p.Process(ip)
}
return p
}
// newWHOIS returns a whois.Interface instance using the given function for
// dialing.
func newWHOIS(dialFunc aghnet.DialContextFunc) (w whois.Interface) {
// TODO(s.chzhen): Consider making configurable.
const (
// defaultTimeout is the timeout for WHOIS requests.
defaultTimeout = 5 * time.Second
// defaultMaxConnReadSize is an upper limit in bytes for reading from a
// net.Conn.
defaultMaxConnReadSize = 64 * 1024
// defaultMaxRedirects is the maximum redirects count.
defaultMaxRedirects = 5
// defaultMaxInfoLen is the maximum length of whois.Info fields.
defaultMaxInfoLen = 250
)
return whois.New(&whois.Config{
DialContext: dialFunc,
ServerAddr: whois.DefaultServer,
Port: whois.DefaultPort,
Timeout: defaultTimeout,
CacheSize: defaultCacheSize,
MaxConnReadSize: defaultMaxConnReadSize,
MaxRedirects: defaultMaxRedirects,
MaxInfoLen: defaultMaxInfoLen,
CacheTTL: defaultIPTTL,
})
}
// type check
var _ AddressProcessor = (*DefaultAddrProc)(nil)
// Process implements the [AddressProcessor] interface for *DefaultAddrProc.
func (p *DefaultAddrProc) Process(ip netip.Addr) {
p.clientIPsMu.Lock()
defer p.clientIPsMu.Unlock()
if p.isClosed {
return
}
select {
case p.clientIPs <- ip:
// Go on.
default:
log.Debug("clients: ip channel is full; len: %d", len(p.clientIPs))
}
}
// process processes the incoming client IP-address information. It is intended
// to be used as a goroutine. Once clientIPs is closed, process exits.
func (p *DefaultAddrProc) process(catchPanics bool) {
if catchPanics {
defer log.OnPanic("addrProcessor.process")
}
log.Info("clients: processing addresses")
for ip := range p.clientIPs {
host := p.processRDNS(ip)
info := p.processWHOIS(ip)
p.addrUpdater.UpdateAddress(ip, host, info)
}
log.Info("clients: finished processing addresses")
}
// processRDNS resolves the clients' IP addresses using reverse DNS. host is
// empty if there were errors or if the information hasn't changed.
func (p *DefaultAddrProc) processRDNS(ip netip.Addr) (host string) {
start := time.Now()
log.Debug("clients: processing %s with rdns", ip)
defer func() {
log.Debug("clients: finished processing %s with rdns in %s", ip, time.Since(start))
}()
ok := p.shouldResolve(ip)
if !ok {
return
}
host, changed := p.rdns.Process(ip)
if !changed {
host = ""
}
return host
}
// shouldResolve returns false if ip is a loopback address, or ip is private and
// resolving of private addresses is disabled.
func (p *DefaultAddrProc) shouldResolve(ip netip.Addr) (ok bool) {
return !ip.IsLoopback() &&
(p.usePrivateRDNS || !p.privateSubnets.Contains(ip.AsSlice()))
}
// processWHOIS looks up the information about clients' IP addresses in the
// WHOIS databases. info is nil if there were errors or if the information
// hasn't changed.
func (p *DefaultAddrProc) processWHOIS(ip netip.Addr) (info *whois.Info) {
start := time.Now()
log.Debug("clients: processing %s with whois", ip)
defer func() {
log.Debug("clients: finished processing %s with whois in %s", ip, time.Since(start))
}()
// TODO(s.chzhen): Move the timeout logic from WHOIS configuration to the
// context.
info, changed := p.whois.Process(context.Background(), ip)
if !changed {
info = nil
}
return info
}
// Close implements the [AddressProcessor] interface for *DefaultAddrProc.
func (p *DefaultAddrProc) Close() (err error) {
p.clientIPsMu.Lock()
defer p.clientIPsMu.Unlock()
if p.isClosed {
return ErrClosed
}
close(p.clientIPs)
p.isClosed = true
return nil
}

View File

@@ -0,0 +1,262 @@
package client_test
import (
"context"
"fmt"
"io"
"net"
"net/netip"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/fakenet"
"github.com/stretchr/testify/assert"
)
func TestEmptyAddrProc(t *testing.T) {
t.Parallel()
p := client.EmptyAddrProc{}
assert.NotPanics(t, func() {
p.Process(testIP)
})
assert.NotPanics(t, func() {
err := p.Close()
assert.NoError(t, err)
})
}
func TestDefaultAddrProc_Process_rDNS(t *testing.T) {
t.Parallel()
privateIP := netip.MustParseAddr("192.168.0.1")
testCases := []struct {
rdnsErr error
ip netip.Addr
name string
host string
usePrivate bool
wantUpd bool
}{{
rdnsErr: nil,
ip: testIP,
name: "success",
host: testHost,
usePrivate: false,
wantUpd: true,
}, {
rdnsErr: nil,
ip: testIP,
name: "no_host",
host: "",
usePrivate: false,
wantUpd: false,
}, {
rdnsErr: nil,
ip: netip.MustParseAddr("127.0.0.1"),
name: "localhost",
host: "",
usePrivate: false,
wantUpd: false,
}, {
rdnsErr: nil,
ip: privateIP,
name: "private_ignored",
host: "",
usePrivate: false,
wantUpd: false,
}, {
rdnsErr: nil,
ip: privateIP,
name: "private_processed",
host: "private.example",
usePrivate: true,
wantUpd: true,
}, {
rdnsErr: errors.Error("rdns error"),
ip: testIP,
name: "rdns_error",
host: "",
usePrivate: false,
wantUpd: false,
}}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
updIPCh := make(chan netip.Addr, 1)
updHostCh := make(chan string, 1)
updInfoCh := make(chan *whois.Info, 1)
p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{
DialContext: func(_ context.Context, _, _ string) (conn net.Conn, err error) {
panic("not implemented")
},
Exchanger: &aghtest.Exchanger{
OnExchange: func(ip netip.Addr) (host string, ttl time.Duration, err error) {
return tc.host, 0, tc.rdnsErr
},
},
PrivateSubnets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
AddressUpdater: &aghtest.AddressUpdater{
OnUpdateAddress: newOnUpdateAddress(tc.wantUpd, updIPCh, updHostCh, updInfoCh),
},
CatchPanics: false,
UseRDNS: true,
UsePrivateRDNS: tc.usePrivate,
UseWHOIS: false,
})
testutil.CleanupAndRequireSuccess(t, p.Close)
p.Process(tc.ip)
if !tc.wantUpd {
return
}
gotIP, _ := testutil.RequireReceive(t, updIPCh, testTimeout)
assert.Equal(t, tc.ip, gotIP)
gotHost, _ := testutil.RequireReceive(t, updHostCh, testTimeout)
assert.Equal(t, tc.host, gotHost)
gotInfo, _ := testutil.RequireReceive(t, updInfoCh, testTimeout)
assert.Nil(t, gotInfo)
})
}
}
// newOnUpdateAddress is a test helper that returns a new OnUpdateAddress
// callback using the provided channels if an update is expected and panicking
// otherwise.
func newOnUpdateAddress(
want bool,
ips chan<- netip.Addr,
hosts chan<- string,
infos chan<- *whois.Info,
) (f func(ip netip.Addr, host string, info *whois.Info)) {
return func(ip netip.Addr, host string, info *whois.Info) {
if !want && (host != "" || info != nil) {
panic(fmt.Errorf("got unexpected update for %v with %q and %v", ip, host, info))
}
ips <- ip
hosts <- host
infos <- info
}
}
func TestDefaultAddrProc_Process_WHOIS(t *testing.T) {
t.Parallel()
testCases := []struct {
wantInfo *whois.Info
exchErr error
name string
wantUpd bool
}{{
wantInfo: &whois.Info{
City: testWHOISCity,
},
exchErr: nil,
name: "success",
wantUpd: true,
}, {
wantInfo: nil,
exchErr: nil,
name: "no_info",
wantUpd: false,
}, {
wantInfo: nil,
exchErr: errors.Error("whois error"),
name: "whois_error",
wantUpd: false,
}}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
whoisConn := &fakenet.Conn{
OnClose: func() (err error) { return nil },
OnRead: func(b []byte) (n int, err error) {
if tc.wantInfo == nil {
return 0, tc.exchErr
}
data := "city: " + tc.wantInfo.City + "\n"
copy(b, data)
return len(data), io.EOF
},
OnSetDeadline: func(_ time.Time) (err error) { return nil },
OnWrite: func(b []byte) (n int, err error) { return len(b), nil },
}
updIPCh := make(chan netip.Addr, 1)
updHostCh := make(chan string, 1)
updInfoCh := make(chan *whois.Info, 1)
p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{
DialContext: func(_ context.Context, _, _ string) (conn net.Conn, err error) {
return whoisConn, nil
},
Exchanger: &aghtest.Exchanger{
OnExchange: func(_ netip.Addr) (_ string, _ time.Duration, _ error) {
panic("not implemented")
},
},
PrivateSubnets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
AddressUpdater: &aghtest.AddressUpdater{
OnUpdateAddress: newOnUpdateAddress(tc.wantUpd, updIPCh, updHostCh, updInfoCh),
},
CatchPanics: false,
UseRDNS: false,
UsePrivateRDNS: false,
UseWHOIS: true,
})
testutil.CleanupAndRequireSuccess(t, p.Close)
p.Process(testIP)
if !tc.wantUpd {
return
}
gotIP, _ := testutil.RequireReceive(t, updIPCh, testTimeout)
assert.Equal(t, testIP, gotIP)
gotHost, _ := testutil.RequireReceive(t, updHostCh, testTimeout)
assert.Empty(t, gotHost)
gotInfo, _ := testutil.RequireReceive(t, updInfoCh, testTimeout)
assert.Equal(t, tc.wantInfo, gotInfo)
})
}
}
func TestDefaultAddrProc_Close(t *testing.T) {
t.Parallel()
p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{})
err := p.Close()
assert.NoError(t, err)
err = p.Close()
assert.ErrorIs(t, err, client.ErrClosed)
}

View File

@@ -0,0 +1,5 @@
// Package client contains types and logic dealing with AdGuard Home's DNS
// clients.
//
// TODO(a.garipov): Expand.
package client

View File

@@ -0,0 +1,25 @@
package client_test
import (
"net/netip"
"testing"
"time"
"github.com/AdguardTeam/golibs/testutil"
)
func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
// testHost is the common hostname for tests.
const testHost = "client.example"
// testTimeout is the common timeout for tests.
const testTimeout = 1 * time.Second
// testWHOISCity is the common city for tests.
const testWHOISCity = "Brussels"
// testIP is the common IP address for tests.
var testIP = netip.MustParseAddr("1.2.3.4")

View File

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

View File

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

View File

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

View File

@@ -13,6 +13,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls" "github.com/AdguardTeam/AdGuardHome/internal/aghtls"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
@@ -270,7 +271,13 @@ type ServerConfig struct {
UDPListenAddrs []*net.UDPAddr // UDP listen address UDPListenAddrs []*net.UDPAddr // UDP listen address
TCPListenAddrs []*net.TCPAddr // TCP listen address TCPListenAddrs []*net.TCPAddr // TCP listen address
UpstreamConfig *proxy.UpstreamConfig // Upstream DNS servers config UpstreamConfig *proxy.UpstreamConfig // Upstream DNS servers config
OnDNSRequest func(d *proxy.DNSContext)
// AddrProcConf defines the configuration for the client IP processor.
// If nil, [client.EmptyAddrProc] is used.
//
// TODO(a.garipov): The use of [client.EmptyAddrProc] is a crutch for tests.
// Remove that.
AddrProcConf *client.DefaultAddrProcConfig
FilteringConfig FilteringConfig
TLSConfig TLSConfig
@@ -298,9 +305,6 @@ type ServerConfig struct {
// DNS64Prefixes is a slice of NAT64 prefixes to be used for DNS64. // DNS64Prefixes is a slice of NAT64 prefixes to be used for DNS64.
DNS64Prefixes []netip.Prefix DNS64Prefixes []netip.Prefix
// ResolveClients signals if the RDNS should resolve clients' addresses.
ResolveClients bool
// UsePrivateRDNS defines if the PTR requests for unknown addresses from // UsePrivateRDNS defines if the PTR requests for unknown addresses from
// locally-served networks should be resolved via private PTR resolvers. // locally-served networks should be resolved via private PTR resolvers.
UsePrivateRDNS bool UsePrivateRDNS bool
@@ -340,6 +344,7 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
UpstreamConfig: srvConf.UpstreamConfig, UpstreamConfig: srvConf.UpstreamConfig,
BeforeRequestHandler: s.beforeRequestHandler, BeforeRequestHandler: s.beforeRequestHandler,
RequestHandler: s.handleDNSRequest, RequestHandler: s.handleDNSRequest,
HTTPSServerName: aghhttp.UserAgent(),
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled, EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
MaxGoroutines: int(srvConf.MaxGoroutines), MaxGoroutines: int(srvConf.MaxGoroutines),
UseDNS64: srvConf.UseDNS64, UseDNS64: srvConf.UseDNS64,

View File

@@ -0,0 +1,57 @@
package dnsforward
import (
"context"
"fmt"
"net"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
)
// DialContext is an [aghnet.DialContextFunc] that uses s to resolve hostnames.
func (s *Server) DialContext(ctx context.Context, network, addr string) (conn net.Conn, err error) {
log.Debug("dnsforward: dialing %q for network %q", addr, network)
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
dialer := &net.Dialer{
// TODO(a.garipov): Consider making configurable.
Timeout: time.Minute * 5,
}
if net.ParseIP(host) != nil {
return dialer.DialContext(ctx, network, addr)
}
addrs, err := s.Resolve(host)
if err != nil {
return nil, fmt.Errorf("resolving %q: %w", host, err)
}
log.Debug("dnsforward: resolving %q: %v", host, addrs)
if len(addrs) == 0 {
return nil, fmt.Errorf("no addresses for host %q", host)
}
var dialErrs []error
for _, a := range addrs {
addr = net.JoinHostPort(a.String(), port)
conn, err = dialer.DialContext(ctx, network, addr)
if err != nil {
dialErrs = append(dialErrs, err)
continue
}
return conn, err
}
// TODO(a.garipov): Use errors.Join in Go 1.20.
return nil, errors.List(fmt.Sprintf("dialing %q", addr), dialErrs...)
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/querylog" "github.com/AdguardTeam/AdGuardHome/internal/querylog"
@@ -99,8 +100,17 @@ type Server struct {
// must be a valid domain name plus dots on each side. // must be a valid domain name plus dots on each side.
localDomainSuffix string localDomainSuffix string
ipset ipsetCtx ipset ipsetCtx
privateNets netutil.SubnetSet privateNets netutil.SubnetSet
// addrProc, if not nil, is used to process clients' IP addresses with rDNS,
// WHOIS, etc.
addrProc client.AddressProcessor
// localResolvers is a DNS proxy instance used to resolve PTR records for
// addresses considered private as per the [privateNets].
//
// TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy.
localResolvers *proxy.Proxy localResolvers *proxy.Proxy
sysResolvers aghnet.SystemResolvers sysResolvers aghnet.SystemResolvers
@@ -170,6 +180,9 @@ const (
// NewServer creates a new instance of the dnsforward.Server // NewServer creates a new instance of the dnsforward.Server
// Note: this function must be called only once // Note: this function must be called only once
//
// TODO(a.garipov): How many constructors and initializers does this thing have?
// Refactor!
func NewServer(p DNSCreateParams) (s *Server, err error) { func NewServer(p DNSCreateParams) (s *Server, err error) {
var localDomainSuffix string var localDomainSuffix string
if p.LocalDomain == "" { if p.LocalDomain == "" {
@@ -231,7 +244,7 @@ func (s *Server) Close() {
s.serverLock.Lock() s.serverLock.Lock()
defer s.serverLock.Unlock() defer s.serverLock.Unlock()
s.dnsFilter = nil // TODO(s.chzhen): Remove it.
s.stats = nil s.stats = nil
s.queryLog = nil s.queryLog = nil
s.dnsProxy = nil s.dnsProxy = nil
@@ -257,14 +270,25 @@ func (s *Server) WriteDiskConfig(c *FilteringConfig) {
c.UpstreamDNS = stringutil.CloneSlice(sc.UpstreamDNS) c.UpstreamDNS = stringutil.CloneSlice(sc.UpstreamDNS)
} }
// RDNSSettings returns the copy of actual RDNS configuration. // LocalPTRResolvers returns the current local PTR resolver configuration.
func (s *Server) RDNSSettings() (localPTRResolvers []string, resolveClients, resolvePTR bool) { func (s *Server) LocalPTRResolvers() (localPTRResolvers []string) {
s.serverLock.RLock() s.serverLock.RLock()
defer s.serverLock.RUnlock() defer s.serverLock.RUnlock()
return stringutil.CloneSlice(s.conf.LocalPTRResolvers), return stringutil.CloneSlice(s.conf.LocalPTRResolvers)
s.conf.ResolveClients, }
s.conf.UsePrivateRDNS
// AddrProcConfig returns the current address processing configuration. Only
// fields c.UsePrivateRDNS, c.UseRDNS, and c.UseWHOIS are filled.
func (s *Server) AddrProcConfig() (c *client.DefaultAddrProcConfig) {
s.serverLock.RLock()
defer s.serverLock.RUnlock()
return &client.DefaultAddrProcConfig{
UsePrivateRDNS: s.conf.UsePrivateRDNS,
UseRDNS: s.conf.AddrProcConf.UseRDNS,
UseWHOIS: s.conf.AddrProcConf.UseWHOIS,
}
} }
// Resolve - get IP addresses by host name from an upstream server. // Resolve - get IP addresses by host name from an upstream server.
@@ -292,17 +316,13 @@ const (
var _ rdns.Exchanger = (*Server)(nil) var _ rdns.Exchanger = (*Server)(nil)
// Exchange implements the [rdns.Exchanger] interface for *Server. // Exchange implements the [rdns.Exchanger] interface for *Server.
func (s *Server) Exchange(ip netip.Addr) (host string, err error) { func (s *Server) Exchange(ip netip.Addr) (host string, ttl time.Duration, err error) {
s.serverLock.RLock() s.serverLock.RLock()
defer s.serverLock.RUnlock() defer s.serverLock.RUnlock()
if !s.conf.ResolveClients {
return "", nil
}
arpa, err := netutil.IPToReversedAddr(ip.AsSlice()) arpa, err := netutil.IPToReversedAddr(ip.AsSlice())
if err != nil { if err != nil {
return "", fmt.Errorf("reversing ip: %w", err) return "", 0, fmt.Errorf("reversing ip: %w", err)
} }
arpa = dns.Fqdn(arpa) arpa = dns.Fqdn(arpa)
@@ -318,71 +338,74 @@ func (s *Server) Exchange(ip netip.Addr) (host string, err error) {
Qclass: dns.ClassINET, Qclass: dns.ClassINET,
}}, }},
} }
ctx := &proxy.DNSContext{
dctx := &proxy.DNSContext{
Proto: "udp", Proto: "udp",
Req: req, Req: req,
StartTime: time.Now(), StartTime: time.Now(),
} }
var resolver *proxy.Proxy var resolver *proxy.Proxy
if s.isPrivateIP(ip) { var errMsg string
if s.privateNets.Contains(ip.AsSlice()) {
if !s.conf.UsePrivateRDNS { if !s.conf.UsePrivateRDNS {
return "", nil return "", 0, nil
} }
resolver = s.localResolvers resolver = s.localResolvers
errMsg = "resolving a private address: %w"
s.recDetector.add(*req) s.recDetector.add(*req)
} else { } else {
resolver = s.internalProxy resolver = s.internalProxy
errMsg = "resolving an address: %w"
}
if err = resolver.Resolve(dctx); err != nil {
return "", 0, fmt.Errorf(errMsg, err)
} }
if err = resolver.Resolve(ctx); err != nil { return hostFromPTR(dctx.Res)
return "", err
}
return hostFromPTR(ctx.Res)
} }
// hostFromPTR returns domain name from the PTR response or error. // hostFromPTR returns domain name from the PTR response or error.
func hostFromPTR(resp *dns.Msg) (host string, err error) { func hostFromPTR(resp *dns.Msg) (host string, ttl time.Duration, err error) {
// Distinguish between NODATA response and a failed request. // Distinguish between NODATA response and a failed request.
if resp.Rcode != dns.RcodeSuccess && resp.Rcode != dns.RcodeNameError { if resp.Rcode != dns.RcodeSuccess && resp.Rcode != dns.RcodeNameError {
return "", fmt.Errorf( return "", 0, fmt.Errorf(
"received %s response: %w", "received %s response: %w",
dns.RcodeToString[resp.Rcode], dns.RcodeToString[resp.Rcode],
ErrRDNSFailed, ErrRDNSFailed,
) )
} }
var ttlSec uint32
log.Debug("dnsforward: resolving ptr, received %d answers", len(resp.Answer))
for _, ans := range resp.Answer { for _, ans := range resp.Answer {
ptr, ok := ans.(*dns.PTR) ptr, ok := ans.(*dns.PTR)
if ok { if !ok {
return strings.TrimSuffix(ptr.Ptr, "."), nil continue
}
// Respect zero TTL records since some DNS servers use it to
// locally-resolved addresses.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/6046.
if ptr.Hdr.Ttl >= ttlSec {
host = ptr.Ptr
ttlSec = ptr.Hdr.Ttl
} }
} }
return "", ErrRDNSNoData if host != "" {
} // NOTE: Don't use [aghnet.NormalizeDomain] to retain original letter
// case.
host = strings.TrimSuffix(host, ".")
ttl = time.Duration(ttlSec) * time.Second
// isPrivateIP returns true if the ip is private. return host, ttl, nil
func (s *Server) isPrivateIP(ip netip.Addr) (ok bool) {
return s.privateNets.Contains(ip.AsSlice())
}
// ShouldResolveClient returns false if ip is a loopback address, or ip is
// private and resolving of private addresses is disabled.
func (s *Server) ShouldResolveClient(ip netip.Addr) (ok bool) {
if ip.IsLoopback() {
return false
} }
isPrivate := s.isPrivateIP(ip) return "", 0, ErrRDNSNoData
s.serverLock.RLock()
defer s.serverLock.RUnlock()
return s.conf.ResolveClients &&
(s.conf.UsePrivateRDNS || !isPrivate)
} }
// Start starts the DNS server. // Start starts the DNS server.
@@ -449,6 +472,7 @@ func (s *Server) filterOurDNSAddrs(addrs []string) (filtered []string, err error
} }
ourAddrsSet := stringutil.NewSet(ourAddrs...) ourAddrsSet := stringutil.NewSet(ourAddrs...)
log.Debug("dnsforward: filtering out %s", ourAddrsSet.String())
// TODO(e.burkov): The approach of subtracting sets of strings is not // TODO(e.burkov): The approach of subtracting sets of strings is not
// really applicable here since in case of listening on all network // really applicable here since in case of listening on all network
@@ -457,23 +481,27 @@ func (s *Server) filterOurDNSAddrs(addrs []string) (filtered []string, err error
return stringutil.FilterOut(addrs, ourAddrsSet.Has), nil return stringutil.FilterOut(addrs, ourAddrsSet.Has), nil
} }
// setupResolvers initializes the resolvers for local addresses. For internal // setupLocalResolvers initializes the resolvers for local addresses. For
// use only. // internal use only.
func (s *Server) setupResolvers(localAddrs []string) (err error) { func (s *Server) setupLocalResolvers() (err error) {
bootstraps := s.conf.BootstrapDNS bootstraps := s.conf.BootstrapDNS
if len(localAddrs) == 0 { resolvers := s.conf.LocalPTRResolvers
localAddrs = s.sysResolvers.Get()
if len(resolvers) == 0 {
resolvers = s.sysResolvers.Get()
bootstraps = nil bootstraps = nil
} else {
resolvers = stringutil.FilterOut(resolvers, IsCommentOrEmpty)
} }
localAddrs, err = s.filterOurDNSAddrs(localAddrs) resolvers, err = s.filterOurDNSAddrs(resolvers)
if err != nil { if err != nil {
return err return err
} }
log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", localAddrs) log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", resolvers)
upsConfig, err := s.prepareUpstreamConfig(localAddrs, nil, &upstream.Options{ uc, err := s.prepareUpstreamConfig(resolvers, nil, &upstream.Options{
Bootstrap: bootstraps, Bootstrap: bootstraps,
Timeout: defaultLocalTimeout, Timeout: defaultLocalTimeout,
// TODO(e.burkov): Should we verify server's certificates? // TODO(e.burkov): Should we verify server's certificates?
@@ -481,15 +509,22 @@ func (s *Server) setupResolvers(localAddrs []string) (err error) {
PreferIPv6: s.conf.BootstrapPreferIPv6, PreferIPv6: s.conf.BootstrapPreferIPv6,
}) })
if err != nil { if err != nil {
return fmt.Errorf("parsing private upstreams: %w", err) return fmt.Errorf("preparing private upstreams: %w", err)
} }
s.localResolvers = &proxy.Proxy{ s.localResolvers = &proxy.Proxy{
Config: proxy.Config{ Config: proxy.Config{
UpstreamConfig: upsConfig, UpstreamConfig: uc,
}, },
} }
if s.conf.UsePrivateRDNS &&
// Only set the upstream config if there are any upstreams. It's safe
// to put nil into [proxy.Config.PrivateRDNSUpstreamConfig].
len(uc.Upstreams)+len(uc.DomainReservedUpstreams)+len(uc.SpecifiedDomainUpstreams) > 0 {
s.dnsProxy.PrivateRDNSUpstreamConfig = uc
}
return nil return nil
} }
@@ -539,25 +574,48 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
return fmt.Errorf("preparing access: %w", err) return fmt.Errorf("preparing access: %w", err)
} }
s.registerHandlers() // Set the proxy here because [setupLocalResolvers] sets its values.
//
// TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy. // TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy.
err = s.setupResolvers(s.conf.LocalPTRResolvers) s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
err = s.setupLocalResolvers()
if err != nil { if err != nil {
return fmt.Errorf("setting up resolvers: %w", err) return fmt.Errorf("setting up resolvers: %w", err)
} }
if s.conf.UsePrivateRDNS {
proxyConfig.PrivateRDNSUpstreamConfig = s.localResolvers.UpstreamConfig
}
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
s.recDetector.clear() s.recDetector.clear()
s.setupAddrProc()
s.registerHandlers()
return nil return nil
} }
// setupAddrProc initializes the address processor. For internal use only.
func (s *Server) setupAddrProc() {
// TODO(a.garipov): This is a crutch for tests; remove.
if s.conf.AddrProcConf == nil {
s.conf.AddrProcConf = &client.DefaultAddrProcConfig{}
}
if s.conf.AddrProcConf.AddressUpdater == nil {
s.addrProc = client.EmptyAddrProc{}
} else {
c := s.conf.AddrProcConf
c.DialContext = s.DialContext
c.PrivateSubnets = s.privateNets
c.UsePrivateRDNS = s.conf.UsePrivateRDNS
s.addrProc = client.NewDefaultAddrProc(s.conf.AddrProcConf)
// Clear the initial addresses to not resolve them again.
//
// TODO(a.garipov): Consider ways of removing this once more client
// logic is moved to package client.
c.InitialAddresses = nil
}
}
// validateBlockingMode returns an error if the blocking mode data aren't valid. // validateBlockingMode returns an error if the blocking mode data aren't valid.
func validateBlockingMode(mode BlockingMode, blockingIPv4, blockingIPv6 net.IP) (err error) { func validateBlockingMode(mode BlockingMode, blockingIPv4, blockingIPv6 net.IP) (err error) {
switch mode { switch mode {
@@ -696,6 +754,11 @@ func (s *Server) Reconfigure(conf *ServerConfig) error {
// TODO(a.garipov): This whole piece of API is weird and needs to be remade. // TODO(a.garipov): This whole piece of API is weird and needs to be remade.
if conf == nil { if conf == nil {
conf = &s.conf conf = &s.conf
} else {
closeErr := s.addrProc.Close()
if closeErr != nil {
log.Error("dnsforward: closing address processor: %s", closeErr)
}
} }
err = s.Prepare(conf) err = s.Prepare(conf)

View File

@@ -1,6 +1,7 @@
package dnsforward package dnsforward
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
@@ -39,11 +40,29 @@ func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m) testutil.DiscardLogOutput(m)
} }
// testTimeout is the common timeout for tests.
//
// TODO(a.garipov): Use more.
const testTimeout = 1 * time.Second
// testQuestionTarget is the common question target for tests.
//
// TODO(a.garipov): Use more.
const testQuestionTarget = "target.example"
const ( const (
tlsServerName = "testdns.adguard.com" tlsServerName = "testdns.adguard.com"
testMessagesCount = 10 testMessagesCount = 10
) )
// testClientAddr is the common net.Addr for tests.
//
// TODO(a.garipov): Use more.
var testClientAddr net.Addr = &net.TCPAddr{
IP: net.IP{1, 2, 3, 4},
Port: 12345,
}
func startDeferStop(t *testing.T, s *Server) { func startDeferStop(t *testing.T, s *Server) {
t.Helper() t.Helper()
@@ -212,6 +231,17 @@ func createTestMessageWithType(host string, qtype uint16) *dns.Msg {
return req return req
} }
// newResp returns the new DNS response with response code set to rcode, req
// used as request, and rrs added.
func newResp(rcode int, req *dns.Msg, ans []dns.RR) (resp *dns.Msg) {
resp = (&dns.Msg{}).SetRcode(req, rcode)
resp.RecursionAvailable = true
resp.Compress = true
resp.Answer = ans
return resp
}
func assertGoogleAResponse(t *testing.T, reply *dns.Msg) { func assertGoogleAResponse(t *testing.T, reply *dns.Msg) {
assertResponse(t, reply, net.IP{8, 8, 8, 8}) assertResponse(t, reply, net.IP{8, 8, 8, 8})
} }
@@ -307,28 +337,26 @@ func TestServer(t *testing.T) {
} }
func TestServer_timeout(t *testing.T) { func TestServer_timeout(t *testing.T) {
const timeout time.Duration = time.Second
t.Run("custom", func(t *testing.T) { t.Run("custom", func(t *testing.T) {
srvConf := &ServerConfig{ srvConf := &ServerConfig{
UpstreamTimeout: timeout, UpstreamTimeout: testTimeout,
FilteringConfig: FilteringConfig{ FilteringConfig: FilteringConfig{
BlockingMode: BlockingModeDefault, BlockingMode: BlockingModeDefault,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
}, },
} }
s, err := NewServer(DNSCreateParams{}) s, err := NewServer(DNSCreateParams{DNSFilter: &filtering.DNSFilter{}})
require.NoError(t, err) require.NoError(t, err)
err = s.Prepare(srvConf) err = s.Prepare(srvConf)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, timeout, s.conf.UpstreamTimeout) assert.Equal(t, testTimeout, s.conf.UpstreamTimeout)
}) })
t.Run("default", func(t *testing.T) { t.Run("default", func(t *testing.T) {
s, err := NewServer(DNSCreateParams{}) s, err := NewServer(DNSCreateParams{DNSFilter: &filtering.DNSFilter{}})
require.NoError(t, err) require.NoError(t, err)
s.conf.FilteringConfig.BlockingMode = BlockingModeDefault s.conf.FilteringConfig.BlockingMode = BlockingModeDefault
@@ -441,7 +469,14 @@ func TestServerRace(t *testing.T) {
} }
func TestSafeSearch(t *testing.T) { func TestSafeSearch(t *testing.T) {
resolver := &aghtest.TestResolver{} resolver := &aghtest.Resolver{
OnLookupIP: func(_ context.Context, _, host string) (ips []net.IP, err error) {
ip4, ip6 := aghtest.HostToIPs(host)
return []net.IP{ip4, ip6}, nil
},
}
safeSearchConf := filtering.SafeSearchConfig{ safeSearchConf := filtering.SafeSearchConfig{
Enabled: true, Enabled: true,
Google: true, Google: true,
@@ -480,7 +515,7 @@ func TestSafeSearch(t *testing.T) {
client := &dns.Client{} client := &dns.Client{}
yandexIP := net.IP{213, 180, 193, 56} yandexIP := net.IP{213, 180, 193, 56}
googleIP, _ := resolver.HostToIPs("forcesafesearch.google.com") googleIP, _ := aghtest.HostToIPs("forcesafesearch.google.com")
testCases := []struct { testCases := []struct {
host string host string
@@ -545,7 +580,7 @@ func TestInvalidRequest(t *testing.T) {
// Send a DNS request without question. // Send a DNS request without question.
_, _, err := (&dns.Client{ _, _, err := (&dns.Client{
Timeout: 500 * time.Millisecond, Timeout: testTimeout,
}).Exchange(&req, addr) }).Exchange(&req, addr)
assert.NoErrorf(t, err, "got a response to an invalid query") assert.NoErrorf(t, err, "got a response to an invalid query")
@@ -928,7 +963,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
Upstream: aghtest.NewBlockUpstream(hostname, true), Upstream: aghtest.NewBlockUpstream(hostname, true),
}) })
ans4, _ := (&aghtest.TestResolver{}).HostToIPs(hostname) ans4, _ := aghtest.HostToIPs(hostname)
filterConf := &filtering.Config{ filterConf := &filtering.Config{
SafeBrowsingEnabled: true, SafeBrowsingEnabled: true,
@@ -1266,25 +1301,57 @@ func TestNewServer(t *testing.T) {
} }
} }
// doubleTTL is a helper function that returns a clone of DNS PTR with appended
// copy of first answer record with doubled TTL.
func doubleTTL(msg *dns.Msg) (resp *dns.Msg) {
if msg == nil {
return nil
}
if len(msg.Answer) == 0 {
return msg
}
rec := msg.Answer[0]
ptr, ok := rec.(*dns.PTR)
if !ok {
return msg
}
clone := *ptr
clone.Hdr.Ttl *= 2
msg.Answer = append(msg.Answer, &clone)
return msg
}
func TestServer_Exchange(t *testing.T) { func TestServer_Exchange(t *testing.T) {
const ( const (
onesHost = "one.one.one.one" onesHost = "one.one.one.one"
twosHost = "two.two.two.two"
localDomainHost = "local.domain" localDomainHost = "local.domain"
defaultTTL = time.Second * 60
) )
var ( var (
onesIP = netip.MustParseAddr("1.1.1.1") onesIP = netip.MustParseAddr("1.1.1.1")
twosIP = netip.MustParseAddr("2.2.2.2")
localIP = netip.MustParseAddr("192.168.1.1") localIP = netip.MustParseAddr("192.168.1.1")
) )
revExtIPv4, err := netutil.IPToReversedAddr(onesIP.AsSlice()) onesRevExtIPv4, err := netutil.IPToReversedAddr(onesIP.AsSlice())
require.NoError(t, err)
twosRevExtIPv4, err := netutil.IPToReversedAddr(twosIP.AsSlice())
require.NoError(t, err) require.NoError(t, err)
extUpstream := &aghtest.UpstreamMock{ extUpstream := &aghtest.UpstreamMock{
OnAddress: func() (addr string) { return "external.upstream.example" }, OnAddress: func() (addr string) { return "external.upstream.example" },
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) { OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
return aghalg.Coalesce( return aghalg.Coalesce(
aghtest.MatchedResponse(req, dns.TypePTR, revExtIPv4, onesHost), aghtest.MatchedResponse(req, dns.TypePTR, onesRevExtIPv4, onesHost),
doubleTTL(aghtest.MatchedResponse(req, dns.TypePTR, twosRevExtIPv4, twosHost)),
new(dns.Msg).SetRcode(req, dns.RcodeNameError), new(dns.Msg).SetRcode(req, dns.RcodeNameError),
), nil ), nil
}, },
@@ -1308,6 +1375,24 @@ func TestServer_Exchange(t *testing.T) {
refusingUpstream := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) { refusingUpstream := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
return new(dns.Msg).SetRcode(req, dns.RcodeRefused), nil return new(dns.Msg).SetRcode(req, dns.RcodeRefused), nil
}) })
zeroTTLUps := &aghtest.UpstreamMock{
OnAddress: func() (addr string) { return "zero.ttl.example" },
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
resp = new(dns.Msg).SetReply(req)
hdr := dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: dns.TypePTR,
Class: dns.ClassINET,
Ttl: 0,
}
resp.Answer = []dns.RR{&dns.PTR{
Hdr: hdr,
Ptr: localDomainHost,
}}
return resp, nil
},
}
srv := &Server{ srv := &Server{
recDetector: newRecursionDetector(0, 1), recDetector: newRecursionDetector(0, 1),
@@ -1320,53 +1405,72 @@ func TestServer_Exchange(t *testing.T) {
}, },
} }
srv.conf.ResolveClients = true
srv.conf.UsePrivateRDNS = true srv.conf.UsePrivateRDNS = true
srv.privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed) srv.privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed)
testCases := []struct { testCases := []struct {
name string req netip.Addr
want string
wantErr error wantErr error
locUpstream upstream.Upstream locUpstream upstream.Upstream
req netip.Addr name string
want string
wantTTL time.Duration
}{{ }{{
name: "external_good", name: "external_good",
want: onesHost, want: onesHost,
wantErr: nil, wantErr: nil,
locUpstream: nil, locUpstream: nil,
req: onesIP, req: onesIP,
wantTTL: defaultTTL,
}, { }, {
name: "local_good", name: "local_good",
want: localDomainHost, want: localDomainHost,
wantErr: nil, wantErr: nil,
locUpstream: locUpstream, locUpstream: locUpstream,
req: localIP, req: localIP,
wantTTL: defaultTTL,
}, { }, {
name: "upstream_error", name: "upstream_error",
want: "", want: "",
wantErr: aghtest.ErrUpstream, wantErr: aghtest.ErrUpstream,
locUpstream: errUpstream, locUpstream: errUpstream,
req: localIP, req: localIP,
wantTTL: 0,
}, { }, {
name: "empty_answer_error", name: "empty_answer_error",
want: "", want: "",
wantErr: ErrRDNSNoData, wantErr: ErrRDNSNoData,
locUpstream: locUpstream, locUpstream: locUpstream,
req: netip.MustParseAddr("192.168.1.2"), req: netip.MustParseAddr("192.168.1.2"),
wantTTL: 0,
}, { }, {
name: "invalid_answer", name: "invalid_answer",
want: "", want: "",
wantErr: ErrRDNSNoData, wantErr: ErrRDNSNoData,
locUpstream: nonPtrUpstream, locUpstream: nonPtrUpstream,
req: localIP, req: localIP,
wantTTL: 0,
}, { }, {
name: "refused", name: "refused",
want: "", want: "",
wantErr: ErrRDNSFailed, wantErr: ErrRDNSFailed,
locUpstream: refusingUpstream, locUpstream: refusingUpstream,
req: localIP, req: localIP,
wantTTL: 0,
}, {
name: "longest_ttl",
want: twosHost,
wantErr: nil,
locUpstream: nil,
req: twosIP,
wantTTL: defaultTTL * 2,
}, {
name: "zero_ttl",
want: localDomainHost,
wantErr: nil,
locUpstream: zeroTTLUps,
req: localIP,
wantTTL: 0,
}} }}
for _, tc := range testCases { for _, tc := range testCases {
@@ -1380,73 +1484,21 @@ func TestServer_Exchange(t *testing.T) {
} }
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
host, eerr := srv.Exchange(tc.req) host, ttl, eerr := srv.Exchange(tc.req)
require.ErrorIs(t, eerr, tc.wantErr) require.ErrorIs(t, eerr, tc.wantErr)
assert.Equal(t, tc.want, host) assert.Equal(t, tc.want, host)
assert.Equal(t, tc.wantTTL, ttl)
}) })
} }
t.Run("resolving_disabled", func(t *testing.T) { t.Run("resolving_disabled", func(t *testing.T) {
srv.conf.UsePrivateRDNS = false srv.conf.UsePrivateRDNS = false
t.Cleanup(func() { srv.conf.UsePrivateRDNS = true })
host, eerr := srv.Exchange(localIP) host, _, eerr := srv.Exchange(localIP)
require.NoError(t, eerr) require.NoError(t, eerr)
assert.Empty(t, host) assert.Empty(t, host)
}) })
} }
func TestServer_ShouldResolveClient(t *testing.T) {
srv := &Server{
privateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
}
testCases := []struct {
ip netip.Addr
want require.BoolAssertionFunc
name string
resolve bool
usePrivate bool
}{{
name: "default",
ip: netip.MustParseAddr("1.1.1.1"),
want: require.True,
resolve: true,
usePrivate: true,
}, {
name: "no_rdns",
ip: netip.MustParseAddr("1.1.1.1"),
want: require.False,
resolve: false,
usePrivate: true,
}, {
name: "loopback",
ip: netip.MustParseAddr("127.0.0.1"),
want: require.False,
resolve: true,
usePrivate: true,
}, {
name: "private_resolve",
ip: netip.MustParseAddr("192.168.0.1"),
want: require.True,
resolve: true,
usePrivate: true,
}, {
name: "private_no_resolve",
ip: netip.MustParseAddr("192.168.0.1"),
want: require.False,
resolve: true,
usePrivate: false,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
srv.conf.ResolveClients = tc.resolve
srv.conf.UsePrivateRDNS = tc.usePrivate
ok := srv.ShouldResolveClient(tc.ip)
tc.want(t, ok)
})
}
}

View File

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

View File

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

View File

@@ -124,7 +124,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
cacheMinTTL := s.conf.CacheMinTTL cacheMinTTL := s.conf.CacheMinTTL
cacheMaxTTL := s.conf.CacheMaxTTL cacheMaxTTL := s.conf.CacheMaxTTL
cacheOptimistic := s.conf.CacheOptimistic cacheOptimistic := s.conf.CacheOptimistic
resolveClients := s.conf.ResolveClients resolveClients := s.conf.AddrProcConf.UseRDNS
usePrivateRDNS := s.conf.UsePrivateRDNS usePrivateRDNS := s.conf.UsePrivateRDNS
localPTRUpstreams := stringutil.CloneSliceOrEmpty(s.conf.LocalPTRResolvers) localPTRUpstreams := stringutil.CloneSliceOrEmpty(s.conf.LocalPTRResolvers)
@@ -314,8 +314,6 @@ func (s *Server) setConfig(dc *jsonDNSConfig) (shouldRestart bool) {
setIfNotNil(&s.conf.ProtectionEnabled, dc.ProtectionEnabled) setIfNotNil(&s.conf.ProtectionEnabled, dc.ProtectionEnabled)
setIfNotNil(&s.conf.EnableDNSSEC, dc.DNSSECEnabled) setIfNotNil(&s.conf.EnableDNSSEC, dc.DNSSECEnabled)
setIfNotNil(&s.conf.AAAADisabled, dc.DisableIPv6) setIfNotNil(&s.conf.AAAADisabled, dc.DisableIPv6)
setIfNotNil(&s.conf.ResolveClients, dc.ResolveClients)
setIfNotNil(&s.conf.UsePrivateRDNS, dc.UsePrivateRDNS)
return s.setConfigRestartable(dc) return s.setConfigRestartable(dc)
} }
@@ -335,6 +333,9 @@ func setIfNotNil[T any](currentPtr, newPtr *T) (hasSet bool) {
// setConfigRestartable sets the parameters which trigger a restart. // setConfigRestartable sets the parameters which trigger a restart.
// shouldRestart is true if the server should be restarted to apply changes. // shouldRestart is true if the server should be restarted to apply changes.
// s.serverLock is expected to be locked. // s.serverLock is expected to be locked.
//
// TODO(a.garipov): Some of these could probably be updated without a restart.
// Inspect and consider refactoring.
func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) { func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) {
for _, hasSet := range []bool{ for _, hasSet := range []bool{
setIfNotNil(&s.conf.UpstreamDNS, dc.Upstreams), setIfNotNil(&s.conf.UpstreamDNS, dc.Upstreams),
@@ -347,6 +348,8 @@ func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) {
setIfNotNil(&s.conf.CacheMinTTL, dc.CacheMinTTL), setIfNotNil(&s.conf.CacheMinTTL, dc.CacheMinTTL),
setIfNotNil(&s.conf.CacheMaxTTL, dc.CacheMaxTTL), setIfNotNil(&s.conf.CacheMaxTTL, dc.CacheMaxTTL),
setIfNotNil(&s.conf.CacheOptimistic, dc.CacheOptimistic), setIfNotNil(&s.conf.CacheOptimistic, dc.CacheOptimistic),
setIfNotNil(&s.conf.AddrProcConf.UseRDNS, dc.ResolveClients),
setIfNotNil(&s.conf.UsePrivateRDNS, dc.UsePrivateRDNS),
} { } {
shouldRestart = shouldRestart || hasSet shouldRestart = shouldRestart || hasSet
if shouldRestart { if shouldRestart {
@@ -664,7 +667,7 @@ func (s *Server) parseUpstreamLine(
PreferIPv6: opts.PreferIPv6, PreferIPv6: opts.PreferIPv6,
} }
if s.dnsFilter != nil && s.dnsFilter.EtcHosts != nil { if s.dnsFilter.EtcHosts != nil {
resolved := s.resolveUpstreamHost(extractUpstreamHost(upstreamAddr)) resolved := s.resolveUpstreamHost(extractUpstreamHost(upstreamAddr))
sortNetIPAddrs(resolved, opts.PreferIPv6) sortNetIPAddrs(resolved, opts.PreferIPv6)
opts.ServerIPAddrs = resolved opts.ServerIPAddrs = resolved

View File

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

View File

@@ -30,6 +30,7 @@ type dnsContext struct {
setts *filtering.Settings setts *filtering.Settings
result *filtering.Result result *filtering.Result
// origResp is the response received from upstream. It is set when the // origResp is the response received from upstream. It is set when the
// response is modified by filters. // response is modified by filters.
origResp *dns.Msg origResp *dns.Msg
@@ -48,13 +49,13 @@ type dnsContext struct {
// clientID is the ClientID from DoH, DoQ, or DoT, if provided. // clientID is the ClientID from DoH, DoQ, or DoT, if provided.
clientID string clientID string
// startTime is the time at which the processing of the request has started.
startTime time.Time
// origQuestion is the question received from the client. It is set // origQuestion is the question received from the client. It is set
// when the request is modified by rewrites. // when the request is modified by rewrites.
origQuestion dns.Question origQuestion dns.Question
// startTime is the time at which the processing of the request has started.
startTime time.Time
// protectionEnabled shows if the filtering is enabled, and if the // protectionEnabled shows if the filtering is enabled, and if the
// server's DNS filter is ready. // server's DNS filter is ready.
protectionEnabled bool protectionEnabled bool
@@ -160,6 +161,22 @@ func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess return resultCodeSuccess
} }
// mozillaFQDN is the domain used to signal the Firefox browser to not use its
// own DoH server.
//
// See https://support.mozilla.org/en-US/kb/canary-domain-use-application-dnsnet.
const mozillaFQDN = "use-application-dns.net."
// healthcheckFQDN is a reserved domain-name used for healthchecking.
//
// [Section 6.2 of RFC 6761] states that DNS Registries/Registrars must not
// grant requests to register test names in the normal way to any person or
// entity, making domain names under the .test TLD free to use in internal
// purposes.
//
// [Section 6.2 of RFC 6761]: https://www.rfc-editor.org/rfc/rfc6761.html#section-6.2
const healthcheckFQDN = "healthcheck.adguardhome.test."
// processInitial terminates the following processing for some requests if // processInitial terminates the following processing for some requests if
// needed and enriches dctx with some client-specific information. // needed and enriches dctx with some client-specific information.
// //
@@ -169,6 +186,8 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
defer log.Debug("dnsforward: finished processing initial") defer log.Debug("dnsforward: finished processing initial")
pctx := dctx.proxyCtx pctx := dctx.proxyCtx
s.processClientIP(pctx.Addr)
q := pctx.Req.Question[0] q := pctx.Req.Question[0]
qt := q.Qtype qt := q.Qtype
if s.conf.AAAADisabled && qt == dns.TypeAAAA { if s.conf.AAAADisabled && qt == dns.TypeAAAA {
@@ -177,28 +196,13 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
return resultCodeFinish return resultCodeFinish
} }
if s.conf.OnDNSRequest != nil { if (qt == dns.TypeA || qt == dns.TypeAAAA) && q.Name == mozillaFQDN {
s.conf.OnDNSRequest(pctx)
}
// Disable Mozilla DoH.
//
// See https://support.mozilla.org/en-US/kb/canary-domain-use-application-dnsnet.
if (qt == dns.TypeA || qt == dns.TypeAAAA) && q.Name == "use-application-dns.net." {
pctx.Res = s.genNXDomain(pctx.Req) pctx.Res = s.genNXDomain(pctx.Req)
return resultCodeFinish return resultCodeFinish
} }
// Handle a reserved domain healthcheck.adguardhome.test. if q.Name == healthcheckFQDN {
//
// [Section 6.2 of RFC 6761] states that DNS Registries/Registrars must not
// grant requests to register test names in the normal way to any person or
// entity, making domain names under test. TLD free to use in internal
// purposes.
//
// [Section 6.2 of RFC 6761]: https://www.rfc-editor.org/rfc/rfc6761.html#section-6.2
if q.Name == "healthcheck.adguardhome.test." {
// Generate a NODATA negative response to make nslookup exit with 0. // Generate a NODATA negative response to make nslookup exit with 0.
pctx.Res = s.makeResponse(pctx.Req) pctx.Res = s.makeResponse(pctx.Req)
@@ -213,11 +217,28 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
// Get the client-specific filtering settings. // Get the client-specific filtering settings.
dctx.protectionEnabled, _ = s.UpdatedProtectionStatus() dctx.protectionEnabled, _ = s.UpdatedProtectionStatus()
dctx.setts = s.getClientRequestFilteringSettings(dctx) dctx.setts = s.clientRequestFilteringSettings(dctx)
return resultCodeSuccess return resultCodeSuccess
} }
// processClientIP sends the client IP address to s.addrProc, if needed.
func (s *Server) processClientIP(addr net.Addr) {
clientIP := netutil.NetAddrToAddrPort(addr).Addr()
if clientIP == (netip.Addr{}) {
log.Info("dnsforward: warning: bad client addr %q", addr)
return
}
// Do not assign s.addrProc to a local variable to then use, since this lock
// also serializes the closure of s.addrProc.
s.serverLock.RLock()
defer s.serverLock.RUnlock()
s.addrProc.Process(clientIP)
}
func (s *Server) setTableHostToIP(t hostToIPTable) { func (s *Server) setTableHostToIP(t hostToIPTable) {
s.tableHostToIPLock.Lock() s.tableHostToIPLock.Lock()
defer s.tableHostToIPLock.Unlock() defer s.tableHostToIPLock.Unlock()
@@ -698,6 +719,20 @@ func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
if s.conf.UsePrivateRDNS { if s.conf.UsePrivateRDNS {
s.recDetector.add(*pctx.Req) s.recDetector.add(*pctx.Req)
if err := s.localResolvers.Resolve(pctx); err != nil { if err := s.localResolvers.Resolve(pctx); err != nil {
log.Debug("dnsforward: resolving private address: %s", err)
// Generate the server failure if the private upstream configuration
// is empty.
//
// TODO(e.burkov): Get rid of this crutch once the local resolvers
// logic is moved to the dnsproxy completely.
if errors.Is(err, upstream.ErrNoUpstreams) {
pctx.Res = s.genServerFailure(pctx.Req)
// Do not even put into query log.
return resultCodeFinish
}
dctx.err = err dctx.err = err
return resultCodeError return resultCodeError
@@ -727,10 +762,6 @@ func (s *Server) processFilteringBeforeRequest(ctx *dnsContext) (rc resultCode)
s.serverLock.RLock() s.serverLock.RLock()
defer s.serverLock.RUnlock() defer s.serverLock.RUnlock()
if s.dnsFilter == nil {
return resultCodeSuccess
}
var err error var err error
if ctx.result, err = s.filterDNSRequest(ctx); err != nil { if ctx.result, err = s.filterDNSRequest(ctx); err != nil {
ctx.err = err ctx.err = err
@@ -937,7 +968,7 @@ func (s *Server) filterAfterResponse(dctx *dnsContext, pctx *proxy.DNSContext) (
// Check the response only if it's from an upstream. Don't check the // Check the response only if it's from an upstream. Don't check the
// response if the protection is disabled since dnsrewrite rules aren't // response if the protection is disabled since dnsrewrite rules aren't
// applied to it anyway. // applied to it anyway.
if !dctx.protectionEnabled || !dctx.responseFromUpstream || s.dnsFilter == nil { if !dctx.protectionEnabled || !dctx.responseFromUpstream {
return resultCodeSuccess return resultCodeSuccess
} }

View File

@@ -12,6 +12,7 @@ import (
"github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -22,6 +23,96 @@ const (
ddrTestFQDN = ddrTestDomainName + "." ddrTestFQDN = ddrTestDomainName + "."
) )
func TestServer_ProcessInitial(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
target string
wantRCode rules.RCode
qType rules.RRType
aaaaDisabled bool
wantRC resultCode
}{{
name: "success",
target: testQuestionTarget,
wantRCode: -1,
qType: dns.TypeA,
aaaaDisabled: false,
wantRC: resultCodeSuccess,
}, {
name: "aaaa_disabled",
target: testQuestionTarget,
wantRCode: dns.RcodeSuccess,
qType: dns.TypeAAAA,
aaaaDisabled: true,
wantRC: resultCodeFinish,
}, {
name: "aaaa_disabled_a",
target: testQuestionTarget,
wantRCode: -1,
qType: dns.TypeA,
aaaaDisabled: true,
wantRC: resultCodeSuccess,
}, {
name: "mozilla_canary",
target: mozillaFQDN,
wantRCode: dns.RcodeNameError,
qType: dns.TypeA,
aaaaDisabled: false,
wantRC: resultCodeFinish,
}, {
name: "adguardhome_healthcheck",
target: healthcheckFQDN,
wantRCode: dns.RcodeSuccess,
qType: dns.TypeA,
aaaaDisabled: false,
wantRC: resultCodeFinish,
}}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
c := ServerConfig{
FilteringConfig: FilteringConfig{
AAAADisabled: tc.aaaaDisabled,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
},
}
s := createTestServer(t, &filtering.Config{}, c, nil)
var gotAddr netip.Addr
s.addrProc = &aghtest.AddressProcessor{
OnProcess: func(ip netip.Addr) { gotAddr = ip },
OnClose: func() (err error) { panic("not implemented") },
}
dctx := &dnsContext{
proxyCtx: &proxy.DNSContext{
Req: createTestMessageWithType(tc.target, tc.qType),
Addr: testClientAddr,
RequestID: 1234,
},
}
gotRC := s.processInitial(dctx)
assert.Equal(t, tc.wantRC, gotRC)
assert.Equal(t, netutil.NetAddrToAddrPort(testClientAddr).Addr(), gotAddr)
if tc.wantRCode > 0 {
gotResp := dctx.proxyCtx.Res
require.NotNil(t, gotResp)
assert.Equal(t, tc.wantRCode, gotResp.Rcode)
}
})
}
}
func TestServer_ProcessDDRQuery(t *testing.T) { func TestServer_ProcessDDRQuery(t *testing.T) {
dohSVCB := &dns.SVCB{ dohSVCB := &dns.SVCB{
Priority: 1, Priority: 1,
@@ -64,7 +155,7 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
}{{ }{{
name: "pass_host", name: "pass_host",
wantRes: resultCodeSuccess, wantRes: resultCodeSuccess,
host: "example.net.", host: testQuestionTarget,
qtype: dns.TypeSVCB, qtype: dns.TypeSVCB,
ddrEnabled: true, ddrEnabled: true,
portDoH: 8043, portDoH: 8043,
@@ -234,33 +325,33 @@ func TestServer_ProcessDetermineLocal(t *testing.T) {
func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) { func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
knownIP := netip.MustParseAddr("1.2.3.4") knownIP := netip.MustParseAddr("1.2.3.4")
testCases := []struct { testCases := []struct {
wantIP netip.Addr
name string name string
host string host string
wantIP netip.Addr
wantRes resultCode wantRes resultCode
isLocalCli bool isLocalCli bool
}{{ }{{
wantIP: knownIP,
name: "local_client_success", name: "local_client_success",
host: "example.lan", host: "example.lan",
wantIP: knownIP,
wantRes: resultCodeSuccess, wantRes: resultCodeSuccess,
isLocalCli: true, isLocalCli: true,
}, { }, {
wantIP: netip.Addr{},
name: "local_client_unknown_host", name: "local_client_unknown_host",
host: "wronghost.lan", host: "wronghost.lan",
wantIP: netip.Addr{},
wantRes: resultCodeSuccess, wantRes: resultCodeSuccess,
isLocalCli: true, isLocalCli: true,
}, { }, {
wantIP: netip.Addr{},
name: "external_client_known_host", name: "external_client_known_host",
host: "example.lan", host: "example.lan",
wantIP: netip.Addr{},
wantRes: resultCodeFinish, wantRes: resultCodeFinish,
isLocalCli: false, isLocalCli: false,
}, { }, {
wantIP: netip.Addr{},
name: "external_client_unknown_host", name: "external_client_unknown_host",
host: "wronghost.lan", host: "wronghost.lan",
wantIP: netip.Addr{},
wantRes: resultCodeFinish, wantRes: resultCodeFinish,
isLocalCli: false, isLocalCli: false,
}} }}
@@ -332,52 +423,52 @@ func TestServer_ProcessDHCPHosts(t *testing.T) {
knownIP := netip.MustParseAddr("1.2.3.4") knownIP := netip.MustParseAddr("1.2.3.4")
testCases := []struct { testCases := []struct {
wantIP netip.Addr
name string name string
host string host string
suffix string suffix string
wantIP netip.Addr
wantRes resultCode wantRes resultCode
qtyp uint16 qtyp uint16
}{{ }{{
wantIP: netip.Addr{},
name: "success_external", name: "success_external",
host: examplecom, host: examplecom,
suffix: defaultLocalDomainSuffix, suffix: defaultLocalDomainSuffix,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess, wantRes: resultCodeSuccess,
qtyp: dns.TypeA, qtyp: dns.TypeA,
}, { }, {
wantIP: netip.Addr{},
name: "success_external_non_a", name: "success_external_non_a",
host: examplecom, host: examplecom,
suffix: defaultLocalDomainSuffix, suffix: defaultLocalDomainSuffix,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess, wantRes: resultCodeSuccess,
qtyp: dns.TypeCNAME, qtyp: dns.TypeCNAME,
}, { }, {
wantIP: knownIP,
name: "success_internal", name: "success_internal",
host: examplelan, host: examplelan,
suffix: defaultLocalDomainSuffix, suffix: defaultLocalDomainSuffix,
wantIP: knownIP,
wantRes: resultCodeSuccess, wantRes: resultCodeSuccess,
qtyp: dns.TypeA, qtyp: dns.TypeA,
}, { }, {
wantIP: netip.Addr{},
name: "success_internal_unknown", name: "success_internal_unknown",
host: "example-new.lan", host: "example-new.lan",
suffix: defaultLocalDomainSuffix, suffix: defaultLocalDomainSuffix,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess, wantRes: resultCodeSuccess,
qtyp: dns.TypeA, qtyp: dns.TypeA,
}, { }, {
wantIP: netip.Addr{},
name: "success_internal_aaaa", name: "success_internal_aaaa",
host: examplelan, host: examplelan,
suffix: defaultLocalDomainSuffix, suffix: defaultLocalDomainSuffix,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess, wantRes: resultCodeSuccess,
qtyp: dns.TypeAAAA, qtyp: dns.TypeAAAA,
}, { }, {
wantIP: knownIP,
name: "success_custom_suffix", name: "success_custom_suffix",
host: "example.custom", host: "example.custom",
suffix: "custom", suffix: "custom",
wantIP: knownIP,
wantRes: resultCodeSuccess, wantRes: resultCodeSuccess,
qtyp: dns.TypeA, qtyp: dns.TypeA,
}} }}
@@ -560,10 +651,8 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
var dnsCtx *dnsContext var dnsCtx *dnsContext
setup := func(use bool) { setup := func(use bool) {
proxyCtx = &proxy.DNSContext{ proxyCtx = &proxy.DNSContext{
Addr: &net.TCPAddr{ Addr: testClientAddr,
IP: net.IP{127, 0, 0, 1}, Req: createTestMessageWithType(reqAddr, dns.TypePTR),
},
Req: createTestMessageWithType(reqAddr, dns.TypePTR),
} }
dnsCtx = &dnsContext{ dnsCtx = &dnsContext{
proxyCtx: proxyCtx, proxyCtx: proxyCtx,

View File

@@ -2,9 +2,9 @@ package dnsforward
import ( import (
"net" "net"
"strings"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/querylog" "github.com/AdguardTeam/AdGuardHome/internal/querylog"
"github.com/AdguardTeam/AdGuardHome/internal/stats" "github.com/AdguardTeam/AdGuardHome/internal/stats"
@@ -24,7 +24,7 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
pctx := dctx.proxyCtx pctx := dctx.proxyCtx
q := pctx.Req.Question[0] q := pctx.Req.Question[0]
host := strings.ToLower(strings.TrimSuffix(q.Name, ".")) host := aghnet.NormalizeDomain(q.Name)
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr) ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
ip = slices.Clone(ip) ip = slices.Clone(ip)
@@ -139,11 +139,14 @@ func (s *Server) updateStats(
clientIP string, clientIP string,
) { ) {
pctx := ctx.proxyCtx pctx := ctx.proxyCtx
e := stats.Entry{} e := &stats.Entry{
e.Domain = strings.ToLower(pctx.Req.Question[0].Name) Domain: aghnet.NormalizeDomain(pctx.Req.Question[0].Name),
if e.Domain != "." { Result: stats.RNotFiltered,
// Remove last ".", but save the domain as is for "." queries. Time: elapsed,
e.Domain = e.Domain[:len(e.Domain)-1] }
if pctx.Upstream != nil {
e.Upstream = pctx.Upstream.Address()
} }
if clientID := ctx.clientID; clientID != "" { if clientID := ctx.clientID; clientID != "" {
@@ -152,9 +155,6 @@ func (s *Server) updateStats(
e.Client = clientIP e.Client = clientIP
} }
e.Time = uint32(elapsed / 1000)
e.Result = stats.RNotFiltered
switch res.Reason { switch res.Reason {
case filtering.FilteredSafeBrowsing: case filtering.FilteredSafeBrowsing:
e.Result = stats.RSafeBrowsing e.Result = stats.RSafeBrowsing
@@ -162,7 +162,8 @@ func (s *Server) updateStats(
e.Result = stats.RParental e.Result = stats.RParental
case filtering.FilteredSafeSearch: case filtering.FilteredSafeSearch:
e.Result = stats.RSafeSearch e.Result = stats.RSafeSearch
case filtering.FilteredBlockList, case
filtering.FilteredBlockList,
filtering.FilteredInvalid, filtering.FilteredInvalid,
filtering.FilteredBlockedService: filtering.FilteredBlockedService:
e.Result = stats.RFiltered e.Result = stats.RFiltered

View File

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

View File

@@ -42,14 +42,6 @@ func (s *Server) loadUpstreams() (upstreams []string, err error) {
// prepareUpstreamSettings sets upstream DNS server settings. // prepareUpstreamSettings sets upstream DNS server settings.
func (s *Server) prepareUpstreamSettings() (err error) { func (s *Server) prepareUpstreamSettings() (err error) {
// We're setting a customized set of RootCAs. The reason is that Go default
// mechanism of loading TLS roots does not always work properly on some
// routers so we're loading roots manually and pass it here.
//
// See [aghtls.SystemRootCAs].
upstream.RootCAs = s.conf.TLSv12Roots
upstream.CipherSuites = s.conf.TLSCiphers
// Load upstreams either from the file, or from the settings // Load upstreams either from the file, or from the settings
var upstreams []string var upstreams []string
upstreams, err = s.loadUpstreams() upstreams, err = s.loadUpstreams()
@@ -62,6 +54,15 @@ func (s *Server) prepareUpstreamSettings() (err error) {
Timeout: s.conf.UpstreamTimeout, Timeout: s.conf.UpstreamTimeout,
HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams), HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams),
PreferIPv6: s.conf.BootstrapPreferIPv6, PreferIPv6: s.conf.BootstrapPreferIPv6,
// Use a customized set of RootCAs, because Go's default mechanism of
// loading TLS roots does not always work properly on some routers so we're
// loading roots manually and pass it here.
//
// See [aghtls.SystemRootCAs].
//
// TODO(a.garipov): Investigate if that's true.
RootCAs: s.conf.TLSv12Roots,
CipherSuites: s.conf.TLSCiphers,
}) })
if err != nil { if err != nil {
return fmt.Errorf("preparing upstream config: %w", err) return fmt.Errorf("preparing upstream config: %w", err)
@@ -93,7 +94,7 @@ func (s *Server) prepareUpstreamConfig(
uc.Upstreams = defaultUpstreamConfig.Upstreams uc.Upstreams = defaultUpstreamConfig.Upstreams
} }
if s.dnsFilter != nil && s.dnsFilter.EtcHosts != nil { if s.dnsFilter.EtcHosts != nil {
err = s.replaceUpstreamsWithHosts(uc, opts) err = s.replaceUpstreamsWithHosts(uc, opts)
if err != nil { if err != nil {
return nil, fmt.Errorf("resolving upstreams with hosts: %w", err) return nil, fmt.Errorf("resolving upstreams with hosts: %w", err)
@@ -190,7 +191,7 @@ func (s *Server) resolveUpstreamsWithHosts(
// extractUpstreamHost returns the hostname of addr without port with an // extractUpstreamHost returns the hostname of addr without port with an
// assumption that any address passed here has already been successfully parsed // assumption that any address passed here has already been successfully parsed
// by [upstream.AddressToUpstream]. This function eesentially mirrors the logic // by [upstream.AddressToUpstream]. This function essentially mirrors the logic
// of [upstream.AddressToUpstream], see TODO on [replaceUpstreamsWithHosts]. // of [upstream.AddressToUpstream], see TODO on [replaceUpstreamsWithHosts].
func extractUpstreamHost(addr string) (host string) { func extractUpstreamHost(addr string) (host string) {
var err error var err error

View File

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

View File

@@ -943,7 +943,7 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
d = &DNSFilter{ d = &DNSFilter{
bufPool: &sync.Pool{ bufPool: &sync.Pool{
New: func() (buf any) { New: func() (buf any) {
bufVal := make([]byte, rulelist.MaxRuleLen) bufVal := make([]byte, rulelist.DefaultRuleBufSize)
return &bufVal return &bufVal
}, },

View File

@@ -6,9 +6,9 @@ import (
"fmt" "fmt"
"hash/crc32" "hash/crc32"
"io" "io"
"unicode"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"golang.org/x/exp/slices"
) )
// Parser is a filtering-rule parser that collects data, such as the checksum // Parser is a filtering-rule parser that collects data, such as the checksum
@@ -48,19 +48,29 @@ type ParseResult struct {
// nil. // nil.
func (p *Parser) Parse(dst io.Writer, src io.Reader, buf []byte) (r *ParseResult, err error) { func (p *Parser) Parse(dst io.Writer, src io.Reader, buf []byte) (r *ParseResult, err error) {
s := bufio.NewScanner(src) s := bufio.NewScanner(src)
s.Buffer(buf, MaxRuleLen)
lineIdx := 0 // Don't use [DefaultRuleBufSize] as the maximum size, since some
// filtering-rule lists compressed by e.g. HostlistsCompiler can have very
// large lines. The buffer optimization still works for the more common
// case of reasonably-sized lines.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/6003.
s.Buffer(buf, bufio.MaxScanTokenSize)
// Use a one-based index for lines and columns, since these errors end up in
// the frontend, and users are more familiar with one-based line and column
// indexes.
lineNum := 1
for s.Scan() { for s.Scan() {
var n int var n int
n, err = p.processLine(dst, s.Bytes(), lineIdx) n, err = p.processLine(dst, s.Bytes(), lineNum)
p.written += n p.written += n
if err != nil { if err != nil {
// Don't wrap the error, because it's informative enough as is. // Don't wrap the error, because it's informative enough as is.
return p.result(), err return p.result(), err
} }
lineIdx++ lineNum++
} }
r = p.result() r = p.result()
@@ -81,7 +91,7 @@ func (p *Parser) result() (r *ParseResult) {
// processLine processes a single line. It may write to dst, and if it does, n // processLine processes a single line. It may write to dst, and if it does, n
// is the number of bytes written. // is the number of bytes written.
func (p *Parser) processLine(dst io.Writer, line []byte, lineIdx int) (n int, err error) { func (p *Parser) processLine(dst io.Writer, line []byte, lineNum int) (n int, err error) {
trimmed := bytes.TrimSpace(line) trimmed := bytes.TrimSpace(line)
if p.written == 0 && isHTMLLine(trimmed) { if p.written == 0 && isHTMLLine(trimmed) {
return 0, ErrHTML return 0, ErrHTML
@@ -95,9 +105,10 @@ func (p *Parser) processLine(dst io.Writer, line []byte, lineIdx int) (n int, er
} }
if badIdx != -1 { if badIdx != -1 {
return 0, fmt.Errorf( return 0, fmt.Errorf(
"line at index %d: character at index %d: non-printable character", "line %d: character %d: likely binary character %q",
lineIdx, lineNum,
badIdx+bytes.Index(line, trimmed), badIdx+bytes.Index(line, trimmed)+1,
trimmed[badIdx],
) )
} }
@@ -130,41 +141,37 @@ func hasPrefixFold(b, prefix []byte) (ok bool) {
} }
// parseLine returns true if the parsed line is a filtering rule. line is // parseLine returns true if the parsed line is a filtering rule. line is
// assumed to be trimmed of whitespace characters. nonPrintIdx is the index of // assumed to be trimmed of whitespace characters. badIdx is the index of the
// the first non-printable character, if any; if there are none, nonPrintIdx is // first character that may indicate that this is a binary file, or -1 if none.
// -1.
// //
// A line is considered a rule if it's not empty, not a comment, and contains // A line is considered a rule if it's not empty, not a comment, and contains
// only printable characters. // only printable characters.
func parseLine(line []byte) (nonPrintIdx int, isRule bool) { func parseLine(line []byte) (badIdx int, isRule bool) {
if len(line) == 0 || line[0] == '#' || line[0] == '!' { if len(line) == 0 || line[0] == '#' || line[0] == '!' {
return -1, false return -1, false
} }
nonPrintIdx = bytes.IndexFunc(line, isNotPrintable) badIdx = slices.IndexFunc(line, likelyBinary)
return nonPrintIdx, nonPrintIdx == -1 return badIdx, badIdx == -1
} }
// isNotPrintable returns true if r is not a printable character that can be // likelyBinary returns true if b is likely to be a byte from a binary file.
// contained in a filtering rule. func likelyBinary(b byte) (ok bool) {
func isNotPrintable(r rune) (ok bool) { return (b < ' ' || b == 0x7f) && b != '\n' && b != '\r' && b != '\t'
// Tab isn't included into Unicode's graphic symbols, so include it here
// explicitly.
return r != '\t' && !unicode.IsGraphic(r)
} }
// parseLineTitle is like [parseLine] but additionally looks for a title. line // parseLineTitle is like [parseLine] but additionally looks for a title. line
// is assumed to be trimmed of whitespace characters. // is assumed to be trimmed of whitespace characters.
func (p *Parser) parseLineTitle(line []byte) (nonPrintIdx int, isRule bool) { func (p *Parser) parseLineTitle(line []byte) (badIdx int, isRule bool) {
if len(line) == 0 || line[0] == '#' { if len(line) == 0 || line[0] == '#' {
return -1, false return -1, false
} }
if line[0] != '!' { if line[0] != '!' {
nonPrintIdx = bytes.IndexFunc(line, isNotPrintable) badIdx = slices.IndexFunc(line, likelyBinary)
return nonPrintIdx, nonPrintIdx == -1 return badIdx, badIdx == -1
} }
const titlePattern = "! Title: " const titlePattern = "! Title: "

View File

@@ -6,10 +6,10 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist" "github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/fakeio"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -17,6 +17,9 @@ import (
func TestParser_Parse(t *testing.T) { func TestParser_Parse(t *testing.T) {
t.Parallel() t.Parallel()
longRule := strings.Repeat("a", rulelist.DefaultRuleBufSize+1) + "\n"
tooLongRule := strings.Repeat("a", bufio.MaxScanTokenSize+1) + "\n"
testCases := []struct { testCases := []struct {
name string name string
in string in string
@@ -74,26 +77,42 @@ func TestParser_Parse(t *testing.T) {
wantTitle: "Test Title", wantTitle: "Test Title",
wantRulesNum: 1, wantRulesNum: 1,
wantWritten: len(testRuleTextBlocked), wantWritten: len(testRuleTextBlocked),
}, {
name: "cosmetic_with_zwnj",
in: testRuleTextCosmetic,
wantDst: testRuleTextCosmetic,
wantErrMsg: "",
wantTitle: "",
wantRulesNum: 1,
wantWritten: len(testRuleTextCosmetic),
}, { }, {
name: "bad_char", name: "bad_char",
in: "! Title: Test Title \n" + in: "! Title: Test Title \n" +
testRuleTextBlocked + testRuleTextBlocked +
">>>\x7F<<<", ">>>\x7F<<<",
wantDst: testRuleTextBlocked, wantDst: testRuleTextBlocked,
wantErrMsg: "line at index 2: " + wantErrMsg: "line 3: " +
"character at index 3: " + "character 4: " +
"non-printable character", "likely binary character '\\x7f'",
wantTitle: "Test Title", wantTitle: "Test Title",
wantRulesNum: 1, wantRulesNum: 1,
wantWritten: len(testRuleTextBlocked), wantWritten: len(testRuleTextBlocked),
}, { }, {
name: "too_long", name: "too_long",
in: strings.Repeat("a", rulelist.MaxRuleLen+1), in: tooLongRule,
wantDst: "", wantDst: "",
wantErrMsg: "scanning filter contents: " + bufio.ErrTooLong.Error(), wantErrMsg: "scanning filter contents: bufio.Scanner: token too long",
wantTitle: "", wantTitle: "",
wantRulesNum: 0, wantRulesNum: 0,
wantWritten: 0, wantWritten: 0,
}, {
name: "longer_than_default",
in: longRule,
wantDst: longRule,
wantErrMsg: "",
wantTitle: "",
wantRulesNum: 1,
wantWritten: len(longRule),
}, { }, {
name: "bad_tab_and_comment", name: "bad_tab_and_comment",
in: testRuleTextBadTab, in: testRuleTextBadTab,
@@ -118,7 +137,7 @@ func TestParser_Parse(t *testing.T) {
t.Parallel() t.Parallel()
dst := &bytes.Buffer{} dst := &bytes.Buffer{}
buf := make([]byte, rulelist.MaxRuleLen) buf := make([]byte, rulelist.DefaultRuleBufSize)
p := rulelist.NewParser() p := rulelist.NewParser()
r, err := p.Parse(dst, strings.NewReader(tc.in), buf) r, err := p.Parse(dst, strings.NewReader(tc.in), buf)
@@ -140,12 +159,12 @@ func TestParser_Parse(t *testing.T) {
func TestParser_Parse_writeError(t *testing.T) { func TestParser_Parse_writeError(t *testing.T) {
t.Parallel() t.Parallel()
dst := &aghtest.Writer{ dst := &fakeio.Writer{
OnWrite: func(b []byte) (n int, err error) { OnWrite: func(b []byte) (n int, err error) {
return 1, errors.Error("test error") return 1, errors.Error("test error")
}, },
} }
buf := make([]byte, rulelist.MaxRuleLen) buf := make([]byte, rulelist.DefaultRuleBufSize)
p := rulelist.NewParser() p := rulelist.NewParser()
r, err := p.Parse(dst, strings.NewReader(testRuleTextBlocked), buf) r, err := p.Parse(dst, strings.NewReader(testRuleTextBlocked), buf)
@@ -165,7 +184,7 @@ func TestParser_Parse_checksums(t *testing.T) {
"# Another comment.\n" "# Another comment.\n"
) )
buf := make([]byte, rulelist.MaxRuleLen) buf := make([]byte, rulelist.DefaultRuleBufSize)
p := rulelist.NewParser() p := rulelist.NewParser()
r, err := p.Parse(&bytes.Buffer{}, strings.NewReader(withoutComments), buf) r, err := p.Parse(&bytes.Buffer{}, strings.NewReader(withoutComments), buf)
@@ -192,7 +211,7 @@ var (
func BenchmarkParser_Parse(b *testing.B) { func BenchmarkParser_Parse(b *testing.B) {
dst := &bytes.Buffer{} dst := &bytes.Buffer{}
src := strings.NewReader(strings.Repeat(testRuleTextBlocked, 1000)) src := strings.NewReader(strings.Repeat(testRuleTextBlocked, 1000))
buf := make([]byte, rulelist.MaxRuleLen) buf := make([]byte, rulelist.DefaultRuleBufSize)
p := rulelist.NewParser() p := rulelist.NewParser()
b.ReportAllocs() b.ReportAllocs()
@@ -204,6 +223,14 @@ func BenchmarkParser_Parse(b *testing.B) {
require.NoError(b, errSink) require.NoError(b, errSink)
require.NotNil(b, resSink) require.NotNil(b, resSink)
// Most recent result, on a ThinkPad X13 with a Ryzen Pro 7 CPU:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkParser_Parse-16 100000000 128.0 ns/op 48 B/op 1 allocs/op
} }
func FuzzParser_Parse(f *testing.F) { func FuzzParser_Parse(f *testing.F) {
@@ -215,15 +242,17 @@ func FuzzParser_Parse(f *testing.F) {
"! Comment", "! Comment",
"! Title ", "! Title ",
"! Title XXX", "! Title XXX",
testRuleTextBadTab,
testRuleTextBlocked,
testRuleTextCosmetic,
testRuleTextEtcHostsTab, testRuleTextEtcHostsTab,
testRuleTextHTML, testRuleTextHTML,
testRuleTextBlocked,
testRuleTextBadTab,
"1.2.3.4", "1.2.3.4",
"1.2.3.4 etc-hosts.example", "1.2.3.4 etc-hosts.example",
">>>\x00<<<", ">>>\x00<<<",
">>>\x7F<<<", ">>>\x7F<<<",
strings.Repeat("a", n+1), strings.Repeat("a", rulelist.DefaultRuleBufSize+1),
strings.Repeat("a", bufio.MaxScanTokenSize+1),
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@@ -4,8 +4,6 @@
// TODO(a.garipov): Expand. // TODO(a.garipov): Expand.
package rulelist package rulelist
// MaxRuleLen is the maximum length of a line with a filtering rule, in bytes. // DefaultRuleBufSize is the default length of a buffer used to read a line with
// // a filtering rule, in bytes.
// TODO(a.garipov): Consider changing this to a rune length, like AdGuardDNS const DefaultRuleBufSize = 1024
// does.
const MaxRuleLen = 1024

View File

@@ -7,8 +7,13 @@ const testTimeout = 1 * time.Second
// Common texts for tests. // Common texts for tests.
const ( const (
testRuleTextHTML = "<!DOCTYPE html>\n"
testRuleTextBlocked = "||blocked.example^\n"
testRuleTextBadTab = "||bad-tab-and-comment.example^\t# A comment.\n" testRuleTextBadTab = "||bad-tab-and-comment.example^\t# A comment.\n"
testRuleTextBlocked = "||blocked.example^\n"
testRuleTextEtcHostsTab = "0.0.0.0 tab..example^\t# A comment.\n" testRuleTextEtcHostsTab = "0.0.0.0 tab..example^\t# A comment.\n"
testRuleTextHTML = "<!DOCTYPE html>\n"
// testRuleTextCosmetic is a cosmetic rule with a zero-width non-joiner.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/6003.
testRuleTextCosmetic = "||cosmetic.example## :has-text(/\u200c/i)\n"
) )

View File

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

View File

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

View File

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

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