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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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