Compare commits

...

94 Commits

Author SHA1 Message Date
Simon Zolin
7d2df26335 Merge: Bump version to v0.95-hotfix
* commit 'ae403fb13752df1fcdf33839d0747e44722382db':
  Bump version to v0.95-hotfix
2019-04-24 14:39:52 +03:00
Simon Zolin
ae403fb137 Bump version to v0.95-hotfix 2019-04-24 14:38:00 +03:00
Simon Zolin
e1bb89c393 Merge: dnsfilter: prevent recursion when both parental control and safebrowsing are enabled
Close #732

* commit 'c4e67690f4fcceb055cbea73610b5974855db96f':
  * dnsfilter: don't use global variable for custom resolver function
  - dnsfilter: prevent recursion when both parental control and safebrowsing are enabled
2019-04-24 12:52:16 +03:00
Simon Zolin
c4e67690f4 * dnsfilter: don't use global variable for custom resolver function 2019-04-24 12:49:12 +03:00
Simon Zolin
f6023b395e - dnsfilter: prevent recursion when both parental control and safebrowsing are enabled 2019-04-24 12:38:05 +03:00
Simon Zolin
cedab695c2 Merge: Bump version to v0.95
* commit '1c339e5fcd73a4ce070f7284b1ea7d1b44d63803':
  Bump version to v0.95
2019-04-23 20:28:54 +03:00
Simon Zolin
1c339e5fcd Bump version to v0.95 2019-04-23 20:23:39 +03:00
Andrey Meshkov
4eb910c35e Merge pull request #203 in DNS/adguard-dns from fix/647 to master
* commit 'c6957bed64acd5b2265b8b94bb07f8bb77520084':
  - install: immediately schedule filters update procedure after installation is complete
2019-04-23 20:15:12 +03:00
Simon Zolin
c6957bed64 - install: immediately schedule filters update procedure after installation is complete 2019-04-23 19:56:21 +03:00
Andrey Meshkov
8e6f7be5b8 Merge pull request #202 in DNS/adguard-dns from fix/647 to master
* commit '528c1a72cac331f08de5fdee4538529c70dff1bb':
  - use 127.0.0.1 as a resolver address when DNS binding address is 0.0.0.0
  - app: don't print filter update error messages on first launch before  DNS server is set up
2019-04-23 19:48:05 +03:00
Simon Zolin
528c1a72ca - use 127.0.0.1 as a resolver address when DNS binding address is 0.0.0.0 2019-04-23 19:37:14 +03:00
Simon Zolin
3087c54a15 - app: don't print filter update error messages on first launch before
DNS server is set up
2019-04-23 19:26:51 +03:00
Simon Zolin
5c65d0cabe Merge: various bugfixes
* commit '4231920ee84074e573539c05c53cb357b1a22154':
  * client: add "DuckDuckGo" to safesearch label text
  - dhcp: fix build on macos #704
  - dnsfilter: fix npe in dnsfilter test
  - dnsfilter: fix safesearch issue #268
2019-04-23 16:20:46 +03:00
Simon Zolin
4231920ee8 * client: add "DuckDuckGo" to safesearch label text 2019-04-23 15:25:09 +03:00
Simon Zolin
d5f46f51b8 - dhcp: fix build on macos #704 2019-04-23 15:14:26 +03:00
Aleksey Dmitrevskiy
a860c8e6ff - dnsfilter: fix npe in dnsfilter test 2019-04-23 15:09:23 +03:00
Aleksey Dmitrevskiy
0794704f74 - dnsfilter: fix safesearch issue #268 2019-04-23 15:08:41 +03:00
Simon Zolin
173ab2a3c1 Merge: - /control/dhcp/find_active_dhcp: fix DHCP server detection
Close #704

* commit '68dc8a13411c09cd8c382a1bf986953659166506':
  * control: update "DHCP server not found" message
  * update openapi.yaml
  - /control/dhcp/find_active_dhcp: fix DHCP server detection
2019-04-23 12:29:15 +03:00
Simon Zolin
68dc8a1341 * control: update "DHCP server not found" message 2019-04-23 12:26:14 +03:00
Simon Zolin
043e89a1a4 * update openapi.yaml 2019-04-23 12:26:14 +03:00
Simon Zolin
c5ed6da5bd - /control/dhcp/find_active_dhcp: fix DHCP server detection
Before this patch we couldn't receive incoming DHCP packets.
Now we bind() to 0.0.0.0 and set the required network interface
 using SO_BINDTODEVICE option.

As an improvement, we now wait until a reply to our request is
 received and skip all unknown packets.
2019-04-23 12:26:14 +03:00
Simon Zolin
69c5f175e8 Merge: Installation wizard #685
* commit '79b0fac01a544e35207420fed78e1c2a63d428d8':
  * control: move /install handlers to a separate file
  + add technical document
  * app: move code for http server loop to a separate function
  * client: fixed getDefaultAddresses structure
  - client: npm audit fix
  * client: validate form on load
  * client: installation wizard additional checks
  * update openapi.yaml
  + service install: a post-install guide of what to do next
  * control: /install/configure: validate port number
  * control: /install/configure: reset configuration back to its current state on error
  + control: /install/*: test TCP port availability for DNS server
  + control: /install/check_config: Check and deactivate DNSStubListener
  * control: /install/configure: refactor
  + control: add /install/check_config handler
  * control: /install/get_addresses: don't check if ports are available
  + app: unix, windows: require root user on first launch
  * setRlimit(): move OS-specific code to separate files
2019-04-23 12:16:34 +03:00
Simon Zolin
79b0fac01a * control: move /install handlers to a separate file 2019-04-23 11:48:30 +03:00
Simon Zolin
bc81a0ecff + add technical document 2019-04-23 11:45:09 +03:00
Simon Zolin
1ac9419c94 * app: move code for http server loop to a separate function 2019-04-23 11:41:53 +03:00
Ildar Kamalov
5f88abb322 * client: fixed getDefaultAddresses structure 2019-04-23 11:41:53 +03:00
Ildar Kamalov
33db419384 - client: npm audit fix 2019-04-23 11:41:53 +03:00
Ildar Kamalov
ceabad0fd0 * client: validate form on load 2019-04-23 11:41:53 +03:00
Ildar Kamalov
f76b7c3d94 * client: installation wizard additional checks 2019-04-23 11:41:53 +03:00
Simon Zolin
9e68a522cb * update openapi.yaml 2019-04-23 11:41:53 +03:00
Simon Zolin
faa7c7b2d4 + service install: a post-install guide of what to do next 2019-04-23 11:41:53 +03:00
Simon Zolin
d326d1bc8b * control: /install/configure: validate port number 2019-04-23 11:41:53 +03:00
Simon Zolin
73fbe8b95a * control: /install/configure: reset configuration back to its current state on error 2019-04-23 11:41:53 +03:00
Simon Zolin
87147ac89f + control: /install/*: test TCP port availability for DNS server 2019-04-23 11:41:53 +03:00
Simon Zolin
8d936b5756 + control: /install/check_config: Check and deactivate DNSStubListener 2019-04-23 11:41:53 +03:00
Simon Zolin
4ca24b7707 * control: /install/configure: refactor 2019-04-23 11:41:53 +03:00
Simon Zolin
133dd75ec3 + control: add /install/check_config handler 2019-04-23 11:41:53 +03:00
Simon Zolin
00c128f0a4 * control: /install/get_addresses: don't check if ports are available
* always use port 80
2019-04-23 11:41:53 +03:00
Simon Zolin
e4b53db558 + app: unix, windows: require root user on first launch 2019-04-23 11:41:53 +03:00
Simon Zolin
1611057852 * setRlimit(): move OS-specific code to separate files 2019-04-22 17:41:10 +03:00
Simon Zolin
00ba63341b Merge: Don't use the system default resolver implicitly
Close #647

* commit 'a1b18776678ee894cd0b558cf8683845056a2dfa':
  + parental, safesearch: use our own DNS resolver instead of system default
  + control: use our own DNS resolver instead of system default
2019-04-22 11:28:43 +03:00
Simon Zolin
a1b1877667 + parental, safesearch: use our own DNS resolver instead of system default 2019-04-18 14:31:13 +03:00
Simon Zolin
bebdc1b5bc + control: use our own DNS resolver instead of system default 2019-04-18 12:47:22 +03:00
Ildar Kamalov
1836e56e6e Merge pull request in DNS/adguard-dns from feature/393 to master
* commit 'f83d026c330ec56b76dc995afabe67bc45f654c7':
  + client: privacy policy link
2019-04-17 18:59:05 +03:00
Ildar Kamalov
f83d026c33 + client: privacy policy link
Closes #393
2019-04-17 15:03:25 +03:00
Alexey Dmitrievskiy
61554ba4e0 Merge: * app, dnsforward: add MinVersion for TLS configs #651
* commit 'c82887d3aa3332cf4d7a7dda22f51b160be306b3':
  * app, dnsforward: add MinVersion for TLS configs
2019-04-17 13:56:20 +03:00
Aleksey Dmitrevskiy
c82887d3aa * app, dnsforward: add MinVersion for TLS configs 2019-04-17 12:02:56 +03:00
Alexey Dmitrievskiy
faeda3f075 Merge: * safesearch: Enfore safe search for duckduckgo #268
* commit 'dfa39293a189f59dfd5da335aa66d80b04568a10':
  Update safesearch.go
  Update safesearch.go
  Update safesearch.go
2019-04-16 18:44:51 +03:00
Alexey Dmitrievskiy
afeadbb454 Merge: increase the default timeout used for downloading filters updates #643
* commit 'c78cee339628c8f91a1c698e731c25d99dec8c8b':
  * control: increase http.Client timeout to 5 minutes
  * control: increase the default timeout used for downloading filters updates
2019-04-16 18:40:44 +03:00
Ildar Kamalov
0e031a4921 Merge pull request in DNS/adguard-dns from fix/710 to master
* commit 'd0942c88c8968b7e410356b1ae89ec7d3e69f2bb':
  * client: replace PUT and DELETE with POST
  * control: replace ensurePUT and ensureDELETE with ensurePOST
2019-04-16 12:19:27 +03:00
Ildar Kamalov
d0942c88c8 * client: replace PUT and DELETE with POST 2019-04-16 12:11:31 +03:00
Ildar Kamalov
794d302ce5 * control: replace ensurePUT and ensureDELETE with ensurePOST 2019-04-16 12:11:12 +03:00
Simon Zolin
7f1f85b08c Merge: + http server: enable gzip compression
Close #626

* commit '3f404bc37e14bbd781f6feb62502a44188b4c7ef':
  + http server: enable gzip compression for /control/querylog
  + http server: enable gzip compression for static documents
2019-04-16 11:11:01 +03:00
Simon Zolin
c8e4f61534 Merge: + config: add "rlimit_nofile" setting - Maximum number of opened fd's per process
* commit '1f1e26f67b00ee4983c65e4e3aabf59a488f687b':
  + config: add "rlimit_nofile" setting - Maximum number of opened fd's per process
2019-04-15 18:34:53 +03:00
Simon Zolin
3f404bc37e + http server: enable gzip compression for /control/querylog 2019-04-15 16:21:12 +03:00
Simon Zolin
7911a24dc8 Merge: app: add '--pidfile FILE' command-line parameter
Close #609

* commit '7746a3e6a9f204e7b7ab498c2f484cf426fb1a18':
  + app: add '--pidfile FILE' command-line parameter
2019-04-15 15:36:22 +03:00
Simon Zolin
7746a3e6a9 + app: add '--pidfile FILE' command-line parameter 2019-04-15 13:41:46 +03:00
Simon Zolin
08bedacf0a Merge: DHCP: check/set static IP
Close #686

* commit '828d3121be807daa8f839dfa7a7ac4ba8a6e7cd8':
  * client: show message if there is no static ip
  * client: rename constant
  * hasStaticIP: use properly named boolean variable
  + client: static_ip warnings
  * client: error text
  * client: disable DHCP check if server enabled and hide errors on disable
  * client: hide error if DHCP enabled and require check DHCP before enabling
  * client: accordion styles
  - client: fix DHCP fields validation
  * client: fix DHCP error message
  + config: set default parameters for DHCP server
  + /control/dhcp/set_config: set static IP
  + /control/dhcp/find_active_dhcp: detect static IP on Linux
  * /control/dhcp/find_active_dhcp: new JSON response format
2019-04-15 12:59:16 +03:00
Ildar Kamalov
74a0938038 Merge pull request in DNS/adguard-dns from fix/700 to master
* commit '4d217583b099da6a2a2773d0870ab39e36a9b141':
  - client: validate encryption if enabled
2019-04-11 09:56:49 +03:00
Ildar Kamalov
828d3121be * client: show message if there is no static ip 2019-04-05 15:15:56 +03:00
Ildar Kamalov
b6ae539c36 * client: rename constant 2019-04-05 14:32:56 +03:00
Simon Zolin
fa5ff053b7 * hasStaticIP: use properly named boolean variable 2019-04-05 12:47:26 +03:00
Ildar Kamalov
6bf57ae84e + client: static_ip warnings 2019-04-04 17:13:51 +03:00
Ildar Kamalov
472dc0b77d * client: error text 2019-04-04 17:13:51 +03:00
Ildar Kamalov
4b821d67f5 * client: disable DHCP check if server enabled and hide errors on disable 2019-04-04 17:13:51 +03:00
Ildar Kamalov
24fc2957c5 * client: hide error if DHCP enabled and require check DHCP before enabling 2019-04-04 17:13:51 +03:00
Ildar Kamalov
6ba0e4686a * client: accordion styles 2019-04-04 17:13:51 +03:00
Ildar Kamalov
b92fb34f37 - client: fix DHCP fields validation 2019-04-04 17:13:51 +03:00
Ildar Kamalov
ffd9f1aaa9 * client: fix DHCP error message 2019-04-04 17:13:51 +03:00
Simon Zolin
d43290fe31 + config: set default parameters for DHCP server 2019-04-04 17:13:51 +03:00
Simon Zolin
ef22a31a93 + /control/dhcp/set_config: set static IP 2019-04-04 17:13:51 +03:00
Simon Zolin
0ed619c9c8 + /control/dhcp/find_active_dhcp: detect static IP on Linux 2019-04-04 17:13:51 +03:00
Simon Zolin
8b6e9ef5f9 * /control/dhcp/find_active_dhcp: new JSON response format 2019-04-04 17:13:51 +03:00
Simon Zolin
cca61a33c6 + http server: enable gzip compression for static documents 2019-04-04 16:58:31 +03:00
Ildar Kamalov
4d217583b0 - client: validate encryption if enabled
Closes #700
2019-04-04 14:54:57 +03:00
Aleksey Dmitrevskiy
c78cee3396 * control: increase http.Client timeout to 5 minutes 2019-04-04 11:04:14 +03:00
Ildar Kamalov
b453d9f41d Merge pull request in DNS/adguard-dns from fix/688 to master
* commit 'e231230f1b2045b705602b2c9d3a14c47f276273':
  - client: fixed leases table on mobile
2019-04-03 12:35:06 +03:00
Ildar Kamalov
e231230f1b - client: fixed leases table on mobile
Closes #688
2019-04-02 13:59:03 +03:00
Aleksey Dmitrevskiy
850e856e6e * control: increase the default timeout used for downloading filters updates 2019-04-01 11:30:26 +03:00
Ildar Kamalov
d6b83d4a63 Merge pull request #187 in DNS/adguard-dns from fix/655 to master
* commit '0c973334be33c77e509676d65394d99ead4b26e2':
  * client: if 0.0.0.0 is selected, then redirect to the current IP
2019-03-29 17:38:02 +03:00
Ildar Kamalov
0c973334be * client: if 0.0.0.0 is selected, then redirect to the current IP
Closes #655
2019-03-29 16:24:59 +03:00
Ildar Kamalov
23ac1726b7 Merge pull request #184 in DNS/adguard-dns from fix/660 to master
* commit '91af0cddcea531dfb7972f56371edf17ef763065':
  - client: header links overflow
  - client: fix settings page if current interface doesn't exist
  - client: request version after DNS status request
  - client: fixed Chrome translation popup issue with Portuguese
  - client: missed translation
2019-03-28 13:59:56 +03:00
Simon Zolin
1f1e26f67b + config: add "rlimit_nofile" setting - Maximum number of opened fd's per process 2019-03-28 13:56:12 +03:00
Ildar Kamalov
91af0cddce - client: header links overflow 2019-03-28 12:28:57 +03:00
Ildar Kamalov
6f014fa53d - client: fix settings page if current interface doesn't exist
Closes #664
2019-03-28 11:53:39 +03:00
Ildar Kamalov
6f2e852e09 - client: request version after DNS status request
Closes #660
2019-03-28 11:45:31 +03:00
Ildar Kamalov
fd82e7c26a - client: fixed Chrome translation popup issue with Portuguese
Closes #656
2019-03-28 11:44:10 +03:00
Ildar Kamalov
2bb5b24d4e - client: missed translation
Closes #652
2019-03-28 11:43:26 +03:00
Simon Zolin
92e70515ae Merge: filter: name was reset after an update
close #661

* commit '3367b9fb2a5e541766fa00a68b72121ac00efcff':
  - filter: name was reset after an update
2019-03-27 14:14:37 +03:00
Simon Zolin
51aec7cbbc Merge: Bump version to v0.94
* commit '395ddb7b3caf97c4c281badf0882dc3baf7f3b88':
  Bump version to v0.94
2019-03-27 11:36:21 +03:00
Simon Zolin
3367b9fb2a - filter: name was reset after an update
After filter is updated its name is reset to the value of Title from
 the filter's contents, which may be ""
2019-03-26 19:04:50 +03:00
Johann Richard
dfa39293a1 Update safesearch.go
* Beautify again with`gofmt -s`
2019-03-24 19:34:29 +01:00
Johann Richard
65364930f7 Update safesearch.go
* Beautify the addition
2019-03-24 19:26:40 +01:00
Johann Richard
53ea2d28cf Update safesearch.go
* Add SafeSearch for duckduckgo.com
2019-03-24 19:24:31 +01:00
54 changed files with 2751 additions and 3136 deletions

327
AGHTechDoc.md Normal file
View File

@@ -0,0 +1,327 @@
# AdGuard Home Technical Document
The document describes technical details and internal algorithms of AdGuard Home.
Contents:
* First startup
* Installation wizard
* "Get install settings" command
* "Check configuration" command
* Disable DNSStubListener
* "Apply configuration" command
* Enable DHCP server
* "Check DHCP" command
* "Enable DHCP" command
* Static IP check/set
## First startup
The first application startup is detected when there's no .yaml configuration file.
We check if the user is root, otherwise we fail with an error.
Web server is started up on port 3000 and automatically redirects requests to `/` to Installation wizard.
After Installation wizard steps are completed, we write configuration to a file and start normal operation.
## Installation wizard
This is the collection of UI screens that are shown to a user on first application startup.
The screens are:
1. Welcome
2. Set up network interface and listening ports for Web and DNS servers
3. Set up administrator username and password
4. Configuration complete
5. Done
Algorithm:
Screen 2:
* UI asks server for initial information and shows it
* User edits the default settings, clicks on "Next" button
* UI asks server to check new settings
* Server searches for the known issues
* UI shows information about the known issues and the means to fix them
* Server applies automatic fixes of the known issues on command from UI
Screen 3:
* UI asks server to apply the configuration
* Server restarts DNS server
### "Get install settings" command
Request:
GET /control/install/get_addresses
Response:
200 OK
{
"web_port":80,
"dns_port":53,
"interfaces":{
"enp2s0":{"name":"enp2s0","mtu":1500,"hardware_address":"","ip_addresses":["",""],"flags":"up|broadcast|multicast"},
"lo":{"name":"lo","mtu":65536,"hardware_address":"","ip_addresses":["127.0.0.1","::1"],"flags":"up|loopback"},
}
}
If `interfaces.flags` doesn't contain `up` flag, UI must show `(Down)` status next to its IP address in interfaces selector.
### "Check configuration" command
Request:
POST /control/install/check_config
{
"web":{"port":80,"ip":"192.168.11.33"},
"dns":{"port":53,"ip":"127.0.0.1","autofix":false},
}
Server should check whether a port is available only in case it itself isn't already listening on that port.
Server replies on success:
200 OK
{
"web":{"status":""},
"dns":{"status":""},
}
Server replies on error:
200 OK
{
"web":{"status":"ERROR MESSAGE"},
"dns":{"status":"ERROR MESSAGE", "can_autofix": true|false},
}
### Disable DNSStubListener
On Linux, if 53 port is not available, server performs several additional checks to determine if the issue can be fixed automatically.
#### Phase 1
Request:
POST /control/install/check_config
{
"dns":{"port":53,"ip":"127.0.0.1","autofix":false}
}
Check if DNSStubListener is enabled:
systemctl is-enabled systemd-resolved
Check if DNSStubListener is active:
grep -E '#?DNSStubListener=yes' /etc/systemd/resolved.conf
If the issue can be fixed automatically, server replies with `"can_autofix":true`
200 OK
{
"dns":{"status":"ERROR MESSAGE", "can_autofix":true},
}
In this case UI shows "Fix" button next to error message.
#### Phase 2
If user clicks on "Fix" button, UI sends request to perform an automatic fix
POST /control/install/check_config
{
"dns":{"port":53,"ip":"127.0.0.1","autofix":true},
}
Deactivate (save backup as `resolved.conf.orig`) and stop DNSStubListener:
sed -r -i.orig 's/#?DNSStubListener=yes/DNSStubListener=no/g' /etc/systemd/resolved.conf
systemctl reload-or-restart systemd-resolved
Server replies:
200 OK
{
"dns":{"status":""},
}
### "Apply configuration" command
Request:
POST /control/install/configure
{
"web":{"port":80,"ip":"192.168.11.33"},
"dns":{"port":53,"ip":"127.0.0.1"},
"username":"u",
"password":"p",
}
Server checks the parameters once again, restarts DNS server, replies:
200 OK
On error, server responds with code 400 or 500. In this case UI should show error message and reset to the beginning.
400 Bad Request
ERROR MESSAGE
## Enable DHCP server
Algorithm:
* UI shows DHCP configuration screen with "Enabled DHCP" button disabled, and "Check DHCP" button enabled
* User clicks on "Check DHCP"; UI sends request to server
* Server may fail to detect whether there is another DHCP server working in the network. In this case UI shows a warning.
* Server may detect that a dynamic IP configuration is used for this interface. In this case UI shows a warning.
* UI enables "Enable DHCP" button
* User clicks on "Enable DHCP"; UI sends request to server
* Server sets a static IP (if necessary), enables DHCP server, sends the status back to UI
* UI shows the status
### "Check DHCP" command
Request:
POST /control/dhcp/find_active_dhcp
vboxnet0
Response:
200 OK
{
"other_server": {
"found": "yes|no|error",
"error": "Error message", // set if found=error
},
"static_ip": {
"static": "yes|no|error",
"ip": "<Current dynamic IP address>", // set if static=no
}
}
If `other_server.found` is:
* `no`: everything is fine - there is no other DHCP server
* `yes`: we found another DHCP server. UI shows a warning.
* `error`: we failed to determine whether there's another DHCP server. `other_server.error` contains error details. UI shows a warning.
If `static_ip.static` is:
* `yes`: everything is fine - server uses static IP address.
* `no`: `static_ip.ip` contains the current dynamic IP address which we may set as static. In this case UI shows a warning:
Your system uses dynamic IP address configuration for interface <CURRENT INTERFACE NAME>. In order to use DHCP server a static IP address must be set. Your current IP address is <static_ip.ip>. We will automatically set this IP address as static if you press Enable DHCP button.
* `error`: this means that the server failed to check for a static IP. In this case UI shows a warning:
In order to use DHCP server a static IP address must be set. We failed to determine if this network interface is configured using static IP address. Please set a static IP address manually.
### "Enable DHCP" command
Request:
POST /control/dhcp/set_config
{
"enabled":true,
"interface_name":"vboxnet0",
"gateway_ip":"192.169.56.1",
"subnet_mask":"255.255.255.0",
"range_start":"192.169.56.3",
"range_end":"192.169.56.3",
"lease_duration":60,
"icmp_timeout_msec":0
}
Response:
200 OK
OK
### Static IP check/set
Before enabling DHCP server we have to make sure the network interface we use has a static IP configured.
#### Phase 1
On Debian systems DHCP is configured by `/etc/dhcpcd.conf`.
To detect if a static IP is used currently we search for line
interface eth0
and then look for line
static ip_address=...
If the interface already has a static IP, everything is set up, we don't have to change anything.
To get the current IP address along with netmask we execute
ip -oneline -family inet address show eth0
which will print:
2: eth0 inet 192.168.0.1/24 brd 192.168.0.255 scope global eth0\ valid_lft forever preferred_lft forever
To get the current gateway address:
ip route show dev enp2s0
which will print:
default via 192.168.0.1 proto dhcp metric 100
#### Phase 2
This method only works on Raspbian.
On Ubuntu DHCP for a network interface can't be disabled via `dhcpcd.conf`. This must be configured in `/etc/netplan/01-netcfg.yaml`.
Fedora doesn't use `dhcpcd.conf` configuration at all.
Step 1.
To set a static IP address we add these lines to `dhcpcd.conf`:
interface eth0
static ip_address=192.168.0.1/24
static routers=192.168.0.1
static domain_name_servers=192.168.0.1
* Don't set 'routers' if we couldn't find gateway IP
* Set 'domain_name_servers' equal to our IP
Step 2.
If we would set a different IP address, we'd need to replace the IP address for the current network configuration. But currently this step isn't necessary.
ip addr replace dev eth0 192.168.0.1/24

192
app.go
View File

@@ -1,19 +1,25 @@
package main
import (
"bufio"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"syscall"
"github.com/AdguardTeam/golibs/log"
"github.com/NYTimes/gziphandler"
"github.com/gobuffalo/packr"
)
@@ -25,6 +31,7 @@ var httpsServer struct {
cond *sync.Cond // reacts to config.TLS.Enabled, PortHTTPS, CertificateChain and PrivateKey
sync.Mutex // protects config.TLS
}
var pidFileName string // PID file name. Empty if no PID file was created.
const (
// Used in config to indicate that syslog or eventlog (win) should be used for logger output
@@ -42,14 +49,6 @@ func main() {
return
}
signalChannel := make(chan os.Signal)
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
go func() {
<-signalChannel
cleanup()
os.Exit(0)
}()
// run the protection
run(args)
}
@@ -79,6 +78,18 @@ func run(args options) {
}
config.firstRun = detectFirstRun()
if config.firstRun {
requireAdminRights()
}
signalChannel := make(chan os.Signal)
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
go func() {
<-signalChannel
cleanup()
cleanupAlways()
os.Exit(0)
}()
// Do the upgrade if necessary
err := upgradeConfig()
@@ -92,6 +103,11 @@ func run(args options) {
log.Fatal(err)
}
if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") &&
config.RlimitNoFile != 0 {
setRlimit(config.RlimitNoFile)
}
// override bind host/port from the console
if args.bindHost != "" {
config.BindHost = args.bindHost
@@ -124,6 +140,10 @@ func run(args options) {
}
}
if len(args.pidFile) != 0 && writePIDFile(args.pidFile) {
pidFileName = args.pidFile
}
// Update filters we've just loaded right away, don't wait for periodic update timer
go func() {
refreshFiltersIfNecessary(false)
@@ -133,8 +153,9 @@ func run(args options) {
// 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(http.FileServer(box))))
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
@@ -147,53 +168,7 @@ func run(args options) {
httpsServer.cond = sync.NewCond(&httpsServer.Mutex)
// for https, we have a separate goroutine loop
go func() {
for { // this is an endless loop
httpsServer.cond.L.Lock()
// this mechanism doesn't let us through until all conditions are ment
for config.TLS.Enabled == false || config.TLS.PortHTTPS == 0 || config.TLS.PrivateKey == "" || config.TLS.CertificateChain == "" { // sleep until necessary data is supplied
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(config.TLS.CertificateChain, config.TLS.PrivateKey, config.TLS.ServerName)
if !data.ValidPair {
log.Fatal(data.WarningValidation)
os.Exit(1)
}
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.CertificateChain))
copy(certchain, []byte(config.TLS.CertificateChain))
privatekey := make([]byte, len(config.TLS.PrivateKey))
copy(privatekey, []byte(config.TLS.PrivateKey))
cert, err := tls.X509KeyPair(certchain, privatekey)
if err != nil {
log.Fatal(err)
os.Exit(1)
}
httpsServer.cond.L.Unlock()
// prepare HTTPS server
httpsServer.server = &http.Server{
Addr: address,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
},
}
printHTTPAddresses("https")
err = httpsServer.server.ListenAndServeTLS("", "")
if err != http.ErrServerClosed {
log.Fatal(err)
os.Exit(1)
}
}
}()
go httpServerLoop()
// this loop is used as an ability to change listening host and/or port
for {
@@ -206,13 +181,107 @@ func run(args options) {
}
err := httpServer.ListenAndServe()
if err != http.ErrServerClosed {
cleanupAlways()
log.Fatal(err)
os.Exit(1)
}
// We use ErrServerClosed as a sign that we need to rebind on new address, so go back to the start of the loop
}
}
func httpServerLoop() {
for {
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 ||
config.TLS.PrivateKey == "" ||
config.TLS.CertificateChain == "" { // sleep until necessary data is supplied
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(config.TLS.CertificateChain, config.TLS.PrivateKey, 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.CertificateChain))
copy(certchain, []byte(config.TLS.CertificateChain))
privatekey := make([]byte, len(config.TLS.PrivateKey))
copy(privatekey, []byte(config.TLS.PrivateKey))
cert, err := tls.X509KeyPair(certchain, privatekey)
if err != nil {
cleanupAlways()
log.Fatal(err)
}
httpsServer.cond.L.Unlock()
// prepare HTTPS server
httpsServer.server = &http.Server{
Addr: address,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
},
}
printHTTPAddresses("https")
err = httpsServer.server.ListenAndServeTLS("", "")
if err != http.ErrServerClosed {
cleanupAlways()
log.Fatal(err)
}
}
}
// Check if the current user has root (administrator) rights
// and if not, ask and try to run as root
func requireAdminRights() {
admin, _ := haveAdminRights()
if admin {
return
}
if runtime.GOOS == "windows" {
log.Fatal("This is the first launch of AdGuard Home. You must run it as Administrator.")
} else {
log.Error("This is the first launch of AdGuard Home. You must run it as root.")
_, _ = io.WriteString(os.Stdout, "Do you want to start AdGuard Home as root user? [y/n] ")
stdin := bufio.NewReader(os.Stdin)
buf, _ := stdin.ReadString('\n')
buf = strings.TrimSpace(buf)
if buf != "y" {
os.Exit(1)
}
cmd := exec.Command("sudo", os.Args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
_ = cmd.Run()
os.Exit(1)
}
}
// Write PID to a file
func writePIDFile(fn string) bool {
data := fmt.Sprintf("%d", os.Getpid())
err := ioutil.WriteFile(fn, []byte(data), 0644)
if err != nil {
log.Error("Couldn't write PID to file %s: %v", fn, err)
return false
}
return true
}
// initWorkingDir initializes the ourWorkingDir
// if no command-line arguments specified, we use the directory where our binary file is located
func initWorkingDir(args options) {
@@ -298,6 +367,13 @@ func cleanup() {
}
}
// This function is called before application exits
func cleanupAlways() {
if len(pidFileName) != 0 {
os.Remove(pidFileName)
}
}
// command-line arguments
type options struct {
verbose bool // is verbose logging enabled
@@ -306,6 +382,7 @@ type options struct {
bindHost string // host address to bind HTTP server on
bindPort int // port to serve HTTP pages on
logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
pidFile string // File name to save PID to
// service control action (see service.ControlAction array + "status" command)
serviceControlAction string
@@ -342,6 +419,7 @@ func loadOptions() options {
{"logfile", "l", "path to the log file. If empty, writes to stdout, if 'syslog' -- system log", func(value string) {
o.logFile = value
}, nil},
{"pidfile", "", "File name to save PID to", func(value string) { o.pidFile = value }, nil},
{"verbose", "v", "enable verbose output", nil, func() { o.verbose = true }},
{"help", "", "print this help", nil, func() {
printHelp()

3019
client/package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

2
client/package.json vendored
View File

@@ -48,7 +48,7 @@
"clean-webpack-plugin": "^0.1.19",
"compression-webpack-plugin": "^1.1.11",
"copy-webpack-plugin": "^4.6.0",
"css-loader": "^0.28.11",
"css-loader": "^2.1.1",
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-config-react-app": "^2.1.0",

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<meta name="google" content="notranslate">
<link rel="shortcut icon" href="favicon.ico">
<title>AdGuard Home</title>
</head>

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<meta name="google" content="notranslate">
<link rel="shortcut icon" href="favicon.ico">
<title>Setup AdGuard Home</title>
</head>

View File

@@ -1,4 +1,5 @@
{
"example_upstream_reserved": "you can specify DNS upstream <0>for a specific domain(s)<\/0>",
"upstream_parallel": "Use parallel queries to speed up resolving by simultaneously querying all upstream servers",
"bootstrap_dns": "Bootstrap DNS servers",
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH\/DoT resolvers you specify as upstreams.",
@@ -11,8 +12,8 @@
"dhcp_description": "If your router does not provide DHCP settings, you can use AdGuard's own built-in DHCP server.",
"dhcp_enable": "Enable DHCP server",
"dhcp_disable": "Disable DHCP server",
"dhcp_not_found": "No active DHCP servers found on the network. It is safe to enable the built-in DHCP server.",
"dhcp_found": "Some active DHCP servers found on the network. It is not safe to enable the built-in DHCP server.",
"dhcp_not_found": "It is safe to enable the built-in DHCP server - we didn't find any active DHCP servers on the network. However, we encourage you to re-check it manually as our automatic test currently doesn't give 100% guarantee.",
"dhcp_found": "An active DHCP server is found on the network. It is not safe to enable the built-in DHCP server.",
"dhcp_leases": "DHCP leases",
"dhcp_leases_not_found": "No DHCP leases found",
"dhcp_config_saved": "Saved DHCP server config",
@@ -31,7 +32,11 @@
"dhcp_ip_addresses": "IP addresses",
"dhcp_table_hostname": "Hostname",
"dhcp_table_expires": "Expires",
"dhcp_warning": "If you want to enable the built-in DHCP server, make sure that there is no other active DHCP server. Otherwise, it can break the internet for connected devices!",
"dhcp_warning": "If you want to enable DHCP server anyway, make sure that there is no other active DHCP server in your network. Otherwise, it can break the Internet for connected devices!",
"dhcp_error": "We could not determine whether there is another DHCP server in the network.",
"dhcp_static_ip_error": "In order to use DHCP server a static IP address must be set. We failed to determine if this network interface is configured using static IP address. Please set a static IP address manually.",
"dhcp_dynamic_ip_found": "Your system uses dynamic IP address configuration for interface <0>{{interfaceName}}</0>. In order to use DHCP server a static IP address must be set. Your current IP address is <0>{{ipAddress}}</0>. We will automatically set this IP address as static if you press Enable DHCP button.",
"error_details": "Error details",
"back": "Back",
"dashboard": "Dashboard",
"settings": "Settings",
@@ -45,6 +50,7 @@
"copyright": "Copyright",
"homepage": "Homepage",
"report_an_issue": "Report an issue",
"privacy_policy": "Privacy policy",
"enable_protection": "Enable protection",
"enabled_protection": "Enabled protection",
"disable_protection": "Disable protection",
@@ -77,7 +83,7 @@
"use_adguard_parental": "Use AdGuard parental control web service",
"use_adguard_parental_hint": "AdGuard Home will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.",
"enforce_safe_search": "Enforce safe search",
"enforce_save_search_hint": "AdGuard Home can enforce safe search in the following search engines: Google, Youtube, Bing, and Yandex.",
"enforce_save_search_hint": "AdGuard Home can enforce safe search in the following search engines: Google, Youtube, Bing, DuckDuckGo and Yandex.",
"no_servers_specified": "No servers specified",
"no_settings": "No settings",
"general_settings": "General settings",
@@ -121,11 +127,10 @@
"example_comment_hash": "# Also a comment",
"example_regex_meaning": "block access to the domains matching the specified regular expression",
"example_upstream_regular": "regular DNS (over UDP)",
"example_upstream_dot": "encrypted <0>DNS-over-TLS</0>",
"example_upstream_doh": "encrypted <0>DNS-over-HTTPS</0>",
"example_upstream_sdns": "you can use <0>DNS Stamps</0> for <1>DNSCrypt</1> or <2>DNS-over-HTTPS</2> resolvers",
"example_upstream_dot": "encrypted <0>DNS-over-TLS<\/0>",
"example_upstream_doh": "encrypted <0>DNS-over-HTTPS<\/0>",
"example_upstream_sdns": "you can use <0>DNS Stamps<\/0> for <1>DNSCrypt<\/1> or <2>DNS-over-HTTPS<\/2> resolvers",
"example_upstream_tcp": "regular DNS (over TCP)",
"example_upstream_reserved": "you can specify DNS upstream <0>for a specific domain(s)</0>",
"all_filters_up_to_date_toast": "All filters are already up-to-date",
"updated_upstream_dns_toast": "Updated the upstream DNS servers",
"dns_test_ok_toast": "Specified DNS servers are working correctly",
@@ -252,5 +257,7 @@
"reset_settings": "Reset settings",
"update_announcement": "AdGuard Home {{version}} is now available! <0>Click here<\/0> for more info.",
"setup_guide": "Setup guide",
"dns_addresses": "DNS addresses"
"dns_addresses": "DNS addresses",
"down": "Down",
"fix": "Fix"
}

View File

@@ -1,132 +1,139 @@
{
"example_upstream_reserved": "puede especificar el DNS de subida <0>para un dominio espec\u00edfico<\/0>",
"upstream_parallel": "Usar consultas paralelas para acelerar la resoluci\u00f3n al consultar simult\u00e1neamente a todos los servidores de subida",
"bootstrap_dns": "Servidores DNS de arranque",
"bootstrap_dns_desc": "Los servidores DNS de arranque se utilizan para resolver las direcciones IP de los resolutores DoH\/DoT que especifique como DNS de subida.",
"url_added_successfully": "URL a\u00f1adida correctamente",
"check_dhcp_servers": "Compruebe si hay servidores DHCP",
"save_config": "Guardar config",
"save_config": "Guardar configuraci\u00f3n",
"enabled_dhcp": "Servidor DHCP habilitado",
"disabled_dhcp": "Servidor DHCP deshabilitado",
"dhcp_title": "Servidor DHCP (experimental)",
"dhcp_description": "Si su enrutador no proporciona la configuraci\u00f3n DHCP, puede utilizar el propio servidor DHCP incorporado de AdGuard.",
"dhcp_description": "Si su router no proporciona la configuraci\u00f3n DHCP, puede utilizar el propio servidor DHCP incorporado de AdGuard.",
"dhcp_enable": "Habilitar servidor DHCP",
"dhcp_disable": "Deshabilitar el servidor DHCP",
"dhcp_not_found": "No se han encontrado servidores DHCP activos en la red. Es seguro habilitar el servidor DHCP incorporado.",
"dhcp_found": "Se encontraron servidores DHCP activos encontrados en la red. No es seguro habilitar el servidor DHCP incorporado.",
"dhcp_leases": "concesi\u00f3nes DHCP",
"dhcp_found": "Algunos servidores DHCP activos se encuentran en la red. No es seguro habilitar el servidor DHCP incorporado.",
"dhcp_leases": "Concesi\u00f3nes DHCP",
"dhcp_leases_not_found": "No se encontraron concesi\u00f3nes DHCP",
"dhcp_config_saved": "Configuraci\u00f3n del servidor DHCP guardada",
"form_error_required": "Campo obligatorio",
"form_error_ip_format": "Formato IPv4 no v\u00e1lido",
"form_error_positive": "Debe ser mayor que 0",
"dhcp_form_gateway_input": "IP de acceso",
"dhcp_form_gateway_input": "IP de puerta de enlace",
"dhcp_form_subnet_input": "M\u00e1scara de subred",
"dhcp_form_range_title": "Rango de direcciones IP",
"dhcp_form_range_start": "Inicio de rango",
"dhcp_form_range_end": "Final de rango",
"dhcp_form_lease_title": "Tiempo de concesi\u00f3n DHCP (en segundos)",
"dhcp_form_lease_input": "duraci\u00f3n de la concesi\u00f3n",
"dhcp_form_lease_input": "Duraci\u00f3n de la concesi\u00f3n",
"dhcp_interface_select": "Seleccione la interfaz DHCP",
"dhcp_hardware_address": "Direcci\u00f3n de hardware",
"dhcp_hardware_address": "Direcci\u00f3n MAC",
"dhcp_ip_addresses": "Direcciones IP",
"dhcp_table_hostname": "Hostname",
"dhcp_table_hostname": "Nombre del host",
"dhcp_table_expires": "Expira",
"dhcp_warning": "Si desea habilitar el servidor DHCP incorporado, aseg\u00farese de que no hay otro servidor DHCP activo. \u00a1De lo contrario, puede dejar sin Internet a los dispositivos conectados!",
"back": "Atr\u00e1s",
"dashboard": "Tablero de rendimiento",
"settings": "Ajustes",
"dashboard": "Panel de control",
"settings": "Configuraci\u00f3n",
"filters": "Filtros",
"query_log": "Log de consulta",
"faq": "FAQ",
"query_log": "Registro de consultas",
"faq": "Preguntas frecuentes",
"version": "versi\u00f3n",
"address": "direcci\u00f3n",
"on": "Activado",
"off": "Desactivado",
"copyright": "Derechos de autor",
"copyright": "Copyright",
"homepage": "P\u00e1gina de inicio",
"report_an_issue": "Reportar un error",
"enable_protection": "Activar la protecci\u00f3n",
"enabled_protection": "Protecci\u00f3n activada",
"disable_protection": "Desactivar protecci\u00f3n",
"disabled_protection": "Protecci\u00f3n desactivada",
"enable_protection": "Habilitar protecci\u00f3n",
"enabled_protection": "Protecci\u00f3n habilitada",
"disable_protection": "Deshabilitar protecci\u00f3n",
"disabled_protection": "Protecci\u00f3n deshabilitada",
"refresh_statics": "Restablecer estad\u00edsticas",
"dns_query": "Consultas DNS",
"blocked_by": "Bloqueado por filtros",
"stats_malware_phishing": "Malware\/phishing bloqueado",
"stats_adult": "Contenido para adultos bloqueado",
"stats_query_domain": "Dominios m\u00e1s solicitados",
"stats_adult": "Sitios para adultos bloqueado",
"stats_query_domain": "Dominios m\u00e1s consultados",
"for_last_24_hours": "en las \u00faltimas 24 horas",
"no_domains_found": "Dominios no encontrados",
"requests_count": "N\u00famero de solicitudes",
"top_blocked_domains": "Dominios m\u00e1s bloqueados",
"top_clients": "Clientes m\u00e1s populares",
"top_clients": "Clientes m\u00e1s frecuentes",
"no_clients_found": "No hay clientes",
"general_statistics": "Estad\u00edsticas generales",
"number_of_dns_query_24_hours": "Una serie de consultas DNS procesadas durante las \u00faltimas 24 horas",
"number_of_dns_query_blocked_24_hours": "El n\u00famero de solicitudes de DNS bloqueadas por los filtros de publicidad y listas de bloqueo de hosts",
"number_of_dns_query_blocked_24_hours_by_sec": "Un n\u00famero de solicitudes de DNS bloqueadas por el m\u00f3dulo de navegaci\u00f3n segura de AdGuard",
"number_of_dns_query_blocked_24_hours_adult": "Un n\u00famero de sitios para adultos bloqueados",
"enforced_save_search": "B\u00fasqueda segura forzada",
"number_of_dns_query_to_safe_search": "Una serie de solicitudes de DNS a los motores de b\u00fasqueda para los que se aplic\u00f3 la B\u00fasqueda Segura",
"number_of_dns_query_24_hours": "N\u00famero de consultas DNS procesadas durante las \u00faltimas 24 horas",
"number_of_dns_query_blocked_24_hours": "N\u00famero de peticiones DNS bloqueadas por los filtros de publicidad y listas de bloqueo de hosts",
"number_of_dns_query_blocked_24_hours_by_sec": "N\u00famero de peticiones DNS bloqueadas por el m\u00f3dulo de navegaci\u00f3n segura de AdGuard",
"number_of_dns_query_blocked_24_hours_adult": "N\u00famero de sitios para adultos bloqueado",
"enforced_save_search": "B\u00fasquedas seguras forzadas",
"number_of_dns_query_to_safe_search": "N\u00famero de peticiones DNS a los motores de b\u00fasqueda para los que se aplic\u00f3 la b\u00fasqueda segura forzada",
"average_processing_time": "Tiempo promedio de procesamiento",
"average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una solicitud DNS",
"average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una petici\u00f3n DNS",
"block_domain_use_filters_and_hosts": "Bloquear dominios usando filtros y archivos hosts",
"filters_block_toggle_hint": "Puede configurar las reglas de bloqueo en los ajustes <a href='#filters'>Filtros<\/a>.",
"use_adguard_browsing_sec": "Usar el servicio web de Seguridad de navegaci\u00f3n de AdGuard",
"use_adguard_browsing_sec_hint": "AdGuard Home comprobar\u00e1 si el dominio est\u00e1 en la lista negra del servicio web de seguridad de navegaci\u00f3n. Utilizar\u00e1 una API de b\u00fasqueda amigable con la privacidad para realizar la comprobaci\u00f3n: s\u00f3lo se env\u00eda al servidor un prefijo corto del hash del nombre de dominio SHA256.",
"use_adguard_parental": "Usar Control Parental de AdGuard ",
"filters_block_toggle_hint": "Puede configurar las reglas de bloqueo en la configuraci\u00f3n de <a href='#filters'>filtros<\/a>.",
"use_adguard_browsing_sec": "Usar el servicio web de seguridad de navegaci\u00f3n de AdGuard",
"use_adguard_browsing_sec_hint": "AdGuard Home comprobar\u00e1 si el dominio est\u00e1 en la lista negra del servicio web de seguridad de navegaci\u00f3n. Utilizar\u00e1 una API de b\u00fasqueda amigable con la privacidad para realizar la comprobaci\u00f3n: solo se env\u00eda al servidor un prefijo corto de hash del nombre de dominio SHA256.",
"use_adguard_parental": "Usar el control parental de AdGuard",
"use_adguard_parental_hint": "AdGuard Home comprobar\u00e1 si el dominio contiene materiales para adultos. Utiliza la misma API amigable con la privacidad que el servicio web de seguridad de navegaci\u00f3n.",
"enforce_safe_search": "Forzar b\u00fasqueda segura",
"enforce_save_search_hint": "AdGuard Home puede forzar la b\u00fasqueda segura en los siguientes motores de b\u00fasqueda: Google, Youtube, Bing y Yandex.",
"enforce_save_search_hint": "AdGuard Home puede forzar la b\u00fasqueda segura en los siguientes motores de b\u00fasqueda: Google, YouTube, Bing y Yandex.",
"no_servers_specified": "No hay servidores especificados",
"no_settings": "No hay ajustes",
"general_settings": "Ajustes generales",
"upstream_dns": "Servidores DNS upstream",
"upstream_dns_hint": "Si mantiene este campo vac\u00edo, AdGuard Home utilizar\u00e1 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> como upstream. Utilice el prefijo tls:\/\/ para DNS sobre servidores TLS.",
"test_upstream_btn": "Probar upstream",
"no_settings": "Sin configuraci\u00f3n",
"general_settings": "Configuraci\u00f3n general",
"upstream_dns": "Servidores DNS de subida",
"upstream_dns_hint": "Si mantiene este campo vac\u00edo, AdGuard Home utilizar\u00e1 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> como DNS de subida. Utilice el prefijo tls:\/\/ para los servidores DNS mediante TLS.",
"test_upstream_btn": "Probar DNS de subida",
"apply_btn": "Aplicar",
"disabled_filtering_toast": "Desactivar filtrado",
"enabled_filtering_toast": "Filtrado activado",
"disabled_safe_browsing_toast": "Navegaci\u00f3n segura desactivada",
"enabled_safe_browsing_toast": "Navegaci\u00f3n segura activada",
"disabled_parental_toast": "Control parental desactivado",
"enabled_parental_toast": "Control parental activado",
"disabled_safe_search_toast": "B\u00fasqueda segura desactivada",
"enabled_save_search_toast": "B\u00fasqueda segura activada",
"enabled_table_header": "Activado",
"disabled_filtering_toast": "Filtrado deshabilitado",
"enabled_filtering_toast": "Filtrado habilitado",
"disabled_safe_browsing_toast": "B\u00fasqueda segura deshabilitada",
"enabled_safe_browsing_toast": "B\u00fasqueda segura habilitada",
"disabled_parental_toast": "Control parental deshabilitado",
"enabled_parental_toast": "Control parental habilitado",
"disabled_safe_search_toast": "B\u00fasqueda segura deshabilitada",
"enabled_save_search_toast": "B\u00fasqueda segura habilitada",
"enabled_table_header": "Habilitado",
"name_table_header": "Nombre",
"filter_url_table_header": "Filtro URL",
"filter_url_table_header": "URL del filtro",
"rules_count_table_header": "N\u00famero de reglas",
"last_time_updated_table_header": "\u00daltima actualizaci\u00f3n",
"actions_table_header": "Acciones",
"delete_table_action": "Eliminar",
"filters_and_hosts": "Filtros y listas de bloqueo de hosts",
"filters_and_hosts_hint": "AdGuard Home entiende reglas b\u00e1sicas de bloqueo y la sintaxis de los archivos de hosts.",
"no_filters_added": "No hay filtros agregados",
"add_filter_btn": "Agregar filtro",
"no_filters_added": "No hay filtros a\u00f1adidos",
"add_filter_btn": "A\u00f1adir filtro",
"cancel_btn": "Cancelar",
"enter_name_hint": "Ingresar nombre",
"enter_url_hint": "Ingresar URL",
"check_updates_btn": "Revisar si hay actualizaciones",
"enter_name_hint": "Ingrese el nombre",
"enter_url_hint": "Ingrese la URL",
"check_updates_btn": "Buscar actualizaciones",
"new_filter_btn": "Nueva suscripci\u00f3n de filtro",
"enter_valid_filter_url": "Ingrese una URL v\u00e1lida para suscribirse o un archivo de hosts.",
"custom_filter_rules": "Personalizar reglas del filtrado",
"custom_filter_rules_hint": "Introduzca una regla en una l\u00ednea. Puede utilizar reglas de bloqueo de anuncios o sintaxis de archivos de hosts.",
"custom_filter_rules_hint": "Ingrese una regla en una l\u00ednea. Puede utilizar reglas de bloqueo de anuncios o la sintaxis de archivos de hosts.",
"examples_title": "Ejemplos",
"example_meaning_filter_block": "bloquear acceso al dominio ejemplo.org\ny a todos sus subdominios",
"example_meaning_filter_whitelist": "desbloquear el acceso al dominio ejemplo.org y a sus subdominios",
"example_meaning_filter_block": "bloquea el acceso al dominio ejemplo.org\ny a todos sus subdominios",
"example_meaning_filter_whitelist": "desbloquea el acceso al dominio ejemplo.org y a todos sus subdominios",
"example_meaning_host_block": "AdGuard Home regresar\u00e1 la direcci\u00f3n 127.0.0.1 para el dominio ejemplo.org (pero no para sus subdominios).",
"example_comment": "! Aqu\u00ed va un comentario",
"example_comment_meaning": "solo un comentario",
"example_comment_hash": "# Tambi\u00e9n un comentario",
"example_upstream_regular": "DNS regular (a trav\u00e9s de UDP)",
"example_upstream_dot": "encriptado <0>DNS-a-trav\u00e9s-de-TLS<\/0>",
"example_upstream_doh": "encriptado <0>DNS-a-trav\u00e9s-de-TLS<\/0>",
"example_upstream_sdns": "puedes usar <0>DNS Stamps<\/0> para <1>DNSCrypt<\/1> o <2>DNS-over-HTTPS<\/2> resolutores",
"example_upstream_tcp": "DNS regular (a trav\u00e9s de TCP)",
"all_filters_up_to_date_toast": "Todos los filtros son actualizados",
"updated_upstream_dns_toast": "Servidores DNS upstream actualizados",
"dns_test_ok_toast": "Servidores DNS especificados funcionan correctamente",
"example_regex_meaning": "bloquear el acceso a los dominios que coincidan con la expresi\u00f3n regular especificada",
"example_upstream_regular": "DNS regular (mediante UDP)",
"example_upstream_dot": "cifrado <0>DNS mediante TLS<\/0>",
"example_upstream_doh": "cifrado <0>DNS mediante HTTPS<\/0>",
"example_upstream_sdns": "puedes usar <0>DNS Stamps<\/0> para <1>DNSCrypt<\/1> o resolutores <2>DNS mediante HTTPS<\/2>",
"example_upstream_tcp": "DNS regular (mediante TCP)",
"all_filters_up_to_date_toast": "Todos los filtros ya est\u00e1n actualizados",
"updated_upstream_dns_toast": "Servidores DNS de subida actualizados",
"dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
"dns_test_not_ok_toast": "Servidor \"{{key}}\": no puede ser usado, por favor, revise si lo ha escrito correctamente",
"unblock_btn": "Desbloquear",
"block_btn": "Bloquear",
"time_table_header": "Tiempo",
"domain_name_table_header": "Nombre de dominio",
"time_table_header": "Hora",
"domain_name_table_header": "Nombre del dominio",
"type_table_header": "Tipo",
"response_table_header": "Respuesta",
"client_table_header": "Cliente",
@@ -134,26 +141,116 @@
"show_all_filter_type": "Mostrar todo",
"show_filtered_type": "Mostrar filtrados",
"no_logs_found": "No se han encontrado registros",
"disabled_log_btn": "Desactivar registro",
"download_log_file_btn": "Descargar el archivo de registro",
"refresh_btn": "Refrescar",
"enabled_log_btn": "Activar registro",
"last_dns_queries": "\u00daltimas 500 solicitudes de DNS",
"previous_btn": "Anterior",
"disabled_log_btn": "Deshabilitar registro",
"download_log_file_btn": "Descargar archivo de registro",
"refresh_btn": "Actualizar",
"enabled_log_btn": "Habilitar registro",
"last_dns_queries": "\u00daltimas 5000 consultas DNS",
"previous_btn": "Atr\u00e1s",
"next_btn": "Siguiente",
"loading_table_status": "Cargando...",
"page_table_footer_text": "P\u00e1gina",
"of_table_footer_text": "de",
"rows_table_footer_text": "filas",
"updated_custom_filtering_toast": "Actualizadas las reglas de filtrado personalizadas",
"rule_removed_from_custom_filtering_toast": "Regla eliminada de las reglas de filtrado personalizadas",
"rule_added_to_custom_filtering_toast": "Regla a\u00f1adida a las reglas de filtrado personalizadas",
"query_log_disabled_toast": "Log de consulta desactivado",
"query_log_enabled_toast": "Log de consulta activado",
"updated_custom_filtering_toast": "Reglas de filtrado personalizado actualizadas",
"rule_removed_from_custom_filtering_toast": "Regla eliminada de las reglas de filtrado personalizado",
"rule_added_to_custom_filtering_toast": "Regla a\u00f1adida a las reglas de filtrado personalizado",
"query_log_disabled_toast": "Registro de consultas deshabilitado",
"query_log_enabled_toast": "Registro de consultas habilitado",
"source_label": "Fuente",
"found_in_known_domain_db": "Encontrado en la base de datos de dominios conocidos.",
"category_label": "Categor\u00eda",
"rule_label": "Regla",
"filter_label": "Filtro",
"unknown_filter": "Filtro desconocido {{filterId}}"
"unknown_filter": "Filtro desconocido {{filterId}}",
"install_welcome_title": "\u00a1Bienvenido a AdGuard Home!",
"install_welcome_desc": "AdGuard Home es un servidor DNS de bloqueo de anuncios y rastreadores en toda la red. Su prop\u00f3sito es permitirle controlar toda su red y todos sus dispositivos, y no requiere el uso de un programa del lado del cliente.",
"install_settings_title": "Interfaz web de administraci\u00f3n",
"install_settings_listen": "Interfaz de escucha",
"install_settings_port": "Puerto",
"install_settings_interface_link": "Su interfaz web de administraci\u00f3n de AdGuard Home estar\u00e1 disponible en las siguientes direcciones:",
"form_error_port": "Ingrese un valor de puerto v\u00e1lido",
"install_settings_dns": "Servidor DNS",
"install_settings_dns_desc": "Deber\u00e1 configurar sus dispositivos o router para usar el servidor DNS en las siguientes direcciones:",
"install_settings_all_interfaces": "Todas las interfaces",
"install_auth_title": "Autenticaci\u00f3n",
"install_auth_desc": "Se recomienda encarecidamente configurar la autenticaci\u00f3n por contrase\u00f1a para la interfaz web del administraci\u00f3n de AdGuard Home. Incluso si solo es accesible en su red local, es importante que est\u00e9 protegido contra el acceso no autorizado.",
"install_auth_username": "Usuario",
"install_auth_password": "Contrase\u00f1a",
"install_auth_confirm": "Confirmar contrase\u00f1a",
"install_auth_username_enter": "Ingrese su nombre de usuario",
"install_auth_password_enter": "Ingrese su contrase\u00f1a",
"install_step": "Paso",
"install_devices_title": "Configure sus dispositivos",
"install_devices_desc": "Para que AdGuard Home comience a funcionar, necesita configurar sus dispositivos para usarlo.",
"install_submit_title": "\u00a1Felicitaciones!",
"install_submit_desc": "El proceso de configuraci\u00f3n ha finalizado y est\u00e1 listo para comenzar a usar AdGuard Home.",
"install_devices_router": "Router",
"install_devices_router_desc": "Esta configuraci\u00f3n cubrir\u00e1 autom\u00e1ticamente todos los dispositivos conectados a su router dom\u00e9stico y no necesitar\u00e1 configurar cada uno de ellos manualmente.",
"install_devices_address": "El servidor DNS de AdGuard Home est\u00e1 escuchando las siguientes direcciones",
"install_devices_router_list_1": "Abra las preferencias de su router. Por lo general, puede acceder a \u00e9l desde su navegador a trav\u00e9s de una URL (como http:\/\/192.168.0.1\/ o http:\/\/192.168.1.1\/). Se le puede pedir que ingrese la contrase\u00f1a. Si no lo recuerda, a menudo puede restablecer la contrase\u00f1a presionando un bot\u00f3n en el router. Algunos routers requieren una aplicaci\u00f3n espec\u00edfica, que en ese caso ya deber\u00eda estar instalada en su computadora\/tel\u00e9fono.",
"install_devices_router_list_2": "Busque la configuraci\u00f3n de DHCP\/DNS. Busque las letras DNS junto a un campo que permita ingresar dos o tres grupos de n\u00fameros, cada uno dividido en cuatro grupos de uno a tres d\u00edgitos.",
"install_devices_router_list_3": "Ingrese las direcciones de su servidor AdGuard Home all\u00ed.",
"install_devices_windows_list_1": "Abra el Panel de control a trav\u00e9s del men\u00fa Inicio o en el buscador de Windows.",
"install_devices_windows_list_2": "Vaya a la categor\u00eda Red e Internet, luego al Centro de redes y recursos compartidos.",
"install_devices_windows_list_3": "En el lado izquierdo de la pantalla, busque Cambiar la configuraci\u00f3n del adaptador y haga clic en \u00e9l.",
"install_devices_windows_list_4": "Seleccione su conexi\u00f3n activa, luego haga clic derecho sobre ella y elija Propiedades.",
"install_devices_windows_list_5": "Busque el Protocolo de Internet versi\u00f3n 4 (TCP\/IP) en la lista, selecci\u00f3nelo y luego haga clic en Propiedades nuevamente.",
"install_devices_windows_list_6": "Elija Usar las siguientes direcciones de servidor DNS e ingrese las direcciones de su servidor AdGuard Home.",
"install_devices_macos_list_1": "Haga clic en el icono de Apple y vaya a Preferencias del sistema.",
"install_devices_macos_list_2": "Haga clic en Red.",
"install_devices_macos_list_3": "Seleccione la primera conexi\u00f3n de la lista y haga clic en Avanzado.",
"install_devices_macos_list_4": "Seleccione la pesta\u00f1a DNS e ingrese las direcciones de su servidor AdGuard Home.",
"install_devices_android_list_1": "En la pantalla de inicio del men\u00fa Android, pulse en Configuraci\u00f3n.",
"install_devices_android_list_2": "Pulse Wi-Fi en el men\u00fa. Aparecer\u00e1 la pantalla que lista todas las redes disponibles (es imposible configurar DNS personalizados para la conexi\u00f3n m\u00f3vil).",
"install_devices_android_list_3": "Mantenga presionada la red a la que est\u00e1 conectado y pulse Modificar red.",
"install_devices_android_list_4": "En algunos dispositivos, es posible que deba marcar la casilla Avanzado para ver m\u00e1s configuraciones. Para ajustar la configuraci\u00f3n DNS de su Android, deber\u00e1 cambiar la configuraci\u00f3n de IP de DHCP a Est\u00e1tica.",
"install_devices_android_list_5": "Cambie los valores de DNS 1 y DNS 2 a las direcciones de su servidor AdGuard Home.",
"install_devices_ios_list_1": "En la pantalla de inicio, pulse en Configuraci\u00f3n.",
"install_devices_ios_list_2": "Elija Wi-Fi en el men\u00fa de la izquierda (es imposible configurar DNS para redes m\u00f3viles).",
"install_devices_ios_list_3": "Pulse sobre el nombre de la red activa en ese momento.",
"install_devices_ios_list_4": "En ese campo DNS ingrese las direcciones de su servidor AdGuard Home.",
"get_started": "Comenzar",
"next": "Siguiente",
"open_dashboard": "Abrir panel de control",
"install_saved": "Guardado correctamente",
"encryption_title": "Cifrado",
"encryption_desc": "Soporte para cifrado (HTTPS\/TLS) tanto para DNS como para la interfaz web de administraci\u00f3n",
"encryption_config_saved": "Configuraci\u00f3n de cifrado guardado",
"encryption_server": "Nombre del servidor",
"encryption_server_enter": "Ingrese su nombre de dominio",
"encryption_server_desc": "Para utilizar HTTPS, debe ingresar el nombre del servidor que coincida con su certificado SSL.",
"encryption_redirect": "Redireccionar a HTTPS autom\u00e1ticamente",
"encryption_redirect_desc": "Si est\u00e1 marcado, AdGuard Home redireccionar\u00e1 autom\u00e1ticamente de HTTP a las direcciones HTTPS.",
"encryption_https": "Puerto HTTPS",
"encryption_https_desc": "Si el puerto HTTPS est\u00e1 configurado, la interfaz de administraci\u00f3n de AdGuard Home ser\u00e1 accesible a trav\u00e9s de HTTPS, y tambi\u00e9n proporcionar\u00e1 DNS mediante HTTPS en la ubicaci\u00f3n '\/dns-query'.",
"encryption_dot": "Puerto para DNS mediante TLS",
"encryption_dot_desc": "Si este puerto est\u00e1 configurado, AdGuard Home ejecutar\u00e1 un servidor DNS mediante TLS en este puerto.",
"encryption_certificates": "Certificados",
"encryption_certificates_desc": "Para utilizar el cifrado, debe proporcionar una cadena de certificados SSL v\u00e1lida para su dominio. Puede obtener un certificado gratuito en <0>{{link}}<\/0> o puede comprarlo en una de las autoridades de certificaci\u00f3n de confianza.",
"encryption_certificates_input": "Copie\/pegue aqu\u00ed sus certificados codificados PEM.",
"encryption_status": "Estado",
"encryption_expire": "Expira",
"encryption_key": "Clave privada",
"encryption_key_input": "Copie\/pegue aqu\u00ed su clave privada codificada PEM para su certificado.",
"encryption_enable": "Habilitar el cifrado (HTTPS, DNS mediante HTTPS y DNS mediante TLS)",
"encryption_enable_desc": "Si el cifrado est\u00e1 habilitado, la interfaz de administraci\u00f3n de AdGuard Home funcionar\u00e1 a trav\u00e9s de HTTPS, y el servidor DNS escuchar\u00e1 las peticiones DNS mediante HTTPS y DNS mediante TLS.",
"encryption_chain_valid": "La cadena de certificado es v\u00e1lida",
"encryption_chain_invalid": "La cadena de certificado no es v\u00e1lida",
"encryption_key_valid": "Esta es una clave privada {{type}} v\u00e1lida",
"encryption_key_invalid": "Esta es una clave privada {{type}} no v\u00e1lida",
"encryption_subject": "Asunto",
"encryption_issuer": "Emisor",
"encryption_hostnames": "Nombres de hosts",
"encryption_reset": "\u00bfEst\u00e1 seguro de que desea restablecer la configuraci\u00f3n de cifrado?",
"topline_expiring_certificate": "Su certificado SSL est\u00e1 a punto de expirar. Actualice la <0>configuraci\u00f3n del cifrado<\/0>.",
"topline_expired_certificate": "Su certificado SSL ha expirado. Actualice la <0>configuraci\u00f3n del cifrado<\/0>.",
"form_error_port_range": "Ingrese el valor del puerto en el rango de 80 - 65535",
"form_error_port_unsafe": "Este es un puerto inseguro",
"form_error_equal": "No deber\u00eda ser igual",
"form_error_password": "La contrase\u00f1a no coincide",
"reset_settings": "Restablecer configuraci\u00f3n",
"update_announcement": "\u00a1AdGuard Home {{version}} ya est\u00e1 disponible! <0>Haga clic aqu\u00ed<\/0> para m\u00e1s informaci\u00f3n.",
"setup_guide": "Gu\u00eda de configuraci\u00f3n",
"dns_addresses": "Direcciones DNS"
}

View File

@@ -1,4 +1,8 @@
{
"example_upstream_reserved": "Voc\u00ea pode especificar um DNS upstream <0>para um dom\u00ednio(s) especifico<\/0>",
"upstream_parallel": "Usar consultas paralelas para acelerar a resolu\u00e7\u00e3o consultando simultaneamente todos os servidores upstream",
"bootstrap_dns": "Servidores DNS de inicializa\u00e7\u00e3o",
"bootstrap_dns_desc": "Servidores DNS de inicializa\u00e7\u00e3o s\u00e3o usados para resolver endere\u00e7os IP dos resolvedores DoH\/DoT que voc\u00ea especifica como upstreams.",
"url_added_successfully": "URL adicionada com sucesso",
"check_dhcp_servers": "Verificar por servidores DHCP",
"save_config": "Salvar configura\u00e7\u00e3o",
@@ -79,7 +83,7 @@
"no_settings": "N\u00e3o configurado",
"general_settings": "Configura\u00e7\u00f5es gerais",
"upstream_dns": "Servidores DNS upstream",
"upstream_dns_hint": "Se voc\u00ea deixar este campo vazio, o AdGuard Home ir\u00e1 usar o<a href='https:\/\/1.1.1.1\/' target='_blank'>DNS da Cloudflare<\/a> como upstream. Use o prefixo tls:\/\/ para servidores DNS com TLS.",
"upstream_dns_hint": "Se voc\u00ea deixar este campo vazio, o AdGuard Home ir\u00e1 usar o<a href='https:\/\/1.1.1.1\/' target='_blank'>DNS da Cloudflare<\/a> como upstream.",
"test_upstream_btn": "Testar upstreams",
"apply_btn": "Aplicar",
"disabled_filtering_toast": "Filtragem desativada",
@@ -118,9 +122,9 @@
"example_comment_hash": "# Tamb\u00e9m um coment\u00e1rio",
"example_regex_meaning": "bloqueia o acesso aos dom\u00ednios correspondentes \u00e0 express\u00e3o regular especificada",
"example_upstream_regular": "DNS regular (atrav\u00e9s do UDP)",
"example_upstream_dot": "DNS criptografado <0>atrav\u00e9s do TLS<\/0>",
"example_upstream_doh": "DNS criptografado <0>atrav\u00e9s do HTTPS<\/0>",
"example_upstream_sdns": "Voc\u00ea pode usar <0>DNS Stamps<\/0> para o <1>DNSCrypt<\/1> ou usar resolvedores <2>DNS-sobre-HTTPS<\/2>",
"example_upstream_dot": "<0>DNS-sobre-TLS<\/0> criptografado",
"example_upstream_doh": "<0>DNS-sobre-HTTPS<\/0> criptografado",
"example_upstream_sdns": "Voc\u00ea pode usar <0>DNS Stamps<\/0>para o <1>DNSCrypt<\/1>ou usar os resolvedores <2>DNS-sobre-HTTPS<\/2>",
"example_upstream_tcp": "DNS regular (atrav\u00e9s do TCP)",
"all_filters_up_to_date_toast": "Todos os filtros j\u00e1 est\u00e3o atualizados",
"updated_upstream_dns_toast": "Atualizado os servidores DNS upstream",
@@ -211,26 +215,26 @@
"open_dashboard": "Abrir painel",
"install_saved": "Salvo com sucesso",
"encryption_title": "Criptografia",
"encryption_desc": "Encryption (HTTPS\/TLS) support for both DNS and admin web interface",
"encryption_desc": "Suporte a criptografia (HTTPS\/TLS) para DNS e interface de administra\u00e7\u00e3o web",
"encryption_config_saved": "Configura\u00e7\u00e3o de criptografia salva",
"encryption_server": "Nome do servidor",
"encryption_server_enter": "Digite seu nome de dom\u00ednio",
"encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate.",
"encryption_server_desc": "Para usar o protocolo HTTPS, voc\u00ea precisa digitar o nome do servidor que corresponde ao seu certificado SSL.",
"encryption_redirect": "Redirecionar automaticamente para HTTPS",
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
"encryption_redirect_desc": "Se marcado, o AdGuard Home ir\u00e1 redirecionar automaticamente os endere\u00e7os HTTP para HTTPS.",
"encryption_https": "Porta HTTPS",
"encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '\/dns-query' location.",
"encryption_https_desc": "Se a porta HTTPS estiver configurada, a interface administrativa do AdGuard Home ser\u00e1 acess\u00edvel via HTTPS e tamb\u00e9m fornecer\u00e1 o DNS-sobre-HTTPS no local '\/dns-query'.",
"encryption_dot": "Porta DNS-sobre-TLS",
"encryption_dot_desc": "Se essa porta estiver configurada, o AdGuard Home ir\u00e1 executar o servidor DNS-sobre- TSL nesta porta.",
"encryption_certificates": "Certificados",
"encryption_certificates_desc": "In order to use encryption, you need to provide a valid SSL certificates chain for your domain. You can get a free certificate on <0>{{link}}<\/0> or you can buy it from one of the trusted Certificate Authorities.",
"encryption_certificates_desc": "Para usar criptografia, voc\u00ea precisa fornecer uma cadeia de certificados SSL v\u00e1lida para seu dom\u00ednio. Voc\u00ea pode obter um certificado gratuito em <0> {{link}}<\/0> ou pode compr\u00e1-lo de uma das autoridades de certifica\u00e7\u00e3o confi\u00e1veis.",
"encryption_certificates_input": "Copie\/cole aqui seu certificado codificado em PEM.",
"encryption_status": "Status",
"encryption_expire": "Expira",
"encryption_key": "Chave privada",
"encryption_key_input": "Copie\/cole aqui a chave privada codificada em PEM para seu certificado.",
"encryption_enable": "Ativar criptografia (HTTPS, DNS-sobre-HTTPS e DNS-sobre-TLS)",
"encryption_enable_desc": "If encryption is enabled, AdGuard Home admin interface will work over HTTPS, and the DNS server will listen for requests over DNS-over-HTTPS and DNS-over-TLS.",
"encryption_enable_desc": "Se a criptografia estiver ativada, a interface administrativa do AdGuard Home funcionar\u00e1 em HTTPS, o servidor DNS ir\u00e1 capturar as solicita\u00e7\u00f5es por meio do DNS-sobre-HTTPS e DNS-sobre-TLS.",
"encryption_chain_valid": "Cadeia de chave v\u00e1lida.",
"encryption_chain_invalid": "A cadeia de certificado \u00e9 inv\u00e1lida",
"encryption_key_valid": "Esta \u00e9 uma chave privada {{type}} v\u00e1lida",
@@ -246,5 +250,7 @@
"form_error_equal": "N\u00e3o deve ser igual",
"form_error_password": "Senhas n\u00e3o coincidem",
"reset_settings": "Redefinir configura\u00e7\u00f5es",
"update_announcement": "AdGuard Home {{version}} est\u00e1 dispon\u00edvel!<0>Clique aqui<\/0> para mais informa\u00e7\u00f5es."
"update_announcement": "AdGuard Home {{version}} est\u00e1 dispon\u00edvel!<0>Clique aqui<\/0> para mais informa\u00e7\u00f5es.",
"setup_guide": "Guia de configura\u00e7\u00e3o",
"dns_addresses": "Endere\u00e7os DNS"
}

View File

@@ -1,4 +1,5 @@
{
"example_upstream_reserved": "\u60a8\u53ef\u660e\u78ba\u6307\u5b9a<0>\u7528\u65bc\u7279\u5b9a\u7684\u7db2\u57df<\/0>\u4e4bDNS\u4e0a\u6e38",
"upstream_parallel": "\u900f\u904e\u540c\u6642\u5730\u67e5\u8a62\u6240\u6709\u4e0a\u6e38\u7684\u4f3a\u670d\u5668\uff0c\u4f7f\u7528\u4e26\u884c\u7684\u67e5\u8a62\u4ee5\u52a0\u901f\u89e3\u6790",
"bootstrap_dns": "\u81ea\u6211\u555f\u52d5\uff08Bootstrap\uff09DNS \u4f3a\u670d\u5668",
"bootstrap_dns_desc": "\u81ea\u6211\u555f\u52d5\uff08Bootstrap\uff09DNS\u4f3a\u670d\u5668\u88ab\u7528\u65bc\u89e3\u6790\u60a8\u660e\u78ba\u6307\u5b9a\u4f5c\u70ba\u4e0a\u6e38\u7684DoH\/DoT\u89e3\u6790\u5668\u4e4bIP\u4f4d\u5740\u3002",
@@ -122,8 +123,8 @@
"example_regex_meaning": "\u5c01\u9396\u81f3\u8207\u5df2\u660e\u78ba\u6307\u5b9a\u7684\u898f\u5247\u904b\u7b97\u5f0f\uff08Regular Expression\uff09\u76f8\u7b26\u7684\u7db2\u57df\u4e4b\u5b58\u53d6",
"example_upstream_regular": "\u4e00\u822c\u7684 DNS\uff08\u900f\u904eUDP\uff09",
"example_upstream_dot": "\u52a0\u5bc6\u7684 <0>DNS-over-TLS<\/0>",
"example_upstream_doh": "\u52a0\u5bc6\u7684 <0>DNS-over-HTTPS <\/0>",
"example_upstream_sdns": "\u60a8\u53ef\u4f7f\u7528\u95dc\u65bc <0>DNSCrypt<\/0> \u6216 <1>DNS-over-HTTPS<\/1> \u89e3\u6790\u5668\u4e4b <2>DNS \u6233\u8a18<\/2>",
"example_upstream_doh": "\u52a0\u5bc6\u7684 <0>DNS-over-HTTPS<\/0>",
"example_upstream_sdns": "\u60a8\u53ef\u4f7f\u7528\u95dc\u65bc <1>DNSCrypt<\/1> \u6216 <2>DNS-over-HTTPS<\/2> \u89e3\u6790\u5668\u4e4b <0>DNS \u6233\u8a18<\/0>",
"example_upstream_tcp": "\u4e00\u822c\u7684 DNS\uff08\u900f\u904eTCP\uff09",
"all_filters_up_to_date_toast": "\u6240\u6709\u7684\u904e\u6ffe\u5668\u5df2\u662f\u6700\u65b0\u7684",
"updated_upstream_dns_toast": "\u5df2\u66f4\u65b0\u4e0a\u6e38\u7684DNS\u4f3a\u670d\u5668",

View File

@@ -139,6 +139,36 @@ export const toggleProtection = status => async (dispatch) => {
}
};
export const getVersionRequest = createAction('GET_VERSION_REQUEST');
export const getVersionFailure = createAction('GET_VERSION_FAILURE');
export const getVersionSuccess = createAction('GET_VERSION_SUCCESS');
export const getVersion = () => async (dispatch) => {
dispatch(getVersionRequest());
try {
const newVersion = await apiClient.getGlobalVersion();
dispatch(getVersionSuccess(newVersion));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getVersionFailure());
}
};
export const getClientsRequest = createAction('GET_CLIENTS_REQUEST');
export const getClientsFailure = createAction('GET_CLIENTS_FAILURE');
export const getClientsSuccess = createAction('GET_CLIENTS_SUCCESS');
export const getClients = () => async (dispatch) => {
dispatch(getClientsRequest());
try {
const clients = await apiClient.getGlobalClients();
dispatch(getClientsSuccess(clients));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getClientsFailure());
}
};
export const dnsStatusRequest = createAction('DNS_STATUS_REQUEST');
export const dnsStatusFailure = createAction('DNS_STATUS_FAILURE');
export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS');
@@ -148,6 +178,8 @@ export const getDnsStatus = () => async (dispatch) => {
try {
const dnsStatus = await apiClient.getGlobalStatus();
dispatch(dnsStatusSuccess(dnsStatus));
dispatch(getVersion());
dispatch(getClients());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(initSettingsFailure());
@@ -205,21 +237,6 @@ export const getStats = () => async (dispatch) => {
}
};
export const getVersionRequest = createAction('GET_VERSION_REQUEST');
export const getVersionFailure = createAction('GET_VERSION_FAILURE');
export const getVersionSuccess = createAction('GET_VERSION_SUCCESS');
export const getVersion = () => async (dispatch) => {
dispatch(getVersionRequest());
try {
const newVersion = await apiClient.getGlobalVersion();
dispatch(getVersionSuccess(newVersion));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getVersionFailure());
}
};
export const getTopStatsRequest = createAction('GET_TOP_STATS_REQUEST');
export const getTopStatsFailure = createAction('GET_TOP_STATS_FAILURE');
export const getTopStatsSuccess = createAction('GET_TOP_STATS_SUCCESS');
@@ -665,18 +682,3 @@ export const toggleDhcp = config => async (dispatch) => {
}
}
};
export const getClientsRequest = createAction('GET_CLIENTS_REQUEST');
export const getClientsFailure = createAction('GET_CLIENTS_FAILURE');
export const getClientsSuccess = createAction('GET_CLIENTS_SUCCESS');
export const getClients = () => async (dispatch) => {
dispatch(getClientsRequest());
try {
const clients = await apiClient.getGlobalClients();
dispatch(getClientsSuccess(clients));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getClientsFailure());
}
};

View File

@@ -44,3 +44,18 @@ export const setAllSettings = values => async (dispatch) => {
dispatch(prevStep());
}
};
export const checkConfigRequest = createAction('CHECK_CONFIG_REQUEST');
export const checkConfigFailure = createAction('CHECK_CONFIG_FAILURE');
export const checkConfigSuccess = createAction('CHECK_CONFIG_SUCCESS');
export const checkConfig = values => async (dispatch) => {
dispatch(checkConfigRequest());
try {
const check = await apiClient.checkConfig(values);
dispatch(checkConfigSuccess(check));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(checkConfigFailure());
}
};

View File

@@ -149,9 +149,9 @@ export default class Api {
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
FILTERING_ENABLE = { path: 'filtering/enable', method: 'POST' };
FILTERING_DISABLE = { path: 'filtering/disable', method: 'POST' };
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'PUT' };
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'DELETE' };
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'PUT' };
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' };
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' };
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'POST' };
FILTERING_ENABLE_FILTER = { path: 'filtering/enable_url', method: 'POST' };
FILTERING_DISABLE_FILTER = { path: 'filtering/disable_url', method: 'POST' };
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' };
@@ -350,6 +350,7 @@ export default class Api {
// Installation
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
INSTALL_CHECK_CONFIG = { path: 'install/check_config', method: 'POST' };
getDefaultAddresses() {
const { path, method } = this.INSTALL_GET_ADDRESSES;
@@ -365,6 +366,15 @@ export default class Api {
return this.makeRequest(path, method, parameters);
}
checkConfig(config) {
const { path, method } = this.INSTALL_CHECK_CONFIG;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
// DNS-over-HTTPS and DNS-over-TLS
TLS_STATUS = { path: 'tls/status', method: 'GET' };
TLS_CONFIG = { path: 'tls/configure', method: 'POST' };

View File

@@ -25,8 +25,6 @@ import i18n from '../../i18n';
class App extends Component {
componentDidMount() {
this.props.getDnsStatus();
this.props.getVersion();
this.props.getClients();
}
componentDidUpdate(prevProps) {
@@ -106,10 +104,8 @@ App.propTypes = {
dashboard: PropTypes.object,
isCoreRunning: PropTypes.bool,
error: PropTypes.string,
getVersion: PropTypes.func,
changeLanguage: PropTypes.func,
encryption: PropTypes.object,
getClients: PropTypes.func,
};
export default withNamespaces()(App);

View File

@@ -117,6 +117,10 @@
.nav-version {
padding: 0;
}
.nav-icon {
display: none;
}
}
@media screen and (min-width: 1280px) {
@@ -127,6 +131,10 @@
.nav-version {
font-size: 0.85rem;
}
.nav-icon {
display: block;
}
}
.dns-status {

View File

@@ -78,7 +78,8 @@ let Interface = (props) => {
</div>
{interfaceValue &&
<div className="col-sm-12 col-md-6">
{renderInterfaceValues(interfaces[interfaceValue])}
{interfaces[interfaceValue] &&
renderInterfaceValues(interfaces[interfaceValue])}
</div>
}
</div>

View File

@@ -1,32 +1,49 @@
import React from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { Trans, withNamespaces } from 'react-i18next';
const columns = [{
Header: 'MAC',
accessor: 'mac',
}, {
Header: 'IP',
accessor: 'ip',
}, {
Header: <Trans>dhcp_table_hostname</Trans>,
accessor: 'hostname',
}, {
Header: <Trans>dhcp_table_expires</Trans>,
accessor: 'expires',
}];
class Leases extends Component {
cellWrap = ({ value }) => (
<div className="logs__row logs__row--overflow">
<span className="logs__text" title={value}>
{value}
</span>
</div>
);
const Leases = props => (
<ReactTable
data={props.leases || []}
columns={columns}
showPagination={false}
noDataText={ props.t('dhcp_leases_not_found') }
minRows={6}
className="-striped -highlight card-table-overflow"
/>
);
render() {
const { leases, t } = this.props;
return (
<ReactTable
data={leases || []}
columns={[
{
Header: 'MAC',
accessor: 'mac',
Cell: this.cellWrap,
}, {
Header: 'IP',
accessor: 'ip',
Cell: this.cellWrap,
}, {
Header: <Trans>dhcp_table_hostname</Trans>,
accessor: 'hostname',
Cell: this.cellWrap,
}, {
Header: <Trans>dhcp_table_expires</Trans>,
accessor: 'expires',
Cell: this.cellWrap,
},
]}
showPagination={false}
noDataText={t('dhcp_leases_not_found')}
minRows={6}
className="-striped -highlight card-table-overflow"
/>
);
}
}
Leases.propTypes = {
leases: PropTypes.array,

View File

@@ -3,10 +3,12 @@ import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Trans, withNamespaces } from 'react-i18next';
import { DHCP_STATUS_RESPONSE } from '../../../helpers/constants';
import Form from './Form';
import Leases from './Leases';
import Interface from './Interface';
import Card from '../../ui/Card';
import Accordion from '../../ui/Accordion';
class Dhcp extends Component {
handleFormSubmit = (values) => {
@@ -19,11 +21,12 @@ class Dhcp extends Component {
getToggleDhcpButton = () => {
const {
config, active, processingDhcp, processingConfig,
config, check, processingDhcp, processingConfig,
} = this.props.dhcp;
const activeDhcpFound = active && active.found;
const otherDhcpFound =
check && check.otherServer && check.otherServer.found === DHCP_STATUS_RESPONSE.YES;
const filledConfig = Object.keys(config).every((key) => {
if (key === 'enabled') {
if (key === 'enabled' || key === 'icmp_timeout_msec') {
return true;
}
@@ -50,7 +53,8 @@ class Dhcp extends Component {
onClick={() => this.handleToggle(config)}
disabled={
!filledConfig
|| activeDhcpFound
|| !check
|| otherDhcpFound
|| processingDhcp
|| processingConfig
}
@@ -60,33 +64,89 @@ class Dhcp extends Component {
);
}
getActiveDhcpMessage = () => {
const { active } = this.props.dhcp;
if (active) {
if (active.error) {
return (
<div className="text-danger mb-2">
{active.error}
</div>
);
}
getActiveDhcpMessage = (t, check) => {
const { found } = check.otherServer;
if (found === DHCP_STATUS_RESPONSE.ERROR) {
return (
<div className="mb-2">
{active.found ? (
<div className="text-danger">
<Trans>dhcp_found</Trans>
</div>
) : (
<div className="text-secondary">
<Trans>dhcp_not_found</Trans>
</div>
)}
<div className="text-danger mb-2">
<Trans>dhcp_error</Trans>
<div className="mt-2 mb-2">
<Accordion label={t('error_details')}>
<span>{check.otherServer.error}</span>
</Accordion>
</div>
</div>
);
}
return (
<div className="mb-2">
{found === DHCP_STATUS_RESPONSE.YES ? (
<div className="text-danger">
<Trans>dhcp_found</Trans>
</div>
) : (
<div className="text-secondary">
<Trans>dhcp_not_found</Trans>
</div>
)}
</div>
);
}
getDhcpWarning = (check) => {
if (check.otherServer.found === DHCP_STATUS_RESPONSE.NO) {
return '';
}
return (
<div className="text-danger">
<Trans>dhcp_warning</Trans>
</div>
);
}
getStaticIpWarning = (t, check, interfaceName) => {
if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) {
return (
<Fragment>
<div className="text-danger mb-2">
<Trans>dhcp_static_ip_error</Trans>
<div className="mt-2 mb-2">
<Accordion label={t('error_details')}>
<span>{check.staticIP.error}</span>
</Accordion>
</div>
</div>
<hr className="mt-4 mb-4"/>
</Fragment>
);
} else if (
check.staticIP.static === DHCP_STATUS_RESPONSE.NO
&& check.staticIP.ip
&& interfaceName
) {
return (
<Fragment>
<div className="text-secondary mb-2">
<Trans
components={[
<strong key="0">example</strong>,
]}
values={{
interfaceName,
ipAddress: check.staticIP.ip,
}}
>
dhcp_dynamic_ip_found
</Trans>
</div>
<hr className="mt-4 mb-4"/>
</Fragment>
);
}
return '';
}
@@ -131,17 +191,21 @@ class Dhcp extends Component {
this.props.findActiveDhcp(dhcp.config.interface_name)
}
disabled={
!dhcp.config.interface_name
dhcp.config.enabled
|| !dhcp.config.interface_name
|| dhcp.processingConfig
}
>
<Trans>check_dhcp_servers</Trans>
</button>
</div>
{this.getActiveDhcpMessage()}
<div className="text-danger">
<Trans>dhcp_warning</Trans>
</div>
{!enabled && dhcp.check &&
<Fragment>
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
{this.getActiveDhcpMessage(t, dhcp.check)}
{this.getDhcpWarning(dhcp.check)}
</Fragment>
}
</Fragment>
}
</div>

View File

@@ -261,7 +261,7 @@ let Form = (props) => {
component="textarea"
type="text"
className="form-control form-control--textarea"
placeholder="Copy/paste your PEM-encoded private key for your cerficate here."
placeholder={t('encryption_key_input')}
onChange={handleChange}
disabled={!isEnabled}
/>

View File

@@ -9,7 +9,9 @@ import Card from '../../ui/Card';
class Encryption extends Component {
componentDidMount() {
this.props.validateTlsConfig(this.props.encryption);
if (this.props.encryption.enabled) {
this.props.validateTlsConfig(this.props.encryption);
}
}
handleFormSubmit = (values) => {

View File

@@ -0,0 +1,32 @@
.accordion {
color: #495057;
}
.accordion__label {
position: relative;
display: inline-block;
padding-left: 25px;
cursor: pointer;
user-select: none;
}
.accordion__label:after {
content: "";
position: absolute;
top: 7px;
left: 0;
width: 17px;
height: 10px;
background-image: url("./svg/chevron-down.svg");
background-repeat: no-repeat;
background-position: center;
background-size: 100%;
}
.accordion__label--open:after {
transform: rotate(180deg);
}
.accordion__content {
padding-top: 5px;
}

View File

@@ -0,0 +1,43 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './Accordion.css';
class Accordion extends Component {
state = {
isOpen: false,
}
handleClick = () => {
this.setState(prevState => ({ isOpen: !prevState.isOpen }));
};
render() {
const accordionClass = this.state.isOpen
? 'accordion__label accordion__label--open'
: 'accordion__label';
return (
<div className="accordion">
<div
className={accordionClass}
onClick={this.handleClick}
>
{this.props.label}
</div>
{this.state.isOpen && (
<div className="accordion__content">
{this.props.children}
</div>
)}
</div>
);
}
}
Accordion.propTypes = {
children: PropTypes.node.isRequired,
label: PropTypes.string.isRequired,
};
export default Accordion;

View File

@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { Trans, withNamespaces } from 'react-i18next';
import { REPOSITORY, LANGUAGES } from '../../helpers/constants';
import { REPOSITORY, LANGUAGES, PRIVACY_POLICY_LINK } from '../../helpers/constants';
import i18n from '../../i18n';
import './Footer.css';
@@ -30,6 +30,9 @@ class Footer extends Component {
<a href={REPOSITORY.URL} className="footer__link" target="_blank" rel="noopener noreferrer">
<Trans>homepage</Trans>
</a>
<a href={PRIVACY_POLICY_LINK} className="footer__link" target="_blank" rel="noopener noreferrer">
<Trans>privacy_policy</Trans>
</a>
<a href={`${REPOSITORY.URL}/issues/new`} className="btn btn-outline-primary btn-sm footer__link footer__link--report" target="_blank" rel="noopener noreferrer">
<Trans>report_an_issue</Trans>
</a>

View File

@@ -22,6 +22,8 @@ export const REPOSITORY = {
TRACKERS_DB: 'https://github.com/AdguardTeam/AdGuardHome/tree/master/client/src/helpers/trackers/adguard.json',
};
export const PRIVACY_POLICY_LINK = 'https://adguard.com/privacy/home.html';
export const LANGUAGES = [
{
key: 'en',
@@ -155,3 +157,11 @@ export const UNSAFE_PORTS = [
6668,
6669,
];
export const ALL_INTERFACES_IP = '0.0.0.0';
export const DHCP_STATUS_RESPONSE = {
YES: 'yes',
NO: 'no',
ERROR: 'error',
};

View File

@@ -2,12 +2,13 @@ import React from 'react';
import PropTypes from 'prop-types';
import { getIpList, getDnsAddress, getWebAddress } from '../../helpers/helpers';
import { ALL_INTERFACES_IP } from '../../helpers/constants';
const AddressList = (props) => {
let webAddress = getWebAddress(props.address, props.port);
let dnsAddress = getDnsAddress(props.address, props.port);
if (props.address === '0.0.0.0') {
if (props.address === ALL_INTERFACES_IP) {
return getIpList(props.interfaces).map((ip) => {
webAddress = getWebAddress(ip, props.port);
dnsAddress = getDnsAddress(ip, props.port);

View File

@@ -25,13 +25,22 @@ class Controls extends Component {
}
renderNextButton(step) {
const {
nextStep,
invalid,
pristine,
install,
ip,
port,
} = this.props;
switch (step) {
case 1:
return (
<button
type="button"
className="btn btn-success btn-lg setup__button"
onClick={this.props.nextStep}
onClick={nextStep}
>
<Trans>get_started</Trans>
</button>
@@ -43,9 +52,11 @@ class Controls extends Component {
type="submit"
className="btn btn-success btn-lg setup__button"
disabled={
this.props.invalid
|| this.props.pristine
|| this.props.install.processingSubmit
invalid
|| pristine
|| install.processingSubmit
|| install.dns.status
|| install.web.status
}
>
<Trans>next</Trans>
@@ -56,7 +67,7 @@ class Controls extends Component {
<button
type="button"
className="btn btn-success btn-lg setup__button"
onClick={this.props.nextStep}
onClick={nextStep}
>
<Trans>next</Trans>
</button>
@@ -66,7 +77,8 @@ class Controls extends Component {
<button
type="button"
className="btn btn-success btn-lg setup__button"
onClick={() => this.props.openDashboard(this.props.address)}
onClick={() =>
this.props.openDashboard(ip, port)}
>
<Trans>open_dashboard</Trans>
</button>
@@ -98,7 +110,8 @@ Controls.propTypes = {
submitting: PropTypes.bool,
invalid: PropTypes.bool,
pristine: PropTypes.bool,
address: PropTypes.string,
ip: PropTypes.string,
port: PropTypes.number,
};
const mapStateToProps = (state) => {

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Field, reduxForm, formValueSelector } from 'redux-form';
@@ -9,6 +9,7 @@ import Controls from './Controls';
import AddressList from './AddressList';
import renderField from './renderField';
import { getInterfaceIp } from '../../helpers/helpers';
import { ALL_INTERFACES_IP } from '../../helpers/constants';
const required = (value) => {
if (value || value === 0) {
@@ -29,10 +30,25 @@ const toNumber = value => value && parseInt(value, 10);
const renderInterfaces = (interfaces => (
Object.keys(interfaces).map((item) => {
const option = interfaces[item];
const { name } = option;
const {
name,
ip_addresses,
flags,
} = option;
if (option.ip_addresses && option.ip_addresses.length > 0) {
if (option && ip_addresses && ip_addresses.length > 0) {
const ip = getInterfaceIp(option);
const isDown = flags && flags.includes('down');
if (isDown) {
return (
<option value={ip} key={name} disabled>
<Fragment>
{name} - {ip} (<Trans>down</Trans>)
</Fragment>
</option>
);
}
return (
<option value={ip} key={name}>
@@ -45,141 +61,191 @@ const renderInterfaces = (interfaces => (
})
));
let Settings = (props) => {
const {
handleSubmit,
webIp,
webPort,
dnsIp,
dnsPort,
interfaces,
invalid,
webWarning,
dnsWarning,
} = props;
class Settings extends Component {
componentDidMount() {
const { web, dns } = this.props.config;
return (
<form className="setup__step" onSubmit={handleSubmit}>
<div className="setup__group">
<div className="setup__subtitle">
<Trans>install_settings_title</Trans>
</div>
<div className="row">
<div className="col-8">
<div className="form-group">
<label>
<Trans>install_settings_listen</Trans>
</label>
<Field
name="web.ip"
component="select"
className="form-control custom-select"
>
<option value="0.0.0.0">
<Trans>install_settings_all_interfaces</Trans>
</option>
{renderInterfaces(interfaces)}
</Field>
this.props.validateForm({
web,
dns,
});
}
render() {
const {
handleSubmit,
handleChange,
handleAutofix,
webIp,
webPort,
dnsIp,
dnsPort,
interfaces,
invalid,
config,
} = this.props;
const {
status: webStatus,
can_autofix: isWebFixAvailable,
} = config.web;
const {
status: dnsStatus,
can_autofix: isDnsFixAvailable,
} = config.dns;
return (
<form className="setup__step" onSubmit={handleSubmit}>
<div className="setup__group">
<div className="setup__subtitle">
<Trans>install_settings_title</Trans>
</div>
<div className="row">
<div className="col-8">
<div className="form-group">
<label>
<Trans>install_settings_listen</Trans>
</label>
<Field
name="web.ip"
component="select"
className="form-control custom-select"
onChange={handleChange}
>
<option value={ALL_INTERFACES_IP}>
<Trans>install_settings_all_interfaces</Trans>
</option>
{renderInterfaces(interfaces)}
</Field>
</div>
</div>
<div className="col-4">
<div className="form-group">
<label>
<Trans>install_settings_port</Trans>
</label>
<Field
name="web.port"
component={renderField}
type="number"
className="form-control"
placeholder="80"
validate={[port, required]}
normalize={toNumber}
onChange={handleChange}
/>
</div>
</div>
<div className="col-12">
{webStatus &&
<div className="setup__error text-danger">
{webStatus}
{isWebFixAvailable &&
<button
type="button"
className="btn btn-secondary btn-sm ml-2"
onClick={() => handleAutofix('web', webIp, webPort)}
>
<Trans>fix</Trans>
</button>
}
</div>
}
</div>
</div>
<div className="col-4">
<div className="form-group">
<label>
<Trans>install_settings_port</Trans>
</label>
<Field
name="web.port"
component={renderField}
type="number"
className="form-control"
placeholder="80"
validate={[port, required]}
normalize={toNumber}
<div className="setup__desc">
<Trans>install_settings_interface_link</Trans>
<div className="mt-1">
<AddressList
interfaces={interfaces}
address={webIp}
port={webPort}
/>
</div>
</div>
</div>
<div className="setup__desc">
<Trans>install_settings_interface_link</Trans>
<div className="mt-1">
<AddressList
interfaces={interfaces}
address={webIp}
port={webPort}
/>
<div className="setup__group">
<div className="setup__subtitle">
<Trans>install_settings_dns</Trans>
</div>
{webWarning &&
<div className="text-danger mt-2">
{webWarning}
<div className="row">
<div className="col-8">
<div className="form-group">
<label>
<Trans>install_settings_listen</Trans>
</label>
<Field
name="dns.ip"
component="select"
className="form-control custom-select"
onChange={handleChange}
>
<option value={ALL_INTERFACES_IP}>
<Trans>install_settings_all_interfaces</Trans>
</option>
{renderInterfaces(interfaces)}
</Field>
</div>
</div>
}
</div>
</div>
<div className="setup__group">
<div className="setup__subtitle">
<Trans>install_settings_dns</Trans>
</div>
<div className="row">
<div className="col-8">
<div className="form-group">
<label>
<Trans>install_settings_listen</Trans>
</label>
<Field
name="dns.ip"
component="select"
className="form-control custom-select"
>
<option value="0.0.0.0">
<Trans>install_settings_all_interfaces</Trans>
</option>
{renderInterfaces(interfaces)}
</Field>
<div className="col-4">
<div className="form-group">
<label>
<Trans>install_settings_port</Trans>
</label>
<Field
name="dns.port"
component={renderField}
type="number"
className="form-control"
placeholder="80"
validate={[port, required]}
normalize={toNumber}
onChange={handleChange}
/>
</div>
</div>
<div className="col-12">
{dnsStatus &&
<div className="setup__error text-danger">
{dnsStatus}
{isDnsFixAvailable &&
<button
type="button"
className="btn btn-secondary btn-sm ml-2"
onClick={() => handleAutofix('dns', dnsIp, dnsPort)}
>
<Trans>fix</Trans>
</button>
}
</div>
}
</div>
</div>
<div className="col-4">
<div className="form-group">
<label>
<Trans>install_settings_port</Trans>
</label>
<Field
name="dns.port"
component={renderField}
type="number"
className="form-control"
placeholder="80"
validate={[port, required]}
normalize={toNumber}
<div className="setup__desc">
<Trans>install_settings_dns_desc</Trans>
<div className="mt-1">
<AddressList
interfaces={interfaces}
address={dnsIp}
port={dnsPort}
isDns={true}
/>
</div>
</div>
</div>
<div className="setup__desc">
<Trans>install_settings_dns_desc</Trans>
<div className="mt-1">
<AddressList
interfaces={interfaces}
address={dnsIp}
port={dnsPort}
isDns={true}
/>
</div>
{dnsWarning &&
<div className="text-danger mt-2">
{dnsWarning}
</div>
}
</div>
</div>
<Controls invalid={invalid} />
</form>
);
};
<Controls invalid={invalid} />
</form>
);
}
}
Settings.propTypes = {
handleSubmit: PropTypes.func.isRequired,
handleChange: PropTypes.func,
handleAutofix: PropTypes.func,
validateForm: PropTypes.func,
webIp: PropTypes.string.isRequired,
dnsIp: PropTypes.string.isRequired,
config: PropTypes.object.isRequired,
webPort: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
@@ -188,8 +254,6 @@ Settings.propTypes = {
PropTypes.string,
PropTypes.number,
]),
webWarning: PropTypes.string.isRequired,
dnsWarning: PropTypes.string.isRequired,
interfaces: PropTypes.object.isRequired,
invalid: PropTypes.bool.isRequired,
initialValues: PropTypes.object,
@@ -197,7 +261,7 @@ Settings.propTypes = {
const selector = formValueSelector('install');
Settings = connect((state) => {
const SettingsForm = connect((state) => {
const webIp = selector(state, 'web.ip');
const webPort = selector(state, 'web.port');
const dnsIp = selector(state, 'dns.ip');
@@ -218,4 +282,4 @@ export default flow([
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
}),
])(Settings);
])(SettingsForm);

View File

@@ -115,3 +115,7 @@
padding-left: 30px;
padding-right: 30px;
}
.setup__error {
margin: -5px 0 5px;
}

View File

@@ -6,7 +6,6 @@ import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import Controls from './Controls';
import { getWebAddress } from '../../helpers/helpers';
let Submit = props => (
<div className="setup__step">
@@ -20,7 +19,8 @@ let Submit = props => (
</div>
<Controls
openDashboard={props.openDashboard}
address={getWebAddress(props.webIp, props.webPort)}
ip={props.webIp}
port={props.webPort}
/>
</div>
);

View File

@@ -1,9 +1,16 @@
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import debounce from 'lodash/debounce';
import * as actionCreators from '../../actions/install';
import { INSTALL_FIRST_STEP, INSTALL_TOTAL_STEPS } from '../../helpers/constants';
import { getWebAddress } from '../../helpers/helpers';
import {
INSTALL_FIRST_STEP,
INSTALL_TOTAL_STEPS,
ALL_INTERFACES_IP,
DEBOUNCE_TIMEOUT,
} from '../../helpers/constants';
import Loading from '../../components/ui/Loading';
import Greeting from './Greeting';
@@ -29,7 +36,37 @@ class Setup extends Component {
this.props.setAllSettings(values);
};
openDashboard = (address) => {
handleFormChange = debounce((values) => {
if (values && values.web.port && values.dns.port) {
this.props.checkConfig(values);
}
}, DEBOUNCE_TIMEOUT);
handleAutofix = (type, ip, port) => {
const data = {
ip,
port,
autofix: true,
};
if (type === 'web') {
this.props.checkConfig({
web: { ...data },
});
} else {
this.props.checkConfig({
dns: { ...data },
});
}
};
openDashboard = (ip, port) => {
let address = getWebAddress(ip, port);
if (ip === ALL_INTERFACES_IP) {
address = getWebAddress(window.location.hostname, port);
}
window.location.replace(address);
}
@@ -52,11 +89,13 @@ class Setup extends Component {
case 2:
return (
<Settings
config={config}
initialValues={config}
interfaces={interfaces}
webWarning={config.web.warning}
dnsWarning={config.dns.warning}
onSubmit={this.nextStep}
onChange={this.handleFormChange}
validateForm={this.handleFormChange}
handleAutofix={this.handleAutofix}
/>
);
case 3:
@@ -105,6 +144,7 @@ class Setup extends Component {
Setup.propTypes = {
getDefaultAddresses: PropTypes.func.isRequired,
setAllSettings: PropTypes.func.isRequired,
checkConfig: PropTypes.func.isRequired,
nextStep: PropTypes.func.isRequired,
prevStep: PropTypes.func.isRequired,
install: PropTypes.object.isRequired,

View File

@@ -292,18 +292,31 @@ const dhcp = handleActions({
[actions.findActiveDhcpRequest]: state => ({ ...state, processingStatus: true }),
[actions.findActiveDhcpFailure]: state => ({ ...state, processingStatus: false }),
[actions.findActiveDhcpSuccess]: (state, { payload }) => ({
...state,
active: payload,
processingStatus: false,
}),
[actions.findActiveDhcpSuccess]: (state, { payload }) => {
const {
other_server: otherServer,
static_ip: staticIP,
} = payload;
const newState = {
...state,
check: {
otherServer,
staticIP,
},
processingStatus: false,
};
return newState;
},
[actions.toggleDhcpRequest]: state => ({ ...state, processingDhcp: true }),
[actions.toggleDhcpFailure]: state => ({ ...state, processingDhcp: false }),
[actions.toggleDhcpSuccess]: (state) => {
const { config } = state;
const newConfig = { ...config, enabled: !config.enabled };
const newState = { ...state, config: newConfig, processingDhcp: false };
const newState = {
...state, config: newConfig, check: null, processingDhcp: false,
};
return newState;
},
@@ -324,7 +337,7 @@ const dhcp = handleActions({
config: {
enabled: false,
},
active: null,
check: null,
leases: [],
});

View File

@@ -10,10 +10,13 @@ const install = handleActions({
[actions.getDefaultAddressesRequest]: state => ({ ...state, processingDefault: true }),
[actions.getDefaultAddressesFailure]: state => ({ ...state, processingDefault: false }),
[actions.getDefaultAddressesSuccess]: (state, { payload }) => {
const values = payload;
values.web.ip = state.web.ip;
values.dns.ip = state.dns.ip;
const newState = { ...state, ...values, processingDefault: false };
const { interfaces } = payload;
const web = { ...state.web, port: payload.web_port };
const dns = { ...state.dns, port: payload.dns_port };
const newState = {
...state, web, dns, interfaces, processingDefault: false,
};
return newState;
},
@@ -23,19 +26,34 @@ const install = handleActions({
[actions.setAllSettingsRequest]: state => ({ ...state, processingSubmit: true }),
[actions.setAllSettingsFailure]: state => ({ ...state, processingSubmit: false }),
[actions.setAllSettingsSuccess]: state => ({ ...state, processingSubmit: false }),
[actions.checkConfigRequest]: state => ({ ...state, processingCheck: true }),
[actions.checkConfigFailure]: state => ({ ...state, processingCheck: false }),
[actions.checkConfigSuccess]: (state, { payload }) => {
const web = { ...state.web, ...payload.web };
const dns = { ...state.dns, ...payload.dns };
const newState = {
...state, web, dns, processingCheck: false,
};
return newState;
},
}, {
step: INSTALL_FIRST_STEP,
processingDefault: true,
processingSubmit: false,
processingCheck: false,
web: {
ip: '0.0.0.0',
port: 80,
warning: '',
status: '',
can_autofix: false,
},
dns: {
ip: '0.0.0.0',
port: 53,
warning: '',
status: '',
can_autofix: false,
},
interfaces: {},
});

View File

@@ -34,11 +34,13 @@ type configuration struct {
ourWorkingDir string // Location of our directory, used to protect against CWD being somewhere else
firstRun bool // if set to true, don't run any services except HTTP web inteface, and serve only first-run html
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
AuthName string `yaml:"auth_name"` // AuthName is the basic auth username
AuthPass string `yaml:"auth_pass"` // AuthPass is the basic auth password
Language string `yaml:"language"` // two-letter ISO 639-1 language code
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
AuthName string `yaml:"auth_name"` // AuthName is the basic auth username
AuthPass string `yaml:"auth_pass"` // AuthPass is the basic auth password
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)
DNS dnsConfig `yaml:"dns"`
TLS tlsConfig `yaml:"tls"`
Filters []filter `yaml:"filters"`
@@ -134,6 +136,10 @@ var config = configuration{
{Filter: dnsfilter.Filter{ID: 3}, Enabled: false, URL: "https://hosts-file.net/ad_servers.txt", Name: "hpHosts - Ad and Tracking servers only"},
{Filter: dnsfilter.Filter{ID: 4}, Enabled: false, URL: "https://www.malwaredomainlist.com/hostslist/hosts.txt", Name: "MalwareDomainList.com Hosts List"},
},
DHCP: dhcpd.ServerConfig{
LeaseDuration: 86400,
ICMPTimeout: 1000,
},
SchemaVersion: currentSchemaVersion,
}

View File

@@ -2,7 +2,6 @@ package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
@@ -19,6 +18,7 @@ import (
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/utils"
"github.com/NYTimes/gziphandler"
"github.com/miekg/dns"
govalidator "gopkg.in/asaskevich/govalidator.v4"
)
@@ -34,8 +34,13 @@ var protocols = []string{"tls://", "https://", "tcp://", "sdns://"}
const versionCheckURL = "https://adguardteam.github.io/AdGuardHome/version.json"
const versionCheckPeriod = time.Hour * 8
var transport = &http.Transport{
DialContext: customDialContext,
}
var client = &http.Client{
Timeout: time.Second * 30,
Timeout: time.Minute * 5,
Transport: transport,
}
var controlLock sync.Mutex
@@ -964,112 +969,6 @@ func handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
}
}
type ipport struct {
IP string `json:"ip,omitempty"`
Port int `json:"port"`
Warning string `json:"warning"`
}
type firstRunData struct {
Web ipport `json:"web"`
DNS ipport `json:"dns"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Interfaces map[string]interface{} `json:"interfaces"`
}
func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
log.Tracef("%s %v", r.Method, r.URL)
data := firstRunData{}
// find out if port 80 is available -- if not, fall back to 3000
if checkPortAvailable("", 80) == nil {
data.Web.Port = 80
} else {
data.Web.Port = 3000
}
// find out if port 53 is available -- if not, show a big warning
data.DNS.Port = 53
if checkPacketPortAvailable("", 53) != nil {
data.DNS.Warning = "Port 53 is not available for binding -- this will make DNS clients unable to contact AdGuard Home."
}
ifaces, err := getValidNetInterfacesForWeb()
if err != nil {
httpError(w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err)
return
}
data.Interfaces = make(map[string]interface{})
for _, iface := range ifaces {
data.Interfaces[iface.Name] = iface
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(data)
if err != nil {
httpError(w, http.StatusInternalServerError, "Unable to marshal default addresses to json: %s", err)
return
}
}
func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
log.Tracef("%s %v", r.Method, r.URL)
newSettings := firstRunData{}
err := json.NewDecoder(r.Body).Decode(&newSettings)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to parse new config json: %s", err)
return
}
restartHTTP := true
if config.BindHost == newSettings.Web.IP && config.BindPort == newSettings.Web.Port {
// no need to rebind
restartHTTP = false
}
// validate that hosts and ports are bindable
if restartHTTP {
err = checkPortAvailable(newSettings.Web.IP, newSettings.Web.Port)
if err != nil {
httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s due to %s", net.JoinHostPort(newSettings.Web.IP, strconv.Itoa(newSettings.Web.Port)), err)
return
}
}
err = checkPacketPortAvailable(newSettings.DNS.IP, newSettings.DNS.Port)
if err != nil {
httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s due to %s", net.JoinHostPort(newSettings.DNS.IP, strconv.Itoa(newSettings.DNS.Port)), err)
return
}
config.firstRun = false
config.BindHost = newSettings.Web.IP
config.BindPort = newSettings.Web.Port
config.DNS.BindHost = newSettings.DNS.IP
config.DNS.Port = newSettings.DNS.Port
config.AuthName = newSettings.Username
config.AuthPass = newSettings.Password
if config.DNS.Port != 0 {
err = startDNSServer()
if err != nil {
httpError(w, http.StatusInternalServerError, "Couldn't start DNS server: %s", err)
return
}
}
httpUpdateConfigReloadDNSReturnOK(w, r)
// 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() {
httpServer.Shutdown(context.TODO())
}()
}
}
// --------------
// DNS-over-HTTPS
// --------------
@@ -1091,16 +990,11 @@ func handleDOH(w http.ResponseWriter, r *http.Request) {
// ------------------------
// registration of handlers
// ------------------------
func registerInstallHandlers() {
http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(handleInstallGetAddresses)))
http.HandleFunc("/control/install/configure", preInstall(ensurePOST(handleInstallConfigure)))
}
func registerControlHandlers() {
http.HandleFunc("/control/status", postInstall(optionalAuth(ensureGET(handleStatus))))
http.HandleFunc("/control/enable_protection", postInstall(optionalAuth(ensurePOST(handleProtectionEnable))))
http.HandleFunc("/control/disable_protection", postInstall(optionalAuth(ensurePOST(handleProtectionDisable))))
http.HandleFunc("/control/querylog", postInstall(optionalAuth(ensureGET(handleQueryLog))))
http.Handle("/control/querylog", postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureGETHandler(handleQueryLog)))))
http.HandleFunc("/control/querylog_enable", postInstall(optionalAuth(ensurePOST(handleQueryLogEnable))))
http.HandleFunc("/control/querylog_disable", postInstall(optionalAuth(ensurePOST(handleQueryLogDisable))))
http.HandleFunc("/control/set_upstreams_config", postInstall(optionalAuth(ensurePOST(handleSetUpstreamConfig))))
@@ -1114,13 +1008,13 @@ func registerControlHandlers() {
http.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON)))
http.HandleFunc("/control/filtering/enable", postInstall(optionalAuth(ensurePOST(handleFilteringEnable))))
http.HandleFunc("/control/filtering/disable", postInstall(optionalAuth(ensurePOST(handleFilteringDisable))))
http.HandleFunc("/control/filtering/add_url", postInstall(optionalAuth(ensurePUT(handleFilteringAddURL))))
http.HandleFunc("/control/filtering/remove_url", postInstall(optionalAuth(ensureDELETE(handleFilteringRemoveURL))))
http.HandleFunc("/control/filtering/add_url", postInstall(optionalAuth(ensurePOST(handleFilteringAddURL))))
http.HandleFunc("/control/filtering/remove_url", postInstall(optionalAuth(ensurePOST(handleFilteringRemoveURL))))
http.HandleFunc("/control/filtering/enable_url", postInstall(optionalAuth(ensurePOST(handleFilteringEnableURL))))
http.HandleFunc("/control/filtering/disable_url", postInstall(optionalAuth(ensurePOST(handleFilteringDisableURL))))
http.HandleFunc("/control/filtering/refresh", postInstall(optionalAuth(ensurePOST(handleFilteringRefresh))))
http.HandleFunc("/control/filtering/status", postInstall(optionalAuth(ensureGET(handleFilteringStatus))))
http.HandleFunc("/control/filtering/set_rules", postInstall(optionalAuth(ensurePUT(handleFilteringSetRules))))
http.HandleFunc("/control/filtering/set_rules", postInstall(optionalAuth(ensurePOST(handleFilteringSetRules))))
http.HandleFunc("/control/safebrowsing/enable", postInstall(optionalAuth(ensurePOST(handleSafeBrowsingEnable))))
http.HandleFunc("/control/safebrowsing/disable", postInstall(optionalAuth(ensurePOST(handleSafeBrowsingDisable))))
http.HandleFunc("/control/safebrowsing/status", postInstall(optionalAuth(ensureGET(handleSafeBrowsingStatus))))

278
control_install.go Normal file
View File

@@ -0,0 +1,278 @@
package main
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"os/exec"
"strconv"
"github.com/AdguardTeam/golibs/log"
)
type firstRunData struct {
WebPort int `json:"web_port"`
DNSPort int `json:"dns_port"`
Interfaces map[string]interface{} `json:"interfaces"`
}
// Get initial installation settings
func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
log.Tracef("%s %v", r.Method, r.URL)
data := firstRunData{}
data.WebPort = 80
data.DNSPort = 53
ifaces, err := getValidNetInterfacesForWeb()
if err != nil {
httpError(w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err)
return
}
data.Interfaces = make(map[string]interface{})
for _, iface := range ifaces {
data.Interfaces[iface.Name] = iface
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(data)
if err != nil {
httpError(w, http.StatusInternalServerError, "Unable to marshal default addresses to json: %s", err)
return
}
}
type checkConfigReqEnt struct {
Port int `json:"port"`
IP string `json:"ip"`
Autofix bool `json:"autofix"`
}
type checkConfigReq struct {
Web checkConfigReqEnt `json:"web"`
DNS checkConfigReqEnt `json:"dns"`
}
type checkConfigRespEnt struct {
Status string `json:"status"`
CanAutofix bool `json:"can_autofix"`
}
type checkConfigResp struct {
Web checkConfigRespEnt `json:"web"`
DNS checkConfigRespEnt `json:"dns"`
}
// Check if ports are available, respond with results
func handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) {
log.Tracef("%s %v", r.Method, r.URL)
reqData := checkConfigReq{}
respData := checkConfigResp{}
err := json.NewDecoder(r.Body).Decode(&reqData)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err)
return
}
if reqData.Web.Port != 0 && reqData.Web.Port != config.BindPort {
err = checkPortAvailable(reqData.Web.IP, reqData.Web.Port)
if err != nil {
respData.Web.Status = fmt.Sprintf("%v", err)
}
}
if reqData.DNS.Port != 0 {
err = checkPacketPortAvailable(reqData.DNS.IP, reqData.DNS.Port)
if errorIsAddrInUse(err) {
canAutofix := checkDNSStubListener()
if canAutofix && reqData.DNS.Autofix {
err = disableDNSStubListener()
if err != nil {
log.Error("Couldn't disable DNSStubListener: %s", err)
}
err = checkPacketPortAvailable(reqData.DNS.IP, reqData.DNS.Port)
canAutofix = false
}
respData.DNS.CanAutofix = canAutofix
}
if err == nil {
err = checkPortAvailable(reqData.DNS.IP, reqData.DNS.Port)
}
if err != nil {
respData.DNS.Status = fmt.Sprintf("%v", err)
}
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(respData)
if err != nil {
httpError(w, http.StatusInternalServerError, "Unable to marshal JSON: %s", err)
return
}
}
// Check if DNSStubListener is active
func checkDNSStubListener() bool {
cmd := exec.Command("systemctl", "is-enabled", "systemd-resolved")
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
_, err := cmd.Output()
if err != nil || cmd.ProcessState.ExitCode() != 0 {
log.Error("command %s has failed: %v code:%d",
cmd.Path, err, cmd.ProcessState.ExitCode())
return false
}
cmd = exec.Command("grep", "-E", "#?DNSStubListener=yes", "/etc/systemd/resolved.conf")
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
_, err = cmd.Output()
if err != nil || cmd.ProcessState.ExitCode() != 0 {
log.Error("command %s has failed: %v code:%d",
cmd.Path, err, cmd.ProcessState.ExitCode())
return false
}
return true
}
// Deactivate DNSStubListener
func disableDNSStubListener() error {
cmd := exec.Command("sed", "-r", "-i.orig", "s/#?DNSStubListener=yes/DNSStubListener=no/g", "/etc/systemd/resolved.conf")
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
_, err := cmd.Output()
if err != nil {
return err
}
if cmd.ProcessState.ExitCode() != 0 {
return fmt.Errorf("process %s exited with an error: %d",
cmd.Path, cmd.ProcessState.ExitCode())
}
cmd = exec.Command("systemctl", "reload-or-restart", "systemd-resolved")
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
_, err = cmd.Output()
if err != nil {
return err
}
if cmd.ProcessState.ExitCode() != 0 {
return fmt.Errorf("process %s exited with an error: %d",
cmd.Path, cmd.ProcessState.ExitCode())
}
return nil
}
type applyConfigReqEnt struct {
IP string `json:"ip"`
Port int `json:"port"`
}
type applyConfigReq struct {
Web applyConfigReqEnt `json:"web"`
DNS applyConfigReqEnt `json:"dns"`
Username string `json:"username"`
Password string `json:"password"`
}
// Copy installation parameters between two configuration objects
func copyInstallSettings(dst *configuration, src *configuration) {
dst.BindHost = src.BindHost
dst.BindPort = src.BindPort
dst.DNS.BindHost = src.DNS.BindHost
dst.DNS.Port = src.DNS.Port
dst.AuthName = src.AuthName
dst.AuthPass = src.AuthPass
}
// Apply new configuration, start DNS server, restart Web server
func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
log.Tracef("%s %v", r.Method, r.URL)
newSettings := applyConfigReq{}
err := json.NewDecoder(r.Body).Decode(&newSettings)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to parse 'configure' JSON: %s", err)
return
}
if newSettings.Web.Port == 0 || newSettings.DNS.Port == 0 {
httpError(w, http.StatusBadRequest, "port value can't be 0")
return
}
restartHTTP := true
if config.BindHost == newSettings.Web.IP && config.BindPort == newSettings.Web.Port {
// no need to rebind
restartHTTP = false
}
// validate that hosts and ports are bindable
if restartHTTP {
err = checkPortAvailable(newSettings.Web.IP, newSettings.Web.Port)
if err != nil {
httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s due to %s",
net.JoinHostPort(newSettings.Web.IP, strconv.Itoa(newSettings.Web.Port)), err)
return
}
}
err = checkPacketPortAvailable(newSettings.DNS.IP, newSettings.DNS.Port)
if err != nil {
httpError(w, http.StatusBadRequest, "%s", err)
return
}
err = checkPortAvailable(newSettings.DNS.IP, newSettings.DNS.Port)
if err != nil {
httpError(w, http.StatusBadRequest, "%s", err)
return
}
var curConfig configuration
copyInstallSettings(&curConfig, &config)
config.firstRun = false
config.BindHost = newSettings.Web.IP
config.BindPort = newSettings.Web.Port
config.DNS.BindHost = newSettings.DNS.IP
config.DNS.Port = newSettings.DNS.Port
config.AuthName = newSettings.Username
config.AuthPass = newSettings.Password
err = startDNSServer()
if err != nil {
config.firstRun = true
copyInstallSettings(&config, &curConfig)
httpError(w, http.StatusInternalServerError, "Couldn't start DNS server: %s", err)
return
}
err = config.write()
if err != nil {
config.firstRun = true
copyInstallSettings(&config, &curConfig)
httpError(w, http.StatusInternalServerError, "Couldn't write config: %s", err)
return
}
go refreshFiltersIfNecessary(false)
// 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() {
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)))
}

183
dhcp.go
View File

@@ -2,14 +2,18 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"os/exec"
"runtime"
"strings"
"time"
"github.com/AdguardTeam/AdGuardHome/dhcpd"
"github.com/AdguardTeam/golibs/file"
"github.com/AdguardTeam/golibs/log"
"github.com/joomcode/errorx"
)
@@ -58,7 +62,17 @@ func handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
}
if newconfig.Enabled {
err := dhcpServer.Start(&newconfig)
staticIP, err := hasStaticIP(newconfig.InterfaceName)
if !staticIP && err == nil {
err = setStaticIP(newconfig.InterfaceName)
if err != nil {
httpError(w, http.StatusInternalServerError, "Failed to configure static IP: %s", err)
return
}
}
err = dhcpServer.Start(&newconfig)
if err != nil {
httpError(w, http.StatusBadRequest, "Failed to start DHCP server: %s", err)
return
@@ -130,6 +144,10 @@ func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
}
}
// Perform the following tasks:
// . Search for another DHCP server running
// . Check if a static IP is configured for the network interface
// Respond with results
func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) {
log.Tracef("%s %v", r.Method, r.URL)
body, err := ioutil.ReadAll(r.Body)
@@ -147,13 +165,35 @@ func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) {
http.Error(w, errorText, http.StatusBadRequest)
return
}
found, err := dhcpd.CheckIfOtherDHCPServersPresent(interfaceName)
result := map[string]interface{}{}
if err != nil {
result["error"] = err.Error()
} else {
result["found"] = found
othSrv := map[string]interface{}{}
foundVal := "no"
if found {
foundVal = "yes"
} else if err != nil {
foundVal = "error"
othSrv["error"] = err.Error()
}
othSrv["found"] = foundVal
staticIP := map[string]interface{}{}
isStaticIP, err := hasStaticIP(interfaceName)
staticIPStatus := "yes"
if err != nil {
staticIPStatus = "error"
staticIP["error"] = err.Error()
} else if !isStaticIP {
staticIPStatus = "no"
staticIP["ip"] = getFullIP(interfaceName)
}
staticIP["static"] = staticIPStatus
result := map[string]interface{}{}
result["other_server"] = othSrv
result["static_ip"] = staticIP
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(result)
if err != nil {
@@ -162,6 +202,137 @@ func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) {
}
}
// Check if network interface has a static IP configured
func hasStaticIP(ifaceName string) (bool, error) {
if runtime.GOOS == "windows" {
return false, errors.New("Can't detect static IP: not supported on Windows")
}
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
if err != nil {
return false, err
}
lines := strings.Split(string(body), "\n")
nameLine := fmt.Sprintf("interface %s", ifaceName)
withinInterfaceCtx := false
for _, line := range lines {
line = strings.TrimSpace(line)
if withinInterfaceCtx && len(line) == 0 {
// an empty line resets our state
withinInterfaceCtx = false
}
if len(line) == 0 || line[0] == '#' {
continue
}
line = strings.TrimSpace(line)
if !withinInterfaceCtx {
if line == nameLine {
// we found our interface
withinInterfaceCtx = true
}
} else {
if strings.HasPrefix(line, "interface ") {
// we found another interface - reset our state
withinInterfaceCtx = false
continue
}
if strings.HasPrefix(line, "static ip_address=") {
return true, nil
}
}
}
return false, nil
}
// Get IP address with netmask
func getFullIP(ifaceName string) string {
cmd := exec.Command("ip", "-oneline", "-family", "inet", "address", "show", ifaceName)
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
d, err := cmd.Output()
if err != nil || cmd.ProcessState.ExitCode() != 0 {
return ""
}
fields := strings.Fields(string(d))
if len(fields) < 4 {
return ""
}
_, _, err = net.ParseCIDR(fields[3])
if err != nil {
return ""
}
return fields[3]
}
// Get gateway IP address
func getGatewayIP(ifaceName string) string {
cmd := exec.Command("ip", "route", "show", "dev", ifaceName)
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
d, err := cmd.Output()
if err != nil || cmd.ProcessState.ExitCode() != 0 {
return ""
}
fields := strings.Fields(string(d))
if len(fields) < 3 || fields[0] != "default" {
return ""
}
ip := net.ParseIP(fields[2])
if ip == nil {
return ""
}
return fields[2]
}
// Set a static IP for network interface
func setStaticIP(ifaceName string) error {
ip := getFullIP(ifaceName)
if len(ip) == 0 {
return errors.New("Can't get IP address")
}
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
if err != nil {
return err
}
ip4, _, err := net.ParseCIDR(ip)
if err != nil {
return err
}
add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n",
ifaceName, ip)
body = append(body, []byte(add)...)
gatewayIP := getGatewayIP(ifaceName)
if len(gatewayIP) != 0 {
add = fmt.Sprintf("static routers=%s\n",
gatewayIP)
body = append(body, []byte(add)...)
}
add = fmt.Sprintf("static domain_name_servers=%s\n\n",
ip4)
body = append(body, []byte(add)...)
err = file.SafeWrite("/etc/dhcpcd.conf", body)
if err != nil {
return err
}
return nil
}
func startDHCPServer() error {
if !config.DHCP.Enabled {
// not enabled, don't do anything

View File

@@ -1,6 +1,7 @@
package dhcpd
import (
"bytes"
"crypto/rand"
"encoding/binary"
"fmt"
@@ -11,6 +12,7 @@ import (
"github.com/AdguardTeam/golibs/log"
"github.com/krolaw/dhcp4"
"golang.org/x/net/ipv4"
)
// CheckIfOtherDHCPServersPresent sends a DHCP request to the specified network interface,
@@ -32,13 +34,13 @@ func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) {
dst := "255.255.255.255:67"
// form a DHCP request packet, try to emulate existing client as much as possible
xID := make([]byte, 8)
xID := make([]byte, 4)
n, err := rand.Read(xID)
if n != 8 && err == nil {
err = fmt.Errorf("Generated less than 8 bytes")
if n != 4 && err == nil {
err = fmt.Errorf("Generated less than 4 bytes")
}
if err != nil {
return false, wrapErrPrint(err, "Couldn't generate 8 random bytes")
return false, wrapErrPrint(err, "Couldn't generate random bytes")
}
hostname, err := os.Hostname()
if err != nil {
@@ -89,58 +91,63 @@ func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) {
// bind to 0.0.0.0:68
log.Tracef("Listening to udp4 %+v", udpAddr)
c, err := net.ListenPacket("udp4", src)
c, err := newBroadcastPacketConn(net.IPv4(0, 0, 0, 0), 68, ifaceName)
if c != nil {
defer c.Close()
}
// spew.Dump(c, err)
// spew.Printf("net.ListenUDP returned %v, %v\n", c, err)
if err != nil {
return false, wrapErrPrint(err, "Couldn't listen to %s", src)
return false, wrapErrPrint(err, "Couldn't listen on :68")
}
// send to 255.255.255.255:67
_, err = c.WriteTo(packet, dstAddr)
cm := ipv4.ControlMessage{}
_, err = c.WriteTo(packet, &cm, dstAddr)
// spew.Dump(n, err)
if err != nil {
return false, wrapErrPrint(err, "Couldn't send a packet to %s", dst)
}
// wait for answer
log.Tracef("Waiting %v for an answer", defaultDiscoverTime)
// TODO: replicate dhclient's behaviour of retrying several times with progressively bigger timeouts
b := make([]byte, 1500)
c.SetReadDeadline(time.Now().Add(defaultDiscoverTime))
n, _, err = c.ReadFrom(b)
if isTimeout(err) {
// timed out -- no DHCP servers
return false, nil
}
if err != nil {
return false, wrapErrPrint(err, "Couldn't receive packet")
}
if n > 0 {
b = b[:n]
}
// spew.Dump(n, fromAddr, err, b)
for {
// wait for answer
log.Tracef("Waiting %v for an answer", defaultDiscoverTime)
// TODO: replicate dhclient's behaviour of retrying several times with progressively bigger timeouts
b := make([]byte, 1500)
_ = c.SetReadDeadline(time.Now().Add(defaultDiscoverTime))
n, _, _, err = c.ReadFrom(b)
if isTimeout(err) {
// timed out -- no DHCP servers
return false, nil
}
if err != nil {
return false, wrapErrPrint(err, "Couldn't receive packet")
}
// spew.Dump(n, fromAddr, err, b)
if n < 240 {
// packet too small for dhcp
return false, wrapErrPrint(err, "got packet that's too small for DHCP")
}
log.Tracef("Received packet (%v bytes)", n)
response := dhcp4.Packet(b[:n])
if response.HLen() > 16 {
// invalid size
return false, wrapErrPrint(err, "got malformed packet with HLen() > 16")
}
if n < 240 {
// packet too small for dhcp
continue
}
parsedOptions := response.ParseOptions()
_, ok := parsedOptions[dhcp4.OptionDHCPMessageType]
if !ok {
return false, wrapErrPrint(err, "got malformed packet without DHCP message type")
}
response := dhcp4.Packet(b[:n])
if response.OpCode() != dhcp4.BootReply ||
response.HType() != 1 /*Ethernet*/ ||
response.HLen() > 16 ||
!bytes.Equal(response.CHAddr(), iface.HardwareAddr) ||
!bytes.Equal(response.XId(), xID) {
continue
}
// that's a DHCP server there
return true, nil
parsedOptions := response.ParseOptions()
if t := parsedOptions[dhcp4.OptionDHCPMessageType]; len(t) != 1 {
continue //packet without DHCP message type
}
log.Tracef("The packet is from an active DHCP server")
// that's a DHCP server there
return true, nil
}
}

45
dhcpd/os_linux.go Normal file
View File

@@ -0,0 +1,45 @@
package dhcpd
import (
"net"
"os"
"syscall"
"golang.org/x/net/ipv4"
)
// Create a socket for receiving broadcast packets
func newBroadcastPacketConn(bindAddr net.IP, port int, ifname string) (*ipv4.PacketConn, error) {
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP)
if err != nil {
return nil, err
}
if err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1); err != nil {
return nil, err
}
if err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
return nil, err
}
if err := syscall.SetsockoptString(s, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, ifname); err != nil {
return nil, err
}
addr := syscall.SockaddrInet4{Port: port}
copy(addr.Addr[:], bindAddr.To4())
err = syscall.Bind(s, &addr)
if err != nil {
syscall.Close(s)
return nil, err
}
f := os.NewFile(uintptr(s), "")
c, err := net.FilePacketConn(f)
f.Close()
if err != nil {
return nil, err
}
p := ipv4.NewPacketConn(c)
return p, nil
}

44
dhcpd/os_unix.go Normal file
View File

@@ -0,0 +1,44 @@
// +build aix darwin dragonfly freebsd netbsd openbsd solaris
package dhcpd
import (
"net"
"os"
"syscall"
"golang.org/x/net/ipv4"
)
// Create a socket for receiving broadcast packets
func newBroadcastPacketConn(bindAddr net.IP, port int, ifname string) (*ipv4.PacketConn, error) {
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP)
if err != nil {
return nil, err
}
if err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1); err != nil {
return nil, err
}
if err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
return nil, err
}
addr := syscall.SockaddrInet4{Port: port}
copy(addr.Addr[:], bindAddr.To4())
err = syscall.Bind(s, &addr)
if err != nil {
syscall.Close(s)
return nil, err
}
f := os.NewFile(uintptr(s), "")
c, err := net.FilePacketConn(f)
f.Close()
if err != nil {
return nil, err
}
p := ipv4.NewPacketConn(c)
return p, nil
}

13
dhcpd/os_windows.go Normal file
View File

@@ -0,0 +1,13 @@
package dhcpd
import (
"errors"
"net"
"golang.org/x/net/ipv4"
)
// Create a socket for receiving broadcast packets
func newBroadcastPacketConn(bindAddr net.IP, port int, ifname string) (*ipv4.PacketConn, error) {
return nil, errors.New("newBroadcastPacketConn(): not supported on Windows")
}

5
dns.go
View File

@@ -50,6 +50,11 @@ func generateServerConfig() dnsforward.ServerConfig {
FilteringConfig: config.DNS.FilteringConfig,
Filters: filters,
}
bindhost := config.DNS.BindHost
if config.DNS.BindHost == "0.0.0.0" {
bindhost = "127.0.0.1"
}
newconfig.ResolverAddress = fmt.Sprintf("%s:%d", bindhost, config.DNS.Port)
if config.TLS.Enabled {
newconfig.TLSConfig = config.TLS.TLSConfig

View File

@@ -3,6 +3,7 @@ package dnsfilter
import (
"bufio"
"bytes"
"context"
"crypto/sha256"
"encoding/json"
"errors"
@@ -16,6 +17,7 @@ import (
"sync/atomic"
"time"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/log"
"github.com/bluele/gcache"
"golang.org/x/net/publicsuffix"
@@ -45,10 +47,11 @@ const enableDelayedCompilation = true // flag for debugging, must be true in pro
// Config allows you to configure DNS filtering with New() or just change variables directly.
type Config struct {
ParentalSensitivity int `yaml:"parental_sensitivity"` // must be either 3, 10, 13 or 17
ParentalEnabled bool `yaml:"parental_enabled"`
SafeSearchEnabled bool `yaml:"safesearch_enabled"`
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
ParentalSensitivity int `yaml:"parental_sensitivity"` // must be either 3, 10, 13 or 17
ParentalEnabled bool `yaml:"parental_enabled"`
SafeSearchEnabled bool `yaml:"safesearch_enabled"`
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
ResolverAddress string // DNS server address
}
type privateConfig struct {
@@ -180,6 +183,10 @@ func (d *Dnsfilter) CheckHost(host string) (Result, error) {
return Result{Reason: NotFilteredNotFound}, nil
}
host = strings.ToLower(host)
// prevent recursion
if host == d.parentalServer || host == d.safeBrowsingServer {
return Result{}, nil
}
// try filter lists first
result, err := d.matchHost(host)
@@ -644,15 +651,17 @@ func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
return Result{}, err
}
res.IP = addrs[0]
// The next bug may occurs: LookupIP returns DNS64 mapped ipv4 address with zero-prefix
for _, i := range addrs {
if ipv4 := i.To4(); ipv4 != nil && len(i) == net.IPv6len {
if ipv4 := i.To4(); ipv4 != nil {
res.IP = ipv4
break
}
}
if len(res.IP) == 0 {
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
}
// Cache result
err = safeSearchCache.Set(host, res)
if err != nil {
@@ -667,10 +676,6 @@ func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
defer timer.LogElapsed("SafeBrowsing HTTP lookup for %s", host)
}
// prevent recursion -- checking the host of safebrowsing server makes no sense
if host == d.safeBrowsingServer {
return Result{}, nil
}
format := func(hashparam string) string {
url := fmt.Sprintf(defaultSafebrowsingURL, d.safeBrowsingServer, hashparam)
return url
@@ -713,10 +718,6 @@ func (d *Dnsfilter) checkParental(host string) (Result, error) {
defer timer.LogElapsed("Parental HTTP lookup for %s", host)
}
// prevent recursion -- checking the host of parental safety server makes no sense
if host == d.parentalServer {
return Result{}, nil
}
format := func(hashparam string) string {
url := fmt.Sprintf(defaultParentalURL, d.parentalServer, hashparam, d.ParentalSensitivity)
return url
@@ -971,6 +972,51 @@ func (d *Dnsfilter) matchHost(host string) (Result, error) {
// lifecycle helper functions
//
type dialFunctionType func(ctx context.Context, network, addr string) (net.Conn, error)
// Connect to a remote server resolving hostname using our own DNS server
func createCustomDialContext(resolverAddr string) dialFunctionType {
return func(ctx context.Context, network, addr string) (net.Conn, error) {
log.Tracef("network:%v addr:%v", network, addr)
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
dialer := &net.Dialer{
Timeout: time.Minute * 5,
}
if net.ParseIP(host) != nil {
con, err := dialer.DialContext(ctx, network, addr)
return con, err
}
r := upstream.NewResolver(resolverAddr, 30*time.Second)
addrs, e := r.LookupIPAddr(ctx, host)
log.Tracef("LookupIPAddr: %s: %v", host, addrs)
if e != nil {
return nil, e
}
var firstErr error
firstErr = nil
for _, a := range addrs {
addr = fmt.Sprintf("%s:%s", a.String(), port)
con, err := dialer.DialContext(ctx, network, addr)
if err != nil {
if firstErr == nil {
firstErr = err
}
continue
}
return con, err
}
return nil, firstErr
}
}
// New creates properly initialized DNS Filter that is ready to be used
func New(c *Config) *Dnsfilter {
d := new(Dnsfilter)
@@ -990,6 +1036,9 @@ func New(c *Config) *Dnsfilter {
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
if c != nil && len(c.ResolverAddress) != 0 {
d.transport.DialContext = createCustomDialContext(c.ResolverAddress)
}
d.client = http.Client{
Transport: d.transport,
Timeout: defaultHTTPTimeout,

View File

@@ -14,6 +14,10 @@ var safeSearchDomains = map[string]string{
"www.bing.com": "strict.bing.com",
"duckduckgo.com": "safe.duckduckgo.com",
"www.duckduckgo.com": "safe.duckduckgo.com",
"start.duckduckgo.com": "safe.duckduckgo.com",
"www.google.com": "forcesafesearch.google.com",
"www.google.ad": "forcesafesearch.google.com",
"www.google.ae": "forcesafesearch.google.com",

View File

@@ -175,7 +175,10 @@ func (s *Server) startInternal(config *ServerConfig) error {
if err != nil {
return errorx.Decorate(err, "Failed to parse TLS keypair")
}
proxyConfig.TLSConfig = &tls.Config{Certificates: []tls.Certificate{keypair}}
proxyConfig.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{keypair},
MinVersion: tls.VersionTLS12,
}
}
if proxyConfig.UDPListenAddr == nil {

View File

@@ -101,7 +101,11 @@ func TestDotServer(t *testing.T) {
// Add our self-signed generated config to roots
roots := x509.NewCertPool()
roots.AppendCertsFromPEM(certPem)
tlsConfig := &tls.Config{ServerName: tlsServerName, RootCAs: roots}
tlsConfig := &tls.Config{
ServerName: tlsServerName,
RootCAs: roots,
MinVersion: tls.VersionTLS12,
}
// Create a DNS-over-TLS client connection
addr := s.dnsProxy.Addr(proxy.ProtoTLS)
@@ -459,7 +463,7 @@ func createServerTLSConfig(t *testing.T) (*tls.Config, []byte, []byte) {
t.Fatalf("failed to create certificate: %s", err)
}
return &tls.Config{Certificates: []tls.Certificate{cert}, ServerName: tlsServerName}, certPem, keyPem
return &tls.Config{Certificates: []tls.Certificate{cert}, ServerName: tlsServerName, MinVersion: tls.VersionTLS12}, certPem, keyPem
}
func createDataDir(t *testing.T) string {

View File

@@ -178,6 +178,10 @@ func periodicallyRefreshFilters() {
func refreshFiltersIfNecessary(force bool) int {
var updateFilters []filter
if config.firstRun {
return 0
}
config.RLock()
for i := range config.Filters {
f := &config.Filters[i] // otherwise we will be operating on a copy
@@ -193,6 +197,7 @@ func refreshFiltersIfNecessary(force bool) int {
var uf filter
uf.ID = f.ID
uf.URL = f.URL
uf.Name = f.Name
uf.checksum = f.checksum
updateFilters = append(updateFilters, uf)
}

3
go.mod
View File

@@ -5,6 +5,7 @@ go 1.12
require (
github.com/AdguardTeam/dnsproxy v0.12.0
github.com/AdguardTeam/golibs v0.1.3
github.com/NYTimes/gziphandler v1.1.1
github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f // indirect
github.com/bluele/gcache v0.0.0-20190203144525-2016d595ccb0
github.com/go-ole/go-ole v1.2.1 // indirect
@@ -18,7 +19,7 @@ require (
github.com/shirou/gopsutil v2.18.10+incompatible
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
github.com/sparrc/go-ping v0.0.0-20181106165434-ef3ab45e41b0
github.com/stretchr/testify v1.2.2
github.com/stretchr/testify v1.3.0
go.uber.org/goleak v0.10.0
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e
golang.org/x/sys v0.0.0-20190122071731-054c452bb702

6
go.sum
View File

@@ -3,6 +3,8 @@ github.com/AdguardTeam/dnsproxy v0.12.0/go.mod h1:lcZM2QPwcWGEL3pz8RYy06nQdbjj4p
github.com/AdguardTeam/golibs v0.1.2/go.mod h1:b0XkhgIcn2TxwX6C5AQMtpIFAgjPehNgxJErWkwA3ko=
github.com/AdguardTeam/golibs v0.1.3 h1:hmapdTtMtIk3T8eQDwTOLdqZLGDKNKk9325uC8z12xg=
github.com/AdguardTeam/golibs v0.1.3/go.mod h1:b0XkhgIcn2TxwX6C5AQMtpIFAgjPehNgxJErWkwA3ko=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f h1:5ZfJxyXo8KyX8DgGXC5B7ILL8y51fci/qYz2B4j8iLY=
github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
@@ -17,6 +19,7 @@ github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 h1:KXlsf+qt/X5ttP
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
github.com/bluele/gcache v0.0.0-20190203144525-2016d595ccb0 h1:vUdUwmQLnT/yuk8PsDhhMVkrfr4aMdcv/0GWzIqOjEY=
github.com/bluele/gcache v0.0.0-20190203144525-2016d595ccb0/go.mod h1:8c4/i2VlovMO2gBnHGQPN5EJw+H0lx1u/5p+cgsXtCk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
@@ -62,8 +65,11 @@ github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
go.uber.org/goleak v0.10.0 h1:G3eWbSNIskeRqtsN/1uI5B+eP73y3JUuBsv9AZjehb4=
go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=

View File

@@ -2,6 +2,7 @@ package main
import (
"bufio"
"context"
"errors"
"fmt"
"io"
@@ -14,7 +15,11 @@ import (
"runtime"
"strconv"
"strings"
"syscall"
"time"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/log"
"github.com/joomcode/errorx"
)
@@ -45,12 +50,19 @@ func ensureGET(handler func(http.ResponseWriter, *http.Request)) func(http.Respo
return ensure("GET", handler)
}
func ensurePUT(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return ensure("PUT", handler)
// Bridge between http.Handler object and Go function
type httpHandler struct {
handler func(http.ResponseWriter, *http.Request)
}
func ensureDELETE(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return ensure("DELETE", handler)
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.handler(w, r)
}
func ensureGETHandler(handler func(http.ResponseWriter, *http.Request)) http.Handler {
h := httpHandler{}
h.handler = ensure("GET", handler)
return &h
}
func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
@@ -293,6 +305,77 @@ func checkPacketPortAvailable(host string, port int) error {
return err
}
// Connect to a remote server resolving hostname using our own DNS server
func customDialContext(ctx context.Context, network, addr string) (net.Conn, error) {
log.Tracef("network:%v addr:%v", network, addr)
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
dialer := &net.Dialer{
Timeout: time.Minute * 5,
}
if net.ParseIP(host) != nil {
con, err := dialer.DialContext(ctx, network, addr)
return con, err
}
bindhost := config.DNS.BindHost
if config.DNS.BindHost == "0.0.0.0" {
bindhost = "127.0.0.1"
}
resolverAddr := fmt.Sprintf("%s:%d", bindhost, config.DNS.Port)
r := upstream.NewResolver(resolverAddr, 30*time.Second)
addrs, e := r.LookupIPAddr(ctx, host)
log.Tracef("LookupIPAddr: %s: %v", host, addrs)
if e != nil {
return nil, e
}
var firstErr error
firstErr = nil
for _, a := range addrs {
addr = fmt.Sprintf("%s:%s", a.String(), port)
con, err := dialer.DialContext(ctx, network, addr)
if err != nil {
if firstErr == nil {
firstErr = err
}
continue
}
return con, err
}
return nil, firstErr
}
// check if error is "address already in use"
func errorIsAddrInUse(err error) bool {
errOpError, ok := err.(*net.OpError)
if !ok {
return false
}
errSyscallError, ok := errOpError.Err.(*os.SyscallError)
if !ok {
return false
}
errErrno, ok := errSyscallError.Err.(syscall.Errno)
if !ok {
return false
}
if runtime.GOOS == "windows" {
const WSAEADDRINUSE = 10048
return errErrno == WSAEADDRINUSE
}
return errErrno == syscall.EADDRINUSE
}
// ---------------------
// debug logging helpers
// ---------------------

View File

@@ -2,7 +2,7 @@ swagger: '2.0'
info:
title: 'AdGuard Home'
description: 'AdGuard Home REST API. Admin web interface is built on top of this REST API.'
version: 0.94.0
version: 0.95.0
schemes:
- http
basePath: /control
@@ -739,6 +739,26 @@ paths:
description: OK
schema:
$ref: "#/definitions/AddressesInfo"
/install/check_config:
post:
tags:
- install
operationId: installCheckConfig
summary: "Checks configuration"
parameters:
- in: "body"
name: "body"
description: "Configuration to be checked"
required: true
schema:
$ref: "#/definitions/CheckConfigRequest"
responses:
200:
description: OK
schema:
$ref: "#/definitions/CheckConfigResponse"
400:
description: "Failed to parse JSON or cannot listen on the specified address"
/install/configure:
post:
tags:
@@ -1122,14 +1142,33 @@ definitions:
DhcpSearchResult:
type: "object"
description: "Information about a DHCP server discovered in the current network"
required:
- "found"
properties:
other_server:
$ref: "#/definitions/DhcpSearchResultOtherServer"
static_ip:
$ref: "#/definitions/DhcpSearchResultStaticIP"
DhcpSearchResultOtherServer:
type: "object"
properties:
found:
type: "boolean"
gateway_ip:
type: "string"
example: "192.168.1.1"
description: "yes|no|error"
example: "no"
error:
type: "string"
description: "Set if found=error"
example: ""
DhcpSearchResultStaticIP:
type: "object"
properties:
static:
type: "string"
description: "yes|no|error"
example: "yes"
ip:
type: "string"
description: "Set if static=no"
example: ""
DnsAnswer:
type: "object"
description: "DNS answer section"
@@ -1320,17 +1359,18 @@ definitions:
type: "integer"
format: "int32"
example: 53
warning:
type: "string"
example: "Cannot bind to this port"
AddressesInfo:
type: "object"
description: "AdGuard Home addresses configuration"
properties:
dns:
$ref: "#/definitions/AddressInfo"
web:
$ref: "#/definitions/AddressInfo"
dns_port:
type: "integer"
format: "int32"
example: 53
web_port:
type: "integer"
format: "int32"
example: 80
interfaces:
type: "object"
description: "Network interfaces dictionary (key is the interface name)"
@@ -1353,6 +1393,43 @@ definitions:
items:
$ref: "#/definitions/Client"
description: "Clients array"
CheckConfigRequest:
type: "object"
description: "Configuration to be checked"
properties:
dns:
$ref: "#/definitions/CheckConfigRequestInfo"
web:
$ref: "#/definitions/CheckConfigRequestInfo"
CheckConfigRequestInfo:
type: "object"
properties:
ip:
type: "string"
example: "127.0.0.1"
port:
type: "integer"
format: "int32"
example: 53
autofix:
type: "boolean"
example: false
CheckConfigResponse:
type: "object"
properties:
dns:
$ref: "#/definitions/CheckConfigResponseInfo"
web:
$ref: "#/definitions/CheckConfigResponseInfo"
CheckConfigResponseInfo:
type: "object"
properties:
status:
type: "string"
example: ""
can_autofix:
type: "boolean"
example: false
InitialConfiguration:
type: "object"
description: "AdGuard Home initial configuration (for the first-install wizard)"

27
os_unix.go Normal file
View File

@@ -0,0 +1,27 @@
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package main
import (
"os"
"syscall"
"github.com/AdguardTeam/golibs/log"
)
// Set user-specified limit of how many fd's we can use
// https://github.com/AdguardTeam/AdGuardHome/issues/659
func setRlimit(val uint) {
var rlim syscall.Rlimit
rlim.Max = uint64(val)
rlim.Cur = uint64(val)
err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim)
if err != nil {
log.Error("Setrlimit() failed: %v", err)
}
}
// Check if the current user has root (administrator) rights
func haveAdminRights() (bool, error) {
return os.Getuid() == 0, nil
}

28
os_windows.go Normal file
View File

@@ -0,0 +1,28 @@
package main
import "golang.org/x/sys/windows"
// Set user-specified limit of how many fd's we can use
func setRlimit(val uint) {
}
func haveAdminRights() (bool, error) {
var token windows.Token
h, _ := windows.GetCurrentProcess()
err := windows.OpenProcessToken(h, windows.TOKEN_QUERY, &token)
if err != nil {
return false, err
}
info := make([]byte, 4)
var returnedLen uint32
err = windows.GetTokenInformation(token, windows.TokenElevation, &info[0], uint32(len(info)), &returnedLen)
token.Close()
if err != nil {
return false, err
}
if info[0] == 0 {
return false, nil
}
return true, nil
}

View File

@@ -32,6 +32,7 @@ func (p *program) Start(s service.Service) error {
func (p *program) Stop(s service.Service) error {
// Stop should not block. Return with a few seconds.
cleanup()
cleanupAlways()
os.Exit(0)
return nil
}
@@ -107,6 +108,15 @@ func handleServiceControlAction(action string) {
log.Fatalf("Failed to start the service: %s", err)
}
log.Printf("Service has been started")
if detectFirstRun() {
log.Printf(`Almost ready!
AdGuard Home is successfully installed and will automatically start on boot.
There are a few more things that must be configured before you can use it.
Click on the link below and follow the Installation Wizard steps to finish setup.`)
printHTTPAddresses("http")
}
} else if action == "uninstall" {
cleanupService()
}

View File

@@ -1,15 +1,15 @@
{
"version": "v0.94",
"announcement": "AdGuard Home v0.94 is now available!",
"announcement_url": "https://github.com/AdguardTeam/AdGuardHome/releases/tag/v0.94",
"download_windows_amd64": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.94/AdGuardHome_v0.94_Windows_amd64.zip",
"download_windows_386": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.94/AdGuardHome_v0.94_Windows_386.zip",
"download_darwin_amd64": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.94/AdGuardHome_v0.94_MacOS.zip",
"download_linux_amd64": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.94/AdGuardHome_v0.94_linux_amd64.tar.gz",
"download_linux_386": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.94/AdGuardHome_v0.94_linux_386.tar.gz",
"download_linux_arm": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.94/AdGuardHome_v0.94_linux_arm.tar.gz",
"download_linux_arm64": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.94/AdGuardHome_v0.94_linux_arm64.tar.gz",
"download_linux_mips": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.94/AdGuardHome_v0.94_linux_mips.tar.gz",
"download_linux_mipsle": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.94/AdGuardHome_v0.94_linux_mipsle.tar.gz",
"version": "v0.95-hotfix",
"announcement": "AdGuard Home v0.95-hotfix is now available!",
"announcement_url": "https://github.com/AdguardTeam/AdGuardHome/releases/tag/v0.95-hotfix",
"download_windows_amd64": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.95-hotfix/AdGuardHome_v0.95-hotfix_Windows_amd64.zip",
"download_windows_386": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.95-hotfix/AdGuardHome_v0.95-hotfix_Windows_386.zip",
"download_darwin_amd64": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.95-hotfix/AdGuardHome_v0.95-hotfix_MacOS.zip",
"download_linux_amd64": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.95-hotfix/AdGuardHome_v0.95-hotfix_linux_amd64.tar.gz",
"download_linux_386": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.95-hotfix/AdGuardHome_v0.95-hotfix_linux_386.tar.gz",
"download_linux_arm": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.95-hotfix/AdGuardHome_v0.95-hotfix_linux_arm.tar.gz",
"download_linux_arm64": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.95-hotfix/AdGuardHome_v0.95-hotfix_linux_arm64.tar.gz",
"download_linux_mips": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.95-hotfix/AdGuardHome_v0.95-hotfix_linux_mips.tar.gz",
"download_linux_mipsle": "https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.95-hotfix/AdGuardHome_v0.95-hotfix_linux_mipsle.tar.gz",
"selfupdate_min_version": "v0.0"
}