mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-11-21 12:35:33 +03:00
Pull request 2100: v0.107.42-rc
Squashed commit of the following: commit 284190f748345c7556e60b67f051ec5f6f080948 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Wed Dec 6 19:36:00 2023 +0300 all: sync with master; upd chlog
This commit is contained in:
parent
df91f016f2
commit
25918e56fa
148 changed files with 7204 additions and 1705 deletions
7
.github/PULL_REQUEST_TEMPLATE
vendored
7
.github/PULL_REQUEST_TEMPLATE
vendored
|
@ -1,7 +1,8 @@
|
|||
Before submitting a PR please make sure that:
|
||||
|
||||
1. You have discussed your solution in an issue and have got an
|
||||
approval from a maintainer.
|
||||
approval from a maintainer. See our
|
||||
[contribution guide](https://github.com/AdguardTeam/AdGuardHome/blob/master/CONTRIBUTING.md).
|
||||
|
||||
2. This isn't a localization fix; please send those to our
|
||||
[CrowdIn](https://crowdin.com/project/adguard-applications/en#/adguard-home)
|
||||
|
@ -13,8 +14,8 @@ Before submitting a PR please make sure that:
|
|||
Add a short description here. The description should include:
|
||||
|
||||
1. Which issue this PR closes (`Closes #NNNN.`) or updates (`Updates
|
||||
#NNNN.`).
|
||||
#NNNN.`). Please do not open PRs without filing an issue first.
|
||||
|
||||
2. A short description of how the change achieves that.
|
||||
|
||||
Do not forget to remove these instructions.
|
||||
Do not forget to remove these instructions!
|
||||
|
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -1,7 +1,7 @@
|
|||
'name': 'build'
|
||||
|
||||
'env':
|
||||
'GO_VERSION': '1.20.11'
|
||||
'GO_VERSION': '1.20.12'
|
||||
'NODE_VERSION': '16'
|
||||
|
||||
'on':
|
||||
|
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -1,7 +1,7 @@
|
|||
'name': 'lint'
|
||||
|
||||
'env':
|
||||
'GO_VERSION': '1.20.11'
|
||||
'GO_VERSION': '1.20.12'
|
||||
|
||||
'on':
|
||||
'push':
|
||||
|
|
59
CHANGELOG.md
59
CHANGELOG.md
|
@ -14,11 +14,11 @@ and this project adheres to
|
|||
<!--
|
||||
## [v0.108.0] - TBA
|
||||
|
||||
## [v0.107.42] - 2023-12-06 (APPROX.)
|
||||
## [v0.107.43] - 2023-12-20 (APPROX.)
|
||||
|
||||
See also the [v0.107.42 GitHub milestone][ms-v0.107.42].
|
||||
See also the [v0.107.43 GitHub milestone][ms-v0.107.43].
|
||||
|
||||
[ms-v0.107.42]: https://github.com/AdguardTeam/AdGuardHome/milestone/76?closed=1
|
||||
[ms-v0.107.43]: https://github.com/AdguardTeam/AdGuardHome/milestone/78?closed=1
|
||||
|
||||
NOTE: Add new changes BELOW THIS COMMENT.
|
||||
-->
|
||||
|
@ -29,6 +29,52 @@ NOTE: Add new changes ABOVE THIS COMMENT.
|
|||
|
||||
|
||||
|
||||
## [v0.107.42] - 2023-12-07
|
||||
|
||||
See also the [v0.107.42 GitHub milestone][ms-v0.107.42].
|
||||
|
||||
### Security
|
||||
|
||||
- Go version has been updated to prevent the possibility of exploiting the
|
||||
CVE-2023-39326, CVE-2023-45283, and CVE-2023-45285 Go vulnerabilities fixed in
|
||||
[Go 1.20.12][go-1.20.12].
|
||||
|
||||
### Added
|
||||
|
||||
- Ability to set client's custom DNS cache ([#6362]).
|
||||
- Ability to disable plain-DNS serving through configuration file if an
|
||||
encrypted protocol is already enabled ([#1660]).
|
||||
- Ability to specify rate limiting settings in the Web UI ([#6369]).
|
||||
|
||||
### Changed
|
||||
|
||||
#### Configuration changes
|
||||
|
||||
- The new property `dns.serve_plain_dns` has been added to the configuration
|
||||
file ([#1660]).
|
||||
- The property `dns.bogus_nxdomain` is now validated more strictly.
|
||||
- Added new properties `clients.persistent.*.upstreams_cache_enabled` and
|
||||
`clients.persistent.*.upstreams_cache_size` that describe cache configuration
|
||||
for each client's custom upstream configuration.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `ipset` entries family validation ([#6420]).
|
||||
- Pre-filling the *New static lease* window with data ([#6402]).
|
||||
- Protection pause timer synchronization ([#5759]).
|
||||
|
||||
[#1660]: https://github.com/AdguardTeam/AdGuardHome/issues/1660
|
||||
[#5759]: https://github.com/AdguardTeam/AdGuardHome/issues/5759
|
||||
[#6362]: https://github.com/AdguardTeam/AdGuardHome/issues/6362
|
||||
[#6369]: https://github.com/AdguardTeam/AdGuardHome/issues/6369
|
||||
[#6402]: https://github.com/AdguardTeam/AdGuardHome/issues/6402
|
||||
[#6420]: https://github.com/AdguardTeam/AdGuardHome/issues/6420
|
||||
|
||||
[go-1.20.12]: https://groups.google.com/g/golang-announce/c/iLGK3x6yuNo/m/z6MJ-eB0AQAJ
|
||||
[ms-v0.107.42]: https://github.com/AdguardTeam/AdGuardHome/milestone/77?closed=1
|
||||
|
||||
|
||||
|
||||
## [v0.107.41] - 2023-11-13
|
||||
|
||||
See also the [v0.107.41 GitHub milestone][ms-v0.107.41].
|
||||
|
@ -2612,11 +2658,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
|||
|
||||
|
||||
<!--
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.42...HEAD
|
||||
[v0.107.42]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.41...v0.107.42
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.43...HEAD
|
||||
[v0.107.43]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.42...v0.107.43
|
||||
-->
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.41...HEAD
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.42...HEAD
|
||||
[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
|
||||
[v0.107.40]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.39...v0.107.40
|
||||
[v0.107.39]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.38...v0.107.39
|
||||
|
|
89
CONTRIBUTING.md
Normal file
89
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,89 @@
|
|||
# Contributing to AdGuard Home
|
||||
|
||||
If you want to contribute to AdGuard Home by filing or commenting on an issue or
|
||||
opening a pull request, please follow the instructions below.
|
||||
|
||||
|
||||
|
||||
## General recommendations
|
||||
|
||||
Please don't:
|
||||
|
||||
* post comments like “+1” or “this”. Use the :+1: reaction on the issue
|
||||
instead, as this allows us to actually see the level of support for issues.
|
||||
|
||||
* file issues about localization errors or send localization updates as PRs.
|
||||
We're using [CrowdIn] to manage our translations and we generally update
|
||||
them before each Beta and Release build. You can learn more about
|
||||
translating AdGuard products [in our Knowledge Base][kb-trans].
|
||||
|
||||
* file issues about a particular filtering-rule list misbehaving. These are
|
||||
tracked through the [separate form for filtering issues][form].
|
||||
|
||||
* send updates to filtering-rule lists, such as the ones for the Blocked
|
||||
Services feature or the list of approved filtering-rule lists. We update
|
||||
them once before each Beta and Release build.
|
||||
|
||||
Please do:
|
||||
|
||||
* follow the template instructions and provide data for reproducing issues.
|
||||
|
||||
* write the title of your issue or pull request in English. Any language is
|
||||
fine in the body, but it is important to keep the title in English to make
|
||||
it easier for people and bots to look up duplicated issues.
|
||||
|
||||
[CrowdIn]: https://crowdin.com/project/adguard-applications/en#/adguard-home
|
||||
[form]: https://link.adtidy.org/forward.html?action=report&app=home&from=github
|
||||
[kb-trans]: https://kb.adguard.com/en/general/adguard-translations
|
||||
|
||||
|
||||
|
||||
## Issues
|
||||
|
||||
### Search first
|
||||
|
||||
Please make sure that the issue is not a duplicate or a question. If it's a
|
||||
duplicate, please react to the original issue with a thumbs up. If it's a
|
||||
question, please look through our [Wiki] and, if you haven't found the answer,
|
||||
post it to the GitHub [Discussions] page.
|
||||
|
||||
[Discussions]: https://github.com/AdguardTeam/AdGuardHome/discussions/categories/q-a
|
||||
[Wiki]: https://github.com/AdguardTeam/AdGuardHome/wiki
|
||||
|
||||
|
||||
|
||||
### Follow the issue template
|
||||
|
||||
Developers need to be able to reproduce the faulty behavior in order to fix an
|
||||
issue, so please make sure that you follow the instructions in the issue
|
||||
template carefully.
|
||||
|
||||
|
||||
|
||||
## Pull requests
|
||||
|
||||
### Discuss your changes first
|
||||
|
||||
Please discuss your changes by opening an issue. The maintainers should
|
||||
evaluate your proposal, and it's generally better if that's done before any code
|
||||
is written.
|
||||
|
||||
|
||||
|
||||
### Review your changes for style
|
||||
|
||||
We have a set of [code guidelines][hacking] that we expect the code to follow.
|
||||
Please make sure you follow it.
|
||||
|
||||
[hacking]: https://github.com/AdguardTeam/CodeGuidelines/blob/master/Go/Go.md
|
||||
|
||||
|
||||
|
||||
### Test your changes
|
||||
|
||||
Make sure that it passes linters and tests by running the corresponding Make
|
||||
targets. For backend changes, it's `make go-check`. For frontend, run
|
||||
`make js-lint`.
|
||||
|
||||
Additionally, a manual test is often required. While we're constantly working
|
||||
on improving our test suites, they're still not as good as we'd like them to be.
|
|
@ -7,7 +7,7 @@
|
|||
# Make sure to sync any changes with the branch overrides below.
|
||||
'variables':
|
||||
'channel': 'edge'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.5'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
|
||||
'stages':
|
||||
- 'Build frontend':
|
||||
|
@ -272,7 +272,7 @@
|
|||
# need to build a few of these.
|
||||
'variables':
|
||||
'channel': 'beta'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.5'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
# 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.5'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
# Make sure to sync any changes with the branch overrides below.
|
||||
'variables':
|
||||
'channel': 'edge'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.5'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
'snapcraftChannel': 'edge'
|
||||
|
||||
'stages':
|
||||
|
@ -191,7 +191,7 @@
|
|||
# need to build a few of these.
|
||||
'variables':
|
||||
'channel': 'beta'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.5'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
'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.5'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
'snapcraftChannel': 'candidate'
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
'key': 'AHBRTSPECS'
|
||||
'name': 'AdGuard Home - Build and run tests'
|
||||
'variables':
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.5'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
|
||||
'stages':
|
||||
- 'Tests':
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"client_settings": "Налады кліентаў",
|
||||
"example_upstream_reserved": "upstream <0>для канкрэтных даменаў</0>;",
|
||||
"example_multiple_upstreams_reserved": "некалькі DNS-сервераў <0>для канкрэтных даменаў</0>;",
|
||||
"example_upstream_comment": "каментар.",
|
||||
"upstream_parallel": "Ужыць адначасныя запыты да ўсіх сервераў для паскарэння апрацоўкі запыту",
|
||||
"parallel_requests": "Паралельныя запыты",
|
||||
|
@ -143,6 +144,8 @@
|
|||
"enforced_save_search": "Ужыты бяспечны пошук",
|
||||
"number_of_dns_query_to_safe_search": "Колькасць запытаў DNS для пошукавых сістэм, для якіх быў ужыты Бяспечны пошук",
|
||||
"average_processing_time": "Сярэдні час апрацоўкі запыту",
|
||||
"average_upstream_response_time": "Сярэдні час водгуку upstream-сервера",
|
||||
"response_time": "Час водгуку",
|
||||
"average_processing_time_hint": "Сярэдні час для апрацоўкі запыту DNS у мілісекундах",
|
||||
"block_domain_use_filters_and_hosts": "Блакаваць дамены з выкарыстаннем фільтраў і файлаў хастоў",
|
||||
"filters_block_toggle_hint": "Вы можаце наладзіць правілы блакавання ў «<a>Фільтрах</a>».",
|
||||
|
@ -307,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Выкарыстоўваць указаны IP для DNS",
|
||||
"edns_use_custom_ip_desc": "Дазволіць выкарыстоўваць уласны IP для DNS",
|
||||
"rate_limit_desc": "Абмежаванне на колькасць запытаў у секунду для кожнага кліента (0 — неабмежавана)",
|
||||
"rate_limit_subnet_len_ipv4": "Даўжыня прэфікса падсеткі для адрасоў IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Даўжыня прэфікса падсеткі для адрасоў IPv4, якія выкарыстоўваюцца для абмежавання хуткасці. Значэнне па змаўчанні 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Даўжыня прэфікса падсеткі IPv4 павінна быць ад 0 да 32",
|
||||
"rate_limit_subnet_len_ipv6": "Даўжыня прэфікса падсеткі для адрасоў IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Даўжыня прэфікса падсеткі для адрасоў IPv6, якія выкарыстоўваюцца для абмежавання хуткасці. Значэнне па змаўчанні 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Даўжыня прэфікса падсеткі IPv6 павінна быць ад 0 да 128",
|
||||
"form_enter_rate_limit_subnet_len": "Увядзіце даўжыню прэфікса падсеткі для абмежавання хуткасці",
|
||||
"rate_limit_whitelist": "Белы спіс з абмежаваннем хуткасці",
|
||||
"rate_limit_whitelist_desc": "IP-адрасы выключаны з абмежавання хуткасці",
|
||||
"rate_limit_whitelist_placeholder": "Увядзіце па адным адрасе на радок",
|
||||
"blocking_ipv4_desc": "IP-адрас, што вяртаецца пры блакаванню A-запыту",
|
||||
"blocking_ipv6_desc": "IP-адрас, што вяртаецца пры блакаванню AAAA-запыту",
|
||||
"blocking_mode_default": "Стандартны: Адказвае з нулёвым IP-адрасам (0.0.0.0 для A; :: для AAAA), калі заблакавана правілам у стылі Adblock; адказвае з IP-адрасам, паказаным у правіле, калі заблакавана правілам у стылі /etc/hosts-style",
|
||||
|
@ -721,5 +734,8 @@
|
|||
"wednesday_short": "Ср.",
|
||||
"thursday_short": "Чц.",
|
||||
"friday_short": "Пт.",
|
||||
"saturday_short": "Сб."
|
||||
"saturday_short": "Сб.",
|
||||
"upstream_dns_cache_configuration": "Канфігурацыя кэша upstream DNS-сервераў",
|
||||
"enable_upstream_dns_cache": "Ўключыць кэшаванне для карыстацкай канфігурацыі upstream-сервераў гэтага кліента",
|
||||
"dns_cache_size": "Памер кэша DNS, у байтах"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Použít vlastní IP pro EDNS",
|
||||
"edns_use_custom_ip_desc": "Povolit použití vlastní IP pro EDNS",
|
||||
"rate_limit_desc": "Počet požadavků za sekundu, které smí jeden klient provádět (0: neomezeno)",
|
||||
"rate_limit_subnet_len_ipv4": "Délka předpony podsítě pro adresy IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Délka předpony podsítě pro adresy IPv4 používané pro omezení rychlosti. Výchozí hodnota je 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Délka předpony podsítě IPv4 by měla být mezi 0 a 32",
|
||||
"rate_limit_subnet_len_ipv6": "Délka předpony podsítě pro adresy IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Délka předpony podsítě pro adresy IPv6 používané pro omezení rychlosti. Výchozí hodnota je 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Délka předpony podsítě IPv6 by měla být mezi 0 a 128",
|
||||
"form_enter_rate_limit_subnet_len": "Zadejte délku předpony podsítě pro omezení rychlosti",
|
||||
"rate_limit_whitelist": "Seznam výjimek pro omezení rychlosti",
|
||||
"rate_limit_whitelist_desc": "IP adresy vyloučené z omezení rychlosti",
|
||||
"rate_limit_whitelist_placeholder": "Zadejte jednu IP adresu na řádek",
|
||||
"blocking_ipv4_desc": "IP adresa, která se má vrátit v případě blokovaného požadavku typu A",
|
||||
"blocking_ipv6_desc": "IP adresa, která se má vrátit v případě blokovaného požadavku typu AAAA",
|
||||
"blocking_mode_default": "Výchozí: Odezva s nulovou IP adresou (0.0.0.0 pro A; :: pro AAAA), pokud je blokováno pravidlem ve stylu Adblock; odezva pomocí IP adresy uvedené v pravidle, pokud je blokováno pravidlem /etc/hosts-style",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Středa",
|
||||
"thursday_short": "Čtvrtek",
|
||||
"friday_short": "Pátek",
|
||||
"saturday_short": "Sobota"
|
||||
"saturday_short": "Sobota",
|
||||
"upstream_dns_cache_configuration": "Konfigurace mezipaměti odchozího DNS",
|
||||
"enable_upstream_dns_cache": "Povolit ukládání do mezipaměti DNS pro vlastní konfiguraci odchozího připojení tohoto klienta",
|
||||
"dns_cache_size": "Velikost mezipaměti DNS v bajtech"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Brug tilpasset IP til EDNS",
|
||||
"edns_use_custom_ip_desc": "Tillad brug af tilpasset IP til EDNS",
|
||||
"rate_limit_desc": "Antallet af forespørgsler pr. sekund tilladt pr. klient (værdien 0 = ubegrænset)",
|
||||
"rate_limit_subnet_len_ipv4": "Længde på undernetpræfiks for IPv4-adresser",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Længde på undernetpræfiks for IPv4-adresser til hastighedsbegrænsning. Standard er 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Længden på IPv4-undernetpræfiks skal være mellem 0 og 32",
|
||||
"rate_limit_subnet_len_ipv6": "Længde på undernetpræfiks for IPv6-adresser",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Længde på undernetpræfiks for IPv6-adresser til hastighedsbegrænsning. Standard er 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Længden på IPv6-undernetpræfiks skal være mellem 0 og 128",
|
||||
"form_enter_rate_limit_subnet_len": "Angiv længden på undernetpræfiks til hastighedsbegrænsning",
|
||||
"rate_limit_whitelist": "Hvidliste til hastighedsbegrænsning",
|
||||
"rate_limit_whitelist_desc": "IP-adresser undtaget fra hastighedsbegrænsning",
|
||||
"rate_limit_whitelist_placeholder": "Angiv én IP-adresse pr. linje",
|
||||
"blocking_ipv4_desc": "Returneret IP-adresse for en blokeret A-forespørgsel",
|
||||
"blocking_ipv6_desc": "Returneret IP-adresse for en blokeret AAAA-forespørgsel",
|
||||
"blocking_mode_default": "Standard: Svar med nul IP-adresse (0.0.0.0 for A; :: for AAAA), når blokeret af Adblock-lignende regel. Svar med IP-adressen angivet i reglen, når blokeret af /etc/hosts-lignende regel",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Ons",
|
||||
"thursday_short": "Tors",
|
||||
"friday_short": "Fre",
|
||||
"saturday_short": "Lør"
|
||||
"saturday_short": "Lør",
|
||||
"upstream_dns_cache_configuration": "Upstream DNS-cacheopsætning",
|
||||
"enable_upstream_dns_cache": "Aktivér DNS-cachelagring for denne klients tilpassede upstream-opsætning",
|
||||
"dns_cache_size": "DNS-cachestørrelse i bytes"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Benutzerdefinierte IP für EDNS verwenden",
|
||||
"edns_use_custom_ip_desc": "Benutzerdefinierte IP für EDNS zulassen",
|
||||
"rate_limit_desc": "Die Anzahl der Anfragen pro Sekunde, die ein einzelner Client stellen darf. Das Setzen auf 0 bedeutet keine Begrenzung.",
|
||||
"rate_limit_subnet_len_ipv4": "Länge des Subnetzpräfixes für IPv4-Adressen",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Subnetpräfixlänge für IPv4-Adressen, die für die Ratebegrenzung verwendet werden. Der Standardwert ist 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Die Subnetzpräfixlänge für IPv4-Adressen sollte zwischen 0 und 32 liegen",
|
||||
"rate_limit_subnet_len_ipv6": "Subnetzpräfixlänge für IPv6-Adressen",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Subnetpräfixlänge für IPv6-Adressen, die für die Ratebegrenzung verwendet werden. Der Standardwert ist 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Die Subnetzpräfixlänge für IPv6-Adressen sollte zwischen 0 und 128 liegen",
|
||||
"form_enter_rate_limit_subnet_len": "Geben Sie die Subnetzpräfixlänge für die Ratebegrenzung ein",
|
||||
"rate_limit_whitelist": "Zulassungsliste für die Ratebegrenzung",
|
||||
"rate_limit_whitelist_desc": "IP-Adressen, die von der Ratebegrenzung ausgeschlossen sind",
|
||||
"rate_limit_whitelist_placeholder": "Geben Sie eine IP-Adresse pro Zeile ein",
|
||||
"blocking_ipv4_desc": "IP-Adresse, die für eine gesperrte A-Anfrage zurückgegeben werden soll",
|
||||
"blocking_ipv6_desc": "IP-Adresse, die für eine gesperrte AAAA-Anfrage zurückgegeben werden soll",
|
||||
"blocking_mode_default": "Standard: Mit Null IP Adress (0.0.0.0 for A; :: for AAAA) antworten, wenn sie durch eine Regel im Adblock-Stil gesperrt sind; mit der in der Regel angegebenen IP-Adresse antworten, wenn sie durch eine Regel im /etc/hosts-Stil gesperrt wurde",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Mi",
|
||||
"thursday_short": "Do",
|
||||
"friday_short": "Fr",
|
||||
"saturday_short": "Sa"
|
||||
"saturday_short": "Sa",
|
||||
"upstream_dns_cache_configuration": "Konfiguration des Upstream-DNS-Cache",
|
||||
"enable_upstream_dns_cache": "Caching für die benutzerdefinierte Upstream-Server-Konfiguration dieses Clients aktivieren",
|
||||
"dns_cache_size": "Größe des DNS-Cache, in Bytes"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Use custom IP for EDNS",
|
||||
"edns_use_custom_ip_desc": "Allow to use custom IP for EDNS",
|
||||
"rate_limit_desc": "The number of requests per second allowed per client. Setting it to 0 means no limit.",
|
||||
"rate_limit_subnet_len_ipv4": "Subnet prefix length for IPv4 addresses",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Subnet prefix length for IPv4 addresses used for rate limiting. The default is 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "The IPv4 subnet prefix length should be between 0 and 32",
|
||||
"rate_limit_subnet_len_ipv6": "Subnet prefix length for IPv6 addresses",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Subnet prefix length for IPv6 addresses used for rate limiting. The default is 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "The IPv6 subnet prefix length should be between 0 and 128",
|
||||
"form_enter_rate_limit_subnet_len": "Enter subnet prefix length for rate limiting",
|
||||
"rate_limit_whitelist": "Rate limiting allowlist",
|
||||
"rate_limit_whitelist_desc": "IP addresses excluded from rate limiting",
|
||||
"rate_limit_whitelist_placeholder": "Enter one IP address per line",
|
||||
"blocking_ipv4_desc": "IP address to be returned for a blocked A request",
|
||||
"blocking_ipv6_desc": "IP address to be returned for a blocked AAAA request",
|
||||
"blocking_mode_default": "Default: Respond with zero IP address (0.0.0.0 for A; :: for AAAA) when blocked by Adblock-style rule; respond with the IP address specified in the rule when blocked by /etc/hosts-style rule",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Wed",
|
||||
"thursday_short": "Thu",
|
||||
"friday_short": "Fri",
|
||||
"saturday_short": "Sat"
|
||||
"saturday_short": "Sat",
|
||||
"upstream_dns_cache_configuration": "Upstream DNS cache configuration",
|
||||
"enable_upstream_dns_cache": "Enable DNS caching for this client's custom upstream configuration",
|
||||
"dns_cache_size": "DNS cache size, in bytes"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Usar IP personalizada para EDNS",
|
||||
"edns_use_custom_ip_desc": "Permitir el uso de IP personalizadas para EDNS",
|
||||
"rate_limit_desc": "Número de peticiones por segundo permitidas por cliente. Establecerlo en 0 significa que no hay límite.",
|
||||
"rate_limit_subnet_len_ipv4": "Longitud del prefijo de subred para direcciones IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Longitud del prefijo de subred para direcciones IPv4 utilizadas para limitar la velocidad. El valor predeterminado es 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "La longitud del prefijo de subred IPv4 debe estar entre 0 y 32",
|
||||
"rate_limit_subnet_len_ipv6": "Longitud del prefijo de subred para direcciones IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Longitud del prefijo de subred para direcciones IPv6 utilizadas para limitar la velocidad. El valor predeterminado es 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "La longitud del prefijo de subred IPv6 debe estar entre 0 y 128",
|
||||
"form_enter_rate_limit_subnet_len": "Ingresa la longitud del prefijo de subred para limitar la velocidad",
|
||||
"rate_limit_whitelist": "Lista de permitidos de limitación de velocidad",
|
||||
"rate_limit_whitelist_desc": "Direcciones IP excluidas de la limitación de velocidad",
|
||||
"rate_limit_whitelist_placeholder": "Ingresa una dirección IP por línea",
|
||||
"blocking_ipv4_desc": "Dirección IP devolverá una petición A bloqueada",
|
||||
"blocking_ipv6_desc": "Dirección IP devolverá una petición AAAA bloqueada",
|
||||
"blocking_mode_default": "Predeterminado: Responde con dirección IP cero (0.0.0.0 para A; :: para AAAA) cuando está bloqueado por la regla de estilo Adblock; responde con la dirección IP especificada en la regla cuando está bloqueado por una regla de estilo /etc/hosts",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Mié.",
|
||||
"thursday_short": "Jue.",
|
||||
"friday_short": "Vie.",
|
||||
"saturday_short": "Sáb."
|
||||
"saturday_short": "Sáb.",
|
||||
"upstream_dns_cache_configuration": "Configuración de la caché DNS upstream",
|
||||
"enable_upstream_dns_cache": "Habilitar el almacenamiento en caché de DNS para la configuración personalizada de este cliente",
|
||||
"dns_cache_size": "Tamaño de la caché DNS, en bytes"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"client_settings": "Päätelaiteasetukset",
|
||||
"example_upstream_reserved": "ylävirta <0>tietyille verkkotunnuksille</0>;",
|
||||
"example_multiple_upstreams_reserved": "useita ylävirtoja <0>tietyille verkkotunnuksille</0>;",
|
||||
"example_upstream_comment": "kommentti.",
|
||||
"upstream_parallel": "Käytä rinnakkaisia pyyntöjä ja nopeuta selvitystä käyttämällä kaikkia ylävirtapalvelimia samanaikaisesti.",
|
||||
"parallel_requests": "Rinnakkaiset pyynnöt",
|
||||
|
@ -143,6 +144,7 @@
|
|||
"enforced_save_search": "Turvallinen haku pakotettiin",
|
||||
"number_of_dns_query_to_safe_search": "DNS-pyyntöjen määrä, joille turvallinen haku pakotettiin käyttöön",
|
||||
"average_processing_time": "Keskimääräinen käsittelyaika",
|
||||
"average_upstream_response_time": "Ylävirran keskimääräinen vasteaika",
|
||||
"response_time": "Vasteaika",
|
||||
"average_processing_time_hint": "Keskimääräinen DNS-pyynnön käsittelyyn kulutettu aika millisekunteina",
|
||||
"block_domain_use_filters_and_hosts": "Estä verkkotunnuksia suodattimilla ja hosts-tiedostoilla",
|
||||
|
@ -308,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Käytä omaa IP-osoitetta EDNS:lle",
|
||||
"edns_use_custom_ip_desc": "Salli oman IP-osoitteen käyttö EDNS-mekanismille.",
|
||||
"rate_limit_desc": "Päätelaitteelle sallittu pyyntöjen enimmäismäärä sekunnissa. Arvo 0 tarkoittaa rajatonta.",
|
||||
"rate_limit_subnet_len_ipv4": "IPv4-osoitteiden aliverkon etuliitteen pituus",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Aliverkon etuliitteen pituus IPv4-osoitteille, joita käytetään nopeuden rajoittamiseen. Oletusarvo on 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "IPv4-aliverkon etuliitteen pituuden tulee olla 0–32",
|
||||
"rate_limit_subnet_len_ipv6": "IPv6-osoitteiden aliverkon etuliitteen pituus",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Aliverkon etuliitteen pituus IPv6-osoitteille, joita käytetään nopeuden rajoittamiseen. Oletusarvo on 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "IPv6-aliverkon etuliitteen pituuden tulee olla 0–128",
|
||||
"form_enter_rate_limit_subnet_len": "Anna aliverkon etuliitteen pituus nopeuden rajoittamista varten",
|
||||
"rate_limit_whitelist": "Nopeutta rajoittava sallittu luettelo",
|
||||
"rate_limit_whitelist_desc": "IP-osoitteet, jotka eivät kuulu nopeusrajoituksen piiriin",
|
||||
"rate_limit_whitelist_placeholder": "Syötä yksi IP-osoite per rivi",
|
||||
"blocking_ipv4_desc": "Estettyyn A-pyyntöön palautettava IP-osoite",
|
||||
"blocking_ipv6_desc": "Estettyyn AAAA-pyyntöön palautettava IP-osoite",
|
||||
"blocking_mode_default": "Oletus: Vastaa IP-nollaosoitteella (0.0.0.0 korvaa A; :: korvaa AAAA) kun estetään mainoseston säännöllä; vastaa säännön määrittämällä IP-osoitteella kun estetään /etc/hosts-tyyppisellä säännöllä",
|
||||
|
@ -722,5 +734,8 @@
|
|||
"wednesday_short": "Ke",
|
||||
"thursday_short": "To",
|
||||
"friday_short": "Pe",
|
||||
"saturday_short": "La"
|
||||
"saturday_short": "La",
|
||||
"upstream_dns_cache_configuration": "Ylävirran DNS-välimuistin määritykset",
|
||||
"enable_upstream_dns_cache": "Käytä DNS-välimuistia tämän päätelaitteen mukautetuissa ylävirtamäärityksissä",
|
||||
"dns_cache_size": "DNS-välimuistin koko tavuina"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Utiliser une IP personnalisée pour EDNS",
|
||||
"edns_use_custom_ip_desc": "Autoriser l'utilisation d'une adresse IP personnalisée pour EDNS",
|
||||
"rate_limit_desc": "Le nombre de requêtes par seconde qu’un seul client est autorisé à faire. Le réglage 0 fait illimité.",
|
||||
"rate_limit_subnet_len_ipv4": "Longueur du préfixe de sous-réseau pour les adresses IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Longueur du préfixe de sous-réseau pour les adresses IPv4 utilisé pour la limitation de vitesse. La valeur par défaut est 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "La longueur du préfixe du sous-réseau IPv4 doit être entre 0 et 32",
|
||||
"rate_limit_subnet_len_ipv6": "Longueur du préfixe de sous-réseau pour les adresses IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Longueur du préfixe de sous-réseau pour les adresses IPv6 utilisé pour la limitation de débit. La valeur par défaut est 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "La longueur du préfixe du sous-réseau IPv6 doit être entre 0 et 128",
|
||||
"form_enter_rate_limit_subnet_len": "Saisissez la longueur du préfixe de sous-réseau pour la limitation de débit",
|
||||
"rate_limit_whitelist": "Liste d'autorisation de limitation de débit",
|
||||
"rate_limit_whitelist_desc": "Adresses IP exclues de la limitation du débit",
|
||||
"rate_limit_whitelist_placeholder": "Saisissez une adresse IP par ligne",
|
||||
"blocking_ipv4_desc": "Adresse IP à renvoyer pour une demande A bloquée",
|
||||
"blocking_ipv6_desc": "Adresse IP à renvoyer pour une demande AAAA bloquée",
|
||||
"blocking_mode_default": "Par défaut : Répondre avec adresse IP zéro (0.0.0.0 pour A ; :: pour AAAA) lorsque bloqué par la règle de style Adblock ; répondre avec l’adresse IP spécifiée dans la règle lorsque bloquée par la règle du style /etc/hosts",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Mer.",
|
||||
"thursday_short": "Jeu.",
|
||||
"friday_short": "Ven.",
|
||||
"saturday_short": "Sam."
|
||||
"saturday_short": "Sam.",
|
||||
"upstream_dns_cache_configuration": "Configuration du cache DNS en amont",
|
||||
"enable_upstream_dns_cache": "Activer la mise en cache pour la configuration personnalisée du serveur en amont de ce client",
|
||||
"dns_cache_size": "Taille du cache DNS, en bytes"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Koristi prilagođeni IP za EDNS",
|
||||
"edns_use_custom_ip_desc": "Dopusti korištenje prilagođenog IP-a za EDNS",
|
||||
"rate_limit_desc": "Broj zahtjeva u sekundi koji su dopušteni po jednom klijentu. Postavljanje na 0 znači neograničeno.",
|
||||
"rate_limit_subnet_len_ipv4": "Duljina prefiksa podmreže za IPv4 adrese",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Duljina prefiksa podmreže za IPv4 adrese koje se koriste za ograničavanje brzine. Zadana vrijednost je 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Dužina IPv4 prefiksa podmreže trebala bi biti između 0 i 32",
|
||||
"rate_limit_subnet_len_ipv6": "Duljina prefiksa podmreže za IPv6 adrese",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Duljina prefiksa podmreže za IPv6 adrese koje se koriste za ograničavanje brzine. Zadana vrijednost je 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Dužina IPv6 prefiksa podmreže trebala bi biti između 0 i 128",
|
||||
"form_enter_rate_limit_subnet_len": "Unesite duljinu prefiksa podmreže za ograničenje brzine",
|
||||
"rate_limit_whitelist": "Popis dopuštenih za ograničavanje brzine",
|
||||
"rate_limit_whitelist_desc": "IP adrese isključene iz ograničenja brzine",
|
||||
"rate_limit_whitelist_placeholder": "Unesite jednu adresu poslužitelja po retku",
|
||||
"blocking_ipv4_desc": "Povratna IP adresa za blokirane A zahtjeve",
|
||||
"blocking_ipv6_desc": "Povratna IP adresa za blokirane AAAA zahtjeve",
|
||||
"blocking_mode_default": "Zadano: Odgovori s nultom IP adresom (0.0.0.0 za A; :: za AAAA) kada ga blokira Adblock slično pravilo; odgovorite s IP adresom definiranom u pravilu kada je blokirano od /etc/hosts sličnog pravila",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Sri",
|
||||
"thursday_short": "Čet",
|
||||
"friday_short": "Pet",
|
||||
"saturday_short": "Sub"
|
||||
"saturday_short": "Sub",
|
||||
"upstream_dns_cache_configuration": "Konfiguracija predmemoriranja upstream DNS poslužitelja",
|
||||
"enable_upstream_dns_cache": "Uključite keširanje za korisničku konfiguraciju upstream servera ovog klijenta",
|
||||
"dns_cache_size": "Veličina DNS predmemorije, u bajtovima"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Használjon egyéni IP-címet az EDNS-hez",
|
||||
"edns_use_custom_ip_desc": "Engedélyezze az egyéni IP-cím használatát az EDNS-hez",
|
||||
"rate_limit_desc": "Maximálisan hány kérést küldhet egy kliens másodpercenkén. Ha 0-ra állítja, akkor nincs korlátozás.",
|
||||
"rate_limit_subnet_len_ipv4": "Az IPv4-címek alhálózati előtagjának hossza",
|
||||
"rate_limit_subnet_len_ipv4_desc": "A sebességkorlátozáshoz használt IPv4-címek alhálózati előtagjának hossza. Az alapértelmezett érték 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Az IPv4 alhálózati előtag hosszának 0 és 32 között kell lennie",
|
||||
"rate_limit_subnet_len_ipv6": "Az IPv6-címek alhálózati előtagjának hossza",
|
||||
"rate_limit_subnet_len_ipv6_desc": "A sebességkorlátozáshoz használt IPv6-címek alhálózati előtagjának hossza. Az alapértelmezett érték 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Az IPv6 alhálózati előtag hosszának 0 és 128 között kell lennie",
|
||||
"form_enter_rate_limit_subnet_len": "Adja meg az alhálózati előtag hosszát a sebességkorlátozáshoz",
|
||||
"rate_limit_whitelist": "Sebességkorlátozó engedélyezési lista",
|
||||
"rate_limit_whitelist_desc": "A sebességkorlátozásból kizárt IP-címek",
|
||||
"rate_limit_whitelist_placeholder": "Adjon meg egy IP-címet soronként",
|
||||
"blocking_ipv4_desc": "A blokkolt A kéréshez visszaadandó IP-cím",
|
||||
"blocking_ipv6_desc": "A blokkolt AAAA kéréshez visszaadandó IP-cím",
|
||||
"blocking_mode_default": "Alapértelmezés: Válaszoljon nulla IP-címmel (vagyis 0.0.0.0 az A-hoz, :: pedig az AAAA-hoz), amikor a blokkolás egy adblock-stílusú szabállyal történik; illetve válaszoljon egy, a szabály által meghatározott IP címmel, amikor a blokkolás egy /etc/hosts stílusú szabállyal történik",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Szer",
|
||||
"thursday_short": "Csüt",
|
||||
"friday_short": "Pén",
|
||||
"saturday_short": "Szom"
|
||||
"saturday_short": "Szom",
|
||||
"upstream_dns_cache_configuration": "Upstream DNS gyorsítótár konfigurációja",
|
||||
"enable_upstream_dns_cache": "A DNS gyorsítótárazásának engedélyezése az ügyfél egyéni upstream konfigurációjához",
|
||||
"dns_cache_size": "DNS gyorsítótár mérete, bájtokban"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Gunakan IP khusus untuk EDNS",
|
||||
"edns_use_custom_ip_desc": "Izinkan untuk menggunakan IP kustom untuk EDNS",
|
||||
"rate_limit_desc": "Jumlah permintaan per detik yang diperbolehkan untuk satu klien. Atur ke 0 untuk tidak terbatas.",
|
||||
"rate_limit_subnet_len_ipv4": "Panjang awalan subnet untuk alamat IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Panjang awalan subnet untuk alamat IPv4 yang digunakan untuk pembatasan kecepatan. Standarnya adalah 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Panjang awalan subnet IPv4 harus antara 0 dan 32",
|
||||
"rate_limit_subnet_len_ipv6": "Panjang awalan subnet untuk alamat IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Panjang awalan subnet untuk alamat IPv6 yang digunakan untuk pembatasan kecepatan. Standarnya adalah 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Panjang awalan subnet IPv6 harus antara 0 dan 128",
|
||||
"form_enter_rate_limit_subnet_len": "Masukkan panjang awalan subnet untuk pembatasan kecepatan",
|
||||
"rate_limit_whitelist": "Daftar pembatasan tarif yang diizinkan",
|
||||
"rate_limit_whitelist_desc": "Alamat IP dikecualikan dari pembatasan tarif",
|
||||
"rate_limit_whitelist_placeholder": "Masukkan satu alamat IP per baris",
|
||||
"blocking_ipv4_desc": "Alamat IP akan dikembalikan untuk permintaan A yang diblokir",
|
||||
"blocking_ipv6_desc": "Alamat IP akan dipulihkan untuk permintaan AAAA yang diblokir",
|
||||
"blocking_mode_default": "Default: Tanggapi dengan alamat IP nol (0.0.0.0 untuk A; :: untuk AAAA) saat diblokir oleh aturan gaya Adblock; tanggapi dengan alamat IP yang ditentukan dalam aturan ketika diblokir oleh aturan gaya host /etc/",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Rab",
|
||||
"thursday_short": "Kam",
|
||||
"friday_short": "Jum",
|
||||
"saturday_short": "Sab"
|
||||
"saturday_short": "Sab",
|
||||
"upstream_dns_cache_configuration": "Konfigurasi cache DNS upstream",
|
||||
"enable_upstream_dns_cache": "Aktifkan cache DNS untuk konfigurasi upstream kustom klien ini",
|
||||
"dns_cache_size": "Ukuran cache DNS, dalam byte"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Usa IP personalizzato per EDNS",
|
||||
"edns_use_custom_ip_desc": "Consentire l'uso di un IP personalizzato per EDNS",
|
||||
"rate_limit_desc": "Il numero di richieste al secondo consentite da un singolo client. Impostare questo valore a 0 rimuove le limitazioni.",
|
||||
"rate_limit_subnet_len_ipv4": "Lunghezza prefisso di sottorete per indirizzi IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Lunghezza prefisso sottorete per indirizzi IPv4 usati per la limitazione della velocità. Valore predefinito 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "La lunghezza del prefisso di sottorete IPv4 deve essere compresa tra 0 e 32",
|
||||
"rate_limit_subnet_len_ipv6": "Lunghezza prefisso di sottorete per indirizzi IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Lunghezza prefisso di sottorete per indirizzi IPv6 usati per la limitazione della velocità. Valore predefinito 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "La lunghezza del prefisso di sottorete IPv6 deve essere compresa tra 0 e 128",
|
||||
"form_enter_rate_limit_subnet_len": "Inserisci lunghezza prefisso di sottorete per limitazione velocità",
|
||||
"rate_limit_whitelist": "Lista consentita per limitazione velocità",
|
||||
"rate_limit_whitelist_desc": "Indirizzi IP esclusi dalla limitazione della velocità",
|
||||
"rate_limit_whitelist_placeholder": "Inserisci un indirizzo IP per riga",
|
||||
"blocking_ipv4_desc": "Indirizzo IP per una richiesta DNS IPv4 bloccata",
|
||||
"blocking_ipv6_desc": "Indirizzo IP restituito per una richiesta DNS IPv6 bloccata",
|
||||
"blocking_mode_default": "Risponde con un indirizzo IP pari a zero (0.0.0.0 per A; :: per AAAA) quando bloccato da una regola in stile Blocca-annunci; risponde con l'indirizzo IP specificato nella regola quando bloccato da una regola in stile /etc/hosts",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Mer",
|
||||
"thursday_short": "Gio",
|
||||
"friday_short": "Ven",
|
||||
"saturday_short": "Sab"
|
||||
"saturday_short": "Sab",
|
||||
"upstream_dns_cache_configuration": "Configurazione cache DNS upstream",
|
||||
"enable_upstream_dns_cache": "Abilita cache DNS per la configurazione upstream personalizzata del client",
|
||||
"dns_cache_size": "Dimensioni cache DNS (in byte)"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "EDNSにカスタムIPを使用する",
|
||||
"edns_use_custom_ip_desc": "EDNS に対してカスタム IP の使用を許可します。",
|
||||
"rate_limit_desc": "一つのクライアントに対して許可される1秒あたりのリクエスト数(「0」に設定すると、制限なしになります)",
|
||||
"rate_limit_subnet_len_ipv4": "IPv4 アドレスのサブネットプレフィックス長",
|
||||
"rate_limit_subnet_len_ipv4_desc": "rate limiting(レート制限)に使用される IPv4 アドレスのサブネットプレフィックス長です。デフォルト値は 24 です。",
|
||||
"rate_limit_subnet_len_ipv4_error": "IPv4 サブネットプレフィックス長は0〜32の範囲内である必要があります。",
|
||||
"rate_limit_subnet_len_ipv6": "IPv6 アドレスのサブネットプレフィックス長",
|
||||
"rate_limit_subnet_len_ipv6_desc": "rate limiting(レート制限)に使用される IPv6 アドレスのサブネットプレフィックス長です。デフォルト値は 56 です。",
|
||||
"rate_limit_subnet_len_ipv6_error": "IPv6 サブネットのプレフィックス長は0〜128の範囲内である必要があります。",
|
||||
"form_enter_rate_limit_subnet_len": "rate limiting(レート制限)のためのサブネットプレフィックス長を入力してください",
|
||||
"rate_limit_whitelist": "rate limiting(レート制限)の許可リスト",
|
||||
"rate_limit_whitelist_desc": "rate limiting(レート制限)の対象から外すIPアドレスを指定できます。",
|
||||
"rate_limit_whitelist_placeholder": "IPアドレスを1行に1つずづ入力してください。",
|
||||
"blocking_ipv4_desc": "ブロックされたAリクエストに対して応答されるIPアドレス",
|
||||
"blocking_ipv6_desc": "ブロックされたAAAAリクエストに対して応答されるIPアドレス",
|
||||
"blocking_mode_default": "デフォルト:Adblock系ルールによってブロックされると、ゼロIPアドレス(Aに対しては「0.0.0.0」、AAAAに対しては「::」)で応答します。/etc/hosts系ルールによってブロックされると、ルールにて指定されているIPアドレスで応答します。",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "水",
|
||||
"thursday_short": "木",
|
||||
"friday_short": "金",
|
||||
"saturday_short": "土"
|
||||
"saturday_short": "土",
|
||||
"upstream_dns_cache_configuration": "Upstream DNS cache configuration(アップストリームDNSキャッシュの構成)",
|
||||
"enable_upstream_dns_cache": "このクライアントのカスタムアップストリーム構成に対してDNSキャッシュを有効にする",
|
||||
"dns_cache_size": "DNSキャッシュサイズ(バイト単位)"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "EDNS에 사용자 지정 IP 사용",
|
||||
"edns_use_custom_ip_desc": "EDNS에 사용자 지정 IP 사용하도록 허용합니다.",
|
||||
"rate_limit_desc": "단일 클라이언트에서 허용 가능한 초 당 요청 생성 숫자 (0: 무제한)",
|
||||
"rate_limit_subnet_len_ipv4": "IPv4 주소의 서브넷 접두사 길이",
|
||||
"rate_limit_subnet_len_ipv4_desc": "속도 제한에 사용되는 IPv4 주소의 서브넷 접두사 길이입니다. 기본값은 24입니다.",
|
||||
"rate_limit_subnet_len_ipv4_error": "IPv4 서브넷 접두사 길이는 0에서 32 사이여야 합니다.",
|
||||
"rate_limit_subnet_len_ipv6": "IPv6 주소의 서브넷 접두사 길이",
|
||||
"rate_limit_subnet_len_ipv6_desc": "속도 제한에 사용되는 IPv6 주소의 서브넷 접두사 길이입니다. 기본값은 56입니다.",
|
||||
"rate_limit_subnet_len_ipv6_error": "IPv6 서브넷 접두사 길이는 0에서 128 사이여야 합니다.",
|
||||
"form_enter_rate_limit_subnet_len": "속도 제한을 위한 서브넷 접두사 길이를 입력하세요",
|
||||
"rate_limit_whitelist": "속도 제한 허용 목록",
|
||||
"rate_limit_whitelist_desc": "속도 제한에서 제외되는 IP 주소",
|
||||
"rate_limit_whitelist_placeholder": "한 줄에 하나씩 IP 주소를 입력하세요.",
|
||||
"blocking_ipv4_desc": "차단된 A 요청에 대해서 반환할 IP 주소",
|
||||
"blocking_ipv6_desc": "차단된 AAAA 요청에 대해서 반환할 IP 주소",
|
||||
"blocking_mode_default": "기본: Adblock 스타일 규칙에 의해 차단되면 제로 IP 주소(A는 0.0.0.0; AAAA는 ::)로 응답합니다; /etc/hosts 스타일 규칙에 의해 차단되면 규칙에 정의된 IP 주소로 응답합니다",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "수",
|
||||
"thursday_short": "목",
|
||||
"friday_short": "금",
|
||||
"saturday_short": "토"
|
||||
"saturday_short": "토",
|
||||
"upstream_dns_cache_configuration": "업스트림 DNS 캐시 설정",
|
||||
"enable_upstream_dns_cache": "이 클라이언트의 사용자 지정 업스트림 설정에서 DNS 캐싱 사용",
|
||||
"dns_cache_size": "DNS 캐시 크기(바이트)"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Aangepast IP-adres gebruiken voor EDNS",
|
||||
"edns_use_custom_ip_desc": "Toestaan om aangepast IP-adres voor EDNS te gebruiken",
|
||||
"rate_limit_desc": "Het aantal verzoeken per seconde toegelaten per toestel. 0 betekent onbeperkt.",
|
||||
"rate_limit_subnet_len_ipv4": "Lengte subnetvoorvoegsel voor IPv4-adressen",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Lengte subnetvoorvoegsel voor IPv4-adressen die worden gebruikt voor snelheidsbeperking. De standaardwaarde is 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "De lengte van het IPv4-subnetvoorvoegsel moet tussen 0 en 32 liggen",
|
||||
"rate_limit_subnet_len_ipv6": "Lengte subnetvoorvoegsel voor IPv6-adressen",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Lengte subnetvoorvoegsel voor IPv6-adressen die worden gebruikt voor snelheidsbeperking. De standaardwaarde is 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "De lengte van het IPv6-subnetvoorvoegsel moet tussen 0 en 128 liggen",
|
||||
"form_enter_rate_limit_subnet_len": "Voer de lengte van het subnetvoorvoegsel in voor snelheidsbeperking",
|
||||
"rate_limit_whitelist": "Toelatingslijst voor snelheidsbeperking",
|
||||
"rate_limit_whitelist_desc": "IP-adressen uitgesloten van snelheidsbeperking",
|
||||
"rate_limit_whitelist_placeholder": "Voer één IP-adres per regel in",
|
||||
"blocking_ipv4_desc": "IP-adres dat moet worden teruggegeven voor een geblokkeerd A-verzoek",
|
||||
"blocking_ipv6_desc": "IP-adres dat moet worden teruggegeven voor een geblokkeerd A-verzoek",
|
||||
"blocking_mode_default": "Standaard: Reageer met een nul IP adres (0.0.0.0 for A; :: voor AAAA) wanneer geblokkeerd door een Adblock-type regel; reageer met het IP-adres dat is opgegeven in de regel wanneer geblokkeerd door een /etc/hosts type regel",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "wo",
|
||||
"thursday_short": "do",
|
||||
"friday_short": "vr",
|
||||
"saturday_short": "za"
|
||||
"saturday_short": "za",
|
||||
"upstream_dns_cache_configuration": "Upstream DNS-cacheconfiguratie",
|
||||
"enable_upstream_dns_cache": "DNS-caching inschakelen voor de aangepaste upstream-configuratie van deze client",
|
||||
"dns_cache_size": "DNS-cachegrootte, in bytes"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Użyj niestandardowego adresu IP dla EDNS",
|
||||
"edns_use_custom_ip_desc": "Zezwól na użycie niestandardowego adresu IP dla EDNS",
|
||||
"rate_limit_desc": "Liczba żądań na sekundę dozwolona na klienta. Ustawienie wartości 0 oznacza brak ograniczeń.",
|
||||
"rate_limit_subnet_len_ipv4": "Długość maski podsieci dla adresów IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Długość maski podsieci dla adresów IPv4 używanych do ograniczania prędkości. Domyślnie jest to 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Długość maski podsieci IPv4 powinna wynosić od 0 do 32",
|
||||
"rate_limit_subnet_len_ipv6": "Długość prefiksu podsieci dla adresów IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Długość prefiksu podsieci dla adresów IPv6 używanych do ograniczania szybkości. Domyślnie jest to 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Długość prefiksu podsieci IPv6 powinna wynosić od 0 do 128",
|
||||
"form_enter_rate_limit_subnet_len": "Wprowadź długość prefiksu podsieci dla ograniczenia prędkości",
|
||||
"rate_limit_whitelist": "Lista zezwoleń ograniczających prędkość",
|
||||
"rate_limit_whitelist_desc": "Adresy IP wykluczone z ograniczania prędkości",
|
||||
"rate_limit_whitelist_placeholder": "Wprowadź po jednym adresie IP w każdym wierszu",
|
||||
"blocking_ipv4_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania A",
|
||||
"blocking_ipv6_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania AAAA",
|
||||
"blocking_mode_default": "Domyślna: Odpowiedz z zerowym adresem IP (0.0.0.0 dla A; :: dla AAAA) po zablokowaniu przez regułę Adblock; odpowiedź adresem IP wpisanym w regule, jeśli jest blokowany przez regułę w stylu /etc/hosts",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Śro",
|
||||
"thursday_short": "Czw",
|
||||
"friday_short": "Pt",
|
||||
"saturday_short": "Sob"
|
||||
"saturday_short": "Sob",
|
||||
"upstream_dns_cache_configuration": "Konfiguracja pamięci podręcznej upstream serwerów DNS",
|
||||
"enable_upstream_dns_cache": "Włącz pamięć podręczną dla niestandardowej konfiguracji serwera upstream tego klienta",
|
||||
"dns_cache_size": "Rozmiar pamięci podręcznej DNS, w bajtach"
|
||||
}
|
||||
|
|
|
@ -303,13 +303,23 @@
|
|||
"download_mobileconfig_dot": "BAixar .mobileconfig para DNS-sobre-TLS",
|
||||
"download_mobileconfig": "Baixar arquivo de configuração",
|
||||
"plain_dns": "DNS simples",
|
||||
"form_enter_rate_limit": "Insira a taxa limite",
|
||||
"rate_limit": "Taxa limite",
|
||||
"form_enter_rate_limit": "Insira a velocidade limite",
|
||||
"rate_limit": "Velocidade limite",
|
||||
"edns_enable": "Ativar a sub-rede do cliente EDNS",
|
||||
"edns_cs_desc": "Adicione a opção de sub-rede de cliente EDNS (ECS) às solicitações de servidor DNS primário e registre os valores enviados pelos clientes no registro de consulta.",
|
||||
"edns_use_custom_ip": "Usar IP personalizado para EDNS",
|
||||
"edns_use_custom_ip_desc": "Permitir o uso de IP personalizado para EDNS",
|
||||
"rate_limit_desc": "O número de solicitações por segundo permitidas por cliente. Definir como 0 significa que não há limite.",
|
||||
"rate_limit_subnet_len_ipv4": "Comprimento do prefixo de sub-rede para endereços IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Comprimento do prefixo de sub-rede para endereços IPv4 usados para limitação de velocidade. O padrão é 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "O comprimento do prefixo da sub-rede IPv4 deve estar entre 0 e 32",
|
||||
"rate_limit_subnet_len_ipv6": "Comprimento do prefixo de sub-rede para endereços IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Comprimento do prefixo de sub-rede para endereços IPv6 usados para limitação de velocidade. O padrão é 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "O comprimento do prefixo da sub-rede IPv6 deve estar entre 0 e 128",
|
||||
"form_enter_rate_limit_subnet_len": "Insira o comprimento do prefixo da sub-rede para limitação de taxa",
|
||||
"rate_limit_whitelist": "Lista de permissões de limitação de velocidade",
|
||||
"rate_limit_whitelist_desc": "Endereços IP excluídos da limitação de velocidade",
|
||||
"rate_limit_whitelist_placeholder": "Insira um endereço IP por linha",
|
||||
"blocking_ipv4_desc": "Endereço de IP a ser retornado para uma solicitação bloqueada",
|
||||
"blocking_ipv6_desc": "Endereço de IP a ser retornado para uma solicitação AAAA bloqueada",
|
||||
"blocking_mode_default": "Padrão: Responder com zero endereço IP (0.0.0.0 para A; :: para AAAA) quando bloqueado pela regra de estilo Adblock; responde com o endereço IP especificado na regra quando bloqueado pela regra /etc/hosts-style",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Quar",
|
||||
"thursday_short": "Qui",
|
||||
"friday_short": "Sex",
|
||||
"saturday_short": "Sab"
|
||||
"saturday_short": "Sab",
|
||||
"upstream_dns_cache_configuration": "Configuração do cache de DNS upstream",
|
||||
"enable_upstream_dns_cache": "Ativar o armazenamento em cache do DNS para a configuração de upstream personalizada deste cliente",
|
||||
"dns_cache_size": "Tamanho do cache do DNS, em bytes"
|
||||
}
|
||||
|
|
|
@ -303,13 +303,23 @@
|
|||
"download_mobileconfig_dot": "Transferir .mobileconfig para DNS-sobre-TLS",
|
||||
"download_mobileconfig": "Transferir ficheiro de configuração",
|
||||
"plain_dns": "DNS simples",
|
||||
"form_enter_rate_limit": "Insira o limite de taxa",
|
||||
"rate_limit": "Limite de taxa",
|
||||
"form_enter_rate_limit": "Insira o limite de velocidade",
|
||||
"rate_limit": "Limite de velocidade",
|
||||
"edns_enable": "Ativar a sub-rede do cliente EDNS",
|
||||
"edns_cs_desc": "Adicione a opção de sub-rede de cliente EDNS (ECS) às solicitações de servidor DNS primário e registre os valores enviados pelos clientes no registo de consulta.",
|
||||
"edns_use_custom_ip": "Usar IP personalizado para EDNS",
|
||||
"edns_use_custom_ip_desc": "Permitir a utilização de IP personalizado para EDNS",
|
||||
"rate_limit_desc": "O número de solicitações por segundo permitido por cliente. Configurando para 0 significa sem limite.",
|
||||
"rate_limit_subnet_len_ipv4": "Comprimento do prefixo de sub-rede para endereços IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Comprimento do prefixo de sub-rede para endereços IPv4 usados para limitação de velocidade. O padrão é 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "O comprimento do prefixo da sub-rede IPv4 deve estar entre 0 e 32",
|
||||
"rate_limit_subnet_len_ipv6": "Comprimento do prefixo de sub-rede para endereços IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Comprimento do prefixo de sub-rede para endereços IPv6 usados para limitação de velocidade. O padrão é 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "O comprimento do prefixo da sub-rede IPv6 deve situar-se entre 0 e 128",
|
||||
"form_enter_rate_limit_subnet_len": "Introduza o comprimento do prefixo da sub-rede para limitação da velocidade",
|
||||
"rate_limit_whitelist": "Lista de permissões de limitação de velocidade",
|
||||
"rate_limit_whitelist_desc": "Endereços IP excluídos da limitação de velocidade",
|
||||
"rate_limit_whitelist_placeholder": "Insira um endereço IP por linha",
|
||||
"blocking_ipv4_desc": "Endereço IP a ser devolvido para uma solicitação A bloqueada",
|
||||
"blocking_ipv6_desc": "Endereço IP a ser devolvido para uma solicitação AAAA bloqueada",
|
||||
"blocking_mode_default": "Predefinido: Responder com zero endereço IP (0.0.0.0 para A; :: para AAAA) quando bloqueado pela regra de estilo Adblock; responde com o endereço IP especificado na regra quando bloqueado pela regra /etc/hosts-style",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Quarta",
|
||||
"thursday_short": "Quinta",
|
||||
"friday_short": "Sexta",
|
||||
"saturday_short": "Sábado"
|
||||
"saturday_short": "Sábado",
|
||||
"upstream_dns_cache_configuration": "Configuração da cache do DNS upstream",
|
||||
"enable_upstream_dns_cache": "Ativar o armazenamento em cache do DNS para a configuração de upstream personalizada deste cliente",
|
||||
"dns_cache_size": "Tamanho da cache DNS, em bytes"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Utilizați IP personalizat pentru EDNS",
|
||||
"edns_use_custom_ip_desc": "Permiteți utilizarea IP-ului personalizat pentru EDNS",
|
||||
"rate_limit_desc": "Numărul de interogări pe secundă permise pe client. Setarea la 0 înseamnă că nu există limită.",
|
||||
"rate_limit_subnet_len_ipv4": "Lungimea prefixului de subrețea pentru adrese IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Lungimea prefixului de subrețea pentru adresele IPv4 utilizate pentru limitarea ratei. Valoarea implicită este 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Lungimea prefixului de subrețea IPv4 ar trebui să fie între 0 și 32",
|
||||
"rate_limit_subnet_len_ipv6": "Lungimea prefixului de subrețea pentru adrese IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Lungimea prefixului de subrețea pentru adresele IPv6 utilizate pentru limitarea ratei. Valoarea implicită este 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Lungimea prefixului de subrețea IPv6 ar trebui să fie între 0 și 128",
|
||||
"form_enter_rate_limit_subnet_len": "Introduceți lungimea prefixului de subrețea pentru limitarea ratei",
|
||||
"rate_limit_whitelist": "Lista permisă pentru limitarea ratei",
|
||||
"rate_limit_whitelist_desc": "Adresele IP excluse de la limitarea ratei",
|
||||
"rate_limit_whitelist_placeholder": "Introduceți o adresă IP per linie",
|
||||
"blocking_ipv4_desc": "Adresa IP de returnat pentru o cerere A de blocare",
|
||||
"blocking_ipv6_desc": "Adresa IP de returnat pentru o cerere AAAA de blocare",
|
||||
"blocking_mode_default": "Implicit: Răspunde cu adresa IP (0.0.0.0 for A; :: pentru AAAA) când sunt blocate de regulă tip Adblock; răspunde cu adresa IP specificată în regulă când sunt blocate de regula tip /etc/hosts",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "mi",
|
||||
"thursday_short": "jo",
|
||||
"friday_short": "vi",
|
||||
"saturday_short": "sa"
|
||||
"saturday_short": "sa",
|
||||
"upstream_dns_cache_configuration": "Configurarea cache-ului DNS în amonte",
|
||||
"enable_upstream_dns_cache": "Activați memoria cache DNS pentru configurația personalizată în amonte a acestui client",
|
||||
"dns_cache_size": "Dimensiunea cache-ului DNS, în octeți"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Использовать указанный IP для EDNS",
|
||||
"edns_use_custom_ip_desc": "Разрешить использовать собственный IP для EDNS",
|
||||
"rate_limit_desc": "Ограничение на количество запросов в секунду для каждого клиента (0 — неограниченно).",
|
||||
"rate_limit_subnet_len_ipv4": "Длина префикса подсети для IPv4-адресов",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Длина префикса подсети для IPv4-адресов, используемых для ограничения скорости. По умолчанию 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Длина префикса IPv4-подсетей должна составлять от 0 до 32",
|
||||
"rate_limit_subnet_len_ipv6": "Длина префикса подсети для IPv6-адресов",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Длина префикса подсети для IPv6-адресов, используемых для ограничения скорости. По умолчанию 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Длина префикса IPv6-подсетей должна составлять от 0 до 128",
|
||||
"form_enter_rate_limit_subnet_len": "Введите длину префикса подсети для ограничения скорости",
|
||||
"rate_limit_whitelist": "Белый список ограничения скорости",
|
||||
"rate_limit_whitelist_desc": "IP-адреса, на которые не распространяется ограничение скорости",
|
||||
"rate_limit_whitelist_placeholder": "Введите по одному адресу на строчку",
|
||||
"blocking_ipv4_desc": "IP-адрес, возвращаемый при блокировке A-запроса",
|
||||
"blocking_ipv6_desc": "IP-адрес, возвращаемый при блокировке AAAA-запроса",
|
||||
"blocking_mode_default": "Стандартный: Отвечает с нулевым IP-адресом, (0.0.0.0 для A; :: для AAAA) когда заблокировано правилом в стиле Adblock; отвечает с IP-адресом, указанным в правиле, когда заблокировано правилом в стиле файлов hosts",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Ср",
|
||||
"thursday_short": "Чт",
|
||||
"friday_short": "Пт",
|
||||
"saturday_short": "Сб"
|
||||
"saturday_short": "Сб",
|
||||
"upstream_dns_cache_configuration": "Конфигурация кеша upstream DNS-серверов",
|
||||
"enable_upstream_dns_cache": "Включить кеширование для пользовательской конфигурации upstream-серверов этого клиента",
|
||||
"dns_cache_size": "Размер DNS-кеша в байтах"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Použiť vlastnú IP adresu pre EDNS",
|
||||
"edns_use_custom_ip_desc": "Povoliť používanie vlastnej IP adresy pre EDNS",
|
||||
"rate_limit_desc": "Počet požiadaviek za sekundu, ktoré môže jeden klient vykonať. Nastavenie na hodnotu 0 znamená neobmedzene.",
|
||||
"rate_limit_subnet_len_ipv4": "Dĺžka prefixu podsiete pre adresy IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Dĺžka prefixu podsiete pre adresy IPv4 používané na obmedzenie rýchlosti. Predvolená hodnota je 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Dĺžka prefixu podsiete IPv4 musí byť od 0 do 32",
|
||||
"rate_limit_subnet_len_ipv6": "Dĺžka prefixu podsiete pre adresy IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Dĺžka prefixu podsiete pre adresy IPv6 používané na obmedzenie rýchlosti. Predvolená hodnota je 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Dĺžka prefixu podsiete IPv6 musí byť od 0 do 128",
|
||||
"form_enter_rate_limit_subnet_len": "Zadajte dĺžku prefixu podsiete pre obmedzenie rýchlosti",
|
||||
"rate_limit_whitelist": "Zoznam povolení obmedzujúcich rýchlosť",
|
||||
"rate_limit_whitelist_desc": "IP adresy vylúčené z obmedzenia rýchlosti",
|
||||
"rate_limit_whitelist_placeholder": "Na každý riadok zadajte IP adresu jedného servera",
|
||||
"blocking_ipv4_desc": "IP adresa, ktorá sa má vrátiť v prípade blokovanej žiadosti A",
|
||||
"blocking_ipv6_desc": "IP adresa, ktorá sa má vrátiť v prípade blokovanej žiadosti AAAA",
|
||||
"blocking_mode_default": "Predvolené: Odpovedať nulovou adresou IP (0,0.0.0 pre A; :: pre AAAA), keď je blokovaná pravidlom v štýle Adblock; odpovedať IP adresou uvedenou v pravidle, keď je blokovaná pravidlom v štýle /etc/hosts",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Str",
|
||||
"thursday_short": "Štr",
|
||||
"friday_short": "Pia",
|
||||
"saturday_short": "Sob"
|
||||
"saturday_short": "Sob",
|
||||
"upstream_dns_cache_configuration": "Konfigurácia cache pamäte DNS pre upstream",
|
||||
"enable_upstream_dns_cache": "Zapnúť ukladanie DNS do cache pamäte pre vlastnú konfiguráciu odosielania tohto klienta",
|
||||
"dns_cache_size": "Veľkosť cache pamäte DNS v bajtoch"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Uporabi IP po meri za EDNS",
|
||||
"edns_use_custom_ip_desc": "Dovoli uporabo naslova IP po meri za EDNS",
|
||||
"rate_limit_desc": "Dovoljeno število zahtev na sekundo na odjemalca. Nastavitev na 0 pomeni brez omejitve.",
|
||||
"rate_limit_subnet_len_ipv4": "Dolžina predpone podomrežja za naslove IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Dolžina predpone podomrežja za naslove IPv4, ki se uporabljajo za omejevanje hitrosti. Privzeto je 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Dolžina predpone podomrežja IPv4 mora biti med 0 in 32",
|
||||
"rate_limit_subnet_len_ipv6": "Dolžina predpone podomrežja za naslove IPv4",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Dolžina predpone podomrežja za naslove IPv6, ki se uporabljajo za omejevanje hitrosti. Privzeta vrednost je 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Dolžina podomrežne predpone IPv6 mora biti med 0 in 128",
|
||||
"form_enter_rate_limit_subnet_len": "Vnesite dolžino predpone podomrežja za omejitev hitrosti",
|
||||
"rate_limit_whitelist": "Seznam dovoljenih za omejevanje hitrosti",
|
||||
"rate_limit_whitelist_desc": "Naslovi IP so izključeni iz omejitve hitrosti",
|
||||
"rate_limit_whitelist_placeholder": "Vnesite en naslov IP na vrstico",
|
||||
"blocking_ipv4_desc": "IP naslov, ki mora biti vrnjen za onemogočeno zahtevo A",
|
||||
"blocking_ipv6_desc": "IP naslov, ki mora biti vrnjen za onemogočeno zahtevo AAAA",
|
||||
"blocking_mode_default": "Privzeto: odgovori z ničelnim naslovom IP (0.0.0.0 za A; :: za AAAA), ko je onemogočen s pravilom v slogu Adblocka; odgovor z naslovom IP, določenim v pravilu, ko je onemogočen s pravilom /etc/hosts",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Sre",
|
||||
"thursday_short": "Čet",
|
||||
"friday_short": "Pet",
|
||||
"saturday_short": "Sob"
|
||||
"saturday_short": "Sob",
|
||||
"upstream_dns_cache_configuration": "Nastavitve predpomnilnika gorvodnega DNS",
|
||||
"enable_upstream_dns_cache": "Omogoči predpomnjenje nastavitev gorvodnega DNS po meri tega odjemalca",
|
||||
"dns_cache_size": "Velikost predpomnilnika DNS, v bajtih"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Koristi prilagođeni IP za EDNS",
|
||||
"edns_use_custom_ip_desc": "Dozvoli korišćenje prilagođenog IP-a za EDNS",
|
||||
"rate_limit_desc": "Broj zahteva u sekundi dozvoljen po klijentu. Postavljanje na 0 znači da nema ograničenja.",
|
||||
"rate_limit_subnet_len_ipv4": "Dužina prefixa podmreže za IPv4 adrese",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Dužina prefixa podmreže za IPv4 adrese koje se koriste za ograničavanje brzine. Podrazumevano je 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Dužina prefixa IPv4 podmreže treba da bude između 0 i 32",
|
||||
"rate_limit_subnet_len_ipv6": "Dužina prefixa podmreže za IPv6 adrese",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Dužina prefixa podmreže za IPv6 adrese koje se koriste za ograničavanje brzine. Podrazumevano je 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Dužina prefixa IPv6 podmreže treba da bude između 0 i 128",
|
||||
"form_enter_rate_limit_subnet_len": "Unesite dužinu prefixa podmreže da biste ograničili brzinu",
|
||||
"rate_limit_whitelist": "Lista dozvoljenih lista za ograničavanje brzine",
|
||||
"rate_limit_whitelist_desc": "IP adrese koje nisu obuhvaćene ograničenjem brzine",
|
||||
"rate_limit_whitelist_placeholder": "Unesite jednu IP adresu servera po redu",
|
||||
"blocking_ipv4_desc": "IP adresa koja će biti vraćena za blokirane zahteve",
|
||||
"blocking_ipv6_desc": "IP adresa koja će biti vraćena za blokirane AAAA zahteve",
|
||||
"blocking_mode_default": "Podrazumevano: Odgovara sa REFUSED kada je blokirano od Adblock-style pravila; odgovara sa IP adresom koja je određena u pravilu kada je blokiran od /etc/hosts-style pravila",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Sre",
|
||||
"thursday_short": "Čet",
|
||||
"friday_short": "Pet",
|
||||
"saturday_short": "Sub"
|
||||
"saturday_short": "Sub",
|
||||
"upstream_dns_cache_configuration": "Konfiguracija keša upstream DNS servera",
|
||||
"enable_upstream_dns_cache": "Uključite keširanje za korisničku konfiguraciju upstream servera ovog klijenta",
|
||||
"dns_cache_size": "Veličina DNS keša, u bajtovima"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,15 @@
|
|||
"edns_use_custom_ip": "Använd anpassad IP för EDNS",
|
||||
"edns_use_custom_ip_desc": "Tillåt att använda anpassad IP för EDNS",
|
||||
"rate_limit_desc": "Antalet förfrågningar per sekund som tillåts per klient. Att sätta den till 0 innebär ingen gräns.",
|
||||
"rate_limit_subnet_len_ipv4": "Prefixlängd för subnät för IPv4-adresser",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Subnätprefixlängd för IPv4-adresser som används för hastighetsbegränsning. Standard är 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "IPv4-subnätets prefixlängd ska vara mellan 0 och 32",
|
||||
"rate_limit_subnet_len_ipv6": "Prefixlängd för subnät för IPv6-adresser",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Subnätprefixlängd för IPv6-adresser som används för hastighetsbegränsning. Standard är 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "IPv6-subnätets prefixlängd ska vara mellan 0 och 128",
|
||||
"form_enter_rate_limit_subnet_len": "Ange subnätprefixlängd för hastighetsbegränsning",
|
||||
"rate_limit_whitelist_desc": "IP-adresser uteslutna från hastighetsbegränsning",
|
||||
"rate_limit_whitelist_placeholder": "Ange en IP-adress per rad",
|
||||
"blocking_ipv4_desc": "IP adress som ska returneras för en blockerad A förfrågan",
|
||||
"blocking_ipv6_desc": "IP adress som ska returneras för en blockerad AAAA förfrågan",
|
||||
"blocking_mode_default": "Standard: Svara med noll IP-adress (0.0.0.0 för A; :: för AAAA) när det blockeras av regel i Adblock-stil; svara med IP-adressen som anges i regeln när den blockeras av regel i /etc/hosts-stil",
|
||||
|
@ -724,5 +733,8 @@
|
|||
"wednesday_short": "Ons",
|
||||
"thursday_short": "Tor",
|
||||
"friday_short": "Fre",
|
||||
"saturday_short": "Lör"
|
||||
"saturday_short": "Lör",
|
||||
"upstream_dns_cache_configuration": "Konfiguration av uppströms DNS-cache",
|
||||
"enable_upstream_dns_cache": "Aktivera DNS-cachelagring för den här klientens anpassade uppströmskonfiguration",
|
||||
"dns_cache_size": "DNS-cachestorlek, i byte"
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
"filters": "Filtreler",
|
||||
"filter": "Filtre",
|
||||
"query_log": "Sorgu Günlüğü",
|
||||
"compact": "Yoğun",
|
||||
"compact": "Sık",
|
||||
"nothing_found": "Hiçbir şey bulunamadı",
|
||||
"faq": "SSS",
|
||||
"version": "Sürüm",
|
||||
|
@ -310,9 +310,19 @@
|
|||
"edns_use_custom_ip": "EDNS için özel IP kullan",
|
||||
"edns_use_custom_ip_desc": "EDNS için özel IP kullanımına izin ver",
|
||||
"rate_limit_desc": "İstemci başına izin verilen saniyedeki istek sayısı. 0 olarak ayarlamak, sınır olmadığı anlamına gelir.",
|
||||
"rate_limit_subnet_len_ipv4": "IPv4 adresleri için alt ağ önek uzunluğu",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Hız sınırlaması için kullanılan IPv4 adreslerinin alt ağ önek uzunluğu. Varsayılan 24'tür",
|
||||
"rate_limit_subnet_len_ipv4_error": "IPv4 alt ağ önek uzunluğu 0 ile 32 arasında olmalıdır",
|
||||
"rate_limit_subnet_len_ipv6": "IPv6 adresleri için alt ağ önek uzunluğu",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Hız sınırlaması için kullanılan IPv6 adreslerinin alt ağ önek uzunluğu. Varsayılan 56'tür",
|
||||
"rate_limit_subnet_len_ipv6_error": "IPv6 alt ağ önek uzunluğu 0 ile 128 arasında olmalıdır",
|
||||
"form_enter_rate_limit_subnet_len": "Hız sınırlaması için alt ağ önek uzunluğunu girin",
|
||||
"rate_limit_whitelist": "Hız sınırlama izin listesi",
|
||||
"rate_limit_whitelist_desc": "Hız sınırlamasından hariç tutulan IP adresleri",
|
||||
"rate_limit_whitelist_placeholder": "Her satıra bir IP adresi girin",
|
||||
"blocking_ipv4_desc": "Engellenen bir A isteği için geri döndürülecek IP adresi",
|
||||
"blocking_ipv6_desc": "Engellenen bir AAAA isteği için geri döndürülecek IP adresi",
|
||||
"blocking_mode_default": "Varsayılan: Reklam engelleme tarzı kural tarafından engellendiğinde sıfır IP adresiyle (A için 0.0.0.0; :: AAAA için) yanıt verin; /etc/hosts-tarzı kural tarafından engellendiğinde, kuralda belirtilen IP adresiyle yanıt verin",
|
||||
"blocking_mode_default": "Varsayılan: Reklam engelleme stili kuralı tarafından engellendiğinde sıfır IP adresiyle (A için 0.0.0.0; :: AAAA için) yanıt verin; /etc/hosts-tarzı kural tarafından engellendiğinde, kuralda belirtilen IP adresiyle yanıt verin",
|
||||
"blocking_mode_refused": "REFUSED: REFUSED koduyla yanıt verin",
|
||||
"blocking_mode_nxdomain": "NXDOMAIN: NXDOMAIN koduyla yanıt verin",
|
||||
"blocking_mode_null_ip": "Boş IP: Sıfır IP adresiyle yanıt verin (A için 0.0.0.0; :: AAAA için)",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Çar",
|
||||
"thursday_short": "Per",
|
||||
"friday_short": "Cum",
|
||||
"saturday_short": "Cmt"
|
||||
"saturday_short": "Cmt",
|
||||
"upstream_dns_cache_configuration": "Üst kaynak DNS önbellek yapılandırması",
|
||||
"enable_upstream_dns_cache": "Bu istemcinin özel üst kaynak yapılandırması için DNS önbelleğe almayı etkinleştir",
|
||||
"dns_cache_size": "DNS önbellek boyutu, bayt cinsinden"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"client_settings": "Налаштування клієнта",
|
||||
"example_upstream_reserved": "DNS-сервер <0>для певних доменів</0>;",
|
||||
"example_multiple_upstreams_reserved": "кілька DNS-серверів <0>для конкретних доменів</0>;",
|
||||
"example_upstream_comment": "коментар.",
|
||||
"upstream_parallel": "Використовувати паралельні запити, щоб пришвидшити вирішення одночасною чергою всіх оригінальних серверів.",
|
||||
"parallel_requests": "Паралельні запити",
|
||||
|
@ -143,6 +144,8 @@
|
|||
"enforced_save_search": "Примусовий безпечний пошук",
|
||||
"number_of_dns_query_to_safe_search": "Кількість DNS-запитів до пошукових систем, для яких примусово застосований безпечний пошук",
|
||||
"average_processing_time": "Середній час обробки",
|
||||
"average_upstream_response_time": "Середній час відгуку upstream-сервера",
|
||||
"response_time": "Час відгуку",
|
||||
"average_processing_time_hint": "Середній час обробки DNS запиту в мілісекундах",
|
||||
"block_domain_use_filters_and_hosts": "Блокування доменів за допомогою фільтрів та hosts-файлів",
|
||||
"filters_block_toggle_hint": "Ви можете налаштувати правила блокування в розділі <a>Фільтри</a>.",
|
||||
|
@ -307,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Використання користувацької IP-адреси для EDNS",
|
||||
"edns_use_custom_ip_desc": "Дозволити використовувати користувацьку IP-адресу для EDNS",
|
||||
"rate_limit_desc": "Кількість запитів в секунду, які може робити один клієнт. Встановлене значення «0» означатиме необмежену кількість.",
|
||||
"rate_limit_subnet_len_ipv4": "Довжина префікса підмережі для адрес IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Довжина префікса підмережі для адрес IPv4, які використовуються для обмеження швидкості. Типовим значенням є 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Довжина префікса підмережі IPv4 має бути від 0 до 32",
|
||||
"rate_limit_subnet_len_ipv6": "Довжина префікса підмережі для адрес IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Довжина префікса підмережі для адрес IPv6, які використовуються для обмеження швидкості. Типовим значенням є 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Довжина префікса підмережі IPv6 має бути від 0 до 128",
|
||||
"form_enter_rate_limit_subnet_len": "Введіть довжину префікса підмережі для обмеження швидкості",
|
||||
"rate_limit_whitelist": "Список дозволених обмежень швидкості",
|
||||
"rate_limit_whitelist_desc": "IP-адреси, на які не поширюється обмеження швидкості",
|
||||
"rate_limit_whitelist_placeholder": "Вводьте одну адресу на рядок",
|
||||
"blocking_ipv4_desc": "IP-адреса, яку потрібно видати для заблокованого A запиту",
|
||||
"blocking_ipv6_desc": "IP-адреса, яку потрібно видати для заблокованого АААА запиту",
|
||||
"blocking_mode_default": "Усталено: відповідь із нульовою IP-адресою (0.0.0.0 для A; :: для AAAA), якщо заблоковано правилом у Adblock-стилі; відповідь зазначеною у правилі IP-адресою, якщо заблокувано правилом у hosts-стилі",
|
||||
|
@ -721,5 +734,8 @@
|
|||
"wednesday_short": "СР",
|
||||
"thursday_short": "ЧТ",
|
||||
"friday_short": "ПТ",
|
||||
"saturday_short": "СБ"
|
||||
"saturday_short": "СБ",
|
||||
"upstream_dns_cache_configuration": "Конфігурація кешу upstream DNS-серверів",
|
||||
"enable_upstream_dns_cache": "Увімкнути кешування для користувацької конфігурації upstream-серверів цього клієнта",
|
||||
"dns_cache_size": "Розмір кешу DNS, у байтах"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "Sử dụng địa chỉ IP tùy chỉnh cho EDNS",
|
||||
"edns_use_custom_ip_desc": "Cho phép sử dụng địa chỉ IP tùy chỉnh cho EDNS",
|
||||
"rate_limit_desc": "Số lượng yêu cầu mỗi giây mà một khách hàng được phép thực hiện (0: không giới hạn)",
|
||||
"rate_limit_subnet_len_ipv4": "Độ dài tiền tố mạng con cho địa chỉ IPv4",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Độ dài tiền tố mạng con cho các địa chỉ IPv4 được sử dụng để giới hạn tốc độ. Mặc định là 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "Độ dài tiền tố mạng con IPv4 phải nằm trong khoảng từ 0 đến 32",
|
||||
"rate_limit_subnet_len_ipv6": "Độ dài tiền tố mạng con cho địa chỉ IPv6",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Độ dài tiền tố mạng con cho các địa chỉ IPv6 được sử dụng để giới hạn tốc độ. Mặc định là 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "Độ dài tiền tố mạng con IPv6 phải nằm trong khoảng từ 0 đến 128",
|
||||
"form_enter_rate_limit_subnet_len": "Nhập độ dài tiền tố mạng con để giới hạn tốc độ",
|
||||
"rate_limit_whitelist": "Danh sách cho phép giới hạn tỷ lệ",
|
||||
"rate_limit_whitelist_desc": "Địa chỉ IP bị loại trừ khỏi giới hạn tốc độ",
|
||||
"rate_limit_whitelist_placeholder": "Nhập một địa chỉ IP trên mỗi dòng",
|
||||
"blocking_ipv4_desc": "Địa chỉ IP được trả lại cho một yêu cầu A bị chặn",
|
||||
"blocking_ipv6_desc": "Địa chỉ IP được trả lại cho một yêu cầu AAA bị chặn",
|
||||
"blocking_mode_default": "Mặc định: Trả lời với NXDOMAIN khi bị chặn bởi quy tắc kiểu Adblock; phản hồi với địa chỉ IP được chỉ định trong quy tắc khi bị chặn bởi quy tắc / etc / hosts-style",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "Thứ 4",
|
||||
"thursday_short": "Thứ 5",
|
||||
"friday_short": "Thứ 6",
|
||||
"saturday_short": "Thứ 7"
|
||||
"saturday_short": "Thứ 7",
|
||||
"upstream_dns_cache_configuration": "Cấu hình bộ nhớ đệm upstream của các máy chủ DNS",
|
||||
"enable_upstream_dns_cache": "Bật bộ nhớ cache cho cấu hình ngược dòng của máy chủ upstream của khách hàng này",
|
||||
"dns_cache_size": "Kích thước bộ nhớ cache DNS, tính bằng byte"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "为 EDNS 使用自定义 IP",
|
||||
"edns_use_custom_ip_desc": "允许为 EDNS 使用自定义 IP",
|
||||
"rate_limit_desc": "每个客户端每秒钟查询次数的限制。设置为 0 意味着不限制。",
|
||||
"rate_limit_subnet_len_ipv4": "IPv4 地址子网前缀长度",
|
||||
"rate_limit_subnet_len_ipv4_desc": "用于速率限制的 IPv4 地址子网前缀长度。默认为 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "IPv4 子网前缀长度应介于 0 到 32 之间",
|
||||
"rate_limit_subnet_len_ipv6": "IPv6 地址子网前缀长度",
|
||||
"rate_limit_subnet_len_ipv6_desc": "用于速率限制的 IPv6 地址子网前缀长度。默认为 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "IPv6 子网前缀长度应介于 0 到 128 之间",
|
||||
"form_enter_rate_limit_subnet_len": "输入用于速率限制的子网前缀长度",
|
||||
"rate_limit_whitelist": "速率限制白名单",
|
||||
"rate_limit_whitelist_desc": "排除在速率限制之外的 IP 地址",
|
||||
"rate_limit_whitelist_placeholder": "每行输入一个 IP 地址",
|
||||
"blocking_ipv4_desc": "拦截 A 记录请求返回的 IP 地址",
|
||||
"blocking_ipv6_desc": "拦截 AAAA 记录请求返回的 IP 地址",
|
||||
"blocking_mode_default": "默认:被 Adblock 规则拦截时反应为零 IP 地址(A记录:0.0.0.0;AAAA记录:::);被 /etc/hosts 规则拦截时反应为规则中指定 IP 地址",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "周三",
|
||||
"thursday_short": "周四",
|
||||
"friday_short": "周五",
|
||||
"saturday_short": "周六"
|
||||
"saturday_short": "周六",
|
||||
"upstream_dns_cache_configuration": "上游 DNS 缓存配置",
|
||||
"enable_upstream_dns_cache": "为该客户端的自定义上游配置启用 DNS 缓存",
|
||||
"dns_cache_size": "DNS 缓存大小,单位:字节"
|
||||
}
|
||||
|
|
|
@ -310,6 +310,16 @@
|
|||
"edns_use_custom_ip": "為 EDNS 使用自訂的 IP",
|
||||
"edns_use_custom_ip_desc": "允許為 EDNS 使用自訂的 IP",
|
||||
"rate_limit_desc": "每個用戶端被允許的每秒請求之數量。設定它為 0 表示無限制。",
|
||||
"rate_limit_subnet_len_ipv4": "IPv4 位址的子網路前綴長度",
|
||||
"rate_limit_subnet_len_ipv4_desc": "用於速率限制的 IPv4 位址的子網路前綴長度。預設值為 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "IPv4 子網路前綴長度應在 0 至 32 之間",
|
||||
"rate_limit_subnet_len_ipv6": "IPv6 位址的子網路前綴長度",
|
||||
"rate_limit_subnet_len_ipv6_desc": "用於速率限制的 IPv6 位址的子網路前綴長度。預設值為 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "IPv6 子網路前綴長度應在 0 至 128 之間",
|
||||
"form_enter_rate_limit_subnet_len": "輸入用於速率限制的子網路前綴長度",
|
||||
"rate_limit_whitelist": "速率限制允許清單",
|
||||
"rate_limit_whitelist_desc": "從速率限制中排除的 IP 位址",
|
||||
"rate_limit_whitelist_placeholder": "每行輸入一個 IP 位址",
|
||||
"blocking_ipv4_desc": "要被返回給已封鎖的 A 請求之 IP 位址",
|
||||
"blocking_ipv6_desc": "要被返回給已封鎖的 AAAA 請求之 IP 位址",
|
||||
"blocking_mode_default": "預設:當被 AdBlock 樣式的規則封鎖時,以零值 IP 位址(0.0.0.0 供 A;:: 供 AAAA)回覆;當被 /etc/hosts 樣式的規則封鎖時,以在該規則中之已明確指定的 IP 位址回覆",
|
||||
|
@ -724,5 +734,8 @@
|
|||
"wednesday_short": "週三",
|
||||
"thursday_short": "週四",
|
||||
"friday_short": "週五",
|
||||
"saturday_short": "週六"
|
||||
"saturday_short": "週六",
|
||||
"upstream_dns_cache_configuration": "上游 DNS 快取設定",
|
||||
"enable_upstream_dns_cache": "啟用本用戶端自訂上游配置的 DNS 快取",
|
||||
"dns_cache_size": "DNS 快取大小,單位:位元"
|
||||
}
|
||||
|
|
|
@ -62,6 +62,10 @@ export const setDnsConfig = (config) => async (dispatch) => {
|
|||
data.upstream_dns = splitByNewLine(config.upstream_dns);
|
||||
hasDnsSettings = true;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(data, 'ratelimit_whitelist')) {
|
||||
data.ratelimit_whitelist = splitByNewLine(config.ratelimit_whitelist);
|
||||
hasDnsSettings = true;
|
||||
}
|
||||
|
||||
await apiClient.setDnsConfig(data);
|
||||
|
||||
|
|
|
@ -338,6 +338,40 @@ export const getDnsStatus = () => async (dispatch) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const timerStatusRequest = createAction('TIMER_STATUS_REQUEST');
|
||||
export const timerStatusFailure = createAction('TIMER_STATUS_FAILURE');
|
||||
export const timerStatusSuccess = createAction('TIMER_STATUS_SUCCESS');
|
||||
|
||||
export const getTimerStatus = () => async (dispatch) => {
|
||||
dispatch(timerStatusRequest());
|
||||
|
||||
const handleRequestError = () => {
|
||||
dispatch(addErrorToast({ error: 'dns_status_error' }));
|
||||
dispatch(dnsStatusFailure());
|
||||
window.location.reload(true);
|
||||
};
|
||||
|
||||
const handleRequestSuccess = (response) => {
|
||||
const dnsStatus = response.data;
|
||||
if (dnsStatus.protection_disabled_duration === 0) {
|
||||
dnsStatus.protection_disabled_duration = null;
|
||||
}
|
||||
const { running } = dnsStatus;
|
||||
const runningStatus = dnsStatus && running;
|
||||
if (runningStatus === true) {
|
||||
dispatch(timerStatusSuccess(dnsStatus));
|
||||
} else {
|
||||
dispatch(setDnsRunningStatus(running));
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
checkStatus(handleRequestSuccess, handleRequestError);
|
||||
} catch (error) {
|
||||
handleRequestError();
|
||||
}
|
||||
};
|
||||
|
||||
export const testUpstreamRequest = createAction('TEST_UPSTREAM_REQUEST');
|
||||
export const testUpstreamFailure = createAction('TEST_UPSTREAM_FAILURE');
|
||||
export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS');
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
} from '../../helpers/constants';
|
||||
import { getLogsUrlParams, setHtmlLangAttr, setUITheme } from '../../helpers/helpers';
|
||||
import Header from '../Header';
|
||||
import { changeLanguage, getDnsStatus } from '../../actions';
|
||||
import { changeLanguage, getDnsStatus, getTimerStatus } from '../../actions';
|
||||
|
||||
import Dashboard from '../../containers/Dashboard';
|
||||
import SetupGuide from '../../containers/SetupGuide';
|
||||
|
@ -126,6 +126,18 @@ const App = () => {
|
|||
|
||||
useEffect(() => {
|
||||
dispatch(getDnsStatus());
|
||||
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
dispatch(getTimerStatus());
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const setLanguage = () => {
|
||||
|
|
|
@ -79,6 +79,10 @@ const ClientsTable = ({
|
|||
} else {
|
||||
config.tags = [];
|
||||
}
|
||||
|
||||
if (typeof values.upstreams_cache_size === 'string') {
|
||||
config.upstreams_cache_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (modalType === MODAL_TYPE.EDIT_FILTERS) {
|
||||
|
|
|
@ -12,8 +12,13 @@ import i18n from '../../../i18n';
|
|||
import Tabs from '../../ui/Tabs';
|
||||
import Examples from '../Dns/Upstream/Examples';
|
||||
import { ScheduleForm } from '../../Filters/Services/ScheduleForm';
|
||||
import { toggleAllServices, trimLinesAndRemoveEmpty, captitalizeWords } from '../../../helpers/helpers';
|
||||
import {
|
||||
toggleAllServices,
|
||||
trimLinesAndRemoveEmpty,
|
||||
captitalizeWords,
|
||||
} from '../../../helpers/helpers';
|
||||
import {
|
||||
toNumber,
|
||||
renderInputField,
|
||||
renderGroupField,
|
||||
CheckboxField,
|
||||
|
@ -21,7 +26,7 @@ import {
|
|||
renderTextareaField,
|
||||
} from '../../../helpers/form';
|
||||
import { validateClientId, validateRequiredValue } from '../../../helpers/validators';
|
||||
import { CLIENT_ID_LINK, FORM_NAME } from '../../../helpers/constants';
|
||||
import { CLIENT_ID_LINK, FORM_NAME, UINT32_RANGE } from '../../../helpers/constants';
|
||||
import './Service.css';
|
||||
|
||||
const settingsCheckboxes = [
|
||||
|
@ -307,6 +312,35 @@ let Form = (props) => {
|
|||
normalizeOnBlur={trimLinesAndRemoveEmpty}
|
||||
/>
|
||||
<Examples />
|
||||
<div className="form__label--bold mt-5 mb-3">
|
||||
{t('upstream_dns_cache_configuration')}
|
||||
</div>
|
||||
<div className="form__group mb-2">
|
||||
<Field
|
||||
name="upstreams_cache_enabled"
|
||||
type="checkbox"
|
||||
component={CheckboxField}
|
||||
placeholder={t('enable_upstream_dns_cache')}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group form__group--settings">
|
||||
<label
|
||||
htmlFor="upstreams_cache_size"
|
||||
className="form__label"
|
||||
>
|
||||
{t('dns_cache_size')}
|
||||
</label>
|
||||
<Field
|
||||
name="upstreams_cache_size"
|
||||
type="number"
|
||||
component={renderInputField}
|
||||
placeholder={t('enter_cache_size')}
|
||||
className="form-control"
|
||||
normalize={toNumber}
|
||||
min={0}
|
||||
max={UINT32_RANGE.MAX}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -27,7 +27,7 @@ class Leases extends Component {
|
|||
<div className="logs__row logs__row--center">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-icon--green btn-outline-secondary btn-sm"
|
||||
className="btn btn-icon btn-icon--green btn-outline-success btn-sm"
|
||||
title={t('make_static')}
|
||||
onClick={this.convertToStatic(row)}
|
||||
disabled={disabledLeasesButton}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Trans, useTranslation } from 'react-i18next';
|
|||
import {
|
||||
renderInputField,
|
||||
renderRadioField,
|
||||
renderTextareaField,
|
||||
CheckboxField,
|
||||
toNumber,
|
||||
} from '../../../../helpers/form';
|
||||
|
@ -14,7 +15,10 @@ import {
|
|||
validateIpv6,
|
||||
validateRequiredValue,
|
||||
validateIp,
|
||||
validateIPv4Subnet,
|
||||
validateIPv6Subnet,
|
||||
} from '../../../../helpers/validators';
|
||||
import { removeEmptyLines } from '../../../../helpers/helpers';
|
||||
import { BLOCKING_MODES, FORM_NAME, UINT32_RANGE } from '../../../../helpers/constants';
|
||||
|
||||
const checkboxes = [
|
||||
|
@ -90,6 +94,69 @@ const Form = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 col-md-7">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit_subnet_len_ipv4"
|
||||
className="form__label form__label--with-desc">
|
||||
<Trans>rate_limit_subnet_len_ipv4</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>rate_limit_subnet_len_ipv4_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name="ratelimit_subnet_len_ipv4"
|
||||
type="number"
|
||||
component={renderInputField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_rate_limit_subnet_len')}
|
||||
normalize={toNumber}
|
||||
validate={[validateRequiredValue, validateIPv4Subnet]}
|
||||
min={0}
|
||||
max={32}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 col-md-7">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit_subnet_len_ipv6"
|
||||
className="form__label form__label--with-desc">
|
||||
<Trans>rate_limit_subnet_len_ipv6</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>rate_limit_subnet_len_ipv6_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name="ratelimit_subnet_len_ipv6"
|
||||
type="number"
|
||||
component={renderInputField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_rate_limit_subnet_len')}
|
||||
normalize={toNumber}
|
||||
validate={[validateRequiredValue, validateIPv6Subnet]}
|
||||
min={0}
|
||||
max={128}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 col-md-7">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit_whitelist"
|
||||
className="form__label form__label--with-desc">
|
||||
<Trans>rate_limit_whitelist</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>rate_limit_whitelist_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name="ratelimit_whitelist"
|
||||
component={renderTextareaField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('rate_limit_whitelist_placeholder')}
|
||||
normalizeOnBlur={removeEmptyLines}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
|
|
|
@ -11,6 +11,9 @@ const Config = () => {
|
|||
const {
|
||||
blocking_mode,
|
||||
ratelimit,
|
||||
ratelimit_subnet_len_ipv4,
|
||||
ratelimit_subnet_len_ipv6,
|
||||
ratelimit_whitelist,
|
||||
blocking_ipv4,
|
||||
blocking_ipv6,
|
||||
blocked_response_ttl,
|
||||
|
@ -36,6 +39,9 @@ const Config = () => {
|
|||
<Form
|
||||
initialValues={{
|
||||
ratelimit,
|
||||
ratelimit_subnet_len_ipv4,
|
||||
ratelimit_subnet_len_ipv6,
|
||||
ratelimit_whitelist,
|
||||
blocking_mode,
|
||||
blocking_ipv4,
|
||||
blocking_ipv6,
|
||||
|
|
|
@ -26,6 +26,10 @@ export const R_WIN_ABSOLUTE_PATH = /^([a-zA-Z]:)?(\\|\/)(?:[^\\/:*?"<>|\x00]+\\)
|
|||
|
||||
export const R_CLIENT_ID = /^[a-z0-9-]{1,63}$/;
|
||||
|
||||
export const R_IPV4_SUBNET = /^([0-9]|[1-2][0-9]|3[0-2])?$/;
|
||||
|
||||
export const R_IPV6_SUBNET = /^([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])?$/;
|
||||
|
||||
export const MIN_PASSWORD_LENGTH = 8;
|
||||
export const MAX_PASSWORD_LENGTH = 72;
|
||||
|
||||
|
|
|
@ -190,6 +190,12 @@ export default {
|
|||
"homepage": "https://github.com/hagezi/dns-blocklists#piracy",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_46.txt"
|
||||
},
|
||||
"hagezi_encrypted_dns_vpn_tor_proxy_bypass": {
|
||||
"name": "HaGeZi's Encrypted DNS/VPN/TOR/Proxy Bypass",
|
||||
"categoryId": "security",
|
||||
"homepage": "https://github.com/hagezi/dns-blocklists#bypass",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_52.txt"
|
||||
},
|
||||
"hagezi_gambling_blocklist": {
|
||||
"name": "HaGeZi's Gambling Blocklist",
|
||||
"categoryId": "other",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"timeUpdated": "2023-11-10T12:55:56.663Z",
|
||||
"timeUpdated": "2023-12-01T15:24:07.522Z",
|
||||
"categories": {
|
||||
"0": "audio_video_player",
|
||||
"1": "comments",
|
||||
|
@ -640,7 +640,8 @@
|
|||
"name": "AdChina",
|
||||
"categoryId": 4,
|
||||
"url": "http://www.adchina.com/",
|
||||
"companyId": "alibaba"
|
||||
"companyId": null,
|
||||
"source": "AdGuard"
|
||||
},
|
||||
"adcito": {
|
||||
"name": "Adcito",
|
||||
|
@ -1321,6 +1322,13 @@
|
|||
"companyId": "adobe",
|
||||
"source": "AdGuard"
|
||||
},
|
||||
"adobe_experience_league": {
|
||||
"name": "Adobe Experience League",
|
||||
"categoryId": 6,
|
||||
"url": "https://experienceleague.adobe.com/",
|
||||
"companyId": "adobe",
|
||||
"source": "AdGuard"
|
||||
},
|
||||
"adobe_login": {
|
||||
"name": "Adobe Login",
|
||||
"categoryId": 2,
|
||||
|
@ -2281,27 +2289,29 @@
|
|||
"name": "Alibaba",
|
||||
"categoryId": 8,
|
||||
"url": "http://www.alibaba.com/",
|
||||
"companyId": "alibaba"
|
||||
"companyId": "softbank",
|
||||
"source": "AdGuard"
|
||||
},
|
||||
"alibaba_cloud": {
|
||||
"name": "Alibaba Cloud",
|
||||
"categoryId": 10,
|
||||
"url": "https://www.alibabacloud.com/",
|
||||
"companyId": "alibaba",
|
||||
"companyId": "softbank",
|
||||
"source": "AdGuard"
|
||||
},
|
||||
"alibaba_ucbrowser": {
|
||||
"name": "UC Browser",
|
||||
"categoryId": 8,
|
||||
"url": "https://ucweb.com/",
|
||||
"companyId": "alibaba",
|
||||
"companyId": "softbank",
|
||||
"source": "AdGuard"
|
||||
},
|
||||
"alipay.com": {
|
||||
"name": "Alipay",
|
||||
"categoryId": 2,
|
||||
"url": "https://www.alipay.com/",
|
||||
"companyId": "alibaba"
|
||||
"url": "https://global.alipay.com/",
|
||||
"companyId": "softbank",
|
||||
"source": "AdGuard"
|
||||
},
|
||||
"alivechat": {
|
||||
"name": "AliveChat",
|
||||
|
@ -3767,10 +3777,11 @@
|
|||
"companyId": "branica"
|
||||
},
|
||||
"braze": {
|
||||
"name": "Braze",
|
||||
"name": "Braze, Inc.",
|
||||
"categoryId": 6,
|
||||
"url": "https://www.braze.com/",
|
||||
"companyId": "braze_inc"
|
||||
"companyId": "braze",
|
||||
"source": "AdGuard"
|
||||
},
|
||||
"brealtime": {
|
||||
"name": "EMX Digital",
|
||||
|
@ -12534,7 +12545,7 @@
|
|||
"name": "Network Time Protocol",
|
||||
"categoryId": 5,
|
||||
"url": "https://ntp.org/",
|
||||
"companyId": "ntppool",
|
||||
"companyId": "network_time_foundation",
|
||||
"source": "AdGuard"
|
||||
},
|
||||
"nttcom_online_marketing_solutions": {
|
||||
|
@ -17062,7 +17073,8 @@
|
|||
"name": "Taobao",
|
||||
"categoryId": 4,
|
||||
"url": "https://world.taobao.com/",
|
||||
"companyId": "alibaba"
|
||||
"companyId": "softbank",
|
||||
"source": "AdGuard"
|
||||
},
|
||||
"tapad": {
|
||||
"name": "Tapad",
|
||||
|
@ -20431,6 +20443,7 @@
|
|||
"nedstat.com": "adobe_experience_cloud",
|
||||
"omtrdc.net": "adobe_experience_cloud",
|
||||
"sitestat.com": "adobe_experience_cloud",
|
||||
"adobedc.net": "adobe_experience_league",
|
||||
"adobelogin.com": "adobe_login",
|
||||
"adobetag.com": "adobe_tagmanager",
|
||||
"typekit.com": "adobe_typekit",
|
||||
|
@ -21046,6 +21059,7 @@
|
|||
"brandwire.tv": "brandwire.tv",
|
||||
"branica.com": "branica",
|
||||
"appboycdn.com": "braze",
|
||||
"braze.com": "braze",
|
||||
"brealtime.com": "brealtime",
|
||||
"bridgetrack.com": "bridgetrack",
|
||||
"brightcove.com": "brightcove",
|
||||
|
@ -23140,8 +23154,11 @@
|
|||
"s-microsoft.com": "microsoft",
|
||||
"trouter.io": "microsoft",
|
||||
"windows.net": "microsoft",
|
||||
"aka.ms": "microsoft",
|
||||
"microsoftazuread-sso.com": "microsoft",
|
||||
"bingapis.com": "microsoft",
|
||||
"msauth.net": "microsoft",
|
||||
"msauthimages.net": "microsoft",
|
||||
"msftauth.net": "microsoft",
|
||||
"msftstatic.com": "microsoft",
|
||||
"msidentity.com": "microsoft",
|
||||
|
@ -23232,10 +23249,13 @@
|
|||
"mrpdata.net": "mrpdata",
|
||||
"mrskincash.com": "mrskincash",
|
||||
"a-msedge.net": "msedge",
|
||||
"b-msedge.net": "msedge",
|
||||
"e-msedge.net": "msedge",
|
||||
"k-msedge.net": "msedge",
|
||||
"l-msedge.net": "msedge",
|
||||
"s-msedge.net": "msedge",
|
||||
"t-msedge.net": "msedge",
|
||||
"wac-msedge.net": "msedge",
|
||||
"msn.com": "msn",
|
||||
"s-msn.com": "msn",
|
||||
"musculahq.appspot.com": "muscula",
|
||||
|
|
|
@ -15,6 +15,8 @@ import {
|
|||
R_DOMAIN,
|
||||
MAX_PASSWORD_LENGTH,
|
||||
MIN_PASSWORD_LENGTH,
|
||||
R_IPV4_SUBNET,
|
||||
R_IPV6_SUBNET,
|
||||
} from './constants';
|
||||
import { ip4ToInt, isValidAbsolutePath } from './form';
|
||||
import { isIpInCidr, parseSubnetMask } from './helpers';
|
||||
|
@ -365,3 +367,25 @@ export const validateIpGateway = (value, allValues) => {
|
|||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param value {string}
|
||||
* @returns {Function}
|
||||
*/
|
||||
export const validateIPv4Subnet = (value) => {
|
||||
if (!R_IPV4_SUBNET.test(value)) {
|
||||
return i18next.t('rate_limit_subnet_len_ipv4_error');
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param value {string}
|
||||
* @returns {Function}
|
||||
*/
|
||||
export const validateIPv6Subnet = (value) => {
|
||||
if (!R_IPV6_SUBNET.test(value)) {
|
||||
return i18next.t('rate_limit_subnet_len_ipv6_error');
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
|
|
@ -44,6 +44,19 @@ const dashboard = handleActions(
|
|||
|
||||
return newState;
|
||||
},
|
||||
[actions.timerStatusSuccess]: (state, { payload }) => {
|
||||
const {
|
||||
protection_enabled: protectionEnabled,
|
||||
protection_disabled_duration: protectionDisabledDuration,
|
||||
} = payload;
|
||||
const newState = {
|
||||
...state,
|
||||
protectionEnabled,
|
||||
protectionDisabledDuration,
|
||||
};
|
||||
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.getVersionRequest]: (state) => ({
|
||||
...state,
|
||||
|
|
|
@ -128,8 +128,7 @@ const dhcp = handleActions(
|
|||
const newState = {
|
||||
...state,
|
||||
isModalOpen: !state.isModalOpen,
|
||||
modalType: payload?.type || '',
|
||||
leaseModalConfig: payload?.config,
|
||||
leaseModalConfig: payload,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
|
|
|
@ -18,6 +18,7 @@ const dnsConfig = handleActions(
|
|||
fallback_dns,
|
||||
bootstrap_dns,
|
||||
local_ptr_upstreams,
|
||||
ratelimit_whitelist,
|
||||
...values
|
||||
} = payload;
|
||||
|
||||
|
@ -30,6 +31,7 @@ const dnsConfig = handleActions(
|
|||
fallback_dns: (fallback_dns && fallback_dns.join('\n')) || '',
|
||||
bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '',
|
||||
local_ptr_upstreams: (local_ptr_upstreams && local_ptr_upstreams.join('\n')) || '',
|
||||
ratelimit_whitelist: (ratelimit_whitelist && ratelimit_whitelist.join('\n')) || '',
|
||||
processingGetConfig: false,
|
||||
};
|
||||
},
|
||||
|
|
37
go.mod
37
go.mod
|
@ -3,20 +3,20 @@ module github.com/AdguardTeam/AdGuardHome
|
|||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.57.3
|
||||
github.com/AdguardTeam/golibs v0.17.2
|
||||
github.com/AdguardTeam/dnsproxy v0.60.0
|
||||
github.com/AdguardTeam/golibs v0.18.0
|
||||
github.com/AdguardTeam/urlfilter v0.17.3
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.7
|
||||
github.com/bluele/gcache v0.0.2
|
||||
github.com/digineo/go-ipset/v2 v2.2.1
|
||||
github.com/dimfeld/httptreemux/v5 v5.5.0
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/go-ping/ping v1.1.0
|
||||
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.3.1
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
|
||||
github.com/kardianos/service v1.2.2
|
||||
|
@ -27,14 +27,14 @@ require (
|
|||
// own code for that. Perhaps, use gopacket.
|
||||
github.com/mdlayher/raw v0.1.0
|
||||
github.com/miekg/dns v1.1.56
|
||||
github.com/quic-go/quic-go v0.39.2
|
||||
github.com/quic-go/quic-go v0.40.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/ti-mo/netfilter v0.5.1
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/sys v0.13.0
|
||||
go.etcd.io/bbolt v1.3.8
|
||||
golang.org/x/crypto v0.15.0
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
|
||||
golang.org/x/net v0.18.0
|
||||
golang.org/x/sys v0.14.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
howett.net/plist v1.0.0
|
||||
|
@ -47,19 +47,20 @@ 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-20230926050212-f7f687d19a98 // indirect
|
||||
github.com/mdlayher/socket v0.5.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||
// TODO(a.garipov): Upgrade to v0.5.0 once we switch to Go 1.21+.
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.13.1 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
||||
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.3.4 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
|
||||
go.uber.org/mock v0.3.0 // indirect
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
golang.org/x/sync v0.4.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.15.0 // indirect
|
||||
)
|
||||
|
|
77
go.sum
77
go.sum
|
@ -1,7 +1,7 @@
|
|||
github.com/AdguardTeam/dnsproxy v0.57.3 h1:0v7D+LQrOL2k2fvkG3Ft3Cn3ayUsvAdlOlJR+gLxSGA=
|
||||
github.com/AdguardTeam/dnsproxy v0.57.3/go.mod h1:ZvkbM71HwpilgkCnTubDiR4Ba6x5Qvnhy2iasMWaTDM=
|
||||
github.com/AdguardTeam/golibs v0.17.2 h1:vg6wHMjUKscnyPGRvxS5kAt7Uw4YxcJiITZliZ476W8=
|
||||
github.com/AdguardTeam/golibs v0.17.2/go.mod h1:DKhCIXHcUYtBhU8ibTLKh1paUL96n5zhQBlx763sj+U=
|
||||
github.com/AdguardTeam/dnsproxy v0.60.0 h1:0gLYoFyWRhQ1MP6g6AqqZXL5/h2QM4FE1aybUZ5HGuE=
|
||||
github.com/AdguardTeam/dnsproxy v0.60.0/go.mod h1:B7FvvTFQZBfey1cJXQo732EyCLX6xj4JqrciCawATzg=
|
||||
github.com/AdguardTeam/golibs v0.18.0 h1:ckS2YK7t2Ub6UkXl0fnreVaM15Zb07Hh1gmFqttjpWg=
|
||||
github.com/AdguardTeam/golibs v0.18.0/go.mod h1:DKhCIXHcUYtBhU8ibTLKh1paUL96n5zhQBlx763sj+U=
|
||||
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/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
|
@ -25,9 +25,9 @@ github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1M
|
|||
github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU=
|
||||
github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ=
|
||||
github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/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-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
|
||||
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
||||
|
@ -41,13 +41,13 @@ 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-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ=
|
||||
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/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.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.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-20231016090811-6a2c8fbdcc1c h1:PgxFEySCI41sH0mB7/2XswdXbUykQsRUGod8Rn+NubM=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
||||
|
@ -71,14 +71,14 @@ github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU
|
|||
github.com/mdlayher/raw v0.1.0 h1:K4PFMVy+AFsp0Zdlrts7yNhxc/uXoPVHi9RzRvtZF2Y=
|
||||
github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5s9Sg=
|
||||
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
|
||||
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
||||
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
|
||||
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU=
|
||||
github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||
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=
|
||||
|
@ -92,10 +92,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
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.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.39.2 h1:hmwAf8zAHlvan0Y5PXxeeBFZEW17IW99sXLry8I2kjk=
|
||||
github.com/quic-go/quic-go v0.39.2/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
|
||||
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.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
|
||||
github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
@ -112,32 +112,32 @@ github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYm
|
|||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
|
||||
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
||||
go.uber.org/mock v0.3.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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
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.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
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/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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
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.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -148,19 +148,18 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-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.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.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=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
||||
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
||||
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=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
package aghnet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/netip"
|
||||
"path"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/hostsfile"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
|
@ -141,13 +144,9 @@ func NewHostsContainer(
|
|||
func (hc *HostsContainer) Close() (err error) {
|
||||
log.Debug("%s: closing", hostsContainerPrefix)
|
||||
|
||||
err = hc.watcher.Close()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("closing fs watcher: %w", err)
|
||||
|
||||
// Go on and close the container either way.
|
||||
}
|
||||
err = errors.Annotate(hc.watcher.Close(), "closing fs watcher: %w")
|
||||
|
||||
// Go on and close the container either way.
|
||||
close(hc.done)
|
||||
|
||||
return err
|
||||
|
@ -319,3 +318,39 @@ func (hc *HostsContainer) refresh() (err error) {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ upstream.Resolver = (*HostsContainer)(nil)
|
||||
|
||||
// LookupNetIP implements the [upstream.Resolver] interface for *HostsContainer.
|
||||
func (hc *HostsContainer) LookupNetIP(
|
||||
ctx context.Context,
|
||||
network string,
|
||||
hostname string,
|
||||
) (addrs []netip.Addr, err error) {
|
||||
// TODO(e.burkov): Think of extracting this logic to a golibs function if
|
||||
// needed anywhere else.
|
||||
var isDesiredProto func(ip netip.Addr) (ok bool)
|
||||
switch network {
|
||||
case "ip4":
|
||||
isDesiredProto = (netip.Addr).Is4
|
||||
case "ip6":
|
||||
isDesiredProto = (netip.Addr).Is6
|
||||
case "ip":
|
||||
isDesiredProto = func(ip netip.Addr) (ok bool) { return true }
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported network: %q", network)
|
||||
}
|
||||
|
||||
idx := hc.current.Load()
|
||||
recs := idx.names[strings.ToLower(hostname)]
|
||||
|
||||
addrs = make([]netip.Addr, 0, len(recs))
|
||||
for _, rec := range recs {
|
||||
if isDesiredProto(rec.Addr) {
|
||||
addrs = append(addrs, rec.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
return slices.Clip(addrs), nil
|
||||
}
|
||||
|
|
|
@ -10,9 +10,11 @@ import (
|
|||
"net"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
@ -307,6 +309,50 @@ func ParseAddrPort(s string, defaultPort uint16) (ipp netip.AddrPort, err error)
|
|||
return ipp, nil
|
||||
}
|
||||
|
||||
// ParseSubnet parses s either as a CIDR prefix itself, or as an IP address,
|
||||
// returning the corresponding single-IP CIDR prefix.
|
||||
//
|
||||
// TODO(e.burkov): Taken from dnsproxy, move to golibs.
|
||||
func ParseSubnet(s string) (p netip.Prefix, err error) {
|
||||
if strings.Contains(s, "/") {
|
||||
p, err = netip.ParsePrefix(s)
|
||||
if err != nil {
|
||||
return netip.Prefix{}, err
|
||||
}
|
||||
} else {
|
||||
var ip netip.Addr
|
||||
ip, err = netip.ParseAddr(s)
|
||||
if err != nil {
|
||||
return netip.Prefix{}, err
|
||||
}
|
||||
|
||||
p = netip.PrefixFrom(ip, ip.BitLen())
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// ParseBootstraps returns the slice of upstream resolvers parsed from addrs.
|
||||
// It additionally returns the closers for each resolver, that should be closed
|
||||
// after use.
|
||||
func ParseBootstraps(
|
||||
addrs []string,
|
||||
opts *upstream.Options,
|
||||
) (boots []*upstream.UpstreamResolver, err error) {
|
||||
boots = make([]*upstream.UpstreamResolver, 0, len(boots))
|
||||
for i, b := range addrs {
|
||||
var r *upstream.UpstreamResolver
|
||||
r, err = upstream.NewUpstreamResolver(b, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bootstrap at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
boots = append(boots, r)
|
||||
}
|
||||
|
||||
return boots, nil
|
||||
}
|
||||
|
||||
// BroadcastFromPref calculates the broadcast IP address for p.
|
||||
func BroadcastFromPref(p netip.Prefix) (bc netip.Addr) {
|
||||
bc = p.Addr().Unmap()
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
@ -116,6 +117,26 @@ func (p *AddressUpdater) UpdateAddress(ip netip.Addr, host string, info *whois.I
|
|||
p.OnUpdateAddress(ip, host, info)
|
||||
}
|
||||
|
||||
// Package dnsforward
|
||||
|
||||
// ClientsContainer is a fake [dnsforward.ClientsContainer] implementation for
|
||||
// tests.
|
||||
type ClientsContainer struct {
|
||||
OnUpstreamConfigByID func(
|
||||
id string,
|
||||
boot upstream.Resolver,
|
||||
) (conf *proxy.CustomUpstreamConfig, err error)
|
||||
}
|
||||
|
||||
// UpstreamConfigByID implements the [dnsforward.ClientsContainer] interface
|
||||
// for *ClientsContainer.
|
||||
func (c *ClientsContainer) UpstreamConfigByID(
|
||||
id string,
|
||||
boot upstream.Resolver,
|
||||
) (conf *proxy.CustomUpstreamConfig, err error) {
|
||||
return c.OnUpstreamConfigByID(id, boot)
|
||||
}
|
||||
|
||||
// Package filtering
|
||||
|
||||
// Resolver is a fake [filtering.Resolver] implementation for tests.
|
||||
|
|
|
@ -2,6 +2,7 @@ package aghtest_test
|
|||
|
||||
import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
)
|
||||
|
||||
|
@ -9,3 +10,6 @@ import (
|
|||
|
||||
// type check
|
||||
var _ filtering.Resolver = (*aghtest.Resolver)(nil)
|
||||
|
||||
// type check
|
||||
var _ dnsforward.ClientsContainer = (*aghtest.ClientsContainer)(nil)
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
)
|
||||
|
||||
|
@ -49,16 +50,16 @@ type ServerConfig struct {
|
|||
// DHCPServer - DHCP server interface
|
||||
type DHCPServer interface {
|
||||
// ResetLeases resets leases.
|
||||
ResetLeases(leases []*Lease) (err error)
|
||||
ResetLeases(leases []*dhcpsvc.Lease) (err error)
|
||||
// GetLeases returns deep clones of the current leases.
|
||||
GetLeases(flags GetLeasesFlags) (leases []*Lease)
|
||||
GetLeases(flags GetLeasesFlags) (leases []*dhcpsvc.Lease)
|
||||
// AddStaticLease - add a static lease
|
||||
AddStaticLease(l *Lease) (err error)
|
||||
AddStaticLease(l *dhcpsvc.Lease) (err error)
|
||||
// RemoveStaticLease - remove a static lease
|
||||
RemoveStaticLease(l *Lease) (err error)
|
||||
RemoveStaticLease(l *dhcpsvc.Lease) (err error)
|
||||
|
||||
// UpdateStaticLease updates IP, hostname of the lease.
|
||||
UpdateStaticLease(l *Lease) (err error)
|
||||
UpdateStaticLease(l *dhcpsvc.Lease) (err error)
|
||||
|
||||
// FindMACbyIP returns a MAC address by the IP address of its lease, if
|
||||
// there is one.
|
||||
|
@ -81,7 +82,7 @@ type DHCPServer interface {
|
|||
Start() (err error)
|
||||
// Stop - stop server
|
||||
Stop() (err error)
|
||||
getLeasesRef() []*Lease
|
||||
getLeasesRef() []*dhcpsvc.Lease
|
||||
}
|
||||
|
||||
// V4ServerConf - server configuration
|
||||
|
|
|
@ -5,9 +5,13 @@ package dhcpd
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/google/renameio/v2/maybe"
|
||||
|
@ -28,7 +32,60 @@ type dataLeases struct {
|
|||
Version int `json:"version"`
|
||||
|
||||
// Leases is the list containing stored DHCP leases.
|
||||
Leases []*Lease `json:"leases"`
|
||||
Leases []*dbLease `json:"leases"`
|
||||
}
|
||||
|
||||
// dbLease is the structure of stored lease.
|
||||
type dbLease struct {
|
||||
Expiry string `json:"expires"`
|
||||
IP netip.Addr `json:"ip"`
|
||||
Hostname string `json:"hostname"`
|
||||
HWAddr string `json:"mac"`
|
||||
IsStatic bool `json:"static"`
|
||||
}
|
||||
|
||||
// fromLease converts *dhcpsvc.Lease to *dbLease.
|
||||
func fromLease(l *dhcpsvc.Lease) (dl *dbLease) {
|
||||
var expiryStr string
|
||||
if !l.IsStatic {
|
||||
// The front-end is waiting for RFC 3999 format of the time value. It
|
||||
// also shouldn't got an Expiry field for static leases.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2692.
|
||||
expiryStr = l.Expiry.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
return &dbLease{
|
||||
Expiry: expiryStr,
|
||||
Hostname: l.Hostname,
|
||||
HWAddr: l.HWAddr.String(),
|
||||
IP: l.IP,
|
||||
IsStatic: l.IsStatic,
|
||||
}
|
||||
}
|
||||
|
||||
// toLease converts *dbLease to *dhcpsvc.Lease.
|
||||
func (dl *dbLease) toLease() (l *dhcpsvc.Lease, err error) {
|
||||
mac, err := net.ParseMAC(dl.HWAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing hardware address: %w", err)
|
||||
}
|
||||
|
||||
expiry := time.Time{}
|
||||
if !dl.IsStatic {
|
||||
expiry, err = time.Parse(time.RFC3339, dl.Expiry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing expiry time: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &dhcpsvc.Lease{
|
||||
Expiry: expiry,
|
||||
IP: dl.IP,
|
||||
Hostname: dl.Hostname,
|
||||
HWAddr: mac,
|
||||
IsStatic: dl.IsStatic,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// dbLoad loads stored leases.
|
||||
|
@ -49,15 +106,22 @@ func (s *server) dbLoad() (err error) {
|
|||
}
|
||||
|
||||
leases := dl.Leases
|
||||
|
||||
leases4 := []*Lease{}
|
||||
leases6 := []*Lease{}
|
||||
leases4 := []*dhcpsvc.Lease{}
|
||||
leases6 := []*dhcpsvc.Lease{}
|
||||
|
||||
for _, l := range leases {
|
||||
if l.IP.Is4() {
|
||||
leases4 = append(leases4, l)
|
||||
var lease *dhcpsvc.Lease
|
||||
lease, err = l.toLease()
|
||||
if err != nil {
|
||||
log.Info("dhcp: invalid lease: %s", err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if lease.IP.Is4() {
|
||||
leases4 = append(leases4, lease)
|
||||
} else {
|
||||
leases6 = append(leases6, l)
|
||||
leases6 = append(leases6, lease)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,8 +137,12 @@ func (s *server) dbLoad() (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
log.Info("dhcp: loaded leases v4:%d v6:%d total-read:%d from DB",
|
||||
len(leases4), len(leases6), len(leases))
|
||||
log.Info(
|
||||
"dhcp: loaded leases v4:%d v6:%d total-read:%d from DB",
|
||||
len(leases4),
|
||||
len(leases6),
|
||||
len(leases),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -83,24 +151,26 @@ func (s *server) dbLoad() (err error) {
|
|||
func (s *server) dbStore() (err error) {
|
||||
// Use an empty slice here as opposed to nil so that it doesn't write
|
||||
// "null" into the database file if leases are empty.
|
||||
leases := []*Lease{}
|
||||
leases := []*dbLease{}
|
||||
|
||||
leases4 := s.srv4.getLeasesRef()
|
||||
leases = append(leases, leases4...)
|
||||
for _, l := range s.srv4.getLeasesRef() {
|
||||
leases = append(leases, fromLease(l))
|
||||
}
|
||||
|
||||
if s.srv6 != nil {
|
||||
leases6 := s.srv6.getLeasesRef()
|
||||
leases = append(leases, leases6...)
|
||||
for _, l := range s.srv6.getLeasesRef() {
|
||||
leases = append(leases, fromLease(l))
|
||||
}
|
||||
}
|
||||
|
||||
return writeDB(s.conf.dbFilePath, leases)
|
||||
}
|
||||
|
||||
// writeDB writes leases to file at path.
|
||||
func writeDB(path string, leases []*Lease) (err error) {
|
||||
func writeDB(path string, leases []*dbLease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "writing db: %w") }()
|
||||
|
||||
slices.SortFunc(leases, func(a, b *Lease) (res int) {
|
||||
slices.SortFunc(leases, func(a, b *dbLease) (res int) {
|
||||
return strings.Compare(a.Hostname, b.Hostname)
|
||||
})
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
package dhcpd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
@ -12,7 +11,6 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -29,105 +27,6 @@ const (
|
|||
defaultBackoff time.Duration = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
// Lease contains the necessary information about a DHCP lease. It's used as is
|
||||
// in the database, so don't change it until it's absolutely necessary, see
|
||||
// [dataVersion].
|
||||
//
|
||||
// TODO(e.burkov): Unexport it and use [dhcpsvc.Lease].
|
||||
type Lease struct {
|
||||
// Expiry is the expiration time of the lease.
|
||||
Expiry time.Time `json:"expires"`
|
||||
|
||||
// Hostname of the client.
|
||||
Hostname string `json:"hostname"`
|
||||
|
||||
// HWAddr is the physical hardware address (MAC address).
|
||||
HWAddr net.HardwareAddr `json:"mac"`
|
||||
|
||||
// IP is the IP address leased to the client.
|
||||
IP netip.Addr `json:"ip"`
|
||||
|
||||
// IsStatic defines if the lease is static.
|
||||
IsStatic bool `json:"static"`
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of l.
|
||||
func (l *Lease) Clone() (clone *Lease) {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Lease{
|
||||
Expiry: l.Expiry,
|
||||
Hostname: l.Hostname,
|
||||
HWAddr: slices.Clone(l.HWAddr),
|
||||
IP: l.IP,
|
||||
IsStatic: l.IsStatic,
|
||||
}
|
||||
}
|
||||
|
||||
// IsBlocklisted returns true if the lease is blocklisted.
|
||||
//
|
||||
// TODO(a.garipov): Just make it a boolean field.
|
||||
func (l *Lease) IsBlocklisted() (ok bool) {
|
||||
if len(l.HWAddr) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, b := range l.HWAddr {
|
||||
if b != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for Lease.
|
||||
func (l Lease) MarshalJSON() ([]byte, error) {
|
||||
var expiryStr string
|
||||
if !l.IsStatic {
|
||||
// The front-end is waiting for RFC 3999 format of the time
|
||||
// value. It also shouldn't got an Expiry field for static
|
||||
// leases.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2692.
|
||||
expiryStr = l.Expiry.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
type lease Lease
|
||||
return json.Marshal(&struct {
|
||||
HWAddr string `json:"mac"`
|
||||
Expiry string `json:"expires,omitempty"`
|
||||
lease
|
||||
}{
|
||||
HWAddr: l.HWAddr.String(),
|
||||
Expiry: expiryStr,
|
||||
lease: lease(l),
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface for *Lease.
|
||||
func (l *Lease) UnmarshalJSON(data []byte) (err error) {
|
||||
type lease Lease
|
||||
aux := struct {
|
||||
*lease
|
||||
HWAddr string `json:"mac"`
|
||||
}{
|
||||
lease: (*lease)(l),
|
||||
}
|
||||
if err = json.Unmarshal(data, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.HWAddr, err = net.ParseMAC(aux.HWAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't parse MAC address: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnLeaseChangedT is a callback for lease changes.
|
||||
type OnLeaseChangedT func(flags int)
|
||||
|
||||
|
@ -370,19 +269,7 @@ func (s *server) Stop() (err error) {
|
|||
|
||||
// Leases returns the list of active DHCP leases.
|
||||
func (s *server) Leases() (leases []*dhcpsvc.Lease) {
|
||||
ls := append(s.srv4.GetLeases(LeasesAll), s.srv6.GetLeases(LeasesAll)...)
|
||||
leases = make([]*dhcpsvc.Lease, len(ls))
|
||||
for i, l := range ls {
|
||||
leases[i] = &dhcpsvc.Lease{
|
||||
Expiry: l.Expiry,
|
||||
Hostname: l.Hostname,
|
||||
HWAddr: l.HWAddr,
|
||||
IP: l.IP,
|
||||
IsStatic: l.IsStatic,
|
||||
}
|
||||
}
|
||||
|
||||
return leases
|
||||
return append(s.srv4.GetLeases(LeasesAll), s.srv6.GetLeases(LeasesAll)...)
|
||||
}
|
||||
|
||||
// MACByIP returns a MAC address by the IP address of its lease, if there is
|
||||
|
@ -414,6 +301,6 @@ func (s *server) IPByHost(host string) (ip netip.Addr) {
|
|||
}
|
||||
|
||||
// AddStaticLease - add static v4 lease
|
||||
func (s *server) AddStaticLease(l *Lease) error {
|
||||
func (s *server) AddStaticLease(l *dhcpsvc.Lease) error {
|
||||
return s.srv4.AddStaticLease(l)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -44,7 +45,7 @@ func TestDB(t *testing.T) {
|
|||
s.srv6, err = v6Create(V6ServerConf{})
|
||||
require.NoError(t, err)
|
||||
|
||||
leases := []*Lease{{
|
||||
leases := []*dhcpsvc.Lease{{
|
||||
Expiry: time.Now().Add(time.Hour),
|
||||
Hostname: "static-1.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
|
|
|
@ -93,13 +93,13 @@ func leasesToStatic(leases []*dhcpsvc.Lease) (static []*leaseStatic) {
|
|||
}
|
||||
|
||||
// toLease converts leaseStatic to Lease or returns error.
|
||||
func (l *leaseStatic) toLease() (lease *Lease, err error) {
|
||||
func (l *leaseStatic) toLease() (lease *dhcpsvc.Lease, err error) {
|
||||
addr, err := net.ParseMAC(l.HWAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't parse MAC address: %w", err)
|
||||
}
|
||||
|
||||
return &Lease{
|
||||
return &dhcpsvc.Lease{
|
||||
HWAddr: addr,
|
||||
IP: l.IP,
|
||||
Hostname: l.Hostname,
|
||||
|
@ -593,7 +593,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.
|
||||
func (s *server) parseLease(r io.Reader) (srv DHCPServer, lease *Lease, err error) {
|
||||
func (s *server) parseLease(r io.Reader) (srv DHCPServer, lease *dhcpsvc.Lease, err error) {
|
||||
l := &leaseStatic{}
|
||||
err = json.NewDecoder(r).Decode(l)
|
||||
if err != nil {
|
||||
|
|
|
@ -2,6 +2,7 @@ package dhcpd
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
|
@ -25,9 +26,9 @@ const (
|
|||
dbFilename = "leases.db"
|
||||
)
|
||||
|
||||
// leaseJSON is the structure of stored lease.
|
||||
// leaseJSON is the structure of stored lease in a legacy database.
|
||||
//
|
||||
// Deprecated: Use [Lease].
|
||||
// Deprecated: Use [dbLease].
|
||||
type leaseJSON struct {
|
||||
HWAddr []byte `json:"mac"`
|
||||
IP []byte `json:"ip"`
|
||||
|
@ -35,13 +36,28 @@ type leaseJSON struct {
|
|||
Expiry int64 `json:"exp"`
|
||||
}
|
||||
|
||||
func normalizeIP(ip net.IP) net.IP {
|
||||
ip4 := ip.To4()
|
||||
if ip4 != nil {
|
||||
return ip4
|
||||
// readOldDB reads the old database from the given path.
|
||||
func readOldDB(path string) (leases []*leaseJSON, err error) {
|
||||
// #nosec G304 -- Trust this path, since it's taken from the old file name
|
||||
// relative to the working directory and should generally be considered
|
||||
// safe.
|
||||
file, err := os.Open(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// Nothing to migrate.
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
defer func() { err = errors.WithDeferred(err, file.Close()) }()
|
||||
|
||||
leases = []*leaseJSON{}
|
||||
err = json.NewDecoder(file).Decode(&leases)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding old db: %w", err)
|
||||
}
|
||||
|
||||
return ip
|
||||
return leases, nil
|
||||
}
|
||||
|
||||
// migrateDB migrates stored leases if necessary.
|
||||
|
@ -51,59 +67,50 @@ func migrateDB(conf *ServerConfig) (err error) {
|
|||
oldLeasesPath := filepath.Join(conf.WorkDir, dbFilename)
|
||||
dataDirPath := filepath.Join(conf.DataDir, dataFilename)
|
||||
|
||||
// #nosec G304 -- Trust this path, since it's taken from the old file name
|
||||
// relative to the working directory and should generally be considered
|
||||
// safe.
|
||||
file, err := os.Open(oldLeasesPath)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
oldLeases, err := readOldDB(oldLeasesPath)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
} else if oldLeases == nil {
|
||||
// Nothing to migrate.
|
||||
return nil
|
||||
} else if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
ljs := []leaseJSON{}
|
||||
err = json.NewDecoder(file).Decode(&ljs)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
leases := []*Lease{}
|
||||
|
||||
for _, lj := range ljs {
|
||||
lj.IP = normalizeIP(lj.IP)
|
||||
|
||||
ip, ok := netip.AddrFromSlice(lj.IP)
|
||||
leases := make([]*dbLease, 0, len(oldLeases))
|
||||
for _, l := range oldLeases {
|
||||
l.IP = normalizeIP(l.IP)
|
||||
ip, ok := netip.AddrFromSlice(l.IP)
|
||||
if !ok {
|
||||
log.Info("dhcp: invalid IP: %s", lj.IP)
|
||||
log.Info("dhcp: invalid IP: %s", l.IP)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
lease := &Lease{
|
||||
Expiry: time.Unix(lj.Expiry, 0),
|
||||
Hostname: lj.Hostname,
|
||||
HWAddr: lj.HWAddr,
|
||||
leases = append(leases, &dbLease{
|
||||
Expiry: time.Unix(l.Expiry, 0).Format(time.RFC3339),
|
||||
Hostname: l.Hostname,
|
||||
HWAddr: net.HardwareAddr(l.HWAddr).String(),
|
||||
IP: ip,
|
||||
IsStatic: lj.Expiry == leaseExpireStatic,
|
||||
}
|
||||
|
||||
leases = append(leases, lease)
|
||||
IsStatic: l.Expiry == leaseExpireStatic,
|
||||
})
|
||||
}
|
||||
|
||||
err = writeDB(dataDirPath, leases)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
// Don't wrap the error since an annotation deferred already.
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Remove(oldLeasesPath)
|
||||
}
|
||||
|
||||
// normalizeIP converts the given IP address to IPv4 if it's IPv4-mapped IPv6,
|
||||
// or leaves it as is otherwise.
|
||||
func normalizeIP(ip net.IP) (normalized net.IP) {
|
||||
normalized = ip.To4()
|
||||
if normalized != nil {
|
||||
return normalized
|
||||
}
|
||||
|
||||
return ip
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package dhcpd
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -27,16 +26,16 @@ func TestMigrateDB(t *testing.T) {
|
|||
err := os.WriteFile(oldLeasesPath, []byte(testData), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantLeases := []*Lease{{
|
||||
Expiry: time.Time{},
|
||||
wantLeases := []*dbLease{{
|
||||
Expiry: time.Unix(1, 0).Format(time.RFC3339),
|
||||
Hostname: "test1",
|
||||
HWAddr: net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
HWAddr: "11:22:33:44:55:66",
|
||||
IP: netip.MustParseAddr("1.2.3.4"),
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Expiry: time.Unix(1231231231, 0),
|
||||
Expiry: time.Unix(1231231231, 0).Format(time.RFC3339),
|
||||
Hostname: "test2",
|
||||
HWAddr: net.HardwareAddr{0x66, 0x55, 0x44, 0x33, 0x22, 0x11},
|
||||
HWAddr: "66:55:44:33:22:11",
|
||||
IP: netip.MustParseAddr("4.3.2.1"),
|
||||
IsStatic: false,
|
||||
}}
|
||||
|
@ -62,12 +61,12 @@ func TestMigrateDB(t *testing.T) {
|
|||
|
||||
leases := dl.Leases
|
||||
|
||||
for i, wl := range wantLeases {
|
||||
assert.Equal(t, wl.Hostname, leases[i].Hostname)
|
||||
assert.Equal(t, wl.HWAddr, leases[i].HWAddr)
|
||||
assert.Equal(t, wl.IP, leases[i].IP)
|
||||
assert.Equal(t, wl.IsStatic, leases[i].IsStatic)
|
||||
for i, wantLease := range wantLeases {
|
||||
assert.Equal(t, wantLease.Hostname, leases[i].Hostname)
|
||||
assert.Equal(t, wantLease.HWAddr, leases[i].HWAddr)
|
||||
assert.Equal(t, wantLease.IP, leases[i].IP)
|
||||
assert.Equal(t, wantLease.IsStatic, leases[i].IsStatic)
|
||||
|
||||
require.True(t, wl.Expiry.Equal(leases[i].Expiry))
|
||||
require.Equal(t, wantLease.Expiry, leases[i].Expiry)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ package dhcpd
|
|||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
)
|
||||
|
||||
type winServer struct{}
|
||||
|
@ -14,19 +16,19 @@ type winServer struct{}
|
|||
// type check
|
||||
var _ DHCPServer = winServer{}
|
||||
|
||||
func (winServer) ResetLeases(_ []*Lease) (err error) { return nil }
|
||||
func (winServer) GetLeases(_ GetLeasesFlags) (leases []*Lease) { return nil }
|
||||
func (winServer) getLeasesRef() []*Lease { return nil }
|
||||
func (winServer) AddStaticLease(_ *Lease) (err error) { return nil }
|
||||
func (winServer) RemoveStaticLease(_ *Lease) (err error) { return nil }
|
||||
func (winServer) UpdateStaticLease(_ *Lease) (err error) { return nil }
|
||||
func (winServer) FindMACbyIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
|
||||
func (winServer) WriteDiskConfig4(_ *V4ServerConf) {}
|
||||
func (winServer) WriteDiskConfig6(_ *V6ServerConf) {}
|
||||
func (winServer) Start() (err error) { return nil }
|
||||
func (winServer) Stop() (err error) { return nil }
|
||||
func (winServer) HostByIP(_ netip.Addr) (host string) { return "" }
|
||||
func (winServer) IPByHost(_ string) (ip netip.Addr) { return netip.Addr{} }
|
||||
func (winServer) ResetLeases(_ []*dhcpsvc.Lease) (err error) { return nil }
|
||||
func (winServer) GetLeases(_ GetLeasesFlags) (leases []*dhcpsvc.Lease) { return nil }
|
||||
func (winServer) getLeasesRef() []*dhcpsvc.Lease { return nil }
|
||||
func (winServer) AddStaticLease(_ *dhcpsvc.Lease) (err error) { return nil }
|
||||
func (winServer) RemoveStaticLease(_ *dhcpsvc.Lease) (err error) { return nil }
|
||||
func (winServer) UpdateStaticLease(_ *dhcpsvc.Lease) (err error) { return nil }
|
||||
func (winServer) FindMACbyIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
|
||||
func (winServer) WriteDiskConfig4(_ *V4ServerConf) {}
|
||||
func (winServer) WriteDiskConfig6(_ *V6ServerConf) {}
|
||||
func (winServer) Start() (err error) { return nil }
|
||||
func (winServer) Stop() (err error) { return nil }
|
||||
func (winServer) HostByIP(_ netip.Addr) (host string) { return "" }
|
||||
func (winServer) IPByHost(_ string) (ip netip.Addr) { return netip.Addr{} }
|
||||
|
||||
func v4Create(_ *V4ServerConf) (s DHCPServer, err error) { return winServer{}, nil }
|
||||
func v6Create(_ V6ServerConf) (s DHCPServer, err error) { return winServer{}, nil }
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
|
@ -38,7 +39,7 @@ type v4Server struct {
|
|||
// have intersections with [implicitOpts].
|
||||
explicitOpts dhcpv4.Options
|
||||
|
||||
// leasesLock protects leases, leaseHosts, and leasedOffsets.
|
||||
// leasesLock protects leases, hostsIndex, ipIndex, and leasedOffsets.
|
||||
leasesLock sync.Mutex
|
||||
|
||||
// leasedOffsets contains offsets from conf.ipRange.start that have been
|
||||
|
@ -46,13 +47,13 @@ type v4Server struct {
|
|||
leasedOffsets *bitSet
|
||||
|
||||
// leases contains all dynamic and static leases.
|
||||
leases []*Lease
|
||||
leases []*dhcpsvc.Lease
|
||||
|
||||
// hostsIndex is the set of all hostnames of all known DHCP clients.
|
||||
hostsIndex map[string]*Lease
|
||||
hostsIndex map[string]*dhcpsvc.Lease
|
||||
|
||||
// ipIndex is an index of leases by their IP addresses.
|
||||
ipIndex map[netip.Addr]*Lease
|
||||
ipIndex map[netip.Addr]*dhcpsvc.Lease
|
||||
}
|
||||
|
||||
func (s *v4Server) enabled() (ok bool) {
|
||||
|
@ -141,7 +142,7 @@ func (s *v4Server) IPByHost(host string) (ip netip.Addr) {
|
|||
}
|
||||
|
||||
// ResetLeases resets leases.
|
||||
func (s *v4Server) ResetLeases(leases []*Lease) (err error) {
|
||||
func (s *v4Server) ResetLeases(leases []*dhcpsvc.Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "dhcpv4: %w") }()
|
||||
|
||||
if s.conf == nil {
|
||||
|
@ -152,8 +153,8 @@ func (s *v4Server) ResetLeases(leases []*Lease) (err error) {
|
|||
defer s.leasesLock.Unlock()
|
||||
|
||||
s.leasedOffsets = newBitSet()
|
||||
s.hostsIndex = make(map[string]*Lease, len(leases))
|
||||
s.ipIndex = make(map[netip.Addr]*Lease, len(leases))
|
||||
s.hostsIndex = make(map[string]*dhcpsvc.Lease, len(leases))
|
||||
s.ipIndex = make(map[netip.Addr]*dhcpsvc.Lease, len(leases))
|
||||
s.leases = nil
|
||||
|
||||
for _, l := range leases {
|
||||
|
@ -173,14 +174,14 @@ func (s *v4Server) ResetLeases(leases []*Lease) (err error) {
|
|||
}
|
||||
|
||||
// getLeasesRef returns the actual leases slice. For internal use only.
|
||||
func (s *v4Server) getLeasesRef() []*Lease {
|
||||
func (s *v4Server) getLeasesRef() []*dhcpsvc.Lease {
|
||||
return s.leases
|
||||
}
|
||||
|
||||
// isBlocklisted returns true if this lease holds a blocklisted IP.
|
||||
//
|
||||
// TODO(a.garipov): Make a method of *Lease?
|
||||
func (s *v4Server) isBlocklisted(l *Lease) (ok bool) {
|
||||
func (s *v4Server) isBlocklisted(l *dhcpsvc.Lease) (ok bool) {
|
||||
if len(l.HWAddr) == 0 {
|
||||
return false
|
||||
}
|
||||
|
@ -196,11 +197,11 @@ func (s *v4Server) isBlocklisted(l *Lease) (ok bool) {
|
|||
|
||||
// GetLeases returns the list of current DHCP leases. It is safe for concurrent
|
||||
// use.
|
||||
func (s *v4Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) {
|
||||
func (s *v4Server) GetLeases(flags GetLeasesFlags) (leases []*dhcpsvc.Lease) {
|
||||
// The function shouldn't return nil, because zero-length slice behaves
|
||||
// differently in cases like marshalling. Our front-end also requires
|
||||
// a non-nil value in the response.
|
||||
leases = []*Lease{}
|
||||
leases = []*dhcpsvc.Lease{}
|
||||
|
||||
getDynamic := flags&LeasesDynamic != 0
|
||||
getStatic := flags&LeasesStatic != 0
|
||||
|
@ -248,7 +249,7 @@ func (s *v4Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
|||
const defaultHwAddrLen = 6
|
||||
|
||||
// Add the specified IP to the black list for a time period
|
||||
func (s *v4Server) blocklistLease(l *Lease) {
|
||||
func (s *v4Server) blocklistLease(l *dhcpsvc.Lease) {
|
||||
l.HWAddr = make(net.HardwareAddr, defaultHwAddrLen)
|
||||
l.Hostname = ""
|
||||
l.Expiry = time.Now().Add(s.conf.leaseTime)
|
||||
|
@ -284,7 +285,7 @@ func (s *v4Server) rmLeaseByIndex(i int) {
|
|||
// Return error if a static lease is found
|
||||
//
|
||||
// TODO(s.chzhen): Refactor the code.
|
||||
func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
|
||||
func (s *v4Server) rmDynamicLease(lease *dhcpsvc.Lease) (err error) {
|
||||
for i, l := range s.leases {
|
||||
isStatic := l.IsStatic
|
||||
|
||||
|
@ -320,7 +321,7 @@ const (
|
|||
)
|
||||
|
||||
// addLease adds a dynamic or static lease.
|
||||
func (s *v4Server) addLease(l *Lease) (err error) {
|
||||
func (s *v4Server) addLease(l *dhcpsvc.Lease) (err error) {
|
||||
r := s.conf.ipRange
|
||||
leaseIP := net.IP(l.IP.AsSlice())
|
||||
offset, inOffset := r.offset(leaseIP)
|
||||
|
@ -352,7 +353,7 @@ func (s *v4Server) addLease(l *Lease) (err error) {
|
|||
}
|
||||
|
||||
// rmLease removes a lease with the same properties.
|
||||
func (s *v4Server) rmLease(lease *Lease) (err error) {
|
||||
func (s *v4Server) rmLease(lease *dhcpsvc.Lease) (err error) {
|
||||
if len(s.leases) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -378,7 +379,7 @@ const ErrUnconfigured errors.Error = "server is unconfigured"
|
|||
|
||||
// AddStaticLease implements the DHCPServer interface for *v4Server. It is
|
||||
// safe for concurrent use.
|
||||
func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
||||
func (s *v4Server) AddStaticLease(l *dhcpsvc.Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "dhcpv4: adding static lease: %w") }()
|
||||
|
||||
if s.conf == nil {
|
||||
|
@ -435,7 +436,7 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
|||
}
|
||||
|
||||
// UpdateStaticLease updates IP, hostname of the static lease.
|
||||
func (s *v4Server) UpdateStaticLease(l *Lease) (err error) {
|
||||
func (s *v4Server) UpdateStaticLease(l *dhcpsvc.Lease) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = errors.Annotate(err, "dhcpv4: updating static lease: %w")
|
||||
|
@ -474,7 +475,7 @@ func (s *v4Server) UpdateStaticLease(l *Lease) (err error) {
|
|||
}
|
||||
|
||||
// validateStaticLease returns an error if the static lease is invalid.
|
||||
func (s *v4Server) validateStaticLease(l *Lease) (err error) {
|
||||
func (s *v4Server) validateStaticLease(l *dhcpsvc.Lease) (err error) {
|
||||
hostname, err := normalizeHostname(l.Hostname)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
|
@ -511,7 +512,7 @@ func (s *v4Server) validateStaticLease(l *Lease) (err error) {
|
|||
|
||||
// updateStaticLease safe removes dynamic lease with the same properties and
|
||||
// then adds a static lease l.
|
||||
func (s *v4Server) updateStaticLease(l *Lease) (err error) {
|
||||
func (s *v4Server) updateStaticLease(l *dhcpsvc.Lease) (err error) {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
|
@ -529,7 +530,7 @@ func (s *v4Server) updateStaticLease(l *Lease) (err error) {
|
|||
}
|
||||
|
||||
// RemoveStaticLease removes a static lease. It is safe for concurrent use.
|
||||
func (s *v4Server) RemoveStaticLease(l *Lease) (err error) {
|
||||
func (s *v4Server) RemoveStaticLease(l *dhcpsvc.Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "dhcpv4: %w") }()
|
||||
|
||||
if s.conf == nil {
|
||||
|
@ -606,7 +607,7 @@ func (s *v4Server) addrAvailable(target net.IP) (avail bool) {
|
|||
}
|
||||
|
||||
// findLease finds a lease by its MAC-address.
|
||||
func (s *v4Server) findLease(mac net.HardwareAddr) (l *Lease) {
|
||||
func (s *v4Server) findLease(mac net.HardwareAddr) (l *dhcpsvc.Lease) {
|
||||
for _, l = range s.leases {
|
||||
if bytes.Equal(mac, l.HWAddr) {
|
||||
return l
|
||||
|
@ -646,8 +647,8 @@ func (s *v4Server) findExpiredLease() int {
|
|||
|
||||
// reserveLease reserves a lease for a client by its MAC-address. It returns
|
||||
// nil if it couldn't allocate a new lease.
|
||||
func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *Lease, err error) {
|
||||
l = &Lease{HWAddr: slices.Clone(mac)}
|
||||
func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *dhcpsvc.Lease, err error) {
|
||||
l = &dhcpsvc.Lease{HWAddr: slices.Clone(mac)}
|
||||
|
||||
nextIP := s.nextIP()
|
||||
if nextIP == nil {
|
||||
|
@ -679,7 +680,7 @@ func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *Lease, err error) {
|
|||
// commitLease refreshes l's values. It takes the desired hostname into account
|
||||
// when setting it into the lease, but generates a unique one if the provided
|
||||
// can't be used.
|
||||
func (s *v4Server) commitLease(l *Lease, hostname string) {
|
||||
func (s *v4Server) commitLease(l *dhcpsvc.Lease, hostname string) {
|
||||
prev := l.Hostname
|
||||
hostname = s.validHostnameForClient(hostname, l.IP)
|
||||
|
||||
|
@ -709,7 +710,7 @@ func (s *v4Server) commitLease(l *Lease, hostname string) {
|
|||
|
||||
// allocateLease allocates a new lease for the MAC address. If there are no IP
|
||||
// addresses left, both l and err are nil.
|
||||
func (s *v4Server) allocateLease(mac net.HardwareAddr) (l *Lease, err error) {
|
||||
func (s *v4Server) allocateLease(mac net.HardwareAddr) (l *dhcpsvc.Lease, err error) {
|
||||
for {
|
||||
l, err = s.reserveLease(mac)
|
||||
if err != nil {
|
||||
|
@ -728,7 +729,7 @@ func (s *v4Server) allocateLease(mac net.HardwareAddr) (l *Lease, err error) {
|
|||
}
|
||||
|
||||
// handleDiscover is the handler for the DHCP Discover request.
|
||||
func (s *v4Server) handleDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err error) {
|
||||
func (s *v4Server) handleDiscover(req, resp *dhcpv4.DHCPv4) (l *dhcpsvc.Lease, err error) {
|
||||
mac := req.ClientHWAddr
|
||||
|
||||
defer s.conf.notify(LeaseChangedDBStore)
|
||||
|
@ -787,7 +788,7 @@ func OptionFQDN(fqdn string) (opt dhcpv4.Option) {
|
|||
// checkLease checks if the pair of mac and ip is already leased. The mismatch
|
||||
// is true when the existing lease has the same hardware address but differs in
|
||||
// its IP address.
|
||||
func (s *v4Server) checkLease(mac net.HardwareAddr, ip net.IP) (lease *Lease, mismatch bool) {
|
||||
func (s *v4Server) checkLease(mac net.HardwareAddr, ip net.IP) (l *dhcpsvc.Lease, mismatch bool) {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
|
@ -798,7 +799,7 @@ func (s *v4Server) checkLease(mac net.HardwareAddr, ip net.IP) (lease *Lease, mi
|
|||
return nil, false
|
||||
}
|
||||
|
||||
for _, l := range s.leases {
|
||||
for _, l = range s.leases {
|
||||
if !bytes.Equal(l.HWAddr, mac) {
|
||||
continue
|
||||
}
|
||||
|
@ -823,7 +824,7 @@ func (s *v4Server) handleSelecting(
|
|||
req *dhcpv4.DHCPv4,
|
||||
reqIP net.IP,
|
||||
sid net.IP,
|
||||
) (l *Lease, needsReply bool) {
|
||||
) (l *dhcpsvc.Lease, needsReply bool) {
|
||||
// Client inserts the address of the selected server in server identifier,
|
||||
// ciaddr MUST be zero.
|
||||
mac := req.ClientHWAddr
|
||||
|
@ -857,7 +858,10 @@ func (s *v4Server) handleSelecting(
|
|||
}
|
||||
|
||||
// handleInitReboot handles the DHCPREQUEST generated during INIT-REBOOT state.
|
||||
func (s *v4Server) handleInitReboot(req *dhcpv4.DHCPv4, reqIP net.IP) (l *Lease, needsReply bool) {
|
||||
func (s *v4Server) handleInitReboot(
|
||||
req *dhcpv4.DHCPv4,
|
||||
reqIP net.IP,
|
||||
) (l *dhcpsvc.Lease, needsReply bool) {
|
||||
mac := req.ClientHWAddr
|
||||
|
||||
ip4 := reqIP.To4()
|
||||
|
@ -899,7 +903,7 @@ func (s *v4Server) handleInitReboot(req *dhcpv4.DHCPv4, reqIP net.IP) (l *Lease,
|
|||
|
||||
// handleRenew handles the DHCPREQUEST generated during RENEWING or REBINDING
|
||||
// state.
|
||||
func (s *v4Server) handleRenew(req *dhcpv4.DHCPv4) (l *Lease, needsReply bool) {
|
||||
func (s *v4Server) handleRenew(req *dhcpv4.DHCPv4) (l *dhcpsvc.Lease, needsReply bool) {
|
||||
mac := req.ClientHWAddr
|
||||
|
||||
// ciaddr MUST be filled in with client's IP address.
|
||||
|
@ -926,7 +930,7 @@ func (s *v4Server) handleRenew(req *dhcpv4.DHCPv4) (l *Lease, needsReply bool) {
|
|||
|
||||
// handleByRequestType handles the DHCPREQUEST according to the state during
|
||||
// which it's generated by client.
|
||||
func (s *v4Server) handleByRequestType(req *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) {
|
||||
func (s *v4Server) handleByRequestType(req *dhcpv4.DHCPv4) (lease *dhcpsvc.Lease, needsReply bool) {
|
||||
reqIP, sid := req.RequestedIPAddress(), req.ServerIdentifier()
|
||||
|
||||
if sid != nil && !sid.IsUnspecified() {
|
||||
|
@ -950,7 +954,7 @@ func (s *v4Server) handleByRequestType(req *dhcpv4.DHCPv4) (lease *Lease, needsR
|
|||
// handleRequest is the handler for a DHCPREQUEST message.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.2.
|
||||
func (s *v4Server) handleRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) {
|
||||
func (s *v4Server) handleRequest(req, resp *dhcpv4.DHCPv4) (lease *dhcpsvc.Lease, needsReply bool) {
|
||||
lease, needsReply = s.handleByRequestType(req)
|
||||
if lease == nil {
|
||||
return nil, needsReply
|
||||
|
@ -1043,7 +1047,7 @@ func (s *v4Server) handleDecline(req, resp *dhcpv4.DHCPv4) (err error) {
|
|||
}
|
||||
|
||||
// findLeaseForIP returns a lease for provided ip and mac.
|
||||
func (s *v4Server) findLeaseForIP(ip net.IP, mac net.HardwareAddr) (l *Lease) {
|
||||
func (s *v4Server) findLeaseForIP(ip net.IP, mac net.HardwareAddr) (l *dhcpsvc.Lease) {
|
||||
netIP, ok := netip.AddrFromSlice(ip)
|
||||
if !ok {
|
||||
log.Info("dhcpv4: invalid IP: %s", ip)
|
||||
|
@ -1106,7 +1110,11 @@ func (s *v4Server) handleRelease(req, resp *dhcpv4.DHCPv4) (err error) {
|
|||
}
|
||||
|
||||
// messageHandler describes a DHCPv4 message handler function.
|
||||
type messageHandler func(s *v4Server, req, resp *dhcpv4.DHCPv4) (rCode int, l *Lease, err error)
|
||||
type messageHandler func(
|
||||
s *v4Server,
|
||||
req *dhcpv4.DHCPv4,
|
||||
resp *dhcpv4.DHCPv4,
|
||||
) (rCode int, l *dhcpsvc.Lease, err error)
|
||||
|
||||
// messageHandlers is a map of handlers for various messages with message types
|
||||
// keys.
|
||||
|
@ -1115,7 +1123,7 @@ var messageHandlers = map[dhcpv4.MessageType]messageHandler{
|
|||
s *v4Server,
|
||||
req *dhcpv4.DHCPv4,
|
||||
resp *dhcpv4.DHCPv4,
|
||||
) (rCode int, l *Lease, err error) {
|
||||
) (rCode int, l *dhcpsvc.Lease, err error) {
|
||||
l, err = s.handleDiscover(req, resp)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("handling discover: %s", err)
|
||||
|
@ -1131,7 +1139,7 @@ var messageHandlers = map[dhcpv4.MessageType]messageHandler{
|
|||
s *v4Server,
|
||||
req *dhcpv4.DHCPv4,
|
||||
resp *dhcpv4.DHCPv4,
|
||||
) (rCode int, l *Lease, err error) {
|
||||
) (rCode int, l *dhcpsvc.Lease, err error) {
|
||||
var toReply bool
|
||||
l, toReply = s.handleRequest(req, resp)
|
||||
if l == nil {
|
||||
|
@ -1149,7 +1157,7 @@ var messageHandlers = map[dhcpv4.MessageType]messageHandler{
|
|||
s *v4Server,
|
||||
req *dhcpv4.DHCPv4,
|
||||
resp *dhcpv4.DHCPv4,
|
||||
) (rCode int, l *Lease, err error) {
|
||||
) (rCode int, l *dhcpsvc.Lease, err error) {
|
||||
err = s.handleDecline(req, resp)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("handling decline: %s", err)
|
||||
|
@ -1161,7 +1169,7 @@ var messageHandlers = map[dhcpv4.MessageType]messageHandler{
|
|||
s *v4Server,
|
||||
req *dhcpv4.DHCPv4,
|
||||
resp *dhcpv4.DHCPv4,
|
||||
) (rCode int, l *Lease, err error) {
|
||||
) (rCode int, l *dhcpsvc.Lease, err error) {
|
||||
err = s.handleRelease(req, resp)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("handling release: %s", err)
|
||||
|
@ -1402,8 +1410,8 @@ func (s *v4Server) Stop() (err error) {
|
|||
// Create DHCPv4 server
|
||||
func v4Create(conf *V4ServerConf) (srv *v4Server, err error) {
|
||||
s := &v4Server{
|
||||
hostsIndex: map[string]*Lease{},
|
||||
ipIndex: map[netip.Addr]*Lease{},
|
||||
hostsIndex: map[string]*dhcpsvc.Lease{},
|
||||
ipIndex: map[netip.Addr]*dhcpsvc.Lease{},
|
||||
}
|
||||
|
||||
err = conf.Validate()
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
|
@ -67,7 +68,7 @@ func TestV4Server_leasing(t *testing.T) {
|
|||
s := defaultSrv(t)
|
||||
|
||||
t.Run("add_static", func(t *testing.T) {
|
||||
err := s.AddStaticLease(&Lease{
|
||||
err := s.AddStaticLease(&dhcpsvc.Lease{
|
||||
Hostname: staticName,
|
||||
HWAddr: staticMAC,
|
||||
IP: staticIP,
|
||||
|
@ -76,7 +77,7 @@ func TestV4Server_leasing(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
t.Run("same_name", func(t *testing.T) {
|
||||
err = s.AddStaticLease(&Lease{
|
||||
err = s.AddStaticLease(&dhcpsvc.Lease{
|
||||
Hostname: staticName,
|
||||
HWAddr: anotherMAC,
|
||||
IP: anotherIP,
|
||||
|
@ -90,7 +91,7 @@ func TestV4Server_leasing(t *testing.T) {
|
|||
"dynamic leases for " + anotherIP.String() +
|
||||
" (" + staticMAC.String() + "): static lease already exists"
|
||||
|
||||
err = s.AddStaticLease(&Lease{
|
||||
err = s.AddStaticLease(&dhcpsvc.Lease{
|
||||
Hostname: anotherName,
|
||||
HWAddr: staticMAC,
|
||||
IP: anotherIP,
|
||||
|
@ -104,7 +105,7 @@ func TestV4Server_leasing(t *testing.T) {
|
|||
"dynamic leases for " + staticIP.String() +
|
||||
" (" + anotherMAC.String() + "): static lease already exists"
|
||||
|
||||
err = s.AddStaticLease(&Lease{
|
||||
err = s.AddStaticLease(&dhcpsvc.Lease{
|
||||
Hostname: anotherName,
|
||||
HWAddr: anotherMAC,
|
||||
IP: staticIP,
|
||||
|
@ -208,11 +209,11 @@ func TestV4Server_AddRemove_static(t *testing.T) {
|
|||
require.Empty(t, ls)
|
||||
|
||||
testCases := []struct {
|
||||
lease *Lease
|
||||
lease *dhcpsvc.Lease
|
||||
name string
|
||||
wantErrMsg string
|
||||
}{{
|
||||
lease: &Lease{
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: "success.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: netip.MustParseAddr("192.168.10.150"),
|
||||
|
@ -220,7 +221,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
|
|||
name: "success",
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
lease: &Lease{
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: "probably-router.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: DefaultGatewayIP,
|
||||
|
@ -229,7 +230,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
|
|||
wantErrMsg: "dhcpv4: adding static lease: " +
|
||||
`can't assign the gateway IP "192.168.10.1" to the lease`,
|
||||
}, {
|
||||
lease: &Lease{
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: "ip6.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: netip.MustParseAddr("ffff::1"),
|
||||
|
@ -238,7 +239,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
|
|||
wantErrMsg: `dhcpv4: adding static lease: ` +
|
||||
`invalid IP "ffff::1": only IPv4 is supported`,
|
||||
}, {
|
||||
lease: &Lease{
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: "bad-mac.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA},
|
||||
IP: netip.MustParseAddr("192.168.10.150"),
|
||||
|
@ -247,7 +248,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
|
|||
wantErrMsg: `dhcpv4: adding static lease: bad mac address "aa:aa": ` +
|
||||
`bad mac address length 2, allowed: [6 8 20]`,
|
||||
}, {
|
||||
lease: &Lease{
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: "bad-lbl-.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: netip.MustParseAddr("192.168.10.150"),
|
||||
|
@ -266,7 +267,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
err = s.RemoveStaticLease(&Lease{
|
||||
err = s.RemoveStaticLease(&dhcpsvc.Lease{
|
||||
IP: tc.lease.IP,
|
||||
HWAddr: tc.lease.HWAddr,
|
||||
})
|
||||
|
@ -289,7 +290,7 @@ func TestV4_AddReplace(t *testing.T) {
|
|||
s, ok := sIface.(*v4Server)
|
||||
require.True(t, ok)
|
||||
|
||||
dynLeases := []Lease{{
|
||||
dynLeases := []dhcpsvc.Lease{{
|
||||
Hostname: "dynamic-1.local",
|
||||
HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: netip.MustParseAddr("192.168.10.150"),
|
||||
|
@ -304,7 +305,7 @@ func TestV4_AddReplace(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
stLeases := []*Lease{{
|
||||
stLeases := []*dhcpsvc.Lease{{
|
||||
Hostname: "static-1.local",
|
||||
HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: netip.MustParseAddr("192.168.10.150"),
|
||||
|
@ -513,7 +514,7 @@ func TestV4StaticLease_Get(t *testing.T) {
|
|||
s.conf.dnsIPAddrs = []netip.Addr{dnsAddr}
|
||||
s.implicitOpts.Update(dhcpv4.OptDNS(dnsAddr.AsSlice()))
|
||||
|
||||
l := &Lease{
|
||||
l := &dhcpsvc.Lease{
|
||||
Hostname: "static-1.local",
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: netip.MustParseAddr("192.168.10.150"),
|
||||
|
@ -779,7 +780,7 @@ func TestV4Server_FindMACbyIP(t *testing.T) {
|
|||
anotherMAC := net.HardwareAddr{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB}
|
||||
|
||||
s := &v4Server{
|
||||
leases: []*Lease{{
|
||||
leases: []*dhcpsvc.Lease{{
|
||||
Hostname: staticName,
|
||||
HWAddr: staticMAC,
|
||||
IP: staticIP,
|
||||
|
@ -791,11 +792,11 @@ func TestV4Server_FindMACbyIP(t *testing.T) {
|
|||
IP: anotherIP,
|
||||
}},
|
||||
}
|
||||
s.ipIndex = map[netip.Addr]*Lease{
|
||||
s.ipIndex = map[netip.Addr]*dhcpsvc.Lease{
|
||||
staticIP: s.leases[0],
|
||||
anotherIP: s.leases[1],
|
||||
}
|
||||
s.hostsIndex = map[string]*Lease{
|
||||
s.hostsIndex = map[string]*dhcpsvc.Lease{
|
||||
staticName: s.leases[0],
|
||||
anotherName: s.leases[1],
|
||||
}
|
||||
|
@ -845,7 +846,7 @@ func TestV4Server_handleDecline(t *testing.T) {
|
|||
s4, ok := s.(*v4Server)
|
||||
require.True(t, ok)
|
||||
|
||||
s4.leases = []*Lease{{
|
||||
s4.leases = []*dhcpsvc.Lease{{
|
||||
Hostname: dynamicName,
|
||||
HWAddr: dynamicMAC,
|
||||
IP: dynamicIP,
|
||||
|
@ -887,7 +888,7 @@ func TestV4Server_handleRelease(t *testing.T) {
|
|||
s4, ok := s.(*v4Server)
|
||||
require.True(t, ok)
|
||||
|
||||
s4.leases = []*Lease{{
|
||||
s4.leases = []*dhcpsvc.Lease{{
|
||||
Hostname: dynamicName,
|
||||
HWAddr: dynamicMAC,
|
||||
IP: dynamicIP,
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
|
@ -31,7 +32,7 @@ type v6Server struct {
|
|||
sid dhcpv6.DUID
|
||||
srv *server6.Server
|
||||
|
||||
leases []*Lease
|
||||
leases []*dhcpsvc.Lease
|
||||
leasesLock sync.Mutex
|
||||
ipAddrs [256]byte
|
||||
}
|
||||
|
@ -87,7 +88,7 @@ func (s *v6Server) IPByHost(host string) (ip netip.Addr) {
|
|||
}
|
||||
|
||||
// ResetLeases resets leases.
|
||||
func (s *v6Server) ResetLeases(leases []*Lease) (err error) {
|
||||
func (s *v6Server) ResetLeases(leases []*dhcpsvc.Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
|
||||
|
||||
s.leasesLock.Lock()
|
||||
|
@ -111,12 +112,14 @@ func (s *v6Server) ResetLeases(leases []*Lease) (err error) {
|
|||
|
||||
// GetLeases returns the list of current DHCP leases. It is safe for concurrent
|
||||
// use.
|
||||
func (s *v6Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) {
|
||||
func (s *v6Server) GetLeases(flags GetLeasesFlags) (leases []*dhcpsvc.Lease) {
|
||||
// The function shouldn't return nil value because zero-length slice
|
||||
// behaves differently in cases like marshalling. Our front-end also
|
||||
// requires non-nil value in the response.
|
||||
leases = []*Lease{}
|
||||
leases = []*dhcpsvc.Lease{}
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
for _, l := range s.leases {
|
||||
if l.IsStatic {
|
||||
if (flags & LeasesStatic) != 0 {
|
||||
|
@ -128,12 +131,12 @@ func (s *v6Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) {
|
|||
}
|
||||
}
|
||||
}
|
||||
s.leasesLock.Unlock()
|
||||
|
||||
return leases
|
||||
}
|
||||
|
||||
// getLeasesRef returns the actual leases slice. For internal use only.
|
||||
func (s *v6Server) getLeasesRef() []*Lease {
|
||||
func (s *v6Server) getLeasesRef() []*dhcpsvc.Lease {
|
||||
return s.leases
|
||||
}
|
||||
|
||||
|
@ -174,7 +177,7 @@ func (s *v6Server) leaseRemoveSwapByIndex(i int) {
|
|||
|
||||
// Remove a dynamic lease with the same properties
|
||||
// Return error if a static lease is found
|
||||
func (s *v6Server) rmDynamicLease(lease *Lease) (err error) {
|
||||
func (s *v6Server) rmDynamicLease(lease *dhcpsvc.Lease) (err error) {
|
||||
for i := 0; i < len(s.leases); i++ {
|
||||
l := s.leases[i]
|
||||
|
||||
|
@ -204,7 +207,7 @@ func (s *v6Server) rmDynamicLease(lease *Lease) (err error) {
|
|||
}
|
||||
|
||||
// AddStaticLease adds a static lease. It is safe for concurrent use.
|
||||
func (s *v6Server) AddStaticLease(l *Lease) (err error) {
|
||||
func (s *v6Server) AddStaticLease(l *dhcpsvc.Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
|
||||
|
||||
if !l.IP.Is6() {
|
||||
|
@ -236,7 +239,7 @@ func (s *v6Server) AddStaticLease(l *Lease) (err error) {
|
|||
}
|
||||
|
||||
// UpdateStaticLease updates IP, hostname of the static lease.
|
||||
func (s *v6Server) UpdateStaticLease(l *Lease) (err error) {
|
||||
func (s *v6Server) UpdateStaticLease(l *dhcpsvc.Lease) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = errors.Annotate(err, "dhcpv6: updating static lease: %w")
|
||||
|
@ -267,7 +270,7 @@ func (s *v6Server) UpdateStaticLease(l *Lease) (err error) {
|
|||
}
|
||||
|
||||
// RemoveStaticLease removes a static lease. It is safe for concurrent use.
|
||||
func (s *v6Server) RemoveStaticLease(l *Lease) (err error) {
|
||||
func (s *v6Server) RemoveStaticLease(l *dhcpsvc.Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
|
||||
|
||||
if !l.IP.Is6() {
|
||||
|
@ -292,7 +295,7 @@ func (s *v6Server) RemoveStaticLease(l *Lease) (err error) {
|
|||
}
|
||||
|
||||
// Add a lease
|
||||
func (s *v6Server) addLease(l *Lease) {
|
||||
func (s *v6Server) addLease(l *dhcpsvc.Lease) {
|
||||
s.leases = append(s.leases, l)
|
||||
ip := l.IP.As16()
|
||||
s.ipAddrs[ip[15]] = 1
|
||||
|
@ -300,7 +303,7 @@ func (s *v6Server) addLease(l *Lease) {
|
|||
}
|
||||
|
||||
// Remove a lease with the same properties
|
||||
func (s *v6Server) rmLease(lease *Lease) (err error) {
|
||||
func (s *v6Server) rmLease(lease *dhcpsvc.Lease) (err error) {
|
||||
for i, l := range s.leases {
|
||||
if l.IP == lease.IP {
|
||||
if !bytes.Equal(l.HWAddr, lease.HWAddr) ||
|
||||
|
@ -318,7 +321,7 @@ func (s *v6Server) rmLease(lease *Lease) (err error) {
|
|||
}
|
||||
|
||||
// Find lease by MAC.
|
||||
func (s *v6Server) findLease(mac net.HardwareAddr) (lease *Lease) {
|
||||
func (s *v6Server) findLease(mac net.HardwareAddr) (lease *dhcpsvc.Lease) {
|
||||
for i := range s.leases {
|
||||
if bytes.Equal(mac, s.leases[i].HWAddr) {
|
||||
return s.leases[i]
|
||||
|
@ -356,8 +359,8 @@ func (s *v6Server) findFreeIP() net.IP {
|
|||
}
|
||||
|
||||
// Reserve lease for MAC
|
||||
func (s *v6Server) reserveLease(mac net.HardwareAddr) *Lease {
|
||||
l := Lease{
|
||||
func (s *v6Server) reserveLease(mac net.HardwareAddr) *dhcpsvc.Lease {
|
||||
l := dhcpsvc.Lease{
|
||||
HWAddr: make([]byte, len(mac)),
|
||||
}
|
||||
|
||||
|
@ -390,7 +393,7 @@ func (s *v6Server) reserveLease(mac net.HardwareAddr) *Lease {
|
|||
return &l
|
||||
}
|
||||
|
||||
func (s *v6Server) commitDynamicLease(l *Lease) {
|
||||
func (s *v6Server) commitDynamicLease(l *dhcpsvc.Lease) {
|
||||
l.Expiry = time.Now().Add(s.conf.leaseTime)
|
||||
|
||||
s.leasesLock.Lock()
|
||||
|
@ -438,7 +441,7 @@ func (s *v6Server) checkSID(msg *dhcpv6.Message) error {
|
|||
}
|
||||
|
||||
// . IAAddress must be equal to the lease's IP
|
||||
func (s *v6Server) checkIA(msg *dhcpv6.Message, lease *Lease) error {
|
||||
func (s *v6Server) checkIA(msg *dhcpv6.Message, lease *dhcpsvc.Lease) error {
|
||||
switch msg.Type() {
|
||||
case dhcpv6.MessageTypeRequest,
|
||||
dhcpv6.MessageTypeConfirm,
|
||||
|
@ -464,7 +467,7 @@ func (s *v6Server) checkIA(msg *dhcpv6.Message, lease *Lease) error {
|
|||
}
|
||||
|
||||
// Store lease in DB (if necessary) and return lease life time
|
||||
func (s *v6Server) commitLease(msg *dhcpv6.Message, lease *Lease) time.Duration {
|
||||
func (s *v6Server) commitLease(msg *dhcpv6.Message, lease *dhcpsvc.Lease) time.Duration {
|
||||
lifetime := s.conf.leaseTime
|
||||
|
||||
switch msg.Type() {
|
||||
|
@ -506,7 +509,7 @@ func (s *v6Server) process(msg *dhcpv6.Message, req, resp dhcpv6.DHCPv6) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
var lease *Lease
|
||||
var lease *dhcpsvc.Lease
|
||||
func() {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
"github.com/insomniacslk/dhcp/iana"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -28,7 +29,7 @@ func TestV6_AddRemove_static(t *testing.T) {
|
|||
require.Empty(t, s.GetLeases(LeasesStatic))
|
||||
|
||||
// Add static lease.
|
||||
l := &Lease{
|
||||
l := &dhcpsvc.Lease{
|
||||
IP: netip.MustParseAddr("2001::1"),
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}
|
||||
|
@ -47,7 +48,7 @@ func TestV6_AddRemove_static(t *testing.T) {
|
|||
assert.True(t, ls[0].IsStatic)
|
||||
|
||||
// Try to remove non-existent static lease.
|
||||
err = s.RemoveStaticLease(&Lease{
|
||||
err = s.RemoveStaticLease(&dhcpsvc.Lease{
|
||||
IP: netip.MustParseAddr("2001::2"),
|
||||
HWAddr: l.HWAddr,
|
||||
})
|
||||
|
@ -72,7 +73,7 @@ func TestV6_AddReplace(t *testing.T) {
|
|||
require.True(t, ok)
|
||||
|
||||
// Add dynamic leases.
|
||||
dynLeases := []*Lease{{
|
||||
dynLeases := []*dhcpsvc.Lease{{
|
||||
IP: netip.MustParseAddr("2001::1"),
|
||||
HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}, {
|
||||
|
@ -84,7 +85,7 @@ func TestV6_AddReplace(t *testing.T) {
|
|||
s.addLease(l)
|
||||
}
|
||||
|
||||
stLeases := []*Lease{{
|
||||
stLeases := []*dhcpsvc.Lease{{
|
||||
IP: netip.MustParseAddr("2001::1"),
|
||||
HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}, {
|
||||
|
@ -126,7 +127,7 @@ func TestV6GetLease(t *testing.T) {
|
|||
LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}
|
||||
|
||||
l := &Lease{
|
||||
l := &dhcpsvc.Lease{
|
||||
IP: netip.MustParseAddr("2001::1"),
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}
|
||||
|
@ -324,7 +325,7 @@ func TestV6_FindMACbyIP(t *testing.T) {
|
|||
anotherMAC := net.HardwareAddr{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB}
|
||||
|
||||
s := &v6Server{
|
||||
leases: []*Lease{{
|
||||
leases: []*dhcpsvc.Lease{{
|
||||
Hostname: staticName,
|
||||
HWAddr: staticMAC,
|
||||
IP: staticIP,
|
||||
|
@ -337,7 +338,7 @@ func TestV6_FindMACbyIP(t *testing.T) {
|
|||
}},
|
||||
}
|
||||
|
||||
s.leases = []*Lease{{
|
||||
s.leases = []*dhcpsvc.Lease{{
|
||||
Hostname: staticName,
|
||||
HWAddr: staticMAC,
|
||||
IP: staticIP,
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Config is the configuration for the DHCP service.
|
||||
|
@ -33,54 +35,58 @@ type InterfaceConfig struct {
|
|||
IPv6 *IPv6Config
|
||||
}
|
||||
|
||||
// IPv4Config is the interface-specific configuration for DHCPv4.
|
||||
type IPv4Config struct {
|
||||
// GatewayIP is the IPv4 address of the network's gateway. It is used as
|
||||
// the default gateway for DHCP clients and also used in calculating the
|
||||
// network-specific broadcast address.
|
||||
GatewayIP netip.Addr
|
||||
// Validate returns an error in conf if any.
|
||||
func (conf *Config) Validate() (err error) {
|
||||
switch {
|
||||
case conf == nil:
|
||||
return errNilConfig
|
||||
case !conf.Enabled:
|
||||
return nil
|
||||
case conf.ICMPTimeout < 0:
|
||||
return newMustErr("icmp timeout", "be non-negative", conf.ICMPTimeout)
|
||||
}
|
||||
|
||||
// SubnetMask is the IPv4 subnet mask of the network. It should be a valid
|
||||
// IPv4 subnet mask (i.e. all 1s followed by all 0s).
|
||||
SubnetMask netip.Addr
|
||||
err = netutil.ValidateDomainName(conf.LocalDomainName)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
// RangeStart is the first address in the range to assign to DHCP clients.
|
||||
RangeStart netip.Addr
|
||||
if len(conf.Interfaces) == 0 {
|
||||
return errNoInterfaces
|
||||
}
|
||||
|
||||
// RangeEnd is the last address in the range to assign to DHCP clients.
|
||||
RangeEnd netip.Addr
|
||||
ifaces := maps.Keys(conf.Interfaces)
|
||||
slices.Sort(ifaces)
|
||||
|
||||
// Options is the list of DHCP options to send to DHCP clients.
|
||||
Options layers.DHCPOptions
|
||||
for _, iface := range ifaces {
|
||||
if err = conf.Interfaces[iface].validate(); err != nil {
|
||||
return fmt.Errorf("interface %q: %w", iface, err)
|
||||
}
|
||||
}
|
||||
|
||||
// LeaseDuration is the TTL of a DHCP lease.
|
||||
LeaseDuration time.Duration
|
||||
|
||||
// Enabled is the state of the DHCPv4 service, whether it is enabled or not
|
||||
// on the specific interface.
|
||||
Enabled bool
|
||||
return nil
|
||||
}
|
||||
|
||||
// IPv6Config is the interface-specific configuration for DHCPv6.
|
||||
type IPv6Config struct {
|
||||
// RangeStart is the first address in the range to assign to DHCP clients.
|
||||
RangeStart netip.Addr
|
||||
|
||||
// Options is the list of DHCP options to send to DHCP clients.
|
||||
Options layers.DHCPOptions
|
||||
|
||||
// LeaseDuration is the TTL of a DHCP lease.
|
||||
LeaseDuration time.Duration
|
||||
|
||||
// RASlaacOnly defines whether the DHCP clients should only use SLAAC for
|
||||
// address assignment.
|
||||
RASLAACOnly bool
|
||||
|
||||
// RAAllowSlaac defines whether the DHCP clients may use SLAAC for address
|
||||
// assignment.
|
||||
RAAllowSLAAC bool
|
||||
|
||||
// Enabled is the state of the DHCPv6 service, whether it is enabled or not
|
||||
// on the specific interface.
|
||||
Enabled bool
|
||||
// newMustErr returns an error that indicates that valName must be as must
|
||||
// describes.
|
||||
func newMustErr(valName, must string, val fmt.Stringer) (err error) {
|
||||
return fmt.Errorf("%s %s must %s", valName, val, must)
|
||||
}
|
||||
|
||||
// validate returns an error in ic, if any.
|
||||
func (ic *InterfaceConfig) validate() (err error) {
|
||||
if ic == nil {
|
||||
return errNilConfig
|
||||
}
|
||||
|
||||
if err = ic.IPv4.validate(); err != nil {
|
||||
return fmt.Errorf("ipv4: %w", err)
|
||||
}
|
||||
|
||||
if err = ic.IPv6.validate(); err != nil {
|
||||
return fmt.Errorf("ipv6: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
88
internal/dhcpsvc/config_test.go
Normal file
88
internal/dhcpsvc/config_test.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package dhcpsvc_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
)
|
||||
|
||||
func TestConfig_Validate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
conf *dhcpsvc.Config
|
||||
wantErrMsg string
|
||||
}{{
|
||||
name: "nil_config",
|
||||
conf: nil,
|
||||
wantErrMsg: "config is nil",
|
||||
}, {
|
||||
name: "disabled",
|
||||
conf: &dhcpsvc.Config{},
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "empty",
|
||||
conf: &dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
},
|
||||
wantErrMsg: `bad domain name "": domain name is empty`,
|
||||
}, {
|
||||
conf: &dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: nil,
|
||||
},
|
||||
name: "no_interfaces",
|
||||
wantErrMsg: "no interfaces specified",
|
||||
}, {
|
||||
conf: &dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: nil,
|
||||
},
|
||||
name: "no_interfaces",
|
||||
wantErrMsg: "no interfaces specified",
|
||||
}, {
|
||||
conf: &dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||
"eth0": nil,
|
||||
},
|
||||
},
|
||||
name: "nil_interface",
|
||||
wantErrMsg: `interface "eth0": config is nil`,
|
||||
}, {
|
||||
conf: &dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||
"eth0": {
|
||||
IPv4: nil,
|
||||
IPv6: &dhcpsvc.IPv6Config{Enabled: false},
|
||||
},
|
||||
},
|
||||
},
|
||||
name: "nil_ipv4",
|
||||
wantErrMsg: `interface "eth0": ipv4: config is nil`,
|
||||
}, {
|
||||
conf: &dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||
"eth0": {
|
||||
IPv4: &dhcpsvc.IPv4Config{Enabled: false},
|
||||
IPv6: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
name: "nil_ipv6",
|
||||
wantErrMsg: `interface "eth0": ipv6: config is nil`,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, tc.conf.Validate())
|
||||
})
|
||||
}
|
||||
}
|
|
@ -10,13 +10,13 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Lease is a DHCP lease.
|
||||
//
|
||||
// TODO(e.burkov): Consider it to [agh], since it also may be needed in
|
||||
// [websvc]. Also think of implementing iterating methods with appropriate
|
||||
// signatures.
|
||||
// TODO(e.burkov): Consider moving it to [agh], since it also may be needed in
|
||||
// [websvc].
|
||||
type Lease struct {
|
||||
// IP is the IP address leased to the client.
|
||||
IP netip.Addr
|
||||
|
@ -34,6 +34,21 @@ type Lease struct {
|
|||
IsStatic bool
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of l.
|
||||
func (l *Lease) Clone() (clone *Lease) {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Lease{
|
||||
Expiry: l.Expiry,
|
||||
Hostname: l.Hostname,
|
||||
HWAddr: slices.Clone(l.HWAddr),
|
||||
IP: l.IP,
|
||||
IsStatic: l.IsStatic,
|
||||
}
|
||||
}
|
||||
|
||||
type Interface interface {
|
||||
agh.ServiceWithConfig[*Config]
|
||||
|
||||
|
@ -56,16 +71,20 @@ type Interface interface {
|
|||
// hostname, either set or generated.
|
||||
IPByHost(host string) (ip netip.Addr)
|
||||
|
||||
// Leases returns all the DHCP leases.
|
||||
Leases() (leases []*Lease)
|
||||
// Leases returns all the active DHCP leases.
|
||||
//
|
||||
// TODO(e.burkov): Consider implementing iterating methods with appropriate
|
||||
// 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(l *Lease) (err error)
|
||||
|
||||
// EditLease changes an existing DHCP lease. It returns an error if there
|
||||
// is no lease equal to old or if new is invalid or already exists.
|
||||
EditLease(old, new *Lease) (err error)
|
||||
// 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(l *Lease) (err error)
|
||||
|
||||
// RemoveLease removes an existing DHCP lease. It returns an error if there
|
||||
// is no lease equal to l.
|
||||
|
@ -79,7 +98,7 @@ type Interface interface {
|
|||
type Empty struct{}
|
||||
|
||||
// type check
|
||||
var _ Interface = Empty{}
|
||||
var _ agh.ServiceWithConfig[*Config] = Empty{}
|
||||
|
||||
// Start implements the [Service] interface for Empty.
|
||||
func (Empty) Start() (err error) { return nil }
|
||||
|
@ -87,11 +106,12 @@ func (Empty) Start() (err error) { return nil }
|
|||
// Shutdown implements the [Service] interface for Empty.
|
||||
func (Empty) Shutdown(_ context.Context) (err error) { return nil }
|
||||
|
||||
var _ agh.ServiceWithConfig[*Config] = Empty{}
|
||||
|
||||
// Config implements the [ServiceWithConfig] interface for Empty.
|
||||
func (Empty) Config() (conf *Config) { return nil }
|
||||
|
||||
// type check
|
||||
var _ Interface = Empty{}
|
||||
|
||||
// Enabled implements the [Interface] interface for Empty.
|
||||
func (Empty) Enabled() (ok bool) { return false }
|
||||
|
||||
|
@ -104,17 +124,14 @@ func (Empty) MACByIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
|
|||
// IPByHost implements the [Interface] interface for Empty.
|
||||
func (Empty) IPByHost(_ string) (ip netip.Addr) { return netip.Addr{} }
|
||||
|
||||
// type check
|
||||
var _ Interface = Empty{}
|
||||
|
||||
// Leases implements the [Interface] interface for Empty.
|
||||
func (Empty) Leases() (leases []*Lease) { return nil }
|
||||
|
||||
// AddLease implements the [Interface] interface for Empty.
|
||||
func (Empty) AddLease(_ *Lease) (err error) { return nil }
|
||||
|
||||
// EditLease implements the [Interface] interface for Empty.
|
||||
func (Empty) EditLease(_, _ *Lease) (err error) { return nil }
|
||||
// UpdateStaticLease implements the [Interface] interface for Empty.
|
||||
func (Empty) UpdateStaticLease(_ *Lease) (err error) { return nil }
|
||||
|
||||
// RemoveLease implements the [Interface] interface for Empty.
|
||||
func (Empty) RemoveLease(_ *Lease) (err error) { return nil }
|
||||
|
|
11
internal/dhcpsvc/errors.go
Normal file
11
internal/dhcpsvc/errors.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package dhcpsvc
|
||||
|
||||
import "github.com/AdguardTeam/golibs/errors"
|
||||
|
||||
const (
|
||||
// errNilConfig is returned when a nil config met.
|
||||
errNilConfig errors.Error = "config is nil"
|
||||
|
||||
// errNoInterfaces is returned when no interfaces found in configuration.
|
||||
errNoInterfaces errors.Error = "no interfaces specified"
|
||||
)
|
98
internal/dhcpsvc/iprange.go
Normal file
98
internal/dhcpsvc/iprange.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
)
|
||||
|
||||
// ipRange is an inclusive range of IP addresses. A zero range doesn't contain
|
||||
// any IP addresses.
|
||||
//
|
||||
// It is safe for concurrent use.
|
||||
type ipRange struct {
|
||||
start netip.Addr
|
||||
end netip.Addr
|
||||
}
|
||||
|
||||
// maxRangeLen is the maximum IP range length. The bitsets used in servers only
|
||||
// accept uints, which can have the size of 32 bit.
|
||||
//
|
||||
// TODO(a.garipov, e.burkov): Reconsider the value for IPv6.
|
||||
const maxRangeLen = math.MaxUint32
|
||||
|
||||
// newIPRange creates a new IP address range. start must be less than end. The
|
||||
// resulting range must not be greater than maxRangeLen.
|
||||
func newIPRange(start, end netip.Addr) (r ipRange, err error) {
|
||||
defer func() { err = errors.Annotate(err, "invalid ip range: %w") }()
|
||||
|
||||
switch false {
|
||||
case start.Is4() == end.Is4():
|
||||
return ipRange{}, fmt.Errorf("%s and %s must be within the same address family", start, end)
|
||||
case start.Less(end):
|
||||
return ipRange{}, fmt.Errorf("start %s is greater than or equal to end %s", start, end)
|
||||
default:
|
||||
diff := (&big.Int{}).Sub(
|
||||
(&big.Int{}).SetBytes(end.AsSlice()),
|
||||
(&big.Int{}).SetBytes(start.AsSlice()),
|
||||
)
|
||||
|
||||
if !diff.IsUint64() || diff.Uint64() > maxRangeLen {
|
||||
return ipRange{}, fmt.Errorf("range length must be within %d", uint32(maxRangeLen))
|
||||
}
|
||||
}
|
||||
|
||||
return ipRange{
|
||||
start: start,
|
||||
end: end,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// contains returns true if r contains ip.
|
||||
func (r ipRange) contains(ip netip.Addr) (ok bool) {
|
||||
// Assume that the end was checked to be within the same address family as
|
||||
// the start during construction.
|
||||
return r.start.Is4() == ip.Is4() && !ip.Less(r.start) && !r.end.Less(ip)
|
||||
}
|
||||
|
||||
// ipPredicate is a function that is called on every IP address in
|
||||
// [ipRange.find].
|
||||
type ipPredicate func(ip netip.Addr) (ok bool)
|
||||
|
||||
// find finds the first IP address in r for which p returns true. It returns an
|
||||
// empty [netip.Addr] if there are no addresses that satisfy p.
|
||||
//
|
||||
// TODO(e.burkov): Use.
|
||||
func (r ipRange) find(p ipPredicate) (ip netip.Addr) {
|
||||
for ip = r.start; !r.end.Less(ip); ip = ip.Next() {
|
||||
if p(ip) {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
// offset returns the offset of ip from the beginning of r. It returns 0 and
|
||||
// false if ip is not in r.
|
||||
func (r ipRange) offset(ip netip.Addr) (offset uint64, ok bool) {
|
||||
if !r.contains(ip) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
startData, ipData := r.start.As16(), ip.As16()
|
||||
be := binary.BigEndian
|
||||
|
||||
// Assume that the range length was checked against maxRangeLen during
|
||||
// construction.
|
||||
return be.Uint64(ipData[8:]) - be.Uint64(startData[8:]), true
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer interface for *ipRange.
|
||||
func (r ipRange) String() (s string) {
|
||||
return fmt.Sprintf("%s-%s", r.start, r.end)
|
||||
}
|
204
internal/dhcpsvc/iprange_internal_test.go
Normal file
204
internal/dhcpsvc/iprange_internal_test.go
Normal file
|
@ -0,0 +1,204 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewIPRange(t *testing.T) {
|
||||
start4 := netip.MustParseAddr("0.0.0.1")
|
||||
end4 := netip.MustParseAddr("0.0.0.3")
|
||||
start6 := netip.MustParseAddr("1::1")
|
||||
end6 := netip.MustParseAddr("1::3")
|
||||
end6Large := netip.MustParseAddr("2::3")
|
||||
|
||||
testCases := []struct {
|
||||
start netip.Addr
|
||||
end netip.Addr
|
||||
name string
|
||||
wantErrMsg string
|
||||
}{{
|
||||
start: start4,
|
||||
end: end4,
|
||||
name: "success_ipv4",
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
start: start6,
|
||||
end: end6,
|
||||
name: "success_ipv6",
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
start: end4,
|
||||
end: start4,
|
||||
name: "start_gt_end",
|
||||
wantErrMsg: "invalid ip range: start 0.0.0.3 is greater than or equal to end 0.0.0.1",
|
||||
}, {
|
||||
start: start4,
|
||||
end: start4,
|
||||
name: "start_eq_end",
|
||||
wantErrMsg: "invalid ip range: start 0.0.0.1 is greater than or equal to end 0.0.0.1",
|
||||
}, {
|
||||
start: start6,
|
||||
end: end6Large,
|
||||
name: "too_large",
|
||||
wantErrMsg: "invalid ip range: range length must be within " +
|
||||
strconv.FormatUint(maxRangeLen, 10),
|
||||
}, {
|
||||
start: start4,
|
||||
end: end6,
|
||||
name: "different_family",
|
||||
wantErrMsg: "invalid ip range: 0.0.0.1 and 1::3 must be within the same address family",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := newIPRange(tc.start, tc.end)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPRange_Contains(t *testing.T) {
|
||||
start, end := netip.MustParseAddr("0.0.0.1"), netip.MustParseAddr("0.0.0.3")
|
||||
r, err := newIPRange(start, end)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
in netip.Addr
|
||||
want assert.BoolAssertionFunc
|
||||
name string
|
||||
}{{
|
||||
in: start,
|
||||
want: assert.True,
|
||||
name: "start",
|
||||
}, {
|
||||
in: end,
|
||||
want: assert.True,
|
||||
name: "end",
|
||||
}, {
|
||||
in: start.Next(),
|
||||
want: assert.True,
|
||||
name: "within",
|
||||
}, {
|
||||
in: netip.MustParseAddr("0.0.0.0"),
|
||||
want: assert.False,
|
||||
name: "before",
|
||||
}, {
|
||||
in: netip.MustParseAddr("0.0.0.4"),
|
||||
want: assert.False,
|
||||
name: "after",
|
||||
}, {
|
||||
in: netip.MustParseAddr("::"),
|
||||
want: assert.False,
|
||||
name: "another_family",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.want(t, r.contains(tc.in))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPRange_Find(t *testing.T) {
|
||||
start, end := netip.MustParseAddr("0.0.0.1"), netip.MustParseAddr("0.0.0.5")
|
||||
r, err := newIPRange(start, end)
|
||||
require.NoError(t, err)
|
||||
|
||||
num, ok := r.offset(end)
|
||||
require.True(t, ok)
|
||||
|
||||
testCases := []struct {
|
||||
predicate ipPredicate
|
||||
want netip.Addr
|
||||
name string
|
||||
}{{
|
||||
predicate: func(ip netip.Addr) (ok bool) {
|
||||
ipData := ip.AsSlice()
|
||||
|
||||
return ipData[len(ipData)-1]%2 == 0
|
||||
},
|
||||
want: netip.MustParseAddr("0.0.0.2"),
|
||||
name: "even",
|
||||
}, {
|
||||
predicate: func(ip netip.Addr) (ok bool) {
|
||||
ipData := ip.AsSlice()
|
||||
|
||||
return ipData[len(ipData)-1]%10 == 0
|
||||
},
|
||||
want: netip.Addr{},
|
||||
name: "none",
|
||||
}, {
|
||||
predicate: func(ip netip.Addr) (ok bool) {
|
||||
return true
|
||||
},
|
||||
want: start,
|
||||
name: "first",
|
||||
}, {
|
||||
predicate: func(ip netip.Addr) (ok bool) {
|
||||
off, _ := r.offset(ip)
|
||||
|
||||
return off == num
|
||||
},
|
||||
want: end,
|
||||
name: "last",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := r.find(tc.predicate)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPRange_Offset(t *testing.T) {
|
||||
start, end := netip.MustParseAddr("0.0.0.1"), netip.MustParseAddr("0.0.0.5")
|
||||
r, err := newIPRange(start, end)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
in netip.Addr
|
||||
name string
|
||||
wantOffset uint64
|
||||
wantOK bool
|
||||
}{{
|
||||
in: netip.MustParseAddr("0.0.0.2"),
|
||||
name: "in",
|
||||
wantOffset: 1,
|
||||
wantOK: true,
|
||||
}, {
|
||||
in: start,
|
||||
name: "in_start",
|
||||
wantOffset: 0,
|
||||
wantOK: true,
|
||||
}, {
|
||||
in: end,
|
||||
name: "in_end",
|
||||
wantOffset: 4,
|
||||
wantOK: true,
|
||||
}, {
|
||||
in: netip.MustParseAddr("0.0.0.6"),
|
||||
name: "out_after",
|
||||
wantOffset: 0,
|
||||
wantOK: false,
|
||||
}, {
|
||||
in: netip.MustParseAddr("0.0.0.0"),
|
||||
name: "out_before",
|
||||
wantOffset: 0,
|
||||
wantOK: false,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
offset, ok := r.offset(tc.in)
|
||||
assert.Equal(t, tc.wantOffset, offset)
|
||||
assert.Equal(t, tc.wantOK, ok)
|
||||
})
|
||||
}
|
||||
}
|
100
internal/dhcpsvc/server.go
Normal file
100
internal/dhcpsvc/server.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// DHCPServer is a DHCP server for both IPv4 and IPv6 address families.
|
||||
type DHCPServer struct {
|
||||
// enabled indicates whether the DHCP server is enabled and can provide
|
||||
// information about its clients.
|
||||
enabled *atomic.Bool
|
||||
|
||||
// localTLD is the top-level domain name to use for resolving DHCP
|
||||
// clients' hostnames.
|
||||
localTLD string
|
||||
|
||||
// interfaces4 is the set of IPv4 interfaces sorted by interface name.
|
||||
interfaces4 []*iface4
|
||||
|
||||
// interfaces6 is the set of IPv6 interfaces sorted by interface name.
|
||||
interfaces6 []*iface6
|
||||
|
||||
// leases is the set of active DHCP leases.
|
||||
leases []*Lease
|
||||
|
||||
// icmpTimeout is the timeout for checking another DHCP server's presence.
|
||||
icmpTimeout time.Duration
|
||||
}
|
||||
|
||||
// New creates a new DHCP server with the given configuration. It returns an
|
||||
// error if the given configuration can't be used.
|
||||
//
|
||||
// TODO(e.burkov): Use.
|
||||
func New(conf *Config) (srv *DHCPServer, err error) {
|
||||
if !conf.Enabled {
|
||||
// TODO(e.burkov): Perhaps return [Empty]?
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ifaces4 := make([]*iface4, len(conf.Interfaces))
|
||||
ifaces6 := make([]*iface6, len(conf.Interfaces))
|
||||
|
||||
ifaceNames := maps.Keys(conf.Interfaces)
|
||||
slices.Sort(ifaceNames)
|
||||
|
||||
var i4 *iface4
|
||||
var i6 *iface6
|
||||
|
||||
for _, ifaceName := range ifaceNames {
|
||||
iface := conf.Interfaces[ifaceName]
|
||||
|
||||
i4, err = newIface4(ifaceName, iface.IPv4)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("interface %q: ipv4: %w", ifaceName, err)
|
||||
} else if i4 != nil {
|
||||
ifaces4 = append(ifaces4, i4)
|
||||
}
|
||||
|
||||
i6 = newIface6(ifaceName, iface.IPv6)
|
||||
if i6 != nil {
|
||||
ifaces6 = append(ifaces6, i6)
|
||||
}
|
||||
}
|
||||
|
||||
enabled := &atomic.Bool{}
|
||||
enabled.Store(conf.Enabled)
|
||||
|
||||
return &DHCPServer{
|
||||
enabled: enabled,
|
||||
interfaces4: ifaces4,
|
||||
interfaces6: ifaces6,
|
||||
localTLD: conf.LocalDomainName,
|
||||
icmpTimeout: conf.ICMPTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
//
|
||||
// TODO(e.burkov): Uncomment when the [Interface] interface is implemented.
|
||||
// var _ Interface = (*DHCPServer)(nil)
|
||||
|
||||
// Enabled implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) Enabled() (ok bool) {
|
||||
return srv.enabled.Load()
|
||||
}
|
||||
|
||||
// Leases implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) Leases() (leases []*Lease) {
|
||||
leases = make([]*Lease, 0, len(srv.leases))
|
||||
for _, lease := range srv.leases {
|
||||
leases = append(leases, lease.Clone())
|
||||
}
|
||||
|
||||
return leases
|
||||
}
|
115
internal/dhcpsvc/server_test.go
Normal file
115
internal/dhcpsvc/server_test.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package dhcpsvc_test
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
)
|
||||
|
||||
// testLocalTLD is a common local TLD for tests.
|
||||
const testLocalTLD = "local"
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
validIPv4Conf := &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,
|
||||
}
|
||||
gwInRangeConf := &dhcpsvc.IPv4Config{
|
||||
Enabled: true,
|
||||
GatewayIP: netip.MustParseAddr("192.168.0.100"),
|
||||
SubnetMask: netip.MustParseAddr("255.255.255.0"),
|
||||
RangeStart: netip.MustParseAddr("192.168.0.1"),
|
||||
RangeEnd: netip.MustParseAddr("192.168.0.254"),
|
||||
LeaseDuration: 1 * time.Hour,
|
||||
}
|
||||
badStartConf := &dhcpsvc.IPv4Config{
|
||||
Enabled: true,
|
||||
GatewayIP: netip.MustParseAddr("192.168.0.1"),
|
||||
SubnetMask: netip.MustParseAddr("255.255.255.0"),
|
||||
RangeStart: netip.MustParseAddr("127.0.0.1"),
|
||||
RangeEnd: netip.MustParseAddr("192.168.0.254"),
|
||||
LeaseDuration: 1 * time.Hour,
|
||||
}
|
||||
|
||||
validIPv6Conf := &dhcpsvc.IPv6Config{
|
||||
Enabled: true,
|
||||
RangeStart: netip.MustParseAddr("2001:db8::1"),
|
||||
LeaseDuration: 1 * time.Hour,
|
||||
RAAllowSLAAC: true,
|
||||
RASLAACOnly: true,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
conf *dhcpsvc.Config
|
||||
name string
|
||||
wantErrMsg string
|
||||
}{{
|
||||
conf: &dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||
"eth0": {
|
||||
IPv4: validIPv4Conf,
|
||||
IPv6: validIPv6Conf,
|
||||
},
|
||||
},
|
||||
},
|
||||
name: "valid",
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
conf: &dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||
"eth0": {
|
||||
IPv4: &dhcpsvc.IPv4Config{Enabled: false},
|
||||
IPv6: &dhcpsvc.IPv6Config{Enabled: false},
|
||||
},
|
||||
},
|
||||
},
|
||||
name: "disabled_interfaces",
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
conf: &dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||
"eth0": {
|
||||
IPv4: gwInRangeConf,
|
||||
IPv6: validIPv6Conf,
|
||||
},
|
||||
},
|
||||
},
|
||||
name: "gateway_within_range",
|
||||
wantErrMsg: `interface "eth0": ipv4: ` +
|
||||
`gateway ip 192.168.0.100 in the ip range 192.168.0.1-192.168.0.254`,
|
||||
}, {
|
||||
conf: &dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||
"eth0": {
|
||||
IPv4: badStartConf,
|
||||
IPv6: validIPv6Conf,
|
||||
},
|
||||
},
|
||||
},
|
||||
name: "bad_start",
|
||||
wantErrMsg: `interface "eth0": ipv4: ` +
|
||||
`range start 127.0.0.1 is not within 192.168.0.1/24`,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := dhcpsvc.New(tc.conf)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
})
|
||||
}
|
||||
}
|
113
internal/dhcpsvc/v4.go
Normal file
113
internal/dhcpsvc/v4.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
// IPv4Config is the interface-specific configuration for DHCPv4.
|
||||
type IPv4Config struct {
|
||||
// GatewayIP is the IPv4 address of the network's gateway. It is used as
|
||||
// the default gateway for DHCP clients and also used in calculating the
|
||||
// network-specific broadcast address.
|
||||
GatewayIP netip.Addr
|
||||
|
||||
// SubnetMask is the IPv4 subnet mask of the network. It should be a valid
|
||||
// IPv4 CIDR (i.e. all 1s followed by all 0s).
|
||||
SubnetMask netip.Addr
|
||||
|
||||
// RangeStart is the first address in the range to assign to DHCP clients.
|
||||
RangeStart netip.Addr
|
||||
|
||||
// RangeEnd is the last address in the range to assign to DHCP clients.
|
||||
RangeEnd netip.Addr
|
||||
|
||||
// Options is the list of DHCP options to send to DHCP clients.
|
||||
Options layers.DHCPOptions
|
||||
|
||||
// LeaseDuration is the TTL of a DHCP lease.
|
||||
LeaseDuration time.Duration
|
||||
|
||||
// Enabled is the state of the DHCPv4 service, whether it is enabled or not
|
||||
// on the specific interface.
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// validate returns an error in conf if any.
|
||||
func (conf *IPv4Config) validate() (err error) {
|
||||
switch {
|
||||
case conf == nil:
|
||||
return errNilConfig
|
||||
case !conf.Enabled:
|
||||
return nil
|
||||
case !conf.GatewayIP.Is4():
|
||||
return newMustErr("gateway ip", "be a valid ipv4", conf.GatewayIP)
|
||||
case !conf.SubnetMask.Is4():
|
||||
return newMustErr("subnet mask", "be a valid ipv4 cidr mask", conf.SubnetMask)
|
||||
case !conf.RangeStart.Is4():
|
||||
return newMustErr("range start", "be a valid ipv4", conf.RangeStart)
|
||||
case !conf.RangeEnd.Is4():
|
||||
return newMustErr("range end", "be a valid ipv4", conf.RangeEnd)
|
||||
case conf.LeaseDuration <= 0:
|
||||
return newMustErr("lease duration", "be less than %d", conf.LeaseDuration)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// iface4 is a DHCP interface for IPv4 address family.
|
||||
type iface4 struct {
|
||||
// gateway is the IP address of the network gateway.
|
||||
gateway netip.Addr
|
||||
|
||||
// subnet is the network subnet.
|
||||
subnet netip.Prefix
|
||||
|
||||
// addrSpace is the IPv4 address space allocated for leasing.
|
||||
addrSpace ipRange
|
||||
|
||||
// name is the name of the interface.
|
||||
name string
|
||||
|
||||
// TODO(e.burkov): Add options.
|
||||
|
||||
// leaseTTL is the time-to-live of dynamic leases on this interface.
|
||||
leaseTTL time.Duration
|
||||
}
|
||||
|
||||
// newIface4 creates a new DHCP interface for IPv4 address family with the given
|
||||
// configuration. It returns an error if the given configuration can't be used.
|
||||
func newIface4(name string, conf *IPv4Config) (i *iface4, err error) {
|
||||
if !conf.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
maskLen, _ := net.IPMask(conf.SubnetMask.AsSlice()).Size()
|
||||
subnet := netip.PrefixFrom(conf.GatewayIP, maskLen)
|
||||
|
||||
switch {
|
||||
case !subnet.Contains(conf.RangeStart):
|
||||
return nil, fmt.Errorf("range start %s is not within %s", conf.RangeStart, subnet)
|
||||
case !subnet.Contains(conf.RangeEnd):
|
||||
return nil, fmt.Errorf("range end %s is not within %s", conf.RangeEnd, subnet)
|
||||
}
|
||||
|
||||
addrSpace, err := newIPRange(conf.RangeStart, conf.RangeEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if addrSpace.contains(conf.GatewayIP) {
|
||||
return nil, fmt.Errorf("gateway ip %s in the ip range %s", conf.GatewayIP, addrSpace)
|
||||
}
|
||||
|
||||
return &iface4{
|
||||
name: name,
|
||||
gateway: conf.GatewayIP,
|
||||
subnet: subnet,
|
||||
addrSpace: addrSpace,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
}, nil
|
||||
}
|
88
internal/dhcpsvc/v6.go
Normal file
88
internal/dhcpsvc/v6.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
// IPv6Config is the interface-specific configuration for DHCPv6.
|
||||
type IPv6Config struct {
|
||||
// RangeStart is the first address in the range to assign to DHCP clients.
|
||||
RangeStart netip.Addr
|
||||
|
||||
// Options is the list of DHCP options to send to DHCP clients.
|
||||
Options layers.DHCPOptions
|
||||
|
||||
// LeaseDuration is the TTL of a DHCP lease.
|
||||
LeaseDuration time.Duration
|
||||
|
||||
// RASlaacOnly defines whether the DHCP clients should only use SLAAC for
|
||||
// address assignment.
|
||||
RASLAACOnly bool
|
||||
|
||||
// RAAllowSlaac defines whether the DHCP clients may use SLAAC for address
|
||||
// assignment.
|
||||
RAAllowSLAAC bool
|
||||
|
||||
// Enabled is the state of the DHCPv6 service, whether it is enabled or not
|
||||
// on the specific interface.
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// validate returns an error in conf if any.
|
||||
func (conf *IPv6Config) validate() (err error) {
|
||||
switch {
|
||||
case conf == nil:
|
||||
return errNilConfig
|
||||
case !conf.Enabled:
|
||||
return nil
|
||||
case !conf.RangeStart.Is6():
|
||||
return fmt.Errorf("range start %s should be a valid ipv6", conf.RangeStart)
|
||||
case conf.LeaseDuration <= 0:
|
||||
return fmt.Errorf("lease duration %s must be positive", conf.LeaseDuration)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// iface6 is a DHCP interface for IPv6 address family.
|
||||
//
|
||||
// TODO(e.burkov): Add options.
|
||||
type iface6 struct {
|
||||
// rangeStart is the first IP address in the range.
|
||||
rangeStart netip.Addr
|
||||
|
||||
// name is the name of the interface.
|
||||
name string
|
||||
|
||||
// leaseTTL is the time-to-live of dynamic leases on this interface.
|
||||
leaseTTL time.Duration
|
||||
|
||||
// raSLAACOnly defines if DHCP should send ICMPv6.RA packets without MO
|
||||
// flags.
|
||||
raSLAACOnly bool
|
||||
|
||||
// raAllowSLAAC defines if DHCP should send ICMPv6.RA packets with MO flags.
|
||||
raAllowSLAAC bool
|
||||
}
|
||||
|
||||
// newIface6 creates a new DHCP interface for IPv6 address family with the given
|
||||
// configuration.
|
||||
//
|
||||
// TODO(e.burkov): Validate properly.
|
||||
func newIface6(name string, conf *IPv6Config) (i *iface6) {
|
||||
if !conf.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &iface6{
|
||||
name: name,
|
||||
rangeStart: conf.RangeStart,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
raSLAACOnly: conf.RASLAACOnly,
|
||||
raAllowSLAAC: conf.RAAllowSLAAC,
|
||||
}
|
||||
}
|
|
@ -27,6 +27,19 @@ import (
|
|||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// ClientsContainer provides information about preconfigured DNS clients.
|
||||
type ClientsContainer interface {
|
||||
// UpstreamConfigByID returns the custom upstream configuration for the
|
||||
// client having id, using boot to initialize the one if necessary. It
|
||||
// returns nil if there is no custom upstream configuration for the client.
|
||||
// The id is expected to be either a string representation of an IP address
|
||||
// or the ClientID.
|
||||
UpstreamConfigByID(
|
||||
id string,
|
||||
boot upstream.Resolver,
|
||||
) (conf *proxy.CustomUpstreamConfig, err error)
|
||||
}
|
||||
|
||||
// Config represents the DNS filtering configuration of AdGuard Home. The zero
|
||||
// Config is empty and ready for use.
|
||||
type Config struct {
|
||||
|
@ -35,10 +48,9 @@ type Config struct {
|
|||
// FilterHandler is an optional additional filtering callback.
|
||||
FilterHandler func(cliAddr netip.Addr, clientID string, settings *filtering.Settings) `yaml:"-"`
|
||||
|
||||
// GetCustomUpstreamByClient is a callback that returns upstreams
|
||||
// configuration based on the client IP address or ClientID. It returns
|
||||
// nil if there are no custom upstreams for the client.
|
||||
GetCustomUpstreamByClient func(id string) (conf *proxy.UpstreamConfig, err error) `yaml:"-"`
|
||||
// ClientsContainer stores the information about special handling of some
|
||||
// DNS clients.
|
||||
ClientsContainer ClientsContainer `yaml:"-"`
|
||||
|
||||
// Anti-DNS amplification
|
||||
|
||||
|
@ -55,7 +67,7 @@ type Config struct {
|
|||
RatelimitSubnetLenIPv6 int `yaml:"ratelimit_subnet_len_ipv6"`
|
||||
|
||||
// RatelimitWhitelist is the list of whitelisted client IP addresses.
|
||||
RatelimitWhitelist []string `yaml:"ratelimit_whitelist"`
|
||||
RatelimitWhitelist []netip.Addr `yaml:"ratelimit_whitelist"`
|
||||
|
||||
// RefuseAny, if true, refuse ANY requests.
|
||||
RefuseAny bool `yaml:"refuse_any"`
|
||||
|
@ -277,32 +289,33 @@ type ServerConfig struct {
|
|||
// UseHTTP3Upstreams defines if HTTP/3 is be allowed for DNS-over-HTTPS
|
||||
// upstreams.
|
||||
UseHTTP3Upstreams bool
|
||||
|
||||
// ServePlainDNS defines if plain DNS is allowed for incoming requests.
|
||||
ServePlainDNS bool
|
||||
}
|
||||
|
||||
// createProxyConfig creates and validates configuration for the main proxy.
|
||||
func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
|
||||
// newProxyConfig creates and validates configuration for the main proxy.
|
||||
func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
|
||||
srvConf := s.conf
|
||||
conf = proxy.Config{
|
||||
UDPListenAddr: srvConf.UDPListenAddrs,
|
||||
TCPListenAddr: srvConf.TCPListenAddrs,
|
||||
HTTP3: srvConf.ServeHTTP3,
|
||||
Ratelimit: int(srvConf.Ratelimit),
|
||||
RatelimitSubnetMaskIPv4: net.CIDRMask(srvConf.RatelimitSubnetLenIPv4, netutil.IPv4BitLen),
|
||||
RatelimitSubnetMaskIPv6: net.CIDRMask(srvConf.RatelimitSubnetLenIPv6, netutil.IPv6BitLen),
|
||||
RatelimitWhitelist: srvConf.RatelimitWhitelist,
|
||||
RefuseAny: srvConf.RefuseAny,
|
||||
TrustedProxies: srvConf.TrustedProxies,
|
||||
CacheMinTTL: srvConf.CacheMinTTL,
|
||||
CacheMaxTTL: srvConf.CacheMaxTTL,
|
||||
CacheOptimistic: srvConf.CacheOptimistic,
|
||||
UpstreamConfig: srvConf.UpstreamConfig,
|
||||
BeforeRequestHandler: s.beforeRequestHandler,
|
||||
RequestHandler: s.handleDNSRequest,
|
||||
HTTPSServerName: aghhttp.UserAgent(),
|
||||
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
|
||||
MaxGoroutines: int(srvConf.MaxGoroutines),
|
||||
UseDNS64: srvConf.UseDNS64,
|
||||
DNS64Prefs: srvConf.DNS64Prefixes,
|
||||
conf = &proxy.Config{
|
||||
HTTP3: srvConf.ServeHTTP3,
|
||||
Ratelimit: int(srvConf.Ratelimit),
|
||||
RatelimitSubnetLenIPv4: srvConf.RatelimitSubnetLenIPv4,
|
||||
RatelimitSubnetLenIPv6: srvConf.RatelimitSubnetLenIPv6,
|
||||
RatelimitWhitelist: srvConf.RatelimitWhitelist,
|
||||
RefuseAny: srvConf.RefuseAny,
|
||||
TrustedProxies: srvConf.TrustedProxies,
|
||||
CacheMinTTL: srvConf.CacheMinTTL,
|
||||
CacheMaxTTL: srvConf.CacheMaxTTL,
|
||||
CacheOptimistic: srvConf.CacheOptimistic,
|
||||
UpstreamConfig: srvConf.UpstreamConfig,
|
||||
BeforeRequestHandler: s.beforeRequestHandler,
|
||||
RequestHandler: s.handleDNSRequest,
|
||||
HTTPSServerName: aghhttp.UserAgent(),
|
||||
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
|
||||
MaxGoroutines: int(srvConf.MaxGoroutines),
|
||||
UseDNS64: srvConf.UseDNS64,
|
||||
DNS64Prefs: srvConf.DNS64Prefixes,
|
||||
}
|
||||
|
||||
if srvConf.EDNSClientSubnet.UseCustom {
|
||||
|
@ -316,27 +329,25 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
|
|||
}
|
||||
|
||||
setProxyUpstreamMode(
|
||||
&conf,
|
||||
conf,
|
||||
srvConf.AllServers,
|
||||
srvConf.FastestAddr,
|
||||
srvConf.FastestTimeout.Duration,
|
||||
)
|
||||
|
||||
for i, s := range srvConf.BogusNXDomain {
|
||||
var subnet *net.IPNet
|
||||
subnet, err = netutil.ParseSubnet(s)
|
||||
if err != nil {
|
||||
log.Error("subnet at index %d: %s", i, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
conf.BogusNXDomain = append(conf.BogusNXDomain, subnet)
|
||||
conf.BogusNXDomain, err = parseBogusNXDOMAIN(srvConf.BogusNXDomain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bogus_nxdomain: %w", err)
|
||||
}
|
||||
|
||||
err = s.prepareTLS(&conf)
|
||||
err = s.prepareTLS(conf)
|
||||
if err != nil {
|
||||
return proxy.Config{}, fmt.Errorf("validating tls: %w", err)
|
||||
return nil, fmt.Errorf("validating tls: %w", err)
|
||||
}
|
||||
|
||||
err = s.preparePlain(conf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating plain: %w", err)
|
||||
}
|
||||
|
||||
if c := srvConf.DNSCryptConfig; c.Enabled {
|
||||
|
@ -347,12 +358,27 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
|
|||
}
|
||||
|
||||
if conf.UpstreamConfig == nil || len(conf.UpstreamConfig.Upstreams) == 0 {
|
||||
return proxy.Config{}, errors.Error("no default upstream servers configured")
|
||||
return nil, errors.Error("no default upstream servers configured")
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// parseBogusNXDOMAIN parses the bogus NXDOMAIN strings into valid subnets.
|
||||
func parseBogusNXDOMAIN(confBogusNXDOMAIN []string) (subnets []netip.Prefix, err error) {
|
||||
for i, s := range confBogusNXDOMAIN {
|
||||
var subnet netip.Prefix
|
||||
subnet, err = aghnet.ParseSubnet(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("subnet at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
subnets = append(subnets, subnet)
|
||||
}
|
||||
|
||||
return subnets, nil
|
||||
}
|
||||
|
||||
const defaultBlockedResponseTTL = 3600
|
||||
|
||||
// initDefaultSettings initializes default settings if nothing
|
||||
|
@ -423,10 +449,7 @@ func collectListenAddr(
|
|||
|
||||
// collectDNSAddrs returns configured set of listening addresses. It also
|
||||
// returns a set of ports of each unspecified listening address.
|
||||
func (conf *ServerConfig) collectDNSAddrs() (
|
||||
addrs map[netip.AddrPort]unit,
|
||||
unspecPorts map[uint16]unit,
|
||||
) {
|
||||
func (conf *ServerConfig) collectDNSAddrs() (addrs mapAddrPortSet, unspecPorts map[uint16]unit) {
|
||||
// TODO(e.burkov): Perhaps, we shouldn't allocate as much memory, since the
|
||||
// TCP and UDP listening addresses are currently the same.
|
||||
addrs = make(map[netip.AddrPort]unit, len(conf.TCPListenAddrs)+len(conf.UDPListenAddrs))
|
||||
|
@ -446,20 +469,64 @@ func (conf *ServerConfig) collectDNSAddrs() (
|
|||
// defaultPlainDNSPort is the default port for plain DNS.
|
||||
const defaultPlainDNSPort uint16 = 53
|
||||
|
||||
// addrPortMatcher is a function that matches an IP address with port.
|
||||
type addrPortMatcher func(addr netip.AddrPort) (ok bool)
|
||||
// addrPortSet is a set of [netip.AddrPort] values.
|
||||
type addrPortSet interface {
|
||||
// Has returns true if addrPort is in the set.
|
||||
Has(addrPort netip.AddrPort) (ok bool)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ addrPortSet = emptyAddrPortSet{}
|
||||
|
||||
// emptyAddrPortSet is the [addrPortSet] containing no values.
|
||||
type emptyAddrPortSet struct{}
|
||||
|
||||
// Has implements the [addrPortSet] interface for [emptyAddrPortSet].
|
||||
func (emptyAddrPortSet) Has(_ netip.AddrPort) (ok bool) { return false }
|
||||
|
||||
// mapAddrPortSet is the [addrPortSet] containing values of [netip.AddrPort] as
|
||||
// keys of a map.
|
||||
type mapAddrPortSet map[netip.AddrPort]unit
|
||||
|
||||
// type check
|
||||
var _ addrPortSet = mapAddrPortSet{}
|
||||
|
||||
// Has implements the [addrPortSet] interface for [mapAddrPortSet].
|
||||
func (m mapAddrPortSet) Has(addrPort netip.AddrPort) (ok bool) {
|
||||
_, ok = m[addrPort]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// combinedAddrPortSet is the [addrPortSet] defined by some IP addresses along
|
||||
// with ports, any combination of which is considered being in the set.
|
||||
type combinedAddrPortSet struct {
|
||||
// TODO(e.burkov): Use sorted slices in combination with binary search.
|
||||
ports map[uint16]unit
|
||||
addrs []netip.Addr
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ addrPortSet = (*combinedAddrPortSet)(nil)
|
||||
|
||||
// Has implements the [addrPortSet] interface for [*combinedAddrPortSet].
|
||||
func (m *combinedAddrPortSet) Has(addrPort netip.AddrPort) (ok bool) {
|
||||
_, ok = m.ports[addrPort.Port()]
|
||||
|
||||
return ok && slices.Contains(m.addrs, addrPort.Addr())
|
||||
}
|
||||
|
||||
// filterOut filters out all the upstreams that match um. It returns all the
|
||||
// closing errors joined.
|
||||
func (m addrPortMatcher) filterOut(upsConf *proxy.UpstreamConfig) (err error) {
|
||||
func filterOutAddrs(upsConf *proxy.UpstreamConfig, set addrPortSet) (err error) {
|
||||
var errs []error
|
||||
delFunc := func(u upstream.Upstream) (ok bool) {
|
||||
// TODO(e.burkov): We should probably consider the protocol of u to
|
||||
// only filter out the listening addresses of the same protocol.
|
||||
addr, parseErr := aghnet.ParseAddrPort(u.Address(), defaultPlainDNSPort)
|
||||
if parseErr != nil || !m(addr) {
|
||||
if parseErr != nil || !set.Has(addr) {
|
||||
// Don't filter out the upstream if it either cannot be parsed, or
|
||||
// does not match um.
|
||||
// does not match m.
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -479,26 +546,20 @@ func (m addrPortMatcher) filterOut(upsConf *proxy.UpstreamConfig) (err error) {
|
|||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// ourAddrsMatcher returns a matcher that matches all the configured listening
|
||||
// ourAddrsSet returns an addrPortSet that contains all the configured listening
|
||||
// addresses.
|
||||
func (conf *ServerConfig) ourAddrsMatcher() (m addrPortMatcher, err error) {
|
||||
func (conf *ServerConfig) ourAddrsSet() (m addrPortSet, err error) {
|
||||
addrs, unspecPorts := conf.collectDNSAddrs()
|
||||
if len(addrs) == 0 {
|
||||
switch {
|
||||
case len(addrs) == 0:
|
||||
log.Debug("dnsforward: no listen addresses")
|
||||
|
||||
// Match no addresses.
|
||||
return func(_ netip.AddrPort) (ok bool) { return false }, nil
|
||||
}
|
||||
|
||||
if len(unspecPorts) == 0 {
|
||||
return emptyAddrPortSet{}, nil
|
||||
case len(unspecPorts) == 0:
|
||||
log.Debug("dnsforward: filtering out addresses %s", addrs)
|
||||
|
||||
m = func(a netip.AddrPort) (ok bool) {
|
||||
_, ok = addrs[a]
|
||||
|
||||
return ok
|
||||
}
|
||||
} else {
|
||||
return addrs, nil
|
||||
default:
|
||||
var ifaceAddrs []netip.Addr
|
||||
ifaceAddrs, err = aghnet.CollectAllIfacesAddrs()
|
||||
if err != nil {
|
||||
|
@ -508,16 +569,11 @@ func (conf *ServerConfig) ourAddrsMatcher() (m addrPortMatcher, err error) {
|
|||
|
||||
log.Debug("dnsforward: filtering out addresses %s on ports %d", ifaceAddrs, unspecPorts)
|
||||
|
||||
m = func(a netip.AddrPort) (ok bool) {
|
||||
if _, ok = unspecPorts[a.Port()]; ok {
|
||||
return slices.Contains(ifaceAddrs, a.Addr())
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
return &combinedAddrPortSet{
|
||||
ports: unspecPorts,
|
||||
addrs: ifaceAddrs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// prepareTLS - prepares TLS configuration for the DNS proxy
|
||||
|
@ -574,7 +630,7 @@ func (s *Server) prepareTLS(proxyConfig *proxy.Config) (err error) {
|
|||
|
||||
// isWildcard returns true if host is a wildcard hostname.
|
||||
func isWildcard(host string) (ok bool) {
|
||||
return len(host) >= 2 && host[0] == '*' && host[1] == '.'
|
||||
return strings.HasPrefix(host, "*.")
|
||||
}
|
||||
|
||||
// matchesDomainWildcard returns true if host matches the domain wildcard
|
||||
|
@ -614,6 +670,31 @@ func (s *Server) onGetCertificate(ch *tls.ClientHelloInfo) (*tls.Certificate, er
|
|||
return &s.conf.cert, nil
|
||||
}
|
||||
|
||||
// preparePlain prepares the plain-DNS configuration for the DNS proxy.
|
||||
// preparePlain assumes that prepareTLS has already been called.
|
||||
func (s *Server) preparePlain(proxyConf *proxy.Config) (err error) {
|
||||
if s.conf.ServePlainDNS {
|
||||
proxyConf.UDPListenAddr = s.conf.UDPListenAddrs
|
||||
proxyConf.TCPListenAddr = s.conf.TCPListenAddrs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
lenEncrypted := len(proxyConf.DNSCryptTCPListenAddr) +
|
||||
len(proxyConf.DNSCryptUDPListenAddr) +
|
||||
len(proxyConf.HTTPSListenAddr) +
|
||||
len(proxyConf.QUICListenAddr) +
|
||||
len(proxyConf.TLSListenAddr)
|
||||
if lenEncrypted == 0 {
|
||||
// TODO(a.garipov): Support full disabling of all DNS.
|
||||
return errors.Error("disabling plain dns requires at least one encrypted protocol")
|
||||
}
|
||||
|
||||
log.Info("dnsforward: warning: plain dns is disabled")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatedProtectionStatus updates protection state, if the protection was
|
||||
// disabled temporarily. Returns the updated state of protection.
|
||||
func (s *Server) UpdatedProtectionStatus() (enabled bool, disabledUntil *time.Time) {
|
||||
|
|
349
internal/dnsforward/configvalidator.go
Normal file
349
internal/dnsforward/configvalidator.go
Normal file
|
@ -0,0 +1,349 @@
|
|||
package dnsforward
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// upstreamConfigValidator parses the [*proxy.UpstreamConfig] and checks the
|
||||
// actual DNS availability of each upstream.
|
||||
type upstreamConfigValidator struct {
|
||||
// general is the general upstream configuration.
|
||||
general []*upstreamResult
|
||||
|
||||
// fallback is the fallback upstream configuration.
|
||||
fallback []*upstreamResult
|
||||
|
||||
// private is the private upstream configuration.
|
||||
private []*upstreamResult
|
||||
}
|
||||
|
||||
// upstreamResult is a result of validation of an [upstream.Upstream] within an
|
||||
// [proxy.UpstreamConfig].
|
||||
type upstreamResult struct {
|
||||
// server is the parsed upstream. It is nil when there was an error during
|
||||
// parsing.
|
||||
server upstream.Upstream
|
||||
|
||||
// err is the error either from parsing or from checking the upstream.
|
||||
err error
|
||||
|
||||
// original is the piece of configuration that have either been turned to an
|
||||
// upstream or caused an error.
|
||||
original string
|
||||
|
||||
// isSpecific is true if the upstream is domain-specific.
|
||||
isSpecific bool
|
||||
}
|
||||
|
||||
// compare compares two [upstreamResult]s. It returns 0 if they are equal, -1
|
||||
// if ur should be sorted before other, and 1 otherwise.
|
||||
//
|
||||
// TODO(e.burkov): Perhaps it makes sense to sort the results with errors near
|
||||
// the end.
|
||||
func (ur *upstreamResult) compare(other *upstreamResult) (res int) {
|
||||
return strings.Compare(ur.original, other.original)
|
||||
}
|
||||
|
||||
// newUpstreamConfigValidator parses the upstream configuration and returns a
|
||||
// validator for it. cv already contains the parsed upstreams along with errors
|
||||
// related.
|
||||
func newUpstreamConfigValidator(
|
||||
general []string,
|
||||
fallback []string,
|
||||
private []string,
|
||||
opts *upstream.Options,
|
||||
) (cv *upstreamConfigValidator) {
|
||||
cv = &upstreamConfigValidator{}
|
||||
|
||||
for _, line := range general {
|
||||
cv.general = cv.insertLineResults(cv.general, line, opts)
|
||||
}
|
||||
for _, line := range fallback {
|
||||
cv.fallback = cv.insertLineResults(cv.fallback, line, opts)
|
||||
}
|
||||
for _, line := range private {
|
||||
cv.private = cv.insertLineResults(cv.private, line, opts)
|
||||
}
|
||||
|
||||
return cv
|
||||
}
|
||||
|
||||
// insertLineResults parses line and inserts the result into s. It can insert
|
||||
// multiple results as well as none.
|
||||
func (cv *upstreamConfigValidator) insertLineResults(
|
||||
s []*upstreamResult,
|
||||
line string,
|
||||
opts *upstream.Options,
|
||||
) (result []*upstreamResult) {
|
||||
upstreams, isSpecific, err := splitUpstreamLine(line)
|
||||
if err != nil {
|
||||
return cv.insert(s, &upstreamResult{
|
||||
err: err,
|
||||
original: line,
|
||||
})
|
||||
}
|
||||
|
||||
for _, upstreamAddr := range upstreams {
|
||||
var res *upstreamResult
|
||||
if upstreamAddr != "#" {
|
||||
res = cv.parseUpstream(upstreamAddr, opts)
|
||||
} else if !isSpecific {
|
||||
res = &upstreamResult{
|
||||
err: errNotDomainSpecific,
|
||||
original: upstreamAddr,
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
res.isSpecific = isSpecific
|
||||
s = cv.insert(s, res)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// insert inserts r into slice in a sorted order, except duplicates. slice must
|
||||
// not be nil.
|
||||
func (cv *upstreamConfigValidator) insert(
|
||||
s []*upstreamResult,
|
||||
r *upstreamResult,
|
||||
) (result []*upstreamResult) {
|
||||
i, has := slices.BinarySearchFunc(s, r, (*upstreamResult).compare)
|
||||
if has {
|
||||
log.Debug("dnsforward: duplicate configuration %q", r.original)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
return slices.Insert(s, i, r)
|
||||
}
|
||||
|
||||
// parseUpstream parses addr and returns the result of parsing. It returns nil
|
||||
// if the specified server points at the default upstream server which is
|
||||
// validated separately.
|
||||
func (cv *upstreamConfigValidator) parseUpstream(
|
||||
addr string,
|
||||
opts *upstream.Options,
|
||||
) (r *upstreamResult) {
|
||||
// Check if the upstream has a valid protocol prefix.
|
||||
//
|
||||
// TODO(e.burkov): Validate the domain name.
|
||||
if proto, _, ok := strings.Cut(addr, "://"); ok {
|
||||
if !slices.Contains(protocols, proto) {
|
||||
return &upstreamResult{
|
||||
err: fmt.Errorf("bad protocol %q", proto),
|
||||
original: addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ups, err := upstream.AddressToUpstream(addr, opts)
|
||||
|
||||
return &upstreamResult{
|
||||
server: ups,
|
||||
err: err,
|
||||
original: addr,
|
||||
}
|
||||
}
|
||||
|
||||
// check tries to exchange with each successfully parsed upstream and enriches
|
||||
// the results with the healthcheck errors. It should not be called after the
|
||||
// [upsConfValidator.close] method, since it makes no sense to check the closed
|
||||
// upstreams.
|
||||
func (cv *upstreamConfigValidator) check() {
|
||||
const (
|
||||
// testTLD is the special-use fully-qualified domain name for testing
|
||||
// the DNS server reachability.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc6761#section-6.2.
|
||||
testTLD = "test."
|
||||
|
||||
// inAddrARPATLD is the special-use fully-qualified domain name for PTR
|
||||
// IP address resolution.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1035#section-3.5.
|
||||
inAddrARPATLD = "in-addr.arpa."
|
||||
)
|
||||
|
||||
commonChecker := &healthchecker{
|
||||
hostname: testTLD,
|
||||
qtype: dns.TypeA,
|
||||
ansEmpty: true,
|
||||
}
|
||||
|
||||
arpaChecker := &healthchecker{
|
||||
hostname: inAddrARPATLD,
|
||||
qtype: dns.TypePTR,
|
||||
ansEmpty: false,
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(cv.general) + len(cv.fallback) + len(cv.private))
|
||||
|
||||
for _, res := range cv.general {
|
||||
go cv.checkSrv(res, wg, commonChecker)
|
||||
}
|
||||
for _, res := range cv.fallback {
|
||||
go cv.checkSrv(res, wg, commonChecker)
|
||||
}
|
||||
for _, res := range cv.private {
|
||||
go cv.checkSrv(res, wg, arpaChecker)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// checkSrv runs hc on the server from res, if any, and stores any occurred
|
||||
// error in res. wg is always marked done in the end. It used to be called in
|
||||
// a separate goroutine.
|
||||
func (cv *upstreamConfigValidator) checkSrv(
|
||||
res *upstreamResult,
|
||||
wg *sync.WaitGroup,
|
||||
hc *healthchecker,
|
||||
) {
|
||||
defer wg.Done()
|
||||
|
||||
if res.server == nil {
|
||||
return
|
||||
}
|
||||
|
||||
res.err = hc.check(res.server)
|
||||
if res.err != nil && res.isSpecific {
|
||||
res.err = domainSpecificTestError{Err: res.err}
|
||||
}
|
||||
}
|
||||
|
||||
// close closes all the upstreams that were successfully parsed. It enriches
|
||||
// the results with deferred closing errors.
|
||||
func (cv *upstreamConfigValidator) close() {
|
||||
for _, slice := range [][]*upstreamResult{cv.general, cv.fallback, cv.private} {
|
||||
for _, r := range slice {
|
||||
if r.server != nil {
|
||||
r.err = errors.WithDeferred(r.err, r.server.Close())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// status returns all the data collected during parsing, healthcheck, and
|
||||
// closing of the upstreams. The returned map is keyed by the original upstream
|
||||
// configuration piece and contains the corresponding error or "OK" if there was
|
||||
// no error.
|
||||
func (cv *upstreamConfigValidator) status() (results map[string]string) {
|
||||
result := map[string]string{}
|
||||
|
||||
for _, res := range cv.general {
|
||||
resultToStatus("general", res, result)
|
||||
}
|
||||
for _, res := range cv.fallback {
|
||||
resultToStatus("fallback", res, result)
|
||||
}
|
||||
for _, res := range cv.private {
|
||||
resultToStatus("private", res, result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// resultToStatus puts "OK" or an error message from res into resMap. section
|
||||
// is the name of the upstream configuration section, i.e. "general",
|
||||
// "fallback", or "private", and only used for logging.
|
||||
//
|
||||
// TODO(e.burkov): Currently, the HTTP handler expects that all the results are
|
||||
// put together in a single map, which may lead to collisions, see AG-27539.
|
||||
// Improve the results compilation.
|
||||
func resultToStatus(section string, res *upstreamResult, resMap map[string]string) {
|
||||
val := "OK"
|
||||
if res.err != nil {
|
||||
val = res.err.Error()
|
||||
}
|
||||
|
||||
prevVal := resMap[res.original]
|
||||
switch prevVal {
|
||||
case "":
|
||||
resMap[res.original] = val
|
||||
case val:
|
||||
log.Debug("dnsforward: duplicating %s config line %q", section, res.original)
|
||||
default:
|
||||
log.Debug(
|
||||
"dnsforward: warning: %s config line %q (%v) had different result %v",
|
||||
section,
|
||||
val,
|
||||
res.original,
|
||||
prevVal,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// domainSpecificTestError is a wrapper for errors returned by checkDNS to mark
|
||||
// the tested upstream domain-specific and therefore consider its errors
|
||||
// non-critical.
|
||||
//
|
||||
// TODO(a.garipov): Some common mechanism of distinguishing between errors and
|
||||
// warnings (non-critical errors) is desired.
|
||||
type domainSpecificTestError struct {
|
||||
// Err is the actual error occurred during healthcheck test.
|
||||
Err error
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ error = domainSpecificTestError{}
|
||||
|
||||
// Error implements the [error] interface for domainSpecificTestError.
|
||||
func (err domainSpecificTestError) Error() (msg string) {
|
||||
return fmt.Sprintf("WARNING: %s", err.Err)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ errors.Wrapper = domainSpecificTestError{}
|
||||
|
||||
// Unwrap implements the [errors.Wrapper] interface for domainSpecificTestError.
|
||||
func (err domainSpecificTestError) Unwrap() (wrapped error) {
|
||||
return err.Err
|
||||
}
|
||||
|
||||
// healthchecker checks the upstream's status by exchanging with it.
|
||||
type healthchecker struct {
|
||||
// hostname is the name of the host to put into healthcheck DNS request.
|
||||
hostname string
|
||||
|
||||
// qtype is the type of DNS request to use for healthcheck.
|
||||
qtype uint16
|
||||
|
||||
// ansEmpty defines if the answer section within the response is expected to
|
||||
// be empty.
|
||||
ansEmpty bool
|
||||
}
|
||||
|
||||
// check exchanges with u and validates the response.
|
||||
func (h *healthchecker) check(u upstream.Upstream) (err error) {
|
||||
req := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: h.hostname,
|
||||
Qtype: h.qtype,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
reply, err := u.Exchange(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't communicate with upstream: %w", err)
|
||||
} else if h.ansEmpty && len(reply.Answer) > 0 {
|
||||
return errWrongResponse
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -4,6 +4,8 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
|
@ -11,10 +13,12 @@ import (
|
|||
)
|
||||
|
||||
// DialContext is an [aghnet.DialContextFunc] that uses s to resolve hostnames.
|
||||
// addr should be a valid host:port address, where host could be a domain name
|
||||
// or an IP address.
|
||||
func (s *Server) DialContext(ctx context.Context, network, addr string) (conn net.Conn, err error) {
|
||||
log.Debug("dnsforward: dialing %q for network %q", addr, network)
|
||||
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -28,21 +32,24 @@ func (s *Server) DialContext(ctx context.Context, network, addr string) (conn ne
|
|||
return dialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
|
||||
addrs, err := s.Resolve(host)
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving %q: %w", host, err)
|
||||
return nil, fmt.Errorf("invalid port %s: %w", portStr, err)
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: resolving %q: %v", host, addrs)
|
||||
|
||||
if len(addrs) == 0 {
|
||||
ips, err := s.Resolve(ctx, network, host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving %q: %w", host, err)
|
||||
} else if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("no addresses for host %q", host)
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: resolved %q: %v", host, ips)
|
||||
|
||||
var dialErrs []error
|
||||
for _, a := range addrs {
|
||||
addr = net.JoinHostPort(a.String(), port)
|
||||
conn, err = dialer.DialContext(ctx, network, addr)
|
||||
for _, ip := range ips {
|
||||
addrPort := netip.AddrPortFrom(ip, uint16(port))
|
||||
conn, err = dialer.DialContext(ctx, network, addrPort.String())
|
||||
if err != nil {
|
||||
dialErrs = append(dialErrs, err)
|
||||
|
||||
|
|
|
@ -292,6 +292,7 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) {
|
|||
Config: Config{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, localUps)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
package dnsforward
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
@ -34,6 +36,11 @@ import (
|
|||
// DefaultTimeout is the default upstream timeout
|
||||
const DefaultTimeout = 10 * time.Second
|
||||
|
||||
// defaultLocalTimeout is the default timeout for resolving addresses from
|
||||
// locally-served networks. It is assumed that local resolvers should work much
|
||||
// faster than ordinary upstreams.
|
||||
const defaultLocalTimeout = 1 * time.Second
|
||||
|
||||
// defaultClientIDCacheCount is the default count of items in the LRU ClientID
|
||||
// cache. The assumption here is that there won't be more than this many
|
||||
// requests between the BeforeRequestHandler stage and the actual processing.
|
||||
|
@ -108,7 +115,7 @@ type Server struct {
|
|||
// stats is the statistics collector for client's DNS usage data.
|
||||
stats stats.Interface
|
||||
|
||||
// access drops unallowed clients.
|
||||
// access drops disallowed clients.
|
||||
access *accessManager
|
||||
|
||||
// localDomainSuffix is the suffix used to detect internal hosts. It
|
||||
|
@ -135,8 +142,21 @@ type Server struct {
|
|||
// PTR resolving.
|
||||
sysResolvers SystemResolvers
|
||||
|
||||
// recDetector is a cache for recursive requests. It is used to detect
|
||||
// and prevent recursive requests only for private upstreams.
|
||||
// etcHosts contains the data from the system's hosts files.
|
||||
etcHosts upstream.Resolver
|
||||
|
||||
// bootstrap is the resolver for upstreams' hostnames.
|
||||
bootstrap upstream.Resolver
|
||||
|
||||
// bootResolvers are the resolvers that should be used for
|
||||
// bootstrapping along with [etcHosts].
|
||||
//
|
||||
// TODO(e.burkov): Use [proxy.UpstreamConfig] when it will implement the
|
||||
// [upstream.Resolver] interface.
|
||||
bootResolvers []*upstream.UpstreamResolver
|
||||
|
||||
// recDetector is a cache for recursive requests. It is used to detect and
|
||||
// prevent recursive requests only for private upstreams.
|
||||
//
|
||||
// See https://github.com/adguardTeam/adGuardHome/issues/3185#issuecomment-851048135.
|
||||
recDetector *recursionDetector
|
||||
|
@ -153,8 +173,8 @@ type Server struct {
|
|||
// during the BeforeRequestHandler stage.
|
||||
clientIDCache cache.Cache
|
||||
|
||||
// DNS proxy instance for internal usage
|
||||
// We don't Start() it and so no listen port is required.
|
||||
// internalProxy resolves internal requests from the application itself. It
|
||||
// isn't started and so no listen ports are required.
|
||||
internalProxy *proxy.Proxy
|
||||
|
||||
// isRunning is true if the DNS server is running.
|
||||
|
@ -185,6 +205,7 @@ type DNSCreateParams struct {
|
|||
DHCPServer DHCP
|
||||
PrivateNets netutil.SubnetSet
|
||||
Anonymizer *aghnet.IPMut
|
||||
EtcHosts *aghnet.HostsContainer
|
||||
LocalDomain string
|
||||
}
|
||||
|
||||
|
@ -217,8 +238,10 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
|
|||
if p.Anonymizer == nil {
|
||||
p.Anonymizer = aghnet.NewIPMut(nil)
|
||||
}
|
||||
|
||||
s = &Server{
|
||||
dnsFilter: p.DNSFilter,
|
||||
dhcpServer: p.DHCPServer,
|
||||
stats: p.Stats,
|
||||
queryLog: p.QueryLog,
|
||||
privateNets: p.PrivateNets,
|
||||
|
@ -230,6 +253,12 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
|
|||
MaxCount: defaultClientIDCacheCount,
|
||||
}),
|
||||
anonymizer: p.Anonymizer,
|
||||
conf: ServerConfig{
|
||||
ServePlainDNS: true,
|
||||
},
|
||||
}
|
||||
if p.EtcHosts != nil {
|
||||
s.etcHosts = p.EtcHosts
|
||||
}
|
||||
|
||||
s.sysResolvers, err = sysresolv.NewSystemResolvers(nil, defaultPlainDNSPort)
|
||||
|
@ -237,8 +266,6 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
|
|||
return nil, fmt.Errorf("initializing system resolvers: %w", err)
|
||||
}
|
||||
|
||||
s.dhcpServer = p.DHCPServer
|
||||
|
||||
if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" {
|
||||
// Use plain DNS on MIPS, encryption is too slow
|
||||
defaultDNS = defaultBootstrap
|
||||
|
@ -274,7 +301,7 @@ func (s *Server) WriteDiskConfig(c *Config) {
|
|||
|
||||
sc := s.conf.Config
|
||||
*c = sc
|
||||
c.RatelimitWhitelist = stringutil.CloneSlice(sc.RatelimitWhitelist)
|
||||
c.RatelimitWhitelist = slices.Clone(sc.RatelimitWhitelist)
|
||||
c.BootstrapDNS = stringutil.CloneSlice(sc.BootstrapDNS)
|
||||
c.FallbackDNS = stringutil.CloneSlice(sc.FallbackDNS)
|
||||
c.AllowedClients = stringutil.CloneSlice(sc.AllowedClients)
|
||||
|
@ -305,15 +332,14 @@ func (s *Server) AddrProcConfig() (c *client.DefaultAddrProcConfig) {
|
|||
}
|
||||
}
|
||||
|
||||
// Resolve - get IP addresses by host name from an upstream server.
|
||||
// No request/response filtering is performed.
|
||||
// Query log and Stats are not updated.
|
||||
// This method may be called before Start().
|
||||
func (s *Server) Resolve(host string) ([]net.IPAddr, error) {
|
||||
// Resolve gets IP addresses by host name from an upstream server. No
|
||||
// request/response filtering is performed. Query log and Stats are not
|
||||
// updated. This method may be called before [Server.Start].
|
||||
func (s *Server) Resolve(ctx context.Context, net, host string) (addr []netip.Addr, err error) {
|
||||
s.serverLock.RLock()
|
||||
defer s.serverLock.RUnlock()
|
||||
|
||||
return s.internalProxy.LookupIPAddr(host)
|
||||
return s.internalProxy.LookupNetIP(ctx, net, host)
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -421,7 +447,7 @@ func hostFromPTR(resp *dns.Msg) (host string, ttl time.Duration, err error) {
|
|||
return "", 0, ErrRDNSNoData
|
||||
}
|
||||
|
||||
// Start starts the DNS server.
|
||||
// Start starts the DNS server. It must only be called after [Server.Prepare].
|
||||
func (s *Server) Start() error {
|
||||
s.serverLock.Lock()
|
||||
defer s.serverLock.Unlock()
|
||||
|
@ -429,48 +455,42 @@ func (s *Server) Start() error {
|
|||
return s.startLocked()
|
||||
}
|
||||
|
||||
// startLocked starts the DNS server without locking. For internal use only.
|
||||
// startLocked starts the DNS server without locking. s.serverLock is expected
|
||||
// to be locked.
|
||||
func (s *Server) startLocked() error {
|
||||
err := s.dnsProxy.Start()
|
||||
if err == nil {
|
||||
s.isRunning = true
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// defaultLocalTimeout is the default timeout for resolving addresses from
|
||||
// locally-served networks. It is assumed that local resolvers should work much
|
||||
// faster than ordinary upstreams.
|
||||
const defaultLocalTimeout = 1 * time.Second
|
||||
|
||||
// setupLocalResolvers initializes the resolvers for local addresses. For
|
||||
// internal use only.
|
||||
func (s *Server) setupLocalResolvers() (err error) {
|
||||
matcher, err := s.conf.ourAddrsMatcher()
|
||||
// setupLocalResolvers initializes the resolvers for local addresses. It
|
||||
// assumes s.serverLock is locked or the Server not running.
|
||||
func (s *Server) setupLocalResolvers(boot upstream.Resolver) (err error) {
|
||||
set, err := s.conf.ourAddrsSet()
|
||||
if err != nil {
|
||||
// Don't wrap the error because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
bootstraps := s.conf.BootstrapDNS
|
||||
resolvers := s.conf.LocalPTRResolvers
|
||||
filterConfig := false
|
||||
|
||||
if len(resolvers) == 0 {
|
||||
sysResolvers := slices.DeleteFunc(s.sysResolvers.Addrs(), matcher)
|
||||
confNeedsFiltering := len(resolvers) > 0
|
||||
if confNeedsFiltering {
|
||||
resolvers = stringutil.FilterOut(resolvers, IsCommentOrEmpty)
|
||||
} else {
|
||||
sysResolvers := slices.DeleteFunc(slices.Clone(s.sysResolvers.Addrs()), set.Has)
|
||||
resolvers = make([]string, 0, len(sysResolvers))
|
||||
for _, r := range sysResolvers {
|
||||
resolvers = append(resolvers, r.String())
|
||||
}
|
||||
} else {
|
||||
resolvers = stringutil.FilterOut(resolvers, IsCommentOrEmpty)
|
||||
filterConfig = true
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", resolvers)
|
||||
|
||||
uc, err := s.prepareUpstreamConfig(resolvers, nil, &upstream.Options{
|
||||
Bootstrap: bootstraps,
|
||||
Bootstrap: boot,
|
||||
Timeout: defaultLocalTimeout,
|
||||
// TODO(e.burkov): Should we verify server's certificates?
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
|
@ -479,8 +499,9 @@ func (s *Server) setupLocalResolvers() (err error) {
|
|||
return fmt.Errorf("preparing private upstreams: %w", err)
|
||||
}
|
||||
|
||||
if filterConfig {
|
||||
if err = matcher.filterOut(uc); err != nil {
|
||||
if confNeedsFiltering {
|
||||
err = filterOutAddrs(uc, set)
|
||||
if err != nil {
|
||||
return fmt.Errorf("filtering private upstreams: %w", err)
|
||||
}
|
||||
}
|
||||
|
@ -491,6 +512,7 @@ func (s *Server) setupLocalResolvers() (err error) {
|
|||
},
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Should we also consider the DNS64 usage?
|
||||
if s.conf.UsePrivateRDNS &&
|
||||
// Only set the upstream config if there are any upstreams. It's safe
|
||||
// to put nil into [proxy.Config.PrivateRDNSUpstreamConfig].
|
||||
|
@ -517,31 +539,19 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
|||
|
||||
s.initDefaultSettings()
|
||||
|
||||
err = s.prepareIpsetListSettings()
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return fmt.Errorf("preparing ipset settings: %w", err)
|
||||
}
|
||||
|
||||
err = s.prepareUpstreamSettings()
|
||||
boot, err := s.prepareInternalDNS()
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
var proxyConfig proxy.Config
|
||||
proxyConfig, err = s.createProxyConfig()
|
||||
proxyConfig, err := s.newProxyConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing proxy: %w", err)
|
||||
}
|
||||
|
||||
s.setupDNS64()
|
||||
|
||||
err = s.prepareInternalProxy()
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing internal proxy: %w", err)
|
||||
}
|
||||
|
||||
s.access, err = newAccessCtx(
|
||||
s.conf.AllowedClients,
|
||||
s.conf.DisallowedClients,
|
||||
|
@ -554,9 +564,9 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
|||
// Set the proxy here because [setupLocalResolvers] sets its values.
|
||||
//
|
||||
// TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy.
|
||||
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
|
||||
s.dnsProxy = &proxy.Proxy{Config: *proxyConfig}
|
||||
|
||||
err = s.setupLocalResolvers()
|
||||
err = s.setupLocalResolvers(boot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up resolvers: %w", err)
|
||||
}
|
||||
|
@ -575,6 +585,38 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// prepareInternalDNS initializes the internal state of s before initializing
|
||||
// the primary DNS proxy instance. It assumes s.serverLock is locked or the
|
||||
// Server not running.
|
||||
func (s *Server) prepareInternalDNS() (boot upstream.Resolver, err error) {
|
||||
err = s.prepareIpsetListSettings()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("preparing ipset settings: %w", err)
|
||||
}
|
||||
|
||||
s.bootstrap, s.bootResolvers, err = s.createBootstrap(s.conf.BootstrapDNS, &upstream.Options{
|
||||
Timeout: DefaultTimeout,
|
||||
HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams),
|
||||
})
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.prepareUpstreamSettings(s.bootstrap)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return s.bootstrap, err
|
||||
}
|
||||
|
||||
err = s.prepareInternalProxy()
|
||||
if err != nil {
|
||||
return s.bootstrap, fmt.Errorf("preparing internal proxy: %w", err)
|
||||
}
|
||||
|
||||
return s.bootstrap, nil
|
||||
}
|
||||
|
||||
// setupFallbackDNS initializes the fallback DNS servers.
|
||||
func (s *Server) setupFallbackDNS() (err error) {
|
||||
fallbacks := s.conf.FallbackDNS
|
||||
|
@ -598,7 +640,8 @@ func (s *Server) setupFallbackDNS() (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// setupAddrProc initializes the address processor. For internal use only.
|
||||
// setupAddrProc initializes the address processor. It assumes s.serverLock is
|
||||
// locked or the Server not running.
|
||||
func (s *Server) setupAddrProc() {
|
||||
// TODO(a.garipov): This is a crutch for tests; remove.
|
||||
if s.conf.AddrProcConf == nil {
|
||||
|
@ -687,7 +730,8 @@ func (s *Server) Stop() error {
|
|||
return s.stopLocked()
|
||||
}
|
||||
|
||||
// stopLocked stops the DNS server without locking. For internal use only.
|
||||
// stopLocked stops the DNS server without locking. s.serverLock is expected to
|
||||
// be locked.
|
||||
func (s *Server) stopLocked() (err error) {
|
||||
// TODO(e.burkov, a.garipov): Return critical errors, not just log them.
|
||||
// This will require filtering all the non-critical errors in
|
||||
|
@ -700,18 +744,11 @@ func (s *Server) stopLocked() (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
if upsConf := s.internalProxy.UpstreamConfig; upsConf != nil {
|
||||
err = upsConf.Close()
|
||||
if err != nil {
|
||||
log.Error("dnsforward: closing internal resolvers: %s", err)
|
||||
}
|
||||
}
|
||||
logCloserErr(s.internalProxy.UpstreamConfig, "dnsforward: closing internal resolvers: %s")
|
||||
logCloserErr(s.localResolvers.UpstreamConfig, "dnsforward: closing local resolvers: %s")
|
||||
|
||||
if upsConf := s.localResolvers.UpstreamConfig; upsConf != nil {
|
||||
err = upsConf.Close()
|
||||
if err != nil {
|
||||
log.Error("dnsforward: closing local resolvers: %s", err)
|
||||
}
|
||||
for _, b := range s.bootResolvers {
|
||||
logCloserErr(b, "dnsforward: closing bootstrap %s: %s", b.Address())
|
||||
}
|
||||
|
||||
s.isRunning = false
|
||||
|
@ -719,6 +756,18 @@ func (s *Server) stopLocked() (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// logCloserErr logs the error returned by c, if any.
|
||||
func logCloserErr(c io.Closer, format string, args ...any) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := c.Close()
|
||||
if err != nil {
|
||||
log.Error(format, append(args, err)...)
|
||||
}
|
||||
}
|
||||
|
||||
// IsRunning returns true if the DNS server is running.
|
||||
func (s *Server) IsRunning() bool {
|
||||
s.serverLock.RLock()
|
||||
|
|
|
@ -54,13 +54,10 @@ const (
|
|||
testMessagesCount = 10
|
||||
)
|
||||
|
||||
// testClientAddr is the common net.Addr for tests.
|
||||
// testClientAddrPort is the common net.Addr for tests.
|
||||
//
|
||||
// TODO(a.garipov): Use more.
|
||||
var testClientAddr net.Addr = &net.TCPAddr{
|
||||
IP: net.IP{1, 2, 3, 4},
|
||||
Port: 12345,
|
||||
}
|
||||
var testClientAddrPort = netip.MustParseAddrPort("1.2.3.4:12345")
|
||||
|
||||
func startDeferStop(t *testing.T, s *Server) {
|
||||
t.Helper()
|
||||
|
@ -182,6 +179,7 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte)
|
|||
Config: Config{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
|
||||
tlsConf.CertificateChainData, tlsConf.PrivateKeyData = certPem, keyPem
|
||||
|
@ -309,6 +307,7 @@ func TestServer(t *testing.T) {
|
|||
Config: Config{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
startDeferStop(t, s)
|
||||
|
@ -347,6 +346,7 @@ func TestServer_timeout(t *testing.T) {
|
|||
Config: Config{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
|
||||
s, err := NewServer(DNSCreateParams{DNSFilter: createTestDNSFilter(t)})
|
||||
|
@ -381,6 +381,7 @@ func TestServer_Prepare_fallbacks(t *testing.T) {
|
|||
},
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
|
||||
s, err := NewServer(DNSCreateParams{})
|
||||
|
@ -402,6 +403,7 @@ func TestServerWithProtectionDisabled(t *testing.T) {
|
|||
Config: Config{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
startDeferStop(t, s)
|
||||
|
@ -479,6 +481,7 @@ func TestServerRace(t *testing.T) {
|
|||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||
},
|
||||
ConfigModified: func() {},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
|
@ -532,6 +535,7 @@ func TestSafeSearch(t *testing.T) {
|
|||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
startDeferStop(t, s)
|
||||
|
@ -594,6 +598,7 @@ func TestInvalidRequest(t *testing.T) {
|
|||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
startDeferStop(t, s)
|
||||
|
||||
|
@ -622,6 +627,7 @@ func TestBlockedRequest(t *testing.T) {
|
|||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
ProtectionEnabled: true,
|
||||
|
@ -644,45 +650,71 @@ func TestBlockedRequest(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestServerCustomClientUpstream(t *testing.T) {
|
||||
const defaultCacheSize = 1024 * 1024
|
||||
|
||||
var upsCalledCounter uint32
|
||||
|
||||
forwardConf := ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
Config: Config{
|
||||
CacheSize: defaultCacheSize,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, forwardConf, nil)
|
||||
s.conf.GetCustomUpstreamByClient = func(_ string) (conf *proxy.UpstreamConfig, err error) {
|
||||
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
aghtest.MatchedResponse(req, dns.TypeA, "host", "192.168.0.1"),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
})
|
||||
|
||||
return &proxy.UpstreamConfig{
|
||||
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
atomic.AddUint32(&upsCalledCounter, 1)
|
||||
|
||||
return aghalg.Coalesce(
|
||||
aghtest.MatchedResponse(req, dns.TypeA, "host", "192.168.0.1"),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
})
|
||||
|
||||
customUpsConf := proxy.NewCustomUpstreamConfig(
|
||||
&proxy.UpstreamConfig{
|
||||
Upstreams: []upstream.Upstream{ups},
|
||||
}, nil
|
||||
},
|
||||
true,
|
||||
defaultCacheSize,
|
||||
forwardConf.EDNSClientSubnet.Enabled,
|
||||
)
|
||||
|
||||
s.conf.ClientsContainer = &aghtest.ClientsContainer{
|
||||
OnUpstreamConfigByID: func(
|
||||
_ string,
|
||||
_ upstream.Resolver,
|
||||
) (conf *proxy.CustomUpstreamConfig, err error) {
|
||||
return customUpsConf, nil
|
||||
},
|
||||
}
|
||||
|
||||
startDeferStop(t, s)
|
||||
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP).String()
|
||||
|
||||
// Send test request.
|
||||
req := createTestMessage("host.")
|
||||
|
||||
reply, err := dns.Exchange(req, addr.String())
|
||||
reply, err := dns.Exchange(req, addr)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, reply.Answer)
|
||||
require.Len(t, reply.Answer, 1)
|
||||
|
||||
assert.Equal(t, dns.RcodeSuccess, reply.Rcode)
|
||||
require.NotEmpty(t, reply.Answer)
|
||||
|
||||
require.Len(t, reply.Answer, 1)
|
||||
assert.Equal(t, net.IP{192, 168, 0, 1}, reply.Answer[0].(*dns.A).A)
|
||||
assert.Equal(t, uint32(1), atomic.LoadUint32(&upsCalledCounter))
|
||||
|
||||
_, err = dns.Exchange(req, addr)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint32(1), atomic.LoadUint32(&upsCalledCounter))
|
||||
}
|
||||
|
||||
// testCNAMEs is a map of names and CNAMEs necessary for the TestUpstream work.
|
||||
|
@ -708,6 +740,7 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) {
|
|||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
testUpstm := &aghtest.Upstream{
|
||||
CName: testCNAMEs,
|
||||
|
@ -740,6 +773,7 @@ func TestBlockCNAME(t *testing.T) {
|
|||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
ProtectionEnabled: true,
|
||||
|
@ -814,6 +848,7 @@ func TestClientRulesForCNAMEMatching(t *testing.T) {
|
|||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
|
@ -858,6 +893,7 @@ func TestNullBlockedRequest(t *testing.T) {
|
|||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
ProtectionEnabled: true,
|
||||
|
@ -923,6 +959,7 @@ func TestBlockedCustomIP(t *testing.T) {
|
|||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
|
||||
// Invalid BlockingIPv4.
|
||||
|
@ -974,6 +1011,7 @@ func TestBlockedByHosts(t *testing.T) {
|
|||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
|
@ -1024,6 +1062,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
|
|||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
startDeferStop(t, s)
|
||||
|
@ -1082,6 +1121,7 @@ func TestRewrite(t *testing.T) {
|
|||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}))
|
||||
|
||||
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
|
|
|
@ -40,6 +40,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
|||
Config: Config{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
|
||||
makeQ := func(qtype rules.RRType) (req *dns.Msg) {
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
|
@ -28,8 +27,7 @@ func (s *Server) beforeRequestHandler(
|
|||
return false, fmt.Errorf("getting clientid: %w", err)
|
||||
}
|
||||
|
||||
addrPort := netutil.NetAddrToAddrPort(pctx.Addr)
|
||||
blocked, _ := s.IsBlockedClient(addrPort.Addr(), clientID)
|
||||
blocked, _ := s.IsBlockedClient(pctx.Addr.Addr(), clientID)
|
||||
if blocked {
|
||||
return s.preBlockedResponse(pctx)
|
||||
}
|
||||
|
@ -60,8 +58,7 @@ func (s *Server) clientRequestFilteringSettings(dctx *dnsContext) (setts *filter
|
|||
setts = s.dnsFilter.Settings()
|
||||
setts.ProtectionEnabled = dctx.protectionEnabled
|
||||
if s.conf.FilterHandler != nil {
|
||||
addrPort := netutil.NetAddrToAddrPort(dctx.proxyCtx.Addr)
|
||||
s.conf.FilterHandler(addrPort.Addr(), dctx.clientID, setts)
|
||||
s.conf.FilterHandler(dctx.proxyCtx.Addr.Addr(), dctx.clientID, setts)
|
||||
}
|
||||
|
||||
return setts
|
||||
|
|
|
@ -35,6 +35,7 @@ func TestHandleDNSRequest_handleDNSRequest(t *testing.T) {
|
|||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
filters := []filtering.Filter{{
|
||||
ID: 0, Data: []byte(rules),
|
||||
|
@ -186,7 +187,7 @@ func TestHandleDNSRequest_handleDNSRequest(t *testing.T) {
|
|||
dctx := &proxy.DNSContext{
|
||||
Proto: proxy.ProtoUDP,
|
||||
Req: tc.req,
|
||||
Addr: &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 1},
|
||||
Addr: testClientAddrPort,
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
@ -325,7 +326,7 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
|
|||
Proto: proxy.ProtoUDP,
|
||||
Req: tc.req,
|
||||
Res: resp,
|
||||
Addr: &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 1},
|
||||
Addr: testClientAddrPort,
|
||||
}
|
||||
|
||||
dctx := &dnsContext{
|
||||
|
|
|
@ -6,20 +6,15 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
|
@ -45,8 +40,19 @@ type jsonDNSConfig struct {
|
|||
// ProtectionEnabled defines if protection is enabled.
|
||||
ProtectionEnabled *bool `json:"protection_enabled"`
|
||||
|
||||
// RateLimit is the number of requests per second allowed per client.
|
||||
RateLimit *uint32 `json:"ratelimit"`
|
||||
// Ratelimit is the number of requests per second allowed per client.
|
||||
Ratelimit *uint32 `json:"ratelimit"`
|
||||
|
||||
// RatelimitSubnetLenIPv4 is a subnet length for IPv4 addresses used for
|
||||
// rate limiting requests.
|
||||
RatelimitSubnetLenIPv4 *int `json:"ratelimit_subnet_len_ipv4"`
|
||||
|
||||
// RatelimitSubnetLenIPv6 is a subnet length for IPv6 addresses used for
|
||||
// rate limiting requests.
|
||||
RatelimitSubnetLenIPv6 *int `json:"ratelimit_subnet_len_ipv6"`
|
||||
|
||||
// RatelimitWhitelist is a list of IP addresses excluded from rate limiting.
|
||||
RatelimitWhitelist *[]netip.Addr `json:"ratelimit_whitelist"`
|
||||
|
||||
// BlockingMode defines the way blocked responses are constructed.
|
||||
BlockingMode *filtering.BlockingMode `json:"blocking_mode"`
|
||||
|
@ -121,6 +127,9 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
|
|||
blockingMode, blockingIPv4, blockingIPv6 := s.dnsFilter.BlockingMode()
|
||||
blockedResponseTTL := s.dnsFilter.BlockedResponseTTL()
|
||||
ratelimit := s.conf.Ratelimit
|
||||
ratelimitSubnetLenIPv4 := s.conf.RatelimitSubnetLenIPv4
|
||||
ratelimitSubnetLenIPv6 := s.conf.RatelimitSubnetLenIPv6
|
||||
ratelimitWhitelist := append([]netip.Addr{}, s.conf.RatelimitWhitelist...)
|
||||
|
||||
customIP := s.conf.EDNSClientSubnet.CustomIP
|
||||
enableEDNSClientSubnet := s.conf.EDNSClientSubnet.Enabled
|
||||
|
@ -157,7 +166,10 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
|
|||
BlockingMode: &blockingMode,
|
||||
BlockingIPv4: blockingIPv4,
|
||||
BlockingIPv6: blockingIPv6,
|
||||
RateLimit: &ratelimit,
|
||||
Ratelimit: &ratelimit,
|
||||
RatelimitSubnetLenIPv4: &ratelimitSubnetLenIPv4,
|
||||
RatelimitSubnetLenIPv6: &ratelimitSubnetLenIPv6,
|
||||
RatelimitWhitelist: &ratelimitWhitelist,
|
||||
EDNSCSCustomIP: customIP,
|
||||
EDNSCSEnabled: &enableEDNSClientSubnet,
|
||||
EDNSCSUseCustom: &useCustom,
|
||||
|
@ -180,13 +192,13 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
|
|||
// defaultLocalPTRUpstreams returns the list of default local PTR resolvers
|
||||
// filtered of AdGuard Home's own DNS server addresses. It may appear empty.
|
||||
func (s *Server) defaultLocalPTRUpstreams() (ups []string, err error) {
|
||||
matcher, err := s.conf.ourAddrsMatcher()
|
||||
matcher, err := s.conf.ourAddrsSet()
|
||||
if err != nil {
|
||||
// Don't wrap the error because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sysResolvers := slices.DeleteFunc(s.sysResolvers.Addrs(), matcher)
|
||||
sysResolvers := slices.DeleteFunc(s.sysResolvers.Addrs(), matcher.Has)
|
||||
ups = make([]string, 0, len(sysResolvers))
|
||||
for _, r := range sysResolvers {
|
||||
ups = append(ups, r.String())
|
||||
|
@ -201,6 +213,7 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
|
|||
aghhttp.WriteJSONResponseOK(w, r, resp)
|
||||
}
|
||||
|
||||
// checkBlockingMode returns an error if blocking mode is invalid.
|
||||
func (req *jsonDNSConfig) checkBlockingMode() (err error) {
|
||||
if req.BlockingMode == nil {
|
||||
return nil
|
||||
|
@ -209,12 +222,21 @@ func (req *jsonDNSConfig) checkBlockingMode() (err error) {
|
|||
return validateBlockingMode(*req.BlockingMode, req.BlockingIPv4, req.BlockingIPv6)
|
||||
}
|
||||
|
||||
func (req *jsonDNSConfig) checkUpstreamsMode() bool {
|
||||
valid := []string{"", "fastest_addr", "parallel"}
|
||||
// checkUpstreamsMode returns an error if the upstream mode is invalid.
|
||||
func (req *jsonDNSConfig) checkUpstreamsMode() (err error) {
|
||||
if req.UpstreamMode == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return req.UpstreamMode == nil || stringutil.InSlice(valid, *req.UpstreamMode)
|
||||
mode := *req.UpstreamMode
|
||||
if ok := slices.Contains([]string{"", "fastest_addr", "parallel"}, mode); !ok {
|
||||
return fmt.Errorf("upstream_mode: incorrect value %q", mode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkBootstrap returns an error if any bootstrap address is invalid.
|
||||
func (req *jsonDNSConfig) checkBootstrap() (err error) {
|
||||
if req.Bootstraps == nil {
|
||||
return nil
|
||||
|
@ -229,6 +251,7 @@ func (req *jsonDNSConfig) checkBootstrap() (err error) {
|
|||
}
|
||||
|
||||
if _, err = upstream.NewUpstreamResolver(b, nil); err != nil {
|
||||
// Don't wrap the error because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -244,67 +267,136 @@ func (req *jsonDNSConfig) checkFallbacks() (err error) {
|
|||
|
||||
err = ValidateUpstreams(*req.Fallbacks)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating fallback servers: %w", err)
|
||||
return fmt.Errorf("fallback servers: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validate returns an error if any field of req is invalid.
|
||||
//
|
||||
// TODO(s.chzhen): Parse, don't validate.
|
||||
func (req *jsonDNSConfig) validate(privateNets netutil.SubnetSet) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "validating dns config: %w") }()
|
||||
|
||||
err = req.validateUpstreamDNSServers(privateNets)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
err = req.checkRatelimitSubnetMaskLen()
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
err = req.checkBlockingMode()
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
err = req.checkUpstreamsMode()
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
err = req.checkCacheTTL()
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateUpstreamDNSServers returns an error if any field of req is invalid.
|
||||
func (req *jsonDNSConfig) validateUpstreamDNSServers(privateNets netutil.SubnetSet) (err error) {
|
||||
if req.Upstreams != nil {
|
||||
err = ValidateUpstreams(*req.Upstreams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating upstream servers: %w", err)
|
||||
return fmt.Errorf("upstream servers: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if req.LocalPTRUpstreams != nil {
|
||||
err = ValidateUpstreamsPrivate(*req.LocalPTRUpstreams, privateNets)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating private upstream servers: %w", err)
|
||||
return fmt.Errorf("private upstream servers: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = req.checkBootstrap()
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
err = req.checkFallbacks()
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
err = req.checkBlockingMode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case !req.checkUpstreamsMode():
|
||||
return errors.Error("upstream_mode: incorrect value")
|
||||
case !req.checkCacheTTL():
|
||||
return errors.Error("cache_ttl_min must be less or equal than cache_ttl_max")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (req *jsonDNSConfig) checkCacheTTL() bool {
|
||||
// checkCacheTTL returns an error if the configuration of the cache TTL is
|
||||
// invalid.
|
||||
func (req *jsonDNSConfig) checkCacheTTL() (err error) {
|
||||
if req.CacheMinTTL == nil && req.CacheMaxTTL == nil {
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
var min, max uint32
|
||||
var minTTL, maxTTL uint32
|
||||
if req.CacheMinTTL != nil {
|
||||
min = *req.CacheMinTTL
|
||||
minTTL = *req.CacheMinTTL
|
||||
}
|
||||
if req.CacheMaxTTL != nil {
|
||||
max = *req.CacheMaxTTL
|
||||
maxTTL = *req.CacheMaxTTL
|
||||
}
|
||||
|
||||
return min <= max
|
||||
if minTTL <= maxTTL {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Error("cache_ttl_min must be less or equal than cache_ttl_max")
|
||||
}
|
||||
|
||||
// checkRatelimitSubnetMaskLen returns an error if the length of the subnet mask
|
||||
// for IPv4 or IPv6 addresses is invalid.
|
||||
func (req *jsonDNSConfig) checkRatelimitSubnetMaskLen() (err error) {
|
||||
err = checkInclusion(req.RatelimitSubnetLenIPv4, 0, netutil.IPv4BitLen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ratelimit_subnet_len_ipv4 is invalid: %w", err)
|
||||
}
|
||||
|
||||
err = checkInclusion(req.RatelimitSubnetLenIPv6, 0, netutil.IPv6BitLen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ratelimit_subnet_len_ipv6 is invalid: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkInclusion returns an error if a ptr is not nil and points to value,
|
||||
// that not in the inclusive range between minN and maxN.
|
||||
func checkInclusion(ptr *int, minN, maxN int) (err error) {
|
||||
if ptr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := *ptr
|
||||
switch {
|
||||
case n < minN:
|
||||
return fmt.Errorf("value %d less than min %d", n, minN)
|
||||
case n > maxN:
|
||||
return fmt.Errorf("value %d greater than max %d", n, maxN)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleSetConfig handles requests to the POST /control/dns_config endpoint.
|
||||
|
@ -401,6 +493,9 @@ func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) {
|
|||
setIfNotNil(&s.conf.CacheOptimistic, dc.CacheOptimistic),
|
||||
setIfNotNil(&s.conf.AddrProcConf.UseRDNS, dc.ResolveClients),
|
||||
setIfNotNil(&s.conf.UsePrivateRDNS, dc.UsePrivateRDNS),
|
||||
setIfNotNil(&s.conf.RatelimitSubnetLenIPv4, dc.RatelimitSubnetLenIPv4),
|
||||
setIfNotNil(&s.conf.RatelimitSubnetLenIPv6, dc.RatelimitSubnetLenIPv6),
|
||||
setIfNotNil(&s.conf.RatelimitWhitelist, dc.RatelimitWhitelist),
|
||||
} {
|
||||
shouldRestart = shouldRestart || hasSet
|
||||
if shouldRestart {
|
||||
|
@ -408,8 +503,8 @@ func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) {
|
|||
}
|
||||
}
|
||||
|
||||
if dc.RateLimit != nil && s.conf.Ratelimit != *dc.RateLimit {
|
||||
s.conf.Ratelimit = *dc.RateLimit
|
||||
if dc.Ratelimit != nil && s.conf.Ratelimit != *dc.Ratelimit {
|
||||
s.conf.Ratelimit = *dc.Ratelimit
|
||||
shouldRestart = true
|
||||
}
|
||||
|
||||
|
@ -424,374 +519,11 @@ type upstreamJSON struct {
|
|||
PrivateUpstreams []string `json:"private_upstream"`
|
||||
}
|
||||
|
||||
// IsCommentOrEmpty returns true if s starts with a "#" character or is empty.
|
||||
// This function is useful for filtering out non-upstream lines from upstream
|
||||
// configs.
|
||||
func IsCommentOrEmpty(s string) (ok bool) {
|
||||
return len(s) == 0 || s[0] == '#'
|
||||
}
|
||||
|
||||
// newUpstreamConfig validates upstreams and returns an appropriate upstream
|
||||
// configuration or nil if it can't be built.
|
||||
//
|
||||
// TODO(e.burkov): Perhaps proxy.ParseUpstreamsConfig should validate upstreams
|
||||
// slice already so that this function may be considered useless.
|
||||
func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err error) {
|
||||
// No need to validate comments and empty lines.
|
||||
upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty)
|
||||
if len(upstreams) == 0 {
|
||||
// Consider this case valid since it means the default server should be
|
||||
// used.
|
||||
return nil, nil
|
||||
// closeBoots closes all the provided bootstrap servers and logs errors if any.
|
||||
func closeBoots(boots []*upstream.UpstreamResolver) {
|
||||
for _, c := range boots {
|
||||
logCloserErr(c, "dnsforward: closing bootstrap %s: %s", c.Address())
|
||||
}
|
||||
|
||||
err = validateUpstreamConfig(upstreams)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf, err = proxy.ParseUpstreamsConfig(
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
Bootstrap: []string{},
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return nil, err
|
||||
} else if len(conf.Upstreams) == 0 {
|
||||
return nil, errors.Error("no default upstreams specified")
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// validateUpstreamConfig validates each upstream from the upstream
|
||||
// configuration and returns an error if any upstream is invalid.
|
||||
//
|
||||
// TODO(e.burkov): Move into aghnet or even into dnsproxy.
|
||||
func validateUpstreamConfig(conf []string) (err error) {
|
||||
for _, u := range conf {
|
||||
var ups []string
|
||||
var domains []string
|
||||
ups, domains, err = separateUpstream(u)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
for _, addr := range ups {
|
||||
_, err = validateUpstream(addr, domains)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating upstream %q: %w", addr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateUpstreams validates each upstream and returns an error if any
|
||||
// upstream is invalid or if there are no default upstreams specified.
|
||||
//
|
||||
// TODO(e.burkov): Move into aghnet or even into dnsproxy.
|
||||
func ValidateUpstreams(upstreams []string) (err error) {
|
||||
_, err = newUpstreamConfig(upstreams)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ValidateUpstreamsPrivate validates each upstream and returns an error if any
|
||||
// upstream is invalid or if there are no default upstreams specified. It also
|
||||
// checks each domain of domain-specific upstreams for being ARPA pointing to
|
||||
// a locally-served network. privateNets must not be nil.
|
||||
func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet) (err error) {
|
||||
conf, err := newUpstreamConfig(upstreams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating config: %w", err)
|
||||
}
|
||||
|
||||
if conf == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
keys := maps.Keys(conf.DomainReservedUpstreams)
|
||||
slices.Sort(keys)
|
||||
|
||||
var errs []error
|
||||
for _, domain := range keys {
|
||||
var subnet netip.Prefix
|
||||
subnet, err = extractARPASubnet(domain)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !privateNets.Contains(subnet.Addr().AsSlice()) {
|
||||
errs = append(
|
||||
errs,
|
||||
fmt.Errorf("arpa domain %q should point to a locally-served network", domain),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Annotate(errors.Join(errs...), "checking domain-specific upstreams: %w")
|
||||
}
|
||||
|
||||
var protocols = []string{
|
||||
"h3://",
|
||||
"https://",
|
||||
"quic://",
|
||||
"sdns://",
|
||||
"tcp://",
|
||||
"tls://",
|
||||
"udp://",
|
||||
}
|
||||
|
||||
// validateUpstream returns an error if u alongside with domains is not a valid
|
||||
// upstream configuration. useDefault is true if the upstream is
|
||||
// domain-specific and is configured to point at the default upstream server
|
||||
// which is validated separately. The upstream is considered domain-specific
|
||||
// only if domains is at least not nil.
|
||||
func validateUpstream(u string, domains []string) (useDefault bool, err error) {
|
||||
// The special server address '#' means that default server must be used.
|
||||
if useDefault = u == "#" && domains != nil; useDefault {
|
||||
return useDefault, nil
|
||||
}
|
||||
|
||||
// Check if the upstream has a valid protocol prefix.
|
||||
//
|
||||
// TODO(e.burkov): Validate the domain name.
|
||||
for _, proto := range protocols {
|
||||
if strings.HasPrefix(u, proto) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if proto, _, ok := strings.Cut(u, "://"); ok {
|
||||
return false, fmt.Errorf("bad protocol %q", proto)
|
||||
}
|
||||
|
||||
// Check if upstream is either an IP or IP with port.
|
||||
if _, err = netip.ParseAddr(u); err == nil {
|
||||
return false, nil
|
||||
} else if _, err = netip.ParseAddrPort(u); err == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// separateUpstream returns the upstreams and the specified domains. domains
|
||||
// is nil when the upstream is not domains-specific. Otherwise it may also be
|
||||
// empty.
|
||||
func separateUpstream(upstreamStr string) (upstreams, domains []string, err error) {
|
||||
if !strings.HasPrefix(upstreamStr, "[/") {
|
||||
return []string{upstreamStr}, nil, nil
|
||||
}
|
||||
|
||||
defer func() { err = errors.Annotate(err, "bad upstream for domain %q: %w", upstreamStr) }()
|
||||
|
||||
parts := strings.Split(upstreamStr[2:], "/]")
|
||||
switch len(parts) {
|
||||
case 2:
|
||||
// Go on.
|
||||
case 1:
|
||||
return nil, nil, errors.Error("missing separator")
|
||||
default:
|
||||
return nil, nil, errors.Error("duplicated separator")
|
||||
}
|
||||
|
||||
for i, host := range strings.Split(parts[0], "/") {
|
||||
if host == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
err = netutil.ValidateDomainName(strings.TrimPrefix(host, "*."))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("domain at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
domains = append(domains, host)
|
||||
}
|
||||
|
||||
return strings.Fields(parts[1]), domains, nil
|
||||
}
|
||||
|
||||
// healthCheckFunc is a signature of function to check if upstream exchanges
|
||||
// properly.
|
||||
type healthCheckFunc func(u upstream.Upstream) (err error)
|
||||
|
||||
// checkDNSUpstreamExc checks if the DNS upstream exchanges correctly.
|
||||
func checkDNSUpstreamExc(u upstream.Upstream) (err error) {
|
||||
// testTLD is the special-use fully-qualified domain name for testing the
|
||||
// DNS server reachability.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc6761#section-6.2.
|
||||
const testTLD = "test."
|
||||
|
||||
req := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: testTLD,
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
var reply *dns.Msg
|
||||
reply, err = u.Exchange(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't communicate with upstream: %w", err)
|
||||
} else if len(reply.Answer) != 0 {
|
||||
return errors.Error("wrong response")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkPrivateUpstreamExc checks if the upstream for resolving private
|
||||
// addresses exchanges correctly.
|
||||
//
|
||||
// TODO(e.burkov): Think about testing the ip6.arpa. as well.
|
||||
func checkPrivateUpstreamExc(u upstream.Upstream) (err error) {
|
||||
// inAddrArpaTLD is the special-use fully-qualified domain name for PTR IP
|
||||
// address resolution.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1035#section-3.5.
|
||||
const inAddrArpaTLD = "in-addr.arpa."
|
||||
|
||||
req := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: inAddrArpaTLD,
|
||||
Qtype: dns.TypePTR,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
if _, err = u.Exchange(req); err != nil {
|
||||
return fmt.Errorf("couldn't communicate with upstream: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// domainSpecificTestError is a wrapper for errors returned by checkDNS to mark
|
||||
// the tested upstream domain-specific and therefore consider its errors
|
||||
// non-critical.
|
||||
//
|
||||
// TODO(a.garipov): Some common mechanism of distinguishing between errors and
|
||||
// warnings (non-critical errors) is desired.
|
||||
type domainSpecificTestError struct {
|
||||
error
|
||||
}
|
||||
|
||||
// Error implements the [error] interface for domainSpecificTestError.
|
||||
func (err domainSpecificTestError) Error() (msg string) {
|
||||
return fmt.Sprintf("WARNING: %s", err.error)
|
||||
}
|
||||
|
||||
// checkDNS parses line, creates DNS upstreams using opts, and checks if the
|
||||
// upstreams are exchanging correctly. It saves the result into a sync.Map
|
||||
// where key is an upstream address and value is "OK", if the upstream
|
||||
// exchanges correctly, or text of the error. It is intended to be used as a
|
||||
// goroutine.
|
||||
//
|
||||
// TODO(s.chzhen): Separate to a different structure/file.
|
||||
func (s *Server) checkDNS(
|
||||
line string,
|
||||
opts *upstream.Options,
|
||||
check healthCheckFunc,
|
||||
wg *sync.WaitGroup,
|
||||
m *sync.Map,
|
||||
) {
|
||||
defer wg.Done()
|
||||
defer log.OnPanic("dnsforward: checking upstreams")
|
||||
|
||||
upstreams, domains, err := separateUpstream(line)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("wrong upstream format: %w", err)
|
||||
m.Store(line, err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
specific := len(domains) > 0
|
||||
|
||||
for _, upstreamAddr := range upstreams {
|
||||
var useDefault bool
|
||||
useDefault, err = validateUpstream(upstreamAddr, domains)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("wrong upstream format: %w", err)
|
||||
m.Store(upstreamAddr, err.Error())
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if useDefault {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: checking if upstream %q works", upstreamAddr)
|
||||
|
||||
err = s.checkUpstreamAddr(upstreamAddr, specific, opts, check)
|
||||
if err != nil {
|
||||
m.Store(upstreamAddr, err.Error())
|
||||
} else {
|
||||
m.Store(upstreamAddr, "OK")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkUpstreamAddr creates the DNS upstream using opts and information from
|
||||
// [s.dnsFilter.EtcHosts]. Checks if the DNS upstream exchanges correctly. It
|
||||
// returns an error if addr is not valid DNS upstream address or the upstream
|
||||
// is not exchanging correctly.
|
||||
func (s *Server) checkUpstreamAddr(
|
||||
addr string,
|
||||
specific bool,
|
||||
opts *upstream.Options,
|
||||
check healthCheckFunc,
|
||||
) (err error) {
|
||||
defer func() {
|
||||
if err != nil && specific {
|
||||
err = domainSpecificTestError{error: err}
|
||||
}
|
||||
}()
|
||||
|
||||
opts = &upstream.Options{
|
||||
Bootstrap: opts.Bootstrap,
|
||||
Timeout: opts.Timeout,
|
||||
PreferIPv6: opts.PreferIPv6,
|
||||
}
|
||||
|
||||
// dnsFilter can be nil during application update.
|
||||
if s.dnsFilter != nil {
|
||||
recs := s.dnsFilter.EtcHostsRecords(extractUpstreamHost(addr))
|
||||
for _, rec := range recs {
|
||||
opts.ServerIPAddrs = append(opts.ServerIPAddrs, rec.Addr.AsSlice())
|
||||
}
|
||||
sortNetIPAddrs(opts.ServerIPAddrs, opts.PreferIPv6)
|
||||
}
|
||||
|
||||
u, err := upstream.AddressToUpstream(addr, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating upstream for %q: %w", addr, err)
|
||||
}
|
||||
|
||||
defer func() { err = errors.WithDeferred(err, u.Close()) }()
|
||||
|
||||
return check(u)
|
||||
}
|
||||
|
||||
// handleTestUpstreamDNS handles requests to the POST /control/test_upstream_dns
|
||||
|
@ -808,46 +540,27 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
|||
req.Upstreams = stringutil.FilterOut(req.Upstreams, IsCommentOrEmpty)
|
||||
req.FallbackDNS = stringutil.FilterOut(req.FallbackDNS, IsCommentOrEmpty)
|
||||
req.PrivateUpstreams = stringutil.FilterOut(req.PrivateUpstreams, IsCommentOrEmpty)
|
||||
req.BootstrapDNS = stringutil.FilterOut(req.BootstrapDNS, IsCommentOrEmpty)
|
||||
|
||||
opts := &upstream.Options{
|
||||
Bootstrap: req.BootstrapDNS,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
}
|
||||
if len(opts.Bootstrap) == 0 {
|
||||
opts.Bootstrap = defaultBootstrap
|
||||
|
||||
var boots []*upstream.UpstreamResolver
|
||||
opts.Bootstrap, boots, err = s.createBootstrap(req.BootstrapDNS, opts)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to parse bootstrap servers: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
defer closeBoots(boots)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
m := &sync.Map{}
|
||||
cv := newUpstreamConfigValidator(req.Upstreams, req.FallbackDNS, req.PrivateUpstreams, opts)
|
||||
cv.check()
|
||||
cv.close()
|
||||
|
||||
wg.Add(len(req.Upstreams) + len(req.FallbackDNS) + len(req.PrivateUpstreams))
|
||||
|
||||
for _, ups := range req.Upstreams {
|
||||
go s.checkDNS(ups, opts, checkDNSUpstreamExc, wg, m)
|
||||
}
|
||||
for _, ups := range req.FallbackDNS {
|
||||
go s.checkDNS(ups, opts, checkDNSUpstreamExc, wg, m)
|
||||
}
|
||||
for _, ups := range req.PrivateUpstreams {
|
||||
go s.checkDNS(ups, opts, checkPrivateUpstreamExc, wg, m)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
result := map[string]string{}
|
||||
m.Range(func(k, v any) bool {
|
||||
// TODO(e.burkov): The upstreams used for both common and private
|
||||
// resolving should be reported separately.
|
||||
ups := k.(string)
|
||||
status := v.(string)
|
||||
|
||||
result[ups] = status
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
aghhttp.WriteJSONResponseOK(w, r, result)
|
||||
aghhttp.WriteJSONResponseOK(w, r, cv.status())
|
||||
}
|
||||
|
||||
// handleCacheClear is the handler for the POST /control/cache_clear HTTP API.
|
||||
|
|
|
@ -72,11 +72,14 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
|
|||
UDPListenAddrs: []*net.UDPAddr{},
|
||||
TCPListenAddrs: []*net.TCPAddr{},
|
||||
Config: Config{
|
||||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||
FallbackDNS: []string{"9.9.9.10"},
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||
FallbackDNS: []string{"9.9.9.10"},
|
||||
RatelimitSubnetLenIPv4: 24,
|
||||
RatelimitSubnetLenIPv6: 56,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ConfigModified: func() {},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
s.sysResolvers = &emptySysResolvers{}
|
||||
|
@ -150,10 +153,13 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
|||
UDPListenAddrs: []*net.UDPAddr{},
|
||||
TCPListenAddrs: []*net.TCPAddr{},
|
||||
Config: Config{
|
||||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||
RatelimitSubnetLenIPv4: 24,
|
||||
RatelimitSubnetLenIPv6: 56,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ConfigModified: func() {},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
s.sysResolvers = &emptySysResolvers{}
|
||||
|
@ -179,11 +185,18 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
|||
name: "blocking_mode_good",
|
||||
wantSet: "",
|
||||
}, {
|
||||
name: "blocking_mode_bad",
|
||||
wantSet: "blocking_ipv4 must be valid ipv4 on custom_ip blocking_mode",
|
||||
name: "blocking_mode_bad",
|
||||
wantSet: "validating dns config: " +
|
||||
"blocking_ipv4 must be valid ipv4 on custom_ip blocking_mode",
|
||||
}, {
|
||||
name: "ratelimit",
|
||||
wantSet: "",
|
||||
}, {
|
||||
name: "ratelimit_subnet_len",
|
||||
wantSet: "",
|
||||
}, {
|
||||
name: "ratelimit_whitelist_not_ip",
|
||||
wantSet: `decoding request: ParseAddr("not.ip"): unexpected character (at "not.ip")`,
|
||||
}, {
|
||||
name: "edns_cs_enabled",
|
||||
wantSet: "",
|
||||
|
@ -206,24 +219,26 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
|||
name: "upstream_mode_fastest_addr",
|
||||
wantSet: "",
|
||||
}, {
|
||||
name: "upstream_dns_bad",
|
||||
wantSet: `validating upstream servers: validating upstream "!!!": not an ip:port`,
|
||||
name: "upstream_dns_bad",
|
||||
wantSet: `validating dns config: ` +
|
||||
`upstream servers: validating upstream "!!!": not an ip:port`,
|
||||
}, {
|
||||
name: "bootstraps_bad",
|
||||
wantSet: `checking bootstrap a: invalid address: bootstrap a:53: ` +
|
||||
wantSet: `validating dns config: checking bootstrap a: invalid address: not a bootstrap: ` +
|
||||
`ParseAddr("a"): unable to parse IP`,
|
||||
}, {
|
||||
name: "cache_bad_ttl",
|
||||
wantSet: `cache_ttl_min must be less or equal than cache_ttl_max`,
|
||||
wantSet: `validating dns config: cache_ttl_min must be less or equal than cache_ttl_max`,
|
||||
}, {
|
||||
name: "upstream_mode_bad",
|
||||
wantSet: `upstream_mode: incorrect value`,
|
||||
wantSet: `validating dns config: upstream_mode: incorrect value "somethingelse"`,
|
||||
}, {
|
||||
name: "local_ptr_upstreams_good",
|
||||
wantSet: "",
|
||||
}, {
|
||||
name: "local_ptr_upstreams_bad",
|
||||
wantSet: `validating private upstream servers: checking domain-specific upstreams: ` +
|
||||
wantSet: `validating dns config: ` +
|
||||
`private upstream servers: checking domain-specific upstreams: ` +
|
||||
`bad arpa domain name "non.arpa.": not a reversed ip network`,
|
||||
}, {
|
||||
name: "local_ptr_upstreams_null",
|
||||
|
@ -347,7 +362,7 @@ func TestValidateUpstreams(t *testing.T) {
|
|||
set: []string{"123.3.7m"},
|
||||
}, {
|
||||
name: "invalid",
|
||||
wantErr: `bad upstream for domain "[/host.com]tls://dns.adguard.com": ` +
|
||||
wantErr: `splitting upstream line "[/host.com]tls://dns.adguard.com": ` +
|
||||
`missing separator`,
|
||||
set: []string{"[/host.com]tls://dns.adguard.com"},
|
||||
}, {
|
||||
|
@ -373,7 +388,7 @@ func TestValidateUpstreams(t *testing.T) {
|
|||
},
|
||||
}, {
|
||||
name: "bad_domain",
|
||||
wantErr: `bad upstream for domain "[/!/]8.8.8.8": domain at index 0: ` +
|
||||
wantErr: `splitting upstream line "[/!/]8.8.8.8": domain at index 0: ` +
|
||||
`bad domain name "!": bad top-level domain name label "!": ` +
|
||||
`bad top-level domain name label rune '!'`,
|
||||
set: []string{"[/!/]8.8.8.8"},
|
||||
|
@ -461,25 +476,15 @@ func newLocalUpstreamListener(t *testing.T, port uint16, handler dns.Handler) (r
|
|||
}
|
||||
|
||||
func TestServer_HandleTestUpstreamDNS(t *testing.T) {
|
||||
goodHandler := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
|
||||
hdlr := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
|
||||
err := w.WriteMsg(new(dns.Msg).SetReply(m))
|
||||
require.NoError(testutil.PanicT{}, err)
|
||||
})
|
||||
badHandler := dns.HandlerFunc(func(w dns.ResponseWriter, _ *dns.Msg) {
|
||||
err := w.WriteMsg(new(dns.Msg))
|
||||
require.NoError(testutil.PanicT{}, err)
|
||||
})
|
||||
|
||||
goodUps := (&url.URL{
|
||||
ups := (&url.URL{
|
||||
Scheme: "tcp",
|
||||
Host: newLocalUpstreamListener(t, 0, goodHandler).String(),
|
||||
Host: newLocalUpstreamListener(t, 0, hdlr).String(),
|
||||
}).String()
|
||||
badUps := (&url.URL{
|
||||
Scheme: "tcp",
|
||||
Host: newLocalUpstreamListener(t, 0, badHandler).String(),
|
||||
}).String()
|
||||
|
||||
goodAndBadUps := strings.Join([]string{goodUps, badUps}, " ")
|
||||
|
||||
const (
|
||||
upsTimeout = 100 * time.Millisecond
|
||||
|
@ -488,7 +493,7 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
|
|||
upstreamHost = "custom.localhost"
|
||||
)
|
||||
|
||||
hostsListener := newLocalUpstreamListener(t, 0, goodHandler)
|
||||
hostsListener := newLocalUpstreamListener(t, 0, hdlr)
|
||||
hostsUps := (&url.URL{
|
||||
Scheme: "tcp",
|
||||
Host: netutil.JoinHostPort(upstreamHost, hostsListener.Port()),
|
||||
|
@ -519,7 +524,9 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
|
|||
Config: Config{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
srv.etcHosts = hc
|
||||
startDeferStop(t, srv)
|
||||
|
||||
testCases := []struct {
|
||||
|
@ -527,43 +534,6 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
|
|||
wantResp map[string]any
|
||||
name string
|
||||
}{{
|
||||
body: map[string]any{
|
||||
"upstream_dns": []string{goodUps},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
goodUps: "OK",
|
||||
},
|
||||
name: "success",
|
||||
}, {
|
||||
body: map[string]any{
|
||||
"upstream_dns": []string{badUps},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
badUps: `couldn't communicate with upstream: exchanging with ` +
|
||||
badUps + ` over tcp: dns: id mismatch`,
|
||||
},
|
||||
name: "broken",
|
||||
}, {
|
||||
body: map[string]any{
|
||||
"upstream_dns": []string{goodUps, badUps},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
goodUps: "OK",
|
||||
badUps: `couldn't communicate with upstream: exchanging with ` +
|
||||
badUps + ` over tcp: dns: id mismatch`,
|
||||
},
|
||||
name: "both",
|
||||
}, {
|
||||
body: map[string]any{
|
||||
"upstream_dns": []string{"[/domain.example/]" + badUps},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
badUps: `WARNING: couldn't communicate ` +
|
||||
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
|
||||
`dns: id mismatch`,
|
||||
},
|
||||
name: "domain_specific_error",
|
||||
}, {
|
||||
body: map[string]any{
|
||||
"upstream_dns": []string{hostsUps},
|
||||
},
|
||||
|
@ -573,63 +543,12 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
|
|||
name: "etc_hosts",
|
||||
}, {
|
||||
body: map[string]any{
|
||||
"fallback_dns": []string{goodUps},
|
||||
"upstream_dns": []string{ups, "#this.is.comment"},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
goodUps: "OK",
|
||||
ups: "OK",
|
||||
},
|
||||
name: "fallback_success",
|
||||
}, {
|
||||
body: map[string]any{
|
||||
"fallback_dns": []string{badUps},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
badUps: `couldn't communicate with upstream: exchanging with ` +
|
||||
badUps + ` over tcp: dns: id mismatch`,
|
||||
},
|
||||
name: "fallback_broken",
|
||||
}, {
|
||||
body: map[string]any{
|
||||
"fallback_dns": []string{goodUps, "#this.is.comment"},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
goodUps: "OK",
|
||||
},
|
||||
name: "fallback_comment_mix",
|
||||
}, {
|
||||
body: map[string]any{
|
||||
"upstream_dns": []string{"[/domain.example/]" + goodUps + " " + badUps},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
goodUps: "OK",
|
||||
badUps: `WARNING: couldn't communicate ` +
|
||||
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
|
||||
`dns: id mismatch`,
|
||||
},
|
||||
name: "multiple_domain_specific_upstreams",
|
||||
}, {
|
||||
body: map[string]any{
|
||||
"upstream_dns": []string{"[/domain.example/]/]1.2.3.4"},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
"[/domain.example/]/]1.2.3.4": `wrong upstream format: ` +
|
||||
`bad upstream for domain "[/domain.example/]/]1.2.3.4": ` +
|
||||
`duplicated separator`,
|
||||
},
|
||||
name: "bad_specification",
|
||||
}, {
|
||||
body: map[string]any{
|
||||
"upstream_dns": []string{"[/domain.example/]" + goodAndBadUps},
|
||||
"fallback_dns": []string{"[/domain.example/]" + goodAndBadUps},
|
||||
"private_upstream": []string{"[/domain.example/]" + goodAndBadUps},
|
||||
},
|
||||
wantResp: map[string]any{
|
||||
goodUps: "OK",
|
||||
badUps: `WARNING: couldn't communicate ` +
|
||||
`with upstream: exchanging with ` + badUps + ` over tcp: ` +
|
||||
`dns: id mismatch`,
|
||||
},
|
||||
name: "all_different",
|
||||
name: "comment_mix",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
|
|
@ -91,7 +91,7 @@ func (s *Server) genForBlockingMode(req *dns.Msg, ips []netip.Addr) (resp *dns.M
|
|||
case filtering.BlockingModeREFUSED:
|
||||
return s.makeResponseREFUSED(req)
|
||||
default:
|
||||
log.Error("dns: invalid blocking mode %q", mode)
|
||||
log.Error("dnsforward: invalid blocking mode %q", mode)
|
||||
|
||||
return s.makeResponse(req)
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ func (s *Server) makeResponseCustomIP(
|
|||
default:
|
||||
// Generally shouldn't happen, since the types are checked in
|
||||
// genDNSFilterMessage.
|
||||
log.Error("dns: invalid msg type %s for custom IP blocking mode", dns.Type(qt))
|
||||
log.Error("dnsforward: invalid msg type %s for custom IP blocking mode", dns.Type(qt))
|
||||
|
||||
return s.makeResponse(req)
|
||||
}
|
||||
|
@ -207,15 +207,7 @@ func (s *Server) genResponseWithIPs(req *dns.Msg, ips []netip.Addr) (resp *dns.M
|
|||
var ans []dns.RR
|
||||
switch req.Question[0].Qtype {
|
||||
case dns.TypeA:
|
||||
for _, ip := range ips {
|
||||
if ip.Is4() {
|
||||
ans = append(ans, s.genAnswerA(req, ip))
|
||||
} else {
|
||||
ans = nil
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
ans = s.genAnswersWithIPv4s(req, ips)
|
||||
case dns.TypeAAAA:
|
||||
for _, ip := range ips {
|
||||
if ip.Is6() {
|
||||
|
@ -232,6 +224,23 @@ func (s *Server) genResponseWithIPs(req *dns.Msg, ips []netip.Addr) (resp *dns.M
|
|||
return resp
|
||||
}
|
||||
|
||||
// genAnswersWithIPv4s generates DNS A answers provided IPv4 addresses. If any
|
||||
// of the IPs isn't an IPv4 address, genAnswersWithIPv4s logs a warning and
|
||||
// returns nil,
|
||||
func (s *Server) genAnswersWithIPv4s(req *dns.Msg, ips []netip.Addr) (ans []dns.RR) {
|
||||
for _, ip := range ips {
|
||||
if !ip.Is4() {
|
||||
log.Info("dnsforward: warning: ip %s is not ipv4 address", ip)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
ans = append(ans, s.genAnswerA(req, ip))
|
||||
}
|
||||
|
||||
return ans
|
||||
}
|
||||
|
||||
// makeResponseNullIP creates a response with 0.0.0.0 for A requests, :: for
|
||||
// AAAA requests, and an empty response for other types.
|
||||
func (s *Server) makeResponseNullIP(req *dns.Msg) (resp *dns.Msg) {
|
||||
|
@ -253,7 +262,7 @@ func (s *Server) makeResponseNullIP(req *dns.Msg) (resp *dns.Msg) {
|
|||
|
||||
func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSContext) *dns.Msg {
|
||||
if newAddr == "" {
|
||||
log.Printf("block host is not specified.")
|
||||
log.Info("dnsforward: block host is not specified")
|
||||
|
||||
return s.genServerFailure(request)
|
||||
}
|
||||
|
@ -276,14 +285,14 @@ func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSCo
|
|||
|
||||
prx := s.proxy()
|
||||
if prx == nil {
|
||||
log.Debug("dns: %s", srvClosedErr)
|
||||
log.Debug("dnsforward: %s", srvClosedErr)
|
||||
|
||||
return s.genServerFailure(request)
|
||||
}
|
||||
|
||||
err = prx.Resolve(newContext)
|
||||
if err != nil {
|
||||
log.Printf("couldn't look up replacement host %q: %s", newAddr, err)
|
||||
log.Info("dnsforward: looking up replacement host %q: %s", newAddr, err)
|
||||
|
||||
return s.genServerFailure(request)
|
||||
}
|
||||
|
|
|
@ -191,7 +191,7 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
|
|||
defer log.Debug("dnsforward: finished processing initial")
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
s.processClientIP(pctx.Addr)
|
||||
s.processClientIP(pctx.Addr.Addr())
|
||||
|
||||
q := pctx.Req.Question[0]
|
||||
qt := q.Qtype
|
||||
|
@ -228,9 +228,8 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
|
|||
}
|
||||
|
||||
// processClientIP sends the client IP address to s.addrProc, if needed.
|
||||
func (s *Server) processClientIP(addr net.Addr) {
|
||||
clientIP := netutil.NetAddrToAddrPort(addr).Addr()
|
||||
if clientIP == (netip.Addr{}) {
|
||||
func (s *Server) processClientIP(addr netip.Addr) {
|
||||
if !addr.IsValid() {
|
||||
log.Info("dnsforward: warning: bad client addr %q", addr)
|
||||
|
||||
return
|
||||
|
@ -241,7 +240,7 @@ func (s *Server) processClientIP(addr net.Addr) {
|
|||
s.serverLock.RLock()
|
||||
defer s.serverLock.RUnlock()
|
||||
|
||||
s.addrProc.Process(clientIP)
|
||||
s.addrProc.Process(addr)
|
||||
}
|
||||
|
||||
// processDDRQuery responds to Discovery of Designated Resolvers (DDR) SVCB
|
||||
|
@ -351,12 +350,7 @@ func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
|
|||
|
||||
rc = resultCodeSuccess
|
||||
|
||||
var ip net.IP
|
||||
if ip, _ = netutil.IPAndPortFromAddr(dctx.proxyCtx.Addr); ip == nil {
|
||||
return rc
|
||||
}
|
||||
|
||||
dctx.isLocalClient = s.privateNets.Contains(ip)
|
||||
dctx.isLocalClient = s.privateNets.Contains(dctx.proxyCtx.Addr.Addr().AsSlice())
|
||||
|
||||
return rc
|
||||
}
|
||||
|
@ -831,14 +825,13 @@ func (s *Server) dhcpHostFromRequest(q *dns.Question) (reqHost string) {
|
|||
|
||||
// setCustomUpstream sets custom upstream settings in pctx, if necessary.
|
||||
func (s *Server) setCustomUpstream(pctx *proxy.DNSContext, clientID string) {
|
||||
customUpsByClient := s.conf.GetCustomUpstreamByClient
|
||||
if pctx.Addr == nil || customUpsByClient == nil {
|
||||
if !pctx.Addr.IsValid() || s.conf.ClientsContainer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Use the ClientID first, since it has a higher priority.
|
||||
id := stringutil.Coalesce(clientID, ipStringFromAddr(pctx.Addr))
|
||||
upsConf, err := customUpsByClient(id)
|
||||
id := stringutil.Coalesce(clientID, pctx.Addr.Addr().String())
|
||||
upsConf, err := s.conf.ClientsContainer.UpstreamConfigByID(id, s.bootstrap)
|
||||
if err != nil {
|
||||
log.Error("dnsforward: getting custom upstreams for client %s: %s", id, err)
|
||||
|
||||
|
@ -847,9 +840,9 @@ func (s *Server) setCustomUpstream(pctx *proxy.DNSContext, clientID string) {
|
|||
|
||||
if upsConf != nil {
|
||||
log.Debug("dnsforward: using custom upstreams for client %s", id)
|
||||
}
|
||||
|
||||
pctx.CustomUpstreamConfig = upsConf
|
||||
pctx.CustomUpstreamConfig = upsConf
|
||||
}
|
||||
}
|
||||
|
||||
// Apply filtering logic after we have received response from upstream servers
|
||||
|
|
|
@ -81,6 +81,7 @@ func TestServer_ProcessInitial(t *testing.T) {
|
|||
AAAADisabled: tc.aaaaDisabled,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
|
@ -96,14 +97,14 @@ func TestServer_ProcessInitial(t *testing.T) {
|
|||
dctx := &dnsContext{
|
||||
proxyCtx: &proxy.DNSContext{
|
||||
Req: createTestMessageWithType(tc.target, tc.qType),
|
||||
Addr: testClientAddr,
|
||||
Addr: testClientAddrPort,
|
||||
RequestID: 1234,
|
||||
},
|
||||
}
|
||||
|
||||
gotRC := s.processInitial(dctx)
|
||||
assert.Equal(t, tc.wantRC, gotRC)
|
||||
assert.Equal(t, netutil.NetAddrToAddrPort(testClientAddr).Addr(), gotAddr)
|
||||
assert.Equal(t, testClientAddrPort.Addr(), gotAddr)
|
||||
|
||||
if tc.wantRCode > 0 {
|
||||
gotResp := dctx.proxyCtx.Res
|
||||
|
@ -180,6 +181,7 @@ func TestServer_ProcessFilteringAfterResponse(t *testing.T) {
|
|||
AAAADisabled: tc.aaaaDisabled,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
|
@ -199,7 +201,7 @@ func TestServer_ProcessFilteringAfterResponse(t *testing.T) {
|
|||
Proto: proxy.ProtoUDP,
|
||||
Req: tc.req,
|
||||
Res: resp,
|
||||
Addr: testClientAddr,
|
||||
Addr: testClientAddrPort,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -369,6 +371,7 @@ func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled b
|
|||
TLSConfig: TLSConfig{
|
||||
ServerName: ddrTestDomainName,
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -394,33 +397,27 @@ func TestServer_ProcessDetermineLocal(t *testing.T) {
|
|||
}
|
||||
|
||||
testCases := []struct {
|
||||
want assert.BoolAssertionFunc
|
||||
name string
|
||||
cliIP net.IP
|
||||
want assert.BoolAssertionFunc
|
||||
name string
|
||||
cliAddr netip.AddrPort
|
||||
}{{
|
||||
want: assert.True,
|
||||
name: "local",
|
||||
cliIP: net.IP{192, 168, 0, 1},
|
||||
want: assert.True,
|
||||
name: "local",
|
||||
cliAddr: netip.MustParseAddrPort("192.168.0.1:1"),
|
||||
}, {
|
||||
want: assert.False,
|
||||
name: "external",
|
||||
cliIP: net.IP{250, 249, 0, 1},
|
||||
want: assert.False,
|
||||
name: "external",
|
||||
cliAddr: netip.MustParseAddrPort("250.249.0.1:1"),
|
||||
}, {
|
||||
want: assert.False,
|
||||
name: "invalid",
|
||||
cliIP: net.IP{1, 2, 3, 4, 5},
|
||||
}, {
|
||||
want: assert.False,
|
||||
name: "nil",
|
||||
cliIP: nil,
|
||||
want: assert.False,
|
||||
name: "invalid",
|
||||
cliAddr: netip.AddrPort{},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
proxyCtx := &proxy.DNSContext{
|
||||
Addr: &net.TCPAddr{
|
||||
IP: tc.cliIP,
|
||||
},
|
||||
Addr: tc.cliAddr,
|
||||
}
|
||||
dctx := &dnsContext{
|
||||
proxyCtx: proxyCtx,
|
||||
|
@ -699,6 +696,7 @@ func TestServer_ProcessRestrictLocal(t *testing.T) {
|
|||
Config: Config{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, ups)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ups}
|
||||
startDeferStop(t, s)
|
||||
|
@ -707,31 +705,31 @@ func TestServer_ProcessRestrictLocal(t *testing.T) {
|
|||
name string
|
||||
want string
|
||||
question net.IP
|
||||
cliIP net.IP
|
||||
cliAddr netip.AddrPort
|
||||
wantLen int
|
||||
}{{
|
||||
name: "from_local_to_external",
|
||||
want: "host1.example.net.",
|
||||
question: net.IP{254, 253, 252, 251},
|
||||
cliIP: net.IP{192, 168, 10, 10},
|
||||
cliAddr: netip.MustParseAddrPort("192.168.10.10:1"),
|
||||
wantLen: 1,
|
||||
}, {
|
||||
name: "from_external_for_local",
|
||||
want: "",
|
||||
question: net.IP{192, 168, 1, 1},
|
||||
cliIP: net.IP{254, 253, 252, 251},
|
||||
cliAddr: netip.MustParseAddrPort("254.253.252.251:1"),
|
||||
wantLen: 0,
|
||||
}, {
|
||||
name: "from_local_for_local",
|
||||
want: "some.local-client.",
|
||||
question: net.IP{192, 168, 1, 1},
|
||||
cliIP: net.IP{192, 168, 1, 2},
|
||||
cliAddr: netip.MustParseAddrPort("192.168.1.2:1"),
|
||||
wantLen: 1,
|
||||
}, {
|
||||
name: "from_external_for_external",
|
||||
want: "host1.example.net.",
|
||||
question: net.IP{254, 253, 252, 251},
|
||||
cliIP: net.IP{254, 253, 252, 255},
|
||||
cliAddr: netip.MustParseAddrPort("254.253.252.255:1"),
|
||||
wantLen: 1,
|
||||
}}
|
||||
|
||||
|
@ -743,9 +741,7 @@ func TestServer_ProcessRestrictLocal(t *testing.T) {
|
|||
pctx := &proxy.DNSContext{
|
||||
Proto: proxy.ProtoTCP,
|
||||
Req: req,
|
||||
Addr: &net.TCPAddr{
|
||||
IP: tc.cliIP,
|
||||
},
|
||||
Addr: tc.cliAddr,
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
@ -776,6 +772,7 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
|
|||
Config: Config{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
},
|
||||
aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
|
@ -789,7 +786,7 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
|
|||
var dnsCtx *dnsContext
|
||||
setup := func(use bool) {
|
||||
proxyCtx = &proxy.DNSContext{
|
||||
Addr: testClientAddr,
|
||||
Addr: testClientAddrPort,
|
||||
Req: createTestMessageWithType(reqAddr, dns.TypePTR),
|
||||
}
|
||||
dnsCtx = &dnsContext{
|
||||
|
|
|
@ -10,9 +10,7 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Write Stats data and logs
|
||||
|
@ -25,13 +23,12 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
|
|||
host := aghnet.NormalizeDomain(q.Name)
|
||||
processingTime := time.Since(dctx.startTime)
|
||||
|
||||
ip, _ := netutil.IPAndPortFromAddr(pctx.Addr)
|
||||
ip = slices.Clone(ip)
|
||||
ip := pctx.Addr.Addr().AsSlice()
|
||||
s.anonymizer.Load()(ip)
|
||||
|
||||
log.Debug("dnsforward: client ip for stats and querylog: %s", ip)
|
||||
|
||||
ipStr := ip.String()
|
||||
ipStr := pctx.Addr.Addr().String()
|
||||
ids := []string{ipStr, dctx.clientID}
|
||||
qt, cl := q.Qtype, q.Qclass
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package dnsforward
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -65,7 +65,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
|
|||
name string
|
||||
domain string
|
||||
proto proxy.Proto
|
||||
addr net.Addr
|
||||
addr netip.AddrPort
|
||||
clientID string
|
||||
wantLogProto querylog.ClientProto
|
||||
wantStatClient string
|
||||
|
@ -76,7 +76,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
|
|||
name: "success_udp",
|
||||
domain: domain,
|
||||
proto: proxy.ProtoUDP,
|
||||
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
|
||||
addr: testClientAddrPort,
|
||||
clientID: "",
|
||||
wantLogProto: "",
|
||||
wantStatClient: "1.2.3.4",
|
||||
|
@ -87,7 +87,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
|
|||
name: "success_tls_clientid",
|
||||
domain: domain,
|
||||
proto: proxy.ProtoTLS,
|
||||
addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
|
||||
addr: testClientAddrPort,
|
||||
clientID: "cli42",
|
||||
wantLogProto: querylog.ClientProtoDoT,
|
||||
wantStatClient: "cli42",
|
||||
|
@ -98,7 +98,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
|
|||
name: "success_tls",
|
||||
domain: domain,
|
||||
proto: proxy.ProtoTLS,
|
||||
addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
|
||||
addr: testClientAddrPort,
|
||||
clientID: "",
|
||||
wantLogProto: querylog.ClientProtoDoT,
|
||||
wantStatClient: "1.2.3.4",
|
||||
|
@ -109,7 +109,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
|
|||
name: "success_quic",
|
||||
domain: domain,
|
||||
proto: proxy.ProtoQUIC,
|
||||
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
|
||||
addr: testClientAddrPort,
|
||||
clientID: "",
|
||||
wantLogProto: querylog.ClientProtoDoQ,
|
||||
wantStatClient: "1.2.3.4",
|
||||
|
@ -120,7 +120,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
|
|||
name: "success_https",
|
||||
domain: domain,
|
||||
proto: proxy.ProtoHTTPS,
|
||||
addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
|
||||
addr: testClientAddrPort,
|
||||
clientID: "",
|
||||
wantLogProto: querylog.ClientProtoDoH,
|
||||
wantStatClient: "1.2.3.4",
|
||||
|
@ -131,7 +131,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
|
|||
name: "success_dnscrypt",
|
||||
domain: domain,
|
||||
proto: proxy.ProtoDNSCrypt,
|
||||
addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
|
||||
addr: testClientAddrPort,
|
||||
clientID: "",
|
||||
wantLogProto: querylog.ClientProtoDNSCrypt,
|
||||
wantStatClient: "1.2.3.4",
|
||||
|
@ -142,7 +142,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
|
|||
name: "success_udp_filtered",
|
||||
domain: domain,
|
||||
proto: proxy.ProtoUDP,
|
||||
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
|
||||
addr: testClientAddrPort,
|
||||
clientID: "",
|
||||
wantLogProto: "",
|
||||
wantStatClient: "1.2.3.4",
|
||||
|
@ -153,7 +153,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
|
|||
name: "success_udp_sb",
|
||||
domain: domain,
|
||||
proto: proxy.ProtoUDP,
|
||||
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
|
||||
addr: testClientAddrPort,
|
||||
clientID: "",
|
||||
wantLogProto: "",
|
||||
wantStatClient: "1.2.3.4",
|
||||
|
@ -164,7 +164,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
|
|||
name: "success_udp_ss",
|
||||
domain: domain,
|
||||
proto: proxy.ProtoUDP,
|
||||
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
|
||||
addr: testClientAddrPort,
|
||||
clientID: "",
|
||||
wantLogProto: "",
|
||||
wantStatClient: "1.2.3.4",
|
||||
|
@ -175,7 +175,7 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
|
|||
name: "success_udp_pc",
|
||||
domain: domain,
|
||||
proto: proxy.ProtoUDP,
|
||||
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 1234},
|
||||
addr: testClientAddrPort,
|
||||
clientID: "",
|
||||
wantLogProto: "",
|
||||
wantStatClient: "1.2.3.4",
|
||||
|
@ -186,10 +186,10 @@ func TestServer_ProcessQueryLogsAndStats(t *testing.T) {
|
|||
name: "success_udp_pc_empty_fqdn",
|
||||
domain: ".",
|
||||
proto: proxy.ProtoUDP,
|
||||
addr: &net.UDPAddr{IP: net.IP{1, 2, 3, 5}, Port: 1234},
|
||||
addr: netip.MustParseAddrPort("4.3.2.1:1234"),
|
||||
clientID: "",
|
||||
wantLogProto: "",
|
||||
wantStatClient: "1.2.3.5",
|
||||
wantStatClient: "4.3.2.1",
|
||||
wantCode: resultCodeSuccess,
|
||||
reason: filtering.FilteredParental,
|
||||
wantStatResult: stats.RParental,
|
||||
|
|
|
@ -19,6 +19,7 @@ func TestGenAnswerHTTPS_andSVCB(t *testing.T) {
|
|||
Config: Config{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
|
||||
req := &dns.Msg{
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -53,6 +56,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -89,6 +95,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -60,6 +63,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -99,6 +105,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "refused",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -138,6 +147,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -177,6 +189,98 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 6,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"blocked_response_ttl": 10,
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"cache_optimistic": false,
|
||||
"resolve_clients": false,
|
||||
"use_private_ptr_resolvers": false,
|
||||
"local_ptr_upstreams": [],
|
||||
"edns_cs_use_custom": false,
|
||||
"edns_cs_custom_ip": ""
|
||||
}
|
||||
},
|
||||
"ratelimit_subnet_len": {
|
||||
"req": {
|
||||
"ratelimit": 12,
|
||||
"ratelimit_subnet_len_ipv4": 32,
|
||||
"ratelimit_subnet_len_ipv6": 128
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"fallback_dns": [],
|
||||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 12,
|
||||
"ratelimit_subnet_len_ipv4": 32,
|
||||
"ratelimit_subnet_len_ipv6": 128,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"blocked_response_ttl": 10,
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"cache_optimistic": false,
|
||||
"resolve_clients": false,
|
||||
"use_private_ptr_resolvers": false,
|
||||
"local_ptr_upstreams": [],
|
||||
"edns_cs_use_custom": false,
|
||||
"edns_cs_custom_ip": ""
|
||||
}
|
||||
},
|
||||
"ratelimit_whitelist_not_ip": {
|
||||
"req": {
|
||||
"ratelimit_whitelist": [
|
||||
"1.2.3.4",
|
||||
"not.ip"
|
||||
]
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"fallback_dns": [],
|
||||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -216,6 +320,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -257,6 +364,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -298,6 +408,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -337,6 +450,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -376,6 +492,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -415,6 +534,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -454,6 +576,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -495,6 +620,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -536,6 +664,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -576,6 +707,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -615,6 +749,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -656,6 +793,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -700,6 +840,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -739,6 +882,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -782,6 +928,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -821,6 +970,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
@ -863,6 +1015,9 @@
|
|||
"protection_enabled": true,
|
||||
"protection_disabled_until": null,
|
||||
"ratelimit": 0,
|
||||
"ratelimit_subnet_len_ipv4": 24,
|
||||
"ratelimit_subnet_len_ipv6": 56,
|
||||
"ratelimit_whitelist": [],
|
||||
"blocking_mode": "default",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
package dnsforward
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
|
@ -18,6 +19,28 @@ import (
|
|||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
// errNotDomainSpecific is returned when the upstream should be
|
||||
// domain-specific, but isn't.
|
||||
errNotDomainSpecific errors.Error = "not a domain-specific upstream"
|
||||
|
||||
// errMissingSeparator is returned when the domain-specific part of the
|
||||
// upstream configuration line isn't closed.
|
||||
errMissingSeparator errors.Error = "missing separator"
|
||||
|
||||
// errDupSeparator is returned when the domain-specific part of the upstream
|
||||
// configuration line contains more than one ending separator.
|
||||
errDupSeparator errors.Error = "duplicated separator"
|
||||
|
||||
// errNoDefaultUpstreams is returned when there are no default upstreams
|
||||
// specified in the upstream configuration.
|
||||
errNoDefaultUpstreams errors.Error = "no default upstreams specified"
|
||||
|
||||
// errWrongResponse is returned when the checked upstream replies in an
|
||||
// unexpected way.
|
||||
errWrongResponse errors.Error = "wrong response"
|
||||
)
|
||||
|
||||
// loadUpstreams parses upstream DNS servers from the configured file or from
|
||||
// the configuration itself.
|
||||
func (s *Server) loadUpstreams() (upstreams []string, err error) {
|
||||
|
@ -39,7 +62,7 @@ func (s *Server) loadUpstreams() (upstreams []string, err error) {
|
|||
}
|
||||
|
||||
// prepareUpstreamSettings sets upstream DNS server settings.
|
||||
func (s *Server) prepareUpstreamSettings() (err error) {
|
||||
func (s *Server) prepareUpstreamSettings(boot upstream.Resolver) (err error) {
|
||||
// Load upstreams either from the file, or from the settings
|
||||
var upstreams []string
|
||||
upstreams, err = s.loadUpstreams()
|
||||
|
@ -48,7 +71,7 @@ func (s *Server) prepareUpstreamSettings() (err error) {
|
|||
}
|
||||
|
||||
s.conf.UpstreamConfig, err = s.prepareUpstreamConfig(upstreams, defaultDNS, &upstream.Options{
|
||||
Bootstrap: s.conf.BootstrapDNS,
|
||||
Bootstrap: boot,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams),
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
|
@ -92,178 +115,9 @@ func (s *Server) prepareUpstreamConfig(
|
|||
uc.Upstreams = defaultUpstreamConfig.Upstreams
|
||||
}
|
||||
|
||||
// dnsFilter can be nil during application update.
|
||||
if s.dnsFilter != nil {
|
||||
err = s.replaceUpstreamsWithHosts(uc, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving upstreams with hosts: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return uc, nil
|
||||
}
|
||||
|
||||
// replaceUpstreamsWithHosts replaces unique upstreams with their resolved
|
||||
// versions based on the system hosts file.
|
||||
//
|
||||
// TODO(e.burkov): This should be performed inside dnsproxy, which should
|
||||
// actually consider /etc/hosts. See TODO on [aghnet.HostsContainer].
|
||||
func (s *Server) replaceUpstreamsWithHosts(
|
||||
upsConf *proxy.UpstreamConfig,
|
||||
opts *upstream.Options,
|
||||
) (err error) {
|
||||
resolved := map[string]*upstream.Options{}
|
||||
|
||||
err = s.resolveUpstreamsWithHosts(resolved, upsConf.Upstreams, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving upstreams: %w", err)
|
||||
}
|
||||
|
||||
hosts := maps.Keys(upsConf.DomainReservedUpstreams)
|
||||
// TODO(e.burkov): Think of extracting sorted range into an util function.
|
||||
slices.Sort(hosts)
|
||||
for _, host := range hosts {
|
||||
err = s.resolveUpstreamsWithHosts(resolved, upsConf.DomainReservedUpstreams[host], opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving upstreams reserved for %s: %w", host, err)
|
||||
}
|
||||
}
|
||||
|
||||
hosts = maps.Keys(upsConf.SpecifiedDomainUpstreams)
|
||||
slices.Sort(hosts)
|
||||
for _, host := range hosts {
|
||||
err = s.resolveUpstreamsWithHosts(resolved, upsConf.SpecifiedDomainUpstreams[host], opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving upstreams specific for %s: %w", host, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveUpstreamsWithHosts resolves the IP addresses of each of the upstreams
|
||||
// and replaces those both in upstreams and resolved. Upstreams that failed to
|
||||
// resolve are placed to resolved as-is. This function only returns error of
|
||||
// upstreams closing.
|
||||
func (s *Server) resolveUpstreamsWithHosts(
|
||||
resolved map[string]*upstream.Options,
|
||||
upstreams []upstream.Upstream,
|
||||
opts *upstream.Options,
|
||||
) (err error) {
|
||||
for i := range upstreams {
|
||||
u := upstreams[i]
|
||||
addr := u.Address()
|
||||
host := extractUpstreamHost(addr)
|
||||
|
||||
withIPs, ok := resolved[host]
|
||||
if !ok {
|
||||
recs := s.dnsFilter.EtcHostsRecords(host)
|
||||
if len(recs) == 0 {
|
||||
resolved[host] = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
withIPs = opts.Clone()
|
||||
withIPs.ServerIPAddrs = make([]net.IP, 0, len(recs))
|
||||
for _, rec := range recs {
|
||||
withIPs.ServerIPAddrs = append(withIPs.ServerIPAddrs, rec.Addr.AsSlice())
|
||||
}
|
||||
|
||||
sortNetIPAddrs(withIPs.ServerIPAddrs, opts.PreferIPv6)
|
||||
resolved[host] = withIPs
|
||||
} else if withIPs == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = u.Close(); err != nil {
|
||||
return fmt.Errorf("closing upstream %s: %w", addr, err)
|
||||
}
|
||||
|
||||
upstreams[i], err = upstream.AddressToUpstream(addr, withIPs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("replacing upstream %s with resolved %s: %w", addr, host, err)
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: using %s for %s", withIPs.ServerIPAddrs, upstreams[i].Address())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractUpstreamHost returns the hostname of addr without port with an
|
||||
// assumption that any address passed here has already been successfully parsed
|
||||
// by [upstream.AddressToUpstream]. This function essentially mirrors the logic
|
||||
// of [upstream.AddressToUpstream], see TODO on [replaceUpstreamsWithHosts].
|
||||
func extractUpstreamHost(addr string) (host string) {
|
||||
var err error
|
||||
if strings.Contains(addr, "://") {
|
||||
var u *url.URL
|
||||
u, err = url.Parse(addr)
|
||||
if err != nil {
|
||||
log.Debug("dnsforward: parsing upstream %s: %s", addr, err)
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
return u.Hostname()
|
||||
}
|
||||
|
||||
// Probably, plain UDP upstream defined by address or address:port.
|
||||
host, err = netutil.SplitHost(addr)
|
||||
if err != nil {
|
||||
return addr
|
||||
}
|
||||
|
||||
return host
|
||||
}
|
||||
|
||||
// sortNetIPAddrs sorts addrs in accordance with the protocol preferences.
|
||||
// Invalid addresses are sorted near the end.
|
||||
//
|
||||
// TODO(e.burkov): This function taken from dnsproxy, which also already
|
||||
// contains a few similar functions. Think of moving to golibs.
|
||||
func sortNetIPAddrs(addrs []net.IP, preferIPv6 bool) {
|
||||
l := len(addrs)
|
||||
if l <= 1 {
|
||||
return
|
||||
}
|
||||
|
||||
slices.SortStableFunc(addrs, func(addrA, addrB net.IP) (res int) {
|
||||
switch len(addrA) {
|
||||
case net.IPv4len, net.IPv6len:
|
||||
switch len(addrB) {
|
||||
case net.IPv4len, net.IPv6len:
|
||||
// Go on.
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
|
||||
// Treat IPv6-mapped IPv4 addresses as IPv6 addresses.
|
||||
aIs4, bIs4 := addrA.To4() != nil, addrB.To4() != nil
|
||||
if aIs4 == bIs4 {
|
||||
return bytes.Compare(addrA, addrB)
|
||||
}
|
||||
|
||||
if aIs4 {
|
||||
if preferIPv6 {
|
||||
return 1
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
if preferIPv6 {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 1
|
||||
})
|
||||
}
|
||||
|
||||
// UpstreamHTTPVersions returns the HTTP versions for upstream configuration
|
||||
// depending on configuration.
|
||||
func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) {
|
||||
|
@ -295,3 +149,221 @@ func setProxyUpstreamMode(
|
|||
conf.UpstreamMode = proxy.UModeLoadBalance
|
||||
}
|
||||
}
|
||||
|
||||
// createBootstrap returns a bootstrap resolver based on the configuration of s.
|
||||
// boots are the upstream resolvers that should be closed after use. r is the
|
||||
// actual bootstrap resolver, which may include the system hosts.
|
||||
//
|
||||
// TODO(e.burkov): This function currently returns a resolver and a slice of
|
||||
// the upstream resolvers, which are essentially the same. boots are returned
|
||||
// for being able to close them afterwards, but it introduces an implicit
|
||||
// contract that r could only be used before that. Anyway, this code should
|
||||
// improve when the [proxy.UpstreamConfig] will become an [upstream.Resolver]
|
||||
// and be used here.
|
||||
func (s *Server) createBootstrap(
|
||||
addrs []string,
|
||||
opts *upstream.Options,
|
||||
) (r upstream.Resolver, boots []*upstream.UpstreamResolver, err error) {
|
||||
if len(addrs) == 0 {
|
||||
addrs = defaultBootstrap
|
||||
}
|
||||
|
||||
boots, err = aghnet.ParseBootstraps(addrs, opts)
|
||||
if err != nil {
|
||||
// Don't wrap the error, since it's informative enough as is.
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var parallel upstream.ParallelResolver
|
||||
for _, b := range boots {
|
||||
parallel = append(parallel, b)
|
||||
}
|
||||
|
||||
if s.etcHosts != nil {
|
||||
r = upstream.ConsequentResolver{s.etcHosts, parallel}
|
||||
} else {
|
||||
r = parallel
|
||||
}
|
||||
|
||||
return r, boots, nil
|
||||
}
|
||||
|
||||
// IsCommentOrEmpty returns true if s starts with a "#" character or is empty.
|
||||
// This function is useful for filtering out non-upstream lines from upstream
|
||||
// configs.
|
||||
func IsCommentOrEmpty(s string) (ok bool) {
|
||||
return len(s) == 0 || s[0] == '#'
|
||||
}
|
||||
|
||||
// newUpstreamConfig validates upstreams and returns an appropriate upstream
|
||||
// configuration or nil if it can't be built.
|
||||
//
|
||||
// TODO(e.burkov): Perhaps proxy.ParseUpstreamsConfig should validate upstreams
|
||||
// slice already so that this function may be considered useless.
|
||||
func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err error) {
|
||||
// No need to validate comments and empty lines.
|
||||
upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty)
|
||||
if len(upstreams) == 0 {
|
||||
// Consider this case valid since it means the default server should be
|
||||
// used.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = validateUpstreamConfig(upstreams)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf, err = proxy.ParseUpstreamsConfig(
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
Bootstrap: net.DefaultResolver,
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return nil, err
|
||||
} else if len(conf.Upstreams) == 0 {
|
||||
return nil, errNoDefaultUpstreams
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// validateUpstreamConfig validates each upstream from the upstream
|
||||
// configuration and returns an error if any upstream is invalid.
|
||||
//
|
||||
// TODO(e.burkov): Merge with [upstreamConfigValidator] somehow.
|
||||
func validateUpstreamConfig(conf []string) (err error) {
|
||||
for _, u := range conf {
|
||||
var ups []string
|
||||
var isSpecific bool
|
||||
ups, isSpecific, err = splitUpstreamLine(u)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
for _, addr := range ups {
|
||||
_, err = validateUpstream(addr, isSpecific)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating upstream %q: %w", addr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateUpstreams validates each upstream and returns an error if any
|
||||
// upstream is invalid or if there are no default upstreams specified.
|
||||
//
|
||||
// TODO(e.burkov): Merge with [upstreamConfigValidator] somehow.
|
||||
func ValidateUpstreams(upstreams []string) (err error) {
|
||||
_, err = newUpstreamConfig(upstreams)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ValidateUpstreamsPrivate validates each upstream and returns an error if any
|
||||
// upstream is invalid or if there are no default upstreams specified. It also
|
||||
// checks each domain of domain-specific upstreams for being ARPA pointing to
|
||||
// a locally-served network. privateNets must not be nil.
|
||||
func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet) (err error) {
|
||||
conf, err := newUpstreamConfig(upstreams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating config: %w", err)
|
||||
}
|
||||
|
||||
if conf == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
keys := maps.Keys(conf.DomainReservedUpstreams)
|
||||
slices.Sort(keys)
|
||||
|
||||
var errs []error
|
||||
for _, domain := range keys {
|
||||
var subnet netip.Prefix
|
||||
subnet, err = extractARPASubnet(domain)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !privateNets.Contains(subnet.Addr().AsSlice()) {
|
||||
errs = append(
|
||||
errs,
|
||||
fmt.Errorf("arpa domain %q should point to a locally-served network", domain),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Annotate(errors.Join(errs...), "checking domain-specific upstreams: %w")
|
||||
}
|
||||
|
||||
// protocols are the supported URL schemes for upstreams.
|
||||
var protocols = []string{"h3", "https", "quic", "sdns", "tcp", "tls", "udp"}
|
||||
|
||||
// validateUpstream returns an error if u alongside with domains is not a valid
|
||||
// upstream configuration. useDefault is true if the upstream is
|
||||
// domain-specific and is configured to point at the default upstream server
|
||||
// which is validated separately. The upstream is considered domain-specific
|
||||
// only if domains is at least not nil.
|
||||
func validateUpstream(u string, isSpecific bool) (useDefault bool, err error) {
|
||||
// The special server address '#' means that default server must be used.
|
||||
if useDefault = u == "#" && isSpecific; useDefault {
|
||||
return useDefault, nil
|
||||
}
|
||||
|
||||
// Check if the upstream has a valid protocol prefix.
|
||||
//
|
||||
// TODO(e.burkov): Validate the domain name.
|
||||
if proto, _, ok := strings.Cut(u, "://"); ok {
|
||||
if !slices.Contains(protocols, proto) {
|
||||
return false, fmt.Errorf("bad protocol %q", proto)
|
||||
}
|
||||
} else if _, err = netip.ParseAddr(u); err == nil {
|
||||
return false, nil
|
||||
} else if _, err = netip.ParseAddrPort(u); err == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// splitUpstreamLine returns the upstreams and the specified domains. domains
|
||||
// is nil when the upstream is not domains-specific. Otherwise it may also be
|
||||
// empty.
|
||||
func splitUpstreamLine(upstreamStr string) (upstreams []string, isSpecific bool, err error) {
|
||||
if !strings.HasPrefix(upstreamStr, "[/") {
|
||||
return []string{upstreamStr}, false, nil
|
||||
}
|
||||
|
||||
defer func() { err = errors.Annotate(err, "splitting upstream line %q: %w", upstreamStr) }()
|
||||
|
||||
doms, ups, found := strings.Cut(upstreamStr[2:], "/]")
|
||||
if !found {
|
||||
return nil, false, errMissingSeparator
|
||||
} else if strings.Contains(ups, "/]") {
|
||||
return nil, false, errDupSeparator
|
||||
}
|
||||
|
||||
for i, host := range strings.Split(doms, "/") {
|
||||
if host == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
err = netutil.ValidateDomainName(strings.TrimPrefix(host, "*."))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("domain at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
isSpecific = true
|
||||
}
|
||||
|
||||
return strings.Fields(ups), isSpecific, nil
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue