Compare commits

...

11 Commits

Author SHA1 Message Date
Ainar Garipov
2069eddf98 Pull request 1896: fix-docker
Squashed commit of the following:

commit e64194bd053085b6bdcef6dcda40e7b52234a2ec
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 28 18:02:22 2023 +0300

    scripts: fix docker tags
2023-06-28 18:12:45 +03:00
Ainar Garipov
c652653ea4 Pull request 1894: upd-all
Squashed commit of the following:

commit 77daec27a1f5650a99b9e404f700a1e323741740
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 28 14:28:32 2023 +0300

    all: fix i18n script; upd deps, i18n, svcs
2023-06-28 15:29:38 +03:00
Ainar Garipov
6889837785 Pull request 1893: all: imp logs; upd dnsproxy
Updates #5285.

Squashed commit of the following:

commit 8e7d17505492b6983ba9e713455b98652938d73d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 28 13:33:14 2023 +0300

    all: imp logs; upd dnsproxy
2023-06-28 13:46:04 +03:00
Ainar Garipov
cf7c12c97b Pull request 1892: next-imp-dnssvc
Squashed commit of the following:

commit 770a3f338ecb270fcff7792a4ffe3cf95492d2ae
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 27 20:10:39 2023 +0300

    dnssvc: fix test for darwin

commit 6564abcc0904784ff3787e1a046d665519a108b3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 27 19:57:19 2023 +0300

    all: fix .gitignore, tests

commit 3ff1be0462b3adea81d98b1f65eeb685d2d72030
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 27 19:30:05 2023 +0300

    next: add conf example; imp dnssvc
2023-06-28 12:48:53 +03:00
Stanislav Chzhen
d88181343c Pull request 1883: 951-blocked-services-client-schedule
Updates #951.

Squashed commit of the following:

commit 94e4766932940a99c5265489bccb46d0ed6cec25
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 27 17:21:41 2023 +0300

    chlog: upd docs

commit b4022c33860c258bf29650413f0c972b849a1758
Merge: cfa24ff01 e7e638443
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 27 16:33:20 2023 +0300

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

commit cfa24ff0190b2bc12736700eeff815525fbaf5fe
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 27 15:04:10 2023 +0300

    chlog: imp docs

commit dad27590d5eefde82758d58fc06a20c139492db8
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jun 26 17:38:08 2023 +0300

    home: imp err msg

commit 7d9ba98c4477000fc2e0f06c3462fe9cd0c65293
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jun 26 16:58:00 2023 +0300

    all: add tests

commit 8e952fc4e3b3d433b29efe47c88d6b7806e99ff8
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 23 16:36:10 2023 +0300

    schedule: add todo

commit 723573a98d5b930334a5fa125eb12593f4a2430d
Merge: 2151ab2a6 e54fc9b1e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 23 11:40:03 2023 +0300

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

commit 2151ab2a627b9833ba6cce9621f72b29d326da75
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jun 23 11:37:49 2023 +0300

    all: add tests

commit 81ab341db3e4053f09b181d8111c0da197bdac05
Merge: aa7ae41a8 66345e855
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 22 17:59:01 2023 +0300

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

commit aa7ae41a868045fe24e390b25f15551fd8821529
Merge: 304389a48 06d465b0d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jun 21 17:10:11 2023 +0300

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

commit 304389a487f728e8ced293ea811a4e0026a37f0d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jun 21 17:05:31 2023 +0300

    home: imp err msg

commit 29cfc7ae2a0bbd5ec3205eae3f6f810519787f26
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 20 20:42:59 2023 +0300

    all: imp err handling

commit 8543868eef6442fd30131d9567b66222999101e9
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 20 18:21:50 2023 +0300

    all: upd chlog

commit c5b614d45e5cf25c30c52343f48139fb34c77539
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jun 20 14:37:47 2023 +0300

    all: add blocked services schedule
2023-06-27 18:03:07 +03:00
Ildar Kamalov
e7e638443f Pull request 1889: AG-22207 handle rewrite update
Updates #1577

Squashed commit of the following:

commit a07ff51fb3f1eb58ab9a922d7bc11ed1d65ef3a7
Merge: 7db696814 2f515e8d8
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jun 23 16:50:09 2023 +0300

    Merge branch 'master' into AG-22207

commit 7db696814f2134fd3a3415cbcfa0ffdac1fabda3
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jun 23 14:57:09 2023 +0300

    fix changelog

commit bf9458b3f18697ca1fc504c51fa443934f76371f
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jun 23 14:48:20 2023 +0300

    changelog

commit bc2bf3f9507957afe63a654334b6e22858d1e41b
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jun 23 13:28:28 2023 +0300

    client: handle rewrite edit
2023-06-23 17:10:59 +03:00
Dimitry Kolyshev
2f515e8d8f Pull request: all: docs
Merge in DNS/adguard-home from cmdline-flag-docs to master

Updates #4235.

Squashed commit of the following:

commit 1d68255b3d3003207eed535f9491fedb1d839e8b
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri Jun 23 16:04:15 2023 +0400

    all: docs
2023-06-23 15:09:00 +03:00
Dimitry Kolyshev
e54fc9b1e9 Pull request: web-addr-cmdline
Merge in DNS/adguard-home from web-addr-cmdline to master

Squashed commit of the following:

commit 27652dbfae227f9a9f4d921f14af1e5897d7830d
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 22 17:51:43 2023 +0400

    home: web-addr opt

commit b234d108e70fed3ff11eeb4986946f8a50dda515
Merge: eef1b5cbb 66345e855
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 22 17:10:57 2023 +0400

    Merge remote-tracking branch 'origin/master' into web-addr-cmdline

    # Conflicts:
    #	CHANGELOG.md

commit eef1b5cbb9f7dd9819c8038800a4bbcf3ff575aa
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 22 17:04:12 2023 +0400

    all: docs

    Updates #4231.

commit b4adb0bae82e44101f7b6685a29926c3d02e95af
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 22 16:58:12 2023 +0400

    all: docker

    Updates #4231.

commit a4012fddc50a3f5143136df8f3b865d7e1b9087c
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 22 11:24:02 2023 +0400

    all: docker

commit bd5cc3308298b1022dea501c2fe79d96df24ce2a
Merge: 36841dd85 123ca8738
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 22 11:22:51 2023 +0400

    Merge remote-tracking branch 'origin/master' into web-addr-cmdline

commit 36841dd85fe1d006e6ec518329cb8ceea0915599
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 21 11:53:40 2023 +0400

    all: docker

commit 5c4756a56d251f1486b92f333f11485b5f4807f4
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 21 11:50:41 2023 +0400

    all: imp docs

commit 7b6fba81f2d5a354bdf259d812d04f3bd64de0c6
Merge: 54c119024 ca313521d
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Jun 21 11:47:36 2023 +0400

    Merge remote-tracking branch 'origin/master' into web-addr-cmdline

commit 54c119024f3999d5c4dec06b21e3bb78803bf388
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jun 20 15:57:54 2023 +0400

    home: imp code

commit c87e0a690376aa6a28a018c3cbbb5de0ad16333d
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jun 20 15:56:45 2023 +0400

    all: imp docs

commit de240d00e334f7fd3dbf39ac08472183deeb32db
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jun 20 15:54:27 2023 +0400

    all: docker

commit 568d5371e73fb6c5ba19a036e7a5135764f6538b
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jun 20 13:38:53 2023 +0400

    all: docs

commit 407e230c8e3bc293e8d533ca5db1f28fec4b3c54
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jun 20 13:24:23 2023 +0400

    home: deprecate opts

commit 6e628fad6f4ee9f23213eeb55bec500db4435175
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Jun 20 13:18:10 2023 +0400

    home: web-addr opt
2023-06-23 10:03:01 +03:00
Dimitry Kolyshev
66345e855e Pull request: filtering: fix filter delete
Merge in DNS/adguard-home from 5700-fix-filter-delete to master

Squashed commit of the following:

commit 51e5c82bf6c12b48b1c07622639216ecba21e115
Merge: b4f8abdf2 37e046acc
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 22 17:05:23 2023 +0400

    Merge remote-tracking branch 'origin/master' into 5700-fix-filter-delete

commit b4f8abdf2e9a8c94594cb0e3b00b37e0ed49578a
Merge: eac30a13f f40ef76c7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 22 16:55:58 2023 +0400

    Merge remote-tracking branch 'origin/master' into 5700-fix-filter-delete

commit eac30a13fb30404b2e058d1889be566d359d5000
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 22 14:03:07 2023 +0400

    filtering: imp code

commit 4e270789b83bdcff09d94ace46dd0b02d2c6893c
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 22 14:02:04 2023 +0400

    all: docs

commit b818468ae29f647bcc0f4e408d10c8469829da95
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jun 22 13:18:12 2023 +0400

    filtering: fix filter delete
2023-06-22 16:09:55 +03:00
Stanislav Chzhen
37e046acc4 Pull request 1886: fix-blocked-services-client-schedule
Merge in DNS/adguard-home from fix-blocked-services-client-schedule to master

Updates #951.

Squashed commit of the following:

commit 1c890953ff4b7862db10f99f4851797050ad9d8e
Merge: ac0b722d1 f40ef76c7
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 22 15:54:03 2023 +0300

    Merge branch 'master' into fix-blocked-services-client-schedule

commit ac0b722d1deef664ab464e0bff387542e027bd3f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 22 14:32:03 2023 +0300

    home: fix typo

commit 64eb519c4b77cff1071059f1f41acfd0e3ea9513
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 22 14:23:07 2023 +0300

    home: add test case

commit c7b60f3ea89e41f8b84bf6f2f5e075e9b3dd45ec
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jun 22 12:00:24 2023 +0300

    home: imp code

commit 7806c920bb0c4b4c44c3fed7e920f795904630b2
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jun 21 20:49:47 2023 +0300

    home: add tests

commit d37c9fde882965f005ddec86ad0502585d2eea95
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jun 21 18:49:43 2023 +0300

    home: fix client blocked services
2023-06-22 15:58:09 +03:00
Eugene Burkov
f40ef76c79 Pull request 1861: 4923-gopacket-dhcp vol.1
Merge in DNS/adguard-home from 4923-gopacket-dhcp to master

Updates #4923.

Squashed commit of the following:

commit edf36ce8b1873272c3daebe8cc8f8132793aac44
Merge: a17513d3e 123ca8738
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jun 22 14:14:39 2023 +0300

    Merge branch 'master' into 4923-gopacket-dhcp

commit a17513d3e0a9e596d56444dfa46478eee15631de
Merge: f04727c29 994906fbd
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jun 21 17:49:09 2023 +0300

    Merge branch 'master' into 4923-gopacket-dhcp

commit f04727c29eaf22f9eb53f3aa33d42d00e177b224
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 20 15:42:31 2023 +0300

    home: revert clients container

commit c58284ac6b5b2274da5eed2e853847d757709e5b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jun 19 21:10:36 2023 +0300

    all: imp code, names, docs

commit 4c4613c939e1325d11655822d9dbc3f05a6d203c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 13 18:51:12 2023 +0300

    all: imp code

commit 0b4a6e0dd561d9b7bb78dea21dcc947bcd0bd583
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jun 7 18:40:15 2023 +0300

    all: imp api

commit 0425edea03d6ca0859657df683bef6ec45bfc399
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jun 5 15:57:23 2023 +0300

    dhcpsvc: introduce package

commit 5628ebe6cccf91e2c48778966730bcbbe9e1d9f2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jun 1 17:49:12 2023 +0300

    WIP
2023-06-22 14:18:43 +03:00
98 changed files with 2569 additions and 1273 deletions

5
.gitignore vendored
View File

@@ -16,10 +16,13 @@
/dist/
/filtering/tests/filtering.TestLotsOfRules*.pprof
/filtering/tests/top-1m.csv
/internal/next/AdGuardHome.yaml
/launchpad_credentials
/querylog.json*
/snapcraft_login
AdGuardHome*
AdGuardHome
AdGuardHome.exe
AdGuardHome.yaml*
coverage.txt
node_modules/

View File

@@ -25,18 +25,54 @@ NOTE: Add new changes BELOW THIS COMMENT.
### Added
- The ability to set inactivity periods for filtering blocked services in the
configuration file ([#951]). The UI changes are coming in the upcoming
releases.
- The new command-line flag `--web-addr` is the address to serve the web UI on,
in the host:port format.
- The ability to set inactivity periods for filtering blocked services, both
globally and per client, in the configuration file ([#951]). The UI changes
are coming in the upcoming releases.
- The ability to edit rewrite rules via `PUT /control/rewrite/update` HTTP API
([#1577]).
and the Web UI ([#1577]).
### Changed
#### Configuration Changes
In this release, the schema version has changed from 20 to 21.
In this release, the schema version has changed from 20 to 22.
- Property `clients.persistent.blocked_services`, which in schema versions 21
and earlier used to be a list containing ids of blocked services, is now an
object containing ids and schedule for blocked services:
```yaml
# BEFORE:
'clients':
'persistent':
- 'name': 'client-name'
'blocked_services':
- id_1
- id_2
# AFTER:
'clients':
'persistent':
- 'name': client-name
'blocked_services':
'ids':
- id_1
- id_2
'schedule':
'time_zone': 'Local'
'sun':
'start': '0s'
'end': '24h'
'mon':
'start': '1h'
'end': '23h'
```
To rollback this change, replace `clients.persistent.blocked_services` object
with the list of ids of blocked services and change the `schema_version` back
to `21`.
- Property `dns.blocked_services`, which in schema versions 20 and earlier used
to be a list containing ids of blocked services, is now an object containing
ids and schedule for blocked services:
@@ -80,8 +116,17 @@ In this release, the schema version has changed from 20 to 21.
To rollback this change, replace `dns.blocked_services` object with the list
of ids of blocked services and change the `schema_version` back to `20`.
### Deprecated
- Flags `-h`, `--host`, `-p`, `--port` have been deprecated. The `-h` flag
will work as an alias for `--help`, instead of the deprecated `--host` in the
future releases.
### Fixed
- Excessive error logging when using DNS-over-QUIC ([#5285]).
- Cannot set `bind_host` in AdGuardHome.yaml (docker version)([#4231], [#4235]).
- The blocklists can now be deleted properly ([#5700]).
- Queries with the question-section target `.`, for example `NS .`, are now
counted in the statistics and correctly shown in the query log ([#5910]).
- Safe Search not working with `AAAA` queries for domains that don't have `AAAA`
@@ -89,6 +134,10 @@ In this release, the schema version has changed from 20 to 21.
[#951]: https://github.com/AdguardTeam/AdGuardHome/issues/951
[#1577]: https://github.com/AdguardTeam/AdGuardHome/issues/1577
[#4231]: https://github.com/AdguardTeam/AdGuardHome/issues/4231
[#4235]: https://github.com/AdguardTeam/AdGuardHome/pull/4235
[#5285]: https://github.com/AdguardTeam/AdGuardHome/issues/5285
[#5700]: https://github.com/AdguardTeam/AdGuardHome/issues/5700
[#5910]: https://github.com/AdguardTeam/AdGuardHome/issues/5910
[#5913]: https://github.com/AdguardTeam/AdGuardHome/issues/5913

View File

@@ -475,7 +475,9 @@
"setup_dns_notice": "Каб выкарыстоўваць <1>DNS-over-HTTPS</1> ці <1>DNS-over-TLS</1>, вам патрэбна <0>наладзіць шыфраванне</0> у наладах AdGuard Home.",
"rewrite_added": "Правіла перанакіравання DNS для «{{key}}» паспяхова дададзена",
"rewrite_deleted": "Правіла перанакіравання DNS для «{{key}}» паспяхова выдалена",
"rewrite_updated": "Перазапіс DNS паспяхова абноўлены",
"rewrite_add": "Дадаць правіла перанакіравання DNS",
"rewrite_edit": "Рэдагаваць перазапіс DNS",
"rewrite_not_found": "Не знойдзена правілаў перанакіравання DNS",
"rewrite_confirm_delete": "Вы ўпэўнены, што хочаце выдаліць правіла перанакіравання DNS для «{{key}}»?",
"rewrite_desc": "Дазваляе лёгка наладзіць карыстацкі DNS-адказ для пэўнага дамена.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Pro použití <1>DNS skrze HTTPS</1> nebo <1>DNS skrze TLS</1> potřebujete v nastaveních AdGuard Home <0>nakonfigurovat šifrování</0>.",
"rewrite_added": "Přesměrování DNS pro „{{key}}“ úspěšně přidáno",
"rewrite_deleted": "Přesměrování DNS pro „{{key}}“ úspěšně smazáno",
"rewrite_updated": "Přesměrování DNS bylo úspěšně aktualizováno",
"rewrite_add": "Přidat přesměrování DNS",
"rewrite_edit": "Upravit přesměrování DNS",
"rewrite_not_found": "Přesměrování DNS nenalezeny",
"rewrite_confirm_delete": "Jste si jisti, že chcete smazat přesměrování DNS pro „{{key}}“?",
"rewrite_desc": "Umožňuje snadno nakonfigurovat vlastní DNS odezvy pro konkrétní název domény.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "For at kunne bruge <1>DNS-over-HTTPS</1> eller <1>DNS-over-TLS</1>, skal du <0>opsætte Krypteringen</0> i AdGuard Homes indstillinger.",
"rewrite_added": "DNS-omskrivning for \"{{key}}\" blev tilføjet",
"rewrite_deleted": "DNS-omskrivning for \"{{key}}\" blev slettet",
"rewrite_updated": "DNS-omskrivning hermed opdateret",
"rewrite_add": "Tilføj DNS-omskrivning",
"rewrite_edit": "Redigér DNS-omskrivning",
"rewrite_not_found": "Ingen DNS-omskrivninger fundet",
"rewrite_confirm_delete": "Sikker på, at du vil slette DNS-omskrivning for \"{{key}}\"?",
"rewrite_desc": "Gør det nemt at opsætte det tilpassede DNS-svar for et specifikt domænenavn.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Um <1>DNS-over-HTTTPS</1> oder <1>DNS-over-TLS</1> verwenden zu können, müssen Sie in den AdGuard Home Einstellungen die <0>Verschlüsselung konfigurieren</0>.",
"rewrite_added": "DNS-Umschreibung für „{{key}}“ erfolgreich hinzugefügt",
"rewrite_deleted": "DNS-Umschreibung für „{{key}}“ erfolgreich entfernt",
"rewrite_updated": "DNS-Rewrite erfolgreich aktualisiert",
"rewrite_add": "DNS-Umschreibung hinzufügen",
"rewrite_edit": "DNS-Rewrite bearbeiten",
"rewrite_not_found": "Keine DNS-Umschreibungen gefunden",
"rewrite_confirm_delete": "Möchten Sie die DNS-Umschreibung für „{{key}}“ wirklich entfernen?",
"rewrite_desc": "Ermöglicht die einfache Konfiguration der benutzerdefinierten DNS-Antwort für einen bestimmten Domainnamen.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "In order to use <1>DNS-over-HTTPS</1> or <1>DNS-over-TLS</1>, you need to <0>configure Encryption</0> in AdGuard Home settings.",
"rewrite_added": "DNS rewrite for \"{{key}}\" successfully added",
"rewrite_deleted": "DNS rewrite for \"{{key}}\" successfully deleted",
"rewrite_updated": "DNS rewrite successfully updated",
"rewrite_add": "Add DNS rewrite",
"rewrite_edit": "Edit DNS rewrite",
"rewrite_not_found": "No DNS rewrites found",
"rewrite_confirm_delete": "Are you sure you want to delete DNS rewrite for \"{{key}}\"?",
"rewrite_desc": "Allows to easily configure custom DNS response for a specific domain name.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Para utilizar <1>DNS mediante HTTPS</1> o <1>DNS mediante TLS</1>, debes <0>configurar el cifrado</0> en la configuración de AdGuard Home.",
"rewrite_added": "Reescritura DNS para \"{{key}}\" añadido correctamente",
"rewrite_deleted": "Reescritura DNS para \"{{key}}\" eliminado correctamente",
"rewrite_updated": "Reconfiguración de DNS actualizada correctamente",
"rewrite_add": "Añadir reescritura DNS",
"rewrite_edit": "Editar reconfiguración de DNS",
"rewrite_not_found": "No se han encontrado reescrituras DNS",
"rewrite_confirm_delete": "¿Estás seguro de que deseas eliminar la reescritura DNS para \"{{key}}\"?",
"rewrite_desc": "Permite configurar fácilmente la respuesta DNS personalizada para un nombre de dominio específico.",

View File

@@ -440,7 +440,9 @@
"setup_dns_notice": "به منظور استفاده از <1>DNS-over-HTTPS</1> یا <1>DNS-over-TLS</1>، شما نیاز به <0>پیکربندی رمزگذاری</0> در تنظیمات AdGuard Home دارید.",
"rewrite_added": "بازنویسی DNS برای \"{{key}}\" با موفقیت اضافه شد",
"rewrite_deleted": "بازنویسی DNS برای \"{{key}}\" با موفقیت حذف شد",
"rewrite_updated": "بازنویسی DNS با موفقیت به روز شد",
"rewrite_add": "افزودن بازنویسی DNS",
"rewrite_edit": "بازنویسی DNS را ویرایش کنید",
"rewrite_not_found": "بازنویسی DNS یافت نشد",
"rewrite_confirm_delete": "آیا واقعا میخواهید بازنویسی DNS برای \"{{key}}\" را حذف کنید؟",
"rewrite_desc": "به آسانی اجازه پیکربندی پاسخ DNS دستی برای یک نام دامنه خاص را می دهد.",

View File

@@ -222,7 +222,7 @@
"all_lists_up_to_date_toast": "Kaikki listat ovat ajan tasalla",
"updated_upstream_dns_toast": "Ylävirtojen palvelimet tallennettiin",
"dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein",
"dns_test_not_ok_toast": "Palvelin \"{{key}}\": ei voitu käyttää, tarkista sen oikeinkirjoitus",
"dns_test_not_ok_toast": "Palvelin \"{{key}}\": Ei voitu käyttää, tarkista oikeinkirjoitus",
"dns_test_warning_toast": "Datavuon \"{{key}}\" ei vastaa testipyyntöihin eikä välttämättä toimi kunnolla",
"unblock": "Salli",
"block": "Estä",
@@ -478,7 +478,9 @@
"setup_dns_notice": "<1>DNS-over-HTTPS</1> tai <1>DNS-over-TLS</1> -toteutuksia varten, on AdGuard Homen <0>Salausasetukset</0> määritettävä.",
"rewrite_added": "Kohteen \"{{key}}\" DNS-uudelleenohjaus lisättiin",
"rewrite_deleted": "Kohteen \"{{key}}\" DNS-uudelleenohjaus poistettiin",
"rewrite_updated": "DNS-uudelleenohjaukset päivitettiin",
"rewrite_add": "Lisää DNS-uudelleenohjaus",
"rewrite_edit": "Muokkaa DNS-uudelleenohjausta",
"rewrite_not_found": "DNS-uudelleenohjauksia ei löytynyt",
"rewrite_confirm_delete": "Haluatko varmasti poistaa DNS-uudelleenohjauksen kohteelle \"{{key}}\"?",
"rewrite_desc": "Mahdollistaa oman DNS-vastauksen helpon määrityksen tietylle verkkotunnukselle.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Pour utiliser le <1>DNS-over-HTTPS</1> ou le <1>DNS-over-TLS</1>, vous devez <0>configurer le Chiffrement</0> dans les paramètres de AdGuard Home.",
"rewrite_added": "Réécriture DNS pour « {{key}} » ajoutée",
"rewrite_deleted": "Réécriture DNS pour « {{key}} » supprimée",
"rewrite_updated": "Réécriture DNS mise à jour",
"rewrite_add": "Ajouter une réécriture DNS",
"rewrite_edit": "Modifier la réécriture DNS",
"rewrite_not_found": "Aucune réécriture DNS trouvée",
"rewrite_confirm_delete": "Voulez-vous vraiment supprimer la réécriture DNS pour « {{key}} » ?",
"rewrite_desc": "Permet de configurer facilement la réponse DNS personnalisée pour un nom de domaine spécifique.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Da biste koristili <1>DNS-over-HTTPS</1> ili <1>DNS-over-TLS</1>, morate <0>postaviti šifriranje</0> u AdGuard Home postavkama.",
"rewrite_added": "DNS prijepis za \"{{key}}\" je uspješno dodan",
"rewrite_deleted": "DNS prijepis za \"{{key}}\" je uspješno uklonjen",
"rewrite_updated": "Prepisivanje DNS-a uspješno ažurirano",
"rewrite_add": "Dodaj DNS prijepis",
"rewrite_edit": "Uredite prepisivanje DNS-a",
"rewrite_not_found": "Nema DNS prijepisa",
"rewrite_confirm_delete": "Jeste li sigurni da želite ukloniti DNS prijepis za \"{{key}}\" klijenta?",
"rewrite_desc": "Omogućuje jednostavno postavljanje prilagođenog DNS odgovora za određenu domenu.",

View File

@@ -167,6 +167,7 @@
"enabled_parental_toast": "Szülői felügyelet engedélyezve",
"disabled_safe_search_toast": "Biztonságos keresés letiltva",
"enabled_save_search_toast": "Biztonságos keresés engedélyezve",
"updated_save_search_toast": "A Biztonságos keresés beállításai frissítve",
"enabled_table_header": "Engedélyezve",
"name_table_header": "Név",
"list_url_table_header": "Lista URL-je",
@@ -290,6 +291,8 @@
"rate_limit": "Kérések korlátozása",
"edns_enable": "EDNS kliens alhálózat engedélyezése",
"edns_cs_desc": "Adja hozzá az EDNS Client Subnet beállítást (ECS) a felfelé irányuló kérésekhez, és naplózza a kliensek által küldött értékeket a lekérdezési naplóban.",
"edns_use_custom_ip": "Használjon egyéni IP-címet az EDNS-hez",
"edns_use_custom_ip_desc": "Engedélyezze az egyéni IP-cím használatát az EDNS-hez",
"rate_limit_desc": "Maximálisan hány kérést küldhet egy kliens másodpercenkén. Ha 0-ra állítja, akkor nincs korlátozás.",
"blocking_ipv4_desc": "A blokkolt A kéréshez visszaadandó IP-cím",
"blocking_ipv6_desc": "A blokkolt AAAA kéréshez visszaadandó IP-cím",
@@ -475,7 +478,9 @@
"setup_dns_notice": "Ahhoz, hogy a <1>DNS-over-HTTPS</1> vagy a <1>DNS-over-TLS</1> valamelyikét használja, muszáj <0>beállítania a titkosítást</0> az AdGuard Home beállításaiban.",
"rewrite_added": "DNS átírás a(z) \"{{key}}\" kulcshoz sikeresen hozzáadva",
"rewrite_deleted": "DNS átírás a(z) \"{{key}}\" kulcshoz sikeresen törölve",
"rewrite_updated": "A DNS újraírása sikeresen frissítve",
"rewrite_add": "DNS átírás hozzáadása",
"rewrite_edit": "DNS újraírás szerkesztése",
"rewrite_not_found": "Nem találhatók DNS átírások",
"rewrite_confirm_delete": "Biztosan törölni szeretné a DNS átírást ehhez: \"{{key}}\"?",
"rewrite_desc": "Lehetővé teszi, hogy egyszerűen beállítson egyéni DNS választ egy adott domain névhez.",
@@ -523,6 +528,10 @@
"statistics_retention_confirm": "Biztos benne, hogy megváltoztatja a statisztika megőrzési idejét? Ha csökkentette az értéket, a megadottnál korábbi adatok elvesznek",
"statistics_cleared": "A statisztikák sikeresen vissza lettek állítva",
"statistics_enable": "Statisztikák engedélyezése",
"ignore_domains": "Figyelmen kívül hagyott domainek (újsorral elválasztva)",
"ignore_domains_title": "Figyelmen kívül hagyott domainek",
"ignore_domains_desc_stats": "Az ezekre a tartományokra vonatkozó lekérdezések nem kerülnek a statisztikákba",
"ignore_domains_desc_query": "Az ezekhez a tartományokhoz tartozó lekérdezések nem kerülnek a lekérdezési naplóba",
"interval_hours": "{{count}} óra",
"interval_hours_plural": "{{count}} óra",
"filters_configuration": "Szűrők beállításai",
@@ -643,5 +652,29 @@
"confirm_dns_cache_clear": "Biztos benne, hogy törölni szeretné a DNS-gyorsítótárat?",
"cache_cleared": "A DNS gyorsítótár sikeresen törlődött",
"clear_cache": "Gyorsítótár törlése",
"protection_section_label": "Védelem"
"make_static": "Statikussá tétel",
"theme_auto_desc": "Automatikus (az eszköz színsémájától függően)",
"theme_dark_desc": "Sötét téma",
"theme_light_desc": "Világos téma",
"disable_for_seconds": "{{count}} másodpercig",
"disable_for_seconds_plural": "{{count}} másodpercig",
"disable_for_minutes": "{{count}} percig",
"disable_for_minutes_plural": "{{count}} percig",
"disable_for_hours": "{{count}} óráig",
"disable_for_hours_plural": "{{count}} óráig",
"disable_until_tomorrow": "Holnapig",
"disable_notify_for_seconds": "Kapcsolja ki a védelmet {{count}} másodpercre",
"disable_notify_for_seconds_plural": "Kapcsolja ki a védelmet {{count}} másodpercre",
"disable_notify_for_minutes": "Kapcsolja ki a védelmet {{count}} percre",
"disable_notify_for_minutes_plural": "Kapcsolja ki a védelmet {{count}} percre",
"disable_notify_for_hours": "Kapcsolja ki a védelmet {{count}} órára",
"disable_notify_for_hours_plural": "Kapcsolja ki a védelmet {{count}} órára",
"disable_notify_until_tomorrow": "Holnapig kapcsolja ki a védelmet",
"enable_protection_timer": "A védelem {{time}}-kor aktiválódik",
"custom_retention_input": "Adja meg a megőrzést órákban",
"custom_rotation_input": "Írja be a forgatást órákban",
"protection_section_label": "Védelem",
"log_and_stats_section_label": "Lekérdezési napló és statisztikák",
"ignore_query_log": "Figyelmen kívül hagyja ezt az ügyfelet a lekérdezési naplóban",
"ignore_statistics": "Hagyja figyelmen kívül ezt az ügyfelet a statisztikákban"
}

View File

@@ -474,7 +474,9 @@
"setup_dns_notice": "Jikalau ingin menggunakan <1>DNS-over-HTTPS</1> atau <1>DNS-over-TLS</1>, Anda perlu <0>mengatur Enkripsi</0> pada pengaturan AdGuard Home.",
"rewrite_added": "DNS rewrite untuk \"{{key}}\" berhasil ditambahkan",
"rewrite_deleted": "DNS rewrite untuk \"{{key}}\" berhasil dihapus",
"rewrite_updated": "Penulisan ulang DNS berhasil diperbarui",
"rewrite_add": "Tambah DNS rewrite",
"rewrite_edit": "Edit penulisan ulang DNS",
"rewrite_not_found": "Tidak ada DNS rewrite ditemukan",
"rewrite_confirm_delete": "Apakah anda yakin ingin menghapus DNS rewrite untuk \"{{key}}\"?",
"rewrite_desc": "Memungkinkan untuk dengan mudah mengkonfigurasi respons DNS kustom untuk nama domain tertentu.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Per utilizzare <1>DNS su HTTPS</1> o <1>DNS su TLS</1>, è necessario <0>configurare la crittografia</0> nelle impostazioni di AdGuard Home.",
"rewrite_added": "Riscrittura DNS per \"{{key}}\" aggiunta correttamente",
"rewrite_deleted": "La riscrittura DNS per \"{{key}}\" è stata eliminata correttamente",
"rewrite_updated": "Riscrittura DNS aggiornata correttamente",
"rewrite_add": "Aggiungi la riscrittura DNS",
"rewrite_edit": "Modifica della riscrittura DNS",
"rewrite_not_found": "Nessuna riscrittura DNS trovata",
"rewrite_confirm_delete": "Sei sicuro di voler cancellare la riscrittura DNS per \"{{key}}\"?",
"rewrite_desc": "Consente di configurare facilmente la risposta DNS personalizzata per un nome di dominio specifico.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "<1>DNS-over-HTTPS</1>または<1>DNS-over-TLS</1>を使用するには、AdGuard Home 設定の<0>暗号化設定</0>が必要です。",
"rewrite_added": "\"{{key}}\" のDNS書き換え情報を追加完了しました",
"rewrite_deleted": "\"{{key}}\" のDNS書き換え情報を削除完了しました",
"rewrite_updated": "DNS rewrite を更新完了しました。",
"rewrite_add": "DNS書き換え情報を追加する",
"rewrite_edit": "DNS rewrite を編集する",
"rewrite_not_found": "DNS書き換え情報はありません",
"rewrite_confirm_delete": "\"{{key}}\" のDNS書き換え情報を削除してもよろしいですか",
"rewrite_desc": "特定のドメイン名に対するDNS応答を簡単にカスタマイズすることを可能にします。",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "<1>DNS-over-HTTPS</1> 또는 <1>DNS-over-TLS를</1> 사용하려면 AdGuard Home 설정에서 <0>암호화를 구성해야 합니다.</0>",
"rewrite_added": "'{{key}}'에 대한 DNS 수정 정보를 성공적으로 추가 됩니다",
"rewrite_deleted": "'{{key}}'에 대한 DNS 수정 정보를 성공적으로 삭제 됩니다",
"rewrite_updated": "DNS 다시 쓰기 업데이트 완료",
"rewrite_add": "DNS 변환 정보를 추가합니다",
"rewrite_edit": "DNS 다시 쓰기 편집",
"rewrite_not_found": "DNS 변경 정보를 찾을 수 없습니다",
"rewrite_confirm_delete": "'{{key}}'에 대한 DNS 변경 정보를 삭제하시겠습니까?",
"rewrite_desc": "특정 도메인 이름에 대한 사용자 지정 DNS 응답을 쉽게 구성할 수 있습니다.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Om <1>DNS-via-HTTPS</1> of <1>DNS-via-TLS</1> te gebruiken, moet je <0>Versleuteling configureren</0> in de AdGuard Home instellingen.",
"rewrite_added": "DNS-herschrijving voor \"{{key}}\" met succes toegevoegd",
"rewrite_deleted": "DNS-herschrijving voor \"{{key}}\" met succes verwijderd",
"rewrite_updated": "DNS-herschrijven succesvol bijgewerkt",
"rewrite_add": "DNS-herschrijving toevoegen",
"rewrite_edit": "DNS-herschrijven bewerken",
"rewrite_not_found": "Geen DNS-herschrijving gevonden",
"rewrite_confirm_delete": "Bent u zeker dat u DNS-herschrijving \"{{key}}\" wilt verwijderen?",
"rewrite_desc": "Hiermee kunt u eenvoudig aangepaste DNS-antwoorden configureren voor een specifieke domeinnaam.",

View File

@@ -457,7 +457,9 @@
"setup_dns_notice": "For å benytte <1>DNS-over-HTTPS</1> eller <1>DNS-over-TLS</1>, må du <0>sette opp Kryptering</0> i AdGuard Home-innstillingene.",
"rewrite_added": "DNS-omdirigeringen for «{{key}}» ble vellykket lagt til",
"rewrite_deleted": "DNS-omdirigeringen for «{{key}}» ble vellykket slettet",
"rewrite_updated": "DNS-omskriving ble oppdatert",
"rewrite_add": "Legg til DNS-omdirigering",
"rewrite_edit": "Rediger DNS-omskriving",
"rewrite_not_found": "Ingen DNS-omdirigeringer ble funnet",
"rewrite_confirm_delete": "Er du sikker på at du vil slette DNS-omdirigeringen for «{{key}}»?",
"rewrite_desc": "Lar deg enkelt konfigurere selvvalgte DNS-tilbakemeldinger for et spesifikt domenenavn.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Aby skorzystać z <1>DNS-over-HTTPS</1> lub <1>DNS-over-TLS</1>, musisz w ustawieniach AdGuard Home <0>skonfigurować szyfrowanie</0>.",
"rewrite_added": "Pomyślnie dodano przepisanie DNS dla „{{key}}”",
"rewrite_deleted": "Przepisanie DNS dla „{{key}}” zostało pomyślnie usunięte",
"rewrite_updated": "Pomyślnie zaktualizowano przepisywanie DNS",
"rewrite_add": "Dodaj przepisywanie DNS",
"rewrite_edit": "Edytuj przepisywanie DNS",
"rewrite_not_found": "Nie znaleziono przepisywania DNS",
"rewrite_confirm_delete": "Czy na pewno chcesz usunąć przepisywanie DNS dla „{{key}}”?",
"rewrite_desc": "Pozwala łatwo skonfigurować niestandardową odpowiedź DNS dla określonej nazwy domeny.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Para usar o <1>DNS-sobre-HTTPS</1> ou <1>DNS-sobre-TLS</1>, você precisa <0>configurar a criptografia</0> nas configurações do AdGuard Home.",
"rewrite_added": "Reescrita de DNS para \"{{key}}\" adicionada com sucesso",
"rewrite_deleted": "Reescrita de DNS para \"{{key}}\" excluída com sucesso",
"rewrite_updated": "Reconfiguração de DNS atualizada com êxito",
"rewrite_add": "Adicionar reescrita de DNS",
"rewrite_edit": "Editar reconfiguração de DNS",
"rewrite_not_found": "Nenhuma reescrita de DNS foi encontrada",
"rewrite_confirm_delete": "Você tem certeza de que deseja excluir a reescrita de DNS para \"{{key}}\"?",
"rewrite_desc": "Permite configurar uma resposta personalizada do DNS para um nome de domínio específico.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Para usar o <1>DNS-sobre-HTTPS</1> ou <1>DNS-sobre-TLS</1>, precisa de <0>configurar a criptografia</0> nas configurações do AdGuard Home.",
"rewrite_added": "Reescrita de DNS para \"{{key}}\" adicionada com sucesso",
"rewrite_deleted": "Reescrita de DNS para \"{{key}}\" excluída com sucesso",
"rewrite_updated": "Reedição de DNS atualizada com sucesso",
"rewrite_add": "Adicionar reescrita de DNS",
"rewrite_edit": "Editar reedição de DNS",
"rewrite_not_found": "Nenhuma reescrita de DNS foi encontrada",
"rewrite_confirm_delete": "Tem a certeza de que deseja excluir a reescrita de DNS para \"{{key}}\"?",
"rewrite_desc": "Permite configurar uma resposta personalizada do DNS para um nome de domínio específico.",

View File

@@ -167,6 +167,7 @@
"enabled_parental_toast": "Control Parental activat",
"disabled_safe_search_toast": "Căutare protejată dezactivată",
"enabled_save_search_toast": "Căutare protejată activată",
"updated_save_search_toast": "Setări Căutare sigură actualizate",
"enabled_table_header": "Activat",
"name_table_header": "Nume",
"list_url_table_header": "Lista URL",
@@ -256,12 +257,12 @@
"query_log_cleared": "Jurnalul de interogare a fost șters cu succes",
"query_log_updated": "Jurnalul de solicitări a fost actualizat cu succes",
"query_log_clear": "Curăță jurnalele",
"query_log_retention": "Retenție jurnale interogare",
"query_log_retention": "Interogarea jurnalelor de rotație",
"query_log_enable": "Activați jurnal",
"query_log_configuration": "Configurația jurnalelor",
"query_log_disabled": "Jurnalul de interogare este dezactivat și poate fi configurat în <0>setări</0>",
"query_log_strict_search": "Utilizați ghilimele duble pentru căutare strictă",
"query_log_retention_confirm": "Sunteți sigur doriți să schimbați retenția jurnalului de interogare? Reducând valoarea intervalului, unele date vor fi pierdute",
"query_log_retention_confirm": "Sigur doriți să modificați rotația jurnalului de interogări? Dacă micșorați valoarea intervalului, unele date se vor pierde",
"anonymize_client_ip": "Anonimizare client IP",
"anonymize_client_ip_desc": "Nu salvați adresa IP completă a clientului în jurnale și statistici",
"dns_config": "Configurația serverului DNS",
@@ -290,6 +291,8 @@
"rate_limit": "Limita ratei",
"edns_enable": "Activați subrețeaua de clienți EDNS",
"edns_cs_desc": "Adaugă opțiunea EDNS Client Subnet (ECS) la solicitările în amonte și înregistrează valorile trimise de clienți în jurnalul de interogare.",
"edns_use_custom_ip": "Utilizați IP personalizat pentru EDNS",
"edns_use_custom_ip_desc": "Permiteți utilizarea IP-ului personalizat pentru EDNS",
"rate_limit_desc": "Numărul de interogări pe secundă permise pe client. Setarea la 0 înseamnă că nu există limită.",
"blocking_ipv4_desc": "Adresa IP de returnat pentru o cerere A de blocare",
"blocking_ipv6_desc": "Adresa IP de returnat pentru o cerere AAAA de blocare",
@@ -475,7 +478,9 @@
"setup_dns_notice": "Pentru a utiliza <1>DNS-over-HTTPS</1> sau <1>DNS-over-TLS</1>, trebuie să <0>configurați Criptarea</0> în setările AdGuard Home.",
"rewrite_added": "Rescriere DNS pentru \"{{key}}\" adăugată cu succes",
"rewrite_deleted": "Rescriere DNS pentru \"{{key}}\" ștearsă cu succes",
"rewrite_updated": "DNS rescrie actualizat cu succes",
"rewrite_add": "Adăugați rescriere DNS",
"rewrite_edit": "Editați rescrierea DNS",
"rewrite_not_found": "Nu s-au găsit rescrieri DNS",
"rewrite_confirm_delete": "Sunteți sigur că doriți să ștergeți rescrierea DNS pentru \"{{key}}\"?",
"rewrite_desc": "Permite configurarea cu ușurință a răspunsului personalizat DNS pentru un nume de domeniu specific.",
@@ -523,6 +528,10 @@
"statistics_retention_confirm": "Sunteți sigur că doriți să schimbați păstrarea statisticilor? Dacă reduceți valoarea intervalului, unele date vor fi pierdute",
"statistics_cleared": "Statisticile au fost șterse cu succes",
"statistics_enable": "Activați statisticile",
"ignore_domains": "Domenii ignorate (separate prin linie nouă)",
"ignore_domains_title": "Domenii ignorate",
"ignore_domains_desc_stats": "Interogările pentru aceste domenii nu sunt scrise în statistici",
"ignore_domains_desc_query": "Interogările pentru aceste domenii nu sunt scrise în jurnalul de interogări",
"interval_hours": "{{count}} oră",
"interval_hours_plural": "{{count}} ore",
"filters_configuration": "Configurația filtrelor",
@@ -643,5 +652,29 @@
"confirm_dns_cache_clear": "Sunteți sigur că doriți să ștergeți memoria cache DNS?",
"cache_cleared": "Cache-ul DNS a fost golit cu succes",
"clear_cache": "Goliți memoria cache",
"protection_section_label": "Protecție"
"make_static": "Faceți static",
"theme_auto_desc": "Auto (pe baza schemei de culori a dispozitivului dvs.)",
"theme_dark_desc": "Temă întunecată",
"theme_light_desc": "Temă luminoasă",
"disable_for_seconds": "Timp de {{count}} secundă",
"disable_for_seconds_plural": "Timp de {{count}} secunde",
"disable_for_minutes": "Timp de {{count}} minut",
"disable_for_minutes_plural": "Timp de {{count}} minute",
"disable_for_hours": "Timp de {{count}} oră",
"disable_for_hours_plural": "Timp de {{count}} ore",
"disable_until_tomorrow": "Până mâine",
"disable_notify_for_seconds": "Dezactivați protecția timp de {{count}} secundă",
"disable_notify_for_seconds_plural": "Dezactivați protecția timp de {{count}} secunde",
"disable_notify_for_minutes": "Dezactivați protecția timp de {{count}} minut",
"disable_notify_for_minutes_plural": "Dezactivați protecția timp de {{count}} minute",
"disable_notify_for_hours": "Dezactivează protecția timp de {{count}} oră",
"disable_notify_for_hours_plural": "Dezactivați protecția timp de {{count}} ore",
"disable_notify_until_tomorrow": "Dezactivează protecția până mâine",
"enable_protection_timer": "Protecția va fi activată în {{time}}",
"custom_retention_input": "Introduceți reținerea în ore",
"custom_rotation_input": "Introduceți rotația în ore",
"protection_section_label": "Protecție",
"log_and_stats_section_label": "Jurnal de interogări și statistici",
"ignore_query_log": "Ignorați acest client în jurnalul de interogări",
"ignore_statistics": "Ignorați acest client în statistici"
}

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Чтобы использовать <1>DNS-over-HTTPS</1> или <1>DNS-over-TLS</1>, вам нужно <0>настроить шифрование</0> в настройках AdGuard Home.",
"rewrite_added": "Правило перезаписи DNS-запросов для «{{key}}» успешно добавлено",
"rewrite_deleted": "Правило перезаписи DNS-запросов для «{{key}}» успешно удалено",
"rewrite_updated": "Правило перезаписи DNS-запросов успешно обновлено",
"rewrite_add": "Добавить правило перезаписи DNS-запросов",
"rewrite_edit": "Редактировать правило перезаписи DNS-запросов",
"rewrite_not_found": "Не найдено правил перезаписи DNS-запросов",
"rewrite_confirm_delete": "Вы уверены, что хотите удалить правило перезаписи DNS-запросов для «{{key}}»?",
"rewrite_desc": "Позволяет легко настроить пользовательский DNS-ответ для определеннного домена.",

View File

@@ -153,6 +153,7 @@
"enabled_parental_toast": "දෙමාපිය පාලනය සබල කෙරිණි",
"disabled_safe_search_toast": "ආරක්‍ෂිත සෙවුම අබල කෙරිණි",
"enabled_save_search_toast": "ආරක්‍ෂිත සෙවුම සබල කෙරිණි",
"updated_save_search_toast": "ආරක්‍ෂිත සෙවුමේ සැකසුම් යාවත්කාල විය",
"enabled_table_header": "සබලයි",
"name_table_header": "නම",
"list_url_table_header": "ඒ.ස.නි.(URL) ලැයිස්තුව",
@@ -237,12 +238,12 @@
"query_log_cleared": "විමසුම් සටහන සාර්ථකව හිස් කර ඇත",
"query_log_updated": "විමසුම් සටහන සාර්ථකව යාවත්කාල කෙරිණි",
"query_log_clear": "විමසුම් සටහන් හිස් කරන්න",
"query_log_retention": "විමසුම් සටහන් රඳවා තබා ගැනීම",
"query_log_retention": "විමසුම් සටහන් රඳවීම",
"query_log_enable": "සටහන සබල කරන්න",
"query_log_configuration": "සටහන් වින්‍යාසය",
"query_log_disabled": "විමසුම් සටහන අබල කර ඇති අතර එය <0>සැකසුම්</0> තුළ වින්‍යාසගත කළ හැකිය",
"query_log_strict_search": "ඉතා නිවැරදිව සෙවීමට ද්විත්ව උද්ධෘතය භාවිතා කරන්න",
"query_log_retention_confirm": "විමසුම් සටහන රඳවා තබා ගැනීම වෙනස් කිරීමට ඇවැසි බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
"query_log_retention_confirm": "විමසුම් සටහන රඳවා තබා ගැනීම වෙනස් කිරීමට වුවමනා ද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
"anonymize_client_ip": "අනුග්‍රාහකයෙහි අ.ජා.කෙ. (IP) නිර්නාමික කරන්න",
"anonymize_client_ip_desc": "සටහන් සහ සංඛ්‍යාලේඛන තුළ අනුග්‍රාහකයේ පූර්ණ අ.ජා.කෙ. ලිපිනය සුරකින්න එපා",
"dns_config": "ව.නා.ප. සේවාදායක වින්‍යාසය",
@@ -270,6 +271,8 @@
"form_enter_rate_limit": "අනුපාත සීමාව ඇතුල් කරන්න",
"rate_limit": "අනුපාත සීමාව",
"edns_enable": "EDNS අනුග්‍රාහක අනුජාලය සබල කරන්න",
"edns_use_custom_ip": "EDNS සඳහා අභිරුචි අ.ජා.කෙ. යොදාගන්න",
"edns_use_custom_ip_desc": "EDNS සඳහා අභිරුචි අ.ජා.කෙ. භාවිතයට ඉඩදෙන්න",
"rate_limit_desc": "එක් අනුග්‍රාහකයකට ඉඩ දී ඇති තත්පරයට ඉල්ලීම් ගණන. එය 0 ලෙස සැකසීම යනුවෙන් අදහස් කරන්නේ සීමාවක් නැති බවයි.",
"blocking_ipv4_desc": "අවහිර කළ A ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය",
"blocking_ipv6_desc": "අවහිර කළ AAAA ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය",
@@ -278,6 +281,9 @@
"blocking_mode_nxdomain": "නොපවතින වසම: NXDOMAIN කේතය සමඟ ප්‍රතිචාර දක්වයි",
"blocking_mode_null_ip": "අභිශූන්‍යය අ.ජා.කෙ.: ශුන්‍ය අ.ජා.කෙ. ලිපිනය සමඟ ප්‍රතිචාර දක්වයි (A සඳහා 0.0.0.0; AAAA සඳහා ::)",
"blocking_mode_custom_ip": "අභිරුචි අන්තර්ජාල කෙටුම්පත: අතින් සැකසූ අ.ජා. කෙ. ලිපිනයක් සමඟ ප්‍රතිචාර දක්වයි",
"theme_auto": "ස්වයං",
"theme_light": "දීප්ත",
"theme_dark": "අඳුරු",
"upstream_dns_client_desc": "ඔබ මෙම ක්ෂේත්‍රය හිස්ව තබා ගන්නේ නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් <0>ව.නා.ප. සැකසුම්</0> හි වින්‍යාසගත කර ඇති සේවාදායක භාවිතා කරනු ඇත.",
"tracker_source": "ලුහුබැඳීම් මූලාශ්‍රය",
"source_label": "මූලාශ්‍රය",
@@ -370,6 +376,7 @@
"encryption_issuer": "නිකුත් කරන්නා",
"encryption_hostnames": "ධාරක නාම",
"encryption_reset": "සංකේතාංකන සැකසුම් යළි පිහිටුවීමට අවශ්‍ය බව ඔබට විශ්වාස ද?",
"encryption_warning": "අවවාදයයි",
"topline_expiring_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත්වීමට ආසන්න වී ඇත. <0>සංකේතන සැකසුම්</0> යාවත්කාල කරන්න.",
"topline_expired_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත් වී ඇත. <0>සංකේතන සැකසුම්</0> යාවත්කාල කරන්න.",
"form_error_port_range": "80-65535 පරාසය හි තොටක අගයක් ඇතුල් කරන්න",
@@ -490,8 +497,10 @@
"statistics_clear": "සංඛ්‍යාලේඛන හිස් කරන්න",
"statistics_clear_confirm": "සංඛ්‍යාලේඛන ඉවත් කිරීමට වුවමනා ද?",
"statistics_retention_confirm": "සංඛ්‍යාලේඛන රඳවා තබා ගැනීම වෙනස් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
"statistics_cleared": "සංඛ්‍යාලේඛන සාර්ථකව ඉවත් කෙරිණි",
"statistics_cleared": "සංඛ්‍යාලේඛන සාර්ථකව හිස් කෙරිණි",
"statistics_enable": "සංඛ්‍යාලේඛන සබල කරන්න",
"ignore_domains": "නොසලකන වසම් (පේළියකට එක බැගින්)",
"ignore_domains_title": "නොසලකන වසම්",
"interval_hours": "පැය {{count}}",
"interval_hours_plural": "පැය {{count}}",
"filters_configuration": "පෙරහන් වින්‍යාසය",
@@ -601,5 +610,31 @@
"parental_control": "දෙමාපිය පාලනය",
"safe_browsing": "ආරක්‍ෂිත පිරික්සුම",
"served_from_cache": "{{value}} <i>(නිහිතයෙන් ගැනිණි)</i>",
"form_error_password_length": "මුරපදය අවම වශයෙන් අකුරු {{value}} ක් දිගු විය යුතුමයි"
"form_error_password_length": "මුරපදය අවම වශයෙන් අකුරු {{value}} ක් දිගු විය යුතුමයි",
"cache_cleared": "ව.නා.ප. නිහිතය හිස් කෙරිණි",
"clear_cache": "නිහිතය මකන්න",
"make_static": "ස්ථිතික කරන්න",
"theme_dark_desc": "අඳුරු තේමාව",
"theme_light_desc": "දීප්ත තේමාව",
"disable_for_seconds": "තත්පර {{count}} ක්",
"disable_for_seconds_plural": "තත්පර {{count}} ක්",
"disable_for_minutes": "විනාඩි {{count}} ක්",
"disable_for_minutes_plural": "විනාඩි {{count}} ක්",
"disable_for_hours": "පැය {{count}} ක්",
"disable_for_hours_plural": "පැය {{count}} ක්",
"disable_until_tomorrow": "හෙට වනතුරු",
"disable_notify_for_seconds": "තත්. {{count}} කට රැකවරණය අබල කරන්න",
"disable_notify_for_seconds_plural": "තත්. {{count}} කට රැකවරණය අබල කරන්න",
"disable_notify_for_minutes": "විනාඩි {{count}} කට රැකවරණය අබල කරන්න",
"disable_notify_for_minutes_plural": "විනාඩි {{count}} කට රැකවරණය අබල කරන්න",
"disable_notify_for_hours": "පැය {{count}} කට රැකවරණය අබල කරන්න",
"disable_notify_for_hours_plural": "පැය {{count}} කට රැකවරණය අබල කරන්න",
"disable_notify_until_tomorrow": "හෙට වනතුරු රැකවරණය අබල කරන්න",
"enable_protection_timer": "{{time}} න් රැකවරණය සබල කෙරේ",
"custom_retention_input": "රඳවා ගැනීම පැය වලින්",
"custom_rotation_input": "රඳවා ගැනීම පැය වලින්",
"protection_section_label": "රැකවරණය",
"log_and_stats_section_label": "විමසුම් සටහන හා සංඛ්‍යාලේඛන",
"ignore_query_log": "සටහනෙහි අනුග්‍රාහකය නොසලකන්න",
"ignore_statistics": "සංඛ්‍යාලේඛනයට අනුග්‍රාහකය නොසලකන්න"
}

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Pre použitie <1>DNS-over-HTTPS</1> alebo <1>DNS-over-TLS</1>, potrebujete v nastaveniach AdGuard Home <0>nakonfigurovať šifrovanie</0>.",
"rewrite_added": "DNS prepísanie pre \"{{key}}\" bolo úspešne pridané",
"rewrite_deleted": "DNS prepísanie pre \"{{key}}\" bolo úspešne vymazané",
"rewrite_updated": "Prepísanie DNS bolo úspešne aktualizované",
"rewrite_add": "Pridať DNS prepísanie",
"rewrite_edit": "Upraviť prepísanie DNS",
"rewrite_not_found": "Neboli nájdené žiadne DNS prepísania",
"rewrite_confirm_delete": "Naozaj chcete odstrániť prepísanie DNS pre \"{{key}}\"?",
"rewrite_desc": "Umožňuje ľahko nakonfigurovať vlastnú odpoveď DNS pre konkrétne meno domény.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Za uporabo <1>DNS-prek-HTTPS</1> ali <1>DNS-prek-TLS</1>, morate <0>konfigurirati šifriranje</0> v nastavitvah AdGuard Home.",
"rewrite_added": "Uspešno je dodano DNS prepisovanje za \"{{key}}\"",
"rewrite_deleted": "Uspešno je izbrisano DNS prepisovanje za \"{{key}}\"",
"rewrite_updated": "DNS prepisovanje uspešno posodobljen",
"rewrite_add": "Dodaj prepisovanje DNS",
"rewrite_edit": "Urejanje prepisa DNS",
"rewrite_not_found": "Ni bilo najdenih prepisovanj DNS",
"rewrite_confirm_delete": "Ali ste prepričani, da želite izbrisati prepisovanje DNS za \"{{key}}\"?",
"rewrite_desc": "Omogoča enostavno konfiguriranje odgovora DNS po meri za določeno ime domene.",

View File

@@ -475,7 +475,9 @@
"setup_dns_notice": "Kako biste koristili <1>DNS-over-HTTPS</1> ili <1>DNS-over-TLS</1>, potrebno je da <0>konfigurišete šifrovanje</0> u AdGuard Home postavkama.",
"rewrite_added": "DNS prepisivanje za \"{{key}}\" je uspešno dodato",
"rewrite_deleted": "DNS prepisivanje za \"{{key}}\" uspešno izbrisano",
"rewrite_updated": "DNS ponovo napisao uspešno ažuriran",
"rewrite_add": "Dodaj DNS prepisivanje",
"rewrite_edit": "Uređivanje DNS prepravke",
"rewrite_not_found": "DNS prepisivanja nisu pronađena",
"rewrite_confirm_delete": "Jeste li sigurni da želite da izbrišete DNS prepisivanje za \"{{key}}\"?",
"rewrite_desc": "Dozvoljava da jednostavno konfigurišete prilagođeni DNS odgovor za određeni domen.",

View File

@@ -475,7 +475,9 @@
"setup_dns_notice": "För att kunna använda <1>DNS-över-HTTPS</1> eller <1>DNS-över-TLS</1>, behöver du <0>konfigurera Kryptering</0> i AdGuard Home-inställningar.",
"rewrite_added": "DNS-omskrivning för \"{{key}}\" lyckad",
"rewrite_deleted": "DNS-omskrivning för \"{{key}}\" har tagits bort",
"rewrite_updated": "DNS-omskrivning har uppdaterats",
"rewrite_add": "Lägg till DNS omskrivning",
"rewrite_edit": "Redigera DNS-omskrivning",
"rewrite_not_found": "Inga DNS omskrivningar hittades",
"rewrite_confirm_delete": "Är du säker på att du vill ta bort DNS-omskrivningen för \"{{key}}\"?",
"rewrite_desc": "Gör det enkelt att konfigurera anpassat DNS svar för ett specifikt domännamn.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "<1>DNS-over-HTTPS</1> veya <1>DNS-over-TLS</1> protokolünü kullanmak için AdGuard Home üzerinde <0>Şifreleme ayarları</0> bölümünden ayarları yapmanız gerekir.",
"rewrite_added": "\"{{key}}\" için DNS yeniden yazımı başarıyla eklendi",
"rewrite_deleted": "\"{{key}}\" için DNS yeniden yazımı başarıyla silindi",
"rewrite_updated": "DNS yeniden yazma başarıyla güncellendi",
"rewrite_add": "DNS yeniden yazımı ekle",
"rewrite_edit": "DNS yeniden yazmayı düzenle",
"rewrite_not_found": "DNS yeniden yazımı bulunamadı",
"rewrite_confirm_delete": "\"{{key}}\" için DNS yeniden yazımını silmek istediğinize emin misiniz?",
"rewrite_desc": "Belirli bir alan adı için özel DNS yanıtını kolayca yapılandırmanızı sağlar.",

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "Для використання <1>DNS-over-HTTPS</1> або <1>DNS-over-TLS</1>, вам потрібно <0>налаштувати Шифрування</0> в налаштуваннях AdGuard Home.",
"rewrite_added": "Перезапис DNS для «{{key}}» успішно додано",
"rewrite_deleted": "Перезапис DNS для «{{key}}» успішно видалено",
"rewrite_updated": "Перезапис DNS успішно оновлено",
"rewrite_add": "Додати перезапис DNS",
"rewrite_edit": "Редагувати перезапис DNS",
"rewrite_not_found": "Перезаписів DNS не знайдено",
"rewrite_confirm_delete": "Ви впевнені, що хочете видалити перезапис DNS для «{{key}}»?",
"rewrite_desc": "Дозволяє легко налаштувати власну відповідь DNS для певного доменного імені.",

View File

@@ -167,6 +167,7 @@
"enabled_parental_toast": "Đã bật quản lý của phụ huynh",
"disabled_safe_search_toast": "Đã tắt tìm kiếm an toàn",
"enabled_save_search_toast": "Đã bật tìm kiếm an toàn",
"updated_save_search_toast": "Cài đặt Tìm kiếm an toàn đã được cập nhật",
"enabled_table_header": "Kích hoạt",
"name_table_header": "Tên",
"list_url_table_header": "URL bộ lọc",
@@ -256,12 +257,12 @@
"query_log_cleared": "Nhật ký truy vấn đã được xóa thành công",
"query_log_updated": "Cập nhật thành công nhật kí truy xuất",
"query_log_clear": "Xóa nhật ký truy vấn",
"query_log_retention": "Lưu giữ nhật ký truy vấn",
"query_log_retention": "Xoay vòng nhật ký truy vấn",
"query_log_enable": "Bật nhật ký",
"query_log_configuration": "Cấu hình nhật ký",
"query_log_disabled": "Nhật ký truy vấn bị vô hiệu hóa và có thể được định cấu hình trong <0>cài đặt</ 0>",
"query_log_strict_search": "Sử dụng dấu ngoặc kép để tìm kiếm nghiêm ngặt",
"query_log_retention_confirm": "Bạn có chắc chắn muốn thay đổi lưu giữ nhật ký truy vấn? Nếu bạn giảm giá trị khoảng, một số dữ liệu sẽ bị mất",
"query_log_retention_confirm": "Bạn có chắc chắn muốn thay đổi xoay vòng nhật ký truy vấn không? Nếu bạn giảm giá trị khoảng thời gian, một số dữ liệu sẽ bị mất",
"anonymize_client_ip": "Ẩn danh IP khách",
"anonymize_client_ip_desc": "Không lưu địa chỉ IP đầy đủ của khách hàng trong nhật ký và thống kê",
"dns_config": "Thiết lập máy chủ DNS",
@@ -290,6 +291,8 @@
"rate_limit": "Giới hạn yêu cầu",
"edns_enable": "Bật mạng con EDNS Client",
"edns_cs_desc": "Thêm tùy chọn EDNS Client Subnet (ECS) vào các yêu cầu ngược dòng và ghi lại các giá trị được gửi bởi các máy khách trong nhật ký truy vấn.",
"edns_use_custom_ip": "Sử dụng địa chỉ IP tùy chỉnh cho EDNS",
"edns_use_custom_ip_desc": "Cho phép sử dụng địa chỉ IP tùy chỉnh cho EDNS",
"rate_limit_desc": "Số lượng yêu cầu mỗi giây mà một khách hàng được phép thực hiện (0: không giới hạn)",
"blocking_ipv4_desc": "Địa chỉ IP được trả lại cho một yêu cầu A bị chặn",
"blocking_ipv6_desc": "Địa chỉ IP được trả lại cho một yêu cầu AAA bị chặn",
@@ -475,7 +478,9 @@
"setup_dns_notice": "Để sử dụng <1>DNS-over-HTTPS</1> hoặc <1>DNS-over-TLS</1>, bạn cần <0>định cấu hình Mã hóa</0> trong cài đặt AdGuard Home.",
"rewrite_added": "DNS viết lại cho \"{{key}}\" đã thêm thành công",
"rewrite_deleted": "DNS viết lại cho \"{{key}}\" đã xóa thành công",
"rewrite_updated": "Viết lại DNS được cập nhật thành công",
"rewrite_add": "Thêm DNS viết lại",
"rewrite_edit": "Chỉnh sửa viết lại DNS",
"rewrite_not_found": "Không tìm thấy DNS viết lại",
"rewrite_confirm_delete": "Bạn có chắc chắn muốn xóa DNS viết lại cho \"{{key}}\" không?",
"rewrite_desc": "Cho phép dễ dàng định cấu hình tùy chỉnh DNS phản hồi cho một tên miền cụ thể.",
@@ -523,6 +528,10 @@
"statistics_retention_confirm": "Bạn có chắc chắn muốn thay đổi lưu giữ số liệu thống kê? Nếu bạn giảm giá trị khoảng, một số dữ liệu sẽ bị mất",
"statistics_cleared": "Xoá thống kê thành công",
"statistics_enable": "Bật thống kê",
"ignore_domains": "Các miền bị bỏ qua (cách nhau bởi dòng mới)",
"ignore_domains_title": "Các miền bị bỏ qua",
"ignore_domains_desc_stats": "Các truy vấn cho các miền này sẽ không được ghi vào thống kê",
"ignore_domains_desc_query": "Các truy vấn cho các miền này sẽ không được ghi vào nhật ký truy vấn",
"interval_hours": "{{count}} giờ",
"interval_hours_plural": "{{count}} giờ",
"filters_configuration": "Cấu hình bộ lọc",
@@ -643,5 +652,29 @@
"confirm_dns_cache_clear": "Bạn có chắc chắn muốn xóa bộ đệm ẩn DNS không?",
"cache_cleared": "Đã xóa thành công bộ đệm DNS",
"clear_cache": "Xóa bộ nhớ cache",
"protection_section_label": "Sự bảo vệ"
"make_static": "Chuyển sang tĩnh",
"theme_auto_desc": "Tự động (dựa trên chủ đề màu của thiết bị của bạn)",
"theme_dark_desc": "Chủ đề tối",
"theme_light_desc": "Chủ đề sáng",
"disable_for_seconds": "Trong {{count}} giây",
"disable_for_seconds_plural": "Trong {{count}} giây",
"disable_for_minutes": "Trong {{count}} phút",
"disable_for_minutes_plural": "Trong {{count}} phút",
"disable_for_hours": "Trong {{count}} giờ",
"disable_for_hours_plural": "Trong {{count}} giờ",
"disable_until_tomorrow": "Cho đến ngày mai",
"disable_notify_for_seconds": "Tắt bảo vệ trong {{count}} giây",
"disable_notify_for_seconds_plural": "Tắt bảo vệ trong {{count}} giây",
"disable_notify_for_minutes": "Tắt bảo vệ trong {{count}} phút",
"disable_notify_for_minutes_plural": "Tắt bảo vệ trong {{count}} phút",
"disable_notify_for_hours": "Tắt bảo vệ trong {{count}} giờ",
"disable_notify_for_hours_plural": "Tắt bảo vệ trong {{count}} giờ",
"disable_notify_until_tomorrow": "Vô hiệu hóa bảo vệ cho đến ngày mai",
"enable_protection_timer": "Bảo vệ sẽ được bật trong {{time}}",
"custom_retention_input": "Nhập thời gian giữ lại theo giờ",
"custom_rotation_input": "Nhập chu kỳ theo giờ",
"protection_section_label": "Sự bảo vệ",
"log_and_stats_section_label": "Nhật ký truy vấn và thống kê",
"ignore_query_log": "Bỏ qua máy khách này trong nhật ký truy vấn",
"ignore_statistics": "Bỏ qua máy khách này trong thống kê"
}

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "为了使用 <1>DNS-over-HTTPS</1> 或者 <1>DNS-over-TLS</1> ,您需要在 AdGuard Home 设置中 <0>配置加密</0> 。",
"rewrite_added": "已成功添加 \"{{key}}\" 的 DNS 重写",
"rewrite_deleted": "已成功删除 \"{{key}}\" 的 DNS 重写",
"rewrite_updated": "DNS 重写已成功更新",
"rewrite_add": "添加 DNS 重写",
"rewrite_edit": "编辑 DNS 重写",
"rewrite_not_found": "未找到 DNS 重写",
"rewrite_confirm_delete": "您确定要删除 \"{{key}}\" 的 DNS 重写?",
"rewrite_desc": "可以轻松地为特定域名配置自定义 DNS 响应。",

View File

@@ -48,6 +48,7 @@
"out_of_range_error": "必須介於 \"{{start}}\" - \"{{end}}\" 範圍之外",
"lower_range_start_error": "必須小於起始值",
"greater_range_start_error": "必須大於起始值",
"gateway_or_subnet_invalid": "無效子網路",
"dhcp_form_gateway_input": "閘道 IP 位址",
"dhcp_form_subnet_input": "子網路遮罩",
"dhcp_form_range_title": "IP 位址範圍",
@@ -195,6 +196,7 @@
"form_error_url_or_path_format": "列表中含有的 URL 網址或絕對路徑",
"custom_filter_rules": "自訂過濾規則",
"custom_filter_rules_hint": "一行一條規則。您可以使用「adblock」語法或「hosts檔案」的語法。",
"system_host_files": "系統 hosts 檔案",
"examples_title": "範例",
"example_meaning_filter_block": "封鎖對 example.org 網域及其所有子網域的存取",
"example_meaning_filter_whitelist": "解除對 example.org 網域及其所有子網域存取封鎖",
@@ -279,6 +281,8 @@
"rate_limit": "速率限制",
"edns_enable": "啟用 EDNS Client Subnet",
"edns_cs_desc": "傳送用戶端的子網路給 DNS 伺服器。",
"edns_use_custom_ip": "使用自訂 EDNS IP",
"edns_use_custom_ip_desc": "允許使用自訂 EDNS IP",
"rate_limit_desc": "限制單一裝置每秒發出的查詢次數(設定為 0 即表示無限制)",
"blocking_ipv4_desc": "回覆指定 IPv4 位址給被封鎖的網域的 A 紀錄查詢",
"blocking_ipv6_desc": "回覆指定 IPv6 位址給被封鎖的網域的 AAAA 紀錄查詢",
@@ -287,6 +291,9 @@
"blocking_mode_nxdomain": "NXDOMAIN回應 NXDOMAIN 狀態碼",
"blocking_mode_null_ip": "Null IP回應零值的 IP 位址A 紀錄回應 0.0.0.0 AAAA 紀錄回應 ::",
"blocking_mode_custom_ip": "自訂 IP 位址:回應一個自訂的 IP 位址",
"theme_auto": "自動",
"theme_light": "明亮",
"theme_dark": "深色",
"upstream_dns_client_desc": "如果您將此欄位留白AdGuard Home 將使用 <0>DNS 設定</0> 內的設定的 DNS 伺服器。",
"tracker_source": "追蹤器來源",
"source_label": "來源",
@@ -397,6 +404,7 @@
"dns_providers": "下列為常見的<0> DNS 伺服器</0>。",
"update_now": "立即更新",
"update_failed": "自動更新發生錯誤。請嘗試依照<a>以下步驟</a> 來手動更新。",
"manual_update": "請嘗試依照<a>下列步驟</a>來手動更新。",
"processing_update": "請稍候AdGuard Home 正在更新",
"clients_title": "用戶端",
"clients_desc": "對已連接到 AdGuard Home 的裝置進行設定",
@@ -613,5 +621,22 @@
"original_response": "原始回應",
"click_to_view_queries": "按一下以檢視查詢結果",
"port_53_faq_link": "連接埠 53 經常被「DNSStubListener」或「systemd-resolved」服務佔用。請閱讀下列有關解決<0>這個問題</0>的說明",
"adg_will_drop_dns_queries": "AdGuard Home 將停止回應此用戶端的所有 DNS 查詢。"
"adg_will_drop_dns_queries": "AdGuard Home 將停止回應此用戶端的所有 DNS 查詢。",
"safe_browsing": "安全瀏覽",
"served_from_cache": "{{value}} <i>(由快取回應)</i>",
"form_error_password_length": "密碼必須至少 {{value}} 個字元長度",
"theme_dark_desc": "深色主題",
"theme_light_desc": "淺色主題",
"disable_for_seconds": "{{count}} 秒",
"disable_for_seconds_plural": "{{count}} 秒",
"disable_for_minutes": "{{count}} 分鐘",
"disable_for_minutes_plural": "{{count}} 分鐘",
"disable_for_hours": "{{count}} 小時",
"disable_for_hours_plural": "{{count}} 小時",
"disable_until_tomorrow": "直到明天",
"disable_notify_for_seconds": "暫停防護 {{count}} 秒",
"disable_notify_for_seconds_plural": "暫停防護 {{count}} 秒",
"disable_notify_for_minutes": "暫停防護 {{count}} 分鐘",
"disable_notify_for_minutes_plural": "暫停防護 {{count}} 分鐘",
"disable_notify_for_hours": "暫停防護 {{count}} 小時"
}

View File

@@ -478,7 +478,9 @@
"setup_dns_notice": "為了使用 <1>DNS-over-HTTPS</1> 或 <1>DNS-over-TLS</1>,您需要在 AdGuard Home 設定裡<0>配置加密</0>。",
"rewrite_added": "對於 \"{{key}}\" 之 DNS 改寫被成功地加入",
"rewrite_deleted": "對於 \"{{key}}\" 之 DNS 改寫被成功地刪除",
"rewrite_updated": "DNS 重寫已成功更新",
"rewrite_add": "新增 DNS 改寫",
"rewrite_edit": "編輯 DNS 重寫",
"rewrite_not_found": "無已發現之 DNS 改寫",
"rewrite_confirm_delete": "您確定您想要刪除對於 \"{{key}}\" 之 DNS 改寫嗎?",
"rewrite_desc": "允許輕易地配置自訂的 DNS 回應供特定的域名。",

View File

@@ -38,6 +38,29 @@ export const addRewrite = (config) => async (dispatch) => {
}
};
export const updateRewriteRequest = createAction('UPDATE_REWRITE_REQUEST');
export const updateRewriteFailure = createAction('UPDATE_REWRITE_FAILURE');
export const updateRewriteSuccess = createAction('UPDATE_REWRITE_SUCCESS');
/**
* @param {Object} config
* @param {string} config.target - current DNS rewrite value
* @param {string} config.update - updated DNS rewrite value
*/
export const updateRewrite = (config) => async (dispatch) => {
dispatch(updateRewriteRequest());
try {
await apiClient.updateRewrite(config);
dispatch(updateRewriteSuccess());
dispatch(toggleRewritesModal());
dispatch(getRewritesList());
dispatch(addSuccessToast(i18next.t('rewrite_updated', { key: config.domain })));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(updateRewriteFailure());
}
};
export const deleteRewriteRequest = createAction('DELETE_REWRITE_REQUEST');
export const deleteRewriteFailure = createAction('DELETE_REWRITE_FAILURE');
export const deleteRewriteSuccess = createAction('DELETE_REWRITE_SUCCESS');

View File

@@ -455,6 +455,8 @@ class Api {
REWRITE_ADD = { path: 'rewrite/add', method: 'POST' };
REWRITE_UPDATE = { path: 'rewrite/update', method: 'PUT' };
REWRITE_DELETE = { path: 'rewrite/delete', method: 'POST' };
getRewritesList() {
@@ -470,6 +472,14 @@ class Api {
return this.makeRequest(path, method, parameters);
}
updateRewrite(config) {
const { path, method } = this.REWRITE_UPDATE;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
deleteRewrite(config) {
const { path, method } = this.REWRITE_DELETE;
const parameters = {

View File

@@ -105,6 +105,7 @@ Form.propTypes = {
submitting: PropTypes.bool.isRequired,
processingAdd: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
initialValues: PropTypes.object,
};
export default flow([

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next';
import ReactModal from 'react-modal';
import { MODAL_TYPE } from '../../../helpers/constants';
import Form from './Form';
const Modal = (props) => {
@@ -12,6 +13,8 @@ const Modal = (props) => {
toggleRewritesModal,
processingAdd,
processingDelete,
modalType,
currentRewrite,
} = props;
return (
@@ -24,13 +27,18 @@ const Modal = (props) => {
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">
<Trans>rewrite_add</Trans>
{modalType === MODAL_TYPE.EDIT_REWRITE ? (
<Trans>rewrite_edit</Trans>
) : (
<Trans>rewrite_add</Trans>
)}
</h4>
<button type="button" className="close" onClick={() => toggleRewritesModal()}>
<span className="sr-only">Close</span>
</button>
</div>
<Form
initialValues={{ ...currentRewrite }}
onSubmit={handleSubmit}
toggleRewritesModal={toggleRewritesModal}
processingAdd={processingAdd}
@@ -47,6 +55,8 @@ Modal.propTypes = {
toggleRewritesModal: PropTypes.func.isRequired,
processingAdd: PropTypes.bool.isRequired,
processingDelete: PropTypes.bool.isRequired,
modalType: PropTypes.string.isRequired,
currentRewrite: PropTypes.object,
};
export default withTranslation()(Modal);

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { withTranslation } from 'react-i18next';
import { sortIp } from '../../../helpers/helpers';
import { MODAL_TYPE } from '../../../helpers/constants';
class Table extends Component {
cellWrap = ({ value }) => (
@@ -31,24 +32,44 @@ class Table extends Component {
maxWidth: 100,
sortable: false,
resizable: false,
Cell: (value) => (
<div className="logs__row logs__row--center">
<button
type="button"
className="btn btn-icon btn-icon--green btn-outline-secondary btn-sm"
onClick={() => this.props.handleDelete({
answer: value.row.answer,
domain: value.row.domain,
})
}
title={this.props.t('delete_table_action')}
>
<svg className="icons">
<use xlinkHref="#delete" />
</svg>
</button>
</div>
),
Cell: (value) => {
const currentRewrite = {
answer: value.row.answer,
domain: value.row.domain,
};
return (
<div className="logs__row logs__row--center">
<button
type="button"
className="btn btn-icon btn-outline-primary btn-sm mr-2"
onClick={() => {
this.props.toggleRewritesModal({
type: MODAL_TYPE.EDIT_REWRITE,
currentRewrite,
});
}}
disabled={this.props.processingUpdate}
title={this.props.t('edit_table_action')}
>
<svg className="icons icon12">
<use xlinkHref="#edit" />
</svg>
</button>
<button
type="button"
className="btn btn-icon btn-outline-secondary btn-sm"
onClick={() => this.props.handleDelete(currentRewrite)}
title={this.props.t('delete_table_action')}
>
<svg className="icons">
<use xlinkHref="#delete" />
</svg>
</button>
</div>
);
},
},
];
@@ -84,7 +105,9 @@ Table.propTypes = {
processing: PropTypes.bool.isRequired,
processingAdd: PropTypes.bool.isRequired,
processingDelete: PropTypes.bool.isRequired,
processingUpdate: PropTypes.bool.isRequired,
handleDelete: PropTypes.func.isRequired,
toggleRewritesModal: PropTypes.func.isRequired,
};
export default withTranslation()(Table);

View File

@@ -6,16 +6,13 @@ import Table from './Table';
import Modal from './Modal';
import Card from '../../ui/Card';
import PageTitle from '../../ui/PageTitle';
import { MODAL_TYPE } from '../../../helpers/constants';
class Rewrites extends Component {
componentDidMount() {
this.props.getRewritesList();
}
handleSubmit = (values) => {
this.props.addRewrite(values);
};
handleDelete = (values) => {
// eslint-disable-next-line no-alert
if (window.confirm(this.props.t('rewrite_confirm_delete', { key: values.domain }))) {
@@ -23,6 +20,19 @@ class Rewrites extends Component {
}
};
handleSubmit = (values) => {
const { modalType, currentRewrite } = this.props.rewrites;
if (modalType === MODAL_TYPE.EDIT_REWRITE && currentRewrite) {
this.props.updateRewrite({
target: currentRewrite,
update: values,
});
} else {
this.props.addRewrite(values);
}
};
render() {
const {
t,
@@ -36,6 +46,9 @@ class Rewrites extends Component {
processing,
processingAdd,
processingDelete,
processingUpdate,
modalType,
currentRewrite,
} = rewrites;
return (
@@ -54,13 +67,15 @@ class Rewrites extends Component {
processing={processing}
processingAdd={processingAdd}
processingDelete={processingDelete}
processingUpdate={processingUpdate}
handleDelete={this.handleDelete}
toggleRewritesModal={toggleRewritesModal}
/>
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => toggleRewritesModal()}
onClick={() => toggleRewritesModal({ type: MODAL_TYPE.ADD_REWRITE })}
disabled={processingAdd}
>
<Trans>rewrite_add</Trans>
@@ -68,10 +83,13 @@ class Rewrites extends Component {
<Modal
isModalOpen={isModalOpen}
modalType={modalType}
toggleRewritesModal={toggleRewritesModal}
handleSubmit={this.handleSubmit}
processingAdd={processingAdd}
processingDelete={processingDelete}
processingUpdate={processingUpdate}
currentRewrite={currentRewrite}
/>
</Fragment>
</Card>
@@ -86,6 +104,7 @@ Rewrites.propTypes = {
toggleRewritesModal: PropTypes.func.isRequired,
addRewrite: PropTypes.func.isRequired,
deleteRewrite: PropTypes.func.isRequired,
updateRewrite: PropTypes.func.isRequired,
rewrites: PropTypes.object.isRequired,
};

View File

@@ -48,6 +48,7 @@ class Table extends Component {
Header: <Trans>list_url_table_header</Trans>,
accessor: 'url',
minWidth: 180,
// eslint-disable-next-line react/prop-types
Cell: ({ value }) => (
<div className="logs__row">
{isValidAbsolutePath(value) ? value

View File

@@ -32,6 +32,8 @@ const ProtectionTimer = ({
};
ProtectionTimer.propTypes = {
protectionDisabledDuration: PropTypes.number,
toggleProtectionSuccess: PropTypes.func.isRequired,
setProtectionTimerTime: PropTypes.func.isRequired,
};

View File

@@ -27,7 +27,6 @@ import {
} from '../../../helpers/constants';
import '../FormButton.css';
const getIntervalTitle = (interval, t) => {
switch (interval) {
case RETENTION_CUSTOM:

View File

@@ -7,7 +7,6 @@ import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { connect } from 'react-redux';
import {
renderRadioField,
toNumber,

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/no-unknown-property */
import React from 'react';
import './Icons.css';

View File

@@ -3,6 +3,7 @@ import {
getRewritesList,
addRewrite,
deleteRewrite,
updateRewrite,
toggleRewritesModal,
} from '../actions/rewrites';
import Rewrites from '../components/Filters/Rewrites';
@@ -17,6 +18,7 @@ const mapDispatchToProps = {
getRewritesList,
addRewrite,
deleteRewrite,
updateRewrite,
toggleRewritesModal,
};

View File

@@ -173,6 +173,8 @@ export const MODAL_TYPE = {
ADD_FILTERS: 'ADD_FILTERS',
EDIT_FILTERS: 'EDIT_FILTERS',
CHOOSE_FILTERING_LIST: 'CHOOSE_FILTERING_LIST',
ADD_REWRITE: 'ADD_REWRITE',
EDIT_REWRITE: 'EDIT_REWRITE',
};
export const CLIENT_ID = {

View File

@@ -845,7 +845,6 @@ export const sortIp = (a, b) => {
}
};
/**
* @param {number} filterId
* @returns {string}

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,27 @@ const rewrites = handleActions(
[actions.deleteRewriteFailure]: (state) => ({ ...state, processingDelete: false }),
[actions.deleteRewriteSuccess]: (state) => ({ ...state, processingDelete: false }),
[actions.toggleRewritesModal]: (state) => {
[actions.updateRewriteRequest]: (state) => ({ ...state, processingUpdate: true }),
[actions.updateRewriteFailure]: (state) => ({ ...state, processingUpdate: false }),
[actions.updateRewriteSuccess]: (state) => {
const newState = {
...state,
processingUpdate: false,
};
return newState;
},
[actions.toggleRewritesModal]: (state, { payload }) => {
if (payload) {
const newState = {
...state,
modalType: payload.type || '',
isModalOpen: !state.isModalOpen,
currentRewrite: payload.currentRewrite,
};
return newState;
}
const newState = {
...state,
isModalOpen: !state.isModalOpen,
@@ -42,7 +62,10 @@ const rewrites = handleActions(
processing: true,
processingAdd: false,
processingDelete: false,
processingUpdate: false,
isModalOpen: false,
modalType: '',
currentRewrite: {},
list: [],
},
);

View File

@@ -82,6 +82,5 @@ CMD [ \
"/opt/adguardhome/AdGuardHome", \
"--no-check-update", \
"-c", "/opt/adguardhome/conf/AdGuardHome.yaml", \
"-h", "0.0.0.0", \
"-w", "/opt/adguardhome/work" \
]

19
go.mod
View File

@@ -3,7 +3,8 @@ module github.com/AdguardTeam/AdGuardHome
go 1.19
require (
github.com/AdguardTeam/dnsproxy v0.50.2
// TODO(a.garipov): Update to a tagged version when it's released.
github.com/AdguardTeam/dnsproxy v0.50.3-0.20230628054307-31e374065768
github.com/AdguardTeam/golibs v0.13.3
github.com/AdguardTeam/urlfilter v0.16.1
github.com/NYTimes/gziphandler v1.1.1
@@ -17,7 +18,7 @@ require (
github.com/google/gopacket v1.1.19
github.com/google/renameio v1.0.1
github.com/google/uuid v1.3.0
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb
github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
github.com/kardianos/service v1.2.2
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
@@ -26,13 +27,13 @@ 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.54
github.com/miekg/dns v1.1.55
github.com/quic-go/quic-go v0.35.1
github.com/stretchr/testify v1.8.4
github.com/ti-mo/netfilter v0.5.0
go.etcd.io/bbolt v1.3.7
golang.org/x/crypto v0.10.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df
golang.org/x/net v0.11.0
golang.org/x/sys v0.9.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
@@ -50,17 +51,17 @@ require (
github.com/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/onsi/ginkgo/v2 v2.10.0 // indirect
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pierrec/lz4/v4 v4.1.18 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/tools v0.9.3 // indirect
golang.org/x/tools v0.10.0 // indirect
)

38
go.sum
View File

@@ -1,5 +1,5 @@
github.com/AdguardTeam/dnsproxy v0.50.2 h1:p1471SsMZ6SMo7T51Olw4aNluahvMwSLMorwxYV18ts=
github.com/AdguardTeam/dnsproxy v0.50.2/go.mod h1:CQhZTkqC8X0ID6glrtyaxgqRRdiYfn1gJulC1cZ5Dn8=
github.com/AdguardTeam/dnsproxy v0.50.3-0.20230628054307-31e374065768 h1:5Ia6wA+tqAlTyzuaOVGSlHmb0osLWXeJUs3NxCuC4gA=
github.com/AdguardTeam/dnsproxy v0.50.3-0.20230628054307-31e374065768/go.mod h1:CQhZTkqC8X0ID6glrtyaxgqRRdiYfn1gJulC1cZ5Dn8=
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
github.com/AdguardTeam/golibs v0.13.3 h1:RT3QbzThtaLiFLkIUDS6/hlGEXrh0zYvdf4bd7UWpGo=
@@ -58,8 +58,8 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb h1:6fDKEAXwe3rsfS4khW3EZ8kEqmSiV9szhMPcDrD+Y7Q=
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df h1:pF1MMIzEJzJ/MyI4bXYXVYyN8CJgoQ2PPKT2z3O/Cl4=
github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -85,18 +85,18 @@ github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -136,13 +136,13 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-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=
@@ -156,8 +156,8 @@ golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -191,8 +191,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -25,11 +25,8 @@ func (s *bitSet) isSet(n uint64) (ok bool) {
var word uint64
word, ok = s.words[wordIdx]
if !ok {
return false
}
return word&(1<<bitIdx) != 0
return ok && word&(1<<bitIdx) != 0
}
// set sets or unsets a bit.

View File

@@ -249,31 +249,30 @@ func (c *dhcpConn) buildEtherPkt(payload []byte, peer *dhcpUnicastAddr) (pkt []b
func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DHCPv4) {
switch giaddr, ciaddr, mtype := req.GatewayIPAddr, req.ClientIPAddr, resp.MessageType(); {
case giaddr != nil && !giaddr.IsUnspecified():
// Send any return messages to the server port on the BOOTP
// relay agent whose address appears in giaddr.
// Send any return messages to the server port on the BOOTP relay agent
// whose address appears in giaddr.
peer = &net.UDPAddr{
IP: giaddr,
Port: dhcpv4.ServerPort,
}
if mtype == dhcpv4.MessageTypeNak {
// Set the broadcast bit in the DHCPNAK, so that the relay agent
// broadcasts it to the client, because the client may not have
// a correct network address or subnet mask, and the client may not
// be answering ARP requests.
// broadcasts it to the client, because the client may not have a
// correct network address or subnet mask, and the client may not be
// answering ARP requests.
resp.SetBroadcast()
}
case mtype == dhcpv4.MessageTypeNak:
// Broadcast any DHCPNAK messages to 0xffffffff.
case ciaddr != nil && !ciaddr.IsUnspecified():
// Unicast DHCPOFFER and DHCPACK messages to the address in
// ciaddr.
// Unicast DHCPOFFER and DHCPACK messages to the address in ciaddr.
peer = &net.UDPAddr{
IP: ciaddr,
Port: dhcpv4.ClientPort,
}
case !req.IsBroadcast() && req.ClientHWAddr != nil:
// Unicast DHCPOFFER and DHCPACK messages to the client's
// hardware address and yiaddr.
// Unicast DHCPOFFER and DHCPACK messages to the client's hardware
// address and yiaddr.
peer = &dhcpUnicastAddr{
Addr: raw.Addr{HardwareAddr: req.ClientHWAddr},
yiaddr: resp.YourIPAddr,

View File

@@ -247,31 +247,30 @@ func (c *dhcpConn) buildEtherPkt(payload []byte, peer *dhcpUnicastAddr) (pkt []b
func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DHCPv4) {
switch giaddr, ciaddr, mtype := req.GatewayIPAddr, req.ClientIPAddr, resp.MessageType(); {
case giaddr != nil && !giaddr.IsUnspecified():
// Send any return messages to the server port on the BOOTP
// relay agent whose address appears in giaddr.
// Send any return messages to the server port on the BOOTP relay agent
// whose address appears in giaddr.
peer = &net.UDPAddr{
IP: giaddr,
Port: dhcpv4.ServerPort,
}
if mtype == dhcpv4.MessageTypeNak {
// Set the broadcast bit in the DHCPNAK, so that the relay agent
// broadcasts it to the client, because the client may not have
// a correct network address or subnet mask, and the client may not
// be answering ARP requests.
// broadcasts it to the client, because the client may not have a
// correct network address or subnet mask, and the client may not be
// answering ARP requests.
resp.SetBroadcast()
}
case mtype == dhcpv4.MessageTypeNak:
// Broadcast any DHCPNAK messages to 0xffffffff.
case ciaddr != nil && !ciaddr.IsUnspecified():
// Unicast DHCPOFFER and DHCPACK messages to the address in
// ciaddr.
// Unicast DHCPOFFER and DHCPACK messages to the address in ciaddr.
peer = &net.UDPAddr{
IP: ciaddr,
Port: dhcpv4.ClientPort,
}
case !req.IsBroadcast() && req.ClientHWAddr != nil:
// Unicast DHCPOFFER and DHCPACK messages to the client's
// hardware address and yiaddr.
// Unicast DHCPOFFER and DHCPACK messages to the client's hardware
// address and yiaddr.
peer = &dhcpUnicastAddr{
Addr: packet.Addr{HardwareAddr: req.ClientHWAddr},
yiaddr: resp.YourIPAddr,

View File

@@ -28,8 +28,9 @@ const (
defaultBackoff time.Duration = 500 * time.Millisecond
)
// Lease contains the necessary information about a DHCP lease. It's used in
// various places. So don't change it without good reason.
// Lease contains the necessary information about a DHCP lease. It's used as is
// in the database, so don't change it until it's absolutely necessary, see
// [dataVersion].
type Lease struct {
// Expiry is the expiration time of the lease.
Expiry time.Time `json:"expires"`
@@ -41,8 +42,6 @@ type Lease struct {
HWAddr net.HardwareAddr `json:"mac"`
// IP is the IP address leased to the client.
//
// TODO(a.garipov): Migrate leases.db.
IP netip.Addr `json:"ip"`
// IsStatic defines if the lease is static.

View File

@@ -200,7 +200,7 @@ func createICMPv6RAPacket(params icmpv6RA) (data []byte, err error) {
func (ra *raCtx) Init() (err error) {
ra.stop.Store(0)
ra.conn = nil
if !(ra.raAllowSLAAC || ra.raSLAACOnly) {
if !ra.raAllowSLAAC && !ra.raSLAACOnly {
return nil
}

View File

@@ -0,0 +1,86 @@
package dhcpsvc
import (
"net/netip"
"time"
"github.com/google/gopacket/layers"
)
// Config is the configuration for the DHCP service.
type Config struct {
// Interfaces stores configurations of DHCP server specific for the network
// interface identified by its name.
Interfaces map[string]*InterfaceConfig
// LocalDomainName is the top-level domain name to use for resolving DHCP
// clients' hostnames.
LocalDomainName string
// ICMPTimeout is the timeout for checking another DHCP server's presence.
ICMPTimeout time.Duration
// Enabled is the state of the service, whether it is enabled or not.
Enabled bool
}
// InterfaceConfig is the configuration of a single DHCP interface.
type InterfaceConfig struct {
// IPv4 is the configuration of DHCP protocol for IPv4.
IPv4 *IPv4Config
// IPv6 is the configuration of DHCP protocol for IPv6.
IPv6 *IPv6Config
}
// IPv4Config is the interface-specific configuration for DHCPv4.
type IPv4Config struct {
// GatewayIP is the IPv4 address of the network's gateway. It is used as
// the default gateway for DHCP clients and also used in calculating the
// network-specific broadcast address.
GatewayIP netip.Addr
// SubnetMask is the IPv4 subnet mask of the network. It should be a valid
// IPv4 subnet mask (i.e. all 1s followed by all 0s).
SubnetMask netip.Addr
// RangeStart is the first address in the range to assign to DHCP clients.
RangeStart netip.Addr
// RangeEnd is the last address in the range to assign to DHCP clients.
RangeEnd netip.Addr
// Options is the list of DHCP options to send to DHCP clients.
Options layers.DHCPOptions
// LeaseDuration is the TTL of a DHCP lease.
LeaseDuration time.Duration
// Enabled is the state of the DHCPv4 service, whether it is enabled or not
// on the specific interface.
Enabled bool
}
// IPv6Config is the interface-specific configuration for DHCPv6.
type IPv6Config struct {
// RangeStart is the first address in the range to assign to DHCP clients.
RangeStart netip.Addr
// Options is the list of DHCP options to send to DHCP clients.
Options layers.DHCPOptions
// LeaseDuration is the TTL of a DHCP lease.
LeaseDuration time.Duration
// RASlaacOnly defines whether the DHCP clients should only use SLAAC for
// address assignment.
RASLAACOnly bool
// RAAllowSlaac defines whether the DHCP clients may use SLAAC for address
// assignment.
RAAllowSLAAC bool
// Enabled is the state of the DHCPv6 service, whether it is enabled or not
// on the specific interface.
Enabled bool
}

120
internal/dhcpsvc/dhcpsvc.go Normal file
View File

@@ -0,0 +1,120 @@
// Package dhcpsvc contains the AdGuard Home DHCP service.
//
// TODO(e.burkov): Add tests.
package dhcpsvc
import (
"context"
"net"
"net/netip"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
)
// Lease is a DHCP lease.
//
// TODO(e.burkov): Consider it to [agh], since it also may be needed in
// [websvc]. Also think of implementing iterating methods with appropriate
// signatures.
type Lease struct {
// IP is the IP address leased to the client.
IP netip.Addr
// Expiry is the expiration time of the lease.
Expiry time.Time
// Hostname of the client.
Hostname string
// HWAddr is the physical hardware address (MAC address).
HWAddr net.HardwareAddr
// IsStatic defines if the lease is static.
IsStatic bool
}
type Interface interface {
agh.ServiceWithConfig[*Config]
// Enabled returns true if DHCP provides information about clients.
Enabled() (ok bool)
// HostByIP returns the hostname of the DHCP client with the given IP
// address. The address will be netip.Addr{} if there is no such client,
// due to an assumption that a DHCP client must always have an IP address.
HostByIP(ip netip.Addr) (host string)
// MACByIP returns the MAC address for the given IP address leased. It
// returns nil if there is no such client, due to an assumption that a DHCP
// client must always have a MAC address.
MACByIP(ip netip.Addr) (mac net.HardwareAddr)
// IPByHost returns the IP address of the DHCP client with the given
// hostname. The hostname will be an empty string if there is no such
// client, due to an assumption that a DHCP client must always have a
// hostname, either set by the client or assigned automatically.
IPByHost(host string) (ip netip.Addr)
// Leases returns all the DHCP leases.
Leases() (leases []*Lease)
// AddLease adds a new DHCP lease. It returns an error if the lease is
// invalid or already exists.
AddLease(l *Lease) (err error)
// EditLease changes an existing DHCP lease. It returns an error if there
// is no lease equal to old or if new is invalid or already exists.
EditLease(old, new *Lease) (err error)
// RemoveLease removes an existing DHCP lease. It returns an error if there
// is no lease equal to l.
RemoveLease(l *Lease) (err error)
// Reset removes all the DHCP leases.
Reset() (err error)
}
// Empty is an [Interface] implementation that does nothing.
type Empty struct{}
// type check
var _ Interface = Empty{}
// Start implements the [Service] interface for Empty.
func (Empty) Start() (err error) { return nil }
// Shutdown implements the [Service] interface for Empty.
func (Empty) Shutdown(_ context.Context) (err error) { return nil }
var _ agh.ServiceWithConfig[*Config] = Empty{}
// Config implements the [ServiceWithConfig] interface for Empty.
func (Empty) Config() (conf *Config) { return nil }
// Enabled implements the [Interface] interface for Empty.
func (Empty) Enabled() (ok bool) { return false }
// HostByIP implements the [Interface] interface for Empty.
func (Empty) HostByIP(_ netip.Addr) (host string) { return "" }
// MACByIP implements the [Interface] interface for Empty.
func (Empty) MACByIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
// IPByHost implements the [Interface] interface for Empty.
func (Empty) IPByHost(_ string) (ip netip.Addr) { return netip.Addr{} }
// Leases implements the [Interface] interface for Empty.
func (Empty) Leases() (leases []*Lease) { return nil }
// AddLease implements the [Interface] interface for Empty.
func (Empty) AddLease(_ *Lease) (err error) { return nil }
// EditLease implements the [Interface] interface for Empty.
func (Empty) EditLease(_, _ *Lease) (err error) { return nil }
// RemoveLease implements the [Interface] interface for Empty.
func (Empty) RemoveLease(_ *Lease) (err error) { return nil }
// Reset implements the [Interface] interface for Empty.
func (Empty) Reset() (err error) { return nil }

View File

@@ -145,10 +145,13 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, pctx *proxy.DNSContext) error
// processRecursion checks the incoming request and halts its handling by
// answering NXDOMAIN if s has tried to resolve it recently.
func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing recursion")
defer log.Debug("dnsforward: finished processing recursion")
pctx := dctx.proxyCtx
if msg := pctx.Req; msg != nil && s.recDetector.check(*msg) {
log.Debug("recursion detected resolving %q", msg.Question[0].Name)
log.Debug("dnsforward: recursion detected resolving %q", msg.Question[0].Name)
pctx.Res = s.genNXDomain(pctx.Req)
return resultCodeFinish
@@ -158,10 +161,13 @@ func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
}
// processInitial terminates the following processing for some requests if
// needed and enriches the ctx with some client-specific information.
// needed and enriches dctx with some client-specific information.
//
// TODO(e.burkov): Decompose into less general processors.
func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing initial")
defer log.Debug("dnsforward: finished processing initial")
pctx := dctx.proxyCtx
q := pctx.Req.Question[0]
qt := q.Qtype
@@ -282,6 +288,9 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
//
// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-10.html.
func (s *Server) processDDRQuery(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing ddr")
defer log.Debug("dnsforward: finished processing ddr")
if !s.conf.HandleDDR {
return resultCodeSuccess
}
@@ -375,6 +384,9 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
// processDetermineLocal determines if the client's IP address is from locally
// served network and saves the result into the context.
func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing local detection")
defer log.Debug("dnsforward: finished processing local detection")
rc = resultCodeSuccess
var ip net.IP
@@ -405,6 +417,9 @@ func (s *Server) dhcpHostToIP(host string) (ip netip.Addr, ok bool) {
//
// TODO(a.garipov): Adapt to AAAA as well.
func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing dhcp hosts")
defer log.Debug("dnsforward: finished processing dhcp hosts")
pctx := dctx.proxyCtx
req := pctx.Req
q := req.Question[0]
@@ -544,6 +559,9 @@ func extractARPASubnet(domain string) (pref netip.Prefix, err error) {
// processRestrictLocal responds with NXDOMAIN to PTR requests for IP addresses
// in locally served network from external clients.
func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing local restriction")
defer log.Debug("dnsforward: finished processing local restriction")
pctx := dctx.proxyCtx
req := pctx.Req
q := req.Question[0]
@@ -613,6 +631,9 @@ func (s *Server) ipToDHCPHost(ip netip.Addr) (host string, ok bool) {
// processDHCPAddrs responds to PTR requests if the target IP is leased by the
// DHCP server.
func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing dhcp addrs")
defer log.Debug("dnsforward: finished processing dhcp addrs")
pctx := dctx.proxyCtx
if pctx.Res != nil {
return resultCodeSuccess
@@ -658,6 +679,9 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
// processLocalPTR responds to PTR requests if the target IP is detected to be
// inside the local network and the query was not answered from DHCP.
func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing local ptr")
defer log.Debug("dnsforward: finished processing local ptr")
pctx := dctx.proxyCtx
if pctx.Res != nil {
return resultCodeSuccess
@@ -692,6 +716,9 @@ func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
// Apply filtering logic
func (s *Server) processFilteringBeforeRequest(ctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing filtering before req")
defer log.Debug("dnsforward: finished processing filtering before req")
if ctx.proxyCtx.Res != nil {
// Go on since the response is already set.
return resultCodeSuccess
@@ -725,6 +752,9 @@ func ipStringFromAddr(addr net.Addr) (ipStr string) {
// processUpstream passes request to upstream servers and handles the response.
func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing upstream")
defer log.Debug("dnsforward: finished processing upstream")
pctx := dctx.proxyCtx
req := pctx.Req
q := req.Question[0]
@@ -871,6 +901,9 @@ func (s *Server) setCustomUpstream(pctx *proxy.DNSContext, clientID string) {
// Apply filtering logic after we have received response from upstream servers
func (s *Server) processFilteringAfterResponse(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing filtering after resp")
defer log.Debug("dnsforward: finished processing filtering after resp")
pctx := dctx.proxyCtx
switch res := dctx.result; res.Reason {
case filtering.NotFilteredAllowList:

View File

@@ -48,12 +48,33 @@ var webRegistered bool
// hostToIPTable is a convenient type alias for tables of host names to an IP
// address.
//
// TODO(e.burkov): Use the [DHCP] interface instead.
type hostToIPTable = map[string]netip.Addr
// ipToHostTable is a convenient type alias for tables of IP addresses to their
// host names. For example, for use with PTR queries.
//
// TODO(e.burkov): Use the [DHCP] interface instead.
type ipToHostTable = map[netip.Addr]string
// DHCP is an interface for accessing DHCP lease data needed in this package.
type DHCP interface {
// HostByIP returns the hostname of the DHCP client with the given IP
// address. The address will be netip.Addr{} if there is no such client,
// due to an assumption that a DHCP client must always have an IP address.
HostByIP(ip netip.Addr) (host string)
// IPByHost returns the IP address of the DHCP client with the given
// hostname. The hostname will be an empty string if there is no such
// client, due to an assumption that a DHCP client must always have a
// hostname, either set by the client or assigned automatically.
IPByHost(host string) (ip netip.Addr)
// Enabled returns true if DHCP provides information about clients.
Enabled() (ok bool)
}
// Server is the main way to start a DNS server.
//
// Example:
@@ -215,7 +236,7 @@ func (s *Server) Close() {
s.dnsProxy = nil
if err := s.ipset.close(); err != nil {
log.Error("closing ipset: %s", err)
log.Error("dnsforward: closing ipset: %s", err)
}
}
@@ -443,7 +464,7 @@ func (s *Server) setupResolvers(localAddrs []string) (err error) {
return err
}
log.Debug("upstreams to resolve PTR for local addresses: %v", localAddrs)
log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", localAddrs)
var upsConfig *proxy.UpstreamConfig
upsConfig, err = proxy.ParseUpstreamsConfig(
@@ -656,7 +677,9 @@ func (s *Server) Reconfigure(conf *ServerConfig) error {
s.serverLock.Lock()
defer s.serverLock.Unlock()
log.Print("Start reconfiguring the server")
log.Info("dnsforward: starting reconfiguring server")
defer log.Info("dnsforward: finished reconfiguring server")
err := s.stopLocked()
if err != nil {
return fmt.Errorf("could not reconfigure the server: %w", err)
@@ -708,13 +731,13 @@ func (s *Server) IsBlockedClient(ip netip.Addr, clientID string) (blocked bool,
// Allow if at least one of the checks allows in allowlist mode, but block
// if at least one of the checks blocks in blocklist mode.
if allowlistMode && blockedByIP && blockedByClientID {
log.Debug("client %v (id %q) is not in access allowlist", ip, clientID)
log.Debug("dnsforward: client %v (id %q) is not in access allowlist", ip, clientID)
// Return now without substituting the empty rule for the
// clientID because the rule can't be empty here.
return true, rule
} else if !allowlistMode && (blockedByIP || blockedByClientID) {
log.Debug("client %v (id %q) is in access blocklist", ip, clientID)
log.Debug("dnsforward: client %v (id %q) is in access blocklist", ip, clientID)
blocked = true
}

View File

@@ -110,6 +110,9 @@ func ipsFromAnswer(ans []dns.RR) (ip4s, ip6s []net.IP) {
// process adds the resolved IP addresses to the domain's ipsets, if any.
func (c *ipsetCtx) process(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: ipset: started processing")
defer log.Debug("dnsforward: ipset: finished processing")
if c.skipIpsetProcessing(dctx) {
return resultCodeSuccess
}
@@ -125,12 +128,12 @@ func (c *ipsetCtx) process(dctx *dnsContext) (rc resultCode) {
n, err := c.ipsetMgr.Add(host, ip4s, ip6s)
if err != nil {
// Consider ipset errors non-critical to the request.
log.Error("ipset: adding host ips: %s", err)
log.Error("dnsforward: ipset: adding host ips: %s", err)
return resultCodeSuccess
}
log.Debug("ipset: added %d new ipset entries", n)
log.Debug("dnsforward: ipset: added %d new ipset entries", n)
return resultCodeSuccess
}

View File

@@ -17,60 +17,78 @@ import (
// Write Stats data and logs
func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing querylog and stats")
defer log.Debug("dnsforward: finished processing querylog and stats")
elapsed := time.Since(dctx.startTime)
pctx := dctx.proxyCtx
shouldLog := true
msg := pctx.Req
q := msg.Question[0]
q := pctx.Req.Question[0]
host := strings.ToLower(strings.TrimSuffix(q.Name, "."))
// don't log ANY request if refuseAny is enabled
if q.Qtype == dns.TypeANY && s.conf.RefuseAny {
shouldLog = false
}
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
ip = slices.Clone(ip)
s.serverLock.RLock()
defer s.serverLock.RUnlock()
s.anonymizer.Load()(ip)
log.Debug("client ip: %s", ip)
log.Debug("dnsforward: client ip for stats and querylog: %s", ip)
ipStr := ip.String()
ids := []string{ipStr, dctx.clientID}
qt, cl := q.Qtype, q.Qclass
// Synchronize access to s.queryLog and s.stats so they won't be suddenly
// uninitialized while in use. This can happen after proxy server has been
// stopped, but its workers haven't yet exited.
if shouldLog &&
s.queryLog != nil &&
// TODO(s.chzhen): Use dnsforward.dnsContext when it will start
// containing persistent client.
s.queryLog.ShouldLog(host, q.Qtype, q.Qclass, ids) {
s.serverLock.RLock()
defer s.serverLock.RUnlock()
if s.shouldLog(host, qt, cl, ids) {
s.logQuery(dctx, pctx, elapsed, ip)
} else {
log.Debug(
"dnsforward: request %s %s from %s ignored; not logging",
dns.Type(q.Qtype),
"dnsforward: request %s %s %q from %s ignored; not adding to querylog",
dns.Class(cl),
dns.Type(qt),
host,
ip,
)
}
if s.stats != nil &&
// TODO(s.chzhen): Use dnsforward.dnsContext when it will start
// containing persistent client.
s.stats.ShouldCount(host, q.Qtype, q.Qclass, ids) {
if s.shouldCountStat(host, qt, cl, ids) {
s.updateStats(dctx, elapsed, *dctx.result, ipStr)
} else {
log.Debug(
"dnsforward: request %s %s %q from %s ignored; not counting in stats",
dns.Class(cl),
dns.Type(qt),
host,
ip,
)
}
return resultCodeSuccess
}
// shouldLog returns true if the query with the given data should be logged in
// the query log. s.serverLock is expected to be locked.
func (s *Server) shouldLog(host string, qt, cl uint16, ids []string) (ok bool) {
if qt == dns.TypeANY && s.conf.RefuseAny {
return false
}
// TODO(s.chzhen): Use dnsforward.dnsContext when it will start containing
// persistent client.
return s.queryLog != nil && s.queryLog.ShouldLog(host, qt, cl, ids)
}
// shouldCountStat returns true if the query with the given data should be
// counted in the statistics. s.serverLock is expected to be locked.
func (s *Server) shouldCountStat(host string, qt, cl uint16, ids []string) (ok bool) {
// TODO(s.chzhen): Use dnsforward.dnsContext when it will start containing
// persistent client.
return s.stats != nil && s.stats.ShouldCount(host, qt, cl, ids)
}
// logQuery pushes the request details into the query log.
func (s *Server) logQuery(
dctx *dnsContext,

View File

@@ -2,6 +2,7 @@ package filtering
import (
"encoding/json"
"fmt"
"net/http"
"time"
@@ -55,11 +56,29 @@ type BlockedServices struct {
IDs []string `yaml:"ids"`
}
// BlockedSvcKnown returns true if a blocked service ID is known.
func BlockedSvcKnown(s string) (ok bool) {
_, ok = serviceRules[s]
// Clone returns a deep copy of blocked services.
func (s *BlockedServices) Clone() (c *BlockedServices) {
if s == nil {
return nil
}
return ok
return &BlockedServices{
Schedule: s.Schedule.Clone(),
IDs: slices.Clone(s.IDs),
}
}
// Validate returns an error if blocked services contain unknown service ID. s
// must not be nil.
func (s *BlockedServices) Validate() (err error) {
for _, id := range s.IDs {
_, ok := serviceRules[id]
if !ok {
return fmt.Errorf("unknown blocked-service %q", id)
}
}
return nil
}
// ApplyBlockedServices - set blocked services settings for this DNS request

View File

@@ -988,17 +988,11 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
}
if d.BlockedServices != nil {
bsvcs := []string{}
for _, s := range d.BlockedServices.IDs {
if !BlockedSvcKnown(s) {
log.Debug("skipping unknown blocked-service %q", s)
err = d.BlockedServices.Validate()
continue
}
bsvcs = append(bsvcs, s)
if err != nil {
return nil, fmt.Errorf("filtering: %w", err)
}
d.BlockedServices.IDs = bsvcs
}
if blockFilters != nil {

View File

@@ -169,7 +169,7 @@ func (d *DNSFilter) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ
deleted = (*filters)[delIdx]
p := deleted.Path(d.DataDir)
err = os.Rename(p, p+".old")
if err != nil {
if err != nil && !errors.Is(err, os.ErrNotExist) {
log.Error("deleting filter %d: renaming file %q: %s", deleted.ID, p, err)
return

View File

@@ -234,6 +234,16 @@ var blockedServices = []blockedService{{
"||z.cn^",
"||zappos^",
},
}, {
ID: "battle_net",
Name: "Battle.net",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M43.11 22.15s3.95.2 3.95-2.12c0-3.03-5.26-5.77-5.26-5.77s.83-1.74 1.34-2.72a37.3 37.3 0 0 0 2.09-5.65c.16-1.1-.09-1.44-.09-1.44-.35 2.34-4.17 9.09-4.47 9.32-3.72-1.75-8.83-2.23-8.83-2.23S26.84 1 22.13 1c-4.67 0-4.65 9.02-4.65 9.02s-1.32-2.56-2.97-2.56c-2.42 0-3.22 3.67-3.22 7.64a37.8 37.8 0 0 0-9.16 1.17c-.36.1-1.49.92-.97.82 1.04-.34 5.95-1.1 10.25-.72.24 3.77 2.44 8.68 2.44 8.68S9.13 31.9 9.13 36.78c0 1.29.56 3.64 3.95 3.64 2.84 0 6.03-1.7 6.63-2.06a6.33 6.33 0 0 0-.91 2.83c0 .54.31 2.06 2.5 2.06 2.82 0 5.96-2.16 5.96-2.16s2.96 4.93 5.5 7.2c.69.6 1.34.71 1.34.71s-2.52-2.43-5.84-8.68c3.08-1.9 6.3-6.4 6.3-6.4l3.3.01c4.6 0 11.11-.96 11.11-4.61 0-3.77-5.86-7.17-5.86-7.17Zm.52-2.26c0 1.33-1.27 1.3-1.27 1.3l-.97.08s-1.82-.97-2.93-1.41c0 0 1.72-2.65 2.12-3.4.3.18 3.05 1.9 3.05 3.43ZM24.43 6.3c2.15 0 5.23 5.1 5.23 5.1s-4.8-.44-8.76 1.89c.1-3.67 1.34-7 3.52-7Zm-8.56 4.13c.69 0 1.36.83 1.64 1.54 0 .47.24 3.2.24 3.2l-3.96-.16c0-3.57 1.4-4.58 2.08-4.58Zm-.4 24.8c-2.17 0-2.62-1.2-2.62-2.29 0-2.45 1.96-5.9 1.96-5.9s2.2 4.63 6.04 6.59a10.02 10.02 0 0 1-5.39 1.6Zm7.02 4.85c-1.52 0-1.7-.98-1.7-1.21 0-.7.55-1.54.55-1.54s2.55-1.73 2.71-1.91l1.89 3.52s-1.93 1.14-3.45 1.14Zm4.74-1.92c-.93-1.62-1.6-3.3-1.6-3.3s3.78.24 5.82-1.86a11.2 11.2 0 0 1-5.65 1.07c4.93-4.34 7.8-7.48 10.23-10.74a9.46 9.46 0 0 0-1.6-1.15c-1.46 1.76-7.16 7.86-12.45 10.88-6.69-3.64-8.09-14.38-8.23-16.6l3.65.34s-1.37 2.44-1.37 4.23c0 1.79.21 1.89.21 1.89s-.04-3.13 1.89-5.54c1.46 7.82 3 11.83 4.19 14.22.6-.25 1.74-.76 1.74-.76s-3.38-9.73-3.19-16.31a13.8 13.8 0 0 1 6.36-1.66c6.73 0 12.14 2.9 12.14 2.9l-2.12 2.95s-1.89-3.42-4.55-4.03c1.4 1.05 2.98 2.44 3.8 4.43a68.4 68.4 0 0 0-14.47-3.59c-.19.8-.17 1.94-.17 1.94s9.03 1.66 15.6 5.43c-.05 8.21-9 14.53-10.23 15.26Zm8.55-6.14s2.8-3.68 2.76-8.55c0 0 4.52 2.8 4.52 5.54 0 3.05-7.28 3-7.28 3Z\"/></svg>"),
Rules: []string{
"||battle.net^",
"||battlenet.com.cn^",
"||bnet.163.com^",
"||bnet.cn^",
},
}, {
ID: "bilibili",
Name: "Bilibili",
@@ -1390,11 +1400,17 @@ var blockedServices = []blockedService{{
"||line-apps.com^",
"||line-cdn.net^",
"||line-scdn.net^",
"||line.biz^",
"||line.me^",
"||line.naver.jp^",
"||linecorp.com^",
"||linefriends.com.tw^",
"||linefriends.com^",
"||linegame.jp^",
"||linemobile.com^",
"||linemyshop.com^",
"||lineshoppingseller.com^",
"||linetv.tw^",
},
}, {
ID: "mail_ru",
@@ -1454,7 +1470,6 @@ var blockedServices = []blockedService{{
"||mastodon.social^",
"||mastodon.uno^",
"||mastodon.world^",
"||mastodon.xyz^",
"||mastodonapp.uk^",
"||mastodonners.nl^",
"||mastodont.cat^",
@@ -1470,7 +1485,7 @@ var blockedServices = []blockedService{{
"||mstdn.plus^",
"||mstdn.social^",
"||muenchen.social^",
"||newsie.social^",
"||nerdculture.de^",
"||noc.social^",
"||norden.social^",
"||nrw.social^",
@@ -1508,6 +1523,7 @@ var blockedServices = []blockedService{{
"||union.place^",
"||universeodon.com^",
"||urbanists.social^",
"||wien.rocks^",
"||wxw.moe^",
},
}, {
@@ -1704,6 +1720,14 @@ var blockedServices = []blockedService{{
"||robloxcdn.com^",
"||robloxdev.cn^",
},
}, {
ID: "rockstar_games",
Name: "Rockstar games",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M12 3c-4.96 0-9 4.04-9 9v26c0 4.96 4.04 9 9 9h26c4.96 0 9-4.04 9-9V12c0-4.96-4.04-9-9-9H12zm0 2h26c3.88 0 7 3.12 7 7v26c0 3.88-3.12 7-7 7H12c-3.88 0-7-3.12-7-7V12c0-3.88 3.12-7 7-7zm3.72 5a1 1 0 0 0-.97.79l-3.87 18a1 1 0 0 0 .98 1.21h4.27a1 1 0 0 0 .97-.79L18.47 23h2.07c.94 0 1.12.15 1.36.73.24.57.3 1.76.1 3.4-.08.68-.05 1.22.02 1.6v.03a1 1 0 0 0 .3.97l3.37 3.12-2.6 5.74a1 1 0 0 0 1.43 1.26l5.58-3.39 4.29 3.33a1 1 0 0 0 1.6-.98l-1.09-5.56 4.7-3.47a1 1 0 0 0-.6-1.8h-4.86l-.82-5.14a1 1 0 0 0-.98-.84 1 1 0 0 0-.88.51l-2.77 5a14.3 14.3 0 0 1 .06-2.83c.15-1.48.01-2.64-.18-3.45-.06-.28-.08-.25-.15-.45.3-.17.4-.13.77-.5.8-.8 1.6-2.18 1.75-4.26.17-2.26-.55-3.98-1.92-4.9C27.65 10.17 25.91 10 24 10h-8.28zm.81 2H24c1.75 0 3.13.25 3.9.77.76.52 1.18 1.27 1.05 3.1-.13 1.67-.69 2.51-1.17 3a2 2 0 0 1-.82.56 1 1 0 0 0-.6 1.44s.12.21.27.82c.14.6.26 1.53.13 2.79a14.24 14.24 0 0 0-.01 3.52h-2.76c-.01-.19-.04-.32 0-.62.22-1.78.25-3.21-.24-4.42A3.38 3.38 0 0 0 20.54 21h-2.87a1 1 0 0 0-.98.78L15.32 28H13.1l3.44-16zm2.76 1.03a1 1 0 0 0-.98.8l-.98 4.94a1 1 0 0 0 .98 1.2h4.47c.79 0 1.65-.12 2.44-.58a3.6 3.6 0 0 0 1.68-2.41 3.3 3.3 0 0 0-.72-2.92 3.35 3.35 0 0 0-2.47-1.03h-4.42zm.82 2h3.6c.41 0 .79.16 1 .4.22.22.36.52.23 1.15-.13.62-.36.88-.72 1.08a3 3 0 0 1-1.44.3h-3.25l.58-2.93zm11.7 10.99.49 3.11a1 1 0 0 0 .98.84h2.69l-2.76 2.05a1 1 0 0 0-.4 1l.7 3.56-2.73-2.12a1 1 0 0 0-1.13-.07l-3.4 2.07 1.56-3.44a1 1 0 0 0-.23-1.15L25.55 30H29a1 1 0 0 0 .88-.51l1.92-3.47z\"/></svg>"),
Rules: []string{
"||rockstargames.com^",
"||rsg.sc^",
},
}, {
ID: "shopee",
Name: "Shopee",

View File

@@ -23,12 +23,14 @@ type Client struct {
safeSearchConf filtering.SafeSearchConfig
SafeSearch filtering.SafeSearch
// BlockedServices is the configuration of blocked services of a client.
BlockedServices *filtering.BlockedServices
Name string
IDs []string
Tags []string
BlockedServices []string
Upstreams []string
IDs []string
Tags []string
Upstreams []string
UseOwnSettings bool
FilteringEnabled bool
@@ -44,9 +46,9 @@ type Client struct {
func (c *Client) ShallowClone() (sh *Client) {
clone := *c
clone.BlockedServices = c.BlockedServices.Clone()
clone.IDs = stringutil.CloneSlice(c.IDs)
clone.Tags = stringutil.CloneSlice(c.Tags)
clone.BlockedServices = stringutil.CloneSlice(c.BlockedServices)
clone.Upstreams = stringutil.CloneSlice(c.Upstreams)
return &clone

View File

@@ -11,6 +11,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
@@ -24,6 +25,23 @@ import (
"golang.org/x/exp/slices"
)
// DHCP is an interface for accessing DHCP lease data the [clientsContainer]
// needs.
type DHCP interface {
// Leases returns all the DHCP leases.
Leases() (leases []*dhcpsvc.Lease)
// HostByIP returns the hostname of the DHCP client with the given IP
// address. The address will be netip.Addr{} if there is no such client,
// due to an assumption that a DHCP client must always have an IP address.
HostByIP(ip netip.Addr) (host string)
// MACByIP returns the MAC address for the given IP address leased. It
// returns nil if there is no such client, due to an assumption that a DHCP
// client must always have a MAC address.
MACByIP(ip netip.Addr) (mac net.HardwareAddr)
}
// clientsContainer is the storage of all runtime and persistent clients.
type clientsContainer struct {
// TODO(a.garipov): Perhaps use a number of separate indices for different
@@ -78,7 +96,7 @@ func (clients *clientsContainer) Init(
etcHosts *aghnet.HostsContainer,
arpdb aghnet.ARPDB,
filteringConf *filtering.Config,
) {
) (err error) {
if clients.list != nil {
log.Fatal("clients.list != nil")
}
@@ -92,23 +110,29 @@ func (clients *clientsContainer) Init(
clients.dhcpServer = dhcpServer
clients.etcHosts = etcHosts
clients.arpdb = arpdb
clients.addFromConfig(objects, filteringConf)
err = clients.addFromConfig(objects, filteringConf)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
clients.safeSearchCacheSize = filteringConf.SafeSearchCacheSize
clients.safeSearchCacheTTL = time.Minute * time.Duration(filteringConf.CacheTime)
if clients.testing {
return
return nil
}
clients.updateFromDHCP(true)
if clients.dhcpServer != nil {
clients.dhcpServer.SetOnLeaseChanged(clients.onDHCPLeaseChanged)
clients.onDHCPLeaseChanged(dhcpd.LeaseChangedAdded)
}
if clients.etcHosts != nil {
go clients.handleHostsUpdates()
}
return nil
}
func (clients *clientsContainer) handleHostsUpdates() {
@@ -148,12 +172,14 @@ func (clients *clientsContainer) reloadARP() {
type clientObject struct {
SafeSearchConf filtering.SafeSearchConfig `yaml:"safe_search"`
// BlockedServices is the configuration of blocked services of a client.
BlockedServices *filtering.BlockedServices `yaml:"blocked_services"`
Name string `yaml:"name"`
Tags []string `yaml:"tags"`
IDs []string `yaml:"ids"`
BlockedServices []string `yaml:"blocked_services"`
Upstreams []string `yaml:"upstreams"`
IDs []string `yaml:"ids"`
Tags []string `yaml:"tags"`
Upstreams []string `yaml:"upstreams"`
UseGlobalSettings bool `yaml:"use_global_settings"`
FilteringEnabled bool `yaml:"filtering_enabled"`
@@ -167,7 +193,10 @@ type clientObject struct {
// addFromConfig initializes the clients container with objects from the
// configuration file.
func (clients *clientsContainer) addFromConfig(objects []*clientObject, filteringConf *filtering.Config) {
func (clients *clientsContainer) addFromConfig(
objects []*clientObject,
filteringConf *filtering.Config,
) (err error) {
for _, o := range objects {
cli := &Client{
Name: o.Name,
@@ -188,7 +217,7 @@ func (clients *clientsContainer) addFromConfig(objects []*clientObject, filterin
if o.SafeSearchConf.Enabled {
o.SafeSearchConf.CustomResolver = safeSearchResolver{}
err := cli.setSafeSearch(
err = cli.setSafeSearch(
o.SafeSearchConf,
filteringConf.SafeSearchCacheSize,
time.Minute*time.Duration(filteringConf.CacheTime),
@@ -200,14 +229,13 @@ func (clients *clientsContainer) addFromConfig(objects []*clientObject, filterin
}
}
for _, s := range o.BlockedServices {
if filtering.BlockedSvcKnown(s) {
cli.BlockedServices = append(cli.BlockedServices, s)
} else {
log.Info("clients: skipping unknown blocked service %q", s)
}
err = o.BlockedServices.Validate()
if err != nil {
return fmt.Errorf("clients: init client blocked services %q: %w", cli.Name, err)
}
cli.BlockedServices = o.BlockedServices.Clone()
for _, t := range o.Tags {
if clients.allTags.Has(t) {
cli.Tags = append(cli.Tags, t)
@@ -218,11 +246,13 @@ func (clients *clientsContainer) addFromConfig(objects []*clientObject, filterin
slices.Sort(cli.Tags)
_, err := clients.Add(cli)
_, err = clients.Add(cli)
if err != nil {
log.Error("clients: adding clients %s: %s", cli.Name, err)
}
}
return nil
}
// forConfig returns all currently known persistent clients as objects for the
@@ -236,10 +266,11 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
o := &clientObject{
Name: cli.Name,
Tags: stringutil.CloneSlice(cli.Tags),
IDs: stringutil.CloneSlice(cli.IDs),
BlockedServices: stringutil.CloneSlice(cli.BlockedServices),
Upstreams: stringutil.CloneSlice(cli.Upstreams),
BlockedServices: cli.BlockedServices.Clone(),
IDs: stringutil.CloneSlice(cli.IDs),
Tags: stringutil.CloneSlice(cli.Tags),
Upstreams: stringutil.CloneSlice(cli.Upstreams),
UseGlobalSettings: !cli.UseOwnSettings,
FilteringEnabled: cli.FilteringEnabled,
@@ -277,15 +308,38 @@ func (clients *clientsContainer) periodicUpdate() {
}
}
// onDHCPLeaseChanged is a callback for the DHCP server. It updates the list of
// runtime clients using the DHCP server's leases.
//
// TODO(e.burkov): Remove when switched to dhcpsvc.
func (clients *clientsContainer) onDHCPLeaseChanged(flags int) {
switch flags {
case dhcpd.LeaseChangedAdded,
dhcpd.LeaseChangedAddedStatic,
dhcpd.LeaseChangedRemovedStatic:
clients.updateFromDHCP(true)
case dhcpd.LeaseChangedRemovedAll:
clients.updateFromDHCP(false)
if clients.dhcpServer == nil || !config.Clients.Sources.DHCP {
return
}
clients.lock.Lock()
defer clients.lock.Unlock()
clients.rmHostsBySrc(ClientSourceDHCP)
if flags == dhcpd.LeaseChangedRemovedAll {
return
}
leases := clients.dhcpServer.Leases(dhcpd.LeasesAll)
n := 0
for _, l := range leases {
if l.Hostname == "" {
continue
}
ok := clients.addHostLocked(l.IP, l.Hostname, ClientSourceDHCP)
if ok {
n++
}
}
log.Debug("clients: added %d client aliases from dhcp", n)
}
// clientSource checks if client with this IP address already exists and returns
@@ -301,11 +355,11 @@ func (clients *clientsContainer) clientSource(ip netip.Addr) (src clientSource)
}
rc, ok := clients.ipToRC[ip]
if !ok {
return ClientSourceNone
if ok {
return rc.Source
}
return rc.Source
return ClientSourceNone
}
// findMultiple is a wrapper around Find to make it a valid client finder for
@@ -466,11 +520,11 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
}
}
if clients.dhcpServer == nil {
return nil, false
if clients.dhcpServer != nil {
return clients.findDHCP(ip)
}
return clients.findDHCP(ip)
return nil, false
}
// findDHCP searches for a client by its MAC, if the DHCP server is active and
@@ -697,28 +751,27 @@ func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) {
_, ok := clients.findLocked(ip.String())
if ok {
log.Debug("clients: client for %s is already created, ignore whois info", ip)
return
}
// TODO(e.burkov): Consider storing WHOIS information separately and
// potentially get rid of [RuntimeClient].
rc, ok := clients.ipToRC[ip]
if ok {
rc.WHOIS = wi
if !ok {
// Create a RuntimeClient implicitly so that we don't do this check
// again.
rc = &RuntimeClient{
Source: ClientSourceWHOIS,
}
clients.ipToRC[ip] = rc
log.Debug("clients: set whois info for runtime client with ip %s: %+v", ip, wi)
} else {
log.Debug("clients: set whois info for runtime client %s: %+v", rc.Host, wi)
return
}
// Create a RuntimeClient implicitly so that we don't do this check
// again.
rc = &RuntimeClient{
Source: ClientSourceWHOIS,
}
rc.WHOIS = wi
clients.ipToRC[ip] = rc
log.Debug("clients: set whois info for runtime client with ip %s: %+v", ip, wi)
}
// AddHost adds a new IP-hostname pairing. The priorities of the sources are
@@ -742,23 +795,19 @@ func (clients *clientsContainer) addHostLocked(
src clientSource,
) (ok bool) {
rc, ok := clients.ipToRC[ip]
if ok {
if rc.Source > src {
return false
}
rc.Host = host
rc.Source = src
} else {
if !ok {
rc = &RuntimeClient{
Host: host,
Source: src,
WHOIS: &whois.Info{},
WHOIS: &whois.Info{},
}
clients.ipToRC[ip] = rc
} else if src < rc.Source {
return false
}
rc.Host = host
rc.Source = src
log.Debug("clients: added %s -> %q [%d]", ip, host, len(clients.ipToRC))
return true
@@ -827,38 +876,6 @@ func (clients *clientsContainer) addFromSystemARP() {
log.Debug("clients: added %d client aliases from arp neighborhood", added)
}
// updateFromDHCP adds the clients that have a non-empty hostname from the DHCP
// server.
func (clients *clientsContainer) updateFromDHCP(add bool) {
if clients.dhcpServer == nil || !config.Clients.Sources.DHCP {
return
}
clients.lock.Lock()
defer clients.lock.Unlock()
clients.rmHostsBySrc(ClientSourceDHCP)
if !add {
return
}
leases := clients.dhcpServer.Leases(dhcpd.LeasesAll)
n := 0
for _, l := range leases {
if l.Hostname == "" {
continue
}
ok := clients.addHostLocked(l.IP, l.Hostname, ClientSourceDHCP)
if ok {
n++
}
}
log.Debug("clients: added %d client aliases from dhcp", n)
}
// close gracefully closes all the client-specific upstream configurations of
// the persistent clients.
func (clients *clientsContainer) close() (err error) {

View File

@@ -16,18 +16,19 @@ import (
// newClientsContainer is a helper that creates a new clients container for
// tests.
func newClientsContainer() (c *clientsContainer) {
func newClientsContainer(t *testing.T) (c *clientsContainer) {
c = &clientsContainer{
testing: true,
}
c.Init(nil, nil, nil, nil, &filtering.Config{})
err := c.Init(nil, nil, nil, nil, &filtering.Config{})
require.NoError(t, err)
return c
}
func TestClients(t *testing.T) {
clients := newClientsContainer()
clients := newClientsContainer(t)
t.Run("add_success", func(t *testing.T) {
var (
@@ -198,7 +199,7 @@ func TestClients(t *testing.T) {
}
func TestClientsWHOIS(t *testing.T) {
clients := newClientsContainer()
clients := newClientsContainer(t)
whois := &whois.Info{
Country: "AU",
Orgname: "Example Org",
@@ -244,7 +245,7 @@ func TestClientsWHOIS(t *testing.T) {
}
func TestClientsAddExisting(t *testing.T) {
clients := newClientsContainer()
clients := newClientsContainer(t)
t.Run("simple", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.1")
@@ -316,7 +317,7 @@ func TestClientsAddExisting(t *testing.T) {
}
func TestClientsCustomUpstream(t *testing.T) {
clients := newClientsContainer()
clients := newClientsContainer(t)
// Add client with upstreams.
ok, err := clients.Add(&Client{

View File

@@ -123,10 +123,14 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C
Name: cj.Name,
IDs: cj.IDs,
Tags: cj.Tags,
BlockedServices: cj.BlockedServices,
Upstreams: cj.Upstreams,
BlockedServices: &filtering.BlockedServices{
Schedule: prev.BlockedServices.Schedule.Clone(),
IDs: cj.BlockedServices,
},
IDs: cj.IDs,
Tags: cj.Tags,
Upstreams: cj.Upstreams,
UseOwnSettings: !cj.UseGlobalSettings,
FilteringEnabled: cj.FilteringEnabled,
@@ -180,7 +184,8 @@ func clientToJSON(c *Client) (cj *clientJSON) {
SafeBrowsingEnabled: c.SafeBrowsingEnabled,
UseGlobalBlockedServices: !c.UseOwnBlockedServices,
BlockedServices: c.BlockedServices,
BlockedServices: c.BlockedServices.IDs,
Upstreams: c.Upstreams,

View File

@@ -473,12 +473,12 @@ func applyAdditionalFiltering(clientIP net.IP, clientID string, setts *filtering
if c.UseOwnBlockedServices {
// TODO(e.burkov): Get rid of this crutch.
svcs := c.BlockedServices
if svcs == nil {
svcs = []string{}
setts.ServicesRules = nil
svcs := c.BlockedServices.IDs
if !c.BlockedServices.Schedule.Contains(time.Now()) {
Context.filters.ApplyBlockedServicesList(setts, svcs)
log.Debug("%s: services for client %q set: %s", pref, c.Name, svcs)
}
Context.filters.ApplyBlockedServicesList(setts, svcs)
log.Debug("%s: services for client %q set: %s", pref, c.Name, svcs)
}
setts.ClientName = c.Name

View File

@@ -0,0 +1,176 @@
package home
import (
"net"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestApplyAdditionalFiltering(t *testing.T) {
var err error
Context.filters, err = filtering.New(&filtering.Config{
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
},
}, nil)
require.NoError(t, err)
Context.clients.idIndex = map[string]*Client{
"default": {
UseOwnSettings: false,
safeSearchConf: filtering.SafeSearchConfig{Enabled: false},
FilteringEnabled: false,
SafeBrowsingEnabled: false,
ParentalEnabled: false,
},
"custom_filtering": {
UseOwnSettings: true,
safeSearchConf: filtering.SafeSearchConfig{Enabled: true},
FilteringEnabled: true,
SafeBrowsingEnabled: true,
ParentalEnabled: true,
},
"partial_custom_filtering": {
UseOwnSettings: true,
safeSearchConf: filtering.SafeSearchConfig{Enabled: true},
FilteringEnabled: true,
SafeBrowsingEnabled: false,
ParentalEnabled: false,
},
}
testCases := []struct {
name string
id string
FilteringEnabled assert.BoolAssertionFunc
SafeSearchEnabled assert.BoolAssertionFunc
SafeBrowsingEnabled assert.BoolAssertionFunc
ParentalEnabled assert.BoolAssertionFunc
}{{
name: "global_settings",
id: "default",
FilteringEnabled: assert.False,
SafeSearchEnabled: assert.False,
SafeBrowsingEnabled: assert.False,
ParentalEnabled: assert.False,
}, {
name: "custom_settings",
id: "custom_filtering",
FilteringEnabled: assert.True,
SafeSearchEnabled: assert.True,
SafeBrowsingEnabled: assert.True,
ParentalEnabled: assert.True,
}, {
name: "partial",
id: "partial_custom_filtering",
FilteringEnabled: assert.True,
SafeSearchEnabled: assert.True,
SafeBrowsingEnabled: assert.False,
ParentalEnabled: assert.False,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
setts := &filtering.Settings{}
applyAdditionalFiltering(net.IP{1, 2, 3, 4}, tc.id, setts)
tc.FilteringEnabled(t, setts.FilteringEnabled)
tc.SafeSearchEnabled(t, setts.SafeSearchEnabled)
tc.SafeBrowsingEnabled(t, setts.SafeBrowsingEnabled)
tc.ParentalEnabled(t, setts.ParentalEnabled)
})
}
}
func TestApplyAdditionalFiltering_blockedServices(t *testing.T) {
filtering.InitModule()
var (
globalBlockedServices = []string{"ok"}
clientBlockedServices = []string{"ok", "mail_ru", "vk"}
invalidBlockedServices = []string{"invalid"}
err error
)
Context.filters, err = filtering.New(&filtering.Config{
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
IDs: globalBlockedServices,
},
}, nil)
require.NoError(t, err)
Context.clients.idIndex = map[string]*Client{
"default": {
UseOwnBlockedServices: false,
},
"no_services": {
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
},
UseOwnBlockedServices: true,
},
"services": {
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
IDs: clientBlockedServices,
},
UseOwnBlockedServices: true,
},
"invalid_services": {
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
IDs: invalidBlockedServices,
},
UseOwnBlockedServices: true,
},
"allow_all": {
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.FullWeekly(),
IDs: clientBlockedServices,
},
UseOwnBlockedServices: true,
},
}
testCases := []struct {
name string
id string
wantLen int
}{{
name: "global_settings",
id: "default",
wantLen: len(globalBlockedServices),
}, {
name: "custom_settings",
id: "no_services",
wantLen: 0,
}, {
name: "custom_settings_block",
id: "services",
wantLen: len(clientBlockedServices),
}, {
name: "custom_settings_invalid",
id: "invalid_services",
wantLen: 0,
}, {
name: "custom_settings_inactive_schedule",
id: "allow_all",
wantLen: 0,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
setts := &filtering.Settings{}
applyAdditionalFiltering(net.IP{1, 2, 3, 4}, tc.id, setts)
require.Len(t, setts.ServicesRules, tc.wantLen)
})
}
}

View File

@@ -355,13 +355,17 @@ func initContextClients() (err error) {
arpdb = aghnet.NewARPDB()
}
Context.clients.Init(
err = Context.clients.Init(
config.Clients.Persistent,
Context.dhcpServer,
Context.etcHosts,
arpdb,
config.DNS.DnsfilterConf,
)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
return nil
}
@@ -382,6 +386,19 @@ func setupBindOpts(opts options) (err error) {
config.BindHost = opts.bindHost
}
// Rewrite deprecated options.
bindAddr := opts.bindAddr
if bindAddr.IsValid() {
config.BindHost = bindAddr.Addr()
config.BindPort = int(bindAddr.Port())
err = checkPorts()
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
}
return nil
}

View File

@@ -35,11 +35,18 @@ type options struct {
serviceControlAction string
// bindHost is the address on which to serve the HTTP UI.
//
// Deprecated: Use bindAddr.
bindHost netip.Addr
// bindPort is the port on which to serve the HTTP UI.
//
// Deprecated: Use bindAddr.
bindPort int
// bindAddr is the address to serve the web UI on.
bindAddr netip.AddrPort
// checkConfig is true if the current invocation is only required to check
// the configuration file and exit.
checkConfig bool
@@ -147,9 +154,10 @@ var cmdLineOpts = []cmdLineOpt{{
return o.bindHost.String(), true
},
description: "Host address to bind HTTP server on.",
longName: "host",
shortName: "h",
description: "Deprecated. Host address to bind HTTP server on. Use --web-addr. " +
"The short -h will work as --help in the future.",
longName: "host",
shortName: "h",
}, {
updateWithValue: func(o options, v string) (options, error) {
var err error
@@ -174,9 +182,23 @@ var cmdLineOpts = []cmdLineOpt{{
return strconv.Itoa(o.bindPort), true
},
description: "Port to serve HTTP pages on.",
description: "Deprecated. Port to serve HTTP pages on. Use --web-addr.",
longName: "port",
shortName: "p",
}, {
updateWithValue: func(o options, v string) (oo options, err error) {
o.bindAddr, err = netip.ParseAddrPort(v)
return o, err
},
updateNoValue: nil,
effect: nil,
serialize: func(o options) (val string, ok bool) {
return o.bindAddr.String(), o.bindAddr.IsValid()
},
description: "Address to serve the web UI on, in the host:port format.",
longName: "web-addr",
shortName: "",
}, {
updateWithValue: func(o options, v string) (options, error) {
o.serviceControlAction = v

View File

@@ -82,6 +82,23 @@ func TestParseBindPort(t *testing.T) {
testParseErr(t, "port too high", "-p", "18446744073709551617") // 2^64 + 1
}
func TestParseBindAddr(t *testing.T) {
wantAddrPort := netip.MustParseAddrPort("1.2.3.4:8089")
assert.Zero(t, testParseOK(t).bindAddr, "empty is not web-addr")
assert.Equal(t, wantAddrPort, testParseOK(t, "--web-addr", "1.2.3.4:8089").bindAddr)
assert.Equal(t, netip.MustParseAddrPort("1.2.3.4:0"), testParseOK(t, "--web-addr", "1.2.3.4:0").bindAddr)
testParseParamMissing(t, "-web-addr")
testParseErr(t, "not an int", "--web-addr", "1.2.3.4:x")
testParseErr(t, "hex not supported", "--web-addr", "1.2.3.4:0x100")
testParseErr(t, "port negative", "--web-addr", "1.2.3.4:-1")
testParseErr(t, "port too high", "--web-addr", "1.2.3.4:65536")
testParseErr(t, "port too high", "--web-addr", "1.2.3.4:4294967297") // 2^32 + 1
testParseErr(t, "port too high", "--web-addr", "1.2.3.4:18446744073709551617") // 2^64 + 1
}
func TestParseLogfile(t *testing.T) {
assert.Equal(t, "", testParseOK(t).logFile, "empty is no log file")
assert.Equal(t, "path", testParseOK(t, "-l", "path").logFile, "-l is log file")
@@ -162,6 +179,10 @@ func TestOptsToArgs(t *testing.T) {
name: "bind_port",
args: []string{"-p", "666"},
opts: options{bindPort: 666},
}, {
name: "web-addr",
args: []string{"--web-addr", "1.2.3.4:8080"},
opts: options{bindAddr: netip.MustParseAddrPort("1.2.3.4:8080")},
}, {
name: "log_file",
args: []string{"-l", "path"},

View File

@@ -228,12 +228,7 @@ func TestRDNS_WorkerLoop(t *testing.T) {
for _, tc := range testCases {
w.Reset()
cc := &clientsContainer{
list: map[string]*Client{},
idIndex: map[string]*Client{},
ipToRC: map[netip.Addr]*RuntimeClient{},
allTags: stringutil.NewSet(),
}
cc := newClientsContainer(t)
ch := make(chan netip.Addr)
rdns := &RDNS{
exchanger: &rDNSExchanger{

View File

@@ -22,7 +22,7 @@ import (
)
// currentSchemaVersion is the current schema version.
const currentSchemaVersion = 21
const currentSchemaVersion = 22
// These aliases are provided for convenience.
type (
@@ -95,6 +95,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
upgradeSchema18to19,
upgradeSchema19to20,
upgradeSchema20to21,
upgradeSchema21to22,
}
n := 0
@@ -1179,6 +1180,82 @@ func upgradeSchema20to21(diskConf yobj) (err error) {
return nil
}
// upgradeSchema21to22 performs the following changes:
//
// # BEFORE:
// 'persistent':
// - 'name': 'client_name'
// 'blocked_services':
// - 'svc_name'
//
// # AFTER:
// 'persistent':
// - 'name': 'client_name'
// 'blocked_services':
// 'ids':
// - 'svc_name'
// 'schedule':
// 'time_zone': 'Local'
func upgradeSchema21to22(diskConf yobj) (err error) {
log.Println("Upgrade yaml: 21 to 22")
diskConf["schema_version"] = 22
const field = "blocked_services"
clientsVal, ok := diskConf["clients"]
if !ok {
return nil
}
clients, ok := clientsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of clients: %T", clientsVal)
}
persistentVal, ok := clients["persistent"]
if !ok {
return nil
}
persistent, ok := persistentVal.([]any)
if !ok {
return fmt.Errorf("unexpected type of persistent clients: %T", persistentVal)
}
for i, val := range persistent {
var c yobj
c, ok = val.(yobj)
if !ok {
return fmt.Errorf("persistent client at index %d: unexpected type %T", i, val)
}
var blockedVal any
blockedVal, ok = c[field]
if !ok {
continue
}
var services yarr
services, ok = blockedVal.(yarr)
if !ok {
return fmt.Errorf(
"persistent client at index %d: unexpected type of blocked services: %T",
i,
blockedVal,
)
}
c[field] = yobj{
"ids": services,
"schedule": yobj{
"time_zone": "Local",
},
}
}
return nil
}
// TODO(a.garipov): Replace with log.Output when we port it to our logging
// package.
func funcName() string {

View File

@@ -1183,3 +1183,73 @@ func TestUpgradeSchema20to21(t *testing.T) {
})
}
}
func TestUpgradeSchema21to22(t *testing.T) {
const newSchemaVer = 22
testCases := []struct {
in yobj
want yobj
name string
}{{
in: yobj{
"clients": yobj{},
},
want: yobj{
"clients": yobj{},
"schema_version": newSchemaVer,
},
name: "nothing",
}, {
in: yobj{
"clients": yobj{
"persistent": []any{yobj{"name": "localhost", "blocked_services": yarr{}}},
},
},
want: yobj{
"clients": yobj{
"persistent": []any{yobj{
"name": "localhost",
"blocked_services": yobj{
"ids": yarr{},
"schedule": yobj{
"time_zone": "Local",
},
},
}},
},
"schema_version": newSchemaVer,
},
name: "no_services",
}, {
in: yobj{
"clients": yobj{
"persistent": []any{yobj{"name": "localhost", "blocked_services": yarr{"ok"}}},
},
},
want: yobj{
"clients": yobj{
"persistent": []any{yobj{
"name": "localhost",
"blocked_services": yobj{
"ids": yarr{"ok"},
"schedule": yobj{
"time_zone": "Local",
},
},
}},
},
"schema_version": newSchemaVer,
},
name: "services",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := upgradeSchema21to22(tc.in)
require.NoError(t, err)
assert.Equal(t, tc.want, tc.in)
})
}
}

View File

@@ -0,0 +1,26 @@
# This is a file showing example configuration for AdGuard Home.
#
# TODO(a.garipov): Move to the top level once the rewrite is over.
dns:
addresses:
- '0.0.0.0:53'
bootstrap_dns:
- '9.9.9.10'
- '149.112.112.10'
- '2620:fe::10'
- '2620:fe::fe:10'
upstream_dns:
- '8.8.8.8'
dns64_prefixes:
- '1234::/64'
upstream_timeout: 1s
bootstrap_prefer_ipv6: true
use_dns64: true
http:
addresses:
- '0.0.0.0:3000'
secure_addresses: []
timeout: 5s
force_https: true
verbose: true

View File

@@ -129,8 +129,8 @@ type commandLineOption struct {
// AdGuard Home.
var commandLineOptions = []*commandLineOption{
confFileIdx: {
// TODO(a.garipov): Remove the ".1" when the new code is ready.
defaultValue: "AdGuardHome.1.yaml",
// TODO(a.garipov): Remove the directory when the new code is ready.
defaultValue: "internal/next/AdGuardHome.yaml",
description: "Path to the config file.",
long: "config",
short: "c",

View File

@@ -23,10 +23,13 @@ type config struct {
//
// TODO(a.garipov): Validate.
type dnsConfig struct {
Addresses []netip.AddrPort `yaml:"addresses"`
BootstrapDNS []string `yaml:"bootstrap_dns"`
UpstreamDNS []string `yaml:"upstream_dns"`
UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"`
Addresses []netip.AddrPort `yaml:"addresses"`
BootstrapDNS []string `yaml:"bootstrap_dns"`
UpstreamDNS []string `yaml:"upstream_dns"`
DNS64Prefixes []netip.Prefix `yaml:"dns64_prefixes"`
UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"`
BootstrapPreferIPv6 bool `yaml:"bootstrap_prefer_ipv6"`
UseDNS64 bool `yaml:"use_dns64"`
}
// httpConfig is the on-disk web API configuration.

View File

@@ -14,7 +14,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"gopkg.in/yaml.v3"
)
@@ -64,12 +63,6 @@ func New(
return nil, err
}
// TODO(a.garipov): Move into a separate function and add other logging
// settings.
if conf.Verbose {
log.SetLevel(log.DEBUG)
}
// TODO(a.garipov): Validate the configuration structure. Return an error
// if it's incorrect.
@@ -102,10 +95,13 @@ func (m *Manager) assemble(
start time.Time,
) (err error) {
dnsConf := &dnssvc.Config{
Addresses: conf.DNS.Addresses,
BootstrapServers: conf.DNS.BootstrapDNS,
UpstreamServers: conf.DNS.UpstreamDNS,
UpstreamTimeout: conf.DNS.UpstreamTimeout.Duration,
Addresses: conf.DNS.Addresses,
BootstrapServers: conf.DNS.BootstrapDNS,
UpstreamServers: conf.DNS.UpstreamDNS,
DNS64Prefixes: conf.DNS.DNS64Prefixes,
UpstreamTimeout: conf.DNS.UpstreamTimeout.Duration,
BootstrapPreferIPv6: conf.DNS.BootstrapPreferIPv6,
UseDNS64: conf.DNS.UseDNS64,
}
err = m.updateDNS(ctx, dnsConf)
if err != nil {

View File

@@ -0,0 +1,35 @@
package dnssvc
import (
"net/netip"
"time"
)
// Config is the AdGuard Home DNS service configuration structure.
//
// TODO(a.garipov): Add timeout for incoming requests.
type Config struct {
// Addresses are the addresses on which to serve plain DNS queries.
Addresses []netip.AddrPort
// BootstrapServers are the addresses of DNS servers used for bootstrapping
// the upstream DNS server addresses.
BootstrapServers []string
// UpstreamServers are the upstream DNS server addresses to use.
UpstreamServers []string
// DNS64Prefixes is a slice of NAT64 prefixes to be used for DNS64. See
// also [Config.UseDNS64].
DNS64Prefixes []netip.Prefix
// UpstreamTimeout is the timeout for upstream requests.
UpstreamTimeout time.Duration
// BootstrapPreferIPv6, if true, instructs the bootstrapper to prefer IPv6
// addresses to IPv4 ones when bootstrapping.
BootstrapPreferIPv6 bool
// UseDNS64, if true, enables DNS64 protection for incoming requests.
UseDNS64 bool
}

View File

@@ -19,40 +19,20 @@ import (
"github.com/AdguardTeam/dnsproxy/upstream"
)
// Config is the AdGuard Home DNS service configuration structure.
//
// TODO(a.garipov): Add timeout for incoming requests.
type Config struct {
// Addresses are the addresses on which to serve plain DNS queries.
Addresses []netip.AddrPort
// Upstreams are the DNS upstreams to use. If not set, upstreams are
// created using data from BootstrapServers, UpstreamServers, and
// UpstreamTimeout.
//
// TODO(a.garipov): Think of a better scheme. Those other three parameters
// are here only to make Config work properly.
Upstreams []upstream.Upstream
// BootstrapServers are the addresses for bootstrapping the upstream DNS
// server addresses.
BootstrapServers []string
// UpstreamServers are the upstream DNS server addresses to use.
UpstreamServers []string
// UpstreamTimeout is the timeout for upstream requests.
UpstreamTimeout time.Duration
}
// Service is the AdGuard Home DNS service. A nil *Service is a valid
// [agh.Service] that does nothing.
//
// TODO(a.garipov): Consider saving a [*proxy.Config] instance for those
// fields that are only used in [New] and [Service.Config].
type Service struct {
proxy *proxy.Proxy
bootstraps []string
upstreams []string
upsTimeout time.Duration
running atomic.Bool
proxy *proxy.Proxy
bootstraps []string
upstreams []string
dns64Prefixes []netip.Prefix
upsTimeout time.Duration
running atomic.Bool
bootstrapPreferIPv6 bool
useDNS64 bool
}
// New returns a new properly initialized *Service. If c is nil, svc is a nil
@@ -64,23 +44,22 @@ func New(c *Config) (svc *Service, err error) {
}
svc = &Service{
bootstraps: c.BootstrapServers,
upstreams: c.UpstreamServers,
upsTimeout: c.UpstreamTimeout,
bootstraps: c.BootstrapServers,
upstreams: c.UpstreamServers,
dns64Prefixes: c.DNS64Prefixes,
upsTimeout: c.UpstreamTimeout,
bootstrapPreferIPv6: c.BootstrapPreferIPv6,
useDNS64: c.UseDNS64,
}
var upstreams []upstream.Upstream
if len(c.Upstreams) > 0 {
upstreams = c.Upstreams
} else {
upstreams, err = addressesToUpstreams(
c.UpstreamServers,
c.BootstrapServers,
c.UpstreamTimeout,
)
if err != nil {
return nil, fmt.Errorf("converting upstreams: %w", err)
}
upstreams, err := addressesToUpstreams(
c.UpstreamServers,
c.BootstrapServers,
c.UpstreamTimeout,
c.BootstrapPreferIPv6,
)
if err != nil {
return nil, fmt.Errorf("converting upstreams: %w", err)
}
svc.proxy = &proxy.Proxy{
@@ -90,6 +69,8 @@ func New(c *Config) (svc *Service, err error) {
UpstreamConfig: &proxy.UpstreamConfig{
Upstreams: upstreams,
},
UseDNS64: c.UseDNS64,
DNS64Prefs: c.DNS64Prefixes,
},
}
@@ -108,12 +89,14 @@ func addressesToUpstreams(
upsStrs []string,
bootstraps []string,
timeout time.Duration,
preferIPv6 bool,
) (upstreams []upstream.Upstream, err error) {
upstreams = make([]upstream.Upstream, len(upsStrs))
for i, upsStr := range upsStrs {
upstreams[i], err = upstream.AddressToUpstream(upsStr, &upstream.Options{
Bootstrap: bootstraps,
Timeout: timeout,
Bootstrap: bootstraps,
Timeout: timeout,
PreferIPv6: preferIPv6,
})
if err != nil {
return nil, fmt.Errorf("upstream at index %d: %w", i, err)
@@ -206,10 +189,13 @@ func (svc *Service) Config() (c *Config) {
}
c = &Config{
Addresses: addrs,
BootstrapServers: svc.bootstraps,
UpstreamServers: svc.upstreams,
UpstreamTimeout: svc.upsTimeout,
Addresses: addrs,
BootstrapServers: svc.bootstraps,
UpstreamServers: svc.upstreams,
DNS64Prefixes: svc.dns64Prefixes,
UpstreamTimeout: svc.upsTimeout,
BootstrapPreferIPv6: svc.bootstrapPreferIPv6,
UseDNS64: svc.useDNS64,
}
return c

View File

@@ -6,10 +6,7 @@ import (
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
@@ -21,36 +18,55 @@ func TestMain(m *testing.M) {
}
// testTimeout is the common timeout for tests.
const testTimeout = 100 * time.Millisecond
const testTimeout = 1 * time.Second
func TestService(t *testing.T) {
const (
bootstrapAddr = "bootstrap.example"
listenAddr = "127.0.0.1:0"
bootstrapAddr = "127.0.0.1:0"
upstreamAddr = "upstream.example"
closeErr errors.Error = "closing failed"
)
ups := &aghtest.UpstreamMock{
OnAddress: func() (addr string) {
return upstreamAddr
},
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
resp = (&dns.Msg{}).SetReply(req)
upstreamErrCh := make(chan error, 1)
upstreamStartedCh := make(chan struct{})
upstreamSrv := &dns.Server{
Addr: bootstrapAddr,
Net: "udp",
Handler: dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
pt := testutil.PanicT{}
return resp, nil
},
OnClose: func() (err error) {
return closeErr
},
resp := (&dns.Msg{}).SetReply(req)
resp.Answer = append(resp.Answer, &dns.A{
Hdr: dns.RR_Header{},
A: netip.MustParseAddrPort(bootstrapAddr).Addr().AsSlice(),
})
writeErr := w.WriteMsg(resp)
require.NoError(pt, writeErr)
}),
NotifyStartedFunc: func() { close(upstreamStartedCh) },
}
go func() {
listenErr := upstreamSrv.ListenAndServe()
if listenErr != nil {
// Log these immediately to see what happens.
t.Logf("upstream listen error: %s", listenErr)
}
upstreamErrCh <- listenErr
}()
_, _ = testutil.RequireReceive(t, upstreamStartedCh, testTimeout)
c := &dnssvc.Config{
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:0")},
Upstreams: []upstream.Upstream{ups},
BootstrapServers: []string{bootstrapAddr},
UpstreamServers: []string{upstreamAddr},
UpstreamTimeout: testTimeout,
Addresses: []netip.AddrPort{netip.MustParseAddrPort(listenAddr)},
BootstrapServers: []string{upstreamSrv.PacketConn.LocalAddr().String()},
UpstreamServers: []string{upstreamAddr},
DNS64Prefixes: nil,
UpstreamTimeout: testTimeout,
BootstrapPreferIPv6: false,
UseDNS64: false,
}
svc, err := dnssvc.New(c)
@@ -82,8 +98,14 @@ func TestService(t *testing.T) {
defer cancel()
cli := &dns.Client{}
resp, _, excErr := cli.ExchangeContext(ctx, req, addr.String())
require.NoError(t, excErr)
var resp *dns.Msg
require.Eventually(t, func() (ok bool) {
var excErr error
resp, _, excErr = cli.ExchangeContext(ctx, req, addr.String())
return excErr == nil
}, testTimeout, testTimeout/10)
assert.NotNil(t, resp)
})
@@ -92,5 +114,12 @@ func TestService(t *testing.T) {
defer cancel()
err = svc.Shutdown(ctx)
require.ErrorIs(t, err, closeErr)
require.NoError(t, err)
err = upstreamSrv.Shutdown()
require.NoError(t, err)
err, ok := testutil.RequireReceive(t, upstreamErrCh, testTimeout)
require.True(t, ok)
require.NoError(t, err)
}

View File

@@ -17,10 +17,13 @@ import (
type ReqPatchSettingsDNS struct {
// TODO(a.garipov): Add more as we go.
Addresses []netip.AddrPort `json:"addresses"`
BootstrapServers []string `json:"bootstrap_servers"`
UpstreamServers []string `json:"upstream_servers"`
UpstreamTimeout JSONDuration `json:"upstream_timeout"`
Addresses []netip.AddrPort `json:"addresses"`
BootstrapServers []string `json:"bootstrap_servers"`
UpstreamServers []string `json:"upstream_servers"`
DNS64Prefixes []netip.Prefix `json:"dns64_prefixes"`
UpstreamTimeout JSONDuration `json:"upstream_timeout"`
BootstrapPreferIPv6 bool `json:"bootstrap_prefer_ipv6"`
UseDNS64 bool `json:"use_dns64"`
}
// HTTPAPIDNSSettings are the DNS settings as used by the HTTP API. See the
@@ -28,10 +31,13 @@ type ReqPatchSettingsDNS struct {
type HTTPAPIDNSSettings struct {
// TODO(a.garipov): Add more as we go.
Addresses []netip.AddrPort `json:"addresses"`
BootstrapServers []string `json:"bootstrap_servers"`
UpstreamServers []string `json:"upstream_servers"`
UpstreamTimeout JSONDuration `json:"upstream_timeout"`
Addresses []netip.AddrPort `json:"addresses"`
BootstrapServers []string `json:"bootstrap_servers"`
UpstreamServers []string `json:"upstream_servers"`
DNS64Prefixes []netip.Prefix `json:"dns64_prefixes"`
UpstreamTimeout JSONDuration `json:"upstream_timeout"`
BootstrapPreferIPv6 bool `json:"bootstrap_prefer_ipv6"`
UseDNS64 bool `json:"use_dns64"`
}
// handlePatchSettingsDNS is the handler for the PATCH /api/v1/settings/dns HTTP
@@ -53,10 +59,13 @@ func (svc *Service) handlePatchSettingsDNS(w http.ResponseWriter, r *http.Reques
}
newConf := &dnssvc.Config{
Addresses: req.Addresses,
BootstrapServers: req.BootstrapServers,
UpstreamServers: req.UpstreamServers,
UpstreamTimeout: time.Duration(req.UpstreamTimeout),
Addresses: req.Addresses,
BootstrapServers: req.BootstrapServers,
UpstreamServers: req.UpstreamServers,
DNS64Prefixes: req.DNS64Prefixes,
UpstreamTimeout: time.Duration(req.UpstreamTimeout),
BootstrapPreferIPv6: req.BootstrapPreferIPv6,
UseDNS64: req.UseDNS64,
}
ctx := r.Context()
@@ -76,9 +85,12 @@ func (svc *Service) handlePatchSettingsDNS(w http.ResponseWriter, r *http.Reques
}
writeJSONOKResponse(w, r, &HTTPAPIDNSSettings{
Addresses: newConf.Addresses,
BootstrapServers: newConf.BootstrapServers,
UpstreamServers: newConf.UpstreamServers,
UpstreamTimeout: JSONDuration(newConf.UpstreamTimeout),
Addresses: newConf.Addresses,
BootstrapServers: newConf.BootstrapServers,
UpstreamServers: newConf.UpstreamServers,
DNS64Prefixes: newConf.DNS64Prefixes,
UpstreamTimeout: JSONDuration(newConf.UpstreamTimeout),
BootstrapPreferIPv6: newConf.BootstrapPreferIPv6,
UseDNS64: newConf.UseDNS64,
})
}

View File

@@ -20,10 +20,13 @@ import (
func TestService_HandlePatchSettingsDNS(t *testing.T) {
wantDNS := &websvc.HTTPAPIDNSSettings{
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.1.1:53")},
BootstrapServers: []string{"1.0.0.1"},
UpstreamServers: []string{"1.1.1.1"},
UpstreamTimeout: websvc.JSONDuration(2 * time.Second),
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.1.1:53")},
BootstrapServers: []string{"1.0.0.1"},
UpstreamServers: []string{"1.1.1.1"},
DNS64Prefixes: []netip.Prefix{netip.MustParsePrefix("1234::/64")},
UpstreamTimeout: websvc.JSONDuration(2 * time.Second),
BootstrapPreferIPv6: true,
UseDNS64: true,
}
var started atomic.Bool
@@ -51,10 +54,13 @@ func TestService_HandlePatchSettingsDNS(t *testing.T) {
}
req := jobj{
"addresses": wantDNS.Addresses,
"bootstrap_servers": wantDNS.BootstrapServers,
"upstream_servers": wantDNS.UpstreamServers,
"upstream_timeout": wantDNS.UpstreamTimeout,
"addresses": wantDNS.Addresses,
"bootstrap_servers": wantDNS.BootstrapServers,
"upstream_servers": wantDNS.UpstreamServers,
"dns64_prefixes": wantDNS.DNS64Prefixes,
"upstream_timeout": wantDNS.UpstreamTimeout,
"bootstrap_prefer_ipv6": wantDNS.BootstrapPreferIPv6,
"use_dns64": wantDNS.UseDNS64,
}
respBody := httpPatch(t, u, req, http.StatusOK)

View File

@@ -27,10 +27,13 @@ func (svc *Service) handleGetSettingsAll(w http.ResponseWriter, r *http.Request)
// TODO(a.garipov): Add all currently supported parameters.
writeJSONOKResponse(w, r, &RespGetV1SettingsAll{
DNS: &HTTPAPIDNSSettings{
Addresses: dnsConf.Addresses,
BootstrapServers: dnsConf.BootstrapServers,
UpstreamServers: dnsConf.UpstreamServers,
UpstreamTimeout: JSONDuration(dnsConf.UpstreamTimeout),
Addresses: dnsConf.Addresses,
BootstrapServers: dnsConf.BootstrapServers,
UpstreamServers: dnsConf.UpstreamServers,
DNS64Prefixes: dnsConf.DNS64Prefixes,
UpstreamTimeout: JSONDuration(dnsConf.UpstreamTimeout),
BootstrapPreferIPv6: dnsConf.BootstrapPreferIPv6,
UseDNS64: dnsConf.UseDNS64,
},
HTTP: &HTTPAPIHTTPSettings{
Addresses: httpConf.Addresses,

View File

@@ -20,10 +20,11 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
// TODO(a.garipov): Add all currently supported parameters.
wantDNS := &websvc.HTTPAPIDNSSettings{
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:53")},
BootstrapServers: []string{"94.140.14.140", "94.140.14.141"},
UpstreamServers: []string{"94.140.14.14", "1.1.1.1"},
UpstreamTimeout: websvc.JSONDuration(1 * time.Second),
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:53")},
BootstrapServers: []string{"94.140.14.140", "94.140.14.141"},
UpstreamServers: []string{"94.140.14.14", "1.1.1.1"},
UpstreamTimeout: websvc.JSONDuration(1 * time.Second),
BootstrapPreferIPv6: true,
}
wantWeb := &websvc.HTTPAPIHTTPSettings{
@@ -36,10 +37,11 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
confMgr := newConfigManager()
confMgr.onDNS = func() (s agh.ServiceWithConfig[*dnssvc.Config]) {
c, err := dnssvc.New(&dnssvc.Config{
Addresses: wantDNS.Addresses,
UpstreamServers: wantDNS.UpstreamServers,
BootstrapServers: wantDNS.BootstrapServers,
UpstreamTimeout: time.Duration(wantDNS.UpstreamTimeout),
Addresses: wantDNS.Addresses,
UpstreamServers: wantDNS.UpstreamServers,
BootstrapServers: wantDNS.BootstrapServers,
UpstreamTimeout: time.Duration(wantDNS.UpstreamTimeout),
BootstrapPreferIPv6: true,
})
require.NoError(t, err)

View File

@@ -6,7 +6,6 @@ import (
"sync/atomic"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghchan"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/stretchr/testify/assert"
)
@@ -26,9 +25,6 @@ func TestWaitListener_Accept(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
done := make(chan struct{})
go aghchan.MustReceive(done, testTimeout)
go func() {
var wrapper net.Listener = &waitListener{
Listener: l,
@@ -39,7 +35,6 @@ func TestWaitListener_Accept(t *testing.T) {
}()
wg.Wait()
close(done)
assert.True(t, accepted.Load())
assert.Eventually(t, accepted.Load, testTimeout, testTimeout/10)
}

View File

@@ -28,6 +28,36 @@ func EmptyWeekly() (w *Weekly) {
}
}
// FullWeekly creates full weekly schedule with local time zone.
//
// TODO(s.chzhen): Consider moving into tests.
func FullWeekly() (w *Weekly) {
fullDay := dayRange{start: 0, end: maxDayRange}
return &Weekly{
location: time.Local,
days: [7]dayRange{
time.Sunday: fullDay,
time.Monday: fullDay,
time.Tuesday: fullDay,
time.Wednesday: fullDay,
time.Thursday: fullDay,
time.Friday: fullDay,
time.Saturday: fullDay,
},
}
}
// Clone returns a deep copy of a weekly.
func (w *Weekly) Clone() (c *Weekly) {
// NOTE: Do not use time.LoadLocation, because the results will be
// different on time zone database update.
return &Weekly{
location: w.location,
days: w.days,
}
}
// Contains returns true if t is within the corresponding day range of the
// schedule in the schedule's time zone.
func (w *Weekly) Contains(t time.Time) (ok bool) {

View File

@@ -4,8 +4,8 @@ go 1.19
require (
github.com/fzipp/gocyclo v0.6.0
github.com/golangci/misspell v0.4.0
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28
github.com/golangci/misspell v0.4.1
github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601
github.com/kisielk/errcheck v1.6.3
github.com/kyoh86/looppointer v0.2.1
github.com/securego/gosec/v2 v2.16.0
@@ -14,11 +14,11 @@ require (
golang.org/x/vuln v0.1.0
honnef.co/go/tools v0.4.3
mvdan.cc/gofumpt v0.5.0
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8
mvdan.cc/unparam v0.0.0-20230610194454-9ea02bef9868
)
require (
github.com/BurntSushi/toml v1.3.1 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.3 // indirect
@@ -26,7 +26,7 @@ require (
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/exp/typeparams v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/exp/typeparams v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.9.0 // indirect

View File

@@ -1,5 +1,5 @@
github.com/BurntSushi/toml v1.3.1 h1:rHnDkSK+/g6DlREUK73PkmIs60pqrnuduK+JmP++JmU=
github.com/BurntSushi/toml v1.3.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -8,8 +8,8 @@ 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.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/golangci/misspell v0.4.0 h1:KtVB/hTK4bbL/S6bs64rYyk8adjmh1BygbBiaAiX+a0=
github.com/golangci/misspell v0.4.0/go.mod h1:W6O/bwV6lGDxUCChm2ykw9NQdd5bYd1Xkjo88UcWyJc=
github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g=
github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI=
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -19,8 +19,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE=
github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE=
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 h1:9alfqbrhuD+9fLZ4iaAVwhlp5PEhmnBt7yvK2Oy5C1U=
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8=
github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8=
github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -53,8 +53,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp/typeparams v0.0.0-20230522175609-2e198f4a06a1 h1:pnP8r+W8Fm7XJ8CWtXi4S9oJmPBTrkfYN/dNbaPj6Y4=
golang.org/x/exp/typeparams v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230626212559-97b1e661b5df h1:jfUqBujZx2dktJVEmZpCkyngz7MWrVv1y9kLOqFNsqw=
golang.org/x/exp/typeparams v0.0.0-20230626212559-97b1e661b5df/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
@@ -112,5 +112,5 @@ honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw=
honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA=
mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E=
mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js=
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 h1:VuJo4Mt0EVPychre4fNlDWDuE5AjXtPJpRUWqZDQhaI=
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8/go.mod h1:Oh/d7dEtzsNHGOq1Cdv8aMm3KdKhVvPbRQcM8WFpBR8=
mvdan.cc/unparam v0.0.0-20230610194454-9ea02bef9868 h1:F4Q7pXcrU9UiU1fq0ZWqSOxKjNAteRuDr7JDk7uVLRQ=
mvdan.cc/unparam v0.0.0-20230610194454-9ea02bef9868/go.mod h1:6ZaiQyI7Tiq0HQ56g6N8TlkSd80/LyagZeaw8mb7jYE=

View File

@@ -62,16 +62,21 @@ readonly docker_output
case "$channel"
in
('release')
docker_tags="--tag=${docker_image_name}:${version},${docker_image_name}:latest"
docker_version_tag="${docker_image_name}:${version}"
docker_channel_tag="${docker_image_name}:latest"
;;
('beta')
docker_tags="--tag=${docker_image_name}:${version},${docker_image_name}:beta"
docker_version_tag="${docker_image_name}:${version}"
docker_channel_tag="${docker_image_name}:beta"
;;
('edge')
docker_tags="--tag=${docker_image_name}:edge"
# Don't set the version tag when pushing to the edge channel.
docker_version_tag="${docker_image_name}:edge"
docker_channel_tag="${docker_image_name}"
;;
('development')
docker_tags="--tag=${docker_image_name}"
docker_version_tag="${docker_image_name}"
docker_channel_tag="${docker_image_name}"
;;
(*)
echo "invalid channel '$channel', supported values are\
@@ -79,7 +84,7 @@ in
exit 1
;;
esac
readonly docker_tags
readonly docker_version_tag docker_channel_tag
# Copy the binaries into a new directory under new names, so that it's easier to
# COPY them later. DO NOT remove the trailing underscores. See file
@@ -122,6 +127,7 @@ $sudo_cmd docker\
--build-arg VERSION="$version"\
--output "$docker_output"\
--platform "$docker_platforms"\
"$docker_tags"\
--tag="$docker_version_tag"\
--tag="$docker_channel_tag"\
-f ./docker/Dockerfile\
.

View File

@@ -288,6 +288,7 @@ func downloadWorker(wg *sync.WaitGroup, client *http.Client, uriCh <-chan *url.U
data, err := getTranslation(client, uri.String())
if err != nil {
log.Error("download worker: getting translation: %s", err)
log.Info("download worker: error response:\n%s", data)
continue
}
@@ -295,6 +296,11 @@ func downloadWorker(wg *sync.WaitGroup, client *http.Client, uriCh <-chan *url.U
q := uri.Query()
code := q.Get("language")
// Fix some TwoSky weirdnesses.
//
// TODO(a.garipov): Remove when those are fixed.
code = strings.ToLower(code)
name := filepath.Join(localesDir, code+".json")
err = os.WriteFile(name, data, 0o664)
if err != nil {
@@ -307,7 +313,8 @@ func downloadWorker(wg *sync.WaitGroup, client *http.Client, uriCh <-chan *url.U
}
}
// getTranslation returns received translation data or error.
// getTranslation returns received translation data and error. If err is not
// nil, data may contain a response from server for inspection.
func getTranslation(client *http.Client, url string) (data []byte, err error) {
resp, err := client.Get(url)
if err != nil {
@@ -319,24 +326,19 @@ func getTranslation(client *http.Client, url string) (data []byte, err error) {
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("url: %q; status code: %s", url, http.StatusText(resp.StatusCode))
return nil, err
// Go on and download the body for inspection.
}
limitReader, err := aghio.LimitReader(resp.Body, readLimit)
if err != nil {
err = fmt.Errorf("limit reading: %w", err)
return nil, err
limitReader, lrErr := aghio.LimitReader(resp.Body, readLimit)
if lrErr != nil {
// Generally shouldn't happen, since the only error returned by
// [aghio.LimitReader] is an argument error.
panic(fmt.Errorf("limit reading: %w", lrErr))
}
data, err = io.ReadAll(limitReader)
if err != nil {
err = fmt.Errorf("reading all: %w", err)
data, readErr := io.ReadAll(limitReader)
return nil, err
}
return data, nil
return data, errors.WithDeferred(err, readErr)
}
// translationURL returns a new url.URL with provided query parameters.
@@ -344,6 +346,18 @@ func translationURL(oldURL *url.URL, baseFile, projectID string, lang langCode)
uri = &url.URL{}
*uri = *oldURL
// Fix some TwoSky weirdnesses.
//
// TODO(a.garipov): Remove when those are fixed.
switch lang {
case "si-lk":
lang = "si-LK"
case "zh-hk":
lang = "zh-HK"
default:
// Go on.
}
q := uri.Query()
q.Set("format", "json")
q.Set("filename", baseFile)