Merge branch 'AdguardTeam:master' into fix/client-watch-hot-proxy

This commit is contained in:
Hoàng Rio 2024-02-22 08:19:36 +07:00 committed by GitHub
commit 11f1f4eba5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
132 changed files with 2179 additions and 556 deletions

View file

@ -1,7 +1,7 @@
'name': 'build'
'env':
'GO_VERSION': '1.20.12'
'GO_VERSION': '1.21.7'
'NODE_VERSION': '16'
'on':
@ -101,7 +101,10 @@
- 'name': 'Set up Docker Buildx'
'uses': 'docker/setup-buildx-action@v1'
- 'name': 'Run snapshot build'
'run': 'make SIGN=0 VERBOSE=1 build-release build-docker'
# Set a custom version string, since the checkout@v2 action does not seem
# to know about the master branch, while the version script uses it to
# count the number of commits within the branch.
'run': 'make SIGN=0 VERBOSE=1 VERSION="v0.0.0-github" build-release build-docker'
'notify':
'needs':

View file

@ -1,7 +1,7 @@
'name': 'lint'
'env':
'GO_VERSION': '1.20.12'
'GO_VERSION': '1.21.7'
'on':
'push':

View file

@ -14,18 +14,79 @@ and this project adheres to
<!--
## [v0.108.0] - TBA
## [v0.107.44] - 2023-12-20 (APPROX.)
## [v0.107.45] - 2024-03-05 (APPROX.)
See also the [v0.107.44 GitHub milestone][ms-v0.107.44].
See also the [v0.107.45 GitHub milestone][ms-v0.107.45].
[ms-v0.107.44]: https://github.com/AdguardTeam/AdGuardHome/milestone/79?closed=1
[ms-v0.107.45]: https://github.com/AdguardTeam/AdGuardHome/milestone/80?closed=1
NOTE: Add new changes BELOW THIS COMMENT.
-->
### Security
- Go version has been updated to prevent the possibility of exploiting the Go
vulnerabilities fixed in Go 1.21.6 and Go 1.21.7.
### Added
- Etc timezones to the timezone list ([#6568]).
- Ability to disable the use of system hosts file information for query
resolution ([#6610]).
- Ability to define custom directories for storage of query log files and
statistics ([#5992]).
- Context menu item in the Query Log to add a Client to the Persistent client
list ([#6679]).
### Changed
- Starting with this release our scripts are using Go's [forward compatibility
mechanism][go-toolchain] for updating the Go version.
**Important note for porters:** This change means that if your `go` version
is 1.21+ but is different from the one required by AdGuard Home, the `go` tool
will automatically download the required version.
If you want to use the version installed on your builder, run:
```sh
go get go@$YOUR_VERSION
go mod tidy
```
and call `make` with `GOTOOLCHAIN=local`.
### Deprecated
- Go 1.21 support. Future versions will require at least Go 1.22 to build.
### Fixed
- Incorrect tracking of the system hosts file's changes ([#6711]).
### Removed
- Go 1.20 support, as it has reached end of life.
[#5992]: https://github.com/AdguardTeam/AdGuardHome/issues/5992
[#6610]: https://github.com/AdguardTeam/AdGuardHome/issues/6610
[#6679]: https://github.com/AdguardTeam/AdGuardHome/issues/6679
[#6711]: https://github.com/AdguardTeam/AdGuardHome/issues/6711
[go-toolchain]: https://go.dev/blog/toolchain
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
## [v0.107.44] - 2024-02-06
See also the [v0.107.44 GitHub milestone][ms-v0.107.44].
### Added
- Timezones in the Etc/ area to the timezone list ([#6568]).
- The schema version of the configuration file to the output of running
`AdGuardHome` (or `AdGuardHome.exe`) with `-v --version` command-line options
([#6545]).
@ -34,8 +95,8 @@ NOTE: Add new changes BELOW THIS COMMENT.
### Changed
- The bootstrapped upstream addresses now updated according to the TTL of the
bootstrap DNS response ([#6321]).
- The bootstrapped upstream addresses are now updated according to the TTL of
the bootstrap DNS response ([#6321]).
- Logging level of timeout errors is now `error` instead of `debug` ([#6574]).
- The field `"upstream_mode"` in `POST /control/dns_config` and
`GET /control/dns_info` HTTP APIs now accepts `load_balance` value. Check
@ -45,8 +106,8 @@ NOTE: Add new changes BELOW THIS COMMENT.
In this release, the schema version has changed from 27 to 28.
- The new property `clients.persistent.*.uid`, which is unique identifier of the
persistent client.
- The new property `clients.persistent.*.uid`, which is a unique identifier of
the persistent client.
- The properties `dns.all_servers` and `dns.fastest_addr` were removed, their
values migrated to newly added field `dns.upstream_mode` that describes the
logic through which upstreams will be used. See also a [Wiki
@ -100,11 +161,8 @@ In this release, the schema version has changed from 27 to 28.
[#6584]: https://github.com/AdguardTeam/AdGuardHome/issues/6584
[#6644]: https://github.com/AdguardTeam/AdGuardHome/issues/6644
[wiki-config]: https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
[ms-v0.107.44]: https://github.com/AdguardTeam/AdGuardHome/milestone/79?closed=1
[wiki-config]: https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration
@ -2752,11 +2810,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.44...HEAD
[v0.107.44]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.43...v0.107.44
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.45...HEAD
[v0.107.45]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.44...v0.107.45
-->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.43...HEAD
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.44...HEAD
[v0.107.44]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.43...v0.107.44
[v0.107.43]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.42...v0.107.43
[v0.107.42]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.41...v0.107.42
[v0.107.41]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.40...v0.107.41

View file

@ -8,7 +8,7 @@
# Makefile. Bump this number every time a significant change is made to
# this Makefile.
#
# AdGuard-Project-Version: 2
# AdGuard-Project-Version: 4
# Don't name these macros "GO" etc., because GNU Make apparently makes
# them exported environment variables with the literal value of
@ -27,6 +27,7 @@ DIST_DIR = dist
GOAMD64 = v1
GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
GOSUMDB = sum.golang.google.cn
GOTOOLCHAIN = go1.21.7
GPG_KEY = devteam@adguard.com
GPG_KEY_PASSPHRASE = not-a-real-password
NPM = npm
@ -56,15 +57,16 @@ BUILD_RELEASE_DEPS_0 = deps js-build
BUILD_RELEASE_DEPS_1 = go-deps
ENV = env\
COMMIT='$(COMMIT)'\
CHANNEL='$(CHANNEL)'\
GPG_KEY='$(GPG_KEY)'\
GPG_KEY_PASSPHRASE='$(GPG_KEY_PASSPHRASE)'\
COMMIT='$(COMMIT)'\
DIST_DIR='$(DIST_DIR)'\
GO="$(GO.MACRO)"\
GOAMD64="$(GOAMD64)"\
GOPROXY='$(GOPROXY)'\
GOSUMDB='$(GOSUMDB)'\
GOTOOLCHAIN='$(GOTOOLCHAIN)'\
GPG_KEY='$(GPG_KEY)'\
GPG_KEY_PASSPHRASE='$(GPG_KEY_PASSPHRASE)'\
PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
RACE='$(RACE)'\
SIGN='$(SIGN)'\
@ -117,6 +119,8 @@ go-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-tools.sh
# targets.
go-test: ; $(ENV) RACE='1' "$(SHELL)" ./scripts/make/go-test.sh
go-upd-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-upd-tools.sh
go-check: go-tools go-lint go-test
# A quick check to make sure that all supported operating systems can be
@ -132,10 +136,3 @@ openapi-lint: ; cd ./openapi/ && $(YARN) test
openapi-show: ; cd ./openapi/ && $(YARN) start
txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh
# TODO(a.garipov): Consider adding to scripts/ and the common project
# structure.
go-upd-tools:
cd ./internal/tools/ &&\
"$(GO.MACRO)" get -u &&\
"$(GO.MACRO)" mod tidy

View file

@ -473,6 +473,9 @@ bug or implementing the feature.
[@kongfl888](https://github.com/kongfl888) (originally by
[@rufengsuixing](https://github.com/rufengsuixing)).
* [AdGuardHome sync](https://github.com/bakito/adguardhome-sync) by
[@bakito](https://github.com/bakito).
* [Terminal-based, real-time traffic monitoring and statistics for your AdGuard Home
instance](https://github.com/Lissy93/AdGuardian-Term) by
[@Lissy93](https://github.com/Lissy93)

View file

@ -7,7 +7,7 @@
# Make sure to sync any changes with the branch overrides below.
'variables':
'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:8.0'
'stages':
- 'Build frontend':
@ -272,7 +272,7 @@
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:8.0'
# release-vX.Y.Z branches are the branches from which the actual final
# release is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@ -287,4 +287,4 @@
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:8.0'

View file

@ -10,7 +10,7 @@
# Make sure to sync any changes with the branch overrides below.
'variables':
'channel': 'edge'
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:8.0'
'snapcraftChannel': 'edge'
'stages':
@ -191,7 +191,7 @@
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:8.0'
'snapcraftChannel': 'beta'
# release-vX.Y.Z branches are the branches from which the actual final
# release is built.
@ -207,5 +207,5 @@
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:8.0'
'snapcraftChannel': 'candidate'

View file

@ -5,7 +5,8 @@
'key': 'AHBRTSPECS'
'name': 'AdGuard Home - Build and run tests'
'variables':
'dockerGo': 'adguard/golang-ubuntu:7.6'
'dockerGo': 'adguard/golang-ubuntu:8.0'
'channel': 'development'
'stages':
- 'Tests':
@ -73,7 +74,7 @@
make\
ARCH="amd64"\
OS="windows darwin linux"\
CHANNEL="development"\
CHANNEL=${bamboo.channel}\
SIGN=0\
PARALLELISM=1\
VERBOSE=2\
@ -115,3 +116,16 @@
'labels': []
'other':
'concurrent-build-plugin': 'system-default'
'branch-overrides':
# rc-vX.Y.Z branches are the release candidate branches. They are created
# from the release branch and are used to build the release candidate
# images.
- '^rc-v[0-9]+\.[0-9]+\.[0-9]+':
# Build betas on release branches manually.
'triggers': []
# Set the default release channel on the release branch to beta, as we
# may need to build a few of these.
'variables':
'dockerGo': 'adguard/golang-ubuntu:8.0'
'channel': 'candidate'

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Upstream DNS-серверы абноўлены",
"dns_test_ok_toast": "Паказаныя серверы DNS працуюць карэктна",
"dns_test_not_ok_toast": "Сервер «{{key}}»: немагчыма выкарыстоўваць, праверце слушнасць напісання",
"dns_test_parsing_error_toast": "Раздзел {{section}}: радок {{line}}: немагчыма выкарыстоўваць, праверце слушнасць напісання",
"dns_test_warning_toast": "Upstream «{{key}}» не адказвае на тэставыя запыты і можа не працаваць належным чынам",
"unblock": "Адблакаваць",
"block": "Заблакаваць",
@ -243,6 +244,7 @@
"allow_this_client": "Дазволіць доступ гэтаму кліенту",
"block_for_this_client_only": "Заблакаваць толькі для гэтага кліента",
"unblock_for_this_client_only": "Адблакаваць толькі для гэтага кліента",
"add_persistent_client": "Дадаць у захаваныя кліенты",
"time_table_header": "Час",
"date": "Дата",
"domain_name_table_header": "Дамен",
@ -462,6 +464,7 @@
"form_add_id": "Дадаць ідэнтыфікатар",
"form_client_name": "Увядзіце імя кліента",
"name": "Назва",
"client_name": "Кліент {{id}}",
"client_global_settings": "Выкарыстаць глабальныя налады",
"client_deleted": "Кліент «{{key}}» паспяхова выдалены",
"client_added": "Кліент «{{key}}» паспяхова дададзены",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Odchozí servery byly úspěšně uloženy",
"dns_test_ok_toast": "Specifikované DNS servery pracují správně",
"dns_test_not_ok_toast": "Server \"{{key}}\": nemohl být použit, zkontrolujte, zda jste ho správně napsali",
"dns_test_parsing_error_toast": "Sekce {{section}}: řádek {{line}}: nelze použít, zkontrolujte prosím, zda jste ho správně napsali",
"dns_test_warning_toast": "Upstream \"{{key}}\" neodpovídá na testovací požadavky a nemusí fungovat správně",
"unblock": "Odblokovat",
"block": "Blokovat",
@ -243,6 +244,7 @@
"allow_this_client": "Povolit tohoto klienta",
"block_for_this_client_only": "Blokovat pouze pro tohoto klienta",
"unblock_for_this_client_only": "Odblokovat pouze pro tohoto klienta",
"add_persistent_client": "Přidat jako trvalého klienta",
"time_table_header": "Čas",
"date": "Datum",
"domain_name_table_header": "Název domény",
@ -465,6 +467,7 @@
"form_add_id": "Přidat identifikátor",
"form_client_name": "Zadejte název klienta",
"name": "Název",
"client_name": "Klient {{id}}",
"client_global_settings": "Použít globální nastavení",
"client_deleted": "Klient \"{{key}}\" byl úspěšně odstraněn",
"client_added": "Klient \"{{key}}\" byl úspěšně přidán",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Upstream-servere er gemt",
"dns_test_ok_toast": "Angivne DNS-servere fungerer korrekt",
"dns_test_not_ok_toast": "Server \"{{key}}\": Kunne ikke bruges. Tjek, at du har angivet den korrekt",
"dns_test_parsing_error_toast": "Sektion {{section}}: linje {{line}}: kunne ikke anvendes. Tjek at den er angivet korrekt",
"dns_test_warning_toast": "Upstream \"{{key}}\" svarer ikke på testforespørgsler og fungerer muligvis ikke korrekt",
"unblock": "Afblokering",
"block": "Blokering",
@ -243,6 +244,7 @@
"allow_this_client": "Tillad denne klient",
"block_for_this_client_only": "Blokér kun for denne klient",
"unblock_for_this_client_only": "Afblokér kun for denne klient",
"add_persistent_client": "Tilføj som vedvarende klient",
"time_table_header": "Tid",
"date": "Dato",
"domain_name_table_header": "Domænenavn",
@ -465,6 +467,7 @@
"form_add_id": "Tilføj identifikator",
"form_client_name": "Angiv klientnavn",
"name": "Navn",
"client_name": "Klient {{id}}",
"client_global_settings": "Brug globale indstillinger",
"client_deleted": "Klient \"{{key}}\" slettet",
"client_added": "Klient \"{{key}}\" tilføjet",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Upstream-Server erfolgreich gespeichert",
"dns_test_ok_toast": "Angegebene DNS-Server arbeiten ordnungsgemäß",
"dns_test_not_ok_toast": "Server „{{key}}“: konnte nicht verwendet werden, bitte überprüfen Sie die korrekte Schreibweise",
"dns_test_parsing_error_toast": "Abschnitt {{section}}: Zeile {{line}}: konnte nicht verwendet werden, bitte überprüfen Sie, ob alles richtig geschrieben ist",
"dns_test_warning_toast": "Upstream „{{key}}“ reagiert nicht auf Testanfragen und funktioniert möglicherweise nicht fehlerfrei",
"unblock": "Entsperren",
"block": "Sperren",
@ -243,6 +244,7 @@
"allow_this_client": "Diesen Client zulassen",
"block_for_this_client_only": "Nur für diesen Client sperren",
"unblock_for_this_client_only": "Nur für diesen Client freigeben",
"add_persistent_client": "Als dauerhaften Client hinzufügen",
"time_table_header": "Zeit",
"date": "Datum",
"domain_name_table_header": "Domainname",
@ -465,6 +467,7 @@
"form_add_id": "Kennung hinzufügen",
"form_client_name": "Clientnamen eingeben",
"name": "Name",
"client_name": "Client {{id}}",
"client_global_settings": "Allgemeine Einstellungen nutzen",
"client_deleted": "Client „{{key}}“ erfolgreich entfernt",
"client_added": "Client „{{key}}“ erfolgreich hinzugefügt",

View file

@ -244,6 +244,7 @@
"allow_this_client": "Allow this client",
"block_for_this_client_only": "Block for this client only",
"unblock_for_this_client_only": "Unblock for this client only",
"add_persistent_client": "Add as persistent client",
"time_table_header": "Time",
"date": "Date",
"domain_name_table_header": "Domain name",
@ -466,6 +467,7 @@
"form_add_id": "Add identifier",
"form_client_name": "Enter client name",
"name": "Name",
"client_name": "Client {{id}}",
"client_global_settings": "Use global settings",
"client_deleted": "Client \"{{key}}\" successfully deleted",
"client_added": "Client \"{{key}}\" successfully added",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Servidores DNS de subida guardados correctamente",
"dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
"dns_test_not_ok_toast": "Servidor \"{{key}}\": no se puede utilizar, por favor revisa si lo has escrito correctamente",
"dns_test_parsing_error_toast": "No se pudo utilizar la sección {{section}}: línea {{line}}:, verifica si la escribiste correctamente",
"dns_test_warning_toast": "DNS de subida \"{{key}}\" no responde a las peticiones de prueba y es posible que no funcione correctamente",
"unblock": "Desbloquear",
"block": "Bloquear",
@ -243,6 +244,7 @@
"allow_this_client": "Permitir a este cliente",
"block_for_this_client_only": "Bloquear solo para este cliente",
"unblock_for_this_client_only": "Desbloquear solo para este cliente",
"add_persistent_client": "Añadir como cliente persistente",
"time_table_header": "Hora",
"date": "Fecha",
"domain_name_table_header": "Nombre del dominio",
@ -465,6 +467,7 @@
"form_add_id": "Añadir identificador",
"form_client_name": "Ingresa el nombre del cliente",
"name": "Nombre",
"client_name": "Cliente {{id}}",
"client_global_settings": "Usar configuración global",
"client_deleted": "Cliente \"{{key}}\" eliminado correctamente",
"client_added": "Cliente \"{{key}}\" añadido correctamente",

View file

@ -220,6 +220,7 @@
"updated_upstream_dns_toast": "سرورهای DNS جریان ارسالی بروز رسانی شده است",
"dns_test_ok_toast": "سرورهای DNS تعیین شده بدرستی کار می کنند",
"dns_test_not_ok_toast": "سرور \"{{key}}\": نمیتواند مورد استفاده قرار گیرد،لطفا بررسی کنید آن را بدرستی نوشته اید",
"dns_test_parsing_error_toast": "بخش {{section}}: خط {{line}}: نمیتواند مورد استفاده قرار گیرد،لطفا بررسی کنید آن را بدرستی نوشته اید",
"unblock": "رفع انسداد",
"block": "مسدود کردن",
"disallow_this_client": "این مشتری را رد کنید",
@ -420,6 +421,7 @@
"form_add_id": "افزودن احرازکننده",
"form_client_name": "نام کلاینت را وارد کنید",
"name": "نام",
"client_name": "مشتری {{id}}",
"client_global_settings": "استفاده از تنظیمات سراسری",
"client_deleted": "کلاینت \"{{key}}\" را با موفقیت حذف کرد",
"client_added": "کلاینت \"{{key}}\" را با موفقیت اضافه کرد",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Ylävirtapalvelimet tallennettiin",
"dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein",
"dns_test_not_ok_toast": "Palvelin \"{{key}}\": Ei voitu käyttää, tarkista oikeinkirjoitus",
"dns_test_parsing_error_toast": "Osio {{section}}: rivi {{line}}: Ei voitu käyttää, tarkista oikeinkirjoitus",
"dns_test_warning_toast": "Datavuon \"{{key}}\" ei vastaa testipyyntöihin eikä välttämättä toimi kunnolla",
"unblock": "Salli",
"block": "Estä",
@ -243,6 +244,7 @@
"allow_this_client": "Salli tämä päätelaite",
"block_for_this_client_only": "Estä vain tältä päätelaitteelta",
"unblock_for_this_client_only": "Salli vain tälle päätelaitteelle",
"add_persistent_client": "Lisää pysyvänä päätelaitteena",
"time_table_header": "Aika",
"date": "Päiväys",
"domain_name_table_header": "Verkkotunnus",
@ -446,7 +448,7 @@
"manual_update": "Seuraa <a>näitä ohjeita</a> päivittääksesi manuaalisesti.",
"processing_update": "Odota kun AdGuard Home päivittyy",
"clients_title": "Pysyvät päätelaitteet",
"clients_desc": "Määritä pysyvät AdGuard Homeen yhdistetyt päätelaitetiedot.",
"clients_desc": "Määritä AdGuard Homeen pysyvästi yhdistettyjen päätelaitteiden tiedot.",
"settings_global": "Yleinen",
"settings_custom": "Mukautettu",
"table_client": "Asiakas",
@ -465,6 +467,7 @@
"form_add_id": "Lisää tunniste",
"form_client_name": "Syötä päätelaitteen nimi",
"name": "Nimi",
"client_name": "Päätelaite {{id}}",
"client_global_settings": "Käytä yleisiä asetuksia",
"client_deleted": "Päätelaite \"{{key}}\" poistettiin",
"client_added": "Päätelaite \"{{key}}\" lisättiin",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Serveurs en amont enregistrés",
"dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent correctement",
"dns_test_not_ok_toast": "Impossible d'utiliser le serveur « {{key}} »: veuillez vérifier si le nom saisi est bien correct",
"dns_test_parsing_error_toast": "La section {{section}}: ligne {{line}}: n'a pas pu être utilisée, veuillez vérifier que vous l'avez écrite correctement",
"dns_test_warning_toast": "L'amont « {{key}} » ne répond pas aux demandes de test et peut ne pas fonctionner correctement",
"unblock": "Débloquer",
"block": "Bloquer",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Uzvodni poslužitelji uspješno su spremljeni",
"dns_test_ok_toast": "Odabrani DNS poslužitelji su trenutno aktivni",
"dns_test_not_ok_toast": "\"{{key}}\" poslužitelja: ne može se upotrijebiti, provjerite jeste li to ispravno napisali",
"dns_test_parsing_error_toast": "Odjeljak {{section}}: redak {{line}}: nije moguće koristiti, provjerite jeste li ispravno napisali",
"dns_test_warning_toast": "Upstream \"{{key}}\" ne odgovara na zahtjeve za testiranje i možda neće raditi ispravno",
"unblock": "Odblokiraj",
"block": "Blokiraj",
@ -243,6 +244,7 @@
"allow_this_client": "Omogući ovog klijenta",
"block_for_this_client_only": "Blokiraj samo za ovog klijenta",
"unblock_for_this_client_only": "Odblokiraj samo za ovog klijenta",
"add_persistent_client": "Dodaj u spremljene klijente",
"time_table_header": "Vrijeme",
"date": "Datum",
"domain_name_table_header": "Naziv domene",
@ -462,6 +464,7 @@
"form_add_id": "Dodaj identifikator",
"form_client_name": "Unesite naziv klijenta",
"name": "Naziv",
"client_name": "Klijent {{id}}",
"client_global_settings": "Koristi globalne postavke",
"client_deleted": "Klijent \"{{key}}\" je uspješno uklonjen",
"client_added": "Klijent \"{{key}}\" je uspješno dodan",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Upstream szerverek sikeresen mentve",
"dns_test_ok_toast": "A megadott DNS-kiszolgálók megfelelően működnek",
"dns_test_not_ok_toast": "Szerver \"{{key}}\": nem használható, ellenőrizze, hogy helyesen írta-e be",
"dns_test_parsing_error_toast": "Szekció {{section}}: sor {{line}}: nem használható, ellenőrizze, hogy helyesen írta-e be",
"dns_test_warning_toast": "A \"{{key}}\" feltöltés nem válaszol a tesztkérelmekre, és lehet, hogy nem működik megfelelően",
"unblock": "Feloldás",
"block": "Blokkolás",
@ -243,6 +244,7 @@
"allow_this_client": "Engedélyezés ennek a kliensnek",
"block_for_this_client_only": "Tiltás csak ennek a kliensnek",
"unblock_for_this_client_only": "Feloldás csak ennek a kliensnek",
"add_persistent_client": "Hozzáadás állandó ügyfélként",
"time_table_header": "Idő",
"date": "Dátum",
"domain_name_table_header": "Domain név",
@ -462,6 +464,7 @@
"form_add_id": "Azonosító hozzáadása",
"form_client_name": "Adja meg a kliens nevét",
"name": "Név",
"client_name": "Ügyfél {{id}}",
"client_global_settings": "Globális beállítások használata",
"client_deleted": "A(z) \"{{key}}\" kliens sikeresen el lett távolítva",
"client_added": "A(z) \"{{key}}\" kliens sikeresen hozzá lett adva",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Server upstream berhasil disimpan",
"dns_test_ok_toast": "Server DNS yang ditentukan bekerja dengan benar",
"dns_test_not_ok_toast": "Server \"{{key}}\": tidak dapat digunakan, mohon cek bahwa Anda telah menulisnya dengan benar",
"dns_test_parsing_error_toast": "Bagian {{section}}: baris {{line}}: tidak dapat digunakan, mohon cek bahwa Anda telah menulisnya dengan benar",
"dns_test_warning_toast": "Upstream \"{{key}}\" tidak menanggapi permintaan pengujian dan mungkin tidak berfungsi dengan baik",
"unblock": "Buka Blokir",
"block": "Blok",
@ -243,6 +244,7 @@
"allow_this_client": "Ijinkan klien ini",
"block_for_this_client_only": "Blok hanya untuk klien ini",
"unblock_for_this_client_only": "Jangan diblok hanya untuk klien ini",
"add_persistent_client": "Tambahkan sebagai klien persisten",
"time_table_header": "Waktu",
"date": "Tanggal",
"domain_name_table_header": "Nama domain",
@ -442,7 +444,7 @@
"fix": "Perbaiki",
"dns_providers": "Berikut adalah <0>daftar penyedia DNS yang dikenal</0> untuk dipilih.",
"update_now": "Perbarui sekarang",
"update_failed": "Pembaruan otomatis gagal. Silahkan <a>ikuti petunjuk ini</a> untuk perbarui secara manual.",
"update_failed": "Pembaruan otomatis gagal. Silakan <a>ikuti langkah-langkah berikut</a> untuk memperbarui secara manual.",
"manual_update": "Silakan <a>mengikuti langkah berikut</a> untuk memperbarui secara manual.",
"processing_update": "Silahkan tunggu, AdGuard Home sedang diperbarui",
"clients_title": "Klien yang gigih",
@ -465,6 +467,7 @@
"form_add_id": "Tambahkan pengenal",
"form_client_name": "Masukkan nama klien",
"name": "Nama",
"client_name": "Klien {{id}}",
"client_global_settings": "Gunakan pengaturan global",
"client_deleted": "Klien \"{{key}}\" berhasil dihapus",
"client_added": "Klien \"{{key}}\" berhasil ditambahkan",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "I server upstream sono stati salvati correttamente",
"dns_test_ok_toast": "I server DNS specificati funzionano correttamente",
"dns_test_not_ok_toast": "Server \"{{key}}\": non può essere utilizzato, assicurati di averlo digitato correttamente",
"dns_test_parsing_error_toast": "Sezione {{section}}: riga {{line}}: non può essere usata, controlla se l'hai scritta correttamente",
"dns_test_warning_toast": "Upstream \"{{key}}\" non risponde alle richieste di test e potrebbe non funzionare correttamente",
"unblock": "Sblocca",
"block": "Blocca",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "アップストリームサーバーを保存しました。",
"dns_test_ok_toast": "指定されたDNSサーバは正しく動作しています",
"dns_test_not_ok_toast": "サーバ \"{{key}}\": 使用できませんでした。正しく入力されているかどうかを確認してください",
"dns_test_parsing_error_toast": "セクション {{section}}: 行 {{line}}: を使用できませんでした。正しく記述されているか確認してください",
"dns_test_warning_toast": "アップストリーム\"{{key}}\"はテストリクエストに応答せず、正しく動作しない可能性があります。",
"unblock": "ブロック解除",
"block": "ブロック",
@ -243,6 +244,7 @@
"allow_this_client": "このクライアントを許可する",
"block_for_this_client_only": "このクライアントに対してのみブロックする",
"unblock_for_this_client_only": "このクライアントに対してのみブロックを解除する",
"add_persistent_client": "永続クライアントとして追加する",
"time_table_header": "時刻",
"date": "購入日時",
"domain_name_table_header": "ドメイン名",
@ -465,6 +467,7 @@
"form_add_id": "識別子を追加する",
"form_client_name": "クライアント名を入力してください",
"name": "名前",
"client_name": "クライアント {{id}}",
"client_global_settings": "グローバル設定を使用する",
"client_deleted": "クライアント \"{{key}}\" の削除に成功しました",
"client_added": "クライアント \"{{key}}\" の追加に成功しました",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "업스트림 서버가 성공적으로 저장되었습니다",
"dns_test_ok_toast": "지정된 DNS 서버가 올바르게 작동하고 있습니다.",
"dns_test_not_ok_toast": "서버 '{{key}}': 사용할 수 없습니다, 제대로 작성했는지 확인하세요",
"dns_test_parsing_error_toast": "섹션 {{section}}: 줄 {{line}}: 사용할 수 없으며, 올바르게 작성했는지 확인하세요.",
"dns_test_warning_toast": "업스트림 '{{key}}'이(가) 테스트 요청에 응답하지 않으며 제대로 작동하지 않을 수 있습니다",
"unblock": "차단 해제",
"block": "차단",
@ -243,6 +244,7 @@
"allow_this_client": "클라이언트 허용",
"block_for_this_client_only": "이 클라이언트에 대해서만 차단",
"unblock_for_this_client_only": "이 클라이언트에 대해서만 차단 해제",
"add_persistent_client": "저장된 클라이언트에 추가",
"time_table_header": "시간",
"date": "날짜",
"domain_name_table_header": "도메인명",
@ -465,6 +467,7 @@
"form_add_id": "식별자 추가",
"form_client_name": "클라이언트 이름 입력",
"name": "이름",
"client_name": "클라이언트 {{id}}",
"client_global_settings": "글로벌 설정 사용",
"client_deleted": "클라이언트 '{{key}}'이(가) 정상적으로 삭제되었습니다",
"client_added": "클라이언트 '{{key}}'이(가) 정상적으로 추가되었습니다",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Upstream-servers succesvol opgeslagen",
"dns_test_ok_toast": "Opgegeven DNS-servers werken correct",
"dns_test_not_ok_toast": "Server \"{{key}}\": kon niet worden gebruikt, controleer of je het correct hebt geschreven",
"dns_test_parsing_error_toast": "Sectie {{section}}: regel {{line}}: kan niet worden gebruikt. Controleer of je het correct hebt geschreven",
"dns_test_warning_toast": "Upstream \"{{key}}\" reageert niet op testverzoeken en werkt mogelijk niet goed",
"unblock": "Deblokkeren",
"block": "Blokkeren",
@ -243,6 +244,7 @@
"allow_this_client": "Toepassing/systeem toelaten",
"block_for_this_client_only": "Alleen voor deze cliënt blokkeren",
"unblock_for_this_client_only": "Alleen voor deze cliënt deblokkeren",
"add_persistent_client": "Toevoegen als permanente client",
"time_table_header": "Tijd",
"date": "Datum",
"domain_name_table_header": "Domein naam",
@ -465,6 +467,7 @@
"form_add_id": "ID toevoegen",
"form_client_name": "Vul gebruikersnaam in",
"name": "Naam",
"client_name": "Client {{id}}",
"client_global_settings": "Gebruik globale instelling",
"client_deleted": "Gebruiker \"{{key}}\" met succes verwijderd",
"client_added": "Gebruiker \"{{key}}\" met succes toegevoegd",

View file

@ -212,6 +212,7 @@
"updated_upstream_dns_toast": "Oppdaterte oppstrøms-DNS-tjenerne",
"dns_test_ok_toast": "De spesifiserte DNS-tjenerne fungerer riktig",
"dns_test_not_ok_toast": "Tjeneren «{{key}}» kunne ikke brukes, vennligst dobbeltsjekk at du har skrevet den riktig",
"dns_test_parsing_error_toast": "Seksjon {{section}}: linje {{line}}: kunne ikke brukes, vennligst sjekk at du har skrevet det riktig",
"unblock": "Tillat",
"block": "Blokker",
"disallow_this_client": "Ikke tillat denne klienten",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Serwery nadrzędne zostały pomyślnie zapisane",
"dns_test_ok_toast": "Określone serwery DNS działają poprawnie",
"dns_test_not_ok_toast": "Serwer \"{{key}}\": nie może być użyte, sprawdź, czy zapisano go poprawnie",
"dns_test_parsing_error_toast": "Sekcja {{section}}: linia {{line}}: nie może być użyte, sprawdź, czy zapisano go poprawnie",
"dns_test_warning_toast": "Upstream \"{{key}}\" nie odpowiada na zapytania testowe i może nie działać prawidłowo",
"unblock": "Odblokuj",
"block": "Zablokuj",
@ -243,6 +244,7 @@
"allow_this_client": "Pozwól temu klientowi",
"block_for_this_client_only": "Zablokuj tylko tego klienta",
"unblock_for_this_client_only": "Odblokuj tylko tego klienta",
"add_persistent_client": "Dodaj do zapisanych klientów",
"time_table_header": "Czas",
"date": "Data",
"domain_name_table_header": "Nazwa domeny",
@ -462,6 +464,7 @@
"form_add_id": "Dodaj identyfikator",
"form_client_name": "Wpisz nazwę klienta",
"name": "Nazwa",
"client_name": "Klient {{id}}",
"client_global_settings": "Użyj ustawień globalnych",
"client_deleted": "Klient \"{{key}}\" został pomyślnie usunięty",
"client_added": "Klient \"{{key}}\" został pomyślnie dodany",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Servidores DNS primário salvos com sucesso",
"dns_test_ok_toast": "Os servidores DNS especificados estão funcionando corretamente",
"dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se você escreveu corretamente",
"dns_test_parsing_error_toast": "A seção {{section}}: linha {{line}}: não pôde ser usada. Verifique se foi escrita corretamente",
"dns_test_warning_toast": "Servidor DNS primário \"{{key}}\" não responde aos Solicitações de teste e pode não funcionar corretamente",
"unblock": "Desbloquear",
"block": "Bloquear",
@ -243,6 +244,7 @@
"allow_this_client": "Permitir este cliente",
"block_for_this_client_only": "Bloquear apenas para este cliente",
"unblock_for_this_client_only": "Desbloquear apenas para este cliente",
"add_persistent_client": "Adicionar como cliente persistente",
"time_table_header": "Data",
"date": "Data",
"domain_name_table_header": "Nome de domínio",
@ -465,6 +467,7 @@
"form_add_id": "Adicionar identificador",
"form_client_name": "Digite o nome do cliente",
"name": "Nome",
"client_name": "Cliente {{id}}",
"client_global_settings": "Usar configurações global",
"client_deleted": "Cliente \"{{key}}\" excluído com sucesso",
"client_added": "Cliente \"{{key}}\" adicionado com sucesso",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Servidores DNS primário guardados com sucesso",
"dns_test_ok_toast": "Os servidores DNS especificados estão a funcionar corretamente",
"dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se o escreveu corretamente",
"dns_test_parsing_error_toast": "A seção {{section}}: linha {{line}}: não pôde ser usada. Verifique se foi escrita corretamente",
"dns_test_warning_toast": "Servidor DNS primário \"{{key}}\" não responde aos solicitações de teste e pode não funcionar corretamente",
"unblock": "Desbloquear",
"block": "Bloquear",
@ -243,6 +244,7 @@
"allow_this_client": "Permitir este cliente",
"block_for_this_client_only": "Bloquear apenas para este cliente",
"unblock_for_this_client_only": "Desbloquear apenas para este cliente",
"add_persistent_client": "Adicionar como cliente persistente",
"time_table_header": "Data",
"date": "Data",
"domain_name_table_header": "Nome do domínio",
@ -465,6 +467,7 @@
"form_add_id": "Adicionar identificador",
"form_client_name": "Insira o nome do cliente",
"name": "Nome",
"client_name": "Cliente {{id}}",
"client_global_settings": "Usar definições globais",
"client_deleted": "Cliente \"{{key}}\" excluído com sucesso",
"client_added": "Cliente \"{{key}}\" adicionado com sucesso",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Serverele din amonte au fost salvate cu succes",
"dns_test_ok_toast": "Serverele DNS specificate funcționează corect",
"dns_test_not_ok_toast": "Serverul \"{{key}}\": nu a putut fi utilizat, verificați dacă l-ați scris corect",
"dns_test_parsing_error_toast": "Secțiune {{section}}: linie {{line}}: nu a putut fi folosit, vă rugăm să verificați dacă l-ați scris corect",
"dns_test_warning_toast": "„{{key}}” în amonte nu răspunde la solicitările de testare și s-ar putea să nu funcționeze corect",
"unblock": "Deblocați",
"block": "Blocați",
@ -243,6 +244,7 @@
"allow_this_client": "Permiteți acest client",
"block_for_this_client_only": "Blocați numai pentru acest client",
"unblock_for_this_client_only": "Deblocați numai pentru acest client",
"add_persistent_client": "Adăugați ca client persistent",
"time_table_header": "Ora",
"date": "Data",
"domain_name_table_header": "Nume domeniu",
@ -462,6 +464,7 @@
"form_add_id": "Adăugați identificator",
"form_client_name": "Introduceți nume client",
"name": "Nume",
"client_name": "Client {{id}}",
"client_global_settings": "Folosiți setări globale",
"client_deleted": "Clientul \"{{key}}\" a fost șters cu succes",
"client_added": "Clientul \"{{key}}\" a fost adăugat cu succes",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "DNS-серверы успешно обновлены",
"dns_test_ok_toast": "Указанные серверы DNS работают корректно",
"dns_test_not_ok_toast": "Сервер «{{key}}»: невозможно использовать, проверьте правильность написания",
"dns_test_parsing_error_toast": "Раздел {{section}}: строка {{line}}: невозможно использовать, проверьте правильность написания",
"dns_test_warning_toast": "Upstream «{{key}}» не отвечает на тестовые запросы и может работать некорректно",
"unblock": "Разблокировать",
"block": "Заблокировать",
@ -243,6 +244,7 @@
"allow_this_client": "Разрешить доступ клиенту",
"block_for_this_client_only": "Заблокировать только для этого клиента",
"unblock_for_this_client_only": "Разблокировать только для этого клиента",
"add_persistent_client": "Добавить в сохранённые клиенты",
"time_table_header": "Время",
"date": "Дата",
"domain_name_table_header": "Домен",
@ -465,6 +467,7 @@
"form_add_id": "Добавить идентификатор",
"form_client_name": "Введите имя клиента",
"name": "Имя",
"client_name": "Клиент {{id}}",
"client_global_settings": "Использовать глобальные настройки",
"client_deleted": "Клиент «{{key}}» успешно удалён",
"client_added": "Клиент «{{key}}» успешно добавлен",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Upstream servery boli úspešne uložené",
"dns_test_ok_toast": "Špecifikované DNS servery pracujú korektne",
"dns_test_not_ok_toast": "Server \"{{key}}\": nemohol byť použitý, skontrolujte, či ste ho správne napísali",
"dns_test_parsing_error_toast": "Sekcia {{section}}: riadok {{line}}: nepodarilo sa použiť, skontrolujte, či ste ho napísali správne",
"dns_test_warning_toast": "Upstream \"{{key}}\" neodpovedá na testovacie dopyty a nemusí fungovať správne",
"unblock": "Odblokovať",
"block": "Blokovať",
@ -243,6 +244,7 @@
"allow_this_client": "Povoliť tohto klienta",
"block_for_this_client_only": "Blokovať len pre tohto klienta",
"unblock_for_this_client_only": "Odblokovať len pre tohto klienta",
"add_persistent_client": "Pridať ako trvalého klienta",
"time_table_header": "Čas",
"date": "Dátum",
"domain_name_table_header": "Meno domény",
@ -465,6 +467,7 @@
"form_add_id": "Pridajte identifikátor",
"form_client_name": "Zadajte meno klienta",
"name": "Meno",
"client_name": "Klient {{id}}",
"client_global_settings": "Použiť globálne nastavenia",
"client_deleted": "\"{{key}}\" klienta bol úspešne vymazaný",
"client_added": "\"{{key}}\" klienta bol úspešne pridaný",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Gorvodni trežniki so uspešno shranjeni",
"dns_test_ok_toast": "Navedeni strežniki DNS delujejo pravilno",
"dns_test_not_ok_toast": "Ni mogoče uporabiti: strežnika \"{{key}}\". Preverite, ali ste ga pravilno napisali",
"dns_test_parsing_error_toast": "Razdelek {{section}}: vrstica {{line}}: ni bilo mogoče uporabiti, preverite, ali ste ga pravilno zapisali",
"dns_test_warning_toast": "Upstream \"{{key}}\" se ne odziva na testne zahteve in morda ne deluje pravilno",
"unblock": "Omogoči",
"block": "Onemogoči",
@ -243,6 +244,7 @@
"allow_this_client": "Dovoli tega odjemalca",
"block_for_this_client_only": "Onemogoči samo za tega odjemalca",
"unblock_for_this_client_only": "Omogoči samo za tega odjemalca",
"add_persistent_client": "Dodaj kot vztrajnega odjemalca",
"time_table_header": "Čas",
"date": "Datum",
"domain_name_table_header": "Ime domene",
@ -465,6 +467,7 @@
"form_add_id": "Dodaj identifikatorja",
"form_client_name": "Vnesite ime odjemalca",
"name": "Ime",
"client_name": "Odjemalec {{id}}",
"client_global_settings": "Uporabi splošne nastavitve",
"client_deleted": "Odjemalec \"{{key}}\" je bil uspešno izbrisan",
"client_added": "Odjemalec \"{{key}}\" je bil uspešno dodan",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Upstream serveri su uspešno sačuvani",
"dns_test_ok_toast": "Dati DNS serveri rade ispravno",
"dns_test_not_ok_toast": "Server \"{{key}}\": se ne može koristiti. Proverite da li ste ga ispravno uneli",
"dns_test_parsing_error_toast": "Odeljak {{section}}: linija {{line}}: ne može se koristiti, molimo proverite da li ste ga ispravno napisali",
"dns_test_warning_toast": "Apstrim \"{{key}}\" ne odgovara na zahteve za testiranje i možda neće raditi kako treba",
"unblock": "Odblokiraj",
"block": "Blokiraj",
@ -243,6 +244,7 @@
"allow_this_client": "Dozvoli ovaj klijent",
"block_for_this_client_only": "Blokiraj samo za ovaj klijent",
"unblock_for_this_client_only": "Odblokiraj samo za ovaj klijent",
"add_persistent_client": "Dodati u sačuvane klijente",
"time_table_header": "Vreme",
"date": "Datum",
"domain_name_table_header": "Ime domena",
@ -462,6 +464,7 @@
"form_add_id": "Dodaj identifikator",
"form_client_name": "Unesite ime klijenta",
"name": "Ime",
"client_name": "Klijent {{id}}",
"client_global_settings": "Koristi globalne postavke",
"client_deleted": "Klijent \"{{key}}\" uspešno izbrisan",
"client_added": "Klijent \"{{key}}\" uspešno dodat",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Sparade uppströms dns-servrar",
"dns_test_ok_toast": "Angivna DNS servrar fungerar korrekt",
"dns_test_not_ok_toast": "Server \"{{key}}\": kunde inte användas. Var snäll och kolla att du skrivit in rätt",
"dns_test_parsing_error_toast": "Avsnitt {{section}}: rad {{line}}: kunde inte användas, kontrollera att du har skrivit det korrekt",
"dns_test_warning_toast": "Uppströms \"{{key}}\" svarar inte på testförfrågningar och kanske inte fungerar korrekt",
"unblock": "Avblockera",
"block": "Blockera",
@ -243,6 +244,7 @@
"allow_this_client": "Tillåt den här klienten",
"block_for_this_client_only": "Blockera endast för denna klient",
"unblock_for_this_client_only": "Avblockera endast för denna klient",
"add_persistent_client": "Lägg till som beständig klient",
"time_table_header": "Tid",
"date": "Datum",
"domain_name_table_header": "Domännamn",
@ -461,6 +463,7 @@
"form_add_id": "Lägg till identifierare",
"form_client_name": "Skriv in klientnamn",
"name": "Namn",
"client_name": "Klient {{id}}",
"client_global_settings": "Använda globala inställningar",
"client_deleted": "Klient \"{{key}}\" har raderats",
"client_added": "Klient \"{{key}}\" har lagts till",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Üst sunucular başarıyla kaydedildi",
"dns_test_ok_toast": "Belirtilen DNS sunucuları düzgün çalışıyor",
"dns_test_not_ok_toast": "Sunucu \"{{key}}\": kullanılamıyor, lütfen doğru yazdığınızdan emin olun",
"dns_test_parsing_error_toast": "{{section}} bölümü: {{line}}. satır: kullanılamadı, lütfen doğru yazdığınızı kontrol edin",
"dns_test_warning_toast": "Üst kaynak \"{{key}}\", test isteklerine yanıt vermiyor ve düzgün çalışmayabilir",
"unblock": "Engeli kaldır",
"block": "Engelle",
@ -243,6 +244,7 @@
"allow_this_client": "Bu istemciye izin ver",
"block_for_this_client_only": "Yalnızca bu istemci için engelle",
"unblock_for_this_client_only": "Yalnızca bu istemci için engellemeyi kaldır",
"add_persistent_client": "Kalıcı istemci olarak ekle",
"time_table_header": "Saat",
"date": "Tarih",
"domain_name_table_header": "Alan adı",
@ -465,6 +467,7 @@
"form_add_id": "Tanımlayıcı ekle",
"form_client_name": "İstemci ismi girin",
"name": "Adı",
"client_name": "İstemci {{id}}",
"client_global_settings": "Genel ayarları kullan",
"client_deleted": "\"{{key}}\" istemcisi başarıyla silindi",
"client_added": "\"{{key}}\" istemcisi başarıyla eklendi",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "DNS-сервери успішно збережено",
"dns_test_ok_toast": "Вказані DNS сервери працюють правильно",
"dns_test_not_ok_toast": "Сервер «{{key}}»: неможливо використати. Перевірте правильність введення",
"dns_test_parsing_error_toast": "Розділ {{section}}: рядок {{line}}: неможливо використати. Перевірте правильність введення",
"dns_test_warning_toast": "Upstream «{{key}}» не відповідає на тестові запити та може працювати не правильно",
"unblock": "Дозволити",
"block": "Заборонити",
@ -243,6 +244,7 @@
"allow_this_client": "Дозволити цей клієнт",
"block_for_this_client_only": "Заборонити тільки цей клієнт",
"unblock_for_this_client_only": "Дозволити тільки цей клієнт",
"add_persistent_client": "Додати в збережені клієнти",
"time_table_header": "Час",
"date": "Дата",
"domain_name_table_header": "Назва домену",
@ -465,6 +467,7 @@
"form_add_id": "Додати ідентифікатор",
"form_client_name": "Введіть ім'я клієнта",
"name": "Ім'я",
"client_name": "Клієнт {{id}}",
"client_global_settings": "Використати загальні налаштування",
"client_deleted": "Клієнта «{{key}}» успішно видалено",
"client_added": "Клієнта «{{key}}» успішно додано",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "Các máy chủ thượng nguồn đã được lưu thành công",
"dns_test_ok_toast": "Máy chủ DNS có thể sử dụng",
"dns_test_not_ok_toast": "Máy chủ \"{{key}}\"': không thể sử dụng, vui lòng kiểm tra lại",
"dns_test_parsing_error_toast": "Phần {{section}}: dòng {{line}}: không thể sử dụng được, vui lòng kiểm tra xem bạn đã viết đúng chưa",
"dns_test_warning_toast": "Ngược lại \"{{key}}\" không phản hồi các yêu cầu kiểm tra và có thể không hoạt động bình thường",
"unblock": "Bỏ chặn",
"block": "Chặn",
@ -243,6 +244,7 @@
"allow_this_client": "Cho phép ứng dụng khách này",
"block_for_this_client_only": "Chỉ chặn ứng dụng khách này",
"unblock_for_this_client_only": "Chỉ hủy chặn ứng dụng khách này",
"add_persistent_client": "Thêm làm ứng dụng khách liên tục",
"time_table_header": "Thời gian",
"date": "Ngày",
"domain_name_table_header": "Tên miền",
@ -462,6 +464,7 @@
"form_add_id": "Thêm định danh",
"form_client_name": "Nhập tên máy khách",
"name": "Tên",
"client_name": "Khách hàng {{id}}",
"client_global_settings": "Sử dụng cài đặt toàn cầu",
"client_deleted": "Máy khách \"{{key}}\" đã xóa thành công",
"client_added": "Máy khách \"{{key}}\" đã thêm thành công",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "上游服务器保存成功",
"dns_test_ok_toast": "指定的 DNS 服务器现已正常运行",
"dns_test_not_ok_toast": "服务器 \"{{key}}\":无法使用,请检查你输入的是否正确",
"dns_test_parsing_error_toast": "第 {{section}} 节:第 {{line}} 行:无法使用,请检查您输入的是否正确",
"dns_test_warning_toast": "上游 “{{key}}” 不响应测试请求,可能无法正常工作",
"unblock": "放行",
"block": "拦截",
@ -243,6 +244,7 @@
"allow_this_client": "允许这个客户端",
"block_for_this_client_only": "仅对此客户端拦截",
"unblock_for_this_client_only": "仅解除对此客户端的拦截",
"add_persistent_client": "添加为持久客户端",
"time_table_header": "时间",
"date": "日期",
"domain_name_table_header": "域名",
@ -465,6 +467,7 @@
"form_add_id": "添加标识符",
"form_client_name": "输入客户端名称",
"name": "名称",
"client_name": "客户端 {{id}}",
"client_global_settings": "使用全局设置",
"client_deleted": "客户端 \"{{key}}\" 删除成功",
"client_added": "客户端 \"{{key}}\" 添加成功",

View file

@ -236,6 +236,7 @@
"updated_upstream_dns_toast": "上游的伺服器被成功地儲存",
"dns_test_ok_toast": "已明確指定的 DNS 伺服器正在正確地運作",
"dns_test_not_ok_toast": "伺服器 \"{{key}}\":無法被使用,請檢查您已正確地填寫它",
"dns_test_parsing_error_toast": "第 {{section}} 節:第 {{line}} 行:無法使用,請檢查您輸入的是否正確",
"dns_test_warning_toast": "上游 “{{key}}” 不回應測試請求,可能無法正常工作",
"unblock": "解除封鎖",
"block": "封鎖",
@ -243,6 +244,7 @@
"allow_this_client": "允許此用戶端",
"block_for_this_client_only": "僅對此用戶端封鎖",
"unblock_for_this_client_only": "僅對此用戶端解除封鎖",
"add_persistent_client": "新增為永久性客戶端",
"time_table_header": "時間",
"date": "日期",
"domain_name_table_header": "域名",
@ -465,6 +467,7 @@
"form_add_id": "新增識別碼",
"form_client_name": "輸入用戶端名稱",
"name": "名稱",
"client_name": "客戶端 {{id}}",
"client_global_settings": "使用全域的設定",
"client_deleted": "用戶端 \"{{key}}\" 被成功地刪除",
"client_added": "用戶端 \"{{key}}\" 被成功地加入",

View file

@ -13,6 +13,8 @@ ReactModal.setAppElement('#root');
const MODAL_TYPE_TO_TITLE_TYPE_MAP = {
[MODAL_TYPE.EDIT_FILTERS]: 'edit',
[MODAL_TYPE.ADD_FILTERS]: 'new',
[MODAL_TYPE.EDIT_CLIENT]: 'edit',
[MODAL_TYPE.ADD_CLIENT]: 'new',
[MODAL_TYPE.SELECT_MODAL_TYPE]: 'new',
[MODAL_TYPE.CHOOSE_FILTERING_LIST]: 'choose',
};

View file

@ -3,7 +3,7 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { nanoid } from 'nanoid';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { Link, useHistory } from 'react-router-dom';
import propTypes from 'prop-types';
import { checkFiltered, getBlockingClientName } from '../../../helpers/helpers';
@ -25,12 +25,14 @@ const ClientCell = ({
}) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const history = useHistory();
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual);
const [isOptionsOpened, setOptionsOpened] = useState(false);
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
const clients = useSelector((state) => state.dashboard.clients);
const source = autoClient?.source;
const whoisAvailable = client_info && Object.keys(client_info.whois).length > 0;
const clientName = client_info?.name || client_id;
@ -55,6 +57,8 @@ const ClientCell = ({
const isFiltered = checkFiltered(reason);
const clientIds = clients.map((c) => c.ids).flat();
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
'mt-2': isDetailed && !client_info?.name && !whoisAvailable,
'white-space--nowrap': isDetailed,
@ -66,7 +70,6 @@ const ClientCell = ({
const renderBlockingButton = (isFiltered, domain) => {
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
const clients = useSelector((state) => state.dashboard.clients);
const {
confirmMessage,
@ -118,6 +121,15 @@ const ClientCell = ({
},
];
if (!clientIds.includes(client)) {
BUTTON_OPTIONS.push({
name: 'add_persistent_client',
onClick: () => {
history.push(`/#clients?clientId=${client}`);
},
});
}
const getOptions = (options) => {
if (options.length === 0) {
return null;

View file

@ -4,6 +4,7 @@ import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory, useLocation } from 'react-router-dom';
import ReactTable from 'react-table';
import { getAllBlockedServices, getBlockedServices } from '../../../../actions/services';
@ -39,8 +40,12 @@ const ClientsTable = ({
}) => {
const [t] = useTranslation();
const dispatch = useDispatch();
const location = useLocation();
const history = useHistory();
const services = useSelector((store) => store?.services);
const globalSettings = useSelector((store) => store?.settings.settingsList) || {};
const params = new URLSearchParams(location.search);
const clientId = params.get('clientId');
const { safesearch } = globalSettings;
@ -48,6 +53,12 @@ const ClientsTable = ({
dispatch(getAllBlockedServices());
dispatch(getBlockedServices());
dispatch(initSettings());
if (clientId) {
toggleClientModal({
type: MODAL_TYPE.ADD_CLIENT,
});
}
}, []);
const handleFormAdd = (values) => {
@ -85,11 +96,15 @@ const ClientsTable = ({
}
}
if (modalType === MODAL_TYPE.EDIT_FILTERS) {
if (modalType === MODAL_TYPE.EDIT_CLIENT) {
handleFormUpdate(config, modalClientName);
} else {
handleFormAdd(config);
}
if (clientId) {
history.push('/#clients');
}
};
const getOptionsWithLabels = (options) => (
@ -133,6 +148,14 @@ const ClientsTable = ({
}
};
const handleClose = () => {
toggleClientModal();
if (clientId) {
history.push('/#clients');
}
};
const columns = [
{
Header: t('table_client'),
@ -298,7 +321,7 @@ const ClientsTable = ({
type="button"
className="btn btn-icon btn-outline-primary btn-sm mr-2"
onClick={() => toggleClientModal({
type: MODAL_TYPE.EDIT_FILTERS,
type: MODAL_TYPE.EDIT_CLIENT,
name: clientName,
})
}
@ -371,12 +394,13 @@ const ClientsTable = ({
<Modal
isModalOpen={isModalOpen}
modalType={modalType}
toggleClientModal={toggleClientModal}
handleClose={handleClose}
currentClientData={currentClientData}
handleSubmit={handleSubmit}
processingAdding={processingAdding}
processingUpdating={processingUpdating}
tagsOptions={tagsOptions}
clientId={clientId}
/>
</>
</Card>

View file

@ -147,7 +147,7 @@ let Form = (props) => {
useGlobalSettings,
useGlobalServices,
blockedServicesSchedule,
toggleClientModal,
handleClose,
processingAdding,
processingUpdating,
invalid,
@ -427,7 +427,7 @@ let Form = (props) => {
disabled={submitting}
onClick={() => {
reset();
toggleClientModal();
handleClose();
}}
>
<Trans>cancel_btn</Trans>
@ -456,7 +456,7 @@ Form.propTypes = {
reset: PropTypes.func.isRequired,
change: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
toggleClientModal: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
useGlobalSettings: PropTypes.bool,
useGlobalServices: PropTypes.bool,
blockedServicesSchedule: PropTypes.object,

View file

@ -6,7 +6,9 @@ import ReactModal from 'react-modal';
import { MODAL_TYPE } from '../../../helpers/constants';
import Form from './Form';
const getInitialData = (initial) => {
const getInitialData = ({
initial, modalType, clientId, clientName,
}) => {
if (initial && initial.blocked_services) {
const { blocked_services } = initial;
const blocked = {};
@ -21,46 +23,60 @@ const getInitialData = (initial) => {
};
}
if (modalType !== MODAL_TYPE.EDIT_CLIENT && clientId) {
return {
...initial,
name: clientName,
ids: [clientId],
};
}
return initial;
};
const Modal = (props) => {
const {
isModalOpen,
const Modal = ({
isModalOpen,
modalType,
currentClientData,
handleSubmit,
handleClose,
processingAdding,
processingUpdating,
tagsOptions,
clientId,
t,
}) => {
const initialData = getInitialData({
initial: currentClientData,
modalType,
currentClientData,
handleSubmit,
toggleClientModal,
processingAdding,
processingUpdating,
tagsOptions,
} = props;
const initialData = getInitialData(currentClientData);
clientId,
clientName: t('client_name', { id: clientId }),
});
return (
<ReactModal
className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--clients"
closeTimeoutMS={0}
isOpen={isModalOpen}
onRequestClose={() => toggleClientModal()}
onRequestClose={handleClose}
>
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">
{modalType === MODAL_TYPE.EDIT_FILTERS ? (
{modalType === MODAL_TYPE.EDIT_CLIENT ? (
<Trans>client_edit</Trans>
) : (
<Trans>client_new</Trans>
)}
</h4>
<button type="button" className="close" onClick={() => toggleClientModal()}>
<button type="button" className="close" onClick={handleClose}>
<span className="sr-only">Close</span>
</button>
</div>
<Form
initialValues={{ ...initialData }}
onSubmit={handleSubmit}
toggleClientModal={toggleClientModal}
handleClose={handleClose}
processingAdding={processingAdding}
processingUpdating={processingUpdating}
tagsOptions={tagsOptions}
@ -75,10 +91,12 @@ Modal.propTypes = {
modalType: PropTypes.string.isRequired,
currentClientData: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
toggleClientModal: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
processingUpdating: PropTypes.bool.isRequired,
tagsOptions: PropTypes.array.isRequired,
t: PropTypes.func.isRequired,
clientId: PropTypes.string,
};
export default withTranslation()(Modal);

View file

@ -182,6 +182,8 @@ export const MODAL_TYPE = {
EDIT_REWRITE: 'EDIT_REWRITE',
EDIT_LEASE: 'EDIT_LEASE',
ADD_LEASE: 'ADD_LEASE',
ADD_CLIENT: 'ADD_CLIENT',
EDIT_CLIENT: 'EDIT_CLIENT',
};
export const CLIENT_ID = {

View file

@ -209,7 +209,7 @@ export default {
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_47.txt"
},
"hagezi_multinormal": {
"name": "HaGeZi Multi NORMAL",
"name": "HaGeZi's Normal Blocklist",
"categoryId": "general",
"homepage": "https://github.com/hagezi/dns-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_34.txt"

32
go.mod
View file

@ -1,11 +1,11 @@
module github.com/AdguardTeam/AdGuardHome
go 1.20
go 1.21.7
require (
github.com/AdguardTeam/dnsproxy v0.64.1
github.com/AdguardTeam/golibs v0.19.0
github.com/AdguardTeam/urlfilter v0.17.3
github.com/AdguardTeam/dnsproxy v0.65.0
github.com/AdguardTeam/golibs v0.20.1
github.com/AdguardTeam/urlfilter v0.18.0
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.2.7
github.com/bluele/gcache v0.0.2
@ -17,8 +17,8 @@ require (
github.com/google/go-cmp v0.6.0
github.com/google/gopacket v1.1.19
github.com/google/renameio/v2 v2.0.0
github.com/google/uuid v1.5.0
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
github.com/google/uuid v1.6.0
github.com/insomniacslk/dhcp v0.0.0-20240204152450-ca2dc33955c1
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
github.com/kardianos/service v1.2.2
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
@ -28,14 +28,14 @@ require (
// own code for that. Perhaps, use gopacket.
github.com/mdlayher/raw v0.1.0
github.com/miekg/dns v1.1.58
github.com/quic-go/quic-go v0.40.1
github.com/quic-go/quic-go v0.41.0
github.com/stretchr/testify v1.8.4
github.com/ti-mo/netfilter v0.5.1
go.etcd.io/bbolt v1.3.8
golang.org/x/crypto v0.18.0
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
golang.org/x/net v0.20.0
golang.org/x/sys v0.16.0
golang.org/x/crypto v0.19.0
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3
golang.org/x/net v0.21.0
golang.org/x/sys v0.17.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.1
@ -48,8 +48,7 @@ require (
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 // indirect
// TODO(a.garipov): Upgrade to v0.5.0 once we switch to Go 1.21+.
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect
github.com/mdlayher/socket v0.5.0 // indirect
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
@ -57,12 +56,11 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
github.com/u-root/uio v0.0.0-20240207234124-abbebccef0fd // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.17.0 // indirect
golang.org/x/tools v0.18.0 // indirect
gonum.org/v1/gonum v0.14.0 // indirect
)

78
go.sum
View file

@ -1,9 +1,9 @@
github.com/AdguardTeam/dnsproxy v0.64.1 h1:Cv2nyNYjUeUxouTQmM0aVTR7LWuhCr/Lu+h3DIAWhG8=
github.com/AdguardTeam/dnsproxy v0.64.1/go.mod h1:dRRAFOjrq4QYM92jGs4lt4BoY0Dm3EY3HkaleoM2Feo=
github.com/AdguardTeam/golibs v0.19.0 h1:y/x+Xn3pDg1ZfQ+QEZapPJqaeVYUIMp/EODMtVhn7PM=
github.com/AdguardTeam/golibs v0.19.0/go.mod h1:3WunclLLfrVAq7fYQRhd6f168FHOEMssnipVXCxDL/w=
github.com/AdguardTeam/urlfilter v0.17.3 h1:fg/ObbnO0Cv6aw0tW6N/ETDMhhNvmcUUOZ7HlmKC3rw=
github.com/AdguardTeam/urlfilter v0.17.3/go.mod h1:Jru7jFfeH2CoDf150uDs+rRYcZBzHHBz05r9REyDKyE=
github.com/AdguardTeam/dnsproxy v0.65.0 h1:mqJjVSkqoqPwThY3tTvnLHQ/AYBYrfWmK2ER91fu4FE=
github.com/AdguardTeam/dnsproxy v0.65.0/go.mod h1:AGYMLPk2zX+I3NIUYS12KUI296mkCyfoMF/luy2uqdk=
github.com/AdguardTeam/golibs v0.20.1 h1:ol8qLjWGZhU9paMMwN+OLWVTUigGsXa29iVTyd62VKY=
github.com/AdguardTeam/golibs v0.20.1/go.mod h1:bgcMgRviCKyU6mkrX+RtT/OsKPFzyppelfRsksMG3KU=
github.com/AdguardTeam/urlfilter v0.18.0 h1:ZZzwODC/ADpjJSODxySrrUnt/fvOCfGFaCW6j+wsGfQ=
github.com/AdguardTeam/urlfilter v0.18.0/go.mod h1:IXxBwedLiZA2viyHkaFxY/8mjub0li2PXRg8a3d9Z1s=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
@ -30,12 +30,15 @@ github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu9
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
@ -43,25 +46,27 @@ 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/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
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/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20240204152450-ca2dc33955c1 h1:L3pm9Kf2G6gJVYawz2SrI5QnV1wzHYbqmKnSHHXJAb8=
github.com/insomniacslk/dhcp v0.0.0-20240204152450-ca2dc33955c1/go.mod h1:izxuNQZeFrbx2nK2fAyN5iNUB34Fe9j0nK4PwLzAkKw=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og=
github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
@ -78,12 +83,13 @@ github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrG
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -92,16 +98,18 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
@ -110,32 +118,35 @@ github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x
github.com/ti-mo/netfilter v0.5.1 h1:cqamEd1c1zmpfpqvInLOro0Znq/RAfw2QL5wL2rAR/8=
github.com/ti-mo/netfilter v0.5.1/go.mod h1:h9UPQ3ZrTZGBitay+LETMxZvNgWGK/efTUcqES2YiLw=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/u-root/uio v0.0.0-20240207234124-abbebccef0fd h1:BQJh5fdHsPa/YuMVrbcSxQKuowGCHYh0GD7hvLaHBK0=
github.com/u-root/uio v0.0.0-20240207234124-abbebccef0fd/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
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.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo=
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
@ -149,10 +160,9 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20220622161953-175b2fd9d664/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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -160,15 +170,17 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
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=
gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0=
gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=

View file

@ -5,9 +5,9 @@ package aghalg
import (
"fmt"
"slices"
"golang.org/x/exp/constraints"
"golang.org/x/exp/slices"
)
// Coalesce returns the first non-zero value. It is named after function

View file

@ -1,11 +1,11 @@
package aghalg_test
import (
"slices"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/stretchr/testify/assert"
"golang.org/x/exp/slices"
)
// elements is a helper function that returns n elements of the buffer.

View file

@ -0,0 +1,86 @@
package aghalg
import (
"slices"
)
// SortedMap is a map that keeps elements in order with internal sorting
// function. Must be initialised by the [NewSortedMap].
type SortedMap[K comparable, V any] struct {
vals map[K]V
cmp func(a, b K) (res int)
keys []K
}
// NewSortedMap initializes the new instance of sorted map. cmp is a sort
// function to keep elements in order.
//
// TODO(s.chzhen): Use cmp.Compare in Go 1.21.
func NewSortedMap[K comparable, V any](cmp func(a, b K) (res int)) SortedMap[K, V] {
return SortedMap[K, V]{
vals: map[K]V{},
cmp: cmp,
}
}
// Set adds val with key to the sorted map. It panics if the m is nil.
func (m *SortedMap[K, V]) Set(key K, val V) {
m.vals[key] = val
i, has := slices.BinarySearchFunc(m.keys, key, m.cmp)
if has {
m.keys[i] = key
} else {
m.keys = slices.Insert(m.keys, i, key)
}
}
// Get returns val by key from the sorted map.
func (m *SortedMap[K, V]) Get(key K) (val V, ok bool) {
if m == nil {
return
}
val, ok = m.vals[key]
return val, ok
}
// Del removes the value by key from the sorted map.
func (m *SortedMap[K, V]) Del(key K) {
if m == nil {
return
}
if _, has := m.vals[key]; !has {
return
}
delete(m.vals, key)
i, _ := slices.BinarySearchFunc(m.keys, key, m.cmp)
m.keys = slices.Delete(m.keys, i, i+1)
}
// Clear removes all elements from the sorted map.
func (m *SortedMap[K, V]) Clear() {
if m == nil {
return
}
m.keys = nil
clear(m.vals)
}
// Range calls cb for each element of the map, sorted by m.cmp. If cb returns
// false it stops.
func (m *SortedMap[K, V]) Range(cb func(K, V) (cont bool)) {
if m == nil {
return
}
for _, k := range m.keys {
if !cb(k, m.vals[k]) {
return
}
}
}

View file

@ -0,0 +1,95 @@
package aghalg
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewSortedMap(t *testing.T) {
var m SortedMap[string, int]
letters := []string{}
for i := 0; i < 10; i++ {
r := string('a' + rune(i))
letters = append(letters, r)
}
t.Run("create_and_fill", func(t *testing.T) {
m = NewSortedMap[string, int](strings.Compare)
nums := []int{}
for i, r := range letters {
m.Set(r, i)
nums = append(nums, i)
}
gotLetters := []string{}
gotNums := []int{}
m.Range(func(k string, v int) bool {
gotLetters = append(gotLetters, k)
gotNums = append(gotNums, v)
return true
})
assert.Equal(t, letters, gotLetters)
assert.Equal(t, nums, gotNums)
n, ok := m.Get(letters[0])
assert.True(t, ok)
assert.Equal(t, nums[0], n)
})
t.Run("clear", func(t *testing.T) {
lastLetter := letters[len(letters)-1]
m.Del(lastLetter)
_, ok := m.Get(lastLetter)
assert.False(t, ok)
m.Clear()
gotLetters := []string{}
m.Range(func(k string, _ int) bool {
gotLetters = append(gotLetters, k)
return true
})
assert.Len(t, gotLetters, 0)
})
}
func TestNewSortedMap_nil(t *testing.T) {
const (
key = "key"
val = "val"
)
var m SortedMap[string, string]
assert.Panics(t, func() {
m.Set(key, val)
})
assert.NotPanics(t, func() {
_, ok := m.Get(key)
assert.False(t, ok)
})
assert.NotPanics(t, func() {
m.Range(func(_, _ string) (cont bool) {
return true
})
})
assert.NotPanics(t, func() {
m.Del(key)
})
assert.NotPanics(t, func() {
m.Clear()
})
}

View file

@ -154,8 +154,8 @@ func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error)
}
// handleEvents concurrently handles the file system events. It closes the
// update channel of HostsContainer when finishes. It's used to be called
// within a separate goroutine.
// update channel of HostsContainer when finishes. It is intended to be used as
// a goroutine.
func (hc *HostsContainer) handleEvents() {
defer log.OnPanic(fmt.Sprintf("%s: handling events", hostsContainerPrefix))

View file

@ -67,6 +67,7 @@ func TestNewHostsContainer(t *testing.T) {
}
hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: onEvents,
OnAdd: onAdd,
OnClose: func() (err error) { return nil },
@ -93,6 +94,7 @@ func TestNewHostsContainer(t *testing.T) {
t.Run("nil_fs", func(t *testing.T) {
require.Panics(t, func() {
_, _ = aghnet.NewHostsContainer(nil, &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
// Those shouldn't panic.
OnEvents: func() (e <-chan struct{}) { return nil },
OnAdd: func(name string) (err error) { return nil },
@ -111,6 +113,7 @@ func TestNewHostsContainer(t *testing.T) {
const errOnAdd errors.Error = "error"
errWatcher := &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: func() (e <-chan struct{}) { panic("not implemented") },
OnAdd: func(name string) (err error) { return errOnAdd },
OnClose: func() (err error) { return nil },
@ -155,6 +158,7 @@ func TestHostsContainer_refresh(t *testing.T) {
t.Cleanup(func() { close(eventsCh) })
w := &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: func() (e <-chan event) { return eventsCh },
OnAdd: func(name string) (err error) {
assert.Equal(t, "dir", name)

View file

@ -1,11 +1,11 @@
package aghnet
import (
"slices"
"strings"
"github.com/AdguardTeam/urlfilter"
"github.com/AdguardTeam/urlfilter/filterlist"
"golang.org/x/exp/slices"
)
// IgnoreEngine contains the list of rules for ignoring hostnames and matches

View file

@ -17,6 +17,7 @@ import (
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/osutil"
)
// DialContextFunc is the semantic alias for dialing functions, such as
@ -32,7 +33,7 @@ var (
netInterfaceAddrs = net.InterfaceAddrs
// rootDirFS is the filesystem pointing to the root directory.
rootDirFS = aghos.RootDirFS()
rootDirFS = osutil.RootDirFS()
)
// ErrNoStaticIPInfo is returned by IfaceHasStaticIP when no information about

View file

@ -8,6 +8,8 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/osutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/fsnotify/fsnotify"
)
@ -18,31 +20,38 @@ type event = struct{}
// FSWatcher tracks all the fyle system events and notifies about those.
//
// TODO(e.burkov, a.garipov): Move into another package like aghfs.
//
// TODO(e.burkov): Add tests.
type FSWatcher interface {
// Start starts watching the added files.
Start() (err error)
// Close stops watching the files and closes an update channel.
io.Closer
// Events should return a read-only channel which notifies about events.
// Events returns the channel to notify about the file system events.
Events() (e <-chan event)
// Add should check if the file named name is accessible and starts tracking
// it.
// Add starts tracking the file. It returns an error if the file can't be
// tracked. It must not be called after Start.
Add(name string) (err error)
}
// osWatcher tracks the file system provided by the OS.
type osWatcher struct {
// w is the actual notifier that is handled by osWatcher.
w *fsnotify.Watcher
// watcher is the actual notifier that is handled by osWatcher.
watcher *fsnotify.Watcher
// events is the channel to notify.
events chan event
// files is the set of tracked files.
files *stringutil.Set
}
const (
// osWatcherPref is a prefix for logging and wrapping errors in osWathcer's
// methods.
osWatcherPref = "os watcher"
)
// osWatcherPref is a prefix for logging and wrapping errors in osWathcer's
// methods.
const osWatcherPref = "os watcher"
// NewOSWritesWatcher creates FSWatcher that tracks the real file system of the
// OS and notifies only about writing events.
@ -55,25 +64,27 @@ func NewOSWritesWatcher() (w FSWatcher, err error) {
return nil, fmt.Errorf("creating watcher: %w", err)
}
fsw := &osWatcher{
w: watcher,
events: make(chan event, 1),
}
go fsw.handleErrors()
go fsw.handleEvents()
return fsw, nil
return &osWatcher{
watcher: watcher,
events: make(chan event, 1),
files: stringutil.NewSet(),
}, nil
}
// handleErrors handles accompanying errors. It used to be called in a separate
// goroutine.
func (w *osWatcher) handleErrors() {
defer log.OnPanic(fmt.Sprintf("%s: handling errors", osWatcherPref))
// type check
var _ FSWatcher = (*osWatcher)(nil)
for err := range w.w.Errors {
log.Error("%s: %s", osWatcherPref, err)
}
// Start implements the FSWatcher interface for *osWatcher.
func (w *osWatcher) Start() (err error) {
go w.handleErrors()
go w.handleEvents()
return nil
}
// Close implements the FSWatcher interface for *osWatcher.
func (w *osWatcher) Close() (err error) {
return w.watcher.Close()
}
// Events implements the FSWatcher interface for *osWatcher.
@ -81,34 +92,42 @@ func (w *osWatcher) Events() (e <-chan event) {
return w.events
}
// Add implements the FSWatcher interface for *osWatcher.
// Add implements the [FSWatcher] interface for *osWatcher.
//
// TODO(e.burkov): Make it accept non-existing files to detect it's creating.
func (w *osWatcher) Add(name string) (err error) {
defer func() { err = errors.Annotate(err, "%s: %w", osWatcherPref) }()
if _, err = fs.Stat(RootDirFS(), name); err != nil {
fi, err := fs.Stat(osutil.RootDirFS(), name)
if err != nil {
return fmt.Errorf("checking file %q: %w", name, err)
}
return w.w.Add(filepath.Join("/", name))
}
name = filepath.Join("/", name)
w.files.Add(name)
// Close implements the FSWatcher interface for *osWatcher.
func (w *osWatcher) Close() (err error) {
return w.w.Close()
// Watch the directory and filter the events by the file name, since the
// common recomendation to the fsnotify package is to watch the directory
// instead of the file itself.
//
// See https://pkg.go.dev/github.com/fsnotify/fsnotify@v1.7.0#readme-watching-a-file-doesn-t-work-well.
if !fi.IsDir() {
name = filepath.Dir(name)
}
return w.watcher.Add(name)
}
// handleEvents notifies about the received file system's event if needed. It
// used to be called in a separate goroutine.
// is intended to be used as a goroutine.
func (w *osWatcher) handleEvents() {
defer log.OnPanic(fmt.Sprintf("%s: handling events", osWatcherPref))
defer close(w.events)
ch := w.w.Events
ch := w.watcher.Events
for e := range ch {
if e.Op&fsnotify.Write == 0 {
if e.Op&fsnotify.Write == 0 || !w.files.Has(e.Name) {
continue
}
@ -131,3 +150,13 @@ func (w *osWatcher) handleEvents() {
}
}
}
// handleErrors handles accompanying errors. It used to be called in a separate
// goroutine.
func (w *osWatcher) handleErrors() {
defer log.OnPanic(fmt.Sprintf("%s: handling errors", osWatcherPref))
for err := range w.watcher.Errors {
log.Error("%s: %s", osWatcherPref, err)
}
}

View file

@ -7,18 +7,16 @@ import (
"bufio"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path"
"runtime"
"slices"
"strconv"
"strings"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
"golang.org/x/exp/slices"
)
// UnsupportedError is returned by functions and methods when a particular
@ -63,7 +61,7 @@ func RunCommand(command string, arguments ...string) (code int, output []byte, e
cmd := exec.Command(command, arguments...)
out, err := cmd.Output()
out = out[:mathutil.Min(len(out), MaxCmdOutputSize)]
out = out[:min(len(out), MaxCmdOutputSize)]
if err != nil {
if eerr := new(exec.ExitError); errors.As(err, &eerr) {
@ -142,7 +140,7 @@ func parsePSOutput(r io.Reader, cmdName string, ignore []int) (largest, instNum
}
instNum++
largest = mathutil.Max(largest, cur)
largest = max(largest, cur)
}
if err = s.Err(); err != nil {
return 0, 0, fmt.Errorf("scanning stdout: %w", err)
@ -156,13 +154,6 @@ func IsOpenWrt() (ok bool) {
return isOpenWrt()
}
// RootDirFS returns the [fs.FS] rooted at the operating system's root. On
// Windows it returns the fs.FS rooted at the volume of the system directory
// (usually, C:).
func RootDirFS() (fsys fs.FS) {
return rootDirFS()
}
// NotifyReconfigureSignal notifies c on receiving reconfigure signals.
func NotifyReconfigureSignal(c chan<- os.Signal) {
notifyReconfigureSignal(c)

View file

@ -7,6 +7,7 @@ import (
"os"
"syscall"
"github.com/AdguardTeam/golibs/osutil"
"github.com/AdguardTeam/golibs/stringutil"
)
@ -40,7 +41,7 @@ func isOpenWrt() (ok bool) {
}
return nil, !stringutil.ContainsFold(string(data), osNameData), nil
}).Walk(RootDirFS(), etcReleasePattern)
}).Walk(osutil.RootDirFS(), etcReleasePattern)
return err == nil && ok
}

View file

@ -3,17 +3,12 @@
package aghos
import (
"io/fs"
"os"
"os/signal"
"golang.org/x/sys/unix"
)
func rootDirFS() (fsys fs.FS) {
return os.DirFS("/")
}
func notifyReconfigureSignal(c chan<- os.Signal) {
signal.Notify(c, unix.SIGHUP)
}

View file

@ -3,29 +3,13 @@
package aghos
import (
"io/fs"
"os"
"os/signal"
"path/filepath"
"syscall"
"github.com/AdguardTeam/golibs/log"
"golang.org/x/sys/windows"
)
func rootDirFS() (fsys fs.FS) {
// TODO(a.garipov): Use a better way if golang/go#44279 is ever resolved.
sysDir, err := windows.GetSystemDirectory()
if err != nil {
log.Error("aghos: getting root filesystem: %s; using C:", err)
// Assume that C: is the safe default.
return os.DirFS("C:")
}
return os.DirFS(filepath.VolumeName(sysDir))
}
func setRlimit(val uint64) (err error) {
return Unsupported("setrlimit")
}

View file

@ -26,14 +26,25 @@ import (
// FSWatcher is a fake [aghos.FSWatcher] implementation for tests.
type FSWatcher struct {
OnStart func() (err error)
OnClose func() (err error)
OnEvents func() (e <-chan struct{})
OnAdd func(name string) (err error)
OnClose func() (err error)
}
// type check
var _ aghos.FSWatcher = (*FSWatcher)(nil)
// Start implements the [aghos.FSWatcher] interface for *FSWatcher.
func (w *FSWatcher) Start() (err error) {
return w.OnStart()
}
// Close implements the [aghos.FSWatcher] interface for *FSWatcher.
func (w *FSWatcher) Close() (err error) {
return w.OnClose()
}
// Events implements the [aghos.FSWatcher] interface for *FSWatcher.
func (w *FSWatcher) Events() (e <-chan struct{}) {
return w.OnEvents()
@ -44,11 +55,6 @@ func (w *FSWatcher) Add(name string) (err error) {
return w.OnAdd(name)
}
// Close implements the [aghos.FSWatcher] interface for *FSWatcher.
func (w *FSWatcher) Close() (err error) {
return w.OnClose()
}
// Package agh
// ServiceWithConfig is a fake [agh.ServiceWithConfig] implementation for tests.

View file

@ -7,13 +7,14 @@ import (
"fmt"
"net"
"net/netip"
"slices"
"sync"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"golang.org/x/exp/slices"
"github.com/AdguardTeam/golibs/osutil"
)
// Variables and functions to substitute in tests.
@ -22,7 +23,7 @@ var (
aghosRunCommand = aghos.RunCommand
// rootDirFS is the filesystem pointing to the root directory.
rootDirFS = aghos.RootDirFS()
rootDirFS = osutil.RootDirFS()
)
// Interface stores and refreshes the network neighborhood reported by ARP

View file

@ -8,6 +8,7 @@ import (
"net"
"net/netip"
"os"
"slices"
"strings"
"time"
@ -15,7 +16,6 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/google/renameio/v2/maybe"
"golang.org/x/exp/slices"
)
const (

View file

@ -6,6 +6,7 @@ import (
"net"
"net/netip"
"path/filepath"
"slices"
"testing"
"time"
@ -13,7 +14,6 @@ import (
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/exp/slices"
)
func TestMain(m *testing.M) {

View file

@ -10,6 +10,7 @@ import (
"net/http"
"net/netip"
"os"
"slices"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
@ -19,7 +20,6 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"golang.org/x/exp/slices"
)
type v4ServerConfJSON struct {
@ -592,7 +592,7 @@ func setOtherDHCPResult(ifaceName string, result *dhcpSearchResult) {
}
// parseLease parses a lease from r. If there is no error returns DHCPServer
// and *Lease. r must be non-nil.
// and *Lease. r must be non-nil.
func (s *server) parseLease(r io.Reader) (srv DHCPServer, lease *dhcpsvc.Lease, err error) {
l := &leaseStatic{}
err = json.NewDecoder(r).Decode(l)

View file

@ -7,6 +7,7 @@ import (
"fmt"
"net"
"net/netip"
"slices"
"strings"
"sync"
"time"
@ -20,7 +21,6 @@ import (
"github.com/go-ping/ping"
"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/dhcpv4/server4"
"golang.org/x/exp/slices"
)
// v4Server is a DHCPv4 server.

View file

@ -2,11 +2,11 @@ package dhcpsvc
import (
"fmt"
"slices"
"time"
"github.com/AdguardTeam/golibs/netutil"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
// Config is the configuration for the DHCP service.

View file

@ -14,7 +14,9 @@ import (
// Interface is a DHCP service.
//
// TODO(e.burkov): Separate HostByIP, MACByIP, IPByHost into a separate
// interface. This is also valid for Enabled method.
// interface. This is also applicable to Enabled method.
//
// TODO(e.burkov): Reconsider the requirements for the leases validity.
type Interface interface {
agh.ServiceWithConfig[*Config]
@ -29,6 +31,8 @@ type Interface interface {
// MACByIP returns the MAC address for the given IP address leased. It
// returns nil if there is no such client, due to an assumption that a DHCP
// client must always have a MAC address.
//
// TODO(e.burkov): Think of a contract for the returned value.
MACByIP(ip netip.Addr) (mac net.HardwareAddr)
// IPByHost returns the IP address of the DHCP client with the given
@ -44,17 +48,17 @@ type Interface interface {
// signatures instead of cloning the whole list.
Leases() (ls []*Lease)
// AddLease adds a new DHCP lease. It returns an error if the lease is
// invalid or already exists.
// AddLease adds a new DHCP lease. l must be valid. It returns an error if
// l already exists.
AddLease(l *Lease) (err error)
// UpdateStaticLease changes an existing DHCP lease. It returns an error if
// there is no lease with such hardware addressor if new values are invalid
// or already exist.
// UpdateStaticLease replaces an existing static DHCP lease. l must be
// valid. It returns an error if the lease with the given hardware address
// doesn't exist or if other values match another existing lease.
UpdateStaticLease(l *Lease) (err error)
// RemoveLease removes an existing DHCP lease. It returns an error if there
// is no lease equal to l.
// RemoveLease removes an existing DHCP lease. l must be valid. It returns
// an error if there is no lease equal to l.
RemoveLease(l *Lease) (err error)
// Reset removes all the DHCP leases.

View file

@ -2,9 +2,8 @@ package dhcpsvc
import (
"fmt"
"slices"
"time"
"golang.org/x/exp/slices"
)
// netInterface is a common part of any network interface within the DHCP
@ -39,3 +38,29 @@ func (iface *netInterface) insertLease(l *Lease) (err error) {
return nil
}
// updateLease replaces an existing lease within iface with the given one. It
// returns an error if there is no lease with such hardware address.
func (iface *netInterface) updateLease(l *Lease) (prev *Lease, err error) {
i, found := slices.BinarySearchFunc(iface.leases, l, compareLeaseMAC)
if !found {
return nil, fmt.Errorf("no lease for mac %s", l.HWAddr)
}
prev, iface.leases[i] = iface.leases[i], l
return prev, nil
}
// removeLease removes an existing lease from iface. It returns an error if
// there is no lease equal to l.
func (iface *netInterface) removeLease(l *Lease) (err error) {
i, found := slices.BinarySearchFunc(iface.leases, l, compareLeaseMAC)
if !found {
return fmt.Errorf("no lease for mac %s", l.HWAddr)
}
iface.leases = slices.Delete(iface.leases, i, i+1)
return nil
}

View file

@ -4,9 +4,8 @@ import (
"bytes"
"net"
"net/netip"
"slices"
"time"
"golang.org/x/exp/slices"
)
// Lease is a DHCP lease.

View file

@ -0,0 +1,126 @@
package dhcpsvc
import (
"fmt"
"net/netip"
"slices"
"strings"
)
// leaseIndex is the set of leases indexed by their identifiers for quick
// lookup.
type leaseIndex struct {
// byAddr is a lookup shortcut for leases by their IP addresses.
byAddr map[netip.Addr]*Lease
// byName is a lookup shortcut for leases by their hostnames.
//
// TODO(e.burkov): Use a slice of leases with the same hostname?
byName map[string]*Lease
}
// newLeaseIndex returns a new index for [Lease]s.
func newLeaseIndex() *leaseIndex {
return &leaseIndex{
byAddr: map[netip.Addr]*Lease{},
byName: map[string]*Lease{},
}
}
// leaseByAddr returns a lease by its IP address.
func (idx *leaseIndex) leaseByAddr(addr netip.Addr) (l *Lease, ok bool) {
l, ok = idx.byAddr[addr]
return l, ok
}
// leaseByName returns a lease by its hostname.
func (idx *leaseIndex) leaseByName(name string) (l *Lease, ok bool) {
// TODO(e.burkov): Probably, use a case-insensitive comparison and store in
// slice. This would require a benchmark.
l, ok = idx.byName[strings.ToLower(name)]
return l, ok
}
// clear removes all leases from idx.
func (idx *leaseIndex) clear() {
clear(idx.byAddr)
clear(idx.byName)
}
// add adds l into idx and into iface. l must be valid, iface should be
// responsible for l's IP. It returns an error if l duplicates at least a
// single value of another lease.
func (idx *leaseIndex) add(l *Lease, iface *netInterface) (err error) {
loweredName := strings.ToLower(l.Hostname)
if _, ok := idx.byAddr[l.IP]; ok {
return fmt.Errorf("lease for ip %s already exists", l.IP)
} else if _, ok = idx.byName[loweredName]; ok {
return fmt.Errorf("lease for hostname %s already exists", l.Hostname)
}
err = iface.insertLease(l)
if err != nil {
return err
}
idx.byAddr[l.IP] = l
idx.byName[loweredName] = l
return nil
}
// remove removes l from idx and from iface. l must be valid, iface should
// contain the same lease or the lease itself. It returns an error if the lease
// not found.
func (idx *leaseIndex) remove(l *Lease, iface *netInterface) (err error) {
loweredName := strings.ToLower(l.Hostname)
if _, ok := idx.byAddr[l.IP]; !ok {
return fmt.Errorf("no lease for ip %s", l.IP)
} else if _, ok = idx.byName[loweredName]; !ok {
return fmt.Errorf("no lease for hostname %s", l.Hostname)
}
err = iface.removeLease(l)
if err != nil {
return err
}
delete(idx.byAddr, l.IP)
delete(idx.byName, loweredName)
return nil
}
// update updates l in idx and in iface. l must be valid, iface should be
// responsible for l's IP. It returns an error if l duplicates at least a
// single value of another lease, except for the updated lease itself.
func (idx *leaseIndex) update(l *Lease, iface *netInterface) (err error) {
loweredName := strings.ToLower(l.Hostname)
existing, ok := idx.byAddr[l.IP]
if ok && !slices.Equal(l.HWAddr, existing.HWAddr) {
return fmt.Errorf("lease for ip %s already exists", l.IP)
}
existing, ok = idx.byName[loweredName]
if ok && !slices.Equal(l.HWAddr, existing.HWAddr) {
return fmt.Errorf("lease for hostname %s already exists", l.Hostname)
}
prev, err := iface.updateLease(l)
if err != nil {
return err
}
delete(idx.byAddr, prev.IP)
delete(idx.byName, strings.ToLower(prev.Hostname))
idx.byAddr[l.IP] = l
idx.byName[loweredName] = l
return nil
}

View file

@ -4,13 +4,13 @@ import (
"fmt"
"net"
"net/netip"
"strings"
"slices"
"sync"
"sync/atomic"
"time"
"github.com/AdguardTeam/golibs/errors"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
// DHCPServer is a DHCP server for both IPv4 and IPv6 address families.
@ -23,17 +23,11 @@ type DHCPServer struct {
// hostnames.
localTLD string
// leasesMu protects the ipIndex and nameIndex fields against concurrent
// access, as well as leaseHandlers within the interfaces.
// leasesMu protects the leases index as well as leases in the interfaces.
leasesMu *sync.RWMutex
// leaseByIP is a lookup shortcut for leases by their IP addresses.
leaseByIP map[netip.Addr]*Lease
// leaseByName is a lookup shortcut for leases by their hostnames.
//
// TODO(e.burkov): Use a slice of leases with the same hostname?
leaseByName map[string]*Lease
// leases stores the DHCP leases for quick lookups.
leases *leaseIndex
// interfaces4 is the set of IPv4 interfaces sorted by interface name.
interfaces4 netInterfacesV4
@ -88,8 +82,7 @@ func New(conf *Config) (srv *DHCPServer, err error) {
enabled: enabled,
localTLD: conf.LocalDomainName,
leasesMu: &sync.RWMutex{},
leaseByIP: map[netip.Addr]*Lease{},
leaseByName: map[string]*Lease{},
leases: newLeaseIndex(),
interfaces4: ifaces4,
interfaces6: ifaces6,
icmpTimeout: conf.ICMPTimeout,
@ -120,6 +113,11 @@ func (srv *DHCPServer) Leases() (leases []*Lease) {
leases = append(leases, lease.Clone())
}
}
for _, iface := range srv.interfaces6 {
for _, lease := range iface.leases {
leases = append(leases, lease.Clone())
}
}
return leases
}
@ -129,7 +127,7 @@ func (srv *DHCPServer) HostByIP(ip netip.Addr) (host string) {
srv.leasesMu.RLock()
defer srv.leasesMu.RUnlock()
if l, ok := srv.leaseByIP[ip]; ok {
if l, ok := srv.leases.leaseByAddr(ip); ok {
return l.Hostname
}
@ -141,7 +139,7 @@ func (srv *DHCPServer) MACByIP(ip netip.Addr) (mac net.HardwareAddr) {
srv.leasesMu.RLock()
defer srv.leasesMu.RUnlock()
if l, ok := srv.leaseByIP[ip]; ok {
if l, ok := srv.leases.leaseByAddr(ip); ok {
return l.HWAddr
}
@ -150,12 +148,10 @@ func (srv *DHCPServer) MACByIP(ip netip.Addr) (mac net.HardwareAddr) {
// IPByHost implements the [Interface] interface for *DHCPServer.
func (srv *DHCPServer) IPByHost(host string) (ip netip.Addr) {
lowered := strings.ToLower(host)
srv.leasesMu.RLock()
defer srv.leasesMu.RUnlock()
if l, ok := srv.leaseByName[lowered]; ok {
if l, ok := srv.leases.leaseByName(host); ok {
return l.IP
}
@ -173,39 +169,76 @@ func (srv *DHCPServer) Reset() (err error) {
for _, iface := range srv.interfaces6 {
iface.reset()
}
maps.Clear(srv.leaseByIP)
maps.Clear(srv.leaseByName)
srv.leases.clear()
return nil
}
// AddLease implements the [Interface] interface for *DHCPServer.
func (srv *DHCPServer) AddLease(l *Lease) (err error) {
var ok bool
var iface *netInterface
defer func() { err = errors.Annotate(err, "adding lease: %w") }()
addr := l.IP
iface, err := srv.ifaceForAddr(addr)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
srv.leasesMu.Lock()
defer srv.leasesMu.Unlock()
return srv.leases.add(l, iface)
}
// UpdateStaticLease implements the [Interface] interface for *DHCPServer.
//
// TODO(e.burkov): Support moving leases between interfaces.
func (srv *DHCPServer) UpdateStaticLease(l *Lease) (err error) {
defer func() { err = errors.Annotate(err, "updating static lease: %w") }()
addr := l.IP
iface, err := srv.ifaceForAddr(addr)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
srv.leasesMu.Lock()
defer srv.leasesMu.Unlock()
return srv.leases.update(l, iface)
}
// RemoveLease implements the [Interface] interface for *DHCPServer.
func (srv *DHCPServer) RemoveLease(l *Lease) (err error) {
defer func() { err = errors.Annotate(err, "removing lease: %w") }()
addr := l.IP
iface, err := srv.ifaceForAddr(addr)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
srv.leasesMu.Lock()
defer srv.leasesMu.Unlock()
return srv.leases.remove(l, iface)
}
// ifaceForAddr returns the handled network interface for the given IP address,
// or an error if no such interface exists.
func (srv *DHCPServer) ifaceForAddr(addr netip.Addr) (iface *netInterface, err error) {
var ok bool
if addr.Is4() {
iface, ok = srv.interfaces4.find(addr)
} else {
iface, ok = srv.interfaces6.find(addr)
}
if !ok {
return fmt.Errorf("no interface for IP address %s", addr)
return nil, fmt.Errorf("no interface for ip %s", addr)
}
srv.leasesMu.Lock()
defer srv.leasesMu.Unlock()
err = iface.insertLease(l)
if err != nil {
return err
}
srv.leaseByIP[l.IP] = l
srv.leaseByName[strings.ToLower(l.Hostname)] = l
return nil
return iface, nil
}

View file

@ -3,6 +3,7 @@ package dhcpsvc_test
import (
"net"
"net/netip"
"strings"
"testing"
"time"
@ -15,6 +16,52 @@ import (
// testLocalTLD is a common local TLD for tests.
const testLocalTLD = "local"
// testInterfaceConf is a common set of interface configurations for tests.
var testInterfaceConf = map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
IPv4: &dhcpsvc.IPv4Config{
Enabled: true,
GatewayIP: netip.MustParseAddr("192.168.0.1"),
SubnetMask: netip.MustParseAddr("255.255.255.0"),
RangeStart: netip.MustParseAddr("192.168.0.2"),
RangeEnd: netip.MustParseAddr("192.168.0.254"),
LeaseDuration: 1 * time.Hour,
},
IPv6: &dhcpsvc.IPv6Config{
Enabled: true,
RangeStart: netip.MustParseAddr("2001:db8::1"),
LeaseDuration: 1 * time.Hour,
RAAllowSLAAC: true,
RASLAACOnly: true,
},
},
"eth1": {
IPv4: &dhcpsvc.IPv4Config{
Enabled: true,
GatewayIP: netip.MustParseAddr("172.16.0.1"),
SubnetMask: netip.MustParseAddr("255.255.255.0"),
RangeStart: netip.MustParseAddr("172.16.0.2"),
RangeEnd: netip.MustParseAddr("172.16.0.255"),
LeaseDuration: 1 * time.Hour,
},
IPv6: &dhcpsvc.IPv6Config{
Enabled: true,
RangeStart: netip.MustParseAddr("2001:db9::1"),
LeaseDuration: 1 * time.Hour,
RAAllowSLAAC: true,
RASLAACOnly: true,
},
},
}
// mustParseMAC parses a hardware address from s and requires no errors.
func mustParseMAC(t require.TestingT, s string) (mac net.HardwareAddr) {
mac, err := net.ParseMAC(s)
require.NoError(t, err)
return mac
}
func TestNew(t *testing.T) {
validIPv4Conf := &dhcpsvc.IPv4Config{
Enabled: true,
@ -117,46 +164,113 @@ func TestNew(t *testing.T) {
}
}
func TestDHCPServer_AddLease(t *testing.T) {
srv, err := dhcpsvc.New(&dhcpsvc.Config{
Enabled: true,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
})
require.NoError(t, err)
const (
host1 = "host1"
host2 = "host2"
host3 = "host3"
)
ip1 := netip.MustParseAddr("192.168.0.2")
ip2 := netip.MustParseAddr("192.168.0.3")
ip3 := netip.MustParseAddr("2001:db8::2")
mac1 := mustParseMAC(t, "01:02:03:04:05:06")
mac2 := mustParseMAC(t, "06:05:04:03:02:01")
mac3 := mustParseMAC(t, "02:03:04:05:06:07")
require.NoError(t, srv.AddLease(&dhcpsvc.Lease{
Hostname: host1,
IP: ip1,
HWAddr: mac1,
IsStatic: true,
}))
testCases := []struct {
name string
lease *dhcpsvc.Lease
wantErrMsg string
}{{
name: "outside_range",
lease: &dhcpsvc.Lease{
Hostname: host2,
IP: netip.MustParseAddr("1.2.3.4"),
HWAddr: mac2,
},
wantErrMsg: "adding lease: no interface for ip 1.2.3.4",
}, {
name: "duplicate_ip",
lease: &dhcpsvc.Lease{
Hostname: host2,
IP: ip1,
HWAddr: mac2,
},
wantErrMsg: "adding lease: lease for ip " + ip1.String() +
" already exists",
}, {
name: "duplicate_hostname",
lease: &dhcpsvc.Lease{
Hostname: host1,
IP: ip2,
HWAddr: mac2,
},
wantErrMsg: "adding lease: lease for hostname " + host1 +
" already exists",
}, {
name: "duplicate_hostname_case",
lease: &dhcpsvc.Lease{
Hostname: strings.ToUpper(host1),
IP: ip2,
HWAddr: mac2,
},
wantErrMsg: "adding lease: lease for hostname " +
strings.ToUpper(host1) + " already exists",
}, {
name: "duplicate_mac",
lease: &dhcpsvc.Lease{
Hostname: host2,
IP: ip2,
HWAddr: mac1,
},
wantErrMsg: "adding lease: lease for mac " + mac1.String() +
" already exists",
}, {
name: "valid",
lease: &dhcpsvc.Lease{
Hostname: host2,
IP: ip2,
HWAddr: mac2,
},
wantErrMsg: "",
}, {
name: "valid_v6",
lease: &dhcpsvc.Lease{
Hostname: host3,
IP: ip3,
HWAddr: mac3,
},
wantErrMsg: "",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.AddLease(tc.lease))
})
}
}
func TestDHCPServer_index(t *testing.T) {
srv, err := dhcpsvc.New(&dhcpsvc.Config{
Enabled: true,
LocalDomainName: testLocalTLD,
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
IPv4: &dhcpsvc.IPv4Config{
Enabled: true,
GatewayIP: netip.MustParseAddr("192.168.0.1"),
SubnetMask: netip.MustParseAddr("255.255.255.0"),
RangeStart: netip.MustParseAddr("192.168.0.2"),
RangeEnd: netip.MustParseAddr("192.168.0.254"),
LeaseDuration: 1 * time.Hour,
},
IPv6: &dhcpsvc.IPv6Config{
Enabled: true,
RangeStart: netip.MustParseAddr("2001:db8::1"),
LeaseDuration: 1 * time.Hour,
RAAllowSLAAC: true,
RASLAACOnly: true,
},
},
"eth1": {
IPv4: &dhcpsvc.IPv4Config{
Enabled: true,
GatewayIP: netip.MustParseAddr("172.16.0.1"),
SubnetMask: netip.MustParseAddr("255.255.255.0"),
RangeStart: netip.MustParseAddr("172.16.0.2"),
RangeEnd: netip.MustParseAddr("172.16.0.255"),
LeaseDuration: 1 * time.Hour,
},
IPv6: &dhcpsvc.IPv6Config{
Enabled: true,
RangeStart: netip.MustParseAddr("2001:db9::1"),
LeaseDuration: 1 * time.Hour,
RAAllowSLAAC: true,
RASLAACOnly: true,
},
},
},
Interfaces: testInterfaceConf,
})
require.NoError(t, err)
@ -173,9 +287,9 @@ func TestDHCPServer_index(t *testing.T) {
ip3 := netip.MustParseAddr("172.16.0.3")
ip4 := netip.MustParseAddr("172.16.0.4")
mac1 := net.HardwareAddr{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}
mac2 := net.HardwareAddr{0x06, 0x05, 0x04, 0x03, 0x02, 0x01}
mac3 := net.HardwareAddr{0x05, 0x04, 0x03, 0x02, 0x01, 0x00}
mac1 := mustParseMAC(t, "01:02:03:04:05:06")
mac2 := mustParseMAC(t, "06:05:04:03:02:01")
mac3 := mustParseMAC(t, "02:03:04:05:06:07")
leases := []*dhcpsvc.Lease{{
Hostname: host1,
@ -226,3 +340,256 @@ func TestDHCPServer_index(t *testing.T) {
assert.Nil(t, srv.MACByIP(netip.Addr{}))
})
}
func TestDHCPServer_UpdateStaticLease(t *testing.T) {
srv, err := dhcpsvc.New(&dhcpsvc.Config{
Enabled: true,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
})
require.NoError(t, err)
const (
host1 = "host1"
host2 = "host2"
host3 = "host3"
host4 = "host4"
host5 = "host5"
host6 = "host6"
)
ip1 := netip.MustParseAddr("192.168.0.2")
ip2 := netip.MustParseAddr("192.168.0.3")
ip3 := netip.MustParseAddr("192.168.0.4")
ip4 := netip.MustParseAddr("2001:db8::2")
ip5 := netip.MustParseAddr("2001:db8::3")
mac1 := mustParseMAC(t, "01:02:03:04:05:06")
mac2 := mustParseMAC(t, "01:02:03:04:05:07")
mac3 := mustParseMAC(t, "06:05:04:03:02:01")
mac4 := mustParseMAC(t, "06:05:04:03:02:02")
leases := []*dhcpsvc.Lease{{
Hostname: host1,
IP: ip1,
HWAddr: mac1,
IsStatic: true,
}, {
Hostname: host2,
IP: ip2,
HWAddr: mac2,
IsStatic: true,
}, {
Hostname: host4,
IP: ip4,
HWAddr: mac4,
IsStatic: true,
}}
for _, l := range leases {
require.NoError(t, srv.AddLease(l))
}
testCases := []struct {
name string
lease *dhcpsvc.Lease
wantErrMsg string
}{{
name: "outside_range",
lease: &dhcpsvc.Lease{
Hostname: host1,
IP: netip.MustParseAddr("1.2.3.4"),
HWAddr: mac1,
},
wantErrMsg: "updating static lease: no interface for ip 1.2.3.4",
}, {
name: "not_found",
lease: &dhcpsvc.Lease{
Hostname: host3,
IP: ip3,
HWAddr: mac3,
},
wantErrMsg: "updating static lease: no lease for mac " + mac3.String(),
}, {
name: "duplicate_ip",
lease: &dhcpsvc.Lease{
Hostname: host1,
IP: ip2,
HWAddr: mac1,
},
wantErrMsg: "updating static lease: lease for ip " + ip2.String() +
" already exists",
}, {
name: "duplicate_hostname",
lease: &dhcpsvc.Lease{
Hostname: host2,
IP: ip1,
HWAddr: mac1,
},
wantErrMsg: "updating static lease: lease for hostname " + host2 +
" already exists",
}, {
name: "duplicate_hostname_case",
lease: &dhcpsvc.Lease{
Hostname: strings.ToUpper(host2),
IP: ip1,
HWAddr: mac1,
},
wantErrMsg: "updating static lease: lease for hostname " +
strings.ToUpper(host2) + " already exists",
}, {
name: "valid",
lease: &dhcpsvc.Lease{
Hostname: host3,
IP: ip3,
HWAddr: mac1,
},
wantErrMsg: "",
}, {
name: "valid_v6",
lease: &dhcpsvc.Lease{
Hostname: host6,
IP: ip5,
HWAddr: mac4,
},
wantErrMsg: "",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.UpdateStaticLease(tc.lease))
})
}
}
func TestDHCPServer_RemoveLease(t *testing.T) {
srv, err := dhcpsvc.New(&dhcpsvc.Config{
Enabled: true,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
})
require.NoError(t, err)
const (
host1 = "host1"
host2 = "host2"
host3 = "host3"
)
ip1 := netip.MustParseAddr("192.168.0.2")
ip2 := netip.MustParseAddr("192.168.0.3")
ip3 := netip.MustParseAddr("2001:db8::2")
mac1 := mustParseMAC(t, "01:02:03:04:05:06")
mac2 := mustParseMAC(t, "02:03:04:05:06:07")
mac3 := mustParseMAC(t, "06:05:04:03:02:01")
leases := []*dhcpsvc.Lease{{
Hostname: host1,
IP: ip1,
HWAddr: mac1,
IsStatic: true,
}, {
Hostname: host3,
IP: ip3,
HWAddr: mac3,
IsStatic: true,
}}
for _, l := range leases {
require.NoError(t, srv.AddLease(l))
}
testCases := []struct {
name string
lease *dhcpsvc.Lease
wantErrMsg string
}{{
name: "not_found_mac",
lease: &dhcpsvc.Lease{
Hostname: host1,
IP: ip1,
HWAddr: mac2,
},
wantErrMsg: "removing lease: no lease for mac " + mac2.String(),
}, {
name: "not_found_ip",
lease: &dhcpsvc.Lease{
Hostname: host1,
IP: ip2,
HWAddr: mac1,
},
wantErrMsg: "removing lease: no lease for ip " + ip2.String(),
}, {
name: "not_found_host",
lease: &dhcpsvc.Lease{
Hostname: host2,
IP: ip1,
HWAddr: mac1,
},
wantErrMsg: "removing lease: no lease for hostname " + host2,
}, {
name: "valid",
lease: &dhcpsvc.Lease{
Hostname: host1,
IP: ip1,
HWAddr: mac1,
},
wantErrMsg: "",
}, {
name: "valid_v6",
lease: &dhcpsvc.Lease{
Hostname: host3,
IP: ip3,
HWAddr: mac3,
},
wantErrMsg: "",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.RemoveLease(tc.lease))
})
}
assert.Empty(t, srv.Leases())
}
func TestDHCPServer_Reset(t *testing.T) {
srv, err := dhcpsvc.New(&dhcpsvc.Config{
Enabled: true,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
})
require.NoError(t, err)
leases := []*dhcpsvc.Lease{{
Hostname: "host1",
IP: netip.MustParseAddr("192.168.0.2"),
HWAddr: mustParseMAC(t, "01:02:03:04:05:06"),
IsStatic: true,
}, {
Hostname: "host2",
IP: netip.MustParseAddr("192.168.0.3"),
HWAddr: mustParseMAC(t, "06:05:04:03:02:01"),
IsStatic: true,
}, {
Hostname: "host3",
IP: netip.MustParseAddr("2001:db8::2"),
HWAddr: mustParseMAC(t, "02:03:04:05:06:07"),
IsStatic: true,
}, {
Hostname: "host4",
IP: netip.MustParseAddr("2001:db8::3"),
HWAddr: mustParseMAC(t, "06:05:04:03:02:02"),
IsStatic: true,
}}
for _, l := range leases {
require.NoError(t, srv.AddLease(l))
}
require.Len(t, srv.Leases(), len(leases))
require.NoError(t, srv.Reset())
assert.Empty(t, srv.Leases())
}

View file

@ -4,12 +4,12 @@ import (
"fmt"
"net"
"net/netip"
"slices"
"time"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/google/gopacket/layers"
"golang.org/x/exp/slices"
)
// IPv4Config is the interface-specific configuration for DHCPv4.

View file

@ -3,12 +3,12 @@ package dhcpsvc
import (
"fmt"
"net/netip"
"slices"
"time"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/google/gopacket/layers"
"golang.org/x/exp/slices"
)
// IPv6Config is the interface-specific configuration for DHCPv6.
@ -150,7 +150,7 @@ func (ifaces netInterfacesV6) find(ip netip.Addr) (iface6 *netInterface, ok bool
const prefLen = netutil.IPv6BitLen - 8
i := slices.IndexFunc(ifaces, func(iface *netInterfaceV6) (contains bool) {
return !iface.rangeStart.Less(ip) &&
return !ip.Less(iface.rangeStart) &&
netip.PrefixFrom(iface.rangeStart, prefLen).Contains(ip)
})
if i < 0 {

View file

@ -7,6 +7,7 @@ import (
"net"
"net/netip"
"os"
"slices"
"strings"
"time"
@ -24,7 +25,6 @@ import (
"github.com/AdguardTeam/golibs/stringutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/ameshkov/dnscrypt/v2"
"golang.org/x/exp/slices"
)
// ClientsContainer provides information about preconfigured DNS clients.
@ -40,7 +40,7 @@ type ClientsContainer interface {
) (conf *proxy.CustomUpstreamConfig, err error)
}
// Config represents the DNS filtering configuration of AdGuard Home. The zero
// Config represents the DNS filtering configuration of AdGuard Home. The zero
// Config is empty and ready for use.
type Config struct {
// Callbacks for other modules

View file

@ -1,10 +1,10 @@
package dnsforward
import (
"slices"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/exp/slices"
)
func TestAnyNameMatches(t *testing.T) {

View file

@ -9,6 +9,7 @@ import (
"net/http"
"net/netip"
"runtime"
"slices"
"strings"
"sync"
"sync/atomic"
@ -30,7 +31,6 @@ import (
"github.com/AdguardTeam/golibs/netutil/sysresolv"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// DefaultTimeout is the default upstream timeout

View file

@ -1330,6 +1330,7 @@ func TestPTRResponseFromHosts(t *testing.T) {
var eventsCalledCounter uint32
hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: func() (e <-chan struct{}) {
assert.Equal(t, uint32(1), atomic.AddUint32(&eventsCalledCounter, 1))

View file

@ -4,6 +4,7 @@ import (
"encoding/binary"
"fmt"
"net"
"slices"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
@ -12,7 +13,6 @@ import (
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// beforeRequestHandler is the handler that is called before any other

View file

@ -6,6 +6,7 @@ import (
"io"
"net/http"
"net/netip"
"slices"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
@ -16,7 +17,6 @@ import (
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"golang.org/x/exp/slices"
)
// jsonDNSConfig is the JSON representation of the DNS server configuration.

View file

@ -418,6 +418,7 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
},
},
&aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: func() (e <-chan struct{}) { return nil },
OnAdd: func(_ string) (err error) { return nil },
OnClose: func() (err error) { return nil },

View file

@ -2,13 +2,13 @@ package dnsforward
import (
"net/netip"
"slices"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// makeResponse creates a DNS response by req and sets necessary flags. It also

View file

@ -4,6 +4,7 @@ import (
"fmt"
"net/netip"
"os"
"slices"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
@ -14,7 +15,6 @@ import (
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
// loadUpstreams parses upstream DNS servers from the configured file or from

View file

@ -4,13 +4,13 @@ import (
"encoding/json"
"fmt"
"net/http"
"slices"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"golang.org/x/exp/slices"
)
// serviceRules maps a service ID to its filtering rules.

View file

@ -6,6 +6,7 @@ import (
"net/http"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"time"
@ -15,7 +16,6 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
"golang.org/x/exp/slices"
)
// filterDir is the subdirectory of a data directory to store downloaded

View file

@ -12,6 +12,7 @@ import (
"path/filepath"
"runtime"
"runtime/debug"
"slices"
"strings"
"sync"
"sync/atomic"
@ -29,7 +30,6 @@ import (
"github.com/AdguardTeam/urlfilter/filterlist"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// The IDs of built-in filter lists.
@ -1113,8 +1113,7 @@ func (d *DNSFilter) periodicallyRefreshFilters(ivl time.Duration) (nextIvl time.
ivl = maxInterval
} else if isNetErr {
ivl *= 2
// TODO(s.chzhen): Use built-in function max in Go 1.21.
ivl = mathutil.Max(ivl, maxInterval)
ivl = max(ivl, maxInterval)
}
return ivl

View file

@ -5,6 +5,7 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"slices"
"strings"
"time"
@ -14,7 +15,6 @@ import (
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
"golang.org/x/net/publicsuffix"
)

View file

@ -3,6 +3,7 @@ package hashprefix
import (
"crypto/sha256"
"encoding/hex"
"slices"
"strings"
"testing"
"time"
@ -12,7 +13,6 @@ import (
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/exp/slices"
)
const (

View file

@ -40,6 +40,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
},
}
watcher := &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: func() (e <-chan struct{}) { return nil },
OnAdd: func(name string) (err error) { return nil },
OnClose: func() (err error) { return nil },

View file

@ -8,6 +8,7 @@ import (
"net/url"
"os"
"path/filepath"
"slices"
"sync"
"time"
@ -15,7 +16,6 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// validateFilterURL validates the filter list URL or file name.

View file

@ -3,6 +3,7 @@ package rewrite
import (
"fmt"
"slices"
"strings"
"sync"
@ -12,7 +13,6 @@ import (
"github.com/AdguardTeam/urlfilter/filterlist"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// Storage is a storage for rewrite rules.

View file

@ -3,10 +3,10 @@ package filtering
import (
"encoding/json"
"net/http"
"slices"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/log"
"golang.org/x/exp/slices"
)
// TODO(d.kolyshev): Use [rewrite.Item] instead.

View file

@ -3,13 +3,12 @@ package filtering
import (
"fmt"
"net/netip"
"slices"
"strings"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
// Legacy DNS rewrites
@ -181,7 +180,7 @@ func findRewrites(
if isWildcard(r.Domain) {
// Don't use rewrites[:0], because we need to return at least one
// item here.
rewrites = rewrites[:mathutil.Max(1, i)]
rewrites = rewrites[:max(1, i)]
break
}

View file

@ -6,9 +6,9 @@ import (
"fmt"
"hash/crc32"
"io"
"slices"
"github.com/AdguardTeam/golibs/errors"
"golang.org/x/exp/slices"
)
// Parser is a filtering-rule parser that collects data, such as the checksum

View file

@ -1927,6 +1927,14 @@ var blockedServices = []blockedService{{
"||minecraftservices.com^",
"||mojang.com^",
},
}, {
ID: "nebula",
Name: "Nebula",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50.8 50.8\"><path d=\"m20.404 31.36 4.847 14.195 4.6-14.295 14.988-.05-11.97-9.002 4.748-14.344-12.316 9.002-12.416-9.15 4.65 14.492-11.773 9.003Z\"/></svg>"),
Rules: []string{
"||nebula.app^",
"||nebula.tv^",
},
}, {
ID: "netflix",
Name: "Netflix",

View file

@ -5,6 +5,7 @@ import (
"fmt"
"net"
"net/netip"
"slices"
"strings"
"time"
@ -16,7 +17,6 @@ import (
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/google/uuid"
"golang.org/x/exp/slices"
)
// UID is the type for the unique IDs of persistent clients.
@ -30,6 +30,16 @@ func NewUID() (uid UID, err error) {
return UID(uuidv7), err
}
// MustNewUID is a wrapper around [NewUID] that panics if there is an error.
func MustNewUID() (uid UID) {
uid, err := NewUID()
if err != nil {
panic(fmt.Errorf("unexpected uuidv7 error: %w", err))
}
return uid
}
// type check
var _ encoding.TextMarshaler = UID{}

View file

@ -0,0 +1,249 @@
package home
import (
"fmt"
"net"
"net/netip"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
)
// macKey contains MAC as byte array of 6, 8, or 20 bytes.
type macKey any
// macToKey converts mac into key of type macKey, which is used as the key of
// the [clientIndex.macToUID]. mac must be valid MAC address.
func macToKey(mac net.HardwareAddr) (key macKey) {
switch len(mac) {
case 6:
return [6]byte(mac)
case 8:
return [8]byte(mac)
case 20:
return [20]byte(mac)
default:
panic(fmt.Errorf("invalid mac address %#v", mac))
}
}
// clientIndex stores all information about persistent clients.
type clientIndex struct {
// clientIDToUID maps client ID to UID.
clientIDToUID map[string]UID
// ipToUID maps IP address to UID.
ipToUID map[netip.Addr]UID
// macToUID maps MAC address to UID.
macToUID map[macKey]UID
// uidToClient maps UID to the persistent client.
uidToClient map[UID]*persistentClient
// subnetToUID maps subnet to UID.
subnetToUID aghalg.SortedMap[netip.Prefix, UID]
}
// NewClientIndex initializes the new instance of client index.
func NewClientIndex() (ci *clientIndex) {
return &clientIndex{
clientIDToUID: map[string]UID{},
ipToUID: map[netip.Addr]UID{},
subnetToUID: aghalg.NewSortedMap[netip.Prefix, UID](subnetCompare),
macToUID: map[macKey]UID{},
uidToClient: map[UID]*persistentClient{},
}
}
// add stores information about a persistent client in the index. c must be
// non-nil and contain UID.
func (ci *clientIndex) add(c *persistentClient) {
if (c.UID == UID{}) {
panic("client must contain uid")
}
for _, id := range c.ClientIDs {
ci.clientIDToUID[id] = c.UID
}
for _, ip := range c.IPs {
ci.ipToUID[ip] = c.UID
}
for _, pref := range c.Subnets {
ci.subnetToUID.Set(pref, c.UID)
}
for _, mac := range c.MACs {
k := macToKey(mac)
ci.macToUID[k] = c.UID
}
ci.uidToClient[c.UID] = c
}
// clashes returns an error if the index contains a different persistent client
// with at least a single identifier contained by c. c must be non-nil.
func (ci *clientIndex) clashes(c *persistentClient) (err error) {
for _, id := range c.ClientIDs {
existing, ok := ci.clientIDToUID[id]
if ok && existing != c.UID {
p := ci.uidToClient[existing]
return fmt.Errorf("another client %q uses the same ID %q", p.Name, id)
}
}
p, ip := ci.clashesIP(c)
if p != nil {
return fmt.Errorf("another client %q uses the same IP %q", p.Name, ip)
}
p, s := ci.clashesSubnet(c)
if p != nil {
return fmt.Errorf("another client %q uses the same subnet %q", p.Name, s)
}
p, mac := ci.clashesMAC(c)
if p != nil {
return fmt.Errorf("another client %q uses the same MAC %q", p.Name, mac)
}
return nil
}
// clashesIP returns a previous client with the same IP address as c. c must be
// non-nil.
func (ci *clientIndex) clashesIP(c *persistentClient) (p *persistentClient, ip netip.Addr) {
for _, ip := range c.IPs {
existing, ok := ci.ipToUID[ip]
if ok && existing != c.UID {
return ci.uidToClient[existing], ip
}
}
return nil, netip.Addr{}
}
// clashesSubnet returns a previous client with the same subnet as c. c must be
// non-nil.
func (ci *clientIndex) clashesSubnet(c *persistentClient) (p *persistentClient, s netip.Prefix) {
for _, s = range c.Subnets {
var existing UID
var ok bool
ci.subnetToUID.Range(func(p netip.Prefix, uid UID) (cont bool) {
if s == p {
existing = uid
ok = true
return false
}
return true
})
if ok && existing != c.UID {
return ci.uidToClient[existing], s
}
}
return nil, netip.Prefix{}
}
// clashesMAC returns a previous client with the same MAC address as c. c must
// be non-nil.
func (ci *clientIndex) clashesMAC(c *persistentClient) (p *persistentClient, mac net.HardwareAddr) {
for _, mac = range c.MACs {
k := macToKey(mac)
existing, ok := ci.macToUID[k]
if ok && existing != c.UID {
return ci.uidToClient[existing], mac
}
}
return nil, nil
}
// find finds persistent client by string representation of the client ID, IP
// address, or MAC.
func (ci *clientIndex) find(id string) (c *persistentClient, ok bool) {
uid, found := ci.clientIDToUID[id]
if found {
return ci.uidToClient[uid], true
}
ip, err := netip.ParseAddr(id)
if err == nil {
// MAC addresses can be successfully parsed as IP addresses.
c, found = ci.findByIP(ip)
if found {
return c, true
}
}
mac, err := net.ParseMAC(id)
if err == nil {
return ci.findByMAC(mac)
}
return nil, false
}
// find finds persistent client by IP address.
func (ci *clientIndex) findByIP(ip netip.Addr) (c *persistentClient, found bool) {
uid, found := ci.ipToUID[ip]
if found {
return ci.uidToClient[uid], true
}
ci.subnetToUID.Range(func(pref netip.Prefix, id UID) (cont bool) {
if pref.Contains(ip) {
uid, found = id, true
return false
}
return true
})
if found {
return ci.uidToClient[uid], true
}
return nil, false
}
// find finds persistent client by MAC.
func (ci *clientIndex) findByMAC(mac net.HardwareAddr) (c *persistentClient, found bool) {
k := macToKey(mac)
uid, found := ci.macToUID[k]
if found {
return ci.uidToClient[uid], true
}
return nil, false
}
// del removes information about persistent client from the index. c must be
// non-nil.
func (ci *clientIndex) del(c *persistentClient) {
for _, id := range c.ClientIDs {
delete(ci.clientIDToUID, id)
}
for _, ip := range c.IPs {
delete(ci.ipToUID, ip)
}
for _, pref := range c.Subnets {
ci.subnetToUID.Del(pref)
}
for _, mac := range c.MACs {
k := macToKey(mac)
delete(ci.macToUID, k)
}
delete(ci.uidToClient, c.UID)
}

View file

@ -0,0 +1,210 @@
package home
import (
"net"
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestClientIndex(t *testing.T) {
const (
cliIPNone = "1.2.3.4"
cliIP1 = "1.1.1.1"
cliIP2 = "2.2.2.2"
cliIPv6 = "1:2:3::4"
cliSubnet = "2.2.2.0/24"
cliSubnetIP = "2.2.2.222"
cliID = "client-id"
cliMAC = "11:11:11:11:11:11"
)
clients := []*persistentClient{{
Name: "client1",
IPs: []netip.Addr{
netip.MustParseAddr(cliIP1),
netip.MustParseAddr(cliIPv6),
},
}, {
Name: "client2",
IPs: []netip.Addr{netip.MustParseAddr(cliIP2)},
Subnets: []netip.Prefix{netip.MustParsePrefix(cliSubnet)},
}, {
Name: "client_with_mac",
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
}, {
Name: "client_with_id",
ClientIDs: []string{cliID},
}}
ci := newIDIndex(clients)
testCases := []struct {
name string
ids []string
want *persistentClient
}{{
name: "ipv4_ipv6",
ids: []string{cliIP1, cliIPv6},
want: clients[0],
}, {
name: "ipv4_subnet",
ids: []string{cliIP2, cliSubnetIP},
want: clients[1],
}, {
name: "mac",
ids: []string{cliMAC},
want: clients[2],
}, {
name: "client_id",
ids: []string{cliID},
want: clients[3],
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for _, id := range tc.ids {
c, ok := ci.find(id)
require.True(t, ok)
assert.Equal(t, tc.want, c)
}
})
}
t.Run("not_found", func(t *testing.T) {
_, ok := ci.find(cliIPNone)
assert.False(t, ok)
})
}
func TestClientIndex_Clashes(t *testing.T) {
const (
cliIP1 = "1.1.1.1"
cliSubnet = "2.2.2.0/24"
cliSubnetIP = "2.2.2.222"
cliID = "client-id"
cliMAC = "11:11:11:11:11:11"
)
clients := []*persistentClient{{
Name: "client_with_ip",
IPs: []netip.Addr{netip.MustParseAddr(cliIP1)},
}, {
Name: "client_with_subnet",
Subnets: []netip.Prefix{netip.MustParsePrefix(cliSubnet)},
}, {
Name: "client_with_mac",
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
}, {
Name: "client_with_id",
ClientIDs: []string{cliID},
}}
ci := newIDIndex(clients)
testCases := []struct {
name string
client *persistentClient
}{{
name: "ipv4",
client: clients[0],
}, {
name: "subnet",
client: clients[1],
}, {
name: "mac",
client: clients[2],
}, {
name: "client_id",
client: clients[3],
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
clone := tc.client.shallowClone()
clone.UID = MustNewUID()
err := ci.clashes(clone)
require.Error(t, err)
ci.del(tc.client)
err = ci.clashes(clone)
require.NoError(t, err)
})
}
}
// mustParseMAC is wrapper around [net.ParseMAC] that panics if there is an
// error.
func mustParseMAC(s string) (mac net.HardwareAddr) {
mac, err := net.ParseMAC(s)
if err != nil {
panic(err)
}
return mac
}
func TestMACToKey(t *testing.T) {
testCases := []struct {
name string
in string
want any
}{{
name: "column6",
in: "00:00:5e:00:53:01",
want: [6]byte(mustParseMAC("00:00:5e:00:53:01")),
}, {
name: "column8",
in: "02:00:5e:10:00:00:00:01",
want: [8]byte(mustParseMAC("02:00:5e:10:00:00:00:01")),
}, {
name: "column20",
in: "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01",
want: [20]byte(mustParseMAC("00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01")),
}, {
name: "hyphen6",
in: "00-00-5e-00-53-01",
want: [6]byte(mustParseMAC("00-00-5e-00-53-01")),
}, {
name: "hyphen8",
in: "02-00-5e-10-00-00-00-01",
want: [8]byte(mustParseMAC("02-00-5e-10-00-00-00-01")),
}, {
name: "hyphen20",
in: "00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01",
want: [20]byte(mustParseMAC("00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01")),
}, {
name: "dot6",
in: "0000.5e00.5301",
want: [6]byte(mustParseMAC("0000.5e00.5301")),
}, {
name: "dot8",
in: "0200.5e10.0000.0001",
want: [8]byte(mustParseMAC("0200.5e10.0000.0001")),
}, {
name: "dot20",
in: "0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001",
want: [20]byte(mustParseMAC("0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001")),
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mac := mustParseMAC(tc.in)
key := macToKey(mac)
assert.Equal(t, tc.want, key)
})
}
assert.Panics(t, func() {
mac := net.HardwareAddr([]byte{1, 2, 3})
_ = macToKey(mac)
})
}

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