Compare commits
39 Commits
v0.101.0
...
1464-owrt-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da2a8daff2 | ||
|
|
5fbf28b9cf | ||
|
|
dced8a5a83 | ||
|
|
33a98aa937 | ||
|
|
e822443600 | ||
|
|
82f36341e3 | ||
|
|
6eadca25d1 | ||
|
|
72c20acb86 | ||
|
|
79bfa6a72b | ||
|
|
a5c2cdaf38 | ||
|
|
7ff743ab32 | ||
|
|
3303d77dad | ||
|
|
58f1831032 | ||
|
|
4dba20941d | ||
|
|
360ee3e392 | ||
|
|
32baa907b6 | ||
|
|
dd7d9dc334 | ||
|
|
db30f27c8f | ||
|
|
8e4bc29103 | ||
|
|
0789e4b20d | ||
|
|
e8129f15c7 | ||
|
|
c77907694d | ||
|
|
fa2f793ac7 | ||
|
|
e5db33705d | ||
|
|
bc9bccc669 | ||
|
|
646725efb7 | ||
|
|
a93652b1c0 | ||
|
|
5f328d20ca | ||
|
|
0e030154ee | ||
|
|
1000aef1d2 | ||
|
|
b345595dbf | ||
|
|
88853b76d9 | ||
|
|
dfa278b845 | ||
|
|
480c6ac753 | ||
|
|
2e054b6732 | ||
|
|
229d040ee2 | ||
|
|
36ba8380de | ||
|
|
5dc7b848df | ||
|
|
01d9078107 |
@@ -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": "ภาษาไทย"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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:
|
||||

|
||||
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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)"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
483
client/src/__locales/ro.json
Normal file
483
client/src/__locales/ro.json
Normal 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"
|
||||
}
|
||||
416
client/src/__locales/th.json
Normal file
416
client/src/__locales/th.json
Normal 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": "ตรวจสอบว่าชื่อโฮสต์ถูกกรอง"
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -112,6 +112,7 @@ const renderMultiselect = (props) => {
|
||||
onChange={value => input.onChange(value)}
|
||||
onBlur={() => input.onBlur(input.value)}
|
||||
placeholder={placeholder}
|
||||
blurInputOnSelect={false}
|
||||
isMulti
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -187,10 +187,6 @@ export const SERVICES = [
|
||||
id: 'snapchat',
|
||||
name: 'Snapchat',
|
||||
},
|
||||
{
|
||||
id: 'messenger',
|
||||
name: 'Messenger',
|
||||
},
|
||||
{
|
||||
id: 'twitch',
|
||||
name: 'Twitch',
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -45,6 +45,7 @@ const dnsConfig = handleActions(
|
||||
blocking_ipv6: DEFAULT_BLOCKING_IPV6,
|
||||
edns_cs_enabled: false,
|
||||
disable_ipv6: false,
|
||||
dnssec_enabled: false,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
BIN
doc/agh-arch.png
BIN
doc/agh-arch.png
Binary file not shown.
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 187 KiB |
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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" {
|
||||
|
||||
88
home/dns.go
88
home/dns.go
@@ -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")
|
||||
}
|
||||
|
||||
308
home/filter.go
308
home/filter.go
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
273
home/home.go
273
home/home.go
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
364
home/openwrt.go
Normal 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
104
home/openwrt_test.go
Normal 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)
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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
189
home/web.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
47
util/tls.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user