Compare commits

...

39 Commits

Author SHA1 Message Date
Simon Zolin
da2a8daff2 + parse "config dnsmasq" 2020-04-23 12:13:41 +03:00
Simon Zolin
5fbf28b9cf * dhcpd: "dnsmasq_leasefile" setting 2020-04-22 18:56:54 +03:00
Simon Zolin
dced8a5a83 rename 2020-03-20 16:29:42 +03:00
Simon Zolin
33a98aa937 minor 2020-03-20 11:45:22 +03:00
Simon Zolin
e822443600 minor 2020-03-19 14:22:16 +03:00
Simon Zolin
82f36341e3 + Replace dnsmasq on OpenWRT (minimal configuration) 2020-03-19 14:17:28 +03:00
Ildar Kamalov
6eadca25d1 Merge: - client: fix tags select on mobile
Closes #1471

Squashed commit of the following:

commit 1ce2c742e268b49332f73c9ac17722dbe0e8dd92
Author: Ildar Kamalov <i.kamalov@adguard.com>
Date:   Tue Mar 17 15:38:04 2020 +0300

    - client: fix tags select on mobile
2020-03-19 10:49:25 +03:00
Simon Zolin
72c20acb86 Merge: + dnsforward: add dnssec_enabled option
Close #66

* commit '79bfa6a72b6cda8c0f5211a3a6df0293c4b6c7d1':
  + client: handle DNSSEC setting
  + dnsforward: add dnssec_enabled option
2020-03-18 18:21:06 +03:00
Ildar Kamalov
79bfa6a72b + client: handle DNSSEC setting 2020-03-18 18:05:02 +03:00
Simon Zolin
a5c2cdaf38 + dnsforward: add dnssec_enabled option 2020-03-18 18:05:02 +03:00
Simon Zolin
7ff743ab32 Merge: * auto-upgrade: don't show Upgrade button on UNIX if running under non-root user
Close #1193

* commit '58f183103248d3efd7e94551ffaebc5df3689ac0':
  * auto-upgrade: don't show Upgrade button on UNIX if running under non-root user
2020-03-18 15:19:30 +03:00
Simon Zolin
3303d77dad Merge: - tls: allow_unencrypted_doh and strict_sni_check settings were reset after POST /control/tls/configure
Close #1484

Squashed commit of the following:

commit 2daae88b80d83889238eaf4808b8a4da4fd8b102
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Wed Mar 18 13:16:23 2020 +0300

    minor

commit 99fa4e27ad45e515c8faef14a77304c2a9687875
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Tue Mar 17 13:46:46 2020 +0300

    - tls: allow_unencrypted_doh and strict_sni_check settings were reset after POST /control/tls/configure
2020-03-18 15:18:15 +03:00
Simon Zolin
58f1831032 * auto-upgrade: don't show Upgrade button on UNIX if running under non-root user 2020-03-17 19:22:45 +03:00
Simon Zolin
4dba20941d Merge: * tech doc: update architecture picture
* commit '360ee3e3926a2786233edf38fd64db36bf61ff7e':
  * tech doc: update architecture picture
2020-03-17 18:47:03 +03:00
Simon Zolin
360ee3e392 * tech doc: update architecture picture 2020-03-17 18:36:10 +03:00
Simon Zolin
32baa907b6 Merge: + Reload configuration command; refactoring
Close #1302

* commit 'dd7d9dc334181b3923b1532aaae51cae77d5ed55':
  * move getDNSAddresses() and "/dns-query" handler to DNS module
  * TLS is now a separate module (logically)
  * move HTTP server code
  * refactor: move blocked-services functions to dnsfilter
  * refactor
  + service: support "-s reload" command
  + clients: re-read auto-clients from /etc/hosts when SIGHUP is received
2020-03-17 17:19:03 +03:00
Simon Zolin
dd7d9dc334 * move getDNSAddresses() and "/dns-query" handler to DNS module 2020-03-17 17:12:02 +03:00
Simon Zolin
db30f27c8f * TLS is now a separate module (logically) 2020-03-17 17:11:24 +03:00
Simon Zolin
8e4bc29103 * move HTTP server code 2020-03-17 17:11:24 +03:00
Simon Zolin
0789e4b20d * refactor: move blocked-services functions to dnsfilter 2020-03-17 17:11:22 +03:00
Simon Zolin
e8129f15c7 * refactor
1. Auth module was initialized inside dns.go - now it's moved to initWeb()

2. stopHTTPServer() wasn't called on server stop - now we do that

3. Don't use postInstall() HTTP filter where it's not necessary.
Now we register handlers after installation is complete.
2020-03-17 15:23:29 +03:00
Simon Zolin
c77907694d + service: support "-s reload" command 2020-03-17 15:21:10 +03:00
Simon Zolin
fa2f793ac7 + clients: re-read auto-clients from /etc/hosts when SIGHUP is received 2020-03-17 15:21:10 +03:00
Ildar Kamalov
e5db33705d Merge: + client: add digit grouping for numbers on the dashboard
Closes #1423

Squashed commit of the following:

commit 6e5de427c48577ebbe4d963f817b66fed9b29bb4
Author: Ildar Kamalov <i.kamalov@adguard.com>
Date:   Wed Mar 11 17:56:39 2020 +0300

    + client: add digit grouping for numbers on the dashboard
2020-03-17 15:15:19 +03:00
Ildar Kamalov
bc9bccc669 Merge: Add thai and romanian languages
Closes #1445

* commit '229d040ee286e656c8986e622a33f18176a3e769':
  + i18n: add thai and romanian languages
  + client: add thai and romanian languages
2020-03-17 15:06:56 +03:00
Simon Zolin
646725efb7 Merge: * DNS filters: optimize filter update
Close #1463

Squashed commit of the following:

commit d5bdc939a2ae9f6d1ae879e4225b1dce09657b92
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Mar 16 16:39:17 2020 +0300

    minor

commit e15b56a0d9db182f9d30b434584018cb1bf038d5
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Thu Mar 12 14:39:07 2020 +0300

    minor

commit 77bf59ca6e556b75af48c5987866af6d5025dae8
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Thu Mar 12 14:30:04 2020 +0300

    minor

commit e19c13f82dd408ed638bd4b68d21cdfebbdf782f
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Thu Mar 12 14:24:50 2020 +0300

    minor

commit 9113c6dae6263aa7ee6e4295c2b60dd3083e2bf0
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Thu Mar 12 14:02:06 2020 +0300

    minor

commit 70283e329e32def3375e893f806a2a02d8ca9f57
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Thu Mar 12 13:35:11 2020 +0300

    logical module Filtering

commit 837a255c6a04941e9fc007a56d71faf4c4213257
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Thu Mar 12 13:11:37 2020 +0300

    minor

commit 1853ed2b57a86dd49508023f47218219399b4fe5
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Thu Mar 12 12:59:28 2020 +0300

    refactor

commit 1ba3cc53c76255439fe54693b40ee9665fdc15e4
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Wed Mar 11 20:12:53 2020 +0300

    * filters: optimize update procedure
2020-03-17 15:00:40 +03:00
Simon Zolin
a93652b1c0 Merge: * install: IPv6 addresses were removed from the list of available addresses
Close #1481

* commit 'dfa278b845dbf890fec800e15495b364f6f4fd2e':
  * install: IPv6 addresses were removed from the list of available addresses
2020-03-17 14:27:18 +03:00
Simon Zolin
5f328d20ca Merge: + DNS, Web: Entware: use special directory with the system root certificates
Close #1311

* commit '0e030154ee4f73d0c1a8e9d092b12be78b0a0ea5':
  - fix tests
  + DNS, Web: Entware: use special directory with the system root certificates
2020-03-17 14:18:02 +03:00
Simon Zolin
0e030154ee - fix tests 2020-03-16 16:29:23 +03:00
Simon Zolin
1000aef1d2 + DNS, Web: Entware: use special directory with the system root certificates
+ use custom RootsCA for HTTPS client, for server cert verify
2020-03-16 15:15:38 +03:00
Simon Zolin
b345595dbf Merge: * blocked-services: remove Messenger
* commit '5dc7b848dffaa0cd3cd3ca55080af90bdda2fec0':
  * blocked-services: remove unknown service names
  * blocked-services: remove Messenger
2020-03-16 15:01:19 +03:00
Simon Zolin
88853b76d9 Merge: + config: new setting 'http_proxy'
Close #458

* commit '2e054b673264bdb563abb6478979d00e76e52c8d':
  + config: new setting 'http_proxy'
2020-03-16 14:58:21 +03:00
Simon Zolin
dfa278b845 * install: IPv6 addresses were removed from the list of available addresses 2020-03-16 14:32:43 +03:00
Simon Zolin
480c6ac753 Merge: - dhcp: crash on error on startup
Squashed commit of the following:

commit 81908b636ec91a562b3ff8634b77c71dc3f009e4
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Mar 13 17:25:36 2020 +0300

    - dhcp: crash on error on startup
2020-03-13 17:30:09 +03:00
Simon Zolin
2e054b6732 + config: new setting 'http_proxy' 2020-03-12 15:11:08 +03:00
Ildar Kamalov
229d040ee2 + i18n: add thai and romanian languages 2020-03-11 17:06:37 +03:00
Ildar Kamalov
36ba8380de + client: add thai and romanian languages 2020-03-11 17:06:17 +03:00
Simon Zolin
5dc7b848df * blocked-services: remove unknown service names 2020-03-02 18:51:48 +03:00
Simon Zolin
01d9078107 * blocked-services: remove Messenger 2020-03-02 15:53:01 +03:00
49 changed files with 2764 additions and 668 deletions

View File

@@ -17,6 +17,7 @@
"pl": "Polski",
"pt-br": "Portuguese (BR)",
"pt-pt": "Portuguese (PT)",
"ro": "Română",
"sk": "Slovenčina",
"sl": "Slovenščina",
"sr-cs": "Srpski",
@@ -30,7 +31,8 @@
"ja": "日本語",
"zh-tw": "正體中文",
"zh-cn": "简体中文",
"ko": "한국어"
"ko": "한국어",
"th": "ภาษาไทย"
}
}
]

View File

@@ -64,6 +64,7 @@ Contents:
* API: Log in
* API: Log out
* API: Get current user info
* Replace dnsmasq on OpenWRT
## Relations between subsystems
@@ -71,7 +72,6 @@ Contents:
![](doc/agh-arch.png)
## First startup
The first application startup is detected when there's no .yaml configuration file.
@@ -889,6 +889,7 @@ Response:
"blocking_ipv4": "1.2.3.4",
"blocking_ipv6": "1:2:3::4",
"edns_cs_enabled": true | false,
"dnssec_enabled": true | false
"disable_ipv6": true | false,
}
@@ -906,6 +907,7 @@ Request:
"blocking_ipv4": "1.2.3.4",
"blocking_ipv6": "1:2:3::4",
"edns_cs_enabled": true | false,
"dnssec_enabled": true | false
"disable_ipv6": true | false,
}
@@ -1255,6 +1257,7 @@ Response:
}
...
],
"answer_dnssec": true,
"client":"127.0.0.1",
"elapsedMs":"0.098403",
"filterId":1,
@@ -1592,3 +1595,70 @@ Response:
}
If no client is configured then authentication is disabled and server sends an empty response.
## Replace dnsmasq on OpenWRT
`/etc/init.d/dnsmasq` script creates a dnsmasq.conf file and then starts dnsmasq.
To replace dnsmasq we have to read system configuration files and update (create new, if necessary) our .yaml file accordingly.
If started as:
./AdGuardHome --import-openwrt-config
* Read `/etc/config/network`:
config interface 'lan'
option netmask '255.255.255.0'
option ipaddr '192.168.8.1'
* Read `/etc/config/dhcp`:
config dhcp 'lan'
option start '100'
option limit '150'
option leasetime '12h'
config dnsmasq
option leasefile '/tmp/dhcp.leases'
* Write this yaml configuration:
dhcp:
enabled: true
interface_name: "br-lan"
gateway_ip: "192.168.8.1"
subnet_mask: "255.255.255.0"
range_start: "192.168.8.100"
range_end: "192.168.8.249"
lease_duration: 86400
icmp_timeout_msec: 1000
dnsmasq_leasefile "/tmp/dhcp.leases"
* Read `/etc/config/dhcp`:
config host '123412341234'
option mac '12:34:12:34:12:34'
option ip '192.168.8.2'
option name 'hostname'
* Add a static lease to leases.db:
12:34:12:34:12:34 | 192.168.8.2 | hostname
* Read `/etc/resolv.conf`:
nameserver <IP1>
nameserver <IP2>
* Write this yaml configuration:
dns:
bootstrap_dns:
- IP1
- IP2
And service script starts AGH like this:
.../AdGuardHome --import-openwrt-config
.../AdGuardHome

View File

@@ -479,5 +479,7 @@
"install_static_configure": "We have detected that a dynamic IP address is used — <0>{{ip}}</0>. Do you want to use it as your static address?",
"confirm_static_ip": "AdGuard Home will configure {{ip}} to be your static IP address. Do you want to proceed?",
"list_updated": "{{count}} list updated",
"list_updated_plural": "{{count}} lists updated"
}
"list_updated_plural": "{{count}} lists updated",
"dnssec_enable": "Enable DNSSEC",
"dnssec_enable_desc": "Set DNSSEC flag in the outcoming DNS queries and check the result (DNSSEC-enabled resolver is required)"
}

View File

@@ -69,7 +69,7 @@
"enabled_protection": "Protección habilitada",
"disable_protection": "Deshabilitar protección",
"disabled_protection": "Protección deshabilitada",
"refresh_statics": "Restablecer estadísticas",
"refresh_statics": "Actualizar estadísticas",
"dns_query": "Consultas DNS",
"blocked_by": "<0>Bloqueado por filtros</0>",
"stats_malware_phishing": "Malware/phishing bloqueado",

View File

@@ -0,0 +1,483 @@
{
"client_settings": "Setări client",
"example_upstream_reserved": "puteți preciza un DNS upstream <0>de domeni/u(ii) specific(e)</0>",
"upstream_parallel": "Folosiți interogări paralele pentru a rezolva rapid prin interogarea simultană a tuturor serverelor upstream",
"bootstrap_dns": "Serverele DNS Bootstrap",
"bootstrap_dns_desc": "Serverele DNS Bootstrap sunt folosite pentru a rezolva adresele IP ale resolverelor DoH/DoT indicate ca upstreams.",
"check_dhcp_servers": "Căutați servere DHCP",
"save_config": "Salvare configurare",
"enabled_dhcp": "Server DHCP activat",
"disabled_dhcp": "Server DHCP dezactivat",
"dhcp_title": "Server DHCP (experimental!)",
"dhcp_description": "Dacă routerul dvs. nu furnizează setări DHCP, puteți utiliza serverul DHCP încorporat AdGuard.",
"dhcp_enable": "Activați serverul DHCP",
"dhcp_disable": "Dezactivați serverul DHCP",
"dhcp_not_found": "Este sigur să activați serverul DHCP încorporat - nu am găsit servere DHCP active în rețea. Cu toate acestea, vă recomandăm să-l verificați manual, deoarece testul nostru automat nu oferă în prezent 100% garanție.",
"dhcp_found": "În rețea se găsește un server DHCP activ. Nu este sigur să activați serverul DHCP încorporat.",
"dhcp_leases": "DHCP închiriate",
"dhcp_static_leases": "DHCP statice închiriate",
"dhcp_leases_not_found": "Nu s-au găsit DHCP închiriate",
"dhcp_config_saved": "Configurare DHCP salvată cu succes",
"form_error_required": "Câmp necesar",
"form_error_ip4_format": "Format IPv4 nevalid",
"form_error_ip6_format": "Format IPv6 nevalid",
"form_error_ip_format": "Format IP nevalid",
"form_error_mac_format": "Format MAC nevalid",
"form_error_client_id_format": "Format ID de client nevalid",
"form_error_positive": "Trebuie să fie mai mare de 0",
"form_error_negative": "Trebuie să fie egală cu 0 sau mai mare",
"dhcp_form_gateway_input": "IP Gateway",
"dhcp_form_subnet_input": "Mască subnet",
"dhcp_form_range_title": "Interval de adrese IP",
"dhcp_form_range_start": "Start interval",
"dhcp_form_range_end": "Sfârșit interval",
"dhcp_form_lease_title": "Timp de închidere DHCP (în secunde)",
"dhcp_form_lease_input": "Durata locației",
"dhcp_interface_select": "Selectați interfața DHCP",
"dhcp_hardware_address": "Adresa mașinii",
"dhcp_ip_addresses": "Adrese IP",
"dhcp_table_hostname": "Hostname",
"dhcp_table_expires": "Expiră",
"dhcp_warning": "Dacă doriți oricum să activați serverul DHCP, verificați că nu există un alt server DHCP activ în rețeaua dvs. Altfel, poate întrerupe Internetul pe toate aparatele conectate!",
"dhcp_error": "Nu am putut determina dacă există un alt server DHCP în rețea.",
"dhcp_static_ip_error": "Pentru a utiliza serverul DHCP trebuie setată o adresă IP statică. Nu am reușit să stabilim dacă această interfață de rețea este configurată folosind adresa IP statică. Vă rugăm să setați manual o adresă IP statică.",
"dhcp_dynamic_ip_found": "Sistemul dvs. folosește configurația dinamică a adreselor IP pentru interfața <0>{{interfaceName}}</0>. Pentru a utiliza serverul DHCP trebuie setată o adresă IP statică. Adresa IP curentă este <0>{{ipAddress}}</0>. Vom seta automat această adresă IP ca statică dacă apăsați butonul Activați DHCP.",
"dhcp_lease_added": "\"{{key}}\" statică închiriată adăugată cu succes",
"dhcp_lease_deleted": "\"{{key}}\" statică închiriată eliminată cu succes",
"dhcp_new_static_lease": "Închiriere statică nouă",
"dhcp_static_leases_not_found": "Nu s-au găsit închirieri statice DHCP",
"dhcp_add_static_lease": "Adăugați închiriere statică",
"dhcp_reset": "Sunteți sigur că doriți să resetați configurația DHCP?",
"delete_confirm": "Sunteți sigur că doriți să ștergeți \"{{key}}\"?",
"form_enter_hostname": "Intrați hostname",
"error_details": "Detalii eroare",
"back": "Înapoi",
"dashboard": "Tablou de bord",
"settings": "Setări",
"filters": "Filtre",
"query_log": "Jurnal interogări",
"faq": "FAQ",
"version": "Versiune",
"address": "adresă",
"on": "ON",
"off": "OFF",
"copyright": "Copyright",
"homepage": "Homepage",
"report_an_issue": "Raportați o problemă",
"privacy_policy": "Politică confidențialitate",
"enable_protection": "Activați protecția",
"enabled_protection": "Protecție activată",
"disable_protection": "Dezactivați protecția",
"disabled_protection": "Protecție dezactivată",
"refresh_statics": "Actualizare statistici",
"dns_query": "Interogări DNS",
"blocked_by": "<0>Blocate de Filtre</0>",
"stats_malware_phishing": "Malware/phishing blocate",
"stats_adult": "Site-uri cu conținut adult blocate",
"stats_query_domain": "Domeniile cele mai căutate",
"for_last_24_hours": "în ultimele 24 ore",
"for_last_days": "în ultimele {{count}} zile",
"for_last_days_plural": "pentru ultimele {{count}} zile",
"no_domains_found": "Nu s-au găsit domenii",
"requests_count": "Cont interogări",
"top_blocked_domains": "Domeniile blocate cel mai des",
"top_clients": "Clienți de top",
"no_clients_found": "Nu au fost găsiți clienți",
"general_statistics": "Statistici generale",
"number_of_dns_query_days": "Un număr de interogări DNS procesate în ultima {{count}} zi",
"number_of_dns_query_days_plural": "Un număr de interogări DNS procesate în ultimele {{count}} zile",
"number_of_dns_query_24_hours": "Un număr de interogări DNS procesate în ultimele 24 de ore",
"number_of_dns_query_blocked_24_hours": "Un număr de solicitări DNS blocate de filtrele de blocare și listele de blocaj de hosts",
"number_of_dns_query_blocked_24_hours_by_sec": "Un număr de solicitări DNS blocate de modulul de securitate de navigare AdGuard",
"number_of_dns_query_blocked_24_hours_adult": "Un număr de site-uri web pentru adulți blocate",
"enforced_save_search": "Căutare protejată întărită",
"number_of_dns_query_to_safe_search": "O serie de solicitări DNS făcute pe motoare de căutare cu Căutarea protejată activată",
"average_processing_time": "Timpul mediu de procesare",
"average_processing_time_hint": "Timp mediu în milisecunde la procesarea unei cereri DNS",
"block_domain_use_filters_and_hosts": "Blocați domenii folosind filtre și fișiere hosts",
"filters_block_toggle_hint": "Puteți configura regulile de blocare în setările <a href='#filters'> Filtre </a>.",
"use_adguard_browsing_sec": "Utilizați serviciul Navigarea în Securitate AdGuard",
"use_adguard_browsing_sec_hint": "AdGuard Home va verifica dacă domeniul este în lista negră a serviciul web de securitate de navigare. Pentru acesta va utiliza un lookup API discret: un prefix scurt al numelui de domeniu SHA256 hash este trimis serverului.",
"use_adguard_parental": "Utilizați controlul parental AdGuard",
"use_adguard_parental_hint": "AdGuard Home va verifica dacă este conținut adult pe domeniu. Utilizează aceeași API discret ca cel utilizat de serviciul de securitate de navigare.",
"enforce_safe_search": "Căutare protejată întărită",
"enforce_save_search_hint": "AdGuard Home poate impune căutarea protejată în următoarele motoare de căutare: Google, Youtube, Bing, DuckDuckGo, Yandex, Pixabay.",
"no_servers_specified": "Nu sunt specificate servere",
"general_settings": "Setări Generale",
"dns_settings": "Setări DNS",
"dns_blocklists": "DNS liste blocări",
"dns_allowlists": "DNS liste autorizări",
"dns_blocklists_desc": "AdGuard Home blochează domenii incluse în liste de blocări.",
"dns_allowlists_desc": "Domeniile DNS autorizate vor fi permise, chiar dacă se află pe orice listă de blocări.",
"custom_filtering_rules": "Reguli filtrare personale",
"encryption_settings": "Setări de criptare",
"dhcp_settings": "Setări DHCP",
"upstream_dns": "Servere upstream DNS",
"upstream_dns_hint": "Dacă mențineți acest câmp gol, AdGuard Home va folosi <a href='https://www.quad9.net/' target='_blank'>Quad9</a> ca upstream.",
"test_upstream_btn": "Testați upstreams",
"upstreams": "Upstreams",
"apply_btn": "Aplică",
"disabled_filtering_toast": "Filtrare dezactivată",
"enabled_filtering_toast": "Filtrare activată",
"disabled_safe_browsing_toast": "Navigare protejată dezactivată",
"enabled_safe_browsing_toast": "Navigare protejată activată",
"disabled_parental_toast": "Control parental dezactivat",
"enabled_parental_toast": "Control parental activat",
"disabled_safe_search_toast": "Căutare protejată dezactivată",
"enabled_save_search_toast": "Căutare protejată activată",
"enabled_table_header": "Activat",
"name_table_header": "Nume",
"list_url_table_header": "Lista URL",
"rules_count_table_header": "Număr de reguli",
"last_time_updated_table_header": "Ultima aducere la zi",
"actions_table_header": "Acțiuni",
"edit_table_action": "Editare",
"delete_table_action": "Sterge",
"filters_and_hosts_hint": "AdGuard Home înțelege regulile de bază de blocare cât și sintaxa fișierelor hosts.",
"no_blocklist_added": "Listă blocări goală",
"no_whitelist_added": "Listă autorizări goală",
"add_blocklist": "Adăugați blocaj",
"add_allowlist": "Adăugați autorizare",
"cancel_btn": "Anulare",
"enter_name_hint": "Intrați numele",
"enter_url_hint": "Intrați URL",
"check_updates_btn": "Caută actualizări",
"new_blocklist": "Nouă blocare",
"new_allowlist": "Nouă autorizare",
"edit_blocklist": "Editare blocare",
"edit_allowlist": "Editare autorizare",
"enter_valid_blocklist": "Intrați un URL valid pentru blocare.",
"enter_valid_allowlist": "Intrați un URL valid pentru autorizare.",
"form_error_url_format": "Format url nevalid",
"custom_filter_rules": "Reguli de filtrare personalizate",
"custom_filter_rules_hint": "Intrați o regulă pe linie. Puteți utiliza reguli de blocare sau sintaxa de fișiere hosts.",
"examples_title": "Exemple",
"example_meaning_filter_block": "blochează accesul la domeniul exemplu.org și la toate subdomeniile sale",
"example_meaning_filter_whitelist": "deblochează accesul la domeniul exemplu.org și la toate subdomeniile sale",
"example_meaning_host_block": "AdGuard Home va returna acum adresa 127.0.0.1 pentru domeniul example.org (dar nu și subdomeniile sale).",
"example_comment": "! Iată cum se adăugă o descriere",
"example_comment_meaning": "comentariu",
"example_comment_hash": "# Astfel putem lăsa comentarii",
"example_regex_meaning": "blocare acces la domenii care corespund expresiei obișnuite specificate",
"example_upstream_regular": "DNS clasic (peste UDP)",
"example_upstream_dot": "<0>DNS-over-TLS</0> criptat",
"example_upstream_doh": "<0>DNS-over-HTTPS</0> criptat",
"example_upstream_sdns": "puteți utiliza <0>DNS Stamps</0> pentru rezolvere <1>DNSCrypt</1> sau <2>DNS-over-HTTPS</2>",
"example_upstream_tcp": "DNS clasic (peste TCP)",
"all_lists_up_to_date_toast": "Toate listele sunt deja la zi",
"updated_upstream_dns_toast": "Serverele DNS upstream aduse la zi",
"dns_test_ok_toast": "Serverele DNS specificate funcționează corect",
"dns_test_not_ok_toast": "Serverul \"{{key}}\": nu a putut fi utilizat, verificați dacă l-ați scris corect",
"unblock_btn": "Deblocați",
"block_btn": "Blocați",
"time_table_header": "Ora",
"domain_name_table_header": "Nume domeniu",
"type_table_header": "Tip",
"response_table_header": "Răspuns",
"client_table_header": "Client",
"empty_response_status": "Gol",
"show_all_filter_type": "Arată tot",
"show_filtered_type": "Arată cele filtrate",
"no_logs_found": "Nici un jurnal găsit",
"refresh_btn": "Actualizare",
"previous_btn": "Anterior",
"next_btn": "Următor",
"loading_table_status": "Se încarcă...",
"page_table_footer_text": "Pagina",
"rows_table_footer_text": "linii",
"updated_custom_filtering_toast": "Reguli personalizate de filtrare aduse la zi",
"rule_removed_from_custom_filtering_toast": "Regulă scoasă din regullei personalizate de filtrare",
"rule_added_to_custom_filtering_toast": "Regula adăugată la regulile de filtrare personalizate",
"query_log_response_status": "Statut: {{value}}",
"query_log_filtered": "Filtrat de {{filter}}",
"query_log_confirm_clear": "Sunteți sigur că doriți să ștergeți întregul jurnal de interogări?",
"query_log_cleared": "Jurnalul de interogare a fost șters cu succes",
"query_log_clear": "Curăță jurnalele",
"query_log_retention": "Retenție jurnale interogare",
"query_log_enable": "Activați jurnal",
"query_log_configuration": "Configurația jurnalelor",
"query_log_disabled": "Jurnalul de interogare este dezactivat și poate fi configurat în <0>setări</0>",
"query_log_strict_search": "Utilizați ghilimele duble pentru căutare strictă",
"query_log_retention_confirm": "Sunteți sigur că doriți să schimbați retenția jurnalului de interogare? Reducând valoarea intervalului, unele date vor fi pierdute",
"dns_config": "Configurația serverului DNS",
"blocking_mode": "Modul de blocare",
"default": "Implicit",
"nxdomain": "NXDOMAIN",
"null_ip": "IP nul",
"custom_ip": "IP personalizat",
"blocking_ipv4": "Blocarea IPv4",
"blocking_ipv6": "Blocarea IPv6",
"form_enter_rate_limit": "Intrați limita ratei",
"rate_limit": "Limita ratei",
"edns_enable": "Activați clientul subnet EDNS",
"edns_cs_desc": "Dacă este activat, AdGuard Home va trimite subnet-ele clienților către serverele DNS.",
"rate_limit_desc": "Numărul de solicitări pe secundă pe care un singur client este permis să le facă (0: nelimitat)",
"blocking_ipv4_desc": "Adresa IP de returnat pentru o cerere A de blocare",
"blocking_ipv6_desc": "Adresa IP de returnat pentru o cerere AAAA de blocare",
"blocking_mode_default": "Implicit: Răspunde cu NXDOMAIN cînd sunt blocate de regula Adblock-style; răspunde cu adresa IP specificată în regulă când sunt blocate de regula /etc/hosts-style",
"blocking_mode_nxdomain": "NXDOMAIN: Răspunde cu codul NXDOMAIN",
"blocking_mode_null_ip": "IP nul: răspunde cu o adresă IP zero (0.0.0.0 pentru A; :: pentru AAAA)",
"blocking_mode_custom_ip": "IP personalizat: răspunde cu o adresă IP setată manual",
"upstream_dns_client_desc": "Dacă mențineți acest câmp gol, AdGuard Home va folosi serverele configurate în <0>setările DNS</0>.",
"source_label": "Sursă",
"found_in_known_domain_db": "Găsit în baza de date de domenii cunoscută.",
"category_label": "Categorie",
"rule_label": "Regulă",
"list_label": "Listă",
"unknown_filter": "Filtru necunoscut {{filterId}}",
"install_welcome_title": "Bun venit la AdGuard Home!",
"install_welcome_desc": "AdGuard Home este un server DNS care blochează anunțuri și trackere la nivel de rețea. Scopul său este de a vă da controlul pe întreaga rețea și toate aparatele dvs. și fără un program din partea clientului.",
"install_settings_title": "Interfață administrator web",
"install_settings_listen": "Interfață de ascultare",
"install_settings_port": "Port",
"install_settings_interface_link": "Interfața dvs. de administrare AdGuard Home va fi disponibilă pe următoarele adrese:",
"form_error_port": "Intrați un port valid",
"install_settings_dns": "Server DNS",
"install_settings_dns_desc": "Va trebui să configurați aparatele sau routerul pentru a utiliza serverul DNS pe următoarele adrese:",
"install_settings_all_interfaces": "Toate interfețele",
"install_auth_title": "Autentificare",
"install_auth_desc": "Este foarte recomandat să configurați o parolă pentru accesul la interfața web de administrare AdGuard Home. Chiar dacă este accesibil numai în rețeaua dvs. locală, este încă important să îl protejați de accesul fără restricții.",
"install_auth_username": "Nume utilizator",
"install_auth_password": "Parola",
"install_auth_confirm": "Confirmați parola",
"install_auth_username_enter": "Intrați numele de utilizator",
"install_auth_password_enter": "Intrați parola",
"install_step": "Etapa",
"install_devices_title": "Configurați aparatele dvs",
"install_devices_desc": "Pentru a începe să utilizați AdGuard Home, trebuie să configurați aparatele.",
"install_submit_title": "Felicitări!",
"install_submit_desc": "Etapa de instalare este terminată și sunteți gata să începeți utilizarea AdGuard Home.",
"install_devices_router": "Router",
"install_devices_router_desc": "Această configurație va acoperi automat toate aparatele conectate la routerul de acasă și nu va trebui să le configurați manual pe fiecare.",
"install_devices_address": "Serverul DNS AdGuard Home ascultă pe următoarele adrese",
"install_devices_router_list_1": "Deschideți preferințele pentru routerul dvs. De obicei, îl puteți accesa din browserul dvs. printr-o adresă URL (cum ar fi http://192.168.0.1/ sau http://192.168.1.1/). Vi se poate cere să introduceți parola. Dacă nu v-o amintiți, puteți reseta adesea parola apăsând un buton de pe routerul propriu-zis. Unele routere necesită o aplicație specifică, care în acest caz ar trebui să fie deja instalată pe computerul/telefonul dvs.",
"install_devices_router_list_2": "Găsiți setările DHCP/DNS. Căutați literele DNS lângă un câmp care să permită două sau trei seturi de numere, fiecare împărțit în patru grupuri de una până la trei cifre.",
"install_devices_router_list_3": "Intrați adresele serverului dvs. AdGuard Home aici.",
"install_devices_windows_list_1": "Deschideți panoul de control prin meniul Start sau căutare Windows.",
"install_devices_windows_list_2": "Accesați categoria \"Rețea și Internet\", apoi la \"Centrul de Rețea și Partajare\".",
"install_devices_windows_list_3": "În partea stângă a ecranului găsiți \"Schimbare setări adaptor\" și faceți clic pe el.",
"install_devices_windows_list_4": "Selectați conexiunea activă, faceți clic dreapta pe ea și alegeți \"Proprietăți\".",
"install_devices_windows_list_5": "Găsiți Internet Protocol Versiunea 4 (TCP/IP) din listă, selectați-l și apoi faceți din nou clic pe Proprietăți.",
"install_devices_windows_list_6": "Alegeți Utilizați următoarele adrese de server DNS și introduceți adresele de server AdGuard Home.",
"install_devices_macos_list_1": "Faceți clic pe pictograma Apple și accesați Preferințele Sistemului.",
"install_devices_macos_list_2": "Faceți clic pe Network.",
"install_devices_macos_list_3": "Selectați prima conexiune din listă și faceți clic pe Avansat.",
"install_devices_macos_list_4": "Selectați fila DNS și introduceți adresele serverului dvs. AdGuard Home.",
"install_devices_android_list_1": "Din ecranul principal al Meniului Android, tapați Setări.",
"install_devices_android_list_2": "Tapați Wi-Fi din meniu. Ecranul cu toate rețelele disponibile va fi afișat (este imposibil să setați DNS personalizat pentru conexiunea mobilă).",
"install_devices_android_list_3": "Apăsați lung pe rețeaua la care sunteți conectat și tapați Modificare Rețea.",
"install_devices_android_list_4": "Pe unele aparate, poate fi necesar să bifați caseta Advanced pentru a vedea setările ulterioare. Pentru a ajusta setările DNS Android, va trebui să comutați setările IP de la DHCP la Static.",
"install_devices_android_list_5": "Schimbați valorile DNS 1 și DNS 2 la cele ale serverului dvs. AdGuard Home.",
"install_devices_ios_list_1": "Din ecranul de start, tapați Setări.",
"install_devices_ios_list_2": "Alegeți Wi-Fi în meniul din stânga (este imposibil să configurați DNS pentru rețelele mobile).",
"install_devices_ios_list_3": "Tapați numele rețelei active curente.",
"install_devices_ios_list_4": "În câmpul DNS, introduceți adresele serverului dvs. AdGuard Home.",
"get_started": "Să începem",
"next": "Următor",
"open_dashboard": "Deschideți Tabloul de bord",
"install_saved": "Salvat cu succes",
"encryption_title": "Criptare",
"encryption_desc": "Suport de Criptare (HTTPS/TLS) pentru DNS și interfața web administrator",
"encryption_config_saved": "Configurația de criptare salvată",
"encryption_server": "Nume de server",
"encryption_server_enter": "Intrați numele domeniului",
"encryption_server_desc": "Pentru a utiliza HTTPS, trebuie intrat numele serverului care corespunde certificatului SSL.",
"encryption_redirect": "Redirecționați automat la HTTPS",
"encryption_redirect_desc": "Dacă este bifat, AdGuard Home vă va redirecționa automat de la adrese HTTP la HTTPS.",
"encryption_https": "Port HTTPS",
"encryption_https_desc": "Dacă portul HTTPS este configurat, interfața administrator AdGuard Home va fi accesibilă prin HTTPS și va oferi de asemenea DNS-over-HTTPS în locația '/DNS-query'.",
"encryption_dot": "Port DNS-over-TLS",
"encryption_dot_desc": "Dacă acest port este configurat, AdGuard Home va rula un server DNS-over-TLS pe acest port.",
"encryption_certificates": "Certificate",
"encryption_certificates_desc": "Pentru a utiliza criptarea, trebuie furnizate o serie de certificate SSL valabile pentru domeniul dvs.. Puteți obține un certificat gratuit pe <0>{{link}}</0> sau îl puteți cumpăra de la una din Autoritățile Certificate de încredere.",
"encryption_certificates_input": "Copiați/lipiți certificatele dvs. PEM-codate aici.",
"encryption_status": "Statut",
"encryption_expire": "Expiră",
"encryption_key": "Cheie privată",
"encryption_key_input": "Copiați/lipiți cheia dvs. privată PEM-codată pentru certificatul dvs. aici.",
"encryption_enable": "Activați criptarea (HTTPS, DNS-over-HTTPS, și DNS-over-TLS)",
"encryption_enable_desc": "Dacă este activată criptarea, interfața administrator AdGuard Home va lucra peste HTTPS, și serverul DNS va asculta pentru cereri peste DNS-over-HTTPS și DNS-over-TLS.",
"encryption_chain_valid": "Lanțul de certificate valid",
"encryption_chain_invalid": "Lanțul de certificate nevalid",
"encryption_key_valid": "Aceasta este o cheie privată {{type}} validă",
"encryption_key_invalid": "Aceasta este o cheie privată {{type}} nevalidă",
"encryption_subject": "Obiect",
"encryption_issuer": "Emitent",
"encryption_hostnames": "Nume de host",
"encryption_reset": "Sunteți sigur că doriți să resetați setările de criptare?",
"topline_expiring_certificate": "Certificatul dvs. SSL este pe cale să expire. Actualizați <0>Setările de criptare</0>.",
"topline_expired_certificate": "Certificatul dvs. SSL a expirat. Actualizați <0>Setările de criptare</0>.",
"form_error_port_range": "Introduceți valoarea portului între 80-65535",
"form_error_port_unsafe": "Acesta este un port nesigur",
"form_error_equal": "Nu trebuie să fie egale",
"form_error_password": "Parolele nu corespund",
"reset_settings": "Resetare setări",
"update_announcement": "AdGuard Home {{version}} este disponibil! <0>Faceți clic aici</0> pentru mai multe informații.",
"setup_guide": "Ghid de instalare",
"dns_addresses": "Adrese DNS",
"dns_start": "Serverul DNS demarează",
"dns_status_error": "Eroare la verificare statut server DNS",
"down": "Down",
"fix": "Fix",
"dns_providers": "Iată o <0>listă de furnizori DNS cunoscuți</0> ce pot fi aleși.",
"update_now": "Actualizați acum",
"update_failed": "Auto-actualizarea a eșuat. Vă rugăm să <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>urmați aceste etape</a> pentru a actualiza manual.",
"processing_update": "Vă rugăm să așteptați, AdGuard Home se actualizează...",
"clients_title": "Clienți",
"clients_desc": "Configură aparatele conectate la AdGuard Home",
"settings_global": "General",
"settings_custom": "Personalizat",
"table_client": "Client",
"table_name": "Nume",
"save_btn": "Salvați",
"client_add": "Adăugați client",
"client_new": "Client nou",
"client_edit": "Editare client",
"client_identifier": "Identificator",
"ip_address": "Adresa IP",
"client_identifier_desc": "Clienții pot fi identificați prin adresa IP, CIDR, adresa MAC. Vă rugăm să rețineți că utilizarea MAC ca identificator este posibilă numai dacă AdGuard Home este și un <0>server DHCP</0>",
"form_enter_ip": "Intrați IP",
"form_enter_mac": "Intrați MAC",
"form_enter_id": "Intrați identificator",
"form_add_id": "Adăugați identificator",
"form_client_name": "Intrați nume client",
"client_global_settings": "Folosiți setări globale",
"client_deleted": "Clientul \"{{key}}\" a fost șters cu succes",
"client_added": "Clientul \"{{key}}\" a fost adăugat cu succes",
"client_updated": "Clientul \"{{key}}\" a fost adus la zi cu succes",
"clients_not_found": "Nu au fost găsiți clienți",
"client_confirm_delete": "Sunteți sigur că doriți să ștergeți clientul \"{{key}}\"?",
"list_confirm_delete": "Sigur doriți să ștergeți această listă?",
"auto_clients_title": "Clienți (runtime)",
"auto_clients_desc": "Date despre clienții folosite de AdGuard Home, dar care nu sunt stocate în configurație",
"access_title": "Setări de acces",
"access_desc": "Aici puteți configura regulile de acces pentru serverul DNS AdGuard Home.",
"access_allowed_title": "Clienți autorizați",
"access_allowed_desc": "O listă de adrese CIDR sau IP. Dacă este configurat, AdGuard Home va accepta doar cereri de la aceste adrese IP.",
"access_disallowed_title": "Clienți neautorizați",
"access_disallowed_desc": "O listă de adrese CIDR sau IP. Dacă este configurat, AdGuard Home va elimina cererile de la aceste adrese IP.",
"access_blocked_title": "Domenii blocate",
"access_blocked_desc": "Nu confundați acest lucru cu filtrele. AdGuard Home va bloca interogări DNS cu aceste domenii în întrebare.",
"access_settings_saved": "Setările de acces au fost salvate cu succes",
"updates_checked": "Actualizările au fost verificate cu succes",
"updates_version_equal": "AdGuard Home este la zi",
"check_updates_now": "Verificați actualizările acum",
"dns_privacy": "Confidențialitate DNS",
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Folosiți stringul <1>{{address}}</1>.",
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Utilizați stringul <1>{{address}}</1>.",
"setup_dns_privacy_3": "<0>Rețineți că protocoalele DNS criptate sunt acceptate numai pe Android 9. Așadar, trebuie să instalați software suplimentar pentru alte sisteme de operare.</0><0>Iată o listă de software pe care o puteți utiliza.</0>",
"setup_dns_privacy_android_1": "Android 9 acceptă nativ DNS-over-TLS. Pentru a o configura, accesați Setări → Rețea și internet → Advanced → Private DNS și introduceți numele de domeniu acolo.",
"setup_dns_privacy_android_2": "<0>AdGuard pentru Android</0> acceptă <1>DNS-over-HTTPS</1> și <1>DNS-over-TLS</1>.",
"setup_dns_privacy_android_3": "<0>Intra</0> adaugă <1>DNS-over-HTTPS</1> suport pentru Android.",
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> acceptă <1>DNS-over-HTTPS</1>, dar pentru a-l configura pentru a utiliza propriul server, va trebui să generezi un <2>DNS Stamp</2> pentru aceasta.",
"setup_dns_privacy_ios_2": "<0>AdGuard pentru iOS</0> acceptă instalarea<1>DNS-over-HTTPS</1> și <1>DNS-over-TLS</1>.",
"setup_dns_privacy_other_title": "Alte implementări",
"setup_dns_privacy_other_1": "AdGuard Home poate fi un client DNS sigur pe orice platformă.",
"setup_dns_privacy_other_2": "<0>dnsproxy</0> acceptă toate protocoalele DNS securizate cunoscute.",
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> acceptă <1>DNS-over-HTTPS</1>.",
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> acceptă <1>DNS-over-HTTPS</1>.",
"setup_dns_privacy_other_5": "Veți găsi mai multe implementări <0>aici</0> și <1>aici</1>.",
"setup_dns_notice": "Pentru a utiliza <1>DNS-over-HTTPS</1> sau <1>DNS-over-TLS</1>, trebuie să <0>configurați Criptarea</0> în setările AdGuard Home.",
"rewrite_added": "Rescriere DNS pentru \"{{key}}\" adăugat cu succes",
"rewrite_deleted": "Rescriere DNS pentru \"{{key}}\" ștearsă cu succes",
"rewrite_add": "Adăugați rescriere DNS",
"rewrite_not_found": "Nu s-au găsit rescrieri DNS",
"rewrite_confirm_delete": "Sunteți sigur că doriți să ștergeți rescrierea DNS pentru \"{{key}}\"?",
"rewrite_desc": "Permite configurarea cu ușurință a răspunsului personalizat DNS pentru un nume de domeniu specific.",
"rewrite_applied": "Regula de rescriere aplicată",
"dns_rewrites": "Rescrieri DNS",
"form_domain": "Intrați un nume de domeniu sau wildcard",
"form_answer": "Intrați adresa IP sau numele de domeniu",
"form_error_domain_format": "Format de răspuns nevalid",
"form_error_answer_format": "Format de răspuns nevalid",
"configure": "Configurați",
"main_settings": "Setări principale",
"block_services": "Blocare anumite servicii",
"blocked_services": "Servicii blocate",
"blocked_services_desc": "Permite blocarea rapidă a site-urilor și serviciilor populare.",
"blocked_services_saved": "Serviciile blocate au fost salvate cu succes",
"blocked_services_global": "Folosiți servicii blocate globale",
"blocked_service": "Serviciu blocat",
"block_all": "Blocați tot",
"unblock_all": "Deblocați tot",
"encryption_certificate_path": "Locația certificatului",
"encryption_private_key_path": "Locația cheii private",
"encryption_certificates_source_path": "Precizați locația certificatelor",
"encryption_certificates_source_content": "Lipiți conținutul certificatelor",
"encryption_key_source_path": "Precizați un fișier cu cheie privată",
"encryption_key_source_content": "Lipiți conținutul cheii private",
"stats_params": "Configurația statisticilor",
"config_successfully_saved": "Configurarea a fost salvată cu succes",
"interval_24_hour": "24 ore",
"interval_days": "{{count}} zi",
"interval_days_plural": "{{count}} zile",
"domain": "Domeniu",
"answer": "Răspuns",
"filter_added_successfully": "Filtrul a fost adăugat cu succes",
"filter_updated": "Filtrul a fost actualizat cu succes",
"statistics_configuration": "Configurația statisticilor",
"statistics_retention": "Statistică retenții",
"statistics_retention_desc": "Dacă reduceți valoarea intervalului, unele date vor fi pierdute",
"statistics_clear": " Șterge statisticile",
"statistics_clear_confirm": "Sunteți sigur că doriți să ștergeți statisticile?",
"statistics_retention_confirm": "Sunteți sigur că doriți să schimbați retenția statisticilor? Dacă reduceți valoarea intervalului, unele date vor fi pierdute",
"statistics_cleared": "Statisticile au fost șterse cu succes",
"interval_hours": "{{count}} oră",
"interval_hours_plural": "{{count}} ore",
"filters_configuration": "Configurația filtrelor",
"filters_enable": "Activați filtrele",
"filters_interval": "Interval de actualizare filtre",
"disabled": "Dezactivat",
"username_label": "Nume utilizator",
"username_placeholder": "Intrați numele de utilizator",
"password_label": "Parola",
"password_placeholder": "Intrați parola",
"sign_in": "Conectare",
"sign_out": "Deconectare",
"forgot_password": "Ați uitat parola?",
"forgot_password_desc": "Vă rugăm să urmați <0>aceste etape</0> pentru a crea o nouă parolă pentru contul de utilizator.",
"location": "Locația",
"orgname": "Numele organizației",
"netname": "Numele rețelei",
"descr": "Descriere",
"whois": "Whois",
"filtering_rules_learn_more": "<0>Aflați mai multe</0> despre crearea propriilor dvs. liste de blocaj de hosts.",
"blocked_by_response": "Blocat de CNAME sau IP ca răspuns",
"try_again": "Încercați din nou",
"domain_desc": "Intrați un nume de domeniu sau wildcard care doriți să fie rescris.",
"example_rewrite_domain": "rescrie răspunsuri numai pentru acest nume de domeniu.",
"example_rewrite_wildcard": "rescrie răspunsuri pentru toate subdomeniile <0>exemplu.org</0>.",
"disable_ipv6": "Dezactivați IPv6",
"disable_ipv6_desc": "Dacă această opțiune este activată, toate interogările DNS pentru adrese IPv6 (tip AAAA) vor fi anulate.",
"autofix_warning_text": "Dacă faceți clic pe \"Fix\", AdGuardHome va configura sistemul dvs. pentru a utiliza serverul DNS AdGuardHome.",
"autofix_warning_list": "Va efectua aceste sarcini: <0>Dezactivare sistem DNSStubListener</0> <0>Setare adresă server DNS la 127.0.0.1</0> <0>Înlocuire țintei legăturii simbolice a /etc/resolv.conf pentru /run/systemd/resolve/resolv.conf</0> <0>Oprire DNSStubListener (reîncărcare servici rezolvat prin sistem)</0>",
"autofix_warning_result": "Ca urmare, toate cererile DNS de la sistemul dvs. vor fi procesate în mod implicit de AdGuardHome.",
"tags_title": "Etichete",
"tags_desc": "Puteți selecta etichetele care corespund clientului. Etichetele pot fi incluse în regulile de filtrare și vă permit să le aplicați mai exact. <0>Aflați mai multe</0>",
"form_select_tags": "Selectați etichete client",
"check_title": "Verificați filtrarea",
"check_desc": "Verificați dacă numele de host este filtrat",
"check": "Verificați",
"form_enter_host": "Intrați un nume de host",
"filtered_custom_rules": "Filtrat prin reguli de filtrare personalizate",
"host_whitelisted": "Numele de host este în lista albă",
"check_ip": "Adrese IP: {{ip}}",
"check_cname": "CNAME: {{cname}}",
"check_reason": "Cauza: {{reason}}",
"check_rule": "Regula: {{rule}}",
"check_service": "Nume servici: {{service}}",
"check_not_found": "Nu se găsește în listele de filtre",
"client_confirm_block": "Sunteți sigur că doriți să blocați clientul \"{{ip}}\"?",
"client_confirm_unblock": "Sunteți sigur că doriți să deblocați clientul \"{{ip}}\"?",
"client_blocked": "Clientul \"{{ip}}\" blocat cu succes",
"client_unblocked": "Clientul \"{{ip}}\" deblocat cu succes",
"static_ip": "Adresa IP Statică",
"static_ip_desc": "AdGuard Home este un server, deci are nevoie de o adresă IP statică pentru a funcționa corect. Altfel, routerul dvs. poate eventual să atribuie o adresă IP diferită acestui dispozitiv.",
"set_static_ip": "Setați o adresă IP statică",
"install_static_ok": "Vești bune! Adresa IP statică este deja configurată",
"install_static_error": "AdGuard Home nu o poate configura automat pentru această interfață de rețea. Vă rugăm să căutați instrucțiuni despre cum să faceți acest lucru manual.",
"install_static_configure": "Am detectat că se folosește o adresă IP dinamică - <0>{{ip}}</0>. Vreți să o folosiți pe aceasta ca adresă statică?",
"confirm_static_ip": "AdGuard Home va configura {{ip}} ca adresa dvs. IP statică. Doriți să continuați?",
"list_updated": "{{count}} listă actualizată",
"list_updated_plural": "{{count}} liste actualizate"
}

View File

@@ -0,0 +1,416 @@
{
"client_settings": "การตั้งค่าไคลเอนต์",
"example_upstream_reserved": "คุณสามารถระบุ DNS อัปสตรีม <0>สำหรับโดเมนเฉพาะ</0>",
"upstream_parallel": "ใช้การสืบค้นแบบขนานเพื่อเพิ่มความเร็วในการแก้ไขโดยการสอบถามเซิร์ฟเวอร์ upstream ทั้งหมดพร้อมกัน",
"bootstrap_dns": "Bootstrap เซิร์ฟเวอร์ DNS",
"bootstrap_dns_desc": "เซิร์ฟเวอร์ Bootstrap DNS ใช้เพื่อแก้ไขที่อยู่ IP ของตัวแก้ไข DoH / DoT ที่คุณระบุว่าเป็น upstreams",
"check_dhcp_servers": "ตรวจสอบ DHCP servers",
"save_config": "บันทึกการตั้งค่า",
"enabled_dhcp": "เปิดการใช้งาน DHCP server แล้ว",
"disabled_dhcp": "ปิดการใช้งาน DHCP server แล้ว",
"dhcp_title": "DHCP server (ยังไม่สมบูรณ์)",
"dhcp_description": "ถ้าหากเราเตอร์ของคุณไม่รองรับการตั้งค่า DHCP คุณสามารถใช้ ADGuard's ทำ DHCP server ได้",
"dhcp_enable": "เปิด DHCP server",
"dhcp_disable": "ปิด DHCP server",
"dhcp_not_found": "มีความปลอดภัยในการเปิดใช้งานเซิร์ฟเวอร์ DHCP ในตัว - เราไม่พบเซิร์ฟเวอร์ DHCP ที่ใช้งานอยู่ในเครือข่าย อย่างไรก็ตามเราขอแนะนำให้คุณตรวจสอบด้วยตนเองอีกครั้งเนื่องจากการทดสอบอัตโนมัติของเราไม่ได้รับประกัน 100%",
"dhcp_found": "พบเซิร์ฟเวอร์ DHCP ที่ใช้งานอยู่ในเครือข่าย ไม่ปลอดภัยที่จะเปิดใช้งานเซิร์ฟเวอร์ DHCP ในตัว",
"dhcp_leases": "สัญญาเช่า DHCP",
"dhcp_static_leases": "DHCP แบบกำหนด",
"dhcp_leases_not_found": "ไม่พบสัญญาเช่า DHCP",
"dhcp_config_saved": "บันทึกการกำหนดค่า DHCP สำเร็จแล้ว",
"form_error_required": "ช่องที่ต้องกรอก",
"form_error_ip4_format": "รูปแบบ IPv4 ไม่ถูกต้อง",
"form_error_ip6_format": "รูปแบบ IPv6 ไม่ถูกต้อง",
"form_error_ip_format": "รูปแบบ IP ไม่ถูกต้อง",
"form_error_mac_format": "รูปแบบ MAC ไม่ถูกต้อง",
"form_error_client_id_format": "รูปแบบ ID ลูกค้าไม่ถูกต้อง",
"form_error_positive": "ต้องมากกว่า 0",
"form_error_negative": "ต้องเท่ากับ 0 หรือมากกว่า",
"dhcp_form_gateway_input": "IP ของเกตเวย์",
"dhcp_form_subnet_input": "ซับเน็ตมาสก์",
"dhcp_form_range_title": "ช่วงของที่อยู่ IP",
"dhcp_form_range_start": "ช่วงเริ่มต้น",
"dhcp_form_range_end": "ช่วงสิ้นสุด",
"dhcp_form_lease_title": "เวลาเช่า DHCP (เป็นวินาที)",
"dhcp_form_lease_input": "ระยะเวลาการเช่า",
"dhcp_interface_select": "เลือกอินเตอร์เฟส DHCP",
"dhcp_hardware_address": "ที่อยู่ฮาร์ดแวร์",
"dhcp_ip_addresses": "ที่อยู่ IP",
"dhcp_table_hostname": "ชื่อโฮสต์",
"dhcp_table_expires": "วันที่หมดอายุ",
"dhcp_warning": "หากคุณต้องการเปิดใช้งานเซิร์ฟเวอร์ DHCP ตรวจสอบให้แน่ใจว่าไม่มีเซิร์ฟเวอร์ DHCP ที่ใช้งานอยู่ในเครือข่ายของคุณ มิฉะนั้นจะทำให้อินเทอร์เน็ตสำหรับอุปกรณ์ที่เชื่อมต่อมีปัญหาได้!",
"dhcp_error": "เราไม่สามารถระบุได้ว่ามีเซิร์ฟเวอร์ DHCP อื่นในเครือข่ายหรือไม่",
"dhcp_static_ip_error": "ในการใช้เซิร์ฟเวอร์ DHCP จะต้องตั้งค่าที่อยู่ IP แบบคงที่ เราไม่สามารถระบุได้ว่ามีการกำหนดค่าอินเทอร์เฟซเครือข่ายนี้โดยใช้ที่อยู่ IP แบบคงที่หรือไม่ โปรดตั้งค่าที่อยู่ IP แบบคงที่ด้วยตนเอง",
"dhcp_dynamic_ip_found": "ระบบของคุณใช้การกำหนดค่าที่อยู่ IP แบบไดนามิกสำหรับอินเทอร์เฟซ <0>{{interfaceName}}</0> ในการใช้เซิร์ฟเวอร์ DHCP จะต้องตั้งค่าที่อยู่ IP แบบคงที่ ที่อยู่ IP ปัจจุบันของคุณคือ <0>{{ipAddress}}</0> เราจะตั้งค่าที่อยู่ IP นี้เป็นแบบคงที่โดยอัตโนมัติหากคุณกดปุ่มเปิดใช้งาน DHCP",
"dhcp_lease_added": "เพิ่มสัญญาเช่าคงที่ \"{{key}}\" สำเร็จแล้ว",
"dhcp_lease_deleted": "ลบสัญญาเช่าคงที่ \"{{key}}\" สำเร็จแล้ว",
"dhcp_new_static_lease": "เช่าใหม่คงที่",
"dhcp_static_leases_not_found": "ไม่พบสัญญาเช่า DHCP แบบคงที่",
"dhcp_add_static_lease": "เพิ่มสัญญาเช่าคงที่",
"dhcp_reset": "คุณแน่ใจหรือว่าต้องการรีเซ็ตการกำหนดค่า DHCP?",
"delete_confirm": "คุณแน่ใจหรือว่าต้องการลบ \"{{key}}\"?",
"form_enter_hostname": "ป้อนชื่อโฮสต์",
"error_details": "รายละเอียดข้อผิดพลาด",
"back": "กลับ",
"dashboard": "แผงควบคุม",
"settings": "การตั้งค่า",
"filters": "ตัวกรอง",
"query_log": "บันทึกการสืบค้น",
"faq": "คำถามที่พบบ่อย",
"version": "รุ่น",
"address": "ที่อยู่",
"on": "เปิด",
"off": "ปิด",
"copyright": "ลิขสิทธิ์",
"homepage": "หน้าหลัก",
"report_an_issue": "รายงานปัญหา",
"privacy_policy": "นโยบายความเป็นส่วนตัว",
"enable_protection": "เปิดใช้งานการป้องกัน",
"enabled_protection": "เปิดใช้งานการป้องกันแล้ว",
"disable_protection": "ปิดใช้งานการป้องกัน",
"disabled_protection": "ปิดใช้งานการป้องกันแล้ว",
"refresh_statics": "รีเฟรชสถิติ",
"dns_query": "การค้นหา DNS",
"blocked_by": "<0>ถูกปิดกั้นโดยตัวกรอง</0>",
"stats_malware_phishing": "ปิดกั้นมัลแวร์/ฟิชชิ่ง แล้ว",
"stats_adult": "ปิดกั้นเว็บไซต์สำหรับผู้ใหญ่แล้ว",
"stats_query_domain": "โดเมนที่เข้าบ่อยสุด",
"for_last_24_hours": "ในช่วง 24 ชั่วโมงที่ผ่านมา",
"for_last_days": "สำหรับ {{count}} วันสุดท้าย",
"for_last_days_plural": "สำหรับ {{count}} วันล่าสุด",
"no_domains_found": "ไม่พบโดเมน",
"requests_count": "จำนวนคำขอ",
"top_blocked_domains": "โดเมนที่ถูกปิดกั้นมากที่สุด",
"top_clients": "ลูกข่ายที่ใช้งานบ่อยสุด",
"no_clients_found": "ไม่มีเครื่องลูกข่าย",
"general_statistics": "สถิติทั่วไป",
"number_of_dns_query_days": "จำนวนการสืบค้น DNS ที่ประมวลผลสำหรับ {{count}} วันล่าสุด",
"number_of_dns_query_days_plural": "จำนวนการสืบค้น DNS ที่ดำเนินการในช่วง {{count}} วันล่าสุด",
"number_of_dns_query_24_hours": "มีการสืบค้น DNS จำนวนมากใน 24 ชั่วโมงที่ผ่านมา",
"number_of_dns_query_blocked_24_hours": "จำนวนคำขอ DNS ที่ถูกปิดกั้นโดยตัวกรองปิดกั้นและโฮสต์รายการปิดกั้น",
"number_of_dns_query_blocked_24_hours_by_sec": "คำขอ DNS จำนวนหนึ่งถูกปิดกั้นโดยโมดูลความปลอดภัยการเรียกดู AdGuard",
"number_of_dns_query_blocked_24_hours_adult": "มีการปิดกั้นเว็บไซต์สำหรับผู้ใหญ่จำนวนหนึ่ง",
"enforced_save_search": "บังคับใช้การค้นหาที่ปลอดภัย",
"number_of_dns_query_to_safe_search": "จำนวนคำขอ DNS ไปยังเครื่องมือค้นหาที่บังคับใช้การค้นหาปลอดภัย",
"average_processing_time": "เวลาประมวลผลโดยเฉลี่ย",
"average_processing_time_hint": "เวลาเฉลี่ยเป็นมิลลิวินาทีในการประมวลผลคำขอ DNS",
"block_domain_use_filters_and_hosts": "ปิดกั้นโดเมนโดยใช้ตัวกรองและไฟล์โฮสต์",
"filters_block_toggle_hint": "คุณสามารถตั้งค่ากฎการปิดกั้นในการตั้งค่า<a href='#filters'>ตัวกรอง</a>",
"use_adguard_browsing_sec": "ใช้บริการเว็บการรักษาความปลอดภัยการเรียกดู AdGuard",
"use_adguard_browsing_sec_hint": "AdGuard Home จะตรวจสอบว่าโดเมนอยู่ในรายการที่ไม่อนุญาตโดยเว็บเซอร์วิสความปลอดภัยการสืบค้นหรือไม่ จะใช้ API การค้นหาที่เป็นมิตรกับข้อมูลส่วนบุคคลเพื่อทำการตรวจสอบ: มีการส่งคำนำหน้าสั้น ๆ ของชื่อโดเมน SHA256 แฮชไปยังเซิร์ฟเวอร์",
"use_adguard_parental": "ใช้บริการเว็บการควบคุมโดยผู้ปกครองของ AdGuard",
"use_adguard_parental_hint": "AdGuard Home จะตรวจสอบว่าโดเมนมีเนื้อหาสำหรับผู้ใหญ่หรือไม่ มันใช้ API ความเป็นส่วนตัวเช่นเดียวกับบริการเว็บการรักษาความปลอดภัยการท่องเว็บ",
"enforce_safe_search": "บังคับใช้การค้นหาที่ปลอดภัย",
"enforce_save_search_hint": "AdGuard Home สามารถบังคับใช้การค้นหาที่ปลอดภัยในเครื่องมือค้นหาต่อไปนี้: Google, Youtube, Bing, DuckDuckGo, Yandex, Pixabay",
"no_servers_specified": "ไม่ได้ระบุเซิร์ฟเวอร์",
"general_settings": "การตั้งค่าทั่วไป",
"dns_settings": "การตั้งค่า DNS",
"encryption_settings": "การตั้งค่าการเข้ารหัส",
"dhcp_settings": "การตั้งค่า DHCP",
"upstream_dns": "เซิร์ฟเวอร์ DNS ต้นทาง",
"upstream_dns_hint": "The current approved translation is not correct, please review my translation:\n\"หากคุณเว้นช่องนี้ว่างไว้ AdGuard Home จะใช้ <a href='https://www.quad9.net/' target='_blank'>Quad9</a> เป็นต้นทาง\"",
"test_upstream_btn": "ทดสอบต้นทาง",
"upstreams": "ต้นทาง",
"apply_btn": "นำไปใช้",
"disabled_filtering_toast": "ปิดใช้งานการกรอง",
"enabled_filtering_toast": "เปิดใช้งานการกรอง",
"disabled_safe_browsing_toast": "ปิดใช้งานการเรียกดูอย่างปลอดภัย",
"enabled_safe_browsing_toast": "เปิดการใช้งาน safebrowsing",
"disabled_parental_toast": "ปิดใช้งานการควบคุมโดยผู้ปกครอง",
"enabled_parental_toast": "เปิดการใช้งานเข้าเว็บไม่พึงประสงค์",
"disabled_safe_search_toast": "ปิดใช้งานการค้นหาที่ปลอดภัย",
"enabled_save_search_toast": "เปิดใช้งานการค้นหาที่ปลอดภัย",
"enabled_table_header": "เปิดใช้งาน",
"name_table_header": "ชื่อ",
"rules_count_table_header": "กฎการนับ",
"last_time_updated_table_header": "ปรับปรุงครั้งล่าสุด",
"actions_table_header": "การกระทำ",
"edit_table_action": "แก้ไข",
"delete_table_action": "ลบ",
"filters_and_hosts_hint": "AdGuard Home เข้าใจกฎปิดกั้นโฆษณาพื้นฐานและโฮสต์ไฟล์ไวยากรณ์",
"cancel_btn": "ยกเลิก",
"enter_name_hint": "ป้อนชื่อ",
"enter_url_hint": "ป้อน URL",
"check_updates_btn": "ตรวจสอบการปรับปรุง",
"custom_filter_rules": "กฎการกรองที่กำหนดเอง",
"custom_filter_rules_hint": "ป้อนหนึ่งกฎในหนึ่งบรรทัด คุณสามารถใช้กฎปิดกั้นโฆษณาหรือโฮสต์ไฟล์ไวยากรณ์",
"examples_title": "ตัวอย่าง",
"example_meaning_filter_block": "ปิดกั้นการเข้าถึงโดเมน example.org และโดเมนย่อยทั้งหมด",
"example_meaning_filter_whitelist": "เลิกปิดกั้นการเข้าถึงโดเมน example.org และโดเมนย่อยทั้งหมด",
"example_meaning_host_block": "ตอนนี้ AdGuard Home จะส่งคืนที่อยู่ 127.0.0.1 สำหรับโดเมน example.org (แต่ไม่ใช่โดเมนย่อย)",
"example_comment": "! นี่ความคิดเห็น",
"example_comment_meaning": "เพียงความคิดเห็น",
"example_comment_hash": "# นอกจากนี้ยังมีความคิดเห็น",
"example_regex_meaning": "ปิดกั้นการเข้าถึงโดเมนที่ตรงกับนิพจน์ทั่วไปที่ระบุ",
"example_upstream_regular": "DNS ปกติ (มากกว่า UDP)",
"example_upstream_dot": "encrypted <0>DNS-over-TLS</0> แล้ว",
"example_upstream_doh": "เข้ารหัส <0>DNS-over-HTTPS</0> แล้ว",
"example_upstream_sdns": "คุณสามรถใช้ <0>DNS Stamps</0> กับ <1>DNSCrypt</1> หรือ <2>DNS-over-HTTPS</2> ตัวแก้ปัญหา",
"example_upstream_tcp": "dNS ปกติ (ผ่าน TCP)",
"updated_upstream_dns_toast": "อัปเดตเซิร์ฟเวอร์ DNS ต้นทาง",
"dns_test_ok_toast": "เซิร์ฟเวอร์ DNS ที่ระบุทำงานอย่างถูกต้อง",
"dns_test_not_ok_toast": "เซิร์ฟเวอร์ \"{{key}}\": ไม่สามารถใช้งานได้ โปรดตรวจสอบว่าคุณเขียนถูกต้อง",
"unblock_btn": "เลิกปิดกั้น",
"block_btn": "ปิดกั้น",
"time_table_header": "เวลา",
"domain_name_table_header": "ชื่อโดเมน",
"type_table_header": "ประเภท",
"response_table_header": "การตอบสนอง",
"client_table_header": "เครื่องลูกข่าย",
"empty_response_status": "ว่างเปล่า",
"show_all_filter_type": "แสดงทั้งหมด",
"show_filtered_type": "แสดงเฉพาะที่กรองแล้ว",
"no_logs_found": "ไม่มีประวัติ",
"refresh_btn": "รีเฟรช",
"previous_btn": "ก่อนหน้า",
"next_btn": "ถัดไป",
"loading_table_status": "กำลังโหลด...",
"page_table_footer_text": "หน้า",
"rows_table_footer_text": "ตาราง",
"updated_custom_filtering_toast": "อัปเดตกฎการกรองที่กำหนดเอง",
"rule_removed_from_custom_filtering_toast": "ลบกฎออกจากกฎการกรองที่กำหนดเองแล้ว",
"rule_added_to_custom_filtering_toast": "เพิ่มกฎในกฎการกรองที่กำหนดเองแล้ว",
"query_log_response_status": "สถานะ: {{value}}",
"query_log_filtered": "กรองโดย {{filter}}",
"query_log_confirm_clear": "คุณแน่ใจหรือไม่ว่าต้องการลบบันทึกการใช้งานทั้งหมด?",
"query_log_cleared": "บันทึกการใช้งานได้รับการล้างเรียบร้อยแล้ว",
"query_log_clear": "ล้างบันทึกการสืบค้น",
"query_log_retention": "แบบสอบถามบันทึกการเก็บรักษา",
"query_log_enable": "เปิดใช้งานบันทึก",
"query_log_configuration": "บันทึกการกำหนดค่า",
"query_log_disabled": "บันทึกแบบสอบถามถูกปิดใช้งานและสามารถกำหนดค่าใน <0>การตั้งค่า</0>",
"query_log_strict_search": "ใช้เครื่องหมายคำพูดคู่เพื่อการค้นหาที่จำกัด",
"query_log_retention_confirm": "คุณแน่ใจหรือไม่ว่าต้องการเปลี่ยนการเก็บข้อมูลบันทึกแบบสอบถาม? หากคุณลดค่าช่วงเวลา ข้อมูลบางอย่างจะหายไป",
"dns_config": "การกำหนดค่าเซิร์ฟเวอร์ DNS",
"blocking_mode": "โหมดการปิดกั้น",
"default": "ค่าเริ่มต้น",
"nxdomain": "NXDOMAIN",
"null_ip": "IP ว่าง",
"custom_ip": "IP กำหนดเอง",
"blocking_ipv4": "ปิดกั้น IPv4",
"blocking_ipv6": "ปิดกั้น IPv6",
"form_enter_rate_limit": "ป้อนขีดจำกัดอัตรา",
"rate_limit": "จำกัดอัตรา",
"edns_enable": "เปิดใช้งานซับเน็ตไคลเอ็นต์ EDNS",
"edns_cs_desc": "หากเปิดใช้งาน AdGuard Home จะส่งซับเน็ตของไคลเอนต์ไปยังเซิร์ฟเวอร์ DNS",
"rate_limit_desc": "จำนวนการร้องขอต่อวินาทีที่อนุญาตให้ไคลเอนต์เดียวทำ (0: ไม่จำกัดจำนวน)",
"blocking_ipv4_desc": "ที่อยู่ IP ที่จะส่งคืนสำหรับคำขอที่ถูกปิดกั้น",
"blocking_ipv6_desc": "ที่อยู่ IP ที่จะส่งคืนสำหรับคำขอ AAAA ที่ถูกปิดกั้น",
"blocking_mode_default": "เริ่มต้น: ตอบสนองด้วย NXDOMAIN เมื่อถูกปิดกั้นโดยกฎสไตล์ปิดกั้นโฆษณา; ตอบกลับด้วยที่อยู่ IP ที่ระบุในกฎเมื่อถูกปิดกั้นโดยกฎ /etc/hosts-hosts",
"blocking_mode_nxdomain": "NXDOMAIN: ตอบสนองด้วยรหัส NXDOMAIN",
"blocking_mode_null_ip": "Null IP: ตอบกลับด้วยที่อยู่เลขศูนย์ IP (0.0.0.0 สำหรับ A; :: สำหรับ AAAA)",
"blocking_mode_custom_ip": "IP ที่กำหนดเอง: ตอบกลับด้วยที่อยู่ IP ที่ตั้งค่าด้วยตนเอง",
"upstream_dns_client_desc": "หากคุณเว้นช่องนี้ว่างไว้ AdGuard Home จะใช้เซิร์ฟเวอร์ที่กำหนดค่าใน <0>การตั้งค่า DNS</0>",
"source_label": "ที่มา",
"found_in_known_domain_db": "พบในฐานข้อมูลโดเมนที่รู้จัก",
"category_label": "ประเภท",
"rule_label": "กฎ",
"unknown_filter": "ตัวกรองที่ไม่รู้จัก {{filterId}}",
"install_welcome_title": "ยินดีต้อนรับสู่ AdGuard Home",
"install_welcome_desc": "AdGuard Home เป็นเซิร์ฟเวอร์ DNS ปิดกั้นโฆษณาและติดตามทั่วทั้งเครือข่าย วัตถุประสงค์คือเพื่อให้คุณควบคุมเครือข่ายทั้งหมดและอุปกรณ์ทั้งหมดของคุณและไม่จำเป็นต้องใช้โปรแกรมฝั่งไคลเอ็นต์",
"install_settings_title": "รูปแบบเว็บสำหรับผู้ดูแล",
"install_settings_listen": "รูปแบบการดักจับ",
"install_settings_port": "พอร์ต",
"install_settings_interface_link": "เว็บอินเตอร์เฟสผู้ดูแลระบบ AdGuard Home ของคุณจะพร้อมใช้งานตามที่อยู่ต่อไปนี้:",
"form_error_port": "ป้อนค่าพอร์ตที่ถูกต้อง",
"install_settings_dns": "เซิรฟ์เวอร์ DNS",
"install_settings_dns_desc": "คุณจะต้องกำหนดค่าอุปกรณ์หรือเราเตอร์ของคุณเพื่อใช้เซิร์ฟเวอร์ DNS ตามที่อยู่ต่อไปนี้:",
"install_settings_all_interfaces": "อินเทอร์เฟซทั้งหมด",
"install_auth_title": "การตรวจสอบสิทธิ์",
"install_auth_desc": "ขอแนะนำอย่างยิ่งให้กำหนดค่าการตรวจสอบรหัสผ่านให้กับส่วนต่อประสานเว็บผู้ดูแลระบบ AdGuard Home ของคุณ แม้ว่ามันจะสามารถเข้าถึงได้เฉพาะในเครือข่ายท้องถิ่นของคุณก็ยังคงเป็นสิ่งสำคัญที่จะปกป้องมันจากการเข้าถึงที่ไม่จำกัด",
"install_auth_username": "ชื่อผู้ใช้",
"install_auth_password": "รหัสผ่าน",
"install_auth_confirm": "ยืนยันรหัสผ่าน",
"install_auth_username_enter": "กรอกชื่อผู้ใช้",
"install_auth_password_enter": "กรอกรหัสผ่าน",
"install_step": "ขั้นตอน",
"install_devices_title": "กำหนดค่าอุปกรณ์ของคุณ",
"install_devices_desc": "ในการเริ่มใช้งาน AdGuard Home คุณต้องกำหนดค่าอุปกรณ์ของคุณเพื่อใช้งาน",
"install_submit_title": "ยินดีด้วย!",
"install_submit_desc": "ขั้นตอนการตั้งค่าเสร็จสิ้นและคุณพร้อมที่จะเริ่มใช้งาน AdGuard Home",
"install_devices_router": "เราเตอร์",
"install_devices_router_desc": "การตั้งค่านี้จะครอบคลุมอุปกรณ์ทั้งหมดที่เชื่อมต่อกับเราเตอร์ที่บ้านของคุณโดยอัตโนมัติและคุณไม่จำเป็นต้องกำหนดค่าแต่ละอุปกรณ์ด้วยตนเอง",
"install_devices_address": "เซิร์ฟเวอร์ DNS ของ AdGuard Home กำลังรับฟังตามที่อยู่ต่อไปนี้",
"install_devices_router_list_1": "เปิดการตั้งค่าสำหรับเราเตอร์ของคุณ โดยปกติแล้วคุณสามารถเข้าถึงได้จากเบราว์เซอร์ของคุณผ่าน URL (เช่น http://192.168.0.1/ หรือ http://192.168.1.1/) คุณอาจถูกขอให้ป้อนรหัสผ่าน หากคุณจำไม่ได้คุณสามารถรีเซ็ตรหัสผ่านได้บ่อยครั้งโดยกดปุ่มบนเราเตอร์เอง เราเตอร์บางตัวต้องการแอปพลิเคชั่นเฉพาะซึ่งในกรณีนี้ควรติดตั้งไว้ในคอมพิวเตอร์/โทรศัพท์ของคุณแล้ว",
"install_devices_router_list_2": "ค้นหาการตั้งค่า DHCP/DNS ค้นหาตัวอักษร DNS ที่อยู่ถัดจากช่องที่อนุญาตให้มีตัวเลขสองหรือสามชุดโดยแต่ละกลุ่มแบ่งออกเป็นสี่กลุ่มหนึ่งถึงสามหลัก",
"install_devices_router_list_3": "ป้อนที่อยู่เซิร์ฟเวอร์ AdGuard Home ของคุณที่นั่น",
"install_devices_windows_list_1": "เปิด Control Panel โดยใช้ Start menu หรือ Windows search",
"install_devices_windows_list_2": "ไปที่หมวด Network and Internet แล้วเลือก Network and Sharing Center",
"install_devices_windows_list_3": "ทางด้านซ้ายจะมีคำว่า Change adapter settings ให้กดเข้าไป",
"install_devices_windows_list_4": "เลือกการเชื่อมต่อที่ใช้งานอยู่ คลิกขวาแล้วเลือก Properties",
"install_devices_windows_list_5": "ค้นหา Internet Protocol Version 4 (TCP/IP) แล้วคลิก Properties อีกครั้ง",
"install_devices_windows_list_6": "ค้นหา DNS server addresses ให้ทำการกรอกหมายเลข AdGuard Home ลงไปในช่อง",
"install_devices_macos_list_1": "คลิกโลโก้แอปเปิ้ลแล้วกด System Preferences",
"install_devices_macos_list_2": "คลิก Network",
"install_devices_macos_list_3": "เลือกการเชื่อมต่อแล้วคลิก Advanced",
"install_devices_macos_list_4": "ค้นหาแท็บ DNS แล้วกรอกหมาเลย AdGuard Home",
"install_devices_android_list_1": "เข้าหน้าเมนู(บางรุ่นจะมีตรงแท็บการแจ้งเตือน) เลือกการตั้งค่า",
"install_devices_android_list_2": "เลือกเมนู Wi-Fi แล้วค้นหา Wi-Fi ที่จะเชื่อมต่อ (ไม่สารถตั้งค่ากับเน็ตมือถือได้)",
"install_devices_android_list_3": "แตะชื่อWi-Fi ที่จะเชื่อมต่อค้างไว้(บางรุ่นให้เลื่อนจอลงไปล่างสุด) เลือกการตั้งค่าเพิ่มเติม",
"install_devices_android_list_4": "ในอุปกรณ์บางอย่างคุณอาจต้องทำเครื่องหมายในช่องสำหรับขั้นสูงเพื่อดูการตั้งค่าเพิ่มเติม หากต้องการปรับการตั้งค่า Android DNS ของคุณคุณจะต้องเปลี่ยนการตั้งค่า IP จาก DHCP เป็นแบบคงที่",
"install_devices_android_list_5": "เปลี่ยนการตั้งค่า DNS ที่ 1 และค่า DNS 2 ถึงที่อยู่เซิร์ฟเวอร์ AdGuard Home ของคุณ",
"install_devices_ios_list_1": "เลือกการตั้งค่า",
"install_devices_ios_list_2": "เลือก Wi-Fi ด้านซ้าย (ไม่สามรถใช้งานได้กับดาต้ามือถือ)",
"install_devices_ios_list_3": "เลือกชื่อที่จะเชื่อมต่อ",
"install_devices_ios_list_4": "กรอก DNS AdGuard Home Server ลงไปในช่อง",
"get_started": "เริ่มต้นการใช้งาน",
"next": "ถัดไป",
"open_dashboard": "เปิดหน้าควบคุม",
"install_saved": "บันทึกเรียบร้อยแล้ว",
"encryption_title": "การเข้ารหัส",
"encryption_desc": "การดข้ารหัส (HTTPS/TLS) รองรับทั้ง DNS และหน้าเว็บแอดมิน",
"encryption_config_saved": "บันทึกการตั้งค่าเข้ารหัสเรียบร้อยแล้ว",
"encryption_server": "ชื่อเซิร์ฟเวอร์",
"encryption_server_enter": "ป้อนชื่อโดเมน",
"encryption_server_desc": "ในการใช้ HTTPS คุณต้องป้อนชื่อเซิร์ฟเวอร์ที่ตรงกับใบรับรอง SSL ของคุณ",
"encryption_redirect": "ไปเส้นทาง HTTPS อัตโนมัติ",
"encryption_redirect_desc": "หากเลือกตัวเลือกนี้ AdGuard Home จะเปลี่ยนเส้นทางคุณจากที่อยู่ HTTP ไปยัง HTTPS โดยอัตโนมัติ",
"encryption_https": "พอร์ท HTTPS",
"encryption_https_desc": "หากมีการกำหนดค่าพอร์ต HTTPS ส่วนติดต่อผู้ดูแลระบบของ AdGuard Home จะสามารถเข้าถึงได้ผ่าน HTTPS และจะให้ DNS-over-HTTPS ในตำแหน่ง '/dns-query'",
"encryption_dot": "พอร์ต DNS-over-TLS",
"encryption_dot_desc": "หากมีการกำหนดค่าพอร์ตนี้ AdGuard Home จะเรียกใช้เซิร์ฟเวอร์ DNS-over-TLS ในพอร์ตนี้",
"encryption_certificates": "ใบรับรอง",
"encryption_certificates_desc": "ในการใช้การเข้ารหัสคุณต้องระบุเชนใบรับรอง SSL ที่ถูกต้องสำหรับโดเมนของคุณ คุณสามารถรับใบรับรองฟรีได้ที่ <0>{{link}}</0> หรือคุณสามารถซื้อได้จากหนึ่งในผู้ออกใบรับรองที่เชื่อถือได้",
"encryption_certificates_input": "คัดลอก/วางใบรับรองที่เข้ารหัส PEM ของคุณที่นี่",
"encryption_status": "สถานะ",
"encryption_expire": "หมดอายุ",
"encryption_key": "รหัสส่วนตัว (Private key)",
"encryption_key_input": "คัดลอก/วาง PEM-encoded private key ของคุณตรงนี้",
"encryption_enable": "เปิดการเข้ารหัส (HTTPS, DNS-over-HTTPS, และ DNS-over-TLS)",
"encryption_enable_desc": "หากเปิดใช้งานการเข้ารหัสอินเทอร์เฟซผู้ดูแลระบบของ AdGuard Home จะทำงานผ่าน HTTPS และเซิร์ฟเวอร์ DNS จะรับฟังคำร้องขอผ่านทาง DNS-over-HTTPS และ DNS-over-TLS",
"encryption_chain_valid": "ใบรับรองมีความน่าเชื่อถือ",
"encryption_chain_invalid": "ใบรับรองไม่มีความน่าเชื่อถือแต่สามรถใช้ได้",
"encryption_key_valid": "นี่เป็นคีย์ส่วนตัว {{type}} ที่ถูกต้อง",
"encryption_key_invalid": "นี่เป็นคีย์ส่วนตัว {{type}} ที่ไม่ถูกต้อง",
"encryption_subject": "เรื่อง:",
"encryption_issuer": "ผู้ออกใบรับรอง:",
"encryption_hostnames": "ชื่อโฮส",
"encryption_reset": "คุณแน่ใจนะว่าจะล้างค่าการเข้ารหัส?",
"topline_expiring_certificate": "ใบรับรอง SSL ของคุณกำลังจะหมดอายุ กรุณาอัปเดท <0>การตั้งค่าเข้ารหัส</0>.",
"topline_expired_certificate": "ใบรับรอง SSL ของคุณหมดอายุแล้ว กรุณาอัปเดท <0>การตั้งค่าเข้ารหัส</0>.",
"form_error_port_range": "ป้อนค่าพอร์ตในช่วง 80-65535",
"form_error_port_unsafe": "เป็นพอร์ทที่ไม่ปลอดภัย",
"form_error_equal": "ไม่ควรตรงกัน",
"form_error_password": "รหัสผ่านไม่ตรงกัน",
"reset_settings": "รีเซ็ตการตั้งค่า",
"update_announcement": "AdGuard Home {{version}} พร้อมแล้ว <0>กดตรงนี้</0> สำหรับข้อมูลเพิ่มเติม",
"setup_guide": "วิธีการตั้งค่า",
"dns_addresses": "ที่อยู่ DNS",
"dns_start": "เซิร์ฟเวอร์ DNS เริ่มทำงาน",
"dns_status_error": "เกิดข้อผิดพลาดในการตรวจสอบสถานะเซิร์ฟเวอร์ DNS",
"down": "ดับ",
"fix": "ซ่อม",
"dns_providers": "นี่คือรายการ <0>ของผู้ให้บริการ DNS ที่เป็นที่รู้จัก</0> ให้เลือก",
"update_now": "อัปเดตตอนนี้",
"update_failed": "อัปเดทล้มเหลว กรุณา <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'> ทำตามขั้นตอน </a> เพื่ออัพเดทด้วยตนเอง",
"processing_update": "รอซักครู่ AdGuard Home กำลังอัปเดท",
"clients_title": "เครื่องลูกข่าย",
"clients_desc": "ตั้งค่าอุปกรณ์เพื่อเชื่อมต่อ AdGuard Home",
"settings_global": "ทั่วโลก",
"settings_custom": "กำหนดเอง",
"table_client": "เครื่องลูกข่าย",
"table_name": "ชื่อ",
"save_btn": "บันทึก",
"client_add": "เพิ่มเครื่องลูกข่าย",
"client_new": "สร้างเครื่องลูกข่าย",
"client_edit": "แก้ไขเครื่องลูกข่าย",
"client_identifier": "ตรวจสอบโดย",
"ip_address": "IP addresses",
"client_identifier_desc": "ลูกค้าสามารถระบุได้โดยที่อยู่ IP, CIDR, ที่อยู่ MAC โปรดทราบว่าการใช้ MAC เป็นตัวระบุเป็นไปได้ก็ต่อเมื่อ AdGuard Home เป็น <0>เซิร์ฟเวอร์ DHCP</0> ด้วย",
"form_enter_ip": "กรอก IP",
"form_enter_mac": "กรอก MAC",
"form_enter_id": "ป้อนตัวระบุ",
"form_add_id": "เพิ่มตัวระบุ",
"form_client_name": "กรอกชื่อเครื่องลูกข่าย",
"client_global_settings": "ใช้การตั้งค่าทั่วโลก",
"client_deleted": "เครื่อง \"{{key}}\" ลบเรียบร้อยแล้ว",
"client_added": "เครื่อง \"{{key}}\" เพิ่มเรียบร้อยแล้ว",
"client_updated": "อัปเดตเครื่อง \"{{key}}\" สำเร็จแล้ว",
"clients_not_found": "ไม่มีเครื่องลูกข่าย",
"client_confirm_delete": "คุณแน่ใจนะว่าจะลบเครื่อง \"{{key}}\"?",
"auto_clients_title": "เครื่อง (runtime)",
"auto_clients_desc": "ข้อมูลเกี่ยวกับไคลเอนต์ที่ใช้ AdGuard Home แต่ไม่ได้เก็บไว้ในการกำหนดค่า",
"access_title": "เข้าถึงการตั้งค่า",
"access_desc": "ที่นี่คุณสามารถกำหนดค่ากฎการเข้าถึงสำหรับเซิร์ฟเวอร์ AdGuard Home DNS",
"access_allowed_title": "ลูกค้าที่ได้รับอนุญาต",
"access_allowed_desc": "รายการ CIDR หรือที่อยู่ IP หากกำหนดค่า AdGuard Home จะยอมรับคำขอจากที่อยู่ IP เหล่านี้เท่านั้น",
"access_disallowed_title": "ลูกค้าไม่ได้รับอนุญาต",
"access_disallowed_desc": "รายการ CIDR หรือที่อยู่ IP หากกำหนดค่าไว้ AdGuard Home จะส่งคำขอจากที่อยู่ IP เหล่านี้",
"access_blocked_title": "โดเมนที่ถูกปิดกั้น",
"check_updates_now": "ตรวจสอบการปรับปรุง",
"setup_dns_privacy_other_title": "การใช้งานอื่น ๆ",
"setup_dns_privacy_other_1": "AdGuard Home จะส่ง DNS ที่ปลอดภัยทุกเครื่อทุกระบบ\n",
"setup_dns_privacy_other_2": "<0>dnsproxy</0> รองรับโปรโตคอล DNS ที่ปลอดภัยที่รู้จักทั้งหมด",
"rewrite_add": "เพิ่ม DNS rewrite",
"form_domain": "ป้อนชื่อโดเมน",
"form_answer": "ป้อนชื่อโดเมนหรือ IP",
"form_error_domain_format": "รูปแบบ Domain ไม่ถูกต้อง",
"form_error_answer_format": "รูปแบบคำตอบไม่ถูกต้อง",
"configure": "กำหนดค่า",
"main_settings": "ตั้งค่าหลัก",
"block_services": "ปิดกั้นบริการเฉพาะ",
"blocked_services": "ปิดกั้นบริการ",
"blocked_services_desc": "อนุญาตให้บล็อกเว็บไซต์และบริการยอดนิยมได้อย่างรวดเร็ว",
"blocked_services_saved": "บันทึกบริการที่ถูกปิดกั้นเรียบร้อยแล้ว",
"blocked_services_global": "ใช้บริการที่ถูกบล็อกทั่วโลก",
"blocked_service": "ปิดกั้นบริการ",
"block_all": "ปิดกั้นทั้งหมด",
"unblock_all": "ปลดล็อคทั้งหมด",
"encryption_certificate_path": "เส้นทางใบรับรอง",
"encryption_private_key_path": "เส้นทางกุญแจส่วนตัว",
"encryption_certificates_source_path": "ตั้งค่าเส้นทาง certificates ",
"encryption_certificates_source_content": "วางเนื้อหา certificates ",
"encryption_key_source_path": "ตั้งค่าไฟล์กุญแจส่วนตัว",
"encryption_key_source_content": "วางเนื้อหาคีย์ส่วนตัว",
"stats_params": "การกำหนดค่าสถิติ",
"config_successfully_saved": "บันทึกการตั้งค่าเรีบยร้อยแล้ว",
"interval_24_hour": "24 ชั่วโมง",
"interval_days": "{{count}} วัน",
"interval_days_plural": "{{count}} วัน",
"domain": "โดเมน",
"answer": "คำตอบ",
"filter_added_successfully": "ตัวกรองเพิ่มเรียบร้อยแล้ว",
"filter_updated": "อัปเดตตัวกรองสำเร็จแล้ว",
"statistics_configuration": "การกำหนดค่าสถิติ",
"statistics_retention": "การเก็บรักษาสถิติ",
"statistics_retention_desc": "หากคุณลดค่าช่วงเวลาข้อมูลบางอย่างจะหายไป",
"statistics_clear": " ล้างค่าสถิติ",
"statistics_clear_confirm": "คุณแน่ใจหรือไม่ว่าต้องการล้างสถิติ?",
"statistics_retention_confirm": "คุณแน่ใจหรือไม่ว่าต้องการเปลี่ยนการเก็บรักษาสถิติ? หากคุณลดค่าช่วงเวลา ข้อมูลบางอย่างจะหายไป",
"statistics_cleared": "สถิติได้ถูกล้างเรียบร้อยแล้ว",
"interval_hours": "{{count}} ชั่วโมง",
"interval_hours_plural": "{{count}} ชั่วโมง",
"filters_configuration": "การกำหนดค่าตัวกรอง",
"filters_enable": "เปิดใช้งานตัวกรอง",
"filters_interval": "ตัวกรองช่วงเวลาการอัปเดต",
"disabled": "ปิดใช้งาน",
"username_label": "ชื่อผู้ใช้",
"username_placeholder": "ป้อนชื่อผู้ใช้",
"password_label": "รหัสผ่าน",
"password_placeholder": "ใส่รหัสผ่าน",
"sign_in": "ลงชื่อเข้าใช้",
"sign_out": "ออกจากระบบ",
"forgot_password": "ลืมรหัสผ่าน?",
"forgot_password_desc": "โปรดปฏิบัติตาม <0>ขั้นตอนเหล่านี้</0> เพื่อสร้างรหัสผ่านใหม่สำหรับบัญชีผู้ใช้ของคุณ",
"location": "ตำแหน่ง",
"orgname": "ชื่อองค์กร",
"netname": "ชื่อเครือข่าย",
"descr": "คำอธิบาย",
"whois": "Whois",
"filtering_rules_learn_more": "<0>เรียนรู้เพิ่มเติม</0> เกี่ยวกับการสร้างรายการปิดกั้นโฮสต์ของคุณเอง",
"blocked_by_response": "ปิดกั้นโดย CNAME หรือ IP ในการตอบกลับ",
"try_again": "ลองอีกครั้ง",
"domain_desc": "ป้อนชื่อโดเมนหรือไวด์การ์ดที่คุณต้องการเขียนใหม่",
"example_rewrite_domain": "เขียนคำตอบซ้ำสำหรับชื่อโดเมนนี้เท่านั้น",
"example_rewrite_wildcard": "เขียนคำตอบใหม่ทั้งหมดสำหรับ <0>example.org</0> โดเมนย่อย",
"disable_ipv6": "ปิดใช้งาน IPv6",
"disable_ipv6_desc": "หากเปิดใช้งานคุณสมบัตินี้การสืบค้น DNS ทั้งหมดสำหรับที่อยู่ IPv6 (ประเภท AAAA) จะถูกทิ้ง",
"autofix_warning_text": "หากคุณคลิก \"แก้ไข\" AdGuardHome จะกำหนดค่าระบบของคุณเพื่อใช้เซิร์ฟเวอร์ AdGuardHome",
"autofix_warning_list": "มันจะทำงานเหล่านี้: <0>ปิดการใช้งานระบบ DNSStubListener</0> <0>ตั้งที่อยู่เซิร์ฟเวอร์ DNS เป็น 127.0.0.1</0> <0>แทนที่เป้าหมายลิงก์สัญลักษณ์ของ /etc/resolv.conf เป็น /run/systemd/resolve/resolv.conf</0> <0>หยุด DNSStubListener (โหลดบริการแก้ไขระบบซ้ำ)</0>",
"autofix_warning_result": "ดังนั้น AdGuardHome จะประมวลผลคำขอ DNS ทั้งหมดจากระบบของคุณตามค่าเริ่มต้น",
"tags_title": "แท็ก",
"tags_desc": "คุณสามารถเลือกแท็กที่สอดคล้องกับลูกค้า แท็กสามารถรวมอยู่ในกฎการกรองและอนุญาตให้คุณใช้งานได้อย่างถูกต้องมากขึ้น <0>เรียนรู้เพิ่มเติม</0>",
"form_select_tags": "เลือกแท็กเครื่อง",
"check_title": "ตรวจสอบการกรอง",
"check_desc": "ตรวจสอบว่าชื่อโฮสต์ถูกกรอง"
}

View File

@@ -5,6 +5,7 @@ import round from 'lodash/round';
import Card from '../ui/Card';
import Tooltip from '../ui/Tooltip';
import { formatNumber } from '../../helpers/helpers';
const tooltipType = 'tooltip-custom--narrow';
@@ -42,7 +43,9 @@ const Counters = (props) => {
<Tooltip text={tooltipTitle} type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">{dnsQueries}</span>
<span className="text-muted">
{formatNumber(dnsQueries)}
</span>
</td>
</tr>
<tr>
@@ -56,7 +59,9 @@ const Counters = (props) => {
/>
</td>
<td className="text-right">
<span className="text-muted">{blockedFiltering}</span>
<span className="text-muted">
{formatNumber(blockedFiltering)}
</span>
</td>
</tr>
<tr>
@@ -68,7 +73,9 @@ const Counters = (props) => {
/>
</td>
<td className="text-right">
<span className="text-muted">{replacedSafebrowsing}</span>
<span className="text-muted">
{formatNumber(replacedSafebrowsing)}
</span>
</td>
</tr>
<tr>
@@ -80,7 +87,9 @@ const Counters = (props) => {
/>
</td>
<td className="text-right">
<span className="text-muted">{replacedParental}</span>
<span className="text-muted">
{formatNumber(replacedParental)}
</span>
</td>
</tr>
<tr>
@@ -92,7 +101,9 @@ const Counters = (props) => {
/>
</td>
<td className="text-right">
<span className="text-muted">{replacedSafesearch}</span>
<span className="text-muted">
{formatNumber(replacedSafesearch)}
</span>
</td>
</tr>
<tr>

View File

@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { STATUS_COLORS } from '../../helpers/constants';
import { formatNumber } from '../../helpers/helpers';
import Card from '../ui/Card';
import Line from '../ui/Line';
@@ -10,10 +11,16 @@ const StatsCard = ({
}) => (
<Card type="card--full" bodyType="card-wrap">
<div className="card-body-stats">
<div className={`card-value card-value-stats text-${color}`}>{total}</div>
<div className={`card-value card-value-stats text-${color}`}>
{formatNumber(total)}
</div>
<div className="card-title-stats">{title}</div>
</div>
{percent >= 0 && (<div className={`card-value card-value-percent text-${color}`}>{percent}</div>)}
{percent >= 0 && (
<div className={`card-value card-value-percent text-${color}`}>
{percent}
</div>
)}
<div className="card-chart-bg">
<Line data={lineData} color={STATUS_COLORS[color]} />
</div>

View File

@@ -112,6 +112,7 @@ const renderMultiselect = (props) => {
onChange={value => input.onChange(value)}
onBlur={() => input.onBlur(input.value)}
placeholder={placeholder}
blurInputOnSelect={false}
isMulti
/>
);

View File

@@ -65,6 +65,18 @@ let Form = ({
/>
</div>
</div>
<div className="col-12">
<div className="form__group form__group--settings">
<Field
name="dnssec_enabled"
type="checkbox"
component={renderSelectField}
placeholder={t('dnssec_enable')}
disabled={processing}
subtitle={t('dnssec_enable_desc')}
/>
</div>
</div>
<div className="col-12">
<div className="form__group form__group--settings">
<Field

View File

@@ -16,6 +16,7 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
blocking_ipv4,
blocking_ipv6,
edns_cs_enabled,
dnssec_enabled,
disable_ipv6,
processingSetConfig,
} = dnsConfig;
@@ -35,6 +36,7 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
blocking_ipv6,
edns_cs_enabled,
disable_ipv6,
dnssec_enabled,
}}
onSubmit={handleFormSubmit}
processing={processingSetConfig}

View File

@@ -1,18 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
const Cell = props => (
import { formatNumber } from '../../helpers/helpers';
const Cell = ({ value, percent, color }) => (
<div className="stats__row">
<div className="stats__row-value mb-1">
<strong>{props.value}</strong>
<small className="ml-3 text-muted">{props.percent}%</small>
<strong>{formatNumber(value)}</strong>
<small className="ml-3 text-muted">{percent}%</small>
</div>
<div className="progress progress-xs">
<div
className="progress-bar"
style={{
width: `${props.percent}%`,
backgroundColor: props.color,
width: `${percent}%`,
backgroundColor: color,
}}
/>
</div>

View File

@@ -84,10 +84,6 @@ const Icons = () => (
<path d="M4.8 3.2L3.2 6.397V19.2h4v2.403h3.198l2.403-2.403H16l4.8-4.8v-11.2zm14.4 10.402L16.8 16H12l-2.398 2.398V16H6.398V4.8H19.2zm0 0" /><path d="M15.2 12.8h-1.598V7.2h1.597zm-3.2 0h-1.602V7.2H12zm0 0" />
</symbol>
<symbol id="service_messenger" viewBox="0 0 24 24" fill="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
<path d="M5.602 22.398L10.398 20l-4.796-2.398zm0 0" /><path d="M12 2.398c-5.3 0-9.602 4.122-9.602 9.204C2.398 16.68 6.7 20.8 12 20.8c5.3 0 9.602-4.121 9.602-9.2 0-5.081-4.301-9.203-9.602-9.203zm.91 11.844l-2.305-2.48-4.328 2.422 4.813-5.098 2.36 2.363 4.218-2.363zm0 0" />
</symbol>
<symbol id="service_snapchat" viewBox="0 0 24 24" fill="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
<path d="M12.176 4c.715 0 3.136.191 4.277 2.668.383.828.285 2.273.211 3.437l-.004.051c-.008.164-.02.32-.027.469.015.02.164.156.492.168.25-.012.54-.086.855-.23a.784.784 0 0 1 .57.008h.005c.254.09.422.261.425.44.004.173-.128.43-.789.68a2.694 2.694 0 0 1-.25.082c-.375.118-.945.293-1.117.692-.097.215-.066.48.09.785 0 .004.004.008.004.012.047.105 1.187 2.62 3.73 3.027.094.016.16.094.153.188a.24.24 0 0 1-.024.101c-.105.238-.578.574-2.234.824-.133.02-.188.188-.266.547-.03.13-.058.258-.101.39-.035.118-.11.173-.235.173h-.02a2.34 2.34 0 0 1-.37-.043 4.986 4.986 0 0 0-.996-.102c-.23 0-.473.02-.715.059-.496.078-.918.367-1.363.672-.653.445-1.32.902-2.364.902-.047 0-.09 0-.136-.004-.028.004-.055.004-.086.004-1.043 0-1.711-.457-2.36-.902-.445-.305-.867-.594-1.363-.672a4.533 4.533 0 0 0-.719-.059c-.418 0-.75.063-.992.106a2.02 2.02 0 0 1-.371.054c-.102 0-.211-.023-.258-.18-.039-.136-.07-.269-.101-.394-.075-.328-.125-.531-.266-.55-1.656-.247-2.129-.587-2.234-.825-.012-.035-.024-.066-.024-.101a.182.182 0 0 1 .156-.188c2.54-.406 3.68-2.922 3.727-3.031.004 0 .004-.004.004-.008.156-.305.187-.57.094-.79-.176-.398-.747-.57-1.122-.687a3.147 3.147 0 0 1-.25-.082c-.75-.289-.812-.582-.785-.734.051-.254.407-.434.692-.434a.49.49 0 0 1 .207.04c.336.152.64.23.906.23.363 0 .52-.148.54-.168-.009-.164-.02-.34-.032-.52-.074-1.164-.168-2.609.21-3.433 1.138-2.477 3.555-2.668 4.27-2.668L12.133 4h.043m0-1.602h-.043l-.313.008v-.004c-.953 0-4.187.262-5.722 3.598-.387.844-.45 1.887-.422 2.922-.922.02-2 .625-2.215 1.726-.082.407-.184 1.786 1.781 2.54.012.003.02.007.031.011-.39.559-1.113 1.34-2.168 1.508-.902.14-1.55.941-1.5 1.86.016.226.067.44.153.64.41.938 1.406 1.363 2.543 1.613a1.83 1.83 0 0 0 1.785 1.305c.246 0 .465-.043.66-.078a3.44 3.44 0 0 1 .703-.082c.149 0 .305.012.465.039.14.023.457.238.711.41.73.5 1.727 1.184 3.266 1.184h.101c.04 0 .078.004.121.004 1.532 0 2.528-.68 3.258-1.176.281-.192.582-.399.723-.422.156-.024.312-.04.46-.04.259 0 .458.032.696.075.266.05.477.074.668.074.852 0 1.543-.508 1.785-1.293 1.137-.25 2.129-.672 2.535-1.593.094-.22.149-.43.16-.649a1.783 1.783 0 0 0-1.496-1.871c-1.054-.168-1.78-.95-2.172-1.508l.036-.011c1.601-.618 1.824-1.645 1.816-2.204-.02-.855-.594-1.601-1.477-1.918a2.37 2.37 0 0 0-.777-.156c.027-1.015-.039-2.078-.422-2.914-1.539-3.336-4.773-3.598-5.73-3.598zm0 0" />
</symbol>

View File

@@ -187,10 +187,6 @@ export const SERVICES = [
id: 'snapchat',
name: 'Snapchat',
},
{
id: 'messenger',
name: 'Messenger',
},
{
id: 'twitch',
name: 'Twitch',

View File

@@ -449,3 +449,12 @@ export const getCurrentFilter = (url, filters) => {
return { name: '', url: '' };
};
/**
* @param number Number to format
* @returns string Returns a string with a language-sensitive representation of this number
*/
export const formatNumber = (num) => {
const currentLanguage = i18n.languages[0] || DEFAULT_LANGUAGE;
return num.toLocaleString(currentLanguage);
};

View File

@@ -32,6 +32,8 @@ import tr from './__locales/tr.json';
import srCS from './__locales/sr-cs.json';
import hr from './__locales/hr.json';
import fa from './__locales/fa.json';
import th from './__locales/th.json';
import ro from './__locales/ro.json';
const resources = {
en: {
@@ -115,6 +117,12 @@ const resources = {
fa: {
translation: fa,
},
th: {
translation: th,
},
ro: {
translation: ro,
},
};
const availableLanguages = Object.keys(LANGUAGES);

View File

@@ -45,6 +45,7 @@ const dnsConfig = handleActions(
blocking_ipv6: DEFAULT_BLOCKING_IPV6,
edns_cs_enabled: false,
disable_ipv6: false,
dnssec_enabled: false,
},
);

View File

@@ -10,6 +10,7 @@ import (
"sync"
"time"
"github.com/AdguardTeam/golibs/file"
"github.com/AdguardTeam/golibs/log"
"github.com/krolaw/dhcp4"
ping "github.com/sparrc/go-ping"
@@ -18,6 +19,8 @@ import (
const defaultDiscoverTime = time.Second * 3
const leaseExpireStatic = 1
var webHandlersRegistered = false
// Lease contains the necessary information about a DHCP lease
// field ordering is important -- yaml fields will mirror ordering from here
type Lease struct {
@@ -41,6 +44,9 @@ type ServerConfig struct {
RangeEnd string `json:"range_end" yaml:"range_end"`
LeaseDuration uint32 `json:"lease_duration" yaml:"lease_duration"` // in seconds
// File path to an additional leases file in dnsmasq format
DnsmasqFilePath string `json:"-" yaml:"dnsmasq_leasefile"`
// IP conflict detector: time (ms) to wait for ICMP reply.
// 0: disable
ICMPTimeout uint32 `json:"icmp_timeout_msec" yaml:"icmp_timeout_msec"`
@@ -121,9 +127,6 @@ func Create(config ServerConfig) *Server {
return nil
}
}
if s.conf.HTTPRegister != nil {
s.registerHandlers()
}
// we can't delay database loading until DHCP server is started,
// because we need static leases functionality available beforehand
@@ -140,12 +143,59 @@ func (s *Server) Init(config ServerConfig) error {
return nil
}
// Save - save leases in DB
func (s *Server) Save() {
s.dbStore()
}
// SetOnLeaseChanged - set callback
func (s *Server) SetOnLeaseChanged(onLeaseChanged onLeaseChangedT) {
s.onLeaseChanged = onLeaseChanged
}
// Write DHCP leases in dnsmasq format
// Format: UNIX_TIME MAC IP HOSTNAME CLIENT_ID
func writeDnsmasqLeases(leases []Lease) string {
s := ""
for _, l := range leases {
t := l.Expiry.Unix()
if t == leaseExpireStatic {
t = 0
}
host := l.Hostname
if len(host) == 0 {
host = "*"
}
cid := "*"
s += fmt.Sprintf("%d %s %s %s %s\n",
t, l.HWAddr.String(), l.IP.String(), host, cid)
}
return s
}
func (s *Server) notify(flags int) {
if len(s.conf.DnsmasqFilePath) != 0 {
switch flags {
case LeaseChangedAdded:
fallthrough
case LeaseChangedAddedStatic:
fallthrough
case LeaseChangedRemovedStatic:
l := s.Leases(LeasesAll)
data := writeDnsmasqLeases(l)
err := file.SafeWrite(s.conf.DnsmasqFilePath, []byte(data))
if err != nil {
log.Error("file write: %s: %s", s.conf.DnsmasqFilePath, err)
}
}
}
if s.onLeaseChanged == nil {
return
}
@@ -221,6 +271,11 @@ func (s *Server) setConfig(config ServerConfig) error {
// Start will listen on port 67 and serve DHCP requests.
func (s *Server) Start() error {
if !webHandlersRegistered && s.conf.HTTPRegister != nil {
webHandlersRegistered = true
s.registerHandlers()
}
// TODO: don't close if interface and addresses are the same
if s.conn != nil {
s.closeConn()
@@ -604,6 +659,11 @@ func (s *Server) handleDecline(p dhcp4.Packet, options dhcp4.Options) dhcp4.Pack
// AddStaticLease adds a static lease (thread-safe)
func (s *Server) AddStaticLease(l Lease) error {
return s.AddStaticLeaseWithFlags(l, true)
}
// AddStaticLeaseWithFlags - add a static lease (thread-safe)
func (s *Server) AddStaticLeaseWithFlags(l Lease, flush bool) error {
if len(l.IP) != 4 {
return fmt.Errorf("Invalid IP")
}
@@ -623,7 +683,9 @@ func (s *Server) AddStaticLease(l Lease) error {
}
s.leases = append(s.leases, &l)
s.reserveIP(l.IP, l.HWAddr)
s.dbStore()
if flush {
s.dbStore()
}
s.leasesLock.Unlock()
s.notify(LeaseChangedAddedStatic)
return nil

View File

@@ -242,3 +242,15 @@ func TestNormalizeLeases(t *testing.T) {
assert.True(t, bytes.Equal(leases[1].HWAddr, []byte{2, 2, 3, 4}))
assert.True(t, bytes.Equal(leases[2].HWAddr, []byte{1, 2, 3, 5}))
}
func TestWriteDnsmasqLeases(t *testing.T) {
leases := []Lease{}
l := Lease{}
l.Expiry = time.Unix(1587559766, 0)
l.HWAddr, _ = net.ParseMAC("12:34:12:34:12:34")
l.IP = net.ParseIP("192.168.8.2")
l.Hostname = "hostname"
leases = append(leases, l)
data := "1587559766 12:34:12:34:12:34 192.168.8.2 hostname *\n"
assert.Equal(t, data, writeDnsmasqLeases(leases))
}

View File

@@ -1,10 +1,9 @@
package home
package dnsfilter
import (
"encoding/json"
"net/http"
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
)
@@ -21,10 +20,9 @@ type svc struct {
// client/src/components/ui/Icons.js
var serviceRulesArray = []svc{
{"whatsapp", []string{"||whatsapp.net^", "||whatsapp.com^"}},
{"facebook", []string{"||facebook.com^", "||facebook.net^", "||fbcdn.net^", "||fb.me^", "||fb.com^", "||fbsbx.com^"}},
{"facebook", []string{"||facebook.com^", "||facebook.net^", "||fbcdn.net^", "||fb.me^", "||fb.com^", "||fbsbx.com^", "||messenger.com^"}},
{"twitter", []string{"||twitter.com^", "||t.co^", "||twimg.com^"}},
{"youtube", []string{"||youtube.com^", "||ytimg.com^", "||youtu.be^", "||googlevideo.com^", "||youtubei.googleapis.com^"}},
{"messenger", []string{"||fb.com^", "||facebook.com^", "||messenger.com^"}},
{"twitch", []string{"||twitch.tv^", "||ttvnw.net^"}},
{"netflix", []string{"||nflxext.com^", "||netflix.com^"}},
{"instagram", []string{"||instagram.com^", "||cdninstagram.com^"}},
@@ -120,7 +118,7 @@ var serviceRulesArray = []svc{
}
// convert array to map
func initServices() {
func initBlockedServices() {
serviceRules = make(map[string][]*rules.NetworkRule)
for _, s := range serviceRulesArray {
netRules := []*rules.NetworkRule{}
@@ -136,9 +134,20 @@ func initServices() {
}
}
// BlockedSvcKnown - return TRUE if a blocked service name is known
func BlockedSvcKnown(s string) bool {
_, ok := serviceRules[s]
return ok
}
// ApplyBlockedServices - set blocked services settings for this DNS request
func ApplyBlockedServices(setts *dnsfilter.RequestFilteringSettings, list []string) {
setts.ServicesRules = []dnsfilter.ServiceEntry{}
func (d *Dnsfilter) ApplyBlockedServices(setts *RequestFilteringSettings, list []string, global bool) {
setts.ServicesRules = []ServiceEntry{}
if global {
d.confLock.RLock()
defer d.confLock.RUnlock()
list = d.Config.BlockedServices
}
for _, name := range list {
rules, ok := serviceRules[name]
@@ -147,51 +156,45 @@ func ApplyBlockedServices(setts *dnsfilter.RequestFilteringSettings, list []stri
continue
}
s := dnsfilter.ServiceEntry{}
s := ServiceEntry{}
s.Name = name
s.Rules = rules
setts.ServicesRules = append(setts.ServicesRules, s)
}
}
func handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
config.RLock()
list := config.DNS.BlockedServices
config.RUnlock()
func (d *Dnsfilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
d.confLock.RLock()
list := d.Config.BlockedServices
d.confLock.RUnlock()
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(list)
if err != nil {
httpError(w, http.StatusInternalServerError, "json.Encode: %s", err)
httpError(r, w, http.StatusInternalServerError, "json.Encode: %s", err)
return
}
}
func handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {
func (d *Dnsfilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {
list := []string{}
err := json.NewDecoder(r.Body).Decode(&list)
if err != nil {
httpError(w, http.StatusBadRequest, "json.Decode: %s", err)
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
return
}
config.Lock()
config.DNS.BlockedServices = list
config.Unlock()
d.confLock.Lock()
d.Config.BlockedServices = list
d.confLock.Unlock()
log.Debug("Updated blocked services list: %d", len(list))
err = writeAllConfigsAndReloadDNS()
if err != nil {
httpError(w, http.StatusBadRequest, "%s", err)
return
}
httpOK(r, w)
d.ConfigModified()
}
// RegisterBlockedServicesHandlers - register HTTP handlers
func RegisterBlockedServicesHandlers() {
httpRegister(http.MethodGet, "/control/blocked_services/list", handleBlockedServicesList)
httpRegister(http.MethodPost, "/control/blocked_services/set", handleBlockedServicesSet)
// registerBlockedServicesHandlers - register HTTP handlers
func (d *Dnsfilter) registerBlockedServicesHandlers() {
d.Config.HTTPRegister("GET", "/control/blocked_services/list", d.handleBlockedServicesList)
d.Config.HTTPRegister("POST", "/control/blocked_services/set", d.handleBlockedServicesSet)
}

View File

@@ -49,6 +49,10 @@ type Config struct {
Rewrites []RewriteEntry `yaml:"rewrites"`
// Names of services to block (globally).
// Per-client settings can override this configuration.
BlockedServices []string `yaml:"blocked_services"`
// Called when the configuration is changed by HTTP request
ConfigModified func() `yaml:"-"`
@@ -175,6 +179,7 @@ func (d *Dnsfilter) WriteDiskConfig(c *Config) {
d.confLock.Lock()
*c = d.Config
c.Rewrites = rewriteArrayDup(d.Config.Rewrites)
// BlockedServices
d.confLock.Unlock()
}
@@ -633,6 +638,18 @@ func New(c *Config, blockFilters []Filter) *Dnsfilter {
d.prepareRewrites()
}
bsvcs := []string{}
for _, s := range d.BlockedServices {
if !BlockedSvcKnown(s) {
log.Debug("skipping unknown blocked-service '%s'", s)
continue
}
bsvcs = append(bsvcs, s)
}
d.BlockedServices = bsvcs
initBlockedServices()
if blockFilters != nil {
err := d.initFiltering(nil, blockFilters)
if err != nil {
@@ -655,6 +672,7 @@ func (d *Dnsfilter) Start() {
if d.Config.HTTPRegister != nil { // for tests
d.registerSecurityHandlers()
d.registerRewritesHandlers()
d.registerBlockedServicesHandlers()
}
}

View File

@@ -136,6 +136,8 @@ type FilteringConfig struct {
EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option
EnableDNSSEC bool `yaml:"enable_dnssec"` // Set DNSSEC flag in outcoming DNS request
// Respond with an empty answer to all AAAA requests
AAAADisabled bool `yaml:"aaaa_disabled"`
@@ -179,6 +181,9 @@ type ServerConfig struct {
FilteringConfig
TLSConfig
TLSAllowUnencryptedDOH bool
TLSv12Roots *x509.CertPool // list of root CAs for TLSv1.2
// Called when the configuration is changed by HTTP request
ConfigModified func()
@@ -338,6 +343,7 @@ func (s *Server) Prepare(config *ServerConfig) error {
MinVersion: tls.VersionTLS12,
}
}
upstream.RootCAs = s.conf.TLSv12Roots
if len(proxyConfig.Upstreams) == 0 {
log.Fatal("len(proxyConfig.Upstreams) == 0")
@@ -508,6 +514,7 @@ type dnsContext struct {
err error // error returned from the module
protectionEnabled bool // filtering is enabled, dnsfilter object is ready
responseFromUpstream bool // response is received from upstream servers
origReqDNSSEC bool // DNSSEC flag in the original request from user
}
const (
@@ -584,6 +591,18 @@ func processUpstream(ctx *dnsContext) int {
}
}
if s.conf.EnableDNSSEC {
opt := d.Req.IsEdns0()
if opt == nil {
log.Debug("DNS: Adding OPT record with DNSSEC flag")
d.Req.SetEdns0(4096, true)
} else if !opt.Do() {
opt.SetDo(true)
} else {
ctx.origReqDNSSEC = true
}
}
// request was not filtered so let it be processed further
err := s.dnsProxy.Resolve(d)
if err != nil {
@@ -595,6 +614,50 @@ func processUpstream(ctx *dnsContext) int {
return resultDone
}
// Process DNSSEC after response from upstream server
func processDNSSECAfterResponse(ctx *dnsContext) int {
d := ctx.proxyCtx
if !ctx.responseFromUpstream || // don't process response if it's not from upstream servers
!ctx.srv.conf.EnableDNSSEC {
return resultDone
}
optResp := d.Res.IsEdns0()
if !ctx.origReqDNSSEC && optResp != nil && optResp.Do() {
return resultDone
}
// Remove RRSIG records from response
// because there is no DO flag in the original request from client,
// but we have EnableDNSSEC set, so we have set DO flag ourselves,
// and now we have to clean up the DNS records our client didn't ask for.
answers := []dns.RR{}
for _, a := range d.Res.Answer {
switch a.(type) {
case *dns.RRSIG:
log.Debug("Removing RRSIG record from response: %v", a)
default:
answers = append(answers, a)
}
}
d.Res.Answer = answers
answers = []dns.RR{}
for _, a := range d.Res.Ns {
switch a.(type) {
case *dns.RRSIG:
log.Debug("Removing RRSIG record from response: %v", a)
default:
answers = append(answers, a)
}
}
d.Res.Ns = answers
return resultDone
}
// Apply filtering logic after we have received response from upstream servers
func processFilteringAfterResponse(ctx *dnsContext) int {
s := ctx.srv
@@ -602,10 +665,6 @@ func processFilteringAfterResponse(ctx *dnsContext) int {
res := ctx.result
var err error
if !ctx.responseFromUpstream {
return resultDone // don't process response if it's not from upstream servers
}
if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 {
d.Req.Question[0] = ctx.origQuestion
d.Res.Question[0] = ctx.origQuestion
@@ -684,14 +743,19 @@ func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error {
processInitial,
processFilteringBeforeRequest,
processUpstream,
processDNSSECAfterResponse,
processFilteringAfterResponse,
processQueryLogsAndStats,
}
for _, process := range mods {
r := process(ctx)
switch r {
case resultDone:
// continue: call the next filter
case resultFinish:
return nil
case resultError:
return ctx.err
}

View File

@@ -28,6 +28,7 @@ type dnsConfigJSON struct {
BlockingIPv4 string `json:"blocking_ipv4"`
BlockingIPv6 string `json:"blocking_ipv6"`
EDNSCSEnabled bool `json:"edns_cs_enabled"`
DNSSECEnabled bool `json:"dnssec_enabled"`
DisableIPv6 bool `json:"disable_ipv6"`
}
@@ -40,6 +41,7 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
resp.BlockingIPv6 = s.conf.BlockingIPv6
resp.RateLimit = s.conf.Ratelimit
resp.EDNSCSEnabled = s.conf.EnableEDNSClientSubnet
resp.DNSSECEnabled = s.conf.EnableDNSSEC
resp.DisableIPv6 = s.conf.AAAADisabled
s.RUnlock()
@@ -119,6 +121,10 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
restart = true
}
if js.Exists("dnssec_enabled") {
s.conf.EnableDNSSEC = req.DNSSECEnabled
}
if js.Exists("disable_ipv6") {
s.conf.AAAADisabled = req.DisableIPv6
}
@@ -376,6 +382,20 @@ func checkDNS(input string, bootstrap []string) error {
return nil
}
func (s *Server) handleDOH(w http.ResponseWriter, r *http.Request) {
if !s.conf.TLSAllowUnencryptedDOH && r.TLS == nil {
httpError(r, w, http.StatusNotFound, "Not Found")
return
}
if !s.IsRunning() {
httpError(r, w, http.StatusInternalServerError, "DNS server is not running")
return
}
s.ServeHTTP(w, r)
}
func (s *Server) registerHandlers() {
s.conf.HTTPRegister("GET", "/control/dns_info", s.handleGetConfig)
s.conf.HTTPRegister("POST", "/control/dns_config", s.handleSetConfig)
@@ -384,4 +404,6 @@ func (s *Server) registerHandlers() {
s.conf.HTTPRegister("GET", "/control/access/list", s.handleAccessList)
s.conf.HTTPRegister("POST", "/control/access/set", s.handleAccessSet)
s.conf.HTTPRegister("", "/dns-query", s.handleDOH)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 187 KiB

View File

@@ -14,6 +14,7 @@ import (
"time"
"github.com/AdguardTeam/AdGuardHome/dhcpd"
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
"github.com/AdguardTeam/AdGuardHome/dnsforward"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/log"
@@ -24,6 +25,8 @@ const (
clientsUpdatePeriod = 1 * time.Hour
)
var webHandlersRegistered = false
// Client information
type Client struct {
IDs []string
@@ -98,15 +101,29 @@ func (clients *clientsContainer) Init(objects []clientObject, dhcpServer *dhcpd.
clients.addFromConfig(objects)
if !clients.testing {
go clients.periodicUpdate()
clients.addFromDHCP()
clients.dhcpServer.SetOnLeaseChanged(clients.onDHCPLeaseChanged)
clients.registerWebHandlers()
}
}
// Start - start the module
func (clients *clientsContainer) Start() {
if !clients.testing {
if !webHandlersRegistered {
webHandlersRegistered = true
clients.registerWebHandlers()
}
go clients.periodicUpdate()
}
}
// Reload - reload auto-clients
func (clients *clientsContainer) Reload() {
clients.addFromHostsFile()
clients.addFromSystemARP()
}
type clientObject struct {
Name string `yaml:"name"`
Tags []string `yaml:"tags"`
@@ -140,11 +157,18 @@ func (clients *clientsContainer) addFromConfig(objects []clientObject) {
SafeBrowsingEnabled: cy.SafeBrowsingEnabled,
UseOwnBlockedServices: !cy.UseGlobalBlockedServices,
BlockedServices: cy.BlockedServices,
Upstreams: cy.Upstreams,
}
for _, s := range cy.BlockedServices {
if !dnsfilter.BlockedSvcKnown(s) {
log.Debug("Clients: skipping unknown blocked-service '%s'", s)
continue
}
cli.BlockedServices = append(cli.BlockedServices, s)
}
for _, t := range cy.Tags {
if !clients.tagKnown(t) {
log.Debug("Clients: skipping unknown tag '%s'", t)
@@ -187,8 +211,7 @@ func (clients *clientsContainer) WriteDiskConfig(objects *[]clientObject) {
func (clients *clientsContainer) periodicUpdate() {
for {
clients.addFromHostsFile()
clients.addFromSystemARP()
clients.Reload()
time.Sleep(clientsUpdatePeriod)
}
}

View File

@@ -2,7 +2,6 @@ package home
import (
"io/ioutil"
"net/http"
"os"
"path/filepath"
"sync"
@@ -29,14 +28,6 @@ type logSettings struct {
Verbose bool `yaml:"verbose"` // If true, verbose logging is enabled
}
// HTTPSServer - HTTPS Server
type HTTPSServer struct {
server *http.Server
cond *sync.Cond // reacts to config.TLS.Enabled, PortHTTPS, CertificateChain and PrivateKey
sync.Mutex // protects config.TLS
shutdown bool // if TRUE, don't restart the server
}
// configuration is loaded from YAML
// field ordering is important -- yaml fields will mirror ordering from here
type configuration struct {
@@ -51,6 +42,7 @@ type configuration struct {
BindHost string `yaml:"bind_host"` // BindHost is the IP address of the HTTP server to bind to
BindPort int `yaml:"bind_port"` // BindPort is the port the HTTP server
Users []User `yaml:"users"` // Users that can access HTTP server
ProxyURL string `yaml:"http_proxy"` // Proxy address for our HTTP client
Language string `yaml:"language"` // two-letter ISO 639-1 language code
RlimitNoFile uint `yaml:"rlimit_nofile"` // Maximum number of opened fd's per process (0: default)
@@ -58,8 +50,8 @@ type configuration struct {
// An active session is automatically refreshed once a day.
WebSessionTTLHours uint32 `yaml:"web_session_ttl"`
DNS dnsConfig `yaml:"dns"`
TLS tlsConfig `yaml:"tls"`
DNS dnsConfig `yaml:"dns"`
TLS tlsConfigSettings `yaml:"tls"`
Filters []filter `yaml:"filters"`
WhitelistFilters []filter `yaml:"whitelist_filters"`
@@ -94,10 +86,6 @@ type dnsConfig struct {
FilteringEnabled bool `yaml:"filtering_enabled"` // whether or not use filter lists
FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` // time period to update filters (in hours)
DnsfilterConf dnsfilter.Config `yaml:",inline"`
// Names of services to block (globally).
// Per-client settings can override this configuration.
BlockedServices []string `yaml:"blocked_services"`
}
type tlsConfigSettings struct {
@@ -113,33 +101,6 @@ type tlsConfigSettings struct {
dnsforward.TLSConfig `yaml:",inline" json:",inline"`
}
// field ordering is not important -- these are for API and are recalculated on each run
type tlsConfigStatus struct {
ValidCert bool `yaml:"-" json:"valid_cert"` // ValidCert is true if the specified certificates chain is a valid chain of X509 certificates
ValidChain bool `yaml:"-" json:"valid_chain"` // ValidChain is true if the specified certificates chain is verified and issued by a known CA
Subject string `yaml:"-" json:"subject,omitempty"` // Subject is the subject of the first certificate in the chain
Issuer string `yaml:"-" json:"issuer,omitempty"` // Issuer is the issuer of the first certificate in the chain
NotBefore time.Time `yaml:"-" json:"not_before,omitempty"` // NotBefore is the NotBefore field of the first certificate in the chain
NotAfter time.Time `yaml:"-" json:"not_after,omitempty"` // NotAfter is the NotAfter field of the first certificate in the chain
DNSNames []string `yaml:"-" json:"dns_names"` // DNSNames is the value of SubjectAltNames field of the first certificate in the chain
// key status
ValidKey bool `yaml:"-" json:"valid_key"` // ValidKey is true if the key is a valid private key
KeyType string `yaml:"-" json:"key_type,omitempty"` // KeyType is one of RSA or ECDSA
// is usable? set by validator
ValidPair bool `yaml:"-" json:"valid_pair"` // ValidPair is true if both certificate and private key are correct
// warnings
WarningValidation string `yaml:"-" json:"warning_validation,omitempty"` // WarningValidation is a validation warning message with the issue description
}
// field ordering is important -- yaml fields will mirror ordering from here
type tlsConfig struct {
tlsConfigSettings `yaml:",inline" json:",inline"`
tlsConfigStatus `yaml:"-" json:",inline"`
}
// initialize to default values, will be changed later when reading config or parsing command line
var config = configuration{
BindPort: 3000,
@@ -159,11 +120,9 @@ var config = configuration{
FilteringEnabled: true, // whether or not use filter lists
FiltersUpdateIntervalHours: 24,
},
TLS: tlsConfig{
tlsConfigSettings: tlsConfigSettings{
PortHTTPS: 443,
PortDNSOverTLS: 853, // needs to be passed through to dnsproxy
},
TLS: tlsConfigSettings{
PortHTTPS: 443,
PortDNSOverTLS: 853, // needs to be passed through to dnsproxy
},
DHCP: dhcpd.ServerConfig{
LeaseDuration: 86400,
@@ -237,12 +196,6 @@ func parseConfig() error {
config.DNS.FiltersUpdateIntervalHours = 24
}
status := tlsConfigStatus{}
if !tlsLoadConfig(&config.TLS, &status) {
log.Error("%s", status.WarningValidation)
return err
}
return nil
}
@@ -271,6 +224,11 @@ func (c *configuration) write() error {
if Context.auth != nil {
config.Users = Context.auth.GetUsers()
}
if Context.tls != nil {
tlsConf := tlsConfigSettings{}
Context.tls.WriteDiskConfig(&tlsConf)
config.TLS = tlsConf
}
if Context.stats != nil {
sdc := stats.DiskConfig{}
@@ -320,20 +278,3 @@ func (c *configuration) write() error {
return nil
}
func writeAllConfigs() error {
err := config.write()
if err != nil {
log.Error("Couldn't write config: %s", err)
return err
}
userFilter := userFilter()
err = userFilter.save()
if err != nil {
log.Error("Couldn't save the user filter: %s", err)
return err
}
return nil
}

View File

@@ -9,8 +9,6 @@ import (
"strconv"
"strings"
"github.com/AdguardTeam/AdGuardHome/util"
"github.com/AdguardTeam/AdGuardHome/dnsforward"
"github.com/AdguardTeam/golibs/log"
"github.com/NYTimes/gziphandler"
@@ -27,9 +25,6 @@ func returnOK(w http.ResponseWriter) {
}
}
func httpOK(r *http.Request, w http.ResponseWriter) {
}
func httpError(w http.ResponseWriter, code int, format string, args ...interface{}) {
text := fmt.Sprintf(format, args...)
log.Info(text)
@@ -39,15 +34,6 @@ func httpError(w http.ResponseWriter, code int, format string, args ...interface
// ---------------
// dns run control
// ---------------
func writeAllConfigsAndReloadDNS() error {
err := writeAllConfigs()
if err != nil {
log.Error("Couldn't write all configs: %s", err)
return err
}
return reconfigureDNSServer()
}
func addDNSAddress(dnsAddresses *[]string, addr string) {
if config.DNS.Port != 53 {
addr = fmt.Sprintf("%s:%d", addr, config.DNS.Port)
@@ -55,46 +41,6 @@ func addDNSAddress(dnsAddresses *[]string, addr string) {
*dnsAddresses = append(*dnsAddresses, addr)
}
// Get the list of DNS addresses the server is listening on
func getDNSAddresses() []string {
dnsAddresses := []string{}
if config.DNS.BindHost == "0.0.0.0" {
ifaces, e := util.GetValidNetInterfacesForWeb()
if e != nil {
log.Error("Couldn't get network interfaces: %v", e)
return []string{}
}
for _, iface := range ifaces {
for _, addr := range iface.Addresses {
addDNSAddress(&dnsAddresses, addr)
}
}
} else {
addDNSAddress(&dnsAddresses, config.DNS.BindHost)
}
if config.TLS.Enabled && len(config.TLS.ServerName) != 0 {
if config.TLS.PortHTTPS != 0 {
addr := config.TLS.ServerName
if config.TLS.PortHTTPS != 443 {
addr = fmt.Sprintf("%s:%d", addr, config.TLS.PortHTTPS)
}
addr = fmt.Sprintf("https://%s/dns-query", addr)
dnsAddresses = append(dnsAddresses, addr)
}
if config.TLS.PortDNSOverTLS != 0 {
addr := fmt.Sprintf("tls://%s:%d", config.TLS.ServerName, config.TLS.PortDNSOverTLS)
dnsAddresses = append(dnsAddresses, addr)
}
}
return dnsAddresses
}
func handleStatus(w http.ResponseWriter, r *http.Request) {
c := dnsforward.FilteringConfig{}
if Context.dnsServer != nil {
@@ -144,23 +90,6 @@ func handleGetProfile(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write(data)
}
// --------------
// DNS-over-HTTPS
// --------------
func handleDOH(w http.ResponseWriter, r *http.Request) {
if !config.TLS.AllowUnencryptedDOH && r.TLS == nil {
httpError(w, http.StatusNotFound, "Not Found")
return
}
if !isRunning() {
httpError(w, http.StatusInternalServerError, "DNS server is not running")
return
}
Context.dnsServer.ServeHTTP(w, r)
}
// ------------------------
// registration of handlers
// ------------------------
@@ -172,16 +101,16 @@ func registerControlHandlers() {
httpRegister(http.MethodPost, "/control/update", handleUpdate)
httpRegister("GET", "/control/profile", handleGetProfile)
RegisterFilteringHandlers()
RegisterTLSHandlers()
RegisterBlockedServicesHandlers()
RegisterAuthHandlers()
http.HandleFunc("/dns-query", postInstall(handleDOH))
}
func httpRegister(method string, url string, handler func(http.ResponseWriter, *http.Request)) {
if len(method) == 0 {
// "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method
http.HandleFunc(url, postInstall(handler))
return
}
http.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler)))))
}
@@ -259,14 +188,16 @@ func preInstallHandler(handler http.Handler) http.Handler {
// it also enforces HTTPS if it is enabled and configured
func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if Context.firstRun &&
!strings.HasPrefix(r.URL.Path, "/install.") &&
r.URL.Path != "/favicon.png" {
http.Redirect(w, r, "/install.html", http.StatusSeeOther) // should not be cacheable
http.Redirect(w, r, "/install.html", http.StatusFound)
return
}
// enforce https?
if config.TLS.ForceHTTPS && r.TLS == nil && config.TLS.Enabled && config.TLS.PortHTTPS != 0 && Context.httpsServer.server != nil {
if r.TLS == nil && Context.web.forceHTTPS && Context.web.httpsServer.server != nil {
// yes, and we want host from host:port
host, _, err := net.SplitHostPort(r.Host)
if err != nil {
@@ -276,13 +207,14 @@ func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.Res
// construct new URL to redirect to
newURL := url.URL{
Scheme: "https",
Host: net.JoinHostPort(host, strconv.Itoa(config.TLS.PortHTTPS)),
Host: net.JoinHostPort(host, strconv.Itoa(Context.web.portHTTPS)),
Path: r.URL.Path,
RawQuery: r.URL.RawQuery,
}
http.Redirect(w, r, newURL.String(), http.StatusTemporaryRedirect)
return
}
w.Header().Set("Access-Control-Allow-Origin", "*")
handler(w, r)
}

View File

@@ -33,7 +33,7 @@ type filterAddJSON struct {
Whitelist bool `json:"whitelist"`
}
func handleFilteringAddURL(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringAddURL(w http.ResponseWriter, r *http.Request) {
fj := filterAddJSON{}
err := json.NewDecoder(r.Body).Decode(&fj)
if err != nil {
@@ -53,52 +53,41 @@ func handleFilteringAddURL(w http.ResponseWriter, r *http.Request) {
}
// Set necessary properties
f := filter{
filt := filter{
Enabled: true,
URL: fj.URL,
Name: fj.Name,
white: fj.Whitelist,
}
f.ID = assignUniqueFilterID()
filt.ID = assignUniqueFilterID()
// Download the filter contents
ok, err := f.update()
ok, err := f.update(&filt)
if err != nil {
httpError(w, http.StatusBadRequest, "Couldn't fetch filter from url %s: %s", f.URL, err)
return
}
if f.RulesCount == 0 {
httpError(w, http.StatusBadRequest, "Filter at the url %s has no rules (maybe it points to blank page?)", f.URL)
httpError(w, http.StatusBadRequest, "Couldn't fetch filter from url %s: %s", filt.URL, err)
return
}
if !ok {
httpError(w, http.StatusBadRequest, "Filter at the url %s is invalid (maybe it points to blank page?)", f.URL)
return
}
// Save the filter contents
err = f.save()
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to save filter %d due to %s", f.ID, err)
httpError(w, http.StatusBadRequest, "Filter at the url %s is invalid (maybe it points to blank page?)", filt.URL)
return
}
// URL is deemed valid, append it to filters, update config, write new filter file and tell dns to reload it
if !filterAdd(f) {
httpError(w, http.StatusBadRequest, "Filter URL already added -- %s", f.URL)
if !filterAdd(filt) {
httpError(w, http.StatusBadRequest, "Filter URL already added -- %s", filt.URL)
return
}
onConfigModified()
enableFilters(true)
_, err = fmt.Fprintf(w, "OK %d rules\n", f.RulesCount)
_, err = fmt.Fprintf(w, "OK %d rules\n", filt.RulesCount)
if err != nil {
httpError(w, http.StatusInternalServerError, "Couldn't write body: %s", err)
}
}
func handleFilteringRemoveURL(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Request) {
type request struct {
URL string `json:"url"`
@@ -156,7 +145,7 @@ type filterURLReq struct {
Data filterURLJSON `json:"data"`
}
func handleFilteringSetURL(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request) {
fj := filterURLReq{}
err := json.NewDecoder(r.Body).Decode(&fj)
if err != nil {
@@ -169,12 +158,12 @@ func handleFilteringSetURL(w http.ResponseWriter, r *http.Request) {
return
}
f := filter{
filt := filter{
Enabled: fj.Data.Enabled,
Name: fj.Data.Name,
URL: fj.Data.URL,
}
status := filterSetProperties(fj.URL, f, fj.Whitelist)
status := f.filterSetProperties(fj.URL, filt, fj.Whitelist)
if (status & statusFound) == 0 {
http.Error(w, "URL doesn't exist", http.StatusBadRequest)
return
@@ -196,7 +185,7 @@ func handleFilteringSetURL(w http.ResponseWriter, r *http.Request) {
if fj.Whitelist {
flags = FilterRefreshAllowlists
}
nUpdated, _ := refreshFilters(flags, true)
nUpdated, _ := f.refreshFilters(flags, true)
// if at least 1 filter has been updated, refreshFilters() restarts the filtering automatically
// if not - we restart the filtering ourselves
restart = false
@@ -209,7 +198,7 @@ func handleFilteringSetURL(w http.ResponseWriter, r *http.Request) {
}
}
func handleFilteringSetRules(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringSetRules(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to read request body: %s", err)
@@ -218,15 +207,10 @@ func handleFilteringSetRules(w http.ResponseWriter, r *http.Request) {
config.UserRules = strings.Split(string(body), "\n")
onConfigModified()
userFilter := userFilter()
err = userFilter.save()
if err != nil {
log.Error("Couldn't save the user filter: %s", err)
}
enableFilters(true)
}
func handleFilteringRefresh(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringRefresh(w http.ResponseWriter, r *http.Request) {
type Req struct {
White bool `json:"whitelist"`
}
@@ -248,7 +232,7 @@ func handleFilteringRefresh(w http.ResponseWriter, r *http.Request) {
if req.White {
flags = FilterRefreshAllowlists
}
resp.Updated, err = refreshFilters(flags|FilterRefreshForce, false)
resp.Updated, err = f.refreshFilters(flags|FilterRefreshForce, false)
Context.controlLock.Lock()
if err != nil {
httpError(w, http.StatusInternalServerError, "%s", err)
@@ -298,7 +282,7 @@ func filterToJSON(f filter) filterJSON {
}
// Get filtering configuration
func handleFilteringStatus(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringStatus(w http.ResponseWriter, r *http.Request) {
resp := filteringConfig{}
config.RLock()
resp.Enabled = config.DNS.FilteringEnabled
@@ -327,7 +311,7 @@ func handleFilteringStatus(w http.ResponseWriter, r *http.Request) {
}
// Set filtering configuration
func handleFilteringConfig(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleFilteringConfig(w http.ResponseWriter, r *http.Request) {
req := filteringConfig{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
@@ -359,13 +343,13 @@ type checkHostResp struct {
IPList []net.IP `json:"ip_addrs"` // list of IP addresses
}
func handleCheckHost(w http.ResponseWriter, r *http.Request) {
func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
host := q.Get("name")
setts := Context.dnsFilter.GetConfig()
setts.FilteringEnabled = true
ApplyBlockedServices(&setts, config.DNS.BlockedServices)
Context.dnsFilter.ApplyBlockedServices(&setts, nil, true)
result, err := Context.dnsFilter.CheckHost(host, dns.TypeA, &setts)
if err != nil {
httpError(w, http.StatusInternalServerError, "couldn't apply filtering: %s: %s", host, err)
@@ -389,15 +373,15 @@ func handleCheckHost(w http.ResponseWriter, r *http.Request) {
}
// RegisterFilteringHandlers - register handlers
func RegisterFilteringHandlers() {
httpRegister("GET", "/control/filtering/status", handleFilteringStatus)
httpRegister("POST", "/control/filtering/config", handleFilteringConfig)
httpRegister("POST", "/control/filtering/add_url", handleFilteringAddURL)
httpRegister("POST", "/control/filtering/remove_url", handleFilteringRemoveURL)
httpRegister("POST", "/control/filtering/set_url", handleFilteringSetURL)
httpRegister("POST", "/control/filtering/refresh", handleFilteringRefresh)
httpRegister("POST", "/control/filtering/set_rules", handleFilteringSetRules)
httpRegister("GET", "/control/filtering/check_host", handleCheckHost)
func (f *Filtering) RegisterFilteringHandlers() {
httpRegister("GET", "/control/filtering/status", f.handleFilteringStatus)
httpRegister("POST", "/control/filtering/config", f.handleFilteringConfig)
httpRegister("POST", "/control/filtering/add_url", f.handleFilteringAddURL)
httpRegister("POST", "/control/filtering/remove_url", f.handleFilteringRemoveURL)
httpRegister("POST", "/control/filtering/set_url", f.handleFilteringSetURL)
httpRegister("POST", "/control/filtering/refresh", f.handleFilteringRefresh)
httpRegister("POST", "/control/filtering/set_rules", f.handleFilteringSetRules)
httpRegister("GET", "/control/filtering/check_host", f.handleCheckHost)
}
func checkFiltersUpdateIntervalHours(i uint32) bool {

View File

@@ -35,7 +35,7 @@ type netInterfaceJSON struct {
}
// Get initial installation settings
func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
func (web *Web) handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
data := firstRunData{}
data.WebPort = 80
data.DNSPort = 53
@@ -93,7 +93,7 @@ type checkConfigResp struct {
}
// Check if ports are available, respond with results
func handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) {
func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) {
reqData := checkConfigReq{}
respData := checkConfigResp{}
err := json.NewDecoder(r.Body).Decode(&reqData)
@@ -275,7 +275,7 @@ func copyInstallSettings(dst *configuration, src *configuration) {
}
// Apply new configuration, start DNS server, restart Web server
func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
newSettings := applyConfigReq{}
err := json.NewDecoder(r.Body).Decode(&newSettings)
if err != nil {
@@ -325,22 +325,11 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
config.DNS.BindHost = newSettings.DNS.IP
config.DNS.Port = newSettings.DNS.Port
err = initDNSServer()
var err2 error
if err == nil {
err2 = startDNSServer()
if err2 != nil {
closeDNSServer()
}
}
if err != nil || err2 != nil {
err = StartMods()
if err != nil {
Context.firstRun = true
copyInstallSettings(&config, &curConfig)
if err != nil {
httpError(w, http.StatusInternalServerError, "Couldn't initialize DNS server: %s", err)
} else {
httpError(w, http.StatusInternalServerError, "Couldn't start DNS server: %s", err2)
}
httpError(w, http.StatusInternalServerError, "%s", err)
return
}
@@ -356,19 +345,21 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
return
}
registerControlHandlers()
// this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block
// until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely
if restartHTTP {
go func() {
_ = Context.httpServer.Shutdown(context.TODO())
_ = Context.web.httpServer.Shutdown(context.TODO())
}()
}
returnOK(w)
}
func registerInstallHandlers() {
http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(handleInstallGetAddresses)))
http.HandleFunc("/control/install/check_config", preInstall(ensurePOST(handleInstallCheckConfig)))
http.HandleFunc("/control/install/configure", preInstall(ensurePOST(handleInstallConfigure)))
func (web *Web) registerInstallHandlers() {
http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses)))
http.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig)))
http.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure)))
}

View File

@@ -52,7 +52,23 @@ func getVersionResp(data []byte) []byte {
}
_, ok := versionJSON[dloadName]
if ok && ret["new_version"] != versionString && versionString >= selfUpdateMinVersion {
ret["can_autoupdate"] = true
canUpdate := true
tlsConf := tlsConfigSettings{}
Context.tls.WriteDiskConfig(&tlsConf)
if runtime.GOOS != "windows" &&
((tlsConf.Enabled && (tlsConf.PortHTTPS < 1024 || tlsConf.PortDNSOverTLS < 1024)) ||
config.BindPort < 1024 ||
config.DNS.Port < 1024) {
// On UNIX, if we're running under a regular user,
// but with CAP_NET_BIND_SERVICE set on a binary file,
// and we're listening on ports <1024,
// we won't be able to restart after we replace the binary file,
// because we'll lose CAP_NET_BIND_SERVICE capability.
canUpdate, _ = util.HaveAdminRights()
}
ret["can_autoupdate"] = canUpdate
}
d, _ := json.Marshal(ret)
@@ -476,7 +492,6 @@ func doUpdate(u *updateInfo) error {
func finishUpdate(u *updateInfo) {
log.Info("Stopping all tasks")
cleanup()
stopHTTPServer()
cleanupAlways()
if runtime.GOOS == "windows" {

View File

@@ -3,13 +3,13 @@ package home
import (
"fmt"
"net"
"os"
"path/filepath"
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
"github.com/AdguardTeam/AdGuardHome/dnsforward"
"github.com/AdguardTeam/AdGuardHome/querylog"
"github.com/AdguardTeam/AdGuardHome/stats"
"github.com/AdguardTeam/AdGuardHome/util"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/log"
@@ -25,13 +25,9 @@ func onConfigModified() {
// Please note that we must do it even if we don't start it
// so that we had access to the query log and the stats
func initDNSServer() error {
var err error
baseDir := Context.getDataDir()
err := os.MkdirAll(baseDir, 0755)
if err != nil {
return fmt.Errorf("Cannot create DNS data dir at %s: %s", baseDir, err)
}
statsConf := stats.Config{
Filename: filepath.Join(baseDir, "stats.db"),
LimitDays: config.DNS.StatsInterval,
@@ -70,18 +66,10 @@ func initDNSServer() error {
return fmt.Errorf("dnsServer.Prepare: %s", err)
}
sessFilename := filepath.Join(baseDir, "sessions.db")
Context.auth = InitAuth(sessFilename, config.Users, config.WebSessionTTLHours*60*60)
if Context.auth == nil {
closeDNSServer()
return fmt.Errorf("Couldn't initialize Auth module")
}
config.Users = nil
Context.rdns = InitRDNS(Context.dnsServer, &Context.clients)
Context.whois = initWhois(&Context.clients)
initFiltering()
Context.filters.Init()
return nil
}
@@ -169,25 +157,74 @@ func generateServerConfig() dnsforward.ServerConfig {
OnDNSRequest: onDNSRequest,
}
if config.TLS.Enabled {
newconfig.TLSConfig = config.TLS.TLSConfig
if config.TLS.PortDNSOverTLS != 0 {
newconfig.TLSListenAddr = &net.TCPAddr{IP: net.ParseIP(config.DNS.BindHost), Port: config.TLS.PortDNSOverTLS}
tlsConf := tlsConfigSettings{}
Context.tls.WriteDiskConfig(&tlsConf)
if tlsConf.Enabled {
newconfig.TLSConfig = tlsConf.TLSConfig
if tlsConf.PortDNSOverTLS != 0 {
newconfig.TLSListenAddr = &net.TCPAddr{
IP: net.ParseIP(config.DNS.BindHost),
Port: tlsConf.PortDNSOverTLS,
}
}
}
newconfig.TLSv12Roots = Context.tlsRoots
newconfig.TLSAllowUnencryptedDOH = tlsConf.AllowUnencryptedDOH
newconfig.FilterHandler = applyAdditionalFiltering
newconfig.GetUpstreamsByClient = getUpstreamsByClient
return newconfig
}
// Get the list of DNS addresses the server is listening on
func getDNSAddresses() []string {
dnsAddresses := []string{}
if config.DNS.BindHost == "0.0.0.0" {
ifaces, e := util.GetValidNetInterfacesForWeb()
if e != nil {
log.Error("Couldn't get network interfaces: %v", e)
return []string{}
}
for _, iface := range ifaces {
for _, addr := range iface.Addresses {
addDNSAddress(&dnsAddresses, addr)
}
}
} else {
addDNSAddress(&dnsAddresses, config.DNS.BindHost)
}
tlsConf := tlsConfigSettings{}
Context.tls.WriteDiskConfig(&tlsConf)
if tlsConf.Enabled && len(tlsConf.ServerName) != 0 {
if tlsConf.PortHTTPS != 0 {
addr := tlsConf.ServerName
if tlsConf.PortHTTPS != 443 {
addr = fmt.Sprintf("%s:%d", addr, tlsConf.PortHTTPS)
}
addr = fmt.Sprintf("https://%s/dns-query", addr)
dnsAddresses = append(dnsAddresses, addr)
}
if tlsConf.PortDNSOverTLS != 0 {
addr := fmt.Sprintf("tls://%s:%d", tlsConf.ServerName, tlsConf.PortDNSOverTLS)
dnsAddresses = append(dnsAddresses, addr)
}
}
return dnsAddresses
}
func getUpstreamsByClient(clientAddr string) []upstream.Upstream {
return Context.clients.FindUpstreams(clientAddr)
}
// If a client has his own settings, apply them
func applyAdditionalFiltering(clientAddr string, setts *dnsfilter.RequestFilteringSettings) {
ApplyBlockedServices(setts, config.DNS.BlockedServices)
Context.dnsFilter.ApplyBlockedServices(setts, nil, true)
if len(clientAddr) == 0 {
return
@@ -201,7 +238,7 @@ func applyAdditionalFiltering(clientAddr string, setts *dnsfilter.RequestFilteri
log.Debug("Using settings for client with IP %s", clientAddr)
if c.UseOwnBlockedServices {
ApplyBlockedServices(setts, c.BlockedServices)
Context.dnsFilter.ApplyBlockedServices(setts, c.BlockedServices, false)
}
setts.ClientTags = c.Tags
@@ -223,13 +260,15 @@ func startDNSServer() error {
enableFilters(false)
Context.clients.Start()
err := Context.dnsServer.Start()
if err != nil {
return errorx.Decorate(err, "Couldn't start forwarding DNS server")
}
Context.dnsFilter.Start()
startFiltering()
Context.filters.Start()
Context.stats.Start()
Context.queryLog.Start()
@@ -294,10 +333,7 @@ func closeDNSServer() {
Context.queryLog = nil
}
if Context.auth != nil {
Context.auth.Close()
Context.auth = nil
}
Context.filters.Close()
log.Debug("Closed all DNS modules")
}

View File

@@ -1,8 +1,10 @@
package home
import (
"bufio"
"fmt"
"hash/crc32"
"io"
"io/ioutil"
"os"
"path/filepath"
@@ -15,30 +17,50 @@ import (
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
"github.com/AdguardTeam/AdGuardHome/util"
"github.com/AdguardTeam/golibs/file"
"github.com/AdguardTeam/golibs/log"
)
var (
nextFilterID = time.Now().Unix() // semi-stable way to generate an unique ID
filterTitleRegexp = regexp.MustCompile(`^! Title: +(.*)$`)
refreshStatus uint32 // 0:none; 1:in progress
refreshLock sync.Mutex
nextFilterID = time.Now().Unix() // semi-stable way to generate an unique ID
)
func initFiltering() {
loadFilters(config.Filters)
loadFilters(config.WhitelistFilters)
// type FilteringConf struct {
// BlockLists []filter
// AllowLists []filter
// UserRules []string
// }
// Filtering - module object
type Filtering struct {
// conf FilteringConf
refreshStatus uint32 // 0:none; 1:in progress
refreshLock sync.Mutex
filterTitleRegexp *regexp.Regexp
}
// Init - initialize the module
func (f *Filtering) Init() {
f.filterTitleRegexp = regexp.MustCompile(`^! Title: +(.*)$`)
_ = os.MkdirAll(filepath.Join(Context.getDataDir(), filterDir), 0755)
f.loadFilters(config.Filters)
f.loadFilters(config.WhitelistFilters)
deduplicateFilters()
updateUniqueFilterID(config.Filters)
updateUniqueFilterID(config.WhitelistFilters)
}
func startFiltering() {
// Start - start the module
func (f *Filtering) Start() {
f.RegisterFilteringHandlers()
// Here we should start updating filters,
// but currently we can't wake up the periodic task to do so.
// So for now we just start this periodic task from here.
go periodicallyRefreshFilters()
go f.periodicallyRefreshFilters()
}
// Close - close the module
func (f *Filtering) Close() {
}
func defaultFilters() []filter {
@@ -83,7 +105,7 @@ const (
// Update properties for a filter specified by its URL
// Return status* flags.
func filterSetProperties(url string, newf filter, whitelist bool) int {
func (f *Filtering) filterSetProperties(url string, newf filter, whitelist bool) int {
r := 0
config.Lock()
defer config.Unlock()
@@ -94,44 +116,44 @@ func filterSetProperties(url string, newf filter, whitelist bool) int {
}
for i := range *filters {
f := &(*filters)[i]
if f.URL != url {
filt := &(*filters)[i]
if filt.URL != url {
continue
}
log.Debug("filter: set properties: %s: {%s %s %v}",
f.URL, newf.Name, newf.URL, newf.Enabled)
f.Name = newf.Name
filt.URL, newf.Name, newf.URL, newf.Enabled)
filt.Name = newf.Name
if f.URL != newf.URL {
if filt.URL != newf.URL {
r |= statusURLChanged | statusUpdateRequired
if filterExistsNoLock(newf.URL) {
return statusURLExists
}
f.URL = newf.URL
f.unload()
f.LastUpdated = time.Time{}
f.checksum = 0
f.RulesCount = 0
filt.URL = newf.URL
filt.unload()
filt.LastUpdated = time.Time{}
filt.checksum = 0
filt.RulesCount = 0
}
if f.Enabled != newf.Enabled {
if filt.Enabled != newf.Enabled {
r |= statusEnabledChanged
f.Enabled = newf.Enabled
if f.Enabled {
filt.Enabled = newf.Enabled
if filt.Enabled {
if (r & statusURLChanged) == 0 {
e := f.load()
e := f.load(filt)
if e != nil {
// This isn't a fatal error,
// because it may occur when someone removes the file from disk.
f.LastUpdated = time.Time{}
f.checksum = 0
f.RulesCount = 0
filt.LastUpdated = time.Time{}
filt.checksum = 0
filt.RulesCount = 0
r |= statusUpdateRequired
}
}
} else {
f.unload()
filt.unload()
}
}
@@ -183,7 +205,7 @@ func filterAdd(f filter) bool {
// Load filters from the disk
// And if any filter has zero ID, assign a new one
func loadFilters(array []filter) {
func (f *Filtering) loadFilters(array []filter) {
for i := range array {
filter := &array[i] // otherwise we're operating on a copy
if filter.ID == 0 {
@@ -195,7 +217,7 @@ func loadFilters(array []filter) {
continue
}
err := filter.load()
err := f.load(filter)
if err != nil {
log.Error("Couldn't load filter %d contents due to %s", filter.ID, err)
}
@@ -235,16 +257,16 @@ func assignUniqueFilterID() int64 {
}
// Sets up a timer that will be checking for filters updates periodically
func periodicallyRefreshFilters() {
func (f *Filtering) periodicallyRefreshFilters() {
const maxInterval = 1 * 60 * 60
intval := 5 // use a dynamically increasing time interval
for {
isNetworkErr := false
if config.DNS.FiltersUpdateIntervalHours != 0 && atomic.CompareAndSwapUint32(&refreshStatus, 0, 1) {
refreshLock.Lock()
_, isNetworkErr = refreshFiltersIfNecessary(FilterRefreshBlocklists | FilterRefreshAllowlists)
refreshLock.Unlock()
refreshStatus = 0
if config.DNS.FiltersUpdateIntervalHours != 0 && atomic.CompareAndSwapUint32(&f.refreshStatus, 0, 1) {
f.refreshLock.Lock()
_, isNetworkErr = f.refreshFiltersIfNecessary(FilterRefreshBlocklists | FilterRefreshAllowlists)
f.refreshLock.Unlock()
f.refreshStatus = 0
if !isNetworkErr {
intval = maxInterval
}
@@ -265,20 +287,20 @@ func periodicallyRefreshFilters() {
// flags: FilterRefresh*
// important:
// TRUE: ignore the fact that we're currently updating the filters
func refreshFilters(flags int, important bool) (int, error) {
set := atomic.CompareAndSwapUint32(&refreshStatus, 0, 1)
func (f *Filtering) refreshFilters(flags int, important bool) (int, error) {
set := atomic.CompareAndSwapUint32(&f.refreshStatus, 0, 1)
if !important && !set {
return 0, fmt.Errorf("Filters update procedure is already running")
}
refreshLock.Lock()
nUpdated, _ := refreshFiltersIfNecessary(flags)
refreshLock.Unlock()
refreshStatus = 0
f.refreshLock.Lock()
nUpdated, _ := f.refreshFiltersIfNecessary(flags)
f.refreshLock.Unlock()
f.refreshStatus = 0
return nUpdated, nil
}
func refreshFiltersArray(filters *[]filter, force bool) (int, []filter, []bool, bool) {
func (f *Filtering) refreshFiltersArray(filters *[]filter, force bool) (int, []filter, []bool, bool) {
var updateFilters []filter
var updateFlags []bool // 'true' if filter data has changed
@@ -312,14 +334,13 @@ func refreshFiltersArray(filters *[]filter, force bool) (int, []filter, []bool,
nfail := 0
for i := range updateFilters {
uf := &updateFilters[i]
updated, err := uf.update()
updated, err := f.update(uf)
updateFlags = append(updateFlags, updated)
if err != nil {
nfail++
log.Printf("Failed to update filter %s: %s\n", uf.URL, err)
continue
}
uf.LastUpdated = now
}
if nfail == len(updateFilters) {
@@ -330,18 +351,6 @@ func refreshFiltersArray(filters *[]filter, force bool) (int, []filter, []bool,
for i := range updateFilters {
uf := &updateFilters[i]
updated := updateFlags[i]
if updated {
err := uf.saveAndBackupOld()
if err != nil {
log.Printf("Failed to save the updated filter %d: %s", uf.ID, err)
continue
}
} else {
e := os.Chtimes(uf.Path(), uf.LastUpdated, uf.LastUpdated)
if e != nil {
log.Error("os.Chtimes(): %v", e)
}
}
config.Lock()
for k := range *filters {
@@ -357,7 +366,6 @@ func refreshFiltersArray(filters *[]filter, force bool) (int, []filter, []bool,
log.Info("Updated filter #%d. Rules: %d -> %d",
f.ID, f.RulesCount, uf.RulesCount)
f.Name = uf.Name
f.Data = nil
f.RulesCount = uf.RulesCount
f.checksum = uf.checksum
updateCount++
@@ -381,18 +389,19 @@ const (
// Algorithm:
// . Get the list of filters to be updated
// . For each filter run the download and checksum check operation
// . Store downloaded data in a temporary file inside data/filters directory
// . For each filter:
// . If filter data hasn't changed, just set new update time on file
// . If filter data has changed:
// . rename the old file (1.txt -> 1.txt.old)
// . store the new data on disk (1.txt)
// . rename the temporary file (<temp> -> 1.txt)
// Note that this method works only on UNIX.
// On Windows we don't pass files to dnsfilter - we pass the whole data.
// . Pass new filters to dnsfilter object - it analyzes new data while the old filters are still active
// . dnsfilter activates new filters
// . Remove the old filter files (1.txt.old)
//
// Return the number of updated filters
// Return TRUE - there was a network error and nothing could be updated
func refreshFiltersIfNecessary(flags int) (int, bool) {
func (f *Filtering) refreshFiltersIfNecessary(flags int) (int, bool) {
log.Debug("Filters: updating...")
updateCount := 0
@@ -405,13 +414,13 @@ func refreshFiltersIfNecessary(flags int) (int, bool) {
force = true
}
if (flags & FilterRefreshBlocklists) != 0 {
updateCount, updateFilters, updateFlags, netError = refreshFiltersArray(&config.Filters, force)
updateCount, updateFilters, updateFlags, netError = f.refreshFiltersArray(&config.Filters, force)
}
if (flags & FilterRefreshAllowlists) != 0 {
updateCountW := 0
var updateFiltersW []filter
var updateFlagsW []bool
updateCountW, updateFiltersW, updateFlagsW, netErrorW = refreshFiltersArray(&config.WhitelistFilters, force)
updateCountW, updateFiltersW, updateFlagsW, netErrorW = f.refreshFiltersArray(&config.WhitelistFilters, force)
updateCount += updateCountW
updateFilters = append(updateFilters, updateFiltersW...)
updateFlags = append(updateFlags, updateFlagsW...)
@@ -449,21 +458,28 @@ func isPrintableText(data []byte) bool {
}
// A helper function that parses filter contents and returns a number of rules and a filter name (if there's any)
func parseFilterContents(contents []byte) (int, string) {
data := string(contents)
func (f *Filtering) parseFilterContents(file io.Reader) (int, uint32, string) {
rulesCount := 0
name := ""
seenTitle := false
r := bufio.NewReader(file)
checksum := uint32(0)
// Count lines in the filter
for len(data) != 0 {
line := util.SplitNext(&data, '\n')
for {
line, err := r.ReadString('\n')
if err != nil {
break
}
checksum = crc32.Update(checksum, crc32.IEEETable, []byte(line))
line = strings.TrimSpace(line)
if len(line) == 0 {
continue
}
if line[0] == '!' {
m := filterTitleRegexp.FindAllStringSubmatch(line, -1)
m := f.filterTitleRegexp.FindAllStringSubmatch(line, -1)
if len(m) > 0 && len(m[0]) >= 2 && !seenTitle {
name = m[0][1]
seenTitle = true
@@ -473,13 +489,36 @@ func parseFilterContents(contents []byte) (int, string) {
}
}
return rulesCount, name
return rulesCount, checksum, name
}
// Perform upgrade on a filter
func (filter *filter) update() (bool, error) {
// Perform upgrade on a filter and update LastUpdated value
func (f *Filtering) update(filter *filter) (bool, error) {
b, err := f.updateIntl(filter)
filter.LastUpdated = time.Now()
if !b {
e := os.Chtimes(filter.Path(), filter.LastUpdated, filter.LastUpdated)
if e != nil {
log.Error("os.Chtimes(): %v", e)
}
}
return b, err
}
func (f *Filtering) updateIntl(filter *filter) (bool, error) {
log.Tracef("Downloading update for filter %d from %s", filter.ID, filter.URL)
tmpfile, err := ioutil.TempFile(filepath.Join(Context.getDataDir(), filterDir), "")
if err != nil {
return false, err
}
defer func() {
if tmpfile != nil {
_ = tmpfile.Close()
_ = os.Remove(tmpfile.Name())
}
}()
resp, err := Context.client.Get(filter.URL)
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
@@ -494,74 +533,81 @@ func (filter *filter) update() (bool, error) {
return false, fmt.Errorf("got status code != 200: %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("Couldn't fetch filter contents from URL %s, skipping: %s", filter.URL, err)
return false, err
htmlTest := true
firstChunk := make([]byte, 4*1024)
firstChunkLen := 0
buf := make([]byte, 64*1024)
total := 0
for {
n, err := resp.Body.Read(buf)
total += n
if htmlTest {
// gather full buffer firstChunk and perform its data tests
num := util.MinInt(n, len(firstChunk)-firstChunkLen)
copied := copy(firstChunk[firstChunkLen:], buf[:num])
firstChunkLen += copied
if firstChunkLen == len(firstChunk) || err == io.EOF {
if !isPrintableText(firstChunk) {
return false, fmt.Errorf("Data contains non-printable characters")
}
s := strings.ToLower(string(firstChunk))
if strings.Index(s, "<html") >= 0 ||
strings.Index(s, "<!doctype") >= 0 {
return false, fmt.Errorf("Data is HTML, not plain text")
}
htmlTest = false
firstChunk = nil
}
}
_, err2 := tmpfile.Write(buf[:n])
if err2 != nil {
return false, err2
}
if err == io.EOF {
break
}
if err != nil {
log.Printf("Couldn't fetch filter contents from URL %s, skipping: %s", filter.URL, err)
return false, err
}
}
// Extract filter name and count number of rules
_, _ = tmpfile.Seek(0, io.SeekStart)
rulesCount, checksum, filterName := f.parseFilterContents(tmpfile)
// Check if the filter has been really changed
checksum := crc32.ChecksumIEEE(body)
if filter.checksum == checksum {
log.Tracef("Filter #%d at URL %s hasn't changed, not updating it", filter.ID, filter.URL)
return false, nil
}
var firstChunk []byte
if len(body) <= 4096 {
firstChunk = body
} else {
firstChunk = body[:4096]
}
if !isPrintableText(firstChunk) {
return false, fmt.Errorf("Data contains non-printable characters")
}
s := strings.ToLower(string(firstChunk))
if strings.Index(s, "<html") >= 0 ||
strings.Index(s, "<!doctype") >= 0 {
return false, fmt.Errorf("Data is HTML, not plain text")
}
// Extract filter name and count number of rules
rulesCount, filterName := parseFilterContents(body)
log.Printf("Filter %d has been updated: %d bytes, %d rules", filter.ID, len(body), rulesCount)
log.Printf("Filter %d has been updated: %d bytes, %d rules",
filter.ID, total, rulesCount)
if filterName != "" {
filter.Name = filterName
}
filter.RulesCount = rulesCount
filter.Data = body
filter.checksum = checksum
filterFilePath := filter.Path()
log.Printf("Saving filter %d contents to: %s", filter.ID, filterFilePath)
err = os.Rename(tmpfile.Name(), filterFilePath)
if err != nil {
return false, err
}
tmpfile.Close()
tmpfile = nil
return true, nil
}
// saves filter contents to the file in dataDir
// This method is safe to call during filters update,
// because it creates a new file and then renames it,
// so the currently opened file descriptors to the old filter file remain valid.
func (filter *filter) save() error {
filterFilePath := filter.Path()
log.Printf("Saving filter %d contents to: %s", filter.ID, filterFilePath)
err := file.SafeWrite(filterFilePath, filter.Data)
// update LastUpdated field after saving the file
filter.LastUpdated = filter.LastTimeUpdated()
return err
}
func (filter *filter) saveAndBackupOld() error {
filterFilePath := filter.Path()
err := os.Rename(filterFilePath, filterFilePath+".old")
if err != nil && !os.IsNotExist(err) {
return err
}
return filter.save()
}
// loads filter contents from the file in dataDir
func (filter *filter) load() error {
func (f *Filtering) load(filter *filter) error {
filterFilePath := filter.Path()
log.Tracef("Loading filter %d contents to: %s", filter.ID, filterFilePath)
@@ -570,17 +616,19 @@ func (filter *filter) load() error {
return err
}
filterFileContents, err := ioutil.ReadFile(filterFilePath)
file, err := os.Open(filterFilePath)
if err != nil {
return err
}
defer file.Close()
st, _ := file.Stat()
log.Tracef("File %s, id %d, length %d", filterFilePath, filter.ID, len(filterFileContents))
rulesCount, _ := parseFilterContents(filterFileContents)
log.Tracef("File %s, id %d, length %d",
filterFilePath, filter.ID, st.Size())
rulesCount, checksum, _ := f.parseFilterContents(file)
filter.RulesCount = rulesCount
filter.Data = nil
filter.checksum = crc32.ChecksumIEEE(filterFileContents)
filter.checksum = checksum
filter.LastUpdated = filter.LastTimeUpdated()
return nil
@@ -588,8 +636,8 @@ func (filter *filter) load() error {
// Clear filter rules
func (filter *filter) unload() {
filter.Data = nil
filter.RulesCount = 0
filter.checksum = 0
}
// Path to the filter contents

View File

@@ -12,29 +12,27 @@ import (
func TestFilters(t *testing.T) {
dir := prepareTestDir()
defer func() { _ = os.RemoveAll(dir) }()
Context = homeContext{}
Context.workDir = dir
Context.client = &http.Client{
Timeout: time.Minute * 5,
Timeout: 5 * time.Second,
}
Context.filters.Init()
f := filter{
URL: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt",
}
// download
ok, err := f.update()
assert.True(t, ok && err == nil)
ok, err := Context.filters.update(&f)
assert.Equal(t, nil, err)
assert.True(t, ok)
// refresh
ok, err = f.update()
ok, err = Context.filters.update(&f)
assert.True(t, !ok && err == nil)
err = f.save()
assert.True(t, err == nil)
err = f.load()
err = Context.filters.load(&f)
assert.True(t, err == nil)
f.unload()

View File

@@ -4,11 +4,13 @@ import (
"bufio"
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"os/signal"
@@ -32,8 +34,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/querylog"
"github.com/AdguardTeam/AdGuardHome/stats"
"github.com/AdguardTeam/golibs/log"
"github.com/NYTimes/gziphandler"
"github.com/gobuffalo/packr"
)
const (
@@ -56,17 +56,18 @@ type homeContext struct {
// Modules
// --
clients clientsContainer // per-client-settings module
stats stats.Stats // statistics module
queryLog querylog.QueryLog // query log module
dnsServer *dnsforward.Server // DNS module
rdns *RDNS // rDNS module
whois *Whois // WHOIS module
dnsFilter *dnsfilter.Dnsfilter // DNS filtering module
dhcpServer *dhcpd.Server // DHCP module
auth *Auth // HTTP authentication module
httpServer *http.Server // HTTP module
httpsServer HTTPSServer // HTTPS module
clients clientsContainer // per-client-settings module
stats stats.Stats // statistics module
queryLog querylog.QueryLog // query log module
dnsServer *dnsforward.Server // DNS module
rdns *RDNS // rDNS module
whois *Whois // WHOIS module
dnsFilter *dnsfilter.Dnsfilter // DNS filtering module
dhcpServer *dhcpd.Server // DHCP module
auth *Auth // HTTP authentication module
filters Filtering // DNS filtering module
web *Web // Web (HTTP, HTTPS) module
tls *TLSMod // TLS module
// Runtime properties
// --
@@ -77,6 +78,7 @@ type homeContext struct {
pidFileName string // PID file name. Empty if no PID file was created.
disableUpdate bool // If set, don't check for updates
controlLock sync.Mutex
tlsRoots *x509.CertPool // list of root CAs for TLSv1.2
transport *http.Transport
client *http.Client
appSignalChannel chan os.Signal // Channel for receiving OS signals by the console app
@@ -112,10 +114,20 @@ func Main(version string, channel string, armVer string) {
Context.appSignalChannel = make(chan os.Signal)
signal.Notify(Context.appSignalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
go func() {
<-Context.appSignalChannel
cleanup()
cleanupAlways()
os.Exit(0)
for {
sig := <-Context.appSignalChannel
log.Info("Received signal '%s'", sig)
switch sig {
case syscall.SIGHUP:
Context.clients.Reload()
Context.tls.Reload()
default:
cleanup()
cleanupAlways()
os.Exit(0)
}
}
}()
// run the protection
@@ -126,21 +138,9 @@ func Main(version string, channel string, armVer string) {
// run is a blocking method!
// nolint
func run(args options) {
// config file path can be overridden by command-line arguments:
Context.configFilename = "AdGuardHome.yaml"
if args.configFilename != "" {
Context.configFilename = args.configFilename
} else {
// Default config file name
Context.configFilename = "AdGuardHome.yaml"
}
// Init some of the Context fields right away
Context.transport = &http.Transport{
DialContext: customDialContext,
}
Context.client = &http.Client{
Timeout: time.Minute * 5,
Transport: Context.transport,
}
// configure working dir and config path
@@ -168,7 +168,19 @@ func run(args options) {
}
initConfig()
initServices()
Context.tlsRoots = util.LoadSystemRootCAs()
Context.transport = &http.Transport{
DialContext: customDialContext,
Proxy: getHTTPProxy,
TLSClientConfig: &tls.Config{
RootCAs: Context.tlsRoots,
},
}
Context.client = &http.Client{
Timeout: time.Minute * 5,
Transport: Context.transport,
}
if !Context.firstRun {
// Do the upgrade if necessary
@@ -192,6 +204,9 @@ func run(args options) {
config.DHCP.HTTPRegister = httpRegister
config.DHCP.ConfigModified = onConfigModified
Context.dhcpServer = dhcpd.Create(config.DHCP)
if Context.dhcpServer == nil {
os.Exit(1)
}
Context.clients.Init(config.Clients, Context.dhcpServer)
config.Clients = nil
@@ -207,6 +222,9 @@ func run(args options) {
if args.bindPort != 0 {
config.BindPort = args.bindPort
}
if len(args.pidFile) != 0 && writePIDFile(args.pidFile) {
Context.pidFileName = args.pidFile
}
if !Context.firstRun {
// Save the updated config
@@ -214,11 +232,42 @@ func run(args options) {
if err != nil {
log.Fatal(err)
}
}
err = initDNSServer()
err := os.MkdirAll(Context.getDataDir(), 0755)
if err != nil {
log.Fatalf("Cannot create DNS data dir at %s: %s", Context.getDataDir(), err)
}
sessFilename := filepath.Join(Context.getDataDir(), "sessions.db")
Context.auth = InitAuth(sessFilename, config.Users, config.WebSessionTTLHours*60*60)
if Context.auth == nil {
log.Fatalf("Couldn't initialize Auth module")
}
config.Users = nil
Context.tls = tlsCreate(config.TLS)
if Context.tls == nil {
log.Fatalf("Can't initialize TLS module")
}
webConf := WebConfig{
firstRun: Context.firstRun,
BindHost: config.BindHost,
BindPort: config.BindPort,
}
Context.web = CreateWeb(&webConf)
if Context.web == nil {
log.Fatalf("Can't initialize Web module")
}
if !Context.firstRun {
err := initDNSServer()
if err != nil {
log.Fatalf("%s", err)
}
Context.tls.Start()
go func() {
err := startDNSServer()
if err != nil {
@@ -232,100 +281,27 @@ func run(args options) {
}
}
if len(args.pidFile) != 0 && writePIDFile(args.pidFile) {
Context.pidFileName = args.pidFile
}
// Initialize and run the admin Web interface
box := packr.NewBox("../build/static")
// if not configured, redirect / to /install.html, otherwise redirect /install.html to /
http.Handle("/", postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(http.FileServer(box)))))
registerControlHandlers()
// add handlers for /install paths, we only need them when we're not configured yet
if Context.firstRun {
log.Info("This is the first launch of AdGuard Home, redirecting everything to /install.html ")
http.Handle("/install.html", preInstallHandler(http.FileServer(box)))
registerInstallHandlers()
}
Context.httpsServer.cond = sync.NewCond(&Context.httpsServer.Mutex)
// for https, we have a separate goroutine loop
go httpServerLoop()
// this loop is used as an ability to change listening host and/or port
for !Context.httpsServer.shutdown {
printHTTPAddresses("http")
// we need to have new instance, because after Shutdown() the Server is not usable
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
Context.httpServer = &http.Server{
Addr: address,
}
err := Context.httpServer.ListenAndServe()
if err != http.ErrServerClosed {
cleanupAlways()
log.Fatal(err)
}
// We use ErrServerClosed as a sign that we need to rebind on new address, so go back to the start of the loop
}
Context.web.Start()
// wait indefinitely for other go-routines to complete their job
select {}
}
func httpServerLoop() {
for !Context.httpsServer.shutdown {
Context.httpsServer.cond.L.Lock()
// this mechanism doesn't let us through until all conditions are met
for config.TLS.Enabled == false ||
config.TLS.PortHTTPS == 0 ||
len(config.TLS.PrivateKeyData) == 0 ||
len(config.TLS.CertificateChainData) == 0 { // sleep until necessary data is supplied
Context.httpsServer.cond.Wait()
}
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.TLS.PortHTTPS))
// validate current TLS config and update warnings (it could have been loaded from file)
data := validateCertificates(string(config.TLS.CertificateChainData), string(config.TLS.PrivateKeyData), config.TLS.ServerName)
if !data.ValidPair {
cleanupAlways()
log.Fatal(data.WarningValidation)
}
config.Lock()
config.TLS.tlsConfigStatus = data // update warnings
config.Unlock()
// prepare certs for HTTPS server
// important -- they have to be copies, otherwise changing the contents in config.TLS will break encryption for in-flight requests
certchain := make([]byte, len(config.TLS.CertificateChainData))
copy(certchain, config.TLS.CertificateChainData)
privatekey := make([]byte, len(config.TLS.PrivateKeyData))
copy(privatekey, config.TLS.PrivateKeyData)
cert, err := tls.X509KeyPair(certchain, privatekey)
if err != nil {
cleanupAlways()
log.Fatal(err)
}
Context.httpsServer.cond.L.Unlock()
// prepare HTTPS server
Context.httpsServer.server = &http.Server{
Addr: address,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
},
}
printHTTPAddresses("https")
err = Context.httpsServer.server.ListenAndServeTLS("", "")
if err != http.ErrServerClosed {
cleanupAlways()
log.Fatal(err)
}
// StartMods - initialize and start DNS after installation
func StartMods() error {
err := initDNSServer()
if err != nil {
return err
}
Context.tls.Start()
err = startDNSServer()
if err != nil {
closeDNSServer()
return err
}
return nil
}
// Check if the current user has root (administrator) rights
@@ -437,6 +413,15 @@ func configureLogger(args options) {
func cleanup() {
log.Info("Stopping AdGuard Home")
if Context.web != nil {
Context.web.Close()
Context.web = nil
}
if Context.auth != nil {
Context.auth.Close()
Context.auth = nil
}
err := stopDNSServer()
if err != nil {
log.Error("Couldn't stop DNS server: %s", err)
@@ -445,17 +430,11 @@ func cleanup() {
if err != nil {
log.Error("Couldn't stop DHCP server: %s", err)
}
}
// Stop HTTP server, possibly waiting for all active connections to be closed
func stopHTTPServer() {
log.Info("Stopping HTTP server...")
Context.httpsServer.shutdown = true
if Context.httpsServer.server != nil {
_ = Context.httpsServer.server.Shutdown(context.TODO())
if Context.tls != nil {
Context.tls.Close()
Context.tls = nil
}
_ = Context.httpServer.Shutdown(context.TODO())
log.Info("Stopped HTTP server")
}
// This function is called before application exits
@@ -478,6 +457,8 @@ type options struct {
checkConfig bool // Check configuration and exit
disableUpdate bool // If set, don't check for updates
hasImportOpenwrtConfig bool // 'import-openwrt-config' argument is specified
// service control action (see service.ControlAction array + "status" command)
serviceControlAction string
@@ -507,7 +488,7 @@ func loadOptions() options {
}
o.bindPort = v
}, nil},
{"service", "s", "Service control action: status, install, uninstall, start, stop, restart", func(value string) {
{"service", "s", "Service control action: status, install, uninstall, start, stop, restart, reload (configuration)", func(value string) {
o.serviceControlAction = value
}, nil},
{"logfile", "l", "Path to log file. If empty: write to stdout; if 'syslog': write to system log", func(value string) {
@@ -516,6 +497,9 @@ func loadOptions() options {
{"pidfile", "", "Path to a file where PID is stored", func(value string) { o.pidFile = value }, nil},
{"check-config", "", "Check configuration and exit", nil, func() { o.checkConfig = true }},
{"no-check-update", "", "Don't check for updates", nil, func() { o.disableUpdate = true }},
{"import-openwrt-config", "", "Create or update YAML configuration file from OpenWRT system configuration", nil, func() {
o.hasImportOpenwrtConfig = true
}},
{"verbose", "v", "Enable verbose output", nil, func() { o.verbose = true }},
{"version", "", "Show the version and exit", nil, func() {
fmt.Printf("AdGuardHome %s\n", versionString)
@@ -568,6 +552,18 @@ func loadOptions() options {
}
}
if o.verbose {
log.SetLevel(log.DEBUG)
}
if o.hasImportOpenwrtConfig {
err := importOpenwrtConfig(o.configFilename)
if err != nil {
log.Fatalf("%s", err)
}
os.Exit(0)
}
return o
}
@@ -576,11 +572,13 @@ func loadOptions() options {
func printHTTPAddresses(proto string) {
var address string
if proto == "https" && config.TLS.ServerName != "" {
if config.TLS.PortHTTPS == 443 {
log.Printf("Go to https://%s", config.TLS.ServerName)
tlsConf := tlsConfigSettings{}
Context.tls.WriteDiskConfig(&tlsConf)
if proto == "https" && tlsConf.ServerName != "" {
if tlsConf.PortHTTPS == 443 {
log.Printf("Go to https://%s", tlsConf.ServerName)
} else {
log.Printf("Go to https://%s:%d", config.TLS.ServerName, config.TLS.PortHTTPS)
log.Printf("Go to https://%s:%d", tlsConf.ServerName, tlsConf.PortHTTPS)
}
} else if config.BindHost == "0.0.0.0" {
log.Println("AdGuard Home is available on the following addresses:")
@@ -658,3 +656,10 @@ func customDialContext(ctx context.Context, network, addr string) (net.Conn, err
}
return nil, errorx.DecorateMany(fmt.Sprintf("couldn't dial to %s", addr), dialErrs...)
}
func getHTTPProxy(req *http.Request) (*url.URL, error) {
if len(config.ProxyURL) == 0 {
return nil, nil
}
return url.Parse(config.ProxyURL)
}

View File

@@ -112,6 +112,7 @@ func TestHome(t *testing.T) {
dir := prepareTestDir()
defer func() { _ = os.RemoveAll(dir) }()
_ = os.MkdirAll(filepath.Join(Context.getDataDir(), filterDir), 0755)
fn := filepath.Join(dir, "AdGuardHome.yaml")
assert.True(t, ioutil.WriteFile(fn, []byte(yamlConf), 0644) == nil)

View File

@@ -40,6 +40,8 @@ var allowedLanguages = map[string]bool{
"sr-cs": true,
"hr": true,
"fa": true,
"th": true,
"ro": true,
}
func isLanguageAllowed(language string) bool {

364
home/openwrt.go Normal file
View File

@@ -0,0 +1,364 @@
package home
import (
"bufio"
"fmt"
"io/ioutil"
"net"
"strconv"
"strings"
"github.com/AdguardTeam/AdGuardHome/dhcpd"
"github.com/AdguardTeam/AdGuardHome/util"
"github.com/AdguardTeam/golibs/log"
)
type openwrtConfig struct {
// network:
netmask string
ipaddr string
// dhcp:
dhcpStart string
dhcpLimit string
dhcpLeasetime string
dhcpDnsmasqLeaseFile string
// dhcp static leases:
leases []dhcpd.Lease
// resolv.conf:
nameservers []string
// yaml.dhcp:
iface string
gwIP string
snMask string
rangeStart string
rangeEnd string
leaseDur uint32
// yaml.dns.bootstrap_dns:
bsDNS []string
}
// Parse command line: "option name 'value'"
func parseCmd(line string) (string, string, string) {
word1 := util.SplitNext(&line, ' ')
word2 := util.SplitNext(&line, ' ')
word3 := util.SplitNext(&line, ' ')
if len(word3) > 2 && word3[0] == '\'' && word3[len(word3)-1] == '\'' {
// 'value' -> value
word3 = word3[1:]
word3 = word3[:len(word3)-1]
}
return word1, word2, word3
}
// Parse system configuration data
// nolint(gocyclo)
func (oc *openwrtConfig) readConf(data []byte, section string, iface string) {
state := 0
sr := strings.NewReader(string(data))
r := bufio.NewReader(sr)
for {
line, err := r.ReadString('\n')
line = strings.TrimSpace(line)
if len(line) == 0 {
if state >= 2 {
return
}
state = 0
}
word1, word2, word3 := parseCmd(line)
switch state {
case 0:
if word1 == "config" {
state = 1
if word2 == section && word3 == iface {
// found the needed section
if word2 == "interface" {
state = 2 // found the needed interface
} else if word2 == "dhcp" {
state = 3
} else if word2 == "dnsmasq" {
state = 4
}
}
}
case 1:
// not interested
break
case 2:
if word1 != "option" {
break
}
switch word2 {
case "netmask":
oc.netmask = word3
case "ipaddr":
oc.ipaddr = word3
}
case 3:
if word1 != "option" {
break
}
switch word2 {
case "start":
oc.dhcpStart = word3
case "limit":
oc.dhcpLimit = word3
case "leasetime":
oc.dhcpLeasetime = word3
}
case 4:
if word1 != "option" {
break
}
switch word2 {
case "leasefile":
oc.dhcpDnsmasqLeaseFile = word3
}
}
if err != nil {
break
}
}
}
// Parse static DHCP leases from system configuration data
func (oc *openwrtConfig) readConfDHCPStatic(data []byte) error {
state := 0
sr := strings.NewReader(string(data))
r := bufio.NewReader(sr)
lease := dhcpd.Lease{}
for {
line, err := r.ReadString('\n')
line = strings.TrimSpace(line)
if len(line) == 0 {
if len(lease.HWAddr) != 0 && len(lease.IP) != 0 {
oc.leases = append(oc.leases, lease)
}
lease = dhcpd.Lease{}
state = 0
}
word1, word2, word3 := parseCmd(line)
switch state {
case 0:
if word1 == "config" {
state = 1
if word2 == "host" {
state = 2
}
}
case 1:
// not interested
break
case 2:
if word1 != "option" {
break
}
switch word2 {
case "mac":
lease.HWAddr, err = net.ParseMAC(word3)
if err != nil {
return err
}
case "ip":
lease.IP = net.ParseIP(word3)
if lease.IP == nil || lease.IP.To4() == nil {
return fmt.Errorf("Invalid IP address")
}
case "name":
lease.Hostname = word3
}
}
if err != nil {
break
}
}
if len(lease.HWAddr) != 0 && len(lease.IP) != 0 {
oc.leases = append(oc.leases, lease)
}
return nil
}
// Parse "/etc/resolv.conf" data
func (oc *openwrtConfig) readResolvConf(data []byte) {
lines := string(data)
for len(lines) != 0 {
line := util.SplitNext(&lines, '\n')
key := util.SplitNext(&line, ' ')
if key == "nameserver" {
val := util.SplitNext(&line, ' ')
oc.nameservers = append(oc.nameservers, val)
}
}
}
// Convert system config parameters to our yaml config format
func (oc *openwrtConfig) prepareOutput() error {
oc.iface = "br-lan"
ipAddr := net.ParseIP(oc.ipaddr)
if ipAddr == nil || ipAddr.To4() == nil {
return fmt.Errorf("Invalid IP: %s", oc.ipaddr)
}
oc.gwIP = oc.ipaddr
ip := net.ParseIP(oc.netmask)
if ip == nil || ip.To4() == nil {
return fmt.Errorf("Invalid IP: %s", oc.netmask)
}
oc.snMask = oc.netmask
nStart, err := strconv.Atoi(oc.dhcpStart)
if err != nil {
return fmt.Errorf("Invalid 'start': %s", oc.dhcpStart)
}
rangeStart := make(net.IP, 4)
copy(rangeStart, ipAddr.To4())
rangeStart[3] = byte(nStart)
oc.rangeStart = rangeStart.String()
nLim, err := strconv.Atoi(oc.dhcpLimit)
if err != nil {
return fmt.Errorf("Invalid 'start': %s", oc.dhcpLimit)
}
n := nStart + nLim - 1
if n <= 0 || n > 255 {
return fmt.Errorf("Invalid 'start' or 'limit': %s/%s", oc.dhcpStart, oc.dhcpLimit)
}
rangeEnd := make(net.IP, 4)
copy(rangeEnd, ipAddr.To4())
rangeEnd[3] = byte(n)
oc.rangeEnd = rangeEnd.String()
if len(oc.dhcpLeasetime) == 0 || oc.dhcpLeasetime[len(oc.dhcpLeasetime)-1] != 'h' {
return fmt.Errorf("Invalid leasetime: %s", oc.dhcpLeasetime)
}
n, err = strconv.Atoi(oc.dhcpLeasetime[:len(oc.dhcpLeasetime)-1])
if err != nil {
return fmt.Errorf("Invalid leasetime: %s", oc.dhcpLeasetime)
}
oc.leaseDur = uint32(n) * 60 * 60
for _, s := range oc.nameservers {
if net.ParseIP(s) == nil {
continue
}
oc.bsDNS = append(oc.bsDNS, s)
}
return nil
}
// Process - read and process system configuration data
func (oc *openwrtConfig) Process() error {
data, err := ioutil.ReadFile("/etc/config/network")
if err != nil {
return err
}
oc.readConf(data, "interface", "lan")
data, err = ioutil.ReadFile("/etc/config/dhcp")
if err != nil {
return err
}
oc.readConf(data, "dhcp", "lan")
oc.readConf(data, "dnsmasq", "")
err = oc.prepareOutput()
if err != nil {
return err
}
err = oc.readConfDHCPStatic(data)
if err != nil {
return err
}
return nil
}
// Read system configuration files and write our configuration files
func importOpenwrtConfig(configFn string) error {
oc := openwrtConfig{}
err := oc.Process()
if err != nil {
return err
}
// config file path can be overridden by command-line arguments:
Context.configFilename = "AdGuardHome.yaml"
if len(configFn) != 0 {
Context.configFilename = configFn
}
initConfig()
if util.FileExists(config.getConfigFilename()) {
err = upgradeConfig()
if err != nil {
return err
}
err = parseConfig()
if err != nil {
return err
}
}
config.DNS.BootstrapDNS = oc.bsDNS
config.DHCP.Enabled = true
config.DHCP.InterfaceName = oc.iface
config.DHCP.GatewayIP = oc.gwIP
config.DHCP.SubnetMask = oc.snMask
config.DHCP.RangeStart = oc.rangeStart
config.DHCP.RangeEnd = oc.rangeEnd
config.DHCP.LeaseDuration = oc.leaseDur
config.DHCP.DnsmasqFilePath = oc.dhcpDnsmasqLeaseFile
err = config.write()
if err != nil {
return err
}
dconf := dhcpd.ServerConfig{
WorkDir: Context.workDir,
}
ds := dhcpd.Create(dconf)
if ds == nil {
return fmt.Errorf("can't initialize DHCP module")
}
for _, ocl := range oc.leases {
l := dhcpd.Lease{
HWAddr: ocl.HWAddr,
IP: ocl.IP.To4(),
Hostname: ocl.Hostname,
}
err = ds.AddStaticLeaseWithFlags(l, false)
if err != nil {
continue
}
log.Debug("Static DHCP lease: %s -> %s (%s)",
l.HWAddr, l.IP, l.Hostname)
}
ds.Save()
return nil
}

104
home/openwrt_test.go Normal file
View File

@@ -0,0 +1,104 @@
package home
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestReadConf(t *testing.T) {
oc := openwrtConfig{}
// "interface"
data := []byte(` config interface 'lan'
option netmask '255.255.255.0'
option ipaddr '192.168.8.1'`)
oc.readConf(data, "interface", "lan")
assert.Equal(t, "255.255.255.0", oc.netmask)
assert.Equal(t, "192.168.8.1", oc.ipaddr)
// "dhcp"
data = []byte(` config dhcp 'unknown'
config dhcp 'lan'
option start '100'
option limit '150'
option leasetime '12h'
config dhcp 'unknown'`)
oc.readConf(data, "dhcp", "lan")
assert.Equal(t, "100", oc.dhcpStart)
assert.Equal(t, "150", oc.dhcpLimit)
assert.Equal(t, "12h", oc.dhcpLeasetime)
// resolv.conf
data = []byte(` # comment
nameserver abab::1234
nameserver 1.2.3.4`)
oc.readResolvConf(data)
assert.Equal(t, "abab::1234", oc.nameservers[0])
assert.Equal(t, "1.2.3.4", oc.nameservers[1])
// prepareOutput()
err := oc.prepareOutput()
assert.Equal(t, nil, err)
assert.Equal(t, "br-lan", oc.iface)
assert.Equal(t, "192.168.8.1", oc.gwIP)
assert.Equal(t, "255.255.255.0", oc.snMask)
assert.Equal(t, "192.168.8.100", oc.rangeStart)
assert.Equal(t, "192.168.8.249", oc.rangeEnd)
assert.Equal(t, uint32(43200), oc.leaseDur)
assert.Equal(t, "abab::1234", oc.bsDNS[0])
assert.Equal(t, "1.2.3.4", oc.bsDNS[1])
tmp := oc.ipaddr
oc.dhcpStart = "invalid" // not an IP
assert.True(t, oc.prepareOutput() != nil)
oc.ipaddr = tmp
tmp = oc.dhcpStart
oc.dhcpStart = "invalid" // not an integer
assert.True(t, oc.prepareOutput() != nil)
oc.dhcpStart = "256" //byte overflow
assert.True(t, oc.prepareOutput() != nil)
oc.dhcpStart = tmp
tmp = oc.dhcpLimit
oc.dhcpLimit = "invalid" // not an integer
assert.True(t, oc.prepareOutput() != nil)
oc.dhcpLimit = "200" //byte overflow
assert.True(t, oc.prepareOutput() != nil)
oc.dhcpLimit = tmp
tmp = oc.dhcpLeasetime
oc.dhcpLeasetime = "12m" // not an 'h'
assert.True(t, oc.prepareOutput() != nil)
oc.dhcpLeasetime = "invalid" // not an integer
assert.True(t, oc.prepareOutput() != nil)
oc.dhcpLeasetime = tmp
// dhcp static leases
data = []byte(`config host '123412341234'
option mac '12:34:12:34:12:34'
option ip '192.168.8.2'
option name 'hostname'`)
assert.True(t, nil == oc.readConfDHCPStatic(data))
assert.Equal(t, 1, len(oc.leases))
assert.Equal(t, "12:34:12:34:12:34", oc.leases[0].HWAddr.String())
assert.Equal(t, "192.168.8.2", oc.leases[0].IP.String())
assert.Equal(t, "hostname", oc.leases[0].Hostname)
// "dnsmasq"
// Note: "config dnsmasq ''" will also work
data = []byte(`
config dhcp 'unknown'
option asdf '100'
config dnsmasq
option asdf '100'
option leasefile '/tmp/dhcp.leases'
option leasetime '12h'`)
oc.readConf(data, "dnsmasq", "")
assert.Equal(t, "/tmp/dhcp.leases", oc.dhcpDnsmasqLeaseFile)
}

View File

@@ -1,9 +1,11 @@
package home
import (
"fmt"
"io/ioutil"
"os"
"runtime"
"strconv"
"strings"
"syscall"
@@ -71,6 +73,48 @@ func svcAction(s service.Service, action string) error {
return err
}
// Send SIGHUP to a process with ID taken from our pid-file
// If pid-file doesn't exist, find our PID using 'ps' command
func sendSigReload() {
if runtime.GOOS == "windows" {
log.Error("Not implemented on Windows")
return
}
pidfile := fmt.Sprintf("/var/run/%s.pid", serviceName)
data, err := ioutil.ReadFile(pidfile)
if os.IsNotExist(err) {
code, psdata, err := util.RunCommand("ps", "-C", serviceName, "-o", "pid=")
if err != nil || code != 0 {
log.Error("Can't find AdGuardHome process: %s code:%d", err, code)
return
}
data = []byte(psdata)
} else if err != nil {
log.Error("Can't read PID file %s: %s", pidfile, err)
return
}
parts := strings.SplitN(string(data), "\n", 2)
if len(parts) == 0 {
log.Error("Can't read PID file %s: bad value", pidfile)
return
}
pid, err := strconv.Atoi(parts[0])
if err != nil {
log.Error("Can't read PID file %s: %s", pidfile, err)
return
}
err = util.SendProcessSignal(pid, syscall.SIGHUP)
if err != nil {
log.Error("Can't send signal to PID %d: %s", pid, err)
return
}
log.Debug("Sent signal to PID %d", pid)
}
// handleServiceControlAction one of the possible control actions:
// install -- installs a service/daemon
// uninstall -- uninstalls it
@@ -84,6 +128,11 @@ func svcAction(s service.Service, action string) error {
func handleServiceControlAction(action string) {
log.Printf("Service control action: %s", action)
if action == "reload" {
sendSigReload()
return
}
pwd, err := os.Getwd()
if err != nil {
log.Fatal("Unable to find the path to the current directory")

View File

@@ -1,9 +1,6 @@
// Control: TLS configuring handlers
package home
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
@@ -16,18 +13,125 @@ import (
"fmt"
"io/ioutil"
"net/http"
"os"
"reflect"
"strings"
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/util"
"github.com/AdguardTeam/golibs/log"
"github.com/joomcode/errorx"
)
var tlsWebHandlersRegistered = false
// TLSMod - TLS module object
type TLSMod struct {
certLastMod time.Time // last modification time of the certificate file
conf tlsConfigSettings
confLock sync.Mutex
status tlsConfigStatus
}
// Create TLS module
func tlsCreate(conf tlsConfigSettings) *TLSMod {
t := &TLSMod{}
t.conf = conf
if t.conf.Enabled {
if !t.load() {
return nil
}
t.setCertFileTime()
}
return t
}
func (t *TLSMod) load() bool {
if !tlsLoadConfig(&t.conf, &t.status) {
return false
}
// validate current TLS config and update warnings (it could have been loaded from file)
data := validateCertificates(string(t.conf.CertificateChainData), string(t.conf.PrivateKeyData), t.conf.ServerName)
if !data.ValidPair {
log.Error(data.WarningValidation)
return false
}
t.status = data
return true
}
// Close - close module
func (t *TLSMod) Close() {
}
// WriteDiskConfig - write config
func (t *TLSMod) WriteDiskConfig(conf *tlsConfigSettings) {
t.confLock.Lock()
*conf = t.conf
t.confLock.Unlock()
}
func (t *TLSMod) setCertFileTime() {
if len(t.conf.CertificatePath) == 0 {
return
}
fi, err := os.Stat(t.conf.CertificatePath)
if err != nil {
log.Error("TLS: %s", err)
return
}
t.certLastMod = fi.ModTime().UTC()
}
// Start - start the module
func (t *TLSMod) Start() {
if !tlsWebHandlersRegistered {
tlsWebHandlersRegistered = true
t.registerWebHandlers()
}
t.confLock.Lock()
tlsConf := t.conf
t.confLock.Unlock()
Context.web.TLSConfigChanged(tlsConf)
}
// Reload - reload certificate file
func (t *TLSMod) Reload() {
t.confLock.Lock()
tlsConf := t.conf
t.confLock.Unlock()
if !tlsConf.Enabled || len(tlsConf.CertificatePath) == 0 {
return
}
fi, err := os.Stat(tlsConf.CertificatePath)
if err != nil {
log.Error("TLS: %s", err)
return
}
if fi.ModTime().UTC().Equal(t.certLastMod) {
log.Debug("TLS: certificate file isn't modified")
return
}
log.Debug("TLS: certificate file is modified")
t.confLock.Lock()
r := t.load()
t.confLock.Unlock()
if !r {
return
}
t.certLastMod = fi.ModTime().UTC()
_ = reconfigureDNSServer()
Context.web.TLSConfigChanged(tlsConf)
}
// Set certificate and private key data
func tlsLoadConfig(tls *tlsConfig, status *tlsConfigStatus) bool {
func tlsLoadConfig(tls *tlsConfigSettings, status *tlsConfigStatus) bool {
tls.CertificateChainData = []byte(tls.CertificateChain)
tls.PrivateKeyData = []byte(tls.PrivateKey)
@@ -61,98 +165,126 @@ func tlsLoadConfig(tls *tlsConfig, status *tlsConfigStatus) bool {
return true
}
// RegisterTLSHandlers registers HTTP handlers for TLS configuration
func RegisterTLSHandlers() {
httpRegister(http.MethodGet, "/control/tls/status", handleTLSStatus)
httpRegister(http.MethodPost, "/control/tls/configure", handleTLSConfigure)
httpRegister(http.MethodPost, "/control/tls/validate", handleTLSValidate)
type tlsConfigStatus struct {
ValidCert bool `json:"valid_cert"` // ValidCert is true if the specified certificates chain is a valid chain of X509 certificates
ValidChain bool `json:"valid_chain"` // ValidChain is true if the specified certificates chain is verified and issued by a known CA
Subject string `json:"subject,omitempty"` // Subject is the subject of the first certificate in the chain
Issuer string `json:"issuer,omitempty"` // Issuer is the issuer of the first certificate in the chain
NotBefore time.Time `json:"not_before,omitempty"` // NotBefore is the NotBefore field of the first certificate in the chain
NotAfter time.Time `json:"not_after,omitempty"` // NotAfter is the NotAfter field of the first certificate in the chain
DNSNames []string `json:"dns_names"` // DNSNames is the value of SubjectAltNames field of the first certificate in the chain
// key status
ValidKey bool `json:"valid_key"` // ValidKey is true if the key is a valid private key
KeyType string `json:"key_type,omitempty"` // KeyType is one of RSA or ECDSA
// is usable? set by validator
ValidPair bool `json:"valid_pair"` // ValidPair is true if both certificate and private key are correct
// warnings
WarningValidation string `json:"warning_validation,omitempty"` // WarningValidation is a validation warning message with the issue description
}
func handleTLSStatus(w http.ResponseWriter, r *http.Request) {
marshalTLS(w, config.TLS)
// field ordering is important -- yaml fields will mirror ordering from here
type tlsConfig struct {
tlsConfigSettings `json:",inline"`
tlsConfigStatus `json:",inline"`
}
func handleTLSValidate(w http.ResponseWriter, r *http.Request) {
data, err := unmarshalTLS(r)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
func (t *TLSMod) handleTLSStatus(w http.ResponseWriter, r *http.Request) {
t.confLock.Lock()
data := tlsConfig{
tlsConfigSettings: t.conf,
tlsConfigStatus: t.status,
}
// check if port is available
// BUT: if we are already using this port, no need
alreadyRunning := false
if Context.httpsServer.server != nil {
alreadyRunning = true
}
if !alreadyRunning {
err = util.CheckPortAvailable(config.BindHost, data.PortHTTPS)
if err != nil {
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", data.PortHTTPS)
return
}
}
status := tlsConfigStatus{}
if tlsLoadConfig(&data, &status) {
status = validateCertificates(string(data.CertificateChainData), string(data.PrivateKeyData), data.ServerName)
}
data.tlsConfigStatus = status
t.confLock.Unlock()
marshalTLS(w, data)
}
func handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
setts, err := unmarshalTLS(r)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
}
if !WebCheckPortAvailable(setts.PortHTTPS) {
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", setts.PortHTTPS)
return
}
status := tlsConfigStatus{}
if tlsLoadConfig(&setts, &status) {
status = validateCertificates(string(setts.CertificateChainData), string(setts.PrivateKeyData), setts.ServerName)
}
data := tlsConfig{
tlsConfigSettings: setts,
tlsConfigStatus: status,
}
marshalTLS(w, data)
}
func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
data, err := unmarshalTLS(r)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to unmarshal TLS config: %s", err)
return
}
// check if port is available
// BUT: if we are already using this port, no need
alreadyRunning := false
if Context.httpsServer.server != nil {
alreadyRunning = true
}
if !alreadyRunning {
err = util.CheckPortAvailable(config.BindHost, data.PortHTTPS)
if err != nil {
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", data.PortHTTPS)
return
}
if !WebCheckPortAvailable(data.PortHTTPS) {
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", data.PortHTTPS)
return
}
status := tlsConfigStatus{}
if !tlsLoadConfig(&data, &status) {
data.tlsConfigStatus = status
marshalTLS(w, data)
data2 := tlsConfig{
tlsConfigSettings: data,
tlsConfigStatus: t.status,
}
marshalTLS(w, data2)
return
}
data.tlsConfigStatus = validateCertificates(string(data.CertificateChainData), string(data.PrivateKeyData), data.ServerName)
status = validateCertificates(string(data.CertificateChainData), string(data.PrivateKeyData), data.ServerName)
restartHTTPS := false
if !reflect.DeepEqual(config.TLS.tlsConfigSettings, data.tlsConfigSettings) {
t.confLock.Lock()
if !reflect.DeepEqual(t.conf, data) {
log.Printf("tls config settings have changed, will restart HTTPS server")
restartHTTPS = true
}
config.TLS = data
err = writeAllConfigsAndReloadDNS()
// Note: don't do just `t.conf = data` because we must preserve all other members of t.conf
t.conf.Enabled = data.Enabled
t.conf.ServerName = data.ServerName
t.conf.ForceHTTPS = data.ForceHTTPS
t.conf.PortHTTPS = data.PortHTTPS
t.conf.PortDNSOverTLS = data.PortDNSOverTLS
t.conf.CertificateChain = data.CertificateChain
t.conf.CertificatePath = data.CertificatePath
t.conf.CertificateChainData = data.CertificateChainData
t.conf.PrivateKey = data.PrivateKey
t.conf.PrivateKeyPath = data.PrivateKeyPath
t.conf.PrivateKeyData = data.PrivateKeyData
t.status = status
t.confLock.Unlock()
t.setCertFileTime()
onConfigModified()
err = reconfigureDNSServer()
if err != nil {
httpError(w, http.StatusInternalServerError, "Couldn't write config file: %s", err)
httpError(w, http.StatusInternalServerError, "%s", err)
return
}
marshalTLS(w, data)
data2 := tlsConfig{
tlsConfigSettings: data,
tlsConfigStatus: t.status,
}
marshalTLS(w, data2)
// this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block
// until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely
if restartHTTPS {
go func() {
time.Sleep(time.Second) // TODO: could not find a way to reliably know that data was fully sent to client by https server, so we wait a bit to let response through before closing the server
Context.httpsServer.cond.L.Lock()
Context.httpsServer.cond.Broadcast()
if Context.httpsServer.server != nil {
Context.httpsServer.server.Shutdown(context.TODO())
}
Context.httpsServer.cond.L.Unlock()
Context.web.TLSConfigChanged(data)
}()
}
}
@@ -200,6 +332,7 @@ func verifyCertChain(data *tlsConfigStatus, certChain string, serverName string)
opts := x509.VerifyOptions{
DNSName: serverName,
Roots: Context.tlsRoots,
}
log.Printf("number of certs - %d", len(parsedCerts))
@@ -336,8 +469,8 @@ func parsePrivateKey(der []byte) (crypto.PrivateKey, string, error) {
}
// unmarshalTLS handles base64-encoded certificates transparently
func unmarshalTLS(r *http.Request) (tlsConfig, error) {
data := tlsConfig{}
func unmarshalTLS(r *http.Request) (tlsConfigSettings, error) {
data := tlsConfigSettings{}
err := json.NewDecoder(r.Body).Decode(&data)
if err != nil {
return data, errorx.Decorate(err, "Failed to parse new TLS config json")
@@ -388,3 +521,10 @@ func marshalTLS(w http.ResponseWriter, data tlsConfig) {
return
}
}
// registerWebHandlers registers HTTP handlers for TLS configuration
func (t *TLSMod) registerWebHandlers() {
httpRegister("GET", "/control/tls/status", t.handleTLSStatus)
httpRegister("POST", "/control/tls/configure", t.handleTLSConfigure)
httpRegister("POST", "/control/tls/validate", t.handleTLSValidate)
}

189
home/web.go Normal file
View File

@@ -0,0 +1,189 @@
package home
import (
"context"
"crypto/tls"
"net"
"net/http"
"strconv"
"sync"
"github.com/AdguardTeam/AdGuardHome/util"
"github.com/AdguardTeam/golibs/log"
"github.com/NYTimes/gziphandler"
"github.com/gobuffalo/packr"
)
type WebConfig struct {
firstRun bool
BindHost string
BindPort int
PortHTTPS int
}
// HTTPSServer - HTTPS Server
type HTTPSServer struct {
server *http.Server
cond *sync.Cond
condLock sync.Mutex
shutdown bool // if TRUE, don't restart the server
enabled bool
cert tls.Certificate
}
// Web - module object
type Web struct {
conf *WebConfig
forceHTTPS bool
portHTTPS int
httpServer *http.Server // HTTP module
httpsServer HTTPSServer // HTTPS module
}
// CreateWeb - create module
func CreateWeb(conf *WebConfig) *Web {
w := Web{}
w.conf = conf
// Initialize and run the admin Web interface
box := packr.NewBox("../build/static")
// if not configured, redirect / to /install.html, otherwise redirect /install.html to /
http.Handle("/", postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(http.FileServer(box)))))
// add handlers for /install paths, we only need them when we're not configured yet
if conf.firstRun {
log.Info("This is the first launch of AdGuard Home, redirecting everything to /install.html ")
http.Handle("/install.html", preInstallHandler(http.FileServer(box)))
w.registerInstallHandlers()
} else {
registerControlHandlers()
}
w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock)
return &w
}
// WebCheckPortAvailable - check if port is available
// BUT: if we are already using this port, no need
func WebCheckPortAvailable(port int) bool {
alreadyRunning := false
if Context.web.httpsServer.server != nil {
alreadyRunning = true
}
if !alreadyRunning {
err := util.CheckPortAvailable(config.BindHost, port)
if err != nil {
return false
}
}
return true
}
// TLSConfigChanged - called when TLS configuration has changed
func (w *Web) TLSConfigChanged(tlsConf tlsConfigSettings) {
log.Debug("Web: applying new TLS configuration")
w.conf.PortHTTPS = tlsConf.PortHTTPS
w.forceHTTPS = (tlsConf.ForceHTTPS && tlsConf.Enabled && tlsConf.PortHTTPS != 0)
w.portHTTPS = tlsConf.PortHTTPS
enabled := tlsConf.Enabled &&
tlsConf.PortHTTPS != 0 &&
len(tlsConf.PrivateKeyData) != 0 &&
len(tlsConf.CertificateChainData) != 0
var cert tls.Certificate
var err error
if enabled {
cert, err = tls.X509KeyPair(tlsConf.CertificateChainData, tlsConf.PrivateKeyData)
if err != nil {
log.Fatal(err)
}
}
w.httpsServer.cond.L.Lock()
if w.httpsServer.server != nil {
w.httpsServer.server.Shutdown(context.TODO())
}
w.httpsServer.enabled = enabled
w.httpsServer.cert = cert
w.httpsServer.cond.Broadcast()
w.httpsServer.cond.L.Unlock()
}
// Start - start serving HTTP requests
func (w *Web) Start() {
// for https, we have a separate goroutine loop
go w.httpServerLoop()
// this loop is used as an ability to change listening host and/or port
for !w.httpsServer.shutdown {
printHTTPAddresses("http")
// we need to have new instance, because after Shutdown() the Server is not usable
address := net.JoinHostPort(w.conf.BindHost, strconv.Itoa(w.conf.BindPort))
w.httpServer = &http.Server{
Addr: address,
}
err := w.httpServer.ListenAndServe()
if err != http.ErrServerClosed {
cleanupAlways()
log.Fatal(err)
}
// We use ErrServerClosed as a sign that we need to rebind on new address, so go back to the start of the loop
}
}
// Close - stop HTTP server, possibly waiting for all active connections to be closed
func (w *Web) Close() {
log.Info("Stopping HTTP server...")
w.httpsServer.cond.L.Lock()
w.httpsServer.shutdown = true
w.httpsServer.cond.L.Unlock()
if w.httpsServer.server != nil {
_ = w.httpsServer.server.Shutdown(context.TODO())
}
if w.httpServer != nil {
_ = w.httpServer.Shutdown(context.TODO())
}
log.Info("Stopped HTTP server")
}
func (w *Web) httpServerLoop() {
for {
w.httpsServer.cond.L.Lock()
if w.httpsServer.shutdown {
w.httpsServer.cond.L.Unlock()
break
}
// this mechanism doesn't let us through until all conditions are met
for !w.httpsServer.enabled { // sleep until necessary data is supplied
w.httpsServer.cond.Wait()
if w.httpsServer.shutdown {
w.httpsServer.cond.L.Unlock()
return
}
}
w.httpsServer.cond.L.Unlock()
// prepare HTTPS server
address := net.JoinHostPort(w.conf.BindHost, strconv.Itoa(w.conf.PortHTTPS))
w.httpsServer.server = &http.Server{
Addr: address,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{w.httpsServer.cert},
MinVersion: tls.VersionTLS12,
RootCAs: Context.tlsRoots,
},
}
printHTTPAddresses("https")
err := w.httpsServer.server.ListenAndServeTLS("", "")
if err != http.ErrServerClosed {
cleanupAlways()
log.Fatal(err)
}
}
}

View File

@@ -1110,6 +1110,8 @@ definitions:
type: "string"
edns_cs_enabled:
type: "boolean"
dnssec_enabled:
type: "boolean"
UpstreamsConfig:
type: "object"
@@ -1517,6 +1519,8 @@ definitions:
description: "Answer from upstream server (optional)"
items:
$ref: "#/definitions/DnsAnswer"
answer_dnssec:
type: "boolean"
client:
type: "string"
example: "192.168.0.1"

View File

@@ -286,7 +286,15 @@ func logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
if msg != nil {
jsonEntry["status"] = dns.RcodeToString[msg.Rcode]
opt := msg.IsEdns0()
dnssecOk := false
if opt != nil {
dnssecOk = opt.Do()
}
jsonEntry["answer_dnssec"] = dnssecOk
}
if len(entry.Result.Rule) > 0 {
jsonEntry["rule"] = entry.Result.Rule
jsonEntry["filterId"] = entry.Result.FilterID

View File

@@ -9,10 +9,6 @@ import (
"strings"
)
// ---------------------
// general helpers
// ---------------------
// fileExists returns TRUE if file exists
func FileExists(fn string) bool {
_, err := os.Stat(fn)
@@ -33,9 +29,6 @@ func RunCommand(command string, arguments ...string) (int, string, error) {
return cmd.ProcessState.ExitCode(), string(out), nil
}
// ---------------------
// debug logging helpers
// ---------------------
func FuncName() string {
pc := make([]uintptr, 10) // at least 1 entry needed
runtime.Callers(2, pc)
@@ -57,3 +50,11 @@ func SplitNext(str *string, splitBy byte) string {
}
return strings.TrimSpace(s)
}
// MinInt - return the minimum value
func MinInt(a, b int) int {
if a < b {
return a
}
return b
}

View File

@@ -88,10 +88,6 @@ func GetValidNetInterfacesForWeb() ([]NetInterface, error) {
if ipNet.IP.IsLinkLocalUnicast() {
continue
}
// ignore IPv6
if ipNet.IP.To4() == nil {
continue
}
netIface.Addresses = append(netIface.Addresses, ipNet.IP.String())
netIface.Subnets = append(netIface.Subnets, ipNet.String())
}

View File

@@ -25,3 +25,8 @@ func SetRlimit(val uint) {
func HaveAdminRights() (bool, error) {
return os.Getuid() == 0, nil
}
// SendProcessSignal - send signal to a process
func SendProcessSignal(pid int, sig syscall.Signal) error {
return syscall.Kill(pid, sig)
}

View File

@@ -25,3 +25,8 @@ func SetRlimit(val uint) {
func HaveAdminRights() (bool, error) {
return os.Getuid() == 0, nil
}
// SendProcessSignal - send signal to a process
func SendProcessSignal(pid int, sig syscall.Signal) error {
return syscall.Kill(pid, sig)
}

View File

@@ -1,6 +1,11 @@
package util
import "golang.org/x/sys/windows"
import (
"fmt"
"syscall"
"golang.org/x/sys/windows"
)
// Set user-specified limit of how many fd's we can use
func SetRlimit(val uint) {
@@ -26,3 +31,7 @@ func HaveAdminRights() (bool, error) {
}
return true, nil
}
func SendProcessSignal(pid int, sig syscall.Signal) error {
return fmt.Errorf("not supported on Windows")
}

47
util/tls.go Normal file
View File

@@ -0,0 +1,47 @@
package util
import (
"crypto/x509"
"io/ioutil"
"os"
"runtime"
"github.com/AdguardTeam/golibs/log"
)
// LoadSystemRootCAs - load root CAs from the system
// Return the x509 certificate pool object
// Return nil if nothing has been found.
// This means that Go.crypto will use its default algorithm to find system root CA list.
// https://github.com/AdguardTeam/AdGuardHome/issues/1311
func LoadSystemRootCAs() *x509.CertPool {
if runtime.GOOS != "linux" {
return nil
}
// Directories with the system root certificates, that aren't supported by Go.crypto
dirs := []string{
"/opt/etc/ssl/certs", // Entware
}
roots := x509.NewCertPool()
for _, dir := range dirs {
fis, err := ioutil.ReadDir(dir)
if err != nil {
if !os.IsNotExist(err) {
log.Error("Opening directory: %s: %s", dir, err)
}
continue
}
rootsAdded := false
for _, fi := range fis {
data, err := ioutil.ReadFile(dir + "/" + fi.Name())
if err == nil && roots.AppendCertsFromPEM(data) {
rootsAdded = true
}
}
if rootsAdded {
return roots
}
}
return nil
}