Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f68f6c9b37 | ||
|
|
dbcc55f528 | ||
|
|
66b549a565 | ||
|
|
a1c9e9d36b | ||
|
|
ab81ff03f6 | ||
|
|
d295621352 | ||
|
|
aebcd74efe | ||
|
|
7d1ca48ae4 | ||
|
|
8a0bc5468b | ||
|
|
e272e19516 | ||
|
|
10f03b7527 | ||
|
|
0d44822c43 | ||
|
|
e83b919dbd | ||
|
|
2eb21ef409 | ||
|
|
7b014082ab | ||
|
|
6b8a46ef3b | ||
|
|
a623ac694b | ||
|
|
7dd2d0af96 | ||
|
|
7e08565212 | ||
|
|
841bb9bc35 | ||
|
|
f016ae172c | ||
|
|
44168292d5 | ||
|
|
e64df20e74 | ||
|
|
890f0322d8 |
12
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
12
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -1,11 +1,9 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report to help us improve AdGuard Home
|
||||
|
||||
---
|
||||
|
||||
<!-- As an open-source project with a dedicated but small maintainer team, it can sometimes take a long time for issues to be addressed so please be patient and we will get back to you as soon as we can.
|
||||
-->
|
||||
<!-- As an open-source project with a dedicated but small maintainer team, it can sometimes take a long time for issues to be addressed so please be patient and we will get back to you as soon as we can. -->
|
||||
|
||||
### Prerequisites
|
||||
|
||||
@@ -17,16 +15,18 @@ Please answer the following questions for yourself before submitting an issue. *
|
||||
|
||||
### Issue Details
|
||||
|
||||
<!--- Please include all relevant details about the environment you experienced the bug in -->
|
||||
<!-- Please include all relevant details about the environment you experienced the bug in. -->
|
||||
|
||||
* **Version of AdGuard Home server:**
|
||||
* <!-- (e.g. v1.0) -->
|
||||
* <!-- (e.g. v0.123.4) -->
|
||||
* **How did you install AdGuard Home:**
|
||||
* <!-- (e.g. Snapcraft, Docker, Github releases) -->
|
||||
* <!-- (e.g. Built from source, Snapcraft, Docker, Github releases, etc.) -->
|
||||
* **How did you setup DNS configuration:**
|
||||
* <!-- (System/Router/IoT) -->
|
||||
* **If it's a router or IoT, please write device model:**
|
||||
* <!-- (e.g. Raspberry Pi 3 Model B) -->
|
||||
* **CPU architecture:**
|
||||
* <!-- (e.g. AMD64, MIPS, etc.) -->
|
||||
* **Operating system and version:**
|
||||
* <!-- (e.g. Ubuntu 18.04.1) -->
|
||||
|
||||
|
||||
56
CHANGELOG.md
56
CHANGELOG.md
@@ -13,6 +13,53 @@ and this project adheres to
|
||||
## [v0.106.0] - 2021-04-26
|
||||
-->
|
||||
|
||||
<!--
|
||||
## [v0.105.2] - 2021-02-24
|
||||
-->
|
||||
|
||||
## [v0.105.1] - 2021-02-15
|
||||
|
||||
### Changed
|
||||
|
||||
- Increased HTTP API timeouts ([#2671], [#2682]).
|
||||
- "Permission denied" errors when checking if the machine has a static IP no
|
||||
longer prevent the DHCP server from starting ([#2667]).
|
||||
- The server name sent by clients of TLS APIs is not only checked when
|
||||
`strict_sni_check` is enabled ([#2664]).
|
||||
- HTTP API request body size limit for the `POST /control/access/set` and `POST
|
||||
/control/filtering/set_rules` HTTP APIs is increased ([#2666], [#2675]).
|
||||
|
||||
### Fixed
|
||||
|
||||
- Error when enabling the DHCP server when AdGuard Home couldn't determine if
|
||||
the machine has a static IP.
|
||||
- Optical issue on custom rules ([#2641]).
|
||||
- Occasional crashes during startup.
|
||||
- The field `"range_start"` in the `GET /control/dhcp/status` HTTP API response
|
||||
is now correctly named again ([#2678]).
|
||||
- DHCPv6 server's `ra_slaac_only` and `ra_allow_slaac` settings aren't reset to
|
||||
`false` on update any more ([#2653]).
|
||||
- The `Vary` header is now added along with `Access-Control-Allow-Origin` to
|
||||
prevent cache-related and other issues in browsers ([#2658]).
|
||||
- The request body size limit is now set for HTTPS requests as well.
|
||||
- Incorrect version tag in the Docker release ([#2663]).
|
||||
- DNSCrypt queries weren't marked as such in logs ([#2662]).
|
||||
|
||||
[#2641]: https://github.com/AdguardTeam/AdGuardHome/issues/2641
|
||||
[#2653]: https://github.com/AdguardTeam/AdGuardHome/issues/2653
|
||||
[#2658]: https://github.com/AdguardTeam/AdGuardHome/issues/2658
|
||||
[#2662]: https://github.com/AdguardTeam/AdGuardHome/issues/2662
|
||||
[#2663]: https://github.com/AdguardTeam/AdGuardHome/issues/2663
|
||||
[#2664]: https://github.com/AdguardTeam/AdGuardHome/issues/2664
|
||||
[#2666]: https://github.com/AdguardTeam/AdGuardHome/issues/2666
|
||||
[#2667]: https://github.com/AdguardTeam/AdGuardHome/issues/2667
|
||||
[#2671]: https://github.com/AdguardTeam/AdGuardHome/issues/2671
|
||||
[#2675]: https://github.com/AdguardTeam/AdGuardHome/issues/2675
|
||||
[#2678]: https://github.com/AdguardTeam/AdGuardHome/issues/2678
|
||||
[#2682]: https://github.com/AdguardTeam/AdGuardHome/issues/2682
|
||||
|
||||
|
||||
|
||||
## [v0.105.0] - 2021-02-10
|
||||
|
||||
### Added
|
||||
@@ -151,9 +198,12 @@ and this project adheres to
|
||||
|
||||
|
||||
<!--
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.0...HEAD
|
||||
[v0.105.0]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...v0.105.0
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.2...HEAD
|
||||
[v0.105.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.1...v0.105.2
|
||||
-->
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...HEAD
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.1...HEAD
|
||||
[v0.105.1]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.0...v0.105.1
|
||||
[v0.105.0]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...v0.105.0
|
||||
[v0.104.3]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.2...v0.104.3
|
||||
[v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.1...v0.104.2
|
||||
|
||||
@@ -155,7 +155,7 @@ It depends.
|
||||
|
||||
"DNS sinkholing" is capable of blocking a big percentage of ads, but it lacks flexibility and power of traditional ad blockers. You can get a good impression about the difference between these methods by reading [this article](https://adguard.com/en/blog/adguard-vs-adaway-dns66/). It compares AdGuard for Android (a traditional ad blocker) to hosts-level ad blockers (which are almost identical to DNS-based blockers in their capabilities).
|
||||
|
||||
However, this level of protection is enough for some users. Additionally, using a DNS-based blocker can help to block ads, tracking and analytics requests on other types of devices, such as SmartTVs, smart speakers or other kinds of IoT devices (on which you can't install tradtional ad blockers).
|
||||
However, this level of protection is enough for some users. Additionally, using a DNS-based blocker can help to block ads, tracking and analytics requests on other types of devices, such as SmartTVs, smart speakers or other kinds of IoT devices (on which you can't install traditional ad blockers).
|
||||
|
||||
**Known limitations**
|
||||
|
||||
@@ -192,6 +192,12 @@ cd AdGuardHome
|
||||
make
|
||||
```
|
||||
|
||||
Please note, that the non-standard `-j` flag is currently not supported, so
|
||||
building with `make -j 4` or setting your `MAKEFLAGS` to include, for example,
|
||||
`-j 4` is likely to break the build. If you do have your `MAKEFLAGS` set to
|
||||
that, and you don't want to change it, you can override it by running
|
||||
`make -j 1`.
|
||||
|
||||
Check the [`Makefile`](https://github.com/AdguardTeam/AdGuardHome/blob/master/Makefile) to learn about other commands.
|
||||
|
||||
**Building for a different platform.** You can build AdGuard for any OS/ARCH just like any other Go project.
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"form_error_ip_format": "Няслушны фармат IP-адраса",
|
||||
"form_error_mac_format": "Некарэктны фармат MAC",
|
||||
"form_error_client_id_format": "Няслушны фармат ID кліента",
|
||||
"form_error_server_name": "Няслушнае імя сервера",
|
||||
"form_error_positive": "Павінна быць больш 0",
|
||||
"form_error_negative": "Павінна быць не менш 0",
|
||||
"range_end_error": "Павінен перавышаць пачатак дыяпазону",
|
||||
@@ -247,10 +248,16 @@
|
||||
"custom_ip": "Свой IP",
|
||||
"blocking_ipv4": "Блакаванне IPv4",
|
||||
"blocking_ipv6": "Блакаванне IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
"client_id": "Ідэнтыфікатар кліента",
|
||||
"client_id_placeholder": "Увядзіце ідэнтыфікатар кліента",
|
||||
"client_id_desc": "Розныя кліенты могуць ідэнтыфікавацца па адмысловым ідэнтыфікатары кліента. <a>Тут</a> вы можаце даведацца больш пра ідэнтыфікацыю кліентаў.",
|
||||
"download_mobileconfig_doh": "Спампаваць .mobileconfig для DNS-over-HTTPS",
|
||||
"download_mobileconfig_dot": "Спампаваць .mobileconfig для DNS-over-TLS",
|
||||
"download_mobileconfig": "Загрузіць файл канфігурацыі",
|
||||
"plain_dns": "Нешыфраваны DNS",
|
||||
"form_enter_rate_limit": "Увядзіце rate limit",
|
||||
"rate_limit": "Ограничение скорости",
|
||||
@@ -269,6 +276,7 @@
|
||||
"source_label": "Крыніца",
|
||||
"found_in_known_domain_db": "Знойдзены ў базе вядомых даменаў.",
|
||||
"category_label": "Катэгорыя",
|
||||
"rule_label": "Правіла(ы)",
|
||||
"list_label": "Спіс",
|
||||
"unknown_filter": "Невядомы фільтр {{filterId}}",
|
||||
"known_tracker": "Вядомы трэкер",
|
||||
@@ -329,6 +337,7 @@
|
||||
"encryption_config_saved": "Налады шыфравання захаваны",
|
||||
"encryption_server": "Імя сервера",
|
||||
"encryption_server_enter": "Увядзіце ваша даменавае імя",
|
||||
"encryption_server_desc": "Для выкарыстання HTTPS вам трэба ўвесці імя сервера, якое падыходзіць вашаму SSL-сертыфікату.",
|
||||
"encryption_redirect": "Аўтаматычна перанакіроўваць на HTTPS",
|
||||
"encryption_redirect_desc": "Калі ўлучана, AdGuard Home будзе аўтаматычна перанакіроўваць вас з HTTP на HTTPS адрас.",
|
||||
"encryption_https": "Порт HTTPS",
|
||||
@@ -384,6 +393,7 @@
|
||||
"client_edit": "Рэдагаваць кліента",
|
||||
"client_identifier": "Ідэнтыфікатар",
|
||||
"ip_address": "IP-адрас",
|
||||
"client_identifier_desc": "Кліенты могуць быць ідэнтыфікаваны па IP-адрасе, CIDR ці MAC-адрасу. Звярніце ўвагу, што выкарыстанне MAC як ідэнтыфікатара магчыма, толькі калі AdGuard Home таксама з'яўляецца і <0>DHCP-серверам</0>",
|
||||
"form_enter_ip": "Увядзіце IP",
|
||||
"form_enter_mac": "Увядзіце MAC",
|
||||
"form_enter_id": "Увядзіце ідэнтыфікатар",
|
||||
@@ -427,6 +437,7 @@
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> падтрымвае <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> падтрымвае <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "Вы можаце знайсці яшчэ варыянты <0>тут</0> і <1>тут</1>.",
|
||||
"setup_dns_privacy_ioc_mac": "Канфігурацыя для iOS і macOS",
|
||||
"setup_dns_notice": "Каб выкарыстоўваць <1>DNS-over-HTTPS</1> ці <1>DNS-over-TLS</1>, вам патрэбна <0>наладзіць шыфраванне</0> у наладах AdGuard Home.",
|
||||
"rewrite_added": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова дададзена",
|
||||
"rewrite_deleted": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова выдалена",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "Vlastní IP",
|
||||
"blocking_ipv4": "Blokování IPv4",
|
||||
"blocking_ipv6": "Blokování IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS přes HTTPS",
|
||||
"dns_over_tls": "DNS přes TLS",
|
||||
"dns_over_quic": "DNS skrze QUIC",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "Tilpasset IP",
|
||||
"blocking_ipv4": "IPv4-blokering",
|
||||
"blocking_ipv6": "IPv6-blokering",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-Quic",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "Benutzerdefinierte IP",
|
||||
"blocking_ipv4": "IPv4-Sperren",
|
||||
"blocking_ipv6": "IPv6-Sperren",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS (DNS-Abrage über HTTPS)",
|
||||
"dns_over_tls": "DNS-over-TLS (DNS-Abrage über TLS)",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "Custom IP",
|
||||
"blocking_ipv4": "Blocking IPv4",
|
||||
"blocking_ipv6": "Blocking IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
|
||||
@@ -248,15 +248,16 @@
|
||||
"custom_ip": "IP personalizada",
|
||||
"blocking_ipv4": "Bloqueo de IPv4",
|
||||
"blocking_ipv6": "Bloqueo de IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS mediante HTTPS",
|
||||
"dns_over_tls": "DNS mediante TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
"dns_over_quic": "DNS mediante QUIC",
|
||||
"client_id": "ID de cliente",
|
||||
"client_id_placeholder": "Ingresa tu ID de cliente",
|
||||
"client_id_desc": "Varios clientes se pueden identificar mediante un ID de cliente especial. <a>Aquí</a> puede aprender más sobre cómo identificar clientes.",
|
||||
"client_id_placeholder": "Ingresa el ID del cliente",
|
||||
"client_id_desc": "Diferentes clientes pueden ser identificados por un ID de cliente especial. <a>Aquí</a> puedes obtener más información sobre cómo identificar clientes.",
|
||||
"download_mobileconfig_doh": "Descargar .mobileconfig para DNS mediante HTTPS",
|
||||
"download_mobileconfig_dot": "Descargar .mobileconfig para DNS mediante TLS",
|
||||
"download_mobileconfig": "Descargar el archivo de configuración",
|
||||
"download_mobileconfig": "Descargar archivo de configuración",
|
||||
"plain_dns": "DNS simple",
|
||||
"form_enter_rate_limit": "Ingresa el límite de cantidad",
|
||||
"rate_limit": "Límite de cantidad",
|
||||
@@ -275,7 +276,7 @@
|
||||
"source_label": "Fuente",
|
||||
"found_in_known_domain_db": "Encontrado en la base de datos de dominios conocidos.",
|
||||
"category_label": "Categoría",
|
||||
"rule_label": "Regla(s)",
|
||||
"rule_label": "Regla",
|
||||
"list_label": "Lista",
|
||||
"unknown_filter": "Filtro desconocido {{filterId}}",
|
||||
"known_tracker": "Rastreador conocido",
|
||||
@@ -336,7 +337,7 @@
|
||||
"encryption_config_saved": "Configuración de cifrado guardado",
|
||||
"encryption_server": "Nombre del servidor",
|
||||
"encryption_server_enter": "Ingresa el nombre del dominio",
|
||||
"encryption_server_desc": "Para utilizar HTTPS, debes ingresar el nombre del servidor que coincida con tu certificado SSL o certificado Wildcard. Si el campo no está establecido, el servidor aceptará conexiones TLS para cualquier dominio.",
|
||||
"encryption_server_desc": "Para utilizar HTTPS, debes ingresar el nombre del servidor que coincida con tu certificado SSL o certificado comodín. Si el campo no está establecido, el servidor aceptará conexiones TLS para cualquier dominio.",
|
||||
"encryption_redirect": "Redireccionar a HTTPS automáticamente",
|
||||
"encryption_redirect_desc": "Si está marcado, AdGuard Home redireccionará automáticamente de HTTP a las direcciones HTTPS.",
|
||||
"encryption_https": "Puerto HTTPS",
|
||||
@@ -392,7 +393,7 @@
|
||||
"client_edit": "Editar cliente",
|
||||
"client_identifier": "Identificador",
|
||||
"ip_address": "Dirección IP",
|
||||
"client_identifier_desc": "Los clientes pueden ser identificados por la dirección IP, MAC, CIDR o un especial ID de cliente (puede ser utilizado para DoT/DoH/DoQ). <0>Aquí</0> puede obtener más información sobre cómo identificar clientes.",
|
||||
"client_identifier_desc": "Los clientes pueden ser identificados por la dirección IP, MAC, CIDR o un ID de cliente especial (puede ser utilizado para DoT/DoH/DoQ). <0>Aquí</0> puedes obtener más información sobre cómo identificar clientes.",
|
||||
"form_enter_ip": "Ingresa la IP",
|
||||
"form_enter_mac": "Ingresa la MAC",
|
||||
"form_enter_id": "Ingresa el identificador",
|
||||
@@ -436,7 +437,7 @@
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> soporta <1>DNS mediante HTTPS</1>.",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> soporta <1>DNS mediante HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "Encontrarás más implementaciones <0>aquí</0> y <1>aquí</1>.",
|
||||
"setup_dns_privacy_ioc_mac": "La configuración de iOS y macOS ",
|
||||
"setup_dns_privacy_ioc_mac": "Configuración de iOS y macOS",
|
||||
"setup_dns_notice": "Para utilizar <1>DNS mediante HTTPS</1> o <1>DNS mediante TLS</1>, debes <0>configurar el cifrado</0> en la configuración de AdGuard Home.",
|
||||
"rewrite_added": "Reescritura DNS para \"{{key}}\" añadido correctamente",
|
||||
"rewrite_deleted": "Reescritura DNS para \"{{key}}\" eliminado correctamente",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "IP personnalisée",
|
||||
"blocking_ipv4": "Blocage IPv4",
|
||||
"blocking_ipv6": "Blocage IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"form_error_ip_format": "Nevažeći format IP adrese",
|
||||
"form_error_mac_format": "Nevažeći MAC format",
|
||||
"form_error_client_id_format": "Nevažeći format ID-a klijenta",
|
||||
"form_error_server_name": "Nevažeće ime poslužitelja",
|
||||
"form_error_positive": "Mora biti veće od 0",
|
||||
"form_error_negative": "Mora biti jednako ili veće od 0",
|
||||
"range_end_error": "Mora biti veće od početne vrijednosti raspona",
|
||||
@@ -247,10 +248,16 @@
|
||||
"custom_ip": "Prilagođen IP",
|
||||
"blocking_ipv4": "Blokiranje IPv4",
|
||||
"blocking_ipv6": "Blokiranje IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-Quic",
|
||||
"client_id": "ID klijenta",
|
||||
"client_id_placeholder": "Unesite ID klijenta",
|
||||
"client_id_desc": "Razni klijenti mogu biti prepoznati po specijalnom identifikatoru. <a>Ovdje</a> možete saznati više kako možete identificirati klijente.",
|
||||
"download_mobileconfig_doh": "Preuzmi .mobileconfig za DNS-over-HTTPS",
|
||||
"download_mobileconfig_dot": "Preuzmi .mobileconfig za DNS-over-TLS",
|
||||
"download_mobileconfig": "Preuzmite konfiguracijsku datoteku",
|
||||
"plain_dns": "Obični DNS",
|
||||
"form_enter_rate_limit": "Unesite ograničenje",
|
||||
"rate_limit": "Ograničenje",
|
||||
@@ -269,6 +276,7 @@
|
||||
"source_label": "Izvor",
|
||||
"found_in_known_domain_db": "Pronađeno u bazi poznatih domena.",
|
||||
"category_label": "Kategorija",
|
||||
"rule_label": "Pravilo",
|
||||
"list_label": "Popis",
|
||||
"unknown_filter": "Nepoznati filtar {{filterId}}",
|
||||
"known_tracker": "Poznati pratitelj",
|
||||
@@ -329,6 +337,7 @@
|
||||
"encryption_config_saved": "Spremljene postavke šifriranja",
|
||||
"encryption_server": "Naziv poslužitelja",
|
||||
"encryption_server_enter": "Unesite naziv domene",
|
||||
"encryption_server_desc": "Kako biste koristili HTTPS, morate unijeti naziv poslužitelja koji odgovara vašem SSL certifikatu.",
|
||||
"encryption_redirect": "Automatski preusmjeri na HTTPS",
|
||||
"encryption_redirect_desc": "Ako je omogućeno, AdGuard Home će vas automatski preusmjeravati s HTTP na HTTPS adrese.",
|
||||
"encryption_https": "HTTPS port",
|
||||
@@ -384,6 +393,7 @@
|
||||
"client_edit": "Uredi klijenta",
|
||||
"client_identifier": "Identifikator",
|
||||
"ip_address": "IP adresa",
|
||||
"client_identifier_desc": "Klijenti se mogu prepoznati po IP adresi, CIDR-u ili MAC adresi. Imajte na umu da je upotreba MAC-a kao identifikatora, moguća samo ako je AdGuard Home također i <0>DHCP poslužitelj</0>",
|
||||
"form_enter_ip": "Unesite IP adresu",
|
||||
"form_enter_mac": "Unesite MAC adresu",
|
||||
"form_enter_id": "Unesi identifikator",
|
||||
@@ -427,6 +437,7 @@
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podržava <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podržava <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "Možete pronaći više implementacija <0>ovdje</0> i <1>ovdje</1>.",
|
||||
"setup_dns_privacy_ioc_mac": "konfiguracija za iOS i macOS",
|
||||
"setup_dns_notice": "Da biste koristili <1>DNS-over-HTTPS</1> ili <1>DNS-over-TLS</1>, morate <0>postaviti šifriranje</0> u AdGuard Home postavkama.",
|
||||
"rewrite_added": "DNS prijepis za \"{{key}}\" je uspješno dodan",
|
||||
"rewrite_deleted": "DNS prijepis za \"{{key}}\" je uspješno uklonjen",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "IP personalizzato",
|
||||
"blocking_ipv4": "Blocca IPv4",
|
||||
"blocking_ipv6": "Blocca IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS su HTTPS",
|
||||
"dns_over_tls": "DNS su TLS",
|
||||
"dns_over_quic": "DNS su Quic",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "カスタムIP",
|
||||
"blocking_ipv4": "ブロック中のIPv4",
|
||||
"blocking_ipv6": "ブロック中のIPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "사용자 지정 IP",
|
||||
"blocking_ipv4": "IPv4 차단",
|
||||
"blocking_ipv6": "IPv6 차단",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "Aangepast IP",
|
||||
"blocking_ipv4": "Blokkeren IP4",
|
||||
"blocking_ipv6": "Blokkeren IP6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-via-HTTPS",
|
||||
"dns_over_tls": "DNS-via-TLS",
|
||||
"dns_over_quic": "DNS-via-QUIC",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"form_error_ip_format": "Ugyldig IPv4-format",
|
||||
"form_error_mac_format": "Ugyldig MAC-format",
|
||||
"form_error_client_id_format": "Ugyldig ID-klientformat",
|
||||
"form_error_server_name": "Ugyldig tjenernavn",
|
||||
"form_error_positive": "Må være høyere enn 0",
|
||||
"form_error_negative": "Må være ≥0",
|
||||
"range_end_error": "Må være høyere enn rekkeviddens start",
|
||||
@@ -247,8 +248,11 @@
|
||||
"custom_ip": "Tilpasset IP",
|
||||
"blocking_ipv4": "IPv4-blokkering",
|
||||
"blocking_ipv6": "IPv6-blokkering",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
"client_id": "Klient-ID",
|
||||
"download_mobileconfig_doh": "Last ned .mobileconfig for DNS-over-HTTPS",
|
||||
"download_mobileconfig_dot": "Last ned .mobileconfig for DNS-over-TLS",
|
||||
"plain_dns": "Ordinær DNS",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "Niestandardowy adres IP",
|
||||
"blocking_ipv4": "Blokowanie IPv4",
|
||||
"blocking_ipv6": "Blokowanie IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "IP personalizado",
|
||||
"blocking_ipv4": "Bloqueando IPv4",
|
||||
"blocking_ipv6": "Bloqueando IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-sobre-HTTPS",
|
||||
"dns_over_tls": "DNS-sobre-TLS",
|
||||
"dns_over_quic": "DNS-sobre-QUIC",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "IP Personalizado",
|
||||
"blocking_ipv4": "A bloquear IPv4",
|
||||
"blocking_ipv6": "A bloquear IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-sobre-HTTPS",
|
||||
"dns_over_tls": "DNS-sobre-TLS",
|
||||
"dns_over_quic": "DNS-sobre-QUIC",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "IP personalizat",
|
||||
"blocking_ipv4": "Blocarea IPv4",
|
||||
"blocking_ipv6": "Blocarea IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "Свой IP",
|
||||
"blocking_ipv4": "Блокировка IPv4",
|
||||
"blocking_ipv6": "Блокировка IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
"enabled_save_search_toast": "ආරක්ෂිත සෙවීම සබල කර ඇත",
|
||||
"enabled_table_header": "සබල කර ඇත",
|
||||
"name_table_header": "නම",
|
||||
"list_url_table_header": "URL ලැයිස්තුව",
|
||||
"list_url_table_header": "ඒ.ස.නි.(URL) ලැයිස්තුව",
|
||||
"rules_count_table_header": "නීති ගණන",
|
||||
"last_time_updated_table_header": "අවසන් වරට යාවත්කාලීන කරන ලද",
|
||||
"actions_table_header": "ක්රියාමාර්ග",
|
||||
@@ -134,7 +134,7 @@
|
||||
"add_allowlist": "අවසර දීමේ ලැයිස්තුවක් එකතු කරන්න",
|
||||
"cancel_btn": "අහෝසි කරන්න",
|
||||
"enter_name_hint": "නම ඇතුළත් කරන්න",
|
||||
"enter_url_or_path_hint": "ලැයිස්තුවක URL හෝ ස්ථීර මාර්ගයක් ඇතුළත් කරන්න",
|
||||
"enter_url_or_path_hint": "ලැයිස්තුවක ඒ.ස.නි.(URL) හෝ ස්ථීර මාර්ගයක් ඇතුළත් කරන්න",
|
||||
"check_updates_btn": "යාවත්කාල පරීක්ෂා කරන්න",
|
||||
"new_blocklist": "නව අවහිර කිරීමේ ලැයිස්තුව",
|
||||
"new_allowlist": "නව අවසර දීමේ ලැයිස්තුව",
|
||||
@@ -142,10 +142,10 @@
|
||||
"edit_allowlist": "අවසර දීමේ ලැයිස්තුව සංස්කරණය කරන්න",
|
||||
"choose_blocklist": "අවහිර කීරීමේ ලැයිස්තුවක් තෝරන්න",
|
||||
"choose_allowlist": "අනවහිර කීරීමේ ලැයිස්තුවක් තෝරන්න",
|
||||
"enter_valid_blocklist": "අවහිර කිරීමේ ලැයිස්තුවට වලංගු URL ලිපිනයක් ඇතුළත් කරන්න.",
|
||||
"enter_valid_allowlist": "අවසර දීමේ ලැයිස්තුවට වලංගු URL ලිපිනයක් ඇතුළත් කරන්න.",
|
||||
"form_error_url_format": "වලංගු නොවන URL ආකෘතියකි",
|
||||
"form_error_url_or_path_format": "ලැයිස්තුවක වලංගු නොවන URL හෝ ස්ථීර මාර්ගයකි",
|
||||
"enter_valid_blocklist": "අවහිර කිරීමේ ලැයිස්තුවට වලංගු ඒ.ස.නි.(URL) ලිපිනයක් ඇතුළත් කරන්න.",
|
||||
"enter_valid_allowlist": "අවසර දීමේ ලැයිස්තුවට වලංගු ඒ.ස.නි.(URL) ලිපිනයක් ඇතුළත් කරන්න.",
|
||||
"form_error_url_format": "වලංගු නොවන ඒ.ස.නි.(URL) ආකෘතියකි",
|
||||
"form_error_url_or_path_format": "ලැයිස්තුවක වලංගු නොවන ඒ.ස.නි.(URL) හෝ ස්ථීර මාර්ගයකි",
|
||||
"custom_filter_rules": "අභිරුචි පෙරීමේ නීති",
|
||||
"custom_filter_rules_hint": "පේළියකට එක් නීතියක් බැගින් ඇතුළත් කරන්න. ඔබට දැන්වීම් අවහිර කිරීමේ නීති හෝ ධාරක ගොනු පද ගැලපුම් භාවිතා කළ හැකිය.",
|
||||
"examples_title": "උදාහරණ",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "Vlastná IP adresa",
|
||||
"blocking_ipv4": "Blokovanie IPv4",
|
||||
"blocking_ipv6": "Blokovanie IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "IP po meri",
|
||||
"blocking_ipv4": "Onemogočanje IPv4",
|
||||
"blocking_ipv6": "Onemogočanje IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-prek-HTTPS",
|
||||
"dns_over_tls": "DNS-prek-TLS",
|
||||
"dns_over_quic": "DNS-prek-QIUC",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "Özel IP",
|
||||
"blocking_ipv4": "IPv4 engelleme",
|
||||
"blocking_ipv6": "IPv6 engelleme",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "IP tuỳ chỉnh",
|
||||
"blocking_ipv4": "Chặn IPv4",
|
||||
"blocking_ipv6": "Chặn IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"custom_ip": "自定义 IP",
|
||||
"blocking_ipv4": "拦截 IPv4",
|
||||
"blocking_ipv6": "拦截 IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"client_settings": "用戶端設定",
|
||||
"example_upstream_reserved": "您可以<0>為特定網域</0>指定上游 DNS",
|
||||
"example_upstream_reserved": "您可<0>對於特定的網域</0>明確指定 DNS 上游",
|
||||
"example_upstream_comment": "您可明確指定註解",
|
||||
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析網域",
|
||||
"parallel_requests": "並行的請求",
|
||||
@@ -32,6 +32,7 @@
|
||||
"form_error_ip_format": "無效的 IP 格式",
|
||||
"form_error_mac_format": "無效的媒體存取控制(MAC)格式",
|
||||
"form_error_client_id_format": "無效的用戶端 ID 格式",
|
||||
"form_error_server_name": "無效的伺服器名稱",
|
||||
"form_error_positive": "必須大於 0",
|
||||
"form_error_negative": "必須等於或大於 0",
|
||||
"range_end_error": "必須大於起始範圍",
|
||||
@@ -247,10 +248,16 @@
|
||||
"custom_ip": "自訂的 IP",
|
||||
"blocking_ipv4": "封鎖 IPv4",
|
||||
"blocking_ipv6": "封鎖 IPv6",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
"client_id": "用戶端 ID",
|
||||
"client_id_placeholder": "輸入用戶端 ID",
|
||||
"client_id_desc": "不同的用戶端可根據特殊的用戶端 ID 被識別。<0>於此</0>,您可了解更多關於如何識別用戶端。",
|
||||
"download_mobileconfig_doh": "下載用於 DNS-over-HTTPS 的 .mobileconfig",
|
||||
"download_mobileconfig_dot": "下載用於 DNS-over-TLS 的 .mobileconfig",
|
||||
"download_mobileconfig": "下載配置檔案",
|
||||
"plain_dns": "一般的 DNS",
|
||||
"form_enter_rate_limit": "輸入速率限制",
|
||||
"rate_limit": "速率限制",
|
||||
@@ -386,6 +393,7 @@
|
||||
"client_edit": "編輯用戶端",
|
||||
"client_identifier": "識別碼",
|
||||
"ip_address": "IP 位址",
|
||||
"client_identifier_desc": "用戶端可根據 IP 位址、無類別網域間路由(CIDR)、媒體存取控制(MAC)位址或特殊的用戶端 ID(可被用於 DoT/DoH/DoQ)被識別。<0>於此</0>,您可了解更多關於如何識別用戶端。",
|
||||
"form_enter_ip": "輸入 IP",
|
||||
"form_enter_mac": "輸入媒體存取控制(MAC)",
|
||||
"form_enter_id": "輸入識別碼",
|
||||
@@ -429,6 +437,7 @@
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> 支援 <1>DNS-over-HTTPS</1>。",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> 支援 <1>DNS-over-HTTPS</1>。",
|
||||
"setup_dns_privacy_other_5": "在<0>這裡</0>和<1>這裡</1>,您將發現更多的執行。",
|
||||
"setup_dns_privacy_ioc_mac": "iOS 和 macOS 配置",
|
||||
"setup_dns_notice": "為了使用 <1>DNS-over-HTTPS</1> 或 <1>DNS-over-TLS</1>,您需要在 AdGuard Home 設定裡<0>配置加密</0>。",
|
||||
"rewrite_added": "對於 \"{{key}}\" 之 DNS 改寫被成功地加入",
|
||||
"rewrite_deleted": "對於 \"{{key}}\" 之 DNS 改寫被成功地刪除",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
--gray-d8: #d8d8d8;
|
||||
--gray-f3: #f3f3f3;
|
||||
--font-family-monospace: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
|
||||
--font-size-disable-autozoom: 1rem;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -13,9 +14,10 @@ body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Disable Auto Zoom in Input - Safari on iPhone https://stackoverflow.com/a/6394497 */
|
||||
@media screen and (max-width: 767px) {
|
||||
input, select, textarea {
|
||||
font-size: 16px !important;
|
||||
font-size: var(--font-size-disable-autozoom);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.5rem;
|
||||
word-wrap: break-word;
|
||||
font-size: 0.9375rem;
|
||||
font-size: var(--font-size-disable-autozoom);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -534,6 +534,7 @@ export const BLOCK_ACTIONS = {
|
||||
};
|
||||
|
||||
export const SCHEME_TO_PROTOCOL_MAP = {
|
||||
dnscrypt: 'dnscrypt',
|
||||
doh: 'dns_over_https',
|
||||
dot: 'dns_over_tls',
|
||||
doq: 'dns_over_quic',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* Disable Auto Zoom in Input - Safari on iPhone https://stackoverflow.com/a/6394497 */
|
||||
@media screen and (max-width: 767px) {
|
||||
input, select, textarea {
|
||||
font-size: 16px !important;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* Disable Auto Zoom in Input - Safari on iPhone https://stackoverflow.com/a/6394497 */
|
||||
@media screen and (max-width: 767px) {
|
||||
input, select, textarea {
|
||||
font-size: 16px !important;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLimitReadCloser(t *testing.T) {
|
||||
@@ -78,11 +79,11 @@ func TestLimitedReadCloser_Read(t *testing.T) {
|
||||
buf := make([]byte, tc.limit+1)
|
||||
|
||||
lreader, err := LimitReadCloser(readCloser, tc.limit)
|
||||
assert.Nil(t, err)
|
||||
require.Nil(t, err)
|
||||
|
||||
n, err := lreader.Read(buf)
|
||||
assert.Equal(t, n, tc.want)
|
||||
assert.Equal(t, tc.err, err)
|
||||
require.Equal(t, tc.err, err)
|
||||
assert.Equal(t, tc.want, n)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,14 +117,14 @@ type ServerInterface interface {
|
||||
}
|
||||
|
||||
// Create - create object
|
||||
func Create(config ServerConfig) *Server {
|
||||
func Create(conf ServerConfig) *Server {
|
||||
s := &Server{}
|
||||
|
||||
s.conf.Enabled = config.Enabled
|
||||
s.conf.InterfaceName = config.InterfaceName
|
||||
s.conf.HTTPRegister = config.HTTPRegister
|
||||
s.conf.ConfigModified = config.ConfigModified
|
||||
s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename)
|
||||
s.conf.Enabled = conf.Enabled
|
||||
s.conf.InterfaceName = conf.InterfaceName
|
||||
s.conf.HTTPRegister = conf.HTTPRegister
|
||||
s.conf.ConfigModified = conf.ConfigModified
|
||||
s.conf.DBFilePath = filepath.Join(conf.WorkDir, dbFilename)
|
||||
|
||||
if !webHandlersRegistered && s.conf.HTTPRegister != nil {
|
||||
if runtime.GOOS == "windows" {
|
||||
@@ -145,7 +145,7 @@ func Create(config ServerConfig) *Server {
|
||||
}
|
||||
|
||||
var err4, err6 error
|
||||
v4conf := config.Conf4
|
||||
v4conf := conf.Conf4
|
||||
v4conf.Enabled = s.conf.Enabled
|
||||
if len(v4conf.RangeStart) == 0 {
|
||||
v4conf.Enabled = false
|
||||
@@ -154,7 +154,7 @@ func Create(config ServerConfig) *Server {
|
||||
v4conf.notify = s.onNotify
|
||||
s.srv4, err4 = v4Create(v4conf)
|
||||
|
||||
v6conf := config.Conf6
|
||||
v6conf := conf.Conf6
|
||||
v6conf.Enabled = s.conf.Enabled
|
||||
if len(v6conf.RangeStart) == 0 {
|
||||
v6conf.Enabled = false
|
||||
@@ -172,6 +172,9 @@ func Create(config ServerConfig) *Server {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.conf.Conf4 = conf.Conf4
|
||||
s.conf.Conf6 = conf.Conf6
|
||||
|
||||
if s.conf.Enabled && !v4conf.Enabled && !v6conf.Enabled {
|
||||
log.Error("Can't enable DHCP server because neither DHCPv4 nor DHCPv6 servers are configured")
|
||||
return nil
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -20,116 +21,156 @@ func TestMain(m *testing.M) {
|
||||
func testNotify(flags uint32) {
|
||||
}
|
||||
|
||||
// Leases database store/load
|
||||
// Leases database store/load.
|
||||
func TestDB(t *testing.T) {
|
||||
var err error
|
||||
s := Server{}
|
||||
s.conf.DBFilePath = dbFilename
|
||||
s := Server{
|
||||
conf: ServerConfig{
|
||||
DBFilePath: dbFilename,
|
||||
},
|
||||
}
|
||||
|
||||
conf := V4ServerConf{
|
||||
s.srv4, err = v4Create(V4ServerConf{
|
||||
Enabled: true,
|
||||
RangeStart: net.IP{192, 168, 10, 100},
|
||||
RangeEnd: net.IP{192, 168, 10, 200},
|
||||
GatewayIP: net.IP{192, 168, 10, 1},
|
||||
SubnetMask: net.IP{255, 255, 255, 0},
|
||||
notify: testNotify,
|
||||
}
|
||||
s.srv4, err = v4Create(conf)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
s.srv6, err = v6Create(V6ServerConf{})
|
||||
assert.Nil(t, err)
|
||||
require.Nil(t, err)
|
||||
|
||||
l := Lease{}
|
||||
l.IP = net.IP{192, 168, 10, 100}
|
||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
exp1 := time.Now().Add(time.Hour)
|
||||
l.Expiry = exp1
|
||||
leases := []Lease{{
|
||||
IP: net.IP{192, 168, 10, 100},
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
Expiry: time.Now().Add(time.Hour),
|
||||
}, {
|
||||
IP: net.IP{192, 168, 10, 101},
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xBB},
|
||||
}}
|
||||
|
||||
srv4, ok := s.srv4.(*v4Server)
|
||||
assert.True(t, ok)
|
||||
require.True(t, ok)
|
||||
|
||||
srv4.addLease(&l)
|
||||
srv4.addLease(&leases[0])
|
||||
require.Nil(t, s.srv4.AddStaticLease(leases[1]))
|
||||
|
||||
l2 := Lease{}
|
||||
l2.IP = net.IP{192, 168, 10, 101}
|
||||
l2.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:bb")
|
||||
err = s.srv4.AddStaticLease(l2)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_ = os.Remove("leases.db")
|
||||
s.dbStore()
|
||||
t.Cleanup(func() {
|
||||
assert.Nil(t, os.Remove(dbFilename))
|
||||
})
|
||||
s.srv4.ResetLeases(nil)
|
||||
|
||||
s.dbLoad()
|
||||
|
||||
ll := s.srv4.GetLeases(LeasesAll)
|
||||
require.Len(t, ll, len(leases))
|
||||
|
||||
assert.Equal(t, "aa:aa:aa:aa:aa:bb", ll[0].HWAddr.String())
|
||||
assert.True(t, net.IP{192, 168, 10, 101}.Equal(ll[0].IP))
|
||||
assert.Equal(t, leases[1].HWAddr, ll[0].HWAddr)
|
||||
assert.Equal(t, leases[1].IP, ll[0].IP)
|
||||
assert.EqualValues(t, leaseExpireStatic, ll[0].Expiry.Unix())
|
||||
|
||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ll[1].HWAddr.String())
|
||||
assert.True(t, net.IP{192, 168, 10, 100}.Equal(ll[1].IP))
|
||||
assert.Equal(t, exp1.Unix(), ll[1].Expiry.Unix())
|
||||
|
||||
_ = os.Remove("leases.db")
|
||||
assert.Equal(t, leases[0].HWAddr, ll[1].HWAddr)
|
||||
assert.Equal(t, leases[0].IP, ll[1].IP)
|
||||
assert.Equal(t, leases[0].Expiry.Unix(), ll[1].Expiry.Unix())
|
||||
}
|
||||
|
||||
func TestIsValidSubnetMask(t *testing.T) {
|
||||
assert.True(t, isValidSubnetMask([]byte{255, 255, 255, 0}))
|
||||
assert.True(t, isValidSubnetMask([]byte{255, 255, 254, 0}))
|
||||
assert.True(t, isValidSubnetMask([]byte{255, 255, 252, 0}))
|
||||
assert.False(t, isValidSubnetMask([]byte{255, 255, 253, 0}))
|
||||
assert.False(t, isValidSubnetMask([]byte{255, 255, 255, 1}))
|
||||
testCases := []struct {
|
||||
mask net.IP
|
||||
want bool
|
||||
}{{
|
||||
mask: net.IP{255, 255, 255, 0},
|
||||
want: true,
|
||||
}, {
|
||||
mask: net.IP{255, 255, 254, 0},
|
||||
want: true,
|
||||
}, {
|
||||
mask: net.IP{255, 255, 252, 0},
|
||||
want: true,
|
||||
}, {
|
||||
mask: net.IP{255, 255, 253, 0},
|
||||
}, {
|
||||
mask: net.IP{255, 255, 255, 1},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.mask.String(), func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, isValidSubnetMask(tc.mask))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeLeases(t *testing.T) {
|
||||
dynLeases := []*Lease{}
|
||||
staticLeases := []*Lease{}
|
||||
dynLeases := []*Lease{{
|
||||
HWAddr: net.HardwareAddr{1, 2, 3, 4},
|
||||
}, {
|
||||
HWAddr: net.HardwareAddr{1, 2, 3, 5},
|
||||
}}
|
||||
|
||||
lease := &Lease{}
|
||||
lease.HWAddr = []byte{1, 2, 3, 4}
|
||||
dynLeases = append(dynLeases, lease)
|
||||
lease = new(Lease)
|
||||
lease.HWAddr = []byte{1, 2, 3, 5}
|
||||
dynLeases = append(dynLeases, lease)
|
||||
|
||||
lease = new(Lease)
|
||||
lease.HWAddr = []byte{1, 2, 3, 4}
|
||||
lease.IP = []byte{0, 2, 3, 4}
|
||||
staticLeases = append(staticLeases, lease)
|
||||
lease = new(Lease)
|
||||
lease.HWAddr = []byte{2, 2, 3, 4}
|
||||
staticLeases = append(staticLeases, lease)
|
||||
staticLeases := []*Lease{{
|
||||
HWAddr: net.HardwareAddr{1, 2, 3, 4},
|
||||
IP: net.IP{0, 2, 3, 4},
|
||||
}, {
|
||||
HWAddr: net.HardwareAddr{2, 2, 3, 4},
|
||||
}}
|
||||
|
||||
leases := normalizeLeases(staticLeases, dynLeases)
|
||||
require.Len(t, leases, 3)
|
||||
|
||||
assert.Len(t, leases, 3)
|
||||
assert.True(t, bytes.Equal(leases[0].HWAddr, []byte{1, 2, 3, 4}))
|
||||
assert.True(t, bytes.Equal(leases[0].IP, []byte{0, 2, 3, 4}))
|
||||
assert.True(t, bytes.Equal(leases[1].HWAddr, []byte{2, 2, 3, 4}))
|
||||
assert.True(t, bytes.Equal(leases[2].HWAddr, []byte{1, 2, 3, 5}))
|
||||
assert.Equal(t, leases[0].HWAddr, dynLeases[0].HWAddr)
|
||||
assert.Equal(t, leases[0].IP, staticLeases[0].IP)
|
||||
assert.Equal(t, leases[1].HWAddr, staticLeases[1].HWAddr)
|
||||
assert.Equal(t, leases[2].HWAddr, dynLeases[1].HWAddr)
|
||||
}
|
||||
|
||||
func TestOptions(t *testing.T) {
|
||||
code, val := parseOptionString(" 12 hex abcdef ")
|
||||
assert.EqualValues(t, 12, code)
|
||||
assert.True(t, bytes.Equal([]byte{0xab, 0xcd, 0xef}, val))
|
||||
testCases := []struct {
|
||||
name string
|
||||
optStr string
|
||||
wantCode uint8
|
||||
wantVal []byte
|
||||
}{{
|
||||
name: "all_right_hex",
|
||||
optStr: " 12 hex abcdef ",
|
||||
wantCode: 12,
|
||||
wantVal: []byte{0xab, 0xcd, 0xef},
|
||||
}, {
|
||||
name: "bad_hex",
|
||||
optStr: " 12 hex abcdef1 ",
|
||||
wantCode: 0,
|
||||
}, {
|
||||
name: "all_right_ip",
|
||||
optStr: "123 ip 1.2.3.4",
|
||||
wantCode: 123,
|
||||
wantVal: net.IPv4(1, 2, 3, 4),
|
||||
}, {
|
||||
name: "bad_code",
|
||||
optStr: "256 ip 1.1.1.1",
|
||||
wantCode: 0,
|
||||
}, {
|
||||
name: "negative_code",
|
||||
optStr: "-1 ip 1.1.1.1",
|
||||
wantCode: 0,
|
||||
}, {
|
||||
name: "bad_ip",
|
||||
optStr: "12 ip 1.1.1.1x",
|
||||
wantCode: 0,
|
||||
}, {
|
||||
name: "bad_mode",
|
||||
optStr: "12 x 1.1.1.1",
|
||||
wantCode: 0,
|
||||
}}
|
||||
|
||||
code, _ = parseOptionString(" 12 hex abcdef1 ")
|
||||
assert.EqualValues(t, 0, code)
|
||||
|
||||
code, val = parseOptionString("123 ip 1.2.3.4")
|
||||
assert.EqualValues(t, 123, code)
|
||||
assert.True(t, net.IP{1, 2, 3, 4}.Equal(net.IP(val)))
|
||||
|
||||
code, _ = parseOptionString("256 ip 1.1.1.1")
|
||||
assert.EqualValues(t, 0, code)
|
||||
code, _ = parseOptionString("-1 ip 1.1.1.1")
|
||||
assert.EqualValues(t, 0, code)
|
||||
code, _ = parseOptionString("12 ip 1.1.1.1x")
|
||||
assert.EqualValues(t, 0, code)
|
||||
code, _ = parseOptionString("12 x 1.1.1.1")
|
||||
assert.EqualValues(t, 0, code)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
code, val := parseOptionString(tc.optStr)
|
||||
require.EqualValues(t, tc.wantCode, code)
|
||||
if tc.wantVal != nil {
|
||||
assert.True(t, bytes.Equal(tc.wantVal, val))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package dhcpd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -11,7 +12,6 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/golibs/jsonutil"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
@@ -29,7 +29,11 @@ type v4ServerConfJSON struct {
|
||||
LeaseDuration uint32 `json:"lease_duration"`
|
||||
}
|
||||
|
||||
func v4JSONToServerConf(j v4ServerConfJSON) V4ServerConf {
|
||||
func v4JSONToServerConf(j *v4ServerConfJSON) V4ServerConf {
|
||||
if j == nil {
|
||||
return V4ServerConf{}
|
||||
}
|
||||
|
||||
return V4ServerConf{
|
||||
GatewayIP: j.GatewayIP,
|
||||
SubnetMask: j.SubnetMask,
|
||||
@@ -44,7 +48,11 @@ type v6ServerConfJSON struct {
|
||||
LeaseDuration uint32 `json:"lease_duration"`
|
||||
}
|
||||
|
||||
func v6JSONToServerConf(j v6ServerConfJSON) V6ServerConf {
|
||||
func v6JSONToServerConf(j *v6ServerConfJSON) V6ServerConf {
|
||||
if j == nil {
|
||||
return V6ServerConf{}
|
||||
}
|
||||
|
||||
return V6ServerConf{
|
||||
RangeStart: j.RangeStart,
|
||||
LeaseDuration: j.LeaseDuration,
|
||||
@@ -83,24 +91,44 @@ func (s *Server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
type dhcpServerConfigJSON struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
InterfaceName string `json:"interface_name"`
|
||||
V4 v4ServerConfJSON `json:"v4"`
|
||||
V6 v6ServerConfJSON `json:"v6"`
|
||||
}
|
||||
|
||||
func (s *Server) enableDHCP(ifaceName string) (code int, err error) {
|
||||
var hasStaticIP bool
|
||||
hasStaticIP, err = sysutil.IfaceHasStaticIP(ifaceName)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, fmt.Errorf("checking static ip: %w", err)
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
// ErrPermission may happen here on Linux systems where
|
||||
// AdGuard Home is installed using Snap. That doesn't
|
||||
// necessarily mean that the machine doesn't have
|
||||
// a static IP, so we can assume that it has and go on.
|
||||
// If the machine doesn't, we'll get an error later.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2667.
|
||||
//
|
||||
// TODO(a.garipov): I was thinking about moving this
|
||||
// into IfaceHasStaticIP, but then we wouldn't be able
|
||||
// to log it. Think about it more.
|
||||
log.Info("error while checking static ip: %s; "+
|
||||
"assuming machine has static ip and going on", err)
|
||||
hasStaticIP = true
|
||||
} else if errors.Is(err, sysutil.ErrNoStaticIPInfo) {
|
||||
// Couldn't obtain a definitive answer. Assume static
|
||||
// IP an go on.
|
||||
log.Info("can't check for static ip; " +
|
||||
"assuming machine has static ip and going on")
|
||||
hasStaticIP = true
|
||||
} else {
|
||||
err = fmt.Errorf("checking static ip: %w", err)
|
||||
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
if !hasStaticIP {
|
||||
err = sysutil.IfaceSetStaticIP(ifaceName)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, fmt.Errorf("setting static ip: %w", err)
|
||||
err = fmt.Errorf("setting static ip: %w", err)
|
||||
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,14 +140,22 @@ func (s *Server) enableDHCP(ifaceName string) (code int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
newconfig := dhcpServerConfigJSON{}
|
||||
newconfig.Enabled = s.conf.Enabled
|
||||
newconfig.InterfaceName = s.conf.InterfaceName
|
||||
type dhcpServerConfigJSON struct {
|
||||
V4 *v4ServerConfJSON `json:"v4"`
|
||||
V6 *v6ServerConfJSON `json:"v6"`
|
||||
InterfaceName string `json:"interface_name"`
|
||||
Enabled nullBool `json:"enabled"`
|
||||
}
|
||||
|
||||
js, err := jsonutil.DecodeObject(&newconfig, r.Body)
|
||||
func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
conf := dhcpServerConfigJSON{}
|
||||
conf.Enabled = boolToNullBool(s.conf.Enabled)
|
||||
conf.InterfaceName = s.conf.InterfaceName
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&conf)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err)
|
||||
httpError(r, w, http.StatusBadRequest,
|
||||
"failed to parse new dhcp config json: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -129,62 +165,72 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
v4Enabled := false
|
||||
v6Enabled := false
|
||||
|
||||
if js.Exists("v4") {
|
||||
v4conf := v4JSONToServerConf(newconfig.V4)
|
||||
v4conf.Enabled = newconfig.Enabled
|
||||
if len(v4conf.RangeStart) == 0 {
|
||||
v4conf.Enabled = false
|
||||
if conf.V4 != nil {
|
||||
v4Conf := v4JSONToServerConf(conf.V4)
|
||||
v4Conf.Enabled = conf.Enabled == nbTrue
|
||||
if len(v4Conf.RangeStart) == 0 {
|
||||
v4Conf.Enabled = false
|
||||
}
|
||||
|
||||
v4Enabled = v4conf.Enabled
|
||||
v4conf.InterfaceName = newconfig.InterfaceName
|
||||
v4Enabled = v4Conf.Enabled
|
||||
v4Conf.InterfaceName = conf.InterfaceName
|
||||
|
||||
c4 := V4ServerConf{}
|
||||
s.srv4.WriteDiskConfig4(&c4)
|
||||
v4conf.notify = c4.notify
|
||||
v4conf.ICMPTimeout = c4.ICMPTimeout
|
||||
v4Conf.notify = c4.notify
|
||||
v4Conf.ICMPTimeout = c4.ICMPTimeout
|
||||
|
||||
s4, err = v4Create(v4conf)
|
||||
s4, err = v4Create(v4Conf)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "invalid dhcpv4 configuration: %s", err)
|
||||
httpError(r, w, http.StatusBadRequest,
|
||||
"invalid dhcpv4 configuration: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if js.Exists("v6") {
|
||||
v6conf := v6JSONToServerConf(newconfig.V6)
|
||||
v6conf.Enabled = newconfig.Enabled
|
||||
if len(v6conf.RangeStart) == 0 {
|
||||
v6conf.Enabled = false
|
||||
if conf.V6 != nil {
|
||||
v6Conf := v6JSONToServerConf(conf.V6)
|
||||
v6Conf.Enabled = conf.Enabled == nbTrue
|
||||
if len(v6Conf.RangeStart) == 0 {
|
||||
v6Conf.Enabled = false
|
||||
}
|
||||
|
||||
v6Enabled = v6conf.Enabled
|
||||
v6conf.InterfaceName = newconfig.InterfaceName
|
||||
v6conf.notify = s.onNotify
|
||||
// Don't overwrite the RA/SLAAC settings from the config file.
|
||||
//
|
||||
// TODO(a.garipov): Perhaps include them into the request to
|
||||
// allow changing them from the HTTP API?
|
||||
v6Conf.RASLAACOnly = s.conf.Conf6.RASLAACOnly
|
||||
v6Conf.RAAllowSLAAC = s.conf.Conf6.RAAllowSLAAC
|
||||
|
||||
s6, err = v6Create(v6conf)
|
||||
v6Enabled = v6Conf.Enabled
|
||||
v6Conf.InterfaceName = conf.InterfaceName
|
||||
v6Conf.notify = s.onNotify
|
||||
|
||||
s6, err = v6Create(v6Conf)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "invalid dhcpv6 configuration: %s", err)
|
||||
httpError(r, w, http.StatusBadRequest,
|
||||
"invalid dhcpv6 configuration: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if newconfig.Enabled && !v4Enabled && !v6Enabled {
|
||||
httpError(r, w, http.StatusBadRequest, "dhcpv4 or dhcpv6 configuration must be complete")
|
||||
if conf.Enabled == nbTrue && !v4Enabled && !v6Enabled {
|
||||
httpError(r, w, http.StatusBadRequest,
|
||||
"dhcpv4 or dhcpv6 configuration must be complete")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
s.Stop()
|
||||
|
||||
if js.Exists("enabled") {
|
||||
s.conf.Enabled = newconfig.Enabled
|
||||
if conf.Enabled != nbNull {
|
||||
s.conf.Enabled = conf.Enabled == nbTrue
|
||||
}
|
||||
|
||||
if js.Exists("interface_name") {
|
||||
s.conf.InterfaceName = newconfig.InterfaceName
|
||||
if conf.InterfaceName != "" {
|
||||
s.conf.InterfaceName = conf.InterfaceName
|
||||
}
|
||||
|
||||
if s4 != nil {
|
||||
@@ -200,7 +246,7 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if s.conf.Enabled {
|
||||
var code int
|
||||
code, err = s.enableDHCP(newconfig.InterfaceName)
|
||||
code, err = s.enableDHCP(conf.InterfaceName)
|
||||
if err != nil {
|
||||
httpError(r, w, code, "enabling dhcp: %s", err)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestServer_notImplemented(t *testing.T) {
|
||||
@@ -14,7 +15,7 @@ func TestServer_notImplemented(t *testing.T) {
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequest(http.MethodGet, "/unsupported", nil)
|
||||
assert.Nil(t, err)
|
||||
require.Nil(t, err)
|
||||
|
||||
h(w, r)
|
||||
assert.Equal(t, http.StatusNotImplemented, w.Code)
|
||||
58
internal/dhcpd/nullbool.go
Normal file
58
internal/dhcpd/nullbool.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// nullBool is a nullable boolean. Use these in JSON requests and responses
|
||||
// instead of pointers to bool.
|
||||
//
|
||||
// TODO(a.garipov): Inspect uses of *bool, move this type into some new package
|
||||
// if we need it somewhere else.
|
||||
type nullBool uint8
|
||||
|
||||
// nullBool values
|
||||
const (
|
||||
nbNull nullBool = iota
|
||||
nbTrue
|
||||
nbFalse
|
||||
)
|
||||
|
||||
// String implements the fmt.Stringer interface for nullBool.
|
||||
func (nb nullBool) String() (s string) {
|
||||
switch nb {
|
||||
case nbNull:
|
||||
return "null"
|
||||
case nbTrue:
|
||||
return "true"
|
||||
case nbFalse:
|
||||
return "false"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("!invalid nullBool %d", uint8(nb))
|
||||
}
|
||||
|
||||
// boolToNullBool converts a bool into a nullBool.
|
||||
func boolToNullBool(cond bool) (nb nullBool) {
|
||||
if cond {
|
||||
return nbTrue
|
||||
}
|
||||
|
||||
return nbFalse
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface for *nullBool.
|
||||
func (nb *nullBool) UnmarshalJSON(b []byte) (err error) {
|
||||
if len(b) == 0 || bytes.Equal(b, []byte("null")) {
|
||||
*nb = nbNull
|
||||
} else if bytes.Equal(b, []byte("true")) {
|
||||
*nb = nbTrue
|
||||
} else if bytes.Equal(b, []byte("false")) {
|
||||
*nb = nbFalse
|
||||
} else {
|
||||
return fmt.Errorf("invalid nullBool value %q", b)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
69
internal/dhcpd/nullbool_test.go
Normal file
69
internal/dhcpd/nullbool_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNullBool_UnmarshalText(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
data []byte
|
||||
wantErrMsg string
|
||||
want nullBool
|
||||
}{{
|
||||
name: "empty",
|
||||
data: []byte{},
|
||||
wantErrMsg: "",
|
||||
want: nbNull,
|
||||
}, {
|
||||
name: "null",
|
||||
data: []byte("null"),
|
||||
wantErrMsg: "",
|
||||
want: nbNull,
|
||||
}, {
|
||||
name: "true",
|
||||
data: []byte("true"),
|
||||
wantErrMsg: "",
|
||||
want: nbTrue,
|
||||
}, {
|
||||
name: "false",
|
||||
data: []byte("false"),
|
||||
wantErrMsg: "",
|
||||
want: nbFalse,
|
||||
}, {
|
||||
name: "invalid",
|
||||
data: []byte("flase"),
|
||||
wantErrMsg: `invalid nullBool value "flase"`,
|
||||
want: nbNull,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var got nullBool
|
||||
err := got.UnmarshalJSON(tc.data)
|
||||
if tc.wantErrMsg == "" {
|
||||
assert.Nil(t, err)
|
||||
} else {
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, tc.wantErrMsg, err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("json", func(t *testing.T) {
|
||||
want := nbTrue
|
||||
var got struct {
|
||||
A nullBool
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(`{"A":true}`), &got)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, want, got.A)
|
||||
})
|
||||
}
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
)
|
||||
|
||||
type raCtx struct {
|
||||
raAllowSlaac bool // send RA packets without MO flags
|
||||
raSlaacOnly bool // send RA packets with MO flags
|
||||
raAllowSLAAC bool // send RA packets without MO flags
|
||||
raSLAACOnly bool // send RA packets with MO flags
|
||||
ipAddr net.IP // source IP address (link-local-unicast)
|
||||
dnsIPAddr net.IP // IP address for DNS Server option
|
||||
prefixIPAddr net.IP // IP address for Prefix option
|
||||
@@ -159,7 +159,7 @@ func createICMPv6RAPacket(params icmpv6RA) []byte {
|
||||
func (ra *raCtx) Init() error {
|
||||
ra.stop.Store(0)
|
||||
ra.conn = nil
|
||||
if !(ra.raAllowSlaac || ra.raSlaacOnly) {
|
||||
if !(ra.raAllowSLAAC || ra.raSLAACOnly) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -167,8 +167,8 @@ func (ra *raCtx) Init() error {
|
||||
ra.ipAddr, ra.dnsIPAddr)
|
||||
|
||||
params := icmpv6RA{
|
||||
managedAddressConfiguration: !ra.raSlaacOnly,
|
||||
otherConfiguration: !ra.raSlaacOnly,
|
||||
managedAddressConfiguration: !ra.raSLAACOnly,
|
||||
otherConfiguration: !ra.raSLAACOnly,
|
||||
mtu: uint32(ra.iface.MTU),
|
||||
prefixLen: 64,
|
||||
recursiveDNSServer: ra.dnsIPAddr,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
@@ -9,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestRA(t *testing.T) {
|
||||
ra := icmpv6RA{
|
||||
data := createICMPv6RAPacket(icmpv6RA{
|
||||
managedAddressConfiguration: false,
|
||||
otherConfiguration: true,
|
||||
mtu: 1500,
|
||||
@@ -17,8 +16,7 @@ func TestRA(t *testing.T) {
|
||||
prefixLen: 64,
|
||||
recursiveDNSServer: net.ParseIP("fe80::800:27ff:fe00:0"),
|
||||
sourceLinkLayerAddress: []byte{0x0a, 0x00, 0x27, 0x00, 0x00, 0x00},
|
||||
}
|
||||
data := createICMPv6RAPacket(ra)
|
||||
})
|
||||
dataCorrect := []byte{
|
||||
0x86, 0x00, 0x00, 0x00, 0x40, 0x40, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00,
|
||||
@@ -27,5 +25,5 @@ func TestRA(t *testing.T) {
|
||||
0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x10, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x08, 0x00, 0x27, 0xff, 0xfe, 0x00, 0x00, 0x00,
|
||||
}
|
||||
assert.True(t, bytes.Equal(data, dataCorrect))
|
||||
assert.Equal(t, dataCorrect, data)
|
||||
}
|
||||
|
||||
@@ -79,12 +79,12 @@ type V6ServerConf struct {
|
||||
|
||||
// The first IP address for dynamic leases
|
||||
// The last allowed IP address ends with 0xff byte
|
||||
RangeStart net.IP `yaml:"range_start"`
|
||||
RangeStart net.IP `yaml:"range_start" json:"range_start"`
|
||||
|
||||
LeaseDuration uint32 `yaml:"lease_duration" json:"lease_duration"` // in seconds
|
||||
|
||||
RaSlaacOnly bool `yaml:"ra_slaac_only" json:"-"` // send ICMPv6.RA packets without MO flags
|
||||
RaAllowSlaac bool `yaml:"ra_allow_slaac" json:"-"` // send ICMPv6.RA packets with MO flags
|
||||
RASLAACOnly bool `yaml:"ra_slaac_only" json:"-"` // send ICMPv6.RA packets without MO flags
|
||||
RAAllowSLAAC bool `yaml:"ra_allow_slaac" json:"-"` // send ICMPv6.RA packets with MO flags
|
||||
|
||||
ipStart net.IP // starting IP address for dynamic leases
|
||||
leaseTime time.Duration // the time during which a dynamic lease is considered valid
|
||||
|
||||
@@ -23,7 +23,8 @@ type v4Server struct {
|
||||
srv *server4.Server
|
||||
leasesLock sync.Mutex
|
||||
leases []*Lease
|
||||
ipAddrs [256]byte
|
||||
// TODO(e.burkov): This field type should be a normal bitmap.
|
||||
ipAddrs [256]byte
|
||||
|
||||
conf V4ServerConf
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fakeIface struct {
|
||||
@@ -79,8 +80,8 @@ func TestIfaceIPAddrs(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, gotErr := ifaceIPAddrs(tc.iface, tc.ipv)
|
||||
require.True(t, errors.Is(gotErr, tc.wantErr))
|
||||
assert.Equal(t, tc.want, got)
|
||||
assert.True(t, errors.Is(gotErr, tc.wantErr))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -140,12 +141,8 @@ func TestIfaceDNSIPAddrs(t *testing.T) {
|
||||
want: nil,
|
||||
wantErr: errTest,
|
||||
}, {
|
||||
name: "ipv4_wait",
|
||||
iface: &waitingFakeIface{
|
||||
addrs: []net.Addr{addr4},
|
||||
err: nil,
|
||||
n: 1,
|
||||
},
|
||||
name: "ipv4_wait",
|
||||
iface: &waitingFakeIface{addrs: []net.Addr{addr4}, err: nil, n: 1},
|
||||
ipv: ipVersion4,
|
||||
want: []net.IP{ip4, ip4},
|
||||
wantErr: nil,
|
||||
@@ -168,12 +165,8 @@ func TestIfaceDNSIPAddrs(t *testing.T) {
|
||||
want: nil,
|
||||
wantErr: errTest,
|
||||
}, {
|
||||
name: "ipv6_wait",
|
||||
iface: &waitingFakeIface{
|
||||
addrs: []net.Addr{addr6},
|
||||
err: nil,
|
||||
n: 1,
|
||||
},
|
||||
name: "ipv6_wait",
|
||||
iface: &waitingFakeIface{addrs: []net.Addr{addr6}, err: nil, n: 1},
|
||||
ipv: ipVersion6,
|
||||
want: []net.IP{ip6, ip6},
|
||||
wantErr: nil,
|
||||
@@ -182,8 +175,8 @@ func TestIfaceDNSIPAddrs(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, gotErr := ifaceDNSIPAddrs(tc.iface, tc.ipv, 2, 0)
|
||||
require.True(t, errors.Is(gotErr, tc.wantErr))
|
||||
assert.Equal(t, tc.want, got)
|
||||
assert.True(t, errors.Is(gotErr, tc.wantErr))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,172 +8,182 @@ import (
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func notify4(flags uint32) {
|
||||
}
|
||||
|
||||
func TestV4StaticLeaseAddRemove(t *testing.T) {
|
||||
conf := V4ServerConf{
|
||||
func TestV4_AddRemove_static(t *testing.T) {
|
||||
s, err := v4Create(V4ServerConf{
|
||||
Enabled: true,
|
||||
RangeStart: net.IP{192, 168, 10, 100},
|
||||
RangeEnd: net.IP{192, 168, 10, 200},
|
||||
GatewayIP: net.IP{192, 168, 10, 1},
|
||||
SubnetMask: net.IP{255, 255, 255, 0},
|
||||
notify: notify4,
|
||||
}
|
||||
s, err := v4Create(conf)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
ls := s.GetLeases(LeasesStatic)
|
||||
assert.Empty(t, ls)
|
||||
|
||||
// add static lease
|
||||
l := Lease{}
|
||||
l.IP = net.IP{192, 168, 10, 150}
|
||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
assert.Nil(t, s.AddStaticLease(l))
|
||||
|
||||
// try to add the same static lease - fail
|
||||
// Add static lease.
|
||||
l := Lease{
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}
|
||||
require.Nil(t, s.AddStaticLease(l))
|
||||
assert.NotNil(t, s.AddStaticLease(l))
|
||||
|
||||
// check
|
||||
ls = s.GetLeases(LeasesStatic)
|
||||
assert.Len(t, ls, 1)
|
||||
assert.True(t, net.IP{192, 168, 10, 150}.Equal(ls[0].IP))
|
||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
||||
require.Len(t, ls, 1)
|
||||
assert.True(t, l.IP.Equal(ls[0].IP))
|
||||
assert.Equal(t, l.HWAddr, ls[0].HWAddr)
|
||||
assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix())
|
||||
|
||||
// try to remove static lease - fail
|
||||
l.IP = net.IP{192, 168, 10, 110}
|
||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
assert.NotNil(t, s.RemoveStaticLease(l))
|
||||
// Try to remove static lease.
|
||||
assert.NotNil(t, s.RemoveStaticLease(Lease{
|
||||
IP: net.IP{192, 168, 10, 110},
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}))
|
||||
|
||||
// remove static lease
|
||||
l.IP = net.IP{192, 168, 10, 150}
|
||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
assert.Nil(t, s.RemoveStaticLease(l))
|
||||
|
||||
// check
|
||||
// Remove static lease.
|
||||
require.Nil(t, s.RemoveStaticLease(l))
|
||||
ls = s.GetLeases(LeasesStatic)
|
||||
assert.Empty(t, ls)
|
||||
}
|
||||
|
||||
func TestV4StaticLeaseAddReplaceDynamic(t *testing.T) {
|
||||
conf := V4ServerConf{
|
||||
func TestV4_AddReplace(t *testing.T) {
|
||||
sIface, err := v4Create(V4ServerConf{
|
||||
Enabled: true,
|
||||
RangeStart: net.IP{192, 168, 10, 100},
|
||||
RangeEnd: net.IP{192, 168, 10, 200},
|
||||
GatewayIP: net.IP{192, 168, 10, 1},
|
||||
SubnetMask: net.IP{255, 255, 255, 0},
|
||||
notify: notify4,
|
||||
}
|
||||
sIface, err := v4Create(conf)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
s, ok := sIface.(*v4Server)
|
||||
assert.True(t, ok)
|
||||
assert.Nil(t, err)
|
||||
require.True(t, ok)
|
||||
|
||||
// add dynamic lease
|
||||
ld := Lease{}
|
||||
ld.IP = net.IP{192, 168, 10, 150}
|
||||
ld.HWAddr, _ = net.ParseMAC("11:aa:aa:aa:aa:aa")
|
||||
s.addLease(&ld)
|
||||
dynLeases := []Lease{{
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}, {
|
||||
IP: net.IP{192, 168, 10, 151},
|
||||
HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}}
|
||||
|
||||
// add dynamic lease
|
||||
{
|
||||
ld := Lease{}
|
||||
ld.IP = net.IP{192, 168, 10, 151}
|
||||
ld.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa")
|
||||
s.addLease(&ld)
|
||||
for i := range dynLeases {
|
||||
s.addLease(&dynLeases[i])
|
||||
}
|
||||
|
||||
// add static lease with the same IP
|
||||
l := Lease{}
|
||||
l.IP = net.IP{192, 168, 10, 150}
|
||||
l.HWAddr, _ = net.ParseMAC("33:aa:aa:aa:aa:aa")
|
||||
assert.Nil(t, s.AddStaticLease(l))
|
||||
stLeases := []Lease{{
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}, {
|
||||
IP: net.IP{192, 168, 10, 152},
|
||||
HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}}
|
||||
|
||||
// add static lease with the same MAC
|
||||
l = Lease{}
|
||||
l.IP = net.IP{192, 168, 10, 152}
|
||||
l.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa")
|
||||
assert.Nil(t, s.AddStaticLease(l))
|
||||
for _, l := range stLeases {
|
||||
require.Nil(t, s.AddStaticLease(l))
|
||||
}
|
||||
|
||||
// check
|
||||
ls := s.GetLeases(LeasesStatic)
|
||||
assert.Len(t, ls, 2)
|
||||
require.Len(t, ls, 2)
|
||||
|
||||
assert.True(t, net.IP{192, 168, 10, 150}.Equal(ls[0].IP))
|
||||
assert.Equal(t, "33:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
||||
assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix())
|
||||
|
||||
assert.True(t, net.IP{192, 168, 10, 152}.Equal(ls[1].IP))
|
||||
assert.Equal(t, "22:aa:aa:aa:aa:aa", ls[1].HWAddr.String())
|
||||
assert.EqualValues(t, leaseExpireStatic, ls[1].Expiry.Unix())
|
||||
for i, l := range ls {
|
||||
assert.True(t, stLeases[i].IP.Equal(l.IP))
|
||||
assert.Equal(t, stLeases[i].HWAddr, l.HWAddr)
|
||||
assert.EqualValues(t, leaseExpireStatic, l.Expiry.Unix())
|
||||
}
|
||||
}
|
||||
|
||||
func TestV4StaticLeaseGet(t *testing.T) {
|
||||
conf := V4ServerConf{
|
||||
func TestV4StaticLease_Get(t *testing.T) {
|
||||
var err error
|
||||
sIface, err := v4Create(V4ServerConf{
|
||||
Enabled: true,
|
||||
RangeStart: net.IP{192, 168, 10, 100},
|
||||
RangeEnd: net.IP{192, 168, 10, 200},
|
||||
GatewayIP: net.IP{192, 168, 10, 1},
|
||||
SubnetMask: net.IP{255, 255, 255, 0},
|
||||
notify: notify4,
|
||||
}
|
||||
sIface, err := v4Create(conf)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
s, ok := sIface.(*v4Server)
|
||||
assert.True(t, ok)
|
||||
assert.Nil(t, err)
|
||||
require.True(t, ok)
|
||||
s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}}
|
||||
|
||||
l := Lease{}
|
||||
l.IP = net.IP{192, 168, 10, 150}
|
||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
assert.Nil(t, s.AddStaticLease(l))
|
||||
l := Lease{
|
||||
IP: net.IP{192, 168, 10, 150},
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}
|
||||
require.Nil(t, s.AddStaticLease(l))
|
||||
|
||||
// "Discover"
|
||||
mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
req, _ := dhcpv4.NewDiscovery(mac)
|
||||
resp, _ := dhcpv4.NewReplyFromRequest(req)
|
||||
assert.Equal(t, 1, s.process(req, resp))
|
||||
var req, resp *dhcpv4.DHCPv4
|
||||
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||
|
||||
// check "Offer"
|
||||
assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String())
|
||||
assert.True(t, net.IP{192, 168, 10, 150}.Equal(resp.YourIPAddr))
|
||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0]))
|
||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier()))
|
||||
assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask())))
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||
t.Run("discover", func(t *testing.T) {
|
||||
var err error
|
||||
|
||||
// "Request"
|
||||
req, _ = dhcpv4.NewRequestFromOffer(resp)
|
||||
resp, _ = dhcpv4.NewReplyFromRequest(req)
|
||||
assert.Equal(t, 1, s.process(req, resp))
|
||||
req, err = dhcpv4.NewDiscovery(mac)
|
||||
require.Nil(t, err)
|
||||
|
||||
// check "Ack"
|
||||
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String())
|
||||
assert.True(t, net.IP{192, 168, 10, 150}.Equal(resp.YourIPAddr))
|
||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0]))
|
||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier()))
|
||||
assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask())))
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, s.process(req, resp))
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("offer", func(t *testing.T) {
|
||||
assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
||||
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||
assert.True(t, l.IP.Equal(resp.YourIPAddr))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
||||
assert.Equal(t, s.conf.subnetMask, resp.SubnetMask())
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||
})
|
||||
|
||||
t.Run("request", func(t *testing.T) {
|
||||
req, err = dhcpv4.NewRequestFromOffer(resp)
|
||||
require.Nil(t, err)
|
||||
|
||||
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, s.process(req, resp))
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("ack", func(t *testing.T) {
|
||||
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
||||
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||
assert.True(t, l.IP.Equal(resp.YourIPAddr))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
||||
assert.Equal(t, s.conf.subnetMask, resp.SubnetMask())
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||
})
|
||||
|
||||
dnsAddrs := resp.DNS()
|
||||
assert.Len(t, dnsAddrs, 1)
|
||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(dnsAddrs[0]))
|
||||
require.Len(t, dnsAddrs, 1)
|
||||
assert.True(t, s.conf.GatewayIP.Equal(dnsAddrs[0]))
|
||||
|
||||
// check lease
|
||||
ls := s.GetLeases(LeasesStatic)
|
||||
assert.Len(t, ls, 1)
|
||||
assert.True(t, net.IP{192, 168, 10, 150}.Equal(ls[0].IP))
|
||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
||||
t.Run("check_lease", func(t *testing.T) {
|
||||
ls := s.GetLeases(LeasesStatic)
|
||||
require.Len(t, ls, 1)
|
||||
assert.True(t, l.IP.Equal(ls[0].IP))
|
||||
assert.Equal(t, mac, ls[0].HWAddr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestV4DynamicLeaseGet(t *testing.T) {
|
||||
conf := V4ServerConf{
|
||||
func TestV4DynamicLease_Get(t *testing.T) {
|
||||
var err error
|
||||
sIface, err := v4Create(V4ServerConf{
|
||||
Enabled: true,
|
||||
RangeStart: net.IP{192, 168, 10, 100},
|
||||
RangeEnd: net.IP{192, 168, 10, 200},
|
||||
@@ -184,58 +194,97 @@ func TestV4DynamicLeaseGet(t *testing.T) {
|
||||
"81 hex 303132",
|
||||
"82 ip 1.2.3.4",
|
||||
},
|
||||
}
|
||||
sIface, err := v4Create(conf)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
s, ok := sIface.(*v4Server)
|
||||
assert.True(t, ok)
|
||||
assert.Nil(t, err)
|
||||
require.True(t, ok)
|
||||
s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}}
|
||||
|
||||
// "Discover"
|
||||
mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
req, _ := dhcpv4.NewDiscovery(mac)
|
||||
resp, _ := dhcpv4.NewReplyFromRequest(req)
|
||||
assert.Equal(t, 1, s.process(req, resp))
|
||||
var req, resp *dhcpv4.DHCPv4
|
||||
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||
|
||||
// check "Offer"
|
||||
assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String())
|
||||
assert.True(t, net.IP{192, 168, 10, 100}.Equal(resp.YourIPAddr))
|
||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0]))
|
||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier()))
|
||||
assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask())))
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||
assert.Equal(t, []byte("012"), resp.Options[uint8(dhcpv4.OptionFQDN)])
|
||||
assert.True(t, net.IP{1, 2, 3, 4}.Equal(net.IP(resp.Options[uint8(dhcpv4.OptionRelayAgentInformation)])))
|
||||
t.Run("discover", func(t *testing.T) {
|
||||
req, err = dhcpv4.NewDiscovery(mac)
|
||||
require.Nil(t, err)
|
||||
|
||||
// "Request"
|
||||
req, _ = dhcpv4.NewRequestFromOffer(resp)
|
||||
resp, _ = dhcpv4.NewReplyFromRequest(req)
|
||||
assert.Equal(t, 1, s.process(req, resp))
|
||||
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, s.process(req, resp))
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
// check "Ack"
|
||||
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String())
|
||||
assert.True(t, net.IP{192, 168, 10, 100}.Equal(resp.YourIPAddr))
|
||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.Router()[0]))
|
||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(resp.ServerIdentifier()))
|
||||
assert.True(t, net.IP{255, 255, 255, 0}.Equal(net.IP(resp.SubnetMask())))
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||
t.Run("offer", func(t *testing.T) {
|
||||
assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
||||
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||
assert.True(t, s.conf.RangeStart.Equal(resp.YourIPAddr))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
||||
assert.Equal(t, s.conf.subnetMask, resp.SubnetMask())
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||
assert.Equal(t, []byte("012"), resp.Options[uint8(dhcpv4.OptionFQDN)])
|
||||
assert.True(t, net.IP{1, 2, 3, 4}.Equal(net.IP(resp.Options[uint8(dhcpv4.OptionRelayAgentInformation)])))
|
||||
})
|
||||
|
||||
t.Run("request", func(t *testing.T) {
|
||||
var err error
|
||||
|
||||
req, err = dhcpv4.NewRequestFromOffer(resp)
|
||||
require.Nil(t, err)
|
||||
|
||||
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, s.process(req, resp))
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("ack", func(t *testing.T) {
|
||||
assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType())
|
||||
assert.Equal(t, mac, resp.ClientHWAddr)
|
||||
assert.True(t, s.conf.RangeStart.Equal(resp.YourIPAddr))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.Router()[0]))
|
||||
assert.True(t, s.conf.GatewayIP.Equal(resp.ServerIdentifier()))
|
||||
assert.Equal(t, s.conf.subnetMask, resp.SubnetMask())
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds())
|
||||
})
|
||||
|
||||
dnsAddrs := resp.DNS()
|
||||
assert.Len(t, dnsAddrs, 1)
|
||||
require.Len(t, dnsAddrs, 1)
|
||||
assert.True(t, net.IP{192, 168, 10, 1}.Equal(dnsAddrs[0]))
|
||||
|
||||
// check lease
|
||||
ls := s.GetLeases(LeasesDynamic)
|
||||
assert.Len(t, ls, 1)
|
||||
assert.True(t, net.IP{192, 168, 10, 100}.Equal(ls[0].IP))
|
||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
||||
t.Run("check_lease", func(t *testing.T) {
|
||||
ls := s.GetLeases(LeasesDynamic)
|
||||
assert.Len(t, ls, 1)
|
||||
assert.True(t, net.IP{192, 168, 10, 100}.Equal(ls[0].IP))
|
||||
assert.Equal(t, mac, ls[0].HWAddr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIP4InRange(t *testing.T) {
|
||||
start := net.IP{192, 168, 10, 100}
|
||||
stop := net.IP{192, 168, 10, 200}
|
||||
assert.False(t, ip4InRange(start, stop, net.IP{192, 168, 10, 99}))
|
||||
assert.False(t, ip4InRange(start, stop, net.IP{192, 168, 11, 100}))
|
||||
assert.False(t, ip4InRange(start, stop, net.IP{192, 168, 11, 201}))
|
||||
assert.True(t, ip4InRange(start, stop, net.IP{192, 168, 10, 100}))
|
||||
|
||||
testCases := []struct {
|
||||
ip net.IP
|
||||
want bool
|
||||
}{{
|
||||
ip: net.IP{192, 168, 10, 99},
|
||||
want: false,
|
||||
}, {
|
||||
ip: net.IP{192, 168, 11, 100},
|
||||
want: false,
|
||||
}, {
|
||||
ip: net.IP{192, 168, 11, 201},
|
||||
want: false,
|
||||
}, {
|
||||
ip: start,
|
||||
want: true,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.ip.String(), func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, ip4InRange(start, stop, tc.ip))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,8 +552,8 @@ func (s *v6Server) initRA(iface *net.Interface) error {
|
||||
}
|
||||
}
|
||||
|
||||
s.ra.raAllowSlaac = s.conf.RaAllowSlaac
|
||||
s.ra.raSlaacOnly = s.conf.RaSlaacOnly
|
||||
s.ra.raAllowSLAAC = s.conf.RAAllowSLAAC
|
||||
s.ra.raSLAACOnly = s.conf.RASLAACOnly
|
||||
s.ra.dnsIPAddr = s.ra.ipAddr
|
||||
s.ra.prefixIPAddr = s.conf.ipStart
|
||||
s.ra.ifaceName = s.conf.InterfaceName
|
||||
@@ -594,7 +594,7 @@ func (s *v6Server) Start() error {
|
||||
}
|
||||
|
||||
// don't initialize DHCPv6 server if we must force the clients to use SLAAC
|
||||
if s.conf.RaSlaacOnly {
|
||||
if s.conf.RASLAACOnly {
|
||||
log.Debug("DHCPv6: not starting DHCPv6 server due to ra_slaac_only=true")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,220 +9,283 @@ import (
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
"github.com/insomniacslk/dhcp/iana"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func notify6(flags uint32) {
|
||||
}
|
||||
|
||||
func TestV6StaticLeaseAddRemove(t *testing.T) {
|
||||
conf := V6ServerConf{
|
||||
func TestV6_AddRemove_static(t *testing.T) {
|
||||
s, err := v6Create(V6ServerConf{
|
||||
Enabled: true,
|
||||
RangeStart: net.ParseIP("2001::1"),
|
||||
notify: notify6,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Empty(t, s.GetLeases(LeasesStatic))
|
||||
|
||||
// Add static lease.
|
||||
l := Lease{
|
||||
IP: net.ParseIP("2001::1"),
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}
|
||||
s, err := v6Create(conf)
|
||||
assert.Nil(t, err)
|
||||
require.Nil(t, s.AddStaticLease(l))
|
||||
|
||||
// Try to add the same static lease.
|
||||
require.NotNil(t, s.AddStaticLease(l))
|
||||
|
||||
ls := s.GetLeases(LeasesStatic)
|
||||
assert.Empty(t, ls)
|
||||
|
||||
// add static lease
|
||||
l := Lease{}
|
||||
l.IP = net.ParseIP("2001::1")
|
||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
assert.Nil(t, s.AddStaticLease(l))
|
||||
|
||||
// try to add static lease - fail
|
||||
assert.NotNil(t, s.AddStaticLease(l))
|
||||
|
||||
// check
|
||||
ls = s.GetLeases(LeasesStatic)
|
||||
assert.Len(t, ls, 1)
|
||||
assert.Equal(t, "2001::1", ls[0].IP.String())
|
||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
||||
require.Len(t, ls, 1)
|
||||
assert.Equal(t, l.IP, ls[0].IP)
|
||||
assert.Equal(t, l.HWAddr, ls[0].HWAddr)
|
||||
assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix())
|
||||
|
||||
// try to remove static lease - fail
|
||||
l.IP = net.ParseIP("2001::2")
|
||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
assert.NotNil(t, s.RemoveStaticLease(l))
|
||||
// Try to remove non-existent static lease.
|
||||
require.NotNil(t, s.RemoveStaticLease(Lease{
|
||||
IP: net.ParseIP("2001::2"),
|
||||
HWAddr: l.HWAddr,
|
||||
}))
|
||||
|
||||
// remove static lease
|
||||
l.IP = net.ParseIP("2001::1")
|
||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
assert.Nil(t, s.RemoveStaticLease(l))
|
||||
// Remove static lease.
|
||||
require.Nil(t, s.RemoveStaticLease(l))
|
||||
|
||||
// check
|
||||
ls = s.GetLeases(LeasesStatic)
|
||||
assert.Empty(t, ls)
|
||||
assert.Empty(t, s.GetLeases(LeasesStatic))
|
||||
}
|
||||
|
||||
func TestV6StaticLeaseAddReplaceDynamic(t *testing.T) {
|
||||
conf := V6ServerConf{
|
||||
func TestV6_AddReplace(t *testing.T) {
|
||||
sIface, err := v6Create(V6ServerConf{
|
||||
Enabled: true,
|
||||
RangeStart: net.ParseIP("2001::1"),
|
||||
notify: notify6,
|
||||
}
|
||||
sIface, err := v6Create(conf)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
s, ok := sIface.(*v6Server)
|
||||
assert.True(t, ok)
|
||||
assert.Nil(t, err)
|
||||
require.True(t, ok)
|
||||
|
||||
// add dynamic lease
|
||||
ld := Lease{}
|
||||
ld.IP = net.ParseIP("2001::1")
|
||||
ld.HWAddr, _ = net.ParseMAC("11:aa:aa:aa:aa:aa")
|
||||
s.addLease(&ld)
|
||||
// Add dynamic leases.
|
||||
dynLeases := []*Lease{{
|
||||
IP: net.ParseIP("2001::1"),
|
||||
HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}, {
|
||||
IP: net.ParseIP("2001::2"),
|
||||
HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}}
|
||||
|
||||
// add dynamic lease
|
||||
{
|
||||
ld := Lease{}
|
||||
ld.IP = net.ParseIP("2001::2")
|
||||
ld.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa")
|
||||
s.addLease(&ld)
|
||||
for _, l := range dynLeases {
|
||||
s.addLease(l)
|
||||
}
|
||||
|
||||
// add static lease with the same IP
|
||||
l := Lease{}
|
||||
l.IP = net.ParseIP("2001::1")
|
||||
l.HWAddr, _ = net.ParseMAC("33:aa:aa:aa:aa:aa")
|
||||
assert.Nil(t, s.AddStaticLease(l))
|
||||
stLeases := []Lease{{
|
||||
IP: net.ParseIP("2001::1"),
|
||||
HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}, {
|
||||
IP: net.ParseIP("2001::3"),
|
||||
HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}}
|
||||
|
||||
// add static lease with the same MAC
|
||||
l = Lease{}
|
||||
l.IP = net.ParseIP("2001::3")
|
||||
l.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa")
|
||||
assert.Nil(t, s.AddStaticLease(l))
|
||||
for _, l := range stLeases {
|
||||
require.Nil(t, s.AddStaticLease(l))
|
||||
}
|
||||
|
||||
// check
|
||||
ls := s.GetLeases(LeasesStatic)
|
||||
assert.Len(t, ls, 2)
|
||||
require.Len(t, ls, 2)
|
||||
|
||||
assert.Equal(t, "2001::1", ls[0].IP.String())
|
||||
assert.Equal(t, "33:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
||||
assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix())
|
||||
|
||||
assert.Equal(t, "2001::3", ls[1].IP.String())
|
||||
assert.Equal(t, "22:aa:aa:aa:aa:aa", ls[1].HWAddr.String())
|
||||
assert.EqualValues(t, leaseExpireStatic, ls[1].Expiry.Unix())
|
||||
for i, l := range ls {
|
||||
assert.True(t, stLeases[i].IP.Equal(l.IP))
|
||||
assert.Equal(t, stLeases[i].HWAddr, l.HWAddr)
|
||||
assert.EqualValues(t, leaseExpireStatic, l.Expiry.Unix())
|
||||
}
|
||||
}
|
||||
|
||||
func TestV6GetLease(t *testing.T) {
|
||||
conf := V6ServerConf{
|
||||
var err error
|
||||
sIface, err := v6Create(V6ServerConf{
|
||||
Enabled: true,
|
||||
RangeStart: net.ParseIP("2001::1"),
|
||||
notify: notify6,
|
||||
}
|
||||
sIface, err := v6Create(conf)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
s, ok := sIface.(*v6Server)
|
||||
assert.True(t, ok)
|
||||
assert.Nil(t, err)
|
||||
s.conf.dnsIPAddrs = []net.IP{net.ParseIP("2000::1")}
|
||||
require.True(t, ok)
|
||||
|
||||
dnsAddr := net.ParseIP("2000::1")
|
||||
s.conf.dnsIPAddrs = []net.IP{dnsAddr}
|
||||
s.sid = dhcpv6.Duid{
|
||||
Type: dhcpv6.DUID_LLT,
|
||||
HwType: iana.HWTypeEthernet,
|
||||
Type: dhcpv6.DUID_LLT,
|
||||
HwType: iana.HWTypeEthernet,
|
||||
LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}
|
||||
s.sid.LinkLayerAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
|
||||
l := Lease{}
|
||||
l.IP = net.ParseIP("2001::1")
|
||||
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
assert.Nil(t, s.AddStaticLease(l))
|
||||
l := Lease{
|
||||
IP: net.ParseIP("2001::1"),
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}
|
||||
require.Nil(t, s.AddStaticLease(l))
|
||||
|
||||
// "Solicit"
|
||||
mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
req, _ := dhcpv6.NewSolicit(mac)
|
||||
msg, _ := req.GetInnerMessage()
|
||||
resp, _ := dhcpv6.NewAdvertiseFromSolicit(msg)
|
||||
assert.True(t, s.process(msg, req, resp))
|
||||
var req, resp, msg *dhcpv6.Message
|
||||
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||
t.Run("solicit", func(t *testing.T) {
|
||||
req, err = dhcpv6.NewSolicit(mac)
|
||||
require.Nil(t, err)
|
||||
|
||||
msg, err = req.GetInnerMessage()
|
||||
require.Nil(t, err)
|
||||
|
||||
resp, err = dhcpv6.NewAdvertiseFromSolicit(msg)
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.True(t, s.process(msg, req, resp))
|
||||
})
|
||||
require.Nil(t, err)
|
||||
resp.AddOption(dhcpv6.OptServerID(s.sid))
|
||||
|
||||
// check "Advertise"
|
||||
assert.Equal(t, dhcpv6.MessageTypeAdvertise, resp.Type())
|
||||
oia := resp.Options.OneIANA()
|
||||
oiaAddr := oia.Options.OneAddress()
|
||||
assert.Equal(t, "2001::1", oiaAddr.IPv6Addr.String())
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds())
|
||||
var oia *dhcpv6.OptIANA
|
||||
var oiaAddr *dhcpv6.OptIAAddress
|
||||
t.Run("advertise", func(t *testing.T) {
|
||||
require.Equal(t, dhcpv6.MessageTypeAdvertise, resp.Type())
|
||||
oia = resp.Options.OneIANA()
|
||||
oiaAddr = oia.Options.OneAddress()
|
||||
|
||||
// "Request"
|
||||
req, _ = dhcpv6.NewRequestFromAdvertise(resp)
|
||||
msg, _ = req.GetInnerMessage()
|
||||
resp, _ = dhcpv6.NewReplyFromMessage(msg)
|
||||
assert.True(t, s.process(msg, req, resp))
|
||||
assert.Equal(t, l.IP, oiaAddr.IPv6Addr)
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds())
|
||||
})
|
||||
|
||||
// check "Reply"
|
||||
assert.Equal(t, dhcpv6.MessageTypeReply, resp.Type())
|
||||
oia = resp.Options.OneIANA()
|
||||
oiaAddr = oia.Options.OneAddress()
|
||||
assert.Equal(t, "2001::1", oiaAddr.IPv6Addr.String())
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds())
|
||||
t.Run("request", func(t *testing.T) {
|
||||
req, err = dhcpv6.NewRequestFromAdvertise(resp)
|
||||
require.Nil(t, err)
|
||||
|
||||
msg, err = req.GetInnerMessage()
|
||||
require.Nil(t, err)
|
||||
|
||||
resp, err = dhcpv6.NewReplyFromMessage(msg)
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.True(t, s.process(msg, req, resp))
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("reply", func(t *testing.T) {
|
||||
require.Equal(t, dhcpv6.MessageTypeReply, resp.Type())
|
||||
oia = resp.Options.OneIANA()
|
||||
oiaAddr = oia.Options.OneAddress()
|
||||
|
||||
assert.Equal(t, l.IP, oiaAddr.IPv6Addr)
|
||||
assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds())
|
||||
})
|
||||
|
||||
dnsAddrs := resp.Options.DNS()
|
||||
assert.Len(t, dnsAddrs, 1)
|
||||
assert.Equal(t, "2000::1", dnsAddrs[0].String())
|
||||
require.Len(t, dnsAddrs, 1)
|
||||
assert.Equal(t, dnsAddr, dnsAddrs[0])
|
||||
|
||||
// check lease
|
||||
ls := s.GetLeases(LeasesStatic)
|
||||
assert.Len(t, ls, 1)
|
||||
assert.Equal(t, "2001::1", ls[0].IP.String())
|
||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
||||
t.Run("lease", func(t *testing.T) {
|
||||
ls := s.GetLeases(LeasesStatic)
|
||||
require.Len(t, ls, 1)
|
||||
assert.Equal(t, l.IP, ls[0].IP)
|
||||
assert.Equal(t, l.HWAddr, ls[0].HWAddr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestV6GetDynamicLease(t *testing.T) {
|
||||
conf := V6ServerConf{
|
||||
sIface, err := v6Create(V6ServerConf{
|
||||
Enabled: true,
|
||||
RangeStart: net.ParseIP("2001::2"),
|
||||
notify: notify6,
|
||||
}
|
||||
sIface, err := v6Create(conf)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
s, ok := sIface.(*v6Server)
|
||||
assert.True(t, ok)
|
||||
assert.Nil(t, err)
|
||||
s.conf.dnsIPAddrs = []net.IP{net.ParseIP("2000::1")}
|
||||
s.sid = dhcpv6.Duid{
|
||||
Type: dhcpv6.DUID_LLT,
|
||||
HwType: iana.HWTypeEthernet,
|
||||
}
|
||||
s.sid.LinkLayerAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
require.True(t, ok)
|
||||
|
||||
// "Solicit"
|
||||
mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
req, _ := dhcpv6.NewSolicit(mac)
|
||||
msg, _ := req.GetInnerMessage()
|
||||
resp, _ := dhcpv6.NewAdvertiseFromSolicit(msg)
|
||||
assert.True(t, s.process(msg, req, resp))
|
||||
dnsAddr := net.ParseIP("2000::1")
|
||||
s.conf.dnsIPAddrs = []net.IP{dnsAddr}
|
||||
s.sid = dhcpv6.Duid{
|
||||
Type: dhcpv6.DUID_LLT,
|
||||
HwType: iana.HWTypeEthernet,
|
||||
LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
}
|
||||
|
||||
var req, resp, msg *dhcpv6.Message
|
||||
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||
t.Run("solicit", func(t *testing.T) {
|
||||
req, err = dhcpv6.NewSolicit(mac)
|
||||
require.Nil(t, err)
|
||||
|
||||
msg, err = req.GetInnerMessage()
|
||||
require.Nil(t, err)
|
||||
|
||||
resp, err = dhcpv6.NewAdvertiseFromSolicit(msg)
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.True(t, s.process(msg, req, resp))
|
||||
})
|
||||
require.Nil(t, err)
|
||||
resp.AddOption(dhcpv6.OptServerID(s.sid))
|
||||
|
||||
// check "Advertise"
|
||||
assert.Equal(t, dhcpv6.MessageTypeAdvertise, resp.Type())
|
||||
oia := resp.Options.OneIANA()
|
||||
oiaAddr := oia.Options.OneAddress()
|
||||
assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String())
|
||||
var oia *dhcpv6.OptIANA
|
||||
var oiaAddr *dhcpv6.OptIAAddress
|
||||
t.Run("advertise", func(t *testing.T) {
|
||||
require.Equal(t, dhcpv6.MessageTypeAdvertise, resp.Type())
|
||||
oia = resp.Options.OneIANA()
|
||||
oiaAddr = oia.Options.OneAddress()
|
||||
assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String())
|
||||
})
|
||||
|
||||
// "Request"
|
||||
req, _ = dhcpv6.NewRequestFromAdvertise(resp)
|
||||
msg, _ = req.GetInnerMessage()
|
||||
resp, _ = dhcpv6.NewReplyFromMessage(msg)
|
||||
assert.True(t, s.process(msg, req, resp))
|
||||
t.Run("request", func(t *testing.T) {
|
||||
req, err = dhcpv6.NewRequestFromAdvertise(resp)
|
||||
require.Nil(t, err)
|
||||
|
||||
// check "Reply"
|
||||
assert.Equal(t, dhcpv6.MessageTypeReply, resp.Type())
|
||||
oia = resp.Options.OneIANA()
|
||||
oiaAddr = oia.Options.OneAddress()
|
||||
assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String())
|
||||
msg, err = req.GetInnerMessage()
|
||||
require.Nil(t, err)
|
||||
|
||||
resp, err = dhcpv6.NewReplyFromMessage(msg)
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.True(t, s.process(msg, req, resp))
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("reply", func(t *testing.T) {
|
||||
require.Equal(t, dhcpv6.MessageTypeReply, resp.Type())
|
||||
oia = resp.Options.OneIANA()
|
||||
oiaAddr = oia.Options.OneAddress()
|
||||
assert.Equal(t, "2001::2", oiaAddr.IPv6Addr.String())
|
||||
})
|
||||
|
||||
dnsAddrs := resp.Options.DNS()
|
||||
assert.Len(t, dnsAddrs, 1)
|
||||
assert.Equal(t, "2000::1", dnsAddrs[0].String())
|
||||
require.Len(t, dnsAddrs, 1)
|
||||
assert.Equal(t, dnsAddr, dnsAddrs[0])
|
||||
|
||||
// check lease
|
||||
ls := s.GetLeases(LeasesDynamic)
|
||||
assert.Len(t, ls, 1)
|
||||
assert.Equal(t, "2001::2", ls[0].IP.String())
|
||||
assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String())
|
||||
|
||||
assert.False(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::1")))
|
||||
assert.False(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2002::2")))
|
||||
assert.True(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::2")))
|
||||
assert.True(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::3")))
|
||||
t.Run("lease", func(t *testing.T) {
|
||||
ls := s.GetLeases(LeasesDynamic)
|
||||
require.Len(t, ls, 1)
|
||||
assert.Equal(t, "2001::2", ls[0].IP.String())
|
||||
assert.Equal(t, mac, ls[0].HWAddr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIP6InRange(t *testing.T) {
|
||||
start := net.ParseIP("2001::2")
|
||||
|
||||
testCases := []struct {
|
||||
ip net.IP
|
||||
want bool
|
||||
}{{
|
||||
ip: net.ParseIP("2001::1"),
|
||||
want: false,
|
||||
}, {
|
||||
ip: net.ParseIP("2002::2"),
|
||||
want: false,
|
||||
}, {
|
||||
ip: start,
|
||||
want: true,
|
||||
}, {
|
||||
ip: net.ParseIP("2001::3"),
|
||||
want: true,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.ip.String(), func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, ip6InRange(start, tc.ip))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
165
internal/dnsforward/clientid.go
Normal file
165
internal/dnsforward/clientid.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
)
|
||||
|
||||
const maxDomainPartLen = 64
|
||||
|
||||
// ValidateClientID returns an error if clientID is not a valid client ID.
|
||||
func ValidateClientID(clientID string) (err error) {
|
||||
if len(clientID) > maxDomainPartLen {
|
||||
return fmt.Errorf("client id %q is too long, max: %d", clientID, maxDomainPartLen)
|
||||
}
|
||||
|
||||
for i, r := range clientID {
|
||||
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("invalid char %q at index %d in client id %q", r, i, clientID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clientIDFromClientServerName extracts and validates a client ID. hostSrvName
|
||||
// is the server name of the host. cliSrvName is the server name as sent by the
|
||||
// client. When strict is true, and client and host server name don't match,
|
||||
// clientIDFromClientServerName will return an error.
|
||||
func clientIDFromClientServerName(hostSrvName, cliSrvName string, strict bool) (clientID string, err error) {
|
||||
if hostSrvName == cliSrvName {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(cliSrvName, hostSrvName) {
|
||||
if !strict {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("client server name %q doesn't match host server name %q", cliSrvName, hostSrvName)
|
||||
}
|
||||
|
||||
clientID = cliSrvName[:len(cliSrvName)-len(hostSrvName)-1]
|
||||
err = ValidateClientID(clientID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid client id: %w", err)
|
||||
}
|
||||
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// processClientIDHTTPS extracts the client's ID from the path of the
|
||||
// client's DNS-over-HTTPS request.
|
||||
func processClientIDHTTPS(ctx *dnsContext) (rc resultCode) {
|
||||
pctx := ctx.proxyCtx
|
||||
r := pctx.HTTPRequest
|
||||
if r == nil {
|
||||
ctx.err = fmt.Errorf("proxy ctx http request of proto %s is nil", pctx.Proto)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
origPath := r.URL.Path
|
||||
parts := strings.Split(path.Clean(origPath), "/")
|
||||
if parts[0] == "" {
|
||||
parts = parts[1:]
|
||||
}
|
||||
|
||||
if len(parts) == 0 || parts[0] != "dns-query" {
|
||||
ctx.err = fmt.Errorf("client id check: invalid path %q", origPath)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
clientID := ""
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
// Just /dns-query, no client ID.
|
||||
return resultCodeSuccess
|
||||
case 2:
|
||||
clientID = parts[1]
|
||||
default:
|
||||
ctx.err = fmt.Errorf("client id check: invalid path %q: extra parts", origPath)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
err := ValidateClientID(clientID)
|
||||
if err != nil {
|
||||
ctx.err = fmt.Errorf("client id check: invalid client id: %w", err)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
ctx.clientID = clientID
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// tlsConn is a narrow interface for *tls.Conn to simplify testing.
|
||||
type tlsConn interface {
|
||||
ConnectionState() (cs tls.ConnectionState)
|
||||
}
|
||||
|
||||
// quicSession is a narrow interface for quic.Session to simplify testing.
|
||||
type quicSession interface {
|
||||
ConnectionState() (cs quic.ConnectionState)
|
||||
}
|
||||
|
||||
// processClientID extracts the client's ID from the server name of the client's
|
||||
// DOT or DOQ request or the path of the client's DOH.
|
||||
func processClientID(dctx *dnsContext) (rc resultCode) {
|
||||
pctx := dctx.proxyCtx
|
||||
proto := pctx.Proto
|
||||
if proto == proxy.ProtoHTTPS {
|
||||
return processClientIDHTTPS(dctx)
|
||||
} else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
srvConf := dctx.srv.conf
|
||||
hostSrvName := srvConf.TLSConfig.ServerName
|
||||
if hostSrvName == "" {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
cliSrvName := ""
|
||||
if proto == proxy.ProtoTLS {
|
||||
conn := pctx.Conn
|
||||
tc, ok := conn.(tlsConn)
|
||||
if !ok {
|
||||
dctx.err = fmt.Errorf("proxy ctx conn of proto %s is %T, want *tls.Conn", proto, conn)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
cliSrvName = tc.ConnectionState().ServerName
|
||||
} else if proto == proxy.ProtoQUIC {
|
||||
qs, ok := pctx.QUICSession.(quicSession)
|
||||
if !ok {
|
||||
dctx.err = fmt.Errorf("proxy ctx quic session of proto %s is %T, want quic.Session", proto, pctx.QUICSession)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
cliSrvName = qs.ConnectionState().ServerName
|
||||
}
|
||||
|
||||
clientID, err := clientIDFromClientServerName(hostSrvName, cliSrvName, srvConf.StrictSNICheck)
|
||||
if err != nil {
|
||||
dctx.err = fmt.Errorf("client id check: %w", err)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
dctx.clientID = clientID
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testTLSConn is a tlsConn for tests.
|
||||
@@ -53,6 +54,7 @@ func TestProcessClientID(t *testing.T) {
|
||||
wantClientID string
|
||||
wantErrMsg string
|
||||
wantRes resultCode
|
||||
strictSNI bool
|
||||
}{{
|
||||
name: "udp",
|
||||
proto: proxy.ProtoUDP,
|
||||
@@ -61,6 +63,7 @@ func TestProcessClientID(t *testing.T) {
|
||||
wantClientID: "",
|
||||
wantErrMsg: "",
|
||||
wantRes: resultCodeSuccess,
|
||||
strictSNI: false,
|
||||
}, {
|
||||
name: "tls_no_client_id",
|
||||
proto: proxy.ProtoTLS,
|
||||
@@ -69,6 +72,26 @@ func TestProcessClientID(t *testing.T) {
|
||||
wantClientID: "",
|
||||
wantErrMsg: "",
|
||||
wantRes: resultCodeSuccess,
|
||||
strictSNI: true,
|
||||
}, {
|
||||
name: "tls_no_client_server_name",
|
||||
proto: proxy.ProtoTLS,
|
||||
hostSrvName: "example.com",
|
||||
cliSrvName: "",
|
||||
wantClientID: "",
|
||||
wantErrMsg: `client id check: client server name "" ` +
|
||||
`doesn't match host server name "example.com"`,
|
||||
wantRes: resultCodeError,
|
||||
strictSNI: true,
|
||||
}, {
|
||||
name: "tls_no_client_server_name_no_strict",
|
||||
proto: proxy.ProtoTLS,
|
||||
hostSrvName: "example.com",
|
||||
cliSrvName: "",
|
||||
wantClientID: "",
|
||||
wantErrMsg: "",
|
||||
wantRes: resultCodeSuccess,
|
||||
strictSNI: false,
|
||||
}, {
|
||||
name: "tls_client_id",
|
||||
proto: proxy.ProtoTLS,
|
||||
@@ -77,30 +100,39 @@ func TestProcessClientID(t *testing.T) {
|
||||
wantClientID: "cli",
|
||||
wantErrMsg: "",
|
||||
wantRes: resultCodeSuccess,
|
||||
strictSNI: true,
|
||||
}, {
|
||||
name: "tls_client_id_hostname_error",
|
||||
proto: proxy.ProtoTLS,
|
||||
hostSrvName: "example.com",
|
||||
cliSrvName: "cli.example.net",
|
||||
wantClientID: "",
|
||||
wantErrMsg: `client id check: client server name "cli.example.net" doesn't match host server name "example.com"`,
|
||||
wantRes: resultCodeError,
|
||||
wantErrMsg: `client id check: client server name "cli.example.net" ` +
|
||||
`doesn't match host server name "example.com"`,
|
||||
wantRes: resultCodeError,
|
||||
strictSNI: true,
|
||||
}, {
|
||||
name: "tls_invalid_client_id",
|
||||
proto: proxy.ProtoTLS,
|
||||
hostSrvName: "example.com",
|
||||
cliSrvName: "!!!.example.com",
|
||||
wantClientID: "",
|
||||
wantErrMsg: `client id check: invalid client id: invalid char '!' at index 0 in client id "!!!"`,
|
||||
wantRes: resultCodeError,
|
||||
wantErrMsg: `client id check: invalid client id: invalid char '!' ` +
|
||||
`at index 0 in client id "!!!"`,
|
||||
wantRes: resultCodeError,
|
||||
strictSNI: true,
|
||||
}, {
|
||||
name: "tls_client_id_too_long",
|
||||
proto: proxy.ProtoTLS,
|
||||
hostSrvName: "example.com",
|
||||
cliSrvName: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789.example.com",
|
||||
name: "tls_client_id_too_long",
|
||||
proto: proxy.ProtoTLS,
|
||||
hostSrvName: "example.com",
|
||||
cliSrvName: `abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmno` +
|
||||
`pqrstuvwxyz0123456789.example.com`,
|
||||
wantClientID: "",
|
||||
wantErrMsg: `client id check: invalid client id: client id "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789" is too long, max: 64`,
|
||||
wantRes: resultCodeError,
|
||||
wantErrMsg: `client id check: invalid client id: client id "abcdefghijklmno` +
|
||||
`pqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789" ` +
|
||||
`is too long, max: 64`,
|
||||
wantRes: resultCodeError,
|
||||
strictSNI: true,
|
||||
}, {
|
||||
name: "quic_client_id",
|
||||
proto: proxy.ProtoQUIC,
|
||||
@@ -109,14 +141,17 @@ func TestProcessClientID(t *testing.T) {
|
||||
wantClientID: "cli",
|
||||
wantErrMsg: "",
|
||||
wantRes: resultCodeSuccess,
|
||||
strictSNI: true,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tlsConf := TLSConfig{
|
||||
ServerName: tc.hostSrvName,
|
||||
StrictSNICheck: tc.strictSNI,
|
||||
}
|
||||
srv := &Server{
|
||||
conf: ServerConfig{
|
||||
TLSConfig: TLSConfig{ServerName: tc.hostSrvName},
|
||||
},
|
||||
conf: ServerConfig{TLSConfig: tlsConf},
|
||||
}
|
||||
|
||||
var conn net.Conn
|
||||
@@ -146,10 +181,11 @@ func TestProcessClientID(t *testing.T) {
|
||||
assert.Equal(t, tc.wantRes, res)
|
||||
assert.Equal(t, tc.wantClientID, dctx.clientID)
|
||||
|
||||
if tc.wantErrMsg != "" && assert.NotNil(t, dctx.err) {
|
||||
assert.Equal(t, tc.wantErrMsg, dctx.err.Error())
|
||||
} else {
|
||||
if tc.wantErrMsg == "" {
|
||||
assert.Nil(t, dctx.err)
|
||||
} else {
|
||||
require.NotNil(t, dctx.err)
|
||||
assert.Equal(t, tc.wantErrMsg, dctx.err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -202,8 +238,9 @@ func TestProcessClientID_https(t *testing.T) {
|
||||
name: "invalid_client_id",
|
||||
path: "/dns-query/!!!",
|
||||
wantClientID: "",
|
||||
wantErrMsg: `client id check: invalid client id: invalid char '!' at index 0 in client id "!!!"`,
|
||||
wantRes: resultCodeError,
|
||||
wantErrMsg: `client id check: invalid client id: invalid char '!'` +
|
||||
` at index 0 in client id "!!!"`,
|
||||
wantRes: resultCodeError,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -225,10 +262,11 @@ func TestProcessClientID_https(t *testing.T) {
|
||||
assert.Equal(t, tc.wantRes, res)
|
||||
assert.Equal(t, tc.wantClientID, dctx.clientID)
|
||||
|
||||
if tc.wantErrMsg != "" && assert.NotNil(t, dctx.err) {
|
||||
assert.Equal(t, tc.wantErrMsg, dctx.err.Error())
|
||||
} else {
|
||||
if tc.wantErrMsg == "" {
|
||||
assert.Nil(t, dctx.err)
|
||||
} else {
|
||||
require.NotNil(t, dctx.err)
|
||||
assert.Equal(t, tc.wantErrMsg, dctx.err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -13,7 +10,6 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@@ -234,154 +230,6 @@ func processInternalHosts(ctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
const maxDomainPartLen = 64
|
||||
|
||||
// ValidateClientID returns an error if clientID is not a valid client ID.
|
||||
func ValidateClientID(clientID string) (err error) {
|
||||
if len(clientID) > maxDomainPartLen {
|
||||
return fmt.Errorf("client id %q is too long, max: %d", clientID, maxDomainPartLen)
|
||||
}
|
||||
|
||||
for i, r := range clientID {
|
||||
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("invalid char %q at index %d in client id %q", r, i, clientID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clientIDFromClientServerName extracts and validates a client ID. hostSrvName
|
||||
// is the server name of the host. cliSrvName is the server name as sent by the
|
||||
// client.
|
||||
func clientIDFromClientServerName(hostSrvName, cliSrvName string) (clientID string, err error) {
|
||||
if hostSrvName == cliSrvName {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(cliSrvName, hostSrvName) {
|
||||
return "", fmt.Errorf("client server name %q doesn't match host server name %q", cliSrvName, hostSrvName)
|
||||
}
|
||||
|
||||
clientID = cliSrvName[:len(cliSrvName)-len(hostSrvName)-1]
|
||||
err = ValidateClientID(clientID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid client id: %w", err)
|
||||
}
|
||||
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// processClientIDHTTPS extracts the client's ID from the path of the
|
||||
// client's DNS-over-HTTPS request.
|
||||
func processClientIDHTTPS(ctx *dnsContext) (rc resultCode) {
|
||||
pctx := ctx.proxyCtx
|
||||
r := pctx.HTTPRequest
|
||||
if r == nil {
|
||||
ctx.err = fmt.Errorf("proxy ctx http request of proto %s is nil", pctx.Proto)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
origPath := r.URL.Path
|
||||
parts := strings.Split(path.Clean(origPath), "/")
|
||||
if parts[0] == "" {
|
||||
parts = parts[1:]
|
||||
}
|
||||
|
||||
if len(parts) == 0 || parts[0] != "dns-query" {
|
||||
ctx.err = fmt.Errorf("client id check: invalid path %q", origPath)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
clientID := ""
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
// Just /dns-query, no client ID.
|
||||
return resultCodeSuccess
|
||||
case 2:
|
||||
clientID = parts[1]
|
||||
default:
|
||||
ctx.err = fmt.Errorf("client id check: invalid path %q: extra parts", origPath)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
err := ValidateClientID(clientID)
|
||||
if err != nil {
|
||||
ctx.err = fmt.Errorf("client id check: invalid client id: %w", err)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
ctx.clientID = clientID
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// tlsConn is a narrow interface for *tls.Conn to simplify testing.
|
||||
type tlsConn interface {
|
||||
ConnectionState() (cs tls.ConnectionState)
|
||||
}
|
||||
|
||||
// quicSession is a narrow interface for quic.Session to simplify testing.
|
||||
type quicSession interface {
|
||||
ConnectionState() (cs quic.ConnectionState)
|
||||
}
|
||||
|
||||
// processClientID extracts the client's ID from the server name of the client's
|
||||
// DOT or DOQ request or the path of the client's DOH.
|
||||
func processClientID(ctx *dnsContext) (rc resultCode) {
|
||||
pctx := ctx.proxyCtx
|
||||
proto := pctx.Proto
|
||||
if proto == proxy.ProtoHTTPS {
|
||||
return processClientIDHTTPS(ctx)
|
||||
} else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
hostSrvName := ctx.srv.conf.TLSConfig.ServerName
|
||||
if hostSrvName == "" {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
cliSrvName := ""
|
||||
if proto == proxy.ProtoTLS {
|
||||
conn := pctx.Conn
|
||||
tc, ok := conn.(tlsConn)
|
||||
if !ok {
|
||||
ctx.err = fmt.Errorf("proxy ctx conn of proto %s is %T, want *tls.Conn", proto, conn)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
cliSrvName = tc.ConnectionState().ServerName
|
||||
} else if proto == proxy.ProtoQUIC {
|
||||
qs, ok := pctx.QUICSession.(quicSession)
|
||||
if !ok {
|
||||
ctx.err = fmt.Errorf("proxy ctx quic session of proto %s is %T, want quic.Session", proto, pctx.QUICSession)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
cliSrvName = qs.ConnectionState().ServerName
|
||||
}
|
||||
|
||||
clientID, err := clientIDFromClientServerName(hostSrvName, cliSrvName)
|
||||
if err != nil {
|
||||
ctx.err = fmt.Errorf("client id check: %w", err)
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
ctx.clientID = clientID
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// Respond to PTR requests if the target IP address is leased by our DHCP server
|
||||
func processInternalIPAddrs(ctx *dnsContext) (rc resultCode) {
|
||||
s := ctx.srv
|
||||
|
||||
@@ -251,12 +251,15 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (ok bool) {
|
||||
|
||||
// Allow the frontend from the HTTP origin to send requests to the HTTPS
|
||||
// server. This can happen when the user has just set up HTTPS with
|
||||
// redirects.
|
||||
// redirects. Prevent cache-related errors by setting the Vary header.
|
||||
//
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin.
|
||||
originURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: r.Host,
|
||||
}
|
||||
w.Header().Set("Access-Control-Allow-Origin", originURL.String())
|
||||
w.Header().Set("Vary", "Origin")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -295,15 +295,20 @@ func run(args options) {
|
||||
BindPort: config.BindPort,
|
||||
BetaBindPort: config.BetaBindPort,
|
||||
|
||||
ReadTimeout: ReadTimeout,
|
||||
ReadHeaderTimeout: ReadHeaderTimeout,
|
||||
WriteTimeout: WriteTimeout,
|
||||
ReadTimeout: readTimeout,
|
||||
ReadHeaderTimeout: readHdrTimeout,
|
||||
WriteTimeout: writeTimeout,
|
||||
}
|
||||
Context.web = CreateWeb(&webConf)
|
||||
if Context.web == nil {
|
||||
log.Fatalf("Can't initialize Web module")
|
||||
}
|
||||
|
||||
Context.ipDetector, err = newIPDetector()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !Context.firstRun {
|
||||
err := initDNSServer()
|
||||
if err != nil {
|
||||
@@ -324,11 +329,6 @@ func run(args options) {
|
||||
}
|
||||
}
|
||||
|
||||
Context.ipDetector, err = newIPDetector()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
Context.web.Start()
|
||||
|
||||
// wait indefinitely for other go-routines to complete their job
|
||||
|
||||
@@ -5,16 +5,15 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIPDetector_detectSpecialNetwork(t *testing.T) {
|
||||
var ipd *ipDetector
|
||||
var err error
|
||||
|
||||
t.Run("newIPDetector", func(t *testing.T) {
|
||||
var err error
|
||||
ipd, err = newIPDetector()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
ipd, err = newIPDetector()
|
||||
require.Nil(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
||||
@@ -22,15 +22,43 @@ func withMiddlewares(h http.Handler, middlewares ...middleware) (wrapped http.Ha
|
||||
return wrapped
|
||||
}
|
||||
|
||||
// RequestBodySizeLimit is maximum request body length in bytes.
|
||||
const RequestBodySizeLimit = 64 * 1024
|
||||
// defaultReqBodySzLim is the default maximum request body size.
|
||||
const defaultReqBodySzLim = 64 * 1024
|
||||
|
||||
// largerReqBodySzLim is the maximum request body size for APIs expecting larger
|
||||
// requests.
|
||||
const largerReqBodySzLim = 4 * 1024 * 1024
|
||||
|
||||
// expectsLargerRequests shows if this request should use a larger body size
|
||||
// limit. These are exceptions for poorly designed current APIs as well as APIs
|
||||
// that are designed to expect large files and requests. Remove once the new,
|
||||
// better APIs are up.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2666 and
|
||||
// https://github.com/AdguardTeam/AdGuardHome/issues/2675.
|
||||
func expectsLargerRequests(r *http.Request) (ok bool) {
|
||||
m := r.Method
|
||||
if m != http.MethodPost {
|
||||
return false
|
||||
}
|
||||
|
||||
p := r.URL.Path
|
||||
return p == "/control/access/set" ||
|
||||
p == "/control/filtering/set_rules"
|
||||
}
|
||||
|
||||
// limitRequestBody wraps underlying handler h, making it's request's body Read
|
||||
// method limited.
|
||||
func limitRequestBody(h http.Handler) (limited http.Handler) {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
r.Body, err = aghio.LimitReadCloser(r.Body, RequestBodySizeLimit)
|
||||
|
||||
var szLim int64 = defaultReqBodySzLim
|
||||
if expectsLargerRequests(r) {
|
||||
szLim = largerReqBodySzLim
|
||||
}
|
||||
|
||||
r.Body, err = aghio.LimitReadCloser(r.Body, szLim)
|
||||
if err != nil {
|
||||
log.Error("limitRequestBody: %s", err)
|
||||
|
||||
|
||||
@@ -9,11 +9,12 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLimitRequestBody(t *testing.T) {
|
||||
errReqLimitReached := &aghio.LimitReachedError{
|
||||
Limit: RequestBodySizeLimit,
|
||||
Limit: defaultReqBodySzLim,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
@@ -28,8 +29,8 @@ func TestLimitRequestBody(t *testing.T) {
|
||||
wantErr: nil,
|
||||
}, {
|
||||
name: "so_big",
|
||||
body: string(make([]byte, RequestBodySizeLimit+1)),
|
||||
want: make([]byte, RequestBodySizeLimit),
|
||||
body: string(make([]byte, defaultReqBodySzLim+1)),
|
||||
want: make([]byte, defaultReqBodySzLim),
|
||||
wantErr: errReqLimitReached,
|
||||
}, {
|
||||
name: "empty",
|
||||
@@ -60,8 +61,8 @@ func TestLimitRequestBody(t *testing.T) {
|
||||
|
||||
lim.ServeHTTP(res, req)
|
||||
|
||||
require.Equal(t, tc.wantErr, err)
|
||||
assert.Equal(t, tc.want, res.Body.Bytes())
|
||||
assert.Equal(t, tc.wantErr, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,17 +16,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// ReadTimeout is the maximum duration for reading the entire request,
|
||||
// readTimeout is the maximum duration for reading the entire request,
|
||||
// including the body.
|
||||
ReadTimeout = 10 * time.Second
|
||||
|
||||
// ReadHeaderTimeout is the amount of time allowed to read request
|
||||
// headers.
|
||||
ReadHeaderTimeout = 10 * time.Second
|
||||
|
||||
// WriteTimeout is the maximum duration before timing out writes of the
|
||||
readTimeout = 60 * time.Second
|
||||
// readHdrTimeout is the amount of time allowed to read request headers.
|
||||
readHdrTimeout = 60 * time.Second
|
||||
// writeTimeout is the maximum duration before timing out writes of the
|
||||
// response.
|
||||
WriteTimeout = 10 * time.Second
|
||||
writeTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
type webConfig struct {
|
||||
@@ -191,7 +188,10 @@ func (web *Web) Start() {
|
||||
WriteTimeout: web.conf.WriteTimeout,
|
||||
}
|
||||
go func() {
|
||||
errs <- web.httpServerBeta.ListenAndServe()
|
||||
betaErr := web.httpServerBeta.ListenAndServe()
|
||||
if betaErr != nil {
|
||||
log.Error("starting beta http server: %s", betaErr)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ func (web *Web) tlsServerLoop() {
|
||||
RootCAs: Context.tlsRoots,
|
||||
CipherSuites: Context.tlsCiphers,
|
||||
},
|
||||
Handler: Context.mux,
|
||||
Handler: withMiddlewares(Context.mux, limitRequestBody),
|
||||
ReadTimeout: web.conf.ReadTimeout,
|
||||
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
|
||||
WriteTimeout: web.conf.WriteTimeout,
|
||||
|
||||
@@ -53,6 +53,7 @@ func NewClientProto(s string) (cp ClientProto, err error) {
|
||||
ClientProtoDOH,
|
||||
ClientProtoDOQ,
|
||||
ClientProtoDOT,
|
||||
ClientProtoDNSCrypt,
|
||||
ClientProtoPlain:
|
||||
|
||||
return cp, nil
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
@@ -11,10 +12,14 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// TODO(a.garipov): Rewrite all of this. Add proper error handling and
|
||||
// inspection. Improve logging. Decrease complexity.
|
||||
|
||||
const (
|
||||
maxDomains = 100 // max number of top domains to store in file or return via Get()
|
||||
maxClients = 100 // max number of top clients to store in file or return via Get()
|
||||
@@ -61,11 +66,12 @@ type unitDB struct {
|
||||
TimeAvg uint32 // usec
|
||||
}
|
||||
|
||||
func createObject(conf Config) (*statsCtx, error) {
|
||||
s := statsCtx{}
|
||||
func createObject(conf Config) (s *statsCtx, err error) {
|
||||
s = &statsCtx{}
|
||||
if !checkInterval(conf.LimitDays) {
|
||||
conf.LimitDays = 1
|
||||
}
|
||||
|
||||
s.conf = &Config{}
|
||||
*s.conf = conf
|
||||
s.conf.limit = conf.LimitDays * 24
|
||||
@@ -84,27 +90,43 @@ func createObject(conf Config) (*statsCtx, error) {
|
||||
log.Tracef("Deleting old units...")
|
||||
firstID := id - s.conf.limit - 1
|
||||
unitDel := 0
|
||||
forEachBkt := func(name []byte, b *bolt.Bucket) error {
|
||||
id := uint32(btoi(name))
|
||||
if id < firstID {
|
||||
err := tx.DeleteBucket(name)
|
||||
if err != nil {
|
||||
log.Debug("tx.DeleteBucket: %s", err)
|
||||
|
||||
// TODO(a.garipov): See if this is actually necessary. Looks
|
||||
// like a rather bizarre solution.
|
||||
errStop := agherr.Error("stop iteration")
|
||||
forEachBkt := func(name []byte, _ *bolt.Bucket) (cberr error) {
|
||||
nameID := uint32(btoi(name))
|
||||
if nameID < firstID {
|
||||
cberr = tx.DeleteBucket(name)
|
||||
if cberr != nil {
|
||||
log.Debug("stats: tx.DeleteBucket: %s", cberr)
|
||||
|
||||
return nil
|
||||
}
|
||||
log.Debug("Stats: deleted unit %d", id)
|
||||
|
||||
log.Debug("stats: deleted unit %d", nameID)
|
||||
unitDel++
|
||||
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("")
|
||||
|
||||
return errStop
|
||||
}
|
||||
|
||||
err = tx.ForEach(forEachBkt)
|
||||
if err != nil && !errors.Is(err, errStop) {
|
||||
log.Debug("stats: deleting units: %s", err)
|
||||
}
|
||||
_ = tx.ForEach(forEachBkt)
|
||||
|
||||
udb = s.loadUnitFromDB(tx, id)
|
||||
|
||||
if unitDel != 0 {
|
||||
s.commitTxn(tx)
|
||||
} else {
|
||||
_ = tx.Rollback()
|
||||
err = tx.Rollback()
|
||||
if err != nil {
|
||||
log.Debug("rolling back: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,8 +137,9 @@ func createObject(conf Config) (*statsCtx, error) {
|
||||
}
|
||||
s.unit = &u
|
||||
|
||||
log.Debug("Stats: initialized")
|
||||
return &s, nil
|
||||
log.Debug("stats: initialized")
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *statsCtx) Start() {
|
||||
@@ -133,7 +156,7 @@ func (s *statsCtx) dbOpen() bool {
|
||||
log.Tracef("db.Open...")
|
||||
s.db, err = bolt.Open(s.conf.Filename, 0o644, nil)
|
||||
if err != nil {
|
||||
log.Error("Stats: open DB: %s: %s", s.conf.Filename, err)
|
||||
log.Error("stats: open DB: %s: %s", s.conf.Filename, err)
|
||||
if err.Error() == "invalid argument" {
|
||||
log.Error("AdGuard Home cannot be initialized due to an incompatible file system.\nPlease read the explanation here: https://github.com/AdguardTeam/AdGuardHome/internal/wiki/Getting-Started#limitations")
|
||||
}
|
||||
@@ -262,10 +285,13 @@ func (s *statsCtx) periodicFlush() {
|
||||
func (s *statsCtx) deleteUnit(tx *bolt.Tx, id uint32) bool {
|
||||
err := tx.DeleteBucket(unitName(id))
|
||||
if err != nil {
|
||||
log.Tracef("bolt DeleteBucket: %s", err)
|
||||
log.Tracef("stats: bolt DeleteBucket: %s", err)
|
||||
|
||||
return false
|
||||
}
|
||||
log.Debug("Stats: deleted unit %d", id)
|
||||
|
||||
log.Debug("stats: deleted unit %d", id)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -390,7 +416,7 @@ func (s *statsCtx) setLimit(limitDays int) {
|
||||
conf := *s.conf
|
||||
conf.limit = uint32(limitDays) * 24
|
||||
s.conf = &conf
|
||||
log.Debug("Stats: set limit: %d", limitDays)
|
||||
log.Debug("stats: set limit: %d", limitDays)
|
||||
}
|
||||
|
||||
func (s *statsCtx) WriteDiskConfig(dc *DiskConfig) {
|
||||
@@ -415,7 +441,7 @@ func (s *statsCtx) Close() {
|
||||
log.Tracef("db.Close")
|
||||
}
|
||||
|
||||
log.Debug("Stats: closed")
|
||||
log.Debug("stats: closed")
|
||||
}
|
||||
|
||||
// Reset counters and clear database
|
||||
@@ -443,7 +469,7 @@ func (s *statsCtx) clear() {
|
||||
|
||||
_ = s.dbOpen()
|
||||
|
||||
log.Debug("Stats: cleared")
|
||||
log.Debug("stats: cleared")
|
||||
}
|
||||
|
||||
// Get Client IP address
|
||||
|
||||
@@ -5,10 +5,17 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// ErrNoStaticIPInfo is returned by IfaceHasStaticIP when no information about
|
||||
// the IP being static is available.
|
||||
const ErrNoStaticIPInfo agherr.Error = "no information about static ip"
|
||||
|
||||
// IfaceHasStaticIP checks if interface is configured to have static IP address.
|
||||
// If it can't give a definitive answer, it returns false and an error for which
|
||||
// errors.Is(err, ErrNoStaticIPInfo) is true.
|
||||
func IfaceHasStaticIP(ifaceName string) (has bool, err error) {
|
||||
return ifaceHasStaticIP(ifaceName)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,11 @@ import (
|
||||
const maxConfigFileSize = 1024 * 1024
|
||||
|
||||
func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
|
||||
var f *os.File
|
||||
// TODO(a.garipov): Currently, this function returns the first
|
||||
// definitive result. So if /etc/dhcpcd.conf has a static IP while
|
||||
// /etc/network/interfaces doesn't, it will return true. Perhaps this
|
||||
// is not the most desirable behavior.
|
||||
|
||||
for _, check := range []struct {
|
||||
checker func(io.Reader, string) (bool, error)
|
||||
filePath string
|
||||
@@ -32,28 +36,37 @@ func ifaceHasStaticIP(ifaceName string) (has bool, err error) {
|
||||
checker: ifacesStaticConfig,
|
||||
filePath: "/etc/network/interfaces",
|
||||
}} {
|
||||
var f *os.File
|
||||
f, err = os.Open(check.filePath)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
// ErrNotExist can happen here if there is no such file.
|
||||
// This is normal, as not every system uses those files.
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
err = nil
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fileReadCloser, err := aghio.LimitReadCloser(f, maxConfigFileSize)
|
||||
var fileReadCloser io.ReadCloser
|
||||
fileReadCloser, err = aghio.LimitReadCloser(f, maxConfigFileSize)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer fileReadCloser.Close()
|
||||
|
||||
has, err = check.checker(fileReadCloser, ifaceName)
|
||||
if has || err != nil {
|
||||
break
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return has, nil
|
||||
}
|
||||
|
||||
return has, err
|
||||
return false, ErrNoStaticIPInfo
|
||||
}
|
||||
|
||||
// dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
|
||||
## v0.105: API changes
|
||||
|
||||
### New `"client_id"` field in `GET /querylog` response
|
||||
|
||||
* The new field `"client_id"` of `QueryLogItem` objects is the ID sent by the
|
||||
client for encrypted requests, if there was any. See the
|
||||
"[Identifying clients]" section of our wiki.
|
||||
|
||||
### New `"dnscrypt"` `"client_proto"` value in `GET /querylog` response
|
||||
|
||||
* The field `"client_proto"` can now have the value `"dnscrypt"` when the
|
||||
@@ -69,6 +75,8 @@
|
||||
|
||||
As well as other documentation fixes.
|
||||
|
||||
[Identifying clients]: https://github.com/AdguardTeam/AdGuardHome/wiki/Clients#idclient
|
||||
|
||||
## v0.103: API changes
|
||||
|
||||
### API: replace settings in GET /control/dns_info & POST /control/dns_config
|
||||
|
||||
@@ -228,7 +228,13 @@ main() {
|
||||
|
||||
download "${URL}" "${PKG_NAME}" || error_exit "Cannot download the package"
|
||||
|
||||
unpack "${PKG_NAME}" "${OUT_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package"
|
||||
if [ "${OS}" = "darwin" ]; then
|
||||
# TODO: remove this after v0.106.0 release
|
||||
mkdir "${AGH_DIR}"
|
||||
unpack "${PKG_NAME}" "${AGH_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package"
|
||||
else
|
||||
unpack "${PKG_NAME}" "${OUT_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package"
|
||||
fi
|
||||
|
||||
# Install AdGuard Home service and run it.
|
||||
( cd "${AGH_DIR}" && ./AdGuardHome -s install || error_exit "Cannot install AdGuardHome as a service" )
|
||||
|
||||
@@ -17,7 +17,15 @@ set -e -f -u
|
||||
readonly channel="$CHANNEL"
|
||||
readonly commit="$COMMIT"
|
||||
readonly dist_dir="$DIST_DIR"
|
||||
readonly version="$VERSION"
|
||||
|
||||
if [ "${VERSION:-}" = 'v0.0.0' -o "${VERSION:-}" = '' ]
|
||||
then
|
||||
readonly version="$(sh ./scripts/make/version.sh)"
|
||||
else
|
||||
readonly version="$VERSION"
|
||||
fi
|
||||
|
||||
echo $version
|
||||
|
||||
# Allow users to use sudo.
|
||||
readonly sudo_cmd="${SUDO:-}"
|
||||
|
||||
468
scripts/translations/package-lock.json
generated
468
scripts/translations/package-lock.json
generated
@@ -1,8 +1,466 @@
|
||||
{
|
||||
"name": "translations",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 1,
|
||||
"version": "0.2.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.2.0",
|
||||
"dependencies": {
|
||||
"request": "^2.88.0",
|
||||
"request-promise": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.5.5",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz",
|
||||
"integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^2.0.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
|
||||
"dependencies": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"node_modules/aws-sign2": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/aws4": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
|
||||
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
|
||||
},
|
||||
"node_modules/bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
|
||||
"dependencies": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz",
|
||||
"integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw=="
|
||||
},
|
||||
"node_modules/caseless": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
|
||||
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"node_modules/dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ecc-jsbn": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
|
||||
"dependencies": {
|
||||
"jsbn": "~0.1.0",
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||
},
|
||||
"node_modules/extsprintf": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
]
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
|
||||
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
|
||||
},
|
||||
"node_modules/fast-json-stable-stringify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
|
||||
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
|
||||
},
|
||||
"node_modules/forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.6",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/getpass": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/har-schema": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/har-validator": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
||||
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
||||
"dependencies": {
|
||||
"ajv": "^6.5.5",
|
||||
"har-schema": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/http-signature": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"jsprim": "^1.2.2",
|
||||
"sshpk": "^1.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8",
|
||||
"npm": ">=1.3.7"
|
||||
}
|
||||
},
|
||||
"node_modules/is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
|
||||
},
|
||||
"node_modules/isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
||||
},
|
||||
"node_modules/jsbn": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
|
||||
},
|
||||
"node_modules/json-schema": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
},
|
||||
"node_modules/json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
|
||||
},
|
||||
"node_modules/jsprim": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"dependencies": {
|
||||
"assert-plus": "1.0.0",
|
||||
"extsprintf": "1.3.0",
|
||||
"json-schema": "0.2.3",
|
||||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.37.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
|
||||
"integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.21",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
|
||||
"integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
|
||||
"dependencies": {
|
||||
"mime-db": "~1.37.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.1.29",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
|
||||
"integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/request": {
|
||||
"version": "2.88.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
|
||||
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
|
||||
"dependencies": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
"aws4": "^1.8.0",
|
||||
"caseless": "~0.12.0",
|
||||
"combined-stream": "~1.0.6",
|
||||
"extend": "~3.0.2",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~2.3.2",
|
||||
"har-validator": "~5.1.0",
|
||||
"http-signature": "~1.2.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.19",
|
||||
"oauth-sign": "~0.9.0",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.5.2",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.4.3",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/request-promise": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz",
|
||||
"integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=",
|
||||
"dependencies": {
|
||||
"bluebird": "^3.5.0",
|
||||
"request-promise-core": "1.1.1",
|
||||
"stealthy-require": "^1.1.0",
|
||||
"tough-cookie": ">=2.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/request-promise-core": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz",
|
||||
"integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=",
|
||||
"dependencies": {
|
||||
"lodash": "^4.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sshpk": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz",
|
||||
"integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==",
|
||||
"dependencies": {
|
||||
"asn1": "~0.2.3",
|
||||
"assert-plus": "^1.0.0",
|
||||
"bcrypt-pbkdf": "^1.0.0",
|
||||
"dashdash": "^1.12.0",
|
||||
"ecc-jsbn": "~0.1.1",
|
||||
"getpass": "^0.1.1",
|
||||
"jsbn": "~0.1.0",
|
||||
"safer-buffer": "^2.0.2",
|
||||
"tweetnacl": "~0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stealthy-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
||||
"dependencies": {
|
||||
"psl": "^1.1.24",
|
||||
"punycode": "^1.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
|
||||
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js/node_modules/punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
|
||||
"bin": {
|
||||
"uuid": "bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
"extsprintf": "^1.2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "6.5.5",
|
||||
@@ -205,9 +663,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.37.0",
|
||||
|
||||
@@ -10,7 +10,9 @@ initialisms = [
|
||||
, "MX"
|
||||
, "PTR"
|
||||
, "QUIC"
|
||||
, "RA"
|
||||
, "SDNS"
|
||||
, "SLAAC"
|
||||
, "SVCB"
|
||||
]
|
||||
dot_import_whitelist = []
|
||||
|
||||
Reference in New Issue
Block a user