mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-11-22 04:55:33 +03:00
all: sync with master; upd chlog
This commit is contained in:
parent
77cda2c2c5
commit
09718a2170
83 changed files with 1755 additions and 560 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -21,7 +21,6 @@
|
||||||
/snapcraft_login
|
/snapcraft_login
|
||||||
AdGuardHome*
|
AdGuardHome*
|
||||||
coverage.txt
|
coverage.txt
|
||||||
leases.db
|
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
!/build/gitkeep
|
!/build/gitkeep
|
||||||
|
|
47
CHANGELOG.md
47
CHANGELOG.md
|
@ -14,11 +14,11 @@ and this project adheres to
|
||||||
<!--
|
<!--
|
||||||
## [v0.108.0] - TBA
|
## [v0.108.0] - TBA
|
||||||
|
|
||||||
## [v0.107.29] - 2023-04-26 (APPROX.)
|
## [v0.107.30] - 2023-04-26 (APPROX.)
|
||||||
|
|
||||||
See also the [v0.107.29 GitHub milestone][ms-v0.107.29].
|
See also the [v0.107.30 GitHub milestone][ms-v0.107.30].
|
||||||
|
|
||||||
[ms-v0.107.29]: https://github.com/AdguardTeam/AdGuardHome/milestone/65?closed=1
|
[ms-v0.107.30]: https://github.com/AdguardTeam/AdGuardHome/milestone/66?closed=1
|
||||||
|
|
||||||
NOTE: Add new changes BELOW THIS COMMENT.
|
NOTE: Add new changes BELOW THIS COMMENT.
|
||||||
-->
|
-->
|
||||||
|
@ -29,6 +29,38 @@ NOTE: Add new changes ABOVE THIS COMMENT.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.107.29] - 2023-04-18
|
||||||
|
|
||||||
|
See also the [v0.107.29 GitHub milestone][ms-v0.107.29].
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The ability to exclude client activity from the query log or statistics by
|
||||||
|
editing client's settings on the Clients settings page in the UI ([#1717],
|
||||||
|
[#4299]).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Stored DHCP leases moved from `leases.db` to `data/leases.json`. The file
|
||||||
|
format has also been optimized.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The `github.com/mdlayher/raw` dependency has been temporarily returned to
|
||||||
|
support raw connections on Darwin ([#5712]).
|
||||||
|
- Incorrect recording of blocked results as “Blocked by CNAME or IP” in the
|
||||||
|
query log ([#5725]).
|
||||||
|
- All Safe Search services being unchecked by default.
|
||||||
|
- Panic when a DNSCrypt stamp is invalid ([#5721]).
|
||||||
|
|
||||||
|
[#5712]: https://github.com/AdguardTeam/AdGuardHome/issues/5712
|
||||||
|
[#5721]: https://github.com/AdguardTeam/AdGuardHome/issues/5721
|
||||||
|
[#5725]: https://github.com/AdguardTeam/AdGuardHome/issues/5725
|
||||||
|
|
||||||
|
[ms-v0.107.29]: https://github.com/AdguardTeam/AdGuardHome/milestone/65?closed=1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [v0.107.28] - 2023-04-12
|
## [v0.107.28] - 2023-04-12
|
||||||
|
|
||||||
See also the [v0.107.28 GitHub milestone][ms-v0.107.28].
|
See also the [v0.107.28 GitHub milestone][ms-v0.107.28].
|
||||||
|
@ -149,12 +181,10 @@ In this release, the schema version has changed from 17 to 20.
|
||||||
|
|
||||||
[#1163]: https://github.com/AdguardTeam/AdGuardHome/issues/1163
|
[#1163]: https://github.com/AdguardTeam/AdGuardHome/issues/1163
|
||||||
[#1333]: https://github.com/AdguardTeam/AdGuardHome/issues/1333
|
[#1333]: https://github.com/AdguardTeam/AdGuardHome/issues/1333
|
||||||
[#1163]: https://github.com/AdguardTeam/AdGuardHome/issues/1717
|
|
||||||
[#1472]: https://github.com/AdguardTeam/AdGuardHome/issues/1472
|
[#1472]: https://github.com/AdguardTeam/AdGuardHome/issues/1472
|
||||||
[#3290]: https://github.com/AdguardTeam/AdGuardHome/issues/3290
|
[#3290]: https://github.com/AdguardTeam/AdGuardHome/issues/3290
|
||||||
[#3459]: https://github.com/AdguardTeam/AdGuardHome/issues/3459
|
[#3459]: https://github.com/AdguardTeam/AdGuardHome/issues/3459
|
||||||
[#4262]: https://github.com/AdguardTeam/AdGuardHome/issues/4262
|
[#4262]: https://github.com/AdguardTeam/AdGuardHome/issues/4262
|
||||||
[#3290]: https://github.com/AdguardTeam/AdGuardHome/issues/4299
|
|
||||||
[#5567]: https://github.com/AdguardTeam/AdGuardHome/issues/5567
|
[#5567]: https://github.com/AdguardTeam/AdGuardHome/issues/5567
|
||||||
[#5701]: https://github.com/AdguardTeam/AdGuardHome/issues/5701
|
[#5701]: https://github.com/AdguardTeam/AdGuardHome/issues/5701
|
||||||
|
|
||||||
|
@ -1920,11 +1950,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.29...HEAD
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.30...HEAD
|
||||||
[v0.107.29]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.28...v0.107.29
|
[v0.107.30]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.29...v0.107.30
|
||||||
-->
|
-->
|
||||||
|
|
||||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.28...HEAD
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.29...HEAD
|
||||||
|
[v0.107.29]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.28...v0.107.29
|
||||||
[v0.107.28]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.27...v0.107.28
|
[v0.107.28]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.27...v0.107.28
|
||||||
[v0.107.27]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.26...v0.107.27
|
[v0.107.27]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.26...v0.107.27
|
||||||
[v0.107.26]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.25...v0.107.26
|
[v0.107.26]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.25...v0.107.26
|
||||||
|
|
|
@ -12,11 +12,40 @@
|
||||||
<link rel="mask-icon" href="assets/safari-pinned-tab.svg" color="#67B279">
|
<link rel="mask-icon" href="assets/safari-pinned-tab.svg" color="#67B279">
|
||||||
<link rel="icon" type="image/png" href="assets/favicon.png" sizes="48x48">
|
<link rel="icon" type="image/png" href="assets/favicon.png" sizes="48x48">
|
||||||
<title>AdGuard Home</title>
|
<title>AdGuard Home</title>
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="DARK"] .wrapper {
|
||||||
|
background-color: #f5f7fb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
You need to enable JavaScript to run this app.
|
You need to enable JavaScript to run this app.
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="root"></div>
|
<div id="root">
|
||||||
|
<div class="wrapper"></div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var LOCAL_STORAGE_THEME_KEY = 'account_theme';
|
||||||
|
var theme = 'light';
|
||||||
|
|
||||||
|
try {
|
||||||
|
theme = window.localStorage.getItem(LOCAL_STORAGE_THEME_KEY);
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.dataset.theme = theme;
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -17,5 +17,12 @@
|
||||||
You need to enable JavaScript to run this app.
|
You need to enable JavaScript to run this app.
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
var currentTheme = prefersDark ? 'dark' : 'light';
|
||||||
|
document.body.dataset.theme = currentTheme;
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -635,5 +635,6 @@
|
||||||
"parental_control": "الرقابة الابويه",
|
"parental_control": "الرقابة الابويه",
|
||||||
"safe_browsing": "تصفح آمن",
|
"safe_browsing": "تصفح آمن",
|
||||||
"served_from_cache": "{{value}} <i>(يتم تقديمه من ذاكرة التخزين المؤقت)</i>",
|
"served_from_cache": "{{value}} <i>(يتم تقديمه من ذاكرة التخزين المؤقت)</i>",
|
||||||
"form_error_password_length": "يجب أن تتكون كلمة المرور من {{value}} من الأحرف على الأقل"
|
"form_error_password_length": "يجب أن تتكون كلمة المرور من {{value}} من الأحرف على الأقل",
|
||||||
|
"protection_section_label": "الحماية"
|
||||||
}
|
}
|
||||||
|
|
|
@ -642,5 +642,6 @@
|
||||||
"anonymizer_notification": "<0>Заўвага:</0> Ананімізацыя IP уключана. Вы можаце адключыць яе ў <1>Агульных наладах</1>.",
|
"anonymizer_notification": "<0>Заўвага:</0> Ананімізацыя IP уключана. Вы можаце адключыць яе ў <1>Агульных наладах</1>.",
|
||||||
"confirm_dns_cache_clear": "Вы ўпэўнены, што хочаце ачысціць кэш DNS?",
|
"confirm_dns_cache_clear": "Вы ўпэўнены, што хочаце ачысціць кэш DNS?",
|
||||||
"cache_cleared": "Кэш DNS паспяхова ачышчаны",
|
"cache_cleared": "Кэш DNS паспяхова ачышчаны",
|
||||||
"clear_cache": "Ачысціць кэш"
|
"clear_cache": "Ачысціць кэш",
|
||||||
|
"protection_section_label": "Ахова"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "Protokol dotazů byl úspěšně vymazán",
|
"query_log_cleared": "Protokol dotazů byl úspěšně vymazán",
|
||||||
"query_log_updated": "Protokol dotazů byl úspěšně aktualizován",
|
"query_log_updated": "Protokol dotazů byl úspěšně aktualizován",
|
||||||
"query_log_clear": "Vymazat protokoly dotazů",
|
"query_log_clear": "Vymazat protokoly dotazů",
|
||||||
"query_log_retention": "Uchování protokolů dotazů",
|
"query_log_retention": "Rotace protokolů dotazů",
|
||||||
"query_log_enable": "Povolit protokol",
|
"query_log_enable": "Povolit protokol",
|
||||||
"query_log_configuration": "Konfigurace protokolů",
|
"query_log_configuration": "Konfigurace protokolů",
|
||||||
"query_log_disabled": "Protokol dotazu je zakázán a lze jej nakonfigurovat v <0>nastavení</0>",
|
"query_log_disabled": "Protokol dotazu je zakázán a lze jej nakonfigurovat v <0>nastavení</0>",
|
||||||
"query_log_strict_search": "Pro striktní vyhledávání použijte dvojité uvozovky",
|
"query_log_strict_search": "Pro striktní vyhledávání použijte dvojité uvozovky",
|
||||||
"query_log_retention_confirm": "Opravdu chcete změnit uchovávání protokolu dotazů? Pokud snížíte hodnotu intervalu, některá data budou ztracena",
|
"query_log_retention_confirm": "Opravdu chcete změnit rotaci protokolu dotazů? Pokud snížíte hodnotu intervalu, některá data budou ztracena",
|
||||||
"anonymize_client_ip": "Anonymizovat IP klienta",
|
"anonymize_client_ip": "Anonymizovat IP klienta",
|
||||||
"anonymize_client_ip_desc": "Neukládat úplnou IP adresu klienta do protokolů a statistik",
|
"anonymize_client_ip_desc": "Neukládat úplnou IP adresu klienta do protokolů a statistik",
|
||||||
"dns_config": "Konfigurace DNS serveru",
|
"dns_config": "Konfigurace DNS serveru",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Vypnout ochranu na {{count}} hod.",
|
"disable_notify_for_hours": "Vypnout ochranu na {{count}} hod.",
|
||||||
"disable_notify_for_hours_plural": "Vypnout ochranu na {{count}} hod.",
|
"disable_notify_for_hours_plural": "Vypnout ochranu na {{count}} hod.",
|
||||||
"disable_notify_until_tomorrow": "Vypnout ochranu do zítřka",
|
"disable_notify_until_tomorrow": "Vypnout ochranu do zítřka",
|
||||||
"enable_protection_timer": "Ochrana bude zapnuta za {{time}}"
|
"enable_protection_timer": "Ochrana bude zapnuta za {{time}}",
|
||||||
|
"custom_retention_input": "Zadejte retenci v hodinách",
|
||||||
|
"custom_rotation_input": "Zadejte rotaci v hodinách",
|
||||||
|
"protection_section_label": "Ochrana",
|
||||||
|
"log_and_stats_section_label": "Protokol dotazů a statistiky",
|
||||||
|
"ignore_query_log": "Ignorovat tohoto klienta v protokolu dotazů",
|
||||||
|
"ignore_statistics": "Ignorovat tohoto klienta ve statistikách"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "Forespørgselsloggen er blevet ryddet",
|
"query_log_cleared": "Forespørgselsloggen er blevet ryddet",
|
||||||
"query_log_updated": "Forespørgselsloggen er blevet opdateret",
|
"query_log_updated": "Forespørgselsloggen er blevet opdateret",
|
||||||
"query_log_clear": "Ryd forespørgselslogfiler",
|
"query_log_clear": "Ryd forespørgselslogfiler",
|
||||||
"query_log_retention": "Opbevar forespørgselslogger i",
|
"query_log_retention": "Rotation af forespørgselslog",
|
||||||
"query_log_enable": "Aktivér log",
|
"query_log_enable": "Aktivér log",
|
||||||
"query_log_configuration": "Opsætning af logger",
|
"query_log_configuration": "Opsætning af logger",
|
||||||
"query_log_disabled": "Forespørgselsloggen er deaktiveret og kan opsættes i <0>indstillingerne</0>",
|
"query_log_disabled": "Forespørgselsloggen er deaktiveret og kan opsættes i <0>indstillingerne</0>",
|
||||||
"query_log_strict_search": "Brug dobbelt anførselstegn til stringent søgning",
|
"query_log_strict_search": "Brug dobbelt anførselstegn til stringent søgning",
|
||||||
"query_log_retention_confirm": "Sikker på, at du vil ændre forespørgselsloggens opbevaringperiode? Mindskes intervalværdien, mistes data",
|
"query_log_retention_confirm": "Sikker på, at forespørgselsloggens rotationstid skal ændres? Mindskes intervalværdien, mistes nogle data",
|
||||||
"anonymize_client_ip": "Anonymisér klient-IP",
|
"anonymize_client_ip": "Anonymisér klient-IP",
|
||||||
"anonymize_client_ip_desc": "Gem ikke fuld klient IP-adresse i logfiler eller statistikker",
|
"anonymize_client_ip_desc": "Gem ikke fuld klient IP-adresse i logfiler eller statistikker",
|
||||||
"dns_config": "DNS-serveropsætning",
|
"dns_config": "DNS-serveropsætning",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Deaktivere beskyttelse i {{count}} time",
|
"disable_notify_for_hours": "Deaktivere beskyttelse i {{count}} time",
|
||||||
"disable_notify_for_hours_plural": "Deaktivere beskyttelse i {{count}} timer",
|
"disable_notify_for_hours_plural": "Deaktivere beskyttelse i {{count}} timer",
|
||||||
"disable_notify_until_tomorrow": "Deaktiver beskyttelse indtil i morgen",
|
"disable_notify_until_tomorrow": "Deaktiver beskyttelse indtil i morgen",
|
||||||
"enable_protection_timer": "Beskyttelse deaktiveres om {{time}}"
|
"enable_protection_timer": "Beskyttelse deaktiveres om {{time}}",
|
||||||
|
"custom_retention_input": "Angiv opbevaringstid i timer",
|
||||||
|
"custom_rotation_input": "Angiv rotationstid i timer",
|
||||||
|
"protection_section_label": "Beskyttelse",
|
||||||
|
"log_and_stats_section_label": "Forespørgselslog og statistik",
|
||||||
|
"ignore_query_log": "Ignorér denne klient i forespørgselslog",
|
||||||
|
"ignore_statistics": "Ignorér denne klient i statistik"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "Das Abfrageprotokoll wurde erfolgreich gelöscht",
|
"query_log_cleared": "Das Abfrageprotokoll wurde erfolgreich gelöscht",
|
||||||
"query_log_updated": "Das Abfrageprotokoll wurde erfolgreich aktualisiert",
|
"query_log_updated": "Das Abfrageprotokoll wurde erfolgreich aktualisiert",
|
||||||
"query_log_clear": "Abfrageprotokolle leeren",
|
"query_log_clear": "Abfrageprotokolle leeren",
|
||||||
"query_log_retention": "Abfrageprotokolle aufbewahren",
|
"query_log_retention": "Rotation der Abfrageprotokolle",
|
||||||
"query_log_enable": "Protokoll aktivieren",
|
"query_log_enable": "Protokoll aktivieren",
|
||||||
"query_log_configuration": "Konfiguration der Protokolle",
|
"query_log_configuration": "Konfiguration der Protokolle",
|
||||||
"query_log_disabled": "Das Abfrageprotokoll ist deaktiviert und kann in den <0>Einstellungen</0> konfiguriert werden.",
|
"query_log_disabled": "Das Abfrageprotokoll ist deaktiviert und kann in den <0>Einstellungen</0> konfiguriert werden.",
|
||||||
"query_log_strict_search": "Doppelte Anführungszeichen für die strikte Suche verwenden",
|
"query_log_strict_search": "Doppelte Anführungszeichen für die strikte Suche verwenden",
|
||||||
"query_log_retention_confirm": "Möchten Sie die Aufbewahrung des Abfrageprotokolls wirklich ändern? Wenn Sie den Zeitabstand verringern, gehen einige Daten verloren.",
|
"query_log_retention_confirm": "Möchten Sie die Abfrageprotokollrotation wirklich ändern? Wenn Sie den Intervallwert verringern, gehen einige Daten verloren",
|
||||||
"anonymize_client_ip": "Client-IP anonymisieren",
|
"anonymize_client_ip": "Client-IP anonymisieren",
|
||||||
"anonymize_client_ip_desc": "Vollständige IP-Adresse des Clients nicht in Protokollen und Statistiken speichern",
|
"anonymize_client_ip_desc": "Vollständige IP-Adresse des Clients nicht in Protokollen und Statistiken speichern",
|
||||||
"dns_config": "DNS-Serverkonfiguration",
|
"dns_config": "DNS-Serverkonfiguration",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Schutz für {{count}} Stunde deaktivieren",
|
"disable_notify_for_hours": "Schutz für {{count}} Stunde deaktivieren",
|
||||||
"disable_notify_for_hours_plural": "Schutz für {{count}} Stunden deaktivieren",
|
"disable_notify_for_hours_plural": "Schutz für {{count}} Stunden deaktivieren",
|
||||||
"disable_notify_until_tomorrow": "Schutz bis morgen deaktivieren",
|
"disable_notify_until_tomorrow": "Schutz bis morgen deaktivieren",
|
||||||
"enable_protection_timer": "Der Schutz wird in {{time}} wieder aktiviert"
|
"enable_protection_timer": "Der Schutz wird in {{time}} wieder aktiviert",
|
||||||
|
"custom_retention_input": "Rückhaltezeit in Stunden eingeben",
|
||||||
|
"custom_rotation_input": "Rotation in Stunden eingeben",
|
||||||
|
"protection_section_label": "Schutz",
|
||||||
|
"log_and_stats_section_label": "Abfrageprotokoll und Statistik",
|
||||||
|
"ignore_query_log": "Diesen Client im Abfrageprotokoll ignorieren",
|
||||||
|
"ignore_statistics": "Diesen Client in der Statistik ignorieren"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "The query log has been successfully cleared",
|
"query_log_cleared": "The query log has been successfully cleared",
|
||||||
"query_log_updated": "The query log has been successfully updated",
|
"query_log_updated": "The query log has been successfully updated",
|
||||||
"query_log_clear": "Clear query logs",
|
"query_log_clear": "Clear query logs",
|
||||||
"query_log_retention": "Query logs retention",
|
"query_log_retention": "Query logs rotation",
|
||||||
"query_log_enable": "Enable log",
|
"query_log_enable": "Enable log",
|
||||||
"query_log_configuration": "Logs configuration",
|
"query_log_configuration": "Logs configuration",
|
||||||
"query_log_disabled": "The query log is disabled and can be configured in the <0>settings</0>",
|
"query_log_disabled": "The query log is disabled and can be configured in the <0>settings</0>",
|
||||||
"query_log_strict_search": "Use double quotes for strict search",
|
"query_log_strict_search": "Use double quotes for strict search",
|
||||||
"query_log_retention_confirm": "Are you sure you want to change query log retention? If you decrease the interval value, some data will be lost",
|
"query_log_retention_confirm": "Are you sure you want to change query log rotation? If you decrease the interval value, some data will be lost",
|
||||||
"anonymize_client_ip": "Anonymize client IP",
|
"anonymize_client_ip": "Anonymize client IP",
|
||||||
"anonymize_client_ip_desc": "Don't save the client's full IP address to logs or statistics",
|
"anonymize_client_ip_desc": "Don't save the client's full IP address to logs or statistics",
|
||||||
"dns_config": "DNS server configuration",
|
"dns_config": "DNS server configuration",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Disable protection for {{count}} hour",
|
"disable_notify_for_hours": "Disable protection for {{count}} hour",
|
||||||
"disable_notify_for_hours_plural": "Disable protection for {{count}} hours",
|
"disable_notify_for_hours_plural": "Disable protection for {{count}} hours",
|
||||||
"disable_notify_until_tomorrow": "Disable protection until tomorrow",
|
"disable_notify_until_tomorrow": "Disable protection until tomorrow",
|
||||||
"enable_protection_timer": "Protection will be enabled in {{time}}"
|
"enable_protection_timer": "Protection will be enabled in {{time}}",
|
||||||
|
"custom_retention_input": "Enter retention in hours",
|
||||||
|
"custom_rotation_input": "Enter rotation in hours",
|
||||||
|
"protection_section_label": "Protection",
|
||||||
|
"log_and_stats_section_label": "Query log and statistics",
|
||||||
|
"ignore_query_log": "Ignore this client in query log",
|
||||||
|
"ignore_statistics": "Ignore this client in statistics"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "El registro de consultas se ha borrado correctamente",
|
"query_log_cleared": "El registro de consultas se ha borrado correctamente",
|
||||||
"query_log_updated": "El registro de consultas se ha actualizado correctamente",
|
"query_log_updated": "El registro de consultas se ha actualizado correctamente",
|
||||||
"query_log_clear": "Borrar registros de consultas",
|
"query_log_clear": "Borrar registros de consultas",
|
||||||
"query_log_retention": "Retención de registros de consultas",
|
"query_log_retention": "Rotanción de registros de consultas",
|
||||||
"query_log_enable": "Habilitar registro",
|
"query_log_enable": "Habilitar registro",
|
||||||
"query_log_configuration": "Configuración de registros",
|
"query_log_configuration": "Configuración de registros",
|
||||||
"query_log_disabled": "El registro de consultas está deshabilitado y se puede configurar en la <0>configuración</0>",
|
"query_log_disabled": "El registro de consultas está deshabilitado y se puede configurar en la <0>configuración</0>",
|
||||||
"query_log_strict_search": "Usar comillas dobles para una búsqueda estricta",
|
"query_log_strict_search": "Usar comillas dobles para una búsqueda estricta",
|
||||||
"query_log_retention_confirm": "¿Estás seguro de que deseas cambiar la retención del registro de consultas? Si disminuye el valor del intervalo, se perderán algunos datos",
|
"query_log_retention_confirm": "¿Está seguro de que deseas cambiar la rotación del registro de consultas? Si reduces el valor del intervalo, se perderán algunos datos",
|
||||||
"anonymize_client_ip": "Anonimizar IP del cliente",
|
"anonymize_client_ip": "Anonimizar IP del cliente",
|
||||||
"anonymize_client_ip_desc": "No guarda la dirección IP completa del cliente en registros o estadísticas",
|
"anonymize_client_ip_desc": "No guarda la dirección IP completa del cliente en registros o estadísticas",
|
||||||
"dns_config": "Configuración del servidor DNS",
|
"dns_config": "Configuración del servidor DNS",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Desactivar la protección por {{count}} hora",
|
"disable_notify_for_hours": "Desactivar la protección por {{count}} hora",
|
||||||
"disable_notify_for_hours_plural": "Desactivar la protección por {{count}} horas",
|
"disable_notify_for_hours_plural": "Desactivar la protección por {{count}} horas",
|
||||||
"disable_notify_until_tomorrow": "Desactivar la protección hasta mañana",
|
"disable_notify_until_tomorrow": "Desactivar la protección hasta mañana",
|
||||||
"enable_protection_timer": "La protección se activará en {{time}}"
|
"enable_protection_timer": "La protección se activará en {{time}}",
|
||||||
|
"custom_retention_input": "Ingresa la retención en horas",
|
||||||
|
"custom_rotation_input": "Ingresa la rotación en horas",
|
||||||
|
"protection_section_label": "Protección",
|
||||||
|
"log_and_stats_section_label": "Registro de consultas y estadísticas",
|
||||||
|
"ignore_query_log": "Ignorar este cliente en el registro de consultas",
|
||||||
|
"ignore_statistics": "Ignorar este cliente en las estadísticas"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "Pyyntöhistorian tyhjennys onnistui",
|
"query_log_cleared": "Pyyntöhistorian tyhjennys onnistui",
|
||||||
"query_log_updated": "Pyyntöhistorian päivitys onnistui",
|
"query_log_updated": "Pyyntöhistorian päivitys onnistui",
|
||||||
"query_log_clear": "Tyhjennä pyyntöhistoria",
|
"query_log_clear": "Tyhjennä pyyntöhistoria",
|
||||||
"query_log_retention": "Pyyntöhistorian säilytys",
|
"query_log_retention": "Kyselylokien kierto",
|
||||||
"query_log_enable": "Käytä historiaa",
|
"query_log_enable": "Käytä historiaa",
|
||||||
"query_log_configuration": "Historian määritys",
|
"query_log_configuration": "Historian määritys",
|
||||||
"query_log_disabled": "Pyyntöhistoria ei ole käytössä. Voit ottaa sen käyttöön <0>asetuksissa</0>",
|
"query_log_disabled": "Pyyntöhistoria ei ole käytössä. Voit ottaa sen käyttöön <0>asetuksissa</0>",
|
||||||
"query_log_strict_search": "Käytä tarkalle haulle lainausmerkkejä",
|
"query_log_strict_search": "Käytä tarkalle haulle lainausmerkkejä",
|
||||||
"query_log_retention_confirm": "Haluatko varmasti muuttaa pyyntöhistoriasi säilytysaikaa? Jos lyhennät aikaa, joitakin tietoja menetetään",
|
"query_log_retention_confirm": "Haluatko varmasti muuttaa kyselylokin kiertoa? Jos pienennät intervalliarvoa, osa tiedoista menetetään",
|
||||||
"anonymize_client_ip": "Piilota päätelaitteen IP-osoite",
|
"anonymize_client_ip": "Piilota päätelaitteen IP-osoite",
|
||||||
"anonymize_client_ip_desc": "Älä tallenna päätelaitteen täydellistä IP-osoitetta historiaan ja tilastoihin.",
|
"anonymize_client_ip_desc": "Älä tallenna päätelaitteen täydellistä IP-osoitetta historiaan ja tilastoihin.",
|
||||||
"dns_config": "DNS-palvelimen määritys",
|
"dns_config": "DNS-palvelimen määritys",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Poista suojaus käytöstä {{count}} tunniksi",
|
"disable_notify_for_hours": "Poista suojaus käytöstä {{count}} tunniksi",
|
||||||
"disable_notify_for_hours_plural": "Poista suojaus käytöstä {{count}} tunniksi",
|
"disable_notify_for_hours_plural": "Poista suojaus käytöstä {{count}} tunniksi",
|
||||||
"disable_notify_until_tomorrow": "Poista suojaus käytöstä huomiseen asti",
|
"disable_notify_until_tomorrow": "Poista suojaus käytöstä huomiseen asti",
|
||||||
"enable_protection_timer": "Suojaus otetaan käyttöön {{time}} kuluttua"
|
"enable_protection_timer": "Suojaus otetaan käyttöön {{time}} kuluttua",
|
||||||
|
"custom_retention_input": "Syötä säilytysaika tunteina",
|
||||||
|
"custom_rotation_input": "Syötä uudistusaika tunteina",
|
||||||
|
"protection_section_label": "Suojaus",
|
||||||
|
"log_and_stats_section_label": "Kyselyhistoria ja tilastot",
|
||||||
|
"ignore_query_log": "Älä huomioi tätä päätettä kyselyhistoriassa",
|
||||||
|
"ignore_statistics": "Älä huomioi tätä päätettä tilastoissa"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "Le journal des requêtes a été effacé",
|
"query_log_cleared": "Le journal des requêtes a été effacé",
|
||||||
"query_log_updated": "Le journal des requêtes a été mis à jour",
|
"query_log_updated": "Le journal des requêtes a été mis à jour",
|
||||||
"query_log_clear": "Effacer journal des requêtes",
|
"query_log_clear": "Effacer journal des requêtes",
|
||||||
"query_log_retention": "Rétention du journal des requêtes",
|
"query_log_retention": "Rotation des journaux de requêtes",
|
||||||
"query_log_enable": "Activer le journal",
|
"query_log_enable": "Activer le journal",
|
||||||
"query_log_configuration": "Configuration du journal",
|
"query_log_configuration": "Configuration du journal",
|
||||||
"query_log_disabled": "Le journal des requêtes est désactivé et peut être configuré dans les <0>paramètres</0>",
|
"query_log_disabled": "Le journal des requêtes est désactivé et peut être configuré dans les <0>paramètres</0>",
|
||||||
"query_log_strict_search": "Utilisez les doubles guillemets pour une recherche stricte",
|
"query_log_strict_search": "Utilisez les doubles guillemets pour une recherche stricte",
|
||||||
"query_log_retention_confirm": "Êtes-vous sûr de vouloir modifier la rétention des journaux de requêtes ? Si vous diminuez la valeur de l'intervalle, certaines données seront perdues",
|
"query_log_retention_confirm": "Êtes-vous sûr de souhaiter modifier la rotation des journaux de requêtes ? Si vous diminuez la valeur de l'intervalle, certaines données seront perdues",
|
||||||
"anonymize_client_ip": "Anonymiser l’IP du client",
|
"anonymize_client_ip": "Anonymiser l’IP du client",
|
||||||
"anonymize_client_ip_desc": "Ne pas enregistrer l’adresse IP complète du client dans les journaux et statistiques",
|
"anonymize_client_ip_desc": "Ne pas enregistrer l’adresse IP complète du client dans les journaux et statistiques",
|
||||||
"dns_config": "Configuration du serveur DNS",
|
"dns_config": "Configuration du serveur DNS",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Désactiver la protection pendant {{count}} heure",
|
"disable_notify_for_hours": "Désactiver la protection pendant {{count}} heure",
|
||||||
"disable_notify_for_hours_plural": "Désactiver la protection pendant {{count}} heures",
|
"disable_notify_for_hours_plural": "Désactiver la protection pendant {{count}} heures",
|
||||||
"disable_notify_until_tomorrow": "Désactiver la protection jusqu'à demain",
|
"disable_notify_until_tomorrow": "Désactiver la protection jusqu'à demain",
|
||||||
"enable_protection_timer": "La protection sera activée dans {{time}}"
|
"enable_protection_timer": "La protection sera activée dans {{time}}",
|
||||||
|
"custom_retention_input": "Saisir la rétention en heures",
|
||||||
|
"custom_rotation_input": "Saisir la rotation en heures",
|
||||||
|
"protection_section_label": "Protection",
|
||||||
|
"log_and_stats_section_label": "Journal des requêtes et statistiques",
|
||||||
|
"ignore_query_log": "Ignorer ce client dans le journal des requêtes",
|
||||||
|
"ignore_statistics": "Ignorer ce client dans les statistiques"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "Zapisnik upita je uspješno uklonjen",
|
"query_log_cleared": "Zapisnik upita je uspješno uklonjen",
|
||||||
"query_log_updated": "Zapisnik upita je uspješno ažuriran",
|
"query_log_updated": "Zapisnik upita je uspješno ažuriran",
|
||||||
"query_log_clear": "Očisti zapisnik upita",
|
"query_log_clear": "Očisti zapisnik upita",
|
||||||
"query_log_retention": "Spremanje zapisnika upita",
|
"query_log_retention": "Rotacija dnevnika upita",
|
||||||
"query_log_enable": "Omogući zapise",
|
"query_log_enable": "Omogući zapise",
|
||||||
"query_log_configuration": "Postavke zapisa",
|
"query_log_configuration": "Postavke zapisa",
|
||||||
"query_log_disabled": "Zapisnik upita je onemogućen i može se postaviti u <0>postavkama</0>",
|
"query_log_disabled": "Zapisnik upita je onemogućen i može se postaviti u <0>postavkama</0>",
|
||||||
"query_log_strict_search": "Koristite dvostruke navodnike za strogo pretraživanje",
|
"query_log_strict_search": "Koristite dvostruke navodnike za strogo pretraživanje",
|
||||||
"query_log_retention_confirm": "Jeste li sigurni da želite promijeniti zadržavanje zapisnika upita? Ako smanjite vrijednost intervala, neki će podaci biti izgubljeni",
|
"query_log_retention_confirm": "Jeste li sigurni da želite promijeniti rotaciju dnevnika upita? Ako smanjite vrijednost intervala, neki će se podaci izgubiti",
|
||||||
"anonymize_client_ip": "Anonimiraj IP klijenta",
|
"anonymize_client_ip": "Anonimiraj IP klijenta",
|
||||||
"anonymize_client_ip_desc": "Ne spremajte cijelu IP adresu klijenta u zapisnike i statistike",
|
"anonymize_client_ip_desc": "Ne spremajte cijelu IP adresu klijenta u zapisnike i statistike",
|
||||||
"dns_config": "DNS postavke poslužitelja",
|
"dns_config": "DNS postavke poslužitelja",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Isključi zaštitu na {{count}} sati",
|
"disable_notify_for_hours": "Isključi zaštitu na {{count}} sati",
|
||||||
"disable_notify_for_hours_plural": "Isključi zaštitu na {{count}} sati",
|
"disable_notify_for_hours_plural": "Isključi zaštitu na {{count}} sati",
|
||||||
"disable_notify_until_tomorrow": "Isključi zaštitu do sutra",
|
"disable_notify_until_tomorrow": "Isključi zaštitu do sutra",
|
||||||
"enable_protection_timer": "Zaštita će biti omogućena u {{time}}"
|
"enable_protection_timer": "Zaštita će biti omogućena u {{time}}",
|
||||||
|
"custom_retention_input": "Unesite zadržavanje u satima",
|
||||||
|
"custom_rotation_input": "Unesite rotaciju u satima",
|
||||||
|
"protection_section_label": "Zaštita",
|
||||||
|
"log_and_stats_section_label": "Zapisnik upita i statistika",
|
||||||
|
"ignore_query_log": "Zanemari ovog klijenta u zapisniku upita",
|
||||||
|
"ignore_statistics": "Ignorirajte ovog klijenta u statistici"
|
||||||
}
|
}
|
||||||
|
|
|
@ -642,5 +642,6 @@
|
||||||
"anonymizer_notification": "<0>Megjegyzés:</0> Az IP anonimizálás engedélyezve van. Az <1>Általános beállításoknál letilthatja</1> .",
|
"anonymizer_notification": "<0>Megjegyzés:</0> Az IP anonimizálás engedélyezve van. Az <1>Általános beállításoknál letilthatja</1> .",
|
||||||
"confirm_dns_cache_clear": "Biztos benne, hogy törölni szeretné a DNS-gyorsítótárat?",
|
"confirm_dns_cache_clear": "Biztos benne, hogy törölni szeretné a DNS-gyorsítótárat?",
|
||||||
"cache_cleared": "A DNS gyorsítótár sikeresen törlődött",
|
"cache_cleared": "A DNS gyorsítótár sikeresen törlődött",
|
||||||
"clear_cache": "Gyorsítótár törlése"
|
"clear_cache": "Gyorsítótár törlése",
|
||||||
|
"protection_section_label": "Védelem"
|
||||||
}
|
}
|
||||||
|
|
|
@ -641,5 +641,6 @@
|
||||||
"anonymizer_notification": "<0>Catatan:</0> Anonimisasi IP diaktifkan. Anda dapat menonaktifkannya di <1>Pengaturan umum</1> .",
|
"anonymizer_notification": "<0>Catatan:</0> Anonimisasi IP diaktifkan. Anda dapat menonaktifkannya di <1>Pengaturan umum</1> .",
|
||||||
"confirm_dns_cache_clear": "Apakah Anda yakin ingin menghapus cache DNS?",
|
"confirm_dns_cache_clear": "Apakah Anda yakin ingin menghapus cache DNS?",
|
||||||
"cache_cleared": "Cache DNS berhasil dibersihkan",
|
"cache_cleared": "Cache DNS berhasil dibersihkan",
|
||||||
"clear_cache": "Hapus cache"
|
"clear_cache": "Hapus cache",
|
||||||
|
"protection_section_label": "Perlindungan"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "Il registro richieste è stato correttamente cancellato",
|
"query_log_cleared": "Il registro richieste è stato correttamente cancellato",
|
||||||
"query_log_updated": "Il registro richieste è stato correttamente aggiornato",
|
"query_log_updated": "Il registro richieste è stato correttamente aggiornato",
|
||||||
"query_log_clear": "Cancella registri richieste",
|
"query_log_clear": "Cancella registri richieste",
|
||||||
"query_log_retention": "Conservazione dei registri richieste",
|
"query_log_retention": "Rotazione dei registri richieste",
|
||||||
"query_log_enable": "Attiva registro",
|
"query_log_enable": "Attiva registro",
|
||||||
"query_log_configuration": "Configurazione registri",
|
"query_log_configuration": "Configurazione registri",
|
||||||
"query_log_disabled": "Il registro richieste è stato disattivato e può essere configurata dalle <0>impostazioni</0>",
|
"query_log_disabled": "Il registro richieste è stato disattivato e può essere configurata dalle <0>impostazioni</0>",
|
||||||
"query_log_strict_search": "Utilizzare le doppie virgolette per una ricerca precisa",
|
"query_log_strict_search": "Utilizzare le doppie virgolette per una ricerca precisa",
|
||||||
"query_log_retention_confirm": "Sei sicuro di voler modificare il registro delle richieste? Se il valore di intervallo dovesse diminuire, alcuni dati andranno persi",
|
"query_log_retention_confirm": "Sei sicuro di voler modificare il registro delle richieste? Se si riduce il valore dell'intervallo, alcuni dati andranno persi",
|
||||||
"anonymize_client_ip": "Anonimizza client IP",
|
"anonymize_client_ip": "Anonimizza client IP",
|
||||||
"anonymize_client_ip_desc": "Non salvare l'indirizzo IP completo del client nel registro o nelle statistiche",
|
"anonymize_client_ip_desc": "Non salvare l'indirizzo IP completo del client nel registro o nelle statistiche",
|
||||||
"dns_config": "Configurazione server DNS",
|
"dns_config": "Configurazione server DNS",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Disattiva la protezione per {{count}} ora",
|
"disable_notify_for_hours": "Disattiva la protezione per {{count}} ora",
|
||||||
"disable_notify_for_hours_plural": "Disattiva la protezione per {{count}} ore",
|
"disable_notify_for_hours_plural": "Disattiva la protezione per {{count}} ore",
|
||||||
"disable_notify_until_tomorrow": "Disattiva la protezione fino a domani",
|
"disable_notify_until_tomorrow": "Disattiva la protezione fino a domani",
|
||||||
"enable_protection_timer": "La protezione verrà attivata in {{time}}"
|
"enable_protection_timer": "La protezione verrà attivata in {{time}}",
|
||||||
|
"custom_retention_input": "Inserisci la conservazione in ore",
|
||||||
|
"custom_rotation_input": "Inserisci la rotazione in ore",
|
||||||
|
"protection_section_label": "Protezione",
|
||||||
|
"log_and_stats_section_label": "Registro richieste e statistiche",
|
||||||
|
"ignore_query_log": "Ignora questo client nel registro delle richieste",
|
||||||
|
"ignore_statistics": "Ignora questo cliente nelle statistiche"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "クエリ・ログの消去に成功しました",
|
"query_log_cleared": "クエリ・ログの消去に成功しました",
|
||||||
"query_log_updated": "クエリ・ログの更新が成功しました",
|
"query_log_updated": "クエリ・ログの更新が成功しました",
|
||||||
"query_log_clear": "クエリ・ログを消去する",
|
"query_log_clear": "クエリ・ログを消去する",
|
||||||
"query_log_retention": "クエリ・ログの保持",
|
"query_log_retention": "クエリ・ログのローテーション",
|
||||||
"query_log_enable": "ログを有効にする",
|
"query_log_enable": "ログを有効にする",
|
||||||
"query_log_configuration": "ログ設定",
|
"query_log_configuration": "ログ設定",
|
||||||
"query_log_disabled": "クエリ・ログは無効になっており、<0>設定</0>で構成できます",
|
"query_log_disabled": "クエリ・ログは無効になっており、<0>設定</0>で構成できます",
|
||||||
"query_log_strict_search": "完全一致検索には二重引用符を使用します",
|
"query_log_strict_search": "完全一致検索には二重引用符を使用します",
|
||||||
"query_log_retention_confirm": "クエリ・ログの保持を変更してもよろしいですか? 期間を短くすると、一部のデータが失われます",
|
"query_log_retention_confirm": "クエリ・ログのローテーションを変更してもよろしいですか? 間隔の値を減らすと、一部のデータが失われます",
|
||||||
"anonymize_client_ip": "クライアントIPを匿名化する",
|
"anonymize_client_ip": "クライアントIPを匿名化する",
|
||||||
"anonymize_client_ip_desc": "ログと統計にクライアントのフルIPアドレスを保存しないようにします。",
|
"anonymize_client_ip_desc": "ログと統計にクライアントのフルIPアドレスを保存しないようにします。",
|
||||||
"dns_config": "DNSサーバ設定",
|
"dns_config": "DNSサーバ設定",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "保護を {{count}} 時間無効にする",
|
"disable_notify_for_hours": "保護を {{count}} 時間無効にする",
|
||||||
"disable_notify_for_hours_plural": "保護を {{count}} 時間無効にする",
|
"disable_notify_for_hours_plural": "保護を {{count}} 時間無効にする",
|
||||||
"disable_notify_until_tomorrow": "明日まで保護を無効にする",
|
"disable_notify_until_tomorrow": "明日まで保護を無効にする",
|
||||||
"enable_protection_timer": "保護は後 {{time}} で有効になります"
|
"enable_protection_timer": "保護は後 {{time}} で有効になります",
|
||||||
|
"custom_retention_input": "保持期間を入力してください(時間単位)",
|
||||||
|
"custom_rotation_input": "ローテーションを入力してください(時間単位)",
|
||||||
|
"protection_section_label": "AdGuardによる保護",
|
||||||
|
"log_and_stats_section_label": "クエリ・ログと統計情報",
|
||||||
|
"ignore_query_log": "クエリ・ログでこのクライアントを無視する",
|
||||||
|
"ignore_statistics": "統計でこのクライアントを無視する"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "쿼리 로그를 성공적으로 초기화했습니다",
|
"query_log_cleared": "쿼리 로그를 성공적으로 초기화했습니다",
|
||||||
"query_log_updated": "질의 로그가 성공적으로 업데이트되었습니다",
|
"query_log_updated": "질의 로그가 성공적으로 업데이트되었습니다",
|
||||||
"query_log_clear": "쿼리 로그 비우기",
|
"query_log_clear": "쿼리 로그 비우기",
|
||||||
"query_log_retention": "쿼리 로그 저장 기간",
|
"query_log_retention": "쿼리 로그 로테이션",
|
||||||
"query_log_enable": "로그 활성화",
|
"query_log_enable": "로그 활성화",
|
||||||
"query_log_configuration": "로그 구성",
|
"query_log_configuration": "로그 구성",
|
||||||
"query_log_disabled": "쿼리 로그가 비활성화되어 있으며 <0>설정</0>에서 설정할 수 있습니다",
|
"query_log_disabled": "쿼리 로그가 비활성화되어 있으며 <0>설정</0>에서 설정할 수 있습니다",
|
||||||
"query_log_strict_search": "검색을 제한하려면 쌍따옴표를 사용해주세요",
|
"query_log_strict_search": "검색을 제한하려면 쌍따옴표를 사용해주세요",
|
||||||
"query_log_retention_confirm": "정말로 쿼리 로그 저장 기간을 변경하시겠습니까? 저장 주기를 낮출 경우, 일부 데이터가 손실됩니다",
|
"query_log_retention_confirm": "쿼리 로그 로테이션을 변경하시겠습니까? 간격 값을 줄이면 일부 데이터가 손실됩니다.",
|
||||||
"anonymize_client_ip": "클라이언트 IP 익명화",
|
"anonymize_client_ip": "클라이언트 IP 익명화",
|
||||||
"anonymize_client_ip_desc": "클라이언트의 전체 IP 주소를 로그와 통계에 저장하저장하지 마세요",
|
"anonymize_client_ip_desc": "클라이언트의 전체 IP 주소를 로그와 통계에 저장하저장하지 마세요",
|
||||||
"dns_config": "DNS 서버 설정",
|
"dns_config": "DNS 서버 설정",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "{{count}}시간 동안 보호 기능 비활성화",
|
"disable_notify_for_hours": "{{count}}시간 동안 보호 기능 비활성화",
|
||||||
"disable_notify_for_hours_plural": "{{count}}시간 동안 보호 기능 비활성화",
|
"disable_notify_for_hours_plural": "{{count}}시간 동안 보호 기능 비활성화",
|
||||||
"disable_notify_until_tomorrow": "내일까지 보호 기능 비활성화",
|
"disable_notify_until_tomorrow": "내일까지 보호 기능 비활성화",
|
||||||
"enable_protection_timer": "{{time}}에 보호 기능이 활성화됩니다."
|
"enable_protection_timer": "{{time}}에 보호 기능이 활성화됩니다.",
|
||||||
|
"custom_retention_input": "시간 단위로 보존 기간 입력",
|
||||||
|
"custom_rotation_input": "시간 단위로 로테이션 입력",
|
||||||
|
"protection_section_label": "보호",
|
||||||
|
"log_and_stats_section_label": "쿼리 로그 및 통계",
|
||||||
|
"ignore_query_log": "쿼리 로그에서 이 클라이언트 무시",
|
||||||
|
"ignore_statistics": "통계에서 이 클라이언트 무시"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "Het query logboek is succesvol geleegd",
|
"query_log_cleared": "Het query logboek is succesvol geleegd",
|
||||||
"query_log_updated": "Het query logboek is succesvol bijgewerkt",
|
"query_log_updated": "Het query logboek is succesvol bijgewerkt",
|
||||||
"query_log_clear": "Leeg query logs",
|
"query_log_clear": "Leeg query logs",
|
||||||
"query_log_retention": "Query logs bewaartermijn",
|
"query_log_retention": "Query logs rotatie",
|
||||||
"query_log_enable": "Log bestanden inschakelen",
|
"query_log_enable": "Log bestanden inschakelen",
|
||||||
"query_log_configuration": "Logbestanden instellingen",
|
"query_log_configuration": "Logbestanden instellingen",
|
||||||
"query_log_disabled": "Het query logboek is uitgeschakeld en kan worden geconfigureerd in de <0>instellingen</0>",
|
"query_log_disabled": "Het query logboek is uitgeschakeld en kan worden geconfigureerd in de <0>instellingen</0>",
|
||||||
"query_log_strict_search": "Gebruik dubbele aanhalingstekens voor strikt zoeken",
|
"query_log_strict_search": "Gebruik dubbele aanhalingstekens voor strikt zoeken",
|
||||||
"query_log_retention_confirm": "Weet u zeker dat u de bewaartermijn van het query logboek wilt wijzigen? Als u de intervalwaarde verlaagt, gaan sommige gegevens verloren",
|
"query_log_retention_confirm": "Weet u zeker dat u de rotatie van het querylogboek wilt wijzigen? Als u de intervalwaarde verlaagt, gaan sommige gegevens verloren",
|
||||||
"anonymize_client_ip": "Cliënt IP anonimiseren",
|
"anonymize_client_ip": "Cliënt IP anonimiseren",
|
||||||
"anonymize_client_ip_desc": "Het volledige IP-adres van de cliënt niet opnemen in logboeken en statistiekbestanden",
|
"anonymize_client_ip_desc": "Het volledige IP-adres van de cliënt niet opnemen in logboeken en statistiekbestanden",
|
||||||
"dns_config": "DNS-server configuratie",
|
"dns_config": "DNS-server configuratie",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Beveiliging uitschakelen voor {{count}} uur",
|
"disable_notify_for_hours": "Beveiliging uitschakelen voor {{count}} uur",
|
||||||
"disable_notify_for_hours_plural": "Beveiliging uitschakelen voor {{count}} uren",
|
"disable_notify_for_hours_plural": "Beveiliging uitschakelen voor {{count}} uren",
|
||||||
"disable_notify_until_tomorrow": "Beveiliging uitschakelen tot morgen",
|
"disable_notify_until_tomorrow": "Beveiliging uitschakelen tot morgen",
|
||||||
"enable_protection_timer": "Bescherming wordt ingeschakeld over {{time}}"
|
"enable_protection_timer": "Bescherming wordt ingeschakeld over {{time}}",
|
||||||
|
"custom_retention_input": "Voer retentie in uren in",
|
||||||
|
"custom_rotation_input": "Voer rotatie in uren in",
|
||||||
|
"protection_section_label": "Bescherming",
|
||||||
|
"log_and_stats_section_label": "Aanvragenlogboek en statistieken",
|
||||||
|
"ignore_query_log": "Deze client negeren in het aanvragenlogboek",
|
||||||
|
"ignore_statistics": "Deze client negeren in de statistieken"
|
||||||
}
|
}
|
||||||
|
|
|
@ -614,5 +614,6 @@
|
||||||
"use_saved_key": "Bruk den tidligere lagrede nøkkelen",
|
"use_saved_key": "Bruk den tidligere lagrede nøkkelen",
|
||||||
"parental_control": "Foreldrekontroll",
|
"parental_control": "Foreldrekontroll",
|
||||||
"safe_browsing": "Sikker surfing",
|
"safe_browsing": "Sikker surfing",
|
||||||
"served_from_cache": "{{value}} <i>(formidlet fra mellomlageret)</i>"
|
"served_from_cache": "{{value}} <i>(formidlet fra mellomlageret)</i>",
|
||||||
|
"protection_section_label": "Beskyttelse"
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,6 +167,7 @@
|
||||||
"enabled_parental_toast": "Włączona Kontrola Rodzicielska",
|
"enabled_parental_toast": "Włączona Kontrola Rodzicielska",
|
||||||
"disabled_safe_search_toast": "Wyłączone bezpieczne wyszukiwanie",
|
"disabled_safe_search_toast": "Wyłączone bezpieczne wyszukiwanie",
|
||||||
"enabled_save_search_toast": "Włączone bezpieczne wyszukiwanie",
|
"enabled_save_search_toast": "Włączone bezpieczne wyszukiwanie",
|
||||||
|
"updated_save_search_toast": "Zaktualizowano ustawienia bezpiecznego wyszukiwania",
|
||||||
"enabled_table_header": "Włączone",
|
"enabled_table_header": "Włączone",
|
||||||
"name_table_header": "Nazwa",
|
"name_table_header": "Nazwa",
|
||||||
"list_url_table_header": "Adres URL listy",
|
"list_url_table_header": "Adres URL listy",
|
||||||
|
@ -256,12 +257,12 @@
|
||||||
"query_log_cleared": "Dziennik zapytań został pomyślnie wyczyszczony",
|
"query_log_cleared": "Dziennik zapytań został pomyślnie wyczyszczony",
|
||||||
"query_log_updated": "Dziennik zapytań został zaktualizowany",
|
"query_log_updated": "Dziennik zapytań został zaktualizowany",
|
||||||
"query_log_clear": "Wyczyść dzienniki zapytań",
|
"query_log_clear": "Wyczyść dzienniki zapytań",
|
||||||
"query_log_retention": "Przechowywanie dzienników zapytań",
|
"query_log_retention": "Rotacja dzienników zapytań",
|
||||||
"query_log_enable": "Włącz dziennik",
|
"query_log_enable": "Włącz dziennik",
|
||||||
"query_log_configuration": "Konfiguracja dzienników",
|
"query_log_configuration": "Konfiguracja dzienników",
|
||||||
"query_log_disabled": "Dziennik zapytań jest wyłączony i można go skonfigurować w <0>ustawieniach</0>",
|
"query_log_disabled": "Dziennik zapytań jest wyłączony i można go skonfigurować w <0>ustawieniach</0>",
|
||||||
"query_log_strict_search": "Używaj podwójnych cudzysłowów do ścisłego wyszukiwania",
|
"query_log_strict_search": "Używaj podwójnych cudzysłowów do ścisłego wyszukiwania",
|
||||||
"query_log_retention_confirm": "Czy na pewno chcesz zmienić sposób przechowywania dziennika zapytań? Jeśli zmniejszysz wartość interwału, niektóre dane zostaną utracone",
|
"query_log_retention_confirm": "Czy na pewno chcesz zmienić rotację dziennika zapytań? Jeśli zmniejszysz wartość interwału, niektóre dane zostaną utracone",
|
||||||
"anonymize_client_ip": "Anonimizuj adres IP klienta",
|
"anonymize_client_ip": "Anonimizuj adres IP klienta",
|
||||||
"anonymize_client_ip_desc": "Nie zapisuj pełnego adresu IP w dziennikach i statystykach",
|
"anonymize_client_ip_desc": "Nie zapisuj pełnego adresu IP w dziennikach i statystykach",
|
||||||
"dns_config": "Konfiguracja serwera DNS",
|
"dns_config": "Konfiguracja serwera DNS",
|
||||||
|
@ -290,6 +291,8 @@
|
||||||
"rate_limit": "Limit ilościowy",
|
"rate_limit": "Limit ilościowy",
|
||||||
"edns_enable": "Włącz podsieć klienta EDNS",
|
"edns_enable": "Włącz podsieć klienta EDNS",
|
||||||
"edns_cs_desc": "Dodaj opcję podsieci klienta EDNS (ECS) do żądań nadrzędnych i rejestruj wartości wysyłane przez klientów w dzienniku zapytań.",
|
"edns_cs_desc": "Dodaj opcję podsieci klienta EDNS (ECS) do żądań nadrzędnych i rejestruj wartości wysyłane przez klientów w dzienniku zapytań.",
|
||||||
|
"edns_use_custom_ip": "Użyj niestandardowego adresu IP dla EDNS",
|
||||||
|
"edns_use_custom_ip_desc": "Zezwól na użycie niestandardowego adresu IP dla EDNS",
|
||||||
"rate_limit_desc": "Liczba żądań na sekundę dozwolona na klienta. Ustawienie wartości 0 oznacza brak ograniczeń.",
|
"rate_limit_desc": "Liczba żądań na sekundę dozwolona na klienta. Ustawienie wartości 0 oznacza brak ograniczeń.",
|
||||||
"blocking_ipv4_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania A",
|
"blocking_ipv4_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania A",
|
||||||
"blocking_ipv6_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania AAAA",
|
"blocking_ipv6_desc": "Adres IP, który ma zostać zwrócony w przypadku zablokowanego żądania AAAA",
|
||||||
|
@ -523,6 +526,10 @@
|
||||||
"statistics_retention_confirm": "Czy chcesz zmienić sposób przechowania statystyk? Jeżeli obniżysz wartość interwału, niektóre dane będą utracone",
|
"statistics_retention_confirm": "Czy chcesz zmienić sposób przechowania statystyk? Jeżeli obniżysz wartość interwału, niektóre dane będą utracone",
|
||||||
"statistics_cleared": "Statystyki zostały pomyślnie wyczyszczone",
|
"statistics_cleared": "Statystyki zostały pomyślnie wyczyszczone",
|
||||||
"statistics_enable": "Włącz statystyki",
|
"statistics_enable": "Włącz statystyki",
|
||||||
|
"ignore_domains": "Ignorowane domeny (każda w nowym wierszu)",
|
||||||
|
"ignore_domains_title": "Ignorowane domeny",
|
||||||
|
"ignore_domains_desc_stats": "Zapytania dla tych domen nie są zapisywane do statystyk",
|
||||||
|
"ignore_domains_desc_query": "Zapytania dla tych domen nie są zapisywane do dziennika",
|
||||||
"interval_hours": "{{count}} godzina",
|
"interval_hours": "{{count}} godzina",
|
||||||
"interval_hours_plural": "{{count}} godziny",
|
"interval_hours_plural": "{{count}} godziny",
|
||||||
"filters_configuration": "Konfiguracja filtrów",
|
"filters_configuration": "Konfiguracja filtrów",
|
||||||
|
@ -642,5 +649,30 @@
|
||||||
"anonymizer_notification": "<0>Uwaga:</0> Anonimizacja IP jest włączona. Możesz ją wyłączyć w <1>Ustawieniach ogólnych</1>.",
|
"anonymizer_notification": "<0>Uwaga:</0> Anonimizacja IP jest włączona. Możesz ją wyłączyć w <1>Ustawieniach ogólnych</1>.",
|
||||||
"confirm_dns_cache_clear": "Czy na pewno chcesz wyczyścić pamięć podręczną DNS?",
|
"confirm_dns_cache_clear": "Czy na pewno chcesz wyczyścić pamięć podręczną DNS?",
|
||||||
"cache_cleared": "Pamięć podręczna DNS została pomyślnie wyczyszczona",
|
"cache_cleared": "Pamięć podręczna DNS została pomyślnie wyczyszczona",
|
||||||
"clear_cache": "Wyczyść pamięć podręczną"
|
"clear_cache": "Wyczyść pamięć podręczną",
|
||||||
|
"make_static": "Ustaw adres statyczny",
|
||||||
|
"theme_auto_desc": "Automatycznie (na podstawie schematu kolorów Twojego urządzenia)",
|
||||||
|
"theme_dark_desc": "Ciemny motyw",
|
||||||
|
"theme_light_desc": "Jasny motyw",
|
||||||
|
"disable_for_seconds": "Na {{count}} sekundę",
|
||||||
|
"disable_for_seconds_plural": "Na {{count}} sekund",
|
||||||
|
"disable_for_minutes": "Na {{count}} minutę",
|
||||||
|
"disable_for_minutes_plural": "Na {{count}} minut",
|
||||||
|
"disable_for_hours": "Na {{count}} godzinę",
|
||||||
|
"disable_for_hours_plural": "Na {{count}} godziny",
|
||||||
|
"disable_until_tomorrow": "Do jutra",
|
||||||
|
"disable_notify_for_seconds": "Wyłącz ochronę na {{count}} sekundę",
|
||||||
|
"disable_notify_for_seconds_plural": "Wyłącz ochronę na {{count}} sekund",
|
||||||
|
"disable_notify_for_minutes": "Wyłącz ochronę na {{count}} minutę",
|
||||||
|
"disable_notify_for_minutes_plural": "Wyłącz ochronę na {{count}} minut",
|
||||||
|
"disable_notify_for_hours": "Wyłącz ochronę na {{count}} godzinę",
|
||||||
|
"disable_notify_for_hours_plural": "Wyłącz ochronę na {{count}} godziny",
|
||||||
|
"disable_notify_until_tomorrow": "Wyłącz ochronę do jutra",
|
||||||
|
"enable_protection_timer": "Ochrona zostanie włączona za {{time}}",
|
||||||
|
"custom_retention_input": "Wprowadź retencję w godzinach",
|
||||||
|
"custom_rotation_input": "Wprowadź rotację w godzinach",
|
||||||
|
"protection_section_label": "Ochrona",
|
||||||
|
"log_and_stats_section_label": "Dziennik zapytań i statystyki",
|
||||||
|
"ignore_query_log": "Zignoruj tego klienta w dzienniku zapytań",
|
||||||
|
"ignore_statistics": "Ignoruj tego klienta w statystykach"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "O registro de consulta foi limpo com sucesso",
|
"query_log_cleared": "O registro de consulta foi limpo com sucesso",
|
||||||
"query_log_updated": "O registro da consulta foi atualizado com sucesso",
|
"query_log_updated": "O registro da consulta foi atualizado com sucesso",
|
||||||
"query_log_clear": "Limpar registros de consulta",
|
"query_log_clear": "Limpar registros de consulta",
|
||||||
"query_log_retention": "Arquivamento de registros de consultas",
|
"query_log_retention": "Rotação de registros de consulta",
|
||||||
"query_log_enable": "Ativar registro",
|
"query_log_enable": "Ativar registro",
|
||||||
"query_log_configuration": "Configuração de registros",
|
"query_log_configuration": "Configuração de registros",
|
||||||
"query_log_disabled": "O registro de consulta está desativado e pode ser configurado em <0>configurações</0>",
|
"query_log_disabled": "O registro de consulta está desativado e pode ser configurado em <0>configurações</0>",
|
||||||
"query_log_strict_search": "Use aspas duplas para uma pesquisa mais criteriosa",
|
"query_log_strict_search": "Use aspas duplas para uma pesquisa mais criteriosa",
|
||||||
"query_log_retention_confirm": "Você tem certeza de que deseja alterar o arquivamento do registro de consulta? Se diminuir o valor de intervalo, alguns dados serão perdidos",
|
"query_log_retention_confirm": "Tem a certeza de que quer alterar a rotação do registo de consulta? Se diminuir o valor do intervalo, alguns dados serão perdidos",
|
||||||
"anonymize_client_ip": "Tornar anônimo o IP do cliente",
|
"anonymize_client_ip": "Tornar anônimo o IP do cliente",
|
||||||
"anonymize_client_ip_desc": "Não salva o endereço de IP completo do cliente em registros ou estatísticas",
|
"anonymize_client_ip_desc": "Não salva o endereço de IP completo do cliente em registros ou estatísticas",
|
||||||
"dns_config": "Configuração do servidor DNS",
|
"dns_config": "Configuração do servidor DNS",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Desativar proteção por {{count}} hora",
|
"disable_notify_for_hours": "Desativar proteção por {{count}} hora",
|
||||||
"disable_notify_for_hours_plural": "Desativar proteção por {{count}} horas",
|
"disable_notify_for_hours_plural": "Desativar proteção por {{count}} horas",
|
||||||
"disable_notify_until_tomorrow": "Desativar a proteção até amanhã",
|
"disable_notify_until_tomorrow": "Desativar a proteção até amanhã",
|
||||||
"enable_protection_timer": "A proteção será ativada em {{time}}"
|
"enable_protection_timer": "A proteção será ativada em {{time}}",
|
||||||
|
"custom_retention_input": "Insira a retenção em horas",
|
||||||
|
"custom_rotation_input": "Insira a rotação em horas",
|
||||||
|
"protection_section_label": "Proteção",
|
||||||
|
"log_and_stats_section_label": "Registro de consultas e estatísticas",
|
||||||
|
"ignore_query_log": "Ignorar este cliente no registo de consultas",
|
||||||
|
"ignore_statistics": "Ignorar este cliente nas estatísticas"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "O registo de consulta foi limpo com sucesso",
|
"query_log_cleared": "O registo de consulta foi limpo com sucesso",
|
||||||
"query_log_updated": "O registo da consulta foi atualizado com sucesso",
|
"query_log_updated": "O registo da consulta foi atualizado com sucesso",
|
||||||
"query_log_clear": "Limpar registos de consulta",
|
"query_log_clear": "Limpar registos de consulta",
|
||||||
"query_log_retention": "Retenção de registos de consulta",
|
"query_log_retention": "Rotação de registros de consulta",
|
||||||
"query_log_enable": "Ativar registo",
|
"query_log_enable": "Ativar registo",
|
||||||
"query_log_configuration": "Definições do registo",
|
"query_log_configuration": "Definições do registo",
|
||||||
"query_log_disabled": "O registo de consulta está desativado e pode ser configurado em <0>definições</0>",
|
"query_log_disabled": "O registo de consulta está desativado e pode ser configurado em <0>definições</0>",
|
||||||
"query_log_strict_search": "Usar aspas duplas para uma pesquisa rigorosa",
|
"query_log_strict_search": "Usar aspas duplas para uma pesquisa rigorosa",
|
||||||
"query_log_retention_confirm": "Tem a certeza de que deseja alterar a retenção do registo de consulta? Se diminuir o valor do intervalo, alguns dados serão perdidos",
|
"query_log_retention_confirm": "Tem a certeza de que quer alterar a rotação do registo de consulta? Se diminuir o valor do intervalo, alguns dados serão perdidos",
|
||||||
"anonymize_client_ip": "Tornar anónimo o IP do cliente",
|
"anonymize_client_ip": "Tornar anónimo o IP do cliente",
|
||||||
"anonymize_client_ip_desc": "Não gurda o endereço de IP completo do cliente em registo ou estatísticas",
|
"anonymize_client_ip_desc": "Não gurda o endereço de IP completo do cliente em registo ou estatísticas",
|
||||||
"dns_config": "Definição do servidor DNS",
|
"dns_config": "Definição do servidor DNS",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Desativar proteção por {{count}} hora",
|
"disable_notify_for_hours": "Desativar proteção por {{count}} hora",
|
||||||
"disable_notify_for_hours_plural": "Desativar proteção por {{count}} horas",
|
"disable_notify_for_hours_plural": "Desativar proteção por {{count}} horas",
|
||||||
"disable_notify_until_tomorrow": "Desativar a proteção até amanhã",
|
"disable_notify_until_tomorrow": "Desativar a proteção até amanhã",
|
||||||
"enable_protection_timer": "A proteção será habilitada em {{time}}"
|
"enable_protection_timer": "A proteção será habilitada em {{time}}",
|
||||||
|
"custom_retention_input": "Insira a retenção em horas",
|
||||||
|
"custom_rotation_input": "Insira a rotação em horas",
|
||||||
|
"protection_section_label": "Proteção",
|
||||||
|
"log_and_stats_section_label": "Log de consulta e estatísticas",
|
||||||
|
"ignore_query_log": "Ignorar este cliente no log de consulta",
|
||||||
|
"ignore_statistics": "Ignorar este cliente nas estatísticas"
|
||||||
}
|
}
|
||||||
|
|
|
@ -642,5 +642,6 @@
|
||||||
"anonymizer_notification": "<0>Nota:</0> Anonimizarea IP este activată. Puteți să o dezactivați în <1>Setări generale</1>.",
|
"anonymizer_notification": "<0>Nota:</0> Anonimizarea IP este activată. Puteți să o dezactivați în <1>Setări generale</1>.",
|
||||||
"confirm_dns_cache_clear": "Sunteți sigur că doriți să ștergeți memoria cache DNS?",
|
"confirm_dns_cache_clear": "Sunteți sigur că doriți să ștergeți memoria cache DNS?",
|
||||||
"cache_cleared": "Cache-ul DNS a fost golit cu succes",
|
"cache_cleared": "Cache-ul DNS a fost golit cu succes",
|
||||||
"clear_cache": "Goliți memoria cache"
|
"clear_cache": "Goliți memoria cache",
|
||||||
|
"protection_section_label": "Protecție"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "Журнал запросов успешно очищен",
|
"query_log_cleared": "Журнал запросов успешно очищен",
|
||||||
"query_log_updated": "Журнал запросов успешно обновлён",
|
"query_log_updated": "Журнал запросов успешно обновлён",
|
||||||
"query_log_clear": "Очистить журнал запросов",
|
"query_log_clear": "Очистить журнал запросов",
|
||||||
"query_log_retention": "Сохранение журнала запросов",
|
"query_log_retention": "Частота ротации журнала запросов",
|
||||||
"query_log_enable": "Включить журнал",
|
"query_log_enable": "Включить журнал",
|
||||||
"query_log_configuration": "Настройка журнала",
|
"query_log_configuration": "Настройка журнала",
|
||||||
"query_log_disabled": "Журнал запросов выключен, его можно включить в <0>настройках</0>",
|
"query_log_disabled": "Журнал запросов выключен, его можно включить в <0>настройках</0>",
|
||||||
"query_log_strict_search": "Используйте двойные кавычки для строгого поиска",
|
"query_log_strict_search": "Используйте двойные кавычки для строгого поиска",
|
||||||
"query_log_retention_confirm": "Вы уверены, что хотите изменить срок хранения запросов? При сокращении интервала данные могут быть утеряны",
|
"query_log_retention_confirm": "Вы уверены, что хотите изменить частоту ротации журнала запросов? При сокращении срока данные могут быть утеряны",
|
||||||
"anonymize_client_ip": "Анонимизировать IP-адрес клиента",
|
"anonymize_client_ip": "Анонимизировать IP-адрес клиента",
|
||||||
"anonymize_client_ip_desc": "Не сохранять полный IP-адрес клиента в журналах и статистике",
|
"anonymize_client_ip_desc": "Не сохранять полный IP-адрес клиента в журналах и статистике",
|
||||||
"dns_config": "Настройки DNS-сервера",
|
"dns_config": "Настройки DNS-сервера",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Отключить защиту на {{count}} час",
|
"disable_notify_for_hours": "Отключить защиту на {{count}} час",
|
||||||
"disable_notify_for_hours_plural": "Отключить защиту на {{count}} часов",
|
"disable_notify_for_hours_plural": "Отключить защиту на {{count}} часов",
|
||||||
"disable_notify_until_tomorrow": "Отключить защиту до завтра",
|
"disable_notify_until_tomorrow": "Отключить защиту до завтра",
|
||||||
"enable_protection_timer": "Защита будет включена в {{time}}"
|
"enable_protection_timer": "Защита будет включена в {{time}}",
|
||||||
|
"custom_retention_input": "Введите срок хранения в часах",
|
||||||
|
"custom_rotation_input": "Введите частоту ротации в часах",
|
||||||
|
"protection_section_label": "Защита",
|
||||||
|
"log_and_stats_section_label": "Журнал запросов и статистика",
|
||||||
|
"ignore_query_log": "Игнорировать этого клиента в журнале запросов",
|
||||||
|
"ignore_statistics": "Игнорировать этого клиента в статистике"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "Denník dopytov bol úspešne vymazaný",
|
"query_log_cleared": "Denník dopytov bol úspešne vymazaný",
|
||||||
"query_log_updated": "Denník dopytov bol úspešne aktualizovaný",
|
"query_log_updated": "Denník dopytov bol úspešne aktualizovaný",
|
||||||
"query_log_clear": "Vymazať denníky dopytov",
|
"query_log_clear": "Vymazať denníky dopytov",
|
||||||
"query_log_retention": "Obdobie záznamu denníka dopytov",
|
"query_log_retention": "Rotácia denníkov dopytov",
|
||||||
"query_log_enable": "Zapnúť denník",
|
"query_log_enable": "Zapnúť denník",
|
||||||
"query_log_configuration": "Konfigurácia denníka",
|
"query_log_configuration": "Konfigurácia denníka",
|
||||||
"query_log_disabled": "Protokol dopytov je vypnutý a možno ho nakonfigurovať v <0>nastaveniach</0>",
|
"query_log_disabled": "Protokol dopytov je vypnutý a možno ho nakonfigurovať v <0>nastaveniach</0>",
|
||||||
"query_log_strict_search": "Na prísne vyhľadávanie použite dvojité úvodzovky",
|
"query_log_strict_search": "Na prísne vyhľadávanie použite dvojité úvodzovky",
|
||||||
"query_log_retention_confirm": "Naozaj chcete zmeniť uchovávanie denníku dopytov? Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
|
"query_log_retention_confirm": "Naozaj chcete zmeniť rotáciu denníka dopytov? Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
|
||||||
"anonymize_client_ip": "Anonymizujte IP klienta",
|
"anonymize_client_ip": "Anonymizujte IP klienta",
|
||||||
"anonymize_client_ip_desc": "Neukladať úplnú IP adresu klienta do protokolov a štatistík",
|
"anonymize_client_ip_desc": "Neukladať úplnú IP adresu klienta do protokolov a štatistík",
|
||||||
"dns_config": "Konfigurácia DNS servera",
|
"dns_config": "Konfigurácia DNS servera",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Vypnite ochranu na {{count}} hodinu",
|
"disable_notify_for_hours": "Vypnite ochranu na {{count}} hodinu",
|
||||||
"disable_notify_for_hours_plural": "Vypnite ochranu na {{count}} hodín",
|
"disable_notify_for_hours_plural": "Vypnite ochranu na {{count}} hodín",
|
||||||
"disable_notify_until_tomorrow": "Vypnúť ochranu do zajtra",
|
"disable_notify_until_tomorrow": "Vypnúť ochranu do zajtra",
|
||||||
"enable_protection_timer": "Ochrana bude zapnutá o {{time}}"
|
"enable_protection_timer": "Ochrana bude zapnutá o {{time}}",
|
||||||
|
"custom_retention_input": "Zadajte retenciu v hodinách",
|
||||||
|
"custom_rotation_input": "Zadajte rotáciu v hodinách",
|
||||||
|
"protection_section_label": "Ochrana",
|
||||||
|
"log_and_stats_section_label": "Protokol dopytov a štatistiky",
|
||||||
|
"ignore_query_log": "Ignorovať tohto klienta v denníku dopytov",
|
||||||
|
"ignore_statistics": "Ignorovanie tohto klienta v štatistikách"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "Dnevnik poizvedb je uspešno izbrisan",
|
"query_log_cleared": "Dnevnik poizvedb je uspešno izbrisan",
|
||||||
"query_log_updated": "Dnevnik poizvedb je bil uspešno posodobljen",
|
"query_log_updated": "Dnevnik poizvedb je bil uspešno posodobljen",
|
||||||
"query_log_clear": "Počisti dnevnike poizvedb",
|
"query_log_clear": "Počisti dnevnike poizvedb",
|
||||||
"query_log_retention": "Zadrževanje dnevnikov poizvedb",
|
"query_log_retention": "Rotacija dnevnikov poizvedb",
|
||||||
"query_log_enable": "Omogoči dnevni",
|
"query_log_enable": "Omogoči dnevni",
|
||||||
"query_log_configuration": "Konfiguracija dnevnikov",
|
"query_log_configuration": "Konfiguracija dnevnikov",
|
||||||
"query_log_disabled": "Dnevnik poizvedb je onemogočen in ga je mogoče konfigurirati v <0>nastavitvah</0>",
|
"query_log_disabled": "Dnevnik poizvedb je onemogočen in ga je mogoče konfigurirati v <0>nastavitvah</0>",
|
||||||
"query_log_strict_search": "Za strogo iskanje uporabite dvojne narekovaje",
|
"query_log_strict_search": "Za strogo iskanje uporabite dvojne narekovaje",
|
||||||
"query_log_retention_confirm": "Ali ste prepričani, da želite spremeniti zadrževanje dnevnika poizvedb? Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni",
|
"query_log_retention_confirm": "Ali ste prepričani, da želite spremeniti rotacijo dnevnika poizvedb? Če zmanjšate vrednost intervala, bodo nekateri podatki izgubljeni",
|
||||||
"anonymize_client_ip": "Anonimiziraj odjemalca IP",
|
"anonymize_client_ip": "Anonimiziraj odjemalca IP",
|
||||||
"anonymize_client_ip_desc": "Ne shrani celotnega naslova IP odjemalca v dnevnikih ali statistiki",
|
"anonymize_client_ip_desc": "Ne shrani celotnega naslova IP odjemalca v dnevnikih ali statistiki",
|
||||||
"dns_config": "Konfiguracija strežnika DNS",
|
"dns_config": "Konfiguracija strežnika DNS",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Onemogoči zaščito za {{count}} uro",
|
"disable_notify_for_hours": "Onemogoči zaščito za {{count}} uro",
|
||||||
"disable_notify_for_hours_plural": "Onemogoči zaščito za {{count}} ur",
|
"disable_notify_for_hours_plural": "Onemogoči zaščito za {{count}} ur",
|
||||||
"disable_notify_until_tomorrow": "Onemogoči zaščito do jutri",
|
"disable_notify_until_tomorrow": "Onemogoči zaščito do jutri",
|
||||||
"enable_protection_timer": "Zaščita bo omogočena ob {{time}}"
|
"enable_protection_timer": "Zaščita bo omogočena ob {{time}}",
|
||||||
|
"custom_retention_input": "Vnesite zadrževanje v urah",
|
||||||
|
"custom_rotation_input": "Vnesite rotacijo v urah",
|
||||||
|
"protection_section_label": "Zaščita",
|
||||||
|
"log_and_stats_section_label": "Dnevnik poizvedb in statistika",
|
||||||
|
"ignore_query_log": "Ignorirajte tega odjemalca v dnevniku poizvedb",
|
||||||
|
"ignore_statistics": "Ignoriranje tega odjemalca v statistiki"
|
||||||
}
|
}
|
||||||
|
|
|
@ -642,5 +642,6 @@
|
||||||
"anonymizer_notification": "<0>Nota:</0> IP prepoznavanje je omogućeno. Možete ga onemogućiti u opštim <1>postavkama</1>.",
|
"anonymizer_notification": "<0>Nota:</0> IP prepoznavanje je omogućeno. Možete ga onemogućiti u opštim <1>postavkama</1>.",
|
||||||
"confirm_dns_cache_clear": "Želite li zaista da obrišite DNS keš?",
|
"confirm_dns_cache_clear": "Želite li zaista da obrišite DNS keš?",
|
||||||
"cache_cleared": "DNS keš je uspešno očišćen",
|
"cache_cleared": "DNS keš je uspešno očišćen",
|
||||||
"clear_cache": "Obriši keš memoriju"
|
"clear_cache": "Obriši keš memoriju",
|
||||||
|
"protection_section_label": "Zaštita"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "Sorgu günlüğü başarıyla temizlendi",
|
"query_log_cleared": "Sorgu günlüğü başarıyla temizlendi",
|
||||||
"query_log_updated": "Sorgu günlüğü başarıyla güncellendi",
|
"query_log_updated": "Sorgu günlüğü başarıyla güncellendi",
|
||||||
"query_log_clear": "Sorgu günlüklerini temizle",
|
"query_log_clear": "Sorgu günlüklerini temizle",
|
||||||
"query_log_retention": "Sorgu günlüklerini sakla",
|
"query_log_retention": "Sorgu günlükleri rotasyonu",
|
||||||
"query_log_enable": "Günlüğü etkinleştir",
|
"query_log_enable": "Günlüğü etkinleştir",
|
||||||
"query_log_configuration": "Günlük yapılandırması",
|
"query_log_configuration": "Günlük yapılandırması",
|
||||||
"query_log_disabled": "Sorgu günlüğü devre dışı bırakıldı, bunu <0>ayarlar</0> kısmından yapılandırılabilirsiniz",
|
"query_log_disabled": "Sorgu günlüğü devre dışı bırakıldı, bunu <0>ayarlar</0> kısmından yapılandırılabilirsiniz",
|
||||||
"query_log_strict_search": "Tam arama için çift tırnak işareti kullanın",
|
"query_log_strict_search": "Tam arama için çift tırnak işareti kullanın",
|
||||||
"query_log_retention_confirm": "Sorgu günlüğü saklama süresini değiştirmek istediğinize emin misiniz? Aralık değerini azaltırsanız, bazı veriler kaybolacaktır",
|
"query_log_retention_confirm": "Sorgu günlüğü rotasyonunu değiştirmek istediğinizden emin misiniz? Aralık değerini düşürürseniz, bazı veriler kaybolacaktır.",
|
||||||
"anonymize_client_ip": "İstemcinin IP adresini gizle",
|
"anonymize_client_ip": "İstemcinin IP adresini gizle",
|
||||||
"anonymize_client_ip_desc": "İstemcinin tam IP adresini günlüklere veya istatistiklere kaydetmeyin",
|
"anonymize_client_ip_desc": "İstemcinin tam IP adresini günlüklere veya istatistiklere kaydetmeyin",
|
||||||
"dns_config": "DNS sunucu yapılandırması",
|
"dns_config": "DNS sunucu yapılandırması",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "Korumayı {{count}} saatliğine devre dışı bırak",
|
"disable_notify_for_hours": "Korumayı {{count}} saatliğine devre dışı bırak",
|
||||||
"disable_notify_for_hours_plural": "Korumayı {{count}} saatliğine devre dışı bırak",
|
"disable_notify_for_hours_plural": "Korumayı {{count}} saatliğine devre dışı bırak",
|
||||||
"disable_notify_until_tomorrow": "Korumayı yarına kadar devre dışı bırak",
|
"disable_notify_until_tomorrow": "Korumayı yarına kadar devre dışı bırak",
|
||||||
"enable_protection_timer": "Koruma {{time}} içinde etkinleştirilecektir"
|
"enable_protection_timer": "Koruma {{time}} içinde etkinleştirilecektir",
|
||||||
|
"custom_retention_input": "Saklama süresini saat olarak girin",
|
||||||
|
"custom_rotation_input": "Rotasyonu saat cinsinden girin",
|
||||||
|
"protection_section_label": "Koruma",
|
||||||
|
"log_and_stats_section_label": "Sorgu günlüğü ve istatistikler",
|
||||||
|
"ignore_query_log": "Sorgu günlüğünde bu istemciyi yoksay",
|
||||||
|
"ignore_statistics": "İstatistiklerde bu istemciyi yoksay"
|
||||||
}
|
}
|
||||||
|
|
|
@ -642,5 +642,6 @@
|
||||||
"anonymizer_notification": "<0>Примітка:</0> IP-анонімізацію ввімкнено. Ви можете вимкнути його в <1>Загальні налаштування</1> .",
|
"anonymizer_notification": "<0>Примітка:</0> IP-анонімізацію ввімкнено. Ви можете вимкнути його в <1>Загальні налаштування</1> .",
|
||||||
"confirm_dns_cache_clear": "Ви впевнені, що бажаєте очистити кеш DNS?",
|
"confirm_dns_cache_clear": "Ви впевнені, що бажаєте очистити кеш DNS?",
|
||||||
"cache_cleared": "Кеш DNS успішно очищено",
|
"cache_cleared": "Кеш DNS успішно очищено",
|
||||||
"clear_cache": "Очистити кеш"
|
"clear_cache": "Очистити кеш",
|
||||||
|
"protection_section_label": "Захист"
|
||||||
}
|
}
|
||||||
|
|
|
@ -642,5 +642,6 @@
|
||||||
"anonymizer_notification": "<0> Lưu ý:</0> Tính năng ẩn danh IP được bật. Bạn có thể tắt nó trong <1> Cài đặt chung</1>.",
|
"anonymizer_notification": "<0> Lưu ý:</0> Tính năng ẩn danh IP được bật. Bạn có thể tắt nó trong <1> Cài đặt chung</1>.",
|
||||||
"confirm_dns_cache_clear": "Bạn có chắc chắn muốn xóa bộ đệm ẩn DNS không?",
|
"confirm_dns_cache_clear": "Bạn có chắc chắn muốn xóa bộ đệm ẩn DNS không?",
|
||||||
"cache_cleared": "Đã xóa thành công bộ đệm DNS",
|
"cache_cleared": "Đã xóa thành công bộ đệm DNS",
|
||||||
"clear_cache": "Xóa bộ nhớ cache"
|
"clear_cache": "Xóa bộ nhớ cache",
|
||||||
|
"protection_section_label": "Sự bảo vệ"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "查询日志已成功清除",
|
"query_log_cleared": "查询日志已成功清除",
|
||||||
"query_log_updated": "已成功更新查询日志",
|
"query_log_updated": "已成功更新查询日志",
|
||||||
"query_log_clear": "清除查询日志",
|
"query_log_clear": "清除查询日志",
|
||||||
"query_log_retention": "查询记录保留时间",
|
"query_log_retention": "查询日志保留时间",
|
||||||
"query_log_enable": "启用日志",
|
"query_log_enable": "启用日志",
|
||||||
"query_log_configuration": "日志配置",
|
"query_log_configuration": "日志配置",
|
||||||
"query_log_disabled": "查询日志已禁用,在<0>这些设置</0>中能配置它们",
|
"query_log_disabled": "查询日志已禁用,在<0>这些设置</0>中能配置它们",
|
||||||
"query_log_strict_search": "使用双引号进行严谨搜索",
|
"query_log_strict_search": "使用双引号进行严谨搜索",
|
||||||
"query_log_retention_confirm": "您确定要更改查询记录保留时间吗? 如果您减少间隔时间的值, 某些数据可能会丢失。",
|
"query_log_retention_confirm": "您确定要更改查询记录保留时间吗?如果减少时间间隔数值,某些数据可能会丢失",
|
||||||
"anonymize_client_ip": "匿名化客户端IP",
|
"anonymize_client_ip": "匿名化客户端IP",
|
||||||
"anonymize_client_ip_desc": "不要在日志和统计信息中保存客户端的完整 IP 地址",
|
"anonymize_client_ip_desc": "不要在日志和统计信息中保存客户端的完整 IP 地址",
|
||||||
"dns_config": "DNS 服务配置",
|
"dns_config": "DNS 服务配置",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "禁用保护 {{count}} 小时",
|
"disable_notify_for_hours": "禁用保护 {{count}} 小时",
|
||||||
"disable_notify_for_hours_plural": "禁用保护 {{count}} 小时",
|
"disable_notify_for_hours_plural": "禁用保护 {{count}} 小时",
|
||||||
"disable_notify_until_tomorrow": "禁用保护直到明天",
|
"disable_notify_until_tomorrow": "禁用保护直到明天",
|
||||||
"enable_protection_timer": "保护将于 {{time}} 启用"
|
"enable_protection_timer": "保护将于 {{time}} 启用",
|
||||||
|
"custom_retention_input": "输入保留时间(小时)",
|
||||||
|
"custom_rotation_input": "输入旋转时间(小时)",
|
||||||
|
"protection_section_label": "防护",
|
||||||
|
"log_and_stats_section_label": "查询日志和统计数据",
|
||||||
|
"ignore_query_log": "在查询日志中忽略此客户端",
|
||||||
|
"ignore_statistics": "在统计数据中忽略此客户端"
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,12 +257,12 @@
|
||||||
"query_log_cleared": "該查詢記錄已被成功地清除",
|
"query_log_cleared": "該查詢記錄已被成功地清除",
|
||||||
"query_log_updated": "該查詢記錄已被成功地更新",
|
"query_log_updated": "該查詢記錄已被成功地更新",
|
||||||
"query_log_clear": "清除查詢記錄",
|
"query_log_clear": "清除查詢記錄",
|
||||||
"query_log_retention": "查詢記錄保留",
|
"query_log_retention": "查詢記錄保留時間",
|
||||||
"query_log_enable": "啟用記錄",
|
"query_log_enable": "啟用記錄",
|
||||||
"query_log_configuration": "記錄配置",
|
"query_log_configuration": "記錄配置",
|
||||||
"query_log_disabled": "查詢記錄被禁用並可在<0>設定</0>中被配置",
|
"query_log_disabled": "查詢記錄被禁用並可在<0>設定</0>中被配置",
|
||||||
"query_log_strict_search": "使用雙引號於嚴謹的搜尋",
|
"query_log_strict_search": "使用雙引號於嚴謹的搜尋",
|
||||||
"query_log_retention_confirm": "您確定您想要更改查詢記錄保留嗎?如果您減少該間隔值,某些資料將被丟失",
|
"query_log_retention_confirm": "您確定要更改記錄檔保存期限嗎?如果您縮短期限部分資料可能將會遺失",
|
||||||
"anonymize_client_ip": "將用戶端 IP 匿名",
|
"anonymize_client_ip": "將用戶端 IP 匿名",
|
||||||
"anonymize_client_ip_desc": "不要儲存用戶端之完整的 IP 位址到記錄或統計資料裡",
|
"anonymize_client_ip_desc": "不要儲存用戶端之完整的 IP 位址到記錄或統計資料裡",
|
||||||
"dns_config": "DNS 伺服器配置",
|
"dns_config": "DNS 伺服器配置",
|
||||||
|
@ -668,5 +668,11 @@
|
||||||
"disable_notify_for_hours": "計 {{count}} 小時禁用防護",
|
"disable_notify_for_hours": "計 {{count}} 小時禁用防護",
|
||||||
"disable_notify_for_hours_plural": "計 {{count}} 小時禁用防護",
|
"disable_notify_for_hours_plural": "計 {{count}} 小時禁用防護",
|
||||||
"disable_notify_until_tomorrow": "禁用防護直到明天",
|
"disable_notify_until_tomorrow": "禁用防護直到明天",
|
||||||
"enable_protection_timer": "防護將於 {{time}} 被啟用"
|
"enable_protection_timer": "防護將於 {{time}} 被啟用",
|
||||||
|
"custom_retention_input": "輸入保留時間(小時)",
|
||||||
|
"custom_rotation_input": "輸入旋轉時間(小時)",
|
||||||
|
"protection_section_label": "防護",
|
||||||
|
"log_and_stats_section_label": "查詢記錄和統計資料",
|
||||||
|
"ignore_query_log": "在查詢記錄中忽略此用戶端",
|
||||||
|
"ignore_statistics": "在統計資料中忽略此用戶端"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,21 +2,6 @@ import { createAction } from 'redux-actions';
|
||||||
import apiClient from '../api/Api';
|
import apiClient from '../api/Api';
|
||||||
import { addErrorToast, addSuccessToast } from './toasts';
|
import { addErrorToast, addSuccessToast } from './toasts';
|
||||||
|
|
||||||
export const getBlockedServicesAvailableServicesRequest = createAction('GET_BLOCKED_SERVICES_AVAILABLE_SERVICES_REQUEST');
|
|
||||||
export const getBlockedServicesAvailableServicesFailure = createAction('GET_BLOCKED_SERVICES_AVAILABLE_SERVICES_FAILURE');
|
|
||||||
export const getBlockedServicesAvailableServicesSuccess = createAction('GET_BLOCKED_SERVICES_AVAILABLE_SERVICES_SUCCESS');
|
|
||||||
|
|
||||||
export const getBlockedServicesAvailableServices = () => async (dispatch) => {
|
|
||||||
dispatch(getBlockedServicesAvailableServicesRequest());
|
|
||||||
try {
|
|
||||||
const data = await apiClient.getBlockedServicesAvailableServices();
|
|
||||||
dispatch(getBlockedServicesAvailableServicesSuccess(data));
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(addErrorToast({ error }));
|
|
||||||
dispatch(getBlockedServicesAvailableServicesFailure());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getBlockedServicesRequest = createAction('GET_BLOCKED_SERVICES_REQUEST');
|
export const getBlockedServicesRequest = createAction('GET_BLOCKED_SERVICES_REQUEST');
|
||||||
export const getBlockedServicesFailure = createAction('GET_BLOCKED_SERVICES_FAILURE');
|
export const getBlockedServicesFailure = createAction('GET_BLOCKED_SERVICES_FAILURE');
|
||||||
export const getBlockedServicesSuccess = createAction('GET_BLOCKED_SERVICES_SUCCESS');
|
export const getBlockedServicesSuccess = createAction('GET_BLOCKED_SERVICES_SUCCESS');
|
||||||
|
|
|
@ -479,19 +479,12 @@ class Api {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocked services
|
// Blocked services
|
||||||
BLOCKED_SERVICES_SERVICES = { path: 'blocked_services/services', method: 'GET' };
|
|
||||||
|
|
||||||
BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' };
|
BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' };
|
||||||
|
|
||||||
BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' };
|
BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' };
|
||||||
|
|
||||||
BLOCKED_SERVICES_ALL = { path: 'blocked_services/all', method: 'GET' };
|
BLOCKED_SERVICES_ALL = { path: 'blocked_services/all', method: 'GET' };
|
||||||
|
|
||||||
getBlockedServicesAvailableServices() {
|
|
||||||
const { path, method } = this.BLOCKED_SERVICES_SERVICES;
|
|
||||||
return this.makeRequest(path, method);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllBlockedServices() {
|
getAllBlockedServices() {
|
||||||
const { path, method } = this.BLOCKED_SERVICES_ALL;
|
const { path, method } = this.BLOCKED_SERVICES_ALL;
|
||||||
return this.makeRequest(path, method);
|
return this.makeRequest(path, method);
|
||||||
|
|
|
@ -41,6 +41,17 @@ const settingsCheckboxes = [
|
||||||
placeholder: 'use_adguard_parental',
|
placeholder: 'use_adguard_parental',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const logAndStatsCheckboxes = [
|
||||||
|
{
|
||||||
|
name: 'ignore_querylog',
|
||||||
|
placeholder: 'ignore_query_log',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ignore_statistics',
|
||||||
|
placeholder: 'ignore_statistics',
|
||||||
|
},
|
||||||
|
];
|
||||||
const validate = (values) => {
|
const validate = (values) => {
|
||||||
const errors = {};
|
const errors = {};
|
||||||
const { name, ids } = values;
|
const { name, ids } = values;
|
||||||
|
@ -148,6 +159,9 @@ let Form = (props) => {
|
||||||
settings: {
|
settings: {
|
||||||
title: 'settings',
|
title: 'settings',
|
||||||
component: <div label="settings" title={props.t('main_settings')}>
|
component: <div label="settings" title={props.t('main_settings')}>
|
||||||
|
<div className="form__label--bot form__label--bold">
|
||||||
|
{t('protection_section_label')}
|
||||||
|
</div>
|
||||||
{settingsCheckboxes.map((setting) => (
|
{settingsCheckboxes.map((setting) => (
|
||||||
<div className="form__group" key={setting.name}>
|
<div className="form__group" key={setting.name}>
|
||||||
<Field
|
<Field
|
||||||
|
@ -185,6 +199,19 @@ let Form = (props) => {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="form__label--bold form__label--top form__label--bot">
|
||||||
|
{t('log_and_stats_section_label')}
|
||||||
|
</div>
|
||||||
|
{logAndStatsCheckboxes.map((setting) => (
|
||||||
|
<div className="form__group" key={setting.name}>
|
||||||
|
<Field
|
||||||
|
name={setting.name}
|
||||||
|
type="checkbox"
|
||||||
|
component={CheckboxField}
|
||||||
|
placeholder={t(setting.placeholder)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>,
|
</div>,
|
||||||
},
|
},
|
||||||
block_services: {
|
block_services: {
|
||||||
|
|
|
@ -1,25 +1,37 @@
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Field, reduxForm } from 'redux-form';
|
import {
|
||||||
|
change,
|
||||||
|
Field,
|
||||||
|
formValueSelector,
|
||||||
|
reduxForm,
|
||||||
|
} from 'redux-form';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { Trans, withTranslation } from 'react-i18next';
|
||||||
import flow from 'lodash/flow';
|
import flow from 'lodash/flow';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CheckboxField,
|
CheckboxField,
|
||||||
renderRadioField,
|
|
||||||
toFloatNumber,
|
toFloatNumber,
|
||||||
renderTextareaField,
|
renderTextareaField, renderInputField, renderRadioField,
|
||||||
} from '../../../helpers/form';
|
} from '../../../helpers/form';
|
||||||
import {
|
import {
|
||||||
FORM_NAME,
|
FORM_NAME,
|
||||||
QUERY_LOG_INTERVALS_DAYS,
|
QUERY_LOG_INTERVALS_DAYS,
|
||||||
HOUR,
|
HOUR,
|
||||||
DAY,
|
DAY,
|
||||||
|
RETENTION_CUSTOM,
|
||||||
|
RETENTION_CUSTOM_INPUT,
|
||||||
|
RETENTION_RANGE,
|
||||||
|
CUSTOM_INTERVAL,
|
||||||
} from '../../../helpers/constants';
|
} from '../../../helpers/constants';
|
||||||
import '../FormButton.css';
|
import '../FormButton.css';
|
||||||
|
|
||||||
|
|
||||||
const getIntervalTitle = (interval, t) => {
|
const getIntervalTitle = (interval, t) => {
|
||||||
switch (interval) {
|
switch (interval) {
|
||||||
|
case RETENTION_CUSTOM:
|
||||||
|
return t('settings_custom');
|
||||||
case 6 * HOUR:
|
case 6 * HOUR:
|
||||||
return t('interval_6_hour');
|
return t('interval_6_hour');
|
||||||
case DAY:
|
case DAY:
|
||||||
|
@ -42,11 +54,26 @@ const getIntervalFields = (processing, t, toNumber) => QUERY_LOG_INTERVALS_DAYS.
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
const Form = (props) => {
|
let Form = (props) => {
|
||||||
const {
|
const {
|
||||||
handleSubmit, submitting, invalid, processing, processingClear, handleClear, t,
|
handleSubmit,
|
||||||
|
submitting,
|
||||||
|
invalid,
|
||||||
|
processing,
|
||||||
|
processingClear,
|
||||||
|
handleClear,
|
||||||
|
t,
|
||||||
|
interval,
|
||||||
|
customInterval,
|
||||||
|
dispatch,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (QUERY_LOG_INTERVALS_DAYS.includes(interval)) {
|
||||||
|
dispatch(change(FORM_NAME.LOG_CONFIG, CUSTOM_INTERVAL, null));
|
||||||
|
}
|
||||||
|
}, [interval]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="form__group form__group--settings">
|
<div className="form__group form__group--settings">
|
||||||
|
@ -73,6 +100,37 @@ const Form = (props) => {
|
||||||
</label>
|
</label>
|
||||||
<div className="form__group form__group--settings">
|
<div className="form__group form__group--settings">
|
||||||
<div className="custom-controls-stacked">
|
<div className="custom-controls-stacked">
|
||||||
|
<Field
|
||||||
|
key={RETENTION_CUSTOM}
|
||||||
|
name="interval"
|
||||||
|
type="radio"
|
||||||
|
component={renderRadioField}
|
||||||
|
value={QUERY_LOG_INTERVALS_DAYS.includes(interval)
|
||||||
|
? RETENTION_CUSTOM
|
||||||
|
: interval
|
||||||
|
}
|
||||||
|
placeholder={getIntervalTitle(RETENTION_CUSTOM, t)}
|
||||||
|
normalize={toFloatNumber}
|
||||||
|
disabled={processing}
|
||||||
|
/>
|
||||||
|
{!QUERY_LOG_INTERVALS_DAYS.includes(interval) && (
|
||||||
|
<div className="form__group--input">
|
||||||
|
<div className="form__desc form__desc--top">
|
||||||
|
{t('custom_rotation_input')}
|
||||||
|
</div>
|
||||||
|
<Field
|
||||||
|
key={RETENTION_CUSTOM_INPUT}
|
||||||
|
name={CUSTOM_INTERVAL}
|
||||||
|
type="number"
|
||||||
|
className="form-control"
|
||||||
|
component={renderInputField}
|
||||||
|
disabled={processing}
|
||||||
|
normalize={toFloatNumber}
|
||||||
|
min={RETENTION_RANGE.MIN}
|
||||||
|
max={RETENTION_RANGE.MAX}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{getIntervalFields(processing, t, toFloatNumber)}
|
{getIntervalFields(processing, t, toFloatNumber)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -96,7 +154,12 @@ const Form = (props) => {
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-success btn-standard btn-large"
|
className="btn btn-success btn-standard btn-large"
|
||||||
disabled={submitting || invalid || processing}
|
disabled={
|
||||||
|
submitting
|
||||||
|
|| invalid
|
||||||
|
|| processing
|
||||||
|
|| (!QUERY_LOG_INTERVALS_DAYS.includes(interval) && !customInterval)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Trans>save_btn</Trans>
|
<Trans>save_btn</Trans>
|
||||||
</button>
|
</button>
|
||||||
|
@ -121,8 +184,22 @@ Form.propTypes = {
|
||||||
processing: PropTypes.bool.isRequired,
|
processing: PropTypes.bool.isRequired,
|
||||||
processingClear: PropTypes.bool.isRequired,
|
processingClear: PropTypes.bool.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
|
interval: PropTypes.number,
|
||||||
|
customInterval: PropTypes.number,
|
||||||
|
dispatch: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selector = formValueSelector(FORM_NAME.LOG_CONFIG);
|
||||||
|
|
||||||
|
Form = connect((state) => {
|
||||||
|
const interval = selector(state, 'interval');
|
||||||
|
const customInterval = selector(state, CUSTOM_INTERVAL);
|
||||||
|
return {
|
||||||
|
interval,
|
||||||
|
customInterval,
|
||||||
|
};
|
||||||
|
})(Form);
|
||||||
|
|
||||||
export default flow([
|
export default flow([
|
||||||
withTranslation(),
|
withTranslation(),
|
||||||
reduxForm({ form: FORM_NAME.LOG_CONFIG }),
|
reduxForm({ form: FORM_NAME.LOG_CONFIG }),
|
||||||
|
|
|
@ -4,15 +4,22 @@ import { withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Card from '../../ui/Card';
|
import Card from '../../ui/Card';
|
||||||
import Form from './Form';
|
import Form from './Form';
|
||||||
|
import { HOUR } from '../../../helpers/constants';
|
||||||
|
|
||||||
class LogsConfig extends Component {
|
class LogsConfig extends Component {
|
||||||
handleFormSubmit = (values) => {
|
handleFormSubmit = (values) => {
|
||||||
const { t, interval: prevInterval } = this.props;
|
const { t, interval: prevInterval } = this.props;
|
||||||
const { interval } = values;
|
const { interval, customInterval, ...rest } = values;
|
||||||
|
|
||||||
const data = { ...values, ignored: values.ignored ? values.ignored.split('\n') : [] };
|
const newInterval = customInterval ? customInterval * HOUR : interval;
|
||||||
|
|
||||||
if (interval !== prevInterval) {
|
const data = {
|
||||||
|
...rest,
|
||||||
|
ignored: values.ignored ? values.ignored.split('\n') : [],
|
||||||
|
interval: newInterval,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (newInterval < prevInterval) {
|
||||||
// eslint-disable-next-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
if (window.confirm(t('query_log_retention_confirm'))) {
|
if (window.confirm(t('query_log_retention_confirm'))) {
|
||||||
this.props.setLogsConfig(data);
|
this.props.setLogsConfig(data);
|
||||||
|
@ -32,7 +39,14 @@ class LogsConfig extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
t, enabled, interval, processing, processingClear, anonymize_client_ip, ignored,
|
t,
|
||||||
|
enabled,
|
||||||
|
interval,
|
||||||
|
processing,
|
||||||
|
processingClear,
|
||||||
|
anonymize_client_ip,
|
||||||
|
ignored,
|
||||||
|
customInterval,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -46,6 +60,7 @@ class LogsConfig extends Component {
|
||||||
initialValues={{
|
initialValues={{
|
||||||
enabled,
|
enabled,
|
||||||
interval,
|
interval,
|
||||||
|
customInterval,
|
||||||
anonymize_client_ip,
|
anonymize_client_ip,
|
||||||
ignored: ignored.join('\n'),
|
ignored: ignored.join('\n'),
|
||||||
}}
|
}}
|
||||||
|
@ -62,6 +77,7 @@ class LogsConfig extends Component {
|
||||||
|
|
||||||
LogsConfig.propTypes = {
|
LogsConfig.propTypes = {
|
||||||
interval: PropTypes.number.isRequired,
|
interval: PropTypes.number.isRequired,
|
||||||
|
customInterval: PropTypes.number,
|
||||||
enabled: PropTypes.bool.isRequired,
|
enabled: PropTypes.bool.isRequired,
|
||||||
anonymize_client_ip: PropTypes.bool.isRequired,
|
anonymize_client_ip: PropTypes.bool.isRequired,
|
||||||
processing: PropTypes.bool.isRequired,
|
processing: PropTypes.bool.isRequired,
|
||||||
|
|
|
@ -18,6 +18,11 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form__group--input {
|
||||||
|
max-width: 300px;
|
||||||
|
margin: 0 1.5rem 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.form__group--checkbox {
|
.form__group--checkbox {
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
}
|
}
|
||||||
|
@ -100,6 +105,14 @@
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form__label--bot {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form__label--top {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.form__status {
|
.form__status {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
|
@ -1,32 +1,44 @@
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Field, reduxForm } from 'redux-form';
|
import {
|
||||||
|
change, Field, formValueSelector, reduxForm,
|
||||||
|
} from 'redux-form';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { Trans, withTranslation } from 'react-i18next';
|
||||||
import flow from 'lodash/flow';
|
import flow from 'lodash/flow';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
renderRadioField,
|
renderRadioField,
|
||||||
toNumber,
|
toNumber,
|
||||||
CheckboxField,
|
CheckboxField,
|
||||||
renderTextareaField,
|
renderTextareaField,
|
||||||
|
toFloatNumber,
|
||||||
|
renderInputField,
|
||||||
} from '../../../helpers/form';
|
} from '../../../helpers/form';
|
||||||
import {
|
import {
|
||||||
FORM_NAME,
|
FORM_NAME,
|
||||||
STATS_INTERVALS_DAYS,
|
STATS_INTERVALS_DAYS,
|
||||||
DAY,
|
DAY,
|
||||||
|
RETENTION_CUSTOM,
|
||||||
|
RETENTION_CUSTOM_INPUT,
|
||||||
|
CUSTOM_INTERVAL,
|
||||||
|
RETENTION_RANGE,
|
||||||
} from '../../../helpers/constants';
|
} from '../../../helpers/constants';
|
||||||
import '../FormButton.css';
|
import '../FormButton.css';
|
||||||
|
|
||||||
const getIntervalTitle = (intervalMs, t) => {
|
const getIntervalTitle = (intervalMs, t) => {
|
||||||
switch (intervalMs / DAY) {
|
switch (intervalMs) {
|
||||||
case 1:
|
case RETENTION_CUSTOM:
|
||||||
|
return t('settings_custom');
|
||||||
|
case DAY:
|
||||||
return t('interval_24_hour');
|
return t('interval_24_hour');
|
||||||
default:
|
default:
|
||||||
return t('interval_days', { count: intervalMs / DAY });
|
return t('interval_days', { count: intervalMs / DAY });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Form = (props) => {
|
let Form = (props) => {
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
processing,
|
processing,
|
||||||
|
@ -35,8 +47,17 @@ const Form = (props) => {
|
||||||
handleReset,
|
handleReset,
|
||||||
processingReset,
|
processingReset,
|
||||||
t,
|
t,
|
||||||
|
interval,
|
||||||
|
customInterval,
|
||||||
|
dispatch,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (STATS_INTERVALS_DAYS.includes(interval)) {
|
||||||
|
dispatch(change(FORM_NAME.STATS_CONFIG, CUSTOM_INTERVAL, null));
|
||||||
|
}
|
||||||
|
}, [interval]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="form__group form__group--settings">
|
<div className="form__group form__group--settings">
|
||||||
|
@ -56,6 +77,37 @@ const Form = (props) => {
|
||||||
</div>
|
</div>
|
||||||
<div className="form__group form__group--settings mt-2">
|
<div className="form__group form__group--settings mt-2">
|
||||||
<div className="custom-controls-stacked">
|
<div className="custom-controls-stacked">
|
||||||
|
<Field
|
||||||
|
key={RETENTION_CUSTOM}
|
||||||
|
name="interval"
|
||||||
|
type="radio"
|
||||||
|
component={renderRadioField}
|
||||||
|
value={STATS_INTERVALS_DAYS.includes(interval)
|
||||||
|
? RETENTION_CUSTOM
|
||||||
|
: interval
|
||||||
|
}
|
||||||
|
placeholder={getIntervalTitle(RETENTION_CUSTOM, t)}
|
||||||
|
normalize={toFloatNumber}
|
||||||
|
disabled={processing}
|
||||||
|
/>
|
||||||
|
{!STATS_INTERVALS_DAYS.includes(interval) && (
|
||||||
|
<div className="form__group--input">
|
||||||
|
<div className="form__desc form__desc--top">
|
||||||
|
{t('custom_retention_input')}
|
||||||
|
</div>
|
||||||
|
<Field
|
||||||
|
key={RETENTION_CUSTOM_INPUT}
|
||||||
|
name={CUSTOM_INTERVAL}
|
||||||
|
type="number"
|
||||||
|
className="form-control"
|
||||||
|
component={renderInputField}
|
||||||
|
disabled={processing}
|
||||||
|
normalize={toFloatNumber}
|
||||||
|
min={RETENTION_RANGE.MIN}
|
||||||
|
max={RETENTION_RANGE.MAX}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{STATS_INTERVALS_DAYS.map((interval) => (
|
{STATS_INTERVALS_DAYS.map((interval) => (
|
||||||
<Field
|
<Field
|
||||||
key={interval}
|
key={interval}
|
||||||
|
@ -90,7 +142,12 @@ const Form = (props) => {
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-success btn-standard btn-large"
|
className="btn btn-success btn-standard btn-large"
|
||||||
disabled={submitting || invalid || processing}
|
disabled={
|
||||||
|
submitting
|
||||||
|
|| invalid
|
||||||
|
|| processing
|
||||||
|
|| (!STATS_INTERVALS_DAYS.includes(interval) && !customInterval)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Trans>save_btn</Trans>
|
<Trans>save_btn</Trans>
|
||||||
</button>
|
</button>
|
||||||
|
@ -116,8 +173,22 @@ Form.propTypes = {
|
||||||
processing: PropTypes.bool.isRequired,
|
processing: PropTypes.bool.isRequired,
|
||||||
processingReset: PropTypes.bool.isRequired,
|
processingReset: PropTypes.bool.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
|
interval: PropTypes.number,
|
||||||
|
customInterval: PropTypes.number,
|
||||||
|
dispatch: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selector = formValueSelector(FORM_NAME.STATS_CONFIG);
|
||||||
|
|
||||||
|
Form = connect((state) => {
|
||||||
|
const interval = selector(state, 'interval');
|
||||||
|
const customInterval = selector(state, CUSTOM_INTERVAL);
|
||||||
|
return {
|
||||||
|
interval,
|
||||||
|
customInterval,
|
||||||
|
};
|
||||||
|
})(Form);
|
||||||
|
|
||||||
export default flow([
|
export default flow([
|
||||||
withTranslation(),
|
withTranslation(),
|
||||||
reduxForm({ form: FORM_NAME.STATS_CONFIG }),
|
reduxForm({ form: FORM_NAME.STATS_CONFIG }),
|
||||||
|
|
|
@ -4,13 +4,18 @@ import { withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Card from '../../ui/Card';
|
import Card from '../../ui/Card';
|
||||||
import Form from './Form';
|
import Form from './Form';
|
||||||
|
import { HOUR } from '../../../helpers/constants';
|
||||||
|
|
||||||
class StatsConfig extends Component {
|
class StatsConfig extends Component {
|
||||||
handleFormSubmit = ({ enabled, interval, ignored }) => {
|
handleFormSubmit = ({
|
||||||
|
enabled, interval, ignored, customInterval,
|
||||||
|
}) => {
|
||||||
const { t, interval: prevInterval } = this.props;
|
const { t, interval: prevInterval } = this.props;
|
||||||
|
const newInterval = customInterval ? customInterval * HOUR : interval;
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
enabled,
|
enabled,
|
||||||
interval,
|
interval: newInterval,
|
||||||
ignored: ignored ? ignored.split('\n') : [],
|
ignored: ignored ? ignored.split('\n') : [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,7 +38,13 @@ class StatsConfig extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
t, interval, processing, processingReset, ignored, enabled,
|
t,
|
||||||
|
interval,
|
||||||
|
customInterval,
|
||||||
|
processing,
|
||||||
|
processingReset,
|
||||||
|
ignored,
|
||||||
|
enabled,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -46,6 +57,7 @@ class StatsConfig extends Component {
|
||||||
<Form
|
<Form
|
||||||
initialValues={{
|
initialValues={{
|
||||||
interval,
|
interval,
|
||||||
|
customInterval,
|
||||||
enabled,
|
enabled,
|
||||||
ignored: ignored.join('\n'),
|
ignored: ignored.join('\n'),
|
||||||
}}
|
}}
|
||||||
|
@ -62,6 +74,7 @@ class StatsConfig extends Component {
|
||||||
|
|
||||||
StatsConfig.propTypes = {
|
StatsConfig.propTypes = {
|
||||||
interval: PropTypes.number.isRequired,
|
interval: PropTypes.number.isRequired,
|
||||||
|
customInterval: PropTypes.number,
|
||||||
ignored: PropTypes.array.isRequired,
|
ignored: PropTypes.array.isRequired,
|
||||||
enabled: PropTypes.bool.isRequired,
|
enabled: PropTypes.bool.isRequired,
|
||||||
processing: PropTypes.bool.isRequired,
|
processing: PropTypes.bool.isRequired,
|
||||||
|
|
|
@ -124,6 +124,7 @@ class Settings extends Component {
|
||||||
enabled={queryLogs.enabled}
|
enabled={queryLogs.enabled}
|
||||||
ignored={queryLogs.ignored}
|
ignored={queryLogs.ignored}
|
||||||
interval={queryLogs.interval}
|
interval={queryLogs.interval}
|
||||||
|
customInterval={queryLogs.customInterval}
|
||||||
anonymize_client_ip={queryLogs.anonymize_client_ip}
|
anonymize_client_ip={queryLogs.anonymize_client_ip}
|
||||||
processing={queryLogs.processingSetConfig}
|
processing={queryLogs.processingSetConfig}
|
||||||
processingClear={queryLogs.processingClear}
|
processingClear={queryLogs.processingClear}
|
||||||
|
@ -134,6 +135,7 @@ class Settings extends Component {
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
<StatsConfig
|
<StatsConfig
|
||||||
interval={stats.interval}
|
interval={stats.interval}
|
||||||
|
customInterval={stats.customInterval}
|
||||||
ignored={stats.ignored}
|
ignored={stats.ignored}
|
||||||
enabled={stats.enabled}
|
enabled={stats.enabled}
|
||||||
processing={stats.processingSetConfig}
|
processing={stats.processingSetConfig}
|
||||||
|
@ -166,6 +168,7 @@ Settings.propTypes = {
|
||||||
stats: PropTypes.shape({
|
stats: PropTypes.shape({
|
||||||
processingGetConfig: PropTypes.bool,
|
processingGetConfig: PropTypes.bool,
|
||||||
interval: PropTypes.number,
|
interval: PropTypes.number,
|
||||||
|
customInterval: PropTypes.number,
|
||||||
enabled: PropTypes.bool,
|
enabled: PropTypes.bool,
|
||||||
ignored: PropTypes.array,
|
ignored: PropTypes.array,
|
||||||
processingSetConfig: PropTypes.bool,
|
processingSetConfig: PropTypes.bool,
|
||||||
|
@ -174,6 +177,7 @@ Settings.propTypes = {
|
||||||
queryLogs: PropTypes.shape({
|
queryLogs: PropTypes.shape({
|
||||||
enabled: PropTypes.bool,
|
enabled: PropTypes.bool,
|
||||||
interval: PropTypes.number,
|
interval: PropTypes.number,
|
||||||
|
customInterval: PropTypes.number,
|
||||||
anonymize_client_ip: PropTypes.bool,
|
anonymize_client_ip: PropTypes.bool,
|
||||||
processingSetConfig: PropTypes.bool,
|
processingSetConfig: PropTypes.bool,
|
||||||
processingClear: PropTypes.bool,
|
processingClear: PropTypes.bool,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
@ -42,12 +42,6 @@ const Footer = () => {
|
||||||
const isLoggedIn = profileName !== '';
|
const isLoggedIn = profileName !== '';
|
||||||
const [currentThemeLocal, setCurrentThemeLocal] = useState(THEMES.auto);
|
const [currentThemeLocal, setCurrentThemeLocal] = useState(THEMES.auto);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isLoggedIn) {
|
|
||||||
setUITheme(currentThemeLocal);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getYear = () => {
|
const getYear = () => {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
return today.getFullYear();
|
return today.getFullYear();
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
background-color: var(--rt-nodata-bgcolor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay--visible {
|
.overlay--visible {
|
||||||
|
|
|
@ -220,6 +220,12 @@ export const STATS_INTERVALS_DAYS = [DAY, DAY * 7, DAY * 30, DAY * 90];
|
||||||
|
|
||||||
export const QUERY_LOG_INTERVALS_DAYS = [HOUR * 6, DAY, DAY * 7, DAY * 30, DAY * 90];
|
export const QUERY_LOG_INTERVALS_DAYS = [HOUR * 6, DAY, DAY * 7, DAY * 30, DAY * 90];
|
||||||
|
|
||||||
|
export const RETENTION_CUSTOM = 1;
|
||||||
|
|
||||||
|
export const RETENTION_CUSTOM_INPUT = 'custom_retention_input';
|
||||||
|
|
||||||
|
export const CUSTOM_INTERVAL = 'customInterval';
|
||||||
|
|
||||||
export const FILTERS_INTERVALS_HOURS = [0, 1, 12, 24, 72, 168];
|
export const FILTERS_INTERVALS_HOURS = [0, 1, 12, 24, 72, 168];
|
||||||
|
|
||||||
// Note that translation strings contain these modes (blocking_mode_CONSTANT)
|
// Note that translation strings contain these modes (blocking_mode_CONSTANT)
|
||||||
|
@ -462,6 +468,11 @@ export const UINT32_RANGE = {
|
||||||
MAX: 4294967295,
|
MAX: 4294967295,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const RETENTION_RANGE = {
|
||||||
|
MIN: 1,
|
||||||
|
MAX: 365 * 24,
|
||||||
|
};
|
||||||
|
|
||||||
export const DHCP_VALUES_PLACEHOLDERS = {
|
export const DHCP_VALUES_PLACEHOLDERS = {
|
||||||
ipv4: {
|
ipv4: {
|
||||||
subnet_mask: '255.255.255.0',
|
subnet_mask: '255.255.255.0',
|
||||||
|
@ -537,3 +548,5 @@ export const DISABLE_PROTECTION_TIMINGS = {
|
||||||
HOUR: 60 * 60 * 1000,
|
HOUR: 60 * 60 * 1000,
|
||||||
TOMORROW: 24 * 60 * 60 * 1000,
|
TOMORROW: 24 * 60 * 60 * 1000,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const LOCAL_STORAGE_THEME_KEY = 'account_theme';
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
STANDARD_WEB_PORT,
|
STANDARD_WEB_PORT,
|
||||||
SPECIAL_FILTER_ID,
|
SPECIAL_FILTER_ID,
|
||||||
THEMES,
|
THEMES,
|
||||||
|
LOCAL_STORAGE_THEME_KEY,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -679,19 +680,60 @@ export const setHtmlLangAttr = (language) => {
|
||||||
window.document.documentElement.lang = language;
|
window.document.documentElement.lang = language;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set local storage field
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const setStorageItem = (key, value) => {
|
||||||
|
if (window.localStorage) {
|
||||||
|
window.localStorage.setItem(key, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get local storage field
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const getStorageItem = (key) => (window.localStorage
|
||||||
|
? window.localStorage.getItem(key)
|
||||||
|
: null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set local storage theme field
|
||||||
|
*
|
||||||
|
* @param {string} theme
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const setTheme = (theme) => {
|
||||||
|
setStorageItem(LOCAL_STORAGE_THEME_KEY, theme);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get local storage theme field
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const getTheme = () => getStorageItem(LOCAL_STORAGE_THEME_KEY) || THEMES.light;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets UI theme.
|
* Sets UI theme.
|
||||||
*
|
*
|
||||||
* @param theme
|
* @param theme
|
||||||
*/
|
*/
|
||||||
export const setUITheme = (theme) => {
|
export const setUITheme = (theme) => {
|
||||||
let currentTheme = theme;
|
let currentTheme = theme || getTheme();
|
||||||
|
|
||||||
if (currentTheme === THEMES.auto) {
|
if (currentTheme === THEMES.auto) {
|
||||||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
currentTheme = prefersDark ? THEMES.dark : THEMES.light;
|
currentTheme = prefersDark ? THEMES.dark : THEMES.light;
|
||||||
}
|
}
|
||||||
|
setTheme(currentTheme);
|
||||||
document.body.dataset.theme = currentTheme;
|
document.body.dataset.theme = currentTheme;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -177,7 +177,7 @@ const dashboard = handleActions(
|
||||||
autoClients: [],
|
autoClients: [],
|
||||||
supportedTags: [],
|
supportedTags: [],
|
||||||
name: '',
|
name: '',
|
||||||
theme: 'auto',
|
theme: undefined,
|
||||||
checkUpdateFlag: false,
|
checkUpdateFlag: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { handleActions } from 'redux-actions';
|
import { handleActions } from 'redux-actions';
|
||||||
|
|
||||||
import * as actions from '../actions/queryLogs';
|
import * as actions from '../actions/queryLogs';
|
||||||
import { DEFAULT_LOGS_FILTER, DAY } from '../helpers/constants';
|
import {
|
||||||
|
DEFAULT_LOGS_FILTER, DAY, QUERY_LOG_INTERVALS_DAYS, HOUR,
|
||||||
|
} from '../helpers/constants';
|
||||||
|
|
||||||
const queryLogs = handleActions(
|
const queryLogs = handleActions(
|
||||||
{
|
{
|
||||||
|
@ -59,6 +61,9 @@ const queryLogs = handleActions(
|
||||||
[actions.getLogsConfigSuccess]: (state, { payload }) => ({
|
[actions.getLogsConfigSuccess]: (state, { payload }) => ({
|
||||||
...state,
|
...state,
|
||||||
...payload,
|
...payload,
|
||||||
|
customInterval: !QUERY_LOG_INTERVALS_DAYS.includes(payload.interval)
|
||||||
|
? payload.interval / HOUR
|
||||||
|
: null,
|
||||||
processingGetConfig: false,
|
processingGetConfig: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -95,6 +100,7 @@ const queryLogs = handleActions(
|
||||||
anonymize_client_ip: false,
|
anonymize_client_ip: false,
|
||||||
isDetailed: true,
|
isDetailed: true,
|
||||||
isEntireLog: false,
|
isEntireLog: false,
|
||||||
|
customInterval: null,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { handleActions } from 'redux-actions';
|
import { handleActions } from 'redux-actions';
|
||||||
import { normalizeTopClients } from '../helpers/helpers';
|
import { normalizeTopClients } from '../helpers/helpers';
|
||||||
import { DAY } from '../helpers/constants';
|
import { DAY, HOUR, STATS_INTERVALS_DAYS } from '../helpers/constants';
|
||||||
|
|
||||||
import * as actions from '../actions/stats';
|
import * as actions from '../actions/stats';
|
||||||
|
|
||||||
|
@ -27,6 +27,9 @@ const stats = handleActions(
|
||||||
[actions.getStatsConfigSuccess]: (state, { payload }) => ({
|
[actions.getStatsConfigSuccess]: (state, { payload }) => ({
|
||||||
...state,
|
...state,
|
||||||
...payload,
|
...payload,
|
||||||
|
customInterval: !STATS_INTERVALS_DAYS.includes(payload.interval)
|
||||||
|
? payload.interval / HOUR
|
||||||
|
: null,
|
||||||
processingGetConfig: false,
|
processingGetConfig: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -93,6 +96,7 @@ const stats = handleActions(
|
||||||
processingStats: true,
|
processingStats: true,
|
||||||
processingReset: false,
|
processingReset: false,
|
||||||
interval: DAY,
|
interval: DAY,
|
||||||
|
customInterval: null,
|
||||||
...defaultStats,
|
...defaultStats,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,19 +4,27 @@
|
||||||
|
|
||||||
/^[[:space:]]+- .+/ {
|
/^[[:space:]]+- .+/ {
|
||||||
if (FNR - prev_line == 1) {
|
if (FNR - prev_line == 1) {
|
||||||
addrs[addrsnum++] = $2
|
addrs[$2] = true
|
||||||
prev_line = FNR
|
prev_line = FNR
|
||||||
|
|
||||||
|
if ($2 == "0.0.0.0" || $2 == "::") {
|
||||||
|
delete addrs
|
||||||
|
addrs["localhost"] = true
|
||||||
|
|
||||||
|
# Drop all the other addresses.
|
||||||
|
prev_line = -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/^[[:space:]]+port:/ { if (is_dns) port = $2 }
|
/^[[:space:]]+port:/ { if (is_dns) port = $2 }
|
||||||
|
|
||||||
END {
|
END {
|
||||||
for (i in addrs) {
|
for (addr in addrs) {
|
||||||
if (match(addrs[i], ":")) {
|
if (match(addr, ":")) {
|
||||||
print "[" addrs[i] "]:" port
|
print "[" addr "]:" port
|
||||||
} else {
|
} else {
|
||||||
print addrs[i] ":" port
|
print addr ":" port
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
22
go.mod
22
go.mod
|
@ -7,7 +7,7 @@ require (
|
||||||
github.com/AdguardTeam/golibs v0.13.2
|
github.com/AdguardTeam/golibs v0.13.2
|
||||||
github.com/AdguardTeam/urlfilter v0.16.1
|
github.com/AdguardTeam/urlfilter v0.16.1
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.2.6
|
github.com/ameshkov/dnscrypt/v2 v2.2.7
|
||||||
github.com/digineo/go-ipset/v2 v2.2.1
|
github.com/digineo/go-ipset/v2 v2.2.1
|
||||||
github.com/dimfeld/httptreemux/v5 v5.5.0
|
github.com/dimfeld/httptreemux/v5 v5.5.0
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
|
@ -22,14 +22,17 @@ require (
|
||||||
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
|
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
|
||||||
github.com/mdlayher/netlink v1.7.1
|
github.com/mdlayher/netlink v1.7.1
|
||||||
github.com/mdlayher/packet v1.1.1
|
github.com/mdlayher/packet v1.1.1
|
||||||
|
// TODO(a.garipov): This package is deprecated; find a new one or use our
|
||||||
|
// own code for that. Perhaps, use gopacket.
|
||||||
|
github.com/mdlayher/raw v0.1.0
|
||||||
github.com/miekg/dns v1.1.53
|
github.com/miekg/dns v1.1.53
|
||||||
github.com/quic-go/quic-go v0.33.0
|
github.com/quic-go/quic-go v0.33.0
|
||||||
github.com/stretchr/testify v1.8.2
|
github.com/stretchr/testify v1.8.2
|
||||||
github.com/ti-mo/netfilter v0.5.0
|
github.com/ti-mo/netfilter v0.5.0
|
||||||
go.etcd.io/bbolt v1.3.7
|
go.etcd.io/bbolt v1.3.7
|
||||||
golang.org/x/crypto v0.7.0
|
golang.org/x/crypto v0.8.0
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
||||||
golang.org/x/net v0.8.0
|
golang.org/x/net v0.9.0
|
||||||
golang.org/x/sys v0.7.0
|
golang.org/x/sys v0.7.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
@ -45,8 +48,7 @@ require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect
|
github.com/google/pprof v0.0.0-20230406165453-00490a63f317 // indirect
|
||||||
github.com/mdlayher/raw v0.1.0 // indirect
|
|
||||||
github.com/mdlayher/socket v0.4.0 // indirect
|
github.com/mdlayher/socket v0.4.0 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.9.2 // indirect
|
github.com/onsi/ginkgo/v2 v2.9.2 // indirect
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
|
@ -54,11 +56,11 @@ require (
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.0 // indirect
|
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.2.0 // indirect
|
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
|
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
|
||||||
golang.org/x/mod v0.9.0 // indirect
|
golang.org/x/mod v0.10.0 // indirect
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
golang.org/x/text v0.8.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
golang.org/x/tools v0.7.0 // indirect
|
golang.org/x/tools v0.8.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
36
go.sum
36
go.sum
|
@ -15,8 +15,8 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.2.6 h1:rE7AFbPWebq7me7RVS66Cipd1m7ef1yf2+C8QzjQXXE=
|
github.com/ameshkov/dnscrypt/v2 v2.2.7 h1:aEitLIR8HcxVodZ79mgRcCiC0A0I5kZPBuWGFwwulAw=
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.2.6/go.mod h1:qPWhwz6FdSmuK7W4sMyvogrez4MWdtzosdqlr0Rg3ow=
|
github.com/ameshkov/dnscrypt/v2 v2.2.7/go.mod h1:qPWhwz6FdSmuK7W4sMyvogrez4MWdtzosdqlr0Rg3ow=
|
||||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||||
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM=
|
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM=
|
||||||
|
@ -55,8 +55,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk=
|
github.com/google/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ=
|
||||||
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
|
github.com/google/pprof v0.0.0-20230406165453-00490a63f317/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
|
||||||
github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU=
|
github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU=
|
||||||
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
|
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
|
||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
@ -123,10 +123,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.0 h1:aUBoQdpHzUWtPw5tQZbsD2GnrWCNu7/RIX1PtqGeLYY=
|
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||||
github.com/quic-go/qtls-go1-20 v0.2.0 h1:jUHn+obJ6WI5JudqBO0Iy1ra5Vh5vsitQ1gXQvkmN+E=
|
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||||
github.com/quic-go/qtls-go1-20 v0.2.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||||
github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
|
github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
|
||||||
github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA=
|
github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA=
|
||||||
github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA=
|
github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA=
|
||||||
|
@ -161,15 +161,15 @@ go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
@ -185,8 +185,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
@ -227,15 +227,15 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
||||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
@ -31,8 +31,16 @@ type ServerConfig struct {
|
||||||
Conf4 V4ServerConf `yaml:"dhcpv4"`
|
Conf4 V4ServerConf `yaml:"dhcpv4"`
|
||||||
Conf6 V6ServerConf `yaml:"dhcpv6"`
|
Conf6 V6ServerConf `yaml:"dhcpv6"`
|
||||||
|
|
||||||
WorkDir string `yaml:"-"`
|
// WorkDir is used to store DHCP leases.
|
||||||
DBFilePath string `yaml:"-"`
|
//
|
||||||
|
// Deprecated: Remove it when migration of DHCP leases will not be needed.
|
||||||
|
WorkDir string `yaml:"-"`
|
||||||
|
|
||||||
|
// DataDir is used to store DHCP leases.
|
||||||
|
DataDir string `yaml:"-"`
|
||||||
|
|
||||||
|
// dbFilePath is the path to the file with stored DHCP leases.
|
||||||
|
dbFilePath string `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DHCPServer - DHCP server interface
|
// DHCPServer - DHCP server interface
|
||||||
|
|
293
internal/dhcpd/conn_darwin.go
Normal file
293
internal/dhcpd/conn_darwin.go
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||||
|
"github.com/mdlayher/ethernet"
|
||||||
|
|
||||||
|
//lint:ignore SA1019 See the TODO in go.mod.
|
||||||
|
"github.com/mdlayher/raw"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dhcpUnicastAddr is the combination of MAC and IP addresses for responding to
|
||||||
|
// the unconfigured host.
|
||||||
|
type dhcpUnicastAddr struct {
|
||||||
|
// raw.Addr is embedded here to make *dhcpUcastAddr a net.Addr without
|
||||||
|
// actually implementing all methods. It also contains the client's
|
||||||
|
// hardware address.
|
||||||
|
raw.Addr
|
||||||
|
|
||||||
|
// yiaddr is an IP address just allocated by server for the host.
|
||||||
|
yiaddr net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// dhcpConn is the net.PacketConn capable of handling both net.UDPAddr and
|
||||||
|
// net.HardwareAddr.
|
||||||
|
type dhcpConn struct {
|
||||||
|
// udpConn is the connection for UDP addresses.
|
||||||
|
udpConn net.PacketConn
|
||||||
|
// bcastIP is the broadcast address specific for the configured
|
||||||
|
// interface's subnet.
|
||||||
|
bcastIP net.IP
|
||||||
|
|
||||||
|
// rawConn is the connection for MAC addresses.
|
||||||
|
rawConn net.PacketConn
|
||||||
|
// srcMAC is the hardware address of the configured network interface.
|
||||||
|
srcMAC net.HardwareAddr
|
||||||
|
// srcIP is the IP address of the configured network interface.
|
||||||
|
srcIP net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDHCPConn creates the special connection for DHCP server.
|
||||||
|
func (s *v4Server) newDHCPConn(iface *net.Interface) (c net.PacketConn, err error) {
|
||||||
|
var ucast net.PacketConn
|
||||||
|
if ucast, err = raw.ListenPacket(iface, uint16(ethernet.EtherTypeIPv4), nil); err != nil {
|
||||||
|
return nil, fmt.Errorf("creating raw udp connection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the UDP connection.
|
||||||
|
var bcast net.PacketConn
|
||||||
|
bcast, err = server4.NewIPv4UDPConn(iface.Name, &net.UDPAddr{
|
||||||
|
// TODO(e.burkov): Listening on zeroes makes the server handle
|
||||||
|
// requests from all the interfaces. Inspect the ways to
|
||||||
|
// specify the interface-specific listening addresses.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/3539.
|
||||||
|
IP: net.IP{0, 0, 0, 0},
|
||||||
|
Port: dhcpv4.ServerPort,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating ipv4 udp connection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dhcpConn{
|
||||||
|
udpConn: bcast,
|
||||||
|
bcastIP: s.conf.broadcastIP.AsSlice(),
|
||||||
|
rawConn: ucast,
|
||||||
|
srcMAC: iface.HardwareAddr,
|
||||||
|
srcIP: s.conf.dnsIPAddrs[0].AsSlice(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapErrs is a helper to wrap the errors from two independent underlying
|
||||||
|
// connections.
|
||||||
|
func (*dhcpConn) wrapErrs(action string, udpConnErr, rawConnErr error) (err error) {
|
||||||
|
switch {
|
||||||
|
case udpConnErr != nil && rawConnErr != nil:
|
||||||
|
return errors.List(fmt.Sprintf("%s both connections", action), udpConnErr, rawConnErr)
|
||||||
|
case udpConnErr != nil:
|
||||||
|
return fmt.Errorf("%s udp connection: %w", action, udpConnErr)
|
||||||
|
case rawConnErr != nil:
|
||||||
|
return fmt.Errorf("%s raw connection: %w", action, rawConnErr)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo implements net.PacketConn for *dhcpConn. It selects the underlying
|
||||||
|
// connection to write to based on the type of addr.
|
||||||
|
func (c *dhcpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
switch addr := addr.(type) {
|
||||||
|
case *dhcpUnicastAddr:
|
||||||
|
// Unicast the message to the client's MAC address. Use the raw
|
||||||
|
// connection.
|
||||||
|
//
|
||||||
|
// Note: unicasting is performed on the only network interface
|
||||||
|
// that is configured. For now it may be not what users expect
|
||||||
|
// so additionally broadcast the message via UDP connection.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/3539.
|
||||||
|
var rerr error
|
||||||
|
n, rerr = c.unicast(p, addr)
|
||||||
|
|
||||||
|
_, uerr := c.broadcast(p, &net.UDPAddr{
|
||||||
|
IP: netutil.IPv4bcast(),
|
||||||
|
Port: dhcpv4.ClientPort,
|
||||||
|
})
|
||||||
|
|
||||||
|
return n, c.wrapErrs("writing to", uerr, rerr)
|
||||||
|
case *net.UDPAddr:
|
||||||
|
if addr.IP.Equal(net.IPv4bcast) {
|
||||||
|
// Broadcast the message for the client which supports
|
||||||
|
// it. Use the UDP connection.
|
||||||
|
return c.broadcast(p, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unicast the message to the client's IP address. Use the UDP
|
||||||
|
// connection.
|
||||||
|
return c.udpConn.WriteTo(p, addr)
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("addr has an unexpected type %T", addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFrom implements net.PacketConn for *dhcpConn.
|
||||||
|
func (c *dhcpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
|
return c.udpConn.ReadFrom(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unicast wraps respData with required frames and writes it to the peer.
|
||||||
|
func (c *dhcpConn) unicast(respData []byte, peer *dhcpUnicastAddr) (n int, err error) {
|
||||||
|
var data []byte
|
||||||
|
data, err = c.buildEtherPkt(respData, peer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.rawConn.WriteTo(data, &peer.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements net.PacketConn for *dhcpConn.
|
||||||
|
func (c *dhcpConn) Close() (err error) {
|
||||||
|
rerr := c.rawConn.Close()
|
||||||
|
if errors.Is(rerr, os.ErrClosed) {
|
||||||
|
// Ignore the error since the actual file is closed already.
|
||||||
|
rerr = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.wrapErrs("closing", c.udpConn.Close(), rerr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr implements net.PacketConn for *dhcpConn.
|
||||||
|
func (c *dhcpConn) LocalAddr() (a net.Addr) {
|
||||||
|
return c.udpConn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline implements net.PacketConn for *dhcpConn.
|
||||||
|
func (c *dhcpConn) SetDeadline(t time.Time) (err error) {
|
||||||
|
return c.wrapErrs("setting deadline on", c.udpConn.SetDeadline(t), c.rawConn.SetDeadline(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline implements net.PacketConn for *dhcpConn.
|
||||||
|
func (c *dhcpConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return c.wrapErrs(
|
||||||
|
"setting reading deadline on",
|
||||||
|
c.udpConn.SetReadDeadline(t),
|
||||||
|
c.rawConn.SetReadDeadline(t),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline implements net.PacketConn for *dhcpConn.
|
||||||
|
func (c *dhcpConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return c.wrapErrs(
|
||||||
|
"setting writing deadline on",
|
||||||
|
c.udpConn.SetWriteDeadline(t),
|
||||||
|
c.rawConn.SetWriteDeadline(t),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ipv4DefaultTTL is the default Time to Live value in seconds as recommended by
|
||||||
|
// RFC-1700.
|
||||||
|
//
|
||||||
|
// See https://datatracker.ietf.org/doc/html/rfc1700.
|
||||||
|
const ipv4DefaultTTL = 64
|
||||||
|
|
||||||
|
// buildEtherPkt wraps the payload with IPv4, UDP and Ethernet frames.
|
||||||
|
// Validation of the payload is a caller's responsibility.
|
||||||
|
func (c *dhcpConn) buildEtherPkt(payload []byte, peer *dhcpUnicastAddr) (pkt []byte, err error) {
|
||||||
|
udpLayer := &layers.UDP{
|
||||||
|
SrcPort: dhcpv4.ServerPort,
|
||||||
|
DstPort: dhcpv4.ClientPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv4Layer := &layers.IPv4{
|
||||||
|
Version: uint8(layers.IPProtocolIPv4),
|
||||||
|
Flags: layers.IPv4DontFragment,
|
||||||
|
TTL: ipv4DefaultTTL,
|
||||||
|
Protocol: layers.IPProtocolUDP,
|
||||||
|
SrcIP: c.srcIP,
|
||||||
|
DstIP: peer.yiaddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore the error since it's only returned for invalid network layer's
|
||||||
|
// type.
|
||||||
|
_ = udpLayer.SetNetworkLayerForChecksum(ipv4Layer)
|
||||||
|
|
||||||
|
ethLayer := &layers.Ethernet{
|
||||||
|
SrcMAC: c.srcMAC,
|
||||||
|
DstMAC: peer.HardwareAddr,
|
||||||
|
EthernetType: layers.EthernetTypeIPv4,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := gopacket.NewSerializeBuffer()
|
||||||
|
setts := gopacket.SerializeOptions{
|
||||||
|
FixLengths: true,
|
||||||
|
ComputeChecksums: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gopacket.SerializeLayers(
|
||||||
|
buf,
|
||||||
|
setts,
|
||||||
|
ethLayer,
|
||||||
|
ipv4Layer,
|
||||||
|
udpLayer,
|
||||||
|
gopacket.Payload(payload),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("serializing layers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// send writes resp for peer to conn considering the req's parameters according
|
||||||
|
// to RFC-2131.
|
||||||
|
//
|
||||||
|
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.1.
|
||||||
|
func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DHCPv4) {
|
||||||
|
switch giaddr, ciaddr, mtype := req.GatewayIPAddr, req.ClientIPAddr, resp.MessageType(); {
|
||||||
|
case giaddr != nil && !giaddr.IsUnspecified():
|
||||||
|
// Send any return messages to the server port on the BOOTP
|
||||||
|
// relay agent whose address appears in giaddr.
|
||||||
|
peer = &net.UDPAddr{
|
||||||
|
IP: giaddr,
|
||||||
|
Port: dhcpv4.ServerPort,
|
||||||
|
}
|
||||||
|
if mtype == dhcpv4.MessageTypeNak {
|
||||||
|
// Set the broadcast bit in the DHCPNAK, so that the relay agent
|
||||||
|
// broadcasts it to the client, because the client may not have
|
||||||
|
// a correct network address or subnet mask, and the client may not
|
||||||
|
// be answering ARP requests.
|
||||||
|
resp.SetBroadcast()
|
||||||
|
}
|
||||||
|
case mtype == dhcpv4.MessageTypeNak:
|
||||||
|
// Broadcast any DHCPNAK messages to 0xffffffff.
|
||||||
|
case ciaddr != nil && !ciaddr.IsUnspecified():
|
||||||
|
// Unicast DHCPOFFER and DHCPACK messages to the address in
|
||||||
|
// ciaddr.
|
||||||
|
peer = &net.UDPAddr{
|
||||||
|
IP: ciaddr,
|
||||||
|
Port: dhcpv4.ClientPort,
|
||||||
|
}
|
||||||
|
case !req.IsBroadcast() && req.ClientHWAddr != nil:
|
||||||
|
// Unicast DHCPOFFER and DHCPACK messages to the client's
|
||||||
|
// hardware address and yiaddr.
|
||||||
|
peer = &dhcpUnicastAddr{
|
||||||
|
Addr: raw.Addr{HardwareAddr: req.ClientHWAddr},
|
||||||
|
yiaddr: resp.YourIPAddr,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Go on since peer is already set to broadcast.
|
||||||
|
}
|
||||||
|
|
||||||
|
pktData := resp.ToBytes()
|
||||||
|
|
||||||
|
log.Debug("dhcpv4: sending %d bytes to %s: %s", len(pktData), peer, resp.Summary())
|
||||||
|
|
||||||
|
_, err := conn.WriteTo(pktData, peer)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
||||||
|
}
|
||||||
|
}
|
219
internal/dhcpd/conn_darwin_internal_test.go
Normal file
219
internal/dhcpd/conn_darwin_internal_test.go
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
//lint:ignore SA1019 See the TODO in go.mod.
|
||||||
|
"github.com/mdlayher/raw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDHCPConn_WriteTo_common(t *testing.T) {
|
||||||
|
respData := (&dhcpv4.DHCPv4{}).ToBytes()
|
||||||
|
udpAddr := &net.UDPAddr{
|
||||||
|
IP: net.IP{1, 2, 3, 4},
|
||||||
|
Port: dhcpv4.ClientPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("unicast_ip", func(t *testing.T) {
|
||||||
|
writeTo := func(_ []byte, addr net.Addr) (_ int, _ error) {
|
||||||
|
assert.Equal(t, udpAddr, addr)
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := &dhcpConn{udpConn: &fakePacketConn{writeTo: writeTo}}
|
||||||
|
|
||||||
|
_, err := conn.WriteTo(respData, udpAddr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unexpected_addr_type", func(t *testing.T) {
|
||||||
|
type unexpectedAddrType struct {
|
||||||
|
net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := &dhcpConn{}
|
||||||
|
n, err := conn.WriteTo(nil, &unexpectedAddrType{})
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
testutil.AssertErrorMsg(t, "addr has an unexpected type *dhcpd.unexpectedAddrType", err)
|
||||||
|
assert.Zero(t, n)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildEtherPkt(t *testing.T) {
|
||||||
|
conn := &dhcpConn{
|
||||||
|
srcMAC: net.HardwareAddr{1, 2, 3, 4, 5, 6},
|
||||||
|
srcIP: net.IP{1, 2, 3, 4},
|
||||||
|
}
|
||||||
|
peer := &dhcpUnicastAddr{
|
||||||
|
Addr: raw.Addr{HardwareAddr: net.HardwareAddr{6, 5, 4, 3, 2, 1}},
|
||||||
|
yiaddr: net.IP{4, 3, 2, 1},
|
||||||
|
}
|
||||||
|
payload := (&dhcpv4.DHCPv4{}).ToBytes()
|
||||||
|
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
pkt, err := conn.buildEtherPkt(payload, peer)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, pkt)
|
||||||
|
|
||||||
|
actualPkt := gopacket.NewPacket(pkt, layers.LayerTypeEthernet, gopacket.DecodeOptions{
|
||||||
|
NoCopy: true,
|
||||||
|
})
|
||||||
|
require.NotNil(t, actualPkt)
|
||||||
|
|
||||||
|
wantTypes := []gopacket.LayerType{
|
||||||
|
layers.LayerTypeEthernet,
|
||||||
|
layers.LayerTypeIPv4,
|
||||||
|
layers.LayerTypeUDP,
|
||||||
|
layers.LayerTypeDHCPv4,
|
||||||
|
}
|
||||||
|
actualLayers := actualPkt.Layers()
|
||||||
|
require.Len(t, actualLayers, len(wantTypes))
|
||||||
|
|
||||||
|
for i, wantType := range wantTypes {
|
||||||
|
layer := actualLayers[i]
|
||||||
|
require.NotNil(t, layer)
|
||||||
|
|
||||||
|
assert.Equal(t, wantType, layer.LayerType())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad_payload", func(t *testing.T) {
|
||||||
|
// Create an invalid DHCP packet.
|
||||||
|
invalidPayload := []byte{1, 2, 3, 4}
|
||||||
|
pkt, err := conn.buildEtherPkt(invalidPayload, peer)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, pkt)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("serializing_error", func(t *testing.T) {
|
||||||
|
// Create a peer with invalid MAC.
|
||||||
|
badPeer := &dhcpUnicastAddr{
|
||||||
|
Addr: raw.Addr{HardwareAddr: net.HardwareAddr{5, 4, 3, 2, 1}},
|
||||||
|
yiaddr: net.IP{4, 3, 2, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt, err := conn.buildEtherPkt(payload, badPeer)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
assert.Empty(t, pkt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV4Server_Send(t *testing.T) {
|
||||||
|
s := &v4Server{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultIP = net.IP{99, 99, 99, 99}
|
||||||
|
knownIP = net.IP{4, 2, 4, 2}
|
||||||
|
knownMAC = net.HardwareAddr{6, 5, 4, 3, 2, 1}
|
||||||
|
)
|
||||||
|
|
||||||
|
defaultPeer := &net.UDPAddr{
|
||||||
|
IP: defaultIP,
|
||||||
|
// Use neither client nor server port to check it actually
|
||||||
|
// changed.
|
||||||
|
Port: dhcpv4.ClientPort + dhcpv4.ServerPort,
|
||||||
|
}
|
||||||
|
defaultResp := &dhcpv4.DHCPv4{}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
want net.Addr
|
||||||
|
req *dhcpv4.DHCPv4
|
||||||
|
resp *dhcpv4.DHCPv4
|
||||||
|
name string
|
||||||
|
}{{
|
||||||
|
name: "giaddr",
|
||||||
|
req: &dhcpv4.DHCPv4{GatewayIPAddr: knownIP},
|
||||||
|
resp: defaultResp,
|
||||||
|
want: &net.UDPAddr{
|
||||||
|
IP: knownIP,
|
||||||
|
Port: dhcpv4.ServerPort,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "nak",
|
||||||
|
req: &dhcpv4.DHCPv4{},
|
||||||
|
resp: &dhcpv4.DHCPv4{
|
||||||
|
Options: dhcpv4.OptionsFromList(
|
||||||
|
dhcpv4.OptMessageType(dhcpv4.MessageTypeNak),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
want: defaultPeer,
|
||||||
|
}, {
|
||||||
|
name: "ciaddr",
|
||||||
|
req: &dhcpv4.DHCPv4{ClientIPAddr: knownIP},
|
||||||
|
resp: &dhcpv4.DHCPv4{},
|
||||||
|
want: &net.UDPAddr{
|
||||||
|
IP: knownIP,
|
||||||
|
Port: dhcpv4.ClientPort,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "chaddr",
|
||||||
|
req: &dhcpv4.DHCPv4{ClientHWAddr: knownMAC},
|
||||||
|
resp: &dhcpv4.DHCPv4{YourIPAddr: knownIP},
|
||||||
|
want: &dhcpUnicastAddr{
|
||||||
|
Addr: raw.Addr{HardwareAddr: knownMAC},
|
||||||
|
yiaddr: knownIP,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "who_are_you",
|
||||||
|
req: &dhcpv4.DHCPv4{},
|
||||||
|
resp: &dhcpv4.DHCPv4{},
|
||||||
|
want: defaultPeer,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
conn := &fakePacketConn{
|
||||||
|
writeTo: func(_ []byte, addr net.Addr) (_ int, _ error) {
|
||||||
|
assert.Equal(t, tc.want, addr)
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
s.send(cloneUDPAddr(defaultPeer), conn, tc.req, tc.resp)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("giaddr_nak", func(t *testing.T) {
|
||||||
|
req := &dhcpv4.DHCPv4{
|
||||||
|
GatewayIPAddr: knownIP,
|
||||||
|
}
|
||||||
|
// Ensure the request is for unicast.
|
||||||
|
req.SetUnicast()
|
||||||
|
resp := &dhcpv4.DHCPv4{
|
||||||
|
Options: dhcpv4.OptionsFromList(
|
||||||
|
dhcpv4.OptMessageType(dhcpv4.MessageTypeNak),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
want := &net.UDPAddr{
|
||||||
|
IP: req.GatewayIPAddr,
|
||||||
|
Port: dhcpv4.ServerPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := &fakePacketConn{
|
||||||
|
writeTo: func(_ []byte, addr net.Addr) (n int, err error) {
|
||||||
|
assert.Equal(t, want, addr)
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
s.send(cloneUDPAddr(defaultPeer), conn, req, resp)
|
||||||
|
assert.True(t, resp.IsBroadcast())
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build darwin || freebsd || linux || openbsd
|
//go:build freebsd || linux || openbsd
|
||||||
|
|
||||||
package dhcpd
|
package dhcpd
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/google/gopacket"
|
"github.com/google/gopacket"
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
|
@ -238,3 +239,53 @@ func (c *dhcpConn) buildEtherPkt(payload []byte, peer *dhcpUnicastAddr) (pkt []b
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send writes resp for peer to conn considering the req's parameters according
|
||||||
|
// to RFC-2131.
|
||||||
|
//
|
||||||
|
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.1.
|
||||||
|
func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DHCPv4) {
|
||||||
|
switch giaddr, ciaddr, mtype := req.GatewayIPAddr, req.ClientIPAddr, resp.MessageType(); {
|
||||||
|
case giaddr != nil && !giaddr.IsUnspecified():
|
||||||
|
// Send any return messages to the server port on the BOOTP
|
||||||
|
// relay agent whose address appears in giaddr.
|
||||||
|
peer = &net.UDPAddr{
|
||||||
|
IP: giaddr,
|
||||||
|
Port: dhcpv4.ServerPort,
|
||||||
|
}
|
||||||
|
if mtype == dhcpv4.MessageTypeNak {
|
||||||
|
// Set the broadcast bit in the DHCPNAK, so that the relay agent
|
||||||
|
// broadcasts it to the client, because the client may not have
|
||||||
|
// a correct network address or subnet mask, and the client may not
|
||||||
|
// be answering ARP requests.
|
||||||
|
resp.SetBroadcast()
|
||||||
|
}
|
||||||
|
case mtype == dhcpv4.MessageTypeNak:
|
||||||
|
// Broadcast any DHCPNAK messages to 0xffffffff.
|
||||||
|
case ciaddr != nil && !ciaddr.IsUnspecified():
|
||||||
|
// Unicast DHCPOFFER and DHCPACK messages to the address in
|
||||||
|
// ciaddr.
|
||||||
|
peer = &net.UDPAddr{
|
||||||
|
IP: ciaddr,
|
||||||
|
Port: dhcpv4.ClientPort,
|
||||||
|
}
|
||||||
|
case !req.IsBroadcast() && req.ClientHWAddr != nil:
|
||||||
|
// Unicast DHCPOFFER and DHCPACK messages to the client's
|
||||||
|
// hardware address and yiaddr.
|
||||||
|
peer = &dhcpUnicastAddr{
|
||||||
|
Addr: packet.Addr{HardwareAddr: req.ClientHWAddr},
|
||||||
|
yiaddr: resp.YourIPAddr,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Go on since peer is already set to broadcast.
|
||||||
|
}
|
||||||
|
|
||||||
|
pktData := resp.ToBytes()
|
||||||
|
|
||||||
|
log.Debug("dhcpv4: sending %d bytes to %s: %s", len(pktData), peer, resp.Summary())
|
||||||
|
|
||||||
|
_, err := conn.WriteTo(pktData, peer)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build darwin || freebsd || linux || openbsd
|
//go:build freebsd || linux || openbsd
|
||||||
|
|
||||||
package dhcpd
|
package dhcpd
|
||||||
|
|
||||||
|
@ -110,3 +110,108 @@ func TestBuildEtherPkt(t *testing.T) {
|
||||||
assert.Empty(t, pkt)
|
assert.Empty(t, pkt)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestV4Server_Send(t *testing.T) {
|
||||||
|
s := &v4Server{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultIP = net.IP{99, 99, 99, 99}
|
||||||
|
knownIP = net.IP{4, 2, 4, 2}
|
||||||
|
knownMAC = net.HardwareAddr{6, 5, 4, 3, 2, 1}
|
||||||
|
)
|
||||||
|
|
||||||
|
defaultPeer := &net.UDPAddr{
|
||||||
|
IP: defaultIP,
|
||||||
|
// Use neither client nor server port to check it actually
|
||||||
|
// changed.
|
||||||
|
Port: dhcpv4.ClientPort + dhcpv4.ServerPort,
|
||||||
|
}
|
||||||
|
defaultResp := &dhcpv4.DHCPv4{}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
want net.Addr
|
||||||
|
req *dhcpv4.DHCPv4
|
||||||
|
resp *dhcpv4.DHCPv4
|
||||||
|
name string
|
||||||
|
}{{
|
||||||
|
name: "giaddr",
|
||||||
|
req: &dhcpv4.DHCPv4{GatewayIPAddr: knownIP},
|
||||||
|
resp: defaultResp,
|
||||||
|
want: &net.UDPAddr{
|
||||||
|
IP: knownIP,
|
||||||
|
Port: dhcpv4.ServerPort,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "nak",
|
||||||
|
req: &dhcpv4.DHCPv4{},
|
||||||
|
resp: &dhcpv4.DHCPv4{
|
||||||
|
Options: dhcpv4.OptionsFromList(
|
||||||
|
dhcpv4.OptMessageType(dhcpv4.MessageTypeNak),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
want: defaultPeer,
|
||||||
|
}, {
|
||||||
|
name: "ciaddr",
|
||||||
|
req: &dhcpv4.DHCPv4{ClientIPAddr: knownIP},
|
||||||
|
resp: &dhcpv4.DHCPv4{},
|
||||||
|
want: &net.UDPAddr{
|
||||||
|
IP: knownIP,
|
||||||
|
Port: dhcpv4.ClientPort,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "chaddr",
|
||||||
|
req: &dhcpv4.DHCPv4{ClientHWAddr: knownMAC},
|
||||||
|
resp: &dhcpv4.DHCPv4{YourIPAddr: knownIP},
|
||||||
|
want: &dhcpUnicastAddr{
|
||||||
|
Addr: packet.Addr{HardwareAddr: knownMAC},
|
||||||
|
yiaddr: knownIP,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "who_are_you",
|
||||||
|
req: &dhcpv4.DHCPv4{},
|
||||||
|
resp: &dhcpv4.DHCPv4{},
|
||||||
|
want: defaultPeer,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
conn := &fakePacketConn{
|
||||||
|
writeTo: func(_ []byte, addr net.Addr) (_ int, _ error) {
|
||||||
|
assert.Equal(t, tc.want, addr)
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
s.send(cloneUDPAddr(defaultPeer), conn, tc.req, tc.resp)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("giaddr_nak", func(t *testing.T) {
|
||||||
|
req := &dhcpv4.DHCPv4{
|
||||||
|
GatewayIPAddr: knownIP,
|
||||||
|
}
|
||||||
|
// Ensure the request is for unicast.
|
||||||
|
req.SetUnicast()
|
||||||
|
resp := &dhcpv4.DHCPv4{
|
||||||
|
Options: dhcpv4.OptionsFromList(
|
||||||
|
dhcpv4.OptMessageType(dhcpv4.MessageTypeNak),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
want := &net.UDPAddr{
|
||||||
|
IP: req.GatewayIPAddr,
|
||||||
|
Port: dhcpv4.ServerPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := &fakePacketConn{
|
||||||
|
writeTo: func(_ []byte, addr net.Addr) (n int, err error) {
|
||||||
|
assert.Equal(t, want, addr)
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
s.send(cloneUDPAddr(defaultPeer), conn, req, resp)
|
||||||
|
assert.True(t, resp.IsBroadcast())
|
||||||
|
})
|
||||||
|
}
|
|
@ -5,43 +5,34 @@ package dhcpd
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/google/renameio/maybe"
|
"github.com/google/renameio/maybe"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dbFilename = "leases.db"
|
const (
|
||||||
|
// dataFilename contains saved leases.
|
||||||
|
dataFilename = "leases.json"
|
||||||
|
|
||||||
type leaseJSON struct {
|
// dataVersion is the current version of the stored DHCP leases structure.
|
||||||
HWAddr []byte `json:"mac"`
|
dataVersion = 1
|
||||||
IP []byte `json:"ip"`
|
)
|
||||||
Hostname string `json:"host"`
|
|
||||||
Expiry int64 `json:"exp"`
|
// dataLeases is the structure of the stored DHCP leases.
|
||||||
|
type dataLeases struct {
|
||||||
|
// Version is the current version of the structure.
|
||||||
|
Version int `json:"version"`
|
||||||
|
|
||||||
|
// Leases is the list containing stored DHCP leases.
|
||||||
|
Leases []*Lease `json:"leases"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeIP(ip net.IP) net.IP {
|
// dbLoad loads stored leases.
|
||||||
ip4 := ip.To4()
|
|
||||||
if ip4 != nil {
|
|
||||||
return ip4
|
|
||||||
}
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load lease table from DB
|
|
||||||
//
|
|
||||||
// TODO(s.chzhen): Decrease complexity.
|
|
||||||
func (s *server) dbLoad() (err error) {
|
func (s *server) dbLoad() (err error) {
|
||||||
dynLeases := []*Lease{}
|
data, err := os.ReadFile(s.conf.dbFilePath)
|
||||||
staticLeases := []*Lease{}
|
|
||||||
v6StaticLeases := []*Lease{}
|
|
||||||
v6DynLeases := []*Lease{}
|
|
||||||
|
|
||||||
data, err := os.ReadFile(s.conf.DBFilePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
return fmt.Errorf("reading db: %w", err)
|
return fmt.Errorf("reading db: %w", err)
|
||||||
|
@ -50,52 +41,30 @@ func (s *server) dbLoad() (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := []leaseJSON{}
|
dl := &dataLeases{}
|
||||||
err = json.Unmarshal(data, &obj)
|
err = json.Unmarshal(data, dl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("decoding db: %w", err)
|
return fmt.Errorf("decoding db: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
numLeases := len(obj)
|
leases := dl.Leases
|
||||||
for i := range obj {
|
|
||||||
obj[i].IP = normalizeIP(obj[i].IP)
|
|
||||||
|
|
||||||
ip, ok := netip.AddrFromSlice(obj[i].IP)
|
leases4 := []*Lease{}
|
||||||
if !ok {
|
leases6 := []*Lease{}
|
||||||
log.Info("dhcp: invalid IP: %s", obj[i].IP)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lease := Lease{
|
for _, l := range leases {
|
||||||
HWAddr: obj[i].HWAddr,
|
if l.IP.Is4() {
|
||||||
IP: ip,
|
leases4 = append(leases4, l)
|
||||||
Hostname: obj[i].Hostname,
|
|
||||||
Expiry: time.Unix(obj[i].Expiry, 0),
|
|
||||||
IsStatic: obj[i].Expiry == leaseExpireStatic,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(obj[i].IP) == 16 {
|
|
||||||
if lease.IsStatic {
|
|
||||||
v6StaticLeases = append(v6StaticLeases, &lease)
|
|
||||||
} else {
|
|
||||||
v6DynLeases = append(v6DynLeases, &lease)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if lease.IsStatic {
|
leases6 = append(leases6, l)
|
||||||
staticLeases = append(staticLeases, &lease)
|
|
||||||
} else {
|
|
||||||
dynLeases = append(dynLeases, &lease)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
leases4 := normalizeLeases(staticLeases, dynLeases)
|
|
||||||
err = s.srv4.ResetLeases(leases4)
|
err = s.srv4.ResetLeases(leases4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("resetting dhcpv4 leases: %w", err)
|
return fmt.Errorf("resetting dhcpv4 leases: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
leases6 := normalizeLeases(v6StaticLeases, v6DynLeases)
|
|
||||||
if s.srv6 != nil {
|
if s.srv6 != nil {
|
||||||
err = s.srv6.ResetLeases(leases6)
|
err = s.srv6.ResetLeases(leases6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -104,90 +73,54 @@ func (s *server) dbLoad() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("dhcp: loaded leases v4:%d v6:%d total-read:%d from DB",
|
log.Info("dhcp: loaded leases v4:%d v6:%d total-read:%d from DB",
|
||||||
len(leases4), len(leases6), numLeases)
|
len(leases4), len(leases6), len(leases))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip duplicate leases
|
// dbStore stores DHCP leases.
|
||||||
// Static leases have a priority over dynamic leases
|
|
||||||
func normalizeLeases(staticLeases, dynLeases []*Lease) []*Lease {
|
|
||||||
leases := []*Lease{}
|
|
||||||
index := map[string]int{}
|
|
||||||
|
|
||||||
for i, lease := range staticLeases {
|
|
||||||
_, ok := index[lease.HWAddr.String()]
|
|
||||||
if ok {
|
|
||||||
continue // skip the lease with the same HW address
|
|
||||||
}
|
|
||||||
index[lease.HWAddr.String()] = i
|
|
||||||
leases = append(leases, lease)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, lease := range dynLeases {
|
|
||||||
_, ok := index[lease.HWAddr.String()]
|
|
||||||
if ok {
|
|
||||||
continue // skip the lease with the same HW address
|
|
||||||
}
|
|
||||||
index[lease.HWAddr.String()] = i
|
|
||||||
leases = append(leases, lease)
|
|
||||||
}
|
|
||||||
|
|
||||||
return leases
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store lease table in DB
|
|
||||||
func (s *server) dbStore() (err error) {
|
func (s *server) dbStore() (err error) {
|
||||||
// Use an empty slice here as opposed to nil so that it doesn't write
|
// Use an empty slice here as opposed to nil so that it doesn't write
|
||||||
// "null" into the database file if leases are empty.
|
// "null" into the database file if leases are empty.
|
||||||
leases := []leaseJSON{}
|
leases := []*Lease{}
|
||||||
|
|
||||||
leases4 := s.srv4.getLeasesRef()
|
leases4 := s.srv4.getLeasesRef()
|
||||||
for _, l := range leases4 {
|
leases = append(leases, leases4...)
|
||||||
if l.Expiry.Unix() == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lease := leaseJSON{
|
|
||||||
HWAddr: l.HWAddr,
|
|
||||||
IP: l.IP.AsSlice(),
|
|
||||||
Hostname: l.Hostname,
|
|
||||||
Expiry: l.Expiry.Unix(),
|
|
||||||
}
|
|
||||||
|
|
||||||
leases = append(leases, lease)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.srv6 != nil {
|
if s.srv6 != nil {
|
||||||
leases6 := s.srv6.getLeasesRef()
|
leases6 := s.srv6.getLeasesRef()
|
||||||
for _, l := range leases6 {
|
leases = append(leases, leases6...)
|
||||||
if l.Expiry.Unix() == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lease := leaseJSON{
|
|
||||||
HWAddr: l.HWAddr,
|
|
||||||
IP: l.IP.AsSlice(),
|
|
||||||
Hostname: l.Hostname,
|
|
||||||
Expiry: l.Expiry.Unix(),
|
|
||||||
}
|
|
||||||
|
|
||||||
leases = append(leases, lease)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var data []byte
|
return writeDB(s.conf.dbFilePath, leases)
|
||||||
data, err = json.Marshal(leases)
|
}
|
||||||
|
|
||||||
|
// writeDB writes leases to file at path.
|
||||||
|
func writeDB(path string, leases []*Lease) (err error) {
|
||||||
|
defer func() { err = errors.Annotate(err, "writing db: %w") }()
|
||||||
|
|
||||||
|
slices.SortFunc(leases, func(a, b *Lease) bool {
|
||||||
|
return a.Hostname < b.Hostname
|
||||||
|
})
|
||||||
|
|
||||||
|
dl := &dataLeases{
|
||||||
|
Version: dataVersion,
|
||||||
|
Leases: leases,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := json.Marshal(dl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("encoding db: %w", err)
|
// Don't wrap the error since it's informative enough as is.
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = maybe.WriteFile(s.conf.DBFilePath, data, 0o644)
|
err = maybe.WriteFile(path, buf, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("writing db: %w", err)
|
// Don't wrap the error since it's informative enough as is.
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("dhcp: stored %d leases in db", len(leases))
|
log.Info("dhcp: stored %d leases in %q", len(leases), path)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// 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
|
|
||||||
|
|
||||||
// DefaultDHCPLeaseTTL is the default time-to-live for leases.
|
// DefaultDHCPLeaseTTL is the default time-to-live for leases.
|
||||||
DefaultDHCPLeaseTTL = uint32(timeutil.Day / time.Second)
|
DefaultDHCPLeaseTTL = uint32(timeutil.Day / time.Second)
|
||||||
|
|
||||||
|
@ -35,10 +28,10 @@ const (
|
||||||
defaultBackoff time.Duration = 500 * time.Millisecond
|
defaultBackoff time.Duration = 500 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
// Lease contains the necessary information about a DHCP lease
|
// Lease contains the necessary information about a DHCP lease. It's used in
|
||||||
|
// various places. So don't change it without good reason.
|
||||||
type Lease struct {
|
type Lease struct {
|
||||||
// Expiry is the expiration time of the lease. The unix timestamp value
|
// Expiry is the expiration time of the lease.
|
||||||
// of 1 means that this is a static lease.
|
|
||||||
Expiry time.Time `json:"expires"`
|
Expiry time.Time `json:"expires"`
|
||||||
|
|
||||||
// Hostname of the client.
|
// Hostname of the client.
|
||||||
|
@ -238,7 +231,7 @@ func Create(conf *ServerConfig) (s *server, err error) {
|
||||||
|
|
||||||
LocalDomainName: conf.LocalDomainName,
|
LocalDomainName: conf.LocalDomainName,
|
||||||
|
|
||||||
DBFilePath: filepath.Join(conf.WorkDir, dbFilename),
|
dbFilePath: filepath.Join(conf.DataDir, dataFilename),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,6 +272,13 @@ func Create(conf *ServerConfig) (s *server, err error) {
|
||||||
return nil, fmt.Errorf("neither dhcpv4 nor dhcpv6 srv is configured")
|
return nil, fmt.Errorf("neither dhcpv4 nor dhcpv6 srv is configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migrate leases db if needed.
|
||||||
|
err = migrateDB(conf)
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error since it's informative enough as is.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Don't delay database loading until the DHCP server is started,
|
// Don't delay database loading until the DHCP server is started,
|
||||||
// because we need static leases functionality available beforehand.
|
// because we need static leases functionality available beforehand.
|
||||||
err = s.dbLoad()
|
err = s.dbLoad()
|
||||||
|
|
|
@ -5,7 +5,7 @@ package dhcpd
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ func TestDB(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
s := server{
|
s := server{
|
||||||
conf: &ServerConfig{
|
conf: &ServerConfig{
|
||||||
DBFilePath: dbFilename,
|
dbFilePath: filepath.Join(t.TempDir(), dataFilename),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,8 +67,6 @@ func TestDB(t *testing.T) {
|
||||||
err = s.dbStore()
|
err = s.dbStore()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
testutil.CleanupAndRequireSuccess(t, func() (err error) { return os.Remove(dbFilename) })
|
|
||||||
|
|
||||||
err = s.srv4.ResetLeases(nil)
|
err = s.srv4.ResetLeases(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -78,36 +76,13 @@ func TestDB(t *testing.T) {
|
||||||
ll := s.srv4.GetLeases(LeasesAll)
|
ll := s.srv4.GetLeases(LeasesAll)
|
||||||
require.Len(t, ll, len(leases))
|
require.Len(t, ll, len(leases))
|
||||||
|
|
||||||
assert.Equal(t, leases[1].HWAddr, ll[0].HWAddr)
|
assert.Equal(t, leases[0].HWAddr, ll[0].HWAddr)
|
||||||
assert.Equal(t, leases[1].IP, ll[0].IP)
|
assert.Equal(t, leases[0].IP, ll[0].IP)
|
||||||
assert.True(t, ll[0].IsStatic)
|
assert.Equal(t, leases[0].Expiry.Unix(), ll[0].Expiry.Unix())
|
||||||
|
|
||||||
assert.Equal(t, leases[0].HWAddr, ll[1].HWAddr)
|
assert.Equal(t, leases[1].HWAddr, ll[1].HWAddr)
|
||||||
assert.Equal(t, leases[0].IP, ll[1].IP)
|
assert.Equal(t, leases[1].IP, ll[1].IP)
|
||||||
assert.Equal(t, leases[0].Expiry.Unix(), ll[1].Expiry.Unix())
|
assert.True(t, ll[1].IsStatic)
|
||||||
}
|
|
||||||
|
|
||||||
func TestNormalizeLeases(t *testing.T) {
|
|
||||||
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: netip.MustParseAddr("0.2.3.4"),
|
|
||||||
}, {
|
|
||||||
HWAddr: net.HardwareAddr{2, 2, 3, 4},
|
|
||||||
}}
|
|
||||||
|
|
||||||
leases := normalizeLeases(staticLeases, dynLeases)
|
|
||||||
require.Len(t, leases, 3)
|
|
||||||
|
|
||||||
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 TestV4Server_badRange(t *testing.T) {
|
func TestV4Server_badRange(t *testing.T) {
|
||||||
|
|
|
@ -639,7 +639,7 @@ func (s *server) handleReset(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Remove(s.conf.DBFilePath)
|
err = os.Remove(s.conf.dbFilePath)
|
||||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
log.Error("dhcp: removing db: %s", err)
|
log.Error("dhcp: removing db: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -651,8 +651,8 @@ func (s *server) handleReset(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
LocalDomainName: s.conf.LocalDomainName,
|
LocalDomainName: s.conf.LocalDomainName,
|
||||||
|
|
||||||
WorkDir: s.conf.WorkDir,
|
DataDir: s.conf.DataDir,
|
||||||
DBFilePath: s.conf.DBFilePath,
|
dbFilePath: s.conf.dbFilePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
v4conf := &V4ServerConf{
|
v4conf := &V4ServerConf{
|
||||||
|
|
|
@ -31,8 +31,7 @@ func TestServer_handleDHCPStatus(t *testing.T) {
|
||||||
s, err := Create(&ServerConfig{
|
s, err := Create(&ServerConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Conf4: *defaultV4ServerConf(),
|
Conf4: *defaultV4ServerConf(),
|
||||||
WorkDir: t.TempDir(),
|
DataDir: t.TempDir(),
|
||||||
DBFilePath: dbFilename,
|
|
||||||
ConfigModified: func() {},
|
ConfigModified: func() {},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
106
internal/dhcpd/migrate.go
Normal file
106
internal/dhcpd/migrate.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// leaseExpireStatic is used to define the Expiry field for static
|
||||||
|
// leases.
|
||||||
|
//
|
||||||
|
// Deprecated: Remove it when migration of DHCP leases will be not needed.
|
||||||
|
leaseExpireStatic = 1
|
||||||
|
|
||||||
|
// dbFilename contains saved leases.
|
||||||
|
//
|
||||||
|
// Deprecated: Use dataFilename.
|
||||||
|
dbFilename = "leases.db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// leaseJSON is the structure of stored lease.
|
||||||
|
//
|
||||||
|
// Deprecated: Use [Lease].
|
||||||
|
type leaseJSON struct {
|
||||||
|
HWAddr []byte `json:"mac"`
|
||||||
|
IP []byte `json:"ip"`
|
||||||
|
Hostname string `json:"host"`
|
||||||
|
Expiry int64 `json:"exp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeIP(ip net.IP) net.IP {
|
||||||
|
ip4 := ip.To4()
|
||||||
|
if ip4 != nil {
|
||||||
|
return ip4
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
// migrateDB migrates stored leases if necessary.
|
||||||
|
func migrateDB(conf *ServerConfig) (err error) {
|
||||||
|
defer func() { err = errors.Annotate(err, "migrating db: %w") }()
|
||||||
|
|
||||||
|
oldLeasesPath := filepath.Join(conf.WorkDir, dbFilename)
|
||||||
|
dataDirPath := filepath.Join(conf.DataDir, dataFilename)
|
||||||
|
|
||||||
|
file, err := os.Open(oldLeasesPath)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
// Nothing to migrate.
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
// Don't wrap the error since it's informative enough as is.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ljs := []leaseJSON{}
|
||||||
|
err = json.NewDecoder(file).Decode(&ljs)
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error since it's informative enough as is.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error since it's informative enough as is.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
leases := []*Lease{}
|
||||||
|
|
||||||
|
for _, lj := range ljs {
|
||||||
|
lj.IP = normalizeIP(lj.IP)
|
||||||
|
|
||||||
|
ip, ok := netip.AddrFromSlice(lj.IP)
|
||||||
|
if !ok {
|
||||||
|
log.Info("dhcp: invalid IP: %s", lj.IP)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lease := &Lease{
|
||||||
|
Expiry: time.Unix(lj.Expiry, 0),
|
||||||
|
Hostname: lj.Hostname,
|
||||||
|
HWAddr: lj.HWAddr,
|
||||||
|
IP: ip,
|
||||||
|
IsStatic: lj.Expiry == leaseExpireStatic,
|
||||||
|
}
|
||||||
|
|
||||||
|
leases = append(leases, lease)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeDB(dataDirPath, leases)
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error since it's informative enough as is.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Remove(oldLeasesPath)
|
||||||
|
}
|
73
internal/dhcpd/migrate_internal_test.go
Normal file
73
internal/dhcpd/migrate_internal_test.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testData = `[
|
||||||
|
{"mac":"ESIzRFVm","ip":"AQIDBA==","host":"test1","exp":1},
|
||||||
|
{"mac":"ZlVEMyIR","ip":"BAMCAQ==","host":"test2","exp":1231231231}
|
||||||
|
]`
|
||||||
|
|
||||||
|
func TestMigrateDB(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
oldLeasesPath := filepath.Join(dir, dbFilename)
|
||||||
|
dataDirPath := filepath.Join(dir, dataFilename)
|
||||||
|
|
||||||
|
err := os.WriteFile(oldLeasesPath, []byte(testData), 0o644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
wantLeases := []*Lease{{
|
||||||
|
Expiry: time.Time{},
|
||||||
|
Hostname: "test1",
|
||||||
|
HWAddr: net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||||
|
IP: netip.MustParseAddr("1.2.3.4"),
|
||||||
|
IsStatic: true,
|
||||||
|
}, {
|
||||||
|
Expiry: time.Unix(1231231231, 0),
|
||||||
|
Hostname: "test2",
|
||||||
|
HWAddr: net.HardwareAddr{0x66, 0x55, 0x44, 0x33, 0x22, 0x11},
|
||||||
|
IP: netip.MustParseAddr("4.3.2.1"),
|
||||||
|
IsStatic: false,
|
||||||
|
}}
|
||||||
|
|
||||||
|
conf := &ServerConfig{
|
||||||
|
WorkDir: dir,
|
||||||
|
DataDir: dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = migrateDB(conf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = os.Stat(oldLeasesPath)
|
||||||
|
require.ErrorIs(t, err, os.ErrNotExist)
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
data, err = os.ReadFile(dataDirPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
dl := &dataLeases{}
|
||||||
|
err = json.Unmarshal(data, dl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
leases := dl.Leases
|
||||||
|
|
||||||
|
for i, wl := range wantLeases {
|
||||||
|
assert.Equal(t, wl.Hostname, leases[i].Hostname)
|
||||||
|
assert.Equal(t, wl.HWAddr, leases[i].HWAddr)
|
||||||
|
assert.Equal(t, wl.IP, leases[i].IP)
|
||||||
|
assert.Equal(t, wl.IsStatic, leases[i].IsStatic)
|
||||||
|
|
||||||
|
require.True(t, wl.Expiry.Equal(leases[i].Expiry))
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"github.com/go-ping/ping"
|
"github.com/go-ping/ping"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||||
"github.com/mdlayher/packet"
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -257,6 +256,8 @@ func (s *v4Server) rmLeaseByIndex(i int) {
|
||||||
|
|
||||||
// Remove a dynamic lease with the same properties
|
// Remove a dynamic lease with the same properties
|
||||||
// Return error if a static lease is found
|
// Return error if a static lease is found
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Refactor the code.
|
||||||
func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
|
func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
|
||||||
for i, l := range s.leases {
|
for i, l := range s.leases {
|
||||||
isStatic := l.IsStatic
|
isStatic := l.IsStatic
|
||||||
|
@ -358,7 +359,6 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
||||||
return fmt.Errorf("can't assign the gateway IP %s to the lease", gwIP)
|
return fmt.Errorf("can't assign the gateway IP %s to the lease", gwIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Expiry = time.Unix(leaseExpireStatic, 0)
|
|
||||||
l.IsStatic = true
|
l.IsStatic = true
|
||||||
|
|
||||||
err = netutil.ValidateMAC(l.HWAddr)
|
err = netutil.ValidateMAC(l.HWAddr)
|
||||||
|
@ -1132,56 +1132,6 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
||||||
s.send(peer, conn, req, resp)
|
s.send(peer, conn, req, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// send writes resp for peer to conn considering the req's parameters according
|
|
||||||
// to RFC-2131.
|
|
||||||
//
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.1.
|
|
||||||
func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DHCPv4) {
|
|
||||||
switch giaddr, ciaddr, mtype := req.GatewayIPAddr, req.ClientIPAddr, resp.MessageType(); {
|
|
||||||
case giaddr != nil && !giaddr.IsUnspecified():
|
|
||||||
// Send any return messages to the server port on the BOOTP
|
|
||||||
// relay agent whose address appears in giaddr.
|
|
||||||
peer = &net.UDPAddr{
|
|
||||||
IP: giaddr,
|
|
||||||
Port: dhcpv4.ServerPort,
|
|
||||||
}
|
|
||||||
if mtype == dhcpv4.MessageTypeNak {
|
|
||||||
// Set the broadcast bit in the DHCPNAK, so that the relay agent
|
|
||||||
// broadcasts it to the client, because the client may not have
|
|
||||||
// a correct network address or subnet mask, and the client may not
|
|
||||||
// be answering ARP requests.
|
|
||||||
resp.SetBroadcast()
|
|
||||||
}
|
|
||||||
case mtype == dhcpv4.MessageTypeNak:
|
|
||||||
// Broadcast any DHCPNAK messages to 0xffffffff.
|
|
||||||
case ciaddr != nil && !ciaddr.IsUnspecified():
|
|
||||||
// Unicast DHCPOFFER and DHCPACK messages to the address in
|
|
||||||
// ciaddr.
|
|
||||||
peer = &net.UDPAddr{
|
|
||||||
IP: ciaddr,
|
|
||||||
Port: dhcpv4.ClientPort,
|
|
||||||
}
|
|
||||||
case !req.IsBroadcast() && req.ClientHWAddr != nil:
|
|
||||||
// Unicast DHCPOFFER and DHCPACK messages to the client's
|
|
||||||
// hardware address and yiaddr.
|
|
||||||
peer = &dhcpUnicastAddr{
|
|
||||||
Addr: packet.Addr{HardwareAddr: req.ClientHWAddr},
|
|
||||||
yiaddr: resp.YourIPAddr,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Go on since peer is already set to broadcast.
|
|
||||||
}
|
|
||||||
|
|
||||||
pktData := resp.ToBytes()
|
|
||||||
|
|
||||||
log.Debug("dhcpv4: sending %d bytes to %s: %s", len(pktData), peer, resp.Summary())
|
|
||||||
|
|
||||||
_, err := conn.WriteTo(pktData, peer)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the IPv4 DHCP server.
|
// Start starts the IPv4 DHCP server.
|
||||||
func (s *v4Server) Start() (err error) {
|
func (s *v4Server) Start() (err error) {
|
||||||
defer func() { err = errors.Annotate(err, "dhcpv4: %w") }()
|
defer func() { err = errors.Annotate(err, "dhcpv4: %w") }()
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"github.com/AdguardTeam/golibs/stringutil"
|
"github.com/AdguardTeam/golibs/stringutil"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
"github.com/mdlayher/packet"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -69,7 +68,6 @@ func TestV4Server_leasing(t *testing.T) {
|
||||||
|
|
||||||
t.Run("add_static", func(t *testing.T) {
|
t.Run("add_static", func(t *testing.T) {
|
||||||
err := s.AddStaticLease(&Lease{
|
err := s.AddStaticLease(&Lease{
|
||||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
|
||||||
Hostname: staticName,
|
Hostname: staticName,
|
||||||
HWAddr: staticMAC,
|
HWAddr: staticMAC,
|
||||||
IP: staticIP,
|
IP: staticIP,
|
||||||
|
@ -79,7 +77,6 @@ func TestV4Server_leasing(t *testing.T) {
|
||||||
|
|
||||||
t.Run("same_name", func(t *testing.T) {
|
t.Run("same_name", func(t *testing.T) {
|
||||||
err = s.AddStaticLease(&Lease{
|
err = s.AddStaticLease(&Lease{
|
||||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
|
||||||
Hostname: staticName,
|
Hostname: staticName,
|
||||||
HWAddr: anotherMAC,
|
HWAddr: anotherMAC,
|
||||||
IP: anotherIP,
|
IP: anotherIP,
|
||||||
|
@ -94,7 +91,6 @@ func TestV4Server_leasing(t *testing.T) {
|
||||||
" (" + staticMAC.String() + "): static lease already exists"
|
" (" + staticMAC.String() + "): static lease already exists"
|
||||||
|
|
||||||
err = s.AddStaticLease(&Lease{
|
err = s.AddStaticLease(&Lease{
|
||||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
|
||||||
Hostname: anotherName,
|
Hostname: anotherName,
|
||||||
HWAddr: staticMAC,
|
HWAddr: staticMAC,
|
||||||
IP: anotherIP,
|
IP: anotherIP,
|
||||||
|
@ -109,7 +105,6 @@ func TestV4Server_leasing(t *testing.T) {
|
||||||
" (" + anotherMAC.String() + "): static lease already exists"
|
" (" + anotherMAC.String() + "): static lease already exists"
|
||||||
|
|
||||||
err = s.AddStaticLease(&Lease{
|
err = s.AddStaticLease(&Lease{
|
||||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
|
||||||
Hostname: anotherName,
|
Hostname: anotherName,
|
||||||
HWAddr: anotherMAC,
|
HWAddr: anotherMAC,
|
||||||
IP: staticIP,
|
IP: staticIP,
|
||||||
|
@ -771,111 +766,6 @@ func (fc *fakePacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
return fc.writeTo(p, addr)
|
return fc.writeTo(p, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV4Server_Send(t *testing.T) {
|
|
||||||
s := &v4Server{}
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultIP = net.IP{99, 99, 99, 99}
|
|
||||||
knownIP = net.IP{4, 2, 4, 2}
|
|
||||||
knownMAC = net.HardwareAddr{6, 5, 4, 3, 2, 1}
|
|
||||||
)
|
|
||||||
|
|
||||||
defaultPeer := &net.UDPAddr{
|
|
||||||
IP: defaultIP,
|
|
||||||
// Use neither client nor server port to check it actually
|
|
||||||
// changed.
|
|
||||||
Port: dhcpv4.ClientPort + dhcpv4.ServerPort,
|
|
||||||
}
|
|
||||||
defaultResp := &dhcpv4.DHCPv4{}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
want net.Addr
|
|
||||||
req *dhcpv4.DHCPv4
|
|
||||||
resp *dhcpv4.DHCPv4
|
|
||||||
name string
|
|
||||||
}{{
|
|
||||||
name: "giaddr",
|
|
||||||
req: &dhcpv4.DHCPv4{GatewayIPAddr: knownIP},
|
|
||||||
resp: defaultResp,
|
|
||||||
want: &net.UDPAddr{
|
|
||||||
IP: knownIP,
|
|
||||||
Port: dhcpv4.ServerPort,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
name: "nak",
|
|
||||||
req: &dhcpv4.DHCPv4{},
|
|
||||||
resp: &dhcpv4.DHCPv4{
|
|
||||||
Options: dhcpv4.OptionsFromList(
|
|
||||||
dhcpv4.OptMessageType(dhcpv4.MessageTypeNak),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
want: defaultPeer,
|
|
||||||
}, {
|
|
||||||
name: "ciaddr",
|
|
||||||
req: &dhcpv4.DHCPv4{ClientIPAddr: knownIP},
|
|
||||||
resp: &dhcpv4.DHCPv4{},
|
|
||||||
want: &net.UDPAddr{
|
|
||||||
IP: knownIP,
|
|
||||||
Port: dhcpv4.ClientPort,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
name: "chaddr",
|
|
||||||
req: &dhcpv4.DHCPv4{ClientHWAddr: knownMAC},
|
|
||||||
resp: &dhcpv4.DHCPv4{YourIPAddr: knownIP},
|
|
||||||
want: &dhcpUnicastAddr{
|
|
||||||
Addr: packet.Addr{HardwareAddr: knownMAC},
|
|
||||||
yiaddr: knownIP,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
name: "who_are_you",
|
|
||||||
req: &dhcpv4.DHCPv4{},
|
|
||||||
resp: &dhcpv4.DHCPv4{},
|
|
||||||
want: defaultPeer,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
conn := &fakePacketConn{
|
|
||||||
writeTo: func(_ []byte, addr net.Addr) (_ int, _ error) {
|
|
||||||
assert.Equal(t, tc.want, addr)
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
s.send(cloneUDPAddr(defaultPeer), conn, tc.req, tc.resp)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("giaddr_nak", func(t *testing.T) {
|
|
||||||
req := &dhcpv4.DHCPv4{
|
|
||||||
GatewayIPAddr: knownIP,
|
|
||||||
}
|
|
||||||
// Ensure the request is for unicast.
|
|
||||||
req.SetUnicast()
|
|
||||||
resp := &dhcpv4.DHCPv4{
|
|
||||||
Options: dhcpv4.OptionsFromList(
|
|
||||||
dhcpv4.OptMessageType(dhcpv4.MessageTypeNak),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
want := &net.UDPAddr{
|
|
||||||
IP: req.GatewayIPAddr,
|
|
||||||
Port: dhcpv4.ServerPort,
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &fakePacketConn{
|
|
||||||
writeTo: func(_ []byte, addr net.Addr) (n int, err error) {
|
|
||||||
assert.Equal(t, want, addr)
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
s.send(cloneUDPAddr(defaultPeer), conn, req, resp)
|
|
||||||
assert.True(t, resp.IsBroadcast())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestV4Server_FindMACbyIP(t *testing.T) {
|
func TestV4Server_FindMACbyIP(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
staticName = "static-client"
|
staticName = "static-client"
|
||||||
|
@ -890,7 +780,6 @@ func TestV4Server_FindMACbyIP(t *testing.T) {
|
||||||
|
|
||||||
s := &v4Server{
|
s := &v4Server{
|
||||||
leases: []*Lease{{
|
leases: []*Lease{{
|
||||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
|
||||||
Hostname: staticName,
|
Hostname: staticName,
|
||||||
HWAddr: staticMAC,
|
HWAddr: staticMAC,
|
||||||
IP: staticIP,
|
IP: staticIP,
|
||||||
|
|
|
@ -66,8 +66,7 @@ func (s *v6Server) ResetLeases(leases []*Lease) (err error) {
|
||||||
s.leases = nil
|
s.leases = nil
|
||||||
for _, l := range leases {
|
for _, l := range leases {
|
||||||
ip := net.IP(l.IP.AsSlice())
|
ip := net.IP(l.IP.AsSlice())
|
||||||
if l.Expiry.Unix() != leaseExpireStatic &&
|
if !l.IsStatic && !ip6InRange(s.conf.ipStart, ip) {
|
||||||
!ip6InRange(s.conf.ipStart, ip) {
|
|
||||||
|
|
||||||
log.Debug("dhcpv6: skipping a lease with IP %v: not within current IP range", l.IP)
|
log.Debug("dhcpv6: skipping a lease with IP %v: not within current IP range", l.IP)
|
||||||
|
|
||||||
|
@ -89,7 +88,7 @@ func (s *v6Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) {
|
||||||
leases = []*Lease{}
|
leases = []*Lease{}
|
||||||
s.leasesLock.Lock()
|
s.leasesLock.Lock()
|
||||||
for _, l := range s.leases {
|
for _, l := range s.leases {
|
||||||
if l.Expiry.Unix() == leaseExpireStatic {
|
if l.IsStatic {
|
||||||
if (flags & LeasesStatic) != 0 {
|
if (flags & LeasesStatic) != 0 {
|
||||||
leases = append(leases, l.Clone())
|
leases = append(leases, l.Clone())
|
||||||
}
|
}
|
||||||
|
@ -150,7 +149,7 @@ func (s *v6Server) rmDynamicLease(lease *Lease) (err error) {
|
||||||
l := s.leases[i]
|
l := s.leases[i]
|
||||||
|
|
||||||
if bytes.Equal(l.HWAddr, lease.HWAddr) {
|
if bytes.Equal(l.HWAddr, lease.HWAddr) {
|
||||||
if l.Expiry.Unix() == leaseExpireStatic {
|
if l.IsStatic {
|
||||||
return fmt.Errorf("static lease already exists")
|
return fmt.Errorf("static lease already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +162,7 @@ func (s *v6Server) rmDynamicLease(lease *Lease) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.IP == lease.IP {
|
if l.IP == lease.IP {
|
||||||
if l.Expiry.Unix() == leaseExpireStatic {
|
if l.IsStatic {
|
||||||
return fmt.Errorf("static lease already exists")
|
return fmt.Errorf("static lease already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +186,7 @@ func (s *v6Server) AddStaticLease(l *Lease) (err error) {
|
||||||
return fmt.Errorf("validating lease: %w", err)
|
return fmt.Errorf("validating lease: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Expiry = time.Unix(leaseExpireStatic, 0)
|
l.IsStatic = true
|
||||||
|
|
||||||
s.leasesLock.Lock()
|
s.leasesLock.Lock()
|
||||||
err = s.rmDynamicLease(l)
|
err = s.rmDynamicLease(l)
|
||||||
|
@ -274,8 +273,7 @@ func (s *v6Server) findLease(mac net.HardwareAddr) *Lease {
|
||||||
func (s *v6Server) findExpiredLease() int {
|
func (s *v6Server) findExpiredLease() int {
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
for i, lease := range s.leases {
|
for i, lease := range s.leases {
|
||||||
if lease.Expiry.Unix() != leaseExpireStatic &&
|
if !lease.IsStatic && lease.Expiry.Unix() <= now {
|
||||||
lease.Expiry.Unix() <= now {
|
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -421,7 +419,7 @@ func (s *v6Server) commitLease(msg *dhcpv6.Message, lease *Lease) time.Duration
|
||||||
dhcpv6.MessageTypeRenew,
|
dhcpv6.MessageTypeRenew,
|
||||||
dhcpv6.MessageTypeRebind:
|
dhcpv6.MessageTypeRebind:
|
||||||
|
|
||||||
if lease.Expiry.Unix() != leaseExpireStatic {
|
if !lease.IsStatic {
|
||||||
s.commitDynamicLease(lease)
|
s.commitDynamicLease(lease)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ func TestV6_AddRemove_static(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, l.IP, ls[0].IP)
|
assert.Equal(t, l.IP, ls[0].IP)
|
||||||
assert.Equal(t, l.HWAddr, ls[0].HWAddr)
|
assert.Equal(t, l.HWAddr, ls[0].HWAddr)
|
||||||
assert.EqualValues(t, leaseExpireStatic, ls[0].Expiry.Unix())
|
assert.True(t, ls[0].IsStatic)
|
||||||
|
|
||||||
// Try to remove non-existent static lease.
|
// Try to remove non-existent static lease.
|
||||||
err = s.RemoveStaticLease(&Lease{
|
err = s.RemoveStaticLease(&Lease{
|
||||||
|
@ -103,7 +103,7 @@ func TestV6_AddReplace(t *testing.T) {
|
||||||
for i, l := range ls {
|
for i, l := range ls {
|
||||||
assert.Equal(t, stLeases[i].IP, l.IP)
|
assert.Equal(t, stLeases[i].IP, l.IP)
|
||||||
assert.Equal(t, stLeases[i].HWAddr, l.HWAddr)
|
assert.Equal(t, stLeases[i].HWAddr, l.HWAddr)
|
||||||
assert.EqualValues(t, leaseExpireStatic, l.Expiry.Unix())
|
assert.True(t, l.IsStatic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,7 +327,6 @@ func TestV6_FindMACbyIP(t *testing.T) {
|
||||||
|
|
||||||
s := &v6Server{
|
s := &v6Server{
|
||||||
leases: []*Lease{{
|
leases: []*Lease{{
|
||||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
|
||||||
Hostname: staticName,
|
Hostname: staticName,
|
||||||
HWAddr: staticMAC,
|
HWAddr: staticMAC,
|
||||||
IP: staticIP,
|
IP: staticIP,
|
||||||
|
@ -341,7 +340,6 @@ func TestV6_FindMACbyIP(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.leases = []*Lease{{
|
s.leases = []*Lease{{
|
||||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
|
||||||
Hostname: staticName,
|
Hostname: staticName,
|
||||||
HWAddr: staticMAC,
|
HWAddr: staticMAC,
|
||||||
IP: staticIP,
|
IP: staticIP,
|
||||||
|
|
|
@ -1438,6 +1438,8 @@ var blockedServices = []blockedService{{
|
||||||
"||mindly.social^",
|
"||mindly.social^",
|
||||||
"||mstdn.ca^",
|
"||mstdn.ca^",
|
||||||
"||mstdn.jp^",
|
"||mstdn.jp^",
|
||||||
|
"||mstdn.party^",
|
||||||
|
"||mstdn.plus^",
|
||||||
"||mstdn.social^",
|
"||mstdn.social^",
|
||||||
"||muenchen.social^",
|
"||muenchen.social^",
|
||||||
"||muenster.im^",
|
"||muenster.im^",
|
||||||
|
@ -1447,7 +1449,6 @@ var blockedServices = []blockedService{{
|
||||||
"||nrw.social^",
|
"||nrw.social^",
|
||||||
"||o3o.ca^",
|
"||o3o.ca^",
|
||||||
"||ohai.social^",
|
"||ohai.social^",
|
||||||
"||pewtix.com^",
|
|
||||||
"||piaille.fr^",
|
"||piaille.fr^",
|
||||||
"||pol.social^",
|
"||pol.social^",
|
||||||
"||ravenation.club^",
|
"||ravenation.club^",
|
||||||
|
@ -1469,7 +1470,6 @@ var blockedServices = []blockedService{{
|
||||||
"||techhub.social^",
|
"||techhub.social^",
|
||||||
"||theblower.au^",
|
"||theblower.au^",
|
||||||
"||tkz.one^",
|
"||tkz.one^",
|
||||||
"||todon.eu^",
|
|
||||||
"||toot.aquilenet.fr^",
|
"||toot.aquilenet.fr^",
|
||||||
"||toot.community^",
|
"||toot.community^",
|
||||||
"||toot.funami.tech^",
|
"||toot.funami.tech^",
|
||||||
|
|
|
@ -3,14 +3,12 @@ package home
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -283,8 +281,8 @@ func TestClientsAddExisting(t *testing.T) {
|
||||||
|
|
||||||
// First, init a DHCP server with a single static lease.
|
// First, init a DHCP server with a single static lease.
|
||||||
config := &dhcpd.ServerConfig{
|
config := &dhcpd.ServerConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
DBFilePath: "leases.db",
|
DataDir: t.TempDir(),
|
||||||
Conf4: dhcpd.V4ServerConf{
|
Conf4: dhcpd.V4ServerConf{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
GatewayIP: netip.MustParseAddr("1.2.3.1"),
|
GatewayIP: netip.MustParseAddr("1.2.3.1"),
|
||||||
|
@ -296,9 +294,6 @@ func TestClientsAddExisting(t *testing.T) {
|
||||||
|
|
||||||
dhcpServer, err := dhcpd.Create(config)
|
dhcpServer, err := dhcpd.Create(config)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testutil.CleanupAndRequireSuccess(t, func() (err error) {
|
|
||||||
return os.Remove("leases.db")
|
|
||||||
})
|
|
||||||
|
|
||||||
clients.dhcpServer = dhcpServer
|
clients.dhcpServer = dhcpServer
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
)
|
)
|
||||||
|
@ -44,6 +45,9 @@ type clientJSON struct {
|
||||||
SafeSearchEnabled bool `json:"safesearch_enabled"`
|
SafeSearchEnabled bool `json:"safesearch_enabled"`
|
||||||
UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
|
UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
|
||||||
UseGlobalSettings bool `json:"use_global_settings"`
|
UseGlobalSettings bool `json:"use_global_settings"`
|
||||||
|
|
||||||
|
IgnoreQueryLog aghalg.NullBool `json:"ignore_querylog"`
|
||||||
|
IgnoreStatistics aghalg.NullBool `json:"ignore_statistics"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type runtimeClientJSON struct {
|
type runtimeClientJSON struct {
|
||||||
|
@ -90,7 +94,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
|
||||||
}
|
}
|
||||||
|
|
||||||
// jsonToClient converts JSON object to Client object.
|
// jsonToClient converts JSON object to Client object.
|
||||||
func (clients *clientsContainer) jsonToClient(cj clientJSON) (c *Client, err error) {
|
func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *Client, err error) {
|
||||||
var safeSearchConf filtering.SafeSearchConfig
|
var safeSearchConf filtering.SafeSearchConfig
|
||||||
if cj.SafeSearchConf != nil {
|
if cj.SafeSearchConf != nil {
|
||||||
safeSearchConf = *cj.SafeSearchConf
|
safeSearchConf = *cj.SafeSearchConf
|
||||||
|
@ -129,6 +133,18 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON) (c *Client, err err
|
||||||
UseOwnBlockedServices: !cj.UseGlobalBlockedServices,
|
UseOwnBlockedServices: !cj.UseGlobalBlockedServices,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cj.IgnoreQueryLog != aghalg.NBNull {
|
||||||
|
c.IgnoreQueryLog = cj.IgnoreQueryLog == aghalg.NBTrue
|
||||||
|
} else if prev != nil {
|
||||||
|
c.IgnoreQueryLog = prev.IgnoreQueryLog
|
||||||
|
}
|
||||||
|
|
||||||
|
if cj.IgnoreStatistics != aghalg.NBNull {
|
||||||
|
c.IgnoreStatistics = cj.IgnoreStatistics == aghalg.NBTrue
|
||||||
|
} else if prev != nil {
|
||||||
|
c.IgnoreStatistics = prev.IgnoreStatistics
|
||||||
|
}
|
||||||
|
|
||||||
if safeSearchConf.Enabled {
|
if safeSearchConf.Enabled {
|
||||||
err = c.setSafeSearch(
|
err = c.setSafeSearch(
|
||||||
safeSearchConf,
|
safeSearchConf,
|
||||||
|
@ -165,6 +181,9 @@ func clientToJSON(c *Client) (cj *clientJSON) {
|
||||||
BlockedServices: c.BlockedServices,
|
BlockedServices: c.BlockedServices,
|
||||||
|
|
||||||
Upstreams: c.Upstreams,
|
Upstreams: c.Upstreams,
|
||||||
|
|
||||||
|
IgnoreQueryLog: aghalg.BoolToNullBool(c.IgnoreQueryLog),
|
||||||
|
IgnoreStatistics: aghalg.BoolToNullBool(c.IgnoreStatistics),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +197,7 @@ func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := clients.jsonToClient(cj)
|
c, err := clients.jsonToClient(cj, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||||
|
|
||||||
|
@ -232,6 +251,8 @@ type updateJSON struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateClient is the handler for POST /control/clients/update HTTP API.
|
// handleUpdateClient is the handler for POST /control/clients/update HTTP API.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Accept updated parameters instead of whole structure.
|
||||||
func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *http.Request) {
|
func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *http.Request) {
|
||||||
dj := updateJSON{}
|
dj := updateJSON{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&dj)
|
err := json.NewDecoder(r.Body).Decode(&dj)
|
||||||
|
@ -247,7 +268,21 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := clients.jsonToClient(dj.Data)
|
var prev *Client
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
func() {
|
||||||
|
clients.lock.Lock()
|
||||||
|
defer clients.lock.Unlock()
|
||||||
|
|
||||||
|
prev, ok = clients.list[dj.Name]
|
||||||
|
}()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
aghhttp.Error(r, w, http.StatusBadRequest, "client not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := clients.jsonToClient(dj.Data, prev)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||||
|
|
||||||
|
|
|
@ -296,12 +296,26 @@ var config = &configuration{
|
||||||
MaxGoroutines: 300,
|
MaxGoroutines: 300,
|
||||||
},
|
},
|
||||||
DnsfilterConf: &filtering.Config{
|
DnsfilterConf: &filtering.Config{
|
||||||
SafeBrowsingCacheSize: 1 * 1024 * 1024,
|
|
||||||
SafeSearchCacheSize: 1 * 1024 * 1024,
|
|
||||||
ParentalCacheSize: 1 * 1024 * 1024,
|
|
||||||
CacheTime: 30,
|
|
||||||
FilteringEnabled: true,
|
FilteringEnabled: true,
|
||||||
FiltersUpdateIntervalHours: 24,
|
FiltersUpdateIntervalHours: 24,
|
||||||
|
|
||||||
|
ParentalEnabled: false,
|
||||||
|
SafeBrowsingEnabled: false,
|
||||||
|
|
||||||
|
SafeBrowsingCacheSize: 1 * 1024 * 1024,
|
||||||
|
SafeSearchCacheSize: 1 * 1024 * 1024,
|
||||||
|
ParentalCacheSize: 1 * 1024 * 1024,
|
||||||
|
CacheTime: 30,
|
||||||
|
|
||||||
|
SafeSearchConf: filtering.SafeSearchConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Bing: true,
|
||||||
|
DuckDuckGo: true,
|
||||||
|
Google: true,
|
||||||
|
Pixabay: true,
|
||||||
|
Yandex: true,
|
||||||
|
YouTube: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
|
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
|
||||||
UsePrivateRDNS: true,
|
UsePrivateRDNS: true,
|
||||||
|
|
|
@ -306,7 +306,9 @@ func setupConfig(opts options) (err error) {
|
||||||
return fmt.Errorf("initializing safesearch: %w", err)
|
return fmt.Errorf("initializing safesearch: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//lint:ignore SA1019 Migration is not over.
|
||||||
config.DHCP.WorkDir = Context.workDir
|
config.DHCP.WorkDir = Context.workDir
|
||||||
|
config.DHCP.DataDir = Context.getDataDir()
|
||||||
config.DHCP.HTTPRegister = httpRegister
|
config.DHCP.HTTPRegister = httpRegister
|
||||||
config.DHCP.ConfigModified = onConfigModified
|
config.DHCP.ConfigModified = onConfigModified
|
||||||
|
|
||||||
|
|
|
@ -249,13 +249,15 @@ var cmdLineOpts = []cmdLineOpt{{
|
||||||
updateNoValue: func(o options) (options, error) { o.noEtcHosts = true; return o, nil },
|
updateNoValue: func(o options) (options, error) { o.noEtcHosts = true; return o, nil },
|
||||||
effect: func(_ options, _ string) (f effect, err error) {
|
effect: func(_ options, _ string) (f effect, err error) {
|
||||||
log.Info(
|
log.Info(
|
||||||
"warning: --no-etc-hosts flag is deprecated and will be removed in the future versions",
|
"warning: --no-etc-hosts flag is deprecated " +
|
||||||
|
"and will be removed in the future versions; " +
|
||||||
|
"set clients.runtime_sources.hosts in the configuration file to false instead",
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
},
|
},
|
||||||
serialize: func(o options) (val string, ok bool) { return "", o.noEtcHosts },
|
serialize: func(o options) (val string, ok bool) { return "", o.noEtcHosts },
|
||||||
description: "Deprecated. Do not use the OS-provided hosts.",
|
description: "Deprecated: use clients.runtime_sources.hosts instead. Do not use the OS-provided hosts.",
|
||||||
longName: "no-etc-hosts",
|
longName: "no-etc-hosts",
|
||||||
shortName: "",
|
shortName: "",
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -58,11 +58,11 @@ func (e *logEntry) addResponse(resp *dns.Msg, isOrig bool) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if isOrig {
|
if isOrig {
|
||||||
e.Answer, err = resp.Pack()
|
|
||||||
err = errors.Annotate(err, "packing answer: %w")
|
|
||||||
} else {
|
|
||||||
e.OrigAnswer, err = resp.Pack()
|
e.OrigAnswer, err = resp.Pack()
|
||||||
err = errors.Annotate(err, "packing orig answer: %w")
|
err = errors.Annotate(err, "packing orig answer: %w")
|
||||||
|
} else {
|
||||||
|
e.Answer, err = resp.Pack()
|
||||||
|
err = errors.Annotate(err, "packing answer: %w")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("querylog: %s", err)
|
log.Error("querylog: %s", err)
|
||||||
|
|
|
@ -288,6 +288,10 @@ func (l *queryLog) readNextEntry(
|
||||||
// Go on and try to match anyway.
|
// Go on and try to match anyway.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.client != nil && e.client.IgnoreQueryLog {
|
||||||
|
return nil, ts, nil
|
||||||
|
}
|
||||||
|
|
||||||
ts = e.Time.UnixNano()
|
ts = e.Time.UnixNano()
|
||||||
if !params.match(e) {
|
if !params.match(e) {
|
||||||
return nil, ts, nil
|
return nil, ts, nil
|
||||||
|
|
|
@ -423,7 +423,7 @@ func (s *StatsCtx) getData(limit uint32) (StatsResp, bool) {
|
||||||
ReplacedParental: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RParental] }),
|
ReplacedParental: statsCollector(units, firstID, timeUnit, func(u *unitDB) (num uint64) { return u.NResult[RParental] }),
|
||||||
TopQueried: topsCollector(units, maxDomains, s.ignored, func(u *unitDB) (pairs []countPair) { return u.Domains }),
|
TopQueried: topsCollector(units, maxDomains, s.ignored, func(u *unitDB) (pairs []countPair) { return u.Domains }),
|
||||||
TopBlocked: topsCollector(units, maxDomains, s.ignored, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }),
|
TopBlocked: topsCollector(units, maxDomains, s.ignored, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }),
|
||||||
TopClients: topsCollector(units, maxClients, nil, func(u *unitDB) (pairs []countPair) { return u.Clients }),
|
TopClients: topsCollector(units, maxClients, nil, topClientPairs(s)),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Total counters:
|
// Total counters:
|
||||||
|
@ -460,3 +460,17 @@ func (s *StatsCtx) getData(limit uint32) (StatsResp, bool) {
|
||||||
|
|
||||||
return data, true
|
return data, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func topClientPairs(s *StatsCtx) (pg pairsGetter) {
|
||||||
|
return func(u *unitDB) (clients []countPair) {
|
||||||
|
for _, c := range u.Clients {
|
||||||
|
if c.Name != "" && !s.shouldCountClient([]string{c.Name}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
clients = append(clients, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clients
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ require (
|
||||||
github.com/kisielk/errcheck v1.6.3
|
github.com/kisielk/errcheck v1.6.3
|
||||||
github.com/kyoh86/looppointer v0.2.1
|
github.com/kyoh86/looppointer v0.2.1
|
||||||
github.com/securego/gosec/v2 v2.15.0
|
github.com/securego/gosec/v2 v2.15.0
|
||||||
golang.org/x/tools v0.7.0
|
golang.org/x/tools v0.8.0
|
||||||
golang.org/x/vuln v0.0.0-20230404205743-41aec7335792
|
golang.org/x/vuln v0.0.0-20230418010118-28ba02ac73db
|
||||||
honnef.co/go/tools v0.4.3
|
honnef.co/go/tools v0.4.3
|
||||||
mvdan.cc/gofumpt v0.4.0
|
mvdan.cc/gofumpt v0.5.0
|
||||||
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8
|
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
|
||||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||||
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||||
|
@ -21,7 +21,7 @@ github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 h1:9alfqbr
|
||||||
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
|
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
|
||||||
github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8=
|
github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8=
|
||||||
github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
|
github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kyoh86/looppointer v0.2.1 h1:Jx9fnkBj/JrIryBLMTYNTj9rvc2SrPS98Dg0w7fxdJg=
|
github.com/kyoh86/looppointer v0.2.1 h1:Jx9fnkBj/JrIryBLMTYNTj9rvc2SrPS98Dg0w7fxdJg=
|
||||||
github.com/kyoh86/looppointer v0.2.1/go.mod h1:q358WcM8cMWU+5vzqukvaZtnJi1kw/MpRHQm3xvTrjw=
|
github.com/kyoh86/looppointer v0.2.1/go.mod h1:q358WcM8cMWU+5vzqukvaZtnJi1kw/MpRHQm3xvTrjw=
|
||||||
|
@ -31,10 +31,9 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6Fx
|
||||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
|
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
|
||||||
github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI=
|
github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI=
|
||||||
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
|
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/securego/gosec/v2 v2.15.0 h1:v4Ym7FF58/jlykYmmhZ7mTm7FQvN/setNm++0fgIAtw=
|
github.com/securego/gosec/v2 v2.15.0 h1:v4Ym7FF58/jlykYmmhZ7mTm7FQvN/setNm++0fgIAtw=
|
||||||
github.com/securego/gosec/v2 v2.15.0/go.mod h1:VOjTrZOkUtSDt2QLSJmQBMWnvwiQPEjg0l+5juIqGk8=
|
github.com/securego/gosec/v2 v2.15.0/go.mod h1:VOjTrZOkUtSDt2QLSJmQBMWnvwiQPEjg0l+5juIqGk8=
|
||||||
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
@ -63,7 +62,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -91,10 +90,10 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||||
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
||||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
|
||||||
golang.org/x/vuln v0.0.0-20230404205743-41aec7335792 h1:NybXXIgk5dslpSHRStwyfI74htFvi9Cyk3mCr9ubE2I=
|
golang.org/x/vuln v0.0.0-20230418010118-28ba02ac73db h1:tLxfII6jPR3mfwEMkyOakawu+Lldo9hIA7vliXnDZYg=
|
||||||
golang.org/x/vuln v0.0.0-20230404205743-41aec7335792/go.mod h1:8gQW8OCBfaUiPaWAPDQf/9V1w+ymmmB/05SwB/EXZNs=
|
golang.org/x/vuln v0.0.0-20230418010118-28ba02ac73db/go.mod h1:64LpnL2PuSMzFYeCmJjYiRbroOUG9aCZYznINnF5PHE=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -104,7 +103,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw=
|
honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw=
|
||||||
honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA=
|
honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA=
|
||||||
mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM=
|
mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E=
|
||||||
mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ=
|
mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js=
|
||||||
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 h1:VuJo4Mt0EVPychre4fNlDWDuE5AjXtPJpRUWqZDQhaI=
|
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 h1:VuJo4Mt0EVPychre4fNlDWDuE5AjXtPJpRUWqZDQhaI=
|
||||||
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8/go.mod h1:Oh/d7dEtzsNHGOq1Cdv8aMm3KdKhVvPbRQcM8WFpBR8=
|
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8/go.mod h1:Oh/d7dEtzsNHGOq1Cdv8aMm3KdKhVvPbRQcM8WFpBR8=
|
||||||
|
|
|
@ -4,6 +4,18 @@
|
||||||
|
|
||||||
## v0.108.0: API changes
|
## v0.108.0: API changes
|
||||||
|
|
||||||
|
## v0.107.29: API changes
|
||||||
|
|
||||||
|
### `GET /control/clients` And `GET /control/clients/find`
|
||||||
|
* The new optional fields `"ignore_querylog"` and `"ignore_statistics"` are set
|
||||||
|
if AdGuard Home excludes client activity from query log or statistics.
|
||||||
|
|
||||||
|
### `POST /control/clients/add` And `POST /control/clients/update`
|
||||||
|
* The new optional fields `"ignore_querylog"` and `"ignore_statistics"` make
|
||||||
|
AdGuard Home exclude client activity from query log or statistics. If not
|
||||||
|
set AdGuard Home will use default value (false). It can be changed in the
|
||||||
|
future versions.
|
||||||
|
|
||||||
## v0.107.27: API changes
|
## v0.107.27: API changes
|
||||||
|
|
||||||
### The new optional fields `"edns_cs_use_custom"` and `"edns_cs_custom_ip"` in `DNSConfig`
|
### The new optional fields `"edns_cs_use_custom"` and `"edns_cs_custom_ip"` in `DNSConfig`
|
||||||
|
|
|
@ -2494,6 +2494,26 @@
|
||||||
'items':
|
'items':
|
||||||
'type': 'string'
|
'type': 'string'
|
||||||
'type': 'array'
|
'type': 'array'
|
||||||
|
'ignore_querylog':
|
||||||
|
'description': |
|
||||||
|
NOTE: If `ignore_querylog` is not set in HTTP API `GET /clients/add`
|
||||||
|
request then default value (false) will be used.
|
||||||
|
|
||||||
|
If `ignore_querylog` is not set in HTTP API `GET /clients/update`
|
||||||
|
request then the existing value will not be changed.
|
||||||
|
|
||||||
|
This behaviour can be changed in the future versions.
|
||||||
|
'type': 'boolean'
|
||||||
|
'ignore_statistics':
|
||||||
|
'description': |
|
||||||
|
NOTE: If `ignore_statistics` is not set in HTTP API `GET
|
||||||
|
/clients/add` request then default value (false) will be used.
|
||||||
|
|
||||||
|
If `ignore_statistics` is not set in HTTP API `GET /clients/update`
|
||||||
|
request then the existing value will not be changed.
|
||||||
|
|
||||||
|
This behaviour can be changed in the future versions.
|
||||||
|
'type': 'boolean'
|
||||||
'ClientAuto':
|
'ClientAuto':
|
||||||
'type': 'object'
|
'type': 'object'
|
||||||
'description': 'Auto-Client information'
|
'description': 'Auto-Client information'
|
||||||
|
@ -2547,6 +2567,8 @@
|
||||||
'whois_info': {}
|
'whois_info': {}
|
||||||
'disallowed': false
|
'disallowed': false
|
||||||
'disallowed_rule': ''
|
'disallowed_rule': ''
|
||||||
|
'ignore_querylog': false
|
||||||
|
'ignore_statistics': false
|
||||||
- '1.2.3.4':
|
- '1.2.3.4':
|
||||||
'name': 'Client 1-2-3-4'
|
'name': 'Client 1-2-3-4'
|
||||||
'ids': ['1.2.3.4']
|
'ids': ['1.2.3.4']
|
||||||
|
@ -2562,6 +2584,8 @@
|
||||||
'whois_info': {}
|
'whois_info': {}
|
||||||
'disallowed': false
|
'disallowed': false
|
||||||
'disallowed_rule': ''
|
'disallowed_rule': ''
|
||||||
|
'ignore_querylog': false
|
||||||
|
'ignore_statistics': false
|
||||||
'AccessListResponse':
|
'AccessListResponse':
|
||||||
'$ref': '#/components/schemas/AccessList'
|
'$ref': '#/components/schemas/AccessList'
|
||||||
'AccessSetRequest':
|
'AccessSetRequest':
|
||||||
|
@ -2643,7 +2667,10 @@
|
||||||
set to true, and this string is empty, then the client IP is
|
set to true, and this string is empty, then the client IP is
|
||||||
disallowed by the "allowed IP list", that is it is not included in
|
disallowed by the "allowed IP list", that is it is not included in
|
||||||
the allowed list.
|
the allowed list.
|
||||||
|
'ignore_querylog':
|
||||||
|
'type': 'boolean'
|
||||||
|
'ignore_statistics':
|
||||||
|
'type': 'boolean'
|
||||||
'WhoisInfo':
|
'WhoisInfo':
|
||||||
'type': 'object'
|
'type': 'object'
|
||||||
'additionalProperties':
|
'additionalProperties':
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# This comment is used to simplify checking local copies of the script. Bump
|
# This comment is used to simplify checking local copies of the script. Bump
|
||||||
# this number every time a remarkable change is made to this script.
|
# this number every time a remarkable change is made to this script.
|
||||||
#
|
#
|
||||||
# AdGuard-Project-Version: 2
|
# AdGuard-Project-Version: 3
|
||||||
|
|
||||||
verbose="${VERBOSE:-0}"
|
verbose="${VERBOSE:-0}"
|
||||||
readonly verbose
|
readonly verbose
|
||||||
|
@ -31,12 +31,11 @@ set -f -u
|
||||||
|
|
||||||
# trailing_newlines is a simple check that makes sure that all plain-text files
|
# trailing_newlines is a simple check that makes sure that all plain-text files
|
||||||
# have a trailing newlines to make sure that all tools work correctly with them.
|
# have a trailing newlines to make sure that all tools work correctly with them.
|
||||||
#
|
|
||||||
# TODO(a.garipov): Add to the standard skeleton project.
|
|
||||||
trailing_newlines() {
|
trailing_newlines() {
|
||||||
nl="$( printf "\n" )"
|
nl="$( printf "\n" )"
|
||||||
readonly nl
|
readonly nl
|
||||||
|
|
||||||
|
# NOTE: Adjust for your project.
|
||||||
git ls-files\
|
git ls-files\
|
||||||
':!*.png'\
|
':!*.png'\
|
||||||
':!*.tar.gz'\
|
':!*.tar.gz'\
|
||||||
|
|
Loading…
Reference in a new issue