Compare commits

...

63 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
Ainar Garipov
0a1887a854 Pull request 1914: upd-flts
Squashed commit of the following:

commit a8932f56fad583ecfcb7efae36fc516454bc6610
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 11 16:20:55 2023 +0300

    filtering: fix docs; upd svcs
2023-07-11 16:27:20 +03:00
Dimitry Kolyshev
65b526b969 Pull request: 5972-ip-dupl-ans
Updates #5972.

Squashed commit of the following:

commit 0e089f9ff8fd7e6d7cb53aa7c3b92435d1d41a81
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jul 11 15:33:16 2023 +0300

    dnsforward: imp code

commit 39527c078fd9ad6ea4906659e185d54e74ef6465
Merge: 03641b0b5 61ed74374
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jul 11 11:29:19 2023 +0300

    Merge remote-tracking branch 'origin/master' into 5972-ip-dupl-ans

    # Conflicts:
    #	CHANGELOG.md

commit 03641b0b511f8e48d386be76d0a4776296cf047d
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jul 10 14:03:28 2023 +0300

    all: dupl ips in answer
2023-07-11 15:46:01 +03:00
Ainar Garipov
61ed743748 Pull request 1913: parental-cache-size
Squashed commit of the following:

commit 6e7dcf0c59c478869e65cb6945d8d262b9eb1879
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 10 19:44:49 2023 +0300

    home: fix parental cache size
2023-07-10 20:00:29 +03:00
Ainar Garipov
c02a14117d Pull request 1912: 5896-safe-browsing-ptr
Updates #5896.

Squashed commit of the following:

commit 49340544a2a8762283397cdb54b91ed534591fa0
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 10 17:59:45 2023 +0300

    hashprefix: fix loop pointer
2023-07-10 19:04:31 +03:00
Ainar Garipov
7b92d53b84 Pull request 1910: new-rulelist-parser
Squashed commit of the following:

commit bac0da6818388d67840b8fe9b633ce0804964ed9
Merge: cb6759b63 f7dd83251
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jul 7 18:22:40 2023 +0300

    Merge branch 'master' into new-rulelist-parser

commit cb6759b63546b35074ec0ae04769ddb5e83ebac1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jul 7 12:18:44 2023 +0300

    all: upd tools

commit d28bf4cb42057b84e0c1325389db121a91f7c989
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 6 19:35:48 2023 +0300

    all: upd chlog

commit 7df637b00331dff5810c3a76f4a7d2cee24148f1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 6 19:30:42 2023 +0300

    rulelist: fix tabs

commit 0598d0d43504b246570e9ee76d79dff0d86413c5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 6 19:06:18 2023 +0300

    all: add go-bench, go-fuzz; imp docs

commit a9ab7726048e216b13876a85991f3e3e8696a029
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 6 18:18:14 2023 +0300

    filtering: add new parser
2023-07-07 18:27:33 +03:00
Ainar Garipov
f7dd832517 Pull request 1909: 5939-rm-healthcheck
Updates #5939.

Squashed commit of the following:

commit 087309b4ef100e97339f49cf1c2e90ba2fa4293f
Merge: 360df813d c21f958ea
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jul 7 13:18:52 2023 +0300

    Merge branch 'master' into 5939-rm-healthcheck

commit 360df813d995f935c591aaea9c56fe4372ca2281
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 5 14:16:18 2023 +0300

    all: rm docker healthcheck
2023-07-07 13:58:15 +03:00
Stanislav Chzhen
c21f958eaf Pull request 1878: AG-22597-imp-rdns
Squashed commit of the following:

commit ccad155c34989943d88a0a260c50845d1f4ece6b
Merge: 0cd889f6a 5a195b441
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 6 17:00:58 2023 +0300

    Merge branch 'master' into AG-22597-imp-rdns

commit 0cd889f6a500f5616af0f8d8fdcde0403b87ad4f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jul 6 12:20:49 2023 +0300

    dnsforward: imp code

commit 1aaa1998b914b0d53142c21fa3bdcae502e4f3f6
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jul 4 20:11:55 2023 +0300

    home: add todo

commit aed232fcf70ef546f373d5235b73abcb4fbb4b6c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jul 4 13:25:28 2023 +0300

    all: imp code, tests

commit 5c028c2766ffb8ebdc358a245a249c6a55d9ad81
Merge: 83d6ae7f6 97af062f7
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jul 3 18:54:42 2023 +0300

    Merge branch 'master' into AG-22597-imp-rdns

commit 83d6ae7f61a7b81a8d73cd6d747035278c64fb70
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jul 3 18:53:05 2023 +0300

    home: imp code

commit 8153988dece0406e51a90a43eaffae59dba30a36
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 30 18:06:09 2023 +0300

    all: imp code

commit 00d3cc11a9378318f176aae00ddf972f255d575c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 30 13:05:04 2023 +0300

    all: add tests

commit ffdc95f237bfdb780922b4390d82cdc0154b0621
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 29 15:20:00 2023 +0300

    all: imp code, docs

commit 0dc60e2b355750ca701558927d22fb9ad187ea7e
Merge: 69dd56bdb d4a4bda64
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 29 15:13:19 2023 +0300

    Merge branch 'master' into AG-22597-imp-rdns

commit 69dd56bdb75056b0fa6bcf6538af7fff93383323
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 23 14:36:29 2023 +0300

    rdns: add tests

commit 16909b51adbe3a3f230291834cc9486dd8a0e8f8
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jun 19 16:28:26 2023 +0300

    rdns: extract rdns
2023-07-06 17:10:06 +03:00
Dimitry Kolyshev
5a195b441c Pull request: log-yaml-conf
Updates #4897.

Squashed commit of the following:

commit 8a961157c9930bf4859ce2209e5016ce94987e12
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jul 5 10:13:24 2023 +0400

    home: imp code

commit 509c07eed06311d773bc3e34b3ca28d9f14186fe
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jul 4 17:33:31 2023 +0400

    all: fix

commit f032e28f98552f238721491c998fca2d7d4b9802
Merge: ee0113435 c46516475
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jul 4 17:31:29 2023 +0400

    Merge remote-tracking branch 'origin/master' into log-yaml-conf

    # Conflicts:
    #	CHANGELOG.md

commit ee011343512e82d4e21cb402759d0284523ba02a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jul 4 12:31:42 2023 +0400

    all: changelog

commit 07f4c4a244b1b6200d3056cde5ebced6254084a7
Merge: 2042c0753 97af062f7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jul 4 12:25:21 2023 +0400

    Merge remote-tracking branch 'origin/master' into log-yaml-conf

commit 2042c0753ec29de6045c3f1de6d075cb93d6ec27
Merge: a1d3a5130 8004b135b
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jul 3 16:25:26 2023 +0400

    Merge remote-tracking branch 'origin/master' into log-yaml-conf

commit a1d3a51307e80f9e509bd6f3bee1a7b17bf1ffe6
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri Jun 30 11:11:32 2023 +0400

    home: imp code

commit 2392a3b02620b8c38e88afb4d75988be85fe1338
Merge: 4224fbed7 39f5c50ac
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 29 16:46:47 2023 +0400

    home: imp code

commit 4224fbed7113e94bee44d0ab0272e8302a8086f3
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 29 16:39:35 2023 +0400

    home: imp code

commit 5ce708cc50bed83e64f062e599fc8b6143d0d44d
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 29 12:48:42 2023 +0400

    all: docs

commit 4b6d898a888818410f59b843c3ca1a685aafec82
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 29 12:20:54 2023 +0400

    home: imp code

commit 431b44eda71f488c747c3efa4da0a6c222b1cf06
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 29 12:03:17 2023 +0400

    home: imp tests

commit 53a5c31060018c37953beb27d80c46f92bbe14af
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 28 17:40:49 2023 +0400

    home: imp docs

commit bfa57d9f21e3326baafd3a52e91d54396d8e03fa
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 28 15:49:40 2023 +0400

    home: log conf

commit 49e06dca9dc2ceb2647b7e36dac145ccf78a6c3f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 28 15:41:02 2023 +0400

    home: log conf

commit 9be432dea7cec8ae0c0d3ee1f73c58c76376d07e
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 28 10:45:01 2023 +0400

    home: log conf
2023-07-06 10:09:04 +03:00
Ainar Garipov
c46516475d Pull request 1906: 5896-safe-browsing-cpu-ram
Updates #5896.

Squashed commit of the following:

commit 81ac59e2f95ef3ad6ac5c4668c07c35d69570454
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 4 16:07:33 2023 +0300

    hashprefix: fix cache coding
2023-07-04 16:17:18 +03:00
Ainar Garipov
97af062f7b Pull request 1905: upd-chlog
Squashed commit of the following:

commit 783d9c9265be564e91e62b24d0e0e3213f1f7d7d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 3 17:14:17 2023 +0300

    all: upd chlog
2023-07-03 17:17:29 +03:00
Ainar Garipov
8004b135b1 Pull request 1904: 5959-fix-error-days
Updates #5959.

* commit '4b9264531be50e81fe610050a12827b71bc3a9cd':
  clients: use constant a day in milliseconds
  clients: fix lint
  fix error days
2023-07-03 14:34:19 +03:00
Ildar Kamalov
4b9264531b clients: use constant a day in milliseconds 2023-07-03 14:29:33 +03:00
Ainar Garipov
9a506d3755 clients: fix lint 2023-07-03 14:23:18 +03:00
qingbo
e320eb29c2 fix error days 2023-07-03 18:48:14 +08:00
Ainar Garipov
282f11a7c2 Pull request 1903: upd-all
Squashed commit of the following:

commit 61838cb3e08dcfd16c9fa521a8243207ec2091aa
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 3 13:30:37 2023 +0300

    all: upd i18n, svcs, tools, trackers
2023-07-03 13:40:30 +03:00
Eugene Burkov
91f3e29c08 Pull request 1891: 5902-bootstrap-hosts
Merge in DNS/adguard-home from 5902-bootstrap-hosts to master

Updates #5902.

Squashed commit of the following:

commit fcc65d3a8d7566acc361f54b18d1af85045225e2
Merge: 0c336af07 1fd6cf1a2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jun 30 12:29:06 2023 +0300

    Merge branch 'master' into 5902-bootstrap-hosts

commit 0c336af07d2864533e1f10029b4321d7cd210a47
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jun 29 15:40:28 2023 +0300

    all: imp & simplify

commit 45aae90035b98b30199cc7fc92991528f4e968c0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jun 28 20:24:43 2023 +0300

    all: imp code, docs

commit e3dbb5bfe5dfbde7af00f39adcc15e9711e5feb0
Merge: a33a8e93c 2069eddf9
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jun 28 18:27:36 2023 +0300

    Merge branch 'master' into 5902-bootstrap-hosts

commit a33a8e93cb36f7d0c4472e524e44de6ff0ab6653
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jun 28 13:27:11 2023 +0300

    aghos: add type check

commit 781a3a248871df2ea37a936c8d6b0b11e2d2f3a4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jun 28 13:09:37 2023 +0300

    all: log changes

commit 4575368655356f84992fad2bfb78cbc1c88da25a
Merge: 636c440fc cf7c12c97
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jun 28 13:08:11 2023 +0300

    Merge branch 'master' into 5902-bootstrap-hosts

commit 636c440fca9cbdfd5c12b7f89432fb9323e01d86
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jun 28 13:06:32 2023 +0300

    all: imp tests

commit 0eff7a747e32216d78abf9db9460cb9d48f31f96
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jun 26 18:40:22 2023 +0300

    dnsforward: imp code

commit 7489a30971e3c76b8f62fd4ca11a977eeabe2cf5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jun 26 17:04:10 2023 +0300

    all: resolve upstreams with hosts
2023-06-30 12:41:10 +03:00
Stanislav Chzhen
1fd6cf1a2f Pull request 1901: 5946-fix-blocked-services-client-schedule
Updates #5946.

Squashed commit of the following:

commit cd6ba613fae56d05a2e51ae1a65e9fcf4a39899e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 30 11:27:52 2023 +0300

    home: fix blocked services client schedule
2023-06-30 11:56:03 +03:00
Ainar Garipov
efed23701a Pull request 1900: fix-docker-script
Closes #5947.

Squashed commit of the following:

commit 309fab7afd78585a561830379f06dd531e6f260e
Merge: 31a6a577d ad1bf5cf6
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jun 30 10:43:36 2023 +0300

    Merge branch 'master' into fix-docker-script

commit 31a6a577ddd2ae8f6a27f852f655628333f8e3da
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 29 21:48:37 2023 +0300

    scripts: fix unbound variable

commit 59b2bb836a11b2f2719d5c5a9bf5d025736d4435
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 29 21:12:37 2023 +0300

    scripts: fix docker
2023-06-30 10:47:44 +03:00
Stanislav Chzhen
ad1bf5cf6e Pull request 1898: fix-whois-gocognit
Squashed commit of the following:

commit 69675b742a3b8a1d513f1b5f9c488577ced329b8
Merge: 0d069b769 ee8eb1d8a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 29 19:31:26 2023 +0300

    Merge branch 'master' into fix-whois-gocognit

commit 0d069b76938d9b3011b8d92865e7630027f1ebd1
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 29 19:21:02 2023 +0300

    whois: imp code more

commit 6c4ab4a4e0451551bcec5472d997a88989f013e4
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 29 18:08:50 2023 +0300

    whois: imp code

commit 0d04ddbd6f8bb3848673c3f86fc26dca2fd4d402
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 29 17:47:06 2023 +0300

    whois: imp docs

commit d7b75f7b42604374cb8cb0aaf141eb5c7fc985d3
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 29 16:54:49 2023 +0300

    all: imp code
2023-06-29 19:35:33 +03:00
Ainar Garipov
ee8eb1d8a6 Pull request 1899: nextapi-pidfile-webaddr
Squashed commit of the following:

commit 73b97b638016dd3992376c2cd7d11b2e85b2c3a4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 29 18:43:05 2023 +0300

    next: use maybe; sync conf

commit 99e18b8fbfad11343a1e66f746085d54be7aafea
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 29 18:13:13 2023 +0300

    next: add local frontend, pidfile, webaddr
2023-06-29 19:10:39 +03:00
Dimitry Kolyshev
39f5c50acd Pull request: home: http conf
Updates #2860.

Squashed commit of the following:

commit 0d55a99d5c0b9f1d8c9497775dd69929e5091eaa
Merge: 73a203ac8 d4a4bda64
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 29 16:25:36 2023 +0400

    Merge remote-tracking branch 'origin/master' into http-yaml-conf

commit 73a203ac8acf083fa289015e1f301d05bf320ea7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 29 16:21:48 2023 +0400

    home: imp docs

commit a4819ace94bfe4427f70f1b8341c9babc9234740
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 29 11:45:30 2023 +0400

    snap: imp script

commit b0913c7ac5c6c46d6a73790fd57d8c5f9d7ace75
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 28 17:34:03 2023 +0400

    all: docs

commit 14820d6d56f958081d9f236277fd34f356bdab33
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 28 13:21:43 2023 +0400

    home: imp tests

commit 9db800d3ce39c36da7959e37b4a46736f4217e5c
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 28 13:17:34 2023 +0400

    all: docs

commit 9174a0ae710da51d85b4e1b1af79eda6a61dd3a2
Merge: ca8c4ae95 d88181343
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 28 10:19:01 2023 +0400

    Merge remote-tracking branch 'origin/master' into http-yaml-conf

    # Conflicts:
    #	CHANGELOG.md
    #	internal/home/upgrade.go
    #	internal/home/upgrade_test.go

commit ca8c4ae954ece25d78ef2f873bb3ba71fa4b8fa9
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 28 10:07:15 2023 +0400

    snap: imp script

commit d84473f8e07b2c6e65023613eb4032fd01951521
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 28 09:59:57 2023 +0400

    snap: imp script

commit 8a0808e42ddbff7d9d3345d758f91b14bb4453be
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jun 27 15:03:53 2023 +0400

    home: http conf

commit e8fbb89cc5748f9d8fa4be9e702756bd8b869de9
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jun 27 14:59:37 2023 +0400

    home: imp code

commit 46541aabc421118562d564675dfd7e594d2056aa
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jun 27 12:36:14 2023 +0400

    snap: bind port

commit cecda5fcfd8c473db42f235b4f586b2193086997
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jun 27 12:12:39 2023 +0400

    docker: bind port

commit 8d8945b70366c6b018616a32421c77eb281a6ea1
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jun 27 11:06:32 2023 +0400

    home: imp code

commit ae5e8c1c4333d7b752c08605d80e41f55ee50e59
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jun 27 11:02:09 2023 +0400

    home: imp code

commit c9ee460f37e32941b84ea5fa94d21b186d6dd82b
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jun 26 17:11:10 2023 +0400

    home: imp code

commit 44c72445112ef38d6ec9c25b197c119edd6c959f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jun 26 11:52:19 2023 +0400

    all: docs

commit e3bf5faeb748f347b1202a496788739ff9219ed0
Merge: 38cc0f639 e7e638443
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jun 26 11:39:12 2023 +0400

    Merge remote-tracking branch 'origin/master' into http-yaml-conf

commit 38cc0f6399040f1fa39d9da31ad6db65a6bdd4cc
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jun 26 11:38:17 2023 +0400

    snap: bind port

commit 3b9cb9e8cc89a67e55cecc7a2040c150f8675b4c
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jun 26 11:25:03 2023 +0400

    docker: bind port

... and 4 more commits
2023-06-29 15:29:52 +03:00
Ainar Garipov
d4a4bda645 Pull request 1897: nextapi-write-conf
Squashed commit of the following:

commit 72f25ffe73d6b8216b01e590fba66fb5f6944113
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 28 21:29:04 2023 +0300

    next: add conf writing, validation
2023-06-29 14:34:06 +03:00
174 changed files with 8103 additions and 3411 deletions

View File

@@ -32,31 +32,33 @@
- 'attributes':
'description': 'On which Platform does the issue occur?'
'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':
- 'Darwin (aka macOS)/AMD64 (aka x86_64)'
- 'Darwin (aka macOS)/ARM64'
- 'FreeBSD/386'
- 'FreeBSD/AMD64 (aka x86_64)'
- 'FreeBSD/ARM64'
- 'FreeBSD/ARMv5'
- 'FreeBSD/ARMv6'
- 'FreeBSD/ARMv7'
- 'Linux/386'
- 'Linux/AMD64 (aka x86_64)'
- 'Linux/ARM64'
- 'Linux/ARMv5'
- 'Linux/ARMv6'
- 'Linux/ARMv7'
- 'Linux/MIPS LE'
- 'Linux/MIPS'
- 'Linux/MIPS64 LE'
- 'Linux/MIPS64'
- 'Linux/PPC64 LE'
- 'OpenBSD/AMD64 (aka x86_64)'
- 'OpenBSD/ARM64'
- 'Windows/386'
- 'Windows/AMD64 (aka x86_64)'
- 'Windows/ARM64'
- 'Darwin (aka macOS), AMD64 (aka x86_64)'
- 'Darwin (aka macOS), ARM64'
- 'FreeBSD, AMD64 (aka x86_64)'
- 'FreeBSD, ARM64'
- 'FreeBSD, ARMv5'
- 'FreeBSD, ARMv6'
- 'FreeBSD, ARMv7'
- 'FreeBSD, 32-bit Intel (aka 386)'
- 'Linux, AMD64 (aka x86_64)'
- 'Linux, ARM64'
- 'Linux, ARMv5'
- 'Linux, ARMv6'
- 'Linux, ARMv7'
- 'Linux, MIPS LE'
- 'Linux, MIPS'
- 'Linux, MIPS64 LE'
- 'Linux, MIPS64'
- 'Linux, PPC64 LE'
- 'Linux, 32-bit Intel (aka 386)'
- 'OpenBSD, AMD64 (aka x86_64)'
- 'OpenBSD, ARM64'
- 'Windows, AMD64 (aka x86_64)'
- 'Windows, ARM64'
- 'Windows, 32-bit Intel (aka 386)'
- 'Custom (please mention in the description)'
'id': 'os'
'type': 'dropdown'
@@ -142,8 +144,10 @@
'type': 'textarea'
'validations':
'required': false
'description': >
Open a bug report. Please do not open bug reports for questions or help
with configuring clients. If you want to ask for help, use the Discussions
section.
# NOTE: GitHub limits the description length to 200 characters. Also, Markdown
# doesn't work here.
'description': |
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'

View File

@@ -48,7 +48,11 @@
'type': 'textarea'
'validations':
'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':
- 'feature request'
'name': 'Feature request or enhancement'

View File

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

View File

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

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@
*.db
*.log
*.snap
*.test
/agh-backup/
/bin/
/build/*

View File

@@ -14,17 +14,216 @@ and this project adheres to
<!--
## [v0.108.0] - TBA
## [v0.107.33] - 2023-06-28 (APPROX.)
## [v0.107.37] - 2023-08-16 (APPROX.)
See also the [v0.107.33 GitHub milestone][ms-v0.107.33].
See also the [v0.107.37 GitHub milestone][ms-v0.107.37].
[ms-v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/milestone/68?closed=1
[ms-v0.107.37]: https://github.com/AdguardTeam/AdGuardHome/milestone/72?closed=1
NOTE: Add new changes BELOW THIS COMMENT.
-->
### Added
- The ability to filter DNS HTTPS records including IPv4/v6 hints. ([#6053]).
- Two new metrics showing total number of responses from each upstream DNS
server and their average processing time in the Web UI ([#1453]).
- The ability to set the port for the `pprof` debug API, see configuration
changes below.
### Changed
- For non-A and non-AAAA requests, which has been filtered, the NODATA response
is returned if the blocking mode isn't set to `Null IP`. In previous versions
it returned NXDOMAIN response in such cases.
#### Configuration Changes
In this release, the schema version has changed from 24 to 25.
- Property `debug_pprof` which used to setup profiling HTTP handler, is now
moved to the new `pprof` object under `http` section. The new object contains
properties `enabled` and `port`:
```yaml
# BEFORE:
'debug_pprof': true
# AFTER:
'http':
'pprof':
'enabled': true
'port': 6060
```
Note that the new default `6060` is used as default. To rollback this change,
remove the new object `pprof`, set back `debug_pprof`, and change the
`schema_version` back to `24`.
### Fixed
- Address already in use when trying to install on port 3000 ([#6099]).
- Panic on using a single-slash filtering rule.
- Panic on shutting down while DNS requests are in process of filtering
([#5948]).
[#1453]: https://github.com/AdguardTeam/AdGuardHome/issues/1453
[#5948]: https://github.com/AdguardTeam/AdGuardHome/issues/5948
[#6053]: https://github.com/AdguardTeam/AdGuardHome/issues/6053
[#6099]: https://github.com/AdguardTeam/AdGuardHome/issues/6099
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
## [v0.107.36] - 2023-08-02
See also the [v0.107.36 GitHub milestone][ms-v0.107.36].
### Security
- Go version has been updated to prevent the possibility of exploiting the
CVE-2023-29409 Go vulnerability fixed in [Go 1.20.7][go-1.20.7].
### Deprecated
- Go 1.20 support. Future versions will require at least Go 1.21 to build.
### Fixed
- Inability to block queries for the root domain, such as `NS .` queries, using
the *Disallowed domains* feature on the *DNS settings* page ([#6049]). Users
who want to block `.` queries should use the `|.^` AdBlock rule or a similar
regular expression.
- Client hostnames not resolving when upstream server responds with zero-TTL
records ([#6046]).
[#6046]: https://github.com/AdguardTeam/AdGuardHome/issues/6046
[#6049]: https://github.com/AdguardTeam/AdGuardHome/issues/6049
[go-1.20.7]: https://groups.google.com/g/golang-announce/c/X0b6CsSAaYI/m/Efv5DbZ9AwAJ
[ms-v0.107.36]: https://github.com/AdguardTeam/AdGuardHome/milestone/71?closed=1
## [v0.107.35] - 2023-07-26
See also the [v0.107.35 GitHub milestone][ms-v0.107.35].
### Changed
- Improved reliability filtering-rule list updates on Unix systems.
### Fixed
- Occasional client information lookup failures that could lead to the DNS
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
- Improved CPU and RAM consumption during updates of filtering-rule lists.
#### Configuration Changes
In this release, the schema version has changed from 23 to 24.
- 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`, `max_backups`, `max_size`, `max_age`, `compress`, `local_time`, and
`verbose`:
```yaml
# BEFORE:
'log_file': ""
'log_max_backups': 0
'log_max_size': 100
'log_max_age': 3
'log_compress': false
'log_localtime': false
'verbose': false
# AFTER:
'log':
'file': ""
'max_backups': 0
'max_size': 100
'max_age': 3
'compress': false
'local_time': false
'verbose': false
```
To rollback this change, remove the new object `log`, set back `log_` and
`verbose` properties and change the `schema_version` back to `23`.
### Deprecated
- Default exposure of the non-standard ports 784 and 8853 for DNS-over-QUIC in
the `Dockerfile`.
### Fixed
- Two unspecified IPs when a host is blocked in two filter lists ([#5972]).
- Incorrect setting of Parental Control cache size.
- Excessive RAM and CPU consumption by Safe Browsing and Parental Control
filters ([#5896]).
### Removed
- The `HEALTHCHECK` section and the use of `tini` in the `ENTRYPOINT` section in
`Dockerfile` ([#5939]). They caused a lot of issues, especially with tools
like `docker-compose` and `podman`.
**NOTE:** Some Docker tools may cache `ENTRYPOINT` sections, so some users may
be required to backup their configuration, stop the container, purge the old
image, and reload it from scratch.
[#5896]: https://github.com/AdguardTeam/AdGuardHome/issues/5896
[#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
[ms-v0.107.34]: https://github.com/AdguardTeam/AdGuardHome/milestone/69?closed=1
## [v0.107.33] - 2023-07-03
See also the [v0.107.33 GitHub milestone][ms-v0.107.33].
### Added
- The new command-line flag `--web-addr` is the address to serve the web UI on,
in the host:port format.
- The ability to set inactivity periods for filtering blocked services, both
@@ -37,8 +236,27 @@ NOTE: Add new changes BELOW THIS COMMENT.
#### Configuration Changes
In this release, the schema version has changed from 20 to 22.
In this release, the schema version has changed from 20 to 23.
- Properties `bind_host`, `bind_port`, and `web_session_ttl` which used to setup
web UI binding configuration, are now moved to a new object `http` containing
new properties `address` and `session_ttl`:
```yaml
# BEFORE:
'bind_host': '1.2.3.4'
'bind_port': 8080
'web_session_ttl': 720
# AFTER:
'http':
'address': '1.2.3.4:8080'
'session_ttl': '720h'
```
Note that the new `http.session_ttl` property is now a duration string. To
rollback this change, remove the new object `http`, set back `bind_host`,
`bind_port`, `web_session_ttl`, and change the `schema_version` back to `22`.
- Property `clients.persistent.blocked_services`, which in schema versions 21
and earlier used to be a list containing ids of blocked services, is now an
object containing ids and schedule for blocked services:
@@ -118,14 +336,20 @@ In this release, the schema version has changed from 20 to 22.
### Deprecated
- The `HEALTHCHECK` section and the use of `tini` in the `ENTRYPOINT` section in
`Dockerfile` ([#5939]). They cause a lot of issues, especially with tools
like `docker-compose` and `podman`, and will be removed in a future release.
- Flags `-h`, `--host`, `-p`, `--port` have been deprecated. The `-h` flag
will work as an alias for `--help`, instead of the deprecated `--host` in the
future releases.
### Fixed
- Ignoring of `/etc/hosts` file when resolving the hostnames of upstream DNS
servers ([#5902]).
- Excessive error logging when using DNS-over-QUIC ([#5285]).
- Cannot set `bind_host` in AdGuardHome.yaml (docker version)([#4231], [#4235]).
- Inability to set `bind_host` in `AdGuardHome.yaml` in Docker ([#4231],
[#4235]).
- The blocklists can now be deleted properly ([#5700]).
- Queries with the question-section target `.`, for example `NS .`, are now
counted in the statistics and correctly shown in the query log ([#5910]).
@@ -138,12 +362,12 @@ In this release, the schema version has changed from 20 to 22.
[#4235]: https://github.com/AdguardTeam/AdGuardHome/pull/4235
[#5285]: https://github.com/AdguardTeam/AdGuardHome/issues/5285
[#5700]: https://github.com/AdguardTeam/AdGuardHome/issues/5700
[#5902]: https://github.com/AdguardTeam/AdGuardHome/issues/5902
[#5910]: https://github.com/AdguardTeam/AdGuardHome/issues/5910
[#5913]: https://github.com/AdguardTeam/AdGuardHome/issues/5913
[#5939]: https://github.com/AdguardTeam/AdGuardHome/discussions/5939
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
[ms-v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/milestone/68?closed=1
@@ -2125,11 +2349,15 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...HEAD
[v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...v0.107.33
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.37...HEAD
[v0.107.37]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.36...v0.107.37
-->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...HEAD
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.36...HEAD
[v0.107.36]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.35...v0.107.36
[v0.107.35]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.34...v0.107.35
[v0.107.34]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...v0.107.34
[v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...v0.107.33
[v0.107.32]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.31...v0.107.32
[v0.107.31]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.30...v0.107.31
[v0.107.30]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.29...v0.107.30

View File

@@ -78,7 +78,7 @@ build: deps quick-build
quick-build: js-build go-build
ci: deps test
ci: deps test go-bench go-fuzz
deps: js-deps go-deps
lint: js-lint go-lint
@@ -104,8 +104,10 @@ js-deps:
js-lint: ; $(NPM) $(NPM_FLAGS) run lint
js-test: ; $(NPM) $(NPM_FLAGS) run test
go-bench: ; $(ENV) "$(SHELL)" ./scripts/make/go-bench.sh
go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh
go-deps: ; $(ENV) "$(SHELL)" ./scripts/make/go-deps.sh
go-fuzz: ; $(ENV) "$(SHELL)" ./scripts/make/go-fuzz.sh
go-lint: ; $(ENV) "$(SHELL)" ./scripts/make/go-lint.sh
go-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-tools.sh
@@ -128,3 +130,10 @@ openapi-lint: ; cd ./openapi/ && $(YARN) test
openapi-show: ; cd ./openapi/ && $(YARN) start
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)
* [Automated install (Unix)](#automated-install-linux-and-mac)
* [Automated install (Linux/Unix/MacOS/FreeBSD/OpenBSD)](#automated-install-linux-and-mac)
* [Alternative methods](#alternative-methods)
* [Guides](#guides)
* [API](#api)
@@ -79,7 +79,7 @@ code.
## <a href="#getting-started" id="getting-started" name="getting-started">Getting Started</a>
### <a href="#automated-install-linux-and-mac" id="automated-install-linux-and-mac" name="automated-install-linux-and-mac">Automated install (Unix)</a>
### <a href="#automated-install-linux-and-mac" id="automated-install-linux-and-mac" name="automated-install-linux-and-mac">Automated install (Linux/Unix/MacOS/FreeBSD/OpenBSD)</a>
To install with `curl` run the following command:
@@ -261,7 +261,7 @@ Run `make init` to prepare the development environment.
You will need this to build AdGuard Home:
* [Go](https://golang.org/dl/) v1.19 or later;
* [Go](https://golang.org/dl/) v1.20 or later;
* [Node.js](https://nodejs.org/en/download/) v10.16.2 or later;
* [npm](https://www.npmjs.com/) v6.14 or later;
* [yarn](https://yarnpkg.com/) v1.22.5 or later.
@@ -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>
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

View File

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

View File

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

View File

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

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Opravdu chcete odstranit klienta \"{{key}}\"?",
"list_confirm_delete": "Opravdu chcete smazat tento seznam?",
"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_desc": "Zde můžete konfigurovat pravidla přístupu pro server DNS AdGuard Home",
"access_allowed_title": "Povolení klienti",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Sikker på, at du vil slette klient \"{{key}}\"?",
"list_confirm_delete": "Sikker på, at du vil slette denne liste?",
"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_desc": "Her kan adgangsregler for AdGuard Home DNS-serveren opsættes",
"access_allowed_title": "Tilladte klienter",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Möchten Sie den Client „{{key}}“ wirklich löschen?",
"list_confirm_delete": "Möchten Sie diese Liste wirklich löschen?",
"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_desc": "Hier können Sie die Zugriffsregeln für den DNS-Server von AdGuard Home konfigurieren",
"access_allowed_title": "Zugelassene Clients",

View File

@@ -125,6 +125,8 @@
"top_clients": "Top clients",
"no_clients_found": "No clients found",
"general_statistics": "General statistics",
"top_upstreams": "Top upstreams",
"no_upstreams_data_found": "No upstreams data found",
"number_of_dns_query_days": "The number of DNS queries processed for the last {{count}} day",
"number_of_dns_query_days_plural": "The number of DNS queries processed for the last {{count}} days",
"number_of_dns_query_24_hours": "The number of DNS queries processed for the last 24 hours",
@@ -134,6 +136,7 @@
"enforced_save_search": "Enforced safe search",
"number_of_dns_query_to_safe_search": "The number of DNS requests to search engines for which Safe Search was enforced",
"average_processing_time": "Average processing time",
"processing_time": "Processing time",
"average_processing_time_hint": "Average time in milliseconds on processing a DNS request",
"block_domain_use_filters_and_hosts": "Block domains using filters and hosts files",
"filters_block_toggle_hint": "You can setup blocking rules in the <a>Filters</a> settings.",
@@ -158,6 +161,7 @@
"upstream_dns_configured_in_file": "Configured in {{path}}",
"test_upstream_btn": "Test upstreams",
"upstreams": "Upstreams",
"upstream": "Upstream",
"apply_btn": "Apply",
"disabled_filtering_toast": "Disabled filtering",
"enabled_filtering_toast": "Enabled filtering",
@@ -444,7 +448,7 @@
"client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?",
"list_confirm_delete": "Are you sure you want to delete this list?",
"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_desc": "Here you can configure access rules for the AdGuard Home DNS server",
"access_allowed_title": "Allowed clients",

View File

@@ -444,7 +444,7 @@
"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?",
"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_desc": "Aquí puedes configurar las reglas de acceso para el servidor DNS de AdGuard Home",
"access_allowed_title": "Clientes permitidos",

View File

@@ -2,21 +2,21 @@
"client_settings": "Päätelaiteasetukset",
"example_upstream_reserved": "ylävirta <0>tietyille verkkotunnuksille</0>;",
"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",
"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_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_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_default_resolver": "Oletusarvoisesti AdGuard Home käyttää seuraavia käänteisDNS-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_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ää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äänteis-DNS-resolvereita: {{ip}}.",
"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",
"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).",
"use_private_ptr_resolvers_title": "Käytä yksityisiä käänteisDNS-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.",
"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äänteis-DNS-resolvereita",
"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",
"save_config": "Tallenna asetukset",
"enabled_dhcp": "DHCP-palvelin otettiin käyttöön",
@@ -220,7 +220,7 @@
"example_upstream_tcp_port": "tavallinen DNS (TCP, portti);",
"example_upstream_tcp_hostname": "tavallinen DNS (TCP, isäntänimi);",
"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_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",
@@ -444,7 +444,7 @@
"client_confirm_delete": "Haluatko varmasti poistaa päätelaitteen \"{{key}}\"?",
"list_confirm_delete": "Haluatko varmasti poistaa tämän listan?",
"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_desc": "Tässä voidaan määrittää AdGuard Homen DNS-palvelimen käyttöoikeussääntöjä.",
"access_allowed_title": "Sallitut päätelaitteet",
@@ -623,7 +623,7 @@
"enter_cache_size": "Syötä välimuistin koko (tavuina)",
"enter_cache_ttl_min_override": "Syötä vähimmä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).",
"ttl_cache_validation": "Välimuistin vähimmäiselinajan on oltava pienempi tai sama kuin enimmäiselinajan",
"cache_optimistic": "Optimistinen välimuisti",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Voulez-vous vraiment supprimer le client « {{key}} » ?",
"list_confirm_delete": "Voulez-vous vraiment supprimer cette liste ?",
"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_desc": "Ici vous pouvez configurer les règles d'accès au serveur DNS AdGuard Home",
"access_allowed_title": "Clients autorisés",

View File

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

View File

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

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Sei sicuro di voler eliminare il client \"{{key}}\"?",
"list_confirm_delete": "Sei sicuro di voler eliminare questo elenco?",
"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_desc": "Qui puoi configurare le regole d'accesso per il server DNS di AdGuard Home",
"access_allowed_title": "Client permessi",

View File

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

View File

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

View File

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

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Czy na pewno chcesz usunąć klienta \"{{key}}\"?",
"list_confirm_delete": "Czy na pewno chcesz usunąć tę listę?",
"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_desc": "Tutaj możesz skonfigurować reguły dostępu dla serwera DNS AdGuard Home",
"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_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_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_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>.",

View File

@@ -444,7 +444,7 @@
"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?",
"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_desc": "Aqui você pode configurar as regras de acesso para o servidores de DNS do AdGuard Home",
"access_allowed_title": "Clientes permitidos",

View File

@@ -444,7 +444,7 @@
"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?",
"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_desc": "Aqui pode configurar as regras de acesso para o servidores de DNS do AdGuard Home",
"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}}\"?",
"list_confirm_delete": "Sigur doriți să ștergeți această listă?",
"auto_clients_title": "Clienți runtime",
"auto_clients_desc": "Dispozitivele care nu se află pe lista de clienți Persistent care pot utiliza în continuare AdGuard Home",
"auto_clients_desc": "Informații despre adresele IP ale dispozitivelor care utilizează sau pot utiliza AdGuard Home. Aceste informații sunt colectate din mai multe surse, inclusiv din fișiere hosts, DNS inversat etc.",
"access_title": "Setări de acces",
"access_desc": "Aici puteți configura regulile de acces pentru serverul DNS AdGuard Home",
"access_allowed_title": "Clienți autorizați",

View File

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

View File

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

View File

@@ -387,7 +387,7 @@
"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_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_invalid": "Certifikačný reťazec je neplatný",
"encryption_key_valid": "Toto je platný {{type}} súkromný kľúč",
@@ -444,7 +444,7 @@
"client_confirm_delete": "Naozaj chcete vymazať \"{{key}}\" klienta?",
"list_confirm_delete": "Naozaj chcete vymazať tento zoznam?",
"auto_clients_title": "Runtime klienti",
"auto_clients_desc": "Zariadenia, ktoré nie sú na zozname trvalých klientov, ktorí môžu stále používať AdGuard Home",
"auto_clients_desc": "Informácie o IP adresách zariadení, ktoré používajú alebo môžu používať AdGuard Home. Tieto informácie sa získavajú z viacerých zdrojov vrátane súborov hosts, reverzného DNS atď.",
"access_title": "Nastavenia prístupu",
"access_desc": "Tu môžete konfigurovať pravidlá prístupu pre server DNS AdGuard Home.",
"access_allowed_title": "Povolení klienti",
@@ -497,7 +497,7 @@
"blocked_services": "Blokované 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_global": "Použite globálne blokované služby",
"blocked_services_global": "Použiť globálne blokované služby",
"blocked_service": "Blokované služby",
"block_all": "Blokovať všetko",
"unblock_all": "Odblokovať všetko",
@@ -554,7 +554,7 @@
"whois": "WHOIS",
"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_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",
"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.",
@@ -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_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_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",
"check_title": "Skontrolujte filtráciu",
"check_desc": "Skontrolujte, či je názov hostiteľa filtrovaný.",
@@ -608,7 +608,7 @@
"show_whitelisted_responses": "Obsiahnuté v bielej listine",
"show_processed_responses": "Spracované",
"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",
"allowed": "Povolené",
"filtered": "Filtrované",

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "Ali ste prepričani, da želite izbrisati odjemalca \"{{key}}\"?",
"list_confirm_delete": "Ali ste prepričani, da želite izbrisati ta seznam?",
"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_desc": "Tukaj lahko nastavite pravila dostopa strežnika DNS AdGuard Home",
"access_allowed_title": "Dovoljeni odjemalci",

View File

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

View File

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

View File

@@ -444,7 +444,7 @@
"client_confirm_delete": "\"{{key}}\" istemcisini silmek istediğinizden emin misiniz?",
"list_confirm_delete": "Bu listeyi silmek istediğinizden emin misiniz?",
"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_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz",
"access_allowed_title": "İzin verilen istemciler",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ import { shallowEqual, useSelector } from 'react-redux';
import Card from '../ui/Card';
import { formatNumber } from '../../helpers/helpers';
import LogsSearchLink from '../ui/LogsSearchLink';
import { RESPONSE_FILTER } from '../../helpers/constants';
import { RESPONSE_FILTER, DAY } from '../../helpers/constants';
import Tooltip from '../ui/Tooltip';
const Row = ({
@@ -54,12 +54,12 @@ const Counters = ({ refreshButton, subtitle }) => {
avgProcessingTime,
} = useSelector((state) => state.stats, shallowEqual);
const { t } = useTranslation();
const days = interval / DAY;
const rows = [
{
label: 'dns_query',
count: numDnsQueries,
tooltipTitle: interval === 1 ? 'number_of_dns_query_24_hours' : t('number_of_dns_query_days', { count: interval }),
tooltipTitle: days === 1 ? 'number_of_dns_query_24_hours' : t('number_of_dns_query_days', { count: days }),
response_status: RESPONSE_FILTER.ALL.QUERY,
},
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -64,12 +64,6 @@ export default {
"homepage": "https://github.com/MasterKia/PersianBlocker",
"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": {
"name": "KOR: List-KR DNS",
"categoryId": "regional",
@@ -166,14 +160,20 @@ export default {
"homepage": "https://github.com/DandelionSprout/adfilt",
"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": {
"name": "Dandelion Sprout's Game Console Adblock List",
"categoryId": "other",
"homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_6.txt"
},
"hagezi_personal": {
"name": "HaGeZi Personal Black \u0026 White",
"hagezi_multinormal": {
"name": "HaGeZi Multi NORMAL",
"categoryId": "general",
"homepage": "https://github.com/hagezi/dns-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_34.txt"

View File

@@ -1,5 +1,5 @@
{
"timeUpdated": "2023-06-26T13:46:24.414Z",
"timeUpdated": "2023-08-01T00:10:42.759Z",
"categories": {
"0": "audio_video_player",
"1": "comments",
@@ -42,7 +42,8 @@
"name": "1822direkt.de",
"categoryId": 8,
"url": "https://www.1822direkt.de/",
"companyId": null
"companyId": "1822direkt",
"source": "AdGuard"
},
"1dmp.io": {
"name": "1DMP",
@@ -69,16 +70,18 @@
"companyId": "dentsu_aegis_network"
},
"1und1": {
"name": "1&1 Internet",
"name": "1&1 IONOS",
"categoryId": 8,
"url": null,
"companyId": null
"url": "http://www.ionos.com/",
"companyId": "1und1",
"source": "AdGuard"
},
"24-ads.com": {
"name": "24-ADS GmbH",
"name": "24-ADS",
"categoryId": 4,
"url": "http://www.24-ads.com/",
"companyId": null
"companyId": "24-ads.com",
"source": "AdGuard"
},
"24_7": {
"name": "[24]7",
@@ -93,10 +96,11 @@
"companyId": "24log"
},
"24smi": {
"name": "24СМИ",
"name": "24SMI",
"categoryId": 8,
"url": "https://24smi.org/",
"companyId": null
"companyId": "24smi",
"source": "AdGuard"
},
"2leep": {
"name": "2leep",
@@ -127,13 +131,15 @@
"name": "4Chan",
"categoryId": 8,
"url": "https://www.4chan.org/",
"companyId": null
"companyId": "4chan",
"source": "AdGuard"
},
"4finance_com": {
"name": "4finance.com",
"name": "4finance",
"categoryId": 2,
"url": "http://4finance.com/",
"companyId": null
"url": "https://4finance.com/",
"companyId": "4finance",
"source": "AdGuard"
},
"4w_marketplace": {
"name": "4w Marketplace",
@@ -179,10 +185,11 @@
"source": "AdGuard"
},
"7tv.de": {
"name": "7tv.de",
"name": "7tv.app",
"categoryId": 0,
"url": "https://www.7tv.de/",
"companyId": null
"url": "https://www.7tv.app/",
"companyId": "7tv",
"source": "AdGuard"
},
"888media": {
"name": "888media",
@@ -2554,7 +2561,7 @@
"name": "Microsoft App Center",
"categoryId": 5,
"url": "https://appcenter.ms/",
"companyId": null,
"companyId": "microsoft",
"source": "AdGuard"
},
"appcues": {
@@ -3348,6 +3355,13 @@
"url": "https://www.microsoft.com/",
"companyId": "microsoft"
},
"binge": {
"name": "Binge",
"categoryId": 0,
"url": "https://binge.com.au/",
"companyId": "foxtel",
"source": "AdGuard"
},
"binlayer": {
"name": "BinLayer",
"categoryId": 4,
@@ -3918,7 +3932,7 @@
"name": "Button",
"categoryId": 4,
"url": "https://www.usebutton.com/",
"companyId": null,
"companyId": "button",
"source": "AdGuard"
},
"buysellads": {
@@ -5269,7 +5283,7 @@
"name": "Crashlytics",
"categoryId": 101,
"url": "https://crashlytics.com/",
"companyId": null,
"companyId": "google",
"source": "AdGuard"
},
"crazy_egg": {
@@ -6420,6 +6434,13 @@
"url": "http://www.amazon.com/",
"companyId": "amazon_associates"
},
"electronic_arts": {
"name": "Electronic Arts",
"categoryId": 2,
"url": "https://www.ea.com/",
"companyId": "electronic_arts",
"source": "AdGuard"
},
"element": {
"name": "Element",
"categoryId": 7,
@@ -7007,6 +7028,13 @@
"url": null,
"companyId": null
},
"farlight_pte_ltd": {
"name": "Farlight Pte Ltd.",
"categoryId": 8,
"url": "https://farlightgames.com/",
"companyId": "farlight",
"source": "AdGuard"
},
"fastly_insights": {
"name": "Fastly Insights",
"categoryId": 6,
@@ -7164,6 +7192,13 @@
"url": "http://flagcounter.com/",
"companyId": "flag_counter"
},
"flash": {
"name": "Flash",
"categoryId": 0,
"url": "https://flashnews.com.au/",
"companyId": "foxtel",
"source": "AdGuard"
},
"flashtalking": {
"name": "Flashtalking",
"categoryId": 4,
@@ -7369,6 +7404,13 @@
"url": "https://publishers.foxaudiencenetwork.com/",
"companyId": "fox_audience_network"
},
"fox_sports": {
"name": "Fox Sports",
"categoryId": 0,
"url": "https://foxsports.com.au/",
"companyId": "foxtel",
"source": "AdGuard"
},
"foxnews_static": {
"name": "Fox News CDN",
"categoryId": 9,
@@ -7381,6 +7423,13 @@
"url": "https://www.foxpush.com/",
"companyId": "foxpush"
},
"foxtel": {
"name": "Foxtel",
"categoryId": 0,
"url": "https://foxtel.com.au/",
"companyId": "foxtel",
"source": "AdGuard"
},
"foxydeal_com": {
"name": "foxydeal.com",
"categoryId": 12,
@@ -7983,12 +8032,40 @@
"url": "http://www.google.com",
"companyId": "google"
},
"google_auth": {
"name": "Google Auth",
"categoryId": 2,
"url": "https://myaccount.google.com/",
"companyId": "google",
"source": "AdGuard"
},
"google_beacons": {
"name": "Google Beacons",
"categoryId": 6,
"url": "https://google.xyz",
"companyId": "google"
},
"google_chat": {
"name": "Google Chat",
"categoryId": 7,
"url": "https://mail.google.com/chat/",
"companyId": "google",
"source": "AdGuard"
},
"google_cloud_platform": {
"name": "Google Cloud Platform",
"categoryId": 10,
"url": "https://cloud.google.com/",
"companyId": "google",
"source": "AdGuard"
},
"google_cloud_storage": {
"name": "Google Cloud Storage",
"categoryId": 10,
"url": "https://cloud.google.com/storage/",
"companyId": "google",
"source": "AdGuard"
},
"google_custom_search": {
"name": "Google Custom Search Ads",
"categoryId": 4,
@@ -8001,6 +8078,27 @@
"url": "https://programmablesearchengine.google.com/about/",
"companyId": "google"
},
"google_dns": {
"name": "Google DNS",
"categoryId": 10,
"url": "hhttps://dns.google/",
"companyId": "google",
"source": "AdGuard"
},
"google_domains": {
"name": "Google Domains",
"categoryId": 10,
"url": "https://domains.google/",
"companyId": "google",
"source": "AdGuard"
},
"google_edge": {
"name": "Google Edge CDN",
"categoryId": 9,
"url": "https://peering.google.com/",
"companyId": "google",
"source": "AdGuard"
},
"google_email": {
"name": "Google Email",
"categoryId": 13,
@@ -8013,12 +8111,47 @@
"url": "https://fonts.google.com/",
"companyId": "google"
},
"google_hosted": {
"name": "Google Hosted",
"categoryId": 10,
"url": "https://workspace.google.com/",
"companyId": "google",
"source": "AdGuard"
},
"google_ima": {
"name": "Google IMA",
"categoryId": 4,
"url": "http://www.google.com",
"companyId": "google"
},
"google_location": {
"name": "Google Location",
"categoryId": 8,
"url": "https://patents.google.com/patent/WO2007025143A1/",
"companyId": "google",
"source": "AdGuard"
},
"google_maps": {
"name": "Google Maps",
"categoryId": 2,
"url": "https://www.google.com/maps/",
"companyId": "google",
"source": "AdGuard"
},
"google_marketing": {
"name": "Google Marketing",
"categoryId": 6,
"url": "https://marketingplatform.google.com/",
"companyId": "google",
"source": "AdGuard"
},
"google_meet": {
"name": "Google Meet",
"categoryId": 2,
"url": "https://meet.google.com/",
"companyId": "google",
"source": "AdGuard"
},
"google_photos": {
"name": "Google Photos",
"categoryId": 9,
@@ -8031,6 +8164,13 @@
"url": "http://www.google.com",
"companyId": "google"
},
"google_play": {
"name": "Google Play",
"categoryId": 8,
"url": "https://play.google.com/",
"companyId": "google",
"source": "AdGuard"
},
"google_plus": {
"name": "Google+ Platform",
"categoryId": 7,
@@ -8110,6 +8250,13 @@
"url": "http://www.google.com",
"companyId": "google"
},
"google_voice": {
"name": "Google Voice",
"categoryId": 2,
"url": "https://voice.google.com/",
"companyId": "google",
"source": "AdGuard"
},
"google_website_optimizer": {
"name": "Google Website Optimizer",
"categoryId": 6,
@@ -8122,6 +8269,13 @@
"url": "http://www.google.com",
"companyId": "google"
},
"google_workspace": {
"name": "Google Workspace",
"categoryId": 2,
"url": "https://workspace.google.com/",
"companyId": "google",
"source": "AdGuard"
},
"googleapis.com": {
"name": "Google APIs",
"categoryId": 9,
@@ -8522,7 +8676,7 @@
"name": "HockeyApp",
"categoryId": 101,
"url": "https://hockeyapp.net/",
"companyId": null,
"companyId": "microsoft",
"source": "AdGuard"
},
"hoholikik.club": {
@@ -9766,6 +9920,13 @@
"url": "http://kavanga.ru/",
"companyId": "kavanga"
},
"kayo_sports": {
"name": "Kayo Sports",
"categoryId": 0,
"url": "https://kayosports.com.au/",
"companyId": "foxtel",
"source": "AdGuard"
},
"keen_io": {
"name": "Keen IO",
"categoryId": 6,
@@ -13093,6 +13254,13 @@
"url": "http://perfectmarket.com/",
"companyId": "perfect_market"
},
"perfops": {
"name": "PerfOps",
"categoryId": 6,
"url": "https://perfops.net/",
"companyId": "perfops",
"source": "AdGuard"
},
"perform_group": {
"name": "Perform Group",
"categoryId": 5,
@@ -13941,10 +14109,11 @@
"companyId": "qihoo_360_technology"
},
"qq.com": {
"name": "qq.com",
"categoryId": 8,
"url": "http://www.qq.com/",
"companyId": "qq.com"
"name": "QQ International",
"categoryId": 2,
"url": "https://www.qq.com/",
"companyId": "tencent",
"source": "AdGuard"
},
"qrius": {
"name": "Qrius",
@@ -16484,6 +16653,13 @@
"url": "http://www.streak.com/",
"companyId": "streak"
},
"streamotion": {
"name": "Streamotion",
"categoryId": 0,
"url": "https://streamotion.com.au/",
"companyId": "foxtel",
"source": "AdGuard"
},
"streamrail.com": {
"name": "StreamRail",
"categoryId": 4,
@@ -16574,6 +16750,13 @@
"url": "http://www.sundaysky.com/",
"companyId": "sundaysky"
},
"supercell": {
"name": "Supercell",
"categoryId": 2,
"url": "https://supercell.com/",
"companyId": "supercell",
"source": "AdGuard"
},
"supercounters": {
"name": "SuperCounters",
"categoryId": 6,
@@ -19163,10 +19346,11 @@
"companyId": "xapads"
},
"xen-media.com": {
"name": "xen-media.com",
"name": "Xen Media",
"categoryId": 11,
"url": null,
"companyId": null
"url": "https://www.xenmedia.net/",
"companyId": "xenmedia",
"source": "AdGuard"
},
"xfreeservice.com": {
"name": "xfreeservice.com",
@@ -19177,8 +19361,9 @@
"xhamster": {
"name": "xHamster",
"categoryId": 3,
"url": null,
"companyId": null
"url": "https://xhamster.com/",
"companyId": "xhamster",
"source": "AdGuard"
},
"xing": {
"name": "Xing",
@@ -19193,10 +19378,11 @@
"companyId": "exoclick"
},
"xnxx_cdn": {
"name": "xnxx CDN",
"name": "XNXX",
"categoryId": 9,
"url": "https://www.xnxx.com",
"companyId": null
"companyId": "xnxx",
"source": "AdGuard"
},
"xplosion": {
"name": "xplosion",
@@ -19211,16 +19397,18 @@
"companyId": "matomy_media"
},
"xvideos_com": {
"name": "xvideos.com",
"name": "Xvideos",
"categoryId": 8,
"url": null,
"companyId": null
"url": "https://www.xvideos.com",
"companyId": "xvideos",
"source": "AdGuard"
},
"xxxlshop.de": {
"name": "xxxlshop.de",
"name": "XXXLutz",
"categoryId": 8,
"url": "https://www.xxxlshop.de/",
"companyId": null
"url": "https://www.xxxlutz.de/",
"companyId": "xxxlutz",
"source": "AdGuard"
},
"xxxlutz": {
"name": "XXXLutz",
@@ -19232,7 +19420,8 @@
"name": "Yabbi",
"categoryId": 4,
"url": "https://yabbi.me/",
"companyId": null
"companyId": "yabbi",
"source": "AdGuard"
},
"yabuka": {
"name": "Yabuka",
@@ -19494,10 +19683,11 @@
"companyId": "yomedia"
},
"yoochoose.net": {
"name": "YOOCHOOSE",
"name": "Ibexa Personalizaton Software",
"categoryId": 4,
"url": "https://yoochoose.com/",
"companyId": null
"url": "https://yoochoose.net/",
"companyId": "ibexa",
"source": "AdGuard"
},
"yotpo": {
"name": "Yotpo",
@@ -19532,8 +19722,9 @@
"youporn": {
"name": "YouPorn",
"categoryId": 3,
"url": null,
"companyId": null
"url": "https://www.youporn.com/",
"companyId": "youporn",
"source": "AdGuard"
},
"youtube": {
"name": "YouTube",
@@ -19671,7 +19862,8 @@
"name": "ZeusClicks",
"categoryId": 4,
"url": "http://zeusclicks.com/",
"companyId": null
"companyId": "zeusclicks",
"source": "AdGuard"
},
"ziff_davis": {
"name": "Ziff Davis",
@@ -19689,7 +19881,8 @@
"name": "Zimbio",
"categoryId": 8,
"url": "http://www.zimbio.com/",
"companyId": null
"companyId": "livinglymedia",
"source": "AdGuard"
},
"zippyshare_widget": {
"name": "Zippyshare Widget",
@@ -20354,6 +20547,7 @@
"amazon.com.au": "amazon",
"amazon-corp.com": "amazon",
"a2z.com": "amazon",
"firetvcaptiveportal.com": "amazon",
"amazon-adsystem.com": "amazon_adsystem",
"serving-sys.com": "amazon_adsystem",
"sizmek.com": "amazon_adsystem",
@@ -20598,6 +20792,7 @@
"bing.com": "bing_ads",
"bing.net": "bing_ads",
"virtualearth.net": "bing_maps",
"binge.com.au": "binge",
"view.binlayer.com": "binlayer",
"widgets.binotel.com": "binotel",
"esendra.fi": "bisnode",
@@ -21212,6 +21407,7 @@
"2mdn.net": "doubleclick",
"doubleclick.net": "doubleclick",
"invitemedia.com": "doubleclick",
"doubleclick.com": "doubleclick",
"doublepimp.com": "doublepimp",
"doublepimpssl.com": "doublepimp",
"redcourtside.com": "doublepimp",
@@ -21292,6 +21488,9 @@
"ekomi.de": "ekomi",
"elasticad.net": "elastic_ad",
"elasticbeanstalk.com": "elastic_beanstalk",
"cloudcell.com": "electronic_arts",
"ea.com": "electronic_arts",
"eamobile.com": "electronic_arts",
"element.io": "element",
"riot.im": "element",
"elicitapp.com": "elicit",
@@ -21412,6 +21611,7 @@
"thefancy.com": "fancy_widget",
"d1q7pknmpq2wkm.cloudfront.net": "fanplayr",
"fap.to": "fap.to",
"farlightgames.com": "farlight_pte_ltd",
"fastly-insights.com": "fastly_insights",
"fastly.net": "fastlylb.net",
"fastlylb.net": "fastlylb.net",
@@ -21435,12 +21635,28 @@
"findizer.fr": "findizer.fr",
"findologic.com": "findologic.com",
"app-measurement.com": "firebase",
"fcm.googleapis.com": "firebase",
"firebaseappcheck.googleapis.com": "firebase",
"firebaseapp.com": "firebase",
"firebase.com": "firebase",
"firebasedynamiclinks.googleapis.com": "firebase",
"firebasedynamiclinks-ipv4.googleapis.com": "firebase",
"firebasedynamiclinks-ipv6.googleapis.com": "firebase",
"firebase.googleapis.com": "firebase",
"firebase.google.com": "firebase",
"firebaseinappmessaging.googleapis.com": "firebase",
"firebaseinstallations.googleapis.com": "firebase",
"firebaselogging.googleapis.com": "firebase",
"firebaselogging-pa.googleapis.com": "firebase",
"firebaseperusertopics-pa.googleapis.com": "firebase",
"firebaseremoteconfig.googleapis.com": "firebase",
"firebaseio.com": "firebaseio.com",
"firstimpression.io": "first_impression",
"fitanalytics.com": "fit_analytics",
"fivetran.com": "fivetran",
"flagads.net": "flag_ads",
"flagcounter.com": "flag_counter",
"flashnews.com.au": "flash",
"flashtalking.com": "flashtalking",
"flattr.com": "flattr_button",
"flexlinks.com": "flexoffers",
@@ -21486,9 +21702,11 @@
"platform.foursquare.com": "foursquare_widget",
"fout.jp": "fout.jp",
"fimserve.com": "fox_audience_network",
"foxsports.com.au": "fox_sports",
"fncstatic.com": "foxnews_static",
"cdn.foxpush.net": "foxpush",
"foxpush.com": "foxpush",
"foxtel.com.au": "foxtel",
"foxydeal.com": "foxydeal_com",
"yabidos.com": "fraudlogix",
"besucherstatistiken.com": "free_counter",
@@ -21649,15 +21867,287 @@
"google.ru": "google",
"google.se": "google",
"google.tn": "google",
"1e100.net": "google",
"agnss.goog": "google",
"channel.status.request.url": "google",
"g.cn": "google",
"g.co": "google",
"google.ad": "google",
"google.ae": "google",
"google.al": "google",
"google.am": "google",
"googleapis.cn": "google",
"google.as": "google",
"google.az": "google",
"google.ba": "google",
"google.bf": "google",
"google.bg": "google",
"google.bi": "google",
"google.bj": "google",
"google.bs": "google",
"google.bt": "google",
"google.by": "google",
"google.cat": "google",
"google.cd": "google",
"google.cf": "google",
"google.cg": "google",
"google.ci": "google",
"google.cl": "google",
"google.cm": "google",
"google.cn": "google",
"google.co.ao": "google",
"google.co.bw": "google",
"google.co.ck": "google",
"google.co.cr": "google",
"googlecode.com": "google",
"google.co.il": "google",
"google.co.ke": "google",
"google.co.kr": "google",
"google.co.ls": "google",
"google.com.af": "google",
"google.com.ag": "google",
"google.com.ai": "google",
"google.com.bd": "google",
"google.com.bh": "google",
"google.com.bn": "google",
"google.com.bo": "google",
"google.com.bz": "google",
"google.com.co": "google",
"google.com.cu": "google",
"google.com.cy": "google",
"google.com.ec": "google",
"google.com.eg": "google",
"google.com.et": "google",
"google.com.fj": "google",
"google.com.gh": "google",
"google.com.gi": "google",
"google.com.gt": "google",
"google.com.hk": "google",
"google.com.jm": "google",
"google.com.kh": "google",
"google.com.kw": "google",
"google.com.lb": "google",
"google.com.my": "google",
"google.com.na": "google",
"google.com.nf": "google",
"google.com.ng": "google",
"google.com.ni": "google",
"google.com.np": "google",
"google.com.om": "google",
"google.com.pa": "google",
"google.com.pe": "google",
"google.com.pg": "google",
"google.com.ph": "google",
"google.com.pk": "google",
"google.com.pr": "google",
"google.com.py": "google",
"google.com.qa": "google",
"google.com.sa": "google",
"google.com.sb": "google",
"google.com.sg": "google",
"google.com.sl": "google",
"google.com.sv": "google",
"google.com.tj": "google",
"google.com.uy": "google",
"google.com.vc": "google",
"google.com.vn": "google",
"google.co.mz": "google",
"google.co.nz": "google",
"google.co.tz": "google",
"google.co.ug": "google",
"google.co.uz": "google",
"google.co.ve": "google",
"google.co.vi": "google",
"google.co.za": "google",
"google.co.zm": "google",
"google.co.zw": "google",
"google.cv": "google",
"google.dj": "google",
"google.dm": "google",
"googledownloads.cn": "google",
"google.ee": "google",
"google.fm": "google",
"google.ga": "google",
"google.ge": "google",
"google.gg": "google",
"google.gl": "google",
"google.gm": "google",
"google.gp": "google",
"google.gy": "google",
"google.hn": "google",
"google.hr": "google",
"google.ht": "google",
"google.im": "google",
"google.in": "google",
"google.iq": "google",
"google.is": "google",
"google.je": "google",
"google.jo": "google",
"google.kg": "google",
"google.ki": "google",
"google.kz": "google",
"google.la": "google",
"google.li": "google",
"google.lk": "google",
"google.lt": "google",
"google.lu": "google",
"google.lv": "google",
"google.md": "google",
"google.me": "google",
"google.mg": "google",
"google.mk": "google",
"google.ml": "google",
"google.mn": "google",
"google.ms": "google",
"google.mu": "google",
"google.mv": "google",
"google.mw": "google",
"google.ne": "google",
"google.net": "google",
"google.nr": "google",
"google.nu": "google",
"googleoptimize.com": "google",
"google.org": "google",
"google.pn": "google",
"google.ps": "google",
"google.rw": "google",
"google.sc": "google",
"google.sh": "google",
"google.si": "google",
"google.sk": "google",
"google.sm": "google",
"google.sn": "google",
"google.so": "google",
"google.sr": "google",
"google.st": "google",
"google.td": "google",
"google.tg": "google",
"google.tk": "google",
"google.tl": "google",
"google.tm": "google",
"google.to": "google",
"google.tt": "google",
"google.us": "google",
"google.vg": "google",
"google.vu": "google",
"googleweblight.in": "google",
"google.ws": "google",
"googlezip.net": "google",
"gstatic.cn": "google",
"news.google.com": "google",
"oo.gl": "google",
"withgoogle.com": "google",
"googleadservices.com": "google_adservices",
"google-analytics.com": "google_analytics",
"ssl-google-analytics.l.google.com": "google_analytics",
"www-googletagmanager.l.google.com": "google_analytics",
"appspot.com": "google_appspot",
"googlehosted.com": "google_appspot",
"accounts.google.com": "google_auth",
"myaccount.google.com": "google_auth",
"oauth2.googleapis.com": "google_auth",
"ogs.google.com": "google_auth",
"securetoken.googleapis.com": "google_auth",
"beacons-google.com": "google_beacons",
"alt1-mtalk.google.com": "google_chat",
"alt2-mtalk.google.com": "google_chat",
"alt3-mtalk.google.com": "google_chat",
"alt4-mtalk.google.com": "google_chat",
"alt5-mtalk.google.com": "google_chat",
"alt6-mtalk.google.com": "google_chat",
"alt7-mtalk.google.com": "google_chat",
"alt8-mtalk.google.com": "google_chat",
"chat.google.com": "google_chat",
"mobile-gtalk4.l.google.com": "google_chat",
"mobile-gtalk.l.google.com": "google_chat",
"mtalk4.google.com": "google_chat",
"mtalk.google.com": "google_chat",
"talk.google.com": "google_chat",
"talk.l.google.com": "google_chat",
"talkx.l.google.com": "google_chat",
"cloud.google.com": "google_cloud_platform",
"gcp.gvt2.com": "google_cloud_platform",
"storage.googleapis.com": "google_cloud_storage",
"adsensecustomsearchads.com": "google_custom_search",
"dns.google": "google_dns",
"dns.google.com": "google_dns",
"google-public-dns-a.google.com": "google_dns",
"google-public-dns-b.google.com": "google_dns",
"domains.google": "google_domains",
"googledomains.com": "google_domains",
"nic.google": "google_domains",
"registry.google": "google_domains",
"edge.google.com": "google_edge",
"mail-ads.google.com": "google_email",
"fonts.googleapis.com": "google_fonts",
"cloudfunctions.net": "google_hosted",
"ghs46.googlehosted.com": "google_hosted",
"ghs4.googlehosted.com": "google_hosted",
"ghs6.googlehosted.com": "google_hosted",
"ghs.googlehosted.com": "google_hosted",
"googlehosted.l.googleusercontent.com": "google_hosted",
"run.app": "google_hosted",
"supl.google.com": "google_location",
"earth.app.goo.gl": "google_maps",
"geo0.ggpht.com": "google_maps",
"geo1.ggpht.com": "google_maps",
"geo2.ggpht.com": "google_maps",
"geo3.ggpht.com": "google_maps",
"kh.google.com": "google_maps",
"maps.app.goo.gl": "google_maps",
"maps.google.ca": "google_maps",
"maps.google.ch": "google_maps",
"maps.google.co.jp": "google_maps",
"maps.google.com": "google_maps",
"maps.google.com.mx": "google_maps",
"maps.google.co.uk": "google_maps",
"maps.google.es": "google_maps",
"maps.google.se": "google_maps",
"maps.gstatic.com": "google_maps",
"adsense.google.com": "google_marketing",
"adservice.google.ca": "google_marketing",
"adservice.google.co.in": "google_marketing",
"adservice.google.co.kr": "google_marketing",
"adservice.google.com": "google_marketing",
"adservice.google.com.ar": "google_marketing",
"adservice.google.com.au": "google_marketing",
"adservice.google.com.br": "google_marketing",
"adservice.google.com.co": "google_marketing",
"adservice.google.com.gt": "google_marketing",
"adservice.google.com.mx": "google_marketing",
"adservice.google.com.pe": "google_marketing",
"adservice.google.com.ph": "google_marketing",
"adservice.google.com.pk": "google_marketing",
"adservice.google.com.tr": "google_marketing",
"adservice.google.com.tw": "google_marketing",
"adservice.google.com.vn": "google_marketing",
"adservice.google.co.uk": "google_marketing",
"adservice.google.co.za": "google_marketing",
"adservice.google.de": "google_marketing",
"adservice.google.dk": "google_marketing",
"adservice.google.es": "google_marketing",
"adservice.google.fr": "google_marketing",
"adservice.google.nl": "google_marketing",
"adservice.google.no": "google_marketing",
"adservice.google.pl": "google_marketing",
"adservice.google.ru": "google_marketing",
"adservice.google.vg": "google_marketing",
"dai.google.com": "google_marketing",
"doubleclickbygoogle.com": "google_marketing",
"googlesyndication-cn.com": "google_marketing",
"duo.google.com": "google_meet",
"hangouts.clients6.google.com": "google_meet",
"hangouts.googleapis.com": "google_meet",
"hangouts.google.com": "google_meet",
"meet.google.com": "google_meet",
"meetings.googleapis.com": "google_meet",
"stun1.l.google.com": "google_meet",
"stun.l.google.com": "google_meet",
"ggpht.com": "google_photos",
"play-fe.googleapis.com": "google_play",
"play.googleapis.com": "google_play",
"play.google.com": "google_play",
"play-lh.googleusercontent.com": "google_play",
"1e100cdn.net": "google_servers",
"gvt1.com": "google_servers",
"gvt2.com": "google_servers",
@@ -21670,7 +22160,22 @@
"pki.goog": "google_trust_services",
"googlecommerce.com": "google_trusted_stores",
"googleusercontent.com": "google_users",
"telephony.goog": "google_voice",
"voice.google.com": "google_voice",
"gmodules.com": "google_widgets",
"calendar.google.com": "google_workspace",
"contacts.google.com": "google_workspace",
"currents.google.com": "google_workspace",
"docs.google.com": "google_workspace",
"drive.google.com": "google_workspace",
"forms.google.com": "google_workspace",
"gsuite.google.com": "google_workspace",
"jamboard.google.com": "google_workspace",
"keep.google.com": "google_workspace",
"plus.google.com": "google_workspace",
"sheets.google.com": "google_workspace",
"slides.google.com": "google_workspace",
"spreadsheets.google.com": "google_workspace",
"googleapis.com": "googleapis.com",
"gooal.herokuapp.com": "goooal",
"gooo.al": "goooal",
@@ -22037,6 +22542,7 @@
"cen.katchup.fr": "katchup",
"kau.li": "kauli",
"kavanga.ru": "kavanga",
"kayosports.com.au": "kayo_sports",
"dc8na2hxrj29i.cloudfront.net": "keen_io",
"keen.io": "keen_io",
"widget.kelkoo.com": "kelkoo",
@@ -22535,6 +23041,7 @@
"mrskincash.com": "mrskincash",
"e-msedge.net": "msedge",
"l-msedge.net": "msedge",
"s-msedge.net": "msedge",
"msn.com": "msn",
"s-msn.com": "msn",
"musculahq.appspot.com": "muscula",
@@ -22847,6 +23354,7 @@
"perfectaudience.com": "perfect_audience",
"prfct.co": "perfect_audience",
"perfectmarket.com": "perfect_market",
"perfops.io": "perfops",
"performgroup.com": "perform_group",
"analytics.performable.com": "performable",
"performancing.com": "performancing_metrics",
@@ -23652,6 +24160,7 @@
"bizsolutions.strands.com": "strands_recommender",
"strava.com": "strava",
"mailfoogae.appspot.com": "streak",
"streamotion.com.au": "streamotion",
"streamrail.com": "streamrail.com",
"streamrail.net": "streamrail.com",
"stridespark.com": "stride",
@@ -23679,6 +24188,8 @@
"sumo.com": "sumome",
"sumome.com": "sumome",
"sundaysky.com": "sundaysky",
"supercell.com": "supercell",
"supercellsupport.com": "supercell",
"supercounters.com": "supercounters",
"superfastcdn.com": "superfastcdn.com",
"socdm.com": "supership",

View File

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

View File

@@ -1,6 +1,6 @@
# A docker file for scripts/make/build-docker.sh.
FROM alpine:3.17
FROM alpine:3.18
ARG BUILD_DATE
ARG VERSION
@@ -25,8 +25,6 @@ RUN apk --no-cache add ca-certificates libcap tzdata && \
mkdir -p /opt/adguardhome/conf /opt/adguardhome/work && \
chown -R nobody: /opt/adguardhome
RUN apk --no-cache add tini
ARG DIST_DIR
ARG TARGETARCH
ARG TARGETOS
@@ -43,43 +41,18 @@ RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
# 68 : UDP : DHCP (client)
# 80 : TCP : HTTP (main)
# 443 : TCP, UDP : HTTPS, DNS-over-HTTPS (incl. HTTP/3), DNSCrypt (main)
# 784 : UDP : DNS-over-QUIC (experimental)
# 853 : TCP, UDP : DNS-over-TLS, DNS-over-QUIC
# 3000 : TCP, UDP : HTTP(S) (alt, incl. HTTP/3)
# 3001 : TCP, UDP : HTTP(S) (beta, incl. HTTP/3)
# 5443 : TCP, UDP : DNSCrypt (alt)
# 6060 : TCP : HTTP (pprof)
# 8853 : UDP : DNS-over-QUIC (experimental)
#
# 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
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
WORKDIR /opt/adguardhome/work
# Install helpers for healthcheck.
COPY --chown=nobody:nogroup\
./${DIST_DIR}/docker/scripts\
/opt/adguardhome/scripts
HEALTHCHECK \
--interval=30s \
--timeout=10s \
--retries=3 \
CMD [ "/opt/adguardhome/scripts/healthcheck.sh" ]
# It seems that the healthckech script sometimes spawns zombie processes, so we
# need a way to handle them, since AdGuard Home doesn't know how to keep track
# of the processes delegated to it by the OS. Use tini as entry point because
# it needs the PID=1 to be the default parent for orphaned processes.
#
# See https://github.com/adguardTeam/adGuardHome/issues/3290.
ENTRYPOINT [ "/sbin/tini", "--" ]
ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
CMD [ \
"/opt/adguardhome/AdGuardHome", \
"--no-check-update", \
"-c", "/opt/adguardhome/conf/AdGuardHome.yaml", \
"-w", "/opt/adguardhome/work" \

View File

@@ -1,29 +0,0 @@
/^[^[:space:]]/ { is_dns = /^dns:/ }
/^[[:space:]]+bind_hosts:/ { if (is_dns) prev_line = FNR }
/^[[:space:]]+- .+/ {
if (FNR - prev_line == 1) {
addrs[$2] = true
prev_line = FNR
if ($2 == "0.0.0.0" || $2 == "'::'") {
# Drop all the other addresses.
delete addrs
addrs[""] = true
prev_line = -1
}
}
}
/^[[:space:]]+port:/ { if (is_dns) port = $2 }
END {
for (addr in addrs) {
if (match(addr, ":")) {
print "[" addr "]:" port
} else {
print addr ":" port
}
}
}

View File

@@ -1,107 +0,0 @@
#!/bin/sh
# AdGuard Home Docker healthcheck script
# Exit the script if a pipeline fails (-e), prevent accidental filename
# expansion (-f), and consider undefined variables as errors (-u).
set -e -f -u
# Function error_exit is an echo wrapper that writes to stderr and stops the
# script execution with code 1.
error_exit() {
echo "$1" 1>&2
exit 1
}
agh_dir="/opt/adguardhome"
readonly agh_dir
filename="${agh_dir}/conf/AdGuardHome.yaml"
readonly filename
if ! [ -f "$filename" ]
then
wget "http://127.0.0.1:3000" -O /dev/null -q || exit 1
exit 0
fi
help_dir="${agh_dir}/scripts"
readonly help_dir
# Parse web host
web_url="$( awk -f "${help_dir}/web-bind.awk" "$filename" )"
readonly web_url
if [ "$web_url" = '' ]
then
error_exit "no web bindings could be retrieved from $filename"
fi
# TODO(e.burkov): Deal with 0 port.
case "$web_url"
in
(*':0')
error_exit '0 in web port is not supported by healthcheck'
;;
(*)
# Go on.
;;
esac
# Parse DNS hosts
dns_hosts="$( awk -f "${help_dir}/dns-bind.awk" "$filename" )"
readonly dns_hosts
if [ "$dns_hosts" = '' ]
then
error_exit "no DNS bindings could be retrieved from $filename"
fi
first_dns="$( echo "$dns_hosts" | head -n 1 )"
readonly first_dns
# TODO(e.burkov): Deal with 0 port.
case "$first_dns"
in
(*':0')
error_exit '0 in DNS port is not supported by healthcheck'
;;
(*)
# Go on.
;;
esac
# Check
# Skip SSL certificate validation since there is no guarantee the container
# trusts the one used. It should be safe to drop the SSL validation since the
# current script intended to be used from inside the container and only checks
# the endpoint availability, ignoring the content of the response.
#
# See https://github.com/AdguardTeam/AdGuardHome/issues/5642.
wget --no-check-certificate "$web_url" -O /dev/null -q || exit 1
test_fqdn="healthcheck.adguardhome.test."
readonly test_fqdn
# The awk script currently returns only port prefixed with colon in case of
# unspecified address.
case "$first_dns"
in
(':'*)
nslookup -type=a "$test_fqdn" "127.0.0.1${first_dns}" > /dev/null ||\
nslookup -type=a "$test_fqdn" "[::1]${first_dns}" > /dev/null ||\
error_exit "nslookup failed for $host"
;;
(*)
echo "$dns_hosts" | while read -r host
do
nslookup -type=a "$test_fqdn" "$host" > /dev/null ||\
error_exit "nslookup failed for $host"
done
;;
esac

View File

@@ -1,13 +0,0 @@
# Don't consider the HTTPS hostname since the enforced HTTPS redirection should
# work if the SSL check skipped. See file docker/healthcheck.sh.
/^bind_host:/ { host = $2 }
/^bind_port:/ { port = $2 }
END {
if (match(host, ":")) {
print "http://[" host "]:" port
} else {
print "http://" host ":" port
}
}

34
go.mod
View File

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

56
go.sum
View File

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

View File

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

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
import (
"fmt"
"net/netip"
"strings"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/stringutil"
)
// 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()
if ip.Is4() {
return strings.Replace(hostname, ".", "-", -1)
return strings.ReplaceAll(hostname, ".", "-")
}
return strings.Replace(hostname, ":", "-", -1)
}
// 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
return strings.ReplaceAll(hostname, ":", "-")
}

View File

@@ -56,15 +56,20 @@ func (rm *requestMatcher) MatchRequest(
) (res *urlfilter.DNSResult, ok bool) {
switch req.DNSType {
case dns.TypeA, dns.TypeAAAA, dns.TypePTR:
log.Debug("%s: handling the request for %s", hostsContainerPrefix, req.Hostname)
log.Debug(
"%s: handling %s request for %s",
hostsContainerPrefix,
dns.Type(req.DNSType),
req.Hostname,
)
rm.stateLock.RLock()
defer rm.stateLock.RUnlock()
return rm.engine.MatchRequest(req)
default:
return nil, false
}
rm.stateLock.RLock()
defer rm.stateLock.RUnlock()
return rm.engine.MatchRequest(req)
}
// Translate returns the source hosts-syntax rule for the generated dnsrewrite
@@ -96,6 +101,8 @@ const hostsContainerPrefix = "hosts container"
// HostsContainer stores the relevant hosts database provided by the OS and
// processes both A/AAAA and PTR DNS requests for those.
//
// TODO(e.burkov): Improve API and move to golibs.
type HostsContainer struct {
// requestMatcher matches the requests and translates the rules. It's
// embedded to implement MatchRequest and Translate for *HostsContainer.
@@ -134,9 +141,9 @@ type HostsRecord struct {
Canonical string
}
// equal returns true if all fields of rec are equal to field in other or they
// Equal returns true if all fields of rec are equal to field in other or they
// both are nil.
func (rec *HostsRecord) equal(other *HostsRecord) (ok bool) {
func (rec *HostsRecord) Equal(other *HostsRecord) (ok bool) {
if rec == nil {
return other == nil
} else if other == nil {
@@ -488,7 +495,7 @@ func (hc *HostsContainer) refresh() (err error) {
}
// hc.last is nil on the first refresh, so let that one through.
if hc.last != nil && maps.EqualFunc(hp.table, hc.last, (*HostsRecord).equal) {
if hc.last != nil && maps.EqualFunc(hp.table, hc.last, (*HostsRecord).Equal) {
log.Debug("%s: no changes detected", hostsContainerPrefix)
return nil

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,11 +2,15 @@ package aghtest
import (
"context"
"io/fs"
"net"
"net/netip"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"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/miekg/dns"
)
@@ -15,94 +19,20 @@ import (
//
// Keep entities in this file in alphabetic order.
// Standard Library
// Package fs
// type check
var _ fs.FS = &FS{}
// FS is a mock [fs.FS] implementation for tests.
type FS struct {
OnOpen func(name string) (fs.File, error)
}
// 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{}
// GlobFS is a mock [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{}
// StatFS is a mock [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 net
// type check
var _ net.Listener = (*Listener)(nil)
// Listener is a mock [net.Listener] implementation for tests.
type Listener struct {
OnAccept func() (conn net.Conn, err error)
OnAddr func() (addr net.Addr)
OnClose func() (err error)
}
// Accept implements the [net.Listener] interface for *Listener.
func (l *Listener) Accept() (conn net.Conn, err error) {
return l.OnAccept()
}
// Addr implements the [net.Listener] interface for *Listener.
func (l *Listener) Addr() (addr net.Addr) {
return l.OnAddr()
}
// Close implements the [net.Listener] interface for *Listener.
func (l *Listener) Close() (err error) {
return l.OnClose()
}
// Module adguard-home
// Package aghos
// type check
var _ aghos.FSWatcher = (*FSWatcher)(nil)
// FSWatcher is a mock [aghos.FSWatcher] implementation for tests.
// FSWatcher is a fake [aghos.FSWatcher] implementation for tests.
type FSWatcher struct {
OnEvents func() (e <-chan struct{})
OnAdd func(name string) (err error)
OnClose func() (err error)
}
// type check
var _ aghos.FSWatcher = (*FSWatcher)(nil)
// Events implements the [aghos.FSWatcher] interface for *FSWatcher.
func (w *FSWatcher) Events() (e <-chan struct{}) {
return w.OnEvents()
@@ -120,16 +50,16 @@ func (w *FSWatcher) Close() (err error) {
// Package agh
// type check
var _ agh.ServiceWithConfig[struct{}] = (*ServiceWithConfig[struct{}])(nil)
// ServiceWithConfig is a mock [agh.ServiceWithConfig] implementation for tests.
// ServiceWithConfig is a fake [agh.ServiceWithConfig] implementation for tests.
type ServiceWithConfig[ConfigType any] struct {
OnStart func() (err error)
OnShutdown func(ctx context.Context) (err error)
OnConfig func() (c ConfigType)
}
// type check
var _ agh.ServiceWithConfig[struct{}] = (*ServiceWithConfig[struct{}])(nil)
// Start implements the [agh.ServiceWithConfig] interface for
// *ServiceWithConfig.
func (s *ServiceWithConfig[_]) Start() (err error) {
@@ -148,14 +78,76 @@ func (s *ServiceWithConfig[ConfigType]) Config() (c ConfigType) {
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
// Package upstream
// type check
var _ upstream.Upstream = (*UpstreamMock)(nil)
// UpstreamMock is a mock [upstream.Upstream] implementation for tests.
// UpstreamMock is a fake [upstream.Upstream] implementation for tests.
//
// TODO(a.garipov): Replace with all uses of Upstream with UpstreamMock and
// rename it to just Upstream.
@@ -165,6 +157,9 @@ type UpstreamMock struct {
OnClose func() (err error)
}
// type check
var _ upstream.Upstream = (*UpstreamMock)(nil)
// Address implements the [upstream.Upstream] interface for *UpstreamMock.
func (u *UpstreamMock) Address() (addr string) {
return u.OnAddress()

View File

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

View File

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

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/log"
"github.com/google/renameio/maybe"
"github.com/google/renameio/v2/maybe"
"golang.org/x/exp/slices"
)

View File

@@ -51,6 +51,9 @@ func migrateDB(conf *ServerConfig) (err error) {
oldLeasesPath := filepath.Join(conf.WorkDir, dbFilename)
dataDirPath := filepath.Join(conf.DataDir, dataFilename)
// #nosec G304 -- Trust this path, since it's taken from the old file name
// relative to the working directory and should generally be considered
// safe.
file, err := os.Open(oldLeasesPath)
if errors.Is(err, os.ErrNotExist) {
// Nothing to migrate.

View File

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

View File

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

View File

@@ -13,9 +13,9 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
@@ -271,7 +271,13 @@ type ServerConfig struct {
UDPListenAddrs []*net.UDPAddr // UDP listen address
TCPListenAddrs []*net.TCPAddr // TCP listen address
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
TLSConfig
@@ -299,9 +305,6 @@ type ServerConfig struct {
// DNS64Prefixes is a slice of NAT64 prefixes to be used for DNS64.
DNS64Prefixes []netip.Prefix
// ResolveClients signals if the RDNS should resolve clients' addresses.
ResolveClients bool
// UsePrivateRDNS defines if the PTR requests for unknown addresses from
// locally-served networks should be resolved via private PTR resolvers.
UsePrivateRDNS bool
@@ -341,6 +344,7 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
UpstreamConfig: srvConf.UpstreamConfig,
BeforeRequestHandler: s.beforeRequestHandler,
RequestHandler: s.handleDNSRequest,
HTTPSServerName: aghhttp.UserAgent(),
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
MaxGoroutines: int(srvConf.MaxGoroutines),
UseDNS64: srvConf.UseDNS64,
@@ -436,102 +440,6 @@ func (s *Server) initDefaultSettings() {
}
}
// UpstreamHTTPVersions returns the HTTP versions for upstream configuration
// depending on configuration.
func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) {
if !http3 {
return upstream.DefaultHTTPVersions
}
return []upstream.HTTPVersion{
upstream.HTTPVersion3,
upstream.HTTPVersion2,
upstream.HTTPVersion11,
}
}
// prepareUpstreamSettings - prepares upstream DNS server settings
func (s *Server) prepareUpstreamSettings() 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
var upstreams []string
if s.conf.UpstreamDNSFileName != "" {
data, err := os.ReadFile(s.conf.UpstreamDNSFileName)
if err != nil {
return fmt.Errorf("reading upstream from file: %w", err)
}
upstreams = stringutil.SplitTrimmed(string(data), "\n")
log.Debug("dns: using %d upstream servers from file %s", len(upstreams), s.conf.UpstreamDNSFileName)
} else {
upstreams = s.conf.UpstreamDNS
}
httpVersions := UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams)
upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty)
upstreamConfig, err := proxy.ParseUpstreamsConfig(
upstreams,
&upstream.Options{
Bootstrap: s.conf.BootstrapDNS,
Timeout: s.conf.UpstreamTimeout,
HTTPVersions: httpVersions,
PreferIPv6: s.conf.BootstrapPreferIPv6,
},
)
if err != nil {
return fmt.Errorf("parsing upstream config: %w", err)
}
if len(upstreamConfig.Upstreams) == 0 {
log.Info("warning: no default upstream servers specified, using %v", defaultDNS)
var uc *proxy.UpstreamConfig
uc, err = proxy.ParseUpstreamsConfig(
defaultDNS,
&upstream.Options{
Bootstrap: s.conf.BootstrapDNS,
Timeout: s.conf.UpstreamTimeout,
HTTPVersions: httpVersions,
PreferIPv6: s.conf.BootstrapPreferIPv6,
},
)
if err != nil {
return fmt.Errorf("parsing default upstreams: %w", err)
}
upstreamConfig.Upstreams = uc.Upstreams
}
s.conf.UpstreamConfig = upstreamConfig
return nil
}
// setProxyUpstreamMode sets the upstream mode and related settings in conf
// based on provided parameters.
func setProxyUpstreamMode(
conf *proxy.Config,
allServers bool,
fastestAddr bool,
fastestTimeout time.Duration,
) {
if allServers {
conf.UpstreamMode = proxy.UModeParallel
} else if fastestAddr {
conf.UpstreamMode = proxy.UModeFastestAddr
conf.FastestPingTimeout = fastestTimeout
} else {
conf.UpstreamMode = proxy.UModeLoadBalance
}
}
// prepareIpsetListSettings reads and prepares the ipset configuration either
// from a file or from the data in the configuration file.
func (s *Server) prepareIpsetListSettings() (err error) {
@@ -540,6 +448,7 @@ func (s *Server) prepareIpsetListSettings() (err error) {
return s.ipset.init(s.conf.IpsetList)
}
// #nosec G304 -- Trust the path explicitly given by the user.
data, err := os.ReadFile(fn)
if err != nil {
return err

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,9 +14,11 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
"github.com/AdguardTeam/AdGuardHome/internal/stats"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
@@ -98,8 +100,17 @@ type Server struct {
// must be a valid domain name plus dots on each side.
localDomainSuffix string
ipset ipsetCtx
privateNets netutil.SubnetSet
ipset ipsetCtx
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
sysResolvers aghnet.SystemResolvers
@@ -169,6 +180,9 @@ const (
// NewServer creates a new instance of the dnsforward.Server
// 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) {
var localDomainSuffix string
if p.LocalDomain == "" {
@@ -230,7 +244,7 @@ func (s *Server) Close() {
s.serverLock.Lock()
defer s.serverLock.Unlock()
s.dnsFilter = nil
// TODO(s.chzhen): Remove it.
s.stats = nil
s.queryLog = nil
s.dnsProxy = nil
@@ -256,14 +270,25 @@ func (s *Server) WriteDiskConfig(c *FilteringConfig) {
c.UpstreamDNS = stringutil.CloneSlice(sc.UpstreamDNS)
}
// RDNSSettings returns the copy of actual RDNS configuration.
func (s *Server) RDNSSettings() (localPTRResolvers []string, resolveClients, resolvePTR bool) {
// LocalPTRResolvers returns the current local PTR resolver configuration.
func (s *Server) LocalPTRResolvers() (localPTRResolvers []string) {
s.serverLock.RLock()
defer s.serverLock.RUnlock()
return stringutil.CloneSlice(s.conf.LocalPTRResolvers),
s.conf.ResolveClients,
s.conf.UsePrivateRDNS
return stringutil.CloneSlice(s.conf.LocalPTRResolvers)
}
// 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.
@@ -277,17 +302,6 @@ func (s *Server) Resolve(host string) ([]net.IPAddr, error) {
return s.internalProxy.LookupIPAddr(host)
}
// RDNSExchanger is a resolver for clients' addresses.
type RDNSExchanger interface {
// Exchange tries to resolve the ip in a suitable way, i.e. either as local
// or as external.
Exchange(ip net.IP) (host string, err error)
// ResolvesPrivatePTR returns true if the RDNSExchanger is able to
// resolve PTR requests for locally-served addresses.
ResolvesPrivatePTR() (ok bool)
}
const (
// ErrRDNSNoData is returned by [RDNSExchanger.Exchange] when the answer
// section of response is either NODATA or has no PTR records.
@@ -299,20 +313,16 @@ const (
)
// type check
var _ RDNSExchanger = (*Server)(nil)
var _ rdns.Exchanger = (*Server)(nil)
// Exchange implements the RDNSExchanger interface for *Server.
func (s *Server) Exchange(ip net.IP) (host string, err error) {
// Exchange implements the [rdns.Exchanger] interface for *Server.
func (s *Server) Exchange(ip netip.Addr) (host string, ttl time.Duration, err error) {
s.serverLock.RLock()
defer s.serverLock.RUnlock()
if !s.conf.ResolveClients {
return "", nil
}
arpa, err := netutil.IPToReversedAddr(ip)
arpa, err := netutil.IPToReversedAddr(ip.AsSlice())
if err != nil {
return "", fmt.Errorf("reversing ip: %w", err)
return "", 0, fmt.Errorf("reversing ip: %w", err)
}
arpa = dns.Fqdn(arpa)
@@ -328,54 +338,74 @@ func (s *Server) Exchange(ip net.IP) (host string, err error) {
Qclass: dns.ClassINET,
}},
}
ctx := &proxy.DNSContext{
dctx := &proxy.DNSContext{
Proto: "udp",
Req: req,
StartTime: time.Now(),
}
var resolver *proxy.Proxy
if s.privateNets.Contains(ip) {
var errMsg string
if s.privateNets.Contains(ip.AsSlice()) {
if !s.conf.UsePrivateRDNS {
return "", nil
return "", 0, nil
}
resolver = s.localResolvers
errMsg = "resolving a private address: %w"
s.recDetector.add(*req)
} else {
resolver = s.internalProxy
errMsg = "resolving an address: %w"
}
if err = resolver.Resolve(dctx); err != nil {
return "", 0, fmt.Errorf(errMsg, err)
}
if err = resolver.Resolve(ctx); err != nil {
return "", err
}
return hostFromPTR(dctx.Res)
}
// hostFromPTR returns domain name from the PTR response or error.
func hostFromPTR(resp *dns.Msg) (host string, ttl time.Duration, err error) {
// Distinguish between NODATA response and a failed request.
resp := ctx.Res
if resp.Rcode != dns.RcodeSuccess && resp.Rcode != dns.RcodeNameError {
return "", fmt.Errorf(
return "", 0, fmt.Errorf(
"received %s response: %w",
dns.RcodeToString[resp.Rcode],
ErrRDNSFailed,
)
}
var ttlSec uint32
log.Debug("dnsforward: resolving ptr, received %d answers", len(resp.Answer))
for _, ans := range resp.Answer {
ptr, ok := ans.(*dns.PTR)
if ok {
return strings.TrimSuffix(ptr.Ptr, "."), nil
if !ok {
continue
}
// Respect zero TTL records since some DNS servers use it to
// locally-resolved addresses.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/6046.
if ptr.Hdr.Ttl >= ttlSec {
host = ptr.Ptr
ttlSec = ptr.Hdr.Ttl
}
}
return "", ErrRDNSNoData
}
if host != "" {
// NOTE: Don't use [aghnet.NormalizeDomain] to retain original letter
// case.
host = strings.TrimSuffix(host, ".")
ttl = time.Duration(ttlSec) * time.Second
// ResolvesPrivatePTR implements the RDNSExchanger interface for *Server.
func (s *Server) ResolvesPrivatePTR() (ok bool) {
s.serverLock.RLock()
defer s.serverLock.RUnlock()
return host, ttl, nil
}
return s.conf.UsePrivateRDNS
return "", 0, ErrRDNSNoData
}
// Start starts the DNS server.
@@ -442,6 +472,7 @@ func (s *Server) filterOurDNSAddrs(addrs []string) (filtered []string, err error
}
ourAddrsSet := stringutil.NewSet(ourAddrs...)
log.Debug("dnsforward: filtering out %s", ourAddrsSet.String())
// TODO(e.burkov): The approach of subtracting sets of strings is not
// really applicable here since in case of listening on all network
@@ -450,43 +481,50 @@ func (s *Server) filterOurDNSAddrs(addrs []string) (filtered []string, err error
return stringutil.FilterOut(addrs, ourAddrsSet.Has), nil
}
// setupResolvers initializes the resolvers for local addresses. For internal
// use only.
func (s *Server) setupResolvers(localAddrs []string) (err error) {
// setupLocalResolvers initializes the resolvers for local addresses. For
// internal use only.
func (s *Server) setupLocalResolvers() (err error) {
bootstraps := s.conf.BootstrapDNS
if len(localAddrs) == 0 {
localAddrs = s.sysResolvers.Get()
resolvers := s.conf.LocalPTRResolvers
if len(resolvers) == 0 {
resolvers = s.sysResolvers.Get()
bootstraps = nil
} else {
resolvers = stringutil.FilterOut(resolvers, IsCommentOrEmpty)
}
localAddrs, err = s.filterOurDNSAddrs(localAddrs)
resolvers, err = s.filterOurDNSAddrs(resolvers)
if err != nil {
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)
var upsConfig *proxy.UpstreamConfig
upsConfig, err = proxy.ParseUpstreamsConfig(
localAddrs,
&upstream.Options{
Bootstrap: bootstraps,
Timeout: defaultLocalTimeout,
// TODO(e.burkov): Should we verify server's certificates?
uc, err := s.prepareUpstreamConfig(resolvers, nil, &upstream.Options{
Bootstrap: bootstraps,
Timeout: defaultLocalTimeout,
// TODO(e.burkov): Should we verify server's certificates?
PreferIPv6: s.conf.BootstrapPreferIPv6,
},
)
PreferIPv6: s.conf.BootstrapPreferIPv6,
})
if err != nil {
return fmt.Errorf("parsing upstreams: %w", err)
return fmt.Errorf("preparing private upstreams: %w", err)
}
s.localResolvers = &proxy.Proxy{
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
}
@@ -510,7 +548,8 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
err = s.prepareUpstreamSettings()
if err != nil {
return fmt.Errorf("preparing upstream settings: %w", err)
// Don't wrap the error, because it's informative enough as is.
return err
}
var proxyConfig proxy.Config
@@ -535,25 +574,48 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
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.
err = s.setupResolvers(s.conf.LocalPTRResolvers)
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
err = s.setupLocalResolvers()
if err != nil {
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.setupAddrProc()
s.registerHandlers()
return nil
}
// setupAddrProc initializes the address processor. For internal use only.
func (s *Server) setupAddrProc() {
// TODO(a.garipov): This is a crutch for tests; remove.
if s.conf.AddrProcConf == nil {
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.
func validateBlockingMode(mode BlockingMode, blockingIPv4, blockingIPv6 net.IP) (err error) {
switch mode {
@@ -692,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.
if conf == nil {
conf = &s.conf
} else {
closeErr := s.addrProc.Close()
if closeErr != nil {
log.Error("dnsforward: closing address processor: %s", closeErr)
}
}
err = s.Prepare(conf)

View File

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

View File

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

View File

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

View File

@@ -124,7 +124,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
cacheMinTTL := s.conf.CacheMinTTL
cacheMaxTTL := s.conf.CacheMaxTTL
cacheOptimistic := s.conf.CacheOptimistic
resolveClients := s.conf.ResolveClients
resolveClients := s.conf.AddrProcConf.UseRDNS
usePrivateRDNS := s.conf.UsePrivateRDNS
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.EnableDNSSEC, dc.DNSSECEnabled)
setIfNotNil(&s.conf.AAAADisabled, dc.DisableIPv6)
setIfNotNil(&s.conf.ResolveClients, dc.ResolveClients)
setIfNotNil(&s.conf.UsePrivateRDNS, dc.UsePrivateRDNS)
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.
// shouldRestart is true if the server should be restarted to apply changes.
// 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) {
for _, hasSet := range []bool{
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.CacheMaxTTL, dc.CacheMaxTTL),
setIfNotNil(&s.conf.CacheOptimistic, dc.CacheOptimistic),
setIfNotNil(&s.conf.AddrProcConf.UseRDNS, dc.ResolveClients),
setIfNotNil(&s.conf.UsePrivateRDNS, dc.UsePrivateRDNS),
} {
shouldRestart = shouldRestart || hasSet
if shouldRestart {
@@ -633,61 +636,70 @@ func (err domainSpecificTestError) Error() (msg string) {
return fmt.Sprintf("WARNING: %s", err.error)
}
// checkDNS checks the upstream server defined by upstreamConfigStr using
// healthCheck for actually exchange messages. It uses bootstrap to resolve the
// upstream's address.
func checkDNS(
upstreamConfigStr string,
bootstrap []string,
bootstrapPrefIPv6 bool,
timeout time.Duration,
healthCheck healthCheckFunc,
) (err error) {
if IsCommentOrEmpty(upstreamConfigStr) {
return nil
// parseUpstreamLine parses line and creates the [upstream.Upstream] using opts
// and information from [s.dnsFilter.EtcHosts]. It returns an error if the line
// is not a valid upstream line, see [upstream.AddressToUpstream]. It's a
// caller's responsibility to close u.
func (s *Server) parseUpstreamLine(
line string,
opts *upstream.Options,
) (u upstream.Upstream, specific bool, err error) {
// Separate upstream from domains list.
upstreamAddr, domains, err := separateUpstream(line)
if err != nil {
return nil, false, fmt.Errorf("wrong upstream format: %w", err)
}
// Separate upstream from domains list.
upstreamAddr, domains, err := separateUpstream(upstreamConfigStr)
if err != nil {
return fmt.Errorf("wrong upstream format: %w", err)
}
specific = len(domains) > 0
useDefault, err := validateUpstream(upstreamAddr, domains)
if err != nil {
return fmt.Errorf("wrong upstream format: %w", err)
return nil, specific, fmt.Errorf("wrong upstream format: %w", err)
} else if useDefault {
return nil
}
if len(bootstrap) == 0 {
bootstrap = defaultBootstrap
return nil, specific, nil
}
log.Debug("dnsforward: checking if upstream %q works", upstreamAddr)
u, err := upstream.AddressToUpstream(upstreamAddr, &upstream.Options{
Bootstrap: bootstrap,
Timeout: timeout,
PreferIPv6: bootstrapPrefIPv6,
})
opts = &upstream.Options{
Bootstrap: opts.Bootstrap,
Timeout: opts.Timeout,
PreferIPv6: opts.PreferIPv6,
}
if s.dnsFilter.EtcHosts != nil {
resolved := s.resolveUpstreamHost(extractUpstreamHost(upstreamAddr))
sortNetIPAddrs(resolved, opts.PreferIPv6)
opts.ServerIPAddrs = resolved
}
u, err = upstream.AddressToUpstream(upstreamAddr, opts)
if err != nil {
return fmt.Errorf("failed to choose upstream for %q: %w", upstreamAddr, err)
return nil, specific, fmt.Errorf("creating upstream for %q: %w", upstreamAddr, err)
}
return u, specific, nil
}
func (s *Server) checkDNS(line string, opts *upstream.Options, check healthCheckFunc) (err error) {
if IsCommentOrEmpty(line) {
return nil
}
var u upstream.Upstream
var specific bool
defer func() {
if err != nil && specific {
err = domainSpecificTestError{error: err}
}
}()
u, specific, err = s.parseUpstreamLine(line, opts)
if err != nil || u == nil {
return err
}
defer func() { err = errors.WithDeferred(err, u.Close()) }()
if err = healthCheck(u); err != nil {
err = fmt.Errorf("upstream %q fails to exchange: %w", upstreamAddr, err)
if domains != nil {
return domainSpecificTestError{error: err}
}
return err
}
log.Debug("dnsforward: upstream %q is ok", upstreamAddr)
return nil
return check(u)
}
func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
@@ -699,47 +711,54 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
return
}
result := map[string]string{}
bootstraps := req.BootstrapDNS
bootstrapPrefIPv6 := s.conf.BootstrapPreferIPv6
timeout := s.conf.UpstreamTimeout
opts := &upstream.Options{
Bootstrap: req.BootstrapDNS,
Timeout: s.conf.UpstreamTimeout,
PreferIPv6: s.conf.BootstrapPreferIPv6,
}
if len(opts.Bootstrap) == 0 {
opts.Bootstrap = defaultBootstrap
}
type upsCheckResult = struct {
res string
err error
host string
}
req.Upstreams = stringutil.FilterOut(req.Upstreams, IsCommentOrEmpty)
req.PrivateUpstreams = stringutil.FilterOut(req.PrivateUpstreams, IsCommentOrEmpty)
upsNum := len(req.Upstreams) + len(req.PrivateUpstreams)
result := make(map[string]string, upsNum)
resCh := make(chan upsCheckResult, upsNum)
checkUps := func(ups string, healthCheck healthCheckFunc) {
res := upsCheckResult{
host: ups,
}
defer func() { resCh <- res }()
checkErr := checkDNS(ups, bootstraps, bootstrapPrefIPv6, timeout, healthCheck)
if checkErr != nil {
res.res = checkErr.Error()
} else {
res.res = "OK"
}
}
for _, ups := range req.Upstreams {
go checkUps(ups, checkDNSUpstreamExc)
go func(ups string) {
resCh <- upsCheckResult{
host: ups,
err: s.checkDNS(ups, opts, checkDNSUpstreamExc),
}
}(ups)
}
for _, ups := range req.PrivateUpstreams {
go checkUps(ups, checkPrivateUpstreamExc)
go func(ups string) {
resCh <- upsCheckResult{
host: ups,
err: s.checkDNS(ups, opts, checkPrivateUpstreamExc),
}
}(ups)
}
for i := 0; i < upsNum; i++ {
pair := <-resCh
// TODO(e.burkov): The upstreams used for both common and private
// resolving should be reported separately.
result[pair.host] = pair.res
pair := <-resCh
if pair.err != nil {
result[pair.host] = pair.err.Error()
} else {
result[pair.host] = "OK"
}
}
close(resCh)
_ = aghhttp.WriteJSONResponse(w, r, result)
}

View File

@@ -13,10 +13,12 @@ import (
"path/filepath"
"strings"
"testing"
"testing/fstest"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/netutil"
@@ -280,6 +282,10 @@ func TestIsCommentOrEmpty(t *testing.T) {
}
func TestValidateUpstreams(t *testing.T) {
const sdnsStamp = `sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_J` +
`S3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczE` +
`uYWRndWFyZC5jb20`
testCases := []struct {
name string
wantErr string
@@ -300,7 +306,7 @@ func TestValidateUpstreams(t *testing.T) {
"[//]tls://1.1.1.1",
"[/www.host.com/]#",
"[/host.com/google.com/]8.8.8.8",
"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
"[/host/]" + sdnsStamp,
},
}, {
name: "with_default",
@@ -310,7 +316,7 @@ func TestValidateUpstreams(t *testing.T) {
"[//]tls://1.1.1.1",
"[/www.host.com/]#",
"[/host.com/google.com/]8.8.8.8",
"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
"[/host/]" + sdnsStamp,
"8.8.8.8",
},
}, {
@@ -326,9 +332,10 @@ func TestValidateUpstreams(t *testing.T) {
wantErr: `validating upstream "123.3.7m": not an ip:port`,
set: []string{"123.3.7m"},
}, {
name: "invalid",
wantErr: `bad upstream for domain "[/host.com]tls://dns.adguard.com": missing separator`,
set: []string{"[/host.com]tls://dns.adguard.com"},
name: "invalid",
wantErr: `bad upstream for domain "[/host.com]tls://dns.adguard.com": ` +
`missing separator`,
set: []string{"[/host.com]tls://dns.adguard.com"},
}, {
name: "invalid",
wantErr: `validating upstream "[host.ru]#": not an ip:port`,
@@ -340,14 +347,14 @@ func TestValidateUpstreams(t *testing.T) {
"1.1.1.1",
"tls://1.1.1.1",
"https://dns.adguard.com/dns-query",
"sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
sdnsStamp,
"udp://dns.google",
"udp://8.8.8.8",
"[/host.com/]1.1.1.1",
"[//]tls://1.1.1.1",
"[/www.host.com/]#",
"[/host.com/google.com/]8.8.8.8",
"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
"[/host/]" + sdnsStamp,
"[/пример.рф/]8.8.8.8",
},
}, {
@@ -418,27 +425,28 @@ func TestValidateUpstreamsPrivate(t *testing.T) {
}
}
func newLocalUpstreamListener(t *testing.T, port int, handler dns.Handler) (real net.Addr) {
func newLocalUpstreamListener(t *testing.T, port uint16, handler dns.Handler) (real netip.AddrPort) {
t.Helper()
startCh := make(chan struct{})
upsSrv := &dns.Server{
Addr: netip.AddrPortFrom(netutil.IPv4Localhost(), uint16(port)).String(),
Addr: netip.AddrPortFrom(netutil.IPv4Localhost(), port).String(),
Net: "tcp",
Handler: handler,
NotifyStartedFunc: func() { close(startCh) },
}
go func() {
t := testutil.PanicT{}
err := upsSrv.ListenAndServe()
require.NoError(t, err)
require.NoError(testutil.PanicT{}, err)
}()
<-startCh
testutil.CleanupAndRequireSuccess(t, upsSrv.Shutdown)
return upsSrv.Listener.Addr()
return testutil.RequireTypeAssert[*net.TCPAddr](t, upsSrv.Listener.Addr()).AddrPort()
}
func TestServer_handleTestUpstreaDNS(t *testing.T) {
func TestServer_HandleTestUpstreamDNS(t *testing.T) {
goodHandler := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
err := w.WriteMsg(new(dns.Msg).SetReply(m))
require.NoError(testutil.PanicT{}, err)
@@ -457,9 +465,38 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
Host: newLocalUpstreamListener(t, 0, badHandler).String(),
}).String()
const upsTimeout = 100 * time.Millisecond
const (
upsTimeout = 100 * time.Millisecond
srv := createTestServer(t, &filtering.Config{}, ServerConfig{
hostsFileName = "hosts"
upstreamHost = "custom.localhost"
)
hostsListener := newLocalUpstreamListener(t, 0, goodHandler)
hostsUps := (&url.URL{
Scheme: "tcp",
Host: netutil.JoinHostPort(upstreamHost, int(hostsListener.Port())),
}).String()
hc, err := aghnet.NewHostsContainer(
filtering.SysHostsListID,
fstest.MapFS{
hostsFileName: &fstest.MapFile{
Data: []byte(hostsListener.Addr().String() + " " + upstreamHost),
},
},
&aghtest.FSWatcher{
OnEvents: func() (e <-chan struct{}) { return nil },
OnAdd: func(_ string) (err error) { return nil },
OnClose: func() (err error) { return nil },
},
hostsFileName,
)
require.NoError(t, err)
srv := createTestServer(t, &filtering.Config{
EtcHosts: hc,
}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
UpstreamTimeout: upsTimeout,
@@ -486,8 +523,7 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
"upstream_dns": []string{badUps},
},
wantResp: map[string]any{
badUps: `upstream "` + badUps + `" fails to exchange: ` +
`couldn't communicate with upstream: exchanging with ` +
badUps: `couldn't communicate with upstream: exchanging with ` +
badUps + ` over tcp: dns: id mismatch`,
},
name: "broken",
@@ -497,20 +533,40 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
},
wantResp: map[string]any{
goodUps: "OK",
badUps: `upstream "` + badUps + `" fails to exchange: ` +
`couldn't communicate with upstream: exchanging with ` +
badUps: `couldn't communicate with upstream: exchanging with ` +
badUps + ` over tcp: dns: id mismatch`,
},
name: "both",
}, {
body: map[string]any{
"upstream_dns": []string{"[/domain.example/]" + badUps},
},
wantResp: map[string]any{
"[/domain.example/]" + badUps: `WARNING: couldn't communicate ` +
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
`dns: id mismatch`,
},
name: "domain_specific_error",
}, {
body: map[string]any{
"upstream_dns": []string{hostsUps},
},
wantResp: map[string]any{
hostsUps: "OK",
},
name: "etc_hosts",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
reqBody, err := json.Marshal(tc.body)
var reqBody []byte
reqBody, err = json.Marshal(tc.body)
require.NoError(t, err)
w := httptest.NewRecorder()
r, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
var r *http.Request
r, err = http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
require.NoError(t, err)
srv.handleTestUpstreamDNS(w, r)
@@ -538,11 +594,15 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
req := map[string]any{
"upstream_dns": []string{sleepyUps},
}
reqBody, err := json.Marshal(req)
var reqBody []byte
reqBody, err = json.Marshal(req)
require.NoError(t, err)
w := httptest.NewRecorder()
r, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
var r *http.Request
r, err = http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
require.NoError(t, err)
srv.handleTestUpstreamDNS(w, r)

View File

@@ -26,11 +26,25 @@ func (s *Server) makeResponse(req *dns.Msg) (resp *dns.Msg) {
return resp
}
// ipsFromRules extracts non-IP addresses from the filtering result rules.
// containsIP returns true if the IP is already in the list.
func containsIP(ips []net.IP, ip net.IP) bool {
for _, a := range ips {
if a.Equal(ip) {
return true
}
}
return false
}
// ipsFromRules extracts unique non-IP addresses from the filtering result
// rules.
func ipsFromRules(resRules []*filtering.ResultRule) (ips []net.IP) {
for _, r := range resRules {
if r.IP != nil {
ips = append(ips, r.IP)
// len(resRules) and len(ips) are actually small enough for O(n^2) to do
// not raise performance questions.
if ip := r.IP; ip != nil && !containsIP(ips, ip) {
ips = append(ips, ip)
}
}
@@ -44,12 +58,13 @@ func (s *Server) genDNSFilterMessage(
res *filtering.Result,
) (resp *dns.Msg) {
req := dctx.Req
if qt := req.Question[0].Qtype; qt != dns.TypeA && qt != dns.TypeAAAA {
qt := req.Question[0].Qtype
if qt != dns.TypeA && qt != dns.TypeAAAA {
if s.conf.BlockingMode == BlockingModeNullIP {
return s.makeResponse(req)
}
return s.genNXDomain(req)
return s.newMsgNODATA(req)
}
switch res.Reason {
@@ -300,6 +315,17 @@ func (s *Server) makeResponseREFUSED(request *dns.Msg) *dns.Msg {
return &resp
}
// newMsgNODATA returns a properly initialized NODATA response.
//
// See https://www.rfc-editor.org/rfc/rfc2308#section-2.2.
func (s *Server) newMsgNODATA(req *dns.Msg) (resp *dns.Msg) {
resp = (&dns.Msg{}).SetRcode(req, dns.RcodeSuccess)
resp.RecursionAvailable = true
resp.Ns = s.genSOA(req)
return resp
}
func (s *Server) genNXDomain(request *dns.Msg) *dns.Msg {
resp := dns.Msg{}
resp.SetRcode(request, dns.RcodeNameError)

View File

@@ -30,6 +30,7 @@ type dnsContext struct {
setts *filtering.Settings
result *filtering.Result
// origResp is the response received from upstream. It is set when the
// response is modified by filters.
origResp *dns.Msg
@@ -48,13 +49,13 @@ type dnsContext struct {
// clientID is the ClientID from DoH, DoQ, or DoT, if provided.
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
// when the request is modified by rewrites.
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
// server's DNS filter is ready.
protectionEnabled bool
@@ -160,6 +161,22 @@ func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
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
// 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")
pctx := dctx.proxyCtx
s.processClientIP(pctx.Addr)
q := pctx.Req.Question[0]
qt := q.Qtype
if s.conf.AAAADisabled && qt == dns.TypeAAAA {
@@ -177,28 +196,13 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
return resultCodeFinish
}
if s.conf.OnDNSRequest != nil {
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." {
if (qt == dns.TypeA || qt == dns.TypeAAAA) && q.Name == mozillaFQDN {
pctx.Res = s.genNXDomain(pctx.Req)
return resultCodeFinish
}
// Handle a reserved domain healthcheck.adguardhome.test.
//
// [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." {
if q.Name == healthcheckFQDN {
// Generate a NODATA negative response to make nslookup exit with 0.
pctx.Res = s.makeResponse(pctx.Req)
@@ -213,11 +217,28 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
// Get the client-specific filtering settings.
dctx.protectionEnabled, _ = s.UpdatedProtectionStatus()
dctx.setts = s.getClientRequestFilteringSettings(dctx)
dctx.setts = s.clientRequestFilteringSettings(dctx)
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) {
s.tableHostToIPLock.Lock()
defer s.tableHostToIPLock.Unlock()
@@ -698,6 +719,20 @@ func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
if s.conf.UsePrivateRDNS {
s.recDetector.add(*pctx.Req)
if err := s.localResolvers.Resolve(pctx); err != nil {
log.Debug("dnsforward: resolving private address: %s", err)
// Generate the server failure if the private upstream configuration
// is empty.
//
// 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
return resultCodeError
@@ -727,10 +762,6 @@ func (s *Server) processFilteringBeforeRequest(ctx *dnsContext) (rc resultCode)
s.serverLock.RLock()
defer s.serverLock.RUnlock()
if s.dnsFilter == nil {
return resultCodeSuccess
}
var err error
if ctx.result, err = s.filterDNSRequest(ctx); err != nil {
ctx.err = err
@@ -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
// response if the protection is disabled since dnsrewrite rules aren't
// applied to it anyway.
if !dctx.protectionEnabled || !dctx.responseFromUpstream || s.dnsFilter == nil {
if !dctx.protectionEnabled || !dctx.responseFromUpstream {
return resultCodeSuccess
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -22,6 +23,96 @@ const (
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) {
dohSVCB := &dns.SVCB{
Priority: 1,
@@ -64,7 +155,7 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
}{{
name: "pass_host",
wantRes: resultCodeSuccess,
host: "example.net.",
host: testQuestionTarget,
qtype: dns.TypeSVCB,
ddrEnabled: true,
portDoH: 8043,
@@ -234,33 +325,33 @@ func TestServer_ProcessDetermineLocal(t *testing.T) {
func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
knownIP := netip.MustParseAddr("1.2.3.4")
testCases := []struct {
wantIP netip.Addr
name string
host string
wantIP netip.Addr
wantRes resultCode
isLocalCli bool
}{{
wantIP: knownIP,
name: "local_client_success",
host: "example.lan",
wantIP: knownIP,
wantRes: resultCodeSuccess,
isLocalCli: true,
}, {
wantIP: netip.Addr{},
name: "local_client_unknown_host",
host: "wronghost.lan",
wantIP: netip.Addr{},
wantRes: resultCodeSuccess,
isLocalCli: true,
}, {
wantIP: netip.Addr{},
name: "external_client_known_host",
host: "example.lan",
wantIP: netip.Addr{},
wantRes: resultCodeFinish,
isLocalCli: false,
}, {
wantIP: netip.Addr{},
name: "external_client_unknown_host",
host: "wronghost.lan",
wantIP: netip.Addr{},
wantRes: resultCodeFinish,
isLocalCli: false,
}}
@@ -332,52 +423,52 @@ func TestServer_ProcessDHCPHosts(t *testing.T) {
knownIP := netip.MustParseAddr("1.2.3.4")
testCases := []struct {
wantIP netip.Addr
name string
host string
suffix string
wantIP netip.Addr
wantRes resultCode
qtyp uint16
}{{
wantIP: netip.Addr{},
name: "success_external",
host: examplecom,
suffix: defaultLocalDomainSuffix,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess,
qtyp: dns.TypeA,
}, {
wantIP: netip.Addr{},
name: "success_external_non_a",
host: examplecom,
suffix: defaultLocalDomainSuffix,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess,
qtyp: dns.TypeCNAME,
}, {
wantIP: knownIP,
name: "success_internal",
host: examplelan,
suffix: defaultLocalDomainSuffix,
wantIP: knownIP,
wantRes: resultCodeSuccess,
qtyp: dns.TypeA,
}, {
wantIP: netip.Addr{},
name: "success_internal_unknown",
host: "example-new.lan",
suffix: defaultLocalDomainSuffix,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess,
qtyp: dns.TypeA,
}, {
wantIP: netip.Addr{},
name: "success_internal_aaaa",
host: examplelan,
suffix: defaultLocalDomainSuffix,
wantIP: netip.Addr{},
wantRes: resultCodeSuccess,
qtyp: dns.TypeAAAA,
}, {
wantIP: knownIP,
name: "success_custom_suffix",
host: "example.custom",
suffix: "custom",
wantIP: knownIP,
wantRes: resultCodeSuccess,
qtyp: dns.TypeA,
}}
@@ -560,10 +651,8 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
var dnsCtx *dnsContext
setup := func(use bool) {
proxyCtx = &proxy.DNSContext{
Addr: &net.TCPAddr{
IP: net.IP{127, 0, 0, 1},
},
Req: createTestMessageWithType(reqAddr, dns.TypePTR),
Addr: testClientAddr,
Req: createTestMessageWithType(reqAddr, dns.TypePTR),
}
dnsCtx = &dnsContext{
proxyCtx: proxyCtx,

View File

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

View File

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

View File

@@ -0,0 +1,312 @@
package dnsforward
import (
"bytes"
"fmt"
"net"
"net/url"
"os"
"strings"
"time"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/AdguardTeam/urlfilter"
"github.com/miekg/dns"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
// loadUpstreams parses upstream DNS servers from the configured file or from
// the configuration itself.
func (s *Server) loadUpstreams() (upstreams []string, err error) {
if s.conf.UpstreamDNSFileName == "" {
return stringutil.FilterOut(s.conf.UpstreamDNS, IsCommentOrEmpty), nil
}
var data []byte
data, err = os.ReadFile(s.conf.UpstreamDNSFileName)
if err != nil {
return nil, fmt.Errorf("reading upstream from file: %w", err)
}
upstreams = stringutil.SplitTrimmed(string(data), "\n")
log.Debug("dnsforward: got %d upstreams in %q", len(upstreams), s.conf.UpstreamDNSFileName)
return stringutil.FilterOut(upstreams, IsCommentOrEmpty), nil
}
// prepareUpstreamSettings sets upstream DNS server settings.
func (s *Server) prepareUpstreamSettings() (err error) {
// Load upstreams either from the file, or from the settings
var upstreams []string
upstreams, err = s.loadUpstreams()
if err != nil {
return fmt.Errorf("loading upstreams: %w", err)
}
s.conf.UpstreamConfig, err = s.prepareUpstreamConfig(upstreams, defaultDNS, &upstream.Options{
Bootstrap: s.conf.BootstrapDNS,
Timeout: s.conf.UpstreamTimeout,
HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams),
PreferIPv6: s.conf.BootstrapPreferIPv6,
// Use a customized set of RootCAs, because Go's default mechanism of
// loading TLS roots does not always work properly on some routers so we're
// loading roots manually and pass it here.
//
// See [aghtls.SystemRootCAs].
//
// TODO(a.garipov): Investigate if that's true.
RootCAs: s.conf.TLSv12Roots,
CipherSuites: s.conf.TLSCiphers,
})
if err != nil {
return fmt.Errorf("preparing upstream config: %w", err)
}
return nil
}
// prepareUpstreamConfig sets upstream configuration based on upstreams and
// configuration of s.
func (s *Server) prepareUpstreamConfig(
upstreams []string,
defaultUpstreams []string,
opts *upstream.Options,
) (uc *proxy.UpstreamConfig, err error) {
uc, err = proxy.ParseUpstreamsConfig(upstreams, opts)
if err != nil {
return nil, fmt.Errorf("parsing upstream config: %w", err)
}
if len(uc.Upstreams) == 0 && defaultUpstreams != nil {
log.Info("dnsforward: warning: no default upstreams specified, using %v", defaultUpstreams)
var defaultUpstreamConfig *proxy.UpstreamConfig
defaultUpstreamConfig, err = proxy.ParseUpstreamsConfig(defaultUpstreams, opts)
if err != nil {
return nil, fmt.Errorf("parsing default upstreams: %w", err)
}
uc.Upstreams = defaultUpstreamConfig.Upstreams
}
if s.dnsFilter.EtcHosts != nil {
err = s.replaceUpstreamsWithHosts(uc, opts)
if err != nil {
return nil, fmt.Errorf("resolving upstreams with hosts: %w", err)
}
}
return uc, nil
}
// replaceUpstreamsWithHosts replaces unique upstreams with their resolved
// versions based on the system hosts file.
//
// TODO(e.burkov): This should be performed inside dnsproxy, which should
// actually consider /etc/hosts. See TODO on [aghnet.HostsContainer].
func (s *Server) replaceUpstreamsWithHosts(
upsConf *proxy.UpstreamConfig,
opts *upstream.Options,
) (err error) {
resolved := map[string]*upstream.Options{}
err = s.resolveUpstreamsWithHosts(resolved, upsConf.Upstreams, opts)
if err != nil {
return fmt.Errorf("resolving upstreams: %w", err)
}
hosts := maps.Keys(upsConf.DomainReservedUpstreams)
// TODO(e.burkov): Think of extracting sorted range into an util function.
slices.Sort(hosts)
for _, host := range hosts {
err = s.resolveUpstreamsWithHosts(resolved, upsConf.DomainReservedUpstreams[host], opts)
if err != nil {
return fmt.Errorf("resolving upstreams reserved for %s: %w", host, err)
}
}
hosts = maps.Keys(upsConf.SpecifiedDomainUpstreams)
slices.Sort(hosts)
for _, host := range hosts {
err = s.resolveUpstreamsWithHosts(resolved, upsConf.SpecifiedDomainUpstreams[host], opts)
if err != nil {
return fmt.Errorf("resolving upstreams specific for %s: %w", host, err)
}
}
return nil
}
// resolveUpstreamsWithHosts resolves the IP addresses of each of the upstreams
// and replaces those both in upstreams and resolved. Upstreams that failed to
// resolve are placed to resolved as-is. This function only returns error of
// upstreams closing.
func (s *Server) resolveUpstreamsWithHosts(
resolved map[string]*upstream.Options,
upstreams []upstream.Upstream,
opts *upstream.Options,
) (err error) {
for i := range upstreams {
u := upstreams[i]
addr := u.Address()
host := extractUpstreamHost(addr)
withIPs, ok := resolved[host]
if !ok {
ips := s.resolveUpstreamHost(host)
if len(ips) == 0 {
resolved[host] = nil
return nil
}
sortNetIPAddrs(ips, opts.PreferIPv6)
withIPs = opts.Clone()
withIPs.ServerIPAddrs = ips
resolved[host] = withIPs
} else if withIPs == nil {
continue
}
if err = u.Close(); err != nil {
return fmt.Errorf("closing upstream %s: %w", addr, err)
}
upstreams[i], err = upstream.AddressToUpstream(addr, withIPs)
if err != nil {
return fmt.Errorf("replacing upstream %s with resolved %s: %w", addr, host, err)
}
log.Debug("dnsforward: using %s for %s", withIPs.ServerIPAddrs, upstreams[i].Address())
}
return nil
}
// extractUpstreamHost returns the hostname of addr without port with an
// assumption that any address passed here has already been successfully parsed
// by [upstream.AddressToUpstream]. This function essentially mirrors the logic
// of [upstream.AddressToUpstream], see TODO on [replaceUpstreamsWithHosts].
func extractUpstreamHost(addr string) (host string) {
var err error
if strings.Contains(addr, "://") {
var u *url.URL
u, err = url.Parse(addr)
if err != nil {
log.Debug("dnsforward: parsing upstream %s: %s", addr, err)
return addr
}
return u.Hostname()
}
// Probably, plain UDP upstream defined by address or address:port.
host, err = netutil.SplitHost(addr)
if err != nil {
return addr
}
return host
}
// resolveUpstreamHost returns the version of ups with IP addresses from the
// system hosts file placed into its options.
func (s *Server) resolveUpstreamHost(host string) (addrs []net.IP) {
req := &urlfilter.DNSRequest{
Hostname: host,
DNSType: dns.TypeA,
}
aRes, _ := s.dnsFilter.EtcHosts.MatchRequest(req)
req.DNSType = dns.TypeAAAA
aaaaRes, _ := s.dnsFilter.EtcHosts.MatchRequest(req)
var ips []net.IP
for _, rw := range append(aRes.DNSRewrites(), aaaaRes.DNSRewrites()...) {
dr := rw.DNSRewrite
if dr == nil || dr.Value == nil {
continue
}
if ip, ok := dr.Value.(net.IP); ok {
ips = append(ips, ip)
}
}
return ips
}
// sortNetIPAddrs sorts addrs in accordance with the protocol preferences.
// Invalid addresses are sorted near the end.
//
// TODO(e.burkov): This function taken from dnsproxy, which also already
// contains a few similar functions. Think of moving to golibs.
func sortNetIPAddrs(addrs []net.IP, preferIPv6 bool) {
l := len(addrs)
if l <= 1 {
return
}
slices.SortStableFunc(addrs, func(addrA, addrB net.IP) (sortsBefore bool) {
switch len(addrA) {
case net.IPv4len, net.IPv6len:
switch len(addrB) {
case net.IPv4len, net.IPv6len:
// Go on.
default:
return true
}
default:
return false
}
if aIs4, bIs4 := addrA.To4() != nil, addrB.To4() != nil; aIs4 != bIs4 {
if aIs4 {
return !preferIPv6
}
return preferIPv6
}
return bytes.Compare(addrA, addrB) < 0
})
}
// UpstreamHTTPVersions returns the HTTP versions for upstream configuration
// depending on configuration.
func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) {
if !http3 {
return upstream.DefaultHTTPVersions
}
return []upstream.HTTPVersion{
upstream.HTTPVersion3,
upstream.HTTPVersion2,
upstream.HTTPVersion11,
}
}
// setProxyUpstreamMode sets the upstream mode and related settings in conf
// based on provided parameters.
func setProxyUpstreamMode(
conf *proxy.Config,
allServers bool,
fastestAddr bool,
fastestTimeout time.Duration,
) {
if allServers {
conf.UpstreamMode = proxy.UModeParallel
} else if fastestAddr {
conf.UpstreamMode = proxy.UModeFastestAddr
conf.FastestPingTimeout = fastestTimeout
} else {
conf.UpstreamMode = proxy.UModeLoadBalance
}
}

View File

@@ -1,10 +1,7 @@
package filtering
import (
"bufio"
"bytes"
"fmt"
"hash/crc32"
"io"
"net/http"
"os"
@@ -14,6 +11,8 @@ import (
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghrenameio"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
@@ -29,9 +28,9 @@ const filterDir = "filters"
// TODO(e.burkov): Use more deterministic approach.
var nextFilterID = time.Now().Unix()
// FilterYAML respresents a filter list in the configuration file.
// FilterYAML represents a filter list in the configuration file.
//
// TODO(e.burkov): Investigate if the field oredering is important.
// TODO(e.burkov): Investigate if the field ordering is important.
type FilterYAML struct {
Enabled bool
URL string // URL or a file path
@@ -85,53 +84,53 @@ func (d *DNSFilter) filterSetProperties(
filters = d.WhitelistFilters
}
i := slices.IndexFunc(filters, func(filt FilterYAML) bool { return filt.URL == listURL })
i := slices.IndexFunc(filters, func(flt FilterYAML) bool { return flt.URL == listURL })
if i == -1 {
return false, errFilterNotExist
}
filt := &filters[i]
flt := &filters[i]
log.Debug(
"filtering: set name to %q, url to %s, enabled to %t for filter %s",
newList.Name,
newList.URL,
newList.Enabled,
filt.URL,
flt.URL,
)
defer func(oldURL, oldName string, oldEnabled bool, oldUpdated time.Time, oldRulesCount int) {
if err != nil {
filt.URL = oldURL
filt.Name = oldName
filt.Enabled = oldEnabled
filt.LastUpdated = oldUpdated
filt.RulesCount = oldRulesCount
flt.URL = oldURL
flt.Name = oldName
flt.Enabled = oldEnabled
flt.LastUpdated = oldUpdated
flt.RulesCount = oldRulesCount
}
}(filt.URL, filt.Name, filt.Enabled, filt.LastUpdated, filt.RulesCount)
}(flt.URL, flt.Name, flt.Enabled, flt.LastUpdated, flt.RulesCount)
filt.Name = newList.Name
flt.Name = newList.Name
if filt.URL != newList.URL {
if flt.URL != newList.URL {
if d.filterExistsLocked(newList.URL) {
return false, errFilterExists
}
shouldRestart = true
filt.URL = newList.URL
filt.LastUpdated = time.Time{}
filt.unload()
flt.URL = newList.URL
flt.LastUpdated = time.Time{}
flt.unload()
}
if filt.Enabled != newList.Enabled {
filt.Enabled = newList.Enabled
if flt.Enabled != newList.Enabled {
flt.Enabled = newList.Enabled
shouldRestart = true
}
if filt.Enabled {
if flt.Enabled {
if shouldRestart {
// Download the filter contents.
shouldRestart, err = d.update(filt)
shouldRestart, err = d.update(flt)
}
} else {
// TODO(e.burkov): The validation of the contents of the new URL is
@@ -139,7 +138,7 @@ func (d *DNSFilter) filterSetProperties(
// possible to set a bad rules source, but the validation should still
// kick in when the filter is enabled. Consider changing this behavior
// to be stricter.
filt.unload()
flt.unload()
}
return shouldRestart, err
@@ -213,7 +212,7 @@ func (d *DNSFilter) loadFilters(array []FilterYAML) {
err := d.load(filter)
if err != nil {
log.Error("Couldn't load filter %d contents due to %s", filter.ID, err)
log.Error("filtering: loading filter %d: %s", filter.ID, err)
}
}
}
@@ -252,24 +251,24 @@ func assignUniqueFilterID() int64 {
// Sets up a timer that will be checking for filters updates periodically
func (d *DNSFilter) periodicallyRefreshFilters() {
const maxInterval = 1 * 60 * 60
intval := 5 // use a dynamically increasing time interval
ivl := 5 // use a dynamically increasing time interval
for {
isNetErr, ok := false, false
if d.FiltersUpdateIntervalHours != 0 {
_, isNetErr, ok = d.tryRefreshFilters(true, true, false)
if ok && !isNetErr {
intval = maxInterval
ivl = maxInterval
}
}
if isNetErr {
intval *= 2
if intval > maxInterval {
intval = maxInterval
ivl *= 2
if ivl > maxInterval {
ivl = maxInterval
}
}
time.Sleep(time.Duration(intval) * time.Second)
time.Sleep(time.Duration(ivl) * time.Second)
}
}
@@ -331,19 +330,20 @@ func (d *DNSFilter) refreshFiltersArray(filters *[]FilterYAML, force bool) (int,
return 0, nil, nil, false
}
nfail := 0
failNum := 0
for i := range updateFilters {
uf := &updateFilters[i]
updated, err := d.update(uf)
updateFlags = append(updateFlags, updated)
if err != nil {
nfail++
log.Printf("Failed to update filter %s: %s\n", uf.URL, err)
failNum++
log.Error("filtering: updating filter from url %q: %s\n", uf.URL, err)
continue
}
}
if nfail == len(updateFilters) {
if failNum == len(updateFilters) {
return 0, nil, nil, true
}
@@ -367,7 +367,13 @@ func (d *DNSFilter) refreshFiltersArray(filters *[]FilterYAML, force bool) (int,
continue
}
log.Info("Updated filter #%d. Rules: %d -> %d", f.ID, f.RulesCount, uf.RulesCount)
log.Info(
"filtering: updated filter %d; rule count: %d (was %d)",
f.ID,
uf.RulesCount,
f.RulesCount,
)
f.Name = uf.Name
f.RulesCount = uf.RulesCount
f.checksum = uf.checksum
@@ -397,9 +403,10 @@ func (d *DNSFilter) refreshFiltersArray(filters *[]FilterYAML, force bool) (int,
//
// TODO(a.garipov, e.burkov): What the hell?
func (d *DNSFilter) refreshFiltersIntl(block, allow, force bool) (int, bool) {
log.Debug("filtering: updating...")
updNum := 0
log.Debug("filtering: starting updating")
defer func() { log.Debug("filtering: finished updating, %d updated", updNum) }()
var lists []FilterYAML
var toUpd []bool
isNetErr := false
@@ -437,131 +444,9 @@ func (d *DNSFilter) refreshFiltersIntl(block, allow, force bool) (int, bool) {
}
}
log.Debug("filtering: update finished: %d lists updated", updNum)
return updNum, false
}
// isPrintableText returns true if data is printable UTF-8 text with CR, LF, TAB
// characters.
//
// TODO(e.burkov): Investigate the purpose of this and improve the
// implementation. Perhaps, use something from the unicode package.
func isPrintableText(data string) (ok bool) {
for _, c := range []byte(data) {
if (c >= ' ' && c != 0x7f) || c == '\n' || c == '\r' || c == '\t' {
continue
}
return false
}
return true
}
// scanLinesWithBreak is essentially a [bufio.ScanLines] which keeps trailing
// line breaks.
func scanLinesWithBreak(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\n'); i >= 0 {
return i + 1, data[0 : i+1], nil
}
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}
// parseFilter copies filter's content from src to dst and returns the number of
// rules, number of bytes written, checksum, and title of the parsed list. dst
// must not be nil.
func (d *DNSFilter) parseFilter(
src io.Reader,
dst io.Writer,
) (rulesNum, written int, checksum uint32, title string, err error) {
scanner := bufio.NewScanner(src)
scanner.Split(scanLinesWithBreak)
titleFound := false
for n := 0; scanner.Scan(); written += n {
line := scanner.Text()
var isRule bool
var likelyTitle string
isRule, likelyTitle, err = d.parseFilterLine(line, !titleFound, written == 0)
if err != nil {
return 0, written, 0, "", err
}
if isRule {
rulesNum++
} else if likelyTitle != "" {
title, titleFound = likelyTitle, true
}
checksum = crc32.Update(checksum, crc32.IEEETable, []byte(line))
n, err = dst.Write([]byte(line))
if err != nil {
return 0, written, 0, "", fmt.Errorf("writing filter line: %w", err)
}
}
if err = scanner.Err(); err != nil {
return 0, written, 0, "", fmt.Errorf("scanning filter contents: %w", err)
}
return rulesNum, written, checksum, title, nil
}
// parseFilterLine returns true if the passed line is a rule. line is
// considered a rule if it's not a comment and contains no title.
func (d *DNSFilter) parseFilterLine(
line string,
lookForTitle bool,
testHTML bool,
) (isRule bool, title string, err error) {
if !isPrintableText(line) {
return false, "", errors.Error("filter contains non-printable characters")
}
line = strings.TrimSpace(line)
if line == "" || line[0] == '#' {
return false, "", nil
}
if testHTML && isHTML(line) {
return false, "", errors.Error("data is HTML, not plain text")
}
if line[0] == '!' && lookForTitle {
match := d.filterTitleRegexp.FindStringSubmatch(line)
if len(match) > 1 {
title = match[1]
}
return false, title, nil
}
return true, "", nil
}
// isHTML returns true if the line contains HTML tags instead of plain text.
// line shouldn have no leading space symbols.
//
// TODO(ameshkov): It actually gives too much false-positives. Perhaps, just
// check if trimmed string begins with angle bracket.
func isHTML(line string) (ok bool) {
line = strings.ToLower(line)
return strings.HasPrefix(line, "<html") || strings.HasPrefix(line, "<!doctype")
}
// update refreshes filter's content and a/mtimes of it's file.
func (d *DNSFilter) update(filter *FilterYAML) (b bool, err error) {
b, err = d.updateIntl(filter)
@@ -573,128 +458,123 @@ func (d *DNSFilter) update(filter *FilterYAML) (b bool, err error) {
filter.LastUpdated,
)
if chErr != nil {
log.Error("os.Chtimes(): %v", chErr)
log.Error("filtering: os.Chtimes(): %s", chErr)
}
}
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 sucсeeded.
func (d *DNSFilter) finalizeUpdate(
file *os.File,
flt *FilterYAML,
updated bool,
name string,
rnum int,
cs uint32,
) (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.Tracef("filter #%d from %s has no changes, skip", flt.ID, flt.URL)
return os.Remove(tmpFileName)
}
fltPath := flt.Path(d.DataDir)
log.Printf("saving contents of filter #%d into %s", flt.ID, fltPath)
// Don't use renamio 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, flt.checksum, flt.RulesCount = aghalg.Coalesce(flt.Name, name), cs, rnum
return nil
}
// updateIntl updates the flt rewriting it's actual file. It returns true if
// the actual update has been performed.
func (d *DNSFilter) updateIntl(flt *FilterYAML) (ok bool, err error) {
log.Tracef("downloading update for filter %d from %s", flt.ID, flt.URL)
log.Debug("filtering: downloading update for filter %d from %q", flt.ID, flt.URL)
var name string
var rnum, n int
var cs uint32
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, name, rnum, cs)
if ok && finErr == nil {
log.Printf("updated filter %d: %d bytes, %d rules", flt.ID, n, rnum)
return
}
err = errors.WithDeferred(err, finErr)
}()
var res *rulelist.ParseResult
// Change the default 0o600 permission to something more acceptable by end
// users.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/3198.
if err = tmpFile.Chmod(0o644); err != nil {
return false, fmt.Errorf("changing file mode: %w", err)
tmpFile, err := aghrenameio.NewPendingFile(flt.Path(d.DataDir), 0o644)
if err != nil {
return false, err
}
defer func() { err = d.finalizeUpdate(tmpFile, flt, res, err, ok) }()
r, err := d.reader(flt.URL)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return false, err
}
defer func() { err = errors.WithDeferred(err, r.Close()) }()
bufPtr := d.bufPool.Get().(*[]byte)
defer d.bufPool.Put(bufPtr)
p := rulelist.NewParser()
res, err = p.Parse(tmpFile, r, *bufPtr)
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())
}
var r io.Reader
if !filepath.IsAbs(flt.URL) {
var resp *http.Response
resp, err = d.HTTPClient.Get(flt.URL)
if err != nil {
log.Printf("requesting filter from %s, skip: %s", flt.URL, err)
log.Info("filtering: saving contents of filter %d into %q", id, flt.Path(d.DataDir))
return false, err
}
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
if resp.StatusCode != http.StatusOK {
log.Printf("got status code %d from %s, skip", 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
err = file.CloseReplace()
if err != nil {
return fmt.Errorf("finalizing update: %w", err)
}
rnum, n, cs, name, err = d.parseFilter(r, tmpFile)
rulesCount := res.RulesCount
log.Info("filtering: updated filter %d: %d bytes, %d rules", id, res.BytesWritten, rulesCount)
return cs != flt.checksum && err == nil, err
flt.Name = aghalg.Coalesce(flt.Name, res.Title)
flt.checksum = res.Checksum
flt.RulesCount = rulesCount
return nil
}
// reader returns an io.ReadCloser reading filtering-rule list data form either
// a file on the filesystem or the filter's HTTP URL.
func (d *DNSFilter) reader(fltURL string) (r io.ReadCloser, err error) {
if !filepath.IsAbs(fltURL) {
r, err = d.readerFromURL(fltURL)
if err != nil {
return nil, fmt.Errorf("reading from url: %w", err)
}
return r, nil
}
r, err = os.Open(fltURL)
if err != nil {
return nil, fmt.Errorf("opening file: %w", err)
}
return r, nil
}
// readerFromURL returns an io.ReadCloser reading filtering-rule list data form
// the filter's URL.
func (d *DNSFilter) readerFromURL(fltURL string) (r io.ReadCloser, err error) {
resp, err := d.HTTPClient.Get(fltURL)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("got status code %d, want %d", resp.StatusCode, http.StatusOK)
}
return resp.Body, nil
}
// loads filter contents from the file in dataDir
func (d *DNSFilter) load(flt *FilterYAML) (err error) {
fileName := flt.Path(d.DataDir)
log.Debug("filtering: loading filter %d from %s", flt.ID, fileName)
log.Debug("filtering: loading filter %d from %q", flt.ID, fileName)
file, err := os.Open(fileName)
if errors.Is(err, os.ErrNotExist) {
@@ -710,14 +590,18 @@ func (d *DNSFilter) load(flt *FilterYAML) (err error) {
return fmt.Errorf("getting filter file stat: %w", err)
}
log.Debug("filtering: file %s, id %d, length %d", fileName, flt.ID, st.Size())
log.Debug("filtering: file %q, id %d, length %d", fileName, flt.ID, st.Size())
rulesCount, _, checksum, _, err := d.parseFilter(file, io.Discard)
bufPtr := d.bufPool.Get().(*[]byte)
defer d.bufPool.Put(bufPtr)
p := rulelist.NewParser()
res, err := p.Parse(io.Discard, file, *bufPtr)
if err != nil {
return fmt.Errorf("parsing filter file: %w", err)
}
flt.RulesCount, flt.checksum, flt.LastUpdated = rulesCount, checksum, st.ModTime()
flt.RulesCount, flt.checksum, flt.LastUpdated = res.RulesCount, res.Checksum, st.ModTime()
return nil
}
@@ -759,8 +643,9 @@ func (d *DNSFilter) enableFiltersLocked(async bool) {
})
}
if err := d.SetFilters(filters, allowFilters, async); err != nil {
log.Debug("enabling filters: %s", err)
err := d.setFilters(filters, allowFilters, async)
if err != nil {
log.Error("filtering: enabling filters: %s", err)
}
d.SetEnabled(d.FilteringEnabled)

View File

@@ -9,7 +9,6 @@ import (
"net/http"
"os"
"path/filepath"
"regexp"
"runtime"
"runtime/debug"
"strings"
@@ -18,6 +17,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
@@ -170,6 +170,15 @@ type Checker interface {
// DNSFilter matches hostnames and DNS requests against filtering rules.
type DNSFilter struct {
// bufPool is a pool of buffers used for filtering-rule list parsing.
bufPool *sync.Pool
rulesStorage *filterlist.RuleStorage
filteringEngine *urlfilter.DNSEngine
rulesStorageAllow *filterlist.RuleStorage
filteringEngineAllow *urlfilter.DNSEngine
safeSearch SafeSearch
// safeBrowsingChecker is the safe browsing hash-prefix checker.
@@ -178,12 +187,6 @@ type DNSFilter struct {
// parentalControl is the parental control hash-prefix checker.
parentalControlChecker Checker
rulesStorage *filterlist.RuleStorage
filteringEngine *urlfilter.DNSEngine
rulesStorageAllow *filterlist.RuleStorage
filteringEngineAllow *urlfilter.DNSEngine
engineLock sync.RWMutex
Config // for direct access by library users, even a = assignment
@@ -196,12 +199,6 @@ type DNSFilter struct {
refreshLock *sync.Mutex
// filterTitleRegexp is the regular expression to retrieve a name of a
// filter list.
//
// TODO(e.burkov): Don't use regexp for such a simple text processing task.
filterTitleRegexp *regexp.Regexp
hostCheckers []hostChecker
}
@@ -339,12 +336,12 @@ func cloneRewrites(entries []*LegacyRewrite) (clone []*LegacyRewrite) {
return clone
}
// SetFilters sets new filters, synchronously or asynchronously. When filters
// setFilters sets new filters, synchronously or asynchronously. When filters
// are set asynchronously, the old filters continue working until the new
// filters are ready.
//
// In this case the caller must ensure that the old filter files are intact.
func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error {
func (d *DNSFilter) setFilters(blockFilters, allowFilters []Filter, async bool) error {
if async {
params := filtersInitializerParams{
allowFilters: allowFilters,
@@ -370,14 +367,7 @@ func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool)
return nil
}
err := d.initFiltering(allowFilters, blockFilters)
if err != nil {
log.Error("filtering: can't initialize filtering subsystem: %s", err)
return err
}
return nil
return d.initFiltering(allowFilters, blockFilters)
}
// Starts initializing new filters by signal from channel
@@ -386,7 +376,8 @@ func (d *DNSFilter) filtersInitializer() {
params := <-d.filtersInitializerChan
err := d.initFiltering(params.allowFilters, params.blockFilters)
if err != nil {
log.Error("Can't initialize filtering subsystem: %s", err)
log.Error("filtering: initializing: %s", err)
continue
}
}
@@ -519,7 +510,7 @@ func (d *DNSFilter) matchSysHosts(
dnsres, _ := d.EtcHosts.MatchRequest(&urlfilter.DNSRequest{
Hostname: host,
SortedClientTags: setts.ClientTags,
// TODO(e.burkov): Wait for urlfilter update to pass net.IP.
// TODO(e.burkov): Wait for urlfilter update to pass netip.Addr.
ClientIP: setts.ClientIP.String(),
ClientName: setts.ClientName,
DNSType: qtype,
@@ -718,7 +709,7 @@ func newRuleStorage(filters []Filter) (rs *filterlist.RuleStorage, err error) {
}
// Initialize urlfilter objects.
func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error {
func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) (err error) {
rulesStorage, err := newRuleStorage(blockFilters)
if err != nil {
return err
@@ -745,7 +736,8 @@ func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error {
// Make sure that the OS reclaims memory as soon as possible.
debug.FreeOSMemory()
log.Debug("initialized filtering engine")
log.Debug("filtering: initialized filtering engine")
return nil
}
@@ -949,8 +941,14 @@ func InitModule() {
// be non-nil.
func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
d = &DNSFilter{
bufPool: &sync.Pool{
New: func() (buf any) {
bufVal := make([]byte, rulelist.DefaultRuleBufSize)
return &bufVal
},
},
refreshLock: &sync.Mutex{},
filterTitleRegexp: regexp.MustCompile(`^! Title: +(.*)$`),
safeBrowsingChecker: c.SafeBrowsingChecker,
parentalControlChecker: c.ParentalControlChecker,
}
@@ -1047,7 +1045,7 @@ func (d *DNSFilter) checkSafeBrowsing(
if log.GetLevel() >= log.DEBUG {
timer := log.StartTimer()
defer timer.LogElapsed("safebrowsing lookup for %q", host)
defer timer.LogElapsed("filtering: safebrowsing lookup for %q", host)
}
res = Result{
@@ -1079,7 +1077,7 @@ func (d *DNSFilter) checkParental(
if log.GetLevel() >= log.DEBUG {
timer := log.StartTimer()
defer timer.LogElapsed("parental lookup for %q", host)
defer timer.LogElapsed("filtering: parental lookup for %q", host)
}
res = Result{

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