Compare commits
53 Commits
v0.108.0-b
...
ADG-9565
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef92c2e891 | ||
|
|
a4364ff7ed | ||
|
|
91270d0b61 | ||
|
|
a4ea6c2233 | ||
|
|
c7b65fa522 | ||
|
|
1a95161784 | ||
|
|
6633ad6304 | ||
|
|
dfa28af907 | ||
|
|
8f75c6ac9d | ||
|
|
446f21a511 | ||
|
|
0b25119c52 | ||
|
|
20e56b7171 | ||
|
|
261c1599a5 | ||
|
|
21945c6058 | ||
|
|
5c15d1bb16 | ||
|
|
efe2ed1304 | ||
|
|
fe07786d2d | ||
|
|
2b55e56306 | ||
|
|
dedbadafc4 | ||
|
|
d3cc2dc930 | ||
|
|
8f53f6505b | ||
|
|
dab608292a | ||
|
|
11dfc7a3e8 | ||
|
|
c234e5dc31 | ||
|
|
3895cfb4f0 | ||
|
|
37b16bcf79 | ||
|
|
4d470f9484 | ||
|
|
a8431297f7 | ||
|
|
9789e5b0fe | ||
|
|
4a49c4db96 | ||
|
|
d578c713ff | ||
|
|
d4ca14806e | ||
|
|
abb738013a | ||
|
|
098cbab7e6 | ||
|
|
d96e65cb0c | ||
|
|
1d6d85cff4 | ||
|
|
ac5a96fada | ||
|
|
6673bb175a | ||
|
|
b1a0f4fa44 | ||
|
|
d06b18a493 | ||
|
|
80ec01dd49 | ||
|
|
47dfa44cf6 | ||
|
|
1d2026bf7e | ||
|
|
e77de2e67d | ||
|
|
e529d29e8a | ||
|
|
41cce62597 | ||
|
|
5b45f6d508 | ||
|
|
73ff401b0f | ||
|
|
6363f8a2e7 | ||
|
|
2c64ab5a51 | ||
|
|
df097341c3 | ||
|
|
4919630ccc | ||
|
|
b32b8f9c7e |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
'name': 'build'
|
'name': 'build'
|
||||||
|
|
||||||
'env':
|
'env':
|
||||||
'GO_VERSION': '1.23.2'
|
'GO_VERSION': '1.23.5'
|
||||||
'NODE_VERSION': '16'
|
'NODE_VERSION': '16'
|
||||||
|
|
||||||
'on':
|
'on':
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}"
|
'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}"
|
||||||
'restore-keys': '${{ runner.os }}-node-'
|
'restore-keys': '${{ runner.os }}-node-'
|
||||||
- 'name': 'Set up Snapcraft'
|
- 'name': 'Set up Snapcraft'
|
||||||
'run': 'sudo apt-get -yq --no-install-suggests --no-install-recommends install snapcraft'
|
'run': 'sudo snap install snapcraft --classic'
|
||||||
- 'name': 'Set up QEMU'
|
- 'name': 'Set up QEMU'
|
||||||
'uses': 'docker/setup-qemu-action@v1'
|
'uses': 'docker/setup-qemu-action@v1'
|
||||||
- 'name': 'Set up Docker Buildx'
|
- 'name': 'Set up Docker Buildx'
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
'name': 'lint'
|
'name': 'lint'
|
||||||
|
|
||||||
'env':
|
'env':
|
||||||
'GO_VERSION': '1.23.2'
|
'GO_VERSION': '1.23.5'
|
||||||
|
|
||||||
'on':
|
'on':
|
||||||
'push':
|
'push':
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,3 +1,8 @@
|
|||||||
|
# This comment is used to simplify checking local copies of the file. Bump
|
||||||
|
# this number every time a significant change is made to this file.
|
||||||
|
#
|
||||||
|
# AdGuard-Project-Version: 1
|
||||||
|
|
||||||
# Please, DO NOT put your text editors' temporary files here. The more are
|
# Please, DO NOT put your text editors' temporary files here. The more are
|
||||||
# added, the harder it gets to maintain and manage projects' gitignores. Put
|
# added, the harder it gets to maintain and manage projects' gitignores. Put
|
||||||
# them into your global gitignore file instead.
|
# them into your global gitignore file instead.
|
||||||
@@ -8,6 +13,7 @@
|
|||||||
# bottom to make sure they take effect.
|
# bottom to make sure they take effect.
|
||||||
*.db
|
*.db
|
||||||
*.log
|
*.log
|
||||||
|
*.out
|
||||||
*.snap
|
*.snap
|
||||||
*.test
|
*.test
|
||||||
/agh-backup/
|
/agh-backup/
|
||||||
@@ -21,6 +27,7 @@
|
|||||||
/launchpad_credentials
|
/launchpad_credentials
|
||||||
/querylog.json*
|
/querylog.json*
|
||||||
/snapcraft_login
|
/snapcraft_login
|
||||||
|
/test-reports/
|
||||||
AdGuardHome
|
AdGuardHome
|
||||||
AdGuardHome.exe
|
AdGuardHome.exe
|
||||||
AdGuardHome.yaml*
|
AdGuardHome.yaml*
|
||||||
|
|||||||
25
.markdownlint.json
Normal file
25
.markdownlint.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"ul-indent": {
|
||||||
|
"indent": 4
|
||||||
|
},
|
||||||
|
"ul-style": {
|
||||||
|
"style": "dash"
|
||||||
|
},
|
||||||
|
"emphasis-style": {
|
||||||
|
"style": "asterisk"
|
||||||
|
},
|
||||||
|
"no-duplicate-heading": {
|
||||||
|
"siblings_only": true
|
||||||
|
},
|
||||||
|
"no-inline-html": {
|
||||||
|
"allowed_elements": [
|
||||||
|
"a"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"no-trailing-spaces": {
|
||||||
|
"br_spaces": 0
|
||||||
|
},
|
||||||
|
"line-length": false,
|
||||||
|
"no-bare-urls": false,
|
||||||
|
"link-fragments": false
|
||||||
|
}
|
||||||
2393
CHANGELOG.md
2393
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
35
Makefile
35
Makefile
@@ -1,14 +1,14 @@
|
|||||||
# Keep the Makefile POSIX-compliant. We currently allow hyphens in
|
# Keep the Makefile POSIX-compliant. We currently allow hyphens in
|
||||||
# target names, but that may change in the future.
|
# target names, but that may change in the future.
|
||||||
#
|
#
|
||||||
# See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html.
|
# See https://pubs.opengroup.org/onlinepubs/9799919799/utilities/make.html.
|
||||||
.POSIX:
|
.POSIX:
|
||||||
|
|
||||||
# This comment is used to simplify checking local copies of the
|
# This comment is used to simplify checking local copies of the
|
||||||
# Makefile. Bump this number every time a significant change is made to
|
# Makefile. Bump this number every time a significant change is made to
|
||||||
# this Makefile.
|
# this Makefile.
|
||||||
#
|
#
|
||||||
# AdGuard-Project-Version: 6
|
# AdGuard-Project-Version: 9
|
||||||
|
|
||||||
# Don't name these macros "GO" etc., because GNU Make apparently makes
|
# Don't name these macros "GO" etc., because GNU Make apparently makes
|
||||||
# them exported environment variables with the literal value of
|
# them exported environment variables with the literal value of
|
||||||
@@ -22,13 +22,12 @@ VERBOSE.MACRO = $${VERBOSE:-0}
|
|||||||
|
|
||||||
CHANNEL = development
|
CHANNEL = development
|
||||||
CLIENT_DIR = client
|
CLIENT_DIR = client
|
||||||
COMMIT = $$( git rev-parse --short HEAD )
|
|
||||||
DEPLOY_SCRIPT_PATH = not/a/real/path
|
DEPLOY_SCRIPT_PATH = not/a/real/path
|
||||||
DIST_DIR = dist
|
DIST_DIR = dist
|
||||||
GOAMD64 = v1
|
GOAMD64 = v1
|
||||||
GOPROXY = https://proxy.golang.org|direct
|
GOPROXY = https://proxy.golang.org|direct
|
||||||
GOTOOLCHAIN = go1.23.2
|
|
||||||
GOTELEMETRY = off
|
GOTELEMETRY = off
|
||||||
|
GOTOOLCHAIN = go1.23.5
|
||||||
GPG_KEY = devteam@adguard.com
|
GPG_KEY = devteam@adguard.com
|
||||||
GPG_KEY_PASSPHRASE = not-a-real-password
|
GPG_KEY_PASSPHRASE = not-a-real-password
|
||||||
NPM = npm
|
NPM = npm
|
||||||
@@ -36,6 +35,7 @@ NPM_FLAGS = --prefix $(CLIENT_DIR)
|
|||||||
NPM_INSTALL_FLAGS = $(NPM_FLAGS) --quiet --no-progress --ignore-engines\
|
NPM_INSTALL_FLAGS = $(NPM_FLAGS) --quiet --no-progress --ignore-engines\
|
||||||
--ignore-optional --ignore-platform --ignore-scripts
|
--ignore-optional --ignore-platform --ignore-scripts
|
||||||
RACE = 0
|
RACE = 0
|
||||||
|
REVISION = $${REVISION:-$$(git rev-parse --short HEAD)}
|
||||||
SIGN = 1
|
SIGN = 1
|
||||||
SIGNER_API_KEY = not-a-real-key
|
SIGNER_API_KEY = not-a-real-key
|
||||||
VERSION = v0.0.0
|
VERSION = v0.0.0
|
||||||
@@ -60,7 +60,6 @@ BUILD_RELEASE_DEPS_1 = go-deps
|
|||||||
|
|
||||||
ENV = env\
|
ENV = env\
|
||||||
CHANNEL='$(CHANNEL)'\
|
CHANNEL='$(CHANNEL)'\
|
||||||
COMMIT='$(COMMIT)'\
|
|
||||||
DEPLOY_SCRIPT_PATH='$(DEPLOY_SCRIPT_PATH)' \
|
DEPLOY_SCRIPT_PATH='$(DEPLOY_SCRIPT_PATH)' \
|
||||||
DIST_DIR='$(DIST_DIR)'\
|
DIST_DIR='$(DIST_DIR)'\
|
||||||
GO="$(GO.MACRO)"\
|
GO="$(GO.MACRO)"\
|
||||||
@@ -70,17 +69,19 @@ ENV = env\
|
|||||||
GOTOOLCHAIN='$(GOTOOLCHAIN)'\
|
GOTOOLCHAIN='$(GOTOOLCHAIN)'\
|
||||||
GPG_KEY='$(GPG_KEY)'\
|
GPG_KEY='$(GPG_KEY)'\
|
||||||
GPG_KEY_PASSPHRASE='$(GPG_KEY_PASSPHRASE)'\
|
GPG_KEY_PASSPHRASE='$(GPG_KEY_PASSPHRASE)'\
|
||||||
|
NEXTAPI='$(NEXTAPI)'\
|
||||||
PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
|
PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
|
||||||
RACE='$(RACE)'\
|
RACE='$(RACE)'\
|
||||||
|
REVISION='$(REVISION)'\
|
||||||
SIGN='$(SIGN)'\
|
SIGN='$(SIGN)'\
|
||||||
SIGNER_API_KEY='$(SIGNER_API_KEY)' \
|
SIGNER_API_KEY='$(SIGNER_API_KEY)' \
|
||||||
NEXTAPI='$(NEXTAPI)'\
|
|
||||||
VERBOSE="$(VERBOSE.MACRO)"\
|
VERBOSE="$(VERBOSE.MACRO)"\
|
||||||
VERSION="$(VERSION)"\
|
VERSION="$(VERSION)"\
|
||||||
|
|
||||||
# Keep the line above blank.
|
# Keep the line above blank.
|
||||||
|
|
||||||
ENV_MISC = env\
|
ENV_MISC = env\
|
||||||
|
PATH="$${PWD}/bin:$$("$(GO.MACRO)" env GOPATH)/bin:$${PATH}"\
|
||||||
VERBOSE="$(VERBOSE.MACRO)"\
|
VERBOSE="$(VERBOSE.MACRO)"\
|
||||||
|
|
||||||
# Keep the line above blank.
|
# Keep the line above blank.
|
||||||
@@ -89,6 +90,8 @@ ENV_MISC = env\
|
|||||||
# full build.
|
# full build.
|
||||||
build: deps quick-build
|
build: deps quick-build
|
||||||
|
|
||||||
|
init: ; git config core.hooksPath ./scripts/hooks
|
||||||
|
|
||||||
quick-build: js-build go-build
|
quick-build: js-build go-build
|
||||||
|
|
||||||
deps: js-deps go-deps
|
deps: js-deps go-deps
|
||||||
@@ -102,9 +105,6 @@ build-docker: ; $(ENV) "$(SHELL)" ./scripts/make/build-docker.sh
|
|||||||
build-release: $(BUILD_RELEASE_DEPS_$(FRONTEND_PREBUILT))
|
build-release: $(BUILD_RELEASE_DEPS_$(FRONTEND_PREBUILT))
|
||||||
$(ENV) "$(SHELL)" ./scripts/make/build-release.sh
|
$(ENV) "$(SHELL)" ./scripts/make/build-release.sh
|
||||||
|
|
||||||
clean: ; $(ENV) "$(SHELL)" ./scripts/make/clean.sh
|
|
||||||
init: ; git config core.hooksPath ./scripts/hooks
|
|
||||||
|
|
||||||
js-build: ; $(NPM) $(NPM_FLAGS) run build-prod
|
js-build: ; $(NPM) $(NPM_FLAGS) run build-prod
|
||||||
js-deps: ; $(NPM) $(NPM_INSTALL_FLAGS) ci
|
js-deps: ; $(NPM) $(NPM_INSTALL_FLAGS) ci
|
||||||
js-lint: ; $(NPM) $(NPM_FLAGS) run lint
|
js-lint: ; $(NPM) $(NPM_FLAGS) run lint
|
||||||
@@ -127,17 +127,16 @@ go-check: go-tools go-lint go-test
|
|||||||
# A quick check to make sure that all operating systems relevant to the
|
# A quick check to make sure that all operating systems relevant to the
|
||||||
# development of the project can be typechecked and built successfully.
|
# development of the project can be typechecked and built successfully.
|
||||||
go-os-check:
|
go-os-check:
|
||||||
env GOOS='darwin' "$(GO.MACRO)" vet ./internal/...
|
$(ENV) GOOS='darwin' "$(GO.MACRO)" vet ./internal/...
|
||||||
env GOOS='freebsd' "$(GO.MACRO)" vet ./internal/...
|
$(ENV) GOOS='freebsd' "$(GO.MACRO)" vet ./internal/...
|
||||||
env GOOS='openbsd' "$(GO.MACRO)" vet ./internal/...
|
$(ENV) GOOS='openbsd' "$(GO.MACRO)" vet ./internal/...
|
||||||
env GOOS='linux' "$(GO.MACRO)" vet ./internal/...
|
$(ENV) GOOS='linux' "$(GO.MACRO)" vet ./internal/...
|
||||||
env GOOS='windows' "$(GO.MACRO)" vet ./internal/...
|
$(ENV) GOOS='windows' "$(GO.MACRO)" vet ./internal/...
|
||||||
|
|
||||||
|
|
||||||
openapi-lint: ; cd ./openapi/ && $(YARN) test
|
|
||||||
openapi-show: ; cd ./openapi/ && $(YARN) start
|
|
||||||
|
|
||||||
txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh
|
txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh
|
||||||
|
|
||||||
md-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/md-lint.sh
|
md-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/md-lint.sh
|
||||||
sh-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/sh-lint.sh
|
sh-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/sh-lint.sh
|
||||||
|
|
||||||
|
openapi-lint: ; cd ./openapi/ && $(YARN) test
|
||||||
|
openapi-show: ; cd ./openapi/ && $(YARN) start
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ If you're running **Linux,** there's a secure and easy way to install AdGuard Ho
|
|||||||
|
|
||||||
[Docker Hub]: https://hub.docker.com/r/adguard/adguardhome
|
[Docker Hub]: https://hub.docker.com/r/adguard/adguardhome
|
||||||
[Snap Store]: https://snapcraft.io/adguard-home
|
[Snap Store]: https://snapcraft.io/adguard-home
|
||||||
[wiki-start]: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started
|
[wiki-start]: https://adguard-dns.io/kb/adguard-home/getting-started/
|
||||||
|
|
||||||
### <a href="#guides" id="guides" name="guides">Guides</a>
|
### <a href="#guides" id="guides" name="guides">Guides</a>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
'variables':
|
'variables':
|
||||||
'channel': 'edge'
|
'channel': 'edge'
|
||||||
'dockerFrontend': 'adguard/home-js-builder:2.0'
|
'dockerFrontend': 'adguard/home-js-builder:2.0'
|
||||||
'dockerGo': 'adguard/go-builder:1.23.2--1'
|
'dockerGo': 'adguard/go-builder:1.23.5--1'
|
||||||
|
|
||||||
'stages':
|
'stages':
|
||||||
- 'Build frontend':
|
- 'Build frontend':
|
||||||
@@ -142,13 +142,15 @@
|
|||||||
# 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 \
|
||||||
--use
|
--name buildx-builder \
|
||||||
|
--driver docker-container \
|
||||||
|
--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
|
||||||
@@ -157,14 +159,14 @@
|
|||||||
docker info
|
docker info
|
||||||
|
|
||||||
# Prepare and push the build.
|
# Prepare and push the build.
|
||||||
env\
|
env \
|
||||||
CHANNEL="${bamboo.channel}"\
|
CHANNEL="${bamboo.channel}" \
|
||||||
COMMIT="${bamboo.repository.revision.number}"\
|
REVISION="${bamboo.repository.revision.number}" \
|
||||||
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':
|
||||||
@@ -276,7 +278,7 @@
|
|||||||
'variables':
|
'variables':
|
||||||
'channel': 'beta'
|
'channel': 'beta'
|
||||||
'dockerFrontend': 'adguard/home-js-builder:2.0'
|
'dockerFrontend': 'adguard/home-js-builder:2.0'
|
||||||
'dockerGo': 'adguard/go-builder:1.23.2--1'
|
'dockerGo': 'adguard/go-builder:1.23.5--1'
|
||||||
# release-vX.Y.Z branches are the branches from which the actual final
|
# release-vX.Y.Z branches are the branches from which the actual final
|
||||||
# release is built.
|
# release is built.
|
||||||
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||||
@@ -292,4 +294,4 @@
|
|||||||
'variables':
|
'variables':
|
||||||
'channel': 'release'
|
'channel': 'release'
|
||||||
'dockerFrontend': 'adguard/home-js-builder:2.0'
|
'dockerFrontend': 'adguard/home-js-builder:2.0'
|
||||||
'dockerGo': 'adguard/go-builder:1.23.2--1'
|
'dockerGo': 'adguard/go-builder:1.23.5--1'
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
'name': 'AdGuard Home - Build and run tests'
|
'name': 'AdGuard Home - Build and run tests'
|
||||||
'variables':
|
'variables':
|
||||||
'dockerFrontend': 'adguard/home-js-builder:2.0'
|
'dockerFrontend': 'adguard/home-js-builder:2.0'
|
||||||
'dockerGo': 'adguard/go-builder:1.23.2--1'
|
'dockerGo': 'adguard/go-builder:1.23.5--1'
|
||||||
'channel': 'development'
|
'channel': 'development'
|
||||||
|
|
||||||
'stages':
|
'stages':
|
||||||
@@ -196,5 +196,5 @@
|
|||||||
# may need to build a few of these.
|
# may need to build a few of these.
|
||||||
'variables':
|
'variables':
|
||||||
'dockerFrontend': 'adguard/home-js-builder:2.0'
|
'dockerFrontend': 'adguard/home-js-builder:2.0'
|
||||||
'dockerGo': 'adguard/go-builder:1.23.2--1'
|
'dockerGo': 'adguard/go-builder:1.23.5--1'
|
||||||
'channel': 'candidate'
|
'channel': 'candidate'
|
||||||
|
|||||||
@@ -641,7 +641,7 @@
|
|||||||
"show_processed_responses": "Verarbeitet",
|
"show_processed_responses": "Verarbeitet",
|
||||||
"blocked_safebrowsing": "Gesperrt durch Internetsicherheit",
|
"blocked_safebrowsing": "Gesperrt durch Internetsicherheit",
|
||||||
"blocked_adult_websites": "Gesperrt durch Kindersicherung",
|
"blocked_adult_websites": "Gesperrt durch Kindersicherung",
|
||||||
"blocked_threats": "Bedrohungen blockiert",
|
"blocked_threats": "Gesperrte Bedrohungen",
|
||||||
"allowed": "Zugelassen",
|
"allowed": "Zugelassen",
|
||||||
"filtered": "Gefiltert",
|
"filtered": "Gefiltert",
|
||||||
"rewritten": "Umgeschrieben",
|
"rewritten": "Umgeschrieben",
|
||||||
|
|||||||
@@ -542,7 +542,7 @@
|
|||||||
"stats_params": "Tilastoinnin määritys",
|
"stats_params": "Tilastoinnin määritys",
|
||||||
"config_successfully_saved": "Asetukset tallennettiin",
|
"config_successfully_saved": "Asetukset tallennettiin",
|
||||||
"interval_6_hour": "6 tuntia",
|
"interval_6_hour": "6 tuntia",
|
||||||
"interval_24_hour": "24 tuntia",
|
"interval_24_hour": "24 tunnilta",
|
||||||
"interval_days": "{{count}} päivä",
|
"interval_days": "{{count}} päivä",
|
||||||
"interval_days_plural": "{{count}} päivää",
|
"interval_days_plural": "{{count}} päivää",
|
||||||
"domain": "Verkkotunnus",
|
"domain": "Verkkotunnus",
|
||||||
|
|||||||
@@ -166,7 +166,7 @@
|
|||||||
"encryption_settings": "Encryptie instellingen",
|
"encryption_settings": "Encryptie instellingen",
|
||||||
"dhcp_settings": "DHCP instellingen",
|
"dhcp_settings": "DHCP instellingen",
|
||||||
"upstream_dns": "Upstream DNS-servers",
|
"upstream_dns": "Upstream DNS-servers",
|
||||||
"upstream_dns_help": "Een server-adres per regel invoeren. <a>Meer weten</a> over het configureren van upstream DNS-servers.",
|
"upstream_dns_help": "Een server-adres per regel invoeren. <a>Meer informatie</a> over het configureren van upstream DNS-servers.",
|
||||||
"upstream_dns_configured_in_file": "Geconfigureerd in {{path}}",
|
"upstream_dns_configured_in_file": "Geconfigureerd in {{path}}",
|
||||||
"test_upstream_btn": "Test upstream",
|
"test_upstream_btn": "Test upstream",
|
||||||
"upstreams": "Upstreams",
|
"upstreams": "Upstreams",
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
"stats_query_domain": "Najczęściej wyszukiwane domeny",
|
"stats_query_domain": "Najczęściej wyszukiwane domeny",
|
||||||
"for_last_hours": "w ciągu ostatniej {{count}} godziny",
|
"for_last_hours": "w ciągu ostatniej {{count}} godziny",
|
||||||
"for_last_hours_plural": "w ciągu ostatnich {{count}} godzin",
|
"for_last_hours_plural": "w ciągu ostatnich {{count}} godzin",
|
||||||
"for_last_days": "za ostatni dzień {{count}}",
|
"for_last_days": "za ostatni {{count}} dzień",
|
||||||
"for_last_days_plural": "z ostatnich {{count}} dni",
|
"for_last_days_plural": "z ostatnich {{count}} dni",
|
||||||
"stats_disabled": "Statystyki zostały wyłączone. Można je włączyć na <0>stronie ustawień</0>.",
|
"stats_disabled": "Statystyki zostały wyłączone. Można je włączyć na <0>stronie ustawień</0>.",
|
||||||
"stats_disabled_short": "Statystyki zostały wyłączone",
|
"stats_disabled_short": "Statystyki zostały wyłączone",
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
"requests_count": "Licznik żądań",
|
"requests_count": "Licznik żądań",
|
||||||
"top_blocked_domains": "Najpopularniejsze zablokowane domeny",
|
"top_blocked_domains": "Najpopularniejsze zablokowane domeny",
|
||||||
"top_clients": "Główni klienci",
|
"top_clients": "Główni klienci",
|
||||||
"no_clients_found": "Nie znaleziono klienta",
|
"no_clients_found": "Nie znaleziono klientów",
|
||||||
"general_statistics": "Ogólne statystyki",
|
"general_statistics": "Ogólne statystyki",
|
||||||
"top_upstreams": "Często żądane serwery nadrzędne",
|
"top_upstreams": "Często żądane serwery nadrzędne",
|
||||||
"no_upstreams_data_found": "Brak danych dotyczących serwerów nadrzędnych",
|
"no_upstreams_data_found": "Brak danych dotyczących serwerów nadrzędnych",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"local_ptr_desc": "ස්ථානීය PTR විමසුම් සඳහා ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන ව.නා.ප. සේවාදායක. මෙම සේවාදායක පුද්ගලික අ.ජා.කෙ. ලිපින පරාසවල PTR විමසුම් විසඳීමට භාවිතා කරයි, උදාහරණයක් ලෙස ප්රතිවර්ත ව.නා.ප. භාවිතයෙන් \"192.168.12.34\". මෙය සකසා නැති නම්, ඇඩ්ගාර්ඩ් හෝම් හි ලිපින සඳහා හැරුනු විට ඔබගේ මෙහෙයුම් පද්ධතියේ පෙරනිමි ව.නා.ප. විසදුම්වල ලිපින භාවිතා කරයි.",
|
"local_ptr_desc": "ස්ථානීය PTR විමසුම් සඳහා ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන ව.නා.ප. සේවාදායක. මෙම සේවාදායක පුද්ගලික අ.ජා.කෙ. ලිපින පරාසවල PTR විමසුම් විසඳීමට භාවිතා කරයි, උදාහරණයක් ලෙස ප්රතිවර්ත ව.නා.ප. භාවිතයෙන් \"192.168.12.34\". මෙය සකසා නැති නම්, ඇඩ්ගාර්ඩ් හෝම් හි ලිපින සඳහා හැරුනු විට ඔබගේ මෙහෙයුම් පද්ධතියේ පෙරනිමි ව.නා.ප. විසදුම්වල ලිපින භාවිතා කරයි.",
|
||||||
"local_ptr_default_resolver": "පෙරනිමි පරිදි, ඇඩ්ගාර්ඩ් හෝම් පහත ප්රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතා කරයි: {{ip}}.",
|
"local_ptr_default_resolver": "පෙරනිමි පරිදි, ඇඩ්ගාර්ඩ් හෝම් පහත ප්රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතා කරයි: {{ip}}.",
|
||||||
"local_ptr_no_default_resolver": "ඇඩ්ගාර්ඩ් හෝම් හට මෙම පද්ධතිය සඳහා සුදුසු පුද්ගලික ප්රතිවර්ත ව.නා.ප. පිළිවිසඳු නිශ්චය කරගත නොහැකි විය.",
|
"local_ptr_no_default_resolver": "ඇඩ්ගාර්ඩ් හෝම් හට මෙම පද්ධතිය සඳහා සුදුසු පුද්ගලික ප්රතිවර්ත ව.නා.ප. පිළිවිසඳු නිශ්චය කරගත නොහැකි විය.",
|
||||||
"local_ptr_placeholder": "පේළියකට එක් සේවාදායක ලිපිනය බැගින් යොදන්න",
|
"local_ptr_placeholder": "පේළියකට අ.ජා.කෙ. ලිපිනය බැගින් ලියන්න",
|
||||||
"resolve_clients_title": "අනුග්රාහකවල අ.ජා.කෙ. ලිපින ප්රතිවර්ත විසඳීම සබල කරන්න",
|
"resolve_clients_title": "අනුග්රාහකවල අ.ජා.කෙ. ලිපින ප්රතිවර්ත විසඳීම සබල කරන්න",
|
||||||
"use_private_ptr_resolvers_title": "පෞද්. ප්රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතය",
|
"use_private_ptr_resolvers_title": "පෞද්. ප්රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතය",
|
||||||
"check_dhcp_servers": "ග.ධා.වි.කෙ. සේවාදායක පරීක්ෂා කරන්න",
|
"check_dhcp_servers": "ග.ධා.වි.කෙ. සේවාදායක පරීක්ෂා කරන්න",
|
||||||
@@ -102,7 +102,6 @@
|
|||||||
"stats_malware_phishing": "අවහිර කළ ද්වේශාංග/තතුබෑම්",
|
"stats_malware_phishing": "අවහිර කළ ද්වේශාංග/තතුබෑම්",
|
||||||
"stats_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි",
|
"stats_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි",
|
||||||
"stats_query_domain": "ප්රචලිත විමසන ලද වසම්",
|
"stats_query_domain": "ප්රචලිත විමසන ලද වසම්",
|
||||||
"for_last_24_hours": "පසුගිය පැය 24 සඳහා",
|
|
||||||
"for_last_days": "පසුගිය දවස් {{count}} සඳහා",
|
"for_last_days": "පසුගිය දවස් {{count}} සඳහා",
|
||||||
"for_last_days_plural": "පසුගිය දවස් {{count}} සඳහා",
|
"for_last_days_plural": "පසුගිය දවස් {{count}} සඳහා",
|
||||||
"stats_disabled": "සංඛ්යාලේඛන අබල කර ඇත. එය <0>සැකසුම් පිටුවෙන්</0> සබල කළ හැකිය.",
|
"stats_disabled": "සංඛ්යාලේඛන අබල කර ඇත. එය <0>සැකසුම් පිටුවෙන්</0> සබල කළ හැකිය.",
|
||||||
@@ -115,13 +114,15 @@
|
|||||||
"general_statistics": "පොදු සංඛ්යාලේඛන",
|
"general_statistics": "පොදු සංඛ්යාලේඛන",
|
||||||
"number_of_dns_query_days": "පසුගිය දවස් {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන",
|
"number_of_dns_query_days": "පසුගිය දවස් {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන",
|
||||||
"number_of_dns_query_days_plural": "පසුගිය දවස් {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන",
|
"number_of_dns_query_days_plural": "පසුගිය දවස් {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන",
|
||||||
"number_of_dns_query_24_hours": "පසුගිය පැය 24 සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන",
|
"number_of_dns_query_hours": "පසුගිය පැය {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන",
|
||||||
|
"number_of_dns_query_hours_plural": "පසුගිය පැය {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන",
|
||||||
"number_of_dns_query_blocked_24_hours": "දැන්වීම් වාරණ පෙරහන් සහ සත්කාරක වාරණ ලැයිස්තු මගින් අවහිර කළ ව.නා.ප. ඉල්ලීම් ගණන",
|
"number_of_dns_query_blocked_24_hours": "දැන්වීම් වාරණ පෙරහන් සහ සත්කාරක වාරණ ලැයිස්තු මගින් අවහිර කළ ව.නා.ප. ඉල්ලීම් ගණන",
|
||||||
"number_of_dns_query_blocked_24_hours_by_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්ෂණ ඒකකය මගින් අවහිර කළ ව.නා.ප. ඉල්ලීම් ගණන",
|
"number_of_dns_query_blocked_24_hours_by_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්ෂණ ඒකකය මගින් අවහිර කළ ව.නා.ප. ඉල්ලීම් ගණන",
|
||||||
"number_of_dns_query_blocked_24_hours_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි ගණන",
|
"number_of_dns_query_blocked_24_hours_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි ගණන",
|
||||||
"enforced_save_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ",
|
"enforced_save_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ",
|
||||||
"number_of_dns_query_to_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්ර සඳහා ව.නා.ප. ඉල්ලීම් ගණන",
|
"number_of_dns_query_to_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්ර සඳහා ව.නා.ප. ඉල්ලීම් ගණන",
|
||||||
"average_processing_time": "සාමාන්ය සැකසුම් කාලය",
|
"average_processing_time": "සාමාන්ය සැකසුම් කාලය",
|
||||||
|
"response_time": "ප්රතිචාර කාලය",
|
||||||
"average_processing_time_hint": "ව.නා.ප. ඉල්ලීමක් සැකසීමේ සාමාන්ය කාලය මිලි තත්පර වලින්",
|
"average_processing_time_hint": "ව.නා.ප. ඉල්ලීමක් සැකසීමේ සාමාන්ය කාලය මිලි තත්පර වලින්",
|
||||||
"block_domain_use_filters_and_hosts": "පෙරහන් හා සත්කාරක ගොනු භාවිතයෙන් වසම් අවහිර කරන්න",
|
"block_domain_use_filters_and_hosts": "පෙරහන් හා සත්කාරක ගොනු භාවිතයෙන් වසම් අවහිර කරන්න",
|
||||||
"filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති <a>පෙරහන්</a> තුළ පිහිටුවිය හැකිය.",
|
"filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති <a>පෙරහන්</a> තුළ පිහිටුවිය හැකිය.",
|
||||||
@@ -130,7 +131,7 @@
|
|||||||
"use_adguard_parental": "ඇඩ්ගාර්ඩ් දෙමාපිය පාලන වියමන සේවාව භාවිතා කරන්න",
|
"use_adguard_parental": "ඇඩ්ගාර්ඩ් දෙමාපිය පාලන වියමන සේවාව භාවිතා කරන්න",
|
||||||
"use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්ෂා කරනු ඇත. එය පිරික්සුම් ආරක්ෂණ වියමන සේවාව මෙන් රහස්යතා හිතකාමී යෙ.ක්ර. අ.මු. (API) භාවිතා කරයි.",
|
"use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්ෂා කරනු ඇත. එය පිරික්සුම් ආරක්ෂණ වියමන සේවාව මෙන් රහස්යතා හිතකාමී යෙ.ක්ර. අ.මු. (API) භාවිතා කරයි.",
|
||||||
"enforce_safe_search": "ආරක්ෂිත සෙවුම භාවිතා කරන්න",
|
"enforce_safe_search": "ආරක්ෂිත සෙවුම භාවිතා කරන්න",
|
||||||
"enforce_save_search_hint": "ඇඩ්ගාර්ඩ් හෝම් පහත සෙවුම් යන්ත්ර තුළ ආරක්ෂිත සෙවුම බලාත්මක කරනු ඇත: ගූගල්, යූටියුබ්, බින්ග්, ඩක්ඩක්ගෝ, යාන්ඩෙක්ස් සහ පික්සාබේ.",
|
"enforce_save_search_hint": "ඇඩ්ගාර්ඩ් හෝම් පහත සෙවුම් යන්ත්ර තුළ ආරක්ෂිත සෙවුම බලාත්මක කරනු ඇත: ගූගල්, යූටියුබ්, බින්ග්, ඩක්ඩක්ගෝ, එකොසියා, යාන්ඩෙක්ස් සහ පික්සාබේ.",
|
||||||
"no_servers_specified": "සේවාදායක කිසිවක් නිශ්චිතව දක්වා නැත",
|
"no_servers_specified": "සේවාදායක කිසිවක් නිශ්චිතව දක්වා නැත",
|
||||||
"general_settings": "පොදු සැකසුම්",
|
"general_settings": "පොදු සැකසුම්",
|
||||||
"dns_settings": "ව.නා.ප. සැකසුම්",
|
"dns_settings": "ව.නා.ප. සැකසුම්",
|
||||||
@@ -196,12 +197,14 @@
|
|||||||
"example_comment_hash": "# එසේම අදහස් දැක්වීමක්.",
|
"example_comment_hash": "# එසේම අදහස් දැක්වීමක්.",
|
||||||
"example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්ය වාක්යවිධියට ගැළපෙන වසම් වෙත ප්රවේශය අවහිර කරයි.",
|
"example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්ය වාක්යවිධියට ගැළපෙන වසම් වෙත ප්රවේශය අවහිර කරයි.",
|
||||||
"example_upstream_regular": "සාමාන්ය ව.නා.ප. (UDP හරහා);",
|
"example_upstream_regular": "සාමාන්ය ව.නා.ප. (UDP හරහා);",
|
||||||
|
"example_upstream_regular_port": "සාමාන්ය ව.නා.ප. (UDP හරහා, තොට සමඟ);",
|
||||||
"example_upstream_udp": "සාමාන්ය ව.නා.ප. (UDP, සත්කාරක-නම හරහා);",
|
"example_upstream_udp": "සාමාන්ය ව.නා.ප. (UDP, සත්කාරක-නම හරහා);",
|
||||||
"example_upstream_dot": "සංකේතිත <0>TLS-මගින්-ව.නා.ප.</0>;",
|
"example_upstream_dot": "සංකේතිත <0>TLS-මගින්-ව.නා.ප.</0>;",
|
||||||
"example_upstream_doh": "සංකේතිත <0>HTTPS-මගින්-ව.නා.ප.</0>;",
|
"example_upstream_doh": "සංකේතිත <0>HTTPS-මගින්-ව.නා.ප.</0>;",
|
||||||
"example_upstream_doq": "සංකේතිත <0>QUIC-මගින්-ව.නා.ප.</0>;",
|
"example_upstream_doq": "සංකේතිත <0>QUIC-මගින්-ව.නා.ප.</0>;",
|
||||||
"example_upstream_sdns": "<1>DNSCrypt</1> හෝ <2>HTTPS-මගින්-ව.නා.ප.</2> පිළිවිසඳු සඳහා <0>ව.නා.ප. මුද්දර</0>;",
|
"example_upstream_sdns": "<1>DNSCrypt</1> හෝ <2>HTTPS-මගින්-ව.නා.ප.</2> පිළිවිසඳු සඳහා <0>ව.නා.ප. මුද්දර</0>;",
|
||||||
"example_upstream_tcp": "සාමාන්ය ව.නා.ප. (TCP/ස.පා.කෙ. හරහා);",
|
"example_upstream_tcp": "සාමාන්ය ව.නා.ප. (TCP/ස.පා.කෙ. හරහා);",
|
||||||
|
"example_upstream_tcp_port": "සාමාන්ය ව.නා.ප. (TCP හරහා, තොට සමඟ);",
|
||||||
"example_upstream_tcp_hostname": "සාමාන්ය ව.නා.ප. (ස.පා.කෙ., සත්කාරක-නම හරහා);",
|
"example_upstream_tcp_hostname": "සාමාන්ය ව.නා.ප. (ස.පා.කෙ., සත්කාරක-නම හරහා);",
|
||||||
"all_lists_up_to_date_toast": "සියළුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි",
|
"all_lists_up_to_date_toast": "සියළුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි",
|
||||||
"dns_test_ok_toast": "සඳහන් කළ ව.නා.ප. සේවාදායක නිවැරදිව ක්රියා කරයි",
|
"dns_test_ok_toast": "සඳහන් කළ ව.නා.ප. සේවාදායක නිවැරදිව ක්රියා කරයි",
|
||||||
@@ -275,6 +278,7 @@
|
|||||||
"edns_use_custom_ip": "EDNS සඳහා අභිරුචි අ.ජා.කෙ. යොදාගන්න",
|
"edns_use_custom_ip": "EDNS සඳහා අභිරුචි අ.ජා.කෙ. යොදාගන්න",
|
||||||
"edns_use_custom_ip_desc": "EDNS සඳහා අභිරුචි අ.ජා.කෙ. භාවිතයට ඉඩදෙන්න",
|
"edns_use_custom_ip_desc": "EDNS සඳහා අභිරුචි අ.ජා.කෙ. භාවිතයට ඉඩදෙන්න",
|
||||||
"rate_limit_desc": "එක් අනුග්රාහකයකට ඉඩ දී ඇති තත්පරයට ඉල්ලීම් ගණන. එය 0 ලෙස සැකසීම යනුවෙන් අදහස් කරන්නේ සීමාවක් නැති බවයි.",
|
"rate_limit_desc": "එක් අනුග්රාහකයකට ඉඩ දී ඇති තත්පරයට ඉල්ලීම් ගණන. එය 0 ලෙස සැකසීම යනුවෙන් අදහස් කරන්නේ සීමාවක් නැති බවයි.",
|
||||||
|
"rate_limit_whitelist_placeholder": "පේළියකට අ.ජා.කෙ. ලිපිනය බැගින් ලියන්න",
|
||||||
"blocking_ipv4_desc": "අවහිර කළ A ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය",
|
"blocking_ipv4_desc": "අවහිර කළ A ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය",
|
||||||
"blocking_ipv6_desc": "අවහිර කළ AAAA ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය",
|
"blocking_ipv6_desc": "අවහිර කළ AAAA ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය",
|
||||||
"blocking_mode_default": "පොදු: දැන්වීම් අවහිර කරන ආකාරයේ නීතියක් මගින් අවහිර කළ විට REFUSED සමඟ ප්රතිචාර දක්වයි; /etc/host-style ආකාරයේ නීතියක් මගින් අවහිර කළ විට නීතියේ දක්වා ඇති අ.ජා.කෙ. ලිපිනය සමඟ ප්රතිචාර දක්වයි",
|
"blocking_mode_default": "පොදු: දැන්වීම් අවහිර කරන ආකාරයේ නීතියක් මගින් අවහිර කළ විට REFUSED සමඟ ප්රතිචාර දක්වයි; /etc/host-style ආකාරයේ නීතියක් මගින් අවහිර කළ විට නීතියේ දක්වා ඇති අ.ජා.කෙ. ලිපිනය සමඟ ප්රතිචාර දක්වයි",
|
||||||
@@ -505,8 +509,8 @@
|
|||||||
"statistics_enable": "සංඛ්යාලේඛන සබල කරන්න",
|
"statistics_enable": "සංඛ්යාලේඛන සබල කරන්න",
|
||||||
"ignore_domains": "නොසලකන වසම් (පේළියකට එක බැගින්)",
|
"ignore_domains": "නොසලකන වසම් (පේළියකට එක බැගින්)",
|
||||||
"ignore_domains_title": "නොසලකන වසම්",
|
"ignore_domains_title": "නොසලකන වසම්",
|
||||||
"ignore_domains_desc_stats": "සංඛ්යාලේඛනයෙහි මෙම වසම් සඳහා විමසුම් නොලියැවෙයි",
|
"ignore_domains_desc_stats": "මෙම නීති වලට ගැළපෙන විමසුම් සංඛ්යාලේඛනයට නොලියැවෙයි",
|
||||||
"ignore_domains_desc_query": "විමසුම් සටහනෙහි මෙම වසම් සඳහා විමසුම් නොලියැවෙයි",
|
"ignore_domains_desc_query": "විමසුම් සටහනට මෙම නීති වලට ගැළපෙන විමසුම් නොලියැවෙයි",
|
||||||
"interval_hours": "පැය {{count}}",
|
"interval_hours": "පැය {{count}}",
|
||||||
"interval_hours_plural": "පැය {{count}}",
|
"interval_hours_plural": "පැය {{count}}",
|
||||||
"filters_configuration": "පෙරහන් වින්යාසය",
|
"filters_configuration": "පෙරහන් වින්යාසය",
|
||||||
@@ -615,8 +619,8 @@
|
|||||||
"use_saved_key": "පෙර සුරැකි යතුර භාවිතා කරන්න",
|
"use_saved_key": "පෙර සුරැකි යතුර භාවිතා කරන්න",
|
||||||
"parental_control": "දෙමාපිය පාලනය",
|
"parental_control": "දෙමාපිය පාලනය",
|
||||||
"safe_browsing": "ආරක්ෂිත පිරික්සුම",
|
"safe_browsing": "ආරක්ෂිත පිරික්සුම",
|
||||||
"served_from_cache": "{{value}} <i>(නිහිතයෙන් ගැනිණි)</i>",
|
"served_from_cache_label": "නිහිතයෙන් සැපයිණි",
|
||||||
"form_error_password_length": "මුරපදය අවම වශයෙන් අකුරු {{value}} ක් දිගු විය යුතුමයි",
|
"form_error_password_length": "මුරපදය අකුරු {{min}} සහ {{value}} ක් අතර විය යුතුය",
|
||||||
"anonymizer_notification": "<0>සටහන:</0> අ.ජා.කෙ. නිර්නාමිකකරණය සබලයි. ඔබට එය <1>පොදු සැකසුම්</1> හරහා අබල කිරීමට හැකිය .",
|
"anonymizer_notification": "<0>සටහන:</0> අ.ජා.කෙ. නිර්නාමිකකරණය සබලයි. ඔබට එය <1>පොදු සැකසුම්</1> හරහා අබල කිරීමට හැකිය .",
|
||||||
"confirm_dns_cache_clear": "ඔබට ව.නා.ප. නිහිතය හිස් කිරීමට වුවමනාද?",
|
"confirm_dns_cache_clear": "ඔබට ව.නා.ප. නිහිතය හිස් කිරීමට වුවමනාද?",
|
||||||
"cache_cleared": "ව.නා.ප. නිහිතය හිස් කෙරිණි",
|
"cache_cleared": "ව.නා.ප. නිහිතය හිස් කෙරිණි",
|
||||||
@@ -646,6 +650,7 @@
|
|||||||
"log_and_stats_section_label": "විමසුම් සටහන හා සංඛ්යාලේඛන",
|
"log_and_stats_section_label": "විමසුම් සටහන හා සංඛ්යාලේඛන",
|
||||||
"ignore_query_log": "විමසුම් සටහනට මෙම අනුග්රාහකය යොදන්න එපා",
|
"ignore_query_log": "විමසුම් සටහනට මෙම අනුග්රාහකය යොදන්න එපා",
|
||||||
"ignore_statistics": "සංඛ්යාලේඛනයට මෙම අනුග්රාහකය යොදන්න එපා",
|
"ignore_statistics": "සංඛ්යාලේඛනයට මෙම අනුග්රාහකය යොදන්න එපා",
|
||||||
|
"schedule_services": "සේවා අවහිර විරාමය",
|
||||||
"schedule_invalid_select": "ආරම්භක වේලාව අවසන් වේලාවට කලින් විය යුතුය",
|
"schedule_invalid_select": "ආරම්භක වේලාව අවසන් වේලාවට කලින් විය යුතුය",
|
||||||
"schedule_select_days": "දවස් තෝරන්න",
|
"schedule_select_days": "දවස් තෝරන්න",
|
||||||
"schedule_timezone": "වේලා කලාපයක් තෝරන්න",
|
"schedule_timezone": "වේලා කලාපයක් තෝරන්න",
|
||||||
|
|||||||
@@ -461,7 +461,7 @@
|
|||||||
"form_enter_mac": "Skriv in MAC",
|
"form_enter_mac": "Skriv in MAC",
|
||||||
"form_enter_id": "Ange identifierare",
|
"form_enter_id": "Ange identifierare",
|
||||||
"form_add_id": "Lägg till identifierare",
|
"form_add_id": "Lägg till identifierare",
|
||||||
"form_client_name": "Skriv in klientnamn",
|
"form_client_name": "Ange klientnamn",
|
||||||
"name": "Namn",
|
"name": "Namn",
|
||||||
"client_name": "Klient {{id}}",
|
"client_name": "Klient {{id}}",
|
||||||
"client_global_settings": "Använda globala inställningar",
|
"client_global_settings": "Använda globala inställningar",
|
||||||
@@ -674,7 +674,6 @@
|
|||||||
"use_saved_key": "Använd den tidigare sparade nyckeln",
|
"use_saved_key": "Använd den tidigare sparade nyckeln",
|
||||||
"parental_control": "Föräldrakontroll",
|
"parental_control": "Föräldrakontroll",
|
||||||
"safe_browsing": "Säker surfning",
|
"safe_browsing": "Säker surfning",
|
||||||
"served_from_cache": "{{value}} <i>(levereras från cache)</i>",
|
|
||||||
"form_error_password_length": "Lösenordet måste vara {{min}} till {{max}} tecken långt",
|
"form_error_password_length": "Lösenordet måste vara {{min}} till {{max}} tecken långt",
|
||||||
"anonymizer_notification": "<0>Observera:</0> IP-anonymisering är aktiverad. Du kan inaktivera den i <1>Allmänna inställningar</1>.",
|
"anonymizer_notification": "<0>Observera:</0> IP-anonymisering är aktiverad. Du kan inaktivera den i <1>Allmänna inställningar</1>.",
|
||||||
"confirm_dns_cache_clear": "Är du säker på att du vill rensa DNS-cache?",
|
"confirm_dns_cache_clear": "Är du säker på att du vill rensa DNS-cache?",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"resolve_clients_title": "Увімкнути зворотне вирішення IP-адрес клієнтів",
|
"resolve_clients_title": "Увімкнути зворотне вирішення IP-адрес клієнтів",
|
||||||
"resolve_clients_desc": "Визначати доменні імена клієнтів за допомогою PTR-запитів до відповідних серверів — приватних DNS-серверів для локальних клієнтів та upstream-серверів для клієнтів з публічними IP-адресами.",
|
"resolve_clients_desc": "Визначати доменні імена клієнтів за допомогою PTR-запитів до відповідних серверів — приватних DNS-серверів для локальних клієнтів та upstream-серверів для клієнтів з публічними IP-адресами.",
|
||||||
"use_private_ptr_resolvers_title": "Використовувати приватні зворотні DNS-резолвери",
|
"use_private_ptr_resolvers_title": "Використовувати приватні зворотні DNS-резолвери",
|
||||||
"use_private_ptr_resolvers_desc": "Надсилати зворотні DNS-запити до вказаних серверів для клієнтів, що обслуговуються локально. Якщо вимкнено, AdGuard Home буде відповідати NXDOMAIN на всі такі PTR-запити, окрім запитів про клієнтів, що уже відомі завдяки DHCP, /etc/hosts тощо.",
|
"use_private_ptr_resolvers_desc": "Розвʼязувати запити PTR, SOA та NS для доменів ARPA, що містять приватні IP-адреси, через приватні вихідні сервери, DHCP, /etc/hosts тощо. Якщо вимкнено, AdGuard Home відповідатиме на всі такі запити з NXDOMAIN.",
|
||||||
"check_dhcp_servers": "Перевірити DHCP-сервери",
|
"check_dhcp_servers": "Перевірити DHCP-сервери",
|
||||||
"save_config": "Зберегти конфігурацію",
|
"save_config": "Зберегти конфігурацію",
|
||||||
"enabled_dhcp": "DHCP-сервер увімкнено",
|
"enabled_dhcp": "DHCP-сервер увімкнено",
|
||||||
@@ -343,10 +343,10 @@
|
|||||||
"known_tracker": "Відомі трекери",
|
"known_tracker": "Відомі трекери",
|
||||||
"install_welcome_title": "Вітаємо в AdGuard Home!",
|
"install_welcome_title": "Вітаємо в AdGuard Home!",
|
||||||
"install_welcome_desc": "AdGuard Home — це мережевий DNS-сервер, що блокує рекламу та відстеження. Його мета — надати вам контроль над усією мережею та всіма пристроями в ній без потреби використання програми на стороні клієнта.",
|
"install_welcome_desc": "AdGuard Home — це мережевий DNS-сервер, що блокує рекламу та відстеження. Його мета — надати вам контроль над усією мережею та всіма пристроями в ній без потреби використання програми на стороні клієнта.",
|
||||||
"install_settings_title": "Веб-інтерфейс адміністратора",
|
"install_settings_title": "Вебінтерфейс адміністратора",
|
||||||
"install_settings_listen": "Мережевий інтерфейс",
|
"install_settings_listen": "Мережевий інтерфейс",
|
||||||
"install_settings_port": "Порт",
|
"install_settings_port": "Порт",
|
||||||
"install_settings_interface_link": "Веб-інтерфейс адміністратора AdGuard Home буде доступний за такими адресами:",
|
"install_settings_interface_link": "Вебінтерфейс адміністратора AdGuard Home буде доступний за такими адресами:",
|
||||||
"form_error_port": "Уведіть правильне значення порту",
|
"form_error_port": "Уведіть правильне значення порту",
|
||||||
"install_settings_dns": "DNS-сервер",
|
"install_settings_dns": "DNS-сервер",
|
||||||
"install_settings_dns_desc": "Вам потрібно буде налаштувати свої пристрої або маршрутизатор для використання DNS-сервера за такими адресами:",
|
"install_settings_dns_desc": "Вам потрібно буде налаштувати свої пристрої або маршрутизатор для використання DNS-сервера за такими адресами:",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"resolve_clients_title": "啟用用戶端的 IP 位址之反向的解析",
|
"resolve_clients_title": "啟用用戶端的 IP 位址之反向的解析",
|
||||||
"resolve_clients_desc": "透過傳送指標(PTR)查詢到對應的解析器(私人 DNS 伺服器供區域的用戶端,上游的伺服器供有公共 IP 位址的用戶端),反向地解析用戶端的 IP 位址變為它們的主機名稱。",
|
"resolve_clients_desc": "透過傳送指標(PTR)查詢到對應的解析器(私人 DNS 伺服器供區域的用戶端,上游的伺服器供有公共 IP 位址的用戶端),反向地解析用戶端的 IP 位址變為它們的主機名稱。",
|
||||||
"use_private_ptr_resolvers_title": "使用私人反向的 DNS 解析器",
|
"use_private_ptr_resolvers_title": "使用私人反向的 DNS 解析器",
|
||||||
"use_private_ptr_resolvers_desc": "使用私人上游伺服器、DHCP、/etc/hosts 等方式解析包含私人 IP 位址的 ARPA 網域的 PTR、SOA 和 NS 請求。如果禁用,AdGuard Home 將對所有此類請求以 NXDOMAIN 回應。",
|
"use_private_ptr_resolvers_desc": "通過私人上游伺服器、DHCP、/etc/hosts 等等,對包含私人 IP 位址的 ARPA 網域解析 PTR、SOA 和 NS 請求。如果被禁用,AdGuard Home 將對所有此類的請求以 NXDOMAIN 回覆。",
|
||||||
"check_dhcp_servers": "檢查動態主機設定協定(DHCP)伺服器",
|
"check_dhcp_servers": "檢查動態主機設定協定(DHCP)伺服器",
|
||||||
"save_config": "儲存配置",
|
"save_config": "儲存配置",
|
||||||
"enabled_dhcp": "動態主機設定協定(DHCP)伺服器被啟用",
|
"enabled_dhcp": "動態主機設定協定(DHCP)伺服器被啟用",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export const getStats = () => async (dispatch: any) => {
|
|||||||
const normalizedTopClients = normalizeTopStats(stats.top_clients);
|
const normalizedTopClients = normalizeTopStats(stats.top_clients);
|
||||||
|
|
||||||
const clientsParams = getParamsForClientsSearch(normalizedTopClients, 'name');
|
const clientsParams = getParamsForClientsSearch(normalizedTopClients, 'name');
|
||||||
const clients = await apiClient.findClients(clientsParams);
|
const clients = await apiClient.searchClients(clientsParams);
|
||||||
const topClientsWithInfo = addClientInfo(normalizedTopClients, clients, 'name');
|
const topClientsWithInfo = addClientInfo(normalizedTopClients, clients, 'name');
|
||||||
|
|
||||||
const normalizedStats = {
|
const normalizedStats = {
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ class Api {
|
|||||||
// Per-client settings
|
// Per-client settings
|
||||||
GET_CLIENTS = { path: 'clients', method: 'GET' };
|
GET_CLIENTS = { path: 'clients', method: 'GET' };
|
||||||
|
|
||||||
FIND_CLIENTS = { path: 'clients/find', method: 'GET' };
|
SEARCH_CLIENTS = { path: 'clients/search', method: 'POST' };
|
||||||
|
|
||||||
ADD_CLIENT = { path: 'clients/add', method: 'POST' };
|
ADD_CLIENT = { path: 'clients/add', method: 'POST' };
|
||||||
|
|
||||||
@@ -453,11 +453,12 @@ class Api {
|
|||||||
return this.makeRequest(path, method, parameters);
|
return this.makeRequest(path, method, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
findClients(params: any) {
|
searchClients(config: any) {
|
||||||
const { path, method } = this.FIND_CLIENTS;
|
const { path, method } = this.SEARCH_CLIENTS;
|
||||||
const url = getPathWithQueryString(path, params);
|
const parameters = {
|
||||||
|
data: config,
|
||||||
return this.makeRequest(url, method);
|
};
|
||||||
|
return this.makeRequest(path, method, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNS access settings
|
// DNS access settings
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ interface RowProps {
|
|||||||
|
|
||||||
const Row = ({ label, count, response_status, tooltipTitle, translationComponents }: RowProps) => {
|
const Row = ({ label, count, response_status, tooltipTitle, translationComponents }: RowProps) => {
|
||||||
const content = response_status ? (
|
const content = response_status ? (
|
||||||
<LogsSearchLink response_status={response_status}>{formatNumber(count)}</LogsSearchLink>
|
<LogsSearchLink response_status={response_status}>{count}</LogsSearchLink>
|
||||||
) : (
|
) : (
|
||||||
count
|
count
|
||||||
);
|
);
|
||||||
@@ -77,16 +77,16 @@ const Counters = ({ refreshButton, subtitle }: CountersProps) => {
|
|||||||
? t('number_of_dns_query_hours', { count: msToHours(interval) })
|
? t('number_of_dns_query_hours', { count: msToHours(interval) })
|
||||||
: t('number_of_dns_query_days', { count: msToDays(interval) });
|
: t('number_of_dns_query_days', { count: msToDays(interval) });
|
||||||
|
|
||||||
const rows = [
|
const rows: RowProps[] = [
|
||||||
{
|
{
|
||||||
label: 'dns_query',
|
label: 'dns_query',
|
||||||
count: numDnsQueries.toString(),
|
count: formatNumber(numDnsQueries),
|
||||||
tooltipTitle: dnsQueryTooltip,
|
tooltipTitle: dnsQueryTooltip,
|
||||||
response_status: RESPONSE_FILTER.ALL.QUERY,
|
response_status: RESPONSE_FILTER.ALL.QUERY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'blocked_by',
|
label: 'blocked_by',
|
||||||
count: numBlockedFiltering.toString(),
|
count: formatNumber(numBlockedFiltering),
|
||||||
tooltipTitle: 'number_of_dns_query_blocked_24_hours',
|
tooltipTitle: 'number_of_dns_query_blocked_24_hours',
|
||||||
response_status: RESPONSE_FILTER.BLOCKED.QUERY,
|
response_status: RESPONSE_FILTER.BLOCKED.QUERY,
|
||||||
|
|
||||||
@@ -98,19 +98,19 @@ const Counters = ({ refreshButton, subtitle }: CountersProps) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'stats_malware_phishing',
|
label: 'stats_malware_phishing',
|
||||||
count: numReplacedSafebrowsing.toString(),
|
count: formatNumber(numReplacedSafebrowsing),
|
||||||
tooltipTitle: 'number_of_dns_query_blocked_24_hours_by_sec',
|
tooltipTitle: 'number_of_dns_query_blocked_24_hours_by_sec',
|
||||||
response_status: RESPONSE_FILTER.BLOCKED_THREATS.QUERY,
|
response_status: RESPONSE_FILTER.BLOCKED_THREATS.QUERY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'stats_adult',
|
label: 'stats_adult',
|
||||||
count: numReplacedParental.toString(),
|
count: formatNumber(numReplacedParental),
|
||||||
tooltipTitle: 'number_of_dns_query_blocked_24_hours_adult',
|
tooltipTitle: 'number_of_dns_query_blocked_24_hours_adult',
|
||||||
response_status: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.QUERY,
|
response_status: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.QUERY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'enforced_save_search',
|
label: 'enforced_save_search',
|
||||||
count: numReplacedSafesearch.toString(),
|
count: formatNumber(numReplacedSafesearch),
|
||||||
tooltipTitle: 'number_of_dns_query_to_safe_search',
|
tooltipTitle: 'number_of_dns_query_to_safe_search',
|
||||||
response_status: RESPONSE_FILTER.SAFE_SEARCH.QUERY,
|
response_status: RESPONSE_FILTER.SAFE_SEARCH.QUERY,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Card from '../ui/Card';
|
|||||||
|
|
||||||
import DomainCell from './DomainCell';
|
import DomainCell from './DomainCell';
|
||||||
import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, TABLES_MIN_ROWS } from '../../helpers/constants';
|
import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, TABLES_MIN_ROWS } from '../../helpers/constants';
|
||||||
|
import { formatNumber } from '../../helpers/helpers';
|
||||||
|
|
||||||
interface TimeCellProps {
|
interface TimeCellProps {
|
||||||
value?: string | number;
|
value?: string | number;
|
||||||
@@ -20,7 +21,7 @@ const TimeCell = ({ value }: TimeCellProps) => {
|
|||||||
return '–';
|
return '–';
|
||||||
}
|
}
|
||||||
|
|
||||||
const valueInMilliseconds = round(Number(value) * 1000);
|
const valueInMilliseconds = formatNumber(round(Number(value) * 1000));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="logs__row o-hidden">
|
<div className="logs__row o-hidden">
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ const Dashboard = ({
|
|||||||
}}
|
}}
|
||||||
disabled={processingProtection}>
|
disabled={processingProtection}>
|
||||||
{protectionDisabledDuration
|
{protectionDisabledDuration
|
||||||
? `${t('enable_protection_timer')} ${getRemaningTimeText(protectionDisabledDuration)}`
|
? `${t('enable_protection_timer', { time: getRemaningTimeText(protectionDisabledDuration) })}`
|
||||||
: getProtectionBtnText(protectionEnabled)}
|
: getProtectionBtnText(protectionEnabled)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ const ClientsTable = ({
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <LogsSearchLink search={row.original.ids[0]}>{content}</LogsSearchLink>;
|
return <LogsSearchLink search={row.original.name}>{content}</LogsSearchLink>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,6 +14,17 @@
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.guide__list {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.guide__list {
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.guide__address {
|
.guide__address {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 7px;
|
margin-bottom: 7px;
|
||||||
|
|||||||
@@ -33,13 +33,13 @@ const SetupGuide = ({
|
|||||||
<Trans>install_devices_address</Trans>:
|
<Trans>install_devices_address</Trans>:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-3">
|
<ul className="guide__list">
|
||||||
{dnsAddresses.map((ip: any) => (
|
{dnsAddresses.map((ip: any) => (
|
||||||
<li key={ip} className="guide__address">
|
<li key={ip} className="guide__address">
|
||||||
{ip}
|
{ip}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Guide dnsAddresses={dnsAddresses} />
|
<Guide dnsAddresses={dnsAddresses} />
|
||||||
|
|||||||
@@ -238,6 +238,12 @@ export default {
|
|||||||
"homepage": "https://github.com/hagezi/dns-blocklists",
|
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_51.txt"
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_51.txt"
|
||||||
},
|
},
|
||||||
|
"hagezi_samsung_tracker_blocklist": {
|
||||||
|
"name": "HaGeZi's Samsung Tracker Blocklist",
|
||||||
|
"categoryId": "other",
|
||||||
|
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||||
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_61.txt"
|
||||||
|
},
|
||||||
"hagezi_the_worlds_most_abused_tlds": {
|
"hagezi_the_worlds_most_abused_tlds": {
|
||||||
"name": "HaGeZi's The World's Most Abused TLDs",
|
"name": "HaGeZi's The World's Most Abused TLDs",
|
||||||
"categoryId": "security",
|
"categoryId": "security",
|
||||||
@@ -256,6 +262,12 @@ export default {
|
|||||||
"homepage": "https://github.com/hagezi/dns-blocklists",
|
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt"
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt"
|
||||||
},
|
},
|
||||||
|
"hagezi_windows_office_tracker_blocklist": {
|
||||||
|
"name": "HaGeZi's Windows/Office Tracker Blocklist",
|
||||||
|
"categoryId": "other",
|
||||||
|
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||||
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_63.txt"
|
||||||
|
},
|
||||||
"hagezi_xiaomi_tracking_blocklist": {
|
"hagezi_xiaomi_tracking_blocklist": {
|
||||||
"name": "HaGeZi's Xiaomi Tracker Blocklist",
|
"name": "HaGeZi's Xiaomi Tracker Blocklist",
|
||||||
"categoryId": "other",
|
"categoryId": "other",
|
||||||
@@ -346,17 +358,17 @@ export default {
|
|||||||
"homepage": "https://github.com/uBlockOrigin/uAssets",
|
"homepage": "https://github.com/uBlockOrigin/uAssets",
|
||||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt"
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt"
|
||||||
},
|
},
|
||||||
|
"ukrainian_security_filter": {
|
||||||
|
"name": "Ukrainian Security Filter",
|
||||||
|
"categoryId": "other",
|
||||||
|
"homepage": "https://github.com/braveinnovators/ukrainian-security-filter",
|
||||||
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_62.txt"
|
||||||
|
},
|
||||||
"urlhaus_filter_online": {
|
"urlhaus_filter_online": {
|
||||||
"name": "Malicious URL Blocklist (URLHaus)",
|
"name": "Malicious URL Blocklist (URLHaus)",
|
||||||
"categoryId": "security",
|
"categoryId": "security",
|
||||||
"homepage": "https://urlhaus.abuse.ch/",
|
"homepage": "https://urlhaus.abuse.ch/",
|
||||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt"
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt"
|
||||||
},
|
|
||||||
"windowsspyblocker_hosts_spy_rules": {
|
|
||||||
"name": "WindowsSpyBlocker - Hosts spy rules",
|
|
||||||
"categoryId": "other",
|
|
||||||
"homepage": "https://github.com/crazy-max/WindowsSpyBlocker",
|
|
||||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -451,13 +451,10 @@ export const getParamsForClientsSearch = (data: any, param: any, additionalParam
|
|||||||
clients.add(e[additionalParam]);
|
clients.add(e[additionalParam]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const params = {};
|
|
||||||
const ids = Array.from(clients.values());
|
|
||||||
ids.forEach((id, i) => {
|
|
||||||
params[`ip${i}`] = id;
|
|
||||||
});
|
|
||||||
|
|
||||||
return params;
|
return {
|
||||||
|
clients: Array.from(clients).map((id) => ({ id })),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -524,7 +521,7 @@ export const getObjDiff = (initialValues: any, values: any) =>
|
|||||||
* @param num {number} to format
|
* @param num {number} to format
|
||||||
* @returns {string} Returns a string with a language-sensitive representation of this number
|
* @returns {string} Returns a string with a language-sensitive representation of this number
|
||||||
*/
|
*/
|
||||||
export const formatNumber = (num: any) => {
|
export const formatNumber = (num: number): string => {
|
||||||
const currentLanguage = i18n.languages[0] || DEFAULT_LANGUAGE;
|
const currentLanguage = i18n.languages[0] || DEFAULT_LANGUAGE;
|
||||||
return num.toLocaleString(currentLanguage);
|
return num.toLocaleString(currentLanguage);
|
||||||
};
|
};
|
||||||
@@ -673,9 +670,16 @@ export const countClientsStatistics = (ids: any, autoClients: any) => {
|
|||||||
* @param {function} t translate
|
* @param {function} t translate
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export const formatElapsedMs = (elapsedMs: any, t: any) => {
|
export const formatElapsedMs = (elapsedMs: string, t: (key: string) => string) => {
|
||||||
const formattedElapsedMs = parseInt(elapsedMs, 10) || parseFloat(elapsedMs).toFixed(2);
|
const parsedElapsedMs = parseInt(elapsedMs, 10);
|
||||||
return `${formattedElapsedMs} ${t('milliseconds_abbreviation')}`;
|
|
||||||
|
if (Number.isNaN(parsedElapsedMs)) {
|
||||||
|
return elapsedMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedMs = formatNumber(parsedElapsedMs);
|
||||||
|
|
||||||
|
return `${formattedMs} ${t('milliseconds_abbreviation')}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -757,12 +761,9 @@ type NestedObject = {
|
|||||||
order: number;
|
order: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getObjectKeysSorted = <
|
export const getObjectKeysSorted = <T extends Record<string, NestedObject>, K extends keyof NestedObject>(
|
||||||
T extends Record<string, NestedObject>,
|
|
||||||
K extends keyof NestedObject
|
|
||||||
>(
|
|
||||||
object: T,
|
object: T,
|
||||||
sortKey: K
|
sortKey: K,
|
||||||
): string[] => {
|
): string[] => {
|
||||||
return Object.entries(object)
|
return Object.entries(object)
|
||||||
.sort(([, a], [, b]) => (a[sortKey] as number) - (b[sortKey] as number))
|
.sort(([, a], [, b]) => (a[sortKey] as number) - (b[sortKey] as number))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"timeUpdated": "2024-09-30T10:04:46.112Z",
|
"timeUpdated": "2025-01-13T10:04:54.031Z",
|
||||||
"categories": {
|
"categories": {
|
||||||
"0": "audio_video_player",
|
"0": "audio_video_player",
|
||||||
"1": "comments",
|
"1": "comments",
|
||||||
@@ -2515,6 +2515,13 @@
|
|||||||
"url": "http://www.ancoramediasolutions.com/",
|
"url": "http://www.ancoramediasolutions.com/",
|
||||||
"companyId": "ancora"
|
"companyId": "ancora"
|
||||||
},
|
},
|
||||||
|
"android": {
|
||||||
|
"name": "Android",
|
||||||
|
"categoryId": 101,
|
||||||
|
"url": "https://www.android.com/",
|
||||||
|
"companyId": "google",
|
||||||
|
"source": "AdGuard"
|
||||||
|
},
|
||||||
"anetwork": {
|
"anetwork": {
|
||||||
"name": "Anetwork",
|
"name": "Anetwork",
|
||||||
"categoryId": 4,
|
"categoryId": 4,
|
||||||
@@ -8195,7 +8202,7 @@
|
|||||||
"google_dns": {
|
"google_dns": {
|
||||||
"name": "Google DNS",
|
"name": "Google DNS",
|
||||||
"categoryId": 10,
|
"categoryId": 10,
|
||||||
"url": "hhttps://dns.google/",
|
"url": "https://dns.google/",
|
||||||
"companyId": "google",
|
"companyId": "google",
|
||||||
"source": "AdGuard"
|
"source": "AdGuard"
|
||||||
},
|
},
|
||||||
@@ -9765,6 +9772,13 @@
|
|||||||
"url": "http://www.jetinteractive.com.au/",
|
"url": "http://www.jetinteractive.com.au/",
|
||||||
"companyId": "jet_interactive"
|
"companyId": "jet_interactive"
|
||||||
},
|
},
|
||||||
|
"jetbrains": {
|
||||||
|
"name": "JetBrains",
|
||||||
|
"categoryId": 8,
|
||||||
|
"url": "https://www.jetbrains.com/",
|
||||||
|
"companyId": "jetbrains",
|
||||||
|
"source": "AdGuard"
|
||||||
|
},
|
||||||
"jetlore": {
|
"jetlore": {
|
||||||
"name": "Jetlore",
|
"name": "Jetlore",
|
||||||
"categoryId": 6,
|
"categoryId": 6,
|
||||||
@@ -13980,6 +13994,13 @@
|
|||||||
"url": "http://prostor-lite.ru/",
|
"url": "http://prostor-lite.ru/",
|
||||||
"companyId": "prostor"
|
"companyId": "prostor"
|
||||||
},
|
},
|
||||||
|
"proton_ag": {
|
||||||
|
"name": "Proton AG",
|
||||||
|
"categoryId": 2,
|
||||||
|
"url": "https://proton.me/",
|
||||||
|
"companyId": "proton_foundation",
|
||||||
|
"source": "AdGuard"
|
||||||
|
},
|
||||||
"provide_support": {
|
"provide_support": {
|
||||||
"name": "Provide Support",
|
"name": "Provide Support",
|
||||||
"categoryId": 2,
|
"categoryId": 2,
|
||||||
@@ -15654,7 +15675,7 @@
|
|||||||
"shareaholic": {
|
"shareaholic": {
|
||||||
"name": "Shareaholic",
|
"name": "Shareaholic",
|
||||||
"categoryId": 6,
|
"categoryId": 6,
|
||||||
"url": "hhttps://www.shareaholic.com/",
|
"url": "https://www.shareaholic.com/",
|
||||||
"companyId": "shareaholic"
|
"companyId": "shareaholic"
|
||||||
},
|
},
|
||||||
"shareasale": {
|
"shareasale": {
|
||||||
@@ -20827,6 +20848,7 @@
|
|||||||
"anametrix.net": "anametrix",
|
"anametrix.net": "anametrix",
|
||||||
"ancestrycdn.com": "ancestry_cdn",
|
"ancestrycdn.com": "ancestry_cdn",
|
||||||
"ancoraplatform.com": "ancora",
|
"ancoraplatform.com": "ancora",
|
||||||
|
"android.com": "android",
|
||||||
"anetwork.ir": "anetwork",
|
"anetwork.ir": "anetwork",
|
||||||
"aniview.com": "aniview.com",
|
"aniview.com": "aniview.com",
|
||||||
"a-ads.com": "anonymousads",
|
"a-ads.com": "anonymousads",
|
||||||
@@ -21872,6 +21894,7 @@
|
|||||||
"fastly-insights.com": "fastly_insights",
|
"fastly-insights.com": "fastly_insights",
|
||||||
"fastly.net": "fastlylb.net",
|
"fastly.net": "fastlylb.net",
|
||||||
"fastlylb.net": "fastlylb.net",
|
"fastlylb.net": "fastlylb.net",
|
||||||
|
"fastly-edge.com": "fastlylb.net",
|
||||||
"fastly-masque.net": "fastlylb.net",
|
"fastly-masque.net": "fastlylb.net",
|
||||||
"fastpic.ru": "fastpic.ru",
|
"fastpic.ru": "fastpic.ru",
|
||||||
"fmpub.net": "federated_media",
|
"fmpub.net": "federated_media",
|
||||||
@@ -22394,6 +22417,7 @@
|
|||||||
"adservice.google.pl": "google_marketing",
|
"adservice.google.pl": "google_marketing",
|
||||||
"adservice.google.ru": "google_marketing",
|
"adservice.google.ru": "google_marketing",
|
||||||
"adservice.google.vg": "google_marketing",
|
"adservice.google.vg": "google_marketing",
|
||||||
|
"adtrafficquality.google": "google_marketing",
|
||||||
"dai.google.com": "google_marketing",
|
"dai.google.com": "google_marketing",
|
||||||
"doubleclickbygoogle.com": "google_marketing",
|
"doubleclickbygoogle.com": "google_marketing",
|
||||||
"googlesyndication-cn.com": "google_marketing",
|
"googlesyndication-cn.com": "google_marketing",
|
||||||
@@ -22747,6 +22771,22 @@
|
|||||||
"jeeng.com": "jeeng",
|
"jeeng.com": "jeeng",
|
||||||
"api.jeeng.com": "jeeng_widgets",
|
"api.jeeng.com": "jeeng_widgets",
|
||||||
"phone-analytics.com": "jet_interactive",
|
"phone-analytics.com": "jet_interactive",
|
||||||
|
"grazie.ai": "jetbrains",
|
||||||
|
"intellij.net": "jetbrains",
|
||||||
|
"jb.gg": "jetbrains",
|
||||||
|
"jetbrains.ai": "jetbrains",
|
||||||
|
"jetbrains.com": "jetbrains",
|
||||||
|
"jetbrains.com.cn": "jetbrains",
|
||||||
|
"jetbrains.dev": "jetbrains",
|
||||||
|
"jetbrains.net": "jetbrains",
|
||||||
|
"jetbrains.org": "jetbrains",
|
||||||
|
"jetbrains.ru": "jetbrains",
|
||||||
|
"jetbrains.space": "jetbrains",
|
||||||
|
"kotl.in": "jetbrains",
|
||||||
|
"kotlinconf.com": "jetbrains",
|
||||||
|
"kotlinlang.org": "jetbrains",
|
||||||
|
"myjetbrains.com": "jetbrains",
|
||||||
|
"talkingkotlin.com": "jetbrains",
|
||||||
"jetlore.com": "jetlore",
|
"jetlore.com": "jetlore",
|
||||||
"pixel.wp.com": "jetpack",
|
"pixel.wp.com": "jetpack",
|
||||||
"stats.wp.com": "jetpack",
|
"stats.wp.com": "jetpack",
|
||||||
@@ -23320,6 +23360,7 @@
|
|||||||
"mrskincash.com": "mrskincash",
|
"mrskincash.com": "mrskincash",
|
||||||
"a-msedge.net": "msedge",
|
"a-msedge.net": "msedge",
|
||||||
"b-msedge.net": "msedge",
|
"b-msedge.net": "msedge",
|
||||||
|
"dual-s-msedge.net": "msedge",
|
||||||
"e-msedge.net": "msedge",
|
"e-msedge.net": "msedge",
|
||||||
"k-msedge.net": "msedge",
|
"k-msedge.net": "msedge",
|
||||||
"l-msedge.net": "msedge",
|
"l-msedge.net": "msedge",
|
||||||
@@ -23766,6 +23807,7 @@
|
|||||||
"tr.prospecteye.com": "prospecteye",
|
"tr.prospecteye.com": "prospecteye",
|
||||||
"prosperent.com": "prosperent",
|
"prosperent.com": "prosperent",
|
||||||
"prostor-lite.ru": "prostor",
|
"prostor-lite.ru": "prostor",
|
||||||
|
"reports.proton.me": "proton_ag",
|
||||||
"providesupport.com": "provide_support",
|
"providesupport.com": "provide_support",
|
||||||
"proximic.com": "proximic",
|
"proximic.com": "proximic",
|
||||||
"proxistore.com": "proxistore.com",
|
"proxistore.com": "proxistore.com",
|
||||||
|
|||||||
52
go.mod
52
go.mod
@@ -1,24 +1,25 @@
|
|||||||
module github.com/AdguardTeam/AdGuardHome
|
module github.com/AdguardTeam/AdGuardHome
|
||||||
|
|
||||||
go 1.23.2
|
go 1.23.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdguardTeam/dnsproxy v0.73.2
|
github.com/AdguardTeam/dnsproxy v0.73.5
|
||||||
github.com/AdguardTeam/golibs v0.27.0
|
github.com/AdguardTeam/golibs v0.31.0
|
||||||
github.com/AdguardTeam/urlfilter v0.19.0
|
github.com/AdguardTeam/urlfilter v0.20.0
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.3.0
|
github.com/ameshkov/dnscrypt/v2 v2.3.0
|
||||||
github.com/bluele/gcache v0.0.2
|
github.com/bluele/gcache v0.0.2
|
||||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
|
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
|
||||||
github.com/digineo/go-ipset/v2 v2.2.1
|
github.com/digineo/go-ipset/v2 v2.2.1
|
||||||
github.com/dimfeld/httptreemux/v5 v5.5.0
|
github.com/fsnotify/fsnotify v1.8.0
|
||||||
github.com/fsnotify/fsnotify v1.7.0
|
// TODO(e.burkov): This package is deprecated; find a new one or use our
|
||||||
github.com/go-ping/ping v1.1.0
|
// own code for that. Perhaps, use gopacket.
|
||||||
|
github.com/go-ping/ping v1.2.0
|
||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
github.com/google/renameio/v2 v2.0.0
|
github.com/google/renameio/v2 v2.0.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49
|
github.com/insomniacslk/dhcp v0.0.0-20241203100832-a481575ed0ef
|
||||||
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
|
||||||
@@ -27,15 +28,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.61
|
github.com/miekg/dns v1.1.62
|
||||||
github.com/quic-go/quic-go v0.47.0
|
github.com/quic-go/quic-go v0.48.2
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/ti-mo/netfilter v0.5.2
|
github.com/ti-mo/netfilter v0.5.2
|
||||||
go.etcd.io/bbolt v1.3.10
|
go.etcd.io/bbolt v1.3.11
|
||||||
golang.org/x/crypto v0.26.0
|
golang.org/x/crypto v0.31.0
|
||||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa
|
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
|
||||||
golang.org/x/net v0.28.0
|
golang.org/x/net v0.33.0
|
||||||
golang.org/x/sys v0.24.0
|
golang.org/x/sys v0.28.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.1
|
howett.net/plist v1.0.1
|
||||||
@@ -48,22 +49,19 @@ require (
|
|||||||
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
|
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20240521024322-9665fa269a30 // indirect
|
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||||
github.com/mdlayher/socket v0.5.1 // indirect
|
github.com/mdlayher/socket v0.5.1 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.17.3 // indirect
|
github.com/onsi/ginkgo/v2 v2.22.1 // 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.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // 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.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||||
go.uber.org/mock v0.4.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
golang.org/x/mod v0.20.0 // indirect
|
golang.org/x/mod v0.22.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
golang.org/x/text v0.17.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
golang.org/x/tools v0.24.0 // indirect
|
golang.org/x/tools v0.28.0 // indirect
|
||||||
gonum.org/v1/gonum v0.15.0 // indirect
|
gonum.org/v1/gonum v0.15.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(a.garipov): Remove once https://github.com/quic-go/quic-go/pull/4685 is merged.
|
|
||||||
replace github.com/quic-go/quic-go => github.com/ainar-g/quic-go v0.0.0-20240930125330-446bd86056fd
|
|
||||||
|
|||||||
102
go.sum
102
go.sum
@@ -1,17 +1,15 @@
|
|||||||
github.com/AdguardTeam/dnsproxy v0.73.2 h1:O6wRXzHsnWL5TkhYcuLWCShVFF0X5RFI6qUmq1ZFVsQ=
|
github.com/AdguardTeam/dnsproxy v0.73.5 h1:3EiVaTfmuW6PcJGbqloR6ZdHACsrYkm9z0eH8ZQTZnQ=
|
||||||
github.com/AdguardTeam/dnsproxy v0.73.2/go.mod h1:zD5WfTctbRvYYk8PS39h6/OT84NTu6QxKbAiBN5PUcI=
|
github.com/AdguardTeam/dnsproxy v0.73.5/go.mod h1:Oqw+k7LyjDObfYzXYCkpgtirbzbUrmotr92jrb3N09I=
|
||||||
github.com/AdguardTeam/golibs v0.27.0 h1:YxCFK6HBGp/ZXp3bv5uei+oLH12UfIYB8u2rh1B6nnU=
|
github.com/AdguardTeam/golibs v0.31.0 h1:Z0oPfLTLw6iZmpE58dePy2Bel0MaX+lnDwtFEE5EmIo=
|
||||||
github.com/AdguardTeam/golibs v0.27.0/go.mod h1:iWdjXPCwmK2g2FKIb/OwEPnovSXeMqRhI8FWLxF5oxE=
|
github.com/AdguardTeam/golibs v0.31.0/go.mod h1:wIkZ9o2UnppeW6/YD7yJB71dYbMhiuC1Fh/I2ElW7GQ=
|
||||||
github.com/AdguardTeam/urlfilter v0.19.0 h1:q7eH13+yNETlpD/VD3u5rLQOripcUdEktqZFy+KiQLk=
|
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=
|
||||||
github.com/AdguardTeam/urlfilter v0.19.0/go.mod h1:+N54ZvxqXYLnXuvpaUhK2exDQW+djZBRSb6F6j0rkBY=
|
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk=
|
||||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
||||||
github.com/ainar-g/quic-go v0.0.0-20240930125330-446bd86056fd h1:mw4LqrCiv3vcKuCxBRg7kA17xfHKM+9hZgFWmyhe/AY=
|
|
||||||
github.com/ainar-g/quic-go v0.0.0-20240930125330-446bd86056fd/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.3.0 h1:pDXDF7eFa6Lw+04C0hoMh8kCAQM8NwUdFEllSP2zNLs=
|
github.com/ameshkov/dnscrypt/v2 v2.3.0 h1:pDXDF7eFa6Lw+04C0hoMh8kCAQM8NwUdFEllSP2zNLs=
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.3.0/go.mod h1:N5hDwgx2cNb4Ay7AhvOSKst+eUiOZ/vbKRO9qMpQttE=
|
github.com/ameshkov/dnscrypt/v2 v2.3.0/go.mod h1:N5hDwgx2cNb4Ay7AhvOSKst+eUiOZ/vbKRO9qMpQttE=
|
||||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||||
@@ -27,16 +25,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1MX77g=
|
github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1MX77g=
|
||||||
github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU=
|
github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU=
|
||||||
github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ=
|
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||||
github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
|
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
|
||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
|
github.com/go-ping/ping v1.2.0 h1:vsJ8slZBZAXNCK4dPcI2PEE9eM9n9RbXbGouVQ/Y4yQ=
|
||||||
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
github.com/go-ping/ping v1.2.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
@@ -46,8 +42,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
github.com/google/pprof v0.0.0-20240521024322-9665fa269a30 h1:r6YdmbD41tGHeCWDyHF691LWtL7D1iSTyJaKejTWwVU=
|
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||||
github.com/google/pprof v0.0.0-20240521024322-9665fa269a30/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||||
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
|
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
|
||||||
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
|
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
|
||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
@@ -55,8 +51,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.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/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 h1:/OuvSMGT9+xnyZ+7MZQ1zdngaCCAdPoSw8B/uurZ7pg=
|
github.com/insomniacslk/dhcp v0.0.0-20241203100832-a481575ed0ef h1:NzQKDfd5ZOPnuZYf9MnRee8x2qecsVqzsnaLjEZiBko=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
|
github.com/insomniacslk/dhcp v0.0.0-20241203100832-a481575ed0ef/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
|
||||||
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.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
|
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
|
||||||
@@ -80,14 +76,14 @@ github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5
|
|||||||
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
|
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
|
||||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||||
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
|
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||||
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
|
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||||
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.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU=
|
github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM=
|
||||||
github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
|
github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM=
|
||||||
github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
|
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
|
||||||
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
|
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||||
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.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||||
@@ -101,6 +97,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
|
|||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
|
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||||
|
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
@@ -109,8 +107,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
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.2 h1:CTjOwFuNNeZ9QPdRXt1MZFLFUf84cKtiQutNauHWd40=
|
github.com/ti-mo/netfilter v0.5.2 h1:CTjOwFuNNeZ9QPdRXt1MZFLFUf84cKtiQutNauHWd40=
|
||||||
github.com/ti-mo/netfilter v0.5.2/go.mod h1:Btx3AtFiOVdHReTDmP9AE+hlkOcvIy403u7BXXbWZKo=
|
github.com/ti-mo/netfilter v0.5.2/go.mod h1:Btx3AtFiOVdHReTDmP9AE+hlkOcvIy403u7BXXbWZKo=
|
||||||
@@ -122,32 +120,32 @@ github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8
|
|||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
|
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||||
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
|
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
|
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
|
||||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||||
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
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=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-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.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
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=
|
||||||
@@ -158,25 +156,25 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
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-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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||||
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=
|
||||||
gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=
|
gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0=
|
||||||
gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=
|
gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o=
|
||||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -14,12 +14,6 @@ import (
|
|||||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTP scheme constants.
|
|
||||||
const (
|
|
||||||
SchemeHTTP = "http"
|
|
||||||
SchemeHTTPS = "https"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RegisterFunc is the function that sets the handler to handle the URL for the
|
// RegisterFunc is the function that sets the handler to handle the URL for the
|
||||||
// method.
|
// method.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ import (
|
|||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default file and directory permissions.
|
// Default file, binary, and directory permissions.
|
||||||
const (
|
const (
|
||||||
DefaultPermDir fs.FileMode = 0o700
|
DefaultPermDir fs.FileMode = 0o700
|
||||||
|
DefaultPermExe fs.FileMode = 0o700
|
||||||
DefaultPermFile fs.FileMode = 0o600
|
DefaultPermFile fs.FileMode = 0o600
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -145,16 +146,6 @@ func IsOpenWrt() (ok bool) {
|
|||||||
return isOpenWrt()
|
return isOpenWrt()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyReconfigureSignal notifies c on receiving reconfigure signals.
|
|
||||||
func NotifyReconfigureSignal(c chan<- os.Signal) {
|
|
||||||
notifyReconfigureSignal(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsReconfigureSignal returns true if sig is a reconfigure signal.
|
|
||||||
func IsReconfigureSignal(sig os.Signal) (ok bool) {
|
|
||||||
return isReconfigureSignal(sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendShutdownSignal sends the shutdown signal to the channel.
|
// SendShutdownSignal sends the shutdown signal to the channel.
|
||||||
func SendShutdownSignal(c chan<- os.Signal) {
|
func SendShutdownSignal(c chan<- os.Signal) {
|
||||||
sendShutdownSignal(c)
|
sendShutdownSignal(c)
|
||||||
|
|||||||
@@ -1,22 +1,11 @@
|
|||||||
//go:build darwin || freebsd || linux || openbsd
|
//go:build unix
|
||||||
|
|
||||||
package aghos
|
package aghos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func notifyReconfigureSignal(c chan<- os.Signal) {
|
|
||||||
signal.Notify(c, unix.SIGHUP)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isReconfigureSignal(sig os.Signal) (ok bool) {
|
|
||||||
return sig == unix.SIGHUP
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendShutdownSignal(_ chan<- os.Signal) {
|
func sendShutdownSignal(_ chan<- os.Signal) {
|
||||||
// On Unix we are already notified by the system.
|
// On Unix we are already notified by the system.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ package aghos
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setRlimit(val uint64) (err error) {
|
func setRlimit(_ uint64) (err error) {
|
||||||
return Unsupported("setrlimit")
|
return Unsupported("setrlimit")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,14 +37,6 @@ func isOpenWrt() (ok bool) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifyReconfigureSignal(c chan<- os.Signal) {
|
|
||||||
signal.Notify(c, windows.SIGHUP)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isReconfigureSignal(sig os.Signal) (ok bool) {
|
|
||||||
return sig == windows.SIGHUP
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendShutdownSignal(c chan<- os.Signal) {
|
func sendShutdownSignal(c chan<- os.Signal) {
|
||||||
c <- os.Interrupt
|
c <- os.Interrupt
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,9 @@ func newPendingFile(filePath string, mode fs.FileMode) (f PendingFile, err error
|
|||||||
return nil, fmt.Errorf("opening pending file: %w", err)
|
return nil, fmt.Errorf("opening pending file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = file.Chmod(mode)
|
// TODO(e.burkov): The [os.Chmod] implementation is useless on Windows,
|
||||||
|
// investigate if it can be removed.
|
||||||
|
err = os.Chmod(file.Name(), mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("preparing pending file: %w", err)
|
return nil, fmt.Errorf("preparing pending file: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (w *FSWatcher) Add(name string) (err error) {
|
|||||||
|
|
||||||
// ServiceWithConfig is a fake [agh.ServiceWithConfig] implementation for tests.
|
// ServiceWithConfig is a fake [agh.ServiceWithConfig] implementation for tests.
|
||||||
type ServiceWithConfig[ConfigType any] struct {
|
type ServiceWithConfig[ConfigType any] struct {
|
||||||
OnStart func() (err error)
|
OnStart func(ctx context.Context) (err error)
|
||||||
OnShutdown func(ctx context.Context) (err error)
|
OnShutdown func(ctx context.Context) (err error)
|
||||||
OnConfig func() (c ConfigType)
|
OnConfig func() (c ConfigType)
|
||||||
}
|
}
|
||||||
@@ -68,8 +68,8 @@ var _ agh.ServiceWithConfig[struct{}] = (*ServiceWithConfig[struct{}])(nil)
|
|||||||
|
|
||||||
// Start implements the [agh.ServiceWithConfig] interface for
|
// Start implements the [agh.ServiceWithConfig] interface for
|
||||||
// *ServiceWithConfig.
|
// *ServiceWithConfig.
|
||||||
func (s *ServiceWithConfig[_]) Start() (err error) {
|
func (s *ServiceWithConfig[_]) Start(ctx context.Context) (err error) {
|
||||||
return s.OnStart()
|
return s.OnStart(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown implements the [agh.ServiceWithConfig] interface for
|
// Shutdown implements the [agh.ServiceWithConfig] interface for
|
||||||
@@ -89,14 +89,14 @@ func (s *ServiceWithConfig[ConfigType]) Config() (c ConfigType) {
|
|||||||
// AddressProcessor is a fake [client.AddressProcessor] implementation for
|
// AddressProcessor is a fake [client.AddressProcessor] implementation for
|
||||||
// tests.
|
// tests.
|
||||||
type AddressProcessor struct {
|
type AddressProcessor struct {
|
||||||
OnProcess func(ip netip.Addr)
|
OnProcess func(ctx context.Context, ip netip.Addr)
|
||||||
OnClose func() (err error)
|
OnClose func() (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process implements the [client.AddressProcessor] interface for
|
// Process implements the [client.AddressProcessor] interface for
|
||||||
// *AddressProcessor.
|
// *AddressProcessor.
|
||||||
func (p *AddressProcessor) Process(ip netip.Addr) {
|
func (p *AddressProcessor) Process(ctx context.Context, ip netip.Addr) {
|
||||||
p.OnProcess(ip)
|
p.OnProcess(ctx, ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implements the [client.AddressProcessor] interface for
|
// Close implements the [client.AddressProcessor] interface for
|
||||||
@@ -107,13 +107,18 @@ func (p *AddressProcessor) Close() (err error) {
|
|||||||
|
|
||||||
// AddressUpdater is a fake [client.AddressUpdater] implementation for tests.
|
// AddressUpdater is a fake [client.AddressUpdater] implementation for tests.
|
||||||
type AddressUpdater struct {
|
type AddressUpdater struct {
|
||||||
OnUpdateAddress func(ip netip.Addr, host string, info *whois.Info)
|
OnUpdateAddress func(ctx context.Context, ip netip.Addr, host string, info *whois.Info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateAddress implements the [client.AddressUpdater] interface for
|
// UpdateAddress implements the [client.AddressUpdater] interface for
|
||||||
// *AddressUpdater.
|
// *AddressUpdater.
|
||||||
func (p *AddressUpdater) UpdateAddress(ip netip.Addr, host string, info *whois.Info) {
|
func (p *AddressUpdater) UpdateAddress(
|
||||||
p.OnUpdateAddress(ip, host, info)
|
ctx context.Context,
|
||||||
|
ip netip.Addr,
|
||||||
|
host string,
|
||||||
|
info *whois.Info,
|
||||||
|
) {
|
||||||
|
p.OnUpdateAddress(ctx, ip, host, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Package dnsforward
|
// Package dnsforward
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
|
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
|
||||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
)
|
)
|
||||||
@@ -22,7 +21,7 @@ const ErrClosed errors.Error = "use of closed address processor"
|
|||||||
|
|
||||||
// AddressProcessor is the interface for types that can process clients.
|
// AddressProcessor is the interface for types that can process clients.
|
||||||
type AddressProcessor interface {
|
type AddressProcessor interface {
|
||||||
Process(ip netip.Addr)
|
Process(ctx context.Context, ip netip.Addr)
|
||||||
Close() (err error)
|
Close() (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ type EmptyAddrProc struct{}
|
|||||||
var _ AddressProcessor = EmptyAddrProc{}
|
var _ AddressProcessor = EmptyAddrProc{}
|
||||||
|
|
||||||
// Process implements the [AddressProcessor] interface for EmptyAddrProc.
|
// Process implements the [AddressProcessor] interface for EmptyAddrProc.
|
||||||
func (EmptyAddrProc) Process(_ netip.Addr) {}
|
func (EmptyAddrProc) Process(_ context.Context, _ netip.Addr) {}
|
||||||
|
|
||||||
// Close implements the [AddressProcessor] interface for EmptyAddrProc.
|
// Close implements the [AddressProcessor] interface for EmptyAddrProc.
|
||||||
func (EmptyAddrProc) Close() (_ error) { return nil }
|
func (EmptyAddrProc) Close() (_ error) { return nil }
|
||||||
@@ -90,12 +89,15 @@ type DefaultAddrProcConfig struct {
|
|||||||
type AddressUpdater interface {
|
type AddressUpdater interface {
|
||||||
// UpdateAddress updates information about an IP address, setting host (if
|
// UpdateAddress updates information about an IP address, setting host (if
|
||||||
// not empty) and WHOIS information (if not nil).
|
// not empty) and WHOIS information (if not nil).
|
||||||
UpdateAddress(ip netip.Addr, host string, info *whois.Info)
|
UpdateAddress(ctx context.Context, ip netip.Addr, host string, info *whois.Info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultAddrProc processes incoming client addresses with rDNS and WHOIS, if
|
// DefaultAddrProc processes incoming client addresses with rDNS and WHOIS, if
|
||||||
// configured, and updates that information in a client storage.
|
// configured, and updates that information in a client storage.
|
||||||
type DefaultAddrProc struct {
|
type DefaultAddrProc struct {
|
||||||
|
// logger is used to log the operation of address processor.
|
||||||
|
logger *slog.Logger
|
||||||
|
|
||||||
// clientIPsMu serializes closure of clientIPs and access to isClosed.
|
// clientIPsMu serializes closure of clientIPs and access to isClosed.
|
||||||
clientIPsMu *sync.Mutex
|
clientIPsMu *sync.Mutex
|
||||||
|
|
||||||
@@ -142,6 +144,7 @@ const (
|
|||||||
// not be nil.
|
// not be nil.
|
||||||
func NewDefaultAddrProc(c *DefaultAddrProcConfig) (p *DefaultAddrProc) {
|
func NewDefaultAddrProc(c *DefaultAddrProcConfig) (p *DefaultAddrProc) {
|
||||||
p = &DefaultAddrProc{
|
p = &DefaultAddrProc{
|
||||||
|
logger: c.BaseLogger.With(slogutil.KeyPrefix, "addrproc"),
|
||||||
clientIPsMu: &sync.Mutex{},
|
clientIPsMu: &sync.Mutex{},
|
||||||
clientIPs: make(chan netip.Addr, defaultQueueSize),
|
clientIPs: make(chan netip.Addr, defaultQueueSize),
|
||||||
rdns: &rdns.Empty{},
|
rdns: &rdns.Empty{},
|
||||||
@@ -164,10 +167,13 @@ func NewDefaultAddrProc(c *DefaultAddrProcConfig) (p *DefaultAddrProc) {
|
|||||||
p.whois = newWHOIS(c.BaseLogger.With(slogutil.KeyPrefix, "whois"), c.DialContext)
|
p.whois = newWHOIS(c.BaseLogger.With(slogutil.KeyPrefix, "whois"), c.DialContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
go p.process(c.CatchPanics)
|
// TODO(s.chzhen): Pass context.
|
||||||
|
ctx := context.TODO()
|
||||||
|
|
||||||
|
go p.process(ctx, c.CatchPanics)
|
||||||
|
|
||||||
for _, ip := range c.InitialAddresses {
|
for _, ip := range c.InitialAddresses {
|
||||||
p.Process(ip)
|
p.Process(ctx, ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p
|
return p
|
||||||
@@ -210,7 +216,7 @@ func newWHOIS(logger *slog.Logger, dialFunc aghnet.DialContextFunc) (w whois.Int
|
|||||||
var _ AddressProcessor = (*DefaultAddrProc)(nil)
|
var _ AddressProcessor = (*DefaultAddrProc)(nil)
|
||||||
|
|
||||||
// Process implements the [AddressProcessor] interface for *DefaultAddrProc.
|
// Process implements the [AddressProcessor] interface for *DefaultAddrProc.
|
||||||
func (p *DefaultAddrProc) Process(ip netip.Addr) {
|
func (p *DefaultAddrProc) Process(ctx context.Context, ip netip.Addr) {
|
||||||
p.clientIPsMu.Lock()
|
p.clientIPsMu.Lock()
|
||||||
defer p.clientIPsMu.Unlock()
|
defer p.clientIPsMu.Unlock()
|
||||||
|
|
||||||
@@ -222,38 +228,42 @@ func (p *DefaultAddrProc) Process(ip netip.Addr) {
|
|||||||
case p.clientIPs <- ip:
|
case p.clientIPs <- ip:
|
||||||
// Go on.
|
// Go on.
|
||||||
default:
|
default:
|
||||||
log.Debug("clients: ip channel is full; len: %d", len(p.clientIPs))
|
p.logger.DebugContext(ctx, "ip channel is full", "len", len(p.clientIPs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process processes the incoming client IP-address information. It is intended
|
// process processes the incoming client IP-address information. It is intended
|
||||||
// to be used as a goroutine. Once clientIPs is closed, process exits.
|
// to be used as a goroutine. Once clientIPs is closed, process exits.
|
||||||
func (p *DefaultAddrProc) process(catchPanics bool) {
|
func (p *DefaultAddrProc) process(ctx context.Context, catchPanics bool) {
|
||||||
if catchPanics {
|
if catchPanics {
|
||||||
defer log.OnPanic("addrProcessor.process")
|
defer slogutil.RecoverAndLog(ctx, p.logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("clients: processing addresses")
|
p.logger.InfoContext(ctx, "processing addresses")
|
||||||
|
|
||||||
ctx := context.TODO()
|
|
||||||
|
|
||||||
for ip := range p.clientIPs {
|
for ip := range p.clientIPs {
|
||||||
host := p.processRDNS(ctx, ip)
|
host := p.processRDNS(ctx, ip)
|
||||||
info := p.processWHOIS(ctx, ip)
|
info := p.processWHOIS(ctx, ip)
|
||||||
|
|
||||||
p.addrUpdater.UpdateAddress(ip, host, info)
|
p.addrUpdater.UpdateAddress(ctx, ip, host, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("clients: finished processing addresses")
|
p.logger.InfoContext(ctx, "finished processing addresses")
|
||||||
}
|
}
|
||||||
|
|
||||||
// processRDNS resolves the clients' IP addresses using reverse DNS. host is
|
// processRDNS resolves the clients' IP addresses using reverse DNS. host is
|
||||||
// empty if there were errors or if the information hasn't changed.
|
// empty if there were errors or if the information hasn't changed.
|
||||||
func (p *DefaultAddrProc) processRDNS(ctx context.Context, ip netip.Addr) (host string) {
|
func (p *DefaultAddrProc) processRDNS(ctx context.Context, ip netip.Addr) (host string) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
log.Debug("clients: processing %s with rdns", ip)
|
p.logger.DebugContext(ctx, "processing rdns", "ip", ip)
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Debug("clients: finished processing %s with rdns in %s", ip, time.Since(start))
|
p.logger.DebugContext(
|
||||||
|
ctx,
|
||||||
|
"finished processing rdns",
|
||||||
|
"ip", ip,
|
||||||
|
"host", host,
|
||||||
|
"elapsed", time.Since(start),
|
||||||
|
)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ok := p.shouldResolve(ip)
|
ok := p.shouldResolve(ip)
|
||||||
@@ -280,9 +290,15 @@ func (p *DefaultAddrProc) shouldResolve(ip netip.Addr) (ok bool) {
|
|||||||
// hasn't changed.
|
// hasn't changed.
|
||||||
func (p *DefaultAddrProc) processWHOIS(ctx context.Context, ip netip.Addr) (info *whois.Info) {
|
func (p *DefaultAddrProc) processWHOIS(ctx context.Context, ip netip.Addr) (info *whois.Info) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
log.Debug("clients: processing %s with whois", ip)
|
p.logger.DebugContext(ctx, "processing whois", "ip", ip)
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Debug("clients: finished processing %s with whois in %s", ip, time.Since(start))
|
p.logger.DebugContext(
|
||||||
|
ctx,
|
||||||
|
"finished processing whois",
|
||||||
|
"ip", ip,
|
||||||
|
"whois", info,
|
||||||
|
"elapsed", time.Since(start),
|
||||||
|
)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// TODO(s.chzhen): Move the timeout logic from WHOIS configuration to the
|
// TODO(s.chzhen): Move the timeout logic from WHOIS configuration to the
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ func TestEmptyAddrProc(t *testing.T) {
|
|||||||
p := client.EmptyAddrProc{}
|
p := client.EmptyAddrProc{}
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
p.Process(testIP)
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
p.Process(ctx, testIP)
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
@@ -120,7 +121,8 @@ func TestDefaultAddrProc_Process_rDNS(t *testing.T) {
|
|||||||
})
|
})
|
||||||
testutil.CleanupAndRequireSuccess(t, p.Close)
|
testutil.CleanupAndRequireSuccess(t, p.Close)
|
||||||
|
|
||||||
p.Process(tc.ip)
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
p.Process(ctx, tc.ip)
|
||||||
|
|
||||||
if !tc.wantUpd {
|
if !tc.wantUpd {
|
||||||
return
|
return
|
||||||
@@ -146,8 +148,8 @@ func newOnUpdateAddress(
|
|||||||
ips chan<- netip.Addr,
|
ips chan<- netip.Addr,
|
||||||
hosts chan<- string,
|
hosts chan<- string,
|
||||||
infos chan<- *whois.Info,
|
infos chan<- *whois.Info,
|
||||||
) (f func(ip netip.Addr, host string, info *whois.Info)) {
|
) (f func(ctx context.Context, ip netip.Addr, host string, info *whois.Info)) {
|
||||||
return func(ip netip.Addr, host string, info *whois.Info) {
|
return func(ctx context.Context, ip netip.Addr, host string, info *whois.Info) {
|
||||||
if !want && (host != "" || info != nil) {
|
if !want && (host != "" || info != nil) {
|
||||||
panic(fmt.Errorf("got unexpected update for %v with %q and %v", ip, host, info))
|
panic(fmt.Errorf("got unexpected update for %v with %q and %v", ip, host, info))
|
||||||
}
|
}
|
||||||
@@ -230,7 +232,8 @@ func TestDefaultAddrProc_Process_WHOIS(t *testing.T) {
|
|||||||
})
|
})
|
||||||
testutil.CleanupAndRequireSuccess(t, p.Close)
|
testutil.CleanupAndRequireSuccess(t, p.Close)
|
||||||
|
|
||||||
p.Process(testIP)
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
p.Process(ctx, testIP)
|
||||||
|
|
||||||
if !tc.wantUpd {
|
if !tc.wantUpd {
|
||||||
return
|
return
|
||||||
@@ -251,7 +254,9 @@ func TestDefaultAddrProc_Process_WHOIS(t *testing.T) {
|
|||||||
func TestDefaultAddrProc_Close(t *testing.T) {
|
func TestDefaultAddrProc_Close(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{})
|
p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{
|
||||||
|
BaseLogger: slogutil.NewDiscardLogger(),
|
||||||
|
})
|
||||||
|
|
||||||
err := p.Close()
|
err := p.Close()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -9,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// macKey contains MAC as byte array of 6, 8, or 20 bytes.
|
// macKey contains MAC as byte array of 6, 8, or 20 bytes.
|
||||||
@@ -330,12 +330,14 @@ func (ci *index) size() (n int) {
|
|||||||
// rangeByName is like [Index.Range] but sorts the persistent clients by name
|
// rangeByName is like [Index.Range] but sorts the persistent clients by name
|
||||||
// before iterating ensuring a predictable order.
|
// before iterating ensuring a predictable order.
|
||||||
func (ci *index) rangeByName(f func(c *Persistent) (cont bool)) {
|
func (ci *index) rangeByName(f func(c *Persistent) (cont bool)) {
|
||||||
cs := maps.Values(ci.uidToClient)
|
clients := slices.SortedStableFunc(
|
||||||
slices.SortFunc(cs, func(a, b *Persistent) (n int) {
|
maps.Values(ci.uidToClient),
|
||||||
return strings.Compare(a.Name, b.Name)
|
func(a, b *Persistent) (res int) {
|
||||||
})
|
return strings.Compare(a.Name, b.Name)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
for _, c := range cs {
|
for _, c := range clients {
|
||||||
if !f(c) {
|
if !f(c) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding"
|
"encoding"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
|
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@@ -136,7 +136,7 @@ type Persistent struct {
|
|||||||
|
|
||||||
// validate returns an error if persistent client information contains errors.
|
// validate returns an error if persistent client information contains errors.
|
||||||
// allTags must be sorted.
|
// allTags must be sorted.
|
||||||
func (c *Persistent) validate(allTags []string) (err error) {
|
func (c *Persistent) validate(ctx context.Context, l *slog.Logger, allTags []string) (err error) {
|
||||||
switch {
|
switch {
|
||||||
case c.Name == "":
|
case c.Name == "":
|
||||||
return errors.Error("empty name")
|
return errors.Error("empty name")
|
||||||
@@ -153,7 +153,7 @@ func (c *Persistent) validate(allTags []string) (err error) {
|
|||||||
|
|
||||||
err = conf.Close()
|
err = conf.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("client: closing upstream config: %s", err)
|
l.ErrorContext(ctx, "client: closing upstream config", slogutil.KeyError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range c.Tags {
|
for _, t := range c.Tags {
|
||||||
@@ -323,20 +323,3 @@ func (c *Persistent) CloseUpstreams() (err error) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSafeSearch initializes and sets the safe search filter for this client.
|
|
||||||
func (c *Persistent) SetSafeSearch(
|
|
||||||
conf filtering.SafeSearchConfig,
|
|
||||||
cacheSize uint,
|
|
||||||
cacheTTL time.Duration,
|
|
||||||
) (err error) {
|
|
||||||
ss, err := safesearch.NewDefault(conf, fmt.Sprintf("client %q", c.Name), cacheSize, cacheTTL)
|
|
||||||
if err != nil {
|
|
||||||
// Don't wrap the error, because it's informative enough as is.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SafeSearch = ss
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -14,7 +15,7 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/hostsfile"
|
"github.com/AdguardTeam/golibs/hostsfile"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// allowedTags is the list of available client tags.
|
// allowedTags is the list of available client tags.
|
||||||
@@ -83,6 +84,10 @@ type HostsContainer interface {
|
|||||||
|
|
||||||
// StorageConfig is the client storage configuration structure.
|
// StorageConfig is the client storage configuration structure.
|
||||||
type StorageConfig struct {
|
type StorageConfig struct {
|
||||||
|
// Logger is used for logging the operation of the client storage. It must
|
||||||
|
// not be nil.
|
||||||
|
Logger *slog.Logger
|
||||||
|
|
||||||
// DHCP is used to match IPs against MACs of persistent clients and update
|
// DHCP is used to match IPs against MACs of persistent clients and update
|
||||||
// [SourceDHCP] runtime client information. It must not be nil.
|
// [SourceDHCP] runtime client information. It must not be nil.
|
||||||
DHCP DHCP
|
DHCP DHCP
|
||||||
@@ -108,6 +113,10 @@ type StorageConfig struct {
|
|||||||
|
|
||||||
// Storage contains information about persistent and runtime clients.
|
// Storage contains information about persistent and runtime clients.
|
||||||
type Storage struct {
|
type Storage struct {
|
||||||
|
// logger is used for logging the operation of the client storage. It must
|
||||||
|
// not be nil.
|
||||||
|
logger *slog.Logger
|
||||||
|
|
||||||
// mu protects indexes of persistent and runtime clients.
|
// mu protects indexes of persistent and runtime clients.
|
||||||
mu *sync.Mutex
|
mu *sync.Mutex
|
||||||
|
|
||||||
@@ -145,12 +154,12 @@ type Storage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewStorage returns initialized client storage. conf must not be nil.
|
// NewStorage returns initialized client storage. conf must not be nil.
|
||||||
func NewStorage(conf *StorageConfig) (s *Storage, err error) {
|
func NewStorage(ctx context.Context, conf *StorageConfig) (s *Storage, err error) {
|
||||||
tags := slices.Clone(allowedTags)
|
tags := slices.Clone(allowedTags)
|
||||||
slices.Sort(tags)
|
slices.Sort(tags)
|
||||||
|
|
||||||
s = &Storage{
|
s = &Storage{
|
||||||
allowedTags: tags,
|
logger: conf.Logger,
|
||||||
mu: &sync.Mutex{},
|
mu: &sync.Mutex{},
|
||||||
index: newIndex(),
|
index: newIndex(),
|
||||||
runtimeIndex: newRuntimeIndex(),
|
runtimeIndex: newRuntimeIndex(),
|
||||||
@@ -158,18 +167,19 @@ func NewStorage(conf *StorageConfig) (s *Storage, err error) {
|
|||||||
etcHosts: conf.EtcHosts,
|
etcHosts: conf.EtcHosts,
|
||||||
arpDB: conf.ARPDB,
|
arpDB: conf.ARPDB,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
|
allowedTags: tags,
|
||||||
arpClientsUpdatePeriod: conf.ARPClientsUpdatePeriod,
|
arpClientsUpdatePeriod: conf.ARPClientsUpdatePeriod,
|
||||||
runtimeSourceDHCP: conf.RuntimeSourceDHCP,
|
runtimeSourceDHCP: conf.RuntimeSourceDHCP,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, p := range conf.InitialClients {
|
for i, p := range conf.InitialClients {
|
||||||
err = s.Add(p)
|
err = s.Add(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("adding client %q at index %d: %w", p.Name, i, err)
|
return nil, fmt.Errorf("adding client %q at index %d: %w", p.Name, i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.ReloadARP()
|
s.ReloadARP(ctx)
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
@@ -177,9 +187,9 @@ func NewStorage(conf *StorageConfig) (s *Storage, err error) {
|
|||||||
// Start starts the goroutines for updating the runtime client information.
|
// Start starts the goroutines for updating the runtime client information.
|
||||||
//
|
//
|
||||||
// TODO(s.chzhen): Pass context.
|
// TODO(s.chzhen): Pass context.
|
||||||
func (s *Storage) Start(_ context.Context) (err error) {
|
func (s *Storage) Start(ctx context.Context) (err error) {
|
||||||
go s.periodicARPUpdate()
|
go s.periodicARPUpdate(ctx)
|
||||||
go s.handleHostsUpdates()
|
go s.handleHostsUpdates(ctx)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -195,15 +205,15 @@ func (s *Storage) Shutdown(_ context.Context) (err error) {
|
|||||||
|
|
||||||
// periodicARPUpdate periodically reloads runtime clients from ARP. It is
|
// periodicARPUpdate periodically reloads runtime clients from ARP. It is
|
||||||
// intended to be used as a goroutine.
|
// intended to be used as a goroutine.
|
||||||
func (s *Storage) periodicARPUpdate() {
|
func (s *Storage) periodicARPUpdate(ctx context.Context) {
|
||||||
defer log.OnPanic("storage")
|
defer slogutil.RecoverAndLog(ctx, s.logger)
|
||||||
|
|
||||||
t := time.NewTicker(s.arpClientsUpdatePeriod)
|
t := time.NewTicker(s.arpClientsUpdatePeriod)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
s.ReloadARP()
|
s.ReloadARP(ctx)
|
||||||
case <-s.done:
|
case <-s.done:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -211,28 +221,28 @@ func (s *Storage) periodicARPUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReloadARP reloads runtime clients from ARP, if configured.
|
// ReloadARP reloads runtime clients from ARP, if configured.
|
||||||
func (s *Storage) ReloadARP() {
|
func (s *Storage) ReloadARP(ctx context.Context) {
|
||||||
if s.arpDB != nil {
|
if s.arpDB != nil {
|
||||||
s.addFromSystemARP()
|
s.addFromSystemARP(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addFromSystemARP adds the IP-hostname pairings from the output of the arp -a
|
// addFromSystemARP adds the IP-hostname pairings from the output of the arp -a
|
||||||
// command.
|
// command.
|
||||||
func (s *Storage) addFromSystemARP() {
|
func (s *Storage) addFromSystemARP(ctx context.Context) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
if err := s.arpDB.Refresh(); err != nil {
|
if err := s.arpDB.Refresh(); err != nil {
|
||||||
s.arpDB = arpdb.Empty{}
|
s.arpDB = arpdb.Empty{}
|
||||||
log.Error("refreshing arp container: %s", err)
|
s.logger.ErrorContext(ctx, "refreshing arp container", slogutil.KeyError, err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ns := s.arpDB.Neighbors()
|
ns := s.arpDB.Neighbors()
|
||||||
if len(ns) == 0 {
|
if len(ns) == 0 {
|
||||||
log.Debug("refreshing arp container: the update is empty")
|
s.logger.DebugContext(ctx, "refreshing arp container: the update is empty")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -246,17 +256,22 @@ func (s *Storage) addFromSystemARP() {
|
|||||||
|
|
||||||
removed := s.runtimeIndex.removeEmpty()
|
removed := s.runtimeIndex.removeEmpty()
|
||||||
|
|
||||||
log.Debug("storage: added %d, removed %d client aliases from arp neighborhood", len(ns), removed)
|
s.logger.DebugContext(
|
||||||
|
ctx,
|
||||||
|
"updating client aliases from arp neighborhood",
|
||||||
|
"added", len(ns),
|
||||||
|
"removed", removed,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleHostsUpdates receives the updates from the hosts container and adds
|
// handleHostsUpdates receives the updates from the hosts container and adds
|
||||||
// them to the clients storage. It is intended to be used as a goroutine.
|
// them to the clients storage. It is intended to be used as a goroutine.
|
||||||
func (s *Storage) handleHostsUpdates() {
|
func (s *Storage) handleHostsUpdates(ctx context.Context) {
|
||||||
if s.etcHosts == nil {
|
if s.etcHosts == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer log.OnPanic("storage")
|
defer slogutil.RecoverAndLog(ctx, s.logger)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -265,7 +280,7 @@ func (s *Storage) handleHostsUpdates() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.addFromHostsFile(upd)
|
s.addFromHostsFile(ctx, upd)
|
||||||
case <-s.done:
|
case <-s.done:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -274,7 +289,7 @@ func (s *Storage) handleHostsUpdates() {
|
|||||||
|
|
||||||
// addFromHostsFile fills the client-hostname pairing index from the system's
|
// addFromHostsFile fills the client-hostname pairing index from the system's
|
||||||
// hosts files.
|
// hosts files.
|
||||||
func (s *Storage) addFromHostsFile(hosts *hostsfile.DefaultStorage) {
|
func (s *Storage) addFromHostsFile(ctx context.Context, hosts *hostsfile.DefaultStorage) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
@@ -294,14 +309,19 @@ func (s *Storage) addFromHostsFile(hosts *hostsfile.DefaultStorage) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
removed := s.runtimeIndex.removeEmpty()
|
removed := s.runtimeIndex.removeEmpty()
|
||||||
log.Debug("storage: added %d, removed %d client aliases from system hosts file", added, removed)
|
s.logger.DebugContext(
|
||||||
|
ctx,
|
||||||
|
"updating client aliases from system hosts file",
|
||||||
|
"added", added,
|
||||||
|
"removed", removed,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// type check
|
// type check
|
||||||
var _ AddressUpdater = (*Storage)(nil)
|
var _ AddressUpdater = (*Storage)(nil)
|
||||||
|
|
||||||
// UpdateAddress implements the [AddressUpdater] interface for *Storage
|
// UpdateAddress implements the [AddressUpdater] interface for *Storage
|
||||||
func (s *Storage) UpdateAddress(ip netip.Addr, host string, info *whois.Info) {
|
func (s *Storage) UpdateAddress(ctx context.Context, ip netip.Addr, host string, info *whois.Info) {
|
||||||
// Common fast path optimization.
|
// Common fast path optimization.
|
||||||
if host == "" && info == nil {
|
if host == "" && info == nil {
|
||||||
return
|
return
|
||||||
@@ -315,12 +335,12 @@ func (s *Storage) UpdateAddress(ip netip.Addr, host string, info *whois.Info) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if info != nil {
|
if info != nil {
|
||||||
s.setWHOISInfo(ip, info)
|
s.setWHOISInfo(ctx, ip, info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDHCP updates [SourceDHCP] runtime client information.
|
// UpdateDHCP updates [SourceDHCP] runtime client information.
|
||||||
func (s *Storage) UpdateDHCP() {
|
func (s *Storage) UpdateDHCP(ctx context.Context) {
|
||||||
if s.dhcp == nil || !s.runtimeSourceDHCP {
|
if s.dhcp == nil || !s.runtimeSourceDHCP {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -338,14 +358,23 @@ func (s *Storage) UpdateDHCP() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removed := s.runtimeIndex.removeEmpty()
|
removed := s.runtimeIndex.removeEmpty()
|
||||||
log.Debug("storage: added %d, removed %d client aliases from dhcp", added, removed)
|
s.logger.DebugContext(
|
||||||
|
ctx,
|
||||||
|
"updating client aliases from dhcp",
|
||||||
|
"added", added,
|
||||||
|
"removed", removed,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setWHOISInfo sets the WHOIS information for a runtime client.
|
// setWHOISInfo sets the WHOIS information for a runtime client.
|
||||||
func (s *Storage) setWHOISInfo(ip netip.Addr, wi *whois.Info) {
|
func (s *Storage) setWHOISInfo(ctx context.Context, ip netip.Addr, wi *whois.Info) {
|
||||||
_, ok := s.index.findByIP(ip)
|
_, ok := s.index.findByIP(ip)
|
||||||
if ok {
|
if ok {
|
||||||
log.Debug("storage: client for %s is already created, ignore whois info", ip)
|
s.logger.DebugContext(
|
||||||
|
ctx,
|
||||||
|
"persistent client is already created, ignore whois info",
|
||||||
|
"ip", ip,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -358,14 +387,14 @@ func (s *Storage) setWHOISInfo(ip netip.Addr, wi *whois.Info) {
|
|||||||
|
|
||||||
rc.setWHOIS(wi)
|
rc.setWHOIS(wi)
|
||||||
|
|
||||||
log.Debug("storage: set whois info for runtime client with ip %s: %+v", ip, wi)
|
s.logger.DebugContext(ctx, "set whois info for runtime client", "ip", ip, "whois", wi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add stores persistent client information or returns an error.
|
// Add stores persistent client information or returns an error.
|
||||||
func (s *Storage) Add(p *Persistent) (err error) {
|
func (s *Storage) Add(ctx context.Context, p *Persistent) (err error) {
|
||||||
defer func() { err = errors.Annotate(err, "adding client: %w") }()
|
defer func() { err = errors.Annotate(err, "adding client: %w") }()
|
||||||
|
|
||||||
err = p.validate(s.allowedTags)
|
err = p.validate(ctx, s.logger, s.allowedTags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap the error since there is already an annotation deferred.
|
// Don't wrap the error since there is already an annotation deferred.
|
||||||
return err
|
return err
|
||||||
@@ -388,7 +417,13 @@ func (s *Storage) Add(p *Persistent) (err error) {
|
|||||||
|
|
||||||
s.index.add(p)
|
s.index.add(p)
|
||||||
|
|
||||||
log.Debug("client storage: added %q: IDs: %q [%d]", p.Name, p.IDs(), s.index.size())
|
s.logger.DebugContext(
|
||||||
|
ctx,
|
||||||
|
"client added",
|
||||||
|
"name", p.Name,
|
||||||
|
"ids", p.IDs(),
|
||||||
|
"clients_count", s.index.size(),
|
||||||
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -470,7 +505,7 @@ func (s *Storage) FindByMAC(mac net.HardwareAddr) (p *Persistent, ok bool) {
|
|||||||
|
|
||||||
// RemoveByName removes persistent client information. ok is false if no such
|
// RemoveByName removes persistent client information. ok is false if no such
|
||||||
// client exists by that name.
|
// client exists by that name.
|
||||||
func (s *Storage) RemoveByName(name string) (ok bool) {
|
func (s *Storage) RemoveByName(ctx context.Context, name string) (ok bool) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
@@ -480,7 +515,7 @@ func (s *Storage) RemoveByName(name string) (ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := p.CloseUpstreams(); err != nil {
|
if err := p.CloseUpstreams(); err != nil {
|
||||||
log.Error("client storage: removing client %q: %s", p.Name, err)
|
s.logger.ErrorContext(ctx, "removing client", "name", p.Name, slogutil.KeyError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.index.remove(p)
|
s.index.remove(p)
|
||||||
@@ -490,10 +525,10 @@ func (s *Storage) RemoveByName(name string) (ok bool) {
|
|||||||
|
|
||||||
// Update finds the stored persistent client by its name and updates its
|
// Update finds the stored persistent client by its name and updates its
|
||||||
// information from p.
|
// information from p.
|
||||||
func (s *Storage) Update(name string, p *Persistent) (err error) {
|
func (s *Storage) Update(ctx context.Context, name string, p *Persistent) (err error) {
|
||||||
defer func() { err = errors.Annotate(err, "updating client: %w") }()
|
defer func() { err = errors.Annotate(err, "updating client: %w") }()
|
||||||
|
|
||||||
err = p.validate(s.allowedTags)
|
err = p.validate(ctx, s.logger, s.allowedTags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap the error since there is already an annotation deferred.
|
// Don't wrap the error since there is already an annotation deferred.
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -15,11 +15,25 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||||
"github.com/AdguardTeam/golibs/hostsfile"
|
"github.com/AdguardTeam/golibs/hostsfile"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// newTestStorage is a helper function that returns initialized storage.
|
||||||
|
func newTestStorage(tb testing.TB) (s *client.Storage) {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
ctx := testutil.ContextWithTimeout(tb, testTimeout)
|
||||||
|
s, err := client.NewStorage(ctx, &client.StorageConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
|
})
|
||||||
|
require.NoError(tb, err)
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// testHostsContainer is a mock implementation of the [client.HostsContainer]
|
// testHostsContainer is a mock implementation of the [client.HostsContainer]
|
||||||
// interface.
|
// interface.
|
||||||
type testHostsContainer struct {
|
type testHostsContainer struct {
|
||||||
@@ -110,7 +124,9 @@ func TestStorage_Add_hostsfile(t *testing.T) {
|
|||||||
onUpd: func() (updates <-chan *hostsfile.DefaultStorage) { return hostCh },
|
onUpd: func() (updates <-chan *hostsfile.DefaultStorage) { return hostCh },
|
||||||
}
|
}
|
||||||
|
|
||||||
storage, err := client.NewStorage(&client.StorageConfig{
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
storage, err := client.NewStorage(ctx, &client.StorageConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
DHCP: client.EmptyDHCP{},
|
DHCP: client.EmptyDHCP{},
|
||||||
EtcHosts: h,
|
EtcHosts: h,
|
||||||
ARPClientsUpdatePeriod: testTimeout / 10,
|
ARPClientsUpdatePeriod: testTimeout / 10,
|
||||||
@@ -198,7 +214,9 @@ func TestStorage_Add_arp(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
storage, err := client.NewStorage(&client.StorageConfig{
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
storage, err := client.NewStorage(ctx, &client.StorageConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
DHCP: client.EmptyDHCP{},
|
DHCP: client.EmptyDHCP{},
|
||||||
ARPDB: a,
|
ARPDB: a,
|
||||||
ARPClientsUpdatePeriod: testTimeout / 10,
|
ARPClientsUpdatePeriod: testTimeout / 10,
|
||||||
@@ -273,8 +291,10 @@ func TestStorage_Add_whois(t *testing.T) {
|
|||||||
cliName3 = "client_three"
|
cliName3 = "client_three"
|
||||||
)
|
)
|
||||||
|
|
||||||
storage, err := client.NewStorage(&client.StorageConfig{
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
DHCP: client.EmptyDHCP{},
|
storage, err := client.NewStorage(ctx, &client.StorageConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
|
DHCP: client.EmptyDHCP{},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -284,7 +304,7 @@ func TestStorage_Add_whois(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("new_client", func(t *testing.T) {
|
t.Run("new_client", func(t *testing.T) {
|
||||||
storage.UpdateAddress(cliIP1, "", whois)
|
storage.UpdateAddress(ctx, cliIP1, "", whois)
|
||||||
cli1 := storage.ClientRuntime(cliIP1)
|
cli1 := storage.ClientRuntime(cliIP1)
|
||||||
require.NotNil(t, cli1)
|
require.NotNil(t, cli1)
|
||||||
|
|
||||||
@@ -292,8 +312,8 @@ func TestStorage_Add_whois(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("existing_runtime_client", func(t *testing.T) {
|
t.Run("existing_runtime_client", func(t *testing.T) {
|
||||||
storage.UpdateAddress(cliIP2, cliName2, nil)
|
storage.UpdateAddress(ctx, cliIP2, cliName2, nil)
|
||||||
storage.UpdateAddress(cliIP2, "", whois)
|
storage.UpdateAddress(ctx, cliIP2, "", whois)
|
||||||
|
|
||||||
cli2 := storage.ClientRuntime(cliIP2)
|
cli2 := storage.ClientRuntime(cliIP2)
|
||||||
require.NotNil(t, cli2)
|
require.NotNil(t, cli2)
|
||||||
@@ -304,14 +324,14 @@ func TestStorage_Add_whois(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can't_set_persistent_client", func(t *testing.T) {
|
t.Run("can't_set_persistent_client", func(t *testing.T) {
|
||||||
err = storage.Add(&client.Persistent{
|
err = storage.Add(ctx, &client.Persistent{
|
||||||
Name: cliName3,
|
Name: cliName3,
|
||||||
UID: client.MustNewUID(),
|
UID: client.MustNewUID(),
|
||||||
IPs: []netip.Addr{cliIP3},
|
IPs: []netip.Addr{cliIP3},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
storage.UpdateAddress(cliIP3, "", whois)
|
storage.UpdateAddress(ctx, cliIP3, "", whois)
|
||||||
rc := storage.ClientRuntime(cliIP3)
|
rc := storage.ClientRuntime(cliIP3)
|
||||||
require.Nil(t, rc)
|
require.Nil(t, rc)
|
||||||
})
|
})
|
||||||
@@ -364,7 +384,9 @@ func TestClientsDHCP(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
storage, err := client.NewStorage(&client.StorageConfig{
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
storage, err := client.NewStorage(ctx, &client.StorageConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
DHCP: d,
|
DHCP: d,
|
||||||
RuntimeSourceDHCP: true,
|
RuntimeSourceDHCP: true,
|
||||||
})
|
})
|
||||||
@@ -378,7 +400,7 @@ func TestClientsDHCP(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("find_persistent", func(t *testing.T) {
|
t.Run("find_persistent", func(t *testing.T) {
|
||||||
err = storage.Add(&client.Persistent{
|
err = storage.Add(ctx, &client.Persistent{
|
||||||
Name: prsCliName,
|
Name: prsCliName,
|
||||||
UID: client.MustNewUID(),
|
UID: client.MustNewUID(),
|
||||||
MACs: []net.HardwareAddr{prsCliMAC},
|
MACs: []net.HardwareAddr{prsCliMAC},
|
||||||
@@ -393,7 +415,7 @@ func TestClientsDHCP(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("leases", func(t *testing.T) {
|
t.Run("leases", func(t *testing.T) {
|
||||||
delete(ipToHost, cliIP1)
|
delete(ipToHost, cliIP1)
|
||||||
storage.UpdateDHCP()
|
storage.UpdateDHCP(ctx)
|
||||||
|
|
||||||
cli1 := storage.ClientRuntime(cliIP1)
|
cli1 := storage.ClientRuntime(cliIP1)
|
||||||
require.Nil(t, cli1)
|
require.Nil(t, cli1)
|
||||||
@@ -421,16 +443,19 @@ func TestClientsDHCP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestClientsAddExisting(t *testing.T) {
|
func TestClientsAddExisting(t *testing.T) {
|
||||||
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
|
||||||
t.Run("simple", func(t *testing.T) {
|
t.Run("simple", func(t *testing.T) {
|
||||||
storage, err := client.NewStorage(&client.StorageConfig{
|
storage, err := client.NewStorage(ctx, &client.StorageConfig{
|
||||||
DHCP: client.EmptyDHCP{},
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
|
DHCP: client.EmptyDHCP{},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ip := netip.MustParseAddr("1.1.1.1")
|
ip := netip.MustParseAddr("1.1.1.1")
|
||||||
|
|
||||||
// Add a client.
|
// Add a client.
|
||||||
err = storage.Add(&client.Persistent{
|
err = storage.Add(ctx, &client.Persistent{
|
||||||
Name: "client1",
|
Name: "client1",
|
||||||
UID: client.MustNewUID(),
|
UID: client.MustNewUID(),
|
||||||
IPs: []netip.Addr{ip, netip.MustParseAddr("1:2:3::4")},
|
IPs: []netip.Addr{ip, netip.MustParseAddr("1:2:3::4")},
|
||||||
@@ -440,7 +465,7 @@ func TestClientsAddExisting(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Now add an auto-client with the same IP.
|
// Now add an auto-client with the same IP.
|
||||||
storage.UpdateAddress(ip, "test", nil)
|
storage.UpdateAddress(ctx, ip, "test", nil)
|
||||||
rc := storage.ClientRuntime(ip)
|
rc := storage.ClientRuntime(ip)
|
||||||
assert.True(t, compareRuntimeInfo(rc, client.SourceRDNS, "test"))
|
assert.True(t, compareRuntimeInfo(rc, client.SourceRDNS, "test"))
|
||||||
})
|
})
|
||||||
@@ -468,8 +493,9 @@ func TestClientsAddExisting(t *testing.T) {
|
|||||||
dhcpServer, err := dhcpd.Create(config)
|
dhcpServer, err := dhcpd.Create(config)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
storage, err := client.NewStorage(&client.StorageConfig{
|
storage, err := client.NewStorage(ctx, &client.StorageConfig{
|
||||||
DHCP: dhcpServer,
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
|
DHCP: dhcpServer,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -484,7 +510,7 @@ func TestClientsAddExisting(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Add a new client with the same IP as for a client with MAC.
|
// Add a new client with the same IP as for a client with MAC.
|
||||||
err = storage.Add(&client.Persistent{
|
err = storage.Add(ctx, &client.Persistent{
|
||||||
Name: "client2",
|
Name: "client2",
|
||||||
UID: client.MustNewUID(),
|
UID: client.MustNewUID(),
|
||||||
IPs: []netip.Addr{ip},
|
IPs: []netip.Addr{ip},
|
||||||
@@ -492,7 +518,7 @@ func TestClientsAddExisting(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Add a new client with the IP from the first client's IP range.
|
// Add a new client with the IP from the first client's IP range.
|
||||||
err = storage.Add(&client.Persistent{
|
err = storage.Add(ctx, &client.Persistent{
|
||||||
Name: "client3",
|
Name: "client3",
|
||||||
UID: client.MustNewUID(),
|
UID: client.MustNewUID(),
|
||||||
IPs: []netip.Addr{netip.MustParseAddr("2.2.2.2")},
|
IPs: []netip.Addr{netip.MustParseAddr("2.2.2.2")},
|
||||||
@@ -506,14 +532,16 @@ func TestClientsAddExisting(t *testing.T) {
|
|||||||
func newStorage(tb testing.TB, m []*client.Persistent) (s *client.Storage) {
|
func newStorage(tb testing.TB, m []*client.Persistent) (s *client.Storage) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
|
||||||
s, err := client.NewStorage(&client.StorageConfig{
|
ctx := testutil.ContextWithTimeout(tb, testTimeout)
|
||||||
DHCP: client.EmptyDHCP{},
|
s, err := client.NewStorage(ctx, &client.StorageConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
|
DHCP: client.EmptyDHCP{},
|
||||||
})
|
})
|
||||||
require.NoError(tb, err)
|
require.NoError(tb, err)
|
||||||
|
|
||||||
for _, c := range m {
|
for _, c := range m {
|
||||||
c.UID = client.MustNewUID()
|
c.UID = client.MustNewUID()
|
||||||
require.NoError(tb, s.Add(c))
|
require.NoError(tb, s.Add(ctx, c))
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Equal(tb, len(m), s.Size())
|
require.Equal(tb, len(m), s.Size())
|
||||||
@@ -555,9 +583,8 @@ func TestStorage_Add(t *testing.T) {
|
|||||||
UID: existingClientUID,
|
UID: existingClientUID,
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := client.NewStorage(&client.StorageConfig{})
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
require.NoError(t, err)
|
s := newTestStorage(t)
|
||||||
|
|
||||||
tags := s.AllowedTags()
|
tags := s.AllowedTags()
|
||||||
require.NotZero(t, len(tags))
|
require.NotZero(t, len(tags))
|
||||||
require.True(t, slices.IsSorted(tags))
|
require.True(t, slices.IsSorted(tags))
|
||||||
@@ -568,7 +595,7 @@ func TestStorage_Add(t *testing.T) {
|
|||||||
_, ok = slices.BinarySearch(tags, notAllowedTag)
|
_, ok = slices.BinarySearch(tags, notAllowedTag)
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
|
|
||||||
err = s.Add(existingClient)
|
err := s.Add(ctx, existingClient)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@@ -669,7 +696,7 @@ func TestStorage_Add(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
err = s.Add(tc.cli)
|
err = s.Add(ctx, tc.cli)
|
||||||
|
|
||||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||||
})
|
})
|
||||||
@@ -687,10 +714,9 @@ func TestStorage_RemoveByName(t *testing.T) {
|
|||||||
UID: client.MustNewUID(),
|
UID: client.MustNewUID(),
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := client.NewStorage(&client.StorageConfig{})
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
require.NoError(t, err)
|
s := newTestStorage(t)
|
||||||
|
err := s.Add(ctx, existingClient)
|
||||||
err = s.Add(existingClient)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@@ -709,19 +735,17 @@ func TestStorage_RemoveByName(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
tc.want(t, s.RemoveByName(tc.cliName))
|
tc.want(t, s.RemoveByName(ctx, tc.cliName))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("duplicate_remove", func(t *testing.T) {
|
t.Run("duplicate_remove", func(t *testing.T) {
|
||||||
s, err = client.NewStorage(&client.StorageConfig{})
|
s = newTestStorage(t)
|
||||||
|
err = s.Add(ctx, existingClient)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = s.Add(existingClient)
|
assert.True(t, s.RemoveByName(ctx, existingName))
|
||||||
require.NoError(t, err)
|
assert.False(t, s.RemoveByName(ctx, existingName))
|
||||||
|
|
||||||
assert.True(t, s.RemoveByName(existingName))
|
|
||||||
assert.False(t, s.RemoveByName(existingName))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1080,6 +1104,7 @@ func TestStorage_Update(t *testing.T) {
|
|||||||
`uses the same ClientID "obstructing_client_id"`,
|
`uses the same ClientID "obstructing_client_id"`,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
s := newStorage(
|
s := newStorage(
|
||||||
@@ -1090,7 +1115,7 @@ func TestStorage_Update(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
err := s.Update(clientName, tc.cli)
|
err := s.Update(ctx, clientName, tc.cli)
|
||||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -521,7 +521,7 @@ func TestUpgradeSchema11to12(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
}{{
|
}{{
|
||||||
ivl: 1,
|
ivl: 1,
|
||||||
want: timeutil.Duration{Duration: timeutil.Day},
|
want: timeutil.Duration(timeutil.Day),
|
||||||
wantErr: "",
|
wantErr: "",
|
||||||
name: "success",
|
name: "success",
|
||||||
}, {
|
}, {
|
||||||
@@ -604,7 +604,7 @@ func TestUpgradeSchema11to12(t *testing.T) {
|
|||||||
ivlVal, ok = ivl.(timeutil.Duration)
|
ivlVal, ok = ivl.(timeutil.Duration)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
assert.Equal(t, 90*24*time.Hour, ivlVal.Duration)
|
assert.Equal(t, 90*24*time.Hour, time.Duration(ivlVal))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1055,12 +1055,12 @@ func TestUpgradeSchema19to20(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
}{{
|
}{{
|
||||||
ivl: 1,
|
ivl: 1,
|
||||||
want: timeutil.Duration{Duration: timeutil.Day},
|
want: timeutil.Duration(timeutil.Day),
|
||||||
wantErr: "",
|
wantErr: "",
|
||||||
name: "success",
|
name: "success",
|
||||||
}, {
|
}, {
|
||||||
ivl: 0,
|
ivl: 0,
|
||||||
want: timeutil.Duration{Duration: timeutil.Day},
|
want: timeutil.Duration(timeutil.Day),
|
||||||
wantErr: "",
|
wantErr: "",
|
||||||
name: "success",
|
name: "success",
|
||||||
}, {
|
}, {
|
||||||
@@ -1143,7 +1143,7 @@ func TestUpgradeSchema19to20(t *testing.T) {
|
|||||||
ivlVal, ok = ivl.(timeutil.Duration)
|
ivlVal, ok = ivl.(timeutil.Duration)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
assert.Equal(t, 24*time.Hour, ivlVal.Duration)
|
assert.Equal(t, 24*time.Hour, time.Duration(ivlVal))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func migrateTo12(diskConf yobj) (err error) {
|
|||||||
qlogIvl = 90
|
qlogIvl = 90
|
||||||
}
|
}
|
||||||
|
|
||||||
dns[field] = timeutil.Duration{Duration: time.Duration(qlogIvl) * timeutil.Day}
|
dns[field] = timeutil.Duration(time.Duration(qlogIvl) * timeutil.Day)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func migrateTo20(diskConf yobj) (err error) {
|
|||||||
ivl = 1
|
ivl = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
stats[field] = timeutil.Duration{Duration: time.Duration(ivl) * timeutil.Day}
|
stats[field] = timeutil.Duration(time.Duration(ivl) * timeutil.Day)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func migrateTo23(diskConf yobj) (err error) {
|
|||||||
|
|
||||||
diskConf["http"] = yobj{
|
diskConf["http"] = yobj{
|
||||||
"address": netip.AddrPortFrom(bindHostAddr, uint16(bindPort)).String(),
|
"address": netip.AddrPortFrom(bindHostAddr, uint16(bindPort)).String(),
|
||||||
"session_ttl": timeutil.Duration{Duration: time.Duration(sessionTTL) * time.Hour}.String(),
|
"session_ttl": timeutil.Duration(time.Duration(sessionTTL) * time.Hour).String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(diskConf, "bind_host")
|
delete(diskConf, "bind_host")
|
||||||
|
|||||||
@@ -1,61 +1,50 @@
|
|||||||
# Testing DHCP Server
|
# Testing DHCP Server
|
||||||
|
|
||||||
Contents:
|
Contents:
|
||||||
* [Test setup with Virtual Box](#vbox)
|
|
||||||
* [Quick test with DHCPTest](#dhcptest)
|
|
||||||
|
|
||||||
## <a href="#vbox" id="vbox" name="vbox">Test setup with Virtual Box</a>
|
- [Test setup with Virtual Box](#vbox)
|
||||||
|
- [Quick test with DHCPTest](#dhcptest)
|
||||||
|
|
||||||
### Prerequisites
|
## <a href="#vbox" id="vbox" name="vbox">Test setup with Virtual Box</a>
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
To set up a test environment for DHCP server you will need:
|
To set up a test environment for DHCP server you will need:
|
||||||
|
|
||||||
* Linux AG Home host machine (Virtual).
|
- Linux AG Home host machine (Virtual)
|
||||||
* Virtual Box.
|
- Virtual Box
|
||||||
* Virtual machine (guest OS doesn't matter).
|
- Virtual machine (guest OS doesn't matter)
|
||||||
|
|
||||||
### Configure Virtual Box
|
### Configure Virtual Box
|
||||||
|
|
||||||
1. Install Virtual Box and run the following command to create a Host-Only
|
1. Install Virtual Box and run the following command to create a Host-Only network:
|
||||||
network:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ VBoxManage hostonlyif create
|
VBoxManage hostonlyif create
|
||||||
```
|
```
|
||||||
|
|
||||||
You can check its status by `ip a` command.
|
You can check its status by `ip a` command.
|
||||||
|
|
||||||
You can also set up Host-Only network using Virtual Box menu:
|
You can also set up Host-Only network using Virtual Box menu in *File → Host Network Manager.*
|
||||||
|
|
||||||
```
|
2. Create your virtual machine and set up its network in *VM Settings → Network → Host-only Adapter.*
|
||||||
File -> Host Network Manager...
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Create your virtual machine and set up its network:
|
3. Start your VM, install an OS. Configure your network interface to use DHCP and the OS should ask for a IP address from our DHCP server.
|
||||||
|
|
||||||
```
|
4. To see the current IP addresses on client OS you can use `ip a` command on Linux or `ipconfig` on Windows.
|
||||||
VM Settings -> Network -> Host-only Adapter
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Start your VM, install an OS. Configure your network interface to use
|
5. To force the client OS to request an IP from DHCP server again, you can use `dhclient` on Linux or `ipconfig /release` on Windows.
|
||||||
DHCP and the OS should ask for a IP address from our DHCP server.
|
|
||||||
|
|
||||||
4. To see the current IP addresses on client OS you can use `ip a` command on
|
### Configure server
|
||||||
Linux or `ipconfig` on Windows.
|
|
||||||
|
|
||||||
5. To force the client OS to request an IP from DHCP server again, you can
|
1. Edit server configuration file `AdGuardHome.yaml`, for example:
|
||||||
use `dhclient` on Linux or `ipconfig /release` on Windows.
|
|
||||||
|
|
||||||
### Configure server
|
```yaml
|
||||||
|
dhcp:
|
||||||
1. Edit server configuration file `AdGuardHome.yaml`, for example:
|
enabled: true
|
||||||
|
interface_name: vboxnet0
|
||||||
```yaml
|
local_domain_name: lan
|
||||||
dhcp:
|
dhcpv4:
|
||||||
enabled: true
|
|
||||||
interface_name: vboxnet0
|
|
||||||
local_domain_name: lan
|
|
||||||
dhcpv4:
|
|
||||||
gateway_ip: 192.168.56.1
|
gateway_ip: 192.168.56.1
|
||||||
subnet_mask: 255.255.255.0
|
subnet_mask: 255.255.255.0
|
||||||
range_start: 192.168.56.2
|
range_start: 192.168.56.2
|
||||||
@@ -63,34 +52,33 @@ To set up a test environment for DHCP server you will need:
|
|||||||
lease_duration: 86400
|
lease_duration: 86400
|
||||||
icmp_timeout_msec: 1000
|
icmp_timeout_msec: 1000
|
||||||
options: []
|
options: []
|
||||||
dhcpv6:
|
dhcpv6:
|
||||||
range_start: 2001::1
|
range_start: 2001::1
|
||||||
lease_duration: 86400
|
lease_duration: 86400
|
||||||
ra_slaac_only: false
|
ra_slaac_only: false
|
||||||
ra_allow_slaac: false
|
ra_allow_slaac: false
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start the server
|
2. Start the server:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./AdGuardHome -v
|
./AdGuardHome -v
|
||||||
```
|
```
|
||||||
|
|
||||||
There should be a message in log which shows that DHCP server is ready:
|
There should be a message in log which shows that DHCP server is ready:
|
||||||
|
|
||||||
```
|
```none
|
||||||
[info] DHCP: listening on 0.0.0.0:67
|
[info] dhcpv4: listening
|
||||||
```
|
```
|
||||||
|
|
||||||
## <a href="#dhcptest" id="dhcptest" name="dhcptest">Quick test with DHCPTest utility</a>
|
## <a href="#dhcptest" id="dhcptest" name="dhcptest">Quick test with DHCPTest utility</a>
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
* [DHCP test utility][dhcptest-gh].
|
- [DHCP test utility][dhcptest-gh].
|
||||||
|
|
||||||
### Quick test
|
### Quick test
|
||||||
|
|
||||||
The DHCP server could be tested for DISCOVER-OFFER packets with in
|
The DHCP server could be tested for DISCOVER-OFFER packets with in interactive mode.
|
||||||
interactive mode.
|
|
||||||
|
|
||||||
[dhcptest-gh]: https://github.com/CyberShadow/dhcptest
|
[dhcptest-gh]: https://github.com/CyberShadow/dhcptest
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ func parseDHCPOptionDur(s string) (val dhcpv4.OptionValue, err error) {
|
|||||||
return nil, fmt.Errorf("decoding dur: %w", err)
|
return nil, fmt.Errorf("decoding dur: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dhcpv4.Duration(v.Duration), nil
|
return dhcpv4.Duration(v), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseDHCPOptionUint parses a DHCP option as an unsigned integer. bitSize is
|
// parseDHCPOptionUint parses a DHCP option as an unsigned integer. bitSize is
|
||||||
|
|||||||
@@ -144,8 +144,8 @@ func TestParseOpt(t *testing.T) {
|
|||||||
in: "24 dur 3y",
|
in: "24 dur 3y",
|
||||||
wantCode: nil,
|
wantCode: nil,
|
||||||
wantVal: nil,
|
wantVal: nil,
|
||||||
wantErrMsg: "invalid option string \"24 dur 3y\": decoding dur: " +
|
wantErrMsg: `invalid option string "24 dur 3y": decoding dur: time: ` +
|
||||||
"unmarshaling duration: time: unknown unit \"y\" in duration \"3y\"",
|
`unknown unit "y" in duration "3y"`,
|
||||||
}, {
|
}, {
|
||||||
name: "u8_error",
|
name: "u8_error",
|
||||||
in: "23 u8 256",
|
in: "23 u8 256",
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ import (
|
|||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/AdguardTeam/golibs/timeutil"
|
"github.com/AdguardTeam/golibs/timeutil"
|
||||||
"github.com/go-ping/ping"
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||||
|
|
||||||
|
//lint:ignore SA1019 See the TODO in go.mod.
|
||||||
|
"github.com/go-ping/ping"
|
||||||
)
|
)
|
||||||
|
|
||||||
// v4Server is a DHCPv4 server.
|
// v4Server is a DHCPv4 server.
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ package dhcpsvc
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/mapsutil"
|
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,14 +79,13 @@ func (conf *Config) Validate() (err error) {
|
|||||||
return errors.Join(errs...)
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapsutil.SortedRange(conf.Interfaces, func(iface string, ic *InterfaceConfig) (ok bool) {
|
for _, iface := range slices.Sorted(maps.Keys(conf.Interfaces)) {
|
||||||
|
ic := conf.Interfaces[iface]
|
||||||
err = ic.validate()
|
err = ic.validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, fmt.Errorf("interface %q: %w", iface, err))
|
errs = append(errs, fmt.Errorf("interface %q: %w", iface, err))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
return errors.Join(errs...)
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ type Empty struct{}
|
|||||||
var _ agh.ServiceWithConfig[*Config] = Empty{}
|
var _ agh.ServiceWithConfig[*Config] = Empty{}
|
||||||
|
|
||||||
// Start implements the [Service] interface for Empty.
|
// Start implements the [Service] interface for Empty.
|
||||||
func (Empty) Start() (err error) { return nil }
|
func (Empty) Start(_ context.Context) (err error) { return nil }
|
||||||
|
|
||||||
// Shutdown implements the [Service] interface for Empty.
|
// Shutdown implements the [Service] interface for Empty.
|
||||||
func (Empty) Shutdown(_ context.Context) (err error) { return nil }
|
func (Empty) Shutdown(_ context.Context) (err error) { return nil }
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/mapsutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DHCPServer is a DHCP server for both IPv4 and IPv6 address families.
|
// DHCPServer is a DHCP server for both IPv4 and IPv6 address families.
|
||||||
@@ -107,7 +108,8 @@ func newInterfaces(
|
|||||||
v6 = make(dhcpInterfacesV6, 0, len(ifaces))
|
v6 = make(dhcpInterfacesV6, 0, len(ifaces))
|
||||||
|
|
||||||
var errs []error
|
var errs []error
|
||||||
mapsutil.SortedRange(ifaces, func(name string, iface *InterfaceConfig) (cont bool) {
|
for _, name := range slices.Sorted(maps.Keys(ifaces)) {
|
||||||
|
iface := ifaces[name]
|
||||||
var i4 *dhcpInterfaceV4
|
var i4 *dhcpInterfaceV4
|
||||||
i4, err = newDHCPInterfaceV4(ctx, l, name, iface.IPv4)
|
i4, err = newDHCPInterfaceV4(ctx, l, name, iface.IPv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -120,9 +122,8 @@ func newInterfaces(
|
|||||||
if i6 != nil {
|
if i6 != nil {
|
||||||
v6 = append(v6, i6)
|
v6 = append(v6, i6)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
if err = errors.Join(errs...); err != nil {
|
if err = errors.Join(errs...); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
|
|||||||
|
|
||||||
cliSrvName, err := clientServerName(pctx, proto)
|
cliSrvName, err := clientServerName(pctx, proto)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", fmt.Errorf("getting client server-name: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
clientID, err = clientIDFromClientServerName(
|
clientID, err = clientIDFromClientServerName(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package dnsforward
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -118,17 +119,13 @@ func clientServerName(pctx *proxy.DNSContext, proto proxy.Proto) (srvName string
|
|||||||
|
|
||||||
switch proto {
|
switch proto {
|
||||||
case proxy.ProtoHTTPS:
|
case proxy.ProtoHTTPS:
|
||||||
r := pctx.HTTPRequest
|
var fromHost bool
|
||||||
if connState := r.TLS; connState != nil {
|
srvName, fromHost, err = clientServerNameFromHTTP(pctx.HTTPRequest)
|
||||||
srvName = connState.ServerName
|
if err != nil {
|
||||||
} else if r.Host != "" {
|
return "", fmt.Errorf("from http: %w", err)
|
||||||
var host string
|
}
|
||||||
host, err = netutil.SplitHost(r.Host)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("parsing host: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
srvName = host
|
if fromHost {
|
||||||
from = "host header"
|
from = "host header"
|
||||||
}
|
}
|
||||||
case proxy.ProtoQUIC:
|
case proxy.ProtoQUIC:
|
||||||
@@ -153,3 +150,23 @@ func clientServerName(pctx *proxy.DNSContext, proto proxy.Proto) (srvName string
|
|||||||
|
|
||||||
return srvName, nil
|
return srvName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clientServerNameFromHTTP returns the TLS server name or the value of the host
|
||||||
|
// header depending on the protocol. fromHost is true if srvName comes from the
|
||||||
|
// "Host" HTTP header.
|
||||||
|
func clientServerNameFromHTTP(r *http.Request) (srvName string, fromHost bool, err error) {
|
||||||
|
if connState := r.TLS; connState != nil {
|
||||||
|
return connState.ServerName, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Host == "" {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
srvName, err = netutil.SplitHost(r.Host)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, fmt.Errorf("parsing host: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return srvName, true, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
|
|||||||
conf.EDNSAddr = net.IP(srvConf.EDNSClientSubnet.CustomIP.AsSlice())
|
conf.EDNSAddr = net.IP(srvConf.EDNSClientSubnet.CustomIP.AsSlice())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = setProxyUpstreamMode(conf, srvConf.UpstreamMode, srvConf.FastestTimeout.Duration)
|
err = setProxyUpstreamMode(conf, srvConf.UpstreamMode, time.Duration(srvConf.FastestTimeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("upstream mode: %w", err)
|
return nil, fmt.Errorf("upstream mode: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -740,7 +740,7 @@ func (s *Server) prepareInternalProxy() (err error) {
|
|||||||
MessageConstructor: s,
|
MessageConstructor: s,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = setProxyUpstreamMode(conf, srvConf.UpstreamMode, srvConf.FastestTimeout.Duration)
|
err = setProxyUpstreamMode(conf, srvConf.UpstreamMode, time.Duration(srvConf.FastestTimeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid upstream mode: %w", err)
|
return fmt.Errorf("invalid upstream mode: %w", err)
|
||||||
}
|
}
|
||||||
@@ -818,6 +818,8 @@ func (s *Server) proxy() (p *proxy.Proxy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reconfigure applies the new configuration to the DNS server.
|
// Reconfigure applies the new configuration to the DNS server.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): This whole piece of API is weird and needs to be remade.
|
||||||
func (s *Server) Reconfigure(conf *ServerConfig) error {
|
func (s *Server) Reconfigure(conf *ServerConfig) error {
|
||||||
s.serverLock.Lock()
|
s.serverLock.Lock()
|
||||||
defer s.serverLock.Unlock()
|
defer s.serverLock.Unlock()
|
||||||
@@ -831,14 +833,15 @@ func (s *Server) Reconfigure(conf *ServerConfig) error {
|
|||||||
// We wait for some time and hope that this fd will be closed.
|
// We wait for some time and hope that this fd will be closed.
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
// TODO(a.garipov): This whole piece of API is weird and needs to be remade.
|
if s.addrProc != nil {
|
||||||
|
err := s.addrProc.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("dnsforward: closing address processor: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if conf == nil {
|
if conf == nil {
|
||||||
conf = &s.conf
|
conf = &s.conf
|
||||||
} else {
|
|
||||||
closeErr := s.addrProc.Close()
|
|
||||||
if closeErr != nil {
|
|
||||||
log.Error("dnsforward: closing address processor: %s", closeErr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(e.burkov): It seems an error here brings the server down, which is
|
// TODO(e.burkov): It seems an error here brings the server down, which is
|
||||||
|
|||||||
@@ -500,6 +500,10 @@ func TestServerRace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSafeSearch(t *testing.T) {
|
func TestSafeSearch(t *testing.T) {
|
||||||
|
const (
|
||||||
|
googleSafeSearch = "forcesafesearch.google.com."
|
||||||
|
)
|
||||||
|
|
||||||
safeSearchConf := filtering.SafeSearchConfig{
|
safeSearchConf := filtering.SafeSearchConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Google: true,
|
Google: true,
|
||||||
@@ -513,12 +517,14 @@ func TestSafeSearch(t *testing.T) {
|
|||||||
SafeSearchCacheSize: 1000,
|
SafeSearchCacheSize: 1000,
|
||||||
CacheTime: 30,
|
CacheTime: 30,
|
||||||
}
|
}
|
||||||
safeSearch, err := safesearch.NewDefault(
|
|
||||||
safeSearchConf,
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
"",
|
safeSearch, err := safesearch.NewDefault(ctx, &safesearch.DefaultConfig{
|
||||||
filterConf.SafeSearchCacheSize,
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
time.Minute*time.Duration(filterConf.CacheTime),
|
ServicesConfig: safeSearchConf,
|
||||||
)
|
CacheSize: filterConf.SafeSearchCacheSize,
|
||||||
|
CacheTTL: time.Minute * time.Duration(filterConf.CacheTime),
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
filterConf.SafeSearch = safeSearch
|
filterConf.SafeSearch = safeSearch
|
||||||
@@ -534,10 +540,17 @@ func TestSafeSearch(t *testing.T) {
|
|||||||
ServePlainDNS: true,
|
ServePlainDNS: true,
|
||||||
}
|
}
|
||||||
s := createTestServer(t, filterConf, forwardConf)
|
s := createTestServer(t, filterConf, forwardConf)
|
||||||
startDeferStop(t, s)
|
|
||||||
|
|
||||||
|
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||||
|
pt := testutil.PanicT{}
|
||||||
|
assert.Equal(pt, googleSafeSearch, req.Question[0].Name)
|
||||||
|
|
||||||
|
return aghtest.MatchedResponse(req, dns.TypeA, googleSafeSearch, "1.2.3.4"), nil
|
||||||
|
})
|
||||||
|
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ups}
|
||||||
|
|
||||||
|
startDeferStop(t, s)
|
||||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP).String()
|
addr := s.dnsProxy.Addr(proxy.ProtoUDP).String()
|
||||||
client := &dns.Client{}
|
|
||||||
|
|
||||||
yandexIP := netip.AddrFrom4([4]byte{213, 180, 193, 56})
|
yandexIP := netip.AddrFrom4([4]byte{213, 180, 193, 56})
|
||||||
|
|
||||||
@@ -584,12 +597,8 @@ func TestSafeSearch(t *testing.T) {
|
|||||||
req := createTestMessage(tc.host)
|
req := createTestMessage(tc.host)
|
||||||
|
|
||||||
var reply *dns.Msg
|
var reply *dns.Msg
|
||||||
require.Eventually(t, func() (ok bool) {
|
reply, err = dns.Exchange(req, addr)
|
||||||
reply, _, err = client.Exchange(req, addr)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return err == nil
|
|
||||||
}, testTimeout*10, testTimeout)
|
|
||||||
require.NoErrorf(t, err, "couldn't talk to server %s: %s", addr, err)
|
|
||||||
|
|
||||||
if tc.wantCNAME != "" {
|
if tc.wantCNAME != "" {
|
||||||
require.Len(t, reply.Answer, 2)
|
require.Len(t, reply.Answer, 2)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package dnsforward
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -203,7 +204,8 @@ func (s *Server) processClientIP(addr netip.Addr) {
|
|||||||
s.serverLock.RLock()
|
s.serverLock.RLock()
|
||||||
defer s.serverLock.RUnlock()
|
defer s.serverLock.RUnlock()
|
||||||
|
|
||||||
s.addrProc.Process(addr)
|
// TODO(s.chzhen): Pass context.
|
||||||
|
s.addrProc.Process(context.TODO(), addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// processDDRQuery responds to Discovery of Designated Resolvers (DDR) SVCB
|
// processDDRQuery responds to Discovery of Designated Resolvers (DDR) SVCB
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package dnsforward
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -90,7 +91,7 @@ func TestServer_ProcessInitial(t *testing.T) {
|
|||||||
|
|
||||||
var gotAddr netip.Addr
|
var gotAddr netip.Addr
|
||||||
s.addrProc = &aghtest.AddressProcessor{
|
s.addrProc = &aghtest.AddressProcessor{
|
||||||
OnProcess: func(ip netip.Addr) { gotAddr = ip },
|
OnProcess: func(ctx context.Context, ip netip.Addr) { gotAddr = ip },
|
||||||
OnClose: func() (err error) { panic("not implemented") },
|
OnClose: func() (err error) { panic("not implemented") },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
# AdGuard Home's DNS filtering go library
|
|
||||||
|
|
||||||
Example use:
|
|
||||||
```bash
|
|
||||||
[ -z "$GOPATH" ] && export GOPATH=$HOME/go
|
|
||||||
go get -d github.com/AdguardTeam/AdGuardHome/filtering
|
|
||||||
```
|
|
||||||
|
|
||||||
Create file filter.go
|
|
||||||
```filter.go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/filtering"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
filter := filtering.New()
|
|
||||||
filter.AddRule("||dou*ck.net^")
|
|
||||||
host := "www.doubleclick.net"
|
|
||||||
res, err := filter.CheckHost(host)
|
|
||||||
if err != nil {
|
|
||||||
// temporary failure
|
|
||||||
log.Fatalf("Failed to check host %q: %s", host, err)
|
|
||||||
}
|
|
||||||
if res.IsFiltered {
|
|
||||||
log.Printf("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rule)
|
|
||||||
} else {
|
|
||||||
log.Printf("Host %s is not filtered, reason - %q", host, res.Reason)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And then run it:
|
|
||||||
```bash
|
|
||||||
go run filter.go
|
|
||||||
```
|
|
||||||
|
|
||||||
You will get:
|
|
||||||
```
|
|
||||||
2000/01/01 00:00:00 Host www.doubleclick.net is filtered, reason - 'FilteredBlackList', matched rule: '||dou*ck.net^'
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also enable checking against AdGuard's SafeBrowsing:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/filtering"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
filter := filtering.New()
|
|
||||||
filter.EnableSafeBrowsing()
|
|
||||||
host := "wmconvirus.narod.ru" // hostname for testing safebrowsing
|
|
||||||
res, err := filter.CheckHost(host)
|
|
||||||
if err != nil {
|
|
||||||
// temporary failure
|
|
||||||
log.Fatalf("Failed to check host %q: %s", host, err)
|
|
||||||
}
|
|
||||||
if res.IsFiltered {
|
|
||||||
log.Printf("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rule)
|
|
||||||
} else {
|
|
||||||
log.Printf("Host %s is not filtered, reason - %q", host, res.Reason)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -33,7 +33,7 @@ func serveHTTPLocally(t *testing.T, h http.Handler) (urlStr string) {
|
|||||||
require.IsType(t, (*net.TCPAddr)(nil), addr)
|
require.IsType(t, (*net.TCPAddr)(nil), addr)
|
||||||
|
|
||||||
return (&url.URL{
|
return (&url.URL{
|
||||||
Scheme: aghhttp.SchemeHTTP,
|
Scheme: urlutil.SchemeHTTP,
|
||||||
Host: addr.String(),
|
Host: addr.String(),
|
||||||
}).String()
|
}).String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
||||||
"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/urlutil"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,19 +41,14 @@ func (d *DNSFilter) validateFilterURL(urlStr string) (err error) {
|
|||||||
|
|
||||||
u, err := url.ParseRequestURI(urlStr)
|
u, err := url.ParseRequestURI(urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap the error since it's informative enough as is.
|
// Don't wrap the error, because it's informative enough as is.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if s := u.Scheme; s != aghhttp.SchemeHTTP && s != aghhttp.SchemeHTTPS {
|
err = urlutil.ValidateHTTPURL(u)
|
||||||
return &url.Error{
|
if err != nil {
|
||||||
Op: "Check scheme",
|
// Don't wrap the error, because it's informative enough as is.
|
||||||
URL: urlStr,
|
return err
|
||||||
Err: fmt.Errorf("only %v allowed", []string{
|
|
||||||
aghhttp.SchemeHTTP,
|
|
||||||
aghhttp.SchemeHTTPS,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ package rulelist
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/urlfilter"
|
"github.com/AdguardTeam/urlfilter"
|
||||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||||
"github.com/c2h5oh/datasize"
|
"github.com/c2h5oh/datasize"
|
||||||
@@ -18,6 +19,9 @@ import (
|
|||||||
//
|
//
|
||||||
// TODO(a.garipov): Merge with [TextEngine] in some way?
|
// TODO(a.garipov): Merge with [TextEngine] in some way?
|
||||||
type Engine struct {
|
type Engine struct {
|
||||||
|
// logger is used to log the operation of the engine and its refreshes.
|
||||||
|
logger *slog.Logger
|
||||||
|
|
||||||
// mu protects engine and storage.
|
// mu protects engine and storage.
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): See if anything else should be protected.
|
// TODO(a.garipov): See if anything else should be protected.
|
||||||
@@ -29,8 +33,7 @@ type Engine struct {
|
|||||||
// storage is the filtering-rule storage. It is saved here to close it.
|
// storage is the filtering-rule storage. It is saved here to close it.
|
||||||
storage *filterlist.RuleStorage
|
storage *filterlist.RuleStorage
|
||||||
|
|
||||||
// name is the human-readable name of the engine, like "allowed", "blocked",
|
// name is the human-readable name of the engine.
|
||||||
// or "custom".
|
|
||||||
name string
|
name string
|
||||||
|
|
||||||
// filters is the data about rule filters in this engine.
|
// filters is the data about rule filters in this engine.
|
||||||
@@ -40,12 +43,15 @@ type Engine struct {
|
|||||||
// EngineConfig is the configuration for rule-list filtering engines created by
|
// EngineConfig is the configuration for rule-list filtering engines created by
|
||||||
// combining refreshable filters.
|
// combining refreshable filters.
|
||||||
type EngineConfig struct {
|
type EngineConfig struct {
|
||||||
// Name is the human-readable name of this engine, like "allowed",
|
// Logger is used to log the operation of the engine. It must not be nil.
|
||||||
// "blocked", or "custom".
|
Logger *slog.Logger
|
||||||
|
|
||||||
|
// name is the human-readable name of the engine; see [EngineNameAllow] and
|
||||||
|
// similar constants.
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
// Filters is the data about rule lists in this engine. There must be no
|
// Filters is the data about rule lists in this engine. There must be no
|
||||||
// other references to the elements of this slice.
|
// other references to the items of this slice. Each item must not be nil.
|
||||||
Filters []*Filter
|
Filters []*Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +59,7 @@ type EngineConfig struct {
|
|||||||
// refreshed, so a refresh should be performed before use.
|
// refreshed, so a refresh should be performed before use.
|
||||||
func NewEngine(c *EngineConfig) (e *Engine) {
|
func NewEngine(c *EngineConfig) (e *Engine) {
|
||||||
return &Engine{
|
return &Engine{
|
||||||
|
logger: c.Logger,
|
||||||
mu: &sync.RWMutex{},
|
mu: &sync.RWMutex{},
|
||||||
name: c.Name,
|
name: c.Name,
|
||||||
filters: c.Filters,
|
filters: c.Filters,
|
||||||
@@ -85,7 +92,7 @@ func (e *Engine) FilterRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// currentEngine returns the current filtering engine.
|
// currentEngine returns the current filtering engine.
|
||||||
func (e *Engine) currentEngine() (enging *urlfilter.DNSEngine) {
|
func (e *Engine) currentEngine() (engine *urlfilter.DNSEngine) {
|
||||||
e.mu.RLock()
|
e.mu.RLock()
|
||||||
defer e.mu.RUnlock()
|
defer e.mu.RUnlock()
|
||||||
|
|
||||||
@@ -96,7 +103,7 @@ func (e *Engine) currentEngine() (enging *urlfilter.DNSEngine) {
|
|||||||
// parseBuf, cli, cacheDir, and maxSize are used for updates of rule-list
|
// parseBuf, cli, cacheDir, and maxSize are used for updates of rule-list
|
||||||
// filters; see [Filter.Refresh].
|
// filters; see [Filter.Refresh].
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): Unexport and test in an internal test or through enigne
|
// TODO(a.garipov): Unexport and test in an internal test or through engine
|
||||||
// tests.
|
// tests.
|
||||||
func (e *Engine) Refresh(
|
func (e *Engine) Refresh(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@@ -115,20 +122,20 @@ func (e *Engine) Refresh(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(filtersToRefresh) == 0 {
|
if len(filtersToRefresh) == 0 {
|
||||||
log.Info("filtering: updating engine %q: no rule-list filters", e.name)
|
e.logger.InfoContext(ctx, "updating: no rule-list filters")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
engRefr := &engineRefresh{
|
engRefr := &engineRefresh{
|
||||||
httpCli: cli,
|
logger: e.logger,
|
||||||
cacheDir: cacheDir,
|
httpCli: cli,
|
||||||
engineName: e.name,
|
cacheDir: cacheDir,
|
||||||
parseBuf: parseBuf,
|
parseBuf: parseBuf,
|
||||||
maxSize: maxSize,
|
maxSize: maxSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleLists, errs := engRefr.process(ctx, e.filters)
|
ruleLists, errs := engRefr.process(ctx, filtersToRefresh)
|
||||||
if isOneTimeoutError(errs) {
|
if isOneTimeoutError(errs) {
|
||||||
// Don't wrap the error since it's informative enough as is.
|
// Don't wrap the error since it's informative enough as is.
|
||||||
return err
|
return err
|
||||||
@@ -141,14 +148,14 @@ func (e *Engine) Refresh(
|
|||||||
return errors.Join(errs...)
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.resetStorage(storage)
|
e.resetStorage(ctx, storage)
|
||||||
|
|
||||||
return errors.Join(errs...)
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resetStorage sets e.storage and e.engine and closes the previous storage.
|
// resetStorage sets e.storage and e.engine and closes the previous storage.
|
||||||
// Errors from closing the previous storage are logged.
|
// Errors from closing the previous storage are logged.
|
||||||
func (e *Engine) resetStorage(storage *filterlist.RuleStorage) {
|
func (e *Engine) resetStorage(ctx context.Context, storage *filterlist.RuleStorage) {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
defer e.mu.Unlock()
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
@@ -161,7 +168,7 @@ func (e *Engine) resetStorage(storage *filterlist.RuleStorage) {
|
|||||||
|
|
||||||
err := prevStorage.Close()
|
err := prevStorage.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("filtering: engine %q: closing old storage: %s", e.name, err)
|
e.logger.WarnContext(ctx, "closing old storage", slogutil.KeyError, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,11 +186,11 @@ func isOneTimeoutError(errs []error) (ok bool) {
|
|||||||
|
|
||||||
// engineRefresh represents a single ongoing engine refresh.
|
// engineRefresh represents a single ongoing engine refresh.
|
||||||
type engineRefresh struct {
|
type engineRefresh struct {
|
||||||
httpCli *http.Client
|
logger *slog.Logger
|
||||||
cacheDir string
|
httpCli *http.Client
|
||||||
engineName string
|
cacheDir string
|
||||||
parseBuf []byte
|
parseBuf []byte
|
||||||
maxSize datasize.ByteSize
|
maxSize datasize.ByteSize
|
||||||
}
|
}
|
||||||
|
|
||||||
// process runs updates of all given rule-list filters. All errors are logged
|
// process runs updates of all given rule-list filters. All errors are logged
|
||||||
@@ -216,12 +223,12 @@ func (r *engineRefresh) process(
|
|||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
|
|
||||||
// Also log immediately, since the update can take a lot of time.
|
// Also log immediately, since the update can take a lot of time.
|
||||||
log.Error(
|
r.logger.ErrorContext(
|
||||||
"filtering: updating engine %q: rule list %s from url %q: %s\n",
|
ctx,
|
||||||
r.engineName,
|
"updating rule list",
|
||||||
f.uid,
|
"uid", f.uid,
|
||||||
f.url,
|
"url", f.url,
|
||||||
err,
|
slogutil.KeyError, err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,17 +244,17 @@ func (r *engineRefresh) processFilter(ctx context.Context, f *Filter) (err error
|
|||||||
}
|
}
|
||||||
|
|
||||||
if prevChecksum == parseRes.Checksum {
|
if prevChecksum == parseRes.Checksum {
|
||||||
log.Info("filtering: engine %q: filter %q: no change", r.engineName, f.uid)
|
r.logger.InfoContext(ctx, "no change in filter", "uid", f.uid)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(
|
r.logger.InfoContext(
|
||||||
"filtering: updated engine %q: filter %q: %d bytes, %d rules",
|
ctx,
|
||||||
r.engineName,
|
"filter updated",
|
||||||
f.uid,
|
"uid", f.uid,
|
||||||
parseRes.BytesWritten,
|
"bytes", parseRes.BytesWritten,
|
||||||
parseRes.RulesCount,
|
"rules", parseRes.RulesCount,
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/AdguardTeam/urlfilter"
|
"github.com/AdguardTeam/urlfilter"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@@ -13,6 +14,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestEngine_Refresh(t *testing.T) {
|
func TestEngine_Refresh(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
cacheDir := t.TempDir()
|
cacheDir := t.TempDir()
|
||||||
|
|
||||||
fileURL, srvURL := newFilterLocations(t, cacheDir, testRuleTextBlocked, testRuleTextBlocked2)
|
fileURL, srvURL := newFilterLocations(t, cacheDir, testRuleTextBlocked, testRuleTextBlocked2)
|
||||||
@@ -21,6 +24,7 @@ func TestEngine_Refresh(t *testing.T) {
|
|||||||
httpFlt := newFilter(t, srvURL, "HTTP Filter")
|
httpFlt := newFilter(t, srvURL, "HTTP Filter")
|
||||||
|
|
||||||
eng := rulelist.NewEngine(&rulelist.EngineConfig{
|
eng := rulelist.NewEngine(&rulelist.EngineConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
Name: "Engine",
|
Name: "Engine",
|
||||||
Filters: []*rulelist.Filter{fileFlt, httpFlt},
|
Filters: []*rulelist.Filter{fileFlt, httpFlt},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ func NewFilter(c *FilterConfig) (f *Filter, err error) {
|
|||||||
// buffer used to parse information from the data. cli and maxSize are only
|
// buffer used to parse information from the data. cli and maxSize are only
|
||||||
// used when f is a URL-based list.
|
// used when f is a URL-based list.
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): Unexport and test in an internal test or through enigne
|
// TODO(a.garipov): Unexport and test in an internal test or through engine
|
||||||
// tests.
|
// tests.
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): Consider not returning parseRes.
|
// TODO(a.garipov): Consider not returning parseRes.
|
||||||
|
|||||||
@@ -8,14 +8,16 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFilter_Refresh(t *testing.T) {
|
func TestFilter_Refresh(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
cacheDir := t.TempDir()
|
cacheDir := t.TempDir()
|
||||||
uid := rulelist.MustNewUID()
|
|
||||||
|
|
||||||
const fltData = testRuleTextTitle + testRuleTextBlocked
|
const fltData = testRuleTextTitle + testRuleTextBlocked
|
||||||
fileURL, srvURL := newFilterLocations(t, cacheDir, fltData, fltData)
|
fileURL, srvURL := newFilterLocations(t, cacheDir, fltData, fltData)
|
||||||
@@ -37,7 +39,7 @@ func TestFilter_Refresh(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
name: "file",
|
name: "file",
|
||||||
url: &url.URL{
|
url: &url.URL{
|
||||||
Scheme: "file",
|
Scheme: urlutil.SchemeFile,
|
||||||
Path: fileURL.Path,
|
Path: fileURL.Path,
|
||||||
},
|
},
|
||||||
wantNewErrMsg: "",
|
wantNewErrMsg: "",
|
||||||
@@ -49,6 +51,9 @@ func TestFilter_Refresh(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
uid := rulelist.MustNewUID()
|
||||||
f, err := rulelist.NewFilter(&rulelist.FilterConfig{
|
f, err := rulelist.NewFilter(&rulelist.FilterConfig{
|
||||||
URL: tc.url,
|
URL: tc.url,
|
||||||
Name: tc.name,
|
Name: tc.name,
|
||||||
|
|||||||
@@ -71,3 +71,10 @@ var _ fmt.Stringer = UID{}
|
|||||||
func (id UID) String() (s string) {
|
func (id UID) String() (s string) {
|
||||||
return uuid.UUID(id).String()
|
return uuid.UUID(id).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Common engine names.
|
||||||
|
const (
|
||||||
|
EngineNameAllow = "allow"
|
||||||
|
EngineNameBlock = "block"
|
||||||
|
EngineNameCustom = "custom"
|
||||||
|
)
|
||||||
|
|||||||
@@ -6,20 +6,16 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
testutil.DiscardLogOutput(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// testTimeout is the common timeout for tests.
|
// testTimeout is the common timeout for tests.
|
||||||
const testTimeout = 1 * time.Second
|
const testTimeout = 1 * time.Second
|
||||||
|
|
||||||
@@ -31,6 +27,7 @@ const testTitle = "Test Title"
|
|||||||
|
|
||||||
// Common rule texts for tests.
|
// Common rule texts for tests.
|
||||||
const (
|
const (
|
||||||
|
testRuleTextAllowed = "||allowed.example^\n"
|
||||||
testRuleTextBadTab = "||bad-tab-and-comment.example^\t# A comment.\n"
|
testRuleTextBadTab = "||bad-tab-and-comment.example^\t# A comment.\n"
|
||||||
testRuleTextBlocked = "||blocked.example^\n"
|
testRuleTextBlocked = "||blocked.example^\n"
|
||||||
testRuleTextBlocked2 = "||blocked-2.example^\n"
|
testRuleTextBlocked2 = "||blocked-2.example^\n"
|
||||||
@@ -79,8 +76,16 @@ func newFilterLocations(
|
|||||||
fileData string,
|
fileData string,
|
||||||
httpData string,
|
httpData string,
|
||||||
) (fileURL, srvURL *url.URL) {
|
) (fileURL, srvURL *url.URL) {
|
||||||
filePath := filepath.Join(cacheDir, "initial.txt")
|
t.Helper()
|
||||||
err := os.WriteFile(filePath, []byte(fileData), 0o644)
|
|
||||||
|
f, err := os.CreateTemp(cacheDir, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = f.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
filePath := f.Name()
|
||||||
|
err = os.WriteFile(filePath, []byte(fileData), 0o644)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
testutil.CleanupAndRequireSuccess(t, func() (err error) {
|
testutil.CleanupAndRequireSuccess(t, func() (err error) {
|
||||||
@@ -88,7 +93,7 @@ func newFilterLocations(
|
|||||||
})
|
})
|
||||||
|
|
||||||
fileURL = &url.URL{
|
fileURL = &url.URL{
|
||||||
Scheme: "file",
|
Scheme: urlutil.SchemeFile,
|
||||||
Path: filePath,
|
Path: filePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
112
internal/filtering/rulelist/storage.go
Normal file
112
internal/filtering/rulelist/storage.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package rulelist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/c2h5oh/datasize"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Storage contains the main filtering engines, including the allowlist, the
|
||||||
|
// blocklist, and the user's custom filtering rules.
|
||||||
|
type Storage struct {
|
||||||
|
// refreshMu makes sure that only one update takes place at a time.
|
||||||
|
refreshMu *sync.Mutex
|
||||||
|
|
||||||
|
allow *Engine
|
||||||
|
block *Engine
|
||||||
|
custom *TextEngine
|
||||||
|
httpCli *http.Client
|
||||||
|
cacheDir string
|
||||||
|
parseBuf []byte
|
||||||
|
maxSize datasize.ByteSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageConfig is the configuration for the filtering-engine storage.
|
||||||
|
type StorageConfig struct {
|
||||||
|
// Logger is used to log the operation of the storage. It must not be nil.
|
||||||
|
Logger *slog.Logger
|
||||||
|
|
||||||
|
// HTTPClient is the HTTP client used to perform updates of rule lists.
|
||||||
|
// It must not be nil.
|
||||||
|
HTTPClient *http.Client
|
||||||
|
|
||||||
|
// CacheDir is the path to the directory used to cache rule-list files.
|
||||||
|
// It must be set.
|
||||||
|
CacheDir string
|
||||||
|
|
||||||
|
// AllowFilters are the filtering-rule lists used to exclude domain names
|
||||||
|
// from the filtering. Each item must not be nil.
|
||||||
|
AllowFilters []*Filter
|
||||||
|
|
||||||
|
// BlockFilters are the filtering-rule lists used to block domain names.
|
||||||
|
// Each item must not be nil.
|
||||||
|
BlockFilters []*Filter
|
||||||
|
|
||||||
|
// CustomRules contains custom rules of the user. They have priority over
|
||||||
|
// both allow- and blacklist rules.
|
||||||
|
CustomRules []string
|
||||||
|
|
||||||
|
// MaxRuleListTextSize is the maximum size of a rule-list file. It must be
|
||||||
|
// greater than zero.
|
||||||
|
MaxRuleListTextSize datasize.ByteSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStorage creates a new filtering-engine storage. The engines are not
|
||||||
|
// refreshed, so a refresh should be performed before use.
|
||||||
|
func NewStorage(c *StorageConfig) (s *Storage, err error) {
|
||||||
|
custom, err := NewTextEngine(&TextEngineConfig{
|
||||||
|
Name: EngineNameCustom,
|
||||||
|
Rules: c.CustomRules,
|
||||||
|
ID: URLFilterIDCustom,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating custom engine: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Storage{
|
||||||
|
refreshMu: &sync.Mutex{},
|
||||||
|
allow: NewEngine(&EngineConfig{
|
||||||
|
Logger: c.Logger.With("engine", EngineNameAllow),
|
||||||
|
Name: EngineNameAllow,
|
||||||
|
Filters: c.AllowFilters,
|
||||||
|
}),
|
||||||
|
block: NewEngine(&EngineConfig{
|
||||||
|
Logger: c.Logger.With("engine", EngineNameBlock),
|
||||||
|
Name: EngineNameBlock,
|
||||||
|
Filters: c.BlockFilters,
|
||||||
|
}),
|
||||||
|
custom: custom,
|
||||||
|
httpCli: c.HTTPClient,
|
||||||
|
cacheDir: c.CacheDir,
|
||||||
|
parseBuf: make([]byte, DefaultRuleBufSize),
|
||||||
|
maxSize: c.MaxRuleListTextSize,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the underlying rule-list engines.
|
||||||
|
func (s *Storage) Close() (err error) {
|
||||||
|
// Don't wrap the errors since they are informative enough as is.
|
||||||
|
return errors.Join(
|
||||||
|
s.allow.Close(),
|
||||||
|
s.block.Close(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh updates all engines in s.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Refresh allow and block separately?
|
||||||
|
func (s *Storage) Refresh(ctx context.Context) (err error) {
|
||||||
|
s.refreshMu.Lock()
|
||||||
|
defer s.refreshMu.Unlock()
|
||||||
|
|
||||||
|
// Don't wrap the errors since they are informative enough as is.
|
||||||
|
return errors.Join(
|
||||||
|
s.allow.Refresh(ctx, s.parseBuf, s.httpCli, s.cacheDir, s.maxSize),
|
||||||
|
s.block.Refresh(ctx, s.parseBuf, s.httpCli, s.cacheDir, s.maxSize),
|
||||||
|
)
|
||||||
|
}
|
||||||
49
internal/filtering/rulelist/storage_test.go
Normal file
49
internal/filtering/rulelist/storage_test.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package rulelist_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
|
"github.com/c2h5oh/datasize"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStorage_Refresh(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cacheDir := t.TempDir()
|
||||||
|
|
||||||
|
allowedFileURL, _ := newFilterLocations(t, cacheDir, testRuleTextAllowed, "")
|
||||||
|
allowedFlt := newFilter(t, allowedFileURL, "Allowed 1")
|
||||||
|
|
||||||
|
blockedFileURL, _ := newFilterLocations(t, cacheDir, testRuleTextBlocked, "")
|
||||||
|
blockedFlt := newFilter(t, blockedFileURL, "Blocked 1")
|
||||||
|
|
||||||
|
strg, err := rulelist.NewStorage(&rulelist.StorageConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
|
HTTPClient: &http.Client{
|
||||||
|
Timeout: testTimeout,
|
||||||
|
},
|
||||||
|
CacheDir: cacheDir,
|
||||||
|
AllowFilters: []*rulelist.Filter{
|
||||||
|
allowedFlt,
|
||||||
|
},
|
||||||
|
BlockFilters: []*rulelist.Filter{
|
||||||
|
blockedFlt,
|
||||||
|
},
|
||||||
|
CustomRules: []string{
|
||||||
|
testRuleTextBlocked2,
|
||||||
|
},
|
||||||
|
MaxRuleListTextSize: 1 * datasize.KB,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutil.CleanupAndRequireSuccess(t, strg.Close)
|
||||||
|
|
||||||
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
err = strg.Refresh(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
@@ -20,15 +20,15 @@ type TextEngine struct {
|
|||||||
// storage is the filtering-rule storage. It is saved here to close it.
|
// storage is the filtering-rule storage. It is saved here to close it.
|
||||||
storage *filterlist.RuleStorage
|
storage *filterlist.RuleStorage
|
||||||
|
|
||||||
// name is the human-readable name of the engine, like "custom".
|
// name is the human-readable name of the engine.
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
// TextEngineConfig is the configuration for a rule-list filtering engine
|
// TextEngineConfig is the configuration for a rule-list filtering engine
|
||||||
// created from a filtering rule text.
|
// created from a filtering rule text.
|
||||||
type TextEngineConfig struct {
|
type TextEngineConfig struct {
|
||||||
// Name is the human-readable name of this engine, like "allowed",
|
// name is the human-readable name of the engine; see [EngineNameAllow] and
|
||||||
// "blocked", or "custom".
|
// similar constants.
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
// Rules is the text of the filtering rules for this engine.
|
// Rules is the text of the filtering rules for this engine.
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestNewTextEngine(t *testing.T) {
|
func TestNewTextEngine(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
eng, err := rulelist.NewTextEngine(&rulelist.TextEngineConfig{
|
eng, err := rulelist.NewTextEngine(&rulelist.TextEngineConfig{
|
||||||
Name: "RulesEngine",
|
Name: "RulesEngine",
|
||||||
Rules: []string{
|
Rules: []string{
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package filtering
|
package filtering
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
// SafeSearch interface describes a service for search engines hosts rewrites.
|
// SafeSearch interface describes a service for search engines hosts rewrites.
|
||||||
type SafeSearch interface {
|
type SafeSearch interface {
|
||||||
// CheckHost checks host with safe search filter. CheckHost must be safe
|
// CheckHost checks host with safe search filter. CheckHost must be safe
|
||||||
// for concurrent use. qtype must be either [dns.TypeA] or [dns.TypeAAAA].
|
// for concurrent use. qtype must be either [dns.TypeA] or [dns.TypeAAAA].
|
||||||
CheckHost(host string, qtype uint16) (res Result, err error)
|
CheckHost(ctx context.Context, host string, qtype uint16) (res Result, err error)
|
||||||
|
|
||||||
// Update updates the configuration of the safe search filter. Update must
|
// Update updates the configuration of the safe search filter. Update must
|
||||||
// be safe for concurrent use. An implementation of Update may ignore some
|
// be safe for concurrent use. An implementation of Update may ignore some
|
||||||
// fields, but it must document which.
|
// fields, but it must document which.
|
||||||
Update(conf SafeSearchConfig) (err error)
|
Update(ctx context.Context, conf SafeSearchConfig) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SafeSearchConfig is a struct with safe search related settings.
|
// SafeSearchConfig is a struct with safe search related settings.
|
||||||
@@ -40,10 +42,13 @@ func (d *DNSFilter) checkSafeSearch(
|
|||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(s.chzhen): Pass context.
|
||||||
|
ctx := context.TODO()
|
||||||
|
|
||||||
clientSafeSearch := setts.ClientSafeSearch
|
clientSafeSearch := setts.ClientSafeSearch
|
||||||
if clientSafeSearch != nil {
|
if clientSafeSearch != nil {
|
||||||
return clientSafeSearch.CheckHost(host, qtype)
|
return clientSafeSearch.CheckHost(ctx, host, qtype)
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.safeSearch.CheckHost(host, qtype)
|
return d.safeSearch.CheckHost(ctx, host, qtype)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package safesearch
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -14,13 +16,20 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
||||||
"github.com/AdguardTeam/golibs/cache"
|
"github.com/AdguardTeam/golibs/cache"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/urlfilter"
|
"github.com/AdguardTeam/urlfilter"
|
||||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||||
"github.com/AdguardTeam/urlfilter/rules"
|
"github.com/AdguardTeam/urlfilter/rules"
|
||||||
|
"github.com/c2h5oh/datasize"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Attribute keys and values for logging.
|
||||||
|
const (
|
||||||
|
LogPrefix = "safesearch"
|
||||||
|
LogKeyClient = "client"
|
||||||
|
)
|
||||||
|
|
||||||
// Service is a enum with service names used as search providers.
|
// Service is a enum with service names used as search providers.
|
||||||
type Service string
|
type Service string
|
||||||
|
|
||||||
@@ -57,9 +66,32 @@ func isServiceProtected(s filtering.SafeSearchConfig, service Service) (ok bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultConfig is the configuration structure for [Default].
|
||||||
|
type DefaultConfig struct {
|
||||||
|
// Logger is used for logging the operation of the safe search filter.
|
||||||
|
Logger *slog.Logger
|
||||||
|
|
||||||
|
// ClientName is the name of the persistent client associated with the safe
|
||||||
|
// search filter, if there is one.
|
||||||
|
ClientName string
|
||||||
|
|
||||||
|
// CacheSize is the size of the filter results cache.
|
||||||
|
CacheSize uint
|
||||||
|
|
||||||
|
// CacheTTL is the Time to Live duration for cached items.
|
||||||
|
CacheTTL time.Duration
|
||||||
|
|
||||||
|
// ServicesConfig contains safe search settings for services. It must not
|
||||||
|
// be nil.
|
||||||
|
ServicesConfig filtering.SafeSearchConfig
|
||||||
|
}
|
||||||
|
|
||||||
// Default is the default safe search filter that uses filtering rules with the
|
// Default is the default safe search filter that uses filtering rules with the
|
||||||
// dnsrewrite modifier.
|
// dnsrewrite modifier.
|
||||||
type Default struct {
|
type Default struct {
|
||||||
|
// logger is used for logging the operation of the safe search filter.
|
||||||
|
logger *slog.Logger
|
||||||
|
|
||||||
// mu protects engine.
|
// mu protects engine.
|
||||||
mu *sync.RWMutex
|
mu *sync.RWMutex
|
||||||
|
|
||||||
@@ -67,33 +99,28 @@ type Default struct {
|
|||||||
// engine may be nil, which means that this safe search filter is disabled.
|
// engine may be nil, which means that this safe search filter is disabled.
|
||||||
engine *urlfilter.DNSEngine
|
engine *urlfilter.DNSEngine
|
||||||
|
|
||||||
cache cache.Cache
|
// cache stores safe search filtering results.
|
||||||
logPrefix string
|
cache cache.Cache
|
||||||
cacheTTL time.Duration
|
|
||||||
|
// cacheTTL is the Time to Live duration for cached items.
|
||||||
|
cacheTTL time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDefault returns an initialized default safe search filter. name is used
|
// NewDefault returns an initialized default safe search filter. ctx is used
|
||||||
// for logging.
|
// to log the initial refresh.
|
||||||
func NewDefault(
|
func NewDefault(ctx context.Context, conf *DefaultConfig) (ss *Default, err error) {
|
||||||
conf filtering.SafeSearchConfig,
|
|
||||||
name string,
|
|
||||||
cacheSize uint,
|
|
||||||
cacheTTL time.Duration,
|
|
||||||
) (ss *Default, err error) {
|
|
||||||
ss = &Default{
|
ss = &Default{
|
||||||
mu: &sync.RWMutex{},
|
logger: conf.Logger,
|
||||||
|
mu: &sync.RWMutex{},
|
||||||
cache: cache.New(cache.Config{
|
cache: cache.New(cache.Config{
|
||||||
EnableLRU: true,
|
EnableLRU: true,
|
||||||
MaxSize: cacheSize,
|
MaxSize: conf.CacheSize,
|
||||||
}),
|
}),
|
||||||
// Use %s, because the client safe-search names already contain double
|
cacheTTL: conf.CacheTTL,
|
||||||
// quotes.
|
|
||||||
logPrefix: fmt.Sprintf("safesearch %s: ", name),
|
|
||||||
cacheTTL: cacheTTL,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ss.resetEngine(rulelist.URLFilterIDSafeSearch, conf)
|
// TODO(s.chzhen): Move to [Default.InitialRefresh].
|
||||||
|
err = ss.resetEngine(ctx, rulelist.URLFilterIDSafeSearch, conf.ServicesConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap the error, because it's informative enough as is.
|
// Don't wrap the error, because it's informative enough as is.
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -102,29 +129,15 @@ func NewDefault(
|
|||||||
return ss, nil
|
return ss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// log is a helper for logging that includes the name of the safe search
|
|
||||||
// filter. level must be one of [log.DEBUG], [log.INFO], and [log.ERROR].
|
|
||||||
func (ss *Default) log(level log.Level, msg string, args ...any) {
|
|
||||||
switch level {
|
|
||||||
case log.DEBUG:
|
|
||||||
log.Debug(ss.logPrefix+msg, args...)
|
|
||||||
case log.INFO:
|
|
||||||
log.Info(ss.logPrefix+msg, args...)
|
|
||||||
case log.ERROR:
|
|
||||||
log.Error(ss.logPrefix+msg, args...)
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("safesearch: unsupported logging level %d", level))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// resetEngine creates new engine for provided safe search configuration and
|
// resetEngine creates new engine for provided safe search configuration and
|
||||||
// sets it in ss.
|
// sets it in ss.
|
||||||
func (ss *Default) resetEngine(
|
func (ss *Default) resetEngine(
|
||||||
|
ctx context.Context,
|
||||||
listID int,
|
listID int,
|
||||||
conf filtering.SafeSearchConfig,
|
conf filtering.SafeSearchConfig,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
if !conf.Enabled {
|
if !conf.Enabled {
|
||||||
ss.log(log.INFO, "disabled")
|
ss.logger.DebugContext(ctx, "disabled")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -149,7 +162,7 @@ func (ss *Default) resetEngine(
|
|||||||
|
|
||||||
ss.engine = urlfilter.NewDNSEngine(rs)
|
ss.engine = urlfilter.NewDNSEngine(rs)
|
||||||
|
|
||||||
ss.log(log.INFO, "reset %d rules", ss.engine.RulesCount)
|
ss.logger.InfoContext(ctx, "reset rules", "count", ss.engine.RulesCount)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -158,10 +171,14 @@ func (ss *Default) resetEngine(
|
|||||||
var _ filtering.SafeSearch = (*Default)(nil)
|
var _ filtering.SafeSearch = (*Default)(nil)
|
||||||
|
|
||||||
// CheckHost implements the [filtering.SafeSearch] interface for *Default.
|
// CheckHost implements the [filtering.SafeSearch] interface for *Default.
|
||||||
func (ss *Default) CheckHost(host string, qtype rules.RRType) (res filtering.Result, err error) {
|
func (ss *Default) CheckHost(
|
||||||
|
ctx context.Context,
|
||||||
|
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.logger.DebugContext(ctx, "lookup finished", "host", host, "elapsed", time.Since(start))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
switch qtype {
|
switch qtype {
|
||||||
@@ -172,9 +189,9 @@ func (ss *Default) CheckHost(host string, qtype rules.RRType) (res filtering.Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check cache. Return cached result if it was found
|
// Check cache. Return cached result if it was found
|
||||||
cachedValue, isFound := ss.getCachedResult(host, qtype)
|
cachedValue, isFound := ss.getCachedResult(ctx, host, qtype)
|
||||||
if isFound {
|
if isFound {
|
||||||
ss.log(log.DEBUG, "found in cache: %q", host)
|
ss.logger.DebugContext(ctx, "found in cache", "host", host)
|
||||||
|
|
||||||
return cachedValue, nil
|
return cachedValue, nil
|
||||||
}
|
}
|
||||||
@@ -186,7 +203,7 @@ func (ss *Default) CheckHost(host string, qtype rules.RRType) (res filtering.Res
|
|||||||
|
|
||||||
fltRes, err := ss.newResult(rewrite, qtype)
|
fltRes, err := ss.newResult(rewrite, qtype)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ss.log(log.DEBUG, "looking up addresses for %q: %s", host, err)
|
ss.logger.ErrorContext(ctx, "looking up addresses", "host", host, slogutil.KeyError, err)
|
||||||
|
|
||||||
return filtering.Result{}, err
|
return filtering.Result{}, err
|
||||||
}
|
}
|
||||||
@@ -195,7 +212,7 @@ func (ss *Default) CheckHost(host string, qtype rules.RRType) (res filtering.Res
|
|||||||
|
|
||||||
// TODO(a.garipov): Consider switch back to resolving CNAME records IPs and
|
// TODO(a.garipov): Consider switch back to resolving CNAME records IPs and
|
||||||
// saving results to cache.
|
// saving results to cache.
|
||||||
ss.setCacheResult(host, qtype, res)
|
ss.setCacheResult(ctx, host, qtype, res)
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
@@ -255,7 +272,12 @@ func (ss *Default) newResult(
|
|||||||
|
|
||||||
// setCacheResult stores data in cache for host. qtype is expected to be either
|
// setCacheResult stores data in cache for host. qtype is expected to be either
|
||||||
// [dns.TypeA] or [dns.TypeAAAA].
|
// [dns.TypeA] or [dns.TypeAAAA].
|
||||||
func (ss *Default) setCacheResult(host string, qtype rules.RRType, res filtering.Result) {
|
func (ss *Default) setCacheResult(
|
||||||
|
ctx context.Context,
|
||||||
|
host string,
|
||||||
|
qtype rules.RRType,
|
||||||
|
res filtering.Result,
|
||||||
|
) {
|
||||||
expire := uint32(time.Now().Add(ss.cacheTTL).Unix())
|
expire := uint32(time.Now().Add(ss.cacheTTL).Unix())
|
||||||
exp := make([]byte, 4)
|
exp := make([]byte, 4)
|
||||||
binary.BigEndian.PutUint32(exp, expire)
|
binary.BigEndian.PutUint32(exp, expire)
|
||||||
@@ -263,7 +285,7 @@ func (ss *Default) setCacheResult(host string, qtype rules.RRType, res filtering
|
|||||||
|
|
||||||
err := gob.NewEncoder(buf).Encode(res)
|
err := gob.NewEncoder(buf).Encode(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ss.log(log.ERROR, "cache encoding: %s", err)
|
ss.logger.ErrorContext(ctx, "cache encoding", slogutil.KeyError, err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -271,12 +293,18 @@ func (ss *Default) setCacheResult(host string, qtype rules.RRType, res filtering
|
|||||||
val := buf.Bytes()
|
val := buf.Bytes()
|
||||||
_ = ss.cache.Set([]byte(dns.Type(qtype).String()+" "+host), val)
|
_ = ss.cache.Set([]byte(dns.Type(qtype).String()+" "+host), val)
|
||||||
|
|
||||||
ss.log(log.DEBUG, "stored in cache: %q, %d bytes", host, len(val))
|
ss.logger.DebugContext(
|
||||||
|
ctx,
|
||||||
|
"stored in cache",
|
||||||
|
"host", host,
|
||||||
|
"entry_size", datasize.ByteSize(len(val)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCachedResult returns stored data from cache for host. qtype is expected
|
// getCachedResult returns stored data from cache for host. qtype is expected
|
||||||
// to be either [dns.TypeA] or [dns.TypeAAAA].
|
// to be either [dns.TypeA] or [dns.TypeAAAA].
|
||||||
func (ss *Default) getCachedResult(
|
func (ss *Default) getCachedResult(
|
||||||
|
ctx context.Context,
|
||||||
host string,
|
host string,
|
||||||
qtype rules.RRType,
|
qtype rules.RRType,
|
||||||
) (res filtering.Result, ok bool) {
|
) (res filtering.Result, ok bool) {
|
||||||
@@ -298,7 +326,7 @@ func (ss *Default) getCachedResult(
|
|||||||
|
|
||||||
err := gob.NewDecoder(buf).Decode(&res)
|
err := gob.NewDecoder(buf).Decode(&res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ss.log(log.ERROR, "cache decoding: %s", err)
|
ss.logger.ErrorContext(ctx, "cache decoding", slogutil.KeyError, err)
|
||||||
|
|
||||||
return filtering.Result{}, false
|
return filtering.Result{}, false
|
||||||
}
|
}
|
||||||
@@ -308,11 +336,11 @@ func (ss *Default) getCachedResult(
|
|||||||
|
|
||||||
// Update implements the [filtering.SafeSearch] interface for *Default. Update
|
// Update implements the [filtering.SafeSearch] interface for *Default. Update
|
||||||
// ignores the CustomResolver and Enabled fields.
|
// ignores the CustomResolver and Enabled fields.
|
||||||
func (ss *Default) Update(conf filtering.SafeSearchConfig) (err error) {
|
func (ss *Default) Update(ctx context.Context, conf filtering.SafeSearchConfig) (err error) {
|
||||||
ss.mu.Lock()
|
ss.mu.Lock()
|
||||||
defer ss.mu.Unlock()
|
defer ss.mu.Unlock()
|
||||||
|
|
||||||
err = ss.resetEngine(rulelist.URLFilterIDSafeSearch, conf)
|
err = ss.resetEngine(ctx, rulelist.URLFilterIDSafeSearch, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap the error, because it's informative enough as is.
|
// Don't wrap the error, because it's informative enough as is.
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/AdguardTeam/urlfilter/rules"
|
"github.com/AdguardTeam/urlfilter/rules"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -21,6 +23,9 @@ const (
|
|||||||
testCacheTTL = 30 * time.Minute
|
testCacheTTL = 30 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// testTimeout is the common timeout for tests and contexts.
|
||||||
|
const testTimeout = 1 * time.Second
|
||||||
|
|
||||||
var defaultSafeSearchConf = filtering.SafeSearchConfig{
|
var defaultSafeSearchConf = filtering.SafeSearchConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Bing: true,
|
Bing: true,
|
||||||
@@ -35,7 +40,12 @@ var defaultSafeSearchConf = filtering.SafeSearchConfig{
|
|||||||
var yandexIP = netip.AddrFrom4([4]byte{213, 180, 193, 56})
|
var yandexIP = netip.AddrFrom4([4]byte{213, 180, 193, 56})
|
||||||
|
|
||||||
func newForTest(t testing.TB, ssConf filtering.SafeSearchConfig) (ss *Default) {
|
func newForTest(t testing.TB, ssConf filtering.SafeSearchConfig) (ss *Default) {
|
||||||
ss, err := NewDefault(ssConf, "", testCacheSize, testCacheTTL)
|
ss, err := NewDefault(testutil.ContextWithTimeout(t, testTimeout), &DefaultConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
|
ServicesConfig: ssConf,
|
||||||
|
CacheSize: testCacheSize,
|
||||||
|
CacheTTL: testCacheTTL,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return ss
|
return ss
|
||||||
@@ -52,16 +62,17 @@ func TestSafeSearchCacheYandex(t *testing.T) {
|
|||||||
const domain = "yandex.ru"
|
const domain = "yandex.ru"
|
||||||
|
|
||||||
ss := newForTest(t, filtering.SafeSearchConfig{Enabled: false})
|
ss := newForTest(t, filtering.SafeSearchConfig{Enabled: false})
|
||||||
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
|
||||||
// Check host with disabled safesearch.
|
// Check host with disabled safesearch.
|
||||||
res, err := ss.CheckHost(domain, testQType)
|
res, err := ss.CheckHost(ctx, domain, testQType)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.False(t, res.IsFiltered)
|
assert.False(t, res.IsFiltered)
|
||||||
assert.Empty(t, res.Rules)
|
assert.Empty(t, res.Rules)
|
||||||
|
|
||||||
ss = newForTest(t, defaultSafeSearchConf)
|
ss = newForTest(t, defaultSafeSearchConf)
|
||||||
res, err = ss.CheckHost(domain, testQType)
|
res, err = ss.CheckHost(ctx, domain, testQType)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// For yandex we already know valid IP.
|
// For yandex we already know valid IP.
|
||||||
@@ -70,7 +81,7 @@ func TestSafeSearchCacheYandex(t *testing.T) {
|
|||||||
assert.Equal(t, res.Rules[0].IP, yandexIP)
|
assert.Equal(t, res.Rules[0].IP, yandexIP)
|
||||||
|
|
||||||
// Check cache.
|
// Check cache.
|
||||||
cachedValue, isFound := ss.getCachedResult(domain, testQType)
|
cachedValue, isFound := ss.getCachedResult(ctx, domain, testQType)
|
||||||
require.True(t, isFound)
|
require.True(t, isFound)
|
||||||
require.Len(t, cachedValue.Rules, 1)
|
require.Len(t, cachedValue.Rules, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
// testTimeout is the common timeout for tests and contexts.
|
||||||
testutil.DiscardLogOutput(m)
|
const testTimeout = 1 * time.Second
|
||||||
}
|
|
||||||
|
|
||||||
// Common test constants.
|
// Common test constants.
|
||||||
const (
|
const (
|
||||||
@@ -47,7 +47,13 @@ var yandexIP = netip.AddrFrom4([4]byte{213, 180, 193, 56})
|
|||||||
|
|
||||||
func TestDefault_CheckHost_yandex(t *testing.T) {
|
func TestDefault_CheckHost_yandex(t *testing.T) {
|
||||||
conf := testConf
|
conf := testConf
|
||||||
ss, err := safesearch.NewDefault(conf, "", testCacheSize, testCacheTTL)
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
ss, err := safesearch.NewDefault(ctx, &safesearch.DefaultConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
|
ServicesConfig: conf,
|
||||||
|
CacheSize: testCacheSize,
|
||||||
|
CacheTTL: testCacheTTL,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
hosts := []string{
|
hosts := []string{
|
||||||
@@ -82,7 +88,7 @@ func TestDefault_CheckHost_yandex(t *testing.T) {
|
|||||||
for _, host := range hosts {
|
for _, host := range hosts {
|
||||||
// Check host for each domain.
|
// Check host for each domain.
|
||||||
var res filtering.Result
|
var res filtering.Result
|
||||||
res, err = ss.CheckHost(host, tc.qt)
|
res, err = ss.CheckHost(ctx, host, tc.qt)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.True(t, res.IsFiltered)
|
assert.True(t, res.IsFiltered)
|
||||||
@@ -103,7 +109,13 @@ func TestDefault_CheckHost_yandex(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefault_CheckHost_google(t *testing.T) {
|
func TestDefault_CheckHost_google(t *testing.T) {
|
||||||
ss, err := safesearch.NewDefault(testConf, "", testCacheSize, testCacheTTL)
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
ss, err := safesearch.NewDefault(ctx, &safesearch.DefaultConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
|
ServicesConfig: testConf,
|
||||||
|
CacheSize: testCacheSize,
|
||||||
|
CacheTTL: testCacheTTL,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Check host for each domain.
|
// Check host for each domain.
|
||||||
@@ -118,7 +130,7 @@ func TestDefault_CheckHost_google(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
t.Run(host, func(t *testing.T) {
|
t.Run(host, func(t *testing.T) {
|
||||||
var res filtering.Result
|
var res filtering.Result
|
||||||
res, err = ss.CheckHost(host, testQType)
|
res, err = ss.CheckHost(ctx, host, testQType)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.True(t, res.IsFiltered)
|
assert.True(t, res.IsFiltered)
|
||||||
@@ -149,13 +161,19 @@ func (r *testResolver) LookupIP(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefault_CheckHost_duckduckgoAAAA(t *testing.T) {
|
func TestDefault_CheckHost_duckduckgoAAAA(t *testing.T) {
|
||||||
ss, err := safesearch.NewDefault(testConf, "", testCacheSize, testCacheTTL)
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
ss, err := safesearch.NewDefault(ctx, &safesearch.DefaultConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
|
ServicesConfig: testConf,
|
||||||
|
CacheSize: testCacheSize,
|
||||||
|
CacheTTL: testCacheTTL,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// The DuckDuckGo safe-search addresses are resolved through CNAMEs, but
|
// The DuckDuckGo safe-search addresses are resolved through CNAMEs, but
|
||||||
// DuckDuckGo doesn't have a safe-search IPv6 address. The result should be
|
// 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.
|
// the same as the one for Yandex IPv6. That is, a NODATA response.
|
||||||
res, err := ss.CheckHost("www.duckduckgo.com", dns.TypeAAAA)
|
res, err := ss.CheckHost(ctx, "www.duckduckgo.com", dns.TypeAAAA)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.True(t, res.IsFiltered)
|
assert.True(t, res.IsFiltered)
|
||||||
@@ -166,32 +184,38 @@ func TestDefault_CheckHost_duckduckgoAAAA(t *testing.T) {
|
|||||||
|
|
||||||
func TestDefault_Update(t *testing.T) {
|
func TestDefault_Update(t *testing.T) {
|
||||||
conf := testConf
|
conf := testConf
|
||||||
ss, err := safesearch.NewDefault(conf, "", testCacheSize, testCacheTTL)
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
ss, err := safesearch.NewDefault(ctx, &safesearch.DefaultConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
|
ServicesConfig: conf,
|
||||||
|
CacheSize: testCacheSize,
|
||||||
|
CacheTTL: testCacheTTL,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
res, err := ss.CheckHost("www.yandex.com", testQType)
|
res, err := ss.CheckHost(ctx, "www.yandex.com", testQType)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.True(t, res.IsFiltered)
|
assert.True(t, res.IsFiltered)
|
||||||
|
|
||||||
err = ss.Update(filtering.SafeSearchConfig{
|
err = ss.Update(ctx, filtering.SafeSearchConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Google: false,
|
Google: false,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
res, err = ss.CheckHost("www.yandex.com", testQType)
|
res, err = ss.CheckHost(ctx, "www.yandex.com", testQType)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.False(t, res.IsFiltered)
|
assert.False(t, res.IsFiltered)
|
||||||
|
|
||||||
err = ss.Update(filtering.SafeSearchConfig{
|
err = ss.Update(ctx, filtering.SafeSearchConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
Google: true,
|
Google: true,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
res, err = ss.CheckHost("www.yandex.com", testQType)
|
res, err = ss.CheckHost(ctx, "www.yandex.com", testQType)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.False(t, res.IsFiltered)
|
assert.False(t, res.IsFiltered)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func (d *DNSFilter) handleSafeSearchSettings(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf := *req
|
conf := *req
|
||||||
err = d.safeSearch.Update(conf)
|
err = d.safeSearch.Update(r.Context(), conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "updating: %s", err)
|
aghhttp.Error(r, w, http.StatusBadRequest, "updating: %s", err)
|
||||||
|
|
||||||
|
|||||||
@@ -2450,7 +2450,7 @@ var blockedServices = []blockedService{{
|
|||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
ID: "telegram",
|
ID: "telegram",
|
||||||
Name: "Telegram",
|
Name: "Telegram (Web)",
|
||||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M46.137,6.552c-0.75-0.636-1.928-0.727-3.146-0.238l-0.002,0C41.708,6.828,6.728,21.832,5.304,22.445 c-0.259,0.09-2.521,0.934-2.288,2.814c0.208,1.695,2.026,2.397,2.248,2.478l8.893,3.045c0.59,1.964,2.765,9.21,3.246,10.758 c0.3,0.965,0.789,2.233,1.646,2.494c0.752,0.29,1.5,0.025,1.984-0.355l5.437-5.043l8.777,6.845l0.209,0.125 c0.596,0.264,1.167,0.396,1.712,0.396c0.421,0,0.825-0.079,1.211-0.237c1.315-0.54,1.841-1.793,1.896-1.935l6.556-34.077 C47.231,7.933,46.675,7.007,46.137,6.552z M22,32l-3,8l-3-10l23-17L22,32z\" /></svg>"),
|
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M46.137,6.552c-0.75-0.636-1.928-0.727-3.146-0.238l-0.002,0C41.708,6.828,6.728,21.832,5.304,22.445 c-0.259,0.09-2.521,0.934-2.288,2.814c0.208,1.695,2.026,2.397,2.248,2.478l8.893,3.045c0.59,1.964,2.765,9.21,3.246,10.758 c0.3,0.965,0.789,2.233,1.646,2.494c0.752,0.29,1.5,0.025,1.984-0.355l5.437-5.043l8.777,6.845l0.209,0.125 c0.596,0.264,1.167,0.396,1.712,0.396c0.421,0,0.825-0.079,1.211-0.237c1.315-0.54,1.841-1.793,1.896-1.935l6.556-34.077 C47.231,7.933,46.675,7.007,46.137,6.552z M22,32l-3,8l-3-10l23-17L22,32z\" /></svg>"),
|
||||||
Rules: []string{
|
Rules: []string{
|
||||||
"||comments.app^",
|
"||comments.app^",
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ func InitAuth(
|
|||||||
trustedProxies: trustedProxies,
|
trustedProxies: trustedProxies,
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
a.db, err = bbolt.Open(dbFilename, aghos.DefaultPermFile, nil)
|
a.db, err = bbolt.Open(dbFilename, aghos.DefaultPermFile, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("auth: open DB: %s: %s", dbFilename, err)
|
log.Error("auth: open DB: %s: %s", dbFilename, err)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package home
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -13,17 +14,23 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||||
"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/filtering/safesearch"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
"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"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/stringutil"
|
"github.com/AdguardTeam/golibs/stringutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 {
|
||||||
|
// baseLogger is used to create loggers with custom prefixes for safe search
|
||||||
|
// filter. It must not be nil.
|
||||||
|
baseLogger *slog.Logger
|
||||||
|
|
||||||
// storage stores information about persistent clients.
|
// storage stores information about persistent clients.
|
||||||
storage *client.Storage
|
storage *client.Storage
|
||||||
|
|
||||||
@@ -61,6 +68,8 @@ type BlockedClientChecker interface {
|
|||||||
// dhcpServer: optional
|
// dhcpServer: optional
|
||||||
// Note: this function must be called only once
|
// Note: this function must be called only once
|
||||||
func (clients *clientsContainer) Init(
|
func (clients *clientsContainer) Init(
|
||||||
|
ctx context.Context,
|
||||||
|
baseLogger *slog.Logger,
|
||||||
objects []*clientObject,
|
objects []*clientObject,
|
||||||
dhcpServer client.DHCP,
|
dhcpServer client.DHCP,
|
||||||
etcHosts *aghnet.HostsContainer,
|
etcHosts *aghnet.HostsContainer,
|
||||||
@@ -72,13 +81,14 @@ func (clients *clientsContainer) Init(
|
|||||||
return errors.Error("clients container already initialized")
|
return errors.Error("clients container already initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clients.baseLogger = baseLogger
|
||||||
clients.safeSearchCacheSize = filteringConf.SafeSearchCacheSize
|
clients.safeSearchCacheSize = filteringConf.SafeSearchCacheSize
|
||||||
clients.safeSearchCacheTTL = time.Minute * time.Duration(filteringConf.CacheTime)
|
clients.safeSearchCacheTTL = time.Minute * time.Duration(filteringConf.CacheTime)
|
||||||
|
|
||||||
confClients := make([]*client.Persistent, 0, len(objects))
|
confClients := make([]*client.Persistent, 0, len(objects))
|
||||||
for i, o := range objects {
|
for i, o := range objects {
|
||||||
var p *client.Persistent
|
var p *client.Persistent
|
||||||
p, err = o.toPersistent(clients.safeSearchCacheSize, clients.safeSearchCacheTTL)
|
p, err = o.toPersistent(ctx, baseLogger, clients.safeSearchCacheSize, clients.safeSearchCacheTTL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("init persistent client at index %d: %w", i, err)
|
return fmt.Errorf("init persistent client at index %d: %w", i, err)
|
||||||
}
|
}
|
||||||
@@ -92,12 +102,13 @@ func (clients *clientsContainer) Init(
|
|||||||
// TODO(e.burkov): The option should probably be returned, since hosts file
|
// TODO(e.burkov): The option should probably be returned, since hosts file
|
||||||
// currently used not only for clients' information enrichment, but also in
|
// currently used not only for clients' information enrichment, but also in
|
||||||
// the filtering module and upstream addresses resolution.
|
// the filtering module and upstream addresses resolution.
|
||||||
var hosts client.HostsContainer = etcHosts
|
var hosts client.HostsContainer
|
||||||
if !config.Clients.Sources.HostsFile {
|
if config.Clients.Sources.HostsFile && etcHosts != nil {
|
||||||
hosts = nil
|
hosts = etcHosts
|
||||||
}
|
}
|
||||||
|
|
||||||
clients.storage, err = client.NewStorage(&client.StorageConfig{
|
clients.storage, err = client.NewStorage(ctx, &client.StorageConfig{
|
||||||
|
Logger: baseLogger.With(slogutil.KeyPrefix, "client_storage"),
|
||||||
InitialClients: confClients,
|
InitialClients: confClients,
|
||||||
DHCP: dhcpServer,
|
DHCP: dhcpServer,
|
||||||
EtcHosts: hosts,
|
EtcHosts: hosts,
|
||||||
@@ -168,6 +179,8 @@ type clientObject struct {
|
|||||||
|
|
||||||
// toPersistent returns an initialized persistent client if there are no errors.
|
// toPersistent returns an initialized persistent client if there are no errors.
|
||||||
func (o *clientObject) toPersistent(
|
func (o *clientObject) toPersistent(
|
||||||
|
ctx context.Context,
|
||||||
|
baseLogger *slog.Logger,
|
||||||
safeSearchCacheSize uint,
|
safeSearchCacheSize uint,
|
||||||
safeSearchCacheTTL time.Duration,
|
safeSearchCacheTTL time.Duration,
|
||||||
) (cli *client.Persistent, err error) {
|
) (cli *client.Persistent, err error) {
|
||||||
@@ -203,14 +216,23 @@ func (o *clientObject) toPersistent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if o.SafeSearchConf.Enabled {
|
if o.SafeSearchConf.Enabled {
|
||||||
err = cli.SetSafeSearch(
|
logger := baseLogger.With(
|
||||||
o.SafeSearchConf,
|
slogutil.KeyPrefix, safesearch.LogPrefix,
|
||||||
safeSearchCacheSize,
|
safesearch.LogKeyClient, cli.Name,
|
||||||
safeSearchCacheTTL,
|
|
||||||
)
|
)
|
||||||
|
var ss *safesearch.Default
|
||||||
|
ss, err = safesearch.NewDefault(ctx, &safesearch.DefaultConfig{
|
||||||
|
Logger: logger,
|
||||||
|
ServicesConfig: o.SafeSearchConf,
|
||||||
|
ClientName: cli.Name,
|
||||||
|
CacheSize: safeSearchCacheSize,
|
||||||
|
CacheTTL: safeSearchCacheTTL,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("init safesearch %q: %w", cli.Name, err)
|
return nil, fmt.Errorf("init safesearch %q: %w", cli.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cli.SafeSearch = ss
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.BlockedServices == nil {
|
if o.BlockedServices == nil {
|
||||||
@@ -378,7 +400,7 @@ func (clients *clientsContainer) UpstreamConfigByID(
|
|||||||
upstreams,
|
upstreams,
|
||||||
&upstream.Options{
|
&upstream.Options{
|
||||||
Bootstrap: bootstrap,
|
Bootstrap: bootstrap,
|
||||||
Timeout: config.DNS.UpstreamTimeout.Duration,
|
Timeout: time.Duration(config.DNS.UpstreamTimeout),
|
||||||
HTTPVersions: dnsforward.UpstreamHTTPVersions(config.DNS.UseHTTP3Upstreams),
|
HTTPVersions: dnsforward.UpstreamHTTPVersions(config.DNS.UseHTTP3Upstreams),
|
||||||
PreferIPv6: config.DNS.BootstrapPreferIPv6,
|
PreferIPv6: config.DNS.BootstrapPreferIPv6,
|
||||||
},
|
},
|
||||||
@@ -396,6 +418,12 @@ func (clients *clientsContainer) UpstreamConfigByID(
|
|||||||
)
|
)
|
||||||
c.UpstreamConfig = conf
|
c.UpstreamConfig = conf
|
||||||
|
|
||||||
|
// TODO(s.chzhen): Pass context.
|
||||||
|
err = clients.storage.Update(context.TODO(), c.Name, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("setting upstream config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return conf, nil
|
return conf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,8 +432,13 @@ var _ client.AddressUpdater = (*clientsContainer)(nil)
|
|||||||
|
|
||||||
// UpdateAddress implements the [client.AddressUpdater] interface for
|
// UpdateAddress implements the [client.AddressUpdater] interface for
|
||||||
// *clientsContainer
|
// *clientsContainer
|
||||||
func (clients *clientsContainer) UpdateAddress(ip netip.Addr, host string, info *whois.Info) {
|
func (clients *clientsContainer) UpdateAddress(
|
||||||
clients.storage.UpdateAddress(ip, host, info)
|
ctx context.Context,
|
||||||
|
ip netip.Addr,
|
||||||
|
host string,
|
||||||
|
info *whois.Info,
|
||||||
|
) {
|
||||||
|
clients.storage.UpdateAddress(ctx, ip, host, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// close gracefully closes all the client-specific upstream configurations of
|
// close gracefully closes all the client-specific upstream configurations of
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -20,16 +22,28 @@ func newClientsContainer(t *testing.T) (c *clientsContainer) {
|
|||||||
testing: true,
|
testing: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, c.Init(nil, client.EmptyDHCP{}, nil, nil, &filtering.Config{}))
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
err := c.Init(
|
||||||
|
ctx,
|
||||||
|
slogutil.NewDiscardLogger(),
|
||||||
|
nil,
|
||||||
|
client.EmptyDHCP{},
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
&filtering.Config{},
|
||||||
|
)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientsCustomUpstream(t *testing.T) {
|
func TestClientsCustomUpstream(t *testing.T) {
|
||||||
clients := newClientsContainer(t)
|
clients := newClientsContainer(t)
|
||||||
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
|
||||||
// Add client with upstreams.
|
// Add client with upstreams.
|
||||||
err := clients.storage.Add(&client.Persistent{
|
err := clients.storage.Add(ctx, &client.Persistent{
|
||||||
Name: "client1",
|
Name: "client1",
|
||||||
UID: client.MustNewUID(),
|
UID: client.MustNewUID(),
|
||||||
IPs: []netip.Addr{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("1:2:3::4")},
|
IPs: []netip.Addr{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("1:2:3::4")},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -10,8 +11,10 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// clientJSON is a common structure used by several handlers to deal with
|
// clientJSON is a common structure used by several handlers to deal with
|
||||||
@@ -103,7 +106,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
clients.storage.UpdateDHCP()
|
clients.storage.UpdateDHCP(r.Context())
|
||||||
|
|
||||||
clients.storage.RangeRuntime(func(rc *client.Runtime) (cont bool) {
|
clients.storage.RangeRuntime(func(rc *client.Runtime) (cont bool) {
|
||||||
src, host := rc.Info()
|
src, host := rc.Info()
|
||||||
@@ -181,6 +184,7 @@ func initPrev(cj clientJSON, prev *client.Persistent) (c *client.Persistent, err
|
|||||||
// jsonToClient converts JSON object to persistent client object if there are no
|
// jsonToClient converts JSON object to persistent client object if there are no
|
||||||
// errors.
|
// errors.
|
||||||
func (clients *clientsContainer) jsonToClient(
|
func (clients *clientsContainer) jsonToClient(
|
||||||
|
ctx context.Context,
|
||||||
cj clientJSON,
|
cj clientJSON,
|
||||||
prev *client.Persistent,
|
prev *client.Persistent,
|
||||||
) (c *client.Persistent, err error) {
|
) (c *client.Persistent, err error) {
|
||||||
@@ -207,14 +211,23 @@ func (clients *clientsContainer) jsonToClient(
|
|||||||
c.UseOwnBlockedServices = !cj.UseGlobalBlockedServices
|
c.UseOwnBlockedServices = !cj.UseGlobalBlockedServices
|
||||||
|
|
||||||
if c.SafeSearchConf.Enabled {
|
if c.SafeSearchConf.Enabled {
|
||||||
err = c.SetSafeSearch(
|
logger := clients.baseLogger.With(
|
||||||
c.SafeSearchConf,
|
slogutil.KeyPrefix, safesearch.LogPrefix,
|
||||||
clients.safeSearchCacheSize,
|
safesearch.LogKeyClient, c.Name,
|
||||||
clients.safeSearchCacheTTL,
|
|
||||||
)
|
)
|
||||||
|
var ss *safesearch.Default
|
||||||
|
ss, err = safesearch.NewDefault(ctx, &safesearch.DefaultConfig{
|
||||||
|
Logger: logger,
|
||||||
|
ServicesConfig: c.SafeSearchConf,
|
||||||
|
ClientName: c.Name,
|
||||||
|
CacheSize: clients.safeSearchCacheSize,
|
||||||
|
CacheTTL: clients.safeSearchCacheTTL,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("creating safesearch for client %q: %w", c.Name, err)
|
return nil, fmt.Errorf("creating safesearch for client %q: %w", c.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.SafeSearch = ss
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
@@ -321,14 +334,14 @@ func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := clients.jsonToClient(cj, nil)
|
c, err := clients.jsonToClient(r.Context(), cj, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = clients.storage.Add(c)
|
err = clients.storage.Add(r.Context(), c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||||
|
|
||||||
@@ -356,7 +369,7 @@ func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !clients.storage.RemoveByName(cj.Name) {
|
if !clients.storage.RemoveByName(r.Context(), cj.Name) {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "Client not found")
|
aghhttp.Error(r, w, http.StatusBadRequest, "Client not found")
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -391,14 +404,14 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := clients.jsonToClient(dj.Data, nil)
|
c, err := clients.jsonToClient(r.Context(), dj.Data, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = clients.storage.Update(dj.Name, c)
|
err = clients.storage.Update(r.Context(), dj.Name, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||||
|
|
||||||
@@ -411,6 +424,8 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleFindClient is the handler for GET /control/clients/find HTTP API.
|
// handleFindClient is the handler for GET /control/clients/find HTTP API.
|
||||||
|
//
|
||||||
|
// Deprecated: Remove it when migration to the new API is over.
|
||||||
func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http.Request) {
|
func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http.Request) {
|
||||||
q := r.URL.Query()
|
q := r.URL.Query()
|
||||||
data := []map[string]*clientJSON{}
|
data := []map[string]*clientJSON{}
|
||||||
@@ -420,19 +435,58 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, _ := netip.ParseAddr(idStr)
|
|
||||||
c, ok := clients.storage.Find(idStr)
|
|
||||||
var cj *clientJSON
|
|
||||||
if !ok {
|
|
||||||
cj = clients.findRuntime(ip, idStr)
|
|
||||||
} else {
|
|
||||||
cj = clientToJSON(c)
|
|
||||||
disallowed, rule := clients.clientChecker.IsBlockedClient(ip, idStr)
|
|
||||||
cj.Disallowed, cj.DisallowedRule = &disallowed, &rule
|
|
||||||
}
|
|
||||||
|
|
||||||
data = append(data, map[string]*clientJSON{
|
data = append(data, map[string]*clientJSON{
|
||||||
idStr: cj,
|
idStr: clients.findClient(idStr),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
aghhttp.WriteJSONResponseOK(w, r, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// findClient returns available information about a client by idStr from the
|
||||||
|
// client's storage or access settings. cj is guaranteed to be non-nil.
|
||||||
|
func (clients *clientsContainer) findClient(idStr string) (cj *clientJSON) {
|
||||||
|
ip, _ := netip.ParseAddr(idStr)
|
||||||
|
c, ok := clients.storage.Find(idStr)
|
||||||
|
if !ok {
|
||||||
|
return clients.findRuntime(ip, idStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
cj = clientToJSON(c)
|
||||||
|
disallowed, rule := clients.clientChecker.IsBlockedClient(ip, idStr)
|
||||||
|
cj.Disallowed, cj.DisallowedRule = &disallowed, &rule
|
||||||
|
|
||||||
|
return cj
|
||||||
|
}
|
||||||
|
|
||||||
|
// searchQueryJSON is a request to the POST /control/clients/search HTTP API.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Add UIDs.
|
||||||
|
type searchQueryJSON struct {
|
||||||
|
Clients []searchClientJSON `json:"clients"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// searchClientJSON is a part of [searchQueryJSON] that contains a string
|
||||||
|
// representation of the client's IP address, CIDR, MAC address, or ClientID.
|
||||||
|
type searchClientJSON struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSearchClient is the handler for the POST /control/clients/search HTTP API.
|
||||||
|
func (clients *clientsContainer) handleSearchClient(w http.ResponseWriter, r *http.Request) {
|
||||||
|
q := searchQueryJSON{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&q)
|
||||||
|
if err != nil {
|
||||||
|
aghhttp.Error(r, w, http.StatusBadRequest, "failed to process request body: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []map[string]*clientJSON{}
|
||||||
|
for _, c := range q.Clients {
|
||||||
|
idStr := c.ID
|
||||||
|
data = append(data, map[string]*clientJSON{
|
||||||
|
idStr: clients.findClient(idStr),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,5 +534,8 @@ func (clients *clientsContainer) registerWebHandlers() {
|
|||||||
httpRegister(http.MethodPost, "/control/clients/add", clients.handleAddClient)
|
httpRegister(http.MethodPost, "/control/clients/add", clients.handleAddClient)
|
||||||
httpRegister(http.MethodPost, "/control/clients/delete", clients.handleDelClient)
|
httpRegister(http.MethodPost, "/control/clients/delete", clients.handleDelClient)
|
||||||
httpRegister(http.MethodPost, "/control/clients/update", clients.handleUpdateClient)
|
httpRegister(http.MethodPost, "/control/clients/update", clients.handleUpdateClient)
|
||||||
|
httpRegister(http.MethodPost, "/control/clients/search", clients.handleSearchClient)
|
||||||
|
|
||||||
|
// Deprecated handler.
|
||||||
httpRegister(http.MethodGet, "/control/clients/find", clients.handleFindClient)
|
httpRegister(http.MethodGet, "/control/clients/find", clients.handleFindClient)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,20 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||||
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// testTimeout is the common timeout for tests and contexts.
|
||||||
|
const testTimeout = 1 * time.Second
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testClientIP1 = "1.1.1.1"
|
testClientIP1 = "1.1.1.1"
|
||||||
testClientIP2 = "2.2.2.2"
|
testClientIP2 = "2.2.2.2"
|
||||||
@@ -103,9 +109,10 @@ func assertPersistentClients(tb testing.TB, clients *clientsContainer, want []*c
|
|||||||
require.NoError(tb, err)
|
require.NoError(tb, err)
|
||||||
|
|
||||||
var got []*client.Persistent
|
var got []*client.Persistent
|
||||||
|
ctx := testutil.ContextWithTimeout(tb, testTimeout)
|
||||||
for _, cj := range clientList.Clients {
|
for _, cj := range clientList.Clients {
|
||||||
var c *client.Persistent
|
var c *client.Persistent
|
||||||
c, err = clients.jsonToClient(*cj, nil)
|
c, err = clients.jsonToClient(ctx, *cj, nil)
|
||||||
require.NoError(tb, err)
|
require.NoError(tb, err)
|
||||||
|
|
||||||
got = append(got, c)
|
got = append(got, c)
|
||||||
@@ -125,10 +132,11 @@ func assertPersistentClientsData(
|
|||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
|
||||||
var got []*client.Persistent
|
var got []*client.Persistent
|
||||||
|
ctx := testutil.ContextWithTimeout(tb, testTimeout)
|
||||||
for _, cm := range data {
|
for _, cm := range data {
|
||||||
for _, cj := range cm {
|
for _, cj := range cm {
|
||||||
var c *client.Persistent
|
var c *client.Persistent
|
||||||
c, err := clients.jsonToClient(*cj, nil)
|
c, err := clients.jsonToClient(ctx, *cj, nil)
|
||||||
require.NoError(tb, err)
|
require.NoError(tb, err)
|
||||||
|
|
||||||
got = append(got, c)
|
got = append(got, c)
|
||||||
@@ -196,13 +204,14 @@ func TestClientsContainer_HandleAddClient(t *testing.T) {
|
|||||||
|
|
||||||
func TestClientsContainer_HandleDelClient(t *testing.T) {
|
func TestClientsContainer_HandleDelClient(t *testing.T) {
|
||||||
clients := newClientsContainer(t)
|
clients := newClientsContainer(t)
|
||||||
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
|
||||||
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
|
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
|
||||||
err := clients.storage.Add(clientOne)
|
err := clients.storage.Add(ctx, clientOne)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
||||||
err = clients.storage.Add(clientTwo)
|
err = clients.storage.Add(ctx, clientTwo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assertPersistentClients(t, clients, []*client.Persistent{clientOne, clientTwo})
|
assertPersistentClients(t, clients, []*client.Persistent{clientOne, clientTwo})
|
||||||
@@ -258,9 +267,10 @@ func TestClientsContainer_HandleDelClient(t *testing.T) {
|
|||||||
|
|
||||||
func TestClientsContainer_HandleUpdateClient(t *testing.T) {
|
func TestClientsContainer_HandleUpdateClient(t *testing.T) {
|
||||||
clients := newClientsContainer(t)
|
clients := newClientsContainer(t)
|
||||||
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
|
||||||
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
|
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
|
||||||
err := clients.storage.Add(clientOne)
|
err := clients.storage.Add(ctx, clientOne)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assertPersistentClients(t, clients, []*client.Persistent{clientOne})
|
assertPersistentClients(t, clients, []*client.Persistent{clientOne})
|
||||||
@@ -341,12 +351,14 @@ func TestClientsContainer_HandleFindClient(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
|
||||||
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
|
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
|
||||||
err := clients.storage.Add(clientOne)
|
err := clients.storage.Add(ctx, clientOne)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
||||||
err = clients.storage.Add(clientTwo)
|
err = clients.storage.Add(ctx, clientTwo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assertPersistentClients(t, clients, []*client.Persistent{clientOne, clientTwo})
|
assertPersistentClients(t, clients, []*client.Persistent{clientOne, clientTwo})
|
||||||
@@ -397,3 +409,145 @@ func TestClientsContainer_HandleFindClient(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientsContainer_HandleSearchClient(t *testing.T) {
|
||||||
|
var (
|
||||||
|
runtimeCli = "runtime_client1"
|
||||||
|
|
||||||
|
runtimeCliIP = "3.3.3.3"
|
||||||
|
blockedCliIP = "4.4.4.4"
|
||||||
|
nonExistentCliIP = "5.5.5.5"
|
||||||
|
|
||||||
|
allowed = false
|
||||||
|
dissallowed = true
|
||||||
|
|
||||||
|
emptyRule = ""
|
||||||
|
disallowedRule = "disallowed_rule"
|
||||||
|
)
|
||||||
|
|
||||||
|
clients := newClientsContainer(t)
|
||||||
|
clients.clientChecker = &testBlockedClientChecker{
|
||||||
|
onIsBlockedClient: func(ip netip.Addr, _ string) (ok bool, rule string) {
|
||||||
|
if ip == netip.MustParseAddr(blockedCliIP) {
|
||||||
|
return true, disallowedRule
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, emptyRule
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
|
||||||
|
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
|
||||||
|
err := clients.storage.Add(ctx, clientOne)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
|
||||||
|
err = clients.storage.Add(ctx, clientTwo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assertPersistentClients(t, clients, []*client.Persistent{clientOne, clientTwo})
|
||||||
|
|
||||||
|
clients.UpdateAddress(ctx, netip.MustParseAddr(runtimeCliIP), runtimeCli, nil)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
query *searchQueryJSON
|
||||||
|
wantPersistent []*client.Persistent
|
||||||
|
wantRuntime *clientJSON
|
||||||
|
}{{
|
||||||
|
name: "single",
|
||||||
|
query: &searchQueryJSON{
|
||||||
|
Clients: []searchClientJSON{{
|
||||||
|
ID: testClientIP1,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
wantPersistent: []*client.Persistent{clientOne},
|
||||||
|
}, {
|
||||||
|
name: "multiple",
|
||||||
|
query: &searchQueryJSON{
|
||||||
|
Clients: []searchClientJSON{{
|
||||||
|
ID: testClientIP1,
|
||||||
|
}, {
|
||||||
|
ID: testClientIP2,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
wantPersistent: []*client.Persistent{clientOne, clientTwo},
|
||||||
|
}, {
|
||||||
|
name: "runtime",
|
||||||
|
query: &searchQueryJSON{
|
||||||
|
Clients: []searchClientJSON{{
|
||||||
|
ID: runtimeCliIP,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
wantRuntime: &clientJSON{
|
||||||
|
Name: runtimeCli,
|
||||||
|
IDs: []string{runtimeCliIP},
|
||||||
|
Disallowed: &allowed,
|
||||||
|
DisallowedRule: &emptyRule,
|
||||||
|
WHOIS: &whois.Info{},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "blocked_access",
|
||||||
|
query: &searchQueryJSON{
|
||||||
|
Clients: []searchClientJSON{{
|
||||||
|
ID: blockedCliIP,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
wantRuntime: &clientJSON{
|
||||||
|
IDs: []string{blockedCliIP},
|
||||||
|
Disallowed: &dissallowed,
|
||||||
|
DisallowedRule: &disallowedRule,
|
||||||
|
WHOIS: &whois.Info{},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "non_existing_client",
|
||||||
|
query: &searchQueryJSON{
|
||||||
|
Clients: []searchClientJSON{{
|
||||||
|
ID: nonExistentCliIP,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
wantRuntime: &clientJSON{
|
||||||
|
IDs: []string{nonExistentCliIP},
|
||||||
|
Disallowed: &allowed,
|
||||||
|
DisallowedRule: &emptyRule,
|
||||||
|
WHOIS: &whois.Info{},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var body []byte
|
||||||
|
body, err = json.Marshal(tc.query)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var r *http.Request
|
||||||
|
r, err = http.NewRequest(http.MethodPost, "", bytes.NewReader(body))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
clients.handleSearchClient(rw, r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusOK, rw.Code)
|
||||||
|
|
||||||
|
body, err = io.ReadAll(rw.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
clientData := []map[string]*clientJSON{}
|
||||||
|
err = json.Unmarshal(body, &clientData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if tc.wantPersistent != nil {
|
||||||
|
assertPersistentClientsData(t, clients, clientData, tc.wantPersistent)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Len(t, clientData, 1)
|
||||||
|
require.Len(t, clientData[0], 1)
|
||||||
|
|
||||||
|
rc := clientData[0][tc.wantRuntime.IDs[0]]
|
||||||
|
assert.Equal(t, tc.wantRuntime, rc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -162,6 +162,12 @@ type configuration struct {
|
|||||||
// SchemaVersion is the version of the configuration schema. See
|
// SchemaVersion is the version of the configuration schema. See
|
||||||
// [configmigrate.LastSchemaVersion].
|
// [configmigrate.LastSchemaVersion].
|
||||||
SchemaVersion uint `yaml:"schema_version"`
|
SchemaVersion uint `yaml:"schema_version"`
|
||||||
|
|
||||||
|
// UnsafeUseCustomUpdateIndexURL is the URL to the custom update index.
|
||||||
|
//
|
||||||
|
// NOTE: It's only exists for testing purposes and should not be used in
|
||||||
|
// release.
|
||||||
|
UnsafeUseCustomUpdateIndexURL bool `yaml:"unsafe_use_custom_update_index_url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// httpConfig is a block with HTTP configuration params.
|
// httpConfig is a block with HTTP configuration params.
|
||||||
@@ -333,7 +339,7 @@ var config = &configuration{
|
|||||||
AuthBlockMin: 15,
|
AuthBlockMin: 15,
|
||||||
HTTPConfig: httpConfig{
|
HTTPConfig: httpConfig{
|
||||||
Address: netip.AddrPortFrom(netip.IPv4Unspecified(), 3000),
|
Address: netip.AddrPortFrom(netip.IPv4Unspecified(), 3000),
|
||||||
SessionTTL: timeutil.Duration{Duration: 30 * timeutil.Day},
|
SessionTTL: timeutil.Duration(30 * timeutil.Day),
|
||||||
Pprof: &httpPprofConfig{
|
Pprof: &httpPprofConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
Port: 6060,
|
Port: 6060,
|
||||||
@@ -349,9 +355,7 @@ var config = &configuration{
|
|||||||
RefuseAny: true,
|
RefuseAny: true,
|
||||||
UpstreamMode: dnsforward.UpstreamModeLoadBalance,
|
UpstreamMode: dnsforward.UpstreamModeLoadBalance,
|
||||||
HandleDDR: true,
|
HandleDDR: true,
|
||||||
FastestTimeout: timeutil.Duration{
|
FastestTimeout: timeutil.Duration(fastip.DefaultPingWaitTimeout),
|
||||||
Duration: fastip.DefaultPingWaitTimeout,
|
|
||||||
},
|
|
||||||
|
|
||||||
TrustedProxies: []netutil.Prefix{{
|
TrustedProxies: []netutil.Prefix{{
|
||||||
Prefix: netip.MustParsePrefix("127.0.0.0/8"),
|
Prefix: netip.MustParsePrefix("127.0.0.0/8"),
|
||||||
@@ -372,7 +376,7 @@ var config = &configuration{
|
|||||||
// was later increased to 300 due to https://github.com/AdguardTeam/AdGuardHome/issues/2257
|
// was later increased to 300 due to https://github.com/AdguardTeam/AdGuardHome/issues/2257
|
||||||
MaxGoroutines: 300,
|
MaxGoroutines: 300,
|
||||||
},
|
},
|
||||||
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
|
UpstreamTimeout: timeutil.Duration(dnsforward.DefaultTimeout),
|
||||||
UsePrivateRDNS: true,
|
UsePrivateRDNS: true,
|
||||||
ServePlainDNS: true,
|
ServePlainDNS: true,
|
||||||
HostsFileEnabled: true,
|
HostsFileEnabled: true,
|
||||||
@@ -385,13 +389,13 @@ var config = &configuration{
|
|||||||
QueryLog: queryLogConfig{
|
QueryLog: queryLogConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
FileEnabled: true,
|
FileEnabled: true,
|
||||||
Interval: timeutil.Duration{Duration: 90 * timeutil.Day},
|
Interval: timeutil.Duration(90 * timeutil.Day),
|
||||||
MemSize: 1000,
|
MemSize: 1000,
|
||||||
Ignored: []string{},
|
Ignored: []string{},
|
||||||
},
|
},
|
||||||
Stats: statsConfig{
|
Stats: statsConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Interval: timeutil.Duration{Duration: 1 * timeutil.Day},
|
Interval: timeutil.Duration(1 * timeutil.Day),
|
||||||
Ignored: []string{},
|
Ignored: []string{},
|
||||||
},
|
},
|
||||||
// NOTE: Keep these parameters in sync with the one put into
|
// NOTE: Keep these parameters in sync with the one put into
|
||||||
@@ -559,8 +563,8 @@ func parseConfig() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.DNS.UpstreamTimeout.Duration == 0 {
|
if config.DNS.UpstreamTimeout == 0 {
|
||||||
config.DNS.UpstreamTimeout = timeutil.Duration{Duration: dnsforward.DefaultTimeout}
|
config.DNS.UpstreamTimeout = timeutil.Duration(dnsforward.DefaultTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not wrap the error because it's informative enough as is.
|
// Do not wrap the error because it's informative enough as is.
|
||||||
@@ -653,7 +657,7 @@ func (c *configuration) write() (err error) {
|
|||||||
if Context.stats != nil {
|
if Context.stats != nil {
|
||||||
statsConf := stats.Config{}
|
statsConf := stats.Config{}
|
||||||
Context.stats.WriteDiskConfig(&statsConf)
|
Context.stats.WriteDiskConfig(&statsConf)
|
||||||
config.Stats.Interval = timeutil.Duration{Duration: statsConf.Limit}
|
config.Stats.Interval = timeutil.Duration(statsConf.Limit)
|
||||||
config.Stats.Enabled = statsConf.Enabled
|
config.Stats.Enabled = statsConf.Enabled
|
||||||
config.Stats.Ignored = statsConf.Ignored.Values()
|
config.Stats.Ignored = statsConf.Ignored.Values()
|
||||||
}
|
}
|
||||||
@@ -664,7 +668,7 @@ func (c *configuration) write() (err error) {
|
|||||||
config.DNS.AnonymizeClientIP = dc.AnonymizeClientIP
|
config.DNS.AnonymizeClientIP = dc.AnonymizeClientIP
|
||||||
config.QueryLog.Enabled = dc.Enabled
|
config.QueryLog.Enabled = dc.Enabled
|
||||||
config.QueryLog.FileEnabled = dc.FileEnabled
|
config.QueryLog.FileEnabled = dc.FileEnabled
|
||||||
config.QueryLog.Interval = timeutil.Duration{Duration: dc.RotationIvl}
|
config.QueryLog.Interval = timeutil.Duration(dc.RotationIvl)
|
||||||
config.QueryLog.MemSize = dc.MemSize
|
config.QueryLog.MemSize = dc.MemSize
|
||||||
config.QueryLog.Ignored = dc.Ignored.Values()
|
config.QueryLog.Ignored = dc.Ignored.Values()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/AdguardTeam/golibs/httphdr"
|
"github.com/AdguardTeam/golibs/httphdr"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||||
"github.com/NYTimes/gziphandler"
|
"github.com/NYTimes/gziphandler"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -376,7 +377,7 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (proceed bool)
|
|||||||
//
|
//
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin.
|
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin.
|
||||||
originURL := &url.URL{
|
originURL := &url.URL{
|
||||||
Scheme: aghhttp.SchemeHTTP,
|
Scheme: urlutil.SchemeHTTP,
|
||||||
Host: r.Host,
|
Host: r.Host,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +396,7 @@ func httpsURL(u *url.URL, host string, portHTTPS uint16) (redirectURL *url.URL)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &url.URL{
|
return &url.URL{
|
||||||
Scheme: aghhttp.SchemeHTTPS,
|
Scheme: urlutil.SchemeHTTPS,
|
||||||
Host: hostPort,
|
Host: hostPort,
|
||||||
Path: u.Path,
|
Path: u.Path,
|
||||||
RawQuery: u.RawQuery,
|
RawQuery: u.RawQuery,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -19,7 +20,7 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/quic-go/quic-go/http3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -124,6 +125,8 @@ func (req *checkConfReq) validateWeb(tcpPorts aghalg.UniqChecker[tcpPort]) (err
|
|||||||
// be set. canAutofix is true if the port can be unbound by AdGuard Home
|
// be set. canAutofix is true if the port can be unbound by AdGuard Home
|
||||||
// automatically.
|
// automatically.
|
||||||
func (req *checkConfReq) validateDNS(
|
func (req *checkConfReq) validateDNS(
|
||||||
|
ctx context.Context,
|
||||||
|
l *slog.Logger,
|
||||||
tcpPorts aghalg.UniqChecker[tcpPort],
|
tcpPorts aghalg.UniqChecker[tcpPort],
|
||||||
) (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") }()
|
||||||
@@ -154,10 +157,10 @@ func (req *checkConfReq) validateDNS(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to fix automatically.
|
// Try to fix automatically.
|
||||||
canAutofix = checkDNSStubListener()
|
canAutofix = checkDNSStubListener(ctx, l)
|
||||||
if canAutofix && req.DNS.Autofix {
|
if canAutofix && req.DNS.Autofix {
|
||||||
if derr := disableDNSStubListener(); derr != nil {
|
if derr := disableDNSStubListener(ctx, l); derr != nil {
|
||||||
log.Error("disabling DNSStubListener: %s", err)
|
l.ErrorContext(ctx, "disabling DNSStubListener", slogutil.KeyError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = aghnet.CheckPort("udp", netip.AddrPortFrom(req.DNS.IP, port))
|
err = aghnet.CheckPort("udp", netip.AddrPortFrom(req.DNS.IP, port))
|
||||||
@@ -184,7 +187,7 @@ func (web *webAPI) handleInstallCheckConfig(w http.ResponseWriter, r *http.Reque
|
|||||||
resp.Web.Status = err.Error()
|
resp.Web.Status = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.DNS.CanAutofix, err = req.validateDNS(tcpPorts); err != nil {
|
if resp.DNS.CanAutofix, err = req.validateDNS(r.Context(), web.logger, tcpPorts); err != nil {
|
||||||
resp.DNS.Status = err.Error()
|
resp.DNS.Status = err.Error()
|
||||||
} else if !req.DNS.IP.IsUnspecified() {
|
} else if !req.DNS.IP.IsUnspecified() {
|
||||||
resp.StaticIP = handleStaticIP(req.DNS.IP, req.SetStaticIP)
|
resp.StaticIP = handleStaticIP(req.DNS.IP, req.SetStaticIP)
|
||||||
@@ -233,27 +236,39 @@ func handleStaticIP(ip netip.Addr, set bool) staticIPJSON {
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if DNSStubListener is active
|
// checkDNSStubListener returns true if DNSStubListener is active.
|
||||||
func checkDNSStubListener() bool {
|
func checkDNSStubListener(ctx context.Context, l *slog.Logger) (ok bool) {
|
||||||
if runtime.GOOS != "linux" {
|
if runtime.GOOS != "linux" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("systemctl", "is-enabled", "systemd-resolved")
|
cmd := exec.Command("systemctl", "is-enabled", "systemd-resolved")
|
||||||
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
l.DebugContext(ctx, "executing", "cmd", cmd.Path, "args", cmd.Args)
|
||||||
_, err := cmd.Output()
|
_, err := cmd.Output()
|
||||||
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
||||||
log.Info("command %s has failed: %v code:%d",
|
l.InfoContext(
|
||||||
cmd.Path, err, cmd.ProcessState.ExitCode())
|
ctx,
|
||||||
|
"execution failed",
|
||||||
|
"cmd", cmd.Path,
|
||||||
|
"code", cmd.ProcessState.ExitCode(),
|
||||||
|
slogutil.KeyError, err,
|
||||||
|
)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = exec.Command("grep", "-E", "#?DNSStubListener=yes", "/etc/systemd/resolved.conf")
|
cmd = exec.Command("grep", "-E", "#?DNSStubListener=yes", "/etc/systemd/resolved.conf")
|
||||||
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
l.DebugContext(ctx, "executing", "cmd", cmd.Path, "args", cmd.Args)
|
||||||
_, err = cmd.Output()
|
_, err = cmd.Output()
|
||||||
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
||||||
log.Info("command %s has failed: %v code:%d",
|
l.InfoContext(
|
||||||
cmd.Path, err, cmd.ProcessState.ExitCode())
|
ctx,
|
||||||
|
"execution failed",
|
||||||
|
"cmd", cmd.Path,
|
||||||
|
"code", cmd.ProcessState.ExitCode(),
|
||||||
|
slogutil.KeyError, err,
|
||||||
|
)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,8 +284,9 @@ DNSStubListener=no
|
|||||||
)
|
)
|
||||||
const resolvConfPath = "/etc/resolv.conf"
|
const resolvConfPath = "/etc/resolv.conf"
|
||||||
|
|
||||||
// Deactivate DNSStubListener
|
// disableDNSStubListener deactivates DNSStubListerner and returns an error, if
|
||||||
func disableDNSStubListener() (err error) {
|
// any.
|
||||||
|
func disableDNSStubListener(ctx context.Context, l *slog.Logger) (err error) {
|
||||||
dir := filepath.Dir(resolvedConfPath)
|
dir := filepath.Dir(resolvedConfPath)
|
||||||
err = os.MkdirAll(dir, 0o755)
|
err = os.MkdirAll(dir, 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -290,7 +306,7 @@ func disableDNSStubListener() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("systemctl", "reload-or-restart", "systemd-resolved")
|
cmd := exec.Command("systemctl", "reload-or-restart", "systemd-resolved")
|
||||||
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
l.DebugContext(ctx, "executing", "cmd", cmd.Path, "args", cmd.Args)
|
||||||
_, err = cmd.Output()
|
_, err = cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -327,9 +343,9 @@ func copyInstallSettings(dst, src *configuration) {
|
|||||||
// shutdownTimeout is the timeout for shutting HTTP server down operation.
|
// shutdownTimeout is the timeout for shutting HTTP server down operation.
|
||||||
const shutdownTimeout = 5 * time.Second
|
const shutdownTimeout = 5 * time.Second
|
||||||
|
|
||||||
// shutdownSrv shuts srv down and prints error messages to the log.
|
// shutdownSrv shuts down srv and logs the error, if any. l must not be nil.
|
||||||
func shutdownSrv(ctx context.Context, srv *http.Server) {
|
func shutdownSrv(ctx context.Context, l *slog.Logger, srv *http.Server) {
|
||||||
defer log.OnPanic("")
|
defer slogutil.RecoverAndLog(ctx, l)
|
||||||
|
|
||||||
if srv == nil {
|
if srv == nil {
|
||||||
return
|
return
|
||||||
@@ -340,19 +356,19 @@ func shutdownSrv(ctx context.Context, srv *http.Server) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const msgFmt = "shutting down http server %q: %s"
|
lvl := slog.LevelDebug
|
||||||
if errors.Is(err, context.Canceled) {
|
if !errors.Is(err, context.Canceled) {
|
||||||
log.Debug(msgFmt, srv.Addr, err)
|
lvl = slog.LevelError
|
||||||
} else {
|
|
||||||
log.Error(msgFmt, srv.Addr, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l.Log(ctx, lvl, "shutting down http server", "addr", srv.Addr, slogutil.KeyError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// shutdownSrv3 shuts srv down and prints error messages to the log.
|
// shutdownSrv3 shuts down srv and logs the error, if any. l must not be nil.
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): Think of a good way to merge with [shutdownSrv].
|
// TODO(a.garipov): Think of a good way to merge with [shutdownSrv].
|
||||||
func shutdownSrv3(srv *http3.Server) {
|
func shutdownSrv3(ctx context.Context, l *slog.Logger, srv *http3.Server) {
|
||||||
defer log.OnPanic("")
|
defer slogutil.RecoverAndLog(ctx, l)
|
||||||
|
|
||||||
if srv == nil {
|
if srv == nil {
|
||||||
return
|
return
|
||||||
@@ -363,12 +379,12 @@ func shutdownSrv3(srv *http3.Server) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const msgFmt = "shutting down http/3 server %q: %s"
|
lvl := slog.LevelDebug
|
||||||
if errors.Is(err, context.Canceled) {
|
if !errors.Is(err, context.Canceled) {
|
||||||
log.Debug(msgFmt, srv.Addr, err)
|
lvl = slog.LevelError
|
||||||
} else {
|
|
||||||
log.Error(msgFmt, srv.Addr, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l.Log(ctx, lvl, "shutting down http/3 server", "addr", srv.Addr, slogutil.KeyError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordMinRunes is the minimum length of user's password in runes.
|
// PasswordMinRunes is the minimum length of user's password in runes.
|
||||||
@@ -436,7 +452,7 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
|
|||||||
// moment we'll allow setting up TLS in the initial configuration or the
|
// moment we'll allow setting up TLS in the initial configuration or the
|
||||||
// configuration itself will use HTTPS protocol, because the underlying
|
// configuration itself will use HTTPS protocol, because the underlying
|
||||||
// functions potentially restart the HTTPS server.
|
// functions potentially restart the HTTPS server.
|
||||||
err = startMods(web.logger)
|
err = startMods(web.baseLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Context.firstRun = true
|
Context.firstRun = true
|
||||||
copyInstallSettings(config, curConfig)
|
copyInstallSettings(config, curConfig)
|
||||||
@@ -472,12 +488,11 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
|
|||||||
// and with its own context, because it waits until all requests are handled
|
// and with its own context, because it waits until all requests are handled
|
||||||
// and will be blocked by it's own caller.
|
// and will be blocked by it's own caller.
|
||||||
go func(timeout time.Duration) {
|
go func(timeout time.Duration) {
|
||||||
defer log.OnPanic("web")
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer slogutil.RecoverAndLog(ctx, web.logger)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
shutdownSrv(ctx, web.httpServer)
|
shutdownSrv(ctx, web.logger, web.httpServer)
|
||||||
}(shutdownTimeout)
|
}(shutdownTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -16,7 +17,8 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
|
"github.com/AdguardTeam/golibs/osutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// temporaryError is the interface for temporary errors from the Go standard
|
// temporaryError is the interface for temporary errors from the Go standard
|
||||||
@@ -52,7 +54,7 @@ func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = web.requestVersionInfo(resp, req.Recheck)
|
err = web.requestVersionInfo(r.Context(), resp, req.Recheck)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap the error, because it's informative enough as is.
|
// Don't wrap the error, because it's informative enough as is.
|
||||||
aghhttp.Error(r, w, http.StatusBadGateway, "%s", err)
|
aghhttp.Error(r, w, http.StatusBadGateway, "%s", err)
|
||||||
@@ -73,32 +75,39 @@ func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// requestVersionInfo sets the VersionInfo field of resp if it can reach the
|
// requestVersionInfo sets the VersionInfo field of resp if it can reach the
|
||||||
// update server.
|
// update server.
|
||||||
func (web *webAPI) requestVersionInfo(resp *versionResponse, recheck bool) (err error) {
|
func (web *webAPI) requestVersionInfo(
|
||||||
|
ctx context.Context,
|
||||||
|
resp *versionResponse,
|
||||||
|
recheck bool,
|
||||||
|
) (err error) {
|
||||||
updater := web.conf.updater
|
updater := web.conf.updater
|
||||||
for i := 0; i != 3; i++ {
|
for range 3 {
|
||||||
resp.VersionInfo, err = updater.VersionInfo(recheck)
|
resp.VersionInfo, err = updater.VersionInfo(recheck)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
var terr temporaryError
|
return nil
|
||||||
if errors.As(err, &terr) && terr.Temporary() {
|
}
|
||||||
// Temporary network error. This case may happen while we're
|
|
||||||
// restarting our DNS server. Log and sleep for some time.
|
|
||||||
//
|
|
||||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/934.
|
|
||||||
d := time.Duration(i) * time.Second
|
|
||||||
log.Info("update: temp net error: %q; sleeping for %s and retrying", err, d)
|
|
||||||
time.Sleep(d)
|
|
||||||
|
|
||||||
continue
|
var terr temporaryError
|
||||||
}
|
if errors.As(err, &terr) && terr.Temporary() {
|
||||||
|
// Temporary network error. This case may happen while we're
|
||||||
|
// restarting our DNS server. Log and sleep for some time.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/934.
|
||||||
|
const sleepTime = 2 * time.Second
|
||||||
|
|
||||||
|
err = fmt.Errorf("temp net error: %w; sleeping for %s and retrying", err, sleepTime)
|
||||||
|
web.logger.InfoContext(ctx, "updating version info", slogutil.KeyError, err)
|
||||||
|
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
vcu := updater.VersionCheckURL()
|
return fmt.Errorf("getting version info: %w", err)
|
||||||
|
|
||||||
return fmt.Errorf("getting version info from %s: %w", vcu, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -139,7 +148,7 @@ func (web *webAPI) handleUpdate(w http.ResponseWriter, r *http.Request) {
|
|||||||
// The background context is used because the underlying functions wrap it
|
// The background context is used because the underlying functions wrap it
|
||||||
// with timeout and shut down the server, which handles current request. It
|
// with timeout and shut down the server, which handles current request. It
|
||||||
// also should be done in a separate goroutine for the same reason.
|
// also should be done in a separate goroutine for the same reason.
|
||||||
go finishUpdate(context.Background(), execPath, web.conf.runningAsService)
|
go finishUpdate(context.Background(), web.logger, execPath, web.conf.runningAsService)
|
||||||
}
|
}
|
||||||
|
|
||||||
// versionResponse is the response for /control/version.json endpoint.
|
// versionResponse is the response for /control/version.json endpoint.
|
||||||
@@ -179,15 +188,17 @@ func tlsConfUsesPrivilegedPorts(c *tlsConfigSettings) (ok bool) {
|
|||||||
return c.Enabled && (c.PortHTTPS < 1024 || c.PortDNSOverTLS < 1024 || c.PortDNSOverQUIC < 1024)
|
return c.Enabled && (c.PortHTTPS < 1024 || c.PortDNSOverTLS < 1024 || c.PortDNSOverQUIC < 1024)
|
||||||
}
|
}
|
||||||
|
|
||||||
// finishUpdate completes an update procedure.
|
// finishUpdate completes an update procedure. It is intended to be used as a
|
||||||
func finishUpdate(ctx context.Context, execPath string, runningAsService bool) {
|
// goroutine.
|
||||||
var err error
|
func finishUpdate(ctx context.Context, l *slog.Logger, execPath string, runningAsService bool) {
|
||||||
|
defer slogutil.RecoverAndExit(ctx, l, osutil.ExitCodeFailure)
|
||||||
|
|
||||||
log.Info("stopping all tasks")
|
l.InfoContext(ctx, "stopping all tasks")
|
||||||
|
|
||||||
cleanup(ctx)
|
cleanup(ctx)
|
||||||
cleanupAlways()
|
cleanupAlways()
|
||||||
|
|
||||||
|
var err error
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
if runningAsService {
|
if runningAsService {
|
||||||
// NOTE: We can't restart the service via "kardianos/service"
|
// NOTE: We can't restart the service via "kardianos/service"
|
||||||
@@ -198,28 +209,28 @@ func finishUpdate(ctx context.Context, execPath string, runningAsService bool) {
|
|||||||
cmd := exec.Command("cmd", "/c", "net stop AdGuardHome & net start AdGuardHome")
|
cmd := exec.Command("cmd", "/c", "net stop AdGuardHome & net start AdGuardHome")
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("restarting: stopping: %s", err)
|
panic(fmt.Errorf("restarting service: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(osutil.ExitCodeSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(execPath, os.Args[1:]...)
|
cmd := exec.Command(execPath, os.Args[1:]...)
|
||||||
log.Info("restarting: %q %q", execPath, os.Args[1:])
|
l.InfoContext(ctx, "restarting", "exec_path", execPath, "args", os.Args[1:])
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("restarting:: %s", err)
|
panic(fmt.Errorf("restarting: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(osutil.ExitCodeSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("restarting: %q %q", execPath, os.Args[1:])
|
l.InfoContext(ctx, "restarting", "exec_path", execPath, "args", os.Args[1:])
|
||||||
err = syscall.Exec(execPath, os.Args, os.Environ())
|
err = syscall.Exec(execPath, os.Args, os.Environ())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("restarting: %s", err)
|
panic(fmt.Errorf("restarting: %w", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||||
"github.com/ameshkov/dnscrypt/v2"
|
"github.com/ameshkov/dnscrypt/v2"
|
||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
@@ -46,14 +47,14 @@ func onConfigModified() {
|
|||||||
|
|
||||||
// initDNS updates all the fields of the [Context] needed to initialize the DNS
|
// initDNS updates all the fields of the [Context] needed to initialize the DNS
|
||||||
// server and initializes it at last. It also must not be called unless
|
// server and initializes it at last. It also must not be called unless
|
||||||
// [config] and [Context] are initialized. l must not be nil.
|
// [config] and [Context] are initialized. baseLogger must not be nil.
|
||||||
func initDNS(l *slog.Logger, statsDir, querylogDir string) (err error) {
|
func initDNS(baseLogger *slog.Logger, statsDir, querylogDir string) (err error) {
|
||||||
anonymizer := config.anonymizer()
|
anonymizer := config.anonymizer()
|
||||||
|
|
||||||
statsConf := stats.Config{
|
statsConf := stats.Config{
|
||||||
Logger: l.With(slogutil.KeyPrefix, "stats"),
|
Logger: baseLogger.With(slogutil.KeyPrefix, "stats"),
|
||||||
Filename: filepath.Join(statsDir, "stats.db"),
|
Filename: filepath.Join(statsDir, "stats.db"),
|
||||||
Limit: config.Stats.Interval.Duration,
|
Limit: time.Duration(config.Stats.Interval),
|
||||||
ConfigModified: onConfigModified,
|
ConfigModified: onConfigModified,
|
||||||
HTTPRegister: httpRegister,
|
HTTPRegister: httpRegister,
|
||||||
Enabled: config.Stats.Enabled,
|
Enabled: config.Stats.Enabled,
|
||||||
@@ -72,13 +73,14 @@ func initDNS(l *slog.Logger, statsDir, querylogDir string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf := querylog.Config{
|
conf := querylog.Config{
|
||||||
|
Logger: baseLogger.With(slogutil.KeyPrefix, "querylog"),
|
||||||
Anonymizer: anonymizer,
|
Anonymizer: anonymizer,
|
||||||
ConfigModified: onConfigModified,
|
ConfigModified: onConfigModified,
|
||||||
HTTPRegister: httpRegister,
|
HTTPRegister: httpRegister,
|
||||||
FindClient: Context.clients.findMultiple,
|
FindClient: Context.clients.findMultiple,
|
||||||
BaseDir: querylogDir,
|
BaseDir: querylogDir,
|
||||||
AnonymizeClientIP: config.DNS.AnonymizeClientIP,
|
AnonymizeClientIP: config.DNS.AnonymizeClientIP,
|
||||||
RotationIvl: config.QueryLog.Interval.Duration,
|
RotationIvl: time.Duration(config.QueryLog.Interval),
|
||||||
MemSize: config.QueryLog.MemSize,
|
MemSize: config.QueryLog.MemSize,
|
||||||
Enabled: config.QueryLog.Enabled,
|
Enabled: config.QueryLog.Enabled,
|
||||||
FileEnabled: config.QueryLog.FileEnabled,
|
FileEnabled: config.QueryLog.FileEnabled,
|
||||||
@@ -112,7 +114,7 @@ func initDNS(l *slog.Logger, statsDir, querylogDir string) (err error) {
|
|||||||
anonymizer,
|
anonymizer,
|
||||||
httpRegister,
|
httpRegister,
|
||||||
tlsConf,
|
tlsConf,
|
||||||
l,
|
baseLogger,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +243,7 @@ func newServerConfig(
|
|||||||
Config: fwdConf,
|
Config: fwdConf,
|
||||||
TLSConfig: newDNSTLSConfig(tlsConf, hosts),
|
TLSConfig: newDNSTLSConfig(tlsConf, hosts),
|
||||||
TLSAllowUnencryptedDoH: tlsConf.AllowUnencryptedDoH,
|
TLSAllowUnencryptedDoH: tlsConf.AllowUnencryptedDoH,
|
||||||
UpstreamTimeout: dnsConf.UpstreamTimeout.Duration,
|
UpstreamTimeout: time.Duration(dnsConf.UpstreamTimeout),
|
||||||
TLSv12Roots: Context.tlsRoots,
|
TLSv12Roots: Context.tlsRoots,
|
||||||
ConfigModified: onConfigModified,
|
ConfigModified: onConfigModified,
|
||||||
HTTPRegister: httpReg,
|
HTTPRegister: httpReg,
|
||||||
@@ -371,7 +373,7 @@ func getDNSEncryption() (de dnsEncryption) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
de.https = (&url.URL{
|
de.https = (&url.URL{
|
||||||
Scheme: "https",
|
Scheme: urlutil.SchemeHTTPS,
|
||||||
Host: addr,
|
Host: addr,
|
||||||
Path: "/dns-query",
|
Path: "/dns-query",
|
||||||
}).String()
|
}).String()
|
||||||
@@ -456,7 +458,8 @@ func startDNSServer() error {
|
|||||||
Context.filters.EnableFilters(false)
|
Context.filters.EnableFilters(false)
|
||||||
|
|
||||||
// TODO(s.chzhen): Pass context.
|
// TODO(s.chzhen): Pass context.
|
||||||
err := Context.clients.Start(context.TODO())
|
ctx := context.TODO()
|
||||||
|
err := Context.clients.Start(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("starting clients container: %w", err)
|
return fmt.Errorf("starting clients container: %w", err)
|
||||||
}
|
}
|
||||||
@@ -468,7 +471,11 @@ func startDNSServer() error {
|
|||||||
|
|
||||||
Context.filters.Start()
|
Context.filters.Start()
|
||||||
Context.stats.Start()
|
Context.stats.Start()
|
||||||
Context.queryLog.Start()
|
|
||||||
|
err = Context.queryLog.Start(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("starting query log: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -524,12 +531,16 @@ func closeDNSServer() {
|
|||||||
if Context.stats != nil {
|
if Context.stats != nil {
|
||||||
err := Context.stats.Close()
|
err := Context.stats.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("closing stats: %s", err)
|
log.Error("closing stats: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if Context.queryLog != nil {
|
if Context.queryLog != nil {
|
||||||
Context.queryLog.Close()
|
// TODO(s.chzhen): Pass context.
|
||||||
|
err := Context.queryLog.Shutdown(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
log.Error("closing query log: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("all dns modules are closed")
|
log.Debug("all dns modules are closed")
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -18,12 +20,15 @@ var testIPv4 = netip.AddrFrom4([4]byte{1, 2, 3, 4})
|
|||||||
func newStorage(tb testing.TB, clients []*client.Persistent) (s *client.Storage) {
|
func newStorage(tb testing.TB, clients []*client.Persistent) (s *client.Storage) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
|
||||||
s, err := client.NewStorage(&client.StorageConfig{})
|
ctx := testutil.ContextWithTimeout(tb, testTimeout)
|
||||||
|
s, err := client.NewStorage(ctx, &client.StorageConfig{
|
||||||
|
Logger: slogutil.NewDiscardLogger(),
|
||||||
|
})
|
||||||
require.NoError(tb, err)
|
require.NoError(tb, err)
|
||||||
|
|
||||||
for _, p := range clients {
|
for _, p := range clients {
|
||||||
p.UID = client.MustNewUID()
|
p.UID = client.MustNewUID()
|
||||||
require.NoError(tb, s.Add(p))
|
require.NoError(tb, s.Add(ctx, p))
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -21,7 +20,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
||||||
@@ -42,6 +40,7 @@ import (
|
|||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||||
"github.com/AdguardTeam/golibs/osutil"
|
"github.com/AdguardTeam/golibs/osutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -115,15 +114,16 @@ func Main(clientBuildFS fs.FS) {
|
|||||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
ctx := context.Background()
|
||||||
for {
|
for {
|
||||||
sig := <-signals
|
sig := <-signals
|
||||||
log.Info("Received signal %q", sig)
|
log.Info("Received signal %q", sig)
|
||||||
switch sig {
|
switch sig {
|
||||||
case syscall.SIGHUP:
|
case syscall.SIGHUP:
|
||||||
Context.clients.storage.ReloadARP()
|
Context.clients.storage.ReloadARP(ctx)
|
||||||
Context.tls.reload()
|
Context.tls.reload()
|
||||||
default:
|
default:
|
||||||
cleanup(context.Background())
|
cleanup(ctx)
|
||||||
cleanupAlways()
|
cleanupAlways()
|
||||||
close(done)
|
close(done)
|
||||||
}
|
}
|
||||||
@@ -148,9 +148,17 @@ func setupContext(opts options) (err error) {
|
|||||||
Context.tlsRoots = aghtls.SystemRootCAs()
|
Context.tlsRoots = aghtls.SystemRootCAs()
|
||||||
Context.mux = http.NewServeMux()
|
Context.mux = http.NewServeMux()
|
||||||
|
|
||||||
|
if !opts.noEtcHosts {
|
||||||
|
err = setupHostsContainer()
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error, because it's informative enough as is.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if Context.firstRun {
|
if Context.firstRun {
|
||||||
log.Info("This is the first time AdGuard Home is launched")
|
log.Info("This is the first time AdGuard Home is launched")
|
||||||
checkPermissions()
|
checkNetworkPermissions()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -159,21 +167,13 @@ func setupContext(opts options) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("parsing configuration file: %s", err)
|
log.Error("parsing configuration file: %s", err)
|
||||||
|
|
||||||
os.Exit(1)
|
os.Exit(osutil.ExitCodeFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.checkConfig {
|
if opts.checkConfig {
|
||||||
log.Info("configuration file is ok")
|
log.Info("configuration file is ok")
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(osutil.ExitCodeSuccess)
|
||||||
}
|
|
||||||
|
|
||||||
if !opts.noEtcHosts {
|
|
||||||
err = setupHostsContainer()
|
|
||||||
if err != nil {
|
|
||||||
// Don't wrap the error, because it's informative enough as is.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -278,8 +278,8 @@ func setupOpts(opts options) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initContextClients initializes Context clients and related fields.
|
// initContextClients initializes Context clients and related fields.
|
||||||
func initContextClients(logger *slog.Logger) (err error) {
|
func initContextClients(ctx context.Context, logger *slog.Logger) (err error) {
|
||||||
err = setupDNSFilteringConf(config.Filtering)
|
err = setupDNSFilteringConf(ctx, logger, config.Filtering)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap the error, because it's informative enough as is.
|
// Don't wrap the error, because it's informative enough as is.
|
||||||
return err
|
return err
|
||||||
@@ -306,6 +306,8 @@ func initContextClients(logger *slog.Logger) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Context.clients.Init(
|
return Context.clients.Init(
|
||||||
|
ctx,
|
||||||
|
logger,
|
||||||
config.Clients.Persistent,
|
config.Clients.Persistent,
|
||||||
Context.dhcpServer,
|
Context.dhcpServer,
|
||||||
Context.etcHosts,
|
Context.etcHosts,
|
||||||
@@ -355,7 +357,11 @@ func setupBindOpts(opts options) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setupDNSFilteringConf sets up DNS filtering configuration settings.
|
// setupDNSFilteringConf sets up DNS filtering configuration settings.
|
||||||
func setupDNSFilteringConf(conf *filtering.Config) (err error) {
|
func setupDNSFilteringConf(
|
||||||
|
ctx context.Context,
|
||||||
|
baseLogger *slog.Logger,
|
||||||
|
conf *filtering.Config,
|
||||||
|
) (err error) {
|
||||||
const (
|
const (
|
||||||
dnsTimeout = 3 * time.Second
|
dnsTimeout = 3 * time.Second
|
||||||
|
|
||||||
@@ -446,12 +452,13 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) {
|
|||||||
conf.ParentalBlockHost = host
|
conf.ParentalBlockHost = host
|
||||||
}
|
}
|
||||||
|
|
||||||
conf.SafeSearch, err = safesearch.NewDefault(
|
logger := baseLogger.With(slogutil.KeyPrefix, safesearch.LogPrefix)
|
||||||
conf.SafeSearchConf,
|
conf.SafeSearch, err = safesearch.NewDefault(ctx, &safesearch.DefaultConfig{
|
||||||
"default",
|
Logger: logger,
|
||||||
conf.SafeSearchCacheSize,
|
ServicesConfig: conf.SafeSearchConf,
|
||||||
cacheTime,
|
CacheSize: conf.SafeSearchCacheSize,
|
||||||
)
|
CacheTTL: cacheTime,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("initializing safesearch: %w", err)
|
return fmt.Errorf("initializing safesearch: %w", err)
|
||||||
}
|
}
|
||||||
@@ -487,15 +494,48 @@ func checkPorts() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isUpdateEnabled returns true if the update is enabled for current
|
||||||
|
// configuration. It also logs the decision. customURL should be true if the
|
||||||
|
// updater is using a custom URL.
|
||||||
|
func isUpdateEnabled(ctx context.Context, l *slog.Logger, opts *options, customURL bool) (ok bool) {
|
||||||
|
if opts.disableUpdate {
|
||||||
|
l.DebugContext(ctx, "updates are disabled by command-line option")
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch version.Channel() {
|
||||||
|
case
|
||||||
|
version.ChannelDevelopment,
|
||||||
|
version.ChannelCandidate:
|
||||||
|
if customURL {
|
||||||
|
l.DebugContext(ctx, "updates are enabled because custom url is used")
|
||||||
|
} else {
|
||||||
|
l.DebugContext(ctx, "updates are disabled for development and candidate builds")
|
||||||
|
}
|
||||||
|
|
||||||
|
return customURL
|
||||||
|
default:
|
||||||
|
l.DebugContext(ctx, "updates are enabled")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initWeb initializes the web module. upd and baseLogger must not be nil.
|
||||||
func initWeb(
|
func initWeb(
|
||||||
|
ctx context.Context,
|
||||||
opts options,
|
opts options,
|
||||||
clientBuildFS fs.FS,
|
clientBuildFS fs.FS,
|
||||||
upd *updater.Updater,
|
upd *updater.Updater,
|
||||||
l *slog.Logger,
|
baseLogger *slog.Logger,
|
||||||
|
customURL bool,
|
||||||
) (web *webAPI, err error) {
|
) (web *webAPI, err error) {
|
||||||
|
logger := baseLogger.With(slogutil.KeyPrefix, "webapi")
|
||||||
|
|
||||||
var clientFS fs.FS
|
var clientFS fs.FS
|
||||||
if opts.localFrontend {
|
if opts.localFrontend {
|
||||||
log.Info("warning: using local frontend files")
|
logger.WarnContext(ctx, "using local frontend files")
|
||||||
|
|
||||||
clientFS = os.DirFS("build/static")
|
clientFS = os.DirFS("build/static")
|
||||||
} else {
|
} else {
|
||||||
@@ -505,20 +545,12 @@ func initWeb(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableUpdate := opts.disableUpdate
|
disableUpdate := !isUpdateEnabled(ctx, baseLogger, &opts, customURL)
|
||||||
switch version.Channel() {
|
|
||||||
case
|
|
||||||
version.ChannelDevelopment,
|
|
||||||
version.ChannelCandidate:
|
|
||||||
disableUpdate = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if disableUpdate {
|
|
||||||
log.Info("AdGuard Home updates are disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
webConf := &webConfig{
|
webConf := &webConfig{
|
||||||
updater: upd,
|
updater: upd,
|
||||||
|
logger: logger,
|
||||||
|
baseLogger: baseLogger,
|
||||||
|
|
||||||
clientFS: clientFS,
|
clientFS: clientFS,
|
||||||
|
|
||||||
@@ -534,9 +566,9 @@ func initWeb(
|
|||||||
serveHTTP3: config.DNS.ServeHTTP3,
|
serveHTTP3: config.DNS.ServeHTTP3,
|
||||||
}
|
}
|
||||||
|
|
||||||
web = newWebAPI(webConf, l)
|
web = newWebAPI(ctx, webConf)
|
||||||
if web == nil {
|
if web == nil {
|
||||||
return nil, fmt.Errorf("initializing web: %w", err)
|
return nil, errors.Error("can not initialize web")
|
||||||
}
|
}
|
||||||
|
|
||||||
return web, nil
|
return web, nil
|
||||||
@@ -549,6 +581,8 @@ func fatalOnError(err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run configures and starts AdGuard Home.
|
// run configures and starts AdGuard Home.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Make opts a pointer.
|
||||||
func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
|
func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
|
||||||
// Configure working dir.
|
// Configure working dir.
|
||||||
err := initWorkingDir(opts)
|
err := initWorkingDir(opts)
|
||||||
@@ -584,7 +618,10 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
|
|||||||
// data first, but also to avoid relying on automatic Go init() function.
|
// data first, but also to avoid relying on automatic Go init() function.
|
||||||
filtering.InitModule()
|
filtering.InitModule()
|
||||||
|
|
||||||
err = initContextClients(slogLogger)
|
// TODO(s.chzhen): Use it for the entire initialization process.
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
err = initContextClients(ctx, slogLogger)
|
||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
err = setupOpts(opts)
|
err = setupOpts(opts)
|
||||||
@@ -593,33 +630,13 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
|
|||||||
execPath, err := os.Executable()
|
execPath, err := os.Executable()
|
||||||
fatalOnError(errors.Annotate(err, "getting executable path: %w"))
|
fatalOnError(errors.Annotate(err, "getting executable path: %w"))
|
||||||
|
|
||||||
u := &url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
// TODO(a.garipov): Make configurable.
|
|
||||||
Host: "static.adtidy.org",
|
|
||||||
Path: path.Join("adguardhome", version.Channel(), "version.json"),
|
|
||||||
}
|
|
||||||
|
|
||||||
confPath := configFilePath()
|
confPath := configFilePath()
|
||||||
log.Debug("using config path %q for updater", confPath)
|
|
||||||
|
|
||||||
upd := updater.NewUpdater(&updater.Config{
|
upd, customURL := newUpdater(ctx, slogLogger, Context.workDir, confPath, execPath, config)
|
||||||
Client: config.Filtering.HTTPClient,
|
|
||||||
Version: version.Version(),
|
|
||||||
Channel: version.Channel(),
|
|
||||||
GOARCH: runtime.GOARCH,
|
|
||||||
GOOS: runtime.GOOS,
|
|
||||||
GOARM: version.GOARM(),
|
|
||||||
GOMIPS: version.GOMIPS(),
|
|
||||||
WorkDir: Context.workDir,
|
|
||||||
ConfName: confPath,
|
|
||||||
ExecPath: execPath,
|
|
||||||
VersionCheckURL: u.String(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO(e.burkov): This could be made earlier, probably as the option's
|
// TODO(e.burkov): This could be made earlier, probably as the option's
|
||||||
// effect.
|
// effect.
|
||||||
cmdlineUpdate(opts, upd, slogLogger)
|
cmdlineUpdate(ctx, slogLogger, opts, upd)
|
||||||
|
|
||||||
if !Context.firstRun {
|
if !Context.firstRun {
|
||||||
// Save the updated config.
|
// Save the updated config.
|
||||||
@@ -627,7 +644,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
|
|||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
if config.HTTPConfig.Pprof.Enabled {
|
if config.HTTPConfig.Pprof.Enabled {
|
||||||
startPprof(config.HTTPConfig.Pprof.Port)
|
startPprof(slogLogger, config.HTTPConfig.Pprof.Port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,7 +664,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
|
|||||||
onConfigModified()
|
onConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.web, err = initWeb(opts, clientBuildFS, upd, slogLogger)
|
Context.web, err = initWeb(ctx, opts, clientBuildFS, upd, slogLogger, customURL)
|
||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&Context, config)
|
statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&Context, config)
|
||||||
@@ -675,18 +692,87 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if permcheck.NeedsMigration(confPath) {
|
if !opts.noPermCheck {
|
||||||
permcheck.Migrate(Context.workDir, dataDir, statsDir, querylogDir, confPath)
|
checkPermissions(ctx, slogLogger, Context.workDir, confPath, dataDir, statsDir, querylogDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
permcheck.Check(Context.workDir, dataDir, statsDir, querylogDir, confPath)
|
Context.web.start(ctx)
|
||||||
|
|
||||||
Context.web.start()
|
|
||||||
|
|
||||||
// Wait for other goroutines to complete their job.
|
// Wait for other goroutines to complete their job.
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newUpdater creates a new AdGuard Home updater. customURL is true if the user
|
||||||
|
// has specified a custom version announcement URL.
|
||||||
|
func newUpdater(
|
||||||
|
ctx context.Context,
|
||||||
|
l *slog.Logger,
|
||||||
|
workDir string,
|
||||||
|
confPath string,
|
||||||
|
execPath string,
|
||||||
|
config *configuration,
|
||||||
|
) (upd *updater.Updater, customURL bool) {
|
||||||
|
// envName is the name of the environment variable that can be used to
|
||||||
|
// override the default version check URL.
|
||||||
|
const envName = "ADGUARD_HOME_TEST_UPDATE_VERSION_URL"
|
||||||
|
|
||||||
|
customURLStr := os.Getenv(envName)
|
||||||
|
|
||||||
|
var versionURL *url.URL
|
||||||
|
switch {
|
||||||
|
case version.Channel() == version.ChannelRelease:
|
||||||
|
// Only enable custom version URL for development builds.
|
||||||
|
l.DebugContext(ctx, "custom version url is disabled for release builds")
|
||||||
|
case !config.UnsafeUseCustomUpdateIndexURL:
|
||||||
|
l.DebugContext(ctx, "custom version url is disabled in config")
|
||||||
|
default:
|
||||||
|
versionURL, _ = url.Parse(customURLStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := urlutil.ValidateHTTPURL(versionURL)
|
||||||
|
if customURL = err == nil; !customURL {
|
||||||
|
l.DebugContext(ctx, "parsing custom version url", slogutil.KeyError, err)
|
||||||
|
|
||||||
|
versionURL = updater.DefaultVersionURL()
|
||||||
|
}
|
||||||
|
|
||||||
|
l.DebugContext(ctx, "creating updater", "config_path", confPath)
|
||||||
|
|
||||||
|
return updater.NewUpdater(&updater.Config{
|
||||||
|
Client: config.Filtering.HTTPClient,
|
||||||
|
Version: version.Version(),
|
||||||
|
Channel: version.Channel(),
|
||||||
|
GOARCH: runtime.GOARCH,
|
||||||
|
GOOS: runtime.GOOS,
|
||||||
|
GOARM: version.GOARM(),
|
||||||
|
GOMIPS: version.GOMIPS(),
|
||||||
|
WorkDir: workDir,
|
||||||
|
ConfName: confPath,
|
||||||
|
ExecPath: execPath,
|
||||||
|
VersionCheckURL: versionURL,
|
||||||
|
}), customURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPermissions checks and migrates permissions of the files and directories
|
||||||
|
// used by AdGuard Home, if needed.
|
||||||
|
func checkPermissions(
|
||||||
|
ctx context.Context,
|
||||||
|
baseLogger *slog.Logger,
|
||||||
|
workDir string,
|
||||||
|
confPath string,
|
||||||
|
dataDir string,
|
||||||
|
statsDir string,
|
||||||
|
querylogDir string,
|
||||||
|
) {
|
||||||
|
l := baseLogger.With(slogutil.KeyPrefix, "permcheck")
|
||||||
|
|
||||||
|
if permcheck.NeedsMigration(ctx, l, workDir, confPath) {
|
||||||
|
permcheck.Migrate(ctx, l, workDir, dataDir, statsDir, querylogDir, confPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
permcheck.Check(ctx, l, workDir, dataDir, statsDir, querylogDir, confPath)
|
||||||
|
}
|
||||||
|
|
||||||
// initUsers initializes context auth module. Clears config users field.
|
// initUsers initializes context auth module. Clears config users field.
|
||||||
func initUsers() (auth *Auth, err error) {
|
func initUsers() (auth *Auth, err error) {
|
||||||
sessFilename := filepath.Join(Context.getDataDir(), "sessions.db")
|
sessFilename := filepath.Join(Context.getDataDir(), "sessions.db")
|
||||||
@@ -701,7 +787,7 @@ func initUsers() (auth *Auth, err error) {
|
|||||||
|
|
||||||
trustedProxies := netutil.SliceSubnetSet(netutil.UnembedPrefixes(config.DNS.TrustedProxies))
|
trustedProxies := netutil.SliceSubnetSet(netutil.UnembedPrefixes(config.DNS.TrustedProxies))
|
||||||
|
|
||||||
sessionTTL := config.HTTPConfig.SessionTTL.Seconds()
|
sessionTTL := time.Duration(config.HTTPConfig.SessionTTL).Seconds()
|
||||||
auth = InitAuth(sessFilename, config.Users, uint32(sessionTTL), rateLimiter, trustedProxies)
|
auth = InitAuth(sessFilename, config.Users, uint32(sessionTTL), rateLimiter, trustedProxies)
|
||||||
if auth == nil {
|
if auth == nil {
|
||||||
return nil, errors.Error("initializing auth module failed")
|
return nil, errors.Error("initializing auth module failed")
|
||||||
@@ -721,15 +807,15 @@ func (c *configuration) anonymizer() (ipmut *aghnet.IPMut) {
|
|||||||
return aghnet.NewIPMut(anonFunc)
|
return aghnet.NewIPMut(anonFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// startMods initializes and starts the DNS server after installation. l must
|
// startMods initializes and starts the DNS server after installation.
|
||||||
// not be nil.
|
// baseLogger must not be nil.
|
||||||
func startMods(l *slog.Logger) (err error) {
|
func startMods(baseLogger *slog.Logger) (err error) {
|
||||||
statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&Context, config)
|
statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&Context, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = initDNS(l, statsDir, querylogDir)
|
err = initDNS(baseLogger, statsDir, querylogDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -746,8 +832,9 @@ func startMods(l *slog.Logger) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the current user permissions are enough to run AdGuard Home
|
// checkNetworkPermissions checks if the current user permissions are enough to
|
||||||
func checkPermissions() {
|
// use the required networking functionality.
|
||||||
|
func checkNetworkPermissions() {
|
||||||
log.Info("Checking if AdGuard Home has necessary permissions")
|
log.Info("Checking if AdGuard Home has necessary permissions")
|
||||||
|
|
||||||
if ok, err := aghnet.CanBindPrivilegedPorts(); !ok || err != nil {
|
if ok, err := aghnet.CanBindPrivilegedPorts(); !ok || err != nil {
|
||||||
@@ -901,7 +988,7 @@ func loadCmdLineOpts() (opts options) {
|
|||||||
exitWithError()
|
exitWithError()
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(osutil.ExitCodeSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
return opts
|
return opts
|
||||||
@@ -925,12 +1012,12 @@ func printHTTPAddresses(proto string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
port := config.HTTPConfig.Address.Port()
|
port := config.HTTPConfig.Address.Port()
|
||||||
if proto == aghhttp.SchemeHTTPS {
|
if proto == urlutil.SchemeHTTPS {
|
||||||
port = tlsConf.PortHTTPS
|
port = tlsConf.PortHTTPS
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(e.burkov): Inspect and perhaps merge with the previous condition.
|
// TODO(e.burkov): Inspect and perhaps merge with the previous condition.
|
||||||
if proto == aghhttp.SchemeHTTPS && tlsConf.ServerName != "" {
|
if proto == urlutil.SchemeHTTPS && tlsConf.ServerName != "" {
|
||||||
printWebAddrs(proto, tlsConf.ServerName, tlsConf.PortHTTPS)
|
printWebAddrs(proto, tlsConf.ServerName, tlsConf.PortHTTPS)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -990,7 +1077,7 @@ type jsonError struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cmdlineUpdate updates current application and exits. l must not be nil.
|
// cmdlineUpdate updates current application and exits. l must not be nil.
|
||||||
func cmdlineUpdate(opts options, upd *updater.Updater, l *slog.Logger) {
|
func cmdlineUpdate(ctx context.Context, l *slog.Logger, opts options, upd *updater.Updater) {
|
||||||
if !opts.performUpdate {
|
if !opts.performUpdate {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1003,20 +1090,19 @@ func cmdlineUpdate(opts options, upd *updater.Updater, l *slog.Logger) {
|
|||||||
err := initDNSServer(nil, nil, nil, nil, nil, nil, &tlsConfigSettings{}, l)
|
err := initDNSServer(nil, nil, nil, nil, nil, nil, &tlsConfigSettings{}, l)
|
||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
log.Info("cmdline update: performing update")
|
l.InfoContext(ctx, "performing update via cli")
|
||||||
|
|
||||||
info, err := upd.VersionInfo(true)
|
info, err := upd.VersionInfo(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
vcu := upd.VersionCheckURL()
|
l.ErrorContext(ctx, "getting version info", slogutil.KeyError, err)
|
||||||
log.Error("getting version info from %s: %s", vcu, err)
|
|
||||||
|
|
||||||
os.Exit(1)
|
os.Exit(osutil.ExitCodeFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.NewVersion == version.Version() {
|
if info.NewVersion == version.Version() {
|
||||||
log.Info("no updates available")
|
l.InfoContext(ctx, "no updates available")
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(osutil.ExitCodeSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = upd.Update(Context.firstRun)
|
err = upd.Update(Context.firstRun)
|
||||||
@@ -1024,10 +1110,10 @@ func cmdlineUpdate(opts options, upd *updater.Updater, l *slog.Logger) {
|
|||||||
|
|
||||||
err = restartService()
|
err = restartService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("restarting service: %s", err)
|
l.DebugContext(ctx, "restarting service", slogutil.KeyError, err)
|
||||||
log.Info("AdGuard Home was not installed as a service. " +
|
l.InfoContext(ctx, "AdGuard Home was not installed as a service. "+
|
||||||
"Please restart running instances of AdGuardHome manually.")
|
"Please restart running instances of AdGuardHome manually.")
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(osutil.ExitCodeSuccess)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,15 @@ func newSlogLogger(ls *logSettings) (l *slog.Logger) {
|
|||||||
return slogutil.NewDiscardLogger()
|
return slogutil.NewDiscardLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lvl := slog.LevelInfo
|
||||||
|
if ls.Verbose {
|
||||||
|
lvl = slog.LevelDebug
|
||||||
|
}
|
||||||
|
|
||||||
return slogutil.New(&slogutil.Config{
|
return slogutil.New(&slogutil.Config{
|
||||||
Format: slogutil.FormatAdGuardLegacy,
|
Format: slogutil.FormatAdGuardLegacy,
|
||||||
|
Level: lvl,
|
||||||
AddTimestamp: true,
|
AddTimestamp: true,
|
||||||
Verbose: ls.Verbose,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/httphdr"
|
"github.com/AdguardTeam/golibs/httphdr"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"howett.net/plist"
|
"howett.net/plist"
|
||||||
)
|
)
|
||||||
@@ -84,7 +84,7 @@ func encodeMobileConfig(d *dnsSettings, clientID string) ([]byte, error) {
|
|||||||
case dnsProtoHTTPS:
|
case dnsProtoHTTPS:
|
||||||
dspName = fmt.Sprintf("%s DoH", d.ServerName)
|
dspName = fmt.Sprintf("%s DoH", d.ServerName)
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Scheme: aghhttp.SchemeHTTPS,
|
Scheme: urlutil.SchemeHTTPS,
|
||||||
Host: d.ServerName,
|
Host: d.ServerName,
|
||||||
Path: path.Join("/dns-query", clientID),
|
Path: path.Join("/dns-query", clientID),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/configmigrate"
|
"github.com/AdguardTeam/AdGuardHome/internal/configmigrate"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/golibs/osutil"
|
||||||
"github.com/AdguardTeam/golibs/stringutil"
|
"github.com/AdguardTeam/golibs/stringutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,6 +79,10 @@ type options struct {
|
|||||||
// localFrontend forces AdGuard Home to use the frontend files from disk
|
// localFrontend forces AdGuard Home to use the frontend files from disk
|
||||||
// rather than the ones that have been compiled into the binary.
|
// rather than the ones that have been compiled into the binary.
|
||||||
localFrontend bool
|
localFrontend bool
|
||||||
|
|
||||||
|
// noPermCheck disables checking and migration of permissions for the
|
||||||
|
// security-sensitive files.
|
||||||
|
noPermCheck bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// initCmdLineOpts completes initialization of the global command-line option
|
// initCmdLineOpts completes initialization of the global command-line option
|
||||||
@@ -305,6 +310,15 @@ var cmdLineOpts = []cmdLineOpt{{
|
|||||||
description: "Run in GL-Inet compatibility mode.",
|
description: "Run in GL-Inet compatibility mode.",
|
||||||
longName: "glinet",
|
longName: "glinet",
|
||||||
shortName: "",
|
shortName: "",
|
||||||
|
}, {
|
||||||
|
updateWithValue: nil,
|
||||||
|
updateNoValue: func(o options) (options, error) { o.noPermCheck = true; return o, nil },
|
||||||
|
effect: nil,
|
||||||
|
serialize: func(o options) (val string, ok bool) { return "", o.noPermCheck },
|
||||||
|
description: "Skip checking and migration of permissions " +
|
||||||
|
"of security-sensitive files.",
|
||||||
|
longName: "no-permcheck",
|
||||||
|
shortName: "",
|
||||||
}, {
|
}, {
|
||||||
updateWithValue: nil,
|
updateWithValue: nil,
|
||||||
updateNoValue: nil,
|
updateNoValue: nil,
|
||||||
@@ -316,7 +330,7 @@ var cmdLineOpts = []cmdLineOpt{{
|
|||||||
fmt.Println(version.Full())
|
fmt.Println(version.Full())
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(osutil.ExitCodeSuccess)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||||
"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/urlutil"
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ AdGuard Home is successfully installed and will automatically start on boot.
|
|||||||
There are a few more things that must be configured before you can use it.
|
There are a few more things that must be configured before you can use it.
|
||||||
Click on the link below and follow the Installation Wizard steps to finish setup.
|
Click on the link below and follow the Installation Wizard steps to finish setup.
|
||||||
AdGuard Home is now available at the following addresses:`)
|
AdGuard Home is now available at the following addresses:`)
|
||||||
printHTTPAddresses(aghhttp.SchemeHTTP)
|
printHTTPAddresses(urlutil.SchemeHTTP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package home
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -11,13 +12,15 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/AdguardTeam/golibs/netutil/httputil"
|
"github.com/AdguardTeam/golibs/netutil/httputil"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||||
|
"github.com/AdguardTeam/golibs/osutil"
|
||||||
"github.com/NYTimes/gziphandler"
|
"github.com/NYTimes/gziphandler"
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/quic-go/quic-go/http3"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
@@ -39,6 +42,13 @@ const (
|
|||||||
type webConfig struct {
|
type webConfig struct {
|
||||||
updater *updater.Updater
|
updater *updater.Updater
|
||||||
|
|
||||||
|
// logger is a slog logger used in webAPI. It must not be nil.
|
||||||
|
logger *slog.Logger
|
||||||
|
|
||||||
|
// baseLogger is used to create loggers for other entities. It must not be
|
||||||
|
// nil.
|
||||||
|
baseLogger *slog.Logger
|
||||||
|
|
||||||
clientFS fs.FS
|
clientFS fs.FS
|
||||||
|
|
||||||
// BindAddr is the binding address with port for plain HTTP web interface.
|
// BindAddr is the binding address with port for plain HTTP web interface.
|
||||||
@@ -94,19 +104,26 @@ type webAPI struct {
|
|||||||
// logger is a slog logger used in webAPI. It must not be nil.
|
// logger is a slog logger used in webAPI. It must not be nil.
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
|
|
||||||
|
// baseLogger is used to create loggers for other entities. It must not be
|
||||||
|
// nil.
|
||||||
|
baseLogger *slog.Logger
|
||||||
|
|
||||||
// httpsServer is the server that handles HTTPS traffic. If it is not nil,
|
// httpsServer is the server that handles HTTPS traffic. If it is not nil,
|
||||||
// [Web.http3Server] must also not be nil.
|
// [Web.http3Server] must also not be nil.
|
||||||
httpsServer httpsServer
|
httpsServer httpsServer
|
||||||
}
|
}
|
||||||
|
|
||||||
// newWebAPI creates a new instance of the web UI and API server. l must not be
|
// newWebAPI creates a new instance of the web UI and API server. conf must be
|
||||||
// nil.
|
// valid.
|
||||||
func newWebAPI(conf *webConfig, l *slog.Logger) (w *webAPI) {
|
//
|
||||||
log.Info("web: initializing")
|
// TODO(a.garipov): Return a proper error.
|
||||||
|
func newWebAPI(ctx context.Context, conf *webConfig) (w *webAPI) {
|
||||||
|
conf.logger.InfoContext(ctx, "initializing")
|
||||||
|
|
||||||
w = &webAPI{
|
w = &webAPI{
|
||||||
conf: conf,
|
conf: conf,
|
||||||
logger: l,
|
logger: conf.logger,
|
||||||
|
baseLogger: conf.baseLogger,
|
||||||
}
|
}
|
||||||
|
|
||||||
clientFS := http.FileServer(http.FS(conf.clientFS))
|
clientFS := http.FileServer(http.FS(conf.clientFS))
|
||||||
@@ -116,7 +133,11 @@ func newWebAPI(conf *webConfig, l *slog.Logger) (w *webAPI) {
|
|||||||
|
|
||||||
// add handlers for /install paths, we only need them when we're not configured yet
|
// add handlers for /install paths, we only need them when we're not configured yet
|
||||||
if conf.firstRun {
|
if conf.firstRun {
|
||||||
log.Info("This is the first launch of AdGuard Home, redirecting everything to /install.html ")
|
conf.logger.InfoContext(
|
||||||
|
ctx,
|
||||||
|
"This is the first launch of AdGuard Home, redirecting everything to /install.html",
|
||||||
|
)
|
||||||
|
|
||||||
Context.mux.Handle("/install.html", preInstallHandler(clientFS))
|
Context.mux.Handle("/install.html", preInstallHandler(clientFS))
|
||||||
w.registerInstallHandlers()
|
w.registerInstallHandlers()
|
||||||
} else {
|
} else {
|
||||||
@@ -152,7 +173,9 @@ func webCheckPortAvailable(port uint16) (ok bool) {
|
|||||||
// tlsConfigChanged updates the TLS configuration and restarts the HTTPS server
|
// tlsConfigChanged updates the TLS configuration and restarts the HTTPS server
|
||||||
// if necessary.
|
// if necessary.
|
||||||
func (web *webAPI) tlsConfigChanged(ctx context.Context, tlsConf tlsConfigSettings) {
|
func (web *webAPI) tlsConfigChanged(ctx context.Context, tlsConf tlsConfigSettings) {
|
||||||
log.Debug("web: applying new tls configuration")
|
defer slogutil.RecoverAndExit(ctx, web.logger, osutil.ExitCodeFailure)
|
||||||
|
|
||||||
|
web.logger.DebugContext(ctx, "applying new tls configuration")
|
||||||
|
|
||||||
enabled := tlsConf.Enabled &&
|
enabled := tlsConf.Enabled &&
|
||||||
tlsConf.PortHTTPS != 0 &&
|
tlsConf.PortHTTPS != 0 &&
|
||||||
@@ -163,7 +186,7 @@ func (web *webAPI) tlsConfigChanged(ctx context.Context, tlsConf tlsConfigSettin
|
|||||||
if enabled {
|
if enabled {
|
||||||
cert, err = tls.X509KeyPair(tlsConf.CertificateChainData, tlsConf.PrivateKeyData)
|
cert, err = tls.X509KeyPair(tlsConf.CertificateChainData, tlsConf.PrivateKeyData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,8 +194,8 @@ func (web *webAPI) tlsConfigChanged(ctx context.Context, tlsConf tlsConfigSettin
|
|||||||
if web.httpsServer.server != nil {
|
if web.httpsServer.server != nil {
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
ctx, cancel = context.WithTimeout(ctx, shutdownTimeout)
|
ctx, cancel = context.WithTimeout(ctx, shutdownTimeout)
|
||||||
shutdownSrv(ctx, web.httpsServer.server)
|
shutdownSrv(ctx, web.logger, web.httpsServer.server)
|
||||||
shutdownSrv3(web.httpsServer.server3)
|
shutdownSrv3(ctx, web.logger, web.httpsServer.server3)
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
@@ -183,32 +206,39 @@ func (web *webAPI) tlsConfigChanged(ctx context.Context, tlsConf tlsConfigSettin
|
|||||||
web.httpsServer.cond.L.Unlock()
|
web.httpsServer.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loggerKeyServer is the key used by [webAPI] to identify servers.
|
||||||
|
const loggerKeyServer = "server"
|
||||||
|
|
||||||
// start - start serving HTTP requests
|
// start - start serving HTTP requests
|
||||||
func (web *webAPI) start() {
|
func (web *webAPI) start(ctx context.Context) {
|
||||||
log.Println("AdGuard Home is available at the following addresses:")
|
defer slogutil.RecoverAndExit(ctx, web.logger, osutil.ExitCodeFailure)
|
||||||
|
|
||||||
|
web.logger.InfoContext(ctx, "AdGuard Home is available at the following addresses:")
|
||||||
|
|
||||||
// for https, we have a separate goroutine loop
|
// for https, we have a separate goroutine loop
|
||||||
go web.tlsServerLoop()
|
go web.tlsServerLoop(ctx)
|
||||||
|
|
||||||
// this loop is used as an ability to change listening host and/or port
|
// this loop is used as an ability to change listening host and/or port
|
||||||
for !web.httpsServer.inShutdown {
|
for !web.httpsServer.inShutdown {
|
||||||
printHTTPAddresses(aghhttp.SchemeHTTP)
|
printHTTPAddresses(urlutil.SchemeHTTP)
|
||||||
errs := make(chan error, 2)
|
errs := make(chan error, 2)
|
||||||
|
|
||||||
// Use an h2c handler to support unencrypted HTTP/2, e.g. for proxies.
|
// Use an h2c handler to support unencrypted HTTP/2, e.g. for proxies.
|
||||||
hdlr := h2c.NewHandler(withMiddlewares(Context.mux, limitRequestBody), &http2.Server{})
|
hdlr := h2c.NewHandler(withMiddlewares(Context.mux, limitRequestBody), &http2.Server{})
|
||||||
|
|
||||||
|
logger := web.baseLogger.With(loggerKeyServer, "plain")
|
||||||
|
|
||||||
// Create a new instance, because the Web is not usable after Shutdown.
|
// Create a new instance, because the Web is not usable after Shutdown.
|
||||||
web.httpServer = &http.Server{
|
web.httpServer = &http.Server{
|
||||||
ErrorLog: log.StdLog("web: plain", log.DEBUG),
|
|
||||||
Addr: web.conf.BindAddr.String(),
|
Addr: web.conf.BindAddr.String(),
|
||||||
Handler: hdlr,
|
Handler: hdlr,
|
||||||
ReadTimeout: web.conf.ReadTimeout,
|
ReadTimeout: web.conf.ReadTimeout,
|
||||||
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
|
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
|
||||||
WriteTimeout: web.conf.WriteTimeout,
|
WriteTimeout: web.conf.WriteTimeout,
|
||||||
|
ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
defer log.OnPanic("web: plain")
|
defer slogutil.RecoverAndLog(ctx, web.logger)
|
||||||
|
|
||||||
errs <- web.httpServer.ListenAndServe()
|
errs <- web.httpServer.ListenAndServe()
|
||||||
}()
|
}()
|
||||||
@@ -216,7 +246,7 @@ func (web *webAPI) start() {
|
|||||||
err := <-errs
|
err := <-errs
|
||||||
if !errors.Is(err, http.ErrServerClosed) {
|
if !errors.Is(err, http.ErrServerClosed) {
|
||||||
cleanupAlways()
|
cleanupAlways()
|
||||||
log.Fatal(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use ErrServerClosed as a sign that we need to rebind on a new
|
// We use ErrServerClosed as a sign that we need to rebind on a new
|
||||||
@@ -226,7 +256,7 @@ func (web *webAPI) start() {
|
|||||||
|
|
||||||
// close gracefully shuts down the HTTP servers.
|
// close gracefully shuts down the HTTP servers.
|
||||||
func (web *webAPI) close(ctx context.Context) {
|
func (web *webAPI) close(ctx context.Context) {
|
||||||
log.Info("stopping http server...")
|
web.logger.InfoContext(ctx, "stopping http server")
|
||||||
|
|
||||||
web.httpsServer.cond.L.Lock()
|
web.httpsServer.cond.L.Lock()
|
||||||
web.httpsServer.inShutdown = true
|
web.httpsServer.inShutdown = true
|
||||||
@@ -236,14 +266,16 @@ func (web *webAPI) close(ctx context.Context) {
|
|||||||
ctx, cancel = context.WithTimeout(ctx, shutdownTimeout)
|
ctx, cancel = context.WithTimeout(ctx, shutdownTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
shutdownSrv(ctx, web.httpsServer.server)
|
shutdownSrv(ctx, web.logger, web.httpsServer.server)
|
||||||
shutdownSrv3(web.httpsServer.server3)
|
shutdownSrv3(ctx, web.logger, web.httpsServer.server3)
|
||||||
shutdownSrv(ctx, web.httpServer)
|
shutdownSrv(ctx, web.logger, web.httpServer)
|
||||||
|
|
||||||
log.Info("stopped http server")
|
web.logger.InfoContext(ctx, "stopped http server")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (web *webAPI) tlsServerLoop() {
|
func (web *webAPI) tlsServerLoop(ctx context.Context) {
|
||||||
|
defer slogutil.RecoverAndExit(ctx, web.logger, osutil.ExitCodeFailure)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
web.httpsServer.cond.L.Lock()
|
web.httpsServer.cond.L.Lock()
|
||||||
if web.httpsServer.inShutdown {
|
if web.httpsServer.inShutdown {
|
||||||
@@ -271,38 +303,40 @@ func (web *webAPI) tlsServerLoop() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
addr := netip.AddrPortFrom(web.conf.BindAddr.Addr(), portHTTPS).String()
|
addr := netip.AddrPortFrom(web.conf.BindAddr.Addr(), portHTTPS).String()
|
||||||
|
logger := web.baseLogger.With(loggerKeyServer, "https")
|
||||||
|
|
||||||
web.httpsServer.server = &http.Server{
|
web.httpsServer.server = &http.Server{
|
||||||
ErrorLog: log.StdLog("web: https", log.DEBUG),
|
Addr: addr,
|
||||||
Addr: addr,
|
Handler: withMiddlewares(Context.mux, limitRequestBody),
|
||||||
TLSConfig: &tls.Config{
|
TLSConfig: &tls.Config{
|
||||||
Certificates: []tls.Certificate{web.httpsServer.cert},
|
Certificates: []tls.Certificate{web.httpsServer.cert},
|
||||||
RootCAs: Context.tlsRoots,
|
RootCAs: Context.tlsRoots,
|
||||||
CipherSuites: Context.tlsCipherIDs,
|
CipherSuites: Context.tlsCipherIDs,
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
},
|
},
|
||||||
Handler: withMiddlewares(Context.mux, limitRequestBody),
|
|
||||||
ReadTimeout: web.conf.ReadTimeout,
|
ReadTimeout: web.conf.ReadTimeout,
|
||||||
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
|
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
|
||||||
WriteTimeout: web.conf.WriteTimeout,
|
WriteTimeout: web.conf.WriteTimeout,
|
||||||
|
ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
|
||||||
}
|
}
|
||||||
|
|
||||||
printHTTPAddresses(aghhttp.SchemeHTTPS)
|
printHTTPAddresses(urlutil.SchemeHTTPS)
|
||||||
|
|
||||||
if web.conf.serveHTTP3 {
|
if web.conf.serveHTTP3 {
|
||||||
go web.mustStartHTTP3(addr)
|
go web.mustStartHTTP3(ctx, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("web: starting https server")
|
web.logger.DebugContext(ctx, "starting https server")
|
||||||
err := web.httpsServer.server.ListenAndServeTLS("", "")
|
err := web.httpsServer.server.ListenAndServeTLS("", "")
|
||||||
if !errors.Is(err, http.ErrServerClosed) {
|
if !errors.Is(err, http.ErrServerClosed) {
|
||||||
cleanupAlways()
|
cleanupAlways()
|
||||||
log.Fatalf("web: https: %s", err)
|
panic(fmt.Errorf("https: %w", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (web *webAPI) mustStartHTTP3(address string) {
|
func (web *webAPI) mustStartHTTP3(ctx context.Context, address string) {
|
||||||
defer log.OnPanic("web: http3")
|
defer slogutil.RecoverAndExit(ctx, web.logger, osutil.ExitCodeFailure)
|
||||||
|
|
||||||
web.httpsServer.server3 = &http3.Server{
|
web.httpsServer.server3 = &http3.Server{
|
||||||
// TODO(a.garipov): See if there is a way to use the error log as
|
// TODO(a.garipov): See if there is a way to use the error log as
|
||||||
@@ -317,16 +351,16 @@ func (web *webAPI) mustStartHTTP3(address string) {
|
|||||||
Handler: withMiddlewares(Context.mux, limitRequestBody),
|
Handler: withMiddlewares(Context.mux, limitRequestBody),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("web: starting http/3 server")
|
web.logger.DebugContext(ctx, "starting http/3 server")
|
||||||
err := web.httpsServer.server3.ListenAndServe()
|
err := web.httpsServer.server3.ListenAndServe()
|
||||||
if !errors.Is(err, http.ErrServerClosed) {
|
if !errors.Is(err, http.ErrServerClosed) {
|
||||||
cleanupAlways()
|
cleanupAlways()
|
||||||
log.Fatalf("web: http3: %s", err)
|
panic(fmt.Errorf("http3: %w", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// startPprof launches the debug and profiling server on the provided port.
|
// startPprof launches the debug and profiling server on the provided port.
|
||||||
func startPprof(port uint16) {
|
func startPprof(baseLogger *slog.Logger, port uint16) {
|
||||||
addr := netip.AddrPortFrom(netutil.IPv4Localhost(), port)
|
addr := netip.AddrPortFrom(netutil.IPv4Localhost(), port)
|
||||||
|
|
||||||
runtime.SetBlockProfileRate(1)
|
runtime.SetBlockProfileRate(1)
|
||||||
@@ -335,13 +369,16 @@ func startPprof(port uint16) {
|
|||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
httputil.RoutePprof(mux)
|
httputil.RoutePprof(mux)
|
||||||
|
|
||||||
go func() {
|
ctx := context.Background()
|
||||||
defer log.OnPanic("pprof server")
|
logger := baseLogger.With(slogutil.KeyPrefix, "pprof")
|
||||||
|
|
||||||
log.Info("pprof: listening on %q", addr)
|
go func() {
|
||||||
|
defer slogutil.RecoverAndLog(ctx, logger)
|
||||||
|
|
||||||
|
logger.InfoContext(ctx, "listening", "addr", addr)
|
||||||
err := http.ListenAndServe(addr.String(), mux)
|
err := http.ListenAndServe(addr.String(), mux)
|
||||||
if !errors.Is(err, http.ErrServerClosed) {
|
if !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Error("pprof: shutting down: %s", err)
|
logger.ErrorContext(ctx, "shutting down", slogutil.KeyError, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,9 @@
|
|||||||
// Package agh contains common entities and interfaces of AdGuard Home.
|
// Package agh contains common entities and interfaces of AdGuard Home.
|
||||||
package agh
|
package agh
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"github.com/AdguardTeam/golibs/service"
|
||||||
// Service is the interface for API servers.
|
)
|
||||||
//
|
|
||||||
// TODO(a.garipov): Consider adding a context to Start.
|
|
||||||
//
|
|
||||||
// TODO(a.garipov): Consider adding a Wait method or making an extension
|
|
||||||
// interface for that.
|
|
||||||
type Service interface {
|
|
||||||
// Start starts the service. It does not block.
|
|
||||||
Start() (err error)
|
|
||||||
|
|
||||||
// Shutdown gracefully stops the service. ctx is used to determine
|
|
||||||
// a timeout before trying to stop the service less gracefully.
|
|
||||||
Shutdown(ctx context.Context) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// type check
|
|
||||||
var _ Service = EmptyService{}
|
|
||||||
|
|
||||||
// EmptyService is a [Service] that does nothing.
|
|
||||||
//
|
|
||||||
// TODO(a.garipov): Remove if unnecessary.
|
|
||||||
type EmptyService struct{}
|
|
||||||
|
|
||||||
// Start implements the [Service] interface for EmptyService.
|
|
||||||
func (EmptyService) Start() (err error) { return nil }
|
|
||||||
|
|
||||||
// Shutdown implements the [Service] interface for EmptyService.
|
|
||||||
func (EmptyService) Shutdown(_ context.Context) (err error) { return nil }
|
|
||||||
|
|
||||||
// ServiceWithConfig is an extension of the [Service] interface for services
|
// ServiceWithConfig is an extension of the [Service] interface for services
|
||||||
// that can return their configuration.
|
// that can return their configuration.
|
||||||
@@ -38,8 +11,9 @@ func (EmptyService) Shutdown(_ context.Context) (err error) { return nil }
|
|||||||
// TODO(a.garipov): Consider removing this generic interface if we figure out
|
// TODO(a.garipov): Consider removing this generic interface if we figure out
|
||||||
// how to make it testable in a better way.
|
// how to make it testable in a better way.
|
||||||
type ServiceWithConfig[ConfigType any] interface {
|
type ServiceWithConfig[ConfigType any] interface {
|
||||||
Service
|
service.Interface
|
||||||
|
|
||||||
|
// Config returns a deep clone of the configuration of the service.
|
||||||
Config() (c ConfigType)
|
Config() (c ConfigType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +25,7 @@ var _ ServiceWithConfig[struct{}] = (*EmptyServiceWithConfig[struct{}])(nil)
|
|||||||
//
|
//
|
||||||
// TODO(a.garipov): Remove if unnecessary.
|
// TODO(a.garipov): Remove if unnecessary.
|
||||||
type EmptyServiceWithConfig[ConfigType any] struct {
|
type EmptyServiceWithConfig[ConfigType any] struct {
|
||||||
EmptyService
|
service.Empty
|
||||||
|
|
||||||
Conf ConfigType
|
Conf ConfigType
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
# AdGuard Home v0.108.0 Changelog DRAFT
|
# AdGuard Home v0.108.0 Changelog DRAFT
|
||||||
|
|
||||||
This changelog should be merged into the main one once the next API matures
|
This changelog should be merged into the main one once the next API matures enough.
|
||||||
enough.
|
|
||||||
|
|
||||||
## [v0.108.0] - TODO
|
## [v0.108.0] - TODO
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- The ability to change the port of the pprof debug API.
|
- The ability to change the port of the pprof debug API.
|
||||||
|
|
||||||
- The ability to log to stderr using `--logFile=stderr`.
|
- The ability to log to stderr using `--logFile=stderr`.
|
||||||
|
|
||||||
- The new `--web-addr` flag to set the Web UI address in a `host:port` form.
|
- The new `--web-addr` flag to set the Web UI address in a `host:port` form.
|
||||||
|
|
||||||
- `SIGHUP` now reloads all configuration from the configuration file ([#5676]).
|
- `SIGHUP` now reloads all configuration from the configuration file ([#5676]).
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -20,20 +22,21 @@ enough.
|
|||||||
|
|
||||||
#### Other changes
|
#### Other changes
|
||||||
|
|
||||||
- `-h` is now an alias for `--help` instead of the removed `--host`, see below.
|
- `-h` is now an alias for `--help` instead of the removed `--host`, see below. Use `--web-addr=host:port` to set an address on which to serve the Web UI.
|
||||||
Use `--web-addr=host:port` to set an address on which to serve the Web UI.
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `--check-config` breaking the configuration file ([#4067]).
|
- `--check-config` breaking the configuration file ([#4067]).
|
||||||
|
|
||||||
- Inconsistent application of `--work-dir/-w` ([#2598], [#2902]).
|
- Inconsistent application of `--work-dir/-w` ([#2598], [#2902]).
|
||||||
|
|
||||||
- The order of `-v/--verbose` and `--version` being significant ([#2893]).
|
- The order of `-v/--verbose` and `--version` being significant ([#2893]).
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- The deprecated `--no-mem-optimization` and `--no-etc-hosts` flags.
|
- The deprecated `--no-mem-optimization` and `--no-etc-hosts` flags.
|
||||||
- `--host` and `-p/--port` flags. Use `--web-addr=host:port` to set an address
|
|
||||||
on which to serve the Web UI. `-h` is now an alias for `--help`, see above.
|
- `--host` and `-p/--port` flags. Use `--web-addr=host:port` to set an address on which to serve the Web UI. `-h` is now an alias for `--help`, see above.
|
||||||
|
|
||||||
[#2598]: https://github.com/AdguardTeam/AdGuardHome/issues/2598
|
[#2598]: https://github.com/AdguardTeam/AdGuardHome/issues/2598
|
||||||
[#2893]: https://github.com/AdguardTeam/AdGuardHome/issues/2893
|
[#2893]: https://github.com/AdguardTeam/AdGuardHome/issues/2893
|
||||||
|
|||||||
@@ -12,11 +12,15 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
|
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
|
"github.com/AdguardTeam/golibs/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main is the entry point of AdGuard Home.
|
// Main is the entry point of AdGuard Home.
|
||||||
func Main(embeddedFrontend fs.FS) {
|
func Main(embeddedFrontend fs.FS) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
cmdName := os.Args[0]
|
cmdName := os.Args[0]
|
||||||
@@ -26,70 +30,69 @@ func Main(embeddedFrontend fs.FS) {
|
|||||||
os.Exit(exitCode)
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = setLog(opts)
|
baseLogger := newBaseLogger(opts)
|
||||||
check(err)
|
|
||||||
|
|
||||||
log.Info("starting adguard home, version %s, pid %d", version.Version(), os.Getpid())
|
baseLogger.InfoContext(
|
||||||
|
ctx,
|
||||||
|
"starting adguard home",
|
||||||
|
"version", version.Version(),
|
||||||
|
"pid", os.Getpid(),
|
||||||
|
)
|
||||||
|
|
||||||
if opts.workDir != "" {
|
if opts.workDir != "" {
|
||||||
log.Info("changing working directory to %q", opts.workDir)
|
baseLogger.InfoContext(ctx, "changing working directory", "dir", opts.workDir)
|
||||||
|
|
||||||
err = os.Chdir(opts.workDir)
|
err = os.Chdir(opts.workDir)
|
||||||
check(err)
|
errors.Check(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
frontend, err := frontendFromOpts(opts, embeddedFrontend)
|
frontend, err := frontendFromOpts(ctx, baseLogger, opts, embeddedFrontend)
|
||||||
check(err)
|
errors.Check(err)
|
||||||
|
|
||||||
|
startCtx, startCancel := context.WithTimeout(ctx, defaultTimeoutStart)
|
||||||
|
defer startCancel()
|
||||||
|
|
||||||
confMgrConf := &configmgr.Config{
|
confMgrConf := &configmgr.Config{
|
||||||
Frontend: frontend,
|
BaseLogger: baseLogger,
|
||||||
WebAddr: opts.webAddr,
|
Logger: baseLogger.With(slogutil.KeyPrefix, "configmgr"),
|
||||||
Start: start,
|
Frontend: frontend,
|
||||||
FileName: opts.confFile,
|
WebAddr: opts.webAddr,
|
||||||
|
Start: start,
|
||||||
|
FileName: opts.confFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
confMgr, err := newConfigMgr(confMgrConf)
|
confMgr, err := configmgr.New(startCtx, confMgrConf)
|
||||||
check(err)
|
errors.Check(err)
|
||||||
|
|
||||||
web := confMgr.Web()
|
web := confMgr.Web()
|
||||||
err = web.Start()
|
err = web.Start(startCtx)
|
||||||
check(err)
|
errors.Check(err)
|
||||||
|
|
||||||
dns := confMgr.DNS()
|
dns := confMgr.DNS()
|
||||||
err = dns.Start()
|
err = dns.Start(startCtx)
|
||||||
check(err)
|
errors.Check(err)
|
||||||
|
|
||||||
sigHdlr := newSignalHandler(
|
sigHdlr := newSignalHandler(
|
||||||
|
baseLogger.With(slogutil.KeyPrefix, service.SignalHandlerPrefix),
|
||||||
confMgrConf,
|
confMgrConf,
|
||||||
opts.pidFile,
|
opts.pidFile,
|
||||||
web,
|
web,
|
||||||
dns,
|
dns,
|
||||||
)
|
)
|
||||||
|
|
||||||
sigHdlr.handle()
|
os.Exit(sigHdlr.handle(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultTimeout is the timeout used for some operations where another timeout
|
// Default timeouts.
|
||||||
// hasn't been defined yet.
|
//
|
||||||
const defaultTimeout = 5 * time.Second
|
// TODO(a.garipov): Make configurable.
|
||||||
|
const (
|
||||||
// ctxWithDefaultTimeout is a helper function that returns a context with
|
defaultTimeoutStart = 1 * time.Minute
|
||||||
// timeout set to defaultTimeout.
|
defaultTimeoutShutdown = 5 * time.Second
|
||||||
func ctxWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) {
|
)
|
||||||
return context.WithTimeout(context.Background(), defaultTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newConfigMgr returns a new configuration manager using defaultTimeout as the
|
// newConfigMgr returns a new configuration manager using defaultTimeout as the
|
||||||
// context timeout.
|
// context timeout.
|
||||||
func newConfigMgr(c *configmgr.Config) (m *configmgr.Manager, err error) {
|
func newConfigMgr(ctx context.Context, c *configmgr.Config) (m *configmgr.Manager, err error) {
|
||||||
ctx, cancel := ctxWithDefaultTimeout()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
return configmgr.New(ctx, c)
|
return configmgr.New(ctx, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check is a simple error-checking helper. It must only be used within Main.
|
|
||||||
func check(err error) {
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// syslogServiceName is the name of the AdGuard Home service used for writing
|
// newBaseLogger constructs a base logger based on the command-line options.
|
||||||
// logs to the system log.
|
// opts must not be nil.
|
||||||
const syslogServiceName = "AdGuardHome"
|
func newBaseLogger(opts *options) (baseLogger *slog.Logger) {
|
||||||
|
var output io.Writer
|
||||||
// setLog sets up the text logging.
|
|
||||||
//
|
|
||||||
// TODO(a.garipov): Add parameters from configuration file.
|
|
||||||
func setLog(opts *options) (err error) {
|
|
||||||
switch opts.confFile {
|
switch opts.confFile {
|
||||||
case "stdout":
|
case "stdout":
|
||||||
log.SetOutput(os.Stdout)
|
output = os.Stdout
|
||||||
case "stderr":
|
case "stderr":
|
||||||
log.SetOutput(os.Stderr)
|
output = os.Stderr
|
||||||
case "syslog":
|
case "syslog":
|
||||||
err = aghos.ConfigureSyslog(syslogServiceName)
|
// TODO(a.garipov): Add a syslog handler to golibs.
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("initializing syslog: %w", err)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
// TODO(a.garipov): Use the path.
|
// TODO(a.garipov): Use the path.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lvl := slog.LevelInfo
|
||||||
if opts.verbose {
|
if opts.verbose {
|
||||||
log.SetLevel(log.DEBUG)
|
lvl = slog.LevelDebug
|
||||||
log.Debug("verbose logging enabled")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return slogutil.New(&slogutil.Config{
|
||||||
|
Output: output,
|
||||||
|
// TODO(a.garipov): Get from config?
|
||||||
|
Format: slogutil.FormatText,
|
||||||
|
Level: lvl,
|
||||||
|
// TODO(a.garipov): Get from config.
|
||||||
|
AddTimestamp: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user