Compare commits

..

14 Commits

Author SHA1 Message Date
Ildar Kamalov
4c89cf7209 remove install from initial state 2024-06-06 13:27:18 +03:00
Igor Lobanov
b943f2011f frontend production build fix 2024-06-05 23:10:55 +02:00
Igor Lobanov
cd1be2d66d production build quickfix 2024-06-05 20:23:14 +02:00
Ainar Garipov
7b8ac01fc2 all: upd node docker 2024-06-05 19:57:31 +03:00
Igor Lobanov
02afed66d5 changelog fixes 2024-06-05 18:23:12 +02:00
Igor Lobanov
9c0f736f0c merge 2024-06-05 18:18:29 +02:00
Igor Lobanov
62c4fbf1e3 empty line in changelog 2024-06-05 16:22:22 +02:00
Igor Lobanov
76b1e44a93 changelog 2024-06-05 16:20:37 +02:00
Igor Lobanov
f783e90040 filters.js -> filters.ts 2024-06-05 16:19:13 +02:00
Igor Lobanov
3d4ce6554c generated file removed 2024-06-05 16:18:03 +02:00
Igor Lobanov
e35ba58f2a rollback unwanted changes 2024-06-05 15:45:21 +02:00
Igor Lobanov
1f30d4216d review fix 2024-06-05 15:27:36 +02:00
Igor Lobanov
6cd4e44f07 missing generated file restoresd 2024-06-05 11:55:39 +02:00
Igor Lobanov
2ab738b303 Frontend rewritten in TypeScript, added Node 18 support 2024-06-05 11:40:32 +02:00
65 changed files with 660 additions and 1756 deletions

View File

@@ -1,7 +1,7 @@
'name': 'build'
'env':
'GO_VERSION': '1.22.5'
'GO_VERSION': '1.22.4'
'NODE_VERSION': '16'
'on':

View File

@@ -1,7 +1,7 @@
'name': 'lint'
'env':
'GO_VERSION': '1.22.5'
'GO_VERSION': '1.22.4'
'on':
'push':

View File

@@ -7,10 +7,6 @@ The format is based on
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
<!--
TODO(a.garipov): Use the common markdown formatting tools.
-->
## [Unreleased]
@@ -18,89 +14,38 @@ TODO(a.garipov): Use the common markdown formatting tools.
<!--
## [v0.108.0] - TBA
## [v0.107.52] - 2024-06-29 (APPROX.)
## [v0.107.51] - 2024-06-22 (APPROX.)
See also the [v0.107.52 GitHub milestone][ms-v0.107.52].
See also the [v0.107.51 GitHub milestone][ms-v0.107.51].
[ms-v0.107.52]: https://github.com/AdguardTeam/AdGuardHome/milestone/87?closed=1
[ms-v0.107.51]: https://github.com/AdguardTeam/AdGuardHome/milestone/86?closed=1
NOTE: Add new changes BELOW THIS COMMENT.
-->
### Security
- Go version has been updated to prevent the possibility of exploiting the Go
vulnerabilities fixed in [Go 1.22.5][go-1.22.5].
### Added
- The ability to disable logging using the new `log.enabled` configuration
property ([#7079]).
### Changed
- Frontend rewritten in TypeScript.
- The `systemd`-based service now uses `journal` for logging by default. It
also doesn't create the `/var/log/` directory anymore ([#7053]).
**NOTE:** With an installed service for changes to take effect, you need to
reinstall the service using `-r` flag of the [install script][install-script]
or via the CLI (with root privileges):
```sh
./AdGuardHome -s uninstall
./AdGuardHome -s install
```
Don't forget to backup your configuration file and other important data before
reinstalling the service.
### Deprecated
- Node 18 support, Node 20 will be required in future releases.
### Fixed
- Unnecessary validation call on the encryption page.
- Missing version in the footer.
- Panic caused by missing user-specific blocked services object in configuration
file ([#7069]).
- Tracking `/etc/hosts` file changes causing panics within particular
filesystems on start ([#7076]).
[#7053]: https://github.com/AdguardTeam/AdGuardHome/issues/7053
[#7069]: https://github.com/AdguardTeam/AdGuardHome/issues/7069
[#7076]: https://github.com/AdguardTeam/AdGuardHome/issues/7076
[#7079]: https://github.com/AdguardTeam/AdGuardHome/issues/7079
[go-1.22.5]: https://groups.google.com/g/golang-announce/c/gyb7aM1C9H4
[install-script]: https://github.com/AdguardTeam/AdGuardHome/?tab=readme-ov-file#automated-install-linux-and-mac
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
## [v0.107.51] - 2024-06-06
See also the [v0.107.51 GitHub milestone][ms-v0.107.51].
### Security
- Go version has been updated to prevent the possibility of exploiting the Go
vulnerabilities fixed in [Go 1.22.4][go-1.22.4].
### Changed
- Frontend rewritten in TypeScript.
- The HTTP server's write timeout has been increased from 1 minute to 5 minutes
to match the one used by AdGuard Home's HTTP client to fetch filtering-list
data ([#7041]).
### Deprecated
- Node 18 support, Node 20 will be required in future releases.
[#7041]: https://github.com/AdguardTeam/AdGuardHome/issues/7041
[go-1.22.4]: https://groups.google.com/g/golang-announce/c/XbxouI9gY7k/
[ms-v0.107.51]: https://github.com/AdguardTeam/AdGuardHome/milestone/86?closed=1
[go-1.22.4]: https://groups.google.com/g/golang-announce/c/XbxouI9gY7k/
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
@@ -3061,12 +3006,11 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
<!--
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.52...HEAD
[v0.107.52]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.51...v0.107.52
-->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.51...HEAD
[v0.107.51]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.50...v0.107.51
-->
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.50...HEAD
[v0.107.50]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.49...v0.107.50
[v0.107.49]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.48...v0.107.49
[v0.107.48]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.47...v0.107.48

View File

@@ -27,7 +27,7 @@ DIST_DIR = dist
GOAMD64 = v1
GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
GOSUMDB = sum.golang.google.cn
GOTOOLCHAIN = go1.22.5
GOTOOLCHAIN = go1.22.4
GPG_KEY = devteam@adguard.com
GPG_KEY_PASSPHRASE = not-a-real-password
NPM = npm

View File

@@ -7,8 +7,8 @@
# Make sure to sync any changes with the branch overrides below.
'variables':
'channel': 'edge'
'dockerFrontend': 'adguard/home-js-builder:2.0'
'dockerGo': 'adguard/go-builder:1.22.5--1'
'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1'
'stages':
- 'Build frontend':
@@ -265,8 +265,8 @@
# need to build a few of these.
'variables':
'channel': 'beta'
'dockerFrontend': 'adguard/home-js-builder:2.0'
'dockerGo': 'adguard/go-builder:1.22.5--1'
'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1'
# release-vX.Y.Z branches are the branches from which the actual final
# release is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@@ -281,5 +281,5 @@
# are the ones that actually get released.
'variables':
'channel': 'release'
'dockerFrontend': 'adguard/home-js-builder:2.0'
'dockerGo': 'adguard/go-builder:1.22.5--1'
'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1'

View File

@@ -5,8 +5,8 @@
'key': 'AHBRTSPECS'
'name': 'AdGuard Home - Build and run tests'
'variables':
'dockerFrontend': 'adguard/home-js-builder:2.0'
'dockerGo': 'adguard/go-builder:1.22.5--1'
'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1'
'channel': 'development'
'stages':
@@ -194,6 +194,6 @@
# Set the default release channel on the release branch to beta, as we
# may need to build a few of these.
'variables':
'dockerFrontend': 'adguard/home-js-builder:2.0'
'dockerGo': 'adguard/go-builder:1.22.5--1'
'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1'
'channel': 'candidate'

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Použijte paralelní požadavky na urychlení řešení simultánním dotazováním na všechny navazující servery.",
"parallel_requests": "Paralelní požadavky",
"load_balancing": "Optimalizace vytížení",
"load_balancing_desc": "Dotazy jednoho odchozího serveru ve stejný čas. AdGuard Home používá náhodný algoritmus pro výběr serverů s nejnižším počtem neúspěšných vyhledávání a nejnižší průměrnou dobou vyhledávání.",
"load_balancing_desc": "Optimalizovaný dotaz na odchozí server. AdGuard Home použije vážený náhodný algoritmus k výběru serveru, takže nejrychlejší server je používán častěji.",
"bootstrap_dns": "Bootstrap DNS servery",
"bootstrap_dns_desc": "IP adresy DNS serverů používaných k překladu IP adres řešitelů DoH/DoT, které zadáte jako odchozí servery. Komentáře nejsou povoleny.",
"fallback_dns_title": "Záložní DNS servery",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Brug parallelforespørgsler til at accelerere fortolkningen ved at forespørge alle upstream-servere samtidigt.",
"parallel_requests": "Parallelle forespørgsler",
"load_balancing": "Belastningsfordeling",
"load_balancing_desc": "Forespørg én upstream-server ad gangen. AdGuard Home bruger en vægtet tilfældighedsalgoritme til vælg af servere med det laveste antal fejlslagne opslag og den laveste gennemsnitlige opslagstid.",
"load_balancing_desc": "Forespørg én server ad gangen. AdGuard Home vil bruge en vægtet randomiseringsalgoritme til valg af server, så den hurtigste server oftere anvendes.",
"bootstrap_dns": "Bootstrap DNS-servere",
"bootstrap_dns_desc": "IP-adresser på DNS-servere, som bruges til at opløse IP-adresser på de DoH/DoT-opløsere, som angives som upstreams. Kommentarer er ikke tilladt.",
"fallback_dns_title": "Reserve DNS-servere",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Parallele Abfragen verwenden, um das Auflösen zu beschleunigen, indem alle Upstream-Server gleichzeitig abgefragt werden.",
"parallel_requests": "Paralleles Abfragen",
"load_balancing": "Lastverteilung",
"load_balancing_desc": "Es wird jeweils ein Upstream-Server abgefragt. AdGuard Home verwendet einen gewichteten Zufallsalgorithmus, um die Server mit der geringsten Anzahl an fehlgeschlagenen Suchvorgängen und der niedrigsten durchschnittlichen Suchzeit auszuwählen.",
"load_balancing_desc": "Einen Server nach dem anderen abfragen. AdGuard Home verwendet den gewichteten Zufallsalgorithmus, um den Server so auszuwählen, dass der schnellste Server häufiger verwendet wird.",
"bootstrap_dns": "Bootstrap-DNS-Server",
"bootstrap_dns_desc": "IP-Adressen der DNS-Server, die zum Auflösen der IP-Adressen von DoH/DoT Upstream-Servern verwendet werden, die Sie angegeben haben. Kommentare sind nicht erlaubt.",
"fallback_dns_title": "Fallback-DNS-Server",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Use parallel queries to speed up resolving by querying all upstream servers simultaneously.",
"parallel_requests": "Parallel requests",
"load_balancing": "Load-balancing",
"load_balancing_desc": "Query one upstream server at a time. AdGuard Home uses a weighted random algorithm to select servers with the lowest number of failed lookups and the lowest average lookup time.",
"load_balancing_desc": "Query one upstream server at a time. AdGuard Home uses its weighted random algorithm to pick the server so that the fastest server is used more often.",
"bootstrap_dns": "Bootstrap DNS servers",
"bootstrap_dns_desc": "IP addresses of DNS servers used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams. Comments are not permitted.",
"fallback_dns_title": "Fallback DNS servers",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los servidores DNS de subida.",
"parallel_requests": "Consultas paralelas",
"load_balancing": "Balanceo de carga",
"load_balancing_desc": "Consulta un servidor upstream a la vez. AdGuard Home utiliza un algoritmo aleatorio ponderado para seleccionar los servidores con el menor número de fallos y el menor tiempo medio de búsqueda.",
"load_balancing_desc": "Consulta un servidor DNS de subida a la vez. AdGuard Home utiliza su algoritmo aleatorio ponderado para elegir el servidor más rápido y sea utilizado con más frecuencia.",
"bootstrap_dns": "Servidores DNS de arranque",
"bootstrap_dns_desc": "Direcciones IP de servidores DNS utilizadas para resolver direcciones IP de los solucionadores DoH/DoT que especifiques como ascendentes. No se permiten comentarios.",
"fallback_dns_title": "Servidores DNS alternativos",

View File

@@ -589,7 +589,6 @@
"cache_optimistic_desc": "AdGuard Home را وادار می کند که از سمت حافظه پنهان پاسخ دهد حتی وقتی که موارد وارد شده منقضی شده باشد و همچنین سعی بر تازه کردن آنها می کند.",
"filter_category_general": "General",
"filter_category_security": "مسدودسازی بدافزار و فیشینگ",
"filter_category_regional": "منطقه‌ای",
"filter_category_other": "ساير",
"use_saved_key": "از کلید ذخیره شده قبلی استفاده کنید",
"parental_control": "نظارت والدین",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Utilisez des requêtes parallèles pour accélérer la résolution en requêtant simultanément tous les serveurs en amont.",
"parallel_requests": "Requêtes en parallèle",
"load_balancing": "Équilibrage de charge",
"load_balancing_desc": "Une requête par serveur en amont à la fois. AdGuard Home utilise un algorithme aléatoire pondéré pour sélectionner les serveurs avec le plus petit nombre d'échecs de recherche et le temps de recherche moyen le plus bas.",
"load_balancing_desc": "Interroger un serveur en amont à la fois. AdGuard Home utilise son algorithme aléatoire pondéré pour choisir le serveur de sorte que le serveur le plus rapide soit utilisé plus souvent.",
"bootstrap_dns": "Serveurs DNS d'amorçage",
"bootstrap_dns_desc": "Les adresses IP des serveurs DNS utilisées pour résoudre les adresses IP des résolveurs DoH/DoT que vous spécifiez comme en amont. Les commentaires ne sont pas autorisés.",
"fallback_dns_title": "Serveurs DNS de repli",

View File

@@ -13,14 +13,14 @@
"fallback_dns_desc": "Popis rezervnih DNS poslužitelja koji se koriste kada uzvodni DNS poslužitelji ne odgovaraju. Sintaksa je ista kao u gornjem polju glavnog uzvodnog toka.",
"fallback_dns_placeholder": "Unesite jedan rezervni DNS poslužitelj po retku",
"local_ptr_title": "Privatni obrnuti DNS poslužitelji",
"local_ptr_desc": "DNS poslužitelji koje koristi AdGuard Home za privatne PTR, SOA i NS zahtjeve. Zahtjev se smatra privatnim ako traži ARPA domenu koja sadrži podmrežu unutar privatnih IP raspona (kao što je \"192.168.12.34\") i dolazi od klijenta s privatnom IP adresom. Ako nije postavljeno, koristit će se zadani DNS rezolveri vašeg OS-a, osim za AdGuard Home IP adrese.",
"local_ptr_desc": "DNS poslužitelji koje AdGuard Home koristi za lokalne PTR upite. Ti se poslužitelji koriste za razrješavanje naziva glavnog računala klijenata s privatnim IP adresama, na primjer \"192.168.12.34\", koristeći obrnuti DNS. Ako nije postavljeno, AdGuard Home koristi adrese zadanih DNS razrješivača vašeg OS-a, osim za adrese samog AdGuard Homea.",
"local_ptr_default_resolver": "Prema zadanim postavkama AdGuard Home koristi sljedeće obrnute DNS razrješivače: {{ip}}.",
"local_ptr_no_default_resolver": "AdGuard Home nije mogao odrediti prikladne privatne obrnute DNS razrješivače za ovaj sustav.",
"local_ptr_placeholder": "Unesite jednu adresu poslužitelja po retku",
"resolve_clients_title": "Omogući obrnuto rješavanje IP adresa klijenata",
"resolve_clients_desc": "Obrnuto razriješite IP adrese klijenata u nazive glavnih računala slanjem PTR upita odgovarajućim razrješivačima (privatni DNS poslužitelji za lokalne klijente, uzvodni poslužitelji za klijente s javnim IP adresama).",
"use_private_ptr_resolvers_title": "Koristi privatne reverzne DNS razrješivače",
"use_private_ptr_resolvers_desc": "Razriješi PTR, SOA i NS zahtjeve za ARPA domene koje sadrže privatne IP adrese putem privatnih uzvodnih poslužitelja, DHCP-a, /etc/hostova itd. Ako je onemogućeno, AdGuard Home će na sve takve zahtjeve odgovoriti s NXDOMAIN.",
"use_private_ptr_resolvers_desc": "Izvršite obrnuta DNS traženja za lokalno poslužene adrese pomoću ovih uzlaznih poslužitelja. Ako je onemogućen, AdGuard Home odgovara S NXDOMAIN-om na sve takve PTR zahtjeve osim za klijente poznate iz DHCP-a, /etc/hosts i tako dalje.",
"check_dhcp_servers": "Provjera DHCP poslužitelja",
"save_config": "Spremi konfiguraciju",
"enabled_dhcp": "DHCP poslužitelj je omogućen",
@@ -425,9 +425,6 @@
"encryption_hostnames": "Nazivi računala",
"encryption_reset": "Jeste li sigurni da želite poništiti postavke šifriranja?",
"encryption_warning": "Upozorenje",
"encryption_plain_dns_enable": "Omogući obični DNS",
"encryption_plain_dns_desc": "Obični DNS je omogućen prema zadanim postavkama. Možete ga onemogućiti kako biste prisilili sve uređaje da koriste šifrirani DNS. Da biste to učinili, morate omogućiti barem jedan kriptirani DNS protokol",
"encryption_plain_dns_error": "Da biste onemogućili obični DNS, omogućite barem jedan kriptirani DNS protokol",
"topline_expiring_certificate": "Vaš SSL certifikat uskoro ističe. Ažurirajte <0>Postavke šifriranja</0>.",
"topline_expired_certificate": "Vaš SSL certifikat je istekao. Ažurirajte <0>Postavke šifriranja</0>.",
"form_error_port_range": "Unesite broj porta od 80 do 65536",
@@ -678,7 +675,7 @@
"use_saved_key": "Korištenje prethodno spremljenog ključa",
"parental_control": "Roditeljska zaštita",
"safe_browsing": "Sigurno surfanje",
"served_from_cache_label": "Posluženo iz predmemorije",
"served_from_cache": "{{value}} <i>(dohvaćeno iz predmemorije)</i>",
"form_error_password_length": "Lozinka mora sadržavati od {{min}} do {{max}} znakova",
"anonymizer_notification": "<0>Napomena:</0>IP anonimizacija je omogućena. Možete ju onemogućiti u <1>općim postavkama</1>.",
"confirm_dns_cache_clear": "Jeste li sigurni da želite očistiti DNS predmemoriju?",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Utilizza richieste parallele per accelerare la risoluzione interrogando simultaneamente tutti i server upstream.",
"parallel_requests": "Richieste parallele",
"load_balancing": "Bilanciamento del carico",
"load_balancing_desc": "Esegui una query su un server upstream alla volta. AdGuard Home utilizza un algoritmo casuale ponderato per selezionare i server con il minor numero di ricerche fallite e il tempo medio di ricerca più basso.",
"load_balancing_desc": "Interroga un server upstream per volta. AdGuard Home utilizzerà un algoritmo casuale ponderato per la selezione del server, in maniera tale da scegliere spesso il più veloce.",
"bootstrap_dns": "Server DNS bootstrap",
"bootstrap_dns_desc": "Indirizzi IP dei server DNS utilizzati per risolvere gli indirizzi IP dei resolver DoH/DoT specificati come upstream. I commenti non sono ammessi.",
"fallback_dns_title": "Server DNS di fallback",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "並列リクエストを使用する(同時にすべてのアップストリームサーバーに処理要求することで解決スピードが向上)",
"parallel_requests": "並列リクエスト",
"load_balancing": "ロードバランシング",
"load_balancing_desc": "一度に1つのアップストリームサーバーをクエリします。AdGuard Home は、重み付き乱択アルゴリズムを使用して、ルックアップに失敗した回数が最も少なく、平均ルックアップ時間が最も短いサーバを選択ます。",
"load_balancing_desc": "一度に1つのアップストリームサーバに処理要求します。 AdGuard Homeは、重み付きランダムアルゴリズムweighted random algorithmを使用してサーバを選択するため、最速のサーバがより頻繁に使用されます。",
"bootstrap_dns": "ブートストラップDNSサーバ",
"bootstrap_dns_desc": "アップストリームとして指定したDoH/DoTリゾルバのIPアドレスを解決するために使用されるDNSサーバーのIPアドレスです。コメントは許可されていません",
"fallback_dns_title": "フォールバックDNSサーバー",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "쿼리 처리 속도를 높이려면 모든 업스트림 서버에서 동시에 병렬 쿼리를 사용해주세요.",
"parallel_requests": "병렬 처리 요청",
"load_balancing": "로드 밸런싱",
"load_balancing_desc": "한 번에 하나의 업스트림 서버를 쿼리합니다. AdGuard Home은 가중 무작위 알고리즘 사용하여 조회 실패 횟수가 가장 적고 평균 조회 시간이 가장 짧은 서버를 선택합니다.",
"load_balancing_desc": "한 번에 하나의 서버씩 질의합니다. AdGuard Home은 가중 랜덤 알고리즘 사용해서 가장 빠른 서버가 자주 사용되도록 서버를 선택합니다.",
"bootstrap_dns": "부트스트랩 DNS 서버",
"bootstrap_dns_desc": "업스트림으로 지정한 DoH/DoT 리졸버의 IP 주소를 확인하는 데 사용되는 DNS 서버의 IP 주소입니다. 주석은 허용되지 않습니다.",
"fallback_dns_title": "폴백 DNS 서버",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Parallelle verzoeken gebruiken om te versnellen door gelijktijdig verzoeken te sturen naar alle upstream servers.",
"parallel_requests": "Parallelle verzoeken",
"load_balancing": "Volume balanceren",
"load_balancing_desc": "Voer zoekopdrachten uit op één upstream-server tegelijk. AdGuard Home gebruikt een gewogen willekeurig algoritme om servers te selecteren met het laagste aantal mislukte zoekopdrachten en de laagste gemiddelde opzoektijd.",
"load_balancing_desc": "Eén server per keer bevragen. AdGuard Home gebruikt hiervoor een gewogen willekeurig algoritme om de server te kiezen zodat de snelste server meer zal gebruikt worden.",
"bootstrap_dns": "Bootstrap DNS-servers",
"bootstrap_dns_desc": "IP-adressen van DNS-servers die worden gebruikt om IP-adressen om te zetten van de DoH/DoT-resolvers die je opgeeft als upstreams. Opmerkingen zijn niet toegestaan.",
"fallback_dns_title": "Back-up DNS-servers",
@@ -495,7 +495,7 @@
"setup_dns_privacy_2": "<0>DNS-via-HTTPS:</0> Gebruik <1>{{address}}</1> string.",
"setup_dns_privacy_3": "<0>Hou er rekening mee dat het beveiligde DNS protocol alleen beschikbaar is voor Android 9. U moet dus extra software installeren voor andere besturingssystemen.</0><0>Hier is een lijst van te gebruiken software.</0>",
"setup_dns_privacy_4": "Op een iOS 14 of macOS Big Sur apparaat kan je een speciaal '.mobileconfig'-bestand downloaden dat <highlight>DNS-via-HTTPS</highlight> of <highlight>DNS-via-TLS</highlight> servers aan de DNS-instellingen toevoegt.",
"setup_dns_privacy_android_1": "Android 9 ondersteunt native DNS-via-TLS. Om het te configureren, ga naar Instellingen → Netwerk & internet → Geavanceerd → Privé-DNS en voer daar je domeinnaam in.",
"setup_dns_privacy_android_1": "Android 9 ondersteunt native DNS-via-TLS. Om het te configureren, ga naar Instellingen → Netwerk & internet → Geavanceerd → Privé DNS en voer daar je domeinnaam in.",
"setup_dns_privacy_android_2": "<0>AdGuard voor Android</0>ondersteunt<1>DNS-via-HTTPS </1>en<1>DNS-via-TLS</1>.",
"setup_dns_privacy_android_3": "<0> Intra </0> voegt <1> DNS-via-HTTPS</1> ondersteuning toe aan Android.",
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> ondersteunt <1> DNS-via-HTTPS </1>, maar om het te configureren op jouw eigen server moet er een <2> DNS-stempel </2> gegenereerd worden.",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS primário",
"parallel_requests": "Solicitações paralelas",
"load_balancing": "Balanceamento de carga",
"load_balancing_desc": "Consulte um servidor upstream por vez. O AdGuard Home usa um algoritmo aleatório ponderado para selecionar servidores com o menor número de falhas e o menor tempo médio de consulta.",
"load_balancing_desc": "Consulte um servidor DNS primário por vez. O AdGuard Home usa seu algoritmo aleatório ponderado para escolher o servidor para que o servidor mais rápido seja usado com mais frequência.",
"bootstrap_dns": "Servidores DNS de inicialização",
"bootstrap_dns_desc": "Endereços IP de servidores DNS usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams. Comentários não são permitidos.",
"fallback_dns_title": "Servidores DNS Fallback",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS",
"parallel_requests": "Solicitações paralelas",
"load_balancing": "Balanceamento de carga",
"load_balancing_desc": "Consulta um servidor a montante de cada vez. O AdGuard Home usa um algoritmo aleatório ponderado para selecionar servidores com o menor número de pesquisas com falha e o menor tempo médio de pesquisa.",
"load_balancing_desc": "Consulte um servidor DNS primário por vez. O AdGuard Home usa seu algoritmo aleatório ponderado para escolher o servidor para que o servidor mais rápido seja usado com mais frequência.",
"bootstrap_dns": "Servidores DNS de arranque",
"bootstrap_dns_desc": "Endereços IP de servidores DNS usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams. Comentários não são permitidos.",
"fallback_dns_title": "Servidores DNS de fallback",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Использовать параллельные запросы ко всем серверам одновременно для ускорения обработки запроса.",
"parallel_requests": "Параллельные запросы",
"load_balancing": "Распределение нагрузки\n",
"load_balancing_desc": "Запрашивайте по одному серверу за раз. AdGuard Home использует алгоритм случайной выборки с учётом веса для выбора серверов с наименьшим количеством неудачных запросов и наименьшим средним временем выполнения запроса.",
"load_balancing_desc": "Запрашивать по одному серверу за раз. AdGuard Home использует алгоритм взвешенного случайного выбора сервера, так что самый быстрый сервер используется чаще.",
"bootstrap_dns": "Bootstrap DNS-серверы",
"bootstrap_dns_desc": "IP-адреса DNS-серверов, используемых для поиска IP-адресов DoH/DoT upstream-серверов, которые вы указали. Комментарии не допускаются.",
"fallback_dns_title": "Резервные DNS-серверы",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Používať paralelné dopyty na zrýchlenie súčasným dopytovaním všetkých upstream serverov súčasne.",
"parallel_requests": "Paralelné dopyty",
"load_balancing": "Vyrovnávanie záťaže",
"load_balancing_desc": "Dopytuje sa súčasne len jeden upstream server. AdGuard Home používa vážený náhodný algoritmus na výber serverov s najnižším počtom neúspešných vyhľadávaní a najnižším priemerným časom vyhľadávania.",
"load_balancing_desc": "Dopytovať len jeden server v danom čase. AdGuard Home použije na výber servera vážený náhodný algoritmus, aby sa najrýchlejší server používal častejšie.",
"bootstrap_dns": "Bootstrap DNS servery",
"bootstrap_dns_desc": "IP adresy serverov DNS používaných na rozlíšenie IP adries prekladačov DoH/DoT, ktoré zadáte ako upstream. Komentáre nie sú povolené.",
"fallback_dns_title": "Záložné servery DNS",
@@ -89,7 +89,7 @@
"form_enter_hostname": "Zadajte meno hostiteľa",
"error_details": "Podrobnosti chyby",
"response_details": "Podrobnosti odpovede",
"request_details": "Podrobnosti dopytu",
"request_details": "Podrobnosti požiadavky",
"client_details": "Podrobnosti klienta",
"details": "Podrobnosti",
"back": "Naspäť",
@@ -308,7 +308,7 @@
"form_enter_rate_limit": "Zadajte rýchlostný limit",
"rate_limit": "Rýchlostný limit",
"edns_enable": "Povoliť klientsku podsiete EDNS",
"edns_cs_desc": "Pridáva možnosť EDNS Client Subnet (ECS) do upstream dopytov a zapíše hodnoty odoslané klientami do denníka dopytov.",
"edns_cs_desc": "Pridáva možnosť EDNS Client Subnet (ECS) do upstream požiadaviek a zapíše hodnoty odoslané klientmi do denníka dopytov.",
"edns_use_custom_ip": "Použiť vlastnú IP adresu pre EDNS",
"edns_use_custom_ip_desc": "Povoliť používanie vlastnej IP adresy pre EDNS",
"rate_limit_desc": "Počet požiadaviek za sekundu, ktoré môže jeden klient vykonať. Nastavenie na hodnotu 0 znamená neobmedzene.",
@@ -480,9 +480,9 @@
"access_title": "Nastavenia prístupu",
"access_desc": "Tu môžete konfigurovať pravidlá prístupu pre server DNS AdGuard Home.",
"access_allowed_title": "Povolení klienti",
"access_allowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home bude akceptovať dopyty iba od týchto klientov.",
"access_allowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home bude akceptovať požiadavky iba od týchto klientov.",
"access_disallowed_title": "Nepovolení klienti",
"access_disallowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home zruší dopyty od týchto klientov. Toto pole sa ignoruje, ak sú v poli Povolení klienti položky.",
"access_disallowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home zruší požiadavky od týchto klientov. Toto pole sa ignoruje, ak sú v poli Povolení klienti položky.",
"access_blocked_title": "Nepovolené domény",
"access_blocked_desc": "Nesmie byť zamieňaná s filtrami. AdGuard Home zruší DNS dopyty, ktoré sa zhodujú s týmito doménami, a tieto dopyty sa nezobrazia ani v denníku dopytov. Môžete určiť presné názvy domén, zástupné znaky alebo pravidlá filtrovania URL adries, napr. \"example.org\", \"*.example.org\" alebo ||example.org^\" zodpovedajúcim spôsobom.",
"access_settings_saved": "Nastavenia prístupu úspešne uložené",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Tüm üst sunucuları eş zamanlı sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.",
"parallel_requests": "Paralel istekler",
"load_balancing": "Yük dengeleme",
"load_balancing_desc": "Aynı anda bir üst kaynak sunucusunu sorgulayın. AdGuard Home, en düşük başarısız arama sayısına ve en düşük ortalama arama süresine sahip sunucuları seçmek için ağırlıklı rastgele bir algoritma kullanır.",
"load_balancing_desc": "Her seferde bir üst sunucuyu sorgulayın. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmasını kullanır, böylece en hızlı sunucu daha sık kullanılır.",
"bootstrap_dns": "DNS Önyükleme sunucuları",
"bootstrap_dns_desc": "Üst kaynak olarak belirttiğiniz DoH/DoT çözümleyicilerin IP adreslerini çözümlemek için kullanılan DNS sunucularının IP adresleri. Yorumlara izin verilmez.",
"fallback_dns_title": "Yedek DNS sunucuları",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "使用并行请求以同时查询所有上游服务器来加快解析速度。",
"parallel_requests": "并行请求",
"load_balancing": "负载均衡",
"load_balancing_desc": "一次查询一台服务器。AdGuard Home 使用加权随机算法来选择具有最少失败查找和最低平均查找时间的服务器。",
"load_balancing_desc": "一次查询一台服务器。AdGuard Home 使用加权随机算法来选择服务器,以便更常使用最快的服务器。",
"bootstrap_dns": "Bootstrap DNS 服务器",
"bootstrap_dns_desc": "DNS 服务器的 IP 地址,用于解析指定为上游的 DoH/DoT 解析器的 IP 地址。不允许添加注释。",
"fallback_dns_title": "后备 DNS 服务器",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。",
"parallel_requests": "並行的請求",
"load_balancing": "負載平衡",
"load_balancing_desc": "次查詢一伺服器。AdGuard Home 使用加權隨機演算法來選擇具有最少失敗查詢和最低平均查詢時間的伺服器。",
"load_balancing_desc": "次查詢一個上游伺服器。AdGuard Home 使用它的加權隨機演算法來選擇伺服器,以便最快的伺服器被更常使用。",
"bootstrap_dns": "自我啟動BootstrapDNS 伺服器",
"bootstrap_dns_desc": "DNS 伺服器的 IP 位址,用於解析您指定為上游伺服器的 DoH/DoT 解析器的 IP 位址。不允許註釋。",
"fallback_dns_title": "應變 DNS 伺服器",

View File

@@ -38,7 +38,7 @@ class Encryption extends Component<EncryptionProps> {
handleFormChange = debounce((values) => {
const submitValues = this.getSubmitValues(values);
if (submitValues.enabled) {
if (submitValues.enabled || submitValues.serve_plain_dns) {
this.props.validateTlsConfig(submitValues);
}
}, DEBOUNCE_TIMEOUT);

View File

@@ -12,11 +12,12 @@ const Version = () => {
const dashboard = useSelector((state: RootState) => state.dashboard, shallowEqual);
const install = useSelector((state: RootState) => state.install, shallowEqual);
if (!dashboard && !install) {
if (!dashboard || !install) {
return null;
}
const version = dashboard?.dnsVersion || install?.dnsVersion;
const { dnsVersion, processingVersion, checkUpdateFlag } = dashboard;
const version = dnsVersion || install?.dnsVersion;
const onClick = () => {
dispatch(getVersion(true));
@@ -34,12 +35,12 @@ const Version = () => {
</>
)}
{dashboard?.checkUpdateFlag && (
{checkUpdateFlag && (
<button
type="button"
className="btn btn-icon btn-icon-sm btn-outline-primary btn-sm ml-2"
onClick={onClick}
disabled={dashboard?.processingVersion}
disabled={processingVersion}
title={t('check_updates_now')}>
<svg className="icons icon12">
<use xlinkHref="#refresh" />

View File

@@ -295,7 +295,7 @@ export default {
"phishing_army": {
"name": "Phishing Army",
"categoryId": "security",
"homepage": "https://phishing.army/",
"homepage": "https://gitlab.com/malware-filter/phishing-filter",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt"
},
"scam_blocklist_by_durablenapkin": {

View File

@@ -1,5 +1,5 @@
{
"timeUpdated": "2024-07-01T00:11:48.891Z",
"timeUpdated": "2024-03-01T00:10:14.031Z",
"categories": {
"0": "audio_video_player",
"1": "comments",
@@ -1182,13 +1182,6 @@
"url": "https://www.admitad.com/en/#",
"companyId": "admitad"
},
"admixer": {
"name": "Admixer",
"categoryId": 4,
"url": "https://admixer.com/",
"companyId": "admixer",
"source": "AdGuard"
},
"admixer.net": {
"name": "Admixer",
"categoryId": 4,
@@ -1480,13 +1473,6 @@
"url": "http://www.adplus.co.id/",
"companyId": "adplus"
},
"adprofex": {
"name": "AdProfex",
"categoryId": 4,
"url": "https://adprofex.com/",
"companyId": "adprofex",
"source": "AdGuard"
},
"adprofy": {
"name": "AdProfy",
"categoryId": 4,
@@ -2033,13 +2019,6 @@
"url": "https://hybridtheory.com/",
"companyId": "affectv"
},
"affilbox": {
"name": "Affilbox",
"categoryId": 4,
"url": "https://affilbox.com/",
"companyId": "affilbox",
"source": "AdGuard"
},
"affiliate-b": {
"name": "Affiliate-B",
"categoryId": 4,
@@ -2394,13 +2373,6 @@
"url": "http://www.amadesa.com/",
"companyId": "amadesa"
},
"amap": {
"name": "Amap",
"categoryId": 2,
"url": "https://www.amap.com/",
"companyId": "softbank",
"source": "AdGuard"
},
"amazon": {
"name": "Amazon.com",
"categoryId": 8,
@@ -4619,13 +4591,6 @@
"url": "http://clickaider.com/",
"companyId": "clickaider"
},
"clickaine": {
"name": "Clickaine",
"categoryId": 4,
"url": "https://clickaine.com/",
"companyId": "clickaine",
"source": "AdGuard"
},
"clickbank": {
"name": "ClickBank",
"categoryId": 4,
@@ -12505,13 +12470,6 @@
"url": "http://nolix.ru/",
"companyId": "nolix"
},
"nonli": {
"name": "Nonli",
"categoryId": 4,
"url": "https://www.nonli.com/",
"companyId": "nonli",
"source": "AdGuard"
},
"nonstop_consulting": {
"name": "Resolution Media",
"categoryId": 4,
@@ -12911,13 +12869,6 @@
"url": "http://opensharecount.com/",
"companyId": "open_share_count"
},
"openai": {
"name": "OpenAI",
"categoryId": 8,
"url": "https://openai.com/",
"companyId": "openai",
"source": "AdGuard"
},
"openload": {
"name": "Openload",
"categoryId": 9,
@@ -20388,7 +20339,6 @@
"adguard.org": "adguard",
"adtidy.org": "adguard",
"agrd.io": "adguard",
"agrd.eu": "adguard",
"adguard-dns.com": "adguard_dns",
"adguard-dns.io": "adguard_dns",
"adguard-vpn.com": "adguard_vpn",
@@ -20463,8 +20413,7 @@
"admicro.vn": "admicro",
"vcmedia.vn": "admicro",
"admitad.com": "admitad.com",
"admixer.net": "admixer",
"admixer.com": "admixer",
"admixer.net": "admixer.net",
"admized.com": "admized",
"admo.tv": "admo.tv",
"a.admob.com": "admob",
@@ -20544,8 +20493,6 @@
"advg.jp": "adplan",
"c.p-advg.com": "adplan",
"adplus.co.id": "adplus",
"adprofex.com": "adprofex",
"ads2.bid": "adprofex",
"adframesrc.com": "adprofy",
"adserve.adpulse.ir": "adpulse",
"ads.adpv.com": "adpv",
@@ -20669,8 +20616,6 @@
"affectv.com": "affectv",
"go.affec.tv": "affectv",
"hybridtheory.com": "affectv",
"affilbox.com": "affilbox",
"affilbox.cz": "affilbox",
"track.affiliate-b.com": "affiliate-b",
"affiliate4you.nl": "affiliate4you",
"ads.affbuzzads.com": "affiliatebuzz",
@@ -20759,7 +20704,6 @@
"inputs.alooma.com": "alooma",
"arena.altitude-arena.com": "altitude_digital",
"amadesa.com": "amadesa",
"amap.com": "amap",
"amazon.ca": "amazon",
"amazon.co.jp": "amazon",
"amazon.co.uk": "amazon",
@@ -21287,7 +21231,6 @@
"clickandchat.com": "click_and_chat",
"software.clickback.com": "click_back",
"hit.clickaider.com": "clickaider",
"clickaine.com": "clickaine",
"clickbank.net": "clickbank",
"cbproads.com": "clickbank_proads",
"adtoll.com": "clickbooth",
@@ -22299,7 +22242,6 @@
"withgoogle.com": "google",
"googleadservices.com": "google_adservices",
"google-analytics.com": "google_analytics",
"app-analytics-services.com": "google_analytics",
"ssl-google-analytics.l.google.com": "google_analytics",
"www-googletagmanager.l.google.com": "google_analytics",
"appspot.com": "google_appspot",
@@ -23454,8 +23396,6 @@
"noaa.gov": "noaa.gov",
"track.noddus.com": "noddus",
"contextbar.ru": "nolix",
"nonli.com": "nonli",
"non.li": "nonli",
"trkme.net": "nonstop_consulting",
"noop.style": "noop.style",
"nosto.com": "nosto.com",
@@ -23533,10 +23473,6 @@
"realmedia.com": "open_adstream",
"realmediadigital.com": "open_adstream",
"opensharecount.com": "open_share_count",
"chatgpt.com": "openai",
"oaistatic.com": "openai",
"oaiusercontent.com": "openai",
"openai.com": "openai",
"oloadcdn.net": "openload",
"openload.co": "openload",
"openstat.net": "openstat",
@@ -23713,7 +23649,6 @@
"popads.net": "popads",
"popadscdn.net": "popads",
"popcash.net": "popcash",
"popcashjs.b-cdn.net": "popcash",
"desv383oqqc0.cloudfront.net": "popcorn_metrics",
"popin.cc": "popin.cc",
"cdn.popmyads.com": "popmyads",
@@ -24421,10 +24356,6 @@
"spoteffects.net": "spoteffect",
"scdn.co": "spotify",
"spotify.com": "spotify",
"pscdn.co": "spotify",
"spotifycdn.com": "spotify",
"spotifycdn.net": "spotify",
"spotilocal.com": "spotify",
"embed.spotify.com": "spotify_embed",
"spotscenered.info": "spotscenered.info",
"spotx.tv": "spotxchange",
@@ -24744,8 +24675,6 @@
"t.co": "twitter",
"twimg.com": "twitter",
"twitter.com": "twitter",
"twttr.com": "twitter",
"x.com": "twitter",
"ads-twitter.com": "twitter_ads",
"analytics.twitter.com": "twitter_analytics",
"tellapart.com": "twitter_for_business",

22
go.mod
View File

@@ -1,11 +1,11 @@
module github.com/AdguardTeam/AdGuardHome
go 1.22.5
go 1.22.4
require (
github.com/AdguardTeam/dnsproxy v0.71.2
github.com/AdguardTeam/golibs v0.24.0
github.com/AdguardTeam/urlfilter v0.19.0
github.com/AdguardTeam/golibs v0.23.2
github.com/AdguardTeam/urlfilter v0.18.0
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.3.0
github.com/bluele/gcache v0.0.2
@@ -27,15 +27,15 @@ require (
// 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.61
github.com/miekg/dns v1.1.59
github.com/quic-go/quic-go v0.44.0
github.com/stretchr/testify v1.9.0
github.com/ti-mo/netfilter v0.5.2
go.etcd.io/bbolt v1.3.10
golang.org/x/crypto v0.24.0
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8
golang.org/x/net v0.26.0
golang.org/x/sys v0.21.0
golang.org/x/crypto v0.23.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.25.0
golang.org/x/sys v0.20.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.1
@@ -58,9 +58,9 @@ require (
github.com/quic-go/qpack v0.4.0 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.21.0 // indirect
gonum.org/v1/gonum v0.15.0 // indirect
)

56
go.sum
View File

@@ -1,9 +1,9 @@
github.com/AdguardTeam/dnsproxy v0.71.2 h1:dFG2wga4GDdj1eI3rU2wqjQ6QGQm9MjLRb5ZzyH3Vgg=
github.com/AdguardTeam/dnsproxy v0.71.2/go.mod h1:huI5zyWhlimHBhg0jt2CMinXzsEHymI+WlvxIfmfEGA=
github.com/AdguardTeam/golibs v0.24.0 h1:qAnOq7BQtwSVo7Co9q703/n+nZ2Ap6smkugU9G9MomY=
github.com/AdguardTeam/golibs v0.24.0/go.mod h1:9/vJcYznW7RlmCT/Qzi8XNZGj+ZbWfHZJmEXKnRpCAU=
github.com/AdguardTeam/urlfilter v0.19.0 h1:q7eH13+yNETlpD/VD3u5rLQOripcUdEktqZFy+KiQLk=
github.com/AdguardTeam/urlfilter v0.19.0/go.mod h1:+N54ZvxqXYLnXuvpaUhK2exDQW+djZBRSb6F6j0rkBY=
github.com/AdguardTeam/golibs v0.23.2 h1:rMjYantwtQ39e8G4zBQ6ZLlm4s3XH30Bc9VxhoOHwao=
github.com/AdguardTeam/golibs v0.23.2/go.mod h1:o9i55Sx6v7qogRQeqaBfmLbC/pZqeMBWi015U5PTDY0=
github.com/AdguardTeam/urlfilter v0.18.0 h1:ZZzwODC/ADpjJSODxySrrUnt/fvOCfGFaCW6j+wsGfQ=
github.com/AdguardTeam/urlfilter v0.18.0/go.mod h1:IXxBwedLiZA2viyHkaFxY/8mjub0li2PXRg8a3d9Z1s=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
@@ -78,8 +78,8 @@ github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU=
@@ -101,8 +101,8 @@ 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/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -114,36 +114,36 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8
github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU=
github.com/ti-mo/netfilter v0.5.2 h1:CTjOwFuNNeZ9QPdRXt1MZFLFUf84cKtiQutNauHWd40=
github.com/ti-mo/netfilter v0.5.2/go.mod h1:Btx3AtFiOVdHReTDmP9AE+hlkOcvIy403u7BXXbWZKo=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM=
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
@@ -158,19 +158,19 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
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=
gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=

View File

@@ -161,8 +161,7 @@ func (hc *HostsContainer) handleEvents() {
defer close(hc.updates)
eventsCh := hc.watcher.Events()
ok := eventsCh != nil
ok, eventsCh := true, hc.watcher.Events()
for ok {
select {
case _, ok = <-eventsCh:

View File

@@ -160,34 +160,3 @@ func (w *osWatcher) handleErrors() {
log.Error("%s: %s", osWatcherPref, err)
}
}
// EmptyFSWatcher is a no-op implementation of the [FSWatcher] interface. It
// may be used on systems not supporting filesystem events.
type EmptyFSWatcher struct{}
// type check
var _ FSWatcher = EmptyFSWatcher{}
// Start implements the [FSWatcher] interface for EmptyFSWatcher. It always
// returns nil error.
func (EmptyFSWatcher) Start() (err error) {
return nil
}
// Close implements the [FSWatcher] interface for EmptyFSWatcher. It always
// returns nil error.
func (EmptyFSWatcher) Close() (err error) {
return nil
}
// Events implements the [FSWatcher] interface for EmptyFSWatcher. It always
// returns nil channel.
func (EmptyFSWatcher) Events() (e <-chan event) {
return nil
}
// Add implements the [FSWatcher] interface for EmptyFSWatcher. It always
// returns nil error.
func (EmptyFSWatcher) Add(_ string) (err error) {
return nil
}

View File

@@ -19,9 +19,25 @@ import (
"github.com/AdguardTeam/golibs/log"
)
// Unsupported is a helper that returns a wrapped [errors.ErrUnsupported].
// UnsupportedError is returned by functions and methods when a particular
// operation Op cannot be performed on the current OS.
type UnsupportedError struct {
Op string
OS string
}
// Error implements the error interface for *UnsupportedError.
func (err *UnsupportedError) Error() (msg string) {
return fmt.Sprintf("%s is unsupported on %s", err.Op, err.OS)
}
// Unsupported is a helper that returns an *UnsupportedError with the Op field
// set to op and the OS field set to the current OS.
func Unsupported(op string) (err error) {
return fmt.Errorf("%s: not supported on %s: %w", op, runtime.GOOS, errors.ErrUnsupported)
return &UnsupportedError{
Op: op,
OS: runtime.GOOS,
}
}
// SetRlimit sets user-specified limit of how many fd's we can use.

View File

@@ -30,8 +30,8 @@ func macToKey(mac net.HardwareAddr) (key macKey) {
}
}
// index stores all information about persistent clients.
type index struct {
// Index stores all information about persistent clients.
type Index struct {
// nameToUID maps client name to UID.
nameToUID map[string]UID
@@ -51,9 +51,9 @@ type index struct {
subnetToUID aghalg.SortedMap[netip.Prefix, UID]
}
// newIndex initializes the new instance of client index.
func newIndex() (ci *index) {
return &index{
// NewIndex initializes the new instance of client index.
func NewIndex() (ci *Index) {
return &Index{
nameToUID: map[string]UID{},
clientIDToUID: map[string]UID{},
ipToUID: map[netip.Addr]UID{},
@@ -63,9 +63,9 @@ func newIndex() (ci *index) {
}
}
// add stores information about a persistent client in the index. c must be
// non-nil, have a UID, and contain at least one identifier.
func (ci *index) add(c *Persistent) {
// Add stores information about a persistent client in the index. c must be
// non-nil and contain UID.
func (ci *Index) Add(c *Persistent) {
if (c.UID == UID{}) {
panic("client must contain uid")
}
@@ -92,9 +92,9 @@ func (ci *index) add(c *Persistent) {
ci.uidToClient[c.UID] = c
}
// clashesUID returns existing persistent client with the same UID as c. Note
// ClashesUID returns existing persistent client with the same UID as c. Note
// that this is only possible when configuration contains duplicate fields.
func (ci *index) clashesUID(c *Persistent) (err error) {
func (ci *Index) ClashesUID(c *Persistent) (err error) {
p, ok := ci.uidToClient[c.UID]
if ok {
return fmt.Errorf("another client %q uses the same uid", p.Name)
@@ -103,9 +103,9 @@ func (ci *index) clashesUID(c *Persistent) (err error) {
return nil
}
// clashes returns an error if the index contains a different persistent client
// Clashes returns an error if the index contains a different persistent client
// with at least a single identifier contained by c. c must be non-nil.
func (ci *index) clashes(c *Persistent) (err error) {
func (ci *Index) Clashes(c *Persistent) (err error) {
if p := ci.clashesName(c); p != nil {
return fmt.Errorf("another client uses the same name %q", p.Name)
}
@@ -139,8 +139,8 @@ func (ci *index) clashes(c *Persistent) (err error) {
// clashesName returns existing persistent client with the same name as c or
// nil. c must be non-nil.
func (ci *index) clashesName(c *Persistent) (existing *Persistent) {
existing, ok := ci.findByName(c.Name)
func (ci *Index) clashesName(c *Persistent) (existing *Persistent) {
existing, ok := ci.FindByName(c.Name)
if !ok {
return nil
}
@@ -154,7 +154,7 @@ func (ci *index) clashesName(c *Persistent) (existing *Persistent) {
// clashesIP returns a previous client with the same IP address as c. c must be
// non-nil.
func (ci *index) clashesIP(c *Persistent) (p *Persistent, ip netip.Addr) {
func (ci *Index) clashesIP(c *Persistent) (p *Persistent, ip netip.Addr) {
for _, ip := range c.IPs {
existing, ok := ci.ipToUID[ip]
if ok && existing != c.UID {
@@ -167,7 +167,7 @@ func (ci *index) clashesIP(c *Persistent) (p *Persistent, ip netip.Addr) {
// clashesSubnet returns a previous client with the same subnet as c. c must be
// non-nil.
func (ci *index) clashesSubnet(c *Persistent) (p *Persistent, s netip.Prefix) {
func (ci *Index) clashesSubnet(c *Persistent) (p *Persistent, s netip.Prefix) {
for _, s = range c.Subnets {
var existing UID
var ok bool
@@ -193,7 +193,7 @@ func (ci *index) clashesSubnet(c *Persistent) (p *Persistent, s netip.Prefix) {
// clashesMAC returns a previous client with the same MAC address as c. c must
// be non-nil.
func (ci *index) clashesMAC(c *Persistent) (p *Persistent, mac net.HardwareAddr) {
func (ci *Index) clashesMAC(c *Persistent) (p *Persistent, mac net.HardwareAddr) {
for _, mac = range c.MACs {
k := macToKey(mac)
existing, ok := ci.macToUID[k]
@@ -205,9 +205,9 @@ func (ci *index) clashesMAC(c *Persistent) (p *Persistent, mac net.HardwareAddr)
return nil, nil
}
// find finds persistent client by string representation of the client ID, IP
// Find finds persistent client by string representation of the client ID, IP
// address, or MAC.
func (ci *index) find(id string) (c *Persistent, ok bool) {
func (ci *Index) Find(id string) (c *Persistent, ok bool) {
uid, found := ci.clientIDToUID[id]
if found {
return ci.uidToClient[uid], true
@@ -224,14 +224,14 @@ func (ci *index) find(id string) (c *Persistent, ok bool) {
mac, err := net.ParseMAC(id)
if err == nil {
return ci.findByMAC(mac)
return ci.FindByMAC(mac)
}
return nil, false
}
// findByName finds persistent client by name.
func (ci *index) findByName(name string) (c *Persistent, found bool) {
// FindByName finds persistent client by name.
func (ci *Index) FindByName(name string) (c *Persistent, found bool) {
uid, found := ci.nameToUID[name]
if found {
return ci.uidToClient[uid], true
@@ -241,7 +241,7 @@ func (ci *index) findByName(name string) (c *Persistent, found bool) {
}
// findByIP finds persistent client by IP address.
func (ci *index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
func (ci *Index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
uid, found := ci.ipToUID[ip]
if found {
return ci.uidToClient[uid], true
@@ -266,8 +266,8 @@ func (ci *index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
return nil, false
}
// findByMAC finds persistent client by MAC.
func (ci *index) findByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
// FindByMAC finds persistent client by MAC.
func (ci *Index) FindByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
k := macToKey(mac)
uid, found := ci.macToUID[k]
if found {
@@ -277,13 +277,13 @@ func (ci *index) findByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
return nil, false
}
// findByIPWithoutZone finds a persistent client by IP address without zone. It
// FindByIPWithoutZone finds a persistent client by IP address without zone. It
// strips the IPv6 zone index from the stored IP addresses before comparing,
// because querylog entries don't have it. See TODO on [querylog.logEntry.IP].
//
// Note that multiple clients can have the same IP address with different zones.
// Therefore, the result of this method is indeterminate.
func (ci *index) findByIPWithoutZone(ip netip.Addr) (c *Persistent) {
func (ci *Index) FindByIPWithoutZone(ip netip.Addr) (c *Persistent) {
if (ip == netip.Addr{}) {
return nil
}
@@ -297,9 +297,9 @@ func (ci *index) findByIPWithoutZone(ip netip.Addr) (c *Persistent) {
return nil
}
// remove removes information about persistent client from the index. c must be
// Delete removes information about persistent client from the index. c must be
// non-nil.
func (ci *index) remove(c *Persistent) {
func (ci *Index) Delete(c *Persistent) {
delete(ci.nameToUID, c.Name)
for _, id := range c.ClientIDs {
@@ -322,14 +322,24 @@ func (ci *index) remove(c *Persistent) {
delete(ci.uidToClient, c.UID)
}
// size returns the number of persistent clients.
func (ci *index) size() (n int) {
// Size returns the number of persistent clients.
func (ci *Index) Size() (n int) {
return len(ci.uidToClient)
}
// rangeByName is like [Index.Range] but sorts the persistent clients by name
// Range calls f for each persistent client, unless cont is false. The order is
// undefined.
func (ci *Index) Range(f func(c *Persistent) (cont bool)) {
for _, c := range ci.uidToClient {
if !f(c) {
return
}
}
}
// RangeByName is like [Index.Range] but sorts the persistent clients by name
// before iterating ensuring a predictable order.
func (ci *index) rangeByName(f func(c *Persistent) (cont bool)) {
func (ci *Index) RangeByName(f func(c *Persistent) (cont bool)) {
cs := maps.Values(ci.uidToClient)
slices.SortFunc(cs, func(a, b *Persistent) (n int) {
return strings.Compare(a.Name, b.Name)
@@ -342,10 +352,10 @@ func (ci *index) rangeByName(f func(c *Persistent) (cont bool)) {
}
}
// closeUpstreams closes upstream configurations of persistent clients.
func (ci *index) closeUpstreams() (err error) {
// CloseUpstreams closes upstream configurations of persistent clients.
func (ci *Index) CloseUpstreams() (err error) {
var errs []error
ci.rangeByName(func(c *Persistent) (cont bool) {
ci.RangeByName(func(c *Persistent) (cont bool) {
err = c.CloseUpstreams()
if err != nil {
errs = append(errs, err)

View File

@@ -11,18 +11,17 @@ import (
// newIDIndex is a helper function that returns a client index filled with
// persistent clients from the m. It also generates a UID for each client.
func newIDIndex(m []*Persistent) (ci *index) {
ci = newIndex()
func newIDIndex(m []*Persistent) (ci *Index) {
ci = NewIndex()
for _, c := range m {
c.UID = MustNewUID()
ci.add(c)
ci.Add(c)
}
return ci
}
// TODO(s.chzhen): Remove.
func TestClientIndex_Find(t *testing.T) {
const (
cliIPNone = "1.2.3.4"
@@ -110,7 +109,7 @@ func TestClientIndex_Find(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for _, id := range tc.ids {
c, ok := ci.find(id)
c, ok := ci.Find(id)
require.True(t, ok)
assert.Equal(t, tc.want, c)
@@ -119,7 +118,7 @@ func TestClientIndex_Find(t *testing.T) {
}
t.Run("not_found", func(t *testing.T) {
_, ok := ci.find(cliIPNone)
_, ok := ci.Find(cliIPNone)
assert.False(t, ok)
})
}
@@ -171,11 +170,11 @@ func TestClientIndex_Clashes(t *testing.T) {
clone := tc.client.ShallowClone()
clone.UID = MustNewUID()
err := ci.clashes(clone)
err := ci.Clashes(clone)
require.Error(t, err)
ci.remove(tc.client)
err = ci.clashes(clone)
ci.Delete(tc.client)
err = ci.Clashes(clone)
require.NoError(t, err)
})
}
@@ -293,7 +292,7 @@ func TestIndex_FindByIPWithoutZone(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
c := ci.findByIPWithoutZone(tc.ip.WithZone(""))
c := ci.FindByIPWithoutZone(tc.ip.WithZone(""))
require.Equal(t, tc.want, c)
})
}
@@ -339,7 +338,7 @@ func TestClientIndex_RangeByName(t *testing.T) {
ci := newIDIndex(tc.want)
var got []*Persistent
ci.rangeByName(func(c *Persistent) (cont bool) {
ci.RangeByName(func(c *Persistent) (cont bool) {
got = append(got, c)
return true

View File

@@ -12,7 +12,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
@@ -65,107 +64,53 @@ type Persistent struct {
// upstream must be used.
UpstreamConfig *proxy.CustomUpstreamConfig
// SafeSearch handles search engine hosts rewrites.
SafeSearch filtering.SafeSearch
// BlockedServices is the configuration of blocked services of a client. It
// must not be nil after initialization.
// BlockedServices is the configuration of blocked services of a client.
BlockedServices *filtering.BlockedServices
// Name of the persistent client. Must not be empty.
Name string
// Tags is a list of client tags that categorize the client.
Tags []string
// Upstreams is a list of custom upstream DNS servers for the client.
Tags []string
Upstreams []string
// IPs is a list of IP addresses that identify the client. The client must
// have at least one ID (IP, subnet, MAC, or ClientID).
IPs []netip.Addr
// Subnets identifying the client. The client must have at least one ID
// (IP, subnet, MAC, or ClientID).
//
// TODO(s.chzhen): Use netutil.Prefix.
Subnets []netip.Prefix
// MACs identifying the client. The client must have at least one ID (IP,
// subnet, MAC, or ClientID).
MACs []net.HardwareAddr
// ClientIDs identifying the client. The client must have at least one ID
// (IP, subnet, MAC, or ClientID).
Subnets []netip.Prefix
MACs []net.HardwareAddr
ClientIDs []string
// UID is the unique identifier of the persistent client.
UID UID
// UpstreamsCacheSize is the cache size for custom upstreams.
UpstreamsCacheSize uint32
// UpstreamsCacheEnabled specifies whether custom upstreams are used.
UpstreamsCacheSize uint32
UpstreamsCacheEnabled bool
// UseOwnSettings specifies whether custom filtering settings are used.
UseOwnSettings bool
// FilteringEnabled specifies whether filtering is enabled.
FilteringEnabled bool
// SafeBrowsingEnabled specifies whether safe browsing is enabled.
SafeBrowsingEnabled bool
// ParentalEnabled specifies whether parental control is enabled.
ParentalEnabled bool
// UseOwnBlockedServices specifies whether custom services are blocked.
UseOwnSettings bool
FilteringEnabled bool
SafeBrowsingEnabled bool
ParentalEnabled bool
UseOwnBlockedServices bool
IgnoreQueryLog bool
IgnoreStatistics bool
// IgnoreQueryLog specifies whether the client requests are logged.
IgnoreQueryLog bool
// IgnoreStatistics specifies whether the client requests are counted.
IgnoreStatistics bool
// SafeSearchConf is the safe search filtering configuration.
//
// TODO(d.kolyshev): Make SafeSearchConf a pointer.
SafeSearchConf filtering.SafeSearchConfig
}
// validate returns an error if persistent client information contains errors.
func (c *Persistent) validate(allTags *container.MapSet[string]) (err error) {
switch {
case c.Name == "":
return errors.Error("empty name")
case c.IDsLen() == 0:
return errors.Error("id required")
case c.UID == UID{}:
return errors.Error("uid required")
}
// SetTags sets the tags if they are known, otherwise logs an unknown tag.
func (c *Persistent) SetTags(tags []string, known *container.MapSet[string]) {
for _, t := range tags {
if !known.Has(t) {
log.Info("skipping unknown tag %q", t)
conf, err := proxy.ParseUpstreamsConfig(c.Upstreams, &upstream.Options{})
if err != nil {
return fmt.Errorf("invalid upstream servers: %w", err)
}
err = conf.Close()
if err != nil {
log.Error("client: closing upstream config: %s", err)
}
for _, t := range c.Tags {
if !allTags.Has(t) {
return fmt.Errorf("invalid tag: %q", t)
continue
}
c.Tags = append(c.Tags, t)
}
// TODO(s.chzhen): Move to the constructor.
slices.Sort(c.Tags)
return nil
}
// SetIDs parses a list of strings into typed fields and returns an error if

View File

@@ -1,16 +1,13 @@
package client
import (
"net/netip"
"testing"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPersistent_EqualIDs(t *testing.T) {
func TestPersistentClient_EqualIDs(t *testing.T) {
const (
ip = "0.0.0.0"
ip1 = "1.1.1.1"
@@ -125,69 +122,3 @@ func TestPersistent_EqualIDs(t *testing.T) {
})
}
}
func TestPersistent_Validate(t *testing.T) {
const (
allowedTag = "allowed_tag"
notAllowedTag = "not_allowed_tag"
)
allowedTags := container.NewMapSet(allowedTag)
testCases := []struct {
name string
cli *Persistent
wantErrMsg string
}{{
name: "success",
cli: &Persistent{
Name: "basic",
IPs: []netip.Addr{
netip.MustParseAddr("1.2.3.4"),
},
UID: MustNewUID(),
},
wantErrMsg: "",
}, {
name: "empty_name",
cli: &Persistent{
Name: "",
},
wantErrMsg: "empty name",
}, {
name: "no_id",
cli: &Persistent{
Name: "no_id",
},
wantErrMsg: "id required",
}, {
name: "no_uid",
cli: &Persistent{
Name: "no_uid",
IPs: []netip.Addr{
netip.MustParseAddr("1.2.3.4"),
},
},
wantErrMsg: "uid required",
}, {
name: "not_allowed_tag",
cli: &Persistent{
Name: "basic",
IPs: []netip.Addr{
netip.MustParseAddr("1.2.3.4"),
},
UID: MustNewUID(),
Tags: []string{
notAllowedTag,
},
},
wantErrMsg: `invalid tag: "` + notAllowedTag + `"`,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.cli.validate(allowedTags)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
})
}
}

View File

@@ -1,289 +0,0 @@
package client
import (
"fmt"
"net"
"net/netip"
"sync"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
)
// Config is the client storage configuration structure.
//
// TODO(s.chzhen): Expand.
type Config struct {
// AllowedTags is a list of all allowed client tags.
AllowedTags []string
}
// Storage contains information about persistent and runtime clients.
type Storage struct {
// allowedTags is a set of all allowed tags.
allowedTags *container.MapSet[string]
// mu protects indexes of persistent and runtime clients.
mu *sync.Mutex
// index contains information about persistent clients.
index *index
// runtimeIndex contains information about runtime clients.
//
// TODO(s.chzhen): Use it.
runtimeIndex *RuntimeIndex
}
// NewStorage returns initialized client storage. conf must not be nil.
func NewStorage(conf *Config) (s *Storage) {
allowedTags := container.NewMapSet(conf.AllowedTags...)
return &Storage{
allowedTags: allowedTags,
mu: &sync.Mutex{},
index: newIndex(),
runtimeIndex: NewRuntimeIndex(),
}
}
// Add stores persistent client information or returns an error.
func (s *Storage) Add(p *Persistent) (err error) {
defer func() { err = errors.Annotate(err, "adding client: %w") }()
err = p.validate(s.allowedTags)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
s.mu.Lock()
defer s.mu.Unlock()
err = s.index.clashesUID(p)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
err = s.index.clashes(p)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
s.index.add(p)
log.Debug("client storage: added %q: IDs: %q [%d]", p.Name, p.IDs(), s.index.size())
return nil
}
// FindByName finds persistent client by name. And returns its shallow copy.
func (s *Storage) FindByName(name string) (p *Persistent, ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
p, ok = s.index.findByName(name)
if ok {
return p.ShallowClone(), ok
}
return nil, false
}
// Find finds persistent client by string representation of the client ID, IP
// address, or MAC. And returns its shallow copy.
func (s *Storage) Find(id string) (p *Persistent, ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
p, ok = s.index.find(id)
if ok {
return p.ShallowClone(), ok
}
return nil, false
}
// FindLoose is like [Storage.Find] but it also tries to find a persistent
// client by IP address without zone. It strips the IPv6 zone index from the
// stored IP addresses before comparing, because querylog entries don't have it.
// See TODO on [querylog.logEntry.IP].
//
// Note that multiple clients can have the same IP address with different zones.
// Therefore, the result of this method is indeterminate.
func (s *Storage) FindLoose(ip netip.Addr, id string) (p *Persistent, ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
p, ok = s.index.find(id)
if ok {
return p.ShallowClone(), ok
}
p = s.index.findByIPWithoutZone(ip)
if p != nil {
return p.ShallowClone(), true
}
return nil, false
}
// FindByMAC finds persistent client by MAC and returns its shallow copy.
func (s *Storage) FindByMAC(mac net.HardwareAddr) (p *Persistent, ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
p, ok = s.index.findByMAC(mac)
if ok {
return p.ShallowClone(), ok
}
return nil, false
}
// RemoveByName removes persistent client information. ok is false if no such
// client exists by that name.
func (s *Storage) RemoveByName(name string) (ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
p, ok := s.index.findByName(name)
if !ok {
return false
}
if err := p.CloseUpstreams(); err != nil {
log.Error("client storage: removing client %q: %s", p.Name, err)
}
s.index.remove(p)
return true
}
// Update finds the stored persistent client by its name and updates its
// information from p.
func (s *Storage) Update(name string, p *Persistent) (err error) {
defer func() { err = errors.Annotate(err, "updating client: %w") }()
err = p.validate(s.allowedTags)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
s.mu.Lock()
defer s.mu.Unlock()
stored, ok := s.index.findByName(name)
if !ok {
return fmt.Errorf("client %q is not found", name)
}
// Client p has a newly generated UID, so replace it with the stored one.
//
// TODO(s.chzhen): Remove when frontend starts handling UIDs.
p.UID = stored.UID
err = s.index.clashes(p)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
s.index.remove(stored)
s.index.add(p)
return nil
}
// RangeByName calls f for each persistent client sorted by name, unless cont is
// false.
func (s *Storage) RangeByName(f func(c *Persistent) (cont bool)) {
s.mu.Lock()
defer s.mu.Unlock()
s.index.rangeByName(f)
}
// Size returns the number of persistent clients.
func (s *Storage) Size() (n int) {
s.mu.Lock()
defer s.mu.Unlock()
return s.index.size()
}
// CloseUpstreams closes upstream configurations of persistent clients.
func (s *Storage) CloseUpstreams() (err error) {
s.mu.Lock()
defer s.mu.Unlock()
return s.index.closeUpstreams()
}
// ClientRuntime returns a copy of the saved runtime client by ip. If no such
// client exists, returns nil.
//
// TODO(s.chzhen): Use it.
func (s *Storage) ClientRuntime(ip netip.Addr) (rc *Runtime) {
s.mu.Lock()
defer s.mu.Unlock()
return s.runtimeIndex.Client(ip)
}
// AddRuntime saves the runtime client information in the storage. IP address
// of a client must be unique. rc must not be nil.
//
// TODO(s.chzhen): Use it.
func (s *Storage) AddRuntime(rc *Runtime) {
s.mu.Lock()
defer s.mu.Unlock()
s.runtimeIndex.Add(rc)
}
// SizeRuntime returns the number of the runtime clients.
//
// TODO(s.chzhen): Use it.
func (s *Storage) SizeRuntime() (n int) {
s.mu.Lock()
defer s.mu.Unlock()
return s.runtimeIndex.Size()
}
// RangeRuntime calls f for each runtime client in an undefined order.
//
// TODO(s.chzhen): Use it.
func (s *Storage) RangeRuntime(f func(rc *Runtime) (cont bool)) {
s.mu.Lock()
defer s.mu.Unlock()
s.runtimeIndex.Range(f)
}
// DeleteRuntime removes the runtime client by ip.
//
// TODO(s.chzhen): Use it.
func (s *Storage) DeleteRuntime(ip netip.Addr) {
s.mu.Lock()
defer s.mu.Unlock()
s.runtimeIndex.Delete(ip)
}
// DeleteBySource removes all runtime clients that have information only from
// the specified source and returns the number of removed clients.
//
// TODO(s.chzhen): Use it.
func (s *Storage) DeleteBySource(src Source) (n int) {
s.mu.Lock()
defer s.mu.Unlock()
return s.runtimeIndex.DeleteBySource(src)
}

View File

@@ -1,481 +0,0 @@
package client_test
import (
"net"
"net/netip"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// newStorage is a helper function that returns a client storage filled with
// persistent clients from the m. It also generates a UID for each client.
func newStorage(tb testing.TB, m []*client.Persistent) (s *client.Storage) {
tb.Helper()
s = client.NewStorage(&client.Config{
AllowedTags: nil,
})
for _, c := range m {
c.UID = client.MustNewUID()
require.NoError(tb, s.Add(c))
}
return s
}
// mustParseMAC is wrapper around [net.ParseMAC] that panics if there is an
// error.
func mustParseMAC(s string) (mac net.HardwareAddr) {
mac, err := net.ParseMAC(s)
if err != nil {
panic(err)
}
return mac
}
func TestStorage_Add(t *testing.T) {
const (
existingName = "existing_name"
existingClientID = "existing_client_id"
)
var (
existingClientUID = client.MustNewUID()
existingIP = netip.MustParseAddr("1.2.3.4")
existingSubnet = netip.MustParsePrefix("1.2.3.0/24")
)
existingClient := &client.Persistent{
Name: existingName,
IPs: []netip.Addr{existingIP},
Subnets: []netip.Prefix{existingSubnet},
ClientIDs: []string{existingClientID},
UID: existingClientUID,
}
s := client.NewStorage(&client.Config{
AllowedTags: nil,
})
err := s.Add(existingClient)
require.NoError(t, err)
testCases := []struct {
name string
cli *client.Persistent
wantErrMsg string
}{{
name: "basic",
cli: &client.Persistent{
Name: "basic",
IPs: []netip.Addr{netip.MustParseAddr("1.1.1.1")},
UID: client.MustNewUID(),
},
wantErrMsg: "",
}, {
name: "duplicate_uid",
cli: &client.Persistent{
Name: "no_uid",
IPs: []netip.Addr{netip.MustParseAddr("2.2.2.2")},
UID: existingClientUID,
},
wantErrMsg: `adding client: another client "existing_name" uses the same uid`,
}, {
name: "duplicate_name",
cli: &client.Persistent{
Name: existingName,
IPs: []netip.Addr{netip.MustParseAddr("3.3.3.3")},
UID: client.MustNewUID(),
},
wantErrMsg: `adding client: another client uses the same name "existing_name"`,
}, {
name: "duplicate_ip",
cli: &client.Persistent{
Name: "duplicate_ip",
IPs: []netip.Addr{existingIP},
UID: client.MustNewUID(),
},
wantErrMsg: `adding client: another client "existing_name" uses the same IP "1.2.3.4"`,
}, {
name: "duplicate_subnet",
cli: &client.Persistent{
Name: "duplicate_subnet",
Subnets: []netip.Prefix{existingSubnet},
UID: client.MustNewUID(),
},
wantErrMsg: `adding client: another client "existing_name" ` +
`uses the same subnet "1.2.3.0/24"`,
}, {
name: "duplicate_client_id",
cli: &client.Persistent{
Name: "duplicate_client_id",
ClientIDs: []string{existingClientID},
UID: client.MustNewUID(),
},
wantErrMsg: `adding client: another client "existing_name" ` +
`uses the same ClientID "existing_client_id"`,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err = s.Add(tc.cli)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
})
}
}
func TestStorage_RemoveByName(t *testing.T) {
const (
existingName = "existing_name"
)
existingClient := &client.Persistent{
Name: existingName,
IPs: []netip.Addr{netip.MustParseAddr("1.2.3.4")},
UID: client.MustNewUID(),
}
s := client.NewStorage(&client.Config{
AllowedTags: nil,
})
err := s.Add(existingClient)
require.NoError(t, err)
testCases := []struct {
want assert.BoolAssertionFunc
name string
cliName string
}{{
name: "existing_client",
cliName: existingName,
want: assert.True,
}, {
name: "non_existing_client",
cliName: "non_existing_client",
want: assert.False,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.want(t, s.RemoveByName(tc.cliName))
})
}
t.Run("duplicate_remove", func(t *testing.T) {
s = client.NewStorage(&client.Config{
AllowedTags: nil,
})
err = s.Add(existingClient)
require.NoError(t, err)
assert.True(t, s.RemoveByName(existingName))
assert.False(t, s.RemoveByName(existingName))
})
}
func TestStorage_Find(t *testing.T) {
const (
cliIPNone = "1.2.3.4"
cliIP1 = "1.1.1.1"
cliIP2 = "2.2.2.2"
cliIPv6 = "1:2:3::4"
cliSubnet = "2.2.2.0/24"
cliSubnetIP = "2.2.2.222"
cliID = "client-id"
cliMAC = "11:11:11:11:11:11"
linkLocalIP = "fe80::abcd:abcd:abcd:ab%eth0"
linkLocalSubnet = "fe80::/16"
)
var (
clientWithBothFams = &client.Persistent{
Name: "client1",
IPs: []netip.Addr{
netip.MustParseAddr(cliIP1),
netip.MustParseAddr(cliIPv6),
},
}
clientWithSubnet = &client.Persistent{
Name: "client2",
IPs: []netip.Addr{netip.MustParseAddr(cliIP2)},
Subnets: []netip.Prefix{netip.MustParsePrefix(cliSubnet)},
}
clientWithMAC = &client.Persistent{
Name: "client_with_mac",
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
}
clientWithID = &client.Persistent{
Name: "client_with_id",
ClientIDs: []string{cliID},
}
clientLinkLocal = &client.Persistent{
Name: "client_link_local",
Subnets: []netip.Prefix{netip.MustParsePrefix(linkLocalSubnet)},
}
)
clients := []*client.Persistent{
clientWithBothFams,
clientWithSubnet,
clientWithMAC,
clientWithID,
clientLinkLocal,
}
s := newStorage(t, clients)
testCases := []struct {
want *client.Persistent
name string
ids []string
}{{
name: "ipv4_ipv6",
ids: []string{cliIP1, cliIPv6},
want: clientWithBothFams,
}, {
name: "ipv4_subnet",
ids: []string{cliIP2, cliSubnetIP},
want: clientWithSubnet,
}, {
name: "mac",
ids: []string{cliMAC},
want: clientWithMAC,
}, {
name: "client_id",
ids: []string{cliID},
want: clientWithID,
}, {
name: "client_link_local_subnet",
ids: []string{linkLocalIP},
want: clientLinkLocal,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for _, id := range tc.ids {
c, ok := s.Find(id)
require.True(t, ok)
assert.Equal(t, tc.want, c)
}
})
}
t.Run("not_found", func(t *testing.T) {
_, ok := s.Find(cliIPNone)
assert.False(t, ok)
})
}
func TestStorage_FindLoose(t *testing.T) {
const (
nonExistingClientID = "client_id"
)
var (
ip = netip.MustParseAddr("fe80::a098:7654:32ef:ff1")
ipWithZone = netip.MustParseAddr("fe80::1ff:fe23:4567:890a%eth2")
)
var (
clientNoZone = &client.Persistent{
Name: "client",
IPs: []netip.Addr{ip},
}
clientWithZone = &client.Persistent{
Name: "client_with_zone",
IPs: []netip.Addr{ipWithZone},
}
)
s := newStorage(
t,
[]*client.Persistent{
clientNoZone,
clientWithZone,
},
)
testCases := []struct {
ip netip.Addr
want assert.BoolAssertionFunc
wantCli *client.Persistent
name string
}{{
name: "without_zone",
ip: ip,
wantCli: clientNoZone,
want: assert.True,
}, {
name: "with_zone",
ip: ipWithZone,
wantCli: clientWithZone,
want: assert.True,
}, {
name: "zero_address",
ip: netip.Addr{},
wantCli: nil,
want: assert.False,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
c, ok := s.FindLoose(tc.ip.WithZone(""), nonExistingClientID)
assert.Equal(t, tc.wantCli, c)
tc.want(t, ok)
})
}
}
func TestStorage_Update(t *testing.T) {
const (
clientName = "client_name"
obstructingName = "obstructing_name"
obstructingClientID = "obstructing_client_id"
)
var (
obstructingIP = netip.MustParseAddr("1.2.3.4")
obstructingSubnet = netip.MustParsePrefix("1.2.3.0/24")
)
obstructingClient := &client.Persistent{
Name: obstructingName,
IPs: []netip.Addr{obstructingIP},
Subnets: []netip.Prefix{obstructingSubnet},
ClientIDs: []string{obstructingClientID},
}
clientToUpdate := &client.Persistent{
Name: clientName,
IPs: []netip.Addr{netip.MustParseAddr("1.1.1.1")},
}
testCases := []struct {
name string
cli *client.Persistent
wantErrMsg string
}{{
name: "basic",
cli: &client.Persistent{
Name: "basic",
IPs: []netip.Addr{netip.MustParseAddr("1.1.1.1")},
UID: client.MustNewUID(),
},
wantErrMsg: "",
}, {
name: "duplicate_name",
cli: &client.Persistent{
Name: obstructingName,
IPs: []netip.Addr{netip.MustParseAddr("3.3.3.3")},
UID: client.MustNewUID(),
},
wantErrMsg: `updating client: another client uses the same name "obstructing_name"`,
}, {
name: "duplicate_ip",
cli: &client.Persistent{
Name: "duplicate_ip",
IPs: []netip.Addr{obstructingIP},
UID: client.MustNewUID(),
},
wantErrMsg: `updating client: another client "obstructing_name" uses the same IP "1.2.3.4"`,
}, {
name: "duplicate_subnet",
cli: &client.Persistent{
Name: "duplicate_subnet",
Subnets: []netip.Prefix{obstructingSubnet},
UID: client.MustNewUID(),
},
wantErrMsg: `updating client: another client "obstructing_name" ` +
`uses the same subnet "1.2.3.0/24"`,
}, {
name: "duplicate_client_id",
cli: &client.Persistent{
Name: "duplicate_client_id",
ClientIDs: []string{obstructingClientID},
UID: client.MustNewUID(),
},
wantErrMsg: `updating client: another client "obstructing_name" ` +
`uses the same ClientID "obstructing_client_id"`,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
s := newStorage(
t,
[]*client.Persistent{
clientToUpdate,
obstructingClient,
},
)
err := s.Update(clientName, tc.cli)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
})
}
}
func TestStorage_RangeByName(t *testing.T) {
sortedClients := []*client.Persistent{{
Name: "clientA",
ClientIDs: []string{"A"},
}, {
Name: "clientB",
ClientIDs: []string{"B"},
}, {
Name: "clientC",
ClientIDs: []string{"C"},
}, {
Name: "clientD",
ClientIDs: []string{"D"},
}, {
Name: "clientE",
ClientIDs: []string{"E"},
}}
testCases := []struct {
name string
want []*client.Persistent
}{{
name: "basic",
want: sortedClients,
}, {
name: "nil",
want: nil,
}, {
name: "one_element",
want: sortedClients[:1],
}, {
name: "two_elements",
want: sortedClients[:2],
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
s := newStorage(t, tc.want)
var got []*client.Persistent
s.RangeByName(func(c *client.Persistent) (cont bool) {
got = append(got, c)
return true
})
assert.Equal(t, tc.want, got)
})
}
}

View File

@@ -2,12 +2,11 @@ package dhcpsvc
import (
"fmt"
"log/slog"
"slices"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/mapsutil"
"github.com/AdguardTeam/golibs/netutil"
"golang.org/x/exp/maps"
)
// Config is the configuration for the DHCP service.
@@ -16,9 +15,6 @@ type Config struct {
// interface identified by its name.
Interfaces map[string]*InterfaceConfig
// Logger will be used to log the DHCP events.
Logger *slog.Logger
// LocalDomainName is the top-level domain name to use for resolving DHCP
// clients' hostnames.
LocalDomainName string
@@ -42,44 +38,36 @@ type InterfaceConfig struct {
}
// Validate returns an error in conf if any.
//
// TODO(e.burkov): Unexport and rewrite the test.
func (conf *Config) Validate() (err error) {
switch {
case conf == nil:
return errNilConfig
case !conf.Enabled:
return nil
}
var errs []error
if conf.ICMPTimeout < 0 {
err = newMustErr("icmp timeout", "be non-negative", conf.ICMPTimeout)
errs = append(errs, err)
case conf.ICMPTimeout < 0:
return newMustErr("icmp timeout", "be non-negative", conf.ICMPTimeout)
}
err = netutil.ValidateDomainName(conf.LocalDomainName)
if err != nil {
// Don't wrap the error since it's informative enough as is.
errs = append(errs, err)
return err
}
if len(conf.Interfaces) == 0 {
errs = append(errs, errNoInterfaces)
return errors.Join(errs...)
return errNoInterfaces
}
mapsutil.SortedRange(conf.Interfaces, func(iface string, ic *InterfaceConfig) (ok bool) {
err = ic.validate()
if err != nil {
errs = append(errs, fmt.Errorf("interface %q: %w", iface, err))
ifaces := maps.Keys(conf.Interfaces)
slices.Sort(ifaces)
for _, iface := range ifaces {
if err = conf.Interfaces[iface].validate(); err != nil {
return fmt.Errorf("interface %q: %w", iface, err)
}
}
return true
})
return errors.Join(errs...)
return nil
}
// validate returns an error in ic, if any.

View File

@@ -23,8 +23,7 @@ func TestConfig_Validate(t *testing.T) {
}, {
name: "empty",
conf: &dhcpsvc.Config{
Enabled: true,
Interfaces: testInterfaceConf,
Enabled: true,
},
wantErrMsg: `bad domain name "": domain name is empty`,
}, {

View File

@@ -11,14 +11,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
)
const (
// keyInterface is the key for logging the network interface name.
keyInterface = "iface"
// keyFamily is the key for logging the handled address family.
keyFamily = "family"
)
// Interface is a DHCP service.
//
// TODO(e.burkov): Separate HostByIP, MACByIP, IPByHost into a separate
@@ -58,21 +50,21 @@ type Interface interface {
// AddLease adds a new DHCP lease. l must be valid. It returns an error if
// l already exists.
AddLease(ctx context.Context, l *Lease) (err error)
AddLease(l *Lease) (err error)
// UpdateStaticLease replaces an existing static DHCP lease. l must be
// valid. It returns an error if the lease with the given hardware address
// doesn't exist or if other values match another existing lease.
UpdateStaticLease(ctx context.Context, l *Lease) (err error)
UpdateStaticLease(l *Lease) (err error)
// RemoveLease removes an existing DHCP lease. l must be valid. It returns
// an error if there is no lease equal to l.
RemoveLease(ctx context.Context, l *Lease) (err error)
RemoveLease(l *Lease) (err error)
// Reset removes all the DHCP leases.
//
// TODO(e.burkov): If it's really needed?
Reset(ctx context.Context) (err error)
Reset() (err error)
}
// Empty is an [Interface] implementation that does nothing.
@@ -109,13 +101,13 @@ func (Empty) IPByHost(_ string) (ip netip.Addr) { return netip.Addr{} }
func (Empty) Leases() (leases []*Lease) { return nil }
// AddLease implements the [Interface] interface for Empty.
func (Empty) AddLease(_ context.Context, _ *Lease) (err error) { return nil }
func (Empty) AddLease(_ *Lease) (err error) { return nil }
// UpdateStaticLease implements the [Interface] interface for Empty.
func (Empty) UpdateStaticLease(_ context.Context, _ *Lease) (err error) { return nil }
func (Empty) UpdateStaticLease(_ *Lease) (err error) { return nil }
// RemoveLease implements the [Interface] interface for Empty.
func (Empty) RemoveLease(_ context.Context, _ *Lease) (err error) { return nil }
func (Empty) RemoveLease(_ *Lease) (err error) { return nil }
// Reset implements the [Interface] interface for Empty.
func (Empty) Reset(_ context.Context) (err error) { return nil }
func (Empty) Reset() (err error) { return nil }

View File

@@ -2,7 +2,6 @@ package dhcpsvc
import (
"fmt"
"log/slog"
"slices"
"time"
)
@@ -12,9 +11,6 @@ import (
//
// TODO(e.burkov): Add other methods as [DHCPServer] evolves.
type netInterface struct {
// logger logs the events related to the network interface.
logger *slog.Logger
// name is the name of the network interface.
name string

View File

@@ -1,17 +1,16 @@
package dhcpsvc
import (
"context"
"fmt"
"log/slog"
"net"
"net/netip"
"slices"
"sync"
"sync/atomic"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/mapsutil"
"golang.org/x/exp/maps"
)
// DHCPServer is a DHCP server for both IPv4 and IPv6 address families.
@@ -20,9 +19,6 @@ type DHCPServer struct {
// information about its clients.
enabled *atomic.Bool
// logger logs common DHCP events.
logger *slog.Logger
// localTLD is the top-level domain name to use for resolving DHCP clients'
// hostnames.
localTLD string
@@ -47,11 +43,8 @@ type DHCPServer struct {
// error if the given configuration can't be used.
//
// TODO(e.burkov): Use.
func New(ctx context.Context, conf *Config) (srv *DHCPServer, err error) {
l := conf.Logger
func New(conf *Config) (srv *DHCPServer, err error) {
if !conf.Enabled {
l.DebugContext(ctx, "disabled")
// TODO(e.burkov): Perhaps return [Empty]?
return nil, nil
}
@@ -59,26 +52,27 @@ func New(ctx context.Context, conf *Config) (srv *DHCPServer, err error) {
// TODO(e.burkov): Add validations scoped to the network interfaces set.
ifaces4 := make(netInterfacesV4, 0, len(conf.Interfaces))
ifaces6 := make(netInterfacesV6, 0, len(conf.Interfaces))
var errs []error
mapsutil.SortedRange(conf.Interfaces, func(name string, iface *InterfaceConfig) (cont bool) {
var i4 *netInterfaceV4
i4, err = newNetInterfaceV4(ctx, l, name, iface.IPv4)
ifaceNames := maps.Keys(conf.Interfaces)
slices.Sort(ifaceNames)
var i4 *netInterfaceV4
var i6 *netInterfaceV6
for _, ifaceName := range ifaceNames {
iface := conf.Interfaces[ifaceName]
i4, err = newNetInterfaceV4(ifaceName, iface.IPv4)
if err != nil {
errs = append(errs, fmt.Errorf("interface %q: ipv4: %w", name, err))
return nil, fmt.Errorf("interface %q: ipv4: %w", ifaceName, err)
} else if i4 != nil {
ifaces4 = append(ifaces4, i4)
}
i6 := newNetInterfaceV6(ctx, l, name, iface.IPv6)
i6 = newNetInterfaceV6(ifaceName, iface.IPv6)
if i6 != nil {
ifaces6 = append(ifaces6, i6)
}
return true
})
if err = errors.Join(errs...); err != nil {
return nil, err
}
enabled := &atomic.Bool{}
@@ -86,7 +80,6 @@ func New(ctx context.Context, conf *Config) (srv *DHCPServer, err error) {
srv = &DHCPServer{
enabled: enabled,
logger: l,
localTLD: conf.LocalDomainName,
leasesMu: &sync.RWMutex{},
leases: newLeaseIndex(),
@@ -166,7 +159,7 @@ func (srv *DHCPServer) IPByHost(host string) (ip netip.Addr) {
}
// Reset implements the [Interface] interface for *DHCPServer.
func (srv *DHCPServer) Reset(ctx context.Context) (err error) {
func (srv *DHCPServer) Reset() (err error) {
srv.leasesMu.Lock()
defer srv.leasesMu.Unlock()
@@ -178,13 +171,11 @@ func (srv *DHCPServer) Reset(ctx context.Context) (err error) {
}
srv.leases.clear()
srv.logger.DebugContext(ctx, "reset leases")
return nil
}
// AddLease implements the [Interface] interface for *DHCPServer.
func (srv *DHCPServer) AddLease(ctx context.Context, l *Lease) (err error) {
func (srv *DHCPServer) AddLease(l *Lease) (err error) {
defer func() { err = errors.Annotate(err, "adding lease: %w") }()
addr := l.IP
@@ -197,27 +188,13 @@ func (srv *DHCPServer) AddLease(ctx context.Context, l *Lease) (err error) {
srv.leasesMu.Lock()
defer srv.leasesMu.Unlock()
err = srv.leases.add(l, iface)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
iface.logger.DebugContext(
ctx, "added lease",
"hostname", l.Hostname,
"ip", l.IP,
"mac", l.HWAddr,
"static", l.IsStatic,
)
return nil
return srv.leases.add(l, iface)
}
// UpdateStaticLease implements the [Interface] interface for *DHCPServer.
//
// TODO(e.burkov): Support moving leases between interfaces.
func (srv *DHCPServer) UpdateStaticLease(ctx context.Context, l *Lease) (err error) {
func (srv *DHCPServer) UpdateStaticLease(l *Lease) (err error) {
defer func() { err = errors.Annotate(err, "updating static lease: %w") }()
addr := l.IP
@@ -230,25 +207,11 @@ func (srv *DHCPServer) UpdateStaticLease(ctx context.Context, l *Lease) (err err
srv.leasesMu.Lock()
defer srv.leasesMu.Unlock()
err = srv.leases.update(l, iface)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
iface.logger.DebugContext(
ctx, "updated lease",
"hostname", l.Hostname,
"ip", l.IP,
"mac", l.HWAddr,
"static", l.IsStatic,
)
return nil
return srv.leases.update(l, iface)
}
// RemoveLease implements the [Interface] interface for *DHCPServer.
func (srv *DHCPServer) RemoveLease(ctx context.Context, l *Lease) (err error) {
func (srv *DHCPServer) RemoveLease(l *Lease) (err error) {
defer func() { err = errors.Annotate(err, "removing lease: %w") }()
addr := l.IP
@@ -261,21 +224,7 @@ func (srv *DHCPServer) RemoveLease(ctx context.Context, l *Lease) (err error) {
srv.leasesMu.Lock()
defer srv.leasesMu.Unlock()
err = srv.leases.remove(l, iface)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
iface.logger.DebugContext(
ctx, "removed lease",
"hostname", l.Hostname,
"ip", l.IP,
"mac", l.HWAddr,
"static", l.IsStatic,
)
return nil
return srv.leases.remove(l, iface)
}
// ifaceForAddr returns the handled network interface for the given IP address,

View File

@@ -8,7 +8,6 @@ import (
"time"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -17,12 +16,6 @@ import (
// testLocalTLD is a common local TLD for tests.
const testLocalTLD = "local"
// testTimeout is a common timeout for tests and contexts.
const testTimeout time.Duration = 10 * time.Second
// discardLog is a logger to discard test output.
var discardLog = slogutil.NewDiscardLogger()
// testInterfaceConf is a common set of interface configurations for tests.
var testInterfaceConf = map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
@@ -110,7 +103,6 @@ func TestNew(t *testing.T) {
}{{
conf: &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
@@ -124,7 +116,6 @@ func TestNew(t *testing.T) {
}, {
conf: &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
@@ -138,7 +129,6 @@ func TestNew(t *testing.T) {
}, {
conf: &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
@@ -153,7 +143,6 @@ func TestNew(t *testing.T) {
}, {
conf: &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
@@ -167,22 +156,17 @@ func TestNew(t *testing.T) {
`range start 127.0.0.1 is not within 192.168.0.1/24`,
}}
ctx := testutil.ContextWithTimeout(t, testTimeout)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := dhcpsvc.New(ctx, tc.conf)
_, err := dhcpsvc.New(tc.conf)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
})
}
}
func TestDHCPServer_AddLease(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{
srv, err := dhcpsvc.New(&dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
})
@@ -202,7 +186,7 @@ func TestDHCPServer_AddLease(t *testing.T) {
mac2 := mustParseMAC(t, "06:05:04:03:02:01")
mac3 := mustParseMAC(t, "02:03:04:05:06:07")
require.NoError(t, srv.AddLease(ctx, &dhcpsvc.Lease{
require.NoError(t, srv.AddLease(&dhcpsvc.Lease{
Hostname: host1,
IP: ip1,
HWAddr: mac1,
@@ -277,17 +261,14 @@ func TestDHCPServer_AddLease(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.AddLease(ctx, tc.lease))
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.AddLease(tc.lease))
})
}
}
func TestDHCPServer_index(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{
srv, err := dhcpsvc.New(&dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
})
@@ -332,7 +313,7 @@ func TestDHCPServer_index(t *testing.T) {
IsStatic: true,
}}
for _, l := range leases {
require.NoError(t, srv.AddLease(ctx, l))
require.NoError(t, srv.AddLease(l))
}
t.Run("ip_idx", func(t *testing.T) {
@@ -361,11 +342,8 @@ func TestDHCPServer_index(t *testing.T) {
}
func TestDHCPServer_UpdateStaticLease(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{
srv, err := dhcpsvc.New(&dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
})
@@ -408,7 +386,7 @@ func TestDHCPServer_UpdateStaticLease(t *testing.T) {
IsStatic: true,
}}
for _, l := range leases {
require.NoError(t, srv.AddLease(ctx, l))
require.NoError(t, srv.AddLease(l))
}
testCases := []struct {
@@ -478,17 +456,14 @@ func TestDHCPServer_UpdateStaticLease(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.UpdateStaticLease(ctx, tc.lease))
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.UpdateStaticLease(tc.lease))
})
}
}
func TestDHCPServer_RemoveLease(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{
srv, err := dhcpsvc.New(&dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
})
@@ -520,7 +495,7 @@ func TestDHCPServer_RemoveLease(t *testing.T) {
IsStatic: true,
}}
for _, l := range leases {
require.NoError(t, srv.AddLease(ctx, l))
require.NoError(t, srv.AddLease(l))
}
testCases := []struct {
@@ -571,7 +546,7 @@ func TestDHCPServer_RemoveLease(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.RemoveLease(ctx, tc.lease))
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.RemoveLease(tc.lease))
})
}
@@ -579,11 +554,8 @@ func TestDHCPServer_RemoveLease(t *testing.T) {
}
func TestDHCPServer_Reset(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{
srv, err := dhcpsvc.New(&dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
})
@@ -612,12 +584,12 @@ func TestDHCPServer_Reset(t *testing.T) {
}}
for _, l := range leases {
require.NoError(t, srv.AddLease(ctx, l))
require.NoError(t, srv.AddLease(l))
}
require.Len(t, srv.Leases(), len(leases))
require.NoError(t, srv.Reset(ctx))
require.NoError(t, srv.Reset())
assert.Empty(t, srv.Leases())
}

View File

@@ -1,15 +1,13 @@
package dhcpsvc
import (
"context"
"fmt"
"log/slog"
"net"
"net/netip"
"slices"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/google/gopacket/layers"
)
@@ -45,133 +43,25 @@ type IPv4Config struct {
}
// validate returns an error in conf if any.
func (c *IPv4Config) validate() (err error) {
if c == nil {
func (conf *IPv4Config) validate() (err error) {
switch {
case conf == nil:
return errNilConfig
} else if !c.Enabled {
case !conf.Enabled:
return nil
case !conf.GatewayIP.Is4():
return newMustErr("gateway ip", "be a valid ipv4", conf.GatewayIP)
case !conf.SubnetMask.Is4():
return newMustErr("subnet mask", "be a valid ipv4 cidr mask", conf.SubnetMask)
case !conf.RangeStart.Is4():
return newMustErr("range start", "be a valid ipv4", conf.RangeStart)
case !conf.RangeEnd.Is4():
return newMustErr("range end", "be a valid ipv4", conf.RangeEnd)
case conf.LeaseDuration <= 0:
return newMustErr("lease duration", "be less than %d", conf.LeaseDuration)
default:
return nil
}
var errs []error
if !c.GatewayIP.Is4() {
err = newMustErr("gateway ip", "be a valid ipv4", c.GatewayIP)
errs = append(errs, err)
}
if !c.SubnetMask.Is4() {
err = newMustErr("subnet mask", "be a valid ipv4 cidr mask", c.SubnetMask)
errs = append(errs, err)
}
if !c.RangeStart.Is4() {
err = newMustErr("range start", "be a valid ipv4", c.RangeStart)
errs = append(errs, err)
}
if !c.RangeEnd.Is4() {
err = newMustErr("range end", "be a valid ipv4", c.RangeEnd)
errs = append(errs, err)
}
if c.LeaseDuration <= 0 {
err = newMustErr("icmp timeout", "be positive", c.LeaseDuration)
errs = append(errs, err)
}
return errors.Join(errs...)
}
// netInterfaceV4 is a DHCP interface for IPv4 address family.
type netInterfaceV4 struct {
// gateway is the IP address of the network gateway.
gateway netip.Addr
// subnet is the network subnet.
subnet netip.Prefix
// addrSpace is the IPv4 address space allocated for leasing.
addrSpace ipRange
// implicitOpts are the options listed in Appendix A of RFC 2131 and
// initialized with default values. It must not have intersections with
// explicitOpts.
implicitOpts layers.DHCPOptions
// explicitOpts are the user-configured options. It must not have
// intersections with implicitOpts.
explicitOpts layers.DHCPOptions
// netInterface is embedded here to provide some common network interface
// logic.
netInterface
}
// newNetInterfaceV4 creates a new DHCP interface for IPv4 address family with
// the given configuration. It returns an error if the given configuration
// can't be used.
func newNetInterfaceV4(
ctx context.Context,
l *slog.Logger,
name string,
conf *IPv4Config,
) (i *netInterfaceV4, err error) {
l = l.With(
keyInterface, name,
keyFamily, netutil.AddrFamilyIPv4,
)
if !conf.Enabled {
l.DebugContext(ctx, "disabled")
return nil, nil
}
maskLen, _ := net.IPMask(conf.SubnetMask.AsSlice()).Size()
subnet := netip.PrefixFrom(conf.GatewayIP, maskLen)
switch {
case !subnet.Contains(conf.RangeStart):
return nil, fmt.Errorf("range start %s is not within %s", conf.RangeStart, subnet)
case !subnet.Contains(conf.RangeEnd):
return nil, fmt.Errorf("range end %s is not within %s", conf.RangeEnd, subnet)
}
addrSpace, err := newIPRange(conf.RangeStart, conf.RangeEnd)
if err != nil {
return nil, err
} else if addrSpace.contains(conf.GatewayIP) {
return nil, fmt.Errorf("gateway ip %s in the ip range %s", conf.GatewayIP, addrSpace)
}
i = &netInterfaceV4{
gateway: conf.GatewayIP,
subnet: subnet,
addrSpace: addrSpace,
netInterface: netInterface{
name: name,
leaseTTL: conf.LeaseDuration,
logger: l,
},
}
i.implicitOpts, i.explicitOpts = conf.options(ctx, l)
return i, nil
}
// netInterfacesV4 is a slice of network interfaces of IPv4 address family.
type netInterfacesV4 []*netInterfaceV4
// find returns the first network interface within ifaces containing ip. It
// returns false if there is no such interface.
func (ifaces netInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok bool) {
i := slices.IndexFunc(ifaces, func(iface *netInterfaceV4) (contains bool) {
return iface.subnet.Contains(ip)
})
if i < 0 {
return nil, false
}
return &ifaces[i].netInterface, true
}
// options returns the implicit and explicit options for the interface. The two
@@ -179,14 +69,14 @@ func (ifaces netInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok bool
// values.
//
// TODO(e.burkov): DRY with the IPv6 version.
func (c *IPv4Config) options(ctx context.Context, l *slog.Logger) (imp, exp layers.DHCPOptions) {
func (conf *IPv4Config) options() (implicit, explicit layers.DHCPOptions) {
// Set default values of host configuration parameters listed in Appendix A
// of RFC-2131.
imp = layers.DHCPOptions{
implicit = layers.DHCPOptions{
// Values From Configuration
layers.NewDHCPOption(layers.DHCPOptSubnetMask, c.SubnetMask.AsSlice()),
layers.NewDHCPOption(layers.DHCPOptRouter, c.GatewayIP.AsSlice()),
layers.NewDHCPOption(layers.DHCPOptSubnetMask, conf.SubnetMask.AsSlice()),
layers.NewDHCPOption(layers.DHCPOptRouter, conf.GatewayIP.AsSlice()),
// IP-Layer Per Host
@@ -338,29 +228,110 @@ func (c *IPv4Config) options(ctx context.Context, l *slog.Logger) (imp, exp laye
// See https://datatracker.ietf.org/doc/html/rfc1122#section-4.2.3.6.
layers.NewDHCPOption(layers.DHCPOptTCPKeepAliveGarbage, []byte{0x1}),
}
slices.SortFunc(imp, compareV4OptionCodes)
slices.SortFunc(implicit, compareV4OptionCodes)
// Set values for explicitly configured options.
for _, o := range c.Options {
i, found := slices.BinarySearchFunc(imp, o, compareV4OptionCodes)
for _, exp := range conf.Options {
i, found := slices.BinarySearchFunc(implicit, exp, compareV4OptionCodes)
if found {
imp = slices.Delete(imp, i, i+1)
implicit = slices.Delete(implicit, i, i+1)
}
i, found = slices.BinarySearchFunc(exp, o, compareV4OptionCodes)
if o.Length > 0 {
exp = slices.Insert(exp, i, o)
i, found = slices.BinarySearchFunc(explicit, exp, compareV4OptionCodes)
if exp.Length > 0 {
explicit = slices.Insert(explicit, i, exp)
} else if found {
exp = slices.Delete(exp, i, i+1)
explicit = slices.Delete(explicit, i, i+1)
}
}
l.DebugContext(ctx, "options", "implicit", imp, "explicit", exp)
log.Debug("dhcpsvc: v4: implicit options: %s", implicit)
log.Debug("dhcpsvc: v4: explicit options: %s", explicit)
return imp, exp
return implicit, explicit
}
// compareV4OptionCodes compares option codes of a and b.
func compareV4OptionCodes(a, b layers.DHCPOption) (res int) {
return int(a.Type) - int(b.Type)
}
// netInterfaceV4 is a DHCP interface for IPv4 address family.
type netInterfaceV4 struct {
// gateway is the IP address of the network gateway.
gateway netip.Addr
// subnet is the network subnet.
subnet netip.Prefix
// addrSpace is the IPv4 address space allocated for leasing.
addrSpace ipRange
// implicitOpts are the options listed in Appendix A of RFC 2131 and
// initialized with default values. It must not have intersections with
// explicitOpts.
implicitOpts layers.DHCPOptions
// explicitOpts are the user-configured options. It must not have
// intersections with implicitOpts.
explicitOpts layers.DHCPOptions
// netInterface is embedded here to provide some common network interface
// logic.
netInterface
}
// newNetInterfaceV4 creates a new DHCP interface for IPv4 address family with
// the given configuration. It returns an error if the given configuration
// can't be used.
func newNetInterfaceV4(name string, conf *IPv4Config) (i *netInterfaceV4, err error) {
if !conf.Enabled {
return nil, nil
}
maskLen, _ := net.IPMask(conf.SubnetMask.AsSlice()).Size()
subnet := netip.PrefixFrom(conf.GatewayIP, maskLen)
switch {
case !subnet.Contains(conf.RangeStart):
return nil, fmt.Errorf("range start %s is not within %s", conf.RangeStart, subnet)
case !subnet.Contains(conf.RangeEnd):
return nil, fmt.Errorf("range end %s is not within %s", conf.RangeEnd, subnet)
}
addrSpace, err := newIPRange(conf.RangeStart, conf.RangeEnd)
if err != nil {
return nil, err
} else if addrSpace.contains(conf.GatewayIP) {
return nil, fmt.Errorf("gateway ip %s in the ip range %s", conf.GatewayIP, addrSpace)
}
i = &netInterfaceV4{
gateway: conf.GatewayIP,
subnet: subnet,
addrSpace: addrSpace,
netInterface: netInterface{
name: name,
leaseTTL: conf.LeaseDuration,
},
}
i.implicitOpts, i.explicitOpts = conf.options()
return i, nil
}
// netInterfacesV4 is a slice of network interfaces of IPv4 address family.
type netInterfacesV4 []*netInterfaceV4
// find returns the first network interface within ifaces containing ip. It
// returns false if there is no such interface.
func (ifaces netInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok bool) {
i := slices.IndexFunc(ifaces, func(iface *netInterfaceV4) (contains bool) {
return iface.subnet.Contains(ip)
})
if i < 0 {
return nil, false
}
return &ifaces[i].netInterface, true
}

View File

@@ -3,10 +3,7 @@ package dhcpsvc
import (
"net/netip"
"testing"
"time"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/google/gopacket/layers"
"github.com/stretchr/testify/assert"
)
@@ -78,12 +75,9 @@ func TestIPv4Config_Options(t *testing.T) {
wantExplicit: layers.DHCPOptions{opt1},
}}
ctx := testutil.ContextWithTimeout(t, time.Second)
l := slogutil.NewDiscardLogger()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
imp, exp := tc.conf.options(ctx, l)
imp, exp := tc.conf.options()
assert.Equal(t, tc.wantExplicit, exp)
for c := range exp {

View File

@@ -1,14 +1,12 @@
package dhcpsvc
import (
"context"
"fmt"
"log/slog"
"net/netip"
"slices"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/google/gopacket/layers"
)
@@ -40,26 +38,50 @@ type IPv6Config struct {
}
// validate returns an error in conf if any.
func (c *IPv6Config) validate() (err error) {
if c == nil {
func (conf *IPv6Config) validate() (err error) {
switch {
case conf == nil:
return errNilConfig
} else if !c.Enabled {
case !conf.Enabled:
return nil
case !conf.RangeStart.Is6():
return fmt.Errorf("range start %s should be a valid ipv6", conf.RangeStart)
case conf.LeaseDuration <= 0:
return fmt.Errorf("lease duration %s must be positive", conf.LeaseDuration)
default:
return nil
}
}
var errs []error
// options returns the implicit and explicit options for the interface. The two
// lists are disjoint and the implicit options are initialized with default
// values.
//
// TODO(e.burkov): Add implicit options according to RFC.
func (conf *IPv6Config) options() (implicit, explicit layers.DHCPv6Options) {
// Set default values of host configuration parameters listed in RFC 8415.
implicit = layers.DHCPv6Options{}
slices.SortFunc(implicit, compareV6OptionCodes)
if !c.RangeStart.Is6() {
err = fmt.Errorf("range start %s should be a valid ipv6", c.RangeStart)
errs = append(errs, err)
// Set values for explicitly configured options.
for _, exp := range conf.Options {
i, found := slices.BinarySearchFunc(implicit, exp, compareV6OptionCodes)
if found {
implicit = slices.Delete(implicit, i, i+1)
}
explicit = append(explicit, exp)
}
if c.LeaseDuration <= 0 {
err = fmt.Errorf("lease duration %s must be positive", c.LeaseDuration)
errs = append(errs, err)
}
log.Debug("dhcpsvc: v6: implicit options: %s", implicit)
log.Debug("dhcpsvc: v6: explicit options: %s", explicit)
return errors.Join(errs...)
return implicit, explicit
}
// compareV6OptionCodes compares option codes of a and b.
func compareV6OptionCodes(a, b layers.DHCPv6Option) (res int) {
return int(a.Code) - int(b.Code)
}
// netInterfaceV6 is a DHCP interface for IPv6 address family.
@@ -94,16 +116,8 @@ type netInterfaceV6 struct {
// the given configuration.
//
// TODO(e.burkov): Validate properly.
func newNetInterfaceV6(
ctx context.Context,
l *slog.Logger,
name string,
conf *IPv6Config,
) (i *netInterfaceV6) {
l = l.With(keyInterface, name, keyFamily, netutil.AddrFamilyIPv6)
func newNetInterfaceV6(name string, conf *IPv6Config) (i *netInterfaceV6) {
if !conf.Enabled {
l.DebugContext(ctx, "disabled")
return nil
}
@@ -112,12 +126,11 @@ func newNetInterfaceV6(
netInterface: netInterface{
name: name,
leaseTTL: conf.LeaseDuration,
logger: l,
},
raSLAACOnly: conf.RASLAACOnly,
raAllowSLAAC: conf.RAAllowSLAAC,
}
i.implicitOpts, i.explicitOpts = conf.options(ctx, l)
i.implicitOpts, i.explicitOpts = conf.options()
return i
}
@@ -146,33 +159,3 @@ func (ifaces netInterfacesV6) find(ip netip.Addr) (iface6 *netInterface, ok bool
return &ifaces[i].netInterface, true
}
// options returns the implicit and explicit options for the interface. The two
// lists are disjoint and the implicit options are initialized with default
// values.
//
// TODO(e.burkov): Add implicit options according to RFC.
func (c *IPv6Config) options(ctx context.Context, l *slog.Logger) (imp, exp layers.DHCPv6Options) {
// Set default values of host configuration parameters listed in RFC 8415.
imp = layers.DHCPv6Options{}
slices.SortFunc(imp, compareV6OptionCodes)
// Set values for explicitly configured options.
for _, e := range c.Options {
i, found := slices.BinarySearchFunc(imp, e, compareV6OptionCodes)
if found {
imp = slices.Delete(imp, i, i+1)
}
exp = append(exp, e)
}
l.DebugContext(ctx, "options", "implicit", imp, "explicit", exp)
return imp, exp
}
// compareV6OptionCodes compares option codes of a and b.
func compareV6OptionCodes(a, b layers.DHCPv6Option) (res int) {
return int(a.Code) - int(b.Code)
}

View File

@@ -6,6 +6,7 @@ import (
"os"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/AdGuardHome/internal/ipset"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
@@ -34,7 +35,7 @@ func (c *ipsetCtx) init(ipsetConf []string) (err error) {
log.Info("ipset: warning: cannot initialize: %s", err)
return nil
} else if errors.Is(err, errors.ErrUnsupported) {
} else if unsupErr := (&aghos.UnsupportedError{}); errors.As(err, &unsupErr) {
log.Info("ipset: warning: %s", err)
return nil

View File

@@ -532,7 +532,7 @@ var blockedServices = []blockedService{{
},
}, {
ID: "cloudflare",
Name: "Cloudflare",
Name: "CloudFlare",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M38 10.813l-.906 3.78-1.907-3.405v1.718c2.899 2.301 4.926 5.79 5.126 9.688.699-.2 1.3-.188 2-.188 1.374 0 2.667.297 3.812.875l-1.031-.593 3.812-.875-3.812-.907L48.5 19h-3.813l2.813-2.688-3.688 1.094 2-3.312-3.312 2 1.094-3.688-2.688 2.781.094-3.874-2 3.28zM27 11c-5 0-9.414 2.992-11.313 7.594-.699-.399-1.687-.688-2.687-.688-3.2 0-5.906 2.606-5.906 5.907v.5c-3.899.3-7.094 3.68-7.094 7.78 0 .802.113 1.52.313 2.22.101.398.5.687 1 .687h47c.398 0 .675-.195.874-.594.5-1.101.813-2.207.813-3.406 0-4.2-3.488-7.594-7.688-7.594-.8 0-1.511.082-2.312.282l4.906 6.625-5.5-4.5L22 29.593l15.094-4.905L28.5 21.5l10.688 1.813v-.125C39.188 16.488 33.699 11 27 11zm19.781 12.656c.434.274.844.586 1.219.938h.5z\" /></svg>"),
Rules: []string{
"||argotunnel.com^",
@@ -709,7 +709,7 @@ var blockedServices = []blockedService{{
},
}, {
ID: "ebay",
Name: "eBay",
Name: "EBay",
IconSVG: []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 50 50\"><path d=\"M 12.601563 13.671875 L 12.636719 24.058594 C 12.632813 22.457031 12.128906 18.101563 6.464844 18.097656 C 0.210938 18.097656 -0.03125 22.964844 0.00390625 24.230469 C 0.00390625 24.230469 -0.304688 29.917969 6.3125 29.917969 C 11.996094 29.917969 12.277344 26.347656 12.277344 26.347656 L 9.664063 26.355469 C 9.664063 26.355469 9.152344 28.320313 6.320313 28.265625 C 2.683594 28.199219 2.546875 24.675781 2.546875 24.675781 L 12.621094 24.675781 C 12.621094 24.675781 12.628906 24.566406 12.636719 24.425781 L 12.644531 26.960938 C 12.644531 26.960938 12.628906 28.507813 12.535156 29.53125 L 14.984375 29.53125 L 15.089844 28.039063 C 15.089844 28.039063 16.230469 29.917969 19.566406 29.917969 C 22.902344 29.917969 25.535156 27.863281 25.609375 24.050781 C 25.675781 20.242188 22.761719 18.117188 19.617188 18.097656 C 16.472656 18.082031 15.121094 19.960938 15.121094 19.960938 L 15.121094 13.671875 Z M 31.054688 18.046875 C 29.566406 18.097656 26.539063 18.558594 26.132813 21.460938 L 28.796875 21.460938 C 28.796875 21.460938 29 19.6875 31.703125 19.738281 C 34.257813 19.785156 34.722656 21.039063 34.707031 22.578125 C 34.707031 22.578125 32.519531 22.585938 31.785156 22.59375 C 30.46875 22.597656 25.863281 22.742188 25.433594 25.550781 C 24.917969 28.890625 27.898438 29.933594 30.230469 29.917969 C 32.5625 29.90625 33.890625 29.207031 34.878906 27.953125 L 34.984375 29.511719 L 37.300781 29.496094 C 37.300781 29.496094 37.242188 28.628906 37.25 26.90625 C 37.257813 25.1875 37.308594 23.65625 37.25 22.574219 C 37.183594 21.316406 37.304688 18.285156 31.875 18.0625 C 31.875 18.0625 31.550781 18.03125 31.054688 18.046875 Z M 35.871094 18.519531 L 41.675781 29.496094 L 39.4375 33.71875 L 42.265625 33.71875 L 50 18.519531 L 47.359375 18.519531 L 43.074219 27.046875 L 38.796875 18.519531 Z M 6.402344 19.765625 C 9.984375 19.761719 9.984375 22.949219 9.984375 22.949219 L 2.628906 22.949219 C 2.628906 22.949219 2.804688 19.765625 6.402344 19.765625 Z M 19.035156 19.800781 C 23.078125 19.699219 22.949219 24.097656 22.949219 24.097656 C 22.949219 24.097656 23.011719 28.167969 19.042969 28.21875 C 15.070313 28.269531 15.136719 24.011719 15.136719 24.011719 C 15.136719 24.011719 14.992188 19.90625 19.035156 19.800781 Z M 34.734375 24.265625 C 34.734375 24.269531 35.195313 28.371094 30.664063 28.3125 C 30.664063 28.3125 28.136719 28.3125 27.988281 26.296875 C 27.832031 24.140625 31.875 24.269531 31.875 24.269531 Z\" /></svg>"),
Rules: []string{
"|ebay-*.s3-us-west-1.amazonaws.com^",

View File

@@ -5,6 +5,7 @@ import (
"net"
"net/netip"
"slices"
"strings"
"sync"
"time"
@@ -15,10 +16,10 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/log"
@@ -44,12 +45,14 @@ type DHCP interface {
// clientsContainer is the storage of all runtime and persistent clients.
type clientsContainer struct {
// storage stores information about persistent clients.
storage *client.Storage
// clientIndex stores information about persistent clients.
clientIndex *client.Index
// runtimeIndex stores information about runtime clients.
runtimeIndex *client.RuntimeIndex
allTags *container.MapSet[string]
// dhcp is the DHCP service implementation.
dhcp DHCP
@@ -101,15 +104,15 @@ func (clients *clientsContainer) Init(
filteringConf *filtering.Config,
) (err error) {
// TODO(s.chzhen): Refactor it.
if clients.storage != nil {
if clients.clientIndex != nil {
return errors.Error("clients container already initialized")
}
clients.runtimeIndex = client.NewRuntimeIndex()
clients.storage = client.NewStorage(&client.Config{
AllowedTags: clientTags,
})
clients.clientIndex = client.NewIndex()
clients.allTags = container.NewMapSet(clientTags...)
// TODO(e.burkov): Use [dhcpsvc] implementation when it's ready.
clients.dhcp = dhcpServer
@@ -214,6 +217,7 @@ type clientObject struct {
// toPersistent returns an initialized persistent client if there are no errors.
func (o *clientObject) toPersistent(
filteringConf *filtering.Config,
allTags *container.MapSet[string],
) (cli *client.Persistent, err error) {
cli = &client.Persistent{
Name: o.Name,
@@ -257,12 +261,6 @@ func (o *clientObject) toPersistent(
}
}
if o.BlockedServices == nil {
o.BlockedServices = &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
}
}
err = o.BlockedServices.Validate()
if err != nil {
return nil, fmt.Errorf("init blocked services %q: %w", cli.Name, err)
@@ -270,7 +268,7 @@ func (o *clientObject) toPersistent(
cli.BlockedServices = o.BlockedServices.Clone()
cli.Tags = slices.Clone(o.Tags)
cli.SetTags(o.Tags, allTags)
return cli, nil
}
@@ -283,14 +281,22 @@ func (clients *clientsContainer) addFromConfig(
) (err error) {
for i, o := range objects {
var cli *client.Persistent
cli, err = o.toPersistent(filteringConf)
cli, err = o.toPersistent(filteringConf, clients.allTags)
if err != nil {
return fmt.Errorf("clients: init persistent client at index %d: %w", i, err)
}
err = clients.storage.Add(cli)
// TODO(s.chzhen): Consider moving to the client index constructor.
err = clients.clientIndex.ClashesUID(cli)
if err != nil {
return fmt.Errorf("adding client %q at index %d: %w", cli.Name, i, err)
return fmt.Errorf("adding client %s at index %d: %w", cli.Name, i, err)
}
err = clients.add(cli)
if err != nil {
// TODO(s.chzhen): Return an error instead of logging if more
// stringent requirements are implemented.
log.Error("clients: adding client %s at index %d: %s", cli.Name, i, err)
}
}
@@ -303,8 +309,8 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
clients.lock.Lock()
defer clients.lock.Unlock()
objs = make([]*clientObject, 0, clients.storage.Size())
clients.storage.RangeByName(func(cli *client.Persistent) (cont bool) {
objs = make([]*clientObject, 0, clients.clientIndex.Size())
clients.clientIndex.Range(func(cli *client.Persistent) (cont bool) {
objs = append(objs, &clientObject{
Name: cli.Name,
@@ -331,6 +337,14 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
return true
})
// Maps aren't guaranteed to iterate in the same order each time, so the
// above loop can generate different orderings when writing to the config
// file: this produces lots of diffs in config files, so sort objects by
// name before writing.
slices.SortStableFunc(objs, func(a, b *clientObject) (res int) {
return strings.Compare(a.Name, b.Name)
})
return objs
}
@@ -348,7 +362,7 @@ func (clients *clientsContainer) periodicUpdate() {
// clientSource checks if client with this IP address already exists and returns
// the source which updated it last. It returns [client.SourceNone] if the
// client doesn't exist. Note that it is only used in tests.
// client doesn't exist.
func (clients *clientsContainer) clientSource(ip netip.Addr) (src client.Source) {
clients.lock.Lock()
defer clients.lock.Unlock()
@@ -407,8 +421,12 @@ func (clients *clientsContainer) clientOrArtificial(
}
}()
cli, ok := clients.storage.FindLoose(ip, id)
if ok {
cli, ok := clients.find(id)
if !ok {
cli = clients.clientIndex.FindByIPWithoutZone(ip)
}
if cli != nil {
return &querylog.Client{
Name: cli.Name,
IgnoreQueryLog: cli.IgnoreQueryLog,
@@ -440,7 +458,7 @@ func (clients *clientsContainer) find(id string) (c *client.Persistent, ok bool)
return nil, false
}
return c, true
return c.ShallowClone(), true
}
// shouldCountClient is a wrapper around [clientsContainer.find] to make it a
@@ -514,7 +532,7 @@ func (clients *clientsContainer) UpstreamConfigByID(
// findLocked searches for a client by its ID. clients.lock is expected to be
// locked.
func (clients *clientsContainer) findLocked(id string) (c *client.Persistent, ok bool) {
c, ok = clients.storage.Find(id)
c, ok = clients.clientIndex.Find(id)
if ok {
return c, true
}
@@ -536,7 +554,7 @@ func (clients *clientsContainer) findDHCP(ip netip.Addr) (c *client.Persistent,
return nil, false
}
return clients.storage.FindByMAC(foundMAC)
return clients.clientIndex.FindByMAC(foundMAC)
}
// runtimeClient returns a runtime client from internal index. Note that it
@@ -570,6 +588,114 @@ func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *client.Ru
return rc
}
// check validates the client. It also sorts the client tags.
func (clients *clientsContainer) check(c *client.Persistent) (err error) {
switch {
case c == nil:
return errors.Error("client is nil")
case c.Name == "":
return errors.Error("invalid name")
case c.IDsLen() == 0:
return errors.Error("id required")
default:
// Go on.
}
for _, t := range c.Tags {
if !clients.allTags.Has(t) {
return fmt.Errorf("invalid tag: %q", t)
}
}
// TODO(s.chzhen): Move to the constructor.
slices.Sort(c.Tags)
_, err = proxy.ParseUpstreamsConfig(c.Upstreams, &upstream.Options{})
if err != nil {
return fmt.Errorf("invalid upstream servers: %w", err)
}
return nil
}
// add adds a persistent client or returns an error.
func (clients *clientsContainer) add(c *client.Persistent) (err error) {
err = clients.check(c)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
clients.lock.Lock()
defer clients.lock.Unlock()
err = clients.clientIndex.Clashes(c)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
clients.addLocked(c)
log.Debug("clients: added %q: ID:%q [%d]", c.Name, c.IDs(), clients.clientIndex.Size())
return nil
}
// addLocked c to the indexes. clients.lock is expected to be locked.
func (clients *clientsContainer) addLocked(c *client.Persistent) {
clients.clientIndex.Add(c)
}
// remove removes a client. ok is false if there is no such client.
func (clients *clientsContainer) remove(name string) (ok bool) {
clients.lock.Lock()
defer clients.lock.Unlock()
c, ok := clients.clientIndex.FindByName(name)
if !ok {
return false
}
clients.removeLocked(c)
return true
}
// removeLocked removes c from the indexes. clients.lock is expected to be
// locked.
func (clients *clientsContainer) removeLocked(c *client.Persistent) {
if err := c.CloseUpstreams(); err != nil {
log.Error("client container: removing client %s: %s", c.Name, err)
}
// Update the ID index.
clients.clientIndex.Delete(c)
}
// update updates a client by its name.
func (clients *clientsContainer) update(prev, c *client.Persistent) (err error) {
err = clients.check(c)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
clients.lock.Lock()
defer clients.lock.Unlock()
err = clients.clientIndex.Clashes(c)
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
clients.removeLocked(prev)
clients.addLocked(c)
return nil
}
// setWHOISInfo sets the WHOIS information for a client. clients.lock is
// expected to be locked.
func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) {
@@ -731,5 +857,5 @@ func (clients *clientsContainer) addFromSystemARP() {
// close gracefully closes all the client-specific upstream configurations of
// the persistent clients.
func (clients *clientsContainer) close() (err error) {
return clients.storage.CloseUpstreams()
return clients.clientIndex.CloseUpstreams()
}

View File

@@ -72,7 +72,7 @@ func TestClients(t *testing.T) {
IPs: []netip.Addr{cli1IP, cliIPv6},
}
err := clients.storage.Add(c)
err := clients.add(c)
require.NoError(t, err)
c = &client.Persistent{
@@ -81,7 +81,7 @@ func TestClients(t *testing.T) {
IPs: []netip.Addr{cli2IP},
}
err = clients.storage.Add(c)
err = clients.add(c)
require.NoError(t, err)
c, ok := clients.find(cli1)
@@ -107,7 +107,7 @@ func TestClients(t *testing.T) {
})
t.Run("add_fail_name", func(t *testing.T) {
err := clients.storage.Add(&client.Persistent{
err := clients.add(&client.Persistent{
Name: "client1",
UID: client.MustNewUID(),
IPs: []netip.Addr{netip.MustParseAddr("1.2.3.5")},
@@ -116,7 +116,7 @@ func TestClients(t *testing.T) {
})
t.Run("add_fail_ip", func(t *testing.T) {
err := clients.storage.Add(&client.Persistent{
err := clients.add(&client.Persistent{
Name: "client3",
UID: client.MustNewUID(),
})
@@ -124,7 +124,7 @@ func TestClients(t *testing.T) {
})
t.Run("update_fail_ip", func(t *testing.T) {
err := clients.storage.Update("client1", &client.Persistent{
err := clients.update(&client.Persistent{Name: "client1"}, &client.Persistent{
Name: "client1",
UID: client.MustNewUID(),
})
@@ -139,11 +139,11 @@ func TestClients(t *testing.T) {
cliNewIP = netip.MustParseAddr(cliNew)
)
prev, ok := clients.storage.FindByName("client1")
prev, ok := clients.clientIndex.FindByName("client1")
require.True(t, ok)
require.NotNil(t, prev)
err := clients.storage.Update("client1", &client.Persistent{
err := clients.update(prev, &client.Persistent{
Name: "client1",
UID: prev.UID,
IPs: []netip.Addr{cliNewIP},
@@ -155,11 +155,11 @@ func TestClients(t *testing.T) {
assert.Equal(t, clients.clientSource(cliNewIP), client.SourcePersistent)
prev, ok = clients.storage.FindByName("client1")
prev, ok = clients.clientIndex.FindByName("client1")
require.True(t, ok)
require.NotNil(t, prev)
err = clients.storage.Update("client1", &client.Persistent{
err = clients.update(prev, &client.Persistent{
Name: "client1-renamed",
UID: prev.UID,
IPs: []netip.Addr{cliNewIP},
@@ -173,7 +173,7 @@ func TestClients(t *testing.T) {
assert.Equal(t, "client1-renamed", c.Name)
assert.True(t, c.UseOwnSettings)
nilCli, ok := clients.storage.FindByName("client1")
nilCli, ok := clients.clientIndex.FindByName("client1")
require.False(t, ok)
assert.Nil(t, nilCli)
@@ -184,7 +184,7 @@ func TestClients(t *testing.T) {
})
t.Run("del_success", func(t *testing.T) {
ok := clients.storage.RemoveByName("client1-renamed")
ok := clients.remove("client1-renamed")
require.True(t, ok)
_, ok = clients.find("1.1.1.2")
@@ -192,7 +192,7 @@ func TestClients(t *testing.T) {
})
t.Run("del_fail", func(t *testing.T) {
ok := clients.storage.RemoveByName("client3")
ok := clients.remove("client3")
assert.False(t, ok)
})
@@ -261,7 +261,7 @@ func TestClientsWHOIS(t *testing.T) {
t.Run("can't_set_manually-added", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.2")
err := clients.storage.Add(&client.Persistent{
err := clients.add(&client.Persistent{
Name: "client1",
UID: client.MustNewUID(),
IPs: []netip.Addr{netip.MustParseAddr("1.1.1.2")},
@@ -272,7 +272,7 @@ func TestClientsWHOIS(t *testing.T) {
rc := clients.runtimeIndex.Client(ip)
require.Nil(t, rc)
assert.True(t, clients.storage.RemoveByName("client1"))
assert.True(t, clients.remove("client1"))
})
}
@@ -283,7 +283,7 @@ func TestClientsAddExisting(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.1")
// Add a client.
err := clients.storage.Add(&client.Persistent{
err := clients.add(&client.Persistent{
Name: "client1",
UID: client.MustNewUID(),
IPs: []netip.Addr{ip, netip.MustParseAddr("1:2:3::4")},
@@ -333,7 +333,7 @@ func TestClientsAddExisting(t *testing.T) {
require.NoError(t, err)
// Add a new client with the same IP as for a client with MAC.
err = clients.storage.Add(&client.Persistent{
err = clients.add(&client.Persistent{
Name: "client2",
UID: client.MustNewUID(),
IPs: []netip.Addr{ip},
@@ -341,7 +341,7 @@ func TestClientsAddExisting(t *testing.T) {
require.NoError(t, err)
// Add a new client with the IP from the first client's IP range.
err = clients.storage.Add(&client.Persistent{
err = clients.add(&client.Persistent{
Name: "client3",
UID: client.MustNewUID(),
IPs: []netip.Addr{netip.MustParseAddr("2.2.2.2")},
@@ -354,7 +354,7 @@ func TestClientsCustomUpstream(t *testing.T) {
clients := newClientsContainer(t)
// Add client with upstreams.
err := clients.storage.Add(&client.Persistent{
err := clients.add(&client.Persistent{
Name: "client1",
UID: client.MustNewUID(),
IPs: []netip.Addr{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("1:2:3::4")},

View File

@@ -96,7 +96,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
clients.lock.Lock()
defer clients.lock.Unlock()
clients.storage.RangeByName(func(c *client.Persistent) (cont bool) {
clients.clientIndex.Range(func(c *client.Persistent) (cont bool) {
cj := clientToJSON(c)
data.Clients = append(data.Clients, cj)
@@ -267,7 +267,7 @@ func copyBlockedServices(
var weekly *schedule.Weekly
if sch != nil {
weekly = sch.Clone()
} else if prev != nil {
} else if prev != nil && prev.BlockedServices != nil {
weekly = prev.BlockedServices.Schedule.Clone()
} else {
weekly = schedule.EmptyWeekly()
@@ -336,7 +336,7 @@ func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.
return
}
err = clients.storage.Add(c)
err = clients.add(c)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
@@ -364,7 +364,7 @@ func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.
return
}
if !clients.storage.RemoveByName(cj.Name) {
if !clients.remove(cj.Name) {
aghhttp.Error(r, w, http.StatusBadRequest, "Client not found")
return
@@ -399,14 +399,30 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
return
}
c, err := clients.jsonToClient(dj.Data, nil)
var prev *client.Persistent
var ok bool
func() {
clients.lock.Lock()
defer clients.lock.Unlock()
prev, ok = clients.clientIndex.FindByName(dj.Name)
}()
if !ok {
aghhttp.Error(r, w, http.StatusBadRequest, "client not found")
return
}
c, err := clients.jsonToClient(dj.Data, prev)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
return
}
err = clients.storage.Update(dj.Name, c)
err = clients.update(prev, c)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)

View File

@@ -49,7 +49,7 @@ func newPersistentClient(name string) (c *client.Persistent) {
Name: name,
UID: client.MustNewUID(),
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
Schedule: &schedule.Weekly{},
},
}
}
@@ -198,11 +198,11 @@ func TestClientsContainer_HandleDelClient(t *testing.T) {
clients := newClientsContainer(t)
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
err := clients.storage.Add(clientOne)
err := clients.add(clientOne)
require.NoError(t, err)
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
err = clients.storage.Add(clientTwo)
err = clients.add(clientTwo)
require.NoError(t, err)
assertPersistentClients(t, clients, []*client.Persistent{clientOne, clientTwo})
@@ -260,7 +260,7 @@ func TestClientsContainer_HandleUpdateClient(t *testing.T) {
clients := newClientsContainer(t)
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
err := clients.storage.Add(clientOne)
err := clients.add(clientOne)
require.NoError(t, err)
assertPersistentClients(t, clients, []*client.Persistent{clientOne})
@@ -342,11 +342,11 @@ func TestClientsContainer_HandleFindClient(t *testing.T) {
}
clientOne := newPersistentClientWithIDs(t, "client1", []string{testClientIP1})
err := clients.storage.Add(clientOne)
err := clients.add(clientOne)
require.NoError(t, err)
clientTwo := newPersistentClientWithIDs(t, "client2", []string{testClientIP2})
err = clients.storage.Add(clientTwo)
err = clients.add(clientTwo)
require.NoError(t, err)
assertPersistentClients(t, clients, []*client.Persistent{clientOne, clientTwo})

View File

@@ -32,9 +32,6 @@ const dataDir = "data"
// logSettings are the logging settings part of the configuration file.
type logSettings struct {
// Enabled indicates whether logging is enabled.
Enabled bool `yaml:"enabled"`
// File is the path to the log file. If empty, logs are written to stdout.
// If "syslog", logs are written to syslog.
File string `yaml:"file"`
@@ -457,14 +454,11 @@ var config = &configuration{
},
},
Log: logSettings{
Enabled: true,
File: "",
Compress: false,
LocalTime: false,
MaxBackups: 0,
MaxSize: 100,
MaxAge: 3,
Compress: false,
LocalTime: false,
Verbose: false,
},
OSConfig: &osConfig{},
SchemaVersion: configmigrate.LastSchemaVersion,

View File

@@ -13,21 +13,17 @@ import (
var testIPv4 = netip.AddrFrom4([4]byte{1, 2, 3, 4})
// newStorage is a helper function that returns a client storage filled with
// persistent clients. It also generates a UID for each client.
func newStorage(tb testing.TB, clients []*client.Persistent) (s *client.Storage) {
tb.Helper()
// newIDIndex is a helper function that returns a client index filled with
// persistent clients from the m. It also generates a UID for each client.
func newIDIndex(m []*client.Persistent) (ci *client.Index) {
ci = client.NewIndex()
s = client.NewStorage(&client.Config{
AllowedTags: nil,
})
for _, p := range clients {
p.UID = client.MustNewUID()
require.NoError(tb, s.Add(p))
for _, c := range m {
c.UID = client.MustNewUID()
ci.Add(c)
}
return s
return ci
}
func TestApplyAdditionalFiltering(t *testing.T) {
@@ -40,8 +36,7 @@ func TestApplyAdditionalFiltering(t *testing.T) {
}, nil)
require.NoError(t, err)
Context.clients.storage = newStorage(t, []*client.Persistent{{
Name: "default",
Context.clients.clientIndex = newIDIndex([]*client.Persistent{{
ClientIDs: []string{"default"},
UseOwnSettings: false,
SafeSearchConf: filtering.SafeSearchConfig{Enabled: false},
@@ -49,7 +44,6 @@ func TestApplyAdditionalFiltering(t *testing.T) {
SafeBrowsingEnabled: false,
ParentalEnabled: false,
}, {
Name: "custom_filtering",
ClientIDs: []string{"custom_filtering"},
UseOwnSettings: true,
SafeSearchConf: filtering.SafeSearchConfig{Enabled: true},
@@ -57,7 +51,6 @@ func TestApplyAdditionalFiltering(t *testing.T) {
SafeBrowsingEnabled: true,
ParentalEnabled: true,
}, {
Name: "partial_custom_filtering",
ClientIDs: []string{"partial_custom_filtering"},
UseOwnSettings: true,
SafeSearchConf: filtering.SafeSearchConfig{Enabled: true},
@@ -128,19 +121,16 @@ func TestApplyAdditionalFiltering_blockedServices(t *testing.T) {
}, nil)
require.NoError(t, err)
Context.clients.storage = newStorage(t, []*client.Persistent{{
Name: "default",
Context.clients.clientIndex = newIDIndex([]*client.Persistent{{
ClientIDs: []string{"default"},
UseOwnBlockedServices: false,
}, {
Name: "no_services",
ClientIDs: []string{"no_services"},
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
},
UseOwnBlockedServices: true,
}, {
Name: "services",
ClientIDs: []string{"services"},
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
@@ -148,7 +138,6 @@ func TestApplyAdditionalFiltering_blockedServices(t *testing.T) {
},
UseOwnBlockedServices: true,
}, {
Name: "invalid_services",
ClientIDs: []string{"invalid_services"},
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
@@ -156,7 +145,6 @@ func TestApplyAdditionalFiltering_blockedServices(t *testing.T) {
},
UseOwnBlockedServices: true,
}, {
Name: "allow_all",
ClientIDs: []string{"allow_all"},
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.FullWeekly(),

View File

@@ -178,7 +178,7 @@ func setupContext(opts options) (err error) {
// unsupported errors and returns nil. If err is nil, logIfUnsupported returns
// nil. Otherwise, it returns err.
func logIfUnsupported(msg string, err error) (outErr error) {
if errors.Is(err, errors.ErrUnsupported) {
if errors.As(err, new(*aghos.UnsupportedError)) {
log.Debug(msg, err)
return nil
@@ -232,9 +232,7 @@ func configureOS(conf *configuration) (err error) {
func setupHostsContainer() (err error) {
hostsWatcher, err := aghos.NewOSWritesWatcher()
if err != nil {
log.Info("WARNING: initializing filesystem watcher: %s; not watching for changes", err)
hostsWatcher = aghos.EmptyFSWatcher{}
return fmt.Errorf("initing hosts watcher: %w", err)
}
paths, err := hostsfile.DefaultHostsPaths()

View File

@@ -21,9 +21,7 @@ func configureLogger(opts options) (err error) {
ls := getLogSettings(opts)
// Configure logger level.
if !ls.Enabled {
log.SetLevel(log.OFF)
} else if ls.Verbose {
if ls.Verbose {
log.SetLevel(log.DEBUG)
}
@@ -93,14 +91,7 @@ func getLogSettings(opts options) (ls *logSettings) {
// separate method in order to configure logger before the actual configuration
// is parsed and applied.
func readLogSettings() (ls *logSettings) {
// TODO(s.chzhen): Add a helper function that returns default parameters
// for this structure and for the global configuration structure [config].
conf := &configuration{
Log: logSettings{
// By default, it is true if the property does not exist.
Enabled: true,
},
}
conf := &configuration{}
yamlFile, err := readConfigFile()
if err != nil {

View File

@@ -460,9 +460,8 @@ var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?>
// 1. The RestartSec setting is set to a lower value of 10 to make sure we
// always restart quickly.
//
// 2. The StandardOutput and StandardError settings are set to redirect the
// output to the systemd journal, see
// https://man7.org/linux/man-pages/man5/systemd.exec.5.html#LOGGING_AND_STANDARD_INPUT/OUTPUT.
// 2. The ExecStartPre setting is added to make sure that the log directory is
// always created to prevent the 209/STDOUT errors.
const systemdScript = `[Unit]
Description={{.Description}}
ConditionFileIsExecutable={{.Path|cmdEscape}}
@@ -472,6 +471,7 @@ ConditionFileIsExecutable={{.Path|cmdEscape}}
[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStartPre=/bin/mkdir -p /var/log/
ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
{{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}
{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}
@@ -479,8 +479,8 @@ ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
{{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}}
{{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}}
{{if and .LogOutput .HasOutputFileSupport -}}
StandardOutput=journal
StandardError=journal
StandardOutput=file:/var/log/{{.Name}}.out
StandardError=file:/var/log/{{.Name}}.err
{{- end}}
{{if gt .LimitNOFILE -1 }}LimitNOFILE={{.LimitNOFILE}}{{end}}
{{if .Restart}}Restart={{.Restart}}{{end}}

View File

@@ -133,14 +133,7 @@ func webCheckPortAvailable(port uint16) (ok bool) {
addrPort := netip.AddrPortFrom(config.HTTPConfig.Address.Addr(), port)
err := aghnet.CheckPort("tcp", addrPort)
if err != nil {
log.Info("web: warning: checking https port: %s", err)
return false
}
return true
return aghnet.CheckPort("tcp", addrPort) == nil
}
// tlsConfigChanged updates the TLS configuration and restarts the HTTPS server

View File

@@ -22,8 +22,8 @@ type Manager interface {
//
// DOMAIN[,DOMAIN].../IPSET_NAME[,IPSET_NAME]...
//
// If ipsetConf is empty, msg and err are nil. The error's chain contains
// [errors.ErrUnsupported] if current OS is not supported.
// If ipsetConf is empty, msg and err are nil. The error is of type
// *aghos.UnsupportedError if the OS is not supported.
func NewManager(ipsetConf []string) (mgr Manager, err error) {
if len(ipsetConf) == 0 {
return nil, nil

View File

@@ -4,14 +4,14 @@ go 1.22.4
require (
github.com/fzipp/gocyclo v0.6.0
github.com/golangci/misspell v0.6.0
github.com/golangci/misspell v0.5.1
github.com/gordonklaus/ineffassign v0.1.0
github.com/kisielk/errcheck v1.7.0
github.com/kyoh86/looppointer v0.2.1
github.com/securego/gosec/v2 v2.20.0
github.com/uudashr/gocognit v1.1.2
golang.org/x/tools v0.22.0
golang.org/x/vuln v1.1.2
golang.org/x/vuln v1.1.1
honnef.co/go/tools v0.4.7
mvdan.cc/gofumpt v0.6.0
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f
@@ -26,10 +26,9 @@ require (
github.com/kyoh86/nolint v0.0.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/exp/typeparams v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/exp/typeparams v0.0.0-20240604190554-fc45aab8b7f8 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/telemetry v0.0.0-20240701175443-4e29c7872ac1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -12,8 +12,8 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs=
github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo=
github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM=
github.com/golangci/misspell v0.5.1/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo=
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -63,8 +63,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
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/typeparams v0.0.0-20240613232115-7f521ea00fb8 h1:+ZJmEdDFzH5H0CnzOrwgbH3elHctfTecW9X0k2tkn5M=
golang.org/x/exp/typeparams v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20240604190554-fc45aab8b7f8 h1:WKP3FgLqWfVutBnw/dr+LNg4fzjyTQP5o+ELTIyoBrs=
golang.org/x/exp/typeparams v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -95,8 +95,6 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240701175443-4e29c7872ac1 h1:jveUVYFLPlIma1aZBg9rrUN+Dqk4e6QbVSGiZGwA/2Y=
golang.org/x/telemetry v0.0.0-20240701175443-4e29c7872ac1/go.mod h1:n38mvGdgc4dA684EC4NwQwoPKSw4jyKw8/DgZHDA1Dk=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -111,8 +109,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/vuln v1.1.2 h1:UkLxe+kAMcrNBpGrFbU0Mc5l7cX97P2nhy21wx5+Qbk=
golang.org/x/vuln v1.1.2/go.mod h1:2o3fRKD8Uz9AraAL3lwd/grWBv+t+SeJnPcqBUJrY24=
golang.org/x/vuln v1.1.1 h1:4nYQg4OSr7uYQMtjuuYqLAEVuTjY4k/CPMYqvv5OPcI=
golang.org/x/vuln v1.1.1/go.mod h1:hNgE+SKMSp2wHVUpW0Ow2ejgKpNJePdML+4YjxrVxik=
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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -4,23 +4,18 @@ package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"net/url"
"os"
"time"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/log"
"github.com/google/renameio/v2/maybe"
)
func main() {
ctx := context.Background()
l := slogutil.New(nil)
urlStr := "https://adguardteam.github.io/HostlistsRegistry/assets/filters.json"
if v, ok := os.LookupEnv("URL"); ok {
urlStr = v
@@ -36,7 +31,7 @@ func main() {
resp, err := c.Get(urlStr)
check(err)
defer slogutil.CloseAndLog(ctx, l, resp.Body, slog.LevelError)
defer log.OnCloserError(resp.Body, log.ERROR)
if resp.StatusCode != http.StatusOK {
panic(fmt.Errorf("expected code %d, got %d", http.StatusOK, resp.StatusCode))
@@ -69,13 +64,13 @@ func main() {
}
for i, f := range hlFlt.Filters {
key := f.FilterKey
id := f.FilterID
cat := f.category()
if cat == "" {
l.WarnContext(ctx, "no fitting category for filter", "key", key, "idx", i)
log.Info("warning: filter %s at index %d does not have a fitting category", id, i)
}
aghFlt.Filters[key] = &aghFiltersFilter{
aghFlt.Filters[id] = &aghFiltersFilter{
Name: f.Name,
CategoryID: cat,
Homepage: f.Homepage,
@@ -123,34 +118,26 @@ type hlFilters struct {
// hlFiltersFilter is the JSON structure for a filter in the Hostlists Registry.
type hlFiltersFilter struct {
DownloadURL string `json:"downloadUrl"`
FilterKey string `json:"filterKey"`
Homepage string `json:"homepage"`
Name string `json:"name"`
Tags []int `json:"tags"`
DownloadURL string `json:"downloadUrl"`
FilterID string `json:"filterId"`
Homepage string `json:"homepage"`
Name string `json:"name"`
Tags []string `json:"tags"`
}
// Known tag IDs. Keep in sync with tags/metadata.json in the source repo.
const (
tagIDGeneral = 1
tagIDSecurity = 2
tagIDRegional = 3
tagIDOther = 4
)
// category returns the AdGuard Home category for this filter. If there is no
// fitting category, cat is empty.
func (f *hlFiltersFilter) category() (cat string) {
for _, t := range f.Tags {
switch t {
case tagIDGeneral:
case "purpose:general":
return "general"
case tagIDSecurity:
return "security"
case tagIDRegional:
return "regional"
case tagIDOther:
case "purpose:other":
return "other"
case "purpose:regional":
return "regional"
case "purpose:security":
return "security"
}
}