Compare commits
172 Commits
release-v0
...
WIP-upd-ur
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
931d61bd9e | ||
|
|
07d48af10c | ||
|
|
e58a415d10 | ||
|
|
ae43ca0605 | ||
|
|
9acb1f364b | ||
|
|
84cd528103 | ||
|
|
56519548f1 | ||
|
|
bdcd17a41a | ||
|
|
1eafb4e7cf | ||
|
|
bf024fb985 | ||
|
|
a832987f7c | ||
|
|
77e5e27d75 | ||
|
|
3505ce8739 | ||
|
|
14d8f58592 | ||
|
|
006cd98869 | ||
|
|
ce1b2bc4f1 | ||
|
|
8f4acce44a | ||
|
|
b04d1ed6c8 | ||
|
|
f987c25598 | ||
|
|
b9b93f1286 | ||
|
|
a7a5e50620 | ||
|
|
0edf71a4af | ||
|
|
5956b97e7f | ||
|
|
d3f39b0aa1 | ||
|
|
e738508d7a | ||
|
|
302faca32f | ||
|
|
1c1ca1c6e3 | ||
|
|
a497dc09ca | ||
|
|
3ce04f48ca | ||
|
|
368a98fb29 | ||
|
|
cbe32c5a73 | ||
|
|
f46c9f74d5 | ||
|
|
4b884ace62 | ||
|
|
7ce7e90865 | ||
|
|
756c932e37 | ||
|
|
c3d5fcc669 | ||
|
|
65a33a1215 | ||
|
|
1a49d2f0c9 | ||
|
|
549b20bdea | ||
|
|
75f01d51f7 | ||
|
|
a82ec09afd | ||
|
|
c0ac82be6a | ||
|
|
24d7dc8e8a | ||
|
|
79d85a24e9 | ||
|
|
f289f4b1b6 | ||
|
|
58515fce43 | ||
|
|
21905d9869 | ||
|
|
a580149ad6 | ||
|
|
6dc9e73ce4 | ||
|
|
5d52e68d26 | ||
|
|
c4ff80fd3a | ||
|
|
ed449c6186 | ||
|
|
1c89394aef | ||
|
|
235316e050 | ||
|
|
0a1ff65b4a | ||
|
|
2a1ad532f4 | ||
|
|
9d144ecb0a | ||
|
|
9b7fe74086 | ||
|
|
82af43039c | ||
|
|
12ee287d0b | ||
|
|
57171f0a61 | ||
|
|
21a1187ed2 | ||
|
|
2c2c0d445b | ||
|
|
9f0fdc5e78 | ||
|
|
96594a3433 | ||
|
|
4c5b38a447 | ||
|
|
0e608fda13 | ||
|
|
8bb95469d9 | ||
|
|
e9e0b7c4f9 | ||
|
|
c70f941bf8 | ||
|
|
a79b61aac3 | ||
|
|
5e71f5df6a | ||
|
|
f31ffcc5d1 | ||
|
|
0d562a7b1f | ||
|
|
3603b1fcab | ||
|
|
82505566f8 | ||
|
|
9ce2a0fb34 | ||
|
|
5cba78a8d5 | ||
|
|
2c33ab6a92 | ||
|
|
beb674ecbc | ||
|
|
b16b1d1d24 | ||
|
|
f8e45c13f3 | ||
|
|
b9790f663a | ||
|
|
778585865e | ||
|
|
573cbafe3f | ||
|
|
e7b3c9969b | ||
|
|
dc0d081b47 | ||
|
|
ded9842cd7 | ||
|
|
89d9b03dfe | ||
|
|
f1d05a49f0 | ||
|
|
9a764b9b82 | ||
|
|
e0b557eda2 | ||
|
|
ea6e033dae | ||
|
|
3afe7c3daf | ||
|
|
afbc7a72e3 | ||
|
|
ff1e108bfe | ||
|
|
b29f320fd4 | ||
|
|
773b80a969 | ||
|
|
f131067278 | ||
|
|
b43aa86cae | ||
|
|
6824eec308 | ||
|
|
18079ca1bb | ||
|
|
a1f29c31b9 | ||
|
|
0ef8344178 | ||
|
|
f53f48cc33 | ||
|
|
2a5b5f1927 | ||
|
|
b290eddc70 | ||
|
|
6d0a43aad6 | ||
|
|
1bc2186c2d | ||
|
|
6584c300b8 | ||
|
|
dc480ae70f | ||
|
|
e783564084 | ||
|
|
0ee34534c6 | ||
|
|
9146df5493 | ||
|
|
76fa60498e | ||
|
|
8455940b59 | ||
|
|
2d46aa7121 | ||
|
|
bf9b35b9c6 | ||
|
|
f9aa5ae86a | ||
|
|
642d68c482 | ||
|
|
5ff7cdbac8 | ||
|
|
504c54ab0e | ||
|
|
90c17c79de | ||
|
|
0b72bcc5a1 | ||
|
|
dc14f89c9f | ||
|
|
2263adbbe0 | ||
|
|
e29261516f | ||
|
|
f12eaf29a2 | ||
|
|
3e2ab87293 | ||
|
|
41e8db4221 | ||
|
|
3f5605c42e | ||
|
|
f7ff02f3b1 | ||
|
|
5ec4a4dab8 | ||
|
|
13871977f9 | ||
|
|
2fdda8a22c | ||
|
|
d82b290251 | ||
|
|
eb15304ff4 | ||
|
|
1a3bf5ebda | ||
|
|
15956f4511 | ||
|
|
09d0ce4578 | ||
|
|
9735a35123 | ||
|
|
813a06d09a | ||
|
|
061136508e | ||
|
|
008f58c863 | ||
|
|
0e4ffd339f | ||
|
|
1458600c37 | ||
|
|
34c95f99f8 | ||
|
|
e9c59b098e | ||
|
|
a0bb5ce8a4 | ||
|
|
01947bedb4 | ||
|
|
a6ca824064 | ||
|
|
380cff07f2 | ||
|
|
d2ce06e1ca | ||
|
|
2ed1f939b5 | ||
|
|
dea8a585f8 | ||
|
|
313555b10c | ||
|
|
661f4ece48 | ||
|
|
52f36f201e | ||
|
|
46cd974e2a | ||
|
|
201ef10de6 | ||
|
|
d9df7c13be | ||
|
|
64e751e579 | ||
|
|
e6e5958595 | ||
|
|
ff3df0ec33 | ||
|
|
ebe86ce00e | ||
|
|
d317e19291 | ||
|
|
39c4999d2d | ||
|
|
7f55bd8461 | ||
|
|
2968a65f14 | ||
|
|
779fbe79b8 | ||
|
|
da0d1cb754 | ||
|
|
ef80c07075 |
49
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
49
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -1,49 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report to help us improve AdGuard Home
|
||||
---
|
||||
|
||||
Have a question or an idea? Please search it [on our forum](https://github.com/AdguardTeam/AdGuardHome/discussions) to make sure it was not yet asked. If you cannot find what you had in mind, please [submit it here](https://github.com/AdguardTeam/AdGuardHome/discussions/new).
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Please answer the following questions for yourself before submitting an issue. **YOU MAY DELETE THE PREREQUISITES SECTION.**
|
||||
|
||||
- [ ] I am running the latest version
|
||||
- [ ] I checked the documentation and found no answer
|
||||
- [ ] I checked to make sure that this issue has not already been filed
|
||||
|
||||
### Issue Details
|
||||
|
||||
<!-- Please include all relevant details about the environment you experienced the bug in. If possible, include the result of running `./AdGuardHome -v --version` from the installation directory. -->
|
||||
|
||||
* **Version of AdGuard Home server:**
|
||||
* <!-- (e.g. v0.123.4) -->
|
||||
* **How did you install AdGuard Home:**
|
||||
* <!-- (e.g. Built from source, Snapcraft, Docker, GitHub releases, etc.) -->
|
||||
* **How did you setup DNS configuration:**
|
||||
* <!-- (System/Router/IoT) -->
|
||||
* **If it's a router or IoT, please write device model:**
|
||||
* <!-- (e.g. Raspberry Pi 3 Model B) -->
|
||||
* **CPU architecture:**
|
||||
* <!-- (e.g. AMD64, MIPS, etc.) -->
|
||||
* **Operating system and version:**
|
||||
* <!-- (e.g. Ubuntu 18.04.1) -->
|
||||
|
||||
### Expected Behavior
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
### Actual Behavior
|
||||
<!-- A clear and concise description of what actually happened. -->
|
||||
|
||||
### Screenshots
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
<details><summary>Screenshot:</summary>
|
||||
|
||||
<!--- drag and drop, upload or paste your screenshot to this area-->
|
||||
|
||||
</details>
|
||||
|
||||
### Additional Information
|
||||
<!-- Add any other context about the problem here. -->
|
||||
26
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
26
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a feature request for AdGuard Home
|
||||
---
|
||||
|
||||
Have a question or an idea? Please search it [on our forum](https://github.com/AdguardTeam/AdGuardHome/discussions) to make sure it was not yet asked. If you cannot find what you had in mind, please [submit it here](https://github.com/AdguardTeam/AdGuardHome/discussions/new).
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Please answer the following questions for yourself before submitting an issue. **YOU MAY DELETE THE PREREQUISITES SECTION.**
|
||||
|
||||
- [ ] I am running the latest version
|
||||
- [ ] I checked the documentation and found no answer
|
||||
- [ ] I checked to make sure that this issue has not already been filed
|
||||
|
||||
### Problem Description
|
||||
<!-- Is your feature request related to a problem? Please add a clear and concise description of what the problem is. -->
|
||||
|
||||
### Proposed Solution
|
||||
<!-- Describe the solution you'd like in a clear and concise manner -->
|
||||
|
||||
### Alternatives Considered
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
### Additional Information
|
||||
<!-- Add any other context about the problem here. -->
|
||||
109
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
109
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
'body':
|
||||
- 'attributes':
|
||||
'description': >
|
||||
Please make sure that the issue is not a duplicate or a question.
|
||||
If it's a duplicate, please react to the original issue with a
|
||||
thumbs up. If it's a question, please post it to the GitHub
|
||||
Discussions page.
|
||||
'label': 'Prerequisites'
|
||||
'options':
|
||||
- 'label': >
|
||||
I have checked the
|
||||
[Wiki](https://github.com/AdguardTeam/AdGuardHome/wiki) and
|
||||
[Discussions](https://github.com/AdguardTeam/AdGuardHome/discussions)
|
||||
and found no answer
|
||||
'required': true
|
||||
- 'label': >
|
||||
I have searched other issues and found no duplicates
|
||||
'required': true
|
||||
- 'label': >
|
||||
I want to report a bug and not ask a question
|
||||
'required': true
|
||||
'id': 'prerequisites'
|
||||
'type': 'checkboxes'
|
||||
- 'attributes':
|
||||
'description': 'On which operating system type does the issue occur?'
|
||||
'label': 'Operating system type'
|
||||
'options':
|
||||
- 'FreeBSD'
|
||||
- 'Linux, OpenWrt'
|
||||
- 'Linux, Other (please mention the version in the description)'
|
||||
- 'macOS (aka Darwin)'
|
||||
- 'OpenBSD'
|
||||
- 'Windows'
|
||||
- 'Other (please mention in the description)'
|
||||
'id': 'os'
|
||||
'type': 'dropdown'
|
||||
'validations':
|
||||
'required': true
|
||||
- 'attributes':
|
||||
'description': 'On which CPU architecture does the issue occur?'
|
||||
'label': 'CPU architecture'
|
||||
'options':
|
||||
- 'AMD64'
|
||||
- 'x86'
|
||||
- '64-bit ARM'
|
||||
- 'ARMv5'
|
||||
- 'ARMv6'
|
||||
- 'ARMv7'
|
||||
- '64-bit MIPS'
|
||||
- '64-bit MIPS LE'
|
||||
- '32-bit MIPS'
|
||||
- '32-bit MIPS LE'
|
||||
- '64-bit PowerPC LE'
|
||||
- 'Other (please mention in the description)'
|
||||
'id': 'arch'
|
||||
'type': 'dropdown'
|
||||
'validations':
|
||||
'required': true
|
||||
- 'attributes':
|
||||
'description': 'How did you install AdGuard Home?'
|
||||
'label': 'Installation'
|
||||
'options':
|
||||
- 'GitHub releases or script from README'
|
||||
- 'Docker'
|
||||
- 'Snapcraft'
|
||||
- 'Custom port'
|
||||
- 'Other (please mention in the description)'
|
||||
'id': 'install'
|
||||
'type': 'dropdown'
|
||||
'validations':
|
||||
'required': true
|
||||
- 'attributes':
|
||||
'description': 'How did you setup AdGuard Home?'
|
||||
'label': 'Setup'
|
||||
'options':
|
||||
- 'On one machine'
|
||||
- 'On a router, DHCP is handled by the router'
|
||||
- 'On a router, DHCP is handled by AdGuard Home'
|
||||
- 'Other (please mention in the description)'
|
||||
'id': 'setup'
|
||||
'type': 'dropdown'
|
||||
'validations':
|
||||
'required': true
|
||||
- 'attributes':
|
||||
'description': 'What version of AdGuard Home are you using?'
|
||||
'label': 'AdGuard Home version'
|
||||
'id': 'version'
|
||||
'type': 'input'
|
||||
'validations':
|
||||
'required': true
|
||||
- 'attributes':
|
||||
'description': 'Please describe the bug'
|
||||
'label': 'Description'
|
||||
'value': |
|
||||
#### What did you do?
|
||||
|
||||
#### Expected result
|
||||
|
||||
#### Actual result
|
||||
|
||||
#### Screenshots (if applicable)
|
||||
|
||||
#### Additional information
|
||||
'id': 'description'
|
||||
'type': 'textarea'
|
||||
'validations':
|
||||
'required': true
|
||||
'description': 'File a bug report'
|
||||
'name': 'Bug'
|
||||
17
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
17
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
'blank_issues_enabled': false
|
||||
'contact_links':
|
||||
- 'about': >
|
||||
Please report filtering issues, for example advertising filters
|
||||
misfiring or safe browsing false positives, using the form on our
|
||||
website
|
||||
'name': 'AdGuard filters issues'
|
||||
'url': 'https://reports.adguard.com/en/new_issue.html'
|
||||
- 'about': >
|
||||
Please use GitHub Discussions for questions
|
||||
'name': 'Q&A Discussions'
|
||||
'url': 'https://github.com/AdguardTeam/AdGuardHome/discussions'
|
||||
- 'about': >
|
||||
Please check our Wiki for configuration file description, frequently
|
||||
asked questions, and other documentation
|
||||
'name': 'Wiki'
|
||||
'url': 'https://github.com/AdguardTeam/AdGuardHome/wiki'
|
||||
41
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
41
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
'body':
|
||||
- 'attributes':
|
||||
'description': >
|
||||
Please make sure that the issue is not a duplicate or a question.
|
||||
If it's a duplicate, please react to the original issue with a
|
||||
thumbs up. If it's a question, please post it to the GitHub
|
||||
Discussions page.
|
||||
'label': 'Prerequisites'
|
||||
'options':
|
||||
- 'label': >
|
||||
I have checked the
|
||||
[Wiki](https://github.com/AdguardTeam/AdGuardHome/wiki) and
|
||||
[Discussions](https://github.com/AdguardTeam/AdGuardHome/discussions)
|
||||
and found no answer
|
||||
'required': true
|
||||
- 'label': >
|
||||
I have searched other issues and found no duplicates
|
||||
'required': true
|
||||
- 'label': >
|
||||
I want to request a feature or enhancement and not ask a
|
||||
question
|
||||
'required': true
|
||||
'id': 'prerequisites'
|
||||
'type': 'checkboxes'
|
||||
- 'attributes':
|
||||
'description': 'Please describe the request'
|
||||
'label': 'Description'
|
||||
'value': |
|
||||
#### What problem are you trying to solve?
|
||||
|
||||
#### Proposed solution
|
||||
|
||||
#### Alternatives considered
|
||||
|
||||
#### Additional information
|
||||
'id': 'description'
|
||||
'type': 'textarea'
|
||||
'validations':
|
||||
'required': true
|
||||
'description': 'Suggest a feature or an enhancement for AdGuard Home'
|
||||
'name': 'Feature request or enhancement'
|
||||
57
CHANGELOG.md
57
CHANGELOG.md
@@ -23,7 +23,7 @@ and this project adheres to
|
||||
### Added
|
||||
|
||||
- Support for Discovery of Designated Resolvers (DDR) according to the [RFC
|
||||
draft][ddr-draft-06] ([#4463]).
|
||||
draft][ddr-draft] ([#4463]).
|
||||
- `windows/arm64` support ([#3057]).
|
||||
|
||||
### Deprecated
|
||||
@@ -33,16 +33,52 @@ and this project adheres to
|
||||
[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
|
||||
[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
|
||||
|
||||
[ddr-draft-06]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html
|
||||
[ddr-draft]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-08
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
## [v0.107.8] - 2022-07-12 (APPROX.)
|
||||
## [v0.107.9] - 2022-08-23 (APPROX.)
|
||||
-->
|
||||
|
||||
|
||||
|
||||
## [v0.107.8] - 2022-07-13
|
||||
|
||||
See also the [v0.107.8 GitHub milestone][ms-v0.107.8].
|
||||
|
||||
### Security
|
||||
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
CVE-2022-1705, CVE-2022-32148, CVE-2022-30631, and other Go vulnerabilities
|
||||
fixed in [Go 1.17.12][go-1.17.12].
|
||||
|
||||
<!--
|
||||
TODO(a.garipov): Use the above format in all similar announcements below.
|
||||
-->
|
||||
|
||||
### Fixed
|
||||
|
||||
- DHCP lease validation incorrectly letting users assign the IP address of the
|
||||
gateway as the address of the lease ([#4698]).
|
||||
- Updater no longer expects a hardcoded name for `AdGuardHome` executable
|
||||
([#4219]).
|
||||
- Inconsistent names of runtime clients from hosts files ([#4683]).
|
||||
- PTR requests for addresses leased by DHCP will now be resolved into hostnames
|
||||
under `dhcp.local_domain_name` ([#4699]).
|
||||
- Broken service installation on OpenWrt ([#4677]).
|
||||
|
||||
[#4219]: https://github.com/AdguardTeam/AdGuardHome/issues/4219
|
||||
[#4677]: https://github.com/AdguardTeam/AdGuardHome/issues/4677
|
||||
[#4683]: https://github.com/AdguardTeam/AdGuardHome/issues/4683
|
||||
[#4698]: https://github.com/AdguardTeam/AdGuardHome/issues/4698
|
||||
[#4699]: https://github.com/AdguardTeam/AdGuardHome/issues/4699
|
||||
|
||||
[go-1.17.12]: https://groups.google.com/g/golang-announce/c/nqrv9fbR0zE
|
||||
[ms-v0.107.8]: https://github.com/AdguardTeam/AdGuardHome/milestone/44?closed=1
|
||||
|
||||
|
||||
|
||||
## [v0.107.7] - 2022-06-06
|
||||
|
||||
See also the [v0.107.7 GitHub milestone][ms-v0.107.7].
|
||||
@@ -51,7 +87,7 @@ See also the [v0.107.7 GitHub milestone][ms-v0.107.7].
|
||||
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
[CVE-2022-29526], [CVE-2022-30634], [CVE-2022-30629], [CVE-2022-30580], and
|
||||
[CVE-2022-29804] vulnerabilities.
|
||||
[CVE-2022-29804] Go vulnerabilities.
|
||||
- Enforced password strength policy ([#3503]).
|
||||
|
||||
### Added
|
||||
@@ -206,7 +242,7 @@ See also the [v0.107.6 GitHub milestone][ms-v0.107.6].
|
||||
|
||||
- `User-Agent` HTTP header removed from outgoing DNS-over-HTTPS requests.
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
[CVE-2022-24675], [CVE-2022-27536], and [CVE-2022-28327] vulnerabilities.
|
||||
[CVE-2022-24675], [CVE-2022-27536], and [CVE-2022-28327] Go vulnerabilities.
|
||||
|
||||
### Added
|
||||
|
||||
@@ -261,7 +297,7 @@ were resolved.
|
||||
### Security
|
||||
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
[CVE-2022-24921] vulnerability.
|
||||
[CVE-2022-24921] Go vulnerability.
|
||||
|
||||
[CVE-2022-24921]: https://www.cvedetails.com/cve/CVE-2022-24921
|
||||
|
||||
@@ -274,7 +310,7 @@ See also the [v0.107.4 GitHub milestone][ms-v0.107.4].
|
||||
### Security
|
||||
|
||||
- Go version was updated to prevent the possibility of exploiting the
|
||||
[CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] vulnerabilities.
|
||||
[CVE-2022-23806], [CVE-2022-23772], and [CVE-2022-23773] Go vulnerabilities.
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1010,11 +1046,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
||||
|
||||
|
||||
<!--
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...HEAD
|
||||
[v0.107.8]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...v0.107.8
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.9...HEAD
|
||||
[v0.107.9]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...v0.107.9
|
||||
-->
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...HEAD
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.8...HEAD
|
||||
[v0.107.8]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.7...v0.107.8
|
||||
[v0.107.7]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.6...v0.107.7
|
||||
[v0.107.6]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.5...v0.107.6
|
||||
[v0.107.5]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.4...v0.107.5
|
||||
|
||||
3
Makefile
3
Makefile
@@ -34,6 +34,8 @@ YARN_INSTALL_FLAGS = $(YARN_FLAGS) --network-timeout 120000 --silent\
|
||||
--ignore-engines --ignore-optional --ignore-platform\
|
||||
--ignore-scripts
|
||||
|
||||
V1API = 0
|
||||
|
||||
# Macros for the build-release target. If FRONTEND_PREBUILT is 0, the
|
||||
# default, the macro $(BUILD_RELEASE_DEPS_$(FRONTEND_PREBUILT)) expands
|
||||
# into BUILD_RELEASE_DEPS_0, and so both frontend and backend
|
||||
@@ -61,6 +63,7 @@ ENV = env\
|
||||
PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
|
||||
RACE='$(RACE)'\
|
||||
SIGN='$(SIGN)'\
|
||||
V1API='$(V1API)'\
|
||||
VERBOSE='$(VERBOSE)'\
|
||||
VERSION='$(VERSION)'\
|
||||
|
||||
|
||||
40
README.md
40
README.md
@@ -1,6 +1,6 @@
|
||||
|
||||
<p align="center">
|
||||
<img src="https://cdn.adguard.com/public/Adguard/Common/adguard_home.svg" width="300px" alt="AdGuard Home" />
|
||||
<img src="https://cdn.adtidy.org/public/Adguard/Common/adguard_home.svg" width="300px" alt="AdGuard Home" />
|
||||
</p>
|
||||
<h3 align="center">Privacy protection center for you and your devices</h3>
|
||||
<p align="center">
|
||||
@@ -38,7 +38,7 @@
|
||||
<br />
|
||||
|
||||
<p align="center">
|
||||
<img src="https://cdn.adguard.com/public/Adguard/Common/adguard_home.gif" width="800" />
|
||||
<img src="https://cdn.adtidy.org/public/Adguard/Common/adguard_home.gif" width="800" />
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
@@ -281,28 +281,28 @@ curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/s
|
||||
```
|
||||
|
||||
* Beta channel builds
|
||||
* Linux: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
|
||||
* Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz)
|
||||
* Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
||||
* Windows: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip)
|
||||
* macOS: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip)
|
||||
* macOS ARM: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_arm64.zip)
|
||||
* FreeBSD: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz)
|
||||
* FreeBSD ARM: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz)
|
||||
* Linux: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
|
||||
* Linux ARM: [32-bit ARMv6](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz)
|
||||
* Linux MIPS: [32-bit MIPS](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
||||
* Windows: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_windows_386.zip)
|
||||
* macOS: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_386.zip)
|
||||
* macOS ARM: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_arm64.zip)
|
||||
* FreeBSD: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz)
|
||||
* FreeBSD ARM: [64-bit](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz)
|
||||
* OpenBSD: (coming soon)
|
||||
* OpenBSD ARM: (coming soon)
|
||||
|
||||
* Edge channel builds
|
||||
* Linux: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_386.tar.gz)
|
||||
* Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz)
|
||||
* Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
||||
* Windows: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_386.zip)
|
||||
* macOS: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_386.zip)
|
||||
* macOS ARM: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_arm64.zip)
|
||||
* FreeBSD: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_386.tar.gz)
|
||||
* FreeBSD ARM: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv7.tar.gz)
|
||||
* OpenBSD: [64-bit (experimental)](https://static.adguard.com/adguardhome/edge/AdGuardHome_openbsd_amd64.tar.gz)
|
||||
* OpenBSD ARM: [64-bit (experimental)](https://static.adguard.com/adguardhome/edge/AdGuardHome_openbsd_arm64.tar.gz)
|
||||
* Linux: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_386.tar.gz)
|
||||
* Linux ARM: [32-bit ARMv6](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi OS stable), [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz)
|
||||
* Linux MIPS: [32-bit MIPS](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adtidy.org/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
||||
* Windows: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_windows_386.zip)
|
||||
* macOS: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_darwin_386.zip)
|
||||
* macOS ARM: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_darwin_arm64.zip)
|
||||
* FreeBSD: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_386.tar.gz)
|
||||
* FreeBSD ARM: [64-bit](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adtidy.org/adguardhome/edge/AdGuardHome_freebsd_armv7.tar.gz)
|
||||
* OpenBSD: [64-bit (experimental)](https://static.adtidy.org/adguardhome/edge/AdGuardHome_openbsd_amd64.tar.gz)
|
||||
* OpenBSD ARM: [64-bit (experimental)](https://static.adtidy.org/adguardhome/edge/AdGuardHome_openbsd_arm64.tar.gz)
|
||||
|
||||
|
||||
<a id="reporting-issues"></a>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Make sure to sync any changes with the branch overrides below.
|
||||
'variables':
|
||||
'channel': 'edge'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.3'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.5'
|
||||
|
||||
'stages':
|
||||
- 'Make release':
|
||||
@@ -22,11 +22,11 @@
|
||||
'jobs':
|
||||
- 'Make and publish docker'
|
||||
|
||||
- 'Publish to static.adguard.com':
|
||||
- 'Publish to static storage':
|
||||
'manual': false
|
||||
'final': false
|
||||
'jobs':
|
||||
- 'Publish to static.adguard.com'
|
||||
- 'Publish to static storage'
|
||||
|
||||
- 'Publish to Snapstore':
|
||||
'manual': false
|
||||
@@ -132,7 +132,7 @@
|
||||
'requirements':
|
||||
- 'adg-docker': 'true'
|
||||
|
||||
'Publish to static.adguard.com':
|
||||
'Publish to static storage':
|
||||
'key': 'PUB'
|
||||
'other':
|
||||
'clean-working-dir': true
|
||||
@@ -285,7 +285,7 @@
|
||||
# need to build a few of these.
|
||||
'variables':
|
||||
'channel': 'beta'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.3'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.5'
|
||||
# release-vX.Y.Z branches are the branches from which the actual final release
|
||||
# is built.
|
||||
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||
@@ -300,4 +300,4 @@
|
||||
# are the ones that actually get released.
|
||||
'variables':
|
||||
'channel': 'release'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.3'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.5'
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'key': 'AHBRTSPECS'
|
||||
'name': 'AdGuard Home - Build and run tests'
|
||||
'variables':
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.3'
|
||||
'dockerGo': 'adguard/golang-ubuntu:4.5'
|
||||
|
||||
'stages':
|
||||
- 'Tests':
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Няслушнае імя сервера",
|
||||
"form_error_subnet": "Падсетка «{{cidr}}» не ўтрымвае IP-адраса «{{ip}}»",
|
||||
"form_error_positive": "Павінна быць больш 0",
|
||||
"form_error_gateway_ip": "Арэнда не можа мець IP-адрас шлюза",
|
||||
"out_of_range_error": "Павінна быць па-за дыяпазонам «{{start}}»-«{{end}}»",
|
||||
"lower_range_start_error": "Павінна быць менш за пачатак дыяпазону",
|
||||
"greater_range_start_error": "Павінна быць больш за пачатак дыяпазону",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Заблакаваныя дамены",
|
||||
"access_blocked_desc": "Не блытайце гэта з фільтрамі. AdGuard Home будзе ігнараваць DNS-запыты з гэтымі даменамі.",
|
||||
"access_settings_saved": "Налады доступу паспяхова захаваны",
|
||||
"updates_checked": "Праверка абнаўленняў прайшла паспяхова",
|
||||
"updates_checked": "Даступная новая версія AdGuard Home",
|
||||
"updates_version_equal": "Версія AdGuard Home актуальная",
|
||||
"check_updates_now": "Праверыць абнаўленні",
|
||||
"dns_privacy": "Зашыфраваны DNS",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Neplatný název serveru",
|
||||
"form_error_subnet": "Podsíť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"",
|
||||
"form_error_positive": "Musí být větší než 0",
|
||||
"form_error_gateway_ip": "Pronájem nemůže mít IP adresu brány",
|
||||
"out_of_range_error": "Musí být mimo rozsah \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Musí být menší než začátek rozsahu",
|
||||
"greater_range_start_error": "Musí být větší než začátek rozsahu",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Blokované domény",
|
||||
"access_blocked_desc": "Nezaměňujte to s filtry. AdGuard Home zruší dotazy DNS odpovídající těmto doménám a tyto dotazy se neobjeví ani v protokolu dotazů. Zde můžete určit přesné názvy domén, zástupné znaky a pravidla filtrování URL adres, např. \"example.org\", \"*.example.org\" nebo \"||example.org^\".",
|
||||
"access_settings_saved": "Nastavení přístupu bylo úspěšně uloženo",
|
||||
"updates_checked": "Aktualizace úspěšně zkontrolovány",
|
||||
"updates_checked": "Nová verze AdGuard Home je k dispozici\n",
|
||||
"updates_version_equal": "AdGuard Home je aktuální",
|
||||
"check_updates_now": "Zkontrolovat aktualizace nyní",
|
||||
"dns_privacy": "Soukromí DNS",
|
||||
|
||||
@@ -445,7 +445,7 @@
|
||||
"access_blocked_title": "Ikke tilladte domæner",
|
||||
"access_blocked_desc": "Ikke at forveksle med filtre. AdGuard Home dropper DNS-forespørgsler matchende disse domæner, ej heller vil forespørgslerne optræde i forespørgselsloggen. Der kan angives præcise domænenavne, jokertegn eller URL-filterregler, f.eks. \"eksempel.org\", \"*.eksempel.org\", \"||eksempel.org^\" eller tilsvarende.",
|
||||
"access_settings_saved": "Adgangsindstillinger gemt",
|
||||
"updates_checked": "Opdateringstjek foretaget",
|
||||
"updates_checked": "En ny version af AdGuard Home er tilgængelig\n",
|
||||
"updates_version_equal": "AdGuard Home er opdateret",
|
||||
"check_updates_now": "Søg efter opdateringer nu",
|
||||
"dns_privacy": "DNS-fortrolighed",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Ungültiger Servername",
|
||||
"form_error_subnet": "Subnetz „{{cidr}}“ enthält nicht die IP-Adresse „{{ip}}“",
|
||||
"form_error_positive": "Muss größer als 0 sein",
|
||||
"form_error_gateway_ip": "Lease kann nicht die IP-Adresse des Gateways haben",
|
||||
"out_of_range_error": "Muss außerhalb des Bereichs „{{start}}“-„{{end}}“ liegen",
|
||||
"lower_range_start_error": "Muss niedriger als der Bereichsbeginn sein",
|
||||
"greater_range_start_error": "Muss größer als der Bereichsbeginn sein",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Nicht zugelassene Domains",
|
||||
"access_blocked_desc": "Verwechseln Sie dies nicht mit Filtern. AdGuard Home verwirft DNS-Abfragen, die mit diesen Domänen übereinstimmen, und diese Abfragen erscheinen nicht einmal im Abfrageprotokoll. Hier können Sie die genauen Domain-Namen, Wildcards und URL-Filter-Regeln angeben, z.B. 'beispiel.org', '*.beispiel.org' oder '||beispiel.org^'.",
|
||||
"access_settings_saved": "Zugriffseinstellungen erfolgreich gespeichert",
|
||||
"updates_checked": "Erfolgreich auf Aktualisierungen geprüft",
|
||||
"updates_checked": "Neue Version von AdGuard Home ist jetzt verfügbar",
|
||||
"updates_version_equal": "AdGuard Home ist aktuell",
|
||||
"check_updates_now": "Jetzt nach Aktualisierungen suchen",
|
||||
"dns_privacy": "DNS-Datenschutz",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Invalid server name",
|
||||
"form_error_subnet": "Subnet \"{{cidr}}\" does not contain the IP address \"{{ip}}\"",
|
||||
"form_error_positive": "Must be greater than 0",
|
||||
"form_error_gateway_ip": "Lease can't have the IP address of the gateway",
|
||||
"out_of_range_error": "Must be out of range \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Must be lower than range start",
|
||||
"greater_range_start_error": "Must be greater than range start",
|
||||
@@ -362,7 +363,7 @@
|
||||
"encryption_config_saved": "Encryption configuration saved",
|
||||
"encryption_server": "Server name",
|
||||
"encryption_server_enter": "Enter your domain name",
|
||||
"encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate or wildcard certificate. If the field is not set, it will accept TLS connections for any domain.",
|
||||
"encryption_server_desc": "If set, AdGuard Home detects ClientIDs, responds to DDR queries, and performs additional connection validations. If not set, these features are disabled. Must match one of the DNS Names in the certificate.",
|
||||
"encryption_redirect": "Redirect to HTTPS automatically",
|
||||
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
|
||||
"encryption_https": "HTTPS port",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Disallowed domains",
|
||||
"access_blocked_desc": "Not to be confused with filters. AdGuard Home drops DNS queries matching these domains, and these queries don't even appear in the query log. You can specify exact domain names, wildcards, or URL filter rules, e.g. \"example.org\", \"*.example.org\", or \"||example.org^\" correspondingly.",
|
||||
"access_settings_saved": "Access settings successfully saved",
|
||||
"updates_checked": "Updates successfully checked",
|
||||
"updates_checked": "A new version of AdGuard Home is available",
|
||||
"updates_version_equal": "AdGuard Home is up-to-date",
|
||||
"check_updates_now": "Check for updates now",
|
||||
"dns_privacy": "DNS Privacy",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Nombre de servidor no válido",
|
||||
"form_error_subnet": "La subred \"{{cidr}}\" no contiene la dirección IP \"{{ip}}\"",
|
||||
"form_error_positive": "Debe ser mayor que 0",
|
||||
"form_error_gateway_ip": "Asignación no puede tener la dirección IP de la puerta de enlace",
|
||||
"out_of_range_error": "Debe estar fuera del rango \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Debe ser inferior que el inicio de rango",
|
||||
"greater_range_start_error": "Debe ser mayor que el inicio de rango",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Dominios no permitidos",
|
||||
"access_blocked_desc": "No debe confundirse con filtros. AdGuard Home descartará las consultas DNS que coincidan con estos dominios, y estas consultas ni siquiera aparecerán en el registro de consultas. Puedes especificar nombres de dominio exactos, comodines o reglas de filtrado de URL, por ejemplo: \"ejemplo.org\", \"*.ejemplo.org\" o \"||ejemplo.org^\" correspondientemente.",
|
||||
"access_settings_saved": "Configuración de acceso guardado correctamente",
|
||||
"updates_checked": "Actualizaciones comprobadas correctamente",
|
||||
"updates_checked": "La nueva versión de AdGuard Home está disponible",
|
||||
"updates_version_equal": "AdGuard Home está actualizado",
|
||||
"check_updates_now": "Buscar actualizaciones ahora",
|
||||
"dns_privacy": "DNS cifrado",
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"bootstrap_dns": "خودراه انداز سرورهای DNS",
|
||||
"bootstrap_dns_desc": "خودراه انداز سرورهای DNS برای تفکیک آدرس آی پی تفکیک کننده های DoH/DoT که شما بعنوان جریان ارسالی تعیین کردید استفاده میشود.",
|
||||
"local_ptr_title": "سرورهای خصوصی DNS",
|
||||
"local_ptr_desc": "سرور یا سرور های DNS ای که AdGuard Home برای درخواست های منابع محلی ارائه شده مورد استفاده قرار خواهد داد. برای مثال، این سرور برای تعیین نام های سرویس دهنده برای سرویس گیرنده با آدرس های آی پی خصوصی مورد استفاده قرار خواهد گرفت. اگر تعیین نشود،AdGuard Home به طور خودکار از تعیین کننده ی DNS پیش فرض شما استفاده خواهد کرد.",
|
||||
"local_ptr_default_resolver": "به طور پیش فرض، AdGuard Home از تعیین کننده های DNS معکوس زیر استفاده می کند: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home نتوانست برای این دستگاه تعیین کننده های DNS معکوس محرمانه مناسب را معین کند.",
|
||||
"local_ptr_placeholder": "در هر خط یک آدرس سرور را وارد کنید",
|
||||
@@ -321,7 +320,6 @@
|
||||
"install_devices_android_list_5": "گروه مقادیر DNS 1 و DNS 2 را به آدرس سرور AdGuard Home خود تغییر دهید.",
|
||||
"install_devices_ios_list_1": "از صفحه خانه،تنظیمات را فشار دهید.",
|
||||
"install_devices_ios_list_2": "وای فای را از منوی چپ انتخاب کنید (پیکربندی DNS دستی برای ارتباط موبایلی غیرممکن است).",
|
||||
"install_devices_ios_list_3": "روی نام شبکه فعال فعلی کلیک کنید.",
|
||||
"install_devices_ios_list_4": "در فیلد DNS آدرس سرور AdGuard Home را وارد کنید",
|
||||
"get_started": "شروع به کار",
|
||||
"next": "بعدی",
|
||||
@@ -413,7 +411,7 @@
|
||||
"access_blocked_title": "دامنه های مسدود شده",
|
||||
"access_blocked_desc": "این را با فیلتر ها به اشتباه نگیرید.AdGuard Home جستار DNS را با این دامنه ها در جستار سوال ها نمی پذیرد.",
|
||||
"access_settings_saved": "تنظیمات دسترسی با موفقیت ذخیره شد",
|
||||
"updates_checked": "بروز رسانی با موفقیت بررسی شد",
|
||||
"updates_checked": "نسخه جدیدی از AdGuard Home در دسترس است",
|
||||
"updates_version_equal": "AdGuard Home بروز است",
|
||||
"check_updates_now": "حالا بررسی برای بروز رسانی",
|
||||
"dns_privacy": "حریم خصوصی DNS",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Virheellinen palvelimen nimi",
|
||||
"form_error_subnet": "Aliverkko \"{{cidr}}\" ei sisällä IP-osoitetta \"{{ip}}\"",
|
||||
"form_error_positive": "Oltava suurempi kuin 0",
|
||||
"form_error_gateway_ip": "Lainalla ei voi olla yhdyskäytävän IP-osoitetta",
|
||||
"out_of_range_error": "Oltava alueen \"{{start}}\" - \"{{end}}\" ulkopuolella",
|
||||
"lower_range_start_error": "Oltava alueen aloitusarvoa pienempi",
|
||||
"greater_range_start_error": "Oltava alueen aloitusarvoa suurempi",
|
||||
@@ -70,7 +71,7 @@
|
||||
"dhcp_error": "AdGuard Home ei voinut tunnistaa, onko verkossa toista aktiivista DHCP-palvelinta",
|
||||
"dhcp_static_ip_error": "Jotta DHCP-palvelinta voidaan käyttää, on määritettävä kiinteä IP-osoite. AdGuard Home ei voinut tunnistaa, onko tälle verkkosovittimelle määritetty IP-osoite kiinteä. Määritä kiinteä IP-osoite itse.",
|
||||
"dhcp_dynamic_ip_found": "Järjestelmäsi käyttää verkkosovittimelle <0>{{interfaceName}}</0> dynaamista IP-osoitetta. Jotta voit käyttää DHCP-palvelinta, on sovittimelle määritettävä kiinteä IP-osoite. Nykyinen IP-osoitteesi on <0>{{ipAddress}}</0>. Tämä osoite määritetään automaattisesti kiinteäksi, jos painat \"Ota DHCP-palvelin käyttöön\" -painiketta.",
|
||||
"dhcp_lease_added": "Kiinteä laina \"{{key}}\" on lisätty",
|
||||
"dhcp_lease_added": "Kiinteä laina \"{{key}}\" lisättiin",
|
||||
"dhcp_lease_deleted": "Kiinteä laina \"{{key}}\" poistettiin",
|
||||
"dhcp_new_static_lease": "Uusi kiinteä laina",
|
||||
"dhcp_static_leases_not_found": "Kiinteitä DHCP-lainoja ei löytynyt",
|
||||
@@ -344,12 +345,12 @@
|
||||
"install_devices_macos_list_2": "Paina \"Verkko\".",
|
||||
"install_devices_macos_list_3": "Valitse listan ensimmäinen yhteys ja paina \"Lisävalinnat\".",
|
||||
"install_devices_macos_list_4": "Valitse DNS-välilehti ja syötä AdGuard Home -palvelimesi osoitteet.",
|
||||
"install_devices_android_list_1": "Napauta Android-laitteesi aloitusnäytöstä tai sovellusvalikosta \"Asetukset\".",
|
||||
"install_devices_android_list_2": "Napauta \"Yhteydet\" ja sitten \"Wi-Fi\". Näytetään kaikki käytettävissä olevat langattomat verkot (mobiiliverkolle ei ole mahdollista määrittää omaa DNS-palvelinta).",
|
||||
"install_devices_android_list_3": "Napauta yhdistetyn verkon vieressä olevaa asetuskuvaketta tai paina verkkoa pitkään ja valitse \"Muokkaa verkkoa\".",
|
||||
"install_devices_android_list_4": "Saatat joutua napauttamaan \"Lisäasetukset\" nähdäksesi lisää valintoja. Muuttaaksesi DNS-asetuksia, on \"IP-asetukset\" -kohdan \"DHCP\" -valinta vaihdettava \"Staattinen\" -valintaan.",
|
||||
"install_devices_android_list_1": "Paina Android-laitteesi aloitusnäytöstä tai sovellusvalikosta \"Asetukset\".",
|
||||
"install_devices_android_list_2": "Paina \"Yhteydet\" ja sitten \"Wi-Fi\". Kaikki käytettävissä olevat langattomat verkot näytetään (mobiiliverkolle ei ole mahdollista määrittää omaa DNS-palvelinta).",
|
||||
"install_devices_android_list_3": "Paina yhdistetyn verkon vieressä olevaa asetuskuvaketta tai paina verkkoa pitkään ja valitse \"Muokkaa verkkoa\".",
|
||||
"install_devices_android_list_4": "Saatat joutua painamaan \"Lisäasetukset\" nähdäksesi enemmän valintoja. Muuttaaksesi DNS-asetuksia, on \"IP-asetukset\" -kohdan \"DHCP\" -valinta vaihdettava \"Staattinen\" -valintaan.",
|
||||
"install_devices_android_list_5": "Syötä \"DNS 1\" ja \"DNS 2\" -kenttiin AdGuard Home -palvelimesi osoitteet.",
|
||||
"install_devices_ios_list_1": "Napauta aloitusnäytöstä \"Asetukset\".",
|
||||
"install_devices_ios_list_1": "Paina aloitusnäytöstä \"Asetukset\".",
|
||||
"install_devices_ios_list_2": "Valitse vasemmalta \"Wi-Fi\" (mobiiliverkolle ei ole mahdollista määrittää omaa DNS-palvelinta).",
|
||||
"install_devices_ios_list_3": "Valitse tällä hetkellä aktiivinen verkko.",
|
||||
"install_devices_ios_list_4": "Syötä \"DNS\" -kenttään AdGuard Home -palvelimesi osoitteet.",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Kielletyt verkkotunnukset",
|
||||
"access_blocked_desc": "Ei pidä sekoittaa suodattimiin. AdGuard Home hylkää näiden verkkotunnusten DNS-pyynnöt, eivätkä nämä pyynnöt näy edes pyyntöhistoriassa. Tähän voidaan syöttää tarkkoja verkkotunnuksia, jokerimerkkejä tai URL-suodatussääntöjä, kuten \"example.org\", \"*.example.org\" tai \"||example.org^\".",
|
||||
"access_settings_saved": "Käytön asetukset tallennettiin",
|
||||
"updates_checked": "Päivitykset tarkastettiin",
|
||||
"updates_checked": "Uusi versio AdGuard Home -ohjelmasta on saatavana\n",
|
||||
"updates_version_equal": "AdGuard Home on ajan tasalla",
|
||||
"check_updates_now": "Tarkista päivitykset nyt",
|
||||
"dns_privacy": "DNS-tietosuoja",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Nom de serveur invalide",
|
||||
"form_error_subnet": "Le sous-réseau « {{cidr}} » ne contient pas l'adresse IP « {{ip}} »",
|
||||
"form_error_positive": "Doit être supérieur à 0",
|
||||
"form_error_gateway_ip": "Le bail ne peut pas avoir d'adresse IP de la passerelle",
|
||||
"out_of_range_error": "Doit être hors plage « {{start}} » - « {{end}} »",
|
||||
"lower_range_start_error": "Doit être inférieur au début de plage",
|
||||
"greater_range_start_error": "Doit être supérieur au début de plage",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Domaines interdits",
|
||||
"access_blocked_desc": "A ne pas confondre avec les filtres. AdGuard Home rejette les requêtes DNS correspondant à ces domaines, et ces requêtes n'apparaissent même pas dans le journal des requêtes. Vous pouvez spécifier des noms de domaine exacts, des caractères génériques ou des règles de filtrage d'URL, par exemple « exemple.org », « *.exemple.org » ou « ||example.org^ » de manière correspondante.",
|
||||
"access_settings_saved": "Paramètres d'accès enregistrés avec succès",
|
||||
"updates_checked": "Mises à jour vérifiées",
|
||||
"updates_checked": "Une nouvelle version de AdGuard Home est disponible",
|
||||
"updates_version_equal": "AdGuard Home est à jour",
|
||||
"check_updates_now": "Vérifier les mises à jour",
|
||||
"dns_privacy": "Confidentialité DNS",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Nevažeće ime poslužitelja",
|
||||
"form_error_subnet": "Podmrežu \"{{cidr}}\" ne sadrži IP adresu \"{{ip}}\"",
|
||||
"form_error_positive": "Mora biti veće od 0",
|
||||
"form_error_gateway_ip": "Najam ne može imati IP adresu pristupnika",
|
||||
"out_of_range_error": "Mora biti izvan ranga \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Mora biti niže od početnog ranga",
|
||||
"greater_range_start_error": "Mora biti veće od krajnjeg ranga",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Nedopuštene domene",
|
||||
"access_blocked_desc": "Ne smije se miješati s filterima. AdGuard Home ispušta DNS upite koji odgovaraju tim domenama, a ti se upiti čak i ne pojavljuju u zapisniku upita. Možete navesti točne nazive domena, zamjenske znakove ili pravila filtriranja URL-a, npr || example.org example.org. example.org^\" u skladu s tim.",
|
||||
"access_settings_saved": "Postavke pristupa su uspješno spremljene",
|
||||
"updates_checked": "Uspješna provjera ažuriranja",
|
||||
"updates_checked": "Dostupna je nova verzija AdGuard Home-a",
|
||||
"updates_version_equal": "AdGuard Home je ažuriran",
|
||||
"check_updates_now": "Provjeri ažuriranja sada",
|
||||
"dns_privacy": "DNS privatnost",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Érvénytelen szervernév",
|
||||
"form_error_subnet": "A(z) \"{{cidr}}\" alhálózat nem tartalmazza a(z) \"{{ip}}\" IP címet",
|
||||
"form_error_positive": "0-nál nagyobbnak kell lennie",
|
||||
"form_error_gateway_ip": "A bérleti szerződés nem tartalmazhatja az átjáró IP-címét",
|
||||
"out_of_range_error": "A következő tartományon kívül legyen: \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Kisebb legyen, mint a tartomány kezdete",
|
||||
"greater_range_start_error": "Nagyobbnak kell lennie, mint a tartomány kezdete",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Nem engedélyezett domainek",
|
||||
"access_blocked_desc": "Ne keverje össze ezt a szűrőkkel. Az AdGuard Home az összes DNS kérést el fogja dobni, ami ezekkel a domainekkel megegyezik, és ezek a lekérések nem is fognak megjelenni a lekérdezési naplóban sem. Megadhatja a pontos domain neveket, a helyettesítő karaktereket vagy az URL szűrési szabályokat, pl. ennek megfelelően \"example.org\", \"*.example.org\", vagy \"||example.org^\".",
|
||||
"access_settings_saved": "A hozzáférési beállítások sikeresen mentésre kerültek",
|
||||
"updates_checked": "A frissítések sikeresen ellenőrizve lettek",
|
||||
"updates_checked": "Elérhető az AdGuard Home új verziója",
|
||||
"updates_version_equal": "Az AdGuard Home naprakész",
|
||||
"check_updates_now": "Frissítések ellenőrzése most",
|
||||
"dns_privacy": "DNS Adatvédelem",
|
||||
|
||||
@@ -445,7 +445,7 @@
|
||||
"access_blocked_title": "Domain yang diblokir",
|
||||
"access_blocked_desc": "Jangan bingung dengan filter. AdGuard Home menghapus kueri DNS yang cocok dengan domain ini, dan kueri ini bahkan tidak muncul di log kueri. Anda dapat menentukan nama domain, karakter pengganti, atau aturan filter URL yang tepat, mis. \"example.org\", \"*.example.org\", atau \"||example.org^\" yang sesuai.",
|
||||
"access_settings_saved": "Pengaturan akses berhasil disimpan",
|
||||
"updates_checked": "Pembaruan berhasil dicek",
|
||||
"updates_checked": "Versi baru AdGuard Home tersedia\n",
|
||||
"updates_version_equal": "AdGuard Home sudah tebaru",
|
||||
"check_updates_now": "Periksa pembaruan sekarang",
|
||||
"dns_privacy": "DNS Privasi",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Nome server non valido",
|
||||
"form_error_subnet": "Il subnet \"{{cidr}}\" non contiene l'indirizzo IP \"{{ip}}\"",
|
||||
"form_error_positive": "Deve essere maggiore di 0",
|
||||
"form_error_gateway_ip": "Il leasing non può avere l'indirizzo IP del gateway",
|
||||
"out_of_range_error": "Deve essere fuori intervallo \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Deve essere inferiore dell'intervallo di inizio",
|
||||
"greater_range_start_error": "Deve essere maggiore dell'intervallo di inizio",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Domini bloccati",
|
||||
"access_blocked_desc": "Da non confondere con i filtri. AdGuard Home eliminerà le richieste DNS corrispondenti a questi domini e queste richieste non verranno visualizzate nel relativo registro. Puoi specificare nomi di dominio esatti, caratteri jolly o regole di filtraggio URL, ad esempio \"esempio.org\", \"*.esempio.org\" o \"||esempio.org^\".",
|
||||
"access_settings_saved": "Impostazioni di accesso salvate correttamente",
|
||||
"updates_checked": "Verifica aggiornamenti riuscita",
|
||||
"updates_checked": "Nuova versione di AdGuard Home è disponibile",
|
||||
"updates_version_equal": "AdGuard Home è aggiornato",
|
||||
"check_updates_now": "Ricerca aggiornamenti ora",
|
||||
"dns_privacy": "Privacy DNS",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "サーバー名が無効です",
|
||||
"form_error_subnet": "IPアドレス「{{ip}}」がサブネット「{{cidr}}」に含まれていません",
|
||||
"form_error_positive": "0より大きい値でなければなりません",
|
||||
"form_error_gateway_ip": "リースはゲートウェイのIPアドレスになっていることができません",
|
||||
"out_of_range_error": "\"{{start}}\"〜\"{{end}}\" の範囲外である必要があります",
|
||||
"lower_range_start_error": "範囲開始よりも低い値である必要があります",
|
||||
"greater_range_start_error": "範囲開始値より大きい値でなければなりません",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "拒否するドメイン",
|
||||
"access_blocked_desc": "こちらをフィルタと混同しないでください。AdGuard Homeは、ここで入力されたドメインに一致するDNSクエリをドロップし、そういったクエリはクエリログにも表示されません。ここでは、「example.org」、「*.example.org」、「 ||example.org^ 」など、特定のドメイン名、ワイルドカード、URLフィルタルールを入力できます。",
|
||||
"access_settings_saved": "アクセス設定の保存に成功しました",
|
||||
"updates_checked": "アップデートの確認に成功しました",
|
||||
"updates_checked": "AdGuard Homeの新バージョンが利用可能です。",
|
||||
"updates_version_equal": "AdGuard Homeは既に最新です",
|
||||
"check_updates_now": "今すぐアップデートを確認する",
|
||||
"dns_privacy": "DNSプライバシー",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "유효하지 않은 서버 이름",
|
||||
"form_error_subnet": "서브넷 \"{{cidr}}\"에 \"{{ip}}\" IP 주소가 없습니다",
|
||||
"form_error_positive": "0보다 커야 합니다",
|
||||
"form_error_gateway_ip": "임대는 게이트웨이의 IP 주소를 가질 수 없습니다",
|
||||
"out_of_range_error": "\"{{start}}\"-\"{{end}}\" 범위 밖이어야 합니다",
|
||||
"lower_range_start_error": "범위 시작보다 작은 값이어야 합니다",
|
||||
"greater_range_start_error": "범위 시작보다 큰 값이어야 합니다",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "차단된 도메인",
|
||||
"access_blocked_desc": "이 기능을 필터와 혼동하지 마세요. AdGuard Home은 이 도메인에 대한 DNS 요청을 무시합니다. 여기에서는 'example.org' '*. example.org', '|| example.org ^'와 같은 특정 도메인 이름, 와일드 카드, URL 필터 규칙을 지정할 수 있습니다.",
|
||||
"access_settings_saved": "액세스 설정이 성공적으로 저장되었습니다.",
|
||||
"updates_checked": "업데이트가 성공적으로 확인되었습니다",
|
||||
"updates_checked": "AdGuard Home의 새 버전을 사용할 수 있습니다",
|
||||
"updates_version_equal": "AdGuard Home 최신 상태입니다.",
|
||||
"check_updates_now": "지금 업데이트 확인",
|
||||
"dns_privacy": "DNS 프라이버시",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Ongeldige servernaam",
|
||||
"form_error_subnet": "Subnet “{{cidr}}” bevat niet het IP-adres “{{ip}}”",
|
||||
"form_error_positive": "Moet groter zijn dan 0",
|
||||
"form_error_gateway_ip": "Lease kan niet het IP-adres van de gateway hebben",
|
||||
"out_of_range_error": "Moet buiten bereik zijn \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Moet lager zijn dan begin reeks",
|
||||
"greater_range_start_error": "Moet groter zijn dan begin reeks",
|
||||
@@ -408,7 +409,7 @@
|
||||
"manual_update": "<a>Volg deze stappen</a> om handmatig bij te werken.",
|
||||
"processing_update": "Even geduld, AdGuard Home wordt bijgewerkt",
|
||||
"clients_title": "Permanente clients",
|
||||
"clients_desc": "Permanente client-records configureren voor apparaten verboden met AdGuard Home",
|
||||
"clients_desc": "Permanente client-records configureren voor apparaten verbonden met AdGuard Home",
|
||||
"settings_global": "Globaal",
|
||||
"settings_custom": "Aangepast",
|
||||
"table_client": "Gebruiker",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Niet toegelaten domeinen",
|
||||
"access_blocked_desc": "Verwar dit niet met filters. AdGuard Home zal deze DNS-zoekopdrachten niet uitvoeren die deze domeinen in de zoekopdracht bevatten. Hier kan je de exacte domeinnamen, wildcards en URL-filter-regels specifiëren, bijv. \"example.org\", \"*.example.org\" of \"||example.org^\".",
|
||||
"access_settings_saved": "Toegangsinstellingen succesvol opgeslagen",
|
||||
"updates_checked": "Met succes op updates gecontroleerd",
|
||||
"updates_checked": "Een nieuwe versie van AdGuard Home is beschikbaar\n",
|
||||
"updates_version_equal": "AdGuard Home is actueel",
|
||||
"check_updates_now": "Controleer op updates",
|
||||
"dns_privacy": "DNS Privacy",
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
"stats_adult": "Blokkerte voksennettsteder",
|
||||
"stats_query_domain": "Mest forespurte domener",
|
||||
"for_last_24_hours": "de siste 24 timene",
|
||||
"for_last_days": "det siste døgnet",
|
||||
"for_last_days": "for den siste {{count}} dagen",
|
||||
"for_last_days_plural": "de siste {{count}} dagene",
|
||||
"stats_disabled": "Statistikkene har blitt skrudd av. Du kan skru den på fra <0>innstillingssiden</0>.",
|
||||
"stats_disabled_short": "Statistikkene har blitt skrudd av",
|
||||
@@ -114,7 +114,7 @@
|
||||
"top_clients": "Vanligste klienter",
|
||||
"no_clients_found": "Ingen klienter ble funnet",
|
||||
"general_statistics": "Generelle statistikker",
|
||||
"number_of_dns_query_days": "Antall DNS-forespørsler som ble behandlet det siste døgnet",
|
||||
"number_of_dns_query_days": "Antall DNS-spørringer behandlet for de siste {{count}} dagene",
|
||||
"number_of_dns_query_days_plural": "Antall DNS-forespørsler som ble behandlet de siste {{count}} dagene",
|
||||
"number_of_dns_query_24_hours": "Antall DNS-forespørsler som ble behandlet de siste 24 timene",
|
||||
"number_of_dns_query_blocked_24_hours": "Antall DNS-forespørsler som ble blokkert av adblock-filtre, hosts-lister, og domene-lister",
|
||||
@@ -429,7 +429,7 @@
|
||||
"access_blocked_title": "Blokkerte domener",
|
||||
"access_blocked_desc": "Ikke forveksle dette med filtre. AdGuard Home vil nekte å behandle DNS-forespørsler som har disse domenene, og disse forespørslene dukker ikke engang opp i forespørselsloggen. Du kan spesifisere nøyaktige domene navn, jokertegn, eller URL-filterregler, f.eks. «example.org», «*.example.log» eller «||example.org^» derav.",
|
||||
"access_settings_saved": "Tilgangsinnstillingene ble vellykket lagret",
|
||||
"updates_checked": "Oppdateringene ble vellykket sett etter",
|
||||
"updates_checked": "En ny versjon av AdGuard Home er tilgjengelig",
|
||||
"updates_version_equal": "AdGuard Home er fullt oppdatert",
|
||||
"check_updates_now": "Se etter oppdateringer nå",
|
||||
"dns_privacy": "DNS-privatliv",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Nieprawidłowa nazwa serwera",
|
||||
"form_error_subnet": "Podsieć \"{{cidr}}\" nie zawiera adresu IP \"{{ip}}\"",
|
||||
"form_error_positive": "Musi być większa niż 0",
|
||||
"form_error_gateway_ip": "Lease nie może mieć adresu IP bramy",
|
||||
"out_of_range_error": "Musi być spoza zakresu \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Musi być niższy niż początek zakresu",
|
||||
"greater_range_start_error": "Musi być większy niż początek zakresu",
|
||||
@@ -117,7 +118,7 @@
|
||||
"stats_adult": "Zablokowane witryny dla dorosłych",
|
||||
"stats_query_domain": "Najczęściej wyszukiwane domeny",
|
||||
"for_last_24_hours": "przez ostatnie 24 godziny",
|
||||
"for_last_days": "z ostatniego dnia",
|
||||
"for_last_days": "za ostatni dzień {{count}}",
|
||||
"for_last_days_plural": "z ostatnich {{count}} dni",
|
||||
"stats_disabled": "Statystyki zostały wyłączone. Można je włączyć na <0>stronie ustawień</0>.",
|
||||
"stats_disabled_short": "Statystyki zostały wyłączone",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Niedozwolone domeny",
|
||||
"access_blocked_desc": "Nie należy ich mylić z filtrami. AdGuard Home usuwa zapytania DNS pasujące do tych domen, a zapytania te nie pojawiają się nawet w dzienniku zapytań. Możesz określić dokładne nazwy domen, symbole wieloznaczne lub reguły filtrowania adresów URL, np. \"example.org\", \"*.example.org\" lub \"||example.org^\".",
|
||||
"access_settings_saved": "Ustawienia dostępu zostały pomyślnie zapisane",
|
||||
"updates_checked": "Aktualizacje pomyślnie sprawdzone",
|
||||
"updates_checked": "Dostępna jest nowa wersja programu AdGuard Home\n",
|
||||
"updates_version_equal": "AdGuard Home jest aktualny",
|
||||
"check_updates_now": "Sprawdź aktualizacje teraz",
|
||||
"dns_privacy": "Prywatny DNS",
|
||||
|
||||
@@ -445,7 +445,7 @@
|
||||
"access_blocked_title": "Domínios bloqueados",
|
||||
"access_blocked_desc": "Não deve ser confundido com filtros. O AdGuard Home elimina as consultas DNS que correspondem a esses domínios, e essas consultas nem aparecem no registro de consultas. Você pode especificar nomes de domínio exatos, caracteres curinga ou regras de filtro de URL, por exemplo \"exemplo.org\", \"*.exemplo.org\", ou \"||exemplo.org^\" correspondentemente.",
|
||||
"access_settings_saved": "Configurações de acesso foram salvas com sucesso",
|
||||
"updates_checked": "Atualizações verificadas com sucesso",
|
||||
"updates_checked": "Uma nova versão do AdGuard Home está disponível\n",
|
||||
"updates_version_equal": "O AdGuard Home está atualizado.",
|
||||
"check_updates_now": "Verificar atualizações",
|
||||
"dns_privacy": "Privacidade de DNS",
|
||||
|
||||
@@ -445,7 +445,7 @@
|
||||
"access_blocked_title": "Domínios bloqueados",
|
||||
"access_blocked_desc": "Não deve ser confundido com filtros. O AdGuard Home elimina as consultas DNS que correspondem a esses domínios, e essas consultas nem aparecem no registro de consultas. Você pode especificar nomes de domínio exatos, caracteres curinga ou regras de filtro de URL, por exemplo \"exemplo.org\", \"*.exemplo.org\", ou \"||exemplo.org^\" correspondentemente.",
|
||||
"access_settings_saved": "Definições de acesso foram guardadas com sucesso",
|
||||
"updates_checked": "Atualizações verificadas com sucesso",
|
||||
"updates_checked": "Uma nova versão do AdGuard Home está disponível\n",
|
||||
"updates_version_equal": "O AdGuard Home está atualizado",
|
||||
"check_updates_now": "Verificar atualizações",
|
||||
"dns_privacy": "Privacidade de DNS",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Nume de server nevalid",
|
||||
"form_error_subnet": "Subrețeaua „{{cidr}}” nu conține adresa IP „{{ip}}”",
|
||||
"form_error_positive": "Trebuie să fie mai mare de 0",
|
||||
"form_error_gateway_ip": "Locația nu poate avea adresa IP a gateway-ului",
|
||||
"out_of_range_error": "Trebuie să fie în afara intervalului „{{start}}”-„{{end}}”",
|
||||
"lower_range_start_error": "Trebuie să fie mai mică decât începutul intervalului",
|
||||
"greater_range_start_error": "Trebuie să fie mai mare decât începutul intervalului",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Domenii blocate",
|
||||
"access_blocked_desc": "A nu se confunda cu filtrele. AdGuard Home respinge cererile DNS pentru aceste domenii, iar aceste cereri nici măcar nu apar în jurnalul de solicitări. Puteți specifica nume exacte de domenii, metacaractere sau reguli de filtrare URL, cum ar fi \"example.org\", \"*.exemple.org\" sau \"||example.org^\" în mod corespunzător.",
|
||||
"access_settings_saved": "Setările de acces au fost salvate cu succes",
|
||||
"updates_checked": "Actualizările au fost verificate cu succes",
|
||||
"updates_checked": "Este disponibilă o nouă versiune de AdGuard Home\n",
|
||||
"updates_version_equal": "AdGuard Home este la zi",
|
||||
"check_updates_now": "Verificați actualizările acum",
|
||||
"dns_privacy": "Confidențialitate DNS",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Некорректное имя сервера",
|
||||
"form_error_subnet": "Подсеть «{{cidr}}» не содержит IP-адрес «{{ip}}»",
|
||||
"form_error_positive": "Должно быть больше 0",
|
||||
"form_error_gateway_ip": "Аренда не может иметь IP-адрес шлюза",
|
||||
"out_of_range_error": "Должно быть вне диапазона «{{start}}»-«{{end}}»",
|
||||
"lower_range_start_error": "Должно быть меньше начала диапазона",
|
||||
"greater_range_start_error": "Должно быть больше начала диапазона",
|
||||
@@ -66,7 +67,7 @@
|
||||
"ip": "IP-адрес",
|
||||
"dhcp_table_hostname": "Имя хоста",
|
||||
"dhcp_table_expires": "Истекает",
|
||||
"dhcp_warning": "Если вы все равно хотите включить DHCP-сервер, убедитесь, что в сети больше нет активных DHCP-серверов. Иначе это может сломать доступ в сеть для подключённых устройств!",
|
||||
"dhcp_warning": "Если вы всё равно хотите включить DHCP-сервер, убедитесь, что в сети больше нет активных DHCP-серверов. Иначе это может сломать доступ в сеть для подключённых устройств!",
|
||||
"dhcp_error": "AdGuard Home не смог определить присутствие других DHCP-серверов в сети",
|
||||
"dhcp_static_ip_error": "Чтобы использовать DHCP-сервер, должен быть установлен статический IP-адрес. AdGuard Home не смог определить, использует ли этот сетевой интерфейс статический IP-адрес. Пожалуйста, установите его вручную.",
|
||||
"dhcp_dynamic_ip_found": "Ваша система использует динамический IP-адрес для интерфейса <0>{{interfaceName}}</0>. Чтобы использовать DHCP-сервер, необходимо установить статический IP-адрес. Ваш текущий IP-адрес – <0>{{ipAddress}}</0>. Мы автоматически установим его как статический, если вы нажмёте кнопку «Включить DHCP-сервер».",
|
||||
@@ -163,8 +164,8 @@
|
||||
"apply_btn": "Применить",
|
||||
"disabled_filtering_toast": "Фильтрация выкл.",
|
||||
"enabled_filtering_toast": "Фильтрация вкл.",
|
||||
"disabled_safe_browsing_toast": "Антифишинг отключен",
|
||||
"enabled_safe_browsing_toast": "Антифишинг включен",
|
||||
"disabled_safe_browsing_toast": "Антифишинг отключён",
|
||||
"enabled_safe_browsing_toast": "Антифишинг включён",
|
||||
"disabled_parental_toast": "Родительский контроль выкл.",
|
||||
"enabled_parental_toast": "Родительский контроль вкл.",
|
||||
"disabled_safe_search_toast": "Безопасный поиск выкл.",
|
||||
@@ -179,7 +180,7 @@
|
||||
"edit_table_action": "Редактировать",
|
||||
"delete_table_action": "Удалить",
|
||||
"elapsed": "Затрачено",
|
||||
"filters_and_hosts_hint": "AdGuard Home распознает базовые правила блокировки и синтаксис файлов hosts.",
|
||||
"filters_and_hosts_hint": "AdGuard Home распознаёт базовые правила блокировки и синтаксис файлов hosts.",
|
||||
"no_blocklist_added": "Чёрные списки не добавлены",
|
||||
"no_whitelist_added": "Белые списки не добавлены",
|
||||
"add_blocklist": "Добавить чёрный список",
|
||||
@@ -328,10 +329,10 @@
|
||||
"install_submit_title": "Поздравляем!",
|
||||
"install_submit_desc": "Настройка завершена, AdGuard Home готов к использованию.",
|
||||
"install_devices_router": "Роутер",
|
||||
"install_devices_router_desc": "Эта настройка покроет все устройства, подключенные к вашему домашнему роутеру, и вам не нужно будет настраивать каждое вручную.",
|
||||
"install_devices_router_desc": "Эта настройка покроет все устройства, подключённые к вашему домашнему роутеру, и вам не нужно будет настраивать каждое вручную.",
|
||||
"install_devices_address": "DNS-сервер AdGuard Home доступен по следующим адресам",
|
||||
"install_devices_router_list_1": "Откройте настройки вашего роутера. Обычно вы можете открыть их в вашем браузере, например, http://192.168.0.1/ или http://192.168.1.1/. Вас могут попросить ввести пароль. Если вы не помните его, пароль часто можно сбросить, нажав на кнопку на самом роутере, но помните, что эта процедура может привести к потере всей конфигурации роутера. Если вашему роутеру необходимо приложение для настройки, установите его на свой телефон или ПК и воспользуйтесь им для настройки роутера.",
|
||||
"install_devices_router_list_2": "Найдите настройки DHCP или DNS. Найдите буквы «DNS» рядом с текстовым полем, в которое можно ввести два или три ряда цифр, разделенных на 4 группы от одной до трёх цифр.",
|
||||
"install_devices_router_list_2": "Найдите настройки DHCP или DNS. Найдите буквы «DNS» рядом с текстовым полем, в которое можно ввести два или три ряда цифр, разделённых на 4 группы от одной до трёх цифр.",
|
||||
"install_devices_router_list_3": "Введите туда адрес вашего AdGuard Home.",
|
||||
"install_devices_router_list_4": "Вы не можете установить собственный DNS-сервер на некоторых типах маршрутизаторов. В этом случае может помочь настройка AdGuard Home в качестве <0>DHCP-сервера</0>. В противном случае вам следует обратиться к руководству по настройке DNS-серверов для вашей конкретной модели маршрутизатора.",
|
||||
"install_devices_windows_list_1": "Откройте Панель управления через меню «Пуск» или через поиск Windows.",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Неразрешённые домены",
|
||||
"access_blocked_desc": "Не путать с фильтрами. AdGuard Home будет игнорировать DNS-запросы с этими доменами. Здесь вы можете уточнить точные имена доменов, шаблоны, правила URL-фильтрации, например, «example.org», «*.example.org» или «||example.org».",
|
||||
"access_settings_saved": "Настройки доступа успешно сохранены",
|
||||
"updates_checked": "Проверка обновлений прошла успешно",
|
||||
"updates_checked": "Доступна новая версия AdGuard Home",
|
||||
"updates_version_equal": "Версия AdGuard Home актуальна",
|
||||
"check_updates_now": "Проверить обновления",
|
||||
"dns_privacy": "Зашифрованный DNS",
|
||||
@@ -589,7 +590,7 @@
|
||||
"validated_with_dnssec": "Подтверждено с помощью DNSSEC",
|
||||
"all_queries": "Все запросы",
|
||||
"show_blocked_responses": "Заблокировано",
|
||||
"show_whitelisted_responses": "В белом списке",
|
||||
"show_whitelisted_responses": "Разрешённые",
|
||||
"show_processed_responses": "Обработан",
|
||||
"blocked_safebrowsing": "Заблокировано согласно базе данных Safe Browsing",
|
||||
"blocked_adult_websites": "Заблокировано Родительским контролем",
|
||||
|
||||
@@ -335,7 +335,7 @@
|
||||
"encryption_dot": "TLS-හරහා-ව.නා.ප. තොට",
|
||||
"encryption_dot_desc": "මෙම තොට වින්යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම කවුළුව හරහා TLS-හරහා-ව.නා.ප. සේවාදායකයක් ධාවනය කරනු ඇත.",
|
||||
"encryption_doq": "QUIC-හරහා-ව.නා.ප. තොට",
|
||||
"encryption_doq_desc": "මෙම තොට වින්යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම තොට හරහා QUIC-හරහා-ව.නා.ප. සේවාදායකයක් ධාවනය කරනු ඇත. එය පරීක්ෂාත්මක වන අතර විශ්වාසදායක නොවිය හැකිය. එසේම, මේ වන විට එයට සහාය දක්වන බොහෝ අනුග්රාහක නැත.",
|
||||
"encryption_doq_desc": "මෙම තොට වින්යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් මෙම තොට හරහා QUIC-හරහා-ව.නා.ප. සේවාදායකයක් ධාවනය කරනු ඇත.",
|
||||
"encryption_certificates": "සහතික",
|
||||
"encryption_certificates_input": "ඔබගේ PEM-කේතනය කළ සහතික පිටපත් කර මෙහි අලවන්න.",
|
||||
"encryption_status": "තත්වය",
|
||||
@@ -406,7 +406,7 @@
|
||||
"access_disallowed_desc": "CIDR හෝ අ.ජා. කෙ. ලිපින ලැයිස්තුවක් වින්යාසගත කර ඇත්නම්, ඇඩ්ගාර්ඩ් හෝම් විසින් එම අ.ජා. කෙ. ලිපින වලින් ඉල්ලීම් අත්හරිනු ඇත.",
|
||||
"access_blocked_title": "නොඉඩ ලත් වසම්",
|
||||
"access_settings_saved": "ප්රවේශ වීමේ සැකසුම් සාර්ථකව සුරකින ලදි",
|
||||
"updates_checked": "යාවත්කාල සාර්ථකව පරික්ෂා කෙරිණි",
|
||||
"updates_checked": "ඇඩ්ගාර්ඩ් හෝම් හි නව අනුවාදයක් තිබේ",
|
||||
"updates_version_equal": "ඇඩ්ගාර්ඩ් හෝම් යාවත්කාලීනයි",
|
||||
"check_updates_now": "දැන් යාවත්කාල පරීක්ෂා කරන්න",
|
||||
"dns_privacy": "ව.නා.ප. රහස්යතා",
|
||||
@@ -414,12 +414,12 @@
|
||||
"setup_dns_privacy_android_2": "<1>HTTPS-හරහා-ව.නා.ප.</1> සහ <1>TLS-හරහා-ව.නා.ප.</1> සඳහා <0>ඇන්ඩ්රොයිඩ් සඳහා ඇඩ්ගාර්ඩ්</0> සහාය දක්වයි.",
|
||||
"setup_dns_privacy_other_title": "වෙනත් ක්රියාවට නැංවූ දෑ",
|
||||
"setup_dns_privacy_other_2": "<0>ඩීඑන්එස්ප්රොක්සි</0> දන්නා සියලුම ආරක්ෂිත ව.නා.ප. කෙටුම්පත් සඳහා සහාය දක්වයි.",
|
||||
"setup_dns_privacy_other_3": "<1>DNS-over-HTTPS</1> සඳහා <0>dnscrypt-පෙරකලාසිය</0> සහාය දක්වයි.",
|
||||
"setup_dns_privacy_other_4": "<1>DNS-over-HTTPS</1> සඳහා <0>මොසිල්ලා ෆයර්ෆොක්ස්</0> සහාය දක්වයි.",
|
||||
"setup_dns_privacy_other_5": "<0>මෙහි</0> සහ <1>මෙහි</1> තවත් ක්රියාවට නැංවූ දෑ ඔබට හමුවනු ඇත.",
|
||||
"setup_dns_privacy_ioc_mac": "අයිඕඑස් සහ මැක්ඕඑස් වින්යාසය",
|
||||
"setup_dns_notice": "ඔබට <1>DNS-over-HTTPS</1> හෝ <1>DNS-over-TLS</1> භාවිතයට ඇඩ්ගාර්ඩ් හෝම් සැකසුම් තුළ <0>සංකේතනය වින්යාසගත</0> කිරීමට ඇවැසිය.",
|
||||
"rewrite_added": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම සාර්ථකව එකතු කෙරිණි",
|
||||
"rewrite_deleted": "\"{{key}}\" සඳහා ව. නා. ප. නැවත ලිවීම සාර්ථකව ඉවත් කෙරිණි",
|
||||
"rewrite_add": "ව.නා.ප. නැවත ලිවීමක් එකතු කරන්න",
|
||||
"rewrite_not_found": "ව.නා.ප. නැවත ලිවීම් හමු නොවිණි",
|
||||
"rewrite_confirm_delete": "\"{{key}}\" සඳහා ව.නා.ප. නැවත ලිවීම ඉවත් කිරීමට අවශ්ය බව ඔබට විශ්වාසද?",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Neplatné meno servera",
|
||||
"form_error_subnet": "Podsieť \"{{cidr}}\" neobsahuje IP adresu \"{{ip}}\"",
|
||||
"form_error_positive": "Musí byť väčšie ako 0",
|
||||
"form_error_gateway_ip": "Prenájom nemôže mať IP adresu brány",
|
||||
"out_of_range_error": "Musí byť mimo rozsahu \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Musí byť nižšie ako začiatok rozsahu",
|
||||
"greater_range_start_error": "Musí byť väčšie ako začiatok rozsahu",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Nepovolené domény",
|
||||
"access_blocked_desc": "Nesmie byť zamieňaná s filtrami. AdGuard Home zruší DNS dopyty, ktoré sa zhodujú s týmito doménami, a tieto dopyty sa nezobrazia ani v denníku dopytov. Môžete určiť presné názvy domén, zástupné znaky alebo pravidlá filtrovania URL adries, napr. \"example.org\", \"*.example.org\" alebo ||example.org^\" zodpovedajúcim spôsobom.",
|
||||
"access_settings_saved": "Nastavenia prístupu úspešne uložené",
|
||||
"updates_checked": "Aktualizácie úspešne skontrolované",
|
||||
"updates_checked": "K dispozícii je nová verzia aplikácie AdGuard Home\n",
|
||||
"updates_version_equal": "AdGuard Home je aktuálny",
|
||||
"check_updates_now": "Skontrolovať aktualizácie teraz",
|
||||
"dns_privacy": "DNS súkromie",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Neveljavno ime strežnika",
|
||||
"form_error_subnet": "Podomrežje \"{{cidr}}\" ne vsebuje naslova IP \"{{ip}}\"",
|
||||
"form_error_positive": "Mora biti večja od 0",
|
||||
"form_error_gateway_ip": "Najem ne more imeti naslova IP prehoda",
|
||||
"out_of_range_error": "Mora biti izven razpona \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Mora biti manjši od začetka razpona",
|
||||
"greater_range_start_error": "Mora biti večji od začetka razpona",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Prepovedane domene",
|
||||
"access_blocked_desc": "Ne gre zamenjati s filtri. AdGuard Home spusti poizvedbe DNS, ki se ujemajo s temi domenami, in te poizvedbe se niti ne pojavijo v dnevniku poizvedb. Določite lahko natančna imena domen, nadomestne znake ali pravila filtriranja URL-jev, npr. ustrezno \"example.org\", \"*.example.org\" ali \"|| example.org ^\".",
|
||||
"access_settings_saved": "Nastavitve dostopa so uspešno shranjene",
|
||||
"updates_checked": "Posodobitve so uspešno preverjene",
|
||||
"updates_checked": "Na voljo je nova različica programa AdGuard Home\n",
|
||||
"updates_version_equal": "AdGuard Home je posodobljen",
|
||||
"check_updates_now": "Preveri obstoj posodobitev zdaj",
|
||||
"dns_privacy": "Zasebnost DNS",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Nevažeće ime servera",
|
||||
"form_error_subnet": "Subnet \"{{cidr}}\" ne sadrži IP adresu \"{{ip}}\"",
|
||||
"form_error_positive": "Mora biti veće od 0",
|
||||
"form_error_gateway_ip": "Zakup ne može imati IP adresu mrežnog prolaza",
|
||||
"out_of_range_error": "Mora biti izvan opsega \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Mora biti manje od početnog opsega",
|
||||
"greater_range_start_error": "Mora biti veće od početnog opsega",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Blokirani domeni",
|
||||
"access_blocked_desc": "Da ne bude zabune sa filterima. AdGuard Home odustaje od DNS upita koji se podudaraju sa ovim domenima, a ovi upiti se čak i ne pojavljuju u evidenciji upita. Možete da navedete tačna imena domena, džoker znakove ili pravila URL filtera, npr. \"example.org\", \"*.example.org\" ili \"|| example.org^\" dopisno.",
|
||||
"access_settings_saved": "Postavke pristupa su uspešno sačuvane",
|
||||
"updates_checked": "Ažuriranja su uspešno proverena",
|
||||
"updates_checked": "Dostupna je nova verzija AdGuard Home-a",
|
||||
"updates_version_equal": "AdGuard Home je ažuriran na najnoviju verziju",
|
||||
"check_updates_now": "Proveri da li postoje ispravke",
|
||||
"dns_privacy": "DNS privatnost",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Ogiltigt servernamn",
|
||||
"form_error_subnet": "Subnätet \"{{cidr}}\" innehåller inte IP-adressen \"{{ip}}\"",
|
||||
"form_error_positive": "Måste vara större än noll",
|
||||
"form_error_gateway_ip": "Lease kan inte ha IP-adressen för gatewayen",
|
||||
"out_of_range_error": "Måste vara utanför intervallet \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Måste vara lägre än starten på intervallet",
|
||||
"greater_range_start_error": "Måste vara högre än starten på intervallet",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Blockerade domäner",
|
||||
"access_blocked_desc": "Ej att förväxla med filter. AdGuard Home kastar DNS-frågor som matchar dessa domäner, och dessa frågor visas inte ens i frågeloggen. Du kan ange exakta domännamn, jokertecken eller URL-filterregler, t.ex. \"example.org\", \"*.example.org\" eller \"||example.org^\" på motsvarande sätt.",
|
||||
"access_settings_saved": "Åtkomstinställningar sparade",
|
||||
"updates_checked": "Sökning efter uppdateringar genomförd",
|
||||
"updates_checked": "En ny version av AdGuard Home är tillgänglig\n",
|
||||
"updates_version_equal": "AdGuard Home är uppdaterat",
|
||||
"check_updates_now": "Sök efter uppdateringar nu",
|
||||
"dns_privacy": "DNS-Integritet",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Geçersiz sunucu adı",
|
||||
"form_error_subnet": "\"{{cidr}}\" alt ağı, \"{{ip}}\" IP adresini içermiyor",
|
||||
"form_error_positive": "0'dan büyük olmalıdır",
|
||||
"form_error_gateway_ip": "Kiralama, ağ geçidinin IP adresine sahip olamaz",
|
||||
"out_of_range_error": "\"{{start}}\"-\"{{end}}\" aralığının dışında olmalıdır",
|
||||
"lower_range_start_error": "Başlangıç aralığından daha düşük olmalıdır",
|
||||
"greater_range_start_error": "Başlangıç aralığından daha büyük olmalıdır",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "İzin verilmeyen alan adları",
|
||||
"access_blocked_desc": "Bu işlem filtrelerle ilgili değildir. AdGuard Home, bu alan adlarından gelen DNS sorgularını yanıtsız bırakır ve bu sorgular sorgu günlüğünde görünmez. Tam alan adlarını, joker karakterleri veya URL filtre kurallarını belirtebilirsiniz, ör. \"example.org\", \"*.example.org\" veya \"||example.org^\".",
|
||||
"access_settings_saved": "Erişim ayarları başarıyla kaydedildi!",
|
||||
"updates_checked": "Güncelleme kontrolü başarılı",
|
||||
"updates_checked": "AdGuard Home'un yeni bir sürümü mevcut",
|
||||
"updates_version_equal": "AdGuard Home yazılımı güncel durumda",
|
||||
"check_updates_now": "Güncellemeleri şimdi denetle",
|
||||
"dns_privacy": "DNS Gizliliği",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "Неправильна назва сервера",
|
||||
"form_error_subnet": "Підмережа «{{cidr}}» не містить IP-адресу «{{ip}}»",
|
||||
"form_error_positive": "Повинно бути більше за 0",
|
||||
"form_error_gateway_ip": "Оренда не може мати IP-адресу шлюзу",
|
||||
"out_of_range_error": "Не повинна бути в діапазоні «{{start}}»−«{{end}}»",
|
||||
"lower_range_start_error": "Має бути меншим за початкову адресу",
|
||||
"greater_range_start_error": "Має бути більшим за початкову адресу",
|
||||
@@ -77,7 +78,7 @@
|
||||
"dhcp_add_static_lease": "Додати статичну оренду",
|
||||
"dhcp_reset_leases": "Скинути всі аренди",
|
||||
"dhcp_reset_leases_confirm": "Ви дійсно хочете скинути усі аренди?",
|
||||
"dhcp_reset_leases_success": "Аренди DHCP успішно скинуто",
|
||||
"dhcp_reset_leases_success": "Оренду DHCP успішно скинуто",
|
||||
"dhcp_reset": "Ви дійсно хочете скинути DHCP-конфігурацію?",
|
||||
"country": "Країна",
|
||||
"city": "Місто",
|
||||
@@ -105,7 +106,7 @@
|
||||
"copyright": "Авторське право",
|
||||
"homepage": "Домашня сторінка",
|
||||
"report_an_issue": "Повідомити про проблему",
|
||||
"privacy_policy": "Політика приватності",
|
||||
"privacy_policy": "Політика конфіденційності",
|
||||
"enable_protection": "Увімкнути захист",
|
||||
"enabled_protection": "Захист увімкнено",
|
||||
"disable_protection": "Вимкнути захист",
|
||||
@@ -218,7 +219,7 @@
|
||||
"example_upstream_tcp": "звичайний DNS (через TCP);",
|
||||
"example_upstream_tcp_hostname": "звичайний DNS (поверх TCP, з назвою вузла);",
|
||||
"all_lists_up_to_date_toast": "Всі списки вже оновлені",
|
||||
"updated_upstream_dns_toast": "DNS-сервери оновлено",
|
||||
"updated_upstream_dns_toast": "DNS-сервери успішно збережено",
|
||||
"dns_test_ok_toast": "Вказані DNS сервери працюють правильно",
|
||||
"dns_test_not_ok_toast": "Сервер «{{key}}»: неможливо використати. Перевірте правильність введення",
|
||||
"unblock": "Дозволити",
|
||||
@@ -245,7 +246,7 @@
|
||||
"loading_table_status": "Завантаження...",
|
||||
"page_table_footer_text": "Сторінка",
|
||||
"rows_table_footer_text": "рядків",
|
||||
"updated_custom_filtering_toast": "Власні правила фільтрування збережено",
|
||||
"updated_custom_filtering_toast": "Власні правила фільтрування успішно збережено",
|
||||
"rule_removed_from_custom_filtering_toast": "Правило вилучено з власних правил фільтрування: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Правило додано до власних правил фільтрування: {{rule}}",
|
||||
"query_log_response_status": "Стан: {{value}}",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "Заборонені домени",
|
||||
"access_blocked_desc": "Не плутайте з фільтрами. AdGuard Home буде ігнорувати DNS-запити з цими доменами, такі запити навіть не будуть записані до журналу. Ви можете вказати точні доменні імена, замінні знаки та правила фільтрування URL-адрес, наприклад, 'example.org', '*.example.org' або '||example.org^' відповідно.",
|
||||
"access_settings_saved": "Налаштування доступу успішно збережено",
|
||||
"updates_checked": "Оновлення успішно перевірені",
|
||||
"updates_checked": "Доступна нова версія AdGuard Home",
|
||||
"updates_version_equal": "AdGuard Home останньої версії",
|
||||
"check_updates_now": "Перевірити наявність оновлень",
|
||||
"dns_privacy": "Конфіденційність DNS",
|
||||
@@ -514,7 +515,7 @@
|
||||
"statistics_clear": "Очистити статистику",
|
||||
"statistics_clear_confirm": "Ви впевнені, що хочете очистити статистику?",
|
||||
"statistics_retention_confirm": "Ви впевнені, що хочете змінити тривалість статистики? Якщо зменшити значення інтервалу, деякі дані будуть втрачені",
|
||||
"statistics_cleared": "Статистика успішно очищена",
|
||||
"statistics_cleared": "Статистику успішно очищено",
|
||||
"statistics_enable": "Увімкнути статистику",
|
||||
"interval_hours": "{{count}} година",
|
||||
"interval_hours_plural": "{{count}} годин(и)",
|
||||
@@ -591,7 +592,7 @@
|
||||
"show_blocked_responses": "Заблоковані",
|
||||
"show_whitelisted_responses": "Дозволені",
|
||||
"show_processed_responses": "Оброблені",
|
||||
"blocked_safebrowsing": "Заблоковано Безпечним переглядом",
|
||||
"blocked_safebrowsing": "Заблоковано модулем «Безпека перегляду»",
|
||||
"blocked_adult_websites": "Заблоковано «Батьківським контролем»",
|
||||
"blocked_threats": "Заблоковано загроз",
|
||||
"allowed": "Дозволено",
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
"all_lists_up_to_date_toast": "Tất cả danh sách đã ở phiên bản mới nhất",
|
||||
"updated_upstream_dns_toast": "Các máy chủ thượng nguồn đã được lưu thành công",
|
||||
"dns_test_ok_toast": "Máy chủ DNS có thể sử dụng",
|
||||
"dns_test_not_ok_toast": "Máy chủ \"\"': không thể sử dụng, vui lòng kiểm tra lại",
|
||||
"dns_test_not_ok_toast": "Máy chủ \"{{key}}\"': không thể sử dụng, vui lòng kiểm tra lại",
|
||||
"unblock": "Bỏ chặn",
|
||||
"block": "Chặn",
|
||||
"disallow_this_client": "Không cho phép client này",
|
||||
@@ -445,7 +445,7 @@
|
||||
"access_blocked_title": "Tên miền bị chặn",
|
||||
"access_blocked_desc": "Đừng nhầm lẫn điều này với các bộ lọc. AdGuard Home sẽ bỏ các truy vấn DNS với các tên miền này trong câu hỏi của truy vấn.",
|
||||
"access_settings_saved": "Cài đặt truy cập đã lưu thành công",
|
||||
"updates_checked": "Đã kiểm tra thành công cập nhật",
|
||||
"updates_checked": "Phiên bản mới của AdGuard Home có sẵn",
|
||||
"updates_version_equal": "AdGuard Home đã được cập nhật",
|
||||
"check_updates_now": "Kiểm tra cập nhật ngay bây giờ",
|
||||
"dns_privacy": "DNS Riêng Tư",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "无效的服务器名",
|
||||
"form_error_subnet": "子网 \"{{cidr}}\" 不包含 IP 地址 \"{{ip}}\"",
|
||||
"form_error_positive": "必须大于 0",
|
||||
"form_error_gateway_ip": "租约期限不能有网关的 IP 地址",
|
||||
"out_of_range_error": "必定超出了范围 \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "必须小于范围起始值",
|
||||
"greater_range_start_error": "必须大于范围起始值",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "不允许的域名",
|
||||
"access_blocked_desc": "不要将此功能与过滤器混淆。AdGuard Home 将排除匹配这些网域的 DNS 查询,并且这些查询将不会在查询日志中显示。在此可以明确指定域名、通配符(wildcard)和网址过滤的规则,例如 \"example.org\"、\"*.example.org\" 或 \"||example.org^\"。",
|
||||
"access_settings_saved": "访问设置保存成功",
|
||||
"updates_checked": "检查更新成功",
|
||||
"updates_checked": "AdGuard Home 的新版本现在可用",
|
||||
"updates_version_equal": "AdGuard Home已经是最新版本",
|
||||
"check_updates_now": "立即检查更新",
|
||||
"dns_privacy": "DNS 隐私",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"form_error_server_name": "無效的伺服器名稱",
|
||||
"form_error_subnet": "子網路 \"{{cidr}}\" 不包含該 IP 位址 \"{{ip}}\"",
|
||||
"form_error_positive": "必須大於 0",
|
||||
"form_error_gateway_ip": "租約不能有閘道的 IP 位址",
|
||||
"out_of_range_error": "必須在\"{{start}}\"-\"{{end}}\"範圍之外",
|
||||
"lower_range_start_error": "必須低於起始範圍",
|
||||
"greater_range_start_error": "必須大於起始範圍",
|
||||
@@ -445,7 +446,7 @@
|
||||
"access_blocked_title": "未被允許的網域",
|
||||
"access_blocked_desc": "不要把這個和過濾器混淆。AdGuard Home 排除與這些網域相符的 DNS 查詢,且這些查詢甚至不會出現在查詢記錄中。您可相應地明確指定確切的域名、萬用字元(wildcard)或網址過濾器的規則,例如,\"example.org\"、\"*.example.org\" 或 \"||example.org^\"。",
|
||||
"access_settings_saved": "存取設定被成功地儲存",
|
||||
"updates_checked": "更新被成功地檢查",
|
||||
"updates_checked": "AdGuard Home 的新版本為可用的",
|
||||
"updates_version_equal": "AdGuard Home 為最新的",
|
||||
"check_updates_now": "立即檢查更新",
|
||||
"dns_privacy": "DNS 隱私",
|
||||
@@ -630,6 +631,6 @@
|
||||
"use_saved_key": "使用該先前已儲存的金鑰",
|
||||
"parental_control": "家長控制",
|
||||
"safe_browsing": "安全瀏覽",
|
||||
"served_from_cache": "{{value}} <i>(由快取提供)</i>",
|
||||
"served_from_cache": "{{value}} <i>(由快取提供)</i>",
|
||||
"form_error_password_length": "密碼必須為至少長 {{value}} 個字元"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export const getTlsStatus = () => async (dispatch) => {
|
||||
export const setTlsConfigRequest = createAction('SET_TLS_CONFIG_REQUEST');
|
||||
export const setTlsConfigFailure = createAction('SET_TLS_CONFIG_FAILURE');
|
||||
export const setTlsConfigSuccess = createAction('SET_TLS_CONFIG_SUCCESS');
|
||||
export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS');
|
||||
|
||||
export const setTlsConfig = (config) => async (dispatch, getState) => {
|
||||
dispatch(setTlsConfigRequest());
|
||||
@@ -39,6 +40,12 @@ export const setTlsConfig = (config) => async (dispatch, getState) => {
|
||||
const response = await apiClient.setTlsConfig(values);
|
||||
response.certificate_chain = atob(response.certificate_chain);
|
||||
response.private_key = atob(response.private_key);
|
||||
|
||||
const dnsStatus = await apiClient.getGlobalStatus();
|
||||
if (dnsStatus) {
|
||||
dispatch(dnsStatusSuccess(dnsStatus));
|
||||
}
|
||||
|
||||
dispatch(setTlsConfigSuccess(response));
|
||||
dispatch(addSuccessToast('encryption_config_saved'));
|
||||
redirectToCurrentProtocol(response, httpPort);
|
||||
|
||||
@@ -29,6 +29,8 @@ class Table extends Component {
|
||||
Header: this.props.t('actions_table_header'),
|
||||
accessor: 'actions',
|
||||
maxWidth: 100,
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
Cell: (value) => (
|
||||
<div className="logs__row logs__row--center">
|
||||
<button
|
||||
|
||||
@@ -36,6 +36,7 @@ class Table extends Component {
|
||||
Cell: this.renderCheckbox,
|
||||
width: 90,
|
||||
className: 'text-center',
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
Header: <Trans>name_table_header</Trans>,
|
||||
@@ -77,12 +78,14 @@ class Table extends Component {
|
||||
},
|
||||
{
|
||||
Header: <Trans>actions_table_header</Trans>,
|
||||
accessor: 'url',
|
||||
accessor: 'actions',
|
||||
className: 'text-center',
|
||||
width: 100,
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
Cell: (row) => {
|
||||
const { value } = row;
|
||||
const { original } = row;
|
||||
const { url } = original;
|
||||
const { t, toggleFilteringModal, handleDelete } = this.props;
|
||||
|
||||
return (
|
||||
@@ -93,7 +96,7 @@ class Table extends Component {
|
||||
title={t('edit_table_action')}
|
||||
onClick={() => toggleFilteringModal({
|
||||
type: MODAL_TYPE.EDIT_FILTERS,
|
||||
url: value,
|
||||
url,
|
||||
})
|
||||
}
|
||||
>
|
||||
@@ -104,7 +107,7 @@ class Table extends Component {
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||
onClick={() => handleDelete(value)}
|
||||
onClick={() => handleDelete(url)}
|
||||
title={t('delete_table_action')}
|
||||
>
|
||||
<svg className="icons">
|
||||
|
||||
@@ -338,10 +338,19 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.logs__row--icons {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.logs__table .logs__row {
|
||||
border-bottom: 2px solid var(--gray-216);
|
||||
}
|
||||
|
||||
.logs__tag {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* QUERY_STATUS_COLORS */
|
||||
.logs__row--blue {
|
||||
background-color: var(--blue);
|
||||
|
||||
@@ -193,7 +193,7 @@ class ClientsTable extends Component {
|
||||
<div className="logs__row o-hidden">
|
||||
<span className="logs__text">
|
||||
{value.map((tag) => (
|
||||
<div key={tag} title={tag} className="small">
|
||||
<div key={tag} title={tag} className="logs__tag small">
|
||||
{tag}
|
||||
</div>
|
||||
))}
|
||||
@@ -225,6 +225,8 @@ class ClientsTable extends Component {
|
||||
Header: this.props.t('actions_table_header'),
|
||||
accessor: 'actions',
|
||||
maxWidth: 100,
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
Cell: (row) => {
|
||||
const clientName = row.original.name;
|
||||
const {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
validateMac,
|
||||
validateRequiredValue,
|
||||
validateIpv4InCidr,
|
||||
validateIpGateway,
|
||||
} from '../../../../helpers/validators';
|
||||
import { FORM_NAME } from '../../../../helpers/constants';
|
||||
import { toggleLeaseModal } from '../../../../actions';
|
||||
@@ -57,6 +58,7 @@ const Form = ({
|
||||
validateRequiredValue,
|
||||
validateIpv4,
|
||||
validateIpv4InCidr,
|
||||
validateIpGateway,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -101,6 +103,7 @@ Form.propTypes = {
|
||||
ip: PropTypes.string.isRequired,
|
||||
hostname: PropTypes.string.isRequired,
|
||||
cidr: PropTypes.string.isRequired,
|
||||
gatewayIp: PropTypes.string,
|
||||
}),
|
||||
pristine: PropTypes.bool.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
|
||||
@@ -13,6 +13,7 @@ const Modal = ({
|
||||
cidr,
|
||||
rangeStart,
|
||||
rangeEnd,
|
||||
gatewayIp,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -42,6 +43,7 @@ const Modal = ({
|
||||
cidr,
|
||||
rangeStart,
|
||||
rangeEnd,
|
||||
gatewayIp,
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
processingAdding={processingAdding}
|
||||
@@ -61,6 +63,7 @@ Modal.propTypes = {
|
||||
cidr: PropTypes.string.isRequired,
|
||||
rangeStart: PropTypes.string,
|
||||
rangeEnd: PropTypes.string,
|
||||
gatewayIp: PropTypes.string,
|
||||
};
|
||||
|
||||
export default withTranslation()(Modal);
|
||||
|
||||
@@ -24,6 +24,7 @@ const StaticLeases = ({
|
||||
cidr,
|
||||
rangeStart,
|
||||
rangeEnd,
|
||||
gatewayIp,
|
||||
}) => {
|
||||
const [t] = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
@@ -70,6 +71,8 @@ const StaticLeases = ({
|
||||
Header: <Trans>actions_table_header</Trans>,
|
||||
accessor: 'actions',
|
||||
maxWidth: 150,
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
// eslint-disable-next-line react/display-name
|
||||
Cell: (row) => {
|
||||
const { ip, mac, hostname } = row.original;
|
||||
@@ -104,6 +107,7 @@ const StaticLeases = ({
|
||||
cidr={cidr}
|
||||
rangeStart={rangeStart}
|
||||
rangeEnd={rangeEnd}
|
||||
gatewayIp={gatewayIp}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -117,6 +121,7 @@ StaticLeases.propTypes = {
|
||||
cidr: PropTypes.string.isRequired,
|
||||
rangeStart: PropTypes.string,
|
||||
rangeEnd: PropTypes.string,
|
||||
gatewayIp: PropTypes.string,
|
||||
};
|
||||
|
||||
cellWrap.propTypes = {
|
||||
|
||||
@@ -278,6 +278,7 @@ const Dhcp = () => {
|
||||
cidr={cidr}
|
||||
rangeStart={dhcp?.values?.v4?.range_start}
|
||||
rangeEnd={dhcp?.values?.v4?.range_end}
|
||||
gatewayIp={dhcp?.values?.v4?.gateway_ip}
|
||||
/>
|
||||
<div className="btn-list mt-2">
|
||||
<button
|
||||
|
||||
@@ -11,16 +11,16 @@ const Examples = (props) => (
|
||||
<code>94.140.14.140</code>: {props.t('example_upstream_regular')}
|
||||
</li>
|
||||
<li>
|
||||
<code>udp://dns-unfiltered.adguard.com</code>: <Trans>example_upstream_udp</Trans>
|
||||
<code>udp://unfiltered.adguard-dns.com</code>: <Trans>example_upstream_udp</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>tcp://94.140.14.140</code>: <Trans>example_upstream_tcp</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>tcp://dns-unfiltered.adguard.com</code>: <Trans>example_upstream_tcp_hostname</Trans>
|
||||
<code>tcp://unfiltered.adguard-dns.com</code>: <Trans>example_upstream_tcp_hostname</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>tls://dns-unfiltered.adguard.com</code>:
|
||||
<code>tls://unfiltered.adguard-dns.com</code>:
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
@@ -39,7 +39,7 @@ const Examples = (props) => (
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>https://dns-unfiltered.adguard.com/dns-query</code>:
|
||||
<code>https://unfiltered.adguard-dns.com/dns-query</code>:
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
@@ -58,7 +58,7 @@ const Examples = (props) => (
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>quic://dns-unfiltered.adguard.com:784</code>:
|
||||
<code>quic://unfiltered.adguard-dns.com:784</code>:
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-icon--green {
|
||||
|
||||
@@ -339,3 +339,14 @@ export const validatePasswordLength = (value) => {
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param value {string}
|
||||
* @returns {Function}
|
||||
*/
|
||||
export const validateIpGateway = (value, allValues) => {
|
||||
if (value === allValues.gatewayIp) {
|
||||
return i18next.t('form_error_gateway_ip');
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
67
internal/aghalg/nullbool.go
Normal file
67
internal/aghalg/nullbool.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package aghalg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// NullBool is a nullable boolean. Use these in JSON requests and responses
|
||||
// instead of pointers to bool.
|
||||
type NullBool uint8
|
||||
|
||||
// NullBool values
|
||||
const (
|
||||
NBNull NullBool = iota
|
||||
NBTrue
|
||||
NBFalse
|
||||
)
|
||||
|
||||
// String implements the fmt.Stringer interface for NullBool.
|
||||
func (nb NullBool) String() (s string) {
|
||||
switch nb {
|
||||
case NBNull:
|
||||
return "null"
|
||||
case NBTrue:
|
||||
return "true"
|
||||
case NBFalse:
|
||||
return "false"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("!invalid NullBool %d", uint8(nb))
|
||||
}
|
||||
|
||||
// BoolToNullBool converts a bool into a NullBool.
|
||||
func BoolToNullBool(cond bool) (nb NullBool) {
|
||||
if cond {
|
||||
return NBTrue
|
||||
}
|
||||
|
||||
return NBFalse
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ json.Marshaler = NBNull
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for NullBool.
|
||||
func (nb NullBool) MarshalJSON() (b []byte, err error) {
|
||||
return []byte(nb.String()), nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ json.Unmarshaler = (*NullBool)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface for *NullBool.
|
||||
func (nb *NullBool) UnmarshalJSON(b []byte) (err error) {
|
||||
if len(b) == 0 || bytes.Equal(b, []byte("null")) {
|
||||
*nb = NBNull
|
||||
} else if bytes.Equal(b, []byte("true")) {
|
||||
*nb = NBTrue
|
||||
} else if bytes.Equal(b, []byte("false")) {
|
||||
*nb = NBFalse
|
||||
} else {
|
||||
return fmt.Errorf("unmarshalling json data into aghalg.NullBool: bad value %q", b)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
113
internal/aghalg/nullbool_test.go
Normal file
113
internal/aghalg/nullbool_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package aghalg_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNullBool_MarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErrMsg string
|
||||
want []byte
|
||||
in aghalg.NullBool
|
||||
}{{
|
||||
name: "null",
|
||||
wantErrMsg: "",
|
||||
want: []byte("null"),
|
||||
in: aghalg.NBNull,
|
||||
}, {
|
||||
name: "true",
|
||||
wantErrMsg: "",
|
||||
want: []byte("true"),
|
||||
in: aghalg.NBTrue,
|
||||
}, {
|
||||
name: "false",
|
||||
wantErrMsg: "",
|
||||
want: []byte("false"),
|
||||
in: aghalg.NBFalse,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.in.MarshalJSON()
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("json", func(t *testing.T) {
|
||||
in := &struct {
|
||||
A aghalg.NullBool
|
||||
}{
|
||||
A: aghalg.NBTrue,
|
||||
}
|
||||
|
||||
got, err := json.Marshal(in)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []byte(`{"A":true}`), got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNullBool_UnmarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErrMsg string
|
||||
data []byte
|
||||
want aghalg.NullBool
|
||||
}{{
|
||||
name: "empty",
|
||||
wantErrMsg: "",
|
||||
data: []byte{},
|
||||
want: aghalg.NBNull,
|
||||
}, {
|
||||
name: "null",
|
||||
wantErrMsg: "",
|
||||
data: []byte("null"),
|
||||
want: aghalg.NBNull,
|
||||
}, {
|
||||
name: "true",
|
||||
wantErrMsg: "",
|
||||
data: []byte("true"),
|
||||
want: aghalg.NBTrue,
|
||||
}, {
|
||||
name: "false",
|
||||
wantErrMsg: "",
|
||||
data: []byte("false"),
|
||||
want: aghalg.NBFalse,
|
||||
}, {
|
||||
name: "invalid",
|
||||
wantErrMsg: `unmarshalling json data into aghalg.NullBool: bad value "invalid"`,
|
||||
data: []byte("invalid"),
|
||||
want: aghalg.NBNull,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var got aghalg.NullBool
|
||||
err := got.UnmarshalJSON(tc.data)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("json", func(t *testing.T) {
|
||||
want := aghalg.NBTrue
|
||||
var got struct {
|
||||
A aghalg.NullBool
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(`{"A":true}`), &got)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, want, got.A)
|
||||
})
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func (hc *HostsContainer) Close() (err error) {
|
||||
}
|
||||
|
||||
// Upd returns the channel into which the updates are sent. The receivable
|
||||
// map's values are guaranteed to be of type of *stringutil.Set.
|
||||
// map's values are guaranteed to be of type of *HostsRecord.
|
||||
func (hc *HostsContainer) Upd() (updates <-chan *netutil.IPMap) {
|
||||
return hc.updates
|
||||
}
|
||||
@@ -290,7 +290,7 @@ func (hp *hostsParser) parseFile(r io.Reader) (patterns []string, cont bool, err
|
||||
continue
|
||||
}
|
||||
|
||||
hp.addPairs(ip, hosts)
|
||||
hp.addRecord(ip, hosts)
|
||||
}
|
||||
|
||||
return nil, true, s.Err()
|
||||
@@ -335,39 +335,66 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
|
||||
return ip, hosts
|
||||
}
|
||||
|
||||
// addPair puts the pair of ip and host to the rules builder if needed. For
|
||||
// each ip the first member of hosts will become the main one.
|
||||
func (hp *hostsParser) addPairs(ip net.IP, hosts []string) {
|
||||
// HostsRecord represents a single hosts file record.
|
||||
type HostsRecord struct {
|
||||
Aliases *stringutil.Set
|
||||
Canonical string
|
||||
}
|
||||
|
||||
// Equal returns true if all fields of rec are equal to field in other or they
|
||||
// both are nil.
|
||||
func (rec *HostsRecord) Equal(other *HostsRecord) (ok bool) {
|
||||
if rec == nil {
|
||||
return other == nil
|
||||
}
|
||||
|
||||
return rec.Canonical == other.Canonical && rec.Aliases.Equal(other.Aliases)
|
||||
}
|
||||
|
||||
// addRecord puts the record for the IP address to the rules builder if needed.
|
||||
// The first host is considered to be the canonical name for the IP address.
|
||||
// hosts must have at least one name.
|
||||
func (hp *hostsParser) addRecord(ip net.IP, hosts []string) {
|
||||
line := strings.Join(append([]string{ip.String()}, hosts...), " ")
|
||||
|
||||
var rec *HostsRecord
|
||||
v, ok := hp.table.Get(ip)
|
||||
if !ok {
|
||||
// This ip is added at the first time.
|
||||
v = stringutil.NewSet()
|
||||
hp.table.Set(ip, v)
|
||||
rec = &HostsRecord{
|
||||
Aliases: stringutil.NewSet(),
|
||||
}
|
||||
|
||||
rec.Canonical, hosts = hosts[0], hosts[1:]
|
||||
hp.addRules(ip, rec.Canonical, line)
|
||||
hp.table.Set(ip, rec)
|
||||
} else {
|
||||
rec, ok = v.(*HostsRecord)
|
||||
if !ok {
|
||||
log.Error("%s: adding pairs: unexpected type %T", hostsContainerPref, v)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var set *stringutil.Set
|
||||
set, ok = v.(*stringutil.Set)
|
||||
if !ok {
|
||||
log.Debug("%s: adding pairs: unexpected value type %T", hostsContainerPref, v)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
processed := strings.Join(append([]string{ip.String()}, hosts...), " ")
|
||||
for _, h := range hosts {
|
||||
if set.Has(h) {
|
||||
for _, host := range hosts {
|
||||
if rec.Canonical == host || rec.Aliases.Has(host) {
|
||||
continue
|
||||
}
|
||||
|
||||
set.Add(h)
|
||||
rec.Aliases.Add(host)
|
||||
|
||||
rule, rulePtr := hp.writeRules(h, ip)
|
||||
hp.translations[rule], hp.translations[rulePtr] = processed, processed
|
||||
|
||||
log.Debug("%s: added ip-host pair %q-%q", hostsContainerPref, ip, h)
|
||||
hp.addRules(ip, host, line)
|
||||
}
|
||||
}
|
||||
|
||||
// addRules adds rules and rule translations for the line.
|
||||
func (hp *hostsParser) addRules(ip net.IP, host, line string) {
|
||||
rule, rulePtr := hp.writeRules(host, ip)
|
||||
hp.translations[rule], hp.translations[rulePtr] = line, line
|
||||
|
||||
log.Debug("%s: added ip-host pair %q-%q", hostsContainerPref, ip, host)
|
||||
}
|
||||
|
||||
// writeRules writes the actual rule for the qtype and the PTR for the host-ip
|
||||
// pair into internal builders.
|
||||
func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string) {
|
||||
@@ -417,6 +444,7 @@ func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string)
|
||||
}
|
||||
|
||||
// equalSet returns true if the internal hosts table just parsed equals target.
|
||||
// target's values must be of type *HostsRecord.
|
||||
func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {
|
||||
if target == nil {
|
||||
// hp.table shouldn't appear nil since it's initialized on each refresh.
|
||||
@@ -427,22 +455,35 @@ func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
hp.table.Range(func(ip net.IP, b interface{}) (cont bool) {
|
||||
// ok is set to true if the target doesn't contain ip or if the
|
||||
// appropriate hosts set isn't equal to the checked one.
|
||||
if a, hasIP := target.Get(ip); !hasIP {
|
||||
ok = true
|
||||
} else if hosts, aok := a.(*stringutil.Set); aok {
|
||||
ok = !hosts.Equal(b.(*stringutil.Set))
|
||||
hp.table.Range(func(ip net.IP, recVal interface{}) (cont bool) {
|
||||
var targetVal interface{}
|
||||
targetVal, ok = target.Get(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Continue only if maps has no discrepancies.
|
||||
return !ok
|
||||
var rec *HostsRecord
|
||||
rec, ok = recVal.(*HostsRecord)
|
||||
if !ok {
|
||||
log.Error("%s: comparing: unexpected type %T", hostsContainerPref, recVal)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var targetRec *HostsRecord
|
||||
targetRec, ok = targetVal.(*HostsRecord)
|
||||
if !ok {
|
||||
log.Error("%s: comparing: target: unexpected type %T", hostsContainerPref, targetVal)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
ok = rec.Equal(targetRec)
|
||||
|
||||
return ok
|
||||
})
|
||||
|
||||
// Return true if every value from the IP map has no discrepancies with the
|
||||
// appropriate one from the target.
|
||||
return !ok
|
||||
return ok
|
||||
}
|
||||
|
||||
// sendUpd tries to send the parsed data to the ch.
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
@@ -159,31 +160,47 @@ func TestHostsContainer_refresh(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
testutil.CleanupAndRequireSuccess(t, hc.Close)
|
||||
|
||||
checkRefresh := func(t *testing.T, wantHosts *stringutil.Set) {
|
||||
upd, ok := <-hc.Upd()
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, upd)
|
||||
checkRefresh := func(t *testing.T, want *HostsRecord) {
|
||||
t.Helper()
|
||||
|
||||
var ok bool
|
||||
var upd *netutil.IPMap
|
||||
select {
|
||||
case upd, ok = <-hc.Upd():
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, upd)
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("did not receive after 1s")
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, upd.Len())
|
||||
|
||||
v, ok := upd.Get(ip)
|
||||
require.True(t, ok)
|
||||
|
||||
var set *stringutil.Set
|
||||
set, ok = v.(*stringutil.Set)
|
||||
require.True(t, ok)
|
||||
require.IsType(t, (*HostsRecord)(nil), v)
|
||||
|
||||
assert.True(t, set.Equal(wantHosts))
|
||||
rec, _ := v.(*HostsRecord)
|
||||
require.NotNil(t, rec)
|
||||
|
||||
assert.Truef(t, rec.Equal(want), "%+v != %+v", rec, want)
|
||||
}
|
||||
|
||||
t.Run("initial_refresh", func(t *testing.T) {
|
||||
checkRefresh(t, stringutil.NewSet("hostname"))
|
||||
checkRefresh(t, &HostsRecord{
|
||||
Aliases: stringutil.NewSet(),
|
||||
Canonical: "hostname",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("second_refresh", func(t *testing.T) {
|
||||
testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + ` alias` + nl)}
|
||||
eventsCh <- event{}
|
||||
checkRefresh(t, stringutil.NewSet("hostname", "alias"))
|
||||
|
||||
checkRefresh(t, &HostsRecord{
|
||||
Aliases: stringutil.NewSet("alias"),
|
||||
Canonical: "hostname",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("double_refresh", func(t *testing.T) {
|
||||
@@ -363,10 +380,15 @@ func TestHostsContainer(t *testing.T) {
|
||||
require.NoError(t, fstest.TestFS(testdata, "etc_hosts"))
|
||||
|
||||
testCases := []struct {
|
||||
want []*rules.DNSRewrite
|
||||
name string
|
||||
req *urlfilter.DNSRequest
|
||||
name string
|
||||
want []*rules.DNSRewrite
|
||||
}{{
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "simplehost",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
name: "simple",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
Value: net.IPv4(1, 0, 0, 1),
|
||||
@@ -376,27 +398,12 @@ func TestHostsContainer(t *testing.T) {
|
||||
Value: net.ParseIP("::1"),
|
||||
RRType: dns.TypeAAAA,
|
||||
}},
|
||||
name: "simple",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "simplehost",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
}, {
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
Value: net.IPv4(1, 0, 0, 0),
|
||||
RRType: dns.TypeA,
|
||||
}, {
|
||||
RCode: dns.RcodeSuccess,
|
||||
Value: net.ParseIP("::"),
|
||||
RRType: dns.TypeAAAA,
|
||||
}},
|
||||
name: "hello_alias",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "hello.world",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
}, {
|
||||
name: "hello_alias",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
Value: net.IPv4(1, 0, 0, 0),
|
||||
@@ -406,26 +413,41 @@ func TestHostsContainer(t *testing.T) {
|
||||
Value: net.ParseIP("::"),
|
||||
RRType: dns.TypeAAAA,
|
||||
}},
|
||||
name: "other_line_alias",
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "hello.world.again",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
name: "other_line_alias",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
Value: net.IPv4(1, 0, 0, 0),
|
||||
RRType: dns.TypeA,
|
||||
}, {
|
||||
RCode: dns.RcodeSuccess,
|
||||
Value: net.ParseIP("::"),
|
||||
RRType: dns.TypeAAAA,
|
||||
}},
|
||||
}, {
|
||||
want: []*rules.DNSRewrite{},
|
||||
name: "hello_subdomain",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "say.hello",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
}, {
|
||||
name: "hello_subdomain",
|
||||
want: []*rules.DNSRewrite{},
|
||||
name: "hello_alias_subdomain",
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "say.hello.world",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
name: "hello_alias_subdomain",
|
||||
want: []*rules.DNSRewrite{},
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "for.testing",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
name: "lots_of_aliases",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
@@ -435,37 +457,37 @@ func TestHostsContainer(t *testing.T) {
|
||||
RRType: dns.TypeAAAA,
|
||||
Value: net.ParseIP("::2"),
|
||||
}},
|
||||
name: "lots_of_aliases",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "for.testing",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "1.0.0.1.in-addr.arpa",
|
||||
DNSType: dns.TypePTR,
|
||||
},
|
||||
name: "reverse",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypePTR,
|
||||
Value: "simplehost.",
|
||||
}},
|
||||
name: "reverse",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "1.0.0.1.in-addr.arpa",
|
||||
DNSType: dns.TypePTR,
|
||||
},
|
||||
}, {
|
||||
want: []*rules.DNSRewrite{},
|
||||
name: "non-existing",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "nonexisting",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
name: "non-existing",
|
||||
want: []*rules.DNSRewrite{},
|
||||
}, {
|
||||
want: nil,
|
||||
name: "bad_type",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "1.0.0.1.in-addr.arpa",
|
||||
DNSType: dns.TypeSRV,
|
||||
},
|
||||
name: "bad_type",
|
||||
want: nil,
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "domain",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
name: "issue_4216_4_6",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
@@ -475,12 +497,12 @@ func TestHostsContainer(t *testing.T) {
|
||||
RRType: dns.TypeAAAA,
|
||||
Value: net.ParseIP("::42"),
|
||||
}},
|
||||
name: "issue_4216_4_6",
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "domain",
|
||||
Hostname: "domain4",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
}, {
|
||||
name: "issue_4216_4",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
@@ -490,12 +512,12 @@ func TestHostsContainer(t *testing.T) {
|
||||
RRType: dns.TypeA,
|
||||
Value: net.IPv4(1, 3, 5, 7),
|
||||
}},
|
||||
name: "issue_4216_4",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "domain4",
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
}, {
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "domain6",
|
||||
DNSType: dns.TypeAAAA,
|
||||
},
|
||||
name: "issue_4216_6",
|
||||
want: []*rules.DNSRewrite{{
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeAAAA,
|
||||
@@ -505,11 +527,6 @@ func TestHostsContainer(t *testing.T) {
|
||||
RRType: dns.TypeAAAA,
|
||||
Value: net.ParseIP("::31"),
|
||||
}},
|
||||
name: "issue_4216_6",
|
||||
req: &urlfilter.DNSRequest{
|
||||
Hostname: "domain6",
|
||||
DNSType: dns.TypeAAAA,
|
||||
},
|
||||
}}
|
||||
|
||||
stubWatcher := aghtest.FSWatcher{
|
||||
|
||||
@@ -19,7 +19,7 @@ type SystemResolvers interface {
|
||||
}
|
||||
|
||||
// NewSystemResolvers returns a SystemResolvers with the cache refresh rate
|
||||
// defined by refreshIvl. It disables auto-resfreshing if refreshIvl is 0. If
|
||||
// defined by refreshIvl. It disables auto-refreshing if refreshIvl is 0. If
|
||||
// nil is passed for hostGenFunc, the default generator will be used.
|
||||
func NewSystemResolvers(
|
||||
hostGenFunc HostGenFunc,
|
||||
|
||||
30
internal/aghtls/aghtls.go
Normal file
30
internal/aghtls/aghtls.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Package aghtls contains utilities for work with TLS.
|
||||
package aghtls
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
// SaferCipherSuites returns a set of default cipher suites with vulnerable and
|
||||
// weak cipher suites removed.
|
||||
func SaferCipherSuites() (safe []uint16) {
|
||||
for _, s := range tls.CipherSuites() {
|
||||
switch s.ID {
|
||||
case
|
||||
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
|
||||
// Less safe 3DES and CBC suites, go on.
|
||||
default:
|
||||
safe = append(safe, s.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return safe
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
@@ -145,7 +146,7 @@ type dhcpServerConfigJSON struct {
|
||||
V4 *v4ServerConfJSON `json:"v4"`
|
||||
V6 *v6ServerConfJSON `json:"v6"`
|
||||
InterfaceName string `json:"interface_name"`
|
||||
Enabled nullBool `json:"enabled"`
|
||||
Enabled aghalg.NullBool `json:"enabled"`
|
||||
}
|
||||
|
||||
func (s *Server) handleDHCPSetConfigV4(
|
||||
@@ -156,7 +157,7 @@ func (s *Server) handleDHCPSetConfigV4(
|
||||
}
|
||||
|
||||
v4Conf := v4JSONToServerConf(conf.V4)
|
||||
v4Conf.Enabled = conf.Enabled == nbTrue
|
||||
v4Conf.Enabled = conf.Enabled == aghalg.NBTrue
|
||||
if len(v4Conf.RangeStart) == 0 {
|
||||
v4Conf.Enabled = false
|
||||
}
|
||||
@@ -183,7 +184,7 @@ func (s *Server) handleDHCPSetConfigV6(
|
||||
}
|
||||
|
||||
v6Conf := v6JSONToServerConf(conf.V6)
|
||||
v6Conf.Enabled = conf.Enabled == nbTrue
|
||||
v6Conf.Enabled = conf.Enabled == aghalg.NBTrue
|
||||
if len(v6Conf.RangeStart) == 0 {
|
||||
v6Conf.Enabled = false
|
||||
}
|
||||
@@ -206,7 +207,7 @@ func (s *Server) handleDHCPSetConfigV6(
|
||||
|
||||
func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
conf := &dhcpServerConfigJSON{}
|
||||
conf.Enabled = boolToNullBool(s.conf.Enabled)
|
||||
conf.Enabled = aghalg.BoolToNullBool(s.conf.Enabled)
|
||||
conf.InterfaceName = s.conf.InterfaceName
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(conf)
|
||||
@@ -230,7 +231,7 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if conf.Enabled == nbTrue && !v4Enabled && !v6Enabled {
|
||||
if conf.Enabled == aghalg.NBTrue && !v4Enabled && !v6Enabled {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "dhcpv4 or dhcpv6 configuration must be complete")
|
||||
|
||||
return
|
||||
@@ -243,8 +244,8 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if conf.Enabled != nbNull {
|
||||
s.conf.Enabled = conf.Enabled == nbTrue
|
||||
if conf.Enabled != aghalg.NBNull {
|
||||
s.conf.Enabled = conf.Enabled == aghalg.NBTrue
|
||||
}
|
||||
|
||||
if conf.InterfaceName != "" {
|
||||
@@ -279,11 +280,11 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
type netInterfaceJSON struct {
|
||||
Name string `json:"name"`
|
||||
GatewayIP net.IP `json:"gateway_ip"`
|
||||
HardwareAddr string `json:"hardware_address"`
|
||||
Flags string `json:"flags"`
|
||||
GatewayIP net.IP `json:"gateway_ip"`
|
||||
Addrs4 []net.IP `json:"ipv4_addresses"`
|
||||
Addrs6 []net.IP `json:"ipv6_addresses"`
|
||||
Flags string `json:"flags"`
|
||||
}
|
||||
|
||||
func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -497,7 +498,6 @@ func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
ip4 := l.IP.To4()
|
||||
|
||||
if ip4 == nil {
|
||||
l.IP = l.IP.To16()
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// nullBool is a nullable boolean. Use these in JSON requests and responses
|
||||
// instead of pointers to bool.
|
||||
//
|
||||
// TODO(a.garipov): Inspect uses of *bool, move this type into some new package
|
||||
// if we need it somewhere else.
|
||||
type nullBool uint8
|
||||
|
||||
// nullBool values
|
||||
const (
|
||||
nbNull nullBool = iota
|
||||
nbTrue
|
||||
nbFalse
|
||||
)
|
||||
|
||||
// String implements the fmt.Stringer interface for nullBool.
|
||||
func (nb nullBool) String() (s string) {
|
||||
switch nb {
|
||||
case nbNull:
|
||||
return "null"
|
||||
case nbTrue:
|
||||
return "true"
|
||||
case nbFalse:
|
||||
return "false"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("!invalid nullBool %d", uint8(nb))
|
||||
}
|
||||
|
||||
// boolToNullBool converts a bool into a nullBool.
|
||||
func boolToNullBool(cond bool) (nb nullBool) {
|
||||
if cond {
|
||||
return nbTrue
|
||||
}
|
||||
|
||||
return nbFalse
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface for *nullBool.
|
||||
func (nb *nullBool) UnmarshalJSON(b []byte) (err error) {
|
||||
if len(b) == 0 || bytes.Equal(b, []byte("null")) {
|
||||
*nb = nbNull
|
||||
} else if bytes.Equal(b, []byte("true")) {
|
||||
*nb = nbTrue
|
||||
} else if bytes.Equal(b, []byte("false")) {
|
||||
*nb = nbFalse
|
||||
} else {
|
||||
return fmt.Errorf("invalid nullBool value %q", b)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNullBool_UnmarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErrMsg string
|
||||
data []byte
|
||||
want nullBool
|
||||
}{{
|
||||
name: "empty",
|
||||
wantErrMsg: "",
|
||||
data: []byte{},
|
||||
want: nbNull,
|
||||
}, {
|
||||
name: "null",
|
||||
wantErrMsg: "",
|
||||
data: []byte("null"),
|
||||
want: nbNull,
|
||||
}, {
|
||||
name: "true",
|
||||
wantErrMsg: "",
|
||||
data: []byte("true"),
|
||||
want: nbTrue,
|
||||
}, {
|
||||
name: "false",
|
||||
wantErrMsg: "",
|
||||
data: []byte("false"),
|
||||
want: nbFalse,
|
||||
}, {
|
||||
name: "invalid",
|
||||
wantErrMsg: `invalid nullBool value "invalid"`,
|
||||
data: []byte("invalid"),
|
||||
want: nbNull,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var got nullBool
|
||||
err := got.UnmarshalJSON(tc.data)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("json", func(t *testing.T) {
|
||||
want := nbTrue
|
||||
var got struct {
|
||||
A nullBool
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(`{"A":true}`), &got)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, want, got.A)
|
||||
})
|
||||
}
|
||||
@@ -333,12 +333,16 @@ func (s *v4Server) rmLease(lease *Lease) (err error) {
|
||||
return errors.Error("lease not found")
|
||||
}
|
||||
|
||||
// AddStaticLease adds a static lease. It is safe for concurrent use.
|
||||
// AddStaticLease implements the DHCPServer interface for *v4Server. It is safe
|
||||
// for concurrent use.
|
||||
func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "dhcpv4: adding static lease: %w") }()
|
||||
|
||||
if ip4 := l.IP.To4(); ip4 == nil {
|
||||
ip := l.IP.To4()
|
||||
if ip == nil {
|
||||
return fmt.Errorf("invalid ip %q, only ipv4 is supported", l.IP)
|
||||
} else if gwIP := s.conf.GatewayIP; gwIP.Equal(ip) {
|
||||
return fmt.Errorf("can't assign the gateway IP %s to the lease", gwIP)
|
||||
}
|
||||
|
||||
l.Expiry = time.Unix(leaseExpireStatic, 0)
|
||||
@@ -377,7 +381,7 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
||||
if err != nil {
|
||||
err = fmt.Errorf(
|
||||
"removing dynamic leases for %s (%s): %w",
|
||||
l.IP,
|
||||
ip,
|
||||
l.HWAddr,
|
||||
err,
|
||||
)
|
||||
@@ -387,7 +391,7 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
||||
|
||||
err = s.addLease(l)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("adding static lease for %s (%s): %w", l.IP, l.HWAddr, err)
|
||||
err = fmt.Errorf("adding static lease for %s (%s): %w", ip, l.HWAddr, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -16,6 +17,13 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultRangeStart = net.IP{192, 168, 10, 100}
|
||||
DefaultRangeEnd = net.IP{192, 168, 10, 200}
|
||||
DefaultGatewayIP = net.IP{192, 168, 10, 1}
|
||||
DefaultSubnetMask = net.IP{255, 255, 255, 0}
|
||||
)
|
||||
|
||||
func notify4(flags uint32) {
|
||||
}
|
||||
|
||||
@@ -24,10 +32,10 @@ func notify4(flags uint32) {
|
||||
func defaultV4ServerConf() (conf V4ServerConf) {
|
||||
return V4ServerConf{
|
||||
Enabled: true,
|
||||
RangeStart: net.IP{192, 168, 10, 100},
|
||||
RangeEnd: net.IP{192, 168, 10, 200},
|
||||
GatewayIP: net.IP{192, 168, 10, 1},
|
||||
SubnetMask: net.IP{255, 255, 255, 0},
|
||||
RangeStart: DefaultRangeStart,
|
||||
RangeEnd: DefaultRangeEnd,
|
||||
GatewayIP: DefaultGatewayIP,
|
||||
SubnetMask: DefaultSubnetMask,
|
||||
notify: notify4,
|
||||
}
|
||||
}
|
||||
@@ -44,44 +52,86 @@ func defaultSrv(t *testing.T) (s DHCPServer) {
|
||||
return s
|
||||
}
|
||||
|
||||
func TestV4_AddRemove_static(t *testing.T) {
|
||||
func TestV4Server_AddRemove_static(t *testing.T) {
|
||||
s := defaultSrv(t)
|
||||
|
||||
ls := s.GetLeases(LeasesStatic)
|
||||
assert.Empty(t, ls)
|
||||
require.Empty(t, ls)
|
||||
|
||||
// Add static lease.
|
||||
l := &Lease{
|
||||
Hostname: "static-1.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
testCases := []struct {
|
||||
lease *Lease
|
||||
name string
|
||||
wantErrMsg string
|
||||
}{{
|
||||
lease: &Lease{
|
||||
Hostname: "success.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
},
|
||||
name: "success",
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
lease: &Lease{
|
||||
Hostname: "probably-router.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: DefaultGatewayIP,
|
||||
},
|
||||
name: "with_gateway_ip",
|
||||
wantErrMsg: "dhcpv4: adding static lease: " +
|
||||
"can't assign the gateway IP 192.168.10.1 to the lease",
|
||||
}, {
|
||||
lease: &Lease{
|
||||
Hostname: "ip6.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: net.ParseIP("ffff::1"),
|
||||
},
|
||||
name: "ipv6",
|
||||
wantErrMsg: `dhcpv4: adding static lease: ` +
|
||||
`invalid ip "ffff::1", only ipv4 is supported`,
|
||||
}, {
|
||||
lease: &Lease{
|
||||
Hostname: "bad-mac.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA},
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
},
|
||||
name: "bad_mac",
|
||||
wantErrMsg: `dhcpv4: adding static lease: bad mac address "aa:aa": ` +
|
||||
`bad mac address length 2, allowed: [6 8 20]`,
|
||||
}, {
|
||||
lease: &Lease{
|
||||
Hostname: "bad-lbl-.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
},
|
||||
name: "bad_hostname",
|
||||
wantErrMsg: `dhcpv4: adding static lease: validating hostname: ` +
|
||||
`bad domain name "bad-lbl-.local": ` +
|
||||
`bad domain name label "bad-lbl-": bad domain name label rune '-'`,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := s.AddStaticLease(tc.lease)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
if tc.wantErrMsg != "" {
|
||||
return
|
||||
}
|
||||
|
||||
err = s.RemoveStaticLease(&Lease{
|
||||
IP: tc.lease.IP,
|
||||
HWAddr: tc.lease.HWAddr,
|
||||
})
|
||||
diffErrMsg := fmt.Sprintf("dhcpv4: lease for ip %s is different: %+v", tc.lease.IP, tc.lease)
|
||||
testutil.AssertErrorMsg(t, diffErrMsg, err)
|
||||
|
||||
// Remove static lease.
|
||||
err = s.RemoveStaticLease(tc.lease)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
ls = s.GetLeases(LeasesStatic)
|
||||
require.Emptyf(t, ls, "after %s", tc.name)
|
||||
}
|
||||
|
||||
err := s.AddStaticLease(l)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.AddStaticLease(l)
|
||||
assert.Error(t, err)
|
||||
|
||||
ls = s.GetLeases(LeasesStatic)
|
||||
require.Len(t, ls, 1)
|
||||
|
||||
assert.True(t, l.IP.Equal(ls[0].IP))
|
||||
assert.Equal(t, l.HWAddr, ls[0].HWAddr)
|
||||
assert.True(t, ls[0].IsStatic())
|
||||
|
||||
// Try to remove static lease.
|
||||
err = s.RemoveStaticLease(&Lease{
|
||||
IP: net.IP{192, 168, 10, 110},
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
|
||||
// Remove static lease.
|
||||
err = s.RemoveStaticLease(l)
|
||||
require.NoError(t, err)
|
||||
ls = s.GetLeases(LeasesStatic)
|
||||
assert.Empty(t, ls)
|
||||
}
|
||||
|
||||
func TestV4_AddReplace(t *testing.T) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
@@ -121,6 +122,7 @@ type FilteringConfig struct {
|
||||
EnableDNSSEC bool `yaml:"enable_dnssec"` // Set AD flag in outcoming DNS request
|
||||
EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option
|
||||
MaxGoroutines uint32 `yaml:"max_goroutines"` // Max. number of parallel goroutines for processing incoming requests
|
||||
HandleDDR bool `yaml:"handle_ddr"` // Handle DDR requests
|
||||
|
||||
// IpsetList is the ipset configuration that allows AdGuard Home to add
|
||||
// IP addresses of the specified domain names to an ipset list. Syntax:
|
||||
@@ -151,7 +153,7 @@ type TLSConfig struct {
|
||||
PrivateKeyData []byte `yaml:"-" json:"-"`
|
||||
|
||||
// ServerName is the hostname of the server. Currently, it is only being
|
||||
// used for ClientID checking.
|
||||
// used for ClientID checking and Discovery of Designated Resolvers (DDR).
|
||||
ServerName string `yaml:"-" json:"-"`
|
||||
|
||||
cert tls.Certificate
|
||||
@@ -276,6 +278,11 @@ func (s *Server) createProxyConfig() (proxy.Config, error) {
|
||||
return proxyConfig, nil
|
||||
}
|
||||
|
||||
const (
|
||||
defaultSafeBrowsingBlockHost = "standard-block.dns.adguard.com"
|
||||
defaultParentalBlockHost = "family-block.dns.adguard.com"
|
||||
)
|
||||
|
||||
// initDefaultSettings initializes default settings if nothing
|
||||
// is configured
|
||||
func (s *Server) initDefaultSettings() {
|
||||
@@ -287,12 +294,12 @@ func (s *Server) initDefaultSettings() {
|
||||
s.conf.BootstrapDNS = defaultBootstrap
|
||||
}
|
||||
|
||||
if len(s.conf.ParentalBlockHost) == 0 {
|
||||
s.conf.ParentalBlockHost = parentalBlockHost
|
||||
if s.conf.ParentalBlockHost == "" {
|
||||
s.conf.ParentalBlockHost = defaultParentalBlockHost
|
||||
}
|
||||
|
||||
if len(s.conf.SafeBrowsingBlockHost) == 0 {
|
||||
s.conf.SafeBrowsingBlockHost = safeBrowsingBlockHost
|
||||
if s.conf.SafeBrowsingBlockHost == "" {
|
||||
s.conf.SafeBrowsingBlockHost = defaultSafeBrowsingBlockHost
|
||||
}
|
||||
|
||||
if s.conf.UDPListenAddrs == nil {
|
||||
@@ -427,6 +434,7 @@ func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
|
||||
|
||||
proxyConfig.TLSConfig = &tls.Config{
|
||||
GetCertificate: s.onGetCertificate,
|
||||
CipherSuites: aghtls.SaferCipherSuites(),
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,10 @@ const (
|
||||
resultCodeError
|
||||
)
|
||||
|
||||
// ddrHostFQDN is the FQDN used in Discovery of Designated Resolvers (DDR) requests.
|
||||
// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html.
|
||||
const ddrHostFQDN = "_dns.resolver.arpa."
|
||||
|
||||
// handleDNSRequest filters the incoming DNS requests and writes them to the query log
|
||||
func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
ctx := &dnsContext{
|
||||
@@ -94,10 +98,11 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
mods := []modProcessFunc{
|
||||
s.processRecursion,
|
||||
s.processInitial,
|
||||
s.processDDRQuery,
|
||||
s.processDetermineLocal,
|
||||
s.processInternalHosts,
|
||||
s.processDHCPHosts,
|
||||
s.processRestrictLocal,
|
||||
s.processInternalIPAddrs,
|
||||
s.processDHCPAddrs,
|
||||
s.processFilteringBeforeRequest,
|
||||
s.processLocalPTR,
|
||||
s.processUpstream,
|
||||
@@ -225,12 +230,10 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
|
||||
)
|
||||
}
|
||||
|
||||
lowhost := strings.ToLower(l.Hostname)
|
||||
lowhost := strings.ToLower(l.Hostname + "." + s.localDomainSuffix)
|
||||
ip := netutil.CloneIP(l.IP)
|
||||
|
||||
ipToHost.Set(l.IP, lowhost)
|
||||
|
||||
ip := make(net.IP, 4)
|
||||
copy(ip, l.IP.To4())
|
||||
ipToHost.Set(ip, lowhost)
|
||||
hostToIP[lowhost] = ip
|
||||
}
|
||||
|
||||
@@ -241,6 +244,98 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
|
||||
s.setTableIPToHost(ipToHost)
|
||||
}
|
||||
|
||||
// processDDRQuery responds to SVCB query for a special use domain name
|
||||
// ‘_dns.resolver.arpa’. The result contains different types of encryption
|
||||
// supported by current user configuration.
|
||||
//
|
||||
// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html.
|
||||
func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) {
|
||||
d := ctx.proxyCtx
|
||||
question := d.Req.Question[0]
|
||||
|
||||
if !s.conf.HandleDDR {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
if question.Name == ddrHostFQDN {
|
||||
if s.dnsProxy.TLSListenAddr == nil && s.conf.HTTPSListenAddrs == nil &&
|
||||
s.dnsProxy.QUICListenAddr == nil || question.Qtype != dns.TypeSVCB {
|
||||
d.Res = s.makeResponse(d.Req)
|
||||
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
d.Res = s.makeDDRResponse(d.Req)
|
||||
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// makeDDRResponse creates DDR answer according to server configuration. The
|
||||
// contructed SVCB resource records have the priority of 1 for each entry,
|
||||
// similar to examples provided by https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html.
|
||||
//
|
||||
// TODO(a.meshkov): Consider setting the priority values based on the protocol.
|
||||
func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
|
||||
resp = s.makeResponse(req)
|
||||
// TODO(e.burkov): Think about storing the FQDN version of the server's
|
||||
// name somewhere.
|
||||
domainName := dns.Fqdn(s.conf.ServerName)
|
||||
|
||||
for _, addr := range s.conf.HTTPSListenAddrs {
|
||||
values := []dns.SVCBKeyValue{
|
||||
&dns.SVCBAlpn{Alpn: []string{"h2"}},
|
||||
&dns.SVCBPort{Port: uint16(addr.Port)},
|
||||
&dns.SVCBDoHPath{Template: "/dns-query?dns"},
|
||||
}
|
||||
|
||||
ans := &dns.SVCB{
|
||||
Hdr: s.hdr(req, dns.TypeSVCB),
|
||||
Priority: 1,
|
||||
Target: domainName,
|
||||
Value: values,
|
||||
}
|
||||
|
||||
resp.Answer = append(resp.Answer, ans)
|
||||
}
|
||||
|
||||
for _, addr := range s.dnsProxy.TLSListenAddr {
|
||||
values := []dns.SVCBKeyValue{
|
||||
&dns.SVCBAlpn{Alpn: []string{"dot"}},
|
||||
&dns.SVCBPort{Port: uint16(addr.Port)},
|
||||
}
|
||||
|
||||
ans := &dns.SVCB{
|
||||
Hdr: s.hdr(req, dns.TypeSVCB),
|
||||
Priority: 1,
|
||||
Target: domainName,
|
||||
Value: values,
|
||||
}
|
||||
|
||||
resp.Answer = append(resp.Answer, ans)
|
||||
}
|
||||
|
||||
for _, addr := range s.dnsProxy.QUICListenAddr {
|
||||
values := []dns.SVCBKeyValue{
|
||||
&dns.SVCBAlpn{Alpn: []string{"doq"}},
|
||||
&dns.SVCBPort{Port: uint16(addr.Port)},
|
||||
}
|
||||
|
||||
ans := &dns.SVCB{
|
||||
Hdr: s.hdr(req, dns.TypeSVCB),
|
||||
Priority: 1,
|
||||
Target: domainName,
|
||||
Value: values,
|
||||
}
|
||||
|
||||
resp.Answer = append(resp.Answer, ans)
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -279,11 +374,11 @@ func (s *Server) hostToIP(host string) (ip net.IP, ok bool) {
|
||||
return ip, true
|
||||
}
|
||||
|
||||
// processInternalHosts respond to A requests if the target hostname is known to
|
||||
// processDHCPHosts respond to A requests if the target hostname is known to
|
||||
// the server.
|
||||
//
|
||||
// TODO(a.garipov): Adapt to AAAA as well.
|
||||
func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) {
|
||||
func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
|
||||
if !s.dhcpServer.Enabled() {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
@@ -298,11 +393,10 @@ func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
reqHost := strings.ToLower(q.Name)
|
||||
reqHost := strings.ToLower(q.Name[:len(q.Name)-1])
|
||||
// TODO(a.garipov): Move everything related to DHCP local domain to the DHCP
|
||||
// server.
|
||||
host := strings.TrimSuffix(reqHost, s.localDomainSuffix)
|
||||
if host == reqHost {
|
||||
if !strings.HasSuffix(reqHost, s.localDomainSuffix) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
@@ -315,7 +409,7 @@ func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
ip, ok := s.hostToIP(host)
|
||||
ip, ok := s.hostToIP(reqHost)
|
||||
if !ok {
|
||||
// TODO(e.burkov): Inspect special cases when user want to apply some
|
||||
// rules handled by other processors to the hosts with TLD.
|
||||
@@ -372,7 +466,7 @@ func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
|
||||
|
||||
// Restrict an access to local addresses for external clients. We also
|
||||
// assume that all the DHCP leases we give are locally-served or at least
|
||||
// don't need to be inaccessible externally.
|
||||
// don't need to be accessible externally.
|
||||
if !s.privateNets.Contains(ip) {
|
||||
log.Debug("dns: addr %s is not from locally-served network", ip)
|
||||
|
||||
@@ -429,7 +523,7 @@ func (s *Server) ipToHost(ip net.IP) (host string, ok bool) {
|
||||
|
||||
// Respond to PTR requests if the target IP is leased by our DHCP server and the
|
||||
// requestor is inside the local network.
|
||||
func (s *Server) processInternalIPAddrs(ctx *dnsContext) (rc resultCode) {
|
||||
func (s *Server) processDHCPAddrs(ctx *dnsContext) (rc resultCode) {
|
||||
d := ctx.proxyCtx
|
||||
if d.Res != nil {
|
||||
return resultCodeSuccess
|
||||
|
||||
@@ -14,6 +14,177 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
ddrTestDomainName = "dns.example.net"
|
||||
ddrTestFQDN = ddrTestDomainName + "."
|
||||
)
|
||||
|
||||
func TestServer_ProcessDDRQuery(t *testing.T) {
|
||||
dohSVCB := &dns.SVCB{
|
||||
Priority: 1,
|
||||
Target: ddrTestFQDN,
|
||||
Value: []dns.SVCBKeyValue{
|
||||
&dns.SVCBAlpn{Alpn: []string{"h2"}},
|
||||
&dns.SVCBPort{Port: 8044},
|
||||
&dns.SVCBDoHPath{Template: "/dns-query?dns"},
|
||||
},
|
||||
}
|
||||
|
||||
dotSVCB := &dns.SVCB{
|
||||
Priority: 1,
|
||||
Target: ddrTestFQDN,
|
||||
Value: []dns.SVCBKeyValue{
|
||||
&dns.SVCBAlpn{Alpn: []string{"dot"}},
|
||||
&dns.SVCBPort{Port: 8043},
|
||||
},
|
||||
}
|
||||
|
||||
doqSVCB := &dns.SVCB{
|
||||
Priority: 1,
|
||||
Target: ddrTestFQDN,
|
||||
Value: []dns.SVCBKeyValue{
|
||||
&dns.SVCBAlpn{Alpn: []string{"doq"}},
|
||||
&dns.SVCBPort{Port: 8042},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
host string
|
||||
want []*dns.SVCB
|
||||
wantRes resultCode
|
||||
portDoH int
|
||||
portDoT int
|
||||
portDoQ int
|
||||
qtype uint16
|
||||
ddrEnabled bool
|
||||
}{{
|
||||
name: "pass_host",
|
||||
wantRes: resultCodeSuccess,
|
||||
host: "example.net.",
|
||||
qtype: dns.TypeSVCB,
|
||||
ddrEnabled: true,
|
||||
portDoH: 8043,
|
||||
}, {
|
||||
name: "pass_qtype",
|
||||
wantRes: resultCodeFinish,
|
||||
host: ddrHostFQDN,
|
||||
qtype: dns.TypeA,
|
||||
ddrEnabled: true,
|
||||
portDoH: 8043,
|
||||
}, {
|
||||
name: "pass_disabled_tls",
|
||||
wantRes: resultCodeFinish,
|
||||
host: ddrHostFQDN,
|
||||
qtype: dns.TypeSVCB,
|
||||
ddrEnabled: true,
|
||||
}, {
|
||||
name: "pass_disabled_ddr",
|
||||
wantRes: resultCodeSuccess,
|
||||
host: ddrHostFQDN,
|
||||
qtype: dns.TypeSVCB,
|
||||
ddrEnabled: false,
|
||||
portDoH: 8043,
|
||||
}, {
|
||||
name: "dot",
|
||||
wantRes: resultCodeFinish,
|
||||
want: []*dns.SVCB{dotSVCB},
|
||||
host: ddrHostFQDN,
|
||||
qtype: dns.TypeSVCB,
|
||||
ddrEnabled: true,
|
||||
portDoT: 8043,
|
||||
}, {
|
||||
name: "doh",
|
||||
wantRes: resultCodeFinish,
|
||||
want: []*dns.SVCB{dohSVCB},
|
||||
host: ddrHostFQDN,
|
||||
qtype: dns.TypeSVCB,
|
||||
ddrEnabled: true,
|
||||
portDoH: 8044,
|
||||
}, {
|
||||
name: "doq",
|
||||
wantRes: resultCodeFinish,
|
||||
want: []*dns.SVCB{doqSVCB},
|
||||
host: ddrHostFQDN,
|
||||
qtype: dns.TypeSVCB,
|
||||
ddrEnabled: true,
|
||||
portDoQ: 8042,
|
||||
}, {
|
||||
name: "dot_doh",
|
||||
wantRes: resultCodeFinish,
|
||||
want: []*dns.SVCB{dotSVCB, dohSVCB},
|
||||
host: ddrHostFQDN,
|
||||
qtype: dns.TypeSVCB,
|
||||
ddrEnabled: true,
|
||||
portDoT: 8043,
|
||||
portDoH: 8044,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := prepareTestServer(t, tc.portDoH, tc.portDoT, tc.portDoQ, tc.ddrEnabled)
|
||||
|
||||
req := createTestMessageWithType(tc.host, tc.qtype)
|
||||
|
||||
dctx := &dnsContext{
|
||||
proxyCtx: &proxy.DNSContext{
|
||||
Req: req,
|
||||
},
|
||||
}
|
||||
|
||||
res := s.processDDRQuery(dctx)
|
||||
require.Equal(t, tc.wantRes, res)
|
||||
|
||||
if tc.wantRes != resultCodeFinish {
|
||||
return
|
||||
}
|
||||
|
||||
msg := dctx.proxyCtx.Res
|
||||
require.NotNil(t, msg)
|
||||
|
||||
for _, v := range tc.want {
|
||||
v.Hdr = s.hdr(req, dns.TypeSVCB)
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, tc.want, msg.Answer)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled bool) (s *Server) {
|
||||
t.Helper()
|
||||
|
||||
proxyConf := proxy.Config{}
|
||||
|
||||
if portDoT > 0 {
|
||||
proxyConf.TLSListenAddr = []*net.TCPAddr{{Port: portDoT}}
|
||||
}
|
||||
|
||||
if portDoQ > 0 {
|
||||
proxyConf.QUICListenAddr = []*net.UDPAddr{{Port: portDoQ}}
|
||||
}
|
||||
|
||||
s = &Server{
|
||||
dnsProxy: &proxy.Proxy{
|
||||
Config: proxyConf,
|
||||
},
|
||||
conf: ServerConfig{
|
||||
FilteringConfig: FilteringConfig{
|
||||
HandleDDR: ddrEnabled,
|
||||
},
|
||||
TLSConfig: TLSConfig{
|
||||
ServerName: ddrTestDomainName,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if portDoH > 0 {
|
||||
s.conf.TLSConfig.HTTPSListenAddrs = []*net.TCPAddr{{Port: portDoH}}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func TestServer_ProcessDetermineLocal(t *testing.T) {
|
||||
s := &Server{
|
||||
privateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
||||
@@ -58,7 +229,7 @@ func TestServer_ProcessDetermineLocal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) {
|
||||
func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
|
||||
knownIP := net.IP{1, 2, 3, 4}
|
||||
|
||||
testCases := []struct {
|
||||
@@ -99,7 +270,7 @@ func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) {
|
||||
dhcpServer: &testDHCP{},
|
||||
localDomainSuffix: defaultLocalDomainSuffix,
|
||||
tableHostToIP: hostToIPTable{
|
||||
"example": knownIP,
|
||||
"example." + defaultLocalDomainSuffix: knownIP,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -121,7 +292,7 @@ func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) {
|
||||
isLocalClient: tc.isLocalCli,
|
||||
}
|
||||
|
||||
res := s.processInternalHosts(dctx)
|
||||
res := s.processDHCPHosts(dctx)
|
||||
require.Equal(t, tc.wantRes, res)
|
||||
pctx := dctx.proxyCtx
|
||||
if tc.wantRes == resultCodeFinish {
|
||||
@@ -147,10 +318,10 @@ func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ProcessInternalHosts(t *testing.T) {
|
||||
func TestServer_ProcessDHCPHosts(t *testing.T) {
|
||||
const (
|
||||
examplecom = "example.com"
|
||||
examplelan = "example.lan"
|
||||
examplelan = "example." + defaultLocalDomainSuffix
|
||||
)
|
||||
|
||||
knownIP := net.IP{1, 2, 3, 4}
|
||||
@@ -199,41 +370,41 @@ func TestServer_ProcessInternalHosts(t *testing.T) {
|
||||
}, {
|
||||
name: "success_custom_suffix",
|
||||
host: "example.custom",
|
||||
suffix: ".custom.",
|
||||
suffix: "custom",
|
||||
wantIP: knownIP,
|
||||
wantRes: resultCodeSuccess,
|
||||
qtyp: dns.TypeA,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s := &Server{
|
||||
dhcpServer: &testDHCP{},
|
||||
localDomainSuffix: tc.suffix,
|
||||
tableHostToIP: hostToIPTable{
|
||||
"example." + tc.suffix: knownIP,
|
||||
},
|
||||
}
|
||||
|
||||
req := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: 1234,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: dns.Fqdn(tc.host),
|
||||
Qtype: tc.qtyp,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
dctx := &dnsContext{
|
||||
proxyCtx: &proxy.DNSContext{
|
||||
Req: req,
|
||||
},
|
||||
isLocalClient: true,
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := &Server{
|
||||
dhcpServer: &testDHCP{},
|
||||
localDomainSuffix: tc.suffix,
|
||||
tableHostToIP: hostToIPTable{
|
||||
"example": knownIP,
|
||||
},
|
||||
}
|
||||
|
||||
req := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: 1234,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: dns.Fqdn(tc.host),
|
||||
Qtype: tc.qtyp,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
dctx := &dnsContext{
|
||||
proxyCtx: &proxy.DNSContext{
|
||||
Req: req,
|
||||
},
|
||||
isLocalClient: true,
|
||||
}
|
||||
|
||||
res := s.processInternalHosts(dctx)
|
||||
res := s.processDHCPHosts(dctx)
|
||||
pctx := dctx.proxyCtx
|
||||
assert.Equal(t, tc.wantRes, res)
|
||||
if tc.wantRes == resultCodeFinish {
|
||||
|
||||
@@ -33,11 +33,6 @@ const DefaultTimeout = 10 * time.Second
|
||||
// requests between the BeforeRequestHandler stage and the actual processing.
|
||||
const defaultClientIDCacheCount = 1024
|
||||
|
||||
const (
|
||||
safeBrowsingBlockHost = "standard-block.dns.adguard.com"
|
||||
parentalBlockHost = "family-block.dns.adguard.com"
|
||||
)
|
||||
|
||||
var defaultDNS = []string{
|
||||
"https://dns10.quad9.net/dns-query",
|
||||
}
|
||||
@@ -107,7 +102,7 @@ type Server struct {
|
||||
// when no suffix is provided.
|
||||
//
|
||||
// See the documentation for Server.localDomainSuffix.
|
||||
const defaultLocalDomainSuffix = ".lan."
|
||||
const defaultLocalDomainSuffix = "lan"
|
||||
|
||||
// DNSCreateParams are parameters to create a new server.
|
||||
type DNSCreateParams struct {
|
||||
@@ -120,17 +115,6 @@ type DNSCreateParams struct {
|
||||
LocalDomain string
|
||||
}
|
||||
|
||||
// domainNameToSuffix converts a domain name into a local domain suffix.
|
||||
func domainNameToSuffix(tld string) (suffix string) {
|
||||
l := len(tld) + 2
|
||||
b := make([]byte, l)
|
||||
b[0] = '.'
|
||||
copy(b[1:], tld)
|
||||
b[l-1] = '.'
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
const (
|
||||
// recursionTTL is the time recursive request is cached for.
|
||||
recursionTTL = 1 * time.Second
|
||||
@@ -151,7 +135,7 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
|
||||
return nil, fmt.Errorf("local domain: %w", err)
|
||||
}
|
||||
|
||||
localDomainSuffix = domainNameToSuffix(p.LocalDomain)
|
||||
localDomainSuffix = p.LocalDomain
|
||||
}
|
||||
|
||||
if p.Anonymizer == nil {
|
||||
|
||||
@@ -1016,10 +1016,13 @@ func (d *testDHCP) Leases(flags dhcpd.GetLeasesFlags) (leases []*dhcpd.Lease) {
|
||||
func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {}
|
||||
|
||||
func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||
const localDomain = "lan"
|
||||
|
||||
s, err := NewServer(DNSCreateParams{
|
||||
DNSFilter: filtering.New(&filtering.Config{}, nil),
|
||||
DHCPServer: &testDHCP{},
|
||||
PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
||||
LocalDomain: localDomain,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1033,14 +1036,13 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||
|
||||
err = s.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(s.Close)
|
||||
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||
req := createTestMessageWithType("34.12.168.192.in-addr.arpa.", dns.TypePTR)
|
||||
|
||||
resp, err := dns.Exchange(req, addr.String())
|
||||
require.NoError(t, err)
|
||||
require.NoErrorf(t, err, "%s", addr)
|
||||
|
||||
require.Len(t, resp.Answer, 1)
|
||||
|
||||
@@ -1049,7 +1051,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||
|
||||
ptr, ok := resp.Answer[0].(*dns.PTR)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "myhost.", ptr.Ptr)
|
||||
assert.Equal(t, dns.Fqdn("myhost."+localDomain), ptr.Ptr)
|
||||
}
|
||||
|
||||
func TestPTRResponseFromHosts(t *testing.T) {
|
||||
|
||||
@@ -43,13 +43,14 @@ var serviceRulesArray = []svc{{
|
||||
}, {
|
||||
name: "youtube",
|
||||
rules: []string{
|
||||
"||youtube.com^",
|
||||
"||ytimg.com^",
|
||||
"||youtu.be^",
|
||||
"||googlevideo.com^",
|
||||
"||youtubei.googleapis.com^",
|
||||
"||youtube-nocookie.com^",
|
||||
"||wide-youtube.l.google.com^",
|
||||
"||youtu.be^",
|
||||
"||youtube",
|
||||
"||youtube-nocookie.com^",
|
||||
"||youtube.com^",
|
||||
"||youtubei.googleapis.com^",
|
||||
"||ytimg.com^",
|
||||
},
|
||||
}, {
|
||||
name: "twitch",
|
||||
|
||||
@@ -24,10 +24,11 @@ import (
|
||||
|
||||
// Safe browsing and parental control methods.
|
||||
|
||||
// TODO(a.garipov): Make configurable.
|
||||
const (
|
||||
dnsTimeout = 3 * time.Second
|
||||
defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query`
|
||||
defaultParentalServer = `https://dns-family.adguard.com/dns-query`
|
||||
defaultSafebrowsingServer = `https://family.adguard-dns.com/dns-query`
|
||||
defaultParentalServer = `https://family.adguard-dns.com/dns-query`
|
||||
sbTXTSuffix = `sb.dns.adguard.com.`
|
||||
pcTXTSuffix = `pc.dns.adguard.com.`
|
||||
)
|
||||
|
||||
@@ -743,8 +743,7 @@ func (clients *clientsContainer) AddHost(ip net.IP, host string, src clientSourc
|
||||
|
||||
// addHostLocked adds a new IP-hostname pairing. For internal use only.
|
||||
func (clients *clientsContainer) addHostLocked(ip net.IP, host string, src clientSource) (ok bool) {
|
||||
var rc *RuntimeClient
|
||||
rc, ok = clients.findRuntimeClientLocked(ip)
|
||||
rc, ok := clients.findRuntimeClientLocked(ip)
|
||||
if ok {
|
||||
if rc.Source > src {
|
||||
return false
|
||||
@@ -799,25 +798,20 @@ func (clients *clientsContainer) addFromHostsFile(hosts *netutil.IPMap) {
|
||||
|
||||
n := 0
|
||||
hosts.Range(func(ip net.IP, v interface{}) (cont bool) {
|
||||
hosts, ok := v.(*stringutil.Set)
|
||||
rec, ok := v.(*aghnet.HostsRecord)
|
||||
if !ok {
|
||||
log.Error("dns: bad type %T in ipToRC for %s", v, ip)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
hosts.Range(func(name string) (cont bool) {
|
||||
if clients.addHostLocked(ip, name, ClientSourceHostsFile) {
|
||||
n++
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
clients.addHostLocked(ip, rec.Canonical, ClientSourceHostsFile)
|
||||
n++
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
log.Debug("clients: added %d client aliases from system hosts-file", n)
|
||||
log.Debug("clients: added %d client aliases from system hosts file", n)
|
||||
}
|
||||
|
||||
// addFromSystemARP adds the IP-hostname pairings from the output of the arp -a
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -187,6 +187,7 @@ var config = &configuration{
|
||||
Ratelimit: 20,
|
||||
RefuseAny: true,
|
||||
AllServers: false,
|
||||
HandleDDR: true,
|
||||
FastestTimeout: timeutil.Duration{
|
||||
Duration: fastip.DefaultPingWaitTimeout,
|
||||
},
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
||||
@@ -117,7 +117,18 @@ func handleUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err := Context.updater.Update()
|
||||
// Retain the current absolute path of the executable, since the updater is
|
||||
// likely to change the position current one to the backup directory.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/4735.
|
||||
execPath, err := os.Executable()
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "getting path: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = Context.updater.Update()
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
|
||||
|
||||
@@ -129,13 +140,10 @@ func handleUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
// The background context is used because the underlying functions wrap
|
||||
// it with timeout and shut down the server, which handles current
|
||||
// request. It also should be done in a separate goroutine due to the
|
||||
// same reason.
|
||||
go func() {
|
||||
finishUpdate(context.Background())
|
||||
}()
|
||||
// The background context is used because the underlying functions wrap it
|
||||
// with timeout and shut down the server, which handles current request. It
|
||||
// also should be done in a separate goroutine for the same reason.
|
||||
go finishUpdate(context.Background(), execPath)
|
||||
}
|
||||
|
||||
// versionResponse is the response for /control/version.json endpoint.
|
||||
@@ -147,8 +155,8 @@ type versionResponse struct {
|
||||
// setAllowedToAutoUpdate sets CanAutoUpdate to true if AdGuard Home is actually
|
||||
// allowed to perform an automatic update by the OS.
|
||||
func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
|
||||
if vr.CanAutoUpdate == nil || !*vr.CanAutoUpdate {
|
||||
return
|
||||
if vr.CanAutoUpdate != aghalg.NBTrue {
|
||||
return nil
|
||||
}
|
||||
|
||||
tlsConf := &tlsConfigSettings{}
|
||||
@@ -162,7 +170,7 @@ func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
vr.CanAutoUpdate = &canUpdate
|
||||
vr.CanAutoUpdate = aghalg.BoolToNullBool(canUpdate)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -174,46 +182,46 @@ func tlsConfUsesPrivilegedPorts(c *tlsConfigSettings) (ok bool) {
|
||||
}
|
||||
|
||||
// finishUpdate completes an update procedure.
|
||||
func finishUpdate(ctx context.Context) {
|
||||
log.Info("Stopping all tasks")
|
||||
func finishUpdate(ctx context.Context, execPath string) {
|
||||
var err error
|
||||
|
||||
log.Info("stopping all tasks")
|
||||
|
||||
cleanup(ctx)
|
||||
cleanupAlways()
|
||||
|
||||
exeName := "AdGuardHome"
|
||||
if runtime.GOOS == "windows" {
|
||||
exeName = "AdGuardHome.exe"
|
||||
}
|
||||
curBinName := filepath.Join(Context.workDir, exeName)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
if Context.runningAsService {
|
||||
// Note:
|
||||
// we can't restart the service via "kardianos/service" package - it kills the process first
|
||||
// we can't start a new instance - Windows doesn't allow it
|
||||
// NOTE: We can't restart the service via "kardianos/service"
|
||||
// package, because it kills the process first we can't start a new
|
||||
// instance, because Windows doesn't allow it.
|
||||
//
|
||||
// TODO(a.garipov): Recheck the claim above.
|
||||
cmd := exec.Command("cmd", "/c", "net stop AdGuardHome & net start AdGuardHome")
|
||||
err := cmd.Start()
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatalf("exec.Command() failed: %s", err)
|
||||
log.Fatalf("restarting: stopping: %s", err)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
cmd := exec.Command(curBinName, os.Args[1:]...)
|
||||
log.Info("Restarting: %v", cmd.Args)
|
||||
cmd := exec.Command(execPath, os.Args[1:]...)
|
||||
log.Info("restarting: %q %q", execPath, os.Args[1:])
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Start()
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatalf("exec.Command() failed: %s", err)
|
||||
log.Fatalf("restarting:: %s", err)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
} else {
|
||||
log.Info("Restarting: %v", os.Args)
|
||||
err := syscall.Exec(curBinName, os.Args, os.Environ())
|
||||
if err != nil {
|
||||
log.Fatalf("syscall.Exec() failed: %s", err)
|
||||
}
|
||||
// Unreachable code
|
||||
}
|
||||
|
||||
log.Info("restarting: %q %q", execPath, os.Args[1:])
|
||||
err = syscall.Exec(execPath, os.Args, os.Environ())
|
||||
if err != nil {
|
||||
log.Fatalf("restarting: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,6 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
|
||||
}
|
||||
|
||||
newConf.TLSv12Roots = Context.tlsRoots
|
||||
newConf.TLSCiphers = Context.tlsCiphers
|
||||
newConf.TLSAllowUnencryptedDoH = tlsConf.AllowUnencryptedDoH
|
||||
|
||||
newConf.FilterHandler = applyAdditionalFiltering
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
@@ -78,7 +79,6 @@ type homeContext struct {
|
||||
disableUpdate bool // If set, don't check for updates
|
||||
controlLock sync.Mutex
|
||||
tlsRoots *x509.CertPool // list of root CAs for TLSv1.2
|
||||
tlsCiphers []uint16 // list of TLS ciphers to use
|
||||
transport *http.Transport
|
||||
client *http.Client
|
||||
appSignalChannel chan os.Signal // Channel for receiving OS signals by the console app
|
||||
@@ -143,13 +143,13 @@ func setupContext(args options) {
|
||||
initConfig()
|
||||
|
||||
Context.tlsRoots = LoadSystemRootCAs()
|
||||
Context.tlsCiphers = InitTLSCiphers()
|
||||
Context.transport = &http.Transport{
|
||||
DialContext: customDialContext,
|
||||
Proxy: getHTTPProxy,
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: Context.tlsRoots,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
RootCAs: Context.tlsRoots,
|
||||
CipherSuites: aghtls.SaferCipherSuites(),
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
}
|
||||
Context.client = &http.Client{
|
||||
|
||||
83
internal/home/service_linux.go
Normal file
83
internal/home/service_linux.go
Normal file
@@ -0,0 +1,83 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package home
|
||||
|
||||
import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/kardianos/service"
|
||||
)
|
||||
|
||||
func chooseSystem() {
|
||||
sys := service.ChosenSystem()
|
||||
// By default, package service uses the SysV system if it cannot detect
|
||||
// anything other, but the update-rc.d fix should not be applied on OpenWrt,
|
||||
// so exclude it explicitly.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/4480 and
|
||||
// https://github.com/AdguardTeam/AdGuardHome/issues/4677.
|
||||
if sys.String() == "unix-systemv" && !aghos.IsOpenWrt() {
|
||||
service.ChooseSystem(sysvSystem{System: sys})
|
||||
}
|
||||
}
|
||||
|
||||
// sysvSystem is a wrapper for service.System that wraps the service.Service
|
||||
// while creating a new one.
|
||||
//
|
||||
// TODO(e.burkov): File a PR to github.com/kardianos/service.
|
||||
type sysvSystem struct {
|
||||
// System is expected to have an unexported type
|
||||
// *service.linuxSystemService.
|
||||
service.System
|
||||
}
|
||||
|
||||
// New returns a wrapped service.Service.
|
||||
func (sys sysvSystem) New(i service.Interface, c *service.Config) (s service.Service, err error) {
|
||||
s, err = sys.System.New(i, c)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
return sysvService{
|
||||
Service: s,
|
||||
name: c.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// sysvService is a wrapper for a service.Service that also calls update-rc.d in
|
||||
// a proper way on installing and uninstalling.
|
||||
type sysvService struct {
|
||||
// Service is expected to have an unexported type *service.sysv.
|
||||
service.Service
|
||||
// name stores the name of the service to call updating script with it.
|
||||
name string
|
||||
}
|
||||
|
||||
// Install wraps service.Service.Install call with calling the updating script.
|
||||
func (svc sysvService) Install() (err error) {
|
||||
err = svc.Service.Install()
|
||||
if err != nil {
|
||||
// Don't wrap an error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = aghos.RunCommand("update-rc.d", svc.name, "defaults")
|
||||
|
||||
// Don't wrap an error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
// Uninstall wraps service.Service.Uninstall call with calling the updating
|
||||
// script.
|
||||
func (svc sysvService) Uninstall() (err error) {
|
||||
err = svc.Service.Uninstall()
|
||||
if err != nil {
|
||||
// Don't wrap an error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = aghos.RunCommand("update-rc.d", svc.name, "remove")
|
||||
|
||||
// Don't wrap an error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
//go:build !openbsd
|
||||
// +build !openbsd
|
||||
//go:build !(openbsd || linux)
|
||||
// +build !openbsd,!linux
|
||||
|
||||
package home
|
||||
|
||||
// chooseSystem checks the current system detected and substitutes it with local
|
||||
// implementation if needed.
|
||||
func chooseSystem() {}
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
var tlsWebHandlersRegistered = false
|
||||
@@ -731,52 +730,3 @@ func LoadSystemRootCAs() (roots *x509.CertPool) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitTLSCiphers performs the same work as initDefaultCipherSuites() from
|
||||
// crypto/tls/common.go but don't uses lots of other default ciphers.
|
||||
func InitTLSCiphers() (ciphers []uint16) {
|
||||
// Check the cpu flags for each platform that has optimized GCM
|
||||
// implementations. The worst case is when all these variables are
|
||||
// false.
|
||||
var (
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
// Keep in sync with crypto/aes/cipher_s390x.go.
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES &&
|
||||
cpu.S390X.HasAESCBC &&
|
||||
cpu.S390X.HasAESCTR &&
|
||||
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
|
||||
|
||||
hasGCMAsm = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X
|
||||
)
|
||||
|
||||
if hasGCMAsm {
|
||||
// If AES-GCM hardware is provided then prioritize AES-GCM
|
||||
// cipher suites.
|
||||
ciphers = []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
}
|
||||
} else {
|
||||
// Without AES-GCM hardware, we put the ChaCha20-Poly1305 cipher
|
||||
// suites first.
|
||||
ciphers = []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
}
|
||||
}
|
||||
|
||||
return append(
|
||||
ciphers,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
@@ -264,9 +265,9 @@ func (web *Web) tlsServerLoop() {
|
||||
Addr: address,
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{web.httpsServer.cert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
RootCAs: Context.tlsRoots,
|
||||
CipherSuites: Context.tlsCiphers,
|
||||
CipherSuites: aghtls.SaferCipherSuites(),
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
Handler: withMiddlewares(Context.mux, limitRequestBody),
|
||||
ReadTimeout: web.conf.ReadTimeout,
|
||||
|
||||
@@ -303,7 +303,7 @@ func NewTestQLogFileData(t *testing.T, data string) (file *QLogFile) {
|
||||
func TestQLog_Seek(t *testing.T) {
|
||||
const nl = "\n"
|
||||
const strV = "%s"
|
||||
const recs = `{"T":"` + strV + `","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://dns-unfiltered.adguard.com:853"}` + nl +
|
||||
const recs = `{"T":"` + strV + `","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://unfiltered.adguard-dns.com:853"}` + nl +
|
||||
`{"T":"` + strV + `"}` + nl +
|
||||
`{"T":"` + strV + `"}` + nl
|
||||
timestamp, _ := time.Parse(time.RFC3339Nano, "2020-08-31T18:44:25.376690873+03:00")
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
)
|
||||
@@ -17,11 +17,12 @@ const versionCheckPeriod = 8 * time.Hour
|
||||
|
||||
// VersionInfo contains information about a new version.
|
||||
type VersionInfo struct {
|
||||
CanAutoUpdate *bool `json:"can_autoupdate,omitempty"`
|
||||
NewVersion string `json:"new_version,omitempty"`
|
||||
Announcement string `json:"announcement,omitempty"`
|
||||
AnnouncementURL string `json:"announcement_url,omitempty"`
|
||||
SelfUpdateMinVersion string `json:"-"`
|
||||
NewVersion string `json:"new_version,omitempty"`
|
||||
Announcement string `json:"announcement,omitempty"`
|
||||
AnnouncementURL string `json:"announcement_url,omitempty"`
|
||||
// TODO(a.garipov): See if the frontend actually still cares about
|
||||
// nullability.
|
||||
CanAutoUpdate aghalg.NullBool `json:"can_autoupdate,omitempty"`
|
||||
}
|
||||
|
||||
// MaxResponseSize is responses on server's requests maximum length in bytes.
|
||||
@@ -67,15 +68,13 @@ func (u *Updater) VersionInfo(forceRecheck bool) (vi VersionInfo, err error) {
|
||||
}
|
||||
|
||||
func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
|
||||
var canAutoUpdate bool
|
||||
info := VersionInfo{
|
||||
CanAutoUpdate: &canAutoUpdate,
|
||||
CanAutoUpdate: aghalg.NBFalse,
|
||||
}
|
||||
versionJSON := map[string]string{
|
||||
"version": "",
|
||||
"announcement": "",
|
||||
"announcement_url": "",
|
||||
"selfupdate_min_version": "",
|
||||
"version": "",
|
||||
"announcement": "",
|
||||
"announcement_url": "",
|
||||
}
|
||||
err := json.Unmarshal(data, &versionJSON)
|
||||
if err != nil {
|
||||
@@ -91,14 +90,9 @@ func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
|
||||
info.NewVersion = versionJSON["version"]
|
||||
info.Announcement = versionJSON["announcement"]
|
||||
info.AnnouncementURL = versionJSON["announcement_url"]
|
||||
info.SelfUpdateMinVersion = versionJSON["selfupdate_min_version"]
|
||||
|
||||
packageURL, ok := u.downloadURL(versionJSON)
|
||||
if ok &&
|
||||
info.NewVersion != u.version &&
|
||||
strings.TrimPrefix(u.version, "v") >= strings.TrimPrefix(info.SelfUpdateMinVersion, "v") {
|
||||
canAutoUpdate = true
|
||||
}
|
||||
info.CanAutoUpdate = aghalg.BoolToNullBool(ok && info.NewVersion != u.version)
|
||||
|
||||
u.newVersion = info.NewVersion
|
||||
u.packageURL = packageURL
|
||||
|
||||
@@ -82,8 +82,9 @@ type Config struct {
|
||||
func NewUpdater(conf *Config) *Updater {
|
||||
u := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "static.adguard.com",
|
||||
Path: path.Join("adguardhome", conf.Channel, "version.json"),
|
||||
// TODO(a.garipov): Make configurable.
|
||||
Host: "static.adtidy.org",
|
||||
Path: path.Join("adguardhome", conf.Channel, "version.json"),
|
||||
}
|
||||
return &Updater{
|
||||
client: conf.Client,
|
||||
@@ -104,11 +105,19 @@ func NewUpdater(conf *Config) *Updater {
|
||||
}
|
||||
|
||||
// Update performs the auto-update.
|
||||
func (u *Updater) Update() error {
|
||||
func (u *Updater) Update() (err error) {
|
||||
u.mu.Lock()
|
||||
defer u.mu.Unlock()
|
||||
|
||||
err := u.prepare()
|
||||
log.Info("updater: updating")
|
||||
defer func() { log.Info("updater: finished; errors: %v", err) }()
|
||||
|
||||
execPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = u.prepare(filepath.Base(execPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -159,7 +168,8 @@ func (u *Updater) VersionCheckURL() (vcu string) {
|
||||
return u.versionCheckURL
|
||||
}
|
||||
|
||||
func (u *Updater) prepare() (err error) {
|
||||
// prepare fills all necessary fields in Updater object.
|
||||
func (u *Updater) prepare(exeName string) (err error) {
|
||||
u.updateDir = filepath.Join(u.workDir, fmt.Sprintf("agh-update-%s", u.newVersion))
|
||||
|
||||
_, pkgNameOnly := filepath.Split(u.packageURL)
|
||||
@@ -170,17 +180,21 @@ func (u *Updater) prepare() (err error) {
|
||||
u.packageName = filepath.Join(u.updateDir, pkgNameOnly)
|
||||
u.backupDir = filepath.Join(u.workDir, "agh-backup")
|
||||
|
||||
exeName := "AdGuardHome"
|
||||
updateExeName := "AdGuardHome"
|
||||
if u.goos == "windows" {
|
||||
exeName = "AdGuardHome.exe"
|
||||
updateExeName = "AdGuardHome.exe"
|
||||
}
|
||||
|
||||
u.backupExeName = filepath.Join(u.backupDir, exeName)
|
||||
u.updateExeName = filepath.Join(u.updateDir, exeName)
|
||||
u.updateExeName = filepath.Join(u.updateDir, updateExeName)
|
||||
|
||||
log.Info("Updating from %s to %s. URL:%s", version.Version(), u.newVersion, u.packageURL)
|
||||
log.Debug(
|
||||
"updater: updating from %s to %s using url: %s",
|
||||
version.Version(),
|
||||
u.newVersion,
|
||||
u.packageURL,
|
||||
)
|
||||
|
||||
// TODO(a.garipov): Use os.Args[0] instead?
|
||||
u.currentExeName = filepath.Join(u.workDir, exeName)
|
||||
_, err = os.Stat(u.currentExeName)
|
||||
if err != nil {
|
||||
@@ -194,7 +208,7 @@ func (u *Updater) unpack() error {
|
||||
var err error
|
||||
_, pkgNameOnly := filepath.Split(u.packageURL)
|
||||
|
||||
log.Debug("updater: unpacking the package")
|
||||
log.Debug("updater: unpacking package")
|
||||
if strings.HasSuffix(pkgNameOnly, ".zip") {
|
||||
u.unpackedFiles, err = zipFileUnpack(u.packageName, u.updateDir)
|
||||
if err != nil {
|
||||
@@ -229,7 +243,7 @@ func (u *Updater) check() error {
|
||||
}
|
||||
|
||||
func (u *Updater) backup() error {
|
||||
log.Debug("updater: backing up the current configuration")
|
||||
log.Debug("updater: backing up current configuration")
|
||||
_ = os.Mkdir(u.backupDir, 0o755)
|
||||
err := copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml"))
|
||||
if err != nil {
|
||||
@@ -252,7 +266,7 @@ func (u *Updater) replace() error {
|
||||
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %s", u.updateDir, u.workDir, err)
|
||||
}
|
||||
|
||||
log.Debug("updater: renaming: %s -> %s", u.currentExeName, u.backupExeName)
|
||||
log.Debug("updater: renaming: %s to %s", u.currentExeName, u.backupExeName)
|
||||
err = os.Rename(u.currentExeName, u.backupExeName)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -268,7 +282,7 @@ func (u *Updater) replace() error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("updater: renamed: %s -> %s", u.updateExeName, u.currentExeName)
|
||||
log.Debug("updater: renamed: %s to %s", u.updateExeName, u.currentExeName)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -297,7 +311,7 @@ func (u *Updater) downloadPackageFile(url, filename string) (err error) {
|
||||
return fmt.Errorf("http request failed: %w", err)
|
||||
}
|
||||
|
||||
log.Debug("updater: reading HTTP body")
|
||||
log.Debug("updater: reading http body")
|
||||
// This use of ReadAll is now safe, because we limited body's Reader.
|
||||
body, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
@@ -343,7 +357,7 @@ func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name st
|
||||
}
|
||||
|
||||
if hdr.Typeflag != tar.TypeReg {
|
||||
log.Debug("updater: %s: unknown file type %d, skipping", name, hdr.Typeflag)
|
||||
log.Info("updater: %s: unknown file type %d, skipping", name, hdr.Typeflag)
|
||||
|
||||
return "", nil
|
||||
}
|
||||
@@ -364,7 +378,7 @@ func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name st
|
||||
return "", fmt.Errorf("io.Copy(): %w", err)
|
||||
}
|
||||
|
||||
log.Tracef("updater: created file %s", outputName)
|
||||
log.Debug("updater: created file %q", outputName)
|
||||
|
||||
return name, nil
|
||||
}
|
||||
@@ -440,7 +454,7 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
|
||||
return "", fmt.Errorf("os.Mkdir(%q): %w", outputName, err)
|
||||
}
|
||||
|
||||
log.Tracef("created directory %q", outputName)
|
||||
log.Debug("updater: created directory %q", outputName)
|
||||
|
||||
return "", nil
|
||||
}
|
||||
@@ -457,7 +471,7 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
|
||||
return "", fmt.Errorf("io.Copy(): %w", err)
|
||||
}
|
||||
|
||||
log.Tracef("created file %s", outputName)
|
||||
log.Debug("updater: created file %q", outputName)
|
||||
|
||||
return name, nil
|
||||
}
|
||||
@@ -516,7 +530,7 @@ func copySupportingFiles(files []string, srcdir, dstdir string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("updater: copied: %q -> %q", src, dst)
|
||||
log.Debug("updater: copied: %q to %q", src, dst)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
@@ -44,28 +45,28 @@ func TestUpdateGetVersion(t *testing.T) {
|
||||
"announcement": "AdGuard Home v0.103.0-beta.2 is now available!",
|
||||
"announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases",
|
||||
"selfupdate_min_version": "v0.0",
|
||||
"download_windows_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip",
|
||||
"download_windows_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip",
|
||||
"download_darwin_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip",
|
||||
"download_darwin_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip",
|
||||
"download_linux_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz",
|
||||
"download_linux_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz",
|
||||
"download_linux_arm": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz",
|
||||
"download_linux_armv5": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz",
|
||||
"download_linux_armv6": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz",
|
||||
"download_linux_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz",
|
||||
"download_linux_arm64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz",
|
||||
"download_linux_mips": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz",
|
||||
"download_linux_mipsle": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz",
|
||||
"download_linux_mips64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz",
|
||||
"download_linux_mips64le": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz",
|
||||
"download_freebsd_386": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz",
|
||||
"download_freebsd_amd64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz",
|
||||
"download_freebsd_arm": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz",
|
||||
"download_freebsd_armv5": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz",
|
||||
"download_freebsd_armv6": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz",
|
||||
"download_freebsd_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz",
|
||||
"download_freebsd_arm64": "https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz"
|
||||
"download_windows_amd64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_windows_amd64.zip",
|
||||
"download_windows_386": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_windows_386.zip",
|
||||
"download_darwin_amd64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_amd64.zip",
|
||||
"download_darwin_386": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_darwin_386.zip",
|
||||
"download_linux_amd64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz",
|
||||
"download_linux_386": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_386.tar.gz",
|
||||
"download_linux_arm": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz",
|
||||
"download_linux_armv5": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz",
|
||||
"download_linux_armv6": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz",
|
||||
"download_linux_armv7": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz",
|
||||
"download_linux_arm64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz",
|
||||
"download_linux_mips": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz",
|
||||
"download_linux_mipsle": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz",
|
||||
"download_linux_mips64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz",
|
||||
"download_linux_mips64le": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz",
|
||||
"download_freebsd_386": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz",
|
||||
"download_freebsd_amd64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz",
|
||||
"download_freebsd_arm": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz",
|
||||
"download_freebsd_armv5": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz",
|
||||
"download_freebsd_armv6": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz",
|
||||
"download_freebsd_armv7": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz",
|
||||
"download_freebsd_arm64": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz"
|
||||
}`
|
||||
|
||||
l, lport := startHTTPServer(jsonData)
|
||||
@@ -92,10 +93,7 @@ func TestUpdateGetVersion(t *testing.T) {
|
||||
assert.Equal(t, "v0.103.0-beta.2", info.NewVersion)
|
||||
assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement)
|
||||
assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL)
|
||||
assert.Equal(t, "v0.0", info.SelfUpdateMinVersion)
|
||||
if assert.NotNil(t, info.CanAutoUpdate) {
|
||||
assert.True(t, *info.CanAutoUpdate)
|
||||
}
|
||||
assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate)
|
||||
|
||||
// check cached
|
||||
_, err = u.VersionInfo(false)
|
||||
@@ -133,7 +131,7 @@ func TestUpdate(t *testing.T) {
|
||||
u.newVersion = "v0.103.1"
|
||||
u.packageURL = fakeURL.String()
|
||||
|
||||
require.NoError(t, u.prepare())
|
||||
require.NoError(t, u.prepare("AdGuardHome"))
|
||||
|
||||
u.currentExeName = filepath.Join(wd, "AdGuardHome")
|
||||
|
||||
@@ -211,7 +209,7 @@ func TestUpdateWindows(t *testing.T) {
|
||||
u.newVersion = "v0.103.1"
|
||||
u.packageURL = fakeURL.String()
|
||||
|
||||
require.NoError(t, u.prepare())
|
||||
require.NoError(t, u.prepare("AdGuardHome.exe"))
|
||||
|
||||
u.currentExeName = filepath.Join(wd, "AdGuardHome.exe")
|
||||
|
||||
@@ -262,7 +260,7 @@ func TestUpdater_VersionInto_ARM(t *testing.T) {
|
||||
"announcement": "AdGuard Home v0.103.0-beta.2 is now available!",
|
||||
"announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases",
|
||||
"selfupdate_min_version": "v0.0",
|
||||
"download_linux_armv7": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz"
|
||||
"download_linux_armv7": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz"
|
||||
}`
|
||||
|
||||
l, lport := startHTTPServer(jsonData)
|
||||
@@ -290,10 +288,7 @@ func TestUpdater_VersionInto_ARM(t *testing.T) {
|
||||
assert.Equal(t, "v0.103.0-beta.2", info.NewVersion)
|
||||
assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement)
|
||||
assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL)
|
||||
assert.Equal(t, "v0.0", info.SelfUpdateMinVersion)
|
||||
if assert.NotNil(t, info.CanAutoUpdate) {
|
||||
assert.True(t, *info.CanAutoUpdate)
|
||||
}
|
||||
assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate)
|
||||
}
|
||||
|
||||
func TestUpdater_VersionInto_MIPS(t *testing.T) {
|
||||
@@ -302,7 +297,7 @@ func TestUpdater_VersionInto_MIPS(t *testing.T) {
|
||||
"announcement": "AdGuard Home v0.103.0-beta.2 is now available!",
|
||||
"announcement_url": "https://github.com/AdguardTeam/AdGuardHome/internal/releases",
|
||||
"selfupdate_min_version": "v0.0",
|
||||
"download_linux_mips_softfloat": "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz"
|
||||
"download_linux_mips_softfloat": "https://static.adtidy.org/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz"
|
||||
}`
|
||||
|
||||
l, lport := startHTTPServer(jsonData)
|
||||
@@ -330,8 +325,5 @@ func TestUpdater_VersionInto_MIPS(t *testing.T) {
|
||||
assert.Equal(t, "v0.103.0-beta.2", info.NewVersion)
|
||||
assert.Equal(t, "AdGuard Home v0.103.0-beta.2 is now available!", info.Announcement)
|
||||
assert.Equal(t, "https://github.com/AdguardTeam/AdGuardHome/internal/releases", info.AnnouncementURL)
|
||||
assert.Equal(t, "v0.0", info.SelfUpdateMinVersion)
|
||||
if assert.NotNil(t, info.CanAutoUpdate) {
|
||||
assert.True(t, *info.CanAutoUpdate)
|
||||
}
|
||||
assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate)
|
||||
}
|
||||
|
||||
33
internal/v1/agh/agh.go
Normal file
33
internal/v1/agh/agh.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Package agh contains common entities and interfaces of AdGuard Home.
|
||||
//
|
||||
// TODO(a.garipov): Move to the upper-level internal/.
|
||||
package agh
|
||||
|
||||
import "context"
|
||||
|
||||
// Service is the interface for API servers.
|
||||
//
|
||||
// TODO(a.garipov): Consider adding a context to Start.
|
||||
//
|
||||
// TODO(a.garipov): Consider adding a Wait method or making an extension
|
||||
// interface for that.
|
||||
type Service interface {
|
||||
// Start starts the service. It does not block.
|
||||
Start() (err error)
|
||||
|
||||
// Shutdown gracefully stops the service. ctx is used to determine
|
||||
// a timeout before trying to stop the service less gracefully.
|
||||
Shutdown(ctx context.Context) (err error)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Service = EmptyService{}
|
||||
|
||||
// EmptyService is a Service that does nothing.
|
||||
type EmptyService struct{}
|
||||
|
||||
// Start implements the Service interface for EmptyService.
|
||||
func (EmptyService) Start() (err error) { return nil }
|
||||
|
||||
// Shutdown implements the Service interface for EmptyService.
|
||||
func (EmptyService) Shutdown(_ context.Context) (err error) { return nil }
|
||||
71
internal/v1/cmd/cmd.go
Normal file
71
internal/v1/cmd/cmd.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Package cmd is the AdGuard Home entry point. It contains the on-disk
|
||||
// configuration file utilities, signal processing logic, and so on.
|
||||
//
|
||||
// TODO(a.garipov): Move to the upper-level internal/.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/v1/websvc"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
)
|
||||
|
||||
// Main is the entry point of application.
|
||||
func Main(clientBuildFS fs.FS) {
|
||||
// # Initial Configuration
|
||||
|
||||
start := time.Now()
|
||||
rand.Seed(start.UnixNano())
|
||||
|
||||
// TODO(a.garipov): Set up logging.
|
||||
|
||||
// # Web Service
|
||||
|
||||
// TODO(a.garipov): Use in the Web service.
|
||||
_ = clientBuildFS
|
||||
|
||||
// TODO(a.garipov): Make configurable.
|
||||
web := websvc.New(&websvc.Config{
|
||||
Addresses: []*netutil.IPPort{{
|
||||
IP: net.IP{127, 0, 0, 1},
|
||||
Port: 3001,
|
||||
}},
|
||||
Start: start,
|
||||
Timeout: 60 * time.Second,
|
||||
})
|
||||
|
||||
err := web.Start()
|
||||
fatalOnError(err)
|
||||
|
||||
sigHdlr := newSignalHandler(
|
||||
web,
|
||||
)
|
||||
|
||||
go sigHdlr.handle()
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
// defaultTimeout is the timeout used for some operations where another timeout
|
||||
// hasn't been defined yet.
|
||||
const defaultTimeout = 15 * time.Second
|
||||
|
||||
// ctxWithDefaultTimeout is a helper function that returns a context with
|
||||
// timeout set to defaultTimeout.
|
||||
func ctxWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), defaultTimeout)
|
||||
}
|
||||
|
||||
// fatalOnError is a helper that exits the program with an error code if err is
|
||||
// not nil. It must only be used within Main.
|
||||
func fatalOnError(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
70
internal/v1/cmd/signal.go
Normal file
70
internal/v1/cmd/signal.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/v1/agh"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// signalHandler processes incoming signals and shuts services down.
|
||||
type signalHandler struct {
|
||||
signal chan os.Signal
|
||||
|
||||
// services are the services that are shut down before application
|
||||
// exiting.
|
||||
services []agh.Service
|
||||
}
|
||||
|
||||
// handle processes OS signals.
|
||||
func (h *signalHandler) handle() {
|
||||
defer log.OnPanic("signalHandler.handle")
|
||||
|
||||
for sig := range h.signal {
|
||||
log.Info("sighdlr: received signal %q", sig)
|
||||
|
||||
if aghos.IsShutdownSignal(sig) {
|
||||
h.shutdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit status constants.
|
||||
const (
|
||||
statusSuccess = 0
|
||||
statusError = 1
|
||||
)
|
||||
|
||||
// shutdown gracefully shuts down all services.
|
||||
func (h *signalHandler) shutdown() {
|
||||
ctx, cancel := ctxWithDefaultTimeout()
|
||||
defer cancel()
|
||||
|
||||
status := statusSuccess
|
||||
|
||||
log.Info("sighdlr: shutting down services")
|
||||
for i, service := range h.services {
|
||||
err := service.Shutdown(ctx)
|
||||
if err != nil {
|
||||
log.Error("sighdlr: shutting down service at index %d: %s", i, err)
|
||||
status = statusError
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("sighdlr: shutting down adguard home")
|
||||
|
||||
os.Exit(status)
|
||||
}
|
||||
|
||||
// newSignalHandler returns a new signalHandler that shuts down svcs.
|
||||
func newSignalHandler(svcs ...agh.Service) (h *signalHandler) {
|
||||
h = &signalHandler{
|
||||
signal: make(chan os.Signal, 1),
|
||||
services: svcs,
|
||||
}
|
||||
|
||||
aghos.NotifyShutdownSignal(h.signal)
|
||||
|
||||
return h
|
||||
}
|
||||
61
internal/v1/websvc/json.go
Normal file
61
internal/v1/websvc/json.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package websvc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// JSON Utilities
|
||||
|
||||
// jsonTime is a time.Time that can be decoded from JSON and encoded into JSON
|
||||
// according to our API conventions.
|
||||
type jsonTime time.Time
|
||||
|
||||
// type check
|
||||
var _ json.Marshaler = jsonTime{}
|
||||
|
||||
// nsecPerMsec is the number of nanoseconds in a millisecond.
|
||||
const nsecPerMsec = float64(time.Millisecond / time.Nanosecond)
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for jsonTime. err is
|
||||
// always nil.
|
||||
func (t jsonTime) MarshalJSON() (b []byte, err error) {
|
||||
msec := float64(time.Time(t).UnixNano()) / nsecPerMsec
|
||||
b = strconv.AppendFloat(nil, msec, 'f', 3, 64)
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ json.Unmarshaler = (*jsonTime)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Marshaler interface for *jsonTime.
|
||||
func (t *jsonTime) UnmarshalJSON(b []byte) (err error) {
|
||||
if t == nil {
|
||||
return fmt.Errorf("json time is nil")
|
||||
}
|
||||
|
||||
msec, err := strconv.ParseFloat(string(b), 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing json time: %w", err)
|
||||
}
|
||||
|
||||
*t = jsonTime(time.Unix(0, int64(msec*nsecPerMsec)).UTC())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeJSONResponse encodes v into w and logs any errors it encounters. r is
|
||||
// used to get additional information from the request.
|
||||
func writeJSONResponse(w io.Writer, r *http.Request, v interface{}) {
|
||||
err := json.NewEncoder(w).Encode(v)
|
||||
if err != nil {
|
||||
log.Error("websvc: writing resp to %s %s: %s", r.Method, r.URL.Path, err)
|
||||
}
|
||||
}
|
||||
16
internal/v1/websvc/middleware.go
Normal file
16
internal/v1/websvc/middleware.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package websvc
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Middlewares
|
||||
|
||||
// jsonMw sets the content type of the response to application/json.
|
||||
func jsonMw(h http.Handler) (wrapped http.HandlerFunc) {
|
||||
f := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(f)
|
||||
}
|
||||
8
internal/v1/websvc/path.go
Normal file
8
internal/v1/websvc/path.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package websvc
|
||||
|
||||
// Path constants
|
||||
const (
|
||||
PathHealthCheck = "/health-check"
|
||||
|
||||
PathV1SystemInfo = "/api/v1/system/info"
|
||||
)
|
||||
35
internal/v1/websvc/system.go
Normal file
35
internal/v1/websvc/system.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package websvc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
)
|
||||
|
||||
// System Handlers
|
||||
|
||||
// RespGetV1SystemInfo describes the response of the GET /api/v1/system/info
|
||||
// HTTP API.
|
||||
type RespGetV1SystemInfo struct {
|
||||
Arch string `json:"arch"`
|
||||
Channel string `json:"channel"`
|
||||
OS string `json:"os"`
|
||||
NewVersion string `json:"new_version,omitempty"`
|
||||
Start jsonTime `json:"start"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// handleGetV1SystemInfo is the handler for the GET /api/v1/system/info HTTP
|
||||
// API.
|
||||
func (svc *Service) handleGetV1SystemInfo(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSONResponse(w, r, &RespGetV1SystemInfo{
|
||||
Arch: runtime.GOARCH,
|
||||
Channel: version.Channel(),
|
||||
OS: runtime.GOOS,
|
||||
// TODO(a.garipov): Fill this when we have an updater.
|
||||
NewVersion: "",
|
||||
Start: jsonTime(svc.start),
|
||||
Version: version.Version(),
|
||||
})
|
||||
}
|
||||
36
internal/v1/websvc/system_test.go
Normal file
36
internal/v1/websvc/system_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package websvc_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/v1/websvc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestService_handleGetV1SystemInfo(t *testing.T) {
|
||||
_, addr := newTestServer(t)
|
||||
u := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: addr,
|
||||
Path: websvc.PathV1SystemInfo,
|
||||
}
|
||||
|
||||
body := httpGet(t, u, http.StatusOK)
|
||||
resp := &websvc.RespGetV1SystemInfo{}
|
||||
err := json.Unmarshal(body, resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
// TODO(a.garipov): Consider making version.Channel and version.Version
|
||||
// testable and test these better.
|
||||
assert.NotEmpty(t, resp.Channel)
|
||||
|
||||
assert.Equal(t, resp.Arch, runtime.GOARCH)
|
||||
assert.Equal(t, resp.OS, runtime.GOOS)
|
||||
assert.Equal(t, testStart, time.Time(resp.Start))
|
||||
}
|
||||
227
internal/v1/websvc/websvc.go
Normal file
227
internal/v1/websvc/websvc.go
Normal file
@@ -0,0 +1,227 @@
|
||||
// Package websvc contains the AdGuard Home web service.
|
||||
//
|
||||
// TODO(a.garipov): Add tests.
|
||||
package websvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/v1/agh"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
httptreemux "github.com/dimfeld/httptreemux/v5"
|
||||
)
|
||||
|
||||
// Config is the AdGuard Home web service configuration structure.
|
||||
type Config struct {
|
||||
// TLS is the optional TLS configuration. If TLS is not nil,
|
||||
// SecureAddresses must not be empty.
|
||||
TLS *tls.Config
|
||||
|
||||
// Addresses are the addresses on which to serve the plain HTTP API.
|
||||
Addresses []*netutil.IPPort
|
||||
|
||||
// SecureAddresses are the addresses on which to serve the HTTPS API. If
|
||||
// SecureAddresses is not empty, TLS must not be nil.
|
||||
SecureAddresses []*netutil.IPPort
|
||||
|
||||
// Start is the time of start of AdGuard Home.
|
||||
Start time.Time
|
||||
|
||||
// Timeout is the timeout for all server operations.
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// Service is the AdGuard Home web service. A nil *Service is a valid service
|
||||
// that does nothing.
|
||||
type Service struct {
|
||||
tls *tls.Config
|
||||
servers []*http.Server
|
||||
start time.Time
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// New returns a new properly initialized *Service. If c is nil, svc is a nil
|
||||
// *Service that does nothing.
|
||||
func New(c *Config) (svc *Service) {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
svc = &Service{
|
||||
tls: c.TLS,
|
||||
start: c.Start,
|
||||
timeout: c.Timeout,
|
||||
}
|
||||
|
||||
mux := newMux(svc)
|
||||
|
||||
for _, a := range c.Addresses {
|
||||
addr := a.String()
|
||||
errLog := log.StdLog("websvc: http: "+addr, log.ERROR)
|
||||
svc.servers = append(svc.servers, &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
ErrorLog: errLog,
|
||||
ReadTimeout: c.Timeout,
|
||||
WriteTimeout: c.Timeout,
|
||||
IdleTimeout: c.Timeout,
|
||||
ReadHeaderTimeout: c.Timeout,
|
||||
})
|
||||
}
|
||||
|
||||
for _, a := range c.SecureAddresses {
|
||||
addr := a.String()
|
||||
errLog := log.StdLog("websvc: https: "+addr, log.ERROR)
|
||||
svc.servers = append(svc.servers, &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
TLSConfig: c.TLS,
|
||||
ErrorLog: errLog,
|
||||
ReadTimeout: c.Timeout,
|
||||
WriteTimeout: c.Timeout,
|
||||
IdleTimeout: c.Timeout,
|
||||
ReadHeaderTimeout: c.Timeout,
|
||||
})
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
// newMux returns a new HTTP request multiplexor for the AdGuard Home web
|
||||
// service.
|
||||
func newMux(svc *Service) (mux *httptreemux.ContextMux) {
|
||||
mux = httptreemux.NewContextMux()
|
||||
|
||||
routes := []struct {
|
||||
handler http.HandlerFunc
|
||||
method string
|
||||
path string
|
||||
isJSON bool
|
||||
}{{
|
||||
handler: svc.handleGetHealthCheck,
|
||||
method: http.MethodGet,
|
||||
path: PathHealthCheck,
|
||||
isJSON: false,
|
||||
}, {
|
||||
handler: svc.handleGetV1SystemInfo,
|
||||
method: http.MethodGet,
|
||||
path: PathV1SystemInfo,
|
||||
isJSON: true,
|
||||
}}
|
||||
|
||||
for _, r := range routes {
|
||||
var h http.HandlerFunc
|
||||
if r.isJSON {
|
||||
// TODO(a.garipov): Consider using httptreemux's MiddlewareFunc.
|
||||
h = jsonMw(r.handler)
|
||||
} else {
|
||||
h = r.handler
|
||||
}
|
||||
|
||||
mux.Handle(r.method, r.path, h)
|
||||
}
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
// Addrs returns all addresses on which this server serves the HTTP API. Addrs
|
||||
// must not be called until Start returns.
|
||||
func (svc *Service) Addrs() (addrs []string) {
|
||||
addrs = make([]string, 0, len(svc.servers))
|
||||
for _, srv := range svc.servers {
|
||||
addrs = append(addrs, srv.Addr)
|
||||
}
|
||||
|
||||
return addrs
|
||||
}
|
||||
|
||||
// handleGetHealthCheck is the handler for the GET /health-check HTTP API.
|
||||
func (svc *Service) handleGetHealthCheck(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = io.WriteString(w, "OK")
|
||||
}
|
||||
|
||||
// unit is a convenient alias for struct{}.
|
||||
type unit = struct{}
|
||||
|
||||
// type check
|
||||
var _ agh.Service = (*Service)(nil)
|
||||
|
||||
// Start implements the agh.Service interface for *Service. svc may be nil.
|
||||
// After Start exits, all HTTP servers have tried to start, possibly failing and
|
||||
// writing error messages to the log.
|
||||
func (svc *Service) Start() (err error) {
|
||||
if svc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
srvs := svc.servers
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(srvs))
|
||||
for _, srv := range srvs {
|
||||
go serve(srv, wg)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serve starts and runs srv and writes all errors into its log.
|
||||
func serve(srv *http.Server, wg *sync.WaitGroup) {
|
||||
addr := srv.Addr
|
||||
defer log.OnPanic(addr)
|
||||
|
||||
var l net.Listener
|
||||
var err error
|
||||
if srv.TLSConfig == nil {
|
||||
l, err = net.Listen("tcp", addr)
|
||||
} else {
|
||||
l, err = tls.Listen("tcp", addr, srv.TLSConfig)
|
||||
}
|
||||
if err != nil {
|
||||
srv.ErrorLog.Printf("starting srv %s: binding: %s", addr, err)
|
||||
}
|
||||
|
||||
// Update the server's address in case the address had the port zero, which
|
||||
// would mean that a random available port was automatically chosen.
|
||||
srv.Addr = l.Addr().String()
|
||||
|
||||
log.Info("websvc: starting srv http://%s", srv.Addr)
|
||||
wg.Done()
|
||||
|
||||
err = srv.Serve(l)
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
srv.ErrorLog.Printf("starting srv %s: %s", addr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown implements the agh.Service interface for *Service. svc may be nil.
|
||||
func (svc *Service) Shutdown(ctx context.Context) (err error) {
|
||||
if svc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for _, srv := range svc.servers {
|
||||
serr := srv.Shutdown(ctx)
|
||||
if serr != nil {
|
||||
errs = append(errs, fmt.Errorf("shutting down srv %s: %w", srv.Addr, serr))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.List("shutting down")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
97
internal/v1/websvc/websvc_test.go
Normal file
97
internal/v1/websvc/websvc_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package websvc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/v1/websvc"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const testTimeout = 1 * time.Second
|
||||
|
||||
// testStart is the server start value for tests.
|
||||
var testStart = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// newTestServer creates and starts a new web service instance as well as its
|
||||
// sole address. It also registers a cleanup procedure, which shuts the
|
||||
// instance down.
|
||||
//
|
||||
// TODO(a.garipov): Use svc or remove it.
|
||||
func newTestServer(t testing.TB) (svc *websvc.Service, addr string) {
|
||||
t.Helper()
|
||||
|
||||
c := &websvc.Config{
|
||||
TLS: nil,
|
||||
Addresses: []*netutil.IPPort{{
|
||||
IP: net.IP{127, 0, 0, 1},
|
||||
Port: 0,
|
||||
}},
|
||||
SecureAddresses: nil,
|
||||
Timeout: testTimeout,
|
||||
Start: testStart,
|
||||
}
|
||||
|
||||
svc = websvc.New(c)
|
||||
|
||||
err := svc.Start()
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
err = svc.Shutdown(ctx)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
addrs := svc.Addrs()
|
||||
require.Len(t, addrs, 1)
|
||||
|
||||
return svc, addrs[0]
|
||||
}
|
||||
|
||||
// httpGet is a helper that performs an HTTP GET request and returns the body of
|
||||
// the response as well as checks that the status code is correct.
|
||||
//
|
||||
// TODO(a.garipov): Add helpers for other methods.
|
||||
func httpGet(t testing.TB, u *url.URL, wantCode int) (body []byte) {
|
||||
t.Helper()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
require.NoErrorf(t, err, "creating req")
|
||||
|
||||
httpCli := &http.Client{
|
||||
Timeout: testTimeout,
|
||||
}
|
||||
resp, err := httpCli.Do(req)
|
||||
require.NoErrorf(t, err, "performing req")
|
||||
require.Equal(t, wantCode, resp.StatusCode)
|
||||
|
||||
testutil.CleanupAndRequireSuccess(t, resp.Body.Close)
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
require.NoErrorf(t, err, "reading body")
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
func TestService_Start_getHealthCheck(t *testing.T) {
|
||||
_, addr := newTestServer(t)
|
||||
u := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: addr,
|
||||
Path: websvc.PathHealthCheck,
|
||||
}
|
||||
|
||||
body := httpGet(t, u, http.StatusOK)
|
||||
|
||||
assert.Equal(t, []byte("OK"), body)
|
||||
}
|
||||
21
main_v1.go
Normal file
21
main_v1.go
Normal file
@@ -0,0 +1,21 @@
|
||||
//go:build v1
|
||||
// +build v1
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/v1/cmd"
|
||||
)
|
||||
|
||||
// Embed the prebuilt client here since we strive to keep .go files inside the
|
||||
// internal directory and the embed package is unable to embed files located
|
||||
// outside of the same or underlying directory.
|
||||
|
||||
//go:embed build2
|
||||
var clientBuildFS embed.FS
|
||||
|
||||
func main() {
|
||||
cmd.Main(clientBuildFS)
|
||||
}
|
||||
@@ -4,7 +4,10 @@
|
||||
|
||||
## v0.108.0: API changes
|
||||
|
||||
## v0.107.7: API changes
|
||||
### The new optional field `"ecs"` in `QueryLogItem`
|
||||
|
||||
* The new optional field `"ecs"` in `GET /control/querylog` contains the IP
|
||||
network from an EDNS Client-Subnet option from the request message if any.
|
||||
|
||||
### The new possible status code in `/install/configure` response.
|
||||
|
||||
@@ -12,11 +15,6 @@
|
||||
`POST /install/configure` which means that the specified password does not
|
||||
meet the strength requirements.
|
||||
|
||||
### The new optional field `"ecs"` in `QueryLogItem`
|
||||
|
||||
* The new optional field `"ecs"` in `GET /control/querylog` contains the IP
|
||||
network from an EDNS Client-Subnet option from the request message if any.
|
||||
|
||||
## v0.107.3: API changes
|
||||
|
||||
### The new field `"version"` in `AddressesInfo`
|
||||
|
||||
4925
openapi/v1.yaml
Normal file
4925
openapi/v1.yaml
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user