Compare commits

..

59 Commits

Author SHA1 Message Date
Stanislav Chzhen
0a6c8b4198 Pull request 2409: upd-proxy
Merge in DNS/adguard-home from upd-proxy to master

Squashed commit of the following:

commit 2d9f5f1c3c48102bc82806721b63a7095332cc0b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue May 13 17:51:14 2025 +0300

    all: upd proxy
2025-05-13 18:16:12 +03:00
Ainar Garipov
ae840c9c96 Pull request 2405: AGDNS-2374-updater-slog
Squashed commit of the following:

commit 89c3df471964b674b7ddafeb22566e5be9b56a13
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon May 12 18:59:39 2025 +0300

    updater: imp log

commit d78ba4368027ddcbb41c10fbf09d43fe0721dc4c
Merge: 68410954c 187b759fc
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon May 12 18:53:33 2025 +0300

    Merge branch 'master' into AGDNS-2374-updater-slog

commit 68410954c80d76b2adafe4ed28fafdd6b6b6daae
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Apr 30 15:54:30 2025 +0300

    updater: imp docs

commit 99a705218fb849bb59dee5b801c5279a501bcf98
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Apr 30 15:40:30 2025 +0300

    updater: imp docs, logs

commit 2a83ee3ebf9610a2703d99ec6a6b327a315f6cce
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Apr 29 21:01:02 2025 +0300

    updater: use slog
2025-05-13 14:42:33 +03:00
Stanislav Chzhen
187b759fc6 Pull request 2404: 7801-fix-cmd-update
Closes #7801.

Squashed commit of the following:

commit f6e924e939eb9487e2c7743f04bb217e758ef253
Merge: 9caa54933 8c8323ae6
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed May 7 16:22:09 2025 +0300

    Merge branch 'master' into 7801-fix-cmd-update

commit 9caa5493302af11b8d522feb2cf6e6f0facaec53
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 29 20:52:09 2025 +0300

    home: imp code

commit 765ea0023972e326c54f0c17ba79f3feca8ff803
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 29 20:16:55 2025 +0300

    all: fix cmd update
2025-05-07 16:34:46 +03:00
Dimitry Kolyshev
8c8323ae68 Pull request: AGDNS-2818-upd-golibs
Merge in DNS/adguard-home from AGDNS-2818-upd-golibs to master

Squashed commit of the following:

commit f2a41b85ec27b306407b3fa96778b266dc8232e9
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 7 09:52:32 2025 +0300

    client: imp code

commit b4b668f7386c4abf0bbcf255c6c7b1edc5050727
Merge: c6f89e0b5 b5c47054a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 7 09:47:54 2025 +0300

    Merge remote-tracking branch 'origin/master' into AGDNS-2818-upd-golibs
    
    # Conflicts:
    #	go.mod
    #	go.sum

commit c6f89e0b5dcaf8842187e72c22cf9109a1c4edc9
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Apr 30 14:10:33 2025 +0300

    client: imp code

commit cf07b1802fb0f1aa005af86c2bd59485683582d2
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Apr 30 10:31:44 2025 +0300

    client: imp code

commit a10d4b1b9265f6eecd7a40746de058b961d6fdd7
Merge: 447a79ca6 e5d0f0b11
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Apr 30 10:24:00 2025 +0300

    Merge remote-tracking branch 'origin/master' into AGDNS-2818-upd-golibs

commit 447a79ca6eb296a339b1e8c57edddcfcce3efdc2
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 28 13:02:40 2025 +0300

    all: upd golibs

commit 693ef4f39d628a97dbe94e1a0c5d2078f31b7e63
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 28 12:47:39 2025 +0300

    all: upd golibs

commit a4f90eac8547eea74bfdaa8b1bb23e0502638777
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 28 10:25:33 2025 +0300

    all: upd golibs
2025-05-07 16:07:25 +03:00
Eugene Burkov
b5c47054ab Pull request 2407: Update i18n
Merge in DNS/adguard-home from upd-all to master

Squashed commit of the following:

commit cb4a2379ee2543391ab85c6dd29bffc083544b2c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue May 6 17:25:50 2025 +0300

    client: upd i18n
2025-05-06 17:50:19 +03:00
Stanislav Chzhen
4776255604 Pull request 2402: AG-40703-fix-custom-cache
Merge in DNS/adguard-home from AG-40703-fix-custom-cache to master

Squashed commit of the following:

commit e9b9aa34d6969e87cc151573912c2f22a1b81cea
Merge: b8ec40b3d e5d0f0b11
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue May 6 16:58:37 2025 +0300

    Merge branch 'master' into AG-40703-fix-custom-cache

commit b8ec40b3dd9f59124bbf5cfc2b303a37750f7497
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue May 6 16:20:48 2025 +0300

    all: upd proxy

commit 026624543c319c022cf5d57d958cc5127cf2a629
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 28 15:46:28 2025 +0300

    all: fix custom cache
2025-05-06 17:07:57 +03:00
Stanislav Chzhen
e5d0f0b119 Pull request 2401: AGDNS-2817-fix-tls-test
Merge in DNS/adguard-home from AGDNS-2817-fix-tls-test to master

Squashed commit of the following:

commit 8cd435e05b6bfb988be90475b28db6703a763b2e
Merge: b9544ba8b af7c2e3a9
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 29 19:54:17 2025 +0300

    Merge branch 'master' into AGDNS-2817-fix-tls-test

commit b9544ba8b9097637a3b217142f092a242c819a63
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 28 15:28:05 2025 +0300

    home: fix tls test
2025-04-29 20:03:03 +03:00
Ainar Garipov
af7c2e3a9d Pull request 2403: 7790-fix-cache-label
Closes #7790.

Squashed commit of the following:

commit 9e871bcd8b0dfefbad41cd4e9893f918c9a4b090
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Apr 29 14:48:58 2025 +0300

    client: fix cache label
2025-04-29 14:59:39 +03:00
Stanislav Chzhen
2c46bc92fe Pull request 2399: AGDNS-2686-fix-custom-upstream-cache
Merge in DNS/adguard-home from AGDNS-2686-fix-custom-upstream-cache to master

Squashed commit of the following:

commit 11ad20a225e0e21a59552dc885fbcb2d3acc1cef
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 24 18:51:35 2025 +0300

    client: imp docs

commit e6d73f2d7a9f2ea181b321dd0029cf6c42ddeba5
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 24 17:36:05 2025 +0300

    all: imp chlog

commit b8fdd884b801db28f03efb00bd871df2332cf40a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 24 17:05:11 2025 +0300

    client: fix dhcp clients cache

commit 1760699fcb8e61580a48e61037b805b8aa8ca8b4
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 24 15:37:50 2025 +0300

    all: upd chlog

commit c6f049c200736032e2d78a2023db7d8cc6c32917
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 24 15:33:53 2025 +0300

    client: imp tests

commit 7432de722292ef74bbdf5fbd875ea67d55b29040
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 24 15:32:26 2025 +0300

    client: fix custom upstream cache
2025-04-25 21:33:36 +03:00
Stanislav Chzhen
61a1403e4e Pull request 2378: AGDNS-2750-find-client
Merge in DNS/adguard-home from AGDNS-2750-find-client to master

Squashed commit of the following:

commit 98f1a8ca4622b6f502a5092273b9724203fe0bd8
Merge: 9270222d8 4ccc2a213
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Apr 23 17:53:20 2025 +0300

    Merge branch 'master' into AGDNS-2750-find-client

commit 9270222d8e9e03038e9434b54496cbb6164463cd
Merge: 6468ceec8 c7c62ad3b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 21 19:40:58 2025 +0300

    Merge branch 'master' into AGDNS-2750-find-client

commit 6468ceec82d30084771a53ff6720a8c11c68bf2f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 21 19:40:52 2025 +0300

    home: imp docs

commit 3fd4735a0d6db4fdf2d46f3da9794a687fdcaa8b
Merge: 1311a5869 a8fdf1c55
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 18 19:43:36 2025 +0300

    Merge branch 'master' into AGDNS-2750-find-client

commit 1311a58695de00f20c9704378ee6e964a44d1c59
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 18 19:42:41 2025 +0300

    home: imp code

commit b1f2c4c883c9476c5135140abac31f8ae6609b4f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Apr 16 16:47:59 2025 +0300

    home: imp code

commit d0a5abd66587c1ad602c2ccf6c8a45a3dfe39a5c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 15 14:58:31 2025 +0300

    client: imp naming

commit 5accdca325551237f003f1c416891b488fe5290b
Merge: 6a00232f7 4d258972d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 14 19:40:40 2025 +0300

    Merge branch 'master' into AGDNS-2750-find-client

commit 6a00232f76a0fe5ce781aa01637b6e04ace7250d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 14 19:30:32 2025 +0300

    home: imp code

commit 8633886457c6aab75f5676494b1f49d9811e9ab9
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 11 15:29:25 2025 +0300

    all: imp code

commit d6f16879e7b054a5ffac59131d2a6eff1da659c0
Merge: 58236fdec 6d282ae71
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 10 21:35:23 2025 +0300

    Merge branch 'master' into AGDNS-2750-find-client

commit 58236fdec5b64e83a44680ff8a89badc18ec81f1
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 10 21:23:01 2025 +0300

    all: upd ci

commit 3c4d946d7970987677d4ac984394e18987a29f9a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 10 21:16:03 2025 +0300

    all: upd go

commit cc1c97734506a9ffbe70fd3c676284e58a21ba46
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 10 20:58:56 2025 +0300

    all: imp code

commit 8f061c933152481a4c80eef2af575efd4919d82b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Apr 9 16:49:11 2025 +0300

    all: imp docs

commit 8d19355f1c519211a56cec3f23d527922d4f2ee0
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 7 21:35:06 2025 +0300

    all: imp code

commit f1e853f57e5d54d13bedcdab4f8e21e112f3a356
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Apr 2 14:57:40 2025 +0300

    all: imp code

commit 6a6ac7f899f29ddc90a583c80562233e646ba1d6
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 1 19:51:56 2025 +0300

    client: imp tests

commit 52040ee7393d0483c682f2f37d7b70f12f9cf621
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 1 19:28:18 2025 +0300

    all: imp code

commit 1e09208dbd2d35c3f6b2ade169324e23d1a643a5
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 26 15:33:02 2025 +0300

    all: imp code

... and 2 more commits
2025-04-23 18:10:52 +03:00
Stanislav Chzhen
4ccc2a2138 Pull request 2398: 7781-fix-serve-plain-dns
Updates #7781.

Squashed commit of the following:

commit 5dff0be1763e7da7dd655bf1e34dfa8402ad96e8
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 22 18:15:42 2025 +0300

    home: fix serve plain dns
2025-04-22 20:31:15 +03:00
Eugene Burkov
72425b80a3 Pull request 2397: Update changelog
Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 10ff5bce544ef796eb22739c7d20d8bf92b0109f
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 22 15:45:45 2025 +0300

    all: upd chlog
2025-04-22 17:39:45 +03:00
Eugene Burkov
c7c62ad3b6 Pull request 2395: Update all
Merge in DNS/adguard-home from upd-all to master

Squashed commit of the following:

commit c4bba4531813bdeb79536f7f601ade80a16a9163
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Apr 21 18:17:06 2025 +0300

    client: upd filters
2025-04-21 18:46:47 +03:00
Stanislav Chzhen
003e7ce0d5 Pull request 2393: 7773-fix-unencrypted_doh
Updates #7773.

Squashed commit of the following:

commit d9ca09c1d9b251998107fc87bd6daeb5999ea803
Merge: b67a71a7a a8fdf1c55
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 21 15:56:57 2025 +0300

    Merge branch 'master' into 7773-fix-unencrypted_doh

commit b67a71a7a9686d36cbf64a3f7561886bff7d9c5c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 18 16:01:49 2025 +0300

    home: imp docs

commit dab9b0582ff1ebc4637d5ec1ea3bc81190ed4066
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 18 15:09:36 2025 +0300

    home: fix unencrypted doh
2025-04-21 16:05:16 +03:00
Stanislav Chzhen
a8fdf1c553 Pull request 2386: AGDNS-2743-aghuser-session
Merge in DNS/adguard-home from AGDNS-2743-aghuser-session to master

Squashed commit of the following:

commit 74fd4bc11eaf784880855fa2c710a747428db146
Merge: 844e865f6 7d479baba
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 18 18:14:36 2025 +0300

    Merge branch 'master' into AGDNS-2743-aghuser-session

commit 844e865f647efb4de7f057c392894c8f65bab422
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 18 15:18:44 2025 +0300

    aghuser: imp fmt

commit 584288e0a3ddbe6d7ae31c80c22b8f397cfd0cae
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 17 20:16:54 2025 +0300

    aghuser: imp tests

commit ea4c8735585f6d30d6dedf2a40a8dd6b07609d07
Merge: c3fd8fe5e 3521e8ed9
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 17 20:10:06 2025 +0300

    Merge branch 'master' into AGDNS-2743-aghuser-session

commit c3fd8fe5eabaf2022a971197c018e140c254006d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 17 15:23:45 2025 +0300

    aghuser: imp tests

commit dfd9aba337227a8d3edc6f5a68f3f039afd1ca0b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Apr 16 21:40:14 2025 +0300

    aghuser: imp code

commit b6e75223bf7960f3a2e94c1a3ed7cc33539b9806
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 14 21:49:20 2025 +0300

    aghuser: imp code

commit 56d6f9d478eec399c376992ffb0f1ca5b797986d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 11 16:58:11 2025 +0300

    aghuser: user db

commit 6fdc2f60bf7f93e72d917abb12af8e4867143b6d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 10 14:11:22 2025 +0300

    all: upd scripts

commit 575946756f3f622360c5feafe3e721eee010e230
Merge: 7e1fac4ec 1cc6c00e4
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 10 14:05:46 2025 +0300

    Merge branch 'master' into AGDNS-2743-aghuser-session

commit 7e1fac4ecb1bde0013bca3f6b64e82d81a78c9c3
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 10 14:05:35 2025 +0300

    aghuser: session storage

commit acfb040f0bdff501c7304ea100b9faf1c07291ae
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 8 15:54:24 2025 +0300

    aghuser: session
2025-04-18 18:34:10 +03:00
Eugene Burkov
7d479baba6 Pull request 2392: Update all
Merge in DNS/adguard-home from upd-all to master

Squashed commit of the following:

commit c866dd80f8717cfbe886e56b88dd4857c9a305be
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 17 21:19:55 2025 +0300

    all: upd golibs

commit 3f48ed7909c190228ab78f1aba0453669f65a2ec
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 17 21:11:41 2025 +0300

    all: upd tools

commit c33cbdd1b7ea12caed672e7b9838e1a531805f19
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 17 21:09:20 2025 +0300

    client: upd i18n
2025-04-17 21:39:18 +03:00
Eugene Burkov
feb9c886d8 Pull request 2389: AG-38975 Update proxy
Merge in DNS/adguard-home from AG-38975-upd-proxy to master

Squashed commit of the following:

commit 94fc1fb9bb1c004fea17d52f586af263b6918694
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 17 20:45:12 2025 +0300

    home: fix typo

commit b1cbf45dcc54c6d4dc7de86868c189114caf7cb1
Merge: c2c868b7d 3521e8ed9
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 17 20:44:14 2025 +0300

    Merge branch 'master' into AG-38975-upd-proxy

commit c2c868b7d938eee6e4e4a19d43b2ed734155c7da
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 17 20:19:23 2025 +0300

    all: upd to tag

commit 1680a9d79f34bd42f0b64712b2e7e968890e7c6e
Merge: df42dec3a 4d258972d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 16 14:08:46 2025 +0300

    Merge branch 'master' into AG-38975-upd-proxy

commit df42dec3a302af0e6e301365a943763cd87d752d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 16 13:43:29 2025 +0300

    all: add thanks

commit f212d6a9ee3d614bd6db776204c65298f0359a87
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Apr 14 17:57:33 2025 +0300

    dnsforward: use bool

commit 055072e29dee0673cf38b198476e252bdbade8d9
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Apr 11 18:35:38 2025 +0300

    stats: imp doc

commit 6554101a73564b7786cd77d250344ba141c0cbb5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 10 19:32:34 2025 +0300

    all: log changes

commit da8ecf17778be9481232222c8bb7682beea475c6
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 10 19:07:04 2025 +0300

    all: upd proxy, add config
2025-04-17 21:03:21 +03:00
Stanislav Chzhen
3521e8ed9f Pull request 2382: AGDNS-2714-tls-config
Merge in DNS/adguard-home from AGDNS-2714-tls-config to master

Squashed commit of the following:

commit 073e5ec367db02690e9527602a1da6bfd29321a0
Merge: 18f38c9d4 4d258972d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Apr 16 18:25:23 2025 +0300

    Merge branch 'master' into AGDNS-2714-tls-config

commit 18f38c9d44337752c6d0f09142658f374de0979f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 11 15:02:00 2025 +0300

    dnsforward: imp docs

commit ed56d3c2bc239bdc9af000d847721c4c43d173a3
Merge: 3ef281ea2 1cc6c00e4
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 10 17:25:08 2025 +0300

    Merge branch 'master' into AGDNS-2714-tls-config

commit 3ef281ea28dc1fcab0a1291fb3221e6324077a10
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Apr 10 17:24:29 2025 +0300

    all: imp docs

commit b75f2874a816d4814d218c3b062d532f02e26ca5
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 7 17:16:59 2025 +0300

    dnsforward: imp code

commit 8ab17b96bca957a172062faaa23b72d5c7ed4d0d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 4 21:26:37 2025 +0300

    all: imp code

commit 1abce97b50fe0406dd1ec85b96a0f99b633325cc
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Apr 2 18:22:15 2025 +0300

    home: imp code

commit debf710f4ebbdfe3e4d2f15b1adcf6b86f8dfc0d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 1 14:52:21 2025 +0300

    home: imp code

commit 4aa26f15b721f2a3f32da29b3f664a02bc5a8608
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 1 14:16:16 2025 +0300

    all: imp code

commit 1a3e72f7a1276f9f797caf9b615f8a552cc9e988
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 31 21:22:40 2025 +0300

    all: imp code

commit 776ab824aef18ea27b59c02ebfc8620c715a867e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 27 14:00:33 2025 +0300

    home: tls config mu

commit 9ebf912f530181043df5c583e82291484996429a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 26 18:58:47 2025 +0300

    all: tls config
2025-04-16 18:57:04 +03:00
Eugene Miroshkin
4d258972d1 Pull request #2367: ADG-9679 add playwright test from python
Merge in DNS/adguard-home from ADG-9679 to master

Squashed commit of the following:

commit d2a759b4636b7ec931bfba625827c8b91c60c7e7
Merge: a5e7eea16 9726171f0
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Mon Apr 14 16:05:01 2025 +0200

    Merge remote-tracking branch 'origin/master' into ADG-9679

commit a5e7eea16e6c29d25290ee79b1918df8af59bb51
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Mon Apr 14 13:56:26 2025 +0200

    vitest version bump

commit 26620d1923d92b3a6eb9b80a364748f2f6f66030
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Thu Apr 10 15:39:11 2025 +0200

    formatting

commit dbab03d1316241eaff0fc9c99d58a1933e415d2b
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Thu Apr 10 15:37:55 2025 +0200

    rollback experiments

commit 4427d984177786f7d905915cf8080166b45d7b46
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Thu Apr 10 15:33:28 2025 +0200

    checking dir structure

commit 2cf7eed247d2869ed285dbee0bf32cf1d8df7e86
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Thu Apr 10 15:21:11 2025 +0200

    fixed docker image builder

commit 8bd06f412fad9dd09df0e076879bd2cbd2f30d1a
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Thu Apr 10 15:18:33 2025 +0200

    home-js-builder version bump

commit 2a83bfeb322a20ec4278e359b18d0466966ec043
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Thu Apr 10 15:17:38 2025 +0200

    try to remove installation dependencies for e2e test (build is already available)

commit 163e4581e83152f99058b798484468009e8e88b0
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Thu Apr 10 13:52:27 2025 +0200

    Revert "changed nslookup to dig in e2e tests"
    
    This reverts commit ecb68200ea28e295f504338cc59c711b5540022b.

commit 15f7c5e2c77e230da77a0f9de0bd9cce8451da95
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Thu Apr 10 13:45:40 2025 +0200

    js-home-builder version bump

commit ecb68200ea28e295f504338cc59c711b5540022b
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Apr 9 15:07:39 2025 +0200

    changed nslookup to dig in e2e tests

commit 77c94a60be8839f3e0ad9d02e7dbb2ebd802d3d6
Author: Eugene Miroshkin <e.miroshkin@adguard.com>
Date:   Wed Apr 9 11:09:15 2025 +0300

    revert timeouts

commit 9dfebc8bcaf2cd3258b39fcbd0f67ab51c2eb46d
Merge: 912f4cb7b 1cc6c00e4
Author: Eugene Miroshkin <e.miroshkin@adguard.com>
Date:   Wed Apr 9 11:02:19 2025 +0300

    merge master

commit 912f4cb7b71f02866244fe447ca0e7fbd2a015bb
Author: Eugene Miroshkin <e.miroshkin@adguard.com>
Date:   Wed Apr 9 10:48:59 2025 +0300

    cleanup code

commit 9da200ebca5b001f4952f33d819d90c1938920ee
Author: Eugene Miroshkin <e.miroshkin@adguard.com>
Date:   Thu Apr 3 17:39:20 2025 +0300

    update tests

commit 794e0bd0a92a41c4d3827b716eeab584a25bd3ed
Author: Eugene Miroshkin <e.miroshkin@adguard.com>
Date:   Thu Mar 13 18:15:58 2025 +0300

    cleanup

commit 9a523b4e255dd24c0f640bc279924ed2c13509a9
Author: Eugene Miroshkin <e.miroshkin@adguard.com>
Date:   Thu Mar 13 18:04:34 2025 +0300

    ADG-9679 add playwright test from python
2025-04-14 18:03:45 +03:00
Eugene Burkov
9726171f0f Pull request 2391: Update changelog
Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 6bae7efe20df984f2abd010add83fcc94a76d848
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Apr 14 14:54:23 2025 +0300

    all: upd chlog
2025-04-14 15:13:22 +03:00
Ainar Garipov
6d282ae716 Pull request 2390: 7588-fix-old-docker
Updates #7588.

Squashed commit of the following:

commit 9add0323ad9fa4ce98a114dd43aa21ec6940ce11
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Apr 10 20:11:39 2025 +0300

    all: fix old docker buildx
2025-04-10 20:26:22 +03:00
Ainar Garipov
6a99c39d11 Pull request 2388: 7588-upd-docker
Updates #7588.

Squashed commit of the following:

commit ce282e4e079e0d18e2acf0fe412c849dfe8ce8d6
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Apr 10 19:31:52 2025 +0300

    scripts: imp build-docker

commit 054fa74d4fe3951129d43e524f713bab610ad86e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Apr 10 17:24:01 2025 +0300

    all: upd base docker
2025-04-10 20:01:14 +03:00
Eugene Burkov
1cc6c00e4b Pull request 2385: Update all
Merge in DNS/adguard-home from upd-all to master

Squashed commit of the following:

commit 05de6cd84465ead8f2c6d94ba22766c37cd51a34
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 8 15:59:00 2025 +0300

    all: fix docs

commit d87020e1674e83043841dc7a95b7e609533ba848
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 8 15:38:08 2025 +0300

    client: upd i18n

commit ad054dedffbbe9ef2782358298afe9ffb6b98ad2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 8 15:24:09 2025 +0300

    all: upd deps

commit 69f13bfb14414fed8d8ef1976628d4558a4207cc
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 8 14:53:30 2025 +0300

    all: upd go, tools
2025-04-08 17:20:47 +03:00
Stanislav Chzhen
1cb6634d67 Pull request 2383: AGDNS-2743-aghuser
Merge in DNS/adguard-home from AGDNS-2743-aghuser to master

Squashed commit of the following:

commit e3920df62be1625a3cfcc314a4aab3d1a378ca53
Merge: 70ce647f4 106785aab
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 7 19:44:15 2025 +0300

    Merge branch 'master' into AGDNS-2743-aghuser

commit 70ce647f47921f2bb34a561d63de2041f31e6bce
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 4 18:17:09 2025 +0300

    aghuser: imp docs

commit 87f6984248189de4a3dc0f2a245775141ea974d0
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Apr 2 19:03:03 2025 +0300

    aghuser: imp code

commit 636ecae85d1fce1657b5699a29451a9079d40222
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 1 17:30:54 2025 +0300

    all: add tests

commit 5c842e94111123cf988332ccd1eb6754fa45585d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 27 21:44:25 2025 +0300

    all: aghuser
2025-04-07 20:41:42 +03:00
Stanislav Chzhen
106785aab0 Pull request 2384: 7734-fix-dhcp-client-filtering
Updates #7734.

Squashed commit of the following:

commit 11e091895bdebc1717bee47827322af56edea3a7
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Apr 7 15:24:07 2025 +0300

    client: fix querylog search

commit 028cf11c480e9831f3925e600d1876ae719d5800
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 4 15:45:19 2025 +0300

    all: upd chlog

commit ee1a70c160d3d7eca5c363708f57916d6df98146
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Apr 4 15:33:34 2025 +0300

    client: fix dhcp client filtering
2025-04-07 19:03:30 +03:00
Ainar Garipov
295b2fefb1 Pull request 2380: ADG-9744-node-20
Squashed commit of the following:

commit 16e43b10edc8218f3d1ccec711d965c654dd851d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Mar 28 12:32:58 2025 +0300

    all: upd chlog

commit 2a8dcab14e9cf43fe941f67f215efe2801f2aa65
Merge: 95a4ea8cc 8b4768aad
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Mar 28 12:32:05 2025 +0300

    Merge branch 'master' into ADG-9744-node-20

commit 95a4ea8ccbcc8cb5911b6c33007e36a04b03e616
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Mar 24 14:06:25 2025 +0300

    bamboo-specs: fix test

commit c535028cd16bf2100b45828fe7f7693b938aaeb5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Mar 24 14:02:47 2025 +0300

    all: imp npm cache

commit bfc9d11153446b865b9756f35753e791d4040595
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Mar 21 19:06:37 2025 +0300

    all: fix specs

commit 7127498d6aa0e0ca4585d79a15ed5f3c5e643e3a
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Mar 21 18:50:57 2025 +0300

    all: upd node; rm outdated openapi lint
2025-03-28 14:54:02 +03:00
Stanislav Chzhen
8b4768aadd Pull request 2371: AGDNS-2714-tls-manager
Merge in DNS/adguard-home from AGDNS-2714-tls-manager to master

Squashed commit of the following:

commit 5c7cd1fa6d8a9bc1fd0f891818589b48bee641dc
Merge: 381f7666b 810ae9483
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 26 14:13:49 2025 +0300

    Merge branch 'master' into AGDNS-2714-tls-manager

commit 381f7666b063d225b114976a280e65df736495fe
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 25 19:53:12 2025 +0300

    home: imp code

commit 20be72abd449fcc76417381edf7d375248a11e9e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 25 19:19:51 2025 +0300

    home: imp code

commit b5a06e6a15b0f8511819267133a551a56e051499
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 24 21:45:41 2025 +0300

    home: imp code

commit a6a5ba727ebbc59d6de4d3762ac196d2cf194875
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 20 21:06:34 2025 +0300

    home: imp docs

commit 71d379bafc3f42377ce72add2cab3a56a796941d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 20 20:47:15 2025 +0300

    all: upd chlog

commit be69a5b85d4cd4295a9b68e1c2c2205179a3e7f2
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 19 20:14:20 2025 +0300

    home: imp docs

commit 85b28db73b59b90365ff23fc5fc90dc1a10cc152
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 19 20:07:59 2025 +0300

    home: imp code

commit c11e4c9e500f7ead96a84575dac08e198569c14d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 19 19:11:59 2025 +0300

    home: imp code

commit 60eff2c66369ca8705a6bb859b5a65d3e6d0df5e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 18 21:27:49 2025 +0300

    home: imp code

commit fa9d57b2834fe3df85630d95b9eb022f1db372b1
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 18 21:14:56 2025 +0300

    home: imp docs

commit 3f561b64750ab57ef83793522a0b313225245e1e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 18 20:59:59 2025 +0300

    home: imp code

commit 927296c49f861d102dad8d24e8b67e6204a6c17a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 18 18:19:22 2025 +0300

    home: imp naming

commit e35f742e42a7304993a924928b51f2452634e258
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 18 17:53:17 2025 +0300

    home: tls manager web api

commit 85a4de7931fea68464fe36c1fb27686eb5b50066
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 18 15:06:34 2025 +0300

    home: tls manager config

commit 515b26d6bd6d837d3db937354f74d895b5793206
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 17 22:15:25 2025 +0300

    home: tls manager
2025-03-26 14:26:57 +03:00
Ainar Garipov
810ae94832 Pull request 2381: 7729-login-label
Closes #7729.

Squashed commit of the following:

commit 110137ce9a958cc4e36d82d4c6fc38c4ec16bde7
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Mar 25 15:50:25 2025 +0300

    client: fix login form labels
2025-03-25 16:51:08 +03:00
Eugene Burkov
fd337967f4 Pull request #2370: AGDNS-2746 Update proxy
Merge in DNS/adguard-home from bind-retry to master

Squashed commit of the following:

commit 42b782368e9eee924b43667ac4f4329fec8ba144
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 21 16:08:25 2025 +0300

    all: upd deps

commit 962d5c381f30c20e6bed6b58922069372ea0428d
Merge: 2e8fdea1b 4adf2bcf0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 21 16:06:46 2025 +0300

    Merge branch 'master' into bind-retry

commit 2e8fdea1ba8244e20f23ca1bf0ef80a347be5a7a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 21 16:06:29 2025 +0300

    all: revert changes

commit 135897b879e29f943d1d2d7d18242579d5b59f22
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 14 15:56:26 2025 +0300

    all: upd proxy
2025-03-21 18:45:06 +03:00
Eugene Burkov
4adf2bcf00 Pull request 2379: Update changelog
Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 2daf656ed9d13b2770015f513d797849fab0565a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 21 14:30:02 2025 +0300

    all: move link

commit 026ffc320d3502b4627e3b943ecf2e308f3447de
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 21 14:24:49 2025 +0300

    all: upd chlog
2025-03-21 14:48:12 +03:00
Ainar Garipov
1eb1b1108c Pull request 2376: 7708-fix-client-addr
Updates #7708.

Squashed commit of the following:

commit 4dd61516c683e3c7eea7beed7e001f37726cae75
Merge: b0763a90b 85e6bf54c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Mar 20 14:03:11 2025 +0300

    Merge branch 'master' into 7708-fix-client-addr

commit b0763a90b70d2f1da6d31e2c5215509d4010220b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Mar 20 13:14:44 2025 +0300

    all: fix client rules
2025-03-20 15:50:04 +03:00
Igor 🐧 Lobanov
85e6bf54c9 Pull request 2375: ADG-9850 query-log-search-form-fix
Updates #7704.

Squashed commit of the following:

commit 16ef53ea6bb687139bc62ffee0a8b87fee676f1c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Mar 20 13:24:48 2025 +0300

    all: upd chlog

commit d0c0935f30683841aaa79ffc149fe5e31938724b
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Mar 19 19:02:13 2025 +0100

    fixed query log search form
2025-03-20 13:57:31 +03:00
Eugene Burkov
cb5de5c653 Pull request 2374: Update changelog
Merge in DNS/adguard-home from upd-chlog to master

Squashed commit of the following:

commit 913df6009cd12af393f66456760898a622bf2b9b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 19 19:36:50 2025 +0300

    all: upd chlog
2025-03-19 19:54:48 +03:00
Igor 🐧 Lobanov
403fa9b1fe Pull request #2373: rc-v0.107.58-hotfix
Merge in DNS/adguard-home from rc-v0.107.58-hotfix to master

Squashed commit of the following:

commit 1837650f36fff704a4e1fa38c0c560f4e14a183d
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Wed Mar 19 13:41:16 2025 +0100

    fixed search field and get parameters sync in logs
2025-03-19 16:42:51 +03:00
Eugene Burkov
2f32b97d2f Pull request 2372: Update all
Merge in DNS/adguard-home from upd-all to master

Squashed commit of the following:

commit 8b29c642c3b4f4d3d4a28cbe3ebc02bb941d0b82
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 18 14:02:23 2025 +0300

    all: fix changelog fmt

commit ef72e9d0fd761796409978cc0d10d38683f5966c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 18 14:01:01 2025 +0300

    client: upd trackers

commit bcf9bfac54004f35ba9b85800b90af4cf9c81e8a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 18 13:59:50 2025 +0300

    client: upd i18n
2025-03-18 14:11:11 +03:00
Stanislav Chzhen
19d2fd47e2 Pull request 2369: AGDNS-2714-tls-manager-logger
Merge in DNS/adguard-home from AGDNS-2714-tls-manager-logger to master

Squashed commit of the following:

commit 4642b731c2609fffd8bbd92e073916d930c3d50f
Merge: 986aebff7 f82dee17f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 17 18:48:23 2025 +0300

    Merge branch 'master' into AGDNS-2714-tls-manager-logger

commit 986aebff793a1d3146a1575abff0eb8878739ff6
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 17 15:42:59 2025 +0300

    home: imp code

commit 3c985959debca14cbaad938a9849ad84e0daa4bc
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 13 19:38:06 2025 +0300

    home: tls manager logger
2025-03-17 19:14:52 +03:00
Stanislav Chzhen
f82dee17f0 Pull request 2365: AGDNS-2714-tls-manager-tests
Merge in DNS/adguard-home from AGDNS-2714-tls-manager-tests to master

Squashed commit of the following:

commit 2a3c6558a4098eb6b531e792884e5ca2bc2dd362
Merge: 85d72559c 1a3853d52
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 17 18:07:49 2025 +0300

    Merge branch 'master' into AGDNS-2714-tls-manager-tests

commit 85d72559c371d4f14b40077d9aec69afa8dc7e73
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 17 17:55:41 2025 +0300

    home: imp tests

commit 9ad19e3cee255b157992e4045f4e27fa5aa54325
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 17 16:21:47 2025 +0300

    home: imp code

commit 8a05bc01998206bf6f3be8b3f0bd8f283158aeab
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 17 15:08:58 2025 +0300

    home: imp tests

commit 85173f986d4c58d8ec8cfbc799317623d9dfdf31
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 13 18:18:56 2025 +0300

    home: add tests

commit add531ea17fd771c071e073757f6021f324c9c75
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 11 19:55:51 2025 +0300

    home: tls manager tests
2025-03-17 18:16:33 +03:00
Stanislav Chzhen
1a3853d52a Pull request 2353: AGDNS-2688-check-host
Merge in DNS/adguard-home from AGDNS-2688-check-host to master

Squashed commit of the following:

commit bd9ed498b0e36fa044e6921fa946062ac40fe616
Merge: 8dffd94a3 c41af2763
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 14 13:42:34 2025 +0300

    Merge branch 'master' into AGDNS-2688-check-host

commit 8dffd94a3bc700cf014cbb16aee9c6339bdc7ffa
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 12 17:12:56 2025 +0300

    filtering: imp code

commit d9a01c8fa60c70e3fd19c40c1a58aec00ae64a6a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 11 20:33:18 2025 +0300

    all: imp code

commit f1aca5f2eb71a1d8bb155a309c618e7a80f8fde5
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Mar 11 16:05:32 2025 +0300

    ADG-9783 update check form

commit a8ebb0401dbaa08fdd04171b1ac66b87d0228c7b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 10 16:41:55 2025 +0300

    dnsforward: imp docs

commit 36f5db9075cc525c13905e0318dfbc4089355523
Merge: 9a746ee9a 66fba942c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 10 16:09:22 2025 +0300

    Merge branch 'master' into AGDNS-2688-check-host

commit 9a746ee9a05895676a60980eb4bd1381fe8d8e4b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 10 16:06:48 2025 +0300

    all: imp docs

commit 0a25e1e8f3536053e30049497bb42a58c6a153d6
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 6 21:48:44 2025 +0300

    all: imp code

commit ec618bc484190dde52a0dc57d144bade8dfc22e2
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 6 17:38:35 2025 +0300

    all: imp code

commit 979c5cfd4c34e2aac46ea11b7fcba8d2929966b8
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 5 21:22:54 2025 +0300

    all: add tests

commit ce0d6117ad7f341edcc018a68acedaa0b718bef1
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 4 15:13:06 2025 +0300

    all: check host
2025-03-14 13:51:45 +03:00
Stanislav Chzhen
c41af2763f Pull request 2366: AG-40702-fix-cache-clear
Merge in DNS/adguard-home from AG-40702-fix-cache-clear to master

Squashed commit of the following:

commit 542a7946a9a92f64a56b1736e7411f83a42b3def
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 12 18:16:35 2025 +0300

    all: fix cache clear
2025-03-12 20:23:54 +03:00
Stanislav Chzhen
ee91a6084f Pull request 2361: imp-test-file-names
Merge in DNS/adguard-home from imp-test-file-names to master

Squashed commit of the following:

commit a0827efdf633fba012c5eb0e0f69eaabf7629724
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 10 21:41:46 2025 +0300

    all: imp tests

commit 21fc274d9276ce0442572261ea39a1c018490870
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 10 19:40:40 2025 +0300

    all: imp test file names
2025-03-11 19:40:14 +03:00
Eugene Burkov
ac8a0ec570 Pull request 2360: Update all
Merge in DNS/adguard-home from upd-all to master

Squashed commit of the following:

commit a9ec07f3810d94f1119d727a317db05a5f0d2d81
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 10 18:26:30 2025 +0300

    client: upd data

commit f1cba36a0048e47f28a9ed481d51529e82e24283
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 10 18:18:46 2025 +0300

    all: upd proxy
2025-03-10 18:38:11 +03:00
Stanislav Chzhen
3255efcaf3 Pull request 2354: AGDNS-2690-global-context-tls
Merge in DNS/adguard-home from AGDNS-2690-global-context-tls to master

Squashed commit of the following:

commit ae1d9e6f3f3b8abefbc5e776eb256577f7fbbb0f
Merge: 6f30f488a bf9be98c7
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 10 18:15:24 2025 +0300

    Merge branch 'master' into AGDNS-2690-global-context-tls

commit 6f30f488aa2305e518000dc6c1028ede83bf1cc6
Merge: baa187ab0 66fba942c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 10 15:08:47 2025 +0300

    Merge branch 'master' into AGDNS-2690-global-context-tls

commit baa187ab0b6db7f41e49dece7b4d0430409e7cae
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 10 15:08:39 2025 +0300

    home: imp docs

commit 96a09389c5049a84bb30ed285cc5e1df9aaa438f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 6 20:15:05 2025 +0300

    home: imp docs

commit 1cd007707af4a7a5160c8fe21b20b84543d59e5a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 6 18:54:07 2025 +0300

    home: imp docs

commit ad3d2b6616c2c3aba566a2158ffc597e5802929f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 4 19:38:45 2025 +0300

    home: global context tls
2025-03-10 18:24:41 +03:00
Eugene Burkov
bf9be98c71 Pull request 2357: 7555-fix-ra-packets
Merge in DNS/adguard-home from 7555-fix-ra-packets to master

* commit '0ed2cd04b22a7b447380ee863bc908603720877d':
  dhcpd: imp docs
  all: fix changelog
  dhcpd: imp docs
  all: imp code & test, log changes
  Change comment message to match implementation
  dhcpd: Fix slaac configuration which was not working due to invalid icmp packet
2025-03-10 16:30:15 +03:00
Eugene Burkov
0ed2cd04b2 Merge branch 'master' into 7555-fix-ra-packets 2025-03-10 16:17:54 +03:00
Ildar Kamalov
66fba942c8 Pull request: ADG-9775 fix disabled button for autofilled login form
Merge in DNS/adguard-home from ADG-9775 to master

Squashed commit of the following:

commit 3544ba8d463d80c69285fba4af1332e80cd2f0c8
Merge: 9631f6c4d 7f9cef948
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Mar 10 13:55:27 2025 +0300

    Merge branch 'master' into ADG-9775

commit 9631f6c4d4e32a5f241ff30b0db9f51fcc7635dc
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Mar 10 12:30:25 2025 +0300

    add changelog

commit 49fa7fe189f69758011cbdf8f78744bef5fcd0a0
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Mar 7 17:02:45 2025 +0300

    ADG-9775 fix disabled button for autofilled login form
2025-03-10 14:25:56 +03:00
Eugene Burkov
7f9cef948c Pull request 2358: Update all
Merge in DNS/adguard-home from upd-all to master

Squashed commit of the following:

commit fb5e87e0cb5617d031a2dac932304917722b1a89
Merge: af4ef937e 64994c7fc
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 7 18:30:39 2025 +0300

    Merge branch 'master' into upd-all

commit af4ef937ee9ae1046cda083a4b0cb6b41ca3dc8c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 7 12:50:48 2025 +0300

    all: log changes, revert trackers

commit ca1197dc69bb845daa5ec3f25d58d995f3d330ef
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 6 19:33:24 2025 +0300

    client: upd i18n

commit d6aa69668633afcabdf956a3e82b920077ddbc75
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 6 19:24:57 2025 +0300

    client: upd vetted filters, companiesdb, blocked services

commit ed6f706c8eafe248b1e851bb0b123f1c46795414
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 6 19:20:32 2025 +0300

    ipset: add bench results

commit 89c1fbe257163aebcddd1abf85b8e4d87536e0ef
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 6 19:14:34 2025 +0300

    all: upd go, tools
2025-03-07 18:51:31 +03:00
Eugene Burkov
3be90f7b75 Merge branch 'master' into 7555-fix-ra-packets 2025-03-07 18:32:14 +03:00
Ildar Kamalov
64994c7fcb Pull request: ADG-9715 formatting of elapsed times less than one millisecond
Merge in DNS/adguard-home from ADG-9715 to master

Squashed commit of the following:

commit d972608ad5429810e0f5ad430f11e037cd05ee40
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Mar 6 12:46:29 2025 +0300

    ADG-9715 formatting of elapsed times less than one millisecond
2025-03-07 16:42:37 +03:00
Eugene Burkov
4b0db6d397 dhcpd: imp docs 2025-03-07 13:01:34 +03:00
Eugene Burkov
777b310a4b all: fix changelog 2025-03-06 16:12:10 +03:00
Eugene Burkov
148a407f6c Merge branch 'master' into 7555-fix-ra-packets 2025-03-06 16:03:25 +03:00
Eugene Burkov
ed947a048e dhcpd: imp docs 2025-03-06 15:54:02 +03:00
Eugene Burkov
74640fb06c all: imp code & test, log changes 2025-03-06 15:15:06 +03:00
Stanislav Chzhen
0d2163c1d6 Pull request 2355: AGDNS-2686-client-manager-clock
Merge in DNS/adguard-home from AGDNS-2686-client-manager-clock to master

Squashed commit of the following:

commit 1d3cafa7f1036a72b766feaee1db00398e51c364
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 4 20:52:39 2025 +0300

    all: client manager clock
2025-03-04 21:18:22 +03:00
Stanislav Chzhen
318bd2901a Pull request 2346: AGDNS-2686-client-upstream-manager
Merge in DNS/adguard-home from AGDNS-2686-client-upstream-manager to master

Squashed commit of the following:

commit 563cb583f01c26434fa04d0e37dcbe2ba15c0912
Merge: f4b0caf5c 61fe269cb
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 3 19:07:35 2025 +0300

    Merge branch 'master' into AGDNS-2686-client-upstream-manager

commit f4b0caf5c8bc48ee8be97f031cd1aa1399eb461c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Feb 27 21:52:51 2025 +0300

    client: imp docs

commit e7d74931b1cc9b62eeadbe1168ae5781d57d6c73
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Feb 26 21:44:04 2025 +0300

    client: imp code

commit 1cba38c1bc3b6b5afb7829c230c4e831f789647e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Feb 26 18:06:17 2025 +0300

    client: fix typo

commit 65b6b1e8c0fde47f367c428a78fefc4c63bc45f9
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Feb 26 17:52:02 2025 +0300

    all: imp code, docs

commit ed158ef09fc26bc9c57c91dbfa04d89fede583d0
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Feb 26 14:34:50 2025 +0300

    client: imp code

commit ab897f64c8751ea158408521116d5b689e6d39a9
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Feb 25 18:26:16 2025 +0300

    all: upd chlog

commit a2c30e3ede6fb61f6d23fd392cc3035dc96f77af
Merge: bdb08ee0e d8ce5b453
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Feb 25 17:40:32 2025 +0300

    Merge branch 'master' into AGDNS-2686-client-upstream-manager

commit bdb08ee0e6122de727f2749a44f5df7e29d0eee2
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Feb 25 17:16:31 2025 +0300

    all: imp tests

commit 00f0eb60474a2297567acf5a3a27e8b5c2d99229
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Feb 20 21:37:58 2025 +0300

    all: imp code, docs

commit 13934176636dd70a17e53bc1956d6cf51602760a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Feb 19 15:58:11 2025 +0300

    all: client upstream manager
2025-03-03 19:19:46 +03:00
Stanislav Chzhen
61fe269cb8 Pull request 2352: AGDNS-2690-signal-handler
Merge in DNS/adguard-home from AGDNS-2690-signal-handler to master

Squashed commit of the following:

commit b6822142312ac814e7c206218bb079a4f364192a
Merge: f597ea1fd 8b2ab8ea8
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 3 18:10:27 2025 +0300

    Merge branch 'master' into AGDNS-2690-signal-handler

commit f597ea1fd27c1153f2b08a339fd17361807627e6
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Feb 27 20:45:55 2025 +0300

    all: fix logger

commit 582e315ecac45e49e156bddb5755f5fb7114111c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Feb 27 18:40:57 2025 +0300

    home: imp code

commit 1e0f48d32a654b71a9c0a9e3cf3afceab00bc7e5
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Feb 27 15:26:19 2025 +0300

    home: imp docs

commit aa43bbc1b20392429d606de5cfd1b1fed940755a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Feb 24 17:24:33 2025 +0300

    home: signal handler
2025-03-03 18:23:01 +03:00
Ildar Kamalov
8b2ab8ea87 Pull request 2322: ADG-9415
Merge in DNS/adguard-home from ADG-9415 to master

Squashed commit of the following:

commit 76bf99499a
Merge: 29529970a 0389515ee
Author: Ildar Kamalov <ik@adguard.com>
Date:   Wed Feb 26 18:31:41 2025 +0300

    Merge branch 'master' into ADG-9415

commit 29529970a3
Merge: b49790daf 782a1a982
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Feb 24 15:44:38 2025 +0300

    Merge branch 'master' into ADG-9415

commit b49790daf8
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Feb 24 15:30:18 2025 +0300

    fix default lease duration value

commit cb307472ec
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Feb 24 10:35:26 2025 +0300

    fix default response status

commit 115e743e1a
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Feb 24 10:32:46 2025 +0300

    fix upstream description

commit 26b0eddaca
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Feb 18 17:40:41 2025 +0300

    use const for test config file

commit 58faa7c537
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Feb 18 17:31:04 2025 +0300

    fix install config

commit 0a3346d911
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Feb 17 15:25:23 2025 +0300

    fix install check config

commit 17c4c26ea8
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Feb 14 17:18:20 2025 +0300

    fix query log

commit 14a2685ae3
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Feb 14 15:52:36 2025 +0300

    fix dhcp initial values

commit e7a8db7afd
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Feb 14 14:37:24 2025 +0300

    fix encryption form values

commit 1c8917f7ac
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Feb 14 14:07:29 2025 +0300

    fix blocked services submit

commit 4dfa536cea
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Feb 14 13:50:47 2025 +0300

    dns config ip validation

commit 4fee83fe13
Author: Ildar Kamalov <ik@adguard.com>
Date:   Wed Feb 12 17:49:54 2025 +0300

    add playwright warning

commit 8c2f36e7a6
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Feb 11 18:36:18 2025 +0300

    fix config file name

commit 83db5f33dc
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Feb 11 16:16:43 2025 +0300

    temp config file

commit 9080c1620f
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Feb 11 15:01:46 2025 +0300

    update readme

commit ee1520307f
Merge: fd12e33c0 2fe2d254b
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Feb 11 14:44:06 2025 +0300

    Merge branch 'master' into ADG-9415

commit fd12e33c06
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Mon Feb 10 10:29:43 2025 +0100

    added typecheck on build, fixed eslint

commit b3849eebc4
Merge: 225167a8b 9bf3ee128
Author: Igor Lobanov <bniwredyc@gmail.com>
Date:   Mon Feb 10 09:43:32 2025 +0100

    Merge branch 'ADG-9415' of https://bit.int.agrd.dev/scm/dns/adguard-home into ADG-9415

... and 94 more commits
2025-02-26 19:37:52 +03:00
Gunjan Gupta
0c72cde4c3 Change comment message to match implementation
Signed-off-by: Gunjan Gupta <viraniac@gmail.com>
2025-01-04 01:50:00 +05:30
Gunjan Gupta
b1657c2b2a dhcpd: Fix slaac configuration which was not working due to invalid icmp packet
Signed-off-by: Gunjan Gupta <viraniac@gmail.com>
2025-01-04 01:12:28 +05:30
185 changed files with 6602 additions and 4590 deletions

View File

@@ -1,8 +1,8 @@
'name': 'build'
'env':
'GO_VERSION': '1.23.6'
'NODE_VERSION': '18'
'GO_VERSION': '1.24.2'
'NODE_VERSION': '20'
'on':
'push':

View File

@@ -1,7 +1,7 @@
'name': 'lint'
'env':
'GO_VERSION': '1.23.6'
'GO_VERSION': '1.24.2'
'on':
'push':

View File

@@ -9,25 +9,131 @@ The format is based on [*Keep a Changelog*](https://keepachangelog.com/en/1.0.0/
<!--
## [v0.108.0] TBA
## [v0.107.58] - 2025-03-11 (APPROX.)
## [v0.107.62] - 2025-04-30 (APPROX.)
See also the [v0.107.58 GitHub milestone][ms-v0.107.58].
See also the [v0.107.62 GitHub milestone][ms-v0.107.62].
[ms-v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/milestone/93?closed=1
[ms-v0.107.62]: https://github.com/AdguardTeam/AdGuardHome/milestone/97?closed=1
NOTE: Add new changes BELOW THIS COMMENT.
-->
### Fixed
- The formatting of large numbers in the clients tables on the *Client settings* page ([#7583]).
- Command line option `--update` when the `dns.serve_plain_dns` configuration property was disabled ([7801]).
[#7583]: https://github.com/AdguardTeam/AdGuardHome/issues/7583
- DNS cache not working for custom upstream configurations.
- Validation process for the DNS-over-TLS, DNS-over-QUIC, and HTTPS ports on the *Encryption Settings* page.
[#7801]: https://github.com/AdguardTeam/AdGuardHome/issues/7801
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
## [v0.107.61] - 2025-04-22
See also the [v0.107.61 GitHub milestone][ms-v0.107.61].
### Security
- Any simultaneous requests that are considered duplicates will now only result in a single request to upstreams, reducing the chance of a cache poisoning attack succeeding. This is controlled by the new configuration object `pending_requests`, which has a single `enabled` property, set to `true` by default.
**NOTE:** We thank [Xiang Li][mr-xiang-li] for reporting this security issue. It's strongly recommended to leave it enabled, otherwise AdGuard Home will be vulnerable to untrusted clients.
### Fixed
- Searching for persistent clients using an exact match for CIDR in the `POST /clients/search HTTP API`.
[mr-xiang-li]: https://lixiang521.com/
[ms-v0.107.61]: https://github.com/AdguardTeam/AdGuardHome/milestone/96?closed=1
## [v0.107.60] - 2025-04-14
See also the [v0.107.60 GitHub milestone][ms-v0.107.60].
### Security
- Go version has been updated to prevent the possibility of exploiting the Go vulnerabilities fixed in [1.24.2][go-1.24.2].
### Changed
- Alpine Linux version in `Dockerfile` has been updated to 3.21 ([#7588]).
### Deprecated
- Node 20 support, Node 22 will be required in future releases.
**NOTE:** `npm` may be replaced with a different tool, such as `pnpm` or `yarn`, in a future release.
### Fixed
- Filtering for DHCP clients ([#7734]).
- Incorrect label on login page ([#7729]).
- Validation process for the HTTPS port on the *Encryption Settings* page.
### Removed
- Node 18 support.
[#7588]: https://github.com/AdguardTeam/AdGuardHome/issues/7588
[#7729]: https://github.com/AdguardTeam/AdGuardHome/issues/7729
[#7734]: https://github.com/AdguardTeam/AdGuardHome/issues/7734
[go-1.24.2]: https://groups.google.com/g/golang-announce/c/Y2uBTVKjBQk
[ms-v0.107.60]: https://github.com/AdguardTeam/AdGuardHome/milestone/95?closed=1
## [v0.107.59] - 2025-03-21
See also the [v0.107.59 GitHub milestone][ms-v0.107.59].
- Rules with the `client` modifier not working ([#7708]).
- The search form not working in the query log ([#7704]).
[#7704]: https://github.com/AdguardTeam/AdGuardHome/issues/7704
[#7708]: https://github.com/AdguardTeam/AdGuardHome/issues/7708
[ms-v0.107.59]: https://github.com/AdguardTeam/AdGuardHome/milestone/94?closed=1
## [v0.107.58] - 2025-03-19
See also the [v0.107.58 GitHub milestone][ms-v0.107.58].
### Security
- Go version has been updated to prevent the possibility of exploiting the Go vulnerabilities fixed in [1.24.1][go-1.24.1].
### Added
- The ability to check filtering rules for host names using an optional query type and optional ClientID or client IP address ([#4036]).
- Optional `client` and `qtype` URL query parameters to the `GET /control/check_host` HTTP API.
### Fixed
- Clearing the DNS cache on the *DNS settings* page now includes both global cache and custom client cache.
- Invalid ICMPv6 Router Advertisement messages ([#7547]).
- Disabled button for autofilled login form.
- Formatting of elapsed times less than one millisecond.
- Changes to global upstream DNS settings not applying to custom client upstream configurations.
- The formatting of large numbers in the clients tables on the *Client settings* page ([#7583]).
[#4036]: https://github.com/AdguardTeam/AdGuardHome/issues/4036
[#7547]: https://github.com/AdguardTeam/AdGuardHome/issues/7547
[#7583]: https://github.com/AdguardTeam/AdGuardHome/issues/7583
[go-1.24.1]: https://groups.google.com/g/golang-announce/c/4t3lzH3I0eI
[ms-v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/milestone/93?closed=1
## [v0.107.57] - 2025-02-20
See also the [v0.107.57 GitHub milestone][ms-v0.107.57].
@@ -47,6 +153,7 @@ See also the [v0.107.57 GitHub milestone][ms-v0.107.57].
### Fixed
- The hostnames of DHCP clients not being shown in the *Top clients* table on the dashboard ([#7627]).
- The formatting of large numbers in the upstream table and query log ([#7590]).
[#7590]: https://github.com/AdguardTeam/AdGuardHome/issues/7590
@@ -3023,11 +3130,15 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
[ms-v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/milestone/28?closed=1
<!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.58...HEAD
[v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.57...v0.107.58
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.62...HEAD
[v0.107.62]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.61...v0.107.62
-->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.57...HEAD
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.61...HEAD
[v0.107.61]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.60...v0.107.61
[v0.107.60]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.59...v0.107.60
[v0.107.59]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.58...v0.107.59
[v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.57...v0.107.58
[v0.107.57]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.56...v0.107.57
[v0.107.56]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.55...v0.107.56
[v0.107.55]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.54...v0.107.55

View File

@@ -27,7 +27,7 @@ DIST_DIR = dist
GOAMD64 = v1
GOPROXY = https://proxy.golang.org|direct
GOTELEMETRY = off
GOTOOLCHAIN = go1.23.6
GOTOOLCHAIN = go1.24.2
GPG_KEY = devteam@adguard.com
GPG_KEY_PASSPHRASE = not-a-real-password
NPM = npm
@@ -38,7 +38,6 @@ REVISION = $${REVISION:-$$(git rev-parse --short HEAD)}
SIGN = 1
SIGNER_API_KEY = not-a-real-key
VERSION = v0.0.0
YARN = yarn
NEXTAPI = 0
@@ -139,5 +138,4 @@ txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh
md-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/md-lint.sh
sh-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/sh-lint.sh
openapi-lint: ; cd ./openapi/ && $(YARN) test
openapi-show: ; cd ./openapi/ && $(YARN) start
# TODO(a.garipov): Re-add openapi-lint.

View File

@@ -205,9 +205,9 @@ Run `make init` to prepare the development environment.
You will need this to build AdGuard Home:
- [Go](https://golang.org/dl/) v1.23 or later;
- [Node.js](https://nodejs.org/en/download/) v18.18 or later;
- [npm](https://www.npmjs.com/) v8 or later;
- [Go](https://golang.org/dl/) v1.24 or later;
- [Node.js](https://nodejs.org/en/download/) v20.19 or later;
- [npm](https://www.npmjs.com/) v10.8 or later;
### <a href="#building" id="building" name="building">Building</a>

View File

@@ -7,8 +7,8 @@
# Make sure to sync any changes with the branch overrides below.
'variables':
'channel': 'edge'
'dockerFrontend': 'adguard/home-js-builder:2.1-bullseye'
'dockerGo': 'adguard/go-builder:1.23.6--1'
'dockerFrontend': 'adguard/home-js-builder:3.1'
'dockerGo': 'adguard/go-builder:1.24.2--1'
'stages':
- 'Build frontend':
@@ -50,7 +50,7 @@
'docker':
'image': '${bamboo.dockerFrontend}'
'volumes':
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
'${system.NPM_DIR}': '${bamboo.cacheNpm}'
'key': 'BF'
'other':
'clean-working-dir': true
@@ -157,6 +157,7 @@
# Print Docker info.
docker info
docker buildx version
# Prepare and push the build.
env \
@@ -277,8 +278,8 @@
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerFrontend': 'adguard/home-js-builder:2.1-bullseye'
'dockerGo': 'adguard/go-builder:1.23.6--1'
'dockerFrontend': 'adguard/home-js-builder:3.1'
'dockerGo': 'adguard/go-builder:1.24.2--1'
# release-vX.Y.Z branches are the branches from which the actual final
# release is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@@ -293,5 +294,5 @@
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerFrontend': 'adguard/home-js-builder:2.1-bullseye'
'dockerGo': 'adguard/go-builder:1.23.6--1'
'dockerFrontend': 'adguard/home-js-builder:3.1'
'dockerGo': 'adguard/go-builder:1.24.2--1'

View File

@@ -5,8 +5,8 @@
'key': 'AHBRTSPECS'
'name': 'AdGuard Home - Build and run tests'
'variables':
'dockerFrontend': 'adguard/home-js-builder:2.1-bullseye'
'dockerGo': 'adguard/go-builder:1.23.6--1'
'dockerFrontend': 'adguard/home-js-builder:3.1'
'dockerGo': 'adguard/go-builder:1.24.2--1'
'channel': 'development'
'stages':
@@ -39,7 +39,7 @@
'docker':
'image': '${bamboo.dockerFrontend}'
'volumes':
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
'${system.NPM_DIR}': '${bamboo.cacheNpm}'
'key': 'JSTEST'
'other':
'clean-working-dir': true
@@ -103,7 +103,7 @@
'docker':
'image': '${bamboo.dockerFrontend}'
'volumes':
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
'${system.NPM_DIR}': '${bamboo.cacheNpm}'
'key': 'BF'
'other':
'clean-working-dir': true
@@ -178,7 +178,7 @@
'docker':
'image': '${bamboo.dockerFrontend}'
'volumes':
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
'${system.NPM_DIR}': '${bamboo.cacheNpm}'
'key': 'E2ETEST'
'other':
'clean-working-dir': true
@@ -233,6 +233,6 @@
# Set the default release channel on the release branch to beta, as we
# may need to build a few of these.
'variables':
'dockerFrontend': 'adguard/home-js-builder:2.1-bullseye'
'dockerGo': 'adguard/go-builder:1.23.6--1'
'dockerFrontend': 'adguard/home-js-builder:3.1'
'dockerGo': 'adguard/go-builder:1.24.2--1'
'channel': 'candidate'

1207
client/package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

4
client/package.json vendored
View File

@@ -66,7 +66,7 @@
"@babel/preset-react": "^7.24.1",
"@playwright/test": "1.50.1",
"@types/lodash": "^4.17.4",
"@types/node": "^22.10.2",
"@types/node": "^22.13.10",
"@types/react": "^17.0.80",
"@types/react-dom": "^18.3.0",
"@types/react-redux": "^7.1.33",
@@ -99,7 +99,7 @@
"stylelint": "^16.5.0",
"ts-loader": "^9.5.1",
"url-loader": "^4.1.1",
"vitest": "^3.0.4",
"vitest": "^3.1.1",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4",

View File

@@ -1,24 +1,24 @@
{
"client_settings": "Налады кліентаў",
"example_upstream_reserved": "upstream <0>для канкрэтных даменаў</0>;",
"example_multiple_upstreams_reserved": "некалькі DNS-сервераў <0>для канкрэтных даменаў</0>;",
"example_multiple_upstreams_reserved": "некалькі сервер DNSаў <0>для канкрэтных даменаў</0>;",
"example_upstream_comment": "каментар.",
"upstream_parallel": "Ужыць адначасныя запыты да ўсіх сервераў для паскарэння апрацоўкі запыту",
"parallel_requests": "Паралельныя запыты",
"load_balancing": "Размеркаванне нагрузкі",
"load_balancing_desc": "Запытвайце па адным серверы за раз. AdGuard Home будзе выкарыстоўваць выпадковы алгарытм для выбару сервера, так што самы хуткі сервер будзе выкарыстоўвацца часцей.",
"bootstrap_dns": "Bootstrap DNS-серверы",
"bootstrap_dns_desc": "IP-адрасы DNS-сервераў, якія выкарыстоўваюцца для вырашэння IP-адрасоў распознавальнікаў DoH/DoT, якія вы ўказваеце ў якасці перадачы. Каментары не дапускаюцца.",
"fallback_dns_title": "Рэзервовыя DNS-серверы",
"fallback_dns_desc": "Спіс рэзервовых DNS-сервераў, якія выкарыстоўваюцца, калі вышэйшыя DNS-серверы не адказваюць. Сінтаксіс такі ж, як і ў галоўным полі ўверх.",
"bootstrap_dns": "Bootstrap сервер DNSы",
"bootstrap_dns_desc": "IP-адрасы сервер DNSаў, якія выкарыстоўваюцца для вырашэння IP-адрасоў распознавальнікаў DoH/DoT, якія вы ўказваеце ў якасці перадачы. Каментары не дапускаюцца.",
"fallback_dns_title": "Рэзервовыя сервер DNSы",
"fallback_dns_desc": "Спіс рэзервовых сервер DNSаў, якія выкарыстоўваюцца, калі вышэйшыя сервер DNSы не адказваюць. Сінтаксіс такі ж, як і ў галоўным полі ўверх.",
"fallback_dns_placeholder": "Увядзіце па адным рэзервовым серверы DNS у радку",
"local_ptr_title": "Прыватныя DNS-серверы",
"local_ptr_title": "Прыватныя сервер DNSы",
"local_ptr_desc": "DNS-серверы, якія AdGuard Home выкарыстоўвае для лакальных PTR-запытаў. Гэтыя серверы выкарыстоўваюцца, каб атрымаць даменавыя імёны кліентаў з прыватнымі IP-адрасамі, напрыклад «192.168.12.34», з дапамогай rDNS. Калі спіс пусты, AdGuard Home выкарыстоўвае прадвызначаныя DNS-серверы вашай АС.",
"local_ptr_default_resolver": "Па змаўчанні AdGuard Home выкарыстоўвае наступныя зваротныя DNS-рэзолверы: {{ip}}.",
"local_ptr_no_default_resolver": "AdGuard Home не змог вызначыць прыдатныя прыватныя адваротныя DNS-рэзолверы для гэтай сістэмы.",
"local_ptr_placeholder": "Увядзіце па адным адрасе на радок",
"resolve_clients_title": "Уключыць запытванне даменавых імёнаў для кліентаў",
"resolve_clients_desc": "AdGuard Home будзе спрабаваць аўтаматычна вызначыць даменавыя імёны кліентаў праз PTR-запыты да адпаведных сервераў (прыватны DNS-сервер для лакальных кліентаў, upstream-серверы для кліентаў з публічным IP-адрасам).",
"resolve_clients_desc": "AdGuard Home будзе спрабаваць аўтаматычна вызначыць даменавыя імёны кліентаў праз PTR-запыты да адпаведных сервераў (прыватны сервер DNS для лакальных кліентаў, upstream-серверы для кліентаў з публічным IP-адрасам).",
"use_private_ptr_resolvers_title": "Ужываць прыватныя адваротныя DNS-рэзолверы",
"use_private_ptr_resolvers_desc": "Пасылаць адваротныя DNS-запыты для лакальна абслугоўных адрасоў на паказаныя серверы. Калі адключана, AdGuard Home будзе адказваць NXDOMAIN на ўсе падобныя PTR-запыты, апроч запытаў пра кліентаў, ужо вядомых па DHCP, /etc/hosts і гэтак далей.",
"check_dhcp_servers": "Праверыць DHCP-серверы",
@@ -101,13 +101,13 @@
"compact": "Компактный",
"nothing_found": "Нічога не знойдзена",
"faq": "FAQ",
"version": "версія",
"version": "Версія",
"address": "Адрас",
"protocol": "Пратакол",
"on": "УКЛ",
"off": "Выкл",
"copyright": "Усе правы захаваныя",
"homepage": "Галоўная",
"homepage": "Хатняя старонка",
"report_an_issue": "Паведаміць пра праблему",
"privacy_policy": "Палітыка прыватнасці",
"enable_protection": "Уключыць абарону",
@@ -165,8 +165,8 @@
"custom_filtering_rules": "Карыстальніцкія правілы фільтрацыі",
"encryption_settings": "Налады шыфравання",
"dhcp_settings": "Налады DHCP",
"upstream_dns": "Upstream DNS-серверы",
"upstream_dns_help": "Увядзіце адрасы сервераў па адным у радку. <a>Даведацца больш </a> пра наладжванне DNS-сервераў.",
"upstream_dns": "Upstream сервер DNSы",
"upstream_dns_help": "Увядзіце адрасы сервераў па адным у радку. <a>Даведацца больш </a> пра наладжванне сервер DNSаў.",
"upstream_dns_configured_in_file": "Наладжаны ў {{path}}",
"test_upstream_btn": "Тэст upstream сервераў",
"upstreams": "Upstreams",
@@ -182,7 +182,7 @@
"enabled_save_search_toast": "Уключаны бяспечны пошук",
"updated_save_search_toast": "Налады бяспечнага пошуку абноўлены",
"enabled_table_header": "УКЛ.",
"name_table_header": "Імя",
"name_table_header": "Назва",
"list_url_table_header": "URL-адрас спіса",
"rules_count_table_header": "Колькасць правілаў:",
"last_time_updated_table_header": "Апошняе абнаўленне",
@@ -196,7 +196,7 @@
"no_whitelist_added": "Белыя спісы не дададзены",
"add_blocklist": "Дадаць чорны спіс",
"add_allowlist": "Дадаць белы спіс",
"cancel_btn": "Адмена",
"cancel_btn": "Скасаваць",
"enter_name_hint": "Увядзіце імя",
"enter_url_or_path_hint": "Увядзіце URL-адрас ці абсалютны шлях да спіса",
"check_updates_btn": "Праверыць абнаўленні",
@@ -219,7 +219,7 @@
"example_meaning_host_block": "адказаць 127.0.0.1 для example.org (але не для яго паддаменаў);",
"example_comment": "! Так можна дадаваць апісанне.",
"example_comment_meaning": "каментар;",
"example_comment_hash": "# І вось так таксама.",
"example_comment_hash": "# Таксама каментарый.",
"example_regex_meaning": "блакаваць доступ да даменаў, якія адпавядаюць зададзенаму рэгулярнаму выразу.",
"example_upstream_regular": "звычайны DNS (наўзверх UDP);",
"example_upstream_regular_port": "звычайны DNS (праз UDP, імя хаста);",
@@ -233,13 +233,13 @@
"example_upstream_tcp_port": "звычайны DNS (праз TCP, імя хаста);",
"example_upstream_tcp_hostname": "звычайны DNS (праз TCP, імя хаста);",
"all_lists_up_to_date_toast": "Усе спісы ўжо абноўлены",
"updated_upstream_dns_toast": "Upstream DNS-серверы абноўлены",
"updated_upstream_dns_toast": "Upstream сервер DNSы абноўлены",
"dns_test_ok_toast": "Паказаныя серверы DNS працуюць карэктна",
"dns_test_not_ok_toast": "Сервер «{{key}}»: немагчыма выкарыстоўваць, праверце слушнасць напісання",
"dns_test_parsing_error_toast": "Раздзел {{section}}: радок {{line}}: немагчыма выкарыстоўваць, праверце слушнасць напісання",
"dns_test_warning_toast": "Upstream «{{key}}» не адказвае на тэставыя запыты і можа не працаваць належным чынам",
"unblock": "Адблакаваць",
"block": "Заблакаваць",
"block": "Заблакіраваць",
"disallow_this_client": "Забараніць доступ гэтаму кліенту",
"allow_this_client": "Дазволіць доступ гэтаму кліенту",
"block_for_this_client_only": "Заблакаваць толькі для гэтага кліента",
@@ -259,7 +259,7 @@
"no_logs_found": "Логі не знойдзены",
"refresh_btn": "Абнавіць",
"previous_btn": "Назад",
"next_btn": "Наперад",
"next_btn": "Далей",
"loading_table_status": "Загрузка...",
"page_table_footer_text": "Старонка",
"rows_table_footer_text": "радкоў",
@@ -280,7 +280,7 @@
"query_log_retention_confirm": "Вы ўпэўнены, што хочаце змяніць тэрмін захоўвання запытаў? Пры памяншэнні інтэрвалу, некаторыя даныя могуць быць страчаны",
"anonymize_client_ip": "Ананімізацыя IP-адрасы кліента",
"anonymize_client_ip_desc": "Не захоўвайце поўныя IP-адрасы гэтых удзельнікаў у часопісах або статыстыцы",
"dns_config": "Налады DNS-сервера",
"dns_config": "Налады сервер DNSа",
"dns_cache_config": "Налада кэша DNS",
"dns_cache_config_desc": "Тут можна наладзіць кэш DNS",
"blocking_mode": "Рэжым блакавання",
@@ -342,14 +342,14 @@
"unknown_filter": "Невядомы фільтр {{filterId}}",
"known_tracker": "Вядомы трэкер",
"install_welcome_title": "Сардэчна запрашаем у AdGuard Home!",
"install_welcome_desc": "AdGuard Home гэта DNS-сервер, што блакуе рэкламу і трэкінг. Яго мэта даць вам магчымасць кантраляваць усю ваша сеціва і ўсе падлучаныя прылады. Ён не патрабуе ўсталёўкі кліенцкіх праграм.",
"install_welcome_desc": "AdGuard Home гэта сервер DNS, што блакуе рэкламу і трэкінг. Яго мэта даць вам магчымасць кантраляваць усю ваша сеціва і ўсе падлучаныя прылады. Ён не патрабуе ўсталёўкі кліенцкіх праграм.",
"install_settings_title": "Ўэб-інтэрфейс адміністравання",
"install_settings_listen": "Інтэрфейс сеціва",
"install_settings_port": "Порт",
"install_settings_interface_link": "Ваш ўэб-інтэрфейс адміністравання AdGuard Home будзе даступны па наступных адрасах:",
"form_error_port": "Увядзіце карэктны нумар порта",
"install_settings_dns": "DNS-сервер",
"install_settings_dns_desc": "Вам будзе трэба наладзіць свае прылады ці роўтар на выкарыстанне DNS-сервера на адным з наступных адрасоў:",
"install_settings_dns_desc": "Вам будзе трэба наладзіць свае прылады ці роўтар на выкарыстанне сервер DNSа на адным з наступных адрасоў:",
"install_settings_all_interfaces": "Усе інтэрфейсы",
"install_auth_title": "Аўтарызацыя",
"install_auth_desc": "Настойліва рэкамендуецца наладзіць аўтэнтыфікацыю паролем для ўэб-інтэрфейсу AdGuard Home. Нават калі ён даступны толькі ў вашай лакальнай сетцы, важна абараніць яго ад неабмежаванага доступу.",
@@ -365,17 +365,17 @@
"install_submit_desc": "Працэдура налады завершана і вы гатовы пачаць выкарыстанне AdGuard Home.",
"install_devices_router": "Роўтар",
"install_devices_router_desc": "Такая наладка аўтаматычна пакрые ўсе прылады, што выкарыстоўваюць ваш хатні роўтар, і вам не трэба будзе наладжваць кожнае з іх у асобнасці.",
"install_devices_address": "DNS-сервер AdGuard Home даступны па наступных адрасах",
"install_devices_address": "сервер DNS AdGuard Home даступны па наступных адрасах",
"install_devices_router_list_1": "Адкрыйце налады вашага роўтара. Звычайна вы можаце адкрыць іх у вашым браўзары, напрыклад, http://192.168.0.1/ ці http://192.168.1.1/. Вас могуць папрасіць увесці пароль. Калі вы не помніце яго, пароль часта можна скінуць, націснуўшы на кнопку на самым роўтары. Некаторыя роўтары патрабуюць адмысловага дадатку, які ў гэтым выпадку павінен быць ужо ўсталявана на ваш кампутар ці тэлефон.",
"install_devices_router_list_2": "Знайдзіце налады DHCP ці DNS. Знайдзіце літары «DNS» поруч з тэкставым полем, у якое можна ўвесці два ці тры шэрагі лічбаў, падзеленых на 4 групы ад адной до трох лічбаў.",
"install_devices_router_list_3": "Увядзіце туды адрас вашага AdGuard Home.",
"install_devices_router_list_4": "Вы не можаце ўсталяваць уласны DNS-сервер на некаторых тыпах маршрутызатараў. У гэтым выпадку можа дапамагчы налада AdGuard Home у якасці <a href='#dhcp'>DHCP-сервера</a>. У адваротным выпадку вам трэба звярнуцца да кіраўніцтва па наладзе DNS-сервераў для вашай пэўнай мадэлі маршрутызатара.",
"install_devices_router_list_4": "Вы не можаце ўсталяваць уласны сервер DNS на некаторых тыпах маршрутызатараў. У гэтым выпадку можа дапамагчы налада AdGuard Home у якасці <a href='#dhcp'>DHCP-сервера</a>. У адваротным выпадку вам трэба звярнуцца да кіраўніцтва па наладзе сервер DNSаў для вашай пэўнай мадэлі маршрутызатара.",
"install_devices_windows_list_1": "Адкрыйце Панэль кіравання праз меню «Пуск» ці праз пошук Windows.",
"install_devices_windows_list_2": "Перайдзіце ў «Сеціва і інтэрнэт», а потым у «Цэнтр кіравання сеціва і агульным доступам».",
"install_devices_windows_list_3": "У левым боку экрана клікніце «Змена параметраў адаптара».",
"install_devices_windows_list_4": "Пстрыкніце правай кнопкай мышы ваша актыўнае злучэнне і абярыце Уласцівасці.",
"install_devices_windows_list_5": "Знайдзіце ў спісе пункт «IP версіі 4 (TCP/IPv4)», вылучыце яго і потым ізноў націсніце «Уласцівасці».",
"install_devices_windows_list_6": "Абярыце «Выкарыстаць наступныя адрасы DNS-сервераў» і ўвядзіце адрас AdGuard Home.",
"install_devices_windows_list_6": "Абярыце «Выкарыстаць наступныя адрасы сервер DNSаў» і ўвядзіце адрас AdGuard Home.",
"install_devices_macos_list_1": "Клікніце па абразку Apple і перайдзіце ў Сістэмныя налады.",
"install_devices_macos_list_2": "Клікніце па іконцы Сеціва.",
"install_devices_macos_list_3": "Абярыце першае падлучэнне ў спісе і націсніце кнопку «Дадаткова».",
@@ -415,7 +415,7 @@
"encryption_key": "Прыватны ключ",
"encryption_key_input": "Скапіюйце сюды прыватны ключ у PEM-кадоўцы.",
"encryption_enable": "Уключыць шыфраванне (HTTPS, DNS-over-HTTPS і DNS-over-TLS)",
"encryption_enable_desc": "Калі шыфраванне ўлучана, ўэб-інтэрфейс AdGuard Home будзе працаваць па HTTPS, а DNS-сервер будзе таксама працаваць па DNS-over-HTTPS і DNS-over-TLS.",
"encryption_enable_desc": "Калі шыфраванне ўлучана, ўэб-інтэрфейс AdGuard Home будзе працаваць па HTTPS, а сервер DNS будзе таксама працаваць па DNS-over-HTTPS і DNS-over-TLS.",
"encryption_chain_valid": "Ланцужок сертыфікатаў валідны",
"encryption_chain_invalid": "Ланцужок сертыфікатаў не валідны",
"encryption_key_valid": "Валідны {{type}} прыватны ключ",
@@ -435,8 +435,8 @@
"update_announcement": "AdGuard Home {{version}} ужо даступная! <0>Націсніце сюды</0>, каб даведацца больш.",
"setup_guide": "Інструкцыя па наладзе",
"dns_addresses": "Адрасы DNS",
"dns_start": "DNS-сервер запускаецца",
"dns_status_error": "Памылка праверкі стану DNS-сервера",
"dns_start": "сервер DNS запускаецца",
"dns_status_error": "Памылка праверкі стану сервер DNSа",
"down": "Уніз",
"fix": "Выправіць",
"dns_providers": "<0>Спіс вядомых DNS-правайдараў</0> на выбар.",
@@ -449,7 +449,7 @@
"settings_global": "Глабальныя",
"settings_custom": "Свае",
"table_client": "Кліент",
"table_name": "Імя",
"table_name": "Назва",
"save_btn": "Захаваць",
"client_add": "Дадаць кліента",
"client_new": "Новы кліент",
@@ -475,7 +475,7 @@
"auto_clients_title": "Кліенты (runtime)",
"auto_clients_desc": "Інфармацыя аб IP-адрасах прылад, якія выкарыстоўваюць або могуць выкарыстоўваць AdGuard Home. Гэтая інфармацыя збіраецца з некалькіх крыніц, уключаючы файлы хостаў, зваротны DNS і г.д.",
"access_title": "Налады доступу",
"access_desc": "Тут вы можаце наладзіць правілы доступу да DNS-серверу AdGuard Home",
"access_desc": "Тут вы можаце наладзіць правілы доступу да сервер DNSу AdGuard Home",
"access_allowed_title": "Дазволеныя кліенты",
"access_allowed_desc": "Спіс CIDR, IP-адрасоў або <a>ClientID</a>. Калі ў гэтым спісе ёсць запісы, AdGuard Home будзе прымаць запыты толькі ад гэтых кліентаў.",
"access_disallowed_title": "Забароненыя кліенты",
@@ -596,7 +596,7 @@
"disable_ipv6_desc": "Ігнараваць усе запыты DNS для адрасоў IPv6 (тып AAAA) і выдаленне дадзеных IPv6 з адказаў тыпу HTTPS.",
"fastest_addr": "Найхуткі IP-адрас",
"fastest_addr_desc": "Апытайце ўсе DNS-серверы і вярніце самы хуткі IP-адрас сярод усіх адказаў. Гэта замарудзіць выкананне DNS-запытаў, бо нам давядзецца чакаць адказаў ад усіх DNS-сервераў, але палепшыць агульную ўзаемасувязь.",
"autofix_warning_text": "Пры націску «Выправіць» AdGuard Home наладзіць вашу сістэму на выкарыстанне DNS-сервера AdGuard Home.",
"autofix_warning_text": "Пры націску «Выправіць» AdGuard Home наладзіць вашу сістэму на выкарыстанне сервер DNSа AdGuard Home.",
"autofix_warning_list": "Будуць выконвацца наступныя заданні: <0>Дэактываваць сістэмны DNSStubListener</0> <0>Усталяваць адрас сервера DNS на 127.0.0.1</0> <0>Стварыць сімвалічную спасылку /etc/resolv.conf на /run/systemd/resolve/resolv.conf</0> <0>Спыніць DNSStubListener (перазагрузіць сістэмную службу)</0>.",
"autofix_warning_result": "У выніку ўсе DNS-запыты ад вашай сістэмы будуць па змаўчанні апрацоўвацца AdGuard Home.\n",
"tags_title": "Тэгі",
@@ -634,12 +634,12 @@
"validated_with_dnssec": "Проверено с помощью DNSSEC",
"all_queries": "Усе запыты",
"show_blocked_responses": "Заблакавана",
"show_whitelisted_responses": "Белы спіс",
"show_whitelisted_responses": "У белым спісе",
"show_processed_responses": "Апрацавана",
"blocked_safebrowsing": "Заблакіравана згодна з базай даных Safe Browsing",
"blocked_adult_websites": "Заблакавана Бацькоўскім кантролем",
"blocked_threats": "Заблакавана пагроз",
"allowed": "Дазволены",
"allowed": "У белым спісе",
"filtered": "Адфільтраваныя",
"rewritten": "Перапісаныя",
"safe_search": "Бяспечны пошук",
@@ -738,7 +738,7 @@
"thursday_short": "Чц.",
"friday_short": "Пт.",
"saturday_short": "Сб.",
"upstream_dns_cache_configuration": "Канфігурацыя кэша upstream DNS-сервераў",
"upstream_dns_cache_configuration": "Канфігурацыя кэша upstream сервер DNSаў",
"enable_upstream_dns_cache": "Ўключыць кэшаванне для карыстацкай канфігурацыі upstream-сервераў гэтага кліента",
"dns_cache_size": "Памер кэша DNS, у байтах"
}

View File

@@ -45,6 +45,7 @@
"filter": "Филтър",
"query_log": "История на заявките",
"compact": "Compact",
"nothing_found": "Нищо не е намерено",
"faq": "ЧЗВ",
"version": "версия",
"address": "Адрес",
@@ -65,14 +66,12 @@
"stats_malware_phishing": "вируси/атаки",
"stats_adult": "сайтове за възрастни",
"stats_query_domain": "Най-отваряни страници",
"for_last_24_hours": "за последните 24 часа",
"no_domains_found": "Няма намерени резултати",
"requests_count": "Сума на заявките",
"top_blocked_domains": "Най-блокирани страници",
"top_clients": "Най-активни IP адреси",
"no_clients_found": "Нямa намерени адреси",
"general_statistics": "Обща статисика",
"number_of_dns_query_24_hours": "Сума на DNS заявки за последните 24 часа",
"number_of_dns_query_blocked_24_hours": "Сума на блокирани DNS заявки от филтрите за реклама и местни",
"number_of_dns_query_blocked_24_hours_by_sec": "Сума на блокирани DNS заявки от AdGuard свързани със сигурността",
"number_of_dns_query_blocked_24_hours_adult": "Сума на блокирани сайтове за възрастни",
@@ -156,6 +155,7 @@
"rule_added_to_custom_filtering_toast": "Добавено до местни правила за филтриране: {{rule}}",
"default": "По подразбиране",
"custom_ip": "Персонализиран IP",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-пред-HTTPS",
"dns_over_quic": "DNS-over-QUIC",
"plain_dns": "Обикновен DNS",

View File

@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Důvod: {{reason}}",
"check_service": "Název služby: {{service}}",
"check_hostname": "Název hostitele nebo domény",
"check_client_id": "Identifikátor klienta (ClientID nebo IP adresa)",
"check_enter_client_id": "Zadejte identifikátor klienta",
"check_dns_record": "Vyberte typ DNS záznamu",
"service_name": "Název služby",
"check_not_found": "Nenalezeno ve Vašich seznamech filtrů",
"client_confirm_block": "Opravdu chcete zablokovat klienta „{{ip}}“?",
@@ -652,7 +656,7 @@
"blocklist": "Zakázaný",
"milliseconds_abbreviation": "ms",
"cache_size": "Velikost mezipaměti",
"cache_size_desc": "Velikost mezipaměti DNS (v bajtech). Chcete-li ukládání do mezipaměti zakázat, ponechte prázdné.",
"cache_size_desc": "Velikost mezipaměti DNS (v bajtech). Chcete-li ukládání do mezipaměti zakázat, nastavte 0.",
"cache_ttl_min_override": "Přepsat minimální hodnotu TTL",
"cache_ttl_max_override": "Přepsat maximální hodnotu TTL",
"enter_cache_size": "Zadejte velikost mezipaměti (v bajtech)",

View File

@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Årsag: {{reason}}",
"check_service": "Tjenestenavn: {{service}}",
"check_hostname": "Værts- eller domænenavn",
"check_client_id": "Klientidentifikator (ClientID eller IP-adresse)",
"check_enter_client_id": "Angiv klientidentifikator",
"check_dns_record": "Vælg DNS-posttype",
"service_name": "Tjenestenavn",
"check_not_found": "Ikke fundet i dine filterlister",
"client_confirm_block": "Sikker på, at du vil blokere klienten \"{{ip}}\"?",
@@ -652,7 +656,7 @@
"blocklist": "Sortliste",
"milliseconds_abbreviation": "ms",
"cache_size": "Cache-størrelse",
"cache_size_desc": "DNS cache-størrelse (i bytes). Lad stå tomt for at deaktivere cache.",
"cache_size_desc": "DNS cache-størrelse (i bytes). Sæt til 0 for at deaktivere cache.",
"cache_ttl_min_override": "Tilsidesæt minimum TTL",
"cache_ttl_max_override": "Tilsidesæt maksimal TTL",
"enter_cache_size": "Angiv cache-størrelse (bytes)",

View File

@@ -97,9 +97,9 @@
"settings": "Einstellungen",
"filters": "Filter",
"filter": "Filter",
"query_log": "Anfragenprotokoll",
"query_log": "Abfrageprotokoll",
"compact": "Kompakt",
"nothing_found": "Nichts gefunden\n",
"nothing_found": "Nichts gefunden",
"faq": "FAQ",
"version": "Version",
"address": "Adresse",
@@ -199,7 +199,7 @@
"cancel_btn": "Abbrechen",
"enter_name_hint": "Name eingeben",
"enter_url_or_path_hint": "URL oder absoluten Pfad der Liste eingeben",
"check_updates_btn": "Nach Aktualisierungen suchen",
"check_updates_btn": "Nach Updates suchen",
"new_blocklist": "Neue Sperrliste",
"new_allowlist": "Neue Positivliste",
"edit_blocklist": "Sperrliste bearbeiten",
@@ -566,7 +566,7 @@
"ignore_domains": "Ignorierte Domains (durch Zeilenumbruch getrennt)",
"ignore_domains_title": "Ignorierte Domains",
"ignore_domains_desc_stats": "Abfragen, die diesen Regeln entsprechen, werden nicht in die Statistik aufgenommen",
"ignore_domains_desc_query": "Abfragen, die diesen Regeln entsprechen, werden nicht in das Anfragenprotokoll aufgenommen",
"ignore_domains_desc_query": "Abfragen, die diesen Regeln entsprechen, werden nicht in das Abfrageprotokoll aufgenommen",
"interval_hours": "{{count}} Stunde",
"interval_hours_plural": "{{count}} Stunden",
"filters_configuration": "Filterkonfiguration",
@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Grund: {{reason}}",
"check_service": "Dienstname: {{service}}",
"check_hostname": "Hostname oder Domainname",
"check_client_id": "Client-Kennung (ClientID oder IP-Adresse)",
"check_enter_client_id": "Client-Kennung eingeben",
"check_dns_record": "DNS-Datensatztyp auswählen",
"service_name": "Name des Dienstes",
"check_not_found": "Nicht in Ihren Filterlisten enthalten",
"client_confirm_block": "Möchten Sie den Client „{{ip}}“ wirklich sperren?",
@@ -652,7 +656,7 @@
"blocklist": "Sperrliste",
"milliseconds_abbreviation": "ms",
"cache_size": "Größe des Cache",
"cache_size_desc": "Größe des DNS-Zwischenspeichers (in Bytes)",
"cache_size_desc": "Größe des DNS-Cache (in Bytes). Um das Caching zu deaktivieren, setzen Sie den Wert auf 0.",
"cache_ttl_min_override": "TTL-Minimalwert überschreiben",
"cache_ttl_max_override": "TTL-Höchstwert überschreiben",
"enter_cache_size": "Größe des Cache (Bytes) eingeben",

View File

@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Reason: {{reason}}",
"check_service": "Service name: {{service}}",
"check_hostname": "Hostname or domain name",
"check_client_id": "Client identifier (ClientID or IP address)",
"check_enter_client_id": "Enter client identifier",
"check_dns_record": "Select DNS record type",
"service_name": "Service name",
"check_not_found": "Not found in your filter lists",
"client_confirm_block": "Are you sure you want to block the client \"{{ip}}\"?",
@@ -652,7 +656,7 @@
"blocklist": "Blocklist",
"milliseconds_abbreviation": "ms",
"cache_size": "Cache size",
"cache_size_desc": "DNS cache size (in bytes). To disable caching, leave empty.",
"cache_size_desc": "DNS cache size (in bytes). To disable caching, set to 0.",
"cache_ttl_min_override": "Override minimum TTL",
"cache_ttl_max_override": "Override maximum TTL",
"enter_cache_size": "Enter cache size (bytes)",

View File

@@ -1,16 +1,16 @@
{
"client_settings": "Configuración de clientes",
"example_upstream_reserved": "un DNS de subida <0>para un dominio específico</0>.",
"example_multiple_upstreams_reserved": "múltiples upstreams <0>para dominios específicos</0>;",
"example_upstream_reserved": "un proveedor DNS <0>para un dominio específico</0>.",
"example_multiple_upstreams_reserved": "múltiples proveedores DNS <0>para dominios específicos</0>.",
"example_upstream_comment": "un comentario.",
"upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los servidores DNS de subida.",
"upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los proveedores DNS.",
"parallel_requests": "Consultas paralelas",
"load_balancing": "Balanceo de carga",
"load_balancing_desc": "Consulta un servidor Dns upstream a la vez.<br/>AdGuard Home utiliza un algoritmo aleatorio ponderado para seleccionar los servidores con el menor número de fallos y el menor tiempo medio de búsqueda.",
"load_balancing_desc": "Consulta un proveedor DNS a la vez.<br/>AdGuard Home utiliza un algoritmo aleatorio ponderado para seleccionar los servidores con el menor número de fallos y el menor tiempo promedio de búsqueda.",
"bootstrap_dns": "Servidores DNS de arranque",
"bootstrap_dns_desc": "Direcciones IP de servidores DNS utilizadas para resolver direcciones IP de los solucionadores DoH/DoT que especifiques como ascendentes. No se permiten comentarios.",
"bootstrap_dns_desc": "Direcciones IP de los servidores DNS utilizados para resolver las direcciones IP de los resolutores DoH/DoT que especifiques como proveedores DNS. No se permiten comentarios.",
"fallback_dns_title": "Servidores DNS alternativos",
"fallback_dns_desc": "Lista de servidores DNS alternativos utilizados cuando los servidores DNS de subida no responden. La sintaxis es la misma que en el campo de los principales DNS de subida anterior.",
"fallback_dns_desc": "Lista de servidores DNS alternativos utilizados cuando los proveedores DNS no responden. La sintaxis es la misma que en el campo de los principales proveedores DNS anterior.",
"fallback_dns_placeholder": "Ingresa un servidor DNS alternativo por línea",
"local_ptr_title": "Servidores DNS inversos y privados",
"local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para consultas PTR, SOA y NS privadas. La petición se considera privada si solicita un dominio ARPA que contiene una subred dentro de rangos IP privados, por ejemplo \"192.168.12.34\", y procede de un cliente con dirección privada. Si no se configura, AdGuard Home utiliza las direcciones de los resolvedores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.",
@@ -18,9 +18,9 @@
"local_ptr_no_default_resolver": "AdGuard Home no pudo determinar los resolutores DNS inversos y privados adecuados para este sistema.",
"local_ptr_placeholder": "Ingresa una dirección IP por línea",
"resolve_clients_title": "Habilitar la resolución inversa de las direcciones IP de clientes",
"resolve_clients_desc": "Resolve de manera inversa las direcciones IP de los clientes a sus nombres de hosts enviando consultas PTR a los resolutores correspondientes (servidores DNS privados para clientes locales, servidores DNS de subida para clientes con direcciones IP públicas).",
"resolve_clients_desc": "Resuelve de manera inversa las direcciones IP de los clientes a sus nombres de hosts enviando consultas PTR a los resolutores correspondientes (servidores DNS privados para clientes locales, proveedores DNS para clientes con direcciones IP públicas).",
"use_private_ptr_resolvers_title": "Usar resolutores DNS inversos y privados",
"use_private_ptr_resolvers_desc": "Resolver las peticiones PTR, SOA y NS para dominios ARPA que contienen direcciones privadas utilizando servidores upstream privados, DHCP, /etc/hosts, etc. Si se desactiva, AdGuard Home responde a todas estas consultas con NXDOMAIN.",
"use_private_ptr_resolvers_desc": "Resuelve peticiones PTR, SOA y NS para dominios ARPA que contienen direcciones IP privadas a través de proveedores DNS privados, DHCP, /etc/hosts, etc. Si se deshabilita, AdGuard Home responde a todas estas peticiones con NXDOMAIN.",
"check_dhcp_servers": "Comprobar si hay servidores DHCP",
"save_config": "Guardar configuración",
"enabled_dhcp": "Servidor DHCP habilitado",
@@ -132,8 +132,8 @@
"top_clients": "Clientes más frecuentes",
"no_clients_found": "No se han encontrado clientes",
"general_statistics": "Estadísticas generales",
"top_upstreams": "DNS de subida más frecuentes",
"no_upstreams_data_found": "No se han encontrado datos de DNS de subida",
"top_upstreams": "Proveedores DNS más frecuentes",
"no_upstreams_data_found": "No se han encontrado datos de proveedores DNS",
"number_of_dns_query_days": "Número de consultas DNS procesadas durante el último {{count}} día",
"number_of_dns_query_days_plural": "Número de consultas DNS procesadas durante los últimos {{count}} días",
"number_of_dns_query_hours": "Número de consultas DNS procesadas durante la última {{count}} hora",
@@ -144,7 +144,7 @@
"enforced_save_search": "Búsquedas seguras forzadas",
"number_of_dns_query_to_safe_search": "Número de peticiones DNS a los motores de búsqueda para los que se aplicó la búsqueda segura forzada",
"average_processing_time": "Tiempo promedio de procesamiento",
"average_upstream_response_time": "Tiempo promedio de respuesta upstream",
"average_upstream_response_time": "Tiempo promedio de respuesta del proveedor DNS",
"response_time": "Tiempo de respuesta",
"average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una petición DNS",
"block_domain_use_filters_and_hosts": "Bloquear dominios usando filtros y archivos hosts",
@@ -157,7 +157,7 @@
"enforce_save_search_hint": "AdGuard Home reforzará la búsqueda segura en los siguientes motores de búsqueda: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex y Pixabay.",
"no_servers_specified": "No hay servidores especificados",
"general_settings": "Configuración general",
"dns_settings": "Configuración del DNS",
"dns_settings": "Configuración DNS",
"dns_blocklists": "Listas de bloqueo DNS",
"dns_allowlists": "Listas de permitido DNS",
"dns_blocklists_desc": "AdGuard Home bloqueará los dominios que coincidan con las listas de bloqueo.",
@@ -165,12 +165,12 @@
"custom_filtering_rules": "Reglas de filtrado personalizado",
"encryption_settings": "Configuración de cifrado",
"dhcp_settings": "Configuración DHCP",
"upstream_dns": "Servidores DNS de subida",
"upstream_dns_help": "Ingresa una dirección de servidor por línea. <a>Más información</a> sobre la configuración de los servidores DNS de subida.",
"upstream_dns": "Proveedores DNS",
"upstream_dns_help": "Ingresa una dirección de servidor por línea. <a>Más información</a> sobre la configuración de los proveedores DNS.",
"upstream_dns_configured_in_file": "Configurado en {{path}}",
"test_upstream_btn": "Probar DNS de subida",
"upstreams": "DNS de subida",
"upstream": "DNS de subida",
"test_upstream_btn": "Probar proveedores DNS",
"upstreams": "Proveedores DNS",
"upstream": "Proveedor DNS",
"apply_btn": "Aplicar",
"disabled_filtering_toast": "Filtrado deshabilitado",
"enabled_filtering_toast": "Filtrado habilitado",
@@ -233,11 +233,11 @@
"example_upstream_tcp_port": "DNS regular (mediante TCP, con puerto).",
"example_upstream_tcp_hostname": "DNS regular (mediante TCP, nombre del host).",
"all_lists_up_to_date_toast": "Todas las listas ya están actualizadas",
"updated_upstream_dns_toast": "Servidores DNS de subida guardados correctamente",
"updated_upstream_dns_toast": "Proveedores DNS guardados correctamente",
"dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
"dns_test_not_ok_toast": "Servidor \"{{key}}\": no se puede utilizar, por favor revisa si lo has escrito correctamente",
"dns_test_parsing_error_toast": "No se pudo utilizar la sección {{section}}: línea {{line}}:, verifica si la escribiste correctamente",
"dns_test_warning_toast": "DNS de subida \"{{key}}\" no responde a las peticiones de prueba y es posible que no funcione correctamente",
"dns_test_warning_toast": "Proveedor DNS \"{{key}}\" no responde a las peticiones de prueba y es posible que no funcione correctamente",
"unblock": "Desbloquear",
"block": "Bloquear",
"disallow_this_client": "No permitir a este cliente",
@@ -294,9 +294,9 @@
"blocked_response_ttl": "Respuesta TTL bloqueada",
"blocked_response_ttl_desc": "Especifica durante cuántos segundos los clientes deben almacenar en cache una respuesta filtrada",
"form_enter_blocked_response_ttl": "Ingresa el TTL de respuesta bloqueada (segundos)",
"upstream_timeout": "Tiempo de espera del upstream",
"upstream_timeout_desc": "Especifica el número de segundos que se debe esperar para recibir una respuesta del servidor upstream",
"form_enter_upstream_timeout": "Ingresa la duración del tiempo de espera del servidor DNS upstream en segundos",
"upstream_timeout": "Tiempo de espera del proveedor DNS",
"upstream_timeout_desc": "Especifica el número de segundos que se debe esperar para recibir una respuesta del proveedor DNS",
"form_enter_upstream_timeout": "Ingresa la duración de tiempo de espera del proveedor DNS en segundos",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS mediante HTTPS",
"dns_over_tls": "DNS mediante TLS",
@@ -311,7 +311,7 @@
"form_enter_rate_limit": "Ingresa el límite de cantidad",
"rate_limit": "Límite de cantidad",
"edns_enable": "Habilitar subred de cliente EDNS",
"edns_cs_desc": "Añade la opción subred de cliente EDNS (ECS) a las peticiones del DNS de subida y registra los valores enviados por los clientes en el registro de consultas.",
"edns_cs_desc": "Añade la opción subred de cliente EDNS (ECS) a las peticiones del proveedor DNS y registra los valores enviados por los clientes en el registro de consultas.",
"edns_use_custom_ip": "Usar IP personalizada para EDNS",
"edns_use_custom_ip_desc": "Permitir el uso de IP personalizadas para EDNS",
"rate_limit_desc": "Número de peticiones por segundo permitidas por cliente. Establecerlo en 0 significa que no hay límite.",
@@ -335,7 +335,7 @@
"theme_auto": "Auto",
"theme_light": "Claro",
"theme_dark": "Oscuro",
"upstream_dns_client_desc": "Si se mantiene este campo vacío, AdGuard Home utilizará los servidores configurados en la <0>configuración del DNS</0>.",
"upstream_dns_client_desc": "Si se mantiene este campo vacío, AdGuard Home utilizará los servidores configurados en la <0>configuración DNS</0>.",
"tracker_source": "Fuente del rastreador",
"source_label": "Fuente",
"found_in_known_domain_db": "Encontrado en la base de datos de dominios conocidos.",
@@ -596,12 +596,12 @@
"example_rewrite_wildcard": "reescribe las respuestas para todos los subdominios de <0>ejemplo.org</0>.",
"rewrite_ip_address": "Dirección IP: utiliza esta IP en una respuesta A o AAAA",
"rewrite_domain_name": "Nombre de dominio: añade un registro CNAME",
"rewrite_A": "<0>A</0>: valor especial, mantiene registros <0>A</0> del DNS de subida",
"rewrite_AAAA": "<0>AAAA</0>: valor especial, mantiene registros <0>AAAA</0> del DNS de subida",
"rewrite_A": "<0>A</0>: valor especial, mantiene registros <0>A</0> del proveedor DNS",
"rewrite_AAAA": "<0>AAAA</0>: valor especial, mantiene registros <0>AAAA</0> del proveedor DNS",
"disable_ipv6": "Deshabilitar resolución de direcciones IPv6",
"disable_ipv6_desc": "Descarta todas las consultas de DNS para direcciones IPv6 (tipo AAAA) y elimina las sugerencias de IPv6 de las respuestas HTTPS.",
"fastest_addr": "Dirección IP más rápida",
"fastest_addr_desc": "Espera a que respondan <b>todos</b> los servidores DNS, mide la velocidad de conexión TCP de cada servidor y devuelve la Dirección IP del servidor con la velocidad de conexión más rápida.<br/>Este modo puede ralentizar significativamente las consultas DNS, si uno o más servidores DNS de upstream no están respondiendo. Asegúrate de que tus servidores DNS upstream sean estables y tu tiempo de espera de upstream sea bajo.",
"fastest_addr_desc": "Espera respuestas de <b>todos</b> los servidores DNS, mide la velocidad de conexión TCP de cada servidor y devuelve la dirección IP del servidor con la velocidad de conexión más rápida.<br/>Este modo puede ralentizar significativamente las consultas DNS, si uno o más proveedores DNS no responden. Asegúrate de que tus proveedores DNS sean estables y de que el tiempo de espera tu proveedor DNS sea bajo.",
"autofix_warning_text": "Si haces clic en \"Corregir\", AdGuard Home configurará tu sistema para utilizar el servidor DNS de AdGuard Home.",
"autofix_warning_list": "Realizará estas tareas: <0>Deshabilitar el sistema DNSStubListener</0> <0>Establecer la dirección del servidor DNS en 127.0.0.1</0> <0>Reemplazar el destino del enlace simbólico de /etc/resolv.conf por /run/systemd/resolve/resolv.conf</0> <0>Detener DNSStubListener (recargar el servicio systemd-resolved)</0>",
"autofix_warning_result": "Como resultado, todas las peticiones DNS de tu sistema serán procesadas por AdGuard Home de manera predeterminada.",
@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Razón: {{reason}}",
"check_service": "Nombre del servicio: {{service}}",
"check_hostname": "Nombre de host o nombre de dominio",
"check_client_id": "Identificador del cliente (ClientID o dirección IP)",
"check_enter_client_id": "Ingresa el identificador del cliente",
"check_dns_record": "Selecciona el tipo de registro DNS",
"service_name": "Nombre del servicio",
"check_not_found": "No se ha encontrado en tus listas de filtros",
"client_confirm_block": "¿Estás seguro de que deseas bloquear al cliente \"{{ip}}\"?",
@@ -652,13 +656,13 @@
"blocklist": "Lista de bloqueo",
"milliseconds_abbreviation": "ms",
"cache_size": "Tamaño de la caché",
"cache_size_desc": "Tamaño de la caché DNS (en bytes). Para deshabilitar el almacenamiento en caché, déjalo vacío.",
"cache_size_desc": "Tamaño de la caché DNS (en bytes). Para desactivar el almacenamiento en caché, configúralo en 0.",
"cache_ttl_min_override": "Anular TTL mínimo",
"cache_ttl_max_override": "Anular TTL máximo",
"enter_cache_size": "Ingresa el tamaño de la caché (bytes)",
"enter_cache_ttl_min_override": "Ingresa el TTL mínimo (en segundos)",
"enter_cache_ttl_max_override": "Ingresa el TTL máximo (en segundos)",
"cache_ttl_min_override_desc": "Amplía el corto tiempo de vida (segundos) de los valores recibidos del servidor DNS de subida al almacenar en caché las respuestas DNS.",
"cache_ttl_min_override_desc": "Amplía el corto tiempo de vida (segundos) de los valores recibidos del proveedor DNS al almacenar en caché las respuestas DNS.",
"cache_ttl_max_override_desc": "Establece un valor de tiempo de vida (segundos) máximo para las entradas en la caché DNS.",
"ttl_cache_validation": "La anulación TTL mínimo de la caché debe ser menor o igual al máximo",
"cache_optimistic": "Caché optimista",
@@ -744,7 +748,7 @@
"thursday_short": "Jue.",
"friday_short": "Vie.",
"saturday_short": "Sáb.",
"upstream_dns_cache_configuration": "Configuración de la caché DNS upstream",
"enable_upstream_dns_cache": "Habilitar el almacenamiento en caché de DNS para la configuración personalizada de este cliente",
"upstream_dns_cache_configuration": "Configuración de la caché del proveedor DNS",
"enable_upstream_dns_cache": "Habilitar el almacenamiento en caché del DNS para la configuración personalizada de este cliente",
"dns_cache_size": "Tamaño de la caché DNS, en bytes"
}

View File

@@ -620,6 +620,10 @@
"check_cname": "CNAME : {{cname}}",
"check_reason": "Raison : {{reason}}",
"check_service": "Nom du service : {{service}}",
"check_hostname": "Nom d'hôte ou nom de domaine",
"check_client_id": "Identifiant du client (ClientID ou adresse IP)",
"check_enter_client_id": "Saisissez l'identifiant du client",
"check_dns_record": "Sélectionnez le type d'enregistrement DNS",
"service_name": "Nom du service",
"check_not_found": "Introuvable dans vos listes de filtres",
"client_confirm_block": "Voulez-vous vraiment bloquer le client « {{ip}} » ?",
@@ -652,7 +656,7 @@
"blocklist": "Liste de blocage",
"milliseconds_abbreviation": "ms",
"cache_size": "Taille du cache",
"cache_size_desc": "Taille du cache DNS (en octets). Pour désactiver la mise en cache, laissez vide.",
"cache_size_desc": "Taille du cache DNS (en octets). Pour désactiver la mise en cache, mettez la valeur sur 0.",
"cache_ttl_min_override": "Remplacer le TTL minimum",
"cache_ttl_max_override": "Remplacer le TTL maximum",
"enter_cache_size": "Entrer la taille du cache (octets)",

View File

@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Motivo: {{reason}}",
"check_service": "Nome servizio: {{service}}",
"check_hostname": "Nome host o nome di dominio",
"check_client_id": "Identificatore client (ClientID o indirizzo IP)",
"check_enter_client_id": "Inserisci identificatore client",
"check_dns_record": "Seleziona il tipo di registrazione DNS",
"service_name": "Nome servizio",
"check_not_found": "Non trovato negli elenchi dei filtri",
"client_confirm_block": "Sei sicuro di voler bloccare il client \"{{ip}}\"?",
@@ -652,7 +656,7 @@
"blocklist": "Lista nera",
"milliseconds_abbreviation": "ms",
"cache_size": "Dimensioni cache",
"cache_size_desc": "Dimensione della cache DNS (in byte). Per disabilitare la memorizzazione nella cache, lascia vuoto.",
"cache_size_desc": "Dimensione della cache DNS (in byte). Per disabilitare la cache, impostare su 0.",
"cache_ttl_min_override": "Sovrascrivi TTL minimo",
"cache_ttl_max_override": "Sovrascrivi TTL massimo",
"enter_cache_size": "Immetti dimensioni cache (in byte)",

View File

@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "理由: {{reason}}",
"check_service": "サービス名: {{service}}",
"check_hostname": "ホスト名またはドメイン名",
"check_client_id": "クライアント識別子 (ClientID または IP アドレス)",
"check_enter_client_id": "クライアント識別子を入力してください",
"check_dns_record": "DNSレコードタイプDNS record typeを選択",
"service_name": "サービス名",
"check_not_found": "フィルタ一覧には見つかりません",
"client_confirm_block": "クライアント\"{{ip}}\"をブロックしてもよろしいですか?",
@@ -652,7 +656,7 @@
"blocklist": "ブロックリスト",
"milliseconds_abbreviation": "ms",
"cache_size": "キャッシュサイズ",
"cache_size_desc": "DNSキャッシュサイズバイト単位※キャッシュを無効化するには、この欄を空してください。",
"cache_size_desc": "DNSキャッシュサイズバイト単位※キャッシュを無効化するには、「0」ゼロしてください。",
"cache_ttl_min_override": "最小TTLの上書き秒単位",
"cache_ttl_max_override": "最大TTLの上書き秒単位",
"enter_cache_size": "キャッシュサイズ(バイト単位)を入力してください",

View File

@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "이유: {{reason}}",
"check_service": "서비스 이름: {{service}}",
"check_hostname": "호스트 이름 또는 도메인 이름",
"check_client_id": "클라이언트 식별자(클라이언트 ID 또는 IP 주소)",
"check_enter_client_id": "클라이언트 식별자 입력",
"check_dns_record": "DNS 레코드 유형 선택",
"service_name": "서비스 이름",
"check_not_found": "필터 목록에서 찾을 수 없음",
"client_confirm_block": "정말로 클라이언트 '{{ip}}'을(를) 차단하시겠습니까?",
@@ -652,7 +656,7 @@
"blocklist": "차단 목록",
"milliseconds_abbreviation": "ms",
"cache_size": "캐시 크기",
"cache_size_desc": "DNS 캐시 크기(바이트). 캐싱을 비활성화하려면 비워 둡니다.",
"cache_size_desc": "DNS 캐시 크기(바이트). 캐싱을 사용하지 않으려면 0으로 설정합니다.",
"cache_ttl_min_override": "최소 TTL (초) 무시",
"cache_ttl_max_override": "최대 TTL (초) 무시",
"enter_cache_size": "캐시 크기를 입력하세요",

View File

@@ -110,9 +110,9 @@
"homepage": "Startpagina",
"report_an_issue": "Rapporteer een probleem",
"privacy_policy": "Privacybeleid",
"enable_protection": "Schakel bescherming in",
"enable_protection": "Bescherming inschakelen",
"enabled_protection": "Bescherming ingeschakeld",
"disable_protection": "Schakel bescherming uit",
"disable_protection": "Bescherming uitschakelen",
"disabled_protection": "Bescherming uitgeschakeld",
"refresh_statics": "Ververs statistieken",
"dns_query": "DNS-queries",
@@ -327,10 +327,10 @@
"rate_limit_whitelist_placeholder": "Voer één IP-adres per regel in",
"blocking_ipv4_desc": "IP-adres dat moet worden teruggegeven voor een geblokkeerd A-verzoek",
"blocking_ipv6_desc": "IP-adres dat moet worden teruggegeven voor een geblokkeerd A-verzoek",
"blocking_mode_default": "Standaard: Reageer met een nul IP adres (0.0.0.0 for A; :: voor AAAA) wanneer geblokkeerd door een Adblock-type regel; reageer met het IP-adres dat is opgegeven in de regel wanneer geblokkeerd door een /etc/hosts type regel",
"blocking_mode_default": "Standaard: Reageer met een nul IP-adres (0.0.0.0 for A; :: voor AAAA) wanneer geblokkeerd door een Adblock-type regel; reageer met het IP-adres dat is opgegeven in de regel wanneer geblokkeerd door een /etc/hosts type regel",
"blocking_mode_refused": "REFUSED: Antwoorden met REFUSED code",
"blocking_mode_nxdomain": "NXDOMAIN: Reageer met NXDOMAIN code",
"blocking_mode_null_ip": "Nul IP: Reageer met een nul IP address (0.0.0.0 voor A; :: voor AAAA)",
"blocking_mode_null_ip": "Nul IP: Reageer met een nul IP-adres (0.0.0.0 voor A; :: voor AAAA)",
"blocking_mode_custom_ip": "Aangepast IP: Reageer met een handmatige ingesteld IP adres",
"theme_auto": "Automatisch",
"theme_light": "Licht",
@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Reden: {{reason}}",
"check_service": "Servicenaam: {{service}}",
"check_hostname": "Hostnaam of domeinnaam",
"check_client_id": "Client identificator (ClientID of IP-adres)",
"check_enter_client_id": "Voer Client identificator in",
"check_dns_record": "Selecteer type DNS-record",
"service_name": "Naam service",
"check_not_found": "Niet in je lijst met filters gevonden",
"client_confirm_block": "Weet je zeker dat je client \"{{ip}}\" wil blokkeren?",
@@ -652,7 +656,7 @@
"blocklist": "Blokkeerlijst",
"milliseconds_abbreviation": "ms",
"cache_size": "Cache grootte",
"cache_size_desc": "DNS-cachegrootte (in bytes). Leeg laten om caching uit te schakelen.",
"cache_size_desc": "DNS-cachegrootte (in bytes). Om caching uit te schakelen, stel deze in op 0.",
"cache_ttl_min_override": "Minimale TTL overschrijven",
"cache_ttl_max_override": "Maximale TTL overschrijven",
"enter_cache_size": "Cache grootte invoeren (bytes)",
@@ -698,13 +702,13 @@
"disable_for_hours": "Voor {{count}} uur",
"disable_for_hours_plural": "Voor {{count}} uren",
"disable_until_tomorrow": "Tot morgen",
"disable_notify_for_seconds": "Beveiliging uitschakelen voor {{count}} seconde",
"disable_notify_for_seconds_plural": "Beveiliging uitschakelen voor {{count}} seconden",
"disable_notify_for_minutes": "Beveiliging uitschakelen voor {{count}} minuut",
"disable_notify_for_minutes_plural": "Beveiliging uitschakelen voor {{count}} minuten",
"disable_notify_for_hours": "Beveiliging uitschakelen voor {{count}} uur",
"disable_notify_for_hours_plural": "Beveiliging uitschakelen voor {{count}} uren",
"disable_notify_until_tomorrow": "Beveiliging uitschakelen tot morgen",
"disable_notify_for_seconds": "Bescherming uitschakelen voor {{count}} seconde",
"disable_notify_for_seconds_plural": "Bescherming uitschakelen voor {{count}} seconden",
"disable_notify_for_minutes": "Bescherming uitschakelen voor {{count}} minuut",
"disable_notify_for_minutes_plural": "Bescherming uitschakelen voor {{count}} minuten",
"disable_notify_for_hours": "Bescherming uitschakelen voor {{count}} uur",
"disable_notify_for_hours_plural": "Bescherming uitschakelen voor {{count}} uren",
"disable_notify_until_tomorrow": "Bescherming uitschakelen tot morgen",
"enable_protection_timer": "Bescherming wordt ingeschakeld over {{time}}",
"custom_retention_input": "Voer retentie in uren in",
"custom_rotation_input": "Voer rotatie in uren in",

View File

@@ -106,7 +106,6 @@
"stats_malware_phishing": "Blokkert skadevare/phishing",
"stats_adult": "Blokkerte voksennettsteder",
"stats_query_domain": "Mest forespurte domener",
"for_last_24_hours": "de siste 24 timene",
"for_last_days": "for den siste {{count}} dagen",
"for_last_days_plural": "de siste {{count}} dagene",
"stats_disabled": "Statistikkene har blitt skrudd av. Du kan skru den på fra <0>innstillingssiden</0>.",
@@ -121,7 +120,6 @@
"no_upstreams_data_found": "Ingen oppstrøms servere data funnet",
"number_of_dns_query_days": "Antall DNS-spørringer behandlet for de siste {{count}} dagene",
"number_of_dns_query_days_plural": "Antall DNS-forespørsler som ble behandlet de siste {{count}} dagene",
"number_of_dns_query_24_hours": "Antall DNS-forespørsler som ble behandlet de siste 24 timene",
"number_of_dns_query_blocked_24_hours": "Antall DNS-forespørsler som ble blokkert av adblock-filtre, hosts-lister, og domene-lister",
"number_of_dns_query_blocked_24_hours_by_sec": "Antall DNS-forespørsler som ble blokkert av AdGuard sin nettlesersikkerhetsmodul",
"number_of_dns_query_blocked_24_hours_adult": "Antall voksennettsteder som ble blokkert",
@@ -266,6 +264,7 @@
"custom_ip": "Tilpasset IP",
"blocking_ipv4": "IPv4-blokkering",
"blocking_ipv6": "IPv6-blokkering",
"blocked_response_ttl": "Blokkerte svars TTL",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -627,7 +626,6 @@
"use_saved_key": "Bruk den tidligere lagrede nøkkelen",
"parental_control": "Foreldrekontroll",
"safe_browsing": "Sikker surfing",
"served_from_cache": "{{value}} <i>(formidlet fra mellomlageret)</i>",
"theme_dark_desc": "Mørkt tema",
"theme_light_desc": "Lyst tema",
"disable_notify_until_tomorrow": "Deaktiver beskyttelsen til i morgen",

View File

@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Motivo: {{reason}}",
"check_service": "Nome do serviço: {{service}}",
"check_hostname": "Nome do anfitrião ou nome de domínio",
"check_client_id": "Identificador do cliente (ClienteID ou endereço de IP)",
"check_enter_client_id": "Insira o identificador do cliente",
"check_dns_record": "Selecione o tipo de registro DNS",
"service_name": "Nome do serviço",
"check_not_found": "Não encontrado em suas listas de filtros",
"client_confirm_block": "Você tem certeza de que deseja bloquear o cliente \"{{ip}}\"?",
@@ -652,7 +656,7 @@
"blocklist": "Lista de bloqueio",
"milliseconds_abbreviation": "ms",
"cache_size": "Tamanho do cache",
"cache_size_desc": "Tamanho do cache do DNS (em bytes). Para desativar o cache, deixe em branco.",
"cache_size_desc": "Tamanho do cache do DNS (em bytes). Para desativar o cache, defina como 0.",
"cache_ttl_min_override": "Sobrepor o TTL mínimo",
"cache_ttl_max_override": "Sobrepor o TTL máximo",
"enter_cache_size": "Digite o tamanho do cache (bytes)",

View File

@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Motivo: {{reason}}",
"check_service": "Nome do serviço: {{service}}",
"check_hostname": "Nome do hospedeiro ou nome de domínio",
"check_client_id": "Identificador do cliente (ClientID ou endereço IP)",
"check_enter_client_id": "Insira o identificador do cliente",
"check_dns_record": "Selecione o tipo de registro DNS",
"service_name": "Nome do serviço",
"check_not_found": "Não encontrado nas tuas listas de filtros",
"client_confirm_block": "Você tem certeza de que deseja bloquear o cliente \"{{ip}}\"?",
@@ -652,7 +656,7 @@
"blocklist": "Lista de bloqueio",
"milliseconds_abbreviation": "ms",
"cache_size": "Tamanho do cache",
"cache_size_desc": "Tamanho do cache DNS (em bytes). Para desativar o cache, deixar o campo vazio.",
"cache_size_desc": "Tamanho do cache DNS (em bytes). Para desativar o cache, defina como 0.",
"cache_ttl_min_override": "Sobrepor o TTL mínimo",
"cache_ttl_max_override": "Sobrepor o TTL máximo",
"enter_cache_size": "Digite o tamanho do cache (bytes)",

View File

@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Причина: {{reason}}",
"check_service": "Название сервиса: {{service}}",
"check_hostname": "Имя хоста или домена",
"check_client_id": "Идентификатор клиента (ClientID или IP-адрес)",
"check_enter_client_id": "Введите идентификатор клиента",
"check_dns_record": "Выберите тип DNS-записи",
"service_name": "Имя сервиса",
"check_not_found": "Не найдено в вашем списке фильтров",
"client_confirm_block": "Вы уверены, что хотите заблокировать клиента «{{ip}}»?",
@@ -652,7 +656,7 @@
"blocklist": "Чёрный список",
"milliseconds_abbreviation": "мс",
"cache_size": "Размер кеша",
"cache_size_desc": "Размера кеша DNS (в байтах). Чтобы отключить кэширование, оставьте поле пустым.",
"cache_size_desc": "Размер кеша DNS (в байтах). Чтобы отключить кеширование, установите значение 0.",
"cache_ttl_min_override": "Переопределить минимальный TTL",
"cache_ttl_max_override": "Переопределить максимальный TTL",
"enter_cache_size": "Введите размер кеша (в байтах)",

View File

@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Dôvod: {{reason}}",
"check_service": "Meno služby: {{service}}",
"check_hostname": "Názov hostiteľa alebo názov domény",
"check_client_id": "Identifikátor klienta (ClientID alebo IP adresa)",
"check_enter_client_id": "Zadajte identifikátor klienta",
"check_dns_record": "Vyberte typ DNS záznamu",
"service_name": "Názov služby",
"check_not_found": "Nenašlo sa vo Vašom zozname filtrov",
"client_confirm_block": "Naozaj chcete zablokovať klienta \"{{ip}}\"?",
@@ -652,7 +656,7 @@
"blocklist": "Zoznam blokovaní",
"milliseconds_abbreviation": "ms",
"cache_size": "Veľkosť cache",
"cache_size_desc": "Veľkosť vyrovnávacej pamäte DNS (v bajtoch). Ak chcete zakázať ukladanie do vyrovnávacej pamäte, ponechajte pole prázdne.",
"cache_size_desc": "Veľkosť vyrovnávacej pamäte DNS (v bajtoch). Ak chcete vypnúť ukladanie do vyrovnávacej pamäte, nastavte hodnotu 0.",
"cache_ttl_min_override": "Prepísať minimálne TTL",
"cache_ttl_max_override": "Prepísať maximálne TTL",
"enter_cache_size": "Zadať veľkosť cache (v bajtoch)",

View File

@@ -40,11 +40,11 @@
"dhcp_ipv4_settings": "DHCP IPv4 Ayarları",
"dhcp_ipv6_settings": "DHCP IPv6 Ayarları",
"form_error_required": "Gerekli alan",
"form_error_ip4_format": "Geçersiz IPv4 adresi",
"form_error_ip4_gateway_format": "Geçersiz ağ geçidi IPv4 adresi",
"form_error_ip6_format": "Geçersiz IPv6 adresi",
"form_error_ip_format": "Geçersiz IP adresi",
"form_error_mac_format": "Geçersiz MAC adresi",
"form_error_ip4_format": "IPv4 adresi geçersiz",
"form_error_ip4_gateway_format": "Ağ geçidi IPv4 adresi geçersiz",
"form_error_ip6_format": "IPv6 adresi geçersiz",
"form_error_ip_format": "IP adresi geçersiz",
"form_error_mac_format": "MAC adresi geçersiz",
"form_error_client_id_format": "İstemci Kimliği yalnızca sayılar, küçük harfler ve kısa çizgiler içermelidir",
"form_error_server_name": "Sunucu adı geçersiz",
"form_error_subnet": "\"{{cidr}}\" alt ağı, \"{{ip}}\" IP adresini içermiyor",
@@ -68,7 +68,7 @@
"ip": "IP",
"dhcp_table_hostname": "Ana makine Adı",
"dhcp_table_expires": "Bitiş tarihi",
"dhcp_warning": "DHCP sunucusunu yine de etkinleştirmek istiyorsanız, ağınızda başka aktif DHCP sunucusu olmadığından emin olun, aksi takdirde ağa bağlı cihazların internet bağlantısı kesilebilir!",
"dhcp_warning": "DHCP sunucusunu yine de etkinleştirmek istiyorsanız, ağınızda başka bir aktif DHCP sunucusu olmadığından emin olun, aksi takdirde ağa bağlı cihazların internet bağlantısı kesilebilir!",
"dhcp_error": "AdGuard Home, ağda başka bir etkin DHCP sunucusu olup olmadığını belirleyemedi",
"dhcp_static_ip_error": "DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. AdGuard Home, bu ağ arayüzünün sabit bir IP adresi kullanılarak yapılandırılıp yapılandırılmadığını belirleyemedi. Lütfen sabit IP adresini elle ayarlayın.",
"dhcp_dynamic_ip_found": "Sisteminiz, <0>{{interfaceName}}</0> arayüzü için dinamik IP adresi yapılandırması kullanıyor. DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. Geçerli olan IP adresiniz <0>{{ipAddress}}</0>. \"DHCP sunucusunu etkinleştir\" düğmesine basarsanız, AdGuard Home bu IP adresini otomatik bir şekilde sabit olarak ayarlayacaktır.",
@@ -147,13 +147,13 @@
"average_upstream_response_time": "Ortalama üst kaynak yanıt süresi",
"response_time": "Yanıt süresi",
"average_processing_time_hint": "Bir DNS isteğinin milisaniye cinsinden ortalama işlem süresi",
"block_domain_use_filters_and_hosts": "Filtre ve hosts dosyalarını kullanarak alan adlarını engelle",
"block_domain_use_filters_and_hosts": "Filtre ve ana bilgisayar dosyalarını kullanarak alan adlarını engelle",
"filters_block_toggle_hint": "<a>Filtreler</a> ayarlarında engelleme kuralları oluşturabilirsiniz.",
"use_adguard_browsing_sec": "AdGuard gezinti koruması web hizmetini kullan",
"use_adguard_browsing_sec_hint": "AdGuard Home, alan adının gezinti koruması web hizmeti tarafından engellenip engellenmediğini kontrol eder. Kontrolü gerçekleştirmek için gizlilik dostu arama API'sini kullanır: sunucuya yalnızca SHA256 karma alan adının kısa bir ön eki gönderilir.",
"use_adguard_parental": "AdGuard ebeveyn denetimi web hizmetini kullan",
"use_adguard_parental_hint": "AdGuard Home, alan adının yetişkin içerik bulundurup bulundurmadığını kontrol eder. Gezinti koruması web hizmeti ile kullandığımız aynı gizlilik dostu API'yi kullanır.",
"enforce_safe_search": "Güvenli Aramayı kullan",
"enforce_safe_search": "Güvenli aramayı kullan",
"enforce_save_search_hint": "AdGuard Home, şu arama motorlarında güvenli aramayı uygular: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.",
"no_servers_specified": "Sunucu belirtilmedi",
"general_settings": "Genel ayarlar",
@@ -294,6 +294,9 @@
"blocked_response_ttl": "Engellenen yanıt kullanım süresi",
"blocked_response_ttl_desc": "İstemcilerin filtrelenmiş bir yanıtı kaç saniye süreyle önbelleğe alması gerektiğini belirtir",
"form_enter_blocked_response_ttl": "Engellenen yanıt kullanım süresini girin (saniye)",
"upstream_timeout": "Üst kaynak zaman aşımı",
"upstream_timeout_desc": "Üst kaynak sunucusundan yanıt almak için kaç saniye bekleneceğini belirtir",
"form_enter_upstream_timeout": "Üst kaynak sunucusu zaman aşımı süresini saniye cinsinden girin",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -308,7 +311,7 @@
"form_enter_rate_limit": "Sıklık limitini girin",
"rate_limit": "Sıklık limiti",
"edns_enable": "EDNS istemci alt ağını etkinleştir",
"edns_cs_desc": "Kaynak yönü isteklerine EDNS İstemci Alt Ağı seçeneğini (ECS) ekleyin ve istemciler tarafından gönderilen değerleri sorgu günlüğüne kaydedin.",
"edns_cs_desc": "Üst sunucu isteklerine ECS (EDNS İstemci Alt Ağı) seçeneğini ekler ve istemciler tarafından gönderilen değerleri sorgu günlüğünde kaydeder.",
"edns_use_custom_ip": "EDNS için özel IP kullan",
"edns_use_custom_ip_desc": "EDNS için özel IP kullanımına izin ver",
"rate_limit_desc": "İstemci başına izin verilen saniyedeki istek sayısı. 0 olarak ayarlamak, sınır olmadığı anlamına gelir.",
@@ -342,17 +345,17 @@
"unknown_filter": "Bilinmeyen filtre {{filterId}}",
"known_tracker": "Bilinen izleyici",
"install_welcome_title": "AdGuard Home'a hoş geldiniz!",
"install_welcome_desc": "AdGuard Home, ağ genelinde reklamları ve izleyicileri engelleyen bir DNS sunucusudur. Tüm ağınızı ve tüm cihazlarınızı kontrol etmenizi sağlar, istemci tarafında herhangi bir program kullanmanıza gerek duymaz.",
"install_welcome_desc": "AdGuard Home, ağ genelinde reklam ve izleyici engelleyen bir DNS sunucusudur. Tüm ağınızı ve cihazlarınızı kontrol etmenizi sağlar ve istemci tarafında ek bir yazılım kullanmanıza gerek duymaz.",
"install_settings_title": "Yönetici Web Arayüzü",
"install_settings_listen": "Dinleme arayüzü",
"install_settings_port": "Bağlantı noktası",
"install_settings_interface_link": "AdGuard Home yönetici web arayüzünüz aşağıdaki adreslerde bulunacaktır:",
"install_settings_interface_link": "AdGuard Home yönetici web arayüzüne aşağıdaki adreslerden erişebilirsiniz:",
"form_error_port": "Geçerli bir bağlantı noktası değeri girin",
"install_settings_dns": "DNS sunucusu",
"install_settings_dns_desc": "Aşağıdaki adreslerde DNS sunucusunu kullanmak için cihazlarınızı veya yönlendiricinizi yapılandırmanız gerekir:",
"install_settings_dns_desc": "Cihazlarınızı veya yönlendiricinizi aşağıdaki adreslerdeki DNS sunucusunu kullanacak şekilde yapılandırmanız gerekir:",
"install_settings_all_interfaces": "Tüm arayüzler",
"install_auth_title": "Kimlik Doğrulama",
"install_auth_desc": "AdGuard Home yönetim web arayüzü için şifre doğrulaması yapılandırılmalıdır. AdGuard Home'a yalnızca yerel ağınızdan erişilebilir olsa bile, onu sınırsız erişimden korumak yine de önemlidir.",
"install_auth_desc": "AdGuard Home yönetici web arayüzüne parola ile kimlik doğrulama yapılandırılmalıdır. AdGuard Home yalnızca yerel ağınızdan erişilebilir olsa bile, yine de yetkisiz erişime karşı korunması önemlidir.",
"install_auth_username": "Kullanıcı adı",
"install_auth_password": "Parola",
"install_auth_confirm": "Parolayı onayla",
@@ -366,10 +369,10 @@
"install_devices_router": "Yönlendirici",
"install_devices_router_desc": "Bu kurulum, ev yönlendiricinize bağlı tüm cihazları otomatik olarak kapsar ve her birini elle yapılandırmanıza gerek yoktur.",
"install_devices_address": "AdGuard Home DNS sunucusu aşağıdaki adresleri dinliyor",
"install_devices_router_list_1": "Yönlendiricinizin ayarlarına gidin. Genellikle tarayıcınızdan http://192.168.0.1/ veya http://192.168.1.1/ gibi bir URL aracılığıyla erişebilirsiniz. Bir parola girmeniz istenebilir. Hatırlamıyorsanız, genellikle yönlendiricinin üzerindeki bir düğmeye basarak parolayı sıfırlayabilirsiniz, ancak bu işlemin seçilmesi durumunda yüksek ihtimalle tüm yönlendirici yapılandırmasını kaybedeceğinizi unutmayın. Yönlendiricinizin kurulumu için bir uygulama gerekiyorsa, lütfen uygulamayı telefonunuza veya PC'nize yükleyin ve yönlendiricinin ayarlarına erişmek için kullanın.",
"install_devices_router_list_1": "Yönlendiricinizin ayarlarına gidin. Genellikle, tarayıcınızdan http://192.168.0.1/ veya http://192.168.1.1/ gibi bir URL üzerinden erişebilirsiniz. Giriş yaparken bir parola girmeniz istenebilir. Parolanızı hatırlamıyorsanız, genellikle yönlendiricinin üzerindeki bir düğmeye basarak parolayı sıfırlayabilirsiniz, ancak bu işlemi seçerseniz yönlendiricinin tüm yapılandırmasını kaybedebileceğinizi unutmayın. Yönlendiricinizin kurulumu için bir uygulama gerekiyorsa, lütfen uygulamayı telefonunuza veya bilgisayarınıza yükleyin ve yönlendiricinin ayarlarına erişmek için bu uygulamayı kullanın.",
"install_devices_router_list_2": "DHCP/DNS ayarlarını bulun. DNS satırlarını arayın, genelde iki veya üç tanedir, üç rakam girilebilen dört ayrı grup içeren satırdır.",
"install_devices_router_list_3": "AdGuard Home sunucu adreslerinizi oraya girin.",
"install_devices_router_list_4": "Bazı yönlendirici türlerinde özel bir DNS sunucusu ayarlanamaz. Bu durumda, AdGuard Home'u <0>DHCP sunucusu</0> olarak ayarlamak yardımcı olabilir. Aksi takdirde, yönlendirici modeliniz için DNS sunucularını nasıl ayarlayacağınız konusunda yönlendirici kılavuzuna bakmalısınız.",
"install_devices_router_list_4": "Bazı yönlendirici türlerinde özel bir DNS sunucusu yapılandırılamaz. Bu durumda, AdGuard Home'u bir <0>DHCP sunucusu</0> olarak yapılandırmak yardımcı olabilir. Aksi takdirde, yönlendirici modelinizde DNS sunucularını nasıl özelleştireceğinizi öğrenmek için yönlendirici kılavuzunu kontrol etmelisiniz.",
"install_devices_windows_list_1": "Başlat menüsünden veya Windows araması aracılığıyla Denetim Masası'nıın.",
"install_devices_windows_list_2": "Ağ ve İnternet kategorisine girin ve ardından Ağ ve Paylaşım Merkezi'ne girin.",
"install_devices_windows_list_3": "Sol panelde \"Bağdaştırıcı ayarlarını değiştirin\" öğesine tıklayın.",
@@ -389,7 +392,7 @@
"install_devices_ios_list_2": "Sol menüde bulunan Wi-Fi bölümüne girin (telefon ağlar için özel DNS sunucusu ayarlanamaz).",
"install_devices_ios_list_3": "O anda aktif olan ağın adına dokunun.",
"install_devices_ios_list_4": "DNS alanına AdGuard Home sunucunuzun adreslerini girin.",
"get_started": "Başlayın",
"get_started": "Başla",
"next": "Sonraki",
"open_dashboard": "Panoyu Aç",
"install_saved": "Başarıyla kaydedildi",
@@ -452,14 +455,14 @@
"settings_global": "Genel",
"settings_custom": "Özel",
"table_client": "İstemci",
"table_name": "AdAdı",
"table_name": "Ad",
"save_btn": "Kaydet",
"client_add": "İstemci Ekle",
"client_new": "Yeni İstemci",
"client_edit": "İstemciyi Düzenle",
"client_identifier": "Tanımlayıcı",
"ip_address": "IP adresi",
"client_identifier_desc": "İstemciler IP adresleri, CIDR, MAC adresleri veya ClientID (DoT/DoH/DoQ için kullanılabilir) ile tanımlanabilir. İstemcileri nasıl tanımlayacağınız hakkında daha fazla bilgiyi <0>buradan</0> edinebilirsiniz.",
"client_identifier_desc": "İstemciler, IP adresi, CIDR, MAC adresi veya ClientID (DoT/DoH/DoQ için kullanılabilir) ile tanımlanabilir. İstemcileri nasıl tanımlayacağınız hakkında daha fazla bilgiye <0>buradan</0> ulaşabilirsiniz.",
"form_enter_ip": "IP girin",
"form_enter_subnet_ip": "\"{{cidr}}\" alt ağına bir IP adresi girin",
"form_enter_mac": "MAC adresi girin",
@@ -476,7 +479,7 @@
"client_confirm_delete": "\"{{key}}\" istemcisini silmek istediğinizden emin misiniz?",
"list_confirm_delete": "Bu listeyi silmek istediğinizden emin misiniz?",
"auto_clients_title": "Çalışma zamanı istemcileri",
"auto_clients_desc": "AdGuard Home'u kullanan veya kullanabilecek cihazların IP adresleri hakkında bilgiler. Bu bilgiler, hosts dosyaları, ters DNS, vb. dâhil olmak üzere çeşitli kaynaklardan toplanır.",
"auto_clients_desc": "AdGuard Home'u kullanan veya kullanabilecek cihazların IP adresleri hakkında bilgiler. Bu bilgiler, ana bilgisayar dosyaları, ters DNS sorguları ve çeşitli diğer kaynaklardan toplanmaktadır.",
"access_title": "Erişim ayarları",
"access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz",
"access_allowed_title": "İzin verilen istemciler",
@@ -598,12 +601,12 @@
"disable_ipv6": "IPv6 adreslerinin çözümlenmesini devre dışı bırak",
"disable_ipv6_desc": "IPv6 adresleri için tüm DNS sorgularını bırakın (AAAA yazın) ve HTTPS yanıtlarından IPv6 ipuçlarını kaldırın.",
"fastest_addr": "En hızlı IP adresi",
"fastest_addr_desc": "Tüm DNS sunucularını sorgulayın ve tüm yanıtlar arasından en hızlı olan IP adresini döndürün. AdGuard Home'un tüm DNS sunucularından yanıt beklemesi gerektiği için DNS sorgularını yavaşlatır, ancak genel bağlantıyı iyileştirir.",
"fastest_addr_desc": "<b>Tüm</b> DNS sunucularından yanıt bekler, her sunucu için TCP bağlantı hızını ölçer ve en hızlı bağlantı hızına sahip sunucunun IP adresini döndürür.<br/>Bu yapılandırma, bir veya daha fazla üst kaynak sunucusu yanıt vermediğinde, DNS sorgularını önemli ölçüde yavaşlatabilir. Üst kaynak sunucularınızın kararlı olduğundan ve üst kaynak zaman aşım sürenizin düşük olduğundan emin olun.",
"autofix_warning_text": "\"Düzelt\" seçeneğine tıklarsanız, AdGuard Home, sisteminizi AdGuard Home DNS sunucusunu kullanacak şekilde yapılandırır.",
"autofix_warning_list": "Bu görevleri gerçekleştirir: <0>Sistem DNSStubListener'ı devre dışı bırakın</0> <0>DNS sunucusu adresini 127.0.0.1 olarak ayarlayın</0> <0>/etc/resolv.conf'un sembolik bağlantı hedefini /run/systemd/resolve/resolv.conf ile değiştirin<0> <0>DNSStubListener'ı durdurun (systemd çözümlenmiş hizmeti yeniden yükleyin)</0>",
"autofix_warning_result": "Sonuç olarak, sisteminizden gelen tüm DNS istekleri varsayılan olarak AdGuard Home tarafından işlenecektir.",
"tags_title": "Etiketler",
"tags_desc": "İstemciye karşılık gelen etiketleri seçebilirsiniz. Etiketleri daha kesin olarak uygulamak için filtreleme kurallarına dâhil edin. <0>Daha fazla bilgi edinin</0>.",
"tags_desc": "İstemciyi tanımlayan etiketleri seçebilirsiniz. Filtreleme kurallarına etiketleri dahil ederek daha hassas bir şekilde uygulayabilirsiniz. <0>Daha fazla bilgi edinin</0>.",
"form_select_tags": "İstemci etiketlerini seçin",
"check_title": "Filtrelemeyi denetleyin",
"check_desc": "Ana makine adının filtreleme durumunu kontrol edin.",
@@ -617,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Sebep: {{reason}}",
"check_service": "Hizmet adı: {{service}}",
"check_hostname": "Ana makine adı veya alan adı",
"check_client_id": "İstemci tanımlayıcısı (ClientID veya IP adresi)",
"check_enter_client_id": "İstemci tanımlayıcısı girin",
"check_dns_record": "DNS kayıt türünü seçin",
"service_name": "Hizmet adı",
"check_not_found": "Filtre listelerinizde bulunamadı",
"client_confirm_block": "\"{{ip}}\" istemcisini engellemek istediğinizden emin misiniz?",
@@ -624,11 +631,11 @@
"client_blocked": "\"{{ip}}\" istemcisi başarıyla engellendi",
"client_unblocked": "\"{{ip}}\" istemcinin engellemesi başarıyla kaldırıldı",
"static_ip": "Sabit IP adresi",
"static_ip_desc": "AdGuard Home bir sunucudur, bu nedenle düzgün çalışması için sabit bir IP adresine ihtiyacı vardır. Aksi takdirde, yönlendiriciniz bir zaman sonra bu cihaza farklı bir IP adresi atayabilir.",
"static_ip_desc": "AdGuard Home bir sunucudur, bu nedenle düzgün çalışabilmesi için sabit bir IP adresine ihtiyaç duyar. Aksi takdirde, yönlendiriciniz bu cihaza farklı bir IP adresi atayabilir.",
"set_static_ip": "Sabit IP adresi ayarla",
"install_static_ok": "Güzel haber! Sabit IP adresi zaten yapılandırılmış",
"install_static_error": "AdGuard Home, bu ağ arayüzü için otomatik olarak yapılandıramıyor. Lütfen bunu elle nasıl yapacağınızla ilgili talimatlara bakın.",
"install_static_configure": "AdGuard Home, <0>{{ip}}</0> dinamik IP adresinin kullanıldığını tespit etti. Sabit adresiniz olarak ayarlanmasını ister misiniz?",
"install_static_configure": "AdGuard Home, <0>{{ip}}</0> sabit IP adresinin kullanıldığını tespit etti. Sabit adresiniz olarak ayarlanmasını ister misiniz?",
"confirm_static_ip": "AdGuard Home, {{ip}} adresini sabit IP adresiniz olacak şekilde yapılandırır. Devam etmek istiyor musunuz?",
"list_updated": "{{count}} liste güncellendi",
"list_updated_plural": "{{count}} liste güncellendi",
@@ -649,7 +656,7 @@
"blocklist": "Engel listesi",
"milliseconds_abbreviation": "ms",
"cache_size": "Önbellek boyutu",
"cache_size_desc": "DNS önbellek boyutu (bayt cinsinden). Önbelleğe almayı devre dışı bırakmak için boş bırakın.",
"cache_size_desc": "DNS önbellek boyutu (bayt cinsinden). Önbelleği devre dışı bırakmak için 0 olarak ayarlayın.",
"cache_ttl_min_override": "Minimum kullanım süresini geçersiz kıl",
"cache_ttl_max_override": "Maksimum kullanım süresini geçersiz kıl",
"enter_cache_size": "Önbellek boyutunu girin (bayt)",
@@ -707,8 +714,8 @@
"custom_rotation_input": "Rotasyonu saat cinsinden girin",
"protection_section_label": "Koruma",
"log_and_stats_section_label": "Sorgu günlüğü ve istatistikler",
"ignore_query_log": "Sorgu günlüğünde bu istemciyi yoksay",
"ignore_statistics": "İstatistiklerde bu istemciyi yoksay",
"ignore_query_log": "Sorgu günlüğünde bu istemciyi gösterme",
"ignore_statistics": "İstatistiklerde bu istemciyi gösterme",
"schedule_services": "Hizmet engellemeyi duraklat",
"schedule_services_desc": "Hizmet engelleme filtresinin duraklatma planını yapılandırın",
"schedule_services_desc_client": "Bu istemci için hizmet engelleme filtresinin duraklatma planını yapılandırın",
@@ -742,6 +749,6 @@
"friday_short": "Cum",
"saturday_short": "Cmt",
"upstream_dns_cache_configuration": "Üst kaynak DNS önbellek yapılandırması",
"enable_upstream_dns_cache": "Bu istemcinin özel üst kaynak yapılandırması için DNS önbelleğe almayı etkinleştir",
"enable_upstream_dns_cache": "Bu istemcinin özel üst kaynak yapılandırması için DNS önbelleğini etkinleştir",
"dns_cache_size": "DNS önbellek boyutu, bayt cinsinden"
}

View File

@@ -620,6 +620,10 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "原因:{{reason}}",
"check_service": "服务名称:{{service}}",
"check_hostname": "主机名或域名",
"check_client_id": "客户端标识符ClientID 或 IP 地址)",
"check_enter_client_id": "输入客户端标识符",
"check_dns_record": "选择 DNS 记录类型",
"service_name": "服务名称",
"check_not_found": "未在您的筛选列表中找到",
"client_confirm_block": "确定要阻止客户端 \"{{ip}}\"?",
@@ -652,7 +656,7 @@
"blocklist": "黑名单",
"milliseconds_abbreviation": "毫秒",
"cache_size": "缓存大小",
"cache_size_desc": "DNS 缓存大小(单位:字节)。若要关闭缓存,请留空。",
"cache_size_desc": "DNS 缓存大小(单位:字节)。若要禁用缓存,请设置为 0。",
"cache_ttl_min_override": "覆盖最小 TTL 值",
"cache_ttl_max_override": "覆盖最大 TTL 值",
"enter_cache_size": "输入缓存大小(字节)",

View File

@@ -1,6 +1,6 @@
{
"client_settings": "用戶端設定",
"example_upstream_reserved": "<0>特定網域</0>上游;",
"example_upstream_reserved": "<0>特定網域</0>上游;",
"example_multiple_upstreams_reserved": "<0>特定網域</0>的多個上游伺服器;",
"example_upstream_comment": "註解。",
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。",
@@ -20,7 +20,7 @@
"resolve_clients_title": "啟用用戶端的 IP 位址之反向的解析",
"resolve_clients_desc": "透過傳送指標PTR查詢到對應的解析器私人 DNS 伺服器供區域的用戶端,上游的伺服器供有公共 IP 位址的用戶端),反向地解析用戶端的 IP 位址變為它們的主機名稱。",
"use_private_ptr_resolvers_title": "使用私人反向的 DNS 解析器",
"use_private_ptr_resolvers_desc": "使用私人上游伺服器、DHCP/etc/hosts 等方式解析包含私人 IP 位址的 ARPA 網域的 PTR、SOA NS 請求。如果停用AdGuard Home 將對所有此類請求以 NXDOMAIN 回應。",
"use_private_ptr_resolvers_desc": "透過私有上游伺服器、DHCP/etc/hosts 等管道,解析含有私有 IP 位址的 ARPA 網域的 PTR、SOA NS 請求。若停用此功能AdGuard Home 將以 NXDOMAIN 回應所有相關請求。",
"check_dhcp_servers": "檢查動態主機設定協定DHCP伺服器",
"save_config": "儲存配置",
"enabled_dhcp": "動態主機設定協定DHCP伺服器被啟用",
@@ -112,8 +112,8 @@
"privacy_policy": "隱私政策",
"enable_protection": "啟用防護",
"enabled_protection": "已啟用防護",
"disable_protection": "用防護",
"disabled_protection": "已用防護",
"disable_protection": "用防護",
"disabled_protection": "已用防護",
"refresh_statics": "重新整理統計資料",
"dns_query": "DNS 查詢",
"blocked_by": "<0>被過濾器封鎖</0>",
@@ -124,8 +124,8 @@
"for_last_hours_plural": "在過去的 {{count}} 小時內",
"for_last_days": "在最近的 {{count}} 日內",
"for_last_days_plural": "在最近的 {{count}} 日內",
"stats_disabled": "統計資料已被禁用。您可從<0>設定頁面</0>中打開它。",
"stats_disabled_short": "該統計資料已被禁用",
"stats_disabled": "統計功能目前停用中,請至<0>設定頁面</0>重新開啟。",
"stats_disabled_short": "該統計資料已用",
"no_domains_found": "無已發現之網域",
"requests_count": "請求總數",
"top_blocked_domains": "熱門已封鎖的網域",
@@ -172,13 +172,13 @@
"upstreams": "上游",
"upstream": "上游伺服器",
"apply_btn": "套用",
"disabled_filtering_toast": "已用過濾",
"disabled_filtering_toast": "已用過濾",
"enabled_filtering_toast": "已啟用過濾",
"disabled_safe_browsing_toast": "已用安全瀏覽",
"disabled_safe_browsing_toast": "已用安全瀏覽",
"enabled_safe_browsing_toast": "已啟用安全瀏覽",
"disabled_parental_toast": "已用家長控制",
"disabled_parental_toast": "已用家長控制",
"enabled_parental_toast": "已啟用家長控制",
"disabled_safe_search_toast": "已用安全搜尋",
"disabled_safe_search_toast": "已用安全搜尋",
"enabled_save_search_toast": "已啟用安全搜尋",
"updated_save_search_toast": "安全搜尋設定更新成功",
"enabled_table_header": "已啟用",
@@ -275,7 +275,7 @@
"query_log_retention": "查詢記錄保留時間",
"query_log_enable": "啟用記錄",
"query_log_configuration": "記錄配置",
"query_log_disabled": "查詢記錄被禁用並可在<0>設定</0>中被配置",
"query_log_disabled": "查詢記錄功能已停用,請至「<0>設定</0>」調整",
"query_log_strict_search": "使用雙引號於嚴謹的搜尋",
"query_log_retention_confirm": "您確定要更改記錄檔保存期限嗎?如果您縮短期限部分資料可能將會遺失",
"anonymize_client_ip": "將用戶端 IP 匿名",
@@ -401,7 +401,7 @@
"encryption_config_saved": "加密配置被儲存",
"encryption_server": "伺服器名稱",
"encryption_server_enter": "輸入您的域名",
"encryption_server_desc": "如果設定AdGuard Home 檢測用戶端 IDs回覆 DDR 查詢,並執行額外的連線驗證。如果未設定,這些功能被禁用。必須與在該憑證裡的 DNS 名稱其中之一相符。",
"encryption_server_desc": "如果設定AdGuard Home 會偵測 ClientID、回應 DDR 查詢,並執行其他連線驗證。如果未設定,則會停用這些功能。必須符合憑證中的一個 DNS 名稱。",
"encryption_redirect": "自動地重新導向到 HTTPS",
"encryption_redirect_desc": "如果被勾選AdGuard Home 將自動地重新導向您從 HTTP 到 HTTPS 位址。",
"encryption_https": "HTTPS 連接埠",
@@ -429,8 +429,8 @@
"encryption_reset": "您確定您想要重置加密設定嗎?",
"encryption_warning": "警告",
"encryption_plain_dns_enable": "啟用一般的 DNS",
"encryption_plain_dns_desc": "預設情況下啟用一般 DNS。使用者可以用它強制所有裝置使用一般的 DNS。為此,必須至少啟用一個一般的 DNS 協定",
"encryption_plain_dns_error": "要禁用一般 DNS至少啟用一個一般的 DNS 協定",
"encryption_plain_dns_desc": "預設啟用一般 DNS。可以用它強制所有裝置使用加密 DNS。若要這樣做,您必須啟用至少一個加密 DNS 通訊協定",
"encryption_plain_dns_error": "若要停用一般 DNS啟用至少一個加密 DNS 通訊協定",
"topline_expiring_certificate": "您的安全通訊端層SSL憑證即將到期。更新<0>加密設定</0>。",
"topline_expired_certificate": "您的安全通訊端層SSL憑證為已到期的。更新<0>加密設定</0>。",
"form_error_port_range": "輸入在 80-65535 之範圍內的連接埠號碼",
@@ -572,7 +572,7 @@
"filters_configuration": "過濾器配置",
"filters_enable": "啟用過濾器",
"filters_interval": "過濾器更新間隔",
"disabled": "已用",
"disabled": "已用",
"username_label": "使用者名稱",
"username_placeholder": "輸入使用者名稱",
"password_label": "密碼",
@@ -598,7 +598,7 @@
"rewrite_domain_name": "域名新增一筆正規名稱CNAME記錄",
"rewrite_A": "<0>A</0>:特殊的數值,阻止 <0>A</0> 記錄免於該上游",
"rewrite_AAAA": "<0>AAAA</0>:特殊的數值,阻止 <0>AAAA</0> 記錄免於該上游",
"disable_ipv6": "用 IPv6 位址解析",
"disable_ipv6": "用 IPv6 位址解析",
"disable_ipv6_desc": "停止所有對於 IPv6 位址(類型 AAAA的 DNS 查詢,並從 HTTPS 回應中移除 IPv6 的提示。",
"fastest_addr": "最快的 IP 位址",
"fastest_addr_desc": "等待<b>所有</b> DNS 伺服器的回應,測量每個伺服器的 TCP 連線速度,並返回連線速度最快的伺服器的 IP 位址。<br/>如果一個或多個上游伺服器沒有回應,此模式會顯著減慢 DNS 查詢速度。確保您的上游伺服器穩定且上游超時時間短。",
@@ -620,6 +620,10 @@
"check_cname": "正規名稱CNAME{{cname}}",
"check_reason": "原因:{{reason}}",
"check_service": "服務名稱:{{service}}",
"check_hostname": "主機名稱或域名",
"check_client_id": "用戶端識別碼ClientID 或 IP 位址)",
"check_enter_client_id": "輸入用戶識別碼",
"check_dns_record": "選擇 DNS 記錄類型",
"service_name": "服務名稱",
"check_not_found": "未在您的過濾器清單中被找到",
"client_confirm_block": "您確定您想要封鎖該用戶端 \"{{ip}}\" 嗎?",
@@ -652,7 +656,7 @@
"blocklist": "封鎖清單",
"milliseconds_abbreviation": "ms",
"cache_size": "快取大小",
"cache_size_desc": "DNS 快取大小(位元組)。要禁用快取,留空。",
"cache_size_desc": "DNS 快取大小(位元組)。若要停用快取,請設為 0。",
"cache_ttl_min_override": "覆寫最小的存活時間TTL",
"cache_ttl_max_override": "覆寫最大的存活時間TTL",
"enter_cache_size": "輸入快取大小(位元組)",
@@ -677,13 +681,13 @@
"port_53_faq_link": "連接埠 53 常被 \"DNSStubListener\" 或 \"systemd-resolved\" 服務佔用。請閱讀有關如何解決這個的<0>用法說明</0>。",
"adg_will_drop_dns_queries": "AdGuard Home 將持續排除來自此用戶端之所有的 DNS 查詢。",
"filter_allowlist": "警告:此動作也將把 \"{{disallowed_rule}}\" 規則排除在被允許的用戶端的清單之外。",
"last_rule_in_allowlist": "因為排除 \"{{disallowed_rule}}\" 規則將禁用\"被允許的用戶端\"清單,無法不允許此用戶端。",
"last_rule_in_allowlist": "無法禁止此用戶端,因為排除規則 \"{{disallowed_rule}}\" 會停用「允許的用戶端」清單。",
"use_saved_key": "使用該先前已儲存的金鑰",
"parental_control": "家長控制",
"safe_browsing": "安全瀏覽",
"served_from_cache_label": "從快取中",
"form_error_password_length": "密碼長度必須為 {{min}} 到 {{max}} 個字符",
"anonymizer_notification": "<0>注意:</0>IP 匿名化被啟用。您可在<1>一般設定</1>中禁用它。",
"anonymizer_notification": "<0>注意:</0>IP 匿名功能已開啟。您可在<1>一般設定</1>中關閉。",
"confirm_dns_cache_clear": "您確定您想要清除 DNS 快取嗎?",
"cache_cleared": "DNS 快取被成功地清除",
"clear_cache": "清除快取",
@@ -698,14 +702,14 @@
"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}} 啟用",
"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": "防護",

View File

@@ -9,13 +9,17 @@ import Info from './Info';
import { RootState } from '../../../initialState';
import { validateRequiredValue } from '../../../helpers/validators';
import { Input } from '../../ui/Controls/Input';
import { DNS_RECORD_TYPES } from '../../../helpers/constants';
import { Select } from '../../ui/Controls/Select';
interface FormValues {
export type FilteringCheckFormValues = {
name: string;
client?: string;
qtype?: string;
}
type Props = {
onSubmit?: (data: FormValues) => void;
onSubmit?: (data: FilteringCheckFormValues) => void;
};
const Check = ({ onSubmit }: Props) => {
@@ -27,11 +31,13 @@ const Check = ({ onSubmit }: Props) => {
const {
control,
handleSubmit,
formState: { isDirty, isValid },
} = useForm<FormValues>({
formState: { isValid },
} = useForm<FilteringCheckFormValues>({
mode: 'onBlur',
defaultValues: {
name: '',
client: '',
qtype: DNS_RECORD_TYPES[0],
},
});
@@ -48,24 +54,56 @@ const Check = ({ onSubmit }: Props) => {
<Input
{...field}
type="text"
label={t('check_hostname')}
data-testid="check_domain_name"
placeholder={t('form_enter_host')}
placeholder="example.com"
error={fieldState.error?.message}
rightAddon={
<span className="input-group-append">
<button
className="btn btn-success btn-standard btn-large"
type="submit"
data-testid="check_domain_submit"
disabled={!isDirty || !isValid || processingCheck}>
{t('check')}
</button>
</span>
}
/>
)}
/>
<Controller
name="client"
control={control}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="check_client_id"
label={t('check_client_id')}
placeholder={t('check_enter_client_id')}
error={fieldState.error?.message}
/>
)}
/>
<Controller
name="qtype"
control={control}
render={({ field }) => (
<Select
{...field}
label={t('check_dns_record')}
data-testid="check_dns_record_type"
>
{DNS_RECORD_TYPES.map((type) => (
<option key={type} value={type}>
{type}
</option>
))}
</Select>
)}
/>
<button
className="btn btn-success btn-standard btn-large"
type="submit"
data-testid="check_domain_submit"
disabled={!isValid || processingCheck}
>
{t('check')}
</button>
{hostname && (
<>
<hr />

View File

@@ -7,7 +7,7 @@ import PageTitle from '../ui/PageTitle';
import Examples from './Examples';
import Check from './Check';
import Check, { FilteringCheckFormValues } from './Check';
import { getTextareaCommentsHighlight, syncScroll } from '../../helpers/highlightTextareaComments';
import { COMMENT_LINE_DEFAULT_TOKEN } from '../../helpers/constants';
@@ -48,8 +48,18 @@ class CustomRules extends Component<CustomRulesProps> {
this.props.setRules(this.props.filtering.userRules);
};
handleCheck = (values: any) => {
this.props.checkHost(values);
handleCheck = (values: FilteringCheckFormValues) => {
const params: FilteringCheckFormValues = { name: values.name };
if (values.client) {
params.client = values.client;
}
if (values.qtype) {
params.qtype = values.qtype;
}
this.props.checkHost(params);
};
onScroll = (e: any) => syncScroll(e, this.ref);
@@ -68,6 +78,7 @@ class CustomRules extends Component<CustomRulesProps> {
<form onSubmit={this.handleSubmit}>
<div className="text-edit-container mb-4">
<textarea
data-testid="custom_rule_textarea"
className="form-control font-monospace text-input"
value={userRules}
onChange={this.handleChange}
@@ -81,6 +92,7 @@ class CustomRules extends Component<CustomRulesProps> {
<div className="card-actions">
<button
data-testid="apply_custom_rule"
className="btn btn-success btn-standard btn-large"
type="submit"
onClick={this.handleSubmit}>

View File

@@ -59,7 +59,7 @@ const Header = () => {
<div className="header__column">
<div className="header__right">
{!processingProfile && name && (
<a href="control/logout" className="btn btn-sm btn-outline-secondary">
<a href="control/logout" className="btn btn-sm btn-outline-secondary" data-testid="sign_out">
{t('sign_out')}
</a>
)}

View File

@@ -288,7 +288,7 @@ const Row = memo(
);
return (
<div style={style} className={className} onClick={onClick} role="row">
<div style={style} className={className} onClick={onClick} role="row" data-testid="querylog_cell">
<DateCell {...rowProps} />
<DomainCell {...rowProps} />

View File

@@ -6,6 +6,8 @@ import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import classNames from 'classnames';
import { useFormContext } from 'react-hook-form';
import queryString from 'query-string';
import {
DEBOUNCE_FILTER_TIMEOUT,
DEFAULT_LOGS_FILTER,
@@ -54,9 +56,17 @@ export const Form = ({ className, setIsLoading }: Props) => {
}
}, [responseStatusValue, setValue]);
useEffect(() => {
const { search: searchUrlParam } = queryString.parse(history.location.search);
if (searchUrlParam !== searchValue) {
setValue('search', searchUrlParam ? searchUrlParam.toString() : '');
}
}, [history.location.search]);
const onInputClear = async () => {
setIsLoading(true);
setValue('search', DEFAULT_LOGS_FILTER.search);
history.push(getLogsUrlParams(DEFAULT_LOGS_FILTER.search, responseStatusValue));
setIsLoading(false);
};
@@ -74,6 +84,7 @@ export const Form = ({ className, setIsLoading }: Props) => {
}}>
<div className="field__search">
<SearchField
data-testid="querylog_search"
value={searchValue}
handleChange={(val) => setValue('search', val)}
onKeyDown={onEnterPress}

View File

@@ -314,7 +314,7 @@ const ClientsTable = ({
}
if (!content) {
return content;
}
}
return <LogsSearchLink search={row.original.name}>{content}</LogsSearchLink>;
},
},

View File

@@ -27,12 +27,14 @@ const SETTINGS = {
enabled: false,
title: i18next.t('use_adguard_browsing_sec'),
subtitle: i18next.t('use_adguard_browsing_sec_hint'),
testId: 'safebrowsing',
[ORDER_KEY]: 0,
},
parental: {
enabled: false,
title: i18next.t('use_adguard_parental'),
subtitle: i18next.t('use_adguard_parental_hint'),
testId: 'parental',
[ORDER_KEY]: 1,
},
};
@@ -90,11 +92,12 @@ class Settings extends Component<SettingsProps> {
renderSettings = (settings: any) =>
getObjectKeysSorted(SETTINGS, ORDER_KEY).map((key: any) => {
const setting = settings[key];
const { enabled, title, subtitle } = setting;
const { enabled, title, subtitle, testId } = setting;
return (
<div key={key} className="form__group form__group--checkbox">
<Checkbox
data-testid={testId}
value={enabled}
title={title}
subtitle={subtitle}
@@ -118,6 +121,7 @@ class Settings extends Component<SettingsProps> {
<>
<div className="form__group form__group--checkbox">
<Checkbox
data-testid="safesearch"
value={enabled}
title={i18next.t('enforce_safe_search')}
subtitle={i18next.t('enforce_save_search_hint')}

View File

@@ -94,14 +94,17 @@ const Footer = () => {
auto: {
desc: t('theme_auto_desc'),
icon: '#auto',
testId: 'theme_auto',
},
dark: {
desc: t('theme_dark_desc'),
icon: '#dark',
testId: 'theme_dark',
},
light: {
desc: t('theme_light_desc'),
icon: '#light',
testId: 'theme_light',
},
};
@@ -113,7 +116,9 @@ const Footer = () => {
type="button"
className="btn btn-sm btn-secondary footer__theme-button"
onClick={() => onThemeChange(theme)}
title={content[theme].desc}>
title={content[theme].desc}
data-testid={content[theme].testId}
>
<svg className={cn('footer__theme-icon', { 'footer__theme-icon--active': currentValue === theme })}>
<use xlinkHref={content[theme].icon} />
</svg>

View File

@@ -523,3 +523,12 @@ export const TIME_UNITS = {
HOURS: 'hours',
DAYS: 'days',
};
export const DNS_RECORD_TYPES = [
"A", "AAAA", "AFSDB", "APL", "CAA", "CDNSKEY", "CDS", "CERT", "CNAME",
"CSYNC", "DHCID", "DLV", "DNAME", "DNSKEY", "DS", "EUI48", "EUI64",
"HINFO", "HIP", "HTTPS", "IPSECKEY", "KEY", "KX", "LOC", "MX", "NAPTR",
"NS", "NSEC", "NSEC3", "NSEC3PARAM", "OPENPGPKEY", "PTR", "RP", "RRSIG",
"SIG", "SMIMEA", "SOA", "SRV", "SSHFP", "SVCB", "TA", "TKEY",
"TLSA", "TSIG", "TXT", "URI", "ZONEMD"
];

View File

@@ -28,11 +28,11 @@ export default {
"homepage": "https://badmojr.github.io/1Hosts/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_24.txt"
},
"1hosts_mini": {
"name": "1Hosts (mini)",
"1hosts_pro": {
"name": "1Hosts (Pro)",
"categoryId": "general",
"homepage": "https://badmojr.github.io/1Hosts/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_38.txt"
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_64.txt"
},
"CHN_adrules": {
"name": "CHN: AdRules DNS List",

View File

@@ -669,15 +669,17 @@ export const countClientsStatistics = (ids: any, autoClients: any) => {
* @returns {string}
*/
export const formatElapsedMs = (elapsedMs: string, t: (key: string) => string) => {
const parsedElapsedMs = parseInt(elapsedMs, 10);
const parsedElapsedMs = parseFloat(elapsedMs);
if (Number.isNaN(parsedElapsedMs)) {
return elapsedMs;
}
const formattedMs = formatNumber(parsedElapsedMs);
const formattedValue = parsedElapsedMs < 1
? parsedElapsedMs.toFixed(2)
: Math.floor(parsedElapsedMs).toString();
return `${formattedMs} ${t('milliseconds_abbreviation')}`;
return `${formattedValue} ${t('milliseconds_abbreviation')}`;
};
/**

View File

@@ -1,5 +1,5 @@
{
"timeUpdated": "2025-01-13T10:04:54.031Z",
"timeUpdated": "2025-03-17T10:05:02.622Z",
"categories": {
"0": "audio_video_player",
"1": "comments",
@@ -5940,7 +5940,8 @@
"name": "Digioh",
"categoryId": 4,
"url": "https://digioh.com/",
"companyId": null
"companyId": "digioh",
"source": "AdGuard"
},
"digital.gov": {
"name": "Digital.gov",
@@ -8261,8 +8262,8 @@
},
"google_marketing": {
"name": "Google Marketing",
"categoryId": 6,
"url": "https://marketingplatform.google.com/",
"categoryId": 4,
"url": "https://marketingplatform.google.com/about/enterprise",
"companyId": "google",
"source": "AdGuard"
},
@@ -9058,6 +9059,13 @@
"url": "https://www.ippen-digital.de/",
"companyId": null
},
"id5-sync": {
"name": "ID5 Sync",
"categoryId": 4,
"url": "https://id5.io/",
"companyId": "id5-sync",
"source": "AdGuard"
},
"id_services": {
"name": "ID Services",
"categoryId": 6,
@@ -20948,6 +20956,9 @@
"wunderloop.net": "audience_science",
"12mlbe.com": "audiencerate",
"audiencesquare.com": "audiencesquare.com",
"ad.gt": "audiencesquare.com",
"audigent.com": "audiencesquare.com",
"hadronid.net": "audiencesquare.com",
"auditude.com": "auditude",
"audtd.com": "audtd.com",
"cdn.augur.io": "augur",
@@ -21403,6 +21414,7 @@
"static.clmbtech.com": "columbia_online",
"combotag.com": "combotag",
"pdk.theplatform.com": "comcast_technology_solutions",
"theplatform.com": "comcast_technology_solutions",
"comm100.cn": "comm100",
"comm100.com": "comm100",
"cdn-cs.com": "commerce_sciences",
@@ -21682,9 +21694,6 @@
"dtmpub.com": "dotomi",
"double.net": "double.net",
"2mdn.net": "doubleclick",
"doubleclick.net": "doubleclick",
"invitemedia.com": "doubleclick",
"doubleclick.com": "doubleclick",
"doublepimp.com": "doublepimp",
"doublepimpssl.com": "doublepimp",
"redcourtside.com": "doublepimp",
@@ -21988,6 +21997,7 @@
"cdn.foxpush.net": "foxpush",
"foxpush.com": "foxpush",
"foxtel.com.au": "foxtel",
"foxtelgroupcdn.net.au": "foxtel",
"foxydeal.com": "foxydeal_com",
"yabidos.com": "fraudlogix",
"besucherstatistiken.com": "free_counter",
@@ -22389,6 +22399,8 @@
"maps.google.es": "google_maps",
"maps.google.se": "google_maps",
"maps.gstatic.com": "google_maps",
"doubleclick.net": "google_marketing",
"invitemedia.com": "google_marketing",
"adsense.google.com": "google_marketing",
"adservice.google.ca": "google_marketing",
"adservice.google.co.in": "google_marketing",
@@ -22419,6 +22431,7 @@
"adservice.google.vg": "google_marketing",
"adtrafficquality.google": "google_marketing",
"dai.google.com": "google_marketing",
"doubleclick.com": "google_marketing",
"doubleclickbygoogle.com": "google_marketing",
"googlesyndication-cn.com": "google_marketing",
"duo.google.com": "google_meet",
@@ -22604,6 +22617,9 @@
"icuazeczpeoohx.com": "icuazeczpeoohx.com",
"id-news.net": "id-news.net",
"idcdn.de": "id-news.net",
"eu-1-id5-sync.com": "id5-sync",
"id5-sync.com": "id5-sync",
"id5.io": "id5-sync",
"cdn.id.services": "id_services",
"e-generator.com": "ideal_media",
"idealo.com": "idealo_com",
@@ -23656,6 +23672,7 @@
"blockmetrics.com": "pagefair",
"pagefair.com": "pagefair",
"pagefair.net": "pagefair",
"btloader.com": "pagefair",
"ghmedia.com": "pagescience",
"777seo.com": "paid-to-promote",
"paid-to-promote.net": "paid-to-promote",

View File

@@ -21,7 +21,7 @@ const Form = ({ onSubmit, processing }: LoginFormProps) => {
control,
formState: { isValid },
} = useForm<LoginFormValues>({
mode: 'onBlur',
mode: 'onChange',
defaultValues: {
username: '',
password: '',
@@ -62,7 +62,7 @@ const Form = ({ onSubmit, processing }: LoginFormProps) => {
{...field}
data-testid="password"
type="password"
label={t('username_label')}
label={t('password_label')}
placeholder={t('password_placeholder')}
error={fieldState.error?.message}
autoComplete="current-password"

View File

@@ -0,0 +1,34 @@
import { test, expect } from '@playwright/test';
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
test.describe('Control Panel', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login.html');
await page.getByTestId('username').click();
await page.getByTestId('username').fill(ADMIN_USERNAME);
await page.getByTestId('password').click();
await page.getByTestId('password').fill(ADMIN_PASSWORD);
await page.keyboard.press('Tab');
await page.getByTestId('sign_in').click();
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
});
test('should sign out successfully', async ({ page }) => {
await page.getByTestId('sign_out').click();
await page.waitForURL((url) => url.href.endsWith('/login.html'));
await expect(page.getByTestId('sign_in')).toBeVisible();
});
test('should change theme to dark and then light', async ({ page }) => {
await page.getByTestId('theme_dark').click();
await expect(page.locator('body[data-theme="dark"]')).toBeVisible();
await page.getByTestId('theme_light').click();
await expect(page.locator('body:not([data-theme="dark"])')).toBeVisible();
});
});

View File

@@ -0,0 +1,52 @@
import { test, expect, type Page } from '@playwright/test';
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
test.describe('DNS Settings', () => {
test.beforeEach(async ({ page }) => {
// Login before each test
await page.goto('/login.html');
await page.getByTestId('username').click();
await page.getByTestId('username').fill(ADMIN_USERNAME);
await page.getByTestId('password').click();
await page.getByTestId('password').fill(ADMIN_PASSWORD);
await page.keyboard.press('Tab');
await page.getByTestId('sign_in').click();
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
});
const runDNSSettingsTest = async (page: Page, address: string) => {
await page.goto('/#dns');
const currentDns = await page.getByTestId('upstream_dns').inputValue();
await page.getByTestId('upstream_dns').fill(address);
await page.getByTestId('dns_upstream_test').click();
await page.waitForTimeout(2000);
await expect(page.getByTestId('upstream_dns')).toHaveValue(address);
await page.getByTestId('upstream_dns').fill(currentDns);
await page.getByTestId('dns_upstream_save').click({ force: true });
};
test('test for Default DNS', async ({ page }) => {
await runDNSSettingsTest(page, 'https://dns10.quad9.net/dns-query');
});
test('test for Plain DNS', async ({ page }) => {
await runDNSSettingsTest(page, '94.140.14.140');
});
test('test for DNS-over-HTTPS', async ({ page }) => {
await runDNSSettingsTest(page, 'https://unfiltered.adguard-dns.com/dns-query');
});
test('test for DNS-over-TLS', async ({ page }) => {
await runDNSSettingsTest(page, 'tls://unfiltered.adguard-dns.com');
});
test('test for DNS-over-QUIC', async ({ page }) => {
await runDNSSettingsTest(page, 'quic://unfiltered.adguard-dns.com');
});
});

View File

@@ -0,0 +1,73 @@
import { test, expect, type Page } from '@playwright/test';
import { execSync } from 'child_process';
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
test.describe('Filtering', () => {
test.beforeEach(async ({ page }) => {
// Login before each test
await page.goto('/login.html');
await page.getByTestId('username').click();
await page.getByTestId('username').fill(ADMIN_USERNAME);
await page.getByTestId('password').click();
await page.getByTestId('password').fill(ADMIN_PASSWORD);
await page.keyboard.press('Tab');
await page.getByTestId('sign_in').click();
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
});
const runTerminalCommand = (command: string) => {
try {
console.info(`Executing command: ${command}`);
const output = execSync(command, { encoding: 'utf-8', stdio: 'pipe' }).trim();
console.info('Command executed successfully.');
console.debug(`Command output:\n${output}`);
return output;
} catch (error: any) {
console.error(`Command execution failed with error:\n${error.message}`);
throw new Error(`Failed to execute command: ${command}\nError: ${error.message}`);
}
}
const runCustomRuleTest = async (page: Page, domain_to_block: string) => {
await page.goto('/#custom_rules');
await page.getByTestId('custom_rule_textarea').fill(domain_to_block);
await page.getByTestId('apply_custom_rule').click();
const nslookupBlockedResult = await runTerminalCommand(`nslookup ${domain_to_block} 127.0.0.1`).toString();
console.info(`nslookup blocked CNAME result: '${nslookupBlockedResult}'`);
const currentRules = await page.getByTestId('custom_rule_textarea').inputValue();
console.debug(`Current rules before removal:\n${currentRules}`);
if (currentRules.includes(domain_to_block)) {
const updatedRules = currentRules
.split('\n')
.filter((line) => line.trim() !== domain_to_block.trim())
.join('\n');
await page.getByTestId('custom_rule_textarea').fill(updatedRules);
console.info(`Rule '${domain_to_block}' removed successfully.`);
console.info('Applying the updated filtering rules after removal.');
await page.getByTestId('apply_custom_rule').click();
await page.waitForLoadState('domcontentloaded');
console.info(`Filtering rules successfully updated after removing '${domain_to_block}'.`);
} else {
console.warn(`Rule '${domain_to_block}' not found. No changes were made.`);
}
const nslookupUnblockedResult = await runTerminalCommand(`nslookup ${domain_to_block} 127.0.0.1`).toString();
console.info(`nslookup unblocked CNAME result: '${nslookupUnblockedResult}'`);
};
test('Test blocking rule for apple.com', async ({ page }) => {
await runCustomRuleTest(page, 'apple.com');
});
});

View File

@@ -0,0 +1,89 @@
import { test, expect } from '@playwright/test';
import { execSync } from 'child_process';
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
test.describe('General Settings', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login.html');
await page.getByTestId('username').click();
await page.getByTestId('username').fill(ADMIN_USERNAME);
await page.getByTestId('password').click();
await page.getByTestId('password').fill(ADMIN_PASSWORD);
await page.keyboard.press('Tab');
await page.getByTestId('sign_in').click();
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
});
test('should toggle browsing security feature and verify DNS changes', async ({ page }) => {
await page.goto('/#settings');
const browsingSecurity = await page.getByTestId('safebrowsing');
const browsingSecurityLabel = await browsingSecurity.locator('xpath=following-sibling::*[1]');
const initialState = await browsingSecurity.isChecked();
if (!initialState) {
await browsingSecurityLabel.click();
await expect(browsingSecurity).toBeChecked();
}
const resultEnabled = execSync('nslookup totalvirus.com 127.0.0.1').toString();
await browsingSecurityLabel.click();
await expect(browsingSecurity).not.toBeChecked();
const resultDisabled = execSync('nslookup totalvirus.com 127.0.0.1').toString();
expect(resultEnabled).not.toEqual(resultDisabled);
if (initialState) {
await browsingSecurityLabel.click();
await expect(browsingSecurity).toBeChecked();
}
});
test('should toggle parental control feature and verify DNS changes', async ({ page }) => {
await page.goto('/#settings');
const parentalControl = page.getByTestId('parental');
const parentalControlLabel = await parentalControl.locator('xpath=following-sibling::*[1]');
const initialState = await parentalControl.isChecked();
if (!initialState) {
await parentalControlLabel.click();
await expect(parentalControl).toBeChecked();
}
const resultEnabled = execSync('nslookup pornhub.com 127.0.0.1').toString();
await parentalControlLabel.click();
await expect(parentalControl).not.toBeChecked();
const resultDisabled = execSync('nslookup pornhub.com 127.0.0.1').toString();
expect(resultEnabled).not.toEqual(resultDisabled);
if (initialState) {
await parentalControlLabel.click();
await expect(parentalControl).toBeChecked();
}
});
test('should toggle safe search feature', async ({ page }) => {
await page.goto('/#settings');
const safeSearch = page.getByTestId('safesearch');
const safeSearchLabel = await safeSearch.locator('xpath=following-sibling::*[1]');
const initialState = await safeSearch.isChecked();
await safeSearchLabel.click();
await expect(safeSearch).not.toBeChecked({ checked: initialState });
await safeSearchLabel.click();
await expect(safeSearch).toBeChecked({ checked: initialState });
});
});

View File

@@ -0,0 +1,124 @@
import { test, expect } from '@playwright/test';
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
test.describe('QueryLog', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login.html');
await page.getByTestId('username').click();
await page.getByTestId('username').fill(ADMIN_USERNAME);
await page.getByTestId('password').click();
await page.getByTestId('password').fill(ADMIN_PASSWORD);
await page.keyboard.press('Tab');
await page.getByTestId('sign_in').click();
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
});
test('Search of queryLog should work correctly', async ({ page }) => {
await page.route('/control/querylog', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(
{
"data": [
{
"answer": [
{
"type": "A",
"value": "77.88.44.242",
"ttl": 294
},
{
"type": "A",
"value": "5.255.255.242",
"ttl": 294
},
{
"type": "A",
"value": "77.88.55.242",
"ttl": 294
}
],
"answer_dnssec": false,
"cached": false,
"client": "127.0.0.1",
"client_info": {
"whois": {},
"name": "localhost",
"disallowed_rule": "127.0.0.1",
"disallowed": false
},
"client_proto": "",
"elapsedMs": "78.163167",
"question": {
"class": "IN",
"name": "ya.ru",
"type": "A"
},
"reason": "NotFilteredNotFound",
"rules": [],
"status": "NOERROR",
"time": "2024-07-17T16:02:37.500662+02:00",
"upstream": "https://dns10.quad9.net:443/dns-query"
},
{
"answer": [
{
"type": "A",
"value": "77.88.55.242",
"ttl": 351
},
{
"type": "A",
"value": "77.88.44.242",
"ttl": 351
},
{
"type": "A",
"value": "5.255.255.242",
"ttl": 351
}
],
"answer_dnssec": false,
"cached": false,
"client": "127.0.0.1",
"client_info": {
"whois": {},
"name": "localhost",
"disallowed_rule": "127.0.0.1",
"disallowed": false
},
"client_proto": "",
"elapsedMs": "5051.070708",
"question": {
"class": "IN",
"name": "ya.ru",
"type": "A"
},
"reason": "NotFilteredNotFound",
"rules": [],
"status": "NOERROR",
"time": "2024-07-17T16:02:37.4983+02:00",
"upstream": "https://dns10.quad9.net:443/dns-query"
}
],
"oldest": "2024-07-17T16:02:37.4983+02:00"
}
),
});
});
await page.goto('/#logs');
await page.getByTestId('querylog_search').fill('127.0.0.1');
const [request] = await Promise.all([
page.waitForRequest((req) => req.url().includes('/control/querylog')),
]);
if (request) {
expect(request.url()).toContain('search=127.0.0.1');
expect(await page.getByTestId('querylog_cell').first().isVisible()).toBe(true);
}
});
});

View File

@@ -1,12 +1,12 @@
# A docker file for scripts/make/build-docker.sh.
FROM alpine:3.18
FROM alpine:3.21
ARG BUILD_DATE
ARG VERSION
ARG VCS_REF
LABEL\
LABEL \
maintainer="AdGuard Team <devteam@adguard.com>" \
org.opencontainers.image.authors="AdGuard Team <devteam@adguard.com>" \
org.opencontainers.image.created=$BUILD_DATE \
@@ -30,8 +30,8 @@ ARG TARGETARCH
ARG TARGETOS
ARG TARGETVARIANT
COPY --chown=nobody:nogroup\
./${DIST_DIR}/docker/AdGuardHome_${TARGETOS}_${TARGETARCH}_${TARGETVARIANT}\
COPY --chown=nobody:nogroup \
./${DIST_DIR}/docker/AdGuardHome_${TARGETOS}_${TARGETARCH}_${TARGETVARIANT} \
/opt/adguardhome/AdGuardHome
RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
@@ -45,8 +45,15 @@ RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
# 3000 : TCP, UDP : HTTP(S) (alt, incl. HTTP/3)
# 5443 : TCP, UDP : DNSCrypt (alt)
# 6060 : TCP : HTTP (pprof)
EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 443/udp 853/tcp\
853/udp 3000/tcp 3000/udp 5443/tcp 5443/udp 6060/tcp
EXPOSE 53/tcp 53/udp \
67/udp \
68/udp \
80/tcp \
443/tcp 443/udp \
853/tcp 853/udp \
3000/tcp 3000/udp \
5443/tcp 5443/udp \
6060/tcp
WORKDIR /opt/adguardhome/work

108
go.mod
View File

@@ -1,21 +1,21 @@
module github.com/AdguardTeam/AdGuardHome
go 1.23.6
go 1.24.2
require (
github.com/AdguardTeam/dnsproxy v0.75.0
github.com/AdguardTeam/golibs v0.32.1
github.com/AdguardTeam/dnsproxy v0.75.5
github.com/AdguardTeam/golibs v0.32.9
github.com/AdguardTeam/urlfilter v0.20.0
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.3.0
github.com/ameshkov/dnscrypt/v2 v2.4.0
github.com/bluele/gcache v0.0.2
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
github.com/digineo/go-ipset/v2 v2.2.1
github.com/fsnotify/fsnotify v1.8.0
github.com/fsnotify/fsnotify v1.9.0
// TODO(e.burkov): This package is deprecated; find a new one or use our
// own code for that. Perhaps, use gopacket.
github.com/go-ping/ping v1.2.0
github.com/google/go-cmp v0.6.0
github.com/google/go-cmp v0.7.0
github.com/google/gopacket v1.1.19
github.com/google/renameio/v2 v2.0.0
github.com/google/uuid v1.6.0
@@ -28,41 +28,105 @@ require (
// 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.63
github.com/quic-go/quic-go v0.49.0
github.com/miekg/dns v1.1.65
github.com/quic-go/quic-go v0.50.1
github.com/stretchr/testify v1.10.0
github.com/ti-mo/netfilter v0.5.2
go.etcd.io/bbolt v1.4.0
golang.org/x/crypto v0.32.0
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3
golang.org/x/net v0.34.0
golang.org/x/sys v0.30.0
golang.org/x/crypto v0.37.0
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
golang.org/x/net v0.39.0
golang.org/x/sys v0.33.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.1
)
require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
cloud.google.com/go v0.120.1 // indirect
cloud.google.com/go/ai v0.10.2 // indirect
cloud.google.com/go/auth v0.16.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/longrunning v0.6.7 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/ameshkov/dnsstamps v1.0.3 // indirect
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 // indirect
github.com/golangci/misspell v0.6.0 // indirect
github.com/google/generative-ai-go v0.19.0 // indirect
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/gordonklaus/ineffassign v0.1.0 // indirect
github.com/jstemmer/go-junit-report/v2 v2.1.0 // indirect
github.com/kisielk/errcheck v1.9.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/securego/gosec/v2 v2.22.3 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.29.0 // indirect
gonum.org/v1/gonum v0.15.1 // indirect
github.com/uudashr/gocognit v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/mock v0.5.2 // indirect
golang.org/x/exp/typeparams v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 // indirect
golang.org/x/term v0.31.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.32.0 // indirect
golang.org/x/vuln v1.1.4 // indirect
gonum.org/v1/gonum v0.16.0 // indirect
google.golang.org/api v0.229.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect
google.golang.org/grpc v1.71.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
honnef.co/go/tools v0.6.1 // indirect
mvdan.cc/editorconfig v0.3.0 // indirect
mvdan.cc/gofumpt v0.8.0 // indirect
mvdan.cc/sh/v3 v3.11.0 // indirect
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect
)
tool (
github.com/fzipp/gocyclo/cmd/gocyclo
github.com/golangci/misspell/cmd/misspell
github.com/gordonklaus/ineffassign
github.com/jstemmer/go-junit-report/v2
github.com/kisielk/errcheck
github.com/securego/gosec/v2/cmd/gosec
github.com/uudashr/gocognit/cmd/gocognit
golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment
golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness
golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
golang.org/x/vuln/cmd/govulncheck
honnef.co/go/tools/cmd/staticcheck
mvdan.cc/gofumpt
mvdan.cc/sh/v3/cmd/shfmt
mvdan.cc/unparam
)

215
go.sum
View File

@@ -1,17 +1,27 @@
github.com/AdguardTeam/dnsproxy v0.75.0 h1:v8/Oq/xPYzNoALR7SEUZEIbKmjnPcXLVhJLFVbrozEc=
github.com/AdguardTeam/dnsproxy v0.75.0/go.mod h1:O2qoXwF4BUBFui7OMUiWSYwapEDcYxKWeur4+jfy9nM=
github.com/AdguardTeam/golibs v0.32.1 h1:Ajf6Q0k+A9zjFbj8HOzNAbHImrV4JtbT0vwy02D6VeI=
github.com/AdguardTeam/golibs v0.32.1/go.mod h1:dXRLSsnJQDxOfQVl0ochy1bfk4NgnJQGQdR1YPJdwcw=
cloud.google.com/go v0.120.1 h1:Z+5V7yd383+9617XDCyszmK5E4wJRJL+tquMfDj9hLM=
cloud.google.com/go v0.120.1/go.mod h1:56Vs7sf/i2jYM6ZL9NYlC82r04PThNcPS5YgFmb0rp8=
cloud.google.com/go/ai v0.10.2 h1:5NHzmZlRs+3kvlsVdjT0cTnLrjQdROJ/8VOljVfs+8o=
cloud.google.com/go/ai v0.10.2/go.mod h1:xZuZuE9d3RgsR132meCnPadiU9XV0qXjpLr+P4J46eE=
cloud.google.com/go/auth v0.16.0 h1:Pd8P1s9WkcrBE2n/PhAwKsdrR35V3Sg2II9B+ndM3CU=
cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
github.com/AdguardTeam/dnsproxy v0.75.5 h1:/P7+Ku4bjl+sVC/FW3PbT7pabgCjKTcrAOHqsZe2e60=
github.com/AdguardTeam/dnsproxy v0.75.5/go.mod h1:fdwtHhrDkTueDagDCasYKZbXdppkkBXW7RGPBNH+pis=
github.com/AdguardTeam/golibs v0.32.9 h1:/6luT0aMOn05/s9eh1yA4lbcHgl0d1iEEvEBbIMMUk0=
github.com/AdguardTeam/golibs v0.32.9/go.mod h1:McV1QFFlKLElKa306V4OL/T2kr7564PhsayfvTWYBVs=
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
github.com/ameshkov/dnscrypt/v2 v2.3.0 h1:pDXDF7eFa6Lw+04C0hoMh8kCAQM8NwUdFEllSP2zNLs=
github.com/ameshkov/dnscrypt/v2 v2.3.0/go.mod h1:N5hDwgx2cNb4Ay7AhvOSKst+eUiOZ/vbKRO9qMpQttE=
github.com/ameshkov/dnscrypt/v2 v2.4.0 h1:if6ZG2cuQmcP2TwSY+D0+8+xbPfoatufGlOQTMNkI9o=
github.com/ameshkov/dnscrypt/v2 v2.4.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI=
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM=
@@ -20,35 +30,67 @@ github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4=
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=
github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das=
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1MX77g=
github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
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.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ping/ping v1.2.0 h1:vsJ8slZBZAXNCK4dPcI2PEE9eM9n9RbXbGouVQ/Y4yQ=
github.com/go-ping/ping v1.2.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs=
github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo=
github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg=
github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
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-20250202011525-fc3143867406 h1:wlQI2cYY0BsWmmPPAnxfQ8SDW0S3Jasn+4B8kXFxprg=
github.com/google/pprof v0.0.0-20250202011525-fc3143867406/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4=
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s=
github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
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-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
@@ -57,8 +99,14 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
github.com/josharian/native v1.0.0/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/jstemmer/go-junit-report/v2 v2.1.0 h1:X3+hPYlSczH9IMIpSC9CQSZA0L+BipYafciZUWHEmsc=
github.com/jstemmer/go-junit-report/v2 v2.1.0/go.mod h1:mgHVr7VUo5Tn8OLVr1cKnLuEy0M92wdRntM99h7RkgQ=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M=
github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
@@ -76,14 +124,12 @@ github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
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.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
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.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
@@ -95,12 +141,18 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q=
github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/securego/gosec/v2 v2.22.3 h1:mRrCNmRF2NgZp4RJ8oJ6yPJ7G4x6OCiAXHd8x4trLRc=
github.com/securego/gosec/v2 v2.22.3/go.mod h1:42M9Xs0v1WseinaB/BmNGO8AVqG8vRfhC2686ACY48k=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
@@ -120,34 +172,63 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=
github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
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.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/exp/typeparams v0.0.0-20250408133849-7e4ce0ab07d0 h1:oMe07YcizemJ09rs2kRkFYAp0pt4e1lYLwPWiEGMpXE=
golang.org/x/exp/typeparams v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ=
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.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
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.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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=
@@ -155,35 +236,65 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 h1:RXY2+rSHXvxO2Y+gKrPjYVaEoGOqh3VEXFhnWAt1Irg=
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3/go.mod h1:RoaXAWDwS90j6FxVKwJdBV+0HCU+llrKUGgJaxiKl6M=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
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=
gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0=
gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o=
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.229.0 h1:p98ymMtqeJ5i3lIBMj5MpR9kzIIgzpHHh8vQ+vgAzx8=
google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0=
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e h1:UdXH7Kzbj+Vzastr5nVfccbmFsmYNygVLSPk1pEfDoY=
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
mvdan.cc/editorconfig v0.3.0 h1:D1D2wLYEYGpawWT5SpM5pRivgEgXjtEXwC9MWhEY0gQ=
mvdan.cc/editorconfig v0.3.0/go.mod h1:NcJHuDtNOTEJ6251indKiWuzK6+VcrMuLzGMLKBFupQ=
mvdan.cc/gofumpt v0.8.0 h1:nZUCeC2ViFaerTcYKstMmfysj6uhQrA2vJe+2vwGU6k=
mvdan.cc/gofumpt v0.8.0/go.mod h1:vEYnSzyGPmjvFkqJWtXkh79UwPWP9/HMxQdGEXZHjpg=
mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8=
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE=

View File

@@ -1,14 +1,15 @@
package aghalg
package aghalg_test
import (
"strings"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/stretchr/testify/assert"
)
func TestNewSortedMap(t *testing.T) {
var m SortedMap[string, int]
var m aghalg.SortedMap[string, int]
letters := []string{}
for i := range 10 {
@@ -17,7 +18,7 @@ func TestNewSortedMap(t *testing.T) {
}
t.Run("create_and_fill", func(t *testing.T) {
m = NewSortedMap[string, int](strings.Compare)
m = aghalg.NewSortedMap[string, int](strings.Compare)
nums := []int{}
for i, r := range letters {
@@ -68,7 +69,7 @@ func TestNewSortedMap_nil(t *testing.T) {
val = "val"
)
var m SortedMap[string, string]
var m aghalg.SortedMap[string, string]
assert.Panics(t, func() {
m.Set(key, val)

View File

@@ -1,9 +1,10 @@
package aghnet
package aghnet_test
import (
"net/netip"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/stretchr/testify/assert"
)
@@ -29,13 +30,13 @@ func TestGenerateHostName(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
hostname := GenerateHostname(tc.ip)
hostname := aghnet.GenerateHostname(tc.ip)
assert.Equal(t, tc.want, hostname)
})
}
})
t.Run("invalid", func(t *testing.T) {
assert.Panics(t, func() { GenerateHostname(netip.Addr{}) })
assert.Panics(t, func() { aghnet.GenerateHostname(netip.Addr{}) })
})
}

View File

@@ -1,22 +1,24 @@
package aghnet
package aghnet_test
import (
"net"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// fakeIface is a stub implementation of aghnet.NetIface to simplify testing.
// fakeIface is a stub implementation of [aghnet.NetIface] interface to simplify
// testing.
type fakeIface struct {
err error
addrs []net.Addr
}
// Addrs implements the NetIface interface for *fakeIface.
// Addrs implements the [aghnet.NetIface] interface for *fakeIface.
func (iface *fakeIface) Addrs() (addrs []net.Addr, err error) {
if iface.err != nil {
return nil, iface.err
@@ -25,6 +27,9 @@ func (iface *fakeIface) Addrs() (addrs []net.Addr, err error) {
return iface.addrs, nil
}
// type check
var _ aghnet.NetIface = (*fakeIface)(nil)
func TestIfaceIPAddrs(t *testing.T) {
const errTest errors.Error = "test error"
@@ -35,76 +40,76 @@ func TestIfaceIPAddrs(t *testing.T) {
addr6 := &net.IPNet{IP: ip6}
testCases := []struct {
iface NetIface
iface aghnet.NetIface
name string
wantErrMsg string
want []net.IP
ipv IPVersion
ipv aghnet.IPVersion
}{{
iface: &fakeIface{addrs: []net.Addr{addr4}, err: nil},
name: "ipv4_success",
wantErrMsg: "",
want: []net.IP{ip4},
ipv: IPVersion4,
ipv: aghnet.IPVersion4,
}, {
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
name: "ipv4_success_with_ipv6",
wantErrMsg: "",
want: []net.IP{ip4},
ipv: IPVersion4,
ipv: aghnet.IPVersion4,
}, {
iface: &fakeIface{addrs: []net.Addr{addr4}, err: errTest},
name: "ipv4_error",
wantErrMsg: errTest.Error(),
want: nil,
ipv: IPVersion4,
ipv: aghnet.IPVersion4,
}, {
iface: &fakeIface{addrs: []net.Addr{addr6}, err: nil},
name: "ipv6_success",
wantErrMsg: "",
want: []net.IP{ip6},
ipv: IPVersion6,
ipv: aghnet.IPVersion6,
}, {
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
name: "ipv6_success_with_ipv4",
wantErrMsg: "",
want: []net.IP{ip6},
ipv: IPVersion6,
ipv: aghnet.IPVersion6,
}, {
iface: &fakeIface{addrs: []net.Addr{addr6}, err: errTest},
name: "ipv6_error",
wantErrMsg: errTest.Error(),
want: nil,
ipv: IPVersion6,
ipv: aghnet.IPVersion6,
}, {
iface: &fakeIface{addrs: nil, err: nil},
name: "bad_proto",
wantErrMsg: "invalid ip version 10",
want: nil,
ipv: IPVersion6 + IPVersion4,
ipv: aghnet.IPVersion6 + aghnet.IPVersion4,
}, {
iface: &fakeIface{addrs: []net.Addr{&net.IPAddr{IP: ip4}}, err: nil},
name: "ipaddr_v4",
wantErrMsg: "",
want: []net.IP{ip4},
ipv: IPVersion4,
ipv: aghnet.IPVersion4,
}, {
iface: &fakeIface{addrs: []net.Addr{&net.IPAddr{IP: ip6, Zone: ""}}, err: nil},
name: "ipaddr_v6",
wantErrMsg: "",
want: []net.IP{ip6},
ipv: IPVersion6,
ipv: aghnet.IPVersion6,
}, {
iface: &fakeIface{addrs: []net.Addr{&net.UnixAddr{}}, err: nil},
name: "non-ipv4",
wantErrMsg: "",
want: nil,
ipv: IPVersion4,
ipv: aghnet.IPVersion4,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got, err := IfaceIPAddrs(tc.iface, tc.ipv)
got, err := aghnet.IfaceIPAddrs(tc.iface, tc.ipv)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
assert.Equal(t, tc.want, got)
@@ -118,7 +123,10 @@ type waitingFakeIface struct {
n int
}
// Addrs implements the NetIface interface for *waitingFakeIface.
// type check
var _ aghnet.NetIface = (*waitingFakeIface)(nil)
// Addrs implements the [aghnet.NetIface] interface for *waitingFakeIface.
func (iface *waitingFakeIface) Addrs() (addrs []net.Addr, err error) {
if iface.err != nil {
return nil, iface.err
@@ -143,76 +151,76 @@ func TestIfaceDNSIPAddrs(t *testing.T) {
addr6 := &net.IPNet{IP: ip6}
testCases := []struct {
iface NetIface
iface aghnet.NetIface
wantErr error
name string
want []net.IP
ipv IPVersion
ipv aghnet.IPVersion
}{{
name: "ipv4_success",
iface: &fakeIface{addrs: []net.Addr{addr4}, err: nil},
ipv: IPVersion4,
ipv: aghnet.IPVersion4,
want: []net.IP{ip4, ip4},
wantErr: nil,
}, {
name: "ipv4_success_with_ipv6",
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
ipv: IPVersion4,
ipv: aghnet.IPVersion4,
want: []net.IP{ip4, ip4},
wantErr: nil,
}, {
name: "ipv4_error",
iface: &fakeIface{addrs: []net.Addr{addr4}, err: errTest},
ipv: IPVersion4,
ipv: aghnet.IPVersion4,
want: nil,
wantErr: errTest,
}, {
name: "ipv4_wait",
iface: &waitingFakeIface{addrs: []net.Addr{addr4}, err: nil, n: 1},
ipv: IPVersion4,
ipv: aghnet.IPVersion4,
want: []net.IP{ip4, ip4},
wantErr: nil,
}, {
name: "ipv6_success",
iface: &fakeIface{addrs: []net.Addr{addr6}, err: nil},
ipv: IPVersion6,
ipv: aghnet.IPVersion6,
want: []net.IP{ip6, ip6},
wantErr: nil,
}, {
name: "ipv6_success_with_ipv4",
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
ipv: IPVersion6,
ipv: aghnet.IPVersion6,
want: []net.IP{ip6, ip6},
wantErr: nil,
}, {
name: "ipv6_error",
iface: &fakeIface{addrs: []net.Addr{addr6}, err: errTest},
ipv: IPVersion6,
ipv: aghnet.IPVersion6,
want: nil,
wantErr: errTest,
}, {
name: "ipv6_wait",
iface: &waitingFakeIface{addrs: []net.Addr{addr6}, err: nil, n: 1},
ipv: IPVersion6,
ipv: aghnet.IPVersion6,
want: []net.IP{ip6, ip6},
wantErr: nil,
}, {
name: "empty",
iface: &fakeIface{addrs: nil, err: nil},
ipv: IPVersion4,
ipv: aghnet.IPVersion4,
want: nil,
wantErr: nil,
}, {
name: "many",
iface: &fakeIface{addrs: []net.Addr{addr4, addr4}},
ipv: IPVersion4,
ipv: aghnet.IPVersion4,
want: []net.IP{ip4, ip4},
wantErr: nil,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got, err := IfaceDNSIPAddrs(tc.iface, tc.ipv, 2, 0)
got, err := aghnet.IfaceDNSIPAddrs(tc.iface, tc.ipv, 2, 0)
require.ErrorIs(t, err, tc.wantErr)
assert.Equal(t, tc.want, got)

View File

@@ -1,9 +1,10 @@
package aghnet
package aghnet_test
import (
"net"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/golibs/netutil"
"github.com/stretchr/testify/assert"
)
@@ -18,7 +19,7 @@ func TestIPMut(t *testing.T) {
}}
t.Run("nil_no_mut", func(t *testing.T) {
ipmut := NewIPMut(nil)
ipmut := aghnet.NewIPMut(nil)
ips := netutil.CloneIPs(testIPs)
for i := range ips {
@@ -28,7 +29,7 @@ func TestIPMut(t *testing.T) {
})
t.Run("not_nil_mut", func(t *testing.T) {
ipmut := NewIPMut(func(ip net.IP) {
ipmut := aghnet.NewIPMut(func(ip net.IP) {
for i := range ip {
ip[i] = 0
}

View File

@@ -1,3 +1,5 @@
//go:build darwin
package aghnet
import (

View File

@@ -0,0 +1,24 @@
package aghnet
import "github.com/AdguardTeam/dnsproxy/upstream"
// UpstreamHTTPVersions returns the HTTP versions for upstream configuration
// depending on configuration.
func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) {
if !http3 {
return upstream.DefaultHTTPVersions
}
return []upstream.HTTPVersion{
upstream.HTTPVersion3,
upstream.HTTPVersion2,
upstream.HTTPVersion11,
}
}
// IsCommentOrEmpty returns true if s starts with a "#" character or is empty.
// This function is useful for filtering out non-upstream lines from upstream
// configs.
func IsCommentOrEmpty(s string) (ok bool) {
return len(s) == 0 || s[0] == '#'
}

View File

@@ -0,0 +1,26 @@
package aghnet_test
import (
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/stretchr/testify/assert"
)
func TestIsCommentOrEmpty(t *testing.T) {
for _, tc := range []struct {
want assert.BoolAssertionFunc
str string
}{{
want: assert.True,
str: "",
}, {
want: assert.True,
str: "# comment",
}, {
want: assert.False,
str: "1.2.3.4",
}} {
tc.want(t, aghnet.IsCommentOrEmpty(tc.str))
}
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/miekg/dns"
)
@@ -121,26 +120,6 @@ func (p *AddressUpdater) UpdateAddress(
p.OnUpdateAddress(ctx, ip, host, info)
}
// Package dnsforward
// ClientsContainer is a fake [dnsforward.ClientsContainer] implementation for
// tests.
type ClientsContainer struct {
OnUpstreamConfigByID func(
id string,
boot upstream.Resolver,
) (conf *proxy.CustomUpstreamConfig, err error)
}
// UpstreamConfigByID implements the [dnsforward.ClientsContainer] interface
// for *ClientsContainer.
func (c *ClientsContainer) UpstreamConfigByID(
id string,
boot upstream.Resolver,
) (conf *proxy.CustomUpstreamConfig, err error) {
return c.OnUpstreamConfigByID(id, boot)
}
// Package filtering
// Resolver is a fake [filtering.Resolver] implementation for tests.

View File

@@ -3,7 +3,6 @@ package aghtest_test
import (
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
)
@@ -12,9 +11,6 @@ import (
// type check
var _ filtering.Resolver = (*aghtest.Resolver)(nil)
// type check
var _ dnsforward.ClientsContainer = (*aghtest.ClientsContainer)(nil)
// type check
//
// TODO(s.chzhen): It's here to avoid the import cycle. Remove it.

View File

@@ -0,0 +1,59 @@
package aghuser
import (
"context"
"github.com/AdguardTeam/golibs/errors"
"golang.org/x/crypto/bcrypt"
)
// Login is the type for web user logins.
type Login string
// NewLogin returns a web user login. The length of s must not be greater than
// [math.MaxUint16].
//
// TODO(s.chzhen): Add more constraints as needed.
func NewLogin(s string) (l Login, err error) {
if s == "" {
return "", errors.ErrEmptyValue
}
return Login(s), nil
}
// Password is an interface that defines methods for handling web user
// passwords.
type Password interface {
// Authenticate returns true if the provided password is allowed.
Authenticate(ctx context.Context, password string) (ok bool)
// Hash returns a hashed representation of the web user password.
Hash() (b []byte)
}
// DefaultPassword is the default bcrypt implementation of the [Password]
// interface.
type DefaultPassword struct {
hash []byte
}
// NewDefaultPassword returns the new properly initialized *DefaultPassword.
func NewDefaultPassword(hash string) (p *DefaultPassword) {
return &DefaultPassword{
hash: []byte(hash),
}
}
// type check
var _ Password = (*DefaultPassword)(nil)
// Authenticate implements the [Password] interface for *DefaultPassword.
func (p *DefaultPassword) Authenticate(ctx context.Context, passwd string) (ok bool) {
return bcrypt.CompareHashAndPassword([]byte(p.hash), []byte(passwd)) == nil
}
// Hash implements the [Password] interface for *DefaultPassword.
func (p *DefaultPassword) Hash() (b []byte) {
return p.hash
}

View File

@@ -0,0 +1,6 @@
package aghuser_test
import "time"
// testTimeout is the common timeout for tests.
const testTimeout = 1 * time.Second

149
internal/aghuser/db.go Normal file
View File

@@ -0,0 +1,149 @@
package aghuser
import (
"cmp"
"context"
"fmt"
"maps"
"slices"
"sync"
"github.com/AdguardTeam/golibs/errors"
)
// DB is an interface that defines methods for interacting with user
// information. All methods must be safe for concurrent use.
//
// TODO(s.chzhen): Use this.
//
// TODO(s.chzhen): Consider updating methods to return a clone.
type DB interface {
// All retrieves all users from the database, sorted by login.
//
// TODO(s.chzhen): Consider function signature change to reflect the
// in-memory implementation, as it currently always returns nil for error.
All(ctx context.Context) (users []*User, err error)
// ByLogin retrieves a user by their login. u must not be modified.
//
// TODO(s.chzhen): Remove this once user sessions support [UserID].
ByLogin(ctx context.Context, login Login) (u *User, err error)
// ByUUID retrieves a user by their unique identifier. u must not be
// modified.
//
// TODO(s.chzhen): Use this.
ByUUID(ctx context.Context, id UserID) (u *User, err error)
// Create adds a new user to the database. If the credentials already
// exist, it returns the [errors.ErrDuplicated] error. It also can return
// an error from the cryptographic randomness reader. u must not be
// modified.
Create(ctx context.Context, u *User) (err error)
}
// DefaultDB is the default in-memory implementation of the [DB] interface.
type DefaultDB struct {
// mu protects all properties below.
mu *sync.Mutex
// loginToUserID maps a web user login to their UserID. The values must not
// be empty.
//
// TODO(s.chzhen): Remove this once user sessions support [UserID].
loginToUserID map[Login]UserID
// userIDToUser maps a UserID to a web user. The values must not be nil.
// It must be synchronized with loginToUserID, meaning all UserIDs stored in
// loginToUserID must also be stored in this map.
userIDToUser map[UserID]*User
}
// NewDefaultDB returns the new properly initialized *DefaultDB.
func NewDefaultDB() (db *DefaultDB) {
return &DefaultDB{
mu: &sync.Mutex{},
loginToUserID: map[Login]UserID{},
userIDToUser: map[UserID]*User{},
}
}
// type check
var _ DB = (*DefaultDB)(nil)
// All implements the [DB] interface for *DefaultDB.
func (db *DefaultDB) All(ctx context.Context) (users []*User, err error) {
db.mu.Lock()
defer db.mu.Unlock()
if len(db.userIDToUser) == 0 {
return nil, nil
}
users = slices.SortedStableFunc(
maps.Values(db.userIDToUser),
func(a, b *User) (res int) {
// TODO(s.chzhen): Consider adding a custom comparer.
return cmp.Compare(a.Login, b.Login)
},
)
return users, nil
}
// ByLogin implements the [DB] interface for *DefaultDB.
func (db *DefaultDB) ByLogin(ctx context.Context, login Login) (u *User, err error) {
db.mu.Lock()
defer db.mu.Unlock()
id, ok := db.loginToUserID[login]
if !ok {
return nil, nil
}
u, ok = db.userIDToUser[id]
if !ok {
// Should not happen.
panic(fmt.Errorf("no web user present with login %q", login))
}
return u, nil
}
// ByUUID implements the [DB] interface for *DefaultDB.
func (db *DefaultDB) ByUUID(ctx context.Context, id UserID) (u *User, err error) {
db.mu.Lock()
defer db.mu.Unlock()
u, ok := db.userIDToUser[id]
if !ok {
return nil, nil
}
return u, nil
}
// Create implements the [DB] interface for *DefaultDB.
func (db *DefaultDB) Create(ctx context.Context, u *User) (err error) {
db.mu.Lock()
defer db.mu.Unlock()
if u.ID == (UserID{}) {
return fmt.Errorf("userid: %w", errors.ErrEmptyValue)
}
_, ok := db.userIDToUser[u.ID]
if ok {
return fmt.Errorf("userid: %w", errors.ErrDuplicated)
}
_, ok = db.loginToUserID[u.Login]
if ok {
return fmt.Errorf("login: %w", errors.ErrDuplicated)
}
db.userIDToUser[u.ID] = u
db.loginToUserID[u.Login] = u.ID
return nil
}

View File

@@ -0,0 +1,83 @@
package aghuser_test
import (
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghuser"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
)
func TestDB(t *testing.T) {
db := aghuser.NewDefaultDB()
const (
userWithIDPassRaw = "user_with_id_password"
userSecondPassRaw = "user_second_password"
)
userWithIDPassHash, err := bcrypt.GenerateFromPassword(
[]byte(userWithIDPassRaw),
bcrypt.DefaultCost,
)
require.NoError(t, err)
userSecondPassHash, err := bcrypt.GenerateFromPassword(
[]byte(userSecondPassRaw),
bcrypt.DefaultCost,
)
require.NoError(t, err)
userWithIDPass := aghuser.NewDefaultPassword(string(userWithIDPassHash))
userSecondPass := aghuser.NewDefaultPassword(string(userSecondPassHash))
var (
userWithID = &aghuser.User{
ID: aghuser.MustNewUserID(),
Login: "user_with_id",
Password: userWithIDPass,
}
userSecond = &aghuser.User{
ID: aghuser.MustNewUserID(),
Login: "user_second",
Password: userSecondPass,
}
userDuplicateLogin = &aghuser.User{
ID: aghuser.MustNewUserID(),
Login: userWithID.Login,
Password: userWithIDPass,
}
)
ctx := testutil.ContextWithTimeout(t, testTimeout)
err = db.Create(ctx, userWithID)
require.NoError(t, err)
err = db.Create(ctx, userSecond)
require.NoError(t, err)
err = db.Create(ctx, userDuplicateLogin)
assert.ErrorIs(t, err, errors.ErrDuplicated)
got, err := db.ByUUID(ctx, userWithID.ID)
require.NoError(t, err)
assert.Equal(t, userWithID, got)
assert.True(t, got.Password.Authenticate(ctx, userWithIDPassRaw))
got, err = db.ByLogin(ctx, userSecond.Login)
require.NoError(t, err)
assert.Equal(t, userSecond, got)
assert.True(t, got.Password.Authenticate(ctx, userSecondPassRaw))
users, err := db.All(ctx)
require.NoError(t, err)
assert.Len(t, users, 2)
assert.Equal(t, []*aghuser.User{userSecond, userWithID}, users)
}

View File

@@ -0,0 +1,35 @@
package aghuser
import (
"crypto/rand"
"time"
)
// SessionToken is the type for the web user session token.
type SessionToken [16]byte
// NewSessionToken returns a cryptographically secure randomly generated web
// user session token. If an error occurs during random generation, it will
// cause the program to crash.
func NewSessionToken() (t SessionToken) {
_, _ = rand.Read(t[:])
return t
}
// Session represents a web user session.
type Session struct {
// Expire indicates when the session will expire.
Expire time.Time
// UserLogin is the login of the web user associated with the session.
//
// TODO(s.chzhen): Remove this field and associate the user by UserID.
UserLogin Login
// Token is the session token.
Token SessionToken
// UserID is the identifier of the web user associated with the session.
UserID UserID
}

View File

@@ -0,0 +1,449 @@
package aghuser
import (
"context"
"encoding/binary"
"fmt"
"log/slog"
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/timeutil"
"go.etcd.io/bbolt"
berrors "go.etcd.io/bbolt/errors"
)
// SessionStorage is an interface that defines methods for handling web user
// sessions. All methods must be safe for concurrent use.
//
// TODO(s.chzhen): Add DeleteAll method.
type SessionStorage interface {
// New creates a new session for the web user.
New(ctx context.Context, u *User) (s *Session, err error)
// FindByToken returns the stored session for the web user based on the session
// token.
//
// TODO(s.chzhen): Consider function signature change to reflect the
// in-memory implementation, as it currently always returns nil for error.
FindByToken(ctx context.Context, t SessionToken) (s *Session, err error)
// DeleteByToken removes a stored web user session by the provided token.
DeleteByToken(ctx context.Context, t SessionToken) (err error)
// Close releases the web user sessions database resources.
Close() (err error)
}
// DefaultSessionStorageConfig represents the web user session storage
// configuration structure.
type DefaultSessionStorageConfig struct {
// Logger is used for logging the operation of the session storage. It must
// not be nil.
Logger *slog.Logger
// Clock is used to get the current time. It must not be nil.
Clock timeutil.Clock
// UserDB contains the web user information such as ID, login, and password.
// It must not be nil.
UserDB DB
// DBPath is the path to the database file where session data is stored. It
// must not be empty.
DBPath string
// SessionTTL is the default Time-To-Live duration for web user sessions.
// It specifies how long a session should last and is a required field.
SessionTTL time.Duration
}
// DefaultSessionStorage is the default bbolt database implementation of the
// [SessionStorage] interface.
type DefaultSessionStorage struct {
// db is an instance of the bbolt database where web user sessions are
// stored by [SessionToken] in the [bucketNameSessions] bucket.
db *bbolt.DB
// logger is used for logging the operation of the session storage.
logger *slog.Logger
// mu protects sessions.
mu *sync.Mutex
// clock is used to get the current time.
clock timeutil.Clock
// userDB contains the web user information such as ID, login, and password.
userDB DB
// sessions maps a session token to a web user session.
sessions map[SessionToken]*Session
// sessionTTL is the default Time-To-Live value for web user sessions.
sessionTTL time.Duration
}
// NewDefaultSessionStorage returns the new properly initialized
// *DefaultSessionStorage.
func NewDefaultSessionStorage(
ctx context.Context,
conf *DefaultSessionStorageConfig,
) (ds *DefaultSessionStorage, err error) {
ds = &DefaultSessionStorage{
clock: conf.Clock,
userDB: conf.UserDB,
logger: conf.Logger,
mu: &sync.Mutex{},
sessions: map[SessionToken]*Session{},
sessionTTL: conf.SessionTTL,
}
dbFilename := conf.DBPath
// TODO(s.chzhen): Pass logger with options.
ds.db, err = bbolt.Open(dbFilename, aghos.DefaultPermFile, nil)
if err != nil {
ds.logger.ErrorContext(ctx, "opening db %q: %w", dbFilename, err)
if errors.Is(err, berrors.ErrInvalid) {
const s = "AdGuard Home cannot be initialized due to an incompatible file system.\n" +
"Please read the explanation here: https://adguard-dns.io/kb/adguard-home/getting-started/#limitations"
slogutil.PrintLines(ctx, ds.logger, slog.LevelError, "", s)
}
return nil, err
}
err = ds.loadSessions(ctx)
if err != nil {
return nil, fmt.Errorf("loading sessions: %w", err)
}
return ds, nil
}
// loadSessions loads web user sessions from the bbolt database.
func (ds *DefaultSessionStorage) loadSessions(ctx context.Context) (err error) {
tx, err := ds.db.Begin(true)
if err != nil {
return fmt.Errorf("starting transaction: %w", err)
}
needRollback := true
defer func() {
if needRollback {
err = errors.WithDeferred(err, tx.Rollback())
}
}()
bkt := tx.Bucket([]byte(bboltBucketSessions))
if bkt == nil {
return nil
}
removed, err := ds.processSessions(ctx, bkt)
if err != nil {
return fmt.Errorf("processing sessions: %w", err)
}
if removed == 0 {
ds.logger.DebugContext(ctx, "loading sessions from db", "stored", len(ds.sessions))
return nil
}
needRollback = false
err = tx.Commit()
if err != nil {
return fmt.Errorf("committing transaction: %w", err)
}
ds.logger.DebugContext(
ctx,
"loading sessions from db",
"stored", len(ds.sessions),
"removed", removed,
)
return nil
}
// processSessions iterates over the sessions bucket and loads or removes
// sessions as needed.
func (ds *DefaultSessionStorage) processSessions(
ctx context.Context,
bkt *bbolt.Bucket,
) (removed int, err error) {
invalidSessions := [][]byte{}
err = bkt.ForEach(ds.bboltSessionHandler(ctx, &invalidSessions))
if err != nil {
return 0, fmt.Errorf("iterating over sessions: %w", err)
}
var errs []error
for _, s := range invalidSessions {
if err = bkt.Delete(s); err != nil {
errs = append(errs, err)
}
}
if err = errors.Join(errs...); err != nil {
return 0, fmt.Errorf("deleting sessions: %w", err)
}
return len(invalidSessions), nil
}
// bboltSessionHandler returns a function for [bbolt.Bucket.ForEach] that
// iterates over stored sessions, deserializes them, and logs any errors
// encountered. The returned error is always nil, as these errors are
// considered non-critical to stop the iteration process.
func (ds *DefaultSessionStorage) bboltSessionHandler(
ctx context.Context,
invalidSessions *[][]byte,
) (fn func(k, v []byte) (err error)) {
now := ds.clock.Now()
return func(k, v []byte) (err error) {
s, err := bboltDecode(v)
if err != nil {
*invalidSessions = append(*invalidSessions, k)
ds.logger.DebugContext(ctx, "deserializing session", slogutil.KeyError, err)
return nil
}
if now.After(s.Expire) {
*invalidSessions = append(*invalidSessions, k)
return nil
}
u, err := ds.userDB.ByLogin(ctx, s.UserLogin)
if err != nil {
// Should not happen, as it currently always returns nil for error.
panic(err)
}
if u == nil {
*invalidSessions = append(*invalidSessions, k)
ds.logger.DebugContext(ctx, "no saved user by name", "name", s.UserLogin)
return nil
}
t := SessionToken(k)
s.Token = t
s.UserID = u.ID
ds.sessions[t] = s
return nil
}
}
// bboltBucketSessions is the name of the bucket storing web user sessions in
// the bbolt database.
const bboltBucketSessions = "sessions-2"
const (
// bboltSessionExpireLen is the length of the expire field in the binary
// entry stored in bbolt.
bboltSessionExpireLen = 4
// bboltSessionNameLen is the length of the name field in the binary entry
// stored in bbolt.
bboltSessionNameLen = 2
)
// bboltDecode deserializes decodes a binary data into a session.
func bboltDecode(data []byte) (s *Session, err error) {
if len(data) < bboltSessionExpireLen+bboltSessionNameLen {
return nil, fmt.Errorf("length of the data is less than expected: got %d", len(data))
}
expireData := data[:bboltSessionExpireLen]
nameLenData := data[bboltSessionExpireLen : bboltSessionExpireLen+bboltSessionNameLen]
nameData := data[bboltSessionExpireLen+bboltSessionNameLen:]
nameLen := binary.BigEndian.Uint16(nameLenData)
if len(nameData) != int(nameLen) {
return nil, fmt.Errorf("login: expected length %d, got %d", nameLen, len(nameData))
}
expire := binary.BigEndian.Uint32(expireData)
return &Session{
Expire: time.Unix(int64(expire), 0),
UserLogin: Login(nameData),
}, nil
}
// bboltEncode serializes a session properties into a binary data.
func bboltEncode(s *Session) (data []byte) {
data = make([]byte, bboltSessionExpireLen+bboltSessionNameLen+len(s.UserLogin))
expireData := data[:bboltSessionExpireLen]
nameLenData := data[bboltSessionExpireLen : bboltSessionExpireLen+bboltSessionNameLen]
nameData := data[bboltSessionExpireLen+bboltSessionNameLen:]
expire := uint32(s.Expire.Unix())
binary.BigEndian.PutUint32(expireData, expire)
binary.BigEndian.PutUint16(nameLenData, uint16(len(s.UserLogin)))
copy(nameData, []byte(s.UserLogin))
return data
}
// type check
var _ SessionStorage = (*DefaultSessionStorage)(nil)
// New implements the [SessionStorage] interface for *DefaultSessionStorage.
func (ds *DefaultSessionStorage) New(ctx context.Context, u *User) (s *Session, err error) {
s = &Session{
Token: NewSessionToken(),
UserID: u.ID,
UserLogin: u.Login,
Expire: ds.clock.Now().Add(ds.sessionTTL),
}
err = ds.store(s)
if err != nil {
return nil, fmt.Errorf("storing session: %w", err)
}
ds.mu.Lock()
defer ds.mu.Unlock()
ds.sessions[s.Token] = s
return s, nil
}
// store saves a web user session in the bbolt database.
func (ds *DefaultSessionStorage) store(s *Session) (err error) {
tx, err := ds.db.Begin(true)
if err != nil {
return fmt.Errorf("starting transaction: %w", err)
}
needRollback := true
defer func() {
if needRollback {
err = errors.WithDeferred(err, tx.Rollback())
}
}()
bkt, err := tx.CreateBucketIfNotExists([]byte(bboltBucketSessions))
if err != nil {
return fmt.Errorf("creating bucket: %w", err)
}
err = bkt.Put(s.Token[:], bboltEncode(s))
if err != nil {
return fmt.Errorf("putting data: %w", err)
}
needRollback = false
err = tx.Commit()
if err != nil {
return fmt.Errorf("committing transaction: %w", err)
}
return nil
}
// FindByToken implements the [SessionStorage] interface for *DefaultSessionStorage.
func (ds *DefaultSessionStorage) FindByToken(ctx context.Context, t SessionToken) (s *Session, err error) {
ds.mu.Lock()
defer ds.mu.Unlock()
s, ok := ds.sessions[t]
if !ok {
return nil, nil
}
now := ds.clock.Now()
if now.After(s.Expire) {
err = ds.deleteByToken(ctx, t)
if err != nil {
return nil, fmt.Errorf("expired session: %w", err)
}
return nil, nil
}
return s, nil
}
// DeleteByToken implements the [SessionStorage] interface for
// *DefaultSessionStorage.
func (ds *DefaultSessionStorage) DeleteByToken(ctx context.Context, t SessionToken) (err error) {
ds.mu.Lock()
defer ds.mu.Unlock()
// Don't wrap the error because it's informative enough as is.
return ds.deleteByToken(ctx, t)
}
// deleteByToken removes stored session by token. ds.mu is expected to be
// locked.
func (ds *DefaultSessionStorage) deleteByToken(ctx context.Context, t SessionToken) (err error) {
err = ds.remove(ctx, t)
if err != nil {
ds.logger.ErrorContext(ctx, "deleting session", slogutil.KeyError, err)
return err
}
delete(ds.sessions, t)
return nil
}
// remove deletes a web user session from the bbolt database.
func (ds *DefaultSessionStorage) remove(ctx context.Context, t SessionToken) (err error) {
tx, err := ds.db.Begin(true)
if err != nil {
return fmt.Errorf("starting transaction: %w", err)
}
needRollback := true
defer func() {
if needRollback {
err = errors.WithDeferred(err, tx.Rollback())
}
}()
bkt := tx.Bucket([]byte(bboltBucketSessions))
if bkt == nil {
return errors.Error("no bucket")
}
err = bkt.Delete(t[:])
if err != nil {
return fmt.Errorf("removing data: %w", err)
}
needRollback = false
err = tx.Commit()
if err != nil {
return fmt.Errorf("committing transaction: %w", err)
}
ds.logger.DebugContext(ctx, "removed session from db")
return err
}
// Close implements the [SessionStorage] interface for *DefaultSessionStorage.
func (ds *DefaultSessionStorage) Close() (err error) {
err = ds.db.Close()
if err != nil {
return fmt.Errorf("closing db: %w", err)
}
return nil
}

View File

@@ -0,0 +1,162 @@
package aghuser_test
import (
"context"
"os"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghuser"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/faketime"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// addSession is a helper function that saves and returns a session for a newly
// generated [aghuser.User] by login.
func addSession(
tb testing.TB,
ctx context.Context,
ds aghuser.SessionStorage,
login aghuser.Login,
) (s *aghuser.Session) {
tb.Helper()
s, err := ds.New(ctx, &aghuser.User{
ID: aghuser.MustNewUserID(),
Login: login,
})
require.NoError(tb, err)
require.NotNil(tb, s)
var got *aghuser.Session
got, err = ds.FindByToken(ctx, s.Token)
require.NoError(tb, err)
require.NotNil(tb, got)
assert.Equal(tb, login, got.UserLogin)
return s
}
func TestDefaultSessionStorage(t *testing.T) {
const (
userLoginFirst aghuser.Login = "user_one"
userLoginSecond aghuser.Login = "user_two"
)
var (
ctx = testutil.ContextWithTimeout(t, testTimeout)
logger = slogutil.NewDiscardLogger()
)
const (
sessionTTL = time.Minute
timeStep = time.Second
)
// Set up a mock clock to test expired sessions. Each call to [clock.Now]
// will return the [date] incremented by [timeStep].
date := time.Now()
clock := &faketime.Clock{
OnNow: func() (now time.Time) {
date = date.Add(timeStep)
return date
},
}
dbFile, err := os.CreateTemp(t.TempDir(), "sessions.db")
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, dbFile.Close)
userDB := aghuser.NewDefaultDB()
err = userDB.Create(ctx, &aghuser.User{
Login: userLoginFirst,
ID: aghuser.MustNewUserID(),
})
require.NoError(t, err)
err = userDB.Create(ctx, &aghuser.User{
Login: userLoginSecond,
ID: aghuser.MustNewUserID(),
})
require.NoError(t, err)
var (
ds *aghuser.DefaultSessionStorage
sessionFirst *aghuser.Session
sessionSecond *aghuser.Session
)
require.True(t, t.Run("prepare_session_storage", func(t *testing.T) {
ds, err = aghuser.NewDefaultSessionStorage(ctx, &aghuser.DefaultSessionStorageConfig{
Clock: clock,
UserDB: userDB,
Logger: logger,
DBPath: dbFile.Name(),
SessionTTL: sessionTTL,
})
require.NoError(t, err)
sessionFirst = addSession(t, ctx, ds, userLoginFirst)
// Advance time to ensure the first session expires before creating the
// second session.
date = date.Add(time.Hour)
sessionSecond = addSession(t, ctx, ds, userLoginSecond)
err = ds.Close()
require.NoError(t, err)
}))
require.True(t, t.Run("load_sessions", func(t *testing.T) {
ds, err = aghuser.NewDefaultSessionStorage(ctx, &aghuser.DefaultSessionStorageConfig{
Clock: clock,
UserDB: userDB,
Logger: logger,
DBPath: dbFile.Name(),
SessionTTL: sessionTTL,
})
require.NoError(t, err)
var got *aghuser.Session
got, err = ds.FindByToken(ctx, sessionFirst.Token)
require.NoError(t, err)
assert.Nil(t, got)
got, err = ds.FindByToken(ctx, sessionSecond.Token)
require.NoError(t, err)
require.NotNil(t, got)
assert.Equal(t, userLoginSecond, got.UserLogin)
err = ds.DeleteByToken(ctx, sessionSecond.Token)
require.NoError(t, err)
got, err = ds.FindByToken(ctx, sessionSecond.Token)
require.NoError(t, err)
assert.Nil(t, got)
}))
require.True(t, t.Run("expired_session", func(t *testing.T) {
testutil.CleanupAndRequireSuccess(t, ds.Close)
sessionFirst = addSession(t, ctx, ds, userLoginFirst)
date = date.Add(time.Hour)
var got *aghuser.Session
got, err = ds.FindByToken(ctx, sessionFirst.Token)
require.NoError(t, err)
assert.Nil(t, got)
}))
}

44
internal/aghuser/user.go Normal file
View File

@@ -0,0 +1,44 @@
// Package aghuser contains types and logic for dealing with AdGuard Home's web
// users.
package aghuser
import (
"fmt"
"github.com/google/uuid"
)
// UserID is the type for the unique IDs of web users.
type UserID uuid.UUID
// NewUserID returns a new web user unique identifier. Any error returned is an
// error from the cryptographic randomness reader.
func NewUserID() (uid UserID, err error) {
uuidv7, err := uuid.NewV7()
return UserID(uuidv7), err
}
// MustNewUserID is a wrapper around [NewUserID] that panics if there is an
// error. It is currently only used in tests.
func MustNewUserID() (uid UserID) {
uid, err := NewUserID()
if err != nil {
panic(fmt.Errorf("unexpected uuidv7 error: %w", err))
}
return uid
}
// User represents a web user.
type User struct {
// Password stores the password information for the web user. It must not
// be nil.
Password Password
// Login is the login name of the web user. It must not be empty.
Login Login
// ID is the unique identifier for the web user. It must not be empty.
ID UserID
}

View File

@@ -11,8 +11,34 @@ import (
"slices"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
)
// ClientID is a unique identifier for a persistent client used in
// DNS-over-HTTPS, DNS-over-TLS, and DNS-over-QUIC queries.
//
// TODO(s.chzhen): Use everywhere.
type ClientID string
// ValidateClientID returns an error if id is not a valid ClientID.
//
// TODO(s.chzhen): Consider implementing [validate.Interface] for ClientID.
func ValidateClientID(id string) (err error) {
err = netutil.ValidateHostnameLabel(id)
if err != nil {
// Replace the domain name label wrapper with our own.
return fmt.Errorf("invalid clientid %q: %w", id, errors.Unwrap(err))
}
return nil
}
// isValidClientID returns false if id is not a valid ClientID.
func isValidClientID(id string) (ok bool) {
return netutil.IsValidHostnameLabel(id)
}
// Source represents the source from which the information about the client has
// been obtained.
type Source uint8

View File

@@ -9,7 +9,6 @@ import (
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/golibs/errors"
)
// macKey contains MAC as byte array of 6, 8, or 20 bytes.
@@ -35,8 +34,8 @@ type index struct {
// nameToUID maps client name to UID.
nameToUID map[string]UID
// clientIDToUID maps client ID to UID.
clientIDToUID map[string]UID
// clientIDToUID maps ClientID to UID.
clientIDToUID map[ClientID]UID
// ipToUID maps IP address to UID.
ipToUID map[netip.Addr]UID
@@ -55,7 +54,7 @@ type index struct {
func newIndex() (ci *index) {
return &index{
nameToUID: map[string]UID{},
clientIDToUID: map[string]UID{},
clientIDToUID: map[ClientID]UID{},
ipToUID: map[netip.Addr]UID{},
subnetToUID: aghalg.NewSortedMap[netip.Prefix, UID](subnetCompare),
macToUID: map[macKey]UID{},
@@ -205,19 +204,19 @@ func (ci *index) clashesMAC(c *Persistent) (p *Persistent, mac net.HardwareAddr)
return nil, nil
}
// find finds persistent client by string representation of the client ID, IP
// find finds persistent client by string representation of the ClientID, IP
// address, or MAC.
func (ci *index) find(id string) (c *Persistent, ok bool) {
uid, found := ci.clientIDToUID[id]
if found {
return ci.uidToClient[uid], true
c, ok = ci.findByClientID(ClientID(id))
if ok {
return c, true
}
ip, err := netip.ParseAddr(id)
if err == nil {
// MAC addresses can be successfully parsed as IP addresses.
c, found = ci.findByIP(ip)
if found {
c, ok = ci.findByIP(ip)
if ok {
return c, true
}
}
@@ -230,6 +229,16 @@ func (ci *index) find(id string) (c *Persistent, ok bool) {
return nil, false
}
// findByClientID finds persistent client by ClientID.
func (ci *index) findByClientID(clientID ClientID) (c *Persistent, ok bool) {
uid, ok := ci.clientIDToUID[clientID]
if ok {
return ci.uidToClient[uid], true
}
return nil, false
}
// findByName finds persistent client by name.
func (ci *index) findByName(name string) (c *Persistent, found bool) {
uid, found := ci.nameToUID[name]
@@ -266,6 +275,26 @@ func (ci *index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
return nil, false
}
// findByCIDR searches for a persistent client with the provided subnet as an
// identifier. Note that this function looks for an exact match of subnets,
// rather than checking if one subnet contains another.
func (ci *index) findByCIDR(subnet netip.Prefix) (c *Persistent, ok bool) {
var uid UID
for pref, id := range ci.subnetToUID.Range {
if subnet == pref {
uid, ok = id, true
break
}
}
if ok {
return ci.uidToClient[uid], true
}
return nil, false
}
// findByMAC finds persistent client by MAC.
func (ci *index) findByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
k := macToKey(mac)
@@ -343,18 +372,3 @@ func (ci *index) rangeByName(f func(c *Persistent) (cont bool)) {
}
}
}
// closeUpstreams closes upstream configurations of persistent clients.
func (ci *index) closeUpstreams() (err error) {
var errs []error
ci.rangeByName(func(c *Persistent) (cont bool) {
err = c.CloseUpstreams()
if err != nil {
errs = append(errs, err)
}
return true
})
return errors.Join(errs...)
}

View File

@@ -5,6 +5,7 @@ import (
"net/netip"
"testing"
"github.com/AdguardTeam/golibs/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -58,12 +59,12 @@ func TestClientIndex_Find(t *testing.T) {
clientWithMAC = &Persistent{
Name: "client_with_mac",
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
MACs: []net.HardwareAddr{errors.Must(net.ParseMAC(cliMAC))},
}
clientWithID = &Persistent{
Name: "client_with_id",
ClientIDs: []string{cliID},
ClientIDs: []ClientID{cliID},
}
clientLinkLocal = &Persistent{
@@ -141,10 +142,10 @@ func TestClientIndex_Clashes(t *testing.T) {
Subnets: []netip.Prefix{netip.MustParsePrefix(cliSubnet)},
}, {
Name: "client_with_mac",
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
MACs: []net.HardwareAddr{errors.Must(net.ParseMAC(cliMAC))},
}, {
Name: "client_with_id",
ClientIDs: []string{cliID},
ClientIDs: []ClientID{cliID},
}}
ci := newIDIndex(clients)
@@ -181,17 +182,6 @@ func TestClientIndex_Clashes(t *testing.T) {
}
}
// mustParseMAC is wrapper around [net.ParseMAC] that panics if there is an
// error.
func mustParseMAC(s string) (mac net.HardwareAddr) {
mac, err := net.ParseMAC(s)
if err != nil {
panic(err)
}
return mac
}
func TestMACToKey(t *testing.T) {
testCases := []struct {
want any
@@ -200,44 +190,44 @@ func TestMACToKey(t *testing.T) {
}{{
name: "column6",
in: "00:00:5e:00:53:01",
want: [6]byte(mustParseMAC("00:00:5e:00:53:01")),
want: [6]byte(errors.Must(net.ParseMAC("00:00:5e:00:53:01"))),
}, {
name: "column8",
in: "02:00:5e:10:00:00:00:01",
want: [8]byte(mustParseMAC("02:00:5e:10:00:00:00:01")),
want: [8]byte(errors.Must(net.ParseMAC("02:00:5e:10:00:00:00:01"))),
}, {
name: "column20",
in: "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01",
want: [20]byte(mustParseMAC("00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01")),
want: [20]byte(errors.Must(net.ParseMAC("00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01"))),
}, {
name: "hyphen6",
in: "00-00-5e-00-53-01",
want: [6]byte(mustParseMAC("00-00-5e-00-53-01")),
want: [6]byte(errors.Must(net.ParseMAC("00-00-5e-00-53-01"))),
}, {
name: "hyphen8",
in: "02-00-5e-10-00-00-00-01",
want: [8]byte(mustParseMAC("02-00-5e-10-00-00-00-01")),
want: [8]byte(errors.Must(net.ParseMAC("02-00-5e-10-00-00-00-01"))),
}, {
name: "hyphen20",
in: "00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01",
want: [20]byte(mustParseMAC("00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01")),
want: [20]byte(errors.Must(net.ParseMAC("00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01"))),
}, {
name: "dot6",
in: "0000.5e00.5301",
want: [6]byte(mustParseMAC("0000.5e00.5301")),
want: [6]byte(errors.Must(net.ParseMAC("0000.5e00.5301"))),
}, {
name: "dot8",
in: "0200.5e10.0000.0001",
want: [8]byte(mustParseMAC("0200.5e10.0000.0001")),
want: [8]byte(errors.Must(net.ParseMAC("0200.5e10.0000.0001"))),
}, {
name: "dot20",
in: "0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001",
want: [20]byte(mustParseMAC("0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001")),
want: [20]byte(errors.Must(net.ParseMAC("0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001"))),
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mac := mustParseMAC(tc.in)
mac := errors.Must(net.ParseMAC(tc.in))
key := macToKey(mac)
assert.Equal(t, tc.want, key)
@@ -302,19 +292,19 @@ func TestIndex_FindByIPWithoutZone(t *testing.T) {
func TestClientIndex_RangeByName(t *testing.T) {
sortedClients := []*Persistent{{
Name: "clientA",
ClientIDs: []string{"A"},
ClientIDs: []ClientID{"A"},
}, {
Name: "clientB",
ClientIDs: []string{"B"},
ClientIDs: []ClientID{"B"},
}, {
Name: "clientC",
ClientIDs: []string{"C"},
ClientIDs: []ClientID{"C"},
}, {
Name: "clientD",
ClientIDs: []string{"D"},
ClientIDs: []ClientID{"D"},
}, {
Name: "clientE",
ClientIDs: []string{"E"},
ClientIDs: []ClientID{"E"},
}}
testCases := []struct {
@@ -349,3 +339,115 @@ func TestClientIndex_RangeByName(t *testing.T) {
})
}
}
func TestIndex_FindByName(t *testing.T) {
const (
clientExistingName = "client_existing"
clientAnotherExistingName = "client_another_existing"
nonExistingClientName = "client_non_existing"
)
var (
clientExisting = &Persistent{
Name: clientExistingName,
IPs: []netip.Addr{netip.MustParseAddr("192.0.2.1")},
}
clientAnotherExisting = &Persistent{
Name: clientAnotherExistingName,
IPs: []netip.Addr{netip.MustParseAddr("192.0.2.2")},
}
)
clients := []*Persistent{
clientExisting,
clientAnotherExisting,
}
ci := newIDIndex(clients)
testCases := []struct {
want *Persistent
found assert.BoolAssertionFunc
name string
clientName string
}{{
want: clientExisting,
found: assert.True,
name: "existing",
clientName: clientExistingName,
}, {
want: clientAnotherExisting,
found: assert.True,
name: "another_existing",
clientName: clientAnotherExistingName,
}, {
want: nil,
found: assert.False,
name: "non_existing",
clientName: nonExistingClientName,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
c, ok := ci.findByName(tc.clientName)
assert.Equal(t, tc.want, c)
tc.found(t, ok)
})
}
}
func TestIndex_FindByMAC(t *testing.T) {
var (
cliMAC = errors.Must(net.ParseMAC("11:11:11:11:11:11"))
cliAnotherMAC = errors.Must(net.ParseMAC("22:22:22:22:22:22"))
nonExistingClientMAC = errors.Must(net.ParseMAC("33:33:33:33:33:33"))
)
var (
clientExisting = &Persistent{
Name: "client",
MACs: []net.HardwareAddr{cliMAC},
}
clientAnotherExisting = &Persistent{
Name: "another_client",
MACs: []net.HardwareAddr{cliAnotherMAC},
}
)
clients := []*Persistent{
clientExisting,
clientAnotherExisting,
}
ci := newIDIndex(clients)
testCases := []struct {
want *Persistent
found assert.BoolAssertionFunc
name string
clientMAC net.HardwareAddr
}{{
want: clientExisting,
found: assert.True,
name: "existing",
clientMAC: cliMAC,
}, {
want: clientAnotherExisting,
found: assert.True,
name: "another_existing",
clientMAC: cliAnotherMAC,
}, {
want: nil,
found: assert.False,
name: "non_existing",
clientMAC: nonExistingClientMAC,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
c, ok := ci.findByMAC(tc.clientMAC)
assert.Equal(t, tc.want, c)
tc.found(t, ok)
})
}
}

View File

@@ -15,7 +15,6 @@ import (
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/google/uuid"
)
@@ -58,12 +57,6 @@ func (uid *UID) UnmarshalText(data []byte) error {
// Persistent contains information about persistent clients.
type Persistent struct {
// UpstreamConfig is the custom upstream configuration for this client. If
// it's nil, it has not been initialized yet. If it's non-nil and empty,
// there are no valid upstreams. If it's non-nil and non-empty, these
// upstream must be used.
UpstreamConfig *proxy.CustomUpstreamConfig
// SafeSearch handles search engine hosts rewrites.
SafeSearch filtering.SafeSearch
@@ -77,7 +70,9 @@ type Persistent struct {
// Tags is a list of client tags that categorize the client.
Tags []string
// Upstreams is a list of custom upstream DNS servers for the client.
// Upstreams is a list of custom upstream DNS servers for the client. If
// it's empty, the custom upstream cache is disabled, regardless of the
// value of UpstreamsCacheEnabled.
Upstreams []string
// IPs is a list of IP addresses that identify the client. The client must
@@ -96,15 +91,16 @@ type Persistent struct {
// ClientIDs identifying the client. The client must have at least one ID
// (IP, subnet, MAC, or ClientID).
ClientIDs []string
ClientIDs []ClientID
// UID is the unique identifier of the persistent client.
UID UID
// UpstreamsCacheSize is the cache size for custom upstreams.
// UpstreamsCacheSize defines the size of the custom upstream cache.
UpstreamsCacheSize uint32
// UpstreamsCacheEnabled specifies whether custom upstreams are used.
// UpstreamsCacheEnabled specifies whether the custom upstream cache is
// used. If true, the list of Upstreams should not be empty.
UpstreamsCacheEnabled bool
// UseOwnSettings specifies whether custom filtering settings are used.
@@ -140,7 +136,7 @@ func (c *Persistent) validate(ctx context.Context, l *slog.Logger, allTags []str
switch {
case c.Name == "":
return errors.Error("empty name")
case c.IDsLen() == 0:
case c.idendifiersLen() == 0:
return errors.Error("id required")
case c.UID == UID{}:
return errors.Error("uid required")
@@ -243,28 +239,15 @@ func (c *Persistent) setID(id string) (err error) {
return err
}
c.ClientIDs = append(c.ClientIDs, strings.ToLower(id))
c.ClientIDs = append(c.ClientIDs, ClientID(strings.ToLower(id)))
return nil
}
// ValidateClientID returns an error if id is not a valid ClientID.
//
// TODO(s.chzhen): It's an exact copy of the [dnsforward.ValidateClientID] to
// avoid the import cycle. Remove it.
func ValidateClientID(id string) (err error) {
err = netutil.ValidateHostnameLabel(id)
if err != nil {
// Replace the domain name label wrapper with our own.
return fmt.Errorf("invalid clientid %q: %w", id, errors.Unwrap(err))
}
return nil
}
// IDs returns a list of client IDs containing at least one element.
func (c *Persistent) IDs() (ids []string) {
ids = make([]string, 0, c.IDsLen())
// Identifiers returns a list of client identifiers containing at least one
// element.
func (c *Persistent) Identifiers() (ids []string) {
ids = make([]string, 0, c.idendifiersLen())
for _, ip := range c.IPs {
ids = append(ids, ip.String())
@@ -278,11 +261,15 @@ func (c *Persistent) IDs() (ids []string) {
ids = append(ids, mac.String())
}
return append(ids, c.ClientIDs...)
for _, cid := range c.ClientIDs {
ids = append(ids, string(cid))
}
return ids
}
// IDsLen returns a length of client ids.
func (c *Persistent) IDsLen() (n int) {
// identifiersLen returns the number of client identifiers.
func (c *Persistent) idendifiersLen() (n int) {
return len(c.IPs) + len(c.Subnets) + len(c.MACs) + len(c.ClientIDs)
}
@@ -312,14 +299,3 @@ func (c *Persistent) ShallowClone() (clone *Persistent) {
return clone
}
// CloseUpstreams closes the client-specific upstream config of c if any.
func (c *Persistent) CloseUpstreams() (err error) {
if c.UpstreamConfig != nil {
if err = c.UpstreamConfig.Close(); err != nil {
return fmt.Errorf("closing upstreams of client %q: %w", c.Name, err)
}
}
return nil
}

View File

@@ -12,10 +12,14 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/arpdb"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil"
)
// allowedTags is the list of available client tags.
@@ -88,6 +92,10 @@ type StorageConfig struct {
// not be nil.
Logger *slog.Logger
// Clock is used by [upstreamManager] to retrieve the current time. It must
// not be nil.
Clock timeutil.Clock
// DHCP is used to match IPs against MACs of persistent clients and update
// [SourceDHCP] runtime client information. It must not be nil.
DHCP DHCP
@@ -126,6 +134,9 @@ type Storage struct {
// runtimeIndex contains information about runtime clients.
runtimeIndex *runtimeIndex
// upstreamManager stores and updates custom client upstream configurations.
upstreamManager *upstreamManager
// dhcp is used to update [SourceDHCP] runtime client information.
dhcp DHCP
@@ -163,6 +174,7 @@ func NewStorage(ctx context.Context, conf *StorageConfig) (s *Storage, err error
mu: &sync.Mutex{},
index: newIndex(),
runtimeIndex: newRuntimeIndex(),
upstreamManager: newUpstreamManager(conf.Logger, conf.Clock),
dhcp: conf.DHCP,
etcHosts: conf.EtcHosts,
arpDB: conf.ARPDB,
@@ -200,7 +212,7 @@ func (s *Storage) Start(ctx context.Context) (err error) {
func (s *Storage) Shutdown(_ context.Context) (err error) {
close(s.done)
return s.closeUpstreams()
return s.upstreamManager.close()
}
// periodicARPUpdate periodically reloads runtime clients from ARP. It is
@@ -416,53 +428,144 @@ func (s *Storage) Add(ctx context.Context, p *Persistent) (err error) {
}
s.index.add(p)
s.upstreamManager.updateCustomUpstreamConfig(p)
s.logger.DebugContext(
ctx,
"client added",
"name", p.Name,
"ids", p.IDs(),
"ids", p.Identifiers(),
"clients_count", s.index.size(),
)
return nil
}
// FindByName finds persistent client by name. And returns its shallow copy.
func (s *Storage) FindByName(name string) (p *Persistent, ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
// FindParams represents the parameters for searching a client. At least one
// field must be non-empty.
type FindParams struct {
// ClientID is a unique identifier for the client used in DoH, DoT, and DoQ
// DNS queries.
ClientID ClientID
p, ok = s.index.findByName(name)
if ok {
return p.ShallowClone(), ok
}
// RemoteIP is the IP address used as a client search parameter.
RemoteIP netip.Addr
return nil, false
// Subnet is the CIDR used as a client search parameter.
Subnet netip.Prefix
// MAC is the physical hardware address used as a client search parameter.
MAC net.HardwareAddr
// UID is the unique ID of persistent client used as a search parameter.
//
// TODO(s.chzhen): Use this.
UID UID
}
// Find finds persistent client by string representation of the client ID, IP
// address, or MAC. And returns its shallow copy.
// ErrBadIdentifier is returned by [FindParams.Set] when it cannot parse the
// provided client identifier.
const ErrBadIdentifier errors.Error = "bad client identifier"
// Set clears the stored search parameters and parses the string representation
// of the search parameter into typed parameter, storing it. In some cases, it
// may result in storing both an IP address and a MAC address because they might
// have identical string representations. It returns [ErrBadIdentifier] if id
// cannot be parsed.
//
// TODO(s.chzhen): Accept ClientIDData structure instead, which will contain
// the parsed IP address, if any.
func (s *Storage) Find(id string) (p *Persistent, ok bool) {
// TODO(s.chzhen): Add support for UID.
func (p *FindParams) Set(id string) (err error) {
*p = FindParams{}
isFound := false
if netutil.IsValidIPString(id) {
// It is safe to use [netip.MustParseAddr] because it has already been
// validated that id contains the string representation of the IP
// address.
p.RemoteIP = netip.MustParseAddr(id)
// Even if id can be parsed as an IP address, it may be a MAC address.
// So do not return prematurely, continue parsing.
isFound = true
}
if netutil.IsValidMACString(id) {
p.MAC, err = net.ParseMAC(id)
if err != nil {
panic(fmt.Errorf("parsing mac from %q: %w", id, err))
}
isFound = true
}
if isFound {
return nil
}
if netutil.IsValidIPPrefixString(id) {
// It is safe to use [netip.MustParsePrefix] because it has already been
// validated that id contains the string representation of IP prefix.
p.Subnet = netip.MustParsePrefix(id)
return nil
}
if !isValidClientID(id) {
return ErrBadIdentifier
}
p.ClientID = ClientID(id)
return nil
}
// Find represents the parameters for searching a client. params must not be
// nil and must have at least one non-empty field.
func (s *Storage) Find(params *FindParams) (p *Persistent, ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
p, ok = s.index.find(id)
isClientID := params.ClientID != ""
isRemoteIP := params.RemoteIP != (netip.Addr{})
isSubnet := params.Subnet != (netip.Prefix{})
isMAC := params.MAC != nil
for {
switch {
case isClientID:
isClientID = false
p, ok = s.index.findByClientID(params.ClientID)
case isRemoteIP:
isRemoteIP = false
p, ok = s.findByIP(params.RemoteIP)
case isSubnet:
isSubnet = false
p, ok = s.index.findByCIDR(params.Subnet)
case isMAC:
isMAC = false
p, ok = s.index.findByMAC(params.MAC)
default:
return nil, false
}
if ok {
return p.ShallowClone(), true
}
}
}
// findByIP finds persistent client by IP address. s.mu is expected to be
// locked.
func (s *Storage) findByIP(addr netip.Addr) (p *Persistent, ok bool) {
p, ok = s.index.findByIP(addr)
if ok {
return p.ShallowClone(), ok
return p, true
}
ip, err := netip.ParseAddr(id)
if err != nil {
return nil, false
}
foundMAC := s.dhcp.MACByIP(ip)
foundMAC := s.dhcp.MACByIP(addr)
if foundMAC != nil {
return s.FindByMAC(foundMAC)
return s.index.findByMAC(foundMAC)
}
return nil, false
@@ -475,6 +578,8 @@ func (s *Storage) Find(id string) (p *Persistent, ok bool) {
//
// Note that multiple clients can have the same IP address with different zones.
// Therefore, the result of this method is indeterminate.
//
// TODO(s.chzhen): Consider accepting [FindParams].
func (s *Storage) FindLoose(ip netip.Addr, id string) (p *Persistent, ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -484,6 +589,11 @@ func (s *Storage) FindLoose(ip netip.Addr, id string) (p *Persistent, ok bool) {
return p.ShallowClone(), ok
}
foundMAC := s.dhcp.MACByIP(ip)
if foundMAC != nil {
return s.index.findByMAC(foundMAC)
}
p = s.index.findByIPWithoutZone(ip)
if p != nil {
return p.ShallowClone(), true
@@ -492,17 +602,6 @@ func (s *Storage) FindLoose(ip netip.Addr, id string) (p *Persistent, ok bool) {
return nil, false
}
// FindByMAC finds persistent client by MAC and returns its shallow copy. s.mu
// is expected to be locked.
func (s *Storage) FindByMAC(mac net.HardwareAddr) (p *Persistent, ok bool) {
p, ok = s.index.findByMAC(mac)
if ok {
return p.ShallowClone(), ok
}
return nil, false
}
// RemoveByName removes persistent client information. ok is false if no such
// client exists by that name.
func (s *Storage) RemoveByName(ctx context.Context, name string) (ok bool) {
@@ -514,12 +613,13 @@ func (s *Storage) RemoveByName(ctx context.Context, name string) (ok bool) {
return false
}
if err := p.CloseUpstreams(); err != nil {
s.logger.ErrorContext(ctx, "removing client", "name", p.Name, slogutil.KeyError, err)
}
s.index.remove(p)
err := s.upstreamManager.remove(p.UID)
if err != nil {
s.logger.DebugContext(ctx, "closing client upstreams", "name", name, slogutil.KeyError, err)
}
return true
}
@@ -556,6 +656,8 @@ func (s *Storage) Update(ctx context.Context, name string, p *Persistent) (err e
s.index.remove(stored)
s.index.add(p)
s.upstreamManager.updateCustomUpstreamConfig(p)
return nil
}
@@ -576,14 +678,6 @@ func (s *Storage) Size() (n int) {
return s.index.size()
}
// closeUpstreams closes upstream configurations of persistent clients.
func (s *Storage) closeUpstreams() (err error) {
s.mu.Lock()
defer s.mu.Unlock()
return s.index.closeUpstreams()
}
// ClientRuntime returns a copy of the saved runtime client by ip. If no such
// client exists, returns nil.
func (s *Storage) ClientRuntime(ip netip.Addr) (rc *Runtime) {
@@ -626,3 +720,83 @@ func (s *Storage) RangeRuntime(f func(rc *Runtime) (cont bool)) {
func (s *Storage) AllowedTags() (tags []string) {
return s.allowedTags
}
// CustomUpstreamConfig implements the [dnsforward.ClientsContainer] interface
// for *Storage
func (s *Storage) CustomUpstreamConfig(
id string,
addr netip.Addr,
) (prxConf *proxy.CustomUpstreamConfig) {
s.mu.Lock()
defer s.mu.Unlock()
c, ok := s.index.findByClientID(ClientID(id))
if !ok {
c, ok = s.findByIP(addr)
}
if !ok {
return nil
}
return s.upstreamManager.customUpstreamConfig(c.UID)
}
// UpdateCommonUpstreamConfig implements the [dnsforward.ClientsContainer]
// interface for *Storage
func (s *Storage) UpdateCommonUpstreamConfig(conf *CommonUpstreamConfig) {
s.mu.Lock()
defer s.mu.Unlock()
s.upstreamManager.updateCommonUpstreamConfig(conf)
}
// ClearUpstreamCache implements the [dnsforward.ClientsContainer] interface for
// *Storage
func (s *Storage) ClearUpstreamCache() {
s.mu.Lock()
defer s.mu.Unlock()
s.upstreamManager.clearUpstreamCache()
}
// ApplyClientFiltering retrieves persistent client information using the
// ClientID or client IP address, and applies it to the filtering settings.
// setts must not be nil.
func (s *Storage) ApplyClientFiltering(id string, addr netip.Addr, setts *filtering.Settings) {
c, ok := s.index.findByClientID(ClientID(id))
if !ok {
c, ok = s.index.findByIP(addr)
}
if !ok {
foundMAC := s.dhcp.MACByIP(addr)
if foundMAC != nil {
c, ok = s.index.findByMAC(foundMAC)
}
}
if !ok {
s.logger.Debug("no client filtering settings found", "clientid", id, "addr", addr)
return
}
s.logger.Debug("applying custom client filtering settings", "client_name", c.Name)
if c.UseOwnBlockedServices {
setts.BlockedServices = c.BlockedServices.Clone()
}
setts.ClientName = c.Name
setts.ClientTags = slices.Clone(c.Tags)
if !c.UseOwnSettings {
return
}
setts.FilteringEnabled = c.FilteringEnabled
setts.SafeSearchEnabled = c.SafeSearchConf.Enabled
setts.ClientSafeSearch = c.SafeSearch
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
setts.ParentalEnabled = c.ParentalEnabled
}

View File

@@ -13,27 +13,35 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/faketime"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// newTestStorage is a helper function that returns initialized storage.
func newTestStorage(tb testing.TB) (s *client.Storage) {
func newTestStorage(tb testing.TB, clock timeutil.Clock) (s *client.Storage) {
tb.Helper()
ctx := testutil.ContextWithTimeout(tb, testTimeout)
s, err := client.NewStorage(ctx, &client.StorageConfig{
Logger: slogutil.NewDiscardLogger(),
Clock: clock,
})
require.NoError(tb, err)
return s
}
// type check
var _ dnsforward.ClientsContainer = (*client.Storage)(nil)
// testHostsContainer is a mock implementation of the [client.HostsContainer]
// interface.
type testHostsContainer struct {
@@ -343,15 +351,15 @@ func TestClientsDHCP(t *testing.T) {
cliName1 = "one.dhcp"
cliIP2 = netip.MustParseAddr("2.2.2.2")
cliMAC2 = mustParseMAC("22:22:22:22:22:22")
cliMAC2 = errors.Must(net.ParseMAC("22:22:22:22:22:22"))
cliName2 = "two.dhcp"
cliIP3 = netip.MustParseAddr("3.3.3.3")
cliMAC3 = mustParseMAC("33:33:33:33:33:33")
cliMAC3 = errors.Must(net.ParseMAC("33:33:33:33:33:33"))
cliName3 = "three.dhcp"
prsCliIP = netip.MustParseAddr("4.3.2.1")
prsCliMAC = mustParseMAC("AA:AA:AA:AA:AA:AA")
prsCliMAC = errors.Must(net.ParseMAC("AA:AA:AA:AA:AA:AA"))
prsCliName = "persistent.dhcp"
otherARPCliName = "other.arp"
@@ -512,7 +520,11 @@ func TestClientsDHCP(t *testing.T) {
})
require.NoError(t, err)
prsCli, ok := storage.Find(prsCliIP.String())
params := &client.FindParams{}
err = params.Set(prsCliIP.String())
require.NoError(t, err)
prsCli, ok := storage.Find(params)
require.True(t, ok)
assert.Equal(t, prsCliName, prsCli.Name)
@@ -656,17 +668,6 @@ func newStorage(tb testing.TB, m []*client.Persistent) (s *client.Storage) {
return s
}
// mustParseMAC is wrapper around [net.ParseMAC] that panics if there is an
// error.
func mustParseMAC(s string) (mac net.HardwareAddr) {
mac, err := net.ParseMAC(s)
if err != nil {
panic(err)
}
return mac
}
func TestStorage_Add(t *testing.T) {
const (
existingName = "existing_name"
@@ -686,12 +687,12 @@ func TestStorage_Add(t *testing.T) {
Name: existingName,
IPs: []netip.Addr{existingIP},
Subnets: []netip.Prefix{existingSubnet},
ClientIDs: []string{existingClientID},
ClientIDs: []client.ClientID{existingClientID},
UID: existingClientUID,
}
ctx := testutil.ContextWithTimeout(t, testTimeout)
s := newTestStorage(t)
s := newTestStorage(t, timeutil.SystemClock{})
tags := s.AllowedTags()
require.NotZero(t, len(tags))
require.True(t, slices.IsSorted(tags))
@@ -754,7 +755,7 @@ func TestStorage_Add(t *testing.T) {
name: "duplicate_client_id",
cli: &client.Persistent{
Name: "duplicate_client_id",
ClientIDs: []string{existingClientID},
ClientIDs: []client.ClientID{existingClientID},
UID: client.MustNewUID(),
},
wantErrMsg: `adding client: another client "existing_name" ` +
@@ -822,7 +823,7 @@ func TestStorage_RemoveByName(t *testing.T) {
}
ctx := testutil.ContextWithTimeout(t, testTimeout)
s := newTestStorage(t)
s := newTestStorage(t, timeutil.SystemClock{})
err := s.Add(ctx, existingClient)
require.NoError(t, err)
@@ -847,7 +848,7 @@ func TestStorage_RemoveByName(t *testing.T) {
}
t.Run("duplicate_remove", func(t *testing.T) {
s = newTestStorage(t)
s = newTestStorage(t, timeutil.SystemClock{})
err = s.Add(ctx, existingClient)
require.NoError(t, err)
@@ -891,12 +892,12 @@ func TestStorage_Find(t *testing.T) {
clientWithMAC = &client.Persistent{
Name: "client_with_mac",
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
MACs: []net.HardwareAddr{errors.Must(net.ParseMAC(cliMAC))},
}
clientWithID = &client.Persistent{
Name: "client_with_id",
ClientIDs: []string{cliID},
ClientIDs: []client.ClientID{cliID},
}
clientLinkLocal = &client.Persistent{
@@ -943,7 +944,11 @@ func TestStorage_Find(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for _, id := range tc.ids {
c, ok := s.Find(id)
params := &client.FindParams{}
err := params.Set(id)
require.NoError(t, err)
c, ok := s.Find(params)
require.True(t, ok)
assert.Equal(t, tc.want, c)
@@ -952,7 +957,11 @@ func TestStorage_Find(t *testing.T) {
}
t.Run("not_found", func(t *testing.T) {
_, ok := s.Find(cliIPNone)
params := &client.FindParams{}
err := params.Set(cliIPNone)
require.NoError(t, err)
_, ok := s.Find(params)
assert.False(t, ok)
})
}
@@ -1018,127 +1027,6 @@ func TestStorage_FindLoose(t *testing.T) {
}
}
func TestStorage_FindByName(t *testing.T) {
const (
cliIP1 = "1.1.1.1"
cliIP2 = "2.2.2.2"
)
const (
clientExistingName = "client_existing"
clientAnotherExistingName = "client_another_existing"
nonExistingClientName = "client_non_existing"
)
var (
clientExisting = &client.Persistent{
Name: clientExistingName,
IPs: []netip.Addr{netip.MustParseAddr(cliIP1)},
}
clientAnotherExisting = &client.Persistent{
Name: clientAnotherExistingName,
IPs: []netip.Addr{netip.MustParseAddr(cliIP2)},
}
)
clients := []*client.Persistent{
clientExisting,
clientAnotherExisting,
}
s := newStorage(t, clients)
testCases := []struct {
want *client.Persistent
name string
clientName string
}{{
name: "existing",
clientName: clientExistingName,
want: clientExisting,
}, {
name: "another_existing",
clientName: clientAnotherExistingName,
want: clientAnotherExisting,
}, {
name: "non_existing",
clientName: nonExistingClientName,
want: nil,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
c, ok := s.FindByName(tc.clientName)
if tc.want == nil {
assert.False(t, ok)
return
}
assert.True(t, ok)
assert.Equal(t, tc.want, c)
})
}
}
func TestStorage_FindByMAC(t *testing.T) {
var (
cliMAC = mustParseMAC("11:11:11:11:11:11")
cliAnotherMAC = mustParseMAC("22:22:22:22:22:22")
nonExistingClientMAC = mustParseMAC("33:33:33:33:33:33")
)
var (
clientExisting = &client.Persistent{
Name: "client",
MACs: []net.HardwareAddr{cliMAC},
}
clientAnotherExisting = &client.Persistent{
Name: "another_client",
MACs: []net.HardwareAddr{cliAnotherMAC},
}
)
clients := []*client.Persistent{
clientExisting,
clientAnotherExisting,
}
s := newStorage(t, clients)
testCases := []struct {
want *client.Persistent
name string
clientMAC net.HardwareAddr
}{{
name: "existing",
clientMAC: cliMAC,
want: clientExisting,
}, {
name: "another_existing",
clientMAC: cliAnotherMAC,
want: clientAnotherExisting,
}, {
name: "non_existing",
clientMAC: nonExistingClientMAC,
want: nil,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
c, ok := s.FindByMAC(tc.clientMAC)
if tc.want == nil {
assert.False(t, ok)
return
}
assert.True(t, ok)
assert.Equal(t, tc.want, c)
})
}
}
func TestStorage_Update(t *testing.T) {
const (
clientName = "client_name"
@@ -1155,7 +1043,7 @@ func TestStorage_Update(t *testing.T) {
Name: obstructingName,
IPs: []netip.Addr{obstructingIP},
Subnets: []netip.Prefix{obstructingSubnet},
ClientIDs: []string{obstructingClientID},
ClientIDs: []client.ClientID{obstructingClientID},
}
clientToUpdate := &client.Persistent{
@@ -1204,7 +1092,7 @@ func TestStorage_Update(t *testing.T) {
name: "duplicate_client_id",
cli: &client.Persistent{
Name: "duplicate_client_id",
ClientIDs: []string{obstructingClientID},
ClientIDs: []client.ClientID{obstructingClientID},
UID: client.MustNewUID(),
},
wantErrMsg: `updating client: another client "obstructing_name" ` +
@@ -1231,19 +1119,19 @@ func TestStorage_Update(t *testing.T) {
func TestStorage_RangeByName(t *testing.T) {
sortedClients := []*client.Persistent{{
Name: "clientA",
ClientIDs: []string{"A"},
ClientIDs: []client.ClientID{"A"},
}, {
Name: "clientB",
ClientIDs: []string{"B"},
ClientIDs: []client.ClientID{"B"},
}, {
Name: "clientC",
ClientIDs: []string{"C"},
ClientIDs: []client.ClientID{"C"},
}, {
Name: "clientD",
ClientIDs: []string{"D"},
ClientIDs: []client.ClientID{"D"},
}, {
Name: "clientE",
ClientIDs: []string{"E"},
ClientIDs: []client.ClientID{"E"},
}}
testCases := []struct {
@@ -1278,3 +1166,320 @@ func TestStorage_RangeByName(t *testing.T) {
})
}
}
func TestStorage_CustomUpstreamConfig(t *testing.T) {
const (
existingClientID = "existing_client_id"
nonExistingClientID = "non_existing_client_id"
)
var (
existingIP = netip.MustParseAddr("192.0.2.1")
nonExistingIP = netip.MustParseAddr("192.0.2.255")
dhcpCliIP = netip.MustParseAddr("192.0.2.2")
dhcpCliMAC = errors.Must(net.ParseMAC("02:00:00:00:00:00"))
testUpstreamTimeout = time.Second
)
date := time.Now()
clock := &faketime.Clock{
OnNow: func() (now time.Time) {
date = date.Add(time.Second)
return date
},
}
ipToMAC := map[netip.Addr]net.HardwareAddr{
dhcpCliIP: dhcpCliMAC,
}
dhcp := &testDHCP{
OnLeases: func() (ls []*dhcpsvc.Lease) {
panic("not implemented")
},
OnHostBy: func(ip netip.Addr) (host string) {
panic("not implemented")
},
OnMACBy: func(ip netip.Addr) (mac net.HardwareAddr) {
return ipToMAC[ip]
},
}
ctx := testutil.ContextWithTimeout(t, testTimeout)
s, err := client.NewStorage(ctx, &client.StorageConfig{
Logger: slogutil.NewDiscardLogger(),
Clock: clock,
DHCP: dhcp,
})
require.NoError(t, err)
s.UpdateCommonUpstreamConfig(&client.CommonUpstreamConfig{
UpstreamTimeout: testUpstreamTimeout,
})
testutil.CleanupAndRequireSuccess(t, func() (err error) {
return s.Shutdown(testutil.ContextWithTimeout(t, testTimeout))
})
err = s.Add(ctx, &client.Persistent{
Name: "client_first",
IPs: []netip.Addr{existingIP},
ClientIDs: []client.ClientID{existingClientID},
UID: client.MustNewUID(),
Upstreams: []string{"192.0.2.0"},
})
require.NoError(t, err)
err = s.Add(ctx, &client.Persistent{
Name: "client_second",
MACs: []net.HardwareAddr{dhcpCliMAC},
UID: client.MustNewUID(),
Upstreams: []string{"192.0.2.0"},
})
require.NoError(t, err)
testCases := []struct {
cliAddr netip.Addr
wantNilConf assert.ValueAssertionFunc
name string
cliID string
}{{
name: "client_id",
cliID: existingClientID,
cliAddr: netip.Addr{},
wantNilConf: assert.NotNil,
}, {
name: "client_addr",
cliID: "",
cliAddr: existingIP,
wantNilConf: assert.NotNil,
}, {
name: "client_dhcp",
cliID: "",
cliAddr: dhcpCliIP,
wantNilConf: assert.NotNil,
}, {
name: "non_existing_client_id",
cliID: nonExistingClientID,
cliAddr: netip.Addr{},
wantNilConf: assert.Nil,
}, {
name: "non_existing_client_addr",
cliID: "",
cliAddr: nonExistingIP,
wantNilConf: assert.Nil,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
conf := s.CustomUpstreamConfig(tc.cliID, tc.cliAddr)
tc.wantNilConf(t, conf)
})
}
t.Run("update_common_config", func(t *testing.T) {
conf := s.CustomUpstreamConfig(existingClientID, existingIP)
require.NotNil(t, conf)
s.UpdateCommonUpstreamConfig(&client.CommonUpstreamConfig{
UpstreamTimeout: testUpstreamTimeout * 2,
})
updConf := s.CustomUpstreamConfig(existingClientID, existingIP)
require.NotNil(t, updConf)
assert.NotEqual(t, conf, updConf)
})
t.Run("same_custom_config", func(t *testing.T) {
firstConf := s.CustomUpstreamConfig(existingClientID, existingIP)
require.NotNil(t, firstConf)
secondConf := s.CustomUpstreamConfig(existingClientID, existingIP)
require.NotNil(t, secondConf)
assert.Same(t, firstConf, secondConf)
})
}
func BenchmarkFindParams_Set(b *testing.B) {
const (
testIPStr = "192.0.2.1"
testCIDRStr = "192.0.2.0/24"
testMACStr = "02:00:00:00:00:00"
testClientID = "clientid"
)
benchCases := []struct {
wantErr error
params *client.FindParams
name string
id string
}{{
wantErr: nil,
params: &client.FindParams{
ClientID: testClientID,
},
name: "client_id",
id: testClientID,
}, {
wantErr: nil,
params: &client.FindParams{
RemoteIP: netip.MustParseAddr(testIPStr),
},
name: "ip_address",
id: testIPStr,
}, {
wantErr: nil,
params: &client.FindParams{
Subnet: netip.MustParsePrefix(testCIDRStr),
},
name: "subnet",
id: testCIDRStr,
}, {
wantErr: nil,
params: &client.FindParams{
MAC: errors.Must(net.ParseMAC(testMACStr)),
},
name: "mac_address",
id: testMACStr,
}, {
wantErr: client.ErrBadIdentifier,
params: &client.FindParams{},
name: "bad_id",
id: "!@#$%^&*()_+",
}}
for _, bc := range benchCases {
b.Run(bc.name, func(b *testing.B) {
params := &client.FindParams{}
var err error
b.ReportAllocs()
for b.Loop() {
err = params.Set(bc.id)
}
assert.ErrorIs(b, err, bc.wantErr)
assert.Equal(b, bc.params, params)
})
}
// Most recent results:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardHome/internal/client
// cpu: Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz
// BenchmarkFindParams_Set/client_id-8 49463488 24.27 ns/op 0 B/op 0 allocs/op
// BenchmarkFindParams_Set/ip_address-8 18740977 62.22 ns/op 0 B/op 0 allocs/op
// BenchmarkFindParams_Set/subnet-8 10848192 110.0 ns/op 0 B/op 0 allocs/op
// BenchmarkFindParams_Set/mac_address-8 8148494 133.2 ns/op 8 B/op 1 allocs/op
// BenchmarkFindParams_Set/bad_id-8 73894278 16.29 ns/op 0 B/op 0 allocs/op
}
func BenchmarkStorage_Find(b *testing.B) {
const (
cliID = "cid"
cliMAC = "02:00:00:00:00:00"
)
const (
cliNameWithID = "client_with_id"
cliNameWithIP = "client_with_ip"
cliNameWithCIDR = "client_with_cidr"
cliNameWithMAC = "client_with_mac"
)
var (
cliIP = netip.MustParseAddr("192.0.2.1")
cliCIDR = netip.MustParsePrefix("192.0.2.0/24")
)
var (
clientWithID = &client.Persistent{
Name: cliNameWithID,
ClientIDs: []client.ClientID{cliID},
}
clientWithIP = &client.Persistent{
Name: cliNameWithIP,
IPs: []netip.Addr{cliIP},
}
clientWithCIDR = &client.Persistent{
Name: cliNameWithCIDR,
Subnets: []netip.Prefix{cliCIDR},
}
clientWithMAC = &client.Persistent{
Name: cliNameWithMAC,
MACs: []net.HardwareAddr{errors.Must(net.ParseMAC(cliMAC))},
}
)
clients := []*client.Persistent{
clientWithID,
clientWithIP,
clientWithCIDR,
clientWithMAC,
}
s := newStorage(b, clients)
benchCases := []struct {
params *client.FindParams
name string
wantName string
}{{
params: &client.FindParams{
ClientID: cliID,
},
name: "client_id",
wantName: cliNameWithID,
}, {
params: &client.FindParams{
RemoteIP: cliIP,
},
name: "ip_address",
wantName: cliNameWithIP,
}, {
params: &client.FindParams{
Subnet: cliCIDR,
},
name: "subnet",
wantName: cliNameWithCIDR,
}, {
params: &client.FindParams{
MAC: errors.Must(net.ParseMAC(cliMAC)),
},
name: "mac_address",
wantName: cliNameWithMAC,
}}
for _, bc := range benchCases {
b.Run(bc.name, func(b *testing.B) {
var p *client.Persistent
var ok bool
b.ReportAllocs()
for b.Loop() {
p, ok = s.Find(bc.params)
}
assert.True(b, ok)
assert.NotNil(b, p)
assert.Equal(b, bc.wantName, p.Name)
})
}
// Most recent results:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardHome/internal/client
// cpu: Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz
// BenchmarkStorage_Find/client_id-8 7070107 154.4 ns/op 240 B/op 2 allocs/op
// BenchmarkStorage_Find/ip_address-8 6831823 168.6 ns/op 248 B/op 2 allocs/op
// BenchmarkStorage_Find/subnet-8 7209050 167.5 ns/op 256 B/op 2 allocs/op
// BenchmarkStorage_Find/mac_address-8 5776131 199.7 ns/op 256 B/op 3 allocs/op
}

View File

@@ -0,0 +1,227 @@
package client
import (
"fmt"
"log/slog"
"slices"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/AdguardTeam/golibs/timeutil"
)
// CommonUpstreamConfig contains common settings for custom client upstream
// configurations.
type CommonUpstreamConfig struct {
Bootstrap upstream.Resolver
UpstreamTimeout time.Duration
BootstrapPreferIPv6 bool
EDNSClientSubnetEnabled bool
UseHTTP3Upstreams bool
}
// customUpstreamConfig contains custom client upstream configuration and the
// timestamp of the latest configuration update.
type customUpstreamConfig struct {
// proxyConf is the constructed upstream configuration for the [proxy],
// derived from the fields below. It is initialized on demand with
// [newCustomUpstreamConfig].
proxyConf *proxy.CustomUpstreamConfig
// commonConfUpdate is the timestamp of the latest configuration update,
// used to check against [upstreamManager.confUpdate] to determine if the
// configuration is up to date.
commonConfUpdate time.Time
// upstreams is the cached list of custom upstream DNS servers used for the
// configuration of proxyConf.
upstreams []string
// upstreamsCacheSize is the cached value of the cache size of the
// upstreams, used for the configuration of proxyConf.
upstreamsCacheSize uint32
// upstreamsCacheEnabled is the cached value indicating whether the cache of
// the upstreams is enabled for the configuration of proxyConf.
upstreamsCacheEnabled bool
// isChanged indicates whether the proxyConf needs to be updated.
isChanged bool
}
// upstreamManager stores and updates custom client upstream configurations.
type upstreamManager struct {
// logger is used for logging the operation of the upstream manager. It
// must not be nil.
//
// TODO(s.chzhen): Consider using a logger with its own prefix.
logger *slog.Logger
// uidToCustomConf maps persistent client UID to the custom client upstream
// configuration. Stored UIDs must be in sync with the [index.uidToClient].
uidToCustomConf map[UID]*customUpstreamConfig
// commonConf is the common upstream configuration.
commonConf *CommonUpstreamConfig
// clock is used to get the current time. It must not be nil.
clock timeutil.Clock
// confUpdate is the timestamp of the latest common upstream configuration
// update.
confUpdate time.Time
}
// newUpstreamManager returns the new properly initialized upstream manager.
func newUpstreamManager(logger *slog.Logger, clock timeutil.Clock) (m *upstreamManager) {
return &upstreamManager{
logger: logger,
uidToCustomConf: make(map[UID]*customUpstreamConfig),
clock: clock,
}
}
// updateCommonUpstreamConfig updates the common upstream configuration and the
// timestamp of the latest configuration update.
func (m *upstreamManager) updateCommonUpstreamConfig(conf *CommonUpstreamConfig) {
m.commonConf = conf
m.confUpdate = m.clock.Now()
}
// updateCustomUpstreamConfig updates the stored custom client upstream
// configuration associated with the persistent client. It also sets
// [customUpstreamConfig.isChanged] to true so [customUpstreamConfig.proxyConf]
// can be updated later in [upstreamManager.customUpstreamConfig].
func (m *upstreamManager) updateCustomUpstreamConfig(c *Persistent) {
cliConf, ok := m.uidToCustomConf[c.UID]
if !ok {
cliConf = &customUpstreamConfig{
commonConfUpdate: m.confUpdate,
}
m.uidToCustomConf[c.UID] = cliConf
}
// TODO(s.chzhen): Compare before cloning.
cliConf.upstreams = slices.Clone(c.Upstreams)
cliConf.upstreamsCacheSize = c.UpstreamsCacheSize
cliConf.upstreamsCacheEnabled = c.UpstreamsCacheEnabled
cliConf.isChanged = true
}
// customUpstreamConfig returns the custom client upstream configuration.
func (m *upstreamManager) customUpstreamConfig(uid UID) (proxyConf *proxy.CustomUpstreamConfig) {
cliConf, ok := m.uidToCustomConf[uid]
if !ok {
// TODO(s.chzhen): Consider panic.
m.logger.Error("no associated custom client upstream config")
return nil
}
if !m.isConfigChanged(cliConf) {
return cliConf.proxyConf
}
if cliConf.proxyConf != nil {
err := cliConf.proxyConf.Close()
if err != nil {
// TODO(s.chzhen): Pass context.
m.logger.Debug("closing custom upstream config", slogutil.KeyError, err)
}
}
proxyConf = newCustomUpstreamConfig(cliConf, m.commonConf)
cliConf.proxyConf = proxyConf
cliConf.commonConfUpdate = m.confUpdate
cliConf.isChanged = false
return proxyConf
}
// isConfigChanged returns true if the update is necessary for the custom client
// upstream configuration.
func (m *upstreamManager) isConfigChanged(cliConf *customUpstreamConfig) (ok bool) {
return !m.confUpdate.Equal(cliConf.commonConfUpdate) || cliConf.isChanged
}
// clearUpstreamCache clears the upstream cache for each stored custom client
// upstream configuration.
func (m *upstreamManager) clearUpstreamCache() {
for _, c := range m.uidToCustomConf {
if c.proxyConf != nil {
c.proxyConf.ClearCache()
}
}
}
// remove deletes the custom client upstream configuration and closes
// [customUpstreamConfig.proxyConf] if necessary.
func (m *upstreamManager) remove(uid UID) (err error) {
cliConf, ok := m.uidToCustomConf[uid]
if !ok {
// TODO(s.chzhen): Consider panic.
return errors.Error("no associated custom client upstream config")
}
delete(m.uidToCustomConf, uid)
if cliConf.proxyConf != nil {
return cliConf.proxyConf.Close()
}
return nil
}
// close shuts down each stored custom client upstream configuration.
func (m *upstreamManager) close() (err error) {
var errs []error
for _, c := range m.uidToCustomConf {
if c.proxyConf == nil {
continue
}
errs = append(errs, c.proxyConf.Close())
}
return errors.Join(errs...)
}
// newCustomUpstreamConfig returns the new properly initialized custom proxy
// upstream configuration for the client.
func newCustomUpstreamConfig(
cliConf *customUpstreamConfig,
conf *CommonUpstreamConfig,
) (proxyConf *proxy.CustomUpstreamConfig) {
upstreams := stringutil.FilterOut(cliConf.upstreams, aghnet.IsCommentOrEmpty)
if len(upstreams) == 0 {
return nil
}
upsConf, err := proxy.ParseUpstreamsConfig(
upstreams,
&upstream.Options{
Bootstrap: conf.Bootstrap,
Timeout: time.Duration(conf.UpstreamTimeout),
HTTPVersions: aghnet.UpstreamHTTPVersions(conf.UseHTTP3Upstreams),
PreferIPv6: conf.BootstrapPreferIPv6,
},
)
if err != nil {
// Should not happen because upstreams are already validated. See
// [Persistent.validate].
panic(fmt.Errorf("creating custom upstream config: %w", err))
}
return proxy.NewCustomUpstreamConfig(
upsConf,
cliConf.upstreamsCacheEnabled,
int(cliConf.upstreamsCacheSize),
conf.EDNSClientSubnetEnabled,
)
}

View File

@@ -4,6 +4,7 @@ import (
"encoding/binary"
"fmt"
"net"
"slices"
"sync/atomic"
"time"
@@ -14,18 +15,48 @@ import (
"golang.org/x/net/ipv6"
)
// raCtx is a context for the Router Advertisement logic.
type raCtx struct {
raAllowSLAAC bool // send RA packets without MO flags
raSLAACOnly bool // send RA packets with MO flags
ipAddr net.IP // source IP address (link-local-unicast)
dnsIPAddr net.IP // IP address for DNS Server option
prefixIPAddr net.IP // IP address for Prefix option
ifaceName string
iface *net.Interface
packetSendPeriod time.Duration // how often RA packets are sent
// raAllowSLAAC is used to determine if the ICMP Router Advertisement
// messages should be sent.
//
// If both raAllowSLAAC and raSLAACOnly are false, the Router Advertisement
// messages aren't sent.
raAllowSLAAC bool
conn *icmp.PacketConn // ICMPv6 socket
stop atomic.Value // stop the packet sending loop
// raSLAACOnly is used to determine if the ICMP Router Advertisement
// messages should set M and O flags, see RFC 4861, section 4.2.
//
// If both raAllowSLAAC and raSLAACOnly are false, the Router Advertisement
// messages aren't sent.
raSLAACOnly bool
// ipAddr is an IP address used within the Source Link-Layer Address option.
// See RFC 4861, section 4.6.1.
ipAddr net.IP
// dnsIPAddr is an IP address used within the DNS Server option.
dnsIPAddr net.IP
// prefixIPAddr is an IP address used within the Prefix Information option.
// See RFC 4861, section 4.6.2.
prefixIPAddr net.IP
// ifaceName is the name of the interface used as a scope of the IP
// addresses.
ifaceName string
// iface is the network interface used to send the ICMPv6 packets.
iface *net.Interface
// packetSendPeriod is the interval between sending the ICMPv6 packets.
packetSendPeriod time.Duration
// conn is the ICMPv6 socket.
conn *icmp.PacketConn
// stop is used to stop the packet sending loop.
stop atomic.Value
}
type icmpv6RA struct {
@@ -38,10 +69,11 @@ type icmpv6RA struct {
mtu uint32
}
// hwAddrToLinkLayerAddr converts a hardware address into a form required by
// RFC4861. That is, a byte slice of length divisible by 8.
// hwAddrToLinkLayerAddr clones the hardware address and returns it as a byte
// slice suitable for the Source Link-Layer Address option in the ICMPv6
// Router Advertisement packet.
//
// See https://tools.ietf.org/html/rfc4861#section-4.6.1.
// TODO(e.burkov): Check if it's safe to use the original slice.
func hwAddrToLinkLayerAddr(hwa net.HardwareAddr) (lla []byte, err error) {
err = netutil.ValidateMAC(hwa)
if err != nil {
@@ -50,19 +82,7 @@ func hwAddrToLinkLayerAddr(hwa net.HardwareAddr) (lla []byte, err error) {
return nil, err
}
if len(hwa) == 6 || len(hwa) == 8 {
lla = make([]byte, 8)
copy(lla, hwa)
return lla, nil
}
// Assume that netutil.ValidateMAC prevents lengths other than 20 by
// now.
lla = make([]byte, 24)
copy(lla, hwa)
return lla, nil
return slices.Clone(hwa), nil
}
// Create an ICMPv6.RouterAdvertisement packet with all necessary options.
@@ -103,15 +123,24 @@ func hwAddrToLinkLayerAddr(hwa net.HardwareAddr) (lla []byte, err error) {
//
// TODO(a.garipov): Replace with an existing implementation from a dependency.
func createICMPv6RAPacket(params icmpv6RA) (data []byte, err error) {
var lla []byte
lla, err = hwAddrToLinkLayerAddr(params.sourceLinkLayerAddress)
lla, err := hwAddrToLinkLayerAddr(params.sourceLinkLayerAddress)
if err != nil {
return nil, fmt.Errorf("converting source link layer address: %w", err)
return nil, fmt.Errorf("converting source link-layer address: %w", err)
}
// Calculate length of the source link-layer address option. As per RFC
// 4861, section 4.6.1, the length should be in units of 8 octets, including
// the type and length fields.
//
// See https://datatracker.ietf.org/doc/html/rfc4861#section-4.6.1.
srcLLAOptLen := len(lla) + 2
// Make sure the value is rounded up to the nearest multiple of 8.
srcLLAOptLenValue := (srcLLAOptLen + 7) / 8
srcLLAPadLen := srcLLAOptLenValue*8 - srcLLAOptLen
// TODO(a.garipov): Don't use a magic constant here. Refactor the code
// and make all constants named instead of all those comments..
data = make([]byte, 82+len(lla))
// and make all constants named instead of all those comments.
data = make([]byte, 80+srcLLAOptLen+srcLLAPadLen)
i := 0
// ICMPv6:
@@ -175,12 +204,11 @@ func createICMPv6RAPacket(params icmpv6RA) (data []byte, err error) {
// Option=Source link-layer address:
data[i] = 1 // Type
data[i+1] = 1 // Length
data[i] = 1 // Type
data[i+1] = byte(srcLLAOptLenValue) // Length
i += 2
copy(data[i:], lla) // Link-Layer Address[8/24]
i += len(lla)
i += len(lla) + srcLLAPadLen
// Option=Recursive DNS Server:

View File

@@ -0,0 +1,63 @@
package dhcpd
import (
"net"
"testing"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCreateICMPv6RAPacket(t *testing.T) {
raConf := icmpv6RA{
managedAddressConfiguration: false,
otherConfiguration: true,
mtu: 1500,
prefix: net.ParseIP("1234::"),
prefixLen: 64,
recursiveDNSServer: net.ParseIP("fe80::800:27ff:fe00:0"),
sourceLinkLayerAddress: []byte{0x0A, 0x00, 0x27, 0x00, 0x00, 0x00},
}
pkt, err := createICMPv6RAPacket(raConf)
require.NoError(t, err)
icmpPkt := &layers.ICMPv6{}
err = icmpPkt.DecodeFromBytes(pkt, gopacket.NilDecodeFeedback)
require.NoError(t, err)
require.Equal(t, layers.LayerTypeICMPv6RouterAdvertisement, icmpPkt.NextLayerType())
raPkt := &layers.ICMPv6RouterAdvertisement{}
err = raPkt.DecodeFromBytes(icmpPkt.LayerPayload(), gopacket.NilDecodeFeedback)
require.NoError(t, err)
assert.Equal(t, raConf.managedAddressConfiguration, raPkt.ManagedAddressConfig())
assert.Equal(t, raConf.otherConfiguration, raPkt.OtherConfig())
wantOpts := layers.ICMPv6Options{{
Type: layers.ICMPv6OptPrefixInfo,
Data: []uint8{
0x40, 0xC0, 0x00, 0x00, 0x0E, 0x10, 0x00, 0x00,
0x0E, 0x10, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
}, {
Type: layers.ICMPv6OptMTU,
Data: []uint8{0x00, 0x00, 0x00, 0x00, 0x05, 0xDC},
}, {
Type: layers.ICMPv6OptSourceAddress,
Data: []uint8{0x0A, 0x00, 0x27, 0x00, 0x00, 0x0},
}, {
// Package layers declares no constant for Recursive DNS Server option.
Type: layers.ICMPv6Opt(25),
Data: []uint8{
0x00, 0x00, 0x00, 0x00, 0x0E, 0x10, 0xFE, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
0x27, 0xFF, 0xFE, 0x00, 0x00, 0x00,
},
}}
assert.Equal(t, wantOpts, raPkt.Options)
}

View File

@@ -1,38 +0,0 @@
package dhcpd
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateICMPv6RAPacket(t *testing.T) {
wantData := []byte{
0x86, 0x00, 0x00, 0x00, 0x40, 0x40, 0x07, 0x08,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x0e, 0x10,
0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00,
0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0xdc,
0x01, 0x01, 0x0a, 0x00, 0x27, 0x00, 0x00, 0x00,
0x00, 0x00, 0x19, 0x03, 0x00, 0x00, 0x00, 0x00,
0x0e, 0x10, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x08, 0x00, 0x27, 0xff, 0xfe, 0x00,
0x00, 0x00,
}
gotData, err := createICMPv6RAPacket(icmpv6RA{
managedAddressConfiguration: false,
otherConfiguration: true,
mtu: 1500,
prefix: net.ParseIP("1234::"),
prefixLen: 64,
recursiveDNSServer: net.ParseIP("fe80::800:27ff:fe00:0"),
sourceLinkLayerAddress: []byte{0x0a, 0x00, 0x27, 0x00, 0x00, 0x00},
})
assert.NoError(t, err)
assert.Equal(t, wantData, gotData)
}

View File

@@ -1,13 +1,11 @@
package dhcpsvc_test
import (
"net"
"net/netip"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/stretchr/testify/require"
)
// testLocalTLD is a common local TLD for tests.
@@ -56,11 +54,3 @@ var testInterfaceConf = map[string]*dhcpsvc.InterfaceConfig{
},
},
}
// mustParseMAC parses a hardware address from s and requires no errors.
func mustParseMAC(t require.TestingT, s string) (mac net.HardwareAddr) {
mac, err := net.ParseMAC(s)
require.NoError(t, err)
return mac
}

View File

@@ -2,6 +2,7 @@ package dhcpsvc_test
import (
"io/fs"
"net"
"net/netip"
"os"
"path"
@@ -11,6 +12,7 @@ import (
"time"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -176,9 +178,9 @@ func TestDHCPServer_AddLease(t *testing.T) {
newIP = netip.MustParseAddr("192.168.0.3")
newIPv6 = netip.MustParseAddr("2001:db8::2")
existMAC = mustParseMAC(t, "01:02:03:04:05:06")
newMAC = mustParseMAC(t, "06:05:04:03:02:01")
ipv6MAC = mustParseMAC(t, "02:03:04:05:06:07")
existMAC = errors.Must(net.ParseMAC("01:02:03:04:05:06"))
newMAC = errors.Must(net.ParseMAC("06:05:04:03:02:01"))
ipv6MAC = errors.Must(net.ParseMAC("02:03:04:05:06:07"))
)
require.NoError(t, srv.AddLease(ctx, &dhcpsvc.Lease{
@@ -291,9 +293,9 @@ func TestDHCPServer_index(t *testing.T) {
ip3 = netip.MustParseAddr("172.16.0.3")
ip4 = netip.MustParseAddr("172.16.0.4")
mac1 = mustParseMAC(t, "01:02:03:04:05:06")
mac2 = mustParseMAC(t, "06:05:04:03:02:01")
mac3 = mustParseMAC(t, "02:03:04:05:06:07")
mac1 = errors.Must(net.ParseMAC("01:02:03:04:05:06"))
mac2 = errors.Must(net.ParseMAC("06:05:04:03:02:01"))
mac3 = errors.Must(net.ParseMAC("02:03:04:05:06:07"))
)
t.Run("ip_idx", func(t *testing.T) {
@@ -349,9 +351,9 @@ func TestDHCPServer_UpdateStaticLease(t *testing.T) {
ip3 = netip.MustParseAddr("192.168.0.4")
ip4 = netip.MustParseAddr("2001:db8::3")
mac1 = mustParseMAC(t, "01:02:03:04:05:06")
mac2 = mustParseMAC(t, "06:05:04:03:02:01")
mac3 = mustParseMAC(t, "06:05:04:03:02:02")
mac1 = errors.Must(net.ParseMAC("01:02:03:04:05:06"))
mac2 = errors.Must(net.ParseMAC("06:05:04:03:02:01"))
mac3 = errors.Must(net.ParseMAC("06:05:04:03:02:02"))
)
testCases := []struct {
@@ -452,9 +454,9 @@ func TestDHCPServer_RemoveLease(t *testing.T) {
newIP = netip.MustParseAddr("192.168.0.3")
newIPv6 = netip.MustParseAddr("2001:db8::2")
existMAC = mustParseMAC(t, "01:02:03:04:05:06")
newMAC = mustParseMAC(t, "02:03:04:05:06:07")
ipv6MAC = mustParseMAC(t, "06:05:04:03:02:01")
existMAC = errors.Must(net.ParseMAC("01:02:03:04:05:06"))
newMAC = errors.Must(net.ParseMAC("02:03:04:05:06:07"))
ipv6MAC = errors.Must(net.ParseMAC("06:05:04:03:02:01"))
)
testCases := []struct {
@@ -559,13 +561,13 @@ func TestServer_Leases(t *testing.T) {
Expiry: expiry,
IP: netip.MustParseAddr("192.168.0.3"),
Hostname: "example.host",
HWAddr: mustParseMAC(t, "AA:AA:AA:AA:AA:AA"),
HWAddr: errors.Must(net.ParseMAC("AA:AA:AA:AA:AA:AA")),
IsStatic: false,
}, {
Expiry: time.Time{},
IP: netip.MustParseAddr("192.168.0.4"),
Hostname: "example.static.host",
HWAddr: mustParseMAC(t, "BB:BB:BB:BB:BB:BB"),
HWAddr: errors.Must(net.ParseMAC("BB:BB:BB:BB:BB:BB")),
IsStatic: true,
}}
assert.ElementsMatch(t, wantLeases, srv.Leases())

View File

@@ -10,6 +10,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
@@ -51,7 +52,7 @@ func processAccessClients(
} else if ipnet, err = netip.ParsePrefix(s); err == nil {
*nets = append(*nets, ipnet)
} else {
err = ValidateClientID(s)
err = client.ValidateClientID(s)
if err != nil {
return fmt.Errorf("value %q at index %d: bad ip, cidr, or clientid", s, i)
}
@@ -239,7 +240,7 @@ func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {
err = validateAccessSet(list)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, err.Error())
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}

View File

@@ -15,7 +15,7 @@ import (
var _ proxy.BeforeRequestHandler = (*Server)(nil)
// HandleBefore is the handler that is called before any other processing,
// including logs. It performs access checks and puts the client ID, if there
// including logs. It performs access checks and puts the ClientID, if there
// is one, into the server's cache.
//
// TODO(d.kolyshev): Extract to separate package.
@@ -74,7 +74,7 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
return "", nil
}
hostSrvName := s.conf.ServerName
hostSrvName := s.conf.TLSConf.ServerName
if hostSrvName == "" {
return "", nil
}
@@ -87,7 +87,7 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
clientID, err = clientIDFromClientServerName(
hostSrvName,
cliSrvName,
s.conf.StrictSNICheck,
s.conf.TLSConf.StrictSNICheck,
)
if err != nil {
return "", fmt.Errorf("clientid check: %w", err)

View File

@@ -121,7 +121,7 @@ func TestServer_HandleBefore_tls(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
s, _ := createTestTLS(t, TLSConfig{
s, _ := createTestTLS(t, &TLSConfig{
TLSListenAddrs: []*net.TCPAddr{{}},
ServerName: tlsServerName,
})
@@ -259,6 +259,7 @@ func TestServer_HandleBefore_udp(t *testing.T) {
}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
TLSConf: &TLSConfig{},
Config: Config{
AllowedClients: tc.allowedClients,
DisallowedClients: tc.disallowedClients,
@@ -266,6 +267,7 @@ func TestServer_HandleBefore_udp(t *testing.T) {
UpstreamDNS: []string{localUpsAddr},
UpstreamMode: UpstreamModeLoadBalance,
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
ClientsContainer: EmptyClientsContainer{},
},
ServePlainDNS: true,
})

View File

@@ -7,26 +7,13 @@ import (
"path"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/quic-go/quic-go"
)
// ValidateClientID returns an error if id is not a valid ClientID.
//
// Keep in sync with [client.ValidateClientID].
func ValidateClientID(id string) (err error) {
err = netutil.ValidateHostnameLabel(id)
if err != nil {
// Replace the domain name label wrapper with our own.
return fmt.Errorf("invalid clientid %q: %w", id, errors.Unwrap(err))
}
return nil
}
// clientIDFromClientServerName extracts and validates a ClientID. hostSrvName
// is the server name of the host. cliSrvName is the server name as sent by the
// client. When strict is true, and client and host server name don't match,
@@ -53,7 +40,7 @@ func clientIDFromClientServerName(
}
clientID = cliSrvName[:len(cliSrvName)-len(hostSrvName)-1]
err = ValidateClientID(clientID)
err = client.ValidateClientID(clientID)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return "", err
@@ -62,7 +49,7 @@ func clientIDFromClientServerName(
return strings.ToLower(clientID), nil
}
// clientIDFromDNSContextHTTPS extracts the client's ID from the path of the
// clientIDFromDNSContextHTTPS extracts the ClientID from the path of the
// client's DNS-over-HTTPS request.
func clientIDFromDNSContextHTTPS(pctx *proxy.DNSContext) (clientID string, err error) {
r := pctx.HTTPRequest
@@ -93,7 +80,7 @@ func clientIDFromDNSContextHTTPS(pctx *proxy.DNSContext) (clientID string, err e
return "", fmt.Errorf("clientid check: invalid path %q: extra parts", origPath)
}
err = ValidateClientID(clientID)
err = client.ValidateClientID(clientID)
if err != nil {
return "", fmt.Errorf("clientid check: %w", err)
}

View File

@@ -212,13 +212,13 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tlsConf := TLSConfig{
tlsConf := &TLSConfig{
ServerName: tc.confSrvName,
StrictSNICheck: tc.strictSNI,
}
srv := &Server{
conf: ServerConfig{TLSConfig: tlsConf},
conf: ServerConfig{TLSConf: tlsConf},
baseLogger: slogutil.NewDiscardLogger(),
}

View File

@@ -0,0 +1,46 @@
package dnsforward
import (
"net/netip"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/dnsproxy/proxy"
)
// ClientsContainer provides information about preconfigured DNS clients.
type ClientsContainer interface {
// CustomUpstreamConfig returns the custom client upstream configuration, if
// any. It prioritizes ClientID over client IP address to identify the
// client.
CustomUpstreamConfig(clientID string, cliAddr netip.Addr) (conf *proxy.CustomUpstreamConfig)
// UpdateCommonUpstreamConfig updates the common upstream configuration.
UpdateCommonUpstreamConfig(conf *client.CommonUpstreamConfig)
// ClearUpstreamCache clears the upstream cache for each stored custom
// client upstream configuration.
ClearUpstreamCache()
}
// EmptyClientsContainer is an [ClientsContainer] implementation that does nothing.
type EmptyClientsContainer struct{}
// type check
var _ ClientsContainer = EmptyClientsContainer{}
// CustomUpstreamConfig implements the [ClientsContainer] interface for
// EmptyClientsContainer.
func (EmptyClientsContainer) CustomUpstreamConfig(
clientID string,
cliAddr netip.Addr,
) (conf *proxy.CustomUpstreamConfig) {
return nil
}
// UpdateCommonUpstreamConfig implements the [ClientsContainer] interface for
// EmptyClientsContainer.
func (EmptyClientsContainer) UpdateCommonUpstreamConfig(conf *client.CommonUpstreamConfig) {}
// ClearUpstreamCache implements the [ClientsContainer] interface for
// EmptyClientsContainer.
func (EmptyClientsContainer) ClearUpstreamCache() {}

View File

@@ -11,12 +11,10 @@ import (
"strings"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/container"
@@ -29,27 +27,11 @@ import (
"github.com/ameshkov/dnscrypt/v2"
)
// ClientsContainer provides information about preconfigured DNS clients.
type ClientsContainer interface {
// UpstreamConfigByID returns the custom upstream configuration for the
// client having id, using boot to initialize the one if necessary. It
// returns nil if there is no custom upstream configuration for the client.
// The id is expected to be either a string representation of an IP address
// or the ClientID.
UpstreamConfigByID(
id string,
boot upstream.Resolver,
) (conf *proxy.CustomUpstreamConfig, err error)
}
// Config represents the DNS filtering configuration of AdGuard Home. The zero
// Config is empty and ready for use.
type Config struct {
// Callbacks for other modules
// FilterHandler is an optional additional filtering callback.
FilterHandler func(cliAddr netip.Addr, clientID string, settings *filtering.Settings) `yaml:"-"`
// ClientsContainer stores the information about special handling of some
// DNS clients.
ClientsContainer ClientsContainer `yaml:"-"`
@@ -185,43 +167,34 @@ type EDNSClientSubnet struct {
UseCustom bool `yaml:"use_custom"`
}
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
// TLSConfig contains the TLS configuration settings for DNS-over-HTTPS (DoH),
// DNS-over-TLS (DoT), DNS-over-QUIC (DoQ), and Discovery of Designated
// Resolvers (DDR).
type TLSConfig struct {
cert tls.Certificate
// Cert is the TLS certificate used for TLS connections. It is nil if
// encryption is disabled.
Cert *tls.Certificate
TLSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"`
QUICListenAddrs []*net.UDPAddr `yaml:"-" json:"-"`
HTTPSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"`
// TLSListenAddrs are the addresses to listen on for DoT connections. Each
// item in the list must be non-nil if Cert is not nil.
TLSListenAddrs []*net.TCPAddr
// PEM-encoded certificates chain
CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"`
// PEM-encoded private key
PrivateKey string `yaml:"private_key" json:"private_key"`
// QUICListenAddrs are the addresses to listen on for DoQ connections. Each
// item in the list must be non-nil if Cert is not nil.
QUICListenAddrs []*net.UDPAddr
CertificatePath string `yaml:"certificate_path" json:"certificate_path"`
PrivateKeyPath string `yaml:"private_key_path" json:"private_key_path"`
CertificateChainData []byte `yaml:"-" json:"-"`
PrivateKeyData []byte `yaml:"-" json:"-"`
// HTTPSListenAddrs should be the addresses AdGuard Home is listening on for
// DoH connections. These addresses are announced with DDR. Each item in
// the list must be non-nil.
HTTPSListenAddrs []*net.TCPAddr
// ServerName is the hostname of the server. Currently, it is only being
// used for ClientID checking and Discovery of Designated Resolvers (DDR).
ServerName string `yaml:"-" json:"-"`
// DNS names from certificate (SAN) or CN value from Subject
dnsNames []string
// OverrideTLSCiphers, when set, contains the names of the cipher suites to
// use. If the slice is empty, the default safe suites are used.
OverrideTLSCiphers []string `yaml:"override_tls_ciphers,omitempty" json:"-"`
ServerName string
// StrictSNICheck controls if the connections with SNI mismatching the
// certificate's ones should be rejected.
StrictSNICheck bool `yaml:"strict_sni_check" json:"-"`
// hasIPAddrs is set during the certificate parsing and is true if the
// configured certificate contains at least a single IP address.
hasIPAddrs bool
StrictSNICheck bool
}
// DNSCryptConfig is the DNSCrypt server configuration struct.
@@ -256,8 +229,11 @@ type ServerConfig struct {
// Remove that.
AddrProcConf *client.DefaultAddrProcConfig
// TLSConf is the TLS configuration for DNS-over-TLS, DNS-over-QUIC, and
// HTTPS. It must not be nil.
TLSConf *TLSConfig
Config
TLSConfig
DNSCryptConfig
TLSAllowUnencryptedDoH bool
@@ -298,6 +274,10 @@ type ServerConfig struct {
// ServePlainDNS defines if plain DNS is allowed for incoming requests.
ServePlainDNS bool
// PendingRequestsEnabled defines if duplicate requests should be forwarded
// to upstreams along with the original one.
PendingRequestsEnabled bool
}
// UpstreamMode is a enumeration of upstream mode representations. See
@@ -341,6 +321,9 @@ func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
UsePrivateRDNS: srvConf.UsePrivateRDNS,
PrivateSubnets: s.privateNets,
MessageConstructor: s,
PendingRequests: &proxy.PendingRequestsConfig{
Enabled: srvConf.PendingRequestsEnabled,
},
}
if srvConf.EDNSClientSubnet.UseCustom {
@@ -467,7 +450,7 @@ func (s *Server) prepareIpsetListSettings() (ipsets []string, err error) {
}
ipsets = stringutil.SplitTrimmed(string(data), "\n")
ipsets = slices.DeleteFunc(ipsets, IsCommentOrEmpty)
ipsets = slices.DeleteFunc(ipsets, aghnet.IsCommentOrEmpty)
log.Debug("dns: using %d ipset rules from file %q", len(ipsets), fn)
@@ -478,7 +461,7 @@ func (s *Server) prepareIpsetListSettings() (ipsets []string, err error) {
// the configuration itself.
func (conf *ServerConfig) loadUpstreams() (upstreams []string, err error) {
if conf.UpstreamDNSFileName == "" {
return stringutil.FilterOut(conf.UpstreamDNS, IsCommentOrEmpty), nil
return stringutil.FilterOut(conf.UpstreamDNS, aghnet.IsCommentOrEmpty), nil
}
var data []byte
@@ -491,7 +474,7 @@ func (conf *ServerConfig) loadUpstreams() (upstreams []string, err error) {
log.Debug("dnsforward: got %d upstreams in %q", len(upstreams), conf.UpstreamDNSFileName)
return stringutil.FilterOut(upstreams, IsCommentOrEmpty), nil
return stringutil.FilterOut(upstreams, aghnet.IsCommentOrEmpty), nil
}
// collectListenAddr adds addrPort to addrs. It also adds its port to
@@ -625,45 +608,33 @@ func (conf *ServerConfig) ourAddrsSet() (m addrPortSet, err error) {
}
}
// prepareTLS - prepares TLS configuration for the DNS proxy
// prepareTLS sets up the TLS configuration for the DNS proxy.
func (s *Server) prepareTLS(proxyConfig *proxy.Config) (err error) {
if len(s.conf.CertificateChainData) == 0 || len(s.conf.PrivateKeyData) == 0 {
if s.conf.TLSConf.Cert == nil {
return
}
if s.conf.TLSConf.TLSListenAddrs == nil && s.conf.TLSConf.QUICListenAddrs == nil {
return nil
}
if s.conf.TLSListenAddrs == nil && s.conf.QUICListenAddrs == nil {
return nil
}
proxyConfig.TLSListenAddr = s.conf.TLSConf.TLSListenAddrs
proxyConfig.QUICListenAddr = s.conf.TLSConf.QUICListenAddrs
proxyConfig.TLSListenAddr = aghalg.CoalesceSlice(
s.conf.TLSListenAddrs,
proxyConfig.TLSListenAddr,
)
proxyConfig.QUICListenAddr = aghalg.CoalesceSlice(
s.conf.QUICListenAddrs,
proxyConfig.QUICListenAddr,
)
s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
if err != nil {
return fmt.Errorf("failed to parse TLS keypair: %w", err)
}
cert, err := x509.ParseCertificate(s.conf.cert.Certificate[0])
cert, err := x509.ParseCertificate(s.conf.TLSConf.Cert.Certificate[0])
if err != nil {
return fmt.Errorf("x509.ParseCertificate(): %w", err)
}
s.conf.hasIPAddrs = aghtls.CertificateHasIP(cert)
s.hasIPAddrs = aghtls.CertificateHasIP(cert)
if s.conf.StrictSNICheck {
if s.conf.TLSConf.StrictSNICheck {
if len(cert.DNSNames) != 0 {
s.conf.dnsNames = cert.DNSNames
s.dnsNames = cert.DNSNames
log.Debug("dns: using certificate's SAN as DNS names: %v", cert.DNSNames)
slices.Sort(s.conf.dnsNames)
slices.Sort(s.dnsNames)
} else {
s.conf.dnsNames = append(s.conf.dnsNames, cert.Subject.CommonName)
s.dnsNames = []string{cert.Subject.CommonName}
log.Debug("dns: using certificate's CN as DNS name: %s", cert.Subject.CommonName)
}
}
@@ -712,11 +683,11 @@ func anyNameMatches(dnsNames []string, sni string) (ok bool) {
// Called by 'tls' package when Client Hello is received
// If the server name (from SNI) supplied by client is incorrect - we terminate the ongoing TLS handshake.
func (s *Server) onGetCertificate(ch *tls.ClientHelloInfo) (*tls.Certificate, error) {
if s.conf.StrictSNICheck && !anyNameMatches(s.conf.dnsNames, ch.ServerName) {
if s.conf.TLSConf.StrictSNICheck && !anyNameMatches(s.dnsNames, ch.ServerName) {
log.Info("dns: tls: unknown SNI in Client Hello: %s", ch.ServerName)
return nil, fmt.Errorf("invalid SNI")
}
return &s.conf.cert, nil
return s.conf.TLSConf.Cert, nil
}
// preparePlain prepares the plain-DNS configuration for the DNS proxy.

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