Compare commits
82 Commits
v0.104.0
...
102-dns-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9efc39306 | ||
|
|
f924523f6a | ||
|
|
6e6ee9697a | ||
|
|
aff09211b2 | ||
|
|
bad1c6acdc | ||
|
|
fcb582679e | ||
|
|
6b60598025 | ||
|
|
b338bf9b3f | ||
|
|
2313eda123 | ||
|
|
e4383189a5 | ||
|
|
09b6eba7d9 | ||
|
|
9b963fc777 | ||
|
|
7f29d4e546 | ||
|
|
a572876775 | ||
|
|
63e513e33e | ||
|
|
dfd28b45ab | ||
|
|
a86172fda4 | ||
|
|
8501a85292 | ||
|
|
4134220c54 | ||
|
|
ab8defdb08 | ||
|
|
7d1d87d6ec | ||
|
|
641db73a86 | ||
|
|
6e615c6eaa | ||
|
|
60d72fb9c3 | ||
|
|
a6e18c4700 | ||
|
|
f9e4e7b024 | ||
|
|
96e83a133f | ||
|
|
b4a35fa887 | ||
|
|
36c7735b85 | ||
|
|
284da7c91b | ||
|
|
e685d81c92 | ||
|
|
1cf9848044 | ||
|
|
c129361e55 | ||
|
|
046ec13fdc | ||
|
|
3045da1742 | ||
|
|
642dcd647c | ||
|
|
62a8fe0b73 | ||
|
|
b1c71a1284 | ||
|
|
4690229d81 | ||
|
|
de257b73aa | ||
|
|
40614d9a7b | ||
|
|
f2eda6d192 | ||
|
|
8a9c6e8a02 | ||
|
|
bf4c256c72 | ||
|
|
6358240e9b | ||
|
|
a19523b258 | ||
|
|
394fc5a9d8 | ||
|
|
09196118e9 | ||
|
|
e802e6645e | ||
|
|
13f708b483 | ||
|
|
979874c92b | ||
|
|
3cc5bf210d | ||
|
|
1e583315a8 | ||
|
|
ee8a033472 | ||
|
|
b85c0b3bf7 | ||
|
|
859ed209a0 | ||
|
|
c6a6f05b1b | ||
|
|
5bf330bf14 | ||
|
|
eefa553100 | ||
|
|
298f74ba81 | ||
|
|
3e1f922252 | ||
|
|
7e16fda57b | ||
|
|
11e794f554 | ||
|
|
2baa33fb1f | ||
|
|
02d16a0b40 | ||
|
|
84ca2f58a8 | ||
|
|
bc20917840 | ||
|
|
62cc334f46 | ||
|
|
d398e4b01c | ||
|
|
3d19f91bed | ||
|
|
df34ee5c09 | ||
|
|
64c1a68fb9 | ||
|
|
d747185b45 | ||
|
|
8afdc049ef | ||
|
|
812b43a4b3 | ||
|
|
ae8de95d89 | ||
|
|
df3fa595a2 | ||
|
|
98f6843aab | ||
|
|
9761f13399 | ||
|
|
a376bf915a | ||
|
|
da5c1ebbbf | ||
|
|
40ebccaab4 |
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
|
||||||
|
|||||||
32
.github/stale.yml
vendored
32
.github/stale.yml
vendored
@@ -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
|
||||||
|
|||||||
307
.github/workflows/build.yml
vendored
307
.github/workflows/build.yml
vendored
@@ -1,172 +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: Set up Docker Buildx
|
|
||||||
uses: crazy-max/ghaction-docker-buildx@v1
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
-
|
|
||||||
name: Docker Buildx (build)
|
|
||||||
run: |
|
|
||||||
make docker-multi-arch
|
|
||||||
-
|
|
||||||
name: Clear
|
|
||||||
if: always() && startsWith(github.ref, 'refs/tags/v')
|
|
||||||
run: |
|
|
||||||
rm -f ${HOME}/.docker/config.json
|
|
||||||
|
|
||||||
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 }}
|
|
||||||
|
|||||||
102
.github/workflows/lint.yml
vendored
102
.github/workflows/lint.yml
vendored
@@ -1,47 +1,55 @@
|
|||||||
name: golangci-lint
|
'name': 'golangci-lint'
|
||||||
on:
|
'on':
|
||||||
push:
|
'push':
|
||||||
tags:
|
'tags':
|
||||||
- v*
|
- 'v*'
|
||||||
branches:
|
'branches':
|
||||||
- '*'
|
- '*'
|
||||||
pull_request:
|
'pull_request':
|
||||||
jobs:
|
'jobs':
|
||||||
golangci:
|
'golangci':
|
||||||
runs-on: ubuntu-latest
|
'runs-on': 'ubuntu-latest'
|
||||||
steps:
|
'steps':
|
||||||
- uses: actions/checkout@v2
|
- 'uses': 'actions/checkout@v2'
|
||||||
- name: golangci-lint
|
- 'name': 'golangci-lint'
|
||||||
uses: golangci/golangci-lint-action@v1
|
'uses': 'golangci/golangci-lint-action@v2.3.0'
|
||||||
with:
|
'with':
|
||||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
# This field is required. Don't set the patch version to always use
|
||||||
version: v1.27
|
# the latest patch version.
|
||||||
|
'version': 'v1.32'
|
||||||
eslint:
|
'eslint':
|
||||||
runs-on: ubuntu-latest
|
'runs-on': 'ubuntu-latest'
|
||||||
steps:
|
'steps':
|
||||||
- uses: actions/checkout@v2
|
- 'uses': 'actions/checkout@v2'
|
||||||
- name: Install modules
|
- 'name': 'Install modules'
|
||||||
run: npm --prefix client ci
|
'run': 'npm --prefix client ci'
|
||||||
- name: Run ESLint
|
- 'name': 'Run ESLint'
|
||||||
run: npm --prefix client run lint
|
'run': 'npm --prefix client run lint'
|
||||||
|
'notify':
|
||||||
|
'needs':
|
||||||
notify:
|
- 'golangci'
|
||||||
needs: [golangci,eslint]
|
- 'eslint'
|
||||||
# Secrets are not passed to workflows that are triggered by a pull request from a fork
|
# Secrets are not passed to workflows that are triggered by a pull request
|
||||||
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
|
# from a fork.
|
||||||
runs-on: ubuntu-latest
|
#
|
||||||
steps:
|
# Use always() to signal to the runner that this job must run even if the
|
||||||
-
|
# previous ones failed.
|
||||||
name: Conclusion
|
'if':
|
||||||
uses: technote-space/workflow-conclusion-action@v1
|
${{ always() &&
|
||||||
-
|
(
|
||||||
name: Send Slack notif
|
github.event_name == 'push' ||
|
||||||
uses: 8398a7/action-slack@v3
|
github.event.pull_request.head.repo.full_name == github.repository
|
||||||
with:
|
)
|
||||||
status: ${{ env.WORKFLOW_CONCLUSION }}
|
}}
|
||||||
fields: repo,message,commit,author
|
'runs-on': 'ubuntu-latest'
|
||||||
env:
|
'steps':
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
- 'name': 'Conclusion'
|
||||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
'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, job'
|
||||||
|
'env':
|
||||||
|
'GITHUB_TOKEN': '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
'SLACK_WEBHOOK_URL': '${{ secrets.SLACK_WEBHOOK_URL }}'
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,7 @@
|
|||||||
/querylog.json
|
/querylog.json
|
||||||
/querylog.json.1
|
/querylog.json.1
|
||||||
coverage.txt
|
coverage.txt
|
||||||
|
leases.db
|
||||||
|
|
||||||
# Test output
|
# Test output
|
||||||
dnsfilter/tests/top-1m.csv
|
dnsfilter/tests/top-1m.csv
|
||||||
|
|||||||
116
.golangci.yml
116
.golangci.yml
@@ -1,79 +1,77 @@
|
|||||||
# options for analysis running
|
# options for analysis running
|
||||||
run:
|
'run':
|
||||||
# default concurrency is a available CPU number
|
# default concurrency is a available CPU number
|
||||||
concurrency: 4
|
'concurrency': 4
|
||||||
|
|
||||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||||
deadline: 2m
|
'deadline': '2m'
|
||||||
|
|
||||||
# which files to skip: they will be analyzed, but issues from them
|
# which files to skip: they will be analyzed, but issues from them
|
||||||
# won't be reported. Default value is empty list, but there is
|
# won't be reported. Default value is empty list, but there is
|
||||||
# no need to include all autogenerated files, we confidently recognize
|
# no need to include all autogenerated files, we confidently recognize
|
||||||
# autogenerated files. If it's not please let us know.
|
# autogenerated files. If it's not please let us know.
|
||||||
skip-files:
|
'skip-files':
|
||||||
- ".*generated.*"
|
- '.*generated.*'
|
||||||
- dnsfilter/rule_to_regexp.go
|
- 'dnsfilter/rule_to_regexp.go'
|
||||||
- util/pprof.go
|
- 'util/pprof.go'
|
||||||
- ".*_test.go"
|
- '.*_test.go'
|
||||||
- client/.*
|
- 'client/.*'
|
||||||
- build/.*
|
- 'build/.*'
|
||||||
- dist/.*
|
- 'dist/.*'
|
||||||
|
|
||||||
|
|
||||||
# all available settings of specific linters
|
# all available settings of specific linters
|
||||||
linters-settings:
|
'linters-settings':
|
||||||
errcheck:
|
'errcheck':
|
||||||
# [deprecated] comma-separated list of pairs of the form pkg:regex
|
# [deprecated] comma-separated list of pairs of the form pkg:regex
|
||||||
# the regex is used to ignore names within pkg. (default "fmt:.*").
|
# the regex is used to ignore names within pkg. (default "fmt:.*").
|
||||||
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
|
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
|
||||||
ignore: fmt:.*,net:SetReadDeadline,net/http:^Write
|
'ignore': 'fmt:.*,net:SetReadDeadline,net/http:^Write'
|
||||||
gocyclo:
|
'gocyclo':
|
||||||
min-complexity: 20
|
'min-complexity': 20
|
||||||
lll:
|
'lll':
|
||||||
line-length: 200
|
'line-length': 200
|
||||||
|
|
||||||
linters:
|
'linters':
|
||||||
enable:
|
'enable':
|
||||||
- deadcode
|
- 'bodyclose'
|
||||||
- errcheck
|
- 'deadcode'
|
||||||
- govet
|
- 'depguard'
|
||||||
- ineffassign
|
- 'dupl'
|
||||||
- staticcheck
|
- 'errcheck'
|
||||||
- structcheck
|
- 'gocyclo'
|
||||||
- unused
|
- 'goimports'
|
||||||
- varcheck
|
- 'golint'
|
||||||
- bodyclose
|
- 'gosec'
|
||||||
- depguard
|
- 'govet'
|
||||||
- dupl
|
- 'ineffassign'
|
||||||
- gocyclo
|
- 'misspell'
|
||||||
- goimports
|
- 'staticcheck'
|
||||||
- golint
|
- 'stylecheck'
|
||||||
- gosec
|
- 'unconvert'
|
||||||
- misspell
|
- 'unused'
|
||||||
- stylecheck
|
- 'varcheck'
|
||||||
- unconvert
|
'disable-all': true
|
||||||
disable-all: true
|
'fast': true
|
||||||
fast: true
|
|
||||||
|
|
||||||
issues:
|
'issues':
|
||||||
# List of regexps of issue texts to exclude, empty list by default.
|
# List of regexps of issue texts to exclude, empty list by default.
|
||||||
# But independently from this option we use default exclude patterns,
|
# But independently from this option we use default exclude patterns,
|
||||||
# it can be disabled by `exclude-use-default: false`. To list all
|
# it can be disabled by `exclude-use-default: false`. To list all
|
||||||
# excluded by default patterns execute `golangci-lint run --help`
|
# excluded by default patterns execute `golangci-lint run --help`
|
||||||
exclude:
|
'exclude':
|
||||||
# structcheck cannot detect usages while they're there
|
# structcheck cannot detect usages while they're there
|
||||||
- .parentalServer. is unused
|
- '.parentalServer. is unused'
|
||||||
- .safeBrowsingServer. is unused
|
- '.safeBrowsingServer. is unused'
|
||||||
# errcheck
|
# errcheck
|
||||||
- Error return value of .s.closeConn. is not checked
|
- 'Error return value of .s.closeConn. is not checked'
|
||||||
- Error return value of ..*.Shutdown.
|
- 'Error return value of ..*.Shutdown.'
|
||||||
# goconst
|
# goconst
|
||||||
- string .forcesafesearch.google.com. has 3 occurrences
|
- 'string .forcesafesearch.google.com. has 3 occurrences'
|
||||||
# gosec: Profiling endpoint is automatically exposed on /debug/pprof
|
# gosec: Profiling endpoint is automatically exposed on /debug/pprof
|
||||||
- G108
|
- 'G108'
|
||||||
# gosec: Subprocess launched with function call as argument or cmd arguments
|
# gosec: Subprocess launched with function call as argument or cmd arguments
|
||||||
- G204
|
- 'G204'
|
||||||
# gosec: Potential DoS vulnerability via decompression bomb
|
# gosec: Potential DoS vulnerability via decompression bomb
|
||||||
- G110
|
- 'G110'
|
||||||
# gosec: Expect WriteFile permissions to be 0600 or less
|
# gosec: Expect WriteFile permissions to be 0600 or less
|
||||||
- G306
|
- 'G306'
|
||||||
|
|||||||
193
.goreleaser.yml
193
.goreleaser.yml
@@ -1,100 +1,107 @@
|
|||||||
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: [ network-bind ]
|
'plugs':
|
||||||
daemon: simple
|
# Add the "netrwork-bind" plug to bind to interfaces.
|
||||||
adguard-home-web:
|
- 'network-bind'
|
||||||
command: adguard-home-web.sh
|
# Add the "netrwork-observe" plug to be able to bind to ports below 1024
|
||||||
plugs: [ desktop ]
|
# (cap_net_bind_service) and also to bind to a particular interface using
|
||||||
|
# SO_BINDTODEVICE (cap_net_raw).
|
||||||
|
- 'network-observe'
|
||||||
|
'daemon': 'simple'
|
||||||
|
'adguard-home-web':
|
||||||
|
'command': 'adguard-home-web.sh'
|
||||||
|
'plugs':
|
||||||
|
- 'desktop'
|
||||||
|
|
||||||
checksum:
|
'checksum':
|
||||||
name_template: 'checksums.txt'
|
'name_template': 'checksums.txt'
|
||||||
|
|||||||
@@ -1558,6 +1558,7 @@ Strict matching can be enabled by enclosing the value in double quotes: e.g. `"a
|
|||||||
* blocked_services - blocked services
|
* blocked_services - blocked services
|
||||||
* blocked_safebrowsing - blocked by safebrowsing
|
* blocked_safebrowsing - blocked by safebrowsing
|
||||||
* blocked_parental - blocked by parental control
|
* blocked_parental - blocked by parental control
|
||||||
|
* blocked_dns_rebinding - blocked by DNS rebinding protection
|
||||||
* whitelisted - whitelisted
|
* whitelisted - whitelisted
|
||||||
* rewritten - all kinds of rewrites
|
* rewritten - all kinds of rewrites
|
||||||
* safe_search - enforced safe search
|
* safe_search - enforced safe search
|
||||||
@@ -1587,7 +1588,7 @@ Response:
|
|||||||
"upstream":"...", // Upstream URL starting with tcp://, tls://, https://, or with an IP address
|
"upstream":"...", // Upstream URL starting with tcp://, tls://, https://, or with an IP address
|
||||||
"answer_dnssec": true,
|
"answer_dnssec": true,
|
||||||
"client":"127.0.0.1",
|
"client":"127.0.0.1",
|
||||||
"client_proto": "" (plain) | "doh" | "dot",
|
"client_proto": "" (plain) | "doh" | "dot" | "doq",
|
||||||
"elapsedMs":"0.098403",
|
"elapsedMs":"0.098403",
|
||||||
"filterId":1,
|
"filterId":1,
|
||||||
"question":{
|
"question":{
|
||||||
|
|||||||
107
CHANGELOG.md
Normal file
107
CHANGELOG.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# AdGuard Home Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on
|
||||||
|
[*Keep a Changelog*](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to
|
||||||
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
<!--
|
||||||
|
## [v0.105.0] - 2020-12-28
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Detecting of network interface configurated 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
|
||||||
|
[#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
|
||||||
|
|
||||||
|
- 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 ([#2271], [#2297]).
|
||||||
|
|
||||||
|
[#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231
|
||||||
|
[#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
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- 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]).
|
||||||
|
|
||||||
|
[#2293]: https://github.com/AdguardTeam/AdGuardHome/issues/2293
|
||||||
|
[#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345
|
||||||
|
[#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved tests output ([#2273]).
|
||||||
|
|
||||||
|
### 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
|
||||||
|
correctly shows that DHCP is not currently available on that OS ([#2295]).
|
||||||
|
- 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
|
||||||
@@ -59,7 +59,6 @@ RUN apk --update --no-cache add \
|
|||||||
ca-certificates \
|
ca-certificates \
|
||||||
libcap \
|
libcap \
|
||||||
libressl \
|
libressl \
|
||||||
tzdata \
|
|
||||||
&& rm -rf /tmp/* /var/cache/apk/*
|
&& rm -rf /tmp/* /var/cache/apk/*
|
||||||
|
|
||||||
COPY --from=builder --chown=nobody:nogroup /app/AdGuardHome /opt/adguardhome/AdGuardHome
|
COPY --from=builder --chown=nobody:nogroup /app/AdGuardHome /opt/adguardhome/AdGuardHome
|
||||||
|
|||||||
207
HACKING.md
Normal file
207
HACKING.md
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
# *AdGuardHome* Developer Guidelines
|
||||||
|
|
||||||
|
As of **2020-11-27**, this document is a work-in-progress, but should still be
|
||||||
|
followed. Some of the rules aren't enforced as thoroughly or remain broken in
|
||||||
|
old code, but this is still the place to find out about what we **want** our
|
||||||
|
code to look like.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```none
|
||||||
|
pkg: fix the network error logging issue
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `pkg` is the package where most changes took place. If there are
|
||||||
|
several such packages, or the change is top-level only, write `all`.
|
||||||
|
|
||||||
|
* Keep your commit messages, including headers, to eighty (**80**) columns.
|
||||||
|
|
||||||
|
* Only use lowercase letters in your commit message headers. The rest of the
|
||||||
|
message should follow the plain text conventions below.
|
||||||
|
|
||||||
|
The only exceptions are direct mentions of identifiers from the source code
|
||||||
|
and filenames like `HACKING.md`.
|
||||||
|
|
||||||
|
## *Go*
|
||||||
|
|
||||||
|
### Code And Naming
|
||||||
|
|
||||||
|
* Avoid `goto`.
|
||||||
|
|
||||||
|
* Avoid `init` and use explicit initialization functions instead.
|
||||||
|
|
||||||
|
* Avoid `new`, especially with structs.
|
||||||
|
|
||||||
|
* Constructors should validate their arguments and return meaningful errors.
|
||||||
|
As a corollary, avoid lazy initialization.
|
||||||
|
|
||||||
|
* Don't use naked `return`s.
|
||||||
|
|
||||||
|
* Don't use underscores in file and package names, unless they're build tags
|
||||||
|
or for tests. This is to prevent accidental build errors with weird tags.
|
||||||
|
|
||||||
|
* Don't write code with more than four (**4**) levels of indentation. Just
|
||||||
|
like [Linus said], plus an additional level for an occasional error check or
|
||||||
|
struct initialization.
|
||||||
|
|
||||||
|
* Eschew external dependencies, including transitive, unless
|
||||||
|
absolutely necessary.
|
||||||
|
|
||||||
|
* 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
|
||||||
|
errors.
|
||||||
|
|
||||||
|
* Prefer constants to variables where possible. Reduce global variables. Use
|
||||||
|
[constant errors] instead of `errors.New`.
|
||||||
|
|
||||||
|
* Use linters.
|
||||||
|
|
||||||
|
* 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
|
||||||
|
standard template:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Foo implements the Fooer interface for *foo.
|
||||||
|
func (f *foo) Foo() {
|
||||||
|
// …
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
|
||||||
|
* Add an empty line before `break`, `continue`, `fallthrough`, and `return`,
|
||||||
|
unless it's the only statement in that block.
|
||||||
|
|
||||||
|
* Use `gofumpt --extra -s`.
|
||||||
|
|
||||||
|
**TODO(a.garipov):** Add to the linters.
|
||||||
|
|
||||||
|
* Write slices of struct like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
ts := []T{{
|
||||||
|
Field: Value0,
|
||||||
|
// …
|
||||||
|
}, {
|
||||||
|
Field: Value1,
|
||||||
|
// …
|
||||||
|
}, {
|
||||||
|
Field: Value2,
|
||||||
|
// …
|
||||||
|
}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recommended Reading
|
||||||
|
|
||||||
|
* <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.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
a common standard, and to allow editing or diffing side-by-side without
|
||||||
|
wrapping.
|
||||||
|
|
||||||
|
The only exception are long hyperlinks.
|
||||||
|
|
||||||
|
* Use U.S. English, as it is the most widely used variety of English in the
|
||||||
|
code right now as well as generally.
|
||||||
|
|
||||||
|
* Use double spacing between sentences to make sentence borders more clear.
|
||||||
|
|
||||||
|
* Use the serial comma (a.k.a. *Oxford* comma) to improve comprehension,
|
||||||
|
decrease ambiguity, and use a common standard.
|
||||||
|
|
||||||
|
* Write todos like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// TODO(usr1): Fix the frobulation issue.
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, if several people need to look at the code:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// TODO(usr1, usr2): Fix the frobulation issue.
|
||||||
|
```
|
||||||
|
|
||||||
|
## *YAML*
|
||||||
|
|
||||||
|
* **TODO(a.garipov):** Define naming conventions for schema names in our
|
||||||
|
*OpenAPI* *YAML* file. And just generally OpenAPI conventions.
|
||||||
|
|
||||||
|
* **TODO(a.garipov):** Find a *YAML* formatter or write our own.
|
||||||
|
|
||||||
|
* All strings, including keys, must be quoted. Reason: the [*NO-rway Law*].
|
||||||
|
|
||||||
|
* Indent with two (**2**) spaces. *YAML* documents can get pretty
|
||||||
|
deeply-nested.
|
||||||
|
|
||||||
|
* No extra indentation in multiline arrays:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
'values':
|
||||||
|
- 'value-1'
|
||||||
|
- 'value-2'
|
||||||
|
- 'value-3'
|
||||||
|
```
|
||||||
|
|
||||||
|
* Prefer single quotes for strings to prevent accidental escaping, unless
|
||||||
|
escaping is required or there are single quotes inside the string (e.g. for
|
||||||
|
*GitHub Actions*).
|
||||||
|
|
||||||
|
* Use `>` for multiline strings, unless you need to keep the line breaks.
|
||||||
|
|
||||||
|
[*NO-rway Law*]: https://news.ycombinator.com/item?id=17359376
|
||||||
32
Makefile
32
Makefile
@@ -33,6 +33,7 @@ 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
|
||||||
|
|
||||||
# See release target
|
# See release target
|
||||||
DIST_DIR=dist
|
DIST_DIR=dist
|
||||||
@@ -109,7 +110,7 @@ $(error DOCKER_IMAGE_NAME value is not set)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
# OS-specific flags
|
# OS-specific flags
|
||||||
TEST_FLAGS := -race
|
TEST_FLAGS := --race $(VERBOSE)
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
TEST_FLAGS :=
|
TEST_FLAGS :=
|
||||||
endif
|
endif
|
||||||
@@ -160,11 +161,13 @@ lint-go:
|
|||||||
@echo Running go linter
|
@echo Running go linter
|
||||||
golangci-lint run
|
golangci-lint run
|
||||||
|
|
||||||
test:
|
test: test-js test-go
|
||||||
@echo Running JS unit-tests
|
|
||||||
|
test-js:
|
||||||
npm run test --prefix client
|
npm run test --prefix client
|
||||||
@echo Running Go unit-tests
|
|
||||||
go test $(TEST_FLAGS) -v -coverprofile=coverage.txt -covermode=atomic ./...
|
test-go:
|
||||||
|
go test $(TEST_FLAGS) --coverprofile coverage.txt ./...
|
||||||
|
|
||||||
ci: client_with_deps
|
ci: client_with_deps
|
||||||
go mod download
|
go mod download
|
||||||
@@ -175,20 +178,11 @@ dependencies:
|
|||||||
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
|
|
||||||
# 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 \
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ Here is a link to AdGuard Home project: https://crowdin.com/project/adguard-appl
|
|||||||
Here's what you can also do to contribute:
|
Here's what you can also do to contribute:
|
||||||
|
|
||||||
1. [Look for issues](https://github.com/AdguardTeam/AdGuardHome/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22+) marked as "help wanted".
|
1. [Look for issues](https://github.com/AdguardTeam/AdGuardHome/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22+) marked as "help wanted".
|
||||||
2. Actualize the list of *Blocked services*. It it can be found in [dnsfilter/blocked_services.go](https://github.com/AdguardTeam/AdGuardHome/blob/master/dnsfilter/blocked_services.go).
|
2. Actualize the list of *Blocked services*. It it can be found in [dnsfilter/blocked_services.go](https://github.com/AdguardTeam/AdGuardHome/blob/master/internal/dnsfilter/blocked_services.go).
|
||||||
3. Actualize the list of known *trackers*. It it can be found in [client/src/helpers/trackers/adguard.json](https://github.com/AdguardTeam/AdGuardHome/blob/master/client/src/helpers/trackers/adguard.json).
|
3. Actualize the list of known *trackers*. It it can be found in [client/src/helpers/trackers/adguard.json](https://github.com/AdguardTeam/AdGuardHome/blob/master/client/src/helpers/trackers/adguard.json).
|
||||||
4. Actualize the list of vetted *blocklists*. It it can be found in [client/src/helpers/filters/filters.json](https://github.com/AdguardTeam/AdGuardHome/blob/master/client/src/helpers/filters/filters.json).
|
4. Actualize the list of vetted *blocklists*. It it can be found in [client/src/helpers/filters/filters.json](https://github.com/AdguardTeam/AdGuardHome/blob/master/client/src/helpers/filters/filters.json).
|
||||||
|
|
||||||
|
|||||||
337
client/package-lock.json
generated
vendored
337
client/package-lock.json
generated
vendored
@@ -2014,72 +2014,168 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nivo/axes": {
|
"@nivo/annotations": {
|
||||||
"version": "0.49.1",
|
"version": "0.64.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nivo/axes/-/axes-0.49.1.tgz",
|
"resolved": "https://registry.npmjs.org/@nivo/annotations/-/annotations-0.64.0.tgz",
|
||||||
"integrity": "sha512-2ZqpKtnZ9HE30H+r565VCrypKRQzAoMbAg1hsht88dlNQRtghBSxbAS0Y4IUW/wgN/AzvOIBJHvxH7bgaB8Oow==",
|
"integrity": "sha512-b9VAVuAn2HztOZckU2GcBwptjCobYV5VgX/jwZTSFeZFLtjZza+QinNL2AbQ2cnmeU3nVCw1dTbJMMZ9fTCCNQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@nivo/core": "0.49.0",
|
"@nivo/colors": "0.64.0",
|
||||||
"d3-format": "^1.3.2",
|
"lodash": "^4.17.11",
|
||||||
|
"react-spring": "^8.0.27"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@nivo/axes": {
|
||||||
|
"version": "0.64.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nivo/axes/-/axes-0.64.0.tgz",
|
||||||
|
"integrity": "sha512-Pm+Y3C67OuBb3JqHpyFKWAoPAnNojb1s5/LFQYVYN1QpKyjeqilGAoLCjHK6ckgfzreiGf7NG+oBgpH8Ncz2fQ==",
|
||||||
|
"requires": {
|
||||||
|
"@nivo/scales": "0.64.0",
|
||||||
|
"d3-format": "^1.4.4",
|
||||||
|
"d3-time": "^1.0.11",
|
||||||
"d3-time-format": "^2.1.3",
|
"d3-time-format": "^2.1.3",
|
||||||
"lodash": "^4.17.4",
|
"react-spring": "^8.0.27"
|
||||||
"react-motion": "^0.5.2",
|
},
|
||||||
"recompose": "^0.26.0"
|
"dependencies": {
|
||||||
|
"d3-format": {
|
||||||
|
"version": "1.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
|
||||||
|
"integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ=="
|
||||||
|
},
|
||||||
|
"d3-time": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
|
||||||
|
},
|
||||||
|
"d3-time-format": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==",
|
||||||
|
"requires": {
|
||||||
|
"d3-time": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@nivo/colors": {
|
||||||
|
"version": "0.64.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nivo/colors/-/colors-0.64.0.tgz",
|
||||||
|
"integrity": "sha512-3CKIkSjKgwSgBsiJoTlZpFUUpaGTl60pF6rFsIiFT30os9jMxP/J4ikQGQ/vMLPTXskZYoxByaMHGKJy5wypqg==",
|
||||||
|
"requires": {
|
||||||
|
"d3-color": "^1.2.3",
|
||||||
|
"d3-scale": "^3.0.0",
|
||||||
|
"d3-scale-chromatic": "^1.3.3",
|
||||||
|
"lodash.get": "^4.4.2",
|
||||||
|
"lodash.isplainobject": "^4.0.6",
|
||||||
|
"react-motion": "^0.5.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nivo/core": {
|
"@nivo/core": {
|
||||||
"version": "0.49.0",
|
"version": "0.64.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.64.0.tgz",
|
||||||
"integrity": "sha512-TCPMUO2aJ7fI+ZB6t3d3EBQtNxJnTzaxLJsrVyn/3AQIjUwccAeo2aIy81wLBGWGtlGNUDNdAbnFzXiJosH0yg==",
|
"integrity": "sha512-tupETbvxgv4B9y3pcXy/lErMwY2aZht+FKSyah1dPFd88LnMD/DOL+to6ociBHmpLQNUMA7wid6R7BlXRY/bmg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"d3-color": "^1.0.3",
|
"d3-color": "^1.2.3",
|
||||||
"d3-format": "^1.3.2",
|
"d3-format": "^1.4.4",
|
||||||
"d3-hierarchy": "^1.1.8",
|
"d3-hierarchy": "^1.1.8",
|
||||||
"d3-interpolate": "^1.3.2",
|
"d3-interpolate": "^2.0.1",
|
||||||
"d3-scale": "^2.1.2",
|
"d3-scale": "^3.0.0",
|
||||||
"d3-scale-chromatic": "^1.3.3",
|
"d3-scale-chromatic": "^1.3.3",
|
||||||
"d3-shape": "^1.2.2",
|
"d3-shape": "^1.3.5",
|
||||||
"d3-time-format": "^2.1.3",
|
"d3-time-format": "^2.1.3",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.11",
|
||||||
"react-measure": "^2.0.2",
|
"react-spring": "^8.0.27",
|
||||||
"react-motion": "^0.5.2",
|
"recompose": "^0.30.0",
|
||||||
"recompose": "^0.26.0"
|
"resize-observer-polyfill": "^1.5.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"d3-format": {
|
||||||
|
"version": "1.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
|
||||||
|
"integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ=="
|
||||||
|
},
|
||||||
|
"d3-time": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
|
||||||
|
},
|
||||||
|
"d3-time-format": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==",
|
||||||
|
"requires": {
|
||||||
|
"d3-time": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nivo/legends": {
|
"@nivo/legends": {
|
||||||
"version": "0.49.0",
|
"version": "0.64.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.64.0.tgz",
|
||||||
"integrity": "sha512-8KbUFYozqwD+/rj4in0mnF9b9CuyNFjVgXqm2KW3ODVlWIgYgjTVlEhlg9VZIArFPlIyyAjEYC88YSRcALHugg==",
|
"integrity": "sha512-L7Mp/of/jY4qE7ef6PXJ8/e3aASBTfsf5BTOh3imSXZT6I4hXa5ppmGAgZ0gOSpcPXuMEjBc0aSIEJoeoytQ/g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"lodash": "^4.17.4",
|
"@nivo/core": "0.64.0",
|
||||||
"recompose": "^0.26.0"
|
"lodash": "^4.17.11",
|
||||||
|
"recompose": "^0.30.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nivo/line": {
|
"@nivo/line": {
|
||||||
"version": "0.49.1",
|
"version": "0.64.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.49.1.tgz",
|
"resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.64.0.tgz",
|
||||||
"integrity": "sha512-wKkOmpnwK2psmZbJReDq+Eh/WV9r1JA8V4Vl4eIRuf971CW0KUT9nCAoc/FcKio0qsiq5wyFt3J5LuAhfzlV/w==",
|
"integrity": "sha512-WkQU28ZL9Mxq42AdmybWe+2qFh/TiUXu+7e6nj41e/8DO95Guxg1XQ+i5zQKuw/UZlqXZs6WOsMW8EMNE4GzXw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@nivo/axes": "0.49.1",
|
"@nivo/annotations": "0.64.0",
|
||||||
"@nivo/core": "0.49.0",
|
"@nivo/axes": "0.64.0",
|
||||||
"@nivo/legends": "0.49.0",
|
"@nivo/colors": "0.64.0",
|
||||||
"@nivo/scales": "0.49.0",
|
"@nivo/legends": "0.64.0",
|
||||||
"d3-format": "^1.3.2",
|
"@nivo/scales": "0.64.0",
|
||||||
"d3-scale": "^2.1.2",
|
"@nivo/tooltip": "0.64.0",
|
||||||
"d3-shape": "^1.2.2",
|
"@nivo/voronoi": "0.64.0",
|
||||||
"lodash": "^4.17.4",
|
"d3-shape": "^1.3.5",
|
||||||
"react-motion": "^0.5.2",
|
"react-spring": "^8.0.27"
|
||||||
"recompose": "^0.26.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nivo/scales": {
|
"@nivo/scales": {
|
||||||
"version": "0.49.0",
|
"version": "0.64.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.64.0.tgz",
|
||||||
"integrity": "sha512-+5Leu4zX6mDSAunf4ZJHeqVlT+ZsqiKXLB6hT/u7r3GjxZP9A+n3rHePhIzikBiBRMlLjyiBlylLzhKBAYbGWQ==",
|
"integrity": "sha512-Jbr1rlfe/gLCippndPaCM7doJzzx1K9oXPxS4JiyvrIUkQoMWiozymZQdEp8kgigI6uwWu5xvPwCOFXalCIhKg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"d3-scale": "^2.1.2",
|
"d3-scale": "^3.0.0",
|
||||||
"d3-time-format": "^2.1.3",
|
"d3-time-format": "^2.1.3",
|
||||||
"lodash": "^4.17.4"
|
"lodash": "^4.17.11"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"d3-time": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
|
||||||
|
},
|
||||||
|
"d3-time-format": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==",
|
||||||
|
"requires": {
|
||||||
|
"d3-time": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@nivo/tooltip": {
|
||||||
|
"version": "0.64.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nivo/tooltip/-/tooltip-0.64.0.tgz",
|
||||||
|
"integrity": "sha512-iGsuCi42uw/8F7OVvPyWdQgxJXVOPTEdtl2WK2FlSJIH7bfnEsZ+R/lTdElY2JAvGHuNW6hQwpNUZdC/2rOatg==",
|
||||||
|
"requires": {
|
||||||
|
"react-spring": "^8.0.27"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@nivo/voronoi": {
|
||||||
|
"version": "0.64.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nivo/voronoi/-/voronoi-0.64.0.tgz",
|
||||||
|
"integrity": "sha512-YdNRzD2rFc1NcAZe9D8gxos+IT2CRPOV/7fUfBCG9SoNw1TtSwSKtEs4xsxmUFmLT1FadWcyKeKuhgJUQnof/A==",
|
||||||
|
"requires": {
|
||||||
|
"@nivo/core": "0.64.0",
|
||||||
|
"d3-delaunay": "^5.1.1",
|
||||||
|
"d3-scale": "^3.0.0",
|
||||||
|
"recompose": "^0.30.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nodelib/fs.scandir": {
|
"@nodelib/fs.scandir": {
|
||||||
@@ -4681,24 +4777,27 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"d3-array": {
|
"d3-array": {
|
||||||
"version": "1.2.4",
|
"version": "2.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.8.0.tgz",
|
||||||
"integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
|
"integrity": "sha512-6V272gsOeg7+9pTW1jSYOR1QE37g95I3my1hBmY+vOUNHRrk9yt4OTz/gK7PMkVAVDrYYq4mq3grTiZ8iJdNIw=="
|
||||||
},
|
|
||||||
"d3-collection": {
|
|
||||||
"version": "1.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz",
|
|
||||||
"integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
|
|
||||||
},
|
},
|
||||||
"d3-color": {
|
"d3-color": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz",
|
||||||
"integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q=="
|
"integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q=="
|
||||||
},
|
},
|
||||||
|
"d3-delaunay": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==",
|
||||||
|
"requires": {
|
||||||
|
"delaunator": "4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"d3-format": {
|
"d3-format": {
|
||||||
"version": "1.4.4",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz",
|
||||||
"integrity": "sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw=="
|
"integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA=="
|
||||||
},
|
},
|
||||||
"d3-hierarchy": {
|
"d3-hierarchy": {
|
||||||
"version": "1.1.9",
|
"version": "1.1.9",
|
||||||
@@ -4706,11 +4805,11 @@
|
|||||||
"integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ=="
|
"integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ=="
|
||||||
},
|
},
|
||||||
"d3-interpolate": {
|
"d3-interpolate": {
|
||||||
"version": "1.4.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz",
|
||||||
"integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==",
|
"integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"d3-color": "1"
|
"d3-color": "1 - 2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"d3-path": {
|
"d3-path": {
|
||||||
@@ -4719,16 +4818,15 @@
|
|||||||
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
|
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
|
||||||
},
|
},
|
||||||
"d3-scale": {
|
"d3-scale": {
|
||||||
"version": "2.2.2",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.2.3.tgz",
|
||||||
"integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==",
|
"integrity": "sha512-8E37oWEmEzj57bHcnjPVOBS3n4jqakOeuv1EDdQSiSrYnMCBdMd3nc4HtKk7uia8DUHcY/CGuJ42xxgtEYrX0g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"d3-array": "^1.2.0",
|
"d3-array": "^2.3.0",
|
||||||
"d3-collection": "1",
|
"d3-format": "1 - 2",
|
||||||
"d3-format": "1",
|
"d3-interpolate": "1.2.0 - 2",
|
||||||
"d3-interpolate": "1",
|
"d3-time": "1 - 2",
|
||||||
"d3-time": "1",
|
"d3-time-format": "2 - 3"
|
||||||
"d3-time-format": "2"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"d3-scale-chromatic": {
|
"d3-scale-chromatic": {
|
||||||
@@ -4738,6 +4836,16 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"d3-color": "1",
|
"d3-color": "1",
|
||||||
"d3-interpolate": "1"
|
"d3-interpolate": "1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"d3-interpolate": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==",
|
||||||
|
"requires": {
|
||||||
|
"d3-color": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"d3-shape": {
|
"d3-shape": {
|
||||||
@@ -4749,16 +4857,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"d3-time": {
|
"d3-time": {
|
||||||
"version": "1.1.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.0.0.tgz",
|
||||||
"integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
|
"integrity": "sha512-2mvhstTFcMvwStWd9Tj3e6CEqtOivtD8AUiHT8ido/xmzrI9ijrUUihZ6nHuf/vsScRBonagOdj0Vv+SEL5G3Q=="
|
||||||
},
|
},
|
||||||
"d3-time-format": {
|
"d3-time-format": {
|
||||||
"version": "2.2.3",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
|
||||||
"integrity": "sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA==",
|
"integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"d3-time": "1"
|
"d3-time": "1 - 2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"damerau-levenshtein": {
|
"damerau-levenshtein": {
|
||||||
@@ -4963,6 +5071,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"delaunator": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag=="
|
||||||
|
},
|
||||||
"delayed-stream": {
|
"delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
@@ -5253,11 +5366,21 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"encoding": {
|
"encoding": {
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||||
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"iconv-lite": "~0.4.13"
|
"iconv-lite": "^0.6.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": {
|
||||||
|
"version": "0.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
||||||
|
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
||||||
|
"requires": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"end-of-stream": {
|
"end-of-stream": {
|
||||||
@@ -6766,11 +6889,6 @@
|
|||||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"get-node-dimensions": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-node-dimensions/-/get-node-dimensions-1.2.1.tgz",
|
|
||||||
"integrity": "sha512-2MSPMu7S1iOTL+BOa6K1S62hB2zUAYNF/lV0gSVlOaacd087lc6nR1H1r0e3B1CerTo+RceOmi1iJW+vp21xcQ=="
|
|
||||||
},
|
|
||||||
"get-package-type": {
|
"get-package-type": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
|
||||||
@@ -7430,6 +7548,7 @@
|
|||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3"
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
}
|
}
|
||||||
@@ -10194,6 +10313,16 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz",
|
||||||
"integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ=="
|
"integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ=="
|
||||||
},
|
},
|
||||||
|
"lodash.get": {
|
||||||
|
"version": "4.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||||
|
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
|
||||||
|
},
|
||||||
|
"lodash.isplainobject": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
|
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
|
||||||
|
},
|
||||||
"lodash.sortby": {
|
"lodash.sortby": {
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
||||||
@@ -12403,17 +12532,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||||
},
|
},
|
||||||
"react-measure": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-measure/-/react-measure-2.3.0.tgz",
|
|
||||||
"integrity": "sha512-dwAvmiOeblj5Dvpnk8Jm7Q8B4THF/f1l1HtKVi0XDecsG6LXwGvzV5R1H32kq3TW6RW64OAf5aoQxpIgLa4z8A==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.2.0",
|
|
||||||
"get-node-dimensions": "^1.2.1",
|
|
||||||
"prop-types": "^15.6.2",
|
|
||||||
"resize-observer-polyfill": "^1.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-modal": {
|
"react-modal": {
|
||||||
"version": "3.11.2",
|
"version": "3.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.11.2.tgz",
|
||||||
@@ -12576,6 +12694,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-spring": {
|
||||||
|
"version": "8.0.27",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-spring/-/react-spring-8.0.27.tgz",
|
||||||
|
"integrity": "sha512-nDpWBe3ZVezukNRandTeLSPcwwTMjNVu1IDq9qA/AMiUqHuRN4BeSWvKr3eIxxg1vtiYiOLy4FqdfCP5IoP77g==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.3.1",
|
||||||
|
"prop-types": "^15.5.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-table": {
|
"react-table": {
|
||||||
"version": "6.11.4",
|
"version": "6.11.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-table/-/react-table-6.11.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-table/-/react-table-6.11.4.tgz",
|
||||||
@@ -12663,13 +12790,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"recompose": {
|
"recompose": {
|
||||||
"version": "0.26.0",
|
"version": "0.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz",
|
||||||
"integrity": "sha512-KwOu6ztO0mN5vy3+zDcc45lgnaUoaQse/a5yLVqtzTK13czSWnFGmXbQVmnoMgDkI5POd1EwIKSbjU1V7xdZog==",
|
"integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.0.0",
|
||||||
"change-emitter": "^0.1.2",
|
"change-emitter": "^0.1.2",
|
||||||
"fbjs": "^0.8.1",
|
"fbjs": "^0.8.1",
|
||||||
"hoist-non-react-statics": "^2.3.1",
|
"hoist-non-react-statics": "^2.3.1",
|
||||||
|
"react-lifecycles-compat": "^3.0.2",
|
||||||
"symbol-observable": "^1.0.4"
|
"symbol-observable": "^1.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -15067,9 +15196,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ua-parser-js": {
|
"ua-parser-js": {
|
||||||
"version": "0.7.21",
|
"version": "0.7.22",
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
||||||
"integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ=="
|
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
|
||||||
},
|
},
|
||||||
"unherit": {
|
"unherit": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
@@ -16086,9 +16215,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"whatwg-fetch": {
|
"whatwg-fetch": {
|
||||||
"version": "3.0.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz",
|
||||||
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
|
"integrity": "sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A=="
|
||||||
},
|
},
|
||||||
"whatwg-mimetype": {
|
"whatwg-mimetype": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
|
|||||||
2
client/package.json
vendored
2
client/package.json
vendored
@@ -13,7 +13,7 @@
|
|||||||
"test:watch": "jest --watch"
|
"test:watch": "jest --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nivo/line": "^0.49.1",
|
"@nivo/line": "^0.64.0",
|
||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"date-fns": "^1.29.0",
|
"date-fns": "^1.29.0",
|
||||||
|
|||||||
@@ -249,6 +249,7 @@
|
|||||||
"blocking_ipv6": "Blocking IPv6",
|
"blocking_ipv6": "Blocking IPv6",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"dns_over_tls": "DNS-over-TLS",
|
||||||
|
"dns_over_quic": "DNS-over-QUIC",
|
||||||
"download_mobileconfig_doh": "Download .mobileconfig for DNS-over-HTTPS",
|
"download_mobileconfig_doh": "Download .mobileconfig for DNS-over-HTTPS",
|
||||||
"download_mobileconfig_dot": "Download .mobileconfig for DNS-over-TLS",
|
"download_mobileconfig_dot": "Download .mobileconfig for DNS-over-TLS",
|
||||||
"plain_dns": "Plain DNS",
|
"plain_dns": "Plain DNS",
|
||||||
@@ -586,5 +587,12 @@
|
|||||||
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this.",
|
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this.",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.",
|
"adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.",
|
||||||
"client_not_in_allowed_clients": "The client is not allowed because it is not in the \"Allowed clients\" list.",
|
"client_not_in_allowed_clients": "The client is not allowed because it is not in the \"Allowed clients\" list.",
|
||||||
"experimental": "Experimental"
|
"experimental": "Experimental",
|
||||||
}
|
"rebinding_title": "DNS Rebinding Protection",
|
||||||
|
"rebinding_desc": "Here you can configure protection against DNS rebinding attacks",
|
||||||
|
"rebinding_protection_enabled": "Enable protection from DNS rebinding attacks",
|
||||||
|
"rebinding_protection_enabled_desc": "If enabled, AdGuard Home will block responses containing host on the local network.",
|
||||||
|
"rebinding_allowed_hosts_title": "Allowed domains",
|
||||||
|
"rebinding_allowed_hosts_desc": "A list of domains. If configured, AdGuard Home will allow responses containing host on the local network from these domains. Here you can specify the exact domain names, wildcards and urlfilter-rules, e.g. 'example.org', '*.example.org' or '||example.org^'.",
|
||||||
|
"blocked_dns_rebinding": "Blocked DNS rebinding"
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,9 +51,10 @@ export const toggleClientBlockSuccess = createAction('TOGGLE_CLIENT_BLOCK_SUCCES
|
|||||||
export const toggleClientBlock = (ip, disallowed, disallowed_rule) => async (dispatch) => {
|
export const toggleClientBlock = (ip, disallowed, disallowed_rule) => async (dispatch) => {
|
||||||
dispatch(toggleClientBlockRequest());
|
dispatch(toggleClientBlockRequest());
|
||||||
try {
|
try {
|
||||||
const {
|
const accessList = await apiClient.getAccessList();
|
||||||
allowed_clients, blocked_hosts, disallowed_clients = [],
|
const allowed_clients = accessList.allowed_clients ?? [];
|
||||||
} = await apiClient.getAccessList();
|
const blocked_hosts = accessList.blocked_hosts ?? [];
|
||||||
|
const disallowed_clients = accessList.disallowed_clients ?? [];
|
||||||
|
|
||||||
const updatedDisallowedClients = disallowed
|
const updatedDisallowedClients = disallowed
|
||||||
? disallowed_clients.filter((client) => client !== disallowed_rule)
|
? disallowed_clients.filter((client) => client !== disallowed_rule)
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ export const setDnsConfig = (config) => async (dispatch) => {
|
|||||||
data.upstream_dns = splitByNewLine(config.upstream_dns);
|
data.upstream_dns = splitByNewLine(config.upstream_dns);
|
||||||
hasDnsSettings = true;
|
hasDnsSettings = true;
|
||||||
}
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(data, 'rebinding_allowed_hosts')) {
|
||||||
|
data.rebinding_allowed_hosts = splitByNewLine(config.rebinding_allowed_hosts);
|
||||||
|
hasDnsSettings = true;
|
||||||
|
}
|
||||||
|
|
||||||
await apiClient.setDnsConfig(data);
|
await apiClient.setDnsConfig(data);
|
||||||
|
|
||||||
|
|||||||
@@ -373,10 +373,14 @@ export const getDhcpStatusFailure = createAction('GET_DHCP_STATUS_FAILURE');
|
|||||||
export const getDhcpStatus = () => async (dispatch) => {
|
export const getDhcpStatus = () => async (dispatch) => {
|
||||||
dispatch(getDhcpStatusRequest());
|
dispatch(getDhcpStatusRequest());
|
||||||
try {
|
try {
|
||||||
const status = await apiClient.getDhcpStatus();
|
|
||||||
const globalStatus = await apiClient.getGlobalStatus();
|
const globalStatus = await apiClient.getGlobalStatus();
|
||||||
status.dhcp_available = globalStatus.dhcp_available;
|
if (globalStatus.dhcp_available) {
|
||||||
dispatch(getDhcpStatusSuccess(status));
|
const status = await apiClient.getDhcpStatus();
|
||||||
|
status.dhcp_available = globalStatus.dhcp_available;
|
||||||
|
dispatch(getDhcpStatusSuccess(status));
|
||||||
|
} else {
|
||||||
|
dispatch(getDhcpStatusFailure());
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatch(addErrorToast({ error }));
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(getDhcpStatusFailure());
|
dispatch(getDhcpStatusFailure());
|
||||||
|
|||||||
@@ -22,11 +22,6 @@
|
|||||||
border-bottom: 6px solid #585965;
|
border-bottom: 6px solid #585965;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-chart-bg {
|
|
||||||
left: -20px;
|
|
||||||
width: calc(100% + 20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1279.98px) {
|
@media (max-width: 1279.98px) {
|
||||||
.table__action {
|
.table__action {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
--gray-4d: #4D4D4D;
|
--gray-4d: #4D4D4D;
|
||||||
--gray-f3: #F3F3F3;
|
--gray-f3: #F3F3F3;
|
||||||
--gray-8: #888;
|
--gray-8: #888;
|
||||||
|
--gray-3: #333;
|
||||||
--danger: #DF3812;
|
--danger: #DF3812;
|
||||||
--white80: rgba(255, 255, 255, 0.8);
|
--white80: rgba(255, 255, 255, 0.8);
|
||||||
|
|
||||||
|
|||||||
@@ -72,30 +72,30 @@ const Interfaces = () => {
|
|||||||
(store) => store.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name,
|
(store) => store.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (processingInterfaces || !interfaces) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const interfaceValue = interface_name && interfaces[interface_name];
|
const interfaceValue = interface_name && interfaces[interface_name];
|
||||||
|
|
||||||
return !processingInterfaces
|
return <div className="row dhcp__interfaces">
|
||||||
&& interfaces
|
<div className="col col__dhcp">
|
||||||
&& <>
|
<Field
|
||||||
<div className="row dhcp__interfaces">
|
name="interface_name"
|
||||||
<div className="col col__dhcp">
|
component={renderSelectField}
|
||||||
<Field
|
className="form-control custom-select pl-4 col-md"
|
||||||
name="interface_name"
|
validate={[validateRequiredValue]}
|
||||||
component={renderSelectField}
|
label='dhcp_interface_select'
|
||||||
className="form-control custom-select pl-4 col-md"
|
>
|
||||||
validate={[validateRequiredValue]}
|
<option value='' disabled={enabled}>
|
||||||
label='dhcp_interface_select'
|
{t('dhcp_interface_select')}
|
||||||
>
|
</option>
|
||||||
<option value='' disabled={enabled}>
|
{renderInterfaces(interfaces)}
|
||||||
{t('dhcp_interface_select')}
|
</Field>
|
||||||
</option>
|
</div>
|
||||||
{renderInterfaces(interfaces)}
|
{interfaceValue
|
||||||
</Field>
|
&& renderInterfaceValues(interfaceValue)}
|
||||||
</div>
|
</div>;
|
||||||
{interfaceValue
|
|
||||||
&& renderInterfaceValues(interfaceValue)}
|
|
||||||
</div>
|
|
||||||
</>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
renderInterfaceValues.propTypes = {
|
renderInterfaceValues.propTypes = {
|
||||||
|
|||||||
@@ -65,9 +65,14 @@ const Dhcp = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getDhcpStatus());
|
dispatch(getDhcpStatus());
|
||||||
dispatch(getDhcpInterfaces());
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dhcp_available) {
|
||||||
|
dispatch(getDhcpInterfaces());
|
||||||
|
}
|
||||||
|
}, [dhcp_available]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const [ipv4] = interfaces?.[interface_name]?.ipv4_addresses ?? [];
|
const [ipv4] = interfaces?.[interface_name]?.ipv4_addresses ?? [];
|
||||||
const [ipv6] = interfaces?.[interface_name]?.ipv6_addresses ?? [];
|
const [ipv6] = interfaces?.[interface_name]?.ipv6_addresses ?? [];
|
||||||
|
|||||||
91
client/src/components/Settings/Dns/Rebinding/Form.js
Normal file
91
client/src/components/Settings/Dns/Rebinding/Form.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Field, reduxForm } from 'redux-form';
|
||||||
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { renderTextareaField, CheckboxField } from '../../../../helpers/form';
|
||||||
|
import { removeEmptyLines } from '../../../../helpers/helpers';
|
||||||
|
import { FORM_NAME } from '../../../../helpers/constants';
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
id: 'rebinding_allowed_hosts',
|
||||||
|
title: 'rebinding_allowed_hosts_title',
|
||||||
|
subtitle: 'rebinding_allowed_hosts_desc',
|
||||||
|
normalizeOnBlur: removeEmptyLines,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const Form = ({
|
||||||
|
handleSubmit, submitting, invalid,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const processingSetConfig = useSelector((state) => state.dnsConfig.processingSetConfig);
|
||||||
|
|
||||||
|
const renderField = ({
|
||||||
|
id, title, subtitle, disabled = processingSetConfig, normalizeOnBlur,
|
||||||
|
}) => <div key={id} className="form__group mb-5">
|
||||||
|
<label className="form__label form__label--with-desc" htmlFor={id}>
|
||||||
|
<Trans>{title}</Trans>
|
||||||
|
</label>
|
||||||
|
<div className="form__desc form__desc--top">
|
||||||
|
<Trans>{subtitle}</Trans>
|
||||||
|
</div>
|
||||||
|
<Field
|
||||||
|
id={id}
|
||||||
|
name={id}
|
||||||
|
component={renderTextareaField}
|
||||||
|
type="text"
|
||||||
|
className="form-control form-control--textarea font-monospace"
|
||||||
|
disabled={disabled}
|
||||||
|
normalizeOnBlur={normalizeOnBlur}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
renderField.propTypes = {
|
||||||
|
id: PropTypes.string,
|
||||||
|
title: PropTypes.string,
|
||||||
|
subtitle: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
normalizeOnBlur: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="form__group form__group--settings">
|
||||||
|
<Field
|
||||||
|
name={'rebinding_protection_enabled'}
|
||||||
|
type="checkbox"
|
||||||
|
component={CheckboxField}
|
||||||
|
placeholder={t('rebinding_protection_enabled')}
|
||||||
|
subtitle={t('rebinding_protection_enabled_desc')}
|
||||||
|
disabled={processingSetConfig}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{fields.map(renderField)}
|
||||||
|
|
||||||
|
<div className="card-actions">
|
||||||
|
<div className="btn-list">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-success btn-standard"
|
||||||
|
disabled={submitting || invalid || processingSetConfig}
|
||||||
|
>
|
||||||
|
<Trans>save_config</Trans>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.propTypes = {
|
||||||
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
|
submitting: PropTypes.bool.isRequired,
|
||||||
|
invalid: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default reduxForm({ form: FORM_NAME.REBINDING })(Form);
|
||||||
36
client/src/components/Settings/Dns/Rebinding/index.js
Normal file
36
client/src/components/Settings/Dns/Rebinding/index.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
import Form from './Form';
|
||||||
|
import Card from '../../../ui/Card';
|
||||||
|
import { setDnsConfig } from '../../../../actions/dnsConfig';
|
||||||
|
|
||||||
|
const RebindingConfig = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
rebinding_protection_enabled, rebinding_allowed_hosts,
|
||||||
|
} = useSelector((state) => state.dnsConfig, shallowEqual);
|
||||||
|
|
||||||
|
const handleFormSubmit = (values) => {
|
||||||
|
dispatch(setDnsConfig(values));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={t('rebinding_title')}
|
||||||
|
subtitle={t('rebinding_desc')}
|
||||||
|
bodyType="card-body box-body--settings"
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
initialValues={{
|
||||||
|
rebinding_protection_enabled,
|
||||||
|
rebinding_allowed_hosts,
|
||||||
|
}}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RebindingConfig;
|
||||||
@@ -8,6 +8,7 @@ import Config from './Config';
|
|||||||
import PageTitle from '../../ui/PageTitle';
|
import PageTitle from '../../ui/PageTitle';
|
||||||
import Loading from '../../ui/Loading';
|
import Loading from '../../ui/Loading';
|
||||||
import CacheConfig from './Cache';
|
import CacheConfig from './Cache';
|
||||||
|
import RebindingConfig from './Rebinding';
|
||||||
import { getDnsConfig } from '../../../actions/dnsConfig';
|
import { getDnsConfig } from '../../../actions/dnsConfig';
|
||||||
import { getAccessList } from '../../../actions/access';
|
import { getAccessList } from '../../../actions/access';
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ const Dns = () => {
|
|||||||
<Config />
|
<Config />
|
||||||
<CacheConfig />
|
<CacheConfig />
|
||||||
<Access />
|
<Access />
|
||||||
|
<RebindingConfig />
|
||||||
</>}
|
</>}
|
||||||
</>;
|
</>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -80,7 +80,6 @@
|
|||||||
.card-body-stats {
|
.card-body-stats {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
height: calc(100% - 3rem);
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 1rem 1.5rem;
|
padding: 1rem 1.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
.line__tooltip {
|
.line__tooltip {
|
||||||
padding: 2px 10px 7px;
|
padding: 2px 10px 7px;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
color: #fff;
|
color: var(--white);
|
||||||
|
background-color: var(--gray-3);
|
||||||
|
border-radius: 4px;
|
||||||
|
opacity: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.line__tooltip-text {
|
.line__tooltip-text {
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-chart-bg path[d^="M0,32"] {
|
||||||
|
transform: translateY(32px);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,56 +1,75 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { ResponsiveLine } from '@nivo/line';
|
import { ResponsiveLine } from '@nivo/line';
|
||||||
|
import addDays from 'date-fns/add_days';
|
||||||
|
import addHours from 'date-fns/add_hours';
|
||||||
|
import subDays from 'date-fns/sub_days';
|
||||||
|
import subHours from 'date-fns/sub_hours';
|
||||||
|
import dateFormat from 'date-fns/format';
|
||||||
|
import round from 'lodash/round';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import './Line.css';
|
import './Line.css';
|
||||||
|
|
||||||
const Line = ({ data, color }) => data
|
const Line = ({
|
||||||
&& <ResponsiveLine
|
data, color = 'black',
|
||||||
|
}) => {
|
||||||
|
const interval = useSelector((state) => state.stats.interval);
|
||||||
|
|
||||||
|
return <ResponsiveLine
|
||||||
|
enableArea
|
||||||
|
animate
|
||||||
|
enableSlices="x"
|
||||||
|
curve="linear"
|
||||||
|
colors={[color]}
|
||||||
data={data}
|
data={data}
|
||||||
margin={{
|
|
||||||
top: data[0].data.every(({ y }) => y === 0) ? 62 : 15,
|
|
||||||
right: 0,
|
|
||||||
bottom: 1,
|
|
||||||
left: 20,
|
|
||||||
}}
|
|
||||||
minY="auto"
|
|
||||||
stacked={false}
|
|
||||||
curve='linear'
|
|
||||||
axisBottom={null}
|
|
||||||
axisLeft={null}
|
|
||||||
enableGridX={false}
|
|
||||||
enableGridY={false}
|
|
||||||
enableDots={false}
|
|
||||||
enableArea={true}
|
|
||||||
animate={false}
|
|
||||||
colorBy={() => (color)}
|
|
||||||
tooltip={(slice) => (
|
|
||||||
<div>
|
|
||||||
{slice.data.map((d) => (
|
|
||||||
<div key={d.serie.id} className="line__tooltip">
|
|
||||||
<span className="line__tooltip-text">
|
|
||||||
<strong>{d.data.y}</strong>
|
|
||||||
<br />
|
|
||||||
<small>{d.data.x}</small>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
theme={{
|
theme={{
|
||||||
tooltip: {
|
crosshair: {
|
||||||
container: {
|
line: {
|
||||||
padding: '0',
|
stroke: 'black',
|
||||||
background: '#333',
|
strokeWidth: 1,
|
||||||
borderRadius: '4px',
|
strokeOpacity: 0.35,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
xScale={{
|
||||||
|
type: 'linear',
|
||||||
|
min: 0,
|
||||||
|
max: 'auto',
|
||||||
|
}}
|
||||||
|
crosshairType="x"
|
||||||
|
axisLeft={false}
|
||||||
|
axisBottom={false}
|
||||||
|
enableGridX={false}
|
||||||
|
enableGridY={false}
|
||||||
|
enablePoints={false}
|
||||||
|
xFormat={(x) => {
|
||||||
|
if (interval === 1 || interval === 7) {
|
||||||
|
const hoursAgo = subHours(Date.now(), 24 * interval);
|
||||||
|
return dateFormat(addHours(hoursAgo, x), 'D MMM HH:00');
|
||||||
|
}
|
||||||
|
|
||||||
|
const daysAgo = subDays(Date.now(), interval - 1);
|
||||||
|
return dateFormat(addDays(daysAgo, x), 'D MMM YYYY');
|
||||||
|
}}
|
||||||
|
yFormat={(y) => round(y, 2)}
|
||||||
|
sliceTooltip={(slice) => {
|
||||||
|
const { xFormatted, yFormatted } = slice.slice.points[0].data;
|
||||||
|
return <div className="line__tooltip">
|
||||||
|
<span className="line__tooltip-text">
|
||||||
|
<strong>{yFormatted}</strong>
|
||||||
|
<br />
|
||||||
|
<small>{xFormatted}</small>
|
||||||
|
</span>
|
||||||
|
</div>;
|
||||||
|
}}
|
||||||
/>;
|
/>;
|
||||||
|
};
|
||||||
|
|
||||||
Line.propTypes = {
|
Line.propTypes = {
|
||||||
data: PropTypes.array.isRequired,
|
data: PropTypes.array.isRequired,
|
||||||
color: PropTypes.string.isRequired,
|
color: PropTypes.string,
|
||||||
|
width: PropTypes.number,
|
||||||
|
height: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Line;
|
export default Line;
|
||||||
|
|||||||
@@ -13452,10 +13452,8 @@ a.icon:hover {
|
|||||||
|
|
||||||
.card-chart-bg {
|
.card-chart-bg {
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
margin-top: -1rem;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-options {
|
.card-options {
|
||||||
|
|||||||
@@ -341,6 +341,7 @@ export const FILTERED_STATUS = {
|
|||||||
REWRITE_HOSTS: 'RewriteEtcHosts',
|
REWRITE_HOSTS: 'RewriteEtcHosts',
|
||||||
FILTERED_SAFE_SEARCH: 'FilteredSafeSearch',
|
FILTERED_SAFE_SEARCH: 'FilteredSafeSearch',
|
||||||
FILTERED_SAFE_BROWSING: 'FilteredSafeBrowsing',
|
FILTERED_SAFE_BROWSING: 'FilteredSafeBrowsing',
|
||||||
|
FILTERED_REBIND: 'FilteredRebind',
|
||||||
FILTERED_PARENTAL: 'FilteredParental',
|
FILTERED_PARENTAL: 'FilteredParental',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -373,6 +374,10 @@ export const RESPONSE_FILTER = {
|
|||||||
QUERY: 'blocked_parental',
|
QUERY: 'blocked_parental',
|
||||||
LABEL: 'blocked_adult_websites',
|
LABEL: 'blocked_adult_websites',
|
||||||
},
|
},
|
||||||
|
BLOCKED_DNS_REBINDING: {
|
||||||
|
QUERY: 'blocked_dns_rebinding',
|
||||||
|
LABEL: 'blocked_dns_rebinding',
|
||||||
|
},
|
||||||
ALLOWED: {
|
ALLOWED: {
|
||||||
QUERY: 'whitelisted',
|
QUERY: 'whitelisted',
|
||||||
LABEL: 'allowed',
|
LABEL: 'allowed',
|
||||||
@@ -414,6 +419,10 @@ export const FILTERED_STATUS_TO_META_MAP = {
|
|||||||
LABEL: 'blocked_service',
|
LABEL: 'blocked_service',
|
||||||
COLOR: QUERY_STATUS_COLORS.RED,
|
COLOR: QUERY_STATUS_COLORS.RED,
|
||||||
},
|
},
|
||||||
|
[FILTERED_STATUS.FILTERED_REBIND]: {
|
||||||
|
LABEL: RESPONSE_FILTER.BLOCKED_DNS_REBINDING.LABEL,
|
||||||
|
COLOR: QUERY_STATUS_COLORS.RED,
|
||||||
|
},
|
||||||
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
|
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
|
||||||
LABEL: RESPONSE_FILTER.SAFE_SEARCH.LABEL,
|
LABEL: RESPONSE_FILTER.SAFE_SEARCH.LABEL,
|
||||||
COLOR: QUERY_STATUS_COLORS.YELLOW,
|
COLOR: QUERY_STATUS_COLORS.YELLOW,
|
||||||
@@ -475,6 +484,7 @@ export const BLOCK_ACTIONS = {
|
|||||||
export const SCHEME_TO_PROTOCOL_MAP = {
|
export const SCHEME_TO_PROTOCOL_MAP = {
|
||||||
doh: 'dns_over_https',
|
doh: 'dns_over_https',
|
||||||
dot: 'dns_over_tls',
|
dot: 'dns_over_tls',
|
||||||
|
doq: 'dns_over_quic',
|
||||||
'': 'plain_dns',
|
'': 'plain_dns',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -508,6 +518,7 @@ export const FORM_NAME = {
|
|||||||
INSTALL: 'install',
|
INSTALL: 'install',
|
||||||
LOGIN: 'login',
|
LOGIN: 'login',
|
||||||
CACHE: 'cache',
|
CACHE: 'cache',
|
||||||
|
REBINDING: 'rebinding',
|
||||||
...DHCP_FORM_NAMES,
|
...DHCP_FORM_NAMES,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import 'url-polyfill';
|
import 'url-polyfill';
|
||||||
import dateParse from 'date-fns/parse';
|
import dateParse from 'date-fns/parse';
|
||||||
import dateFormat from 'date-fns/format';
|
import dateFormat from 'date-fns/format';
|
||||||
import subHours from 'date-fns/sub_hours';
|
|
||||||
import addHours from 'date-fns/add_hours';
|
|
||||||
import addDays from 'date-fns/add_days';
|
|
||||||
import subDays from 'date-fns/sub_days';
|
|
||||||
import round from 'lodash/round';
|
import round from 'lodash/round';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
@@ -105,21 +101,10 @@ export const normalizeLogs = (logs) => logs.map((log) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const normalizeHistory = (history, interval) => {
|
export const normalizeHistory = (history) => history.map((item, idx) => ({
|
||||||
if (interval === 1 || interval === 7) {
|
x: idx,
|
||||||
const hoursAgo = subHours(Date.now(), 24 * interval);
|
y: item,
|
||||||
return history.map((item, index) => ({
|
}));
|
||||||
x: dateFormat(addHours(hoursAgo, index), 'D MMM HH:00'),
|
|
||||||
y: round(item, 2),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const daysAgo = subDays(Date.now(), interval - 1);
|
|
||||||
return history.map((item, index) => ({
|
|
||||||
x: dateFormat(addDays(daysAgo, index), 'D MMM YYYY'),
|
|
||||||
y: round(item, 2),
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const normalizeTopStats = (stats) => (
|
export const normalizeTopStats = (stats) => (
|
||||||
stats.map((item) => ({
|
stats.map((item) => ({
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const getTextareaCommentsHighlight = (
|
|||||||
) => {
|
) => {
|
||||||
const renderLine = (line, idx) => renderHighlightedLine(line, idx, commentLineTokens);
|
const renderLine = (line, idx) => renderHighlightedLine(line, idx, commentLineTokens);
|
||||||
|
|
||||||
return <code className={classnames('text-output', className)} ref={ref}>{lines?.split('\n').map(renderLine)}</code>;
|
return <code className={classnames('text-output font-monospace', className)} ref={ref}>{lines?.split('\n').map(renderLine)}</code>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const syncScroll = (e, ref) => {
|
export const syncScroll = (e, ref) => {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const dnsConfig = handleActions(
|
|||||||
blocking_ipv6,
|
blocking_ipv6,
|
||||||
upstream_dns,
|
upstream_dns,
|
||||||
bootstrap_dns,
|
bootstrap_dns,
|
||||||
|
rebinding_allowed_hosts,
|
||||||
...values
|
...values
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
@@ -24,8 +25,9 @@ const dnsConfig = handleActions(
|
|||||||
...values,
|
...values,
|
||||||
blocking_ipv4: blocking_ipv4 || DEFAULT_BLOCKING_IPV4,
|
blocking_ipv4: blocking_ipv4 || DEFAULT_BLOCKING_IPV4,
|
||||||
blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6,
|
blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6,
|
||||||
upstream_dns: (upstream_dns && upstream_dns.join('\n')) || '',
|
upstream_dns: upstream_dns?.join('\n') || '',
|
||||||
bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '',
|
bootstrap_dns: bootstrap_dns?.join('\n') || '',
|
||||||
|
rebinding_allowed_hosts: rebinding_allowed_hosts?.join('\n') || '',
|
||||||
processingGetConfig: false,
|
processingGetConfig: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
1
client/webpack.dev.js
vendored
1
client/webpack.dev.js
vendored
@@ -44,7 +44,6 @@ const getDevServerConfig = (proxyUrl = BASE_URL) => {
|
|||||||
proxy: {
|
proxy: {
|
||||||
[proxyUrl]: `http://${devServerHost}:${port}`,
|
[proxyUrl]: `http://${devServerHost}:${port}`,
|
||||||
},
|
},
|
||||||
open: true,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,215 +0,0 @@
|
|||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
|
||||||
|
|
||||||
package dhcpd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/dhcpd/nclient4"
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv6/nclient6"
|
|
||||||
"github.com/insomniacslk/dhcp/iana"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CheckIfOtherDHCPServersPresentV4 sends a DHCP request to the specified network interface,
|
|
||||||
// and waits for a response for a period defined by defaultDiscoverTime
|
|
||||||
func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) {
|
|
||||||
iface, err := net.InterfaceByName(ifaceName)
|
|
||||||
if err != nil {
|
|
||||||
return false, wrapErrPrint(err, "Couldn't find interface by name %s", ifaceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get ipv4 address of an interface
|
|
||||||
ifaceIPNet := getIfaceIPv4(*iface)
|
|
||||||
if len(ifaceIPNet) == 0 {
|
|
||||||
return false, fmt.Errorf("couldn't find IPv4 address of interface %s %+v", ifaceName, iface)
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
return false, fmt.Errorf("can't find DHCP server: not supported on macOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
srcIP := ifaceIPNet[0]
|
|
||||||
src := net.JoinHostPort(srcIP.String(), "68")
|
|
||||||
dst := "255.255.255.255:67"
|
|
||||||
|
|
||||||
hostname, _ := os.Hostname()
|
|
||||||
|
|
||||||
req, err := dhcpv4.NewDiscovery(iface.HardwareAddr)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("dhcpv4.NewDiscovery: %s", err)
|
|
||||||
}
|
|
||||||
req.Options.Update(dhcpv4.OptClientIdentifier(iface.HardwareAddr))
|
|
||||||
req.Options.Update(dhcpv4.OptHostName(hostname))
|
|
||||||
|
|
||||||
// resolve 0.0.0.0:68
|
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp4", src)
|
|
||||||
if err != nil {
|
|
||||||
return false, wrapErrPrint(err, "Couldn't resolve UDP address %s", src)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !udpAddr.IP.To4().Equal(srcIP) {
|
|
||||||
return false, wrapErrPrint(err, "Resolved UDP address is not %s", src)
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve 255.255.255.255:67
|
|
||||||
dstAddr, err := net.ResolveUDPAddr("udp4", dst)
|
|
||||||
if err != nil {
|
|
||||||
return false, wrapErrPrint(err, "Couldn't resolve UDP address %s", dst)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bind to 0.0.0.0:68
|
|
||||||
log.Tracef("Listening to udp4 %+v", udpAddr)
|
|
||||||
c, err := nclient4.NewRawUDPConn(ifaceName, 68)
|
|
||||||
if err != nil {
|
|
||||||
return false, wrapErrPrint(err, "Couldn't listen on :68")
|
|
||||||
}
|
|
||||||
if c != nil {
|
|
||||||
defer c.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// send to 255.255.255.255:67
|
|
||||||
_, err = c.WriteTo(req.ToBytes(), dstAddr)
|
|
||||||
if err != nil {
|
|
||||||
return false, wrapErrPrint(err, "Couldn't send a packet to %s", dst)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
// wait for answer
|
|
||||||
log.Tracef("Waiting %v for an answer", defaultDiscoverTime)
|
|
||||||
// TODO: replicate dhclient's behaviour of retrying several times with progressively bigger timeouts
|
|
||||||
b := make([]byte, 1500)
|
|
||||||
_ = c.SetReadDeadline(time.Now().Add(defaultDiscoverTime))
|
|
||||||
n, _, err := c.ReadFrom(b)
|
|
||||||
if isTimeout(err) {
|
|
||||||
// timed out -- no DHCP servers
|
|
||||||
log.Debug("DHCPv4: didn't receive DHCP response")
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return false, wrapErrPrint(err, "Couldn't receive packet")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Tracef("Received packet (%v bytes)", n)
|
|
||||||
|
|
||||||
response, err := dhcpv4.FromBytes(b[:n])
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("DHCPv4: dhcpv4.FromBytes: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("DHCPv4: received message from server: %s", response.Summary())
|
|
||||||
|
|
||||||
if !(response.OpCode == dhcpv4.OpcodeBootReply &&
|
|
||||||
response.HWType == iana.HWTypeEthernet &&
|
|
||||||
bytes.Equal(response.ClientHWAddr, iface.HardwareAddr) &&
|
|
||||||
bytes.Equal(response.TransactionID[:], req.TransactionID[:]) &&
|
|
||||||
response.Options.Has(dhcpv4.OptionDHCPMessageType)) {
|
|
||||||
log.Debug("DHCPv4: received message from server doesn't match our request")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Tracef("The packet is from an active DHCP server")
|
|
||||||
// that's a DHCP server there
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckIfOtherDHCPServersPresentV6 sends a DHCP request to the specified network interface,
|
|
||||||
// and waits for a response for a period defined by defaultDiscoverTime
|
|
||||||
func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) {
|
|
||||||
iface, err := net.InterfaceByName(ifaceName)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("DHCPv6: net.InterfaceByName: %s: %s", ifaceName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ifaceIPNet := getIfaceIPv6(*iface)
|
|
||||||
if len(ifaceIPNet) == 0 {
|
|
||||||
return false, fmt.Errorf("DHCPv6: couldn't find IPv6 address of interface %s %+v", ifaceName, iface)
|
|
||||||
}
|
|
||||||
|
|
||||||
srcIP := ifaceIPNet[0]
|
|
||||||
src := net.JoinHostPort(srcIP.String(), "546")
|
|
||||||
dst := "[ff02::1:2]:547"
|
|
||||||
|
|
||||||
req, err := dhcpv6.NewSolicit(iface.HardwareAddr)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("DHCPv6: dhcpv6.NewSolicit: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp6", src)
|
|
||||||
if err != nil {
|
|
||||||
return false, wrapErrPrint(err, "DHCPv6: Couldn't resolve UDP address %s", src)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !udpAddr.IP.To16().Equal(srcIP) {
|
|
||||||
return false, wrapErrPrint(err, "DHCPv6: Resolved UDP address is not %s", src)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstAddr, err := net.ResolveUDPAddr("udp6", dst)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("DHCPv6: Couldn't resolve UDP address %s: %s", dst, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("DHCPv6: Listening to udp6 %+v", udpAddr)
|
|
||||||
c, err := nclient6.NewIPv6UDPConn(ifaceName, dhcpv6.DefaultClientPort)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("DHCPv6: Couldn't listen on :546: %s", err)
|
|
||||||
}
|
|
||||||
if c != nil {
|
|
||||||
defer c.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.WriteTo(req.ToBytes(), dstAddr)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("DHCPv6: Couldn't send a packet to %s: %s", dst, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
log.Debug("DHCPv6: Waiting %v for an answer", defaultDiscoverTime)
|
|
||||||
b := make([]byte, 4096)
|
|
||||||
_ = c.SetReadDeadline(time.Now().Add(defaultDiscoverTime))
|
|
||||||
n, _, err := c.ReadFrom(b)
|
|
||||||
if isTimeout(err) {
|
|
||||||
log.Debug("DHCPv6: didn't receive DHCP response")
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return false, wrapErrPrint(err, "Couldn't receive packet")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("DHCPv6: Received packet (%v bytes)", n)
|
|
||||||
|
|
||||||
resp, err := dhcpv6.FromBytes(b[:n])
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("DHCPv6: dhcpv6.FromBytes: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("DHCPv6: received message from server: %s", resp.Summary())
|
|
||||||
|
|
||||||
cid := req.Options.ClientID()
|
|
||||||
msg, err := resp.GetInnerMessage()
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("DHCPv6: resp.GetInnerMessage: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rcid := msg.Options.ClientID()
|
|
||||||
if resp.Type() == dhcpv6.MessageTypeAdvertise &&
|
|
||||||
msg.TransactionID == req.TransactionID &&
|
|
||||||
rcid != nil &&
|
|
||||||
cid.Equal(*rcid) {
|
|
||||||
log.Debug("DHCPv6: The packet is from an active DHCP server")
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("DHCPv6: received message from server doesn't match our request")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,312 +0,0 @@
|
|||||||
package dhcpd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/util"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/file"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a static IP for the specified 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, 0)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package dhcpd
|
|
||||||
|
|
||||||
// 'u-root/u-root' package, a dependency of 'insomniacslk/dhcp' package, doesn't build on Windows
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
type winServer struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *winServer) ResetLeases(leases []*Lease) {
|
|
||||||
}
|
|
||||||
func (s *winServer) GetLeases(flags int) []Lease {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (s *winServer) GetLeasesRef() []*Lease {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (s *winServer) AddStaticLease(lease Lease) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (s *winServer) RemoveStaticLease(l Lease) error {
|
|
||||||
return 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
|
|
||||||
}
|
|
||||||
@@ -1,149 +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
|
|
||||||
var exp []byte
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
47
go.mod
47
go.mod
@@ -3,34 +3,41 @@ module github.com/AdguardTeam/AdGuardHome
|
|||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdguardTeam/dnsproxy v0.32.6
|
github.com/AdguardTeam/dnsproxy v0.33.2
|
||||||
github.com/AdguardTeam/golibs v0.4.2
|
github.com/AdguardTeam/golibs v0.4.4
|
||||||
github.com/AdguardTeam/urlfilter v0.12.3
|
github.com/AdguardTeam/urlfilter v0.13.0
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
|
github.com/ameshkov/dnscrypt/v2 v2.0.0
|
||||||
github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect
|
github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect
|
||||||
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
|
github.com/joomcode/errorx v1.0.3 // indirect
|
||||||
github.com/kardianos/service v1.1.0
|
github.com/kardianos/service v1.2.0
|
||||||
github.com/marten-seemann/qtls-go1-15 v0.1.1 // indirect
|
github.com/karrick/godirwalk v1.16.1 // indirect
|
||||||
|
github.com/lucas-clemente/quic-go v0.19.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.31
|
github.com/miekg/dns v1.1.35
|
||||||
github.com/pkg/errors v0.9.1
|
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.5.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-20200820211705-5c72a883971a
|
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
|
||||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
|
||||||
golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
|
||||||
golang.org/x/text v0.3.3 // indirect
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
|
||||||
google.golang.org/protobuf v1.25.0 // indirect
|
golang.org/x/text v0.3.4 // 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
|
||||||
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
|
||||||
|
howett.net/plist v0.0.0-20201026045517-117a925f2150
|
||||||
)
|
)
|
||||||
|
|||||||
340
go.sum
340
go.sum
@@ -2,58 +2,96 @@ 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.32.6 h1:HCKQFUvyvIP3lrJxP8t8+mE/DJwLk2HDmzI/af5PUu4=
|
github.com/AdguardTeam/dnsproxy v0.33.2 h1:k5aMcsw3TA/G2DR8EjIkwutDPuuRkKh8xij4cFWC6Fk=
|
||||||
github.com/AdguardTeam/dnsproxy v0.32.6/go.mod h1:ZLDrKIypYxBDz2N9FQHgeehuHrwTbuhZXdGwNySshbw=
|
github.com/AdguardTeam/dnsproxy v0.33.2/go.mod h1:kLi6lMpErnZThy5haiRSis4q0KTB8uPWO4JQsU1EDJA=
|
||||||
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.3/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||||
|
github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw=
|
||||||
|
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.13.0 h1:MfO46K81JVTkhgP6gRu/buKl5wAOSfusjiDwjT1JN1c=
|
||||||
github.com/AdguardTeam/urlfilter v0.12.3/go.mod h1:1fcCQx5TGJANrQN6sHNNM9KPBl7qx7BJml45ko6vru0=
|
github.com/AdguardTeam/urlfilter v0.13.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 v1.1.0 h1:2vAt5dD6ZmqlAxEAfzRcLBnkvdf8NI46Kn9InSwQbSI=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/ameshkov/dnscrypt v1.1.0/go.mod h1:ikduAxNLCTEfd1AaCgpIA5TgroIVQ8JY3Vb095fiFJg=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/ameshkov/dnscrypt/v2 v2.0.0 h1:i83G8MeGLrAFgUL8GSu98TVhtFDEifF7SIS7Qi/RZ3U=
|
||||||
|
github.com/ameshkov/dnscrypt/v2 v2.0.0/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/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/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 h1:KXlsf+qt/X5ttPGEjR0tPH1xaWWoKBEg9Q1THAj2h3I=
|
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 h1:KXlsf+qt/X5ttPGEjR0tPH1xaWWoKBEg9Q1THAj2h3I=
|
||||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
||||||
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=
|
||||||
@@ -63,27 +101,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=
|
||||||
@@ -98,7 +154,10 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
|
|||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||||
|
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=
|
||||||
@@ -107,84 +166,150 @@ 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.0 h1:JhQDdqxdwdmGdKsKgXi1+coHRoGhvU6z0rNzOJqZ/4o=
|
github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys=
|
||||||
github.com/lucas-clemente/quic-go v0.18.0/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg=
|
github.com/lucas-clemente/quic-go v0.18.1/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg=
|
||||||
|
github.com/lucas-clemente/quic-go v0.19.1 h1:J9TkQJGJVOR3UmGhd4zdVYwKSA0EoXbLRf15uQJ6gT4=
|
||||||
|
github.com/lucas-clemente/quic-go v0.19.1/go.mod h1:ZUygOqIoai0ASXXLJ92LTnKdbqh9MHCLTX6Nr1jUrK0=
|
||||||
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/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.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
github.com/marten-seemann/qpack v0.2.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||||
|
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 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.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.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
|
github.com/miekg/dns v1.1.34/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
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/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=
|
||||||
@@ -193,25 +318,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/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=
|
||||||
@@ -235,24 +380,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=
|
||||||
@@ -261,63 +417,113 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
|||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
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/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-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
|
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o=
|
||||||
|
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/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/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-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
|
|
||||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
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-20201016165138-7b1cca2348c0/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/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
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=
|
||||||
@@ -327,22 +533,36 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||||
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/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=
|
||||||
@@ -351,8 +571,17 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ix
|
|||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
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-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/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/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/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=
|
||||||
@@ -360,42 +589,77 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
|||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
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/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=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
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=
|
||||||
@@ -409,6 +673,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=
|
||||||
@@ -416,10 +681,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=
|
||||||
@@ -427,13 +695,21 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
|||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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-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/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=
|
||||||
|
|||||||
@@ -1,266 +0,0 @@
|
|||||||
package home
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/dhcpd"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClients(t *testing.T) {
|
|
||||||
var c Client
|
|
||||||
var e error
|
|
||||||
var b bool
|
|
||||||
clients := clientsContainer{}
|
|
||||||
clients.testing = true
|
|
||||||
|
|
||||||
clients.Init(nil, nil, nil)
|
|
||||||
|
|
||||||
// add
|
|
||||||
c = Client{
|
|
||||||
IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
|
|
||||||
Name: "client1",
|
|
||||||
}
|
|
||||||
b, e = clients.Add(c)
|
|
||||||
if !b || e != nil {
|
|
||||||
t.Fatalf("Add #1")
|
|
||||||
}
|
|
||||||
|
|
||||||
// add #2
|
|
||||||
c = Client{
|
|
||||||
IDs: []string{"2.2.2.2"},
|
|
||||||
Name: "client2",
|
|
||||||
}
|
|
||||||
b, e = clients.Add(c)
|
|
||||||
if !b || e != nil {
|
|
||||||
t.Fatalf("Add #2")
|
|
||||||
}
|
|
||||||
|
|
||||||
c, b = clients.Find("1.1.1.1")
|
|
||||||
assert.True(t, b && c.Name == "client1")
|
|
||||||
|
|
||||||
c, b = clients.Find("1:2:3::4")
|
|
||||||
assert.True(t, b && c.Name == "client1")
|
|
||||||
|
|
||||||
c, b = clients.Find("2.2.2.2")
|
|
||||||
assert.True(t, b && c.Name == "client2")
|
|
||||||
|
|
||||||
// failed add - name in use
|
|
||||||
c = Client{
|
|
||||||
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 = Client{
|
|
||||||
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.1.1.1", ClientSourceHostsFile))
|
|
||||||
assert.True(t, clients.Exists("2.2.2.2", ClientSourceHostsFile))
|
|
||||||
|
|
||||||
// failed update - no such name
|
|
||||||
c.IDs = []string{"1.2.3.0"}
|
|
||||||
c.Name = "client3"
|
|
||||||
if clients.Update("client3", c) == nil {
|
|
||||||
t.Fatalf("Update")
|
|
||||||
}
|
|
||||||
|
|
||||||
// failed update - name in use
|
|
||||||
c.IDs = []string{"1.2.3.0"}
|
|
||||||
c.Name = "client2"
|
|
||||||
if clients.Update("client1", c) == nil {
|
|
||||||
t.Fatalf("Update - name in use")
|
|
||||||
}
|
|
||||||
|
|
||||||
// failed update - ip in use
|
|
||||||
c.IDs = []string{"2.2.2.2"}
|
|
||||||
c.Name = "client1"
|
|
||||||
if clients.Update("client1", c) == nil {
|
|
||||||
t.Fatalf("Update - ip in use")
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
c.IDs = []string{"1.1.1.2"}
|
|
||||||
c.Name = "client1"
|
|
||||||
if clients.Update("client1", c) != nil {
|
|
||||||
t.Fatalf("Update")
|
|
||||||
}
|
|
||||||
|
|
||||||
// get after update
|
|
||||||
assert.True(t, !clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
|
||||||
assert.True(t, clients.Exists("1.1.1.2", ClientSourceHostsFile))
|
|
||||||
|
|
||||||
// update - rename
|
|
||||||
c.IDs = []string{"1.1.1.2"}
|
|
||||||
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
|
|
||||||
if clients.Del("client3") {
|
|
||||||
t.Fatalf("Del - no such name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove
|
|
||||||
assert.True(t, !(!clients.Del("client1-renamed") || clients.Exists("1.1.1.2", ClientSourceHostsFile)))
|
|
||||||
|
|
||||||
// add host client
|
|
||||||
b, e = clients.AddHost("1.1.1.1", "host", ClientSourceARP)
|
|
||||||
if !b || e != nil {
|
|
||||||
t.Fatalf("clientAddHost")
|
|
||||||
}
|
|
||||||
|
|
||||||
// failed add - ip exists
|
|
||||||
b, e = clients.AddHost("1.1.1.1", "host1", ClientSourceRDNS)
|
|
||||||
if b || e != nil {
|
|
||||||
t.Fatalf("clientAddHost - ip exists")
|
|
||||||
}
|
|
||||||
|
|
||||||
// overwrite with new data
|
|
||||||
b, e = clients.AddHost("1.1.1.1", "host2", ClientSourceARP)
|
|
||||||
if !b || e != nil {
|
|
||||||
t.Fatalf("clientAddHost - overwrite with new data")
|
|
||||||
}
|
|
||||||
|
|
||||||
// overwrite with new data (higher priority)
|
|
||||||
b, e = clients.AddHost("1.1.1.1", "host3", ClientSourceHostsFile)
|
|
||||||
if !b || e != nil {
|
|
||||||
t.Fatalf("clientAddHost - overwrite with new data (higher priority)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// get
|
|
||||||
assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClientsWhois(t *testing.T) {
|
|
||||||
var c Client
|
|
||||||
clients := clientsContainer{}
|
|
||||||
clients.testing = true
|
|
||||||
clients.Init(nil, nil, nil)
|
|
||||||
|
|
||||||
whois := [][]string{{"orgname", "orgname-val"}, {"country", "country-val"}}
|
|
||||||
// set whois info on new client
|
|
||||||
clients.SetWhoisInfo("1.1.1.255", whois)
|
|
||||||
assert.True(t, clients.ipHost["1.1.1.255"].WhoisInfo[0][1] == "orgname-val")
|
|
||||||
|
|
||||||
// set whois info on existing auto-client
|
|
||||||
_, _ = clients.AddHost("1.1.1.1", "host", ClientSourceRDNS)
|
|
||||||
clients.SetWhoisInfo("1.1.1.1", whois)
|
|
||||||
assert.True(t, clients.ipHost["1.1.1.1"].WhoisInfo[0][1] == "orgname-val")
|
|
||||||
|
|
||||||
// Check that we cannot set whois info on a manually-added client
|
|
||||||
c = Client{
|
|
||||||
IDs: []string{"1.1.1.2"},
|
|
||||||
Name: "client1",
|
|
||||||
}
|
|
||||||
_, _ = clients.Add(c)
|
|
||||||
clients.SetWhoisInfo("1.1.1.2", whois)
|
|
||||||
assert.True(t, clients.ipHost["1.1.1.2"] == nil)
|
|
||||||
_ = clients.Del("client1")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClientsAddExisting(t *testing.T) {
|
|
||||||
var c Client
|
|
||||||
clients := clientsContainer{}
|
|
||||||
clients.testing = true
|
|
||||||
clients.Init(nil, nil, nil)
|
|
||||||
|
|
||||||
// some test variables
|
|
||||||
mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
|
||||||
testIP := "1.2.3.4"
|
|
||||||
|
|
||||||
// add a client
|
|
||||||
c = Client{
|
|
||||||
IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa", "2.2.2.0/24"},
|
|
||||||
Name: "client1",
|
|
||||||
}
|
|
||||||
ok, err := clients.Add(c)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
// add an auto-client with the same IP - it's allowed
|
|
||||||
ok, err = clients.AddHost("1.1.1.1", "test", ClientSourceRDNS)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
// now some more complicated stuff
|
|
||||||
// first, init a DHCP server with a single static lease
|
|
||||||
config := dhcpd.ServerConfig{
|
|
||||||
DBFilePath: "leases.db",
|
|
||||||
}
|
|
||||||
defer func() { _ = os.Remove("leases.db") }()
|
|
||||||
clients.dhcpServer = dhcpd.Create(config)
|
|
||||||
err = clients.dhcpServer.AddStaticLease(dhcpd.Lease{
|
|
||||||
HWAddr: mac,
|
|
||||||
IP: net.ParseIP(testIP).To4(),
|
|
||||||
Hostname: "testhost",
|
|
||||||
Expiry: time.Now().Add(time.Hour),
|
|
||||||
})
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
// add a new client with the same IP as for a client with MAC
|
|
||||||
c = Client{
|
|
||||||
IDs: []string{testIP},
|
|
||||||
Name: "client2",
|
|
||||||
}
|
|
||||||
ok, err = clients.Add(c)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
// add a new client with the IP from the client1's IP range
|
|
||||||
c = Client{
|
|
||||||
IDs: []string{"2.2.2.2"},
|
|
||||||
Name: "client3",
|
|
||||||
}
|
|
||||||
ok, err = clients.Add(c)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClientsCustomUpstream(t *testing.T) {
|
|
||||||
clients := clientsContainer{}
|
|
||||||
clients.testing = true
|
|
||||||
|
|
||||||
clients.Init(nil, nil, nil)
|
|
||||||
|
|
||||||
// add client with upstreams
|
|
||||||
client := Client{
|
|
||||||
IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
|
|
||||||
Name: "client1",
|
|
||||||
Upstreams: []string{
|
|
||||||
"1.1.1.1",
|
|
||||||
"[/example.org/]8.8.8.8",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ok, err := clients.Add(client)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.True(t, ok)
|
|
||||||
|
|
||||||
config := clients.FindUpstreams("1.2.3.4")
|
|
||||||
assert.Nil(t, config)
|
|
||||||
|
|
||||||
config = clients.FindUpstreams("1.1.1.1")
|
|
||||||
assert.NotNil(t, config)
|
|
||||||
assert.Equal(t, 1, len(config.Upstreams))
|
|
||||||
assert.Equal(t, 1, len(config.DomainReservedUpstreams))
|
|
||||||
}
|
|
||||||
67
internal/agherr/agherr.go
Normal file
67
internal/agherr/agherr.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Package agherr contains the extended error type, and the function for
|
||||||
|
// wrapping several errors.
|
||||||
|
package agherr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error is the constant error type.
|
||||||
|
type Error string
|
||||||
|
|
||||||
|
// Error implements the error interface for Error.
|
||||||
|
func (err Error) Error() (msg string) {
|
||||||
|
return string(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// manyError is an error containing several wrapped errors. It is created to be
|
||||||
|
// a simpler version of the API provided by github.com/joomcode/errorx.
|
||||||
|
type manyError struct {
|
||||||
|
message string
|
||||||
|
underlying []error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Many wraps several errors and returns a single error.
|
||||||
|
func Many(message string, underlying ...error) error {
|
||||||
|
err := &manyError{
|
||||||
|
message: message,
|
||||||
|
underlying: underlying,
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface for *manyError.
|
||||||
|
func (e *manyError) Error() string {
|
||||||
|
switch len(e.underlying) {
|
||||||
|
case 0:
|
||||||
|
return e.message
|
||||||
|
case 1:
|
||||||
|
return fmt.Sprintf("%s: %s", e.message, e.underlying[0])
|
||||||
|
default:
|
||||||
|
b := &strings.Builder{}
|
||||||
|
|
||||||
|
// Ignore errors, since strings.(*Buffer).Write never returns
|
||||||
|
// errors.
|
||||||
|
_, _ = fmt.Fprintf(b, "%s: %s (hidden: %s", e.message, e.underlying[0], e.underlying[1])
|
||||||
|
for _, u := range e.underlying[2:] {
|
||||||
|
// See comment above.
|
||||||
|
_, _ = fmt.Fprintf(b, ", %s", u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See comment above.
|
||||||
|
_, _ = b.WriteString(")")
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap implements the hidden errors.wrapper interface for *manyError.
|
||||||
|
func (e *manyError) Unwrap() error {
|
||||||
|
if len(e.underlying) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.underlying[0]
|
||||||
|
}
|
||||||
78
internal/agherr/agherr_test.go
Normal file
78
internal/agherr/agherr_test.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package agherr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
testutil.DiscardLogOutput(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestError_Error(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
want string
|
||||||
|
err error
|
||||||
|
}{{
|
||||||
|
name: "simple",
|
||||||
|
want: "a",
|
||||||
|
err: Many("a"),
|
||||||
|
}, {
|
||||||
|
name: "wrapping",
|
||||||
|
want: "a: b",
|
||||||
|
err: Many("a", errors.New("b")),
|
||||||
|
}, {
|
||||||
|
name: "wrapping several",
|
||||||
|
want: "a: b (hidden: c, d)",
|
||||||
|
err: Many("a", errors.New("b"), errors.New("c"), errors.New("d")),
|
||||||
|
}, {
|
||||||
|
name: "wrapping wrapper",
|
||||||
|
want: "a: b: c (hidden: d)",
|
||||||
|
err: Many("a", Many("b", errors.New("c"), errors.New("d"))),
|
||||||
|
}}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
assert.Equal(t, tc.want, tc.err.Error(), tc.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestError_Unwrap(t *testing.T) {
|
||||||
|
const (
|
||||||
|
errSimple = iota
|
||||||
|
errWrapped
|
||||||
|
errNil
|
||||||
|
)
|
||||||
|
errs := []error{
|
||||||
|
errSimple: errors.New("a"),
|
||||||
|
errWrapped: fmt.Errorf("err: %w", errors.New("nested")),
|
||||||
|
errNil: nil,
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
want error
|
||||||
|
wrapped error
|
||||||
|
}{{
|
||||||
|
name: "simple",
|
||||||
|
want: errs[errSimple],
|
||||||
|
wrapped: Many("a", errs[errSimple]),
|
||||||
|
}, {
|
||||||
|
name: "nested",
|
||||||
|
want: errs[errWrapped],
|
||||||
|
wrapped: Many("b", errs[errWrapped]),
|
||||||
|
}, {
|
||||||
|
name: "nil passed",
|
||||||
|
want: errs[errNil],
|
||||||
|
wrapped: Many("c", errs[errNil]),
|
||||||
|
}, {
|
||||||
|
name: "nil not passed",
|
||||||
|
want: nil,
|
||||||
|
wrapped: Many("d"),
|
||||||
|
}}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
assert.Equal(t, tc.want, errors.Unwrap(tc.wrapped), tc.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
59
internal/aghio/limitedreadcloser.go
Normal file
59
internal/aghio/limitedreadcloser.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
108
internal/aghio/limitedreadcloser_test.go
Normal file
108
internal/aghio/limitedreadcloser_test.go
Normal file
@@ -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())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
282
internal/dhcpd/checkother.go
Normal file
282
internal/dhcpd/checkother.go
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd/nclient4"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv6/nclient6"
|
||||||
|
"github.com/insomniacslk/dhcp/iana"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckIfOtherDHCPServersPresentV4 sends a DHCP request to the specified network interface,
|
||||||
|
// and waits for a response for a period defined by defaultDiscoverTime
|
||||||
|
func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) {
|
||||||
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("couldn't find interface by name %s: %w", ifaceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ifaceIPNet, err := ifaceIPAddrs(iface, ipVersion4)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("getting ipv4 addrs for iface %s: %w", ifaceName, err)
|
||||||
|
}
|
||||||
|
if len(ifaceIPNet) == 0 {
|
||||||
|
return false, fmt.Errorf("interface %s has no ipv4 addresses", ifaceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(a.garipov): Find out what this is about. Perhaps this
|
||||||
|
// information is outdated or at least incomplete.
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
return false, fmt.Errorf("can't find DHCP server: not supported on macOS")
|
||||||
|
}
|
||||||
|
|
||||||
|
srcIP := ifaceIPNet[0]
|
||||||
|
src := net.JoinHostPort(srcIP.String(), "68")
|
||||||
|
dst := "255.255.255.255:67"
|
||||||
|
|
||||||
|
hostname, _ := os.Hostname()
|
||||||
|
|
||||||
|
req, err := dhcpv4.NewDiscovery(iface.HardwareAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("dhcpv4.NewDiscovery: %w", err)
|
||||||
|
}
|
||||||
|
req.Options.Update(dhcpv4.OptClientIdentifier(iface.HardwareAddr))
|
||||||
|
req.Options.Update(dhcpv4.OptHostName(hostname))
|
||||||
|
|
||||||
|
// resolve 0.0.0.0:68
|
||||||
|
udpAddr, err := net.ResolveUDPAddr("udp4", src)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("couldn't resolve UDP address %s: %w", src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !udpAddr.IP.To4().Equal(srcIP) {
|
||||||
|
return false, fmt.Errorf("resolved UDP address is not %s: %w", src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve 255.255.255.255:67
|
||||||
|
dstAddr, err := net.ResolveUDPAddr("udp4", dst)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("couldn't resolve UDP address %s: %w", dst, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind to 0.0.0.0:68
|
||||||
|
log.Tracef("Listening to udp4 %+v", udpAddr)
|
||||||
|
c, err := nclient4.NewRawUDPConn(ifaceName, 68)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("couldn't listen on :68: %w", err)
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
defer c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// send to 255.255.255.255:67
|
||||||
|
_, err = c.WriteTo(req.ToBytes(), dstAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("couldn't send a packet to %s: %w", dst, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
ok, next, err := tryConn4(req, c, iface)
|
||||||
|
if next {
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("dhcpv4: trying a connection: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(a.garipov): Refactor further. Inspect error handling, remove parameter
|
||||||
|
// next, address the TODO, merge with tryConn6, etc.
|
||||||
|
func tryConn4(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, next bool, err error) {
|
||||||
|
// TODO: replicate dhclient's behavior of retrying several times with
|
||||||
|
// progressively longer timeouts.
|
||||||
|
log.Tracef("dhcpv4: waiting %v for an answer", defaultDiscoverTime)
|
||||||
|
|
||||||
|
b := make([]byte, 1500)
|
||||||
|
err = c.SetDeadline(time.Now().Add(defaultDiscoverTime))
|
||||||
|
if err != nil {
|
||||||
|
return false, false, fmt.Errorf("setting deadline: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, _, err := c.ReadFrom(b)
|
||||||
|
if err != nil {
|
||||||
|
if isTimeout(err) {
|
||||||
|
log.Debug("dhcpv4: didn't receive dhcp response")
|
||||||
|
|
||||||
|
return false, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, false, fmt.Errorf("receiving packet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracef("dhcpv4: received packet, %d bytes", n)
|
||||||
|
|
||||||
|
response, err := dhcpv4.FromBytes(b[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("dhcpv4: encoding: %s", err)
|
||||||
|
|
||||||
|
return false, true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("dhcpv4: received message from server: %s", response.Summary())
|
||||||
|
|
||||||
|
if !(response.OpCode == dhcpv4.OpcodeBootReply &&
|
||||||
|
response.HWType == iana.HWTypeEthernet &&
|
||||||
|
bytes.Equal(response.ClientHWAddr, iface.HardwareAddr) &&
|
||||||
|
bytes.Equal(response.TransactionID[:], req.TransactionID[:]) &&
|
||||||
|
response.Options.Has(dhcpv4.OptionDHCPMessageType)) {
|
||||||
|
|
||||||
|
log.Debug("dhcpv4: received message from server doesn't match our request")
|
||||||
|
|
||||||
|
return false, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracef("dhcpv4: the packet is from an active dhcp server")
|
||||||
|
|
||||||
|
return true, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckIfOtherDHCPServersPresentV6 sends a DHCP request to the specified network interface,
|
||||||
|
// and waits for a response for a period defined by defaultDiscoverTime
|
||||||
|
func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) {
|
||||||
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("dhcpv6: net.InterfaceByName: %s: %w", ifaceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ifaceIPNet, err := ifaceIPAddrs(iface, ipVersion6)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("getting ipv6 addrs for iface %s: %w", ifaceName, err)
|
||||||
|
}
|
||||||
|
if len(ifaceIPNet) == 0 {
|
||||||
|
return false, fmt.Errorf("interface %s has no ipv6 addresses", ifaceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcIP := ifaceIPNet[0]
|
||||||
|
src := net.JoinHostPort(srcIP.String(), "546")
|
||||||
|
dst := "[ff02::1:2]:547"
|
||||||
|
|
||||||
|
req, err := dhcpv6.NewSolicit(iface.HardwareAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("dhcpv6: dhcpv6.NewSolicit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
udpAddr, err := net.ResolveUDPAddr("udp6", src)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("dhcpv6: Couldn't resolve UDP address %s: %w", src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !udpAddr.IP.To16().Equal(srcIP) {
|
||||||
|
return false, fmt.Errorf("dhcpv6: Resolved UDP address is not %s: %w", src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dstAddr, err := net.ResolveUDPAddr("udp6", dst)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("dhcpv6: Couldn't resolve UDP address %s: %w", dst, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("DHCPv6: Listening to udp6 %+v", udpAddr)
|
||||||
|
c, err := nclient6.NewIPv6UDPConn(ifaceName, dhcpv6.DefaultClientPort)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("dhcpv6: Couldn't listen on :546: %w", err)
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
defer c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.WriteTo(req.ToBytes(), dstAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("dhcpv6: Couldn't send a packet to %s: %w", dst, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
ok, next, err := tryConn6(req, c)
|
||||||
|
if next {
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("dhcpv6: trying a connection: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(a.garipov): See the comment on tryConn4. Sigh…
|
||||||
|
func tryConn6(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error) {
|
||||||
|
// TODO: replicate dhclient's behavior of retrying several times with
|
||||||
|
// progressively longer timeouts.
|
||||||
|
log.Tracef("dhcpv6: waiting %v for an answer", defaultDiscoverTime)
|
||||||
|
|
||||||
|
b := make([]byte, 4096)
|
||||||
|
err = c.SetDeadline(time.Now().Add(defaultDiscoverTime))
|
||||||
|
if err != nil {
|
||||||
|
return false, false, fmt.Errorf("setting deadline: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, _, err := c.ReadFrom(b)
|
||||||
|
if err != nil {
|
||||||
|
if isTimeout(err) {
|
||||||
|
log.Debug("dhcpv6: didn't receive dhcp response")
|
||||||
|
|
||||||
|
return false, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, false, fmt.Errorf("receiving packet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracef("dhcpv6: received packet, %d bytes", n)
|
||||||
|
|
||||||
|
response, err := dhcpv6.FromBytes(b[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("dhcpv6: encoding: %s", err)
|
||||||
|
|
||||||
|
return false, true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("dhcpv6: received message from server: %s", response.Summary())
|
||||||
|
|
||||||
|
cid := req.Options.ClientID()
|
||||||
|
msg, err := response.GetInnerMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("dhcpv6: resp.GetInnerMessage(): %s", err)
|
||||||
|
|
||||||
|
return false, true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rcid := msg.Options.ClientID()
|
||||||
|
if !(response.Type() == dhcpv6.MessageTypeAdvertise &&
|
||||||
|
msg.TransactionID == req.TransactionID &&
|
||||||
|
rcid != nil &&
|
||||||
|
cid.Equal(*rcid)) {
|
||||||
|
|
||||||
|
log.Debug("dhcpv6: received message from server doesn't match our request")
|
||||||
|
|
||||||
|
return false, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracef("dhcpv6: the packet is from an active dhcp server")
|
||||||
|
|
||||||
|
return true, false, 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)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Package dhcpd provides a DHCP server.
|
||||||
package dhcpd
|
package dhcpd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -5,16 +6,19 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultDiscoverTime = time.Second * 3
|
const (
|
||||||
const leaseExpireStatic = 1
|
defaultDiscoverTime = time.Second * 3
|
||||||
|
leaseExpireStatic = 1
|
||||||
|
)
|
||||||
|
|
||||||
var webHandlersRegistered = false
|
var webHandlersRegistered = false
|
||||||
|
|
||||||
@@ -48,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()
|
||||||
@@ -70,19 +75,16 @@ 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{}
|
||||||
|
|
||||||
s.conf.Enabled = config.Enabled
|
s.conf.Enabled = config.Enabled
|
||||||
s.conf.InterfaceName = config.InterfaceName
|
s.conf.InterfaceName = config.InterfaceName
|
||||||
s.conf.HTTPRegister = config.HTTPRegister
|
s.conf.HTTPRegister = config.HTTPRegister
|
||||||
@@ -90,8 +92,21 @@ func Create(config ServerConfig) *Server {
|
|||||||
s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename)
|
s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename)
|
||||||
|
|
||||||
if !webHandlersRegistered && s.conf.HTTPRegister != nil {
|
if !webHandlersRegistered && s.conf.HTTPRegister != nil {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
// Our DHCP server doesn't work on Windows yet, so
|
||||||
|
// signal that to the front with an HTTP 501.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): This needs refactoring. We
|
||||||
|
// shouldn't even try and initialize a DHCP server on
|
||||||
|
// Windows, but there are currently too many
|
||||||
|
// interconnected parts--such as HTTP handlers and
|
||||||
|
// frontend--to make that work properly.
|
||||||
|
s.registerNotImplementedHandlers()
|
||||||
|
} else {
|
||||||
|
s.registerHandlers()
|
||||||
|
}
|
||||||
|
|
||||||
webHandlersRegistered = true
|
webHandlersRegistered = true
|
||||||
s.registerHandlers()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var err4, err6 error
|
var err4, err6 error
|
||||||
@@ -130,7 +145,7 @@ func Create(config ServerConfig) *Server {
|
|||||||
// we can't delay database loading until DHCP server is started,
|
// we can't delay database loading until DHCP server is started,
|
||||||
// because we need static leases functionality available beforehand
|
// because we need static leases functionality available beforehand
|
||||||
s.dbLoad()
|
s.dbLoad()
|
||||||
return &s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// server calls this function after DB is updated
|
// server calls this function after DB is updated
|
||||||
@@ -9,13 +9,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func check(t *testing.T, result bool, msg string) {
|
func TestMain(m *testing.M) {
|
||||||
if !result {
|
testutil.DiscardLogOutput(m)
|
||||||
t.Fatal(msg)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNotify(flags uint32) {
|
func testNotify(flags uint32) {
|
||||||
@@ -83,7 +82,6 @@ func TestIsValidSubnetMask(t *testing.T) {
|
|||||||
func TestNormalizeLeases(t *testing.T) {
|
func TestNormalizeLeases(t *testing.T) {
|
||||||
dynLeases := []*Lease{}
|
dynLeases := []*Lease{}
|
||||||
staticLeases := []*Lease{}
|
staticLeases := []*Lease{}
|
||||||
leases := []*Lease{}
|
|
||||||
|
|
||||||
lease := &Lease{}
|
lease := &Lease{}
|
||||||
lease.HWAddr = []byte{1, 2, 3, 4}
|
lease.HWAddr = []byte{1, 2, 3, 4}
|
||||||
@@ -100,7 +98,7 @@ func TestNormalizeLeases(t *testing.T) {
|
|||||||
lease.HWAddr = []byte{2, 2, 3, 4}
|
lease.HWAddr = []byte{2, 2, 3, 4}
|
||||||
staticLeases = append(staticLeases, lease)
|
staticLeases = append(staticLeases, lease)
|
||||||
|
|
||||||
leases = normalizeLeases(staticLeases, dynLeases)
|
leases := normalizeLeases(staticLeases, dynLeases)
|
||||||
|
|
||||||
assert.True(t, len(leases) == 3)
|
assert.True(t, len(leases) == 3)
|
||||||
assert.True(t, bytes.Equal(leases[0].HWAddr, []byte{1, 2, 3, 4}))
|
assert.True(t, bytes.Equal(leases[0].HWAddr, []byte{1, 2, 3, 4}))
|
||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
@@ -175,7 +175,7 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
v6conf.InterfaceName = newconfig.InterfaceName
|
v6conf.InterfaceName = newconfig.InterfaceName
|
||||||
v6conf.notify = s.onNotify
|
v6conf.notify = s.onNotify
|
||||||
s6, err = v6Create(v6conf)
|
s6, err = v6Create(v6conf)
|
||||||
if s6 == nil {
|
if err != nil {
|
||||||
httpError(r, w, http.StatusBadRequest, "Invalid DHCPv6 configuration: %s", err)
|
httpError(r, w, http.StatusBadRequest, "Invalid DHCPv6 configuration: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -206,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
|
||||||
@@ -283,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,26 +300,27 @@ 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 {
|
||||||
errorText := fmt.Sprintf("failed to read request body: %s", err)
|
msg := fmt.Sprintf("failed to read request body: %s", err)
|
||||||
log.Error(errorText)
|
log.Error(msg)
|
||||||
http.Error(w, errorText, http.StatusBadRequest)
|
http.Error(w, msg, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaceName := strings.TrimSpace(string(body))
|
interfaceName := strings.TrimSpace(string(body))
|
||||||
if interfaceName == "" {
|
if interfaceName == "" {
|
||||||
errorText := fmt.Sprintf("empty interface name specified")
|
msg := "empty interface name specified"
|
||||||
log.Error(errorText)
|
log.Error(msg)
|
||||||
http.Error(w, errorText, http.StatusBadRequest)
|
http.Error(w, msg, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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"
|
||||||
@@ -335,7 +336,7 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
|
|||||||
foundVal := "no"
|
foundVal := "no"
|
||||||
if found4 {
|
if found4 {
|
||||||
foundVal = "yes"
|
foundVal = "yes"
|
||||||
} else if err != nil {
|
} else if err4 != nil {
|
||||||
foundVal = "error"
|
foundVal = "error"
|
||||||
othSrv["error"] = err4.Error()
|
othSrv["error"] = err4.Error()
|
||||||
}
|
}
|
||||||
@@ -370,7 +371,6 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
lj := staticLeaseJSON{}
|
lj := staticLeaseJSON{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&lj)
|
err := json.NewDecoder(r.Body).Decode(&lj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -424,7 +424,6 @@ func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
lj := staticLeaseJSON{}
|
lj := staticLeaseJSON{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&lj)
|
err := json.NewDecoder(r.Body).Decode(&lj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -501,11 +500,51 @@ func (s *Server) handleReset(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) registerHandlers() {
|
func (s *Server) registerHandlers() {
|
||||||
s.conf.HTTPRegister("GET", "/control/dhcp/status", s.handleDHCPStatus)
|
s.conf.HTTPRegister(http.MethodGet, "/control/dhcp/status", s.handleDHCPStatus)
|
||||||
s.conf.HTTPRegister("GET", "/control/dhcp/interfaces", s.handleDHCPInterfaces)
|
s.conf.HTTPRegister(http.MethodGet, "/control/dhcp/interfaces", s.handleDHCPInterfaces)
|
||||||
s.conf.HTTPRegister("POST", "/control/dhcp/set_config", s.handleDHCPSetConfig)
|
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/set_config", s.handleDHCPSetConfig)
|
||||||
s.conf.HTTPRegister("POST", "/control/dhcp/find_active_dhcp", s.handleDHCPFindActiveServer)
|
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/find_active_dhcp", s.handleDHCPFindActiveServer)
|
||||||
s.conf.HTTPRegister("POST", "/control/dhcp/add_static_lease", s.handleDHCPAddStaticLease)
|
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/add_static_lease", s.handleDHCPAddStaticLease)
|
||||||
s.conf.HTTPRegister("POST", "/control/dhcp/remove_static_lease", s.handleDHCPRemoveStaticLease)
|
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/remove_static_lease", s.handleDHCPRemoveStaticLease)
|
||||||
s.conf.HTTPRegister("POST", "/control/dhcp/reset", s.handleReset)
|
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/reset", s.handleReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// Message is the error message, an opaque string.
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// notImplemented returns a handler that replies to any request with an HTTP 501
|
||||||
|
// Not Implemented status and a JSON error with the provided message msg.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Either take the logger from the server after we've
|
||||||
|
// refactored logging or make this not a method of *Server.
|
||||||
|
func (s *Server) notImplemented(msg string) (f func(http.ResponseWriter, *http.Request)) {
|
||||||
|
return func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusNotImplemented)
|
||||||
|
|
||||||
|
err := json.NewEncoder(w).Encode(&jsonError{
|
||||||
|
Message: msg,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("writing 501 json response: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) registerNotImplementedHandlers() {
|
||||||
|
h := s.notImplemented("dhcp is not supported on windows")
|
||||||
|
|
||||||
|
s.conf.HTTPRegister(http.MethodGet, "/control/dhcp/status", h)
|
||||||
|
s.conf.HTTPRegister(http.MethodGet, "/control/dhcp/interfaces", h)
|
||||||
|
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/set_config", h)
|
||||||
|
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/find_active_dhcp", h)
|
||||||
|
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/add_static_lease", h)
|
||||||
|
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/remove_static_lease", h)
|
||||||
|
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/reset", h)
|
||||||
}
|
}
|
||||||
22
internal/dhcpd/dhcphttp_test.go
Normal file
22
internal/dhcpd/dhcphttp_test.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package dhcpd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServer_notImplemented(t *testing.T) {
|
||||||
|
s := &Server{}
|
||||||
|
h := s.notImplemented("never!")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "/unsupported", nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
h(w, r)
|
||||||
|
assert.Equal(t, http.StatusNotImplemented, w.Code)
|
||||||
|
assert.Equal(t, `{"message":"never!"}`+"\n", w.Body.String())
|
||||||
|
}
|
||||||
@@ -4,9 +4,6 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
|
||||||
"github.com/joomcode/errorx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func isTimeout(err error) bool {
|
func isTimeout(err error) bool {
|
||||||
@@ -17,37 +14,6 @@ func isTimeout(err error) bool {
|
|||||||
return operr.Timeout()
|
return operr.Timeout()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get IPv4 address list
|
|
||||||
func getIfaceIPv4(iface net.Interface) []net.IP {
|
|
||||||
addrs, err := iface.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var res []net.IP
|
|
||||||
for _, a := range addrs {
|
|
||||||
ipnet, ok := a.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ipnet.IP.To4() != nil {
|
|
||||||
res = append(res, ipnet.IP.To4())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapErrPrint(err error, message string, args ...interface{}) error {
|
|
||||||
var errx error
|
|
||||||
if err == nil {
|
|
||||||
errx = fmt.Errorf(message, args...)
|
|
||||||
} else {
|
|
||||||
errx = errorx.Decorate(err, message, args...)
|
|
||||||
}
|
|
||||||
log.Println(errx.Error())
|
|
||||||
return errx
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseIPv4(text string) (net.IP, error) {
|
func parseIPv4(text string) (net.IP, error) {
|
||||||
result := net.ParseIP(text)
|
result := net.ParseIP(text)
|
||||||
if result == nil {
|
if result == nil {
|
||||||
@@ -19,9 +19,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -48,14 +46,12 @@ const (
|
|||||||
ServerPort = 67
|
ServerPort = 67
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// DefaultServers is the address of all link-local DHCP servers and
|
||||||
// DefaultServers is the address of all link-local DHCP servers and
|
// relay agents.
|
||||||
// relay agents.
|
var DefaultServers = &net.UDPAddr{
|
||||||
DefaultServers = &net.UDPAddr{
|
IP: net.IPv4bcast,
|
||||||
IP: net.IPv4bcast,
|
Port: ServerPort,
|
||||||
Port: ServerPort,
|
}
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrNoResponse is returned when no response packet is received.
|
// ErrNoResponse is returned when no response packet is received.
|
||||||
@@ -319,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) {
|
||||||
@@ -375,14 +351,6 @@ func WithHWAddr(hwAddr net.HardwareAddr) ClientOpt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint
|
|
||||||
func withBufferCap(n int) ClientOpt {
|
|
||||||
return func(c *Client) (err error) {
|
|
||||||
c.bufferCap = n
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithRetry configures the number of retransmissions to attempt.
|
// WithRetry configures the number of retransmissions to attempt.
|
||||||
//
|
//
|
||||||
// Default is 3.
|
// Default is 3.
|
||||||
@@ -17,11 +17,16 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/testutil"
|
||||||
"github.com/hugelgupf/socketpair"
|
"github.com/hugelgupf/socketpair"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
testutil.DiscardLogOutput(m)
|
||||||
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
received []*dhcpv4.DHCPv4
|
received []*dhcpv4.DHCPv4
|
||||||
@@ -74,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
|
||||||
}
|
}
|
||||||
@@ -87,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))
|
||||||
}
|
}
|
||||||
@@ -122,6 +127,13 @@ func newPacket(op dhcpv4.OpcodeType, xid dhcpv4.TransactionID) *dhcpv4.DHCPv4 {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withBufferCap(n int) ClientOpt {
|
||||||
|
return func(c *Client) (err error) {
|
||||||
|
c.bufferCap = n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSendAndRead(t *testing.T) {
|
func TestSendAndRead(t *testing.T) {
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
desc string
|
desc string
|
||||||
@@ -297,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
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ func (ra *raCtx) Init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("DHCPv6 RA: source IP address: %s DNS IP address: %s",
|
log.Debug("dhcpv6 ra: source IP address: %s DNS IP address: %s",
|
||||||
ra.ipAddr, ra.dnsIPAddr)
|
ra.ipAddr, ra.dnsIPAddr)
|
||||||
|
|
||||||
params := icmpv6RA{
|
params := icmpv6RA{
|
||||||
@@ -180,26 +180,29 @@ 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: %s", 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
con6 := ra.conn.IPv6PacketConn()
|
con6 := ra.conn.IPv6PacketConn()
|
||||||
|
|
||||||
if err := con6.SetHopLimit(255); err != nil {
|
if err := con6.SetHopLimit(255); err != nil {
|
||||||
return fmt.Errorf("DHCPv6 RA: SetHopLimit: %s", err)
|
return fmt.Errorf("dhcpv6 ra: SetHopLimit: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := con6.SetMulticastHopLimit(255); err != nil {
|
if err := con6.SetMulticastHopLimit(255); err != nil {
|
||||||
return fmt.Errorf("DHCPv6 RA: SetMulticastHopLimit: %s", err)
|
return fmt.Errorf("dhcpv6 ra: SetMulticastHopLimit: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := &ipv6.ControlMessage{
|
msg := &ipv6.ControlMessage{
|
||||||
@@ -212,28 +215,30 @@ func (ra *raCtx) Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Debug("DHCPv6 RA: starting to send periodic RouterAdvertisement packets")
|
log.Debug("dhcpv6 ra: starting to send periodic RouterAdvertisement packets")
|
||||||
for ra.stop.Load() == 0 {
|
for ra.stop.Load() == 0 {
|
||||||
_, err = con6.WriteTo(data, msg, addr)
|
_, err = con6.WriteTo(data, msg, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("DHCPv6 RA: WriteTo: %s", err)
|
log.Error("dhcpv6 ra: WriteTo: %s", err)
|
||||||
}
|
}
|
||||||
time.Sleep(ra.packetSendPeriod)
|
time.Sleep(ra.packetSendPeriod)
|
||||||
}
|
}
|
||||||
log.Debug("DHCPv6 RA: loop exit")
|
log.Debug("dhcpv6 ra: loop exit")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
success = true
|
success = true
|
||||||
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
|
||||||
@@ -36,7 +38,7 @@ func (s *v4Server) WriteDiskConfig6(c *V6ServerConf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return TRUE if IP address is within range [start..stop]
|
// Return TRUE if IP address is within range [start..stop]
|
||||||
func ip4InRange(start net.IP, stop net.IP, ip net.IP) bool {
|
func ip4InRange(start, stop, ip net.IP) bool {
|
||||||
if len(start) != 4 || len(stop) != 4 {
|
if len(start) != 4 || len(stop) != 4 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -55,7 +57,7 @@ func (s *v4Server) ResetLeases(leases []*Lease) {
|
|||||||
if l.Expiry.Unix() != leaseExpireStatic &&
|
if l.Expiry.Unix() != leaseExpireStatic &&
|
||||||
!ip4InRange(s.conf.ipStart, s.conf.ipEnd, l.IP) {
|
!ip4InRange(s.conf.ipStart, s.conf.ipEnd, l.IP) {
|
||||||
|
|
||||||
log.Debug("DHCPv4: skipping a lease with IP %v: not within current IP range", l.IP)
|
log.Debug("dhcpv4: skipping a lease with IP %v: not within current IP range", l.IP)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +126,7 @@ func (s *v4Server) blacklistLease(lease *Lease) {
|
|||||||
// Remove (swap) lease by index
|
// Remove (swap) lease by index
|
||||||
func (s *v4Server) leaseRemoveSwapByIndex(i int) {
|
func (s *v4Server) leaseRemoveSwapByIndex(i int) {
|
||||||
s.ipAddrs[s.leases[i].IP[3]] = 0
|
s.ipAddrs[s.leases[i].IP[3]] = 0
|
||||||
log.Debug("DHCPv4: removed lease %s", s.leases[i].HWAddr)
|
log.Debug("dhcpv4: removed lease %s", s.leases[i].HWAddr)
|
||||||
|
|
||||||
n := len(s.leases)
|
n := len(s.leases)
|
||||||
if i != n-1 {
|
if i != n-1 {
|
||||||
@@ -168,7 +170,7 @@ func (s *v4Server) rmDynamicLease(lease Lease) error {
|
|||||||
func (s *v4Server) addLease(l *Lease) {
|
func (s *v4Server) addLease(l *Lease) {
|
||||||
s.leases = append(s.leases, l)
|
s.leases = append(s.leases, l)
|
||||||
s.ipAddrs[l.IP[3]] = 1
|
s.ipAddrs[l.IP[3]] = 1
|
||||||
log.Debug("DHCPv4: added lease %s <-> %s", l.IP, l.HWAddr)
|
log.Debug("dhcpv4: added lease %s <-> %s", l.IP, l.HWAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a lease with the same properties
|
// Remove a lease with the same properties
|
||||||
@@ -178,8 +180,7 @@ func (s *v4Server) rmLease(lease Lease) error {
|
|||||||
|
|
||||||
if !bytes.Equal(l.HWAddr, lease.HWAddr) ||
|
if !bytes.Equal(l.HWAddr, lease.HWAddr) ||
|
||||||
l.Hostname != lease.Hostname {
|
l.Hostname != lease.Hostname {
|
||||||
|
return fmt.Errorf("lease not found")
|
||||||
return fmt.Errorf("Lease not found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.leaseRemoveSwapByIndex(i)
|
s.leaseRemoveSwapByIndex(i)
|
||||||
@@ -238,7 +239,6 @@ func (s *v4Server) RemoveStaticLease(l Lease) error {
|
|||||||
// Send ICMP to the specified machine
|
// Send ICMP to the specified machine
|
||||||
// Return TRUE if it doesn't reply, which probably means that the IP is available
|
// Return TRUE if it doesn't reply, which probably means that the IP is available
|
||||||
func (s *v4Server) addrAvailable(target net.IP) bool {
|
func (s *v4Server) addrAvailable(target net.IP) bool {
|
||||||
|
|
||||||
if s.conf.ICMPTimeout == 0 {
|
if s.conf.ICMPTimeout == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -246,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,15 +257,20 @@ func (s *v4Server) addrAvailable(target net.IP) bool {
|
|||||||
pinger.OnRecv = func(pkt *ping.Packet) {
|
pinger.OnRecv = func(pkt *ping.Packet) {
|
||||||
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)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("DHCPv4: ICMP procedure is complete: %v", target)
|
log.Debug("dhcpv4: ICMP procedure is complete: %v", target)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,7 +343,7 @@ func (s *v4Server) commitLease(l *Lease) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process Discover request and return lease
|
// Process Discover request and return lease
|
||||||
func (s *v4Server) processDiscover(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) *Lease {
|
func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) *Lease {
|
||||||
mac := req.ClientHWAddr
|
mac := req.ClientHWAddr
|
||||||
|
|
||||||
s.leasesLock.Lock()
|
s.leasesLock.Lock()
|
||||||
@@ -349,7 +355,7 @@ func (s *v4Server) processDiscover(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) *Lea
|
|||||||
for lease == nil {
|
for lease == nil {
|
||||||
lease = s.reserveLease(mac)
|
lease = s.reserveLease(mac)
|
||||||
if lease == nil {
|
if lease == nil {
|
||||||
log.Debug("DHCPv4: No more IP addresses")
|
log.Debug("dhcpv4: No more IP addresses")
|
||||||
if toStore {
|
if toStore {
|
||||||
s.conf.notify(LeaseChangedDBStore)
|
s.conf.notify(LeaseChangedDBStore)
|
||||||
}
|
}
|
||||||
@@ -372,7 +378,7 @@ func (s *v4Server) processDiscover(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) *Lea
|
|||||||
reqIP := req.Options.Get(dhcpv4.OptionRequestedIPAddress)
|
reqIP := req.Options.Get(dhcpv4.OptionRequestedIPAddress)
|
||||||
if len(reqIP) != 0 &&
|
if len(reqIP) != 0 &&
|
||||||
!bytes.Equal(reqIP, lease.IP) {
|
!bytes.Equal(reqIP, lease.IP) {
|
||||||
log.Debug("DHCPv4: different RequestedIP: %v != %v", reqIP, lease.IP)
|
log.Debug("dhcpv4: different RequestedIP: %v != %v", reqIP, lease.IP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,12 +413,11 @@ func (o *optFQDN) ToBytes() []byte {
|
|||||||
|
|
||||||
copy(b[i:], []byte(o.name))
|
copy(b[i:], []byte(o.name))
|
||||||
return b
|
return b
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process Request request and return lease
|
// Process Request request and return lease
|
||||||
// Return false if we don't need to reply
|
// Return false if we don't need to reply
|
||||||
func (s *v4Server) processRequest(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) (*Lease, bool) {
|
func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (*Lease, bool) {
|
||||||
var lease *Lease
|
var lease *Lease
|
||||||
mac := req.ClientHWAddr
|
mac := req.ClientHWAddr
|
||||||
hostname := req.Options.Get(dhcpv4.OptionHostName)
|
hostname := req.Options.Get(dhcpv4.OptionHostName)
|
||||||
@@ -424,12 +429,12 @@ func (s *v4Server) processRequest(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) (*Lea
|
|||||||
sid := req.Options.Get(dhcpv4.OptionServerIdentifier)
|
sid := req.Options.Get(dhcpv4.OptionServerIdentifier)
|
||||||
if len(sid) != 0 &&
|
if len(sid) != 0 &&
|
||||||
!bytes.Equal(sid, s.conf.dnsIPAddrs[0]) {
|
!bytes.Equal(sid, s.conf.dnsIPAddrs[0]) {
|
||||||
log.Debug("DHCPv4: Bad OptionServerIdentifier in Request message for %s", mac)
|
log.Debug("dhcpv4: Bad OptionServerIdentifier in Request message for %s", mac)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(reqIP) != 4 {
|
if len(reqIP) != 4 {
|
||||||
log.Debug("DHCPv4: Bad OptionRequestedIPAddress in Request message for %s", mac)
|
log.Debug("dhcpv4: Bad OptionRequestedIPAddress in Request message for %s", mac)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,7 +443,7 @@ func (s *v4Server) processRequest(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) (*Lea
|
|||||||
if bytes.Equal(l.HWAddr, mac) {
|
if bytes.Equal(l.HWAddr, mac) {
|
||||||
if !bytes.Equal(l.IP, reqIP) {
|
if !bytes.Equal(l.IP, reqIP) {
|
||||||
s.leasesLock.Unlock()
|
s.leasesLock.Unlock()
|
||||||
log.Debug("DHCPv4: Mismatched OptionRequestedIPAddress in Request message for %s", mac)
|
log.Debug("dhcpv4: Mismatched OptionRequestedIPAddress in Request message for %s", mac)
|
||||||
return nil, true
|
return nil, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,7 +454,7 @@ func (s *v4Server) processRequest(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) (*Lea
|
|||||||
s.leasesLock.Unlock()
|
s.leasesLock.Unlock()
|
||||||
|
|
||||||
if lease == nil {
|
if lease == nil {
|
||||||
log.Debug("DHCPv4: No lease for %s", mac)
|
log.Debug("dhcpv4: No lease for %s", mac)
|
||||||
return nil, true
|
return nil, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,8 +480,7 @@ func (s *v4Server) processRequest(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) (*Lea
|
|||||||
// Return 1: OK
|
// Return 1: OK
|
||||||
// Return 0: error; reply with Nak
|
// Return 0: error; reply with Nak
|
||||||
// Return -1: error; don't reply
|
// Return -1: error; don't reply
|
||||||
func (s *v4Server) process(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) int {
|
func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
|
||||||
|
|
||||||
var lease *Lease
|
var lease *Lease
|
||||||
|
|
||||||
resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0]))
|
resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0]))
|
||||||
@@ -519,7 +523,7 @@ func (s *v4Server) process(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) int {
|
|||||||
// client(0.0.0.0:68) -> (Request:ClientMAC,Type=Request,ClientID,ReqIP||ClientIP,HostName,ServerID,ParamReqList) -> server(255.255.255.255:67)
|
// client(0.0.0.0:68) -> (Request:ClientMAC,Type=Request,ClientID,ReqIP||ClientIP,HostName,ServerID,ParamReqList) -> server(255.255.255.255:67)
|
||||||
// client(255.255.255.255:68) <- (Reply:YourIP,ClientMAC,Type=ACK,ServerID,SubnetMask,LeaseTime) <- server(<IP>:67)
|
// client(255.255.255.255:68) <- (Reply:YourIP,ClientMAC,Type=ACK,ServerID,SubnetMask,LeaseTime) <- server(<IP>:67)
|
||||||
func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4.DHCPv4) {
|
func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4.DHCPv4) {
|
||||||
log.Debug("DHCPv4: received message: %s", req.Summary())
|
log.Debug("dhcpv4: received message: %s", req.Summary())
|
||||||
|
|
||||||
switch req.MessageType() {
|
switch req.MessageType() {
|
||||||
case dhcpv4.MessageTypeDiscover,
|
case dhcpv4.MessageTypeDiscover,
|
||||||
@@ -527,18 +531,18 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
|||||||
//
|
//
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Debug("DHCPv4: unsupported message type %d", req.MessageType())
|
log.Debug("dhcpv4: unsupported message type %d", req.MessageType())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := dhcpv4.NewReplyFromRequest(req)
|
resp, err := dhcpv4.NewReplyFromRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("DHCPv4: dhcpv4.New: %s", err)
|
log.Debug("dhcpv4: dhcpv4.New: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req.ClientHWAddr) != 6 {
|
if len(req.ClientHWAddr) != 6 {
|
||||||
log.Debug("DHCPv4: Invalid ClientHWAddr")
|
log.Debug("dhcpv4: Invalid ClientHWAddr")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,33 +553,41 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
|||||||
resp.Options.Update(dhcpv4.OptMessageType(dhcpv4.MessageTypeNak))
|
resp.Options.Update(dhcpv4.OptMessageType(dhcpv4.MessageTypeNak))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("DHCPv4: sending: %s", resp.Summary())
|
log.Debug("dhcpv4: sending: %s", resp.Summary())
|
||||||
|
|
||||||
_, err = conn.WriteTo(resp.ToBytes(), peer)
|
_, err = conn.WriteTo(resp.ToBytes(), peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("DHCPv4: conn.Write to %s failed: %s", peer, err)
|
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start - start 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 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
iface, err := net.InterfaceByName(s.conf.InterfaceName)
|
ifaceName := s.conf.InterfaceName
|
||||||
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("DHCPv4: Couldn't find interface by name %s: %s", s.conf.InterfaceName, err)
|
return fmt.Errorf("dhcpv4: finding interface %s by name: %w", ifaceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("DHCPv4: starting...")
|
log.Debug("dhcpv4: starting...")
|
||||||
s.conf.dnsIPAddrs = getIfaceIPv4(*iface)
|
|
||||||
if len(s.conf.dnsIPAddrs) == 0 {
|
dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion4, defaultMaxAttempts, defaultBackoff)
|
||||||
log.Debug("DHCPv4: no IPv6 address for interface %s", iface.Name)
|
if err != nil {
|
||||||
|
return fmt.Errorf("dhcpv4: interface %s: %w", ifaceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dnsIPAddrs) == 0 {
|
||||||
|
// No available IP addresses which may appear later.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.conf.dnsIPAddrs = dnsIPAddrs
|
||||||
|
|
||||||
laddr := &net.UDPAddr{
|
laddr := &net.UDPAddr{
|
||||||
IP: net.ParseIP("0.0.0.0"),
|
IP: net.ParseIP("0.0.0.0"),
|
||||||
Port: dhcpv4.ServerPort,
|
Port: dhcpv4.ServerPort,
|
||||||
@@ -585,12 +597,13 @@ func (s *v4Server) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("DHCPv4: listening")
|
log.Info("dhcpv4: listening")
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err = s.srv.Serve()
|
err = s.srv.Serve()
|
||||||
log.Debug("DHCPv4: srv.Serve: %s", err)
|
log.Debug("dhcpv4: srv.Serve: %s", err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,10 +613,10 @@ func (s *v4Server) Stop() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("DHCPv4: stopping")
|
log.Debug("dhcpv4: stopping")
|
||||||
err := s.srv.Close()
|
err := s.srv.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("DHCPv4: srv.Close: %s", err)
|
log.Error("dhcpv4: srv.Close: %s", err)
|
||||||
}
|
}
|
||||||
// now s.srv.Serve() will return
|
// now s.srv.Serve() will return
|
||||||
s.srv = nil
|
s.srv = nil
|
||||||
@@ -621,31 +634,31 @@ func v4Create(conf V4ServerConf) (DHCPServer, error) {
|
|||||||
var err error
|
var err error
|
||||||
s.conf.routerIP, err = parseIPv4(s.conf.GatewayIP)
|
s.conf.routerIP, err = parseIPv4(s.conf.GatewayIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, fmt.Errorf("DHCPv4: %s", err)
|
return s, fmt.Errorf("dhcpv4: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
subnet, err := parseIPv4(s.conf.SubnetMask)
|
subnet, err := parseIPv4(s.conf.SubnetMask)
|
||||||
if err != nil || !isValidSubnetMask(subnet) {
|
if err != nil || !isValidSubnetMask(subnet) {
|
||||||
return s, fmt.Errorf("DHCPv4: invalid subnet mask: %s", s.conf.SubnetMask)
|
return s, fmt.Errorf("dhcpv4: invalid subnet mask: %s", s.conf.SubnetMask)
|
||||||
}
|
}
|
||||||
s.conf.subnetMask = make([]byte, 4)
|
s.conf.subnetMask = make([]byte, 4)
|
||||||
copy(s.conf.subnetMask, subnet)
|
copy(s.conf.subnetMask, subnet)
|
||||||
|
|
||||||
s.conf.ipStart, err = parseIPv4(conf.RangeStart)
|
s.conf.ipStart, err = parseIPv4(conf.RangeStart)
|
||||||
if s.conf.ipStart == nil {
|
if s.conf.ipStart == nil {
|
||||||
return s, fmt.Errorf("DHCPv4: %s", err)
|
return s, fmt.Errorf("dhcpv4: %w", err)
|
||||||
}
|
}
|
||||||
if s.conf.ipStart[0] == 0 {
|
if s.conf.ipStart[0] == 0 {
|
||||||
return s, fmt.Errorf("DHCPv4: invalid range start IP")
|
return s, fmt.Errorf("dhcpv4: invalid range start IP")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.conf.ipEnd, err = parseIPv4(conf.RangeEnd)
|
s.conf.ipEnd, err = parseIPv4(conf.RangeEnd)
|
||||||
if s.conf.ipEnd == nil {
|
if s.conf.ipEnd == nil {
|
||||||
return s, fmt.Errorf("DHCPv4: %s", err)
|
return s, fmt.Errorf("dhcpv4: %w", err)
|
||||||
}
|
}
|
||||||
if !net.IP.Equal(s.conf.ipStart[:3], s.conf.ipEnd[:3]) ||
|
if !net.IP.Equal(s.conf.ipStart[:3], s.conf.ipEnd[:3]) ||
|
||||||
s.conf.ipStart[3] > s.conf.ipEnd[3] {
|
s.conf.ipStart[3] > s.conf.ipEnd[3] {
|
||||||
return s, fmt.Errorf("DHCPv4: range end IP should match range start IP")
|
return s, fmt.Errorf("dhcpv4: range end IP should match range start IP")
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.LeaseDuration == 0 {
|
if conf.LeaseDuration == 0 {
|
||||||
@@ -658,7 +671,7 @@ func v4Create(conf V4ServerConf) (DHCPServer, error) {
|
|||||||
for _, o := range conf.Options {
|
for _, o := range conf.Options {
|
||||||
code, val := parseOptionString(o)
|
code, val := parseOptionString(o)
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
log.Debug("DHCPv4: bad option string: %s", o)
|
log.Debug("dhcpv4: bad option string: %s", o)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
123
internal/dhcpd/v46.go
Normal file
123
internal/dhcpd/v46.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
189
internal/dhcpd/v46_test.go
Normal file
189
internal/dhcpd/v46_test.go
Normal file
@@ -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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
23
internal/dhcpd/v46_windows.go
Normal file
23
internal/dhcpd/v46_windows.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package dhcpd
|
||||||
|
|
||||||
|
// 'u-root/u-root' package, a dependency of 'insomniacslk/dhcp' package, doesn't build on Windows
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
type winServer struct{}
|
||||||
|
|
||||||
|
func (s *winServer) ResetLeases(leases []*Lease) {}
|
||||||
|
func (s *winServer) GetLeases(flags int) []Lease { return nil }
|
||||||
|
func (s *winServer) GetLeasesRef() []*Lease { return nil }
|
||||||
|
func (s *winServer) AddStaticLease(lease Lease) error { return nil }
|
||||||
|
func (s *winServer) RemoveStaticLease(l Lease) error { return 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
|
||||||
@@ -41,10 +43,11 @@ func (s *v6Server) WriteDiskConfig6(c *V6ServerConf) {
|
|||||||
|
|
||||||
// Return TRUE if IP address is within range [start..0xff]
|
// Return TRUE if IP address is within range [start..0xff]
|
||||||
// nolint(staticcheck)
|
// nolint(staticcheck)
|
||||||
func ip6InRange(start net.IP, ip net.IP) bool {
|
func ip6InRange(start, ip net.IP) bool {
|
||||||
if len(start) != 16 {
|
if len(start) != 16 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
//lint:ignore SA1021 TODO(e.burkov): Ignore this for now, think about using masks.
|
||||||
if !bytes.Equal(start[:15], ip[:15]) {
|
if !bytes.Equal(start[:15], ip[:15]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -72,12 +75,10 @@ func (s *v6Server) GetLeases(flags int) []Lease {
|
|||||||
var result []Lease
|
var result []Lease
|
||||||
s.leasesLock.Lock()
|
s.leasesLock.Lock()
|
||||||
for _, lease := range s.leases {
|
for _, lease := range s.leases {
|
||||||
|
|
||||||
if lease.Expiry.Unix() == leaseExpireStatic {
|
if lease.Expiry.Unix() == leaseExpireStatic {
|
||||||
if (flags & LeasesStatic) != 0 {
|
if (flags & LeasesStatic) != 0 {
|
||||||
result = append(result, *lease)
|
result = append(result, *lease)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (flags & LeasesDynamic) != 0 {
|
if (flags & LeasesDynamic) != 0 {
|
||||||
result = append(result, *lease)
|
result = append(result, *lease)
|
||||||
@@ -214,8 +215,7 @@ func (s *v6Server) rmLease(lease Lease) error {
|
|||||||
|
|
||||||
if !bytes.Equal(l.HWAddr, lease.HWAddr) ||
|
if !bytes.Equal(l.HWAddr, lease.HWAddr) ||
|
||||||
l.Hostname != lease.Hostname {
|
l.Hostname != lease.Hostname {
|
||||||
|
return fmt.Errorf("lease not found")
|
||||||
return fmt.Errorf("Lease not found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.leaseRemoveSwapByIndex(i)
|
s.leaseRemoveSwapByIndex(i)
|
||||||
@@ -302,7 +302,7 @@ func (s *v6Server) commitDynamicLease(l *Lease) {
|
|||||||
// Check Client ID
|
// Check Client ID
|
||||||
func (s *v6Server) checkCID(msg *dhcpv6.Message) error {
|
func (s *v6Server) checkCID(msg *dhcpv6.Message) error {
|
||||||
if msg.Options.ClientID() == nil {
|
if msg.Options.ClientID() == nil {
|
||||||
return fmt.Errorf("DHCPv6: no ClientID option in request")
|
return fmt.Errorf("dhcpv6: no ClientID option in request")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -317,7 +317,7 @@ func (s *v6Server) checkSID(msg *dhcpv6.Message) error {
|
|||||||
dhcpv6.MessageTypeRebind:
|
dhcpv6.MessageTypeRebind:
|
||||||
|
|
||||||
if sid != nil {
|
if sid != nil {
|
||||||
return fmt.Errorf("DHCPv6: drop packet: ServerID option in message %s", msg.Type().String())
|
return fmt.Errorf("dhcpv6: drop packet: ServerID option in message %s", msg.Type().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
case dhcpv6.MessageTypeRequest,
|
case dhcpv6.MessageTypeRequest,
|
||||||
@@ -326,10 +326,10 @@ func (s *v6Server) checkSID(msg *dhcpv6.Message) error {
|
|||||||
dhcpv6.MessageTypeDecline:
|
dhcpv6.MessageTypeDecline:
|
||||||
|
|
||||||
if sid == nil {
|
if sid == nil {
|
||||||
return fmt.Errorf("DHCPv6: drop packet: no ServerID option in message %s", msg.Type().String())
|
return fmt.Errorf("dhcpv6: drop packet: no ServerID option in message %s", msg.Type().String())
|
||||||
}
|
}
|
||||||
if !sid.Equal(s.sid) {
|
if !sid.Equal(s.sid) {
|
||||||
return fmt.Errorf("DHCPv6: drop packet: mismatched ServerID option in message %s: %s",
|
return fmt.Errorf("dhcpv6: drop packet: mismatched ServerID option in message %s: %s",
|
||||||
msg.Type().String(), sid.String())
|
msg.Type().String(), sid.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,7 +371,7 @@ func (s *v6Server) commitLease(msg *dhcpv6.Message, lease *Lease) time.Duration
|
|||||||
//
|
//
|
||||||
|
|
||||||
case dhcpv6.MessageTypeConfirm:
|
case dhcpv6.MessageTypeConfirm:
|
||||||
lifetime = lease.Expiry.Sub(time.Now())
|
lifetime = time.Until(lease.Expiry)
|
||||||
|
|
||||||
case dhcpv6.MessageTypeRequest,
|
case dhcpv6.MessageTypeRequest,
|
||||||
dhcpv6.MessageTypeRenew,
|
dhcpv6.MessageTypeRenew,
|
||||||
@@ -385,7 +385,7 @@ func (s *v6Server) commitLease(msg *dhcpv6.Message, lease *Lease) time.Duration
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find a lease associated with MAC and prepare response
|
// Find a lease associated with MAC and prepare response
|
||||||
func (s *v6Server) process(msg *dhcpv6.Message, req dhcpv6.DHCPv6, resp dhcpv6.DHCPv6) bool {
|
func (s *v6Server) process(msg *dhcpv6.Message, req, resp dhcpv6.DHCPv6) bool {
|
||||||
switch msg.Type() {
|
switch msg.Type() {
|
||||||
case dhcpv6.MessageTypeSolicit,
|
case dhcpv6.MessageTypeSolicit,
|
||||||
dhcpv6.MessageTypeRequest,
|
dhcpv6.MessageTypeRequest,
|
||||||
@@ -539,26 +539,6 @@ func (s *v6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get IPv6 address list
|
|
||||||
func getIfaceIPv6(iface net.Interface) []net.IP {
|
|
||||||
addrs, err := iface.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var res []net.IP
|
|
||||||
for _, a := range addrs {
|
|
||||||
ipnet, ok := a.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ipnet.IP.To4() == nil {
|
|
||||||
res = append(res, ipnet.IP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
@@ -580,23 +560,32 @@ func (s *v6Server) initRA(iface *net.Interface) error {
|
|||||||
return s.ra.Init()
|
return s.ra.Init()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start - start server
|
// Start starts the IPv6 DHCP server.
|
||||||
func (s *v6Server) Start() error {
|
func (s *v6Server) Start() error {
|
||||||
if !s.conf.Enabled {
|
if !s.conf.Enabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
iface, err := net.InterfaceByName(s.conf.InterfaceName)
|
ifaceName := s.conf.InterfaceName
|
||||||
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapErrPrint(err, "Couldn't find interface by name %s", s.conf.InterfaceName)
|
return fmt.Errorf("dhcpv6: finding interface %s by name: %w", ifaceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.conf.dnsIPAddrs = getIfaceIPv6(*iface)
|
log.Debug("dhcpv6: starting...")
|
||||||
if len(s.conf.dnsIPAddrs) == 0 {
|
|
||||||
log.Debug("DHCPv6: no IPv6 address for interface %s", iface.Name)
|
dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion6, defaultMaxAttempts, defaultBackoff)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dhcpv6: interface %s: %w", ifaceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dnsIPAddrs) == 0 {
|
||||||
|
// No available IP addresses which may appear later.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.conf.dnsIPAddrs = dnsIPAddrs
|
||||||
|
|
||||||
err = s.initRA(iface)
|
err = s.initRA(iface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -608,10 +597,10 @@ 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)
|
||||||
}
|
}
|
||||||
s.sid = dhcpv6.Duid{
|
s.sid = dhcpv6.Duid{
|
||||||
Type: dhcpv6.DUID_LLT,
|
Type: dhcpv6.DUID_LLT,
|
||||||
@@ -639,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 {
|
||||||
@@ -647,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
|
||||||
}
|
}
|
||||||
@@ -666,7 +659,7 @@ func v6Create(conf V6ServerConf) (DHCPServer, error) {
|
|||||||
|
|
||||||
s.conf.ipStart = net.ParseIP(conf.RangeStart)
|
s.conf.ipStart = net.ParseIP(conf.RangeStart)
|
||||||
if s.conf.ipStart == nil || s.conf.ipStart.To16() == nil {
|
if s.conf.ipStart == nil || s.conf.ipStart.To16() == nil {
|
||||||
return s, fmt.Errorf("DHCPv6: invalid range-start IP: %s", conf.RangeStart)
|
return s, fmt.Errorf("dhcpv6: invalid range-start IP: %s", conf.RangeStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.LeaseDuration == 0 {
|
if conf.LeaseDuration == 0 {
|
||||||
@@ -22,12 +22,12 @@ func main() {
|
|||||||
res, err := filter.CheckHost(host)
|
res, err := filter.CheckHost(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// temporary failure
|
// temporary failure
|
||||||
log.Fatalf("Failed to check host '%s': %s", host, err)
|
log.Fatalf("Failed to check host %q: %s", host, err)
|
||||||
}
|
}
|
||||||
if res.IsFiltered {
|
if res.IsFiltered {
|
||||||
log.Printf("Host %s is filtered, reason - '%s', matched rule: '%s'", host, res.Reason, res.Rule)
|
log.Printf("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rule)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Host %s is not filtered, reason - '%s'", host, res.Reason)
|
log.Printf("Host %s is not filtered, reason - %q", host, res.Reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -59,12 +59,12 @@ func main() {
|
|||||||
res, err := filter.CheckHost(host)
|
res, err := filter.CheckHost(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// temporary failure
|
// temporary failure
|
||||||
log.Fatalf("Failed to check host '%s': %s", host, err)
|
log.Fatalf("Failed to check host %q: %s", host, err)
|
||||||
}
|
}
|
||||||
if res.IsFiltered {
|
if res.IsFiltered {
|
||||||
log.Printf("Host %s is filtered, reason - '%s', matched rule: '%s'", host, res.Reason, res.Rule)
|
log.Printf("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rule)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Host %s is not filtered, reason - '%s'", host, res.Reason)
|
log.Printf("Host %s is not filtered, reason - %q", host, res.Reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Package dnsfilter implements a DNS filter.
|
||||||
package dnsfilter
|
package dnsfilter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/cache"
|
"github.com/AdguardTeam/golibs/cache"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
@@ -145,6 +146,8 @@ const (
|
|||||||
FilteredSafeSearch
|
FilteredSafeSearch
|
||||||
// FilteredBlockedService - the host is blocked by "blocked services" settings
|
// FilteredBlockedService - the host is blocked by "blocked services" settings
|
||||||
FilteredBlockedService
|
FilteredBlockedService
|
||||||
|
// FilteredRebind - the request was blocked due to DNS rebinding protection
|
||||||
|
FilteredRebind
|
||||||
|
|
||||||
// ReasonRewrite - rewrite rule was applied
|
// ReasonRewrite - rewrite rule was applied
|
||||||
ReasonRewrite
|
ReasonRewrite
|
||||||
@@ -164,6 +167,7 @@ var reasonNames = []string{
|
|||||||
"FilteredInvalid",
|
"FilteredInvalid",
|
||||||
"FilteredSafeSearch",
|
"FilteredSafeSearch",
|
||||||
"FilteredBlockedService",
|
"FilteredBlockedService",
|
||||||
|
"FilteredRebind",
|
||||||
|
|
||||||
"Rewrite",
|
"Rewrite",
|
||||||
"RewriteEtcHosts",
|
"RewriteEtcHosts",
|
||||||
@@ -176,6 +180,16 @@ func (r Reason) String() string {
|
|||||||
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{}
|
||||||
@@ -199,7 +213,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 []Filter, 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,
|
||||||
@@ -252,11 +266,20 @@ func (d *Dnsfilter) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if d.rulesStorageWhite != nil {
|
||||||
d.rulesStorageWhite.Close()
|
err = d.rulesStorageWhite.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("dnsfilter: rulesStorageWhite.Close: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +304,7 @@ type Result struct {
|
|||||||
CanonName string `json:",omitempty"` // CNAME value
|
CanonName string `json:",omitempty"` // CNAME value
|
||||||
|
|
||||||
// for RewriteEtcHosts:
|
// for RewriteEtcHosts:
|
||||||
ReverseHost string `json:",omitempty"`
|
ReverseHosts []string `json:",omitempty"`
|
||||||
|
|
||||||
// for ReasonRewrite & RewriteEtcHosts:
|
// for ReasonRewrite & RewriteEtcHosts:
|
||||||
IPList []net.IP `json:",omitempty"` // list of IP addresses
|
IPList []net.IP `json:",omitempty"` // list of IP addresses
|
||||||
@@ -325,17 +348,8 @@ 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 {
|
||||||
ips := d.Config.AutoHosts.Process(host, qtype)
|
matched := d.checkAutoHosts(host, qtype, &result)
|
||||||
if ips != nil {
|
if matched {
|
||||||
result.Reason = RewriteEtcHosts
|
|
||||||
result.IPList = ips
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
revHost := d.Config.AutoHosts.ProcessReverse(host, qtype)
|
|
||||||
if len(revHost) != 0 {
|
|
||||||
result.Reason = RewriteEtcHosts
|
|
||||||
result.ReverseHost = revHost + "."
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,6 +415,31 @@ 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) {
|
||||||
|
ips := d.Config.AutoHosts.Process(host, qtype)
|
||||||
|
if ips != nil {
|
||||||
|
result.Reason = RewriteEtcHosts
|
||||||
|
result.IPList = ips
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype)
|
||||||
|
if len(revHosts) != 0 {
|
||||||
|
result.Reason = RewriteEtcHosts
|
||||||
|
|
||||||
|
// TODO(a.garipov): Optimize this with a buffer.
|
||||||
|
result.ReverseHosts = make([]string, len(revHosts))
|
||||||
|
for i := range revHosts {
|
||||||
|
result.ReverseHosts[i] = revHosts[i] + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Process rewrites table
|
// Process rewrites table
|
||||||
// . Find CNAME for a domain name (exact match or by wildcard)
|
// . Find CNAME for a domain name (exact match or by wildcard)
|
||||||
// . if found and CNAME equals to domain name - this is an exception; exit
|
// . if found and CNAME equals to domain name - this is an exception; exit
|
||||||
@@ -481,13 +520,10 @@ func matchBlockedServicesRules(host string, svcs []ServiceEntry) Result {
|
|||||||
// Adding rule and matching against the rules
|
// Adding rule and matching against the rules
|
||||||
//
|
//
|
||||||
|
|
||||||
// Return TRUE if file exists
|
// fileExists returns true if file exists.
|
||||||
func fileExists(fn string) bool {
|
func fileExists(fn string) bool {
|
||||||
_, err := os.Stat(fn)
|
_, err := os.Stat(fn)
|
||||||
if err != nil {
|
return err == nil
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilter.DNSEngine, error) {
|
func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilter.DNSEngine, error) {
|
||||||
@@ -501,19 +537,18 @@ func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilte
|
|||||||
RulesText: string(f.Data),
|
RulesText: string(f.Data),
|
||||||
IgnoreCosmetic: true,
|
IgnoreCosmetic: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if !fileExists(f.FilePath) {
|
} else if !fileExists(f.FilePath) {
|
||||||
list = &filterlist.StringRuleList{
|
list = &filterlist.StringRuleList{
|
||||||
ID: int(f.ID),
|
ID: int(f.ID),
|
||||||
IgnoreCosmetic: true,
|
IgnoreCosmetic: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if runtime.GOOS == "windows" {
|
} else if runtime.GOOS == "windows" {
|
||||||
// On Windows we don't pass a file to urlfilter because
|
// On Windows we don't pass a file to urlfilter because
|
||||||
// it's difficult to update this file while it's being used.
|
// it's difficult to update this file while it's being
|
||||||
|
// used.
|
||||||
data, err := ioutil.ReadFile(f.FilePath)
|
data, err := ioutil.ReadFile(f.FilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("ioutil.ReadFile(): %s: %s", f.FilePath, err)
|
return nil, nil, fmt.Errorf("ioutil.ReadFile(): %s: %w", f.FilePath, err)
|
||||||
}
|
}
|
||||||
list = &filterlist.StringRuleList{
|
list = &filterlist.StringRuleList{
|
||||||
ID: int(f.ID),
|
ID: int(f.ID),
|
||||||
@@ -525,7 +560,7 @@ func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilte
|
|||||||
var err error
|
var err error
|
||||||
list, err = filterlist.NewFileRuleList(int(f.ID), f.FilePath, true)
|
list, err = filterlist.NewFileRuleList(int(f.ID), f.FilePath, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("filterlist.NewFileRuleList(): %s: %s", f.FilePath, err)
|
return nil, nil, fmt.Errorf("filterlist.NewFileRuleList(): %s: %w", f.FilePath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listArray = append(listArray, list)
|
listArray = append(listArray, list)
|
||||||
@@ -533,13 +568,13 @@ func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilte
|
|||||||
|
|
||||||
rulesStorage, err := filterlist.NewRuleStorage(listArray)
|
rulesStorage, err := filterlist.NewRuleStorage(listArray)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("filterlist.NewRuleStorage(): %s", err)
|
return nil, nil, fmt.Errorf("filterlist.NewRuleStorage(): %w", err)
|
||||||
}
|
}
|
||||||
filteringEngine := urlfilter.NewDNSEngine(rulesStorage)
|
filteringEngine := urlfilter.NewDNSEngine(rulesStorage)
|
||||||
return rulesStorage, filteringEngine, nil
|
return rulesStorage, filteringEngine, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@@ -565,18 +600,21 @@ func (d *Dnsfilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// matchHost is a low-level way to check only if hostname is filtered by rules, skipping expensive safebrowsing and parental lookups
|
// matchHost is a low-level way to check only if hostname is filtered by rules,
|
||||||
|
// 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) (Result, 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.filteringEngineWhite != nil {
|
||||||
rr, ok := d.filteringEngineWhite.MatchRequest(ureq)
|
rr, ok := d.filteringEngineWhite.MatchRequest(ureq)
|
||||||
@@ -590,7 +628,7 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
|||||||
rule = rr.HostRulesV6[0]
|
rule = rr.HostRulesV6[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("Filtering: found whitelist rule for host '%s': '%s' list_id: %d",
|
log.Debug("Filtering: found whitelist rule for host %q: %q list_id: %d",
|
||||||
host, rule.Text(), rule.GetFilterListID())
|
host, rule.Text(), rule.GetFilterListID())
|
||||||
res := makeResult(rule, NotFilteredWhiteList)
|
res := makeResult(rule, NotFilteredWhiteList)
|
||||||
return res, nil
|
return res, nil
|
||||||
@@ -607,7 +645,7 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
|||||||
}
|
}
|
||||||
|
|
||||||
if rr.NetworkRule != nil {
|
if rr.NetworkRule != nil {
|
||||||
log.Debug("Filtering: found rule for host '%s': '%s' list_id: %d",
|
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
|
||||||
host, rr.NetworkRule.Text(), rr.NetworkRule.GetFilterListID())
|
host, rr.NetworkRule.Text(), rr.NetworkRule.GetFilterListID())
|
||||||
reason := FilteredBlackList
|
reason := FilteredBlackList
|
||||||
if rr.NetworkRule.Whitelist {
|
if rr.NetworkRule.Whitelist {
|
||||||
@@ -619,7 +657,7 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
|||||||
|
|
||||||
if qtype == dns.TypeA && rr.HostRulesV4 != nil {
|
if qtype == dns.TypeA && rr.HostRulesV4 != nil {
|
||||||
rule := rr.HostRulesV4[0] // note that we process only 1 matched rule
|
rule := rr.HostRulesV4[0] // note that we process only 1 matched rule
|
||||||
log.Debug("Filtering: found rule for host '%s': '%s' 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, FilteredBlackList)
|
||||||
res.IP = rule.IP.To4()
|
res.IP = rule.IP.To4()
|
||||||
@@ -628,7 +666,7 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
|||||||
|
|
||||||
if qtype == dns.TypeAAAA && rr.HostRulesV6 != nil {
|
if qtype == dns.TypeAAAA && rr.HostRulesV6 != nil {
|
||||||
rule := rr.HostRulesV6[0] // note that we process only 1 matched rule
|
rule := rr.HostRulesV6[0] // note that we process only 1 matched rule
|
||||||
log.Debug("Filtering: found rule for host '%s': '%s' 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, FilteredBlackList)
|
||||||
res.IP = rule.IP
|
res.IP = rule.IP
|
||||||
@@ -644,7 +682,7 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
|||||||
} else if rr.HostRulesV6 != nil {
|
} else if rr.HostRulesV6 != nil {
|
||||||
rule = rr.HostRulesV6[0]
|
rule = rr.HostRulesV6[0]
|
||||||
}
|
}
|
||||||
log.Debug("Filtering: found rule for host '%s': '%s' 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, FilteredBlackList)
|
||||||
res.IP = net.IP{}
|
res.IP = net.IP{}
|
||||||
@@ -666,21 +704,18 @@ func makeResult(rule rules.Rule, reason Reason) Result {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitModule() - manually initialize blocked services map
|
// InitModule manually initializes blocked services map.
|
||||||
func InitModule() {
|
func InitModule() {
|
||||||
initBlockedServices()
|
initBlockedServices()
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates properly initialized DNS Filter that is ready to be used
|
// New creates properly initialized DNS Filter that is ready to be used.
|
||||||
func New(c *Config, blockFilters []Filter) *Dnsfilter {
|
func New(c *Config, blockFilters []Filter) *Dnsfilter {
|
||||||
|
|
||||||
if c != nil {
|
if c != nil {
|
||||||
cacheConf := cache.Config{
|
cacheConf := cache.Config{
|
||||||
EnableLRU: true,
|
EnableLRU: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize objects only once
|
|
||||||
|
|
||||||
if gctx.safebrowsingCache == nil {
|
if gctx.safebrowsingCache == nil {
|
||||||
cacheConf.MaxSize = c.SafeBrowsingCacheSize
|
cacheConf.MaxSize = c.SafeBrowsingCacheSize
|
||||||
gctx.safebrowsingCache = cache.New(cacheConf)
|
gctx.safebrowsingCache = cache.New(cacheConf)
|
||||||
@@ -713,7 +748,7 @@ func New(c *Config, blockFilters []Filter) *Dnsfilter {
|
|||||||
bsvcs := []string{}
|
bsvcs := []string{}
|
||||||
for _, s := range d.BlockedServices {
|
for _, s := range d.BlockedServices {
|
||||||
if !BlockedSvcKnown(s) {
|
if !BlockedSvcKnown(s) {
|
||||||
log.Debug("skipping unknown blocked-service '%s'", s)
|
log.Debug("skipping unknown blocked-service %q", s)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bsvcs = append(bsvcs, s)
|
bsvcs = append(bsvcs, s)
|
||||||
@@ -750,7 +785,7 @@ func (d *Dnsfilter) Start() {
|
|||||||
// stats
|
// stats
|
||||||
//
|
//
|
||||||
|
|
||||||
// GetStats return dns filtering stats since startup
|
// GetStats return dns filtering stats since startup.
|
||||||
func (d *Dnsfilter) GetStats() Stats {
|
func (d *Dnsfilter) GetStats() Stats {
|
||||||
return gctx.stats
|
return gctx.stats
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,23 @@
|
|||||||
package dnsfilter
|
package dnsfilter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"strings"
|
||||||
"path"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/testutil"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/urlfilter/rules"
|
"github.com/AdguardTeam/urlfilter/rules"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
testutil.DiscardLogOutput(m)
|
||||||
|
}
|
||||||
|
|
||||||
var setts RequestFilteringSettings
|
var setts RequestFilteringSettings
|
||||||
|
|
||||||
// HELPERS
|
// HELPERS
|
||||||
@@ -36,13 +41,6 @@ func purgeCaches() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _Func() string {
|
|
||||||
pc := make([]uintptr, 10) // at least 1 entry needed
|
|
||||||
runtime.Callers(2, pc)
|
|
||||||
f := runtime.FuncForPC(pc[0])
|
|
||||||
return path.Base(f.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewForTest(c *Config, filters []Filter) *Dnsfilter {
|
func NewForTest(c *Config, filters []Filter) *Dnsfilter {
|
||||||
setts = RequestFilteringSettings{}
|
setts = RequestFilteringSettings{}
|
||||||
setts.FilteringEnabled = true
|
setts.FilteringEnabled = true
|
||||||
@@ -71,7 +69,7 @@ func (d *Dnsfilter) checkMatch(t *testing.T, hostname string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) checkMatchIP(t *testing.T, hostname string, 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)
|
ret, err := d.CheckHost(hostname, qtype, &setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -107,7 +105,7 @@ func TestEtcHostsMatching(t *testing.T) {
|
|||||||
::1 host2
|
::1 host2
|
||||||
`,
|
`,
|
||||||
addr, addr6)
|
addr, addr6)
|
||||||
filters := []Filter{Filter{
|
filters := []Filter{{
|
||||||
ID: 0, Data: []byte(text),
|
ID: 0, Data: []byte(text),
|
||||||
}}
|
}}
|
||||||
d := NewForTest(nil, filters)
|
d := NewForTest(nil, filters)
|
||||||
@@ -147,10 +145,17 @@ func TestEtcHostsMatching(t *testing.T) {
|
|||||||
// SAFE BROWSING
|
// SAFE BROWSING
|
||||||
|
|
||||||
func TestSafeBrowsing(t *testing.T) {
|
func TestSafeBrowsing(t *testing.T) {
|
||||||
|
logOutput := &bytes.Buffer{}
|
||||||
|
testutil.ReplaceLogWriter(t, logOutput)
|
||||||
|
testutil.ReplaceLogLevel(t, log.DEBUG)
|
||||||
|
|
||||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
gctx.stats.Safebrowsing.Requests = 0
|
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"))
|
||||||
|
|
||||||
d.checkMatch(t, "test.wmconvirus.narod.ru")
|
d.checkMatch(t, "test.wmconvirus.narod.ru")
|
||||||
d.checkMatchEmpty(t, "yandex.ru")
|
d.checkMatchEmpty(t, "yandex.ru")
|
||||||
d.checkMatchEmpty(t, "pornhub.com")
|
d.checkMatchEmpty(t, "pornhub.com")
|
||||||
@@ -300,7 +305,6 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
|
|||||||
t.Fatalf("Failed to lookup for %s", safeDomain)
|
t.Fatalf("Failed to lookup for %s", safeDomain)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("IP addresses: %v", ips)
|
|
||||||
ip := ips[0]
|
ip := ips[0]
|
||||||
for _, i := range ips {
|
for _, i := range ips {
|
||||||
if i.To4() != nil {
|
if i.To4() != nil {
|
||||||
@@ -334,9 +338,14 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
|
|||||||
// PARENTAL
|
// PARENTAL
|
||||||
|
|
||||||
func TestParentalControl(t *testing.T) {
|
func TestParentalControl(t *testing.T) {
|
||||||
|
logOutput := &bytes.Buffer{}
|
||||||
|
testutil.ReplaceLogWriter(t, logOutput)
|
||||||
|
testutil.ReplaceLogLevel(t, log.DEBUG)
|
||||||
|
|
||||||
d := NewForTest(&Config{ParentalEnabled: true}, nil)
|
d := NewForTest(&Config{ParentalEnabled: true}, nil)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
d.checkMatch(t, "pornhub.com")
|
d.checkMatch(t, "pornhub.com")
|
||||||
|
assert.True(t, strings.Contains(logOutput.String(), "Parental lookup for pornhub.com"))
|
||||||
d.checkMatch(t, "www.pornhub.com")
|
d.checkMatch(t, "www.pornhub.com")
|
||||||
d.checkMatchEmpty(t, "www.yandex.ru")
|
d.checkMatchEmpty(t, "www.yandex.ru")
|
||||||
d.checkMatchEmpty(t, "yandex.ru")
|
d.checkMatchEmpty(t, "yandex.ru")
|
||||||
@@ -351,11 +360,16 @@ func TestParentalControl(t *testing.T) {
|
|||||||
|
|
||||||
// FILTERING
|
// FILTERING
|
||||||
|
|
||||||
var blockingRules = "||example.org^\n"
|
const nl = "\n"
|
||||||
var whitelistRules = "||example.org^\n@@||test.example.org\n"
|
|
||||||
var importantRules = "@@||example.org^\n||test.example.org^$important\n"
|
const (
|
||||||
var regexRules = "/example\\.org/\n@@||test.example.org^\n"
|
blockingRules = `||example.org^` + nl
|
||||||
var maskRules = "test*.example.org^\nexam*.com\n"
|
whitelistRules = `||example.org^` + nl + `@@||test.example.org` + nl
|
||||||
|
importantRules = `@@||example.org^` + nl + `||test.example.org^$important` + nl
|
||||||
|
regexRules = `/example\.org/` + nl + `@@||test.example.org^` + 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 {
|
||||||
testname string
|
testname string
|
||||||
@@ -363,56 +377,63 @@ 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, FilteredBlackList, 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, FilteredBlackList, dns.TypeA},
|
||||||
{"blocking", blockingRules, "test.example.org", true, FilteredBlackList},
|
{"blocking", blockingRules, "test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||||
{"blocking", blockingRules, "test.test.example.org", true, FilteredBlackList},
|
{"blocking", blockingRules, "test.test.example.org", true, FilteredBlackList, 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},
|
{"whitelist", whitelistRules, "example.org", true, FilteredBlackList, dns.TypeA},
|
||||||
{"whitelist", whitelistRules, "test.example.org", false, NotFilteredWhiteList},
|
{"whitelist", whitelistRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||||
{"whitelist", whitelistRules, "test.test.example.org", false, NotFilteredWhiteList},
|
{"whitelist", whitelistRules, "test.test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||||
{"whitelist", whitelistRules, "testexample.org", false, NotFilteredNotFound},
|
{"whitelist", whitelistRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||||
{"whitelist", whitelistRules, "onemoreexample.org", false, NotFilteredNotFound},
|
{"whitelist", whitelistRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||||
|
|
||||||
{"important", importantRules, "example.org", false, NotFilteredWhiteList},
|
{"important", importantRules, "example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||||
{"important", importantRules, "test.example.org", true, FilteredBlackList},
|
{"important", importantRules, "test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||||
{"important", importantRules, "test.test.example.org", true, FilteredBlackList},
|
{"important", importantRules, "test.test.example.org", true, FilteredBlackList, 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, FilteredBlackList, dns.TypeA},
|
||||||
{"regex", regexRules, "test.example.org", false, NotFilteredWhiteList},
|
{"regex", regexRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||||
{"regex", regexRules, "test.test.example.org", false, NotFilteredWhiteList},
|
{"regex", regexRules, "test.test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||||
{"regex", regexRules, "testexample.org", true, FilteredBlackList},
|
{"regex", regexRules, "testexample.org", true, FilteredBlackList, dns.TypeA},
|
||||||
{"regex", regexRules, "onemoreexample.org", true, FilteredBlackList},
|
{"regex", regexRules, "onemoreexample.org", true, FilteredBlackList, dns.TypeA},
|
||||||
|
|
||||||
{"mask", maskRules, "test.example.org", true, FilteredBlackList},
|
{"mask", maskRules, "test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||||
{"mask", maskRules, "test2.example.org", true, FilteredBlackList},
|
{"mask", maskRules, "test2.example.org", true, FilteredBlackList, dns.TypeA},
|
||||||
{"mask", maskRules, "example.com", true, FilteredBlackList},
|
{"mask", maskRules, "example.com", true, FilteredBlackList, dns.TypeA},
|
||||||
{"mask", maskRules, "exampleeee.com", true, FilteredBlackList},
|
{"mask", maskRules, "exampleeee.com", true, FilteredBlackList, dns.TypeA},
|
||||||
{"mask", maskRules, "onemoreexamsite.com", true, FilteredBlackList},
|
{"mask", maskRules, "onemoreexamsite.com", true, FilteredBlackList, 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, FilteredBlackList, dns.TypeAAAA},
|
||||||
|
{"dnstype", dnstypeRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||||
|
{"dnstype", dnstypeRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeAAAA},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMatching(t *testing.T) {
|
func TestMatching(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(fmt.Sprintf("%s-%s", test.testname, test.hostname), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%s-%s", test.testname, test.hostname), func(t *testing.T) {
|
||||||
filters := []Filter{Filter{
|
filters := []Filter{{
|
||||||
ID: 0, Data: []byte(test.rules),
|
ID: 0, Data: []byte(test.rules),
|
||||||
}}
|
}}
|
||||||
d := NewForTest(nil, filters)
|
d := NewForTest(nil, filters)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
ret, err := d.CheckHost(test.hostname, dns.TypeA, &setts)
|
ret, 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)
|
||||||
}
|
}
|
||||||
@@ -430,14 +451,14 @@ func TestWhitelist(t *testing.T) {
|
|||||||
rules := `||host1^
|
rules := `||host1^
|
||||||
||host2^
|
||host2^
|
||||||
`
|
`
|
||||||
filters := []Filter{Filter{
|
filters := []Filter{{
|
||||||
ID: 0, Data: []byte(rules),
|
ID: 0, Data: []byte(rules),
|
||||||
}}
|
}}
|
||||||
|
|
||||||
whiteRules := `||host1^
|
whiteRules := `||host1^
|
||||||
||host3^
|
||host3^
|
||||||
`
|
`
|
||||||
whiteFilters := []Filter{Filter{
|
whiteFilters := []Filter{{
|
||||||
ID: 0, Data: []byte(whiteRules),
|
ID: 0, Data: []byte(whiteRules),
|
||||||
}}
|
}}
|
||||||
d := NewForTest(nil, filters)
|
d := NewForTest(nil, filters)
|
||||||
@@ -455,7 +476,6 @@ func TestWhitelist(t *testing.T) {
|
|||||||
assert.True(t, err == nil)
|
assert.True(t, err == nil)
|
||||||
assert.True(t, ret.IsFiltered && ret.Reason == FilteredBlackList)
|
assert.True(t, ret.IsFiltered && ret.Reason == FilteredBlackList)
|
||||||
assert.True(t, ret.Rule == "||host2^")
|
assert.True(t, ret.Rule == "||host2^")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CLIENT SETTINGS
|
// CLIENT SETTINGS
|
||||||
@@ -476,7 +496,7 @@ func applyClientSettings(setts *RequestFilteringSettings) {
|
|||||||
// then apply per-client settings and check behaviour once again
|
// then apply per-client settings and check behaviour once again
|
||||||
func TestClientSettings(t *testing.T) {
|
func TestClientSettings(t *testing.T) {
|
||||||
var r Result
|
var r Result
|
||||||
filters := []Filter{Filter{
|
filters := []Filter{{
|
||||||
ID: 0, Data: []byte("||example.org^\n"),
|
ID: 0, Data: []byte("||example.org^\n"),
|
||||||
}}
|
}}
|
||||||
d := NewForTest(&Config{ParentalEnabled: true, SafeBrowsingEnabled: false}, filters)
|
d := NewForTest(&Config{ParentalEnabled: true, SafeBrowsingEnabled: false}, filters)
|
||||||
@@ -532,13 +552,6 @@ func TestClientSettings(t *testing.T) {
|
|||||||
assert.True(t, r.IsFiltered && r.Reason == FilteredBlockedService)
|
assert.True(t, r.IsFiltered && r.Reason == FilteredBlockedService)
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareTestDir() string {
|
|
||||||
const dir = "./agh-test"
|
|
||||||
_ = os.RemoveAll(dir)
|
|
||||||
_ = os.MkdirAll(dir, 0755)
|
|
||||||
return dir
|
|
||||||
}
|
|
||||||
|
|
||||||
// BENCHMARKS
|
// BENCHMARKS
|
||||||
|
|
||||||
func BenchmarkSafeBrowsing(b *testing.B) {
|
func BenchmarkSafeBrowsing(b *testing.B) {
|
||||||
@@ -149,7 +149,6 @@ type rewriteEntryJSON struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
@@ -171,7 +170,6 @@ 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 {
|
||||||
@@ -194,7 +192,6 @@ func (d *Dnsfilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -12,13 +12,13 @@ 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)
|
||||||
@@ -107,9 +107,9 @@ 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()
|
||||||
|
|
||||||
@@ -136,8 +136,8 @@ 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()
|
||||||
|
|
||||||
@@ -156,8 +156,8 @@ 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()
|
||||||
|
|
||||||
@@ -176,11 +176,11 @@ 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,5 +1,152 @@
|
|||||||
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}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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",
|
||||||
@@ -22,11 +22,13 @@ import (
|
|||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dnsTimeout = 3 * time.Second
|
const (
|
||||||
const defaultSafebrowsingServer = "https://dns-family.adguard.com/dns-query"
|
dnsTimeout = 3 * time.Second
|
||||||
const defaultParentalServer = "https://dns-family.adguard.com/dns-query"
|
defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query`
|
||||||
const sbTXTSuffix = "sb.dns.adguard.com."
|
defaultParentalServer = `https://dns-family.adguard.com/dns-query`
|
||||||
const pcTXTSuffix = "pc.dns.adguard.com."
|
sbTXTSuffix = `sb.dns.adguard.com.`
|
||||||
|
pcTXTSuffix = `pc.dns.adguard.com.`
|
||||||
|
)
|
||||||
|
|
||||||
func (d *Dnsfilter) initSecurityServices() error {
|
func (d *Dnsfilter) initSecurityServices() error {
|
||||||
var err error
|
var err error
|
||||||
@@ -60,7 +62,7 @@ expire byte[4]
|
|||||||
hash byte[32]
|
hash byte[32]
|
||||||
...
|
...
|
||||||
*/
|
*/
|
||||||
func (c *sbCtx) setCache(prefix []byte, hashes []byte) {
|
func (c *sbCtx) setCache(prefix, hashes []byte) {
|
||||||
d := make([]byte, 4+len(hashes))
|
d := make([]byte, 4+len(hashes))
|
||||||
expire := uint(time.Now().Unix()) + c.cacheTime*60
|
expire := uint(time.Now().Unix()) + c.cacheTime*60
|
||||||
binary.BigEndian.PutUint32(d[:4], uint32(expire))
|
binary.BigEndian.PutUint32(d[:4], uint32(expire))
|
||||||
@@ -69,31 +71,35 @@ func (c *sbCtx) setCache(prefix []byte, 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,16 +164,28 @@ func hostnameToHashes(host string) map[[32]byte]string {
|
|||||||
|
|
||||||
// convert hash array to string
|
// convert hash array to string
|
||||||
func (c *sbCtx) getQuestion() string {
|
func (c *sbCtx) getQuestion() string {
|
||||||
q := ""
|
b := &strings.Builder{}
|
||||||
|
encoder := hex.NewEncoder(b)
|
||||||
|
|
||||||
for hash := range c.hashToHost {
|
for hash := range c.hashToHost {
|
||||||
q += fmt.Sprintf("%s.", hex.EncodeToString(hash[0:2]))
|
// Ignore errors, since strings.(*Buffer).Write never returns
|
||||||
|
// errors.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov, a.garipov): Find out and document why exactly
|
||||||
|
// this slice.
|
||||||
|
_, _ = encoder.Write(hash[0:2])
|
||||||
|
_, _ = b.WriteRune('.')
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.svc == "SafeBrowsing" {
|
if c.svc == "SafeBrowsing" {
|
||||||
q += sbTXTSuffix
|
// See comment above.
|
||||||
} else {
|
_, _ = b.WriteString(sbTXTSuffix)
|
||||||
q += pcTXTSuffix
|
return b.String()
|
||||||
}
|
}
|
||||||
return q
|
|
||||||
|
// See comment above.
|
||||||
|
_, _ = b.WriteString(pcTXTSuffix)
|
||||||
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the target hash in TXT response
|
// Find the target hash in TXT response
|
||||||
@@ -240,106 +258,71 @@ 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)
|
||||||
|
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) {
|
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 {
|
Rule: "adguard-malware-shavar",
|
||||||
return result, nil
|
|
||||||
} 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
|
|
||||||
// nolint:dupl
|
|
||||||
func (d *Dnsfilter) checkParental(host string) (Result, error) {
|
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 {
|
Rule: "parental CATEGORY_BLACKLISTED",
|
||||||
return result, nil
|
|
||||||
} 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{}) {
|
||||||
@@ -5,7 +5,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||||
"github.com/AdguardTeam/golibs/cache"
|
"github.com/AdguardTeam/golibs/cache"
|
||||||
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,16 +25,16 @@ func TestSafeBrowsingHash(t *testing.T) {
|
|||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
|
||||||
c := &sbCtx{
|
c := &sbCtx{
|
||||||
svc: "SafeBrowsing",
|
svc: "SafeBrowsing",
|
||||||
|
hashToHost: hashes,
|
||||||
}
|
}
|
||||||
|
|
||||||
// test getQuestion()
|
|
||||||
c.hashToHost = hashes
|
|
||||||
q := c.getQuestion()
|
q := c.getQuestion()
|
||||||
assert.True(t, strings.Index(q, "7a1b.") >= 0)
|
|
||||||
assert.True(t, strings.Index(q, "af5a.") >= 0)
|
assert.True(t, strings.Contains(q, "7a1b."))
|
||||||
assert.True(t, strings.Index(q, "eb11.") >= 0)
|
assert.True(t, strings.Contains(q, "af5a."))
|
||||||
assert.True(t, strings.Index(q, "sb.dns.adguard.com.") > 0)
|
assert.True(t, strings.Contains(q, "eb11."))
|
||||||
|
assert.True(t, strings.HasSuffix(q, "sb.dns.adguard.com."))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSafeBrowsingCache(t *testing.T) {
|
func TestSafeBrowsingCache(t *testing.T) {
|
||||||
@@ -88,4 +90,47 @@ func TestSafeBrowsingCache(t *testing.T) {
|
|||||||
hash = sha256.Sum256([]byte("nonexisting.com"))
|
hash = sha256.Sum256([]byte("nonexisting.com"))
|
||||||
_, ok = c.hashToHost[hash]
|
_, ok = c.hashToHost[hash]
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
c = &sbCtx{
|
||||||
|
svc: "SafeBrowsing",
|
||||||
|
cacheTime: 100,
|
||||||
|
}
|
||||||
|
conf = cache.Config{}
|
||||||
|
c.cache = cache.New(conf)
|
||||||
|
|
||||||
|
hash = sha256.Sum256([]byte("sub.host.com"))
|
||||||
|
c.hashToHost = make(map[[32]byte]string)
|
||||||
|
c.hashToHost[hash] = "sub.host.com"
|
||||||
|
|
||||||
|
c.cache.Set(hash[0:2], make([]byte, 32))
|
||||||
|
assert.Equal(t, 0, c.getCached())
|
||||||
|
}
|
||||||
|
|
||||||
|
// testErrUpstream implements upstream.Upstream interface for replacing real
|
||||||
|
// upstream in tests.
|
||||||
|
type testErrUpstream struct{}
|
||||||
|
|
||||||
|
// Exchange always returns nil Msg and non-nil error.
|
||||||
|
func (teu *testErrUpstream) Exchange(*dns.Msg) (*dns.Msg, error) {
|
||||||
|
return nil, agherr.Error("bad")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (teu *testErrUpstream) Address() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSBPC_checkErrorUpstream(t *testing.T) {
|
||||||
|
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||||
|
defer d.Close()
|
||||||
|
|
||||||
|
ups := &testErrUpstream{}
|
||||||
|
|
||||||
|
d.safeBrowsingUpstream = ups
|
||||||
|
d.parentalUpstream = ups
|
||||||
|
|
||||||
|
_, err := d.checkSafeBrowsing("smthng.com")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
_, err = d.checkParental("smthng.com")
|
||||||
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ func (a *accessCtx) Init(allowedClients, disallowedClients, blockedHosts []strin
|
|||||||
listArray = append(listArray, list)
|
listArray = append(listArray, list)
|
||||||
rulesStorage, err := filterlist.NewRuleStorage(listArray)
|
rulesStorage, err := filterlist.NewRuleStorage(listArray)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("filterlist.NewRuleStorage(): %s", err)
|
return fmt.Errorf("filterlist.NewRuleStorage(): %w", err)
|
||||||
}
|
}
|
||||||
a.blockedHostsEngine = urlfilter.NewDNSEngine(rulesStorage)
|
a.blockedHostsEngine = urlfilter.NewDNSEngine(rulesStorage)
|
||||||
|
|
||||||
@@ -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^",
|
||||||
@@ -10,12 +10,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/AdGuardHome/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||||
"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/joomcode/errorx"
|
"github.com/ameshkov/dnscrypt/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FilteringConfig represents the DNS filtering configuration of AdGuard Home
|
// FilteringConfig represents the DNS filtering configuration of AdGuard Home
|
||||||
@@ -76,6 +76,11 @@ type FilteringConfig struct {
|
|||||||
CacheMinTTL uint32 `yaml:"cache_ttl_min"` // override TTL value (minimum) received from upstream server
|
CacheMinTTL uint32 `yaml:"cache_ttl_min"` // override TTL value (minimum) received from upstream server
|
||||||
CacheMaxTTL uint32 `yaml:"cache_ttl_max"` // override TTL value (maximum) received from upstream server
|
CacheMaxTTL uint32 `yaml:"cache_ttl_max"` // override TTL value (maximum) received from upstream server
|
||||||
|
|
||||||
|
// DNS rebinding protection settings
|
||||||
|
// --
|
||||||
|
RebindingProtectionEnabled bool `yaml:"rebinding_protection_enabled"`
|
||||||
|
RebindingAllowedHosts []string `yaml:"rebinding_allowed_hosts"`
|
||||||
|
|
||||||
// Other settings
|
// Other settings
|
||||||
// --
|
// --
|
||||||
|
|
||||||
@@ -95,19 +100,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.
|
||||||
@@ -120,6 +139,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
|
||||||
@@ -185,6 +205,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")
|
||||||
@@ -252,14 +279,14 @@ func (s *Server) prepareUpstreamSettings() error {
|
|||||||
upstreams = filterOutComments(upstreams)
|
upstreams = filterOutComments(upstreams)
|
||||||
upstreamConfig, err := proxy.ParseUpstreamsConfig(upstreams, s.conf.BootstrapDNS, DefaultTimeout)
|
upstreamConfig, err := proxy.ParseUpstreamsConfig(upstreams, s.conf.BootstrapDNS, DefaultTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("DNS: proxy.ParseUpstreamsConfig: %s", err)
|
return fmt.Errorf("dns: proxy.ParseUpstreamsConfig: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(upstreamConfig.Upstreams) == 0 {
|
if len(upstreamConfig.Upstreams) == 0 {
|
||||||
log.Info("Warning: no default upstream servers specified, using %v", defaultDNS)
|
log.Info("Warning: no default upstream servers specified, using %v", defaultDNS)
|
||||||
uc, err := proxy.ParseUpstreamsConfig(defaultDNS, s.conf.BootstrapDNS, DefaultTimeout)
|
uc, err := proxy.ParseUpstreamsConfig(defaultDNS, s.conf.BootstrapDNS, DefaultTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("DNS: failed to parse default upstreams: %v", err)
|
return fmt.Errorf("dns: failed to parse default upstreams: %v", err)
|
||||||
}
|
}
|
||||||
upstreamConfig.Upstreams = uc.Upstreams
|
upstreamConfig.Upstreams = uc.Upstreams
|
||||||
}
|
}
|
||||||
@@ -300,13 +327,13 @@ func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
|
|||||||
var err error
|
var err error
|
||||||
s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
|
s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorx.Decorate(err, "Failed to parse TLS keypair")
|
return fmt.Errorf("failed to parse TLS keypair: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.conf.StrictSNICheck {
|
if s.conf.StrictSNICheck {
|
||||||
x, err := x509.ParseCertificate(s.conf.cert.Certificate[0])
|
x, err := x509.ParseCertificate(s.conf.cert.Certificate[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorx.Decorate(err, "x509.ParseCertificate(): %s", err)
|
return fmt.Errorf("x509.ParseCertificate(): %w", err)
|
||||||
}
|
}
|
||||||
if len(x.DNSNames) != 0 {
|
if len(x.DNSNames) != 0 {
|
||||||
s.conf.dnsNames = x.DNSNames
|
s.conf.dnsNames = x.DNSNames
|
||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/AdGuardHome/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/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@@ -48,6 +48,7 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
|||||||
processFilteringBeforeRequest,
|
processFilteringBeforeRequest,
|
||||||
processUpstream,
|
processUpstream,
|
||||||
processDNSSECAfterResponse,
|
processDNSSECAfterResponse,
|
||||||
|
processRebindingFilteringAfterResponse,
|
||||||
processFilteringAfterResponse,
|
processFilteringAfterResponse,
|
||||||
s.ipset.process,
|
s.ipset.process,
|
||||||
processQueryLogsAndStats,
|
processQueryLogsAndStats,
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Package dnsforward contains a DNS forwarding server.
|
||||||
package dnsforward
|
package dnsforward
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -8,13 +9,12 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/AdGuardHome/querylog"
|
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||||
"github.com/AdguardTeam/AdGuardHome/stats"
|
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/joomcode/errorx"
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,6 +53,7 @@ type Server struct {
|
|||||||
queryLog querylog.QueryLog // Query log instance
|
queryLog querylog.QueryLog // Query log instance
|
||||||
stats stats.Stats
|
stats stats.Stats
|
||||||
access *accessCtx
|
access *accessCtx
|
||||||
|
rebinding *dnsRebindChecker
|
||||||
|
|
||||||
ipset ipsetCtx
|
ipset ipsetCtx
|
||||||
|
|
||||||
@@ -122,6 +123,7 @@ func (s *Server) WriteDiskConfig(c *FilteringConfig) {
|
|||||||
c.DisallowedClients = stringArrayDup(sc.DisallowedClients)
|
c.DisallowedClients = stringArrayDup(sc.DisallowedClients)
|
||||||
c.BlockedHosts = stringArrayDup(sc.BlockedHosts)
|
c.BlockedHosts = stringArrayDup(sc.BlockedHosts)
|
||||||
c.UpstreamDNS = stringArrayDup(sc.UpstreamDNS)
|
c.UpstreamDNS = stringArrayDup(sc.UpstreamDNS)
|
||||||
|
c.RebindingAllowedHosts = stringArrayDup(sc.RebindingAllowedHosts)
|
||||||
s.RUnlock()
|
s.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,12 +183,9 @@ func (s *Server) Prepare(config *ServerConfig) error {
|
|||||||
s.conf.BlockingIPAddrv4 = net.ParseIP(s.conf.BlockingIPv4)
|
s.conf.BlockingIPAddrv4 = net.ParseIP(s.conf.BlockingIPv4)
|
||||||
s.conf.BlockingIPAddrv6 = net.ParseIP(s.conf.BlockingIPv6)
|
s.conf.BlockingIPAddrv6 = net.ParseIP(s.conf.BlockingIPv6)
|
||||||
if s.conf.BlockingIPAddrv4 == nil || s.conf.BlockingIPAddrv6 == nil {
|
if s.conf.BlockingIPAddrv4 == nil || s.conf.BlockingIPAddrv6 == nil {
|
||||||
return fmt.Errorf("DNS: invalid custom blocking IP address specified")
|
return fmt.Errorf("dns: invalid custom blocking IP address specified")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.conf.MaxGoroutines == 0 {
|
|
||||||
s.conf.MaxGoroutines = 50
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set default values in the case if nothing is configured
|
// Set default values in the case if nothing is configured
|
||||||
@@ -224,6 +223,13 @@ func (s *Server) Prepare(config *ServerConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize DNS rebinding module
|
||||||
|
// --
|
||||||
|
s.rebinding, err = newRebindChecker(s.conf.RebindingAllowedHosts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Register web handlers if necessary
|
// Register web handlers if necessary
|
||||||
// --
|
// --
|
||||||
if !webRegistered && s.conf.HTTPRegister != nil {
|
if !webRegistered && s.conf.HTTPRegister != nil {
|
||||||
@@ -249,7 +255,7 @@ func (s *Server) stopInternal() error {
|
|||||||
if s.dnsProxy != nil {
|
if s.dnsProxy != nil {
|
||||||
err := s.dnsProxy.Stop()
|
err := s.dnsProxy.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorx.Decorate(err, "could not stop the DNS server properly")
|
return fmt.Errorf("could not stop the DNS server properly: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +278,7 @@ func (s *Server) Reconfigure(config *ServerConfig) error {
|
|||||||
log.Print("Start reconfiguring the server")
|
log.Print("Start reconfiguring the server")
|
||||||
err := s.stopInternal()
|
err := s.stopInternal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorx.Decorate(err, "could not reconfigure the server")
|
return fmt.Errorf("could not reconfigure the server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
||||||
@@ -281,12 +287,12 @@ func (s *Server) Reconfigure(config *ServerConfig) error {
|
|||||||
|
|
||||||
err = s.Prepare(config)
|
err = s.Prepare(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorx.Decorate(err, "could not reconfigure the server")
|
return fmt.Errorf("could not reconfigure the server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.startInternal()
|
err = s.startInternal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorx.Decorate(err, "could not reconfigure the server")
|
return fmt.Errorf("could not reconfigure the server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -18,16 +18,21 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/testutil"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
testutil.DiscardLogOutput(m)
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tlsServerName = "testdns.adguard.com"
|
tlsServerName = "testdns.adguard.com"
|
||||||
testMessagesCount = 10
|
testMessagesCount = 10
|
||||||
@@ -733,6 +738,100 @@ func TestRewrite(t *testing.T) {
|
|||||||
_ = s.Stop()
|
_ = s.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBlockedDNSRebinding(t *testing.T) {
|
||||||
|
s := createTestServer(t)
|
||||||
|
|
||||||
|
err := s.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to start server: %s", err)
|
||||||
|
}
|
||||||
|
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||||
|
|
||||||
|
//
|
||||||
|
// DNS rebinding protection
|
||||||
|
//
|
||||||
|
req := dns.Msg{}
|
||||||
|
req.Id = dns.Id()
|
||||||
|
req.RecursionDesired = true
|
||||||
|
req.Question = []dns.Question{
|
||||||
|
{Name: "192-168-1-250.nip.io.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
s.conf.RebindingProtectionEnabled = true
|
||||||
|
s.Unlock()
|
||||||
|
|
||||||
|
reply, err := dns.Exchange(&req, addr.String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't talk to server %s: %s", addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reply.Answer) != 1 {
|
||||||
|
t.Fatalf("DNS server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer))
|
||||||
|
}
|
||||||
|
|
||||||
|
a, ok := reply.Answer[0].(*dns.A)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("DNS server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !net.IPv4zero.Equal(a.A) {
|
||||||
|
t.Fatalf("DNS server %s returned wrong answer instead of 0.0.0.0: %v", addr, a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
s.conf.RebindingProtectionEnabled = false
|
||||||
|
s.Unlock()
|
||||||
|
|
||||||
|
reply, err = dns.Exchange(&req, addr.String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't talk to server %s: %s", addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reply.Answer) != 1 {
|
||||||
|
t.Fatalf("DNS server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer))
|
||||||
|
}
|
||||||
|
|
||||||
|
a, ok = reply.Answer[0].(*dns.A)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("DNS server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !net.IPv4(192, 168, 1, 250).Equal(a.A) {
|
||||||
|
t.Fatalf("DNS server %s returned wrong answer instead of 192.168.1.250: %v", addr, a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
s.conf.RebindingProtectionEnabled = true
|
||||||
|
s.rebinding, _ = newRebindChecker([]string{
|
||||||
|
"||nip.io^",
|
||||||
|
})
|
||||||
|
s.Unlock()
|
||||||
|
|
||||||
|
reply, err = dns.Exchange(&req, addr.String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't talk to server %s: %s", addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reply.Answer) != 1 {
|
||||||
|
t.Fatalf("DNS server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer))
|
||||||
|
}
|
||||||
|
|
||||||
|
a, ok = reply.Answer[0].(*dns.A)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("DNS server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !net.IPv4(192, 168, 1, 250).Equal(a.A) {
|
||||||
|
t.Fatalf("DNS server %s returned wrong answer instead of 192.168.1.250: %v", addr, a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.Stop()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DNS server failed to stop: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createTestServer(t *testing.T) *Server {
|
func createTestServer(t *testing.T) *Server {
|
||||||
rules := `||nxdomain.example.org
|
rules := `||nxdomain.example.org
|
||||||
||null.example.org^
|
||null.example.org^
|
||||||
@@ -751,11 +850,14 @@ func createTestServer(t *testing.T) *Server {
|
|||||||
c.CacheTime = 30
|
c.CacheTime = 30
|
||||||
|
|
||||||
f := dnsfilter.New(&c, filters)
|
f := dnsfilter.New(&c, filters)
|
||||||
|
|
||||||
s := NewServer(DNSCreateParams{DNSFilter: f})
|
s := NewServer(DNSCreateParams{DNSFilter: f})
|
||||||
s.conf.UDPListenAddr = &net.UDPAddr{Port: 0}
|
s.conf.UDPListenAddr = &net.UDPAddr{Port: 0}
|
||||||
s.conf.TCPListenAddr = &net.TCPAddr{Port: 0}
|
s.conf.TCPListenAddr = &net.TCPAddr{Port: 0}
|
||||||
s.conf.UpstreamDNS = []string{"8.8.8.8:53", "8.8.4.4:53"}
|
s.conf.UpstreamDNS = []string{"8.8.8.8:53", "8.8.4.4:53"}
|
||||||
s.conf.FilteringConfig.ProtectionEnabled = true
|
s.conf.FilteringConfig.ProtectionEnabled = true
|
||||||
|
s.conf.ConfigModified = func() {}
|
||||||
|
|
||||||
err := s.Prepare(nil)
|
err := s.Prepare(nil)
|
||||||
assert.True(t, err == nil)
|
assert.True(t, err == nil)
|
||||||
return s
|
return s
|
||||||
@@ -815,12 +917,12 @@ func sendTestMessageAsync(t *testing.T, conn *dns.Conn, g *sync.WaitGroup) {
|
|||||||
req := createGoogleATestMessage()
|
req := createGoogleATestMessage()
|
||||||
err := conn.WriteMsg(req)
|
err := conn.WriteMsg(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("cannot write message: %s", err)
|
panic(fmt.Sprintf("cannot write message: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := conn.ReadMsg()
|
res, err := conn.ReadMsg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("cannot read response to message: %s", err)
|
panic(fmt.Sprintf("cannot read response to message: %s", err))
|
||||||
}
|
}
|
||||||
assertGoogleAResponse(t, res)
|
assertGoogleAResponse(t, res)
|
||||||
}
|
}
|
||||||
@@ -916,20 +1018,23 @@ func publicKey(priv interface{}) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateUpstream(t *testing.T) {
|
func TestValidateUpstream(t *testing.T) {
|
||||||
invalidUpstreams := []string{"1.2.3.4.5",
|
invalidUpstreams := []string{
|
||||||
|
"1.2.3.4.5",
|
||||||
"123.3.7m",
|
"123.3.7m",
|
||||||
"htttps://google.com/dns-query",
|
"htttps://google.com/dns-query",
|
||||||
"[/host.com]tls://dns.adguard.com",
|
"[/host.com]tls://dns.adguard.com",
|
||||||
"[host.ru]#",
|
"[host.ru]#",
|
||||||
}
|
}
|
||||||
|
|
||||||
validDefaultUpstreams := []string{"1.1.1.1",
|
validDefaultUpstreams := []string{
|
||||||
|
"1.1.1.1",
|
||||||
"tls://1.1.1.1",
|
"tls://1.1.1.1",
|
||||||
"https://dns.adguard.com/dns-query",
|
"https://dns.adguard.com/dns-query",
|
||||||
"sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
"sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||||
}
|
}
|
||||||
|
|
||||||
validUpstreams := []string{"[/host.com/]1.1.1.1",
|
validUpstreams := []string{
|
||||||
|
"[/host.com/]1.1.1.1",
|
||||||
"[//]tls://1.1.1.1",
|
"[//]tls://1.1.1.1",
|
||||||
"[/www.host.com/]#",
|
"[/www.host.com/]#",
|
||||||
"[/host.com/google.com/]8.8.8.8",
|
"[/host.com/google.com/]8.8.8.8",
|
||||||
@@ -975,7 +1080,8 @@ func TestValidateUpstreamsSet(t *testing.T) {
|
|||||||
assert.Nil(t, err, "comments should not be validated")
|
assert.Nil(t, err, "comments should not be validated")
|
||||||
|
|
||||||
// Set of valid upstreams. There is no default upstream specified
|
// Set of valid upstreams. There is no default upstream specified
|
||||||
upstreamsSet = []string{"[/host.com/]1.1.1.1",
|
upstreamsSet = []string{
|
||||||
|
"[/host.com/]1.1.1.1",
|
||||||
"[//]tls://1.1.1.1",
|
"[//]tls://1.1.1.1",
|
||||||
"[/www.host.com/]#",
|
"[/www.host.com/]#",
|
||||||
"[/host.com/google.com/]8.8.8.8",
|
"[/host.com/google.com/]8.8.8.8",
|
||||||
@@ -1029,9 +1135,7 @@ func (d *testDHCP) Leases(flags int) []dhcpd.Lease {
|
|||||||
l.Hostname = "localhost"
|
l.Hostname = "localhost"
|
||||||
return []dhcpd.Lease{l}
|
return []dhcpd.Lease{l}
|
||||||
}
|
}
|
||||||
func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {
|
func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||||
dhcp := &testDHCP{}
|
dhcp := &testDHCP{}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
package dnsforward
|
package dnsforward
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/joomcode/errorx"
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,29 +50,32 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
|||||||
res, err := s.dnsFilter.CheckHost(host, d.Req.Question[0].Qtype, ctx.setts)
|
res, err := s.dnsFilter.CheckHost(host, d.Req.Question[0].Qtype, ctx.setts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Return immediately if there's an error
|
// Return immediately if there's an error
|
||||||
return nil, errorx.Decorate(err, "dnsfilter failed to check host '%s'", host)
|
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 - '%s', matched rule: '%s'", host, res.Reason, res.Rule)
|
log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rule)
|
||||||
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 == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 {
|
||||||
ctx.origQuestion = d.Req.Question[0]
|
ctx.origQuestion = d.Req.Question[0]
|
||||||
// resolve canonical name, not the original host name
|
// 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.RewriteEtcHosts && len(res.ReverseHost) != 0 {
|
|
||||||
|
|
||||||
resp := s.makeResponse(req)
|
resp := s.makeResponse(req)
|
||||||
ptr := &dns.PTR{}
|
for _, h := range res.ReverseHosts {
|
||||||
ptr.Hdr = dns.RR_Header{
|
hdr := dns.RR_Header{
|
||||||
Name: req.Question[0].Name,
|
Name: req.Question[0].Name,
|
||||||
Rrtype: dns.TypePTR,
|
Rrtype: dns.TypePTR,
|
||||||
Ttl: s.conf.BlockedResponseTTL,
|
Ttl: s.conf.BlockedResponseTTL,
|
||||||
Class: dns.ClassINET,
|
Class: dns.ClassINET,
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := &dns.PTR{
|
||||||
|
Hdr: hdr,
|
||||||
|
Ptr: h,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Answer = append(resp.Answer, ptr)
|
||||||
}
|
}
|
||||||
ptr.Ptr = res.ReverseHost
|
|
||||||
resp.Answer = append(resp.Answer, ptr)
|
|
||||||
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.RewriteEtcHosts {
|
||||||
resp := s.makeResponse(req)
|
resp := s.makeResponse(req)
|
||||||
@@ -109,8 +113,8 @@ func (s *Server) filterDNSResponse(ctx *dnsContext) (*dnsfilter.Result, error) {
|
|||||||
|
|
||||||
switch v := a.(type) {
|
switch v := a.(type) {
|
||||||
case *dns.CNAME:
|
case *dns.CNAME:
|
||||||
log.Debug("DNSFwd: Checking CNAME %s for %s", v.Target, v.Hdr.Name)
|
|
||||||
host = strings.TrimSuffix(v.Target, ".")
|
host = strings.TrimSuffix(v.Target, ".")
|
||||||
|
log.Debug("DNSFwd: Checking CNAME %s for %s", v.Target, v.Hdr.Name)
|
||||||
|
|
||||||
case *dns.A:
|
case *dns.A:
|
||||||
host = v.A.String()
|
host = v.A.String()
|
||||||
@@ -136,7 +140,6 @@ func (s *Server) filterDNSResponse(ctx *dnsContext) (*dnsfilter.Result, error) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
||||||
} else if res.IsFiltered {
|
} else if res.IsFiltered {
|
||||||
d.Res = s.genDNSFilterMessage(d, &res)
|
d.Res = s.genDNSFilterMessage(d, &res)
|
||||||
log.Debug("DNSFwd: Matched %s by response: %s", d.Req.Question[0].Name, host)
|
log.Debug("DNSFwd: Matched %s by response: %s", d.Req.Question[0].Name, host)
|
||||||
@@ -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"
|
||||||
@@ -17,236 +16,305 @@ import (
|
|||||||
|
|
||||||
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{}) {
|
||||||
text := fmt.Sprintf(format, args...)
|
text := fmt.Sprintf(format, args...)
|
||||||
log.Info("DNS: %s %s: %s", r.Method, r.URL, text)
|
log.Info("dns: %s %s: %s", r.Method, r.URL, text)
|
||||||
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"`
|
||||||
|
|
||||||
|
RebindingProtectionEnabled *bool `json:"rebinding_protection_enabled"`
|
||||||
|
RebindingAllowedHosts *[]string `json:"rebinding_allowed_hosts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
rebindingEnabled := s.conf.RebindingProtectionEnabled
|
||||||
|
rebindingAllowedHosts := stringArrayDup(s.conf.RebindingAllowedHosts)
|
||||||
|
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,
|
||||||
|
RebindingProtectionEnabled: &rebindingEnabled,
|
||||||
|
RebindingAllowedHosts: &rebindingAllowedHosts,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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: %s", 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restart = restart || s.setRebindingConfig(dc)
|
||||||
|
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
|
||||||
@@ -322,7 +390,7 @@ func separateUpstream(upstream string) (string, bool, error) {
|
|||||||
// split domains and upstream string
|
// split domains and upstream string
|
||||||
domainsAndUpstream := strings.Split(strings.TrimPrefix(upstream, "[/"), "/]")
|
domainsAndUpstream := strings.Split(strings.TrimPrefix(upstream, "[/"), "/]")
|
||||||
if len(domainsAndUpstream) != 2 {
|
if len(domainsAndUpstream) != 2 {
|
||||||
return "", defaultUpstream, fmt.Errorf("wrong DNS upstream per domain specification: %s", upstream)
|
return "", defaultUpstream, fmt.Errorf("wrong dns upstream per domain specification: %s", upstream)
|
||||||
}
|
}
|
||||||
|
|
||||||
// split domains list and validate each one
|
// split domains list and validate each one
|
||||||
@@ -357,7 +425,7 @@ func checkPlainDNS(upstream string) error {
|
|||||||
|
|
||||||
_, err = strconv.ParseInt(port, 0, 64)
|
_, err = strconv.ParseInt(port, 0, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s is not a valid port: %s", port, err)
|
return fmt.Errorf("%s is not a valid port: %w", port, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -405,7 +473,7 @@ func checkDNS(input string, bootstrap []string) error {
|
|||||||
// separate upstream from domains list
|
// separate upstream from domains list
|
||||||
input, defaultUpstream, err := separateUpstream(input)
|
input, defaultUpstream, err := separateUpstream(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wrong upstream format: %s", err)
|
return fmt.Errorf("wrong upstream format: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to check this DNS server
|
// No need to check this DNS server
|
||||||
@@ -414,17 +482,17 @@ func checkDNS(input string, bootstrap []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err := validateUpstream(input); err != nil {
|
if _, err := validateUpstream(input); err != nil {
|
||||||
return fmt.Errorf("wrong upstream format: %s", err)
|
return fmt.Errorf("wrong upstream format: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(bootstrap) == 0 {
|
if len(bootstrap) == 0 {
|
||||||
bootstrap = defaultBootstrap
|
bootstrap = defaultBootstrap
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("Checking if DNS %s works...", input)
|
log.Debug("checking if dns %s works...", input)
|
||||||
u, err := upstream.AddressToUpstream(input, upstream.Options{Bootstrap: bootstrap, Timeout: DefaultTimeout})
|
u, err := upstream.AddressToUpstream(input, upstream.Options{Bootstrap: bootstrap, Timeout: DefaultTimeout})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to choose upstream for %s: %s", input, err)
|
return fmt.Errorf("failed to choose upstream for %s: %w", input, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req := dns.Msg{}
|
req := dns.Msg{}
|
||||||
@@ -435,18 +503,18 @@ func checkDNS(input string, bootstrap []string) error {
|
|||||||
}
|
}
|
||||||
reply, err := u.Exchange(&req)
|
reply, err := u.Exchange(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("couldn't communicate with DNS server %s: %s", input, err)
|
return fmt.Errorf("couldn't communicate with dns server %s: %w", input, err)
|
||||||
}
|
}
|
||||||
if len(reply.Answer) != 1 {
|
if len(reply.Answer) != 1 {
|
||||||
return fmt.Errorf("DNS server %s returned wrong answer", input)
|
return fmt.Errorf("dns server %s returned wrong answer", input)
|
||||||
}
|
}
|
||||||
if t, ok := reply.Answer[0].(*dns.A); ok {
|
if t, ok := reply.Answer[0].(*dns.A); ok {
|
||||||
if !net.IPv4(8, 8, 8, 8).Equal(t.A) {
|
if !net.IPv4(8, 8, 8, 8).Equal(t.A) {
|
||||||
return fmt.Errorf("DNS server %s returned wrong answer: %v", input, t.A)
|
return fmt.Errorf("dns server %s returned wrong answer: %v", input, t.A)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("DNS %s works OK", input)
|
log.Debug("dns %s works OK", input)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +530,7 @@ func (s *Server) handleDOH(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !s.IsRunning() {
|
if !s.IsRunning() {
|
||||||
httpError(r, w, http.StatusInternalServerError, "DNS server is not running")
|
httpError(r, w, http.StatusInternalServerError, "dns server is not running")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
170
internal/dnsforward/http_test.go
Normal file
170
internal/dnsforward/http_test.go
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
package dnsforward
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||||
|
s := createTestServer(t)
|
||||||
|
err := s.Start()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer assert.Nil(t, s.Stop())
|
||||||
|
|
||||||
|
defaultConf := s.conf
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
conf func() ServerConfig
|
||||||
|
want string
|
||||||
|
}{{
|
||||||
|
name: "all_right",
|
||||||
|
conf: func() ServerConfig {
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}, {
|
||||||
|
name: "fastest_addr",
|
||||||
|
conf: func() ServerConfig {
|
||||||
|
conf := defaultConf
|
||||||
|
conf.FastestAddr = true
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}, {
|
||||||
|
name: "parallel",
|
||||||
|
conf: func() ServerConfig {
|
||||||
|
conf := defaultConf
|
||||||
|
conf.AllServers = true
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
s.conf = tc.conf()
|
||||||
|
s.handleGetConfig(w, nil)
|
||||||
|
assert.Equal(t, tc.want, w.Body.String())
|
||||||
|
|
||||||
|
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
|
||||||
|
})
|
||||||
|
w.Body.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) {
|
||||||
|
s := createTestServer(t)
|
||||||
|
|
||||||
|
defaultConf := s.conf
|
||||||
|
|
||||||
|
err := s.Start()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer func() {
|
||||||
|
assert.Nil(t, s.Stop())
|
||||||
|
}()
|
||||||
|
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n"
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
req string
|
||||||
|
wantSet string
|
||||||
|
wantGet string
|
||||||
|
}{{
|
||||||
|
name: "upstream_dns",
|
||||||
|
req: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"]}",
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}, {
|
||||||
|
name: "bootstraps",
|
||||||
|
req: "{\"bootstrap_dns\":[\"9.9.9.10\"]}",
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}, {
|
||||||
|
name: "blocking_mode_good",
|
||||||
|
req: "{\"blocking_mode\":\"refused\"}",
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}, {
|
||||||
|
name: "blocking_mode_bad",
|
||||||
|
req: "{\"blocking_mode\":\"custom_ip\"}",
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}, {
|
||||||
|
name: "ratelimit",
|
||||||
|
req: "{\"ratelimit\":6}",
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}, {
|
||||||
|
name: "edns_cs_enabled",
|
||||||
|
req: "{\"edns_cs_enabled\":true}",
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}, {
|
||||||
|
name: "dnssec_enabled",
|
||||||
|
req: "{\"dnssec_enabled\":true}",
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}, {
|
||||||
|
name: "cache_size",
|
||||||
|
req: "{\"cache_size\":1024}",
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}, {
|
||||||
|
name: "upstream_mode_parallel",
|
||||||
|
req: "{\"upstream_mode\":\"parallel\"}",
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}, {
|
||||||
|
name: "upstream_mode_fastest_addr",
|
||||||
|
req: "{\"upstream_mode\":\"fastest_addr\"}",
|
||||||
|
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,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||||
|
}, {
|
||||||
|
name: "upstream_dns_bad",
|
||||||
|
req: "{\"upstream_dns\":[\"\"]}",
|
||||||
|
wantSet: "wrong upstreams specification: missing port in address\n",
|
||||||
|
wantGet: defaultConfJSON,
|
||||||
|
}, {
|
||||||
|
name: "bootstraps_bad",
|
||||||
|
req: "{\"bootstrap_dns\":[\"a\"]}",
|
||||||
|
wantSet: "a can not be used as bootstrap dns cause: invalid bootstrap server address: Resolver a is not eligible to be a bootstrap DNS server\n",
|
||||||
|
wantGet: defaultConfJSON,
|
||||||
|
}, {
|
||||||
|
name: "cache_bad_ttl",
|
||||||
|
req: "{\"cache_ttl_min\":1024,\"cache_ttl_max\":512}",
|
||||||
|
wantSet: "cache_ttl_min must be less or equal than cache_ttl_max\n",
|
||||||
|
wantGet: defaultConfJSON,
|
||||||
|
}, {
|
||||||
|
name: "upstream_mode_bad",
|
||||||
|
req: "{\"upstream_mode\":\"somethingelse\"}",
|
||||||
|
wantSet: "upstream_mode: incorrect value\n",
|
||||||
|
wantGet: defaultConfJSON,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
rBody := ioutil.NopCloser(strings.NewReader(tc.req))
|
||||||
|
r, err := http.NewRequest(http.MethodPost, "http://example.com", rBody)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
s.handleSetConfig(w, r)
|
||||||
|
assert.Equal(t, tc.wantSet, w.Body.String())
|
||||||
|
w.Body.Reset()
|
||||||
|
|
||||||
|
s.handleGetConfig(w, nil)
|
||||||
|
assert.Equal(t, tc.wantGet, w.Body.String())
|
||||||
|
w.Body.Reset()
|
||||||
|
})
|
||||||
|
s.conf = defaultConf
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/util"
|
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -31,13 +31,13 @@ func (c *ipsetCtx) init(ipsetConfig []string) {
|
|||||||
it = strings.TrimSpace(it)
|
it = strings.TrimSpace(it)
|
||||||
hostsAndNames := strings.Split(it, "/")
|
hostsAndNames := strings.Split(it, "/")
|
||||||
if len(hostsAndNames) != 2 {
|
if len(hostsAndNames) != 2 {
|
||||||
log.Debug("IPSET: invalid value '%s'", it)
|
log.Debug("IPSET: invalid value %q", it)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ipsetNames := strings.Split(hostsAndNames[1], ",")
|
ipsetNames := strings.Split(hostsAndNames[1], ",")
|
||||||
if len(ipsetNames) == 0 {
|
if len(ipsetNames) == 0 {
|
||||||
log.Debug("IPSET: invalid value '%s'", it)
|
log.Debug("IPSET: invalid value %q", it)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bad := false
|
bad := false
|
||||||
@@ -49,7 +49,7 @@ func (c *ipsetCtx) init(ipsetConfig []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if bad {
|
if bad {
|
||||||
log.Debug("IPSET: invalid value '%s'", it)
|
log.Debug("IPSET: invalid value %q", it)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ func (c *ipsetCtx) init(ipsetConfig []string) {
|
|||||||
host = strings.TrimSpace(host)
|
host = strings.TrimSpace(host)
|
||||||
host = strings.ToLower(host)
|
host = strings.ToLower(host)
|
||||||
if len(host) == 0 {
|
if len(host) == 0 {
|
||||||
log.Debug("IPSET: invalid value '%s'", it)
|
log.Debug("IPSET: invalid value %q", it)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.ipsetList[host] = ipsetNames
|
c.ipsetList[host] = ipsetNames
|
||||||
@@ -131,7 +131,7 @@ func (c *ipsetCtx) process(ctx *dnsContext) int {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
log.Info("IPSET: ipset add: code:%d output:'%s'", code, out)
|
log.Info("IPSET: ipset add: code:%d output:%q", code, out)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Debug("IPSET: added %s(%s) -> %s", host, ipStr, name)
|
log.Debug("IPSET: added %s(%s) -> %s", host, ipStr, name)
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package dnsforward
|
package dnsforward
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,7 +46,6 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
|
|||||||
if s.conf.BlockingMode == "null_ip" {
|
if s.conf.BlockingMode == "null_ip" {
|
||||||
// it means that we should return 0.0.0.0 or :: for any blocked request
|
// it means that we should return 0.0.0.0 or :: for any blocked request
|
||||||
return s.makeResponseNullIP(m)
|
return s.makeResponseNullIP(m)
|
||||||
|
|
||||||
} else if s.conf.BlockingMode == "custom_ip" {
|
} else if s.conf.BlockingMode == "custom_ip" {
|
||||||
// means that we should return custom IP for any blocked request
|
// means that we should return custom IP for any blocked request
|
||||||
|
|
||||||
@@ -56,12 +55,10 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
|
|||||||
case dns.TypeAAAA:
|
case dns.TypeAAAA:
|
||||||
return s.genAAAARecord(m, s.conf.BlockingIPAddrv6)
|
return s.genAAAARecord(m, s.conf.BlockingIPAddrv6)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if s.conf.BlockingMode == "nxdomain" {
|
} else if s.conf.BlockingMode == "nxdomain" {
|
||||||
// means that we should return NXDOMAIN for any blocked request
|
// means that we should return NXDOMAIN for any blocked request
|
||||||
|
|
||||||
return s.genNXDomain(m)
|
return s.genNXDomain(m)
|
||||||
|
|
||||||
} else if s.conf.BlockingMode == "refused" {
|
} else if s.conf.BlockingMode == "refused" {
|
||||||
// means that we should return NXDOMAIN for any blocked request
|
// means that we should return NXDOMAIN for any blocked request
|
||||||
|
|
||||||
@@ -148,7 +145,6 @@ func (s *Server) makeResponseNullIP(req *dns.Msg) *dns.Msg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSContext) *dns.Msg {
|
func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSContext) *dns.Msg {
|
||||||
|
|
||||||
ip := net.ParseIP(newAddr)
|
ip := net.ParseIP(newAddr)
|
||||||
if ip != nil {
|
if ip != nil {
|
||||||
return s.genResponseWithIP(request, ip)
|
return s.genResponseWithIP(request, ip)
|
||||||
@@ -168,7 +164,7 @@ func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSCo
|
|||||||
|
|
||||||
err := s.dnsProxy.Resolve(newContext)
|
err := s.dnsProxy.Resolve(newContext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Couldn't look up replacement host '%s': %s", newAddr, err)
|
log.Printf("Couldn't look up replacement host %q: %s", newAddr, err)
|
||||||
return s.genServerFailure(request)
|
return s.genServerFailure(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
227
internal/dnsforward/rebind.go
Normal file
227
internal/dnsforward/rebind.go
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
// DNS Rebinding protection
|
||||||
|
|
||||||
|
package dnsforward
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/urlfilter"
|
||||||
|
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dnsRebindChecker struct {
|
||||||
|
allowDomainEngine *urlfilter.DNSEngine
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRebindChecker(allowedHosts []string) (*dnsRebindChecker, error) {
|
||||||
|
buf := strings.Builder{}
|
||||||
|
for _, s := range allowedHosts {
|
||||||
|
buf.WriteString(s)
|
||||||
|
buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
rulesStorage, err := filterlist.NewRuleStorage([]filterlist.RuleList{
|
||||||
|
&filterlist.StringRuleList{
|
||||||
|
ID: int(0),
|
||||||
|
RulesText: buf.String(),
|
||||||
|
IgnoreCosmetic: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dnsRebindChecker{
|
||||||
|
allowDomainEngine: urlfilter.NewDNSEngine(rulesStorage),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dnsRebindChecker) isAllowedDomain(domain string) bool {
|
||||||
|
_, ok := c.allowDomainEngine.Match(domain)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPrivate reports whether ip is a private address, according to
|
||||||
|
// RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses).
|
||||||
|
func (*dnsRebindChecker) isPrivate(ip net.IP) bool {
|
||||||
|
//TODO: remove once https://github.com/golang/go/pull/42793 makes it to stdlib
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
return ip4[0] == 10 ||
|
||||||
|
(ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
|
||||||
|
(ip4[0] == 192 && ip4[1] == 168)
|
||||||
|
}
|
||||||
|
return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dnsRebindChecker) isRebindHost(host string) bool {
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
return c.isRebindIP(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return host == "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dnsRebindChecker) isLocalNetworkV4(ip4 net.IP) bool {
|
||||||
|
switch {
|
||||||
|
case ip4[0] == 0:
|
||||||
|
/* 0.0.0.0/8 (RFC 5735 section 3. "here" network) */
|
||||||
|
case ip4[0] == 10:
|
||||||
|
/* 10.0.0.0/8 (private) */
|
||||||
|
case ip4[0] == 172 && ip4[1]&0x10 == 0x10:
|
||||||
|
/* 172.16.0.0/12 (private) */
|
||||||
|
case ip4[0] == 169 && ip4[1] == 254:
|
||||||
|
/* 169.254.0.0/16 (zeroconf) */
|
||||||
|
case ip4[0] == 192 && ip4[1] == 0 && ip4[2] == 2:
|
||||||
|
/* 192.0.2.0/24 (test-net) */
|
||||||
|
case ip4[0] == 198 && ip4[1] == 51 && ip4[2] == 100:
|
||||||
|
/* 198.51.100.0/24(test-net) */
|
||||||
|
case ip4[0] == 203 && ip4[1] == 0 && ip4[2] == 113:
|
||||||
|
/* 203.0.113.0/24 (test-net) */
|
||||||
|
case ip4.Equal(net.IPv4bcast):
|
||||||
|
/* 255.255.255.255/32 (broadcast)*/
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dnsRebindChecker) isLocalNetworkV6(ip6 net.IP) bool {
|
||||||
|
return ip6.Equal(net.IPv6zero) ||
|
||||||
|
ip6.Equal(net.IPv6unspecified) ||
|
||||||
|
ip6.Equal(net.IPv6interfacelocalallnodes) ||
|
||||||
|
ip6.Equal(net.IPv6linklocalallnodes) ||
|
||||||
|
ip6.Equal(net.IPv6linklocalallrouters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dnsRebindChecker) isRebindIP(ip net.IP) bool {
|
||||||
|
// This is compatible with dnsmasq definition
|
||||||
|
// See: https://github.com/imp/dnsmasq/blob/4e7694d7107d2299f4aaededf8917fceb5dfb924/src/rfc1035.c#L412
|
||||||
|
|
||||||
|
rebind := false
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
rebind = c.isLocalNetworkV4(ip4)
|
||||||
|
} else {
|
||||||
|
rebind = c.isLocalNetworkV6(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rebind || c.isPrivate(ip) || ip.IsLoopback()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks DNS rebinding attacks
|
||||||
|
// Note both whitelisted and cached hosts will bypass rebinding check (see: processFilteringAfterResponse()).
|
||||||
|
func (s *Server) isResponseRebind(domain, host string) bool {
|
||||||
|
if !s.conf.RebindingProtectionEnabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if log.GetLevel() >= log.DEBUG {
|
||||||
|
timer := log.StartTimer()
|
||||||
|
defer timer.LogElapsed("DNS Rebinding check for %s -> %s", domain, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.rebinding.isAllowedDomain(domain) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.rebinding.isRebindHost(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func processRebindingFilteringAfterResponse(ctx *dnsContext) int {
|
||||||
|
s := ctx.srv
|
||||||
|
d := ctx.proxyCtx
|
||||||
|
res := ctx.result
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if !ctx.responseFromUpstream || res.Reason == dnsfilter.ReasonRewrite {
|
||||||
|
return resultDone
|
||||||
|
}
|
||||||
|
|
||||||
|
originalRes := d.Res
|
||||||
|
ctx.result, err = s.preventRebindResponse(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.err = err
|
||||||
|
return resultError
|
||||||
|
}
|
||||||
|
if ctx.result != nil {
|
||||||
|
ctx.origResp = originalRes // matched by response
|
||||||
|
} else {
|
||||||
|
ctx.result = &dnsfilter.Result{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) setRebindingConfig(dc dnsConfig) bool {
|
||||||
|
restart := false
|
||||||
|
|
||||||
|
if dc.RebindingProtectionEnabled != nil {
|
||||||
|
s.conf.RebindingProtectionEnabled = *dc.RebindingProtectionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.RebindingAllowedHosts != nil {
|
||||||
|
s.conf.RebindingAllowedHosts = *dc.RebindingAllowedHosts
|
||||||
|
restart = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return restart
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) preventRebindResponse(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||||
|
d := ctx.proxyCtx
|
||||||
|
|
||||||
|
for _, a := range d.Res.Answer {
|
||||||
|
m := ""
|
||||||
|
domainName := ""
|
||||||
|
host := ""
|
||||||
|
|
||||||
|
switch v := a.(type) {
|
||||||
|
case *dns.CNAME:
|
||||||
|
host = strings.TrimSuffix(v.Target, ".")
|
||||||
|
domainName = v.Hdr.Name
|
||||||
|
m = fmt.Sprintf("DNSRebind: Checking CNAME %s for %s", v.Target, v.Hdr.Name)
|
||||||
|
|
||||||
|
case *dns.A:
|
||||||
|
host = v.A.String()
|
||||||
|
domainName = v.Hdr.Name
|
||||||
|
m = fmt.Sprintf("DNSRebind: Checking record A (%s) for %s", host, v.Hdr.Name)
|
||||||
|
|
||||||
|
case *dns.AAAA:
|
||||||
|
host = v.AAAA.String()
|
||||||
|
domainName = v.Hdr.Name
|
||||||
|
m = fmt.Sprintf("DNSRebind: Checking record AAAA (%s) for %s", host, v.Hdr.Name)
|
||||||
|
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s.RLock()
|
||||||
|
if !s.conf.RebindingProtectionEnabled {
|
||||||
|
s.RUnlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug(m)
|
||||||
|
blocked := s.isResponseRebind(strings.TrimSuffix(domainName, "."), host)
|
||||||
|
s.RUnlock()
|
||||||
|
|
||||||
|
if blocked {
|
||||||
|
res := &dnsfilter.Result{
|
||||||
|
IsFiltered: true,
|
||||||
|
Reason: dnsfilter.FilteredRebind,
|
||||||
|
Rule: "adguard-rebind-protection",
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Res = s.genDNSFilterMessage(d, res)
|
||||||
|
log.Debug("DNSRebind: Matched %s by response: %s", d.Req.Question[0].Name, host)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
113
internal/dnsforward/rebind_test.go
Normal file
113
internal/dnsforward/rebind_test.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package dnsforward
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRebindingPrivateAddresses(t *testing.T) {
|
||||||
|
c, _ := newRebindChecker(nil)
|
||||||
|
|
||||||
|
r1 := byte(rand.Int31() & 0xFE)
|
||||||
|
r2 := byte(rand.Int31() & 0xFE)
|
||||||
|
r3 := byte(rand.Int31() & 0xFE)
|
||||||
|
|
||||||
|
for _, ip := range []net.IP{
|
||||||
|
net.IPv4(0, r1, r2, r3), /* 0.0.0.0/8 (RFC 5735 section 3. "here" network) */
|
||||||
|
net.IPv4(127, r1, r2, r3), /* 127.0.0.0/8 (loopback) */
|
||||||
|
net.IPv4(10, r1, r2, r3), /* 10.0.0.0/8 (private) */
|
||||||
|
net.IPv4(172, 0x10|byte(1&rand.Int31()), r2, r3), /* 172.16.0.0/12 (private) */
|
||||||
|
net.IPv4(192, 168, r2, r3), /* 192.168.0.0/16 (private) */
|
||||||
|
net.IPv4(169, 254, r2, r3), /* 169.254.0.0/16 (zeroconf) */
|
||||||
|
net.IPv4(192, 0, 2, r3), /* 192.0.2.0/24 (test-net) */
|
||||||
|
net.IPv4(198, 51, 100, r3), /* 198.51.100.0/24(test-net) */
|
||||||
|
net.IPv4(203, 0, 113, r3), /* 203.0.113.0/24 (test-net) */
|
||||||
|
net.IPv4(255, 255, 255, 255), /* 255.255.255.255/32 (broadcast)*/
|
||||||
|
|
||||||
|
/* RFC 6303 4.3 (unspecified & loopback) */
|
||||||
|
net.IPv6zero,
|
||||||
|
net.IPv6unspecified,
|
||||||
|
|
||||||
|
/* RFC 6303 4.4 */
|
||||||
|
/* RFC 6303 4.5 */
|
||||||
|
/* RFC 6303 4.6 */
|
||||||
|
net.IPv6interfacelocalallnodes,
|
||||||
|
net.IPv6linklocalallnodes,
|
||||||
|
net.IPv6linklocalallrouters,
|
||||||
|
|
||||||
|
/* (TODO) Check IPv4-mapped IPv6 addresses */
|
||||||
|
} {
|
||||||
|
assert.Truef(t, c.isRebindIP(ip), "%s is not a rebind", ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRebindLocalhost(t *testing.T) {
|
||||||
|
c := &dnsRebindChecker{}
|
||||||
|
assert.False(t, c.isRebindHost("example.com"))
|
||||||
|
assert.False(t, c.isRebindHost("200.0.0.1"))
|
||||||
|
assert.True(t, c.isRebindHost("127.0.0.1"))
|
||||||
|
assert.True(t, c.isRebindHost("localhost"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsResponseRebind(t *testing.T) {
|
||||||
|
c, _ := newRebindChecker([]string{
|
||||||
|
"||totally-safe.com^",
|
||||||
|
})
|
||||||
|
s := &Server{
|
||||||
|
rebinding: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range []string{
|
||||||
|
"0.1.2.3", /* 0.0.0.0/8 (RFC 5735 section 3. "here" network) */
|
||||||
|
"127.1.2.3", /* 127.0.0.0/8 (loopback) */
|
||||||
|
"10.1.2.3", /* 10.0.0.0/8 (private) */
|
||||||
|
"172.16.2.3", /* 172.16.0.0/12 (private) */
|
||||||
|
"192.168.2.3", /* 192.168.0.0/16 (private) */
|
||||||
|
"169.254.2.3", /* 169.254.0.0/16 (zeroconf) */
|
||||||
|
"192.0.2.3", /* 192.0.2.0/24 (test-net) */
|
||||||
|
"198.51.100.3", /* 198.51.100.0/24(test-net) */
|
||||||
|
"203.0.113.3", /* 203.0.113.0/24 (test-net) */
|
||||||
|
"255.255.255.255", /* 255.255.255.255/32 (broadcast)*/
|
||||||
|
|
||||||
|
/* RFC 6303 4.3 (unspecified & loopback) */
|
||||||
|
net.IPv6zero.String(),
|
||||||
|
net.IPv6unspecified.String(),
|
||||||
|
|
||||||
|
/* RFC 6303 4.4 */
|
||||||
|
/* RFC 6303 4.5 */
|
||||||
|
/* RFC 6303 4.6 */
|
||||||
|
net.IPv6interfacelocalallnodes.String(),
|
||||||
|
net.IPv6linklocalallnodes.String(),
|
||||||
|
net.IPv6linklocalallrouters.String(),
|
||||||
|
|
||||||
|
"localhost",
|
||||||
|
} {
|
||||||
|
s.conf.RebindingProtectionEnabled = true
|
||||||
|
assert.Truef(t, s.isResponseRebind("example.com", host), "host: %s", host)
|
||||||
|
assert.Falsef(t, s.isResponseRebind("totally-safe.com", host), "host: %s", host)
|
||||||
|
assert.Falsef(t, s.isResponseRebind("absolutely.totally-safe.com", host), "host: %s", host)
|
||||||
|
|
||||||
|
s.conf.RebindingProtectionEnabled = false
|
||||||
|
assert.Falsef(t, s.isResponseRebind("example.com", host), "host: %s", host)
|
||||||
|
assert.Falsef(t, s.isResponseRebind("totally-safe.com", host), "host: %s", host)
|
||||||
|
assert.Falsef(t, s.isResponseRebind("absolutely.totally-safe.com", host), "host: %s", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range []string{
|
||||||
|
"200.168.2.3",
|
||||||
|
"another-example.com",
|
||||||
|
} {
|
||||||
|
s.conf.RebindingProtectionEnabled = true
|
||||||
|
assert.Falsef(t, s.isResponseRebind("example.com", host), "host: %s", host)
|
||||||
|
assert.Falsef(t, s.isResponseRebind("totally-safe.com", host), "host: %s", host)
|
||||||
|
assert.Falsef(t, s.isResponseRebind("absolutely.totally-legit.com", host), "host: %s", host)
|
||||||
|
|
||||||
|
s.conf.RebindingProtectionEnabled = false
|
||||||
|
assert.Falsef(t, s.isResponseRebind("example.com", host), "host: %s", host)
|
||||||
|
assert.Falsef(t, s.isResponseRebind("totally-safe.com", host), "host: %s", host)
|
||||||
|
assert.Falsef(t, s.isResponseRebind("absolutely.totally-legit.com", host), "host: %s", host)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,12 +5,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/querylog"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/miekg/dns"
|
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
||||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/stats"
|
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Write Stats data and logs
|
// Write Stats data and logs
|
||||||
@@ -40,10 +39,16 @@ func processQueryLogsAndStats(ctx *dnsContext) int {
|
|||||||
ClientIP: getIP(d.Addr),
|
ClientIP: getIP(d.Addr),
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.Proto == "https" {
|
switch d.Proto {
|
||||||
p.ClientProto = "doh"
|
case proxy.ProtoHTTPS:
|
||||||
} else if d.Proto == "tls" {
|
p.ClientProto = querylog.ClientProtoDOH
|
||||||
p.ClientProto = "dot"
|
case proxy.ProtoQUIC:
|
||||||
|
p.ClientProto = querylog.ClientProtoDOQ
|
||||||
|
case proxy.ProtoTLS:
|
||||||
|
p.ClientProto = querylog.ClientProtoDOT
|
||||||
|
default:
|
||||||
|
// Consider this a plain DNS-over-UDP or DNS-over-TCL
|
||||||
|
// request.
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.Upstream != nil {
|
if d.Upstream != nil {
|
||||||
@@ -27,7 +27,7 @@ func stringArrayDup(a []string) []string {
|
|||||||
|
|
||||||
// Get IP address from net.Addr object
|
// Get IP address from net.Addr object
|
||||||
// Note: we can't use net.SplitHostPort(a.String()) because of IPv6 zone:
|
// Note: we can't use net.SplitHostPort(a.String()) because of IPv6 zone:
|
||||||
// https://github.com/AdguardTeam/AdGuardHome/issues/1261
|
// https://github.com/AdguardTeam/AdGuardHome/internal/issues/1261
|
||||||
func ipFromAddr(a net.Addr) string {
|
func ipFromAddr(a net.Addr) string {
|
||||||
switch addr := a.(type) {
|
switch addr := a.(type) {
|
||||||
case *net.UDPAddr:
|
case *net.UDPAddr:
|
||||||
@@ -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"
|
||||||
@@ -17,22 +19,22 @@ import (
|
|||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const cookieTTL = 365 * 24 // in hours
|
const (
|
||||||
const sessionCookieName = "agh_session"
|
cookieTTL = 365 * 24 // in hours
|
||||||
|
sessionCookieName = "agh_session"
|
||||||
|
)
|
||||||
|
|
||||||
type session struct {
|
type session struct {
|
||||||
userName string
|
userName string
|
||||||
expire uint32 // expiration time (in seconds)
|
expire uint32 // expiration time (in seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
expire byte[4]
|
|
||||||
name_len byte[2]
|
|
||||||
name byte[]
|
|
||||||
*/
|
|
||||||
func (s *session) serialize() []byte {
|
func (s *session) serialize() []byte {
|
||||||
var data []byte
|
const (
|
||||||
data = make([]byte, 4+2+len(s.userName))
|
expireLen = 4
|
||||||
|
nameLen = 2
|
||||||
|
)
|
||||||
|
data := make([]byte, expireLen+nameLen+len(s.userName))
|
||||||
binary.BigEndian.PutUint32(data[0:4], s.expire)
|
binary.BigEndian.PutUint32(data[0:4], s.expire)
|
||||||
binary.BigEndian.PutUint16(data[4:6], uint16(len(s.userName)))
|
binary.BigEndian.PutUint16(data[4:6], uint16(len(s.userName)))
|
||||||
copy(data[6:], []byte(s.userName))
|
copy(data[6:], []byte(s.userName))
|
||||||
@@ -76,13 +78,12 @@ 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, 0644, nil)
|
a.db, err = bbolt.Open(dbFilename, 0o644, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Auth: open DB: %s: %s", dbFilename, err)
|
log.Error("Auth: open DB: %s: %s", dbFilename, err)
|
||||||
if err.Error() == "invalid argument" {
|
if err.Error() == "invalid argument" {
|
||||||
log.Error("AdGuard Home cannot be initialized due to an incompatible file system.\nPlease read the explanation here: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#limitations")
|
log.Error("AdGuard Home cannot be initialized due to an incompatible file system.\nPlease read the explanation here: https://github.com/AdguardTeam/AdGuardHome/internal/wiki/Getting-Started#limitations")
|
||||||
}
|
}
|
||||||
return nil
|
return 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,9 +322,13 @@ 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='%s'", req.Name)
|
log.Info("Auth: invalid user name or password: name=%q", req.Name)
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
http.Error(w, "invalid user name or password", http.StatusBadRequest)
|
http.Error(w, "invalid user name or password", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -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,10 +379,56 @@ 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" {
|
||||||
// redirect to dashboard if already authenticated
|
// redirect to dashboard if already authenticated
|
||||||
authRequired := Context.auth != nil && Context.auth.AuthRequired()
|
authRequired := Context.auth != nil && Context.auth.AuthRequired()
|
||||||
@@ -393,46 +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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -474,7 +491,7 @@ func (a *Auth) UserAdd(u *User, password string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UserFind - find a user
|
// UserFind - find a user
|
||||||
func (a *Auth) UserFind(login string, password string) User {
|
func (a *Auth) UserFind(login, password string) User {
|
||||||
a.lock.Lock()
|
a.lock.Lock()
|
||||||
defer a.lock.Unlock()
|
defer a.lock.Unlock()
|
||||||
for _, u := range a.users {
|
for _, u := range a.users {
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user