Merge branch 'master' of ssh://bit.adguard.com:7999/dns/adguard-home
This commit is contained in:
commit
c62dd03921
16
.codecov.yml
16
.codecov.yml
|
@ -1,8 +1,8 @@
|
||||||
coverage:
|
'coverage':
|
||||||
status:
|
'status':
|
||||||
project:
|
'project':
|
||||||
default:
|
'default':
|
||||||
target: 40%
|
'target': '40%'
|
||||||
threshold: null
|
'threshold': null
|
||||||
patch: false
|
'patch': false
|
||||||
changes: false
|
'changes': false
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
set -e;
|
|
||||||
|
|
||||||
found=0
|
set -e -f -u
|
||||||
git diff --cached --name-only | grep -q '.js$' && found=1
|
|
||||||
if [ $found == 1 ]; then
|
if [ "$(git diff --cached --name-only -- '*.js')" ]
|
||||||
npm --prefix client run lint || exit 1
|
then
|
||||||
npm run test --prefix client || exit 1
|
make js-lint js-test
|
||||||
fi
|
fi
|
||||||
|
|
||||||
found=0
|
if [ "$(git diff --cached --name-only -- '*.go' 'go.mod')" ]
|
||||||
git diff --cached --name-only | grep -q '.go$' && found=1
|
then
|
||||||
if [ $found == 1 ]; then
|
make go-lint go-test
|
||||||
make lint-go || exit 1
|
|
||||||
go test ./... || exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit 0;
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
# Number of days of inactivity before an issue becomes stale
|
# Number of days of inactivity before an issue becomes stale.
|
||||||
daysUntilStale: 60
|
'daysUntilStale': 60
|
||||||
# Number of days of inactivity before a stale issue is closed
|
# Number of days of inactivity before a stale issue is closed.
|
||||||
daysUntilClose: 7
|
'daysUntilClose': 7
|
||||||
# Issues with these labels will never be considered stale
|
# Issues with these labels will never be considered stale.
|
||||||
exemptLabels:
|
'exemptLabels':
|
||||||
- 'bug'
|
- 'bug'
|
||||||
- 'enhancement'
|
- 'enhancement'
|
||||||
- 'feature request'
|
- 'feature request'
|
||||||
- 'localization'
|
- 'localization'
|
||||||
# Label to use when marking an issue as stale
|
# Label to use when marking an issue as stale.
|
||||||
staleLabel: 'wontfix'
|
'staleLabel': 'wontfix'
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
# Comment to post when marking an issue as stale. Set to `false` to disable.
|
||||||
markComment: >
|
'markComment': >
|
||||||
This issue has been automatically marked as stale because it has not had
|
This issue has been automatically marked as stale because it has not had
|
||||||
recent activity. It will be closed if no further activity occurs. Thank you
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
for your contributions.
|
for your contributions.
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
# Comment to post when closing a stale issue. Set to `false` to disable.
|
||||||
closeComment: false
|
'closeComment': false
|
||||||
|
|
|
@ -1,170 +1,145 @@
|
||||||
name: build
|
'name': 'build'
|
||||||
|
|
||||||
env:
|
'env':
|
||||||
GO_VERSION: 1.14
|
'GO_VERSION': '1.14'
|
||||||
NODE_VERSION: 13
|
'NODE_VERSION': '13'
|
||||||
|
|
||||||
on:
|
'on':
|
||||||
push:
|
'push':
|
||||||
branches:
|
'branches':
|
||||||
- '*'
|
- '*'
|
||||||
tags:
|
'tags':
|
||||||
- v*
|
- 'v*'
|
||||||
pull_request:
|
'pull_request':
|
||||||
|
|
||||||
jobs:
|
'jobs':
|
||||||
|
'test':
|
||||||
|
'runs-on': '${{ matrix.os }}'
|
||||||
|
'env':
|
||||||
|
'GO111MODULE': 'on'
|
||||||
|
'GOPROXY': 'https://goproxy.io'
|
||||||
|
'strategy':
|
||||||
|
'fail-fast': false
|
||||||
|
'matrix':
|
||||||
|
'os':
|
||||||
|
- 'ubuntu-latest'
|
||||||
|
- 'macOS-latest'
|
||||||
|
- 'windows-latest'
|
||||||
|
'steps':
|
||||||
|
- 'name': 'Checkout'
|
||||||
|
'uses': 'actions/checkout@v2'
|
||||||
|
'with':
|
||||||
|
'fetch-depth': 0
|
||||||
|
- 'name': 'Set up Go'
|
||||||
|
'uses': 'actions/setup-go@v2'
|
||||||
|
'with':
|
||||||
|
'go-version': '${{ env.GO_VERSION }}'
|
||||||
|
- 'name': 'Set up Node'
|
||||||
|
'uses': 'actions/setup-node@v1'
|
||||||
|
'with':
|
||||||
|
'node-version': '${{ env.NODE_VERSION }}'
|
||||||
|
- 'name': 'Set up Go modules cache'
|
||||||
|
'uses': 'actions/cache@v2'
|
||||||
|
'with':
|
||||||
|
'path': '~/go/pkg/mod'
|
||||||
|
'key': "${{ runner.os }}-go-${{ hashFiles('go.sum') }}"
|
||||||
|
'restore-keys': '${{ runner.os }}-go-'
|
||||||
|
- 'name': 'Get npm cache directory'
|
||||||
|
'id': 'npm-cache'
|
||||||
|
'run': 'echo "::set-output name=dir::$(npm config get cache)"'
|
||||||
|
- 'name': 'Set up npm cache'
|
||||||
|
'uses': 'actions/cache@v2'
|
||||||
|
'with':
|
||||||
|
'path': '${{ steps.npm-cache.outputs.dir }}'
|
||||||
|
'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}"
|
||||||
|
'restore-keys': '${{ runner.os }}-node-'
|
||||||
|
- 'name': 'Run make ci'
|
||||||
|
'shell': 'bash'
|
||||||
|
'run': 'make ci'
|
||||||
|
- 'name': 'Upload coverage'
|
||||||
|
'uses': 'codecov/codecov-action@v1'
|
||||||
|
'if': "success() && matrix.os == 'ubuntu-latest'"
|
||||||
|
'with':
|
||||||
|
'token': '${{ secrets.CODECOV_TOKEN }}'
|
||||||
|
'file': './coverage.txt'
|
||||||
|
'app':
|
||||||
|
'runs-on': 'ubuntu-latest'
|
||||||
|
'needs': 'test'
|
||||||
|
'steps':
|
||||||
|
- 'name': 'Checkout'
|
||||||
|
'uses': 'actions/checkout@v2'
|
||||||
|
'with':
|
||||||
|
'fetch-depth': 0
|
||||||
|
- 'name': 'Set up Go'
|
||||||
|
'uses': 'actions/setup-go@v2'
|
||||||
|
'with':
|
||||||
|
'go-version': '${{ env.GO_VERSION }}'
|
||||||
|
- 'name': 'Set up Node'
|
||||||
|
'uses': 'actions/setup-node@v1'
|
||||||
|
'with':
|
||||||
|
'node-version': '${{ env.NODE_VERSION }}'
|
||||||
|
- 'name': 'Set up Go modules cache'
|
||||||
|
'uses': 'actions/cache@v2'
|
||||||
|
'with':
|
||||||
|
'path': '~/go/pkg/mod'
|
||||||
|
'key': "${{ runner.os }}-go-${{ hashFiles('go.sum') }}"
|
||||||
|
'restore-keys': '${{ runner.os }}-go-'
|
||||||
|
- 'name': 'Get npm cache directory'
|
||||||
|
'id': 'npm-cache'
|
||||||
|
'run': 'echo "::set-output name=dir::$(npm config get cache)"'
|
||||||
|
- 'name': 'Set up node_modules cache'
|
||||||
|
'uses': 'actions/cache@v2'
|
||||||
|
'with':
|
||||||
|
'path': '${{ steps.npm-cache.outputs.dir }}'
|
||||||
|
'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}"
|
||||||
|
'restore-keys': '${{ runner.os }}-node-'
|
||||||
|
- 'name': 'Set up Snapcraft'
|
||||||
|
'run': 'sudo apt-get -yq --no-install-suggests --no-install-recommends install snapcraft'
|
||||||
|
- 'name': 'Set up GoReleaser'
|
||||||
|
'run': 'curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | BINDIR="$(go env GOPATH)/bin" sh'
|
||||||
|
- 'name': 'Run snapshot build'
|
||||||
|
'run': 'make release'
|
||||||
|
|
||||||
test:
|
'docker':
|
||||||
runs-on: ${{ matrix.os }}
|
'runs-on': 'ubuntu-latest'
|
||||||
env:
|
'needs': 'test'
|
||||||
GO111MODULE: on
|
'steps':
|
||||||
GOPROXY: https://goproxy.io
|
- 'name': 'Checkout'
|
||||||
strategy:
|
'uses': 'actions/checkout@v2'
|
||||||
fail-fast: false
|
'with':
|
||||||
matrix:
|
'fetch-depth': 0
|
||||||
os:
|
- 'name': 'Set up QEMU'
|
||||||
- ubuntu-latest
|
'uses': 'docker/setup-qemu-action@v1'
|
||||||
- macOS-latest
|
- 'name': 'Set up Docker Buildx'
|
||||||
- windows-latest
|
'uses': 'docker/setup-buildx-action@v1'
|
||||||
steps:
|
- 'name': 'Docker Buildx (build)'
|
||||||
-
|
'run': 'make docker-multi-arch'
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
-
|
|
||||||
name: Set up Go
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
|
|
||||||
-
|
'notify':
|
||||||
name: Set up Node
|
'needs':
|
||||||
uses: actions/setup-node@v1
|
- 'app'
|
||||||
with:
|
- 'docker'
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
# Secrets are not passed to workflows that are triggered by a pull request
|
||||||
-
|
# from a fork.
|
||||||
name: Set up Go modules cache
|
#
|
||||||
uses: actions/cache@v2
|
# Use always() to signal to the runner that this job must run even if the
|
||||||
with:
|
# previous ones failed.
|
||||||
path: ~/go/pkg/mod
|
'if':
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
|
${{ always() &&
|
||||||
restore-keys: |
|
(
|
||||||
${{ runner.os }}-go-
|
github.event_name == 'push' ||
|
||||||
-
|
github.event.pull_request.head.repo.full_name == github.repository
|
||||||
name: Get npm cache directory
|
)
|
||||||
id: npm-cache
|
}}
|
||||||
run: |
|
'runs-on': 'ubuntu-latest'
|
||||||
echo "::set-output name=dir::$(npm config get cache)"
|
'steps':
|
||||||
-
|
- 'name': 'Conclusion'
|
||||||
name: Set up npm cache
|
'uses': 'technote-space/workflow-conclusion-action@v1'
|
||||||
uses: actions/cache@v2
|
- 'name': 'Send Slack notif'
|
||||||
with:
|
'uses': '8398a7/action-slack@v3'
|
||||||
path: ${{ steps.npm-cache.outputs.dir }}
|
'with':
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}
|
'status': '${{ env.WORKFLOW_CONCLUSION }}'
|
||||||
restore-keys: |
|
'fields': 'repo, message, commit, author, job'
|
||||||
${{ runner.os }}-node-
|
'env':
|
||||||
-
|
'GITHUB_TOKEN': '${{ secrets.GITHUB_TOKEN }}'
|
||||||
name: Run make ci
|
'SLACK_WEBHOOK_URL': '${{ secrets.SLACK_WEBHOOK_URL }}'
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
make ci
|
|
||||||
-
|
|
||||||
name: Upload coverage
|
|
||||||
uses: codecov/codecov-action@v1
|
|
||||||
if: success() && matrix.os == 'ubuntu-latest'
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
|
||||||
file: ./coverage.txt
|
|
||||||
|
|
||||||
app:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: test
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
-
|
|
||||||
name: Set up Go
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
-
|
|
||||||
name: Set up Node
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
-
|
|
||||||
name: Set up Go modules cache
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: ~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
-
|
|
||||||
name: Get npm cache directory
|
|
||||||
id: npm-cache
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=dir::$(npm config get cache)"
|
|
||||||
-
|
|
||||||
name: Set up node_modules cache
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: ${{ steps.npm-cache.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-
|
|
||||||
-
|
|
||||||
name: Set up Snapcraft
|
|
||||||
run: |
|
|
||||||
sudo apt-get -yq --no-install-suggests --no-install-recommends install snapcraft
|
|
||||||
-
|
|
||||||
name: Set up GoReleaser
|
|
||||||
run: |
|
|
||||||
curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | BINDIR="$(go env GOPATH)/bin" sh
|
|
||||||
-
|
|
||||||
name: Run snapshot build
|
|
||||||
run: |
|
|
||||||
make release
|
|
||||||
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: test
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
-
|
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
-
|
|
||||||
name: Docker Buildx (build)
|
|
||||||
run: |
|
|
||||||
make docker-multi-arch
|
|
||||||
|
|
||||||
notify:
|
|
||||||
needs: [app, docker]
|
|
||||||
# Secrets are not passed to workflows that are triggered by a pull request from a fork
|
|
||||||
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Conclusion
|
|
||||||
uses: technote-space/workflow-conclusion-action@v1
|
|
||||||
-
|
|
||||||
name: Send Slack notif
|
|
||||||
uses: 8398a7/action-slack@v3
|
|
||||||
with:
|
|
||||||
status: ${{ env.WORKFLOW_CONCLUSION }}
|
|
||||||
fields: repo,message,commit,author
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
||||||
|
|
|
@ -1,47 +1,52 @@
|
||||||
name: golangci-lint
|
'name': 'lint'
|
||||||
on:
|
'on':
|
||||||
push:
|
'push':
|
||||||
tags:
|
'tags':
|
||||||
- v*
|
- 'v*'
|
||||||
branches:
|
'branches':
|
||||||
- '*'
|
- '*'
|
||||||
pull_request:
|
'pull_request':
|
||||||
jobs:
|
'jobs':
|
||||||
golangci:
|
'go-lint':
|
||||||
runs-on: ubuntu-latest
|
'runs-on': 'ubuntu-latest'
|
||||||
steps:
|
'steps':
|
||||||
- uses: actions/checkout@v2
|
- 'uses': 'actions/checkout@v2'
|
||||||
- name: golangci-lint
|
- 'name': 'run-lint'
|
||||||
uses: golangci/golangci-lint-action@v1
|
'run': >
|
||||||
with:
|
make go-install-tools go-lint
|
||||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
'eslint':
|
||||||
version: v1.27
|
'runs-on': 'ubuntu-latest'
|
||||||
|
'steps':
|
||||||
eslint:
|
- 'uses': 'actions/checkout@v2'
|
||||||
runs-on: ubuntu-latest
|
- 'name': 'Install modules'
|
||||||
steps:
|
'run': 'npm --prefix client ci'
|
||||||
- uses: actions/checkout@v2
|
- 'name': 'Run ESLint'
|
||||||
- name: Install modules
|
'run': 'npm --prefix client run lint'
|
||||||
run: npm --prefix client ci
|
'notify':
|
||||||
- name: Run ESLint
|
'needs':
|
||||||
run: npm --prefix client run lint
|
- 'go-lint'
|
||||||
|
- 'eslint'
|
||||||
|
# Secrets are not passed to workflows that are triggered by a pull request
|
||||||
notify:
|
# from a fork.
|
||||||
needs: [golangci,eslint]
|
#
|
||||||
# Secrets are not passed to workflows that are triggered by a pull request from a fork
|
# Use always() to signal to the runner that this job must run even if the
|
||||||
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
|
# previous ones failed.
|
||||||
runs-on: ubuntu-latest
|
'if':
|
||||||
steps:
|
${{ always() &&
|
||||||
-
|
(
|
||||||
name: Conclusion
|
github.event_name == 'push' ||
|
||||||
uses: technote-space/workflow-conclusion-action@v1
|
github.event.pull_request.head.repo.full_name == github.repository
|
||||||
-
|
)
|
||||||
name: Send Slack notif
|
}}
|
||||||
uses: 8398a7/action-slack@v3
|
'runs-on': 'ubuntu-latest'
|
||||||
with:
|
'steps':
|
||||||
status: ${{ env.WORKFLOW_CONCLUSION }}
|
- 'name': 'Conclusion'
|
||||||
fields: repo,message,commit,author
|
'uses': 'technote-space/workflow-conclusion-action@v1'
|
||||||
env:
|
- 'name': 'Send Slack notif'
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
'uses': '8398a7/action-slack@v3'
|
||||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
'with':
|
||||||
|
'status': '${{ env.WORKFLOW_CONCLUSION }}'
|
||||||
|
'fields': 'repo, message, commit, author, job'
|
||||||
|
'env':
|
||||||
|
'GITHUB_TOKEN': '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
'SLACK_WEBHOOK_URL': '${{ secrets.SLACK_WEBHOOK_URL }}'
|
||||||
|
|
|
@ -1,30 +1,22 @@
|
||||||
.DS_Store
|
# Please, DO NOT put your text editors' temporary files here. The more are
|
||||||
/.vscode
|
# added, the harder it gets to maintain and manage projects' gitignores. Put
|
||||||
.idea
|
# them into your global gitignore file instead.
|
||||||
/AdGuardHome
|
#
|
||||||
/AdGuardHome.exe
|
# See https://stackoverflow.com/a/7335487/1892060.
|
||||||
/AdGuardHome.yaml
|
#
|
||||||
/AdGuardHome.log
|
# Only build, run, and test outputs here. Sorted.
|
||||||
/data/
|
|
||||||
/build/
|
|
||||||
/dist/
|
|
||||||
/client/node_modules/
|
|
||||||
/querylog.json
|
|
||||||
/querylog.json.1
|
|
||||||
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
|
|
||||||
|
|
||||||
# IntelliJ IDEA project files
|
|
||||||
*.iml
|
|
||||||
|
|
||||||
# Packr
|
|
||||||
*-packr.go
|
*-packr.go
|
||||||
|
*.db
|
||||||
|
*.snap
|
||||||
|
/bin/
|
||||||
|
/build/
|
||||||
|
/data/
|
||||||
|
/dist/
|
||||||
|
/dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof
|
||||||
|
/dnsfilter/tests/top-1m.csv
|
||||||
|
/launchpad_credentials
|
||||||
|
/querylog.json*
|
||||||
|
/snapcraft_login
|
||||||
|
AdGuardHome*
|
||||||
|
coverage.txt
|
||||||
|
node_modules/
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
# options for analysis running
|
|
||||||
run:
|
|
||||||
# default concurrency is a available CPU number
|
|
||||||
concurrency: 4
|
|
||||||
|
|
||||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
|
||||||
deadline: 2m
|
|
||||||
|
|
||||||
# which files to skip: they will be analyzed, but issues from them
|
|
||||||
# won't be reported. Default value is empty list, but there is
|
|
||||||
# no need to include all autogenerated files, we confidently recognize
|
|
||||||
# autogenerated files. If it's not please let us know.
|
|
||||||
skip-files:
|
|
||||||
- ".*generated.*"
|
|
||||||
- dnsfilter/rule_to_regexp.go
|
|
||||||
- util/pprof.go
|
|
||||||
- ".*_test.go"
|
|
||||||
- client/.*
|
|
||||||
- build/.*
|
|
||||||
- dist/.*
|
|
||||||
|
|
||||||
|
|
||||||
# all available settings of specific linters
|
|
||||||
linters-settings:
|
|
||||||
errcheck:
|
|
||||||
# [deprecated] comma-separated list of pairs of the form pkg:regex
|
|
||||||
# the regex is used to ignore names within pkg. (default "fmt:.*").
|
|
||||||
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
|
|
||||||
ignore: fmt:.*,net:SetReadDeadline,net/http:^Write
|
|
||||||
gocyclo:
|
|
||||||
min-complexity: 20
|
|
||||||
lll:
|
|
||||||
line-length: 200
|
|
||||||
|
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
- deadcode
|
|
||||||
- errcheck
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- staticcheck
|
|
||||||
- structcheck
|
|
||||||
- unused
|
|
||||||
- varcheck
|
|
||||||
- bodyclose
|
|
||||||
- depguard
|
|
||||||
- dupl
|
|
||||||
- gocyclo
|
|
||||||
- goimports
|
|
||||||
- golint
|
|
||||||
- gosec
|
|
||||||
- misspell
|
|
||||||
- stylecheck
|
|
||||||
- unconvert
|
|
||||||
disable-all: true
|
|
||||||
fast: true
|
|
||||||
|
|
||||||
issues:
|
|
||||||
# List of regexps of issue texts to exclude, empty list by default.
|
|
||||||
# But independently from this option we use default exclude patterns,
|
|
||||||
# it can be disabled by `exclude-use-default: false`. To list all
|
|
||||||
# excluded by default patterns execute `golangci-lint run --help`
|
|
||||||
exclude:
|
|
||||||
# structcheck cannot detect usages while they're there
|
|
||||||
- .parentalServer. is unused
|
|
||||||
- .safeBrowsingServer. is unused
|
|
||||||
# errcheck
|
|
||||||
- Error return value of .s.closeConn. is not checked
|
|
||||||
- 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
|
|
||||||
# gosec: Potential DoS vulnerability via decompression bomb
|
|
||||||
- G110
|
|
||||||
# gosec: Expect WriteFile permissions to be 0600 or less
|
|
||||||
- G306
|
|
207
.goreleaser.yml
207
.goreleaser.yml
|
@ -1,106 +1,115 @@
|
||||||
project_name: AdGuardHome
|
'project_name': 'AdGuardHome'
|
||||||
|
|
||||||
env:
|
'env':
|
||||||
- GO111MODULE=on
|
- 'GO111MODULE=on'
|
||||||
- GOPROXY=https://goproxy.io
|
- 'GOPROXY=https://goproxy.io'
|
||||||
|
|
||||||
before:
|
'before':
|
||||||
hooks:
|
'hooks':
|
||||||
- go mod download
|
- 'go mod download'
|
||||||
- go generate ./...
|
- 'go generate ./...'
|
||||||
|
|
||||||
builds:
|
'builds':
|
||||||
- main: ./main.go
|
- 'main': './main.go'
|
||||||
ldflags:
|
'ldflags':
|
||||||
- -s -w -X main.version={{.Version}} -X main.channel={{.Env.CHANNEL}} -X main.goarm={{.Env.GOARM}}
|
- '-s -w -X main.version={{.Version}} -X main.channel={{.Env.CHANNEL}} -X main.goarm={{.Env.GOARM}}'
|
||||||
env:
|
'env':
|
||||||
- CGO_ENABLED=0
|
- 'CGO_ENABLED=0'
|
||||||
goos:
|
'goos':
|
||||||
- darwin
|
- 'darwin'
|
||||||
- linux
|
- 'linux'
|
||||||
- freebsd
|
- 'freebsd'
|
||||||
- windows
|
- 'windows'
|
||||||
goarch:
|
'goarch':
|
||||||
- 386
|
- '386'
|
||||||
- amd64
|
- 'amd64'
|
||||||
- arm
|
- 'arm'
|
||||||
- arm64
|
- 'arm64'
|
||||||
- mips
|
- 'mips'
|
||||||
- mipsle
|
- 'mipsle'
|
||||||
- mips64
|
- 'mips64'
|
||||||
- mips64le
|
- 'mips64le'
|
||||||
goarm:
|
'goarm':
|
||||||
- 5
|
- '5'
|
||||||
- 6
|
- '6'
|
||||||
- 7
|
- '7'
|
||||||
gomips:
|
'gomips':
|
||||||
- softfloat
|
- 'softfloat'
|
||||||
ignore:
|
'ignore':
|
||||||
- goos: freebsd
|
- 'goos': 'freebsd'
|
||||||
goarch: mips
|
'goarch': 'mips'
|
||||||
- goos: freebsd
|
- 'goos': 'freebsd'
|
||||||
goarch: mipsle
|
'goarch': 'mipsle'
|
||||||
|
|
||||||
archives:
|
'archives':
|
||||||
- # Archive name template.
|
- # Archive name template.
|
||||||
# Defaults:
|
# Defaults:
|
||||||
# - if format is `tar.gz`, `tar.xz`, `gz` or `zip`:
|
# - if format is `tar.gz`, `tar.xz`, `gz` or `zip`:
|
||||||
# - `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}`
|
# - `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}`
|
||||||
# - if format is `binary`:
|
# - if format is `binary`:
|
||||||
# - `{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}`
|
# - `{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}`
|
||||||
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
|
'name_template': '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}'
|
||||||
wrap_in_directory: "AdGuardHome"
|
'wrap_in_directory': 'AdGuardHome'
|
||||||
format_overrides:
|
'format_overrides':
|
||||||
- goos: windows
|
- 'goos': 'windows'
|
||||||
format: zip
|
'format': 'zip'
|
||||||
- goos: darwin
|
- 'goos': 'darwin'
|
||||||
format: zip
|
'format': 'zip'
|
||||||
files:
|
'files':
|
||||||
- LICENSE.txt
|
- 'LICENSE.txt'
|
||||||
- README.md
|
- 'README.md'
|
||||||
|
|
||||||
snapcrafts:
|
'snapcrafts':
|
||||||
- name: adguard-home
|
- 'name': 'adguard-home'
|
||||||
base: core18
|
'base': 'core20'
|
||||||
name_template: '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
'name_template': '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||||
summary: Network-wide ads & trackers blocking DNS server
|
'summary': 'Network-wide ads & trackers blocking DNS server'
|
||||||
description: |
|
'description': |
|
||||||
AdGuard Home is a network-wide software for blocking ads & tracking. After
|
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
|
you set it up, it'll cover ALL your home devices, and you don't need any
|
||||||
client-side software for that.
|
client-side software for that.
|
||||||
|
|
||||||
It operates as a DNS server that re-routes tracking domains to a "black hole,"
|
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
|
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
|
on software we use for our public AdGuard DNS servers -- both share a lot
|
||||||
of common code.
|
of common code.
|
||||||
grade: stable
|
'grade': 'stable'
|
||||||
confinement: strict
|
'confinement': 'strict'
|
||||||
publish: false
|
'publish': false
|
||||||
license: GPL-3.0
|
'license': 'GPL-3.0'
|
||||||
extra_files:
|
'extra_files':
|
||||||
- source: scripts/snap/local/adguard-home-web.sh
|
- 'source': 'scripts/snap/local/adguard-home-web.sh'
|
||||||
destination: adguard-home-web.sh
|
'destination': 'adguard-home-web.sh'
|
||||||
mode: 0755
|
'mode': 0755
|
||||||
- source: scripts/snap/gui/adguard-home-web.desktop
|
- 'source': 'scripts/snap/gui/adguard-home-web.desktop'
|
||||||
destination: meta/gui/adguard-home-web.desktop
|
'destination': 'meta/gui/adguard-home-web.desktop'
|
||||||
mode: 0644
|
'mode': 0644
|
||||||
- source: scripts/snap/gui/adguard-home-web.png
|
- 'source': 'scripts/snap/gui/adguard-home-web.png'
|
||||||
destination: meta/gui/adguard-home-web.png
|
'destination': 'meta/gui/adguard-home-web.png'
|
||||||
mode: 0644
|
'mode': 0644
|
||||||
apps:
|
'apps':
|
||||||
adguard-home:
|
'adguard-home':
|
||||||
command: AdGuardHome -w $SNAP_DATA --no-check-update
|
'command': 'AdGuardHome -w $SNAP_DATA --no-check-update'
|
||||||
plugs:
|
'plugs':
|
||||||
# Add the "netrwork-bind" plug to bind to interfaces.
|
# Add the "netrwork-bind" plug to bind to interfaces.
|
||||||
- network-bind
|
- 'network-bind'
|
||||||
# Add the "netrwork-control" plug to be able to bind to ports below
|
# Add the "netrwork-observe" plug to be able to bind to ports below 1024
|
||||||
# 1024 (cap_net_bind_service) and also to bind to a particular
|
# (cap_net_bind_service) and also to bind to a particular interface using
|
||||||
# interface using SO_BINDTODEVICE (cap_net_raw).
|
# SO_BINDTODEVICE (cap_net_raw).
|
||||||
- network-control
|
- 'network-observe'
|
||||||
daemon: simple
|
'daemon': 'simple'
|
||||||
adguard-home-web:
|
'adguard-home-web':
|
||||||
command: adguard-home-web.sh
|
'command': 'adguard-home-web.sh'
|
||||||
plugs: [ desktop ]
|
'plugs':
|
||||||
|
- 'desktop'
|
||||||
|
|
||||||
checksum:
|
'checksum':
|
||||||
name_template: 'checksums.txt'
|
'name_template': 'checksums.txt'
|
||||||
|
|
||||||
|
'snapshot':
|
||||||
|
# TODO(a.garipov): A temporary solution to trim the prerelease versions.
|
||||||
|
# A real solution would consist of making a better versioning scheme that also
|
||||||
|
# doesn't break SemVer or Snapcraft.
|
||||||
|
#
|
||||||
|
# See https://github.com/AdguardTeam/AdGuardHome/issues/2412.
|
||||||
|
'name_template': '{{ slice .Tag 0 8 }}-SNAPSHOT-{{ .ShortCommit }}'
|
||||||
|
|
|
@ -1833,16 +1833,22 @@ Response:
|
||||||
200 OK
|
200 OK
|
||||||
|
|
||||||
{
|
{
|
||||||
"reason":"FilteredBlackList",
|
"reason":"FilteredBlackList",
|
||||||
"filter_id":1,
|
"rules":{
|
||||||
"rule":"||doubleclick.net^",
|
"filter_list_id":42,
|
||||||
"service_name": "...", // set if reason=FilteredBlockedService
|
"text":"||doubleclick.net^",
|
||||||
|
},
|
||||||
// if reason=ReasonRewrite:
|
// If we have "reason":"FilteredBlockedService".
|
||||||
"cname": "...",
|
"service_name": "...",
|
||||||
"ip_addrs": ["1.2.3.4", ...],
|
// If we have "reason":"Rewrite".
|
||||||
|
"cname": "...",
|
||||||
|
"ip_addrs": ["1.2.3.4", ...]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
There are also deprecated properties `filter_id` and `rule` on the top level of
|
||||||
|
the response object. Their usaga should be replaced with
|
||||||
|
`rules[*].filter_list_id` and `rules[*].text` correspondingly. See the
|
||||||
|
_OpenAPI_ documentation and the `./openapi/CHANGELOG.md` file.
|
||||||
|
|
||||||
## Log-in page
|
## Log-in page
|
||||||
|
|
||||||
|
|
106
CHANGELOG.md
106
CHANGELOG.md
|
@ -9,17 +9,115 @@ and this project adheres to
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
<!--
|
||||||
|
## [v0.105.0] - 2020-12-28
|
||||||
|
-->
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- This changelog :-) (#2294).
|
- `$dnsrewrite` modifier for filters ([#2102]).
|
||||||
|
- The host checking API and the query logs API can now return multiple matched
|
||||||
|
rules ([#2102]).
|
||||||
|
- Detecting of network interface configured to have static IP address via
|
||||||
|
`/etc/network/interfaces` ([#2302]).
|
||||||
|
- DNSCrypt protocol support ([#1361]).
|
||||||
|
- A 5 second wait period until a DHCP server's network interface gets an IP
|
||||||
|
address ([#2304]).
|
||||||
|
- `$dnstype` modifier for filters ([#2337]).
|
||||||
|
- HTTP API request body size limit ([#2305]).
|
||||||
|
|
||||||
|
[#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361
|
||||||
|
[#2102]: https://github.com/AdguardTeam/AdGuardHome/issues/2102
|
||||||
|
[#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302
|
||||||
|
[#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304
|
||||||
|
[#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305
|
||||||
|
[#2337]: https://github.com/AdguardTeam/AdGuardHome/issues/2337
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- When `dns.bogus_nxdomain` option is used, the server will now transform
|
||||||
|
responses if there is at least one bogus address instead of all of them
|
||||||
|
([#2394]). The new behavior is the same as in `dnsmasq`.
|
||||||
|
- Post-updating relaunch possibility is now determined OS-dependently ([#2231],
|
||||||
|
[#2391]).
|
||||||
|
- Made the mobileconfig HTTP API more robust and predictable, add parameters and
|
||||||
|
improve error response ([#2358]).
|
||||||
|
- Improved HTTP requests handling and timeouts ([#2343]).
|
||||||
|
- Our snap package now uses the `core20` image as its base ([#2306]).
|
||||||
|
- Various internal improvements ([#2267], [#2271], [#2297]).
|
||||||
|
|
||||||
|
[#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231
|
||||||
|
[#2267]: https://github.com/AdguardTeam/AdGuardHome/issues/2267
|
||||||
|
[#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271
|
||||||
|
[#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297
|
||||||
|
[#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306
|
||||||
|
[#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343
|
||||||
|
[#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358
|
||||||
|
[#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391
|
||||||
|
[#2394]: https://github.com/AdguardTeam/AdGuardHome/issues/2394
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Inability to set DNS cache TTL limits ([#2459]).
|
||||||
|
- Possible freezes on slower machines ([#2225]).
|
||||||
|
- A mitigation against records being shown in the wrong order on the query log
|
||||||
|
page ([#2293]).
|
||||||
|
- A JSON parsing error in query log ([#2345]).
|
||||||
|
- Incorrect detection of the IPv6 address of an interface as well as another
|
||||||
|
infinite loop in the `/dhcp/find_active_dhcp` HTTP API ([#2355]).
|
||||||
|
|
||||||
|
[#2225]: https://github.com/AdguardTeam/AdGuardHome/issues/2225
|
||||||
|
[#2293]: https://github.com/AdguardTeam/AdGuardHome/issues/2293
|
||||||
|
[#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345
|
||||||
|
[#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355
|
||||||
|
[#2459]: https://github.com/AdguardTeam/AdGuardHome/issues/2459
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Support for pre-v0.99.3 format of query logs ([#2102]).
|
||||||
|
|
||||||
|
## [v0.104.3] - 2020-11-19
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The accidentally exposed profiler HTTP API ([#2336]).
|
||||||
|
|
||||||
|
[#2336]: https://github.com/AdguardTeam/AdGuardHome/issues/2336
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.104.2] - 2020-11-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- This changelog :-) ([#2294]).
|
||||||
- `HACKING.md`, a guide for developers.
|
- `HACKING.md`, a guide for developers.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improved tests output (#2273).
|
- Improved tests output ([#2273]).
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Query logs from file not loading after the ones buffered in memory ([#2325]).
|
||||||
|
- Unnecessary errors in query logs when switching between log files ([#2324]).
|
||||||
- `404 Not Found` errors on the DHCP settings page on *Windows*. The page now
|
- `404 Not Found` errors on the DHCP settings page on *Windows*. The page now
|
||||||
correctly shows that DHCP is not currently available on that OS (#2295).
|
correctly shows that DHCP is not currently available on that OS ([#2295]).
|
||||||
- Infinite loop in `/dhcp/find_active_dhcp` (#2301).
|
- Infinite loop in `/dhcp/find_active_dhcp` ([#2301]).
|
||||||
|
|
||||||
|
[#2273]: https://github.com/AdguardTeam/AdGuardHome/issues/2273
|
||||||
|
[#2294]: https://github.com/AdguardTeam/AdGuardHome/issues/2294
|
||||||
|
[#2295]: https://github.com/AdguardTeam/AdGuardHome/issues/2295
|
||||||
|
[#2301]: https://github.com/AdguardTeam/AdGuardHome/issues/2301
|
||||||
|
[#2324]: https://github.com/AdguardTeam/AdGuardHome/issues/2324
|
||||||
|
[#2325]: https://github.com/AdguardTeam/AdGuardHome/issues/2325
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.105.0...HEAD
|
||||||
|
[v0.105.0]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...v0.105.0
|
||||||
|
-->
|
||||||
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...HEAD
|
||||||
|
[v0.104.3]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.2...v0.104.3
|
||||||
|
[v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.1...v0.104.2
|
||||||
|
|
186
HACKING.md
186
HACKING.md
|
@ -1,10 +1,17 @@
|
||||||
# AdGuardHome Developer Guidelines
|
# *AdGuardHome* Developer Guidelines
|
||||||
|
|
||||||
As of **2020-11-12**, this document is still a work-in-progress. Some of the
|
As of **December 2020**, this document is partially a work-in-progress, but
|
||||||
rules aren't enforced, and others might change. Still, this is a good place to
|
should still be followed. Some of the rules aren't enforced as thoroughly or
|
||||||
find out about how we **want** our code to look like.
|
remain broken in old code, but this is still the place to find out about what we
|
||||||
|
**want** our code to look like.
|
||||||
|
|
||||||
## Git
|
The rules are mostly sorted in the alphabetical order.
|
||||||
|
|
||||||
|
## *Git*
|
||||||
|
|
||||||
|
* Call your branches either `NNNN-fix-foo` (where `NNNN` is the ID of the
|
||||||
|
*GitHub* issue you worked on in this branch) or just `fix-foo` if there was
|
||||||
|
no *GitHub* issue.
|
||||||
|
|
||||||
* Follow the commit message header format:
|
* Follow the commit message header format:
|
||||||
|
|
||||||
|
@ -13,28 +20,33 @@ find out about how we **want** our code to look like.
|
||||||
```
|
```
|
||||||
|
|
||||||
Where `pkg` is the package where most changes took place. If there are
|
Where `pkg` is the package where most changes took place. If there are
|
||||||
several such packages, just write `all`.
|
several such packages, or the change is top-level only, write `all`.
|
||||||
|
|
||||||
* Keep your commit messages to be no wider than eighty (**80**) columns.
|
* Keep your commit messages, including headers, to eighty (**80**) columns.
|
||||||
|
|
||||||
* Only use lowercase letters in your commit message headers.
|
* Only use lowercase letters in your commit message headers. The rest of the
|
||||||
|
message should follow the plain text conventions below.
|
||||||
|
|
||||||
## Go
|
The only exceptions are direct mentions of identifiers from the source code
|
||||||
|
and filenames like `HACKING.md`.
|
||||||
|
|
||||||
* <https://github.com/golang/go/wiki/CodeReviewComments>.
|
## *Go*
|
||||||
|
|
||||||
* <https://github.com/golang/go/wiki/TestComments>.
|
> Not Golang, not GO, not GOLANG, not GoLang. It is Go in natural language,
|
||||||
|
> golang for others.
|
||||||
|
|
||||||
* <https://go-proverbs.github.io/>
|
— [@rakyll](https://twitter.com/rakyll/status/1229850223184269312)
|
||||||
|
|
||||||
|
### Code And Naming
|
||||||
|
|
||||||
|
* Avoid `goto`.
|
||||||
|
|
||||||
* Avoid `init` and use explicit initialization functions instead.
|
* Avoid `init` and use explicit initialization functions instead.
|
||||||
|
|
||||||
* Avoid `new`, especially with structs.
|
* Avoid `new`, especially with structs.
|
||||||
|
|
||||||
* Document everything, including unexported top-level identifiers, to build
|
* Constructors should validate their arguments and return meaningful errors.
|
||||||
a habit of writing documentation.
|
As a corollary, avoid lazy initialization.
|
||||||
|
|
||||||
* Don't put variable names into any kind of quotes.
|
|
||||||
|
|
||||||
* Don't use naked `return`s.
|
* Don't use naked `return`s.
|
||||||
|
|
||||||
|
@ -48,7 +60,17 @@ find out about how we **want** our code to look like.
|
||||||
* Eschew external dependencies, including transitive, unless
|
* Eschew external dependencies, including transitive, unless
|
||||||
absolutely necessary.
|
absolutely necessary.
|
||||||
|
|
||||||
* No `goto`.
|
* Name benchmarks and tests using the same convention as examples. For
|
||||||
|
example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func TestFunction(t *testing.T) { /* … */ }
|
||||||
|
func TestFunction_suffix(t *testing.T) { /* … */ }
|
||||||
|
func TestType_Method(t *testing.T) { /* … */ }
|
||||||
|
func TestType_Method_suffix(t *testing.T) { /* … */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
* Name the deferred errors (e.g. when closing something) `cerr`.
|
||||||
|
|
||||||
* No shadowing, since it can often lead to subtle bugs, especially with
|
* No shadowing, since it can often lead to subtle bugs, especially with
|
||||||
errors.
|
errors.
|
||||||
|
@ -56,29 +78,61 @@ find out about how we **want** our code to look like.
|
||||||
* Prefer constants to variables where possible. Reduce global variables. Use
|
* Prefer constants to variables where possible. Reduce global variables. Use
|
||||||
[constant errors] instead of `errors.New`.
|
[constant errors] instead of `errors.New`.
|
||||||
|
|
||||||
* Put comments above the documented entity, **not** to the side, to improve
|
* Unused arguments in anonymous functions must be called `_`:
|
||||||
readability.
|
|
||||||
|
|
||||||
* Use `gofumpt --extra -s`.
|
```go
|
||||||
|
v.onSuccess = func(_ int, msg string) {
|
||||||
**TODO(a.garipov):** Add to the linters.
|
// …
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
* Use linters.
|
* Use linters.
|
||||||
|
|
||||||
* Use named returns to improve readability of function signatures.
|
* Use named returns to improve readability of function signatures.
|
||||||
|
|
||||||
|
* Write logs and error messages in lowercase only to make it easier to `grep`
|
||||||
|
logs and error messages without using the `-i` flag.
|
||||||
|
|
||||||
|
[constant errors]: https://dave.cheney.net/2016/04/07/constant-errors
|
||||||
|
[Linus said]: https://www.kernel.org/doc/html/v4.17/process/coding-style.html#indentation
|
||||||
|
|
||||||
|
### Commenting
|
||||||
|
|
||||||
|
* See also the *Text, Including Comments* section below.
|
||||||
|
|
||||||
|
* Document everything, including unexported top-level identifiers, to build
|
||||||
|
a habit of writing documentation.
|
||||||
|
|
||||||
|
* Don't put identifiers into any kind of quotes.
|
||||||
|
|
||||||
|
* Put comments above the documented entity, **not** to the side, to improve
|
||||||
|
readability.
|
||||||
|
|
||||||
* When a method implements an interface, start the doc comment with the
|
* When a method implements an interface, start the doc comment with the
|
||||||
standard template:
|
standard template:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Foo implements the Fooer interface for *foo.
|
// Foo implements the Fooer interface for *foo.
|
||||||
func (f *foo) Foo() {
|
func (f *foo) Foo() {
|
||||||
// …
|
// …
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* Write logs and error messages in lowercase only to make it easier to `grep`
|
When the implemented interface is unexported:
|
||||||
logs and error messages without using the `-i` flag.
|
|
||||||
|
```go
|
||||||
|
// Unwrap implements the hidden wrapper interface for *fooError.
|
||||||
|
func (err *fooError) Unwrap() (unwrapped error) {
|
||||||
|
// …
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
|
||||||
|
* Add an empty line before `break`, `continue`, `fallthrough`, and `return`,
|
||||||
|
unless it's the only statement in that block.
|
||||||
|
|
||||||
|
* Use `gofumpt --extra -s`.
|
||||||
|
|
||||||
* Write slices of struct like this:
|
* Write slices of struct like this:
|
||||||
|
|
||||||
|
@ -95,11 +149,64 @@ find out about how we **want** our code to look like.
|
||||||
}}
|
}}
|
||||||
```
|
```
|
||||||
|
|
||||||
[constant errors]: https://dave.cheney.net/2016/04/07/constant-errors
|
### Recommended Reading
|
||||||
[Linus said]: https://www.kernel.org/doc/html/v4.17/process/coding-style.html#indentation
|
|
||||||
|
* <https://github.com/golang/go/wiki/CodeReviewComments>.
|
||||||
|
|
||||||
|
* <https://github.com/golang/go/wiki/TestComments>.
|
||||||
|
|
||||||
|
* <https://go-proverbs.github.io/>
|
||||||
|
|
||||||
|
## *Markdown*
|
||||||
|
|
||||||
|
* **TODO(a.garipov):** Define our *Markdown* conventions.
|
||||||
|
|
||||||
|
## Shell Scripting
|
||||||
|
|
||||||
|
* Avoid bashisms and GNUisms, prefer *POSIX* features only.
|
||||||
|
|
||||||
|
* Prefer `'raw strings'` to `"double quoted strings"` whenever possible.
|
||||||
|
|
||||||
|
* Put spaces within `$( cmd )`, `$(( expr ))`, and `{ cmd; }`.
|
||||||
|
|
||||||
|
* Put utility flags in the ASCII order and **don't** group them together. For
|
||||||
|
example, `ls -1 -A -q`.
|
||||||
|
|
||||||
|
* `snake_case`, not `camelCase`.
|
||||||
|
|
||||||
|
* Use `set -e -f -u` and also `set -x` in verbose mode.
|
||||||
|
|
||||||
|
* Use the `"$var"` form instead of the `$var` form, unless word splitting is
|
||||||
|
required.
|
||||||
|
|
||||||
|
* When concatenating, always use the form with curly braces to prevent
|
||||||
|
accidental bad variable names. That is, `"${var}_tmp.txt"` and **not**
|
||||||
|
`"$var_tmp.txt"`. The latter will try to lookup variable `var_tmp`.
|
||||||
|
|
||||||
|
* When concatenating, surround the whole string with quotes. That is, use
|
||||||
|
this:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dir="${TOP_DIR}/sub"
|
||||||
|
```
|
||||||
|
|
||||||
|
And **not** this:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Bad!
|
||||||
|
dir="${TOP_DIR}"/sub
|
||||||
|
```
|
||||||
|
|
||||||
## Text, Including Comments
|
## Text, Including Comments
|
||||||
|
|
||||||
|
* End sentences with appropriate punctuation.
|
||||||
|
|
||||||
|
* Headers should be written with all initial letters capitalized, except for
|
||||||
|
references to variable names that start with a lowercase letter.
|
||||||
|
|
||||||
|
* Start sentences with a capital letter, unless the first word is a reference
|
||||||
|
to a variable name that starts with a lowercase letter.
|
||||||
|
|
||||||
* Text should wrap at eighty (**80**) columns to be more readable, to use
|
* Text should wrap at eighty (**80**) columns to be more readable, to use
|
||||||
a common standard, and to allow editing or diffing side-by-side without
|
a common standard, and to allow editing or diffing side-by-side without
|
||||||
wrapping.
|
wrapping.
|
||||||
|
@ -111,7 +218,7 @@ find out about how we **want** our code to look like.
|
||||||
|
|
||||||
* Use double spacing between sentences to make sentence borders more clear.
|
* Use double spacing between sentences to make sentence borders more clear.
|
||||||
|
|
||||||
* Use the serial comma (a.k.a. Oxford comma) to improve comprehension,
|
* Use the serial comma (a.k.a. *Oxford* comma) to improve comprehension,
|
||||||
decrease ambiguity, and use a common standard.
|
decrease ambiguity, and use a common standard.
|
||||||
|
|
||||||
* Write todos like this:
|
* Write todos like this:
|
||||||
|
@ -126,17 +233,17 @@ find out about how we **want** our code to look like.
|
||||||
// TODO(usr1, usr2): Fix the frobulation issue.
|
// TODO(usr1, usr2): Fix the frobulation issue.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Markdown
|
## *YAML*
|
||||||
|
|
||||||
* **TODO(a.garipov):** Define our Markdown conventions.
|
* **TODO(a.garipov):** Define naming conventions for schema names in our
|
||||||
|
*OpenAPI* *YAML* file. And just generally OpenAPI conventions.
|
||||||
|
|
||||||
## YAML
|
* **TODO(a.garipov):** Find a *YAML* formatter or write our own.
|
||||||
|
|
||||||
* **TODO(a.garipov):** Find a YAML formatter or write our own.
|
* All strings, including keys, must be quoted. Reason: the [*NO-rway Law*].
|
||||||
|
|
||||||
* All strings, including keys, must be quoted. Reason: the [NO-rway Law].
|
* Indent with two (**2**) spaces. *YAML* documents can get pretty
|
||||||
|
deeply-nested.
|
||||||
* Indent with two (**2**) spaces.
|
|
||||||
|
|
||||||
* No extra indentation in multiline arrays:
|
* No extra indentation in multiline arrays:
|
||||||
|
|
||||||
|
@ -147,7 +254,10 @@ find out about how we **want** our code to look like.
|
||||||
- 'value-3'
|
- 'value-3'
|
||||||
```
|
```
|
||||||
|
|
||||||
* Prefer single quotes for string to prevent accidental escaping, unless
|
* Prefer single quotes for strings to prevent accidental escaping, unless
|
||||||
escaping is required.
|
escaping is required or there are single quotes inside the string (e.g. for
|
||||||
|
*GitHub Actions*).
|
||||||
|
|
||||||
[NO-rway Law]: https://news.ycombinator.com/item?id=17359376
|
* Use `>` for multiline strings, unless you need to keep the line breaks.
|
||||||
|
|
||||||
|
[*NO-rway Law*]: https://news.ycombinator.com/item?id=17359376
|
||||||
|
|
67
Makefile
67
Makefile
|
@ -26,13 +26,16 @@
|
||||||
# * DOCKER_IMAGE_NAME - adguard/adguard-home
|
# * DOCKER_IMAGE_NAME - adguard/adguard-home
|
||||||
# * DOCKER_OUTPUT - type=image,name=adguard/adguard-home,push=true
|
# * DOCKER_OUTPUT - type=image,name=adguard/adguard-home,push=true
|
||||||
|
|
||||||
GOPATH := $(shell go env GOPATH)
|
GO := go
|
||||||
|
GOPATH := $(shell $(GO) env GOPATH)
|
||||||
PWD := $(shell pwd)
|
PWD := $(shell pwd)
|
||||||
TARGET=AdGuardHome
|
TARGET=AdGuardHome
|
||||||
BASE_URL="https://static.adguard.com/adguardhome/$(CHANNEL)"
|
BASE_URL="https://static.adguard.com/adguardhome/$(CHANNEL)"
|
||||||
GPG_KEY := devteam@adguard.com
|
GPG_KEY := devteam@adguard.com
|
||||||
GPG_KEY_PASSPHRASE :=
|
GPG_KEY_PASSPHRASE :=
|
||||||
GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE)
|
GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE)
|
||||||
|
VERBOSE := -v
|
||||||
|
REBUILD_CLIENT = 1
|
||||||
|
|
||||||
# See release target
|
# See release target
|
||||||
DIST_DIR=dist
|
DIST_DIR=dist
|
||||||
|
@ -64,7 +67,9 @@ endif
|
||||||
|
|
||||||
# Version properties
|
# Version properties
|
||||||
COMMIT=$(shell git rev-parse --short HEAD)
|
COMMIT=$(shell git rev-parse --short HEAD)
|
||||||
TAG_NAME=$(shell git describe --abbrev=0)
|
# TODO(a.garipov): The cut call is a temporary solution to trim
|
||||||
|
# prerelease versions. See the comment in .goreleaser.yml.
|
||||||
|
TAG_NAME=$(shell git describe --abbrev=0 | cut -c 1-8)
|
||||||
RELEASE_VERSION=$(TAG_NAME)
|
RELEASE_VERSION=$(TAG_NAME)
|
||||||
SNAPSHOT_VERSION=$(RELEASE_VERSION)-SNAPSHOT-$(COMMIT)
|
SNAPSHOT_VERSION=$(RELEASE_VERSION)-SNAPSHOT-$(COMMIT)
|
||||||
|
|
||||||
|
@ -109,7 +114,7 @@ $(error DOCKER_IMAGE_NAME value is not set)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# OS-specific flags
|
# OS-specific flags
|
||||||
TEST_FLAGS := --race -v
|
TEST_FLAGS := --race $(VERBOSE)
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
TEST_FLAGS :=
|
TEST_FLAGS :=
|
||||||
endif
|
endif
|
||||||
|
@ -120,10 +125,11 @@ all: build
|
||||||
init:
|
init:
|
||||||
git config core.hooksPath .githooks
|
git config core.hooksPath .githooks
|
||||||
|
|
||||||
build: client_with_deps
|
build:
|
||||||
go mod download
|
test '$(REBUILD_CLIENT)' = '1' && $(MAKE) client_with_deps || exit 0
|
||||||
PATH=$(GOPATH)/bin:$(PATH) go generate ./...
|
$(GO) mod download
|
||||||
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)"
|
PATH=$(GOPATH)/bin:$(PATH) $(GO) generate ./...
|
||||||
|
CGO_ENABLED=0 $(GO) build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)"
|
||||||
PATH=$(GOPATH)/bin:$(PATH) packr clean
|
PATH=$(GOPATH)/bin:$(PATH) packr clean
|
||||||
|
|
||||||
client:
|
client:
|
||||||
|
@ -150,47 +156,40 @@ docker:
|
||||||
@echo Now you can run the docker image:
|
@echo Now you can run the docker image:
|
||||||
@echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME)
|
@echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME)
|
||||||
|
|
||||||
lint: lint-js lint-go
|
lint: js-lint go-lint
|
||||||
|
|
||||||
lint-js: dependencies
|
js-lint: dependencies
|
||||||
@echo Running js linter
|
|
||||||
npm --prefix client run lint
|
npm --prefix client run lint
|
||||||
|
|
||||||
lint-go:
|
go-install-tools:
|
||||||
@echo Running go linter
|
env GO=$(GO) sh ./scripts/go-install-tools.sh
|
||||||
golangci-lint run
|
|
||||||
|
|
||||||
test: test-js test-go
|
go-lint:
|
||||||
|
env GO=$(GO) PATH="$$PWD/bin:$$PATH" sh ./scripts/go-lint.sh
|
||||||
|
|
||||||
test-js:
|
test: js-test go-test
|
||||||
|
|
||||||
|
js-test:
|
||||||
npm run test --prefix client
|
npm run test --prefix client
|
||||||
|
|
||||||
test-go:
|
go-test:
|
||||||
go test $(TEST_FLAGS) --coverprofile coverage.txt ./...
|
$(GO) test $(TEST_FLAGS) --coverprofile coverage.txt ./...
|
||||||
|
|
||||||
ci: client_with_deps
|
ci: client_with_deps
|
||||||
go mod download
|
$(GO) mod download
|
||||||
$(MAKE) test
|
$(MAKE) test
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
npm --prefix client ci
|
npm --prefix client ci
|
||||||
go mod download
|
$(GO) mod download
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
# make build output
|
rm -f ./AdGuardHome ./AdGuardHome.exe ./coverage.txt
|
||||||
rm -f AdGuardHome
|
rm -f -r ./build/ ./client/node_modules/ ./data/ ./$(DIST_DIR)/
|
||||||
rm -f AdGuardHome.exe
|
# Set the GOPATH explicitly in case make clean is called from under sudo
|
||||||
# tests output
|
# after a Docker build.
|
||||||
rm -rf data
|
env PATH="$(GOPATH)/bin:$$PATH" packr clean
|
||||||
rm -f coverage.txt
|
rm -f -r ./bin/
|
||||||
# static build output
|
|
||||||
rm -rf build
|
|
||||||
# dist folder
|
|
||||||
rm -rf $(DIST_DIR)
|
|
||||||
# client deps
|
|
||||||
rm -rf client/node_modules
|
|
||||||
# packr-generated files
|
|
||||||
PATH=$(GOPATH)/bin:$(PATH) packr clean || true
|
|
||||||
|
|
||||||
docker-multi-arch:
|
docker-multi-arch:
|
||||||
DOCKER_CLI_EXPERIMENTAL=enabled \
|
DOCKER_CLI_EXPERIMENTAL=enabled \
|
||||||
|
@ -208,7 +207,7 @@ docker-multi-arch:
|
||||||
@echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME)
|
@echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME)
|
||||||
|
|
||||||
release: client_with_deps
|
release: client_with_deps
|
||||||
go mod download
|
$(GO) mod download
|
||||||
@echo Starting release build: version $(VERSION), channel $(CHANNEL)
|
@echo Starting release build: version $(VERSION), channel $(CHANNEL)
|
||||||
CHANNEL=$(CHANNEL) $(GORELEASER_COMMAND)
|
CHANNEL=$(CHANNEL) $(GORELEASER_COMMAND)
|
||||||
$(call write_version_file,$(VERSION))
|
$(call write_version_file,$(VERSION))
|
||||||
|
|
11
README.md
11
README.md
|
@ -171,9 +171,6 @@ You will need this to build AdGuard Home:
|
||||||
* [node.js](https://nodejs.org/en/download/) v10.16.2 or later.
|
* [node.js](https://nodejs.org/en/download/) v10.16.2 or later.
|
||||||
* [npm](https://www.npmjs.com/) v6.14 or later.
|
* [npm](https://www.npmjs.com/) v6.14 or later.
|
||||||
|
|
||||||
Optionally, for Go devs:
|
|
||||||
* [golangci-lint](https://github.com/golangci/golangci-lint)
|
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
Open Terminal and execute these commands:
|
Open Terminal and execute these commands:
|
||||||
|
@ -186,7 +183,7 @@ make
|
||||||
|
|
||||||
Check the [`Makefile`](https://github.com/AdguardTeam/AdGuardHome/blob/master/Makefile) to learn about other commands.
|
Check the [`Makefile`](https://github.com/AdguardTeam/AdGuardHome/blob/master/Makefile) to learn about other commands.
|
||||||
|
|
||||||
**Building for a different platform.** You can build AdGuard for any OS/ARCH just like any other Golang project.
|
**Building for a different platform.** You can build AdGuard for any OS/ARCH just like any other Go project.
|
||||||
In order to do this, specify `GOOS` and `GOARCH` env variables before running make.
|
In order to do this, specify `GOOS` and `GOARCH` env variables before running make.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
@ -258,7 +255,7 @@ curl -sSL https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scrip
|
||||||
|
|
||||||
* Beta channel builds
|
* Beta channel builds
|
||||||
* Linux: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
|
* Linux: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
|
||||||
* Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Rapsberry Pi), [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz)
|
* Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi), [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz)
|
||||||
* Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
* Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
||||||
* Windows: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip)
|
* Windows: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip)
|
||||||
* MacOS: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip)
|
* MacOS: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip)
|
||||||
|
@ -267,7 +264,7 @@ curl -sSL https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scrip
|
||||||
|
|
||||||
* Edge channel builds
|
* Edge channel builds
|
||||||
* Linux: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_386.tar.gz)
|
* Linux: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_386.tar.gz)
|
||||||
* Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Rapsberry Pi), [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz)
|
* Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi), [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz)
|
||||||
* Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
* Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
||||||
* Windows: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_386.zip)
|
* Windows: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_386.zip)
|
||||||
* MacOS: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_386.zip)
|
* MacOS: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_386.zip)
|
||||||
|
@ -331,4 +328,4 @@ For a full list of all node.js packages in use, please take a look at [client/pa
|
||||||
<a id="privacy"></a>
|
<a id="privacy"></a>
|
||||||
## Privacy
|
## Privacy
|
||||||
|
|
||||||
Our main idea is that you are the one, who should be in control of your data. So it is only natural, that AdGuard Home does not collect any usage statistics, and does not use any web services unless you configure it to do so. Full policy with every bit that _could in theory be_ sent by AdGuard Home is available [here](https://adguard.com/en/privacy/home.html).
|
Our main idea is that you are the one, who should be in control of your data. So it is only natural, that AdGuard Home does not collect any usage statistics, and does not use any web services unless you configure it to do so. Full policy with every bit that _could in theory be_ sent by AdGuard Home is available [here](https://adguard.com/en/privacy/home.html).
|
||||||
|
|
|
@ -273,15 +273,15 @@ describe('sortIp', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('invalid input', () => {
|
describe('invalid input', () => {
|
||||||
const originalError = console.error;
|
const originalWarn = console.warn;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
console.error = jest.fn();
|
console.warn = jest.fn();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
expect(console.error).toHaveBeenCalled();
|
expect(console.warn).toHaveBeenCalled();
|
||||||
console.error = originalError;
|
console.warn = originalWarn;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('invalid strings', () => {
|
test('invalid strings', () => {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
import { withTranslation } from 'react-i18next';
|
import { withTranslation } from 'react-i18next';
|
||||||
|
import { sortIp } from '../../../helpers/helpers';
|
||||||
|
|
||||||
class Table extends Component {
|
class Table extends Component {
|
||||||
cellWrap = ({ value }) => (
|
cellWrap = ({ value }) => (
|
||||||
|
@ -21,6 +22,7 @@ class Table extends Component {
|
||||||
{
|
{
|
||||||
Header: this.props.t('answer'),
|
Header: this.props.t('answer'),
|
||||||
accessor: 'answer',
|
accessor: 'answer',
|
||||||
|
sortMethod: sortIp,
|
||||||
Cell: this.cellWrap,
|
Cell: this.cellWrap,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -259,7 +259,7 @@ let Form = (props) => {
|
||||||
</div>
|
</div>
|
||||||
<div className="form__desc mt-0 mb-2">
|
<div className="form__desc mt-0 mb-2">
|
||||||
<Trans components={[
|
<Trans components={[
|
||||||
<a href="https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists#ctag"
|
<a target="_blank" rel="noopener noreferrer" href="https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists#ctag"
|
||||||
key="0">link</a>,
|
key="0">link</a>,
|
||||||
]}>
|
]}>
|
||||||
tags_desc
|
tags_desc
|
||||||
|
|
|
@ -72,12 +72,12 @@ const Interfaces = () => {
|
||||||
(store) => store.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name,
|
(store) => store.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name,
|
||||||
);
|
);
|
||||||
|
|
||||||
const interfaceValue = interface_name && interfaces[interface_name];
|
|
||||||
|
|
||||||
if (processingInterfaces || !interfaces) {
|
if (processingInterfaces || !interfaces) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const interfaceValue = interface_name && interfaces[interface_name];
|
||||||
|
|
||||||
return <div className="row dhcp__interfaces">
|
return <div className="row dhcp__interfaces">
|
||||||
<div className="col col__dhcp">
|
<div className="col col__dhcp">
|
||||||
<Field
|
<Field
|
||||||
|
|
|
@ -113,9 +113,6 @@ const Dhcp = () => {
|
||||||
const enteredSomeValue = enteredSomeV4Value || enteredSomeV6Value || interfaceName;
|
const enteredSomeValue = enteredSomeV4Value || enteredSomeV6Value || interfaceName;
|
||||||
|
|
||||||
const getToggleDhcpButton = () => {
|
const getToggleDhcpButton = () => {
|
||||||
const otherDhcpFound = check && (check.v4.other_server.found === STATUS_RESPONSE.YES
|
|
||||||
|| check.v6.other_server.found === STATUS_RESPONSE.YES);
|
|
||||||
|
|
||||||
const filledConfig = interface_name && (Object.values(v4)
|
const filledConfig = interface_name && (Object.values(v4)
|
||||||
.every(Boolean) || Object.values(v6)
|
.every(Boolean) || Object.values(v6)
|
||||||
.every(Boolean));
|
.every(Boolean));
|
||||||
|
@ -141,7 +138,7 @@ const Dhcp = () => {
|
||||||
className={className}
|
className={className}
|
||||||
onClick={enabled ? onClickDisable : onClickEnable}
|
onClick={enabled ? onClickDisable : onClickEnable}
|
||||||
disabled={processingDhcp || processingConfig
|
disabled={processingDhcp || processingConfig
|
||||||
|| (!enabled && (!filledConfig || !check || otherDhcpFound))}
|
|| (!enabled && (!filledConfig || !check))}
|
||||||
>
|
>
|
||||||
<Trans>{enabled ? 'dhcp_disable' : 'dhcp_enable'}</Trans>
|
<Trans>{enabled ? 'dhcp_disable' : 'dhcp_enable'}</Trans>
|
||||||
</button>;
|
</button>;
|
||||||
|
|
|
@ -232,7 +232,7 @@ let Form = (props) => {
|
||||||
<Trans
|
<Trans
|
||||||
values={{ link: 'letsencrypt.org' }}
|
values={{ link: 'letsencrypt.org' }}
|
||||||
components={[
|
components={[
|
||||||
<a href="https://letsencrypt.org/" key="0">
|
<a target="_blank" rel="noopener noreferrer" href="https://letsencrypt.org/" key="0">
|
||||||
link
|
link
|
||||||
</a>,
|
</a>,
|
||||||
]}
|
]}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import flow from 'lodash/flow';
|
||||||
import { CheckboxField, toNumber } from '../../../helpers/form';
|
import { CheckboxField, toNumber } from '../../../helpers/form';
|
||||||
import {
|
import {
|
||||||
FILTERS_INTERVALS_HOURS,
|
FILTERS_INTERVALS_HOURS,
|
||||||
FILTERS_LINK,
|
FILTERS_RELATIVE_LINK,
|
||||||
FORM_NAME,
|
FORM_NAME,
|
||||||
} from '../../../helpers/constants';
|
} from '../../../helpers/constants';
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ const Form = (props) => {
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
a: <a href={FILTERS_LINK} rel="noopener noreferrer" />,
|
a: <a href={FILTERS_RELATIVE_LINK} rel="noopener noreferrer" />,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -6,6 +6,50 @@ import { useSelector } from 'react-redux';
|
||||||
import Topline from './Topline';
|
import Topline from './Topline';
|
||||||
import { EMPTY_DATE } from '../../helpers/constants';
|
import { EMPTY_DATE } from '../../helpers/constants';
|
||||||
|
|
||||||
|
const EXPIRATION_ENUM = {
|
||||||
|
VALID: 'VALID',
|
||||||
|
EXPIRED: 'EXPIRED',
|
||||||
|
EXPIRING: 'EXPIRING',
|
||||||
|
};
|
||||||
|
|
||||||
|
const EXPIRATION_STATE = {
|
||||||
|
[EXPIRATION_ENUM.EXPIRED]: {
|
||||||
|
toplineType: 'danger',
|
||||||
|
i18nKey: 'topline_expired_certificate',
|
||||||
|
},
|
||||||
|
[EXPIRATION_ENUM.EXPIRING]: {
|
||||||
|
toplineType: 'warning',
|
||||||
|
i18nKey: 'topline_expiring_certificate',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const getExpirationFlags = (not_after) => {
|
||||||
|
const DAYS_BEFORE_EXPIRATION = 5;
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const isExpiring = isAfter(addDays(now, DAYS_BEFORE_EXPIRATION), not_after);
|
||||||
|
const isExpired = isAfter(now, not_after);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isExpiring,
|
||||||
|
isExpired,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getExpirationEnumKey = (not_after) => {
|
||||||
|
const { isExpiring, isExpired } = getExpirationFlags(not_after);
|
||||||
|
|
||||||
|
if (isExpired) {
|
||||||
|
return EXPIRATION_ENUM.EXPIRED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExpiring) {
|
||||||
|
return EXPIRATION_ENUM.EXPIRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXPIRATION_ENUM.VALID;
|
||||||
|
};
|
||||||
|
|
||||||
const EncryptionTopline = () => {
|
const EncryptionTopline = () => {
|
||||||
const not_after = useSelector((state) => state.encryption.not_after);
|
const not_after = useSelector((state) => state.encryption.not_after);
|
||||||
|
|
||||||
|
@ -13,30 +57,21 @@ const EncryptionTopline = () => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAboutExpire = isAfter(addDays(Date.now(), 30), not_after);
|
const expirationStateKey = getExpirationEnumKey(not_after);
|
||||||
const isExpired = isAfter(Date.now(), not_after);
|
|
||||||
|
|
||||||
if (isExpired) {
|
if (expirationStateKey === EXPIRATION_ENUM.VALID) {
|
||||||
return (
|
return null;
|
||||||
<Topline type="danger">
|
|
||||||
<Trans components={[<a href="#encryption" key="0">link</a>]}>
|
|
||||||
topline_expired_certificate
|
|
||||||
</Trans>
|
|
||||||
</Topline>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAboutExpire) {
|
const { toplineType, i18nKey } = EXPIRATION_STATE[expirationStateKey];
|
||||||
return (
|
|
||||||
<Topline type="warning">
|
return (
|
||||||
|
<Topline type={toplineType}>
|
||||||
<Trans components={[<a href="#encryption" key="0">link</a>]}>
|
<Trans components={[<a href="#encryption" key="0">link</a>]}>
|
||||||
topline_expiring_certificate
|
{i18nKey}
|
||||||
</Trans>
|
</Trans>
|
||||||
</Topline>
|
</Topline>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EncryptionTopline;
|
export default EncryptionTopline;
|
||||||
|
|
|
@ -2,22 +2,25 @@ import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import Tabs from './Tabs';
|
import Tabs from './Tabs';
|
||||||
import Icons from './Icons';
|
import Icons from './Icons';
|
||||||
|
import { getPathWithQueryString } from '../../helpers/helpers';
|
||||||
|
|
||||||
const MOBILE_CONFIG_LINKS = {
|
const MOBILE_CONFIG_LINKS = {
|
||||||
DOT: '/apple/dot.mobileconfig',
|
DOT: '/apple/dot.mobileconfig',
|
||||||
DOH: '/apple/doh.mobileconfig',
|
DOH: '/apple/doh.mobileconfig',
|
||||||
};
|
};
|
||||||
|
const renderMobileconfigInfo = ({ label, components, server_name }) => <li key={label}>
|
||||||
const renderMobileconfigInfo = ({ label, components }) => <li key={label}>
|
|
||||||
<Trans components={components}>{label}</Trans>
|
<Trans components={components}>{label}</Trans>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href={MOBILE_CONFIG_LINKS.DOT} download>{i18next.t('download_mobileconfig_dot')}</a>
|
<a href={getPathWithQueryString(MOBILE_CONFIG_LINKS.DOT, { host: server_name })}
|
||||||
|
download>{i18next.t('download_mobileconfig_dot')}</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href={MOBILE_CONFIG_LINKS.DOH} download>{i18next.t('download_mobileconfig_doh')}</a>
|
<a href={getPathWithQueryString(MOBILE_CONFIG_LINKS.DOH, { host: server_name })}
|
||||||
|
download>{i18next.t('download_mobileconfig_doh')}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>;
|
</li>;
|
||||||
|
@ -38,37 +41,8 @@ const renderLi = ({ label, components }) => <li key={label}>
|
||||||
</Trans>
|
</Trans>
|
||||||
</li>;
|
</li>;
|
||||||
|
|
||||||
const dnsPrivacyList = [{
|
const getDnsPrivacyList = (server_name) => {
|
||||||
title: 'Android',
|
const iosList = [
|
||||||
list: [
|
|
||||||
{
|
|
||||||
label: 'setup_dns_privacy_android_1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'setup_dns_privacy_android_2',
|
|
||||||
components: [
|
|
||||||
{
|
|
||||||
key: 0,
|
|
||||||
href: 'https://adguard.com/adguard-android/overview.html',
|
|
||||||
},
|
|
||||||
<code key="1">text</code>,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'setup_dns_privacy_android_3',
|
|
||||||
components: [
|
|
||||||
{
|
|
||||||
key: 0,
|
|
||||||
href: 'https://getintra.org/',
|
|
||||||
},
|
|
||||||
<code key="1">text</code>,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'iOS',
|
|
||||||
list: [
|
|
||||||
{
|
{
|
||||||
label: 'setup_dns_privacy_ios_2',
|
label: 'setup_dns_privacy_ios_2',
|
||||||
components: [
|
components: [
|
||||||
|
@ -79,13 +53,6 @@ const dnsPrivacyList = [{
|
||||||
<code key="1">text</code>,
|
<code key="1">text</code>,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'setup_dns_privacy_4',
|
|
||||||
components: {
|
|
||||||
highlight: <code />,
|
|
||||||
},
|
|
||||||
renderComponent: renderMobileconfigInfo,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'setup_dns_privacy_ios_1',
|
label: 'setup_dns_privacy_ios_1',
|
||||||
components: [
|
components: [
|
||||||
|
@ -93,68 +60,114 @@ const dnsPrivacyList = [{
|
||||||
key: 0,
|
key: 0,
|
||||||
href: 'https://itunes.apple.com/app/id1452162351',
|
href: 'https://itunes.apple.com/app/id1452162351',
|
||||||
},
|
},
|
||||||
<code key="1">text</code>,
|
<code key="1">text</code>,
|
||||||
{
|
{
|
||||||
key: 2,
|
key: 2,
|
||||||
href: 'https://dnscrypt.info/stamps',
|
href: 'https://dnscrypt.info/stamps',
|
||||||
},
|
},
|
||||||
|
|
||||||
],
|
],
|
||||||
},
|
}];
|
||||||
],
|
/* Insert second element if can generate .mobileconfig links */
|
||||||
},
|
if (server_name) {
|
||||||
{
|
iosList.splice(1, 0, {
|
||||||
title: 'setup_dns_privacy_other_title',
|
label: 'setup_dns_privacy_4',
|
||||||
list: [
|
components: {
|
||||||
{
|
highlight: <code />,
|
||||||
label: 'setup_dns_privacy_other_1',
|
},
|
||||||
},
|
renderComponent: ({ label, components }) => renderMobileconfigInfo({
|
||||||
{
|
label,
|
||||||
label: 'setup_dns_privacy_other_2',
|
components,
|
||||||
components: [
|
server_name,
|
||||||
{
|
}),
|
||||||
key: 0,
|
});
|
||||||
href: 'https://github.com/AdguardTeam/dnsproxy',
|
}
|
||||||
},
|
|
||||||
],
|
return [{
|
||||||
},
|
title: 'Android',
|
||||||
{
|
list: [
|
||||||
href: 'https://github.com/jedisct1/dnscrypt-proxy',
|
{
|
||||||
label: 'setup_dns_privacy_other_3',
|
label: 'setup_dns_privacy_android_1',
|
||||||
components: [
|
},
|
||||||
{
|
{
|
||||||
key: 0,
|
label: 'setup_dns_privacy_android_2',
|
||||||
href: 'https://github.com/jedisct1/dnscrypt-proxy',
|
components: [
|
||||||
},
|
{
|
||||||
|
key: 0,
|
||||||
|
href: 'https://adguard.com/adguard-android/overview.html',
|
||||||
|
},
|
||||||
<code key="1">text</code>,
|
<code key="1">text</code>,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'setup_dns_privacy_other_4',
|
label: 'setup_dns_privacy_android_3',
|
||||||
components: [
|
components: [
|
||||||
{
|
{
|
||||||
key: 0,
|
key: 0,
|
||||||
href: 'https://support.mozilla.org/kb/firefox-dns-over-https',
|
href: 'https://getintra.org/',
|
||||||
},
|
},
|
||||||
<code key="1">text</code>,
|
<code key="1">text</code>,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
label: 'setup_dns_privacy_other_5',
|
},
|
||||||
components: [
|
{
|
||||||
{
|
title: 'iOS',
|
||||||
key: 0,
|
list: iosList,
|
||||||
href: 'https://dnscrypt.info/implementations',
|
},
|
||||||
},
|
{
|
||||||
{
|
title: 'setup_dns_privacy_other_title',
|
||||||
key: 1,
|
list: [
|
||||||
href: 'https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Clients',
|
{
|
||||||
},
|
label: 'setup_dns_privacy_other_1',
|
||||||
],
|
},
|
||||||
},
|
{
|
||||||
],
|
label: 'setup_dns_privacy_other_2',
|
||||||
},
|
components: [
|
||||||
];
|
{
|
||||||
|
key: 0,
|
||||||
|
href: 'https://github.com/AdguardTeam/dnsproxy',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: 'https://github.com/jedisct1/dnscrypt-proxy',
|
||||||
|
label: 'setup_dns_privacy_other_3',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
href: 'https://github.com/jedisct1/dnscrypt-proxy',
|
||||||
|
},
|
||||||
|
<code key="1">text</code>,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'setup_dns_privacy_other_4',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
href: 'https://support.mozilla.org/kb/firefox-dns-over-https',
|
||||||
|
},
|
||||||
|
<code key="1">text</code>,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'setup_dns_privacy_other_5',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
href: 'https://dnscrypt.info/implementations',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
href: 'https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Clients',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
const renderDnsPrivacyList = ({ title, list }) => <div className="tab__paragraph" key={title}>
|
const renderDnsPrivacyList = ({ title, list }) => <div className="tab__paragraph" key={title}>
|
||||||
<strong><Trans>{title}</Trans></strong>
|
<strong><Trans>{title}</Trans></strong>
|
||||||
|
@ -172,6 +185,7 @@ const getTabs = ({
|
||||||
tlsAddress,
|
tlsAddress,
|
||||||
httpsAddress,
|
httpsAddress,
|
||||||
showDnsPrivacyNotice,
|
showDnsPrivacyNotice,
|
||||||
|
server_name,
|
||||||
t,
|
t,
|
||||||
}) => ({
|
}) => ({
|
||||||
Router: {
|
Router: {
|
||||||
|
@ -277,7 +291,7 @@ const getTabs = ({
|
||||||
setup_dns_privacy_3
|
setup_dns_privacy_3
|
||||||
</Trans>
|
</Trans>
|
||||||
</div>
|
</div>
|
||||||
{dnsPrivacyList.map(renderDnsPrivacyList)}
|
{getDnsPrivacyList(server_name).map(renderDnsPrivacyList)}
|
||||||
</>}
|
</>}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
@ -299,6 +313,7 @@ const renderContent = ({ title, list, getTitle }) => <div key={title} label={i18
|
||||||
|
|
||||||
const Guide = ({ dnsAddresses }) => {
|
const Guide = ({ dnsAddresses }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const server_name = useSelector((state) => state.encryption.server_name);
|
||||||
const tlsAddress = dnsAddresses?.filter((item) => item.includes('tls://')) ?? '';
|
const tlsAddress = dnsAddresses?.filter((item) => item.includes('tls://')) ?? '';
|
||||||
const httpsAddress = dnsAddresses?.filter((item) => item.includes('https://')) ?? '';
|
const httpsAddress = dnsAddresses?.filter((item) => item.includes('https://')) ?? '';
|
||||||
const showDnsPrivacyNotice = httpsAddress.length < 1 && tlsAddress.length < 1;
|
const showDnsPrivacyNotice = httpsAddress.length < 1 && tlsAddress.length < 1;
|
||||||
|
@ -309,6 +324,7 @@ const Guide = ({ dnsAddresses }) => {
|
||||||
tlsAddress,
|
tlsAddress,
|
||||||
httpsAddress,
|
httpsAddress,
|
||||||
showDnsPrivacyNotice,
|
showDnsPrivacyNotice,
|
||||||
|
server_name,
|
||||||
t,
|
t,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -53,10 +53,10 @@ export const REPOSITORY = {
|
||||||
export const PRIVACY_POLICY_LINK = 'https://adguard.com/privacy/home.html';
|
export const PRIVACY_POLICY_LINK = 'https://adguard.com/privacy/home.html';
|
||||||
export const PORT_53_FAQ_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ#bindinuse';
|
export const PORT_53_FAQ_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ#bindinuse';
|
||||||
export const UPSTREAM_CONFIGURATION_WIKI_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams';
|
export const UPSTREAM_CONFIGURATION_WIKI_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams';
|
||||||
export const FILTERS_LINK = '#filters';
|
|
||||||
|
|
||||||
export const GETTING_STARTED_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update';
|
export const GETTING_STARTED_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update';
|
||||||
|
|
||||||
|
export const FILTERS_RELATIVE_LINK = '#filters';
|
||||||
|
|
||||||
export const ADDRESS_IN_USE_TEXT = 'address already in use';
|
export const ADDRESS_IN_USE_TEXT = 'address already in use';
|
||||||
|
|
||||||
export const INSTALL_FIRST_STEP = 1;
|
export const INSTALL_FIRST_STEP = 1;
|
||||||
|
|
|
@ -687,7 +687,7 @@ export const sortIp = (a, b) => {
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.warn(e);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -64,7 +64,7 @@ export const validateClientId = (value) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const formattedValue = value ? value.trim() : value;
|
const formattedValue = value.trim();
|
||||||
if (formattedValue && !(
|
if (formattedValue && !(
|
||||||
R_IPV4.test(formattedValue)
|
R_IPV4.test(formattedValue)
|
||||||
|| R_IPV6.test(formattedValue)
|
|| R_IPV6.test(formattedValue)
|
||||||
|
|
|
@ -12,7 +12,7 @@ const renderItem = ({
|
||||||
|
|
||||||
return <li key={ip}>{isDns
|
return <li key={ip}>{isDns
|
||||||
? <strong>{dnsAddress}</strong>
|
? <strong>{dnsAddress}</strong>
|
||||||
: <a href={webAddress}>{webAddress}</a>
|
: <a href={webAddress} target="_blank" rel="noopener noreferrer">{webAddress}</a>
|
||||||
}
|
}
|
||||||
</li>;
|
</li>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,8 @@ const encryption = handleActions({
|
||||||
const newState = {
|
const newState = {
|
||||||
...state,
|
...state,
|
||||||
...payload,
|
...payload,
|
||||||
|
/* TODO: handle property delete on api refactor */
|
||||||
|
server_name: payload.server_name || '',
|
||||||
processing: false,
|
processing: false,
|
||||||
};
|
};
|
||||||
return newState;
|
return newState;
|
||||||
|
@ -20,6 +22,7 @@ const encryption = handleActions({
|
||||||
const newState = {
|
const newState = {
|
||||||
...state,
|
...state,
|
||||||
...payload,
|
...payload,
|
||||||
|
server_name: payload.server_name || '',
|
||||||
processingConfig: false,
|
processingConfig: false,
|
||||||
};
|
};
|
||||||
return newState;
|
return newState;
|
||||||
|
@ -49,6 +52,7 @@ const encryption = handleActions({
|
||||||
subject,
|
subject,
|
||||||
warning_validation,
|
warning_validation,
|
||||||
dns_names,
|
dns_names,
|
||||||
|
server_name: payload.server_name || '',
|
||||||
processingValidate: false,
|
processingValidate: false,
|
||||||
};
|
};
|
||||||
return newState;
|
return newState;
|
||||||
|
|
37
go.mod
37
go.mod
|
@ -3,35 +3,36 @@ module github.com/AdguardTeam/AdGuardHome
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdguardTeam/dnsproxy v0.33.2
|
github.com/AdguardTeam/dnsproxy v0.33.7
|
||||||
github.com/AdguardTeam/golibs v0.4.3
|
github.com/AdguardTeam/golibs v0.4.4
|
||||||
github.com/AdguardTeam/urlfilter v0.12.3
|
github.com/AdguardTeam/urlfilter v0.14.0
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect
|
github.com/ameshkov/dnscrypt/v2 v2.0.1
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
|
github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663
|
||||||
|
github.com/gobuffalo/envy v1.9.0 // indirect
|
||||||
github.com/gobuffalo/packr v1.30.1
|
github.com/gobuffalo/packr v1.30.1
|
||||||
|
github.com/gobuffalo/packr/v2 v2.8.1 // indirect
|
||||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714
|
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8
|
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8
|
||||||
github.com/joomcode/errorx v1.0.3 // indirect
|
github.com/kardianos/service v1.2.0
|
||||||
github.com/kardianos/service v1.1.0
|
github.com/karrick/godirwalk v1.16.1 // indirect
|
||||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7
|
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7
|
||||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065
|
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065
|
||||||
github.com/miekg/dns v1.1.35
|
github.com/miekg/dns v1.1.35
|
||||||
github.com/rogpeppe/go-internal v1.5.2 // indirect
|
github.com/rogpeppe/go-internal v1.6.2 // indirect
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/sirupsen/logrus v1.6.0 // indirect
|
github.com/sirupsen/logrus v1.7.0 // indirect
|
||||||
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c
|
github.com/spf13/cobra v1.1.1 // indirect
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/u-root/u-root v6.0.0+incompatible
|
github.com/u-root/u-root v7.0.0+incompatible
|
||||||
go.etcd.io/bbolt v1.3.4
|
go.etcd.io/bbolt v1.3.5
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
|
golang.org/x/net v0.0.0-20201216054612-986b41b23924
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
|
||||||
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c
|
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e
|
||||||
golang.org/x/text v0.3.4 // indirect
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
gopkg.in/yaml.v2 v2.3.0
|
gopkg.in/yaml.v2 v2.3.0
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
|
howett.net/plist v0.0.0-20201026045517-117a925f2150
|
||||||
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5
|
|
||||||
)
|
)
|
||||||
|
|
328
go.sum
328
go.sum
|
@ -2,62 +2,92 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
github.com/AdguardTeam/dnsproxy v0.33.2 h1:k5aMcsw3TA/G2DR8EjIkwutDPuuRkKh8xij4cFWC6Fk=
|
github.com/AdguardTeam/dnsproxy v0.33.7 h1:DXsLTJoBSUejB2ZqVHyMG0/kXD8PzuVPbLCsGKBdaDc=
|
||||||
github.com/AdguardTeam/dnsproxy v0.33.2/go.mod h1:kLi6lMpErnZThy5haiRSis4q0KTB8uPWO4JQsU1EDJA=
|
github.com/AdguardTeam/dnsproxy v0.33.7/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs=
|
||||||
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||||
github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o=
|
github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o=
|
||||||
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||||
github.com/AdguardTeam/golibs v0.4.3 h1:nXTLLLlIyU4BSRF0An5azS0uimSK/YpIMOBAO0/v1RY=
|
github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw=
|
||||||
github.com/AdguardTeam/golibs v0.4.3/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||||
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
|
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
|
||||||
github.com/AdguardTeam/urlfilter v0.12.3 h1:FMjQG0eTgrr8xA3z2zaLVcCgGdpzoECPGWwgPjtwPNs=
|
github.com/AdguardTeam/urlfilter v0.14.0 h1:+aAhOvZDVGzl5gTERB4pOJCL1zxMyw7vLecJJ6TQTCw=
|
||||||
github.com/AdguardTeam/urlfilter v0.12.3/go.mod h1:1fcCQx5TGJANrQN6sHNNM9KPBl7qx7BJml45ko6vru0=
|
github.com/AdguardTeam/urlfilter v0.14.0/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.0.0 h1:i83G8MeGLrAFgUL8GSu98TVhtFDEifF7SIS7Qi/RZ3U=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.0.0/go.mod h1:nbZnxJt4edIPx2Haa8n2XtC2g5AWcsdQiSuXkNH8eDI=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/ameshkov/dnscrypt/v2 v2.0.1 h1:igNVNM6NLBOqYUzHXaDUxn8i+wJXOsosY0/xEBirixA=
|
||||||
|
github.com/ameshkov/dnscrypt/v2 v2.0.1/go.mod h1:nbZnxJt4edIPx2Haa8n2XtC2g5AWcsdQiSuXkNH8eDI=
|
||||||
github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug=
|
github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug=
|
||||||
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 h1:KXlsf+qt/X5ttPGEjR0tPH1xaWWoKBEg9Q1THAj2h3I=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 h1:M57m0xQqZIhx7CEJgeLSvRFKEK1RjzRuIXiA3HfYU7g=
|
github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 h1:M57m0xQqZIhx7CEJgeLSvRFKEK1RjzRuIXiA3HfYU7g=
|
||||||
github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
|
@ -67,27 +97,45 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||||
|
github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 h1:jI2GiiRh+pPbey52EVmbU6kuLiXqwy4CXZ4gwUBj8Y0=
|
||||||
|
github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-test/deep v1.0.5 h1:AKODKU3pDH1RzZzm6YZu77YWtEAq6uh1rLIAQlay2qc=
|
github.com/go-test/deep v1.0.5 h1:AKODKU3pDH1RzZzm6YZu77YWtEAq6uh1rLIAQlay2qc=
|
||||||
github.com/go-test/deep v1.0.5/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
github.com/go-test/deep v1.0.5/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||||
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
||||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||||
|
github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE=
|
||||||
|
github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
|
||||||
github.com/gobuffalo/logger v1.0.0 h1:xw9Ko9EcC5iAFprrjJ6oZco9UpzS5MQ4jAwghsLHdy4=
|
github.com/gobuffalo/logger v1.0.0 h1:xw9Ko9EcC5iAFprrjJ6oZco9UpzS5MQ4jAwghsLHdy4=
|
||||||
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||||
|
github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc=
|
||||||
|
github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM=
|
||||||
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
|
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
|
||||||
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||||
|
github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
|
||||||
|
github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
|
||||||
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
|
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
|
||||||
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
|
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
|
||||||
github.com/gobuffalo/packr/v2 v2.5.1 h1:TFOeY2VoGamPjQLiNDT3mn//ytzk236VMO2j7iHxJR4=
|
github.com/gobuffalo/packr/v2 v2.5.1 h1:TFOeY2VoGamPjQLiNDT3mn//ytzk236VMO2j7iHxJR4=
|
||||||
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
|
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
|
||||||
|
github.com/gobuffalo/packr/v2 v2.8.1 h1:tkQpju6i3EtMXJ9uoF5GT6kB+LMTimDWD8Xvbz6zDVA=
|
||||||
|
github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
|
@ -105,6 +153,7 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
@ -113,85 +162,144 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
|
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
|
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
|
||||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8 h1:u+vle+5E78+cT/CSMD5/Y3NUpMgA83Yu2KhG+Zbco/k=
|
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8 h1:R1oP0/QEyvaL7dm+mBQouQ9V1X6gqQr5taZA1yaq5zQ=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8/go.mod h1:CfMdguCK66I5DAUJgGKyNz8aB6vO5dZzkm9Xep6WGvw=
|
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI=
|
||||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/joomcode/errorx v1.0.1 h1:CalpDWz14ZHd68fIqluJasJosAewpz2TFaJALrUxjrk=
|
github.com/joomcode/errorx v1.0.1 h1:CalpDWz14ZHd68fIqluJasJosAewpz2TFaJALrUxjrk=
|
||||||
github.com/joomcode/errorx v1.0.1/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
|
github.com/joomcode/errorx v1.0.1/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
|
||||||
github.com/joomcode/errorx v1.0.3 h1:3e1mi0u7/HTPNdg6d6DYyKGBhA5l9XpsfuVE29NxnWw=
|
github.com/joomcode/errorx v1.0.3 h1:3e1mi0u7/HTPNdg6d6DYyKGBhA5l9XpsfuVE29NxnWw=
|
||||||
github.com/joomcode/errorx v1.0.3/go.mod h1:eQzdtdlNyN7etw6YCS4W4+lu442waxZYw5yvz0ULrRo=
|
github.com/joomcode/errorx v1.0.3/go.mod h1:eQzdtdlNyN7etw6YCS4W4+lu442waxZYw5yvz0ULrRo=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||||
|
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/kardianos/service v1.1.0 h1:QV2SiEeWK42P0aEmGcsAgjApw/lRxkwopvT+Gu6t1/0=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/kardianos/service v1.1.0/go.mod h1:RrJI2xn5vve/r32U5suTbeaSGoMU6GbNPoj36CVYcHc=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g=
|
||||||
|
github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||||
github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU=
|
github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU=
|
||||||
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||||
|
github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
|
||||||
|
github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw=
|
||||||
|
github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
|
||||||
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys=
|
github.com/lucas-clemente/quic-go v0.19.3 h1:eCDQqvGBB+kCTkA0XrAFtNe81FMa0/fn4QSoeAbmiF4=
|
||||||
github.com/lucas-clemente/quic-go v0.18.1/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg=
|
github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8=
|
||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/marten-seemann/qpack v0.2.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI=
|
||||||
|
github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
|
||||||
|
github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY=
|
||||||
|
github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
|
||||||
|
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
|
||||||
|
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||||
|
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||||
github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc=
|
github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc=
|
||||||
github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs=
|
github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs=
|
||||||
github.com/marten-seemann/qtls-go1-15 v0.1.0 h1:i/YPXVxz8q9umso/5y474CNcHmTpA+5DH+mFPjx6PZg=
|
|
||||||
github.com/marten-seemann/qtls-go1-15 v0.1.0/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
|
||||||
github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ=
|
github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ=
|
||||||
github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||||
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE=
|
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE=
|
||||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
||||||
|
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||||
|
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||||
|
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||||
|
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w=
|
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w=
|
||||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/miekg/dns v1.1.34/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
|
||||||
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
||||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
|
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
|
||||||
|
@ -200,27 +308,45 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
|
||||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w=
|
github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w=
|
||||||
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shirou/gopsutil v2.20.3+incompatible h1:0JVooMPsT7A7HqEYdydp/OfjSOYSjhXV7w1hkKj/NPQ=
|
github.com/shirou/gopsutil v2.20.3+incompatible h1:0JVooMPsT7A7HqEYdydp/OfjSOYSjhXV7w1hkKj/NPQ=
|
||||||
github.com/shirou/gopsutil v2.20.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
github.com/shirou/gopsutil v2.20.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
|
@ -244,24 +370,35 @@ github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b
|
||||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||||
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c h1:gqEdF4VwBu3lTKGHS9rXE9x1/pEaSwCXRLOZRF6qtlw=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0=
|
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||||
|
github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||||
|
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
|
||||||
|
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||||
|
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||||
|
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
@ -272,65 +409,114 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
github.com/u-root/u-root v6.0.0+incompatible h1:YqPGmRoRyYmeg17KIWFRSyVq6LX5T6GSzawyA6wG6EE=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/u-root/u-root v6.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
|
github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8=
|
||||||
|
github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
|
||||||
|
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||||
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||||
|
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604=
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA=
|
||||||
|
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA=
|
||||||
|
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY=
|
||||||
|
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -342,22 +528,34 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03i
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -367,9 +565,21 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
||||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c h1:+B+zPA6081G5cEb2triOIJpcvSW4AYzmIyWAqMn2JAc=
|
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
||||||
|
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201214095126-aec9a390925b h1:tv7/y4pd+sR8bcNb2D6o7BNU6zjWm0VjQLac+w7fNNM=
|
||||||
|
golang.org/x/sys v0.0.0-20201214095126-aec9a390925b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs=
|
||||||
|
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -381,17 +591,33 @@ golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
|
||||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -400,23 +626,38 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
@ -430,6 +671,7 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
@ -437,10 +679,13 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
@ -449,17 +694,18 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 h1:AQkaJpH+/FmqRjmXZPELom5zIERYZfwTjnHpfoVMQEc=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
howett.net/plist v0.0.0-20201026045517-117a925f2150 h1:s7O/9fwMNd6O1yXyQ8zv+U7dfl8k+zdiLWAY8h7XdVI=
|
||||||
|
howett.net/plist v0.0.0-20201026045517-117a925f2150/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||||
|
|
|
@ -65,3 +65,9 @@ func (e *manyError) Unwrap() error {
|
||||||
|
|
||||||
return e.underlying[0]
|
return e.underlying[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wrapper is a copy of the hidden errors.wrapper interface for tests, linting,
|
||||||
|
// etc.
|
||||||
|
type wrapper interface {
|
||||||
|
Unwrap() error
|
||||||
|
}
|
||||||
|
|
|
@ -41,6 +41,8 @@ func TestError_Error(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestError_Unwrap(t *testing.T) {
|
func TestError_Unwrap(t *testing.T) {
|
||||||
|
var _ wrapper = &manyError{}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errSimple = iota
|
errSimple = iota
|
||||||
errWrapped
|
errWrapped
|
||||||
|
@ -48,7 +50,7 @@ func TestError_Unwrap(t *testing.T) {
|
||||||
)
|
)
|
||||||
errs := []error{
|
errs := []error{
|
||||||
errSimple: errors.New("a"),
|
errSimple: errors.New("a"),
|
||||||
errWrapped: fmt.Errorf("%w", errors.New("nested")),
|
errWrapped: fmt.Errorf("err: %w", errors.New("nested")),
|
||||||
errNil: nil,
|
errNil: nil,
|
||||||
}
|
}
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Package aghio contains extensions for io package's types and methods
|
||||||
|
package aghio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LimitReachedError records the limit and the operation that caused it.
|
||||||
|
type LimitReachedError struct {
|
||||||
|
Limit int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements error interface for LimitReachedError.
|
||||||
|
// TODO(a.garipov): Think about error string format.
|
||||||
|
func (lre *LimitReachedError) Error() string {
|
||||||
|
return fmt.Sprintf("attempted to read more than %d bytes", lre.Limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// limitedReadCloser is a wrapper for io.ReadCloser with limited reader and
|
||||||
|
// dealing with agherr package.
|
||||||
|
type limitedReadCloser struct {
|
||||||
|
limit int64
|
||||||
|
n int64
|
||||||
|
rc io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements Reader interface.
|
||||||
|
func (lrc *limitedReadCloser) Read(p []byte) (n int, err error) {
|
||||||
|
if lrc.n == 0 {
|
||||||
|
return 0, &LimitReachedError{
|
||||||
|
Limit: lrc.limit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if int64(len(p)) > lrc.n {
|
||||||
|
p = p[0:lrc.n]
|
||||||
|
}
|
||||||
|
n, err = lrc.rc.Read(p)
|
||||||
|
lrc.n -= int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements Closer interface.
|
||||||
|
func (lrc *limitedReadCloser) Close() error {
|
||||||
|
return lrc.rc.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LimitReadCloser wraps ReadCloser to make it's Reader stop with
|
||||||
|
// ErrLimitReached after n bytes read.
|
||||||
|
func LimitReadCloser(rc io.ReadCloser, n int64) (limited io.ReadCloser, err error) {
|
||||||
|
if n < 0 {
|
||||||
|
return nil, fmt.Errorf("aghio: invalid n in LimitReadCloser: %d", n)
|
||||||
|
}
|
||||||
|
return &limitedReadCloser{
|
||||||
|
limit: n,
|
||||||
|
n: n,
|
||||||
|
rc: rc,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package aghio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLimitReadCloser(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
n int64
|
||||||
|
want error
|
||||||
|
}{{
|
||||||
|
name: "positive",
|
||||||
|
n: 1,
|
||||||
|
want: nil,
|
||||||
|
}, {
|
||||||
|
name: "zero",
|
||||||
|
n: 0,
|
||||||
|
want: nil,
|
||||||
|
}, {
|
||||||
|
name: "negative",
|
||||||
|
n: -1,
|
||||||
|
want: fmt.Errorf("aghio: invalid n in LimitReadCloser: -1"),
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, err := LimitReadCloser(nil, tc.n)
|
||||||
|
assert.Equal(t, tc.want, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLimitedReadCloser_Read(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
limit int64
|
||||||
|
rStr string
|
||||||
|
want int
|
||||||
|
err error
|
||||||
|
}{{
|
||||||
|
name: "perfectly_match",
|
||||||
|
limit: 3,
|
||||||
|
rStr: "abc",
|
||||||
|
want: 3,
|
||||||
|
err: nil,
|
||||||
|
}, {
|
||||||
|
name: "eof",
|
||||||
|
limit: 3,
|
||||||
|
rStr: "",
|
||||||
|
want: 0,
|
||||||
|
err: io.EOF,
|
||||||
|
}, {
|
||||||
|
name: "limit_reached",
|
||||||
|
limit: 0,
|
||||||
|
rStr: "abc",
|
||||||
|
want: 0,
|
||||||
|
err: &LimitReachedError{
|
||||||
|
Limit: 0,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "truncated",
|
||||||
|
limit: 2,
|
||||||
|
rStr: "abc",
|
||||||
|
want: 2,
|
||||||
|
err: nil,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
readCloser := ioutil.NopCloser(strings.NewReader(tc.rStr))
|
||||||
|
buf := make([]byte, tc.limit+1)
|
||||||
|
|
||||||
|
lreader, err := LimitReadCloser(readCloser, tc.limit)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
n, err := lreader.Read(buf)
|
||||||
|
assert.Equal(t, n, tc.want)
|
||||||
|
assert.Equal(t, tc.err, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLimitedReadCloser_LimitReachedError(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
want string
|
||||||
|
err error
|
||||||
|
}{{
|
||||||
|
name: "simplest",
|
||||||
|
want: "attempted to read more than 0 bytes",
|
||||||
|
err: &LimitReachedError{
|
||||||
|
Limit: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.want, tc.err.Error())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) {
|
||||||
return false, fmt.Errorf("couldn't find interface by name %s: %w", ifaceName, err)
|
return false, fmt.Errorf("couldn't find interface by name %s: %w", ifaceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ifaceIPNet, err := ifaceIPv4Addrs(iface)
|
ifaceIPNet, err := ifaceIPAddrs(iface, ipVersion4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("getting ipv4 addrs for iface %s: %w", ifaceName, err)
|
return false, fmt.Errorf("getting ipv4 addrs for iface %s: %w", ifaceName, err)
|
||||||
}
|
}
|
||||||
|
@ -94,12 +94,11 @@ func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) {
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ok {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ok, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +161,7 @@ func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) {
|
||||||
return false, fmt.Errorf("dhcpv6: net.InterfaceByName: %s: %w", ifaceName, err)
|
return false, fmt.Errorf("dhcpv6: net.InterfaceByName: %s: %w", ifaceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ifaceIPNet, err := ifaceIPv6Addrs(iface)
|
ifaceIPNet, err := ifaceIPAddrs(iface, ipVersion6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("getting ipv6 addrs for iface %s: %w", ifaceName, err)
|
return false, fmt.Errorf("getting ipv6 addrs for iface %s: %w", ifaceName, err)
|
||||||
}
|
}
|
||||||
|
@ -216,12 +215,11 @@ func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) {
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ok {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ok, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,6 @@ func (s *Server) dbLoad() {
|
||||||
} else {
|
} else {
|
||||||
v6DynLeases = append(v6DynLeases, &lease)
|
v6DynLeases = append(v6DynLeases, &lease)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if obj[i].Expiry == leaseExpireStatic {
|
if obj[i].Expiry == leaseExpireStatic {
|
||||||
staticLeases = append(staticLeases, &lease)
|
staticLeases = append(staticLeases, &lease)
|
||||||
|
|
|
@ -52,6 +52,7 @@ type ServerConfig struct {
|
||||||
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"`
|
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnLeaseChangedT is a callback for lease changes.
|
||||||
type OnLeaseChangedT func(flags int)
|
type OnLeaseChangedT func(flags int)
|
||||||
|
|
||||||
// flags for onLeaseChanged()
|
// flags for onLeaseChanged()
|
||||||
|
@ -74,16 +75,12 @@ type Server struct {
|
||||||
onLeaseChanged []OnLeaseChangedT
|
onLeaseChanged []OnLeaseChangedT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServerInterface is an interface for servers.
|
||||||
type ServerInterface interface {
|
type ServerInterface interface {
|
||||||
Leases(flags int) []Lease
|
Leases(flags int) []Lease
|
||||||
SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT)
|
SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckConfig checks the configuration
|
|
||||||
func (s *Server) CheckConfig(config ServerConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create - create object
|
// Create - create object
|
||||||
func Create(config ServerConfig) *Server {
|
func Create(config ServerConfig) *Server {
|
||||||
s := &Server{}
|
s := &Server{}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||||
"github.com/AdguardTeam/golibs/jsonutil"
|
"github.com/AdguardTeam/golibs/jsonutil"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
@ -205,9 +206,9 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
s.dbLoad()
|
s.dbLoad()
|
||||||
|
|
||||||
if s.conf.Enabled {
|
if s.conf.Enabled {
|
||||||
staticIP, err := HasStaticIP(newconfig.InterfaceName)
|
staticIP, err := sysutil.IfaceHasStaticIP(newconfig.InterfaceName)
|
||||||
if !staticIP && err == nil {
|
if !staticIP && err == nil {
|
||||||
err = SetStaticIP(newconfig.InterfaceName)
|
err = sysutil.IfaceSetStaticIP(newconfig.InterfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(r, w, http.StatusInternalServerError, "Failed to configure static IP: %s", err)
|
httpError(r, w, http.StatusInternalServerError, "Failed to configure static IP: %s", err)
|
||||||
return
|
return
|
||||||
|
@ -282,7 +283,7 @@ func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 {
|
if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 {
|
||||||
jsonIface.GatewayIP = getGatewayIP(iface.Name)
|
jsonIface.GatewayIP = sysutil.GatewayIP(iface.Name)
|
||||||
response[iface.Name] = jsonIface
|
response[iface.Name] = jsonIface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -299,6 +300,7 @@ func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||||
// . Check if a static IP is configured for the network interface
|
// . Check if a static IP is configured for the network interface
|
||||||
// Respond with results
|
// Respond with results
|
||||||
func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// This use of ReadAll is safe, because request's body is now limited.
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := fmt.Sprintf("failed to read request body: %s", err)
|
msg := fmt.Sprintf("failed to read request body: %s", err)
|
||||||
|
@ -318,7 +320,7 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
|
||||||
found4, err4 := CheckIfOtherDHCPServersPresentV4(interfaceName)
|
found4, err4 := CheckIfOtherDHCPServersPresentV4(interfaceName)
|
||||||
|
|
||||||
staticIP := map[string]interface{}{}
|
staticIP := map[string]interface{}{}
|
||||||
isStaticIP, err := HasStaticIP(interfaceName)
|
isStaticIP, err := sysutil.IfaceHasStaticIP(interfaceName)
|
||||||
staticIPStatus := "yes"
|
staticIPStatus := "yes"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
staticIPStatus = "error"
|
staticIPStatus = "error"
|
||||||
|
@ -508,6 +510,9 @@ func (s *Server) registerHandlers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// jsonError is a generic JSON error response.
|
// jsonError is a generic JSON error response.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Merge together with the implementations in .../home and
|
||||||
|
// other packages after refactoring the web handler registering.
|
||||||
type jsonError struct {
|
type jsonError struct {
|
||||||
// Message is the error message, an opaque string.
|
// Message is the error message, an opaque string.
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
|
|
@ -19,9 +19,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
@ -317,26 +315,6 @@ func WithTimeout(d time.Duration) ClientOpt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSummaryLogger logs one-line DHCPv4 message summaries when sent & received.
|
|
||||||
func WithSummaryLogger() ClientOpt {
|
|
||||||
return func(c *Client) (err error) {
|
|
||||||
c.logger = ShortSummaryLogger{
|
|
||||||
Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags),
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDebugLogger logs multi-line full DHCPv4 messages when sent & received.
|
|
||||||
func WithDebugLogger() ClientOpt {
|
|
||||||
return func(c *Client) (err error) {
|
|
||||||
c.logger = DebugLogger{
|
|
||||||
Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags),
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLogger set the logger (see interface Logger).
|
// WithLogger set the logger (see interface Logger).
|
||||||
func WithLogger(newLogger Logger) ClientOpt {
|
func WithLogger(newLogger Logger) ClientOpt {
|
||||||
return func(c *Client) (err error) {
|
return func(c *Client) (err error) {
|
||||||
|
|
|
@ -79,7 +79,7 @@ func serveAndClient(ctx context.Context, responses [][]*dhcpv4.DHCPv4, opts ...C
|
||||||
return mc, serverConn
|
return mc, serverConn
|
||||||
}
|
}
|
||||||
|
|
||||||
func ComparePacket(got *dhcpv4.DHCPv4, want *dhcpv4.DHCPv4) error {
|
func ComparePacket(got, want *dhcpv4.DHCPv4) error {
|
||||||
if got == nil && got == want {
|
if got == nil && got == want {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ func ComparePacket(got *dhcpv4.DHCPv4, want *dhcpv4.DHCPv4) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func pktsExpected(got []*dhcpv4.DHCPv4, want []*dhcpv4.DHCPv4) error {
|
func pktsExpected(got, want []*dhcpv4.DHCPv4) error {
|
||||||
if len(got) != len(want) {
|
if len(got) != len(want) {
|
||||||
return fmt.Errorf("got %d packets, want %d packets", len(got), len(want))
|
return fmt.Errorf("got %d packets, want %d packets", len(got), len(want))
|
||||||
}
|
}
|
||||||
|
@ -309,10 +309,10 @@ func TestMultipleSendAndRead(t *testing.T) {
|
||||||
newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x44, 0x44, 0x44, 0x44}),
|
newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x44, 0x44, 0x44, 0x44}),
|
||||||
},
|
},
|
||||||
server: [][]*dhcpv4.DHCPv4{
|
server: [][]*dhcpv4.DHCPv4{
|
||||||
[]*dhcpv4.DHCPv4{ // Response for first packet.
|
{ // Response for first packet.
|
||||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}),
|
||||||
},
|
},
|
||||||
[]*dhcpv4.DHCPv4{ // Response for second packet.
|
{ // Response for second packet.
|
||||||
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x44, 0x44, 0x44, 0x44}),
|
newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x44, 0x44, 0x44, 0x44}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,17 +17,13 @@ import (
|
||||||
"github.com/u-root/u-root/pkg/uio"
|
"github.com/u-root/u-root/pkg/uio"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// BroadcastMac is the broadcast MAC address.
|
||||||
// BroadcastMac is the broadcast MAC address.
|
//
|
||||||
//
|
// Any UDP packet sent to this address is broadcast on the subnet.
|
||||||
// Any UDP packet sent to this address is broadcast on the subnet.
|
var BroadcastMac = net.HardwareAddr([]byte{255, 255, 255, 255, 255, 255})
|
||||||
BroadcastMac = net.HardwareAddr([]byte{255, 255, 255, 255, 255, 255})
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
// ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr".
|
||||||
// ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr".
|
var ErrUDPAddrIsRequired = errors.New("must supply UDPAddr")
|
||||||
ErrUDPAddrIsRequired = errors.New("must supply UDPAddr")
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewRawUDPConn returns a UDP connection bound to the interface and port
|
// NewRawUDPConn returns a UDP connection bound to the interface and port
|
||||||
// given based on a raw packet socket. All packets are broadcasted.
|
// given based on a raw packet socket. All packets are broadcasted.
|
||||||
|
@ -68,7 +64,7 @@ func NewBroadcastUDPConn(rawPacketConn net.PacketConn, boundAddr *net.UDPAddr) n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func udpMatch(addr *net.UDPAddr, bound *net.UDPAddr) bool {
|
func udpMatch(addr, bound *net.UDPAddr) bool {
|
||||||
if bound == nil {
|
if bound == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,7 +281,7 @@ func (b UDP) Checksum() uint16 {
|
||||||
// CalculateChecksum calculates the checksum of the udp packet, given the total
|
// CalculateChecksum calculates the checksum of the udp packet, given the total
|
||||||
// length of the packet and the checksum of the network-layer pseudo-header
|
// length of the packet and the checksum of the network-layer pseudo-header
|
||||||
// (excluding the total length) and the checksum of the payload.
|
// (excluding the total length) and the checksum of the payload.
|
||||||
func (b UDP) CalculateChecksum(partialChecksum uint16, totalLen uint16) uint16 {
|
func (b UDP) CalculateChecksum(partialChecksum, totalLen uint16) uint16 {
|
||||||
// Add the length portion of the checksum to the pseudo-checksum.
|
// Add the length portion of the checksum to the pseudo-checksum.
|
||||||
tmp := make([]byte, 2)
|
tmp := make([]byte, 2)
|
||||||
binary.BigEndian.PutUint16(tmp, totalLen)
|
binary.BigEndian.PutUint16(tmp, totalLen)
|
||||||
|
@ -336,13 +336,13 @@ func ChecksumCombine(a, b uint16) uint16 {
|
||||||
// given destination protocol and network address, ignoring the length
|
// given destination protocol and network address, ignoring the length
|
||||||
// field. Pseudo-headers are needed by transport layers when calculating
|
// field. Pseudo-headers are needed by transport layers when calculating
|
||||||
// their own checksum.
|
// their own checksum.
|
||||||
func PseudoHeaderChecksum(protocol TransportProtocolNumber, srcAddr net.IP, dstAddr net.IP) uint16 {
|
func PseudoHeaderChecksum(protocol TransportProtocolNumber, srcAddr, dstAddr net.IP) uint16 {
|
||||||
xsum := Checksum([]byte(srcAddr), 0)
|
xsum := Checksum([]byte(srcAddr), 0)
|
||||||
xsum = Checksum([]byte(dstAddr), xsum)
|
xsum = Checksum([]byte(dstAddr), xsum)
|
||||||
return Checksum([]byte{0, uint8(protocol)}, xsum)
|
return Checksum([]byte{0, uint8(protocol)}, xsum)
|
||||||
}
|
}
|
||||||
|
|
||||||
func udp4pkt(packet []byte, dest *net.UDPAddr, src *net.UDPAddr) []byte {
|
func udp4pkt(packet []byte, dest, src *net.UDPAddr) []byte {
|
||||||
ipLen := IPv4MinimumSize
|
ipLen := IPv4MinimumSize
|
||||||
udpLen := UDPMinimumSize
|
udpLen := UDPMinimumSize
|
||||||
|
|
||||||
|
|
|
@ -1,312 +0,0 @@
|
||||||
package dhcpd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/file"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HasStaticIP check if the network interface has a static IP configured
|
|
||||||
//
|
|
||||||
// Supports: Raspbian.
|
|
||||||
func HasStaticIP(ifaceName string) (bool, error) {
|
|
||||||
if runtime.GOOS == "linux" {
|
|
||||||
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasStaticIPDhcpcdConf(string(body), ifaceName), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
return hasStaticIPDarwin(ifaceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, fmt.Errorf("cannot check if IP is static: not supported on %s", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStaticIP sets a static IP for the network interface.
|
|
||||||
func SetStaticIP(ifaceName string) error {
|
|
||||||
if runtime.GOOS == "linux" {
|
|
||||||
return setStaticIPDhcpdConf(ifaceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
return setStaticIPDarwin(ifaceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("cannot set static IP on %s", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for dhcpcd.conf
|
|
||||||
func hasStaticIPDhcpcdConf(dhcpConf, ifaceName string) bool {
|
|
||||||
lines := strings.Split(dhcpConf, "\n")
|
|
||||||
nameLine := fmt.Sprintf("interface %s", ifaceName)
|
|
||||||
withinInterfaceCtx := false
|
|
||||||
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
|
|
||||||
if withinInterfaceCtx && len(line) == 0 {
|
|
||||||
// an empty line resets our state
|
|
||||||
withinInterfaceCtx = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(line) == 0 || line[0] == '#' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
|
|
||||||
if !withinInterfaceCtx {
|
|
||||||
if line == nameLine {
|
|
||||||
// we found our interface
|
|
||||||
withinInterfaceCtx = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if strings.HasPrefix(line, "interface ") {
|
|
||||||
// we found another interface - reset our state
|
|
||||||
withinInterfaceCtx = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(line, "static ip_address=") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get gateway IP address
|
|
||||||
func getGatewayIP(ifaceName string) string {
|
|
||||||
cmd := exec.Command("ip", "route", "show", "dev", ifaceName)
|
|
||||||
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
|
||||||
d, err := cmd.Output()
|
|
||||||
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Fields(string(d))
|
|
||||||
if len(fields) < 3 || fields[0] != "default" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(fields[2])
|
|
||||||
if ip == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return fields[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
// setStaticIPDhcpdConf - updates /etc/dhcpd.conf and sets the current IP address to be static
|
|
||||||
func setStaticIPDhcpdConf(ifaceName string) error {
|
|
||||||
ip := util.GetSubnet(ifaceName)
|
|
||||||
if len(ip) == 0 {
|
|
||||||
return errors.New("can't get IP address")
|
|
||||||
}
|
|
||||||
|
|
||||||
ip4, _, err := net.ParseCIDR(ip)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gatewayIP := getGatewayIP(ifaceName)
|
|
||||||
add := updateStaticIPDhcpcdConf(ifaceName, ip, gatewayIP, ip4.String())
|
|
||||||
|
|
||||||
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
body = append(body, []byte(add)...)
|
|
||||||
err = file.SafeWrite("/etc/dhcpcd.conf", body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// updates dhcpd.conf content -- sets static IP address there
|
|
||||||
// for dhcpcd.conf
|
|
||||||
func updateStaticIPDhcpcdConf(ifaceName, ip, gatewayIP, dnsIP string) string {
|
|
||||||
var body []byte
|
|
||||||
|
|
||||||
add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n",
|
|
||||||
ifaceName, ip)
|
|
||||||
body = append(body, []byte(add)...)
|
|
||||||
|
|
||||||
if len(gatewayIP) != 0 {
|
|
||||||
add = fmt.Sprintf("static routers=%s\n",
|
|
||||||
gatewayIP)
|
|
||||||
body = append(body, []byte(add)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
add = fmt.Sprintf("static domain_name_servers=%s\n\n",
|
|
||||||
dnsIP)
|
|
||||||
body = append(body, []byte(add)...)
|
|
||||||
|
|
||||||
return string(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if network interface has a static IP configured
|
|
||||||
// Supports: MacOS.
|
|
||||||
func hasStaticIPDarwin(ifaceName string) (bool, error) {
|
|
||||||
portInfo, err := getCurrentHardwarePortInfo(ifaceName)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return portInfo.static, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// setStaticIPDarwin - uses networksetup util to set the current IP address to be static
|
|
||||||
// Additionally it configures the current DNS servers as well
|
|
||||||
func setStaticIPDarwin(ifaceName string) error {
|
|
||||||
portInfo, err := getCurrentHardwarePortInfo(ifaceName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if portInfo.static {
|
|
||||||
return errors.New("IP address is already static")
|
|
||||||
}
|
|
||||||
|
|
||||||
dnsAddrs, err := getEtcResolvConfServers()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
args := make([]string, 0)
|
|
||||||
args = append(args, "-setdnsservers", portInfo.name)
|
|
||||||
args = append(args, dnsAddrs...)
|
|
||||||
|
|
||||||
// Setting DNS servers is necessary when configuring a static IP
|
|
||||||
code, _, err := util.RunCommand("networksetup", args...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if code != 0 {
|
|
||||||
return fmt.Errorf("failed to set DNS servers, code=%d", code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actually configures hardware port to have static IP
|
|
||||||
code, _, err = util.RunCommand("networksetup", "-setmanual",
|
|
||||||
portInfo.name, portInfo.ip, portInfo.subnet, portInfo.gatewayIP)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if code != 0 {
|
|
||||||
return fmt.Errorf("failed to set DNS servers, code=%d", code)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCurrentHardwarePortInfo gets information the specified network interface
|
|
||||||
func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) {
|
|
||||||
// First of all we should find hardware port name
|
|
||||||
m := getNetworkSetupHardwareReports()
|
|
||||||
hardwarePort, ok := m[ifaceName]
|
|
||||||
if !ok {
|
|
||||||
return hardwarePortInfo{}, fmt.Errorf("could not find hardware port for %s", ifaceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return getHardwarePortInfo(hardwarePort)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNetworkSetupHardwareReports parses the output of the `networksetup -listallhardwareports` command
|
|
||||||
// it returns a map where the key is the interface name, and the value is the "hardware port"
|
|
||||||
// returns nil if it fails to parse the output
|
|
||||||
func getNetworkSetupHardwareReports() map[string]string {
|
|
||||||
_, out, err := util.RunCommand("networksetup", "-listallhardwareports")
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
re, err := regexp.Compile("Hardware Port: (.*?)\nDevice: (.*?)\n")
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
m := make(map[string]string)
|
|
||||||
|
|
||||||
matches := re.FindAllStringSubmatch(out, -1)
|
|
||||||
for i := range matches {
|
|
||||||
port := matches[i][1]
|
|
||||||
device := matches[i][2]
|
|
||||||
m[device] = port
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// hardwarePortInfo - information obtained using MacOS networksetup
|
|
||||||
// about the current state of the internet connection
|
|
||||||
type hardwarePortInfo struct {
|
|
||||||
name string
|
|
||||||
ip string
|
|
||||||
subnet string
|
|
||||||
gatewayIP string
|
|
||||||
static bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHardwarePortInfo(hardwarePort string) (hardwarePortInfo, error) {
|
|
||||||
h := hardwarePortInfo{}
|
|
||||||
|
|
||||||
_, out, err := util.RunCommand("networksetup", "-getinfo", hardwarePort)
|
|
||||||
if err != nil {
|
|
||||||
return h, err
|
|
||||||
}
|
|
||||||
|
|
||||||
re := regexp.MustCompile("IP address: (.*?)\nSubnet mask: (.*?)\nRouter: (.*?)\n")
|
|
||||||
|
|
||||||
match := re.FindStringSubmatch(out)
|
|
||||||
if len(match) == 0 {
|
|
||||||
return h, errors.New("could not find hardware port info")
|
|
||||||
}
|
|
||||||
|
|
||||||
h.name = hardwarePort
|
|
||||||
h.ip = match[1]
|
|
||||||
h.subnet = match[2]
|
|
||||||
h.gatewayIP = match[3]
|
|
||||||
|
|
||||||
if strings.Index(out, "Manual Configuration") == 0 {
|
|
||||||
h.static = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets a list of nameservers currently configured in the /etc/resolv.conf
|
|
||||||
func getEtcResolvConfServers() ([]string, error) {
|
|
||||||
body, err := ioutil.ReadFile("/etc/resolv.conf")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
re := regexp.MustCompile("nameserver ([a-zA-Z0-9.:]+)")
|
|
||||||
|
|
||||||
matches := re.FindAllStringSubmatch(string(body), -1)
|
|
||||||
if len(matches) == 0 {
|
|
||||||
return nil, errors.New("found no DNS servers in /etc/resolv.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs := make([]string, 0)
|
|
||||||
for i := range matches {
|
|
||||||
addrs = append(addrs, matches[i][1])
|
|
||||||
}
|
|
||||||
|
|
||||||
return addrs, nil
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
|
||||||
|
|
||||||
package dhcpd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHasStaticIPDhcpcdConf(t *testing.T) {
|
|
||||||
dhcpdConf := `#comment
|
|
||||||
# comment
|
|
||||||
|
|
||||||
interface eth0
|
|
||||||
static ip_address=192.168.0.1/24
|
|
||||||
|
|
||||||
# interface wlan0
|
|
||||||
static ip_address=192.168.1.1/24
|
|
||||||
|
|
||||||
# comment
|
|
||||||
`
|
|
||||||
assert.True(t, !hasStaticIPDhcpcdConf(dhcpdConf, "wlan0"))
|
|
||||||
|
|
||||||
dhcpdConf = `#comment
|
|
||||||
# comment
|
|
||||||
|
|
||||||
interface eth0
|
|
||||||
static ip_address=192.168.0.1/24
|
|
||||||
|
|
||||||
# interface wlan0
|
|
||||||
static ip_address=192.168.1.1/24
|
|
||||||
|
|
||||||
# comment
|
|
||||||
|
|
||||||
interface wlan0
|
|
||||||
# comment
|
|
||||||
static ip_address=192.168.2.1/24
|
|
||||||
`
|
|
||||||
assert.True(t, hasStaticIPDhcpcdConf(dhcpdConf, "wlan0"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetStaticIPDhcpcdConf(t *testing.T) {
|
|
||||||
dhcpcdConf := `
|
|
||||||
interface wlan0
|
|
||||||
static ip_address=192.168.0.2/24
|
|
||||||
static routers=192.168.0.1
|
|
||||||
static domain_name_servers=192.168.0.2
|
|
||||||
|
|
||||||
`
|
|
||||||
s := updateStaticIPDhcpcdConf("wlan0", "192.168.0.2/24", "192.168.0.1", "192.168.0.2")
|
|
||||||
assert.Equal(t, dhcpcdConf, s)
|
|
||||||
|
|
||||||
// without gateway
|
|
||||||
dhcpcdConf = `
|
|
||||||
interface wlan0
|
|
||||||
static ip_address=192.168.0.2/24
|
|
||||||
static domain_name_servers=192.168.0.2
|
|
||||||
|
|
||||||
`
|
|
||||||
s = updateStaticIPDhcpcdConf("wlan0", "192.168.0.2/24", "", "192.168.0.2")
|
|
||||||
assert.Equal(t, dhcpcdConf, s)
|
|
||||||
}
|
|
|
@ -180,15 +180,18 @@ func (ra *raCtx) Init() error {
|
||||||
data := createICMPv6RAPacket(params)
|
data := createICMPv6RAPacket(params)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
success := false
|
||||||
ipAndScope := ra.ipAddr.String() + "%" + ra.ifaceName
|
ipAndScope := ra.ipAddr.String() + "%" + ra.ifaceName
|
||||||
ra.conn, err = icmp.ListenPacket("ip6:ipv6-icmp", ipAndScope)
|
ra.conn, err = icmp.ListenPacket("ip6:ipv6-icmp", ipAndScope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("dhcpv6 ra: icmp.ListenPacket: %w", err)
|
return fmt.Errorf("dhcpv6 ra: icmp.ListenPacket: %w", err)
|
||||||
}
|
}
|
||||||
success := false
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if !success {
|
if !success {
|
||||||
ra.Close()
|
cerr := ra.Close()
|
||||||
|
if cerr != nil {
|
||||||
|
log.Error("closing context: %s", cerr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -227,13 +230,15 @@ func (ra *raCtx) Init() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close - close module
|
// Close closes the module.
|
||||||
func (ra *raCtx) Close() {
|
func (ra *raCtx) Close() (err error) {
|
||||||
log.Debug("dhcpv6 ra: closing")
|
log.Debug("dhcpv6 ra: closing")
|
||||||
|
|
||||||
ra.stop.Store(1)
|
ra.stop.Store(1)
|
||||||
|
|
||||||
if ra.conn != nil {
|
if ra.conn != nil {
|
||||||
ra.conn.Close()
|
return ra.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
|
@ -11,12 +11,14 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/go-ping/ping"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||||
"github.com/sparrc/go-ping"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// v4Server - DHCPv4 server
|
// v4Server is a DHCPv4 server.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Think about unifying this and v6Server.
|
||||||
type v4Server struct {
|
type v4Server struct {
|
||||||
srv *server4.Server
|
srv *server4.Server
|
||||||
leasesLock sync.Mutex
|
leasesLock sync.Mutex
|
||||||
|
@ -244,6 +246,7 @@ func (s *v4Server) addrAvailable(target net.IP) bool {
|
||||||
pinger, err := ping.NewPinger(target.String())
|
pinger, err := ping.NewPinger(target.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("ping.NewPinger(): %v", err)
|
log.Error("ping.NewPinger(): %v", err)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,7 +258,12 @@ func (s *v4Server) addrAvailable(target net.IP) bool {
|
||||||
reply = true
|
reply = true
|
||||||
}
|
}
|
||||||
log.Debug("dhcpv4: Sending ICMP Echo to %v", target)
|
log.Debug("dhcpv4: Sending ICMP Echo to %v", target)
|
||||||
pinger.Run()
|
|
||||||
|
err = pinger.Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("pinger.Run(): %v", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if reply {
|
if reply {
|
||||||
log.Info("dhcpv4: IP conflict: %v is already used by another device", target)
|
log.Info("dhcpv4: IP conflict: %v is already used by another device", target)
|
||||||
|
@ -554,27 +562,6 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ifaceIPv4Addrs returns the interface's IPv4 addresses.
|
|
||||||
func ifaceIPv4Addrs(iface *net.Interface) (ips []net.IP, err error) {
|
|
||||||
addrs, err := iface.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, a := range addrs {
|
|
||||||
ipnet, ok := a.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip := ipnet.IP.To4(); ip != nil {
|
|
||||||
ips = append(ips, ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the IPv4 DHCP server.
|
// Start starts the IPv4 DHCP server.
|
||||||
func (s *v4Server) Start() error {
|
func (s *v4Server) Start() error {
|
||||||
if !s.conf.Enabled {
|
if !s.conf.Enabled {
|
||||||
|
@ -589,26 +576,14 @@ func (s *v4Server) Start() error {
|
||||||
|
|
||||||
log.Debug("dhcpv4: starting...")
|
log.Debug("dhcpv4: starting...")
|
||||||
|
|
||||||
dnsIPAddrs, err := ifaceIPv4Addrs(iface)
|
dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion4, defaultMaxAttempts, defaultBackoff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("dhcpv4: getting ipv4 addrs for iface %s: %w", ifaceName, err)
|
return fmt.Errorf("dhcpv4: interface %s: %w", ifaceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch len(dnsIPAddrs) {
|
if len(dnsIPAddrs) == 0 {
|
||||||
case 0:
|
// No available IP addresses which may appear later.
|
||||||
log.Debug("dhcpv4: no ipv4 address for interface %s", iface.Name)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
case 1:
|
|
||||||
// Some Android devices use 8.8.8.8 if there is no secondary DNS
|
|
||||||
// server. Fix that by setting the secondary DNS address to our
|
|
||||||
// address as well.
|
|
||||||
//
|
|
||||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/1708.
|
|
||||||
log.Debug("dhcpv4: setting secondary dns ip to iself for interface %s", iface.Name)
|
|
||||||
dnsIPAddrs = append(dnsIPAddrs, dnsIPAddrs[0])
|
|
||||||
default:
|
|
||||||
// Go on.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.conf.dnsIPAddrs = dnsIPAddrs
|
s.conf.dnsIPAddrs = dnsIPAddrs
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ipVersion is a documentational alias for int. Use it when the integer means
|
||||||
|
// IP version.
|
||||||
|
type ipVersion = int
|
||||||
|
|
||||||
|
// IP version constants.
|
||||||
|
const (
|
||||||
|
ipVersion4 ipVersion = 4
|
||||||
|
ipVersion6 ipVersion = 6
|
||||||
|
)
|
||||||
|
|
||||||
|
// netIface is the interface for network interface methods.
|
||||||
|
type netIface interface {
|
||||||
|
Addrs() ([]net.Addr, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ifaceIPAddrs returns the interface's IP addresses.
|
||||||
|
func ifaceIPAddrs(iface netIface, ipv ipVersion) (ips []net.IP, err error) {
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range addrs {
|
||||||
|
var ip net.IP
|
||||||
|
switch a := a.(type) {
|
||||||
|
case *net.IPAddr:
|
||||||
|
ip = a.IP
|
||||||
|
case *net.IPNet:
|
||||||
|
ip = a.IP
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume that net.(*Interface).Addrs can only return valid IPv4
|
||||||
|
// and IPv6 addresses. Thus, if it isn't an IPv4 address, it
|
||||||
|
// must be an IPv6 one.
|
||||||
|
switch ipv {
|
||||||
|
case ipVersion4:
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
ips = append(ips, ip4)
|
||||||
|
}
|
||||||
|
case ipVersion6:
|
||||||
|
if ip6 := ip.To4(); ip6 == nil {
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid ip version %d", ipv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently used defaults for ifaceDNSAddrs.
|
||||||
|
const (
|
||||||
|
defaultMaxAttempts int = 10
|
||||||
|
|
||||||
|
defaultBackoff time.Duration = 500 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
// ifaceDNSIPAddrs returns IP addresses of the interface suitable to send to
|
||||||
|
// clients as DNS addresses. If err is nil, addrs contains either no addresses
|
||||||
|
// or at least two.
|
||||||
|
//
|
||||||
|
// It makes up to maxAttempts attempts to get the addresses if there are none,
|
||||||
|
// each time using the provided backoff. Sometimes an interface needs a few
|
||||||
|
// seconds to really ititialize.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/2304.
|
||||||
|
func ifaceDNSIPAddrs(
|
||||||
|
iface netIface,
|
||||||
|
ipv ipVersion,
|
||||||
|
maxAttempts int,
|
||||||
|
backoff time.Duration,
|
||||||
|
) (addrs []net.IP, err error) {
|
||||||
|
var n int
|
||||||
|
waitForIP:
|
||||||
|
for n = 1; n <= maxAttempts; n++ {
|
||||||
|
addrs, err = ifaceIPAddrs(iface, ipv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting ip addrs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(addrs) {
|
||||||
|
case 0:
|
||||||
|
log.Debug("dhcpv%d: attempt %d: no ip addresses", ipv, n)
|
||||||
|
|
||||||
|
time.Sleep(backoff)
|
||||||
|
case 1:
|
||||||
|
// Some Android devices use 8.8.8.8 if there is not
|
||||||
|
// a secondary DNS server. Fix that by setting the
|
||||||
|
// secondary DNS address to the same address.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/1708.
|
||||||
|
log.Debug("dhcpv%d: setting secondary dns ip to itself", ipv)
|
||||||
|
addrs = append(addrs, addrs[0])
|
||||||
|
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
break waitForIP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
// Don't return errors in case the users want to try and enable
|
||||||
|
// the DHCP server later.
|
||||||
|
log.Error("dhcpv%d: no ip address for interface after %d attempts and %s", ipv, n, time.Duration(n)*backoff)
|
||||||
|
} else {
|
||||||
|
log.Debug("dhcpv%d: got addresses %s after %d attempts", ipv, addrs, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs, nil
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeIface struct {
|
||||||
|
addrs []net.Addr
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addrs implements the netIface interface for *fakeIface.
|
||||||
|
func (iface *fakeIface) Addrs() (addrs []net.Addr, err error) {
|
||||||
|
if iface.err != nil {
|
||||||
|
return nil, iface.err
|
||||||
|
}
|
||||||
|
|
||||||
|
return iface.addrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIfaceIPAddrs(t *testing.T) {
|
||||||
|
const errTest agherr.Error = "test error"
|
||||||
|
|
||||||
|
ip4 := net.IP{1, 2, 3, 4}
|
||||||
|
addr4 := &net.IPNet{IP: ip4}
|
||||||
|
|
||||||
|
ip6 := net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}
|
||||||
|
addr6 := &net.IPNet{IP: ip6}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
iface netIface
|
||||||
|
ipv ipVersion
|
||||||
|
want []net.IP
|
||||||
|
wantErr error
|
||||||
|
}{{
|
||||||
|
name: "ipv4_success",
|
||||||
|
iface: &fakeIface{addrs: []net.Addr{addr4}, err: nil},
|
||||||
|
ipv: ipVersion4,
|
||||||
|
want: []net.IP{ip4},
|
||||||
|
wantErr: nil,
|
||||||
|
}, {
|
||||||
|
name: "ipv4_success_with_ipv6",
|
||||||
|
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
||||||
|
ipv: ipVersion4,
|
||||||
|
want: []net.IP{ip4},
|
||||||
|
wantErr: nil,
|
||||||
|
}, {
|
||||||
|
name: "ipv4_error",
|
||||||
|
iface: &fakeIface{addrs: []net.Addr{addr4}, err: errTest},
|
||||||
|
ipv: ipVersion4,
|
||||||
|
want: nil,
|
||||||
|
wantErr: errTest,
|
||||||
|
}, {
|
||||||
|
name: "ipv6_success",
|
||||||
|
iface: &fakeIface{addrs: []net.Addr{addr6}, err: nil},
|
||||||
|
ipv: ipVersion6,
|
||||||
|
want: []net.IP{ip6},
|
||||||
|
wantErr: nil,
|
||||||
|
}, {
|
||||||
|
name: "ipv6_success_with_ipv4",
|
||||||
|
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
||||||
|
ipv: ipVersion6,
|
||||||
|
want: []net.IP{ip6},
|
||||||
|
wantErr: nil,
|
||||||
|
}, {
|
||||||
|
name: "ipv6_error",
|
||||||
|
iface: &fakeIface{addrs: []net.Addr{addr6}, err: errTest},
|
||||||
|
ipv: ipVersion6,
|
||||||
|
want: nil,
|
||||||
|
wantErr: errTest,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got, gotErr := ifaceIPAddrs(tc.iface, tc.ipv)
|
||||||
|
assert.Equal(t, tc.want, got)
|
||||||
|
assert.True(t, errors.Is(gotErr, tc.wantErr))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type waitingFakeIface struct {
|
||||||
|
addrs []net.Addr
|
||||||
|
err error
|
||||||
|
n int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addrs implements the netIface interface for *waitingFakeIface.
|
||||||
|
func (iface *waitingFakeIface) Addrs() (addrs []net.Addr, err error) {
|
||||||
|
if iface.err != nil {
|
||||||
|
return nil, iface.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if iface.n == 0 {
|
||||||
|
return iface.addrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
iface.n--
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIfaceDNSIPAddrs(t *testing.T) {
|
||||||
|
const errTest agherr.Error = "test error"
|
||||||
|
|
||||||
|
ip4 := net.IP{1, 2, 3, 4}
|
||||||
|
addr4 := &net.IPNet{IP: ip4}
|
||||||
|
|
||||||
|
ip6 := net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}
|
||||||
|
addr6 := &net.IPNet{IP: ip6}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
iface netIface
|
||||||
|
ipv ipVersion
|
||||||
|
want []net.IP
|
||||||
|
wantErr error
|
||||||
|
}{{
|
||||||
|
name: "ipv4_success",
|
||||||
|
iface: &fakeIface{addrs: []net.Addr{addr4}, err: nil},
|
||||||
|
ipv: ipVersion4,
|
||||||
|
want: []net.IP{ip4, ip4},
|
||||||
|
wantErr: nil,
|
||||||
|
}, {
|
||||||
|
name: "ipv4_success_with_ipv6",
|
||||||
|
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
||||||
|
ipv: ipVersion4,
|
||||||
|
want: []net.IP{ip4, ip4},
|
||||||
|
wantErr: nil,
|
||||||
|
}, {
|
||||||
|
name: "ipv4_error",
|
||||||
|
iface: &fakeIface{addrs: []net.Addr{addr4}, err: errTest},
|
||||||
|
ipv: ipVersion4,
|
||||||
|
want: nil,
|
||||||
|
wantErr: errTest,
|
||||||
|
}, {
|
||||||
|
name: "ipv4_wait",
|
||||||
|
iface: &waitingFakeIface{
|
||||||
|
addrs: []net.Addr{addr4},
|
||||||
|
err: nil,
|
||||||
|
n: 1,
|
||||||
|
},
|
||||||
|
ipv: ipVersion4,
|
||||||
|
want: []net.IP{ip4, ip4},
|
||||||
|
wantErr: nil,
|
||||||
|
}, {
|
||||||
|
name: "ipv6_success",
|
||||||
|
iface: &fakeIface{addrs: []net.Addr{addr6}, err: nil},
|
||||||
|
ipv: ipVersion6,
|
||||||
|
want: []net.IP{ip6, ip6},
|
||||||
|
wantErr: nil,
|
||||||
|
}, {
|
||||||
|
name: "ipv6_success_with_ipv4",
|
||||||
|
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
||||||
|
ipv: ipVersion6,
|
||||||
|
want: []net.IP{ip6, ip6},
|
||||||
|
wantErr: nil,
|
||||||
|
}, {
|
||||||
|
name: "ipv6_error",
|
||||||
|
iface: &fakeIface{addrs: []net.Addr{addr6}, err: errTest},
|
||||||
|
ipv: ipVersion6,
|
||||||
|
want: nil,
|
||||||
|
wantErr: errTest,
|
||||||
|
}, {
|
||||||
|
name: "ipv6_wait",
|
||||||
|
iface: &waitingFakeIface{
|
||||||
|
addrs: []net.Addr{addr6},
|
||||||
|
err: nil,
|
||||||
|
n: 1,
|
||||||
|
},
|
||||||
|
ipv: ipVersion6,
|
||||||
|
want: []net.IP{ip6, ip6},
|
||||||
|
wantErr: nil,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got, gotErr := ifaceDNSIPAddrs(tc.iface, tc.ipv, 2, 0)
|
||||||
|
assert.Equal(t, tc.want, got)
|
||||||
|
assert.True(t, errors.Is(gotErr, tc.wantErr))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,47 +1,23 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
package dhcpd
|
package dhcpd
|
||||||
|
|
||||||
// 'u-root/u-root' package, a dependency of 'insomniacslk/dhcp' package, doesn't build on Windows
|
// 'u-root/u-root' package, a dependency of 'insomniacslk/dhcp' package, doesn't build on Windows
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
type winServer struct {
|
type winServer struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *winServer) ResetLeases(leases []*Lease) {
|
func (s *winServer) ResetLeases(leases []*Lease) {}
|
||||||
}
|
func (s *winServer) GetLeases(flags int) []Lease { return nil }
|
||||||
func (s *winServer) GetLeases(flags int) []Lease {
|
func (s *winServer) GetLeasesRef() []*Lease { return nil }
|
||||||
return nil
|
func (s *winServer) AddStaticLease(lease Lease) error { return nil }
|
||||||
}
|
func (s *winServer) RemoveStaticLease(l Lease) error { return nil }
|
||||||
func (s *winServer) GetLeasesRef() []*Lease {
|
func (s *winServer) FindMACbyIP(ip net.IP) net.HardwareAddr { return nil }
|
||||||
return nil
|
func (s *winServer) WriteDiskConfig4(c *V4ServerConf) {}
|
||||||
}
|
func (s *winServer) WriteDiskConfig6(c *V6ServerConf) {}
|
||||||
func (s *winServer) AddStaticLease(lease Lease) error {
|
func (s *winServer) Start() error { return nil }
|
||||||
return nil
|
func (s *winServer) Stop() {}
|
||||||
}
|
func (s *winServer) Reset() {}
|
||||||
func (s *winServer) RemoveStaticLease(l Lease) error {
|
func v4Create(conf V4ServerConf) (DHCPServer, error) { return &winServer{}, nil }
|
||||||
return nil
|
func v6Create(conf V6ServerConf) (DHCPServer, error) { return &winServer{}, nil }
|
||||||
}
|
|
||||||
func (s *winServer) FindMACbyIP(ip net.IP) net.HardwareAddr {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *winServer) WriteDiskConfig4(c *V4ServerConf) {
|
|
||||||
}
|
|
||||||
func (s *winServer) WriteDiskConfig6(c *V6ServerConf) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *winServer) Start() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (s *winServer) Stop() {
|
|
||||||
}
|
|
||||||
func (s *winServer) Reset() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func v4Create(conf V4ServerConf) (DHCPServer, error) {
|
|
||||||
return &winServer{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func v6Create(conf V6ServerConf) (DHCPServer, error) {
|
|
||||||
return &winServer{}, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,7 +17,9 @@ import (
|
||||||
|
|
||||||
const valueIAID = "ADGH" // value for IANA.ID
|
const valueIAID = "ADGH" // value for IANA.ID
|
||||||
|
|
||||||
// v6Server - DHCPv6 server
|
// v6Server is a DHCPv6 server.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Think about unifying this and v4Server.
|
||||||
type v6Server struct {
|
type v6Server struct {
|
||||||
srv *server6.Server
|
srv *server6.Server
|
||||||
leasesLock sync.Mutex
|
leasesLock sync.Mutex
|
||||||
|
@ -537,27 +539,6 @@ func (s *v6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ifaceIPv6Addrs returns the interface's IPv6 addresses.
|
|
||||||
func ifaceIPv6Addrs(iface *net.Interface) (ips []net.IP, err error) {
|
|
||||||
addrs, err := iface.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, a := range addrs {
|
|
||||||
ipnet, ok := a.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip := ipnet.IP.To16(); ip != nil {
|
|
||||||
ips = append(ips, ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize RA module
|
// initialize RA module
|
||||||
func (s *v6Server) initRA(iface *net.Interface) error {
|
func (s *v6Server) initRA(iface *net.Interface) error {
|
||||||
// choose the source IP address - should be link-local-unicast
|
// choose the source IP address - should be link-local-unicast
|
||||||
|
@ -591,24 +572,16 @@ func (s *v6Server) Start() error {
|
||||||
return fmt.Errorf("dhcpv6: finding interface %s by name: %w", ifaceName, err)
|
return fmt.Errorf("dhcpv6: finding interface %s by name: %w", ifaceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("dhcpv4: starting...")
|
log.Debug("dhcpv6: starting...")
|
||||||
|
|
||||||
dnsIPAddrs, err := ifaceIPv6Addrs(iface)
|
dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion6, defaultMaxAttempts, defaultBackoff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("dhcpv6: getting ipv6 addrs for iface %s: %w", ifaceName, err)
|
return fmt.Errorf("dhcpv6: interface %s: %w", ifaceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch len(dnsIPAddrs) {
|
if len(dnsIPAddrs) == 0 {
|
||||||
case 0:
|
// No available IP addresses which may appear later.
|
||||||
log.Debug("dhcpv6: no ipv6 address for interface %s", iface.Name)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
case 1:
|
|
||||||
// See the comment in (*v4Server).Start.
|
|
||||||
log.Debug("dhcpv6: setting secondary dns ip to iself for interface %s", iface.Name)
|
|
||||||
dnsIPAddrs = append(dnsIPAddrs, dnsIPAddrs[0])
|
|
||||||
default:
|
|
||||||
// Go on.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.conf.dnsIPAddrs = dnsIPAddrs
|
s.conf.dnsIPAddrs = dnsIPAddrs
|
||||||
|
@ -624,7 +597,7 @@ func (s *v6Server) Start() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("DHCPv6: starting...")
|
log.Debug("dhcpv6: listening...")
|
||||||
|
|
||||||
if len(iface.HardwareAddr) != 6 {
|
if len(iface.HardwareAddr) != 6 {
|
||||||
return fmt.Errorf("dhcpv6: invalid MAC %s", iface.HardwareAddr)
|
return fmt.Errorf("dhcpv6: invalid MAC %s", iface.HardwareAddr)
|
||||||
|
@ -655,7 +628,10 @@ func (s *v6Server) Start() error {
|
||||||
|
|
||||||
// Stop - stop server
|
// Stop - stop server
|
||||||
func (s *v6Server) Stop() {
|
func (s *v6Server) Stop() {
|
||||||
s.ra.Close()
|
err := s.ra.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("dhcpv6: s.ra.Close: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
// DHCPv6 server may not be initialized if ra_slaac_only=true
|
// DHCPv6 server may not be initialized if ra_slaac_only=true
|
||||||
if s.srv == nil {
|
if s.srv == nil {
|
||||||
|
@ -663,10 +639,11 @@ func (s *v6Server) Stop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("DHCPv6: stopping")
|
log.Debug("DHCPv6: stopping")
|
||||||
err := s.srv.Close()
|
err = s.srv.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("DHCPv6: srv.Close: %s", err)
|
log.Error("DHCPv6: srv.Close: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// now server.Serve() will return
|
// now server.Serve() will return
|
||||||
s.srv = nil
|
s.srv = nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,7 +188,7 @@ func BlockedSvcKnown(s string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyBlockedServices - set blocked services settings for this DNS request
|
// ApplyBlockedServices - set blocked services settings for this DNS request
|
||||||
func (d *Dnsfilter) ApplyBlockedServices(setts *RequestFilteringSettings, list []string, global bool) {
|
func (d *DNSFilter) ApplyBlockedServices(setts *RequestFilteringSettings, list []string, global bool) {
|
||||||
setts.ServicesRules = []ServiceEntry{}
|
setts.ServicesRules = []ServiceEntry{}
|
||||||
if global {
|
if global {
|
||||||
d.confLock.RLock()
|
d.confLock.RLock()
|
||||||
|
@ -210,7 +210,7 @@ func (d *Dnsfilter) ApplyBlockedServices(setts *RequestFilteringSettings, list [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
|
||||||
d.confLock.RLock()
|
d.confLock.RLock()
|
||||||
list := d.Config.BlockedServices
|
list := d.Config.BlockedServices
|
||||||
d.confLock.RUnlock()
|
d.confLock.RUnlock()
|
||||||
|
@ -223,7 +223,7 @@ func (d *Dnsfilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Req
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {
|
||||||
list := []string{}
|
list := []string{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&list)
|
err := json.NewDecoder(r.Body).Decode(&list)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -241,7 +241,7 @@ func (d *Dnsfilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerBlockedServicesHandlers - register HTTP handlers
|
// registerBlockedServicesHandlers - register HTTP handlers
|
||||||
func (d *Dnsfilter) registerBlockedServicesHandlers() {
|
func (d *DNSFilter) registerBlockedServicesHandlers() {
|
||||||
d.Config.HTTPRegister("GET", "/control/blocked_services/list", d.handleBlockedServicesList)
|
d.Config.HTTPRegister("GET", "/control/blocked_services/list", d.handleBlockedServicesList)
|
||||||
d.Config.HTTPRegister("POST", "/control/blocked_services/set", d.handleBlockedServicesSet)
|
d.Config.HTTPRegister("POST", "/control/blocked_services/set", d.handleBlockedServicesSet)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
// Package dnsfilter implements a DNS filter.
|
// Package dnsfilter implements a DNS request and response filter.
|
||||||
package dnsfilter
|
package dnsfilter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -91,12 +91,12 @@ type filtersInitializerParams struct {
|
||||||
blockFilters []Filter
|
blockFilters []Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dnsfilter holds added rules and performs hostname matches against the rules
|
// DNSFilter matches hostnames and DNS requests against filtering rules.
|
||||||
type Dnsfilter struct {
|
type DNSFilter struct {
|
||||||
rulesStorage *filterlist.RuleStorage
|
rulesStorage *filterlist.RuleStorage
|
||||||
filteringEngine *urlfilter.DNSEngine
|
filteringEngine *urlfilter.DNSEngine
|
||||||
rulesStorageWhite *filterlist.RuleStorage
|
rulesStorageAllow *filterlist.RuleStorage
|
||||||
filteringEngineWhite *urlfilter.DNSEngine
|
filteringEngineAllow *urlfilter.DNSEngine
|
||||||
engineLock sync.RWMutex
|
engineLock sync.RWMutex
|
||||||
|
|
||||||
parentalServer string // access via methods
|
parentalServer string // access via methods
|
||||||
|
@ -127,15 +127,16 @@ const (
|
||||||
|
|
||||||
// NotFilteredNotFound - host was not find in any checks, default value for result
|
// NotFilteredNotFound - host was not find in any checks, default value for result
|
||||||
NotFilteredNotFound Reason = iota
|
NotFilteredNotFound Reason = iota
|
||||||
// NotFilteredWhiteList - the host is explicitly whitelisted
|
// NotFilteredAllowList - the host is explicitly allowed
|
||||||
NotFilteredWhiteList
|
NotFilteredAllowList
|
||||||
// NotFilteredError - there was a transitive error during check
|
// NotFilteredError is returned when there was an error during
|
||||||
|
// checking. Reserved, currently unused.
|
||||||
NotFilteredError
|
NotFilteredError
|
||||||
|
|
||||||
// reasons for filtering
|
// reasons for filtering
|
||||||
|
|
||||||
// FilteredBlackList - the host was matched to be advertising host
|
// FilteredBlockList - the host was matched to be advertising host
|
||||||
FilteredBlackList
|
FilteredBlockList
|
||||||
// FilteredSafeBrowsing - the host was matched to be malicious/phishing
|
// FilteredSafeBrowsing - the host was matched to be malicious/phishing
|
||||||
FilteredSafeBrowsing
|
FilteredSafeBrowsing
|
||||||
// FilteredParental - the host was matched to be outside of parental control settings
|
// FilteredParental - the host was matched to be outside of parental control settings
|
||||||
|
@ -147,38 +148,60 @@ const (
|
||||||
// FilteredBlockedService - the host is blocked by "blocked services" settings
|
// FilteredBlockedService - the host is blocked by "blocked services" settings
|
||||||
FilteredBlockedService
|
FilteredBlockedService
|
||||||
|
|
||||||
// ReasonRewrite - rewrite rule was applied
|
// ReasonRewrite is returned when there was a rewrite by
|
||||||
|
// a legacy DNS Rewrite rule.
|
||||||
ReasonRewrite
|
ReasonRewrite
|
||||||
|
|
||||||
// RewriteEtcHosts - rewrite by /etc/hosts rule
|
// RewriteAutoHosts is returned when there was a rewrite by
|
||||||
RewriteEtcHosts
|
// autohosts rules (/etc/hosts and so on).
|
||||||
|
RewriteAutoHosts
|
||||||
|
|
||||||
|
// DNSRewriteRule is returned when a $dnsrewrite filter rule was
|
||||||
|
// applied.
|
||||||
|
DNSRewriteRule
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(a.garipov): Resync with actual code names or replace completely
|
||||||
|
// in HTTP API v1.
|
||||||
var reasonNames = []string{
|
var reasonNames = []string{
|
||||||
"NotFilteredNotFound",
|
NotFilteredNotFound: "NotFilteredNotFound",
|
||||||
"NotFilteredWhiteList",
|
NotFilteredAllowList: "NotFilteredWhiteList",
|
||||||
"NotFilteredError",
|
NotFilteredError: "NotFilteredError",
|
||||||
|
|
||||||
"FilteredBlackList",
|
FilteredBlockList: "FilteredBlackList",
|
||||||
"FilteredSafeBrowsing",
|
FilteredSafeBrowsing: "FilteredSafeBrowsing",
|
||||||
"FilteredParental",
|
FilteredParental: "FilteredParental",
|
||||||
"FilteredInvalid",
|
FilteredInvalid: "FilteredInvalid",
|
||||||
"FilteredSafeSearch",
|
FilteredSafeSearch: "FilteredSafeSearch",
|
||||||
"FilteredBlockedService",
|
FilteredBlockedService: "FilteredBlockedService",
|
||||||
|
|
||||||
"Rewrite",
|
ReasonRewrite: "Rewrite",
|
||||||
"RewriteEtcHosts",
|
|
||||||
|
RewriteAutoHosts: "RewriteEtcHosts",
|
||||||
|
|
||||||
|
DNSRewriteRule: "DNSRewriteRule",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Reason) String() string {
|
func (r Reason) String() string {
|
||||||
if uint(r) >= uint(len(reasonNames)) {
|
if r < 0 || int(r) >= len(reasonNames) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return reasonNames[r]
|
return reasonNames[r]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In returns true if reasons include r.
|
||||||
|
func (r Reason) In(reasons ...Reason) bool {
|
||||||
|
for _, reason := range reasons {
|
||||||
|
if r == reason {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// GetConfig - get configuration
|
// GetConfig - get configuration
|
||||||
func (d *Dnsfilter) GetConfig() RequestFilteringSettings {
|
func (d *DNSFilter) GetConfig() RequestFilteringSettings {
|
||||||
c := RequestFilteringSettings{}
|
c := RequestFilteringSettings{}
|
||||||
// d.confLock.RLock()
|
// d.confLock.RLock()
|
||||||
c.SafeSearchEnabled = d.Config.SafeSearchEnabled
|
c.SafeSearchEnabled = d.Config.SafeSearchEnabled
|
||||||
|
@ -189,7 +212,7 @@ func (d *Dnsfilter) GetConfig() RequestFilteringSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteDiskConfig - write configuration
|
// WriteDiskConfig - write configuration
|
||||||
func (d *Dnsfilter) WriteDiskConfig(c *Config) {
|
func (d *DNSFilter) WriteDiskConfig(c *Config) {
|
||||||
d.confLock.Lock()
|
d.confLock.Lock()
|
||||||
*c = d.Config
|
*c = d.Config
|
||||||
c.Rewrites = rewriteArrayDup(d.Config.Rewrites)
|
c.Rewrites = rewriteArrayDup(d.Config.Rewrites)
|
||||||
|
@ -200,7 +223,7 @@ func (d *Dnsfilter) WriteDiskConfig(c *Config) {
|
||||||
// SetFilters - set new filters (synchronously or asynchronously)
|
// SetFilters - set new filters (synchronously or asynchronously)
|
||||||
// When filters are set asynchronously, the old filters continue working until the new filters are ready.
|
// When filters are set asynchronously, the old filters continue working until the new filters are ready.
|
||||||
// In this case the caller must ensure that the old filter files are intact.
|
// In this case the caller must ensure that the old filter files are intact.
|
||||||
func (d *Dnsfilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error {
|
func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error {
|
||||||
if async {
|
if async {
|
||||||
params := filtersInitializerParams{
|
params := filtersInitializerParams{
|
||||||
allowFilters: allowFilters,
|
allowFilters: allowFilters,
|
||||||
|
@ -234,7 +257,7 @@ func (d *Dnsfilter) SetFilters(blockFilters, allowFilters []Filter, async bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts initializing new filters by signal from channel
|
// Starts initializing new filters by signal from channel
|
||||||
func (d *Dnsfilter) filtersInitializer() {
|
func (d *DNSFilter) filtersInitializer() {
|
||||||
for {
|
for {
|
||||||
params := <-d.filtersInitializerChan
|
params := <-d.filtersInitializerChan
|
||||||
err := d.initFiltering(params.allowFilters, params.blockFilters)
|
err := d.initFiltering(params.allowFilters, params.blockFilters)
|
||||||
|
@ -246,23 +269,31 @@ func (d *Dnsfilter) filtersInitializer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close - close the object
|
// Close - close the object
|
||||||
func (d *Dnsfilter) Close() {
|
func (d *DNSFilter) Close() {
|
||||||
d.engineLock.Lock()
|
d.engineLock.Lock()
|
||||||
defer d.engineLock.Unlock()
|
defer d.engineLock.Unlock()
|
||||||
d.reset()
|
d.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) reset() {
|
func (d *DNSFilter) reset() {
|
||||||
|
var err error
|
||||||
|
|
||||||
if d.rulesStorage != nil {
|
if d.rulesStorage != nil {
|
||||||
_ = d.rulesStorage.Close()
|
err = d.rulesStorage.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("dnsfilter: rulesStorage.Close: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.rulesStorageWhite != nil {
|
|
||||||
d.rulesStorageWhite.Close()
|
if d.rulesStorageAllow != nil {
|
||||||
|
err = d.rulesStorageAllow.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("dnsfilter: rulesStorageAllow.Close: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type dnsFilterContext struct {
|
type dnsFilterContext struct {
|
||||||
stats Stats
|
|
||||||
safebrowsingCache cache.Cache
|
safebrowsingCache cache.Cache
|
||||||
parentalCache cache.Cache
|
parentalCache cache.Cache
|
||||||
safeSearchCache cache.Cache
|
safeSearchCache cache.Cache
|
||||||
|
@ -270,34 +301,63 @@ type dnsFilterContext struct {
|
||||||
|
|
||||||
var gctx dnsFilterContext // global dnsfilter context
|
var gctx dnsFilterContext // global dnsfilter context
|
||||||
|
|
||||||
// Result holds state of hostname check
|
// ResultRule contains information about applied rules.
|
||||||
type Result struct {
|
type ResultRule struct {
|
||||||
IsFiltered bool `json:",omitempty"` // True if the host name is filtered
|
// FilterListID is the ID of the rule's filter list.
|
||||||
Reason Reason `json:",omitempty"` // Reason for blocking / unblocking
|
FilterListID int64 `json:",omitempty"`
|
||||||
Rule string `json:",omitempty"` // Original rule text
|
// Text is the text of the rule.
|
||||||
IP net.IP `json:",omitempty"` // Not nil only in the case of a hosts file syntax
|
Text string `json:",omitempty"`
|
||||||
FilterID int64 `json:",omitempty"` // Filter ID the rule belongs to
|
// IP is the host IP. It is nil unless the rule uses the
|
||||||
|
// /etc/hosts syntax or the reason is FilteredSafeSearch.
|
||||||
// for ReasonRewrite:
|
IP net.IP `json:",omitempty"`
|
||||||
CanonName string `json:",omitempty"` // CNAME value
|
|
||||||
|
|
||||||
// for RewriteEtcHosts:
|
|
||||||
ReverseHosts []string `json:",omitempty"`
|
|
||||||
|
|
||||||
// for ReasonRewrite & RewriteEtcHosts:
|
|
||||||
IPList []net.IP `json:",omitempty"` // list of IP addresses
|
|
||||||
|
|
||||||
// for FilteredBlockedService:
|
|
||||||
ServiceName string `json:",omitempty"` // Name of the blocked service
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matched can be used to see if any match at all was found, no matter filtered or not
|
// Result contains the result of a request check.
|
||||||
|
//
|
||||||
|
// All fields transitively have omitempty tags so that the query log
|
||||||
|
// doesn't become too large.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Clarify relationships between fields. Perhaps
|
||||||
|
// replace with a sum type or an interface?
|
||||||
|
type Result struct {
|
||||||
|
// IsFiltered is true if the request is filtered.
|
||||||
|
IsFiltered bool `json:",omitempty"`
|
||||||
|
|
||||||
|
// Reason is the reason for blocking or unblocking the request.
|
||||||
|
Reason Reason `json:",omitempty"`
|
||||||
|
|
||||||
|
// Rules are applied rules. If Rules are not empty, each rule
|
||||||
|
// is not nil.
|
||||||
|
Rules []*ResultRule `json:",omitempty"`
|
||||||
|
|
||||||
|
// ReverseHosts is the reverse lookup rewrite result. It is
|
||||||
|
// empty unless Reason is set to RewriteAutoHosts.
|
||||||
|
ReverseHosts []string `json:",omitempty"`
|
||||||
|
|
||||||
|
// IPList is the lookup rewrite result. It is empty unless
|
||||||
|
// Reason is set to RewriteAutoHosts or ReasonRewrite.
|
||||||
|
IPList []net.IP `json:",omitempty"`
|
||||||
|
|
||||||
|
// CanonName is the CNAME value from the lookup rewrite result.
|
||||||
|
// It is empty unless Reason is set to ReasonRewrite.
|
||||||
|
CanonName string `json:",omitempty"`
|
||||||
|
|
||||||
|
// ServiceName is the name of the blocked service. It is empty
|
||||||
|
// unless Reason is set to FilteredBlockedService.
|
||||||
|
ServiceName string `json:",omitempty"`
|
||||||
|
|
||||||
|
// DNSRewriteResult is the $dnsrewrite filter rule result.
|
||||||
|
DNSRewriteResult *DNSRewriteResult `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matched returns true if any match at all was found regardless of
|
||||||
|
// whether it was filtered or not.
|
||||||
func (r Reason) Matched() bool {
|
func (r Reason) Matched() bool {
|
||||||
return r != NotFilteredNotFound
|
return r != NotFilteredNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckHostRules tries to match the host against filtering rules only
|
// CheckHostRules tries to match the host against filtering rules only.
|
||||||
func (d *Dnsfilter) CheckHostRules(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
|
func (d *DNSFilter) CheckHostRules(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
|
||||||
if !setts.FilteringEnabled {
|
if !setts.FilteringEnabled {
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
@ -305,9 +365,9 @@ func (d *Dnsfilter) CheckHostRules(host string, qtype uint16, setts *RequestFilt
|
||||||
return d.matchHost(host, qtype, *setts)
|
return d.matchHost(host, qtype, *setts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckHost tries to match the host against filtering rules,
|
// CheckHost tries to match the host against filtering rules, then
|
||||||
// then safebrowsing and parental if they are enabled
|
// safebrowsing and parental control rules, if they are enabled.
|
||||||
func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
|
func (d *DNSFilter) CheckHost(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) {
|
||||||
// sometimes DNS clients will try to resolve ".", which is a request to get root servers
|
// sometimes DNS clients will try to resolve ".", which is a request to get root servers
|
||||||
if host == "" {
|
if host == "" {
|
||||||
return Result{Reason: NotFilteredNotFound}, nil
|
return Result{Reason: NotFilteredNotFound}, nil
|
||||||
|
@ -326,15 +386,12 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
|
||||||
// Now check the hosts file -- do we have any rules for it?
|
// Now check the hosts file -- do we have any rules for it?
|
||||||
// just like DNS rewrites, it has higher priority than filtering rules.
|
// just like DNS rewrites, it has higher priority than filtering rules.
|
||||||
if d.Config.AutoHosts != nil {
|
if d.Config.AutoHosts != nil {
|
||||||
matched, err := d.checkAutoHosts(host, qtype, &result)
|
matched := d.checkAutoHosts(host, qtype, &result)
|
||||||
if matched {
|
if matched {
|
||||||
return result, err
|
return result, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then check the filter lists.
|
|
||||||
// if request is blocked -- it should be blocked.
|
|
||||||
// if it is whitelisted -- we should do nothing with it anymore.
|
|
||||||
if setts.FilteringEnabled {
|
if setts.FilteringEnabled {
|
||||||
result, err = d.matchHost(host, qtype, *setts)
|
result, err = d.matchHost(host, qtype, *setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -393,18 +450,18 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (matched bool, err error) {
|
func (d *DNSFilter) checkAutoHosts(host string, qtype uint16, result *Result) (matched bool) {
|
||||||
ips := d.Config.AutoHosts.Process(host, qtype)
|
ips := d.Config.AutoHosts.Process(host, qtype)
|
||||||
if ips != nil {
|
if ips != nil {
|
||||||
result.Reason = RewriteEtcHosts
|
result.Reason = RewriteAutoHosts
|
||||||
result.IPList = ips
|
result.IPList = ips
|
||||||
|
|
||||||
return true, nil
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype)
|
revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype)
|
||||||
if len(revHosts) != 0 {
|
if len(revHosts) != 0 {
|
||||||
result.Reason = RewriteEtcHosts
|
result.Reason = RewriteAutoHosts
|
||||||
|
|
||||||
// TODO(a.garipov): Optimize this with a buffer.
|
// TODO(a.garipov): Optimize this with a buffer.
|
||||||
result.ReverseHosts = make([]string, len(revHosts))
|
result.ReverseHosts = make([]string, len(revHosts))
|
||||||
|
@ -412,10 +469,10 @@ func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (m
|
||||||
result.ReverseHosts[i] = revHosts[i] + "."
|
result.ReverseHosts[i] = revHosts[i] + "."
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process rewrites table
|
// Process rewrites table
|
||||||
|
@ -425,9 +482,7 @@ func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (m
|
||||||
// . repeat for the new domain name (Note: we return only the last CNAME)
|
// . repeat for the new domain name (Note: we return only the last CNAME)
|
||||||
// . Find A or AAAA record for a domain name (exact match or by wildcard)
|
// . Find A or AAAA record for a domain name (exact match or by wildcard)
|
||||||
// . if found, set IP addresses (IPv4 or IPv6 depending on qtype) in Result.IPList array
|
// . if found, set IP addresses (IPv4 or IPv6 depending on qtype) in Result.IPList array
|
||||||
func (d *Dnsfilter) processRewrites(host string, qtype uint16) Result {
|
func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
|
||||||
var res Result
|
|
||||||
|
|
||||||
d.confLock.RLock()
|
d.confLock.RLock()
|
||||||
defer d.confLock.RUnlock()
|
defer d.confLock.RUnlock()
|
||||||
|
|
||||||
|
@ -442,7 +497,8 @@ func (d *Dnsfilter) processRewrites(host string, qtype uint16) Result {
|
||||||
log.Debug("Rewrite: CNAME for %s is %s", host, rr[0].Answer)
|
log.Debug("Rewrite: CNAME for %s is %s", host, rr[0].Answer)
|
||||||
|
|
||||||
if host == rr[0].Answer { // "host == CNAME" is an exception
|
if host == rr[0].Answer { // "host == CNAME" is an exception
|
||||||
res.Reason = 0
|
res.Reason = NotFilteredNotFound
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,9 +540,16 @@ func matchBlockedServicesRules(host string, svcs []ServiceEntry) Result {
|
||||||
res.Reason = FilteredBlockedService
|
res.Reason = FilteredBlockedService
|
||||||
res.IsFiltered = true
|
res.IsFiltered = true
|
||||||
res.ServiceName = s.Name
|
res.ServiceName = s.Name
|
||||||
res.Rule = rule.Text()
|
|
||||||
log.Debug("Blocked Services: matched rule: %s host: %s service: %s",
|
ruleText := rule.Text()
|
||||||
res.Rule, host, s.Name)
|
res.Rules = []*ResultRule{{
|
||||||
|
FilterListID: int64(rule.GetFilterListID()),
|
||||||
|
Text: ruleText,
|
||||||
|
}}
|
||||||
|
|
||||||
|
log.Debug("blocked services: matched rule: %s host: %s service: %s",
|
||||||
|
ruleText, host, s.Name)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -553,12 +616,12 @@ func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize urlfilter objects.
|
// Initialize urlfilter objects.
|
||||||
func (d *Dnsfilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
||||||
rulesStorage, filteringEngine, err := createFilteringEngine(blockFilters)
|
rulesStorage, filteringEngine, err := createFilteringEngine(blockFilters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rulesStorageWhite, filteringEngineWhite, err := createFilteringEngine(allowFilters)
|
rulesStorageAllow, filteringEngineAllow, err := createFilteringEngine(allowFilters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -567,8 +630,8 @@ func (d *Dnsfilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
||||||
d.reset()
|
d.reset()
|
||||||
d.rulesStorage = rulesStorage
|
d.rulesStorage = rulesStorage
|
||||||
d.filteringEngine = filteringEngine
|
d.filteringEngine = filteringEngine
|
||||||
d.rulesStorageWhite = rulesStorageWhite
|
d.rulesStorageAllow = rulesStorageAllow
|
||||||
d.filteringEngineWhite = filteringEngineWhite
|
d.filteringEngineAllow = filteringEngineAllow
|
||||||
d.engineLock.Unlock()
|
d.engineLock.Unlock()
|
||||||
|
|
||||||
// Make sure that the OS reclaims memory as soon as possible
|
// Make sure that the OS reclaims memory as soon as possible
|
||||||
|
@ -578,36 +641,48 @@ func (d *Dnsfilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// matchHostProcessAllowList processes the allowlist logic of host
|
||||||
|
// matching.
|
||||||
|
func (d *DNSFilter) matchHostProcessAllowList(host string, dnsres urlfilter.DNSResult) (res Result, err error) {
|
||||||
|
var rule rules.Rule
|
||||||
|
if dnsres.NetworkRule != nil {
|
||||||
|
rule = dnsres.NetworkRule
|
||||||
|
} else if len(dnsres.HostRulesV4) > 0 {
|
||||||
|
rule = dnsres.HostRulesV4[0]
|
||||||
|
} else if len(dnsres.HostRulesV6) > 0 {
|
||||||
|
rule = dnsres.HostRulesV6[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule == nil {
|
||||||
|
return Result{}, fmt.Errorf("invalid dns result: rules are empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Filtering: found allowlist rule for host %q: %q list_id: %d",
|
||||||
|
host, rule.Text(), rule.GetFilterListID())
|
||||||
|
|
||||||
|
return makeResult(rule, NotFilteredAllowList), nil
|
||||||
|
}
|
||||||
|
|
||||||
// matchHost is a low-level way to check only if hostname is filtered by rules,
|
// matchHost is a low-level way to check only if hostname is filtered by rules,
|
||||||
// skipping expensive safebrowsing and parental lookups.
|
// skipping expensive safebrowsing and parental lookups.
|
||||||
func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringSettings) (Result, error) {
|
func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringSettings) (res Result, err error) {
|
||||||
d.engineLock.RLock()
|
d.engineLock.RLock()
|
||||||
// Keep in mind that this lock must be held no just when calling Match()
|
// Keep in mind that this lock must be held no just when calling Match()
|
||||||
// but also while using the rules returned by it.
|
// but also while using the rules returned by it.
|
||||||
defer d.engineLock.RUnlock()
|
defer d.engineLock.RUnlock()
|
||||||
|
|
||||||
ureq := urlfilter.DNSRequest{}
|
ureq := urlfilter.DNSRequest{
|
||||||
ureq.Hostname = host
|
Hostname: host,
|
||||||
ureq.ClientIP = setts.ClientIP
|
SortedClientTags: setts.ClientTags,
|
||||||
ureq.ClientName = setts.ClientName
|
ClientIP: setts.ClientIP,
|
||||||
ureq.SortedClientTags = setts.ClientTags
|
ClientName: setts.ClientName,
|
||||||
|
DNSType: qtype,
|
||||||
|
}
|
||||||
|
|
||||||
if d.filteringEngineWhite != nil {
|
if d.filteringEngineAllow != nil {
|
||||||
rr, ok := d.filteringEngineWhite.MatchRequest(ureq)
|
dnsres, ok := d.filteringEngineAllow.MatchRequest(ureq)
|
||||||
if ok {
|
if ok {
|
||||||
var rule rules.Rule
|
return d.matchHostProcessAllowList(host, dnsres)
|
||||||
if rr.NetworkRule != nil {
|
|
||||||
rule = rr.NetworkRule
|
|
||||||
} else if rr.HostRulesV4 != nil {
|
|
||||||
rule = rr.HostRulesV4[0]
|
|
||||||
} else if rr.HostRulesV6 != nil {
|
|
||||||
rule = rr.HostRulesV6[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Filtering: found whitelist rule for host %q: %q list_id: %d",
|
|
||||||
host, rule.Text(), rule.GetFilterListID())
|
|
||||||
res := makeResult(rule, NotFilteredWhiteList)
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,68 +690,87 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rr, ok := d.filteringEngine.MatchRequest(ureq)
|
dnsres, ok := d.filteringEngine.MatchRequest(ureq)
|
||||||
if !ok {
|
|
||||||
|
// Check DNS rewrites first, because the API there is a bit
|
||||||
|
// awkward.
|
||||||
|
if dnsr := dnsres.DNSRewrites(); len(dnsr) > 0 {
|
||||||
|
res = d.processDNSRewrites(dnsr)
|
||||||
|
if res.Reason == DNSRewriteRule && res.CanonName == host {
|
||||||
|
// A rewrite of a host to itself. Go on and
|
||||||
|
// try matching other things.
|
||||||
|
} else {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
} else if !ok {
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if rr.NetworkRule != nil {
|
if dnsres.NetworkRule != nil {
|
||||||
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
||||||
host, rr.NetworkRule.Text(), rr.NetworkRule.GetFilterListID())
|
host, dnsres.NetworkRule.Text(), dnsres.NetworkRule.GetFilterListID())
|
||||||
reason := FilteredBlackList
|
reason := FilteredBlockList
|
||||||
if rr.NetworkRule.Whitelist {
|
if dnsres.NetworkRule.Whitelist {
|
||||||
reason = NotFilteredWhiteList
|
reason = NotFilteredAllowList
|
||||||
}
|
}
|
||||||
res := makeResult(rr.NetworkRule, reason)
|
|
||||||
return res, nil
|
return makeResult(dnsres.NetworkRule, reason), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if qtype == dns.TypeA && rr.HostRulesV4 != nil {
|
if qtype == dns.TypeA && dnsres.HostRulesV4 != nil {
|
||||||
rule := rr.HostRulesV4[0] // note that we process only 1 matched rule
|
rule := dnsres.HostRulesV4[0] // note that we process only 1 matched rule
|
||||||
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
||||||
host, rule.Text(), rule.GetFilterListID())
|
host, rule.Text(), rule.GetFilterListID())
|
||||||
res := makeResult(rule, FilteredBlackList)
|
res = makeResult(rule, FilteredBlockList)
|
||||||
res.IP = rule.IP.To4()
|
res.Rules[0].IP = rule.IP.To4()
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if qtype == dns.TypeAAAA && rr.HostRulesV6 != nil {
|
if qtype == dns.TypeAAAA && dnsres.HostRulesV6 != nil {
|
||||||
rule := rr.HostRulesV6[0] // note that we process only 1 matched rule
|
rule := dnsres.HostRulesV6[0] // note that we process only 1 matched rule
|
||||||
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
||||||
host, rule.Text(), rule.GetFilterListID())
|
host, rule.Text(), rule.GetFilterListID())
|
||||||
res := makeResult(rule, FilteredBlackList)
|
res = makeResult(rule, FilteredBlockList)
|
||||||
res.IP = rule.IP
|
res.Rules[0].IP = rule.IP
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if rr.HostRulesV4 != nil || rr.HostRulesV6 != nil {
|
if dnsres.HostRulesV4 != nil || dnsres.HostRulesV6 != nil {
|
||||||
// Question Type doesn't match the host rules
|
// Question Type doesn't match the host rules
|
||||||
// Return the first matched host rule, but without an IP address
|
// Return the first matched host rule, but without an IP address
|
||||||
var rule rules.Rule
|
var rule rules.Rule
|
||||||
if rr.HostRulesV4 != nil {
|
if dnsres.HostRulesV4 != nil {
|
||||||
rule = rr.HostRulesV4[0]
|
rule = dnsres.HostRulesV4[0]
|
||||||
} else if rr.HostRulesV6 != nil {
|
} else if dnsres.HostRulesV6 != nil {
|
||||||
rule = rr.HostRulesV6[0]
|
rule = dnsres.HostRulesV6[0]
|
||||||
}
|
}
|
||||||
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
||||||
host, rule.Text(), rule.GetFilterListID())
|
host, rule.Text(), rule.GetFilterListID())
|
||||||
res := makeResult(rule, FilteredBlackList)
|
res = makeResult(rule, FilteredBlockList)
|
||||||
res.IP = net.IP{}
|
res.Rules[0].IP = net.IP{}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct Result object
|
// makeResult returns a properly constructed Result.
|
||||||
func makeResult(rule rules.Rule, reason Reason) Result {
|
func makeResult(rule rules.Rule, reason Reason) Result {
|
||||||
res := Result{}
|
res := Result{
|
||||||
res.FilterID = int64(rule.GetFilterListID())
|
Reason: reason,
|
||||||
res.Rule = rule.Text()
|
Rules: []*ResultRule{{
|
||||||
res.Reason = reason
|
FilterListID: int64(rule.GetFilterListID()),
|
||||||
if reason == FilteredBlackList {
|
Text: rule.Text(),
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
if reason == FilteredBlockList {
|
||||||
res.IsFiltered = true
|
res.IsFiltered = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -686,7 +780,7 @@ func InitModule() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates properly initialized DNS Filter that is ready to be used.
|
// New creates properly initialized DNS Filter that is ready to be used.
|
||||||
func New(c *Config, blockFilters []Filter) *Dnsfilter {
|
func New(c *Config, blockFilters []Filter) *DNSFilter {
|
||||||
if c != nil {
|
if c != nil {
|
||||||
cacheConf := cache.Config{
|
cacheConf := cache.Config{
|
||||||
EnableLRU: true,
|
EnableLRU: true,
|
||||||
|
@ -708,7 +802,7 @@ func New(c *Config, blockFilters []Filter) *Dnsfilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d := new(Dnsfilter)
|
d := new(DNSFilter)
|
||||||
|
|
||||||
err := d.initSecurityServices()
|
err := d.initSecurityServices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -746,7 +840,7 @@ func New(c *Config, blockFilters []Filter) *Dnsfilter {
|
||||||
// Start - start the module:
|
// Start - start the module:
|
||||||
// . start async filtering initializer goroutine
|
// . start async filtering initializer goroutine
|
||||||
// . register web handlers
|
// . register web handlers
|
||||||
func (d *Dnsfilter) Start() {
|
func (d *DNSFilter) Start() {
|
||||||
d.filtersInitializerChan = make(chan filtersInitializerParams, 1)
|
d.filtersInitializerChan = make(chan filtersInitializerParams, 1)
|
||||||
go d.filtersInitializer()
|
go d.filtersInitializer()
|
||||||
|
|
||||||
|
@ -756,12 +850,3 @@ func (d *Dnsfilter) Start() {
|
||||||
d.registerBlockedServicesHandlers()
|
d.registerBlockedServicesHandlers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// stats
|
|
||||||
//
|
|
||||||
|
|
||||||
// GetStats return dns filtering stats since startup.
|
|
||||||
func (d *Dnsfilter) GetStats() Stats {
|
|
||||||
return gctx.stats
|
|
||||||
}
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ func purgeCaches() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewForTest(c *Config, filters []Filter) *Dnsfilter {
|
func NewForTest(c *Config, filters []Filter) *DNSFilter {
|
||||||
setts = RequestFilteringSettings{}
|
setts = RequestFilteringSettings{}
|
||||||
setts.FilteringEnabled = true
|
setts.FilteringEnabled = true
|
||||||
if c != nil {
|
if c != nil {
|
||||||
|
@ -58,38 +58,48 @@ func NewForTest(c *Config, filters []Filter) *Dnsfilter {
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) checkMatch(t *testing.T, hostname string) {
|
func (d *DNSFilter) checkMatch(t *testing.T, hostname string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error while matching host %s: %s", hostname, err)
|
t.Errorf("Error while matching host %s: %s", hostname, err)
|
||||||
}
|
}
|
||||||
if !ret.IsFiltered {
|
if !res.IsFiltered {
|
||||||
t.Errorf("Expected hostname %s to match", hostname)
|
t.Errorf("Expected hostname %s to match", hostname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) checkMatchIP(t *testing.T, hostname, ip string, qtype uint16) {
|
func (d *DNSFilter) checkMatchIP(t *testing.T, hostname, ip string, qtype uint16) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ret, err := d.CheckHost(hostname, qtype, &setts)
|
|
||||||
|
res, err := d.CheckHost(hostname, qtype, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error while matching host %s: %s", hostname, err)
|
t.Errorf("Error while matching host %s: %s", hostname, err)
|
||||||
}
|
}
|
||||||
if !ret.IsFiltered {
|
|
||||||
|
if !res.IsFiltered {
|
||||||
t.Errorf("Expected hostname %s to match", hostname)
|
t.Errorf("Expected hostname %s to match", hostname)
|
||||||
}
|
}
|
||||||
if ret.IP == nil || ret.IP.String() != ip {
|
|
||||||
t.Errorf("Expected ip %s to match, actual: %v", ip, ret.IP)
|
if len(res.Rules) == 0 {
|
||||||
|
t.Errorf("Expected result to have rules")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r := res.Rules[0]
|
||||||
|
if r.IP == nil || r.IP.String() != ip {
|
||||||
|
t.Errorf("Expected ip %s to match, actual: %v", ip, r.IP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) checkMatchEmpty(t *testing.T, hostname string) {
|
func (d *DNSFilter) checkMatchEmpty(t *testing.T, hostname string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error while matching host %s: %s", hostname, err)
|
t.Errorf("Error while matching host %s: %s", hostname, err)
|
||||||
}
|
}
|
||||||
if ret.IsFiltered {
|
if res.IsFiltered {
|
||||||
t.Errorf("Expected hostname %s to not match", hostname)
|
t.Errorf("Expected hostname %s to not match", hostname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,26 +130,43 @@ func TestEtcHostsMatching(t *testing.T) {
|
||||||
d.checkMatchIP(t, "block.com", "0.0.0.0", dns.TypeA)
|
d.checkMatchIP(t, "block.com", "0.0.0.0", dns.TypeA)
|
||||||
|
|
||||||
// ...but empty IPv6
|
// ...but empty IPv6
|
||||||
ret, err := d.CheckHost("block.com", dns.TypeAAAA, &setts)
|
res, err := d.CheckHost("block.com", dns.TypeAAAA, &setts)
|
||||||
assert.True(t, err == nil && ret.IsFiltered && ret.IP != nil && len(ret.IP) == 0)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ret.Rule == "0.0.0.0 block.com")
|
assert.True(t, res.IsFiltered)
|
||||||
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.Equal(t, "0.0.0.0 block.com", res.Rules[0].Text)
|
||||||
|
assert.Len(t, res.Rules[0].IP, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// IPv6
|
// IPv6
|
||||||
d.checkMatchIP(t, "ipv6.com", addr6, dns.TypeAAAA)
|
d.checkMatchIP(t, "ipv6.com", addr6, dns.TypeAAAA)
|
||||||
|
|
||||||
// ...but empty IPv4
|
// ...but empty IPv4
|
||||||
ret, err = d.CheckHost("ipv6.com", dns.TypeA, &setts)
|
res, err = d.CheckHost("ipv6.com", dns.TypeA, &setts)
|
||||||
assert.True(t, err == nil && ret.IsFiltered && ret.IP != nil && len(ret.IP) == 0)
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, res.IsFiltered)
|
||||||
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.Equal(t, "::1 ipv6.com", res.Rules[0].Text)
|
||||||
|
assert.Len(t, res.Rules[0].IP, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// 2 IPv4 (return only the first one)
|
// 2 IPv4 (return only the first one)
|
||||||
ret, err = d.CheckHost("host2", dns.TypeA, &setts)
|
res, err = d.CheckHost("host2", dns.TypeA, &setts)
|
||||||
assert.True(t, err == nil && ret.IsFiltered)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ret.IP != nil && ret.IP.Equal(net.ParseIP("0.0.0.1")))
|
assert.True(t, res.IsFiltered)
|
||||||
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
loopback4 := net.IP{0, 0, 0, 1}
|
||||||
|
assert.Equal(t, res.Rules[0].IP, loopback4)
|
||||||
|
}
|
||||||
|
|
||||||
// ...and 1 IPv6 address
|
// ...and 1 IPv6 address
|
||||||
ret, err = d.CheckHost("host2", dns.TypeAAAA, &setts)
|
res, err = d.CheckHost("host2", dns.TypeAAAA, &setts)
|
||||||
assert.True(t, err == nil && ret.IsFiltered)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ret.IP != nil && ret.IP.Equal(net.ParseIP("::1")))
|
assert.True(t, res.IsFiltered)
|
||||||
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
loopback6 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
||||||
|
assert.Equal(t, res.Rules[0].IP, loopback6)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFE BROWSING
|
// SAFE BROWSING
|
||||||
|
@ -151,7 +178,6 @@ func TestSafeBrowsing(t *testing.T) {
|
||||||
|
|
||||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
gctx.stats.Safebrowsing.Requests = 0
|
|
||||||
d.checkMatch(t, "wmconvirus.narod.ru")
|
d.checkMatch(t, "wmconvirus.narod.ru")
|
||||||
|
|
||||||
assert.True(t, strings.Contains(logOutput.String(), "SafeBrowsing lookup for wmconvirus.narod.ru"))
|
assert.True(t, strings.Contains(logOutput.String(), "SafeBrowsing lookup for wmconvirus.narod.ru"))
|
||||||
|
@ -206,13 +232,11 @@ func TestCheckHostSafeSearchYandex(t *testing.T) {
|
||||||
|
|
||||||
// Check host for each domain
|
// Check host for each domain
|
||||||
for _, host := range yandex {
|
for _, host := range yandex {
|
||||||
result, err := d.CheckHost(host, dns.TypeA, &setts)
|
res, err := d.CheckHost(host, dns.TypeA, &setts)
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Errorf("SafeSearch doesn't work for yandex domain `%s` cause %s", host, err)
|
assert.True(t, res.IsFiltered)
|
||||||
}
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.Equal(t, res.Rules[0].IP.String(), "213.180.193.56")
|
||||||
if result.IP.String() != "213.180.193.56" {
|
|
||||||
t.Errorf("SafeSearch doesn't work for yandex domain `%s`", host)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,13 +250,11 @@ func TestCheckHostSafeSearchGoogle(t *testing.T) {
|
||||||
|
|
||||||
// Check host for each domain
|
// Check host for each domain
|
||||||
for _, host := range googleDomains {
|
for _, host := range googleDomains {
|
||||||
result, err := d.CheckHost(host, dns.TypeA, &setts)
|
res, err := d.CheckHost(host, dns.TypeA, &setts)
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Errorf("SafeSearch doesn't work for %s cause %s", host, err)
|
assert.True(t, res.IsFiltered)
|
||||||
}
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.NotEqual(t, res.Rules[0].IP.String(), "0.0.0.0")
|
||||||
if result.IP == nil {
|
|
||||||
t.Errorf("SafeSearch doesn't work for %s", host)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,40 +264,30 @@ func TestSafeSearchCacheYandex(t *testing.T) {
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
domain := "yandex.ru"
|
domain := "yandex.ru"
|
||||||
|
|
||||||
var result Result
|
// Check host with disabled safesearch.
|
||||||
var err error
|
res, err := d.CheckHost(domain, dns.TypeA, &setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
// Check host with disabled safesearch
|
assert.False(t, res.IsFiltered)
|
||||||
result, err = d.CheckHost(domain, dns.TypeA, &setts)
|
assert.Len(t, res.Rules, 0)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Cannot check host due to %s", err)
|
|
||||||
}
|
|
||||||
if result.IP != nil {
|
|
||||||
t.Fatalf("SafeSearch is not enabled but there is an answer for `%s` !", domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
result, err = d.CheckHost(domain, dns.TypeA, &setts)
|
res, err = d.CheckHost(domain, dns.TypeA, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
|
t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fir yandex we already know valid ip
|
// For yandex we already know valid ip.
|
||||||
if result.IP.String() != "213.180.193.56" {
|
if assert.Len(t, res.Rules, 1) {
|
||||||
t.Fatalf("Wrong IP for %s safesearch: %s", domain, result.IP.String())
|
assert.Equal(t, res.Rules[0].IP.String(), "213.180.193.56")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check cache
|
// Check cache.
|
||||||
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain)
|
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain)
|
||||||
|
assert.True(t, isFound)
|
||||||
if !isFound {
|
if assert.Len(t, cachedValue.Rules, 1) {
|
||||||
t.Fatalf("Safesearch cache doesn't work for %s!", domain)
|
assert.Equal(t, cachedValue.Rules[0].IP.String(), "213.180.193.56")
|
||||||
}
|
|
||||||
|
|
||||||
if cachedValue.IP.String() != "213.180.193.56" {
|
|
||||||
t.Fatalf("Wrong IP in cache for %s safesearch: %s", domain, cachedValue.IP.String())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,13 +295,10 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||||
d := NewForTest(nil, nil)
|
d := NewForTest(nil, nil)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
domain := "www.google.ru"
|
domain := "www.google.ru"
|
||||||
result, err := d.CheckHost(domain, dns.TypeA, &setts)
|
res, err := d.CheckHost(domain, dns.TypeA, &setts)
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Fatalf("Cannot check host due to %s", err)
|
assert.False(t, res.IsFiltered)
|
||||||
}
|
assert.Len(t, res.Rules, 0)
|
||||||
if result.IP != nil {
|
|
||||||
t.Fatalf("SafeSearch is not enabled but there is an answer!")
|
|
||||||
}
|
|
||||||
|
|
||||||
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
@ -313,25 +322,17 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err = d.CheckHost(domain, dns.TypeA, &setts)
|
res, err = d.CheckHost(domain, dns.TypeA, &setts)
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.True(t, res.Rules[0].IP.Equal(ip))
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.IP.String() != ip.String() {
|
// Check cache.
|
||||||
t.Fatalf("Wrong IP for %s safesearch: %s. Should be: %s",
|
|
||||||
domain, result.IP.String(), ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check cache
|
|
||||||
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain)
|
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain)
|
||||||
|
assert.True(t, isFound)
|
||||||
if !isFound {
|
if assert.Len(t, cachedValue.Rules, 1) {
|
||||||
t.Fatalf("Safesearch cache doesn't work for %s!", domain)
|
assert.True(t, cachedValue.Rules[0].IP.Equal(ip))
|
||||||
}
|
|
||||||
|
|
||||||
if cachedValue.IP.String() != ip.String() {
|
|
||||||
t.Fatalf("Wrong IP in cache for %s safesearch: %s", domain, cachedValue.IP.String())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,10 +365,11 @@ const nl = "\n"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
blockingRules = `||example.org^` + nl
|
blockingRules = `||example.org^` + nl
|
||||||
whitelistRules = `||example.org^` + nl + `@@||test.example.org` + nl
|
allowlistRules = `||example.org^` + nl + `@@||test.example.org` + nl
|
||||||
importantRules = `@@||example.org^` + nl + `||test.example.org^$important` + nl
|
importantRules = `@@||example.org^` + nl + `||test.example.org^$important` + nl
|
||||||
regexRules = `/example\.org/` + nl + `@@||test.example.org^` + nl
|
regexRules = `/example\.org/` + nl + `@@||test.example.org^` + nl
|
||||||
maskRules = `test*.example.org^` + nl + `exam*.com` + nl
|
maskRules = `test*.example.org^` + nl + `exam*.com` + nl
|
||||||
|
dnstypeRules = `||example.org^$dnstype=AAAA` + nl + `@@||test.example.org^` + nl
|
||||||
)
|
)
|
||||||
|
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
|
@ -376,44 +378,51 @@ var tests = []struct {
|
||||||
hostname string
|
hostname string
|
||||||
isFiltered bool
|
isFiltered bool
|
||||||
reason Reason
|
reason Reason
|
||||||
|
dnsType uint16
|
||||||
}{
|
}{
|
||||||
{"sanity", "||doubleclick.net^", "www.doubleclick.net", true, FilteredBlackList},
|
{"sanity", "||doubleclick.net^", "www.doubleclick.net", true, FilteredBlockList, dns.TypeA},
|
||||||
{"sanity", "||doubleclick.net^", "nodoubleclick.net", false, NotFilteredNotFound},
|
{"sanity", "||doubleclick.net^", "nodoubleclick.net", false, NotFilteredNotFound, dns.TypeA},
|
||||||
{"sanity", "||doubleclick.net^", "doubleclick.net.ru", false, NotFilteredNotFound},
|
{"sanity", "||doubleclick.net^", "doubleclick.net.ru", false, NotFilteredNotFound, dns.TypeA},
|
||||||
{"sanity", "||doubleclick.net^", "wmconvirus.narod.ru", false, NotFilteredNotFound},
|
{"sanity", "||doubleclick.net^", "wmconvirus.narod.ru", false, NotFilteredNotFound, dns.TypeA},
|
||||||
|
|
||||||
{"blocking", blockingRules, "example.org", true, FilteredBlackList},
|
{"blocking", blockingRules, "example.org", true, FilteredBlockList, dns.TypeA},
|
||||||
{"blocking", blockingRules, "test.example.org", true, FilteredBlackList},
|
{"blocking", blockingRules, "test.example.org", true, FilteredBlockList, dns.TypeA},
|
||||||
{"blocking", blockingRules, "test.test.example.org", true, FilteredBlackList},
|
{"blocking", blockingRules, "test.test.example.org", true, FilteredBlockList, dns.TypeA},
|
||||||
{"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound},
|
{"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||||
{"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound},
|
{"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||||
|
|
||||||
{"whitelist", whitelistRules, "example.org", true, FilteredBlackList},
|
{"allowlist", allowlistRules, "example.org", true, FilteredBlockList, dns.TypeA},
|
||||||
{"whitelist", whitelistRules, "test.example.org", false, NotFilteredWhiteList},
|
{"allowlist", allowlistRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA},
|
||||||
{"whitelist", whitelistRules, "test.test.example.org", false, NotFilteredWhiteList},
|
{"allowlist", allowlistRules, "test.test.example.org", false, NotFilteredAllowList, dns.TypeA},
|
||||||
{"whitelist", whitelistRules, "testexample.org", false, NotFilteredNotFound},
|
{"allowlist", allowlistRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||||
{"whitelist", whitelistRules, "onemoreexample.org", false, NotFilteredNotFound},
|
{"allowlist", allowlistRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||||
|
|
||||||
{"important", importantRules, "example.org", false, NotFilteredWhiteList},
|
{"important", importantRules, "example.org", false, NotFilteredAllowList, dns.TypeA},
|
||||||
{"important", importantRules, "test.example.org", true, FilteredBlackList},
|
{"important", importantRules, "test.example.org", true, FilteredBlockList, dns.TypeA},
|
||||||
{"important", importantRules, "test.test.example.org", true, FilteredBlackList},
|
{"important", importantRules, "test.test.example.org", true, FilteredBlockList, dns.TypeA},
|
||||||
{"important", importantRules, "testexample.org", false, NotFilteredNotFound},
|
{"important", importantRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||||
{"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound},
|
{"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||||
|
|
||||||
{"regex", regexRules, "example.org", true, FilteredBlackList},
|
{"regex", regexRules, "example.org", true, FilteredBlockList, dns.TypeA},
|
||||||
{"regex", regexRules, "test.example.org", false, NotFilteredWhiteList},
|
{"regex", regexRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA},
|
||||||
{"regex", regexRules, "test.test.example.org", false, NotFilteredWhiteList},
|
{"regex", regexRules, "test.test.example.org", false, NotFilteredAllowList, dns.TypeA},
|
||||||
{"regex", regexRules, "testexample.org", true, FilteredBlackList},
|
{"regex", regexRules, "testexample.org", true, FilteredBlockList, dns.TypeA},
|
||||||
{"regex", regexRules, "onemoreexample.org", true, FilteredBlackList},
|
{"regex", regexRules, "onemoreexample.org", true, FilteredBlockList, dns.TypeA},
|
||||||
|
|
||||||
{"mask", maskRules, "test.example.org", true, FilteredBlackList},
|
{"mask", maskRules, "test.example.org", true, FilteredBlockList, dns.TypeA},
|
||||||
{"mask", maskRules, "test2.example.org", true, FilteredBlackList},
|
{"mask", maskRules, "test2.example.org", true, FilteredBlockList, dns.TypeA},
|
||||||
{"mask", maskRules, "example.com", true, FilteredBlackList},
|
{"mask", maskRules, "example.com", true, FilteredBlockList, dns.TypeA},
|
||||||
{"mask", maskRules, "exampleeee.com", true, FilteredBlackList},
|
{"mask", maskRules, "exampleeee.com", true, FilteredBlockList, dns.TypeA},
|
||||||
{"mask", maskRules, "onemoreexamsite.com", true, FilteredBlackList},
|
{"mask", maskRules, "onemoreexamsite.com", true, FilteredBlockList, dns.TypeA},
|
||||||
{"mask", maskRules, "example.org", false, NotFilteredNotFound},
|
{"mask", maskRules, "example.org", false, NotFilteredNotFound, dns.TypeA},
|
||||||
{"mask", maskRules, "testexample.org", false, NotFilteredNotFound},
|
{"mask", maskRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||||
{"mask", maskRules, "example.co.uk", false, NotFilteredNotFound},
|
{"mask", maskRules, "example.co.uk", false, NotFilteredNotFound, dns.TypeA},
|
||||||
|
|
||||||
|
{"dnstype", dnstypeRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||||
|
{"dnstype", dnstypeRules, "example.org", false, NotFilteredNotFound, dns.TypeA},
|
||||||
|
{"dnstype", dnstypeRules, "example.org", true, FilteredBlockList, dns.TypeAAAA},
|
||||||
|
{"dnstype", dnstypeRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA},
|
||||||
|
{"dnstype", dnstypeRules, "test.example.org", false, NotFilteredAllowList, dns.TypeAAAA},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMatching(t *testing.T) {
|
func TestMatching(t *testing.T) {
|
||||||
|
@ -425,15 +434,15 @@ func TestMatching(t *testing.T) {
|
||||||
d := NewForTest(nil, filters)
|
d := NewForTest(nil, filters)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
ret, err := d.CheckHost(test.hostname, dns.TypeA, &setts)
|
res, err := d.CheckHost(test.hostname, test.dnsType, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error while matching host %s: %s", test.hostname, err)
|
t.Errorf("Error while matching host %s: %s", test.hostname, err)
|
||||||
}
|
}
|
||||||
if ret.IsFiltered != test.isFiltered {
|
if res.IsFiltered != test.isFiltered {
|
||||||
t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, ret.IsFiltered, test.isFiltered)
|
t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, res.IsFiltered, test.isFiltered)
|
||||||
}
|
}
|
||||||
if ret.Reason != test.reason {
|
if res.Reason != test.reason {
|
||||||
t.Errorf("Hostname %s has wrong reason (%v must be %v)", test.hostname, ret.Reason.String(), test.reason.String())
|
t.Errorf("Hostname %s has wrong reason (%v must be %v)", test.hostname, res.Reason.String(), test.reason.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -458,16 +467,20 @@ func TestWhitelist(t *testing.T) {
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
// matched by white filter
|
// matched by white filter
|
||||||
ret, err := d.CheckHost("host1", dns.TypeA, &setts)
|
res, err := d.CheckHost("host1", dns.TypeA, &setts)
|
||||||
assert.True(t, err == nil)
|
assert.True(t, err == nil)
|
||||||
assert.True(t, !ret.IsFiltered && ret.Reason == NotFilteredWhiteList)
|
assert.True(t, !res.IsFiltered && res.Reason == NotFilteredAllowList)
|
||||||
assert.True(t, ret.Rule == "||host1^")
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.True(t, res.Rules[0].Text == "||host1^")
|
||||||
|
}
|
||||||
|
|
||||||
// not matched by white filter, but matched by block filter
|
// not matched by white filter, but matched by block filter
|
||||||
ret, err = d.CheckHost("host2", dns.TypeA, &setts)
|
res, err = d.CheckHost("host2", dns.TypeA, &setts)
|
||||||
assert.True(t, err == nil)
|
assert.True(t, err == nil)
|
||||||
assert.True(t, ret.IsFiltered && ret.Reason == FilteredBlackList)
|
assert.True(t, res.IsFiltered && res.Reason == FilteredBlockList)
|
||||||
assert.True(t, ret.Rule == "||host2^")
|
if assert.Len(t, res.Rules, 1) {
|
||||||
|
assert.True(t, res.Rules[0].Text == "||host2^")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CLIENT SETTINGS
|
// CLIENT SETTINGS
|
||||||
|
@ -498,8 +511,8 @@ func TestClientSettings(t *testing.T) {
|
||||||
|
|
||||||
// blocked by filters
|
// blocked by filters
|
||||||
r, _ = d.CheckHost("example.org", dns.TypeA, &setts)
|
r, _ = d.CheckHost("example.org", dns.TypeA, &setts)
|
||||||
if !r.IsFiltered || r.Reason != FilteredBlackList {
|
if !r.IsFiltered || r.Reason != FilteredBlockList {
|
||||||
t.Fatalf("CheckHost FilteredBlackList")
|
t.Fatalf("CheckHost FilteredBlockList")
|
||||||
}
|
}
|
||||||
|
|
||||||
// blocked by parental
|
// blocked by parental
|
||||||
|
@ -551,11 +564,11 @@ func BenchmarkSafeBrowsing(b *testing.B) {
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
hostname := "wmconvirus.narod.ru"
|
hostname := "wmconvirus.narod.ru"
|
||||||
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Errorf("Error while matching host %s: %s", hostname, err)
|
b.Errorf("Error while matching host %s: %s", hostname, err)
|
||||||
}
|
}
|
||||||
if !ret.IsFiltered {
|
if !res.IsFiltered {
|
||||||
b.Errorf("Expected hostname %s to match", hostname)
|
b.Errorf("Expected hostname %s to match", hostname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -567,11 +580,11 @@ func BenchmarkSafeBrowsingParallel(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
hostname := "wmconvirus.narod.ru"
|
hostname := "wmconvirus.narod.ru"
|
||||||
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
res, err := d.CheckHost(hostname, dns.TypeA, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Errorf("Error while matching host %s: %s", hostname, err)
|
b.Errorf("Error while matching host %s: %s", hostname, err)
|
||||||
}
|
}
|
||||||
if !ret.IsFiltered {
|
if !res.IsFiltered {
|
||||||
b.Errorf("Expected hostname %s to match", hostname)
|
b.Errorf("Expected hostname %s to match", hostname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package dnsfilter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/AdguardTeam/urlfilter/rules"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSRewriteResult is the result of application of $dnsrewrite rules.
|
||||||
|
type DNSRewriteResult struct {
|
||||||
|
RCode rules.RCode `json:",omitempty"`
|
||||||
|
Response DNSRewriteResultResponse `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSRewriteResultResponse is the collection of DNS response records
|
||||||
|
// the server returns.
|
||||||
|
type DNSRewriteResultResponse map[rules.RRType][]rules.RRValue
|
||||||
|
|
||||||
|
// processDNSRewrites processes DNS rewrite rules in dnsr. It returns
|
||||||
|
// an empty result if dnsr is empty. Otherwise, the result will have
|
||||||
|
// either CanonName or DNSRewriteResult set.
|
||||||
|
func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) {
|
||||||
|
if len(dnsr) == 0 {
|
||||||
|
return Result{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rules []*ResultRule
|
||||||
|
dnsrr := &DNSRewriteResult{
|
||||||
|
Response: DNSRewriteResultResponse{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nr := range dnsr {
|
||||||
|
dr := nr.DNSRewrite
|
||||||
|
if dr.NewCNAME != "" {
|
||||||
|
// NewCNAME rules have a higher priority than
|
||||||
|
// the other rules.
|
||||||
|
rules := []*ResultRule{{
|
||||||
|
FilterListID: int64(nr.GetFilterListID()),
|
||||||
|
Text: nr.RuleText,
|
||||||
|
}}
|
||||||
|
|
||||||
|
return Result{
|
||||||
|
Reason: DNSRewriteRule,
|
||||||
|
Rules: rules,
|
||||||
|
CanonName: dr.NewCNAME,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch dr.RCode {
|
||||||
|
case dns.RcodeSuccess:
|
||||||
|
dnsrr.RCode = dr.RCode
|
||||||
|
dnsrr.Response[dr.RRType] = append(dnsrr.Response[dr.RRType], dr.Value)
|
||||||
|
rules = append(rules, &ResultRule{
|
||||||
|
FilterListID: int64(nr.GetFilterListID()),
|
||||||
|
Text: nr.RuleText,
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
// RcodeRefused and other such codes have higher
|
||||||
|
// priority. Return immediately.
|
||||||
|
rules := []*ResultRule{{
|
||||||
|
FilterListID: int64(nr.GetFilterListID()),
|
||||||
|
Text: nr.RuleText,
|
||||||
|
}}
|
||||||
|
dnsrr = &DNSRewriteResult{
|
||||||
|
RCode: dr.RCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result{
|
||||||
|
Reason: DNSRewriteRule,
|
||||||
|
Rules: rules,
|
||||||
|
DNSRewriteResult: dnsrr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result{
|
||||||
|
Reason: DNSRewriteRule,
|
||||||
|
Rules: rules,
|
||||||
|
DNSRewriteResult: dnsrr,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
package dnsfilter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
|
||||||
|
const text = `
|
||||||
|
|cname^$dnsrewrite=new_cname
|
||||||
|
|
||||||
|
|a_record^$dnsrewrite=127.0.0.1
|
||||||
|
|
||||||
|
|aaaa_record^$dnsrewrite=::1
|
||||||
|
|
||||||
|
|txt_record^$dnsrewrite=NOERROR;TXT;hello_world
|
||||||
|
|
||||||
|
|refused^$dnsrewrite=REFUSED
|
||||||
|
|
||||||
|
|a_records^$dnsrewrite=127.0.0.1
|
||||||
|
|a_records^$dnsrewrite=127.0.0.2
|
||||||
|
|
||||||
|
|aaaa_records^$dnsrewrite=::1
|
||||||
|
|aaaa_records^$dnsrewrite=::2
|
||||||
|
|
||||||
|
|disable_one^$dnsrewrite=127.0.0.1
|
||||||
|
|disable_one^$dnsrewrite=127.0.0.2
|
||||||
|
@@||disable_one^$dnsrewrite=127.0.0.1
|
||||||
|
|
||||||
|
|disable_cname^$dnsrewrite=127.0.0.1
|
||||||
|
|disable_cname^$dnsrewrite=new_cname
|
||||||
|
@@||disable_cname^$dnsrewrite=new_cname
|
||||||
|
|
||||||
|
|disable_cname_many^$dnsrewrite=127.0.0.1
|
||||||
|
|disable_cname_many^$dnsrewrite=new_cname_1
|
||||||
|
|disable_cname_many^$dnsrewrite=new_cname_2
|
||||||
|
@@||disable_cname_many^$dnsrewrite=new_cname_1
|
||||||
|
|
||||||
|
|disable_all^$dnsrewrite=127.0.0.1
|
||||||
|
|disable_all^$dnsrewrite=127.0.0.2
|
||||||
|
@@||disable_all^$dnsrewrite
|
||||||
|
`
|
||||||
|
f := NewForTest(nil, []Filter{{ID: 0, Data: []byte(text)}})
|
||||||
|
setts := &RequestFilteringSettings{
|
||||||
|
FilteringEnabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv4p1 := net.IPv4(127, 0, 0, 1)
|
||||||
|
ipv4p2 := net.IPv4(127, 0, 0, 2)
|
||||||
|
ipv6p1 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
||||||
|
ipv6p2 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
|
||||||
|
|
||||||
|
t.Run("cname", func(t *testing.T) {
|
||||||
|
dtyp := dns.TypeA
|
||||||
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
|
res, err := f.CheckHostRules(host, dtyp, setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "new_cname", res.CanonName)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("a_record", func(t *testing.T) {
|
||||||
|
dtyp := dns.TypeA
|
||||||
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
|
res, err := f.CheckHostRules(host, dtyp, setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
|
||||||
|
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
|
||||||
|
if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 1) {
|
||||||
|
assert.Equal(t, ipv4p1, ipVals[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("aaaa_record", func(t *testing.T) {
|
||||||
|
dtyp := dns.TypeAAAA
|
||||||
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
|
res, err := f.CheckHostRules(host, dtyp, setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
|
||||||
|
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
|
||||||
|
if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 1) {
|
||||||
|
assert.Equal(t, ipv6p1, ipVals[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("txt_record", func(t *testing.T) {
|
||||||
|
dtyp := dns.TypeTXT
|
||||||
|
host := path.Base(t.Name())
|
||||||
|
res, err := f.CheckHostRules(host, dtyp, setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
|
||||||
|
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
|
||||||
|
if strVals := dnsrr.Response[dtyp]; assert.Len(t, strVals, 1) {
|
||||||
|
assert.Equal(t, "hello_world", strVals[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("refused", func(t *testing.T) {
|
||||||
|
host := path.Base(t.Name())
|
||||||
|
res, err := f.CheckHostRules(host, dns.TypeA, setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
|
||||||
|
assert.Equal(t, dns.RcodeRefused, dnsrr.RCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("a_records", func(t *testing.T) {
|
||||||
|
dtyp := dns.TypeA
|
||||||
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
|
res, err := f.CheckHostRules(host, dtyp, setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
|
||||||
|
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
|
||||||
|
if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 2) {
|
||||||
|
assert.Equal(t, ipv4p1, ipVals[0])
|
||||||
|
assert.Equal(t, ipv4p2, ipVals[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("aaaa_records", func(t *testing.T) {
|
||||||
|
dtyp := dns.TypeAAAA
|
||||||
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
|
res, err := f.CheckHostRules(host, dtyp, setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
|
||||||
|
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
|
||||||
|
if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 2) {
|
||||||
|
assert.Equal(t, ipv6p1, ipVals[0])
|
||||||
|
assert.Equal(t, ipv6p2, ipVals[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("disable_one", func(t *testing.T) {
|
||||||
|
dtyp := dns.TypeA
|
||||||
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
|
res, err := f.CheckHostRules(host, dtyp, setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
|
||||||
|
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
|
||||||
|
if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 1) {
|
||||||
|
assert.Equal(t, ipv4p2, ipVals[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("disable_cname", func(t *testing.T) {
|
||||||
|
dtyp := dns.TypeA
|
||||||
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
|
res, err := f.CheckHostRules(host, dtyp, setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "", res.CanonName)
|
||||||
|
|
||||||
|
if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) {
|
||||||
|
assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode)
|
||||||
|
if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 1) {
|
||||||
|
assert.Equal(t, ipv4p1, ipVals[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("disable_cname_many", func(t *testing.T) {
|
||||||
|
dtyp := dns.TypeA
|
||||||
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
|
res, err := f.CheckHostRules(host, dtyp, setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "new_cname_2", res.CanonName)
|
||||||
|
assert.Nil(t, res.DNSRewriteResult)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("disable_all", func(t *testing.T) {
|
||||||
|
dtyp := dns.TypeA
|
||||||
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
|
res, err := f.CheckHostRules(host, dtyp, setts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "", res.CanonName)
|
||||||
|
assert.Len(t, res.Rules, 0)
|
||||||
|
})
|
||||||
|
}
|
|
@ -95,7 +95,7 @@ func (r *RewriteEntry) prepare() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) prepareRewrites() {
|
func (d *DNSFilter) prepareRewrites() {
|
||||||
for i := range d.Rewrites {
|
for i := range d.Rewrites {
|
||||||
d.Rewrites[i].prepare()
|
d.Rewrites[i].prepare()
|
||||||
}
|
}
|
||||||
|
@ -148,8 +148,7 @@ type rewriteEntryJSON struct {
|
||||||
Answer string `json:"answer"`
|
Answer string `json:"answer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
arr := []*rewriteEntryJSON{}
|
arr := []*rewriteEntryJSON{}
|
||||||
|
|
||||||
d.confLock.Lock()
|
d.confLock.Lock()
|
||||||
|
@ -170,8 +169,7 @@ func (d *Dnsfilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
jsent := rewriteEntryJSON{}
|
jsent := rewriteEntryJSON{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&jsent)
|
err := json.NewDecoder(r.Body).Decode(&jsent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -193,8 +191,7 @@ func (d *Dnsfilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
jsent := rewriteEntryJSON{}
|
jsent := rewriteEntryJSON{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&jsent)
|
err := json.NewDecoder(r.Body).Decode(&jsent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -221,7 +218,7 @@ func (d *Dnsfilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request)
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) registerRewritesHandlers() {
|
func (d *DNSFilter) registerRewritesHandlers() {
|
||||||
d.Config.HTTPRegister("GET", "/control/rewrite/list", d.handleRewriteList)
|
d.Config.HTTPRegister("GET", "/control/rewrite/list", d.handleRewriteList)
|
||||||
d.Config.HTTPRegister("POST", "/control/rewrite/add", d.handleRewriteAdd)
|
d.Config.HTTPRegister("POST", "/control/rewrite/add", d.handleRewriteAdd)
|
||||||
d.Config.HTTPRegister("POST", "/control/rewrite/delete", d.handleRewriteDelete)
|
d.Config.HTTPRegister("POST", "/control/rewrite/delete", d.handleRewriteDelete)
|
||||||
|
|
|
@ -9,16 +9,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRewrites(t *testing.T) {
|
func TestRewrites(t *testing.T) {
|
||||||
d := Dnsfilter{}
|
d := DNSFilter{}
|
||||||
// CNAME, A, AAAA
|
// CNAME, A, AAAA
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
RewriteEntry{"somecname", "somehost.com", 0, nil},
|
{"somecname", "somehost.com", 0, nil},
|
||||||
RewriteEntry{"somehost.com", "0.0.0.0", 0, nil},
|
{"somehost.com", "0.0.0.0", 0, nil},
|
||||||
|
|
||||||
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
|
{"host.com", "1.2.3.4", 0, nil},
|
||||||
RewriteEntry{"host.com", "1.2.3.5", 0, nil},
|
{"host.com", "1.2.3.5", 0, nil},
|
||||||
RewriteEntry{"host.com", "1:2:3::4", 0, nil},
|
{"host.com", "1:2:3::4", 0, nil},
|
||||||
RewriteEntry{"www.host.com", "host.com", 0, nil},
|
{"www.host.com", "host.com", 0, nil},
|
||||||
}
|
}
|
||||||
d.prepareRewrites()
|
d.prepareRewrites()
|
||||||
r := d.processRewrites("host2.com", dns.TypeA)
|
r := d.processRewrites("host2.com", dns.TypeA)
|
||||||
|
@ -39,8 +39,8 @@ func TestRewrites(t *testing.T) {
|
||||||
|
|
||||||
// wildcard
|
// wildcard
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
|
{"host.com", "1.2.3.4", 0, nil},
|
||||||
RewriteEntry{"*.host.com", "1.2.3.5", 0, nil},
|
{"*.host.com", "1.2.3.5", 0, nil},
|
||||||
}
|
}
|
||||||
d.prepareRewrites()
|
d.prepareRewrites()
|
||||||
r = d.processRewrites("host.com", dns.TypeA)
|
r = d.processRewrites("host.com", dns.TypeA)
|
||||||
|
@ -56,8 +56,8 @@ func TestRewrites(t *testing.T) {
|
||||||
|
|
||||||
// override a wildcard
|
// override a wildcard
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
RewriteEntry{"a.host.com", "1.2.3.4", 0, nil},
|
{"a.host.com", "1.2.3.4", 0, nil},
|
||||||
RewriteEntry{"*.host.com", "1.2.3.5", 0, nil},
|
{"*.host.com", "1.2.3.5", 0, nil},
|
||||||
}
|
}
|
||||||
d.prepareRewrites()
|
d.prepareRewrites()
|
||||||
r = d.processRewrites("a.host.com", dns.TypeA)
|
r = d.processRewrites("a.host.com", dns.TypeA)
|
||||||
|
@ -67,8 +67,8 @@ func TestRewrites(t *testing.T) {
|
||||||
|
|
||||||
// wildcard + CNAME
|
// wildcard + CNAME
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
|
{"host.com", "1.2.3.4", 0, nil},
|
||||||
RewriteEntry{"*.host.com", "host.com", 0, nil},
|
{"*.host.com", "host.com", 0, nil},
|
||||||
}
|
}
|
||||||
d.prepareRewrites()
|
d.prepareRewrites()
|
||||||
r = d.processRewrites("www.host.com", dns.TypeA)
|
r = d.processRewrites("www.host.com", dns.TypeA)
|
||||||
|
@ -78,9 +78,9 @@ func TestRewrites(t *testing.T) {
|
||||||
|
|
||||||
// 2 CNAMEs
|
// 2 CNAMEs
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
RewriteEntry{"b.host.com", "a.host.com", 0, nil},
|
{"b.host.com", "a.host.com", 0, nil},
|
||||||
RewriteEntry{"a.host.com", "host.com", 0, nil},
|
{"a.host.com", "host.com", 0, nil},
|
||||||
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
|
{"host.com", "1.2.3.4", 0, nil},
|
||||||
}
|
}
|
||||||
d.prepareRewrites()
|
d.prepareRewrites()
|
||||||
r = d.processRewrites("b.host.com", dns.TypeA)
|
r = d.processRewrites("b.host.com", dns.TypeA)
|
||||||
|
@ -91,9 +91,9 @@ func TestRewrites(t *testing.T) {
|
||||||
|
|
||||||
// 2 CNAMEs + wildcard
|
// 2 CNAMEs + wildcard
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
RewriteEntry{"b.host.com", "a.host.com", 0, nil},
|
{"b.host.com", "a.host.com", 0, nil},
|
||||||
RewriteEntry{"a.host.com", "x.somehost.com", 0, nil},
|
{"a.host.com", "x.somehost.com", 0, nil},
|
||||||
RewriteEntry{"*.somehost.com", "1.2.3.4", 0, nil},
|
{"*.somehost.com", "1.2.3.4", 0, nil},
|
||||||
}
|
}
|
||||||
d.prepareRewrites()
|
d.prepareRewrites()
|
||||||
r = d.processRewrites("b.host.com", dns.TypeA)
|
r = d.processRewrites("b.host.com", dns.TypeA)
|
||||||
|
@ -104,12 +104,12 @@ func TestRewrites(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewritesLevels(t *testing.T) {
|
func TestRewritesLevels(t *testing.T) {
|
||||||
d := Dnsfilter{}
|
d := DNSFilter{}
|
||||||
// exact host, wildcard L2, wildcard L3
|
// exact host, wildcard L2, wildcard L3
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
RewriteEntry{"host.com", "1.1.1.1", 0, nil},
|
{"host.com", "1.1.1.1", 0, nil},
|
||||||
RewriteEntry{"*.host.com", "2.2.2.2", 0, nil},
|
{"*.host.com", "2.2.2.2", 0, nil},
|
||||||
RewriteEntry{"*.sub.host.com", "3.3.3.3", 0, nil},
|
{"*.sub.host.com", "3.3.3.3", 0, nil},
|
||||||
}
|
}
|
||||||
d.prepareRewrites()
|
d.prepareRewrites()
|
||||||
|
|
||||||
|
@ -133,11 +133,11 @@ func TestRewritesLevels(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewritesExceptionCNAME(t *testing.T) {
|
func TestRewritesExceptionCNAME(t *testing.T) {
|
||||||
d := Dnsfilter{}
|
d := DNSFilter{}
|
||||||
// wildcard; exception for a sub-domain
|
// wildcard; exception for a sub-domain
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
RewriteEntry{"*.host.com", "2.2.2.2", 0, nil},
|
{"*.host.com", "2.2.2.2", 0, nil},
|
||||||
RewriteEntry{"sub.host.com", "sub.host.com", 0, nil},
|
{"sub.host.com", "sub.host.com", 0, nil},
|
||||||
}
|
}
|
||||||
d.prepareRewrites()
|
d.prepareRewrites()
|
||||||
|
|
||||||
|
@ -153,11 +153,11 @@ func TestRewritesExceptionCNAME(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewritesExceptionWC(t *testing.T) {
|
func TestRewritesExceptionWC(t *testing.T) {
|
||||||
d := Dnsfilter{}
|
d := DNSFilter{}
|
||||||
// wildcard; exception for a sub-wildcard
|
// wildcard; exception for a sub-wildcard
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
RewriteEntry{"*.host.com", "2.2.2.2", 0, nil},
|
{"*.host.com", "2.2.2.2", 0, nil},
|
||||||
RewriteEntry{"*.sub.host.com", "*.sub.host.com", 0, nil},
|
{"*.sub.host.com", "*.sub.host.com", 0, nil},
|
||||||
}
|
}
|
||||||
d.prepareRewrites()
|
d.prepareRewrites()
|
||||||
|
|
||||||
|
@ -173,14 +173,14 @@ func TestRewritesExceptionWC(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewritesExceptionIP(t *testing.T) {
|
func TestRewritesExceptionIP(t *testing.T) {
|
||||||
d := Dnsfilter{}
|
d := DNSFilter{}
|
||||||
// exception for AAAA record
|
// exception for AAAA record
|
||||||
d.Rewrites = []RewriteEntry{
|
d.Rewrites = []RewriteEntry{
|
||||||
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
|
{"host.com", "1.2.3.4", 0, nil},
|
||||||
RewriteEntry{"host.com", "AAAA", 0, nil},
|
{"host.com", "AAAA", 0, nil},
|
||||||
RewriteEntry{"host2.com", "::1", 0, nil},
|
{"host2.com", "::1", 0, nil},
|
||||||
RewriteEntry{"host2.com", "A", 0, nil},
|
{"host2.com", "A", 0, nil},
|
||||||
RewriteEntry{"host3.com", "A", 0, nil},
|
{"host3.com", "A", 0, nil},
|
||||||
}
|
}
|
||||||
d.prepareRewrites()
|
d.prepareRewrites()
|
||||||
|
|
||||||
|
|
|
@ -1,148 +0,0 @@
|
||||||
package dnsfilter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/gob"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/cache"
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
expire byte[4]
|
|
||||||
res Result
|
|
||||||
*/
|
|
||||||
func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) int {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
|
|
||||||
exp := make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint32(exp, uint32(expire))
|
|
||||||
_, _ = buf.Write(exp)
|
|
||||||
|
|
||||||
enc := gob.NewEncoder(&buf)
|
|
||||||
err := enc.Encode(res)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("gob.Encode(): %s", err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
val := buf.Bytes()
|
|
||||||
_ = cache.Set([]byte(host), val)
|
|
||||||
return len(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCachedResult(cache cache.Cache, host string) (Result, bool) {
|
|
||||||
data := cache.Get([]byte(host))
|
|
||||||
if data == nil {
|
|
||||||
return Result{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
exp := int(binary.BigEndian.Uint32(data[:4]))
|
|
||||||
if exp <= int(time.Now().Unix()) {
|
|
||||||
cache.Del([]byte(host))
|
|
||||||
return Result{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
buf.Write(data[4:])
|
|
||||||
dec := gob.NewDecoder(&buf)
|
|
||||||
r := Result{}
|
|
||||||
err := dec.Decode(&r)
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("gob.Decode(): %s", err)
|
|
||||||
return Result{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SafeSearchDomain returns replacement address for search engine
|
|
||||||
func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
|
|
||||||
val, ok := safeSearchDomains[host]
|
|
||||||
return val, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
|
|
||||||
if log.GetLevel() >= log.DEBUG {
|
|
||||||
timer := log.StartTimer()
|
|
||||||
defer timer.LogElapsed("SafeSearch: lookup for %s", host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check cache. Return cached result if it was found
|
|
||||||
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, host)
|
|
||||||
if isFound {
|
|
||||||
// atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1)
|
|
||||||
log.Tracef("SafeSearch: found in cache: %s", host)
|
|
||||||
return cachedValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
safeHost, ok := d.SafeSearchDomain(host)
|
|
||||||
if !ok {
|
|
||||||
return Result{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
res := Result{IsFiltered: true, Reason: FilteredSafeSearch}
|
|
||||||
if ip := net.ParseIP(safeHost); ip != nil {
|
|
||||||
res.IP = ip
|
|
||||||
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
|
||||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO this address should be resolved with upstream that was configured in dnsforward
|
|
||||||
addrs, err := net.LookupIP(safeHost)
|
|
||||||
if err != nil {
|
|
||||||
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
|
|
||||||
return Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, i := range addrs {
|
|
||||||
if ipv4 := i.To4(); ipv4 != nil {
|
|
||||||
res.IP = ipv4
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res.IP) == 0 {
|
|
||||||
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache result
|
|
||||||
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
|
||||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
|
|
||||||
d.Config.SafeSearchEnabled = true
|
|
||||||
d.Config.ConfigModified()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
|
|
||||||
d.Config.SafeSearchEnabled = false
|
|
||||||
d.Config.ConfigModified()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"enabled": d.Config.SafeSearchEnabled,
|
|
||||||
}
|
|
||||||
jsonVal, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_, err = w.Write(jsonVal)
|
|
||||||
if err != nil {
|
|
||||||
httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,3 @@
|
||||||
// Safe Browsing, Parental Control
|
|
||||||
|
|
||||||
package dnsfilter
|
package dnsfilter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -22,6 +20,8 @@ import (
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Safe browsing and parental control methods.
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dnsTimeout = 3 * time.Second
|
dnsTimeout = 3 * time.Second
|
||||||
defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query`
|
defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query`
|
||||||
|
@ -30,7 +30,7 @@ const (
|
||||||
pcTXTSuffix = `pc.dns.adguard.com.`
|
pcTXTSuffix = `pc.dns.adguard.com.`
|
||||||
)
|
)
|
||||||
|
|
||||||
func (d *Dnsfilter) initSecurityServices() error {
|
func (d *DNSFilter) initSecurityServices() error {
|
||||||
var err error
|
var err error
|
||||||
d.safeBrowsingServer = defaultSafebrowsingServer
|
d.safeBrowsingServer = defaultSafebrowsingServer
|
||||||
d.parentalServer = defaultParentalServer
|
d.parentalServer = defaultParentalServer
|
||||||
|
@ -71,31 +71,35 @@ func (c *sbCtx) setCache(prefix, hashes []byte) {
|
||||||
log.Debug("%s: stored in cache: %v", c.svc, prefix)
|
log.Debug("%s: stored in cache: %v", c.svc, prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// findInHash returns 32-byte hash if it's found in hashToHost.
|
||||||
|
func (c *sbCtx) findInHash(val []byte) (hash32 [32]byte, found bool) {
|
||||||
|
for i := 4; i < len(val); i += 32 {
|
||||||
|
hash := val[i : i+32]
|
||||||
|
|
||||||
|
copy(hash32[:], hash[0:32])
|
||||||
|
|
||||||
|
_, found = c.hashToHost[hash32]
|
||||||
|
if found {
|
||||||
|
return hash32, found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [32]byte{}, false
|
||||||
|
}
|
||||||
|
|
||||||
func (c *sbCtx) getCached() int {
|
func (c *sbCtx) getCached() int {
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
hashesToRequest := map[[32]byte]string{}
|
hashesToRequest := map[[32]byte]string{}
|
||||||
for k, v := range c.hashToHost {
|
for k, v := range c.hashToHost {
|
||||||
key := k[0:2]
|
key := k[0:2]
|
||||||
val := c.cache.Get(key)
|
val := c.cache.Get(key)
|
||||||
if val != nil {
|
if val == nil || now >= int64(binary.BigEndian.Uint32(val)) {
|
||||||
expire := binary.BigEndian.Uint32(val)
|
|
||||||
if now >= int64(expire) {
|
|
||||||
val = nil
|
|
||||||
} else {
|
|
||||||
for i := 4; i < len(val); i += 32 {
|
|
||||||
hash := val[i : i+32]
|
|
||||||
var hash32 [32]byte
|
|
||||||
copy(hash32[:], hash[0:32])
|
|
||||||
_, found := c.hashToHost[hash32]
|
|
||||||
if found {
|
|
||||||
log.Debug("%s: found in cache: %s: blocked by %v", c.svc, c.host, hash32)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if val == nil {
|
|
||||||
hashesToRequest[k] = v
|
hashesToRequest[k] = v
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hash32, found := c.findInHash(val); found {
|
||||||
|
log.Debug("%s: found in cache: %s: blocked by %v", c.svc, c.host, hash32)
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,106 +258,75 @@ func (c *sbCtx) storeCache(hashes [][]byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disabling "dupl": the algorithm of SB/PC is similar, but it uses different data
|
func check(c *sbCtx, r Result, u upstream.Upstream) (Result, error) {
|
||||||
// nolint:dupl
|
c.hashToHost = hostnameToHashes(c.host)
|
||||||
func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
|
switch c.getCached() {
|
||||||
|
case -1:
|
||||||
|
return Result{}, nil
|
||||||
|
case 1:
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
question := c.getQuestion()
|
||||||
|
|
||||||
|
log.Tracef("%s: checking %s: %s", c.svc, c.host, question)
|
||||||
|
req := (&dns.Msg{}).SetQuestion(question, dns.TypeTXT)
|
||||||
|
|
||||||
|
resp, err := u.Exchange(req)
|
||||||
|
if err != nil {
|
||||||
|
return Result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
matched, receivedHashes := c.processTXT(resp)
|
||||||
|
|
||||||
|
c.storeCache(receivedHashes)
|
||||||
|
if matched {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSFilter) checkSafeBrowsing(host string) (Result, error) {
|
||||||
if log.GetLevel() >= log.DEBUG {
|
if log.GetLevel() >= log.DEBUG {
|
||||||
timer := log.StartTimer()
|
timer := log.StartTimer()
|
||||||
defer timer.LogElapsed("SafeBrowsing lookup for %s", host)
|
defer timer.LogElapsed("SafeBrowsing lookup for %s", host)
|
||||||
}
|
}
|
||||||
|
ctx := &sbCtx{
|
||||||
result := Result{}
|
host: host,
|
||||||
hashes := hostnameToHashes(host)
|
svc: "SafeBrowsing",
|
||||||
|
cache: gctx.safebrowsingCache,
|
||||||
c := &sbCtx{
|
cacheTime: d.Config.CacheTime,
|
||||||
host: host,
|
|
||||||
svc: "SafeBrowsing",
|
|
||||||
hashToHost: hashes,
|
|
||||||
cache: gctx.safebrowsingCache,
|
|
||||||
cacheTime: d.Config.CacheTime,
|
|
||||||
}
|
}
|
||||||
|
res := Result{
|
||||||
// check cache
|
IsFiltered: true,
|
||||||
match := c.getCached()
|
Reason: FilteredSafeBrowsing,
|
||||||
if match < 0 {
|
Rules: []*ResultRule{{
|
||||||
return result, nil
|
Text: "adguard-malware-shavar",
|
||||||
} else if match > 0 {
|
}},
|
||||||
result.IsFiltered = true
|
|
||||||
result.Reason = FilteredSafeBrowsing
|
|
||||||
result.Rule = "adguard-malware-shavar"
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
return check(ctx, res, d.safeBrowsingUpstream)
|
||||||
question := c.getQuestion()
|
|
||||||
log.Tracef("SafeBrowsing: checking %s: %s", host, question)
|
|
||||||
|
|
||||||
req := dns.Msg{}
|
|
||||||
req.SetQuestion(question, dns.TypeTXT)
|
|
||||||
resp, err := d.safeBrowsingUpstream.Exchange(&req)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
matched, receivedHashes := c.processTXT(resp)
|
|
||||||
if matched {
|
|
||||||
result.IsFiltered = true
|
|
||||||
result.Reason = FilteredSafeBrowsing
|
|
||||||
result.Rule = "adguard-malware-shavar"
|
|
||||||
}
|
|
||||||
c.storeCache(receivedHashes)
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disabling "dupl": the algorithm of SB/PC is similar, but it uses different data
|
func (d *DNSFilter) checkParental(host string) (Result, error) {
|
||||||
// nolint:dupl
|
|
||||||
func (d *Dnsfilter) checkParental(host string) (Result, error) {
|
|
||||||
if log.GetLevel() >= log.DEBUG {
|
if log.GetLevel() >= log.DEBUG {
|
||||||
timer := log.StartTimer()
|
timer := log.StartTimer()
|
||||||
defer timer.LogElapsed("Parental lookup for %s", host)
|
defer timer.LogElapsed("Parental lookup for %s", host)
|
||||||
}
|
}
|
||||||
|
ctx := &sbCtx{
|
||||||
result := Result{}
|
host: host,
|
||||||
hashes := hostnameToHashes(host)
|
svc: "Parental",
|
||||||
|
cache: gctx.parentalCache,
|
||||||
c := &sbCtx{
|
cacheTime: d.Config.CacheTime,
|
||||||
host: host,
|
|
||||||
svc: "Parental",
|
|
||||||
hashToHost: hashes,
|
|
||||||
cache: gctx.parentalCache,
|
|
||||||
cacheTime: d.Config.CacheTime,
|
|
||||||
}
|
}
|
||||||
|
res := Result{
|
||||||
// check cache
|
IsFiltered: true,
|
||||||
match := c.getCached()
|
Reason: FilteredParental,
|
||||||
if match < 0 {
|
Rules: []*ResultRule{{
|
||||||
return result, nil
|
Text: "parental CATEGORY_BLACKLISTED",
|
||||||
} else if match > 0 {
|
}},
|
||||||
result.IsFiltered = true
|
|
||||||
result.Reason = FilteredParental
|
|
||||||
result.Rule = "parental CATEGORY_BLACKLISTED"
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
return check(ctx, res, d.parentalUpstream)
|
||||||
question := c.getQuestion()
|
|
||||||
log.Tracef("Parental: checking %s: %s", host, question)
|
|
||||||
|
|
||||||
req := dns.Msg{}
|
|
||||||
req.SetQuestion(question, dns.TypeTXT)
|
|
||||||
resp, err := d.parentalUpstream.Exchange(&req)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
matched, receivedHashes := c.processTXT(resp)
|
|
||||||
if matched {
|
|
||||||
result.IsFiltered = true
|
|
||||||
result.Reason = FilteredParental
|
|
||||||
result.Rule = "parental CATEGORY_BLACKLISTED"
|
|
||||||
}
|
|
||||||
c.storeCache(receivedHashes)
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) {
|
func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) {
|
||||||
|
@ -362,17 +335,17 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string,
|
||||||
http.Error(w, text, code)
|
http.Error(w, text, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Config.SafeBrowsingEnabled = true
|
d.Config.SafeBrowsingEnabled = true
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Config.SafeBrowsingEnabled = false
|
d.Config.SafeBrowsingEnabled = false
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"enabled": d.Config.SafeBrowsingEnabled,
|
"enabled": d.Config.SafeBrowsingEnabled,
|
||||||
}
|
}
|
||||||
|
@ -389,17 +362,17 @@ func (d *Dnsfilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Requ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Config.ParentalEnabled = true
|
d.Config.ParentalEnabled = true
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleParentalDisable(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleParentalDisable(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Config.ParentalEnabled = false
|
d.Config.ParentalEnabled = false
|
||||||
d.Config.ConfigModified()
|
d.Config.ConfigModified()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) {
|
func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"enabled": d.Config.ParentalEnabled,
|
"enabled": d.Config.ParentalEnabled,
|
||||||
}
|
}
|
||||||
|
@ -417,7 +390,7 @@ func (d *Dnsfilter) handleParentalStatus(w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) registerSecurityHandlers() {
|
func (d *DNSFilter) registerSecurityHandlers() {
|
||||||
d.Config.HTTPRegister("POST", "/control/safebrowsing/enable", d.handleSafeBrowsingEnable)
|
d.Config.HTTPRegister("POST", "/control/safebrowsing/enable", d.handleSafeBrowsingEnable)
|
||||||
d.Config.HTTPRegister("POST", "/control/safebrowsing/disable", d.handleSafeBrowsingDisable)
|
d.Config.HTTPRegister("POST", "/control/safebrowsing/disable", d.handleSafeBrowsingDisable)
|
||||||
d.Config.HTTPRegister("GET", "/control/safebrowsing/status", d.handleSafeBrowsingStatus)
|
d.Config.HTTPRegister("GET", "/control/safebrowsing/status", d.handleSafeBrowsingStatus)
|
|
@ -1,5 +1,155 @@
|
||||||
package dnsfilter
|
package dnsfilter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/cache"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
expire byte[4]
|
||||||
|
res Result
|
||||||
|
*/
|
||||||
|
func (d *DNSFilter) setCacheResult(cache cache.Cache, host string, res Result) int {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
|
||||||
|
exp := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(exp, uint32(expire))
|
||||||
|
_, _ = buf.Write(exp)
|
||||||
|
|
||||||
|
enc := gob.NewEncoder(&buf)
|
||||||
|
err := enc.Encode(res)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("gob.Encode(): %s", err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
val := buf.Bytes()
|
||||||
|
_ = cache.Set([]byte(host), val)
|
||||||
|
return len(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCachedResult(cache cache.Cache, host string) (Result, bool) {
|
||||||
|
data := cache.Get([]byte(host))
|
||||||
|
if data == nil {
|
||||||
|
return Result{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := int(binary.BigEndian.Uint32(data[:4]))
|
||||||
|
if exp <= int(time.Now().Unix()) {
|
||||||
|
cache.Del([]byte(host))
|
||||||
|
return Result{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.Write(data[4:])
|
||||||
|
dec := gob.NewDecoder(&buf)
|
||||||
|
r := Result{}
|
||||||
|
err := dec.Decode(&r)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("gob.Decode(): %s", err)
|
||||||
|
return Result{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeSearchDomain returns replacement address for search engine
|
||||||
|
func (d *DNSFilter) SafeSearchDomain(host string) (string, bool) {
|
||||||
|
val, ok := safeSearchDomains[host]
|
||||||
|
return val, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSFilter) checkSafeSearch(host string) (Result, error) {
|
||||||
|
if log.GetLevel() >= log.DEBUG {
|
||||||
|
timer := log.StartTimer()
|
||||||
|
defer timer.LogElapsed("SafeSearch: lookup for %s", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cache. Return cached result if it was found
|
||||||
|
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, host)
|
||||||
|
if isFound {
|
||||||
|
// atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1)
|
||||||
|
log.Tracef("SafeSearch: found in cache: %s", host)
|
||||||
|
return cachedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
safeHost, ok := d.SafeSearchDomain(host)
|
||||||
|
if !ok {
|
||||||
|
return Result{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := Result{
|
||||||
|
IsFiltered: true,
|
||||||
|
Reason: FilteredSafeSearch,
|
||||||
|
Rules: []*ResultRule{{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip := net.ParseIP(safeHost); ip != nil {
|
||||||
|
res.Rules[0].IP = ip
|
||||||
|
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||||
|
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this address should be resolved with upstream that was configured in dnsforward
|
||||||
|
ips, err := net.LookupIP(safeHost)
|
||||||
|
if err != nil {
|
||||||
|
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
|
||||||
|
return Result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if ipv4 := ip.To4(); ipv4 != nil {
|
||||||
|
res.Rules[0].IP = ipv4
|
||||||
|
|
||||||
|
l := d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||||
|
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, l)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSFilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
|
d.Config.SafeSearchEnabled = true
|
||||||
|
d.Config.ConfigModified()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
|
||||||
|
d.Config.SafeSearchEnabled = false
|
||||||
|
d.Config.ConfigModified()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"enabled": d.Config.SafeSearchEnabled,
|
||||||
|
}
|
||||||
|
jsonVal, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, err = w.Write(jsonVal)
|
||||||
|
if err != nil {
|
||||||
|
httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var safeSearchDomains = map[string]string{
|
var safeSearchDomains = map[string]string{
|
||||||
"yandex.com": "213.180.193.56",
|
"yandex.com": "213.180.193.56",
|
||||||
"yandex.ru": "213.180.193.56",
|
"yandex.ru": "213.180.193.56",
|
||||||
|
|
|
@ -50,7 +50,8 @@ func TestIsBlockedIPDisallowed(t *testing.T) {
|
||||||
|
|
||||||
func TestIsBlockedIPBlockedDomain(t *testing.T) {
|
func TestIsBlockedIPBlockedDomain(t *testing.T) {
|
||||||
a := &accessCtx{}
|
a := &accessCtx{}
|
||||||
assert.True(t, a.Init(nil, nil, []string{"host1",
|
assert.True(t, a.Init(nil, nil, []string{
|
||||||
|
"host1",
|
||||||
"host2",
|
"host2",
|
||||||
"*.host.com",
|
"*.host.com",
|
||||||
"||host3.com^",
|
"||host3.com^",
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/ameshkov/dnscrypt/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FilteringConfig represents the DNS filtering configuration of AdGuard Home
|
// FilteringConfig represents the DNS filtering configuration of AdGuard Home
|
||||||
|
@ -94,19 +95,33 @@ type FilteringConfig struct {
|
||||||
type TLSConfig struct {
|
type TLSConfig struct {
|
||||||
TLSListenAddr *net.TCPAddr `yaml:"-" json:"-"`
|
TLSListenAddr *net.TCPAddr `yaml:"-" json:"-"`
|
||||||
QUICListenAddr *net.UDPAddr `yaml:"-" json:"-"`
|
QUICListenAddr *net.UDPAddr `yaml:"-" json:"-"`
|
||||||
StrictSNICheck bool `yaml:"strict_sni_check" json:"-"` // Reject connection if the client uses server name (in SNI) that doesn't match the certificate
|
|
||||||
|
|
||||||
CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"` // PEM-encoded certificates chain
|
// Reject connection if the client uses server name (in SNI) that doesn't match the certificate
|
||||||
PrivateKey string `yaml:"private_key" json:"private_key"` // PEM-encoded private key
|
StrictSNICheck bool `yaml:"strict_sni_check" json:"-"`
|
||||||
|
|
||||||
CertificatePath string `yaml:"certificate_path" json:"certificate_path"` // certificate file name
|
// PEM-encoded certificates chain
|
||||||
PrivateKeyPath string `yaml:"private_key_path" json:"private_key_path"` // private key file name
|
CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"`
|
||||||
|
// PEM-encoded private key
|
||||||
|
PrivateKey string `yaml:"private_key" json:"private_key"`
|
||||||
|
|
||||||
|
CertificatePath string `yaml:"certificate_path" json:"certificate_path"`
|
||||||
|
PrivateKeyPath string `yaml:"private_key_path" json:"private_key_path"`
|
||||||
|
|
||||||
CertificateChainData []byte `yaml:"-" json:"-"`
|
CertificateChainData []byte `yaml:"-" json:"-"`
|
||||||
PrivateKeyData []byte `yaml:"-" json:"-"`
|
PrivateKeyData []byte `yaml:"-" json:"-"`
|
||||||
|
|
||||||
cert tls.Certificate // nolint(structcheck) - linter thinks that this field is unused, while TLSConfig is directly included into ServerConfig
|
cert tls.Certificate
|
||||||
dnsNames []string // nolint(structcheck) // DNS names from certificate (SAN) or CN value from Subject
|
// DNS names from certificate (SAN) or CN value from Subject
|
||||||
|
dnsNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSCryptConfig is the DNSCrypt server configuration struct.
|
||||||
|
type DNSCryptConfig struct {
|
||||||
|
UDPListenAddr *net.UDPAddr
|
||||||
|
TCPListenAddr *net.TCPAddr
|
||||||
|
ProviderName string
|
||||||
|
ResolverCert *dnscrypt.Cert
|
||||||
|
Enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerConfig represents server configuration.
|
// ServerConfig represents server configuration.
|
||||||
|
@ -119,6 +134,7 @@ type ServerConfig struct {
|
||||||
|
|
||||||
FilteringConfig
|
FilteringConfig
|
||||||
TLSConfig
|
TLSConfig
|
||||||
|
DNSCryptConfig
|
||||||
TLSAllowUnencryptedDOH bool
|
TLSAllowUnencryptedDOH bool
|
||||||
|
|
||||||
TLSv12Roots *x509.CertPool // list of root CAs for TLSv1.2
|
TLSv12Roots *x509.CertPool // list of root CAs for TLSv1.2
|
||||||
|
@ -184,6 +200,13 @@ func (s *Server) createProxyConfig() (proxy.Config, error) {
|
||||||
return proxyConfig, err
|
return proxyConfig, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.conf.DNSCryptConfig.Enabled {
|
||||||
|
proxyConfig.DNSCryptUDPListenAddr = []*net.UDPAddr{s.conf.DNSCryptConfig.UDPListenAddr}
|
||||||
|
proxyConfig.DNSCryptTCPListenAddr = []*net.TCPAddr{s.conf.DNSCryptConfig.TCPListenAddr}
|
||||||
|
proxyConfig.DNSCryptProviderName = s.conf.DNSCryptConfig.ProviderName
|
||||||
|
proxyConfig.DNSCryptResolverCert = s.conf.DNSCryptConfig.ResolverCert
|
||||||
|
}
|
||||||
|
|
||||||
// Validate proxy config
|
// Validate proxy config
|
||||||
if proxyConfig.UpstreamConfig == nil || len(proxyConfig.UpstreamConfig.Upstreams) == 0 {
|
if proxyConfig.UpstreamConfig == nil || len(proxyConfig.UpstreamConfig.Upstreams) == 0 {
|
||||||
return proxyConfig, errors.New("no default upstream servers configured")
|
return proxyConfig, errors.New("no default upstream servers configured")
|
||||||
|
|
|
@ -366,7 +366,9 @@ func processFilteringAfterResponse(ctx *dnsContext) int {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch res.Reason {
|
switch res.Reason {
|
||||||
case dnsfilter.ReasonRewrite:
|
case dnsfilter.ReasonRewrite,
|
||||||
|
dnsfilter.DNSRewriteRule:
|
||||||
|
|
||||||
if len(ctx.origQuestion.Name) == 0 {
|
if len(ctx.origQuestion.Name) == 0 {
|
||||||
// origQuestion is set in case we get only CNAME without IP from rewrites table
|
// origQuestion is set in case we get only CNAME without IP from rewrites table
|
||||||
break
|
break
|
||||||
|
@ -378,11 +380,11 @@ func processFilteringAfterResponse(ctx *dnsContext) int {
|
||||||
if len(d.Res.Answer) != 0 {
|
if len(d.Res.Answer) != 0 {
|
||||||
answer := []dns.RR{}
|
answer := []dns.RR{}
|
||||||
answer = append(answer, s.genCNAMEAnswer(d.Req, res.CanonName))
|
answer = append(answer, s.genCNAMEAnswer(d.Req, res.CanonName))
|
||||||
answer = append(answer, d.Res.Answer...) // host -> IP
|
answer = append(answer, d.Res.Answer...)
|
||||||
d.Res.Answer = answer
|
d.Res.Answer = answer
|
||||||
}
|
}
|
||||||
|
|
||||||
case dnsfilter.NotFilteredWhiteList:
|
case dnsfilter.NotFilteredAllowList:
|
||||||
// nothing
|
// nothing
|
||||||
|
|
||||||
default:
|
default:
|
|
@ -48,7 +48,7 @@ var webRegistered bool
|
||||||
// The zero Server is empty and ready for use.
|
// The zero Server is empty and ready for use.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
dnsProxy *proxy.Proxy // DNS proxy instance
|
dnsProxy *proxy.Proxy // DNS proxy instance
|
||||||
dnsFilter *dnsfilter.Dnsfilter // DNS filter instance
|
dnsFilter *dnsfilter.DNSFilter // DNS filter instance
|
||||||
dhcpServer dhcpd.ServerInterface // DHCP server instance (optional)
|
dhcpServer dhcpd.ServerInterface // DHCP server instance (optional)
|
||||||
queryLog querylog.QueryLog // Query log instance
|
queryLog querylog.QueryLog // Query log instance
|
||||||
stats stats.Stats
|
stats stats.Stats
|
||||||
|
@ -74,7 +74,7 @@ type Server struct {
|
||||||
|
|
||||||
// DNSCreateParams - parameters for NewServer()
|
// DNSCreateParams - parameters for NewServer()
|
||||||
type DNSCreateParams struct {
|
type DNSCreateParams struct {
|
||||||
DNSFilter *dnsfilter.Dnsfilter
|
DNSFilter *dnsfilter.DNSFilter
|
||||||
Stats stats.Stats
|
Stats stats.Stats
|
||||||
QueryLog querylog.QueryLog
|
QueryLog querylog.QueryLog
|
||||||
DHCPServer dhcpd.ServerInterface
|
DHCPServer dhcpd.ServerInterface
|
||||||
|
|
|
@ -296,7 +296,7 @@ func TestBlockedRequest(t *testing.T) {
|
||||||
|
|
||||||
func TestServerCustomClientUpstream(t *testing.T) {
|
func TestServerCustomClientUpstream(t *testing.T) {
|
||||||
s := createTestServer(t)
|
s := createTestServer(t)
|
||||||
s.conf.GetCustomUpstreamByClient = func(clientAddr string) *proxy.UpstreamConfig {
|
s.conf.GetCustomUpstreamByClient = func(_ string) *proxy.UpstreamConfig {
|
||||||
uc := &proxy.UpstreamConfig{}
|
uc := &proxy.UpstreamConfig{}
|
||||||
u := &testUpstream{}
|
u := &testUpstream{}
|
||||||
u.ipv4 = map[string][]net.IP{}
|
u.ipv4 = map[string][]net.IP{}
|
||||||
|
@ -473,7 +473,7 @@ func TestBlockCNAME(t *testing.T) {
|
||||||
func TestClientRulesForCNAMEMatching(t *testing.T) {
|
func TestClientRulesForCNAMEMatching(t *testing.T) {
|
||||||
s := createTestServer(t)
|
s := createTestServer(t)
|
||||||
testUpstm := &testUpstream{testCNAMEs, testIPv4, nil}
|
testUpstm := &testUpstream{testCNAMEs, testIPv4, nil}
|
||||||
s.conf.FilterHandler = func(clientAddr string, settings *dnsfilter.RequestFilteringSettings) {
|
s.conf.FilterHandler = func(_ string, settings *dnsfilter.RequestFilteringSettings) {
|
||||||
settings.FilteringEnabled = false
|
settings.FilteringEnabled = false
|
||||||
}
|
}
|
||||||
err := s.startWithUpstream(testUpstm)
|
err := s.startWithUpstream(testUpstm)
|
||||||
|
@ -863,6 +863,8 @@ func sendTestMessages(t *testing.T, conn *dns.Conn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func exchangeAndAssertResponse(t *testing.T, client *dns.Client, addr net.Addr, host, ip string) {
|
func exchangeAndAssertResponse(t *testing.T, client *dns.Client, addr net.Addr, host, ip string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
req := createTestMessage(host)
|
req := createTestMessage(host)
|
||||||
reply, _, err := client.Exchange(req, addr.String())
|
reply, _, err := client.Exchange(req, addr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -900,6 +902,8 @@ func assertGoogleAResponse(t *testing.T, reply *dns.Msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertResponse(t *testing.T, reply *dns.Msg, ip string) {
|
func assertResponse(t *testing.T, reply *dns.Msg, ip string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if len(reply.Answer) != 1 {
|
if len(reply.Answer) != 1 {
|
||||||
t.Fatalf("DNS server returned reply with wrong number of answers - %d", len(reply.Answer))
|
t.Fatalf("DNS server returned reply with wrong number of answers - %d", len(reply.Answer))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package dnsforward
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/urlfilter/rules"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// filterDNSRewriteResponse handles a single DNS rewrite response entry.
|
||||||
|
// It returns the constructed answer resource record.
|
||||||
|
func (s *Server) filterDNSRewriteResponse(req *dns.Msg, rr rules.RRType, v rules.RRValue) (ans dns.RR, err error) {
|
||||||
|
switch rr {
|
||||||
|
case dns.TypeA, dns.TypeAAAA:
|
||||||
|
ip, ok := v.(net.IP)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("value has type %T, not net.IP", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rr == dns.TypeA {
|
||||||
|
return s.genAAnswer(req, ip.To4()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.genAAAAAnswer(req, ip), nil
|
||||||
|
case dns.TypeTXT:
|
||||||
|
str, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("value has type %T, not string", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.genTXTAnswer(req, []string{str}), nil
|
||||||
|
default:
|
||||||
|
log.Debug("don't know how to handle dns rr type %d, skipping", rr)
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterDNSRewrite handles dnsrewrite filters. It constructs a DNS
|
||||||
|
// response and sets it into d.Res.
|
||||||
|
func (s *Server) filterDNSRewrite(req *dns.Msg, res dnsfilter.Result, d *proxy.DNSContext) (err error) {
|
||||||
|
resp := s.makeResponse(req)
|
||||||
|
dnsrr := res.DNSRewriteResult
|
||||||
|
if dnsrr == nil {
|
||||||
|
return agherr.Error("no dns rewrite rule content")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Rcode = dnsrr.RCode
|
||||||
|
if resp.Rcode != dns.RcodeSuccess {
|
||||||
|
d.Res = resp
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if dnsrr.Response == nil {
|
||||||
|
return agherr.Error("no dns rewrite rule responses")
|
||||||
|
}
|
||||||
|
|
||||||
|
rr := req.Question[0].Qtype
|
||||||
|
values := dnsrr.Response[rr]
|
||||||
|
for i, v := range values {
|
||||||
|
var ans dns.RR
|
||||||
|
ans, err = s.filterDNSRewriteResponse(req, rr, v)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dns rewrite response for %d[%d]: %w", rr, i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Answer = append(resp.Answer, ans)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Res = resp
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -42,7 +42,8 @@ func (s *Server) getClientRequestFilteringSettings(d *proxy.DNSContext) *dnsfilt
|
||||||
return &setts
|
return &setts
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterDNSRequest applies the dnsFilter and sets d.Res if the request was filtered
|
// filterDNSRequest applies the dnsFilter and sets d.Res if the request
|
||||||
|
// was filtered.
|
||||||
func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||||
d := ctx.proxyCtx
|
d := ctx.proxyCtx
|
||||||
req := d.Req
|
req := d.Req
|
||||||
|
@ -52,13 +53,17 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||||
// Return immediately if there's an error
|
// Return immediately if there's an error
|
||||||
return nil, fmt.Errorf("dnsfilter failed to check host %q: %w", host, err)
|
return nil, fmt.Errorf("dnsfilter failed to check host %q: %w", host, err)
|
||||||
} else if res.IsFiltered {
|
} else if res.IsFiltered {
|
||||||
log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rule)
|
log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rules[0].Text)
|
||||||
d.Res = s.genDNSFilterMessage(d, &res)
|
d.Res = s.genDNSFilterMessage(d, &res)
|
||||||
} else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 {
|
} else if res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.DNSRewriteRule) &&
|
||||||
|
res.CanonName != "" &&
|
||||||
|
len(res.IPList) == 0 {
|
||||||
|
// Resolve the new canonical name, not the original host
|
||||||
|
// name. The original question is readded in
|
||||||
|
// processFilteringAfterResponse.
|
||||||
ctx.origQuestion = d.Req.Question[0]
|
ctx.origQuestion = d.Req.Question[0]
|
||||||
// resolve canonical name, not the original host name
|
|
||||||
d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
|
d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
|
||||||
} else if res.Reason == dnsfilter.RewriteEtcHosts && len(res.ReverseHosts) != 0 {
|
} else if res.Reason == dnsfilter.RewriteAutoHosts && len(res.ReverseHosts) != 0 {
|
||||||
resp := s.makeResponse(req)
|
resp := s.makeResponse(req)
|
||||||
for _, h := range res.ReverseHosts {
|
for _, h := range res.ReverseHosts {
|
||||||
hdr := dns.RR_Header{
|
hdr := dns.RR_Header{
|
||||||
|
@ -77,7 +82,7 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Res = resp
|
d.Res = resp
|
||||||
} else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteEtcHosts {
|
} else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteAutoHosts {
|
||||||
resp := s.makeResponse(req)
|
resp := s.makeResponse(req)
|
||||||
|
|
||||||
name := host
|
name := host
|
||||||
|
@ -99,6 +104,11 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Res = resp
|
d.Res = resp
|
||||||
|
} else if res.Reason == dnsfilter.DNSRewriteRule {
|
||||||
|
err = s.filterDNSRewrite(req, res, d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &res, err
|
return &res, err
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/jsonutil"
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/utils"
|
"github.com/AdguardTeam/golibs/utils"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
@ -21,232 +20,293 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string,
|
||||||
http.Error(w, text, code)
|
http.Error(w, text, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
type dnsConfigJSON struct {
|
type dnsConfig struct {
|
||||||
Upstreams []string `json:"upstream_dns"`
|
Upstreams *[]string `json:"upstream_dns"`
|
||||||
UpstreamsFile string `json:"upstream_dns_file"`
|
UpstreamsFile *string `json:"upstream_dns_file"`
|
||||||
Bootstraps []string `json:"bootstrap_dns"`
|
Bootstraps *[]string `json:"bootstrap_dns"`
|
||||||
|
|
||||||
ProtectionEnabled bool `json:"protection_enabled"`
|
ProtectionEnabled *bool `json:"protection_enabled"`
|
||||||
RateLimit uint32 `json:"ratelimit"`
|
RateLimit *uint32 `json:"ratelimit"`
|
||||||
BlockingMode string `json:"blocking_mode"`
|
BlockingMode *string `json:"blocking_mode"`
|
||||||
BlockingIPv4 string `json:"blocking_ipv4"`
|
BlockingIPv4 *string `json:"blocking_ipv4"`
|
||||||
BlockingIPv6 string `json:"blocking_ipv6"`
|
BlockingIPv6 *string `json:"blocking_ipv6"`
|
||||||
EDNSCSEnabled bool `json:"edns_cs_enabled"`
|
EDNSCSEnabled *bool `json:"edns_cs_enabled"`
|
||||||
DNSSECEnabled bool `json:"dnssec_enabled"`
|
DNSSECEnabled *bool `json:"dnssec_enabled"`
|
||||||
DisableIPv6 bool `json:"disable_ipv6"`
|
DisableIPv6 *bool `json:"disable_ipv6"`
|
||||||
UpstreamMode string `json:"upstream_mode"`
|
UpstreamMode *string `json:"upstream_mode"`
|
||||||
CacheSize uint32 `json:"cache_size"`
|
CacheSize *uint32 `json:"cache_size"`
|
||||||
CacheMinTTL uint32 `json:"cache_ttl_min"`
|
CacheMinTTL *uint32 `json:"cache_ttl_min"`
|
||||||
CacheMaxTTL uint32 `json:"cache_ttl_max"`
|
CacheMaxTTL *uint32 `json:"cache_ttl_max"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getDNSConfig() dnsConfig {
|
||||||
|
s.RLock()
|
||||||
|
upstreams := stringArrayDup(s.conf.UpstreamDNS)
|
||||||
|
upstreamFile := s.conf.UpstreamDNSFileName
|
||||||
|
bootstraps := stringArrayDup(s.conf.BootstrapDNS)
|
||||||
|
protectionEnabled := s.conf.ProtectionEnabled
|
||||||
|
blockingMode := s.conf.BlockingMode
|
||||||
|
BlockingIPv4 := s.conf.BlockingIPv4
|
||||||
|
BlockingIPv6 := s.conf.BlockingIPv6
|
||||||
|
Ratelimit := s.conf.Ratelimit
|
||||||
|
EnableEDNSClientSubnet := s.conf.EnableEDNSClientSubnet
|
||||||
|
EnableDNSSEC := s.conf.EnableDNSSEC
|
||||||
|
AAAADisabled := s.conf.AAAADisabled
|
||||||
|
CacheSize := s.conf.CacheSize
|
||||||
|
CacheMinTTL := s.conf.CacheMinTTL
|
||||||
|
CacheMaxTTL := s.conf.CacheMaxTTL
|
||||||
|
var upstreamMode string
|
||||||
|
if s.conf.FastestAddr {
|
||||||
|
upstreamMode = "fastest_addr"
|
||||||
|
} else if s.conf.AllServers {
|
||||||
|
upstreamMode = "parallel"
|
||||||
|
}
|
||||||
|
s.RUnlock()
|
||||||
|
return dnsConfig{
|
||||||
|
Upstreams: &upstreams,
|
||||||
|
UpstreamsFile: &upstreamFile,
|
||||||
|
Bootstraps: &bootstraps,
|
||||||
|
ProtectionEnabled: &protectionEnabled,
|
||||||
|
BlockingMode: &blockingMode,
|
||||||
|
BlockingIPv4: &BlockingIPv4,
|
||||||
|
BlockingIPv6: &BlockingIPv6,
|
||||||
|
RateLimit: &Ratelimit,
|
||||||
|
EDNSCSEnabled: &EnableEDNSClientSubnet,
|
||||||
|
DNSSECEnabled: &EnableDNSSEC,
|
||||||
|
DisableIPv6: &AAAADisabled,
|
||||||
|
CacheSize: &CacheSize,
|
||||||
|
CacheMinTTL: &CacheMinTTL,
|
||||||
|
CacheMaxTTL: &CacheMaxTTL,
|
||||||
|
UpstreamMode: &upstreamMode,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
resp := dnsConfigJSON{}
|
resp := s.getDNSConfig()
|
||||||
s.RLock()
|
|
||||||
resp.Upstreams = stringArrayDup(s.conf.UpstreamDNS)
|
|
||||||
resp.UpstreamsFile = s.conf.UpstreamDNSFileName
|
|
||||||
resp.Bootstraps = stringArrayDup(s.conf.BootstrapDNS)
|
|
||||||
|
|
||||||
resp.ProtectionEnabled = s.conf.ProtectionEnabled
|
|
||||||
resp.BlockingMode = s.conf.BlockingMode
|
|
||||||
resp.BlockingIPv4 = s.conf.BlockingIPv4
|
|
||||||
resp.BlockingIPv6 = s.conf.BlockingIPv6
|
|
||||||
resp.RateLimit = s.conf.Ratelimit
|
|
||||||
resp.EDNSCSEnabled = s.conf.EnableEDNSClientSubnet
|
|
||||||
resp.DNSSECEnabled = s.conf.EnableDNSSEC
|
|
||||||
resp.DisableIPv6 = s.conf.AAAADisabled
|
|
||||||
resp.CacheSize = s.conf.CacheSize
|
|
||||||
resp.CacheMinTTL = s.conf.CacheMinTTL
|
|
||||||
resp.CacheMaxTTL = s.conf.CacheMaxTTL
|
|
||||||
if s.conf.FastestAddr {
|
|
||||||
resp.UpstreamMode = "fastest_addr"
|
|
||||||
} else if s.conf.AllServers {
|
|
||||||
resp.UpstreamMode = "parallel"
|
|
||||||
}
|
|
||||||
s.RUnlock()
|
|
||||||
|
|
||||||
js, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
httpError(r, w, http.StatusInternalServerError, "json.Marshal: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_, _ = w.Write(js)
|
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
if err := enc.Encode(resp); err != nil {
|
||||||
|
httpError(r, w, http.StatusInternalServerError, "json.Encoder: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkBlockingMode(req dnsConfigJSON) bool {
|
func (req *dnsConfig) checkBlockingMode() bool {
|
||||||
bm := req.BlockingMode
|
if req.BlockingMode == nil {
|
||||||
if !(bm == "default" || bm == "refused" || bm == "nxdomain" || bm == "null_ip" || bm == "custom_ip") {
|
return true
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bm := *req.BlockingMode
|
||||||
if bm == "custom_ip" {
|
if bm == "custom_ip" {
|
||||||
ip := net.ParseIP(req.BlockingIPv4)
|
if req.BlockingIPv4 == nil || req.BlockingIPv6 == nil {
|
||||||
if ip == nil || ip.To4() == nil {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ip = net.ParseIP(req.BlockingIPv6)
|
ip4 := net.ParseIP(*req.BlockingIPv4)
|
||||||
if ip == nil {
|
if ip4 == nil || ip4.To4() == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ip6 := net.ParseIP(*req.BlockingIPv6)
|
||||||
|
return ip6 != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, valid := range []string{
|
||||||
|
"default",
|
||||||
|
"refused",
|
||||||
|
"nxdomain",
|
||||||
|
"null_ip",
|
||||||
|
} {
|
||||||
|
if bm == valid {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate bootstrap server address
|
func (req *dnsConfig) checkUpstreamsMode() bool {
|
||||||
func checkBootstrap(addr string) error {
|
if req.UpstreamMode == nil {
|
||||||
if addr == "" { // additional check is required because NewResolver() allows empty address
|
return true
|
||||||
return fmt.Errorf("invalid bootstrap server address: empty")
|
|
||||||
}
|
}
|
||||||
_, err := upstream.NewResolver(addr, 0)
|
|
||||||
if err != nil {
|
for _, valid := range []string{
|
||||||
return fmt.Errorf("invalid bootstrap server address: %w", err)
|
"",
|
||||||
|
"fastest_addr",
|
||||||
|
"parallel",
|
||||||
|
} {
|
||||||
|
if *req.UpstreamMode == valid {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *dnsConfig) checkBootstrap() (string, error) {
|
||||||
|
if req.Bootstraps == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, boot := range *req.Bootstraps {
|
||||||
|
if boot == "" {
|
||||||
|
return boot, fmt.Errorf("invalid bootstrap server address: empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := upstream.NewResolver(boot, 0); err != nil {
|
||||||
|
return boot, fmt.Errorf("invalid bootstrap server address: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *dnsConfig) checkCacheTTL() bool {
|
||||||
|
if req.CacheMinTTL == nil && req.CacheMaxTTL == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var min, max uint32
|
||||||
|
if req.CacheMinTTL != nil {
|
||||||
|
min = *req.CacheMinTTL
|
||||||
|
}
|
||||||
|
if req.CacheMaxTTL != nil {
|
||||||
|
max = *req.CacheMaxTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
return min <= max
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint(gocyclo) - we need to check each JSON field separately
|
|
||||||
func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
req := dnsConfigJSON{}
|
req := dnsConfig{}
|
||||||
js, err := jsonutil.DecodeObject(&req, r.Body)
|
dec := json.NewDecoder(r.Body)
|
||||||
if err != nil {
|
if err := dec.Decode(&req); err != nil {
|
||||||
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
httpError(r, w, http.StatusBadRequest, "json Encode: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if js.Exists("upstream_dns") {
|
if req.Upstreams != nil {
|
||||||
err = ValidateUpstreams(req.Upstreams)
|
if err := ValidateUpstreams(*req.Upstreams); err != nil {
|
||||||
if err != nil {
|
|
||||||
httpError(r, w, http.StatusBadRequest, "wrong upstreams specification: %s", err)
|
httpError(r, w, http.StatusBadRequest, "wrong upstreams specification: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if js.Exists("bootstrap_dns") {
|
if errBoot, err := req.checkBootstrap(); err != nil {
|
||||||
for _, boot := range req.Bootstraps {
|
httpError(r, w, http.StatusBadRequest, "%s can not be used as bootstrap dns cause: %s", errBoot, err)
|
||||||
if err := checkBootstrap(boot); err != nil {
|
return
|
||||||
httpError(r, w, http.StatusBadRequest, "%s can not be used as bootstrap dns cause: %s", boot, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if js.Exists("blocking_mode") && !checkBlockingMode(req) {
|
if !req.checkBlockingMode() {
|
||||||
httpError(r, w, http.StatusBadRequest, "blocking_mode: incorrect value")
|
httpError(r, w, http.StatusBadRequest, "blocking_mode: incorrect value")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if js.Exists("upstream_mode") &&
|
if !req.checkUpstreamsMode() {
|
||||||
!(req.UpstreamMode == "" || req.UpstreamMode == "fastest_addr" || req.UpstreamMode == "parallel") {
|
|
||||||
httpError(r, w, http.StatusBadRequest, "upstream_mode: incorrect value")
|
httpError(r, w, http.StatusBadRequest, "upstream_mode: incorrect value")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.CacheMinTTL > req.CacheMaxTTL {
|
if !req.checkCacheTTL() {
|
||||||
httpError(r, w, http.StatusBadRequest, "cache_ttl_min must be less or equal than cache_ttl_max")
|
httpError(r, w, http.StatusBadRequest, "cache_ttl_min must be less or equal than cache_ttl_max")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
restart := false
|
if s.setConfig(req) {
|
||||||
s.Lock()
|
if err := s.Reconfigure(nil); err != nil {
|
||||||
|
|
||||||
if js.Exists("upstream_dns") {
|
|
||||||
s.conf.UpstreamDNS = req.Upstreams
|
|
||||||
restart = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if js.Exists("upstream_dns_file") {
|
|
||||||
s.conf.UpstreamDNSFileName = req.UpstreamsFile
|
|
||||||
restart = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if js.Exists("bootstrap_dns") {
|
|
||||||
s.conf.BootstrapDNS = req.Bootstraps
|
|
||||||
restart = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if js.Exists("protection_enabled") {
|
|
||||||
s.conf.ProtectionEnabled = req.ProtectionEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if js.Exists("blocking_mode") {
|
|
||||||
s.conf.BlockingMode = req.BlockingMode
|
|
||||||
if req.BlockingMode == "custom_ip" {
|
|
||||||
if js.Exists("blocking_ipv4") {
|
|
||||||
s.conf.BlockingIPv4 = req.BlockingIPv4
|
|
||||||
s.conf.BlockingIPAddrv4 = net.ParseIP(req.BlockingIPv4)
|
|
||||||
}
|
|
||||||
if js.Exists("blocking_ipv6") {
|
|
||||||
s.conf.BlockingIPv6 = req.BlockingIPv6
|
|
||||||
s.conf.BlockingIPAddrv6 = net.ParseIP(req.BlockingIPv6)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if js.Exists("ratelimit") {
|
|
||||||
if s.conf.Ratelimit != req.RateLimit {
|
|
||||||
restart = true
|
|
||||||
}
|
|
||||||
s.conf.Ratelimit = req.RateLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
if js.Exists("edns_cs_enabled") {
|
|
||||||
s.conf.EnableEDNSClientSubnet = req.EDNSCSEnabled
|
|
||||||
restart = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if js.Exists("dnssec_enabled") {
|
|
||||||
s.conf.EnableDNSSEC = req.DNSSECEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if js.Exists("disable_ipv6") {
|
|
||||||
s.conf.AAAADisabled = req.DisableIPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
if js.Exists("cache_size") {
|
|
||||||
s.conf.CacheSize = req.CacheSize
|
|
||||||
restart = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if js.Exists("cache_ttl_min") {
|
|
||||||
s.conf.CacheMinTTL = req.CacheMinTTL
|
|
||||||
restart = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if js.Exists("cache_ttl_max") {
|
|
||||||
s.conf.CacheMaxTTL = req.CacheMaxTTL
|
|
||||||
restart = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if js.Exists("upstream_mode") {
|
|
||||||
s.conf.FastestAddr = false
|
|
||||||
s.conf.AllServers = false
|
|
||||||
switch req.UpstreamMode {
|
|
||||||
case "":
|
|
||||||
//
|
|
||||||
|
|
||||||
case "parallel":
|
|
||||||
s.conf.AllServers = true
|
|
||||||
|
|
||||||
case "fastest_addr":
|
|
||||||
s.conf.FastestAddr = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Unlock()
|
|
||||||
s.conf.ConfigModified()
|
|
||||||
|
|
||||||
if restart {
|
|
||||||
err = s.Reconfigure(nil)
|
|
||||||
if err != nil {
|
|
||||||
httpError(r, w, http.StatusInternalServerError, "%s", err)
|
httpError(r, w, http.StatusInternalServerError, "%s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) setConfig(dc dnsConfig) (restart bool) {
|
||||||
|
s.Lock()
|
||||||
|
|
||||||
|
if dc.Upstreams != nil {
|
||||||
|
s.conf.UpstreamDNS = *dc.Upstreams
|
||||||
|
restart = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.UpstreamsFile != nil {
|
||||||
|
s.conf.UpstreamDNSFileName = *dc.UpstreamsFile
|
||||||
|
restart = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.Bootstraps != nil {
|
||||||
|
s.conf.BootstrapDNS = *dc.Bootstraps
|
||||||
|
restart = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.ProtectionEnabled != nil {
|
||||||
|
s.conf.ProtectionEnabled = *dc.ProtectionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.BlockingMode != nil {
|
||||||
|
s.conf.BlockingMode = *dc.BlockingMode
|
||||||
|
if *dc.BlockingMode == "custom_ip" {
|
||||||
|
s.conf.BlockingIPv4 = *dc.BlockingIPv4
|
||||||
|
s.conf.BlockingIPAddrv4 = net.ParseIP(*dc.BlockingIPv4)
|
||||||
|
s.conf.BlockingIPv6 = *dc.BlockingIPv6
|
||||||
|
s.conf.BlockingIPAddrv6 = net.ParseIP(*dc.BlockingIPv6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.RateLimit != nil {
|
||||||
|
if s.conf.Ratelimit != *dc.RateLimit {
|
||||||
|
restart = true
|
||||||
|
}
|
||||||
|
s.conf.Ratelimit = *dc.RateLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.EDNSCSEnabled != nil {
|
||||||
|
s.conf.EnableEDNSClientSubnet = *dc.EDNSCSEnabled
|
||||||
|
restart = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.DNSSECEnabled != nil {
|
||||||
|
s.conf.EnableDNSSEC = *dc.DNSSECEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.DisableIPv6 != nil {
|
||||||
|
s.conf.AAAADisabled = *dc.DisableIPv6
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.CacheSize != nil {
|
||||||
|
s.conf.CacheSize = *dc.CacheSize
|
||||||
|
restart = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.CacheMinTTL != nil {
|
||||||
|
s.conf.CacheMinTTL = *dc.CacheMinTTL
|
||||||
|
restart = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.CacheMaxTTL != nil {
|
||||||
|
s.conf.CacheMaxTTL = *dc.CacheMaxTTL
|
||||||
|
restart = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.UpstreamMode != nil {
|
||||||
|
switch *dc.UpstreamMode {
|
||||||
|
case "parallel":
|
||||||
|
s.conf.AllServers = true
|
||||||
|
s.conf.FastestAddr = false
|
||||||
|
case "fastest_addr":
|
||||||
|
s.conf.AllServers = false
|
||||||
|
s.conf.FastestAddr = true
|
||||||
|
default:
|
||||||
|
s.conf.AllServers = false
|
||||||
|
s.conf.FastestAddr = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Unlock()
|
||||||
|
s.conf.ConfigModified()
|
||||||
|
return restart
|
||||||
|
}
|
||||||
|
|
||||||
type upstreamJSON struct {
|
type upstreamJSON struct {
|
||||||
Upstreams []string `json:"upstream_dns"` // Upstreams
|
Upstreams []string `json:"upstream_dns"` // Upstreams
|
||||||
BootstrapDNS []string `json:"bootstrap_dns"` // Bootstrap DNS
|
BootstrapDNS []string `json:"bootstrap_dns"` // Bootstrap DNS
|
|
@ -29,7 +29,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||||
conf: func() ServerConfig {
|
conf: func() ServerConfig {
|
||||||
return defaultConf
|
return defaultConf
|
||||||
},
|
},
|
||||||
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}, {
|
}, {
|
||||||
name: "fastest_addr",
|
name: "fastest_addr",
|
||||||
conf: func() ServerConfig {
|
conf: func() ServerConfig {
|
||||||
|
@ -37,7 +37,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||||
conf.FastestAddr = true
|
conf.FastestAddr = true
|
||||||
return conf
|
return conf
|
||||||
},
|
},
|
||||||
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}, {
|
}, {
|
||||||
name: "parallel",
|
name: "parallel",
|
||||||
conf: func() ServerConfig {
|
conf: func() ServerConfig {
|
||||||
|
@ -45,7 +45,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||||
conf.AllServers = true
|
conf.AllServers = true
|
||||||
return conf
|
return conf
|
||||||
},
|
},
|
||||||
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
@ -73,7 +73,7 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) {
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
const defaultConfJSON = "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}"
|
const defaultConfJSON = "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n"
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
req string
|
req string
|
||||||
|
@ -83,52 +83,52 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) {
|
||||||
name: "upstream_dns",
|
name: "upstream_dns",
|
||||||
req: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"]}",
|
req: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"]}",
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
wantGet: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}, {
|
}, {
|
||||||
name: "bootstraps",
|
name: "bootstraps",
|
||||||
req: "{\"bootstrap_dns\":[\"9.9.9.10\"]}",
|
req: "{\"bootstrap_dns\":[\"9.9.9.10\"]}",
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}, {
|
}, {
|
||||||
name: "blocking_mode_good",
|
name: "blocking_mode_good",
|
||||||
req: "{\"blocking_mode\":\"refused\"}",
|
req: "{\"blocking_mode\":\"refused\"}",
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"refused\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"refused\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}, {
|
}, {
|
||||||
name: "blocking_mode_bad",
|
name: "blocking_mode_bad",
|
||||||
req: "{\"blocking_mode\":\"custom_ip\"}",
|
req: "{\"blocking_mode\":\"custom_ip\"}",
|
||||||
wantSet: "blocking_mode: incorrect value\n",
|
wantSet: "blocking_mode: incorrect value\n",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}, {
|
}, {
|
||||||
name: "ratelimit",
|
name: "ratelimit",
|
||||||
req: "{\"ratelimit\":6}",
|
req: "{\"ratelimit\":6}",
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":6,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":6,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}, {
|
}, {
|
||||||
name: "edns_cs_enabled",
|
name: "edns_cs_enabled",
|
||||||
req: "{\"edns_cs_enabled\":true}",
|
req: "{\"edns_cs_enabled\":true}",
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":true,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":true,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}, {
|
}, {
|
||||||
name: "dnssec_enabled",
|
name: "dnssec_enabled",
|
||||||
req: "{\"dnssec_enabled\":true}",
|
req: "{\"dnssec_enabled\":true}",
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":true,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":true,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}, {
|
}, {
|
||||||
name: "cache_size",
|
name: "cache_size",
|
||||||
req: "{\"cache_size\":1024}",
|
req: "{\"cache_size\":1024}",
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":1024,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":1024,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}, {
|
}, {
|
||||||
name: "upstream_mode_parallel",
|
name: "upstream_mode_parallel",
|
||||||
req: "{\"upstream_mode\":\"parallel\"}",
|
req: "{\"upstream_mode\":\"parallel\"}",
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}, {
|
}, {
|
||||||
name: "upstream_mode_fastest_addr",
|
name: "upstream_mode_fastest_addr",
|
||||||
req: "{\"upstream_mode\":\"fastest_addr\"}",
|
req: "{\"upstream_mode\":\"fastest_addr\"}",
|
||||||
wantSet: "",
|
wantSet: "",
|
||||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||||
}, {
|
}, {
|
||||||
name: "upstream_dns_bad",
|
name: "upstream_dns_bad",
|
||||||
req: "{\"upstream_dns\":[\"\"]}",
|
req: "{\"upstream_dns\":[\"\"]}",
|
|
@ -1,22 +1,27 @@
|
||||||
package dnsforward
|
package dnsforward
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create a DNS response by DNS request and set necessary flags
|
// Create a DNS response by DNS request and set necessary flags
|
||||||
func (s *Server) makeResponse(req *dns.Msg) *dns.Msg {
|
func (s *Server) makeResponse(req *dns.Msg) (resp *dns.Msg) {
|
||||||
resp := dns.Msg{}
|
resp = &dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
RecursionAvailable: true,
|
||||||
|
},
|
||||||
|
Compress: true,
|
||||||
|
}
|
||||||
|
|
||||||
resp.SetReply(req)
|
resp.SetReply(req)
|
||||||
resp.RecursionAvailable = true
|
|
||||||
resp.Compress = true
|
return resp
|
||||||
return &resp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// genDNSFilterMessage generates a DNS message corresponding to the filtering result
|
// genDNSFilterMessage generates a DNS message corresponding to the filtering result
|
||||||
|
@ -39,8 +44,10 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
|
||||||
// If the query was filtered by "Safe search", dnsfilter also must return
|
// If the query was filtered by "Safe search", dnsfilter also must return
|
||||||
// the IP address that must be used in response.
|
// the IP address that must be used in response.
|
||||||
// In this case regardless of the filtering method, we should return it
|
// In this case regardless of the filtering method, we should return it
|
||||||
if result.Reason == dnsfilter.FilteredSafeSearch && result.IP != nil {
|
if result.Reason == dnsfilter.FilteredSafeSearch &&
|
||||||
return s.genResponseWithIP(m, result.IP)
|
len(result.Rules) > 0 &&
|
||||||
|
result.Rules[0].IP != nil {
|
||||||
|
return s.genResponseWithIP(m, result.Rules[0].IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.conf.BlockingMode == "null_ip" {
|
if s.conf.BlockingMode == "null_ip" {
|
||||||
|
@ -68,8 +75,8 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
|
||||||
// Default blocking mode
|
// Default blocking mode
|
||||||
// If there's an IP specified in the rule, return it
|
// If there's an IP specified in the rule, return it
|
||||||
// For host-type rules, return null IP
|
// For host-type rules, return null IP
|
||||||
if result.IP != nil {
|
if len(result.Rules) > 0 && result.Rules[0].IP != nil {
|
||||||
return s.genResponseWithIP(m, result.IP)
|
return s.genResponseWithIP(m, result.Rules[0].IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.makeResponseNullIP(m)
|
return s.makeResponseNullIP(m)
|
||||||
|
@ -119,6 +126,18 @@ func (s *Server) genAAAAAnswer(req *dns.Msg, ip net.IP) *dns.AAAA {
|
||||||
return answer
|
return answer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) genTXTAnswer(req *dns.Msg, strs []string) (answer *dns.TXT) {
|
||||||
|
return &dns.TXT{
|
||||||
|
Hdr: dns.RR_Header{
|
||||||
|
Name: req.Question[0].Name,
|
||||||
|
Rrtype: dns.TypeTXT,
|
||||||
|
Ttl: s.conf.BlockedResponseTTL,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
},
|
||||||
|
Txt: strs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// generate DNS response message with an IP address
|
// generate DNS response message with an IP address
|
||||||
func (s *Server) genResponseWithIP(req *dns.Msg, ip net.IP) *dns.Msg {
|
func (s *Server) genResponseWithIP(req *dns.Msg, ip net.IP) *dns.Msg {
|
||||||
if req.Question[0].Qtype == dns.TypeA && ip.To4() != nil {
|
if req.Question[0].Qtype == dns.TypeA && ip.To4() != nil {
|
||||||
|
|
|
@ -91,7 +91,7 @@ func (s *Server) updateStats(d *proxy.DNSContext, elapsed time.Duration, res dns
|
||||||
case dnsfilter.FilteredSafeSearch:
|
case dnsfilter.FilteredSafeSearch:
|
||||||
e.Result = stats.RSafeSearch
|
e.Result = stats.RSafeSearch
|
||||||
|
|
||||||
case dnsfilter.FilteredBlackList:
|
case dnsfilter.FilteredBlockList:
|
||||||
fallthrough
|
fallthrough
|
||||||
case dnsfilter.FilteredInvalid:
|
case dnsfilter.FilteredInvalid:
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -76,7 +78,6 @@ func InitAuth(dbFilename string, users []User, sessionTTL uint32) *Auth {
|
||||||
a := Auth{}
|
a := Auth{}
|
||||||
a.sessionTTL = sessionTTL
|
a.sessionTTL = sessionTTL
|
||||||
a.sessions = make(map[string]*session)
|
a.sessions = make(map[string]*session)
|
||||||
rand.Seed(time.Now().UTC().Unix())
|
|
||||||
var err error
|
var err error
|
||||||
a.db, err = bbolt.Open(dbFilename, 0o644, nil)
|
a.db, err = bbolt.Open(dbFilename, 0o644, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -275,23 +276,28 @@ type loginJSON struct {
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSession(u *User) []byte {
|
func getSession(u *User) ([]byte, error) {
|
||||||
// the developers don't currently believe that using a
|
maxSalt := big.NewInt(math.MaxUint32)
|
||||||
// non-cryptographic RNG for the session hash salt is
|
salt, err := rand.Int(rand.Reader, maxSalt)
|
||||||
// insecure
|
if err != nil {
|
||||||
salt := rand.Uint32() //nolint:gosec
|
return nil, err
|
||||||
d := []byte(fmt.Sprintf("%d%s%s", salt, u.Name, u.PasswordHash))
|
|
||||||
hash := sha256.Sum256(d)
|
|
||||||
return hash[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Auth) httpCookie(req loginJSON) string {
|
|
||||||
u := a.UserFind(req.Name, req.Password)
|
|
||||||
if len(u.Name) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sess := getSession(&u)
|
d := []byte(fmt.Sprintf("%s%s%s", salt, u.Name, u.PasswordHash))
|
||||||
|
hash := sha256.Sum256(d)
|
||||||
|
return hash[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) httpCookie(req loginJSON) (string, error) {
|
||||||
|
u := a.UserFind(req.Name, req.Password)
|
||||||
|
if len(u.Name) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sess, err := getSession(&u)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
expire := now.Add(cookieTTL * time.Hour)
|
expire := now.Add(cookieTTL * time.Hour)
|
||||||
|
@ -305,7 +311,7 @@ func (a *Auth) httpCookie(req loginJSON) string {
|
||||||
a.addSession(sess, &s)
|
a.addSession(sess, &s)
|
||||||
|
|
||||||
return fmt.Sprintf("%s=%s; Path=/; HttpOnly; Expires=%s",
|
return fmt.Sprintf("%s=%s; Path=/; HttpOnly; Expires=%s",
|
||||||
sessionCookieName, hex.EncodeToString(sess), expstr)
|
sessionCookieName, hex.EncodeToString(sess), expstr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -316,7 +322,11 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cookie := Context.auth.httpCookie(req)
|
cookie, err := Context.auth.httpCookie(req)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, http.StatusBadRequest, "crypto rand reader: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
if len(cookie) == 0 {
|
if len(cookie) == 0 {
|
||||||
log.Info("Auth: invalid user name or password: name=%q", req.Name)
|
log.Info("Auth: invalid user name or password: name=%q", req.Name)
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
@ -350,7 +360,7 @@ func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// RegisterAuthHandlers - register handlers
|
// RegisterAuthHandlers - register handlers
|
||||||
func RegisterAuthHandlers() {
|
func RegisterAuthHandlers() {
|
||||||
http.Handle("/control/login", postInstallHandler(ensureHandler("POST", handleLogin)))
|
Context.mux.Handle("/control/login", postInstallHandler(ensureHandler("POST", handleLogin)))
|
||||||
httpRegister("GET", "/control/logout", handleLogout)
|
httpRegister("GET", "/control/logout", handleLogout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,7 +379,54 @@ func parseCookie(cookie string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint(gocyclo)
|
// optionalAuthThird return true if user should authenticate first.
|
||||||
|
func optionalAuthThird(w http.ResponseWriter, r *http.Request) (authFirst bool) {
|
||||||
|
authFirst = false
|
||||||
|
|
||||||
|
// redirect to login page if not authenticated
|
||||||
|
ok := false
|
||||||
|
cookie, err := r.Cookie(sessionCookieName)
|
||||||
|
|
||||||
|
if glProcessCookie(r) {
|
||||||
|
log.Debug("Auth: authentification was handled by GL-Inet submodule")
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
} else if err == nil {
|
||||||
|
r := Context.auth.CheckSession(cookie.Value)
|
||||||
|
if r == 0 {
|
||||||
|
ok = true
|
||||||
|
} else if r < 0 {
|
||||||
|
log.Debug("Auth: invalid cookie value: %s", cookie)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// there's no Cookie, check Basic authentication
|
||||||
|
user, pass, ok2 := r.BasicAuth()
|
||||||
|
if ok2 {
|
||||||
|
u := Context.auth.UserFind(user, pass)
|
||||||
|
if len(u.Name) != 0 {
|
||||||
|
ok = true
|
||||||
|
} else {
|
||||||
|
log.Info("Auth: invalid Basic Authorization value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||||
|
if glProcessRedirect(w, r) {
|
||||||
|
log.Debug("Auth: redirected to login page by GL-Inet submodule")
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Location", "/login.html")
|
||||||
|
w.WriteHeader(http.StatusFound)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
_, _ = w.Write([]byte("Forbidden"))
|
||||||
|
}
|
||||||
|
authFirst = true
|
||||||
|
}
|
||||||
|
return authFirst
|
||||||
|
}
|
||||||
|
|
||||||
func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/login.html" {
|
if r.URL.Path == "/login.html" {
|
||||||
|
@ -392,45 +449,7 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re
|
||||||
// process as usual
|
// process as usual
|
||||||
// no additional auth requirements
|
// no additional auth requirements
|
||||||
} else if Context.auth != nil && Context.auth.AuthRequired() {
|
} else if Context.auth != nil && Context.auth.AuthRequired() {
|
||||||
// redirect to login page if not authenticated
|
if optionalAuthThird(w, r) {
|
||||||
ok := false
|
|
||||||
cookie, err := r.Cookie(sessionCookieName)
|
|
||||||
|
|
||||||
if glProcessCookie(r) {
|
|
||||||
log.Debug("Auth: authentification was handled by GL-Inet submodule")
|
|
||||||
ok = true
|
|
||||||
|
|
||||||
} else if err == nil {
|
|
||||||
r := Context.auth.CheckSession(cookie.Value)
|
|
||||||
if r == 0 {
|
|
||||||
ok = true
|
|
||||||
} else if r < 0 {
|
|
||||||
log.Debug("Auth: invalid cookie value: %s", cookie)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// there's no Cookie, check Basic authentication
|
|
||||||
user, pass, ok2 := r.BasicAuth()
|
|
||||||
if ok2 {
|
|
||||||
u := Context.auth.UserFind(user, pass)
|
|
||||||
if len(u.Name) != 0 {
|
|
||||||
ok = true
|
|
||||||
} else {
|
|
||||||
log.Info("Auth: invalid Basic Authorization value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
|
||||||
if glProcessRedirect(w, r) {
|
|
||||||
log.Debug("Auth: redirected to login page by GL-Inet submodule")
|
|
||||||
} else {
|
|
||||||
w.Header().Set("Location", "/login.html")
|
|
||||||
w.WriteHeader(http.StatusFound)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
_, _ = w.Write([]byte("Forbidden"))
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ func TestMain(m *testing.M) {
|
||||||
func prepareTestDir() string {
|
func prepareTestDir() string {
|
||||||
const dir = "./agh-test"
|
const dir = "./agh-test"
|
||||||
_ = os.RemoveAll(dir)
|
_ = os.RemoveAll(dir)
|
||||||
_ = os.MkdirAll(dir, 0755)
|
_ = os.MkdirAll(dir, 0o755)
|
||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ func TestAuth(t *testing.T) {
|
||||||
fn := filepath.Join(dir, "sessions.db")
|
fn := filepath.Join(dir, "sessions.db")
|
||||||
|
|
||||||
users := []User{
|
users := []User{
|
||||||
User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
||||||
}
|
}
|
||||||
a := InitAuth(fn, nil, 60)
|
a := InitAuth(fn, nil, 60)
|
||||||
s := session{}
|
s := session{}
|
||||||
|
@ -41,7 +41,8 @@ func TestAuth(t *testing.T) {
|
||||||
assert.True(t, a.CheckSession("notfound") == -1)
|
assert.True(t, a.CheckSession("notfound") == -1)
|
||||||
a.RemoveSession("notfound")
|
a.RemoveSession("notfound")
|
||||||
|
|
||||||
sess := getSession(&users[0])
|
sess, err := getSession(&users[0])
|
||||||
|
assert.Nil(t, err)
|
||||||
sessStr := hex.EncodeToString(sess)
|
sessStr := hex.EncodeToString(sess)
|
||||||
|
|
||||||
now := time.Now().UTC().Unix()
|
now := time.Now().UTC().Unix()
|
||||||
|
@ -105,7 +106,7 @@ func TestAuthHTTP(t *testing.T) {
|
||||||
fn := filepath.Join(dir, "sessions.db")
|
fn := filepath.Join(dir, "sessions.db")
|
||||||
|
|
||||||
users := []User{
|
users := []User{
|
||||||
User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
||||||
}
|
}
|
||||||
Context.auth = InitAuth(fn, users, 60)
|
Context.auth = InitAuth(fn, users, 60)
|
||||||
|
|
||||||
|
@ -136,7 +137,8 @@ func TestAuthHTTP(t *testing.T) {
|
||||||
assert.True(t, handlerCalled)
|
assert.True(t, handlerCalled)
|
||||||
|
|
||||||
// perform login
|
// perform login
|
||||||
cookie := Context.auth.httpCookie(loginJSON{Name: "name", Password: "password"})
|
cookie, err := Context.auth.httpCookie(loginJSON{Name: "name", Password: "password"})
|
||||||
|
assert.Nil(t, err)
|
||||||
assert.True(t, cookie != "")
|
assert.True(t, cookie != "")
|
||||||
|
|
||||||
// get /
|
// get /
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,8 +19,10 @@ var GLMode bool
|
||||||
|
|
||||||
var glFilePrefix = "/tmp/gl_token_"
|
var glFilePrefix = "/tmp/gl_token_"
|
||||||
|
|
||||||
const glTokenTimeoutSeconds = 3600
|
const (
|
||||||
const glCookieName = "Admin-Token"
|
glTokenTimeoutSeconds = 3600
|
||||||
|
glCookieName = "Admin-Token"
|
||||||
|
)
|
||||||
|
|
||||||
func glProcessRedirect(w http.ResponseWriter, r *http.Request) bool {
|
func glProcessRedirect(w http.ResponseWriter, r *http.Request) bool {
|
||||||
if !GLMode {
|
if !GLMode {
|
||||||
|
@ -71,14 +74,28 @@ func archIsLittleEndian() bool {
|
||||||
return (b == 0x04)
|
return (b == 0x04)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MaxFileSize is a maximum file length in bytes.
|
||||||
|
const MaxFileSize = 1024 * 1024
|
||||||
|
|
||||||
func glGetTokenDate(file string) uint32 {
|
func glGetTokenDate(file string) uint32 {
|
||||||
f, err := os.Open(file)
|
f, err := os.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("os.Open: %s", err)
|
log.Error("os.Open: %s", err)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
fileReadCloser, err := aghio.LimitReadCloser(f, MaxFileSize)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("LimitReadCloser: %s", err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
defer fileReadCloser.Close()
|
||||||
|
|
||||||
var dateToken uint32
|
var dateToken uint32
|
||||||
bs, err := ioutil.ReadAll(f)
|
|
||||||
|
// This use of ReadAll is now safe, because we limited reader.
|
||||||
|
bs, err := ioutil.ReadAll(fileReadCloser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("ioutil.ReadAll: %s", err)
|
log.Error("ioutil.ReadAll: %s", err)
|
||||||
return 0
|
return 0
|
|
@ -25,7 +25,7 @@ func TestAuthGL(t *testing.T) {
|
||||||
} else {
|
} else {
|
||||||
binary.BigEndian.PutUint32(data, tval)
|
binary.BigEndian.PutUint32(data, tval)
|
||||||
}
|
}
|
||||||
assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0644))
|
assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0o644))
|
||||||
assert.False(t, glCheckToken("test"))
|
assert.False(t, glCheckToken("test"))
|
||||||
|
|
||||||
tval = uint32(time.Now().UTC().Unix() + 60)
|
tval = uint32(time.Now().UTC().Unix() + 60)
|
||||||
|
@ -35,7 +35,7 @@ func TestAuthGL(t *testing.T) {
|
||||||
} else {
|
} else {
|
||||||
binary.BigEndian.PutUint32(data, tval)
|
binary.BigEndian.PutUint32(data, tval)
|
||||||
}
|
}
|
||||||
assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0644))
|
assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0o644))
|
||||||
r, _ := http.NewRequest("GET", "http://localhost/", nil)
|
r, _ := http.NewRequest("GET", "http://localhost/", nil)
|
||||||
r.AddCookie(&http.Cookie{Name: glCookieName, Value: "test"})
|
r.AddCookie(&http.Cookie{Name: glCookieName, Value: "test"})
|
||||||
assert.True(t, glProcessCookie(r))
|
assert.True(t, glProcessCookie(r))
|
|
@ -570,31 +570,35 @@ func (clients *clientsContainer) SetWhoisInfo(ip string, info [][]string) {
|
||||||
// so we overwrite existing entries with an equal or higher priority
|
// so we overwrite existing entries with an equal or higher priority
|
||||||
func (clients *clientsContainer) AddHost(ip, host string, source clientSource) (bool, error) {
|
func (clients *clientsContainer) AddHost(ip, host string, source clientSource) (bool, error) {
|
||||||
clients.lock.Lock()
|
clients.lock.Lock()
|
||||||
b, e := clients.addHost(ip, host, source)
|
b := clients.addHost(ip, host, source)
|
||||||
clients.lock.Unlock()
|
clients.lock.Unlock()
|
||||||
return b, e
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (clients *clientsContainer) addHost(ip, host string, source clientSource) (bool, error) {
|
func (clients *clientsContainer) addHost(ip, host string, source clientSource) (addedNew bool) {
|
||||||
// check auto-clients index
|
|
||||||
ch, ok := clients.ipHost[ip]
|
ch, ok := clients.ipHost[ip]
|
||||||
if ok && ch.Source > source {
|
if ok {
|
||||||
return false, nil
|
if ch.Source > source {
|
||||||
} else if ok {
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
ch.Source = source
|
ch.Source = source
|
||||||
} else {
|
} else {
|
||||||
ch = &ClientHost{
|
ch = &ClientHost{
|
||||||
Host: host,
|
Host: host,
|
||||||
Source: source,
|
Source: source,
|
||||||
}
|
}
|
||||||
|
|
||||||
clients.ipHost[ip] = ch
|
clients.ipHost[ip] = ch
|
||||||
}
|
}
|
||||||
log.Debug("Clients: added %q -> %q [%d]", ip, host, len(clients.ipHost))
|
|
||||||
return true, nil
|
log.Debug("clients: added %q -> %q [%d]", ip, host, len(clients.ipHost))
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all entries that match the specified source
|
// Remove all entries that match the specified source
|
||||||
func (clients *clientsContainer) rmHosts(source clientSource) int {
|
func (clients *clientsContainer) rmHosts(source clientSource) {
|
||||||
n := 0
|
n := 0
|
||||||
for k, v := range clients.ipHost {
|
for k, v := range clients.ipHost {
|
||||||
if v.Source == source {
|
if v.Source == source {
|
||||||
|
@ -602,8 +606,8 @@ func (clients *clientsContainer) rmHosts(source clientSource) int {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Debug("Clients: removed %d client aliases", n)
|
|
||||||
return n
|
log.Debug("clients: removed %d client aliases", n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// addFromHostsFile fills the clients hosts list from the system's hosts files.
|
// addFromHostsFile fills the clients hosts list from the system's hosts files.
|
||||||
|
@ -613,15 +617,12 @@ func (clients *clientsContainer) addFromHostsFile() {
|
||||||
clients.lock.Lock()
|
clients.lock.Lock()
|
||||||
defer clients.lock.Unlock()
|
defer clients.lock.Unlock()
|
||||||
|
|
||||||
_ = clients.rmHosts(ClientSourceHostsFile)
|
clients.rmHosts(ClientSourceHostsFile)
|
||||||
|
|
||||||
n := 0
|
n := 0
|
||||||
for ip, names := range hosts {
|
for ip, names := range hosts {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
ok, err := clients.addHost(ip, name, ClientSourceHostsFile)
|
ok := clients.addHost(ip, name, ClientSourceHostsFile)
|
||||||
if err != nil {
|
|
||||||
log.Debug("Clients: %s", err)
|
|
||||||
}
|
|
||||||
if ok {
|
if ok {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
@ -650,7 +651,7 @@ func (clients *clientsContainer) addFromSystemARP() {
|
||||||
|
|
||||||
clients.lock.Lock()
|
clients.lock.Lock()
|
||||||
defer clients.lock.Unlock()
|
defer clients.lock.Unlock()
|
||||||
_ = clients.rmHosts(ClientSourceARP)
|
clients.rmHosts(ClientSourceARP)
|
||||||
|
|
||||||
n := 0
|
n := 0
|
||||||
lines := strings.Split(string(data), "\n")
|
lines := strings.Split(string(data), "\n")
|
||||||
|
@ -668,10 +669,7 @@ func (clients *clientsContainer) addFromSystemARP() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, e := clients.addHost(ip, host, ClientSourceARP)
|
ok := clients.addHost(ip, host, ClientSourceARP)
|
||||||
if e != nil {
|
|
||||||
log.Tracef("%s", e)
|
|
||||||
}
|
|
||||||
if ok {
|
if ok {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
@ -689,7 +687,7 @@ func (clients *clientsContainer) addFromDHCP() {
|
||||||
clients.lock.Lock()
|
clients.lock.Lock()
|
||||||
defer clients.lock.Unlock()
|
defer clients.lock.Unlock()
|
||||||
|
|
||||||
_ = clients.rmHosts(ClientSourceDHCP)
|
clients.rmHosts(ClientSourceDHCP)
|
||||||
|
|
||||||
leases := clients.dhcpServer.Leases(dhcpd.LeasesAll)
|
leases := clients.dhcpServer.Leases(dhcpd.LeasesAll)
|
||||||
n := 0
|
n := 0
|
||||||
|
@ -697,7 +695,7 @@ func (clients *clientsContainer) addFromDHCP() {
|
||||||
if len(l.Hostname) == 0 {
|
if len(l.Hostname) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ok, _ := clients.addHost(l.IP.String(), l.Hostname, ClientSourceDHCP)
|
ok := clients.addHost(l.IP.String(), l.Hostname, ClientSourceDHCP)
|
||||||
if ok {
|
if ok {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,144 +12,155 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestClients(t *testing.T) {
|
func TestClients(t *testing.T) {
|
||||||
var c Client
|
|
||||||
var e error
|
|
||||||
var b bool
|
|
||||||
clients := clientsContainer{}
|
clients := clientsContainer{}
|
||||||
clients.testing = true
|
clients.testing = true
|
||||||
|
|
||||||
clients.Init(nil, nil, nil)
|
clients.Init(nil, nil, nil)
|
||||||
|
|
||||||
// add
|
t.Run("add_success", func(t *testing.T) {
|
||||||
c = Client{
|
c := Client{
|
||||||
IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
|
IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
|
||||||
Name: "client1",
|
Name: "client1",
|
||||||
}
|
}
|
||||||
b, e = clients.Add(c)
|
|
||||||
if !b || e != nil {
|
|
||||||
t.Fatalf("Add #1")
|
|
||||||
}
|
|
||||||
|
|
||||||
// add #2
|
b, err := clients.Add(c)
|
||||||
c = Client{
|
assert.True(t, b)
|
||||||
IDs: []string{"2.2.2.2"},
|
assert.Nil(t, err)
|
||||||
Name: "client2",
|
|
||||||
}
|
|
||||||
b, e = clients.Add(c)
|
|
||||||
if !b || e != nil {
|
|
||||||
t.Fatalf("Add #2")
|
|
||||||
}
|
|
||||||
|
|
||||||
c, b = clients.Find("1.1.1.1")
|
c = Client{
|
||||||
assert.True(t, b && c.Name == "client1")
|
IDs: []string{"2.2.2.2"},
|
||||||
|
Name: "client2",
|
||||||
|
}
|
||||||
|
|
||||||
c, b = clients.Find("1:2:3::4")
|
b, err = clients.Add(c)
|
||||||
assert.True(t, b && c.Name == "client1")
|
assert.True(t, b)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
c, b = clients.Find("2.2.2.2")
|
c, b = clients.Find("1.1.1.1")
|
||||||
assert.True(t, b && c.Name == "client2")
|
assert.True(t, b && c.Name == "client1")
|
||||||
|
|
||||||
// failed add - name in use
|
c, b = clients.Find("1:2:3::4")
|
||||||
c = Client{
|
assert.True(t, b && c.Name == "client1")
|
||||||
IDs: []string{"1.2.3.5"},
|
|
||||||
Name: "client1",
|
|
||||||
}
|
|
||||||
b, _ = clients.Add(c)
|
|
||||||
if b {
|
|
||||||
t.Fatalf("Add - name in use")
|
|
||||||
}
|
|
||||||
|
|
||||||
// failed add - ip in use
|
c, b = clients.Find("2.2.2.2")
|
||||||
c = Client{
|
assert.True(t, b && c.Name == "client2")
|
||||||
IDs: []string{"2.2.2.2"},
|
|
||||||
Name: "client3",
|
|
||||||
}
|
|
||||||
b, e = clients.Add(c)
|
|
||||||
if b || e == nil {
|
|
||||||
t.Fatalf("Add - ip in use")
|
|
||||||
}
|
|
||||||
|
|
||||||
// get
|
assert.True(t, !clients.Exists("1.2.3.4", ClientSourceHostsFile))
|
||||||
assert.True(t, !clients.Exists("1.2.3.4", ClientSourceHostsFile))
|
assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
||||||
assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
assert.True(t, clients.Exists("2.2.2.2", ClientSourceHostsFile))
|
||||||
assert.True(t, clients.Exists("2.2.2.2", ClientSourceHostsFile))
|
})
|
||||||
|
|
||||||
// failed update - no such name
|
t.Run("add_fail_name", func(t *testing.T) {
|
||||||
c.IDs = []string{"1.2.3.0"}
|
c := Client{
|
||||||
c.Name = "client3"
|
IDs: []string{"1.2.3.5"},
|
||||||
if clients.Update("client3", c) == nil {
|
Name: "client1",
|
||||||
t.Fatalf("Update")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// failed update - name in use
|
b, err := clients.Add(c)
|
||||||
c.IDs = []string{"1.2.3.0"}
|
assert.False(t, b)
|
||||||
c.Name = "client2"
|
assert.Nil(t, err)
|
||||||
if clients.Update("client1", c) == nil {
|
})
|
||||||
t.Fatalf("Update - name in use")
|
|
||||||
}
|
|
||||||
|
|
||||||
// failed update - ip in use
|
t.Run("add_fail_ip", func(t *testing.T) {
|
||||||
c.IDs = []string{"2.2.2.2"}
|
c := Client{
|
||||||
c.Name = "client1"
|
IDs: []string{"2.2.2.2"},
|
||||||
if clients.Update("client1", c) == nil {
|
Name: "client3",
|
||||||
t.Fatalf("Update - ip in use")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
b, err := clients.Add(c)
|
||||||
c.IDs = []string{"1.1.1.2"}
|
assert.False(t, b)
|
||||||
c.Name = "client1"
|
assert.NotNil(t, err)
|
||||||
if clients.Update("client1", c) != nil {
|
})
|
||||||
t.Fatalf("Update")
|
|
||||||
}
|
|
||||||
|
|
||||||
// get after update
|
t.Run("update_fail_name", func(t *testing.T) {
|
||||||
assert.True(t, !clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
c := Client{
|
||||||
assert.True(t, clients.Exists("1.1.1.2", ClientSourceHostsFile))
|
IDs: []string{"1.2.3.0"},
|
||||||
|
Name: "client3",
|
||||||
|
}
|
||||||
|
|
||||||
// update - rename
|
err := clients.Update("client3", c)
|
||||||
c.IDs = []string{"1.1.1.2"}
|
assert.NotNil(t, err)
|
||||||
c.Name = "client1-renamed"
|
|
||||||
c.UseOwnSettings = true
|
|
||||||
assert.True(t, clients.Update("client1", c) == nil)
|
|
||||||
c = Client{}
|
|
||||||
c, b = clients.Find("1.1.1.2")
|
|
||||||
assert.True(t, b && c.Name == "client1-renamed" && c.IDs[0] == "1.1.1.2" && c.UseOwnSettings)
|
|
||||||
assert.True(t, clients.list["client1"] == nil)
|
|
||||||
|
|
||||||
// failed remove - no such name
|
c = Client{
|
||||||
if clients.Del("client3") {
|
IDs: []string{"1.2.3.0"},
|
||||||
t.Fatalf("Del - no such name")
|
Name: "client2",
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove
|
err = clients.Update("client3", c)
|
||||||
assert.True(t, !(!clients.Del("client1-renamed") || clients.Exists("1.1.1.2", ClientSourceHostsFile)))
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
// add host client
|
t.Run("update_fail_ip", func(t *testing.T) {
|
||||||
b, e = clients.AddHost("1.1.1.1", "host", ClientSourceARP)
|
c := Client{
|
||||||
if !b || e != nil {
|
IDs: []string{"2.2.2.2"},
|
||||||
t.Fatalf("clientAddHost")
|
Name: "client1",
|
||||||
}
|
}
|
||||||
|
|
||||||
// failed add - ip exists
|
err := clients.Update("client1", c)
|
||||||
b, e = clients.AddHost("1.1.1.1", "host1", ClientSourceRDNS)
|
assert.NotNil(t, err)
|
||||||
if b || e != nil {
|
})
|
||||||
t.Fatalf("clientAddHost - ip exists")
|
|
||||||
}
|
|
||||||
|
|
||||||
// overwrite with new data
|
t.Run("update_success", func(t *testing.T) {
|
||||||
b, e = clients.AddHost("1.1.1.1", "host2", ClientSourceARP)
|
c := Client{
|
||||||
if !b || e != nil {
|
IDs: []string{"1.1.1.2"},
|
||||||
t.Fatalf("clientAddHost - overwrite with new data")
|
Name: "client1",
|
||||||
}
|
}
|
||||||
|
|
||||||
// overwrite with new data (higher priority)
|
err := clients.Update("client1", c)
|
||||||
b, e = clients.AddHost("1.1.1.1", "host3", ClientSourceHostsFile)
|
assert.Nil(t, err)
|
||||||
if !b || e != nil {
|
|
||||||
t.Fatalf("clientAddHost - overwrite with new data (higher priority)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// get
|
assert.True(t, !clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
||||||
assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
assert.True(t, clients.Exists("1.1.1.2", ClientSourceHostsFile))
|
||||||
|
|
||||||
|
c = Client{
|
||||||
|
IDs: []string{"1.1.1.2"},
|
||||||
|
Name: "client1-renamed",
|
||||||
|
UseOwnSettings: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = clients.Update("client1", c)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
c, b := clients.Find("1.1.1.2")
|
||||||
|
assert.True(t, b)
|
||||||
|
assert.True(t, c.Name == "client1-renamed")
|
||||||
|
assert.True(t, c.IDs[0] == "1.1.1.2")
|
||||||
|
assert.True(t, c.UseOwnSettings)
|
||||||
|
assert.Nil(t, clients.list["client1"])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("del_success", func(t *testing.T) {
|
||||||
|
b := clients.Del("client1-renamed")
|
||||||
|
assert.True(t, b)
|
||||||
|
assert.False(t, clients.Exists("1.1.1.2", ClientSourceHostsFile))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("del_fail", func(t *testing.T) {
|
||||||
|
b := clients.Del("client3")
|
||||||
|
assert.False(t, b)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("addhost_success", func(t *testing.T) {
|
||||||
|
b, err := clients.AddHost("1.1.1.1", "host", ClientSourceARP)
|
||||||
|
assert.True(t, b)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
b, err = clients.AddHost("1.1.1.1", "host2", ClientSourceARP)
|
||||||
|
assert.True(t, b)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
b, err = clients.AddHost("1.1.1.1", "host3", ClientSourceHostsFile)
|
||||||
|
assert.True(t, b)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("addhost_fail", func(t *testing.T) {
|
||||||
|
b, err := clients.AddHost("1.1.1.1", "host1", ClientSourceRDNS)
|
||||||
|
assert.False(t, b)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientsWhois(t *testing.T) {
|
func TestClientsWhois(t *testing.T) {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package home
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -95,8 +94,8 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, _ *http
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert JSON object to Client object
|
// Convert JSON object to Client object
|
||||||
func jsonToClient(cj clientJSON) (*Client, error) {
|
func jsonToClient(cj clientJSON) (c *Client) {
|
||||||
c := Client{
|
return &Client{
|
||||||
Name: cj.Name,
|
Name: cj.Name,
|
||||||
IDs: cj.IDs,
|
IDs: cj.IDs,
|
||||||
Tags: cj.Tags,
|
Tags: cj.Tags,
|
||||||
|
@ -111,7 +110,6 @@ func jsonToClient(cj clientJSON) (*Client, error) {
|
||||||
|
|
||||||
Upstreams: cj.Upstreams,
|
Upstreams: cj.Upstreams,
|
||||||
}
|
}
|
||||||
return &c, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert Client object to JSON
|
// Convert Client object to JSON
|
||||||
|
@ -150,24 +148,15 @@ func clientHostToJSON(ip string, ch ClientHost) clientJSON {
|
||||||
|
|
||||||
// Add a new client
|
// Add a new client
|
||||||
func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.Request) {
|
func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.Request) {
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cj := clientJSON{}
|
cj := clientJSON{}
|
||||||
err = json.Unmarshal(body, &cj)
|
err := json.NewDecoder(r.Body).Decode(&cj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
httpError(w, http.StatusBadRequest, "failed to process request body: %s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := jsonToClient(cj)
|
c := jsonToClient(cj)
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusBadRequest, "%s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok, err := clients.Add(*c)
|
ok, err := clients.Add(*c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "%s", err)
|
httpError(w, http.StatusBadRequest, "%s", err)
|
||||||
|
@ -183,16 +172,17 @@ func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.
|
||||||
|
|
||||||
// Remove client
|
// Remove client
|
||||||
func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.Request) {
|
func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.Request) {
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
cj := clientJSON{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&cj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
|
httpError(w, http.StatusBadRequest, "failed to process request body: %s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cj := clientJSON{}
|
if len(cj.Name) == 0 {
|
||||||
err = json.Unmarshal(body, &cj)
|
httpError(w, http.StatusBadRequest, "client's name must be non-empty")
|
||||||
if err != nil || len(cj.Name) == 0 {
|
|
||||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,29 +201,20 @@ type updateJSON struct {
|
||||||
|
|
||||||
// Update client's properties
|
// Update client's properties
|
||||||
func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *http.Request) {
|
func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *http.Request) {
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
dj := updateJSON{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&dj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
|
httpError(w, http.StatusBadRequest, "failed to process request body: %s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var dj updateJSON
|
|
||||||
err = json.Unmarshal(body, &dj)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(dj.Name) == 0 {
|
if len(dj.Name) == 0 {
|
||||||
httpError(w, http.StatusBadRequest, "Invalid request")
|
httpError(w, http.StatusBadRequest, "Invalid request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := jsonToClient(dj.Data)
|
c := jsonToClient(dj.Data)
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusBadRequest, "%s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = clients.Update(dj.Name, *c)
|
err = clients.Update(dj.Name, *c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "%s", err)
|
httpError(w, http.StatusBadRequest, "%s", err)
|
|
@ -99,6 +99,16 @@ type tlsConfigSettings struct {
|
||||||
PortDNSOverTLS int `yaml:"port_dns_over_tls" json:"port_dns_over_tls,omitempty"` // DNS-over-TLS port. If 0, DOT will be disabled
|
PortDNSOverTLS int `yaml:"port_dns_over_tls" json:"port_dns_over_tls,omitempty"` // DNS-over-TLS port. If 0, DOT will be disabled
|
||||||
PortDNSOverQUIC uint16 `yaml:"port_dns_over_quic" json:"port_dns_over_quic,omitempty"` // DNS-over-QUIC port. If 0, DoQ will be disabled
|
PortDNSOverQUIC uint16 `yaml:"port_dns_over_quic" json:"port_dns_over_quic,omitempty"` // DNS-over-QUIC port. If 0, DoQ will be disabled
|
||||||
|
|
||||||
|
// PortDNSCrypt is the port for DNSCrypt requests. If it's zero,
|
||||||
|
// DNSCrypt is disabled.
|
||||||
|
PortDNSCrypt int `yaml:"port_dnscrypt" json:"port_dnscrypt"`
|
||||||
|
// DNSCryptConfigFile is the path to the DNSCrypt config file. Must be
|
||||||
|
// set if PortDNSCrypt is not zero.
|
||||||
|
//
|
||||||
|
// See https://github.com/AdguardTeam/dnsproxy and
|
||||||
|
// https://github.com/ameshkov/dnscrypt.
|
||||||
|
DNSCryptConfigFile string `yaml:"dnscrypt_config_file" json:"dnscrypt_config_file"`
|
||||||
|
|
||||||
// Allow DOH queries via unencrypted HTTP (e.g. for reverse proxying)
|
// Allow DOH queries via unencrypted HTTP (e.g. for reverse proxying)
|
||||||
AllowUnencryptedDOH bool `yaml:"allow_unencrypted_doh" json:"allow_unencrypted_doh"`
|
AllowUnencryptedDOH bool `yaml:"allow_unencrypted_doh" json:"allow_unencrypted_doh"`
|
||||||
|
|
||||||
|
|
|
@ -107,24 +107,24 @@ func registerControlHandlers() {
|
||||||
httpRegister(http.MethodGet, "/control/status", handleStatus)
|
httpRegister(http.MethodGet, "/control/status", handleStatus)
|
||||||
httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage)
|
httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage)
|
||||||
httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage)
|
httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage)
|
||||||
http.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON)))
|
Context.mux.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON)))
|
||||||
httpRegister(http.MethodPost, "/control/update", handleUpdate)
|
httpRegister(http.MethodPost, "/control/update", handleUpdate)
|
||||||
httpRegister(http.MethodGet, "/control/profile", handleGetProfile)
|
httpRegister(http.MethodGet, "/control/profile", handleGetProfile)
|
||||||
|
|
||||||
// No auth is necessary for DOH/DOT configurations
|
// No auth is necessary for DOH/DOT configurations
|
||||||
http.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoh))
|
Context.mux.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDOH))
|
||||||
http.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDot))
|
Context.mux.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDOT))
|
||||||
RegisterAuthHandlers()
|
RegisterAuthHandlers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func httpRegister(method string, url string, handler func(http.ResponseWriter, *http.Request)) {
|
func httpRegister(method, url string, handler func(http.ResponseWriter, *http.Request)) {
|
||||||
if len(method) == 0 {
|
if len(method) == 0 {
|
||||||
// "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method
|
// "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method
|
||||||
http.HandleFunc(url, postInstall(handler))
|
Context.mux.HandleFunc(url, postInstall(handler))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler)))))
|
Context.mux.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler)))))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
@ -201,7 +201,6 @@ func preInstallHandler(handler http.Handler) http.Handler {
|
||||||
// it also enforces HTTPS if it is enabled and configured
|
// it also enforces HTTPS if it is enabled and configured
|
||||||
func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if Context.firstRun &&
|
if Context.firstRun &&
|
||||||
!strings.HasPrefix(r.URL.Path, "/install.") &&
|
!strings.HasPrefix(r.URL.Path, "/install.") &&
|
||||||
!strings.HasPrefix(r.URL.Path, "/assets/") {
|
!strings.HasPrefix(r.URL.Path, "/assets/") {
|
||||||
|
|
|
@ -196,9 +196,9 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request
|
||||||
}
|
}
|
||||||
if (status&statusUpdateRequired) != 0 && fj.Data.Enabled {
|
if (status&statusUpdateRequired) != 0 && fj.Data.Enabled {
|
||||||
// download new filter and apply its rules
|
// download new filter and apply its rules
|
||||||
flags := FilterRefreshBlocklists
|
flags := filterRefreshBlocklists
|
||||||
if fj.Whitelist {
|
if fj.Whitelist {
|
||||||
flags = FilterRefreshAllowlists
|
flags = filterRefreshAllowlists
|
||||||
}
|
}
|
||||||
nUpdated, _ := f.refreshFilters(flags, true)
|
nUpdated, _ := f.refreshFilters(flags, true)
|
||||||
// if at least 1 filter has been updated, refreshFilters() restarts the filtering automatically
|
// if at least 1 filter has been updated, refreshFilters() restarts the filtering automatically
|
||||||
|
@ -214,6 +214,7 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Filtering) handleFilteringSetRules(w http.ResponseWriter, r *http.Request) {
|
func (f *Filtering) handleFilteringSetRules(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// This use of ReadAll is safe, because request's body is now limited.
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "Failed to read request body: %s", err)
|
httpError(w, http.StatusBadRequest, "Failed to read request body: %s", err)
|
||||||
|
@ -243,11 +244,11 @@ func (f *Filtering) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.controlLock.Unlock()
|
Context.controlLock.Unlock()
|
||||||
flags := FilterRefreshBlocklists
|
flags := filterRefreshBlocklists
|
||||||
if req.White {
|
if req.White {
|
||||||
flags = FilterRefreshAllowlists
|
flags = filterRefreshAllowlists
|
||||||
}
|
}
|
||||||
resp.Updated, err = f.refreshFilters(flags|FilterRefreshForce, false)
|
resp.Updated, err = f.refreshFilters(flags|filterRefreshForce, false)
|
||||||
Context.controlLock.Lock()
|
Context.controlLock.Lock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusInternalServerError, "%s", err)
|
httpError(w, http.StatusInternalServerError, "%s", err)
|
||||||
|
@ -345,10 +346,25 @@ func (f *Filtering) handleFilteringConfig(w http.ResponseWriter, r *http.Request
|
||||||
enableFilters(true)
|
enableFilters(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type checkHostRespRule struct {
|
||||||
|
FilterListID int64 `json:"filter_list_id"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
type checkHostResp struct {
|
type checkHostResp struct {
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
FilterID int64 `json:"filter_id"`
|
|
||||||
Rule string `json:"rule"`
|
// FilterID is the ID of the rule's filter list.
|
||||||
|
//
|
||||||
|
// Deprecated: Use Rules[*].FilterListID.
|
||||||
|
FilterID int64 `json:"filter_id"`
|
||||||
|
|
||||||
|
// Rule is the text of the matched rule.
|
||||||
|
//
|
||||||
|
// Deprecated: Use Rules[*].Text.
|
||||||
|
Rule string `json:"rule"`
|
||||||
|
|
||||||
|
Rules []*checkHostRespRule `json:"rules"`
|
||||||
|
|
||||||
// for FilteredBlockedService:
|
// for FilteredBlockedService:
|
||||||
SvcName string `json:"service_name"`
|
SvcName string `json:"service_name"`
|
||||||
|
@ -373,11 +389,23 @@ func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
resp := checkHostResp{}
|
resp := checkHostResp{}
|
||||||
resp.Reason = result.Reason.String()
|
resp.Reason = result.Reason.String()
|
||||||
resp.FilterID = result.FilterID
|
|
||||||
resp.Rule = result.Rule
|
|
||||||
resp.SvcName = result.ServiceName
|
resp.SvcName = result.ServiceName
|
||||||
resp.CanonName = result.CanonName
|
resp.CanonName = result.CanonName
|
||||||
resp.IPList = result.IPList
|
resp.IPList = result.IPList
|
||||||
|
|
||||||
|
if len(result.Rules) > 0 {
|
||||||
|
resp.FilterID = result.Rules[0].FilterListID
|
||||||
|
resp.Rule = result.Rules[0].Text
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Rules = make([]*checkHostRespRule, len(result.Rules))
|
||||||
|
for i, r := range result.Rules {
|
||||||
|
resp.Rules[i] = &checkHostRespRule{
|
||||||
|
FilterListID: r.FilterListID,
|
||||||
|
Text: r.Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
js, err := json.Marshal(resp)
|
js, err := json.Marshal(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusInternalServerError, "json encode: %s", err)
|
httpError(w, http.StatusInternalServerError, "json encode: %s", err)
|
|
@ -15,7 +15,7 @@ import (
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
@ -167,7 +167,7 @@ func handleStaticIP(ip string, set bool) staticIPJSON {
|
||||||
|
|
||||||
if set {
|
if set {
|
||||||
// Try to set static IP for the specified interface
|
// Try to set static IP for the specified interface
|
||||||
err := dhcpd.SetStaticIP(interfaceName)
|
err := sysutil.IfaceSetStaticIP(interfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Static = "error"
|
resp.Static = "error"
|
||||||
resp.Error = err.Error()
|
resp.Error = err.Error()
|
||||||
|
@ -177,7 +177,7 @@ func handleStaticIP(ip string, set bool) staticIPJSON {
|
||||||
|
|
||||||
// Fallthrough here even if we set static IP
|
// Fallthrough here even if we set static IP
|
||||||
// Check if we have a static IP and return the details
|
// Check if we have a static IP and return the details
|
||||||
isStaticIP, err := dhcpd.HasStaticIP(interfaceName)
|
isStaticIP, err := sysutil.IfaceHasStaticIP(interfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Static = "error"
|
resp.Static = "error"
|
||||||
resp.Error = err.Error()
|
resp.Error = err.Error()
|
||||||
|
@ -273,7 +273,7 @@ type applyConfigReq struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy installation parameters between two configuration objects
|
// Copy installation parameters between two configuration objects
|
||||||
func copyInstallSettings(dst *configuration, src *configuration) {
|
func copyInstallSettings(dst, src *configuration) {
|
||||||
dst.BindHost = src.BindHost
|
dst.BindHost = src.BindHost
|
||||||
dst.BindPort = src.BindPort
|
dst.BindPort = src.BindPort
|
||||||
dst.DNS.BindHost = src.DNS.BindHost
|
dst.DNS.BindHost = src.DNS.BindHost
|
||||||
|
@ -372,7 +372,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (web *Web) registerInstallHandlers() {
|
func (web *Web) registerInstallHandlers() {
|
||||||
http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses)))
|
Context.mux.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses)))
|
||||||
http.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig)))
|
Context.mux.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig)))
|
||||||
http.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure)))
|
Context.mux.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure)))
|
||||||
}
|
}
|
|
@ -10,8 +10,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/update"
|
"github.com/AdguardTeam/AdGuardHome/internal/update"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -104,12 +104,7 @@ func getVersionResp(info update.VersionInfo) []byte {
|
||||||
tlsConf.PortDNSOverQUIC < 1024)) ||
|
tlsConf.PortDNSOverQUIC < 1024)) ||
|
||||||
config.BindPort < 1024 ||
|
config.BindPort < 1024 ||
|
||||||
config.DNS.Port < 1024) {
|
config.DNS.Port < 1024) {
|
||||||
// On UNIX, if we're running under a regular user,
|
canUpdate, _ = sysutil.CanBindPrivilegedPorts()
|
||||||
// but with CAP_NET_BIND_SERVICE set on a binary file,
|
|
||||||
// and we're listening on ports <1024,
|
|
||||||
// we won't be able to restart after we replace the binary file,
|
|
||||||
// because we'll lose CAP_NET_BIND_SERVICE capability.
|
|
||||||
canUpdate, _ = util.HaveAdminRights()
|
|
||||||
}
|
}
|
||||||
ret["can_autoupdate"] = canUpdate
|
ret["can_autoupdate"] = canUpdate
|
||||||
}
|
}
|
|
@ -81,7 +81,7 @@ func TestTargzFileUnpack(t *testing.T) {
|
||||||
fn := "../dist/AdGuardHome_linux_amd64.tar.gz"
|
fn := "../dist/AdGuardHome_linux_amd64.tar.gz"
|
||||||
outdir := "../test-unpack"
|
outdir := "../test-unpack"
|
||||||
defer os.RemoveAll(outdir)
|
defer os.RemoveAll(outdir)
|
||||||
_ = os.Mkdir(outdir, 0755)
|
_ = os.Mkdir(outdir, 0o755)
|
||||||
files, e := targzFileUnpack(fn, outdir)
|
files, e := targzFileUnpack(fn, outdir)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
t.Fatalf("FAILED: %s", e)
|
t.Fatalf("FAILED: %s", e)
|
||||||
|
@ -92,7 +92,7 @@ func TestTargzFileUnpack(t *testing.T) {
|
||||||
func TestZipFileUnpack(t *testing.T) {
|
func TestZipFileUnpack(t *testing.T) {
|
||||||
fn := "../dist/AdGuardHome_windows_amd64.zip"
|
fn := "../dist/AdGuardHome_windows_amd64.zip"
|
||||||
outdir := "../test-unpack"
|
outdir := "../test-unpack"
|
||||||
_ = os.Mkdir(outdir, 0755)
|
_ = os.Mkdir(outdir, 0o755)
|
||||||
files, e := zipFileUnpack(fn, outdir)
|
files, e := zipFileUnpack(fn, outdir)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
t.Fatalf("FAILED: %s", e)
|
t.Fatalf("FAILED: %s", e)
|
|
@ -3,8 +3,10 @@ package home
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||||
|
@ -12,6 +14,8 @@ import (
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/ameshkov/dnscrypt/v2"
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Called by other modules when configuration is changed
|
// Called by other modules when configuration is changed
|
||||||
|
@ -70,7 +74,12 @@ func initDNSServer() error {
|
||||||
}
|
}
|
||||||
Context.dnsServer = dnsforward.NewServer(p)
|
Context.dnsServer = dnsforward.NewServer(p)
|
||||||
Context.clients.dnsServer = Context.dnsServer
|
Context.clients.dnsServer = Context.dnsServer
|
||||||
dnsConfig := generateServerConfig()
|
dnsConfig, err := generateServerConfig()
|
||||||
|
if err != nil {
|
||||||
|
closeDNSServer()
|
||||||
|
return fmt.Errorf("generateServerConfig: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
err = Context.dnsServer.Prepare(&dnsConfig)
|
err = Context.dnsServer.Prepare(&dnsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
closeDNSServer()
|
closeDNSServer()
|
||||||
|
@ -88,60 +97,6 @@ func isRunning() bool {
|
||||||
return Context.dnsServer != nil && Context.dnsServer.IsRunning()
|
return Context.dnsServer != nil && Context.dnsServer.IsRunning()
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint (gocyclo)
|
|
||||||
// Return TRUE if IP is within public Internet IP range
|
|
||||||
func isPublicIP(ip net.IP) bool {
|
|
||||||
ip4 := ip.To4()
|
|
||||||
if ip4 != nil {
|
|
||||||
switch ip4[0] {
|
|
||||||
case 0:
|
|
||||||
return false // software
|
|
||||||
case 10:
|
|
||||||
return false // private network
|
|
||||||
case 127:
|
|
||||||
return false // loopback
|
|
||||||
case 169:
|
|
||||||
if ip4[1] == 254 {
|
|
||||||
return false // link-local
|
|
||||||
}
|
|
||||||
case 172:
|
|
||||||
if ip4[1] >= 16 && ip4[1] <= 31 {
|
|
||||||
return false // private network
|
|
||||||
}
|
|
||||||
case 192:
|
|
||||||
if (ip4[1] == 0 && ip4[2] == 0) || // private network
|
|
||||||
(ip4[1] == 0 && ip4[2] == 2) || // documentation
|
|
||||||
(ip4[1] == 88 && ip4[2] == 99) || // reserved
|
|
||||||
(ip4[1] == 168) { // private network
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case 198:
|
|
||||||
if (ip4[1] == 18 || ip4[2] == 19) || // private network
|
|
||||||
(ip4[1] == 51 || ip4[2] == 100) { // documentation
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case 203:
|
|
||||||
if ip4[1] == 0 && ip4[2] == 113 { // documentation
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case 224:
|
|
||||||
if ip4[1] == 0 && ip4[2] == 0 { // multicast
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case 255:
|
|
||||||
if ip4[1] == 255 && ip4[2] == 255 && ip4[3] == 255 { // subnet
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func onDNSRequest(d *proxy.DNSContext) {
|
func onDNSRequest(d *proxy.DNSContext) {
|
||||||
ip := dnsforward.GetIPString(d.Addr)
|
ip := dnsforward.GetIPString(d.Addr)
|
||||||
if ip == "" {
|
if ip == "" {
|
||||||
|
@ -153,15 +108,16 @@ func onDNSRequest(d *proxy.DNSContext) {
|
||||||
if !ipAddr.IsLoopback() {
|
if !ipAddr.IsLoopback() {
|
||||||
Context.rdns.Begin(ip)
|
Context.rdns.Begin(ip)
|
||||||
}
|
}
|
||||||
if isPublicIP(ipAddr) {
|
if !Context.ipDetector.detectSpecialNetwork(ipAddr) {
|
||||||
Context.whois.Begin(ip)
|
Context.whois.Begin(ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateServerConfig() dnsforward.ServerConfig {
|
func generateServerConfig() (newconfig dnsforward.ServerConfig, err error) {
|
||||||
newconfig := dnsforward.ServerConfig{
|
bindHost := net.ParseIP(config.DNS.BindHost)
|
||||||
UDPListenAddr: &net.UDPAddr{IP: net.ParseIP(config.DNS.BindHost), Port: config.DNS.Port},
|
newconfig = dnsforward.ServerConfig{
|
||||||
TCPListenAddr: &net.TCPAddr{IP: net.ParseIP(config.DNS.BindHost), Port: config.DNS.Port},
|
UDPListenAddr: &net.UDPAddr{IP: bindHost, Port: config.DNS.Port},
|
||||||
|
TCPListenAddr: &net.TCPAddr{IP: bindHost, Port: config.DNS.Port},
|
||||||
FilteringConfig: config.DNS.FilteringConfig,
|
FilteringConfig: config.DNS.FilteringConfig,
|
||||||
ConfigModified: onConfigModified,
|
ConfigModified: onConfigModified,
|
||||||
HTTPRegister: httpRegister,
|
HTTPRegister: httpRegister,
|
||||||
|
@ -175,35 +131,86 @@ func generateServerConfig() dnsforward.ServerConfig {
|
||||||
|
|
||||||
if tlsConf.PortDNSOverTLS != 0 {
|
if tlsConf.PortDNSOverTLS != 0 {
|
||||||
newconfig.TLSListenAddr = &net.TCPAddr{
|
newconfig.TLSListenAddr = &net.TCPAddr{
|
||||||
IP: net.ParseIP(config.DNS.BindHost),
|
IP: bindHost,
|
||||||
Port: tlsConf.PortDNSOverTLS,
|
Port: tlsConf.PortDNSOverTLS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsConf.PortDNSOverQUIC != 0 {
|
if tlsConf.PortDNSOverQUIC != 0 {
|
||||||
newconfig.QUICListenAddr = &net.UDPAddr{
|
newconfig.QUICListenAddr = &net.UDPAddr{
|
||||||
IP: net.ParseIP(config.DNS.BindHost),
|
IP: bindHost,
|
||||||
Port: int(tlsConf.PortDNSOverQUIC),
|
Port: int(tlsConf.PortDNSOverQUIC),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tlsConf.PortDNSCrypt != 0 {
|
||||||
|
newconfig.DNSCryptConfig, err = newDNSCrypt(bindHost, tlsConf)
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error, because it's already
|
||||||
|
// wrapped by newDNSCrypt.
|
||||||
|
return dnsforward.ServerConfig{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newconfig.TLSv12Roots = Context.tlsRoots
|
newconfig.TLSv12Roots = Context.tlsRoots
|
||||||
newconfig.TLSCiphers = Context.tlsCiphers
|
newconfig.TLSCiphers = Context.tlsCiphers
|
||||||
newconfig.TLSAllowUnencryptedDOH = tlsConf.AllowUnencryptedDOH
|
newconfig.TLSAllowUnencryptedDOH = tlsConf.AllowUnencryptedDOH
|
||||||
|
|
||||||
newconfig.FilterHandler = applyAdditionalFiltering
|
newconfig.FilterHandler = applyAdditionalFiltering
|
||||||
newconfig.GetCustomUpstreamByClient = Context.clients.FindUpstreams
|
newconfig.GetCustomUpstreamByClient = Context.clients.FindUpstreams
|
||||||
return newconfig
|
|
||||||
|
return newconfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSEncryption struct {
|
func newDNSCrypt(bindHost net.IP, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) {
|
||||||
|
if tlsConf.DNSCryptConfigFile == "" {
|
||||||
|
return dnscc, agherr.Error("no dnscrypt_config_file")
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(tlsConf.DNSCryptConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
return dnscc, fmt.Errorf("opening dnscrypt config: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
rc := &dnscrypt.ResolverConfig{}
|
||||||
|
err = yaml.NewDecoder(f).Decode(rc)
|
||||||
|
if err != nil {
|
||||||
|
return dnscc, fmt.Errorf("decoding dnscrypt config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := rc.CreateCert()
|
||||||
|
if err != nil {
|
||||||
|
return dnscc, fmt.Errorf("creating dnscrypt cert: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
udpAddr := &net.UDPAddr{
|
||||||
|
IP: bindHost,
|
||||||
|
Port: tlsConf.PortDNSCrypt,
|
||||||
|
}
|
||||||
|
tcpAddr := &net.TCPAddr{
|
||||||
|
IP: bindHost,
|
||||||
|
Port: tlsConf.PortDNSCrypt,
|
||||||
|
}
|
||||||
|
|
||||||
|
return dnsforward.DNSCryptConfig{
|
||||||
|
UDPListenAddr: udpAddr,
|
||||||
|
TCPListenAddr: tcpAddr,
|
||||||
|
ResolverCert: cert,
|
||||||
|
ProviderName: rc.ProviderName,
|
||||||
|
Enabled: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dnsEncryption struct {
|
||||||
https string
|
https string
|
||||||
tls string
|
tls string
|
||||||
quic string
|
quic string
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDNSEncryption() DNSEncryption {
|
func getDNSEncryption() dnsEncryption {
|
||||||
dnsEncryption := DNSEncryption{}
|
dnsEncryption := dnsEncryption{}
|
||||||
|
|
||||||
tlsConf := tlsConfigSettings{}
|
tlsConf := tlsConfigSettings{}
|
||||||
|
|
||||||
|
@ -327,7 +334,7 @@ func startDNSServer() error {
|
||||||
if !ipAddr.IsLoopback() {
|
if !ipAddr.IsLoopback() {
|
||||||
Context.rdns.Begin(ip)
|
Context.rdns.Begin(ip)
|
||||||
}
|
}
|
||||||
if isPublicIP(ipAddr) {
|
if !Context.ipDetector.detectSpecialNetwork(ipAddr) {
|
||||||
Context.whois.Begin(ip)
|
Context.whois.Begin(ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,11 +342,16 @@ func startDNSServer() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func reconfigureDNSServer() error {
|
func reconfigureDNSServer() (err error) {
|
||||||
newconfig := generateServerConfig()
|
var newconfig dnsforward.ServerConfig
|
||||||
err := Context.dnsServer.Reconfigure(&newconfig)
|
newconfig, err = generateServerConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("couldn't start forwarding DNS server: %w", err)
|
return fmt.Errorf("generating forwarding dns server config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Context.dnsServer.Reconfigure(&newconfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("starting forwarding dns server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -254,7 +255,7 @@ func (f *Filtering) periodicallyRefreshFilters() {
|
||||||
isNetworkErr := false
|
isNetworkErr := false
|
||||||
if config.DNS.FiltersUpdateIntervalHours != 0 && atomic.CompareAndSwapUint32(&f.refreshStatus, 0, 1) {
|
if config.DNS.FiltersUpdateIntervalHours != 0 && atomic.CompareAndSwapUint32(&f.refreshStatus, 0, 1) {
|
||||||
f.refreshLock.Lock()
|
f.refreshLock.Lock()
|
||||||
_, isNetworkErr = f.refreshFiltersIfNecessary(FilterRefreshBlocklists | FilterRefreshAllowlists)
|
_, isNetworkErr = f.refreshFiltersIfNecessary(filterRefreshBlocklists | filterRefreshAllowlists)
|
||||||
f.refreshLock.Unlock()
|
f.refreshLock.Unlock()
|
||||||
f.refreshStatus = 0
|
f.refreshStatus = 0
|
||||||
if !isNetworkErr {
|
if !isNetworkErr {
|
||||||
|
@ -274,7 +275,7 @@ func (f *Filtering) periodicallyRefreshFilters() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh filters
|
// Refresh filters
|
||||||
// flags: FilterRefresh*
|
// flags: filterRefresh*
|
||||||
// important:
|
// important:
|
||||||
// TRUE: ignore the fact that we're currently updating the filters
|
// TRUE: ignore the fact that we're currently updating the filters
|
||||||
func (f *Filtering) refreshFilters(flags int, important bool) (int, error) {
|
func (f *Filtering) refreshFilters(flags int, important bool) (int, error) {
|
||||||
|
@ -367,14 +368,14 @@ func (f *Filtering) refreshFiltersArray(filters *[]filter, force bool) (int, []f
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FilterRefreshForce = 1 // ignore last file modification date
|
filterRefreshForce = 1 // ignore last file modification date
|
||||||
FilterRefreshAllowlists = 2 // update allow-lists
|
filterRefreshAllowlists = 2 // update allow-lists
|
||||||
FilterRefreshBlocklists = 4 // update block-lists
|
filterRefreshBlocklists = 4 // update block-lists
|
||||||
)
|
)
|
||||||
|
|
||||||
// Checks filters updates if necessary
|
// Checks filters updates if necessary
|
||||||
// If force is true, it ignores the filter.LastUpdated field value
|
// If force is true, it ignores the filter.LastUpdated field value
|
||||||
// flags: FilterRefresh*
|
// flags: filterRefresh*
|
||||||
//
|
//
|
||||||
// Algorithm:
|
// Algorithm:
|
||||||
// . Get the list of filters to be updated
|
// . Get the list of filters to be updated
|
||||||
|
@ -400,13 +401,13 @@ func (f *Filtering) refreshFiltersIfNecessary(flags int) (int, bool) {
|
||||||
netError := false
|
netError := false
|
||||||
netErrorW := false
|
netErrorW := false
|
||||||
force := false
|
force := false
|
||||||
if (flags & FilterRefreshForce) != 0 {
|
if (flags & filterRefreshForce) != 0 {
|
||||||
force = true
|
force = true
|
||||||
}
|
}
|
||||||
if (flags & FilterRefreshBlocklists) != 0 {
|
if (flags & filterRefreshBlocklists) != 0 {
|
||||||
updateCount, updateFilters, updateFlags, netError = f.refreshFiltersArray(&config.Filters, force)
|
updateCount, updateFilters, updateFlags, netError = f.refreshFiltersArray(&config.Filters, force)
|
||||||
}
|
}
|
||||||
if (flags & FilterRefreshAllowlists) != 0 {
|
if (flags & filterRefreshAllowlists) != 0 {
|
||||||
updateCountW := 0
|
updateCountW := 0
|
||||||
var updateFiltersW []filter
|
var updateFiltersW []filter
|
||||||
var updateFlagsW []bool
|
var updateFlagsW []bool
|
||||||
|
@ -497,46 +498,7 @@ func (f *Filtering) update(filter *filter) (bool, error) {
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint(gocyclo)
|
func (f *Filtering) read(reader io.Reader, tmpFile *os.File, filter *filter) (int, error) {
|
||||||
func (f *Filtering) updateIntl(filter *filter) (bool, error) {
|
|
||||||
log.Tracef("Downloading update for filter %d from %s", filter.ID, filter.URL)
|
|
||||||
|
|
||||||
tmpFile, err := ioutil.TempFile(filepath.Join(Context.getDataDir(), filterDir), "")
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if tmpFile != nil {
|
|
||||||
_ = tmpFile.Close()
|
|
||||||
_ = os.Remove(tmpFile.Name())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var reader io.Reader
|
|
||||||
if filepath.IsAbs(filter.URL) {
|
|
||||||
f, err := os.Open(filter.URL)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("open file: %w", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
reader = f
|
|
||||||
} else {
|
|
||||||
resp, err := Context.client.Get(filter.URL)
|
|
||||||
if resp != nil && resp.Body != nil {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Couldn't request filter from URL %s, skipping: %s", filter.URL, err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
log.Printf("Got status code %d from URL %s, skipping", resp.StatusCode, filter.URL)
|
|
||||||
return false, fmt.Errorf("got status code != 200: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
reader = resp.Body
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlTest := true
|
htmlTest := true
|
||||||
firstChunk := make([]byte, 4*1024)
|
firstChunk := make([]byte, 4*1024)
|
||||||
firstChunkLen := 0
|
firstChunkLen := 0
|
||||||
|
@ -556,12 +518,12 @@ func (f *Filtering) updateIntl(filter *filter) (bool, error) {
|
||||||
|
|
||||||
if firstChunkLen == len(firstChunk) || err == io.EOF {
|
if firstChunkLen == len(firstChunk) || err == io.EOF {
|
||||||
if !isPrintableText(firstChunk, firstChunkLen) {
|
if !isPrintableText(firstChunk, firstChunkLen) {
|
||||||
return false, fmt.Errorf("data contains non-printable characters")
|
return total, fmt.Errorf("data contains non-printable characters")
|
||||||
}
|
}
|
||||||
|
|
||||||
s := strings.ToLower(string(firstChunk))
|
s := strings.ToLower(string(firstChunk))
|
||||||
if strings.Contains(s, "<html") || strings.Contains(s, "<!doctype") {
|
if strings.Contains(s, "<html") || strings.Contains(s, "<!doctype") {
|
||||||
return false, fmt.Errorf("data is HTML, not plain text")
|
return total, fmt.Errorf("data is HTML, not plain text")
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlTest = false
|
htmlTest = false
|
||||||
|
@ -571,17 +533,70 @@ func (f *Filtering) updateIntl(filter *filter) (bool, error) {
|
||||||
|
|
||||||
_, err2 := tmpFile.Write(buf[:n])
|
_, err2 := tmpFile.Write(buf[:n])
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return false, err2
|
return total, err2
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
return total, nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Couldn't fetch filter contents from URL %s, skipping: %s", filter.URL, err)
|
log.Printf("Couldn't fetch filter contents from URL %s, skipping: %s", filter.URL, err)
|
||||||
return false, err
|
return total, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateIntl returns true if filter update performed successfully.
|
||||||
|
func (f *Filtering) updateIntl(filter *filter) (updated bool, err error) {
|
||||||
|
updated = false
|
||||||
|
log.Tracef("Downloading update for filter %d from %s", filter.ID, filter.URL)
|
||||||
|
|
||||||
|
tmpFile, err := ioutil.TempFile(filepath.Join(Context.getDataDir(), filterDir), "")
|
||||||
|
if err != nil {
|
||||||
|
return updated, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if tmpFile != nil {
|
||||||
|
if err := tmpFile.Close(); err != nil {
|
||||||
|
log.Printf("Couldn't close temporary file: %s", err)
|
||||||
|
}
|
||||||
|
tmpFileName := tmpFile.Name()
|
||||||
|
if err := os.Remove(tmpFileName); err != nil {
|
||||||
|
log.Printf("Couldn't delete temporary file %s: %s", tmpFileName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var reader io.Reader
|
||||||
|
if filepath.IsAbs(filter.URL) {
|
||||||
|
f, err := os.Open(filter.URL)
|
||||||
|
if err != nil {
|
||||||
|
return updated, fmt.Errorf("open file: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
reader = f
|
||||||
|
} else {
|
||||||
|
resp, err := Context.client.Get(filter.URL)
|
||||||
|
if resp != nil && resp.Body != nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Couldn't request filter from URL %s, skipping: %s", filter.URL, err)
|
||||||
|
return updated, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
log.Printf("Got status code %d from URL %s, skipping", resp.StatusCode, filter.URL)
|
||||||
|
return updated, fmt.Errorf("got status code != 200: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
reader = resp.Body
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := f.read(reader, tmpFile, filter)
|
||||||
|
if err != nil {
|
||||||
|
return updated, err
|
||||||
|
}
|
||||||
|
|
||||||
// Extract filter name and count number of rules
|
// Extract filter name and count number of rules
|
||||||
_, _ = tmpFile.Seek(0, io.SeekStart)
|
_, _ = tmpFile.Seek(0, io.SeekStart)
|
||||||
|
@ -589,7 +604,7 @@ func (f *Filtering) updateIntl(filter *filter) (bool, error) {
|
||||||
// Check if the filter has been really changed
|
// Check if the filter has been really changed
|
||||||
if filter.checksum == checksum {
|
if filter.checksum == checksum {
|
||||||
log.Tracef("Filter #%d at URL %s hasn't changed, not updating it", filter.ID, filter.URL)
|
log.Tracef("Filter #%d at URL %s hasn't changed, not updating it", filter.ID, filter.URL)
|
||||||
return false, nil
|
return updated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Filter %d has been updated: %d bytes, %d rules",
|
log.Printf("Filter %d has been updated: %d bytes, %d rules",
|
||||||
|
@ -606,11 +621,12 @@ func (f *Filtering) updateIntl(filter *filter) (bool, error) {
|
||||||
_ = tmpFile.Close()
|
_ = tmpFile.Close()
|
||||||
err = os.Rename(tmpFile.Name(), filterFilePath)
|
err = os.Rename(tmpFile.Name(), filterFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return updated, err
|
||||||
}
|
}
|
||||||
tmpFile = nil
|
tmpFile = nil
|
||||||
|
updated = true
|
||||||
|
|
||||||
return true, nil
|
return updated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loads filter contents from the file in dataDir
|
// loads filter contents from the file in dataDir
|
||||||
|
|
|
@ -12,7 +12,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func testStartFilterListener() net.Listener {
|
func testStartFilterListener() net.Listener {
|
||||||
http.HandleFunc("/filters/1.txt", func(w http.ResponseWriter, r *http.Request) {
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/filters/1.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||||
content := `||example.org^$third-party
|
content := `||example.org^$third-party
|
||||||
# Inline comment example
|
# Inline comment example
|
||||||
||example.com^$third-party
|
||example.com^$third-party
|
||||||
|
@ -26,7 +27,7 @@ func testStartFilterListener() net.Listener {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() { _ = http.Serve(listener, nil) }()
|
go func() { _ = http.Serve(listener, mux) }()
|
||||||
return listener
|
return listener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/update"
|
"github.com/AdguardTeam/AdGuardHome/internal/update"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
@ -43,6 +44,7 @@ var (
|
||||||
updateChannel = "none"
|
updateChannel = "none"
|
||||||
versionCheckURL = ""
|
versionCheckURL = ""
|
||||||
ARMVersion = ""
|
ARMVersion = ""
|
||||||
|
MIPSVersion = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
// Global context
|
// Global context
|
||||||
|
@ -56,7 +58,7 @@ type homeContext struct {
|
||||||
dnsServer *dnsforward.Server // DNS module
|
dnsServer *dnsforward.Server // DNS module
|
||||||
rdns *RDNS // rDNS module
|
rdns *RDNS // rDNS module
|
||||||
whois *Whois // WHOIS module
|
whois *Whois // WHOIS module
|
||||||
dnsFilter *dnsfilter.Dnsfilter // DNS filtering module
|
dnsFilter *dnsfilter.DNSFilter // DNS filtering module
|
||||||
dhcpServer *dhcpd.Server // DHCP module
|
dhcpServer *dhcpd.Server // DHCP module
|
||||||
auth *Auth // HTTP authentication module
|
auth *Auth // HTTP authentication module
|
||||||
filters Filtering // DNS filtering module
|
filters Filtering // DNS filtering module
|
||||||
|
@ -65,6 +67,11 @@ type homeContext struct {
|
||||||
autoHosts util.AutoHosts // IP-hostname pairs taken from system configuration (e.g. /etc/hosts) files
|
autoHosts util.AutoHosts // IP-hostname pairs taken from system configuration (e.g. /etc/hosts) files
|
||||||
updater *update.Updater
|
updater *update.Updater
|
||||||
|
|
||||||
|
ipDetector *ipDetector
|
||||||
|
|
||||||
|
// mux is our custom http.ServeMux.
|
||||||
|
mux *http.ServeMux
|
||||||
|
|
||||||
// Runtime properties
|
// Runtime properties
|
||||||
// --
|
// --
|
||||||
|
|
||||||
|
@ -92,11 +99,12 @@ func (c *homeContext) getDataDir() string {
|
||||||
var Context homeContext
|
var Context homeContext
|
||||||
|
|
||||||
// Main is the entry point
|
// Main is the entry point
|
||||||
func Main(version, channel, armVer string) {
|
func Main(version, channel, armVer, mipsVer string) {
|
||||||
// Init update-related global variables
|
// Init update-related global variables
|
||||||
versionString = version
|
versionString = version
|
||||||
updateChannel = channel
|
updateChannel = channel
|
||||||
ARMVersion = armVer
|
ARMVersion = armVer
|
||||||
|
MIPSVersion = mipsVer
|
||||||
versionCheckURL = "https://static.adguard.com/adguardhome/" + updateChannel + "/version.json"
|
versionCheckURL = "https://static.adguard.com/adguardhome/" + updateChannel + "/version.json"
|
||||||
|
|
||||||
// config can be specified, which reads options from there, but other command line flags have to override config values
|
// config can be specified, which reads options from there, but other command line flags have to override config values
|
||||||
|
@ -133,35 +141,19 @@ func Main(version, channel, armVer string) {
|
||||||
|
|
||||||
// version - returns the current version string
|
// version - returns the current version string
|
||||||
func version() string {
|
func version() string {
|
||||||
|
// TODO(a.garipov): I'm pretty sure we can extract some of this stuff
|
||||||
|
// from the build info.
|
||||||
msg := "AdGuard Home, version %s, channel %s, arch %s %s"
|
msg := "AdGuard Home, version %s, channel %s, arch %s %s"
|
||||||
if ARMVersion != "" {
|
if ARMVersion != "" {
|
||||||
msg = msg + " v" + ARMVersion
|
msg = msg + " v" + ARMVersion
|
||||||
|
} else if MIPSVersion != "" {
|
||||||
|
msg = msg + " " + MIPSVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(msg, versionString, updateChannel, runtime.GOOS, runtime.GOARCH)
|
return fmt.Sprintf(msg, versionString, updateChannel, runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
// run initializes configuration and runs the AdGuard Home
|
func setupContext(args options) {
|
||||||
// run is a blocking method!
|
|
||||||
// nolint
|
|
||||||
func run(args options) {
|
|
||||||
// configure config filename
|
|
||||||
initConfigFilename(args)
|
|
||||||
|
|
||||||
// configure working dir and config path
|
|
||||||
initWorkingDir(args)
|
|
||||||
|
|
||||||
// configure log level and output
|
|
||||||
configureLogger(args)
|
|
||||||
|
|
||||||
// Go memory hacks
|
|
||||||
memoryUsage(args)
|
|
||||||
|
|
||||||
// print the first message after logger is configured
|
|
||||||
log.Println(version())
|
|
||||||
log.Debug("Current working directory is %s", Context.workDir)
|
|
||||||
if args.runningAsService {
|
|
||||||
log.Info("AdGuard Home is running as a service")
|
|
||||||
}
|
|
||||||
Context.runningAsService = args.runningAsService
|
Context.runningAsService = args.runningAsService
|
||||||
Context.disableUpdate = args.disableUpdate
|
Context.disableUpdate = args.disableUpdate
|
||||||
|
|
||||||
|
@ -179,7 +171,8 @@ func run(args options) {
|
||||||
DialContext: customDialContext,
|
DialContext: customDialContext,
|
||||||
Proxy: getHTTPProxy,
|
Proxy: getHTTPProxy,
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
RootCAs: Context.tlsRoots,
|
RootCAs: Context.tlsRoots,
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Context.client = &http.Client{
|
Context.client = &http.Client{
|
||||||
|
@ -206,11 +199,10 @@ func run(args options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'clients' module uses 'dnsfilter' module's static data (dnsfilter.BlockedSvcKnown()),
|
Context.mux = http.NewServeMux()
|
||||||
// so we have to initialize dnsfilter's static data first,
|
}
|
||||||
// but also avoid relying on automatic Go init() function
|
|
||||||
dnsfilter.InitModule()
|
|
||||||
|
|
||||||
|
func setupConfig(args options) {
|
||||||
config.DHCP.WorkDir = Context.workDir
|
config.DHCP.WorkDir = Context.workDir
|
||||||
config.DHCP.HTTPRegister = httpRegister
|
config.DHCP.HTTPRegister = httpRegister
|
||||||
config.DHCP.ConfigModified = onConfigModified
|
config.DHCP.ConfigModified = onConfigModified
|
||||||
|
@ -238,7 +230,7 @@ func run(args options) {
|
||||||
|
|
||||||
if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") &&
|
if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") &&
|
||||||
config.RlimitNoFile != 0 {
|
config.RlimitNoFile != 0 {
|
||||||
util.SetRlimit(config.RlimitNoFile)
|
sysutil.SetRlimit(config.RlimitNoFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// override bind host/port from the console
|
// override bind host/port from the console
|
||||||
|
@ -251,6 +243,37 @@ func run(args options) {
|
||||||
if len(args.pidFile) != 0 && writePIDFile(args.pidFile) {
|
if len(args.pidFile) != 0 && writePIDFile(args.pidFile) {
|
||||||
Context.pidFileName = args.pidFile
|
Context.pidFileName = args.pidFile
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run performs configurating and starts AdGuard Home.
|
||||||
|
func run(args options) {
|
||||||
|
// configure config filename
|
||||||
|
initConfigFilename(args)
|
||||||
|
|
||||||
|
// configure working dir and config path
|
||||||
|
initWorkingDir(args)
|
||||||
|
|
||||||
|
// configure log level and output
|
||||||
|
configureLogger(args)
|
||||||
|
|
||||||
|
// Go memory hacks
|
||||||
|
memoryUsage(args)
|
||||||
|
|
||||||
|
// print the first message after logger is configured
|
||||||
|
log.Println(version())
|
||||||
|
log.Debug("Current working directory is %s", Context.workDir)
|
||||||
|
if args.runningAsService {
|
||||||
|
log.Info("AdGuard Home is running as a service")
|
||||||
|
}
|
||||||
|
|
||||||
|
setupContext(args)
|
||||||
|
|
||||||
|
// clients package uses dnsfilter package'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()
|
||||||
|
|
||||||
|
setupConfig(args)
|
||||||
|
|
||||||
if !Context.firstRun {
|
if !Context.firstRun {
|
||||||
// Save the updated config
|
// Save the updated config
|
||||||
|
@ -292,10 +315,14 @@ func run(args options) {
|
||||||
log.Fatalf("Can't initialize TLS module")
|
log.Fatalf("Can't initialize TLS module")
|
||||||
}
|
}
|
||||||
|
|
||||||
webConf := WebConfig{
|
webConf := webConfig{
|
||||||
firstRun: Context.firstRun,
|
firstRun: Context.firstRun,
|
||||||
BindHost: config.BindHost,
|
BindHost: config.BindHost,
|
||||||
BindPort: config.BindPort,
|
BindPort: config.BindPort,
|
||||||
|
|
||||||
|
ReadTimeout: ReadTimeout,
|
||||||
|
ReadHeaderTimeout: ReadHeaderTimeout,
|
||||||
|
WriteTimeout: WriteTimeout,
|
||||||
}
|
}
|
||||||
Context.web = CreateWeb(&webConf)
|
Context.web = CreateWeb(&webConf)
|
||||||
if Context.web == nil {
|
if Context.web == nil {
|
||||||
|
@ -322,6 +349,11 @@ func run(args options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Context.ipDetector, err = newIPDetector()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
Context.web.Start()
|
Context.web.Start()
|
||||||
|
|
||||||
// wait indefinitely for other go-routines to complete their job
|
// wait indefinitely for other go-routines to complete their job
|
||||||
|
@ -352,7 +384,7 @@ func checkPermissions() {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
// On Windows we need to have admin rights to run properly
|
// On Windows we need to have admin rights to run properly
|
||||||
|
|
||||||
admin, _ := util.HaveAdminRights()
|
admin, _ := sysutil.HaveAdminRights()
|
||||||
if admin {
|
if admin {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -469,7 +501,7 @@ func configureLogger(args options) {
|
||||||
|
|
||||||
if ls.LogFile == configSyslog {
|
if ls.LogFile == configSyslog {
|
||||||
// Use syslog where it is possible and eventlog on Windows
|
// Use syslog where it is possible and eventlog on Windows
|
||||||
err := util.ConfigureSyslog(serviceName)
|
err := sysutil.ConfigureSyslog(serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("cannot initialize syslog: %s", err)
|
log.Fatalf("cannot initialize syslog: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -659,3 +691,12 @@ func getHTTPProxy(req *http.Request) (*url.URL, error) {
|
||||||
}
|
}
|
||||||
return url.Parse(config.ProxyURL)
|
return url.Parse(config.ProxyURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// jsonError is a generic JSON error response.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Merge together with the implementations in .../dhcpd and
|
||||||
|
// other packages after refactoring the web handler registering.
|
||||||
|
type jsonError struct {
|
||||||
|
// Message is the error message, an opaque string.
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ func TestHome(t *testing.T) {
|
||||||
fn := filepath.Join(dir, "AdGuardHome.yaml")
|
fn := filepath.Join(dir, "AdGuardHome.yaml")
|
||||||
|
|
||||||
// Prepare the test config
|
// Prepare the test config
|
||||||
assert.True(t, ioutil.WriteFile(fn, []byte(yamlConf), 0644) == nil)
|
assert.True(t, ioutil.WriteFile(fn, []byte(yamlConf), 0o644) == nil)
|
||||||
fn, _ = filepath.Abs(fn)
|
fn, _ = filepath.Abs(fn)
|
||||||
|
|
||||||
config = configuration{} // the global variable is dirty because of the previous tests run
|
config = configuration{} // the global variable is dirty because of the previous tests run
|
||||||
|
|
|
@ -66,6 +66,7 @@ func handleI18nCurrentLanguage(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleI18nChangeLanguage(w http.ResponseWriter, r *http.Request) {
|
func handleI18nChangeLanguage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// This use of ReadAll is safe, because request's body is now limited.
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := fmt.Sprintf("failed to read request body: %s", err)
|
msg := fmt.Sprintf("failed to read request body: %s", err)
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package home
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
// ipDetector describes IP address properties.
|
||||||
|
type ipDetector struct {
|
||||||
|
nets []*net.IPNet
|
||||||
|
}
|
||||||
|
|
||||||
|
// newIPDetector returns a new IP detector.
|
||||||
|
func newIPDetector() (ipd *ipDetector, err error) {
|
||||||
|
specialNetworks := []string{
|
||||||
|
"0.0.0.0/8",
|
||||||
|
"10.0.0.0/8",
|
||||||
|
"100.64.0.0/10",
|
||||||
|
"127.0.0.0/8",
|
||||||
|
"169.254.0.0/16",
|
||||||
|
"172.16.0.0/12",
|
||||||
|
"192.0.0.0/24",
|
||||||
|
"192.0.0.0/29",
|
||||||
|
"192.0.2.0/24",
|
||||||
|
"192.88.99.0/24",
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"198.18.0.0/15",
|
||||||
|
"198.51.100.0/24",
|
||||||
|
"203.0.113.0/24",
|
||||||
|
"240.0.0.0/4",
|
||||||
|
"255.255.255.255/32",
|
||||||
|
"::1/128",
|
||||||
|
"::/128",
|
||||||
|
"64:ff9b::/96",
|
||||||
|
// Since this network is used for mapping IPv4 addresses, we
|
||||||
|
// don't include it.
|
||||||
|
// "::ffff:0:0/96",
|
||||||
|
"100::/64",
|
||||||
|
"2001::/23",
|
||||||
|
"2001::/32",
|
||||||
|
"2001:2::/48",
|
||||||
|
"2001:db8::/32",
|
||||||
|
"2001:10::/28",
|
||||||
|
"2002::/16",
|
||||||
|
"fc00::/7",
|
||||||
|
"fe80::/10",
|
||||||
|
}
|
||||||
|
|
||||||
|
ipd = &ipDetector{
|
||||||
|
nets: make([]*net.IPNet, len(specialNetworks)),
|
||||||
|
}
|
||||||
|
for i, ipnetStr := range specialNetworks {
|
||||||
|
_, ipnet, err := net.ParseCIDR(ipnetStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ipd.nets[i] = ipnet
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectSpecialNetwork returns true if IP address is contained by any of
|
||||||
|
// special-purpose IP address registries according to RFC-6890
|
||||||
|
// (https://tools.ietf.org/html/rfc6890).
|
||||||
|
func (ipd *ipDetector) detectSpecialNetwork(ip net.IP) bool {
|
||||||
|
for _, ipnet := range ipd.nets {
|
||||||
|
if ipnet.Contains(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIPDetector_detectSpecialNetwork(t *testing.T) {
|
||||||
|
var ipd *ipDetector
|
||||||
|
|
||||||
|
t.Run("newIPDetector", func(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
ipd, err = newIPDetector()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
ip net.IP
|
||||||
|
want bool
|
||||||
|
}{{
|
||||||
|
name: "not_specific",
|
||||||
|
ip: net.ParseIP("8.8.8.8"),
|
||||||
|
want: false,
|
||||||
|
}, {
|
||||||
|
name: "this_host_on_this_network",
|
||||||
|
ip: net.ParseIP("0.0.0.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "private-Use",
|
||||||
|
ip: net.ParseIP("10.0.0.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "shared_address_space",
|
||||||
|
ip: net.ParseIP("100.64.0.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "loopback",
|
||||||
|
ip: net.ParseIP("127.0.0.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "link_local",
|
||||||
|
ip: net.ParseIP("169.254.0.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "private-use",
|
||||||
|
ip: net.ParseIP("172.16.0.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "ietf_protocol_assignments",
|
||||||
|
ip: net.ParseIP("192.0.0.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "ds-lite",
|
||||||
|
ip: net.ParseIP("192.0.0.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "documentation_(test-net-1)",
|
||||||
|
ip: net.ParseIP("192.0.2.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "6to4_relay_anycast",
|
||||||
|
ip: net.ParseIP("192.88.99.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "private-use",
|
||||||
|
ip: net.ParseIP("192.168.0.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "benchmarking",
|
||||||
|
ip: net.ParseIP("198.18.0.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "documentation_(test-net-2)",
|
||||||
|
ip: net.ParseIP("198.51.100.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "documentation_(test-net-3)",
|
||||||
|
ip: net.ParseIP("203.0.113.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "reserved",
|
||||||
|
ip: net.ParseIP("240.0.0.0"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "limited_broadcast",
|
||||||
|
ip: net.ParseIP("255.255.255.255"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "loopback_address",
|
||||||
|
ip: net.ParseIP("::1"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "unspecified_address",
|
||||||
|
ip: net.ParseIP("::"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "ipv4-ipv6_translation",
|
||||||
|
ip: net.ParseIP("64:ff9b::"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "discard-only_address_block",
|
||||||
|
ip: net.ParseIP("100::"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "ietf_protocol_assignments",
|
||||||
|
ip: net.ParseIP("2001::"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "teredo",
|
||||||
|
ip: net.ParseIP("2001::"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "benchmarking",
|
||||||
|
ip: net.ParseIP("2001:2::"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "documentation",
|
||||||
|
ip: net.ParseIP("2001:db8::"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "orchid",
|
||||||
|
ip: net.ParseIP("2001:10::"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "6to4",
|
||||||
|
ip: net.ParseIP("2002::"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "unique-local",
|
||||||
|
ip: net.ParseIP("fc00::"),
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "linked-scoped_unicast",
|
||||||
|
ip: net.ParseIP("fe80::"),
|
||||||
|
want: true,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.want, ipd.detectSpecialNetwork(tc.ip))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// middlerware is a wrapper function signature.
|
||||||
|
type middleware func(http.Handler) http.Handler
|
||||||
|
|
||||||
|
// withMiddlewares consequently wraps h with all the middlewares.
|
||||||
|
func withMiddlewares(h http.Handler, middlewares ...middleware) (wrapped http.Handler) {
|
||||||
|
wrapped = h
|
||||||
|
|
||||||
|
for _, mw := range middlewares {
|
||||||
|
wrapped = mw(wrapped)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestBodySizeLimit is maximum request body length in bytes.
|
||||||
|
const RequestBodySizeLimit = 64 * 1024
|
||||||
|
|
||||||
|
// limitRequestBody wraps underlying handler h, making it's request's body Read
|
||||||
|
// method limited.
|
||||||
|
func limitRequestBody(h http.Handler) (limited http.Handler) {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
r.Body, err = aghio.LimitReadCloser(r.Body, RequestBodySizeLimit)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("limitRequestBody: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLimitRequestBody(t *testing.T) {
|
||||||
|
errReqLimitReached := &aghio.LimitReachedError{
|
||||||
|
Limit: RequestBodySizeLimit,
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
body string
|
||||||
|
want []byte
|
||||||
|
wantErr error
|
||||||
|
}{{
|
||||||
|
name: "not_so_big",
|
||||||
|
body: "somestr",
|
||||||
|
want: []byte("somestr"),
|
||||||
|
wantErr: nil,
|
||||||
|
}, {
|
||||||
|
name: "so_big",
|
||||||
|
body: string(make([]byte, RequestBodySizeLimit+1)),
|
||||||
|
want: make([]byte, RequestBodySizeLimit),
|
||||||
|
wantErr: errReqLimitReached,
|
||||||
|
}, {
|
||||||
|
name: "empty",
|
||||||
|
body: "",
|
||||||
|
want: []byte(nil),
|
||||||
|
wantErr: nil,
|
||||||
|
}}
|
||||||
|
|
||||||
|
makeHandler := func(err *error) http.HandlerFunc {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var b []byte
|
||||||
|
b, *err = ioutil.ReadAll(r.Body)
|
||||||
|
w.Write(b)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
handler := makeHandler(&err)
|
||||||
|
lim := limitRequestBody(handler)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "https://www.example.com", strings.NewReader(tc.body))
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
|
||||||
|
lim.ServeHTTP(res, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.want, res.Body.Bytes())
|
||||||
|
assert.Equal(t, tc.wantErr, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,22 @@
|
||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
uuid "github.com/satori/go.uuid"
|
uuid "github.com/satori/go.uuid"
|
||||||
"howett.net/plist"
|
"howett.net/plist"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DNSSettings struct {
|
type dnsSettings struct {
|
||||||
DNSProtocol string
|
DNSProtocol string
|
||||||
ServerURL string `plist:",omitempty"`
|
ServerURL string `plist:",omitempty"`
|
||||||
ServerName string `plist:",omitempty"`
|
ServerName string `plist:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PayloadContent struct {
|
type payloadContent struct {
|
||||||
Name string
|
Name string
|
||||||
PayloadDescription string
|
PayloadDescription string
|
||||||
PayloadDisplayName string
|
PayloadDisplayName string
|
||||||
|
@ -23,11 +24,11 @@ type PayloadContent struct {
|
||||||
PayloadType string
|
PayloadType string
|
||||||
PayloadUUID string
|
PayloadUUID string
|
||||||
PayloadVersion int
|
PayloadVersion int
|
||||||
DNSSettings DNSSettings
|
DNSSettings dnsSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
type MobileConfig struct {
|
type mobileConfig struct {
|
||||||
PayloadContent []PayloadContent
|
PayloadContent []payloadContent
|
||||||
PayloadDescription string
|
PayloadDescription string
|
||||||
PayloadDisplayName string
|
PayloadDisplayName string
|
||||||
PayloadIdentifier string
|
PayloadIdentifier string
|
||||||
|
@ -46,19 +47,20 @@ const (
|
||||||
dnsProtoTLS = "TLS"
|
dnsProtoTLS = "TLS"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getMobileConfig(d DNSSettings) ([]byte, error) {
|
func getMobileConfig(d dnsSettings) ([]byte, error) {
|
||||||
var name string
|
var name string
|
||||||
switch d.DNSProtocol {
|
switch d.DNSProtocol {
|
||||||
case dnsProtoHTTPS:
|
case dnsProtoHTTPS:
|
||||||
name = fmt.Sprintf("%s DoH", d.ServerName)
|
name = fmt.Sprintf("%s DoH", d.ServerName)
|
||||||
|
d.ServerURL = fmt.Sprintf("https://%s/dns-query", d.ServerName)
|
||||||
case dnsProtoTLS:
|
case dnsProtoTLS:
|
||||||
name = fmt.Sprintf("%s DoT", d.ServerName)
|
name = fmt.Sprintf("%s DoT", d.ServerName)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("bad dns protocol %q", d.DNSProtocol)
|
return nil, fmt.Errorf("bad dns protocol %q", d.DNSProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := MobileConfig{
|
data := mobileConfig{
|
||||||
PayloadContent: []PayloadContent{{
|
PayloadContent: []payloadContent{{
|
||||||
Name: name,
|
Name: name,
|
||||||
PayloadDescription: "Configures device to use AdGuard Home",
|
PayloadDescription: "Configures device to use AdGuard Home",
|
||||||
PayloadDisplayName: name,
|
PayloadDisplayName: name,
|
||||||
|
@ -80,34 +82,46 @@ func getMobileConfig(d DNSSettings) ([]byte, error) {
|
||||||
return plist.MarshalIndent(data, plist.XMLFormat, "\t")
|
return plist.MarshalIndent(data, plist.XMLFormat, "\t")
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMobileConfig(w http.ResponseWriter, d DNSSettings) {
|
func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) {
|
||||||
|
host := r.URL.Query().Get("host")
|
||||||
|
if host == "" {
|
||||||
|
host = Context.tls.conf.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
if host == "" {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
|
||||||
|
const msg = "no host in query parameters and no server_name"
|
||||||
|
err := json.NewEncoder(w).Encode(&jsonError{
|
||||||
|
Message: msg,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("writing 500 json response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d := dnsSettings{
|
||||||
|
DNSProtocol: dnsp,
|
||||||
|
ServerName: host,
|
||||||
|
}
|
||||||
|
|
||||||
mobileconfig, err := getMobileConfig(d)
|
mobileconfig, err := getMobileConfig(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusInternalServerError, "plist.MarshalIndent: %s", err)
|
httpError(w, http.StatusInternalServerError, "plist.MarshalIndent: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/xml")
|
w.Header().Set("Content-Type", "application/xml")
|
||||||
_, _ = w.Write(mobileconfig)
|
_, _ = w.Write(mobileconfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMobileConfigDoh(w http.ResponseWriter, r *http.Request) {
|
func handleMobileConfigDOH(w http.ResponseWriter, r *http.Request) {
|
||||||
handleMobileConfig(w, DNSSettings{
|
handleMobileConfig(w, r, dnsProtoHTTPS)
|
||||||
DNSProtocol: dnsProtoHTTPS,
|
|
||||||
ServerURL: fmt.Sprintf("https://%s/dns-query", r.Host),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMobileConfigDot(w http.ResponseWriter, r *http.Request) {
|
func handleMobileConfigDOT(w http.ResponseWriter, r *http.Request) {
|
||||||
var err error
|
handleMobileConfig(w, r, dnsProtoTLS)
|
||||||
|
|
||||||
var host string
|
|
||||||
host, _, err = net.SplitHostPort(r.Host)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusBadRequest, "getting host: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMobileConfig(w, DNSSettings{
|
|
||||||
DNSProtocol: dnsProtoTLS,
|
|
||||||
ServerName: host,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,25 +9,132 @@ import (
|
||||||
"howett.net/plist"
|
"howett.net/plist"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHandleMobileConfigDot(t *testing.T) {
|
func TestHandleMobileConfigDOH(t *testing.T) {
|
||||||
var err error
|
t.Run("success", func(t *testing.T) {
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig?host=example.org", nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
var r *http.Request
|
w := httptest.NewRecorder()
|
||||||
r, err = http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
handleMobileConfigDOH(w, r)
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
handleMobileConfigDot(w, r)
|
var mc mobileConfig
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
var mc MobileConfig
|
if assert.Equal(t, 1, len(mc.PayloadContent)) {
|
||||||
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name)
|
||||||
assert.Nil(t, err)
|
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName)
|
||||||
|
assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
|
||||||
|
assert.Equal(t, "https://example.org/dns-query", mc.PayloadContent[0].DNSSettings.ServerURL)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if assert.Equal(t, 1, len(mc.PayloadContent)) {
|
t.Run("success_no_host", func(t *testing.T) {
|
||||||
assert.Equal(t, "example.com DoT", mc.PayloadContent[0].Name)
|
oldTLSConf := Context.tls
|
||||||
assert.Equal(t, "example.com DoT", mc.PayloadContent[0].PayloadDisplayName)
|
t.Cleanup(func() { Context.tls = oldTLSConf })
|
||||||
assert.Equal(t, "example.com", mc.PayloadContent[0].DNSSettings.ServerName)
|
|
||||||
}
|
Context.tls = &TLSMod{
|
||||||
|
conf: tlsConfigSettings{ServerName: "example.org"},
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handleMobileConfigDOH(w, r)
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
|
var mc mobileConfig
|
||||||
|
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
if assert.Equal(t, 1, len(mc.PayloadContent)) {
|
||||||
|
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name)
|
||||||
|
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName)
|
||||||
|
assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
|
||||||
|
assert.Equal(t, "https://example.org/dns-query", mc.PayloadContent[0].DNSSettings.ServerURL)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("error_no_host", func(t *testing.T) {
|
||||||
|
oldTLSConf := Context.tls
|
||||||
|
t.Cleanup(func() { Context.tls = oldTLSConf })
|
||||||
|
|
||||||
|
Context.tls = &TLSMod{conf: tlsConfigSettings{}}
|
||||||
|
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handleMobileConfigDOH(w, r)
|
||||||
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMobileConfigDOT(t *testing.T) {
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig?host=example.org", nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handleMobileConfigDOT(w, r)
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
|
var mc mobileConfig
|
||||||
|
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
if assert.Equal(t, 1, len(mc.PayloadContent)) {
|
||||||
|
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name)
|
||||||
|
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName)
|
||||||
|
assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("success_no_host", func(t *testing.T) {
|
||||||
|
oldTLSConf := Context.tls
|
||||||
|
t.Cleanup(func() { Context.tls = oldTLSConf })
|
||||||
|
|
||||||
|
Context.tls = &TLSMod{
|
||||||
|
conf: tlsConfigSettings{ServerName: "example.org"},
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handleMobileConfigDOT(w, r)
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
|
var mc mobileConfig
|
||||||
|
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
if assert.Equal(t, 1, len(mc.PayloadContent)) {
|
||||||
|
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name)
|
||||||
|
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName)
|
||||||
|
assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("error_no_host", func(t *testing.T) {
|
||||||
|
oldTLSConf := Context.tls
|
||||||
|
t.Cleanup(func() { Context.tls = oldTLSConf })
|
||||||
|
|
||||||
|
Context.tls = &TLSMod{conf: tlsConfigSettings{}}
|
||||||
|
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handleMobileConfigDOT(w, r)
|
||||||
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
|
@ -109,7 +110,7 @@ func sendSigReload() {
|
||||||
log.Error("Can't read PID file %s: %s", pidfile, err)
|
log.Error("Can't read PID file %s: %s", pidfile, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = util.SendProcessSignal(pid, syscall.SIGHUP)
|
err = sysutil.SendProcessSignal(pid, syscall.SIGHUP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Can't send signal to PID %d: %s", pid, err)
|
log.Error("Can't send signal to PID %d: %s", pid, err)
|
||||||
return
|
return
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue