diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 5603c7d794e..9aaebca40ec 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -1,11 +1,9 @@ --- name: Bug report about: Create a bug report to help us improve AdGuard Home - --- - +Have a question or an idea? Please search it [on our forum](https://github.com/AdguardTeam/AdGuardHome/discussions) to make sure it was not yet asked. If you cannot find what you had in mind, please [submit it here](https://github.com/AdguardTeam/AdGuardHome/discussions/new). ### Prerequisites @@ -17,16 +15,18 @@ Please answer the following questions for yourself before submitting an issue. * ### Issue Details - + * **Version of AdGuard Home server:** - * + * * **How did you install AdGuard Home:** - * + * * **How did you setup DNS configuration:** * * **If it's a router or IoT, please write device model:** * +* **CPU architecture:** + * * **Operating system and version:** * diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index 4937f5b6c03..094531b3b2e 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -1,11 +1,9 @@ --- name: Feature request -about: Suggest an idea for AdGuard Home - +about: Suggest a feature request for AdGuard Home --- - +Have a question or an idea? Please search it [on our forum](https://github.com/AdguardTeam/AdGuardHome/discussions) to make sure it was not yet asked. If you cannot find what you had in mind, please [submit it here](https://github.com/AdguardTeam/AdGuardHome/discussions/new). ### Prerequisites diff --git a/.github/stale.yml b/.github/stale.yml index 5c7c4caa03c..518db8b932c 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,20 +1,22 @@ # Number of days of inactivity before an issue becomes stale. -'daysUntilStale': 60 +'daysUntilStale': 90 # Number of days of inactivity before a stale issue is closed. -'daysUntilClose': 7 +'daysUntilClose': 15 # Issues with these labels will never be considered stale. 'exemptLabels': - 'bug' - 'enhancement' - 'feature request' - 'localization' +- 'needs investigation' - 'recurrent' +- 'research' # Label to use when marking an issue as stale. 'staleLabel': 'wontfix' # Comment to post when marking an issue as stale. Set to `false` to disable. 'markComment': > This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you + recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable. 'closeComment': false diff --git a/CHANGELOG.md b/CHANGELOG.md index f16494d9d3d..a409cd8a2ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,14 +14,78 @@ and this project adheres to --> ### Fixed +- Incomplete hostnames with trailing zero-bytes handling ([#2582]). +- Wrong DNS-over-TLS ALPN configuration ([#2681]). +- Inconsistent responses for messages with EDNS0 and AD when DNS caching is + enabled ([#2600]). +- Incomplete OpenWRT detection ([#2757]). +- DHCP lease's `expired` field incorrect time format ([#2692]). +- Incomplete DNS upstreams validation ([#2674]). +- Wrong parsing of DHCP options of the `ip` type ([#2688]). + +[#2582]: https://github.com/AdguardTeam/AdGuardHome/issues/2582 +[#2600]: https://github.com/AdguardTeam/AdGuardHome/issues/2600 +[#2674]: https://github.com/AdguardTeam/AdGuardHome/issues/2674 +[#2681]: https://github.com/AdguardTeam/AdGuardHome/issues/2681 +[#2688]: https://github.com/AdguardTeam/AdGuardHome/issues/2688 +[#2692]: https://github.com/AdguardTeam/AdGuardHome/issues/2692 +[#2757]: https://github.com/AdguardTeam/AdGuardHome/issues/2757 + +### Security + +- Session token doesn't contain user's information anymore ([#2470]). + +[#2470]: https://github.com/AdguardTeam/AdGuardHome/issues/2470 + + + +## [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 @@ -161,10 +225,12 @@ and this project adheres to + [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 ---> -[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 [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 diff --git a/README.md b/README.md index 519119e91b1..f9ee5dae307 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/client/src/__locales/be.json b/client/src/__locales/be.json index e2d35a79e63..b89af602584 100644 --- a/client/src/__locales/be.json +++ b/client/src/__locales/be.json @@ -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": "Розныя кліенты могуць ідэнтыфікавацца па адмысловым ідэнтыфікатары кліента. Тут вы можаце даведацца больш пра ідэнтыфікацыю кліентаў.", "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-серверам", "form_enter_ip": "Увядзіце IP", "form_enter_mac": "Увядзіце MAC", "form_enter_id": "Увядзіце ідэнтыфікатар", @@ -427,6 +437,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy падтрымвае <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox падтрымвае <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Вы можаце знайсці яшчэ варыянты <0>тут і <1>тут.", + "setup_dns_privacy_ioc_mac": "Канфігурацыя для iOS і macOS", "setup_dns_notice": "Каб выкарыстоўваць <1>DNS-over-HTTPS ці <1>DNS-over-TLS, вам патрэбна <0>наладзіць шыфраванне у наладах AdGuard Home.", "rewrite_added": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова дададзена", "rewrite_deleted": "Правіла перанакіравання DNS для \"{{key}}\" паспяхова выдалена", diff --git a/client/src/__locales/cs.json b/client/src/__locales/cs.json index ca5d8a5c82b..84cf3e32e52 100644 --- a/client/src/__locales/cs.json +++ b/client/src/__locales/cs.json @@ -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", diff --git a/client/src/__locales/da.json b/client/src/__locales/da.json index 97105ad365c..3ba7f9b4a26 100644 --- a/client/src/__locales/da.json +++ b/client/src/__locales/da.json @@ -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", diff --git a/client/src/__locales/de.json b/client/src/__locales/de.json index 7e35661e1a8..17d5832ee8a 100644 --- a/client/src/__locales/de.json +++ b/client/src/__locales/de.json @@ -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", diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 697a7307442..3b0aa663425 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -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", diff --git a/client/src/__locales/es.json b/client/src/__locales/es.json index 3be25fac3bf..cf0d7d024e8 100644 --- a/client/src/__locales/es.json +++ b/client/src/__locales/es.json @@ -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. Aquí 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. Aquí 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í 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í 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 soporta <1>DNS mediante HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox soporta <1>DNS mediante HTTPS.", "setup_dns_privacy_other_5": "Encontrarás más implementaciones <0>aquí y <1>aquí.", - "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 o <1>DNS mediante TLS, debes <0>configurar el cifrado en la configuración de AdGuard Home.", "rewrite_added": "Reescritura DNS para \"{{key}}\" añadido correctamente", "rewrite_deleted": "Reescritura DNS para \"{{key}}\" eliminado correctamente", diff --git a/client/src/__locales/fr.json b/client/src/__locales/fr.json index 36ec94e7d22..064f4ccc628 100644 --- a/client/src/__locales/fr.json +++ b/client/src/__locales/fr.json @@ -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", diff --git a/client/src/__locales/hr.json b/client/src/__locales/hr.json index 7b02b4d01f3..ab5567f0fbe 100644 --- a/client/src/__locales/hr.json +++ b/client/src/__locales/hr.json @@ -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. Ovdje 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", "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 podržava <1>DNS-over-HTTPS.", "setup_dns_privacy_other_4": "<0>Mozilla Firefox podržava <1>DNS-over-HTTPS.", "setup_dns_privacy_other_5": "Možete pronaći više implementacija <0>ovdje i <1>ovdje.", + "setup_dns_privacy_ioc_mac": "konfiguracija za iOS i macOS", "setup_dns_notice": "Da biste koristili <1>DNS-over-HTTPS ili <1>DNS-over-TLS, morate <0>postaviti šifriranje u AdGuard Home postavkama.", "rewrite_added": "DNS prijepis za \"{{key}}\" je uspješno dodan", "rewrite_deleted": "DNS prijepis za \"{{key}}\" je uspješno uklonjen", diff --git a/client/src/__locales/it.json b/client/src/__locales/it.json index 5079165bd41..1f3a02078d4 100644 --- a/client/src/__locales/it.json +++ b/client/src/__locales/it.json @@ -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", diff --git a/client/src/__locales/ja.json b/client/src/__locales/ja.json index 6c4ef890246..7eb453260f5 100644 --- a/client/src/__locales/ja.json +++ b/client/src/__locales/ja.json @@ -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", diff --git a/client/src/__locales/ko.json b/client/src/__locales/ko.json index c266c8c4d23..2f25cbc6ffa 100644 --- a/client/src/__locales/ko.json +++ b/client/src/__locales/ko.json @@ -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", diff --git a/client/src/__locales/nl.json b/client/src/__locales/nl.json index d5be0f992c8..b903ced69ff 100644 --- a/client/src/__locales/nl.json +++ b/client/src/__locales/nl.json @@ -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", diff --git a/client/src/__locales/no.json b/client/src/__locales/no.json index 58637b4a3d6..dde12de0b0f 100644 --- a/client/src/__locales/no.json +++ b/client/src/__locales/no.json @@ -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", diff --git a/client/src/__locales/pl.json b/client/src/__locales/pl.json index 55f172b3bbe..e44ef6d4e04 100644 --- a/client/src/__locales/pl.json +++ b/client/src/__locales/pl.json @@ -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", diff --git a/client/src/__locales/pt-br.json b/client/src/__locales/pt-br.json index 01a3a933dea..844aa1b6063 100644 --- a/client/src/__locales/pt-br.json +++ b/client/src/__locales/pt-br.json @@ -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", diff --git a/client/src/__locales/pt-pt.json b/client/src/__locales/pt-pt.json index e8c70cc941d..8e08db076df 100644 --- a/client/src/__locales/pt-pt.json +++ b/client/src/__locales/pt-pt.json @@ -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", diff --git a/client/src/__locales/ro.json b/client/src/__locales/ro.json index 6523bd30dfc..80929301d3d 100644 --- a/client/src/__locales/ro.json +++ b/client/src/__locales/ro.json @@ -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", diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json index ea58c7f341d..1b9f0a39c8f 100644 --- a/client/src/__locales/ru.json +++ b/client/src/__locales/ru.json @@ -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", diff --git a/client/src/__locales/si-lk.json b/client/src/__locales/si-lk.json index 015c477c497..429093551d6 100644 --- a/client/src/__locales/si-lk.json +++ b/client/src/__locales/si-lk.json @@ -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": "උදාහරණ", diff --git a/client/src/__locales/sk.json b/client/src/__locales/sk.json index af88853a476..7c0d4fa9964 100644 --- a/client/src/__locales/sk.json +++ b/client/src/__locales/sk.json @@ -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", diff --git a/client/src/__locales/sl.json b/client/src/__locales/sl.json index 4a35e53a957..007283bb616 100644 --- a/client/src/__locales/sl.json +++ b/client/src/__locales/sl.json @@ -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", diff --git a/client/src/__locales/tr.json b/client/src/__locales/tr.json index 0276740d1a9..04b067293ac 100644 --- a/client/src/__locales/tr.json +++ b/client/src/__locales/tr.json @@ -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", diff --git a/client/src/__locales/vi.json b/client/src/__locales/vi.json index 85a5df2f3a0..368b0f9aef4 100644 --- a/client/src/__locales/vi.json +++ b/client/src/__locales/vi.json @@ -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", diff --git a/client/src/__locales/zh-cn.json b/client/src/__locales/zh-cn.json index 54b72644d12..edf26ede87b 100644 --- a/client/src/__locales/zh-cn.json +++ b/client/src/__locales/zh-cn.json @@ -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", diff --git a/client/src/__locales/zh-tw.json b/client/src/__locales/zh-tw.json index 258c661cde4..84df5c3eeb8 100644 --- a/client/src/__locales/zh-tw.json +++ b/client/src/__locales/zh-tw.json @@ -1,6 +1,6 @@ { "client_settings": "用戶端設定", - "example_upstream_reserved": "您可以<0>為特定網域指定上游 DNS", + "example_upstream_reserved": "您可<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>於此,您可了解更多關於如何識別用戶端。", "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>於此,您可了解更多關於如何識別用戶端。", "form_enter_ip": "輸入 IP", "form_enter_mac": "輸入媒體存取控制(MAC)", "form_enter_id": "輸入識別碼", @@ -429,6 +437,7 @@ "setup_dns_privacy_other_3": "<0>dnscrypt-proxy 支援 <1>DNS-over-HTTPS。", "setup_dns_privacy_other_4": "<0>Mozilla Firefox 支援 <1>DNS-over-HTTPS。", "setup_dns_privacy_other_5": "在<0>這裡和<1>這裡,您將發現更多的執行。", + "setup_dns_privacy_ioc_mac": "iOS 和 macOS 配置", "setup_dns_notice": "為了使用 <1>DNS-over-HTTPS 或 <1>DNS-over-TLS,您需要在 AdGuard Home 設定裡<0>配置加密。", "rewrite_added": "對於 \"{{key}}\" 之 DNS 改寫被成功地加入", "rewrite_deleted": "對於 \"{{key}}\" 之 DNS 改寫被成功地刪除", diff --git a/client/src/components/App/index.css b/client/src/components/App/index.css index 2b1ee76dbff..990f13e438d 100644 --- a/client/src/components/App/index.css +++ b/client/src/components/App/index.css @@ -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); } } diff --git a/client/src/components/ui/texareaCommentsHighlight.css b/client/src/components/ui/texareaCommentsHighlight.css index 8551966da8c..19c84faca97 100644 --- a/client/src/components/ui/texareaCommentsHighlight.css +++ b/client/src/components/ui/texareaCommentsHighlight.css @@ -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; } diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index bab8c925f2c..0c9919ac3c5 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -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', diff --git a/client/src/install/Setup/Setup.css b/client/src/install/Setup/Setup.css index 522d57c1698..b08f4cb8e92 100644 --- a/client/src/install/Setup/Setup.css +++ b/client/src/install/Setup/Setup.css @@ -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; } } diff --git a/client/src/login/Login/Login.css b/client/src/login/Login/Login.css index a6b84e96a98..0d3dbfc19b9 100644 --- a/client/src/login/Login/Login.css +++ b/client/src/login/Login/Login.css @@ -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; } } diff --git a/go.mod b/go.mod index b8d18f43d1e..03afd7baba7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome go 1.14 require ( - github.com/AdguardTeam/dnsproxy v0.33.9 + github.com/AdguardTeam/dnsproxy v0.35.4 github.com/AdguardTeam/golibs v0.4.4 github.com/AdguardTeam/urlfilter v0.14.3 github.com/NYTimes/gziphandler v1.1.1 diff --git a/go.sum b/go.sum index 4afc5ca7150..8a91875fe66 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/AdguardTeam/dnsproxy v0.33.9 h1:HUwywkhUV/M73E7qWcBAF+SdsNq742s82Lvox4pr/tM= -github.com/AdguardTeam/dnsproxy v0.33.9/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs= +github.com/AdguardTeam/dnsproxy v0.35.4 h1:zuVVtDouhYXXWHb8Zp0oCcVMop6PDCQ9PosQ5fJApsg= +github.com/AdguardTeam/dnsproxy v0.35.4/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o= github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= diff --git a/internal/aghio/limitedreadcloser_test.go b/internal/aghio/limitedreadcloser_test.go index 1f10e32bf7e..9cccda17538 100644 --- a/internal/aghio/limitedreadcloser_test.go +++ b/internal/aghio/limitedreadcloser_test.go @@ -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) }) } } diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index 185099910e5..7a48d239f93 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -19,7 +19,12 @@ import ( const ( defaultDiscoverTime = time.Second * 3 - leaseExpireStatic = 1 + // leaseExpireStatic is used to define the Expiry field for static + // leases. + // + // TODO(e.burkov): Remove it when static leases determining mechanism + // will be improved. + leaseExpireStatic = 1 ) var webHandlersRegistered = false @@ -37,12 +42,24 @@ type Lease struct { // MarshalJSON implements the json.Marshaler interface for *Lease. func (l *Lease) MarshalJSON() ([]byte, error) { + var expiryStr string + if expiry := l.Expiry; expiry.Unix() != leaseExpireStatic { + // The front-end is waiting for RFC 3999 format of the time + // value. It also shouldn't got an Expiry field for static + // leases. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/2692. + expiryStr = expiry.Format(time.RFC3339) + } + type lease Lease return json.Marshal(&struct { HWAddr string `json:"mac"` + Expiry string `json:"expires,omitempty"` *lease }{ HWAddr: l.HWAddr.String(), + Expiry: expiryStr, lease: (*lease)(l), }) } @@ -117,14 +134,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 +162,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 +171,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 +189,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 @@ -245,14 +265,10 @@ const ( LeasesAll = LeasesDynamic | LeasesStatic ) -// Leases returns the list of current DHCP leases (thread-safe) -func (s *Server) Leases(flags int) []Lease { - result := s.srv4.GetLeases(flags) - - v6leases := s.srv6.GetLeases(flags) - result = append(result, v6leases...) - - return result +// Leases returns the list of active IPv4 and IPv6 DHCP leases. It's safe for +// concurrent use. +func (s *Server) Leases(flags int) (leases []Lease) { + return append(s.srv4.GetLeases(flags), s.srv6.GetLeases(flags)...) } // FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases @@ -290,14 +306,22 @@ func parseOptionString(s string) (uint8, []byte) { if err != nil { return 0, nil } - case "ip": ip := net.ParseIP(sval) if ip == nil { return 0, nil } - val = ip + // Most DHCP options require IPv4, so do not put the 16-byte + // version if we can. Otherwise, the clients will receive weird + // data that looks like four IPv4 addresses. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/2688. + if ip4 := ip.To4(); ip4 != nil { + val = ip4 + } else { + val = ip + } default: return 0, nil } diff --git a/internal/dhcpd/dhcpd_test.go b/internal/dhcpd/dhcpd_test.go index 1aa1b9a6ab5..cca733d9310 100644 --- a/internal/dhcpd/dhcpd_test.go +++ b/internal/dhcpd/dhcpd_test.go @@ -3,7 +3,6 @@ package dhcpd import ( - "bytes" "net" "os" "testing" @@ -11,6 +10,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 +20,171 @@ 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{} - - 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) + dynLeases := []*Lease{{ + HWAddr: net.HardwareAddr{1, 2, 3, 4}, + }, { + HWAddr: net.HardwareAddr{1, 2, 3, 5}, + }} + + 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)) - - 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) + testCases := []struct { + name string + optStr string + wantVal []byte + wantCode uint8 + }{{ + name: "success_hex", + optStr: "12 hex abcdef", + wantVal: []byte{0xab, 0xcd, 0xef}, + wantCode: 12, + }, { + name: "bad_hex", + optStr: "12 hex abcdefx", + wantVal: nil, + wantCode: 0, + }, { + name: "success_ip", + optStr: "123 ip 1.2.3.4", + wantVal: net.IP{1, 2, 3, 4}, + wantCode: 123, + }, { + name: "success_ipv6", + optStr: "123 ip ::1234", + wantVal: net.IP{ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0x12, 0x34, + }, + wantCode: 123, + }, { + name: "bad_code", + optStr: "256 ip 1.1.1.1", + wantVal: nil, + wantCode: 0, + }, { + name: "negative_code", + optStr: "-1 ip 1.1.1.1", + wantVal: nil, + wantCode: 0, + }, { + name: "bad_ip", + optStr: "12 ip 1.1.1.1x", + wantVal: nil, + wantCode: 0, + }, { + name: "bad_mode", + wantVal: nil, + optStr: "12 x 1.1.1.1", + wantCode: 0, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + code, val := parseOptionString(tc.optStr) + require.Equal(t, tc.wantCode, code) + if tc.wantVal != nil { + assert.Equal(t, tc.wantVal, val) + } + }) + } } diff --git a/internal/dhcpd/dhcphttp.go b/internal/dhcpd/http.go similarity index 79% rename from internal/dhcpd/dhcphttp.go rename to internal/dhcpd/http.go index b6b5c729f1a..2dab11321d3 100644 --- a/internal/dhcpd/dhcphttp.go +++ b/internal/dhcpd/http.go @@ -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 } +type dhcpServerConfigJSON struct { + V4 *v4ServerConfJSON `json:"v4"` + V6 *v6ServerConfJSON `json:"v6"` + InterfaceName string `json:"interface_name"` + Enabled nullBool `json:"enabled"` +} + func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { - newconfig := dhcpServerConfigJSON{} - newconfig.Enabled = s.conf.Enabled - newconfig.InterfaceName = s.conf.InterfaceName + conf := dhcpServerConfigJSON{} + conf.Enabled = boolToNullBool(s.conf.Enabled) + conf.InterfaceName = s.conf.InterfaceName - js, err := jsonutil.DecodeObject(&newconfig, r.Body) + 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 + + v6Enabled = v6Conf.Enabled + v6Conf.InterfaceName = conf.InterfaceName + v6Conf.notify = s.onNotify - s6, err = v6Create(v6conf) + 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) diff --git a/internal/dhcpd/dhcphttp_test.go b/internal/dhcpd/http_test.go similarity index 87% rename from internal/dhcpd/dhcphttp_test.go rename to internal/dhcpd/http_test.go index 47b926dcc1a..36a89a6ef7a 100644 --- a/internal/dhcpd/dhcphttp_test.go +++ b/internal/dhcpd/http_test.go @@ -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) diff --git a/internal/dhcpd/nullbool.go b/internal/dhcpd/nullbool.go new file mode 100644 index 00000000000..b07f6768e07 --- /dev/null +++ b/internal/dhcpd/nullbool.go @@ -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 +} diff --git a/internal/dhcpd/nullbool_test.go b/internal/dhcpd/nullbool_test.go new file mode 100644 index 00000000000..2570dd44ffb --- /dev/null +++ b/internal/dhcpd/nullbool_test.go @@ -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) + }) +} diff --git a/internal/dhcpd/routeradv.go b/internal/dhcpd/routeradv.go index f1d63c7d554..59aad9d1398 100644 --- a/internal/dhcpd/routeradv.go +++ b/internal/dhcpd/routeradv.go @@ -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, diff --git a/internal/dhcpd/routeradv_test.go b/internal/dhcpd/routeradv_test.go index 95f3d4fa25d..4a0f4c5bd6e 100644 --- a/internal/dhcpd/routeradv_test.go +++ b/internal/dhcpd/routeradv_test.go @@ -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) } diff --git a/internal/dhcpd/server.go b/internal/dhcpd/server.go index 4adbca5ac07..2fac533e85c 100644 --- a/internal/dhcpd/server.go +++ b/internal/dhcpd/server.go @@ -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 diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go index 2f5484a2446..7d24699e76e 100644 --- a/internal/dhcpd/v4.go +++ b/internal/dhcpd/v4.go @@ -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 } diff --git a/internal/dhcpd/v46_test.go b/internal/dhcpd/v46_test.go index 6007205dfcb..6495eeeeda2 100644 --- a/internal/dhcpd/v46_test.go +++ b/internal/dhcpd/v46_test.go @@ -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)) }) } } diff --git a/internal/dhcpd/v4_test.go b/internal/dhcpd/v4_test.go index 8edb31137ee..d204a200617 100644 --- a/internal/dhcpd/v4_test.go +++ b/internal/dhcpd/v4_test.go @@ -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) - - // 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) - - // 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) + require.True(t, ok) + + 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}, + }} + + 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) - - 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()) + require.Len(t, ls, 2) - 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)) - - // "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)) - - // 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()) - - // "Request" - req, _ = dhcpv4.NewRequestFromOffer(resp) - resp, _ = dhcpv4.NewReplyFromRequest(req) - assert.Equal(t, 1, s.process(req, resp)) - - // 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()) + l := Lease{ + IP: net.IP{192, 168, 10, 150}, + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + } + require.Nil(t, s.AddStaticLease(l)) + + var req, resp *dhcpv4.DHCPv4 + mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} + + t.Run("discover", func(t *testing.T) { + var err error + + req, err = dhcpv4.NewDiscovery(mac) + 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("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])) - - // 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()) + require.Len(t, dnsAddrs, 1) + assert.True(t, s.conf.GatewayIP.Equal(dnsAddrs[0])) + + 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)) - - // 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)]))) - - // "Request" - req, _ = dhcpv4.NewRequestFromOffer(resp) - resp, _ = dhcpv4.NewReplyFromRequest(req) - assert.Equal(t, 1, s.process(req, resp)) - - // 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()) + var req, resp *dhcpv4.DHCPv4 + mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} + + t.Run("discover", func(t *testing.T) { + req, err = dhcpv4.NewDiscovery(mac) + 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("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)) + }) + } } diff --git a/internal/dhcpd/v6.go b/internal/dhcpd/v6.go index 517885513f8..0b43d6d1cfc 100644 --- a/internal/dhcpd/v6.go +++ b/internal/dhcpd/v6.go @@ -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 } diff --git a/internal/dhcpd/v6_test.go b/internal/dhcpd/v6_test.go index 9cdf3ee4465..3eb06a89895 100644 --- a/internal/dhcpd/v6_test.go +++ b/internal/dhcpd/v6_test.go @@ -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) - - // 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 lease - { - ld := Lease{} - ld.IP = net.ParseIP("2001::2") - ld.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa") - s.addLease(&ld) + require.True(t, ok) + + // 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}, + }} + + 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)) - - // "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)) + + l := Lease{ + IP: net.ParseIP("2001::1"), + HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + } + require.Nil(t, s.AddStaticLease(l)) + + 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()) - - // "Request" - req, _ = dhcpv6.NewRequestFromAdvertise(resp) - msg, _ = req.GetInnerMessage() - resp, _ = dhcpv6.NewReplyFromMessage(msg) - assert.True(t, s.process(msg, req, resp)) - - // 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()) + 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() - dnsAddrs := resp.Options.DNS() - assert.Len(t, dnsAddrs, 1) - assert.Equal(t, "2000::1", dnsAddrs[0].String()) + assert.Equal(t, l.IP, oiaAddr.IPv6Addr) + assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds()) + }) - // 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("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() + require.Len(t, dnsAddrs, 1) + assert.Equal(t, dnsAddr, dnsAddrs[0]) + + 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")} + 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") - - // "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::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()) + }) + + t.Run("request", func(t *testing.T) { + req, err = dhcpv6.NewRequestFromAdvertise(resp) + require.Nil(t, err) + + msg, err = req.GetInnerMessage() + require.Nil(t, err) - // "Request" - req, _ = dhcpv6.NewRequestFromAdvertise(resp) - msg, _ = req.GetInnerMessage() - resp, _ = dhcpv6.NewReplyFromMessage(msg) - assert.True(t, s.process(msg, req, resp)) + resp, err = dhcpv6.NewReplyFromMessage(msg) + 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()) + 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()) - - // 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"))) + require.Len(t, dnsAddrs, 1) + assert.Equal(t, dnsAddr, dnsAddrs[0]) + + 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)) + }) + } } diff --git a/internal/dnsforward/clientid.go b/internal/dnsforward/clientid.go new file mode 100644 index 00000000000..c497c7b7fb9 --- /dev/null +++ b/internal/dnsforward/clientid.go @@ -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 +} diff --git a/internal/dnsforward/dns_test.go b/internal/dnsforward/clientid_test.go similarity index 73% rename from internal/dnsforward/dns_test.go rename to internal/dnsforward/clientid_test.go index bd0ef4ab686..503203f9737 100644 --- a/internal/dnsforward/dns_test.go +++ b/internal/dnsforward/clientid_test.go @@ -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()) } }) } diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index a80e7ad5ee7..fdc8e5e2052 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -276,14 +276,24 @@ func (s *Server) prepareUpstreamSettings() error { upstreams = s.conf.UpstreamDNS } upstreams = filterOutComments(upstreams) - upstreamConfig, err := proxy.ParseUpstreamsConfig(upstreams, s.conf.BootstrapDNS, DefaultTimeout) + upstreamConfig, err := proxy.ParseUpstreamsConfig(upstreams, + upstream.Options{ + Bootstrap: s.conf.BootstrapDNS, + Timeout: DefaultTimeout, + }, + ) if err != nil { return fmt.Errorf("dns: proxy.ParseUpstreamsConfig: %w", err) } if len(upstreamConfig.Upstreams) == 0 { - log.Info("Warning: no default upstream servers specified, using %v", defaultDNS) - uc, err := proxy.ParseUpstreamsConfig(defaultDNS, s.conf.BootstrapDNS, DefaultTimeout) + log.Info("warning: no default upstream servers specified, using %v", defaultDNS) + uc, err := proxy.ParseUpstreamsConfig(defaultDNS, + upstream.Options{ + Bootstrap: s.conf.BootstrapDNS, + Timeout: DefaultTimeout, + }, + ) if err != nil { return fmt.Errorf("dns: failed to parse default upstreams: %v", err) } diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index f8e7bff06e6..acc6aa8614c 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -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 diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index e7508bc8a49..0a2269dcfb7 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/utils" @@ -149,7 +150,7 @@ func (req *dnsConfig) checkBootstrap() (string, error) { return boot, fmt.Errorf("invalid bootstrap server address: empty") } - if _, err := upstream.NewResolver(boot, 0); err != nil { + if _, err := upstream.NewResolver(boot, upstream.Options{Timeout: 0}); err != nil { return boot, fmt.Errorf("invalid bootstrap server address: %w", err) } } @@ -314,6 +315,16 @@ func ValidateUpstreams(upstreams []string) error { return nil } + _, err := proxy.ParseUpstreamsConfig(upstreams, + upstream.Options{ + Bootstrap: []string{}, + Timeout: DefaultTimeout, + }, + ) + if err != nil { + return err + } + var defaultUpstreamFound bool for _, u := range upstreams { d, err := validateUpstream(u) diff --git a/internal/home/auth.go b/internal/home/auth.go index 26b577870d4..76e7e7b6cfe 100644 --- a/internal/home/auth.go +++ b/internal/home/auth.go @@ -2,13 +2,10 @@ package home import ( "crypto/rand" - "crypto/sha256" "encoding/binary" "encoding/hex" "encoding/json" "fmt" - "math" - "math/big" "net/http" "strings" "sync" @@ -20,8 +17,12 @@ import ( ) const ( - cookieTTL = 365 * 24 // in hours + // cookieTTL is given in hours. + cookieTTL = 365 * 24 sessionCookieName = "agh_session" + + // sessionTokenSize is the length of session token in bytes. + sessionTokenSize = 16 ) type session struct { @@ -285,16 +286,29 @@ type loginJSON struct { Password string `json:"password"` } -func getSession(u *User) ([]byte, error) { - maxSalt := big.NewInt(math.MaxUint32) - salt, err := rand.Int(rand.Reader, maxSalt) +// newSessionToken returns cryptographically secure randomly generated slice of +// bytes of sessionTokenSize length. +// +// TODO(e.burkov): Think about using byte array instead of byte slice. +func newSessionToken() (data []byte, err error) { + randData := make([]byte, sessionTokenSize) + + _, err = rand.Read(randData) if err != nil { return nil, err } - d := []byte(fmt.Sprintf("%s%s%s", salt, u.Name, u.PasswordHash)) - hash := sha256.Sum256(d) - return hash[:], nil + return randData, nil +} + +// cookieTimeFormat is the format to be used in (time.Time).Format for cookie's +// expiry field. +const cookieTimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT" + +// cookieExpiryFormat returns the formatted exp to be used in cookie string. +// It's quite simple for now, but probably will be expanded in the future. +func cookieExpiryFormat(exp time.Time) (formatted string) { + return exp.Format(cookieTimeFormat) } func (a *Auth) httpCookie(req loginJSON) (string, error) { @@ -303,24 +317,23 @@ func (a *Auth) httpCookie(req loginJSON) (string, error) { return "", nil } - sess, err := getSession(&u) + sess, err := newSessionToken() if err != nil { return "", err } now := time.Now().UTC() - expire := now.Add(cookieTTL * time.Hour) - expstr := expire.Format(time.RFC1123) - expstr = expstr[:len(expstr)-len("UTC")] // "UTC" -> "GMT" - expstr += "GMT" - - s := session{} - s.userName = u.Name - s.expire = uint32(now.Unix()) + a.sessionTTL - a.addSession(sess, &s) - - return fmt.Sprintf("%s=%s; Path=/; HttpOnly; Expires=%s", - sessionCookieName, hex.EncodeToString(sess), expstr), nil + + a.addSession(sess, &session{ + userName: u.Name, + expire: uint32(now.Unix()) + a.sessionTTL, + }) + + return fmt.Sprintf( + "%s=%s; Path=/; HttpOnly; Expires=%s", + sessionCookieName, hex.EncodeToString(sess), + cookieExpiryFormat(now.Add(cookieTTL*time.Hour)), + ), nil } func handleLogin(w http.ResponseWriter, r *http.Request) { diff --git a/internal/home/auth_test.go b/internal/home/auth_test.go index 4dbd07b6938..7dbbf3c62f7 100644 --- a/internal/home/auth_test.go +++ b/internal/home/auth_test.go @@ -1,6 +1,8 @@ package home import ( + "bytes" + "crypto/rand" "encoding/hex" "net/http" "net/url" @@ -11,6 +13,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMain(m *testing.M) { @@ -24,14 +27,34 @@ func prepareTestDir() string { return dir } +func TestNewSessionToken(t *testing.T) { + // Successful case. + token, err := newSessionToken() + require.Nil(t, err) + assert.Len(t, token, sessionTokenSize) + + // Break the rand.Reader. + prevReader := rand.Reader + t.Cleanup(func() { + rand.Reader = prevReader + }) + rand.Reader = &bytes.Buffer{} + + // Unsuccessful case. + token, err = newSessionToken() + require.NotNil(t, err) + assert.Empty(t, token) +} + func TestAuth(t *testing.T) { dir := prepareTestDir() - defer func() { _ = os.RemoveAll(dir) }() + t.Cleanup(func() { _ = os.RemoveAll(dir) }) fn := filepath.Join(dir, "sessions.db") - users := []User{ - {Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"}, - } + users := []User{{ + Name: "name", + PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2", + }} a := InitAuth(fn, nil, 60) s := session{} @@ -41,7 +64,7 @@ func TestAuth(t *testing.T) { assert.Equal(t, checkSessionNotFound, a.checkSession("notfound")) a.RemoveSession("notfound") - sess, err := getSession(&users[0]) + sess, err := newSessionToken() assert.Nil(t, err) sessStr := hex.EncodeToString(sess) diff --git a/internal/home/clients.go b/internal/home/clients.go index c3eb366f13f..6c314874bf6 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -17,6 +17,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/dnsproxy/proxy" + "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/utils" ) @@ -295,7 +296,12 @@ func (clients *clientsContainer) FindUpstreams(ip string) *proxy.UpstreamConfig } if c.upstreamConfig == nil { - config, err := proxy.ParseUpstreamsConfig(c.Upstreams, config.DNS.BootstrapDNS, dnsforward.DefaultTimeout) + config, err := proxy.ParseUpstreamsConfig(c.Upstreams, + upstream.Options{ + Bootstrap: config.DNS.BootstrapDNS, + Timeout: dnsforward.DefaultTimeout, + }, + ) if err == nil { c.upstreamConfig = &config } @@ -580,9 +586,22 @@ func (clients *clientsContainer) SetWhoisInfo(ip string, info [][]string) { log.Debug("clients: set whois info for auto-client with IP %s: %q", ip, info) } +// sanitizeHost proccesses a host to remove some special symbols causing errors. +// Logic may be expanded in the future. +func sanitizeHost(host string) (processed string) { + // cutset brings together all the deprecated sets. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/2582. + const cutset = "\x00" + + return strings.TrimRight(host, cutset) +} + // AddHost adds a new IP-hostname pairing. The priorities of the sources is // taken into account. ok is true if the pairing was added. func (clients *clientsContainer) AddHost(ip, host string, src clientSource) (ok bool, err error) { + host = sanitizeHost(host) + clients.lock.Lock() ok = clients.addHostLocked(ip, host, src) clients.lock.Unlock() diff --git a/internal/home/clients_test.go b/internal/home/clients_test.go index a098bf4c352..e6200ddd2ad 100644 --- a/internal/home/clients_test.go +++ b/internal/home/clients_test.go @@ -290,3 +290,29 @@ func TestClientsCustomUpstream(t *testing.T) { assert.Equal(t, 1, len(config.Upstreams)) assert.Equal(t, 1, len(config.DomainReservedUpstreams)) } + +func TestProcessHost(t *testing.T) { + const ( + name int = iota + host + want + + fieldsNum + ) + + testCases := [][fieldsNum]string{{ + name: "valid", + host: "abc", + want: "abc", + }, { + name: "with_trailing_zero_byte", + host: "abc\x00", + want: "abc", + }} + + for _, tc := range testCases { + t.Run(tc[name], func(t *testing.T) { + assert.Equal(t, tc[want], sanitizeHost(tc[host])) + }) + } +} diff --git a/internal/home/control.go b/internal/home/control.go index 71bf52e53f3..19876b12b81 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -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 } diff --git a/internal/home/home.go b/internal/home/home.go index 1b6312c8c60..bfc17750ad5 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -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 { @@ -315,6 +320,7 @@ func run(args options) { go func() { err := startDNSServer() if err != nil { + closeDNSServer() log.Fatal(err) } }() @@ -324,11 +330,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 diff --git a/internal/home/home_test.go b/internal/home/home_test.go index 02613b7c44e..033b8e26fe1 100644 --- a/internal/home/home_test.go +++ b/internal/home/home_test.go @@ -146,7 +146,7 @@ func TestHome(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) // test DNS over UDP - r, err := upstream.NewResolver("127.0.0.1:5354", 3*time.Second) + r, err := upstream.NewResolver("127.0.0.1:5354", upstream.Options{Timeout: 3 * time.Second}) assert.Nil(t, err) addrs, err := r.LookupIPAddr(context.TODO(), "static.adguard.com") assert.Nil(t, err) diff --git a/internal/home/ipdetector_test.go b/internal/home/ipdetector_test.go index ee20612f80e..6609ba08f28 100644 --- a/internal/home/ipdetector_test.go +++ b/internal/home/ipdetector_test.go @@ -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 diff --git a/internal/home/middlewares.go b/internal/home/middlewares.go index d530ace87c5..de38c3faa2c 100644 --- a/internal/home/middlewares.go +++ b/internal/home/middlewares.go @@ -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) diff --git a/internal/home/middlewares_test.go b/internal/home/middlewares_test.go index 53b7a933d1c..8397302bdb3 100644 --- a/internal/home/middlewares_test.go +++ b/internal/home/middlewares_test.go @@ -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) }) } } diff --git a/internal/home/web.go b/internal/home/web.go index 0048f42755a..e016e341e79 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -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, diff --git a/internal/querylog/qlog.go b/internal/querylog/qlog.go index 41ce98231e2..4726f075744 100644 --- a/internal/querylog/qlog.go +++ b/internal/querylog/qlog.go @@ -53,6 +53,7 @@ func NewClientProto(s string) (cp ClientProto, err error) { ClientProtoDOH, ClientProtoDOQ, ClientProtoDOT, + ClientProtoDNSCrypt, ClientProtoPlain: return cp, nil diff --git a/internal/stats/unit.go b/internal/stats/unit.go index d955d04fa48..96775c72037 100644 --- a/internal/stats/unit.go +++ b/internal/stats/unit.go @@ -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 diff --git a/internal/sysutil/net.go b/internal/sysutil/net.go index 0e3b448ebcb..9ba41704c1f 100644 --- a/internal/sysutil/net.go +++ b/internal/sysutil/net.go @@ -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) } diff --git a/internal/sysutil/net_linux.go b/internal/sysutil/net_linux.go index 8f47cf428fc..1964f9dda96 100644 --- a/internal/sysutil/net_linux.go +++ b/internal/sysutil/net_linux.go @@ -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 diff --git a/internal/util/autohosts.go b/internal/util/autohosts.go index 1bfdb44b2c9..e4cea58c65c 100644 --- a/internal/util/autohosts.go +++ b/internal/util/autohosts.go @@ -21,7 +21,7 @@ type onChangedT func() // AutoHosts - automatic DNS records type AutoHosts struct { // lock protects table and tableReverse. - lock sync.Mutex + lock sync.RWMutex // table is the host-to-IPs map. table map[string][]net.IP // tableReverse is the IP-to-hosts map. @@ -119,15 +119,14 @@ func (a *AutoHosts) Process(host string, qtype uint16) []net.IP { } var ipsCopy []net.IP - a.lock.Lock() + a.lock.RLock() + defer a.lock.RUnlock() if ips, ok := a.table[host]; ok { ipsCopy = make([]net.IP, len(ips)) copy(ipsCopy, ips) } - a.lock.Unlock() - log.Debug("AutoHosts: answer: %s -> %v", host, ipsCopy) return ipsCopy } @@ -145,8 +144,8 @@ func (a *AutoHosts) ProcessReverse(addr string, qtype uint16) (hosts []string) { ipStr := ipReal.String() - a.lock.Lock() - defer a.lock.Unlock() + a.lock.RLock() + defer a.lock.RUnlock() hosts = a.tableReverse[ipStr] @@ -161,8 +160,8 @@ func (a *AutoHosts) ProcessReverse(addr string, qtype uint16) (hosts []string) { // List returns an IP-to-hostnames table. It is safe for concurrent use. func (a *AutoHosts) List() (ipToHosts map[string][]string) { - a.lock.Lock() - defer a.lock.Unlock() + a.lock.RLock() + defer a.lock.RUnlock() ipToHosts = make(map[string][]string, len(a.tableReverse)) for k, v := range a.tableReverse { @@ -339,10 +338,13 @@ func (a *AutoHosts) updateHosts() { } } - a.lock.Lock() - a.table = table - a.tableReverse = tableRev - a.lock.Unlock() + func() { + a.lock.Lock() + defer a.lock.Unlock() + + a.table = table + a.tableReverse = tableRev + }() a.notify() } diff --git a/internal/util/helpers.go b/internal/util/helpers.go index b575b269dd3..1b759f45165 100644 --- a/internal/util/helpers.go +++ b/internal/util/helpers.go @@ -5,10 +5,12 @@ package util import ( + "bytes" "fmt" "io/ioutil" "os" "os/exec" + "path/filepath" "runtime" "strings" ) @@ -64,16 +66,43 @@ func SplitNext(str *string, splitBy byte) string { return strings.TrimSpace(s) } -// IsOpenWRT checks if OS is OpenWRT. +// IsOpenWRT returns true if host OS is OpenWRT. func IsOpenWRT() bool { if runtime.GOOS != "linux" { return false } - body, err := ioutil.ReadFile("/etc/os-release") + const etcDir = "/etc" + + // TODO(e.burkov): Take care of dealing with fs package after updating + // Go version to 1.16. + fileInfos, err := ioutil.ReadDir(etcDir) if err != nil { return false } - return strings.Contains(string(body), "OpenWrt") + // fNameSubstr is a part of a name of the desired file. + const fNameSubstr = "release" + osNameData := []byte("OpenWrt") + + for _, fileInfo := range fileInfos { + if fileInfo.IsDir() { + continue + } + + if !strings.Contains(fileInfo.Name(), fNameSubstr) { + continue + } + + body, err := ioutil.ReadFile(filepath.Join(etcDir, fileInfo.Name())) + if err != nil { + continue + } + + if bytes.Contains(body, osNameData) { + return true + } + } + + return false } diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index fab1099ebc1..7cbc626b351 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -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 diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 5de5a607df2..5892d704fdb 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1279,6 +1279,8 @@ 'type': 'string' 'edns_cs_enabled': 'type': 'boolean' + 'disable_ipv6': + 'type': 'boolean' 'dnssec_enabled': 'type': 'boolean' 'cache_size': diff --git a/scripts/install.sh b/scripts/install.sh index 551f84fc1f3..7a410cee9aa 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -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" ) diff --git a/scripts/make/build-docker.sh b/scripts/make/build-docker.sh index 29b86ba62d2..92adb4fb0c1 100644 --- a/scripts/make/build-docker.sh +++ b/scripts/make/build-docker.sh @@ -26,7 +26,6 @@ else fi echo $version -exit 0 # Allow users to use sudo. readonly sudo_cmd="${SUDO:-}" diff --git a/scripts/translations/package-lock.json b/scripts/translations/package-lock.json index 5361d8597db..42f5d23a230 100644 --- a/scripts/translations/package-lock.json +++ b/scripts/translations/package-lock.json @@ -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", diff --git a/staticcheck.conf b/staticcheck.conf index 146f83cb13b..4dd931764d5 100644 --- a/staticcheck.conf +++ b/staticcheck.conf @@ -10,7 +10,9 @@ initialisms = [ , "MX" , "PTR" , "QUIC" +, "RA" , "SDNS" +, "SLAAC" , "SVCB" ] dot_import_whitelist = []