all: sync with master; upd chlog

This commit is contained in:
Ainar Garipov 2023-07-03 14:10:40 +03:00
parent cadb765b7d
commit b22b16d98c
140 changed files with 6739 additions and 2521 deletions

View file

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

View file

@ -23,19 +23,32 @@
'id': 'prerequisites' 'id': 'prerequisites'
'type': 'checkboxes' 'type': 'checkboxes'
- 'attributes': - 'attributes':
'description': 'Please describe the request' 'description': 'Please describe the problem you are trying to solve'
'label': 'Description' 'label': 'The problem'
'value': | 'placeholder': >
#### What problem are you trying to solve? Please describe the problem you are 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'

20
.github/PULL_REQUEST_TEMPLATE vendored Normal file
View file

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

View file

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

5
.gitignore vendored
View file

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

View file

@ -14,34 +14,180 @@ and this project adheres to
<!-- <!--
## [v0.108.0] - TBA ## [v0.108.0] - TBA
## [v0.107.32] - 2023-06-28 (APPROX.) ## [v0.107.34] - 2023-07-26 (APPROX.)
See also the [v0.107.32 GitHub milestone][ms-v0.107.32]. See also the [v0.107.34 GitHub milestone][ms-v0.107.34].
[ms-v0.107.32]: https://github.com/AdguardTeam/AdGuardHome/milestone/68?closed=1 [ms-v0.107.34]: https://github.com/AdguardTeam/AdGuardHome/milestone/69?closed=1
NOTE: Add new changes BELOW THIS COMMENT. NOTE: Add new changes BELOW THIS COMMENT.
--> -->
### Added
- The ability to edit rewrite rules via `PUT /control/rewrite/update` HTTP API
([#1577]).
[#1577]: https://github.com/AdguardTeam/AdGuardHome/issues/1577
<!-- <!--
NOTE: Add new changes ABOVE THIS COMMENT. NOTE: Add new changes ABOVE THIS COMMENT.
--> -->
## [v0.107.33] - 2023-07-03
See also the [v0.107.33 GitHub milestone][ms-v0.107.33].
### Added
- The new command-line flag `--web-addr` is the address to serve the web UI on,
in the host:port format.
- The ability to set inactivity periods for filtering blocked services, both
globally and per client, in the configuration file ([#951]). The UI changes
are coming in the upcoming releases.
- The ability to edit rewrite rules via `PUT /control/rewrite/update` HTTP API
and the Web UI ([#1577]).
### Changed
#### Configuration Changes
In this release, the schema version has changed from 20 to 23.
- Properties `bind_host`, `bind_port`, and `web_session_ttl` which used to setup
web UI binding configuration, are now moved to a new object `http` containing
new properties `address` and `session_ttl`:
```yaml
# BEFORE:
'bind_host': '1.2.3.4'
'bind_port': 8080
'web_session_ttl': 720
# AFTER:
'http':
'address': '1.2.3.4:8080'
'session_ttl': '720h'
```
Note that the new `http.session_ttl` property is now a duration string. To
rollback this change, remove the new object `http`, set back `bind_host`,
`bind_port`, `web_session_ttl`, and change the `schema_version` back to `22`.
- Property `clients.persistent.blocked_services`, which in schema versions 21
and earlier used to be a list containing ids of blocked services, is now an
object containing ids and schedule for blocked services:
```yaml
# BEFORE:
'clients':
'persistent':
- 'name': 'client-name'
'blocked_services':
- id_1
- id_2
# AFTER:
'clients':
'persistent':
- 'name': client-name
'blocked_services':
'ids':
- id_1
- id_2
'schedule':
'time_zone': 'Local'
'sun':
'start': '0s'
'end': '24h'
'mon':
'start': '1h'
'end': '23h'
```
To rollback this change, replace `clients.persistent.blocked_services` object
with the list of ids of blocked services and change the `schema_version` back
to `21`.
- Property `dns.blocked_services`, which in schema versions 20 and earlier used
to be a list containing ids of blocked services, is now an object containing
ids and schedule for blocked services:
```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`.
### Deprecated
- `HEALTHCHECK` and `ENTRYPOINT` sections in `Dockerfile` ([#5939]). They cause
a lot of issues, especially with tools like `docker-compose` and `podman`, and
will be removed in a future release.
- Flags `-h`, `--host`, `-p`, `--port` have been deprecated. The `-h` flag
will work as an alias for `--help`, instead of the deprecated `--host` in the
future releases.
### Fixed
- Ignoring of `/etc/hosts` file when resolving the hostnames of upstream DNS
servers ([#5902]).
- Excessive error logging when using DNS-over-QUIC ([#5285]).
- Inability to set `bind_host` in `AdGuardHome.yaml` in Docker ([#4231],
[#4235]).
- The blocklists can now be deleted properly ([#5700]).
- Queries with the question-section target `.`, for example `NS .`, are now
counted in the statistics and correctly shown in the query log ([#5910]).
- Safe Search not working with `AAAA` queries for domains that don't have `AAAA`
records ([#5913]).
[#951]: https://github.com/AdguardTeam/AdGuardHome/issues/951
[#1577]: https://github.com/AdguardTeam/AdGuardHome/issues/1577
[#4231]: https://github.com/AdguardTeam/AdGuardHome/issues/4231
[#4235]: https://github.com/AdguardTeam/AdGuardHome/pull/4235
[#5285]: https://github.com/AdguardTeam/AdGuardHome/issues/5285
[#5700]: https://github.com/AdguardTeam/AdGuardHome/issues/5700
[#5902]: https://github.com/AdguardTeam/AdGuardHome/issues/5902
[#5910]: https://github.com/AdguardTeam/AdGuardHome/issues/5910
[#5913]: https://github.com/AdguardTeam/AdGuardHome/issues/5913
[#5939]: https://github.com/AdguardTeam/AdGuardHome/discussions/5939
[ms-v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/milestone/68?closed=1
## [v0.107.32] - 2023-06-13 ## [v0.107.32] - 2023-06-13
### Fixed ### Fixed
- DNSCrypt upstream not resetting the client and resolver information on - DNSCrypt upstream not resetting the client and resolver information on
dialing errors ([#5872]). dialing errors ([#5872]).
@ -2014,11 +2160,12 @@ 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.34...HEAD
[v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...v0.107.33 [v0.107.34]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...v0.107.34
--> -->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...HEAD [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.33...HEAD
[v0.107.33]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.32...v0.107.33
[v0.107.32]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.31...v0.107.32 [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.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.30]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.29...v0.107.30

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -1,13 +1,5 @@
# Don't consider the HTTPS hostname since the enforced HTTPS redirection should # Don't consider the HTTPS hostname since the enforced HTTPS redirection should
# work if the SSL check skipped. See file docker/healthcheck.sh. # work if the SSL check skipped. See file docker/healthcheck.sh.
/^bind_host:/ { host = $2 } /^[^[:space:]]/ { is_http = /^http:/ }
/^bind_port:/ { port = $2 } /^[[:space:]]+address:/ { if (is_http) print "http://" $2 }
END {
if (match(host, ":")) {
print "http://[" host "]:" port
} else {
print "http://" host ":" port
}
}

31
go.mod
View file

@ -3,8 +3,9 @@ module github.com/AdguardTeam/AdGuardHome
go 1.19 go 1.19
require ( require (
github.com/AdguardTeam/dnsproxy v0.50.2 // TODO(a.garipov): Update to a tagged version when it's released.
github.com/AdguardTeam/golibs v0.13.2 github.com/AdguardTeam/dnsproxy v0.50.3-0.20230628054307-31e374065768
github.com/AdguardTeam/golibs v0.13.3
github.com/AdguardTeam/urlfilter v0.16.1 github.com/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
@ -15,7 +16,7 @@ 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-20230612134759-b20c9ba983df
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
@ -24,15 +25,15 @@ require (
// 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.55
github.com/quic-go/quic-go v0.35.1 github.com/quic-go/quic-go v0.35.1
github.com/stretchr/testify v1.8.2 github.com/stretchr/testify v1.8.4
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.9.0 golang.org/x/crypto v0.10.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df
golang.org/x/net v0.10.0 golang.org/x/net v0.11.0
golang.org/x/sys v0.8.0 golang.org/x/sys v0.9.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
@ -49,17 +50,17 @@ require (
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-20230602150820-91b7bce49751 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.4.1 // indirect
github.com/onsi/ginkgo/v2 v2.10.0 // indirect github.com/onsi/ginkgo/v2 v2.11.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
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.11.0 // indirect
golang.org/x/sync v0.2.0 // indirect golang.org/x/sync v0.3.0 // indirect
golang.org/x/text v0.9.0 // indirect golang.org/x/text v0.10.0 // indirect
golang.org/x/tools v0.9.3 // indirect golang.org/x/tools v0.10.0 // indirect
) )

66
go.sum
View file

@ -1,9 +1,9 @@
github.com/AdguardTeam/dnsproxy v0.50.2 h1:p1471SsMZ6SMo7T51Olw4aNluahvMwSLMorwxYV18ts= github.com/AdguardTeam/dnsproxy v0.50.3-0.20230628054307-31e374065768 h1:5Ia6wA+tqAlTyzuaOVGSlHmb0osLWXeJUs3NxCuC4gA=
github.com/AdguardTeam/dnsproxy v0.50.2/go.mod h1:CQhZTkqC8X0ID6glrtyaxgqRRdiYfn1gJulC1cZ5Dn8= github.com/AdguardTeam/dnsproxy v0.50.3-0.20230628054307-31e374065768/go.mod h1:CQhZTkqC8X0ID6glrtyaxgqRRdiYfn1gJulC1cZ5Dn8=
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.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.2 h1:BPASsyQKmb+b8VnvsNOHp7bKfcZl9Z+Z2UhPjOiupSc= github.com/AdguardTeam/golibs v0.13.3 h1:RT3QbzThtaLiFLkIUDS6/hlGEXrh0zYvdf4bd7UWpGo=
github.com/AdguardTeam/golibs v0.13.2/go.mod h1:7ylQLv2Lqsc3UW3jHoITynYk6Y1tYtgEMkR09ppfsN8= github.com/AdguardTeam/golibs v0.13.3/go.mod h1:wkJ6EUsN4np/9Gp7+9QeooY9E2U2WCLJYAioLCzkHsI=
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= github.com/AdguardTeam/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=
@ -56,8 +56,8 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 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/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/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df h1:pF1MMIzEJzJ/MyI4bXYXVYyN8CJgoQ2PPKT2z3O/Cl4=
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/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=
@ -83,18 +83,18 @@ github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.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.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e 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.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible 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=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -111,17 +111,13 @@ github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5
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/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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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=
@ -136,15 +132,15 @@ 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.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/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=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-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-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -154,12 +150,12 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
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.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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=
@ -179,22 +175,22 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-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.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-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.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-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

@ -56,15 +56,20 @@ func (rm *requestMatcher) MatchRequest(
) (res *urlfilter.DNSResult, ok bool) { ) (res *urlfilter.DNSResult, ok bool) {
switch req.DNSType { switch req.DNSType {
case dns.TypeA, dns.TypeAAAA, dns.TypePTR: case dns.TypeA, dns.TypeAAAA, dns.TypePTR:
log.Debug("%s: handling the request for %s", hostsContainerPrefix, req.Hostname) log.Debug(
"%s: handling %s request for %s",
hostsContainerPrefix,
dns.Type(req.DNSType),
req.Hostname,
)
rm.stateLock.RLock()
defer rm.stateLock.RUnlock()
return rm.engine.MatchRequest(req)
default: default:
return nil, false return nil, false
} }
rm.stateLock.RLock()
defer rm.stateLock.RUnlock()
return rm.engine.MatchRequest(req)
} }
// Translate returns the source hosts-syntax rule for the generated dnsrewrite // Translate returns the source hosts-syntax rule for the generated dnsrewrite
@ -96,6 +101,8 @@ const hostsContainerPrefix = "hosts container"
// HostsContainer stores the relevant hosts database provided by the OS and // HostsContainer stores the relevant hosts database provided by the OS and
// processes both A/AAAA and PTR DNS requests for those. // processes both A/AAAA and PTR DNS requests for those.
//
// TODO(e.burkov): Improve API and move to golibs.
type HostsContainer struct { type HostsContainer struct {
// requestMatcher matches the requests and translates the rules. It's // requestMatcher matches the requests and translates the rules. It's
// embedded to implement MatchRequest and Translate for *HostsContainer. // embedded to implement MatchRequest and Translate for *HostsContainer.

View file

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

View file

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

View file

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

View file

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

View file

@ -51,6 +51,9 @@ func migrateDB(conf *ServerConfig) (err error) {
oldLeasesPath := filepath.Join(conf.WorkDir, dbFilename) oldLeasesPath := filepath.Join(conf.WorkDir, dbFilename)
dataDirPath := filepath.Join(conf.DataDir, dataFilename) dataDirPath := filepath.Join(conf.DataDir, dataFilename)
// #nosec G304 -- Trust this path, since it's taken from the old file name
// relative to the working directory and should generally be considered
// safe.
file, err := os.Open(oldLeasesPath) file, err := os.Open(oldLeasesPath)
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
// Nothing to migrate. // Nothing to migrate.

View file

@ -200,7 +200,7 @@ func createICMPv6RAPacket(params icmpv6RA) (data []byte, err error) {
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
if !(ra.raAllowSLAAC || ra.raSLAACOnly) { if !ra.raAllowSLAAC && !ra.raSLAACOnly {
return nil return nil
} }

View file

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

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

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

View file

@ -15,7 +15,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghtls" "github.com/AdguardTeam/AdGuardHome/internal/aghtls"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"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" "github.com/AdguardTeam/golibs/netutil"
@ -436,102 +435,6 @@ func (s *Server) initDefaultSettings() {
} }
} }
// UpstreamHTTPVersions returns the HTTP versions for upstream configuration
// depending on configuration.
func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) {
if !http3 {
return upstream.DefaultHTTPVersions
}
return []upstream.HTTPVersion{
upstream.HTTPVersion3,
upstream.HTTPVersion2,
upstream.HTTPVersion11,
}
}
// prepareUpstreamSettings - prepares upstream DNS server settings
func (s *Server) prepareUpstreamSettings() error {
// We're setting a customized set of RootCAs. The reason is that Go default
// mechanism of loading TLS roots does not always work properly on some
// routers so we're loading roots manually and pass it here.
//
// See [aghtls.SystemRootCAs].
upstream.RootCAs = s.conf.TLSv12Roots
upstream.CipherSuites = s.conf.TLSCiphers
// Load upstreams either from the file, or from the settings
var upstreams []string
if s.conf.UpstreamDNSFileName != "" {
data, err := os.ReadFile(s.conf.UpstreamDNSFileName)
if err != nil {
return fmt.Errorf("reading upstream from file: %w", err)
}
upstreams = stringutil.SplitTrimmed(string(data), "\n")
log.Debug("dns: using %d upstream servers from file %s", len(upstreams), s.conf.UpstreamDNSFileName)
} else {
upstreams = s.conf.UpstreamDNS
}
httpVersions := UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams)
upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty)
upstreamConfig, err := proxy.ParseUpstreamsConfig(
upstreams,
&upstream.Options{
Bootstrap: s.conf.BootstrapDNS,
Timeout: s.conf.UpstreamTimeout,
HTTPVersions: httpVersions,
PreferIPv6: s.conf.BootstrapPreferIPv6,
},
)
if err != nil {
return fmt.Errorf("parsing upstream config: %w", err)
}
if len(upstreamConfig.Upstreams) == 0 {
log.Info("warning: no default upstream servers specified, using %v", defaultDNS)
var uc *proxy.UpstreamConfig
uc, err = proxy.ParseUpstreamsConfig(
defaultDNS,
&upstream.Options{
Bootstrap: s.conf.BootstrapDNS,
Timeout: s.conf.UpstreamTimeout,
HTTPVersions: httpVersions,
PreferIPv6: s.conf.BootstrapPreferIPv6,
},
)
if err != nil {
return fmt.Errorf("parsing default upstreams: %w", err)
}
upstreamConfig.Upstreams = uc.Upstreams
}
s.conf.UpstreamConfig = upstreamConfig
return nil
}
// setProxyUpstreamMode sets the upstream mode and related settings in conf
// based on provided parameters.
func setProxyUpstreamMode(
conf *proxy.Config,
allServers bool,
fastestAddr bool,
fastestTimeout time.Duration,
) {
if allServers {
conf.UpstreamMode = proxy.UModeParallel
} else if fastestAddr {
conf.UpstreamMode = proxy.UModeFastestAddr
conf.FastestPingTimeout = fastestTimeout
} else {
conf.UpstreamMode = proxy.UModeLoadBalance
}
}
// prepareIpsetListSettings reads and prepares the ipset configuration either // prepareIpsetListSettings reads and prepares the ipset configuration either
// from a file or from the data in the configuration file. // from a file or from the data in the configuration file.
func (s *Server) prepareIpsetListSettings() (err error) { func (s *Server) prepareIpsetListSettings() (err error) {
@ -540,6 +443,7 @@ func (s *Server) prepareIpsetListSettings() (err error) {
return s.ipset.init(s.conf.IpsetList) return s.ipset.init(s.conf.IpsetList)
} }
// #nosec G304 -- Trust the path explicitly given by the user.
data, err := os.ReadFile(fn) data, err := os.ReadFile(fn)
if err != nil { if err != nil {
return err return err

View file

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

View file

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

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.GetConfig() setts := s.dnsFilter.Settings()
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

@ -633,61 +633,70 @@ func (err domainSpecificTestError) Error() (msg string) {
return fmt.Sprintf("WARNING: %s", err.error) return fmt.Sprintf("WARNING: %s", err.error)
} }
// checkDNS checks the upstream server defined by upstreamConfigStr using // parseUpstreamLine parses line and creates the [upstream.Upstream] using opts
// healthCheck for actually exchange messages. It uses bootstrap to resolve the // and information from [s.dnsFilter.EtcHosts]. It returns an error if the line
// upstream's address. // is not a valid upstream line, see [upstream.AddressToUpstream]. It's a
func checkDNS( // caller's responsibility to close u.
upstreamConfigStr string, func (s *Server) parseUpstreamLine(
bootstrap []string, line string,
bootstrapPrefIPv6 bool, opts *upstream.Options,
timeout time.Duration, ) (u upstream.Upstream, specific bool, err error) {
healthCheck healthCheckFunc, // Separate upstream from domains list.
) (err error) { upstreamAddr, domains, err := separateUpstream(line)
if IsCommentOrEmpty(upstreamConfigStr) { if err != nil {
return nil return nil, false, fmt.Errorf("wrong upstream format: %w", err)
} }
// Separate upstream from domains list. specific = len(domains) > 0
upstreamAddr, domains, err := separateUpstream(upstreamConfigStr)
if err != nil {
return fmt.Errorf("wrong upstream format: %w", err)
}
useDefault, err := validateUpstream(upstreamAddr, domains) useDefault, err := validateUpstream(upstreamAddr, domains)
if err != nil { if err != nil {
return fmt.Errorf("wrong upstream format: %w", err) return nil, specific, fmt.Errorf("wrong upstream format: %w", err)
} else if useDefault { } else if useDefault {
return nil return nil, specific, nil
}
if len(bootstrap) == 0 {
bootstrap = defaultBootstrap
} }
log.Debug("dnsforward: checking if upstream %q works", upstreamAddr) log.Debug("dnsforward: checking if upstream %q works", upstreamAddr)
u, err := upstream.AddressToUpstream(upstreamAddr, &upstream.Options{ opts = &upstream.Options{
Bootstrap: bootstrap, Bootstrap: opts.Bootstrap,
Timeout: timeout, Timeout: opts.Timeout,
PreferIPv6: bootstrapPrefIPv6, PreferIPv6: opts.PreferIPv6,
}) }
if s.dnsFilter != nil && s.dnsFilter.EtcHosts != nil {
resolved := s.resolveUpstreamHost(extractUpstreamHost(upstreamAddr))
sortNetIPAddrs(resolved, opts.PreferIPv6)
opts.ServerIPAddrs = resolved
}
u, err = upstream.AddressToUpstream(upstreamAddr, opts)
if err != nil { if err != nil {
return fmt.Errorf("failed to choose upstream for %q: %w", upstreamAddr, err) return nil, specific, fmt.Errorf("creating upstream for %q: %w", upstreamAddr, err)
}
return u, specific, nil
}
func (s *Server) checkDNS(line string, opts *upstream.Options, check healthCheckFunc) (err error) {
if IsCommentOrEmpty(line) {
return nil
}
var u upstream.Upstream
var specific bool
defer func() {
if err != nil && specific {
err = domainSpecificTestError{error: err}
}
}()
u, specific, err = s.parseUpstreamLine(line, opts)
if err != nil || u == nil {
return err
} }
defer func() { err = errors.WithDeferred(err, u.Close()) }() defer func() { err = errors.WithDeferred(err, u.Close()) }()
if err = healthCheck(u); err != nil { return check(u)
err = fmt.Errorf("upstream %q fails to exchange: %w", upstreamAddr, err)
if domains != nil {
return domainSpecificTestError{error: err}
}
return err
}
log.Debug("dnsforward: upstream %q is ok", upstreamAddr)
return nil
} }
func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) { func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
@ -699,47 +708,54 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
return return
} }
result := map[string]string{} opts := &upstream.Options{
bootstraps := req.BootstrapDNS Bootstrap: req.BootstrapDNS,
bootstrapPrefIPv6 := s.conf.BootstrapPreferIPv6 Timeout: s.conf.UpstreamTimeout,
timeout := s.conf.UpstreamTimeout PreferIPv6: s.conf.BootstrapPreferIPv6,
}
if len(opts.Bootstrap) == 0 {
opts.Bootstrap = defaultBootstrap
}
type upsCheckResult = struct { type upsCheckResult = struct {
res string err error
host string host string
} }
req.Upstreams = stringutil.FilterOut(req.Upstreams, IsCommentOrEmpty)
req.PrivateUpstreams = stringutil.FilterOut(req.PrivateUpstreams, IsCommentOrEmpty)
upsNum := len(req.Upstreams) + len(req.PrivateUpstreams) upsNum := len(req.Upstreams) + len(req.PrivateUpstreams)
result := make(map[string]string, upsNum)
resCh := make(chan upsCheckResult, upsNum) resCh := make(chan upsCheckResult, upsNum)
checkUps := func(ups string, healthCheck healthCheckFunc) {
res := upsCheckResult{
host: ups,
}
defer func() { resCh <- res }()
checkErr := checkDNS(ups, bootstraps, bootstrapPrefIPv6, timeout, healthCheck)
if checkErr != nil {
res.res = checkErr.Error()
} else {
res.res = "OK"
}
}
for _, ups := range req.Upstreams { for _, ups := range req.Upstreams {
go checkUps(ups, checkDNSUpstreamExc) go func(ups string) {
resCh <- upsCheckResult{
host: ups,
err: s.checkDNS(ups, opts, checkDNSUpstreamExc),
}
}(ups)
} }
for _, ups := range req.PrivateUpstreams { for _, ups := range req.PrivateUpstreams {
go checkUps(ups, checkPrivateUpstreamExc) go func(ups string) {
resCh <- upsCheckResult{
host: ups,
err: s.checkDNS(ups, opts, checkPrivateUpstreamExc),
}
}(ups)
} }
for i := 0; i < upsNum; i++ { for i := 0; i < upsNum; i++ {
pair := <-resCh
// TODO(e.burkov): The upstreams used for both common and private // TODO(e.burkov): The upstreams used for both common and private
// resolving should be reported separately. // resolving should be reported separately.
result[pair.host] = pair.res pair := <-resCh
if pair.err != nil {
result[pair.host] = pair.err.Error()
} else {
result[pair.host] = "OK"
}
} }
close(resCh)
_ = aghhttp.WriteJSONResponse(w, r, result) _ = aghhttp.WriteJSONResponse(w, r, result)
} }

View file

@ -13,10 +13,12 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"testing/fstest"
"time" "time"
"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/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/golibs/httphdr" "github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
@ -280,6 +282,10 @@ func TestIsCommentOrEmpty(t *testing.T) {
} }
func TestValidateUpstreams(t *testing.T) { func TestValidateUpstreams(t *testing.T) {
const sdnsStamp = `sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_J` +
`S3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczE` +
`uYWRndWFyZC5jb20`
testCases := []struct { testCases := []struct {
name string name string
wantErr string wantErr string
@ -300,7 +306,7 @@ func TestValidateUpstreams(t *testing.T) {
"[//]tls://1.1.1.1", "[//]tls://1.1.1.1",
"[/www.host.com/]#", "[/www.host.com/]#",
"[/host.com/google.com/]8.8.8.8", "[/host.com/google.com/]8.8.8.8",
"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", "[/host/]" + sdnsStamp,
}, },
}, { }, {
name: "with_default", name: "with_default",
@ -310,7 +316,7 @@ func TestValidateUpstreams(t *testing.T) {
"[//]tls://1.1.1.1", "[//]tls://1.1.1.1",
"[/www.host.com/]#", "[/www.host.com/]#",
"[/host.com/google.com/]8.8.8.8", "[/host.com/google.com/]8.8.8.8",
"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", "[/host/]" + sdnsStamp,
"8.8.8.8", "8.8.8.8",
}, },
}, { }, {
@ -326,9 +332,10 @@ func TestValidateUpstreams(t *testing.T) {
wantErr: `validating upstream "123.3.7m": not an ip:port`, wantErr: `validating upstream "123.3.7m": not an ip:port`,
set: []string{"123.3.7m"}, set: []string{"123.3.7m"},
}, { }, {
name: "invalid", name: "invalid",
wantErr: `bad upstream for domain "[/host.com]tls://dns.adguard.com": missing separator`, wantErr: `bad upstream for domain "[/host.com]tls://dns.adguard.com": ` +
set: []string{"[/host.com]tls://dns.adguard.com"}, `missing separator`,
set: []string{"[/host.com]tls://dns.adguard.com"},
}, { }, {
name: "invalid", name: "invalid",
wantErr: `validating upstream "[host.ru]#": not an ip:port`, wantErr: `validating upstream "[host.ru]#": not an ip:port`,
@ -340,14 +347,14 @@ func TestValidateUpstreams(t *testing.T) {
"1.1.1.1", "1.1.1.1",
"tls://1.1.1.1", "tls://1.1.1.1",
"https://dns.adguard.com/dns-query", "https://dns.adguard.com/dns-query",
"sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", sdnsStamp,
"udp://dns.google", "udp://dns.google",
"udp://8.8.8.8", "udp://8.8.8.8",
"[/host.com/]1.1.1.1", "[/host.com/]1.1.1.1",
"[//]tls://1.1.1.1", "[//]tls://1.1.1.1",
"[/www.host.com/]#", "[/www.host.com/]#",
"[/host.com/google.com/]8.8.8.8", "[/host.com/google.com/]8.8.8.8",
"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", "[/host/]" + sdnsStamp,
"[/пример.рф/]8.8.8.8", "[/пример.рф/]8.8.8.8",
}, },
}, { }, {
@ -418,27 +425,28 @@ func TestValidateUpstreamsPrivate(t *testing.T) {
} }
} }
func newLocalUpstreamListener(t *testing.T, port int, handler dns.Handler) (real net.Addr) { func newLocalUpstreamListener(t *testing.T, port uint16, handler dns.Handler) (real netip.AddrPort) {
t.Helper()
startCh := make(chan struct{}) startCh := make(chan struct{})
upsSrv := &dns.Server{ upsSrv := &dns.Server{
Addr: netip.AddrPortFrom(netutil.IPv4Localhost(), uint16(port)).String(), Addr: netip.AddrPortFrom(netutil.IPv4Localhost(), port).String(),
Net: "tcp", Net: "tcp",
Handler: handler, Handler: handler,
NotifyStartedFunc: func() { close(startCh) }, NotifyStartedFunc: func() { close(startCh) },
} }
go func() { go func() {
t := testutil.PanicT{}
err := upsSrv.ListenAndServe() err := upsSrv.ListenAndServe()
require.NoError(t, err) require.NoError(testutil.PanicT{}, err)
}() }()
<-startCh <-startCh
testutil.CleanupAndRequireSuccess(t, upsSrv.Shutdown) testutil.CleanupAndRequireSuccess(t, upsSrv.Shutdown)
return upsSrv.Listener.Addr() return testutil.RequireTypeAssert[*net.TCPAddr](t, upsSrv.Listener.Addr()).AddrPort()
} }
func TestServer_handleTestUpstreaDNS(t *testing.T) { func TestServer_HandleTestUpstreamDNS(t *testing.T) {
goodHandler := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) { goodHandler := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
err := w.WriteMsg(new(dns.Msg).SetReply(m)) err := w.WriteMsg(new(dns.Msg).SetReply(m))
require.NoError(testutil.PanicT{}, err) require.NoError(testutil.PanicT{}, err)
@ -457,9 +465,38 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
Host: newLocalUpstreamListener(t, 0, badHandler).String(), Host: newLocalUpstreamListener(t, 0, badHandler).String(),
}).String() }).String()
const upsTimeout = 100 * time.Millisecond const (
upsTimeout = 100 * time.Millisecond
srv := createTestServer(t, &filtering.Config{}, ServerConfig{ hostsFileName = "hosts"
upstreamHost = "custom.localhost"
)
hostsListener := newLocalUpstreamListener(t, 0, goodHandler)
hostsUps := (&url.URL{
Scheme: "tcp",
Host: netutil.JoinHostPort(upstreamHost, int(hostsListener.Port())),
}).String()
hc, err := aghnet.NewHostsContainer(
filtering.SysHostsListID,
fstest.MapFS{
hostsFileName: &fstest.MapFile{
Data: []byte(hostsListener.Addr().String() + " " + upstreamHost),
},
},
&aghtest.FSWatcher{
OnEvents: func() (e <-chan struct{}) { return nil },
OnAdd: func(_ string) (err error) { return nil },
OnClose: func() (err error) { return nil },
},
hostsFileName,
)
require.NoError(t, err)
srv := createTestServer(t, &filtering.Config{
EtcHosts: hc,
}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}}, UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}},
UpstreamTimeout: upsTimeout, UpstreamTimeout: upsTimeout,
@ -486,8 +523,7 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
"upstream_dns": []string{badUps}, "upstream_dns": []string{badUps},
}, },
wantResp: map[string]any{ wantResp: map[string]any{
badUps: `upstream "` + badUps + `" fails to exchange: ` + badUps: `couldn't communicate with upstream: exchanging with ` +
`couldn't communicate with upstream: exchanging with ` +
badUps + ` over tcp: dns: id mismatch`, badUps + ` over tcp: dns: id mismatch`,
}, },
name: "broken", name: "broken",
@ -497,20 +533,40 @@ 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: `couldn't communicate with upstream: exchanging with ` +
`couldn't communicate with upstream: exchanging with ` +
badUps + ` over tcp: dns: id mismatch`, badUps + ` over tcp: dns: id mismatch`,
}, },
name: "both", name: "both",
}, {
body: map[string]any{
"upstream_dns": []string{"[/domain.example/]" + badUps},
},
wantResp: map[string]any{
"[/domain.example/]" + badUps: `WARNING: couldn't communicate ` +
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
`dns: id mismatch`,
},
name: "domain_specific_error",
}, {
body: map[string]any{
"upstream_dns": []string{hostsUps},
},
wantResp: map[string]any{
hostsUps: "OK",
},
name: "etc_hosts",
}} }}
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
reqBody, err := json.Marshal(tc.body) var reqBody []byte
reqBody, err = json.Marshal(tc.body)
require.NoError(t, err) require.NoError(t, err)
w := httptest.NewRecorder() w := httptest.NewRecorder()
r, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
var r *http.Request
r, err = http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
require.NoError(t, err) require.NoError(t, err)
srv.handleTestUpstreamDNS(w, r) srv.handleTestUpstreamDNS(w, r)
@ -538,11 +594,15 @@ func TestServer_handleTestUpstreaDNS(t *testing.T) {
req := map[string]any{ req := map[string]any{
"upstream_dns": []string{sleepyUps}, "upstream_dns": []string{sleepyUps},
} }
reqBody, err := json.Marshal(req)
var reqBody []byte
reqBody, err = json.Marshal(req)
require.NoError(t, err) require.NoError(t, err)
w := httptest.NewRecorder() w := httptest.NewRecorder()
r, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
var r *http.Request
r, err = http.NewRequest(http.MethodPost, "", bytes.NewReader(reqBody))
require.NoError(t, err) require.NoError(t, err)
srv.handleTestUpstreamDNS(w, r) srv.handleTestUpstreamDNS(w, r)

View file

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

View file

@ -57,16 +57,13 @@ 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:
// If the query was filtered by Safe Search, filtering also must return return s.genForBlockingMode(req, ipsFromRules(res.Rules))
// 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

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

View file

@ -46,6 +46,10 @@ 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
} }
@ -54,9 +58,12 @@ func (l *testStats) ShouldCount(string, uint16, uint16, []string) bool {
return true return true
} }
func TestProcessQueryLogsAndStats(t *testing.T) { func TestServer_ProcessQueryLogsAndStats(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
@ -67,6 +74,7 @@ func TestProcessQueryLogsAndStats(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: "",
@ -77,6 +85,7 @@ func TestProcessQueryLogsAndStats(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",
@ -87,6 +96,7 @@ func TestProcessQueryLogsAndStats(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: "",
@ -97,6 +107,7 @@ func TestProcessQueryLogsAndStats(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: "",
@ -107,6 +118,7 @@ func TestProcessQueryLogsAndStats(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: "",
@ -117,6 +129,7 @@ func TestProcessQueryLogsAndStats(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: "",
@ -127,6 +140,7 @@ func TestProcessQueryLogsAndStats(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: "",
@ -137,6 +151,7 @@ func TestProcessQueryLogsAndStats(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: "",
@ -147,6 +162,7 @@ func TestProcessQueryLogsAndStats(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: "",
@ -157,6 +173,7 @@ func TestProcessQueryLogsAndStats(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: "",
@ -165,6 +182,17 @@ func TestProcessQueryLogsAndStats(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)
@ -181,7 +209,7 @@ func TestProcessQueryLogsAndStats(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: "example.com.", Name: tc.domain,
}}, }},
} }
pctx := &proxy.DNSContext{ pctx := &proxy.DNSContext{

View file

@ -0,0 +1,311 @@
package dnsforward
import (
"bytes"
"fmt"
"net"
"net/url"
"os"
"strings"
"time"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/AdguardTeam/urlfilter"
"github.com/miekg/dns"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
// loadUpstreams parses upstream DNS servers from the configured file or from
// the configuration itself.
func (s *Server) loadUpstreams() (upstreams []string, err error) {
if s.conf.UpstreamDNSFileName == "" {
return stringutil.FilterOut(s.conf.UpstreamDNS, IsCommentOrEmpty), nil
}
var data []byte
data, err = os.ReadFile(s.conf.UpstreamDNSFileName)
if err != nil {
return nil, fmt.Errorf("reading upstream from file: %w", err)
}
upstreams = stringutil.SplitTrimmed(string(data), "\n")
log.Debug("dnsforward: got %d upstreams in %q", len(upstreams), s.conf.UpstreamDNSFileName)
return stringutil.FilterOut(upstreams, IsCommentOrEmpty), nil
}
// prepareUpstreamSettings sets upstream DNS server settings.
func (s *Server) prepareUpstreamSettings() (err error) {
// We're setting a customized set of RootCAs. The reason is that Go default
// mechanism of loading TLS roots does not always work properly on some
// routers so we're loading roots manually and pass it here.
//
// See [aghtls.SystemRootCAs].
upstream.RootCAs = s.conf.TLSv12Roots
upstream.CipherSuites = s.conf.TLSCiphers
// Load upstreams either from the file, or from the settings
var upstreams []string
upstreams, err = s.loadUpstreams()
if err != nil {
return fmt.Errorf("loading upstreams: %w", err)
}
s.conf.UpstreamConfig, err = s.prepareUpstreamConfig(upstreams, defaultDNS, &upstream.Options{
Bootstrap: s.conf.BootstrapDNS,
Timeout: s.conf.UpstreamTimeout,
HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams),
PreferIPv6: s.conf.BootstrapPreferIPv6,
})
if err != nil {
return fmt.Errorf("preparing upstream config: %w", err)
}
return nil
}
// prepareUpstreamConfig sets upstream configuration based on upstreams and
// configuration of s.
func (s *Server) prepareUpstreamConfig(
upstreams []string,
defaultUpstreams []string,
opts *upstream.Options,
) (uc *proxy.UpstreamConfig, err error) {
uc, err = proxy.ParseUpstreamsConfig(upstreams, opts)
if err != nil {
return nil, fmt.Errorf("parsing upstream config: %w", err)
}
if len(uc.Upstreams) == 0 && defaultUpstreams != nil {
log.Info("dnsforward: warning: no default upstreams specified, using %v", defaultUpstreams)
var defaultUpstreamConfig *proxy.UpstreamConfig
defaultUpstreamConfig, err = proxy.ParseUpstreamsConfig(defaultUpstreams, opts)
if err != nil {
return nil, fmt.Errorf("parsing default upstreams: %w", err)
}
uc.Upstreams = defaultUpstreamConfig.Upstreams
}
if s.dnsFilter != nil && s.dnsFilter.EtcHosts != nil {
err = s.replaceUpstreamsWithHosts(uc, opts)
if err != nil {
return nil, fmt.Errorf("resolving upstreams with hosts: %w", err)
}
}
return uc, nil
}
// replaceUpstreamsWithHosts replaces unique upstreams with their resolved
// versions based on the system hosts file.
//
// TODO(e.burkov): This should be performed inside dnsproxy, which should
// actually consider /etc/hosts. See TODO on [aghnet.HostsContainer].
func (s *Server) replaceUpstreamsWithHosts(
upsConf *proxy.UpstreamConfig,
opts *upstream.Options,
) (err error) {
resolved := map[string]*upstream.Options{}
err = s.resolveUpstreamsWithHosts(resolved, upsConf.Upstreams, opts)
if err != nil {
return fmt.Errorf("resolving upstreams: %w", err)
}
hosts := maps.Keys(upsConf.DomainReservedUpstreams)
// TODO(e.burkov): Think of extracting sorted range into an util function.
slices.Sort(hosts)
for _, host := range hosts {
err = s.resolveUpstreamsWithHosts(resolved, upsConf.DomainReservedUpstreams[host], opts)
if err != nil {
return fmt.Errorf("resolving upstreams reserved for %s: %w", host, err)
}
}
hosts = maps.Keys(upsConf.SpecifiedDomainUpstreams)
slices.Sort(hosts)
for _, host := range hosts {
err = s.resolveUpstreamsWithHosts(resolved, upsConf.SpecifiedDomainUpstreams[host], opts)
if err != nil {
return fmt.Errorf("resolving upstreams specific for %s: %w", host, err)
}
}
return nil
}
// resolveUpstreamsWithHosts resolves the IP addresses of each of the upstreams
// and replaces those both in upstreams and resolved. Upstreams that failed to
// resolve are placed to resolved as-is. This function only returns error of
// upstreams closing.
func (s *Server) resolveUpstreamsWithHosts(
resolved map[string]*upstream.Options,
upstreams []upstream.Upstream,
opts *upstream.Options,
) (err error) {
for i := range upstreams {
u := upstreams[i]
addr := u.Address()
host := extractUpstreamHost(addr)
withIPs, ok := resolved[host]
if !ok {
ips := s.resolveUpstreamHost(host)
if len(ips) == 0 {
resolved[host] = nil
return nil
}
sortNetIPAddrs(ips, opts.PreferIPv6)
withIPs = opts.Clone()
withIPs.ServerIPAddrs = ips
resolved[host] = withIPs
} else if withIPs == nil {
continue
}
if err = u.Close(); err != nil {
return fmt.Errorf("closing upstream %s: %w", addr, err)
}
upstreams[i], err = upstream.AddressToUpstream(addr, withIPs)
if err != nil {
return fmt.Errorf("replacing upstream %s with resolved %s: %w", addr, host, err)
}
log.Debug("dnsforward: using %s for %s", withIPs.ServerIPAddrs, upstreams[i].Address())
}
return nil
}
// extractUpstreamHost returns the hostname of addr without port with an
// assumption that any address passed here has already been successfully parsed
// by [upstream.AddressToUpstream]. This function eesentially mirrors the logic
// of [upstream.AddressToUpstream], see TODO on [replaceUpstreamsWithHosts].
func extractUpstreamHost(addr string) (host string) {
var err error
if strings.Contains(addr, "://") {
var u *url.URL
u, err = url.Parse(addr)
if err != nil {
log.Debug("dnsforward: parsing upstream %s: %s", addr, err)
return addr
}
return u.Hostname()
}
// Probably, plain UDP upstream defined by address or address:port.
host, err = netutil.SplitHost(addr)
if err != nil {
return addr
}
return host
}
// resolveUpstreamHost returns the version of ups with IP addresses from the
// system hosts file placed into its options.
func (s *Server) resolveUpstreamHost(host string) (addrs []net.IP) {
req := &urlfilter.DNSRequest{
Hostname: host,
DNSType: dns.TypeA,
}
aRes, _ := s.dnsFilter.EtcHosts.MatchRequest(req)
req.DNSType = dns.TypeAAAA
aaaaRes, _ := s.dnsFilter.EtcHosts.MatchRequest(req)
var ips []net.IP
for _, rw := range append(aRes.DNSRewrites(), aaaaRes.DNSRewrites()...) {
dr := rw.DNSRewrite
if dr == nil || dr.Value == nil {
continue
}
if ip, ok := dr.Value.(net.IP); ok {
ips = append(ips, ip)
}
}
return ips
}
// sortNetIPAddrs sorts addrs in accordance with the protocol preferences.
// Invalid addresses are sorted near the end.
//
// TODO(e.burkov): This function taken from dnsproxy, which also already
// contains a few similar functions. Think of moving to golibs.
func sortNetIPAddrs(addrs []net.IP, preferIPv6 bool) {
l := len(addrs)
if l <= 1 {
return
}
slices.SortStableFunc(addrs, func(addrA, addrB net.IP) (sortsBefore bool) {
switch len(addrA) {
case net.IPv4len, net.IPv6len:
switch len(addrB) {
case net.IPv4len, net.IPv6len:
// Go on.
default:
return true
}
default:
return false
}
if aIs4, bIs4 := addrA.To4() != nil, addrB.To4() != nil; aIs4 != bIs4 {
if aIs4 {
return !preferIPv6
}
return preferIPv6
}
return bytes.Compare(addrA, addrB) < 0
})
}
// UpstreamHTTPVersions returns the HTTP versions for upstream configuration
// depending on configuration.
func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) {
if !http3 {
return upstream.DefaultHTTPVersions
}
return []upstream.HTTPVersion{
upstream.HTTPVersion3,
upstream.HTTPVersion2,
upstream.HTTPVersion11,
}
}
// setProxyUpstreamMode sets the upstream mode and related settings in conf
// based on provided parameters.
func setProxyUpstreamMode(
conf *proxy.Config,
allServers bool,
fastestAddr bool,
fastestTimeout time.Duration,
) {
if allServers {
conf.UpstreamMode = proxy.UModeParallel
} else if fastestAddr {
conf.UpstreamMode = proxy.UModeFastestAddr
conf.FastestPingTimeout = fastestTimeout
} else {
conf.UpstreamMode = proxy.UModeLoadBalance
}
}

View file

@ -2,9 +2,12 @@ package filtering
import ( import (
"encoding/json" "encoding/json"
"fmt"
"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"
@ -44,23 +47,57 @@ func initBlockedServices() {
log.Debug("filtering: initialized %d services", l) log.Debug("filtering: initialized %d services", l)
} }
// BlockedSvcKnown returns true if a blocked service ID is known. // BlockedServices is the configuration of blocked services.
func BlockedSvcKnown(s string) (ok bool) { type BlockedServices struct {
_, ok = serviceRules[s] // Schedule is blocked services schedule for every day of the week.
Schedule *schedule.Weekly `yaml:"schedule"`
return ok // IDs is the names of blocked services.
IDs []string `yaml:"ids"`
}
// Clone returns a deep copy of blocked services.
func (s *BlockedServices) Clone() (c *BlockedServices) {
if s == nil {
return nil
}
return &BlockedServices{
Schedule: s.Schedule.Clone(),
IDs: slices.Clone(s.IDs),
}
}
// Validate returns an error if blocked services contain unknown service ID. s
// must not be nil.
func (s *BlockedServices) Validate() (err error) {
for _, id := range s.IDs {
_, ok := serviceRules[id]
if !ok {
return fmt.Errorf("unknown blocked-service %q", id)
}
}
return nil
} }
// ApplyBlockedServices - set blocked services settings for this DNS request // ApplyBlockedServices - set blocked services settings for this DNS request
func (d *DNSFilter) ApplyBlockedServices(setts *Settings, list []string) { func (d *DNSFilter) ApplyBlockedServices(setts *Settings) {
d.confLock.RLock()
defer d.confLock.RUnlock()
setts.ServicesRules = []ServiceEntry{} setts.ServicesRules = []ServiceEntry{}
if list == nil {
d.confLock.RLock()
defer d.confLock.RUnlock()
list = d.Config.BlockedServices bsvc := d.BlockedServices
// TODO(s.chzhen): Use startTime from [dnsforward.dnsContext].
if !bsvc.Schedule.Contains(time.Now()) {
d.ApplyBlockedServicesList(setts, bsvc.IDs)
} }
}
// ApplyBlockedServicesList appends filtering rules to the settings.
func (d *DNSFilter) ApplyBlockedServicesList(setts *Settings, list []string) {
for _, name := range list { for _, name := range list {
rules, ok := serviceRules[name] rules, ok := serviceRules[name]
if !ok { if !ok {
@ -90,7 +127,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 list := d.Config.BlockedServices.IDs
d.confLock.RUnlock() d.confLock.RUnlock()
_ = aghhttp.WriteJSONResponse(w, r, list) _ = aghhttp.WriteJSONResponse(w, r, list)
@ -106,7 +143,7 @@ func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ
} }
d.confLock.Lock() d.confLock.Lock()
d.Config.BlockedServices = list d.Config.BlockedServices.IDs = 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

@ -103,9 +103,9 @@ type Config struct {
Rewrites []*LegacyRewrite `yaml:"rewrites"` Rewrites []*LegacyRewrite `yaml:"rewrites"`
// Names of services to block (globally). // BlockedServices is the configuration of blocked services.
// Per-client settings can override this configuration. // Per-client settings can override this configuration.
BlockedServices []string `yaml:"blocked_services"` BlockedServices *BlockedServices `yaml:"blocked_services"`
// EtcHosts is a container of IP-hostname pairs taken from the operating // 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).
@ -298,12 +298,12 @@ func (d *DNSFilter) SetEnabled(enabled bool) {
atomic.StoreUint32(&d.enabled, mathutil.BoolToNumber[uint32](enabled)) atomic.StoreUint32(&d.enabled, mathutil.BoolToNumber[uint32](enabled))
} }
// GetConfig - get configuration // Settings returns filtering settings.
func (d *DNSFilter) GetConfig() (s Settings) { func (d *DNSFilter) Settings() (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,
@ -519,7 +519,7 @@ func (d *DNSFilter) matchSysHosts(
dnsres, _ := d.EtcHosts.MatchRequest(&urlfilter.DNSRequest{ dnsres, _ := d.EtcHosts.MatchRequest(&urlfilter.DNSRequest{
Hostname: host, Hostname: host,
SortedClientTags: setts.ClientTags, SortedClientTags: setts.ClientTags,
// TODO(e.burkov): Wait for urlfilter update to pass net.IP. // TODO(e.burkov): Wait for urlfilter update to pass netip.Addr.
ClientIP: setts.ClientIP.String(), ClientIP: setts.ClientIP.String(),
ClientName: setts.ClientName, ClientName: setts.ClientName,
DNSType: qtype, DNSType: qtype,
@ -987,16 +987,13 @@ 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)
} }
bsvcs := []string{} if d.BlockedServices != nil {
for _, s := range d.BlockedServices { err = d.BlockedServices.Validate()
if !BlockedSvcKnown(s) {
log.Debug("skipping unknown blocked-service %q", s)
continue if err != nil {
return nil, fmt.Errorf("filtering: %w", err)
} }
bsvcs = append(bsvcs, s)
} }
d.BlockedServices = bsvcs
if blockFilters != nil { if blockFilters != nil {
err = d.initFiltering(nil, blockFilters) err = d.initFiltering(nil, blockFilters)

View file

@ -169,7 +169,7 @@ func (d *DNSFilter) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ
deleted = (*filters)[delIdx] deleted = (*filters)[delIdx]
p := deleted.Path(d.DataDir) p := deleted.Path(d.DataDir)
err = os.Rename(p, p+".old") err = os.Rename(p, p+".old")
if err != nil { if err != nil && !errors.Is(err, os.ErrNotExist) {
log.Error("deleting filter %d: renaming file %q: %s", deleted.ID, p, err) log.Error("deleting filter %d: renaming file %q: %s", deleted.ID, p, err)
return return
@ -416,12 +416,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.GetConfig() setts := d.Settings()
setts.FilteringEnabled = true setts.FilteringEnabled = true
setts.ProtectionEnabled = true setts.ProtectionEnabled = true
d.ApplyBlockedServices(&setts, nil) d.ApplyBlockedServices(setts)
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,
@ -555,6 +555,7 @@ 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

@ -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 initialisation. // TODO(a.garipov): Check cnames for cycles on initialization.
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,6 +6,7 @@ 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.
@ -91,3 +92,62 @@ 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

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

View file

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

View file

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

@ -27,6 +27,25 @@ var blockedServices = []blockedService{{
"||9cache.com^", "||9cache.com^",
"||9gag.com^", "||9gag.com^",
}, },
}, {
ID: "activision_blizzard",
Name: "Activision Blizzard",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"-237 0 1572 1572\"><path d=\"m549.1.2 548.4 1571.4H798l-74.2-200H374.5l-74.3 200H.7zM626 1085.1l-83-274.3-82.9 274.3z\"/></svg>"),
Rules: []string{
"||activision.com^",
"||activisionblizzard.com^",
"||demonware.net^",
},
}, {
ID: "aliexpress",
Name: "AliExpress",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M9 4C6.25 4 4 6.25 4 9v32c0 2.75 2.25 5 5 5h32c2.75 0 5-2.25 5-5V9c0-2.75-2.25-5-5-5H9zm0 2h32c1.668 0 3 1.332 3 3v3.38A3.973 3.973 0 0 0 41 11H9a3.973 3.973 0 0 0-3 1.38V9c0-1.668 1.332-3 3-3zm6 11a1 1 0 0 1 1 1c0 4.962 4.037 9 9 9s9-4.038 9-9a1 1 0 1 1 2 0c0 6.065-4.935 11-11 11s-11-4.935-11-11a1 1 0 0 1 1-1z\"/></svg>"),
Rules: []string{
"||ae-rus.net^",
"||ae-rus.ru^",
"||aliexpress.com^",
"||aliexpress.ru^",
},
}, { }, {
ID: "amazon", ID: "amazon",
Name: "Amazon", Name: "Amazon",
@ -234,6 +253,16 @@ var blockedServices = []blockedService{{
"||z.cn^", "||z.cn^",
"||zappos^", "||zappos^",
}, },
}, {
ID: "battle_net",
Name: "Battle.net",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M43.11 22.15s3.95.2 3.95-2.12c0-3.03-5.26-5.77-5.26-5.77s.83-1.74 1.34-2.72a37.3 37.3 0 0 0 2.09-5.65c.16-1.1-.09-1.44-.09-1.44-.35 2.34-4.17 9.09-4.47 9.32-3.72-1.75-8.83-2.23-8.83-2.23S26.84 1 22.13 1c-4.67 0-4.65 9.02-4.65 9.02s-1.32-2.56-2.97-2.56c-2.42 0-3.22 3.67-3.22 7.64a37.8 37.8 0 0 0-9.16 1.17c-.36.1-1.49.92-.97.82 1.04-.34 5.95-1.1 10.25-.72.24 3.77 2.44 8.68 2.44 8.68S9.13 31.9 9.13 36.78c0 1.29.56 3.64 3.95 3.64 2.84 0 6.03-1.7 6.63-2.06a6.33 6.33 0 0 0-.91 2.83c0 .54.31 2.06 2.5 2.06 2.82 0 5.96-2.16 5.96-2.16s2.96 4.93 5.5 7.2c.69.6 1.34.71 1.34.71s-2.52-2.43-5.84-8.68c3.08-1.9 6.3-6.4 6.3-6.4l3.3.01c4.6 0 11.11-.96 11.11-4.61 0-3.77-5.86-7.17-5.86-7.17Zm.52-2.26c0 1.33-1.27 1.3-1.27 1.3l-.97.08s-1.82-.97-2.93-1.41c0 0 1.72-2.65 2.12-3.4.3.18 3.05 1.9 3.05 3.43ZM24.43 6.3c2.15 0 5.23 5.1 5.23 5.1s-4.8-.44-8.76 1.89c.1-3.67 1.34-7 3.52-7Zm-8.56 4.13c.69 0 1.36.83 1.64 1.54 0 .47.24 3.2.24 3.2l-3.96-.16c0-3.57 1.4-4.58 2.08-4.58Zm-.4 24.8c-2.17 0-2.62-1.2-2.62-2.29 0-2.45 1.96-5.9 1.96-5.9s2.2 4.63 6.04 6.59a10.02 10.02 0 0 1-5.39 1.6Zm7.02 4.85c-1.52 0-1.7-.98-1.7-1.21 0-.7.55-1.54.55-1.54s2.55-1.73 2.71-1.91l1.89 3.52s-1.93 1.14-3.45 1.14Zm4.74-1.92c-.93-1.62-1.6-3.3-1.6-3.3s3.78.24 5.82-1.86a11.2 11.2 0 0 1-5.65 1.07c4.93-4.34 7.8-7.48 10.23-10.74a9.46 9.46 0 0 0-1.6-1.15c-1.46 1.76-7.16 7.86-12.45 10.88-6.69-3.64-8.09-14.38-8.23-16.6l3.65.34s-1.37 2.44-1.37 4.23c0 1.79.21 1.89.21 1.89s-.04-3.13 1.89-5.54c1.46 7.82 3 11.83 4.19 14.22.6-.25 1.74-.76 1.74-.76s-3.38-9.73-3.19-16.31a13.8 13.8 0 0 1 6.36-1.66c6.73 0 12.14 2.9 12.14 2.9l-2.12 2.95s-1.89-3.42-4.55-4.03c1.4 1.05 2.98 2.44 3.8 4.43a68.4 68.4 0 0 0-14.47-3.59c-.19.8-.17 1.94-.17 1.94s9.03 1.66 15.6 5.43c-.05 8.21-9 14.53-10.23 15.26Zm8.55-6.14s2.8-3.68 2.76-8.55c0 0 4.52 2.8 4.52 5.54 0 3.05-7.28 3-7.28 3Z\"/></svg>"),
Rules: []string{
"||battle.net^",
"||battlenet.com.cn^",
"||bnet.163.com^",
"||bnet.cn^",
},
}, { }, {
ID: "bilibili", ID: "bilibili",
Name: "Bilibili", Name: "Bilibili",
@ -283,6 +312,21 @@ var blockedServices = []blockedService{{
"||mincdn.com^", "||mincdn.com^",
"||yo9.com^", "||yo9.com^",
}, },
}, {
ID: "blizzard_entertainment",
Name: "Blizzard Entertainment",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 -32 128 128\"><path fill-rule=\"evenodd\" d=\"M105 2h3v1h2l2 1 1 1h3l1 1h4l1 1 2 2v1l1 3v4l1 2v6l-1 2v2l-1 3v2l-1 2v14l-1 2v1l-1 3-1 1h-3l-1 1h-6a5 5 0 0 0 1-6l2-1h-1l-1-3v-3a350 350 0 0 1 0-8l-1-3v-1l-1-1V9h1V6l-1-1-4-3Zm9 13v10h1v25a8 8 0 0 0 2-4l1-1 1-3V30l1-1v-2l1-1v-5l-1-2-2-3-1-1h-3Z\" clip-rule=\"evenodd\"/><path fill-rule=\"evenodd\" d=\"M101 24v1l2 1h1v2h1l1 2v5l1 2s0-1 0 0l1 7 1 2v7l-1 5h-2l-2-2-4-1 1-3 1-2a22 22 0 0 0-1-10l-1-4h-1l1-4-1-1-2-3v2l-1 1v3l1 6v4l1 1-1 3v4l1 1-1 2v4l-1-1a13 13 0 0 0-4-5l-2-2 2-5V27l-1-1v-4l-1-1v-5h-1a33 33 0 0 1 0-4l4-4h-2l-4-4h-1V3h10l2 1 2 1h1c2 0 2 1 3 2l2 3 1 3v1l-1 2v1a11 11 0 0 1-1 4l-4 3ZM96 9v13l1 1a3 3 0 0 0 1-1c1 0 2-1 2-3v-1l1-1v-3l-2-3-2-2h-1ZM26 3l1 1h1l2 3v5l1 1v2l-1 1v9l1 1 1 1-1 7v9l-1 1 1 1-1 1v8h3l1-1h7v-1h16v6l1 2h-6l-1-1h-2l-1-1H31a4 4 0 0 0-3-1l-1 1h-1l-1 1h-5l1-1a10 10 0 0 0 3-2v-9l1-1-1-1V35l1-1V21l-1-1v-4l1-1v-3l1-2-1-3h-1l-2-2-1-1 1-1h4Z\" clip-rule=\"evenodd\"/><path fill-rule=\"evenodd\" d=\"M84 60v-3l-1-2v-4l-3-2v-1l1-2a11 11 0 0 0 2-6l-1-1-3-2h-2v3l1 1h1l-1 2h-4l-2 1-2 1-1-2v-1l1-1 1-1 1-2v-5l1-1v-6l1-1v-3l1-1v-3l1-2 1-1-1-1 1-1 1-3 1-1V7l1-1c1-1 0-4 2-3l1 3 1 1 1 2v1l1 5 1 3v2l1 1v2l1 1v8l1 3v9l-1 1-2 5v3l-1 2v4l-1 1h-1Zm-4-36-1 1v2l-1 2v4l4 1h2v-7l-1-3-2-1-1-1v2Z\" clip-rule=\"evenodd\"/><path fill-rule=\"evenodd\" d=\"M77 4v1l-2 3v2l-1 2v1l-1 1-1 4v7h-1v2a5 5 0 0 1-1 2v2l-2 2v7l-1 2v2l-2 4v3-1h3v-1l3-1 1-1 3-2h3l1 1-1 1a3 3 0 0 0 0 1l1 1v5l-1 1h-7v-1h-2l-2 1h-4l-2 1-1-2v-2l1-1v-1l1-1-1-1 1-1v-2l1-2-1-2v-8l1-2 2-5-1-1 1-2v-1l1-1v-4l1-1 2-4v-2l1-1h-3V8h-1l-1 1-2 3-1 4h-1l-1-1v-2l1-1V4h16ZM32 4h9l1 2-3 2 1 2-1 1v13l1 2-1 2v6l-1 1v2l1 1v5l-1 1 1 2 1 1 2 1v2h-7l-2 1-1-1 3-2v-8a4 4 0 0 1 0-2l1-1v-3l-1-14v-2l1-1h-1V7l-2-1h-1l-1-1 1-1Zm12 0h14v15c-2 1-2 4-3 6v2c-1 0-3 1-2 4h-1l-2 3-1 2v3l-1 2-2 5h2l1-1h2l1-1c1-1 1-3 3-3l1-2 2-2h1l1 3h-1v1l-1 1v7h-8l-1 1-2-1h-3l-1-1 1-1v-3l-1-2 1-1-1-1 1-3v-2l1-2 1-3a7 7 0 0 1 2-4l1-4 2-2 2-3v-3h1l2-3V8l-3-1h-2l-1 1a3 3 0 0 0-2 3l-1 1v4l-1 1-1 1v-1l-1-1V4ZM17 22l1 1h1v3s0-1 0 0l2 1v5l1 2-1 8v3a6 6 0 0 1 0 2l-1 2-1 2-1 3-3 2-2 2-3 1-1-1-1 1H1l-1-1 2-1 1-4V26l1-1-1-3V11l1-1-1-1H2V8L1 7 0 6V5l1-1h15l1 1c2 0 3 1 3 2l1 3v6l-4 6Zm-6-11v9h1l1-2 2-1v-6h-1l-1-1h-2v1Zm0 19-1 1 1 2-1 3v9a2 2 0 0 0 0 1v6l-1 1 3-1 1-2h1v-4l1-4v-5l-1-1 1-3-1-1v-2s0 1 0 0v-1l-1-1a20 20 0 0 1-2-2v4Z\" clip-rule=\"evenodd\"/></svg>"),
Rules: []string{
"||battle.net^",
"||battlenet.com.cn^",
"||blizzard.cn^",
"||blizzardgames.cn^",
"||blz-contentstack.com^",
"||blzstatic.cn^",
"||bnet.163.com^",
"||bnet.cn^",
"||lizzard.com^",
},
}, { }, {
ID: "cloudflare", ID: "cloudflare",
Name: "CloudFlare", Name: "CloudFlare",
@ -319,6 +363,14 @@ var blockedServices = []blockedService{{
"||warp.plus^", "||warp.plus^",
"||workers.dev^", "||workers.dev^",
}, },
}, {
ID: "clubhouse",
Name: "Clubhouse",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M29.8 4a1 1 0 0 0-.92.7 1 1 0 0 0 .36 1.1 31.2 31.2 0 0 1 6 6.02 1 1 0 1 0 1.6-1.2 33.2 33.2 0 0 0-6.4-6.4A1 1 0 0 0 29.8 4Zm-7.16 1.06c-.46 0-.87.3-.99.74a1 1 0 0 0 .5 1.15 31.13 31.13 0 0 1 11.13 10.6 1 1 0 1 0 1.7-1.07A33.12 33.12 0 0 0 23.11 5.2a.96.96 0 0 0-.48-.14ZM14.5 7.01a3.42 3.42 0 0 0-3.27 2.28l-.26-.27A3.49 3.49 0 0 0 8.5 8.01c-.9 0-1.8.34-2.48 1.01a3.51 3.51 0 0 0-.57 4.17c-.52.15-1.01.42-1.43.84a3.52 3.52 0 0 0 0 4.94l.27.27c-.46.16-.9.41-1.27.79a3.52 3.52 0 0 0 0 4.94l.88.88 16.47 16.47a9.01 9.01 0 0 0 12.72 0l4.23-4.22a9.94 9.94 0 0 0 2.3-3.59l2.63-7.08a8.03 8.03 0 0 1 1.84-2.87l1.74-1.73 1-1a4.02 4.02 0 0 0 0-5.66 4.02 4.02 0 0 0-5.66 0l-1 1-.7.71-4.2 4.2a2.98 2.98 0 0 1-4.24 0L17.9 8.96l-.94-.94a3.49 3.49 0 0 0-2.47-1.01Zm0 1.98c.38 0 .76.15 1.06.45l.94.94 13.1 13.1a5.02 5.02 0 0 0 7.08 0l4.2-4.18.7-.71 1-1c.8-.8 2.05-.8 2.83 0 .8.79.8 2.04 0 2.83l-2.73 2.73a10.03 10.03 0 0 0-2.3 3.58l-2.63 7.08a8.02 8.02 0 0 1-1.84 2.87l-4.23 4.23a6.99 6.99 0 0 1-9.9 0L4.44 23.56a1.5 1.5 0 0 1 0-2.12c.59-.59 1.45-.55 2.08.08l.1.09 8.2 8.37a1 1 0 0 0 .97.29 1 1 0 0 0 .46-1.68l-9.52-9.73-.01-.01-1.28-1.29a1.5 1.5 0 0 1 0-2.12c.6-.6 1.47-.58 2.08.03l9.18 9.17a1 1 0 0 0 1.69-.43 1 1 0 0 0-.28-.98L9 14.13l-.06-.07-1.5-1.5c-.6-.6-.6-1.53 0-2.12a1.5 1.5 0 0 1 2.12 0L20.8 21.67a1 1 0 0 0 1.68-.44 1 1 0 0 0-.27-.97l-8.7-8.7-.06-.06a1.4 1.4 0 0 1-.01-2.06c.3-.3.68-.45 1.06-.45ZM4.23 32a1 1 0 0 0-.82 1.51c3 5.18 7.36 9.46 12.59 12.37a1 1 0 0 0 1.51-.89 1 1 0 0 0-.54-.86A31.16 31.16 0 0 1 5.15 32.5a1.01 1.01 0 0 0-.92-.51Z\"/></svg>"),
Rules: []string{
"||clubhouse.com^",
"||clubhouseapi.com^",
},
}, { }, {
ID: "crunchyroll", ID: "crunchyroll",
Name: "Crunchyroll", Name: "Crunchyroll",
@ -726,6 +778,18 @@ var blockedServices = []blockedService{{
"||xxbay.com^", "||xxbay.com^",
"||yibei.org^", "||yibei.org^",
}, },
}, {
ID: "electronic_arts",
Name: "Electronic Arts",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 1000 1000\"><path d=\"M500 1000C224.3 1000 0 775.7 0 500S224.3 0 500 0s500 224.3 500 500-224.3 500-500 500zm84.63-693.4H302.05l-42.87 68.9h282.25zm57.75.66L469.63 582.33H278.02l44.2-68.96h114.85l43.87-68.93h-265.5l-43.86 68.93h62.9L147.2 651.05h364.2L645.9 438.9l49.05 74.46h-44.23l-41.88 68.96H739.8l45.48 68.72h83.54z\"/></svg>"),
Rules: []string{
"||ea.com^",
"||eamobile.com^",
"||easports.com^",
"||nearpolar.com^",
"||swtor.com^",
"||tnt-ea.com^",
},
}, { }, {
ID: "epic_games", ID: "epic_games",
Name: "Epic Games", Name: "Epic Games",
@ -1390,11 +1454,39 @@ var blockedServices = []blockedService{{
"||line-apps.com^", "||line-apps.com^",
"||line-cdn.net^", "||line-cdn.net^",
"||line-scdn.net^", "||line-scdn.net^",
"||line.biz^",
"||line.me^", "||line.me^",
"||line.naver.jp^", "||line.naver.jp^",
"||linecorp.com^", "||linecorp.com^",
"||linefriends.com.tw^",
"||linefriends.com^",
"||linegame.jp^",
"||linemobile.com^",
"||linemyshop.com^", "||linemyshop.com^",
"||lineshoppingseller.com^", "||lineshoppingseller.com^",
"||linetv.tw^",
},
}, {
ID: "linkedin",
Name: "LinkedIn",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M41,4H9C6.24,4,4,6.24,4,9v32c0,2.76,2.24,5,5,5h32c2.76,0,5-2.24,5-5V9C46,6.24,43.76,4,41,4z M17,20v19h-6V20H17z M11,14.47c0-1.4,1.2-2.47,3-2.47s2.93,1.07,3,2.47c0,1.4-1.12,2.53-3,2.53C12.2,17,11,15.87,11,14.47z M39,39h-6c0,0,0-9.26,0-10 c0-2-1-4-3.5-4.04h-0.08C27,24.96,26,27.02,26,29c0,0.91,0,10,0,10h-6V20h6v2.56c0,0,1.93-2.56,5.81-2.56 c3.97,0,7.19,2.73,7.19,8.26V39z\"/></svg>"),
Rules: []string{
"||bizographics.com^",
"||cs1404.wpc.epsiloncdn.net^",
"||cs767.wpc.epsiloncdn.net^",
"||l-0005.dc-msedge.net^",
"||l-0005.l-dc-msedge.net^",
"||l-0005.l-msedge.net^",
"||l-0015.l-msedge.net^",
"||licdn.cn^",
"||licdn.com^",
"||linkedin.at^",
"||linkedin.be^",
"||linkedin.cn^",
"||linkedin.com^",
"||linkedin.nl^",
"||linkedin.qtlcdn.com^",
"||lnkd.in^",
}, },
}, { }, {
ID: "mail_ru", ID: "mail_ru",
@ -1438,7 +1530,6 @@ var blockedServices = []blockedService{{
"||masto.pt^", "||masto.pt^",
"||mastodon.au^", "||mastodon.au^",
"||mastodon.bida.im^", "||mastodon.bida.im^",
"||mastodon.com.tr^",
"||mastodon.eus^", "||mastodon.eus^",
"||mastodon.green^", "||mastodon.green^",
"||mastodon.ie^", "||mastodon.ie^",
@ -1454,7 +1545,7 @@ var blockedServices = []blockedService{{
"||mastodon.social^", "||mastodon.social^",
"||mastodon.uno^", "||mastodon.uno^",
"||mastodon.world^", "||mastodon.world^",
"||mastodon.xyz^", "||mastodon.zaclys.com^",
"||mastodonapp.uk^", "||mastodonapp.uk^",
"||mastodonners.nl^", "||mastodonners.nl^",
"||mastodont.cat^", "||mastodont.cat^",
@ -1465,12 +1556,12 @@ var blockedServices = []blockedService{{
"||metalhead.club^", "||metalhead.club^",
"||mindly.social^", "||mindly.social^",
"||mstdn.ca^", "||mstdn.ca^",
"||mstdn.jp^",
"||mstdn.party^", "||mstdn.party^",
"||mstdn.plus^", "||mstdn.plus^",
"||mstdn.social^", "||mstdn.social^",
"||muenchen.social^", "||muenchen.social^",
"||newsie.social^", "||muenster.im^",
"||nerdculture.de^",
"||noc.social^", "||noc.social^",
"||norden.social^", "||norden.social^",
"||nrw.social^", "||nrw.social^",
@ -1498,16 +1589,17 @@ 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^",
"||toot.io^", "||toot.io^",
"||toot.wales^", "||toot.wales^",
"||troet.cafe^", "||troet.cafe^",
"||twingyeo.kr^",
"||union.place^", "||union.place^",
"||universeodon.com^", "||universeodon.com^",
"||urbanists.social^", "||urbanists.social^",
"||wien.rocks^",
"||wxw.moe^", "||wxw.moe^",
}, },
}, { }, {
@ -1550,6 +1642,44 @@ var blockedServices = []blockedService{{
"||nflxso.net^", "||nflxso.net^",
"||nflxvideo.net^", "||nflxvideo.net^",
}, },
}, {
ID: "nintendo",
Name: "Nintendo",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M6 7v36h12.6V21.75l13 20.78.27.47H44V7H31.4v1l.04 20.22L18.5 7.47 18.22 7Zm2 2h9.1l14.5 23.22 1.84 3v-3.5L33.4 9H42v32h-9L18.44 17.75l-1.85-2.94V41H8Z\"/></svg>"),
Rules: []string{
"||nintendo-europe.com^",
"||nintendo.be^",
"||nintendo.co.jp^",
"||nintendo.co.uk^",
"||nintendo.com.au^",
"||nintendo.com^",
"||nintendo.de^",
"||nintendo.es^",
"||nintendo.eu^",
"||nintendo.fr^",
"||nintendo.it^",
"||nintendo.jp^",
"||nintendo.net^",
"||nintendo.nl^",
"||nintendoswitch.cn^",
"||nintendowifi.net^",
},
}, {
ID: "nvidia",
Name: "Nvidia",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 48 48\"><path d=\"M20 8a2 2 0 0 0-2 2v2.55l.84-.05c10.76-.37 17.78 8.82 17.78 8.82s-8.05 9.8-16.44 9.8c-.73 0-1.47-.07-2.18-.19v-2.2c.73.23 1.52.35 2.3.35 5.88 0 11.35-7.6 11.35-7.6s-5.07-6.91-12.81-6.66l-.82.03v-2.3c-9.49.77-17.68 8.8-17.68 8.8S4.97 34.76 18 35.98v-2.44c.59.07 1.22.12 1.81.12 7.82 0 13.47-3.99 18.94-8.7.91.73 4.62 2.49 5.4 3.26-5.2 4.36-17.33 7.86-24.2 7.86-.66 0-1.32-.03-1.95-.1V38c0 1.1.9 2 2 2h25a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H20zm-2 6.86v2.82a11.8 11.8 0 0 1 1.57-.07c4.95 0 7.9 3.85 7.9 3.85l-4.03 3.39c-1.8-3.02-2.43-4.35-5.44-4.7v8.57c-4.06-1.38-5.4-6.14-5.4-6.14s2.37-2.83 5.38-2.46H18v-2.44a15.66 15.66 0 0 0-9.22 4.46s2 7.52 9.22 8.8v2.6c-9.56-1.17-12.82-11.7-12.82-11.7s4.27-6.3 12.82-6.97z\"/></svg>"),
Rules: []string{
"||geforce.com^",
"||geforcenow.com^",
"||nvidia.cn^",
"||nvidia.com.global.ogslb.com^",
"||nvidia.com^",
"||nvidia.eu^",
"||nvidia.partners^",
"||nvidiagrid.net^",
"||nvidianews.com^",
"||tegrazone.com^",
},
}, { }, {
ID: "ok", ID: "ok",
Name: "OK.ru", Name: "OK.ru",
@ -1704,6 +1834,14 @@ var blockedServices = []blockedService{{
"||robloxcdn.com^", "||robloxcdn.com^",
"||robloxdev.cn^", "||robloxdev.cn^",
}, },
}, {
ID: "rockstar_games",
Name: "Rockstar games",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M12 3c-4.96 0-9 4.04-9 9v26c0 4.96 4.04 9 9 9h26c4.96 0 9-4.04 9-9V12c0-4.96-4.04-9-9-9H12zm0 2h26c3.88 0 7 3.12 7 7v26c0 3.88-3.12 7-7 7H12c-3.88 0-7-3.12-7-7V12c0-3.88 3.12-7 7-7zm3.72 5a1 1 0 0 0-.97.79l-3.87 18a1 1 0 0 0 .98 1.21h4.27a1 1 0 0 0 .97-.79L18.47 23h2.07c.94 0 1.12.15 1.36.73.24.57.3 1.76.1 3.4-.08.68-.05 1.22.02 1.6v.03a1 1 0 0 0 .3.97l3.37 3.12-2.6 5.74a1 1 0 0 0 1.43 1.26l5.58-3.39 4.29 3.33a1 1 0 0 0 1.6-.98l-1.09-5.56 4.7-3.47a1 1 0 0 0-.6-1.8h-4.86l-.82-5.14a1 1 0 0 0-.98-.84 1 1 0 0 0-.88.51l-2.77 5a14.3 14.3 0 0 1 .06-2.83c.15-1.48.01-2.64-.18-3.45-.06-.28-.08-.25-.15-.45.3-.17.4-.13.77-.5.8-.8 1.6-2.18 1.75-4.26.17-2.26-.55-3.98-1.92-4.9C27.65 10.17 25.91 10 24 10h-8.28zm.81 2H24c1.75 0 3.13.25 3.9.77.76.52 1.18 1.27 1.05 3.1-.13 1.67-.69 2.51-1.17 3a2 2 0 0 1-.82.56 1 1 0 0 0-.6 1.44s.12.21.27.82c.14.6.26 1.53.13 2.79a14.24 14.24 0 0 0-.01 3.52h-2.76c-.01-.19-.04-.32 0-.62.22-1.78.25-3.21-.24-4.42A3.38 3.38 0 0 0 20.54 21h-2.87a1 1 0 0 0-.98.78L15.32 28H13.1l3.44-16zm2.76 1.03a1 1 0 0 0-.98.8l-.98 4.94a1 1 0 0 0 .98 1.2h4.47c.79 0 1.65-.12 2.44-.58a3.6 3.6 0 0 0 1.68-2.41 3.3 3.3 0 0 0-.72-2.92 3.35 3.35 0 0 0-2.47-1.03h-4.42zm.82 2h3.6c.41 0 .79.16 1 .4.22.22.36.52.23 1.15-.13.62-.36.88-.72 1.08a3 3 0 0 1-1.44.3h-3.25l.58-2.93zm11.7 10.99.49 3.11a1 1 0 0 0 .98.84h2.69l-2.76 2.05a1 1 0 0 0-.4 1l.7 3.56-2.73-2.12a1 1 0 0 0-1.13-.07l-3.4 2.07 1.56-3.44a1 1 0 0 0-.23-1.15L25.55 30H29a1 1 0 0 0 .88-.51l1.92-3.47z\"/></svg>"),
Rules: []string{
"||rockstargames.com^",
"||rsg.sc^",
},
}, { }, {
ID: "shopee", ID: "shopee",
Name: "Shopee", Name: "Shopee",
@ -1940,6 +2078,16 @@ var blockedServices = []blockedService{{
"||twvid.com^", "||twvid.com^",
"||vine.co^", "||vine.co^",
}, },
}, {
ID: "ubisoft",
Name: "Ubisoft",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 32 32\"><path d=\"M15.22 3C7.14 3 3.66 10.18 3.66 10.18l1.03.74s-1.3 2.45-1.26 5.6A12.5 12.5 0 0 0 16.08 29a12.5 12.5 0 0 0 12.49-12.46c0-9-6.98-13.54-13.35-13.54zm.07 2.2c6.3 0 11.2 5.07 11.2 10.98 0 6.27-4.71 10.62-10.2 10.62-4.04 0-7.69-3.08-7.69-7.3a5.8 5.8 0 0 1 2.75-5.03l.21.23a6.37 6.37 0 0 0-1.53 3.91c0 3.32 2.6 5.62 5.88 5.62 4.18 0 6.97-3.56 6.97-7.7 0-4.81-4.25-8.9-9.36-8.9a11.1 11.1 0 0 0-6.61 2.3l-.21-.2a10.07 10.07 0 0 1 8.59-4.54zM13.4 9.8c3.26 0 6.44 2.15 7.24 5.22l-.3.1a8.35 8.35 0 0 0-6.52-3.44c-5.08 0-7.75 4.62-7.36 8.47l-.3.12s-.56-1.24-.56-2.71a7.8 7.8 0 0 1 7.8-7.76zm2.15 5.33a2.77 2.77 0 0 1 2.78 2.74c0 1.23-.79 1.96-.79 1.96l.94.65s-.93 1.46-2.82 1.46a3.4 3.4 0 0 1-.1-6.8z\"/></svg>"),
Rules: []string{
"||ubi.com^",
"||ubisoft.com^",
"||ubisoft.org^",
"||ubisoftconnect.com^",
},
}, { }, {
ID: "valorant", ID: "valorant",
Name: "Valorant", Name: "Valorant",

View file

@ -7,6 +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" "github.com/AdguardTeam/golibs/stringutil"
) )
@ -22,12 +23,14 @@ type Client struct {
safeSearchConf filtering.SafeSearchConfig safeSearchConf filtering.SafeSearchConfig
SafeSearch filtering.SafeSearch SafeSearch filtering.SafeSearch
// BlockedServices is the configuration of blocked services of a client.
BlockedServices *filtering.BlockedServices
Name string Name string
IDs []string IDs []string
Tags []string Tags []string
BlockedServices []string Upstreams []string
Upstreams []string
UseOwnSettings bool UseOwnSettings bool
FilteringEnabled bool FilteringEnabled bool
@ -43,9 +46,9 @@ type Client struct {
func (c *Client) ShallowClone() (sh *Client) { func (c *Client) ShallowClone() (sh *Client) {
clone := *c clone := *c
clone.BlockedServices = c.BlockedServices.Clone()
clone.IDs = stringutil.CloneSlice(c.IDs) clone.IDs = stringutil.CloneSlice(c.IDs)
clone.Tags = stringutil.CloneSlice(c.Tags) clone.Tags = stringutil.CloneSlice(c.Tags)
clone.BlockedServices = stringutil.CloneSlice(c.BlockedServices)
clone.Upstreams = stringutil.CloneSlice(c.Upstreams) clone.Upstreams = stringutil.CloneSlice(c.Upstreams)
return &clone return &clone
@ -127,14 +130,13 @@ 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 {
WHOISInfo *RuntimeClientWHOISInfo // WHOIS is the filtered WHOIS data of a client.
Host string WHOIS *whois.Info
Source clientSource
}
// RuntimeClientWHOISInfo is the filtered WHOIS data for a runtime client. // Host is the host name of a client.
type RuntimeClientWHOISInfo struct { Host string
City string `json:"city,omitempty"`
Country string `json:"country,omitempty"` // Source is the source from which the information about the client has
Orgname string `json:"orgname,omitempty"` // been obtained.
Source clientSource
} }

View file

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

View file

@ -9,25 +9,26 @@ import (
"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/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// newClientsContainer is a helper that creates a new clients container for // newClientsContainer is a helper that creates a new clients container for
// tests. // tests.
func newClientsContainer() (c *clientsContainer) { func newClientsContainer(t *testing.T) (c *clientsContainer) {
c = &clientsContainer{ c = &clientsContainer{
testing: true, testing: true,
} }
c.Init(nil, nil, nil, nil, &filtering.Config{}) err := c.Init(nil, nil, nil, nil, &filtering.Config{})
require.NoError(t, err)
return c return c
} }
func TestClients(t *testing.T) { func TestClients(t *testing.T) {
clients := newClientsContainer() clients := newClientsContainer(t)
t.Run("add_success", func(t *testing.T) { t.Run("add_success", func(t *testing.T) {
var ( var (
@ -198,8 +199,8 @@ func TestClients(t *testing.T) {
} }
func TestClientsWHOIS(t *testing.T) { func TestClientsWHOIS(t *testing.T) {
clients := newClientsContainer() clients := newClientsContainer(t)
whois := &RuntimeClientWHOISInfo{ whois := &whois.Info{
Country: "AU", Country: "AU",
Orgname: "Example Org", Orgname: "Example Org",
} }
@ -210,7 +211,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.WHOISInfo, whois) assert.Equal(t, rc.WHOIS, whois)
}) })
t.Run("existing_auto-client", func(t *testing.T) { t.Run("existing_auto-client", func(t *testing.T) {
@ -222,7 +223,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.WHOISInfo, whois) assert.Equal(t, rc.WHOIS, whois)
}) })
t.Run("can't_set_manually-added", func(t *testing.T) { t.Run("can't_set_manually-added", func(t *testing.T) {
@ -244,7 +245,7 @@ func TestClientsWHOIS(t *testing.T) {
} }
func TestClientsAddExisting(t *testing.T) { func TestClientsAddExisting(t *testing.T) {
clients := newClientsContainer() clients := newClientsContainer(t)
t.Run("simple", func(t *testing.T) { t.Run("simple", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.1") ip := netip.MustParseAddr("1.1.1.1")
@ -316,7 +317,7 @@ func TestClientsAddExisting(t *testing.T) {
} }
func TestClientsCustomUpstream(t *testing.T) { func TestClientsCustomUpstream(t *testing.T) {
clients := newClientsContainer() clients := newClientsContainer(t)
// Add client with upstreams. // Add client with upstreams.
ok, err := clients.Add(&Client{ ok, err := clients.Add(&Client{

View file

@ -9,6 +9,8 @@ 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/schedule"
"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
@ -28,7 +30,8 @@ type clientJSON struct {
// the allowlist. // the allowlist.
DisallowedRule *string `json:"disallowed_rule,omitempty"` DisallowedRule *string `json:"disallowed_rule,omitempty"`
WHOISInfo *RuntimeClientWHOISInfo `json:"whois_info,omitempty"` // WHOIS is the filtered WHOIS data of a client.
WHOIS *whois.Info `json:"whois_info,omitempty"`
SafeSearchConf *filtering.SafeSearchConfig `json:"safe_search"` SafeSearchConf *filtering.SafeSearchConfig `json:"safe_search"`
Name string `json:"name"` Name string `json:"name"`
@ -51,7 +54,7 @@ type clientJSON struct {
} }
type runtimeClientJSON struct { type runtimeClientJSON struct {
WHOISInfo *RuntimeClientWHOISInfo `json:"whois_info"` WHOIS *whois.Info `json:"whois_info"`
IP netip.Addr `json:"ip"` IP netip.Addr `json:"ip"`
Name string `json:"name"` Name string `json:"name"`
@ -78,7 +81,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{
WHOISInfo: rc.WHOISInfo, WHOIS: rc.WHOIS,
Name: rc.Host, Name: rc.Host,
Source: rc.Source, Source: rc.Source,
@ -116,15 +119,24 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C
} }
} }
weekly := schedule.EmptyWeekly()
if prev != nil {
weekly = prev.BlockedServices.Schedule.Clone()
}
c = &Client{ c = &Client{
safeSearchConf: safeSearchConf, safeSearchConf: safeSearchConf,
Name: cj.Name, Name: cj.Name,
IDs: cj.IDs, BlockedServices: &filtering.BlockedServices{
Tags: cj.Tags, Schedule: weekly,
BlockedServices: cj.BlockedServices, IDs: cj.BlockedServices,
Upstreams: cj.Upstreams, },
IDs: cj.IDs,
Tags: cj.Tags,
Upstreams: cj.Upstreams,
UseOwnSettings: !cj.UseGlobalSettings, UseOwnSettings: !cj.UseGlobalSettings,
FilteringEnabled: cj.FilteringEnabled, FilteringEnabled: cj.FilteringEnabled,
@ -178,7 +190,8 @@ func clientToJSON(c *Client) (cj *clientJSON) {
SafeBrowsingEnabled: c.SafeBrowsingEnabled, SafeBrowsingEnabled: c.SafeBrowsingEnabled,
UseGlobalBlockedServices: !c.UseOwnBlockedServices, UseGlobalBlockedServices: !c.UseOwnBlockedServices,
BlockedServices: c.BlockedServices,
BlockedServices: c.BlockedServices.IDs,
Upstreams: c.Upstreams, Upstreams: c.Upstreams,
@ -344,16 +357,16 @@ 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,
WHOISInfo: &RuntimeClientWHOISInfo{}, WHOIS: &whois.Info{},
} }
return cj return cj
} }
cj = &clientJSON{ cj = &clientJSON{
Name: rc.Host, Name: rc.Host,
IDs: []string{idStr}, IDs: []string{idStr},
WHOISInfo: rc.WHOISInfo, WHOIS: rc.WHOIS,
} }
disallowed, rule := clients.dnsServer.IsBlockedClient(ip, idStr) disallowed, rule := clients.dnsServer.IsBlockedClient(ip, idStr)

View file

@ -14,6 +14,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/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/schedule"
"github.com/AdguardTeam/AdGuardHome/internal/stats" "github.com/AdguardTeam/AdGuardHome/internal/stats"
"github.com/AdguardTeam/dnsproxy/fastip" "github.com/AdguardTeam/dnsproxy/fastip"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
@ -90,18 +91,17 @@ type clientSourcesConfig struct {
HostsFile bool `yaml:"hosts"` HostsFile bool `yaml:"hosts"`
} }
// configuration is loaded from YAML // configuration is loaded from YAML.
// field ordering is important -- yaml fields will mirror ordering from here //
// Field ordering is important, YAML fields better not to be reordered, if it's
// not absolutely necessary.
type configuration struct { type configuration struct {
// Raw file data to avoid re-reading of configuration file // Raw file data to avoid re-reading of configuration file
// It's reset after config is parsed // It's reset after config is parsed
fileData []byte fileData []byte
// BindHost is the address for the web interface server to listen on. // HTTPConfig is the block with http conf.
BindHost netip.Addr `yaml:"bind_host"` HTTPConfig httpConfig `yaml:"http"`
// BindPort is the port for the web interface server to listen on.
BindPort int `yaml:"bind_port"`
// Users are the clients capable for accessing the web interface. // Users are the clients capable for accessing the web interface.
Users []webUser `yaml:"users"` Users []webUser `yaml:"users"`
// AuthAttempts is the maximum number of failed login attempts a user // AuthAttempts is the maximum number of failed login attempts a user
@ -119,10 +119,6 @@ type configuration struct {
// DebugPProf defines if the profiling HTTP handler will listen on :6060. // DebugPProf defines if the profiling HTTP handler will listen on :6060.
DebugPProf bool `yaml:"debug_pprof"` DebugPProf bool `yaml:"debug_pprof"`
// TTL for a web session (in hours)
// An active session is automatically refreshed once a day.
WebSessionTTLHours uint32 `yaml:"web_session_ttl"`
DNS dnsConfig `yaml:"dns"` DNS dnsConfig `yaml:"dns"`
TLS tlsConfigSettings `yaml:"tls"` TLS tlsConfigSettings `yaml:"tls"`
QueryLog queryLogConfig `yaml:"querylog"` QueryLog queryLogConfig `yaml:"querylog"`
@ -155,7 +151,23 @@ type configuration struct {
SchemaVersion int `yaml:"schema_version"` // keeping last so that users will be less tempted to change it -- used when upgrading between versions SchemaVersion int `yaml:"schema_version"` // keeping last so that users will be less tempted to change it -- used when upgrading between versions
} }
// field ordering is important -- yaml fields will mirror ordering from here // httpConfig is a block with HTTP configuration params.
//
// Field ordering is important, YAML fields better not to be reordered, if it's
// not absolutely necessary.
type httpConfig struct {
// Address is the address to serve the web UI on.
Address netip.AddrPort
// SessionTTL for a web session.
// An active session is automatically refreshed once a day.
SessionTTL timeutil.Duration `yaml:"session_ttl"`
}
// dnsConfig is a block with DNS configuration params.
//
// Field ordering is important, YAML fields better not to be reordered, if it's
// not absolutely necessary.
type dnsConfig struct { type dnsConfig struct {
BindHosts []netip.Addr `yaml:"bind_hosts"` BindHosts []netip.Addr `yaml:"bind_hosts"`
Port int `yaml:"port"` Port int `yaml:"port"`
@ -260,11 +272,12 @@ type statsConfig struct {
// //
// TODO(a.garipov, e.burkov): This global is awful and must be removed. // TODO(a.garipov, e.burkov): This global is awful and must be removed.
var config = &configuration{ var config = &configuration{
BindPort: 3000, AuthAttempts: 5,
BindHost: netip.IPv4Unspecified(), AuthBlockMin: 15,
AuthAttempts: 5, HTTPConfig: httpConfig{
AuthBlockMin: 15, Address: netip.AddrPortFrom(netip.IPv4Unspecified(), 3000),
WebSessionTTLHours: 30 * 24, SessionTTL: timeutil.Duration{Duration: 30 * timeutil.Day},
},
DNS: dnsConfig{ DNS: dnsConfig{
BindHosts: []netip.Addr{netip.IPv4Unspecified()}, BindHosts: []netip.Addr{netip.IPv4Unspecified()},
Port: defaultPortDNS, Port: defaultPortDNS,
@ -316,6 +329,11 @@ var config = &configuration{
Yandex: true, Yandex: true,
YouTube: true, YouTube: true,
}, },
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
IDs: []string{},
},
}, },
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout}, UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
UsePrivateRDNS: true, UsePrivateRDNS: true,
@ -421,8 +439,8 @@ func readLogSettings() (ls *logSettings) {
// validateBindHosts returns error if any of binding hosts from configuration is // validateBindHosts returns error if any of binding hosts from configuration is
// not a valid IP address. // not a valid IP address.
func validateBindHosts(conf *configuration) (err error) { func validateBindHosts(conf *configuration) (err error) {
if !conf.BindHost.IsValid() { if !conf.HTTPConfig.Address.IsValid() {
return errors.Error("bind_host is not a valid ip address") return errors.Error("http.address is not a valid ip address")
} }
for i, addr := range conf.DNS.BindHosts { for i, addr := range conf.DNS.BindHosts {
@ -456,7 +474,7 @@ func parseConfig() (err error) {
} }
tcpPorts := aghalg.UniqChecker[tcpPort]{} tcpPorts := aghalg.UniqChecker[tcpPort]{}
addPorts(tcpPorts, tcpPort(config.BindPort)) addPorts(tcpPorts, tcpPort(config.HTTPConfig.Address.Port()))
udpPorts := aghalg.UniqChecker[udpPort]{} udpPorts := aghalg.UniqChecker[udpPort]{}
addPorts(udpPorts, udpPort(config.DNS.Port)) addPorts(udpPorts, udpPort(config.DNS.Port))

View file

@ -103,7 +103,7 @@ type statusResponse struct {
Language string `json:"language"` Language string `json:"language"`
DNSAddrs []string `json:"dns_addresses"` DNSAddrs []string `json:"dns_addresses"`
DNSPort int `json:"dns_port"` DNSPort int `json:"dns_port"`
HTTPPort int `json:"http_port"` HTTPPort uint16 `json:"http_port"`
// ProtectionDisabledDuration is the duration of the protection pause in // ProtectionDisabledDuration is the duration of the protection pause in
// milliseconds. // milliseconds.
@ -158,7 +158,7 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
Language: config.Language, Language: config.Language,
DNSAddrs: dnsAddrs, DNSAddrs: dnsAddrs,
DNSPort: config.DNS.Port, DNSPort: config.DNS.Port,
HTTPPort: config.BindPort, HTTPPort: config.HTTPConfig.Address.Port(),
ProtectionDisabledDuration: protectionDisabledDuration, ProtectionDisabledDuration: protectionDisabledDuration,
ProtectionEnabled: protectionEnabled, ProtectionEnabled: protectionEnabled,
IsRunning: isRunning(), IsRunning: isRunning(),

View file

@ -96,8 +96,9 @@ type checkConfResp struct {
func (req *checkConfReq) validateWeb(tcpPorts aghalg.UniqChecker[tcpPort]) (err error) { func (req *checkConfReq) validateWeb(tcpPorts aghalg.UniqChecker[tcpPort]) (err error) {
defer func() { err = errors.Annotate(err, "validating ports: %w") }() defer func() { err = errors.Annotate(err, "validating ports: %w") }()
portInt := req.Web.Port // TODO(a.garipov): Declare all port variables anywhere as uint16.
port := tcpPort(portInt) reqPort := uint16(req.Web.Port)
port := tcpPort(reqPort)
addPorts(tcpPorts, port) addPorts(tcpPorts, port)
if err = tcpPorts.Validate(); err != nil { if err = tcpPorts.Validate(); err != nil {
// Reset the value for the port to 1 to make sure that validateDNS // Reset the value for the port to 1 to make sure that validateDNS
@ -108,15 +109,15 @@ func (req *checkConfReq) validateWeb(tcpPorts aghalg.UniqChecker[tcpPort]) (err
return err return err
} }
switch portInt { switch reqPort {
case 0, config.BindPort: case 0, config.HTTPConfig.Address.Port():
return nil return nil
default: default:
// Go on and check the port binding only if it's not zero or won't be // Go on and check the port binding only if it's not zero or won't be
// unbound after install. // unbound after install.
} }
return aghnet.CheckPort("tcp", netip.AddrPortFrom(req.Web.IP, uint16(portInt))) return aghnet.CheckPort("tcp", netip.AddrPortFrom(req.Web.IP, reqPort))
} }
// validateDNS returns error if the DNS part of the initial configuration can't // validateDNS returns error if the DNS part of the initial configuration can't
@ -127,11 +128,11 @@ func (req *checkConfReq) validateDNS(
) (canAutofix bool, err error) { ) (canAutofix bool, err error) {
defer func() { err = errors.Annotate(err, "validating ports: %w") }() defer func() { err = errors.Annotate(err, "validating ports: %w") }()
port := req.DNS.Port port := uint16(req.DNS.Port)
switch port { switch port {
case 0: case 0:
return false, nil return false, nil
case config.BindPort: case config.HTTPConfig.Address.Port():
// Go on and only check the UDP port since the TCP one is already bound // Go on and only check the UDP port since the TCP one is already bound
// by AdGuard Home for web interface. // by AdGuard Home for web interface.
default: default:
@ -318,8 +319,7 @@ type applyConfigReq struct {
// copyInstallSettings copies the installation parameters between two // copyInstallSettings copies the installation parameters between two
// configuration structures. // configuration structures.
func copyInstallSettings(dst, src *configuration) { func copyInstallSettings(dst, src *configuration) {
dst.BindHost = src.BindHost dst.HTTPConfig = src.HTTPConfig
dst.BindPort = src.BindPort
dst.DNS.BindHosts = src.DNS.BindHosts dst.DNS.BindHosts = src.DNS.BindHosts
dst.DNS.Port = src.DNS.Port dst.DNS.Port = src.DNS.Port
} }
@ -413,8 +413,7 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
copyInstallSettings(curConfig, config) copyInstallSettings(curConfig, config)
Context.firstRun = false Context.firstRun = false
config.BindHost = req.Web.IP config.HTTPConfig.Address = netip.AddrPortFrom(req.Web.IP, uint16(req.Web.Port))
config.BindPort = req.Web.Port
config.DNS.BindHosts = []netip.Addr{req.DNS.IP} config.DNS.BindHosts = []netip.Addr{req.DNS.IP}
config.DNS.Port = req.DNS.Port config.DNS.Port = req.DNS.Port
@ -487,7 +486,8 @@ func decodeApplyConfigReq(r io.Reader) (req *applyConfigReq, restartHTTP bool, e
return nil, false, errors.Error("ports cannot be 0") return nil, false, errors.Error("ports cannot be 0")
} }
restartHTTP = config.BindHost != req.Web.IP || config.BindPort != req.Web.Port addrPort := config.HTTPConfig.Address
restartHTTP = addrPort.Addr() != req.Web.IP || int(addrPort.Port()) != req.Web.Port
if restartHTTP { if restartHTTP {
err = aghnet.CheckPort("tcp", netip.AddrPortFrom(req.Web.IP, uint16(req.Web.Port))) err = aghnet.CheckPort("tcp", netip.AddrPortFrom(req.Web.IP, uint16(req.Web.Port)))
if err != nil { if err != nil {

View file

@ -157,7 +157,9 @@ func (vr *versionResponse) setAllowedToAutoUpdate() (err error) {
Context.tls.WriteDiskConfig(tlsConf) Context.tls.WriteDiskConfig(tlsConf)
canUpdate := true canUpdate := true
if tlsConfUsesPrivilegedPorts(tlsConf) || config.BindPort < 1024 || config.DNS.Port < 1024 { if tlsConfUsesPrivilegedPorts(tlsConf) ||
config.HTTPConfig.Address.Port() < 1024 ||
config.DNS.Port < 1024 {
canUpdate, err = aghnet.CanBindPrivilegedPorts() canUpdate, err = aghnet.CanBindPrivilegedPorts()
if err != nil { if err != nil {
return fmt.Errorf("checking ability to bind privileged ports: %w", err) return fmt.Errorf("checking ability to bind privileged ports: %w", err)

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