Merge branch 'master' of github.com:AdguardTeam/AdGuardHome

 Conflicts:
	openapi/openapi.yaml
This commit is contained in:
Jonas Marklén 2020-05-02 10:09:25 +02:00
commit efd2b31104
No known key found for this signature in database
GPG Key ID: F19F3AA368016FEE
54 changed files with 2009 additions and 747 deletions

6
.gitignore vendored
View File

@ -17,3 +17,9 @@ coverage.txt
# Test output # Test output
dnsfilter/tests/top-1m.csv dnsfilter/tests/top-1m.csv
dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof
# Snapcraft build temporary files
*.snap
launchpad_credentials
snapcraft_login
snapcraft.yaml.bak

View File

@ -13,6 +13,7 @@ run:
skip-files: skip-files:
- ".*generated.*" - ".*generated.*"
- dnsfilter/rule_to_regexp.go - dnsfilter/rule_to_regexp.go
- util/pprof.go
- ".*_test.go" - ".*_test.go"
@ -65,5 +66,7 @@ issues:
- Error return value of ..*.Shutdown. - Error return value of ..*.Shutdown.
# goconst # goconst
- string .forcesafesearch.google.com. has 3 occurrences - string .forcesafesearch.google.com. has 3 occurrences
# gosec: Profiling endpoint is automatically exposed on /debug/pprof
- G108
# gosec: Subprocess launched with function call as argument or cmd arguments # gosec: Subprocess launched with function call as argument or cmd arguments
- G204 - G204

View File

@ -86,7 +86,7 @@ matrix:
- node -v - node -v
- npm -v - npm -v
# Prepare releases # Prepare releases
- ./release.sh - ./build_release.sh
- ls -l dist - ls -l dist
deploy: deploy:

View File

@ -882,6 +882,9 @@ Response:
200 OK 200 OK
{ {
"upstream_dns": ["tls://...", ...],
"bootstrap_dns": ["1.2.3.4", ...],
"protection_enabled": true | false, "protection_enabled": true | false,
"ratelimit": 1234, "ratelimit": 1234,
"blocking_mode": "default" | "nxdomain" | "null_ip" | "custom_ip", "blocking_mode": "default" | "nxdomain" | "null_ip" | "custom_ip",
@ -890,6 +893,8 @@ Response:
"edns_cs_enabled": true | false, "edns_cs_enabled": true | false,
"dnssec_enabled": true | false "dnssec_enabled": true | false
"disable_ipv6": true | false, "disable_ipv6": true | false,
"fastest_addr": true | false, // use Fastest Address algorithm
"parallel_requests": true | false, // send DNS requests to all upstream servers at once
} }
@ -900,6 +905,9 @@ Request:
POST /control/dns_config POST /control/dns_config
{ {
"upstream_dns": ["tls://...", ...],
"bootstrap_dns": ["1.2.3.4", ...],
"protection_enabled": true | false, "protection_enabled": true | false,
"ratelimit": 1234, "ratelimit": 1234,
"blocking_mode": "default" | "nxdomain" | "null_ip" | "custom_ip", "blocking_mode": "default" | "nxdomain" | "null_ip" | "custom_ip",
@ -908,6 +916,8 @@ Request:
"edns_cs_enabled": true | false, "edns_cs_enabled": true | false,
"dnssec_enabled": true | false "dnssec_enabled": true | false
"disable_ipv6": true | false, "disable_ipv6": true | false,
"fastest_addr": true | false, // use Fastest Address algorithm
"parallel_requests": true | false, // send DNS requests to all upstream servers at once
} }
Response: Response:

View File

@ -5,6 +5,9 @@ GOPATH := $(shell go env GOPATH)
JSFILES = $(shell find client -path client/node_modules -prune -o -type f -name '*.js') JSFILES = $(shell find client -path client/node_modules -prune -o -type f -name '*.js')
STATIC = build/static/index.html STATIC = build/static/index.html
CHANNEL ?= release CHANNEL ?= release
DOCKER_IMAGE_DEV_NAME=adguardhome-dev
DOCKERFILE=packaging/docker/Dockerfile
DOCKERFILE_HUB=packaging/docker/Dockerfile.travis
TARGET=AdGuardHome TARGET=AdGuardHome
@ -26,6 +29,11 @@ $(TARGET): $(STATIC) *.go home/*.go dhcpd/*.go dnsfilter/*.go dnsforward/*.go
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(GIT_VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)" -asmflags="-trimpath=$(PWD)" -gcflags="-trimpath=$(PWD)" CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(GIT_VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)" -asmflags="-trimpath=$(PWD)" -gcflags="-trimpath=$(PWD)"
PATH=$(GOPATH)/bin:$(PATH) packr clean PATH=$(GOPATH)/bin:$(PATH) packr clean
docker:
docker build -t "$(DOCKER_IMAGE_DEV_NAME)" -f "$(DOCKERFILE)" .
@echo Now you can run the docker image:
@echo docker run --name "$(DOCKER_IMAGE_DEV_NAME)" -p 53:53/tcp -p 53:53/udp -p 3000:3000/tcp $(DOCKER_IMAGE_DEV_NAME)
clean: clean:
$(MAKE) cleanfast $(MAKE) cleanfast
rm -rf build rm -rf build

View File

@ -26,9 +26,19 @@
<a href="https://golangci.com/r/github.com/AdguardTeam/AdGuardHome"> <a href="https://golangci.com/r/github.com/AdguardTeam/AdGuardHome">
<img src="https://golangci.com/badges/github.com/AdguardTeam/AdGuardHome.svg" alt="GolangCI" /> <img src="https://golangci.com/badges/github.com/AdguardTeam/AdGuardHome.svg" alt="GolangCI" />
</a> </a>
<br />
<a href="https://github.com/AdguardTeam/AdGuardHome/releases"> <a href="https://github.com/AdguardTeam/AdGuardHome/releases">
<img src="https://img.shields.io/github/release/AdguardTeam/AdGuardHome/all.svg" alt="Latest release" /> <img src="https://img.shields.io/github/release/AdguardTeam/AdGuardHome/all.svg" alt="Latest release" />
</a> </a>
<a href="https://snapcraft.io/adguard-home">
<img alt="adguard-home" src="https://snapcraft.io/adguard-home/badge.svg" />
</a>
<a href="https://hub.docker.com/r/adguard/adguardhome">
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/adguard/adguardhome.svg?maxAge=604800" />
</a>
<a href="https://hub.docker.com/r/adguard/adguardhome">
<img alt="Docker Stars" src="https://img.shields.io/docker/stars/adguard/adguardhome.svg?maxAge=604800" />
</a>
</p> </p>
<br /> <br />
@ -59,7 +69,9 @@ It operates as a DNS server that re-routes tracking domains to a "black hole," t
<a id="getting-started"></a> <a id="getting-started"></a>
## Getting Started ## Getting Started
Please read the [Getting Started](https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started) article on our Wiki to learn how to install AdGuard Home, and how to configure your devices to use it. Please read the **[Getting Started](https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started)** article on our Wiki to learn how to install AdGuard Home, and how to configure your devices to use it.
If you're running **Linux**, there's a secure and easy way to install AdGuard Home - you can get it from the [Snap Store](https://snapcraft.io/adguard-home).
Alternatively, you can use our [official Docker image](https://hub.docker.com/r/adguard/adguardhome). Alternatively, you can use our [official Docker image](https://hub.docker.com/r/adguard/adguardhome).
@ -69,6 +81,7 @@ Alternatively, you can use our [official Docker image](https://hub.docker.com/r/
* [AdGuard Home as a DNS-over-HTTPS or DNS-over-TLS server](https://github.com/AdguardTeam/AdGuardHome/wiki/Encryption) * [AdGuard Home as a DNS-over-HTTPS or DNS-over-TLS server](https://github.com/AdguardTeam/AdGuardHome/wiki/Encryption)
* [How to install and run AdGuard Home on Raspberry Pi](https://github.com/AdguardTeam/AdGuardHome/wiki/Raspberry-Pi) * [How to install and run AdGuard Home on Raspberry Pi](https://github.com/AdguardTeam/AdGuardHome/wiki/Raspberry-Pi)
* [How to install and run AdGuard Home on a Virtual Private Server](https://github.com/AdguardTeam/AdGuardHome/wiki/VPS) * [How to install and run AdGuard Home on a Virtual Private Server](https://github.com/AdguardTeam/AdGuardHome/wiki/VPS)
* [How to write your own hosts blocklists properly](https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists)
### API ### API
@ -165,11 +178,12 @@ You are welcome to fork this repository, make your changes and submit a pull req
<a id="test-unstable-versions"></a> <a id="test-unstable-versions"></a>
### Test unstable versions ### Test unstable versions
There are two options how you can install an unstable version. There are three options how you can install an unstable version.
You can either install a beta version of AdGuard Home which we update periodically,
or you can use the Docker image from the `edge` tag, which is synced with the repo master branch. 1. You can either install a beta version of AdGuard Home which we update periodically.
2. You can use the Docker image from the `edge` tag, which is synced with the repo master branch.
3. You can install AdGuard Home from `beta` or `edge` channels on the Snap Store.
* [Docker Hub](https://hub.docker.com/r/adguard/adguardhome)
* Beta builds * Beta builds
* [Raspberry Pi (32-bit ARMv6)](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm.tar.gz) * [Raspberry Pi (32-bit ARMv6)](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm.tar.gz)
* [MacOS](https://static.adguard.com/adguardhome/beta/AdGuardHome_MacOS.zip) * [MacOS](https://static.adguard.com/adguardhome/beta/AdGuardHome_MacOS.zip)
@ -182,6 +196,8 @@ or you can use the Docker image from the `edge` tag, which is synced with the re
* [Linux 32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz) * [Linux 32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz)
* [MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips.tar.gz) * [MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips.tar.gz)
* [MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle.tar.gz) * [MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle.tar.gz)
* [Docker Hub](https://hub.docker.com/r/adguard/adguardhome)
* [Snap Store](https://snapcraft.io/adguard-home)
<a id="reporting-issues"></a> <a id="reporting-issues"></a>
### Report issues ### Report issues

View File

@ -4,7 +4,7 @@ set -eE
set -o pipefail set -o pipefail
set -x set -x
DOCKERFILE="Dockerfile.travis" DOCKERFILE="packaging/docker/Dockerfile.hub"
IMAGE_NAME="adguard/adguardhome" IMAGE_NAME="adguard/adguardhome"
if [[ "${TRAVIS_BRANCH}" == "master" ]] if [[ "${TRAVIS_BRANCH}" == "master" ]]

282
build_snap.sh Executable file
View File

@ -0,0 +1,282 @@
#!/usr/bin/env bash
set -eE
set -o pipefail
set -x
BUILDER_IMAGE="adguard/snapcraft:1.0"
SNAPCRAFT_TMPL="packaging/snap/snapcraft.yaml"
SNAP_NAME="adguard-home"
LAUNCHPAD_CREDENTIALS_DIR=".local/share/snapcraft/provider/launchpad"
if [[ -z ${VERSION} ]]; then
VERSION=`git describe --abbrev=4 --dirty --always --tags`
echo "VERSION env variable is not set, getting it from git: ${VERSION}"
fi
# If bash is interactive, set `-it` parameter for docker run
INTERACTIVE=""
if [ -t 0 ] ; then
INTERACTIVE="-it"
fi
function usage() {
cat <<EOF
Usage: ${0##*/} command [options]
Please note that in order for the builds to work properly, you need to setup some env variables.
These are necessary for "remote-build' command.
Read this doc on how to generate them: https://uci.readthedocs.io/en/latest/oauth.html
* LAUNCHPAD_KEY -- launchpad CI key
* LAUNCHPAD_ACCESS_TOKEN -- launchpad access token
* LAUNCHPAD_ACCESS_SECRET -- launchpad access secret
These are necessary for snapcraft publish command to work.
They can be exported using "snapcraft export-login"
* SNAPCRAFT_MACAROON
* SNAPCRAFT_UBUNTU_DISCHARGE
* SNAPCRAFT_EMAIL
Examples:
${0##*/} build-docker - builds snaps using remote-build inside a Docker environment
${0##*/} build - builds snaps using remote-build
${0##*/} publish-docker-beta - publishes snaps to the beta channel using Docker environment
${0##*/} publish-docker-release - publishes snaps to the release channel using Docker environment
${0##*/} publish-beta - publishes snaps to the beta channel
${0##*/} publish-release - publishes snaps to the release channel
${0##*/} cleanup - clean up temporary files that were created by the builds
EOF
exit 1
}
#######################################
# helper functions
#######################################
function prepare() {
if [ -z "${LAUNCHPAD_KEY}" ] || [ -z "${LAUNCHPAD_ACCESS_TOKEN}" ] || [ -z "${LAUNCHPAD_ACCESS_SECRET}" ]; then
echo "Launchpad oauth tokens are not set, exiting"
usage
exit 1
fi
if [ -z "${SNAPCRAFT_MACAROON}" ] || [ -z "${SNAPCRAFT_UBUNTU_DISCHARGE}" ] || [ -z "${SNAPCRAFT_EMAIL}" ]; then
echo "Snapcraft auth params are not set, exiting"
usage
exit 1
fi
# Launchpad oauth tokens data is necessary to run snapcraft remote-build
#
# Here's an instruction on how to generate launchpad OAuth tokens:
# https://uci.readthedocs.io/en/latest/oauth.html
#
# Launchpad credentials are necessary to run snapcraft remote-build command
echo "[1]
consumer_key = ${LAUNCHPAD_KEY}
consumer_secret =
access_token = ${LAUNCHPAD_ACCESS_TOKEN}
access_secret = ${LAUNCHPAD_ACCESS_SECRET}
" > launchpad_credentials
# Snapcraft login data
# It can be exported using snapcraft export-login command
echo "[login.ubuntu.com]
macaroon = ${SNAPCRAFT_MACAROON}
unbound_discharge = ${SNAPCRAFT_UBUNTU_DISCHARGE}
email = ${SNAPCRAFT_EMAIL}" > snapcraft_login
# Prepare the snap configuration
cp ${SNAPCRAFT_TMPL} ./snapcraft.yaml
sed -i.bak 's/dev_version/'"${VERSION}"'/g' ./snapcraft.yaml
rm -f snapcraft.yaml.bak
}
build_snap() {
# prepare credentials
prepare
# copy them to the directory where snapcraft will be able to read them
mkdir -p ~/${LAUNCHPAD_CREDENTIALS_DIR}
cp -f snapcraft_login ~/${LAUNCHPAD_CREDENTIALS_DIR}/credentials
chmod 600 ~/${LAUNCHPAD_CREDENTIALS_DIR}/credentials
# run the build
snapcraft remote-build --build-on=${ARCH} --launchpad-accept-public-upload
# remove the credentials - we don't need them anymore
rm -rf ~/${LAUNCHPAD_CREDENTIALS_DIR}
# remove version from the file name
rename_snap_file
# cleanup credentials
cleanup
}
build_snap_docker() {
# prepare credentials
prepare
docker run ${INTERACTIVE} --rm \
-v $(pwd):/build \
-v $(pwd)/launchpad_credentials:/root/${LAUNCHPAD_CREDENTIALS_DIR}/credentials:ro \
${BUILDER_IMAGE} \
snapcraft remote-build --build-on=${ARCH} --launchpad-accept-public-upload
# remove version from the file name
rename_snap_file
# cleanup credentials
cleanup
}
rename_snap_file() {
# In order to make working with snaps easier later on
# we remove version from the file name
# Check that the snap file exists
snapFile="${SNAP_NAME}_${VERSION}_${ARCH}.snap"
if [ ! -f ${snapFile} ]; then
echo "Snap file ${snapFile} not found!"
exit 1
fi
mv -f ${snapFile} "${SNAP_NAME}_${ARCH}.snap"
}
publish_snap() {
# prepare credentials
prepare
# Check that the snap file exists
snapFile="${SNAP_NAME}_${ARCH}.snap"
if [ ! -f ${snapFile} ]; then
echo "Snap file ${snapFile} not found!"
exit 1
fi
# Login if necessary
snapcraft login --with=snapcraft_login
# Push to the channel
snapcraft push --release=${CHANNEL} ${snapFile}
# cleanup credentials
cleanup
}
publish_snap_docker() {
# prepare credentials
prepare
# Check that the snap file exists
snapFile="${SNAP_NAME}_${ARCH}.snap"
if [ ! -f ${snapFile} ]; then
echo "Snap file ${snapFile} not found!"
exit 1
fi
# Login and publish the snap
docker run ${INTERACTIVE} --rm \
-v $(pwd):/build \
${BUILDER_IMAGE} \
sh -c "snapcraft login --with=/build/snapcraft_login && snapcraft push --release=${CHANNEL} /build/${snapFile}"
# cleanup credentials
cleanup
}
#######################################
# main functions
#######################################
build() {
if [[ -n "$1" ]]; then
echo "ARCH is set to $1"
ARCH=$1 build_snap
else
ARCH=i386 build_snap
ARCH=arm64 build_snap
ARCH=armhf build_snap
ARCH=amd64 build_snap
fi
}
build_docker() {
if [[ -n "$1" ]]; then
echo "ARCH is set to $1"
ARCH=$1 build_snap_docker
else
ARCH=i386 build_snap_docker
ARCH=arm64 build_snap_docker
ARCH=armhf build_snap_docker
ARCH=amd64 build_snap_docker
fi
}
publish_docker() {
if [[ -z $1 ]]; then
echo "No channel specified"
exit 1
fi
CHANNEL="${1}"
if [ "$CHANNEL" != "stable" ] && [ "$CHANNEL" != "beta" ]; then
echo "$CHANNEL is an invalid value for the update channel!"
exit 1
fi
ARCH=i386 publish_snap_docker
ARCH=arm64 publish_snap_docker
ARCH=armhf publish_snap_docker
ARCH=amd64 publish_snap_docker
}
publish() {
if [[ -z $1 ]]; then
echo "No channel specified"
exit 1
fi
CHANNEL="${1}"
if [ "$CHANNEL" != "stable" ] && [ "$CHANNEL" != "beta" ]; then
echo "$CHANNEL is an invalid value for the update channel!"
exit 1
fi
ARCH=i386 publish_snap
ARCH=arm64 publish_snap
ARCH=armhf publish_snap
ARCH=amd64 publish_snap
}
cleanup() {
rm -f launchpad_credentials
rm -f snapcraft.yaml
rm -f snapcraft.yaml.bak
rm -f snapcraft_login
git checkout snapcraft.yaml
}
#######################################
# main
#######################################
if [[ -z $1 || $1 == "--help" || $1 == "-h" ]]; then
usage
fi
case "$1" in
"build-docker") build_docker $2 ;;
"build") build $2 ;;
"publish-docker-beta") publish_docker beta ;;
"publish-docker-release") publish_docker stable ;;
"publish-beta") publish beta ;;
"publish-release") publish stable ;;
"prepare") prepare ;;
"cleanup") cleanup ;;
*) usage ;;
esac
exit 0

828
client/package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
{ {
"client_settings": "Client settings", "client_settings": "Client settings",
"example_upstream_reserved": "you can specify DNS upstream <0>for a specific domain(s)</0>", "example_upstream_reserved": "You can specify DNS upstream <0>for the specific domain(s)</0>",
"upstream_parallel": "Use parallel queries to speed up resolving by simultaneously querying all upstream servers", "upstream_parallel": "Use parallel requests to speed up resolving by simultaneously querying all upstream servers",
"parallel_requests": "Parallel requests",
"bootstrap_dns": "Bootstrap DNS 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.", "bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams.",
"check_dhcp_servers": "Check for DHCP servers", "check_dhcp_servers": "Check for DHCP servers",
@ -256,6 +257,7 @@
"install_devices_router_list_1": "Open the preferences for your router. Usually, you can access it from your browser via a URL (like http://192.168.0.1/ or http://192.168.1.1/). You may be asked to enter the password. If you don't remember it, you can often reset the password by pressing a button on the router itself. Some routers require a specific application, which in that case should be already installed on your computer/phone.", "install_devices_router_list_1": "Open the preferences for your router. Usually, you can access it from your browser via a URL (like http://192.168.0.1/ or http://192.168.1.1/). You may be asked to enter the password. If you don't remember it, you can often reset the password by pressing a button on the router itself. Some routers require a specific application, which in that case should be already installed on your computer/phone.",
"install_devices_router_list_2": "Find the DHCP/DNS settings. Look for the DNS letters next to a field which allows two or three sets of numbers, each broken into four groups of one to three digits.", "install_devices_router_list_2": "Find the DHCP/DNS settings. Look for the DNS letters next to a field which allows two or three sets of numbers, each broken into four groups of one to three digits.",
"install_devices_router_list_3": "Enter your AdGuard Home server addresses there.", "install_devices_router_list_3": "Enter your AdGuard Home server addresses there.",
"install_devices_router_list_4": "You can't set a custom DNS server on some types of routers. In this case it may help if you set up AdGuard Home as a DHCP server. Otherwise, you should search for the manual on how to customize DNS servers for your particular router model.",
"install_devices_windows_list_1": "Open Control Panel through Start menu or Windows search.", "install_devices_windows_list_1": "Open Control Panel through Start menu or Windows search.",
"install_devices_windows_list_2": "Go to Network and Internet category and then to Network and Sharing Center.", "install_devices_windows_list_2": "Go to Network and Internet category and then to Network and Sharing Center.",
"install_devices_windows_list_3": "On the left side of the screen find Change adapter settings and click on it.", "install_devices_windows_list_3": "On the left side of the screen find Change adapter settings and click on it.",
@ -453,6 +455,8 @@
"example_rewrite_wildcard": "rewrite responses for all <0>example.org</0> subdomains.", "example_rewrite_wildcard": "rewrite responses for all <0>example.org</0> subdomains.",
"disable_ipv6": "Disable IPv6", "disable_ipv6": "Disable IPv6",
"disable_ipv6_desc": "If this feature is enabled, all DNS queries for IPv6 addresses (type AAAA) will be dropped.", "disable_ipv6_desc": "If this feature is enabled, all DNS queries for IPv6 addresses (type AAAA) will be dropped.",
"fastest_addr": "Fastest IP address",
"fastest_addr_desc": "Query all DNS servers and return the fastest IP address among all responses",
"autofix_warning_text": "If you click \"Fix\", AdGuard Home will configure your system to use AdGuard Home DNS server.", "autofix_warning_text": "If you click \"Fix\", AdGuard Home will configure your system to use AdGuard Home DNS server.",
"autofix_warning_list": "It will perform these tasks: <0>Deactivate system DNSStubListener</0> <0>Set DNS server address to 127.0.0.1</0> <0>Replace symbolic link target of /etc/resolv.conf with /run/systemd/resolve/resolv.conf</0> <0>Stop DNSStubListener (reload systemd-resolved service)</0>", "autofix_warning_list": "It will perform these tasks: <0>Deactivate system DNSStubListener</0> <0>Set DNS server address to 127.0.0.1</0> <0>Replace symbolic link target of /etc/resolv.conf with /run/systemd/resolve/resolv.conf</0> <0>Stop DNSStubListener (reload systemd-resolved service)</0>",
"autofix_warning_result": "As a result all DNS requests from your system will be processed by AdGuard Home by default.", "autofix_warning_result": "As a result all DNS requests from your system will be processed by AdGuard Home by default.",

View File

@ -2,6 +2,7 @@ import { createAction } from 'redux-actions';
import apiClient from '../api/Api'; import apiClient from '../api/Api';
import { addErrorToast, addSuccessToast } from './index'; import { addErrorToast, addSuccessToast } from './index';
import { normalizeTextarea } from '../helpers/helpers';
export const getDnsConfigRequest = createAction('GET_DNS_CONFIG_REQUEST'); export const getDnsConfigRequest = createAction('GET_DNS_CONFIG_REQUEST');
export const getDnsConfigFailure = createAction('GET_DNS_CONFIG_FAILURE'); export const getDnsConfigFailure = createAction('GET_DNS_CONFIG_FAILURE');
@ -25,8 +26,26 @@ export const setDnsConfigSuccess = createAction('SET_DNS_CONFIG_SUCCESS');
export const setDnsConfig = config => async (dispatch) => { export const setDnsConfig = config => async (dispatch) => {
dispatch(setDnsConfigRequest()); dispatch(setDnsConfigRequest());
try { try {
await apiClient.setDnsConfig(config); const data = { ...config };
let hasDnsSettings = false;
if (Object.prototype.hasOwnProperty.call(data, 'bootstrap_dns')) {
data.bootstrap_dns = normalizeTextarea(config.bootstrap_dns);
hasDnsSettings = true;
}
if (Object.prototype.hasOwnProperty.call(data, 'upstream_dns')) {
data.upstream_dns = normalizeTextarea(config.upstream_dns);
hasDnsSettings = true;
}
await apiClient.setDnsConfig(data);
if (hasDnsSettings) {
dispatch(addSuccessToast('updated_upstream_dns_toast'));
} else {
dispatch(addSuccessToast('config_successfully_saved')); dispatch(addSuccessToast('config_successfully_saved'));
}
dispatch(setDnsConfigSuccess(config)); dispatch(setDnsConfigSuccess(config));
} catch (error) { } catch (error) {
dispatch(addErrorToast({ error })); dispatch(addErrorToast({ error }));

View File

@ -244,6 +244,7 @@ export const getDnsStatus = () => async (dispatch) => {
dispatch(dnsStatusFailure()); dispatch(dnsStatusFailure());
window.location.reload(true); window.location.reload(true);
}; };
const handleRequestSuccess = (response) => { const handleRequestSuccess = (response) => {
const dnsStatus = response.data; const dnsStatus = response.data;
const { running } = dnsStatus; const { running } = dnsStatus;
@ -265,42 +266,6 @@ export const getDnsStatus = () => async (dispatch) => {
} }
}; };
export const getDnsSettingsRequest = createAction('GET_DNS_SETTINGS_REQUEST');
export const getDnsSettingsFailure = createAction('GET_DNS_SETTINGS_FAILURE');
export const getDnsSettingsSuccess = createAction('GET_DNS_SETTINGS_SUCCESS');
export const getDnsSettings = () => async (dispatch) => {
dispatch(getDnsSettingsRequest());
try {
const dnsStatus = await apiClient.getGlobalStatus();
dispatch(getDnsSettingsSuccess(dnsStatus));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getDnsSettingsFailure());
}
};
export const handleUpstreamChange = createAction('HANDLE_UPSTREAM_CHANGE');
export const setUpstreamRequest = createAction('SET_UPSTREAM_REQUEST');
export const setUpstreamFailure = createAction('SET_UPSTREAM_FAILURE');
export const setUpstreamSuccess = createAction('SET_UPSTREAM_SUCCESS');
export const setUpstream = config => async (dispatch) => {
dispatch(setUpstreamRequest());
try {
const values = { ...config };
values.bootstrap_dns = normalizeTextarea(values.bootstrap_dns);
values.upstream_dns = normalizeTextarea(values.upstream_dns);
await apiClient.setUpstream(values);
dispatch(addSuccessToast('updated_upstream_dns_toast'));
dispatch(setUpstreamSuccess(config));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setUpstreamFailure());
}
};
export const testUpstreamRequest = createAction('TEST_UPSTREAM_REQUEST'); export const testUpstreamRequest = createAction('TEST_UPSTREAM_REQUEST');
export const testUpstreamFailure = createAction('TEST_UPSTREAM_FAILURE'); export const testUpstreamFailure = createAction('TEST_UPSTREAM_FAILURE');
export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS'); export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS');

View File

@ -32,7 +32,6 @@ class Api {
// Global methods // Global methods
GLOBAL_STATUS = { path: 'status', method: 'GET' }; GLOBAL_STATUS = { path: 'status', method: 'GET' };
GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstreams_config', method: 'POST' };
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' }; GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' };
GLOBAL_VERSION = { path: 'version.json', method: 'POST' }; GLOBAL_VERSION = { path: 'version.json', method: 'POST' };
GLOBAL_UPDATE = { path: 'update', method: 'POST' }; GLOBAL_UPDATE = { path: 'update', method: 'POST' };
@ -42,15 +41,6 @@ class Api {
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
setUpstream(url) {
const { path, method } = this.GLOBAL_SET_UPSTREAM_DNS;
const config = {
data: url,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, config);
}
testUpstream(servers) { testUpstream(servers) {
const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS; const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS;
const config = { const config = {

View File

@ -58,7 +58,7 @@ const BlockedDomains = ({
noDataText={t('no_domains_found')} noDataText={t('no_domains_found')}
minRows={6} minRows={6}
defaultPageSize={100} defaultPageSize={100}
className="-highlight card-table-overflow stats__table" className="-highlight card-table-overflow--limited stats__table"
/> />
</Card> </Card>
); );

View File

@ -119,7 +119,7 @@ const Clients = ({
noDataText={t('no_clients_found')} noDataText={t('no_clients_found')}
minRows={6} minRows={6}
defaultPageSize={100} defaultPageSize={100}
className="-highlight card-table-overflow clients__table" className="-highlight card-table-overflow--limited clients__table"
getTrProps={(_state, rowInfo) => { getTrProps={(_state, rowInfo) => {
if (!rowInfo) { if (!rowInfo) {
return {}; return {};

View File

@ -59,7 +59,7 @@ const QueriedDomains = ({
noDataText={t('no_domains_found')} noDataText={t('no_domains_found')}
minRows={6} minRows={6}
defaultPageSize={100} defaultPageSize={100}
className="-highlight card-table-overflow stats__table" className="-highlight card-table-overflow--limited stats__table"
/> />
</Card> </Card>
); );

View File

@ -17,7 +17,35 @@ import {
} from '../../../../helpers/form'; } from '../../../../helpers/form';
import { BLOCKING_MODES } from '../../../../helpers/constants'; import { BLOCKING_MODES } from '../../../../helpers/constants';
const getFields = (processing, t) => Object.values(BLOCKING_MODES).map(mode => ( const checkboxes = [{
name: 'edns_cs_enabled',
placeholder: 'edns_enable',
subtitle: 'edns_cs_desc',
},
{
name: 'dnssec_enabled',
placeholder: 'dnssec_enable',
subtitle: 'dnssec_enable_desc',
},
{
name: 'disable_ipv6',
placeholder: 'disable_ipv6',
subtitle: 'disable_ipv6_desc',
}];
const customIps = [{
description: 'blocking_ipv4_desc',
name: 'blocking_ipv4',
validateIp: ipv4,
},
{
description: 'blocking_ipv6_desc',
name: 'blocking_ipv6',
validateIp: ipv6,
}];
const getFields = (processing, t) => Object.values(BLOCKING_MODES)
.map(mode => (
<Field <Field
key={mode} key={mode}
name="blocking_mode" name="blocking_mode"
@ -31,12 +59,13 @@ const getFields = (processing, t) => Object.values(BLOCKING_MODES).map(mode => (
let Form = ({ let Form = ({
handleSubmit, submitting, invalid, processing, blockingMode, t, handleSubmit, submitting, invalid, processing, blockingMode, t,
}) => ( }) =>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="row"> <div className="row">
<div className="col-12 col-sm-6"> <div className="col-12 col-sm-6">
<div className="form__group form__group--settings"> <div className="form__group form__group--settings">
<label htmlFor="ratelimit" className="form__label form__label--with-desc"> <label htmlFor="ratelimit"
className="form__label form__label--with-desc">
<Trans>rate_limit</Trans> <Trans>rate_limit</Trans>
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">
@ -53,49 +82,27 @@ let Form = ({
/> />
</div> </div>
</div> </div>
<div className="col-12"> {checkboxes.map(({ name, placeholder, subtitle }) =>
<div className="col-12" key={name}>
<div className="form__group form__group--settings"> <div className="form__group form__group--settings">
<Field <Field
name="edns_cs_enabled" name={name}
type="checkbox" type="checkbox"
component={renderSelectField} component={renderSelectField}
placeholder={t('edns_enable')} placeholder={t(placeholder)}
disabled={processing} disabled={processing}
subtitle={t('edns_cs_desc')} subtitle={t(subtitle)}
/> />
</div> </div>
</div> </div>)}
<div className="col-12">
<div className="form__group form__group--settings">
<Field
name="dnssec_enabled"
type="checkbox"
component={renderSelectField}
placeholder={t('dnssec_enable')}
disabled={processing}
subtitle={t('dnssec_enable_desc')}
/>
</div>
</div>
<div className="col-12">
<div className="form__group form__group--settings">
<Field
name="disable_ipv6"
type="checkbox"
component={renderSelectField}
placeholder={t('disable_ipv6')}
disabled={processing}
subtitle={t('disable_ipv6_desc')}
/>
</div>
</div>
<div className="col-12"> <div className="col-12">
<div className="form__group form__group--settings mb-4"> <div className="form__group form__group--settings mb-4">
<label className="form__label form__label--with-desc"> <label className="form__label form__label--with-desc">
<Trans>blocking_mode</Trans> <Trans>blocking_mode</Trans>
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">
{Object.values(BLOCKING_MODES).map(mode => ( {Object.values(BLOCKING_MODES)
.map(mode => (
<li key={mode}> <li key={mode}>
<Trans>{`blocking_mode_${mode}`}</Trans> <Trans>{`blocking_mode_${mode}`}</Trans>
</li> </li>
@ -108,40 +115,27 @@ let Form = ({
</div> </div>
{blockingMode === BLOCKING_MODES.custom_ip && ( {blockingMode === BLOCKING_MODES.custom_ip && (
<Fragment> <Fragment>
<div className="col-12 col-sm-6"> {customIps.map(({
description,
name,
validateIp,
}) => <div className="col-12 col-sm-6" key={name}>
<div className="form__group form__group--settings"> <div className="form__group form__group--settings">
<label htmlFor="blocking_ipv4" className="form__label form__label--with-desc"> <label className="form__label form__label--with-desc"
<Trans>blocking_ipv4</Trans> htmlFor={name}><Trans>{name}</Trans>
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">
<Trans>blocking_ipv4_desc</Trans> <Trans>{description}</Trans>
</div> </div>
<Field <Field
name="blocking_ipv4" name={name}
component={renderInputField} component={renderInputField}
className="form-control" className="form-control"
placeholder={t('form_enter_ip')} placeholder={t('form_enter_ip')}
validate={[ipv4, required]} validate={[validateIp, required]}
/> />
</div> </div>
</div> </div>)}
<div className="col-12 col-sm-6">
<div className="form__group form__group--settings">
<label htmlFor="ip_address" className="form__label form__label--with-desc">
<Trans>blocking_ipv6</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>blocking_ipv6_desc</Trans>
</div>
<Field
name="blocking_ipv6"
component={renderInputField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[ipv6, required]}
/>
</div>
</div>
</Fragment> </Fragment>
)} )}
</div> </div>
@ -152,8 +146,7 @@ let Form = ({
> >
<Trans>save_btn</Trans> <Trans>save_btn</Trans>
</button> </button>
</form> </form>;
);
Form.propTypes = { Form.propTypes = {
blockingMode: PropTypes.string.isRequired, blockingMode: PropTypes.string.isRequired,

View File

@ -6,21 +6,50 @@ import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow'; import flow from 'lodash/flow';
import classnames from 'classnames'; import classnames from 'classnames';
import { renderSelectField } from '../../../../helpers/form';
import Examples from './Examples'; import Examples from './Examples';
import { renderSelectField } from '../../../../helpers/form';
const getInputFields = (parallel_requests_selected, fastest_addr_selected) => [{
// eslint-disable-next-line react/display-name
getTitle: () => <label className="form__label" htmlFor="upstream_dns">
<Trans>upstream_dns</Trans>
</label>,
name: 'upstream_dns',
type: 'text',
component: 'textarea',
className: 'form-control form-control--textarea',
placeholder: 'upstream_dns',
},
{
name: 'parallel_requests',
placeholder: 'parallel_requests',
component: renderSelectField,
type: 'checkbox',
subtitle: 'upstream_parallel',
disabled: fastest_addr_selected,
},
{
name: 'fastest_addr',
placeholder: 'fastest_addr',
component: renderSelectField,
type: 'checkbox',
subtitle: 'fastest_addr_desc',
disabled: parallel_requests_selected,
}];
let Form = (props) => { let Form = (props) => {
const { const {
t, t,
handleSubmit, handleSubmit,
testUpstream, testUpstream,
upstreamDns,
bootstrapDns,
allServers,
submitting, submitting,
invalid, invalid,
processingSetUpstream, processingSetConfig,
processingTestUpstream, processingTestUpstream,
fastest_addr,
parallel_requests,
upstream_dns,
bootstrap_dns,
} = props; } = props;
const testButtonClass = classnames({ const testButtonClass = classnames({
@ -28,42 +57,31 @@ let Form = (props) => {
'btn btn-primary btn-standard mr-2 btn-loading': processingTestUpstream, 'btn btn-primary btn-standard mr-2 btn-loading': processingTestUpstream,
}); });
const INPUT_FIELDS = getInputFields(parallel_requests, fastest_addr);
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="row"> <div className="row">
<div className="col-12"> {INPUT_FIELDS.map(({
<div className="form__group form__group--settings"> name, component, type, className, placeholder, getTitle, subtitle, disabled,
<label className="form__label" htmlFor="upstream_dns"> }) => <div className="col-12 mb-4" key={name}>
<Trans>upstream_dns</Trans> {typeof getTitle === 'function' && getTitle()}
</label>
<Field <Field
id="upstream_dns" id={name}
name="upstream_dns" name={name}
component="textarea" component={component}
type="text" type={type}
className="form-control form-control--textarea" className={className}
placeholder={t('upstream_dns')} placeholder={t(placeholder)}
disabled={processingSetUpstream || processingTestUpstream} subtitle={t(subtitle)}
disabled={processingSetConfig || processingTestUpstream || disabled}
/> />
</div> </div>)}
</div>
<div className="col-12">
<div className="form__group form__group--settings">
<Field
name="all_servers"
type="checkbox"
component={renderSelectField}
placeholder={t('upstream_parallel')}
disabled={processingSetUpstream}
/>
</div>
</div>
<div className="col-12"> <div className="col-12">
<Examples /> <Examples />
<hr /> <hr />
</div> </div>
<div className="col-12"> <div className="col-12 mb-4">
<div className="form__group">
<label <label
className="form__label form__label--with-desc" className="form__label form__label--with-desc"
htmlFor="bootstrap_dns" htmlFor="bootstrap_dns"
@ -80,11 +98,10 @@ let Form = (props) => {
type="text" type="text"
className="form-control form-control--textarea form-control--textarea-small" className="form-control form-control--textarea form-control--textarea-small"
placeholder={t('bootstrap_dns')} placeholder={t('bootstrap_dns')}
disabled={processingSetUpstream} disabled={processingSetConfig}
/> />
</div> </div>
</div> </div>
</div>
<div className="card-actions"> <div className="card-actions">
<div className="btn-list"> <div className="btn-list">
<button <button
@ -92,12 +109,11 @@ let Form = (props) => {
className={testButtonClass} className={testButtonClass}
onClick={() => onClick={() =>
testUpstream({ testUpstream({
upstream_dns: upstreamDns, upstream_dns,
bootstrap_dns: bootstrapDns, bootstrap_dns,
all_servers: allServers,
}) })
} }
disabled={!upstreamDns || processingTestUpstream} disabled={!upstream_dns || processingTestUpstream}
> >
<Trans>test_upstream_btn</Trans> <Trans>test_upstream_btn</Trans>
</button> </button>
@ -105,7 +121,7 @@ let Form = (props) => {
type="submit" type="submit"
className="btn btn-success btn-standard" className="btn btn-success btn-standard"
disabled={ disabled={
submitting || invalid || processingSetUpstream || processingTestUpstream submitting || invalid || processingSetConfig || processingTestUpstream
} }
> >
<Trans>apply_btn</Trans> <Trans>apply_btn</Trans>
@ -122,24 +138,28 @@ Form.propTypes = {
submitting: PropTypes.bool, submitting: PropTypes.bool,
invalid: PropTypes.bool, invalid: PropTypes.bool,
initialValues: PropTypes.object, initialValues: PropTypes.object,
upstreamDns: PropTypes.string, upstream_dns: PropTypes.string,
bootstrapDns: PropTypes.string, bootstrap_dns: PropTypes.string,
allServers: PropTypes.bool, fastest_addr: PropTypes.bool,
parallel_requests: PropTypes.bool,
processingTestUpstream: PropTypes.bool, processingTestUpstream: PropTypes.bool,
processingSetUpstream: PropTypes.bool, processingSetConfig: PropTypes.bool,
t: PropTypes.func, t: PropTypes.func,
}; };
const selector = formValueSelector('upstreamForm'); const selector = formValueSelector('upstreamForm');
Form = connect((state) => { Form = connect((state) => {
const upstreamDns = selector(state, 'upstream_dns'); const upstream_dns = selector(state, 'upstream_dns');
const bootstrapDns = selector(state, 'bootstrap_dns'); const bootstrap_dns = selector(state, 'bootstrap_dns');
const allServers = selector(state, 'all_servers'); const fastest_addr = selector(state, 'fastest_addr');
const parallel_requests = selector(state, 'parallel_requests');
return { return {
upstreamDns, upstream_dns,
bootstrapDns, bootstrap_dns,
allServers, fastest_addr,
parallel_requests,
}; };
})(Form); })(Form);

View File

@ -7,7 +7,7 @@ import Card from '../../../ui/Card';
class Upstream extends Component { class Upstream extends Component {
handleSubmit = (values) => { handleSubmit = (values) => {
this.props.setUpstream(values); this.props.setDnsConfig(values);
}; };
handleTest = (values) => { handleTest = (values) => {
@ -17,11 +17,14 @@ class Upstream extends Component {
render() { render() {
const { const {
t, t,
upstreamDns: upstream_dns,
bootstrapDns: bootstrap_dns,
allServers: all_servers,
processingSetUpstream,
processingTestUpstream, processingTestUpstream,
dnsConfig: {
upstream_dns,
bootstrap_dns,
fastest_addr,
parallel_requests,
processingSetConfig,
},
} = this.props; } = this.props;
return ( return (
@ -36,12 +39,13 @@ class Upstream extends Component {
initialValues={{ initialValues={{
upstream_dns, upstream_dns,
bootstrap_dns, bootstrap_dns,
all_servers, fastest_addr,
parallel_requests,
}} }}
testUpstream={this.handleTest} testUpstream={this.handleTest}
onSubmit={this.handleSubmit} onSubmit={this.handleSubmit}
processingTestUpstream={processingTestUpstream} processingTestUpstream={processingTestUpstream}
processingSetUpstream={processingSetUpstream} processingSetConfig={processingSetConfig}
/> />
</div> </div>
</div> </div>
@ -51,14 +55,11 @@ class Upstream extends Component {
} }
Upstream.propTypes = { Upstream.propTypes = {
upstreamDns: PropTypes.string,
bootstrapDns: PropTypes.string,
allServers: PropTypes.bool,
setUpstream: PropTypes.func.isRequired,
testUpstream: PropTypes.func.isRequired, testUpstream: PropTypes.func.isRequired,
processingSetUpstream: PropTypes.bool.isRequired,
processingTestUpstream: PropTypes.bool.isRequired, processingTestUpstream: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
dnsConfig: PropTypes.object.isRequired,
setDnsConfig: PropTypes.func.isRequired,
}; };
export default withNamespaces()(Upstream); export default withNamespaces()(Upstream);

View File

@ -10,7 +10,6 @@ import Loading from '../../ui/Loading';
class Dns extends Component { class Dns extends Component {
componentDidMount() { componentDidMount() {
this.props.getDnsSettings();
this.props.getAccessList(); this.props.getAccessList();
this.props.getDnsConfig(); this.props.getDnsConfig();
} }
@ -18,59 +17,45 @@ class Dns extends Component {
render() { render() {
const { const {
t, t,
dashboard,
settings, settings,
access, access,
setAccessList, setAccessList,
testUpstream, testUpstream,
setUpstream,
dnsConfig, dnsConfig,
setDnsConfig, setDnsConfig,
} = this.props; } = this.props;
const isDataLoading = dashboard.processingDnsSettings const isDataLoading = access.processing || dnsConfig.processingGetConfig;
|| access.processing
|| dnsConfig.processingGetConfig;
const isDataReady = !dashboard.processingDnsSettings
&& !access.processing
&& !dnsConfig.processingGetConfig;
return ( return (
<Fragment> <Fragment>
<PageTitle title={t('dns_settings')} /> <PageTitle title={t('dns_settings')} />
{isDataLoading && <Loading />} {isDataLoading ?
{isDataReady && ( <Loading /> :
<Fragment> <Fragment>
<Upstream
processingTestUpstream={settings.processingTestUpstream}
testUpstream={testUpstream}
dnsConfig={dnsConfig}
setDnsConfig={setDnsConfig}
/>
<Config <Config
dnsConfig={dnsConfig} dnsConfig={dnsConfig}
setDnsConfig={setDnsConfig} setDnsConfig={setDnsConfig}
/> />
<Upstream
upstreamDns={dashboard.upstreamDns}
bootstrapDns={dashboard.bootstrapDns}
allServers={dashboard.allServers}
processingTestUpstream={settings.processingTestUpstream}
processingSetUpstream={settings.processingSetUpstream}
setUpstream={setUpstream}
testUpstream={testUpstream}
/>
<Access access={access} setAccessList={setAccessList} /> <Access access={access} setAccessList={setAccessList} />
</Fragment> </Fragment>}
)}
</Fragment> </Fragment>
); );
} }
} }
Dns.propTypes = { Dns.propTypes = {
dashboard: PropTypes.object.isRequired,
settings: PropTypes.object.isRequired, settings: PropTypes.object.isRequired,
setUpstream: PropTypes.func.isRequired,
testUpstream: PropTypes.func.isRequired, testUpstream: PropTypes.func.isRequired,
getAccessList: PropTypes.func.isRequired, getAccessList: PropTypes.func.isRequired,
setAccessList: PropTypes.func.isRequired, setAccessList: PropTypes.func.isRequired,
access: PropTypes.object.isRequired, access: PropTypes.object.isRequired,
getDnsSettings: PropTypes.func.isRequired,
dnsConfig: PropTypes.object.isRequired, dnsConfig: PropTypes.object.isRequired,
setDnsConfig: PropTypes.func.isRequired, setDnsConfig: PropTypes.func.isRequired,
getDnsConfig: PropTypes.func.isRequired, getDnsConfig: PropTypes.func.isRequired,

View File

@ -10,6 +10,11 @@
} }
.card-table-overflow { .card-table-overflow {
overflow-y: auto;
max-height: 100%;
}
.card-table-overflow--limited {
overflow-y: auto; overflow-y: auto;
max-height: 280px; max-height: 280px;
} }

View File

@ -83,6 +83,7 @@
.checkbox__input:disabled + .checkbox__label { .checkbox__input:disabled + .checkbox__label {
cursor: default; cursor: default;
color: var(--gray);
} }
.checkbox__input:disabled + .checkbox__label:before { .checkbox__input:disabled + .checkbox__label:before {

View File

@ -34,6 +34,9 @@ const Guide = (props) => {
<li> <li>
<Trans>install_devices_router_list_3</Trans> <Trans>install_devices_router_list_3</Trans>
</li> </li>
<li>
<Trans>install_devices_router_list_4</Trans>
</li>
</ol> </ol>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { handleUpstreamChange, setUpstream, testUpstream, getDnsSettings } from '../actions'; import { testUpstream } from '../actions';
import { getAccessList, setAccessList } from '../actions/access'; import { getAccessList, setAccessList } from '../actions/access';
import { import {
getRewritesList, getRewritesList,
@ -25,8 +25,6 @@ const mapStateToProps = (state) => {
}; };
const mapDispatchToProps = { const mapDispatchToProps = {
handleUpstreamChange,
setUpstream,
testUpstream, testUpstream,
getAccessList, getAccessList,
setAccessList, setAccessList,
@ -34,7 +32,6 @@ const mapDispatchToProps = {
addRewrite, addRewrite,
deleteRewrite, deleteRewrite,
toggleRewritesModal, toggleRewritesModal,
getDnsSettings,
getDnsConfig, getDnsConfig,
setDnsConfig, setDnsConfig,
}; };

View File

@ -117,11 +117,12 @@ export const renderSelectField = ({
placeholder, placeholder,
subtitle, subtitle,
disabled, disabled,
onClick,
modifier = 'checkbox--form', modifier = 'checkbox--form',
meta: { touched, error }, meta: { touched, error },
}) => ( }) => (
<Fragment> <Fragment>
<label className={`checkbox ${modifier}`}> <label className={`checkbox ${modifier}`} onClick={onClick}>
<span className="checkbox__marker" /> <span className="checkbox__marker" />
<input {...input} type="checkbox" className="checkbox__input" disabled={disabled} /> <input {...input} type="checkbox" className="checkbox__input" disabled={disabled} />
<span className="checkbox__label"> <span className="checkbox__label">

View File

@ -300,6 +300,7 @@ class Settings extends Component {
</button> </button>
} }
</div> </div>
{isDnsFixAvailable &&
<div className="text-muted mb-2"> <div className="text-muted mb-2">
<p className="mb-1"> <p className="mb-1">
<Trans>autofix_warning_text</Trans> <Trans>autofix_warning_text</Trans>
@ -310,7 +311,7 @@ class Settings extends Component {
<p className="mb-1"> <p className="mb-1">
<Trans>autofix_warning_result</Trans> <Trans>autofix_warning_result</Trans>
</p> </p>
</div> </div>}
<hr className="divider--small" /> <hr className="divider--small" />
</Fragment> </Fragment>
} }

View File

@ -15,6 +15,8 @@ const dnsConfig = handleActions(
const { const {
blocking_ipv4, blocking_ipv4,
blocking_ipv6, blocking_ipv6,
upstream_dns,
bootstrap_dns,
...values ...values
} = payload; } = payload;
@ -23,6 +25,8 @@ const dnsConfig = handleActions(
...values, ...values,
blocking_ipv4: blocking_ipv4 || DEFAULT_BLOCKING_IPV4, blocking_ipv4: blocking_ipv4 || DEFAULT_BLOCKING_IPV4,
blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6, blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6,
upstream_dns: (upstream_dns && upstream_dns.join('\n')) || '',
bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '',
processingGetConfig: false, processingGetConfig: false,
}; };
}, },

View File

@ -35,14 +35,6 @@ const settings = handleActions(
const newSettingsList = { ...settingsList, [settingKey]: newSetting }; const newSettingsList = { ...settingsList, [settingKey]: newSetting };
return { ...state, settingsList: newSettingsList }; return { ...state, settingsList: newSettingsList };
}, },
[actions.setUpstreamRequest]: state => ({ ...state, processingSetUpstream: true }),
[actions.setUpstreamFailure]: state => ({ ...state, processingSetUpstream: false }),
[actions.setUpstreamSuccess]: (state, { payload }) => ({
...state,
...payload,
processingSetUpstream: false,
}),
[actions.testUpstreamRequest]: state => ({ ...state, processingTestUpstream: true }), [actions.testUpstreamRequest]: state => ({ ...state, processingTestUpstream: true }),
[actions.testUpstreamFailure]: state => ({ ...state, processingTestUpstream: false }), [actions.testUpstreamFailure]: state => ({ ...state, processingTestUpstream: false }),
[actions.testUpstreamSuccess]: state => ({ ...state, processingTestUpstream: false }), [actions.testUpstreamSuccess]: state => ({ ...state, processingTestUpstream: false }),
@ -50,7 +42,6 @@ const settings = handleActions(
{ {
processing: true, processing: true,
processingTestUpstream: false, processingTestUpstream: false,
processingSetUpstream: false,
processingDhcpStatus: false, processingDhcpStatus: false,
settingsList: {}, settingsList: {},
}, },
@ -67,12 +58,9 @@ const dashboard = handleActions(
version, version,
dns_port: dnsPort, dns_port: dnsPort,
dns_addresses: dnsAddresses, dns_addresses: dnsAddresses,
upstream_dns: upstreamDns,
bootstrap_dns: bootstrapDns,
all_servers: allServers,
protection_enabled: protectionEnabled, protection_enabled: protectionEnabled,
language,
http_port: httpPort, http_port: httpPort,
language,
} = payload; } = payload;
const newState = { const newState = {
...state, ...state,
@ -81,9 +69,6 @@ const dashboard = handleActions(
dnsVersion: version, dnsVersion: version,
dnsPort, dnsPort,
dnsAddresses, dnsAddresses,
upstreamDns: (upstreamDns && upstreamDns.join('\n')) || '',
bootstrapDns: (bootstrapDns && bootstrapDns.join('\n')) || '',
allServers,
protectionEnabled, protectionEnabled,
language, language,
httpPort, httpPort,
@ -138,11 +123,6 @@ const dashboard = handleActions(
return newState; return newState;
}, },
[actions.handleUpstreamChange]: (state, { payload }) => {
const { upstreamDns } = payload;
return { ...state, upstreamDns };
},
[actions.getLanguageSuccess]: (state, { payload }) => { [actions.getLanguageSuccess]: (state, { payload }) => {
const newState = { ...state, language: payload }; const newState = { ...state, language: payload };
return newState; return newState;
@ -159,24 +139,6 @@ const dashboard = handleActions(
return newState; return newState;
}, },
[actions.getDnsSettingsRequest]: state => ({ ...state, processingDnsSettings: true }),
[actions.getDnsSettingsFailure]: state => ({ ...state, processingDnsSettings: false }),
[actions.getDnsSettingsSuccess]: (state, { payload }) => {
const {
upstream_dns: upstreamDns,
bootstrap_dns: bootstrapDns,
all_servers: allServers,
} = payload;
return {
...state,
allServers,
upstreamDns: (upstreamDns && upstreamDns.join('\n')) || '',
bootstrapDns: (bootstrapDns && bootstrapDns.join('\n')) || '',
processingDnsSettings: false,
};
},
[actions.getProfileRequest]: state => ({ ...state, processingProfile: true }), [actions.getProfileRequest]: state => ({ ...state, processingProfile: true }),
[actions.getProfileFailure]: state => ({ ...state, processingProfile: false }), [actions.getProfileFailure]: state => ({ ...state, processingProfile: false }),
[actions.getProfileSuccess]: (state, { payload }) => ({ [actions.getProfileSuccess]: (state, { payload }) => ({
@ -191,11 +153,7 @@ const dashboard = handleActions(
processingVersion: true, processingVersion: true,
processingClients: true, processingClients: true,
processingUpdate: false, processingUpdate: false,
processingDnsSettings: true,
processingProfile: true, processingProfile: true,
upstreamDns: '',
bootstrapDns: '',
allServers: false,
protectionEnabled: false, protectionEnabled: false,
processingProtection: false, processingProtection: false,
httpPort: 80, httpPort: 80,

View File

@ -24,6 +24,7 @@ var serviceRulesArray = []svc{
"||facebook.com^", "||facebook.com^",
"||facebook.net^", "||facebook.net^",
"||fbcdn.net^", "||fbcdn.net^",
"||accountkit.com^",
"||fb.me^", "||fb.me^",
"||fb.com^", "||fb.com^",
"||fbsbx.com^", "||fbsbx.com^",
@ -31,7 +32,7 @@ var serviceRulesArray = []svc{
"||facebookcorewwwi.onion^", "||facebookcorewwwi.onion^",
"||fbcdn.com^", "||fbcdn.com^",
}}, }},
{"twitter", []string{"||twitter.com^", "||t.co^", "||twimg.com^"}}, {"twitter", []string{"||twitter.com^", "||twttr.com^", "||t.co^", "||twimg.com^"}},
{"youtube", []string{ {"youtube", []string{
"||youtube.com^", "||youtube.com^",
"||ytimg.com^", "||ytimg.com^",
@ -40,17 +41,31 @@ var serviceRulesArray = []svc{
"||youtubei.googleapis.com^", "||youtubei.googleapis.com^",
"||youtube-nocookie.com^", "||youtube-nocookie.com^",
}}, }},
{"twitch", []string{"||twitch.tv^", "||ttvnw.net^"}}, {"twitch", []string{"||twitch.tv^", "||ttvnw.net^", "||jtvnw.net^", "||twitchcdn.net^"}},
{"netflix", []string{"||nflxext.com^", "||netflix.com^"}}, {"netflix", []string{"||nflxext.com^", "||netflix.com^", "||nflximg.net^", "||nflxvideo.net^"}},
{"instagram", []string{"||instagram.com^", "||cdninstagram.com^"}}, {"instagram", []string{"||instagram.com^", "||cdninstagram.com^"}},
{"snapchat", []string{"||snapchat.com^", "||sc-cdn.net^", "||impala-media-production.s3.amazonaws.com^"}}, {"snapchat", []string{
"||snapchat.com^",
"||sc-cdn.net^",
"||snap-dev.net^",
"||snapkit.co",
"||snapads.com^",
"||impala-media-production.s3.amazonaws.com^",
}},
{"discord", []string{"||discord.gg^", "||discordapp.net^", "||discordapp.com^", "||discord.media^"}}, {"discord", []string{"||discord.gg^", "||discordapp.net^", "||discordapp.com^", "||discord.media^"}},
{"ok", []string{"||ok.ru^"}}, {"ok", []string{"||ok.ru^"}},
{"skype", []string{"||skype.com^"}}, {"skype", []string{"||skype.com^", "||skypeassets.com^"}},
{"vk", []string{"||vk.com^"}}, {"vk", []string{"||vk.com^", "||userapi.com^", "||vk-cdn.net^", "||vkuservideo.net^"}},
{"origin", []string{"||origin.com^", "||signin.ea.com^", "||accounts.ea.com^"}}, {"origin", []string{"||origin.com^", "||signin.ea.com^", "||accounts.ea.com^"}},
{"steam", []string{"||steam.com^", "||steampowered.com^"}}, {"steam", []string{
{"epic_games", []string{"||epicgames.com^"}}, "||steam.com^",
"||steampowered.com^",
"||steamcommunity.com^",
"||steamstatic.com^",
"||steamstore-a.akamaihd.net^",
"||steamcdn-a.akamaihd.net^",
}},
{"epic_games", []string{"||epicgames.com^", "||easyanticheat.net^", "||easy.ac^", "||eac-cdn.com^"}},
{"reddit", []string{"||reddit.com^", "||redditstatic.com^", "||redditmedia.com^", "||redd.it^"}}, {"reddit", []string{"||reddit.com^", "||redditstatic.com^", "||redditmedia.com^", "||redd.it^"}},
{"mail_ru", []string{"||mail.ru^"}}, {"mail_ru", []string{"||mail.ru^"}},
{"cloudflare", []string{ {"cloudflare", []string{
@ -72,7 +87,13 @@ var serviceRulesArray = []svc{
{"amazon", []string{ {"amazon", []string{
"||amazon.com^", "||amazon.com^",
"||media-amazon.com^", "||media-amazon.com^",
"||primevideo.com^",
"||amazontrust.com^",
"||images-amazon.com^", "||images-amazon.com^",
"||ssl-images-amazon.com^",
"||amazonpay.com^",
"||amazonpay.in^",
"||amazon-adsystem.com^",
"||a2z.com^", "||a2z.com^",
"||amazon.ae^", "||amazon.ae^",
"||amazon.ca^", "||amazon.ca^",
@ -88,6 +109,7 @@ var serviceRulesArray = []svc{
"||amazon.co.jp^", "||amazon.co.jp^",
"||amazon.com.mx^", "||amazon.com.mx^",
"||amazon.co.uk^", "||amazon.co.uk^",
"||createspace.com^",
}}, }},
{"ebay", []string{ {"ebay", []string{
"||ebay.com^", "||ebay.com^",
@ -119,6 +141,7 @@ var serviceRulesArray = []svc{
{"tiktok", []string{ {"tiktok", []string{
"||tiktok.com^", "||tiktok.com^",
"||tiktokcdn.com^", "||tiktokcdn.com^",
"||musical.ly^",
"||snssdk.com^", "||snssdk.com^",
"||amemv.com^", "||amemv.com^",
"||toutiao.com^", "||toutiao.com^",

View File

@ -628,6 +628,11 @@ func makeResult(rule rules.Rule, reason Reason) Result {
return res return res
} }
// InitModule() - manually initialize blocked services map
func InitModule() {
initBlockedServices()
}
// New creates properly initialized DNS Filter that is ready to be used // New creates properly initialized DNS Filter that is ready to be used
func New(c *Config, blockFilters []Filter) *Dnsfilter { func New(c *Config, blockFilters []Filter) *Dnsfilter {
@ -677,8 +682,6 @@ func New(c *Config, blockFilters []Filter) *Dnsfilter {
} }
d.BlockedServices = bsvcs d.BlockedServices = bsvcs
initBlockedServices()
if blockFilters != nil { if blockFilters != nil {
err := d.initFiltering(nil, blockFilters) err := d.initFiltering(nil, blockFilters)
if err != nil { if err != nil {

View File

@ -532,96 +532,6 @@ func TestClientSettings(t *testing.T) {
assert.True(t, r.IsFiltered && r.Reason == FilteredBlockedService) assert.True(t, r.IsFiltered && r.Reason == FilteredBlockedService)
} }
func TestRewrites(t *testing.T) {
d := Dnsfilter{}
// CNAME, A, AAAA
d.Rewrites = []RewriteEntry{
RewriteEntry{"somecname", "somehost.com", 0, nil},
RewriteEntry{"somehost.com", "0.0.0.0", 0, nil},
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
RewriteEntry{"host.com", "1.2.3.5", 0, nil},
RewriteEntry{"host.com", "1:2:3::4", 0, nil},
RewriteEntry{"www.host.com", "host.com", 0, nil},
}
d.prepareRewrites()
r := d.processRewrites("host2.com")
assert.Equal(t, NotFilteredNotFound, r.Reason)
r = d.processRewrites("www.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, "host.com", r.CanonName)
assert.True(t, len(r.IPList) == 3)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
assert.True(t, r.IPList[1].Equal(net.ParseIP("1.2.3.5")))
assert.True(t, r.IPList[2].Equal(net.ParseIP("1:2:3::4")))
// wildcard
d.Rewrites = []RewriteEntry{
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
RewriteEntry{"*.host.com", "1.2.3.5", 0, nil},
}
d.prepareRewrites()
r = d.processRewrites("host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
r = d.processRewrites("www.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.5")))
r = d.processRewrites("www.host2.com")
assert.Equal(t, NotFilteredNotFound, r.Reason)
// override a wildcard
d.Rewrites = []RewriteEntry{
RewriteEntry{"a.host.com", "1.2.3.4", 0, nil},
RewriteEntry{"*.host.com", "1.2.3.5", 0, nil},
}
d.prepareRewrites()
r = d.processRewrites("a.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.True(t, len(r.IPList) == 1)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
// wildcard + CNAME
d.Rewrites = []RewriteEntry{
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
RewriteEntry{"*.host.com", "host.com", 0, nil},
}
d.prepareRewrites()
r = d.processRewrites("www.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, "host.com", r.CanonName)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
// 2 CNAMEs
d.Rewrites = []RewriteEntry{
RewriteEntry{"b.host.com", "a.host.com", 0, nil},
RewriteEntry{"a.host.com", "host.com", 0, nil},
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
}
d.prepareRewrites()
r = d.processRewrites("b.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, "host.com", r.CanonName)
assert.True(t, len(r.IPList) == 1)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
// 2 CNAMEs + wildcard
d.Rewrites = []RewriteEntry{
RewriteEntry{"b.host.com", "a.host.com", 0, nil},
RewriteEntry{"a.host.com", "x.somehost.com", 0, nil},
RewriteEntry{"*.somehost.com", "1.2.3.4", 0, nil},
}
d.prepareRewrites()
r = d.processRewrites("b.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, "x.somehost.com", r.CanonName)
assert.True(t, len(r.IPList) == 1)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
}
func prepareTestDir() string { func prepareTestDir() string {
const dir = "./agh-test" const dir = "./agh-test"
_ = os.RemoveAll(dir) _ = os.RemoveAll(dir)

View File

@ -42,7 +42,10 @@ func (a rewritesArray) Len() int { return len(a) }
func (a rewritesArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a rewritesArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
// Priority: CNAME, A/AAAA; exact, wildcard. // Priority:
// . CNAME > A/AAAA;
// . exact > wildcard;
// . higher level wildcard > lower level wildcard
func (a rewritesArray) Less(i, j int) bool { func (a rewritesArray) Less(i, j int) bool {
if a[i].Type == dns.TypeCNAME && a[j].Type != dns.TypeCNAME { if a[i].Type == dns.TypeCNAME && a[j].Type != dns.TypeCNAME {
return false return false
@ -50,13 +53,18 @@ func (a rewritesArray) Less(i, j int) bool {
return true return true
} }
if isWildcard(a[i].Domain) && !isWildcard(a[j].Domain) { if isWildcard(a[i].Domain) {
if !isWildcard(a[j].Domain) {
return false return false
} else if !isWildcard(a[i].Domain) && isWildcard(a[j].Domain) { }
} else {
if isWildcard(a[j].Domain) {
return true return true
} }
}
return i < j // both are wildcards
return len(a[i].Domain) > len(a[j].Domain)
} }
// Prepare entry for use // Prepare entry for use
@ -86,6 +94,7 @@ func (d *Dnsfilter) prepareRewrites() {
// Get the list of matched rewrite entries. // Get the list of matched rewrite entries.
// Priority: CNAME, A/AAAA; exact, wildcard. // Priority: CNAME, A/AAAA; exact, wildcard.
// If matched exactly, don't return wildcard entries. // If matched exactly, don't return wildcard entries.
// If matched by several wildcards, select the more specific one
func findRewrites(a []RewriteEntry, host string) []RewriteEntry { func findRewrites(a []RewriteEntry, host string) []RewriteEntry {
rr := rewritesArray{} rr := rewritesArray{}
for _, r := range a { for _, r := range a {
@ -111,7 +120,10 @@ func findRewrites(a []RewriteEntry, host string) []RewriteEntry {
break break
} }
} }
} else {
rr = rr[:1]
} }
return rr return rr
} }

127
dnsfilter/rewrites_test.go Normal file
View File

@ -0,0 +1,127 @@
package dnsfilter
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRewrites(t *testing.T) {
d := Dnsfilter{}
// CNAME, A, AAAA
d.Rewrites = []RewriteEntry{
RewriteEntry{"somecname", "somehost.com", 0, nil},
RewriteEntry{"somehost.com", "0.0.0.0", 0, nil},
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
RewriteEntry{"host.com", "1.2.3.5", 0, nil},
RewriteEntry{"host.com", "1:2:3::4", 0, nil},
RewriteEntry{"www.host.com", "host.com", 0, nil},
}
d.prepareRewrites()
r := d.processRewrites("host2.com")
assert.Equal(t, NotFilteredNotFound, r.Reason)
r = d.processRewrites("www.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, "host.com", r.CanonName)
assert.True(t, len(r.IPList) == 3)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
assert.True(t, r.IPList[1].Equal(net.ParseIP("1.2.3.5")))
assert.True(t, r.IPList[2].Equal(net.ParseIP("1:2:3::4")))
// wildcard
d.Rewrites = []RewriteEntry{
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
RewriteEntry{"*.host.com", "1.2.3.5", 0, nil},
}
d.prepareRewrites()
r = d.processRewrites("host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
r = d.processRewrites("www.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.5")))
r = d.processRewrites("www.host2.com")
assert.Equal(t, NotFilteredNotFound, r.Reason)
// override a wildcard
d.Rewrites = []RewriteEntry{
RewriteEntry{"a.host.com", "1.2.3.4", 0, nil},
RewriteEntry{"*.host.com", "1.2.3.5", 0, nil},
}
d.prepareRewrites()
r = d.processRewrites("a.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.True(t, len(r.IPList) == 1)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
// wildcard + CNAME
d.Rewrites = []RewriteEntry{
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
RewriteEntry{"*.host.com", "host.com", 0, nil},
}
d.prepareRewrites()
r = d.processRewrites("www.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, "host.com", r.CanonName)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
// 2 CNAMEs
d.Rewrites = []RewriteEntry{
RewriteEntry{"b.host.com", "a.host.com", 0, nil},
RewriteEntry{"a.host.com", "host.com", 0, nil},
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
}
d.prepareRewrites()
r = d.processRewrites("b.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, "host.com", r.CanonName)
assert.True(t, len(r.IPList) == 1)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
// 2 CNAMEs + wildcard
d.Rewrites = []RewriteEntry{
RewriteEntry{"b.host.com", "a.host.com", 0, nil},
RewriteEntry{"a.host.com", "x.somehost.com", 0, nil},
RewriteEntry{"*.somehost.com", "1.2.3.4", 0, nil},
}
d.prepareRewrites()
r = d.processRewrites("b.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, "x.somehost.com", r.CanonName)
assert.True(t, len(r.IPList) == 1)
assert.True(t, r.IPList[0].Equal(net.ParseIP("1.2.3.4")))
}
func TestRewritesLevels(t *testing.T) {
d := Dnsfilter{}
// exact host, wildcard L2, wildcard L3
d.Rewrites = []RewriteEntry{
RewriteEntry{"host.com", "1.1.1.1", 0, nil},
RewriteEntry{"*.host.com", "2.2.2.2", 0, nil},
RewriteEntry{"*.sub.host.com", "3.3.3.3", 0, nil},
}
d.prepareRewrites()
// match exact
r := d.processRewrites("host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, 1, len(r.IPList))
assert.Equal(t, "1.1.1.1", r.IPList[0].String())
// match L2
r = d.processRewrites("sub.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, 1, len(r.IPList))
assert.Equal(t, "2.2.2.2", r.IPList[0].String())
// match L3
r = d.processRewrites("my.sub.host.com")
assert.Equal(t, ReasonRewrite, r.Reason)
assert.Equal(t, 1, len(r.IPList))
assert.Equal(t, "3.3.3.3", r.IPList[0].String())
}

View File

@ -141,6 +141,8 @@ type FilteringConfig struct {
// Respond with an empty answer to all AAAA requests // Respond with an empty answer to all AAAA requests
AAAADisabled bool `yaml:"aaaa_disabled"` AAAADisabled bool `yaml:"aaaa_disabled"`
FastestAddrAlgo bool `yaml:"fastest_addr"` // use Fastest Address algorithm
AllowedClients []string `yaml:"allowed_clients"` // IP addresses of whitelist clients AllowedClients []string `yaml:"allowed_clients"` // IP addresses of whitelist clients
DisallowedClients []string `yaml:"disallowed_clients"` // IP addresses of clients that should be blocked DisallowedClients []string `yaml:"disallowed_clients"` // IP addresses of clients that should be blocked
BlockedHosts []string `yaml:"blocked_hosts"` // hosts that should be blocked BlockedHosts []string `yaml:"blocked_hosts"` // hosts that should be blocked
@ -305,6 +307,7 @@ func (s *Server) Prepare(config *ServerConfig) error {
RequestHandler: s.handleDNSRequest, RequestHandler: s.handleDNSRequest,
AllServers: s.conf.AllServers, AllServers: s.conf.AllServers,
EnableEDNSClientSubnet: s.conf.EnableEDNSClientSubnet, EnableEDNSClientSubnet: s.conf.EnableEDNSClientSubnet,
FindFastestAddr: s.conf.FastestAddrAlgo,
} }
intlProxyConfig := proxy.Config{ intlProxyConfig := proxy.Config{

View File

@ -22,6 +22,9 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string,
} }
type dnsConfigJSON struct { type dnsConfigJSON struct {
Upstreams []string `json:"upstream_dns"`
Bootstraps []string `json:"bootstrap_dns"`
ProtectionEnabled bool `json:"protection_enabled"` ProtectionEnabled bool `json:"protection_enabled"`
RateLimit uint32 `json:"ratelimit"` RateLimit uint32 `json:"ratelimit"`
BlockingMode string `json:"blocking_mode"` BlockingMode string `json:"blocking_mode"`
@ -30,11 +33,16 @@ type dnsConfigJSON struct {
EDNSCSEnabled bool `json:"edns_cs_enabled"` EDNSCSEnabled bool `json:"edns_cs_enabled"`
DNSSECEnabled bool `json:"dnssec_enabled"` DNSSECEnabled bool `json:"dnssec_enabled"`
DisableIPv6 bool `json:"disable_ipv6"` DisableIPv6 bool `json:"disable_ipv6"`
FastestAddr bool `json:"fastest_addr"`
ParallelRequests bool `json:"parallel_requests"`
} }
func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) { func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
resp := dnsConfigJSON{} resp := dnsConfigJSON{}
s.RLock() s.RLock()
resp.Upstreams = stringArrayDup(s.conf.UpstreamDNS)
resp.Bootstraps = stringArrayDup(s.conf.BootstrapDNS)
resp.ProtectionEnabled = s.conf.ProtectionEnabled resp.ProtectionEnabled = s.conf.ProtectionEnabled
resp.BlockingMode = s.conf.BlockingMode resp.BlockingMode = s.conf.BlockingMode
resp.BlockingIPv4 = s.conf.BlockingIPv4 resp.BlockingIPv4 = s.conf.BlockingIPv4
@ -43,6 +51,8 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
resp.EDNSCSEnabled = s.conf.EnableEDNSClientSubnet resp.EDNSCSEnabled = s.conf.EnableEDNSClientSubnet
resp.DNSSECEnabled = s.conf.EnableDNSSEC resp.DNSSECEnabled = s.conf.EnableDNSSEC
resp.DisableIPv6 = s.conf.AAAADisabled resp.DisableIPv6 = s.conf.AAAADisabled
resp.FastestAddr = s.conf.FastestAddrAlgo
resp.ParallelRequests = s.conf.AllServers
s.RUnlock() s.RUnlock()
js, err := json.Marshal(resp) js, err := json.Marshal(resp)
@ -75,6 +85,7 @@ func checkBlockingMode(req dnsConfigJSON) bool {
return true return true
} }
// nolint(gocyclo) - we need to check each JSON field separately
func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) { func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
req := dnsConfigJSON{} req := dnsConfigJSON{}
js, err := jsonutil.DecodeObject(&req, r.Body) js, err := jsonutil.DecodeObject(&req, r.Body)
@ -83,6 +94,25 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
return return
} }
if js.Exists("upstream_dns") {
if len(req.Upstreams) != 0 {
err = ValidateUpstreams(req.Upstreams)
if err != nil {
httpError(r, w, http.StatusBadRequest, "wrong upstreams specification: %s", err)
return
}
}
}
if js.Exists("bootstrap_dns") {
for _, host := range req.Bootstraps {
if err := checkPlainDNS(host); err != nil {
httpError(r, w, http.StatusBadRequest, "%s can not be used as bootstrap dns cause: %s", host, err)
return
}
}
}
if js.Exists("blocking_mode") && !checkBlockingMode(req) { if js.Exists("blocking_mode") && !checkBlockingMode(req) {
httpError(r, w, http.StatusBadRequest, "blocking_mode: incorrect value") httpError(r, w, http.StatusBadRequest, "blocking_mode: incorrect value")
return return
@ -91,6 +121,16 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
restart := false restart := false
s.Lock() s.Lock()
if js.Exists("upstream_dns") {
s.conf.UpstreamDNS = req.Upstreams
restart = true
}
if js.Exists("bootstrap_dns") {
s.conf.BootstrapDNS = req.Bootstraps
restart = true
}
if js.Exists("protection_enabled") { if js.Exists("protection_enabled") {
s.conf.ProtectionEnabled = req.ProtectionEnabled s.conf.ProtectionEnabled = req.ProtectionEnabled
} }
@ -129,6 +169,14 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
s.conf.AAAADisabled = req.DisableIPv6 s.conf.AAAADisabled = req.DisableIPv6
} }
if js.Exists("fastest_addr") {
s.conf.FastestAddrAlgo = req.FastestAddr
}
if js.Exists("parallel_requests") {
s.conf.AllServers = req.ParallelRequests
}
s.Unlock() s.Unlock()
s.conf.ConfigModified() s.conf.ConfigModified()
@ -144,51 +192,6 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
type upstreamJSON struct { type upstreamJSON struct {
Upstreams []string `json:"upstream_dns"` // Upstreams Upstreams []string `json:"upstream_dns"` // Upstreams
BootstrapDNS []string `json:"bootstrap_dns"` // Bootstrap DNS BootstrapDNS []string `json:"bootstrap_dns"` // Bootstrap DNS
AllServers bool `json:"all_servers"` // --all-servers param for dnsproxy
}
func (s *Server) handleSetUpstreamConfig(w http.ResponseWriter, r *http.Request) {
req := upstreamJSON{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
httpError(r, w, http.StatusBadRequest, "Failed to parse new upstreams config json: %s", err)
return
}
if len(req.Upstreams) != 0 {
err = ValidateUpstreams(req.Upstreams)
if err != nil {
httpError(r, w, http.StatusBadRequest, "wrong upstreams specification: %s", err)
return
}
}
newconf := FilteringConfig{}
newconf.UpstreamDNS = req.Upstreams
// bootstrap servers are plain DNS only
for _, host := range req.BootstrapDNS {
if err := checkPlainDNS(host); err != nil {
httpError(r, w, http.StatusBadRequest, "%s can not be used as bootstrap dns cause: %s", host, err)
return
}
}
newconf.BootstrapDNS = req.BootstrapDNS
newconf.AllServers = req.AllServers
s.Lock()
s.conf.UpstreamDNS = newconf.UpstreamDNS
s.conf.BootstrapDNS = newconf.BootstrapDNS
s.conf.AllServers = newconf.AllServers
s.Unlock()
s.conf.ConfigModified()
err = s.Reconfigure(nil)
if err != nil {
httpError(r, w, http.StatusInternalServerError, "%s", err)
return
}
} }
// ValidateUpstreams validates each upstream and returns an error if any upstream is invalid or if there are no default upstreams specified // ValidateUpstreams validates each upstream and returns an error if any upstream is invalid or if there are no default upstreams specified
@ -399,7 +402,6 @@ func (s *Server) handleDOH(w http.ResponseWriter, r *http.Request) {
func (s *Server) registerHandlers() { func (s *Server) registerHandlers() {
s.conf.HTTPRegister("GET", "/control/dns_info", s.handleGetConfig) s.conf.HTTPRegister("GET", "/control/dns_info", s.handleGetConfig)
s.conf.HTTPRegister("POST", "/control/dns_config", s.handleSetConfig) s.conf.HTTPRegister("POST", "/control/dns_config", s.handleSetConfig)
s.conf.HTTPRegister("POST", "/control/set_upstreams_config", s.handleSetUpstreamConfig)
s.conf.HTTPRegister("POST", "/control/test_upstream_dns", s.handleTestUpstreamDNS) s.conf.HTTPRegister("POST", "/control/test_upstream_dns", s.handleTestUpstreamDNS)
s.conf.HTTPRegister("GET", "/control/access/list", s.handleAccessList) s.conf.HTTPRegister("GET", "/control/access/list", s.handleAccessList)

2
go.mod
View File

@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome
go 1.14 go 1.14
require ( require (
github.com/AdguardTeam/dnsproxy v0.26.1 github.com/AdguardTeam/dnsproxy v0.27.1
github.com/AdguardTeam/golibs v0.4.2 github.com/AdguardTeam/golibs v0.4.2
github.com/AdguardTeam/urlfilter v0.10.0 github.com/AdguardTeam/urlfilter v0.10.0
github.com/NYTimes/gziphandler v1.1.1 github.com/NYTimes/gziphandler v1.1.1

4
go.sum
View File

@ -1,5 +1,5 @@
github.com/AdguardTeam/dnsproxy v0.26.1 h1:ZdrrEuNlMqiBlJpcDStitO3m6VBGCb1AP7ndaor2I6E= github.com/AdguardTeam/dnsproxy v0.27.1 h1:CQ3vtGSNbHNeYkxC6pALwugTSssP2MnsjdxkvVMzEp4=
github.com/AdguardTeam/dnsproxy v0.26.1/go.mod h1:hOYFV9TW+pd5XKYz7KZf2FFD8SvSPqjyGTxUae86s58= github.com/AdguardTeam/dnsproxy v0.27.1/go.mod h1:hOYFV9TW+pd5XKYz7KZf2FFD8SvSPqjyGTxUae86s58=
github.com/AdguardTeam/golibs v0.4.0 h1:4VX6LoOqFe9p9Gf55BeD8BvJD6M6RDYmgEiHrENE9KU= github.com/AdguardTeam/golibs v0.4.0 h1:4VX6LoOqFe9p9Gf55BeD8BvJD6M6RDYmgEiHrENE9KU=
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o= github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o=

View File

@ -47,7 +47,7 @@ func TestAuth(t *testing.T) {
// add session with TTL = 2 sec // add session with TTL = 2 sec
s = session{} s = session{}
s.expire = uint32(now + 2) s.expire = uint32(time.Now().UTC().Unix() + 2)
a.addSession(sess, &s) a.addSession(sess, &s)
assert.True(t, a.CheckSession(sessStr) == 0) assert.True(t, a.CheckSession(sessStr) == 0)
@ -59,7 +59,7 @@ func TestAuth(t *testing.T) {
// the session is still alive // the session is still alive
assert.True(t, a.CheckSession(sessStr) == 0) assert.True(t, a.CheckSession(sessStr) == 0)
// reset our expiration time because CheckSession() has just updated it // reset our expiration time because CheckSession() has just updated it
s.expire = uint32(now + 2) s.expire = uint32(time.Now().UTC().Unix() + 2)
a.storeSession(sess, &s) a.storeSession(sess, &s)
a.Close() a.Close()

View File

@ -45,6 +45,7 @@ type configuration struct {
ProxyURL string `yaml:"http_proxy"` // Proxy address for our HTTP client ProxyURL string `yaml:"http_proxy"` // Proxy address for our HTTP client
Language string `yaml:"language"` // two-letter ISO 639-1 language code 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) RlimitNoFile uint `yaml:"rlimit_nofile"` // Maximum number of opened fd's per process (0: default)
DebugPProf bool `yaml:"debug_pprof"` // Enable pprof HTTP server on port 6060
// TTL for a web session (in hours) // TTL for a web session (in hours)
// An active session is automatically refreshed once a day. // An active session is automatically refreshed once a day.

View File

@ -55,9 +55,6 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
"language": config.Language, "language": config.Language,
"protection_enabled": c.ProtectionEnabled, "protection_enabled": c.ProtectionEnabled,
"bootstrap_dns": c.BootstrapDNS,
"upstream_dns": c.UpstreamDNS,
"all_servers": c.AllServers,
} }
jsonVal, err := json.Marshal(data) jsonVal, err := json.Marshal(data)

View File

@ -351,6 +351,11 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
registerControlHandlers() registerControlHandlers()
returnOK(w)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
// this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block // 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 // until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely
if restartHTTP { if restartHTTP {
@ -358,8 +363,6 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
_ = Context.web.httpServer.Shutdown(context.TODO()) _ = Context.web.httpServer.Shutdown(context.TODO())
}() }()
} }
returnOK(w)
} }
func (web *Web) registerInstallHandlers() { func (web *Web) registerInstallHandlers() {

View File

@ -548,7 +548,9 @@ func handleUpdate(w http.ResponseWriter, r *http.Request) {
} }
returnOK(w) returnOK(w)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
time.Sleep(time.Second) // wait (hopefully) until response is sent (not sure whether it's really necessary)
go finishUpdate(u) go finishUpdate(u)
} }

View File

@ -208,6 +208,11 @@ func run(args options) {
} }
} }
// 'clients' module uses 'dnsfilter' module's static data (dnsfilter.BlockedSvcKnown()),
// so we have to initialize dnsfilter's static data first,
// but also avoid relying on automatic Go init() function
dnsfilter.InitModule()
config.DHCP.WorkDir = Context.workDir config.DHCP.WorkDir = Context.workDir
config.DHCP.HTTPRegister = httpRegister config.DHCP.HTTPRegister = httpRegister
config.DHCP.ConfigModified = onConfigModified config.DHCP.ConfigModified = onConfigModified
@ -242,6 +247,16 @@ func run(args options) {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if config.DebugPProf {
mux := http.NewServeMux()
util.PProfRegisterWebHandlers(mux)
go func() {
log.Info("pprof: listening on localhost:6060")
err := http.ListenAndServe("localhost:6060", mux)
log.Error("Error while running the pprof server: %s", err)
}()
}
} }
err := os.MkdirAll(Context.getDataDir(), 0755) err := os.MkdirAll(Context.getDataDir(), 0755)

View File

@ -279,11 +279,14 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
tlsConfigStatus: t.status, tlsConfigStatus: t.status,
} }
marshalTLS(w, data2) marshalTLS(w, data2)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
// this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block // 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 // until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely
if restartHTTPS { if restartHTTPS {
go func() { go func() {
time.Sleep(time.Second) // TODO: could not find a way to reliably know that data was fully sent to client by https server, so we wait a bit to let response through before closing the server
Context.web.TLSConfigChanged(data) Context.web.TLSConfigChanged(data)
}() }()
} }

View File

@ -1,6 +1,70 @@
# AdGuard Home API Change Log # AdGuard Home API Change Log
## v0.102: API changes
### API: Get general status: GET /control/status
* Removed "upstream_dns", "bootstrap_dns", "all_servers" parameters
### API: Get DNS general settings: GET /control/dns_info
* Added "parallel_requests", "upstream_dns", "bootstrap_dns" parameters
Request:
GET /control/dns_info
Response:
200 OK
{
"upstream_dns": ["tls://...", ...],
"bootstrap_dns": ["1.2.3.4", ...],
"protection_enabled": true | false,
"ratelimit": 1234,
"blocking_mode": "default" | "nxdomain" | "null_ip" | "custom_ip",
"blocking_ipv4": "1.2.3.4",
"blocking_ipv6": "1:2:3::4",
"edns_cs_enabled": true | false,
"dnssec_enabled": true | false
"disable_ipv6": true | false,
"fastest_addr": true | false, // use Fastest Address algorithm
"parallel_requests": true | false, // send DNS requests to all upstream servers at once
}
### API: Set DNS general settings: POST /control/dns_config
* Added "parallel_requests", "upstream_dns", "bootstrap_dns" parameters
* removed /control/set_upstreams_config method
Request:
POST /control/dns_config
{
"upstream_dns": ["tls://...", ...],
"bootstrap_dns": ["1.2.3.4", ...],
"protection_enabled": true | false,
"ratelimit": 1234,
"blocking_mode": "default" | "nxdomain" | "null_ip" | "custom_ip",
"blocking_ipv4": "1.2.3.4",
"blocking_ipv6": "1:2:3::4",
"edns_cs_enabled": true | false,
"dnssec_enabled": true | false
"disable_ipv6": true | false,
"fastest_addr": true | false, // use Fastest Address algorithm
"parallel_requests": true | false, // send DNS requests to all upstream servers at once
}
Response:
200 OK
## v0.101: API changes ## v0.101: API changes
### API: Refresh filters: POST /control/filtering/refresh ### API: Refresh filters: POST /control/filtering/refresh

View File

@ -0,0 +1,23 @@
FROM alpine:latest
LABEL maintainer="AdGuard Team <devteam@adguard.com>"
# Update CA certs
RUN apk --no-cache --update add ca-certificates libcap && \
rm -rf /var/cache/apk/* && \
mkdir -p /opt/adguardhome/conf /opt/adguardhome/work && \
chown -R nobody: /opt/adguardhome
COPY --chown=nobody:nogroup ./AdGuardHome /opt/adguardhome/AdGuardHome
RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 853/tcp 3000/tcp
VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
WORKDIR /opt/adguardhome/work
#USER nobody
ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
CMD ["-h", "0.0.0.0", "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work", "--no-check-update"]

View File

@ -0,0 +1,6 @@
## Docker images
* `Dockerfile` is used for local development. Build it using `make docker` command.
* `Dockerfile.hub` is used to publish AdGuard images to Docker Hub: https://hub.docker.com/r/adguard/adguardhome
Check out `build_docker.sh` for the details.

5
packaging/snap/README.md Normal file
View File

@ -0,0 +1,5 @@
## Snapcraft
Configuration for our snap.
Check out `build_snap.sh` for more details.

View File

@ -0,0 +1,32 @@
name: adguard-home
base: core18
version: 'dev_version'
summary: Network-wide ads & trackers blocking DNS server
description: |
AdGuard Home is a network-wide software for blocking ads & tracking. After
you set it up, it'll cover ALL your home devices, and you don't need any
client-side software for that.
It operates as a DNS server that re-routes tracking domains to a "black hole,"
thus preventing your devices from connecting to those servers. It's based
on software we use for our public AdGuard DNS servers -- both share a lot
of common code.
grade: stable
confinement: strict
parts:
adguard-home:
plugin: make
source: .
build-snaps: [ node/13/stable, go ]
build-packages: [ git, build-essential ]
override-build: |
make clean
make
cp AdGuardHome ${SNAPCRAFT_PART_INSTALL}/
apps:
adguard-home:
command: AdGuardHome -w ${SNAP_DATA} --no-check-update
plugs: [ network-bind ]
daemon: simple
restart-condition: always

View File

@ -238,7 +238,7 @@ func (l *queryLog) getData(params getDataParams) map[string]interface{} {
entries := append(memoryEntries, fileEntries...) entries := append(memoryEntries, fileEntries...)
if len(entries) > getDataLimit { if len(entries) > getDataLimit {
// remove extra records // remove extra records
entries = entries[(len(entries) - getDataLimit):] entries = entries[:getDataLimit]
} }
if len(entries) == getDataLimit { if len(entries) == getDataLimit {
// change the "oldest" value here. // change the "oldest" value here.

41
snapcraft.yaml Normal file
View File

@ -0,0 +1,41 @@
# Note that this snapcraft.yaml file is used for automatic Edge channel builds ONLY!
# We use packaging/snap/snapcraft.yaml for beta and release builds
# Check out build_snap.sh for more details
name: adguard-home
base: core18
version: 'edge'
summary: Network-wide ads & trackers blocking DNS server
description: |
AdGuard Home is a network-wide software for blocking ads & tracking. After
you set it up, it'll cover ALL your home devices, and you don't need any
client-side software for that.
It operates as a DNS server that re-routes tracking domains to a "black hole,"
thus preventing your devices from connecting to those servers. It's based
on software we use for our public AdGuard DNS servers -- both share a lot
of common code.
grade: stable
confinement: strict
architectures:
- build-on: amd64
- build-on: armhf
- build-on: i386
- build-on: arm64
parts:
adguard-home:
plugin: make
source: .
build-snaps: [ node/13/stable, go ]
build-packages: [ git, build-essential ]
override-build: |
make clean
make
cp AdGuardHome ${SNAPCRAFT_PART_INSTALL}/
apps:
adguard-home:
command: AdGuardHome -w ${SNAP_DATA} --no-check-update
plugs: [ network-bind ]
daemon: simple
restart-condition: always

352
util/pprof.go Normal file
View File

@ -0,0 +1,352 @@
// Modified pprof package
// The problem with the mainstream package is that it registers HTTP handlers
// inside init() function.
// This behaviour makes it impossible to enable pprof on demand in runtime.
// But this package has a separate function for this -
// PProfRegisterWebHandlers().
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package pprof serves via its HTTP server runtime profiling data
// in the format expected by the pprof visualization tool.
//
// The package is typically only imported for the side effect of
// registering its HTTP handlers.
// The handled paths all begin with /debug/pprof/.
//
// To use pprof, link this package into your program:
// import _ "net/http/pprof"
//
// If your application is not already running an http server, you
// need to start one. Add "net/http" and "log" to your imports and
// the following code to your main function:
//
// go func() {
// log.Println(http.ListenAndServe("localhost:6060", nil))
// }()
//
// If you are not using DefaultServeMux, you will have to register handlers
// with the mux you are using.
//
// Then use the pprof tool to look at the heap profile:
//
// go tool pprof http://localhost:6060/debug/pprof/heap
//
// Or to look at a 30-second CPU profile:
//
// go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
//
// Or to look at the goroutine blocking profile, after calling
// runtime.SetBlockProfileRate in your program:
//
// go tool pprof http://localhost:6060/debug/pprof/block
//
// Or to collect a 5-second execution trace:
//
// wget http://localhost:6060/debug/pprof/trace?seconds=5
//
// Or to look at the holders of contended mutexes, after calling
// runtime.SetMutexProfileFraction in your program:
//
// go tool pprof http://localhost:6060/debug/pprof/mutex
//
// To view all available profiles, open http://localhost:6060/debug/pprof/
// in your browser.
//
// For a study of the facility in action, visit
//
// https://blog.golang.org/2011/06/profiling-go-programs.html
//
package util
import (
"bufio"
"bytes"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"runtime"
"runtime/pprof"
"runtime/trace"
"sort"
"strconv"
"strings"
"time"
)
// PProfRegisterWebHandlers - register HTTP handlers for pprof
func PProfRegisterWebHandlers(mux *http.ServeMux) {
mux.HandleFunc("/debug/pprof/", Index)
mux.HandleFunc("/debug/pprof/cmdline", Cmdline)
mux.HandleFunc("/debug/pprof/profile", Profile)
mux.HandleFunc("/debug/pprof/symbol", Symbol)
mux.HandleFunc("/debug/pprof/trace", Trace)
}
// Cmdline responds with the running program's
// command line, with arguments separated by NUL bytes.
// The package initialization registers it as /debug/pprof/cmdline.
func Cmdline(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, strings.Join(os.Args, "\x00"))
}
func sleep(w http.ResponseWriter, d time.Duration) {
var clientGone <-chan bool
if cn, ok := w.(http.CloseNotifier); ok {
clientGone = cn.CloseNotify()
}
select {
case <-time.After(d):
case <-clientGone:
}
}
func durationExceedsWriteTimeout(r *http.Request, seconds float64) bool {
srv, ok := r.Context().Value(http.ServerContextKey).(*http.Server)
return ok && srv.WriteTimeout != 0 && seconds >= srv.WriteTimeout.Seconds()
}
func serveError(w http.ResponseWriter, status int, txt string) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Go-Pprof", "1")
w.Header().Del("Content-Disposition")
w.WriteHeader(status)
fmt.Fprintln(w, txt)
}
// Profile responds with the pprof-formatted cpu profile.
// Profiling lasts for duration specified in seconds GET parameter, or for 30 seconds if not specified.
// The package initialization registers it as /debug/pprof/profile.
func Profile(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
sec, err := strconv.ParseInt(r.FormValue("seconds"), 10, 64)
if sec <= 0 || err != nil {
sec = 30
}
if durationExceedsWriteTimeout(r, float64(sec)) {
serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
return
}
// Set Content Type assuming StartCPUProfile will work,
// because if it does it starts writing.
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", `attachment; filename="profile"`)
if err := pprof.StartCPUProfile(w); err != nil {
// StartCPUProfile failed, so no writes yet.
serveError(w, http.StatusInternalServerError,
fmt.Sprintf("Could not enable CPU profiling: %s", err))
return
}
sleep(w, time.Duration(sec)*time.Second)
pprof.StopCPUProfile()
}
// Trace responds with the execution trace in binary form.
// Tracing lasts for duration specified in seconds GET parameter, or for 1 second if not specified.
// The package initialization registers it as /debug/pprof/trace.
func Trace(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
sec, err := strconv.ParseFloat(r.FormValue("seconds"), 64)
if sec <= 0 || err != nil {
sec = 1
}
if durationExceedsWriteTimeout(r, sec) {
serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
return
}
// Set Content Type assuming trace.Start will work,
// because if it does it starts writing.
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", `attachment; filename="trace"`)
if err := trace.Start(w); err != nil {
// trace.Start failed, so no writes yet.
serveError(w, http.StatusInternalServerError,
fmt.Sprintf("Could not enable tracing: %s", err))
return
}
sleep(w, time.Duration(sec*float64(time.Second)))
trace.Stop()
}
// Symbol looks up the program counters listed in the request,
// responding with a table mapping program counters to function names.
// The package initialization registers it as /debug/pprof/symbol.
func Symbol(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
// We have to read the whole POST body before
// writing any output. Buffer the output here.
var buf bytes.Buffer
// We don't know how many symbols we have, but we
// do have symbol information. Pprof only cares whether
// this number is 0 (no symbols available) or > 0.
fmt.Fprintf(&buf, "num_symbols: 1\n")
var b *bufio.Reader
if r.Method == "POST" {
b = bufio.NewReader(r.Body)
} else {
b = bufio.NewReader(strings.NewReader(r.URL.RawQuery))
}
for {
word, err := b.ReadSlice('+')
if err == nil {
word = word[0 : len(word)-1] // trim +
}
pc, _ := strconv.ParseUint(string(word), 0, 64)
if pc != 0 {
f := runtime.FuncForPC(uintptr(pc))
if f != nil {
fmt.Fprintf(&buf, "%#x %s\n", pc, f.Name())
}
}
// Wait until here to check for err; the last
// symbol will have an err because it doesn't end in +.
if err != nil {
if err != io.EOF {
fmt.Fprintf(&buf, "reading request: %v\n", err)
}
break
}
}
w.Write(buf.Bytes())
}
// Handler returns an HTTP handler that serves the named profile.
func Handler(name string) http.Handler {
return handler(name)
}
type handler string
func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
p := pprof.Lookup(string(name))
if p == nil {
serveError(w, http.StatusNotFound, "Unknown profile")
return
}
gc, _ := strconv.Atoi(r.FormValue("gc"))
if name == "heap" && gc > 0 {
runtime.GC()
}
debug, _ := strconv.Atoi(r.FormValue("debug"))
if debug != 0 {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
} else {
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name))
}
p.WriteTo(w, debug)
}
var profileDescriptions = map[string]string{
"allocs": "A sampling of all past memory allocations",
"block": "Stack traces that led to blocking on synchronization primitives",
"cmdline": "The command line invocation of the current program",
"goroutine": "Stack traces of all current goroutines",
"heap": "A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.",
"mutex": "Stack traces of holders of contended mutexes",
"profile": "CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.",
"threadcreate": "Stack traces that led to the creation of new OS threads",
"trace": "A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.",
}
// Index responds with the pprof-formatted profile named by the request.
// For example, "/debug/pprof/heap" serves the "heap" profile.
// Index responds to a request for "/debug/pprof/" with an HTML page
// listing the available profiles.
func Index(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/debug/pprof/") {
name := strings.TrimPrefix(r.URL.Path, "/debug/pprof/")
if name != "" {
handler(name).ServeHTTP(w, r)
return
}
}
type profile struct {
Name string
Href string
Desc string
Count int
}
var profiles []profile
for _, p := range pprof.Profiles() {
profiles = append(profiles, profile{
Name: p.Name(),
Href: p.Name() + "?debug=1",
Desc: profileDescriptions[p.Name()],
Count: p.Count(),
})
}
// Adding other profiles exposed from within this package
for _, p := range []string{"cmdline", "profile", "trace"} {
profiles = append(profiles, profile{
Name: p,
Href: p,
Desc: profileDescriptions[p],
})
}
sort.Slice(profiles, func(i, j int) bool {
return profiles[i].Name < profiles[j].Name
})
if err := indexTmpl.Execute(w, profiles); err != nil {
log.Print(err)
}
}
var indexTmpl = template.Must(template.New("index").Parse(`<html>
<head>
<title>/debug/pprof/</title>
<style>
.profile-name{
display:inline-block;
width:6rem;
}
</style>
</head>
<body>
/debug/pprof/<br>
<br>
Types of profiles available:
<table>
<thead><td>Count</td><td>Profile</td></thead>
{{range .}}
<tr>
<td>{{.Count}}</td><td><a href={{.Href}}>{{.Name}}</a></td>
</tr>
{{end}}
</table>
<a href="goroutine?debug=2">full goroutine stack dump</a>
<br/>
<p>
Profile Descriptions:
<ul>
{{range .}}
<li><div class=profile-name>{{.Name}}:</div> {{.Desc}}</li>
{{end}}
</ul>
</p>
</body>
</html>
`))