Compare commits
84 Commits
v0.104.0
...
1383-devic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
503974c210 | ||
|
|
e2738fdf3f | ||
|
|
e02308dd42 | ||
|
|
ef178610d6 | ||
|
|
5c7b6bbf5c | ||
|
|
7f9a3a73b4 | ||
|
|
73c30590e0 | ||
|
|
b7bf7f78df | ||
|
|
6aacb2105c | ||
|
|
88d44b4370 | ||
|
|
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:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 40%
|
||||
threshold: null
|
||||
patch: false
|
||||
changes: false
|
||||
'coverage':
|
||||
'status':
|
||||
'project':
|
||||
'default':
|
||||
'target': '40%'
|
||||
'threshold': null
|
||||
'patch': false
|
||||
'changes': false
|
||||
|
||||
@@ -11,7 +11,7 @@ fi
|
||||
found=0
|
||||
git diff --cached --name-only | grep -q '.go$' && found=1
|
||||
if [ $found == 1 ]; then
|
||||
make lint-go || exit 1
|
||||
make go-lint || exit 1
|
||||
go test ./... || exit 1
|
||||
fi
|
||||
|
||||
|
||||
32
.github/stale.yml
vendored
32
.github/stale.yml
vendored
@@ -1,19 +1,19 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- 'bug'
|
||||
- 'enhancement'
|
||||
- 'feature request'
|
||||
- 'localization'
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: 'wontfix'
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
# Number of days of inactivity before an issue becomes stale.
|
||||
'daysUntilStale': 60
|
||||
# Number of days of inactivity before a stale issue is closed.
|
||||
'daysUntilClose': 7
|
||||
# Issues with these labels will never be considered stale.
|
||||
'exemptLabels':
|
||||
- 'bug'
|
||||
- 'enhancement'
|
||||
- 'feature request'
|
||||
- 'localization'
|
||||
# Label to use when marking an issue as stale.
|
||||
'staleLabel': 'wontfix'
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable.
|
||||
'markComment': >
|
||||
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
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable.
|
||||
'closeComment': false
|
||||
|
||||
307
.github/workflows/build.yml
vendored
307
.github/workflows/build.yml
vendored
@@ -1,172 +1,145 @@
|
||||
name: build
|
||||
'name': 'build'
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.14
|
||||
NODE_VERSION: 13
|
||||
'env':
|
||||
'GO_VERSION': '1.14'
|
||||
'NODE_VERSION': '13'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
tags:
|
||||
- v*
|
||||
pull_request:
|
||||
'on':
|
||||
'push':
|
||||
'branches':
|
||||
- '*'
|
||||
'tags':
|
||||
- 'v*'
|
||||
'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:
|
||||
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 }}
|
||||
'docker':
|
||||
'runs-on': 'ubuntu-latest'
|
||||
'needs': 'test'
|
||||
'steps':
|
||||
- 'name': 'Checkout'
|
||||
'uses': 'actions/checkout@v2'
|
||||
'with':
|
||||
'fetch-depth': 0
|
||||
- 'name': 'Set up QEMU'
|
||||
'uses': 'docker/setup-qemu-action@v1'
|
||||
- 'name': 'Set up Docker Buildx'
|
||||
'uses': 'docker/setup-buildx-action@v1'
|
||||
- 'name': 'Docker Buildx (build)'
|
||||
'run': 'make docker-multi-arch'
|
||||
|
||||
-
|
||||
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
|
||||
|
||||
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 }}
|
||||
'notify':
|
||||
'needs':
|
||||
- 'app'
|
||||
- 'docker'
|
||||
# Secrets are not passed to workflows that are triggered by a pull request
|
||||
# from a fork.
|
||||
#
|
||||
# Use always() to signal to the runner that this job must run even if the
|
||||
# previous ones failed.
|
||||
'if':
|
||||
${{ always() &&
|
||||
(
|
||||
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, job'
|
||||
'env':
|
||||
'GITHUB_TOKEN': '${{ secrets.GITHUB_TOKEN }}'
|
||||
'SLACK_WEBHOOK_URL': '${{ secrets.SLACK_WEBHOOK_URL }}'
|
||||
|
||||
99
.github/workflows/lint.yml
vendored
99
.github/workflows/lint.yml
vendored
@@ -1,47 +1,52 @@
|
||||
name: golangci-lint
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
branches:
|
||||
- '*'
|
||||
pull_request:
|
||||
jobs:
|
||||
golangci:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v1
|
||||
with:
|
||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||
version: v1.27
|
||||
|
||||
eslint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install modules
|
||||
run: npm --prefix client ci
|
||||
- name: Run ESLint
|
||||
run: npm --prefix client run lint
|
||||
|
||||
|
||||
notify:
|
||||
needs: [golangci,eslint]
|
||||
# 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 }}
|
||||
'name': 'lint'
|
||||
'on':
|
||||
'push':
|
||||
'tags':
|
||||
- 'v*'
|
||||
'branches':
|
||||
- '*'
|
||||
'pull_request':
|
||||
'jobs':
|
||||
'go-lint':
|
||||
'runs-on': 'ubuntu-latest'
|
||||
'steps':
|
||||
- 'uses': 'actions/checkout@v2'
|
||||
- 'name': 'run-lint'
|
||||
'run': >
|
||||
make go-install-tools go-lint
|
||||
'eslint':
|
||||
'runs-on': 'ubuntu-latest'
|
||||
'steps':
|
||||
- 'uses': 'actions/checkout@v2'
|
||||
- 'name': 'Install modules'
|
||||
'run': 'npm --prefix client ci'
|
||||
- 'name': 'Run ESLint'
|
||||
'run': 'npm --prefix client run lint'
|
||||
'notify':
|
||||
'needs':
|
||||
- 'go-lint'
|
||||
- 'eslint'
|
||||
# Secrets are not passed to workflows that are triggered by a pull request
|
||||
# from a fork.
|
||||
#
|
||||
# Use always() to signal to the runner that this job must run even if the
|
||||
# previous ones failed.
|
||||
'if':
|
||||
${{ always() &&
|
||||
(
|
||||
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, job'
|
||||
'env':
|
||||
'GITHUB_TOKEN': '${{ secrets.GITHUB_TOKEN }}'
|
||||
'SLACK_WEBHOOK_URL': '${{ secrets.SLACK_WEBHOOK_URL }}'
|
||||
|
||||
50
.gitignore
vendored
50
.gitignore
vendored
@@ -1,30 +1,22 @@
|
||||
.DS_Store
|
||||
/.vscode
|
||||
.idea
|
||||
/AdGuardHome
|
||||
/AdGuardHome.exe
|
||||
/AdGuardHome.yaml
|
||||
/AdGuardHome.log
|
||||
/data/
|
||||
/build/
|
||||
/dist/
|
||||
/client/node_modules/
|
||||
/querylog.json
|
||||
/querylog.json.1
|
||||
coverage.txt
|
||||
|
||||
# Test output
|
||||
dnsfilter/tests/top-1m.csv
|
||||
dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof
|
||||
|
||||
# Snapcraft build temporary files
|
||||
*.snap
|
||||
launchpad_credentials
|
||||
snapcraft_login
|
||||
snapcraft.yaml.bak
|
||||
|
||||
# IntelliJ IDEA project files
|
||||
*.iml
|
||||
|
||||
# Packr
|
||||
# Please, DO NOT put your text editors' temporary files here. The more are
|
||||
# added, the harder it gets to maintain and manage projects' gitignores. Put
|
||||
# them into your global gitignore file instead.
|
||||
#
|
||||
# See https://stackoverflow.com/a/7335487/1892060.
|
||||
#
|
||||
# Only build, run, and test outputs here. Sorted.
|
||||
*-packr.go
|
||||
*.db
|
||||
*.snap
|
||||
/bin/
|
||||
/build/
|
||||
/data/
|
||||
/dist/
|
||||
/dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof
|
||||
/dnsfilter/tests/top-1m.csv
|
||||
/launchpad_credentials
|
||||
/querylog.json*
|
||||
/snapcraft_login
|
||||
AdGuardHome*
|
||||
coverage.txt
|
||||
node_modules/
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
# options for analysis running
|
||||
run:
|
||||
# default concurrency is a available CPU number
|
||||
concurrency: 4
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
deadline: 2m
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# won't be reported. Default value is empty list, but there is
|
||||
# no need to include all autogenerated files, we confidently recognize
|
||||
# autogenerated files. If it's not please let us know.
|
||||
skip-files:
|
||||
- ".*generated.*"
|
||||
- dnsfilter/rule_to_regexp.go
|
||||
- util/pprof.go
|
||||
- ".*_test.go"
|
||||
- client/.*
|
||||
- build/.*
|
||||
- dist/.*
|
||||
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
errcheck:
|
||||
# [deprecated] comma-separated list of pairs of the form pkg:regex
|
||||
# the regex is used to ignore names within pkg. (default "fmt:.*").
|
||||
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
|
||||
ignore: fmt:.*,net:SetReadDeadline,net/http:^Write
|
||||
gocyclo:
|
||||
min-complexity: 20
|
||||
lll:
|
||||
line-length: 200
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- deadcode
|
||||
- errcheck
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- unused
|
||||
- varcheck
|
||||
- bodyclose
|
||||
- depguard
|
||||
- dupl
|
||||
- gocyclo
|
||||
- goimports
|
||||
- golint
|
||||
- gosec
|
||||
- misspell
|
||||
- stylecheck
|
||||
- unconvert
|
||||
disable-all: true
|
||||
fast: true
|
||||
|
||||
issues:
|
||||
# List of regexps of issue texts to exclude, empty list by default.
|
||||
# But independently from this option we use default exclude patterns,
|
||||
# it can be disabled by `exclude-use-default: false`. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`
|
||||
exclude:
|
||||
# structcheck cannot detect usages while they're there
|
||||
- .parentalServer. is unused
|
||||
- .safeBrowsingServer. is unused
|
||||
# errcheck
|
||||
- Error return value of .s.closeConn. is not checked
|
||||
- Error return value of ..*.Shutdown.
|
||||
# goconst
|
||||
- string .forcesafesearch.google.com. has 3 occurrences
|
||||
# gosec: Profiling endpoint is automatically exposed on /debug/pprof
|
||||
- G108
|
||||
# gosec: Subprocess launched with function call as argument or cmd arguments
|
||||
- G204
|
||||
# gosec: Potential DoS vulnerability via decompression bomb
|
||||
- G110
|
||||
# gosec: Expect WriteFile permissions to be 0600 or less
|
||||
- G306
|
||||
201
.goreleaser.yml
201
.goreleaser.yml
@@ -1,100 +1,115 @@
|
||||
project_name: AdGuardHome
|
||||
'project_name': 'AdGuardHome'
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
- GOPROXY=https://goproxy.io
|
||||
'env':
|
||||
- 'GO111MODULE=on'
|
||||
- 'GOPROXY=https://goproxy.io'
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod download
|
||||
- go generate ./...
|
||||
'before':
|
||||
'hooks':
|
||||
- 'go mod download'
|
||||
- 'go generate ./...'
|
||||
|
||||
builds:
|
||||
- main: ./main.go
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.channel={{.Env.CHANNEL}} -X main.goarm={{.Env.GOARM}}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
- freebsd
|
||||
- windows
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- mips
|
||||
- mipsle
|
||||
- mips64
|
||||
- mips64le
|
||||
goarm:
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
gomips:
|
||||
- softfloat
|
||||
ignore:
|
||||
- goos: freebsd
|
||||
goarch: mips
|
||||
- goos: freebsd
|
||||
goarch: mipsle
|
||||
'builds':
|
||||
- 'main': './main.go'
|
||||
'ldflags':
|
||||
- '-s -w -X main.version={{.Version}} -X main.channel={{.Env.CHANNEL}} -X main.goarm={{.Env.GOARM}}'
|
||||
'env':
|
||||
- 'CGO_ENABLED=0'
|
||||
'goos':
|
||||
- 'darwin'
|
||||
- 'linux'
|
||||
- 'freebsd'
|
||||
- 'windows'
|
||||
'goarch':
|
||||
- '386'
|
||||
- 'amd64'
|
||||
- 'arm'
|
||||
- 'arm64'
|
||||
- 'mips'
|
||||
- 'mipsle'
|
||||
- 'mips64'
|
||||
- 'mips64le'
|
||||
'goarm':
|
||||
- '5'
|
||||
- '6'
|
||||
- '7'
|
||||
'gomips':
|
||||
- 'softfloat'
|
||||
'ignore':
|
||||
- 'goos': 'freebsd'
|
||||
'goarch': 'mips'
|
||||
- 'goos': 'freebsd'
|
||||
'goarch': 'mipsle'
|
||||
|
||||
archives:
|
||||
- # Archive name template.
|
||||
# Defaults:
|
||||
# - if format is `tar.gz`, `tar.xz`, `gz` or `zip`:
|
||||
# - `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}`
|
||||
# - if format is `binary`:
|
||||
# - `{{ .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 }}"
|
||||
wrap_in_directory: "AdGuardHome"
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
- goos: darwin
|
||||
format: zip
|
||||
files:
|
||||
- LICENSE.txt
|
||||
- README.md
|
||||
'archives':
|
||||
- # Archive name template.
|
||||
# Defaults:
|
||||
# - if format is `tar.gz`, `tar.xz`, `gz` or `zip`:
|
||||
# - `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}`
|
||||
# - if format is `binary`:
|
||||
# - `{{ .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 }}'
|
||||
'wrap_in_directory': 'AdGuardHome'
|
||||
'format_overrides':
|
||||
- 'goos': 'windows'
|
||||
'format': 'zip'
|
||||
- 'goos': 'darwin'
|
||||
'format': 'zip'
|
||||
'files':
|
||||
- 'LICENSE.txt'
|
||||
- 'README.md'
|
||||
|
||||
snapcrafts:
|
||||
- name: adguard-home
|
||||
base: core18
|
||||
name_template: '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||
summary: Network-wide ads & trackers blocking DNS server
|
||||
description: |
|
||||
AdGuard Home is a network-wide software for blocking ads & tracking. After
|
||||
you set it up, it'll cover ALL your home devices, and you don't need any
|
||||
client-side software for that.
|
||||
'snapcrafts':
|
||||
- 'name': 'adguard-home'
|
||||
'base': 'core20'
|
||||
'name_template': '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||
'summary': 'Network-wide ads & trackers blocking DNS server'
|
||||
'description': |
|
||||
AdGuard Home is a network-wide software for blocking ads & tracking. After
|
||||
you set it up, it'll cover ALL your home devices, and you don't need any
|
||||
client-side software for that.
|
||||
|
||||
It operates as a DNS server that re-routes tracking domains to a "black hole,"
|
||||
thus preventing your devices from connecting to those servers. It's based
|
||||
on software we use for our public AdGuard DNS servers -- both share a lot
|
||||
of common code.
|
||||
grade: stable
|
||||
confinement: strict
|
||||
publish: false
|
||||
license: GPL-3.0
|
||||
extra_files:
|
||||
- source: scripts/snap/local/adguard-home-web.sh
|
||||
destination: adguard-home-web.sh
|
||||
mode: 0755
|
||||
- source: scripts/snap/gui/adguard-home-web.desktop
|
||||
destination: meta/gui/adguard-home-web.desktop
|
||||
mode: 0644
|
||||
- source: scripts/snap/gui/adguard-home-web.png
|
||||
destination: meta/gui/adguard-home-web.png
|
||||
mode: 0644
|
||||
apps:
|
||||
adguard-home:
|
||||
command: AdGuardHome -w $SNAP_DATA --no-check-update
|
||||
plugs: [ network-bind ]
|
||||
daemon: simple
|
||||
adguard-home-web:
|
||||
command: adguard-home-web.sh
|
||||
plugs: [ desktop ]
|
||||
It operates as a DNS server that re-routes tracking domains to a "black hole,"
|
||||
thus preventing your devices from connecting to those servers. It's based
|
||||
on software we use for our public AdGuard DNS servers -- both share a lot
|
||||
of common code.
|
||||
'grade': 'stable'
|
||||
'confinement': 'strict'
|
||||
'publish': false
|
||||
'license': 'GPL-3.0'
|
||||
'extra_files':
|
||||
- 'source': 'scripts/snap/local/adguard-home-web.sh'
|
||||
'destination': 'adguard-home-web.sh'
|
||||
'mode': 0755
|
||||
- 'source': 'scripts/snap/gui/adguard-home-web.desktop'
|
||||
'destination': 'meta/gui/adguard-home-web.desktop'
|
||||
'mode': 0644
|
||||
- 'source': 'scripts/snap/gui/adguard-home-web.png'
|
||||
'destination': 'meta/gui/adguard-home-web.png'
|
||||
'mode': 0644
|
||||
'apps':
|
||||
'adguard-home':
|
||||
'command': 'AdGuardHome -w $SNAP_DATA --no-check-update'
|
||||
'plugs':
|
||||
# Add the "netrwork-bind" plug to bind to interfaces.
|
||||
- 'network-bind'
|
||||
# Add the "netrwork-observe" plug to be able to bind to ports below 1024
|
||||
# (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:
|
||||
name_template: 'checksums.txt'
|
||||
'checksum':
|
||||
'name_template': 'checksums.txt'
|
||||
|
||||
'snapshot':
|
||||
# TODO(a.garipov): A temporary solution to trim the prerelease versions.
|
||||
# A real solution would consist of making a better versioning scheme that also
|
||||
# doesn't break SemVer or Snapcraft.
|
||||
#
|
||||
# See https://github.com/AdguardTeam/AdGuardHome/issues/2412.
|
||||
'name_template': '{{ slice .Tag 0 8 }}-SNAPSHOT-{{ .ShortCommit }}'
|
||||
|
||||
@@ -1587,7 +1587,7 @@ Response:
|
||||
"upstream":"...", // Upstream URL starting with tcp://, tls://, https://, or with an IP address
|
||||
"answer_dnssec": true,
|
||||
"client":"127.0.0.1",
|
||||
"client_proto": "" (plain) | "doh" | "dot",
|
||||
"client_proto": "" (plain) | "doh" | "dot" | "doq",
|
||||
"elapsedMs":"0.098403",
|
||||
"filterId":1,
|
||||
"question":{
|
||||
|
||||
109
CHANGELOG.md
Normal file
109
CHANGELOG.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# 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 ([#2267], [#2271], [#2297]).
|
||||
|
||||
[#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231
|
||||
[#2267]: https://github.com/AdguardTeam/AdGuardHome/issues/2267
|
||||
[#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271
|
||||
[#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297
|
||||
[#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306
|
||||
[#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343
|
||||
[#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358
|
||||
[#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391
|
||||
|
||||
### 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 \
|
||||
libcap \
|
||||
libressl \
|
||||
tzdata \
|
||||
&& rm -rf /tmp/* /var/cache/apk/*
|
||||
|
||||
COPY --from=builder --chown=nobody:nogroup /app/AdGuardHome /opt/adguardhome/AdGuardHome
|
||||
|
||||
225
HACKING.md
Normal file
225
HACKING.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# *AdGuardHome* Developer Guidelines
|
||||
|
||||
As of **December 2020**, this document is partially 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*
|
||||
|
||||
> Not Golang, not GO, not GOLANG, not GoLang. It is Go in natural language,
|
||||
> golang for others.
|
||||
|
||||
— [@rakyll](https://twitter.com/rakyll/status/1229850223184269312)
|
||||
|
||||
### 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`.
|
||||
|
||||
* 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.
|
||||
|
||||
## Shell Scripting
|
||||
|
||||
* Avoid bashisms, prefer *POSIX* features only.
|
||||
|
||||
* Prefer `'raw strings'` to `"double quoted strings"` whenever possible.
|
||||
|
||||
* Put spaces within `$( cmd )`, `$(( expr ))`, and `{ cmd; }`.
|
||||
|
||||
* `snake_case`, not `camelCase`.
|
||||
|
||||
* Use `set -e -f -u` and also `set -x` in verbose mode.
|
||||
|
||||
* Use the `"$var"` form instead of the `$var` form, unless word splitting is
|
||||
required.
|
||||
|
||||
## 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
|
||||
65
Makefile
65
Makefile
@@ -26,13 +26,15 @@
|
||||
# * DOCKER_IMAGE_NAME - adguard/adguard-home
|
||||
# * DOCKER_OUTPUT - type=image,name=adguard/adguard-home,push=true
|
||||
|
||||
GOPATH := $(shell go env GOPATH)
|
||||
GO := go
|
||||
GOPATH := $(shell $(GO) env GOPATH)
|
||||
PWD := $(shell pwd)
|
||||
TARGET=AdGuardHome
|
||||
BASE_URL="https://static.adguard.com/adguardhome/$(CHANNEL)"
|
||||
GPG_KEY := devteam@adguard.com
|
||||
GPG_KEY_PASSPHRASE :=
|
||||
GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE)
|
||||
VERBOSE := -v
|
||||
|
||||
# See release target
|
||||
DIST_DIR=dist
|
||||
@@ -64,7 +66,9 @@ endif
|
||||
|
||||
# Version properties
|
||||
COMMIT=$(shell git rev-parse --short HEAD)
|
||||
TAG_NAME=$(shell git describe --abbrev=0)
|
||||
# TODO(a.garipov): The cut call is a temporary solution to trim
|
||||
# prerelease versions. See the comment in .goreleaser.yml.
|
||||
TAG_NAME=$(shell git describe --abbrev=0 | cut -c 1-8)
|
||||
RELEASE_VERSION=$(TAG_NAME)
|
||||
SNAPSHOT_VERSION=$(RELEASE_VERSION)-SNAPSHOT-$(COMMIT)
|
||||
|
||||
@@ -109,7 +113,7 @@ $(error DOCKER_IMAGE_NAME value is not set)
|
||||
endif
|
||||
|
||||
# OS-specific flags
|
||||
TEST_FLAGS := -race
|
||||
TEST_FLAGS := --race $(VERBOSE)
|
||||
ifeq ($(OS),Windows_NT)
|
||||
TEST_FLAGS :=
|
||||
endif
|
||||
@@ -121,9 +125,9 @@ init:
|
||||
git config core.hooksPath .githooks
|
||||
|
||||
build: client_with_deps
|
||||
go mod download
|
||||
PATH=$(GOPATH)/bin:$(PATH) go generate ./...
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)"
|
||||
$(GO) mod download
|
||||
PATH=$(GOPATH)/bin:$(PATH) $(GO) generate ./...
|
||||
CGO_ENABLED=0 $(GO) build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)"
|
||||
PATH=$(GOPATH)/bin:$(PATH) packr clean
|
||||
|
||||
client:
|
||||
@@ -150,45 +154,40 @@ docker:
|
||||
@echo Now you can run the docker image:
|
||||
@echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME)
|
||||
|
||||
lint: lint-js lint-go
|
||||
lint: js-lint go-lint
|
||||
|
||||
lint-js: dependencies
|
||||
@echo Running js linter
|
||||
js-lint: dependencies
|
||||
npm --prefix client run lint
|
||||
|
||||
lint-go:
|
||||
@echo Running go linter
|
||||
golangci-lint run
|
||||
go-install-tools:
|
||||
env GO=$(GO) sh ./scripts/go-install-tools.sh
|
||||
|
||||
test:
|
||||
@echo Running JS unit-tests
|
||||
go-lint:
|
||||
env GO=$(GO) PATH="$$PWD/bin:$$PATH" sh ./scripts/go-lint.sh
|
||||
|
||||
test: js-test go-test
|
||||
|
||||
js-test:
|
||||
npm run test --prefix client
|
||||
@echo Running Go unit-tests
|
||||
go test $(TEST_FLAGS) -v -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
go-test:
|
||||
$(GO) test $(TEST_FLAGS) --coverprofile coverage.txt ./...
|
||||
|
||||
ci: client_with_deps
|
||||
go mod download
|
||||
$(GO) mod download
|
||||
$(MAKE) test
|
||||
|
||||
dependencies:
|
||||
npm --prefix client ci
|
||||
go mod download
|
||||
$(GO) mod download
|
||||
|
||||
clean:
|
||||
# make build output
|
||||
rm -f AdGuardHome
|
||||
rm -f AdGuardHome.exe
|
||||
# tests output
|
||||
rm -rf data
|
||||
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
|
||||
rm -f ./AdGuardHome ./AdGuardHome.exe ./coverage.txt
|
||||
rm -f -r ./build/ ./client/node_modules/ ./data/ ./$(DIST_DIR)/
|
||||
# Set the GOPATH explicitly in case make clean is called from under sudo
|
||||
# after a Docker build.
|
||||
env PATH="$(GOPATH)/bin:$$PATH" packr clean
|
||||
rm -f -r ./bin/
|
||||
|
||||
docker-multi-arch:
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled \
|
||||
@@ -206,7 +205,7 @@ docker-multi-arch:
|
||||
@echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME)
|
||||
|
||||
release: client_with_deps
|
||||
go mod download
|
||||
$(GO) mod download
|
||||
@echo Starting release build: version $(VERSION), channel $(CHANNEL)
|
||||
CHANNEL=$(CHANNEL) $(GORELEASER_COMMAND)
|
||||
$(call write_version_file,$(VERSION))
|
||||
|
||||
@@ -171,9 +171,6 @@ You will need this to build AdGuard Home:
|
||||
* [node.js](https://nodejs.org/en/download/) v10.16.2 or later.
|
||||
* [npm](https://www.npmjs.com/) v6.14 or later.
|
||||
|
||||
Optionally, for Go devs:
|
||||
* [golangci-lint](https://github.com/golangci/golangci-lint)
|
||||
|
||||
### Building
|
||||
|
||||
Open Terminal and execute these commands:
|
||||
@@ -186,7 +183,7 @@ make
|
||||
|
||||
Check the [`Makefile`](https://github.com/AdguardTeam/AdGuardHome/blob/master/Makefile) to learn about other commands.
|
||||
|
||||
**Building for a different platform.** You can build AdGuard for any OS/ARCH just like any other Golang project.
|
||||
**Building for a different platform.** You can build AdGuard for any OS/ARCH just like any other Go project.
|
||||
In order to do this, specify `GOOS` and `GOARCH` env variables before running make.
|
||||
|
||||
For example:
|
||||
@@ -293,7 +290,7 @@ Here is a link to AdGuard Home project: https://crowdin.com/project/adguard-appl
|
||||
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".
|
||||
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).
|
||||
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).
|
||||
|
||||
@@ -331,4 +328,4 @@ For a full list of all node.js packages in use, please take a look at [client/pa
|
||||
<a id="privacy"></a>
|
||||
## Privacy
|
||||
|
||||
Our main idea is that you are the one, who should be in control of your data. So it is only natural, that AdGuard Home does not collect any usage statistics, and does not use any web services unless you configure it to do so. Full policy with every bit that _could in theory be_ sent by AdGuard Home is available [here](https://adguard.com/en/privacy/home.html).
|
||||
Our main idea is that you are the one, who should be in control of your data. So it is only natural, that AdGuard Home does not collect any usage statistics, and does not use any web services unless you configure it to do so. Full policy with every bit that _could in theory be_ sent by AdGuard Home is available [here](https://adguard.com/en/privacy/home.html).
|
||||
|
||||
349
client/package-lock.json
generated
vendored
349
client/package-lock.json
generated
vendored
@@ -2014,72 +2014,168 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@nivo/axes": {
|
||||
"version": "0.49.1",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/axes/-/axes-0.49.1.tgz",
|
||||
"integrity": "sha512-2ZqpKtnZ9HE30H+r565VCrypKRQzAoMbAg1hsht88dlNQRtghBSxbAS0Y4IUW/wgN/AzvOIBJHvxH7bgaB8Oow==",
|
||||
"@nivo/annotations": {
|
||||
"version": "0.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/annotations/-/annotations-0.64.0.tgz",
|
||||
"integrity": "sha512-b9VAVuAn2HztOZckU2GcBwptjCobYV5VgX/jwZTSFeZFLtjZza+QinNL2AbQ2cnmeU3nVCw1dTbJMMZ9fTCCNQ==",
|
||||
"requires": {
|
||||
"@nivo/core": "0.49.0",
|
||||
"d3-format": "^1.3.2",
|
||||
"@nivo/colors": "0.64.0",
|
||||
"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",
|
||||
"lodash": "^4.17.4",
|
||||
"react-motion": "^0.5.2",
|
||||
"recompose": "^0.26.0"
|
||||
"react-spring": "^8.0.27"
|
||||
},
|
||||
"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": {
|
||||
"version": "0.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.49.0.tgz",
|
||||
"integrity": "sha512-TCPMUO2aJ7fI+ZB6t3d3EBQtNxJnTzaxLJsrVyn/3AQIjUwccAeo2aIy81wLBGWGtlGNUDNdAbnFzXiJosH0yg==",
|
||||
"version": "0.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.64.0.tgz",
|
||||
"integrity": "sha512-tupETbvxgv4B9y3pcXy/lErMwY2aZht+FKSyah1dPFd88LnMD/DOL+to6ociBHmpLQNUMA7wid6R7BlXRY/bmg==",
|
||||
"requires": {
|
||||
"d3-color": "^1.0.3",
|
||||
"d3-format": "^1.3.2",
|
||||
"d3-color": "^1.2.3",
|
||||
"d3-format": "^1.4.4",
|
||||
"d3-hierarchy": "^1.1.8",
|
||||
"d3-interpolate": "^1.3.2",
|
||||
"d3-scale": "^2.1.2",
|
||||
"d3-interpolate": "^2.0.1",
|
||||
"d3-scale": "^3.0.0",
|
||||
"d3-scale-chromatic": "^1.3.3",
|
||||
"d3-shape": "^1.2.2",
|
||||
"d3-shape": "^1.3.5",
|
||||
"d3-time-format": "^2.1.3",
|
||||
"lodash": "^4.17.4",
|
||||
"react-measure": "^2.0.2",
|
||||
"react-motion": "^0.5.2",
|
||||
"recompose": "^0.26.0"
|
||||
"lodash": "^4.17.11",
|
||||
"react-spring": "^8.0.27",
|
||||
"recompose": "^0.30.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": {
|
||||
"version": "0.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.49.0.tgz",
|
||||
"integrity": "sha512-8KbUFYozqwD+/rj4in0mnF9b9CuyNFjVgXqm2KW3ODVlWIgYgjTVlEhlg9VZIArFPlIyyAjEYC88YSRcALHugg==",
|
||||
"version": "0.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.64.0.tgz",
|
||||
"integrity": "sha512-L7Mp/of/jY4qE7ef6PXJ8/e3aASBTfsf5BTOh3imSXZT6I4hXa5ppmGAgZ0gOSpcPXuMEjBc0aSIEJoeoytQ/g==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.4",
|
||||
"recompose": "^0.26.0"
|
||||
"@nivo/core": "0.64.0",
|
||||
"lodash": "^4.17.11",
|
||||
"recompose": "^0.30.0"
|
||||
}
|
||||
},
|
||||
"@nivo/line": {
|
||||
"version": "0.49.1",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.49.1.tgz",
|
||||
"integrity": "sha512-wKkOmpnwK2psmZbJReDq+Eh/WV9r1JA8V4Vl4eIRuf971CW0KUT9nCAoc/FcKio0qsiq5wyFt3J5LuAhfzlV/w==",
|
||||
"version": "0.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.64.0.tgz",
|
||||
"integrity": "sha512-WkQU28ZL9Mxq42AdmybWe+2qFh/TiUXu+7e6nj41e/8DO95Guxg1XQ+i5zQKuw/UZlqXZs6WOsMW8EMNE4GzXw==",
|
||||
"requires": {
|
||||
"@nivo/axes": "0.49.1",
|
||||
"@nivo/core": "0.49.0",
|
||||
"@nivo/legends": "0.49.0",
|
||||
"@nivo/scales": "0.49.0",
|
||||
"d3-format": "^1.3.2",
|
||||
"d3-scale": "^2.1.2",
|
||||
"d3-shape": "^1.2.2",
|
||||
"lodash": "^4.17.4",
|
||||
"react-motion": "^0.5.2",
|
||||
"recompose": "^0.26.0"
|
||||
"@nivo/annotations": "0.64.0",
|
||||
"@nivo/axes": "0.64.0",
|
||||
"@nivo/colors": "0.64.0",
|
||||
"@nivo/legends": "0.64.0",
|
||||
"@nivo/scales": "0.64.0",
|
||||
"@nivo/tooltip": "0.64.0",
|
||||
"@nivo/voronoi": "0.64.0",
|
||||
"d3-shape": "^1.3.5",
|
||||
"react-spring": "^8.0.27"
|
||||
}
|
||||
},
|
||||
"@nivo/scales": {
|
||||
"version": "0.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.49.0.tgz",
|
||||
"integrity": "sha512-+5Leu4zX6mDSAunf4ZJHeqVlT+ZsqiKXLB6hT/u7r3GjxZP9A+n3rHePhIzikBiBRMlLjyiBlylLzhKBAYbGWQ==",
|
||||
"version": "0.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.64.0.tgz",
|
||||
"integrity": "sha512-Jbr1rlfe/gLCippndPaCM7doJzzx1K9oXPxS4JiyvrIUkQoMWiozymZQdEp8kgigI6uwWu5xvPwCOFXalCIhKg==",
|
||||
"requires": {
|
||||
"d3-scale": "^2.1.2",
|
||||
"d3-scale": "^3.0.0",
|
||||
"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": {
|
||||
@@ -2970,12 +3066,6 @@
|
||||
"pkg-up": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001062",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001062.tgz",
|
||||
"integrity": "sha512-ei9ZqeOnN7edDrb24QfJ0OZicpEbsWxv7WusOiQGz/f2SfvBgHHbOEwBJ8HKGVSyx8Z6ndPjxzR6m0NQq+0bfw==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "7.0.30",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.30.tgz",
|
||||
@@ -3832,9 +3922,9 @@
|
||||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001059",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001059.tgz",
|
||||
"integrity": "sha512-oOrc+jPJWooKIA0IrNZ5sYlsXc7NP7KLhNWrSGEJhnfSzDvDJ0zd3i6HXsslExY9bbu+x0FQ5C61LcqmPt7bOQ==",
|
||||
"version": "1.0.30001165",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001165.tgz",
|
||||
"integrity": "sha512-8cEsSMwXfx7lWSUMA2s08z9dIgsnR5NAqjXP23stdsU3AUWkCr/rr4s4OFtHXn5XXr6+7kam3QFVoYyXNPdJPA==",
|
||||
"dev": true
|
||||
},
|
||||
"capture-exit": {
|
||||
@@ -4681,24 +4771,27 @@
|
||||
"dev": true
|
||||
},
|
||||
"d3-array": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
|
||||
"integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
|
||||
},
|
||||
"d3-collection": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz",
|
||||
"integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.8.0.tgz",
|
||||
"integrity": "sha512-6V272gsOeg7+9pTW1jSYOR1QE37g95I3my1hBmY+vOUNHRrk9yt4OTz/gK7PMkVAVDrYYq4mq3grTiZ8iJdNIw=="
|
||||
},
|
||||
"d3-color": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz",
|
||||
"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": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.4.tgz",
|
||||
"integrity": "sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw=="
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz",
|
||||
"integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA=="
|
||||
},
|
||||
"d3-hierarchy": {
|
||||
"version": "1.1.9",
|
||||
@@ -4706,11 +4799,11 @@
|
||||
"integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ=="
|
||||
},
|
||||
"d3-interpolate": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz",
|
||||
"integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz",
|
||||
"integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==",
|
||||
"requires": {
|
||||
"d3-color": "1"
|
||||
"d3-color": "1 - 2"
|
||||
}
|
||||
},
|
||||
"d3-path": {
|
||||
@@ -4719,16 +4812,15 @@
|
||||
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
|
||||
},
|
||||
"d3-scale": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz",
|
||||
"integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==",
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.2.3.tgz",
|
||||
"integrity": "sha512-8E37oWEmEzj57bHcnjPVOBS3n4jqakOeuv1EDdQSiSrYnMCBdMd3nc4HtKk7uia8DUHcY/CGuJ42xxgtEYrX0g==",
|
||||
"requires": {
|
||||
"d3-array": "^1.2.0",
|
||||
"d3-collection": "1",
|
||||
"d3-format": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-time": "1",
|
||||
"d3-time-format": "2"
|
||||
"d3-array": "^2.3.0",
|
||||
"d3-format": "1 - 2",
|
||||
"d3-interpolate": "1.2.0 - 2",
|
||||
"d3-time": "1 - 2",
|
||||
"d3-time-format": "2 - 3"
|
||||
}
|
||||
},
|
||||
"d3-scale-chromatic": {
|
||||
@@ -4738,6 +4830,16 @@
|
||||
"requires": {
|
||||
"d3-color": "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": {
|
||||
@@ -4749,16 +4851,16 @@
|
||||
}
|
||||
},
|
||||
"d3-time": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
|
||||
"integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.0.0.tgz",
|
||||
"integrity": "sha512-2mvhstTFcMvwStWd9Tj3e6CEqtOivtD8AUiHT8ido/xmzrI9ijrUUihZ6nHuf/vsScRBonagOdj0Vv+SEL5G3Q=="
|
||||
},
|
||||
"d3-time-format": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.3.tgz",
|
||||
"integrity": "sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
|
||||
"integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
|
||||
"requires": {
|
||||
"d3-time": "1"
|
||||
"d3-time": "1 - 2"
|
||||
}
|
||||
},
|
||||
"damerau-levenshtein": {
|
||||
@@ -4963,6 +5065,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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@@ -5253,11 +5360,21 @@
|
||||
"dev": true
|
||||
},
|
||||
"encoding": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
||||
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"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": {
|
||||
@@ -6766,11 +6883,6 @@
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"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": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
|
||||
@@ -7430,6 +7542,7 @@
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
@@ -10194,6 +10307,16 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz",
|
||||
"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": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
||||
@@ -12403,17 +12526,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"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": {
|
||||
"version": "3.11.2",
|
||||
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.11.2.tgz",
|
||||
@@ -12576,6 +12688,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": {
|
||||
"version": "6.11.4",
|
||||
"resolved": "https://registry.npmjs.org/react-table/-/react-table-6.11.4.tgz",
|
||||
@@ -12663,13 +12784,15 @@
|
||||
}
|
||||
},
|
||||
"recompose": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.26.0.tgz",
|
||||
"integrity": "sha512-KwOu6ztO0mN5vy3+zDcc45lgnaUoaQse/a5yLVqtzTK13czSWnFGmXbQVmnoMgDkI5POd1EwIKSbjU1V7xdZog==",
|
||||
"version": "0.30.0",
|
||||
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz",
|
||||
"integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"change-emitter": "^0.1.2",
|
||||
"fbjs": "^0.8.1",
|
||||
"hoist-non-react-statics": "^2.3.1",
|
||||
"react-lifecycles-compat": "^3.0.2",
|
||||
"symbol-observable": "^1.0.4"
|
||||
}
|
||||
},
|
||||
@@ -15067,9 +15190,9 @@
|
||||
}
|
||||
},
|
||||
"ua-parser-js": {
|
||||
"version": "0.7.21",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
|
||||
"integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ=="
|
||||
"version": "0.7.22",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
||||
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
|
||||
},
|
||||
"unherit": {
|
||||
"version": "1.1.3",
|
||||
@@ -16086,9 +16209,9 @@
|
||||
}
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
|
||||
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz",
|
||||
"integrity": "sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A=="
|
||||
},
|
||||
"whatwg-mimetype": {
|
||||
"version": "2.3.0",
|
||||
|
||||
2
client/package.json
vendored
2
client/package.json
vendored
@@ -13,7 +13,7 @@
|
||||
"test:watch": "jest --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nivo/line": "^0.49.1",
|
||||
"@nivo/line": "^0.64.0",
|
||||
"axios": "^0.19.2",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^1.29.0",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"form_error_ip_format": "Invalid IP format",
|
||||
"form_error_mac_format": "Invalid MAC format",
|
||||
"form_error_client_id_format": "Invalid client ID format",
|
||||
"form_error_server_name": "Invalid server name or wildcard certificate",
|
||||
"form_error_positive": "Must be greater than 0",
|
||||
"form_error_negative": "Must be equal to 0 or greater",
|
||||
"range_end_error": "Must be greater than range start",
|
||||
@@ -249,6 +250,7 @@
|
||||
"blocking_ipv6": "Blocking IPv6",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"dns_over_quic": "DNS-over-QUIC",
|
||||
"download_mobileconfig_doh": "Download .mobileconfig for DNS-over-HTTPS",
|
||||
"download_mobileconfig_dot": "Download .mobileconfig for DNS-over-TLS",
|
||||
"plain_dns": "Plain DNS",
|
||||
@@ -330,7 +332,7 @@
|
||||
"encryption_config_saved": "Encryption config saved",
|
||||
"encryption_server": "Server name",
|
||||
"encryption_server_enter": "Enter your domain name",
|
||||
"encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate.",
|
||||
"encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate or wildcard certificate. If the field is not set, it will accept TLS connections for any domain.",
|
||||
"encryption_redirect": "Redirect to HTTPS automatically",
|
||||
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
|
||||
"encryption_https": "HTTPS port",
|
||||
@@ -386,7 +388,7 @@
|
||||
"client_edit": "Edit Client",
|
||||
"client_identifier": "Identifier",
|
||||
"ip_address": "IP address",
|
||||
"client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address. Please note that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server</0>",
|
||||
"client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address or domain. Please note that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server</0>",
|
||||
"form_enter_ip": "Enter IP",
|
||||
"form_enter_mac": "Enter MAC",
|
||||
"form_enter_id": "Enter identifier",
|
||||
@@ -587,4 +589,4 @@
|
||||
"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.",
|
||||
"experimental": "Experimental"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,15 +273,15 @@ describe('sortIp', () => {
|
||||
});
|
||||
});
|
||||
describe('invalid input', () => {
|
||||
const originalError = console.error;
|
||||
const originalWarn = console.warn;
|
||||
|
||||
beforeEach(() => {
|
||||
console.error = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
expect(console.error).toHaveBeenCalled();
|
||||
console.error = originalError;
|
||||
expect(console.warn).toHaveBeenCalled();
|
||||
console.warn = originalWarn;
|
||||
});
|
||||
|
||||
test('invalid strings', () => {
|
||||
|
||||
@@ -51,9 +51,10 @@ export const toggleClientBlockSuccess = createAction('TOGGLE_CLIENT_BLOCK_SUCCES
|
||||
export const toggleClientBlock = (ip, disallowed, disallowed_rule) => async (dispatch) => {
|
||||
dispatch(toggleClientBlockRequest());
|
||||
try {
|
||||
const {
|
||||
allowed_clients, blocked_hosts, disallowed_clients = [],
|
||||
} = await apiClient.getAccessList();
|
||||
const accessList = await apiClient.getAccessList();
|
||||
const allowed_clients = accessList.allowed_clients ?? [];
|
||||
const blocked_hosts = accessList.blocked_hosts ?? [];
|
||||
const disallowed_clients = accessList.disallowed_clients ?? [];
|
||||
|
||||
const updatedDisallowedClients = disallowed
|
||||
? disallowed_clients.filter((client) => client !== disallowed_rule)
|
||||
|
||||
@@ -373,10 +373,14 @@ export const getDhcpStatusFailure = createAction('GET_DHCP_STATUS_FAILURE');
|
||||
export const getDhcpStatus = () => async (dispatch) => {
|
||||
dispatch(getDhcpStatusRequest());
|
||||
try {
|
||||
const status = await apiClient.getDhcpStatus();
|
||||
const globalStatus = await apiClient.getGlobalStatus();
|
||||
status.dhcp_available = globalStatus.dhcp_available;
|
||||
dispatch(getDhcpStatusSuccess(status));
|
||||
if (globalStatus.dhcp_available) {
|
||||
const status = await apiClient.getDhcpStatus();
|
||||
status.dhcp_available = globalStatus.dhcp_available;
|
||||
dispatch(getDhcpStatusSuccess(status));
|
||||
} else {
|
||||
dispatch(getDhcpStatusFailure());
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getDhcpStatusFailure());
|
||||
|
||||
@@ -22,11 +22,6 @@
|
||||
border-bottom: 6px solid #585965;
|
||||
}
|
||||
|
||||
.card-chart-bg {
|
||||
left: -20px;
|
||||
width: calc(100% + 20px);
|
||||
}
|
||||
|
||||
@media (max-width: 1279.98px) {
|
||||
.table__action {
|
||||
position: absolute;
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { sortIp } from '../../../helpers/helpers';
|
||||
|
||||
class Table extends Component {
|
||||
cellWrap = ({ value }) => (
|
||||
@@ -21,6 +22,7 @@ class Table extends Component {
|
||||
{
|
||||
Header: this.props.t('answer'),
|
||||
accessor: 'answer',
|
||||
sortMethod: sortIp,
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
--gray-4d: #4D4D4D;
|
||||
--gray-f3: #F3F3F3;
|
||||
--gray-8: #888;
|
||||
--gray-3: #333;
|
||||
--danger: #DF3812;
|
||||
--white80: rgba(255, 255, 255, 0.8);
|
||||
|
||||
|
||||
@@ -259,7 +259,7 @@ let Form = (props) => {
|
||||
</div>
|
||||
<div className="form__desc mt-0 mb-2">
|
||||
<Trans components={[
|
||||
<a href="https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists#ctag"
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists#ctag"
|
||||
key="0">link</a>,
|
||||
]}>
|
||||
tags_desc
|
||||
|
||||
@@ -72,30 +72,30 @@ const Interfaces = () => {
|
||||
(store) => store.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name,
|
||||
);
|
||||
|
||||
if (processingInterfaces || !interfaces) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const interfaceValue = interface_name && interfaces[interface_name];
|
||||
|
||||
return !processingInterfaces
|
||||
&& interfaces
|
||||
&& <>
|
||||
<div className="row dhcp__interfaces">
|
||||
<div className="col col__dhcp">
|
||||
<Field
|
||||
name="interface_name"
|
||||
component={renderSelectField}
|
||||
className="form-control custom-select pl-4 col-md"
|
||||
validate={[validateRequiredValue]}
|
||||
label='dhcp_interface_select'
|
||||
>
|
||||
<option value='' disabled={enabled}>
|
||||
{t('dhcp_interface_select')}
|
||||
</option>
|
||||
{renderInterfaces(interfaces)}
|
||||
</Field>
|
||||
</div>
|
||||
{interfaceValue
|
||||
&& renderInterfaceValues(interfaceValue)}
|
||||
</div>
|
||||
</>;
|
||||
return <div className="row dhcp__interfaces">
|
||||
<div className="col col__dhcp">
|
||||
<Field
|
||||
name="interface_name"
|
||||
component={renderSelectField}
|
||||
className="form-control custom-select pl-4 col-md"
|
||||
validate={[validateRequiredValue]}
|
||||
label='dhcp_interface_select'
|
||||
>
|
||||
<option value='' disabled={enabled}>
|
||||
{t('dhcp_interface_select')}
|
||||
</option>
|
||||
{renderInterfaces(interfaces)}
|
||||
</Field>
|
||||
</div>
|
||||
{interfaceValue
|
||||
&& renderInterfaceValues(interfaceValue)}
|
||||
</div>;
|
||||
};
|
||||
|
||||
renderInterfaceValues.propTypes = {
|
||||
|
||||
@@ -65,9 +65,14 @@ const Dhcp = () => {
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getDhcpStatus());
|
||||
dispatch(getDhcpInterfaces());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (dhcp_available) {
|
||||
dispatch(getDhcpInterfaces());
|
||||
}
|
||||
}, [dhcp_available]);
|
||||
|
||||
useEffect(() => {
|
||||
const [ipv4] = interfaces?.[interface_name]?.ipv4_addresses ?? [];
|
||||
const [ipv6] = interfaces?.[interface_name]?.ipv6_addresses ?? [];
|
||||
@@ -108,9 +113,6 @@ const Dhcp = () => {
|
||||
const enteredSomeValue = enteredSomeV4Value || enteredSomeV6Value || interfaceName;
|
||||
|
||||
const getToggleDhcpButton = () => {
|
||||
const otherDhcpFound = check && (check.v4.other_server.found === STATUS_RESPONSE.YES
|
||||
|| check.v6.other_server.found === STATUS_RESPONSE.YES);
|
||||
|
||||
const filledConfig = interface_name && (Object.values(v4)
|
||||
.every(Boolean) || Object.values(v6)
|
||||
.every(Boolean));
|
||||
@@ -136,7 +138,7 @@ const Dhcp = () => {
|
||||
className={className}
|
||||
onClick={enabled ? onClickDisable : onClickEnable}
|
||||
disabled={processingDhcp || processingConfig
|
||||
|| (!enabled && (!filledConfig || !check || otherDhcpFound))}
|
||||
|| (!enabled && (!filledConfig || !check))}
|
||||
>
|
||||
<Trans>{enabled ? 'dhcp_disable' : 'dhcp_enable'}</Trans>
|
||||
</button>;
|
||||
|
||||
@@ -50,7 +50,7 @@ const CertificateStatus = ({
|
||||
{dnsNames && (
|
||||
<li>
|
||||
<Trans>encryption_hostnames</Trans>:
|
||||
{dnsNames}
|
||||
{dnsNames.join(', ')}
|
||||
</li>
|
||||
)}
|
||||
</Fragment>
|
||||
@@ -65,7 +65,7 @@ CertificateStatus.propTypes = {
|
||||
subject: PropTypes.string,
|
||||
issuer: PropTypes.string,
|
||||
notAfter: PropTypes.string,
|
||||
dnsNames: PropTypes.string,
|
||||
dnsNames: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
export default withTranslation()(CertificateStatus);
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
toNumber,
|
||||
} from '../../../helpers/form';
|
||||
import {
|
||||
validateIsSafePort, validatePort, validatePortQuic, validatePortTLS,
|
||||
validateServerName, validateIsSafePort, validatePort, validatePortQuic, validatePortTLS,
|
||||
} from '../../../helpers/validators';
|
||||
import i18n from '../../../i18n';
|
||||
import KeyStatus from './KeyStatus';
|
||||
@@ -127,6 +127,7 @@ let Form = (props) => {
|
||||
placeholder={t('encryption_server_enter')}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
validate={validateServerName}
|
||||
/>
|
||||
<div className="form__desc">
|
||||
<Trans>encryption_server_desc</Trans>
|
||||
@@ -232,7 +233,7 @@ let Form = (props) => {
|
||||
<Trans
|
||||
values={{ link: 'letsencrypt.org' }}
|
||||
components={[
|
||||
<a href="https://letsencrypt.org/" key="0">
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://letsencrypt.org/" key="0">
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
@@ -413,7 +414,7 @@ Form.propTypes = {
|
||||
valid_key: PropTypes.bool,
|
||||
valid_cert: PropTypes.bool,
|
||||
valid_pair: PropTypes.bool,
|
||||
dns_names: PropTypes.string,
|
||||
dns_names: PropTypes.arrayOf(PropTypes.string),
|
||||
key_type: PropTypes.string,
|
||||
issuer: PropTypes.string,
|
||||
subject: PropTypes.string,
|
||||
|
||||
@@ -7,7 +7,7 @@ import flow from 'lodash/flow';
|
||||
import { CheckboxField, toNumber } from '../../../helpers/form';
|
||||
import {
|
||||
FILTERS_INTERVALS_HOURS,
|
||||
FILTERS_LINK,
|
||||
FILTERS_RELATIVE_LINK,
|
||||
FORM_NAME,
|
||||
} from '../../../helpers/constants';
|
||||
|
||||
@@ -45,7 +45,7 @@ const Form = (props) => {
|
||||
} = props;
|
||||
|
||||
const components = {
|
||||
a: <a href={FILTERS_LINK} rel="noopener noreferrer" />,
|
||||
a: <a href={FILTERS_RELATIVE_LINK} rel="noopener noreferrer" />,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
.card-body-stats {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
height: calc(100% - 3rem);
|
||||
margin: 0;
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,50 @@ import { useSelector } from 'react-redux';
|
||||
import Topline from './Topline';
|
||||
import { EMPTY_DATE } from '../../helpers/constants';
|
||||
|
||||
const EXPIRATION_ENUM = {
|
||||
VALID: 'VALID',
|
||||
EXPIRED: 'EXPIRED',
|
||||
EXPIRING: 'EXPIRING',
|
||||
};
|
||||
|
||||
const EXPIRATION_STATE = {
|
||||
[EXPIRATION_ENUM.EXPIRED]: {
|
||||
toplineType: 'danger',
|
||||
i18nKey: 'topline_expired_certificate',
|
||||
},
|
||||
[EXPIRATION_ENUM.EXPIRING]: {
|
||||
toplineType: 'warning',
|
||||
i18nKey: 'topline_expiring_certificate',
|
||||
},
|
||||
};
|
||||
|
||||
const getExpirationFlags = (not_after) => {
|
||||
const DAYS_BEFORE_EXPIRATION = 5;
|
||||
|
||||
const now = Date.now();
|
||||
const isExpiring = isAfter(addDays(now, DAYS_BEFORE_EXPIRATION), not_after);
|
||||
const isExpired = isAfter(now, not_after);
|
||||
|
||||
return {
|
||||
isExpiring,
|
||||
isExpired,
|
||||
};
|
||||
};
|
||||
|
||||
const getExpirationEnumKey = (not_after) => {
|
||||
const { isExpiring, isExpired } = getExpirationFlags(not_after);
|
||||
|
||||
if (isExpired) {
|
||||
return EXPIRATION_ENUM.EXPIRED;
|
||||
}
|
||||
|
||||
if (isExpiring) {
|
||||
return EXPIRATION_ENUM.EXPIRING;
|
||||
}
|
||||
|
||||
return EXPIRATION_ENUM.VALID;
|
||||
};
|
||||
|
||||
const EncryptionTopline = () => {
|
||||
const not_after = useSelector((state) => state.encryption.not_after);
|
||||
|
||||
@@ -13,30 +57,21 @@ const EncryptionTopline = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isAboutExpire = isAfter(addDays(Date.now(), 30), not_after);
|
||||
const isExpired = isAfter(Date.now(), not_after);
|
||||
const expirationStateKey = getExpirationEnumKey(not_after);
|
||||
|
||||
if (isExpired) {
|
||||
return (
|
||||
<Topline type="danger">
|
||||
<Trans components={[<a href="#encryption" key="0">link</a>]}>
|
||||
topline_expired_certificate
|
||||
</Trans>
|
||||
</Topline>
|
||||
);
|
||||
if (expirationStateKey === EXPIRATION_ENUM.VALID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isAboutExpire) {
|
||||
return (
|
||||
<Topline type="warning">
|
||||
const { toplineType, i18nKey } = EXPIRATION_STATE[expirationStateKey];
|
||||
|
||||
return (
|
||||
<Topline type={toplineType}>
|
||||
<Trans components={[<a href="#encryption" key="0">link</a>]}>
|
||||
topline_expiring_certificate
|
||||
{i18nKey}
|
||||
</Trans>
|
||||
</Topline>
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
);
|
||||
};
|
||||
|
||||
export default EncryptionTopline;
|
||||
|
||||
@@ -2,22 +2,27 @@ import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import i18next from 'i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Tabs from './Tabs';
|
||||
import Icons from './Icons';
|
||||
import { getPathWithQueryString } from '../../helpers/helpers';
|
||||
|
||||
const MOBILE_CONFIG_LINKS = {
|
||||
DOT: '/apple/dot.mobileconfig',
|
||||
DOH: '/apple/doh.mobileconfig',
|
||||
};
|
||||
|
||||
const renderMobileconfigInfo = ({ label, components }) => <li key={label}>
|
||||
/* FIXME: find out `client_id` */
|
||||
const renderMobileconfigInfo = ({ label, components, server_name }) => <li key={label}>
|
||||
<Trans components={components}>{label}</Trans>
|
||||
<ul>
|
||||
<li>
|
||||
<a href={MOBILE_CONFIG_LINKS.DOT} download>{i18next.t('download_mobileconfig_dot')}</a>
|
||||
<a href={getPathWithQueryString(MOBILE_CONFIG_LINKS.DOT, { host: server_name, client_id: 'client_id' })}
|
||||
download>{i18next.t('download_mobileconfig_dot')}</a>
|
||||
</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, client_id: 'client_id' })}
|
||||
download>{i18next.t('download_mobileconfig_doh')}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>;
|
||||
@@ -38,37 +43,8 @@ const renderLi = ({ label, components }) => <li key={label}>
|
||||
</Trans>
|
||||
</li>;
|
||||
|
||||
const dnsPrivacyList = [{
|
||||
title: 'Android',
|
||||
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: [
|
||||
const getDnsPrivacyList = (server_name) => {
|
||||
const iosList = [
|
||||
{
|
||||
label: 'setup_dns_privacy_ios_2',
|
||||
components: [
|
||||
@@ -79,13 +55,6 @@ const dnsPrivacyList = [{
|
||||
<code key="1">text</code>,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'setup_dns_privacy_4',
|
||||
components: {
|
||||
highlight: <code />,
|
||||
},
|
||||
renderComponent: renderMobileconfigInfo,
|
||||
},
|
||||
{
|
||||
label: 'setup_dns_privacy_ios_1',
|
||||
components: [
|
||||
@@ -93,68 +62,114 @@ const dnsPrivacyList = [{
|
||||
key: 0,
|
||||
href: 'https://itunes.apple.com/app/id1452162351',
|
||||
},
|
||||
<code key="1">text</code>,
|
||||
{
|
||||
key: 2,
|
||||
href: 'https://dnscrypt.info/stamps',
|
||||
},
|
||||
<code key="1">text</code>,
|
||||
{
|
||||
key: 2,
|
||||
href: 'https://dnscrypt.info/stamps',
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'setup_dns_privacy_other_title',
|
||||
list: [
|
||||
{
|
||||
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',
|
||||
},
|
||||
}];
|
||||
/* Insert second element if can generate .mobileconfig links */
|
||||
if (server_name) {
|
||||
iosList.splice(1, 0, {
|
||||
label: 'setup_dns_privacy_4',
|
||||
components: {
|
||||
highlight: <code />,
|
||||
},
|
||||
renderComponent: ({ label, components }) => renderMobileconfigInfo({
|
||||
label,
|
||||
components,
|
||||
server_name,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return [{
|
||||
title: 'Android',
|
||||
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_other_4',
|
||||
components: [
|
||||
{
|
||||
key: 0,
|
||||
href: 'https://support.mozilla.org/kb/firefox-dns-over-https',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'setup_dns_privacy_android_3',
|
||||
components: [
|
||||
{
|
||||
key: 0,
|
||||
href: 'https://getintra.org/',
|
||||
},
|
||||
<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',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'iOS',
|
||||
list: iosList,
|
||||
},
|
||||
{
|
||||
title: 'setup_dns_privacy_other_title',
|
||||
list: [
|
||||
{
|
||||
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}>
|
||||
<strong><Trans>{title}</Trans></strong>
|
||||
@@ -172,6 +187,7 @@ const getTabs = ({
|
||||
tlsAddress,
|
||||
httpsAddress,
|
||||
showDnsPrivacyNotice,
|
||||
server_name,
|
||||
t,
|
||||
}) => ({
|
||||
Router: {
|
||||
@@ -277,7 +293,7 @@ const getTabs = ({
|
||||
setup_dns_privacy_3
|
||||
</Trans>
|
||||
</div>
|
||||
{dnsPrivacyList.map(renderDnsPrivacyList)}
|
||||
{getDnsPrivacyList(server_name).map(renderDnsPrivacyList)}
|
||||
</>}
|
||||
</div>
|
||||
</div>;
|
||||
@@ -299,6 +315,7 @@ const renderContent = ({ title, list, getTitle }) => <div key={title} label={i18
|
||||
|
||||
const Guide = ({ dnsAddresses }) => {
|
||||
const { t } = useTranslation();
|
||||
const server_name = useSelector((state) => state.encryption.server_name);
|
||||
const tlsAddress = dnsAddresses?.filter((item) => item.includes('tls://')) ?? '';
|
||||
const httpsAddress = dnsAddresses?.filter((item) => item.includes('https://')) ?? '';
|
||||
const showDnsPrivacyNotice = httpsAddress.length < 1 && tlsAddress.length < 1;
|
||||
@@ -309,6 +326,7 @@ const Guide = ({ dnsAddresses }) => {
|
||||
tlsAddress,
|
||||
httpsAddress,
|
||||
showDnsPrivacyNotice,
|
||||
server_name,
|
||||
t,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
.line__tooltip {
|
||||
padding: 2px 10px 7px;
|
||||
line-height: 1.1;
|
||||
color: #fff;
|
||||
color: var(--white);
|
||||
background-color: var(--gray-3);
|
||||
border-radius: 4px;
|
||||
opacity: 90%;
|
||||
}
|
||||
|
||||
.line__tooltip-text {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.card-chart-bg path[d^="M0,32"] {
|
||||
transform: translateY(32px);
|
||||
}
|
||||
|
||||
@@ -1,56 +1,75 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
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';
|
||||
|
||||
const Line = ({ data, color }) => data
|
||||
&& <ResponsiveLine
|
||||
const Line = ({
|
||||
data, color = 'black',
|
||||
}) => {
|
||||
const interval = useSelector((state) => state.stats.interval);
|
||||
|
||||
return <ResponsiveLine
|
||||
enableArea
|
||||
animate
|
||||
enableSlices="x"
|
||||
curve="linear"
|
||||
colors={[color]}
|
||||
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={{
|
||||
tooltip: {
|
||||
container: {
|
||||
padding: '0',
|
||||
background: '#333',
|
||||
borderRadius: '4px',
|
||||
crosshair: {
|
||||
line: {
|
||||
stroke: 'black',
|
||||
strokeWidth: 1,
|
||||
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 = {
|
||||
data: PropTypes.array.isRequired,
|
||||
color: PropTypes.string.isRequired,
|
||||
color: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
};
|
||||
|
||||
export default Line;
|
||||
|
||||
@@ -13452,10 +13452,8 @@ a.icon:hover {
|
||||
|
||||
.card-chart-bg {
|
||||
height: 4rem;
|
||||
margin-top: -1rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-options {
|
||||
|
||||
@@ -13,6 +13,10 @@ export const R_MAC = /^((([a-fA-F0-9][a-fA-F0-9]+[-]){5}|([a-fA-F0-9][a-fA-F0-9]
|
||||
|
||||
export const R_CIDR_IPV6 = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))$/;
|
||||
|
||||
export const R_DOMAIN = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/;
|
||||
|
||||
export const R_SERVER_NAME = /^(\*\.)?[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/;
|
||||
|
||||
export const R_PATH_LAST_PART = /\/[^/]*$/;
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
@@ -53,10 +57,10 @@ export const REPOSITORY = {
|
||||
export const PRIVACY_POLICY_LINK = 'https://adguard.com/privacy/home.html';
|
||||
export const PORT_53_FAQ_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ#bindinuse';
|
||||
export const UPSTREAM_CONFIGURATION_WIKI_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams';
|
||||
export const FILTERS_LINK = '#filters';
|
||||
|
||||
export const GETTING_STARTED_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update';
|
||||
|
||||
export const FILTERS_RELATIVE_LINK = '#filters';
|
||||
|
||||
export const ADDRESS_IN_USE_TEXT = 'address already in use';
|
||||
|
||||
export const INSTALL_FIRST_STEP = 1;
|
||||
@@ -475,6 +479,7 @@ export const BLOCK_ACTIONS = {
|
||||
export const SCHEME_TO_PROTOCOL_MAP = {
|
||||
doh: 'dns_over_https',
|
||||
dot: 'dns_over_tls',
|
||||
doq: 'dns_over_quic',
|
||||
'': 'plain_dns',
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import 'url-polyfill';
|
||||
import dateParse from 'date-fns/parse';
|
||||
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 axios from 'axios';
|
||||
import i18n from 'i18next';
|
||||
@@ -105,21 +101,10 @@ export const normalizeLogs = (logs) => logs.map((log) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const normalizeHistory = (history, interval) => {
|
||||
if (interval === 1 || interval === 7) {
|
||||
const hoursAgo = subHours(Date.now(), 24 * interval);
|
||||
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 normalizeHistory = (history) => history.map((item, idx) => ({
|
||||
x: idx,
|
||||
y: item,
|
||||
}));
|
||||
|
||||
export const normalizeTopStats = (stats) => (
|
||||
stats.map((item) => ({
|
||||
@@ -702,7 +687,7 @@ export const sortIp = (a, b) => {
|
||||
|
||||
return 0;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.warn(e);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ export const getTextareaCommentsHighlight = (
|
||||
) => {
|
||||
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) => {
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
R_URL_REQUIRES_PROTOCOL,
|
||||
STANDARD_WEB_PORT,
|
||||
UNSAFE_PORTS,
|
||||
R_DOMAIN,
|
||||
R_SERVER_NAME,
|
||||
} from './constants';
|
||||
import { getLastIpv4Octet, isValidAbsolutePath } from './form';
|
||||
|
||||
@@ -71,12 +73,28 @@ export const validateClientId = (value) => {
|
||||
|| R_MAC.test(formattedValue)
|
||||
|| R_CIDR.test(formattedValue)
|
||||
|| R_CIDR_IPV6.test(formattedValue)
|
||||
|| R_DOMAIN.test(formattedValue)
|
||||
)) {
|
||||
return 'form_error_client_id_format';
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param value {string}
|
||||
* @returns {undefined|string}
|
||||
*/
|
||||
export const validateServerName = (value) => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
const formattedValue = value ? value.trim() : value;
|
||||
if (formattedValue && !R_SERVER_NAME.test(formattedValue)) {
|
||||
return 'form_error_server_name';
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param value {string}
|
||||
* @returns {undefined|string}
|
||||
|
||||
@@ -12,7 +12,7 @@ const renderItem = ({
|
||||
|
||||
return <li key={ip}>{isDns
|
||||
? <strong>{dnsAddress}</strong>
|
||||
: <a href={webAddress}>{webAddress}</a>
|
||||
: <a href={webAddress} target="_blank" rel="noopener noreferrer">{webAddress}</a>
|
||||
}
|
||||
</li>;
|
||||
};
|
||||
|
||||
@@ -9,6 +9,8 @@ const encryption = handleActions({
|
||||
const newState = {
|
||||
...state,
|
||||
...payload,
|
||||
/* TODO: handle property delete on api refactor */
|
||||
server_name: payload.server_name || '',
|
||||
processing: false,
|
||||
};
|
||||
return newState;
|
||||
@@ -20,6 +22,7 @@ const encryption = handleActions({
|
||||
const newState = {
|
||||
...state,
|
||||
...payload,
|
||||
server_name: payload.server_name || '',
|
||||
processingConfig: false,
|
||||
};
|
||||
return newState;
|
||||
@@ -49,6 +52,7 @@ const encryption = handleActions({
|
||||
subject,
|
||||
warning_validation,
|
||||
dns_names,
|
||||
server_name: payload.server_name || '',
|
||||
processingValidate: false,
|
||||
};
|
||||
return newState;
|
||||
|
||||
1
client/webpack.dev.js
vendored
1
client/webpack.dev.js
vendored
@@ -44,7 +44,6 @@ const getDevServerConfig = (proxyUrl = BASE_URL) => {
|
||||
proxy: {
|
||||
[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
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.32.6
|
||||
github.com/AdguardTeam/golibs v0.4.2
|
||||
github.com/AdguardTeam/urlfilter v0.12.3
|
||||
github.com/AdguardTeam/dnsproxy v0.33.2
|
||||
github.com/AdguardTeam/golibs v0.4.4
|
||||
github.com/AdguardTeam/urlfilter v0.13.0
|
||||
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/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/v2 v2.8.1 // indirect
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714
|
||||
github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8
|
||||
github.com/joomcode/errorx v1.0.3
|
||||
github.com/kardianos/service v1.1.0
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.1 // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8
|
||||
github.com/joomcode/errorx v1.0.3 // indirect
|
||||
github.com/kardianos/service v1.2.0
|
||||
github.com/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/raw v0.0.0-20191009151244-50f2db8cc065
|
||||
github.com/miekg/dns v1.1.31
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/rogpeppe/go-internal v1.6.2 // indirect
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/sirupsen/logrus v1.6.0 // indirect
|
||||
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/u-root/u-root v6.0.0+incompatible
|
||||
go.etcd.io/bbolt v1.3.4
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73
|
||||
golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20
|
||||
golang.org/x/text v0.3.3 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0 // indirect
|
||||
github.com/spf13/cobra v1.1.1 // indirect
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/u-root/u-root v7.0.0+incompatible
|
||||
go.etcd.io/bbolt v1.3.5
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
|
||||
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/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.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.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/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/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=
|
||||
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.32.6/go.mod h1:ZLDrKIypYxBDz2N9FQHgeehuHrwTbuhZXdGwNySshbw=
|
||||
github.com/AdguardTeam/dnsproxy v0.33.2 h1:k5aMcsw3TA/G2DR8EjIkwutDPuuRkKh8xij4cFWC6Fk=
|
||||
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.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o=
|
||||
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/urlfilter v0.12.3 h1:FMjQG0eTgrr8xA3z2zaLVcCgGdpzoECPGWwgPjtwPNs=
|
||||
github.com/AdguardTeam/urlfilter v0.12.3/go.mod h1:1fcCQx5TGJANrQN6sHNNM9KPBl7qx7BJml45ko6vru0=
|
||||
github.com/AdguardTeam/urlfilter v0.13.0 h1:MfO46K81JVTkhgP6gRu/buKl5wAOSfusjiDwjT1JN1c=
|
||||
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/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/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/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/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/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
||||
github.com/ameshkov/dnscrypt v1.1.0 h1:2vAt5dD6ZmqlAxEAfzRcLBnkvdf8NI46Kn9InSwQbSI=
|
||||
github.com/ameshkov/dnscrypt v1.1.0/go.mod h1:ikduAxNLCTEfd1AaCgpIA5TgroIVQ8JY3Vb095fiFJg=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
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/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/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/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/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/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
||||
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/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/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/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
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.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-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-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/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/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/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/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/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/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
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/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-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/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/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||
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.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/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/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/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
|
||||
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.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.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/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-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/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.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.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
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.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
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 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.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
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.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.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-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/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 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/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/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.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/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/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
|
||||
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/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-20200621044212-d74cd86ad5b8/go.mod h1:CfMdguCK66I5DAUJgGKyNz8aB6vO5dZzkm9Xep6WGvw=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8 h1:R1oP0/QEyvaL7dm+mBQouQ9V1X6gqQr5taZA1yaq5zQ=
|
||||
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/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
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/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/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
|
||||
github.com/joomcode/errorx v1.0.3 h1:3e1mi0u7/HTPNdg6d6DYyKGBhA5l9XpsfuVE29NxnWw=
|
||||
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/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/kardianos/service v1.1.0/go.mod h1:RrJI2xn5vve/r32U5suTbeaSGoMU6GbNPoj36CVYcHc=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
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/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/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/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
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.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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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.0/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg=
|
||||
github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys=
|
||||
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/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/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.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
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-go1-15 v0.1.0 h1:i/YPXVxz8q9umso/5y474CNcHmTpA+5DH+mFPjx6PZg=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.0/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.1/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/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/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-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w=
|
||||
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/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/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
|
||||
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.34/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
||||
github.com/miekg/dns v1.1.35/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-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/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/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/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/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
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.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
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/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
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/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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/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.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-20190129233127-fd36f4220a90/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-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-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.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||
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/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/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/shirou/gopsutil v2.20.3+incompatible h1:0JVooMPsT7A7HqEYdydp/OfjSOYSjhXV7w1hkKj/NPQ=
|
||||
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/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 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/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/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
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/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/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
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/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.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/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
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.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.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
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.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
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/u-root/u-root v6.0.0+incompatible h1:YqPGmRoRyYmeg17KIWFRSyVq6LX5T6GSzawyA6wG6EE=
|
||||
github.com/u-root/u-root v6.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
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/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
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=
|
||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
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.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.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=
|
||||
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-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-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-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-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/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
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-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-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-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-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.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-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-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-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-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-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-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-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-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-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/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/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-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-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-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/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=
|
||||
@@ -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-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-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-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-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-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-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-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-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-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-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-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-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-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-20200202164722-d101bd2416d5/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-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-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=
|
||||
golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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.3.0/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.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
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-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-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-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-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-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-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-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/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-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-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-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
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.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.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-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-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-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-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-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/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.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.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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
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.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
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 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
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/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/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.2/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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
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=
|
||||
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-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=
|
||||
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 h1:AQkaJpH+/FmqRjmXZPELom5zIERYZfwTjnHpfoVMQEc=
|
||||
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
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/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
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))
|
||||
}
|
||||
73
internal/agherr/agherr.go
Normal file
73
internal/agherr/agherr.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// 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]
|
||||
}
|
||||
|
||||
// wrapper is a copy of the hidden errors.wrapper interface for tests, linting,
|
||||
// etc.
|
||||
type wrapper interface {
|
||||
Unwrap() error
|
||||
}
|
||||
80
internal/agherr/agherr_test.go
Normal file
80
internal/agherr/agherr_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
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) {
|
||||
var _ wrapper = &manyError{}
|
||||
|
||||
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 {
|
||||
v6DynLeases = append(v6DynLeases, &lease)
|
||||
}
|
||||
|
||||
} else {
|
||||
if obj[i].Expiry == leaseExpireStatic {
|
||||
staticLeases = append(staticLeases, &lease)
|
||||
@@ -1,3 +1,4 @@
|
||||
// Package dhcpd provides a DHCP server.
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
@@ -5,16 +6,19 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/util"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
const defaultDiscoverTime = time.Second * 3
|
||||
const leaseExpireStatic = 1
|
||||
const (
|
||||
defaultDiscoverTime = time.Second * 3
|
||||
leaseExpireStatic = 1
|
||||
)
|
||||
|
||||
var webHandlersRegistered = false
|
||||
|
||||
@@ -48,6 +52,7 @@ type ServerConfig struct {
|
||||
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"`
|
||||
}
|
||||
|
||||
// OnLeaseChangedT is a callback for lease changes.
|
||||
type OnLeaseChangedT func(flags int)
|
||||
|
||||
// flags for onLeaseChanged()
|
||||
@@ -70,19 +75,16 @@ type Server struct {
|
||||
onLeaseChanged []OnLeaseChangedT
|
||||
}
|
||||
|
||||
// ServerInterface is an interface for servers.
|
||||
type ServerInterface interface {
|
||||
Leases(flags int) []Lease
|
||||
SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT)
|
||||
}
|
||||
|
||||
// CheckConfig checks the configuration
|
||||
func (s *Server) CheckConfig(config ServerConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create - create object
|
||||
func Create(config ServerConfig) *Server {
|
||||
s := Server{}
|
||||
s := &Server{}
|
||||
|
||||
s.conf.Enabled = config.Enabled
|
||||
s.conf.InterfaceName = config.InterfaceName
|
||||
s.conf.HTTPRegister = config.HTTPRegister
|
||||
@@ -90,8 +92,21 @@ func Create(config ServerConfig) *Server {
|
||||
s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename)
|
||||
|
||||
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
|
||||
s.registerHandlers()
|
||||
}
|
||||
|
||||
var err4, err6 error
|
||||
@@ -130,7 +145,7 @@ func Create(config ServerConfig) *Server {
|
||||
// we can't delay database loading until DHCP server is started,
|
||||
// because we need static leases functionality available beforehand
|
||||
s.dbLoad()
|
||||
return &s
|
||||
return s
|
||||
}
|
||||
|
||||
// server calls this function after DB is updated
|
||||
@@ -9,13 +9,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func check(t *testing.T, result bool, msg string) {
|
||||
if !result {
|
||||
t.Fatal(msg)
|
||||
}
|
||||
func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
func testNotify(flags uint32) {
|
||||
@@ -83,7 +82,6 @@ func TestIsValidSubnetMask(t *testing.T) {
|
||||
func TestNormalizeLeases(t *testing.T) {
|
||||
dynLeases := []*Lease{}
|
||||
staticLeases := []*Lease{}
|
||||
leases := []*Lease{}
|
||||
|
||||
lease := &Lease{}
|
||||
lease.HWAddr = []byte{1, 2, 3, 4}
|
||||
@@ -100,7 +98,7 @@ func TestNormalizeLeases(t *testing.T) {
|
||||
lease.HWAddr = []byte{2, 2, 3, 4}
|
||||
staticLeases = append(staticLeases, lease)
|
||||
|
||||
leases = normalizeLeases(staticLeases, dynLeases)
|
||||
leases := normalizeLeases(staticLeases, dynLeases)
|
||||
|
||||
assert.True(t, len(leases) == 3)
|
||||
assert.True(t, bytes.Equal(leases[0].HWAddr, []byte{1, 2, 3, 4}))
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"strings"
|
||||
"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/log"
|
||||
)
|
||||
@@ -175,7 +175,7 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
v6conf.InterfaceName = newconfig.InterfaceName
|
||||
v6conf.notify = s.onNotify
|
||||
s6, err = v6Create(v6conf)
|
||||
if s6 == nil {
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "Invalid DHCPv6 configuration: %s", err)
|
||||
return
|
||||
}
|
||||
@@ -206,9 +206,9 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
s.dbLoad()
|
||||
|
||||
if s.conf.Enabled {
|
||||
staticIP, err := HasStaticIP(newconfig.InterfaceName)
|
||||
staticIP, err := sysutil.IfaceHasStaticIP(newconfig.InterfaceName)
|
||||
if !staticIP && err == nil {
|
||||
err = SetStaticIP(newconfig.InterfaceName)
|
||||
err = sysutil.IfaceSetStaticIP(newconfig.InterfaceName)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Failed to configure static IP: %s", err)
|
||||
return
|
||||
@@ -283,7 +283,7 @@ func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 {
|
||||
jsonIface.GatewayIP = getGatewayIP(iface.Name)
|
||||
jsonIface.GatewayIP = sysutil.GatewayIP(iface.Name)
|
||||
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
|
||||
// Respond with results
|
||||
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)
|
||||
if err != nil {
|
||||
errorText := fmt.Sprintf("failed to read request body: %s", err)
|
||||
log.Error(errorText)
|
||||
http.Error(w, errorText, http.StatusBadRequest)
|
||||
msg := fmt.Sprintf("failed to read request body: %s", err)
|
||||
log.Error(msg)
|
||||
http.Error(w, msg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
interfaceName := strings.TrimSpace(string(body))
|
||||
if interfaceName == "" {
|
||||
errorText := fmt.Sprintf("empty interface name specified")
|
||||
log.Error(errorText)
|
||||
http.Error(w, errorText, http.StatusBadRequest)
|
||||
msg := "empty interface name specified"
|
||||
log.Error(msg)
|
||||
http.Error(w, msg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
found4, err4 := CheckIfOtherDHCPServersPresentV4(interfaceName)
|
||||
|
||||
staticIP := map[string]interface{}{}
|
||||
isStaticIP, err := HasStaticIP(interfaceName)
|
||||
isStaticIP, err := sysutil.IfaceHasStaticIP(interfaceName)
|
||||
staticIPStatus := "yes"
|
||||
if err != nil {
|
||||
staticIPStatus = "error"
|
||||
@@ -335,7 +336,7 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
|
||||
foundVal := "no"
|
||||
if found4 {
|
||||
foundVal = "yes"
|
||||
} else if err != nil {
|
||||
} else if err4 != nil {
|
||||
foundVal = "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) {
|
||||
|
||||
lj := staticLeaseJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&lj)
|
||||
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) {
|
||||
|
||||
lj := staticLeaseJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&lj)
|
||||
if err != nil {
|
||||
@@ -501,11 +500,51 @@ func (s *Server) handleReset(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *Server) registerHandlers() {
|
||||
s.conf.HTTPRegister("GET", "/control/dhcp/status", s.handleDHCPStatus)
|
||||
s.conf.HTTPRegister("GET", "/control/dhcp/interfaces", s.handleDHCPInterfaces)
|
||||
s.conf.HTTPRegister("POST", "/control/dhcp/set_config", s.handleDHCPSetConfig)
|
||||
s.conf.HTTPRegister("POST", "/control/dhcp/find_active_dhcp", s.handleDHCPFindActiveServer)
|
||||
s.conf.HTTPRegister("POST", "/control/dhcp/add_static_lease", s.handleDHCPAddStaticLease)
|
||||
s.conf.HTTPRegister("POST", "/control/dhcp/remove_static_lease", s.handleDHCPRemoveStaticLease)
|
||||
s.conf.HTTPRegister("POST", "/control/dhcp/reset", s.handleReset)
|
||||
s.conf.HTTPRegister(http.MethodGet, "/control/dhcp/status", s.handleDHCPStatus)
|
||||
s.conf.HTTPRegister(http.MethodGet, "/control/dhcp/interfaces", s.handleDHCPInterfaces)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/set_config", s.handleDHCPSetConfig)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/find_active_dhcp", s.handleDHCPFindActiveServer)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/add_static_lease", s.handleDHCPAddStaticLease)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/remove_static_lease", s.handleDHCPRemoveStaticLease)
|
||||
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"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/joomcode/errorx"
|
||||
)
|
||||
|
||||
func isTimeout(err error) bool {
|
||||
@@ -17,37 +14,6 @@ func isTimeout(err error) bool {
|
||||
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) {
|
||||
result := net.ParseIP(text)
|
||||
if result == nil {
|
||||
@@ -19,9 +19,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -48,14 +46,12 @@ const (
|
||||
ServerPort = 67
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultServers is the address of all link-local DHCP servers and
|
||||
// relay agents.
|
||||
DefaultServers = &net.UDPAddr{
|
||||
IP: net.IPv4bcast,
|
||||
Port: ServerPort,
|
||||
}
|
||||
)
|
||||
// DefaultServers is the address of all link-local DHCP servers and
|
||||
// relay agents.
|
||||
var DefaultServers = &net.UDPAddr{
|
||||
IP: net.IPv4bcast,
|
||||
Port: ServerPort,
|
||||
}
|
||||
|
||||
var (
|
||||
// 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).
|
||||
func WithLogger(newLogger Logger) ClientOpt {
|
||||
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.
|
||||
//
|
||||
// Default is 3.
|
||||
@@ -17,11 +17,16 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/testutil"
|
||||
"github.com/hugelgupf/socketpair"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
mu sync.Mutex
|
||||
received []*dhcpv4.DHCPv4
|
||||
@@ -74,7 +79,7 @@ func serveAndClient(ctx context.Context, responses [][]*dhcpv4.DHCPv4, opts ...C
|
||||
return mc, serverConn
|
||||
}
|
||||
|
||||
func ComparePacket(got *dhcpv4.DHCPv4, want *dhcpv4.DHCPv4) error {
|
||||
func ComparePacket(got, want *dhcpv4.DHCPv4) error {
|
||||
if got == nil && got == want {
|
||||
return nil
|
||||
}
|
||||
@@ -87,7 +92,7 @@ func ComparePacket(got *dhcpv4.DHCPv4, want *dhcpv4.DHCPv4) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func pktsExpected(got []*dhcpv4.DHCPv4, want []*dhcpv4.DHCPv4) error {
|
||||
func pktsExpected(got, want []*dhcpv4.DHCPv4) error {
|
||||
if 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
|
||||
}
|
||||
|
||||
func withBufferCap(n int) ClientOpt {
|
||||
return func(c *Client) (err error) {
|
||||
c.bufferCap = n
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendAndRead(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
desc string
|
||||
@@ -297,10 +309,10 @@ func TestMultipleSendAndRead(t *testing.T) {
|
||||
newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x44, 0x44, 0x44, 0x44}),
|
||||
},
|
||||
server: [][]*dhcpv4.DHCPv4{
|
||||
[]*dhcpv4.DHCPv4{ // Response for first packet.
|
||||
{ // Response for first packet.
|
||||
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}),
|
||||
},
|
||||
},
|
||||
@@ -17,17 +17,13 @@ import (
|
||||
"github.com/u-root/u-root/pkg/uio"
|
||||
)
|
||||
|
||||
var (
|
||||
// BroadcastMac is the broadcast MAC address.
|
||||
//
|
||||
// Any UDP packet sent to this address is broadcast on the subnet.
|
||||
BroadcastMac = net.HardwareAddr([]byte{255, 255, 255, 255, 255, 255})
|
||||
)
|
||||
// BroadcastMac is the broadcast MAC address.
|
||||
//
|
||||
// Any UDP packet sent to this address is broadcast on the subnet.
|
||||
var 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 = errors.New("must supply UDPAddr")
|
||||
)
|
||||
// ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr".
|
||||
var ErrUDPAddrIsRequired = errors.New("must supply UDPAddr")
|
||||
|
||||
// NewRawUDPConn returns a UDP connection bound to the interface and port
|
||||
// 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 {
|
||||
return true
|
||||
}
|
||||
@@ -281,7 +281,7 @@ func (b UDP) Checksum() uint16 {
|
||||
// CalculateChecksum calculates the checksum of the udp packet, given the total
|
||||
// length of the packet and the checksum of the network-layer pseudo-header
|
||||
// (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.
|
||||
tmp := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(tmp, totalLen)
|
||||
@@ -336,13 +336,13 @@ func ChecksumCombine(a, b uint16) uint16 {
|
||||
// given destination protocol and network address, ignoring the length
|
||||
// field. Pseudo-headers are needed by transport layers when calculating
|
||||
// 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(dstAddr), 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
|
||||
udpLen := UDPMinimumSize
|
||||
|
||||
@@ -163,7 +163,7 @@ func (ra *raCtx) Init() error {
|
||||
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)
|
||||
|
||||
params := icmpv6RA{
|
||||
@@ -180,26 +180,29 @@ func (ra *raCtx) Init() error {
|
||||
data := createICMPv6RAPacket(params)
|
||||
|
||||
var err error
|
||||
success := false
|
||||
ipAndScope := ra.ipAddr.String() + "%" + ra.ifaceName
|
||||
ra.conn, err = icmp.ListenPacket("ip6:ipv6-icmp", ipAndScope)
|
||||
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() {
|
||||
if !success {
|
||||
ra.Close()
|
||||
cerr := ra.Close()
|
||||
if cerr != nil {
|
||||
log.Error("closing context: %s", cerr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
con6 := ra.conn.IPv6PacketConn()
|
||||
|
||||
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 {
|
||||
return fmt.Errorf("DHCPv6 RA: SetMulticastHopLimit: %s", err)
|
||||
return fmt.Errorf("dhcpv6 ra: SetMulticastHopLimit: %w", err)
|
||||
}
|
||||
|
||||
msg := &ipv6.ControlMessage{
|
||||
@@ -212,28 +215,30 @@ func (ra *raCtx) Init() error {
|
||||
}
|
||||
|
||||
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 {
|
||||
_, err = con6.WriteTo(data, msg, addr)
|
||||
if err != nil {
|
||||
log.Error("DHCPv6 RA: WriteTo: %s", err)
|
||||
log.Error("dhcpv6 ra: WriteTo: %s", err)
|
||||
}
|
||||
time.Sleep(ra.packetSendPeriod)
|
||||
}
|
||||
log.Debug("DHCPv6 RA: loop exit")
|
||||
log.Debug("dhcpv6 ra: loop exit")
|
||||
}()
|
||||
|
||||
success = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close - close module
|
||||
func (ra *raCtx) Close() {
|
||||
log.Debug("DHCPv6 RA: closing")
|
||||
// Close closes the module.
|
||||
func (ra *raCtx) Close() (err error) {
|
||||
log.Debug("dhcpv6 ra: closing")
|
||||
|
||||
ra.stop.Store(1)
|
||||
|
||||
if ra.conn != nil {
|
||||
ra.conn.Close()
|
||||
return ra.conn.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -11,12 +11,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/go-ping/ping"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"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 {
|
||||
srv *server4.Server
|
||||
leasesLock sync.Mutex
|
||||
@@ -36,7 +38,7 @@ func (s *v4Server) WriteDiskConfig6(c *V6ServerConf) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return false
|
||||
}
|
||||
@@ -55,7 +57,7 @@ func (s *v4Server) ResetLeases(leases []*Lease) {
|
||||
if l.Expiry.Unix() != leaseExpireStatic &&
|
||||
!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
|
||||
}
|
||||
|
||||
@@ -124,7 +126,7 @@ func (s *v4Server) blacklistLease(lease *Lease) {
|
||||
// Remove (swap) lease by index
|
||||
func (s *v4Server) leaseRemoveSwapByIndex(i int) {
|
||||
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)
|
||||
if i != n-1 {
|
||||
@@ -168,7 +170,7 @@ func (s *v4Server) rmDynamicLease(lease Lease) error {
|
||||
func (s *v4Server) addLease(l *Lease) {
|
||||
s.leases = append(s.leases, l)
|
||||
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
|
||||
@@ -178,8 +180,7 @@ func (s *v4Server) rmLease(lease Lease) error {
|
||||
|
||||
if !bytes.Equal(l.HWAddr, lease.HWAddr) ||
|
||||
l.Hostname != lease.Hostname {
|
||||
|
||||
return fmt.Errorf("Lease not found")
|
||||
return fmt.Errorf("lease not found")
|
||||
}
|
||||
|
||||
s.leaseRemoveSwapByIndex(i)
|
||||
@@ -238,7 +239,6 @@ func (s *v4Server) RemoveStaticLease(l Lease) error {
|
||||
// Send ICMP to the specified machine
|
||||
// Return TRUE if it doesn't reply, which probably means that the IP is available
|
||||
func (s *v4Server) addrAvailable(target net.IP) bool {
|
||||
|
||||
if s.conf.ICMPTimeout == 0 {
|
||||
return true
|
||||
}
|
||||
@@ -246,6 +246,7 @@ func (s *v4Server) addrAvailable(target net.IP) bool {
|
||||
pinger, err := ping.NewPinger(target.String())
|
||||
if err != nil {
|
||||
log.Error("ping.NewPinger(): %v", err)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -256,15 +257,20 @@ func (s *v4Server) addrAvailable(target net.IP) bool {
|
||||
pinger.OnRecv = func(pkt *ping.Packet) {
|
||||
reply = true
|
||||
}
|
||||
log.Debug("DHCPv4: Sending ICMP Echo to %v", target)
|
||||
pinger.Run()
|
||||
log.Debug("dhcpv4: Sending ICMP Echo to %v", target)
|
||||
|
||||
err = pinger.Run()
|
||||
if err != nil {
|
||||
log.Error("pinger.Run(): %v", err)
|
||||
return true
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
log.Debug("DHCPv4: ICMP procedure is complete: %v", target)
|
||||
log.Debug("dhcpv4: ICMP procedure is complete: %v", target)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -337,7 +343,7 @@ func (s *v4Server) commitLease(l *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
|
||||
|
||||
s.leasesLock.Lock()
|
||||
@@ -349,7 +355,7 @@ func (s *v4Server) processDiscover(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) *Lea
|
||||
for lease == nil {
|
||||
lease = s.reserveLease(mac)
|
||||
if lease == nil {
|
||||
log.Debug("DHCPv4: No more IP addresses")
|
||||
log.Debug("dhcpv4: No more IP addresses")
|
||||
if toStore {
|
||||
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)
|
||||
if len(reqIP) != 0 &&
|
||||
!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))
|
||||
return b
|
||||
|
||||
}
|
||||
|
||||
// Process Request request and return lease
|
||||
// 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
|
||||
mac := req.ClientHWAddr
|
||||
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)
|
||||
if len(sid) != 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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.IP, reqIP) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -449,7 +454,7 @@ func (s *v4Server) processRequest(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) (*Lea
|
||||
s.leasesLock.Unlock()
|
||||
|
||||
if lease == nil {
|
||||
log.Debug("DHCPv4: No lease for %s", mac)
|
||||
log.Debug("dhcpv4: No lease for %s", mac)
|
||||
return nil, true
|
||||
}
|
||||
|
||||
@@ -475,8 +480,7 @@ func (s *v4Server) processRequest(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) (*Lea
|
||||
// Return 1: OK
|
||||
// Return 0: error; reply with Nak
|
||||
// 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
|
||||
|
||||
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(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) {
|
||||
log.Debug("DHCPv4: received message: %s", req.Summary())
|
||||
log.Debug("dhcpv4: received message: %s", req.Summary())
|
||||
|
||||
switch req.MessageType() {
|
||||
case dhcpv4.MessageTypeDiscover,
|
||||
@@ -527,18 +531,18 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
||||
//
|
||||
|
||||
default:
|
||||
log.Debug("DHCPv4: unsupported message type %d", req.MessageType())
|
||||
log.Debug("dhcpv4: unsupported message type %d", req.MessageType())
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := dhcpv4.NewReplyFromRequest(req)
|
||||
if err != nil {
|
||||
log.Debug("DHCPv4: dhcpv4.New: %s", err)
|
||||
log.Debug("dhcpv4: dhcpv4.New: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.ClientHWAddr) != 6 {
|
||||
log.Debug("DHCPv4: Invalid ClientHWAddr")
|
||||
log.Debug("dhcpv4: Invalid ClientHWAddr")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -549,33 +553,41 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
||||
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)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Start - start server
|
||||
// Start starts the IPv4 DHCP server.
|
||||
func (s *v4Server) Start() error {
|
||||
if !s.conf.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
iface, err := net.InterfaceByName(s.conf.InterfaceName)
|
||||
ifaceName := s.conf.InterfaceName
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
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...")
|
||||
s.conf.dnsIPAddrs = getIfaceIPv4(*iface)
|
||||
if len(s.conf.dnsIPAddrs) == 0 {
|
||||
log.Debug("DHCPv4: no IPv6 address for interface %s", iface.Name)
|
||||
log.Debug("dhcpv4: starting...")
|
||||
|
||||
dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion4, defaultMaxAttempts, defaultBackoff)
|
||||
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
|
||||
}
|
||||
|
||||
s.conf.dnsIPAddrs = dnsIPAddrs
|
||||
|
||||
laddr := &net.UDPAddr{
|
||||
IP: net.ParseIP("0.0.0.0"),
|
||||
Port: dhcpv4.ServerPort,
|
||||
@@ -585,12 +597,13 @@ func (s *v4Server) Start() error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("DHCPv4: listening")
|
||||
log.Info("dhcpv4: listening")
|
||||
|
||||
go func() {
|
||||
err = s.srv.Serve()
|
||||
log.Debug("DHCPv4: srv.Serve: %s", err)
|
||||
log.Debug("dhcpv4: srv.Serve: %s", err)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -600,10 +613,10 @@ func (s *v4Server) Stop() {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("DHCPv4: stopping")
|
||||
log.Debug("dhcpv4: stopping")
|
||||
err := s.srv.Close()
|
||||
if err != nil {
|
||||
log.Error("DHCPv4: srv.Close: %s", err)
|
||||
log.Error("dhcpv4: srv.Close: %s", err)
|
||||
}
|
||||
// now s.srv.Serve() will return
|
||||
s.srv = nil
|
||||
@@ -621,31 +634,31 @@ func v4Create(conf V4ServerConf) (DHCPServer, error) {
|
||||
var err error
|
||||
s.conf.routerIP, err = parseIPv4(s.conf.GatewayIP)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("DHCPv4: %s", err)
|
||||
return s, fmt.Errorf("dhcpv4: %w", err)
|
||||
}
|
||||
|
||||
subnet, err := parseIPv4(s.conf.SubnetMask)
|
||||
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)
|
||||
copy(s.conf.subnetMask, subnet)
|
||||
|
||||
s.conf.ipStart, err = parseIPv4(conf.RangeStart)
|
||||
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 {
|
||||
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)
|
||||
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]) ||
|
||||
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 {
|
||||
@@ -658,7 +671,7 @@ func v4Create(conf V4ServerConf) (DHCPServer, error) {
|
||||
for _, o := range conf.Options {
|
||||
code, val := parseOptionString(o)
|
||||
if code == 0 {
|
||||
log.Debug("DHCPv4: bad option string: %s", o)
|
||||
log.Debug("dhcpv4: bad option string: %s", o)
|
||||
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
|
||||
|
||||
// v6Server - DHCPv6 server
|
||||
// v6Server is a DHCPv6 server.
|
||||
//
|
||||
// TODO(a.garipov): Think about unifying this and v4Server.
|
||||
type v6Server struct {
|
||||
srv *server6.Server
|
||||
leasesLock sync.Mutex
|
||||
@@ -41,10 +43,11 @@ func (s *v6Server) WriteDiskConfig6(c *V6ServerConf) {
|
||||
|
||||
// Return TRUE if IP address is within range [start..0xff]
|
||||
// nolint(staticcheck)
|
||||
func ip6InRange(start net.IP, ip net.IP) bool {
|
||||
func ip6InRange(start, ip net.IP) bool {
|
||||
if len(start) != 16 {
|
||||
return false
|
||||
}
|
||||
//lint:ignore SA1021 TODO(e.burkov): Ignore this for now, think about using masks.
|
||||
if !bytes.Equal(start[:15], ip[:15]) {
|
||||
return false
|
||||
}
|
||||
@@ -72,12 +75,10 @@ func (s *v6Server) GetLeases(flags int) []Lease {
|
||||
var result []Lease
|
||||
s.leasesLock.Lock()
|
||||
for _, lease := range s.leases {
|
||||
|
||||
if lease.Expiry.Unix() == leaseExpireStatic {
|
||||
if (flags & LeasesStatic) != 0 {
|
||||
result = append(result, *lease)
|
||||
}
|
||||
|
||||
} else {
|
||||
if (flags & LeasesDynamic) != 0 {
|
||||
result = append(result, *lease)
|
||||
@@ -214,8 +215,7 @@ func (s *v6Server) rmLease(lease Lease) error {
|
||||
|
||||
if !bytes.Equal(l.HWAddr, lease.HWAddr) ||
|
||||
l.Hostname != lease.Hostname {
|
||||
|
||||
return fmt.Errorf("Lease not found")
|
||||
return fmt.Errorf("lease not found")
|
||||
}
|
||||
|
||||
s.leaseRemoveSwapByIndex(i)
|
||||
@@ -302,7 +302,7 @@ func (s *v6Server) commitDynamicLease(l *Lease) {
|
||||
// Check Client ID
|
||||
func (s *v6Server) checkCID(msg *dhcpv6.Message) error {
|
||||
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
|
||||
}
|
||||
@@ -317,7 +317,7 @@ func (s *v6Server) checkSID(msg *dhcpv6.Message) error {
|
||||
dhcpv6.MessageTypeRebind:
|
||||
|
||||
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,
|
||||
@@ -326,10 +326,10 @@ func (s *v6Server) checkSID(msg *dhcpv6.Message) error {
|
||||
dhcpv6.MessageTypeDecline:
|
||||
|
||||
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) {
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -371,7 +371,7 @@ func (s *v6Server) commitLease(msg *dhcpv6.Message, lease *Lease) time.Duration
|
||||
//
|
||||
|
||||
case dhcpv6.MessageTypeConfirm:
|
||||
lifetime = lease.Expiry.Sub(time.Now())
|
||||
lifetime = time.Until(lease.Expiry)
|
||||
|
||||
case dhcpv6.MessageTypeRequest,
|
||||
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
|
||||
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() {
|
||||
case dhcpv6.MessageTypeSolicit,
|
||||
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
|
||||
func (s *v6Server) initRA(iface *net.Interface) error {
|
||||
// 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()
|
||||
}
|
||||
|
||||
// Start - start server
|
||||
// Start starts the IPv6 DHCP server.
|
||||
func (s *v6Server) Start() error {
|
||||
if !s.conf.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
iface, err := net.InterfaceByName(s.conf.InterfaceName)
|
||||
ifaceName := s.conf.InterfaceName
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
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)
|
||||
if len(s.conf.dnsIPAddrs) == 0 {
|
||||
log.Debug("DHCPv6: no IPv6 address for interface %s", iface.Name)
|
||||
log.Debug("dhcpv6: starting...")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
s.conf.dnsIPAddrs = dnsIPAddrs
|
||||
|
||||
err = s.initRA(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -608,10 +597,10 @@ func (s *v6Server) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debug("DHCPv6: starting...")
|
||||
log.Debug("dhcpv6: listening...")
|
||||
|
||||
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{
|
||||
Type: dhcpv6.DUID_LLT,
|
||||
@@ -639,7 +628,10 @@ func (s *v6Server) Start() error {
|
||||
|
||||
// Stop - stop server
|
||||
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
|
||||
if s.srv == nil {
|
||||
@@ -647,10 +639,11 @@ func (s *v6Server) Stop() {
|
||||
}
|
||||
|
||||
log.Debug("DHCPv6: stopping")
|
||||
err := s.srv.Close()
|
||||
err = s.srv.Close()
|
||||
if err != nil {
|
||||
log.Error("DHCPv6: srv.Close: %s", err)
|
||||
}
|
||||
|
||||
// now server.Serve() will return
|
||||
s.srv = nil
|
||||
}
|
||||
@@ -666,7 +659,7 @@ func v6Create(conf V6ServerConf) (DHCPServer, error) {
|
||||
|
||||
s.conf.ipStart = net.ParseIP(conf.RangeStart)
|
||||
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 {
|
||||
@@ -22,12 +22,12 @@ func main() {
|
||||
res, err := filter.CheckHost(host)
|
||||
if err != nil {
|
||||
// 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 {
|
||||
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 {
|
||||
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)
|
||||
if err != nil {
|
||||
// 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 {
|
||||
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 {
|
||||
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
|
||||
|
||||
import (
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/util"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
@@ -128,8 +129,6 @@ const (
|
||||
NotFilteredNotFound Reason = iota
|
||||
// NotFilteredWhiteList - the host is explicitly whitelisted
|
||||
NotFilteredWhiteList
|
||||
// NotFilteredError - there was a transitive error during check
|
||||
NotFilteredError
|
||||
|
||||
// reasons for filtering
|
||||
|
||||
@@ -176,6 +175,16 @@ func (r Reason) String() string {
|
||||
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
|
||||
func (d *Dnsfilter) GetConfig() RequestFilteringSettings {
|
||||
c := RequestFilteringSettings{}
|
||||
@@ -199,7 +208,7 @@ func (d *Dnsfilter) WriteDiskConfig(c *Config) {
|
||||
// SetFilters - set new filters (synchronously or asynchronously)
|
||||
// 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.
|
||||
func (d *Dnsfilter) SetFilters(blockFilters []Filter, allowFilters []Filter, async bool) error {
|
||||
func (d *Dnsfilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error {
|
||||
if async {
|
||||
params := filtersInitializerParams{
|
||||
allowFilters: allowFilters,
|
||||
@@ -252,11 +261,20 @@ func (d *Dnsfilter) Close() {
|
||||
}
|
||||
|
||||
func (d *Dnsfilter) reset() {
|
||||
var err error
|
||||
|
||||
if d.rulesStorage != nil {
|
||||
_ = d.rulesStorage.Close()
|
||||
err = d.rulesStorage.Close()
|
||||
if err != nil {
|
||||
log.Error("dnsfilter: rulesStorage.Close: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if d.rulesStorageWhite != nil {
|
||||
d.rulesStorageWhite.Close()
|
||||
err = d.rulesStorageWhite.Close()
|
||||
if err != nil {
|
||||
log.Error("dnsfilter: rulesStorageWhite.Close: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +299,7 @@ type Result struct {
|
||||
CanonName string `json:",omitempty"` // CNAME value
|
||||
|
||||
// for RewriteEtcHosts:
|
||||
ReverseHost string `json:",omitempty"`
|
||||
ReverseHosts []string `json:",omitempty"`
|
||||
|
||||
// for ReasonRewrite & RewriteEtcHosts:
|
||||
IPList []net.IP `json:",omitempty"` // list of IP addresses
|
||||
@@ -325,17 +343,8 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
|
||||
// Now check the hosts file -- do we have any rules for it?
|
||||
// just like DNS rewrites, it has higher priority than filtering rules.
|
||||
if d.Config.AutoHosts != nil {
|
||||
ips := d.Config.AutoHosts.Process(host, qtype)
|
||||
if ips != nil {
|
||||
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 + "."
|
||||
matched := d.checkAutoHosts(host, qtype, &result)
|
||||
if matched {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
@@ -401,6 +410,31 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
|
||||
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
|
||||
// . Find CNAME for a domain name (exact match or by wildcard)
|
||||
// . if found and CNAME equals to domain name - this is an exception; exit
|
||||
@@ -481,13 +515,10 @@ func matchBlockedServicesRules(host string, svcs []ServiceEntry) Result {
|
||||
// Adding rule and matching against the rules
|
||||
//
|
||||
|
||||
// Return TRUE if file exists
|
||||
// fileExists returns true if file exists.
|
||||
func fileExists(fn string) bool {
|
||||
_, err := os.Stat(fn)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilter.DNSEngine, error) {
|
||||
@@ -501,19 +532,18 @@ func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilte
|
||||
RulesText: string(f.Data),
|
||||
IgnoreCosmetic: true,
|
||||
}
|
||||
|
||||
} else if !fileExists(f.FilePath) {
|
||||
list = &filterlist.StringRuleList{
|
||||
ID: int(f.ID),
|
||||
IgnoreCosmetic: true,
|
||||
}
|
||||
|
||||
} else if runtime.GOOS == "windows" {
|
||||
// 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)
|
||||
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{
|
||||
ID: int(f.ID),
|
||||
@@ -525,7 +555,7 @@ func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilte
|
||||
var err error
|
||||
list, err = filterlist.NewFileRuleList(int(f.ID), f.FilePath, true)
|
||||
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)
|
||||
@@ -533,13 +563,13 @@ func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilte
|
||||
|
||||
rulesStorage, err := filterlist.NewRuleStorage(listArray)
|
||||
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)
|
||||
return rulesStorage, filteringEngine, nil
|
||||
}
|
||||
|
||||
// Initialize urlfilter objects
|
||||
// Initialize urlfilter objects.
|
||||
func (d *Dnsfilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
||||
rulesStorage, filteringEngine, err := createFilteringEngine(blockFilters)
|
||||
if err != nil {
|
||||
@@ -565,18 +595,21 @@ func (d *Dnsfilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
||||
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) {
|
||||
d.engineLock.RLock()
|
||||
// Keep in mind that this lock must be held no just when calling Match()
|
||||
// but also while using the rules returned by it.
|
||||
defer d.engineLock.RUnlock()
|
||||
|
||||
ureq := urlfilter.DNSRequest{}
|
||||
ureq.Hostname = host
|
||||
ureq.ClientIP = setts.ClientIP
|
||||
ureq.ClientName = setts.ClientName
|
||||
ureq.SortedClientTags = setts.ClientTags
|
||||
ureq := urlfilter.DNSRequest{
|
||||
Hostname: host,
|
||||
SortedClientTags: setts.ClientTags,
|
||||
ClientIP: setts.ClientIP,
|
||||
ClientName: setts.ClientName,
|
||||
DNSType: qtype,
|
||||
}
|
||||
|
||||
if d.filteringEngineWhite != nil {
|
||||
rr, ok := d.filteringEngineWhite.MatchRequest(ureq)
|
||||
@@ -590,7 +623,7 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
||||
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())
|
||||
res := makeResult(rule, NotFilteredWhiteList)
|
||||
return res, nil
|
||||
@@ -607,7 +640,7 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
||||
}
|
||||
|
||||
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())
|
||||
reason := FilteredBlackList
|
||||
if rr.NetworkRule.Whitelist {
|
||||
@@ -619,7 +652,7 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
||||
|
||||
if qtype == dns.TypeA && rr.HostRulesV4 != nil {
|
||||
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())
|
||||
res := makeResult(rule, FilteredBlackList)
|
||||
res.IP = rule.IP.To4()
|
||||
@@ -628,7 +661,7 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
||||
|
||||
if qtype == dns.TypeAAAA && rr.HostRulesV6 != nil {
|
||||
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())
|
||||
res := makeResult(rule, FilteredBlackList)
|
||||
res.IP = rule.IP
|
||||
@@ -644,7 +677,7 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
||||
} else if rr.HostRulesV6 != nil {
|
||||
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())
|
||||
res := makeResult(rule, FilteredBlackList)
|
||||
res.IP = net.IP{}
|
||||
@@ -666,21 +699,18 @@ func makeResult(rule rules.Rule, reason Reason) Result {
|
||||
return res
|
||||
}
|
||||
|
||||
// InitModule() - manually initialize blocked services map
|
||||
// InitModule manually initializes blocked services map.
|
||||
func InitModule() {
|
||||
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 {
|
||||
|
||||
if c != nil {
|
||||
cacheConf := cache.Config{
|
||||
EnableLRU: true,
|
||||
}
|
||||
|
||||
// initialize objects only once
|
||||
|
||||
if gctx.safebrowsingCache == nil {
|
||||
cacheConf.MaxSize = c.SafeBrowsingCacheSize
|
||||
gctx.safebrowsingCache = cache.New(cacheConf)
|
||||
@@ -713,7 +743,7 @@ func New(c *Config, blockFilters []Filter) *Dnsfilter {
|
||||
bsvcs := []string{}
|
||||
for _, s := range d.BlockedServices {
|
||||
if !BlockedSvcKnown(s) {
|
||||
log.Debug("skipping unknown blocked-service '%s'", s)
|
||||
log.Debug("skipping unknown blocked-service %q", s)
|
||||
continue
|
||||
}
|
||||
bsvcs = append(bsvcs, s)
|
||||
@@ -745,12 +775,3 @@ func (d *Dnsfilter) Start() {
|
||||
d.registerBlockedServicesHandlers()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// stats
|
||||
//
|
||||
|
||||
// GetStats return dns filtering stats since startup
|
||||
func (d *Dnsfilter) GetStats() Stats {
|
||||
return gctx.stats
|
||||
}
|
||||
@@ -1,18 +1,23 @@
|
||||
package dnsfilter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/testutil"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
var setts RequestFilteringSettings
|
||||
|
||||
// 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 {
|
||||
setts = RequestFilteringSettings{}
|
||||
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()
|
||||
ret, err := d.CheckHost(hostname, qtype, &setts)
|
||||
if err != nil {
|
||||
@@ -107,7 +105,7 @@ func TestEtcHostsMatching(t *testing.T) {
|
||||
::1 host2
|
||||
`,
|
||||
addr, addr6)
|
||||
filters := []Filter{Filter{
|
||||
filters := []Filter{{
|
||||
ID: 0, Data: []byte(text),
|
||||
}}
|
||||
d := NewForTest(nil, filters)
|
||||
@@ -147,10 +145,17 @@ func TestEtcHostsMatching(t *testing.T) {
|
||||
// SAFE BROWSING
|
||||
|
||||
func TestSafeBrowsing(t *testing.T) {
|
||||
logOutput := &bytes.Buffer{}
|
||||
testutil.ReplaceLogWriter(t, logOutput)
|
||||
testutil.ReplaceLogLevel(t, log.DEBUG)
|
||||
|
||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||
defer d.Close()
|
||||
gctx.stats.Safebrowsing.Requests = 0
|
||||
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.checkMatchEmpty(t, "yandex.ru")
|
||||
d.checkMatchEmpty(t, "pornhub.com")
|
||||
@@ -300,7 +305,6 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||
t.Fatalf("Failed to lookup for %s", safeDomain)
|
||||
}
|
||||
|
||||
t.Logf("IP addresses: %v", ips)
|
||||
ip := ips[0]
|
||||
for _, i := range ips {
|
||||
if i.To4() != nil {
|
||||
@@ -334,9 +338,14 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
|
||||
// PARENTAL
|
||||
|
||||
func TestParentalControl(t *testing.T) {
|
||||
logOutput := &bytes.Buffer{}
|
||||
testutil.ReplaceLogWriter(t, logOutput)
|
||||
testutil.ReplaceLogLevel(t, log.DEBUG)
|
||||
|
||||
d := NewForTest(&Config{ParentalEnabled: true}, nil)
|
||||
defer d.Close()
|
||||
d.checkMatch(t, "pornhub.com")
|
||||
assert.True(t, strings.Contains(logOutput.String(), "Parental lookup for pornhub.com"))
|
||||
d.checkMatch(t, "www.pornhub.com")
|
||||
d.checkMatchEmpty(t, "www.yandex.ru")
|
||||
d.checkMatchEmpty(t, "yandex.ru")
|
||||
@@ -351,11 +360,16 @@ func TestParentalControl(t *testing.T) {
|
||||
|
||||
// FILTERING
|
||||
|
||||
var blockingRules = "||example.org^\n"
|
||||
var whitelistRules = "||example.org^\n@@||test.example.org\n"
|
||||
var importantRules = "@@||example.org^\n||test.example.org^$important\n"
|
||||
var regexRules = "/example\\.org/\n@@||test.example.org^\n"
|
||||
var maskRules = "test*.example.org^\nexam*.com\n"
|
||||
const nl = "\n"
|
||||
|
||||
const (
|
||||
blockingRules = `||example.org^` + nl
|
||||
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 {
|
||||
testname string
|
||||
@@ -363,56 +377,63 @@ var tests = []struct {
|
||||
hostname string
|
||||
isFiltered bool
|
||||
reason Reason
|
||||
dnsType uint16
|
||||
}{
|
||||
{"sanity", "||doubleclick.net^", "www.doubleclick.net", true, FilteredBlackList},
|
||||
{"sanity", "||doubleclick.net^", "nodoubleclick.net", false, NotFilteredNotFound},
|
||||
{"sanity", "||doubleclick.net^", "doubleclick.net.ru", false, NotFilteredNotFound},
|
||||
{"sanity", "||doubleclick.net^", "wmconvirus.narod.ru", false, NotFilteredNotFound},
|
||||
{"sanity", "||doubleclick.net^", "www.doubleclick.net", true, FilteredBlackList, dns.TypeA},
|
||||
{"sanity", "||doubleclick.net^", "nodoubleclick.net", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"sanity", "||doubleclick.net^", "doubleclick.net.ru", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"sanity", "||doubleclick.net^", "wmconvirus.narod.ru", false, NotFilteredNotFound, dns.TypeA},
|
||||
|
||||
{"blocking", blockingRules, "example.org", true, FilteredBlackList},
|
||||
{"blocking", blockingRules, "test.example.org", true, FilteredBlackList},
|
||||
{"blocking", blockingRules, "test.test.example.org", true, FilteredBlackList},
|
||||
{"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound},
|
||||
{"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound},
|
||||
{"blocking", blockingRules, "example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"blocking", blockingRules, "test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"blocking", blockingRules, "test.test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
|
||||
{"whitelist", whitelistRules, "example.org", true, FilteredBlackList},
|
||||
{"whitelist", whitelistRules, "test.example.org", false, NotFilteredWhiteList},
|
||||
{"whitelist", whitelistRules, "test.test.example.org", false, NotFilteredWhiteList},
|
||||
{"whitelist", whitelistRules, "testexample.org", false, NotFilteredNotFound},
|
||||
{"whitelist", whitelistRules, "onemoreexample.org", false, NotFilteredNotFound},
|
||||
{"whitelist", whitelistRules, "example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"whitelist", whitelistRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||
{"whitelist", whitelistRules, "test.test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||
{"whitelist", whitelistRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"whitelist", whitelistRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
|
||||
{"important", importantRules, "example.org", false, NotFilteredWhiteList},
|
||||
{"important", importantRules, "test.example.org", true, FilteredBlackList},
|
||||
{"important", importantRules, "test.test.example.org", true, FilteredBlackList},
|
||||
{"important", importantRules, "testexample.org", false, NotFilteredNotFound},
|
||||
{"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound},
|
||||
{"important", importantRules, "example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||
{"important", importantRules, "test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"important", importantRules, "test.test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"important", importantRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
|
||||
{"regex", regexRules, "example.org", true, FilteredBlackList},
|
||||
{"regex", regexRules, "test.example.org", false, NotFilteredWhiteList},
|
||||
{"regex", regexRules, "test.test.example.org", false, NotFilteredWhiteList},
|
||||
{"regex", regexRules, "testexample.org", true, FilteredBlackList},
|
||||
{"regex", regexRules, "onemoreexample.org", true, FilteredBlackList},
|
||||
{"regex", regexRules, "example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"regex", regexRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||
{"regex", regexRules, "test.test.example.org", false, NotFilteredWhiteList, dns.TypeA},
|
||||
{"regex", regexRules, "testexample.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"regex", regexRules, "onemoreexample.org", true, FilteredBlackList, dns.TypeA},
|
||||
|
||||
{"mask", maskRules, "test.example.org", true, FilteredBlackList},
|
||||
{"mask", maskRules, "test2.example.org", true, FilteredBlackList},
|
||||
{"mask", maskRules, "example.com", true, FilteredBlackList},
|
||||
{"mask", maskRules, "exampleeee.com", true, FilteredBlackList},
|
||||
{"mask", maskRules, "onemoreexamsite.com", true, FilteredBlackList},
|
||||
{"mask", maskRules, "example.org", false, NotFilteredNotFound},
|
||||
{"mask", maskRules, "testexample.org", false, NotFilteredNotFound},
|
||||
{"mask", maskRules, "example.co.uk", false, NotFilteredNotFound},
|
||||
{"mask", maskRules, "test.example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"mask", maskRules, "test2.example.org", true, FilteredBlackList, dns.TypeA},
|
||||
{"mask", maskRules, "example.com", true, FilteredBlackList, dns.TypeA},
|
||||
{"mask", maskRules, "exampleeee.com", true, FilteredBlackList, dns.TypeA},
|
||||
{"mask", maskRules, "onemoreexamsite.com", true, FilteredBlackList, dns.TypeA},
|
||||
{"mask", maskRules, "example.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"mask", maskRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA},
|
||||
{"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) {
|
||||
for _, test := range tests {
|
||||
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),
|
||||
}}
|
||||
d := NewForTest(nil, filters)
|
||||
defer d.Close()
|
||||
|
||||
ret, err := d.CheckHost(test.hostname, dns.TypeA, &setts)
|
||||
ret, err := d.CheckHost(test.hostname, test.dnsType, &setts)
|
||||
if err != nil {
|
||||
t.Errorf("Error while matching host %s: %s", test.hostname, err)
|
||||
}
|
||||
@@ -430,14 +451,14 @@ func TestWhitelist(t *testing.T) {
|
||||
rules := `||host1^
|
||||
||host2^
|
||||
`
|
||||
filters := []Filter{Filter{
|
||||
filters := []Filter{{
|
||||
ID: 0, Data: []byte(rules),
|
||||
}}
|
||||
|
||||
whiteRules := `||host1^
|
||||
||host3^
|
||||
`
|
||||
whiteFilters := []Filter{Filter{
|
||||
whiteFilters := []Filter{{
|
||||
ID: 0, Data: []byte(whiteRules),
|
||||
}}
|
||||
d := NewForTest(nil, filters)
|
||||
@@ -455,7 +476,6 @@ func TestWhitelist(t *testing.T) {
|
||||
assert.True(t, err == nil)
|
||||
assert.True(t, ret.IsFiltered && ret.Reason == FilteredBlackList)
|
||||
assert.True(t, ret.Rule == "||host2^")
|
||||
|
||||
}
|
||||
|
||||
// CLIENT SETTINGS
|
||||
@@ -476,7 +496,7 @@ func applyClientSettings(setts *RequestFilteringSettings) {
|
||||
// then apply per-client settings and check behaviour once again
|
||||
func TestClientSettings(t *testing.T) {
|
||||
var r Result
|
||||
filters := []Filter{Filter{
|
||||
filters := []Filter{{
|
||||
ID: 0, Data: []byte("||example.org^\n"),
|
||||
}}
|
||||
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)
|
||||
}
|
||||
|
||||
func prepareTestDir() string {
|
||||
const dir = "./agh-test"
|
||||
_ = os.RemoveAll(dir)
|
||||
_ = os.MkdirAll(dir, 0755)
|
||||
return dir
|
||||
}
|
||||
|
||||
// BENCHMARKS
|
||||
|
||||
func BenchmarkSafeBrowsing(b *testing.B) {
|
||||
@@ -149,7 +149,6 @@ type rewriteEntryJSON struct {
|
||||
}
|
||||
|
||||
func (d *Dnsfilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
arr := []*rewriteEntryJSON{}
|
||||
|
||||
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) {
|
||||
|
||||
jsent := rewriteEntryJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&jsent)
|
||||
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) {
|
||||
|
||||
jsent := rewriteEntryJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&jsent)
|
||||
if err != nil {
|
||||
@@ -12,13 +12,13 @@ func TestRewrites(t *testing.T) {
|
||||
d := Dnsfilter{}
|
||||
// CNAME, A, AAAA
|
||||
d.Rewrites = []RewriteEntry{
|
||||
RewriteEntry{"somecname", "somehost.com", 0, nil},
|
||||
RewriteEntry{"somehost.com", "0.0.0.0", 0, nil},
|
||||
{"somecname", "somehost.com", 0, nil},
|
||||
{"somehost.com", "0.0.0.0", 0, nil},
|
||||
|
||||
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
|
||||
RewriteEntry{"host.com", "1.2.3.5", 0, nil},
|
||||
RewriteEntry{"host.com", "1:2:3::4", 0, nil},
|
||||
RewriteEntry{"www.host.com", "host.com", 0, nil},
|
||||
{"host.com", "1.2.3.4", 0, nil},
|
||||
{"host.com", "1.2.3.5", 0, nil},
|
||||
{"host.com", "1:2:3::4", 0, nil},
|
||||
{"www.host.com", "host.com", 0, nil},
|
||||
}
|
||||
d.prepareRewrites()
|
||||
r := d.processRewrites("host2.com", dns.TypeA)
|
||||
@@ -39,8 +39,8 @@ func TestRewrites(t *testing.T) {
|
||||
|
||||
// wildcard
|
||||
d.Rewrites = []RewriteEntry{
|
||||
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
|
||||
RewriteEntry{"*.host.com", "1.2.3.5", 0, nil},
|
||||
{"host.com", "1.2.3.4", 0, nil},
|
||||
{"*.host.com", "1.2.3.5", 0, nil},
|
||||
}
|
||||
d.prepareRewrites()
|
||||
r = d.processRewrites("host.com", dns.TypeA)
|
||||
@@ -56,8 +56,8 @@ func TestRewrites(t *testing.T) {
|
||||
|
||||
// override a wildcard
|
||||
d.Rewrites = []RewriteEntry{
|
||||
RewriteEntry{"a.host.com", "1.2.3.4", 0, nil},
|
||||
RewriteEntry{"*.host.com", "1.2.3.5", 0, nil},
|
||||
{"a.host.com", "1.2.3.4", 0, nil},
|
||||
{"*.host.com", "1.2.3.5", 0, nil},
|
||||
}
|
||||
d.prepareRewrites()
|
||||
r = d.processRewrites("a.host.com", dns.TypeA)
|
||||
@@ -67,8 +67,8 @@ func TestRewrites(t *testing.T) {
|
||||
|
||||
// wildcard + CNAME
|
||||
d.Rewrites = []RewriteEntry{
|
||||
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
|
||||
RewriteEntry{"*.host.com", "host.com", 0, nil},
|
||||
{"host.com", "1.2.3.4", 0, nil},
|
||||
{"*.host.com", "host.com", 0, nil},
|
||||
}
|
||||
d.prepareRewrites()
|
||||
r = d.processRewrites("www.host.com", dns.TypeA)
|
||||
@@ -78,9 +78,9 @@ func TestRewrites(t *testing.T) {
|
||||
|
||||
// 2 CNAMEs
|
||||
d.Rewrites = []RewriteEntry{
|
||||
RewriteEntry{"b.host.com", "a.host.com", 0, nil},
|
||||
RewriteEntry{"a.host.com", "host.com", 0, nil},
|
||||
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
|
||||
{"b.host.com", "a.host.com", 0, nil},
|
||||
{"a.host.com", "host.com", 0, nil},
|
||||
{"host.com", "1.2.3.4", 0, nil},
|
||||
}
|
||||
d.prepareRewrites()
|
||||
r = d.processRewrites("b.host.com", dns.TypeA)
|
||||
@@ -91,9 +91,9 @@ func TestRewrites(t *testing.T) {
|
||||
|
||||
// 2 CNAMEs + wildcard
|
||||
d.Rewrites = []RewriteEntry{
|
||||
RewriteEntry{"b.host.com", "a.host.com", 0, nil},
|
||||
RewriteEntry{"a.host.com", "x.somehost.com", 0, nil},
|
||||
RewriteEntry{"*.somehost.com", "1.2.3.4", 0, nil},
|
||||
{"b.host.com", "a.host.com", 0, nil},
|
||||
{"a.host.com", "x.somehost.com", 0, nil},
|
||||
{"*.somehost.com", "1.2.3.4", 0, nil},
|
||||
}
|
||||
d.prepareRewrites()
|
||||
r = d.processRewrites("b.host.com", dns.TypeA)
|
||||
@@ -107,9 +107,9 @@ func TestRewritesLevels(t *testing.T) {
|
||||
d := Dnsfilter{}
|
||||
// exact host, wildcard L2, wildcard L3
|
||||
d.Rewrites = []RewriteEntry{
|
||||
RewriteEntry{"host.com", "1.1.1.1", 0, nil},
|
||||
RewriteEntry{"*.host.com", "2.2.2.2", 0, nil},
|
||||
RewriteEntry{"*.sub.host.com", "3.3.3.3", 0, nil},
|
||||
{"host.com", "1.1.1.1", 0, nil},
|
||||
{"*.host.com", "2.2.2.2", 0, nil},
|
||||
{"*.sub.host.com", "3.3.3.3", 0, nil},
|
||||
}
|
||||
d.prepareRewrites()
|
||||
|
||||
@@ -136,8 +136,8 @@ func TestRewritesExceptionCNAME(t *testing.T) {
|
||||
d := Dnsfilter{}
|
||||
// wildcard; exception for a sub-domain
|
||||
d.Rewrites = []RewriteEntry{
|
||||
RewriteEntry{"*.host.com", "2.2.2.2", 0, nil},
|
||||
RewriteEntry{"sub.host.com", "sub.host.com", 0, nil},
|
||||
{"*.host.com", "2.2.2.2", 0, nil},
|
||||
{"sub.host.com", "sub.host.com", 0, nil},
|
||||
}
|
||||
d.prepareRewrites()
|
||||
|
||||
@@ -156,8 +156,8 @@ func TestRewritesExceptionWC(t *testing.T) {
|
||||
d := Dnsfilter{}
|
||||
// wildcard; exception for a sub-wildcard
|
||||
d.Rewrites = []RewriteEntry{
|
||||
RewriteEntry{"*.host.com", "2.2.2.2", 0, nil},
|
||||
RewriteEntry{"*.sub.host.com", "*.sub.host.com", 0, nil},
|
||||
{"*.host.com", "2.2.2.2", 0, nil},
|
||||
{"*.sub.host.com", "*.sub.host.com", 0, nil},
|
||||
}
|
||||
d.prepareRewrites()
|
||||
|
||||
@@ -176,11 +176,11 @@ func TestRewritesExceptionIP(t *testing.T) {
|
||||
d := Dnsfilter{}
|
||||
// exception for AAAA record
|
||||
d.Rewrites = []RewriteEntry{
|
||||
RewriteEntry{"host.com", "1.2.3.4", 0, nil},
|
||||
RewriteEntry{"host.com", "AAAA", 0, nil},
|
||||
RewriteEntry{"host2.com", "::1", 0, nil},
|
||||
RewriteEntry{"host2.com", "A", 0, nil},
|
||||
RewriteEntry{"host3.com", "A", 0, nil},
|
||||
{"host.com", "1.2.3.4", 0, nil},
|
||||
{"host.com", "AAAA", 0, nil},
|
||||
{"host2.com", "::1", 0, nil},
|
||||
{"host2.com", "A", 0, nil},
|
||||
{"host3.com", "A", 0, nil},
|
||||
}
|
||||
d.prepareRewrites()
|
||||
|
||||
@@ -1,5 +1,152 @@
|
||||
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{
|
||||
"yandex.com": "213.180.193.56",
|
||||
"yandex.ru": "213.180.193.56",
|
||||
@@ -22,11 +22,13 @@ import (
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
const dnsTimeout = 3 * time.Second
|
||||
const defaultSafebrowsingServer = "https://dns-family.adguard.com/dns-query"
|
||||
const defaultParentalServer = "https://dns-family.adguard.com/dns-query"
|
||||
const sbTXTSuffix = "sb.dns.adguard.com."
|
||||
const pcTXTSuffix = "pc.dns.adguard.com."
|
||||
const (
|
||||
dnsTimeout = 3 * time.Second
|
||||
defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query`
|
||||
defaultParentalServer = `https://dns-family.adguard.com/dns-query`
|
||||
sbTXTSuffix = `sb.dns.adguard.com.`
|
||||
pcTXTSuffix = `pc.dns.adguard.com.`
|
||||
)
|
||||
|
||||
func (d *Dnsfilter) initSecurityServices() error {
|
||||
var err error
|
||||
@@ -60,7 +62,7 @@ expire byte[4]
|
||||
hash byte[32]
|
||||
...
|
||||
*/
|
||||
func (c *sbCtx) setCache(prefix []byte, hashes []byte) {
|
||||
func (c *sbCtx) setCache(prefix, hashes []byte) {
|
||||
d := make([]byte, 4+len(hashes))
|
||||
expire := uint(time.Now().Unix()) + c.cacheTime*60
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
now := time.Now().Unix()
|
||||
hashesToRequest := map[[32]byte]string{}
|
||||
for k, v := range c.hashToHost {
|
||||
key := k[0:2]
|
||||
val := c.cache.Get(key)
|
||||
if val != nil {
|
||||
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 {
|
||||
if val == nil || now >= int64(binary.BigEndian.Uint32(val)) {
|
||||
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
|
||||
func (c *sbCtx) getQuestion() string {
|
||||
q := ""
|
||||
b := &strings.Builder{}
|
||||
encoder := hex.NewEncoder(b)
|
||||
|
||||
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" {
|
||||
q += sbTXTSuffix
|
||||
} else {
|
||||
q += pcTXTSuffix
|
||||
// See comment above.
|
||||
_, _ = b.WriteString(sbTXTSuffix)
|
||||
return b.String()
|
||||
}
|
||||
return q
|
||||
|
||||
// See comment above.
|
||||
_, _ = b.WriteString(pcTXTSuffix)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// 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
|
||||
// nolint:dupl
|
||||
func check(c *sbCtx, r Result, u upstream.Upstream) (Result, error) {
|
||||
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) {
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("SafeBrowsing lookup for %s", host)
|
||||
}
|
||||
|
||||
result := Result{}
|
||||
hashes := hostnameToHashes(host)
|
||||
|
||||
c := &sbCtx{
|
||||
host: host,
|
||||
svc: "SafeBrowsing",
|
||||
hashToHost: hashes,
|
||||
cache: gctx.safebrowsingCache,
|
||||
cacheTime: d.Config.CacheTime,
|
||||
ctx := &sbCtx{
|
||||
host: host,
|
||||
svc: "SafeBrowsing",
|
||||
cache: gctx.safebrowsingCache,
|
||||
cacheTime: d.Config.CacheTime,
|
||||
}
|
||||
|
||||
// check cache
|
||||
match := c.getCached()
|
||||
if match < 0 {
|
||||
return result, nil
|
||||
} else if match > 0 {
|
||||
result.IsFiltered = true
|
||||
result.Reason = FilteredSafeBrowsing
|
||||
result.Rule = "adguard-malware-shavar"
|
||||
return result, nil
|
||||
res := Result{
|
||||
IsFiltered: true,
|
||||
Reason: FilteredSafeBrowsing,
|
||||
Rule: "adguard-malware-shavar",
|
||||
}
|
||||
|
||||
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
|
||||
return check(ctx, res, d.safeBrowsingUpstream)
|
||||
}
|
||||
|
||||
// Disabling "dupl": the algorithm of SB/PC is similar, but it uses different data
|
||||
// nolint:dupl
|
||||
func (d *Dnsfilter) checkParental(host string) (Result, error) {
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("Parental lookup for %s", host)
|
||||
}
|
||||
|
||||
result := Result{}
|
||||
hashes := hostnameToHashes(host)
|
||||
|
||||
c := &sbCtx{
|
||||
host: host,
|
||||
svc: "Parental",
|
||||
hashToHost: hashes,
|
||||
cache: gctx.parentalCache,
|
||||
cacheTime: d.Config.CacheTime,
|
||||
ctx := &sbCtx{
|
||||
host: host,
|
||||
svc: "Parental",
|
||||
cache: gctx.parentalCache,
|
||||
cacheTime: d.Config.CacheTime,
|
||||
}
|
||||
|
||||
// check cache
|
||||
match := c.getCached()
|
||||
if match < 0 {
|
||||
return result, nil
|
||||
} else if match > 0 {
|
||||
result.IsFiltered = true
|
||||
result.Reason = FilteredParental
|
||||
result.Rule = "parental CATEGORY_BLACKLISTED"
|
||||
return result, nil
|
||||
res := Result{
|
||||
IsFiltered: true,
|
||||
Reason: FilteredParental,
|
||||
Rule: "parental CATEGORY_BLACKLISTED",
|
||||
}
|
||||
|
||||
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
|
||||
return check(ctx, res, d.parentalUpstream)
|
||||
}
|
||||
|
||||
func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) {
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -23,16 +25,16 @@ func TestSafeBrowsingHash(t *testing.T) {
|
||||
assert.False(t, ok)
|
||||
|
||||
c := &sbCtx{
|
||||
svc: "SafeBrowsing",
|
||||
svc: "SafeBrowsing",
|
||||
hashToHost: hashes,
|
||||
}
|
||||
|
||||
// test getQuestion()
|
||||
c.hashToHost = hashes
|
||||
q := c.getQuestion()
|
||||
assert.True(t, strings.Index(q, "7a1b.") >= 0)
|
||||
assert.True(t, strings.Index(q, "af5a.") >= 0)
|
||||
assert.True(t, strings.Index(q, "eb11.") >= 0)
|
||||
assert.True(t, strings.Index(q, "sb.dns.adguard.com.") > 0)
|
||||
|
||||
assert.True(t, strings.Contains(q, "7a1b."))
|
||||
assert.True(t, strings.Contains(q, "af5a."))
|
||||
assert.True(t, strings.Contains(q, "eb11."))
|
||||
assert.True(t, strings.HasSuffix(q, "sb.dns.adguard.com."))
|
||||
}
|
||||
|
||||
func TestSafeBrowsingCache(t *testing.T) {
|
||||
@@ -88,4 +90,47 @@ func TestSafeBrowsingCache(t *testing.T) {
|
||||
hash = sha256.Sum256([]byte("nonexisting.com"))
|
||||
_, ok = c.hashToHost[hash]
|
||||
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)
|
||||
rulesStorage, err := filterlist.NewRuleStorage(listArray)
|
||||
if err != nil {
|
||||
return fmt.Errorf("filterlist.NewRuleStorage(): %s", err)
|
||||
return fmt.Errorf("filterlist.NewRuleStorage(): %w", err)
|
||||
}
|
||||
a.blockedHostsEngine = urlfilter.NewDNSEngine(rulesStorage)
|
||||
|
||||
@@ -50,7 +50,8 @@ func TestIsBlockedIPDisallowed(t *testing.T) {
|
||||
|
||||
func TestIsBlockedIPBlockedDomain(t *testing.T) {
|
||||
a := &accessCtx{}
|
||||
assert.True(t, a.Init(nil, nil, []string{"host1",
|
||||
assert.True(t, a.Init(nil, nil, []string{
|
||||
"host1",
|
||||
"host2",
|
||||
"*.host.com",
|
||||
"||host3.com^",
|
||||
@@ -10,12 +10,12 @@ import (
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/util"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/joomcode/errorx"
|
||||
"github.com/ameshkov/dnscrypt/v2"
|
||||
)
|
||||
|
||||
// FilteringConfig represents the DNS filtering configuration of AdGuard Home
|
||||
@@ -95,19 +95,33 @@ type FilteringConfig struct {
|
||||
type TLSConfig struct {
|
||||
TLSListenAddr *net.TCPAddr `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
|
||||
PrivateKey string `yaml:"private_key" json:"private_key"` // PEM-encoded private key
|
||||
// Reject connection if the client uses server name (in SNI) that doesn't match the certificate
|
||||
StrictSNICheck bool `yaml:"strict_sni_check" json:"-"`
|
||||
|
||||
CertificatePath string `yaml:"certificate_path" json:"certificate_path"` // certificate file name
|
||||
PrivateKeyPath string `yaml:"private_key_path" json:"private_key_path"` // private key file name
|
||||
// PEM-encoded certificates chain
|
||||
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:"-"`
|
||||
PrivateKeyData []byte `yaml:"-" json:"-"`
|
||||
|
||||
cert tls.Certificate // nolint(structcheck) - linter thinks that this field is unused, while TLSConfig is directly included into ServerConfig
|
||||
dnsNames []string // nolint(structcheck) // DNS names from certificate (SAN) or CN value from Subject
|
||||
cert tls.Certificate
|
||||
// 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.
|
||||
@@ -120,6 +134,7 @@ type ServerConfig struct {
|
||||
|
||||
FilteringConfig
|
||||
TLSConfig
|
||||
DNSCryptConfig
|
||||
TLSAllowUnencryptedDOH bool
|
||||
|
||||
TLSv12Roots *x509.CertPool // list of root CAs for TLSv1.2
|
||||
@@ -185,6 +200,13 @@ func (s *Server) createProxyConfig() (proxy.Config, error) {
|
||||
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
|
||||
if proxyConfig.UpstreamConfig == nil || len(proxyConfig.UpstreamConfig.Upstreams) == 0 {
|
||||
return proxyConfig, errors.New("no default upstream servers configured")
|
||||
@@ -252,14 +274,14 @@ func (s *Server) prepareUpstreamSettings() error {
|
||||
upstreams = filterOutComments(upstreams)
|
||||
upstreamConfig, err := proxy.ParseUpstreamsConfig(upstreams, s.conf.BootstrapDNS, DefaultTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DNS: proxy.ParseUpstreamsConfig: %s", err)
|
||||
return fmt.Errorf("dns: proxy.ParseUpstreamsConfig: %w", err)
|
||||
}
|
||||
|
||||
if len(upstreamConfig.Upstreams) == 0 {
|
||||
log.Info("Warning: no default upstream servers specified, using %v", defaultDNS)
|
||||
uc, err := proxy.ParseUpstreamsConfig(defaultDNS, s.conf.BootstrapDNS, DefaultTimeout)
|
||||
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
|
||||
}
|
||||
@@ -300,13 +322,13 @@ func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
|
||||
var err error
|
||||
s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
|
||||
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 {
|
||||
x, err := x509.ParseCertificate(s.conf.cert.Certificate[0])
|
||||
if err != nil {
|
||||
return errorx.Decorate(err, "x509.ParseCertificate(): %s", err)
|
||||
return fmt.Errorf("x509.ParseCertificate(): %w", err)
|
||||
}
|
||||
if len(x.DNSNames) != 0 {
|
||||
s.conf.dnsNames = x.DNSNames
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/util"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
@@ -1,3 +1,4 @@
|
||||
// Package dnsforward contains a DNS forwarding server.
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
@@ -8,13 +9,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/querylog"
|
||||
"github.com/AdguardTeam/AdGuardHome/stats"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/joomcode/errorx"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@@ -181,12 +181,9 @@ func (s *Server) Prepare(config *ServerConfig) error {
|
||||
s.conf.BlockingIPAddrv4 = net.ParseIP(s.conf.BlockingIPv4)
|
||||
s.conf.BlockingIPAddrv6 = net.ParseIP(s.conf.BlockingIPv6)
|
||||
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
|
||||
@@ -249,7 +246,7 @@ func (s *Server) stopInternal() error {
|
||||
if s.dnsProxy != nil {
|
||||
err := s.dnsProxy.Stop()
|
||||
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 +269,7 @@ func (s *Server) Reconfigure(config *ServerConfig) error {
|
||||
log.Print("Start reconfiguring the server")
|
||||
err := s.stopInternal()
|
||||
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.
|
||||
@@ -281,12 +278,12 @@ func (s *Server) Reconfigure(config *ServerConfig) error {
|
||||
|
||||
err = s.Prepare(config)
|
||||
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()
|
||||
if err != nil {
|
||||
return errorx.Decorate(err, "could not reconfigure the server")
|
||||
return fmt.Errorf("could not reconfigure the server: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -18,16 +18,21 @@ import (
|
||||
"testing"
|
||||
"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/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
const (
|
||||
tlsServerName = "testdns.adguard.com"
|
||||
testMessagesCount = 10
|
||||
@@ -751,11 +756,14 @@ func createTestServer(t *testing.T) *Server {
|
||||
c.CacheTime = 30
|
||||
|
||||
f := dnsfilter.New(&c, filters)
|
||||
|
||||
s := NewServer(DNSCreateParams{DNSFilter: f})
|
||||
s.conf.UDPListenAddr = &net.UDPAddr{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.FilteringConfig.ProtectionEnabled = true
|
||||
s.conf.ConfigModified = func() {}
|
||||
|
||||
err := s.Prepare(nil)
|
||||
assert.True(t, err == nil)
|
||||
return s
|
||||
@@ -815,12 +823,12 @@ func sendTestMessageAsync(t *testing.T, conn *dns.Conn, g *sync.WaitGroup) {
|
||||
req := createGoogleATestMessage()
|
||||
err := conn.WriteMsg(req)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot write message: %s", err)
|
||||
panic(fmt.Sprintf("cannot write message: %s", err))
|
||||
}
|
||||
|
||||
res, err := conn.ReadMsg()
|
||||
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)
|
||||
}
|
||||
@@ -916,20 +924,23 @@ func publicKey(priv interface{}) interface{} {
|
||||
}
|
||||
|
||||
func TestValidateUpstream(t *testing.T) {
|
||||
invalidUpstreams := []string{"1.2.3.4.5",
|
||||
invalidUpstreams := []string{
|
||||
"1.2.3.4.5",
|
||||
"123.3.7m",
|
||||
"htttps://google.com/dns-query",
|
||||
"[/host.com]tls://dns.adguard.com",
|
||||
"[host.ru]#",
|
||||
}
|
||||
|
||||
validDefaultUpstreams := []string{"1.1.1.1",
|
||||
validDefaultUpstreams := []string{
|
||||
"1.1.1.1",
|
||||
"tls://1.1.1.1",
|
||||
"https://dns.adguard.com/dns-query",
|
||||
"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",
|
||||
"[/www.host.com/]#",
|
||||
"[/host.com/google.com/]8.8.8.8",
|
||||
@@ -975,7 +986,8 @@ func TestValidateUpstreamsSet(t *testing.T) {
|
||||
assert.Nil(t, err, "comments should not be validated")
|
||||
|
||||
// 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",
|
||||
"[/www.host.com/]#",
|
||||
"[/host.com/google.com/]8.8.8.8",
|
||||
@@ -1029,9 +1041,7 @@ func (d *testDHCP) Leases(flags int) []dhcpd.Lease {
|
||||
l.Hostname = "localhost"
|
||||
return []dhcpd.Lease{l}
|
||||
}
|
||||
func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {
|
||||
return
|
||||
}
|
||||
func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {}
|
||||
|
||||
func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||
dhcp := &testDHCP{}
|
||||
@@ -1,12 +1,13 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/joomcode/errorx"
|
||||
|
||||
"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)
|
||||
if err != nil {
|
||||
// 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 {
|
||||
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)
|
||||
|
||||
} else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 {
|
||||
ctx.origQuestion = d.Req.Question[0]
|
||||
// resolve canonical name, not the original host name
|
||||
d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
|
||||
|
||||
} else if res.Reason == dnsfilter.RewriteEtcHosts && len(res.ReverseHost) != 0 {
|
||||
|
||||
} else if res.Reason == dnsfilter.RewriteEtcHosts && len(res.ReverseHosts) != 0 {
|
||||
resp := s.makeResponse(req)
|
||||
ptr := &dns.PTR{}
|
||||
ptr.Hdr = dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Rrtype: dns.TypePTR,
|
||||
Ttl: s.conf.BlockedResponseTTL,
|
||||
Class: dns.ClassINET,
|
||||
for _, h := range res.ReverseHosts {
|
||||
hdr := dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Rrtype: dns.TypePTR,
|
||||
Ttl: s.conf.BlockedResponseTTL,
|
||||
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
|
||||
} else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteEtcHosts {
|
||||
resp := s.makeResponse(req)
|
||||
@@ -136,7 +140,6 @@ func (s *Server) filterDNSResponse(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
} else if res.IsFiltered {
|
||||
d.Res = s.genDNSFilterMessage(d, &res)
|
||||
log.Debug("DNSFwd: Matched %s by response: %s", d.Req.Question[0].Name, host)
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/jsonutil"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/utils"
|
||||
"github.com/miekg/dns"
|
||||
@@ -17,236 +16,296 @@ import (
|
||||
|
||||
func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) {
|
||||
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)
|
||||
}
|
||||
|
||||
type dnsConfigJSON struct {
|
||||
Upstreams []string `json:"upstream_dns"`
|
||||
UpstreamsFile string `json:"upstream_dns_file"`
|
||||
Bootstraps []string `json:"bootstrap_dns"`
|
||||
type dnsConfig struct {
|
||||
Upstreams *[]string `json:"upstream_dns"`
|
||||
UpstreamsFile *string `json:"upstream_dns_file"`
|
||||
Bootstraps *[]string `json:"bootstrap_dns"`
|
||||
|
||||
ProtectionEnabled bool `json:"protection_enabled"`
|
||||
RateLimit uint32 `json:"ratelimit"`
|
||||
BlockingMode string `json:"blocking_mode"`
|
||||
BlockingIPv4 string `json:"blocking_ipv4"`
|
||||
BlockingIPv6 string `json:"blocking_ipv6"`
|
||||
EDNSCSEnabled bool `json:"edns_cs_enabled"`
|
||||
DNSSECEnabled bool `json:"dnssec_enabled"`
|
||||
DisableIPv6 bool `json:"disable_ipv6"`
|
||||
UpstreamMode string `json:"upstream_mode"`
|
||||
CacheSize uint32 `json:"cache_size"`
|
||||
CacheMinTTL uint32 `json:"cache_ttl_min"`
|
||||
CacheMaxTTL uint32 `json:"cache_ttl_max"`
|
||||
ProtectionEnabled *bool `json:"protection_enabled"`
|
||||
RateLimit *uint32 `json:"ratelimit"`
|
||||
BlockingMode *string `json:"blocking_mode"`
|
||||
BlockingIPv4 *string `json:"blocking_ipv4"`
|
||||
BlockingIPv6 *string `json:"blocking_ipv6"`
|
||||
EDNSCSEnabled *bool `json:"edns_cs_enabled"`
|
||||
DNSSECEnabled *bool `json:"dnssec_enabled"`
|
||||
DisableIPv6 *bool `json:"disable_ipv6"`
|
||||
UpstreamMode *string `json:"upstream_mode"`
|
||||
CacheSize *uint32 `json:"cache_size"`
|
||||
CacheMinTTL *uint32 `json:"cache_ttl_min"`
|
||||
CacheMaxTTL *uint32 `json:"cache_ttl_max"`
|
||||
}
|
||||
|
||||
func (s *Server) getDNSConfig() dnsConfig {
|
||||
s.RLock()
|
||||
upstreams := stringArrayDup(s.conf.UpstreamDNS)
|
||||
upstreamFile := s.conf.UpstreamDNSFileName
|
||||
bootstraps := stringArrayDup(s.conf.BootstrapDNS)
|
||||
protectionEnabled := s.conf.ProtectionEnabled
|
||||
blockingMode := s.conf.BlockingMode
|
||||
BlockingIPv4 := s.conf.BlockingIPv4
|
||||
BlockingIPv6 := s.conf.BlockingIPv6
|
||||
Ratelimit := s.conf.Ratelimit
|
||||
EnableEDNSClientSubnet := s.conf.EnableEDNSClientSubnet
|
||||
EnableDNSSEC := s.conf.EnableDNSSEC
|
||||
AAAADisabled := s.conf.AAAADisabled
|
||||
CacheSize := s.conf.CacheSize
|
||||
CacheMinTTL := s.conf.CacheMinTTL
|
||||
CacheMaxTTL := s.conf.CacheMaxTTL
|
||||
var upstreamMode string
|
||||
if s.conf.FastestAddr {
|
||||
upstreamMode = "fastest_addr"
|
||||
} else if s.conf.AllServers {
|
||||
upstreamMode = "parallel"
|
||||
}
|
||||
s.RUnlock()
|
||||
return dnsConfig{
|
||||
Upstreams: &upstreams,
|
||||
UpstreamsFile: &upstreamFile,
|
||||
Bootstraps: &bootstraps,
|
||||
ProtectionEnabled: &protectionEnabled,
|
||||
BlockingMode: &blockingMode,
|
||||
BlockingIPv4: &BlockingIPv4,
|
||||
BlockingIPv6: &BlockingIPv6,
|
||||
RateLimit: &Ratelimit,
|
||||
EDNSCSEnabled: &EnableEDNSClientSubnet,
|
||||
DNSSECEnabled: &EnableDNSSEC,
|
||||
DisableIPv6: &AAAADisabled,
|
||||
CacheSize: &CacheSize,
|
||||
CacheMinTTL: &CacheMinTTL,
|
||||
CacheMaxTTL: &CacheMaxTTL,
|
||||
UpstreamMode: &upstreamMode,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
resp := dnsConfigJSON{}
|
||||
s.RLock()
|
||||
resp.Upstreams = stringArrayDup(s.conf.UpstreamDNS)
|
||||
resp.UpstreamsFile = s.conf.UpstreamDNSFileName
|
||||
resp.Bootstraps = stringArrayDup(s.conf.BootstrapDNS)
|
||||
resp := s.getDNSConfig()
|
||||
|
||||
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.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 {
|
||||
bm := req.BlockingMode
|
||||
if !(bm == "default" || bm == "refused" || bm == "nxdomain" || bm == "null_ip" || bm == "custom_ip") {
|
||||
return false
|
||||
func (req *dnsConfig) checkBlockingMode() bool {
|
||||
if req.BlockingMode == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
bm := *req.BlockingMode
|
||||
if bm == "custom_ip" {
|
||||
ip := net.ParseIP(req.BlockingIPv4)
|
||||
if ip == nil || ip.To4() == nil {
|
||||
if req.BlockingIPv4 == nil || req.BlockingIPv6 == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ip = net.ParseIP(req.BlockingIPv6)
|
||||
if ip == nil {
|
||||
ip4 := net.ParseIP(*req.BlockingIPv4)
|
||||
if ip4 == nil || ip4.To4() == nil {
|
||||
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 checkBootstrap(addr string) error {
|
||||
if addr == "" { // additional check is required because NewResolver() allows empty address
|
||||
return fmt.Errorf("invalid bootstrap server address: empty")
|
||||
func (req *dnsConfig) checkUpstreamsMode() bool {
|
||||
if req.UpstreamMode == nil {
|
||||
return true
|
||||
}
|
||||
_, err := upstream.NewResolver(addr, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid bootstrap server address: %s", err)
|
||||
|
||||
for _, valid := range []string{
|
||||
"",
|
||||
"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) {
|
||||
req := dnsConfigJSON{}
|
||||
js, err := jsonutil.DecodeObject(&req, r.Body)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
||||
req := dnsConfig{}
|
||||
dec := json.NewDecoder(r.Body)
|
||||
if err := dec.Decode(&req); err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "json Encode: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if js.Exists("upstream_dns") {
|
||||
err = ValidateUpstreams(req.Upstreams)
|
||||
if err != nil {
|
||||
if req.Upstreams != nil {
|
||||
if err := ValidateUpstreams(*req.Upstreams); err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "wrong upstreams specification: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if js.Exists("bootstrap_dns") {
|
||||
for _, boot := range req.Bootstraps {
|
||||
if err := checkBootstrap(boot); err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "%s can not be used as bootstrap dns cause: %s", boot, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if errBoot, err := req.checkBootstrap(); err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "%s can not be used as bootstrap dns cause: %s", errBoot, err)
|
||||
return
|
||||
}
|
||||
|
||||
if js.Exists("blocking_mode") && !checkBlockingMode(req) {
|
||||
if !req.checkBlockingMode() {
|
||||
httpError(r, w, http.StatusBadRequest, "blocking_mode: incorrect value")
|
||||
return
|
||||
}
|
||||
|
||||
if js.Exists("upstream_mode") &&
|
||||
!(req.UpstreamMode == "" || req.UpstreamMode == "fastest_addr" || req.UpstreamMode == "parallel") {
|
||||
if !req.checkUpstreamsMode() {
|
||||
httpError(r, w, http.StatusBadRequest, "upstream_mode: incorrect value")
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
||||
restart := false
|
||||
s.Lock()
|
||||
|
||||
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 {
|
||||
if s.setConfig(req) {
|
||||
if err := s.Reconfigure(nil); err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "%s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) setConfig(dc dnsConfig) (restart bool) {
|
||||
s.Lock()
|
||||
|
||||
if dc.Upstreams != nil {
|
||||
s.conf.UpstreamDNS = *dc.Upstreams
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.UpstreamsFile != nil {
|
||||
s.conf.UpstreamDNSFileName = *dc.UpstreamsFile
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.Bootstraps != nil {
|
||||
s.conf.BootstrapDNS = *dc.Bootstraps
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.ProtectionEnabled != nil {
|
||||
s.conf.ProtectionEnabled = *dc.ProtectionEnabled
|
||||
}
|
||||
|
||||
if dc.BlockingMode != nil {
|
||||
s.conf.BlockingMode = *dc.BlockingMode
|
||||
if *dc.BlockingMode == "custom_ip" {
|
||||
s.conf.BlockingIPv4 = *dc.BlockingIPv4
|
||||
s.conf.BlockingIPAddrv4 = net.ParseIP(*dc.BlockingIPv4)
|
||||
s.conf.BlockingIPv6 = *dc.BlockingIPv6
|
||||
s.conf.BlockingIPAddrv6 = net.ParseIP(*dc.BlockingIPv6)
|
||||
}
|
||||
}
|
||||
|
||||
if dc.RateLimit != nil {
|
||||
if s.conf.Ratelimit != *dc.RateLimit {
|
||||
restart = true
|
||||
}
|
||||
s.conf.Ratelimit = *dc.RateLimit
|
||||
}
|
||||
|
||||
if dc.EDNSCSEnabled != nil {
|
||||
s.conf.EnableEDNSClientSubnet = *dc.EDNSCSEnabled
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.DNSSECEnabled != nil {
|
||||
s.conf.EnableDNSSEC = *dc.DNSSECEnabled
|
||||
}
|
||||
|
||||
if dc.DisableIPv6 != nil {
|
||||
s.conf.AAAADisabled = *dc.DisableIPv6
|
||||
}
|
||||
|
||||
if dc.CacheSize != nil {
|
||||
s.conf.CacheSize = *dc.CacheSize
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.CacheMinTTL != nil {
|
||||
s.conf.CacheMinTTL = *dc.CacheMinTTL
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.CacheMaxTTL != nil {
|
||||
s.conf.CacheMaxTTL = *dc.CacheMaxTTL
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.UpstreamMode != nil {
|
||||
switch *dc.UpstreamMode {
|
||||
case "parallel":
|
||||
s.conf.AllServers = true
|
||||
s.conf.FastestAddr = false
|
||||
case "fastest_addr":
|
||||
s.conf.AllServers = false
|
||||
s.conf.FastestAddr = true
|
||||
default:
|
||||
s.conf.AllServers = false
|
||||
s.conf.FastestAddr = false
|
||||
}
|
||||
}
|
||||
s.Unlock()
|
||||
s.conf.ConfigModified()
|
||||
return restart
|
||||
}
|
||||
|
||||
type upstreamJSON struct {
|
||||
Upstreams []string `json:"upstream_dns"` // Upstreams
|
||||
BootstrapDNS []string `json:"bootstrap_dns"` // Bootstrap DNS
|
||||
@@ -322,7 +381,7 @@ func separateUpstream(upstream string) (string, bool, error) {
|
||||
// split domains and upstream string
|
||||
domainsAndUpstream := strings.Split(strings.TrimPrefix(upstream, "[/"), "/]")
|
||||
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
|
||||
@@ -357,7 +416,7 @@ func checkPlainDNS(upstream string) error {
|
||||
|
||||
_, err = strconv.ParseInt(port, 0, 64)
|
||||
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
|
||||
@@ -405,7 +464,7 @@ func checkDNS(input string, bootstrap []string) error {
|
||||
// separate upstream from domains list
|
||||
input, defaultUpstream, err := separateUpstream(input)
|
||||
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
|
||||
@@ -414,17 +473,17 @@ func checkDNS(input string, bootstrap []string) error {
|
||||
}
|
||||
|
||||
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 {
|
||||
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})
|
||||
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{}
|
||||
@@ -435,18 +494,18 @@ func checkDNS(input string, bootstrap []string) error {
|
||||
}
|
||||
reply, err := u.Exchange(&req)
|
||||
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 {
|
||||
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 !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
|
||||
}
|
||||
|
||||
@@ -462,7 +521,7 @@ func (s *Server) handleDOH(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if !s.IsRunning() {
|
||||
httpError(r, w, http.StatusInternalServerError, "DNS server is not running")
|
||||
httpError(r, w, http.StatusInternalServerError, "dns server is not running")
|
||||
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}\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}\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}\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}\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}\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}\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}\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}\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}\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}\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}\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}\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}\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}\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"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/util"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
@@ -31,13 +31,13 @@ func (c *ipsetCtx) init(ipsetConfig []string) {
|
||||
it = strings.TrimSpace(it)
|
||||
hostsAndNames := strings.Split(it, "/")
|
||||
if len(hostsAndNames) != 2 {
|
||||
log.Debug("IPSET: invalid value '%s'", it)
|
||||
log.Debug("IPSET: invalid value %q", it)
|
||||
continue
|
||||
}
|
||||
|
||||
ipsetNames := strings.Split(hostsAndNames[1], ",")
|
||||
if len(ipsetNames) == 0 {
|
||||
log.Debug("IPSET: invalid value '%s'", it)
|
||||
log.Debug("IPSET: invalid value %q", it)
|
||||
continue
|
||||
}
|
||||
bad := false
|
||||
@@ -49,7 +49,7 @@ func (c *ipsetCtx) init(ipsetConfig []string) {
|
||||
}
|
||||
}
|
||||
if bad {
|
||||
log.Debug("IPSET: invalid value '%s'", it)
|
||||
log.Debug("IPSET: invalid value %q", it)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func (c *ipsetCtx) init(ipsetConfig []string) {
|
||||
host = strings.TrimSpace(host)
|
||||
host = strings.ToLower(host)
|
||||
if len(host) == 0 {
|
||||
log.Debug("IPSET: invalid value '%s'", it)
|
||||
log.Debug("IPSET: invalid value %q", it)
|
||||
continue
|
||||
}
|
||||
c.ipsetList[host] = ipsetNames
|
||||
@@ -131,7 +131,7 @@ func (c *ipsetCtx) process(ctx *dnsContext) int {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
log.Debug("IPSET: added %s(%s) -> %s", host, ipStr, name)
|
||||
@@ -1,12 +1,12 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@@ -46,7 +46,6 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
|
||||
if s.conf.BlockingMode == "null_ip" {
|
||||
// it means that we should return 0.0.0.0 or :: for any blocked request
|
||||
return s.makeResponseNullIP(m)
|
||||
|
||||
} else if s.conf.BlockingMode == "custom_ip" {
|
||||
// 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:
|
||||
return s.genAAAARecord(m, s.conf.BlockingIPAddrv6)
|
||||
}
|
||||
|
||||
} else if s.conf.BlockingMode == "nxdomain" {
|
||||
// means that we should return NXDOMAIN for any blocked request
|
||||
|
||||
return s.genNXDomain(m)
|
||||
|
||||
} else if s.conf.BlockingMode == "refused" {
|
||||
// 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 {
|
||||
|
||||
ip := net.ParseIP(newAddr)
|
||||
if ip != nil {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user