Compare commits

..

2 Commits

Author SHA1 Message Date
Artem Krisanov
521aedc5bc Merge branch 'master' of ssh://bit.adguard.com:7999/dns/adguard-home into AG-21485 2023-04-18 14:27:28 +03:00
Artem Krisanov
40ff26ea21 Login theme bugfix. 2023-04-18 14:11:28 +03:00
166 changed files with 3398 additions and 7592 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

1
.gitignore vendored
View File

@@ -21,6 +21,7 @@
/snapcraft_login /snapcraft_login
AdGuardHome* AdGuardHome*
coverage.txt coverage.txt
leases.db
node_modules/ node_modules/
!/build/gitkeep !/build/gitkeep

View File

@@ -14,164 +14,20 @@ and this project adheres to
<!-- <!--
## [v0.108.0] - TBA ## [v0.108.0] - TBA
## [v0.107.33] - 2023-06-28 (APPROX.) ## [v0.107.29] - 2023-04-26 (APPROX.)
See also the [v0.107.33 GitHub milestone][ms-v0.107.33]. See also the [v0.107.29 GitHub milestone][ms-v0.107.29].
[ms-v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/milestone/68?closed=1 [ms-v0.107.29]: https://github.com/AdguardTeam/AdGuardHome/milestone/65?closed=1
NOTE: Add new changes BELOW THIS COMMENT. NOTE: Add new changes BELOW THIS COMMENT.
--> -->
### Added ### Added
- The new HTTP API, `GET /control/querylog/export`, which can be used to
export query log items. See `openapi/openapi.yaml` for the full description
([#3389]).
- The ability to set inactivity periods for filtering blocked services in the
configuration file ([#951]). The UI changes are coming in the upcoming
releases.
- The ability to edit rewrite rules via `PUT /control/rewrite/update` HTTP API
([#1577]).
### Changed
#### Configuration Changes
In this release, the schema version has changed from 20 to 21.
- Property `dns.blocked_services`, which in schema versions 20 and earlier used
to be a list containing ids of blocked services, is now an object containing
ids and schedule for blocked services:
```yaml
# BEFORE:
'blocked_services':
- id_1
- id_2
# AFTER:
'blocked_services':
'ids':
- id_1
- id_2
'schedule':
'time_zone': 'Local'
'sun':
'start': '0s'
'end': '24h'
'mon':
'start': '10m'
'end': '23h30m'
'tue':
'start': '20m'
'end': '23h'
'wed':
'start': '30m'
'end': '22h30m'
'thu':
'start': '40m'
'end': '22h'
'fri':
'start': '50m'
'end': '21h30m'
'sat':
'start': '1h'
'end': '21h'
```
To rollback this change, replace `dns.blocked_services` object with the list
of ids of blocked services and change the `schema_version` back to `20`.
### Fixed
- Queries with the question-section target `.`, for example `NS .`, are now
counted in the statistics and correctly shown in the query log ([#5910]).
- Safe Search not working with `AAAA` queries for domains that don't have `AAAA`
records ([#5913]).
[#951]: https://github.com/AdguardTeam/AdGuardHome/issues/951
[#1577]: https://github.com/AdguardTeam/AdGuardHome/issues/1577
[#3389]: https://github.com/AdguardTeam/AdGuardHome/issues/3389
[#5910]: https://github.com/AdguardTeam/AdGuardHome/issues/5910
[#5913]: https://github.com/AdguardTeam/AdGuardHome/issues/5913
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
## [v0.107.32] - 2023-06-13
### Fixed
- DNSCrypt upstream not resetting the client and resolver information on
dialing errors ([#5872]).
## [v0.107.31] - 2023-06-08
See also the [v0.107.31 GitHub milestone][ms-v0.107.31].
### Fixed
- Startup errors on OpenWrt ([#5872]).
- Plain-UDP upstreams always falling back to TCP, causing outages and slowdowns
([#5873], [#5874]).
[#5872]: https://github.com/AdguardTeam/AdGuardHome/issues/5872
[#5873]: https://github.com/AdguardTeam/AdGuardHome/issues/5873
[#5874]: https://github.com/AdguardTeam/AdGuardHome/issues/5874
[ms-v0.107.31]: https://github.com/AdguardTeam/AdGuardHome/milestone/67?closed=1
## [v0.107.30] - 2023-06-07
See also the [v0.107.30 GitHub milestone][ms-v0.107.30].
### Security
- Go version has been updated to prevent the possibility of exploiting the
CVE-2023-29402, CVE-2023-29403, and CVE-2023-29404 Go vulnerabilities fixed in
[Go 1.19.10][go-1.19.10].
### Fixed
- Unquoted IPv6 bind hosts with trailing colons erroneously considered
unspecified addresses are now properly validated ([#5752]).
**NOTE:** the Docker healthcheck script now also doesn't interpret the `""`
value as unspecified address.
- Incorrect `Content-Type` header value in `POST /control/version.json` and `GET
/control/dhcp/interfaces` HTTP APIs ([#5716]).
- Provided bootstrap servers are now used to resolve the hostnames of plain
UDP/TCP upstream servers.
[#5716]: https://github.com/AdguardTeam/AdGuardHome/issues/5716
[go-1.19.10]: https://groups.google.com/g/golang-announce/c/q5135a9d924/m/j0ZoAJOHAwAJ
[ms-v0.107.30]: https://github.com/AdguardTeam/AdGuardHome/milestone/66?closed=1
## [v0.107.29] - 2023-04-18
See also the [v0.107.29 GitHub milestone][ms-v0.107.29].
### Added
- The ability to exclude client activity from the query log or statistics by - The ability to exclude client activity from the query log or statistics by
editing client's settings on the respective page in the UI ([#1717], [#4299]). editing client's settings on the Clients settings page in the UI ([#1717],
[#4299]).
### Changed
- Stored DHCP leases moved from `leases.db` to `data/leases.json`. The file
format has also been optimized.
### Fixed ### Fixed
@@ -182,12 +38,14 @@ See also the [v0.107.29 GitHub milestone][ms-v0.107.29].
- All Safe Search services being unchecked by default. - All Safe Search services being unchecked by default.
- Panic when a DNSCrypt stamp is invalid ([#5721]). - Panic when a DNSCrypt stamp is invalid ([#5721]).
[#5712]: https://github.com/AdguardTeam/AdGuardHome/issues/5712 [#1717]: https://github.com/AdguardTeam/AdGuardHome/issues/1717
[#4299]: https://github.com/AdguardTeam/AdGuardHome/issues/4299
[#5721]: https://github.com/AdguardTeam/AdGuardHome/issues/5721 [#5721]: https://github.com/AdguardTeam/AdGuardHome/issues/5721
[#5725]: https://github.com/AdguardTeam/AdGuardHome/issues/5725 [#5725]: https://github.com/AdguardTeam/AdGuardHome/issues/5725
[#5752]: https://github.com/AdguardTeam/AdGuardHome/issues/5752
[ms-v0.107.29]: https://github.com/AdguardTeam/AdGuardHome/milestone/65?closed=1 <!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
@@ -312,9 +170,11 @@ In this release, the schema version has changed from 17 to 20.
[#1163]: https://github.com/AdguardTeam/AdGuardHome/issues/1163 [#1163]: https://github.com/AdguardTeam/AdGuardHome/issues/1163
[#1333]: https://github.com/AdguardTeam/AdGuardHome/issues/1333 [#1333]: https://github.com/AdguardTeam/AdGuardHome/issues/1333
[#1472]: https://github.com/AdguardTeam/AdGuardHome/issues/1472 [#1472]: https://github.com/AdguardTeam/AdGuardHome/issues/1472
[#1717]: https://github.com/AdguardTeam/AdGuardHome/issues/1717
[#3290]: https://github.com/AdguardTeam/AdGuardHome/issues/3290 [#3290]: https://github.com/AdguardTeam/AdGuardHome/issues/3290
[#3459]: https://github.com/AdguardTeam/AdGuardHome/issues/3459 [#3459]: https://github.com/AdguardTeam/AdGuardHome/issues/3459
[#4262]: https://github.com/AdguardTeam/AdGuardHome/issues/4262 [#4262]: https://github.com/AdguardTeam/AdGuardHome/issues/4262
[#4299]: https://github.com/AdguardTeam/AdGuardHome/issues/4299
[#5567]: https://github.com/AdguardTeam/AdGuardHome/issues/5567 [#5567]: https://github.com/AdguardTeam/AdGuardHome/issues/5567
[#5701]: https://github.com/AdguardTeam/AdGuardHome/issues/5701 [#5701]: https://github.com/AdguardTeam/AdGuardHome/issues/5701
@@ -2080,15 +1940,11 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!-- <!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.29...HEAD
[v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...v0.107.33 [v0.107.29]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.28...v0.107.29
--> -->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.28...HEAD
[v0.107.32]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.31...v0.107.32
[v0.107.31]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.30...v0.107.31
[v0.107.30]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.29...v0.107.30
[v0.107.29]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.28...v0.107.29
[v0.107.28]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.27...v0.107.28 [v0.107.28]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.27...v0.107.28
[v0.107.27]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.26...v0.107.27 [v0.107.27]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.26...v0.107.27
[v0.107.26]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.25...v0.107.26 [v0.107.26]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.25...v0.107.26

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
# Make sure to sync any changes with the branch overrides below. # Make sure to sync any changes with the branch overrides below.
'variables': 'variables':
'channel': 'edge' 'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:6.7' 'dockerGo': 'adguard/golang-ubuntu:6.3'
'stages': 'stages':
- 'Build frontend': - 'Build frontend':
@@ -34,6 +34,12 @@
'jobs': 'jobs':
- 'Publish to static storage' - 'Publish to static storage'
- 'Publish to Snapstore':
'manual': false
'final': false
'jobs':
- 'Publish to Snapstore'
- 'Publish to GitHub Releases': - 'Publish to GitHub Releases':
'manual': false 'manual': false
'final': false 'final': false
@@ -65,7 +71,7 @@
make js-deps js-build make js-deps js-build
'artifacts': 'artifacts':
- 'name': 'AdGuardHome frontend' - 'name': 'AdGuardHome frontend'
'pattern': 'build/**' 'pattern': 'build*/**'
'shared': true 'shared': true
'required': true 'required': true
'requirements': 'requirements':
@@ -198,6 +204,59 @@
'requirements': 'requirements':
- 'adg-docker': 'true' - 'adg-docker': 'true'
'Publish to Snapstore':
'docker':
'image': '${bamboo.dockerGo}'
'key': 'PTS'
'other':
'clean-working-dir': true
'tasks':
- 'clean'
- 'checkout':
'repository': 'bamboo-deploy-publisher'
'path': 'bamboo-deploy-publisher'
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
set -e -f -u -x
cd ./dist/
channel="${bamboo.channel}"
readonly channel
case "$channel"
in
('release')
snapchannel='candidate'
;;
('beta')
snapchannel='beta'
;;
('edge')
snapchannel='edge'
;;
(*)
echo "invalid channel '$channel'"
exit 1
;;
esac
env\
SNAPCRAFT_CHANNEL="$snapchannel"\
SNAPCRAFT_EMAIL="${bamboo.snapcraftEmail}"\
SNAPCRAFT_MACAROON="${bamboo.snapcraftMacaroonPassword}"\
SNAPCRAFT_UBUNTU_DISCHARGE="${bamboo.snapcraftUbuntuDischargePassword}"\
../bamboo-deploy-publisher/deploy.sh adguard-home-snap
'final-tasks':
- 'clean'
'requirements':
- 'adg-docker': 'true'
'Publish to GitHub Releases': 'Publish to GitHub Releases':
'key': 'PTGR' 'key': 'PTGR'
'other': 'other':
@@ -237,8 +296,8 @@
- 'adg-docker': 'true' - 'adg-docker': 'true'
'triggers': 'triggers':
# Don't use minute values that end with a zero or a five as these are often # Don't use minute values that end with a zero or a five as these are often used
# used in CI and so resources during these minutes can be quite busy. # in CI and so resources during these minutes can be quite busy.
- 'cron': '0 42 13 ? * MON-FRI *' - 'cron': '0 42 13 ? * MON-FRI *'
'branches': 'branches':
'create': 'manually' 'create': 'manually'
@@ -263,8 +322,8 @@
'concurrent-build-plugin': 'system-default' 'concurrent-build-plugin': 'system-default'
'branch-overrides': 'branch-overrides':
# beta-vX.Y branches are the branches into which the commits that are needed # beta-vX.Y branches are the branches into which the commits that are needed to
# to release a new patch version are initially cherry-picked. # release a new patch version are initially cherry-picked.
- '^beta-v[0-9]+\.[0-9]+': - '^beta-v[0-9]+\.[0-9]+':
# Build betas on release branches manually. # Build betas on release branches manually.
'triggers': [] 'triggers': []
@@ -272,9 +331,9 @@
# need to build a few of these. # need to build a few of these.
'variables': 'variables':
'channel': 'beta' 'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:6.7' 'dockerGo': 'adguard/golang-ubuntu:6.3'
# release-vX.Y.Z branches are the branches from which the actual final # release-vX.Y.Z branches are the branches from which the actual final release
# release is built. # is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+': - '^release-v[0-9]+\.[0-9]+\.[0-9]+':
# Disable integration branches for release branches. # Disable integration branches for release branches.
'branch-config': 'branch-config':
@@ -287,4 +346,4 @@
# are the ones that actually get released. # are the ones that actually get released.
'variables': 'variables':
'channel': 'release' 'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:6.7' 'dockerGo': 'adguard/golang-ubuntu:6.3'

View File

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

View File

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

View File

@@ -635,6 +635,5 @@
"parental_control": "الرقابة الابويه", "parental_control": "الرقابة الابويه",
"safe_browsing": "تصفح آمن", "safe_browsing": "تصفح آمن",
"served_from_cache": "{{value}} <i>(يتم تقديمه من ذاكرة التخزين المؤقت)</i>", "served_from_cache": "{{value}} <i>(يتم تقديمه من ذاكرة التخزين المؤقت)</i>",
"form_error_password_length": "يجب أن تتكون كلمة المرور من {{value}} من الأحرف على الأقل", "form_error_password_length": "يجب أن تتكون كلمة المرور من {{value}} من الأحرف على الأقل"
"protection_section_label": "الحماية"
} }

View File

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

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "Protokol dotazů byl úspěšně vymazán", "query_log_cleared": "Protokol dotazů byl úspěšně vymazán",
"query_log_updated": "Protokol dotazů byl úspěšně aktualizován", "query_log_updated": "Protokol dotazů byl úspěšně aktualizován",
"query_log_clear": "Vymazat protokoly dotazů", "query_log_clear": "Vymazat protokoly dotazů",
"query_log_retention": "Rotace protokolů dotazů", "query_log_retention": "Uchování protokolů dotazů",
"query_log_enable": "Povolit protokol", "query_log_enable": "Povolit protokol",
"query_log_configuration": "Konfigurace protokolů", "query_log_configuration": "Konfigurace protokolů",
"query_log_disabled": "Protokol dotazu je zakázán a lze jej nakonfigurovat v <0>nastavení</0>", "query_log_disabled": "Protokol dotazu je zakázán a lze jej nakonfigurovat v <0>nastavení</0>",
"query_log_strict_search": "Pro striktní vyhledávání použijte dvojité uvozovky", "query_log_strict_search": "Pro striktní vyhledávání použijte dvojité uvozovky",
"query_log_retention_confirm": "Opravdu chcete změnit rotaci protokolu dotazů? Pokud snížíte hodnotu intervalu, některá data budou ztracena", "query_log_retention_confirm": "Opravdu chcete změnit uchovávání protokolu dotazů? Pokud snížíte hodnotu intervalu, některá data budou ztracena",
"anonymize_client_ip": "Anonymizovat IP klienta", "anonymize_client_ip": "Anonymizovat IP klienta",
"anonymize_client_ip_desc": "Neukládat úplnou IP adresu klienta do protokolů a statistik", "anonymize_client_ip_desc": "Neukládat úplnou IP adresu klienta do protokolů a statistik",
"dns_config": "Konfigurace DNS serveru", "dns_config": "Konfigurace DNS serveru",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Vypnout ochranu na {{count}} hod.", "disable_notify_for_hours": "Vypnout ochranu na {{count}} hod.",
"disable_notify_for_hours_plural": "Vypnout ochranu na {{count}} hod.", "disable_notify_for_hours_plural": "Vypnout ochranu na {{count}} hod.",
"disable_notify_until_tomorrow": "Vypnout ochranu do zítřka", "disable_notify_until_tomorrow": "Vypnout ochranu do zítřka",
"enable_protection_timer": "Ochrana bude zapnuta za {{time}}", "enable_protection_timer": "Ochrana bude zapnuta za {{time}}"
"custom_retention_input": "Zadejte retenci v hodinách",
"custom_rotation_input": "Zadejte rotaci v hodinách",
"protection_section_label": "Ochrana",
"log_and_stats_section_label": "Protokol dotazů a statistiky",
"ignore_query_log": "Ignorovat tohoto klienta v protokolu dotazů",
"ignore_statistics": "Ignorovat tohoto klienta ve statistikách"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "Forespørgselsloggen er blevet ryddet", "query_log_cleared": "Forespørgselsloggen er blevet ryddet",
"query_log_updated": "Forespørgselsloggen er blevet opdateret", "query_log_updated": "Forespørgselsloggen er blevet opdateret",
"query_log_clear": "Ryd forespørgselslogfiler", "query_log_clear": "Ryd forespørgselslogfiler",
"query_log_retention": "Rotation af forespørgselslog", "query_log_retention": "Opbevar forespørgselslogger i",
"query_log_enable": "Aktivér log", "query_log_enable": "Aktivér log",
"query_log_configuration": "Opsætning af logger", "query_log_configuration": "Opsætning af logger",
"query_log_disabled": "Forespørgselsloggen er deaktiveret og kan opsættes i <0>indstillingerne</0>", "query_log_disabled": "Forespørgselsloggen er deaktiveret og kan opsættes i <0>indstillingerne</0>",
"query_log_strict_search": "Brug dobbelt anførselstegn til stringent søgning", "query_log_strict_search": "Brug dobbelt anførselstegn til stringent søgning",
"query_log_retention_confirm": "Sikker på, at forespørgselsloggens rotationstid skal ændres? Mindskes intervalværdien, mistes nogle data", "query_log_retention_confirm": "Sikker på, at du vil ændre forespørgselsloggens opbevaringperiode? Mindskes intervalværdien, mistes data",
"anonymize_client_ip": "Anonymisér klient-IP", "anonymize_client_ip": "Anonymisér klient-IP",
"anonymize_client_ip_desc": "Gem ikke fuld klient IP-adresse i logfiler eller statistikker", "anonymize_client_ip_desc": "Gem ikke fuld klient IP-adresse i logfiler eller statistikker",
"dns_config": "DNS-serveropsætning", "dns_config": "DNS-serveropsætning",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Deaktivere beskyttelse i {{count}} time", "disable_notify_for_hours": "Deaktivere beskyttelse i {{count}} time",
"disable_notify_for_hours_plural": "Deaktivere beskyttelse i {{count}} timer", "disable_notify_for_hours_plural": "Deaktivere beskyttelse i {{count}} timer",
"disable_notify_until_tomorrow": "Deaktiver beskyttelse indtil i morgen", "disable_notify_until_tomorrow": "Deaktiver beskyttelse indtil i morgen",
"enable_protection_timer": "Beskyttelse deaktiveres om {{time}}", "enable_protection_timer": "Beskyttelse deaktiveres om {{time}}"
"custom_retention_input": "Angiv opbevaringstid i timer",
"custom_rotation_input": "Angiv rotationstid i timer",
"protection_section_label": "Beskyttelse",
"log_and_stats_section_label": "Forespørgselslog og statistik",
"ignore_query_log": "Ignorér denne klient i forespørgselslog",
"ignore_statistics": "Ignorér denne klient i statistik"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "Das Abfrageprotokoll wurde erfolgreich gelöscht", "query_log_cleared": "Das Abfrageprotokoll wurde erfolgreich gelöscht",
"query_log_updated": "Das Abfrageprotokoll wurde erfolgreich aktualisiert", "query_log_updated": "Das Abfrageprotokoll wurde erfolgreich aktualisiert",
"query_log_clear": "Abfrageprotokolle leeren", "query_log_clear": "Abfrageprotokolle leeren",
"query_log_retention": "Rotation der Abfrageprotokolle", "query_log_retention": "Abfrageprotokolle aufbewahren",
"query_log_enable": "Protokoll aktivieren", "query_log_enable": "Protokoll aktivieren",
"query_log_configuration": "Konfiguration der Protokolle", "query_log_configuration": "Konfiguration der Protokolle",
"query_log_disabled": "Das Abfrageprotokoll ist deaktiviert und kann in den <0>Einstellungen</0> konfiguriert werden.", "query_log_disabled": "Das Abfrageprotokoll ist deaktiviert und kann in den <0>Einstellungen</0> konfiguriert werden.",
"query_log_strict_search": "Doppelte Anführungszeichen für die strikte Suche verwenden", "query_log_strict_search": "Doppelte Anführungszeichen für die strikte Suche verwenden",
"query_log_retention_confirm": "Möchten Sie die Abfrageprotokollrotation wirklich ändern? Wenn Sie den Intervallwert verringern, gehen einige Daten verloren", "query_log_retention_confirm": "Möchten Sie die Aufbewahrung des Abfrageprotokolls wirklich ändern? Wenn Sie den Zeitabstand verringern, gehen einige Daten verloren.",
"anonymize_client_ip": "Client-IP anonymisieren", "anonymize_client_ip": "Client-IP anonymisieren",
"anonymize_client_ip_desc": "Vollständige IP-Adresse des Clients nicht in Protokollen und Statistiken speichern", "anonymize_client_ip_desc": "Vollständige IP-Adresse des Clients nicht in Protokollen und Statistiken speichern",
"dns_config": "DNS-Serverkonfiguration", "dns_config": "DNS-Serverkonfiguration",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Schutz für {{count}} Stunde deaktivieren", "disable_notify_for_hours": "Schutz für {{count}} Stunde deaktivieren",
"disable_notify_for_hours_plural": "Schutz für {{count}} Stunden deaktivieren", "disable_notify_for_hours_plural": "Schutz für {{count}} Stunden deaktivieren",
"disable_notify_until_tomorrow": "Schutz bis morgen deaktivieren", "disable_notify_until_tomorrow": "Schutz bis morgen deaktivieren",
"enable_protection_timer": "Der Schutz wird in {{time}} wieder aktiviert", "enable_protection_timer": "Der Schutz wird in {{time}} wieder aktiviert"
"custom_retention_input": "Rückhaltezeit in Stunden eingeben",
"custom_rotation_input": "Rotation in Stunden eingeben",
"protection_section_label": "Schutz",
"log_and_stats_section_label": "Abfrageprotokoll und Statistik",
"ignore_query_log": "Diesen Client im Abfrageprotokoll ignorieren",
"ignore_statistics": "Diesen Client in der Statistik ignorieren"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "El registro de consultas se ha borrado correctamente", "query_log_cleared": "El registro de consultas se ha borrado correctamente",
"query_log_updated": "El registro de consultas se ha actualizado correctamente", "query_log_updated": "El registro de consultas se ha actualizado correctamente",
"query_log_clear": "Borrar registros de consultas", "query_log_clear": "Borrar registros de consultas",
"query_log_retention": "Rotanción de registros de consultas", "query_log_retention": "Retención de registros de consultas",
"query_log_enable": "Habilitar registro", "query_log_enable": "Habilitar registro",
"query_log_configuration": "Configuración de registros", "query_log_configuration": "Configuración de registros",
"query_log_disabled": "El registro de consultas está deshabilitado y se puede configurar en la <0>configuración</0>", "query_log_disabled": "El registro de consultas está deshabilitado y se puede configurar en la <0>configuración</0>",
"query_log_strict_search": "Usar comillas dobles para una búsqueda estricta", "query_log_strict_search": "Usar comillas dobles para una búsqueda estricta",
"query_log_retention_confirm": "¿Está seguro de que deseas cambiar la rotación del registro de consultas? Si reduces el valor del intervalo, se perderán algunos datos", "query_log_retention_confirm": "¿Estás seguro de que deseas cambiar la retención del registro de consultas? Si disminuye el valor del intervalo, se perderán algunos datos",
"anonymize_client_ip": "Anonimizar IP del cliente", "anonymize_client_ip": "Anonimizar IP del cliente",
"anonymize_client_ip_desc": "No guarda la dirección IP completa del cliente en registros o estadísticas", "anonymize_client_ip_desc": "No guarda la dirección IP completa del cliente en registros o estadísticas",
"dns_config": "Configuración del servidor DNS", "dns_config": "Configuración del servidor DNS",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Desactivar la protección por {{count}} hora", "disable_notify_for_hours": "Desactivar la protección por {{count}} hora",
"disable_notify_for_hours_plural": "Desactivar la protección por {{count}} horas", "disable_notify_for_hours_plural": "Desactivar la protección por {{count}} horas",
"disable_notify_until_tomorrow": "Desactivar la protección hasta mañana", "disable_notify_until_tomorrow": "Desactivar la protección hasta mañana",
"enable_protection_timer": "La protección se activará en {{time}}", "enable_protection_timer": "La protección se activará en {{time}}"
"custom_retention_input": "Ingresa la retención en horas",
"custom_rotation_input": "Ingresa la rotación en horas",
"protection_section_label": "Protección",
"log_and_stats_section_label": "Registro de consultas y estadísticas",
"ignore_query_log": "Ignorar este cliente en el registro de consultas",
"ignore_statistics": "Ignorar este cliente en las estadísticas"
} }

View File

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

View File

@@ -86,7 +86,7 @@
"request_details": "Pyynnön tiedot", "request_details": "Pyynnön tiedot",
"client_details": "Päätelaitteen tiedot", "client_details": "Päätelaitteen tiedot",
"details": "Yksityiskohdat", "details": "Yksityiskohdat",
"back": "Palaa takaisin", "back": "Takaisin",
"dashboard": "Tila", "dashboard": "Tila",
"settings": "Asetukset", "settings": "Asetukset",
"filters": "Suodattimet", "filters": "Suodattimet",
@@ -146,8 +146,8 @@
"no_servers_specified": "Palvelimia ei ole määritetty", "no_servers_specified": "Palvelimia ei ole määritetty",
"general_settings": "Yleiset asetukset", "general_settings": "Yleiset asetukset",
"dns_settings": "DNS-asetukset", "dns_settings": "DNS-asetukset",
"dns_blocklists": "DNS-estot", "dns_blocklists": "DNS-estolistat",
"dns_allowlists": "DNS-sallinnat", "dns_allowlists": "DNS-sallittujen listat",
"dns_blocklists_desc": "AdGuard Home estää estolistalla olevat verkkotunnukset.", "dns_blocklists_desc": "AdGuard Home estää estolistalla olevat verkkotunnukset.",
"dns_allowlists_desc": "DNS-sallittujen listalla olevat verkkotunnukset sallitaan myös silloin, jos ne ovat jollain muulla estolistalla.", "dns_allowlists_desc": "DNS-sallittujen listalla olevat verkkotunnukset sallitaan myös silloin, jos ne ovat jollain muulla estolistalla.",
"custom_filtering_rules": "Omat suodatussäännöt", "custom_filtering_rules": "Omat suodatussäännöt",
@@ -257,12 +257,12 @@
"query_log_cleared": "Pyyntöhistorian tyhjennys onnistui", "query_log_cleared": "Pyyntöhistorian tyhjennys onnistui",
"query_log_updated": "Pyyntöhistorian päivitys onnistui", "query_log_updated": "Pyyntöhistorian päivitys onnistui",
"query_log_clear": "Tyhjennä pyyntöhistoria", "query_log_clear": "Tyhjennä pyyntöhistoria",
"query_log_retention": "Kyselylokien kierto", "query_log_retention": "Pyyntöhistorian säilytys",
"query_log_enable": "Käytä historiaa", "query_log_enable": "Käytä historiaa",
"query_log_configuration": "Historian määritys", "query_log_configuration": "Historian määritys",
"query_log_disabled": "Pyyntöhistoria ei ole käytössä. Voit ottaa sen käyttöön <0>asetuksissa</0>", "query_log_disabled": "Pyyntöhistoria ei ole käytössä. Voit ottaa sen käyttöön <0>asetuksissa</0>",
"query_log_strict_search": "Käytä tarkalle haulle lainausmerkkejä", "query_log_strict_search": "Käytä tarkalle haulle lainausmerkkejä",
"query_log_retention_confirm": "Haluatko varmasti muuttaa kyselylokin kiertoa? Jos pienennät intervalliarvoa, osa tiedoista menetetään", "query_log_retention_confirm": "Haluatko varmasti muuttaa pyyntöhistoriasi säilytysaikaa? Jos lyhennät aikaa, joitakin tietoja menetetään",
"anonymize_client_ip": "Piilota päätelaitteen IP-osoite", "anonymize_client_ip": "Piilota päätelaitteen IP-osoite",
"anonymize_client_ip_desc": "Älä tallenna päätelaitteen täydellistä IP-osoitetta historiaan ja tilastoihin.", "anonymize_client_ip_desc": "Älä tallenna päätelaitteen täydellistä IP-osoitetta historiaan ja tilastoihin.",
"dns_config": "DNS-palvelimen määritys", "dns_config": "DNS-palvelimen määritys",
@@ -627,7 +627,7 @@
"cache_optimistic": "Optimistinen välimuisti", "cache_optimistic": "Optimistinen välimuisti",
"cache_optimistic_desc": "Pakota AdGuard Home vastaamaan välimuistista vaikka tiedot olisivat vanhentuneet. Pyri samalla myös päivittämään tiedot.", "cache_optimistic_desc": "Pakota AdGuard Home vastaamaan välimuistista vaikka tiedot olisivat vanhentuneet. Pyri samalla myös päivittämään tiedot.",
"filter_category_general": "Yleiset", "filter_category_general": "Yleiset",
"filter_category_security": "Tietoturva", "filter_category_security": "Turvallisuus",
"filter_category_regional": "Alueelliset", "filter_category_regional": "Alueelliset",
"filter_category_other": "Muut", "filter_category_other": "Muut",
"filter_category_general_desc": "Listat, jotka estävät seurannan ja mainokset useimmilla laitteilla", "filter_category_general_desc": "Listat, jotka estävät seurannan ja mainokset useimmilla laitteilla",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Poista suojaus käytöstä {{count}} tunniksi", "disable_notify_for_hours": "Poista suojaus käytöstä {{count}} tunniksi",
"disable_notify_for_hours_plural": "Poista suojaus käytöstä {{count}} tunniksi", "disable_notify_for_hours_plural": "Poista suojaus käytöstä {{count}} tunniksi",
"disable_notify_until_tomorrow": "Poista suojaus käytöstä huomiseen asti", "disable_notify_until_tomorrow": "Poista suojaus käytöstä huomiseen asti",
"enable_protection_timer": "Suojaus otetaan käyttöön {{time}} kuluttua", "enable_protection_timer": "Suojaus otetaan käyttöön {{time}} kuluttua"
"custom_retention_input": "Syötä säilytysaika tunteina",
"custom_rotation_input": "Syötä uudistusaika tunteina",
"protection_section_label": "Suojaus",
"log_and_stats_section_label": "Kyselyhistoria ja tilastot",
"ignore_query_log": "Älä huomioi tätä päätettä kyselyhistoriassa",
"ignore_statistics": "Älä huomioi tätä päätettä tilastoissa"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "Le journal des requêtes a été effacé", "query_log_cleared": "Le journal des requêtes a été effacé",
"query_log_updated": "Le journal des requêtes a été mis à jour", "query_log_updated": "Le journal des requêtes a été mis à jour",
"query_log_clear": "Effacer journal des requêtes", "query_log_clear": "Effacer journal des requêtes",
"query_log_retention": "Rotation des journaux de requêtes", "query_log_retention": "Rétention du journal des requêtes",
"query_log_enable": "Activer le journal", "query_log_enable": "Activer le journal",
"query_log_configuration": "Configuration du journal", "query_log_configuration": "Configuration du journal",
"query_log_disabled": "Le journal des requêtes est désactivé et peut être configuré dans les <0>paramètres</0>", "query_log_disabled": "Le journal des requêtes est désactivé et peut être configuré dans les <0>paramètres</0>",
"query_log_strict_search": "Utilisez les doubles guillemets pour une recherche stricte", "query_log_strict_search": "Utilisez les doubles guillemets pour une recherche stricte",
"query_log_retention_confirm": "Êtes-vous sûr de souhaiter modifier la rotation des journaux de requêtes ? Si vous diminuez la valeur de l'intervalle, certaines données seront perdues", "query_log_retention_confirm": "Êtes-vous sûr de vouloir modifier la rétention des journaux de requêtes ? Si vous diminuez la valeur de l'intervalle, certaines données seront perdues",
"anonymize_client_ip": "Anonymiser lIP du client", "anonymize_client_ip": "Anonymiser lIP du client",
"anonymize_client_ip_desc": "Ne pas enregistrer ladresse IP complète du client dans les journaux et statistiques", "anonymize_client_ip_desc": "Ne pas enregistrer ladresse IP complète du client dans les journaux et statistiques",
"dns_config": "Configuration du serveur DNS", "dns_config": "Configuration du serveur DNS",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Désactiver la protection pendant {{count}} heure", "disable_notify_for_hours": "Désactiver la protection pendant {{count}} heure",
"disable_notify_for_hours_plural": "Désactiver la protection pendant {{count}} heures", "disable_notify_for_hours_plural": "Désactiver la protection pendant {{count}} heures",
"disable_notify_until_tomorrow": "Désactiver la protection jusqu'à demain", "disable_notify_until_tomorrow": "Désactiver la protection jusqu'à demain",
"enable_protection_timer": "La protection sera activée dans {{time}}", "enable_protection_timer": "La protection sera activée dans {{time}}"
"custom_retention_input": "Saisir la rétention en heures",
"custom_rotation_input": "Saisir la rotation en heures",
"protection_section_label": "Protection",
"log_and_stats_section_label": "Journal des requêtes et statistiques",
"ignore_query_log": "Ignorer ce client dans le journal des requêtes",
"ignore_statistics": "Ignorer ce client dans les statistiques"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "Zapisnik upita je uspješno uklonjen", "query_log_cleared": "Zapisnik upita je uspješno uklonjen",
"query_log_updated": "Zapisnik upita je uspješno ažuriran", "query_log_updated": "Zapisnik upita je uspješno ažuriran",
"query_log_clear": "Očisti zapisnik upita", "query_log_clear": "Očisti zapisnik upita",
"query_log_retention": "Rotacija dnevnika upita", "query_log_retention": "Spremanje zapisnika upita",
"query_log_enable": "Omogući zapise", "query_log_enable": "Omogući zapise",
"query_log_configuration": "Postavke zapisa", "query_log_configuration": "Postavke zapisa",
"query_log_disabled": "Zapisnik upita je onemogućen i može se postaviti u <0>postavkama</0>", "query_log_disabled": "Zapisnik upita je onemogućen i može se postaviti u <0>postavkama</0>",
"query_log_strict_search": "Koristite dvostruke navodnike za strogo pretraživanje", "query_log_strict_search": "Koristite dvostruke navodnike za strogo pretraživanje",
"query_log_retention_confirm": "Jeste li sigurni da želite promijeniti rotaciju dnevnika upita? Ako smanjite vrijednost intervala, neki će se podaci izgubiti", "query_log_retention_confirm": "Jeste li sigurni da želite promijeniti zadržavanje zapisnika upita? Ako smanjite vrijednost intervala, neki će podaci biti izgubljeni",
"anonymize_client_ip": "Anonimiraj IP klijenta", "anonymize_client_ip": "Anonimiraj IP klijenta",
"anonymize_client_ip_desc": "Ne spremajte cijelu IP adresu klijenta u zapisnike i statistike", "anonymize_client_ip_desc": "Ne spremajte cijelu IP adresu klijenta u zapisnike i statistike",
"dns_config": "DNS postavke poslužitelja", "dns_config": "DNS postavke poslužitelja",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Isključi zaštitu na {{count}} sati", "disable_notify_for_hours": "Isključi zaštitu na {{count}} sati",
"disable_notify_for_hours_plural": "Isključi zaštitu na {{count}} sati", "disable_notify_for_hours_plural": "Isključi zaštitu na {{count}} sati",
"disable_notify_until_tomorrow": "Isključi zaštitu do sutra", "disable_notify_until_tomorrow": "Isključi zaštitu do sutra",
"enable_protection_timer": "Zaštita će biti omogućena u {{time}}", "enable_protection_timer": "Zaštita će biti omogućena u {{time}}"
"custom_retention_input": "Unesite zadržavanje u satima",
"custom_rotation_input": "Unesite rotaciju u satima",
"protection_section_label": "Zaštita",
"log_and_stats_section_label": "Zapisnik upita i statistika",
"ignore_query_log": "Zanemari ovog klijenta u zapisniku upita",
"ignore_statistics": "Ignorirajte ovog klijenta u statistici"
} }

View File

@@ -642,6 +642,5 @@
"anonymizer_notification": "<0>Megjegyzés:</0> Az IP anonimizálás engedélyezve van. Az <1>Általános beállításoknál letilthatja</1> .", "anonymizer_notification": "<0>Megjegyzés:</0> Az IP anonimizálás engedélyezve van. Az <1>Általános beállításoknál letilthatja</1> .",
"confirm_dns_cache_clear": "Biztos benne, hogy törölni szeretné a DNS-gyorsítótárat?", "confirm_dns_cache_clear": "Biztos benne, hogy törölni szeretné a DNS-gyorsítótárat?",
"cache_cleared": "A DNS gyorsítótár sikeresen törlődött", "cache_cleared": "A DNS gyorsítótár sikeresen törlődött",
"clear_cache": "Gyorsítótár törlése", "clear_cache": "Gyorsítótár törlése"
"protection_section_label": "Védelem"
} }

View File

@@ -641,6 +641,5 @@
"anonymizer_notification": "<0>Catatan:</0> Anonimisasi IP diaktifkan. Anda dapat menonaktifkannya di <1>Pengaturan umum</1> .", "anonymizer_notification": "<0>Catatan:</0> Anonimisasi IP diaktifkan. Anda dapat menonaktifkannya di <1>Pengaturan umum</1> .",
"confirm_dns_cache_clear": "Apakah Anda yakin ingin menghapus cache DNS?", "confirm_dns_cache_clear": "Apakah Anda yakin ingin menghapus cache DNS?",
"cache_cleared": "Cache DNS berhasil dibersihkan", "cache_cleared": "Cache DNS berhasil dibersihkan",
"clear_cache": "Hapus cache", "clear_cache": "Hapus cache"
"protection_section_label": "Perlindungan"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "Il registro richieste è stato correttamente cancellato", "query_log_cleared": "Il registro richieste è stato correttamente cancellato",
"query_log_updated": "Il registro richieste è stato correttamente aggiornato", "query_log_updated": "Il registro richieste è stato correttamente aggiornato",
"query_log_clear": "Cancella registri richieste", "query_log_clear": "Cancella registri richieste",
"query_log_retention": "Rotazione dei registri richieste", "query_log_retention": "Conservazione dei registri richieste",
"query_log_enable": "Attiva registro", "query_log_enable": "Attiva registro",
"query_log_configuration": "Configurazione registri", "query_log_configuration": "Configurazione registri",
"query_log_disabled": "Il registro richieste è stato disattivato e può essere configurata dalle <0>impostazioni</0>", "query_log_disabled": "Il registro richieste è stato disattivato e può essere configurata dalle <0>impostazioni</0>",
"query_log_strict_search": "Utilizzare le doppie virgolette per una ricerca precisa", "query_log_strict_search": "Utilizzare le doppie virgolette per una ricerca precisa",
"query_log_retention_confirm": "Sei sicuro di voler modificare il registro delle richieste? Se si riduce il valore dell'intervallo, alcuni dati andranno persi", "query_log_retention_confirm": "Sei sicuro di voler modificare il registro delle richieste? Se il valore di intervallo dovesse diminuire, alcuni dati andranno persi",
"anonymize_client_ip": "Anonimizza client IP", "anonymize_client_ip": "Anonimizza client IP",
"anonymize_client_ip_desc": "Non salvare l'indirizzo IP completo del client nel registro o nelle statistiche", "anonymize_client_ip_desc": "Non salvare l'indirizzo IP completo del client nel registro o nelle statistiche",
"dns_config": "Configurazione server DNS", "dns_config": "Configurazione server DNS",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Disattiva la protezione per {{count}} ora", "disable_notify_for_hours": "Disattiva la protezione per {{count}} ora",
"disable_notify_for_hours_plural": "Disattiva la protezione per {{count}} ore", "disable_notify_for_hours_plural": "Disattiva la protezione per {{count}} ore",
"disable_notify_until_tomorrow": "Disattiva la protezione fino a domani", "disable_notify_until_tomorrow": "Disattiva la protezione fino a domani",
"enable_protection_timer": "La protezione verrà attivata in {{time}}", "enable_protection_timer": "La protezione verrà attivata in {{time}}"
"custom_retention_input": "Inserisci la conservazione in ore",
"custom_rotation_input": "Inserisci la rotazione in ore",
"protection_section_label": "Protezione",
"log_and_stats_section_label": "Registro richieste e statistiche",
"ignore_query_log": "Ignora questo client nel registro delle richieste",
"ignore_statistics": "Ignora questo cliente nelle statistiche"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "クエリ・ログの消去に成功しました", "query_log_cleared": "クエリ・ログの消去に成功しました",
"query_log_updated": "クエリ・ログの更新が成功しました", "query_log_updated": "クエリ・ログの更新が成功しました",
"query_log_clear": "クエリ・ログを消去する", "query_log_clear": "クエリ・ログを消去する",
"query_log_retention": "クエリ・ログのローテーション", "query_log_retention": "クエリ・ログの保持",
"query_log_enable": "ログを有効にする", "query_log_enable": "ログを有効にする",
"query_log_configuration": "ログ設定", "query_log_configuration": "ログ設定",
"query_log_disabled": "クエリ・ログは無効になっており、<0>設定</0>で構成できます", "query_log_disabled": "クエリ・ログは無効になっており、<0>設定</0>で構成できます",
"query_log_strict_search": "完全一致検索には二重引用符を使用します", "query_log_strict_search": "完全一致検索には二重引用符を使用します",
"query_log_retention_confirm": "クエリ・ログのローテーションを変更してもよろしいですか? 間隔の値を減らすと、一部のデータが失われます", "query_log_retention_confirm": "クエリ・ログの保持を変更してもよろしいですか? 期間を短くすると、一部のデータが失われます",
"anonymize_client_ip": "クライアントIPを匿名化する", "anonymize_client_ip": "クライアントIPを匿名化する",
"anonymize_client_ip_desc": "ログと統計にクライアントのフルIPアドレスを保存しないようにします。", "anonymize_client_ip_desc": "ログと統計にクライアントのフルIPアドレスを保存しないようにします。",
"dns_config": "DNSサーバ設定", "dns_config": "DNSサーバ設定",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "保護を {{count}} 時間無効にする", "disable_notify_for_hours": "保護を {{count}} 時間無効にする",
"disable_notify_for_hours_plural": "保護を {{count}} 時間無効にする", "disable_notify_for_hours_plural": "保護を {{count}} 時間無効にする",
"disable_notify_until_tomorrow": "明日まで保護を無効にする", "disable_notify_until_tomorrow": "明日まで保護を無効にする",
"enable_protection_timer": "保護は後 {{time}} で有効になります", "enable_protection_timer": "保護は後 {{time}} で有効になります"
"custom_retention_input": "保持期間を入力してください(時間単位)",
"custom_rotation_input": "ローテーションを入力してください(時間単位)",
"protection_section_label": "AdGuardによる保護",
"log_and_stats_section_label": "クエリ・ログと統計情報",
"ignore_query_log": "クエリ・ログでこのクライアントを無視する",
"ignore_statistics": "統計でこのクライアントを無視する"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "쿼리 로그를 성공적으로 초기화했습니다", "query_log_cleared": "쿼리 로그를 성공적으로 초기화했습니다",
"query_log_updated": "질의 로그가 성공적으로 업데이트되었습니다", "query_log_updated": "질의 로그가 성공적으로 업데이트되었습니다",
"query_log_clear": "쿼리 로그 비우기", "query_log_clear": "쿼리 로그 비우기",
"query_log_retention": "쿼리 로그 로테이션", "query_log_retention": "쿼리 로그 저장 기간",
"query_log_enable": "로그 활성화", "query_log_enable": "로그 활성화",
"query_log_configuration": "로그 구성", "query_log_configuration": "로그 구성",
"query_log_disabled": "쿼리 로그가 비활성화되어 있으며 <0>설정</0>에서 설정할 수 있습니다", "query_log_disabled": "쿼리 로그가 비활성화되어 있으며 <0>설정</0>에서 설정할 수 있습니다",
"query_log_strict_search": "검색을 제한하려면 쌍따옴표를 사용해주세요", "query_log_strict_search": "검색을 제한하려면 쌍따옴표를 사용해주세요",
"query_log_retention_confirm": "쿼리 로그 로테이션을 변경하시겠습니까? 간격 값을 줄이면 일부 데이터가 손실됩니다.", "query_log_retention_confirm": "정말로 쿼리 로그 저장 기간을 변경하시겠습니까? 저장 주기를 낮출 경우, 일부 데이터가 손실됩니다",
"anonymize_client_ip": "클라이언트 IP 익명화", "anonymize_client_ip": "클라이언트 IP 익명화",
"anonymize_client_ip_desc": "클라이언트의 전체 IP 주소를 로그와 통계에 저장하저장하지 마세요", "anonymize_client_ip_desc": "클라이언트의 전체 IP 주소를 로그와 통계에 저장하저장하지 마세요",
"dns_config": "DNS 서버 설정", "dns_config": "DNS 서버 설정",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "{{count}}시간 동안 보호 기능 비활성화", "disable_notify_for_hours": "{{count}}시간 동안 보호 기능 비활성화",
"disable_notify_for_hours_plural": "{{count}}시간 동안 보호 기능 비활성화", "disable_notify_for_hours_plural": "{{count}}시간 동안 보호 기능 비활성화",
"disable_notify_until_tomorrow": "내일까지 보호 기능 비활성화", "disable_notify_until_tomorrow": "내일까지 보호 기능 비활성화",
"enable_protection_timer": "{{time}}에 보호 기능이 활성화됩니다.", "enable_protection_timer": "{{time}}에 보호 기능이 활성화됩니다."
"custom_retention_input": "시간 단위로 보존 기간 입력",
"custom_rotation_input": "시간 단위로 로테이션 입력",
"protection_section_label": "보호",
"log_and_stats_section_label": "쿼리 로그 및 통계",
"ignore_query_log": "쿼리 로그에서 이 클라이언트 무시",
"ignore_statistics": "통계에서 이 클라이언트 무시"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "Het query logboek is succesvol geleegd", "query_log_cleared": "Het query logboek is succesvol geleegd",
"query_log_updated": "Het query logboek is succesvol bijgewerkt", "query_log_updated": "Het query logboek is succesvol bijgewerkt",
"query_log_clear": "Leeg query logs", "query_log_clear": "Leeg query logs",
"query_log_retention": "Query logs rotatie", "query_log_retention": "Query logs bewaartermijn",
"query_log_enable": "Log bestanden inschakelen", "query_log_enable": "Log bestanden inschakelen",
"query_log_configuration": "Logbestanden instellingen", "query_log_configuration": "Logbestanden instellingen",
"query_log_disabled": "Het query logboek is uitgeschakeld en kan worden geconfigureerd in de <0>instellingen</0>", "query_log_disabled": "Het query logboek is uitgeschakeld en kan worden geconfigureerd in de <0>instellingen</0>",
"query_log_strict_search": "Gebruik dubbele aanhalingstekens voor strikt zoeken", "query_log_strict_search": "Gebruik dubbele aanhalingstekens voor strikt zoeken",
"query_log_retention_confirm": "Weet u zeker dat u de rotatie van het querylogboek wilt wijzigen? Als u de intervalwaarde verlaagt, gaan sommige gegevens verloren", "query_log_retention_confirm": "Weet u zeker dat u de bewaartermijn van het query logboek wilt wijzigen? Als u de intervalwaarde verlaagt, gaan sommige gegevens verloren",
"anonymize_client_ip": "Cliënt IP anonimiseren", "anonymize_client_ip": "Cliënt IP anonimiseren",
"anonymize_client_ip_desc": "Het volledige IP-adres van de cliënt niet opnemen in logboeken en statistiekbestanden", "anonymize_client_ip_desc": "Het volledige IP-adres van de cliënt niet opnemen in logboeken en statistiekbestanden",
"dns_config": "DNS-server configuratie", "dns_config": "DNS-server configuratie",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Beveiliging uitschakelen voor {{count}} uur", "disable_notify_for_hours": "Beveiliging uitschakelen voor {{count}} uur",
"disable_notify_for_hours_plural": "Beveiliging uitschakelen voor {{count}} uren", "disable_notify_for_hours_plural": "Beveiliging uitschakelen voor {{count}} uren",
"disable_notify_until_tomorrow": "Beveiliging uitschakelen tot morgen", "disable_notify_until_tomorrow": "Beveiliging uitschakelen tot morgen",
"enable_protection_timer": "Bescherming wordt ingeschakeld over {{time}}", "enable_protection_timer": "Bescherming wordt ingeschakeld over {{time}}"
"custom_retention_input": "Voer retentie in uren in",
"custom_rotation_input": "Voer rotatie in uren in",
"protection_section_label": "Bescherming",
"log_and_stats_section_label": "Aanvragenlogboek en statistieken",
"ignore_query_log": "Deze client negeren in het aanvragenlogboek",
"ignore_statistics": "Deze client negeren in de statistieken"
} }

View File

@@ -282,8 +282,6 @@
"blocking_mode_null_ip": "Null IP: Svar med en 0-IP-adresse (0.0.0.0 for A; :: for AAAA)", "blocking_mode_null_ip": "Null IP: Svar med en 0-IP-adresse (0.0.0.0 for A; :: for AAAA)",
"blocking_mode_custom_ip": "Tilpasset IP: Svar med en manuelt valgt IP-adresse", "blocking_mode_custom_ip": "Tilpasset IP: Svar med en manuelt valgt IP-adresse",
"theme_auto": "Auto", "theme_auto": "Auto",
"theme_light": "Lyst tema",
"theme_dark": "Mørkt tema",
"upstream_dns_client_desc": "Hvis dette feltet holdes tomt, vil AdGuard Home bruke tjenerne som er satt opp i <0>DNS-innstillingene</0>.", "upstream_dns_client_desc": "Hvis dette feltet holdes tomt, vil AdGuard Home bruke tjenerne som er satt opp i <0>DNS-innstillingene</0>.",
"tracker_source": "Sporerkilde", "tracker_source": "Sporerkilde",
"source_label": "Kilde", "source_label": "Kilde",
@@ -616,6 +614,5 @@
"use_saved_key": "Bruk den tidligere lagrede nøkkelen", "use_saved_key": "Bruk den tidligere lagrede nøkkelen",
"parental_control": "Foreldrekontroll", "parental_control": "Foreldrekontroll",
"safe_browsing": "Sikker surfing", "safe_browsing": "Sikker surfing",
"served_from_cache": "{{value}} <i>(formidlet fra mellomlageret)</i>", "served_from_cache": "{{value}} <i>(formidlet fra mellomlageret)</i>"
"protection_section_label": "Beskyttelse"
} }

View File

@@ -167,7 +167,6 @@
"enabled_parental_toast": "Włączona Kontrola Rodzicielska", "enabled_parental_toast": "Włączona Kontrola Rodzicielska",
"disabled_safe_search_toast": "Wyłączone bezpieczne wyszukiwanie", "disabled_safe_search_toast": "Wyłączone bezpieczne wyszukiwanie",
"enabled_save_search_toast": "Włączone bezpieczne wyszukiwanie", "enabled_save_search_toast": "Włączone bezpieczne wyszukiwanie",
"updated_save_search_toast": "Zaktualizowano ustawienia bezpiecznego wyszukiwania",
"enabled_table_header": "Włączone", "enabled_table_header": "Włączone",
"name_table_header": "Nazwa", "name_table_header": "Nazwa",
"list_url_table_header": "Adres URL listy", "list_url_table_header": "Adres URL listy",
@@ -222,7 +221,7 @@
"all_lists_up_to_date_toast": "Wszystkie listy są już aktualne", "all_lists_up_to_date_toast": "Wszystkie listy są już aktualne",
"updated_upstream_dns_toast": "Serwery nadrzędne zostały pomyślnie zapisane", "updated_upstream_dns_toast": "Serwery nadrzędne zostały pomyślnie zapisane",
"dns_test_ok_toast": "Określone serwery DNS działają poprawnie", "dns_test_ok_toast": "Określone serwery DNS działają poprawnie",
"dns_test_not_ok_toast": "Serwer \"{{key}}\": nie może być użyte, sprawdź, czy zapisano go poprawnie", "dns_test_not_ok_toast": "Serwer \"{{key}}\": nie można go użyć, sprawdź, czy napisałeś go poprawnie",
"dns_test_warning_toast": "Upstream \"{{key}}\" nie odpowiada na zapytania testowe i może nie działać prawidłowo", "dns_test_warning_toast": "Upstream \"{{key}}\" nie odpowiada na zapytania testowe i może nie działać prawidłowo",
"unblock": "Odblokuj", "unblock": "Odblokuj",
"block": "Zablokuj", "block": "Zablokuj",
@@ -257,12 +256,12 @@
"query_log_cleared": "Dziennik zapytań został pomyślnie wyczyszczony", "query_log_cleared": "Dziennik zapytań został pomyślnie wyczyszczony",
"query_log_updated": "Dziennik zapytań został zaktualizowany", "query_log_updated": "Dziennik zapytań został zaktualizowany",
"query_log_clear": "Wyczyść dzienniki zapytań", "query_log_clear": "Wyczyść dzienniki zapytań",
"query_log_retention": "Rotacja dzienników zapytań", "query_log_retention": "Przechowywanie dzienników zapytań",
"query_log_enable": "Włącz dziennik", "query_log_enable": "Włącz dziennik",
"query_log_configuration": "Konfiguracja dzienników", "query_log_configuration": "Konfiguracja dzienników",
"query_log_disabled": "Dziennik zapytań jest wyłączony i można go skonfigurować w <0>ustawieniach</0>", "query_log_disabled": "Dziennik zapytań jest wyłączony i można go skonfigurować w <0>ustawieniach</0>",
"query_log_strict_search": "Używaj podwójnych cudzysłowów do ścisłego wyszukiwania", "query_log_strict_search": "Używaj podwójnych cudzysłowów do ścisłego wyszukiwania",
"query_log_retention_confirm": "Czy na pewno chcesz zmienić rotację dziennika zapytań? Jeśli zmniejszysz wartość interwału, niektóre dane zostaną utracone", "query_log_retention_confirm": "Czy na pewno chcesz zmienić sposób przechowywania dziennika zapytań? Jeśli zmniejszysz wartość interwału, niektóre dane zostaną utracone",
"anonymize_client_ip": "Anonimizuj adres IP klienta", "anonymize_client_ip": "Anonimizuj adres IP klienta",
"anonymize_client_ip_desc": "Nie zapisuj pełnego adresu IP w dziennikach i statystykach", "anonymize_client_ip_desc": "Nie zapisuj pełnego adresu IP w dziennikach i statystykach",
"dns_config": "Konfiguracja serwera DNS", "dns_config": "Konfiguracja serwera DNS",
@@ -291,8 +290,6 @@
"rate_limit": "Limit ilościowy", "rate_limit": "Limit ilościowy",
"edns_enable": "Włącz podsieć klienta EDNS", "edns_enable": "Włącz podsieć klienta EDNS",
"edns_cs_desc": "Dodaj opcję podsieci klienta EDNS (ECS) do żądań nadrzędnych i rejestruj wartości wysyłane przez klientów w dzienniku zapytań.", "edns_cs_desc": "Dodaj opcję podsieci klienta EDNS (ECS) do żądań nadrzędnych i rejestruj wartości wysyłane przez klientów w dzienniku zapytań.",
"edns_use_custom_ip": "Użyj niestandardowego adresu IP dla EDNS",
"edns_use_custom_ip_desc": "Zezwól na użycie niestandardowego adresu IP dla EDNS",
"rate_limit_desc": "Liczba żądań na sekundę dozwolona na klienta. Ustawienie wartości 0 oznacza brak ograniczeń.", "rate_limit_desc": "Liczba żądań na sekundę dozwolona na klienta. Ustawienie wartości 0 oznacza brak ograniczeń.",
"blocking_ipv4_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania A", "blocking_ipv4_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania A",
"blocking_ipv6_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania AAAA", "blocking_ipv6_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania AAAA",
@@ -346,7 +343,7 @@
"install_devices_windows_list_2": "Przejdź do kategorii Sieć i Internet, a następnie do Centrum sieci i udostępniania.", "install_devices_windows_list_2": "Przejdź do kategorii Sieć i Internet, a następnie do Centrum sieci i udostępniania.",
"install_devices_windows_list_3": "W lewym panelu kliknij \"Zmień ustawienia adaptera\".", "install_devices_windows_list_3": "W lewym panelu kliknij \"Zmień ustawienia adaptera\".",
"install_devices_windows_list_4": "Kliknij prawym przyciskiem myszy aktywne połączenie i wybierz Właściwości.", "install_devices_windows_list_4": "Kliknij prawym przyciskiem myszy aktywne połączenie i wybierz Właściwości.",
"install_devices_windows_list_5": "Znajdź na liście \"Protokół internetowy w wersji 4 (TCP/IPv4)\" (lub w przypadku IPv6 \"Protokół internetowy w wersji 6 (TCP/IPv6)\"), zaznacz go i ponownie kliknij Właściwości.", "install_devices_windows_list_5": "Znajdź na liście \"Protokół internetowy w wersji 4 (TCP/IPv4)\" (lub w przypadku IPv6 \"Protokół internetowy w wersji 6 (TCP/IPv6)\"), zaznacz go i ponownie kliknij na Właściwości.",
"install_devices_windows_list_6": "Wybierz opcję \"Użyj następujących adresów serwerów DNS\" i wprowadź adresy serwerów AdGuard Home.", "install_devices_windows_list_6": "Wybierz opcję \"Użyj następujących adresów serwerów DNS\" i wprowadź adresy serwerów AdGuard Home.",
"install_devices_macos_list_1": "Kliknij ikonę Apple i przejdź do Preferencje systemowe.", "install_devices_macos_list_1": "Kliknij ikonę Apple i przejdź do Preferencje systemowe.",
"install_devices_macos_list_2": "Kliknij Sieć.", "install_devices_macos_list_2": "Kliknij Sieć.",
@@ -396,7 +393,7 @@
"encryption_issuer": "Zgłaszający", "encryption_issuer": "Zgłaszający",
"encryption_hostnames": "Nazwy hostów", "encryption_hostnames": "Nazwy hostów",
"encryption_reset": "Czy na pewno chcesz zresetować ustawienia szyfrowania?", "encryption_reset": "Czy na pewno chcesz zresetować ustawienia szyfrowania?",
"encryption_warning": "Uwaga", "encryption_warning": "Uwaga!",
"topline_expiring_certificate": "Twój certyfikat SSL wkrótce wygaśnie. Zaktualizuj <0>Ustawienia szyfrowania</0>.", "topline_expiring_certificate": "Twój certyfikat SSL wkrótce wygaśnie. Zaktualizuj <0>Ustawienia szyfrowania</0>.",
"topline_expired_certificate": "Twój certyfikat SSL wygasł. Zaktualizuj <0>Ustawienia szyfrowania</0>.", "topline_expired_certificate": "Twój certyfikat SSL wygasł. Zaktualizuj <0>Ustawienia szyfrowania</0>.",
"form_error_port_range": "Wpisz numer portu z zakresu 80-65535", "form_error_port_range": "Wpisz numer portu z zakresu 80-65535",
@@ -526,10 +523,6 @@
"statistics_retention_confirm": "Czy chcesz zmienić sposób przechowania statystyk? Jeżeli obniżysz wartość interwału, niektóre dane będą utracone", "statistics_retention_confirm": "Czy chcesz zmienić sposób przechowania statystyk? Jeżeli obniżysz wartość interwału, niektóre dane będą utracone",
"statistics_cleared": "Statystyki zostały pomyślnie wyczyszczone", "statistics_cleared": "Statystyki zostały pomyślnie wyczyszczone",
"statistics_enable": "Włącz statystyki", "statistics_enable": "Włącz statystyki",
"ignore_domains": "Ignorowane domeny (każda w nowym wierszu)",
"ignore_domains_title": "Ignorowane domeny",
"ignore_domains_desc_stats": "Zapytania dla tych domen nie są zapisywane do statystyk",
"ignore_domains_desc_query": "Zapytania dla tych domen nie są zapisywane do dziennika",
"interval_hours": "{{count}} godzina", "interval_hours": "{{count}} godzina",
"interval_hours_plural": "{{count}} godziny", "interval_hours_plural": "{{count}} godziny",
"filters_configuration": "Konfiguracja filtrów", "filters_configuration": "Konfiguracja filtrów",
@@ -542,7 +535,7 @@
"password_placeholder": "Wpisz hasło", "password_placeholder": "Wpisz hasło",
"sign_in": "Zaloguj się", "sign_in": "Zaloguj się",
"sign_out": "Wyloguj się", "sign_out": "Wyloguj się",
"forgot_password": "Zapomniano hasła?", "forgot_password": "Zapomniałeś hasła?",
"forgot_password_desc": "Wykonaj <0>te kroki</0>, aby utworzyć nowe hasło do konta użytkownika.", "forgot_password_desc": "Wykonaj <0>te kroki</0>, aby utworzyć nowe hasło do konta użytkownika.",
"location": "Lokalizacja", "location": "Lokalizacja",
"orgname": "Nazwa firmy", "orgname": "Nazwa firmy",
@@ -649,30 +642,5 @@
"anonymizer_notification": "<0>Uwaga:</0> Anonimizacja IP jest włączona. Możesz ją wyłączyć w <1>Ustawieniach ogólnych</1>.", "anonymizer_notification": "<0>Uwaga:</0> Anonimizacja IP jest włączona. Możesz ją wyłączyć w <1>Ustawieniach ogólnych</1>.",
"confirm_dns_cache_clear": "Czy na pewno chcesz wyczyścić pamięć podręczną DNS?", "confirm_dns_cache_clear": "Czy na pewno chcesz wyczyścić pamięć podręczną DNS?",
"cache_cleared": "Pamięć podręczna DNS została pomyślnie wyczyszczona", "cache_cleared": "Pamięć podręczna DNS została pomyślnie wyczyszczona",
"clear_cache": "Wyczyść pamięć podręczną", "clear_cache": "Wyczyść pamięć podręczną"
"make_static": "Ustaw adres statyczny",
"theme_auto_desc": "Automatycznie (na podstawie schematu kolorów Twojego urządzenia)",
"theme_dark_desc": "Ciemny motyw",
"theme_light_desc": "Jasny motyw",
"disable_for_seconds": "Na {{count}} sekundę",
"disable_for_seconds_plural": "Na {{count}} sekund",
"disable_for_minutes": "Na {{count}} minutę",
"disable_for_minutes_plural": "Na {{count}} minut",
"disable_for_hours": "Na {{count}} godzinę",
"disable_for_hours_plural": "Na {{count}} godziny",
"disable_until_tomorrow": "Do jutra",
"disable_notify_for_seconds": "Wyłącz ochronę na {{count}} sekundę",
"disable_notify_for_seconds_plural": "Wyłącz ochronę na {{count}} sekund",
"disable_notify_for_minutes": "Wyłącz ochronę na {{count}} minutę",
"disable_notify_for_minutes_plural": "Wyłącz ochronę na {{count}} minut",
"disable_notify_for_hours": "Wyłącz ochronę na {{count}} godzinę",
"disable_notify_for_hours_plural": "Wyłącz ochronę na {{count}} godziny",
"disable_notify_until_tomorrow": "Wyłącz ochronę do jutra",
"enable_protection_timer": "Ochrona zostanie włączona za {{time}}",
"custom_retention_input": "Wprowadź retencję w godzinach",
"custom_rotation_input": "Wprowadź rotację w godzinach",
"protection_section_label": "Ochrona",
"log_and_stats_section_label": "Dziennik zapytań i statystyki",
"ignore_query_log": "Zignoruj tego klienta w dzienniku zapytań",
"ignore_statistics": "Ignoruj tego klienta w statystykach"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "O registro de consulta foi limpo com sucesso", "query_log_cleared": "O registro de consulta foi limpo com sucesso",
"query_log_updated": "O registro da consulta foi atualizado com sucesso", "query_log_updated": "O registro da consulta foi atualizado com sucesso",
"query_log_clear": "Limpar registros de consulta", "query_log_clear": "Limpar registros de consulta",
"query_log_retention": "Rotação de registros de consulta", "query_log_retention": "Arquivamento de registros de consultas",
"query_log_enable": "Ativar registro", "query_log_enable": "Ativar registro",
"query_log_configuration": "Configuração de registros", "query_log_configuration": "Configuração de registros",
"query_log_disabled": "O registro de consulta está desativado e pode ser configurado em <0>configurações</0>", "query_log_disabled": "O registro de consulta está desativado e pode ser configurado em <0>configurações</0>",
"query_log_strict_search": "Use aspas duplas para uma pesquisa mais criteriosa", "query_log_strict_search": "Use aspas duplas para uma pesquisa mais criteriosa",
"query_log_retention_confirm": "Tem a certeza de que quer alterar a rotação do registo de consulta? Se diminuir o valor do intervalo, alguns dados serão perdidos", "query_log_retention_confirm": "Você tem certeza de que deseja alterar o arquivamento do registro de consulta? Se diminuir o valor de intervalo, alguns dados serão perdidos",
"anonymize_client_ip": "Tornar anônimo o IP do cliente", "anonymize_client_ip": "Tornar anônimo o IP do cliente",
"anonymize_client_ip_desc": "Não salva o endereço de IP completo do cliente em registros ou estatísticas", "anonymize_client_ip_desc": "Não salva o endereço de IP completo do cliente em registros ou estatísticas",
"dns_config": "Configuração do servidor DNS", "dns_config": "Configuração do servidor DNS",
@@ -529,7 +529,7 @@
"ignore_domains": "Domínios ignorados (separados por nova linha)", "ignore_domains": "Domínios ignorados (separados por nova linha)",
"ignore_domains_title": "Domínios ignorados", "ignore_domains_title": "Domínios ignorados",
"ignore_domains_desc_stats": "As consultas para esses domínios não são gravadas nas estatísticas", "ignore_domains_desc_stats": "As consultas para esses domínios não são gravadas nas estatísticas",
"ignore_domains_desc_query": "As consultas para esses domínios não são gravadas no registro de consulta", "ignore_domains_desc_query": "As consultas para esses domínios não são gravadas no log de consulta",
"interval_hours": "{{count}} hora", "interval_hours": "{{count}} hora",
"interval_hours_plural": "{{count}} horas", "interval_hours_plural": "{{count}} horas",
"filters_configuration": "Configuração de filtros", "filters_configuration": "Configuração de filtros",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Desativar proteção por {{count}} hora", "disable_notify_for_hours": "Desativar proteção por {{count}} hora",
"disable_notify_for_hours_plural": "Desativar proteção por {{count}} horas", "disable_notify_for_hours_plural": "Desativar proteção por {{count}} horas",
"disable_notify_until_tomorrow": "Desativar a proteção até amanhã", "disable_notify_until_tomorrow": "Desativar a proteção até amanhã",
"enable_protection_timer": "A proteção será ativada em {{time}}", "enable_protection_timer": "A proteção será ativada em {{time}}"
"custom_retention_input": "Insira a retenção em horas",
"custom_rotation_input": "Insira a rotação em horas",
"protection_section_label": "Proteção",
"log_and_stats_section_label": "Registro de consultas e estatísticas",
"ignore_query_log": "Ignorar este cliente no registo de consultas",
"ignore_statistics": "Ignorar este cliente nas estatísticas"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "O registo de consulta foi limpo com sucesso", "query_log_cleared": "O registo de consulta foi limpo com sucesso",
"query_log_updated": "O registo da consulta foi atualizado com sucesso", "query_log_updated": "O registo da consulta foi atualizado com sucesso",
"query_log_clear": "Limpar registos de consulta", "query_log_clear": "Limpar registos de consulta",
"query_log_retention": "Rotação de registros de consulta", "query_log_retention": "Retenção de registos de consulta",
"query_log_enable": "Ativar registo", "query_log_enable": "Ativar registo",
"query_log_configuration": "Definições do registo", "query_log_configuration": "Definições do registo",
"query_log_disabled": "O registo de consulta está desativado e pode ser configurado em <0>definições</0>", "query_log_disabled": "O registo de consulta está desativado e pode ser configurado em <0>definições</0>",
"query_log_strict_search": "Usar aspas duplas para uma pesquisa rigorosa", "query_log_strict_search": "Usar aspas duplas para uma pesquisa rigorosa",
"query_log_retention_confirm": "Tem a certeza de que quer alterar a rotação do registo de consulta? Se diminuir o valor do intervalo, alguns dados serão perdidos", "query_log_retention_confirm": "Tem a certeza de que deseja alterar a retenção do registo de consulta? Se diminuir o valor do intervalo, alguns dados serão perdidos",
"anonymize_client_ip": "Tornar anónimo o IP do cliente", "anonymize_client_ip": "Tornar anónimo o IP do cliente",
"anonymize_client_ip_desc": "Não gurda o endereço de IP completo do cliente em registo ou estatísticas", "anonymize_client_ip_desc": "Não gurda o endereço de IP completo do cliente em registo ou estatísticas",
"dns_config": "Definição do servidor DNS", "dns_config": "Definição do servidor DNS",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Desativar proteção por {{count}} hora", "disable_notify_for_hours": "Desativar proteção por {{count}} hora",
"disable_notify_for_hours_plural": "Desativar proteção por {{count}} horas", "disable_notify_for_hours_plural": "Desativar proteção por {{count}} horas",
"disable_notify_until_tomorrow": "Desativar a proteção até amanhã", "disable_notify_until_tomorrow": "Desativar a proteção até amanhã",
"enable_protection_timer": "A proteção será habilitada em {{time}}", "enable_protection_timer": "A proteção será habilitada em {{time}}"
"custom_retention_input": "Insira a retenção em horas",
"custom_rotation_input": "Insira a rotação em horas",
"protection_section_label": "Proteção",
"log_and_stats_section_label": "Log de consulta e estatísticas",
"ignore_query_log": "Ignorar este cliente no log de consulta",
"ignore_statistics": "Ignorar este cliente nas estatísticas"
} }

View File

@@ -642,6 +642,5 @@
"anonymizer_notification": "<0>Nota:</0> Anonimizarea IP este activată. Puteți să o dezactivați în <1>Setări generale</1>.", "anonymizer_notification": "<0>Nota:</0> Anonimizarea IP este activată. Puteți să o dezactivați în <1>Setări generale</1>.",
"confirm_dns_cache_clear": "Sunteți sigur că doriți să ștergeți memoria cache DNS?", "confirm_dns_cache_clear": "Sunteți sigur că doriți să ștergeți memoria cache DNS?",
"cache_cleared": "Cache-ul DNS a fost golit cu succes", "cache_cleared": "Cache-ul DNS a fost golit cu succes",
"clear_cache": "Goliți memoria cache", "clear_cache": "Goliți memoria cache"
"protection_section_label": "Protecție"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "Журнал запросов успешно очищен", "query_log_cleared": "Журнал запросов успешно очищен",
"query_log_updated": "Журнал запросов успешно обновлён", "query_log_updated": "Журнал запросов успешно обновлён",
"query_log_clear": "Очистить журнал запросов", "query_log_clear": "Очистить журнал запросов",
"query_log_retention": "Частота ротации журнала запросов", "query_log_retention": "Сохранение журнала запросов",
"query_log_enable": "Включить журнал", "query_log_enable": "Включить журнал",
"query_log_configuration": "Настройка журнала", "query_log_configuration": "Настройка журнала",
"query_log_disabled": "Журнал запросов выключен, его можно включить в <0>настройках</0>", "query_log_disabled": "Журнал запросов выключен, его можно включить в <0>настройках</0>",
"query_log_strict_search": "Используйте двойные кавычки для строгого поиска", "query_log_strict_search": "Используйте двойные кавычки для строгого поиска",
"query_log_retention_confirm": "Вы уверены, что хотите изменить частоту ротации журнала запросов? При сокращении срока данные могут быть утеряны", "query_log_retention_confirm": "Вы уверены, что хотите изменить срок хранения запросов? При сокращении интервала данные могут быть утеряны",
"anonymize_client_ip": "Анонимизировать IP-адрес клиента", "anonymize_client_ip": "Анонимизировать IP-адрес клиента",
"anonymize_client_ip_desc": "Не сохранять полный IP-адрес клиента в журналах и статистике", "anonymize_client_ip_desc": "Не сохранять полный IP-адрес клиента в журналах и статистике",
"dns_config": "Настройки DNS-сервера", "dns_config": "Настройки DNS-сервера",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Отключить защиту на {{count}} час", "disable_notify_for_hours": "Отключить защиту на {{count}} час",
"disable_notify_for_hours_plural": "Отключить защиту на {{count}} часов", "disable_notify_for_hours_plural": "Отключить защиту на {{count}} часов",
"disable_notify_until_tomorrow": "Отключить защиту до завтра", "disable_notify_until_tomorrow": "Отключить защиту до завтра",
"enable_protection_timer": "Защита будет включена в {{time}}", "enable_protection_timer": "Защита будет включена в {{time}}"
"custom_retention_input": "Введите срок хранения в часах",
"custom_rotation_input": "Введите частоту ротации в часах",
"protection_section_label": "Защита",
"log_and_stats_section_label": "Журнал запросов и статистика",
"ignore_query_log": "Игнорировать этого клиента в журнале запросов",
"ignore_statistics": "Игнорировать этого клиента в статистике"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "Denník dopytov bol úspešne vymazaný", "query_log_cleared": "Denník dopytov bol úspešne vymazaný",
"query_log_updated": "Denník dopytov bol úspešne aktualizovaný", "query_log_updated": "Denník dopytov bol úspešne aktualizovaný",
"query_log_clear": "Vymazať denníky dopytov", "query_log_clear": "Vymazať denníky dopytov",
"query_log_retention": "Rotácia denníkov dopytov", "query_log_retention": "Obdobie záznamu denníka dopytov",
"query_log_enable": "Zapnúť denník", "query_log_enable": "Zapnúť denník",
"query_log_configuration": "Konfigurácia denníka", "query_log_configuration": "Konfigurácia denníka",
"query_log_disabled": "Protokol dopytov je vypnutý a možno ho nakonfigurovať v <0>nastaveniach</0>", "query_log_disabled": "Protokol dopytov je vypnutý a možno ho nakonfigurovať v <0>nastaveniach</0>",
"query_log_strict_search": "Na prísne vyhľadávanie použite dvojité úvodzovky", "query_log_strict_search": "Na prísne vyhľadávanie použite dvojité úvodzovky",
"query_log_retention_confirm": "Naozaj chcete zmeniť rotáciu denníka dopytov? Ak znížite hodnotu intervalu, niektoré údaje sa stratia", "query_log_retention_confirm": "Naozaj chcete zmeniť uchovávanie denníku dopytov? Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
"anonymize_client_ip": "Anonymizujte IP klienta", "anonymize_client_ip": "Anonymizujte IP klienta",
"anonymize_client_ip_desc": "Neukladať úplnú IP adresu klienta do protokolov a štatistík", "anonymize_client_ip_desc": "Neukladať úplnú IP adresu klienta do protokolov a štatistík",
"dns_config": "Konfigurácia DNS servera", "dns_config": "Konfigurácia DNS servera",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Vypnite ochranu na {{count}} hodinu", "disable_notify_for_hours": "Vypnite ochranu na {{count}} hodinu",
"disable_notify_for_hours_plural": "Vypnite ochranu na {{count}} hodín", "disable_notify_for_hours_plural": "Vypnite ochranu na {{count}} hodín",
"disable_notify_until_tomorrow": "Vypnúť ochranu do zajtra", "disable_notify_until_tomorrow": "Vypnúť ochranu do zajtra",
"enable_protection_timer": "Ochrana bude zapnutá o {{time}}", "enable_protection_timer": "Ochrana bude zapnutá o {{time}}"
"custom_retention_input": "Zadajte retenciu v hodinách",
"custom_rotation_input": "Zadajte rotáciu v hodinách",
"protection_section_label": "Ochrana",
"log_and_stats_section_label": "Protokol dopytov a štatistiky",
"ignore_query_log": "Ignorovať tohto klienta v denníku dopytov",
"ignore_statistics": "Ignorovanie tohto klienta v štatistikách"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "Dnevnik poizvedb je uspešno izbrisan", "query_log_cleared": "Dnevnik poizvedb je uspešno izbrisan",
"query_log_updated": "Dnevnik poizvedb je bil uspešno posodobljen", "query_log_updated": "Dnevnik poizvedb je bil uspešno posodobljen",
"query_log_clear": "Počisti dnevnike poizvedb", "query_log_clear": "Počisti dnevnike poizvedb",
"query_log_retention": "Rotacija dnevnikov poizvedb", "query_log_retention": "Zadrževanje dnevnikov poizvedb",
"query_log_enable": "Omogoči dnevni", "query_log_enable": "Omogoči dnevni",
"query_log_configuration": "Konfiguracija dnevnikov", "query_log_configuration": "Konfiguracija dnevnikov",
"query_log_disabled": "Dnevnik poizvedb je onemogočen in ga je mogoče konfigurirati v <0>nastavitvah</0>", "query_log_disabled": "Dnevnik poizvedb je onemogočen in ga je mogoče konfigurirati v <0>nastavitvah</0>",
"query_log_strict_search": "Za strogo iskanje uporabite dvojne narekovaje", "query_log_strict_search": "Za strogo iskanje uporabite dvojne narekovaje",
"query_log_retention_confirm": "Ali ste prepričani, da želite spremeniti rotacijo dnevnika poizvedb? Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni", "query_log_retention_confirm": "Ali ste prepričani, da želite spremeniti zadrževanje dnevnika poizvedb? Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni",
"anonymize_client_ip": "Anonimiziraj odjemalca IP", "anonymize_client_ip": "Anonimiziraj odjemalca IP",
"anonymize_client_ip_desc": "Ne shrani celotnega naslova IP odjemalca v dnevnikih ali statistiki", "anonymize_client_ip_desc": "Ne shrani celotnega naslova IP odjemalca v dnevnikih ali statistiki",
"dns_config": "Konfiguracija strežnika DNS", "dns_config": "Konfiguracija strežnika DNS",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Onemogoči zaščito za {{count}} uro", "disable_notify_for_hours": "Onemogoči zaščito za {{count}} uro",
"disable_notify_for_hours_plural": "Onemogoči zaščito za {{count}} ur", "disable_notify_for_hours_plural": "Onemogoči zaščito za {{count}} ur",
"disable_notify_until_tomorrow": "Onemogoči zaščito do jutri", "disable_notify_until_tomorrow": "Onemogoči zaščito do jutri",
"enable_protection_timer": "Zaščita bo omogočena ob {{time}}", "enable_protection_timer": "Zaščita bo omogočena ob {{time}}"
"custom_retention_input": "Vnesite zadrževanje v urah",
"custom_rotation_input": "Vnesite rotacijo v urah",
"protection_section_label": "Zaščita",
"log_and_stats_section_label": "Dnevnik poizvedb in statistika",
"ignore_query_log": "Ignorirajte tega odjemalca v dnevniku poizvedb",
"ignore_statistics": "Ignoriranje tega odjemalca v statistiki"
} }

View File

@@ -642,6 +642,5 @@
"anonymizer_notification": "<0>Nota:</0> IP prepoznavanje je omogućeno. Možete ga onemogućiti u opštim <1>postavkama</1>.", "anonymizer_notification": "<0>Nota:</0> IP prepoznavanje je omogućeno. Možete ga onemogućiti u opštim <1>postavkama</1>.",
"confirm_dns_cache_clear": "Želite li zaista da obrišite DNS keš?", "confirm_dns_cache_clear": "Želite li zaista da obrišite DNS keš?",
"cache_cleared": "DNS keš je uspešno očišćen", "cache_cleared": "DNS keš je uspešno očišćen",
"clear_cache": "Obriši keš memoriju", "clear_cache": "Obriši keš memoriju"
"protection_section_label": "Zaštita"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "Sorgu günlüğü başarıyla temizlendi", "query_log_cleared": "Sorgu günlüğü başarıyla temizlendi",
"query_log_updated": "Sorgu günlüğü başarıyla güncellendi", "query_log_updated": "Sorgu günlüğü başarıyla güncellendi",
"query_log_clear": "Sorgu günlüklerini temizle", "query_log_clear": "Sorgu günlüklerini temizle",
"query_log_retention": "Sorgu günlükleri rotasyonu", "query_log_retention": "Sorgu günlüklerini sakla",
"query_log_enable": "Günlüğü etkinleştir", "query_log_enable": "Günlüğü etkinleştir",
"query_log_configuration": "Günlük yapılandırması", "query_log_configuration": "Günlük yapılandırması",
"query_log_disabled": "Sorgu günlüğü devre dışı bırakıldı, bunu <0>ayarlar</0> kısmından yapılandırılabilirsiniz", "query_log_disabled": "Sorgu günlüğü devre dışı bırakıldı, bunu <0>ayarlar</0> kısmından yapılandırılabilirsiniz",
"query_log_strict_search": "Tam arama için çift tırnak işareti kullanın", "query_log_strict_search": "Tam arama için çift tırnak işareti kullanın",
"query_log_retention_confirm": "Sorgu günlüğü rotasyonunu değiştirmek istediğinizden emin misiniz? Aralık değerini düşürürseniz, bazı veriler kaybolacaktır.", "query_log_retention_confirm": "Sorgu günlüğü saklama süresini değiştirmek istediğinize emin misiniz? Aralık değerini azaltırsanız, bazı veriler kaybolacaktır",
"anonymize_client_ip": "İstemcinin IP adresini gizle", "anonymize_client_ip": "İstemcinin IP adresini gizle",
"anonymize_client_ip_desc": "İstemcinin tam IP adresini günlüklere veya istatistiklere kaydetmeyin", "anonymize_client_ip_desc": "İstemcinin tam IP adresini günlüklere veya istatistiklere kaydetmeyin",
"dns_config": "DNS sunucu yapılandırması", "dns_config": "DNS sunucu yapılandırması",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "Korumayı {{count}} saatliğine devre dışı bırak", "disable_notify_for_hours": "Korumayı {{count}} saatliğine devre dışı bırak",
"disable_notify_for_hours_plural": "Korumayı {{count}} saatliğine devre dışı bırak", "disable_notify_for_hours_plural": "Korumayı {{count}} saatliğine devre dışı bırak",
"disable_notify_until_tomorrow": "Korumayı yarına kadar devre dışı bırak", "disable_notify_until_tomorrow": "Korumayı yarına kadar devre dışı bırak",
"enable_protection_timer": "Koruma {{time}} içinde etkinleştirilecektir", "enable_protection_timer": "Koruma {{time}} içinde etkinleştirilecektir"
"custom_retention_input": "Saklama süresini saat olarak girin",
"custom_rotation_input": "Rotasyonu saat cinsinden girin",
"protection_section_label": "Koruma",
"log_and_stats_section_label": "Sorgu günlüğü ve istatistikler",
"ignore_query_log": "Sorgu günlüğünde bu istemciyi yoksay",
"ignore_statistics": "İstatistiklerde bu istemciyi yoksay"
} }

View File

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

View File

@@ -642,6 +642,5 @@
"anonymizer_notification": "<0> Lưu ý:</0> Tính năng ẩn danh IP được bật. Bạn có thể tắt nó trong <1> Cài đặt chung</1>.", "anonymizer_notification": "<0> Lưu ý:</0> Tính năng ẩn danh IP được bật. Bạn có thể tắt nó trong <1> Cài đặt chung</1>.",
"confirm_dns_cache_clear": "Bạn có chắc chắn muốn xóa bộ đệm ẩn DNS không?", "confirm_dns_cache_clear": "Bạn có chắc chắn muốn xóa bộ đệm ẩn DNS không?",
"cache_cleared": "Đã xóa thành công bộ đệm DNS", "cache_cleared": "Đã xóa thành công bộ đệm DNS",
"clear_cache": "Xóa bộ nhớ cache", "clear_cache": "Xóa bộ nhớ cache"
"protection_section_label": "Sự bảo vệ"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "查询日志已成功清除", "query_log_cleared": "查询日志已成功清除",
"query_log_updated": "已成功更新查询日志", "query_log_updated": "已成功更新查询日志",
"query_log_clear": "清除查询日志", "query_log_clear": "清除查询日志",
"query_log_retention": "查询日志保留时间", "query_log_retention": "查询记录保留时间",
"query_log_enable": "启用日志", "query_log_enable": "启用日志",
"query_log_configuration": "日志配置", "query_log_configuration": "日志配置",
"query_log_disabled": "查询日志已禁用,在<0>这些设置</0>中能配置它们", "query_log_disabled": "查询日志已禁用,在<0>这些设置</0>中能配置它们",
"query_log_strict_search": "使用双引号进行严谨搜索", "query_log_strict_search": "使用双引号进行严谨搜索",
"query_log_retention_confirm": "您确定要更改查询记录保留时间吗?如果减少时间间隔数值,某些数据可能会丢失", "query_log_retention_confirm": "您确定要更改查询记录保留时间吗? 如果减少间隔时间的值, 某些数据可能会丢失",
"anonymize_client_ip": "匿名化客户端IP", "anonymize_client_ip": "匿名化客户端IP",
"anonymize_client_ip_desc": "不要在日志和统计信息中保存客户端的完整 IP 地址", "anonymize_client_ip_desc": "不要在日志和统计信息中保存客户端的完整 IP 地址",
"dns_config": "DNS 服务配置", "dns_config": "DNS 服务配置",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "禁用保护 {{count}} 小时", "disable_notify_for_hours": "禁用保护 {{count}} 小时",
"disable_notify_for_hours_plural": "禁用保护 {{count}} 小时", "disable_notify_for_hours_plural": "禁用保护 {{count}} 小时",
"disable_notify_until_tomorrow": "禁用保护直到明天", "disable_notify_until_tomorrow": "禁用保护直到明天",
"enable_protection_timer": "保护将于 {{time}} 启用", "enable_protection_timer": "保护将于 {{time}} 启用"
"custom_retention_input": "输入保留时间(小时)",
"custom_rotation_input": "输入旋转时间(小时)",
"protection_section_label": "防护",
"log_and_stats_section_label": "查询日志和统计数据",
"ignore_query_log": "在查询日志中忽略此客户端",
"ignore_statistics": "在统计数据中忽略此客户端"
} }

View File

@@ -257,12 +257,12 @@
"query_log_cleared": "該查詢記錄已被成功地清除", "query_log_cleared": "該查詢記錄已被成功地清除",
"query_log_updated": "該查詢記錄已被成功地更新", "query_log_updated": "該查詢記錄已被成功地更新",
"query_log_clear": "清除查詢記錄", "query_log_clear": "清除查詢記錄",
"query_log_retention": "查詢記錄保留時間", "query_log_retention": "查詢記錄保留",
"query_log_enable": "啟用記錄", "query_log_enable": "啟用記錄",
"query_log_configuration": "記錄配置", "query_log_configuration": "記錄配置",
"query_log_disabled": "查詢記錄被禁用並可在<0>設定</0>中被配置", "query_log_disabled": "查詢記錄被禁用並可在<0>設定</0>中被配置",
"query_log_strict_search": "使用雙引號於嚴謹的搜尋", "query_log_strict_search": "使用雙引號於嚴謹的搜尋",
"query_log_retention_confirm": "您確定要更改記錄檔保存期限嗎?如果您縮短期限部分資料可能將會遺失", "query_log_retention_confirm": "您確定您想要更改查詢記錄保留嗎?如果您減少該間隔值,某些資料將被丟失",
"anonymize_client_ip": "將用戶端 IP 匿名", "anonymize_client_ip": "將用戶端 IP 匿名",
"anonymize_client_ip_desc": "不要儲存用戶端之完整的 IP 位址到記錄或統計資料裡", "anonymize_client_ip_desc": "不要儲存用戶端之完整的 IP 位址到記錄或統計資料裡",
"dns_config": "DNS 伺服器配置", "dns_config": "DNS 伺服器配置",
@@ -668,11 +668,5 @@
"disable_notify_for_hours": "計 {{count}} 小時禁用防護", "disable_notify_for_hours": "計 {{count}} 小時禁用防護",
"disable_notify_for_hours_plural": "計 {{count}} 小時禁用防護", "disable_notify_for_hours_plural": "計 {{count}} 小時禁用防護",
"disable_notify_until_tomorrow": "禁用防護直到明天", "disable_notify_until_tomorrow": "禁用防護直到明天",
"enable_protection_timer": "防護將於 {{time}} 被啟用", "enable_protection_timer": "防護將於 {{time}} 被啟用"
"custom_retention_input": "輸入保留時間(小時)",
"custom_rotation_input": "輸入旋轉時間(小時)",
"protection_section_label": "防護",
"log_and_stats_section_label": "查詢記錄和統計資料",
"ignore_query_log": "在查詢記錄中忽略此用戶端",
"ignore_statistics": "在統計資料中忽略此用戶端"
} }

View File

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

View File

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

View File

@@ -7,10 +7,11 @@
addrs[$2] = true addrs[$2] = true
prev_line = FNR prev_line = FNR
if ($2 == "0.0.0.0" || $2 == "'::'") { if ($2 == "0.0.0.0" || $2 == "::") {
# Drop all the other addresses.
delete addrs delete addrs
addrs[""] = true addrs["localhost"] = true
# Drop all the other addresses.
prev_line = -1 prev_line = -1
} }
} }

View File

@@ -61,11 +61,8 @@ then
error_exit "no DNS bindings could be retrieved from $filename" error_exit "no DNS bindings could be retrieved from $filename"
fi fi
first_dns="$( echo "$dns_hosts" | head -n 1 )"
readonly first_dns
# TODO(e.burkov): Deal with 0 port. # TODO(e.burkov): Deal with 0 port.
case "$first_dns" case "$( echo "$dns_hosts" | head -n 1 )"
in in
(*':0') (*':0')
error_exit '0 in DNS port is not supported by healthcheck' error_exit '0 in DNS port is not supported by healthcheck'
@@ -85,23 +82,8 @@ esac
# See https://github.com/AdguardTeam/AdGuardHome/issues/5642. # See https://github.com/AdguardTeam/AdGuardHome/issues/5642.
wget --no-check-certificate "$web_url" -O /dev/null -q || exit 1 wget --no-check-certificate "$web_url" -O /dev/null -q || exit 1
test_fqdn="healthcheck.adguardhome.test."
readonly test_fqdn
# The awk script currently returns only port prefixed with colon in case of
# unspecified address.
case "$first_dns"
in
(':'*)
nslookup -type=a "$test_fqdn" "127.0.0.1${first_dns}" > /dev/null ||\
nslookup -type=a "$test_fqdn" "[::1]${first_dns}" > /dev/null ||\
error_exit "nslookup failed for $host"
;;
(*)
echo "$dns_hosts" | while read -r host echo "$dns_hosts" | while read -r host
do do
nslookup -type=a "$test_fqdn" "$host" > /dev/null ||\ nslookup -type=a healthcheck.adguardhome.test. "$host" > /dev/null ||\
error_exit "nslookup failed for $host" error_exit "nslookup failed for $host"
done done
;;
esac

38
go.mod
View File

@@ -3,12 +3,11 @@ module github.com/AdguardTeam/AdGuardHome
go 1.19 go 1.19
require ( require (
github.com/AdguardTeam/dnsproxy v0.50.2 github.com/AdguardTeam/dnsproxy v0.48.3
github.com/AdguardTeam/golibs v0.13.3 github.com/AdguardTeam/golibs v0.13.2
github.com/AdguardTeam/urlfilter v0.16.1 github.com/AdguardTeam/urlfilter v0.16.1
github.com/NYTimes/gziphandler v1.1.1 github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.2.7 github.com/ameshkov/dnscrypt/v2 v2.2.7
github.com/bluele/gcache v0.0.2
github.com/digineo/go-ipset/v2 v2.2.1 github.com/digineo/go-ipset/v2 v2.2.1
github.com/dimfeld/httptreemux/v5 v5.5.0 github.com/dimfeld/httptreemux/v5 v5.5.0
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0
@@ -17,24 +16,24 @@ require (
github.com/google/gopacket v1.1.19 github.com/google/gopacket v1.1.19
github.com/google/renameio v1.0.1 github.com/google/renameio v1.0.1
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
github.com/kardianos/service v1.2.2 github.com/kardianos/service v1.2.2
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
github.com/mdlayher/netlink v1.7.2 github.com/mdlayher/netlink v1.7.1
github.com/mdlayher/packet v1.1.2 github.com/mdlayher/packet v1.1.1
// TODO(a.garipov): This package is deprecated; find a new one or use our // TODO(a.garipov): This package is deprecated; find a new one or use our
// own code for that. Perhaps, use gopacket. // own code for that. Perhaps, use gopacket.
github.com/mdlayher/raw v0.1.0 github.com/mdlayher/raw v0.1.0
github.com/miekg/dns v1.1.54 github.com/miekg/dns v1.1.53
github.com/quic-go/quic-go v0.35.1 github.com/quic-go/quic-go v0.33.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.2
github.com/ti-mo/netfilter v0.5.0 github.com/ti-mo/netfilter v0.5.0
go.etcd.io/bbolt v1.3.7 go.etcd.io/bbolt v1.3.7
golang.org/x/crypto v0.10.0 golang.org/x/crypto v0.8.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 golang.org/x/exp v0.0.0-20230321023759-10a507213a29
golang.org/x/net v0.11.0 golang.org/x/net v0.9.0
golang.org/x/sys v0.9.0 golang.org/x/sys v0.7.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.0 howett.net/plist v1.0.0
@@ -45,12 +44,13 @@ require (
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
github.com/ameshkov/dnsstamps v1.0.3 // indirect github.com/ameshkov/dnsstamps v1.0.3 // indirect
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect github.com/google/pprof v0.0.0-20230406165453-00490a63f317 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.4.0 // indirect
github.com/onsi/ginkgo/v2 v2.10.0 // indirect github.com/onsi/ginkgo/v2 v2.9.2 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
@@ -60,7 +60,7 @@ require (
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
golang.org/x/mod v0.10.0 // indirect golang.org/x/mod v0.10.0 // indirect
golang.org/x/sync v0.2.0 // indirect golang.org/x/sync v0.1.0 // indirect
golang.org/x/text v0.10.0 // indirect golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.9.3 // indirect golang.org/x/tools v0.8.0 // indirect
) )

119
go.sum
View File

@@ -1,9 +1,9 @@
github.com/AdguardTeam/dnsproxy v0.50.2 h1:p1471SsMZ6SMo7T51Olw4aNluahvMwSLMorwxYV18ts= github.com/AdguardTeam/dnsproxy v0.48.3 h1:h9xgDSmd1MqsPFNApyaPVXolmSTtzOWOcfWvPeDEP6s=
github.com/AdguardTeam/dnsproxy v0.50.2/go.mod h1:CQhZTkqC8X0ID6glrtyaxgqRRdiYfn1gJulC1cZ5Dn8= github.com/AdguardTeam/dnsproxy v0.48.3/go.mod h1:Y7g7jRTd/u7+KJ/QvnGI2PCE8vnisp6EsW47/Sz0DZw=
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw= github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
github.com/AdguardTeam/golibs v0.13.3 h1:RT3QbzThtaLiFLkIUDS6/hlGEXrh0zYvdf4bd7UWpGo= github.com/AdguardTeam/golibs v0.13.2 h1:BPASsyQKmb+b8VnvsNOHp7bKfcZl9Z+Z2UhPjOiupSc=
github.com/AdguardTeam/golibs v0.13.3/go.mod h1:wkJ6EUsN4np/9Gp7+9QeooY9E2U2WCLJYAioLCzkHsI= github.com/AdguardTeam/golibs v0.13.2/go.mod h1:7ylQLv2Lqsc3UW3jHoITynYk6Y1tYtgEMkR09ppfsN8=
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
github.com/AdguardTeam/urlfilter v0.16.1 h1:ZPi0rjqo8cQf2FVdzo6cqumNoHZx2KPXj2yZa1A5BBw= github.com/AdguardTeam/urlfilter v0.16.1 h1:ZPi0rjqo8cQf2FVdzo6cqumNoHZx2KPXj2yZa1A5BBw=
github.com/AdguardTeam/urlfilter v0.16.1/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI= github.com/AdguardTeam/urlfilter v0.16.1/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
@@ -31,9 +31,10 @@ github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1M
github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU= github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU=
github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ= github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ=
github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw= github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
@@ -44,54 +45,72 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= github.com/google/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ=
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= github.com/google/pprof v0.0.0-20230406165453-00490a63f317/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU= github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU=
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb h1:6fDKEAXwe3rsfS4khW3EZ8kEqmSiV9szhMPcDrD+Y7Q= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 h1:Z72DOke2yOK0Ms4Z2LK1E1OrRJXOxSj5DllTz2FYTRg=
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8/go.mod h1:m5WMe03WCvWcXjRnhvaAbAAXdCnu20J5P+mmH44ZzpE=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60= github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE= github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og= github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og=
github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg=
github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ=
github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= github.com/mdlayher/packet v1.1.1 h1:7Fv4OEMYqPl7//uBm04VgPpnSNi8fbBZznppgh6WMr8=
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4= github.com/mdlayher/packet v1.1.1/go.mod h1:DRvYY5mH4M4lUqAnMg04E60U4fjUKMZ/4g2cHElZkKo=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.1.0 h1:K4PFMVy+AFsp0Zdlrts7yNhxc/uXoPVHi9RzRvtZF2Y= github.com/mdlayher/raw v0.1.0 h1:K4PFMVy+AFsp0Zdlrts7yNhxc/uXoPVHi9RzRvtZF2Y=
github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5s9Sg= github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5s9Sg=
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E= github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI= github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs= github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE= github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
@@ -108,18 +127,24 @@ github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc8
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo= github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g= github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA=
github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA= github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA=
github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ= github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU= github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU=
github.com/ti-mo/netfilter v0.5.0 h1:MZmsUw5bFRecOb0AeyjOPxTHg4UxYzyEs0Ek/6Lxoy8= github.com/ti-mo/netfilter v0.5.0 h1:MZmsUw5bFRecOb0AeyjOPxTHg4UxYzyEs0Ek/6Lxoy8=
github.com/ti-mo/netfilter v0.5.0/go.mod h1:nt+8B9hx/QpqHr7Hazq+2qMCCA8u2OTkyc/7+U9ARz8= github.com/ti-mo/netfilter v0.5.0/go.mod h1:nt+8B9hx/QpqHr7Hazq+2qMCCA8u2OTkyc/7+U9ARz8=
@@ -127,6 +152,7 @@ github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E=
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg= github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@@ -134,10 +160,11 @@ go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -145,25 +172,40 @@ golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -177,22 +219,23 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -72,8 +72,8 @@ func WriteJSONResponse(w http.ResponseWriter, r *http.Request, resp any) (err er
// WriteJSONResponseCode is like [WriteJSONResponse] but adds the ability to // WriteJSONResponseCode is like [WriteJSONResponse] but adds the ability to
// redefine the status code. // redefine the status code.
func WriteJSONResponseCode(w http.ResponseWriter, r *http.Request, code int, resp any) (err error) { func WriteJSONResponseCode(w http.ResponseWriter, r *http.Request, code int, resp any) (err error) {
w.Header().Set(httphdr.ContentType, HdrValApplicationJSON)
w.WriteHeader(code) w.WriteHeader(code)
w.Header().Set(httphdr.ContentType, HdrValApplicationJSON)
err = json.NewEncoder(w).Encode(resp) err = json.NewEncoder(w).Encode(resp)
if err != nil { if err != nil {
Error(r, w, http.StatusInternalServerError, "encoding resp: %s", err) Error(r, w, http.StatusInternalServerError, "encoding resp: %s", err)

View File

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

View File

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

View File

@@ -1,6 +0,0 @@
package aghos
// PreCheckActionStart performs the service start action pre-check.
func PreCheckActionStart() (err error) {
return preCheckActionStart()
}

View File

@@ -1,32 +0,0 @@
//go:build darwin
package aghos
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/AdguardTeam/golibs/log"
)
// preCheckActionStart performs the service start action pre-check. It warns
// user that the service should be installed into Applications directory.
func preCheckActionStart() (err error) {
exe, err := os.Executable()
if err != nil {
return fmt.Errorf("getting executable path: %v", err)
}
exe, err = filepath.EvalSymlinks(exe)
if err != nil {
return fmt.Errorf("evaluating executable symlinks: %v", err)
}
if !strings.HasPrefix(exe, "/Applications/") {
log.Info("warning: service must be started from within the /Applications directory")
}
return err
}

View File

@@ -1,8 +0,0 @@
//go:build !darwin
package aghos
// preCheckActionStart performs the service start action pre-check.
func preCheckActionStart() (err error) {
return nil
}

View File

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

View File

@@ -31,16 +31,8 @@ type ServerConfig struct {
Conf4 V4ServerConf `yaml:"dhcpv4"` Conf4 V4ServerConf `yaml:"dhcpv4"`
Conf6 V6ServerConf `yaml:"dhcpv6"` Conf6 V6ServerConf `yaml:"dhcpv6"`
// WorkDir is used to store DHCP leases.
//
// Deprecated: Remove it when migration of DHCP leases will not be needed.
WorkDir string `yaml:"-"` WorkDir string `yaml:"-"`
DBFilePath string `yaml:"-"`
// DataDir is used to store DHCP leases.
DataDir string `yaml:"-"`
// dbFilePath is the path to the file with stored DHCP leases.
dbFilePath string `yaml:"-"`
} }
// DHCPServer - DHCP server interface // DHCPServer - DHCP server interface

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,34 +5,43 @@ package dhcpd
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net"
"net/netip"
"os" "os"
"time"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/google/renameio/maybe" "github.com/google/renameio/maybe"
"golang.org/x/exp/slices"
) )
const ( const dbFilename = "leases.db"
// dataFilename contains saved leases.
dataFilename = "leases.json"
// dataVersion is the current version of the stored DHCP leases structure. type leaseJSON struct {
dataVersion = 1 HWAddr []byte `json:"mac"`
) IP []byte `json:"ip"`
Hostname string `json:"host"`
// dataLeases is the structure of the stored DHCP leases. Expiry int64 `json:"exp"`
type dataLeases struct {
// Version is the current version of the structure.
Version int `json:"version"`
// Leases is the list containing stored DHCP leases.
Leases []*Lease `json:"leases"`
} }
// dbLoad loads stored leases. func normalizeIP(ip net.IP) net.IP {
ip4 := ip.To4()
if ip4 != nil {
return ip4
}
return ip
}
// Load lease table from DB
//
// TODO(s.chzhen): Decrease complexity.
func (s *server) dbLoad() (err error) { func (s *server) dbLoad() (err error) {
data, err := os.ReadFile(s.conf.dbFilePath) dynLeases := []*Lease{}
staticLeases := []*Lease{}
v6StaticLeases := []*Lease{}
v6DynLeases := []*Lease{}
data, err := os.ReadFile(s.conf.DBFilePath)
if err != nil { if err != nil {
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("reading db: %w", err) return fmt.Errorf("reading db: %w", err)
@@ -41,30 +50,52 @@ func (s *server) dbLoad() (err error) {
return nil return nil
} }
dl := &dataLeases{} obj := []leaseJSON{}
err = json.Unmarshal(data, dl) err = json.Unmarshal(data, &obj)
if err != nil { if err != nil {
return fmt.Errorf("decoding db: %w", err) return fmt.Errorf("decoding db: %w", err)
} }
leases := dl.Leases numLeases := len(obj)
for i := range obj {
obj[i].IP = normalizeIP(obj[i].IP)
leases4 := []*Lease{} ip, ok := netip.AddrFromSlice(obj[i].IP)
leases6 := []*Lease{} if !ok {
log.Info("dhcp: invalid IP: %s", obj[i].IP)
continue
}
for _, l := range leases { lease := Lease{
if l.IP.Is4() { HWAddr: obj[i].HWAddr,
leases4 = append(leases4, l) IP: ip,
Hostname: obj[i].Hostname,
Expiry: time.Unix(obj[i].Expiry, 0),
IsStatic: obj[i].Expiry == leaseExpireStatic,
}
if len(obj[i].IP) == 16 {
if lease.IsStatic {
v6StaticLeases = append(v6StaticLeases, &lease)
} else { } else {
leases6 = append(leases6, l) v6DynLeases = append(v6DynLeases, &lease)
}
} else {
if lease.IsStatic {
staticLeases = append(staticLeases, &lease)
} else {
dynLeases = append(dynLeases, &lease)
}
} }
} }
leases4 := normalizeLeases(staticLeases, dynLeases)
err = s.srv4.ResetLeases(leases4) err = s.srv4.ResetLeases(leases4)
if err != nil { if err != nil {
return fmt.Errorf("resetting dhcpv4 leases: %w", err) return fmt.Errorf("resetting dhcpv4 leases: %w", err)
} }
leases6 := normalizeLeases(v6StaticLeases, v6DynLeases)
if s.srv6 != nil { if s.srv6 != nil {
err = s.srv6.ResetLeases(leases6) err = s.srv6.ResetLeases(leases6)
if err != nil { if err != nil {
@@ -73,54 +104,90 @@ func (s *server) dbLoad() (err error) {
} }
log.Info("dhcp: loaded leases v4:%d v6:%d total-read:%d from DB", log.Info("dhcp: loaded leases v4:%d v6:%d total-read:%d from DB",
len(leases4), len(leases6), len(leases)) len(leases4), len(leases6), numLeases)
return nil return nil
} }
// dbStore stores DHCP leases. // Skip duplicate leases
// Static leases have a priority over dynamic leases
func normalizeLeases(staticLeases, dynLeases []*Lease) []*Lease {
leases := []*Lease{}
index := map[string]int{}
for i, lease := range staticLeases {
_, ok := index[lease.HWAddr.String()]
if ok {
continue // skip the lease with the same HW address
}
index[lease.HWAddr.String()] = i
leases = append(leases, lease)
}
for i, lease := range dynLeases {
_, ok := index[lease.HWAddr.String()]
if ok {
continue // skip the lease with the same HW address
}
index[lease.HWAddr.String()] = i
leases = append(leases, lease)
}
return leases
}
// Store lease table in DB
func (s *server) dbStore() (err error) { func (s *server) dbStore() (err error) {
// Use an empty slice here as opposed to nil so that it doesn't write // Use an empty slice here as opposed to nil so that it doesn't write
// "null" into the database file if leases are empty. // "null" into the database file if leases are empty.
leases := []*Lease{} leases := []leaseJSON{}
leases4 := s.srv4.getLeasesRef() leases4 := s.srv4.getLeasesRef()
leases = append(leases, leases4...) for _, l := range leases4 {
if l.Expiry.Unix() == 0 {
continue
}
lease := leaseJSON{
HWAddr: l.HWAddr,
IP: l.IP.AsSlice(),
Hostname: l.Hostname,
Expiry: l.Expiry.Unix(),
}
leases = append(leases, lease)
}
if s.srv6 != nil { if s.srv6 != nil {
leases6 := s.srv6.getLeasesRef() leases6 := s.srv6.getLeasesRef()
leases = append(leases, leases6...) for _, l := range leases6 {
if l.Expiry.Unix() == 0 {
continue
} }
return writeDB(s.conf.dbFilePath, leases) lease := leaseJSON{
HWAddr: l.HWAddr,
IP: l.IP.AsSlice(),
Hostname: l.Hostname,
Expiry: l.Expiry.Unix(),
} }
// writeDB writes leases to file at path. leases = append(leases, lease)
func writeDB(path string, leases []*Lease) (err error) { }
defer func() { err = errors.Annotate(err, "writing db: %w") }()
slices.SortFunc(leases, func(a, b *Lease) bool {
return a.Hostname < b.Hostname
})
dl := &dataLeases{
Version: dataVersion,
Leases: leases,
} }
buf, err := json.Marshal(dl) var data []byte
data, err = json.Marshal(leases)
if err != nil { if err != nil {
// Don't wrap the error since it's informative enough as is. return fmt.Errorf("encoding db: %w", err)
return err
} }
err = maybe.WriteFile(path, buf, 0o644) err = maybe.WriteFile(s.conf.DBFilePath, data, 0o644)
if err != nil { if err != nil {
// Don't wrap the error since it's informative enough as is. return fmt.Errorf("writing db: %w", err)
return err
} }
log.Info("dhcp: stored %d leases in %q", len(leases), path) log.Info("dhcp: stored %d leases in db", len(leases))
return nil return nil
} }

View File

@@ -15,6 +15,13 @@ import (
) )
const ( const (
// leaseExpireStatic is used to define the Expiry field for static
// leases.
//
// TODO(e.burkov): Remove it when static leases determining mechanism
// will be improved.
leaseExpireStatic = 1
// DefaultDHCPLeaseTTL is the default time-to-live for leases. // DefaultDHCPLeaseTTL is the default time-to-live for leases.
DefaultDHCPLeaseTTL = uint32(timeutil.Day / time.Second) DefaultDHCPLeaseTTL = uint32(timeutil.Day / time.Second)
@@ -28,10 +35,10 @@ const (
defaultBackoff time.Duration = 500 * time.Millisecond defaultBackoff time.Duration = 500 * time.Millisecond
) )
// Lease contains the necessary information about a DHCP lease. It's used in // Lease contains the necessary information about a DHCP lease
// various places. So don't change it without good reason.
type Lease struct { type Lease struct {
// Expiry is the expiration time of the lease. // Expiry is the expiration time of the lease. The unix timestamp value
// of 1 means that this is a static lease.
Expiry time.Time `json:"expires"` Expiry time.Time `json:"expires"`
// Hostname of the client. // Hostname of the client.
@@ -231,7 +238,7 @@ func Create(conf *ServerConfig) (s *server, err error) {
LocalDomainName: conf.LocalDomainName, LocalDomainName: conf.LocalDomainName,
dbFilePath: filepath.Join(conf.DataDir, dataFilename), DBFilePath: filepath.Join(conf.WorkDir, dbFilename),
}, },
} }
@@ -239,26 +246,39 @@ func Create(conf *ServerConfig) (s *server, err error) {
// [aghhttp.RegisterFunc]. // [aghhttp.RegisterFunc].
s.registerHandlers() s.registerHandlers()
v4Enabled, v6Enabled, err := s.setServers(conf) v4conf := conf.Conf4
v4conf.InterfaceName = s.conf.InterfaceName
v4conf.notify = s.onNotify
v4conf.Enabled = s.conf.Enabled && v4conf.RangeStart.IsValid()
s.srv4, err = v4Create(&v4conf)
if err != nil { if err != nil {
// Don't wrap the error, because it's informative enough as is. if v4conf.Enabled {
return nil, err return nil, fmt.Errorf("creating dhcpv4 srv: %w", err)
}
log.Debug("dhcpd: warning: creating dhcpv4 srv: %s", err)
}
v6conf := conf.Conf6
v6conf.Enabled = s.conf.Enabled
if len(v6conf.RangeStart) == 0 {
v6conf.Enabled = false
}
v6conf.InterfaceName = s.conf.InterfaceName
v6conf.notify = s.onNotify
s.srv6, err = v6Create(v6conf)
if err != nil {
return nil, fmt.Errorf("creating dhcpv6 srv: %w", err)
} }
s.conf.Conf4 = conf.Conf4 s.conf.Conf4 = conf.Conf4
s.conf.Conf6 = conf.Conf6 s.conf.Conf6 = conf.Conf6
if s.conf.Enabled && !v4Enabled && !v6Enabled { if s.conf.Enabled && !v4conf.Enabled && !v6conf.Enabled {
return nil, fmt.Errorf("neither dhcpv4 nor dhcpv6 srv is configured") return nil, fmt.Errorf("neither dhcpv4 nor dhcpv6 srv is configured")
} }
// Migrate leases db if needed.
err = migrateDB(conf)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return nil, err
}
// Don't delay database loading until the DHCP server is started, // Don't delay database loading until the DHCP server is started,
// because we need static leases functionality available beforehand. // because we need static leases functionality available beforehand.
err = s.dbLoad() err = s.dbLoad()
@@ -269,39 +289,6 @@ func Create(conf *ServerConfig) (s *server, err error) {
return s, nil return s, nil
} }
// setServers updates DHCPv4 and DHCPv6 servers created from the provided
// configuration conf.
func (s *server) setServers(conf *ServerConfig) (v4Enabled, v6Enabled bool, err error) {
v4conf := conf.Conf4
v4conf.InterfaceName = s.conf.InterfaceName
v4conf.notify = s.onNotify
v4conf.Enabled = s.conf.Enabled && v4conf.RangeStart.IsValid()
s.srv4, err = v4Create(&v4conf)
if err != nil {
if v4conf.Enabled {
return true, false, fmt.Errorf("creating dhcpv4 srv: %w", err)
}
log.Debug("dhcpd: warning: creating dhcpv4 srv: %s", err)
}
v6conf := conf.Conf6
v6conf.InterfaceName = s.conf.InterfaceName
v6conf.notify = s.onNotify
v6conf.Enabled = s.conf.Enabled
if len(v6conf.RangeStart) == 0 {
v6conf.Enabled = false
}
s.srv6, err = v6Create(v6conf)
if err != nil {
return v4conf.Enabled, v6conf.Enabled, fmt.Errorf("creating dhcpv6 srv: %w", err)
}
return v4conf.Enabled, v6conf.Enabled, nil
}
// Enabled returns true when the server is enabled. // Enabled returns true when the server is enabled.
func (s *server) Enabled() (ok bool) { func (s *server) Enabled() (ok bool) {
return s.conf.Enabled return s.conf.Enabled

View File

@@ -5,7 +5,7 @@ package dhcpd
import ( import (
"net" "net"
"net/netip" "net/netip"
"path/filepath" "os"
"testing" "testing"
"time" "time"
@@ -27,7 +27,7 @@ func TestDB(t *testing.T) {
var err error var err error
s := server{ s := server{
conf: &ServerConfig{ conf: &ServerConfig{
dbFilePath: filepath.Join(t.TempDir(), dataFilename), DBFilePath: dbFilename,
}, },
} }
@@ -67,6 +67,8 @@ func TestDB(t *testing.T) {
err = s.dbStore() err = s.dbStore()
require.NoError(t, err) require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, func() (err error) { return os.Remove(dbFilename) })
err = s.srv4.ResetLeases(nil) err = s.srv4.ResetLeases(nil)
require.NoError(t, err) require.NoError(t, err)
@@ -76,13 +78,36 @@ func TestDB(t *testing.T) {
ll := s.srv4.GetLeases(LeasesAll) ll := s.srv4.GetLeases(LeasesAll)
require.Len(t, ll, len(leases)) require.Len(t, ll, len(leases))
assert.Equal(t, leases[0].HWAddr, ll[0].HWAddr) assert.Equal(t, leases[1].HWAddr, ll[0].HWAddr)
assert.Equal(t, leases[0].IP, ll[0].IP) assert.Equal(t, leases[1].IP, ll[0].IP)
assert.Equal(t, leases[0].Expiry.Unix(), ll[0].Expiry.Unix()) assert.True(t, ll[0].IsStatic)
assert.Equal(t, leases[1].HWAddr, ll[1].HWAddr) assert.Equal(t, leases[0].HWAddr, ll[1].HWAddr)
assert.Equal(t, leases[1].IP, ll[1].IP) assert.Equal(t, leases[0].IP, ll[1].IP)
assert.True(t, ll[1].IsStatic) assert.Equal(t, leases[0].Expiry.Unix(), ll[1].Expiry.Unix())
}
func TestNormalizeLeases(t *testing.T) {
dynLeases := []*Lease{{
HWAddr: net.HardwareAddr{1, 2, 3, 4},
}, {
HWAddr: net.HardwareAddr{1, 2, 3, 5},
}}
staticLeases := []*Lease{{
HWAddr: net.HardwareAddr{1, 2, 3, 4},
IP: netip.MustParseAddr("0.2.3.4"),
}, {
HWAddr: net.HardwareAddr{2, 2, 3, 4},
}}
leases := normalizeLeases(staticLeases, dynLeases)
require.Len(t, leases, 3)
assert.Equal(t, leases[0].HWAddr, dynLeases[0].HWAddr)
assert.Equal(t, leases[0].IP, staticLeases[0].IP)
assert.Equal(t, leases[1].HWAddr, staticLeases[1].HWAddr)
assert.Equal(t, leases[2].HWAddr, dynLeases[1].HWAddr)
} }
func TestV4Server_badRange(t *testing.T) { func TestV4Server_badRange(t *testing.T) {

View File

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

View File

@@ -31,7 +31,8 @@ func TestServer_handleDHCPStatus(t *testing.T) {
s, err := Create(&ServerConfig{ s, err := Create(&ServerConfig{
Enabled: true, Enabled: true,
Conf4: *defaultV4ServerConf(), Conf4: *defaultV4ServerConf(),
DataDir: t.TempDir(), WorkDir: t.TempDir(),
DBFilePath: dbFilename,
ConfigModified: func() {}, ConfigModified: func() {},
}) })
require.NoError(t, err) require.NoError(t, err)

View File

@@ -1,106 +0,0 @@
package dhcpd
import (
"encoding/json"
"net"
"net/netip"
"os"
"path/filepath"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
)
const (
// leaseExpireStatic is used to define the Expiry field for static
// leases.
//
// Deprecated: Remove it when migration of DHCP leases will be not needed.
leaseExpireStatic = 1
// dbFilename contains saved leases.
//
// Deprecated: Use dataFilename.
dbFilename = "leases.db"
)
// leaseJSON is the structure of stored lease.
//
// Deprecated: Use [Lease].
type leaseJSON struct {
HWAddr []byte `json:"mac"`
IP []byte `json:"ip"`
Hostname string `json:"host"`
Expiry int64 `json:"exp"`
}
func normalizeIP(ip net.IP) net.IP {
ip4 := ip.To4()
if ip4 != nil {
return ip4
}
return ip
}
// migrateDB migrates stored leases if necessary.
func migrateDB(conf *ServerConfig) (err error) {
defer func() { err = errors.Annotate(err, "migrating db: %w") }()
oldLeasesPath := filepath.Join(conf.WorkDir, dbFilename)
dataDirPath := filepath.Join(conf.DataDir, dataFilename)
file, err := os.Open(oldLeasesPath)
if errors.Is(err, os.ErrNotExist) {
// Nothing to migrate.
return nil
} else if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
ljs := []leaseJSON{}
err = json.NewDecoder(file).Decode(&ljs)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
err = file.Close()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
leases := []*Lease{}
for _, lj := range ljs {
lj.IP = normalizeIP(lj.IP)
ip, ok := netip.AddrFromSlice(lj.IP)
if !ok {
log.Info("dhcp: invalid IP: %s", lj.IP)
continue
}
lease := &Lease{
Expiry: time.Unix(lj.Expiry, 0),
Hostname: lj.Hostname,
HWAddr: lj.HWAddr,
IP: ip,
IsStatic: lj.Expiry == leaseExpireStatic,
}
leases = append(leases, lease)
}
err = writeDB(dataDirPath, leases)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
return os.Remove(oldLeasesPath)
}

View File

@@ -1,73 +0,0 @@
package dhcpd
import (
"encoding/json"
"net"
"net/netip"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const testData = `[
{"mac":"ESIzRFVm","ip":"AQIDBA==","host":"test1","exp":1},
{"mac":"ZlVEMyIR","ip":"BAMCAQ==","host":"test2","exp":1231231231}
]`
func TestMigrateDB(t *testing.T) {
dir := t.TempDir()
oldLeasesPath := filepath.Join(dir, dbFilename)
dataDirPath := filepath.Join(dir, dataFilename)
err := os.WriteFile(oldLeasesPath, []byte(testData), 0o644)
require.NoError(t, err)
wantLeases := []*Lease{{
Expiry: time.Time{},
Hostname: "test1",
HWAddr: net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
IP: netip.MustParseAddr("1.2.3.4"),
IsStatic: true,
}, {
Expiry: time.Unix(1231231231, 0),
Hostname: "test2",
HWAddr: net.HardwareAddr{0x66, 0x55, 0x44, 0x33, 0x22, 0x11},
IP: netip.MustParseAddr("4.3.2.1"),
IsStatic: false,
}}
conf := &ServerConfig{
WorkDir: dir,
DataDir: dir,
}
err = migrateDB(conf)
require.NoError(t, err)
_, err = os.Stat(oldLeasesPath)
require.ErrorIs(t, err, os.ErrNotExist)
var data []byte
data, err = os.ReadFile(dataDirPath)
require.NoError(t, err)
dl := &dataLeases{}
err = json.Unmarshal(data, dl)
require.NoError(t, err)
leases := dl.Leases
for i, wl := range wantLeases {
assert.Equal(t, wl.Hostname, leases[i].Hostname)
assert.Equal(t, wl.HWAddr, leases[i].HWAddr)
assert.Equal(t, wl.IP, leases[i].IP)
assert.Equal(t, wl.IsStatic, leases[i].IsStatic)
require.True(t, wl.Expiry.Equal(leases[i].Expiry))
}
}

View File

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

View File

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

View File

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

View File

@@ -68,6 +68,7 @@ func TestV4Server_leasing(t *testing.T) {
t.Run("add_static", func(t *testing.T) { t.Run("add_static", func(t *testing.T) {
err := s.AddStaticLease(&Lease{ err := s.AddStaticLease(&Lease{
Expiry: time.Unix(leaseExpireStatic, 0),
Hostname: staticName, Hostname: staticName,
HWAddr: staticMAC, HWAddr: staticMAC,
IP: staticIP, IP: staticIP,
@@ -77,6 +78,7 @@ func TestV4Server_leasing(t *testing.T) {
t.Run("same_name", func(t *testing.T) { t.Run("same_name", func(t *testing.T) {
err = s.AddStaticLease(&Lease{ err = s.AddStaticLease(&Lease{
Expiry: time.Unix(leaseExpireStatic, 0),
Hostname: staticName, Hostname: staticName,
HWAddr: anotherMAC, HWAddr: anotherMAC,
IP: anotherIP, IP: anotherIP,
@@ -91,6 +93,7 @@ func TestV4Server_leasing(t *testing.T) {
" (" + staticMAC.String() + "): static lease already exists" " (" + staticMAC.String() + "): static lease already exists"
err = s.AddStaticLease(&Lease{ err = s.AddStaticLease(&Lease{
Expiry: time.Unix(leaseExpireStatic, 0),
Hostname: anotherName, Hostname: anotherName,
HWAddr: staticMAC, HWAddr: staticMAC,
IP: anotherIP, IP: anotherIP,
@@ -105,6 +108,7 @@ func TestV4Server_leasing(t *testing.T) {
" (" + anotherMAC.String() + "): static lease already exists" " (" + anotherMAC.String() + "): static lease already exists"
err = s.AddStaticLease(&Lease{ err = s.AddStaticLease(&Lease{
Expiry: time.Unix(leaseExpireStatic, 0),
Hostname: anotherName, Hostname: anotherName,
HWAddr: anotherMAC, HWAddr: anotherMAC,
IP: staticIP, IP: staticIP,
@@ -227,7 +231,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
}, },
name: "with_gateway_ip", name: "with_gateway_ip",
wantErrMsg: "dhcpv4: adding static lease: " + wantErrMsg: "dhcpv4: adding static lease: " +
`can't assign the gateway IP "192.168.10.1" to the lease`, "can't assign the gateway IP 192.168.10.1 to the lease",
}, { }, {
lease: &Lease{ lease: &Lease{
Hostname: "ip6.local", Hostname: "ip6.local",
@@ -236,7 +240,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
}, },
name: "ipv6", name: "ipv6",
wantErrMsg: `dhcpv4: adding static lease: ` + wantErrMsg: `dhcpv4: adding static lease: ` +
`invalid IP "ffff::1": only IPv4 is supported`, `invalid ip "ffff::1", only ipv4 is supported`,
}, { }, {
lease: &Lease{ lease: &Lease{
Hostname: "bad-mac.local", Hostname: "bad-mac.local",
@@ -780,6 +784,7 @@ func TestV4Server_FindMACbyIP(t *testing.T) {
s := &v4Server{ s := &v4Server{
leases: []*Lease{{ leases: []*Lease{{
Expiry: time.Unix(leaseExpireStatic, 0),
Hostname: staticName, Hostname: staticName,
HWAddr: staticMAC, HWAddr: staticMAC,
IP: staticIP, IP: staticIP,

View File

@@ -30,7 +30,7 @@ type v6Server struct {
leasesLock sync.Mutex leasesLock sync.Mutex
leases []*Lease leases []*Lease
ipAddrs [256]byte ipAddrs [256]byte
sid dhcpv6.DUID sid dhcpv6.Duid
ra raCtx // RA module ra raCtx // RA module
@@ -66,7 +66,8 @@ func (s *v6Server) ResetLeases(leases []*Lease) (err error) {
s.leases = nil s.leases = nil
for _, l := range leases { for _, l := range leases {
ip := net.IP(l.IP.AsSlice()) ip := net.IP(l.IP.AsSlice())
if !l.IsStatic && !ip6InRange(s.conf.ipStart, ip) { if l.Expiry.Unix() != leaseExpireStatic &&
!ip6InRange(s.conf.ipStart, ip) {
log.Debug("dhcpv6: skipping a lease with IP %v: not within current IP range", l.IP) log.Debug("dhcpv6: skipping a lease with IP %v: not within current IP range", l.IP)
@@ -88,7 +89,7 @@ func (s *v6Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) {
leases = []*Lease{} leases = []*Lease{}
s.leasesLock.Lock() s.leasesLock.Lock()
for _, l := range s.leases { for _, l := range s.leases {
if l.IsStatic { if l.Expiry.Unix() == leaseExpireStatic {
if (flags & LeasesStatic) != 0 { if (flags & LeasesStatic) != 0 {
leases = append(leases, l.Clone()) leases = append(leases, l.Clone())
} }
@@ -149,7 +150,7 @@ func (s *v6Server) rmDynamicLease(lease *Lease) (err error) {
l := s.leases[i] l := s.leases[i]
if bytes.Equal(l.HWAddr, lease.HWAddr) { if bytes.Equal(l.HWAddr, lease.HWAddr) {
if l.IsStatic { if l.Expiry.Unix() == leaseExpireStatic {
return fmt.Errorf("static lease already exists") return fmt.Errorf("static lease already exists")
} }
@@ -162,7 +163,7 @@ func (s *v6Server) rmDynamicLease(lease *Lease) (err error) {
} }
if l.IP == lease.IP { if l.IP == lease.IP {
if l.IsStatic { if l.Expiry.Unix() == leaseExpireStatic {
return fmt.Errorf("static lease already exists") return fmt.Errorf("static lease already exists")
} }
@@ -186,7 +187,7 @@ func (s *v6Server) AddStaticLease(l *Lease) (err error) {
return fmt.Errorf("validating lease: %w", err) return fmt.Errorf("validating lease: %w", err)
} }
l.IsStatic = true l.Expiry = time.Unix(leaseExpireStatic, 0)
s.leasesLock.Lock() s.leasesLock.Lock()
err = s.rmDynamicLease(l) err = s.rmDynamicLease(l)
@@ -273,7 +274,8 @@ func (s *v6Server) findLease(mac net.HardwareAddr) *Lease {
func (s *v6Server) findExpiredLease() int { func (s *v6Server) findExpiredLease() int {
now := time.Now().Unix() now := time.Now().Unix()
for i, lease := range s.leases { for i, lease := range s.leases {
if !lease.IsStatic && lease.Expiry.Unix() <= now { if lease.Expiry.Unix() != leaseExpireStatic &&
lease.Expiry.Unix() <= now {
return i return i
} }
} }
@@ -419,7 +421,7 @@ func (s *v6Server) commitLease(msg *dhcpv6.Message, lease *Lease) time.Duration
dhcpv6.MessageTypeRenew, dhcpv6.MessageTypeRenew,
dhcpv6.MessageTypeRebind: dhcpv6.MessageTypeRebind:
if !lease.IsStatic { if lease.Expiry.Unix() != leaseExpireStatic {
s.commitDynamicLease(lease) s.commitDynamicLease(lease)
} }
} }
@@ -586,31 +588,9 @@ func (s *v6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6.
} }
} }
// configureDNSIPAddrs updates v6Server configuration with the slice of DNS IP // initialize RA module
// addresses of provided interface iface. Initializes RA module. func (s *v6Server) initRA(iface *net.Interface) error {
func (s *v6Server) configureDNSIPAddrs(iface *net.Interface) (ok bool, err error) { // choose the source IP address - should be link-local-unicast
dnsIPAddrs, err := aghnet.IfaceDNSIPAddrs(
iface,
aghnet.IPVersion6,
defaultMaxAttempts,
defaultBackoff,
)
if err != nil {
return false, fmt.Errorf("interface %s: %w", iface.Name, err)
}
if len(dnsIPAddrs) == 0 {
return false, nil
}
s.conf.dnsIPAddrs = dnsIPAddrs
return true, s.initRA(iface)
}
// initRA initializes RA module.
func (s *v6Server) initRA(iface *net.Interface) (err error) {
// Choose the source IP address - should be link-local-unicast.
s.ra.ipAddr = s.conf.dnsIPAddrs[0] s.ra.ipAddr = s.conf.dnsIPAddrs[0]
for _, ip := range s.conf.dnsIPAddrs { for _, ip := range s.conf.dnsIPAddrs {
if ip.IsLinkLocalUnicast() { if ip.IsLinkLocalUnicast() {
@@ -626,7 +606,6 @@ func (s *v6Server) initRA(iface *net.Interface) (err error) {
s.ra.ifaceName = s.conf.InterfaceName s.ra.ifaceName = s.conf.InterfaceName
s.ra.iface = iface s.ra.iface = iface
s.ra.packetSendPeriod = 1 * time.Second s.ra.packetSendPeriod = 1 * time.Second
return s.ra.Init() return s.ra.Init()
} }
@@ -646,47 +625,63 @@ func (s *v6Server) Start() (err error) {
log.Debug("dhcpv6: starting...") log.Debug("dhcpv6: starting...")
ok, err := s.configureDNSIPAddrs(iface) dnsIPAddrs, err := aghnet.IfaceDNSIPAddrs(
iface,
aghnet.IPVersion6,
defaultMaxAttempts,
defaultBackoff,
)
if err != nil { if err != nil {
// Don't wrap the error, because it's informative enough as is. return fmt.Errorf("interface %s: %w", ifaceName, err)
return err
} }
if !ok { if len(dnsIPAddrs) == 0 {
// No available IP addresses which may appear later. // No available IP addresses which may appear later.
return nil return nil
} }
// Don't initialize DHCPv6 server if we must force the clients to use SLAAC. s.conf.dnsIPAddrs = dnsIPAddrs
err = s.initRA(iface)
if err != nil {
return err
}
// don't initialize DHCPv6 server if we must force the clients to use SLAAC
if s.conf.RASLAACOnly { if s.conf.RASLAACOnly {
log.Debug("not starting dhcpv6 server due to ra_slaac_only=true") log.Debug("not starting dhcpv6 server due to ra_slaac_only=true")
return nil return nil
} }
log.Debug("dhcpv6: listening...")
err = netutil.ValidateMAC(iface.HardwareAddr) err = netutil.ValidateMAC(iface.HardwareAddr)
if err != nil { if err != nil {
return fmt.Errorf("validating interface %s: %w", iface.Name, err) return fmt.Errorf("validating interface %s: %w", iface.Name, err)
} }
s.sid = &dhcpv6.DUIDLLT{ s.sid = dhcpv6.Duid{
HWType: iana.HWTypeEthernet, Type: dhcpv6.DUID_LLT,
HwType: iana.HWTypeEthernet,
LinkLayerAddr: iface.HardwareAddr, LinkLayerAddr: iface.HardwareAddr,
Time: dhcpv6.GetTime(), Time: dhcpv6.GetTime(),
} }
s.srv, err = server6.NewServer(iface.Name, nil, s.packetHandler, server6.WithDebugLogger()) laddr := &net.UDPAddr{
IP: net.ParseIP("::"),
Port: dhcpv6.DefaultServerPort,
}
s.srv, err = server6.NewServer(iface.Name, laddr, s.packetHandler, server6.WithDebugLogger())
if err != nil { if err != nil {
return err return err
} }
log.Debug("dhcpv6: listening...")
go func() { go func() {
if sErr := s.srv.Serve(); errors.Is(sErr, net.ErrClosed) { if serr := s.srv.Serve(); errors.Is(serr, net.ErrClosed) {
log.Info("dhcpv6: server is closed") log.Info("dhcpv6: server is closed")
} else if sErr != nil { } else if serr != nil {
log.Error("dhcpv6: srv.Serve: %s", sErr) log.Error("dhcpv6: srv.Serve: %s", serr)
} }
}() }()

View File

@@ -44,7 +44,7 @@ func TestV6_AddRemove_static(t *testing.T) {
assert.Equal(t, l.IP, ls[0].IP) assert.Equal(t, l.IP, ls[0].IP)
assert.Equal(t, l.HWAddr, ls[0].HWAddr) assert.Equal(t, l.HWAddr, ls[0].HWAddr)
assert.True(t, ls[0].IsStatic) assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix())
// Try to remove non-existent static lease. // Try to remove non-existent static lease.
err = s.RemoveStaticLease(&Lease{ err = s.RemoveStaticLease(&Lease{
@@ -103,7 +103,7 @@ func TestV6_AddReplace(t *testing.T) {
for i, l := range ls { for i, l := range ls {
assert.Equal(t, stLeases[i].IP, l.IP) assert.Equal(t, stLeases[i].IP, l.IP)
assert.Equal(t, stLeases[i].HWAddr, l.HWAddr) assert.Equal(t, stLeases[i].HWAddr, l.HWAddr)
assert.True(t, l.IsStatic) assert.EqualValues(t, leaseExpireStatic, l.Expiry.Unix())
} }
} }
@@ -121,8 +121,9 @@ func TestV6GetLease(t *testing.T) {
dnsAddr := net.ParseIP("2000::1") dnsAddr := net.ParseIP("2000::1")
s.conf.dnsIPAddrs = []net.IP{dnsAddr} s.conf.dnsIPAddrs = []net.IP{dnsAddr}
s.sid = &dhcpv6.DUIDLL{ s.sid = dhcpv6.Duid{
HWType: iana.HWTypeEthernet, Type: dhcpv6.DUID_LLT,
HwType: iana.HWTypeEthernet,
LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
} }
@@ -215,8 +216,9 @@ func TestV6GetDynamicLease(t *testing.T) {
dnsAddr := net.ParseIP("2000::1") dnsAddr := net.ParseIP("2000::1")
s.conf.dnsIPAddrs = []net.IP{dnsAddr} s.conf.dnsIPAddrs = []net.IP{dnsAddr}
s.sid = &dhcpv6.DUIDLL{ s.sid = dhcpv6.Duid{
HWType: iana.HWTypeEthernet, Type: dhcpv6.DUID_LLT,
HwType: iana.HWTypeEthernet,
LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
} }
@@ -325,6 +327,7 @@ func TestV6_FindMACbyIP(t *testing.T) {
s := &v6Server{ s := &v6Server{
leases: []*Lease{{ leases: []*Lease{{
Expiry: time.Unix(leaseExpireStatic, 0),
Hostname: staticName, Hostname: staticName,
HWAddr: staticMAC, HWAddr: staticMAC,
IP: staticIP, IP: staticIP,
@@ -338,6 +341,7 @@ func TestV6_FindMACbyIP(t *testing.T) {
} }
s.leases = []*Lease{{ s.leases = []*Lease{{
Expiry: time.Unix(leaseExpireStatic, 0),
Hostname: staticName, Hostname: staticName,
HWAddr: staticMAC, HWAddr: staticMAC,
IP: staticIP, IP: staticIP,

View File

@@ -23,7 +23,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/hashprefix"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch" "github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
"github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/dnsproxy/upstream"
@@ -916,23 +915,13 @@ func TestBlockedByHosts(t *testing.T) {
} }
func TestBlockedBySafeBrowsing(t *testing.T) { func TestBlockedBySafeBrowsing(t *testing.T) {
const ( const hostname = "wmconvirus.narod.ru"
hostname = "wmconvirus.narod.ru"
cacheTime = 10 * time.Minute
cacheSize = 10000
)
sbChecker := hashprefix.New(&hashprefix.Config{
CacheTime: cacheTime,
CacheSize: cacheSize,
Upstream: aghtest.NewBlockUpstream(hostname, true),
})
sbUps := aghtest.NewBlockUpstream(hostname, true)
ans4, _ := (&aghtest.TestResolver{}).HostToIPs(hostname) ans4, _ := (&aghtest.TestResolver{}).HostToIPs(hostname)
filterConf := &filtering.Config{ filterConf := &filtering.Config{
SafeBrowsingEnabled: true, SafeBrowsingEnabled: true,
SafeBrowsingChecker: sbChecker,
} }
forwardConf := ServerConfig{ forwardConf := ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}}, UDPListenAddrs: []*net.UDPAddr{{}},
@@ -946,6 +935,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
}, },
} }
s := createTestServer(t, filterConf, forwardConf, nil) s := createTestServer(t, filterConf, forwardConf, nil)
s.dnsFilter.SetSafeBrowsingUpstream(sbUps)
startDeferStop(t, s) startDeferStop(t, s)
addr := s.dnsProxy.Addr(proxy.ProtoUDP) addr := s.dnsProxy.Addr(proxy.ProtoUDP)

View File

@@ -53,14 +53,14 @@ func (s *Server) beforeRequestHandler(
// getClientRequestFilteringSettings looks up client filtering settings using // getClientRequestFilteringSettings looks up client filtering settings using
// the client's IP address and ID, if any, from dctx. // the client's IP address and ID, if any, from dctx.
func (s *Server) getClientRequestFilteringSettings(dctx *dnsContext) *filtering.Settings { func (s *Server) getClientRequestFilteringSettings(dctx *dnsContext) *filtering.Settings {
setts := s.dnsFilter.Settings() setts := s.dnsFilter.GetConfig()
setts.ProtectionEnabled = dctx.protectionEnabled setts.ProtectionEnabled = dctx.protectionEnabled
if s.conf.FilterHandler != nil { if s.conf.FilterHandler != nil {
ip, _ := netutil.IPAndPortFromAddr(dctx.proxyCtx.Addr) ip, _ := netutil.IPAndPortFromAddr(dctx.proxyCtx.Addr)
s.conf.FilterHandler(ip, dctx.clientID, setts) s.conf.FilterHandler(ip, dctx.clientID, &setts)
} }
return setts return &setts
} }
// filterDNSRequest applies the dnsFilter and sets dctx.proxyCtx.Res if the // filterDNSRequest applies the dnsFilter and sets dctx.proxyCtx.Res if the

View File

@@ -205,8 +205,8 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
wantSet: `validating upstream servers: validating upstream "!!!": not an ip:port`, wantSet: `validating upstream servers: validating upstream "!!!": not an ip:port`,
}, { }, {
name: "bootstraps_bad", name: "bootstraps_bad",
wantSet: `checking bootstrap a: invalid address: bootstrap a:53: ` + wantSet: `checking bootstrap a: invalid address: ` +
`ParseAddr("a"): unable to parse IP`, `Resolver a is not eligible to be a bootstrap DNS server`,
}, { }, {
name: "cache_bad_ttl", name: "cache_bad_ttl",
wantSet: `cache_ttl_min must be less or equal than cache_ttl_max`, wantSet: `cache_ttl_min must be less or equal than cache_ttl_max`,
@@ -487,8 +487,7 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
}, },
wantResp: map[string]any{ wantResp: map[string]any{
badUps: `upstream "` + badUps + `" fails to exchange: ` + badUps: `upstream "` + badUps + `" fails to exchange: ` +
`couldn't communicate with upstream: exchanging with ` + `couldn't communicate with upstream: dns: id mismatch`,
badUps + ` over tcp: dns: id mismatch`,
}, },
name: "broken", name: "broken",
}, { }, {
@@ -498,8 +497,7 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
wantResp: map[string]any{ wantResp: map[string]any{
goodUps: "OK", goodUps: "OK",
badUps: `upstream "` + badUps + `" fails to exchange: ` + badUps: `upstream "` + badUps + `" fails to exchange: ` +
`couldn't communicate with upstream: exchanging with ` + `couldn't communicate with upstream: dns: id mismatch`,
badUps + ` over tcp: dns: id mismatch`,
}, },
name: "both", name: "both",
}} }}

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,8 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/cache"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil" "github.com/AdguardTeam/golibs/mathutil"
@@ -73,12 +75,6 @@ type Resolver interface {
// Config allows you to configure DNS filtering with New() or just change variables directly. // Config allows you to configure DNS filtering with New() or just change variables directly.
type Config struct { type Config struct {
// SafeBrowsingChecker is the safe browsing hash-prefix checker.
SafeBrowsingChecker Checker `yaml:"-"`
// ParentControl is the parental control hash-prefix checker.
ParentalControlChecker Checker `yaml:"-"`
// enabled is used to be returned within Settings. // enabled is used to be returned within Settings.
// //
// It is of type uint32 to be accessed by atomic. // It is of type uint32 to be accessed by atomic.
@@ -103,9 +99,9 @@ type Config struct {
Rewrites []*LegacyRewrite `yaml:"rewrites"` Rewrites []*LegacyRewrite `yaml:"rewrites"`
// BlockedServices is the configuration of blocked services. // Names of services to block (globally).
// Per-client settings can override this configuration. // Per-client settings can override this configuration.
BlockedServices *BlockedServices `yaml:"blocked_services"` BlockedServices []string `yaml:"blocked_services"`
// EtcHosts is a container of IP-hostname pairs taken from the operating // EtcHosts is a container of IP-hostname pairs taken from the operating
// system configuration files (e.g. /etc/hosts). // system configuration files (e.g. /etc/hosts).
@@ -162,22 +158,8 @@ type hostChecker struct {
name string name string
} }
// Checker is used for safe browsing or parental control hash-prefix filtering.
type Checker interface {
// Check returns true if request for the host should be blocked.
Check(host string) (block bool, err error)
}
// DNSFilter matches hostnames and DNS requests against filtering rules. // DNSFilter matches hostnames and DNS requests against filtering rules.
type DNSFilter struct { type DNSFilter struct {
safeSearch SafeSearch
// safeBrowsingChecker is the safe browsing hash-prefix checker.
safeBrowsingChecker Checker
// parentalControl is the parental control hash-prefix checker.
parentalControlChecker Checker
rulesStorage *filterlist.RuleStorage rulesStorage *filterlist.RuleStorage
filteringEngine *urlfilter.DNSEngine filteringEngine *urlfilter.DNSEngine
@@ -186,6 +168,14 @@ type DNSFilter struct {
engineLock sync.RWMutex engineLock sync.RWMutex
parentalServer string // access via methods
safeBrowsingServer string // access via methods
parentalUpstream upstream.Upstream
safeBrowsingUpstream upstream.Upstream
safebrowsingCache cache.Cache
parentalCache cache.Cache
Config // for direct access by library users, even a = assignment Config // for direct access by library users, even a = assignment
// confLock protects Config. // confLock protects Config.
confLock sync.RWMutex confLock sync.RWMutex
@@ -202,6 +192,7 @@ type DNSFilter struct {
// TODO(e.burkov): Don't use regexp for such a simple text processing task. // TODO(e.burkov): Don't use regexp for such a simple text processing task.
filterTitleRegexp *regexp.Regexp filterTitleRegexp *regexp.Regexp
safeSearch SafeSearch
hostCheckers []hostChecker hostCheckers []hostChecker
} }
@@ -298,12 +289,12 @@ func (d *DNSFilter) SetEnabled(enabled bool) {
atomic.StoreUint32(&d.enabled, mathutil.BoolToNumber[uint32](enabled)) atomic.StoreUint32(&d.enabled, mathutil.BoolToNumber[uint32](enabled))
} }
// Settings returns filtering settings. // GetConfig - get configuration
func (d *DNSFilter) Settings() (s *Settings) { func (d *DNSFilter) GetConfig() (s Settings) {
d.confLock.RLock() d.confLock.RLock()
defer d.confLock.RUnlock() defer d.confLock.RUnlock()
return &Settings{ return Settings{
FilteringEnabled: atomic.LoadUint32(&d.Config.enabled) != 0, FilteringEnabled: atomic.LoadUint32(&d.Config.enabled) != 0,
SafeSearchEnabled: d.Config.SafeSearchConf.Enabled, SafeSearchEnabled: d.Config.SafeSearchConf.Enabled,
SafeBrowsingEnabled: d.Config.SafeBrowsingEnabled, SafeBrowsingEnabled: d.Config.SafeBrowsingEnabled,
@@ -951,10 +942,17 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
d = &DNSFilter{ d = &DNSFilter{
refreshLock: &sync.Mutex{}, refreshLock: &sync.Mutex{},
filterTitleRegexp: regexp.MustCompile(`^! Title: +(.*)$`), filterTitleRegexp: regexp.MustCompile(`^! Title: +(.*)$`),
safeBrowsingChecker: c.SafeBrowsingChecker,
parentalControlChecker: c.ParentalControlChecker,
} }
d.safebrowsingCache = cache.New(cache.Config{
EnableLRU: true,
MaxSize: c.SafeBrowsingCacheSize,
})
d.parentalCache = cache.New(cache.Config{
EnableLRU: true,
MaxSize: c.ParentalCacheSize,
})
d.safeSearch = c.SafeSearch d.safeSearch = c.SafeSearch
d.hostCheckers = []hostChecker{{ d.hostCheckers = []hostChecker{{
@@ -979,6 +977,11 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
defer func() { err = errors.Annotate(err, "filtering: %w") }() defer func() { err = errors.Annotate(err, "filtering: %w") }()
err = d.initSecurityServices()
if err != nil {
return nil, fmt.Errorf("initializing services: %s", err)
}
d.Config = *c d.Config = *c
d.filtersMu = &sync.RWMutex{} d.filtersMu = &sync.RWMutex{}
@@ -987,19 +990,16 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
return nil, fmt.Errorf("rewrites: preparing: %s", err) return nil, fmt.Errorf("rewrites: preparing: %s", err)
} }
if d.BlockedServices != nil {
bsvcs := []string{} bsvcs := []string{}
for _, s := range d.BlockedServices.IDs { for _, s := range d.BlockedServices {
if !BlockedSvcKnown(s) { if !BlockedSvcKnown(s) {
log.Debug("skipping unknown blocked-service %q", s) log.Debug("skipping unknown blocked-service %q", s)
continue continue
} }
bsvcs = append(bsvcs, s) bsvcs = append(bsvcs, s)
} }
d.BlockedServices.IDs = bsvcs d.BlockedServices = bsvcs
}
if blockFilters != nil { if blockFilters != nil {
err = d.initFiltering(nil, blockFilters) err = d.initFiltering(nil, blockFilters)
@@ -1038,69 +1038,3 @@ func (d *DNSFilter) Start() {
// So for now we just start this periodic task from here. // So for now we just start this periodic task from here.
go d.periodicallyRefreshFilters() go d.periodicallyRefreshFilters()
} }
// Safe browsing and parental control methods.
// TODO(a.garipov): Unify with checkParental.
func (d *DNSFilter) checkSafeBrowsing(
host string,
_ uint16,
setts *Settings,
) (res Result, err error) {
if !setts.ProtectionEnabled || !setts.SafeBrowsingEnabled {
return Result{}, nil
}
if log.GetLevel() >= log.DEBUG {
timer := log.StartTimer()
defer timer.LogElapsed("safebrowsing lookup for %q", host)
}
res = Result{
Rules: []*ResultRule{{
Text: "adguard-malware-shavar",
FilterListID: SafeBrowsingListID,
}},
Reason: FilteredSafeBrowsing,
IsFiltered: true,
}
block, err := d.safeBrowsingChecker.Check(host)
if !block || err != nil {
return Result{}, err
}
return res, nil
}
// TODO(a.garipov): Unify with checkSafeBrowsing.
func (d *DNSFilter) checkParental(
host string,
_ uint16,
setts *Settings,
) (res Result, err error) {
if !setts.ProtectionEnabled || !setts.ParentalEnabled {
return Result{}, nil
}
if log.GetLevel() >= log.DEBUG {
timer := log.StartTimer()
defer timer.LogElapsed("parental lookup for %q", host)
}
res = Result{
Rules: []*ResultRule{{
Text: "parental CATEGORY_BLACKLISTED",
FilterListID: ParentalListID,
}},
Reason: FilteredParental,
IsFiltered: true,
}
block, err := d.parentalControlChecker.Check(host)
if !block || err != nil {
return Result{}, err
}
return res, nil
}

View File

@@ -7,7 +7,7 @@ import (
"testing" "testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/hashprefix" "github.com/AdguardTeam/golibs/cache"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/urlfilter/rules" "github.com/AdguardTeam/urlfilter/rules"
@@ -27,6 +27,17 @@ const (
// Helpers. // Helpers.
func purgeCaches(d *DNSFilter) {
for _, c := range []cache.Cache{
d.safebrowsingCache,
d.parentalCache,
} {
if c != nil {
c.Clear()
}
}
}
func newForTest(t testing.TB, c *Config, filters []Filter) (f *DNSFilter, setts *Settings) { func newForTest(t testing.TB, c *Config, filters []Filter) (f *DNSFilter, setts *Settings) {
setts = &Settings{ setts = &Settings{
ProtectionEnabled: true, ProtectionEnabled: true,
@@ -47,15 +58,9 @@ func newForTest(t testing.TB, c *Config, filters []Filter) (f *DNSFilter, setts
f, err := New(c, filters) f, err := New(c, filters)
require.NoError(t, err) require.NoError(t, err)
return f, setts purgeCaches(f)
}
func newChecker(host string) Checker { return f, setts
return hashprefix.New(&hashprefix.Config{
CacheTime: 10,
CacheSize: 100000,
Upstream: aghtest.NewBlockUpstream(host, true),
})
} }
func (d *DNSFilter) checkMatch(t *testing.T, hostname string, setts *Settings) { func (d *DNSFilter) checkMatch(t *testing.T, hostname string, setts *Settings) {
@@ -170,14 +175,10 @@ func TestSafeBrowsing(t *testing.T) {
aghtest.ReplaceLogWriter(t, logOutput) aghtest.ReplaceLogWriter(t, logOutput)
aghtest.ReplaceLogLevel(t, log.DEBUG) aghtest.ReplaceLogLevel(t, log.DEBUG)
sbChecker := newChecker(sbBlocked) d, setts := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
d, setts := newForTest(t, &Config{
SafeBrowsingEnabled: true,
SafeBrowsingChecker: sbChecker,
}, nil)
t.Cleanup(d.Close) t.Cleanup(d.Close)
d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
d.checkMatch(t, sbBlocked, setts) d.checkMatch(t, sbBlocked, setts)
require.Contains(t, logOutput.String(), fmt.Sprintf("safebrowsing lookup for %q", sbBlocked)) require.Contains(t, logOutput.String(), fmt.Sprintf("safebrowsing lookup for %q", sbBlocked))
@@ -187,17 +188,18 @@ func TestSafeBrowsing(t *testing.T) {
d.checkMatchEmpty(t, pcBlocked, setts) d.checkMatchEmpty(t, pcBlocked, setts)
// Cached result. // Cached result.
d.safeBrowsingServer = "127.0.0.1"
d.checkMatch(t, sbBlocked, setts) d.checkMatch(t, sbBlocked, setts)
d.checkMatchEmpty(t, pcBlocked, setts) d.checkMatchEmpty(t, pcBlocked, setts)
d.safeBrowsingServer = defaultSafebrowsingServer
} }
func TestParallelSB(t *testing.T) { func TestParallelSB(t *testing.T) {
d, setts := newForTest(t, &Config{ d, setts := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
SafeBrowsingEnabled: true,
SafeBrowsingChecker: newChecker(sbBlocked),
}, nil)
t.Cleanup(d.Close) t.Cleanup(d.Close)
d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
t.Run("group", func(t *testing.T) { t.Run("group", func(t *testing.T) {
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
t.Run(fmt.Sprintf("aaa%d", i), func(t *testing.T) { t.Run(fmt.Sprintf("aaa%d", i), func(t *testing.T) {
@@ -218,12 +220,10 @@ func TestParentalControl(t *testing.T) {
aghtest.ReplaceLogWriter(t, logOutput) aghtest.ReplaceLogWriter(t, logOutput)
aghtest.ReplaceLogLevel(t, log.DEBUG) aghtest.ReplaceLogLevel(t, log.DEBUG)
d, setts := newForTest(t, &Config{ d, setts := newForTest(t, &Config{ParentalEnabled: true}, nil)
ParentalEnabled: true,
ParentalControlChecker: newChecker(pcBlocked),
}, nil)
t.Cleanup(d.Close) t.Cleanup(d.Close)
d.SetParentalUpstream(aghtest.NewBlockUpstream(pcBlocked, true))
d.checkMatch(t, pcBlocked, setts) d.checkMatch(t, pcBlocked, setts)
require.Contains(t, logOutput.String(), fmt.Sprintf("parental lookup for %q", pcBlocked)) require.Contains(t, logOutput.String(), fmt.Sprintf("parental lookup for %q", pcBlocked))
@@ -233,6 +233,7 @@ func TestParentalControl(t *testing.T) {
d.checkMatchEmpty(t, "api.jquery.com", setts) d.checkMatchEmpty(t, "api.jquery.com", setts)
// Test cached result. // Test cached result.
d.parentalServer = "127.0.0.1"
d.checkMatch(t, pcBlocked, setts) d.checkMatch(t, pcBlocked, setts)
d.checkMatchEmpty(t, "yandex.ru", setts) d.checkMatchEmpty(t, "yandex.ru", setts)
} }
@@ -594,8 +595,6 @@ func TestClientSettings(t *testing.T) {
&Config{ &Config{
ParentalEnabled: true, ParentalEnabled: true,
SafeBrowsingEnabled: false, SafeBrowsingEnabled: false,
SafeBrowsingChecker: newChecker(sbBlocked),
ParentalControlChecker: newChecker(pcBlocked),
}, },
[]Filter{{ []Filter{{
ID: 0, Data: []byte("||example.org^\n"), ID: 0, Data: []byte("||example.org^\n"),
@@ -603,6 +602,9 @@ func TestClientSettings(t *testing.T) {
) )
t.Cleanup(d.Close) t.Cleanup(d.Close)
d.SetParentalUpstream(aghtest.NewBlockUpstream(pcBlocked, true))
d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
type testCase struct { type testCase struct {
name string name string
host string host string
@@ -663,12 +665,11 @@ func TestClientSettings(t *testing.T) {
// Benchmarks. // Benchmarks.
func BenchmarkSafeBrowsing(b *testing.B) { func BenchmarkSafeBrowsing(b *testing.B) {
d, setts := newForTest(b, &Config{ d, setts := newForTest(b, &Config{SafeBrowsingEnabled: true}, nil)
SafeBrowsingEnabled: true,
SafeBrowsingChecker: newChecker(sbBlocked),
}, nil)
b.Cleanup(d.Close) b.Cleanup(d.Close)
d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
res, err := d.CheckHost(sbBlocked, dns.TypeA, setts) res, err := d.CheckHost(sbBlocked, dns.TypeA, setts)
require.NoError(b, err) require.NoError(b, err)
@@ -678,12 +679,11 @@ func BenchmarkSafeBrowsing(b *testing.B) {
} }
func BenchmarkSafeBrowsingParallel(b *testing.B) { func BenchmarkSafeBrowsingParallel(b *testing.B) {
d, setts := newForTest(b, &Config{ d, setts := newForTest(b, &Config{SafeBrowsingEnabled: true}, nil)
SafeBrowsingEnabled: true,
SafeBrowsingChecker: newChecker(sbBlocked),
}, nil)
b.Cleanup(d.Close) b.Cleanup(d.Close)
d.SetSafeBrowsingUpstream(aghtest.NewBlockUpstream(sbBlocked, true))
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
res, err := d.CheckHost(sbBlocked, dns.TypeA, setts) res, err := d.CheckHost(sbBlocked, dns.TypeA, setts)

View File

@@ -1,130 +0,0 @@
package hashprefix
import (
"encoding/binary"
"time"
"github.com/AdguardTeam/golibs/log"
)
// expirySize is the size of expiry in cacheItem.
const expirySize = 8
// cacheItem represents an item that we will store in the cache.
type cacheItem struct {
// expiry is the time when cacheItem will expire.
expiry time.Time
// hashes is the hashed hostnames.
hashes []hostnameHash
}
// toCacheItem decodes cacheItem from data. data must be at least equal to
// expiry size.
func toCacheItem(data []byte) *cacheItem {
t := time.Unix(int64(binary.BigEndian.Uint64(data)), 0)
data = data[expirySize:]
hashes := make([]hostnameHash, len(data)/hashSize)
for i := 0; i < len(data); i += hashSize {
var hash hostnameHash
copy(hash[:], data[i:i+hashSize])
hashes = append(hashes, hash)
}
return &cacheItem{
expiry: t,
hashes: hashes,
}
}
// fromCacheItem encodes cacheItem into data.
func fromCacheItem(item *cacheItem) (data []byte) {
data = make([]byte, len(item.hashes)*hashSize+expirySize)
expiry := item.expiry.Unix()
binary.BigEndian.PutUint64(data[:expirySize], uint64(expiry))
for _, v := range item.hashes {
// nolint:looppointer // The subsilce is used for a copy.
data = append(data, v[:]...)
}
return data
}
// findInCache finds hashes in the cache. If nothing found returns list of
// hashes, prefixes of which will be sent to upstream.
func (c *Checker) findInCache(
hashes []hostnameHash,
) (found, blocked bool, hashesToRequest []hostnameHash) {
now := time.Now()
i := 0
for _, hash := range hashes {
// nolint:looppointer // The subsilce is used for a safe cache lookup.
data := c.cache.Get(hash[:prefixLen])
if data == nil {
hashes[i] = hash
i++
continue
}
item := toCacheItem(data)
if now.After(item.expiry) {
hashes[i] = hash
i++
continue
}
if ok := findMatch(hashes, item.hashes); ok {
return true, true, nil
}
}
if i == 0 {
return true, false, nil
}
return false, false, hashes[:i]
}
// storeInCache caches hashes.
func (c *Checker) storeInCache(hashesToRequest, respHashes []hostnameHash) {
hashToStore := make(map[prefix][]hostnameHash)
for _, hash := range respHashes {
var pref prefix
// nolint:looppointer // The subsilce is used for a copy.
copy(pref[:], hash[:])
hashToStore[pref] = append(hashToStore[pref], hash)
}
for pref, hash := range hashToStore {
// nolint:looppointer // The subsilce is used for a safe cache lookup.
c.setCache(pref[:], hash)
}
for _, hash := range hashesToRequest {
// nolint:looppointer // The subsilce is used for a safe cache lookup.
pref := hash[:prefixLen]
val := c.cache.Get(pref)
if val == nil {
c.setCache(pref, nil)
}
}
}
// setCache stores hash in cache.
func (c *Checker) setCache(pref []byte, hashes []hostnameHash) {
item := &cacheItem{
expiry: time.Now().Add(c.cacheTime),
hashes: hashes,
}
c.cache.Set(pref, fromCacheItem(item))
log.Debug("%s: stored in cache: %v", c.svc, pref)
}

View File

@@ -1,245 +0,0 @@
// Package hashprefix used for safe browsing and parent control.
package hashprefix
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
"time"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/cache"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
"golang.org/x/net/publicsuffix"
)
const (
// prefixLen is the length of the hash prefix of the filtered hostname.
prefixLen = 2
// hashSize is the size of hashed hostname.
hashSize = sha256.Size
// hexSize is the size of hexadecimal representation of hashed hostname.
hexSize = hashSize * 2
)
// prefix is the type of the SHA256 hash prefix used to match against the
// domain-name database.
type prefix [prefixLen]byte
// hostnameHash is the hashed hostname.
//
// TODO(s.chzhen): Split into prefix and suffix.
type hostnameHash [hashSize]byte
// findMatch returns true if one of the a hostnames matches one of the b.
func findMatch(a, b []hostnameHash) (matched bool) {
for _, hash := range a {
if slices.Contains(b, hash) {
return true
}
}
return false
}
// Config is the configuration structure for safe browsing and parental
// control.
type Config struct {
// Upstream is the upstream DNS server.
Upstream upstream.Upstream
// ServiceName is the name of the service.
ServiceName string
// TXTSuffix is the TXT suffix for DNS request.
TXTSuffix string
// CacheTime is the time period to store hash.
CacheTime time.Duration
// CacheSize is the maximum size of the cache. If it's zero, cache size is
// unlimited.
CacheSize uint
}
type Checker struct {
// upstream is the upstream DNS server.
upstream upstream.Upstream
// cache stores hostname hashes.
cache cache.Cache
// svc is the name of the service.
svc string
// txtSuffix is the TXT suffix for DNS request.
txtSuffix string
// cacheTime is the time period to store hash.
cacheTime time.Duration
}
// New returns Checker.
func New(conf *Config) (c *Checker) {
return &Checker{
upstream: conf.Upstream,
cache: cache.New(cache.Config{
EnableLRU: true,
MaxSize: conf.CacheSize,
}),
svc: conf.ServiceName,
txtSuffix: conf.TXTSuffix,
cacheTime: conf.CacheTime,
}
}
// Check returns true if request for the host should be blocked.
func (c *Checker) Check(host string) (ok bool, err error) {
hashes := hostnameToHashes(host)
found, blocked, hashesToRequest := c.findInCache(hashes)
if found {
log.Debug("%s: found %q in cache, blocked: %t", c.svc, host, blocked)
return blocked, nil
}
question := c.getQuestion(hashesToRequest)
log.Debug("%s: checking %s: %s", c.svc, host, question)
req := (&dns.Msg{}).SetQuestion(question, dns.TypeTXT)
resp, err := c.upstream.Exchange(req)
if err != nil {
return false, fmt.Errorf("getting hashes: %w", err)
}
matched, receivedHashes := c.processAnswer(hashesToRequest, resp, host)
c.storeInCache(hashesToRequest, receivedHashes)
return matched, nil
}
// hostnameToHashes returns hashes that should be checked by the hash prefix
// filter.
func hostnameToHashes(host string) (hashes []hostnameHash) {
// subDomainNum defines how many labels should be hashed to match against a
// hash prefix filter.
const subDomainNum = 4
pubSuf, icann := publicsuffix.PublicSuffix(host)
if !icann {
// Check the full private domain space.
pubSuf = ""
}
nDots := 0
i := strings.LastIndexFunc(host, func(r rune) (ok bool) {
if r == '.' {
nDots++
}
return nDots == subDomainNum
})
if i != -1 {
host = host[i+1:]
}
sub := netutil.Subdomains(host)
for _, s := range sub {
if s == pubSuf {
break
}
sum := sha256.Sum256([]byte(s))
hashes = append(hashes, sum)
}
return hashes
}
// getQuestion combines hexadecimal encoded prefixes of hashed hostnames into
// string.
func (c *Checker) getQuestion(hashes []hostnameHash) (q string) {
b := &strings.Builder{}
for _, hash := range hashes {
// nolint:looppointer // The subsilce is used for safe hex encoding.
stringutil.WriteToBuilder(b, hex.EncodeToString(hash[:prefixLen]), ".")
}
stringutil.WriteToBuilder(b, c.txtSuffix)
return b.String()
}
// processAnswer returns true if DNS response matches the hash, and received
// hashed hostnames from the upstream.
func (c *Checker) processAnswer(
hashesToRequest []hostnameHash,
resp *dns.Msg,
host string,
) (matched bool, receivedHashes []hostnameHash) {
txtCount := 0
for _, a := range resp.Answer {
txt, ok := a.(*dns.TXT)
if !ok {
continue
}
txtCount++
receivedHashes = c.appendHashesFromTXT(receivedHashes, txt, host)
}
log.Debug("%s: received answer for %s with %d TXT count", c.svc, host, txtCount)
matched = findMatch(hashesToRequest, receivedHashes)
if matched {
log.Debug("%s: matched %s", c.svc, host)
return true, receivedHashes
}
return false, receivedHashes
}
// appendHashesFromTXT appends received hashed hostnames.
func (c *Checker) appendHashesFromTXT(
hashes []hostnameHash,
txt *dns.TXT,
host string,
) (receivedHashes []hostnameHash) {
log.Debug("%s: received hashes for %s: %v", c.svc, host, txt.Txt)
for _, t := range txt.Txt {
if len(t) != hexSize {
log.Debug("%s: wrong hex size %d for %s %s", c.svc, len(t), host, t)
continue
}
buf, err := hex.DecodeString(t)
if err != nil {
log.Debug("%s: decoding hex string %s: %s", c.svc, t, err)
continue
}
var hash hostnameHash
copy(hash[:], buf)
hashes = append(hashes, hash)
}
return hashes
}

View File

@@ -1,248 +0,0 @@
package hashprefix
import (
"crypto/sha256"
"encoding/hex"
"strings"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/cache"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/exp/slices"
)
const (
cacheTime = 10 * time.Minute
cacheSize = 10000
)
func TestChcker_getQuestion(t *testing.T) {
const suf = "sb.dns.adguard.com."
// test hostnameToHashes()
hashes := hostnameToHashes("1.2.3.sub.host.com")
assert.Len(t, hashes, 3)
hash := sha256.Sum256([]byte("3.sub.host.com"))
hexPref1 := hex.EncodeToString(hash[:prefixLen])
assert.True(t, slices.Contains(hashes, hash))
hash = sha256.Sum256([]byte("sub.host.com"))
hexPref2 := hex.EncodeToString(hash[:prefixLen])
assert.True(t, slices.Contains(hashes, hash))
hash = sha256.Sum256([]byte("host.com"))
hexPref3 := hex.EncodeToString(hash[:prefixLen])
assert.True(t, slices.Contains(hashes, hash))
hash = sha256.Sum256([]byte("com"))
assert.False(t, slices.Contains(hashes, hash))
c := &Checker{
svc: "SafeBrowsing",
txtSuffix: suf,
}
q := c.getQuestion(hashes)
assert.Contains(t, q, hexPref1)
assert.Contains(t, q, hexPref2)
assert.Contains(t, q, hexPref3)
assert.True(t, strings.HasSuffix(q, suf))
}
func TestHostnameToHashes(t *testing.T) {
testCases := []struct {
name string
host string
wantLen int
}{{
name: "basic",
host: "example.com",
wantLen: 1,
}, {
name: "sub_basic",
host: "www.example.com",
wantLen: 2,
}, {
name: "private_domain",
host: "foo.co.uk",
wantLen: 1,
}, {
name: "sub_private_domain",
host: "bar.foo.co.uk",
wantLen: 2,
}, {
name: "private_domain_v2",
host: "foo.blogspot.co.uk",
wantLen: 4,
}, {
name: "sub_private_domain_v2",
host: "bar.foo.blogspot.co.uk",
wantLen: 4,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
hashes := hostnameToHashes(tc.host)
assert.Len(t, hashes, tc.wantLen)
})
}
}
func TestChecker_storeInCache(t *testing.T) {
c := &Checker{
svc: "SafeBrowsing",
cacheTime: cacheTime,
}
conf := cache.Config{}
c.cache = cache.New(conf)
// store in cache hashes for "3.sub.host.com" and "host.com"
// and empty data for hash-prefix for "sub.host.com"
hashes := []hostnameHash{}
hash := sha256.Sum256([]byte("sub.host.com"))
hashes = append(hashes, hash)
var hashesArray []hostnameHash
hash4 := sha256.Sum256([]byte("3.sub.host.com"))
hashesArray = append(hashesArray, hash4)
hash2 := sha256.Sum256([]byte("host.com"))
hashesArray = append(hashesArray, hash2)
c.storeInCache(hashes, hashesArray)
// match "3.sub.host.com" or "host.com" from cache
hashes = []hostnameHash{}
hash = sha256.Sum256([]byte("3.sub.host.com"))
hashes = append(hashes, hash)
hash = sha256.Sum256([]byte("sub.host.com"))
hashes = append(hashes, hash)
hash = sha256.Sum256([]byte("host.com"))
hashes = append(hashes, hash)
found, blocked, _ := c.findInCache(hashes)
assert.True(t, found)
assert.True(t, blocked)
// match "sub.host.com" from cache
hashes = []hostnameHash{}
hash = sha256.Sum256([]byte("sub.host.com"))
hashes = append(hashes, hash)
found, blocked, _ = c.findInCache(hashes)
assert.True(t, found)
assert.False(t, blocked)
// Match "sub.host.com" from cache. Another hash for "host.example" is not
// in the cache, so get data for it from the server.
hashes = []hostnameHash{}
hash = sha256.Sum256([]byte("sub.host.com"))
hashes = append(hashes, hash)
hash = sha256.Sum256([]byte("host.example"))
hashes = append(hashes, hash)
found, _, hashesToRequest := c.findInCache(hashes)
assert.False(t, found)
hash = sha256.Sum256([]byte("sub.host.com"))
ok := slices.Contains(hashesToRequest, hash)
assert.False(t, ok)
hash = sha256.Sum256([]byte("host.example"))
ok = slices.Contains(hashesToRequest, hash)
assert.True(t, ok)
c = &Checker{
svc: "SafeBrowsing",
cacheTime: cacheTime,
}
c.cache = cache.New(cache.Config{})
hashes = []hostnameHash{}
hash = sha256.Sum256([]byte("sub.host.com"))
hashes = append(hashes, hash)
c.cache.Set(hash[:prefixLen], make([]byte, expirySize+hashSize))
found, _, _ = c.findInCache(hashes)
assert.False(t, found)
}
func TestChecker_Check(t *testing.T) {
const hostname = "example.org"
testCases := []struct {
name string
wantBlock bool
}{{
name: "sb_no_block",
wantBlock: false,
}, {
name: "sb_block",
wantBlock: true,
}, {
name: "pc_no_block",
wantBlock: false,
}, {
name: "pc_block",
wantBlock: true,
}}
for _, tc := range testCases {
c := New(&Config{
CacheTime: cacheTime,
CacheSize: cacheSize,
})
// Prepare the upstream.
ups := aghtest.NewBlockUpstream(hostname, tc.wantBlock)
var numReq int
onExchange := ups.OnExchange
ups.OnExchange = func(req *dns.Msg) (resp *dns.Msg, err error) {
numReq++
return onExchange(req)
}
c.upstream = ups
t.Run(tc.name, func(t *testing.T) {
// Firstly, check the request blocking.
hits := 0
res := false
res, err := c.Check(hostname)
require.NoError(t, err)
if tc.wantBlock {
assert.True(t, res)
hits++
} else {
require.False(t, res)
}
// Check the cache state, check the response is now cached.
assert.Equal(t, 1, c.cache.Stats().Count)
assert.Equal(t, hits, c.cache.Stats().Hit)
// There was one request to an upstream.
assert.Equal(t, 1, numReq)
// Now make the same request to check the cache was used.
res, err = c.Check(hostname)
require.NoError(t, err)
if tc.wantBlock {
assert.True(t, res)
} else {
require.False(t, res)
}
// Check the cache state, it should've been used.
assert.Equal(t, 1, c.cache.Stats().Count)
assert.Equal(t, hits+1, c.cache.Stats().Hit)
// Check that there were no additional requests.
assert.Equal(t, 1, numReq)
})
}
}

View File

@@ -8,7 +8,6 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"sync"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
@@ -416,12 +415,12 @@ type checkHostResp struct {
func (d *DNSFilter) handleCheckHost(w http.ResponseWriter, r *http.Request) { func (d *DNSFilter) handleCheckHost(w http.ResponseWriter, r *http.Request) {
host := r.URL.Query().Get("name") host := r.URL.Query().Get("name")
setts := d.Settings() setts := d.GetConfig()
setts.FilteringEnabled = true setts.FilteringEnabled = true
setts.ProtectionEnabled = true setts.ProtectionEnabled = true
d.ApplyBlockedServices(setts) d.ApplyBlockedServices(&setts, nil)
result, err := d.CheckHost(host, dns.TypeA, setts) result, err := d.CheckHost(host, dns.TypeA, &setts)
if err != nil { if err != nil {
aghhttp.Error( aghhttp.Error(
r, r,
@@ -459,80 +458,6 @@ func (d *DNSFilter) handleCheckHost(w http.ResponseWriter, r *http.Request) {
_ = aghhttp.WriteJSONResponse(w, r, resp) _ = aghhttp.WriteJSONResponse(w, r, resp)
} }
// setProtectedBool sets the value of a boolean pointer under a lock. l must
// protect the value under ptr.
//
// TODO(e.burkov): Make it generic?
func setProtectedBool(mu *sync.RWMutex, ptr *bool, val bool) {
mu.Lock()
defer mu.Unlock()
*ptr = val
}
// protectedBool gets the value of a boolean pointer under a read lock. l must
// protect the value under ptr.
//
// TODO(e.burkov): Make it generic?
func protectedBool(mu *sync.RWMutex, ptr *bool) (val bool) {
mu.RLock()
defer mu.RUnlock()
return *ptr
}
// handleSafeBrowsingEnable is the handler for the POST
// /control/safebrowsing/enable HTTP API.
func (d *DNSFilter) handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) {
setProtectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled, true)
d.Config.ConfigModified()
}
// handleSafeBrowsingDisable is the handler for the POST
// /control/safebrowsing/disable HTTP API.
func (d *DNSFilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) {
setProtectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled, false)
d.Config.ConfigModified()
}
// handleSafeBrowsingStatus is the handler for the GET
// /control/safebrowsing/status HTTP API.
func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) {
resp := &struct {
Enabled bool `json:"enabled"`
}{
Enabled: protectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled),
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
// handleParentalEnable is the handler for the POST /control/parental/enable
// HTTP API.
func (d *DNSFilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) {
setProtectedBool(&d.confLock, &d.Config.ParentalEnabled, true)
d.Config.ConfigModified()
}
// handleParentalDisable is the handler for the POST /control/parental/disable
// HTTP API.
func (d *DNSFilter) handleParentalDisable(w http.ResponseWriter, r *http.Request) {
setProtectedBool(&d.confLock, &d.Config.ParentalEnabled, false)
d.Config.ConfigModified()
}
// handleParentalStatus is the handler for the GET /control/parental/status
// HTTP API.
func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) {
resp := &struct {
Enabled bool `json:"enabled"`
}{
Enabled: protectedBool(&d.confLock, &d.Config.ParentalEnabled),
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
// RegisterFilteringHandlers - register handlers // RegisterFilteringHandlers - register handlers
func (d *DNSFilter) RegisterFilteringHandlers() { func (d *DNSFilter) RegisterFilteringHandlers() {
registerHTTP := d.HTTPRegister registerHTTP := d.HTTPRegister
@@ -555,7 +480,6 @@ func (d *DNSFilter) RegisterFilteringHandlers() {
registerHTTP(http.MethodGet, "/control/rewrite/list", d.handleRewriteList) registerHTTP(http.MethodGet, "/control/rewrite/list", d.handleRewriteList)
registerHTTP(http.MethodPost, "/control/rewrite/add", d.handleRewriteAdd) registerHTTP(http.MethodPost, "/control/rewrite/add", d.handleRewriteAdd)
registerHTTP(http.MethodPut, "/control/rewrite/update", d.handleRewriteUpdate)
registerHTTP(http.MethodPost, "/control/rewrite/delete", d.handleRewriteDelete) registerHTTP(http.MethodPost, "/control/rewrite/delete", d.handleRewriteDelete)
registerHTTP(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesIDs) registerHTTP(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesIDs)

View File

@@ -8,7 +8,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -137,171 +136,3 @@ func TestDNSFilter_handleFilteringSetURL(t *testing.T) {
}) })
} }
} }
func TestDNSFilter_handleSafeBrowsingStatus(t *testing.T) {
const (
testTimeout = time.Second
statusURL = "/control/safebrowsing/status"
)
confModCh := make(chan struct{})
filtersDir := t.TempDir()
testCases := []struct {
name string
url string
enabled bool
wantStatus assert.BoolAssertionFunc
}{{
name: "enable_off",
url: "/control/safebrowsing/enable",
enabled: false,
wantStatus: assert.True,
}, {
name: "enable_on",
url: "/control/safebrowsing/enable",
enabled: true,
wantStatus: assert.True,
}, {
name: "disable_on",
url: "/control/safebrowsing/disable",
enabled: true,
wantStatus: assert.False,
}, {
name: "disable_off",
url: "/control/safebrowsing/disable",
enabled: false,
wantStatus: assert.False,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
handlers := make(map[string]http.Handler)
d, err := New(&Config{
ConfigModified: func() {
testutil.RequireSend(testutil.PanicT{}, confModCh, struct{}{}, testTimeout)
},
DataDir: filtersDir,
HTTPRegister: func(_, url string, handler http.HandlerFunc) {
handlers[url] = handler
},
SafeBrowsingEnabled: tc.enabled,
}, nil)
require.NoError(t, err)
t.Cleanup(d.Close)
d.RegisterFilteringHandlers()
require.NotEmpty(t, handlers)
require.Contains(t, handlers, statusURL)
r := httptest.NewRequest(http.MethodPost, tc.url, nil)
w := httptest.NewRecorder()
go handlers[tc.url].ServeHTTP(w, r)
testutil.RequireReceive(t, confModCh, testTimeout)
r = httptest.NewRequest(http.MethodGet, statusURL, nil)
w = httptest.NewRecorder()
handlers[statusURL].ServeHTTP(w, r)
require.Equal(t, http.StatusOK, w.Code)
status := struct {
Enabled bool `json:"enabled"`
}{
Enabled: false,
}
err = json.NewDecoder(w.Body).Decode(&status)
require.NoError(t, err)
tc.wantStatus(t, status.Enabled)
})
}
}
func TestDNSFilter_handleParentalStatus(t *testing.T) {
const (
testTimeout = time.Second
statusURL = "/control/parental/status"
)
confModCh := make(chan struct{})
filtersDir := t.TempDir()
testCases := []struct {
name string
url string
enabled bool
wantStatus assert.BoolAssertionFunc
}{{
name: "enable_off",
url: "/control/parental/enable",
enabled: false,
wantStatus: assert.True,
}, {
name: "enable_on",
url: "/control/parental/enable",
enabled: true,
wantStatus: assert.True,
}, {
name: "disable_on",
url: "/control/parental/disable",
enabled: true,
wantStatus: assert.False,
}, {
name: "disable_off",
url: "/control/parental/disable",
enabled: false,
wantStatus: assert.False,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
handlers := make(map[string]http.Handler)
d, err := New(&Config{
ConfigModified: func() {
testutil.RequireSend(testutil.PanicT{}, confModCh, struct{}{}, testTimeout)
},
DataDir: filtersDir,
HTTPRegister: func(_, url string, handler http.HandlerFunc) {
handlers[url] = handler
},
ParentalEnabled: tc.enabled,
}, nil)
require.NoError(t, err)
t.Cleanup(d.Close)
d.RegisterFilteringHandlers()
require.NotEmpty(t, handlers)
require.Contains(t, handlers, statusURL)
r := httptest.NewRequest(http.MethodPost, tc.url, nil)
w := httptest.NewRecorder()
go handlers[tc.url].ServeHTTP(w, r)
testutil.RequireReceive(t, confModCh, testTimeout)
r = httptest.NewRequest(http.MethodGet, statusURL, nil)
w = httptest.NewRecorder()
handlers[statusURL].ServeHTTP(w, r)
require.Equal(t, http.StatusOK, w.Code)
status := struct {
Enabled bool `json:"enabled"`
}{
Enabled: false,
}
err = json.NewDecoder(w.Body).Decode(&status)
require.NoError(t, err)
tc.wantStatus(t, status.Enabled)
})
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,433 @@
package filtering
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"fmt"
"net"
"net/http"
"strings"
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/cache"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
"golang.org/x/net/publicsuffix"
)
// Safe browsing and parental control methods.
// TODO(a.garipov): Make configurable.
const (
dnsTimeout = 3 * time.Second
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.`
)
// SetParentalUpstream sets the parental upstream for *DNSFilter.
//
// TODO(e.burkov): Remove this in v1 API to forbid the direct access.
func (d *DNSFilter) SetParentalUpstream(u upstream.Upstream) {
d.parentalUpstream = u
}
// SetSafeBrowsingUpstream sets the safe browsing upstream for *DNSFilter.
//
// TODO(e.burkov): Remove this in v1 API to forbid the direct access.
func (d *DNSFilter) SetSafeBrowsingUpstream(u upstream.Upstream) {
d.safeBrowsingUpstream = u
}
func (d *DNSFilter) initSecurityServices() error {
var err error
d.safeBrowsingServer = defaultSafebrowsingServer
d.parentalServer = defaultParentalServer
opts := &upstream.Options{
Timeout: dnsTimeout,
ServerIPAddrs: []net.IP{
{94, 140, 14, 15},
{94, 140, 15, 16},
net.ParseIP("2a10:50c0::bad1:ff"),
net.ParseIP("2a10:50c0::bad2:ff"),
},
}
parUps, err := upstream.AddressToUpstream(d.parentalServer, opts)
if err != nil {
return fmt.Errorf("converting parental server: %w", err)
}
d.SetParentalUpstream(parUps)
sbUps, err := upstream.AddressToUpstream(d.safeBrowsingServer, opts)
if err != nil {
return fmt.Errorf("converting safe browsing server: %w", err)
}
d.SetSafeBrowsingUpstream(sbUps)
return nil
}
/*
expire byte[4]
hash byte[32]
...
*/
func (c *sbCtx) setCache(prefix, hashes []byte) {
d := make([]byte, 4+len(hashes))
expire := uint(time.Now().Unix()) + c.cacheTime*60
binary.BigEndian.PutUint32(d[:4], uint32(expire))
copy(d[4:], hashes)
c.cache.Set(prefix, d)
log.Debug("%s: stored in cache: %v", c.svc, prefix)
}
// findInHash returns 32-byte hash if it's found in hashToHost.
func (c *sbCtx) findInHash(val []byte) (hash32 [32]byte, found bool) {
for i := 4; i < len(val); i += 32 {
hash := val[i : i+32]
copy(hash32[:], hash[0:32])
_, found = c.hashToHost[hash32]
if found {
return hash32, found
}
}
return [32]byte{}, false
}
func (c *sbCtx) getCached() int {
now := time.Now().Unix()
hashesToRequest := map[[32]byte]string{}
for k, v := range c.hashToHost {
// nolint:looppointer // The subsilce is used for a safe cache lookup.
val := c.cache.Get(k[0:2])
if val == nil || now >= int64(binary.BigEndian.Uint32(val)) {
hashesToRequest[k] = v
continue
}
if hash32, found := c.findInHash(val); found {
log.Debug("%s: found in cache: %s: blocked by %v", c.svc, c.host, hash32)
return 1
}
}
if len(hashesToRequest) == 0 {
log.Debug("%s: found in cache: %s: not blocked", c.svc, c.host)
return -1
}
c.hashToHost = hashesToRequest
return 0
}
type sbCtx struct {
host string
svc string
hashToHost map[[32]byte]string
cache cache.Cache
cacheTime uint
}
func hostnameToHashes(host string) map[[32]byte]string {
hashes := map[[32]byte]string{}
tld, icann := publicsuffix.PublicSuffix(host)
if !icann {
// private suffixes like cloudfront.net
tld = ""
}
curhost := host
nDots := 0
for i := len(curhost) - 1; i >= 0; i-- {
if curhost[i] == '.' {
nDots++
if nDots == 4 {
curhost = curhost[i+1:] // "xxx.a.b.c.d" -> "a.b.c.d"
break
}
}
}
for {
if curhost == "" {
// we've reached end of string
break
}
if tld != "" && curhost == tld {
// we've reached the TLD, don't hash it
break
}
sum := sha256.Sum256([]byte(curhost))
hashes[sum] = curhost
pos := strings.IndexByte(curhost, byte('.'))
if pos < 0 {
break
}
curhost = curhost[pos+1:]
}
return hashes
}
// convert hash array to string
func (c *sbCtx) getQuestion() string {
b := &strings.Builder{}
for hash := range c.hashToHost {
// nolint:looppointer // The subsilce is used for safe hex encoding.
stringutil.WriteToBuilder(b, hex.EncodeToString(hash[0:2]), ".")
}
if c.svc == "SafeBrowsing" {
stringutil.WriteToBuilder(b, sbTXTSuffix)
return b.String()
}
stringutil.WriteToBuilder(b, pcTXTSuffix)
return b.String()
}
// Find the target hash in TXT response
func (c *sbCtx) processTXT(resp *dns.Msg) (bool, [][]byte) {
matched := false
hashes := [][]byte{}
for _, a := range resp.Answer {
txt, ok := a.(*dns.TXT)
if !ok {
continue
}
log.Debug("%s: received hashes for %s: %v", c.svc, c.host, txt.Txt)
for _, t := range txt.Txt {
if len(t) != 32*2 {
continue
}
hash, err := hex.DecodeString(t)
if err != nil {
continue
}
hashes = append(hashes, hash)
if !matched {
var hash32 [32]byte
copy(hash32[:], hash)
var hashHost string
hashHost, ok = c.hashToHost[hash32]
if ok {
log.Debug("%s: matched %s by %s/%s", c.svc, c.host, hashHost, t)
matched = true
}
}
}
}
return matched, hashes
}
func (c *sbCtx) storeCache(hashes [][]byte) {
slices.SortFunc(hashes, func(a, b []byte) (sortsBefore bool) {
return bytes.Compare(a, b) == -1
})
var curData []byte
var prevPrefix []byte
for i, hash := range hashes {
// nolint:looppointer // The subsilce is used for a safe comparison.
if !bytes.Equal(hash[0:2], prevPrefix) {
if i != 0 {
c.setCache(prevPrefix, curData)
curData = nil
}
prevPrefix = hashes[i][0:2]
}
curData = append(curData, hash...)
}
if len(prevPrefix) != 0 {
c.setCache(prevPrefix, curData)
}
for hash := range c.hashToHost {
// nolint:looppointer // The subsilce is used for a safe cache lookup.
prefix := hash[0:2]
val := c.cache.Get(prefix)
if val == nil {
c.setCache(prefix, nil)
}
}
}
func check(c *sbCtx, r Result, u upstream.Upstream) (Result, error) {
c.hashToHost = hostnameToHashes(c.host)
switch c.getCached() {
case -1:
return Result{}, nil
case 1:
return r, nil
}
question := c.getQuestion()
log.Tracef("%s: checking %s: %s", c.svc, c.host, question)
req := (&dns.Msg{}).SetQuestion(question, dns.TypeTXT)
resp, err := u.Exchange(req)
if err != nil {
return Result{}, err
}
matched, receivedHashes := c.processTXT(resp)
c.storeCache(receivedHashes)
if matched {
return r, nil
}
return Result{}, nil
}
// TODO(a.garipov): Unify with checkParental.
func (d *DNSFilter) checkSafeBrowsing(
host string,
_ uint16,
setts *Settings,
) (res Result, err error) {
if !setts.ProtectionEnabled || !setts.SafeBrowsingEnabled {
return Result{}, nil
}
if log.GetLevel() >= log.DEBUG {
timer := log.StartTimer()
defer timer.LogElapsed("safebrowsing lookup for %q", host)
}
sctx := &sbCtx{
host: host,
svc: "SafeBrowsing",
cache: d.safebrowsingCache,
cacheTime: d.Config.CacheTime,
}
res = Result{
Rules: []*ResultRule{{
Text: "adguard-malware-shavar",
FilterListID: SafeBrowsingListID,
}},
Reason: FilteredSafeBrowsing,
IsFiltered: true,
}
return check(sctx, res, d.safeBrowsingUpstream)
}
// TODO(a.garipov): Unify with checkSafeBrowsing.
func (d *DNSFilter) checkParental(
host string,
_ uint16,
setts *Settings,
) (res Result, err error) {
if !setts.ProtectionEnabled || !setts.ParentalEnabled {
return Result{}, nil
}
if log.GetLevel() >= log.DEBUG {
timer := log.StartTimer()
defer timer.LogElapsed("parental lookup for %q", host)
}
sctx := &sbCtx{
host: host,
svc: "Parental",
cache: d.parentalCache,
cacheTime: d.Config.CacheTime,
}
res = Result{
Rules: []*ResultRule{{
Text: "parental CATEGORY_BLACKLISTED",
FilterListID: ParentalListID,
}},
Reason: FilteredParental,
IsFiltered: true,
}
return check(sctx, res, d.parentalUpstream)
}
// setProtectedBool sets the value of a boolean pointer under a lock. l must
// protect the value under ptr.
//
// TODO(e.burkov): Make it generic?
func setProtectedBool(mu *sync.RWMutex, ptr *bool, val bool) {
mu.Lock()
defer mu.Unlock()
*ptr = val
}
// protectedBool gets the value of a boolean pointer under a read lock. l must
// protect the value under ptr.
//
// TODO(e.burkov): Make it generic?
func protectedBool(mu *sync.RWMutex, ptr *bool) (val bool) {
mu.RLock()
defer mu.RUnlock()
return *ptr
}
func (d *DNSFilter) handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) {
setProtectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled, true)
d.Config.ConfigModified()
}
func (d *DNSFilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) {
setProtectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled, false)
d.Config.ConfigModified()
}
func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) {
resp := &struct {
Enabled bool `json:"enabled"`
}{
Enabled: protectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled),
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
func (d *DNSFilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) {
setProtectedBool(&d.confLock, &d.Config.ParentalEnabled, true)
d.Config.ConfigModified()
}
func (d *DNSFilter) handleParentalDisable(w http.ResponseWriter, r *http.Request) {
setProtectedBool(&d.confLock, &d.Config.ParentalEnabled, false)
d.Config.ConfigModified()
}
func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) {
resp := &struct {
Enabled bool `json:"enabled"`
}{
Enabled: protectedBool(&d.confLock, &d.Config.ParentalEnabled),
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}

View File

@@ -0,0 +1,226 @@
package filtering
import (
"crypto/sha256"
"strings"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/cache"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSafeBrowsingHash(t *testing.T) {
// test hostnameToHashes()
hashes := hostnameToHashes("1.2.3.sub.host.com")
assert.Len(t, hashes, 3)
_, ok := hashes[sha256.Sum256([]byte("3.sub.host.com"))]
assert.True(t, ok)
_, ok = hashes[sha256.Sum256([]byte("sub.host.com"))]
assert.True(t, ok)
_, ok = hashes[sha256.Sum256([]byte("host.com"))]
assert.True(t, ok)
_, ok = hashes[sha256.Sum256([]byte("com"))]
assert.False(t, ok)
c := &sbCtx{
svc: "SafeBrowsing",
hashToHost: hashes,
}
q := c.getQuestion()
assert.Contains(t, q, "7a1b.")
assert.Contains(t, q, "af5a.")
assert.Contains(t, q, "eb11.")
assert.True(t, strings.HasSuffix(q, "sb.dns.adguard.com."))
}
func TestSafeBrowsingCache(t *testing.T) {
c := &sbCtx{
svc: "SafeBrowsing",
cacheTime: 100,
}
conf := cache.Config{}
c.cache = cache.New(conf)
// store in cache hashes for "3.sub.host.com" and "host.com"
// and empty data for hash-prefix for "sub.host.com"
hash := sha256.Sum256([]byte("sub.host.com"))
c.hashToHost = make(map[[32]byte]string)
c.hashToHost[hash] = "sub.host.com"
var hashesArray [][]byte
hash4 := sha256.Sum256([]byte("3.sub.host.com"))
hashesArray = append(hashesArray, hash4[:])
hash2 := sha256.Sum256([]byte("host.com"))
hashesArray = append(hashesArray, hash2[:])
c.storeCache(hashesArray)
// match "3.sub.host.com" or "host.com" from cache
c.hashToHost = make(map[[32]byte]string)
hash = sha256.Sum256([]byte("3.sub.host.com"))
c.hashToHost[hash] = "3.sub.host.com"
hash = sha256.Sum256([]byte("sub.host.com"))
c.hashToHost[hash] = "sub.host.com"
hash = sha256.Sum256([]byte("host.com"))
c.hashToHost[hash] = "host.com"
assert.Equal(t, 1, c.getCached())
// match "sub.host.com" from cache
c.hashToHost = make(map[[32]byte]string)
hash = sha256.Sum256([]byte("sub.host.com"))
c.hashToHost[hash] = "sub.host.com"
assert.Equal(t, -1, c.getCached())
// Match "sub.host.com" from cache. Another hash for "host.example" is not
// in the cache, so get data for it from the server.
c.hashToHost = make(map[[32]byte]string)
hash = sha256.Sum256([]byte("sub.host.com"))
c.hashToHost[hash] = "sub.host.com"
hash = sha256.Sum256([]byte("host.example"))
c.hashToHost[hash] = "host.example"
assert.Empty(t, c.getCached())
hash = sha256.Sum256([]byte("sub.host.com"))
_, ok := c.hashToHost[hash]
assert.False(t, ok)
hash = sha256.Sum256([]byte("host.example"))
_, ok = c.hashToHost[hash]
assert.True(t, ok)
c = &sbCtx{
svc: "SafeBrowsing",
cacheTime: 100,
}
conf = cache.Config{}
c.cache = cache.New(conf)
hash = sha256.Sum256([]byte("sub.host.com"))
c.hashToHost = make(map[[32]byte]string)
c.hashToHost[hash] = "sub.host.com"
c.cache.Set(hash[0:2], make([]byte, 32))
assert.Empty(t, c.getCached())
}
func TestSBPC_checkErrorUpstream(t *testing.T) {
d, _ := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
t.Cleanup(d.Close)
ups := aghtest.NewErrorUpstream()
d.SetSafeBrowsingUpstream(ups)
d.SetParentalUpstream(ups)
setts := &Settings{
ProtectionEnabled: true,
SafeBrowsingEnabled: true,
ParentalEnabled: true,
}
_, err := d.checkSafeBrowsing("smthng.com", dns.TypeA, setts)
assert.Error(t, err)
_, err = d.checkParental("smthng.com", dns.TypeA, setts)
assert.Error(t, err)
}
func TestSBPC(t *testing.T) {
d, _ := newForTest(t, &Config{SafeBrowsingEnabled: true}, nil)
t.Cleanup(d.Close)
const hostname = "example.org"
setts := &Settings{
ProtectionEnabled: true,
SafeBrowsingEnabled: true,
ParentalEnabled: true,
}
testCases := []struct {
testCache cache.Cache
testFunc func(host string, _ uint16, _ *Settings) (res Result, err error)
name string
block bool
}{{
testCache: d.safebrowsingCache,
testFunc: d.checkSafeBrowsing,
name: "sb_no_block",
block: false,
}, {
testCache: d.safebrowsingCache,
testFunc: d.checkSafeBrowsing,
name: "sb_block",
block: true,
}, {
testCache: d.parentalCache,
testFunc: d.checkParental,
name: "pc_no_block",
block: false,
}, {
testCache: d.parentalCache,
testFunc: d.checkParental,
name: "pc_block",
block: true,
}}
for _, tc := range testCases {
// Prepare the upstream.
ups := aghtest.NewBlockUpstream(hostname, tc.block)
var numReq int
onExchange := ups.OnExchange
ups.OnExchange = func(req *dns.Msg) (resp *dns.Msg, err error) {
numReq++
return onExchange(req)
}
d.SetSafeBrowsingUpstream(ups)
d.SetParentalUpstream(ups)
t.Run(tc.name, func(t *testing.T) {
// Firstly, check the request blocking.
hits := 0
res, err := tc.testFunc(hostname, dns.TypeA, setts)
require.NoError(t, err)
if tc.block {
assert.True(t, res.IsFiltered)
require.Len(t, res.Rules, 1)
hits++
} else {
require.False(t, res.IsFiltered)
}
// Check the cache state, check the response is now cached.
assert.Equal(t, 1, tc.testCache.Stats().Count)
assert.Equal(t, hits, tc.testCache.Stats().Hit)
// There was one request to an upstream.
assert.Equal(t, 1, numReq)
// Now make the same request to check the cache was used.
res, err = tc.testFunc(hostname, dns.TypeA, setts)
require.NoError(t, err)
if tc.block {
assert.True(t, res.IsFiltered)
require.Len(t, res.Rules, 1)
} else {
require.False(t, res.IsFiltered)
}
// Check the cache state, it should've been used.
assert.Equal(t, 1, tc.testCache.Stats().Count)
assert.Equal(t, hits+1, tc.testCache.Stats().Hit)
// Check that there were no additional requests.
assert.Equal(t, 1, numReq)
})
purgeCaches(d)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,13 +3,15 @@ package home
import ( import (
"net" "net"
"net/netip" "net/netip"
"os"
"runtime" "runtime"
"testing" "testing"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/whois" "github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -98,8 +100,22 @@ func TestClients(t *testing.T) {
assert.False(t, ok) assert.False(t, ok)
}) })
t.Run("update_fail_name", func(t *testing.T) {
err := clients.Update("client3", &Client{
IDs: []string{"1.2.3.0"},
Name: "client3",
})
require.Error(t, err)
err = clients.Update("client3", &Client{
IDs: []string{"1.2.3.0"},
Name: "client2",
})
assert.Error(t, err)
})
t.Run("update_fail_ip", func(t *testing.T) { t.Run("update_fail_ip", func(t *testing.T) {
err := clients.Update(&Client{Name: "client1"}, &Client{ err := clients.Update("client1", &Client{
IDs: []string{"2.2.2.2"}, IDs: []string{"2.2.2.2"},
Name: "client1", Name: "client1",
}) })
@@ -115,10 +131,7 @@ func TestClients(t *testing.T) {
cliNewIP = netip.MustParseAddr(cliNew) cliNewIP = netip.MustParseAddr(cliNew)
) )
prev, ok := clients.list["client1"] err := clients.Update("client1", &Client{
require.True(t, ok)
err := clients.Update(prev, &Client{
IDs: []string{cliNew}, IDs: []string{cliNew},
Name: "client1", Name: "client1",
}) })
@@ -127,10 +140,7 @@ func TestClients(t *testing.T) {
assert.Equal(t, clients.clientSource(cliOldIP), ClientSourceNone) assert.Equal(t, clients.clientSource(cliOldIP), ClientSourceNone)
assert.Equal(t, clients.clientSource(cliNewIP), ClientSourcePersistent) assert.Equal(t, clients.clientSource(cliNewIP), ClientSourcePersistent)
prev, ok = clients.list["client1"] err = clients.Update("client1", &Client{
require.True(t, ok)
err = clients.Update(prev, &Client{
IDs: []string{cliNew}, IDs: []string{cliNew},
Name: "client1-renamed", Name: "client1-renamed",
UseOwnSettings: true, UseOwnSettings: true,
@@ -199,7 +209,7 @@ func TestClients(t *testing.T) {
func TestClientsWHOIS(t *testing.T) { func TestClientsWHOIS(t *testing.T) {
clients := newClientsContainer() clients := newClientsContainer()
whois := &whois.Info{ whois := &RuntimeClientWHOISInfo{
Country: "AU", Country: "AU",
Orgname: "Example Org", Orgname: "Example Org",
} }
@@ -210,7 +220,7 @@ func TestClientsWHOIS(t *testing.T) {
rc := clients.ipToRC[ip] rc := clients.ipToRC[ip]
require.NotNil(t, rc) require.NotNil(t, rc)
assert.Equal(t, rc.WHOIS, whois) assert.Equal(t, rc.WHOISInfo, whois)
}) })
t.Run("existing_auto-client", func(t *testing.T) { t.Run("existing_auto-client", func(t *testing.T) {
@@ -222,7 +232,7 @@ func TestClientsWHOIS(t *testing.T) {
rc := clients.ipToRC[ip] rc := clients.ipToRC[ip]
require.NotNil(t, rc) require.NotNil(t, rc)
assert.Equal(t, rc.WHOIS, whois) assert.Equal(t, rc.WHOISInfo, whois)
}) })
t.Run("can't_set_manually-added", func(t *testing.T) { t.Run("can't_set_manually-added", func(t *testing.T) {
@@ -274,7 +284,7 @@ func TestClientsAddExisting(t *testing.T) {
// First, init a DHCP server with a single static lease. // First, init a DHCP server with a single static lease.
config := &dhcpd.ServerConfig{ config := &dhcpd.ServerConfig{
Enabled: true, Enabled: true,
DataDir: t.TempDir(), DBFilePath: "leases.db",
Conf4: dhcpd.V4ServerConf{ Conf4: dhcpd.V4ServerConf{
Enabled: true, Enabled: true,
GatewayIP: netip.MustParseAddr("1.2.3.1"), GatewayIP: netip.MustParseAddr("1.2.3.1"),
@@ -286,6 +296,9 @@ func TestClientsAddExisting(t *testing.T) {
dhcpServer, err := dhcpd.Create(config) dhcpServer, err := dhcpd.Create(config)
require.NoError(t, err) require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, func() (err error) {
return os.Remove("leases.db")
})
clients.dhcpServer = dhcpServer clients.dhcpServer = dhcpServer

View File

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

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