diff --git a/.gitignore b/.gitignore index 78d61cdf..77c43738 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,9 @@ coverage.txt # Test output dnsfilter/tests/top-1m.csv dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof + +# Snapcraft build temporary files +*.snap +launchpad_credentials +snapcraft_login +snapcraft.yaml.bak \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index 341a62d4..60b7ee1c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,6 +13,7 @@ run: skip-files: - ".*generated.*" - dnsfilter/rule_to_regexp.go + - util/pprof.go - ".*_test.go" @@ -65,5 +66,7 @@ issues: - Error return value of ..*.Shutdown. # goconst - 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 - - G204 \ No newline at end of file + - G204 diff --git a/.travis.yml b/.travis.yml index fd369c2e..b2e5a2dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -86,7 +86,7 @@ matrix: - node -v - npm -v # Prepare releases - - ./release.sh + - ./build_release.sh - ls -l dist deploy: @@ -118,4 +118,4 @@ matrix: - docker login -u="$DOCKER_USER" -p="$DOCKER_PASSWORD" - ./build_docker.sh after_script: - - docker images + - docker images diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 4c8e0fda..5a3b001c 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -882,6 +882,9 @@ 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", @@ -890,6 +893,8 @@ Response: "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 } @@ -900,6 +905,9 @@ 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", @@ -908,6 +916,8 @@ Request: "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: diff --git a/Makefile b/Makefile index 74934562..ff32c957 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,9 @@ GOPATH := $(shell go env GOPATH) JSFILES = $(shell find client -path client/node_modules -prune -o -type f -name '*.js') STATIC = build/static/index.html CHANNEL ?= release +DOCKER_IMAGE_DEV_NAME=adguardhome-dev +DOCKERFILE=packaging/docker/Dockerfile +DOCKERFILE_HUB=packaging/docker/Dockerfile.travis 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)" 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: $(MAKE) cleanfast rm -rf build diff --git a/README.md b/README.md index 99575e34..9cf5953e 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,19 @@ GolangCI +
Latest release + + adguard-home + + + Docker Pulls + + + Docker Stars +


@@ -59,7 +69,9 @@ It operates as a DNS server that re-routes tracking domains to a "black hole," t ## 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). @@ -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) * [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 write your own hosts blocklists properly](https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists) ### API @@ -165,11 +178,12 @@ You are welcome to fork this repository, make your changes and submit a pull req ### Test unstable versions -There are two 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. +There are three options how you can install an unstable version. + +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 * [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) @@ -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) * [MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips.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) ### Report issues diff --git a/build_docker.sh b/build_docker.sh index 1123d269..81173574 100755 --- a/build_docker.sh +++ b/build_docker.sh @@ -4,7 +4,7 @@ set -eE set -o pipefail set -x -DOCKERFILE="Dockerfile.travis" +DOCKERFILE="packaging/docker/Dockerfile.hub" IMAGE_NAME="adguard/adguardhome" if [[ "${TRAVIS_BRANCH}" == "master" ]] diff --git a/release.sh b/build_release.sh similarity index 100% rename from release.sh rename to build_release.sh diff --git a/build_snap.sh b/build_snap.sh new file mode 100755 index 00000000..f44b950c --- /dev/null +++ b/build_snap.sh @@ -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 < 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 \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 0f020587..874c9e1e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -167,9 +167,9 @@ } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "ms": { @@ -778,9 +778,9 @@ } }, "acorn": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.2.tgz", - "integrity": "sha512-cJrKCNcr2kv8dlDnbw+JPUGjHZzo4myaxOLmpOX8a+rgX94YeTcTMv/LFJUSByRpc+i4GgVnnhLxvMu/2Y+rqw==", + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true }, "acorn-dynamic-import": { @@ -3208,6 +3208,17 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -3734,6 +3745,22 @@ "nth-check": "~1.0.1" } }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true + }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + } + }, "css-what": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", @@ -3752,6 +3779,33 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "csso": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", + "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha.39" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.39", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", + "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", + "dev": true, + "requires": { + "mdn-data": "2.0.6", + "source-map": "^0.6.1" + } + }, + "mdn-data": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", + "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==", + "dev": true + } + } + }, "csstype": { "version": "2.6.8", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz", @@ -4024,9 +4078,9 @@ "dev": true }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } @@ -4918,6 +4972,12 @@ "acorn-jsx": "^3.0.0" } }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", @@ -6226,9 +6286,9 @@ }, "dependencies": { "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } @@ -6268,20 +6328,12 @@ "dev": true }, "gonzales-pe": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.2.3.tgz", - "integrity": "sha512-Kjhohco0esHQnOiqqdJeNz/5fyPkOMD/d6XVjwTAoPGUFh0mCollPUTUTa2OZy4dYNAqlPIQdTiNzJTWdd9Htw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", "dev": true, "requires": { - "minimist": "1.1.x" - }, - "dependencies": { - "minimist": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz", - "integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=", - "dev": true - } + "minimist": "^1.2.5" } }, "graceful-fs": { @@ -6324,6 +6376,12 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -6890,9 +6948,9 @@ "dev": true }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "micromatch": { @@ -7589,6 +7647,16 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "jsesc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", @@ -7706,13 +7774,13 @@ } }, "loader-fs-cache": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz", - "integrity": "sha1-VuC/CL2XCLJqdltoUJhAyN7J/bw=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz", + "integrity": "sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA==", "dev": true, "requires": { "find-cache-dir": "^0.1.1", - "mkdirp": "0.5.1" + "mkdirp": "^0.5.1" }, "dependencies": { "find-cache-dir": { @@ -7978,9 +8046,9 @@ } }, "mdn-data": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", - "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", "dev": true }, "media-typer": { @@ -8070,9 +8138,9 @@ }, "dependencies": { "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } @@ -8136,9 +8204,9 @@ } }, "minimist": { - "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "minimist-options": { @@ -8191,12 +8259,12 @@ } }, "mkdirp": { - "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "minimist": "0.0.8" + "minimist": "^1.2.5" } }, "move-concurrently": { @@ -8284,9 +8352,9 @@ "dev": true }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } @@ -8489,6 +8557,12 @@ "integrity": "sha512-05KzQ70lSeGSrZJQXE5wNDiTkBJDlUT/myi6RX9dVIvz7a7Qh4oH93BQdiPMn27nldYvVQCKMUaM83AfizZlsQ==", "dev": true }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, "object-keys": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", @@ -8512,6 +8586,18 @@ } } }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", @@ -8539,6 +8625,80 @@ } } }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } + } + }, "obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -8879,30 +9039,39 @@ "dev": true }, "portfinder": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", - "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", + "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", "dev": true, "requires": { - "async": "^1.5.2", - "debug": "^2.2.0", - "mkdirp": "0.5.x" + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.1" }, "dependencies": { "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "dev": true, "requires": { - "ms": "2.0.0" + "lodash": "^4.17.14" } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, @@ -10028,171 +10197,6 @@ "postcss-value-parser": "3.x", "svgo": "1.x", "xmldoc": "1.x" - }, - "dependencies": { - "coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "dev": true, - "requires": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - } - }, - "css-select": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz", - "integrity": "sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^2.1.2", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - }, - "dependencies": { - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", - "dev": true - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, - "requires": { - "boolbase": "~1.0.0" - } - } - } - }, - "csso": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz", - "integrity": "sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==", - "dev": true, - "requires": { - "css-tree": "1.0.0-alpha.29" - }, - "dependencies": { - "css-tree": { - "version": "1.0.0-alpha.29", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz", - "integrity": "sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==", - "dev": true, - "requires": { - "mdn-data": "~1.1.0", - "source-map": "^0.5.3" - } - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "svgo": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.2.2.tgz", - "integrity": "sha512-rAfulcwp2D9jjdGu+0CuqlrAUin6bBWrpoqXWwKDZZZJfXcUXQSxLJOFJCQCSA0x0pP2U0TxSlJu2ROq5Bq6qA==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.28", - "css-url-regex": "^1.1.0", - "csso": "^3.5.1", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "dependencies": { - "css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", - "dev": true - }, - "css-tree": { - "version": "1.0.0-alpha.28", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz", - "integrity": "sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w==", - "dev": true, - "requires": { - "mdn-data": "~1.1.0", - "source-map": "^0.5.3" - } - }, - "css-url-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/css-url-regex/-/css-url-regex-1.1.0.tgz", - "integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=", - "dev": true - }, - "object.values": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", - "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "dev": true - }, - "unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", - "dev": true - } - } - } } }, "postcss-syntax": { @@ -11772,6 +11776,12 @@ "safe-buffer": "^5.1.1" } }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, "state-toggle": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.1.tgz", @@ -11871,6 +11881,296 @@ } } }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -12572,9 +12872,9 @@ "dev": true }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "micromatch": { @@ -12645,6 +12945,66 @@ } } }, + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "dependencies": { + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-what": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", + "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==", + "dev": true + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + } + } + }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -13041,6 +13401,12 @@ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index a30e5d0b..ffbadfbd 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -1,7 +1,8 @@ { "client_settings": "Client settings", - "example_upstream_reserved": "you can specify DNS upstream <0>for a specific domain(s)", - "upstream_parallel": "Use parallel queries to speed up resolving by simultaneously querying all upstream servers", + "example_upstream_reserved": "You can specify DNS upstream <0>for the specific domain(s)", + "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_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", @@ -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_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_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_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.", @@ -453,6 +455,8 @@ "example_rewrite_wildcard": "rewrite responses for all <0>example.org subdomains.", "disable_ipv6": "Disable IPv6", "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_list": "It will perform these tasks: <0>Deactivate system DNSStubListener <0>Set DNS server address to 127.0.0.1 <0>Replace symbolic link target of /etc/resolv.conf with /run/systemd/resolve/resolv.conf <0>Stop DNSStubListener (reload systemd-resolved service)", "autofix_warning_result": "As a result all DNS requests from your system will be processed by AdGuard Home by default.", diff --git a/client/src/actions/dnsConfig.js b/client/src/actions/dnsConfig.js index 1976613e..c7a4dc2f 100644 --- a/client/src/actions/dnsConfig.js +++ b/client/src/actions/dnsConfig.js @@ -2,6 +2,7 @@ import { createAction } from 'redux-actions'; import apiClient from '../api/Api'; import { addErrorToast, addSuccessToast } from './index'; +import { normalizeTextarea } from '../helpers/helpers'; export const getDnsConfigRequest = createAction('GET_DNS_CONFIG_REQUEST'); 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) => { dispatch(setDnsConfigRequest()); try { - await apiClient.setDnsConfig(config); - dispatch(addSuccessToast('config_successfully_saved')); + 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(setDnsConfigSuccess(config)); } catch (error) { dispatch(addErrorToast({ error })); diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 0d212b1b..0cff2116 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -244,6 +244,7 @@ export const getDnsStatus = () => async (dispatch) => { dispatch(dnsStatusFailure()); window.location.reload(true); }; + const handleRequestSuccess = (response) => { const dnsStatus = response.data; 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 testUpstreamFailure = createAction('TEST_UPSTREAM_FAILURE'); export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS'); diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 94a05b19..f678d7c3 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -32,7 +32,6 @@ class Api { // Global methods 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_VERSION = { path: 'version.json', method: 'POST' }; GLOBAL_UPDATE = { path: 'update', method: 'POST' }; @@ -42,15 +41,6 @@ class Api { 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) { const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS; const config = { diff --git a/client/src/components/Dashboard/BlockedDomains.js b/client/src/components/Dashboard/BlockedDomains.js index 3823d52d..0bb61f4c 100644 --- a/client/src/components/Dashboard/BlockedDomains.js +++ b/client/src/components/Dashboard/BlockedDomains.js @@ -58,7 +58,7 @@ const BlockedDomains = ({ noDataText={t('no_domains_found')} minRows={6} defaultPageSize={100} - className="-highlight card-table-overflow stats__table" + className="-highlight card-table-overflow--limited stats__table" /> ); diff --git a/client/src/components/Dashboard/Clients.js b/client/src/components/Dashboard/Clients.js index 0a9ec903..f0b73ffe 100644 --- a/client/src/components/Dashboard/Clients.js +++ b/client/src/components/Dashboard/Clients.js @@ -119,7 +119,7 @@ const Clients = ({ noDataText={t('no_clients_found')} minRows={6} defaultPageSize={100} - className="-highlight card-table-overflow clients__table" + className="-highlight card-table-overflow--limited clients__table" getTrProps={(_state, rowInfo) => { if (!rowInfo) { return {}; diff --git a/client/src/components/Dashboard/QueriedDomains.js b/client/src/components/Dashboard/QueriedDomains.js index 85c39cfb..8bc7674a 100644 --- a/client/src/components/Dashboard/QueriedDomains.js +++ b/client/src/components/Dashboard/QueriedDomains.js @@ -59,7 +59,7 @@ const QueriedDomains = ({ noDataText={t('no_domains_found')} minRows={6} defaultPageSize={100} - className="-highlight card-table-overflow stats__table" + className="-highlight card-table-overflow--limited stats__table" /> ); diff --git a/client/src/components/Settings/Dns/Config/Form.js b/client/src/components/Settings/Dns/Config/Form.js index 1db400e8..46fae81e 100644 --- a/client/src/components/Settings/Dns/Config/Form.js +++ b/client/src/components/Settings/Dns/Config/Form.js @@ -17,26 +17,55 @@ import { } from '../../../../helpers/form'; 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 => ( + + )); let Form = ({ handleSubmit, submitting, invalid, processing, blockingMode, t, -}) => ( +}) =>
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
+ {checkboxes.map(({ name, placeholder, subtitle }) => +
+
+ +
+
)}
- {Object.values(BLOCKING_MODES).map(mode => ( -
  • - {`blocking_mode_${mode}`} -
  • - ))} + {Object.values(BLOCKING_MODES) + .map(mode => ( +
  • + {`blocking_mode_${mode}`} +
  • + ))}
    {getFields(processing, t)} @@ -108,40 +115,27 @@ let Form = ({
    {blockingMode === BLOCKING_MODES.custom_ip && ( -
    + {customIps.map(({ + description, + name, + validateIp, + }) =>
    -
    -
    -
    -
    - -
    - blocking_ipv6_desc -
    - -
    -
    +
    )}
    )}
    @@ -152,8 +146,7 @@ let Form = ({ > save_btn - -); + ; Form.propTypes = { blockingMode: PropTypes.string.isRequired, diff --git a/client/src/components/Settings/Dns/Upstream/Form.js b/client/src/components/Settings/Dns/Upstream/Form.js index e6164818..961ac252 100644 --- a/client/src/components/Settings/Dns/Upstream/Form.js +++ b/client/src/components/Settings/Dns/Upstream/Form.js @@ -6,21 +6,50 @@ import { Trans, withNamespaces } from 'react-i18next'; import flow from 'lodash/flow'; import classnames from 'classnames'; -import { renderSelectField } from '../../../../helpers/form'; 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: () => , + 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) => { const { t, handleSubmit, testUpstream, - upstreamDns, - bootstrapDns, - allServers, submitting, invalid, - processingSetUpstream, + processingSetConfig, processingTestUpstream, + fastest_addr, + parallel_requests, + upstream_dns, + bootstrap_dns, } = props; const testButtonClass = classnames({ @@ -28,61 +57,49 @@ let Form = (props) => { 'btn btn-primary btn-standard mr-2 btn-loading': processingTestUpstream, }); + const INPUT_FIELDS = getInputFields(parallel_requests, fastest_addr); + return (
    -
    -
    - - -
    -
    -
    -
    - -
    -
    + {INPUT_FIELDS.map(({ + name, component, type, className, placeholder, getTitle, subtitle, disabled, + }) =>
    + {typeof getTitle === 'function' && getTitle()} + +
    )}

    -
    -
    - -
    - bootstrap_dns_desc -
    - +
    + +
    + bootstrap_dns_desc
    +
    @@ -92,12 +109,11 @@ let Form = (props) => { className={testButtonClass} onClick={() => testUpstream({ - upstream_dns: upstreamDns, - bootstrap_dns: bootstrapDns, - all_servers: allServers, + upstream_dns, + bootstrap_dns, }) } - disabled={!upstreamDns || processingTestUpstream} + disabled={!upstream_dns || processingTestUpstream} > test_upstream_btn @@ -105,7 +121,7 @@ let Form = (props) => { type="submit" className="btn btn-success btn-standard" disabled={ - submitting || invalid || processingSetUpstream || processingTestUpstream + submitting || invalid || processingSetConfig || processingTestUpstream } > apply_btn @@ -122,24 +138,28 @@ Form.propTypes = { submitting: PropTypes.bool, invalid: PropTypes.bool, initialValues: PropTypes.object, - upstreamDns: PropTypes.string, - bootstrapDns: PropTypes.string, - allServers: PropTypes.bool, + upstream_dns: PropTypes.string, + bootstrap_dns: PropTypes.string, + fastest_addr: PropTypes.bool, + parallel_requests: PropTypes.bool, processingTestUpstream: PropTypes.bool, - processingSetUpstream: PropTypes.bool, + processingSetConfig: PropTypes.bool, t: PropTypes.func, }; const selector = formValueSelector('upstreamForm'); Form = connect((state) => { - const upstreamDns = selector(state, 'upstream_dns'); - const bootstrapDns = selector(state, 'bootstrap_dns'); - const allServers = selector(state, 'all_servers'); + const upstream_dns = selector(state, 'upstream_dns'); + const bootstrap_dns = selector(state, 'bootstrap_dns'); + const fastest_addr = selector(state, 'fastest_addr'); + const parallel_requests = selector(state, 'parallel_requests'); + return { - upstreamDns, - bootstrapDns, - allServers, + upstream_dns, + bootstrap_dns, + fastest_addr, + parallel_requests, }; })(Form); diff --git a/client/src/components/Settings/Dns/Upstream/index.js b/client/src/components/Settings/Dns/Upstream/index.js index 292c2cfd..62a93795 100644 --- a/client/src/components/Settings/Dns/Upstream/index.js +++ b/client/src/components/Settings/Dns/Upstream/index.js @@ -7,7 +7,7 @@ import Card from '../../../ui/Card'; class Upstream extends Component { handleSubmit = (values) => { - this.props.setUpstream(values); + this.props.setDnsConfig(values); }; handleTest = (values) => { @@ -17,11 +17,14 @@ class Upstream extends Component { render() { const { t, - upstreamDns: upstream_dns, - bootstrapDns: bootstrap_dns, - allServers: all_servers, - processingSetUpstream, processingTestUpstream, + dnsConfig: { + upstream_dns, + bootstrap_dns, + fastest_addr, + parallel_requests, + processingSetConfig, + }, } = this.props; return ( @@ -36,12 +39,13 @@ class Upstream extends Component { initialValues={{ upstream_dns, bootstrap_dns, - all_servers, + fastest_addr, + parallel_requests, }} testUpstream={this.handleTest} onSubmit={this.handleSubmit} processingTestUpstream={processingTestUpstream} - processingSetUpstream={processingSetUpstream} + processingSetConfig={processingSetConfig} />
    @@ -51,14 +55,11 @@ class Upstream extends Component { } Upstream.propTypes = { - upstreamDns: PropTypes.string, - bootstrapDns: PropTypes.string, - allServers: PropTypes.bool, - setUpstream: PropTypes.func.isRequired, testUpstream: PropTypes.func.isRequired, - processingSetUpstream: PropTypes.bool.isRequired, processingTestUpstream: PropTypes.bool.isRequired, t: PropTypes.func.isRequired, + dnsConfig: PropTypes.object.isRequired, + setDnsConfig: PropTypes.func.isRequired, }; export default withNamespaces()(Upstream); diff --git a/client/src/components/Settings/Dns/index.js b/client/src/components/Settings/Dns/index.js index aae1fdab..8eb6e5a4 100644 --- a/client/src/components/Settings/Dns/index.js +++ b/client/src/components/Settings/Dns/index.js @@ -10,7 +10,6 @@ import Loading from '../../ui/Loading'; class Dns extends Component { componentDidMount() { - this.props.getDnsSettings(); this.props.getAccessList(); this.props.getDnsConfig(); } @@ -18,59 +17,45 @@ class Dns extends Component { render() { const { t, - dashboard, settings, access, setAccessList, testUpstream, - setUpstream, dnsConfig, setDnsConfig, } = this.props; - const isDataLoading = dashboard.processingDnsSettings - || access.processing - || dnsConfig.processingGetConfig; - const isDataReady = !dashboard.processingDnsSettings - && !access.processing - && !dnsConfig.processingGetConfig; + const isDataLoading = access.processing || dnsConfig.processingGetConfig; return ( - {isDataLoading && } - {isDataReady && ( + {isDataLoading ? + : + - - - )} + } ); } } Dns.propTypes = { - dashboard: PropTypes.object.isRequired, settings: PropTypes.object.isRequired, - setUpstream: PropTypes.func.isRequired, testUpstream: PropTypes.func.isRequired, getAccessList: PropTypes.func.isRequired, setAccessList: PropTypes.func.isRequired, access: PropTypes.object.isRequired, - getDnsSettings: PropTypes.func.isRequired, dnsConfig: PropTypes.object.isRequired, setDnsConfig: PropTypes.func.isRequired, getDnsConfig: PropTypes.func.isRequired, diff --git a/client/src/components/ui/Card.css b/client/src/components/ui/Card.css index 577fa60a..e5840f7f 100644 --- a/client/src/components/ui/Card.css +++ b/client/src/components/ui/Card.css @@ -10,6 +10,11 @@ } .card-table-overflow { + overflow-y: auto; + max-height: 100%; +} + +.card-table-overflow--limited { overflow-y: auto; max-height: 280px; } diff --git a/client/src/components/ui/Checkbox.css b/client/src/components/ui/Checkbox.css index ff657898..bab88c79 100644 --- a/client/src/components/ui/Checkbox.css +++ b/client/src/components/ui/Checkbox.css @@ -83,6 +83,7 @@ .checkbox__input:disabled + .checkbox__label { cursor: default; + color: var(--gray); } .checkbox__input:disabled + .checkbox__label:before { diff --git a/client/src/components/ui/Guide.js b/client/src/components/ui/Guide.js index bc18366a..cbb0035b 100644 --- a/client/src/components/ui/Guide.js +++ b/client/src/components/ui/Guide.js @@ -34,6 +34,9 @@ const Guide = (props) => {
  • install_devices_router_list_3
  • +
  • + install_devices_router_list_4 +
  • diff --git a/client/src/containers/Dns.js b/client/src/containers/Dns.js index f32e1510..961c2f3e 100644 --- a/client/src/containers/Dns.js +++ b/client/src/containers/Dns.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { handleUpstreamChange, setUpstream, testUpstream, getDnsSettings } from '../actions'; +import { testUpstream } from '../actions'; import { getAccessList, setAccessList } from '../actions/access'; import { getRewritesList, @@ -25,8 +25,6 @@ const mapStateToProps = (state) => { }; const mapDispatchToProps = { - handleUpstreamChange, - setUpstream, testUpstream, getAccessList, setAccessList, @@ -34,7 +32,6 @@ const mapDispatchToProps = { addRewrite, deleteRewrite, toggleRewritesModal, - getDnsSettings, getDnsConfig, setDnsConfig, }; diff --git a/client/src/helpers/form.js b/client/src/helpers/form.js index d075feee..97c0daa2 100644 --- a/client/src/helpers/form.js +++ b/client/src/helpers/form.js @@ -117,29 +117,30 @@ export const renderSelectField = ({ placeholder, subtitle, disabled, + onClick, modifier = 'checkbox--form', meta: { touched, error }, }) => ( - - + {!disabled && + touched && + (error && {error})} + ); export const renderServiceField = ({ diff --git a/client/src/install/Setup/Settings.js b/client/src/install/Setup/Settings.js index 876aa05b..1c31143f 100644 --- a/client/src/install/Setup/Settings.js +++ b/client/src/install/Setup/Settings.js @@ -217,19 +217,19 @@ class Settings extends Component {
    {webStatus && -
    - {webStatus} - {isWebFixAvailable && - - } -
    -
    +
    + {webStatus} + {isWebFixAvailable && + + } +
    +
    }
    @@ -287,32 +287,33 @@ class Settings extends Component {
    {dnsStatus && - -
    - {dnsStatus} - {isDnsFixAvailable && - - } -
    -
    -

    - autofix_warning_text -

    - text]}> - autofix_warning_list - -

    - autofix_warning_result -

    -
    -
    -
    + +
    + {dnsStatus} + {isDnsFixAvailable && + + } +
    + {isDnsFixAvailable && +
    +

    + autofix_warning_text +

    + text]}> + autofix_warning_list + +

    + autofix_warning_result +

    +
    } +
    +
    }
    diff --git a/client/src/reducers/dnsConfig.js b/client/src/reducers/dnsConfig.js index ad45e631..9f55bee7 100644 --- a/client/src/reducers/dnsConfig.js +++ b/client/src/reducers/dnsConfig.js @@ -15,6 +15,8 @@ const dnsConfig = handleActions( const { blocking_ipv4, blocking_ipv6, + upstream_dns, + bootstrap_dns, ...values } = payload; @@ -23,6 +25,8 @@ const dnsConfig = handleActions( ...values, blocking_ipv4: blocking_ipv4 || DEFAULT_BLOCKING_IPV4, 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, }; }, diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index 046fce67..245cf17d 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -35,14 +35,6 @@ const settings = handleActions( const newSettingsList = { ...settingsList, [settingKey]: newSetting }; 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.testUpstreamFailure]: state => ({ ...state, processingTestUpstream: false }), [actions.testUpstreamSuccess]: state => ({ ...state, processingTestUpstream: false }), @@ -50,7 +42,6 @@ const settings = handleActions( { processing: true, processingTestUpstream: false, - processingSetUpstream: false, processingDhcpStatus: false, settingsList: {}, }, @@ -67,12 +58,9 @@ const dashboard = handleActions( version, dns_port: dnsPort, dns_addresses: dnsAddresses, - upstream_dns: upstreamDns, - bootstrap_dns: bootstrapDns, - all_servers: allServers, protection_enabled: protectionEnabled, - language, http_port: httpPort, + language, } = payload; const newState = { ...state, @@ -81,9 +69,6 @@ const dashboard = handleActions( dnsVersion: version, dnsPort, dnsAddresses, - upstreamDns: (upstreamDns && upstreamDns.join('\n')) || '', - bootstrapDns: (bootstrapDns && bootstrapDns.join('\n')) || '', - allServers, protectionEnabled, language, httpPort, @@ -138,11 +123,6 @@ const dashboard = handleActions( return newState; }, - [actions.handleUpstreamChange]: (state, { payload }) => { - const { upstreamDns } = payload; - return { ...state, upstreamDns }; - }, - [actions.getLanguageSuccess]: (state, { payload }) => { const newState = { ...state, language: payload }; return newState; @@ -159,24 +139,6 @@ const dashboard = handleActions( 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.getProfileFailure]: state => ({ ...state, processingProfile: false }), [actions.getProfileSuccess]: (state, { payload }) => ({ @@ -191,11 +153,7 @@ const dashboard = handleActions( processingVersion: true, processingClients: true, processingUpdate: false, - processingDnsSettings: true, processingProfile: true, - upstreamDns: '', - bootstrapDns: '', - allServers: false, protectionEnabled: false, processingProtection: false, httpPort: 80, diff --git a/dnsfilter/blocked_services.go b/dnsfilter/blocked_services.go index f11eed5a..1c77d1bc 100644 --- a/dnsfilter/blocked_services.go +++ b/dnsfilter/blocked_services.go @@ -24,6 +24,7 @@ var serviceRulesArray = []svc{ "||facebook.com^", "||facebook.net^", "||fbcdn.net^", + "||accountkit.com^", "||fb.me^", "||fb.com^", "||fbsbx.com^", @@ -31,7 +32,7 @@ var serviceRulesArray = []svc{ "||facebookcorewwwi.onion^", "||fbcdn.com^", }}, - {"twitter", []string{"||twitter.com^", "||t.co^", "||twimg.com^"}}, + {"twitter", []string{"||twitter.com^", "||twttr.com^", "||t.co^", "||twimg.com^"}}, {"youtube", []string{ "||youtube.com^", "||ytimg.com^", @@ -40,17 +41,31 @@ var serviceRulesArray = []svc{ "||youtubei.googleapis.com^", "||youtube-nocookie.com^", }}, - {"twitch", []string{"||twitch.tv^", "||ttvnw.net^"}}, - {"netflix", []string{"||nflxext.com^", "||netflix.com^"}}, + {"twitch", []string{"||twitch.tv^", "||ttvnw.net^", "||jtvnw.net^", "||twitchcdn.net^"}}, + {"netflix", []string{"||nflxext.com^", "||netflix.com^", "||nflximg.net^", "||nflxvideo.net^"}}, {"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^"}}, {"ok", []string{"||ok.ru^"}}, - {"skype", []string{"||skype.com^"}}, - {"vk", []string{"||vk.com^"}}, + {"skype", []string{"||skype.com^", "||skypeassets.com^"}}, + {"vk", []string{"||vk.com^", "||userapi.com^", "||vk-cdn.net^", "||vkuservideo.net^"}}, {"origin", []string{"||origin.com^", "||signin.ea.com^", "||accounts.ea.com^"}}, - {"steam", []string{"||steam.com^", "||steampowered.com^"}}, - {"epic_games", []string{"||epicgames.com^"}}, + {"steam", []string{ + "||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^"}}, {"mail_ru", []string{"||mail.ru^"}}, {"cloudflare", []string{ @@ -72,7 +87,13 @@ var serviceRulesArray = []svc{ {"amazon", []string{ "||amazon.com^", "||media-amazon.com^", + "||primevideo.com^", + "||amazontrust.com^", "||images-amazon.com^", + "||ssl-images-amazon.com^", + "||amazonpay.com^", + "||amazonpay.in^", + "||amazon-adsystem.com^", "||a2z.com^", "||amazon.ae^", "||amazon.ca^", @@ -88,6 +109,7 @@ var serviceRulesArray = []svc{ "||amazon.co.jp^", "||amazon.com.mx^", "||amazon.co.uk^", + "||createspace.com^", }}, {"ebay", []string{ "||ebay.com^", @@ -119,6 +141,7 @@ var serviceRulesArray = []svc{ {"tiktok", []string{ "||tiktok.com^", "||tiktokcdn.com^", + "||musical.ly^", "||snssdk.com^", "||amemv.com^", "||toutiao.com^", diff --git a/dnsfilter/dnsfilter.go b/dnsfilter/dnsfilter.go index 60a32d39..82b56187 100644 --- a/dnsfilter/dnsfilter.go +++ b/dnsfilter/dnsfilter.go @@ -628,6 +628,11 @@ func makeResult(rule rules.Rule, reason Reason) Result { return res } +// InitModule() - manually initialize blocked services map +func InitModule() { + initBlockedServices() +} + // New creates properly initialized DNS Filter that is ready to be used func New(c *Config, blockFilters []Filter) *Dnsfilter { @@ -677,8 +682,6 @@ func New(c *Config, blockFilters []Filter) *Dnsfilter { } d.BlockedServices = bsvcs - initBlockedServices() - if blockFilters != nil { err := d.initFiltering(nil, blockFilters) if err != nil { diff --git a/dnsfilter/dnsfilter_test.go b/dnsfilter/dnsfilter_test.go index 532e1c3c..6515b53d 100644 --- a/dnsfilter/dnsfilter_test.go +++ b/dnsfilter/dnsfilter_test.go @@ -532,96 +532,6 @@ func TestClientSettings(t *testing.T) { 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 { const dir = "./agh-test" _ = os.RemoveAll(dir) diff --git a/dnsfilter/rewrites.go b/dnsfilter/rewrites.go index 029d3332..cbc02a16 100644 --- a/dnsfilter/rewrites.go +++ b/dnsfilter/rewrites.go @@ -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] } -// 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 { if a[i].Type == dns.TypeCNAME && a[j].Type != dns.TypeCNAME { return false @@ -50,13 +53,18 @@ func (a rewritesArray) Less(i, j int) bool { return true } - if isWildcard(a[i].Domain) && !isWildcard(a[j].Domain) { - return false - } else if !isWildcard(a[i].Domain) && isWildcard(a[j].Domain) { - return true + if isWildcard(a[i].Domain) { + if !isWildcard(a[j].Domain) { + return false + } + } else { + if isWildcard(a[j].Domain) { + return true + } } - return i < j + // both are wildcards + return len(a[i].Domain) > len(a[j].Domain) } // Prepare entry for use @@ -86,6 +94,7 @@ func (d *Dnsfilter) prepareRewrites() { // Get the list of matched rewrite entries. // Priority: CNAME, A/AAAA; exact, wildcard. // 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 { rr := rewritesArray{} for _, r := range a { @@ -111,7 +120,10 @@ func findRewrites(a []RewriteEntry, host string) []RewriteEntry { break } } + } else { + rr = rr[:1] } + return rr } diff --git a/dnsfilter/rewrites_test.go b/dnsfilter/rewrites_test.go new file mode 100644 index 00000000..6da3e0f9 --- /dev/null +++ b/dnsfilter/rewrites_test.go @@ -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()) +} diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index b191a966..4f630afb 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -141,6 +141,8 @@ type FilteringConfig struct { // Respond with an empty answer to all AAAA requests AAAADisabled bool `yaml:"aaaa_disabled"` + FastestAddrAlgo bool `yaml:"fastest_addr"` // use Fastest Address algorithm + AllowedClients []string `yaml:"allowed_clients"` // IP addresses of whitelist clients DisallowedClients []string `yaml:"disallowed_clients"` // IP addresses of clients 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, AllServers: s.conf.AllServers, EnableEDNSClientSubnet: s.conf.EnableEDNSClientSubnet, + FindFastestAddr: s.conf.FastestAddrAlgo, } intlProxyConfig := proxy.Config{ diff --git a/dnsforward/dnsforward_http.go b/dnsforward/dnsforward_http.go index 414b728b..4658cbbf 100644 --- a/dnsforward/dnsforward_http.go +++ b/dnsforward/dnsforward_http.go @@ -22,6 +22,9 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string, } type dnsConfigJSON struct { + Upstreams []string `json:"upstream_dns"` + Bootstraps []string `json:"bootstrap_dns"` + ProtectionEnabled bool `json:"protection_enabled"` RateLimit uint32 `json:"ratelimit"` BlockingMode string `json:"blocking_mode"` @@ -30,11 +33,16 @@ type dnsConfigJSON struct { EDNSCSEnabled bool `json:"edns_cs_enabled"` DNSSECEnabled bool `json:"dnssec_enabled"` 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) { resp := dnsConfigJSON{} s.RLock() + resp.Upstreams = stringArrayDup(s.conf.UpstreamDNS) + resp.Bootstraps = stringArrayDup(s.conf.BootstrapDNS) + resp.ProtectionEnabled = s.conf.ProtectionEnabled resp.BlockingMode = s.conf.BlockingMode 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.DNSSECEnabled = s.conf.EnableDNSSEC resp.DisableIPv6 = s.conf.AAAADisabled + resp.FastestAddr = s.conf.FastestAddrAlgo + resp.ParallelRequests = s.conf.AllServers s.RUnlock() js, err := json.Marshal(resp) @@ -75,6 +85,7 @@ func checkBlockingMode(req dnsConfigJSON) bool { return true } +// nolint(gocyclo) - we need to check each JSON field separately func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) { req := dnsConfigJSON{} js, err := jsonutil.DecodeObject(&req, r.Body) @@ -83,6 +94,25 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) { 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) { httpError(r, w, http.StatusBadRequest, "blocking_mode: incorrect value") return @@ -91,6 +121,16 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) { restart := false 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") { s.conf.ProtectionEnabled = req.ProtectionEnabled } @@ -129,6 +169,14 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) { 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.conf.ConfigModified() @@ -144,51 +192,6 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) { type upstreamJSON struct { Upstreams []string `json:"upstream_dns"` // Upstreams 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 @@ -399,7 +402,6 @@ func (s *Server) handleDOH(w http.ResponseWriter, r *http.Request) { func (s *Server) registerHandlers() { s.conf.HTTPRegister("GET", "/control/dns_info", s.handleGetConfig) 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("GET", "/control/access/list", s.handleAccessList) diff --git a/go.mod b/go.mod index c5f10d71..85c12c02 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome go 1.14 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/urlfilter v0.10.0 github.com/NYTimes/gziphandler v1.1.1 diff --git a/go.sum b/go.sum index a50d167e..636d419e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/AdguardTeam/dnsproxy v0.26.1 h1:ZdrrEuNlMqiBlJpcDStitO3m6VBGCb1AP7ndaor2I6E= -github.com/AdguardTeam/dnsproxy v0.26.1/go.mod h1:hOYFV9TW+pd5XKYz7KZf2FFD8SvSPqjyGTxUae86s58= +github.com/AdguardTeam/dnsproxy v0.27.1 h1:CQ3vtGSNbHNeYkxC6pALwugTSssP2MnsjdxkvVMzEp4= +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/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o= diff --git a/home/auth_test.go b/home/auth_test.go index 38f826ec..a4829b93 100644 --- a/home/auth_test.go +++ b/home/auth_test.go @@ -47,7 +47,7 @@ func TestAuth(t *testing.T) { // add session with TTL = 2 sec s = session{} - s.expire = uint32(now + 2) + s.expire = uint32(time.Now().UTC().Unix() + 2) a.addSession(sess, &s) assert.True(t, a.CheckSession(sessStr) == 0) @@ -59,7 +59,7 @@ func TestAuth(t *testing.T) { // the session is still alive assert.True(t, a.CheckSession(sessStr) == 0) // 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.Close() diff --git a/home/config.go b/home/config.go index 2d78b42c..35940ebd 100644 --- a/home/config.go +++ b/home/config.go @@ -45,6 +45,7 @@ type configuration struct { ProxyURL string `yaml:"http_proxy"` // Proxy address for our HTTP client Language string `yaml:"language"` // two-letter ISO 639-1 language code RlimitNoFile uint `yaml:"rlimit_nofile"` // Maximum number of opened fd's per process (0: default) + DebugPProf bool `yaml:"debug_pprof"` // Enable pprof HTTP server on port 6060 // TTL for a web session (in hours) // An active session is automatically refreshed once a day. diff --git a/home/control.go b/home/control.go index 4d1dbe0c..310b9f20 100644 --- a/home/control.go +++ b/home/control.go @@ -55,9 +55,6 @@ func handleStatus(w http.ResponseWriter, r *http.Request) { "language": config.Language, "protection_enabled": c.ProtectionEnabled, - "bootstrap_dns": c.BootstrapDNS, - "upstream_dns": c.UpstreamDNS, - "all_servers": c.AllServers, } jsonVal, err := json.Marshal(data) diff --git a/home/control_install.go b/home/control_install.go index 4a39983f..864ad96b 100644 --- a/home/control_install.go +++ b/home/control_install.go @@ -351,6 +351,11 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { 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 // until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely if restartHTTP { @@ -358,8 +363,6 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { _ = Context.web.httpServer.Shutdown(context.TODO()) }() } - - returnOK(w) } func (web *Web) registerInstallHandlers() { diff --git a/home/control_update.go b/home/control_update.go index b0080e44..6115fac1 100644 --- a/home/control_update.go +++ b/home/control_update.go @@ -548,7 +548,9 @@ func handleUpdate(w http.ResponseWriter, r *http.Request) { } 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) } diff --git a/home/home.go b/home/home.go index 98ad8841..ebc25af4 100644 --- a/home/home.go +++ b/home/home.go @@ -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.HTTPRegister = httpRegister config.DHCP.ConfigModified = onConfigModified @@ -242,6 +247,16 @@ func run(args options) { if err != nil { 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) diff --git a/home/tls.go b/home/tls.go index 1e6267fe..5977f859 100644 --- a/home/tls.go +++ b/home/tls.go @@ -279,11 +279,14 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) { tlsConfigStatus: t.status, } 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 // until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely if restartHTTPS { go func() { - time.Sleep(time.Second) // TODO: could not find a way to reliably know that data was fully sent to client by https server, so we wait a bit to let response through before closing the server Context.web.TLSConfigChanged(data) }() } diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index 877c1f7c..193b69fc 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -1,6 +1,70 @@ # 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 ### API: Refresh filters: POST /control/filtering/refresh diff --git a/Dockerfile b/packaging/docker/Dockerfile similarity index 100% rename from Dockerfile rename to packaging/docker/Dockerfile diff --git a/packaging/docker/Dockerfile.hub b/packaging/docker/Dockerfile.hub new file mode 100644 index 00000000..0e11574e --- /dev/null +++ b/packaging/docker/Dockerfile.hub @@ -0,0 +1,23 @@ +FROM alpine:latest +LABEL maintainer="AdGuard Team " + +# 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"] diff --git a/packaging/docker/README.md b/packaging/docker/README.md new file mode 100644 index 00000000..0d7ff06f --- /dev/null +++ b/packaging/docker/README.md @@ -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. \ No newline at end of file diff --git a/packaging/snap/README.md b/packaging/snap/README.md new file mode 100644 index 00000000..764100ff --- /dev/null +++ b/packaging/snap/README.md @@ -0,0 +1,5 @@ +## Snapcraft + +Configuration for our snap. + +Check out `build_snap.sh` for more details. \ No newline at end of file diff --git a/packaging/snap/snapcraft.yaml b/packaging/snap/snapcraft.yaml new file mode 100644 index 00000000..1d6c5274 --- /dev/null +++ b/packaging/snap/snapcraft.yaml @@ -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 \ No newline at end of file diff --git a/querylog/qlog.go b/querylog/qlog.go index f8792d8f..247bb519 100644 --- a/querylog/qlog.go +++ b/querylog/qlog.go @@ -238,7 +238,7 @@ func (l *queryLog) getData(params getDataParams) map[string]interface{} { entries := append(memoryEntries, fileEntries...) if len(entries) > getDataLimit { // remove extra records - entries = entries[(len(entries) - getDataLimit):] + entries = entries[:getDataLimit] } if len(entries) == getDataLimit { // change the "oldest" value here. diff --git a/snapcraft.yaml b/snapcraft.yaml new file mode 100644 index 00000000..c5cda82b --- /dev/null +++ b/snapcraft.yaml @@ -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 diff --git a/util/pprof.go b/util/pprof.go new file mode 100644 index 00000000..031e6c78 --- /dev/null +++ b/util/pprof.go @@ -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(` + +/debug/pprof/ + + + +/debug/pprof/
    +
    +Types of profiles available: + + +{{range .}} + + + +{{end}} +
    CountProfile
    {{.Count}}{{.Name}}
    +full goroutine stack dump +
    +

    +Profile Descriptions: +

      +{{range .}} +
    • {{.Name}}:
      {{.Desc}}
    • +{{end}} +
    +

    + + +`))