Compare commits

..

51 Commits

Author SHA1 Message Date
Dimitry Kolyshev
94458c5658 Merge remote-tracking branch 'origin/master' into 3389-querylog-export
# Conflicts:
#	CHANGELOG.md
2023-06-22 13:28:17 +04:00
Ainar Garipov
123ca87388 Pull request 1885: AG-23334-fix-snap-plan
Merge in DNS/adguard-home from AG-23334-fix-snap-plan to master

Squashed commit of the following:

commit 5d632d1d63c56911e005d0e772e82a509302e948
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 21 17:42:36 2023 +0300

    bamboo-specs: fix snap build; fmt
2023-06-21 17:47:16 +03:00
Ainar Garipov
994906fbd4 Pull request 1884: AG-23334-split-snap
Merge in DNS/adguard-home from AG-23334-split-snap to master

Squashed commit of the following:

commit 5a3d0f105d6930a0868f342821618a2a4acae282
Merge: bba693db6 06d465b0d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 21 17:10:15 2023 +0300

    Merge branch 'master' into AG-23334-split-snap

commit bba693db60fc7e154df3bc6bf186292ee9bc4ed5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 21 16:50:45 2023 +0300

    scripts/snap: fix docs; imp data

commit cb4a1d5bed147a41dda69b80b7ae6c3902c26538
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 21 14:03:48 2023 +0300

    all: fix scripts; imp docs

commit f88320b16ed7679e151a5358f4ac8e0212b4a827
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 20 18:52:21 2023 +0300

    all: split snap ci
2023-06-21 17:14:10 +03:00
Stanislav Chzhen
06d465b0d1 Pull request 1858: AG-22594-imp-whois
Merge in DNS/adguard-home from AG-22594-imp-whois to master

Squashed commit of the following:

commit 093feed53291d02469fb1bd8d99472597ebd5015
Merge: 956d20dc4 ca313521d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jun 21 12:42:40 2023 +0300

    Merge branch 'master' into AG-22594-imp-whois

commit 956d20dc473dcec90895b6f618fc56e96e9ff833
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 20 18:30:48 2023 +0300

    whois: imp code more

commit c771fd9c5e4d90e76d079a0d25ab097ab5652a42
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 20 15:05:45 2023 +0300

    whois: imp code

commit 21900fd468e10d9aee22149a6312b8596ff39810
Merge: 8dbe132c0 371261b2c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 20 11:34:06 2023 +0300

    Merge branch 'master' into AG-22594-imp-whois

commit 8dbe132c08d3ad4a63b0d4bdb9d00a5bc25971f4
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 20 11:33:26 2023 +0300

    whois: imp code more

commit f5e761a260237579c67cbd48f01ea90499bff6b0
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jun 19 16:04:35 2023 +0300

    whois: imp code

commit 2780f7e16aacddad8736f83b77ef9bfa1271f8b1
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 16 17:33:47 2023 +0300

    all: imp code

commit 1fc67016068b745a46b3d0d341ab14f9f5bdc9aa
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 16 17:29:19 2023 +0300

    whois: imp tests

commit 204761870764fb10feea20065d79dee8c321e70b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 16 11:55:37 2023 +0300

    all: upd deps

commit ded4f59498c5c544277b9c8e249567626547680e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jun 14 20:43:32 2023 +0300

    all: imp tests

commit 0eed9834ff9dd94d0788ce69d0bb0521fa725410
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jun 14 19:31:49 2023 +0300

    all: imp code

commit 9f867587c8ad87363b8c8b061ead536c1ec59c5d
Merge: 504e9484d 681c604c2
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 13 14:20:44 2023 +0300

    Merge branch 'master' into AG-22594-imp-whois

commit 504e9484dd84ab9d7c84a3f8399993d6422d3b67
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 13 14:18:06 2023 +0300

    all: imp cache

commit c492abe41ace7ad76fcd4e297c22b910a90fec30
Merge: db36adb9c 826b314f1
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 9 16:06:12 2023 +0300

    Merge branch 'master' into AG-22594-imp-whois

commit db36adb9c14ce92b3971db0d87ec313d5bcd787e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 9 15:53:33 2023 +0300

    all: add todo

commit 5cf192de9f93cd0d8521a3a6b4ded7f2bc5e0031
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 8 14:59:26 2023 +0300

    all: imp docs

commit 021aa3eb5b9476a93b4af5fc90cc9ccf014ca152
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jun 5 18:35:25 2023 +0300

    all: imp naming

commit 4626c3a7fa3f2543501806c9fa1a19531547f394
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 2 17:41:00 2023 +0300

    all: imp tests

commit 1afcc9605ca176e4c7f76a03a2c996cf7d6bde13
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 2 12:44:32 2023 +0300

    all: imp docs

commit cdd0544ff1a63faed5ced3dae6bfb3b783e45428
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 1 17:21:37 2023 +0300

    all: add docs

... and 2 more commits
2023-06-21 12:53:53 +03:00
Ainar Garipov
ca313521dc Pull request 1881: 5913-fix-safesearch-ipv6-better
Updates #5913.

Squashed commit of the following:

commit 6bff5ee1b77ae1812e2803361e60ef12148da5c7
Merge: 0a6f49008 2902f030b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 20 14:14:47 2023 +0300

    Merge branch 'master' into 5913-fix-safesearch-ipv6-better

commit 0a6f49008ac1c786ba380cc3c9a4c2c0c1f60815
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 20 14:11:18 2023 +0300

    safesearch: imp tests

commit 3f9056d26816fb753a394a7bcf86f2ae1201d19c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jun 19 23:01:03 2023 +0300

    safesearch: fix ipv6 more
2023-06-20 14:48:36 +03:00
Ainar Garipov
2902f030be Pull request 1879: nextapi-opts
Merge in DNS/adguard-home from nextapi-opts to master

Squashed commit of the following:

commit 01f27e374785f47f41470126ddc17cf447e84dd7
Merge: 17d3b06e0 371261b2c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 20 13:43:08 2023 +0300

    Merge branch 'master' into nextapi-opts

commit 17d3b06e0551908b06be67233181ce4067b6c1cc
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 20 13:15:31 2023 +0300

    next: imp chlog

commit 19d5ea9db0ee077c55c9f1d54a07a3c6b56b4e2e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 20 12:58:48 2023 +0300

    cmd: typo

commit 082ad5b5fc634a1533127e428680ccf55c0811ab
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jun 19 22:49:16 2023 +0300

    cmd: imp api, docs, names

commit a49b3cbcc591f36530306c2e08cf1ea7dc8f6d30
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jun 19 17:13:50 2023 +0300

    next/cmd: add opt parsing
2023-06-20 14:11:34 +03:00
Dimitry Kolyshev
371261b2c6 Pull request: dnsforward: stat empty queries
Merge in DNS/adguard-home from 5910-stats-malformed-entry to master

Squashed commit of the following:

commit ea6718daf0d65dc4b13cd08430da7fdeb6f7fd5b
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jun 19 17:34:50 2023 +0400

    all: imp docs

commit 5341c5b351c87556b91a7f692960edc76f44b21a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jun 19 16:47:13 2023 +0400

    all: docs

commit 1703c421f501484a80ddcf4ca1ba24f71fa5d225
Merge: f56b62490 d26c480d0
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jun 19 16:46:13 2023 +0400

    Merge remote-tracking branch 'origin/master' into 5910-stats-malformed-entry

    # Conflicts:
    #	CHANGELOG.md

commit f56b62490c7033e1d0b620a9cb4ba8de64862de0
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jun 19 14:49:43 2023 +0400

    all: docs

commit be954a8cab950d038c8cf08b95158069fa0747ba
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jun 19 14:44:05 2023 +0400

    querylog: empty queries

commit 0cda309d402156e750c7387e471da6795aa43bd6
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Jun 19 14:26:41 2023 +0400

    dnsforward: stat empty queries
2023-06-20 10:36:20 +03:00
Ainar Garipov
d26c480d03 Pull request 1877: 5913-fix-safesearch-ipv6
Updates #5913.

Squashed commit of the following:

commit a0ab1320ea22dc1b4e2804ef2d14e0091daa6a1e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jun 19 15:23:44 2023 +0300

    all: fmt; typo

commit 3a2e561c535bbbd2b2eeeaa1a6f423bc123b1a6b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jun 19 15:16:28 2023 +0300

    all: fix safesearch for ipv6
2023-06-19 15:45:01 +03:00
Ainar Garipov
b6d00f774b Pull request 1875: next-gocognit
Merge in DNS/adguard-home from next-gocognit to master

Squashed commit of the following:

commit 554458a63b44e71244b4364168e6e9903b880638
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jun 16 19:58:14 2023 +0300

    all: add tool; typo

commit 1b97f93811d1ea826762cfa43fef49ae43e06939
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jun 16 19:49:40 2023 +0300

    all: add gocognit
2023-06-19 12:21:32 +03:00
Dimitry Kolyshev
6fea435d89 Merge remote-tracking branch 'origin/master' into 3389-querylog-export
# Conflicts:
#	CHANGELOG.md
2023-06-15 14:40:06 +04:00
Dimitry Kolyshev
05706bd7ea querylog: bench search 2023-06-15 14:38:44 +04:00
Stanislav Chzhen
d3ada9881a Pull request 1854: 951-blocked-services-schedule
Merge in DNS/adguard-home from 951-blocked-services-schedule to master

Squashed commit of the following:

commit 0f0770292f8aa9dc0dddc05edefacf6655f329b8
Merge: 2705fbcfb 7309a5335
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jun 14 15:00:56 2023 +0300

    Merge branch 'master' into 951-blocked-services-schedule

commit 2705fbcfba57392bcc0fa9b6e1f1dfdce9796963
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jun 14 14:56:01 2023 +0300

    schedule: imp field alignment

commit ff8fa040558e97192a5a2c91c1d5722c67d88b3d
Merge: f99936f36 9fda7bfd3
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 13 11:49:15 2023 +0300

    Merge branch 'master' into 951-blocked-services-schedule

commit f99936f36be3453b50d772ce10bfdc1ad14879c5
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 9 19:14:06 2023 +0300

    schedule: add tests

commit 8436c2c5675d5cc22a3554ded2ce4a49eeeefc91
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 8 20:10:03 2023 +0300

    schedule: imp code

commit 850a3d93e659e0584bd449cf5af3f13b36be9e62
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 8 18:57:23 2023 +0300

    schedule: imp tests

commit d54c42b782a4a806d2f35abbea1826ba6394a64c
Merge: 21dfa51ea 156c199bb
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 8 18:12:03 2023 +0300

    Merge branch 'master' into 951-blocked-services-schedule

commit 21dfa51ea7441c655ac39f2cbceeaa598d364bff
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 8 18:02:47 2023 +0300

    all: add todo

commit d0d4532c8911fb6365f6af66ceb72a70b158b77d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jun 7 18:54:44 2023 +0300

    all: upd chlog

commit dc1d55fa23c0ec8e7f7b7cd25d3d0cbf3e51e236
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jun 7 17:34:28 2023 +0300

    all: add tests

commit 601775307c2b23fa9d6cf2aa271793e8fd5f1797
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jun 7 13:20:00 2023 +0300

    all: add schedule pkg

commit e0bcad0193a559a89f5f5052f5a985fa730d23be
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 2 13:03:04 2023 +0300

    filtering: add test case

commit da7a7c20c84151ff83414cb5d576a91740a1be7e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed May 31 15:31:48 2023 +0300

    filtering: imp code

commit 9845a11cdae25eafe39a50f8cdc408fefe1fd746
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon May 29 17:46:24 2023 +0300

    filtering: add type check

commit f4bcea8ad90d584ceff236ccce4f246e391e0e33
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon May 29 17:37:25 2023 +0300

    all: imp code

commit 9de3a0f37eb65d0db2431c27dd4d69136c1485a7
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon May 29 15:20:33 2023 +0300

    filtering: imp err msg

commit 1befab59cf2a12a3bc8296066ce1115dff5a8843
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon May 29 14:31:14 2023 +0300

    filtering: imp tests

commit 09506f924f363f1563009cec0c44ad40d08b8ddd
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri May 26 18:44:12 2023 +0300

    filtering: add tests

commit 36c42f523fcfe7fa6dca5f6f13e6c79f491c1ad5
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri May 26 17:57:01 2023 +0300

    all: fix typos

commit 69399cd3ea17691ff0848baf43ff09b18b82114c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu May 25 18:50:51 2023 +0300

    all: upd chlog

... and 2 more commits
2023-06-14 15:08:57 +03:00
Ainar Garipov
7309a53356 Pull request 1874: upd-github-templates
Merge in DNS/adguard-home from upd-github-templates to master

Squashed commit of the following:

commit 713a98668e5ee1fa3d64881496043b499ad6ac09
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 13 20:10:35 2023 +0300

    all: imp github issue templates more

commit 308a3fcd5564d7536b6ae1192cd17c04b6752ea1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 13 19:38:28 2023 +0300

    all: imp issue tmpls
2023-06-14 13:09:09 +03:00
Dimitry Kolyshev
00327757e1 querylog: bench search 2023-06-14 12:07:16 +04:00
Dimitry Kolyshev
5f0e53ded7 home: write timeout middleware 2023-06-14 10:51:17 +04:00
Dimitry Kolyshev
5cd4ce766d Merge remote-tracking branch 'origin/master' into 3389-querylog-export 2023-06-14 10:08:11 +04:00
Dimitry Kolyshev
e695fd9885 querylog: timeout revert 2023-06-14 10:07:22 +04:00
Ainar Garipov
15b937d68b Pull request 1873: upd-chlog
Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit feef14c20617f664832e4b99f9854e1fc7faba9f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 13 17:47:44 2023 +0300

    all: upd chlog
2023-06-13 17:51:41 +03:00
Ainar Garipov
681c604c22 Pull request 1870: nextapi-frontend-fs
Merge in DNS/adguard-home from nextapi-frontend-fs to master

Squashed commit of the following:

commit 3ed959f21939cf5590c27426af46906cbffed502
Merge: e60bbdd04 9fda7bfd3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 13 13:37:00 2023 +0300

    Merge branch 'master' into nextapi-frontend-fs

commit e60bbdd04ce841c1aaaa198cc9dc85ae14799ffa
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jun 9 14:53:09 2023 +0300

    next: support frontend fs
2023-06-13 13:41:13 +03:00
Dimitry Kolyshev
c43053e7d2 querylog: timeout 2023-06-13 14:36:31 +04:00
Dimitry Kolyshev
86e25944b3 querylog: imp code 2023-06-13 13:08:15 +04:00
Dimitry Kolyshev
fd7260f6de Merge remote-tracking branch 'origin/master' into 3389-querylog-export
# Conflicts:
#	CHANGELOG.md
2023-06-13 10:38:17 +04:00
Eugene Burkov
9fda7bfd34 Pull request 1871: upd-proxy
Merge in DNS/adguard-home from upd-proxy to master

Updates #5872.

Squashed commit of the following:

commit fac8d1cdeb53cb5cfe867c7a3521bb5ca76331ef
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jun 9 19:18:35 2023 +0300

    all: fix chlog

commit 10f64ba5683422ecc3cb5d0bd6c679ed4e80e0cc
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jun 9 19:14:07 2023 +0300

    all: log changes

commit 14410c4997c674a4f8a4adf8b77c1cf692131d24
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jun 9 18:48:09 2023 +0300

    all: upd proxy
2023-06-09 19:22:10 +03:00
Ainar Garipov
826b314f19 Pull request 1869: upd-chlog
Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit dafdadea12f9c2324649ed06973d7bd818d4b815
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 8 20:04:38 2023 +0300

    all: upd chlog
2023-06-08 20:09:45 +03:00
Eugene Burkov
156c199bbb Pull request 1868: 5874-fix-tcp-fallback
Merge in DNS/adguard-home from 5874-fix-tcp-fallback to master

Updates #5783.
Updates #5784.

Squashed commit of the following:

commit b57ca38d21308d71c3a2dd5c5d4b87a31e845930
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jun 8 16:22:36 2023 +0300

    all: upd proxy
2023-06-08 16:28:01 +03:00
Ainar Garipov
040596f3a0 Pull request 1867: 5874-openwrt-setsockopt
Updates #5874.

Squashed commit of the following:

commit 297839fc2f95c6602fc5d571d382d2bd5c08b0cf
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 8 12:38:37 2023 +0300

    all: upd dnsproxy
2023-06-08 12:47:40 +03:00
Ainar Garipov
fde082af44 Pull request 1866: upd-chlog
Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 73e90eb68edeb82d99637b51bd99ff12cf590e25
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 7 20:45:45 2023 +0300

    all: upd chlog
2023-06-08 12:16:13 +03:00
Ainar Garipov
329e144b4f Pull request 1865: upd-all
Merge in DNS/adguard-home from upd-all to master

Squashed commit of the following:

commit daa594cef7a741d2d43ca0602274aa04b9d2ea80
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 7 19:37:56 2023 +0300

    all: upd flts, i18n, svcs, trackers
2023-06-07 19:42:19 +03:00
Ainar Garipov
367b319f10 Pull request 1864: upd-go
Merge in DNS/adguard-home from upd-go to master

Squashed commit of the following:

commit 688019427edac3346bbf49bb3d58a5df3c4f30d6
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 7 18:19:17 2023 +0300

    all: upd go, deps, tools
2023-06-07 19:18:23 +03:00
Eugene Burkov
d6fe0bd740 Pull request 1862: upd-proxy
Merge in DNS/adguard-home from upd-proxy to master

Squashed commit of the following:

commit b258697cdac6a6ac83c94814dbe8551c5685fa3b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 6 13:22:17 2023 +0300

    all: upd proxy
2023-06-06 13:30:25 +03:00
Eugene Burkov
ae7bd79a6c Pull request 1860: upd-proxy
Merge in DNS/adguard-home from upd-proxy to master

Squashed commit of the following:

commit abb1f27954bf8c20461d86b382f59479a75a9958
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jun 2 16:57:53 2023 +0300

    all: upd dnsproxy
2023-06-02 17:19:44 +03:00
Dimitry Kolyshev
c591e46254 querylog: imp code 2023-06-02 15:26:04 +03:00
Dimitry Kolyshev
66d9ea7cca querylog: imp docs 2023-06-02 15:15:37 +03:00
Dimitry Kolyshev
dafc785845 querylog: imp code docs 2023-06-02 12:09:50 +03:00
Dimitry Kolyshev
e9b17891bb Merge remote-tracking branch 'origin/master' into 3389-querylog-export 2023-06-02 12:00:57 +03:00
Andrey Meshkov
0211424191 Pull request 1859: doc: add AdGuardian-Term to readme
Merge in DNS/adguard-home from fix-5858 to master

Closes #5858

Squashed commit of the following:

commit 3a5866bdff9bd087c6165b0356fa05bcd5e9d88d
Author: Andrey Meshkov <am@adguard.com>
Date:   Wed May 31 14:28:37 2023 +0300

    doc: add AdGuardian-Term to readme

    Closes #5858
2023-05-31 20:42:39 +03:00
Dimitry Kolyshev
0b27f048a7 querylog: imp code 2023-05-31 16:26:56 +03:00
Dimitry Kolyshev
649454e77b querylog: imp code 2023-05-31 15:11:33 +03:00
Dimitry Kolyshev
ca22d8524d all: imp docs 2023-05-31 15:11:15 +03:00
Dimitry Kolyshev
07f4f0474c all: imp docs 2023-05-31 12:06:13 +03:00
Dimitry Kolyshev
8813e135b6 querylog: export 2023-05-31 11:49:15 +03:00
Dimitry Kolyshev
f4f2c11eb9 all: export querylog docs 2023-05-29 13:29:41 +03:00
Dimitry Kolyshev
ea8d634f65 Pull request: dhcpd imp code
Merge in DNS/adguard-home from dhcpd-imp-code to master

Squashed commit of the following:

commit 413403c169bd3f6b5f2ed63b783d078dbb15e054
Merge: eed183850 0fec990bc
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri May 26 12:46:25 2023 +0300

    Merge remote-tracking branch 'origin/master' into dhcpd-imp-code

commit eed1838502add1e16e5d3ada03778f21913fd5e5
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri May 26 12:16:46 2023 +0300

    dhcpd: imp docs

commit fa4fe036f7b1f2b49201bf0b5b904f99989082f0
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 25 11:32:34 2023 +0300

    all: lint script

commit a4022b3d4bbfa709e5096397bbe64ea406c8a366
Merge: e08ff3a26 cbc7985e7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 25 11:29:57 2023 +0300

    Merge remote-tracking branch 'origin/master' into dhcpd-imp-code

    # Conflicts:
    #	scripts/make/go-lint.sh

commit e08ff3a26414e201d6e75608363db941fa2f5b39
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 24 15:43:11 2023 +0300

    dhcpd: imp code

commit 970b538f8ea94d3732d77bfb648e402a1d28ab06
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 24 15:40:36 2023 +0300

    dhcpd: imp code

commit 0e5916ddd7514af983e8557080d55d6aeb6fbbc0
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 24 15:37:17 2023 +0300

    dhcpd: imp code

commit e06a6c6031b232e76ae2be3e3b8fe1a2ffa715e0
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 23 16:40:09 2023 +0300

    dhcpd: imp code

commit eed4ff10ff1b29c71d70fb7978706efde89afee1
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 23 15:45:06 2023 +0300

    all: lint script

commit 87f84ace5f6f34dbc28befa8257d1d2492c5e0a4
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 23 15:44:23 2023 +0300

    dhcpd: imp code

commit a54c9929d51de1f1e6807d650fd08dd80ddbf147
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 23 14:29:42 2023 +0300

    dhcpd: imp code

commit 1bbea342f7f55587724aa9a29d9657e5ce75f5d8
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 23 14:12:09 2023 +0300

    dhcpd: imp code

commit 48fb4eff73683e799ddb3076afdcf7b067ca24b6
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 23 13:57:59 2023 +0300

    dhcpd: imp code

commit f6cd7fcb8d4c1c815a20875d777ea1eca2c8ea89
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 23 13:17:54 2023 +0300

    dhcpd: imp code

commit 2b91dc25bbaa16dba6d9389a4e2224cf91eb4554
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 23 12:57:46 2023 +0300

    dhcpd: imp code

commit 34f5dd58764080f6202fc9a1abd751a15dbf7090
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 22 15:31:39 2023 +0300

    dhcpd: imp code

commit 12ef0d225064a1b78adf7b2cfca21a8dba92ca8e
Merge: 6b62a7665 24b41100c
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 22 13:03:41 2023 +0300

    Merge remote-tracking branch 'origin/master' into dhcpd-imp-code

commit 6b62a7665720b85398d65a1926518a63e6bb6403
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 22 12:55:43 2023 +0300

    dhcpd: imp code

commit 18c5cdf0480fac7711282027a64d58704c75af5f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 22 12:48:30 2023 +0300

    dhcpd: imp code

commit e7c1f4324cba3fe86cf56df6b971791a5a8790de
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 22 12:37:15 2023 +0300

    dhcpd: imp code

... and 1 more commit
2023-05-26 12:50:03 +03:00
Eugene Burkov
0fec990bcf Pull request 1856: fix-dhcpd-tags
Merge in DNS/adguard-home from fix-dhcpd-tags to master

Updates #5615.

Squashed commit of the following:

commit 386b5a67b07cf8b06f4e4003870fbf3db5dfc94d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri May 26 12:23:41 2023 +0300

    dhcpd: fix bsd tags
2023-05-26 12:31:55 +03:00
Stanislav Chzhen
a7680a593a Pull request 1855: fix-clients-races
Merge in DNS/adguard-home from fix-clients-races to master

Squashed commit of the following:

commit 2b29271b4c04b1c3046bd4cb3fb82f82fff0973b
Merge: 7f553feff cbc7985e7
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu May 25 17:43:07 2023 +0300

    Merge branch 'master' into fix-clients-races

commit 7f553feff3aa46ae124a984776b60ed625148a1f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu May 25 16:25:43 2023 +0300

    home: imp docs more

commit aaf74666cf069fc6cdd5a5e64915fef5675e0382
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu May 25 13:57:42 2023 +0300

    home: imp docs

commit 50085085da3ae07b58bcc471906725339eec353e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed May 24 15:41:52 2023 +0300

    home: imp code more

commit 9dc001640ec4bef62b2ccaed307a9daeeb3a8d3a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue May 23 15:30:53 2023 +0300

    home: imp code

commit fa906e78ec0777c8c585622873addd0f4791554e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue May 23 14:17:16 2023 +0300

    home: fix clients races
2023-05-25 17:45:55 +03:00
Dimitry Kolyshev
cbc7985e75 Pull request: querylog imp code
Merge in DNS/adguard-home from querylog-imp-code to master

Squashed commit of the following:

commit a58ad36508a2355b686d314dec51ac0b5e357281
Merge: df5494f2c 941eb1dd7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 24 15:26:55 2023 +0300

    Merge remote-tracking branch 'origin/master' into querylog-imp-code

commit df5494f2c337736690a3c2a547c2d71858d0378f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 24 15:24:43 2023 +0300

    querylog: imp code

commit 8c3c2b76dd5858e7b107f222c112e9cde2477fb3
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 24 12:14:15 2023 +0300

    all: lint script

commit be04a4decfaf20a1649d32ecaab3c1c6bb205ffd
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 24 12:03:12 2023 +0300

    querylog: imp code

commit fe7beacff3a5cfcf2332c4998b9c65820284eaf7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 24 11:57:33 2023 +0300

    querylog: imp docs

commit 2ae239c57d12524fbc092f582842af2ad726c1d0
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 24 11:46:54 2023 +0300

    querylog: imp code

commit 417216cefbf154fa870f8f43468f35e0e345971f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 24 11:25:44 2023 +0300

    querylog: imp code

commit 514b6ee99113844a4e0dad30dc53703e3220c289
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 24 11:14:13 2023 +0300

    querylog: imp docs

commit 321351a3abb524208daacd5a3a7fbf5f07ab259d
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 22 16:38:31 2023 +0300

    querylog: imp code

commit ee91de5c43210b5bc213f933d411adb894d2e586
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 22 16:01:32 2023 +0300

    querylog: imp code

commit 862ff12177fb769d5cb2ec250eaee538dc91d70a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 22 15:07:24 2023 +0300

    querylog: imp code

commit cc62c1c4ae8b813d03ccf51b596ba1ebf44d9a1f
Merge: 37ace34e9 24b41100c
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 22 13:09:10 2023 +0300

    Merge remote-tracking branch 'origin/master' into querylog-imp-code

commit 37ace34e91e5189bef6e774db960f40cdaa18270
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 22 11:23:08 2023 +0300

    querylog: imp code

commit 8417815a6349f10b5dbad410ce28aab98bc479fa
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 22 11:08:29 2023 +0300

    querylog: imp docs

commit 4e5cde74d25713f78675aa3e18083b4fb5e619f3
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri May 19 16:41:34 2023 +0300

    querylog: imp code

commit 3494eab7006240f652a0217d305ac916bd6c3c83
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri May 19 16:13:08 2023 +0300

    all: lint script

commit 704534ce6278e7d9b1bef30a3acc4e59f25693bc
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri May 19 16:12:04 2023 +0300

    querylog: imp code

commit 48510102a2fa5187f78067d2b9157dac62f8bb56
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri May 19 15:52:57 2023 +0300

    querylog: imp code

commit 89c273aea0e6758eb749a2d3bbaf1bc385a57797
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri May 19 15:40:50 2023 +0300

    querylog: imp code

commit 0057fe64553ad38de0fda10efb9d3512c9a00e45
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri May 19 13:54:46 2023 +0300

    querylog: imp code

... and 1 more commit
2023-05-24 16:33:15 +03:00
Ainar Garipov
941eb1dd73 Pull request 1851: upd-golang-ubuntu-2
Merge in DNS/adguard-home from upd-golang-ubuntu-2 to master

Squashed commit of the following:

commit 9fdd86a0d352cd3a046bca19d6acf0f549adfc53
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon May 22 17:36:06 2023 +0300

    all: upd golang-ubuntu again
2023-05-22 17:40:09 +03:00
Ainar Garipov
fc64e0089e Pull request 1850: upd-golang-ubuntu
Merge in DNS/adguard-home from upd-golang-ubuntu to master

Squashed commit of the following:

commit d4c79a1ddbf732f2d8500830b9f53f547ce33f23
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon May 22 16:29:48 2023 +0300

    all: upd golang-ubuntu
2023-05-22 16:34:41 +03:00
Ainar Garipov
24b41100c3 Pull request 1848: fix-snap
Merge in DNS/adguard-home from fix-snap to master

Squashed commit of the following:

commit 4a12b2709229150e1b896b71b2b0a99249f94fde
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Sat May 20 11:51:25 2023 +0300

    bamboo-specs: upd snap script
2023-05-22 12:43:01 +03:00
Stanislav Chzhen
b72a3d01b8 Pull request 1845: AG-21324-update-dhcp
Merge in DNS/adguard-home from AG-21324-update-dhcp to master

Squashed commit of the following:

commit 20499d71ffe62f34576f9328db5dc6fb5c929c28
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed May 17 14:23:09 2023 +0300

    dhcpd: imp tests

commit fa6add1410a98d1b9dfd833bcb20ef9fb7ff57ca
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue May 16 09:52:38 2023 +0100

    all: update dhcp
2023-05-17 15:16:02 +03:00
Dimitry Kolyshev
0393e41096 Pull request: 1577: rewrite edit http api
Merge in DNS/adguard-home from 1577-rewrite-edit to master

Squashed commit of the following:

commit d03bee2a14337d169eea950b3df18a447c02b422
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri May 12 12:54:15 2023 +0300

    filtering: imp tests

commit bd68320df6dc057d922d91551cd00c74ebfaad6c
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri May 12 10:38:12 2023 +0300

    filtering: rewrite http tests

commit 0d8bbcd0194c0db89a6d4b45927669423c9bbb59
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 11 12:06:30 2023 +0300

    filtering: rewrite http tests

commit 29080384dd8fa80d5286d2fac1a4429d712bbafa
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 10 17:24:00 2023 +0300

    filtering: imp code

commit 96c6b1c98debfae565c5e6254746959a4307744e
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 10 16:56:32 2023 +0300

    filtering: imp code

commit b5d0c50ea11f9d829ba9d2b188fcc471a965e012
Merge: 5fa9e1c37 c77b2a0ce
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 10 16:37:19 2023 +0300

    Merge remote-tracking branch 'origin/master' into 1577-rewrite-edit

commit 5fa9e1c3714e107f893c03efa72227f3ed88691c
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 10 16:35:19 2023 +0300

    filtering: imp code

commit dd9dce8fbf0ce4bd200f2fc2fbf580e025920cd5
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 10 13:06:43 2023 +0300

    docs: rewrite http update

commit 0c67b040e80787b084c4669bb20db8d6d145fc1b
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 10 13:04:31 2023 +0300

    filtering: rewrite http update
2023-05-12 13:04:19 +03:00
120 changed files with 5493 additions and 2207 deletions

View File

@@ -10,52 +10,58 @@
- 'label': >
I have checked the
[Wiki](https://github.com/AdguardTeam/AdGuardHome/wiki) and
[Discussions](https://github.com/AdguardTeam/AdGuardHome/discussions)
[Discussions](https://github.com/AdguardTeam/AdGuardHome/discussions/categories/q-a)
and found no answer
'required': true
- 'label': >
I have searched other issues and found no duplicates
'required': true
- 'label': >
I want to report a bug and not ask a question
I want to report a bug and not [ask a question or ask for
help](https://github.com/AdguardTeam/AdGuardHome/discussions/categories/q-a)
'required': true
- 'label': >
I have set up AdGuard Home correctly and [configured clients to
use it](https://github.com/AdguardTeam/AdGuardHome/wiki/Clients).
(Use the
[Discussions](https://github.com/AdguardTeam/AdGuardHome/discussions/categories/q-a)
for help with installing and configuring clients.)
'required': true
'id': 'prerequisites'
'type': 'checkboxes'
- 'attributes':
'description': 'On which operating system type does the issue occur?'
'label': 'Operating system type'
'description': 'On which Platform does the issue occur?'
'label': 'Platform (OS and CPU architecture)'
'options':
- 'FreeBSD'
- 'Linux, OpenWrt'
- 'Linux, Other (please mention the version in the description)'
- 'macOS (aka Darwin)'
- 'OpenBSD'
- 'Windows'
- 'Other (please mention in the description)'
- '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'
- 'Custom (please mention in the description)'
'id': 'os'
'type': 'dropdown'
'validations':
'required': true
- 'attributes':
'description': 'On which CPU architecture does the issue occur?'
'label': 'CPU architecture'
'options':
- 'AMD64'
- 'x86'
- '64-bit ARM'
- 'ARMv5'
- 'ARMv6'
- 'ARMv7'
- '64-bit MIPS'
- '64-bit MIPS LE'
- '32-bit MIPS'
- '32-bit MIPS LE'
- '64-bit PowerPC LE'
- 'Other (please mention in the description)'
'id': 'arch'
'type': 'dropdown'
'validations':
'required': true
- 'attributes':
'description': 'How did you install AdGuard Home?'
'label': 'Installation'
@@ -63,7 +69,7 @@
- 'GitHub releases or script from README'
- 'Docker'
- 'Snapcraft'
- 'Custom port'
- 'Custom package (OpenWrt, HomeAssistant, etc; please mention in the description)'
- 'Other (please mention in the description)'
'id': 'install'
'type': 'dropdown'
@@ -89,21 +95,55 @@
'validations':
'required': true
- 'attributes':
'description': 'Please describe the bug'
'label': 'Description'
'description': >
Please describe what you did. An `nslookup` or a `dig` command is
the best way. For crashes, please provide a full failure log.
'label': 'Action'
'value': |
#### What did you do?
#### Expected result
#### Actual result
#### Screenshots (if applicable)
#### Additional information
'id': 'description'
```sh
nslookup -debug -type=a 'www.example.com' '$YOUR_AGH_ADDRESS'
```
'id': 'failing_action'
'type': 'textarea'
'validations':
'required': true
'description': 'File a bug report'
- 'attributes':
'description': >
What did you expect to see? Please add a description and/or
screenshots, if applicable.
'label': 'Expected result'
'placeholder': >
What did you expect to see?
'id': 'expected'
'type': 'textarea'
'validations':
'required': true
- 'attributes':
'description': >
What happened instead? Please add a description and/or screenshots,
if applicable.
'label': 'Actual result'
'placeholder': >
What did you see instead?
'id': 'result'
'type': 'textarea'
'validations':
'required': true
- 'attributes':
'description': >
Please add additional information, such as non-standard OS or port,
here. You can also put screenshots here, if applicable. For
example, it is better to copy and paste text from a terminal instead
of posting a screenshot of the terminal.
'label': 'Additional information and/or screenshots'
'placeholder': >
Additional OS information, screenshots of the UI, etc.
'id': 'additional'
'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.
'name': 'Bug'

View File

@@ -23,19 +23,32 @@
'id': 'prerequisites'
'type': 'checkboxes'
- 'attributes':
'description': 'Please describe the request'
'label': 'Description'
'value': |
#### What problem are you trying to solve?
#### Proposed solution
#### Alternatives considered
#### Additional information
'id': 'description'
'description': 'Please describe the problem you are trying to solve'
'label': 'The problem'
'placeholder': >
Please describe the problem you are trying to solve
'id': 'problem'
'type': 'textarea'
'validations':
'required': true
- 'attributes':
'description': 'What feature are you proposing to solve this problem?'
'label': 'Proposed solution'
'placeholder': >
What feature are you proposing to solve this problem?
'id': 'proposed_solution'
'type': 'textarea'
'validations':
'required': true
- 'attributes':
'label': 'Alternatives considered and additional information'
'placeholder': >
Are there any other ways to solve the problem?
'id': 'additional'
'type': 'textarea'
'validations':
'required': false
'description': 'Suggest a feature or an enhancement for AdGuard Home'
'labels':
- 'feature request'
'name': 'Feature request or enhancement'

20
.github/PULL_REQUEST_TEMPLATE vendored Normal file
View File

@@ -0,0 +1,20 @@
Before submitting a PR please make sure that:
1. You have discussed your solution in an issue and have got an
approval from a maintainer.
2. This isn't a localization fix; please send those to our
[CrowdIn](https://crowdin.com/project/adguard-applications/en#/adguard-home)
page.
3. Your code follows our
[code guidelines](https://github.com/AdguardTeam/CodeGuidelines/blob/master/Go/Go.md).
Add a short description here. The description should include:
1. Which issue this PR closes (`Closes #NNNN.`) or updates (`Updates
#NNNN.`).
2. A short description of how the change achieves that.
Do not forget to remove these instructions.

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
'name': 'potential-duplicates'
'on':
'issues':
'types':
- 'opened'
'jobs':
'run':
'runs-on': 'ubuntu-latest'
'steps':
- 'uses': 'wow-actions/potential-duplicates@v1'
'with':
'GITHUB_TOKEN': '${{ secrets.GITHUB_TOKEN }}'
'state': 'all'
'threshold': 0.6
'comment': |
Potential duplicates: {{#issues}}
* [#{{ number }}] {{ title }} ({{ accuracy }}%)
{{/issues}}

View File

@@ -14,15 +14,132 @@ and this project adheres to
<!--
## [v0.108.0] - TBA
## [v0.107.30] - 2023-04-26 (APPROX.)
## [v0.107.33] - 2023-06-28 (APPROX.)
See also the [v0.107.30 GitHub milestone][ms-v0.107.30].
See also the [v0.107.33 GitHub milestone][ms-v0.107.33].
[ms-v0.107.30]: https://github.com/AdguardTeam/AdGuardHome/milestone/66?closed=1
[ms-v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/milestone/68?closed=1
NOTE: Add new changes BELOW THIS COMMENT.
-->
### Added
- The new HTTP API, `GET /control/querylog/export`, which can be used to
export query log items. See `openapi/openapi.yaml` for the full description
([#3389]).
- The ability to set inactivity periods for filtering blocked services in the
configuration file ([#951]). The UI changes are coming in the upcoming
releases.
- The ability to edit rewrite rules via `PUT /control/rewrite/update` HTTP API
([#1577]).
### Changed
#### Configuration Changes
In this release, the schema version has changed from 20 to 21.
- Property `dns.blocked_services`, which in schema versions 20 and earlier used
to be a list containing ids of blocked services, is now an object containing
ids and schedule for blocked services:
```yaml
# BEFORE:
'blocked_services':
- id_1
- id_2
# AFTER:
'blocked_services':
'ids':
- id_1
- id_2
'schedule':
'time_zone': 'Local'
'sun':
'start': '0s'
'end': '24h'
'mon':
'start': '10m'
'end': '23h30m'
'tue':
'start': '20m'
'end': '23h'
'wed':
'start': '30m'
'end': '22h30m'
'thu':
'start': '40m'
'end': '22h'
'fri':
'start': '50m'
'end': '21h30m'
'sat':
'start': '1h'
'end': '21h'
```
To rollback this change, replace `dns.blocked_services` object with the list
of ids of blocked services and change the `schema_version` back to `20`.
### Fixed
- Queries with the question-section target `.`, for example `NS .`, are now
counted in the statistics and correctly shown in the query log ([#5910]).
- Safe Search not working with `AAAA` queries for domains that don't have `AAAA`
records ([#5913]).
[#951]: https://github.com/AdguardTeam/AdGuardHome/issues/951
[#1577]: https://github.com/AdguardTeam/AdGuardHome/issues/1577
[#3389]: https://github.com/AdguardTeam/AdGuardHome/issues/3389
[#5910]: https://github.com/AdguardTeam/AdGuardHome/issues/5910
[#5913]: https://github.com/AdguardTeam/AdGuardHome/issues/5913
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
## [v0.107.32] - 2023-06-13
### Fixed
- DNSCrypt upstream not resetting the client and resolver information on
dialing errors ([#5872]).
## [v0.107.31] - 2023-06-08
See also the [v0.107.31 GitHub milestone][ms-v0.107.31].
### Fixed
- Startup errors on OpenWrt ([#5872]).
- Plain-UDP upstreams always falling back to TCP, causing outages and slowdowns
([#5873], [#5874]).
[#5872]: https://github.com/AdguardTeam/AdGuardHome/issues/5872
[#5873]: https://github.com/AdguardTeam/AdGuardHome/issues/5873
[#5874]: https://github.com/AdguardTeam/AdGuardHome/issues/5874
[ms-v0.107.31]: https://github.com/AdguardTeam/AdGuardHome/milestone/67?closed=1
## [v0.107.30] - 2023-06-07
See also the [v0.107.30 GitHub milestone][ms-v0.107.30].
### Security
- Go version has been updated to prevent the possibility of exploiting the
CVE-2023-29402, CVE-2023-29403, and CVE-2023-29404 Go vulnerabilities fixed in
[Go 1.19.10][go-1.19.10].
### Fixed
- Unquoted IPv6 bind hosts with trailing colons erroneously considered
@@ -37,9 +154,8 @@ NOTE: Add new changes BELOW THIS COMMENT.
[#5716]: https://github.com/AdguardTeam/AdGuardHome/issues/5716
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
[go-1.19.10]: https://groups.google.com/g/golang-announce/c/q5135a9d924/m/j0ZoAJOHAwAJ
[ms-v0.107.30]: https://github.com/AdguardTeam/AdGuardHome/milestone/66?closed=1
@@ -1964,11 +2080,14 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.30...HEAD
[v0.107.30]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.29...v0.107.30
[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.29...HEAD
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...HEAD
[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
[v0.107.29]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.28...v0.107.29
[v0.107.28]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.27...v0.107.28
[v0.107.27]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.26...v0.107.27

View File

@@ -466,6 +466,10 @@ bug or implementing the feature.
Home](https://github.com/ebrianne/adguard-exporter) by
[@ebrianne](https://github.com/ebrianne).
* [Terminal-based, real-time traffic monitoring and statistics for your AdGuard Home
instance](https://github.com/Lissy93/AdGuardian-Term) by
[@Lissy93](https://github.com/Lissy93)
* [AdGuard Home on GLInet
routers](https://forum.gl-inet.com/t/adguardhome-on-gl-routers/10664) by
[Gl-Inet](https://gl-inet.com/).

View File

@@ -1,5 +1,8 @@
---
!include test.yaml
---
!include release.yaml
---
!include snapcraft.yaml
---
!include test.yaml

View File

@@ -1,349 +1,290 @@
---
'version': 2
'plan':
'project-key': 'AGH'
'key': 'AGHBSNAPSPECS'
'name': 'AdGuard Home - Build and publish release'
'project-key': 'AGH'
'key': 'AGHBSNAPSPECS'
'name': 'AdGuard Home - Build and publish release'
# Make sure to sync any changes with the branch overrides below.
'variables':
'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:6.3'
'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:6.7'
'stages':
- 'Build frontend':
'manual': false
'final': false
'jobs':
- 'Build frontend'
- 'Build frontend':
'manual': false
'final': false
'jobs':
- 'Build frontend'
- 'Make release':
'manual': false
'final': false
'jobs':
- 'Make release'
- 'Make release':
'manual': false
'final': false
'jobs':
- 'Make release'
- 'Make and publish docker':
'manual': false
'final': false
'jobs':
- 'Make and publish docker'
- 'Make and publish docker':
'manual': false
'final': false
'jobs':
- 'Make and publish docker'
- 'Publish to static storage':
'manual': false
'final': false
'jobs':
- 'Publish to static storage'
- 'Publish to static storage':
'manual': false
'final': false
'jobs':
- 'Publish to static storage'
- 'Publish to Snapstore':
'manual': false
'final': false
'jobs':
- 'Publish to Snapstore'
- 'Publish to GitHub Releases':
'manual': false
'final': false
'jobs':
- 'Publish to GitHub Releases'
- 'Publish to GitHub Releases':
'manual': false
'final': false
'jobs':
- 'Publish to GitHub Releases'
'Build frontend':
'docker':
'image': '${bamboo.dockerGo}'
'volumes':
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
'key': 'BF'
'other':
'clean-working-dir': true
'tasks':
- 'checkout':
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
'docker':
'image': '${bamboo.dockerGo}'
'volumes':
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
'key': 'BF'
'other':
'clean-working-dir': true
'tasks':
- 'checkout':
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
set -e -f -u -x
set -e -f -u -x
# Explicitly checkout the revision that we need.
git checkout "${bamboo.repository.revision.number}"
# Explicitly checkout the revision that we need.
git checkout "${bamboo.repository.revision.number}"
make js-deps js-build
'artifacts':
- 'name': 'AdGuardHome frontend'
'pattern': 'build*/**'
'shared': true
'required': true
'requirements':
- 'adg-docker': 'true'
make js-deps js-build
'artifacts':
- 'name': 'AdGuardHome frontend'
'pattern': 'build/**'
'shared': true
'required': true
'requirements':
- 'adg-docker': 'true'
'Make release':
'docker':
'image': '${bamboo.dockerGo}'
'volumes':
'${system.GO_CACHE_DIR}': '${bamboo.cacheGo}'
'${system.GO_PKG_CACHE_DIR}': '${bamboo.cacheGoPkg}'
'key': 'MR'
'other':
'clean-working-dir': true
'tasks':
- 'checkout':
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
'docker':
'image': '${bamboo.dockerGo}'
'volumes':
'${system.GO_CACHE_DIR}': '${bamboo.cacheGo}'
'${system.GO_PKG_CACHE_DIR}': '${bamboo.cacheGoPkg}'
'key': 'MR'
'other':
'clean-working-dir': true
'tasks':
- 'checkout':
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
set -e -f -u -x
set -e -f -u -x
# Explicitly checkout the revision that we need.
git checkout "${bamboo.repository.revision.number}"
# Explicitly checkout the revision that we need.
git checkout "${bamboo.repository.revision.number}"
# Run the build with the specified channel.
echo "${bamboo.gpgSecretKeyPart1}${bamboo.gpgSecretKeyPart2}"\
| awk '{ gsub(/\\n/, "\n"); print; }'\
| gpg --import --batch --yes
# Run the build with the specified channel.
echo "${bamboo.gpgSecretKeyPart1}${bamboo.gpgSecretKeyPart2}"\
| awk '{ gsub(/\\n/, "\n"); print; }'\
| gpg --import --batch --yes
make\
CHANNEL=${bamboo.channel}\
GPG_KEY_PASSPHRASE=${bamboo.gpgPassword}\
FRONTEND_PREBUILT=1\
PARALLELISM=1\
VERBOSE=2\
build-release
# TODO(a.garipov): Use more fine-grained artifact rules.
'artifacts':
- 'name': 'AdGuardHome dists'
'pattern': 'dist/**'
'shared': true
'required': true
'requirements':
- 'adg-docker': 'true'
make\
CHANNEL=${bamboo.channel}\
GPG_KEY_PASSPHRASE=${bamboo.gpgPassword}\
FRONTEND_PREBUILT=1\
PARALLELISM=1\
VERBOSE=2\
build-release
# TODO(a.garipov): Use more fine-grained artifact rules.
'artifacts':
- 'name': 'AdGuardHome dists'
'pattern': 'dist/**'
'shared': true
'required': true
'requirements':
- 'adg-docker': 'true'
'Make and publish docker':
'key': 'MPD'
'other':
'clean-working-dir': true
'tasks':
- 'checkout':
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
'key': 'MPD'
'other':
'clean-working-dir': true
'tasks':
- 'checkout':
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
set -e -f -u -x
set -e -f -u -x
COMMIT="${bamboo.repository.revision.number}"
export COMMIT
readonly COMMIT
COMMIT="${bamboo.repository.revision.number}"
export COMMIT
readonly COMMIT
# Explicitly checkout the revision that we need.
git checkout "$COMMIT"
# Explicitly checkout the revision that we need.
git checkout "$COMMIT"
# Install Qemu, create builder.
docker version -f '{{ .Server.Experimental }}'
docker buildx rm buildx-builder || :
docker buildx create --name buildx-builder --driver docker-container\
--use
docker buildx inspect --bootstrap
# Install Qemu, create builder.
docker version -f '{{ .Server.Experimental }}'
docker buildx rm buildx-builder || :
docker buildx create --name buildx-builder --driver docker-container\
--use
docker buildx inspect --bootstrap
# Login to DockerHub.
docker login -u="${bamboo.dockerHubUsername}"\
-p="${bamboo.dockerHubPassword}"
# Login to DockerHub.
docker login -u="${bamboo.dockerHubUsername}"\
-p="${bamboo.dockerHubPassword}"
# Boot the builder.
docker buildx inspect --bootstrap
# Boot the builder.
docker buildx inspect --bootstrap
# Print Docker info.
docker info
# Print Docker info.
docker info
# Prepare and push the build.
env\
CHANNEL="${bamboo.channel}"\
DIST_DIR='dist'\
DOCKER_IMAGE_NAME='adguard/adguardhome'\
DOCKER_OUTPUT="type=image,name=adguard/adguardhome,push=true"\
VERBOSE='1'\
sh ./scripts/make/build-docker.sh
'environment':
DOCKER_CLI_EXPERIMENTAL=enabled
'final-tasks':
- 'clean'
'requirements':
- 'adg-docker': 'true'
# Prepare and push the build.
env\
CHANNEL="${bamboo.channel}"\
DIST_DIR='dist'\
DOCKER_IMAGE_NAME='adguard/adguardhome'\
DOCKER_OUTPUT="type=image,name=adguard/adguardhome,push=true"\
VERBOSE='1'\
sh ./scripts/make/build-docker.sh
'environment':
DOCKER_CLI_EXPERIMENTAL=enabled
'final-tasks':
- 'clean'
'requirements':
- 'adg-docker': 'true'
'Publish to static storage':
'key': 'PUB'
'other':
'clean-working-dir': true
'tasks':
- 'clean'
- 'checkout':
'repository': 'bamboo-deploy-publisher'
'path': 'bamboo-deploy-publisher'
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
'key': 'PUB'
'other':
'clean-working-dir': true
'tasks':
- 'clean'
- 'checkout':
'repository': 'bamboo-deploy-publisher'
'path': 'bamboo-deploy-publisher'
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
set -e -f -u -x
set -e -f -u -x
cd ./dist/
cd ./dist/
CHANNEL="${bamboo.channel}"
export CHANNEL
CHANNEL="${bamboo.channel}"
export CHANNEL
../bamboo-deploy-publisher/deploy.sh adguard-home-"$CHANNEL"
'final-tasks':
- 'clean'
'requirements':
- 'adg-docker': 'true'
'Publish to Snapstore':
'docker':
'image': '${bamboo.dockerGo}'
'key': 'PTS'
'other':
'clean-working-dir': true
'tasks':
- 'clean'
- 'checkout':
'repository': 'bamboo-deploy-publisher'
'path': 'bamboo-deploy-publisher'
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
set -e -f -u -x
cd ./dist/
channel="${bamboo.channel}"
readonly channel
case "$channel"
in
('release')
snapchannel='candidate'
;;
('beta')
snapchannel='beta'
;;
('edge')
snapchannel='edge'
;;
(*)
echo "invalid channel '$channel'"
exit 1
;;
esac
env\
SNAPCRAFT_CHANNEL="$snapchannel"\
SNAPCRAFT_EMAIL="${bamboo.snapcraftEmail}"\
SNAPCRAFT_MACAROON="${bamboo.snapcraftMacaroonPassword}"\
SNAPCRAFT_UBUNTU_DISCHARGE="${bamboo.snapcraftUbuntuDischargePassword}"\
../bamboo-deploy-publisher/deploy.sh adguard-home-snap
'final-tasks':
- 'clean'
'requirements':
- 'adg-docker': 'true'
../bamboo-deploy-publisher/deploy.sh adguard-home-"$CHANNEL"
'final-tasks':
- 'clean'
'requirements':
- 'adg-docker': 'true'
'Publish to GitHub Releases':
'key': 'PTGR'
'other':
'clean-working-dir': true
'tasks':
- 'clean'
- 'checkout':
'repository': 'bamboo-deploy-publisher'
'path': 'bamboo-deploy-publisher'
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
'key': 'PTGR'
'other':
'clean-working-dir': true
'tasks':
- 'clean'
- 'checkout':
'repository': 'bamboo-deploy-publisher'
'path': 'bamboo-deploy-publisher'
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
set -e -f -u -x
set -e -f -u -x
channel="${bamboo.channel}"
readonly channel
channel="${bamboo.channel}"
readonly channel
if [ "$channel" != 'release' ] && [ "${channel}" != 'beta' ]
then
echo "don't publish to GitHub Releases for this channel"
if [ "$channel" != 'release' ] && [ "${channel}" != 'beta' ]
then
echo "don't publish to GitHub Releases for this channel"
exit 0
fi
exit 0
fi
cd ./dist/
cd ./dist/
env\
GITHUB_TOKEN="${bamboo.githubPublicRepoPassword}"\
../bamboo-deploy-publisher/deploy.sh adguard-home-github
'final-tasks':
- 'clean'
'requirements':
- 'adg-docker': 'true'
env\
GITHUB_TOKEN="${bamboo.githubPublicRepoPassword}"\
../bamboo-deploy-publisher/deploy.sh adguard-home-github
'final-tasks':
- 'clean'
'requirements':
- 'adg-docker': 'true'
'triggers':
# Don't use minute values that end with a zero or a five as these are often used
# in CI and so resources during these minutes can be quite busy.
- 'cron': '0 42 13 ? * MON-FRI *'
# Don't use minute values that end with a zero or a five as these are often
# used in CI and so resources during these minutes can be quite busy.
- 'cron': '0 42 13 ? * MON-FRI *'
'branches':
'create': 'manually'
'delete':
'after-deleted-days': 1
'after-inactive-days': 30
'integration':
'push-on-success': false
'merge-from': 'AdGuard Home - Build and publish release'
'link-to-jira': true
'create': 'manually'
'delete':
'after-deleted-days': 1
'after-inactive-days': 30
'integration':
'push-on-success': false
'merge-from': 'AdGuard Home - Build and publish release'
'link-to-jira': true
'notifications':
- 'events':
- 'plan-completed'
'recipients':
- 'webhook':
'name': 'Build webhook'
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo?channel=adguard-qa'
- 'events':
- 'plan-completed'
'recipients':
- 'webhook':
'name': 'Build webhook'
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo?channel=adguard-qa'
'labels': []
'other':
'concurrent-build-plugin': 'system-default'
'concurrent-build-plugin': 'system-default'
'branch-overrides':
# beta-vX.Y branches are the branches into which the commits that are needed to
# release a new patch version are initially cherry-picked.
- '^beta-v[0-9]+\.[0-9]+':
# Build betas on release branches manually.
'triggers': []
# Set the default release channel on the release branch to beta, as we may
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:6.3'
# release-vX.Y.Z branches are the branches from which the actual final release
# is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
# Disable integration branches for release branches.
'branch-config':
'integration':
'push-on-success': false
'merge-from': 'beta-v0.107'
# Build final releases on release branches manually.
'triggers': []
# Set the default release channel on the final branch to release, as these
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:6.3'
# beta-vX.Y branches are the branches into which the commits that are needed
# to release a new patch version are initially cherry-picked.
- '^beta-v[0-9]+\.[0-9]+':
# Build betas on release branches manually.
'triggers': []
# Set the default release channel on the release branch to beta, as we may
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:6.7'
# release-vX.Y.Z branches are the branches from which the actual final
# release is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
# Disable integration branches for release branches.
'branch-config':
'integration':
'push-on-success': false
'merge-from': 'beta-v0.107'
# Build final releases on release branches manually.
'triggers': []
# Set the default release channel on the final branch to release, as these
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:6.7'

211
bamboo-specs/snapcraft.yaml Normal file
View File

@@ -0,0 +1,211 @@
---
# This part of the release build is separate from the one described in
# release.yaml, because the Snapcraft infrastructure is brittle, and timeouts
# during logins and uploads often lead to release blocking.
'version': 2
'plan':
'project-key': 'AGH'
'key': 'AGHSNAP'
'name': 'AdGuard Home - Build and publish Snapcraft release'
# Make sure to sync any changes with the branch overrides below.
'variables':
'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:6.7'
'snapcraftChannel': 'edge'
'stages':
- 'Download release':
'manual': false
'final': false
'jobs':
- 'Download release'
- 'Build packages':
'manual': false
'final': false
'jobs':
- 'Build packages'
- 'Publish to Snapstore':
'manual': false
'final': false
'jobs':
- 'Publish to Snapstore'
# TODO(a.garipov): Consider using the Artifact Downloader Task if it ever learns
# about plan branches.
'Download release':
'artifacts':
- 'name': 'i386_binary'
'pattern': 'AdGuardHome_i386'
'shared': true
'required': true
- 'name': 'amd64_binary'
'pattern': 'AdGuardHome_amd64'
'shared': true
'required': true
- 'name': 'armhf_binary'
'pattern': 'AdGuardHome_armhf'
'shared': true
'required': true
- 'name': 'arm64_binary'
'pattern': 'AdGuardHome_arm64'
'shared': true
'required': true
'docker':
'image': '${bamboo.dockerGo}'
'key': 'DR'
'other':
'clean-working-dir': true
'tasks':
- 'checkout':
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
set -e -f -u -x
env\
CHANNEL="${bamboo.channel}"\
VERBOSE='1'\
sh ./scripts/snap/download.sh
'requirements':
- 'adg-docker': 'true'
'Build packages':
'artifact-subscriptions':
- 'artifact': 'i386_binary'
- 'artifact': 'amd64_binary'
- 'artifact': 'armhf_binary'
- 'artifact': 'arm64_binary'
'artifacts':
- 'name': 'i386_snap'
'pattern': 'AdGuardHome_i386.snap'
'shared': true
'required': true
- 'name': 'amd64_snap'
'pattern': 'AdGuardHome_amd64.snap'
'shared': true
'required': true
- 'name': 'armhf_snap'
'pattern': 'AdGuardHome_armhf.snap'
'shared': true
'required': true
- 'name': 'arm64_snap'
'pattern': 'AdGuardHome_arm64.snap'
'shared': true
'required': true
'docker':
'image': '${bamboo.dockerGo}'
'key': 'BP'
'other':
'clean-working-dir': true
'tasks':
- 'checkout':
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
set -e -f -u -x
env\
VERBOSE='1'\
sh ./scripts/snap/build.sh
'requirements':
- 'adg-docker': 'true'
'Publish to Snapstore':
'artifact-subscriptions':
- 'artifact': 'i386_snap'
- 'artifact': 'amd64_snap'
- 'artifact': 'armhf_snap'
- 'artifact': 'arm64_snap'
'docker':
'image': '${bamboo.dockerGo}'
'key': 'PTS'
'other':
'clean-working-dir': true
'tasks':
- 'checkout':
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
set -e -f -u -x
env\
SNAPCRAFT_CHANNEL="${bamboo.snapcraftChannel}"\
SNAPCRAFT_STORE_CREDENTIALS="${bamboo.snapcraftMacaroonPassword}"\
VERBOSE='1'\
sh ./scripts/snap/upload.sh
'final-tasks':
- 'clean'
'requirements':
- 'adg-docker': 'true'
'triggers':
# Don't use minute values that end with a zero or a five as these are often
# used in CI and so resources during these minutes can be quite busy.
#
# NOTE: The time is chosen to be exactly one hour after the main release
# build as defined as in release.yaml.
- 'cron': '0 42 14 ? * MON-FRI *'
'branches':
'create': 'manually'
'delete':
'after-deleted-days': 1
'after-inactive-days': 30
'integration':
'push-on-success': false
'merge-from': 'AdGuard Home - Build and publish Snapcraft release'
'link-to-jira': true
'notifications':
- 'events':
- 'plan-completed'
'recipients':
- 'webhook':
'name': 'Build webhook'
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo?channel=adguard-qa'
'labels': []
'other':
'concurrent-build-plugin': 'system-default'
'branch-overrides':
# beta-vX.Y branches are the branches into which the commits that are needed
# to release a new patch version are initially cherry-picked.
- '^beta-v[0-9]+\.[0-9]+':
# Build betas on release branches manually.
'triggers': []
# Set the default release channel on the release branch to beta, as we may
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:6.7'
'snapcraftChannel': 'beta'
# release-vX.Y.Z branches are the branches from which the actual final
# release is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
# Disable integration branches for release branches.
'branch-config':
'integration':
'push-on-success': false
'merge-from': 'beta-v0.107'
# Build final releases on release branches manually.
'triggers': []
# Set the default release channel on the final branch to release, as these
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:6.7'
'snapcraftChannel': 'candidate'

View File

@@ -1,64 +1,64 @@
---
'version': 2
'plan':
'project-key': 'AGH'
'key': 'AHBRTSPECS'
'name': 'AdGuard Home - Build and run tests'
'project-key': 'AGH'
'key': 'AHBRTSPECS'
'name': 'AdGuard Home - Build and run tests'
'variables':
'dockerGo': 'adguard/golang-ubuntu:6.3'
'dockerGo': 'adguard/golang-ubuntu:6.7'
'stages':
- 'Tests':
'manual': false
'final': false
'jobs':
- 'Test'
- 'Tests':
'manual': false
'final': false
'jobs':
- 'Test'
'Test':
'docker':
'image': '${bamboo.dockerGo}'
'volumes':
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
'${system.GO_CACHE_DIR}': '${bamboo.cacheGo}'
'${system.GO_PKG_CACHE_DIR}': '${bamboo.cacheGoPkg}'
'key': 'TEST'
'other':
'clean-working-dir': true
'tasks':
- 'checkout':
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
'docker':
'image': '${bamboo.dockerGo}'
'volumes':
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
'${system.GO_CACHE_DIR}': '${bamboo.cacheGo}'
'${system.GO_PKG_CACHE_DIR}': '${bamboo.cacheGoPkg}'
'key': 'TEST'
'other':
'clean-working-dir': true
'tasks':
- 'checkout':
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
set -e -f -u -x
set -e -f -u -x
make VERBOSE=1 ci go-tools lint
'final-tasks':
- 'clean'
'requirements':
- 'adg-docker': 'true'
make VERBOSE=1 ci go-tools lint
'final-tasks':
- 'clean'
'requirements':
- 'adg-docker': 'true'
'branches':
'create': 'for-pull-request'
'delete':
'after-deleted-days': 1
'after-inactive-days': 5
'integration':
'push-on-success': false
'merge-from': 'AdGuard Home - Build and run tests'
'link-to-jira': true
'create': 'for-pull-request'
'delete':
'after-deleted-days': 1
'after-inactive-days': 5
'integration':
'push-on-success': false
'merge-from': 'AdGuard Home - Build and run tests'
'link-to-jira': true
'notifications':
- 'events':
- 'plan-status-changed'
'recipients':
- 'webhook':
'name': 'Build webhook'
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo'
- 'events':
- 'plan-status-changed'
'recipients':
- 'webhook':
'name': 'Build webhook'
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo'
'labels': []
'other':
'concurrent-build-plugin': 'system-default'
'concurrent-build-plugin': 'system-default'

View File

@@ -150,7 +150,7 @@
"dns_allowlists": "Белыя спісы DNS",
"dns_blocklists_desc": "AdGuard Home будзе блакаваць дамены з чорных спісаў.",
"dns_allowlists_desc": "Дамены з белых спісаў DNS будуць дазволены, нават калі яны знаходзяцца ў любым з чорных спісаў.",
"custom_filtering_rules": "Карыстацкія правілы фільтрацыі",
"custom_filtering_rules": "Карыстальніцкія правілы фільтрацыі",
"encryption_settings": "Налады шыфравання",
"dhcp_settings": "Налады DHCP",
"upstream_dns": "Upstream DNS-серверы",
@@ -247,7 +247,7 @@
"loading_table_status": "Загрузка...",
"page_table_footer_text": "Старонка",
"rows_table_footer_text": "радкоў",
"updated_custom_filtering_toast": "Занесены змены ў карыстацкія правілы",
"updated_custom_filtering_toast": "Карыстальніцкія правілы паспяхова захаваны",
"rule_removed_from_custom_filtering_toast": "Карыстацкае правіла выдалена: {{rule}}",
"rule_added_to_custom_filtering_toast": "Карыстацкае правіла дададзена: {{rule}}",
"query_log_response_status": "Статус: {{value}}",
@@ -568,7 +568,7 @@
"check_desc": "Праверыць фільтрацыю імя хаста",
"check": "Праверыць",
"form_enter_host": "Увядзіце імя хаста",
"filtered_custom_rules": "Адфільтраваны з дапамогай карыстацкіх правілаў фільтрацыі",
"filtered_custom_rules": "Адфільтраваны з дапамогай карыстальніцкіх правіл фільтрацыі",
"choose_from_list": "Абраць са спіса",
"add_custom_list": "Дадаць свой спіс",
"host_whitelisted": "Хост занесены ў белы спіс",

View File

@@ -268,6 +268,8 @@
"blocking_mode_nxdomain": "NXDOMAIN: پاسخ با کُد NXDOMAIN",
"blocking_mode_null_ip": "Null IP: پاسخ با آدرس آی پی صفر(0.0.0.0 برای A; :: برای AAAA)",
"blocking_mode_custom_ip": "آی پی دستی: پاسخ با آدرس آی پی دستی تنظیم شده",
"theme_light": "پوسته روشن",
"theme_dark": "پوسته تیره",
"upstream_dns_client_desc": "اگر این فیلد را خالی نگه دارید، AdGuard Home از سرور پیکربندی شده در <0> تنظیماتDNS </0> استفاده می کند.",
"tracker_source": "منبع ردیاب",
"source_label": "منبع",
@@ -567,5 +569,6 @@
"use_saved_key": "از کلید ذخیره شده قبلی استفاده کنید",
"parental_control": "نظارت والدین",
"safe_browsing": "وب گردی اَمن",
"form_error_password_length": "رمزعبور باید حداقل {{value}} کاراکتر باشد."
"form_error_password_length": "رمزعبور باید حداقل {{value}} کاراکتر باشد.",
"protection_section_label": "حفاظت"
}

View File

@@ -86,7 +86,7 @@
"request_details": "Pyynnön tiedot",
"client_details": "Päätelaitteen tiedot",
"details": "Yksityiskohdat",
"back": "Takaisin",
"back": "Palaa takaisin",
"dashboard": "Tila",
"settings": "Asetukset",
"filters": "Suodattimet",
@@ -146,8 +146,8 @@
"no_servers_specified": "Palvelimia ei ole määritetty",
"general_settings": "Yleiset asetukset",
"dns_settings": "DNS-asetukset",
"dns_blocklists": "DNS-estolistat",
"dns_allowlists": "DNS-sallittujen listat",
"dns_blocklists": "DNS-estot",
"dns_allowlists": "DNS-sallinnat",
"dns_blocklists_desc": "AdGuard Home estää estolistalla olevat verkkotunnukset.",
"dns_allowlists_desc": "DNS-sallittujen listalla olevat verkkotunnukset sallitaan myös silloin, jos ne ovat jollain muulla estolistalla.",
"custom_filtering_rules": "Omat suodatussäännöt",
@@ -627,7 +627,7 @@
"cache_optimistic": "Optimistinen välimuisti",
"cache_optimistic_desc": "Pakota AdGuard Home vastaamaan välimuistista vaikka tiedot olisivat vanhentuneet. Pyri samalla myös päivittämään tiedot.",
"filter_category_general": "Yleiset",
"filter_category_security": "Turvallisuus",
"filter_category_security": "Tietoturva",
"filter_category_regional": "Alueelliset",
"filter_category_other": "Muut",
"filter_category_general_desc": "Listat, jotka estävät seurannan ja mainokset useimmilla laitteilla",

View File

@@ -282,6 +282,8 @@
"blocking_mode_null_ip": "Null IP: Svar med en 0-IP-adresse (0.0.0.0 for A; :: for AAAA)",
"blocking_mode_custom_ip": "Tilpasset IP: Svar med en manuelt valgt IP-adresse",
"theme_auto": "Auto",
"theme_light": "Lyst tema",
"theme_dark": "Mørkt tema",
"upstream_dns_client_desc": "Hvis dette feltet holdes tomt, vil AdGuard Home bruke tjenerne som er satt opp i <0>DNS-innstillingene</0>.",
"tracker_source": "Sporerkilde",
"source_label": "Kilde",

View File

@@ -222,7 +222,7 @@
"all_lists_up_to_date_toast": "Wszystkie listy są już aktualne",
"updated_upstream_dns_toast": "Serwery nadrzędne zostały pomyślnie zapisane",
"dns_test_ok_toast": "Określone serwery DNS działają poprawnie",
"dns_test_not_ok_toast": "Serwer \"{{key}}\": nie można go użyć, sprawdź, czy napisałeś go poprawnie",
"dns_test_not_ok_toast": "Serwer \"{{key}}\": nie może być użyte, sprawdź, czy zapisano go poprawnie",
"dns_test_warning_toast": "Upstream \"{{key}}\" nie odpowiada na zapytania testowe i może nie działać prawidłowo",
"unblock": "Odblokuj",
"block": "Zablokuj",
@@ -346,7 +346,7 @@
"install_devices_windows_list_2": "Przejdź do kategorii Sieć i Internet, a następnie do Centrum sieci i udostępniania.",
"install_devices_windows_list_3": "W lewym panelu kliknij \"Zmień ustawienia adaptera\".",
"install_devices_windows_list_4": "Kliknij prawym przyciskiem myszy aktywne połączenie i wybierz Właściwości.",
"install_devices_windows_list_5": "Znajdź na liście \"Protokół internetowy w wersji 4 (TCP/IPv4)\" (lub w przypadku IPv6 \"Protokół internetowy w wersji 6 (TCP/IPv6)\"), zaznacz go i ponownie kliknij na Właściwości.",
"install_devices_windows_list_5": "Znajdź na liście \"Protokół internetowy w wersji 4 (TCP/IPv4)\" (lub w przypadku IPv6 \"Protokół internetowy w wersji 6 (TCP/IPv6)\"), zaznacz go i ponownie kliknij Właściwości.",
"install_devices_windows_list_6": "Wybierz opcję \"Użyj następujących adresów serwerów DNS\" i wprowadź adresy serwerów AdGuard Home.",
"install_devices_macos_list_1": "Kliknij ikonę Apple i przejdź do Preferencje systemowe.",
"install_devices_macos_list_2": "Kliknij Sieć.",
@@ -396,7 +396,7 @@
"encryption_issuer": "Zgłaszający",
"encryption_hostnames": "Nazwy hostów",
"encryption_reset": "Czy na pewno chcesz zresetować ustawienia szyfrowania?",
"encryption_warning": "Uwaga!",
"encryption_warning": "Uwaga",
"topline_expiring_certificate": "Twój certyfikat SSL wkrótce wygaśnie. Zaktualizuj <0>Ustawienia szyfrowania</0>.",
"topline_expired_certificate": "Twój certyfikat SSL wygasł. Zaktualizuj <0>Ustawienia szyfrowania</0>.",
"form_error_port_range": "Wpisz numer portu z zakresu 80-65535",
@@ -542,7 +542,7 @@
"password_placeholder": "Wpisz hasło",
"sign_in": "Zaloguj się",
"sign_out": "Wyloguj się",
"forgot_password": "Zapomniałeś hasła?",
"forgot_password": "Zapomniano hasła?",
"forgot_password_desc": "Wykonaj <0>te kroki</0>, aby utworzyć nowe hasło do konta użytkownika.",
"location": "Lokalizacja",
"orgname": "Nazwa firmy",

View File

@@ -529,7 +529,7 @@
"ignore_domains": "Domínios ignorados (separados por nova linha)",
"ignore_domains_title": "Domínios ignorados",
"ignore_domains_desc_stats": "As consultas para esses domínios não são gravadas nas estatísticas",
"ignore_domains_desc_query": "As consultas para esses domínios não são gravadas no log de consulta",
"ignore_domains_desc_query": "As consultas para esses domínios não são gravadas no registro de consulta",
"interval_hours": "{{count}} hora",
"interval_hours_plural": "{{count}} horas",
"filters_configuration": "Configuração de filtros",

View File

@@ -529,7 +529,7 @@
"ignore_domains": "Domínios ignorados (separados por nova linha)",
"ignore_domains_title": "Domínios ignorados",
"ignore_domains_desc_stats": "As consultas para estes domínios não aparecem nas estatísticas",
"ignore_domains_desc_query": "As consultas para estes domínios nãoaparecem no registo de consultas",
"ignore_domains_desc_query": "As consultas para estes domínios não aparecem no registo de consultas",
"interval_hours": "{{count}} hora",
"interval_hours_plural": "{{count}} horas",
"filters_configuration": "Definição dos filtros",

View File

@@ -167,6 +167,7 @@
"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 списку",
@@ -290,6 +291,8 @@
"rate_limit": "Обмеження швидкості",
"edns_enable": "Увімкнути відправку EDNS Client Subnet",
"edns_cs_desc": "Додавати параметр EDNS Client Subnet (ECS) до запитів до upstream-серверів, а також записувати в журнал значення, що надсилаються клієнтами.",
"edns_use_custom_ip": "Використання користувацької IP-адреси для EDNS",
"edns_use_custom_ip_desc": "Дозволити використовувати користувацьку IP-адресу для EDNS",
"rate_limit_desc": "Кількість запитів в секунду, які може робити один клієнт. Встановлене значення «0» означатиме необмежену кількість.",
"blocking_ipv4_desc": "IP-адреса, яку потрібно видати для заблокованого A запиту",
"blocking_ipv6_desc": "IP-адреса, яку потрібно видати для заблокованого АААА запиту",
@@ -523,6 +526,10 @@
"statistics_retention_confirm": "Ви впевнені, що хочете змінити тривалість статистики? Якщо зменшити значення інтервалу, деякі дані будуть втрачені",
"statistics_cleared": "Статистику успішно очищено",
"statistics_enable": "Увімкнути статистику",
"ignore_domains": "Ігноровані домени (по одному на рядок)",
"ignore_domains_title": "Ігноровані домени",
"ignore_domains_desc_stats": "Запити для цих доменів в статистику не пишуться",
"ignore_domains_desc_query": "Запити для цих доменів не записуються до журналу запитів",
"interval_hours": "{{count}} година",
"interval_hours_plural": "{{count}} годин(и)",
"filters_configuration": "Конфігурація фільтрів",
@@ -643,5 +650,29 @@
"confirm_dns_cache_clear": "Ви впевнені, що бажаєте очистити кеш DNS?",
"cache_cleared": "Кеш DNS успішно очищено",
"clear_cache": "Очистити кеш",
"protection_section_label": "Захист"
"make_static": "Зробити статичним",
"theme_auto_desc": "Автоматична (на основі теми вашого пристрою)",
"theme_dark_desc": "Темна тема",
"theme_light_desc": "Світла тема",
"disable_for_seconds": "На {{count}} секунду",
"disable_for_seconds_plural": "На {{count}} секунд",
"disable_for_minutes": "На {{count}} хвилину",
"disable_for_minutes_plural": "На {{count}} хвилин",
"disable_for_hours": "На {{count}} годину",
"disable_for_hours_plural": "На {{count}} годин",
"disable_until_tomorrow": "До завтра",
"disable_notify_for_seconds": "Вимкнення захисту на {{count}} секунду",
"disable_notify_for_seconds_plural": "Вимкнення захисту на {{count}} секунд",
"disable_notify_for_minutes": "Вимкнення захисту на {{count}} хвилину",
"disable_notify_for_minutes_plural": "Вимкнення захисту на {{count}} хвилин",
"disable_notify_for_hours": "Вимкнення захисту на {{count}} годину",
"disable_notify_for_hours_plural": "Вимкнення захисту на {{count}} годин",
"disable_notify_until_tomorrow": "Відключення захисту до завтра",
"enable_protection_timer": "Захист буде ввімкнено о {{time}}",
"custom_retention_input": "Введіть час в годинах",
"custom_rotation_input": "Введіть час в годинах",
"protection_section_label": "Захист",
"log_and_stats_section_label": "Журнал запитів і статистика",
"ignore_query_log": "Ігнорувати цей клієнт у журналі запитів",
"ignore_statistics": "Ігноруйте цей клієнт в статистиці"
}

View File

@@ -100,6 +100,12 @@ export default {
"homepage": "https://github.com/DandelionSprout/adfilt",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_13.txt"
},
"POL_cert_polska_list_of_malicious_domains": {
"name": "POL: CERT Polska List of malicious domains",
"categoryId": "regional",
"homepage": "https://cert.pl/posts/2020/03/ostrzezenia_phishing/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_41.txt"
},
"POL_polish_filters_for_pi_hole": {
"name": "POL: Polish filters for Pi-hole",
"categoryId": "regional",
@@ -118,6 +124,12 @@ export default {
"homepage": "https://github.com/bkrucarci/turk-adlist",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_26.txt"
},
"TUR_turkish_ad_hosts": {
"name": "TUR: Turkish Ad Hosts",
"categoryId": "regional",
"homepage": "https://github.com/symbuzzer/Turkish-Ad-Hosts",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_40.txt"
},
"VNM_abpvn": {
"name": "VNM: ABPVN List",
"categoryId": "regional",
@@ -214,6 +226,12 @@ export default {
"homepage": "https://github.com/durablenapkin/scamblocklist",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_10.txt"
},
"shadowwhisperers_malware_list": {
"name": "ShadowWhisperer's Malware List",
"categoryId": "security",
"homepage": "https://github.com/ShadowWhisperer/BlockLists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_42.txt"
},
"staklerware_indicators_list": {
"name": "Stalkerware Indicators List",
"categoryId": "security",

View File

@@ -1,5 +1,5 @@
{
"timeUpdated": "2023-04-06T10:46:09.881Z",
"timeUpdated": "2023-06-01T00:12:12.660Z",
"categories": {
"0": "audio_video_player",
"1": "comments",
@@ -19526,6 +19526,13 @@
"companyId": "qualcomm",
"source": "AdGuard"
},
"qualcomm_location_service": {
"name": "Qualcomm Location Service",
"categoryId": 15,
"url": "https://www.qualcomm.com/site/privacy/services",
"companyId": "qualcomm",
"source": "AdGuard"
},
"recaptcha": {
"name": "reCAPTCHA",
"categoryId": 8,
@@ -19533,6 +19540,55 @@
"companyId": "google",
"source": "AdGuard"
},
"samsung": {
"name": "Samsung",
"categoryId": 8,
"url": "https://www.samsung.com/",
"companyId": "samsung",
"source": "AdGuard"
},
"samsungads": {
"name": "Samsung Ads",
"categoryId": 4,
"url": "https://www.samsung.com/business/samsungads/",
"companyId": "samsung",
"source": "AdGuard"
},
"samsungapps": {
"name": "Samsung Apps",
"categoryId": 101,
"url": "https://www.samsung.com/au/apps/",
"companyId": "samsung",
"source": "AdGuard"
},
"samsungmobile": {
"name": "Samsung Mobile",
"categoryId": 101,
"url": "https://www.samsung.com/mobile/",
"companyId": "samsung",
"source": "AdGuard"
},
"samsungpush": {
"name": "Samsung Push",
"categoryId": 8,
"url": null,
"companyId": "samsung",
"source": "AdGuard"
},
"samsungsds": {
"name": "Samsung SDS",
"categoryId": 10,
"url": "https://www.samsungsds.com/",
"companyId": "samsung",
"source": "AdGuard"
},
"samsungtv": {
"name": "Samsung TV",
"categoryId": 15,
"url": "https://www.samsung.com/au/tvs/",
"companyId": "samsung",
"source": "AdGuard"
},
"sectigo": {
"name": "Sectigo Limited",
"categoryId": 5,
@@ -19589,6 +19645,13 @@
"companyId": "telstra",
"source": "AdGuard"
},
"ubuntu": {
"name": "Ubuntu",
"categoryId": 8,
"url": "https://ubuntu.com/",
"companyId": "ubuntu",
"source": "AdGuard"
},
"unity_ads": {
"name": "Unity Ads",
"categoryId": 4,
@@ -19651,6 +19714,13 @@
"url": "https://www.3gpp.org/",
"companyId": "3gpp",
"source": "AdGuard"
},
"7plus": {
"name": "7plus",
"categoryId": 0,
"url": "https://7plus.com.au/",
"companyId": "7plus",
"source": "AdGuard"
}
},
"trackerDomains": {
@@ -19843,8 +19913,8 @@
"adfreestyle.pl": "adfreestyle",
"adfront.org": "adfront",
"adfrontiers.com": "adfrontiers",
"adgear.com": "adgear",
"adgrx.com": "adgear",
"adgear.com": "samsungads",
"adgrx.com": "samsungads",
"adgebra.co.in": "adgebra",
"adgenie.co.uk": "adgenie",
"ad.adgile.com": "adgile",
@@ -24056,6 +24126,10 @@
"safebrowsing.g.applimg.com": "apple",
"applvn.com": "applovin",
"applovin.com": "applovin",
"bitbucket.org": "atlassian.net",
"jira.com": "atlassian.net",
"ss-inf.net": "atlassian.net",
"stspg-customer.com": "statuspage.io",
"blob.core.windows.net": "azure_blob_storage",
"azure.com": "azure",
"trafficmanager.net": "azure",
@@ -24063,6 +24137,21 @@
"mobileapptracking.com": "branch",
"bttn.io": "button",
"cloudflare-dns.com": "cloudflare",
"cloudflare-dm-cmpimg.com": "cloudflare",
"cloudflare-ipfs.com": "cloudflare",
"cloudflare-quic.com": "cloudflare",
"cloudflare-terms-of-service-abuse.com": "cloudflare",
"cloudflare.tv": "cloudflare",
"cloudflareaccess.com": "cloudflare",
"cloudflareclient.com": "cloudflare",
"cloudflareinsights.com": "cloudflare",
"cloudflareok.com": "cloudflare",
"cloudflareportal.com": "cloudflare",
"cloudflareresolve.com": "cloudflare",
"cloudflaressl.com": "cloudflare",
"cloudflarestatus.com": "cloudflare",
"pacloudflare.com": "cloudflare",
"sn-cloudflare.com": "cloudflare",
"crashlytics.com": "crashlytics",
"phicdn.net": "digicert_trust_seal",
"domain.glass": "domainglass",
@@ -24092,6 +24181,9 @@
"qy.net": "iqiyi",
"iqiyi.com": "iqiyi",
"iq.com": "iqiyi",
"ironsrc.com": "ironsource",
"ironsrc.net": "ironsource",
"supersonicads.com": "ironsource",
"karambasecurity.com": "karambasecurity",
"kik.com": "kik",
"apikik.com": "kik",
@@ -24121,6 +24213,23 @@
"mozilla.com": "mozilla",
"mozilla.net": "mozilla",
"mozilla.org": "mozilla",
"flxvpn.net": "netflix",
"netflix.ca": "netflix",
"netflix.com.au": "netflix",
"netflix.net": "netflix",
"netflixdnstest1.com": "netflix",
"netflixdnstest10.com": "netflix",
"netflixdnstest2.com": "netflix",
"netflixdnstest3.com": "netflix",
"netflixdnstest4.com": "netflix",
"netflixdnstest5.com": "netflix",
"netflixdnstest6.com": "netflix",
"netflixdnstest7.com": "netflix",
"netflixdnstest8.com": "netflix",
"netflixdnstest9.com": "netflix",
"netflixinvestor.com": "netflix",
"netflixstudios.com": "netflix",
"netflixtechblog.com": "netflix",
"nflximg.com": "netflix",
"netify.ai": "netify",
"nab.com": "nab",
@@ -24144,9 +24253,69 @@
"oztam.com.au": "oztam",
"plex.tv": "plex",
"plex.direct": "plex",
"xtracloud.net": "qualcomm",
"qualcomm.com": "qualcomm",
"gpsonextra.net": "qualcomm_location_service",
"izatcloud.net": "qualcomm_location_service",
"xtracloud.net": "qualcomm_location_service",
"recaptcha.net": "recaptcha",
"samsungacr.com": "samsungads",
"samsungadhub.com": "samsungads",
"samsungads.com": "samsungads",
"samsungtifa.com": "samsungads",
"game-mode.net": "samsung",
"gos-gsp.io": "samsung",
"lldns.net": "samsung",
"pavv.co.kr": "samsung",
"remotesamsung.com": "samsung",
"samsung-gamelauncher.com": "samsung",
"samsung.co.kr": "samsung",
"samsung.com": "samsung",
"samsung.com.cn": "samsung",
"samsungcloud.com": "samsung",
"samsungcloudcdn.com": "samsung",
"samsungcloudprint.com": "samsung",
"samsungcloudsolution.com": "samsung",
"samsungcloudsolution.net": "samsung",
"samsungelectronics.com": "samsung",
"samsunghealth.com": "samsung",
"samsungiotcloud.com": "samsung",
"samsungknox.com": "samsung",
"samsungnyc.com": "samsung",
"samsungosp.com": "samsung",
"samsungotn.net": "samsung",
"samsungpositioning.com": "samsung",
"samsungqbe.com": "samsung",
"samsungrm.net": "samsung",
"samsungrs.com": "samsung",
"samsungsemi.com": "samsung",
"samsungsetup.com": "samsung",
"samsungusa.com": "samsung",
"secb2b.com": "samsung",
"smartthings.com": "samsung",
"ospserver.net": "samsungmobile",
"samsungdms.net": "samsungmobile",
"samsungmax.com": "samsungmobile",
"samsungmobile.com": "samsungmobile",
"secmobilesvc.com": "samsungmobile",
"internetat.tv": "samsungtv",
"samsungcloud.tv": "samsungtv",
"samsungsds.com": "samsungsds",
"push.samsungosp.com": "samsungpush",
"pushmessage.samsung.com": "samsungpush",
"scs.samsungqbe.com": "samsungpush",
"ssp.samsung.com": "samsungpush",
"aibixby.com": "samsungapps",
"findmymobile.samsung.com": "samsungapps",
"samsapps.cust.lldns.net": "samsungapps",
"samsung-omc.com": "samsungapps",
"samsungapps.com": "samsungapps",
"samsungdiroute.net": "samsungapps",
"samsungdive.com": "samsungapps",
"samsungdm.com": "samsungapps",
"samsungdmroute.com": "samsungapps",
"samsungmdec.com": "samsungapps",
"samsungvisioncloud.com": "samsungapps",
"sbixby.com": "samsungapps",
"sectigo.com": "sectigo",
"showrss.info": "showrss",
"similarweb.io": "similarweb",
@@ -24171,6 +24340,13 @@
"telstra.com.au": "telstra",
"telstra.com": "telstra",
"usertrust.com": "trustlogo",
"canonical.com": "ubuntu",
"launchpad.net": "ubuntu",
"launchpadcontent.net": "ubuntu",
"snapcraft.io": "ubuntu",
"snapcraftcontent.com": "ubuntu",
"ubuntu.com": "ubuntu",
"ubuntucompanyservices.co.za": "ubuntu",
"unityads.unity3d.com": "unity_ads",
"exp-tas.com": "vscode",
"vscode-unpkg.net": "vscode",
@@ -24190,6 +24366,7 @@
"yandex.kz": "yandex",
"appmetrica.yandex.com": "yandex_appmetrica",
"3gppnetwork.org": "3gpp",
"3gpp.org": "3gpp"
"3gpp.org": "3gpp",
"swm.digital": "7plus"
}
}

38
go.mod
View File

@@ -3,11 +3,12 @@ module github.com/AdguardTeam/AdGuardHome
go 1.19
require (
github.com/AdguardTeam/dnsproxy v0.49.1
github.com/AdguardTeam/golibs v0.13.2
github.com/AdguardTeam/dnsproxy v0.50.2
github.com/AdguardTeam/golibs v0.13.3
github.com/AdguardTeam/urlfilter v0.16.1
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.2.7
github.com/bluele/gcache v0.0.2
github.com/digineo/go-ipset/v2 v2.2.1
github.com/dimfeld/httptreemux/v5 v5.5.0
github.com/fsnotify/fsnotify v1.6.0
@@ -16,24 +17,24 @@ require (
github.com/google/gopacket v1.1.19
github.com/google/renameio v1.0.1
github.com/google/uuid v1.3.0
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb
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
github.com/mdlayher/netlink v1.7.1
github.com/mdlayher/packet v1.1.1
github.com/mdlayher/netlink v1.7.2
github.com/mdlayher/packet v1.1.2
// TODO(a.garipov): This package is deprecated; find a new one or use our
// own code for that. Perhaps, use gopacket.
github.com/mdlayher/raw v0.1.0
github.com/miekg/dns v1.1.53
github.com/quic-go/quic-go v0.33.0
github.com/stretchr/testify v1.8.2
github.com/miekg/dns v1.1.54
github.com/quic-go/quic-go v0.35.1
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.8.0
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
golang.org/x/net v0.9.0
golang.org/x/sys v0.7.0
golang.org/x/crypto v0.10.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/net v0.11.0
golang.org/x/sys v0.9.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.0
@@ -44,13 +45,12 @@ require (
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
github.com/ameshkov/dnsstamps v1.0.3 // indirect
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
github.com/bluele/gcache v0.0.2 // indirect
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-20230406165453-00490a63f317 // indirect
github.com/mdlayher/socket v0.4.0 // indirect
github.com/onsi/ginkgo/v2 v2.9.2 // indirect
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/onsi/ginkgo/v2 v2.10.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pkg/errors v0.9.1 // indirect
@@ -60,7 +60,7 @@ require (
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.10.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.8.0 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/tools v0.9.3 // indirect
)

119
go.sum
View File

@@ -1,9 +1,9 @@
github.com/AdguardTeam/dnsproxy v0.49.1 h1:JpStBK05uCgA3ldleaNLRmIwE9V7vRg7/kVJQSdnQYg=
github.com/AdguardTeam/dnsproxy v0.49.1/go.mod h1:Y7g7jRTd/u7+KJ/QvnGI2PCE8vnisp6EsW47/Sz0DZw=
github.com/AdguardTeam/dnsproxy v0.50.2 h1:p1471SsMZ6SMo7T51Olw4aNluahvMwSLMorwxYV18ts=
github.com/AdguardTeam/dnsproxy v0.50.2/go.mod h1:CQhZTkqC8X0ID6glrtyaxgqRRdiYfn1gJulC1cZ5Dn8=
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.2 h1:BPASsyQKmb+b8VnvsNOHp7bKfcZl9Z+Z2UhPjOiupSc=
github.com/AdguardTeam/golibs v0.13.2/go.mod h1:7ylQLv2Lqsc3UW3jHoITynYk6Y1tYtgEMkR09ppfsN8=
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/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=
@@ -31,10 +31,9 @@ github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1M
github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU=
github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ=
github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
@@ -45,72 +44,54 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.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-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ=
github.com/google/pprof v0.0.0-20230406165453-00490a63f317/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
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/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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 h1:Z72DOke2yOK0Ms4Z2LK1E1OrRJXOxSj5DllTz2FYTRg=
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8/go.mod h1:m5WMe03WCvWcXjRnhvaAbAAXdCnu20J5P+mmH44ZzpE=
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb h1:6fDKEAXwe3rsfS4khW3EZ8kEqmSiV9szhMPcDrD+Y7Q=
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb/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=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og=
github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg=
github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
github.com/mdlayher/packet v1.1.1 h1:7Fv4OEMYqPl7//uBm04VgPpnSNi8fbBZznppgh6WMr8=
github.com/mdlayher/packet v1.1.1/go.mod h1:DRvYY5mH4M4lUqAnMg04E60U4fjUKMZ/4g2cHElZkKo=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
github.com/mdlayher/raw v0.1.0 h1:K4PFMVy+AFsp0Zdlrts7yNhxc/uXoPVHi9RzRvtZF2Y=
github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5s9Sg=
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
@@ -127,24 +108,18 @@ 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.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA=
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/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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU=
github.com/ti-mo/netfilter v0.5.0 h1:MZmsUw5bFRecOb0AeyjOPxTHg4UxYzyEs0Ek/6Lxoy8=
github.com/ti-mo/netfilter v0.5.0/go.mod h1:nt+8B9hx/QpqHr7Hazq+2qMCCA8u2OTkyc/7+U9ARz8=
@@ -152,7 +127,6 @@ github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E=
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@@ -160,11 +134,10 @@ 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.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/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-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
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=
@@ -172,40 +145,25 @@ golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.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-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
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/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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -219,23 +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.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/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/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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
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.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
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

@@ -304,7 +304,7 @@ func tryConn6(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error)
if !(response.Type() == dhcpv6.MessageTypeAdvertise &&
msg.TransactionID == req.TransactionID &&
rcid != nil &&
cid.Equal(*rcid)) {
cid.Equal(rcid)) {
log.Debug("dhcpv6: received message from server doesn't match our request")

View File

@@ -1,17 +0,0 @@
//go:build windows
package aghnet
import (
"net"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
)
// listenPacketReusable announces on the local network address additionally
// configuring the socket to have a reusable binding.
func listenPacketReusable(_, _, _ string) (c net.PacketConn, err error) {
// TODO(e.burkov): Check if we are able to control sockets on Windows
// in the same way as on Unix.
return nil, aghos.Unsupported("listening packet reusable")
}

View File

@@ -1,46 +1,60 @@
# DHCP server
# Testing DHCP Server
Contents:
* [Test setup with Virtual Box](#vbox)
* [Test setup with Virtual Box](#vbox)
* [Quick test with DHCPTest](#dhcptest)
<a id="vbox"></a>
## Test setup with Virtual Box
## <a href="#vbox" id="vbox" name="vbox">Test setup with Virtual Box</a>
To set up a test environment for DHCP server you need:
### Prerequisites
* Linux host machine
* Virtual Box
* Virtual machine (guest OS doesn't matter)
To set up a test environment for DHCP server you will need:
### Configure client
* Linux AG Home host machine (Virtual).
* Virtual Box.
* Virtual machine (guest OS doesn't matter).
1. Install Virtual Box and run the following command to create a Host-Only network:
### Configure Virtual Box
$ VBoxManage hostonlyif create
1. Install Virtual Box and run the following command to create a Host-Only
network:
You can check its status by `ip a` command.
```sh
$ VBoxManage hostonlyif create
```
You can check its status by `ip a` command.
You can also set up Host-Only network using Virtual Box menu:
You can also set up Host-Only network using Virtual Box menu:
```
File -> Host Network Manager...
```
File -> Host Network Manager...
2. Create your virtual machine and set up its network:
2. Create your virtual machine and set up its network:
```
VM Settings -> Network -> Host-only Adapter
```
VM Settings -> Network -> Host-only Adapter
3. Start your VM, install an OS. Configure your network interface to use
DHCP and the OS should ask for a IP address from our DHCP server.
3. Start your VM, install an OS. Configure your network interface to use DHCP and the OS should ask for a IP address from our DHCP server.
4. To see the current IP addresses on client OS you can use `ip a` command on
Linux or `ipconfig` on Windows.
4. To see the current IP address on client OS you can use `ip a` command on Linux or `ipconfig` on Windows.
5. To force the client OS to request an IP from DHCP server again, you can
use `dhclient` on Linux or `ipconfig /release` on Windows.
5. To force the client OS to request an IP from DHCP server again, you can use `dhclient` on Linux or `ipconfig /release` on Windows.
### Configure server
### Configure server
1. Edit server configuration file `AdGuardHome.yaml`, for example:
1. Edit server configuration file 'AdGuardHome.yaml', for example:
dhcp:
```yaml
dhcp:
enabled: true
interface_name: vboxnet0
local_domain_name: lan
dhcpv4:
gateway_ip: 192.168.56.1
subnet_mask: 255.255.255.0
@@ -54,11 +68,29 @@ To set up a test environment for DHCP server you need:
lease_duration: 86400
ra_slaac_only: false
ra_allow_slaac: false
```
2. Start the server
2. Start the server
./AdGuardHome
```sh
./AdGuardHome -v
```
There should be a message in log which shows that DHCP server is ready:
There should be a message in log which shows that DHCP server is ready:
[info] DHCP: listening on 0.0.0.0:67
```
[info] DHCP: listening on 0.0.0.0:67
```
## <a href="#dhcptest" id="dhcptest" name="dhcptest">Quick test with DHCPTest utility</a>
### Prerequisites
* [DHCP test utility][dhcptest-gh].
### Quick test
The DHCP server could be tested for DISCOVER-OFFER packets with in
interactive mode.
[dhcptest-gh]: https://github.com/CyberShadow/dhcptest

View File

@@ -1,4 +1,4 @@
//go:build darwin
//go:build darwin || freebsd || openbsd
package dhcpd

View File

@@ -1,4 +1,4 @@
//go:build darwin
//go:build darwin || freebsd || openbsd
package dhcpd

View File

@@ -1,4 +1,4 @@
//go:build freebsd || linux || openbsd
//go:build linux
package dhcpd

View File

@@ -1,4 +1,4 @@
//go:build freebsd || linux || openbsd
//go:build linux
package dhcpd

View File

@@ -239,36 +239,16 @@ func Create(conf *ServerConfig) (s *server, err error) {
// [aghhttp.RegisterFunc].
s.registerHandlers()
v4conf := conf.Conf4
v4conf.InterfaceName = s.conf.InterfaceName
v4conf.notify = s.onNotify
v4conf.Enabled = s.conf.Enabled && v4conf.RangeStart.IsValid()
s.srv4, err = v4Create(&v4conf)
v4Enabled, v6Enabled, err := s.setServers(conf)
if err != nil {
if v4conf.Enabled {
return nil, fmt.Errorf("creating dhcpv4 srv: %w", err)
}
log.Debug("dhcpd: warning: creating dhcpv4 srv: %s", err)
}
v6conf := conf.Conf6
v6conf.Enabled = s.conf.Enabled
if len(v6conf.RangeStart) == 0 {
v6conf.Enabled = false
}
v6conf.InterfaceName = s.conf.InterfaceName
v6conf.notify = s.onNotify
s.srv6, err = v6Create(v6conf)
if err != nil {
return nil, fmt.Errorf("creating dhcpv6 srv: %w", err)
// Don't wrap the error, because it's informative enough as is.
return nil, err
}
s.conf.Conf4 = conf.Conf4
s.conf.Conf6 = conf.Conf6
if s.conf.Enabled && !v4conf.Enabled && !v6conf.Enabled {
if s.conf.Enabled && !v4Enabled && !v6Enabled {
return nil, fmt.Errorf("neither dhcpv4 nor dhcpv6 srv is configured")
}
@@ -289,6 +269,39 @@ func Create(conf *ServerConfig) (s *server, err error) {
return s, nil
}
// setServers updates DHCPv4 and DHCPv6 servers created from the provided
// configuration conf.
func (s *server) setServers(conf *ServerConfig) (v4Enabled, v6Enabled bool, err error) {
v4conf := conf.Conf4
v4conf.InterfaceName = s.conf.InterfaceName
v4conf.notify = s.onNotify
v4conf.Enabled = s.conf.Enabled && v4conf.RangeStart.IsValid()
s.srv4, err = v4Create(&v4conf)
if err != nil {
if v4conf.Enabled {
return true, false, fmt.Errorf("creating dhcpv4 srv: %w", err)
}
log.Debug("dhcpd: warning: creating dhcpv4 srv: %s", err)
}
v6conf := conf.Conf6
v6conf.InterfaceName = s.conf.InterfaceName
v6conf.notify = s.onNotify
v6conf.Enabled = s.conf.Enabled
if len(v6conf.RangeStart) == 0 {
v6conf.Enabled = false
}
s.srv6, err = v6Create(v6conf)
if err != nil {
return v4conf.Enabled, v6conf.Enabled, fmt.Errorf("creating dhcpv6 srv: %w", err)
}
return v4conf.Enabled, v6conf.Enabled, nil
}
// Enabled returns true when the server is enabled.
func (s *server) Enabled() (ok bool) {
return s.conf.Enabled

View File

@@ -16,6 +16,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
)
type v4ServerConfJSON struct {
@@ -263,6 +264,28 @@ func (s *server) handleDHCPSetConfigV6(
return srv6, enabled, err
}
// createServers returns DHCPv4 and DHCPv6 servers created from the provided
// configuration conf.
func (s *server) createServers(conf *dhcpServerConfigJSON) (srv4, srv6 DHCPServer, err error) {
srv4, v4Enabled, err := s.handleDHCPSetConfigV4(conf)
if err != nil {
return nil, nil, fmt.Errorf("bad dhcpv4 configuration: %s", err)
}
srv6, v6Enabled, err := s.handleDHCPSetConfigV6(conf)
if err != nil {
return nil, nil, fmt.Errorf("bad dhcpv6 configuration: %s", err)
}
if conf.Enabled == aghalg.NBTrue && !v4Enabled && !v6Enabled {
return nil, nil, fmt.Errorf("dhcpv4 or dhcpv6 configuration must be complete")
}
return srv4, srv6, nil
}
// handleDHCPSetConfig is the handler for the POST /control/dhcp/set_config
// HTTP API.
func (s *server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
conf := &dhcpServerConfigJSON{}
conf.Enabled = aghalg.BoolToNullBool(s.conf.Enabled)
@@ -275,22 +298,9 @@ func (s *server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
return
}
srv4, v4Enabled, err := s.handleDHCPSetConfigV4(conf)
srv4, srv6, err := s.createServers(conf)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "bad dhcpv4 configuration: %s", err)
return
}
srv6, v6Enabled, err := s.handleDHCPSetConfigV6(conf)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "bad dhcpv6 configuration: %s", err)
return
}
if conf.Enabled == aghalg.NBTrue && !v4Enabled && !v6Enabled {
aghhttp.Error(r, w, http.StatusBadRequest, "dhcpv4 or dhcpv6 configuration must be complete")
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
@@ -350,10 +360,10 @@ type netInterfaceJSON struct {
Addrs6 []netip.Addr `json:"ipv6_addresses"`
}
// handleDHCPInterfaces is the handler for the GET /control/dhcp/interfaces HTTP
// API.
// handleDHCPInterfaces is the handler for the GET /control/dhcp/interfaces
// HTTP API.
func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
resp := map[string]netInterfaceJSON{}
resp := map[string]*netInterfaceJSON{}
ifaces, err := net.Interfaces()
if err != nil {
@@ -364,68 +374,23 @@ func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
for _, iface := range ifaces {
if iface.Flags&net.FlagLoopback != 0 {
// it's a loopback, skip it
continue
}
if iface.Flags&net.FlagBroadcast == 0 {
// this interface doesn't support broadcast, skip it
// It's a loopback, skip it.
continue
}
var addrs []net.Addr
addrs, err = iface.Addrs()
if err != nil {
aghhttp.Error(
r,
w,
http.StatusInternalServerError,
"Failed to get addresses for interface %s: %s",
iface.Name,
err,
)
if iface.Flags&net.FlagBroadcast == 0 {
// This interface doesn't support broadcast, skip it.
continue
}
jsonIface, iErr := newNetInterfaceJSON(iface)
if iErr != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", iErr)
return
}
jsonIface := netInterfaceJSON{
Name: iface.Name,
HardwareAddr: iface.HardwareAddr.String(),
}
if iface.Flags != 0 {
jsonIface.Flags = iface.Flags.String()
}
// we don't want link-local addresses in json, so skip them
for _, addr := range addrs {
ipnet, ok := addr.(*net.IPNet)
if !ok {
// not an IPNet, should not happen
aghhttp.Error(
r,
w,
http.StatusInternalServerError,
"got iface.Addrs() element %[1]s that is not net.IPNet, it is %[1]T",
addr)
return
}
// ignore link-local
//
// TODO(e.burkov): Try to listen DHCP on LLA as well.
if ipnet.IP.IsLinkLocalUnicast() {
continue
}
if ip4 := ipnet.IP.To4(); ip4 != nil {
addr := netip.AddrFrom4(*(*[4]byte)(ip4))
jsonIface.Addrs4 = append(jsonIface.Addrs4, addr)
} else {
addr := netip.AddrFrom16(*(*[16]byte)(ipnet.IP))
jsonIface.Addrs6 = append(jsonIface.Addrs6, addr)
}
}
if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 {
jsonIface.GatewayIP = aghnet.GatewayIP(iface.Name)
if jsonIface != nil {
resp[iface.Name] = jsonIface
}
}
@@ -433,6 +398,64 @@ func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
// newNetInterfaceJSON creates a JSON object from a [net.Interface] iface.
func newNetInterfaceJSON(iface net.Interface) (out *netInterfaceJSON, err error) {
addrs, err := iface.Addrs()
if err != nil {
return nil, fmt.Errorf(
"failed to get addresses for interface %s: %s",
iface.Name,
err,
)
}
out = &netInterfaceJSON{
Name: iface.Name,
HardwareAddr: iface.HardwareAddr.String(),
}
if iface.Flags != 0 {
out.Flags = iface.Flags.String()
}
// We don't want link-local addresses in JSON, so skip them.
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
// Not an IPNet, should not happen.
return nil, fmt.Errorf("got iface.Addrs() element %[1]s that is not"+
" net.IPNet, it is %[1]T", addr)
}
// Ignore link-local.
//
// TODO(e.burkov): Try to listen DHCP on LLA as well.
if ipNet.IP.IsLinkLocalUnicast() {
continue
}
vAddr, iErr := netutil.IPToAddrNoMapped(ipNet.IP)
if iErr != nil {
// Not an IPNet, should not happen.
return nil, fmt.Errorf("failed to convert IP address %[1]s: %w", addr, iErr)
}
if vAddr.Is4() {
out.Addrs4 = append(out.Addrs4, vAddr)
} else {
out.Addrs6 = append(out.Addrs6, vAddr)
}
}
if len(out.Addrs4)+len(out.Addrs6) == 0 {
return nil, nil
}
out.GatewayIP = aghnet.GatewayIP(iface.Name)
return out, nil
}
// dhcpSearchOtherResult contains information about other DHCP server for
// specific network interface.
type dhcpSearchOtherResult struct {

View File

@@ -1,15 +0,0 @@
//go:build windows
package dhcpd
import (
"net"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"golang.org/x/net/ipv4"
)
// Create a socket for receiving broadcast packets
func newBroadcastPacketConn(_ net.IP, _ int, _ string) (*ipv4.PacketConn, error) {
return nil, aghos.Unsupported("newBroadcastPacketConn")
}

View File

@@ -7,6 +7,7 @@ import (
"sync/atomic"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"golang.org/x/net/icmp"
@@ -195,7 +196,7 @@ func createICMPv6RAPacket(params icmpv6RA) (data []byte, err error) {
return data, nil
}
// Init - initialize RA module
// Init initializes RA module.
func (ra *raCtx) Init() (err error) {
ra.stop.Store(0)
ra.conn = nil
@@ -203,8 +204,7 @@ func (ra *raCtx) Init() (err error) {
return nil
}
log.Debug("dhcpv6 ra: source IP address: %s DNS IP address: %s",
ra.ipAddr, ra.dnsIPAddr)
log.Debug("dhcpv6 ra: source IP address: %s DNS IP address: %s", ra.ipAddr, ra.dnsIPAddr)
params := icmpv6RA{
managedAddressConfiguration: !ra.raSLAACOnly,
@@ -223,18 +223,15 @@ func (ra *raCtx) Init() (err error) {
return fmt.Errorf("creating packet: %w", err)
}
success := false
ipAndScope := ra.ipAddr.String() + "%" + ra.ifaceName
ra.conn, err = icmp.ListenPacket("ip6:ipv6-icmp", ipAndScope)
if err != nil {
return fmt.Errorf("dhcpv6 ra: icmp.ListenPacket: %w", err)
}
defer func() {
if !success {
derr := ra.Close()
if derr != nil {
log.Error("closing context: %s", derr)
}
if err != nil {
err = errors.WithDeferred(err, ra.Close())
}
}()
@@ -269,7 +266,6 @@ func (ra *raCtx) Init() (err error) {
log.Debug("dhcpv6 ra: loop exit")
}()
success = true
return nil
}

View File

@@ -342,8 +342,8 @@ func (s *v4Server) rmLease(lease *Lease) (err error) {
// server to be configured and it's not.
const ErrUnconfigured errors.Error = "server is unconfigured"
// AddStaticLease implements the DHCPServer interface for *v4Server. It is safe
// for concurrent use.
// AddStaticLease implements the DHCPServer interface for *v4Server. It is
// safe for concurrent use.
func (s *v4Server) AddStaticLease(l *Lease) (err error) {
defer func() { err = errors.Annotate(err, "dhcpv4: adding static lease: %w") }()
@@ -354,21 +354,23 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
l.IP = l.IP.Unmap()
if !l.IP.Is4() {
return fmt.Errorf("invalid ip %q, only ipv4 is supported", l.IP)
return fmt.Errorf("invalid IP %q: only IPv4 is supported", l.IP)
} else if gwIP := s.conf.GatewayIP; gwIP == l.IP {
return fmt.Errorf("can't assign the gateway IP %s to the lease", gwIP)
return fmt.Errorf("can't assign the gateway IP %q to the lease", gwIP)
}
l.IsStatic = true
err = netutil.ValidateMAC(l.HWAddr)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
if hostname := l.Hostname; hostname != "" {
hostname, err = normalizeHostname(hostname)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
@@ -386,32 +388,9 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
l.Hostname = hostname
}
// Perform the following actions in an anonymous function to make sure
// that the lock gets unlocked before the notification step.
func() {
s.leasesLock.Lock()
defer s.leasesLock.Unlock()
err = s.rmDynamicLease(l)
if err != nil {
err = fmt.Errorf(
"removing dynamic leases for %s (%s): %w",
l.IP,
l.HWAddr,
err,
)
return
}
err = s.addLease(l)
if err != nil {
err = fmt.Errorf("adding static lease for %s (%s): %w", l.IP, l.HWAddr, err)
return
}
}()
err = s.updateStaticLease(l)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
@@ -421,6 +400,25 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
return nil
}
// updateStaticLease safe removes dynamic lease with the same properties and
// then adds a static lease l.
func (s *v4Server) updateStaticLease(l *Lease) (err error) {
s.leasesLock.Lock()
defer s.leasesLock.Unlock()
err = s.rmDynamicLease(l)
if err != nil {
return fmt.Errorf("removing dynamic leases for %s (%s): %w", l.IP, l.HWAddr, err)
}
err = s.addLease(l)
if err != nil {
return fmt.Errorf("adding static lease for %s (%s): %w", l.IP, l.HWAddr, err)
}
return nil
}
// RemoveStaticLease removes a static lease. It is safe for concurrent use.
func (s *v4Server) RemoveStaticLease(l *Lease) (err error) {
defer func() { err = errors.Annotate(err, "dhcpv4: %w") }()
@@ -894,24 +892,9 @@ func (s *v4Server) handleDecline(req, resp *dhcpv4.DHCPv4) (err error) {
reqIP = req.ClientIPAddr
}
netIP, ok := netip.AddrFromSlice(reqIP)
if !ok {
log.Info("dhcpv4: invalid IP: %s", reqIP)
return nil
}
var oldLease *Lease
for _, l := range s.leases {
if bytes.Equal(l.HWAddr, mac) && l.IP == netIP {
oldLease = l
break
}
}
oldLease := s.findLeaseForIP(reqIP, mac)
if oldLease == nil {
log.Info("dhcpv4: lease with ip %s for %s not found", reqIP, mac)
log.Info("dhcpv4: lease with IP %s for %s not found", reqIP, mac)
return nil
}
@@ -925,7 +908,7 @@ func (s *v4Server) handleDecline(req, resp *dhcpv4.DHCPv4) (err error) {
if err != nil {
return fmt.Errorf("allocating new lease for %s: %w", mac, err)
} else if newLease == nil {
log.Info("dhcpv4: allocating new lease for %s: no more ip addresses", mac)
log.Info("dhcpv4: allocating new lease for %s: no more IP addresses", mac)
resp.YourIPAddr = make([]byte, 4)
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
@@ -941,15 +924,32 @@ func (s *v4Server) handleDecline(req, resp *dhcpv4.DHCPv4) (err error) {
return fmt.Errorf("adding new lease for %s: %w", mac, err)
}
log.Info("dhcpv4: changed ip from %s to %s for %s", reqIP, newLease.IP, mac)
resp.YourIPAddr = net.IP(newLease.IP.AsSlice())
log.Info("dhcpv4: changed IP from %s to %s for %s", reqIP, newLease.IP, mac)
resp.YourIPAddr = newLease.IP.AsSlice()
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
return nil
}
// findLeaseForIP returns a lease for provided ip and mac.
func (s *v4Server) findLeaseForIP(ip net.IP, mac net.HardwareAddr) (l *Lease) {
netIP, ok := netip.AddrFromSlice(ip)
if !ok {
log.Info("dhcpv4: invalid IP: %s", ip)
return nil
}
for _, il := range s.leases {
if bytes.Equal(il.HWAddr, mac) && il.IP == netIP {
return il
}
}
return nil
}
// handleRelease is the handler for the DHCP Release request.
func (s *v4Server) handleRelease(req, resp *dhcpv4.DHCPv4) (err error) {
mac := req.ClientHWAddr
@@ -995,11 +995,80 @@ func (s *v4Server) handleRelease(req, resp *dhcpv4.DHCPv4) (err error) {
return nil
}
// Find a lease associated with MAC and prepare response
// Return 1: OK
// Return 0: error; reply with Nak
// Return -1: error; don't reply
func (s *v4Server) handle(req, resp *dhcpv4.DHCPv4) int {
// messageHandler describes a DHCPv4 message handler function.
type messageHandler func(s *v4Server, req, resp *dhcpv4.DHCPv4) (rCode int, l *Lease, err error)
// messageHandlers is a map of handlers for various messages with message types
// keys.
var messageHandlers = map[dhcpv4.MessageType]messageHandler{
dhcpv4.MessageTypeDiscover: func(
s *v4Server,
req *dhcpv4.DHCPv4,
resp *dhcpv4.DHCPv4,
) (rCode int, l *Lease, err error) {
l, err = s.handleDiscover(req, resp)
if err != nil {
return 0, nil, fmt.Errorf("handling discover: %s", err)
}
if l == nil {
return 0, nil, nil
}
return 1, l, nil
},
dhcpv4.MessageTypeRequest: func(
s *v4Server,
req *dhcpv4.DHCPv4,
resp *dhcpv4.DHCPv4,
) (rCode int, l *Lease, err error) {
var toReply bool
l, toReply = s.handleRequest(req, resp)
if l == nil {
if toReply {
return 0, nil, nil
}
// Drop the packet.
return -1, nil, nil
}
return 1, l, nil
},
dhcpv4.MessageTypeDecline: func(
s *v4Server,
req *dhcpv4.DHCPv4,
resp *dhcpv4.DHCPv4,
) (rCode int, l *Lease, err error) {
err = s.handleDecline(req, resp)
if err != nil {
return 0, nil, fmt.Errorf("handling decline: %s", err)
}
return 1, nil, nil
},
dhcpv4.MessageTypeRelease: func(
s *v4Server,
req *dhcpv4.DHCPv4,
resp *dhcpv4.DHCPv4,
) (rCode int, l *Lease, err error) {
err = s.handleRelease(req, resp)
if err != nil {
return 0, nil, fmt.Errorf("handling release: %s", err)
}
return 1, nil, nil
},
}
// handle processes request, it finds a lease associated with MAC address and
// prepares response.
//
// Possible return values are:
// - "1": OK,
// - "0": error, reply with Nak,
// - "-1": error, don't reply.
func (s *v4Server) handle(req, resp *dhcpv4.DHCPv4) (rCode int) {
var err error
// Include server's identifier option since any reply should contain it.
@@ -1007,47 +1076,26 @@ func (s *v4Server) handle(req, resp *dhcpv4.DHCPv4) int {
// See https://datatracker.ietf.org/doc/html/rfc2131#page-29.
resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0].AsSlice()))
// TODO(a.garipov): Refactor this into handlers.
var l *Lease
switch mt := req.MessageType(); mt {
case dhcpv4.MessageTypeDiscover:
l, err = s.handleDiscover(req, resp)
if err != nil {
log.Error("dhcpv4: handling discover: %s", err)
handler := messageHandlers[req.MessageType()]
if handler == nil {
s.updateOptions(req, resp)
return 0
}
return 1
}
if l == nil {
return 0
}
case dhcpv4.MessageTypeRequest:
var toReply bool
l, toReply = s.handleRequest(req, resp)
if l == nil {
if toReply {
return 0
}
return -1 // drop packet
}
case dhcpv4.MessageTypeDecline:
err = s.handleDecline(req, resp)
if err != nil {
log.Error("dhcpv4: handling decline: %s", err)
rCode, l, err := handler(s, req, resp)
if err != nil {
log.Error("dhcpv4: %s", err)
return 0
}
case dhcpv4.MessageTypeRelease:
err = s.handleRelease(req, resp)
if err != nil {
log.Error("dhcpv4: handling release: %s", err)
return 0
}
return 0
}
if rCode != 1 {
return rCode
}
if l != nil {
resp.YourIPAddr = net.IP(l.IP.AsSlice())
resp.YourIPAddr = l.IP.AsSlice()
}
s.updateOptions(req, resp)
@@ -1162,23 +1210,8 @@ func (s *v4Server) Start() (err error) {
// No available IP addresses which may appear later.
return nil
}
// Update the value of Domain Name Server option separately from others if
// not assigned yet since its value is available only at server's start.
//
// TODO(e.burkov): Initialize as implicit option with the rest of default
// options when it will be possible to do before the call to Start.
if !s.explicitOpts.Has(dhcpv4.OptionDomainNameServer) {
s.implicitOpts.Update(dhcpv4.OptDNS(dnsIPAddrs...))
}
for _, ip := range dnsIPAddrs {
ip = ip.To4()
if ip == nil {
continue
}
s.conf.dnsIPAddrs = append(s.conf.dnsIPAddrs, netip.AddrFrom4(*(*[4]byte)(ip)))
}
s.configureDNSIPAddrs(dnsIPAddrs)
var c net.PacketConn
if c, err = s.newDHCPConn(iface); err != nil {
@@ -1199,10 +1232,10 @@ func (s *v4Server) Start() (err error) {
log.Info("dhcpv4: listening")
go func() {
if serr := s.srv.Serve(); errors.Is(serr, net.ErrClosed) {
if sErr := s.srv.Serve(); errors.Is(sErr, net.ErrClosed) {
log.Info("dhcpv4: server is closed")
} else if serr != nil {
log.Error("dhcpv4: srv.Serve: %s", serr)
} else if sErr != nil {
log.Error("dhcpv4: srv.Serve: %s", sErr)
}
}()
@@ -1213,6 +1246,28 @@ func (s *v4Server) Start() (err error) {
return nil
}
// configureDNSIPAddrs updates v4Server configuration with provided slice of
// dns IP addresses.
func (s *v4Server) configureDNSIPAddrs(dnsIPAddrs []net.IP) {
// Update the value of Domain Name Server option separately from others if
// not assigned yet since its value is available only at server's start.
//
// TODO(e.burkov): Initialize as implicit option with the rest of default
// options when it will be possible to do before the call to Start.
if !s.explicitOpts.Has(dhcpv4.OptionDomainNameServer) {
s.implicitOpts.Update(dhcpv4.OptDNS(dnsIPAddrs...))
}
for _, ip := range dnsIPAddrs {
vAddr, err := netutil.IPToAddr(ip, netutil.AddrFamilyIPv4)
if err != nil {
continue
}
s.conf.dnsIPAddrs = append(s.conf.dnsIPAddrs, vAddr)
}
}
// Stop - stop server
func (s *v4Server) Stop() (err error) {
if s.srv == nil {

View File

@@ -227,7 +227,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
},
name: "with_gateway_ip",
wantErrMsg: "dhcpv4: adding static lease: " +
"can't assign the gateway IP 192.168.10.1 to the lease",
`can't assign the gateway IP "192.168.10.1" to the lease`,
}, {
lease: &Lease{
Hostname: "ip6.local",
@@ -236,7 +236,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
},
name: "ipv6",
wantErrMsg: `dhcpv4: adding static lease: ` +
`invalid ip "ffff::1", only ipv4 is supported`,
`invalid IP "ffff::1": only IPv4 is supported`,
}, {
lease: &Lease{
Hostname: "bad-mac.local",

View File

@@ -30,7 +30,7 @@ type v6Server struct {
leasesLock sync.Mutex
leases []*Lease
ipAddrs [256]byte
sid dhcpv6.Duid
sid dhcpv6.DUID
ra raCtx // RA module
@@ -586,9 +586,31 @@ func (s *v6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6.
}
}
// initialize RA module
func (s *v6Server) initRA(iface *net.Interface) error {
// choose the source IP address - should be link-local-unicast
// configureDNSIPAddrs updates v6Server configuration with the slice of DNS IP
// addresses of provided interface iface. Initializes RA module.
func (s *v6Server) configureDNSIPAddrs(iface *net.Interface) (ok bool, err error) {
dnsIPAddrs, err := aghnet.IfaceDNSIPAddrs(
iface,
aghnet.IPVersion6,
defaultMaxAttempts,
defaultBackoff,
)
if err != nil {
return false, fmt.Errorf("interface %s: %w", iface.Name, err)
}
if len(dnsIPAddrs) == 0 {
return false, nil
}
s.conf.dnsIPAddrs = dnsIPAddrs
return true, s.initRA(iface)
}
// initRA initializes RA module.
func (s *v6Server) initRA(iface *net.Interface) (err error) {
// Choose the source IP address - should be link-local-unicast.
s.ra.ipAddr = s.conf.dnsIPAddrs[0]
for _, ip := range s.conf.dnsIPAddrs {
if ip.IsLinkLocalUnicast() {
@@ -604,6 +626,7 @@ func (s *v6Server) initRA(iface *net.Interface) error {
s.ra.ifaceName = s.conf.InterfaceName
s.ra.iface = iface
s.ra.packetSendPeriod = 1 * time.Second
return s.ra.Init()
}
@@ -623,63 +646,47 @@ func (s *v6Server) Start() (err error) {
log.Debug("dhcpv6: starting...")
dnsIPAddrs, err := aghnet.IfaceDNSIPAddrs(
iface,
aghnet.IPVersion6,
defaultMaxAttempts,
defaultBackoff,
)
ok, err := s.configureDNSIPAddrs(iface)
if err != nil {
return fmt.Errorf("interface %s: %w", ifaceName, err)
// Don't wrap the error, because it's informative enough as is.
return err
}
if len(dnsIPAddrs) == 0 {
if !ok {
// No available IP addresses which may appear later.
return nil
}
s.conf.dnsIPAddrs = dnsIPAddrs
err = s.initRA(iface)
if err != nil {
return err
}
// don't initialize DHCPv6 server if we must force the clients to use SLAAC
// Don't initialize DHCPv6 server if we must force the clients to use SLAAC.
if s.conf.RASLAACOnly {
log.Debug("not starting dhcpv6 server due to ra_slaac_only=true")
return nil
}
log.Debug("dhcpv6: listening...")
err = netutil.ValidateMAC(iface.HardwareAddr)
if err != nil {
return fmt.Errorf("validating interface %s: %w", iface.Name, err)
}
s.sid = dhcpv6.Duid{
Type: dhcpv6.DUID_LLT,
HwType: iana.HWTypeEthernet,
s.sid = &dhcpv6.DUIDLLT{
HWType: iana.HWTypeEthernet,
LinkLayerAddr: iface.HardwareAddr,
Time: dhcpv6.GetTime(),
}
laddr := &net.UDPAddr{
IP: net.ParseIP("::"),
Port: dhcpv6.DefaultServerPort,
}
s.srv, err = server6.NewServer(iface.Name, laddr, s.packetHandler, server6.WithDebugLogger())
s.srv, err = server6.NewServer(iface.Name, nil, s.packetHandler, server6.WithDebugLogger())
if err != nil {
return err
}
log.Debug("dhcpv6: listening...")
go func() {
if serr := s.srv.Serve(); errors.Is(serr, net.ErrClosed) {
if sErr := s.srv.Serve(); errors.Is(sErr, net.ErrClosed) {
log.Info("dhcpv6: server is closed")
} else if serr != nil {
log.Error("dhcpv6: srv.Serve: %s", serr)
} else if sErr != nil {
log.Error("dhcpv6: srv.Serve: %s", sErr)
}
}()

View File

@@ -121,9 +121,8 @@ func TestV6GetLease(t *testing.T) {
dnsAddr := net.ParseIP("2000::1")
s.conf.dnsIPAddrs = []net.IP{dnsAddr}
s.sid = dhcpv6.Duid{
Type: dhcpv6.DUID_LLT,
HwType: iana.HWTypeEthernet,
s.sid = &dhcpv6.DUIDLL{
HWType: iana.HWTypeEthernet,
LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
}
@@ -216,9 +215,8 @@ func TestV6GetDynamicLease(t *testing.T) {
dnsAddr := net.ParseIP("2000::1")
s.conf.dnsIPAddrs = []net.IP{dnsAddr}
s.sid = dhcpv6.Duid{
Type: dhcpv6.DUID_LLT,
HwType: iana.HWTypeEthernet,
s.sid = &dhcpv6.DUIDLL{
HWType: iana.HWTypeEthernet,
LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
}

View File

@@ -53,14 +53,14 @@ func (s *Server) beforeRequestHandler(
// 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.GetConfig()
setts := s.dnsFilter.Settings()
setts.ProtectionEnabled = dctx.protectionEnabled
if s.conf.FilterHandler != nil {
ip, _ := netutil.IPAndPortFromAddr(dctx.proxyCtx.Addr)
s.conf.FilterHandler(ip, dctx.clientID, &setts)
s.conf.FilterHandler(ip, dctx.clientID, setts)
}
return &setts
return setts
}
// filterDNSRequest applies the dnsFilter and sets dctx.proxyCtx.Res if the

View File

@@ -487,7 +487,8 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
},
wantResp: map[string]any{
badUps: `upstream "` + badUps + `" fails to exchange: ` +
`couldn't communicate with upstream: dns: id mismatch`,
`couldn't communicate with upstream: exchanging with ` +
badUps + ` over tcp: dns: id mismatch`,
},
name: "broken",
}, {
@@ -497,7 +498,8 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
wantResp: map[string]any{
goodUps: "OK",
badUps: `upstream "` + badUps + `" fails to exchange: ` +
`couldn't communicate with upstream: dns: id mismatch`,
`couldn't communicate with upstream: exchanging with ` +
badUps + ` over tcp: dns: id mismatch`,
},
name: "both",
}}

View File

@@ -57,16 +57,13 @@ func (s *Server) genDNSFilterMessage(
return s.genBlockedHost(req, s.conf.SafeBrowsingBlockHost, dctx)
case filtering.FilteredParental:
return s.genBlockedHost(req, s.conf.ParentalBlockHost, dctx)
case filtering.FilteredSafeSearch:
// If Safe Search generated the necessary IP addresses, use them.
// Otherwise, if there were no errors, there are no addresses for the
// requested IP version, so produce a NODATA response.
return s.genResponseWithIPs(req, ipsFromRules(res.Rules))
default:
// If the query was filtered by Safe Search, filtering also must return
// the IP addresses that must be used in response. Return them
// regardless of the filtering method.
ips := ipsFromRules(res.Rules)
if res.Reason == filtering.FilteredSafeSearch && len(ips) > 0 {
return s.genResponseWithIPs(req, ips)
}
return s.genForBlockingMode(req, ips)
return s.genForBlockingMode(req, ipsFromRules(res.Rules))
}
}

View File

@@ -123,7 +123,10 @@ func (s *Server) updateStats(
pctx := ctx.proxyCtx
e := stats.Entry{}
e.Domain = strings.ToLower(pctx.Req.Question[0].Name)
e.Domain = e.Domain[:len(e.Domain)-1] // remove last "."
if e.Domain != "." {
// Remove last ".", but save the domain as is for "." queries.
e.Domain = e.Domain[:len(e.Domain)-1]
}
if clientID := ctx.clientID; clientID != "" {
e.Client = clientID

View File

@@ -46,6 +46,10 @@ type testStats struct {
// Update implements the [stats.Interface] interface for *testStats.
func (l *testStats) Update(e stats.Entry) {
if e.Domain == "" {
return
}
l.lastEntry = e
}
@@ -54,9 +58,12 @@ func (l *testStats) ShouldCount(string, uint16, uint16, []string) bool {
return true
}
func TestProcessQueryLogsAndStats(t *testing.T) {
func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
const domain = "example.com."
testCases := []struct {
name string
domain string
proto proxy.Proto
addr net.Addr
clientID string
@@ -67,6 +74,7 @@ func TestProcessQueryLogsAndStats(t *testing.T) {
wantStatResult stats.Result
}{{
name: "success_udp",
domain: domain,
proto: proxy.ProtoUDP,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
@@ -77,6 +85,7 @@ func TestProcessQueryLogsAndStats(t *testing.T) {
wantStatResult: stats.RNotFiltered,
}, {
name: "success_tls_clientid",
domain: domain,
proto: proxy.ProtoTLS,
addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "cli42",
@@ -87,6 +96,7 @@ func TestProcessQueryLogsAndStats(t *testing.T) {
wantStatResult: stats.RNotFiltered,
}, {
name: "success_tls",
domain: domain,
proto: proxy.ProtoTLS,
addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
@@ -97,6 +107,7 @@ func TestProcessQueryLogsAndStats(t *testing.T) {
wantStatResult: stats.RNotFiltered,
}, {
name: "success_quic",
domain: domain,
proto: proxy.ProtoQUIC,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
@@ -107,6 +118,7 @@ func TestProcessQueryLogsAndStats(t *testing.T) {
wantStatResult: stats.RNotFiltered,
}, {
name: "success_https",
domain: domain,
proto: proxy.ProtoHTTPS,
addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
@@ -117,6 +129,7 @@ func TestProcessQueryLogsAndStats(t *testing.T) {
wantStatResult: stats.RNotFiltered,
}, {
name: "success_dnscrypt",
domain: domain,
proto: proxy.ProtoDNSCrypt,
addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
@@ -127,6 +140,7 @@ func TestProcessQueryLogsAndStats(t *testing.T) {
wantStatResult: stats.RNotFiltered,
}, {
name: "success_udp_filtered",
domain: domain,
proto: proxy.ProtoUDP,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
@@ -137,6 +151,7 @@ func TestProcessQueryLogsAndStats(t *testing.T) {
wantStatResult: stats.RFiltered,
}, {
name: "success_udp_sb",
domain: domain,
proto: proxy.ProtoUDP,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
@@ -147,6 +162,7 @@ func TestProcessQueryLogsAndStats(t *testing.T) {
wantStatResult: stats.RSafeBrowsing,
}, {
name: "success_udp_ss",
domain: domain,
proto: proxy.ProtoUDP,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
@@ -157,6 +173,7 @@ func TestProcessQueryLogsAndStats(t *testing.T) {
wantStatResult: stats.RSafeSearch,
}, {
name: "success_udp_pc",
domain: domain,
proto: proxy.ProtoUDP,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
clientID: "",
@@ -165,6 +182,17 @@ func TestProcessQueryLogsAndStats(t *testing.T) {
wantCode: resultCodeSuccess,
reason: filtering.FilteredParental,
wantStatResult: stats.RParental,
}, {
name: "success_udp_pc_empty_fqdn",
domain: ".",
proto: proxy.ProtoUDP,
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 5}, Port: 1234},
clientID: "",
wantLogProto: "",
wantStatClient: "1.2.3.5",
wantCode: resultCodeSuccess,
reason: filtering.FilteredParental,
wantStatResult: stats.RParental,
}}
ups, err := upstream.AddressToUpstream("1.1.1.1", nil)
@@ -181,7 +209,7 @@ func TestProcessQueryLogsAndStats(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
req := &dns.Msg{
Question: []dns.Question{{
Name: "example.com.",
Name: tc.domain,
}},
}
pctx := &proxy.DNSContext{

View File

@@ -3,8 +3,10 @@ package filtering
import (
"encoding/json"
"net/http"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"golang.org/x/exp/slices"
@@ -44,6 +46,15 @@ func initBlockedServices() {
log.Debug("filtering: initialized %d services", l)
}
// BlockedServices is the configuration of blocked services.
type BlockedServices struct {
// Schedule is blocked services schedule for every day of the week.
Schedule *schedule.Weekly `yaml:"schedule"`
// IDs is the names of blocked services.
IDs []string `yaml:"ids"`
}
// BlockedSvcKnown returns true if a blocked service ID is known.
func BlockedSvcKnown(s string) (ok bool) {
_, ok = serviceRules[s]
@@ -52,15 +63,22 @@ func BlockedSvcKnown(s string) (ok bool) {
}
// ApplyBlockedServices - set blocked services settings for this DNS request
func (d *DNSFilter) ApplyBlockedServices(setts *Settings, list []string) {
func (d *DNSFilter) ApplyBlockedServices(setts *Settings) {
d.confLock.RLock()
defer d.confLock.RUnlock()
setts.ServicesRules = []ServiceEntry{}
if list == nil {
d.confLock.RLock()
defer d.confLock.RUnlock()
list = d.Config.BlockedServices
bsvc := d.BlockedServices
// TODO(s.chzhen): Use startTime from [dnsforward.dnsContext].
if !bsvc.Schedule.Contains(time.Now()) {
d.ApplyBlockedServicesList(setts, bsvc.IDs)
}
}
// ApplyBlockedServicesList appends filtering rules to the settings.
func (d *DNSFilter) ApplyBlockedServicesList(setts *Settings, list []string) {
for _, name := range list {
rules, ok := serviceRules[name]
if !ok {
@@ -90,7 +108,7 @@ func (d *DNSFilter) handleBlockedServicesAll(w http.ResponseWriter, r *http.Requ
func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
d.confLock.RLock()
list := d.Config.BlockedServices
list := d.Config.BlockedServices.IDs
d.confLock.RUnlock()
_ = aghhttp.WriteJSONResponse(w, r, list)
@@ -106,7 +124,7 @@ func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ
}
d.confLock.Lock()
d.Config.BlockedServices = list
d.Config.BlockedServices.IDs = list
d.confLock.Unlock()
log.Debug("Updated blocked services list: %d", len(list))

View File

@@ -3,7 +3,6 @@ package filtering
import (
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// DNSRewriteResult is the result of application of $dnsrewrite rules.
@@ -25,13 +24,7 @@ func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) {
Response: DNSRewriteResultResponse{},
}
slices.SortFunc(dnsr, rewriteSortsBefore)
for i, nr := range dnsr {
if i > 0 && containsWildcard(nr) {
break
}
for _, nr := range dnsr {
dr := nr.DNSRewrite
if dr.NewCNAME != "" {
// NewCNAME rules have a higher priority than other rules.
@@ -80,19 +73,3 @@ func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) {
Reason: RewrittenRule,
}
}
func rewriteSortsBefore(a, b *rules.NetworkRule) (sortsBefore bool) {
return len(a.Shortcut) > len(b.Shortcut)
}
func containsWildcard(r *rules.NetworkRule) (ok bool) {
for _, c := range r.RuleText {
if c == '*' {
return true
} else if c == '^' {
break
}
}
return false
}

View File

@@ -5,7 +5,6 @@ import (
"path"
"testing"
"github.com/AdguardTeam/urlfilter"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -203,32 +202,3 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
assert.Equal(t, "new-ptr-with-dot.", ptr)
})
}
func TestDNSFilter_ProcessDNSRewrites(t *testing.T) {
const text = `
|www.example.com^$dnsrewrite=127.0.0.1
|*.example.com^$dnsrewrite=127.0.0.2
`
host := "www.example.com"
rrtype := dns.TypeA
f, _ := newForTest(t, nil, []Filter{{ID: 0, Data: []byte(text)}})
setts := &Settings{
FilteringEnabled: true,
}
ufReq := &urlfilter.DNSRequest{
Hostname: host,
SortedClientTags: setts.ClientTags,
ClientIP: setts.ClientIP.String(),
ClientName: setts.ClientName,
DNSType: rrtype,
}
dres, matched := f.filteringEngine.MatchRequest(ufReq)
require.False(t, matched)
res := f.processDNSResultRewrites(dres, host)
assert.Len(t, res.Rules, 1)
}

View File

@@ -103,9 +103,9 @@ type Config struct {
Rewrites []*LegacyRewrite `yaml:"rewrites"`
// Names of services to block (globally).
// BlockedServices is the configuration of blocked services.
// Per-client settings can override this configuration.
BlockedServices []string `yaml:"blocked_services"`
BlockedServices *BlockedServices `yaml:"blocked_services"`
// EtcHosts is a container of IP-hostname pairs taken from the operating
// system configuration files (e.g. /etc/hosts).
@@ -298,12 +298,12 @@ func (d *DNSFilter) SetEnabled(enabled bool) {
atomic.StoreUint32(&d.enabled, mathutil.BoolToNumber[uint32](enabled))
}
// GetConfig - get configuration
func (d *DNSFilter) GetConfig() (s Settings) {
// Settings returns filtering settings.
func (d *DNSFilter) Settings() (s *Settings) {
d.confLock.RLock()
defer d.confLock.RUnlock()
return Settings{
return &Settings{
FilteringEnabled: atomic.LoadUint32(&d.Config.enabled) != 0,
SafeSearchEnabled: d.Config.SafeSearchConf.Enabled,
SafeBrowsingEnabled: d.Config.SafeBrowsingEnabled,
@@ -987,16 +987,19 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
return nil, fmt.Errorf("rewrites: preparing: %s", err)
}
bsvcs := []string{}
for _, s := range d.BlockedServices {
if !BlockedSvcKnown(s) {
log.Debug("skipping unknown blocked-service %q", s)
if d.BlockedServices != nil {
bsvcs := []string{}
for _, s := range d.BlockedServices.IDs {
if !BlockedSvcKnown(s) {
log.Debug("skipping unknown blocked-service %q", s)
continue
continue
}
bsvcs = append(bsvcs, s)
}
bsvcs = append(bsvcs, s)
d.BlockedServices.IDs = bsvcs
}
d.BlockedServices = bsvcs
if blockFilters != nil {
err = d.initFiltering(nil, blockFilters)

View File

@@ -416,12 +416,12 @@ type checkHostResp struct {
func (d *DNSFilter) handleCheckHost(w http.ResponseWriter, r *http.Request) {
host := r.URL.Query().Get("name")
setts := d.GetConfig()
setts := d.Settings()
setts.FilteringEnabled = true
setts.ProtectionEnabled = true
d.ApplyBlockedServices(&setts, nil)
result, err := d.CheckHost(host, dns.TypeA, &setts)
d.ApplyBlockedServices(setts)
result, err := d.CheckHost(host, dns.TypeA, setts)
if err != nil {
aghhttp.Error(
r,
@@ -555,6 +555,7 @@ func (d *DNSFilter) RegisterFilteringHandlers() {
registerHTTP(http.MethodGet, "/control/rewrite/list", d.handleRewriteList)
registerHTTP(http.MethodPost, "/control/rewrite/add", d.handleRewriteAdd)
registerHTTP(http.MethodPut, "/control/rewrite/update", d.handleRewriteUpdate)
registerHTTP(http.MethodPost, "/control/rewrite/delete", d.handleRewriteDelete)
registerHTTP(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesIDs)

View File

@@ -84,7 +84,7 @@ func (s *DefaultStorage) MatchRequest(dReq *urlfilter.DNSRequest) (rws []*rules.
return nil
}
// TODO(a.garipov): Check cnames for cycles on initialisation.
// TODO(a.garipov): Check cnames for cycles on initialization.
cnames := stringutil.NewSet()
host := dReq.Hostname
for len(rrules) > 0 && rrules[0].DNSRewrite != nil && rrules[0].DNSRewrite.NewCNAME != "" {

View File

@@ -6,6 +6,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/log"
"golang.org/x/exp/slices"
)
// TODO(d.kolyshev): Use [rewrite.Item] instead.
@@ -91,3 +92,62 @@ func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request)
d.Config.ConfigModified()
}
// rewriteUpdateJSON is a struct for JSON object with rewrite rule update info.
type rewriteUpdateJSON struct {
Target rewriteEntryJSON `json:"target"`
Update rewriteEntryJSON `json:"update"`
}
// handleRewriteUpdate is the handler for the PUT /control/rewrite/update HTTP
// API.
func (d *DNSFilter) handleRewriteUpdate(w http.ResponseWriter, r *http.Request) {
updateJSON := rewriteUpdateJSON{}
err := json.NewDecoder(r.Body).Decode(&updateJSON)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
return
}
rwDel := &LegacyRewrite{
Domain: updateJSON.Target.Domain,
Answer: updateJSON.Target.Answer,
}
rwAdd := &LegacyRewrite{
Domain: updateJSON.Update.Domain,
Answer: updateJSON.Update.Answer,
}
err = rwAdd.normalize()
if err != nil {
// Shouldn't happen currently, since normalize only returns a non-nil
// error when a rewrite is nil, but be change-proof.
aghhttp.Error(r, w, http.StatusBadRequest, "normalizing: %s", err)
return
}
index := -1
defer func() {
if index >= 0 {
d.Config.ConfigModified()
}
}()
d.confLock.Lock()
defer d.confLock.Unlock()
index = slices.IndexFunc(d.Config.Rewrites, rwDel.equal)
if index == -1 {
aghhttp.Error(r, w, http.StatusBadRequest, "target rule not found")
return
}
d.Config.Rewrites = slices.Replace(d.Config.Rewrites, index, index+1, rwAdd)
log.Debug("rewrite: removed element: %s -> %s", rwDel.Domain, rwDel.Answer)
log.Debug("rewrite: added element: %s -> %s", rwAdd.Domain, rwAdd.Answer)
}

View File

@@ -0,0 +1,237 @@
package filtering_test
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TODO(d.kolyshev): Use [rewrite.Item] instead.
type rewriteJSON struct {
Domain string `json:"domain"`
Answer string `json:"answer"`
}
type rewriteUpdateJSON struct {
Target rewriteJSON `json:"target"`
Update rewriteJSON `json:"update"`
}
const (
// testTimeout is the common timeout for tests.
testTimeout = 100 * time.Millisecond
listURL = "/control/rewrite/list"
addURL = "/control/rewrite/add"
deleteURL = "/control/rewrite/delete"
updateURL = "/control/rewrite/update"
decodeErrorMsg = "json.Decode: json: cannot unmarshal string into Go value of type" +
" filtering.rewriteEntryJSON\n"
)
func TestDNSFilter_handleRewriteHTTP(t *testing.T) {
confModCh := make(chan struct{})
reqCh := make(chan struct{})
testRewrites := []*rewriteJSON{
{Domain: "example.local", Answer: "example.rewrite"},
{Domain: "one.local", Answer: "one.rewrite"},
}
testRewritesJSON, mErr := json.Marshal(testRewrites)
require.NoError(t, mErr)
testCases := []struct {
reqData any
name string
url string
method string
wantList []*rewriteJSON
wantBody string
wantConfMod bool
wantStatus int
}{{
name: "list",
url: listURL,
method: http.MethodGet,
reqData: nil,
wantConfMod: false,
wantStatus: http.StatusOK,
wantBody: string(testRewritesJSON) + "\n",
wantList: testRewrites,
}, {
name: "add",
url: addURL,
method: http.MethodPost,
reqData: rewriteJSON{Domain: "add.local", Answer: "add.rewrite"},
wantConfMod: true,
wantStatus: http.StatusOK,
wantBody: "",
wantList: append(
testRewrites,
&rewriteJSON{Domain: "add.local", Answer: "add.rewrite"},
),
}, {
name: "add_error",
url: addURL,
method: http.MethodPost,
reqData: "invalid_json",
wantConfMod: false,
wantStatus: http.StatusBadRequest,
wantBody: decodeErrorMsg,
wantList: testRewrites,
}, {
name: "delete",
url: deleteURL,
method: http.MethodPost,
reqData: rewriteJSON{Domain: "one.local", Answer: "one.rewrite"},
wantConfMod: true,
wantStatus: http.StatusOK,
wantBody: "",
wantList: []*rewriteJSON{{Domain: "example.local", Answer: "example.rewrite"}},
}, {
name: "delete_error",
url: deleteURL,
method: http.MethodPost,
reqData: "invalid_json",
wantConfMod: false,
wantStatus: http.StatusBadRequest,
wantBody: decodeErrorMsg,
wantList: testRewrites,
}, {
name: "update",
url: updateURL,
method: http.MethodPut,
reqData: rewriteUpdateJSON{
Target: rewriteJSON{Domain: "one.local", Answer: "one.rewrite"},
Update: rewriteJSON{Domain: "upd.local", Answer: "upd.rewrite"},
},
wantConfMod: true,
wantStatus: http.StatusOK,
wantBody: "",
wantList: []*rewriteJSON{
{Domain: "example.local", Answer: "example.rewrite"},
{Domain: "upd.local", Answer: "upd.rewrite"},
},
}, {
name: "update_error",
url: updateURL,
method: http.MethodPut,
reqData: "invalid_json",
wantConfMod: false,
wantStatus: http.StatusBadRequest,
wantBody: "json.Decode: json: cannot unmarshal string into Go value of type" +
" filtering.rewriteUpdateJSON\n",
wantList: testRewrites,
}, {
name: "update_error_target",
url: updateURL,
method: http.MethodPut,
reqData: rewriteUpdateJSON{
Target: rewriteJSON{Domain: "inv.local", Answer: "inv.rewrite"},
Update: rewriteJSON{Domain: "upd.local", Answer: "upd.rewrite"},
},
wantConfMod: false,
wantStatus: http.StatusBadRequest,
wantBody: "target rule not found\n",
wantList: testRewrites,
}}
for _, tc := range testCases {
onConfModified := func() {
if !tc.wantConfMod {
panic("config modified has been fired")
}
testutil.RequireSend(testutil.PanicT{}, confModCh, struct{}{}, testTimeout)
}
t.Run(tc.name, func(t *testing.T) {
handlers := make(map[string]http.Handler)
d, err := filtering.New(&filtering.Config{
ConfigModified: onConfModified,
HTTPRegister: func(_, url string, handler http.HandlerFunc) {
handlers[url] = handler
},
Rewrites: rewriteEntriesToLegacyRewrites(testRewrites),
}, nil)
require.NoError(t, err)
t.Cleanup(d.Close)
d.RegisterFilteringHandlers()
require.NotEmpty(t, handlers)
require.Contains(t, handlers, listURL)
require.Contains(t, handlers, tc.url)
var body io.Reader
if tc.reqData != nil {
data, rErr := json.Marshal(tc.reqData)
require.NoError(t, rErr)
body = bytes.NewReader(data)
}
r := httptest.NewRequest(tc.method, tc.url, body)
w := httptest.NewRecorder()
go func() {
handlers[tc.url].ServeHTTP(w, r)
testutil.RequireSend(testutil.PanicT{}, reqCh, struct{}{}, testTimeout)
}()
if tc.wantConfMod {
testutil.RequireReceive(t, confModCh, testTimeout)
}
testutil.RequireReceive(t, reqCh, testTimeout)
assert.Equal(t, tc.wantStatus, w.Code)
respBody, err := io.ReadAll(w.Body)
require.NoError(t, err)
assert.Equal(t, []byte(tc.wantBody), respBody)
assertRewritesList(t, handlers[listURL], tc.wantList)
})
}
}
// assertRewritesList checks if rewrites list equals the list received from the
// handler by listURL.
func assertRewritesList(t *testing.T, handler http.Handler, wantList []*rewriteJSON) {
t.Helper()
r := httptest.NewRequest(http.MethodGet, listURL, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, r)
require.Equal(t, http.StatusOK, w.Code)
var actual []*rewriteJSON
err := json.NewDecoder(w.Body).Decode(&actual)
require.NoError(t, err)
assert.Equal(t, wantList, actual)
}
// rewriteEntriesToLegacyRewrites gets legacy rewrites from json entries.
func rewriteEntriesToLegacyRewrites(entries []*rewriteJSON) (rw []*filtering.LegacyRewrite) {
for _, entry := range entries {
rw = append(rw, &filtering.LegacyRewrite{
Domain: entry.Domain,
Answer: entry.Answer,
})
}
return rw
}

View File

@@ -161,12 +161,8 @@ func (ss *Default) resetEngine(
// type check
var _ filtering.SafeSearch = (*Default)(nil)
// CheckHost implements the [filtering.SafeSearch] interface for
// *DefaultSafeSearch.
func (ss *Default) CheckHost(
host string,
qtype rules.RRType,
) (res filtering.Result, err error) {
// CheckHost implements the [filtering.SafeSearch] interface for *Default.
func (ss *Default) CheckHost(host string, qtype rules.RRType) (res filtering.Result, err error) {
start := time.Now()
defer func() {
ss.log(log.DEBUG, "lookup for %q finished in %s", host, time.Since(start))
@@ -196,14 +192,10 @@ func (ss *Default) CheckHost(
return filtering.Result{}, err
}
if fltRes != nil {
res = *fltRes
ss.setCacheResult(host, qtype, res)
res = *fltRes
ss.setCacheResult(host, qtype, res)
return res, nil
}
return filtering.Result{}, fmt.Errorf("no ipv4 addresses for %q", host)
return res, nil
}
// searchHost looks up DNS rewrites in the internal DNS filtering engine.
@@ -229,7 +221,11 @@ func (ss *Default) searchHost(host string, qtype rules.RRType) (res *rules.DNSRe
}
// newResult creates Result object from rewrite rule. qtype must be either
// [dns.TypeA] or [dns.TypeAAAA].
// [dns.TypeA] or [dns.TypeAAAA]. If err is nil, res is never nil, so that the
// empty result is converted into a NODATA response.
//
// TODO(a.garipov): Use the main rewrite result mechanism used in
// [dnsforward.Server.filterDNSRequest].
func (ss *Default) newResult(
rewrite *rules.DNSRewrite,
qtype rules.RRType,
@@ -243,9 +239,10 @@ func (ss *Default) newResult(
}
if rewrite.RRType == qtype {
ip, ok := rewrite.Value.(net.IP)
v := rewrite.Value
ip, ok := v.(net.IP)
if !ok || ip == nil {
return nil, nil
return nil, fmt.Errorf("expected ip rewrite value, got %T(%[1]v)", v)
}
res.Rules[0].IP = ip
@@ -255,14 +252,14 @@ func (ss *Default) newResult(
host := rewrite.NewCNAME
if host == "" {
return nil, nil
return res, nil
}
ss.log(log.DEBUG, "resolving %q", host)
ips, err := ss.resolver.LookupIP(context.Background(), qtypeToProto(qtype), host)
if err != nil {
return nil, err
return nil, fmt.Errorf("resolving cname: %w", err)
}
ss.log(log.DEBUG, "resolved %s", ips)
@@ -276,11 +273,9 @@ func (ss *Default) newResult(
}
res.Rules[0].IP = ip
return res, nil
}
return nil, nil
return res, nil
}
// qtypeToProto returns "ip4" for [dns.TypeA] and "ip6" for [dns.TypeAAAA].

View File

@@ -1,6 +1,7 @@
package safesearch_test
import (
"context"
"net"
"testing"
"time"
@@ -71,6 +72,25 @@ func TestDefault_CheckHost_yandex(t *testing.T) {
}
}
func TestDefault_CheckHost_yandexAAAA(t *testing.T) {
conf := testConf
ss, err := safesearch.NewDefault(conf, "", testCacheSize, testCacheTTL)
require.NoError(t, err)
res, err := ss.CheckHost("www.yandex.ru", dns.TypeAAAA)
require.NoError(t, err)
assert.True(t, res.IsFiltered)
// TODO(a.garipov): Currently, the safe-search filter returns a single rule
// with a nil IP address. This isn't really necessary and should be changed
// once the TODO in [safesearch.Default.newResult] is resolved.
require.Len(t, res.Rules, 1)
assert.Nil(t, res.Rules[0].IP)
assert.EqualValues(t, filtering.SafeSearchListID, res.Rules[0].FilterListID)
}
func TestDefault_CheckHost_google(t *testing.T) {
resolver := &aghtest.TestResolver{}
ip, _ := resolver.HostToIPs("forcesafesearch.google.com")
@@ -105,6 +125,56 @@ func TestDefault_CheckHost_google(t *testing.T) {
}
}
// testResolver is a [filtering.Resolver] for tests.
//
// TODO(a.garipov): Move to aghtest and use everywhere.
type testResolver struct {
OnLookupIP func(ctx context.Context, network, host string) (ips []net.IP, err error)
}
// type check
var _ filtering.Resolver = (*testResolver)(nil)
// LookupIP implements the [filtering.Resolver] interface for *testResolver.
func (r *testResolver) LookupIP(
ctx context.Context,
network string,
host string,
) (ips []net.IP, err error) {
return r.OnLookupIP(ctx, network, host)
}
func TestDefault_CheckHost_duckduckgoAAAA(t *testing.T) {
conf := testConf
conf.CustomResolver = &testResolver{
OnLookupIP: func(_ context.Context, network, host string) (ips []net.IP, err error) {
assert.Equal(t, "ip6", network)
assert.Equal(t, "safe.duckduckgo.com", host)
return nil, nil
},
}
ss, err := safesearch.NewDefault(conf, "", testCacheSize, testCacheTTL)
require.NoError(t, err)
// The DuckDuckGo safe-search addresses are resolved through CNAMEs, but
// DuckDuckGo doesn't have a safe-search IPv6 address. The result should be
// the same as the one for Yandex IPv6. That is, a NODATA response.
res, err := ss.CheckHost("www.duckduckgo.com", dns.TypeAAAA)
require.NoError(t, err)
assert.True(t, res.IsFiltered)
// TODO(a.garipov): Currently, the safe-search filter returns a single rule
// with a nil IP address. This isn't really necessary and should be changed
// once the TODO in [safesearch.Default.newResult] is resolved.
require.Len(t, res.Rules, 1)
assert.Nil(t, res.Rules[0].IP)
assert.EqualValues(t, filtering.SafeSearchListID, res.Rules[0].FilterListID)
}
func TestDefault_Update(t *testing.T) {
conf := testConf
ss, err := safesearch.NewDefault(conf, "", testCacheSize, testCacheTTL)

View File

@@ -12,6 +12,14 @@ type blockedService struct {
// blockedServices contains raw blocked service data.
var blockedServices = []blockedService{{
ID: "500px",
Name: "500px",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 5 14 L 2.5 26 L 6.800781 26 C 6.800781 26 7.699219 24.300781 10.199219 24.300781 C 12.699219 24.300781 14 26.199219 14 28.300781 C 14 30.402344 12.5 32.800781 10.199219 32.800781 C 7.898438 32.800781 6.5 30.398438 6.5 29 L 2 29 C 2 30.199219 3 36 10.199219 36 C 15.15625 36 17.417969 33.121094 18.015625 31.898438 C 19.386719 34.34375 21.992188 36 24.984375 36 C 27.253906 36 29.777344 34.808594 32.5 32.453125 C 35.222656 34.808594 37.746094 36 40.015625 36 C 44.417969 36 48 32.410156 48 28 C 48 23.589844 44.417969 20 40.015625 20 C 37.746094 20 35.222656 21.191406 32.5 23.546875 C 29.777344 21.191406 27.253906 20 24.984375 20 C 21.832031 20 19.105469 21.847656 17.8125 24.511719 C 17.113281 23.382813 15.414063 21 11.902344 21 C 8.101563 21 7.300781 22.597656 7.300781 22.597656 C 7.300781 22.597656 7.699219 21.300781 8.300781 18 L 17 18 L 17 14 Z M 24.984375 25 C 25.453125 25 26.800781 25.226563 29.230469 27.328125 L 30.011719 28 L 29.230469 28.671875 C 26.800781 30.773438 25.453125 31 24.984375 31 C 23.339844 31 22 29.652344 22 28 C 22 26.347656 23.339844 25 24.984375 25 Z M 40.015625 25 C 41.660156 25 43 26.347656 43 28 C 43 29.652344 41.660156 31 40.015625 31 C 39.546875 31 38.199219 30.773438 35.769531 28.671875 L 34.988281 28 L 35.769531 27.328125 C 38.199219 25.226563 39.546875 25 40.015625 25 Z\"/></svg>"),
Rules: []string{
"||500px.com^",
"||500px.org^",
},
}, {
ID: "9gag",
Name: "9GAG",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 44 14 C 44 13.644531 43.8125 13.316406 43.507813 13.136719 C 40.453125 11.347656 28.46875 4.847656 25.535156 3.136719 C 25.222656 2.957031 24.839844 2.957031 24.527344 3.136719 C 21.128906 5.117188 10.089844 11.621094 7.496094 13.136719 C 7.1875 13.316406 7 13.644531 7 14 L 7 20 C 7 20.378906 7.214844 20.722656 7.550781 20.894531 C 7.660156 20.949219 18.597656 26.453125 24.5 29.867188 C 24.8125 30.046875 25.195313 30.046875 25.507813 29.863281 C 27.269531 28.828125 29.117188 27.859375 30.902344 26.921875 C 32.253906 26.214844 33.636719 25.488281 35.003906 24.722656 C 35.007813 26.820313 35.003906 29.296875 35 30.40625 L 25 35.859375 L 14.480469 30.121094 C 14.144531 29.9375 13.730469 29.964844 13.417969 30.1875 L 6.417969 35.1875 C 6.140625 35.386719 5.980469 35.714844 6.003906 36.054688 C 6.023438 36.398438 6.214844 36.707031 6.515625 36.871094 L 24.542969 46.871094 C 24.695313 46.957031 24.859375 47 25.027344 47 C 25.195313 47 25.363281 46.957031 25.515625 46.875 L 43.484375 36.875 C 43.804688 36.695313 44 36.363281 44 36 C 44 36 43.992188 21.011719 44 14 Z M 25 20 L 18 16 L 25 12 L 32 16 Z\" /></svg>"),
@@ -1180,6 +1188,18 @@ var blockedServices = []blockedService{{
"||zuckerberg.com^",
"||zuckerberg.net^",
},
}, {
ID: "flickr",
Name: "Flickr",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 9 4 C 6.2504839 4 4 6.2504839 4 9 L 4 41 C 4 43.749516 6.2504839 46 9 46 L 41 46 C 43.749516 46 46 43.749516 46 41 L 46 9 C 46 6.2504839 43.749516 4 41 4 L 9 4 z M 9 6 L 41 6 C 42.668484 6 44 7.3315161 44 9 L 44 41 C 44 42.668484 42.668484 44 41 44 L 9 44 C 7.3315161 44 6 42.668484 6 41 L 6 9 C 6 7.3315161 7.3315161 6 9 6 z M 16 17 C 11.59 17 8 20.59 8 25 C 8 29.41 11.59 33 16 33 C 20.41 33 24 29.41 24 25 C 24 20.59 20.41 17 16 17 z M 34 17 C 29.59 17 26 20.59 26 25 C 26 29.41 29.59 33 34 33 C 38.41 33 42 29.41 42 25 C 42 20.59 38.41 17 34 17 z\"/></svg>"),
Rules: []string{
"||flic.kr^",
"||flickr.com^",
"||flickr.net^",
"||flickrprints.com^",
"||flickrpro.com^",
"||staticflickr.com^",
},
}, {
ID: "gog",
Name: "GOG",
@@ -1325,6 +1345,13 @@ var blockedServices = []blockedService{{
"||kakao.com^",
"||kgslb.com^",
},
}, {
ID: "kik",
Name: "Kik",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 3.5039062 12 C 1.9347705 11.994817 0.87857579 12.97636 0.4453125 13.849609 C 0.01204921 14.722858 0 15.564453 0 15.564453 A 1.0001 1.0001 0 0 0 0 15.59375 L 0 35 A 1.0001 1.0001 0 0 0 0.00390625 35.078125 C 0.00390625 35.078125 0.05696144 35.828363 0.5390625 36.554688 C 1.0211636 37.281011 2.0459252 38.004441 3.5019531 38.001953 C 4.8916439 38.000053 5.8837351 37.273604 6.3769531 36.578125 C 6.8701712 35.882646 6.9863281 35.166016 6.9863281 35.166016 A 1.0001 1.0001 0 0 0 7 35 L 7 31.802734 L 10.167969 36.554688 L 10.130859 36.494141 C 10.511831 37.164615 11.143097 37.525465 11.742188 37.730469 C 12.341278 37.935473 12.950104 38.001953 13.5 38.001953 C 15.411725 38.001953 17 36.431487 17 34.5 C 17 34.056649 16.90825 34.03442 16.851562 33.912109 C 16.794882 33.789799 16.730864 33.671331 16.654297 33.537109 C 16.501163 33.268666 16.298339 32.944015 16.058594 32.572266 C 15.579103 31.828767 14.950355 30.90254 14.322266 29.992188 C 13.310206 28.525308 12.655222 27.610988 12.300781 27.113281 L 14.707031 24.707031 A 1.0001 1.0001 0 0 0 14.738281 24.673828 C 14.738281 24.673828 15.354706 24.012223 15.748047 23.042969 C 16.141388 22.073714 16.298687 20.56089 15.259766 19.349609 C 14.281705 18.208994 12.842689 18.141009 11.925781 18.416016 C 11.008874 18.691022 10.371094 19.222656 10.371094 19.222656 A 1.0001 1.0001 0 0 0 10.292969 19.292969 L 6.9980469 22.587891 L 6.9921875 15.646484 A 1.0001 1.0001 0 0 0 6.9902344 15.580078 C 6.9902344 15.580078 6.9441634 14.743069 6.5058594 13.875 C 6.0675579 13.006938 5.0412971 12.005313 3.5039062 12 z M 30.503906 12 C 28.93477 11.9948 27.878577 12.97636 27.445312 13.849609 C 27.012049 14.722858 27 15.564453 27 15.564453 A 1.0001 1.0001 0 0 0 27 15.59375 L 27 35 A 1.0001 1.0001 0 0 0 27.003906 35.078125 C 27.003906 35.078125 27.056966 35.828363 27.539062 36.554688 C 28.021165 37.281011 29.045925 38.004441 30.501953 38.001953 C 31.891644 38.000053 32.883735 37.273604 33.376953 36.578125 C 33.870171 35.882646 33.986328 35.166016 33.986328 35.166016 A 1.0001 1.0001 0 0 0 34 35 L 34 31.802734 L 37.167969 36.554688 L 37.130859 36.494141 C 37.511831 37.164615 38.143096 37.525465 38.742188 37.730469 C 39.341277 37.935473 39.950104 38.001953 40.5 38.001953 C 42.411725 38.001953 44 36.431487 44 34.5 C 44 34.056649 43.908251 34.03442 43.851562 33.912109 C 43.794882 33.789799 43.730864 33.671331 43.654297 33.537109 C 43.501163 33.268666 43.298339 32.944015 43.058594 32.572266 C 42.579103 31.828767 41.950355 30.90254 41.322266 29.992188 C 40.310206 28.525308 39.655222 27.610988 39.300781 27.113281 L 41.707031 24.707031 A 1.0001 1.0001 0 0 0 41.738281 24.673828 C 41.738281 24.673828 42.354706 24.012223 42.748047 23.042969 C 43.141388 22.073714 43.298687 20.56089 42.259766 19.349609 C 41.281705 18.208994 39.842689 18.141009 38.925781 18.416016 C 38.008874 18.691022 37.371094 19.222656 37.371094 19.222656 A 1.0001 1.0001 0 0 0 37.292969 19.292969 L 33.998047 22.587891 L 33.992188 15.646484 A 1.0001 1.0001 0 0 0 33.990234 15.580078 C 33.990234 15.580078 33.944164 14.743069 33.505859 13.875 C 33.067647 13.006938 32.041297 12.005313 30.503906 12 z M 21.507812 18 C 19.85324 17.98686 18.785557 19.124468 18.382812 20.09375 C 18.181441 20.578391 18.090615 21.031738 18.044922 21.375 C 18.022072 21.546631 18.011459 21.69063 18.005859 21.796875 C 18.000252 21.90312 18 22.065333 18 21.984375 L 17.982422 34.998047 A 1.0001 1.0001 0 0 0 17.990234 35.134766 C 17.990234 35.134766 18.085674 35.862804 18.576172 36.568359 C 19.06667 37.273915 20.071581 37.997467 21.486328 38 C 22.885358 38.0026 23.885897 37.278643 24.380859 36.580078 C 24.875822 35.881513 24.986328 35.160156 24.986328 35.160156 A 1.0001 1.0001 0 0 0 25 35 L 25 21.996094 C 25 21.996094 25.02572 21.084043 24.625 20.117188 C 24.224283 19.150332 23.164841 18.013078 21.507812 18 z M 46.5 24 C 44.578848 24 43 25.578848 43 27.5 C 43 29.421152 44.578848 31 46.5 31 C 48.421152 31 50 29.421152 50 27.5 C 50 25.578848 48.421152 24 46.5 24 z M 46.5 26 C 47.340272 26 48 26.659728 48 27.5 C 48 28.340272 47.340272 29 46.5 29 C 45.659728 29 45 28.340272 45 27.5 C 45 26.659728 45.659728 26 46.5 26 z\"/></svg>"),
Rules: []string{
"||kik.com^",
},
}, {
ID: "lazada",
Name: "Lazada",
@@ -1385,6 +1412,7 @@ var blockedServices = []blockedService{{
Rules: []string{
"||aus.social^",
"||awscommunity.social^",
"||climatejustice.social^",
"||cyberplace.social^",
"||defcon.social^",
"||det.social^",
@@ -1442,13 +1470,13 @@ var blockedServices = []blockedService{{
"||mstdn.plus^",
"||mstdn.social^",
"||muenchen.social^",
"||muenster.im^",
"||newsie.social^",
"||noc.social^",
"||norden.social^",
"||nrw.social^",
"||o3o.ca^",
"||ohai.social^",
"||pewtix.com^",
"||piaille.fr^",
"||pol.social^",
"||ravenation.club^",
@@ -1480,7 +1508,6 @@ var blockedServices = []blockedService{{
"||union.place^",
"||universeodon.com^",
"||urbanists.social^",
"||wien.rocks^",
"||wxw.moe^",
},
}, {
@@ -1827,6 +1854,13 @@ var blockedServices = []blockedService{{
"||tx.me^",
"||usercontent.dev^",
},
}, {
ID: "tidal",
Name: "Tidal",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 9 12 C 8.7615 12 8.5237969 12.091437 8.3417969 12.273438 L 1.2734375 19.341797 C 0.9094375 19.705797 0.9094375 20.294203 1.2734375 20.658203 L 8.3417969 27.726562 C 8.7057969 28.090563 9.2942031 28.090563 9.6582031 27.726562 L 16.726562 20.658203 C 16.908563 20.476203 17 20.2385 17 20 C 17 19.7615 16.908563 19.523797 16.726562 19.341797 L 9.6582031 12.273438 C 9.4762031 12.091437 9.2385 12 9 12 z M 17 20 C 17 20.2385 17.091438 20.476203 17.273438 20.658203 L 24.341797 27.726562 C 24.523797 27.908563 24.7615 28 25 28 C 25.2385 28 25.476203 27.908563 25.658203 27.726562 L 32.726562 20.658203 C 32.908563 20.476203 33 20.2385 33 20 C 33 19.7615 32.908563 19.523797 32.726562 19.341797 L 25.658203 12.273438 C 25.294203 11.909437 24.705797 11.909437 24.341797 12.273438 L 17.273438 19.341797 C 17.091437 19.523797 17 19.7615 17 20 z M 33 20 C 33 20.2385 33.091437 20.476203 33.273438 20.658203 L 40.341797 27.726562 C 40.705797 28.090563 41.294203 28.090563 41.658203 27.726562 L 48.726562 20.658203 C 49.090563 20.294203 49.090563 19.705797 48.726562 19.341797 L 41.658203 12.273438 C 41.294203 11.909437 40.705797 11.909437 40.341797 12.273438 L 33.273438 19.341797 C 33.091437 19.523797 33 19.7615 33 20 z M 25 28 C 24.7615 28 24.523797 28.091437 24.341797 28.273438 L 17.273438 35.341797 C 16.909437 35.705797 16.909437 36.294203 17.273438 36.658203 L 24.341797 43.726562 C 24.705797 44.090562 25.294203 44.090562 25.658203 43.726562 L 32.726562 36.658203 C 33.090563 36.294203 33.090563 35.705797 32.726562 35.341797 L 25.658203 28.273438 C 25.476203 28.091437 25.2385 28 25 28 z\"/></svg>"),
Rules: []string{
"||tidal.com^",
},
}, {
ID: "tiktok",
Name: "TikTok",

View File

@@ -7,7 +7,9 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/stringutil"
)
// Client contains information about persistent clients.
@@ -37,6 +39,19 @@ type Client struct {
IgnoreStatistics bool
}
// ShallowClone returns a deep copy of the client, except upstreamConfig,
// safeSearchConf, SafeSearch fields, because it's difficult to copy them.
func (c *Client) ShallowClone() (sh *Client) {
clone := *c
clone.IDs = stringutil.CloneSlice(c.IDs)
clone.Tags = stringutil.CloneSlice(c.Tags)
clone.BlockedServices = stringutil.CloneSlice(c.BlockedServices)
clone.Upstreams = stringutil.CloneSlice(c.Upstreams)
return &clone
}
// closeUpstreams closes the client-specific upstream config of c if any.
func (c *Client) closeUpstreams() (err error) {
if c.upstreamConfig != nil {
@@ -113,14 +128,13 @@ func (cs clientSource) MarshalText() (text []byte, err error) {
// RuntimeClient is a client information about which has been obtained using the
// source described in the Source field.
type RuntimeClient struct {
WHOISInfo *RuntimeClientWHOISInfo
Host string
Source clientSource
}
// WHOIS is the filtered WHOIS data of a client.
WHOIS *whois.Info
// RuntimeClientWHOISInfo is the filtered WHOIS data for a runtime client.
type RuntimeClientWHOISInfo struct {
City string `json:"city,omitempty"`
Country string `json:"country,omitempty"`
Orgname string `json:"orgname,omitempty"`
// Host is the host name of a client.
Host string
// Source is the source from which the information about the client has
// been obtained.
Source clientSource
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
@@ -307,18 +308,6 @@ func (clients *clientsContainer) clientSource(ip netip.Addr) (src clientSource)
return rc.Source
}
func toQueryLogWHOIS(wi *RuntimeClientWHOISInfo) (cw *querylog.ClientWHOIS) {
if wi == nil {
return &querylog.ClientWHOIS{}
}
return &querylog.ClientWHOIS{
City: wi.City,
Country: wi.Country,
Orgname: wi.Orgname,
}
}
// findMultiple is a wrapper around Find to make it a valid client finder for
// the query log. c is never nil; if no information about the client is found,
// it returns an artificial client record by only setting the blocking-related
@@ -352,7 +341,7 @@ func (clients *clientsContainer) clientOrArtificial(
defer func() {
c.Disallowed, c.DisallowedRule = clients.dnsServer.IsBlockedClient(ip, id)
if c.WHOIS == nil {
c.WHOIS = &querylog.ClientWHOIS{}
c.WHOIS = &whois.Info{}
}
}()
@@ -369,7 +358,7 @@ func (clients *clientsContainer) clientOrArtificial(
if ok {
return &querylog.Client{
Name: rc.Host,
WHOIS: toQueryLogWHOIS(rc.WHOISInfo),
WHOIS: rc.WHOIS,
}, false
}
@@ -378,6 +367,7 @@ func (clients *clientsContainer) clientOrArtificial(
}, true
}
// Find returns a shallow copy of the client if there is one found.
func (clients *clientsContainer) Find(id string) (c *Client, ok bool) {
clients.lock.Lock()
defer clients.lock.Unlock()
@@ -387,20 +377,18 @@ func (clients *clientsContainer) Find(id string) (c *Client, ok bool) {
return nil, false
}
c.IDs = stringutil.CloneSlice(c.IDs)
c.Tags = stringutil.CloneSlice(c.Tags)
c.BlockedServices = stringutil.CloneSlice(c.BlockedServices)
c.Upstreams = stringutil.CloneSlice(c.Upstreams)
return c, true
return c.ShallowClone(), true
}
// shouldCountClient is a wrapper around Find to make it a valid client
// information finder for the statistics. If no information about the client
// is found, it returns true.
func (clients *clientsContainer) shouldCountClient(ids []string) (y bool) {
clients.lock.Lock()
defer clients.lock.Unlock()
for _, id := range ids {
client, ok := clients.Find(id)
client, ok := clients.findLocked(id)
if ok {
return !client.IgnoreStatistics
}
@@ -617,6 +605,15 @@ func (clients *clientsContainer) Add(c *Client) (ok bool, err error) {
}
}
clients.add(c)
log.Debug("clients: added %q: ID:%q [%d]", c.Name, c.IDs, len(clients.list))
return true, nil
}
// add c to the indexes. clients.lock is expected to be locked.
func (clients *clientsContainer) add(c *Client) {
// update Name index
clients.list[c.Name] = c
@@ -624,10 +621,6 @@ func (clients *clientsContainer) Add(c *Client) (ok bool, err error) {
for _, id := range c.IDs {
clients.idIndex[id] = c
}
log.Debug("clients: added %q: ID:%q [%d]", c.Name, c.IDs, len(clients.list))
return true, nil
}
// Del removes a client. ok is false if there is no such client.
@@ -645,92 +638,59 @@ func (clients *clientsContainer) Del(name string) (ok bool) {
log.Error("client container: removing client %s: %s", name, err)
}
clients.del(c)
return true
}
// del removes c from the indexes. clients.lock is expected to be locked.
func (clients *clientsContainer) del(c *Client) {
// update Name index
delete(clients.list, name)
delete(clients.list, c.Name)
// update ID index
for _, id := range c.IDs {
delete(clients.idIndex, id)
}
return true
}
// Update updates a client by its name.
func (clients *clientsContainer) Update(name string, c *Client) (err error) {
func (clients *clientsContainer) Update(prev, c *Client) (err error) {
err = clients.check(c)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
clients.lock.Lock()
defer clients.lock.Unlock()
prev, ok := clients.list[name]
if !ok {
return errors.Error("client not found")
}
// First, check the name index.
// Check the name index.
if prev.Name != c.Name {
_, ok = clients.list[c.Name]
_, ok := clients.list[c.Name]
if ok {
return errors.Error("client already exists")
}
}
// Second, update the ID index.
err = clients.updateIDIndex(prev, c.IDs)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
// Update name index.
if prev.Name != c.Name {
delete(clients.list, prev.Name)
clients.list[c.Name] = prev
}
// Update upstreams cache.
err = c.closeUpstreams()
if err != nil {
return err
}
*prev = *c
return nil
}
// updateIDIndex updates the ID index data for cli using the information from
// newIDs.
func (clients *clientsContainer) updateIDIndex(cli *Client, newIDs []string) (err error) {
if slices.Equal(cli.IDs, newIDs) {
return nil
}
for _, id := range newIDs {
existing, ok := clients.idIndex[id]
if ok && existing != cli {
return fmt.Errorf("id %q is used by client with name %q", id, existing.Name)
// Check the ID index.
if !slices.Equal(prev.IDs, c.IDs) {
for _, id := range c.IDs {
existing, ok := clients.idIndex[id]
if ok && existing != prev {
return fmt.Errorf("id %q is used by client with name %q", id, existing.Name)
}
}
}
// Update the IDs in the index.
for _, id := range cli.IDs {
delete(clients.idIndex, id)
}
for _, id := range newIDs {
clients.idIndex[id] = cli
}
clients.del(prev)
clients.add(c)
return nil
}
// setWHOISInfo sets the WHOIS information for a client.
func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWHOISInfo) {
func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) {
clients.lock.Lock()
defer clients.lock.Unlock()
@@ -742,7 +702,7 @@ func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWH
rc, ok := clients.ipToRC[ip]
if ok {
rc.WHOISInfo = wi
rc.WHOIS = wi
log.Debug("clients: set whois info for runtime client %s: %+v", rc.Host, wi)
return
@@ -754,7 +714,7 @@ func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWH
Source: ClientSourceWHOIS,
}
rc.WHOISInfo = wi
rc.WHOIS = wi
clients.ipToRC[ip] = rc
@@ -791,9 +751,9 @@ func (clients *clientsContainer) addHostLocked(
rc.Source = src
} else {
rc = &RuntimeClient{
Host: host,
Source: src,
WHOISInfo: &RuntimeClientWHOISInfo{},
Host: host,
Source: src,
WHOIS: &whois.Info{},
}
clients.ipToRC[ip] = rc

View File

@@ -9,7 +9,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -98,22 +98,8 @@ func TestClients(t *testing.T) {
assert.False(t, ok)
})
t.Run("update_fail_name", func(t *testing.T) {
err := clients.Update("client3", &Client{
IDs: []string{"1.2.3.0"},
Name: "client3",
})
require.Error(t, err)
err = clients.Update("client3", &Client{
IDs: []string{"1.2.3.0"},
Name: "client2",
})
assert.Error(t, err)
})
t.Run("update_fail_ip", func(t *testing.T) {
err := clients.Update("client1", &Client{
err := clients.Update(&Client{Name: "client1"}, &Client{
IDs: []string{"2.2.2.2"},
Name: "client1",
})
@@ -129,7 +115,10 @@ func TestClients(t *testing.T) {
cliNewIP = netip.MustParseAddr(cliNew)
)
err := clients.Update("client1", &Client{
prev, ok := clients.list["client1"]
require.True(t, ok)
err := clients.Update(prev, &Client{
IDs: []string{cliNew},
Name: "client1",
})
@@ -138,7 +127,10 @@ func TestClients(t *testing.T) {
assert.Equal(t, clients.clientSource(cliOldIP), ClientSourceNone)
assert.Equal(t, clients.clientSource(cliNewIP), ClientSourcePersistent)
err = clients.Update("client1", &Client{
prev, ok = clients.list["client1"]
require.True(t, ok)
err = clients.Update(prev, &Client{
IDs: []string{cliNew},
Name: "client1-renamed",
UseOwnSettings: true,
@@ -207,7 +199,7 @@ func TestClients(t *testing.T) {
func TestClientsWHOIS(t *testing.T) {
clients := newClientsContainer()
whois := &RuntimeClientWHOISInfo{
whois := &whois.Info{
Country: "AU",
Orgname: "Example Org",
}
@@ -218,7 +210,7 @@ func TestClientsWHOIS(t *testing.T) {
rc := clients.ipToRC[ip]
require.NotNil(t, rc)
assert.Equal(t, rc.WHOISInfo, whois)
assert.Equal(t, rc.WHOIS, whois)
})
t.Run("existing_auto-client", func(t *testing.T) {
@@ -230,7 +222,7 @@ func TestClientsWHOIS(t *testing.T) {
rc := clients.ipToRC[ip]
require.NotNil(t, rc)
assert.Equal(t, rc.WHOISInfo, whois)
assert.Equal(t, rc.WHOIS, whois)
})
t.Run("can't_set_manually-added", func(t *testing.T) {

View File

@@ -9,6 +9,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
)
// clientJSON is a common structure used by several handlers to deal with
@@ -28,7 +29,8 @@ type clientJSON struct {
// the allowlist.
DisallowedRule *string `json:"disallowed_rule,omitempty"`
WHOISInfo *RuntimeClientWHOISInfo `json:"whois_info,omitempty"`
// WHOIS is the filtered WHOIS data of a client.
WHOIS *whois.Info `json:"whois_info,omitempty"`
SafeSearchConf *filtering.SafeSearchConfig `json:"safe_search"`
Name string `json:"name"`
@@ -51,7 +53,7 @@ type clientJSON struct {
}
type runtimeClientJSON struct {
WHOISInfo *RuntimeClientWHOISInfo `json:"whois_info"`
WHOIS *whois.Info `json:"whois_info"`
IP netip.Addr `json:"ip"`
Name string `json:"name"`
@@ -78,7 +80,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
for ip, rc := range clients.ipToRC {
cj := runtimeClientJSON{
WHOISInfo: rc.WHOISInfo,
WHOIS: rc.WHOIS,
Name: rc.Host,
Source: rc.Source,
@@ -289,7 +291,7 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
return
}
err = clients.Update(dj.Name, c)
err = clients.Update(prev, c)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
@@ -344,16 +346,16 @@ func (clients *clientsContainer) findRuntime(ip netip.Addr, idStr string) (cj *c
IDs: []string{idStr},
Disallowed: &disallowed,
DisallowedRule: &rule,
WHOISInfo: &RuntimeClientWHOISInfo{},
WHOIS: &whois.Info{},
}
return cj
}
cj = &clientJSON{
Name: rc.Host,
IDs: []string{idStr},
WHOISInfo: rc.WHOISInfo,
Name: rc.Host,
IDs: []string{idStr},
WHOIS: rc.WHOIS,
}
disallowed, rule := clients.dnsServer.IsBlockedClient(ip, idStr)

View File

@@ -14,6 +14,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
"github.com/AdguardTeam/AdGuardHome/internal/stats"
"github.com/AdguardTeam/dnsproxy/fastip"
"github.com/AdguardTeam/golibs/errors"
@@ -316,6 +317,11 @@ var config = &configuration{
Yandex: true,
YouTube: true,
},
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
IDs: []string{},
},
},
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
UsePrivateRDNS: true,

View File

@@ -8,6 +8,7 @@ import (
"net/url"
"os"
"path/filepath"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
@@ -17,6 +18,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
"github.com/AdguardTeam/AdGuardHome/internal/stats"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
@@ -25,7 +27,7 @@ import (
yaml "gopkg.in/yaml.v3"
)
// Default ports.
// Default listening ports.
const (
defaultPortDNS = 53
defaultPortHTTP = 80
@@ -169,13 +171,72 @@ func initDNSServer(
Context.rdns = NewRDNS(Context.dnsServer, &Context.clients, config.DNS.UsePrivateRDNS)
}
if config.Clients.Sources.WHOIS {
Context.whois = initWHOIS(&Context.clients)
}
initWHOIS()
return nil
}
// initWHOIS initializes the WHOIS.
//
// TODO(s.chzhen): Consider making configurable.
func initWHOIS() {
const (
// defaultQueueSize is the size of queue of IPs for WHOIS processing.
defaultQueueSize = 255
// defaultTimeout is the timeout for WHOIS requests.
defaultTimeout = 5 * time.Second
// defaultCacheSize is the maximum size of the cache. If it's zero,
// cache size is unlimited.
defaultCacheSize = 10_000
// defaultMaxConnReadSize is an upper limit in bytes for reading from
// net.Conn.
defaultMaxConnReadSize = 64 * 1024
// defaultMaxRedirects is the maximum redirects count.
defaultMaxRedirects = 5
// defaultMaxInfoLen is the maximum length of whois.Info fields.
defaultMaxInfoLen = 250
// defaultIPTTL is the Time to Live duration for cached IP addresses.
defaultIPTTL = 1 * time.Hour
)
Context.whoisCh = make(chan netip.Addr, defaultQueueSize)
var w whois.Interface
if config.Clients.Sources.WHOIS {
w = whois.New(&whois.Config{
DialContext: customDialContext,
ServerAddr: whois.DefaultServer,
Port: whois.DefaultPort,
Timeout: defaultTimeout,
CacheSize: defaultCacheSize,
MaxConnReadSize: defaultMaxConnReadSize,
MaxRedirects: defaultMaxRedirects,
MaxInfoLen: defaultMaxInfoLen,
CacheTTL: defaultIPTTL,
})
} else {
w = whois.Empty{}
}
go func() {
defer log.OnPanic("whois")
for ip := range Context.whoisCh {
info, changed := w.Process(context.Background(), ip)
if info != nil && changed {
Context.clients.setWHOISInfo(ip, info)
}
}
}()
}
// parseSubnetSet parses a slice of subnets. If the slice is empty, it returns
// a subnet set that matches all locally served networks, see
// [netutil.IsLocallyServed].
@@ -218,9 +279,7 @@ func onDNSRequest(pctx *proxy.DNSContext) {
Context.rdns.Begin(ip)
}
if srcs.WHOIS && !netutil.IsSpecialPurposeAddr(ip) {
Context.whois.Begin(ip)
}
Context.whoisCh <- ip
}
func ipsToTCPAddrs(ips []netip.Addr, port int) (tcpAddrs []*net.TCPAddr) {
@@ -390,7 +449,7 @@ func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering
// pref is a prefix for logging messages around the scope.
const pref = "applying filters"
Context.filters.ApplyBlockedServices(setts, nil)
Context.filters.ApplyBlockedServices(setts)
log.Debug("%s: looking for client with ip %s and clientid %q", pref, clientIP, clientID)
@@ -418,7 +477,7 @@ func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering
if svcs == nil {
svcs = []string{}
}
Context.filters.ApplyBlockedServices(setts, svcs)
Context.filters.ApplyBlockedServicesList(setts, svcs)
log.Debug("%s: services for client %q set: %s", pref, c.Name, svcs)
}
@@ -463,9 +522,7 @@ func startDNSServer() error {
Context.rdns.Begin(ip)
}
if srcs.WHOIS && !netutil.IsSpecialPurposeAddr(ip) {
Context.whois.Begin(ip)
}
Context.whoisCh <- ip
}
return nil

View File

@@ -57,7 +57,6 @@ type homeContext struct {
queryLog querylog.QueryLog // query log module
dnsServer *dnsforward.Server // DNS module
rdns *RDNS // rDNS module
whois *WHOIS // WHOIS module
dhcpServer dhcpd.Interface // DHCP module
auth *Auth // HTTP authentication module
filters *filtering.DNSFilter // DNS filtering module
@@ -84,6 +83,9 @@ type homeContext struct {
client *http.Client
appSignalChannel chan os.Signal // Channel for receiving OS signals by the console app
// whoisCh is the channel for receiving IPs for WHOIS processing.
whoisCh chan netip.Addr
// tlsCipherIDs are the ID of the cipher suites that AdGuard Home must use.
tlsCipherIDs []uint16

View File

@@ -3,13 +3,13 @@ package home
import (
"io"
"net/http"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
"github.com/AdguardTeam/golibs/log"
)
// middlerware is a wrapper function signature.
// middleware is a wrapper function signature.
type middleware func(http.Handler) http.Handler
// withMiddlewares consequently wraps h with all the middlewares.
@@ -75,3 +75,48 @@ func limitRequestBody(h http.Handler) (limited http.Handler) {
h.ServeHTTP(w, rr)
})
}
const (
// defaultWriteTimeout is the maximum duration before timing out writes of
// the response.
defaultWriteTimeout = 60 * time.Second
// longerWriteTimeout is the maximum duration before timing out for APIs
// expecting longer response requests.
longerWriteTimeout = 5 * time.Minute
)
// expectsLongTimeoutRequests shows if this request should use a bigger write
// timeout value. These are exceptions for poorly designed current APIs as
// well as APIs that are designed to expect large files and requests. Remove
// once the new, better APIs are up.
//
// TODO(d.kolyshev): This could be achieved with [http.NewResponseController]
// with go v1.20.
func expectsLongTimeoutRequests(r *http.Request) (ok bool) {
if r.Method != http.MethodGet {
return false
}
return r.URL.Path == "/control/querylog/export"
}
// addWriteTimeout wraps underlying handler h, adding a response write timeout.
func addWriteTimeout(h http.Handler) (limited http.Handler) {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var handler http.Handler
if expectsLongTimeoutRequests(r) {
handler = http.TimeoutHandler(h, longerWriteTimeout, "write timeout exceeded")
} else {
handler = http.TimeoutHandler(h, defaultWriteTimeout, "write timeout exceeded")
}
handler.ServeHTTP(w, r)
})
}
// limitHandler wraps underlying handler h with default limits, such as request
// body limit and write timeout.
func limitHandler(h http.Handler) (limited http.Handler) {
return limitRequestBody(addWriteTimeout(h))
}

View File

@@ -22,7 +22,7 @@ import (
)
// currentSchemaVersion is the current schema version.
const currentSchemaVersion = 20
const currentSchemaVersion = 21
// These aliases are provided for convenience.
type (
@@ -94,6 +94,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
upgradeSchema17to18,
upgradeSchema18to19,
upgradeSchema19to20,
upgradeSchema20to21,
}
n := 0
@@ -1128,6 +1129,56 @@ func upgradeSchema19to20(diskConf yobj) (err error) {
return nil
}
// upgradeSchema20to21 performs the following changes:
//
// # BEFORE:
// 'dns':
// 'blocked_services':
// - 'svc_name'
//
// # AFTER:
// 'dns':
// 'blocked_services':
// 'ids':
// - 'svc_name'
// 'schedule':
// 'time_zone': 'Local'
func upgradeSchema20to21(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 20 to 21")
diskConf["schema_version"] = 21
const field = "blocked_services"
dnsVal, ok := diskConf["dns"]
if !ok {
return nil
}
dns, ok := dnsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
}
blockedVal, ok := dns[field]
if !ok {
return nil
}
services, ok := blockedVal.(yarr)
if !ok {
return fmt.Errorf("unexpected type of blocked: %T", blockedVal)
}
dns[field] = yobj{
"ids": services,
"schedule": yobj{
"time_zone": "Local",
},
}
return nil
}
// TODO(a.garipov): Replace with log.Output when we port it to our logging
// package.
func funcName() string {

View File

@@ -1140,3 +1140,46 @@ func TestUpgradeSchema19to20(t *testing.T) {
assert.Equal(t, 24*time.Hour, ivlVal.Duration)
})
}
func TestUpgradeSchema20to21(t *testing.T) {
const newSchemaVer = 21
testCases := []struct {
in yobj
want yobj
name string
}{{
name: "nothing",
in: yobj{},
want: yobj{
"schema_version": newSchemaVer,
},
}, {
name: "no_clients",
in: yobj{
"dns": yobj{
"blocked_services": yarr{"ok"},
},
},
want: yobj{
"dns": yobj{
"blocked_services": yobj{
"ids": yarr{"ok"},
"schedule": yobj{
"time_zone": "Local",
},
},
},
"schema_version": newSchemaVer,
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := upgradeSchema20to21(tc.in)
require.NoError(t, err)
assert.Equal(t, tc.want, tc.in)
})
}
}

View File

@@ -25,11 +25,13 @@ const (
// readTimeout is the maximum duration for reading the entire request,
// including the body.
readTimeout = 60 * time.Second
// readHdrTimeout is the amount of time allowed to read request headers.
readHdrTimeout = 60 * time.Second
// writeTimeout is the maximum duration before timing out writes of the
// response.
writeTimeout = 60 * time.Second
// response. This limit is overwritten by [addWriteTimeout] middleware.
writeTimeout = 10 * time.Minute
)
type webConfig struct {
@@ -169,7 +171,7 @@ func (web *webAPI) start() {
errs := make(chan error, 2)
// Use an h2c handler to support unencrypted HTTP/2, e.g. for proxies.
hdlr := h2c.NewHandler(withMiddlewares(Context.mux, limitRequestBody), &http2.Server{})
hdlr := h2c.NewHandler(withMiddlewares(Context.mux, limitHandler), &http2.Server{})
// Create a new instance, because the Web is not usable after Shutdown.
hostStr := web.conf.BindHost.String()
@@ -254,7 +256,7 @@ func (web *webAPI) tlsServerLoop() {
CipherSuites: Context.tlsCipherIDs,
MinVersion: tls.VersionTLS12,
},
Handler: withMiddlewares(Context.mux, limitRequestBody),
Handler: withMiddlewares(Context.mux, limitHandler),
ReadTimeout: web.conf.ReadTimeout,
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
WriteTimeout: web.conf.WriteTimeout,
@@ -288,7 +290,7 @@ func (web *webAPI) mustStartHTTP3(address string) {
CipherSuites: Context.tlsCipherIDs,
MinVersion: tls.VersionTLS12,
},
Handler: withMiddlewares(Context.mux, limitRequestBody),
Handler: withMiddlewares(Context.mux, limitHandler),
}
log.Debug("web: starting http/3 server")

View File

@@ -1,259 +0,0 @@
package home
import (
"context"
"encoding/binary"
"fmt"
"io"
"net"
"net/netip"
"strings"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
"github.com/AdguardTeam/golibs/cache"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
)
const (
defaultServer = "whois.arin.net"
defaultPort = "43"
maxValueLength = 250
whoisTTL = 1 * 60 * 60 // 1 hour
)
// WHOIS - module context
type WHOIS struct {
clients *clientsContainer
ipChan chan netip.Addr
// dialContext specifies the dial function for creating unencrypted TCP
// connections.
dialContext func(ctx context.Context, network, addr string) (conn net.Conn, err error)
// Contains IP addresses of clients
// An active IP address is resolved once again after it expires.
// If IP address couldn't be resolved, it stays here for some time to prevent further attempts to resolve the same IP.
ipAddrs cache.Cache
// TODO(a.garipov): Rewrite to use time.Duration. Like, seriously, why?
timeoutMsec uint
}
// initWHOIS creates the WHOIS module context.
func initWHOIS(clients *clientsContainer) *WHOIS {
w := WHOIS{
timeoutMsec: 5000,
clients: clients,
ipAddrs: cache.New(cache.Config{
EnableLRU: true,
MaxCount: 10000,
}),
dialContext: customDialContext,
ipChan: make(chan netip.Addr, 255),
}
go w.workerLoop()
return &w
}
// If the value is too large - cut it and append "..."
func trimValue(s string) string {
if len(s) <= maxValueLength {
return s
}
return s[:maxValueLength-3] + "..."
}
// isWHOISComment returns true if the string is empty or is a WHOIS comment.
func isWHOISComment(s string) (ok bool) {
return len(s) == 0 || s[0] == '#' || s[0] == '%'
}
// strmap is an alias for convenience.
type strmap = map[string]string
// whoisParse parses a subset of plain-text data from the WHOIS response into
// a string map.
func whoisParse(data string) (m strmap) {
m = strmap{}
var orgname string
lines := strings.Split(data, "\n")
for _, l := range lines {
if isWHOISComment(l) {
continue
}
kv := strings.SplitN(l, ":", 2)
if len(kv) != 2 {
continue
}
k := strings.ToLower(strings.TrimSpace(kv[0]))
v := strings.TrimSpace(kv[1])
if v == "" {
continue
}
switch k {
case "orgname", "org-name":
k = "orgname"
v = trimValue(v)
orgname = v
case "city", "country":
v = trimValue(v)
case "descr", "netname":
k = "orgname"
v = stringutil.Coalesce(orgname, v)
orgname = v
case "whois":
k = "whois"
case "referralserver":
k = "whois"
v = strings.TrimPrefix(v, "whois://")
default:
continue
}
m[k] = v
}
return m
}
// MaxConnReadSize is an upper limit in bytes for reading from net.Conn.
const MaxConnReadSize = 64 * 1024
// Send request to a server and receive the response
func (w *WHOIS) query(ctx context.Context, target, serverAddr string) (data string, err error) {
addr, _, _ := net.SplitHostPort(serverAddr)
if addr == "whois.arin.net" {
target = "n + " + target
}
conn, err := w.dialContext(ctx, "tcp", serverAddr)
if err != nil {
return "", err
}
defer func() { err = errors.WithDeferred(err, conn.Close()) }()
r, err := aghio.LimitReader(conn, MaxConnReadSize)
if err != nil {
return "", err
}
_ = conn.SetReadDeadline(time.Now().Add(time.Duration(w.timeoutMsec) * time.Millisecond))
_, err = conn.Write([]byte(target + "\r\n"))
if err != nil {
return "", err
}
// This use of ReadAll is now safe, because we limited the conn Reader.
var whoisData []byte
whoisData, err = io.ReadAll(r)
if err != nil {
return "", err
}
return string(whoisData), nil
}
// Query WHOIS servers (handle redirects)
func (w *WHOIS) queryAll(ctx context.Context, target string) (string, error) {
server := net.JoinHostPort(defaultServer, defaultPort)
const maxRedirects = 5
for i := 0; i != maxRedirects; i++ {
resp, err := w.query(ctx, target, server)
if err != nil {
return "", err
}
log.Debug("whois: received response (%d bytes) from %s IP:%s", len(resp), server, target)
m := whoisParse(resp)
redir, ok := m["whois"]
if !ok {
return resp, nil
}
redir = strings.ToLower(redir)
_, _, err = net.SplitHostPort(redir)
if err != nil {
server = net.JoinHostPort(redir, defaultPort)
} else {
server = redir
}
log.Debug("whois: redirected to %s IP:%s", redir, target)
}
return "", fmt.Errorf("whois: redirect loop")
}
// Request WHOIS information
func (w *WHOIS) process(ctx context.Context, ip netip.Addr) (wi *RuntimeClientWHOISInfo) {
resp, err := w.queryAll(ctx, ip.String())
if err != nil {
log.Debug("whois: error: %s IP:%s", err, ip)
return nil
}
log.Debug("whois: IP:%s response: %d bytes", ip, len(resp))
m := whoisParse(resp)
wi = &RuntimeClientWHOISInfo{
City: m["city"],
Country: m["country"],
Orgname: m["orgname"],
}
// Don't return an empty struct so that the frontend doesn't get
// confused.
if *wi == (RuntimeClientWHOISInfo{}) {
return nil
}
return wi
}
// Begin - begin requesting WHOIS info
func (w *WHOIS) Begin(ip netip.Addr) {
ipBytes := ip.AsSlice()
now := uint64(time.Now().Unix())
expire := w.ipAddrs.Get(ipBytes)
if len(expire) != 0 {
exp := binary.BigEndian.Uint64(expire)
if exp > now {
return
}
}
expire = make([]byte, 8)
binary.BigEndian.PutUint64(expire, now+whoisTTL)
_ = w.ipAddrs.Set(ipBytes, expire)
log.Debug("whois: adding %s", ip)
select {
case w.ipChan <- ip:
default:
log.Debug("whois: queue is full")
}
}
// workerLoop processes the IP addresses it got from the channel and associates
// the retrieving WHOIS info with a client.
func (w *WHOIS) workerLoop() {
for ip := range w.ipChan {
info := w.process(context.Background(), ip)
if info == nil {
continue
}
w.clients.setWHOISInfo(ip, info)
}
}

View File

@@ -1,152 +0,0 @@
package home
import (
"context"
"io"
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// fakeConn is a mock implementation of net.Conn to simplify testing.
//
// TODO(e.burkov): Search for other places in code where it may be used. Move
// into aghtest then.
type fakeConn struct {
// Conn is embedded here simply to make *fakeConn a net.Conn without
// actually implementing all methods.
net.Conn
data []byte
}
// Write implements net.Conn interface for *fakeConn. It always returns 0 and a
// nil error without mutating the slice.
func (c *fakeConn) Write(_ []byte) (n int, err error) {
return 0, nil
}
// Read implements net.Conn interface for *fakeConn. It puts the content of
// c.data field into b up to the b's capacity.
func (c *fakeConn) Read(b []byte) (n int, err error) {
return copy(b, c.data), io.EOF
}
// Close implements net.Conn interface for *fakeConn. It always returns nil.
func (c *fakeConn) Close() (err error) {
return nil
}
// SetReadDeadline implements net.Conn interface for *fakeConn. It always
// returns nil.
func (c *fakeConn) SetReadDeadline(_ time.Time) (err error) {
return nil
}
// fakeDial is a mock implementation of customDialContext to simplify testing.
func (c *fakeConn) fakeDial(ctx context.Context, network, addr string) (conn net.Conn, err error) {
return c, nil
}
func TestWHOIS(t *testing.T) {
const (
nl = "\n"
data = `OrgName: FakeOrg LLC` + nl +
`City: Nonreal` + nl +
`Country: Imagiland` + nl
)
fc := &fakeConn{
data: []byte(data),
}
w := WHOIS{
timeoutMsec: 5000,
dialContext: fc.fakeDial,
}
resp, err := w.queryAll(context.Background(), "1.2.3.4")
assert.NoError(t, err)
m := whoisParse(resp)
require.NotEmpty(t, m)
assert.Equal(t, "FakeOrg LLC", m["orgname"])
assert.Equal(t, "Imagiland", m["country"])
assert.Equal(t, "Nonreal", m["city"])
}
func TestWHOISParse(t *testing.T) {
const (
city = "Nonreal"
country = "Imagiland"
orgname = "FakeOrgLLC"
whois = "whois.example.net"
)
testCases := []struct {
want strmap
name string
in string
}{{
want: strmap{},
name: "empty",
in: ``,
}, {
want: strmap{},
name: "comments",
in: "%\n#",
}, {
want: strmap{},
name: "no_colon",
in: "city",
}, {
want: strmap{},
name: "no_value",
in: "city:",
}, {
want: strmap{"city": city},
name: "city",
in: `city: ` + city,
}, {
want: strmap{"country": country},
name: "country",
in: `country: ` + country,
}, {
want: strmap{"orgname": orgname},
name: "orgname",
in: `orgname: ` + orgname,
}, {
want: strmap{"orgname": orgname},
name: "orgname_hyphen",
in: `org-name: ` + orgname,
}, {
want: strmap{"orgname": orgname},
name: "orgname_descr",
in: `descr: ` + orgname,
}, {
want: strmap{"orgname": orgname},
name: "orgname_netname",
in: `netname: ` + orgname,
}, {
want: strmap{"whois": whois},
name: "whois",
in: `whois: ` + whois,
}, {
want: strmap{"whois": whois},
name: "referralserver",
in: `referralserver: whois://` + whois,
}, {
want: strmap{},
name: "other",
in: `other: value`,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := whoisParse(tc.in)
assert.Equal(t, tc.want, got)
})
}
}

View File

@@ -0,0 +1,39 @@
# AdGuard Home v0.108.0 Changelog DRAFT
This changelog should be merged into the main one once the next API matures
enough.
## [v0.108.0] - TODO
### Added
- The ability to log to stderr using `--logFile=stderr`.
- The new `--web-addr` flag to set the Web UI address in a `host:port` form.
- `SIGHUP` now reloads all configuration from the configuration file ([#5676]).
### Changed
#### New HTTP API
**TODO(a.garipov):** Describe the new API and add a link to the new OpenAPI doc.
#### Other changes
- `-h` is now an alias for `--help` instead of the removed `--host`, see below.
Use `--web-addr=host:port` to set an address on which to serve the Web UI.
### Fixed
- Inconsistent application of `--work-dir/-w` ([#2598], [#2902]).
- The order of `-v/--verbose` and `--version` being significant ([#2893]).
### Removed
- The deprecated `--no-mem-optimization` and `--no-etc-hosts` flags.
- `--host` and `-p/--port` flags. Use `--web-addr=host:port` to set an address
on which to serve the Web UI. `-h` is now an alias for `--help`, see above.
[#2598]: https://github.com/AdguardTeam/AdGuardHome/issues/2598
[#2893]: https://github.com/AdguardTeam/AdGuardHome/issues/2893
[#2902]: https://github.com/AdguardTeam/AdGuardHome/issues/2902
[#5676]: https://github.com/AdguardTeam/AdGuardHome/issues/5676

View File

@@ -1,5 +1,5 @@
// Package cmd is the AdGuard Home entry point. It contains the on-disk
// configuration file utilities, signal processing logic, and so on.
// Package cmd is the AdGuard Home entry point. It assembles the configuration
// file manager, sets up signal processing logic, and so on.
//
// TODO(a.garipov): Move to the upper-level internal/.
package cmd
@@ -7,7 +7,6 @@ package cmd
import (
"context"
"io/fs"
"math/rand"
"os"
"time"
@@ -16,51 +15,57 @@ import (
"github.com/AdguardTeam/golibs/log"
)
// Main is the entry point of application.
func Main(clientBuildFS fs.FS) {
// Main is the entry point of AdGuard Home.
func Main(frontend fs.FS) {
start := time.Now()
// Initial Configuration
start := time.Now()
rand.Seed(start.UnixNano())
cmdName := os.Args[0]
opts, err := parseOptions(cmdName, os.Args[1:])
exitCode, needExit := processOptions(opts, cmdName, err)
if needExit {
os.Exit(exitCode)
}
// TODO(a.garipov): Set up logging.
err = setLog(opts)
check(err)
log.Info("starting adguard home, version %s, pid %d", version.Version(), os.Getpid())
if opts.workDir != "" {
log.Info("changing working directory to %q", opts.workDir)
err = os.Chdir(opts.workDir)
check(err)
}
// Web Service
// TODO(a.garipov): Use in the Web service.
_ = clientBuildFS
// TODO(a.garipov): Set up configuration file name.
const confFile = "AdGuardHome.1.yaml"
confMgr, err := configmgr.New(confFile, start)
fatalOnError(err)
confMgr, err := configmgr.New(opts.confFile, frontend, start)
check(err)
web := confMgr.Web()
err = web.Start()
fatalOnError(err)
check(err)
dns := confMgr.DNS()
err = dns.Start()
fatalOnError(err)
check(err)
sigHdlr := newSignalHandler(
confFile,
opts.confFile,
frontend,
start,
web,
dns,
)
go sigHdlr.handle()
select {}
sigHdlr.handle()
}
// defaultTimeout is the timeout used for some operations where another timeout
// hasn't been defined yet.
const defaultTimeout = 15 * time.Second
const defaultTimeout = 5 * time.Second
// ctxWithDefaultTimeout is a helper function that returns a context with
// timeout set to defaultTimeout.
@@ -68,10 +73,9 @@ func ctxWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(context.Background(), defaultTimeout)
}
// fatalOnError is a helper that exits the program with an error code if err is
// not nil. It must only be used within Main.
func fatalOnError(err error) {
// check is a simple error-checking helper. It must only be used within Main.
func check(err error) {
if err != nil {
log.Fatal(err)
panic(err)
}
}

39
internal/next/cmd/log.go Normal file
View File

@@ -0,0 +1,39 @@
package cmd
import (
"fmt"
"os"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/golibs/log"
)
// syslogServiceName is the name of the AdGuard Home service used for writing
// logs to the system log.
const syslogServiceName = "AdGuardHome"
// setLog sets up the text logging.
//
// TODO(a.garipov): Add parameters from configuration file.
func setLog(opts *options) (err error) {
switch opts.confFile {
case "stdout":
log.SetOutput(os.Stdout)
case "stderr":
log.SetOutput(os.Stderr)
case "syslog":
err = aghos.ConfigureSyslog(syslogServiceName)
if err != nil {
return fmt.Errorf("initializing syslog: %w", err)
}
default:
// TODO(a.garipov): Use the path.
}
if opts.verbose {
log.SetLevel(log.DEBUG)
log.Debug("verbose logging enabled")
}
return nil
}

403
internal/next/cmd/opt.go Normal file
View File

@@ -0,0 +1,403 @@
package cmd
import (
"flag"
"fmt"
"io"
"net/netip"
"os"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/version"
"golang.org/x/exp/slices"
)
// options contains all command-line options for the AdGuardHome(.exe) binary.
type options struct {
// confFile is the path to the configuration file.
confFile string
// logFile is the path to the log file. Special values:
//
// - "stdout": Write to stdout (the default).
// - "stderr": Write to stderr.
// - "syslog": Write to the system log.
logFile string
// pidFile is the path to the file where to store the PID.
//
// TODO(a.garipov): Use.
pidFile string
// serviceAction is the service control action to perform:
//
// - "install": Installs AdGuard Home as a system service.
// - "uninstall": Uninstalls it.
// - "status": Prints the service status.
// - "start": Starts the previously installed service.
// - "stop": Stops the previously installed service.
// - "restart": Restarts the previously installed service.
// - "reload": Reloads the configuration.
// - "run": This is a special command that is not supposed to be used
// directly it is specified when we register a service, and it indicates
// to the app that it is being run as a service.
//
// TODO(a.garipov): Use.
serviceAction string
// workDir is the path to the working directory. It is applied before all
// other configuration is read, so all relative paths are relative to it.
workDir string
// webAddrs contains the addresses on which to serve the web UI.
//
// TODO(a.garipov): Use.
webAddrs []netip.AddrPort
// checkConfig, if true, instructs AdGuard Home to check the configuration
// file and exit with a corresponding exit code.
//
// TODO(a.garipov): Use.
checkConfig bool
// disableUpdate, if true, prevents AdGuard Home from automatically checking
// for updates.
//
// TODO(a.garipov): Use.
disableUpdate bool
// glinetMode enables the GL-Inet compatibility mode.
//
// TODO(a.garipov): Use.
glinetMode bool
// help, if true, instructs AdGuard Home to print the command-line option
// help message and quit with a successful exit-code.
help bool
// localFrontend, if true, instructs AdGuard Home to use the local frontend
// directory instead of the files compiled into the binary.
//
// TODO(a.garipov): Use.
localFrontend bool
// performUpdate, if true, instructs AdGuard Home to update the current
// binary and restart the service in case it's installed.
//
// TODO(a.garipov): Use.
performUpdate bool
// verbose, if true, instructs AdGuard Home to enable verbose logging.
verbose bool
// version, if true, instructs AdGuard Home to print the version to stdout
// and quit with a successful exit-code. If verbose is also true, print a
// more detailed version description.
version bool
}
// Indexes to help with the [commandLineOptions] initialization.
const (
confFileIdx = iota
logFileIdx
pidFileIdx
serviceActionIdx
workDirIdx
webAddrsIdx
checkConfigIdx
disableUpdateIdx
glinetModeIdx
helpIdx
localFrontend
performUpdateIdx
verboseIdx
versionIdx
)
// commandLineOption contains information about a command-line option: its long
// and, if there is one, short forms, the value type, the description, and the
// default value.
type commandLineOption struct {
defaultValue any
description string
long string
short string
valueType string
}
// commandLineOptions are all command-line options currently supported by
// AdGuard Home.
var commandLineOptions = []*commandLineOption{
confFileIdx: {
// TODO(a.garipov): Remove the ".1" when the new code is ready.
defaultValue: "AdGuardHome.1.yaml",
description: "Path to the config file.",
long: "config",
short: "c",
valueType: "path",
},
logFileIdx: {
defaultValue: "stdout",
description: `Path to log file. Special values include "stdout", "stderr", and "syslog".`,
long: "logfile",
short: "l",
valueType: "path",
},
pidFileIdx: {
defaultValue: "",
description: "Path to the file where to store the PID.",
long: "pidfile",
short: "",
valueType: "path",
},
serviceActionIdx: {
defaultValue: "",
description: `Service control action: "status", "install" (as a service), ` +
`"uninstall" (as a service), "start", "stop", "restart", "reload" (configuration).`,
long: "service",
short: "s",
valueType: "action",
},
workDirIdx: {
defaultValue: "",
description: `Path to the working directory. ` +
`It is applied before all other configuration is read, ` +
`so all relative paths are relative to it.`,
long: "work-dir",
short: "w",
valueType: "path",
},
webAddrsIdx: {
defaultValue: []netip.AddrPort(nil),
description: `Address(es) to serve the web UI on, in the host:port format. ` +
`Can be used multiple times.`,
long: "web-addr",
short: "",
valueType: "host:port",
},
checkConfigIdx: {
defaultValue: false,
description: "Check configuration and quit.",
long: "check-config",
short: "",
valueType: "",
},
disableUpdateIdx: {
defaultValue: false,
description: "Disable automatic update checking.",
long: "no-check-update",
short: "",
valueType: "",
},
glinetModeIdx: {
defaultValue: false,
description: "Run in GL-Inet compatibility mode.",
long: "glinet",
short: "",
valueType: "",
},
helpIdx: {
defaultValue: false,
description: "Print this help message and quit.",
long: "help",
short: "h",
valueType: "",
},
localFrontend: {
defaultValue: false,
description: "Use local frontend directories.",
long: "local-frontend",
short: "",
valueType: "",
},
performUpdateIdx: {
defaultValue: false,
description: "Update the current binary and restart the service in case it's installed.",
long: "update",
short: "",
valueType: "",
},
verboseIdx: {
defaultValue: false,
description: "Enable verbose logging.",
long: "verbose",
short: "v",
valueType: "",
},
versionIdx: {
defaultValue: false,
description: `Print the version to stdout and quit. ` +
`Print a more detailed version description with -v.`,
long: "version",
short: "",
valueType: "",
},
}
// parseOptions parses the command-line options for AdGuardHome.
func parseOptions(cmdName string, args []string) (opts *options, err error) {
flags := flag.NewFlagSet(cmdName, flag.ContinueOnError)
opts = &options{}
for i, fieldPtr := range []any{
confFileIdx: &opts.confFile,
logFileIdx: &opts.logFile,
pidFileIdx: &opts.pidFile,
serviceActionIdx: &opts.serviceAction,
workDirIdx: &opts.workDir,
webAddrsIdx: &opts.webAddrs,
checkConfigIdx: &opts.checkConfig,
disableUpdateIdx: &opts.disableUpdate,
glinetModeIdx: &opts.glinetMode,
helpIdx: &opts.help,
localFrontend: &opts.localFrontend,
performUpdateIdx: &opts.performUpdate,
verboseIdx: &opts.verbose,
versionIdx: &opts.version,
} {
addOption(flags, fieldPtr, commandLineOptions[i])
}
flags.Usage = func() { usage(cmdName, os.Stderr) }
err = flags.Parse(args)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return nil, err
}
return opts, nil
}
// addOption adds the command-line option described by o to flags using fieldPtr
// as the pointer to the value.
func addOption(flags *flag.FlagSet, fieldPtr any, o *commandLineOption) {
switch fieldPtr := fieldPtr.(type) {
case *string:
flags.StringVar(fieldPtr, o.long, o.defaultValue.(string), o.description)
if o.short != "" {
flags.StringVar(fieldPtr, o.short, o.defaultValue.(string), o.description)
}
case *[]netip.AddrPort:
flags.Func(o.long, o.description, func(s string) (err error) {
addr, err := netip.ParseAddrPort(s)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
*fieldPtr = append(*fieldPtr, addr)
return nil
})
case *bool:
flags.BoolVar(fieldPtr, o.long, o.defaultValue.(bool), o.description)
if o.short != "" {
flags.BoolVar(fieldPtr, o.short, o.defaultValue.(bool), o.description)
}
default:
panic(fmt.Errorf("unexpected field pointer type %T", fieldPtr))
}
}
// usage prints a usage message similar to the one printed by package flag but
// taking long vs. short versions into account as well as using more informative
// value hints.
func usage(cmdName string, output io.Writer) {
options := slices.Clone(commandLineOptions)
slices.SortStableFunc(options, func(a, b *commandLineOption) (sortsBefore bool) {
return a.long < b.long
})
b := &strings.Builder{}
_, _ = fmt.Fprintf(b, "Usage of %s:\n", cmdName)
for _, o := range options {
writeUsageLine(b, o)
// Use four spaces before the tab to trigger good alignment for both 4-
// and 8-space tab stops.
if shouldIncludeDefault(o.defaultValue) {
_, _ = fmt.Fprintf(b, " \t%s (Default value: %q)\n", o.description, o.defaultValue)
} else {
_, _ = fmt.Fprintf(b, " \t%s\n", o.description)
}
}
_, _ = io.WriteString(output, b.String())
}
// shouldIncludeDefault returns true if this default value should be printed.
func shouldIncludeDefault(v any) (ok bool) {
switch v := v.(type) {
case bool:
return v
case string:
return v != ""
default:
return v == nil
}
}
// writeUsageLine writes the usage line for the provided command-line option.
func writeUsageLine(b *strings.Builder, o *commandLineOption) {
if o.short == "" {
if o.valueType == "" {
_, _ = fmt.Fprintf(b, " --%s\n", o.long)
} else {
_, _ = fmt.Fprintf(b, " --%s=%s\n", o.long, o.valueType)
}
return
}
if o.valueType == "" {
_, _ = fmt.Fprintf(b, " --%s/-%s\n", o.long, o.short)
} else {
_, _ = fmt.Fprintf(b, " --%[1]s=%[3]s/-%[2]s %[3]s\n", o.long, o.short, o.valueType)
}
}
// processOptions decides if AdGuard Home should exit depending on the results
// of command-line option parsing.
func processOptions(
opts *options,
cmdName string,
parseErr error,
) (exitCode int, needExit bool) {
if parseErr != nil {
// Assume that usage has already been printed.
return 2, true
}
if opts.help {
usage(cmdName, os.Stdout)
return 0, true
}
if opts.version {
if opts.verbose {
fmt.Println(version.Verbose())
} else {
fmt.Printf("AdGuard Home %s\n", version.Version())
}
return 0, true
}
return 0, false
}

View File

@@ -1,6 +1,7 @@
package cmd
import (
"io/fs"
"os"
"time"
@@ -18,6 +19,10 @@ type signalHandler struct {
// confFile is the path to the configuration file.
confFile string
// frontend is the filesystem with the frontend and other statically
// compiled files.
frontend fs.FS
// start is the time at which AdGuard Home has been started.
start time.Time
@@ -49,7 +54,7 @@ func (h *signalHandler) reconfigure() {
status := h.shutdown()
if status != statusSuccess {
log.Info("sighdlr: reconfiruging: exiting with status %d", status)
log.Info("sighdlr: reconfiguring: exiting with status %d", status)
os.Exit(status)
}
@@ -58,16 +63,16 @@ func (h *signalHandler) reconfigure() {
// reconfigured without the full shutdown, and the error handling is
// currently not the best.
confMgr, err := configmgr.New(h.confFile, h.start)
fatalOnError(err)
confMgr, err := configmgr.New(h.confFile, h.frontend, h.start)
check(err)
web := confMgr.Web()
err = web.Start()
fatalOnError(err)
check(err)
dns := confMgr.DNS()
err = dns.Start()
fatalOnError(err)
check(err)
h.services = []agh.Service{
dns,
@@ -103,10 +108,16 @@ func (h *signalHandler) shutdown() (status int) {
}
// newSignalHandler returns a new signalHandler that shuts down svcs.
func newSignalHandler(confFile string, start time.Time, svcs ...agh.Service) (h *signalHandler) {
func newSignalHandler(
confFile string,
frontend fs.FS,
start time.Time,
svcs ...agh.Service,
) (h *signalHandler) {
h = &signalHandler{
signal: make(chan os.Signal, 1),
confFile: confFile,
frontend: frontend,
start: start,
services: svcs,
}

View File

@@ -5,6 +5,7 @@ package configmgr
import (
"context"
"fmt"
"io/fs"
"os"
"sync"
"time"
@@ -42,8 +43,12 @@ type Manager struct {
// New creates a new *Manager that persists changes to the file pointed to by
// fileName. It reads the configuration file and populates the service fields.
// start is the startup time of AdGuard Home.
func New(fileName string, start time.Time) (m *Manager, err error) {
defer func() { err = errors.Annotate(err, "reading config") }()
func New(
fileName string,
frontend fs.FS,
start time.Time,
) (m *Manager, err error) {
defer func() { err = errors.Annotate(err, "reading config: %w") }()
conf := &config{}
f, err := os.Open(fileName)
@@ -79,7 +84,7 @@ func New(fileName string, start time.Time) (m *Manager, err error) {
ctx, cancel := context.WithTimeout(context.Background(), assemblyTimeout)
defer cancel()
err = m.assemble(ctx, conf, start)
err = m.assemble(ctx, conf, frontend, start)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return nil, err
@@ -90,7 +95,12 @@ func New(fileName string, start time.Time) (m *Manager, err error) {
// assemble creates all services and puts them into the corresponding fields.
// The fields of conf must not be modified after calling assemble.
func (m *Manager) assemble(ctx context.Context, conf *config, start time.Time) (err error) {
func (m *Manager) assemble(
ctx context.Context,
conf *config,
frontend fs.FS,
start time.Time,
) (err error) {
dnsConf := &dnssvc.Config{
Addresses: conf.DNS.Addresses,
BootstrapServers: conf.DNS.BootstrapDNS,
@@ -104,6 +114,7 @@ func (m *Manager) assemble(ctx context.Context, conf *config, start time.Time) (
webSvcConf := &websvc.Config{
ConfigManager: m,
Frontend: frontend,
// TODO(a.garipov): Fill from config file.
TLS: nil,
Start: start,
@@ -199,7 +210,10 @@ func (m *Manager) updateWeb(ctx context.Context, c *websvc.Config) (err error) {
}
}
m.web = websvc.New(c)
m.web, err = websvc.New(c)
if err != nil {
return fmt.Errorf("creating web svc: %w", err)
}
return nil
}

View File

@@ -53,6 +53,7 @@ func (svc *Service) handlePatchSettingsHTTP(w http.ResponseWriter, r *http.Reque
newConf := &Config{
ConfigManager: svc.confMgr,
Frontend: svc.frontend,
TLS: svc.tls,
Addresses: req.Addresses,
SecureAddresses: req.SecureAddresses,
@@ -77,34 +78,41 @@ func (svc *Service) handlePatchSettingsHTTP(w http.ResponseWriter, r *http.Reque
// Launch the new HTTP service in a separate goroutine to let this handler
// finish and thus, this server to shutdown.
go func() {
defer cancelUpd()
go svc.relaunch(updCtx, cancelUpd, newConf)
}
updErr := svc.confMgr.UpdateWeb(updCtx, newConf)
if updErr != nil {
writeJSONErrorResponse(w, r, fmt.Errorf("updating: %w", updErr))
// relaunch updates the web service in the configuration manager and starts it.
// It is intended to be used as a goroutine.
func (svc *Service) relaunch(ctx context.Context, cancel context.CancelFunc, newConf *Config) {
defer log.OnPanic("websvc: relaunching")
defer cancel()
err := svc.confMgr.UpdateWeb(ctx, newConf)
if err != nil {
log.Error("websvc: updating web: %s", err)
return
}
// TODO(a.garipov): Consider better ways to do this.
const maxUpdDur = 5 * time.Second
updStart := time.Now()
var newSvc agh.ServiceWithConfig[*Config]
for newSvc = svc.confMgr.Web(); newSvc == svc; {
if time.Since(updStart) >= maxUpdDur {
log.Error("websvc: failed to update svc after %s", maxUpdDur)
return
}
// TODO(a.garipov): Consider better ways to do this.
const maxUpdDur = 10 * time.Second
updStart := time.Now()
var newSvc agh.ServiceWithConfig[*Config]
for newSvc = svc.confMgr.Web(); newSvc == svc; {
if time.Since(updStart) >= maxUpdDur {
log.Error("websvc: failed to update svc after %s", maxUpdDur)
log.Debug("websvc: waiting for new websvc to be configured")
return
}
time.Sleep(100 * time.Millisecond)
}
log.Debug("websvc: waiting for new websvc to be configured")
time.Sleep(1 * time.Second)
}
updErr = newSvc.Start()
if updErr != nil {
log.Error("websvc: new svc failed to start with error: %s", updErr)
}
}()
err = newSvc.Start()
if err != nil {
log.Error("websvc: new svc failed to start with error: %s", err)
}
}

View File

@@ -24,21 +24,20 @@ func TestService_HandlePatchSettingsHTTP(t *testing.T) {
ForceHTTPS: false,
}
svc, err := websvc.New(&websvc.Config{
TLS: &tls.Config{
Certificates: []tls.Certificate{{}},
},
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:80")},
SecureAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:443")},
Timeout: 5 * time.Second,
ForceHTTPS: true,
})
require.NoError(t, err)
confMgr := newConfigManager()
confMgr.onWeb = func() (s agh.ServiceWithConfig[*websvc.Config]) {
return websvc.New(&websvc.Config{
TLS: &tls.Config{
Certificates: []tls.Certificate{{}},
},
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:80")},
SecureAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:443")},
Timeout: 5 * time.Second,
ForceHTTPS: true,
})
}
confMgr.onUpdateWeb = func(ctx context.Context, c *websvc.Config) (err error) {
return nil
}
confMgr.onWeb = func() (s agh.ServiceWithConfig[*websvc.Config]) { return svc }
confMgr.onUpdateWeb = func(ctx context.Context, c *websvc.Config) (err error) { return nil }
_, addr := newTestServer(t, confMgr)
u := &url.URL{
@@ -56,7 +55,7 @@ func TestService_HandlePatchSettingsHTTP(t *testing.T) {
respBody := httpPatch(t, u, req, http.StatusOK)
resp := &websvc.HTTPAPIHTTPSettings{}
err := json.Unmarshal(respBody, resp)
err = json.Unmarshal(respBody, resp)
require.NoError(t, err)
assert.Equal(t, wantWeb, resp)

View File

@@ -2,9 +2,11 @@ package websvc
import (
"net/http"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/log"
)
// Middlewares
@@ -19,3 +21,18 @@ func jsonMw(h http.Handler) (wrapped http.HandlerFunc) {
return http.HandlerFunc(f)
}
// logMw logs the queries with level debug.
func logMw(h http.Handler) (wrapped http.HandlerFunc) {
f := func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
m, u := r.Method, r.RequestURI
log.Debug("websvc: %s %s started", m, u)
defer func() { log.Debug("websvc: %s %s finished in %s", m, u, time.Since(start)) }()
h.ServeHTTP(w, r)
}
return http.HandlerFunc(f)
}

View File

@@ -2,6 +2,9 @@ package websvc
// Path constants
const (
PathRoot = "/"
PathFrontend = "/*filepath"
PathHealthCheck = "/health-check"
PathV1SettingsAll = "/api/v1/settings/all"

View File

@@ -46,16 +46,19 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
return c
}
svc, err := websvc.New(&websvc.Config{
TLS: &tls.Config{
Certificates: []tls.Certificate{{}},
},
Addresses: wantWeb.Addresses,
SecureAddresses: wantWeb.SecureAddresses,
Timeout: time.Duration(wantWeb.Timeout),
ForceHTTPS: true,
})
require.NoError(t, err)
confMgr.onWeb = func() (s agh.ServiceWithConfig[*websvc.Config]) {
return websvc.New(&websvc.Config{
TLS: &tls.Config{
Certificates: []tls.Certificate{{}},
},
Addresses: wantWeb.Addresses,
SecureAddresses: wantWeb.SecureAddresses,
Timeout: time.Duration(wantWeb.Timeout),
ForceHTTPS: true,
})
return svc
}
_, addr := newTestServer(t, confMgr)
@@ -67,7 +70,7 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
body := httpGet(t, u, http.StatusOK)
resp := &websvc.RespGetV1SettingsAll{}
err := json.Unmarshal(body, resp)
err = json.Unmarshal(body, resp)
require.NoError(t, err)
assert.Equal(t, wantDNS, resp.DNS)

View File

@@ -11,6 +11,7 @@ import (
"crypto/tls"
"fmt"
"io"
"io/fs"
"net"
"net/http"
"net/netip"
@@ -39,6 +40,10 @@ type Config struct {
// dynamically reconfigure them.
ConfigManager ConfigManager
// Frontend is the filesystem with the frontend and other statically
// compiled files.
Frontend fs.FS
// TLS is the optional TLS configuration. If TLS is not nil,
// SecureAddresses must not be empty.
TLS *tls.Config
@@ -67,6 +72,7 @@ type Config struct {
// [agh.Service] that does nothing.
type Service struct {
confMgr ConfigManager
frontend fs.FS
tls *tls.Config
start time.Time
servers []*http.Server
@@ -77,13 +83,22 @@ type Service struct {
// New returns a new properly initialized *Service. If c is nil, svc is a nil
// *Service that does nothing. The fields of c must not be modified after
// calling New.
func New(c *Config) (svc *Service) {
//
// TODO(a.garipov): Get rid of this special handling of nil or explain it
// better.
func New(c *Config) (svc *Service, err error) {
if c == nil {
return nil
return nil, nil
}
frontend, err := fs.Sub(c.Frontend, "build/static")
if err != nil {
return nil, fmt.Errorf("frontend fs: %w", err)
}
svc = &Service{
confMgr: c.ConfigManager,
frontend: frontend,
tls: c.TLS,
start: c.Start,
timeout: c.Timeout,
@@ -121,10 +136,10 @@ func New(c *Config) (svc *Service) {
})
}
return svc
return svc, nil
}
// newMux returns a new HTTP request multiplexor for the AdGuard Home web
// newMux returns a new HTTP request multiplexer for the AdGuard Home web
// service.
func newMux(svc *Service) (mux *httptreemux.ContextMux) {
mux = httptreemux.NewContextMux()
@@ -132,41 +147,54 @@ func newMux(svc *Service) (mux *httptreemux.ContextMux) {
routes := []struct {
handler http.HandlerFunc
method string
path string
pattern string
isJSON bool
}{{
handler: svc.handleGetHealthCheck,
method: http.MethodGet,
path: PathHealthCheck,
pattern: PathHealthCheck,
isJSON: false,
}, {
handler: http.FileServer(http.FS(svc.frontend)).ServeHTTP,
method: http.MethodGet,
pattern: PathFrontend,
isJSON: false,
}, {
handler: http.FileServer(http.FS(svc.frontend)).ServeHTTP,
method: http.MethodGet,
pattern: PathRoot,
isJSON: false,
}, {
handler: svc.handleGetSettingsAll,
method: http.MethodGet,
path: PathV1SettingsAll,
pattern: PathV1SettingsAll,
isJSON: true,
}, {
handler: svc.handlePatchSettingsDNS,
method: http.MethodPatch,
path: PathV1SettingsDNS,
pattern: PathV1SettingsDNS,
isJSON: true,
}, {
handler: svc.handlePatchSettingsHTTP,
method: http.MethodPatch,
path: PathV1SettingsHTTP,
pattern: PathV1SettingsHTTP,
isJSON: true,
}, {
handler: svc.handleGetV1SystemInfo,
method: http.MethodGet,
path: PathV1SystemInfo,
pattern: PathV1SystemInfo,
isJSON: true,
}}
for _, r := range routes {
var hdlr http.Handler
if r.isJSON {
mux.Handle(r.method, r.path, jsonMw(r.handler))
hdlr = jsonMw(r.handler)
} else {
mux.Handle(r.method, r.path, r.handler)
hdlr = r.handler
}
mux.Handle(r.method, r.pattern, logMw(hdlr))
}
return mux

View File

@@ -5,12 +5,14 @@ import (
"context"
"encoding/json"
"io"
"io/fs"
"net/http"
"net/netip"
"net/url"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
@@ -87,7 +89,10 @@ func newTestServer(
t.Helper()
c := &websvc.Config{
ConfigManager: confMgr,
ConfigManager: confMgr,
Frontend: &aghtest.FS{
OnOpen: func(_ string) (_ fs.File, _ error) { return nil, fs.ErrNotExist },
},
TLS: nil,
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:0")},
SecureAddresses: nil,
@@ -96,9 +101,10 @@ func newTestServer(
ForceHTTPS: false,
}
svc = websvc.New(c)
svc, err := websvc.New(c)
require.NoError(t, err)
err := svc.Start()
err = svc.Start()
require.NoError(t, err)
t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)

View File

@@ -1,23 +1,15 @@
package querylog
import "github.com/AdguardTeam/AdGuardHome/internal/whois"
// Client is the information required by the query log to match against clients
// during searches.
type Client struct {
WHOIS *ClientWHOIS `json:"whois,omitempty"`
Name string `json:"name"`
DisallowedRule string `json:"disallowed_rule"`
Disallowed bool `json:"disallowed"`
IgnoreQueryLog bool `json:"-"`
}
// ClientWHOIS is the filtered WHOIS data for the client.
//
// TODO(a.garipov): Merge with home.RuntimeClientWHOISInfo after the
// refactoring is done.
type ClientWHOIS struct {
City string `json:"city,omitempty"`
Country string `json:"country,omitempty"`
Orgname string `json:"orgname,omitempty"`
WHOIS *whois.Info `json:"whois,omitempty"`
Name string `json:"name"`
DisallowedRule string `json:"disallowed_rule"`
Disallowed bool `json:"disallowed"`
IgnoreQueryLog bool `json:"-"`
}
// clientCacheKey is the key by which a cached client information is found.

108
internal/querylog/csv.go Normal file
View File

@@ -0,0 +1,108 @@
package querylog
import (
"strconv"
"strings"
"time"
"github.com/AdguardTeam/golibs/log"
"github.com/miekg/dns"
)
// csvRow is an alias type for csv rows.
type csvRow = [18]string
// csvHeaderRow is a slice of strings with column names for CSV header row.
var csvHeaderRow = csvRow{
"ans_dnssec",
"ans_rcode",
"ans_type",
"ans_value",
"cached",
"client_ip",
"client_id",
"ecs",
"elapsed",
"filter_id",
"filter_rule",
"proto",
"qclass",
"qname",
"qtype",
"reason",
"time",
"upstream",
}
// toCSV returns a slice of strings with entry fields according to the
// csvHeaderRow slice.
func (e *logEntry) toCSV() (out *csvRow) {
var filterID, filterRule string
if e.Result.IsFiltered && len(e.Result.Rules) > 0 {
rule := e.Result.Rules[0]
filterID = strconv.FormatInt(rule.FilterListID, 10)
filterRule = rule.Text
}
aData := ansData(e)
return &csvRow{
strconv.FormatBool(e.AuthenticatedData),
aData.rCode,
aData.typ,
aData.value,
strconv.FormatBool(e.Cached),
e.IP.String(),
e.ClientID,
e.ReqECS,
strconv.FormatFloat(e.Elapsed.Seconds()*1000, 'f', -1, 64),
filterID,
filterRule,
string(e.ClientProto),
e.QClass,
e.QHost,
e.QType,
e.Result.Reason.String(),
e.Time.Format(time.RFC3339Nano),
e.Upstream,
}
}
// csvAnswer is a helper struct for csv row answer fields.
type csvAnswer struct {
rCode string
typ string
value string
}
// ansData returns a map with message answer data.
func ansData(entry *logEntry) (out csvAnswer) {
if len(entry.Answer) == 0 {
return out
}
msg := &dns.Msg{}
if err := msg.Unpack(entry.Answer); err != nil {
log.Debug("querylog: failed to unpack dns msg answer: %v: %s", entry.Answer, err)
return out
}
out.rCode = dns.RcodeToString[msg.Rcode]
if len(msg.Answer) == 0 {
return out
}
rr := msg.Answer[0]
header := rr.Header()
out.typ = dns.TypeToString[header.Rrtype]
// Remove the header string from the answer value since it's mostly
// unnecessary in the log.
out.value = strings.TrimPrefix(rr.String(), header.String())
return out
}

View File

@@ -0,0 +1,73 @@
package querylog
import (
"net"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var testDate = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
func TestLogEntry_toCSV(t *testing.T) {
ans, err := dns.NewRR("www.example.org. IN A 127.0.0.1")
require.NoError(t, err)
ansBytes, err := (&dns.Msg{Answer: []dns.RR{ans}}).Pack()
require.NoError(t, err)
testCases := []struct {
entry *logEntry
want *csvRow
name string
}{{
name: "simple",
entry: &logEntry{
Time: testDate,
QHost: "test.host",
QType: "A",
QClass: "IN",
ReqECS: "",
ClientID: "test-client-id",
ClientProto: ClientProtoDoH,
Upstream: "https://test.upstream:443/dns-query",
Answer: ansBytes,
OrigAnswer: nil,
IP: net.IP{1, 2, 3, 4},
Result: filtering.Result{},
Elapsed: 500 * time.Millisecond,
Cached: false,
AuthenticatedData: false,
},
want: &[18]string{
"false",
"NOERROR",
"A",
"127.0.0.1",
"false",
"1.2.3.4",
"test-client-id",
"",
"500",
"",
"",
"doh",
"IN",
"test.host",
"A",
"NotFilteredNotFound",
"2022-01-01T00:00:00Z",
"https://test.upstream:443/dns-query",
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.want, tc.entry.toCSV())
})
}
}

View File

@@ -3,19 +3,24 @@ package querylog
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
)
// logEntryHandler represents a handler for decoding json token to the logEntry
// struct.
type logEntryHandler func(t json.Token, ent *logEntry) error
// logEntryHandlers is the map of log entry decode handlers for various keys.
var logEntryHandlers = map[string]logEntryHandler{
"CID": func(t json.Token, ent *logEntry) error {
v, ok := t.(string)
@@ -166,6 +171,7 @@ var logEntryHandlers = map[string]logEntryHandler{
},
}
// decodeResultRuleKey decodes the token of "Rules" type to logEntry struct.
func decodeResultRuleKey(key string, i int, dec *json.Decoder, ent *logEntry) {
var vToken json.Token
switch key {
@@ -189,6 +195,8 @@ func decodeResultRuleKey(key string, i int, dec *json.Decoder, ent *logEntry) {
}
}
// decodeVTokenAndAddRule decodes the "Rules" toke as [filtering.ResultRule]
// and then adds the decoded object to the slice of result rules.
func decodeVTokenAndAddRule(
key string,
i int,
@@ -213,6 +221,8 @@ func decodeVTokenAndAddRule(
return newRules, vToken
}
// decodeResultRules parses the dec's tokens into logEntry ent interpreting it
// as a slice of the result rules.
func decodeResultRules(dec *json.Decoder, ent *logEntry) {
for {
delimToken, err := dec.Token()
@@ -224,48 +234,53 @@ func decodeResultRules(dec *json.Decoder, ent *logEntry) {
return
}
if d, ok := delimToken.(json.Delim); ok {
if d != '[' {
log.Debug("decodeResultRules: unexpected delim %q", d)
if d, ok := delimToken.(json.Delim); !ok {
return
} else if d != '[' {
log.Debug("decodeResultRules: unexpected delim %q", d)
}
err = decodeResultRuleToken(dec, ent)
if err != nil {
if err != io.EOF && !errors.Is(err, ErrEndOfToken) {
log.Debug("decodeResultRules err: %s", err)
}
} else {
return
}
}
}
i := 0
for {
var keyToken json.Token
keyToken, err = dec.Token()
if err != nil {
if err != io.EOF {
log.Debug("decodeResultRules err: %s", err)
}
return
}
if d, ok := keyToken.(json.Delim); ok {
switch d {
case '}':
i++
case ']':
return
default:
// Go on.
}
continue
}
key, ok := keyToken.(string)
if !ok {
log.Debug("decodeResultRules: keyToken is %T (%[1]v) and not string", keyToken)
return
}
decodeResultRuleKey(key, i, dec, ent)
// decodeResultRuleToken decodes the tokens of "Rules" type to the logEntry ent.
func decodeResultRuleToken(dec *json.Decoder, ent *logEntry) (err error) {
i := 0
for {
var keyToken json.Token
keyToken, err = dec.Token()
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
if d, ok := keyToken.(json.Delim); ok {
switch d {
case '}':
i++
case ']':
return ErrEndOfToken
default:
// Go on.
}
continue
}
key, ok := keyToken.(string)
if !ok {
return fmt.Errorf("keyToken is %T (%[1]v) and not string", keyToken)
}
decodeResultRuleKey(key, i, dec, ent)
}
}
@@ -322,6 +337,8 @@ func decodeResultReverseHosts(dec *json.Decoder, ent *logEntry) {
}
}
// decodeResultIPList parses the dec's tokens into logEntry ent interpreting it
// as the result IP addresses list.
func decodeResultIPList(dec *json.Decoder, ent *logEntry) {
for {
itemToken, err := dec.Token()
@@ -355,6 +372,8 @@ func decodeResultIPList(dec *json.Decoder, ent *logEntry) {
}
}
// decodeResultDNSRewriteResultKey decodes the token of "DNSRewriteResult" type
// to the logEntry struct.
func decodeResultDNSRewriteResultKey(key string, dec *json.Decoder, ent *logEntry) {
var err error
@@ -395,50 +414,29 @@ func decodeResultDNSRewriteResultKey(key string, dec *json.Decoder, ent *logEntr
log.Debug("decodeResultDNSRewriteResultKey response err: %s", err)
}
for rrType, rrValues := range ent.Result.DNSRewriteResult.Response {
switch rrType {
case
dns.TypeA,
dns.TypeAAAA:
for i, v := range rrValues {
s, _ := v.(string)
rrValues[i] = net.ParseIP(s)
}
default:
// Go on.
}
}
ent.parseDNSRewriteResultIPs()
default:
// Go on.
}
}
// decodeResultDNSRewriteResult parses the dec's tokens into logEntry ent
// interpreting it as the result DNSRewriteResult.
func decodeResultDNSRewriteResult(dec *json.Decoder, ent *logEntry) {
for {
keyToken, err := dec.Token()
key, err := parseKeyToken(dec)
if err != nil {
if err != io.EOF {
log.Debug("decodeResultDNSRewriteResult err: %s", err)
if err != io.EOF && !errors.Is(err, ErrEndOfToken) {
log.Debug("decodeResultDNSRewriteResult: %s", err)
}
return
}
if d, ok := keyToken.(json.Delim); ok {
if d == '}' {
return
}
if key == "" {
continue
}
key, ok := keyToken.(string)
if !ok {
log.Debug("decodeResultDNSRewriteResult: keyToken is %T (%[1]v) and not string", keyToken)
return
}
decodeResultDNSRewriteResultKey(key, dec, ent)
}
}
@@ -474,34 +472,51 @@ func translateResult(ent *logEntry) {
res.IPList = nil
}
// ErrEndOfToken is an error returned by parse key token when the closing
// bracket is found.
const ErrEndOfToken errors.Error = "end of token"
// parseKeyToken parses the dec's token key.
func parseKeyToken(dec *json.Decoder) (key string, err error) {
keyToken, err := dec.Token()
if err != nil {
return "", err
}
if d, ok := keyToken.(json.Delim); ok {
if d == '}' {
return "", ErrEndOfToken
}
return "", nil
}
key, ok := keyToken.(string)
if !ok {
return "", fmt.Errorf("keyToken is %T (%[1]v) and not string", keyToken)
}
return key, nil
}
// decodeResult decodes a token of "Result" type to logEntry struct.
func decodeResult(dec *json.Decoder, ent *logEntry) {
defer translateResult(ent)
for {
keyToken, err := dec.Token()
key, err := parseKeyToken(dec)
if err != nil {
if err != io.EOF {
log.Debug("decodeResult err: %s", err)
if err != io.EOF && !errors.Is(err, ErrEndOfToken) {
log.Debug("decodeResult: %s", err)
}
return
}
if d, ok := keyToken.(json.Delim); ok {
if d == '}' {
return
}
if key == "" {
continue
}
key, ok := keyToken.(string)
if !ok {
log.Debug("decodeResult: keyToken is %T (%[1]v) and not string", keyToken)
return
}
decHandler, ok := resultDecHandlers[key]
if ok {
decHandler(dec, ent)
@@ -527,13 +542,16 @@ func decodeResult(dec *json.Decoder, ent *logEntry) {
}
}
// resultHandlers is the map of log entry decode handlers for various keys.
var resultHandlers = map[string]logEntryHandler{
"IsFiltered": func(t json.Token, ent *logEntry) error {
v, ok := t.(bool)
if !ok {
return nil
}
ent.Result.IsFiltered = v
return nil
},
"Rule": func(t json.Token, ent *logEntry) error {
@@ -578,11 +596,14 @@ var resultHandlers = map[string]logEntryHandler{
if !ok {
return nil
}
i, err := v.Int64()
if err != nil {
return err
}
ent.Result.Reason = filtering.Reason(i)
return nil
},
"ServiceName": func(t json.Token, ent *logEntry) error {
@@ -607,6 +628,7 @@ var resultHandlers = map[string]logEntryHandler{
},
}
// resultDecHandlers is the map of decode handlers for various keys.
var resultDecHandlers = map[string]func(dec *json.Decoder, ent *logEntry){
"ReverseHosts": decodeResultReverseHosts,
"IPList": decodeResultIPList,
@@ -614,9 +636,11 @@ var resultDecHandlers = map[string]func(dec *json.Decoder, ent *logEntry){
"DNSRewriteResult": decodeResultDNSRewriteResult,
}
// decodeLogEntry decodes string str to logEntry ent.
func decodeLogEntry(ent *logEntry, str string) {
dec := json.NewDecoder(strings.NewReader(str))
dec.UseNumber()
for {
keyToken, err := dec.Token()
if err != nil {

View File

@@ -68,3 +68,19 @@ func (e *logEntry) addResponse(resp *dns.Msg, isOrig bool) {
log.Error("querylog: %s", err)
}
}
// parseDNSRewriteResultIPs fills logEntry's DNSRewriteResult response records
// with the IP addresses parsed from the raw strings.
func (e *logEntry) parseDNSRewriteResultIPs() {
for rrType, rrValues := range e.Result.DNSRewriteResult.Response {
switch rrType {
case dns.TypeA, dns.TypeAAAA:
for i, v := range rrValues {
s, _ := v.(string)
rrValues[i] = net.ParseIP(s)
}
default:
// Go on.
}
}
}

View File

@@ -1,6 +1,7 @@
package querylog
import (
"encoding/csv"
"encoding/json"
"fmt"
"math"
@@ -14,6 +15,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/AdguardTeam/golibs/timeutil"
@@ -62,6 +64,7 @@ func (l *queryLog) initWeb() {
l.conf.HTTPRegister(http.MethodGet, "/control/querylog", l.handleQueryLog)
l.conf.HTTPRegister(http.MethodPost, "/control/querylog_clear", l.handleQueryLogClear)
l.conf.HTTPRegister(http.MethodGet, "/control/querylog/config", l.handleGetQueryLogConfig)
l.conf.HTTPRegister(http.MethodGet, "/control/querylog/export", l.handleQueryLogExport)
l.conf.HTTPRegister(
http.MethodPut,
"/control/querylog/config/update",
@@ -96,6 +99,73 @@ func (l *queryLog) handleQueryLog(w http.ResponseWriter, r *http.Request) {
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
// exportChunkSize is a size of one search-flush iteration for query log export.
//
// TODO(a.meshkov): Consider making configurable.
const exportChunkSize = 500
// handleQueryLogExport is the handler for the GET /control/querylog/export
// HTTP API.
func (l *queryLog) handleQueryLogExport(w http.ResponseWriter, r *http.Request) {
searchCriteria, err := parseSearchCriteria(r.URL.Query())
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "parsing params: %s", err)
return
}
params := &searchParams{
limit: exportChunkSize,
searchCriteria: searchCriteria,
}
w.Header().Set(httphdr.ContentType, "text/csv; charset=UTF-8; header=present")
w.Header().Set(httphdr.ContentDisposition, "attachment;filename=data.csv")
csvWriter := csv.NewWriter(w)
// Write header.
if err = csvWriter.Write(csvHeaderRow[:]); err != nil {
http.Error(w, "writing csv header", http.StatusInternalServerError)
return
}
csvWriter.Flush()
var entries []*logEntry
for {
func() {
l.confMu.RLock()
defer l.confMu.RUnlock()
entries, _ = l.search(params)
}()
if len(entries) == 0 {
break
}
params.offset += params.limit
for _, entry := range entries {
row := entry.toCSV()
if err = csvWriter.Write(row[:]); err != nil {
// TODO(a.garipov): Set Trailer X-Error header.
log.Error("%s %s %s: %s: %s", r.Method, r.Host, r.URL, "writing csv record", err)
return
}
}
csvWriter.Flush()
}
if err = csvWriter.Error(); err != nil {
// TODO(a.garipov): Set Trailer X-Error header.
log.Error("%s %s %s: %s: %s", r.Method, r.Host, r.URL, "writing csv", err)
}
}
// handleQueryLogClear is the handler for the POST /control/querylog/clear HTTP
// API.
func (l *queryLog) handleQueryLogClear(_ http.ResponseWriter, _ *http.Request) {
@@ -360,6 +430,17 @@ func parseSearchParams(r *http.Request) (p *searchParams, err error) {
p.maxFileScanEntries = 0
}
p.searchCriteria, err = parseSearchCriteria(q)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return nil, err
}
return p, nil
}
// parseSearchCriteria parses a list of search criteria from the query.
func parseSearchCriteria(q url.Values) (searchCriteria []searchCriterion, err error) {
for _, v := range []struct {
urlField string
ct criterionType
@@ -378,9 +459,9 @@ func parseSearchParams(r *http.Request) (p *searchParams, err error) {
}
if ok {
p.searchCriteria = append(p.searchCriteria, c)
searchCriteria = append(searchCriteria, c)
}
}
return p, nil
return searchCriteria, nil
}

View File

@@ -16,32 +16,35 @@ import (
"github.com/miekg/dns"
)
const (
queryLogFileName = "querylog.json" // .gz added during compression
)
// queryLogFileName is a name of the log file. ".gz" extension is added later
// during compression.
const queryLogFileName = "querylog.json"
// queryLog is a structure that writes and reads the DNS query log
// queryLog is a structure that writes and reads the DNS query log.
type queryLog struct {
findClient func(ids []string) (c *Client, err error)
// confMu protects conf.
confMu *sync.RWMutex
conf *Config
conf *Config
anonymizer *aghnet.IPMut
findClient func(ids []string) (c *Client, err error)
// logFile is the path to the log file.
logFile string
// bufferLock protects buffer.
bufferLock sync.RWMutex
// buffer contains recent log entries. The entries in this buffer must not
// be modified.
buffer []*logEntry
fileFlushLock sync.Mutex // synchronize a file-flushing goroutine and main thread
flushPending bool // don't start another goroutine while the previous one is still running
// bufferLock protects buffer.
bufferLock sync.RWMutex
// fileFlushLock synchronizes a file-flushing goroutine and main thread.
fileFlushLock sync.Mutex
fileWriteLock sync.Mutex
anonymizer *aghnet.IPMut
flushPending bool
}
// ClientProto values are names of the client protocols.
@@ -155,6 +158,46 @@ func (l *queryLog) clear() {
log.Debug("querylog: cleared")
}
// newLogEntry creates an instance of logEntry from parameters.
func newLogEntry(params *AddParams) (entry *logEntry) {
q := params.Question.Question[0]
qHost := q.Name
if qHost != "." {
qHost = strings.ToLower(q.Name[:len(q.Name)-1])
}
entry = &logEntry{
// TODO(d.kolyshev): Export this timestamp to func params.
Time: time.Now(),
QHost: qHost,
QType: dns.Type(q.Qtype).String(),
QClass: dns.Class(q.Qclass).String(),
ClientID: params.ClientID,
ClientProto: params.ClientProto,
Result: *params.Result,
Upstream: params.Upstream,
IP: params.ClientIP,
Elapsed: params.Elapsed,
Cached: params.Cached,
AuthenticatedData: params.AuthenticatedData,
}
if params.ReqECS != nil {
entry.ReqECS = params.ReqECS.String()
}
entry.addResponse(params.Answer, false)
entry.addResponse(params.OrigAnswer, true)
return entry
}
// Add implements the [QueryLog] interface for *queryLog.
func (l *queryLog) Add(params *AddParams) {
var isEnabled, fileIsEnabled bool
var memSize uint32
@@ -181,35 +224,7 @@ func (l *queryLog) Add(params *AddParams) {
params.Result = &filtering.Result{}
}
now := time.Now()
q := params.Question.Question[0]
entry := &logEntry{
Time: now,
QHost: strings.ToLower(q.Name[:len(q.Name)-1]),
QType: dns.Type(q.Qtype).String(),
QClass: dns.Class(q.Qclass).String(),
ClientID: params.ClientID,
ClientProto: params.ClientProto,
Result: *params.Result,
Upstream: params.Upstream,
IP: params.ClientIP,
Elapsed: params.Elapsed,
Cached: params.Cached,
AuthenticatedData: params.AuthenticatedData,
}
if params.ReqECS != nil {
entry.ReqECS = params.ReqECS.String()
}
entry.addResponse(params.Answer, false)
entry.addResponse(params.OrigAnswer, true)
entry := newLogEntry(params)
needFlush := false
func() {

View File

@@ -43,11 +43,13 @@ func TestQueryLog(t *testing.T) {
// Add memory entries.
addEntry(l, "test.example.org", net.IPv4(1, 1, 1, 3), net.IPv4(2, 2, 2, 3))
addEntry(l, "example.com", net.IPv4(1, 1, 1, 4), net.IPv4(2, 2, 2, 4))
addEntry(l, "", net.IPv4(1, 1, 1, 5), net.IPv4(2, 2, 2, 5))
type tcAssertion struct {
num int
host string
answer, client net.IP
host string
answer net.IP
client net.IP
num int
}
testCases := []struct {
@@ -58,10 +60,11 @@ func TestQueryLog(t *testing.T) {
name: "all",
sCr: []searchCriterion{},
want: []tcAssertion{
{num: 0, host: "example.com", answer: net.IPv4(1, 1, 1, 4), client: net.IPv4(2, 2, 2, 4)},
{num: 1, host: "test.example.org", answer: net.IPv4(1, 1, 1, 3), client: net.IPv4(2, 2, 2, 3)},
{num: 2, host: "example.org", answer: net.IPv4(1, 1, 1, 2), client: net.IPv4(2, 2, 2, 2)},
{num: 3, host: "example.org", answer: net.IPv4(1, 1, 1, 1), client: net.IPv4(2, 2, 2, 1)},
{num: 0, host: ".", answer: net.IPv4(1, 1, 1, 5), client: net.IPv4(2, 2, 2, 5)},
{num: 1, host: "example.com", answer: net.IPv4(1, 1, 1, 4), client: net.IPv4(2, 2, 2, 4)},
{num: 2, host: "test.example.org", answer: net.IPv4(1, 1, 1, 3), client: net.IPv4(2, 2, 2, 3)},
{num: 3, host: "example.org", answer: net.IPv4(1, 1, 1, 2), client: net.IPv4(2, 2, 2, 2)},
{num: 4, host: "example.org", answer: net.IPv4(1, 1, 1, 1), client: net.IPv4(2, 2, 2, 1)},
},
}, {
name: "by_domain_strict",
@@ -103,10 +106,11 @@ func TestQueryLog(t *testing.T) {
value: "2.2.2",
}},
want: []tcAssertion{
{num: 0, host: "example.com", answer: net.IPv4(1, 1, 1, 4), client: net.IPv4(2, 2, 2, 4)},
{num: 1, host: "test.example.org", answer: net.IPv4(1, 1, 1, 3), client: net.IPv4(2, 2, 2, 3)},
{num: 2, host: "example.org", answer: net.IPv4(1, 1, 1, 2), client: net.IPv4(2, 2, 2, 2)},
{num: 3, host: "example.org", answer: net.IPv4(1, 1, 1, 1), client: net.IPv4(2, 2, 2, 1)},
{num: 0, host: ".", answer: net.IPv4(1, 1, 1, 5), client: net.IPv4(2, 2, 2, 5)},
{num: 1, host: "example.com", answer: net.IPv4(1, 1, 1, 4), client: net.IPv4(2, 2, 2, 4)},
{num: 2, host: "test.example.org", answer: net.IPv4(1, 1, 1, 3), client: net.IPv4(2, 2, 2, 3)},
{num: 3, host: "example.org", answer: net.IPv4(1, 1, 1, 2), client: net.IPv4(2, 2, 2, 2)},
{num: 4, host: "example.org", answer: net.IPv4(1, 1, 1, 1), client: net.IPv4(2, 2, 2, 1)},
},
}}

View File

@@ -12,141 +12,181 @@ import (
"github.com/AdguardTeam/golibs/log"
)
// Timestamp not found errors.
const (
ErrTSNotFound errors.Error = "ts not found"
ErrTSTooLate errors.Error = "ts too late"
ErrTSTooEarly errors.Error = "ts too early"
// Timestamp not found errors.
errTSNotFound errors.Error = "ts not found"
errTSTooLate errors.Error = "ts too late"
errTSTooEarly errors.Error = "ts too early"
// maxEntrySize is a maximum size of the entry.
//
// TODO: Find a way to grow buffer instead of relying on this value when
// reading strings.
maxEntrySize = 16 * 1024
// bufferSize should be enough for at least this number of entries.
bufferSize = 100 * maxEntrySize
)
// TODO: Find a way to grow buffer instead of relying on this value when reading strings
const maxEntrySize = 16 * 1024
// buffer should be enough for at least this number of entries
const bufferSize = 100 * maxEntrySize
// QLogFile represents a single query log file
// It allows reading from the file in the reverse order
// qLogFile represents a single query log file. It allows reading from the
// file in the reverse order.
//
// Please note that this is a stateful object.
// Internally, it contains a pointer to a specific position in the file,
// and it reads lines in reverse order starting from that position.
type QLogFile struct {
file *os.File // the query log file
position int64 // current position in the file
// Please note, that this is a stateful object. Internally, it contains a
// pointer to a specific position in the file, and it reads lines in reverse
// order starting from that position.
type qLogFile struct {
// file is the query log file.
file *os.File
buffer []byte // buffer that we've read from the file
bufferStart int64 // start of the buffer (in the file)
bufferLen int // buffer len
// buffer that we've read from the file.
buffer []byte
lock sync.Mutex // We use mutex to make it thread-safe
// lock is a mutex to make it thread-safe.
lock sync.Mutex
// position is the position in the file.
position int64
// bufferStart is the start of the buffer (in the file).
bufferStart int64
// bufferLen is the length of the buffer.
bufferLen int
}
// NewQLogFile initializes a new instance of the QLogFile
func NewQLogFile(path string) (*QLogFile, error) {
// newQLogFile initializes a new instance of the qLogFile.
func newQLogFile(path string) (qf *qLogFile, err error) {
f, err := os.OpenFile(path, os.O_RDONLY, 0o644)
if err != nil {
return nil, err
}
return &QLogFile{
file: f,
}, nil
return &qLogFile{file: f}, nil
}
// validateQLogLineIdx returns error if the line index is not valid to continue
// search.
func (q *qLogFile) validateQLogLineIdx(lineIdx, lastProbeLineIdx, ts, fSize int64) (err error) {
if lineIdx == lastProbeLineIdx {
if lineIdx == 0 {
return errTSTooEarly
}
// If we're testing the same line twice then most likely the scope is
// too narrow and we won't find anything anymore in any other file.
return fmt.Errorf("looking up timestamp %d in %q: %w", ts, q.file.Name(), errTSNotFound)
} else if lineIdx == fSize {
return errTSTooLate
}
return nil
}
// seekTS performs binary search in the query log file looking for a record
// with the specified timestamp. Once the record is found, it sets
// "position" so that the next ReadNext call returned that record.
// with the specified timestamp. Once the record is found, it sets "position"
// so that the next ReadNext call returned that record.
//
// The algorithm is rather simple:
// 1. It starts with the position in the middle of a file
// 2. Shifts back to the beginning of the line
// 3. Checks the log record timestamp
// 4. If it is lower than the timestamp we are looking for,
// it shifts seek position to 3/4 of the file. Otherwise, to 1/4 of the file.
// 5. It performs the search again, every time the search scope is narrowed twice.
// 1. It starts with the position in the middle of a file.
// 2. Shifts back to the beginning of the line.
// 3. Checks the log record timestamp.
// 4. If it is lower than the timestamp we are looking for, it shifts seek
// position to 3/4 of the file. Otherwise, to 1/4 of the file.
// 5. It performs the search again, every time the search scope is narrowed
// twice.
//
// Returns:
// * It returns the position of the the line with the timestamp we were looking for
// so that when we call "ReadNext" this line was returned.
// * Depth of the search (how many times we compared timestamps).
// * If we could not find it, it returns one of the errors described above.
func (q *QLogFile) seekTS(timestamp int64) (int64, int, error) {
// - It returns the position of the line with the timestamp we were looking
// for so that when we call "ReadNext" this line was returned.
// - Depth of the search (how many times we compared timestamps).
// - If we could not find it, it returns one of the errors described above.
func (q *qLogFile) seekTS(timestamp int64) (pos int64, depth int, err error) {
q.lock.Lock()
defer q.lock.Unlock()
// Empty the buffer
// Empty the buffer.
q.buffer = nil
// First of all, check the file size
// First of all, check the file size.
fileInfo, err := q.file.Stat()
if err != nil {
return 0, 0, err
}
// Define the search scope
start := int64(0) // start of the search interval (position in the file)
end := fileInfo.Size() // end of the search interval (position in the file)
probe := (end - start) / 2 // probe -- approximate index of the line we'll try to check
// Define the search scope.
// Start of the search interval (position in the file).
start := int64(0)
// End of the search interval (position in the file).
end := fileInfo.Size()
// Probe is the approximate index of the line we'll try to check.
probe := (end - start) / 2
var line string
var lineIdx int64 // index of the probe line in the file
// Index of the probe line in the file.
var lineIdx int64
var lineEndIdx int64
var lastProbeLineIdx int64 // index of the last probe line
// Index of the last probe line.
var lastProbeLineIdx int64
lastProbeLineIdx = -1
// Count seek depth in order to detect mistakes
// If depth is too large, we should stop the search
depth := 0
// Count seek depth in order to detect mistakes. If depth is too large,
// we should stop the search.
for {
// Get the line at the specified position
// Get the line at the specified position.
line, lineIdx, lineEndIdx, err = q.readProbeLine(probe)
if err != nil {
return 0, depth, err
}
if lineIdx == lastProbeLineIdx {
if lineIdx == 0 {
return 0, depth, ErrTSTooEarly
}
// If we're testing the same line twice then most likely
// the scope is too narrow and we won't find anything
// anymore in any other file.
return 0, depth, fmt.Errorf("looking up timestamp %d in %q: %w", timestamp, q.file.Name(), ErrTSNotFound)
} else if lineIdx == fileInfo.Size() {
return 0, depth, ErrTSTooLate
// Check if the line index if invalid.
err = q.validateQLogLineIdx(lineIdx, lastProbeLineIdx, timestamp, fileInfo.Size())
if err != nil {
return 0, depth, err
}
// Save the last found idx
// Save the last found idx.
lastProbeLineIdx = lineIdx
// Get the timestamp from the query log record
// Get the timestamp from the query log record.
ts := readQLogTimestamp(line)
if ts == 0 {
return 0, depth, fmt.Errorf("looking up timestamp %d in %q: record %q has empty timestamp", timestamp, q.file.Name(), line)
return 0, depth, fmt.Errorf(
"looking up timestamp %d in %q: record %q has empty timestamp",
timestamp,
q.file.Name(),
line,
)
}
if ts == timestamp {
// Hurray, returning the result
// Hurray, returning the result.
break
}
// Narrow the scope and repeat the search
// Narrow the scope and repeat the search.
if ts > timestamp {
// If the timestamp we're looking for is OLDER than what we found
// Then the line is somewhere on the LEFT side from the current probe position
// If the timestamp we're looking for is OLDER than what we found,
// then the line is somewhere on the LEFT side from the current
// probe position.
end = lineIdx
} else {
// If the timestamp we're looking for is NEWER than what we found
// Then the line is somewhere on the RIGHT side from the current probe position
// If the timestamp we're looking for is NEWER than what we found,
// then the line is somewhere on the RIGHT side from the current
// probe position.
start = lineEndIdx
}
probe = start + (end-start)/2
depth++
if depth >= 100 {
return 0, depth, fmt.Errorf("looking up timestamp %d in %q: depth %d too high: %w", timestamp, q.file.Name(), depth, ErrTSNotFound)
return 0, depth, fmt.Errorf(
"looking up timestamp %d in %q: depth %d too high: %w",
timestamp,
q.file.Name(),
depth,
errTSNotFound,
)
}
}
@@ -154,37 +194,39 @@ func (q *QLogFile) seekTS(timestamp int64) (int64, int, error) {
return q.position, depth, nil
}
// SeekStart changes the current position to the end of the file
// Please note that we're reading query log in the reverse order
// and that's why log start is actually the end of file
// SeekStart changes the current position to the end of the file. Please note,
// that we're reading query log in the reverse order and that's why log start
// is actually the end of file.
//
// Returns nil if we were able to change the current position.
// Returns error in any other case.
func (q *QLogFile) SeekStart() (int64, error) {
// Returns nil if we were able to change the current position. Returns error
// in any other case.
func (q *qLogFile) SeekStart() (int64, error) {
q.lock.Lock()
defer q.lock.Unlock()
// Empty the buffer
// Empty the buffer.
q.buffer = nil
// First of all, check the file size
// First of all, check the file size.
fileInfo, err := q.file.Stat()
if err != nil {
return 0, err
}
// Place the position to the very end of file
// Place the position to the very end of file.
q.position = fileInfo.Size() - 1
if q.position < 0 {
q.position = 0
}
return q.position, nil
}
// ReadNext reads the next line (in the reverse order) from the file
// and shifts the current position left to the next (actually prev) line.
// returns io.EOF if there's nothing to read more
func (q *QLogFile) ReadNext() (string, error) {
// ReadNext reads the next line (in the reverse order) from the file and shifts
// the current position left to the next (actually prev) line.
//
// Returns io.EOF if there's nothing more to read.
func (q *qLogFile) ReadNext() (string, error) {
q.lock.Lock()
defer q.lock.Unlock()
@@ -197,35 +239,34 @@ func (q *QLogFile) ReadNext() (string, error) {
return "", err
}
// Shift position
// Shift position.
if lineIdx == 0 {
q.position = 0
} else {
// there's usually a line break before the line
// so we should shift one more char left from the line
// line\nline
// There's usually a line break before the line, so we should shift one
// more char left from the line "\nline".
q.position = lineIdx - 1
}
return line, err
}
// Close frees the underlying resources
func (q *QLogFile) Close() error {
// Close frees the underlying resources.
func (q *qLogFile) Close() error {
return q.file.Close()
}
// readNextLine reads the next line from the specified position
// this line actually have to END on that position.
// readNextLine reads the next line from the specified position. This line
// actually have to END on that position.
//
// the algorithm is:
// 1. check if we have the buffer initialized
// 2. if it is, scan it and look for the line there
// 3. if we cannot find the line there, read the prev chunk into the buffer
// 4. read the line from the buffer
func (q *QLogFile) readNextLine(position int64) (string, int64, error) {
// The algorithm is:
// 1. Check if we have the buffer initialized.
// 2. If it is so, scan it and look for the line there.
// 3. If we cannot find the line there, read the prev chunk into the buffer.
// 4. Read the line from the buffer.
func (q *qLogFile) readNextLine(position int64) (string, int64, error) {
relativePos := position - q.bufferStart
if q.buffer == nil || (relativePos < maxEntrySize && q.bufferStart != 0) {
// Time to re-init the buffer
// Time to re-init the buffer.
err := q.initBuffer(position)
if err != nil {
return "", 0, err
@@ -233,8 +274,7 @@ func (q *QLogFile) readNextLine(position int64) (string, int64, error) {
relativePos = position - q.bufferStart
}
// Look for the end of the prev line
// This is where we'll read from
// Look for the end of the prev line, this is where we'll read from.
startLine := int64(0)
for i := relativePos - 1; i >= 0; i-- {
if q.buffer[i] == '\n' {
@@ -245,18 +285,19 @@ func (q *QLogFile) readNextLine(position int64) (string, int64, error) {
line := string(q.buffer[startLine:relativePos])
lineIdx := q.bufferStart + startLine
return line, lineIdx, nil
}
// initBuffer initializes the QLogFile buffer.
// the goal is to read a chunk of file that includes the line with the specified position.
func (q *QLogFile) initBuffer(position int64) error {
// initBuffer initializes the qLogFile buffer. The goal is to read a chunk of
// file that includes the line with the specified position.
func (q *qLogFile) initBuffer(position int64) error {
q.bufferStart = int64(0)
if position > bufferSize {
q.bufferStart = position - bufferSize
}
// Seek to this position
// Seek to this position.
_, err := q.file.Seek(q.bufferStart, io.SeekStart)
if err != nil {
return err
@@ -271,34 +312,35 @@ func (q *QLogFile) initBuffer(position int64) error {
return err
}
// readProbeLine reads a line that includes the specified position
// this method is supposed to be used when we use binary search in the Seek method
// in the case of consecutive reads, use readNext (it uses a better buffer)
func (q *QLogFile) readProbeLine(position int64) (string, int64, int64, error) {
// First of all, we should read a buffer that will include the query log line
// In order to do this, we'll define the boundaries
// readProbeLine reads a line that includes the specified position. This
// method is supposed to be used when we use binary search in the Seek method.
// In the case of consecutive reads, use readNext, cause it uses better buffer.
func (q *qLogFile) readProbeLine(position int64) (string, int64, int64, error) {
// First of all, we should read a buffer that will include the query log
// line. In order to do this, we'll define the boundaries.
seekPosition := int64(0)
relativePos := position // position relative to the buffer we're going to read
// Position relative to the buffer we're going to read.
relativePos := position
if position > maxEntrySize {
seekPosition = position - maxEntrySize
relativePos = maxEntrySize
}
// Seek to this position
// Seek to this position.
_, err := q.file.Seek(seekPosition, io.SeekStart)
if err != nil {
return "", 0, 0, err
}
// The buffer size is 2*maxEntrySize
// The buffer size is 2*maxEntrySize.
buffer := make([]byte, maxEntrySize*2)
bufferLen, err := q.file.Read(buffer)
if err != nil {
return "", 0, 0, err
}
// Now start looking for the new line character starting
// from the relativePos and going left
// Now start looking for the new line character starting from the
// relativePos and going left.
startLine := int64(0)
for i := relativePos - 1; i >= 0; i-- {
if buffer[i] == '\n' {
@@ -306,7 +348,7 @@ func (q *QLogFile) readProbeLine(position int64) (string, int64, int64, error) {
break
}
}
// Looking for the end of line now
// Looking for the end of line now.
endLine := int64(bufferLen)
lineEndIdx := endLine + seekPosition
for i := relativePos; i < int64(bufferLen); i++ {
@@ -317,13 +359,13 @@ func (q *QLogFile) readProbeLine(position int64) (string, int64, int64, error) {
}
}
// Finally we can return the string we were looking for
// Finally we can return the string we were looking for.
lineIdx := startLine + seekPosition
return string(buffer[startLine:endLine]), lineIdx, lineEndIdx, nil
}
// readJSONvalue reads a JSON string in form of '"key":"value"'. prefix must be
// of the form '"key":"' to generate less garbage.
// readJSONValue reads a JSON string in form of '"key":"value"'. prefix must
// be of the form '"key":"' to generate less garbage.
func readJSONValue(s, prefix string) string {
i := strings.Index(s, prefix)
if i == -1 {
@@ -340,7 +382,7 @@ func readJSONValue(s, prefix string) string {
return s[start:end]
}
// readQLogTimestamp reads the timestamp field from the query log line
// readQLogTimestamp reads the timestamp field from the query log line.
func readQLogTimestamp(str string) int64 {
val := readJSONValue(str, `"T":"`)
if len(val) == 0 {
@@ -351,10 +393,12 @@ func readQLogTimestamp(str string) int64 {
log.Error("Couldn't find timestamp: %s", str)
return 0
}
tm, err := time.Parse(time.RFC3339Nano, val)
if err != nil {
log.Error("Couldn't parse timestamp: %s", val)
return 0
}
return tm.UnixNano()
}

View File

@@ -72,15 +72,15 @@ func prepareTestFiles(t *testing.T, filesNum, linesNum int) []string {
return files
}
// newTestQLogFile creates new *QLogFile for tests and registers the required
// newTestQLogFile creates new *qLogFile for tests and registers the required
// cleanup functions.
func newTestQLogFile(t *testing.T, linesNum int) (file *QLogFile) {
func newTestQLogFile(t *testing.T, linesNum int) (file *qLogFile) {
t.Helper()
testFile := prepareTestFiles(t, 1, linesNum)[0]
// Create the new QLogFile instance.
file, err := NewQLogFile(testFile)
// Create the new qLogFile instance.
file, err := newQLogFile(testFile)
require.NoError(t, err)
assert.NotNil(t, file)
@@ -240,7 +240,7 @@ func TestQLogFile_SeekTS_bad(t *testing.T) {
}
}
func getQLogFileLine(q *QLogFile, lineNumber int) (line string, err error) {
func getQLogFileLine(q *qLogFile, lineNumber int) (line string, err error) {
if _, err = q.SeekStart(); err != nil {
return line, err
}
@@ -256,7 +256,7 @@ func getQLogFileLine(q *QLogFile, lineNumber int) (line string, err error) {
// Check adding and loading (with filtering) entries from disk and memory.
func TestQLogFile(t *testing.T) {
// Create the new QLogFile instance.
// Create the new qLogFile instance.
q := newTestQLogFile(t, 2)
// Seek to the start.
@@ -285,7 +285,7 @@ func TestQLogFile(t *testing.T) {
assert.Empty(t, line)
}
func NewTestQLogFileData(t *testing.T, data string) (file *QLogFile) {
func newTestQLogFileData(t *testing.T, data string) (file *qLogFile) {
f, err := os.CreateTemp(t.TempDir(), "*.txt")
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, f.Close)
@@ -293,7 +293,7 @@ func NewTestQLogFileData(t *testing.T, data string) (file *QLogFile) {
_, err = f.WriteString(data)
require.NoError(t, err)
file, err = NewQLogFile(f.Name())
file, err = newQLogFile(f.Name())
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, file.Close)
@@ -309,9 +309,9 @@ func TestQLog_Seek(t *testing.T) {
timestamp, _ := time.Parse(time.RFC3339Nano, "2020-08-31T18:44:25.376690873+03:00")
testCases := []struct {
wantErr error
name string
delta int
wantErr error
wantDepth int
}{{
name: "ok",
@@ -321,12 +321,12 @@ func TestQLog_Seek(t *testing.T) {
}, {
name: "too_late",
delta: 2,
wantErr: ErrTSTooLate,
wantErr: errTSTooLate,
wantDepth: 2,
}, {
name: "too_early",
delta: -2,
wantErr: ErrTSTooEarly,
wantErr: errTSTooEarly,
wantDepth: 1,
}}
@@ -338,7 +338,7 @@ func TestQLog_Seek(t *testing.T) {
timestamp.Add(time.Second).Format(time.RFC3339Nano),
)
q := NewTestQLogFileData(t, data)
q := newTestQLogFileData(t, data)
_, depth, err := q.seekTS(timestamp.Add(time.Second * time.Duration(tc.delta)).UnixNano())
require.Truef(t, errors.Is(err, tc.wantErr), "%v", err)

View File

@@ -9,36 +9,36 @@ import (
"github.com/AdguardTeam/golibs/log"
)
// QLogReader allows reading from multiple query log files in the reverse order.
// qLogReader allows reading from multiple query log files in the reverse
// order.
//
// Please note that this is a stateful object.
// Internally, it contains a pointer to a particular query log file, and
// to a specific position in this file, and it reads lines in reverse order
// starting from that position.
type QLogReader struct {
// qFiles - array with the query log files
// The order is - from oldest to newest
qFiles []*QLogFile
// Please note that this is a stateful object. Internally, it contains a
// pointer to a particular query log file, and to a specific position in this
// file, and it reads lines in reverse order starting from that position.
type qLogReader struct {
// qFiles is an array with the query log files. The order is from oldest
// to newest.
qFiles []*qLogFile
currentFile int // Index of the current file
// currentFile is the index of the current file.
currentFile int
}
// NewQLogReader initializes a QLogReader instance
// with the specified files
func NewQLogReader(files []string) (*QLogReader, error) {
qFiles := make([]*QLogFile, 0)
// newQLogReader initializes a qLogReader instance with the specified files.
func newQLogReader(files []string) (*qLogReader, error) {
qFiles := make([]*qLogFile, 0)
for _, f := range files {
q, err := NewQLogFile(f)
q, err := newQLogFile(f)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
continue
}
// Close what we've already opened.
cerr := closeQFiles(qFiles)
if cerr != nil {
log.Debug("querylog: closing files: %s", cerr)
cErr := closeQFiles(qFiles)
if cErr != nil {
log.Debug("querylog: closing files: %s", cErr)
}
return nil, err
@@ -47,31 +47,28 @@ func NewQLogReader(files []string) (*QLogReader, error) {
qFiles = append(qFiles, q)
}
return &QLogReader{
qFiles: qFiles,
currentFile: (len(qFiles) - 1),
}, nil
return &qLogReader{qFiles: qFiles, currentFile: len(qFiles) - 1}, nil
}
// seekTS performs binary search of a query log record with the specified
// timestamp. If the record is found, it sets QLogReader's position to point to
// that line, so that the next ReadNext call returned this line.
func (r *QLogReader) seekTS(timestamp int64) (err error) {
// timestamp. If the record is found, it sets qLogReader's position to point
// to that line, so that the next ReadNext call returned this line.
func (r *qLogReader) seekTS(timestamp int64) (err error) {
for i := len(r.qFiles) - 1; i >= 0; i-- {
q := r.qFiles[i]
_, _, err = q.seekTS(timestamp)
if err != nil {
if errors.Is(err, ErrTSTooEarly) {
if errors.Is(err, errTSTooEarly) {
// Look at the next file, since we've reached the end of this
// one. If there is no next file, it's not found.
err = ErrTSNotFound
err = errTSNotFound
continue
} else if errors.Is(err, ErrTSTooLate) {
} else if errors.Is(err, errTSTooLate) {
// Just seek to the start then. timestamp is probably between
// the end of the previous one and the start of this one.
return r.SeekStart()
} else if errors.Is(err, ErrTSNotFound) {
} else if errors.Is(err, errTSNotFound) {
return err
} else {
return fmt.Errorf("seekts: file at index %d: %w", i, err)
@@ -80,7 +77,7 @@ func (r *QLogReader) seekTS(timestamp int64) (err error) {
// The search is finished, and the searched element has been found.
// Update currentFile only, position is already set properly in
// QLogFile.
// qLogFile.
r.currentFile = i
return nil
@@ -93,13 +90,13 @@ func (r *QLogReader) seekTS(timestamp int64) (err error) {
return nil
}
// SeekStart changes the current position to the end of the newest file
// Please note that we're reading query log in the reverse order
// and that's why log start is actually the end of file
// SeekStart changes the current position to the end of the newest file.
// Please note that we're reading query log in the reverse order and that's why
// the log starts actually at the end of file.
//
// Returns nil if we were able to change the current position.
// Returns error in any other case.
func (r *QLogReader) SeekStart() error {
// Returns nil if we were able to change the current position. Returns error
// in any other cases.
func (r *qLogReader) SeekStart() error {
if len(r.qFiles) == 0 {
return nil
}
@@ -110,10 +107,12 @@ func (r *QLogReader) SeekStart() error {
return err
}
// ReadNext reads the next line (in the reverse order) from the query log files.
// and shifts the current position left to the next (actually prev) line (or the next file).
// returns io.EOF if there's nothing to read more.
func (r *QLogReader) ReadNext() (string, error) {
// ReadNext reads the next line (in the reverse order) from the query log
// files. Then shifts the current position left to the next (actually prev)
// line (or the next file).
//
// Returns io.EOF if there is nothing more to read.
func (r *qLogReader) ReadNext() (string, error) {
if len(r.qFiles) == 0 {
return "", io.EOF
}
@@ -122,7 +121,7 @@ func (r *QLogReader) ReadNext() (string, error) {
q := r.qFiles[r.currentFile]
line, err := q.ReadNext()
if err != nil {
// Shift to the older file
// Shift to the older file.
r.currentFile--
if r.currentFile < 0 {
break
@@ -130,10 +129,10 @@ func (r *QLogReader) ReadNext() (string, error) {
q = r.qFiles[r.currentFile]
// Set it's position to the start right away
// Set its position to the start right away.
_, err = q.SeekStart()
// This is unexpected, return an error right away
// This is unexpected, return an error right away.
if err != nil {
return "", err
}
@@ -142,17 +141,17 @@ func (r *QLogReader) ReadNext() (string, error) {
}
}
// Nothing to read anymore
// Nothing to read anymore.
return "", io.EOF
}
// Close closes the QLogReader
func (r *QLogReader) Close() error {
// Close closes the qLogReader.
func (r *qLogReader) Close() error {
return closeQFiles(r.qFiles)
}
// closeQFiles - helper method to close multiple QLogFile instances
func closeQFiles(qFiles []*QLogFile) error {
// closeQFiles is a helper method to close multiple qLogFile instances.
func closeQFiles(qFiles []*qLogFile) error {
var errs []error
for _, q := range qFiles {
@@ -163,7 +162,7 @@ func closeQFiles(qFiles []*QLogFile) error {
}
if len(errs) > 0 {
return errors.List("error while closing QLogReader", errs...)
return errors.List("error while closing qLogReader", errs...)
}
return nil

View File

@@ -10,15 +10,15 @@ import (
"github.com/stretchr/testify/require"
)
// newTestQLogReader creates new *QLogReader for tests and registers the
// newTestQLogReader creates new *qLogReader for tests and registers the
// required cleanup functions.
func newTestQLogReader(t *testing.T, filesNum, linesNum int) (reader *QLogReader) {
func newTestQLogReader(t *testing.T, filesNum, linesNum int) (reader *qLogReader) {
t.Helper()
testFiles := prepareTestFiles(t, filesNum, linesNum)
// Create the new QLogReader instance.
reader, err := NewQLogReader(testFiles)
// Create the new qLogReader instance.
reader, err := newQLogReader(testFiles)
require.NoError(t, err)
assert.NotNil(t, reader)
@@ -75,9 +75,9 @@ func TestQLogReader_Seek(t *testing.T) {
r := newTestQLogReader(t, 2, 10000)
testCases := []struct {
want error
name string
time string
want error
}{{
name: "not_too_old",
time: "2020-02-18T22:39:35.920973+03:00",
@@ -97,7 +97,7 @@ func TestQLogReader_Seek(t *testing.T) {
}, {
name: "non-existent_long_ago",
time: "2000-02-19T01:23:16.920973+03:00",
want: ErrTSNotFound,
want: errTSNotFound,
}, {
name: "non-existent_far_ahead",
time: "2100-02-19T01:23:16.920973+03:00",
@@ -105,7 +105,7 @@ func TestQLogReader_Seek(t *testing.T) {
}, {
name: "non-existent_but_could",
time: "2020-02-18T22:36:37.000000+03:00",
want: ErrTSNotFound,
want: errTSNotFound,
}}
for _, tc := range testCases {
@@ -125,9 +125,9 @@ func TestQLogReader_ReadNext(t *testing.T) {
r := newTestQLogReader(t, filesNum, linesNum)
testCases := []struct {
want error
name string
start int
want error
}{{
name: "ok",
start: 0,

View File

@@ -1,9 +1,11 @@
package querylog
import (
"fmt"
"io"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"golang.org/x/exp/slices"
)
@@ -134,84 +136,112 @@ func (l *queryLog) search(params *searchParams) (entries []*logEntry, oldest tim
return entries, oldest
}
// searchFiles looks up log records from all log files. It optionally uses the
// client cache, if provided. searchFiles does not scan more than
// maxFileScanEntries so callers may need to call it several times to get all
// results. oldest and total are the time of the oldest processed entry and the
// total number of processed entries, including discarded ones, correspondingly.
func (l *queryLog) searchFiles(
params *searchParams,
cache clientCache,
) (entries []*logEntry, oldest time.Time, total int) {
// seekRecord changes the current position to the next record older than the
// provided parameter.
func (r *qLogReader) seekRecord(olderThan time.Time) (err error) {
if olderThan.IsZero() {
return r.SeekStart()
}
err = r.seekTS(olderThan.UnixNano())
if err == nil {
// Read to the next record, because we only need the one that goes
// after it.
_, err = r.ReadNext()
}
return err
}
// setQLogReader creates a reader with the specified files and sets the
// position to the next record older than the provided parameter.
func (l *queryLog) setQLogReader(olderThan time.Time) (qr *qLogReader, err error) {
files := []string{
l.logFile + ".1",
l.logFile,
}
r, err := NewQLogReader(files)
r, err := newQLogReader(files)
if err != nil {
log.Error("querylog: opening qlog reader: %s", err)
return entries, oldest, 0
}
defer func() {
closeErr := r.Close()
if closeErr != nil {
log.Error("querylog: closing file: %s", err)
}
}()
if params.olderThan.IsZero() {
err = r.SeekStart()
} else {
err = r.seekTS(params.olderThan.UnixNano())
if err == nil {
// Read to the next record, because we only need the one that goes
// after it.
_, err = r.ReadNext()
}
return nil, fmt.Errorf("opening qlog reader: %s", err)
}
err = r.seekRecord(olderThan)
if err != nil {
log.Debug("querylog: cannot seek to %s: %s", params.olderThan, err)
defer func() { err = errors.WithDeferred(err, r.Close()) }()
log.Debug("querylog: cannot seek to %s: %s", olderThan, err)
return entries, oldest, 0
return nil, nil
}
totalLimit := params.offset + params.limit
oldestNano := int64(0)
return r, nil
}
// By default, we do not scan more than maxFileScanEntries at once. The
// idea is to make search calls faster so that the UI could handle it and
// show something quicker. This behavior can be overridden if
// maxFileScanEntries is set to 0.
// readEntries reads entries from the reader to totalLimit. By default, we do
// not scan more than maxFileScanEntries at once. The idea is to make search
// calls faster so that the UI could handle it and show something quicker.
// This behavior can be overridden if maxFileScanEntries is set to 0.
func (l *queryLog) readEntries(
r *qLogReader,
params *searchParams,
cache clientCache,
totalLimit int,
) (entries []*logEntry, oldestNano int64, total int) {
for total < params.maxFileScanEntries || params.maxFileScanEntries <= 0 {
var e *logEntry
var ts int64
e, ts, err = l.readNextEntry(r, params, cache)
if err != nil {
if err == io.EOF {
ent, ts, rErr := l.readNextEntry(r, params, cache)
if rErr != nil {
if rErr == io.EOF {
oldestNano = 0
break
}
log.Error("querylog: reading next entry: %s", err)
log.Error("querylog: reading next entry: %s", rErr)
}
oldestNano = ts
total++
if e != nil {
entries = append(entries, e)
if len(entries) == totalLimit {
break
}
if ent == nil {
continue
}
entries = append(entries, ent)
if len(entries) == totalLimit {
break
}
}
return entries, oldestNano, total
}
// searchFiles looks up log records from all log files. It optionally uses the
// client cache, if provided. searchFiles does not scan more than
// maxFileScanEntries so callers may need to call it several times to get all
// the results. oldest and total are the time of the oldest processed entry
// and the total number of processed entries, including discarded ones,
// correspondingly.
func (l *queryLog) searchFiles(
params *searchParams,
cache clientCache,
) (entries []*logEntry, oldest time.Time, total int) {
r, err := l.setQLogReader(params.olderThan)
if err != nil {
log.Error("querylog: %s", err)
}
if r == nil {
return entries, oldest, 0
}
defer func() {
if closeErr := r.Close(); closeErr != nil {
log.Error("querylog: closing file: %s", closeErr)
}
}()
totalLimit := params.offset + params.limit
entries, oldestNano, total := l.readEntries(r, params, cache, totalLimit)
if oldestNano != 0 {
oldest = time.Unix(0, oldestNano)
}
@@ -243,11 +273,11 @@ func (f quickMatchClientFinder) findClient(clientID, ip string) (c *Client) {
}
// readNextEntry reads the next log entry and checks if it matches the search
// criteria. It optionally uses the client cache, if provided. e is nil if the
// entry doesn't match the search criteria. ts is the timestamp of the
// criteria. It optionally uses the client cache, if provided. e is nil if
// the entry doesn't match the search criteria. ts is the timestamp of the
// processed entry.
func (l *queryLog) readNextEntry(
r *QLogReader,
r *qLogReader,
params *searchParams,
cache clientCache,
) (e *logEntry, ts int64, err error) {

View File

@@ -93,3 +93,67 @@ func TestQueryLog_Search_findClient(t *testing.T) {
assert.Equal(t, knownClientName, gotClient.Name)
}
// BenchmarkQueryLog_Search compares the speed of search with limit-offset
// parameters and the one with oldenThan timestamp specified.
func BenchmarkQueryLog_Search(b *testing.B) {
l, err := newQueryLog(Config{
Enabled: true,
RotationIvl: timeutil.Day,
MemSize: 100,
BaseDir: b.TempDir(),
})
require.NoError(b, err)
const (
entNum = 100000
firstPageDomain = "first.example.org"
secondPageDomain = "second.example.org"
)
// Add entries to the log.
for i := 0; i < entNum; i++ {
addEntry(l, secondPageDomain, net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
}
// Write them to the first file.
require.NoError(b, l.flushLogBuffer())
// Add more to the in-memory part of log.
for i := 0; i < entNum; i++ {
addEntry(l, firstPageDomain, net.IPv4(1, 1, 1, 1), net.IPv4(2, 2, 2, 1))
}
b.Run("limit_offset", func(b *testing.B) {
params := newSearchParams()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
params.offset += params.limit
_, _ = l.search(params)
}
})
b.Run("timestamp", func(b *testing.B) {
params := newSearchParams()
params.olderThan = time.Now().Add(-1 * time.Hour)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
params.olderThan = params.olderThan.Add(1 * time.Minute)
_, _ = l.search(params)
}
})
// Most recent result, on a MBP15:
//
// goos: darwin
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardHome/internal/querylog
// cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
// BenchmarkQueryLog_Search
// BenchmarkQueryLog_Search/limit_offset
// BenchmarkQueryLog_Search/limit_offset-12 547 2066079 ns/op 2325019 B/op 26633 allocs/op
// BenchmarkQueryLog_Search/timestamp
// BenchmarkQueryLog_Search/timestamp-12 1303 2028888 ns/op 2219337 B/op 25194 allocs/op
}

View File

@@ -2,18 +2,25 @@ package querylog
import "time"
// searchParams represent the search query sent by the client
// searchParams represent the search query sent by the client.
type searchParams struct {
// searchCriteria - list of search criteria that we use to get filter results
searchCriteria []searchCriterion
// olderThen - return entries that are older than this value
// if not set - disregard it and return any value
// olderThen represents a parameter for entries that are older than this
// parameter value. If not set, disregard it and return any value.
olderThan time.Time
offset int // offset for the search
limit int // limit the number of records returned
maxFileScanEntries int // maximum log entries to scan in query log files. if 0 - no limit
// searchCriteria is a list of search criteria that we use to get filter
// results.
searchCriteria []searchCriterion
// offset for the search.
offset int
// limit the number of records returned.
limit int
// maxFileScanEntries is a maximum of log entries to scan in query log
// files. If not set, then no limit.
maxFileScanEntries int
}
// newSearchParams - creates an empty instance of searchParams

View File

@@ -0,0 +1,220 @@
// Package schedule provides types for scheduling.
package schedule
import (
"fmt"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/timeutil"
"gopkg.in/yaml.v3"
)
// Weekly is a schedule for one week. Each day of the week has one range with
// a beginning and an end.
type Weekly struct {
// location is used to calculate the offsets of the day ranges.
location *time.Location
// days are the day ranges of this schedule. The indexes of this array are
// the [time.Weekday] values.
days [7]dayRange
}
// EmptyWeekly creates empty weekly schedule with local time zone.
func EmptyWeekly() (w *Weekly) {
return &Weekly{
location: time.Local,
}
}
// Contains returns true if t is within the corresponding day range of the
// schedule in the schedule's time zone.
func (w *Weekly) Contains(t time.Time) (ok bool) {
t = t.In(w.location)
wd := t.Weekday()
dr := w.days[wd]
// Calculate the offset of the day range.
//
// NOTE: Do not use [time.Truncate] since it requires UTC time zone.
y, m, d := t.Date()
day := time.Date(y, m, d, 0, 0, 0, 0, w.location)
offset := t.Sub(day)
return dr.contains(offset)
}
// type check
var _ yaml.Unmarshaler = (*Weekly)(nil)
// UnmarshalYAML implements the [yaml.Unmarshaler] interface for *Weekly.
func (w *Weekly) UnmarshalYAML(value *yaml.Node) (err error) {
conf := &weeklyConfig{}
err = value.Decode(conf)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
weekly := Weekly{}
weekly.location, err = time.LoadLocation(conf.TimeZone)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
days := []dayConfig{
time.Sunday: conf.Sunday,
time.Monday: conf.Monday,
time.Tuesday: conf.Tuesday,
time.Wednesday: conf.Wednesday,
time.Thursday: conf.Thursday,
time.Friday: conf.Friday,
time.Saturday: conf.Saturday,
}
for i, d := range days {
r := dayRange{
start: d.Start.Duration,
end: d.End.Duration,
}
err = w.validate(r)
if err != nil {
return fmt.Errorf("weekday %s: %w", time.Weekday(i), err)
}
weekly.days[i] = r
}
*w = weekly
return nil
}
// weeklyConfig is the YAML configuration structure of Weekly.
type weeklyConfig struct {
// TimeZone is the local time zone.
TimeZone string `yaml:"time_zone"`
// Days of the week.
Sunday dayConfig `yaml:"sun,omitempty"`
Monday dayConfig `yaml:"mon,omitempty"`
Tuesday dayConfig `yaml:"tue,omitempty"`
Wednesday dayConfig `yaml:"wed,omitempty"`
Thursday dayConfig `yaml:"thu,omitempty"`
Friday dayConfig `yaml:"fri,omitempty"`
Saturday dayConfig `yaml:"sat,omitempty"`
}
// dayConfig is the YAML configuration structure of dayRange.
type dayConfig struct {
Start timeutil.Duration `yaml:"start"`
End timeutil.Duration `yaml:"end"`
}
// maxDayRange is the maximum value for day range end.
const maxDayRange = 24 * time.Hour
// validate returns the day range rounding errors, if any.
func (w *Weekly) validate(r dayRange) (err error) {
defer func() { err = errors.Annotate(err, "bad day range: %w") }()
err = r.validate()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
start := r.start.Truncate(time.Minute)
end := r.end.Truncate(time.Minute)
switch {
case start != r.start:
return fmt.Errorf("start %s isn't rounded to minutes", r.start)
case end != r.end:
return fmt.Errorf("end %s isn't rounded to minutes", r.end)
default:
return nil
}
}
// type check
var _ yaml.Marshaler = (*Weekly)(nil)
// MarshalYAML implements the [yaml.Marshaler] interface for *Weekly.
func (w *Weekly) MarshalYAML() (v any, err error) {
return weeklyConfig{
TimeZone: w.location.String(),
Sunday: dayConfig{
Start: timeutil.Duration{Duration: w.days[time.Sunday].start},
End: timeutil.Duration{Duration: w.days[time.Sunday].end},
},
Monday: dayConfig{
Start: timeutil.Duration{Duration: w.days[time.Monday].start},
End: timeutil.Duration{Duration: w.days[time.Monday].end},
},
Tuesday: dayConfig{
Start: timeutil.Duration{Duration: w.days[time.Tuesday].start},
End: timeutil.Duration{Duration: w.days[time.Tuesday].end},
},
Wednesday: dayConfig{
Start: timeutil.Duration{Duration: w.days[time.Wednesday].start},
End: timeutil.Duration{Duration: w.days[time.Wednesday].end},
},
Thursday: dayConfig{
Start: timeutil.Duration{Duration: w.days[time.Thursday].start},
End: timeutil.Duration{Duration: w.days[time.Thursday].end},
},
Friday: dayConfig{
Start: timeutil.Duration{Duration: w.days[time.Friday].start},
End: timeutil.Duration{Duration: w.days[time.Friday].end},
},
Saturday: dayConfig{
Start: timeutil.Duration{Duration: w.days[time.Saturday].start},
End: timeutil.Duration{Duration: w.days[time.Saturday].end},
},
}, nil
}
// dayRange represents a single interval within a day. The interval begins at
// start and ends before end. That is, it contains a time point T if start <=
// T < end.
type dayRange struct {
// start is an offset from the beginning of the day. It must be greater
// than or equal to zero and less than 24h.
start time.Duration
// end is an offset from the beginning of the day. It must be greater than
// or equal to zero and less than or equal to 24h.
end time.Duration
}
// validate returns the day range validation errors, if any.
func (r dayRange) validate() (err error) {
switch {
case r == dayRange{}:
return nil
case r.start < 0:
return fmt.Errorf("start %s is negative", r.start)
case r.end < 0:
return fmt.Errorf("end %s is negative", r.end)
case r.start >= r.end:
return fmt.Errorf("start %s is greater or equal to end %s", r.start, r.end)
case r.start >= maxDayRange:
return fmt.Errorf("start %s is greater or equal to %s", r.start, maxDayRange)
case r.end > maxDayRange:
return fmt.Errorf("end %s is greater than %s", r.end, maxDayRange)
default:
return nil
}
}
// contains returns true if start <= offset < end, where offset is the time
// duration from the beginning of the day.
func (r *dayRange) contains(offset time.Duration) (ok bool) {
return r.start <= offset && offset < r.end
}

View File

@@ -0,0 +1,371 @@
package schedule
import (
"testing"
"time"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
func TestWeekly_Contains(t *testing.T) {
baseTime := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
otherTime := baseTime.Add(1 * timeutil.Day)
// NOTE: In the Etc area the sign of the offsets is flipped. So, Etc/GMT-3
// is actually UTC+03:00.
otherTZ := time.FixedZone("Etc/GMT-3", 3*60*60)
// baseSchedule, 12:00 to 14:00.
baseSchedule := &Weekly{
days: [7]dayRange{
time.Friday: {start: 12 * time.Hour, end: 14 * time.Hour},
},
location: time.UTC,
}
// allDaySchedule, 00:00 to 24:00.
allDaySchedule := &Weekly{
days: [7]dayRange{
time.Friday: {start: 0, end: 24 * time.Hour},
},
location: time.UTC,
}
// oneMinSchedule, 00:00 to 00:01.
oneMinSchedule := &Weekly{
days: [7]dayRange{
time.Friday: {start: 0, end: 1 * time.Minute},
},
location: time.UTC,
}
testCases := []struct {
schedule *Weekly
assert assert.BoolAssertionFunc
t time.Time
name string
}{{
schedule: EmptyWeekly(),
assert: assert.False,
t: baseTime,
name: "empty",
}, {
schedule: allDaySchedule,
assert: assert.True,
t: baseTime,
name: "same_day_all_day",
}, {
schedule: baseSchedule,
assert: assert.True,
t: baseTime.Add(13 * time.Hour),
name: "same_day_inside",
}, {
schedule: baseSchedule,
assert: assert.False,
t: baseTime.Add(11 * time.Hour),
name: "same_day_outside",
}, {
schedule: allDaySchedule,
assert: assert.True,
t: baseTime.Add(24*time.Hour - time.Second),
name: "same_day_last_second",
}, {
schedule: allDaySchedule,
assert: assert.False,
t: otherTime,
name: "other_day_all_day",
}, {
schedule: baseSchedule,
assert: assert.False,
t: otherTime.Add(13 * time.Hour),
name: "other_day_inside",
}, {
schedule: baseSchedule,
assert: assert.False,
t: otherTime.Add(11 * time.Hour),
name: "other_day_outside",
}, {
schedule: baseSchedule,
assert: assert.True,
t: baseTime.Add(13 * time.Hour).In(otherTZ),
name: "same_day_inside_other_tz",
}, {
schedule: baseSchedule,
assert: assert.False,
t: baseTime.Add(11 * time.Hour).In(otherTZ),
name: "same_day_outside_other_tz",
}, {
schedule: oneMinSchedule,
assert: assert.True,
t: baseTime,
name: "one_minute_beginning",
}, {
schedule: oneMinSchedule,
assert: assert.True,
t: baseTime.Add(1*time.Minute - 1),
name: "one_minute_end",
}, {
schedule: oneMinSchedule,
assert: assert.False,
t: baseTime.Add(1 * time.Minute),
name: "one_minute_past_end",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.assert(t, tc.schedule.Contains(tc.t))
})
}
}
const brusselsSunday = `
sun:
start: 12h
end: 14h
time_zone: Europe/Brussels
`
func TestWeekly_UnmarshalYAML(t *testing.T) {
const (
sameTime = `
sun:
start: 9h
end: 9h
`
negativeStart = `
sun:
start: -1h
end: 1h
`
badTZ = `
time_zone: "bad_timezone"
`
badYAML = `
yaml: "bad"
yaml: "bad"
`
)
brusseltsTZ, err := time.LoadLocation("Europe/Brussels")
require.NoError(t, err)
brusselsWeekly := &Weekly{
days: [7]dayRange{{
start: time.Hour * 12,
end: time.Hour * 14,
}},
location: brusseltsTZ,
}
testCases := []struct {
name string
wantErrMsg string
data []byte
want *Weekly
}{{
name: "empty",
wantErrMsg: "",
data: []byte(""),
want: &Weekly{},
}, {
name: "null",
wantErrMsg: "",
data: []byte("null"),
want: &Weekly{},
}, {
name: "brussels_sunday",
wantErrMsg: "",
data: []byte(brusselsSunday),
want: brusselsWeekly,
}, {
name: "start_equal_end",
wantErrMsg: "weekday Sunday: bad day range: start 9h0m0s is greater or equal to end 9h0m0s",
data: []byte(sameTime),
want: &Weekly{},
}, {
name: "start_negative",
wantErrMsg: "weekday Sunday: bad day range: start -1h0m0s is negative",
data: []byte(negativeStart),
want: &Weekly{},
}, {
name: "bad_time_zone",
wantErrMsg: "unknown time zone bad_timezone",
data: []byte(badTZ),
want: &Weekly{},
}, {
name: "bad_yaml",
wantErrMsg: "yaml: unmarshal errors:\n line 3: mapping key \"yaml\" already defined at line 2",
data: []byte(badYAML),
want: &Weekly{},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
w := &Weekly{}
err = yaml.Unmarshal(tc.data, w)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
assert.Equal(t, tc.want, w)
})
}
}
func TestWeekly_MarshalYAML(t *testing.T) {
brusselsTZ, err := time.LoadLocation("Europe/Brussels")
require.NoError(t, err)
brusselsWeekly := &Weekly{
days: [7]dayRange{time.Sunday: {
start: time.Hour * 12,
end: time.Hour * 14,
}},
location: brusselsTZ,
}
testCases := []struct {
name string
data []byte
want *Weekly
}{{
name: "empty",
data: []byte(""),
want: &Weekly{},
}, {
name: "null",
data: []byte("null"),
want: &Weekly{},
}, {
name: "brussels_sunday",
data: []byte(brusselsSunday),
want: brusselsWeekly,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var data []byte
data, err = yaml.Marshal(brusselsWeekly)
require.NoError(t, err)
w := &Weekly{}
err = yaml.Unmarshal(data, w)
require.NoError(t, err)
assert.Equal(t, brusselsWeekly, w)
})
}
}
func TestWeekly_Validate(t *testing.T) {
testCases := []struct {
name string
in dayRange
wantErrMsg string
}{{
name: "empty",
wantErrMsg: "",
in: dayRange{},
}, {
name: "start_seconds",
wantErrMsg: "bad day range: start 1s isn't rounded to minutes",
in: dayRange{
start: time.Second,
end: time.Hour,
},
}, {
name: "end_seconds",
wantErrMsg: "bad day range: end 1s isn't rounded to minutes",
in: dayRange{
start: 0,
end: time.Second,
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
w := &Weekly{}
err := w.validate(tc.in)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
})
}
}
func TestDayRange_Validate(t *testing.T) {
testCases := []struct {
name string
in dayRange
wantErrMsg string
}{{
name: "empty",
wantErrMsg: "",
in: dayRange{},
}, {
name: "valid",
wantErrMsg: "",
in: dayRange{
start: time.Hour,
end: time.Hour * 2,
},
}, {
name: "valid_end_max",
wantErrMsg: "",
in: dayRange{
start: 0,
end: time.Hour * 24,
},
}, {
name: "start_negative",
wantErrMsg: "start -1h0m0s is negative",
in: dayRange{
start: time.Hour * -1,
end: time.Hour * 2,
},
}, {
name: "end_negative",
wantErrMsg: "end -1h0m0s is negative",
in: dayRange{
start: 0,
end: time.Hour * -1,
},
}, {
name: "start_equal_end",
wantErrMsg: "start 1h0m0s is greater or equal to end 1h0m0s",
in: dayRange{
start: time.Hour,
end: time.Hour,
},
}, {
name: "start_greater_end",
wantErrMsg: "start 2h0m0s is greater or equal to end 1h0m0s",
in: dayRange{
start: time.Hour * 2,
end: time.Hour,
},
}, {
name: "start_equal_max",
wantErrMsg: "start 24h0m0s is greater or equal to 24h0m0s",
in: dayRange{
start: time.Hour * 24,
end: time.Hour * 48,
},
}, {
name: "end_greater_max",
wantErrMsg: "end 48h0m0s is greater than 24h0m0s",
in: dayRange{
start: 0,
end: time.Hour * 48,
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.in.validate()
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
})
}
}

View File

@@ -8,16 +8,17 @@ require (
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28
github.com/kisielk/errcheck v1.6.3
github.com/kyoh86/looppointer v0.2.1
github.com/securego/gosec/v2 v2.15.0
golang.org/x/tools v0.8.0
golang.org/x/vuln v0.0.0-20230418010118-28ba02ac73db
github.com/securego/gosec/v2 v2.16.0
github.com/uudashr/gocognit v1.0.6
golang.org/x/tools v0.10.0
golang.org/x/vuln v0.1.0
honnef.co/go/tools v0.4.3
mvdan.cc/gofumpt v0.5.0
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8
)
require (
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/BurntSushi/toml v1.3.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.3 // indirect
@@ -25,9 +26,9 @@ require (
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/exp/typeparams v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.9.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,17 +1,19 @@
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.1 h1:rHnDkSK+/g6DlREUK73PkmIs60pqrnuduK+JmP++JmU=
github.com/BurntSushi/toml v1.3.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/golangci/misspell v0.4.0 h1:KtVB/hTK4bbL/S6bs64rYyk8adjmh1BygbBiaAiX+a0=
github.com/golangci/misspell v0.4.0/go.mod h1:W6O/bwV6lGDxUCChm2ykw9NQdd5bYd1Xkjo88UcWyJc=
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
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/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -29,15 +31,17 @@ github.com/kyoh86/nolint v0.0.1 h1:GjNxDEkVn2wAxKHtP7iNTrRxytRZ1wXxLV5j4XzGfRU=
github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ewq9gtI=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI=
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/securego/gosec/v2 v2.15.0 h1:v4Ym7FF58/jlykYmmhZ7mTm7FQvN/setNm++0fgIAtw=
github.com/securego/gosec/v2 v2.15.0/go.mod h1:VOjTrZOkUtSDt2QLSJmQBMWnvwiQPEjg0l+5juIqGk8=
github.com/securego/gosec/v2 v2.16.0 h1:Pi0JKoasQQ3NnoRao/ww/N/XdynIB9NRYYZT5CyOs5U=
github.com/securego/gosec/v2 v2.16.0/go.mod h1:xvLcVZqUfo4aAQu56TNv7/Ltz6emAOQAEsrZrt7uGlI=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/uudashr/gocognit v1.0.6 h1:2Cgi6MweCsdB6kpcVQp7EW4U23iBFQWfTXiWlyp842Y=
github.com/uudashr/gocognit v1.0.6/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -49,25 +53,26 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp/typeparams v0.0.0-20230321023759-10a507213a29 h1:e7LhZmJ631l59keHP9ssC3sgSn3/oiEHKHKXDkimURY=
golang.org/x/exp/typeparams v0.0.0-20230321023759-10a507213a29/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230522175609-2e198f4a06a1 h1:pnP8r+W8Fm7XJ8CWtXi4S9oJmPBTrkfYN/dNbaPj6Y4=
golang.org/x/exp/typeparams v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -77,23 +82,25 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/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/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.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.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
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-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/vuln v0.0.0-20230418010118-28ba02ac73db h1:tLxfII6jPR3mfwEMkyOakawu+Lldo9hIA7vliXnDZYg=
golang.org/x/vuln v0.0.0-20230418010118-28ba02ac73db/go.mod h1:64LpnL2PuSMzFYeCmJjYiRbroOUG9aCZYznINnF5PHE=
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
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/vuln v0.1.0 h1:9GRdj6wAIkDrsMevuolY+SXERPjQPp2P1ysYA0jpZe0=
golang.org/x/vuln v0.1.0/go.mod h1:/YuzZYjGbwB8y19CisAppfyw3uTZnuCz3r+qgx/QRzU=
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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

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