Merge branch 'master' of github.com:AdguardTeam/AdGuardHome
Conflicts: openapi/openapi.yaml
This commit is contained in:
commit
efd2b31104
|
@ -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
|
|
@ -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
|
|
@ -86,7 +86,7 @@ matrix:
|
|||
- node -v
|
||||
- npm -v
|
||||
# Prepare releases
|
||||
- ./release.sh
|
||||
- ./build_release.sh
|
||||
- ls -l dist
|
||||
|
||||
deploy:
|
||||
|
|
|
@ -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:
|
||||
|
|
8
Makefile
8
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
|
||||
|
|
26
README.md
26
README.md
|
@ -26,9 +26,19 @@
|
|||
<a href="https://golangci.com/r/github.com/AdguardTeam/AdGuardHome">
|
||||
<img src="https://golangci.com/badges/github.com/AdguardTeam/AdGuardHome.svg" alt="GolangCI" />
|
||||
</a>
|
||||
<br />
|
||||
<a href="https://github.com/AdguardTeam/AdGuardHome/releases">
|
||||
<img src="https://img.shields.io/github/release/AdguardTeam/AdGuardHome/all.svg" alt="Latest release" />
|
||||
</a>
|
||||
<a href="https://snapcraft.io/adguard-home">
|
||||
<img alt="adguard-home" src="https://snapcraft.io/adguard-home/badge.svg" />
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/adguard/adguardhome">
|
||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/adguard/adguardhome.svg?maxAge=604800" />
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/adguard/adguardhome">
|
||||
<img alt="Docker Stars" src="https://img.shields.io/docker/stars/adguard/adguardhome.svg?maxAge=604800" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
@ -59,7 +69,9 @@ It operates as a DNS server that re-routes tracking domains to a "black hole," t
|
|||
<a id="getting-started"></a>
|
||||
## 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
|
|||
<a id="test-unstable-versions"></a>
|
||||
### 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)
|
||||
|
||||
<a id="reporting-issues"></a>
|
||||
### Report issues
|
||||
|
|
|
@ -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" ]]
|
||||
|
|
|
@ -0,0 +1,282 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -eE
|
||||
set -o pipefail
|
||||
set -x
|
||||
|
||||
BUILDER_IMAGE="adguard/snapcraft:1.0"
|
||||
SNAPCRAFT_TMPL="packaging/snap/snapcraft.yaml"
|
||||
SNAP_NAME="adguard-home"
|
||||
LAUNCHPAD_CREDENTIALS_DIR=".local/share/snapcraft/provider/launchpad"
|
||||
|
||||
if [[ -z ${VERSION} ]]; then
|
||||
VERSION=`git describe --abbrev=4 --dirty --always --tags`
|
||||
echo "VERSION env variable is not set, getting it from git: ${VERSION}"
|
||||
fi
|
||||
|
||||
# If bash is interactive, set `-it` parameter for docker run
|
||||
INTERACTIVE=""
|
||||
if [ -t 0 ] ; then
|
||||
INTERACTIVE="-it"
|
||||
fi
|
||||
|
||||
function usage() {
|
||||
cat <<EOF
|
||||
Usage: ${0##*/} command [options]
|
||||
|
||||
Please note that in order for the builds to work properly, you need to setup some env variables.
|
||||
|
||||
These are necessary for "remote-build' command.
|
||||
Read this doc on how to generate them: https://uci.readthedocs.io/en/latest/oauth.html
|
||||
|
||||
* LAUNCHPAD_KEY -- launchpad CI key
|
||||
* LAUNCHPAD_ACCESS_TOKEN -- launchpad access token
|
||||
* LAUNCHPAD_ACCESS_SECRET -- launchpad access secret
|
||||
|
||||
These are necessary for snapcraft publish command to work.
|
||||
They can be exported using "snapcraft export-login"
|
||||
|
||||
* SNAPCRAFT_MACAROON
|
||||
* SNAPCRAFT_UBUNTU_DISCHARGE
|
||||
* SNAPCRAFT_EMAIL
|
||||
|
||||
Examples:
|
||||
${0##*/} build-docker - builds snaps using remote-build inside a Docker environment
|
||||
${0##*/} build - builds snaps using remote-build
|
||||
${0##*/} publish-docker-beta - publishes snaps to the beta channel using Docker environment
|
||||
${0##*/} publish-docker-release - publishes snaps to the release channel using Docker environment
|
||||
${0##*/} publish-beta - publishes snaps to the beta channel
|
||||
${0##*/} publish-release - publishes snaps to the release channel
|
||||
${0##*/} cleanup - clean up temporary files that were created by the builds
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
#######################################
|
||||
# helper functions
|
||||
#######################################
|
||||
|
||||
function prepare() {
|
||||
if [ -z "${LAUNCHPAD_KEY}" ] || [ -z "${LAUNCHPAD_ACCESS_TOKEN}" ] || [ -z "${LAUNCHPAD_ACCESS_SECRET}" ]; then
|
||||
echo "Launchpad oauth tokens are not set, exiting"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${SNAPCRAFT_MACAROON}" ] || [ -z "${SNAPCRAFT_UBUNTU_DISCHARGE}" ] || [ -z "${SNAPCRAFT_EMAIL}" ]; then
|
||||
echo "Snapcraft auth params are not set, exiting"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Launchpad oauth tokens data is necessary to run snapcraft remote-build
|
||||
#
|
||||
# Here's an instruction on how to generate launchpad OAuth tokens:
|
||||
# https://uci.readthedocs.io/en/latest/oauth.html
|
||||
#
|
||||
# Launchpad credentials are necessary to run snapcraft remote-build command
|
||||
echo "[1]
|
||||
consumer_key = ${LAUNCHPAD_KEY}
|
||||
consumer_secret =
|
||||
access_token = ${LAUNCHPAD_ACCESS_TOKEN}
|
||||
access_secret = ${LAUNCHPAD_ACCESS_SECRET}
|
||||
" > launchpad_credentials
|
||||
|
||||
# Snapcraft login data
|
||||
# It can be exported using snapcraft export-login command
|
||||
echo "[login.ubuntu.com]
|
||||
macaroon = ${SNAPCRAFT_MACAROON}
|
||||
unbound_discharge = ${SNAPCRAFT_UBUNTU_DISCHARGE}
|
||||
email = ${SNAPCRAFT_EMAIL}" > snapcraft_login
|
||||
|
||||
# Prepare the snap configuration
|
||||
cp ${SNAPCRAFT_TMPL} ./snapcraft.yaml
|
||||
sed -i.bak 's/dev_version/'"${VERSION}"'/g' ./snapcraft.yaml
|
||||
rm -f snapcraft.yaml.bak
|
||||
}
|
||||
|
||||
build_snap() {
|
||||
# prepare credentials
|
||||
prepare
|
||||
|
||||
# copy them to the directory where snapcraft will be able to read them
|
||||
mkdir -p ~/${LAUNCHPAD_CREDENTIALS_DIR}
|
||||
cp -f snapcraft_login ~/${LAUNCHPAD_CREDENTIALS_DIR}/credentials
|
||||
chmod 600 ~/${LAUNCHPAD_CREDENTIALS_DIR}/credentials
|
||||
|
||||
# run the build
|
||||
snapcraft remote-build --build-on=${ARCH} --launchpad-accept-public-upload
|
||||
|
||||
# remove the credentials - we don't need them anymore
|
||||
rm -rf ~/${LAUNCHPAD_CREDENTIALS_DIR}
|
||||
|
||||
# remove version from the file name
|
||||
rename_snap_file
|
||||
|
||||
# cleanup credentials
|
||||
cleanup
|
||||
}
|
||||
|
||||
build_snap_docker() {
|
||||
# prepare credentials
|
||||
prepare
|
||||
|
||||
docker run ${INTERACTIVE} --rm \
|
||||
-v $(pwd):/build \
|
||||
-v $(pwd)/launchpad_credentials:/root/${LAUNCHPAD_CREDENTIALS_DIR}/credentials:ro \
|
||||
${BUILDER_IMAGE} \
|
||||
snapcraft remote-build --build-on=${ARCH} --launchpad-accept-public-upload
|
||||
|
||||
# remove version from the file name
|
||||
rename_snap_file
|
||||
|
||||
# cleanup credentials
|
||||
cleanup
|
||||
}
|
||||
|
||||
rename_snap_file() {
|
||||
# In order to make working with snaps easier later on
|
||||
# we remove version from the file name
|
||||
|
||||
# Check that the snap file exists
|
||||
snapFile="${SNAP_NAME}_${VERSION}_${ARCH}.snap"
|
||||
if [ ! -f ${snapFile} ]; then
|
||||
echo "Snap file ${snapFile} not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv -f ${snapFile} "${SNAP_NAME}_${ARCH}.snap"
|
||||
}
|
||||
|
||||
publish_snap() {
|
||||
# prepare credentials
|
||||
prepare
|
||||
|
||||
# Check that the snap file exists
|
||||
snapFile="${SNAP_NAME}_${ARCH}.snap"
|
||||
if [ ! -f ${snapFile} ]; then
|
||||
echo "Snap file ${snapFile} not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Login if necessary
|
||||
snapcraft login --with=snapcraft_login
|
||||
|
||||
# Push to the channel
|
||||
snapcraft push --release=${CHANNEL} ${snapFile}
|
||||
|
||||
# cleanup credentials
|
||||
cleanup
|
||||
}
|
||||
|
||||
publish_snap_docker() {
|
||||
# prepare credentials
|
||||
prepare
|
||||
|
||||
# Check that the snap file exists
|
||||
snapFile="${SNAP_NAME}_${ARCH}.snap"
|
||||
if [ ! -f ${snapFile} ]; then
|
||||
echo "Snap file ${snapFile} not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Login and publish the snap
|
||||
docker run ${INTERACTIVE} --rm \
|
||||
-v $(pwd):/build \
|
||||
${BUILDER_IMAGE} \
|
||||
sh -c "snapcraft login --with=/build/snapcraft_login && snapcraft push --release=${CHANNEL} /build/${snapFile}"
|
||||
|
||||
# cleanup credentials
|
||||
cleanup
|
||||
}
|
||||
|
||||
#######################################
|
||||
# main functions
|
||||
#######################################
|
||||
|
||||
build() {
|
||||
if [[ -n "$1" ]]; then
|
||||
echo "ARCH is set to $1"
|
||||
ARCH=$1 build_snap
|
||||
else
|
||||
ARCH=i386 build_snap
|
||||
ARCH=arm64 build_snap
|
||||
ARCH=armhf build_snap
|
||||
ARCH=amd64 build_snap
|
||||
fi
|
||||
}
|
||||
|
||||
build_docker() {
|
||||
if [[ -n "$1" ]]; then
|
||||
echo "ARCH is set to $1"
|
||||
ARCH=$1 build_snap_docker
|
||||
else
|
||||
ARCH=i386 build_snap_docker
|
||||
ARCH=arm64 build_snap_docker
|
||||
ARCH=armhf build_snap_docker
|
||||
ARCH=amd64 build_snap_docker
|
||||
fi
|
||||
}
|
||||
|
||||
publish_docker() {
|
||||
if [[ -z $1 ]]; then
|
||||
echo "No channel specified"
|
||||
exit 1
|
||||
fi
|
||||
CHANNEL="${1}"
|
||||
if [ "$CHANNEL" != "stable" ] && [ "$CHANNEL" != "beta" ]; then
|
||||
echo "$CHANNEL is an invalid value for the update channel!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ARCH=i386 publish_snap_docker
|
||||
ARCH=arm64 publish_snap_docker
|
||||
ARCH=armhf publish_snap_docker
|
||||
ARCH=amd64 publish_snap_docker
|
||||
}
|
||||
|
||||
publish() {
|
||||
if [[ -z $1 ]]; then
|
||||
echo "No channel specified"
|
||||
exit 1
|
||||
fi
|
||||
CHANNEL="${1}"
|
||||
if [ "$CHANNEL" != "stable" ] && [ "$CHANNEL" != "beta" ]; then
|
||||
echo "$CHANNEL is an invalid value for the update channel!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ARCH=i386 publish_snap
|
||||
ARCH=arm64 publish_snap
|
||||
ARCH=armhf publish_snap
|
||||
ARCH=amd64 publish_snap
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -f launchpad_credentials
|
||||
rm -f snapcraft.yaml
|
||||
rm -f snapcraft.yaml.bak
|
||||
rm -f snapcraft_login
|
||||
git checkout snapcraft.yaml
|
||||
}
|
||||
|
||||
#######################################
|
||||
# main
|
||||
#######################################
|
||||
if [[ -z $1 || $1 == "--help" || $1 == "-h" ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
"build-docker") build_docker $2 ;;
|
||||
"build") build $2 ;;
|
||||
"publish-docker-beta") publish_docker beta ;;
|
||||
"publish-docker-release") publish_docker stable ;;
|
||||
"publish-beta") publish beta ;;
|
||||
"publish-release") publish stable ;;
|
||||
"prepare") prepare ;;
|
||||
"cleanup") cleanup ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
|
||||
exit 0
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"client_settings": "Client settings",
|
||||
"example_upstream_reserved": "you can specify DNS upstream <0>for a specific domain(s)</0>",
|
||||
"upstream_parallel": "Use parallel queries to speed up resolving by simultaneously querying all upstream servers",
|
||||
"example_upstream_reserved": "You can specify DNS upstream <0>for the specific domain(s)</0>",
|
||||
"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</0> 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> <0>Set DNS server address to 127.0.0.1</0> <0>Replace symbolic link target of /etc/resolv.conf with /run/systemd/resolve/resolv.conf</0> <0>Stop DNSStubListener (reload systemd-resolved service)</0>",
|
||||
"autofix_warning_result": "As a result all DNS requests from your system will be processed by AdGuard Home by default.",
|
||||
|
|
|
@ -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 }));
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -17,26 +17,55 @@ import {
|
|||
} from '../../../../helpers/form';
|
||||
import { BLOCKING_MODES } from '../../../../helpers/constants';
|
||||
|
||||
const getFields = (processing, t) => Object.values(BLOCKING_MODES).map(mode => (
|
||||
<Field
|
||||
key={mode}
|
||||
name="blocking_mode"
|
||||
type="radio"
|
||||
component={renderRadioField}
|
||||
value={mode}
|
||||
placeholder={t(mode)}
|
||||
disabled={processing}
|
||||
/>
|
||||
));
|
||||
const checkboxes = [{
|
||||
name: 'edns_cs_enabled',
|
||||
placeholder: 'edns_enable',
|
||||
subtitle: 'edns_cs_desc',
|
||||
},
|
||||
{
|
||||
name: 'dnssec_enabled',
|
||||
placeholder: 'dnssec_enable',
|
||||
subtitle: 'dnssec_enable_desc',
|
||||
},
|
||||
{
|
||||
name: 'disable_ipv6',
|
||||
placeholder: 'disable_ipv6',
|
||||
subtitle: 'disable_ipv6_desc',
|
||||
}];
|
||||
|
||||
const customIps = [{
|
||||
description: 'blocking_ipv4_desc',
|
||||
name: 'blocking_ipv4',
|
||||
validateIp: ipv4,
|
||||
},
|
||||
{
|
||||
description: 'blocking_ipv6_desc',
|
||||
name: 'blocking_ipv6',
|
||||
validateIp: ipv6,
|
||||
}];
|
||||
|
||||
const getFields = (processing, t) => Object.values(BLOCKING_MODES)
|
||||
.map(mode => (
|
||||
<Field
|
||||
key={mode}
|
||||
name="blocking_mode"
|
||||
type="radio"
|
||||
component={renderRadioField}
|
||||
value={mode}
|
||||
placeholder={t(mode)}
|
||||
disabled={processing}
|
||||
/>
|
||||
));
|
||||
|
||||
let Form = ({
|
||||
handleSubmit, submitting, invalid, processing, blockingMode, t,
|
||||
}) => (
|
||||
}) =>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-12 col-sm-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit" className="form__label form__label--with-desc">
|
||||
<label htmlFor="ratelimit"
|
||||
className="form__label form__label--with-desc">
|
||||
<Trans>rate_limit</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
|
@ -53,53 +82,31 @@ let Form = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name="edns_cs_enabled"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('edns_enable')}
|
||||
disabled={processing}
|
||||
subtitle={t('edns_cs_desc')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name="dnssec_enabled"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('dnssec_enable')}
|
||||
disabled={processing}
|
||||
subtitle={t('dnssec_enable_desc')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name="disable_ipv6"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('disable_ipv6')}
|
||||
disabled={processing}
|
||||
subtitle={t('disable_ipv6_desc')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{checkboxes.map(({ name, placeholder, subtitle }) =>
|
||||
<div className="col-12" key={name}>
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name={name}
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t(placeholder)}
|
||||
disabled={processing}
|
||||
subtitle={t(subtitle)}
|
||||
/>
|
||||
</div>
|
||||
</div>)}
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings mb-4">
|
||||
<label className="form__label form__label--with-desc">
|
||||
<Trans>blocking_mode</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
{Object.values(BLOCKING_MODES).map(mode => (
|
||||
<li key={mode}>
|
||||
<Trans >{`blocking_mode_${mode}`}</Trans>
|
||||
</li>
|
||||
))}
|
||||
{Object.values(BLOCKING_MODES)
|
||||
.map(mode => (
|
||||
<li key={mode}>
|
||||
<Trans>{`blocking_mode_${mode}`}</Trans>
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
<div className="custom-controls-stacked">
|
||||
{getFields(processing, t)}
|
||||
|
@ -108,40 +115,27 @@ let Form = ({
|
|||
</div>
|
||||
{blockingMode === BLOCKING_MODES.custom_ip && (
|
||||
<Fragment>
|
||||
<div className="col-12 col-sm-6">
|
||||
{customIps.map(({
|
||||
description,
|
||||
name,
|
||||
validateIp,
|
||||
}) => <div className="col-12 col-sm-6" key={name}>
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="blocking_ipv4" className="form__label form__label--with-desc">
|
||||
<Trans>blocking_ipv4</Trans>
|
||||
<label className="form__label form__label--with-desc"
|
||||
htmlFor={name}><Trans>{name}</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>blocking_ipv4_desc</Trans>
|
||||
<Trans>{description}</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name="blocking_ipv4"
|
||||
name={name}
|
||||
component={renderInputField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_ip')}
|
||||
validate={[ipv4, required]}
|
||||
validate={[validateIp, required]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ip_address" className="form__label form__label--with-desc">
|
||||
<Trans>blocking_ipv6</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>blocking_ipv6_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name="blocking_ipv6"
|
||||
component={renderInputField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_ip')}
|
||||
validate={[ipv6, required]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>)}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
|
@ -152,8 +146,7 @@ let Form = ({
|
|||
>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
</form>;
|
||||
|
||||
Form.propTypes = {
|
||||
blockingMode: PropTypes.string.isRequired,
|
||||
|
|
|
@ -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: () => <label className="form__label" htmlFor="upstream_dns">
|
||||
<Trans>upstream_dns</Trans>
|
||||
</label>,
|
||||
name: 'upstream_dns',
|
||||
type: 'text',
|
||||
component: 'textarea',
|
||||
className: 'form-control form-control--textarea',
|
||||
placeholder: 'upstream_dns',
|
||||
},
|
||||
{
|
||||
name: 'parallel_requests',
|
||||
placeholder: 'parallel_requests',
|
||||
component: renderSelectField,
|
||||
type: 'checkbox',
|
||||
subtitle: 'upstream_parallel',
|
||||
disabled: fastest_addr_selected,
|
||||
},
|
||||
{
|
||||
name: 'fastest_addr',
|
||||
placeholder: 'fastest_addr',
|
||||
component: renderSelectField,
|
||||
type: 'checkbox',
|
||||
subtitle: 'fastest_addr_desc',
|
||||
disabled: parallel_requests_selected,
|
||||
}];
|
||||
|
||||
let Form = (props) => {
|
||||
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 (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label" htmlFor="upstream_dns">
|
||||
<Trans>upstream_dns</Trans>
|
||||
</label>
|
||||
<Field
|
||||
id="upstream_dns"
|
||||
name="upstream_dns"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea"
|
||||
placeholder={t('upstream_dns')}
|
||||
disabled={processingSetUpstream || processingTestUpstream}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name="all_servers"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('upstream_parallel')}
|
||||
disabled={processingSetUpstream}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{INPUT_FIELDS.map(({
|
||||
name, component, type, className, placeholder, getTitle, subtitle, disabled,
|
||||
}) => <div className="col-12 mb-4" key={name}>
|
||||
{typeof getTitle === 'function' && getTitle()}
|
||||
<Field
|
||||
id={name}
|
||||
name={name}
|
||||
component={component}
|
||||
type={type}
|
||||
className={className}
|
||||
placeholder={t(placeholder)}
|
||||
subtitle={t(subtitle)}
|
||||
disabled={processingSetConfig || processingTestUpstream || disabled}
|
||||
/>
|
||||
</div>)}
|
||||
<div className="col-12">
|
||||
<Examples />
|
||||
<hr />
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="form__group">
|
||||
<label
|
||||
className="form__label form__label--with-desc"
|
||||
htmlFor="bootstrap_dns"
|
||||
>
|
||||
<Trans>bootstrap_dns</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>bootstrap_dns_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
id="bootstrap_dns"
|
||||
name="bootstrap_dns"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea form-control--textarea-small"
|
||||
placeholder={t('bootstrap_dns')}
|
||||
disabled={processingSetUpstream}
|
||||
/>
|
||||
<div className="col-12 mb-4">
|
||||
<label
|
||||
className="form__label form__label--with-desc"
|
||||
htmlFor="bootstrap_dns"
|
||||
>
|
||||
<Trans>bootstrap_dns</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>bootstrap_dns_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
id="bootstrap_dns"
|
||||
name="bootstrap_dns"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea form-control--textarea-small"
|
||||
placeholder={t('bootstrap_dns')}
|
||||
disabled={processingSetConfig}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-actions">
|
||||
|
@ -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}
|
||||
>
|
||||
<Trans>test_upstream_btn</Trans>
|
||||
</button>
|
||||
|
@ -105,7 +121,7 @@ let Form = (props) => {
|
|||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={
|
||||
submitting || invalid || processingSetUpstream || processingTestUpstream
|
||||
submitting || invalid || processingSetConfig || processingTestUpstream
|
||||
}
|
||||
>
|
||||
<Trans>apply_btn</Trans>
|
||||
|
@ -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);
|
||||
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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);
|
||||
|
|
|
@ -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 (
|
||||
<Fragment>
|
||||
<PageTitle title={t('dns_settings')} />
|
||||
{isDataLoading && <Loading />}
|
||||
{isDataReady && (
|
||||
{isDataLoading ?
|
||||
<Loading /> :
|
||||
<Fragment>
|
||||
<Upstream
|
||||
processingTestUpstream={settings.processingTestUpstream}
|
||||
testUpstream={testUpstream}
|
||||
dnsConfig={dnsConfig}
|
||||
setDnsConfig={setDnsConfig}
|
||||
/>
|
||||
<Config
|
||||
dnsConfig={dnsConfig}
|
||||
setDnsConfig={setDnsConfig}
|
||||
/>
|
||||
<Upstream
|
||||
upstreamDns={dashboard.upstreamDns}
|
||||
bootstrapDns={dashboard.bootstrapDns}
|
||||
allServers={dashboard.allServers}
|
||||
processingTestUpstream={settings.processingTestUpstream}
|
||||
processingSetUpstream={settings.processingSetUpstream}
|
||||
setUpstream={setUpstream}
|
||||
testUpstream={testUpstream}
|
||||
/>
|
||||
<Access access={access} setAccessList={setAccessList} />
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
}
|
||||
|
||||
.card-table-overflow {
|
||||
overflow-y: auto;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.card-table-overflow--limited {
|
||||
overflow-y: auto;
|
||||
max-height: 280px;
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
|
||||
.checkbox__input:disabled + .checkbox__label {
|
||||
cursor: default;
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.checkbox__input:disabled + .checkbox__label:before {
|
||||
|
|
|
@ -34,6 +34,9 @@ const Guide = (props) => {
|
|||
<li>
|
||||
<Trans>install_devices_router_list_3</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_router_list_4</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -117,29 +117,30 @@ export const renderSelectField = ({
|
|||
placeholder,
|
||||
subtitle,
|
||||
disabled,
|
||||
onClick,
|
||||
modifier = 'checkbox--form',
|
||||
meta: { touched, error },
|
||||
}) => (
|
||||
<Fragment>
|
||||
<label className={`checkbox ${modifier}`}>
|
||||
<span className="checkbox__marker" />
|
||||
<input {...input} type="checkbox" className="checkbox__input" disabled={disabled} />
|
||||
<span className="checkbox__label">
|
||||
<span className="checkbox__label-text checkbox__label-text--long">
|
||||
<span className="checkbox__label-title">{placeholder}</span>
|
||||
{subtitle && (
|
||||
<span
|
||||
className="checkbox__label-subtitle"
|
||||
dangerouslySetInnerHTML={{ __html: subtitle }}
|
||||
/>
|
||||
)}
|
||||
<Fragment>
|
||||
<label className={`checkbox ${modifier}`} onClick={onClick}>
|
||||
<span className="checkbox__marker" />
|
||||
<input {...input} type="checkbox" className="checkbox__input" disabled={disabled} />
|
||||
<span className="checkbox__label">
|
||||
<span className="checkbox__label-text checkbox__label-text--long">
|
||||
<span className="checkbox__label-title">{placeholder}</span>
|
||||
{subtitle && (
|
||||
<span
|
||||
className="checkbox__label-subtitle"
|
||||
dangerouslySetInnerHTML={{ __html: subtitle }}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
{!disabled &&
|
||||
touched &&
|
||||
(error && <span className="form__message form__message--error">{error}</span>)}
|
||||
</Fragment>
|
||||
</label>
|
||||
{!disabled &&
|
||||
touched &&
|
||||
(error && <span className="form__message form__message--error">{error}</span>)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export const renderServiceField = ({
|
||||
|
|
|
@ -217,19 +217,19 @@ class Settings extends Component {
|
|||
</div>
|
||||
<div className="col-12">
|
||||
{webStatus &&
|
||||
<div className="setup__error text-danger">
|
||||
{webStatus}
|
||||
{isWebFixAvailable &&
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-sm ml-2"
|
||||
onClick={() => this.handleAutofix('web')}
|
||||
>
|
||||
<Trans>fix</Trans>
|
||||
</button>
|
||||
}
|
||||
<hr className="divider--small" />
|
||||
</div>
|
||||
<div className="setup__error text-danger">
|
||||
{webStatus}
|
||||
{isWebFixAvailable &&
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-sm ml-2"
|
||||
onClick={() => this.handleAutofix('web')}
|
||||
>
|
||||
<Trans>fix</Trans>
|
||||
</button>
|
||||
}
|
||||
<hr className="divider--small" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -287,32 +287,33 @@ class Settings extends Component {
|
|||
</div>
|
||||
<div className="col-12">
|
||||
{dnsStatus &&
|
||||
<Fragment>
|
||||
<div className="setup__error text-danger">
|
||||
{dnsStatus}
|
||||
{isDnsFixAvailable &&
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-sm ml-2"
|
||||
onClick={() => this.handleAutofix('dns')}
|
||||
>
|
||||
<Trans>fix</Trans>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div className="text-muted mb-2">
|
||||
<p className="mb-1">
|
||||
<Trans>autofix_warning_text</Trans>
|
||||
</p>
|
||||
<Trans components={[<li key="0">text</li>]}>
|
||||
autofix_warning_list
|
||||
</Trans>
|
||||
<p className="mb-1">
|
||||
<Trans>autofix_warning_result</Trans>
|
||||
</p>
|
||||
</div>
|
||||
<hr className="divider--small" />
|
||||
</Fragment>
|
||||
<Fragment>
|
||||
<div className="setup__error text-danger">
|
||||
{dnsStatus}
|
||||
{isDnsFixAvailable &&
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-sm ml-2"
|
||||
onClick={() => this.handleAutofix('dns')}
|
||||
>
|
||||
<Trans>fix</Trans>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
{isDnsFixAvailable &&
|
||||
<div className="text-muted mb-2">
|
||||
<p className="mb-1">
|
||||
<Trans>autofix_warning_text</Trans>
|
||||
</p>
|
||||
<Trans components={[<li key="0">text</li>]}>
|
||||
autofix_warning_list
|
||||
</Trans>
|
||||
<p className="mb-1">
|
||||
<Trans>autofix_warning_result</Trans>
|
||||
</p>
|
||||
</div>}
|
||||
<hr className="divider--small" />
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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^",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
|
|
2
go.mod
2
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
|
||||
|
|
4
go.sum
4
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=
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
15
home/home.go
15
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)
|
||||
|
|
|
@ -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)
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
FROM alpine:latest
|
||||
LABEL maintainer="AdGuard Team <devteam@adguard.com>"
|
||||
|
||||
# Update CA certs
|
||||
RUN apk --no-cache --update add ca-certificates libcap && \
|
||||
rm -rf /var/cache/apk/* && \
|
||||
mkdir -p /opt/adguardhome/conf /opt/adguardhome/work && \
|
||||
chown -R nobody: /opt/adguardhome
|
||||
|
||||
COPY --chown=nobody:nogroup ./AdGuardHome /opt/adguardhome/AdGuardHome
|
||||
|
||||
RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
|
||||
|
||||
EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 853/tcp 3000/tcp
|
||||
|
||||
VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
|
||||
|
||||
WORKDIR /opt/adguardhome/work
|
||||
|
||||
#USER nobody
|
||||
|
||||
ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
|
||||
CMD ["-h", "0.0.0.0", "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work", "--no-check-update"]
|
|
@ -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.
|
|
@ -0,0 +1,5 @@
|
|||
## Snapcraft
|
||||
|
||||
Configuration for our snap.
|
||||
|
||||
Check out `build_snap.sh` for more details.
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,352 @@
|
|||
// Modified pprof package
|
||||
// The problem with the mainstream package is that it registers HTTP handlers
|
||||
// inside init() function.
|
||||
// This behaviour makes it impossible to enable pprof on demand in runtime.
|
||||
// But this package has a separate function for this -
|
||||
// PProfRegisterWebHandlers().
|
||||
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package pprof serves via its HTTP server runtime profiling data
|
||||
// in the format expected by the pprof visualization tool.
|
||||
//
|
||||
// The package is typically only imported for the side effect of
|
||||
// registering its HTTP handlers.
|
||||
// The handled paths all begin with /debug/pprof/.
|
||||
//
|
||||
// To use pprof, link this package into your program:
|
||||
// import _ "net/http/pprof"
|
||||
//
|
||||
// If your application is not already running an http server, you
|
||||
// need to start one. Add "net/http" and "log" to your imports and
|
||||
// the following code to your main function:
|
||||
//
|
||||
// go func() {
|
||||
// log.Println(http.ListenAndServe("localhost:6060", nil))
|
||||
// }()
|
||||
//
|
||||
// If you are not using DefaultServeMux, you will have to register handlers
|
||||
// with the mux you are using.
|
||||
//
|
||||
// Then use the pprof tool to look at the heap profile:
|
||||
//
|
||||
// go tool pprof http://localhost:6060/debug/pprof/heap
|
||||
//
|
||||
// Or to look at a 30-second CPU profile:
|
||||
//
|
||||
// go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
|
||||
//
|
||||
// Or to look at the goroutine blocking profile, after calling
|
||||
// runtime.SetBlockProfileRate in your program:
|
||||
//
|
||||
// go tool pprof http://localhost:6060/debug/pprof/block
|
||||
//
|
||||
// Or to collect a 5-second execution trace:
|
||||
//
|
||||
// wget http://localhost:6060/debug/pprof/trace?seconds=5
|
||||
//
|
||||
// Or to look at the holders of contended mutexes, after calling
|
||||
// runtime.SetMutexProfileFraction in your program:
|
||||
//
|
||||
// go tool pprof http://localhost:6060/debug/pprof/mutex
|
||||
//
|
||||
// To view all available profiles, open http://localhost:6060/debug/pprof/
|
||||
// in your browser.
|
||||
//
|
||||
// For a study of the facility in action, visit
|
||||
//
|
||||
// https://blog.golang.org/2011/06/profiling-go-programs.html
|
||||
//
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PProfRegisterWebHandlers - register HTTP handlers for pprof
|
||||
func PProfRegisterWebHandlers(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/debug/pprof/", Index)
|
||||
mux.HandleFunc("/debug/pprof/cmdline", Cmdline)
|
||||
mux.HandleFunc("/debug/pprof/profile", Profile)
|
||||
mux.HandleFunc("/debug/pprof/symbol", Symbol)
|
||||
mux.HandleFunc("/debug/pprof/trace", Trace)
|
||||
}
|
||||
|
||||
// Cmdline responds with the running program's
|
||||
// command line, with arguments separated by NUL bytes.
|
||||
// The package initialization registers it as /debug/pprof/cmdline.
|
||||
func Cmdline(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
fmt.Fprintf(w, strings.Join(os.Args, "\x00"))
|
||||
}
|
||||
|
||||
func sleep(w http.ResponseWriter, d time.Duration) {
|
||||
var clientGone <-chan bool
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
clientGone = cn.CloseNotify()
|
||||
}
|
||||
select {
|
||||
case <-time.After(d):
|
||||
case <-clientGone:
|
||||
}
|
||||
}
|
||||
|
||||
func durationExceedsWriteTimeout(r *http.Request, seconds float64) bool {
|
||||
srv, ok := r.Context().Value(http.ServerContextKey).(*http.Server)
|
||||
return ok && srv.WriteTimeout != 0 && seconds >= srv.WriteTimeout.Seconds()
|
||||
}
|
||||
|
||||
func serveError(w http.ResponseWriter, status int, txt string) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Header().Set("X-Go-Pprof", "1")
|
||||
w.Header().Del("Content-Disposition")
|
||||
w.WriteHeader(status)
|
||||
fmt.Fprintln(w, txt)
|
||||
}
|
||||
|
||||
// Profile responds with the pprof-formatted cpu profile.
|
||||
// Profiling lasts for duration specified in seconds GET parameter, or for 30 seconds if not specified.
|
||||
// The package initialization registers it as /debug/pprof/profile.
|
||||
func Profile(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
sec, err := strconv.ParseInt(r.FormValue("seconds"), 10, 64)
|
||||
if sec <= 0 || err != nil {
|
||||
sec = 30
|
||||
}
|
||||
|
||||
if durationExceedsWriteTimeout(r, float64(sec)) {
|
||||
serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
|
||||
return
|
||||
}
|
||||
|
||||
// Set Content Type assuming StartCPUProfile will work,
|
||||
// because if it does it starts writing.
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="profile"`)
|
||||
if err := pprof.StartCPUProfile(w); err != nil {
|
||||
// StartCPUProfile failed, so no writes yet.
|
||||
serveError(w, http.StatusInternalServerError,
|
||||
fmt.Sprintf("Could not enable CPU profiling: %s", err))
|
||||
return
|
||||
}
|
||||
sleep(w, time.Duration(sec)*time.Second)
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
// Trace responds with the execution trace in binary form.
|
||||
// Tracing lasts for duration specified in seconds GET parameter, or for 1 second if not specified.
|
||||
// The package initialization registers it as /debug/pprof/trace.
|
||||
func Trace(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
sec, err := strconv.ParseFloat(r.FormValue("seconds"), 64)
|
||||
if sec <= 0 || err != nil {
|
||||
sec = 1
|
||||
}
|
||||
|
||||
if durationExceedsWriteTimeout(r, sec) {
|
||||
serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
|
||||
return
|
||||
}
|
||||
|
||||
// Set Content Type assuming trace.Start will work,
|
||||
// because if it does it starts writing.
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="trace"`)
|
||||
if err := trace.Start(w); err != nil {
|
||||
// trace.Start failed, so no writes yet.
|
||||
serveError(w, http.StatusInternalServerError,
|
||||
fmt.Sprintf("Could not enable tracing: %s", err))
|
||||
return
|
||||
}
|
||||
sleep(w, time.Duration(sec*float64(time.Second)))
|
||||
trace.Stop()
|
||||
}
|
||||
|
||||
// Symbol looks up the program counters listed in the request,
|
||||
// responding with a table mapping program counters to function names.
|
||||
// The package initialization registers it as /debug/pprof/symbol.
|
||||
func Symbol(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
|
||||
// We have to read the whole POST body before
|
||||
// writing any output. Buffer the output here.
|
||||
var buf bytes.Buffer
|
||||
|
||||
// We don't know how many symbols we have, but we
|
||||
// do have symbol information. Pprof only cares whether
|
||||
// this number is 0 (no symbols available) or > 0.
|
||||
fmt.Fprintf(&buf, "num_symbols: 1\n")
|
||||
|
||||
var b *bufio.Reader
|
||||
if r.Method == "POST" {
|
||||
b = bufio.NewReader(r.Body)
|
||||
} else {
|
||||
b = bufio.NewReader(strings.NewReader(r.URL.RawQuery))
|
||||
}
|
||||
|
||||
for {
|
||||
word, err := b.ReadSlice('+')
|
||||
if err == nil {
|
||||
word = word[0 : len(word)-1] // trim +
|
||||
}
|
||||
pc, _ := strconv.ParseUint(string(word), 0, 64)
|
||||
if pc != 0 {
|
||||
f := runtime.FuncForPC(uintptr(pc))
|
||||
if f != nil {
|
||||
fmt.Fprintf(&buf, "%#x %s\n", pc, f.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until here to check for err; the last
|
||||
// symbol will have an err because it doesn't end in +.
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
fmt.Fprintf(&buf, "reading request: %v\n", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
// Handler returns an HTTP handler that serves the named profile.
|
||||
func Handler(name string) http.Handler {
|
||||
return handler(name)
|
||||
}
|
||||
|
||||
type handler string
|
||||
|
||||
func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
p := pprof.Lookup(string(name))
|
||||
if p == nil {
|
||||
serveError(w, http.StatusNotFound, "Unknown profile")
|
||||
return
|
||||
}
|
||||
gc, _ := strconv.Atoi(r.FormValue("gc"))
|
||||
if name == "heap" && gc > 0 {
|
||||
runtime.GC()
|
||||
}
|
||||
debug, _ := strconv.Atoi(r.FormValue("debug"))
|
||||
if debug != 0 {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name))
|
||||
}
|
||||
p.WriteTo(w, debug)
|
||||
}
|
||||
|
||||
var profileDescriptions = map[string]string{
|
||||
"allocs": "A sampling of all past memory allocations",
|
||||
"block": "Stack traces that led to blocking on synchronization primitives",
|
||||
"cmdline": "The command line invocation of the current program",
|
||||
"goroutine": "Stack traces of all current goroutines",
|
||||
"heap": "A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.",
|
||||
"mutex": "Stack traces of holders of contended mutexes",
|
||||
"profile": "CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.",
|
||||
"threadcreate": "Stack traces that led to the creation of new OS threads",
|
||||
"trace": "A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.",
|
||||
}
|
||||
|
||||
// Index responds with the pprof-formatted profile named by the request.
|
||||
// For example, "/debug/pprof/heap" serves the "heap" profile.
|
||||
// Index responds to a request for "/debug/pprof/" with an HTML page
|
||||
// listing the available profiles.
|
||||
func Index(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.URL.Path, "/debug/pprof/") {
|
||||
name := strings.TrimPrefix(r.URL.Path, "/debug/pprof/")
|
||||
if name != "" {
|
||||
handler(name).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type profile struct {
|
||||
Name string
|
||||
Href string
|
||||
Desc string
|
||||
Count int
|
||||
}
|
||||
var profiles []profile
|
||||
for _, p := range pprof.Profiles() {
|
||||
profiles = append(profiles, profile{
|
||||
Name: p.Name(),
|
||||
Href: p.Name() + "?debug=1",
|
||||
Desc: profileDescriptions[p.Name()],
|
||||
Count: p.Count(),
|
||||
})
|
||||
}
|
||||
|
||||
// Adding other profiles exposed from within this package
|
||||
for _, p := range []string{"cmdline", "profile", "trace"} {
|
||||
profiles = append(profiles, profile{
|
||||
Name: p,
|
||||
Href: p,
|
||||
Desc: profileDescriptions[p],
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(profiles, func(i, j int) bool {
|
||||
return profiles[i].Name < profiles[j].Name
|
||||
})
|
||||
|
||||
if err := indexTmpl.Execute(w, profiles); err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
|
||||
var indexTmpl = template.Must(template.New("index").Parse(`<html>
|
||||
<head>
|
||||
<title>/debug/pprof/</title>
|
||||
<style>
|
||||
.profile-name{
|
||||
display:inline-block;
|
||||
width:6rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
/debug/pprof/<br>
|
||||
<br>
|
||||
Types of profiles available:
|
||||
<table>
|
||||
<thead><td>Count</td><td>Profile</td></thead>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<td>{{.Count}}</td><td><a href={{.Href}}>{{.Name}}</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
<a href="goroutine?debug=2">full goroutine stack dump</a>
|
||||
<br/>
|
||||
<p>
|
||||
Profile Descriptions:
|
||||
<ul>
|
||||
{{range .}}
|
||||
<li><div class=profile-name>{{.Name}}:</div> {{.Desc}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
`))
|
Loading…
Reference in New Issue