Compare commits
31 Commits
v0.108.0-b
...
6263-custo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6b704fb58 | ||
|
|
780d001cef | ||
|
|
5dd10d9fc6 | ||
|
|
820bcf0e23 | ||
|
|
1320043e95 | ||
|
|
f28dcefa29 | ||
|
|
8b9708b8e0 | ||
|
|
6eba447958 | ||
|
|
29cc387c5f | ||
|
|
73358263e8 | ||
|
|
cb9262dced | ||
|
|
e7ca515630 | ||
|
|
23392d08b1 | ||
|
|
a09cd190c0 | ||
|
|
feb15745d2 | ||
|
|
789060e4d4 | ||
|
|
0f5e8ca56f | ||
|
|
a700bfb42e | ||
|
|
7669419b59 | ||
|
|
db42254d76 | ||
|
|
4a9dc1d6c1 | ||
|
|
944e0d7a4b | ||
|
|
c8f1112d4e | ||
|
|
1c0bf95b5a | ||
|
|
388583cefe | ||
|
|
8bb1aad739 | ||
|
|
9b91a87406 | ||
|
|
94bceaa84d | ||
|
|
ebb06a5831 | ||
|
|
f44faa9be3 | ||
|
|
b1b5c41cfe |
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!
|
||||
|
||||
33
CHANGELOG.md
33
CHANGELOG.md
@@ -23,6 +23,39 @@ See also the [v0.107.42 GitHub milestone][ms-v0.107.42].
|
||||
NOTE: Add new changes BELOW THIS COMMENT.
|
||||
-->
|
||||
|
||||
### Added
|
||||
|
||||
- Ability to set client's custom DNS cache ([#6362], [dnsproxy#169]).
|
||||
- Ability to disable plain-DNS serving through configuration file if an
|
||||
encrypted protocol is already used ([#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
|
||||
|
||||
[dnsproxy#169] https://github.com/AdguardTeam/dnsproxy/issues/169
|
||||
|
||||
<!--
|
||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||
-->
|
||||
|
||||
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.
|
||||
@@ -307,6 +307,7 @@
|
||||
"edns_use_custom_ip": "Выкарыстоўваць указаны IP для DNS",
|
||||
"edns_use_custom_ip_desc": "Дазволіць выкарыстоўваць уласны IP для DNS",
|
||||
"rate_limit_desc": "Абмежаванне на колькасць запытаў у секунду для кожнага кліента (0 — неабмежавана)",
|
||||
"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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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ä",
|
||||
|
||||
@@ -310,6 +310,7 @@
|
||||
"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_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",
|
||||
|
||||
@@ -310,6 +310,7 @@
|
||||
"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_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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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/",
|
||||
|
||||
@@ -310,6 +310,7 @@
|
||||
"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_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",
|
||||
|
||||
@@ -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アドレスで応答します。",
|
||||
|
||||
@@ -310,6 +310,7 @@
|
||||
"edns_use_custom_ip": "EDNS에 사용자 지정 IP 사용",
|
||||
"edns_use_custom_ip_desc": "EDNS에 사용자 지정 IP 사용하도록 허용합니다.",
|
||||
"rate_limit_desc": "단일 클라이언트에서 허용 가능한 초 당 요청 생성 숫자 (0: 무제한)",
|
||||
"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 주소로 응답합니다",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -310,6 +310,7 @@
|
||||
"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_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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -310,6 +310,7 @@
|
||||
"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_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",
|
||||
|
||||
@@ -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,6 +310,16 @@
|
||||
"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",
|
||||
|
||||
@@ -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-стилі",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 地址",
|
||||
|
||||
@@ -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 位址回覆",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
|
||||
5
go.mod
5
go.mod
@@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.57.3
|
||||
github.com/AdguardTeam/dnsproxy v0.59.1
|
||||
github.com/AdguardTeam/golibs v0.17.2
|
||||
github.com/AdguardTeam/urlfilter v0.17.3
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
@@ -48,7 +48,8 @@ require (
|
||||
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-20231101202521-4ca4178f5c7a // indirect
|
||||
github.com/mdlayher/socket v0.5.0 // 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
|
||||
|
||||
8
go.sum
8
go.sum
@@ -1,5 +1,5 @@
|
||||
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/dnsproxy v0.59.1 h1:G/6T32EuPF0rhRkACkLFwD0pajI9351a1LACpuA2UcE=
|
||||
github.com/AdguardTeam/dnsproxy v0.59.1/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/urlfilter v0.17.3 h1:fg/ObbnO0Cv6aw0tW6N/ETDMhhNvmcUUOZ7HlmKC3rw=
|
||||
@@ -71,8 +71,8 @@ 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=
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -43,7 +43,7 @@ func (conf *Config) Validate() (err error) {
|
||||
case !conf.Enabled:
|
||||
return nil
|
||||
case conf.ICMPTimeout < 0:
|
||||
return fmt.Errorf("icmp timeout %s must be non-negative", conf.ICMPTimeout)
|
||||
return newMustErr("icmp timeout", "be non-negative", conf.ICMPTimeout)
|
||||
}
|
||||
|
||||
err = netutil.ValidateDomainName(conf.LocalDomainName)
|
||||
@@ -68,9 +68,9 @@ func (conf *Config) Validate() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mustBeErr returns an error that indicates that valName must be as must
|
||||
// newMustErr returns an error that indicates that valName must be as must
|
||||
// describes.
|
||||
func mustBeErr(valName, must string, val fmt.Stringer) (err error) {
|
||||
func newMustErr(valName, must string, val fmt.Stringer) (err error) {
|
||||
return fmt.Errorf("%s %s must %s", valName, val, must)
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -57,6 +72,9 @@ type Interface interface {
|
||||
IPByHost(host string) (ip netip.Addr)
|
||||
|
||||
// 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
|
||||
@@ -91,6 +109,9 @@ func (Empty) Shutdown(_ context.Context) (err error) { return nil }
|
||||
// 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 }
|
||||
|
||||
@@ -103,9 +124,6 @@ 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 }
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ type DHCPServer struct {
|
||||
// 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
|
||||
}
|
||||
@@ -75,3 +78,23 @@ func New(conf *Config) (srv *DHCPServer, err error) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -45,15 +45,15 @@ func (conf *IPv4Config) validate() (err error) {
|
||||
case !conf.Enabled:
|
||||
return nil
|
||||
case !conf.GatewayIP.Is4():
|
||||
return mustBeErr("gateway ip", "be a valid ipv4", conf.GatewayIP)
|
||||
return newMustErr("gateway ip", "be a valid ipv4", conf.GatewayIP)
|
||||
case !conf.SubnetMask.Is4():
|
||||
return mustBeErr("subnet mask", "be a valid ipv4 cidr mask", conf.SubnetMask)
|
||||
return newMustErr("subnet mask", "be a valid ipv4 cidr mask", conf.SubnetMask)
|
||||
case !conf.RangeStart.Is4():
|
||||
return mustBeErr("range start", "be a valid ipv4", conf.RangeStart)
|
||||
return newMustErr("range start", "be a valid ipv4", conf.RangeStart)
|
||||
case !conf.RangeEnd.Is4():
|
||||
return mustBeErr("range end", "be a valid ipv4", conf.RangeEnd)
|
||||
return newMustErr("range end", "be a valid ipv4", conf.RangeEnd)
|
||||
case conf.LeaseDuration <= 0:
|
||||
return mustBeErr("lease duration", "be less than %d", conf.LeaseDuration)
|
||||
return newMustErr("lease duration", "be less than %d", conf.LeaseDuration)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -277,14 +289,15 @@ 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,
|
||||
conf = &proxy.Config{
|
||||
HTTP3: srvConf.ServeHTTP3,
|
||||
Ratelimit: int(srvConf.Ratelimit),
|
||||
RatelimitSubnetMaskIPv4: net.CIDRMask(srvConf.RatelimitSubnetLenIPv4, netutil.IPv4BitLen),
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package dnsforward
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
@@ -108,7 +109,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 +136,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 +167,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 +199,7 @@ type DNSCreateParams struct {
|
||||
DHCPServer DHCP
|
||||
PrivateNets netutil.SubnetSet
|
||||
Anonymizer *aghnet.IPMut
|
||||
EtcHosts *aghnet.HostsContainer
|
||||
LocalDomain string
|
||||
}
|
||||
|
||||
@@ -217,19 +232,25 @@ 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,
|
||||
// TODO(e.burkov): Use some case-insensitive string comparison.
|
||||
localDomainSuffix: strings.ToLower(localDomainSuffix),
|
||||
etcHosts: p.EtcHosts,
|
||||
recDetector: newRecursionDetector(recursionTTL, cachedRecurrentReqNum),
|
||||
clientIDCache: cache.New(cache.Config{
|
||||
EnableLRU: true,
|
||||
MaxCount: defaultClientIDCacheCount,
|
||||
}),
|
||||
anonymizer: p.Anonymizer,
|
||||
conf: ServerConfig{
|
||||
ServePlainDNS: true,
|
||||
},
|
||||
}
|
||||
|
||||
s.sysResolvers, err = sysresolv.NewSystemResolvers(nil, defaultPlainDNSPort)
|
||||
@@ -237,8 +258,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
|
||||
@@ -421,7 +440,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,12 +448,14 @@ 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
|
||||
}
|
||||
|
||||
@@ -443,34 +464,31 @@ func (s *Server) startLocked() error {
|
||||
// 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 +497,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 +510,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 +537,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 +562,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 +583,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(boot)
|
||||
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 +638,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 +728,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 +742,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 +754,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()
|
||||
|
||||
@@ -182,6 +182,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 +310,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 +349,7 @@ func TestServer_timeout(t *testing.T) {
|
||||
Config: Config{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
|
||||
s, err := NewServer(DNSCreateParams{DNSFilter: createTestDNSFilter(t)})
|
||||
@@ -381,6 +384,7 @@ func TestServer_Prepare_fallbacks(t *testing.T) {
|
||||
},
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
|
||||
s, err := NewServer(DNSCreateParams{})
|
||||
@@ -402,6 +406,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 +484,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 +538,7 @@ func TestSafeSearch(t *testing.T) {
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
startDeferStop(t, s)
|
||||
@@ -594,6 +601,7 @@ func TestInvalidRequest(t *testing.T) {
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
startDeferStop(t, s)
|
||||
|
||||
@@ -622,6 +630,7 @@ func TestBlockedRequest(t *testing.T) {
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
ProtectionEnabled: true,
|
||||
@@ -644,45 +653,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 +743,7 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) {
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
testUpstm := &aghtest.Upstream{
|
||||
CName: testCNAMEs,
|
||||
@@ -740,6 +776,7 @@ func TestBlockCNAME(t *testing.T) {
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
ProtectionEnabled: true,
|
||||
@@ -814,6 +851,7 @@ func TestClientRulesForCNAMEMatching(t *testing.T) {
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
@@ -858,6 +896,7 @@ func TestNullBlockedRequest(t *testing.T) {
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
ProtectionEnabled: true,
|
||||
@@ -923,6 +962,7 @@ func TestBlockedCustomIP(t *testing.T) {
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
|
||||
// Invalid BlockingIPv4.
|
||||
@@ -974,6 +1014,7 @@ func TestBlockedByHosts(t *testing.T) {
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
@@ -1024,6 +1065,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
startDeferStop(t, s)
|
||||
@@ -1082,6 +1124,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) {
|
||||
|
||||
@@ -35,6 +35,7 @@ func TestHandleDNSRequest_handleDNSRequest(t *testing.T) {
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
filters := []filtering.Filter{{
|
||||
ID: 0, Data: []byte(rules),
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
@@ -45,8 +46,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 *[]string `json:"ratelimit_whitelist"`
|
||||
|
||||
// BlockingMode defines the way blocked responses are constructed.
|
||||
BlockingMode *filtering.BlockingMode `json:"blocking_mode"`
|
||||
@@ -121,6 +133,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 := stringutil.CloneSliceOrEmpty(s.conf.RatelimitWhitelist)
|
||||
|
||||
customIP := s.conf.EDNSClientSubnet.CustomIP
|
||||
enableEDNSClientSubnet := s.conf.EDNSClientSubnet.Enabled
|
||||
@@ -157,7 +172,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 +198,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 +219,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 +228,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 +257,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 +273,157 @@ 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.checkRatelimitWhitelist()
|
||||
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
|
||||
}
|
||||
|
||||
// checkRatelimitWhitelist returns an error if any of IP addresses is invalid.
|
||||
func (req *jsonDNSConfig) checkRatelimitWhitelist() (err error) {
|
||||
if req.RatelimitWhitelist == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, ipStr := range *req.RatelimitWhitelist {
|
||||
if _, err = netip.ParseAddr(ipStr); err != nil {
|
||||
return fmt.Errorf("ratelimit whitelist: at index %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleSetConfig handles requests to the POST /control/dns_config endpoint.
|
||||
@@ -401,6 +520,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 +530,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
|
||||
}
|
||||
|
||||
@@ -454,7 +576,7 @@ func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err erro
|
||||
conf, err = proxy.ParseUpstreamsConfig(
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
Bootstrap: []string{},
|
||||
Bootstrap: net.DefaultResolver,
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
)
|
||||
@@ -769,22 +891,11 @@ func (s *Server) checkUpstreamAddr(
|
||||
}
|
||||
}()
|
||||
|
||||
opts = &upstream.Options{
|
||||
u, err := upstream.AddressToUpstream(addr, &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)
|
||||
}
|
||||
@@ -794,6 +905,13 @@ func (s *Server) checkUpstreamAddr(
|
||||
return check(u)
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
|
||||
// handleTestUpstreamDNS handles requests to the POST /control/test_upstream_dns
|
||||
// endpoint.
|
||||
func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -808,15 +926,21 @@ 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{}
|
||||
|
||||
@@ -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,19 @@ 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: `validating dns config: ratelimit whitelist: at index 1: ParseAddr("not.ip"): ` +
|
||||
`unexpected character (at "not.ip")`,
|
||||
}, {
|
||||
name: "edns_cs_enabled",
|
||||
wantSet: "",
|
||||
@@ -206,24 +220,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",
|
||||
@@ -519,7 +535,9 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
|
||||
Config: Config{
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
srv.etcHosts = hc
|
||||
startDeferStop(t, srv)
|
||||
|
||||
testCases := []struct {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -831,14 +831,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 == nil || 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)
|
||||
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 +846,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{
|
||||
@@ -180,6 +181,7 @@ func TestServer_ProcessFilteringAfterResponse(t *testing.T) {
|
||||
AAAADisabled: tc.aaaaDisabled,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
@@ -369,6 +371,7 @@ func prepareTestServer(t *testing.T, portDoH, portDoT, portDoQ int, ddrEnabled b
|
||||
TLSConfig: TLSConfig{
|
||||
ServerName: ddrTestDomainName,
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -699,6 +702,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)
|
||||
@@ -776,6 +780,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(
|
||||
|
||||
@@ -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,21 +1,15 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// loadUpstreams parses upstream DNS servers from the configured file or from
|
||||
@@ -39,7 +33,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 +42,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 +86,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 +120,41 @@ 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
|
||||
}
|
||||
|
||||
@@ -98,6 +98,8 @@ type Config struct {
|
||||
|
||||
// EtcHosts is a container of IP-hostname pairs taken from the operating
|
||||
// system configuration files (e.g. /etc/hosts).
|
||||
//
|
||||
// TODO(e.burkov): Move it to dnsforward entirely.
|
||||
EtcHosts *aghnet.HostsContainer `yaml:"-"`
|
||||
|
||||
// Called when the configuration is changed by HTTP request
|
||||
|
||||
@@ -509,6 +509,15 @@ var blockedServices = []blockedService{{
|
||||
"||clubhouse.com^",
|
||||
"||clubhouseapi.com^",
|
||||
},
|
||||
}, {
|
||||
ID: "coolapk",
|
||||
Name: "CoolApk",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 384 384\"><path fill-rule=\"evenodd\" d=\"M105.62 104.96c30-1.4 57 6.8 81 24.5a717.7 717.7 0 0 0 34.5 27.5l29-48c12-7.4 21-4.8 27 8l79 142c3 15-3.3 22-18.5 20.5a3007.8 3007.8 0 0 1-103-76 166.46 166.46 0 0 1 25.5-17.5 574.67 574.67 0 0 1 33 24l.5-1a1227.62 1227.62 0 0 0-33-58 2174.49 2174.49 0 0 0-33.5 54c-15 25-35 45.6-59.5 61.5a104.39 104.39 0 0 1-90 6c-41.3-23.1-57.1-58-47.5-104.5a89.1 89.1 0 0 1 75.5-63zm1 31c23-1.6 43.7 4.6 62 18.5a777.4 777.4 0 0 1 26 20.5 668.04 668.04 0 0 0 25.5-17 318.5 318.5 0 0 1-38 57 95.75 95.75 0 0 1-49.5 32.5c-32.5 7-56.4-4.2-71.5-33.5-9.8-30.7-1-54.5 26.5-71.5 6.2-2.9 12.5-5 19-6.5z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||coolapk.com^",
|
||||
"||coolapkmarket.com^",
|
||||
"||coolapkmarket.net^",
|
||||
},
|
||||
}, {
|
||||
ID: "crunchyroll",
|
||||
Name: "Crunchyroll",
|
||||
@@ -1923,6 +1932,14 @@ var blockedServices = []blockedService{{
|
||||
Rules: []string{
|
||||
"||ok.ru^",
|
||||
},
|
||||
}, {
|
||||
ID: "olvid",
|
||||
Name: "Olvid",
|
||||
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 250 250\"><path d=\"M51 2.5c-18.4 4-35 17.3-43.3 34.6C.3 52.4.5 50.3.5 126v67.5l2.3 8c3.4 11.7 8.3 19.8 17.6 29 5.9 5.9 10.1 9 15.6 11.7 14.2 7 12.9 6.9 91.3 6.6 69.5-.3 70.3-.3 76-2.5 17.3-6.6 30.3-17.7 37.4-32 7.5-14.9 7.4-13.9 7.1-92.3-.3-68.8-.3-69.6-2.5-76-3.3-9.6-9-18.6-16.3-26-7.6-7.5-14.8-12-25.2-15.8l-7.3-2.7-69.5-.2c-56.7-.1-70.7.1-76 1.2zm95 39.9c39.6 9.5 66 42.4 66 82.1 0 32.6-17.1 60.1-46.5 74.6-14.9 7.4-23 9.2-40.5 9.2-17.3 0-25.5-1.8-39.7-8.8A85.66 85.66 0 0 1 40.4 145c-2.5-9.1-2.5-31.9 0-41 9.3-33.7 34.8-56.6 70.4-63 6.9-1.3 27.7-.5 35.2 1.4z\"/><path d=\"M113.5 78.4a43.05 43.05 0 0 0-29 23.9c-10.1 21.4-4.2 47.7 13.8 61.4 1.7 1.4 2.5 2.7 2.1 3.7-.9 2.3-9.1 8.1-16.2 11.4l-6.2 2.9 3.8.7c10.4 1.7 31.4-.2 44.2-4 17.5-5.2 32.8-17.4 39.5-31.5 5-10.5 6.4-20.9 4.3-31.6-2.9-14.6-10-25.4-20.9-32-9.9-5.9-23.7-7.8-35.4-4.9z\"/></svg>"),
|
||||
Rules: []string{
|
||||
"||olvid-attachment-chunks.s3.eu-west-3.amazonaws.com^",
|
||||
"||olvid.io^",
|
||||
},
|
||||
}, {
|
||||
ID: "onlyfans",
|
||||
Name: "OnlyFans",
|
||||
|
||||
@@ -14,12 +14,13 @@ import (
|
||||
|
||||
// Client contains information about persistent clients.
|
||||
type Client struct {
|
||||
// upstreamConfig is the custom upstream config for this client. If
|
||||
// it's nil, it has not been initialized yet. If it's non-nil and
|
||||
// empty, there are no valid upstreams. If it's non-nil and non-empty,
|
||||
// these upstream must be used.
|
||||
upstreamConfig *proxy.UpstreamConfig
|
||||
// upstreamConfig is the custom upstream configuration for this client. If
|
||||
// it's nil, it has not been initialized yet. If it's non-nil and empty,
|
||||
// there are no valid upstreams. If it's non-nil and non-empty, these
|
||||
// upstream must be used.
|
||||
upstreamConfig *proxy.CustomUpstreamConfig
|
||||
|
||||
// TODO(d.kolyshev): Make safeSearchConf a pointer.
|
||||
safeSearchConf filtering.SafeSearchConfig
|
||||
SafeSearch filtering.SafeSearch
|
||||
|
||||
@@ -32,6 +33,9 @@ type Client struct {
|
||||
Tags []string
|
||||
Upstreams []string
|
||||
|
||||
UpstreamsCacheSize uint32
|
||||
UpstreamsCacheEnabled bool
|
||||
|
||||
UseOwnSettings bool
|
||||
FilteringEnabled bool
|
||||
SafeBrowsingEnabled bool
|
||||
@@ -57,8 +61,7 @@ func (c *Client) ShallowClone() (sh *Client) {
|
||||
// closeUpstreams closes the client-specific upstream config of c if any.
|
||||
func (c *Client) closeUpstreams() (err error) {
|
||||
if c.upstreamConfig != nil {
|
||||
err = c.upstreamConfig.Close()
|
||||
if err != nil {
|
||||
if err = c.upstreamConfig.Close(); err != nil {
|
||||
return fmt.Errorf("closing upstreams of client %q: %w", c.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,13 @@ func (clients *clientsContainer) Init(
|
||||
return nil
|
||||
}
|
||||
|
||||
if clients.etcHosts != nil {
|
||||
// The clients.etcHosts may be nil even if config.Clients.Sources.HostsFile
|
||||
// is true, because of the deprecated option --no-etc-hosts.
|
||||
//
|
||||
// TODO(e.burkov): The option should probably be returned, since hosts file
|
||||
// currently used not only for clients' information enrichment, but also in
|
||||
// the filtering module and upstream addresses resolution.
|
||||
if config.Clients.Sources.HostsFile && clients.etcHosts != nil {
|
||||
go clients.handleHostsUpdates()
|
||||
}
|
||||
|
||||
@@ -179,6 +185,14 @@ type clientObject struct {
|
||||
Tags []string `yaml:"tags"`
|
||||
Upstreams []string `yaml:"upstreams"`
|
||||
|
||||
// UpstreamsCacheSize is the DNS cache size (in bytes).
|
||||
//
|
||||
// TODO(d.kolyshev): Use [datasize.Bytesize].
|
||||
UpstreamsCacheSize uint32 `yaml:"upstreams_cache_size"`
|
||||
|
||||
// UpstreamsCacheEnabled indicates if the DNS cache is enabled.
|
||||
UpstreamsCacheEnabled bool `yaml:"upstreams_cache_enabled"`
|
||||
|
||||
UseGlobalSettings bool `yaml:"use_global_settings"`
|
||||
FilteringEnabled bool `yaml:"filtering_enabled"`
|
||||
ParentalEnabled bool `yaml:"parental_enabled"`
|
||||
@@ -210,6 +224,8 @@ func (clients *clientsContainer) addFromConfig(
|
||||
UseOwnBlockedServices: !o.UseGlobalBlockedServices,
|
||||
IgnoreQueryLog: o.IgnoreQueryLog,
|
||||
IgnoreStatistics: o.IgnoreStatistics,
|
||||
UpstreamsCacheEnabled: o.UpstreamsCacheEnabled,
|
||||
UpstreamsCacheSize: o.UpstreamsCacheSize,
|
||||
}
|
||||
|
||||
if o.SafeSearchConf.Enabled {
|
||||
@@ -278,6 +294,8 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
|
||||
UseGlobalBlockedServices: !cli.UseOwnBlockedServices,
|
||||
IgnoreQueryLog: cli.IgnoreQueryLog,
|
||||
IgnoreStatistics: cli.IgnoreStatistics,
|
||||
UpstreamsCacheEnabled: cli.UpstreamsCacheEnabled,
|
||||
UpstreamsCacheSize: cli.UpstreamsCacheSize,
|
||||
}
|
||||
|
||||
objs = append(objs, o)
|
||||
@@ -419,18 +437,24 @@ func (clients *clientsContainer) shouldCountClient(ids []string) (y bool) {
|
||||
return true
|
||||
}
|
||||
|
||||
// findUpstreams returns upstreams configured for the client, identified either
|
||||
// by its IP address or its ClientID. upsConf is nil if the client isn't found
|
||||
// or if the client has no custom upstreams.
|
||||
func (clients *clientsContainer) findUpstreams(
|
||||
// type check
|
||||
var _ dnsforward.ClientsContainer = (*clientsContainer)(nil)
|
||||
|
||||
// UpstreamConfigByID implements the [dnsforward.ClientsContainer] interface for
|
||||
// *clientsContainer. upsConf is nil if the client isn't found or if the client
|
||||
// has no custom upstreams.
|
||||
func (clients *clientsContainer) UpstreamConfigByID(
|
||||
id string,
|
||||
) (upsConf *proxy.UpstreamConfig, err error) {
|
||||
bootstrap upstream.Resolver,
|
||||
) (conf *proxy.CustomUpstreamConfig, err error) {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
c, ok := clients.findLocked(id)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
} else if c.upstreamConfig != nil {
|
||||
return c.upstreamConfig, nil
|
||||
}
|
||||
|
||||
upstreams := stringutil.FilterOut(c.Upstreams, dnsforward.IsCommentOrEmpty)
|
||||
@@ -438,24 +462,27 @@ func (clients *clientsContainer) findUpstreams(
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if c.upstreamConfig != nil {
|
||||
return c.upstreamConfig, nil
|
||||
}
|
||||
|
||||
var conf *proxy.UpstreamConfig
|
||||
conf, err = proxy.ParseUpstreamsConfig(
|
||||
var upsConf *proxy.UpstreamConfig
|
||||
upsConf, err = proxy.ParseUpstreamsConfig(
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
Bootstrap: config.DNS.BootstrapDNS,
|
||||
Bootstrap: bootstrap,
|
||||
Timeout: config.DNS.UpstreamTimeout.Duration,
|
||||
HTTPVersions: dnsforward.UpstreamHTTPVersions(config.DNS.UseHTTP3Upstreams),
|
||||
PreferIPv6: config.DNS.BootstrapPreferIPv6,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf = proxy.NewCustomUpstreamConfig(
|
||||
upsConf,
|
||||
c.UpstreamsCacheEnabled,
|
||||
int(c.UpstreamsCacheSize),
|
||||
config.DNS.EDNSClientSubnet.Enabled,
|
||||
)
|
||||
c.upstreamConfig = conf
|
||||
|
||||
return conf, nil
|
||||
@@ -672,10 +699,6 @@ func (clients *clientsContainer) Del(name string) (ok bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := c.closeUpstreams(); err != nil {
|
||||
log.Error("client container: removing client %s: %s", name, err)
|
||||
}
|
||||
|
||||
clients.del(c)
|
||||
|
||||
return true
|
||||
@@ -683,10 +706,14 @@ func (clients *clientsContainer) Del(name string) (ok bool) {
|
||||
|
||||
// del removes c from the indexes. clients.lock is expected to be locked.
|
||||
func (clients *clientsContainer) del(c *Client) {
|
||||
// update Name index
|
||||
if err := c.closeUpstreams(); err != nil {
|
||||
log.Error("client container: removing client %s: %s", c.Name, err)
|
||||
}
|
||||
|
||||
// Update the name index.
|
||||
delete(clients.list, c.Name)
|
||||
|
||||
// update ID index
|
||||
// Update the ID index.
|
||||
for _, id := range c.IDs {
|
||||
delete(clients.idIndex, id)
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ func TestClientsAddExisting(t *testing.T) {
|
||||
|
||||
clients.dhcp = dhcpServer
|
||||
|
||||
err = dhcpServer.AddStaticLease(&dhcpd.Lease{
|
||||
err = dhcpServer.AddStaticLease(&dhcpsvc.Lease{
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
IP: ip,
|
||||
Hostname: "testhost",
|
||||
@@ -355,13 +355,11 @@ func TestClientsCustomUpstream(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.True(t, ok)
|
||||
|
||||
config, err := clients.findUpstreams("1.2.3.4")
|
||||
assert.Nil(t, config)
|
||||
upsConf, err := clients.UpstreamConfigByID("1.2.3.4", net.DefaultResolver)
|
||||
assert.Nil(t, upsConf)
|
||||
assert.NoError(t, err)
|
||||
|
||||
config, err = clients.findUpstreams("1.1.1.1")
|
||||
require.NotNil(t, config)
|
||||
upsConf, err = clients.UpstreamConfigByID("1.1.1.1", net.DefaultResolver)
|
||||
require.NotNil(t, upsConf)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, config.Upstreams, 1)
|
||||
assert.Len(t, config.DomainReservedUpstreams, 1)
|
||||
}
|
||||
|
||||
@@ -56,34 +56,9 @@ type clientJSON struct {
|
||||
|
||||
IgnoreQueryLog aghalg.NullBool `json:"ignore_querylog"`
|
||||
IgnoreStatistics aghalg.NullBool `json:"ignore_statistics"`
|
||||
}
|
||||
|
||||
// copySettings returns a copy of specific settings from JSON or a previous
|
||||
// client.
|
||||
func (j *clientJSON) copySettings(
|
||||
prev *Client,
|
||||
) (weekly *schedule.Weekly, ignoreQueryLog, ignoreStatistics bool) {
|
||||
if j.Schedule != nil {
|
||||
weekly = j.Schedule.Clone()
|
||||
} else if prev != nil && prev.BlockedServices != nil {
|
||||
weekly = prev.BlockedServices.Schedule.Clone()
|
||||
} else {
|
||||
weekly = schedule.EmptyWeekly()
|
||||
}
|
||||
|
||||
if j.IgnoreQueryLog != aghalg.NBNull {
|
||||
ignoreQueryLog = j.IgnoreQueryLog == aghalg.NBTrue
|
||||
} else if prev != nil {
|
||||
ignoreQueryLog = prev.IgnoreQueryLog
|
||||
}
|
||||
|
||||
if j.IgnoreStatistics != aghalg.NBNull {
|
||||
ignoreStatistics = j.IgnoreStatistics == aghalg.NBTrue
|
||||
} else if prev != nil {
|
||||
ignoreStatistics = prev.IgnoreStatistics
|
||||
}
|
||||
|
||||
return weekly, ignoreQueryLog, ignoreStatistics
|
||||
UpstreamsCacheSize uint32 `json:"upstreams_cache_size"`
|
||||
UpstreamsCacheEnabled aghalg.NullBool `json:"upstreams_cache_enabled"`
|
||||
}
|
||||
|
||||
type runtimeClientJSON struct {
|
||||
@@ -142,36 +117,35 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
|
||||
|
||||
// jsonToClient converts JSON object to Client object.
|
||||
func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *Client, err error) {
|
||||
var safeSearchConf filtering.SafeSearchConfig
|
||||
if cj.SafeSearchConf != nil {
|
||||
safeSearchConf = *cj.SafeSearchConf
|
||||
} else {
|
||||
// TODO(d.kolyshev): Remove after cleaning the deprecated
|
||||
// [clientJSON.SafeSearchEnabled] field.
|
||||
safeSearchConf = filtering.SafeSearchConfig{
|
||||
Enabled: cj.SafeSearchEnabled,
|
||||
}
|
||||
safeSearchConf := copySafeSearch(cj.SafeSearchConf, cj.SafeSearchEnabled)
|
||||
|
||||
// Set default service flags for enabled safesearch.
|
||||
if safeSearchConf.Enabled {
|
||||
safeSearchConf.Bing = true
|
||||
safeSearchConf.DuckDuckGo = true
|
||||
safeSearchConf.Google = true
|
||||
safeSearchConf.Pixabay = true
|
||||
safeSearchConf.Yandex = true
|
||||
safeSearchConf.YouTube = true
|
||||
}
|
||||
var ignoreQueryLog bool
|
||||
if cj.IgnoreQueryLog != aghalg.NBNull {
|
||||
ignoreQueryLog = cj.IgnoreQueryLog == aghalg.NBTrue
|
||||
} else if prev != nil {
|
||||
ignoreQueryLog = prev.IgnoreQueryLog
|
||||
}
|
||||
|
||||
weekly, ignoreQueryLog, ignoreStatistics := cj.copySettings(prev)
|
||||
|
||||
bs := &filtering.BlockedServices{
|
||||
Schedule: weekly,
|
||||
IDs: cj.BlockedServices,
|
||||
var ignoreStatistics bool
|
||||
if cj.IgnoreStatistics != aghalg.NBNull {
|
||||
ignoreStatistics = cj.IgnoreStatistics == aghalg.NBTrue
|
||||
} else if prev != nil {
|
||||
ignoreStatistics = prev.IgnoreStatistics
|
||||
}
|
||||
err = bs.Validate()
|
||||
|
||||
var upsCacheEnabled bool
|
||||
var upsCacheSize uint32
|
||||
if cj.UpstreamsCacheEnabled != aghalg.NBNull {
|
||||
upsCacheEnabled = cj.UpstreamsCacheEnabled == aghalg.NBTrue
|
||||
upsCacheSize = cj.UpstreamsCacheSize
|
||||
} else if prev != nil {
|
||||
upsCacheEnabled = prev.UpstreamsCacheEnabled
|
||||
upsCacheSize = prev.UpstreamsCacheSize
|
||||
}
|
||||
|
||||
svcs, err := copyBlockedServices(cj.Schedule, cj.BlockedServices, prev)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating blocked services: %w", err)
|
||||
return nil, fmt.Errorf("invalid blocked services: %w", err)
|
||||
}
|
||||
|
||||
c = &Client{
|
||||
@@ -179,7 +153,7 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C
|
||||
|
||||
Name: cj.Name,
|
||||
|
||||
BlockedServices: bs,
|
||||
BlockedServices: svcs,
|
||||
|
||||
IDs: cj.IDs,
|
||||
Tags: cj.Tags,
|
||||
@@ -192,6 +166,8 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C
|
||||
UseOwnBlockedServices: !cj.UseGlobalBlockedServices,
|
||||
IgnoreQueryLog: ignoreQueryLog,
|
||||
IgnoreStatistics: ignoreStatistics,
|
||||
UpstreamsCacheEnabled: upsCacheEnabled,
|
||||
UpstreamsCacheSize: upsCacheSize,
|
||||
}
|
||||
|
||||
if safeSearchConf.Enabled {
|
||||
@@ -208,6 +184,63 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// copySafeSearch returns safe search config created from provided parameters.
|
||||
func copySafeSearch(
|
||||
jsonConf *filtering.SafeSearchConfig,
|
||||
enabled bool,
|
||||
) (conf filtering.SafeSearchConfig) {
|
||||
if jsonConf != nil {
|
||||
return *jsonConf
|
||||
}
|
||||
|
||||
// TODO(d.kolyshev): Remove after cleaning the deprecated
|
||||
// [clientJSON.SafeSearchEnabled] field.
|
||||
conf = filtering.SafeSearchConfig{
|
||||
Enabled: enabled,
|
||||
}
|
||||
|
||||
// Set default service flags for enabled safesearch.
|
||||
if conf.Enabled {
|
||||
conf.Bing = true
|
||||
conf.DuckDuckGo = true
|
||||
conf.Google = true
|
||||
conf.Pixabay = true
|
||||
conf.Yandex = true
|
||||
conf.YouTube = true
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
// copyBlockedServices converts a json blocked services to an internal blocked
|
||||
// services.
|
||||
func copyBlockedServices(
|
||||
sch *schedule.Weekly,
|
||||
svcStrs []string,
|
||||
prev *Client,
|
||||
) (svcs *filtering.BlockedServices, err error) {
|
||||
var weekly *schedule.Weekly
|
||||
if sch != nil {
|
||||
weekly = sch.Clone()
|
||||
} else if prev != nil && prev.BlockedServices != nil {
|
||||
weekly = prev.BlockedServices.Schedule.Clone()
|
||||
} else {
|
||||
weekly = schedule.EmptyWeekly()
|
||||
}
|
||||
|
||||
svcs = &filtering.BlockedServices{
|
||||
Schedule: weekly,
|
||||
IDs: svcStrs,
|
||||
}
|
||||
|
||||
err = svcs.Validate()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating blocked services: %w", err)
|
||||
}
|
||||
|
||||
return svcs, nil
|
||||
}
|
||||
|
||||
// clientToJSON converts Client object to JSON.
|
||||
func clientToJSON(c *Client) (cj *clientJSON) {
|
||||
// TODO(d.kolyshev): Remove after cleaning the deprecated
|
||||
@@ -235,6 +268,9 @@ func clientToJSON(c *Client) (cj *clientJSON) {
|
||||
|
||||
IgnoreQueryLog: aghalg.BoolToNullBool(c.IgnoreQueryLog),
|
||||
IgnoreStatistics: aghalg.BoolToNullBool(c.IgnoreStatistics),
|
||||
|
||||
UpstreamsCacheSize: c.UpstreamsCacheSize,
|
||||
UpstreamsCacheEnabled: aghalg.BoolToNullBool(c.UpstreamsCacheEnabled),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,8 @@ type configuration struct {
|
||||
// Theme is a UI theme for current user.
|
||||
Theme Theme `yaml:"theme"`
|
||||
|
||||
// TODO(a.garipov): Make DNS and the fields below pointers and validate
|
||||
// and/or reset on explicit nulling.
|
||||
DNS dnsConfig `yaml:"dns"`
|
||||
TLS tlsConfigSettings `yaml:"tls"`
|
||||
QueryLog queryLogConfig `yaml:"querylog"`
|
||||
@@ -214,18 +216,21 @@ type dnsConfig struct {
|
||||
// DNS64Prefixes is the list of NAT64 prefixes to be used for DNS64.
|
||||
DNS64Prefixes []netip.Prefix `yaml:"dns64_prefixes"`
|
||||
|
||||
// ServeHTTP3 defines if HTTP/3 is be allowed for incoming requests.
|
||||
// ServeHTTP3 defines if HTTP/3 is allowed for incoming requests.
|
||||
//
|
||||
// TODO(a.garipov): Add to the UI when HTTP/3 support is no longer
|
||||
// experimental.
|
||||
ServeHTTP3 bool `yaml:"serve_http3"`
|
||||
|
||||
// UseHTTP3Upstreams defines if HTTP/3 is be allowed for DNS-over-HTTPS
|
||||
// UseHTTP3Upstreams defines if HTTP/3 is allowed for DNS-over-HTTPS
|
||||
// upstreams.
|
||||
//
|
||||
// TODO(a.garipov): Add to the UI when HTTP/3 support is no longer
|
||||
// experimental.
|
||||
UseHTTP3Upstreams bool `yaml:"use_http3_upstreams"`
|
||||
|
||||
// ServePlainDNS defines if plain DNS is allowed for incoming requests.
|
||||
ServePlainDNS bool `yaml:"serve_plain_dns"`
|
||||
}
|
||||
|
||||
type tlsConfigSettings struct {
|
||||
@@ -333,6 +338,7 @@ var config = &configuration{
|
||||
},
|
||||
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
|
||||
UsePrivateRDNS: true,
|
||||
ServePlainDNS: true,
|
||||
},
|
||||
TLS: tlsConfigSettings{
|
||||
PortHTTPS: defaultPortHTTPS,
|
||||
|
||||
@@ -138,28 +138,28 @@ func initDNSServer(
|
||||
QueryLog: qlog,
|
||||
PrivateNets: privateNets,
|
||||
Anonymizer: anonymizer,
|
||||
LocalDomain: config.DHCP.LocalDomainName,
|
||||
DHCPServer: dhcpSrv,
|
||||
EtcHosts: Context.etcHosts,
|
||||
LocalDomain: config.DHCP.LocalDomainName,
|
||||
})
|
||||
defer func() {
|
||||
if err != nil {
|
||||
closeDNSServer()
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
closeDNSServer()
|
||||
|
||||
return fmt.Errorf("dnsforward.NewServer: %w", err)
|
||||
}
|
||||
|
||||
Context.clients.dnsServer = Context.dnsServer
|
||||
|
||||
dnsConf, err := newServerConfig(tlsConf, httpReg)
|
||||
dnsConf, err := newServerConfig(&config.DNS, config.Clients.Sources, tlsConf, httpReg)
|
||||
if err != nil {
|
||||
closeDNSServer()
|
||||
|
||||
return fmt.Errorf("newServerConfig: %w", err)
|
||||
}
|
||||
|
||||
err = Context.dnsServer.Prepare(dnsConf)
|
||||
if err != nil {
|
||||
closeDNSServer()
|
||||
|
||||
return fmt.Errorf("dnsServer.Prepare: %w", err)
|
||||
}
|
||||
|
||||
@@ -222,21 +222,37 @@ func ipsToUDPAddrs(ips []netip.Addr, port uint16) (udpAddrs []*net.UDPAddr) {
|
||||
return udpAddrs
|
||||
}
|
||||
|
||||
// newServerConfig converts values from the configuration file into the internal
|
||||
// DNS server configuration. All arguments must not be nil.
|
||||
func newServerConfig(
|
||||
dnsConf *dnsConfig,
|
||||
clientSrcConf *clientSourcesConfig,
|
||||
tlsConf *tlsConfigSettings,
|
||||
httpReg aghhttp.RegisterFunc,
|
||||
) (newConf *dnsforward.ServerConfig, err error) {
|
||||
dnsConf := config.DNS
|
||||
hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()})
|
||||
|
||||
fwdConf := dnsConf.Config
|
||||
fwdConf.FilterHandler = applyAdditionalFiltering
|
||||
fwdConf.ClientsContainer = &Context.clients
|
||||
|
||||
newConf = &dnsforward.ServerConfig{
|
||||
UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port),
|
||||
TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port),
|
||||
Config: dnsConf.Config,
|
||||
ConfigModified: onConfigModified,
|
||||
HTTPRegister: httpReg,
|
||||
UseDNS64: config.DNS.UseDNS64,
|
||||
DNS64Prefixes: config.DNS.DNS64Prefixes,
|
||||
UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port),
|
||||
TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port),
|
||||
Config: fwdConf,
|
||||
TLSConfig: newDNSTLSConfig(tlsConf, hosts),
|
||||
TLSAllowUnencryptedDoH: tlsConf.AllowUnencryptedDoH,
|
||||
UpstreamTimeout: dnsConf.UpstreamTimeout.Duration,
|
||||
TLSv12Roots: Context.tlsRoots,
|
||||
ConfigModified: onConfigModified,
|
||||
HTTPRegister: httpReg,
|
||||
LocalPTRResolvers: dnsConf.LocalPTRResolvers,
|
||||
UseDNS64: dnsConf.UseDNS64,
|
||||
DNS64Prefixes: dnsConf.DNS64Prefixes,
|
||||
UsePrivateRDNS: dnsConf.UsePrivateRDNS,
|
||||
ServeHTTP3: dnsConf.ServeHTTP3,
|
||||
UseHTTP3Upstreams: dnsConf.UseHTTP3Upstreams,
|
||||
ServePlainDNS: dnsConf.ServePlainDNS,
|
||||
}
|
||||
|
||||
var initialAddresses []netip.Addr
|
||||
@@ -254,79 +270,81 @@ func newServerConfig(
|
||||
AddressUpdater: &Context.clients,
|
||||
InitialAddresses: initialAddresses,
|
||||
CatchPanics: true,
|
||||
UseRDNS: config.Clients.Sources.RDNS,
|
||||
UseWHOIS: config.Clients.Sources.WHOIS,
|
||||
UseRDNS: clientSrcConf.RDNS,
|
||||
UseWHOIS: clientSrcConf.WHOIS,
|
||||
}
|
||||
|
||||
if tlsConf.Enabled {
|
||||
newConf.TLSConfig = tlsConf.TLSConfig
|
||||
newConf.TLSConfig.ServerName = tlsConf.ServerName
|
||||
|
||||
if tlsConf.PortHTTPS != 0 {
|
||||
newConf.HTTPSListenAddrs = ipsToTCPAddrs(hosts, tlsConf.PortHTTPS)
|
||||
}
|
||||
|
||||
if tlsConf.PortDNSOverTLS != 0 {
|
||||
newConf.TLSListenAddrs = ipsToTCPAddrs(hosts, tlsConf.PortDNSOverTLS)
|
||||
}
|
||||
|
||||
if tlsConf.PortDNSOverQUIC != 0 {
|
||||
newConf.QUICListenAddrs = ipsToUDPAddrs(hosts, tlsConf.PortDNSOverQUIC)
|
||||
}
|
||||
|
||||
if tlsConf.PortDNSCrypt != 0 {
|
||||
newConf.DNSCryptConfig, err = newDNSCrypt(hosts, *tlsConf)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's already wrapped by
|
||||
// newDNSCrypt.
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
newConf.DNSCryptConfig, err = newDNSCryptConfig(tlsConf, hosts)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's already wrapped by
|
||||
// newDNSCryptConfig.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newConf.TLSv12Roots = Context.tlsRoots
|
||||
newConf.TLSAllowUnencryptedDoH = tlsConf.AllowUnencryptedDoH
|
||||
|
||||
newConf.FilterHandler = applyAdditionalFiltering
|
||||
newConf.GetCustomUpstreamByClient = Context.clients.findUpstreams
|
||||
|
||||
newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers
|
||||
newConf.UpstreamTimeout = dnsConf.UpstreamTimeout.Duration
|
||||
|
||||
newConf.UsePrivateRDNS = dnsConf.UsePrivateRDNS
|
||||
newConf.ServeHTTP3 = dnsConf.ServeHTTP3
|
||||
newConf.UseHTTP3Upstreams = dnsConf.UseHTTP3Upstreams
|
||||
|
||||
return newConf, nil
|
||||
}
|
||||
|
||||
func newDNSCrypt(hosts []netip.Addr, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) {
|
||||
if tlsConf.DNSCryptConfigFile == "" {
|
||||
return dnscc, errors.Error("no dnscrypt_config_file")
|
||||
// newDNSTLSConfig converts values from the configuration file into the internal
|
||||
// TLS settings for the DNS server. tlsConf must not be nil.
|
||||
func newDNSTLSConfig(conf *tlsConfigSettings, addrs []netip.Addr) (dnsConf dnsforward.TLSConfig) {
|
||||
if !conf.Enabled {
|
||||
return dnsforward.TLSConfig{}
|
||||
}
|
||||
|
||||
f, err := os.Open(tlsConf.DNSCryptConfigFile)
|
||||
dnsConf = conf.TLSConfig
|
||||
dnsConf.ServerName = conf.ServerName
|
||||
|
||||
if conf.PortHTTPS != 0 {
|
||||
dnsConf.HTTPSListenAddrs = ipsToTCPAddrs(addrs, conf.PortHTTPS)
|
||||
}
|
||||
|
||||
if conf.PortDNSOverTLS != 0 {
|
||||
dnsConf.TLSListenAddrs = ipsToTCPAddrs(addrs, conf.PortDNSOverTLS)
|
||||
}
|
||||
|
||||
if conf.PortDNSOverQUIC != 0 {
|
||||
dnsConf.QUICListenAddrs = ipsToUDPAddrs(addrs, conf.PortDNSOverQUIC)
|
||||
}
|
||||
|
||||
return dnsConf
|
||||
}
|
||||
|
||||
// newDNSCryptConfig converts values from the configuration file into the
|
||||
// internal DNSCrypt settings for the DNS server. conf must not be nil.
|
||||
func newDNSCryptConfig(
|
||||
conf *tlsConfigSettings,
|
||||
addrs []netip.Addr,
|
||||
) (dnsCryptConf dnsforward.DNSCryptConfig, err error) {
|
||||
if !conf.Enabled || conf.PortDNSCrypt == 0 {
|
||||
return dnsforward.DNSCryptConfig{}, nil
|
||||
}
|
||||
|
||||
if conf.DNSCryptConfigFile == "" {
|
||||
return dnsforward.DNSCryptConfig{}, errors.Error("no dnscrypt_config_file")
|
||||
}
|
||||
|
||||
f, err := os.Open(conf.DNSCryptConfigFile)
|
||||
if err != nil {
|
||||
return dnscc, fmt.Errorf("opening dnscrypt config: %w", err)
|
||||
return dnsforward.DNSCryptConfig{}, fmt.Errorf("opening dnscrypt config: %w", err)
|
||||
}
|
||||
defer func() { err = errors.WithDeferred(err, f.Close()) }()
|
||||
|
||||
rc := &dnscrypt.ResolverConfig{}
|
||||
err = yaml.NewDecoder(f).Decode(rc)
|
||||
if err != nil {
|
||||
return dnscc, fmt.Errorf("decoding dnscrypt config: %w", err)
|
||||
return dnsforward.DNSCryptConfig{}, fmt.Errorf("decoding dnscrypt config: %w", err)
|
||||
}
|
||||
|
||||
cert, err := rc.CreateCert()
|
||||
if err != nil {
|
||||
return dnscc, fmt.Errorf("creating dnscrypt cert: %w", err)
|
||||
return dnsforward.DNSCryptConfig{}, fmt.Errorf("creating dnscrypt cert: %w", err)
|
||||
}
|
||||
|
||||
return dnsforward.DNSCryptConfig{
|
||||
ResolverCert: cert,
|
||||
ProviderName: rc.ProviderName,
|
||||
UDPListenAddrs: ipsToUDPAddrs(hosts, tlsConf.PortDNSCrypt),
|
||||
TCPListenAddrs: ipsToTCPAddrs(hosts, tlsConf.PortDNSCrypt),
|
||||
UDPListenAddrs: ipsToUDPAddrs(addrs, conf.PortDNSCrypt),
|
||||
TCPListenAddrs: ipsToTCPAddrs(addrs, conf.PortDNSCrypt),
|
||||
Enabled: true,
|
||||
}, nil
|
||||
}
|
||||
@@ -342,34 +360,36 @@ func getDNSEncryption() (de dnsEncryption) {
|
||||
|
||||
Context.tls.WriteDiskConfig(&tlsConf)
|
||||
|
||||
if tlsConf.Enabled && len(tlsConf.ServerName) != 0 {
|
||||
hostname := tlsConf.ServerName
|
||||
if tlsConf.PortHTTPS != 0 {
|
||||
addr := hostname
|
||||
if p := tlsConf.PortHTTPS; p != defaultPortHTTPS {
|
||||
addr = netutil.JoinHostPort(addr, p)
|
||||
}
|
||||
if !tlsConf.Enabled || len(tlsConf.ServerName) == 0 {
|
||||
return dnsEncryption{}
|
||||
}
|
||||
|
||||
de.https = (&url.URL{
|
||||
Scheme: "https",
|
||||
Host: addr,
|
||||
Path: "/dns-query",
|
||||
}).String()
|
||||
hostname := tlsConf.ServerName
|
||||
if tlsConf.PortHTTPS != 0 {
|
||||
addr := hostname
|
||||
if p := tlsConf.PortHTTPS; p != defaultPortHTTPS {
|
||||
addr = netutil.JoinHostPort(addr, p)
|
||||
}
|
||||
|
||||
if p := tlsConf.PortDNSOverTLS; p != 0 {
|
||||
de.tls = (&url.URL{
|
||||
Scheme: "tls",
|
||||
Host: netutil.JoinHostPort(hostname, p),
|
||||
}).String()
|
||||
}
|
||||
de.https = (&url.URL{
|
||||
Scheme: "https",
|
||||
Host: addr,
|
||||
Path: "/dns-query",
|
||||
}).String()
|
||||
}
|
||||
|
||||
if p := tlsConf.PortDNSOverQUIC; p != 0 {
|
||||
de.quic = (&url.URL{
|
||||
Scheme: "quic",
|
||||
Host: netutil.JoinHostPort(hostname, p),
|
||||
}).String()
|
||||
}
|
||||
if p := tlsConf.PortDNSOverTLS; p != 0 {
|
||||
de.tls = (&url.URL{
|
||||
Scheme: "tls",
|
||||
Host: netutil.JoinHostPort(hostname, p),
|
||||
}).String()
|
||||
}
|
||||
|
||||
if p := tlsConf.PortDNSOverQUIC; p != 0 {
|
||||
de.quic = (&url.URL{
|
||||
Scheme: "quic",
|
||||
Host: netutil.JoinHostPort(hostname, p),
|
||||
}).String()
|
||||
}
|
||||
|
||||
return de
|
||||
@@ -454,7 +474,7 @@ func reconfigureDNSServer() (err error) {
|
||||
tlsConf := &tlsConfigSettings{}
|
||||
Context.tls.WriteDiskConfig(tlsConf)
|
||||
|
||||
newConf, err := newServerConfig(tlsConf, httpRegister)
|
||||
newConf, err := newServerConfig(&config.DNS, config.Clients.Sources, tlsConf, httpRegister)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating forwarding dns server config: %w", err)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
@@ -160,7 +159,7 @@ func setupContext(opts options) (err error) {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if !opts.noEtcHosts && config.Clients.Sources.HostsFile {
|
||||
if !opts.noEtcHosts {
|
||||
err = setupHostsContainer()
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
@@ -239,13 +238,13 @@ func setupHostsContainer() (err error) {
|
||||
)
|
||||
if err != nil {
|
||||
closeErr := hostsWatcher.Close()
|
||||
if errors.Is(err, aghnet.ErrNoHostsPaths) && closeErr == nil {
|
||||
if errors.Is(err, aghnet.ErrNoHostsPaths) {
|
||||
log.Info("warning: initing hosts container: %s", err)
|
||||
|
||||
return nil
|
||||
return closeErr
|
||||
}
|
||||
|
||||
return errors.WithDeferred(fmt.Errorf("initing hosts container: %w", err), closeErr)
|
||||
return errors.Join(fmt.Errorf("initializing hosts container: %w", err), closeErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -294,19 +293,13 @@ func initContextClients() (err error) {
|
||||
arpDB = arpdb.New()
|
||||
}
|
||||
|
||||
err = Context.clients.Init(
|
||||
return Context.clients.Init(
|
||||
config.Clients.Persistent,
|
||||
Context.dhcpServer,
|
||||
Context.etcHosts,
|
||||
arpDB,
|
||||
config.Filtering,
|
||||
)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupBindOpts overrides bind host/port from the opts.
|
||||
@@ -376,11 +369,15 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) {
|
||||
|
||||
upsOpts := &upstream.Options{
|
||||
Timeout: dnsTimeout,
|
||||
ServerIPAddrs: []net.IP{
|
||||
{94, 140, 14, 15},
|
||||
{94, 140, 15, 16},
|
||||
net.ParseIP("2a10:50c0::bad1:ff"),
|
||||
net.ParseIP("2a10:50c0::bad2:ff"),
|
||||
Bootstrap: upstream.StaticResolver{
|
||||
// 94.140.14.15.
|
||||
netip.AddrFrom4([4]byte{94, 140, 14, 15}),
|
||||
// 94.140.14.16.
|
||||
netip.AddrFrom4([4]byte{94, 140, 14, 16}),
|
||||
// 2a10:50c0::bad1:ff.
|
||||
netip.AddrFrom16([16]byte{42, 16, 80, 192, 12: 186, 209, 0, 255}),
|
||||
// 2a10:50c0::bad2:ff.
|
||||
netip.AddrFrom16([16]byte{42, 16, 80, 192, 12: 186, 210, 0, 255}),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -296,6 +296,10 @@ func (m *manager) ipsets(names []string) (sets []props, err error) {
|
||||
return nil, fmt.Errorf("unknown ipset %q", n)
|
||||
}
|
||||
|
||||
if p.family != netfilter.ProtoIPv4 && p.family != netfilter.ProtoIPv6 {
|
||||
return nil, fmt.Errorf("%q unexpected ipset family %q", p.name, p.family)
|
||||
}
|
||||
|
||||
sets = append(sets, p)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,14 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
|
||||
// TODO(a.garipov): Add a “dnsproxy proxy” package to shield us from changes
|
||||
// and replacement of module dnsproxy.
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
)
|
||||
|
||||
// Service is the AdGuard Home DNS service. A nil *Service is a valid
|
||||
@@ -27,6 +30,7 @@ import (
|
||||
type Service struct {
|
||||
proxy *proxy.Proxy
|
||||
bootstraps []string
|
||||
bootstrapResolvers []*upstream.UpstreamResolver
|
||||
upstreams []string
|
||||
dns64Prefixes []netip.Prefix
|
||||
upsTimeout time.Duration
|
||||
@@ -52,7 +56,7 @@ func New(c *Config) (svc *Service, err error) {
|
||||
useDNS64: c.UseDNS64,
|
||||
}
|
||||
|
||||
upstreams, err := addressesToUpstreams(
|
||||
upstreams, resolvers, err := addressesToUpstreams(
|
||||
c.UpstreamServers,
|
||||
c.BootstrapServers,
|
||||
c.UpstreamTimeout,
|
||||
@@ -62,6 +66,7 @@ func New(c *Config) (svc *Service, err error) {
|
||||
return nil, fmt.Errorf("converting upstreams: %w", err)
|
||||
}
|
||||
|
||||
svc.bootstrapResolvers = resolvers
|
||||
svc.proxy = &proxy.Proxy{
|
||||
Config: proxy.Config{
|
||||
UDPListenAddr: udpAddrs(c.Addresses),
|
||||
@@ -90,20 +95,37 @@ func addressesToUpstreams(
|
||||
bootstraps []string,
|
||||
timeout time.Duration,
|
||||
preferIPv6 bool,
|
||||
) (upstreams []upstream.Upstream, err error) {
|
||||
) (upstreams []upstream.Upstream, boots []*upstream.UpstreamResolver, err error) {
|
||||
opts := &upstream.Options{
|
||||
Timeout: timeout,
|
||||
PreferIPv6: preferIPv6,
|
||||
}
|
||||
|
||||
boots, err = aghnet.ParseBootstraps(bootstraps, opts)
|
||||
if err != nil {
|
||||
// Don't wrap the error, since it's informative enough as is.
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Add system hosts resolver here.
|
||||
var bootstrap upstream.ParallelResolver
|
||||
for _, r := range boots {
|
||||
bootstrap = append(bootstrap, r)
|
||||
}
|
||||
|
||||
upstreams = make([]upstream.Upstream, len(upsStrs))
|
||||
for i, upsStr := range upsStrs {
|
||||
upstreams[i], err = upstream.AddressToUpstream(upsStr, &upstream.Options{
|
||||
Bootstrap: bootstraps,
|
||||
Bootstrap: bootstrap,
|
||||
Timeout: timeout,
|
||||
PreferIPv6: preferIPv6,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("upstream at index %d: %w", i, err)
|
||||
return nil, boots, fmt.Errorf("upstream at index %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
return upstreams, nil
|
||||
return upstreams, boots, nil
|
||||
}
|
||||
|
||||
// tcpAddrs converts []netip.AddrPort into []*net.TCPAddr.
|
||||
@@ -162,7 +184,15 @@ func (svc *Service) Shutdown(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return svc.proxy.Stop()
|
||||
errs := []error{
|
||||
svc.proxy.Stop(),
|
||||
}
|
||||
|
||||
for _, b := range svc.bootstrapResolvers {
|
||||
errs = append(errs, errors.Annotate(b.Close(), "closing bootstrap %s: %w", b.Address()))
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// Config returns the current configuration of the web service. Config must not
|
||||
|
||||
@@ -4,6 +4,37 @@
|
||||
|
||||
## v0.108.0: API changes
|
||||
|
||||
## v0.107.42: API changes
|
||||
|
||||
### The new fields `"upstreams_cache_enabled"` and `"upstreams_cache_size"` in `Client` object
|
||||
|
||||
* The new field `"upstreams_cache_enabled"` in `GET /control/clients`,
|
||||
`GET /control/clients/find`, `POST /control/clients/add`, and
|
||||
`POST /control/clients/update` methods shows if client's DNS cache is enabled
|
||||
for the client. If not set AdGuard Home will use default value (false).
|
||||
|
||||
* The new field `"upstreams_cache_size"` in `GET /control/clients`,
|
||||
`GET /control/clients/find`, `POST /control/clients/add`, and
|
||||
`POST /control/clients/update` methods is the size of client's DNS cache in
|
||||
bytes.
|
||||
|
||||
### The new field `"ratelimit_subnet_len_ipv4"` in `DNSConfig` object
|
||||
|
||||
* The new field `"ratelimit_subnet_len_ipv4"` in `GET /control/dns_info` and
|
||||
`POST /control/dns_config` is the length of the subnet mask for IPv4
|
||||
addresses.
|
||||
|
||||
### The new field `"ratelimit_subnet_len_ipv6"` in `DNSConfig` object
|
||||
|
||||
* The new field `"ratelimit_subnet_len_ipv6"` in `GET /control/dns_info` and
|
||||
`POST /control/dns_config` is the length of the subnet mask for IPv6
|
||||
addresses.
|
||||
|
||||
### The new field `"ratelimit_whitelist"` in `DNSConfig` object
|
||||
|
||||
* The new field `"blocked_response_ttl"` in `GET /control/dns_info` and `POST
|
||||
/control/dns_config` is the list of IP addresses excluded from rate limiting.
|
||||
|
||||
## v0.107.39: API changes
|
||||
|
||||
### New HTTP API 'POST /control/dhcp/update_static_lease'
|
||||
|
||||
@@ -1468,6 +1468,23 @@
|
||||
'type': 'boolean'
|
||||
'ratelimit':
|
||||
'type': 'integer'
|
||||
'ratelimit_subnet_subnet_len_ipv4':
|
||||
'description': 'Length of the subnet mask for IPv4 addresses.'
|
||||
'type': 'integer'
|
||||
'default': 24
|
||||
'minimum': 0
|
||||
'maximum': 32
|
||||
'ratelimit_subnet_subnet_len_ipv6':
|
||||
'description': 'Length of the subnet mask for IPv6 addresses.'
|
||||
'type': 'integer'
|
||||
'default': 56
|
||||
'minimum': 0
|
||||
'maximum': 128
|
||||
'ratelimit_whitelist':
|
||||
'type': 'array'
|
||||
'description': 'List of IP addresses excluded from rate limiting.'
|
||||
'items':
|
||||
'type': 'string'
|
||||
'blocking_mode':
|
||||
'type': 'string'
|
||||
'enum':
|
||||
@@ -2667,6 +2684,25 @@
|
||||
If `ignore_statistics` is not set in HTTP API `GET /clients/update`
|
||||
request then the existing value will not be changed.
|
||||
|
||||
This behaviour can be changed in the future versions.
|
||||
'type': 'boolean'
|
||||
'upstreams_cache_enabled':
|
||||
'description': |
|
||||
NOTE: If `upstreams_cache_enabled` is not set in HTTP API
|
||||
`GET /clients/add` request then default value (false) will be used.
|
||||
|
||||
If `upstreams_cache_enabled` is not set in HTTP API
|
||||
`GET /clients/update` request then the existing value will not be
|
||||
changed.
|
||||
|
||||
This behaviour can be changed in the future versions.
|
||||
'type': 'boolean'
|
||||
'upstreams_cache_size':
|
||||
'description': |
|
||||
NOTE: If `upstreams_cache_enabled` is not set in HTTP API
|
||||
`GET /clients/update` request then the existing value will not be
|
||||
changed.
|
||||
|
||||
This behaviour can be changed in the future versions.
|
||||
'type': 'boolean'
|
||||
'ClientAuto':
|
||||
|
||||
@@ -185,7 +185,6 @@ run_linter gocognit --over='18'\
|
||||
|
||||
run_linter gocognit --over='15'\
|
||||
./internal/aghos/\
|
||||
./internal/dnsforward/\
|
||||
./internal/filtering/\
|
||||
;
|
||||
|
||||
@@ -198,10 +197,13 @@ run_linter gocognit --over='13'\
|
||||
;
|
||||
|
||||
run_linter gocognit --over='12'\
|
||||
./internal/updater/\
|
||||
./internal/filtering/rewrite/\
|
||||
;
|
||||
|
||||
run_linter gocognit --over='11'\
|
||||
./internal/updater/\
|
||||
;
|
||||
|
||||
run_linter gocognit --over='10'\
|
||||
./internal/aghalg/\
|
||||
./internal/aghchan/\
|
||||
@@ -212,6 +214,7 @@ run_linter gocognit --over='10'\
|
||||
./internal/client/\
|
||||
./internal/confmigrate/\
|
||||
./internal/dhcpsvc\
|
||||
./internal/dnsforward/\
|
||||
./internal/filtering/hashprefix/\
|
||||
./internal/filtering/rulelist/\
|
||||
./internal/filtering/safesearch/\
|
||||
|
||||
Reference in New Issue
Block a user